《word2vec Parameter Learning Explained》论文学习笔记
目录:
文章目录
- 1 Continuous Bag-of-Word Model
- 1.1 One-word context
- Update equation for hidden→output weights
- Update equation for input→hidden weights
- 1.2 Multi-word context
- 2 Skip-Gram Model
- 3 Optimizing Computational Efficiency
- 3.1 Hierarchical Softmax
- 3.2 Negative Sampling
由于word2vec模型学习生成的词向量表示方法能够携带句子的语义信息(semantic meanings),因此非常适用于多种NLP任务。
这篇论文详细地推导和解释了word2vec模型的参数更新公式,包括:CBOW(continuous bag-of-word)模型和SG(skip-gram)模型,以及两种参数优化技术:hierarchical softmax 和 negative sampling.
1 Continuous Bag-of-Word Model
1.1 One-word context
我们从CBOW模型的最简单版本开始介绍——One-word context。即我们假定context(预测目标单词的上下文信息)只有一个单词,也就是说One-word context 模型是在只要一个上下文单词(one context word)的情况下来预测一个目标单词(one target word)的。(注:对于初学神经网络的读者,建议先看完附录A之后,在回到此处阅读下文)。
如图1描述的就是One-word context定义之下的神经网络模型。这里我们假设文本词汇量的大小为V,隐藏层的大小为N,相邻层的神经元是全连接的。输入层是一个用one-hot方式编码的单词向量x=(x1,...,xV)\mathbf x=(x_1,...,x_V)x=(x1,...,xV),其中只有一个xix_ixi为1,其余均为0。
从输入层到 隐藏层的权重值可以用一个V×NV\times NV×N维的矩阵WWW来表示,即
W=(ω11ω12...ω1Nω21ω22...ω2N............ωV1ωV2...ωVN)W= \begin{pmatrix} \omega_{11}&\omega_{12}&...&\omega_{1N}\\ \omega_{21}&\omega_{22}&...&\omega_{2N}\\ ...&...&...&...\\ \omega_{V1}&\omega_{V2}&...&\omega_{VN} \end{pmatrix} W=⎝⎜⎜⎛ω11ω21...ωV1ω12ω22...ωV2............ω1Nω2N...ωVN⎠⎟⎟⎞
其中WWW矩阵的每一行代表的是一个与输入层相关的单词的N维向量表示形式vω\mathbf v_{\omega}vω。那么假设我们给定了一个输入单词(a context),其单词向量的第k个元素xk=1x_k=1xk=1,其余均为0,则有
h=WTx=W(k,∙)Txk=vωIT(1)\mathbf h= \mathbf W^Tx=\mathbf W_{(k,\bullet)}^T x_k=\mathbf v_{\omega_I}^T\tag{1}h=WTx=W(k,∙)Txk=vωIT(1)
从(1)式我们可以看出,hhh向量完全是从WWW矩阵第k行复制过来的(同vωI\mathbf v_{\omega_I}vωI均为N维向量)。vωI\mathbf v_{\omega_I}vωI即为输入单词ωI\omega_IωI的一种向量表示(其实就是输入向量,我们后面会提到)。
分析完输入层到隐藏层之后,我们再看隐藏层到输出层,同样连接权重用一个新的N × V矩阵W′={ωij′}\mathbf W'=\{\omega_{ij}' \}W′={ωij′}来表示如下:
W′=(ω11′ω12′...ω1V′ω21′ω22′...ω2V′............ωN1′ωN2′...ωNV′)\mathbf W'= \begin{pmatrix} \omega_{11}'&\omega_{12}'&...&\omega_{1V}'\\ \omega_{21}'&\omega_{22}'&...&\omega_{2V}'\\ ...&...&...&...\\ \omega_{N1}'&\omega_{N2}'&...&\omega_{NV}' \end{pmatrix} W′=⎝⎜⎜⎛ω11′ω21′...ωN1′ω12′ω22′...ωN2′............ω1V′ω2V′...ωNV′⎠⎟⎟⎞
通过这些权重,我们可以为词表中的每一个单词都计算出一个得分 μj\mu_jμj
μj=vωj′Th(2)\mu_j=\mathbf {v_{\omega_j}'}^T\mathbf h\tag{2}μj=vωj′Th(2)
其中,vωj′{v_{\omega_j}'}vωj′即为矩阵W′\mathbf W'W′的第j列向量(也是N维向量,其实就是单词w的输出向量,我们后面会提到)。
经过以上讨论之后,我们可以使用一种对数-线性分类模型softmax函数来计算单词的后验分布(是多项式分布)
p(ωj∣ωI)=yj=exp(μj)∑j′=1Vexp(μj′)(3)p(\omega_j|\omega_I)=y_j=\frac{\exp(\mu_j)}{\sum_{j'=1}^V\exp(\mu_{j'})}\tag{3}p(ωj∣ωI)=yj=∑j′=1Vexp(μj′)exp(μj)(3)
其中, yjy_jyj表示输出层第j个神经单元的输出值。将(1)式和(2)式代入(3)式我们可以得到:
p(ωj∣ωI)=exp(vωj′TvωI)∑j′=1Vexp(vωj′TvωI)(4)p(\omega_j|\omega_I)=\frac{\exp({\mathbf v_{\omega_j}'}^T \mathbf v_{\omega_I})}{\sum_{j'=1}^V\exp({\mathbf v_{\omega_j}'}^T \mathbf v_{\omega_I})}\tag{4}p(ωj∣ωI)=∑j′=1Vexp(vωj′TvωI)exp(vωj′TvωI)(4)
注意:正如前文所述,vω\mathbf v_\omegavω和vω′\mathbf v_\omega'vω′是单词的两种向量表示形式。其中vω\mathbf v_\omegavω实际上是权重矩阵W\mathbf WW(input->hidden)的某一行向量,vω′\mathbf v_\omega'vω′则是权重矩阵W′\mathbf W'W′(hidden->output)的某一列向量。我们将vω\mathbf v_\omegavω和vω′\mathbf v_\omega'vω′分别称为“输入向量(input vector)”和“输出向量(output vector)”(二者均为N维向量)。
Update equation for hidden→output weights
接下来让我们推到权重矩阵的更新公式,尽管在实际的计算过程中这样做是不切实际的(我们在之后再谈)。
在我们推导hidden→output权重的更新公式的过程中,需要用到神经网络的反向传播算法,对这部分内容不熟悉的读者可以参考附录A的内容。
由以上描述可知,该模型训练的目标就是求公式(4)的最大值。公式(4)代表的就是给定上下文信息(这里为一个单词ωI\omega_IωI)以及其权重矩阵的情况下,预测其实际输出单词(即上下文信息的中心词ωO\omega_OωO)的条件概率
maxp(ωO∣ωI)=maxyj∗=maxlogyj∗=μj∗−log∑j′=1Vexp(μj′):=−E(7)\begin{aligned} \max p(\omega_O|\omega_I) &=\max y_{j^*} \\ &=\max \log y_{j^*} \\ &=\mu_{j^*} - \log \sum_{j'=1}^V \exp(\mu_{j'}):=-E\tag{7} \end{aligned} maxp(ωO∣ωI)=maxyj∗=maxlogyj∗=μj∗−logj′=1∑Vexp(μj′):=−E(7)
其中,E=−logp(ωO∣ωI)E=-\log p(\omega_O|\omega_I)E=−logp(ωO∣ωI) 为该模型的损失函数(我们需要找出它的最小值),μj∗\mu_{j}^*μj∗的表示方式由公式(2)而来,j∗j^*j∗则为实际输出单词的索引下标。我们注意到该损失函数可以理解为一种特殊情形下的交叉熵计算。
现在我们开始推导从隐藏层到输出层的权重矩阵在模型训练过程中的参数更新公式。首先我们对损失函数E=−logp(ωO∣ωI)E=-\log p(\omega_O|\omega_I)E=−logp(ωO∣ωI) 求关于得分μj\mu_jμj的偏导数,得结果为:
∂E∂μj=yj−tj:=ej(8)\frac{\partial E}{\partial\mu_j}=y_j-t_j:=e_j\tag{8}∂μj∂E=yj−tj:=ej(8)
其中,tj=1(j=j∗)t_j=1(j=j^*)tj=1(j=j∗) ,即当且仅当输出层的第j个神经单元为真实的输出单词时 tjt_jtj的取值为1。接下来我们根据链式法则求出损失函数EEE关于矩阵W′W'W′元素 ωij′\omega_{ij}'ωij′的偏导数为:
∂E∂ωij′=∂E∂μj⋅∂μj∂ωij′=ej⋅hi(9)\frac{\partial E}{\partial \omega_{ij}'}=\frac{\partial E}{\partial \mu_j}\cdot \frac{\partial \mu_j}{\partial \omega_{ij}'}=e_j\cdot h_i \tag{9}∂ωij′∂E=∂μj∂E⋅∂ωij′∂μj=ej⋅hi(9)
因此,采用随机梯度下降算法(SGD),我们最终得到了隐藏层到输出层(hidden→\rightarrow→output)权重的更新公式如下:
ωij′(new)=ωij′(old)−η⋅ej⋅hi(10)\begin{aligned} {\omega_{ij}'}^{(new)}={\omega_{ij}'}^{(old)}-\eta \cdot e_j \cdot h_i\tag{10} \end{aligned} ωij′(new)=ωij′(old)−η⋅ej⋅hi(10)
or
vωj′(new)=vωj′(old)−η⋅ej⋅hforj=1,2,...V.(11)\begin{aligned} {\mathbf v_{\omega_j}'}^{(new)}= {\mathbf v_{\omega_j}'}^{(old)} - \eta \cdot e_j \cdot \mathbf h \space \space for\space j=1,2,...V.\tag{11} \end{aligned} vωj′(new)=vωj′(old)−η⋅ej⋅hforj=1,2,...V.(11)
其中, η>0\eta >0η>0为参数更新的学习速率;ej=yj−tje_j=y_j-t_jej=yj−tj;hih_ihi 为隐藏层的第i个神经单元;vωj′\mathbf v_{\omega_j}'vωj′为ωj\omega_jωj的输出向量。
由公式(11)我们可以看出:在更新权重参数的过程中,我们需要检查词汇表中的每一个单词,计算出它的输出概率yjy_jyj,并与期望输出tjt_jtj(取值只能为0或者1)进行比较。比较过程如下:
1)如果yj>tjy_j>t_jyj>tj(“overestimating”),那么就从向量vωj′\mathbf v_{\omega_j}'vωj′中减去隐藏向量h\mathbf hh的一部分(例如vωI\mathbf v_{\omega_I}vωI),这样向量vωj′\mathbf v_{\omega_j}'vωj′就会与向量vωI\mathbf v_{\omega_I}vωI相差更远。
2)如果yj<tjy_j<t_jyj<tj(“underestimating”,这种情况只有在tj=1t_j=1tj=1时,才会发生,此时ωj=ωO\omega_j=\omega_Oωj=ωO),则将隐藏向量hhh的一部分加入vωO′\mathbf v_{\omega_O}'vωO′,使得vωO′\mathbf v_{\omega_O}'vωO′与vωI\mathbf v_{\omega_I}vωI更接近。
3)如果yjy_jyj与tjt_jtj非常接近,则此时ej=yj−tje_j=y_j-t_jej=yj−tj由于(公式(8))非常接近于0,故更新参数基本上没什么变化。
这里需要再次提醒的是:vω\mathbf v_{\omega}vω和vω′\mathbf v_\omega 'vω′是单词ω\omegaω的两种不同的向量表示形式。
Update equation for input→hidden weights
在介绍完hidden→\rightarrow→output的权重矩阵更新公式之后,我们接着介绍input→\rightarrow→hidden的权重矩阵WWW的更新过程。我们继续对损失函数EEE求关于隐藏层hih_ihi 的偏导数,得:
∂E∂hi=∑j=1V∂E∂μj⋅∂μj∂hi=∑j=1Vej⋅ωij′:=EHi(12)\frac{\partial E}{\partial h_i}=\sum_{j=1}^V \frac{\partial E}{\partial \mu_j} \cdot \frac{\partial \mu_j}{\partial h_i}=\sum_{j=1}^V e_j \cdot \omega_{ij}':=EH_i \tag{12}∂hi∂E=j=1∑V∂μj∂E⋅∂hi∂μj=j=1∑Vej⋅ωij′:=EHi(12)
其中hih_ihi为隐藏层第i个神经单元的输出;μj\mu_jμj在公式(2)中已经定义,表示输出层第j个神经单元的输入;ej=yj−tje_j=y_j-t_jej=yj−tj为输出层第j个单词的预测误差。因此EH应该是一个N维向量,它的每一个元素代表的是词汇表中的每个单词的预测误差eje_jej与ωij′\omega_{ij}'ωij′在j=1到V上的乘积之和。
接下来,我们需要求出损失函数EEE关于权重矩阵WWW的偏导数。首先,分解公式(1),我们知道隐藏层激活单元的输出hih_ihi是输入层x\mathbf xx与权重的线性组合,即
hi=∑k=1Vxk⋅ωki(13)h_i=\sum_{k=1}^V x_k \cdot \omega_{ki} \tag{13}hi=k=1∑Vxk⋅ωki(13)
因此对于权重矩阵WWW的每一个元素,我们求关于EEE的偏导数,得到:
∂E∂ωki=∂E∂hi⋅∂hi∂ωki=EHi⋅xk(14)\frac{\partial E}{\partial \omega_{ki}}=\frac{\partial E}{\partial h_i} \cdot \frac{\partial h_i}{\partial \omega_{ki}}=EH_i \cdot x_k \tag{14}∂ωki∂E=∂hi∂E⋅∂ωki∂hi=EHi⋅xk(14)
因此我们利用张量乘积的方式,便可得到:
∂E∂W=x⊗EH=xEHT(15)\frac{\partial E}{\partial W}=\mathbf x \otimes EH = \mathbf xEH^T \tag{15}∂W∂E=x⊗EH=xEHT(15)
我们再次得到了一个V×NV\times NV×N的矩阵。由于x\mathbf xx向量只有一个非0元素,因此∂E∂W\frac{\partial E}{\partial W}∂W∂E 只有一行是N维非0向量EHTEH^TEHT,因此矩阵WWW的更新公式为:
vωI(new)=vωI(old)−η⋅EHT(16){\mathbf v_{\omega_I}}^{(new)}={\mathbf v_{\omega_I}}^{(old)}-\eta \cdot EH^T \tag{16}vωI(new)=vωI(old)−η⋅EHT(16)
其中vωI\mathbf v_{\omega_I}vωI是矩阵WWW的其中一行,是唯一的上下文单词(context word)的“输入向量”,也是矩阵WWW唯一的导数非0的行向量。 除了vωI\mathbf v_{\omega_I}vωI以外,矩阵WWW的其他行向量在参数更新迭代过程中都会保持不变(因为其导数为0)。
与矩阵W′W'W′的更新过程相似,对于公式(16),我们分析如下:
1)如果过高地估计了某个单词ωj\omega_jωj作为最终输出单词的概率(即:yj>tjy_j>t_jyj>tj),则上下文单词ωI\omega_IωI(context word )的输入向量与单词ωj\omega_jωj的输出向量在更新的过程中会相差越来越大。
2)如果相反,某个单词ωj\omega_jωj作为最终输出单词的概率被低估(即:yj<tjy_j<t_jyj<tj),则单词ωI\omega_IωI的输入向量与单词ωj\omega_jωj的输出向量在更新过程中会越来越接近。
3)如果对于单词ωI\omega_IωI的概率预测是准确的,则对于单词的输入向量在更新过程中几乎保持不变。
因此,上下文单词ωI\omega_IωI(context word )的输入向量的更新取决于词汇表中所有单词的预测误差。预测误差越大,则该单词对于上下文单词的输入向量的更新过程影响越大。
在介绍完One-word context的CBOW模型之后,我们接着介绍multi-word context下的CBOW模型。
1.2 Multi-word context
根据字面意思我们就可以看出,基于multi-word context的CBOW模型就是利用多个上下文单词来推测中心单词target word的一种模型。其结构如图2所示:
其隐藏层的输出值的计算过程为:首先将输入的上下文单词(context words)的向量叠加起来并取其平均值,接着与input→\rightarrow→hidden的权重矩阵相乘,作为最终的结果,公式如下:
h=1CWT(x1+x2+⋯+xC)=1C(vω1+vω2+⋯+vωC)T(18)\begin{aligned} \mathbf h &= \frac{1}{C} \mathbf W^T(\mathbf x_1 + \mathbf x_2 + \cdots +\mathbf x_C)\\ & = \frac{1}{C}(\mathbf v_{\omega_1}+\mathbf v_{\omega_2} + \cdots+\mathbf v_{\omega_C})^T\tag{18} \end{aligned} h=C1WT(x1+x2+⋯+xC)=C1(vω1+vω2+⋯+vωC)T(18)
其中CCC为上下文单词的个数,ω1,...,ωC\omega_1,...,\omega_Cω1,...,ωC为上下文单词,vω\mathbf v_\omegavω为单词ω\omegaω的输入向量。损失函数为:
E=−logp(ωO∣ωI,1,...,ωI,C)=−μj∗+log∑j′=1Vexp(μj′)=−vωO′T⋅h+log∑j′=1Vexp(vωj′T⋅h)(21)\begin{aligned} E &= - \log p(\omega_O|\omega_{I,1},...,\omega_{I,C})\\ & =- \mu_{j^*} + \log \sum_{j'=1}^{V} exp(\mu_{j'})\\ & = - {\mathbf v_{\omega_O}'}^T \cdot \mathbf h + \log \sum_{j'=1}^{V} \exp({\mathbf v_{\omega_j}'}^T \cdot \mathbf h)\tag{21} \end{aligned} E=−logp(ωO∣ωI,1,...,ωI,C)=−μj∗+logj′=1∑Vexp(μj′)=−vωO′T⋅h+logj′=1∑Vexp(vωj′T⋅h)(21)
同样,由hidden→\rightarrow→output的权重更新公式与one-word-context模型下的一模一样,即类似于公式(11),我们直接写在下面:
vωj′(new)=vωj′(old)−η⋅ej⋅hforj=1,2,...,V(22){\mathbf v_{\omega_j}'}^{(new)}={\mathbf v_{\omega_j}'}^{(old)}-\eta \cdot e_j \cdot \mathbf h \space \space \space for \space \space j=1,2,...,V\tag{22}vωj′(new)=vωj′(old)−η⋅ej⋅hforj=1,2,...,V(22)
由input→\rightarrow→hidden 的权重矩阵更新公式与公式(16)类似,只不过现在我们需要对每一个上下文单词ωI,c\omega_{I,c}ωI,c都执行如下更新公式:
vωI,c(new)=vωI,c(old)−1C⋅η⋅EHTforc=1,2,...,C.(23){\mathbf v_{\omega_{I,c}}}^{(new)}={\mathbf v_{\omega_{I,c}}}^{(old)} - \frac{1}{C}\cdot \eta \cdot EH^T \space \space for \space \space c=1,2,...,C.\tag{23}vωI,c(new)=vωI,c(old)−C1⋅η⋅EHTforc=1,2,...,C.(23)
其中vωI,c{\mathbf v_{\omega_{I,c}}}vωI,c为上下文context中第c 个单词的输入向量;η\etaη为正学习速率;EH=∂E∂hiEH=\frac{\partial E}{\partial h_i}EH=∂hi∂E由公式(12)给出。
2 Skip-Gram Model
与CBOW模型正好相反,Skip-Gram模型是根据中心单词(target word)来预测其上上下文信息(context words)。如图3所示,为Skip-Gram模型的结构示意图。
我们仍然使用vωI\mathbf v_{\omega_I}vωI来表示输入层上唯一的那个单词的输入向量,因此,我们对于隐藏层的输出值h\mathbf hh的计算公式与第一节公式(1)相同,表示如下:
h=W(k,∙)T:=vωI(24)\mathbf h = {\mathbf W}_{(k,\bullet)}^T := \mathbf v_{\omega_I}\tag {24}h=W(k,∙)T:=vωI(24)
公式(24)显示:h\mathbf hh向量其实就是input->hidden权重矩阵W\mathbf WW的某一行结合输入单词ωI\omega _IωI的向量拷贝。在输出层,与CBOW模型的输出为单个多项式分布不同的是,SG模型在输出层输出了C个多项式分布。每个输出都使用相同的hidden->output矩阵计算:
p(ωc,j=ωO,c∣ωI)=yc,j=exp(μc,j)∑j′=1Vexp(μj′)(25)p(\omega_{c,j}=\omega_{O,c}|\omega_I)=y_{c,j}=\frac{\exp(\mu_{c,j})}{\sum_{{j}'=1}^V \exp(\mu_{j}')}\tag{25}p(ωc,j=ωO,c∣ωI)=yc,j=∑j′=1Vexp(μj′)exp(μc,j)(25)
其中,ωc,j\omega_{c,j}ωc,j表示输出层的第c个panel的第j个单词(何为panel?就是输出层的表示每个上下文单词的神经元的组合,图中一种有C个context words,所以总共有C个panel);ωO,c\omega_{O,c}ωO,c实际上表示的是输出上下文单词(output context words)的第c个单词;ωI\omega_IωI是唯一的输入单词;yc,jy_{c,j}yc,j为输出层的第c个panel上的第j个神经单元的概率输出值;μc,j\mu_{c,j}μc,j表示的是输出层第c个panel的第j个神经元的输入值;由于输出层的所有panels共享同一权重矩阵W′\mathbf W{}'W′,因此:
μc,j=μj=vωj′T⋅h,forc=1,2,...,C(26)\mu_{c,j}=\mu_j={\mathbf v_{\omega_j}'}^T\cdot \mathbf h, \space for \space c=1,2,...,C\tag{26}μc,j=μj=vωj′T⋅h,forc=1,2,...,C(26)
其中,vωj′\mathbf v_{\omega_j}'vωj′为词汇表第j个单词ωj\omega_jωj的输出向量;同样,它也是取自于hidden→\rightarrow→output权重矩阵W′\mathbf W'W′的一列。
SG模型参数更新公式的推导过程与one-word-context 模型的推导过程大体上一样。这里我们将损失函数变为:
E=−logp(ωO,1,ωO,2,...,ωO,C∣ωI)=−log∏c=1Cexp(μc,jc∗)∑j′=1Vexp(μj′)=−∑c=1Cμjc∗+C⋅log∑j′=1Vexp(μj′)(29)\begin{aligned} E &=-\log p(\omega_{O,1},\omega_{O,2},...,\omega_{O,C}|\omega_I)\\ &=-\log \prod_{c=1}^C \frac{\exp(\mu_{c,j_c^*})}{\sum_{j'=1}^V \exp(\mu_{j'})}\\ &=-\sum_{c=1}^C \mu_{j_c^* }+C\cdot\log\sum_{j'=1}^V\exp(\mu_{j'})\tag{29} \end{aligned} E=−logp(ωO,1,ωO,2,...,ωO,C∣ωI)=−logc=1∏C∑j′=1Vexp(μj′)exp(μc,jc∗)=−c=1∑Cμjc∗+C⋅logj′=1∑Vexp(μj′)(29)
其中,jc∗j_c^*jc∗为第c个输出层输出的上下文单词在词汇表中的真实索引。
在得到损失函数EEE之后,我们对输出层的每一个panel上的所有激活单元的输入值μc,j\mu_{c,j}μc,j,均求其关于EEE的偏导数,得:
∂E∂μc,j=yc,j−tc,j:=ec,j(30)\frac{\partial E}{\partial \mu_{c,j}}=y_{c,j}-t_{c,j}:=e_{c,j}\tag {30}∂μc,j∂E=yc,j−tc,j:=ec,j(30)
其中ec,je_{c,j}ec,j为输出层神经元的预测误差,与公式(8)类似。为了简化符号,我们定义一个VVV维的向量EI={EI1,...,EIV}EI={\{EI_1,...,EI_V\}}EI={EI1,...,EIV}作为所有上下文单词的预测误差之和,EIjEI_jEIj用公式定义如下:
KaTeX parse error: \tag works only in display equations
接下来,我们计算hidden->output权重矩阵W′\mathbf W'W′关于EEE的偏导数为:
∂E∂ωij′=∑c=1C∂E∂μc,j⋅∂μc,j∂ωij′=EIj⋅hi(32)\frac{\partial E}{\partial \omega_{ij}'}=\sum_{c=1}^C\frac{\partial E}{\partial \mu_{c,j}}\cdot\frac{\partial \mu_{c,j}}{\partial \omega_{ij}'}=EI_j\cdot h_i\tag{32}∂ωij′∂E=c=1∑C∂μc,j∂E⋅∂ωij′∂μc,j=EIj⋅hi(32)
这样,我们就得到了hidden→\rightarrow→output权重矩阵W′\mathbf W'W′的参数更新公式为:
ωij′(new)=ωij′(old)−η⋅EIj⋅hi(33){\omega_{ij}^{'}}^{(new)}={\omega_{ij}^{'}}^{(old)}-\eta\cdot EI_j\cdot h_i\tag{33}ωij′(new)=ωij′(old)−η⋅EIj⋅hi(33)
或者
vωj′(new)=vωj′(old)−η⋅EIj⋅hforj=1,2,...,V.(34){\mathbf v_{\omega_j}'}^{(new)}={\mathbf v_{\omega_j}'}^{(old)}-\eta \cdot EI_j \cdot \mathbf h \space\space\space for \space j=1,2,...,V.\tag{34}vωj′(new)=vωj′(old)−η⋅EIj⋅hforj=1,2,...,V.(34)
上述参数更新公式的直观概念理解与上文公式(11)无二,除了一点就是:输出层的预测误差的计算是基于多个上下文单词context words,而不是单个目标单词 target word;需注意的是对于每一个训练样本,我们都要利用该参数更新公式来更新hidden→\rightarrow→output权重矩阵W′\mathbf W'W′的每个元素。
同样,对于input→\rightarrow→hidden权重矩阵W\mathbf WW的参数更新公式的推导过程,除了考虑要将预测误差eje_jej替换为EIjEI_jEIj外,其他也与上文公式(12)到公式(16)类似。这里我们直接给出更新公式:
vωI(new)=vωI(old)−η⋅EHT(35){\mathbf v_{\omega_I}}^{(new)}={\mathbf v_{\omega_I}}^{(old)}-\eta \cdot EH^T\tag{35}vωI(new)=vωI(old)−η⋅EHT(35)
其中,EHEHEH是一个NNN维向量,组成该向量的每一个元素可以用如下公式表示:
EHi=∑j=1VEIj⋅ωij′(36)EH_i=\sum_{j=1}^V EI_j\cdot\omega_{ij}'\tag{36}EHi=j=1∑VEIj⋅ωij′(36)
公式(36)的直观理解与公式(16)类似,这里不作描述。
3 Optimizing Computational Efficiency
总结以上的模型介绍,我们发现所有模型的词汇表中的每个单词都存在两个向量表示形式:输入向量vω\mathbf v_\omegavω与输出向量vω′\mathbf v_\omega'vω′.对于输入向量的参数学习成本并不高,但对于输出向量的学习成本代价是非常昂贵的。根据更新公式(22)和(23),我们可以发现,为了更新输出向量vω′\mathbf v_\omega 'vω′,对于每一个训练样例,我们必须迭代遍历词汇表中所有的单词ωj\omega_jωj,计算出它们的输入值μj\mu_jμj、概率预测值yjy_jyj(或者SG模型中的yc,jy_{c,j}yc,j),预测误差eje_jej(或者SG模型的EIjEI_jEIj)。最终使用预测误差更新它们的输出向量vj′\mathbf v_j'vj′.
显然,对于每一个训练样例都要对所有单词计算上述各值,其成本是昂贵的。特别是对于大型的词汇表,这种计算方式是不切实际的。因此为了解决这个问题,直观的方式是限制必须要更新的训练样例的输出向量的数目。一种有效的实现方式就是:hierarchical softmax(分层softmax),另一种实现通过采样的方式解决,我们在下个章节来讨论。
这两种方法都是通过只优化输出向量更新的计算过程来实现的。在我们的公式推导过程中,我们关心的有三个值:(1)EEE,新的目标函数;(2)∂E∂vω′\frac{\partial E}{\partial \mathbf v_\omega'}∂vω′∂E,新的关于输出向量的更新公式;(3)∂E∂h\frac{\partial E}{\partial \mathbf h}∂h∂E,为了更新输入向量反向传播的预测误差的加权和。
3.1 Hierarchical Softmax
Hierarchical softmax 是一种有效的计算 softmax 的方式。该模型使用一棵二叉树来表示词汇表中的所有单词。所有的VVV个单词都在二叉树的叶节点上。非叶子节点一共有V−1V-1V−1个。对于每个叶子节点,从根节点root到该叶子节点只有一条路径;这条路径用来评估用该叶子节点代表该叶子节点上单词的概率值。二叉树的结构如图4所示:
Figure 4: An example binary tree for the hierarchical softmax model.
其中白色的树节点代表的是词汇表中的单词,灰色节点为内部节点。图中高亮显示的是一条从根节点到ω2\omega_2ω2的路径。该条路径的长度为L(ω2)=4L(\omega_2)=4L(ω2)=4。n(ω,j)n(\omega,j)n(ω,j)表示从根节点到单词ω\omegaω 的路径上的第j个节点。
在hierarchical softmax模型中,所有的词汇单词没有输出向量表示形式。不同的是,二叉树的每一个内部节点都有一个输出向量vn(ω,j)′{\mathbf v_{n(\omega,j)}'}vn(ω,j)′。因此一个单词作为输出单词的概率计算公式定义如下:
p(ω=ωO)=∏j=1L(ω)−1σ([[n(ω,j+1)=ch(n(ω,j))]]⋅vn(w,j)′Th)(37)p(\omega = \omega_O)=\prod_{j=1}^{L(\omega)-1}\sigma \bigg(\Big[\Big[n\big(\omega,j+1\big)=ch\big(n\small(\omega,j\small)\big)\Big]\Big]\cdot{\mathbf v_{n(w,j)}'}^T\mathbf h\bigg)\tag{37}p(ω=ωO)=j=1∏L(ω)−1σ([[n(ω,j+1)=ch(n(ω,j))]]⋅vn(w,j)′Th)(37)
其中,ch(n)ch(n)ch(n)为节点nnn的左孩子节点;vn(ω,j)′\mathbf v_{n(\omega,j)}'vn(ω,j)′是内部节点n(ω,j)n(\omega,j)n(ω,j)的向量表示(输出向量);h\mathbf hh是隐藏层的输出值(在SG模型中,h=vωIh=\mathbf v_{\omega_I}h=vωI;而在CBOW模型中,h=1C∑c=1Cvωc\mathbf h=\frac{1}{C}\sum_{c=1}^C \mathbf v_{\omega_c}h=C1∑c=1Cvωc);[[x]][[x]][[x]]是一种特殊的函数定义如下:
[[x]]={1if xis true−1,otherwise(38)[[x]]= \begin{cases} 1 & \text{if $x$ is true} \\ -1, & \text{otherwise} \end{cases}\tag{38} [[x]]={1−1,ifxis trueotherwise(38)
接下来,我们通过一个直观地例子来理解公式(37)。如图4所示,假定我们需要计算单词ω2\omega_2ω2作为输出单词的概率。我们将这个概率定义为从根节点开始随机游走到叶节点ω2\omega_2ω2的概率。则在每一个内部节点(包括根节点),我们都需要确定其路径指向左孩子节点还是右孩子节点的概率。我们将经过内部节点的路径指向左孩子的概率定义为:
p(n,left)=σ(vn′T⋅h)(39)p(n,left)=\sigma({\mathbf v_n'}^T\cdot\mathbf h)\tag{39}p(n,left)=σ(vn′T⋅h)(39)
我们可以看出,公式(39)的值取决于内部节点的向量表示vn′\mathbf v_n'vn′和隐藏层的输出值h\mathbf hh(h\mathbf hh的值取决于输入单词的向量表示)。显然,内部节点的路径指向右孩子的概率则可以表示为:
p(n,right)=1−σ(vn′T⋅h)=σ(−vn′T⋅h)(40)p(n,right)=1-\sigma({\mathbf v_n'}^T\cdot\mathbf h)=\sigma(-{\mathbf v_n'}^T\cdot \mathbf h)\tag{40}p(n,right)=1−σ(vn′T⋅h)=σ(−vn′T⋅h)(40)
顺着图4中从根节点到单词ω2\omega_2ω2节点的路径,我们可以计算出ω2\omega_2ω2作为输出单词的概率为:
p(ω2=ωO)=p(n(ω2,1),left)⋅p(n(ω2,2),left)⋅p(n(ω2,3),right)=σ(vn(ω2,1)′Th)⋅σ(vn(ω2,2)′Th)⋅σ(−vn(ω2,3)′Th)⋅(42)\begin{aligned} p(\omega_2=\omega_O) &=p\Big(n(\omega_2,1),left\Big)\cdot p\Big(n(\omega_2,2),left\Big)\cdot p\Big(n(\omega_2,3),right\Big)\\ & =\sigma \Big({\mathbf v_{n(\omega_2,1)}'}^T\mathbf h\Big)\cdot\sigma \Big({\mathbf v_{n(\omega_2,2)}'}^T\mathbf h\Big)\cdot\sigma \Big(-{\mathbf v_{n(\omega_2,3)}'}^T\mathbf h\Big)\cdot \tag{42} \end{aligned} p(ω2=ωO)=p(n(ω2,1),left)⋅p(n(ω2,2),left)⋅p(n(ω2,3),right)=σ(vn(ω2,1)′Th)⋅σ(vn(ω2,2)′Th)⋅σ(−vn(ω2,3)′Th)⋅(42)
不难证明
∑i=1Vp(ωi=ωO)=1(43)\sum_{i=1}^{V}p(\omega_i=\omega_O)=1\tag{43}i=1∑Vp(ωi=ωO)=1(43)
现在我们开始推导内部节点的向量表示形式的参数更新公式。为了简化步骤,我们首先考虑单个上下文单词(one-word context)的模型。
为了简化公式,我们定义子公式的简化符号如下:
[[⋅]]:=[[n(ω,j+1)=ch(n(ω,j))]](44)[[\cdot]]:=[[n(\omega,j+1)=ch(n(\omega,j))]]\tag{44}[[⋅]]:=[[n(ω,j+1)=ch(n(ω,j))]](44)
vj′:=vnω,j′(45)\mathbf v_j':=\mathbf v_{n_{\omega,j}}'\tag{45}vj′:=vnω,j′(45)
则,给定一个训练样例,其误差函数我们可以定义如下:
E=−logp(ω=ωO∣ωI)=−∑j=1L(ω)−1logσ([[⋅]]vj′Th)(46)E=-\log p(\omega = \omega_O|\omega_I)=-\sum_{j=1}^{L(\omega)-1}\log\sigma([[\cdot]]{\mathbf v_j'}^T\mathbf h)\tag{46}E=−logp(ω=ωO∣ωI)=−j=1∑L(ω)−1logσ([[⋅]]vj′Th)(46)
对于误差函数EEE,我们取其关于vj′h\mathbf v_j'\mathbf hvj′h的偏导数,得:
∂E∂vj′h=(σ([[⋅]]vj′Th)−1)[[⋅]]={σ(vj′Th)−1,([[.]]=1)σ(vj′Th),([[.]]=-1)=σ(vj′Th)−tj(49)\begin{aligned} \frac{\partial E}{\partial \mathbf v_j'\mathbf h} &=\Big(\sigma([[\cdot]]{\mathbf v_j'}^T\mathbf h)-1\Big)[[\cdot]]\\ &= \begin{cases} \sigma({\mathbf v_j'}^T\mathbf h)-1 ,&\text{([[.]]=1)} \\ \sigma({\mathbf v_j'}^T\mathbf h),&\text {([[.]]=-1)} \end{cases}\\ &=\sigma({\mathbf v_j'}^T\mathbf h)-t_j\tag{49} \end{aligned} ∂vj′h∂E=(σ([[⋅]]vj′Th)−1)[[⋅]]={σ(vj′Th)−1,σ(vj′Th),([[.]]=1)([[.]]=-1)=σ(vj′Th)−tj(49)
其中tj=1t_j=1tj=1(如果[[⋅]]=1[[\cdot]]=1[[⋅]]=1)或者tj=0t_j=0tj=0(如果[[⋅]]=−1[[\cdot]]=-1[[⋅]]=−1)。
紧接着我们计算内部节点n(ω,j)n(\omega,j)n(ω,j)的向量表示vj′\mathbf v_j'vj′关于函数EEE的偏导数,得:
∂E∂vj′=∂E∂vj′h⋅∂vj′h∂vj′=(σ(vj′Th)−tj)⋅h(50)\frac{\partial E}{\partial \mathbf v_j'}=\frac{\partial E}{\partial \mathbf v_j'\mathbf h}\cdot \frac{\partial \mathbf v_j'\mathbf h}{\partial \mathbf v_j'}=\Big(\sigma({\mathbf v_j'}^T\mathbf h)-t_j\Big)\cdot \mathbf h\tag{50}∂vj′∂E=∂vj′h∂E⋅∂vj′∂vj′h=(σ(vj′Th)−tj)⋅h(50)
因此,更新公式为:
vj′(new)=vj′(old)−η(σ(vj′Th)−tj)⋅h,forj=1,2,...,L(ω)−1(51){\mathbf v_j'}^{(new)}={\mathbf v_j'}^{(old)}-\eta\Big(\sigma({\mathbf v_j'}^T\mathbf h)-t_j\Big)\cdot \mathbf h\space,\space for \space j=1,2,...,L(\omega)-1\tag{51}vj′(new)=vj′(old)−η(σ(vj′Th)−tj)⋅h,forj=1,2,...,L(ω)−1(51)
我们可以将σ(vj′Th)−tj\sigma({\mathbf v_j'}^T\mathbf h)-t_jσ(vj′Th)−tj理解为内部节点n(ω,j)n(\omega,j)n(ω,j)的预测误差。每一个内部节点的“任务”就是预测其随机游走路径是指向左孩子节点还是指向右孩子节点。tj=1t_j=1tj=1意味着节点n(ω,j)n(\omega,j)n(ω,j)的路径指向左孩子节点;tj=0t_j=0tj=0则表示指向右孩子节点。σ(vj′Th)\sigma({\mathbf v_j'}^T\mathbf h)σ(vj′Th)是预测结果。对于一个训练实例,如果内部节点的预测值非常接近于真实值,则它的向量表示$ \mathbf v_j’的更新变化很小;否则的更新变化很小;否则的更新变化很小;否则 \mathbf v_j’$向量指向一个适当的方向是的该实例的预测误差逐渐减小。以上更新公式既能应用于CBOW模型,又能应用于SG模型。当在SG模型中使用该更新公式时,我们需要对C个output context words的每一个单词都重复此更新过程。
为了使用反向传播该预测误差来学习训练input→\rightarrow→hidden的权重,我们对误差函数EEE求关于隐藏层输出值的偏导数,如下:
∂E∂h=∑j=1L(ω)−1∂E∂vj′h⋅∂vj′h∂h=∑j=1L(ω)−1(σ(vj′Th)−tj)⋅vj′:=EH(54)\begin{aligned} \frac{\partial E}{\partial \mathbf h} &=\sum_{j=1}^{L(\omega)-1}\frac{\partial E}{\partial \mathbf v_j'\mathbf h}\cdot\frac{\partial \mathbf v_j'\mathbf h}{\partial \mathbf h}\\ &=\sum_{j=1}^{L(\omega)-1}\Big(\sigma({\mathbf v_j'}^T\mathbf h)-t_j\Big)\cdot \mathbf v_j'\\ &:=EH\tag{54} \end{aligned} ∂h∂E=j=1∑L(ω)−1∂vj′h∂E⋅∂h∂vj′h=j=1∑L(ω)−1(σ(vj′Th)−tj)⋅vj′:=EH(54)
接下来我们根据公式(23)便可以获得CBOW模型输入向量的更新公式。对于SG模型,我们需要计算上下文信息中的每个单词的EHEHEH值,并将EH值的和带入公式(35),就能够得到输入向量的更新公式。
从以上更新公式我们可以看出:经过改进的模型Hierarchical softmax的每个训练样例的每个上下文单词的计算复杂度从O(V)O(V)O(V)降为O(log(V))O(log(V))O(log(V))级别。但是模型的参数几乎没有什么改变(内部节点对应V-1维向量,而原始模型的单词的输出向量维数为V)。
3.2 Negative Sampling
Negative Sampling模型的思想比hierarchical softmax模型更直接了当,即:在每次迭代的过程中,有大量的输出向量需要更新,为了解决这一困难,negative sampling提出了只更新其中一部分输出向量的解决方案。
显然,最终需要输出的上下文单词(正样本)在采样的过程中应该保留下来并更新,同时我们需要采集一些单词作为负样本(因此称为“negative sampling”)。在采样的过程中,我们可以任意选择一种概率分布。我们将这种概率分布称为“噪声分布”(the noise distribution),用Pn(ω)P_n(\omega)Pn(ω)来表示。我们可以根据经验选择一种较好的分布。
在 word2vec中,我们无需使用一种能够产生良好定义的后验多项式分布的负采样形式,本文作者证明了使用下面简单的训练目标函数能够产生可靠的、高质量的 word embeddings:
E=−logσ(vωO′Th)−∑ωj∈Wneglogσ(−vωj′Th)(55)E=-\log \sigma({\mathbf v_{\omega_O}'}^T\mathbf h)-\sum_{\omega_j\in W_{neg}} \log \sigma({-\mathbf v_{\omega_j}'}^T\mathbf h)\tag{55}E=−logσ(vωO′Th)−ωj∈Wneg∑logσ(−vωj′Th)(55)
其中ωO\omega_OωO是输出单词(the positive sample),vωO′\mathbf v_{\omega_O}'vωO′是输出向量;h\mathbf hh是隐藏层的输出值:在CBOW模型中h=1C∑c=1Cvωc\mathbf h=\frac{1}{C}\sum_{c=1}^{C} \mathbf v_{\omega_c}h=C1∑c=1Cvωc,在SG模型中h=vωI\mathbf h=\mathbf v_{\omega_I}h=vωI;Wneg={ωj∣j=1,...,K}W_{neg}={\{\omega_j|j=1,...,K\}}Wneg={ωj∣j=1,...,K}是基于分布Pn(ω)P_n(\omega)Pn(ω)采样的一系列单词。
为了获得negative sampling模型的词向量更新公式,我们首先计算E关于输出单元ωj\omega_jωj的输入vωj′Th{\mathbf v_{\omega_j}'}^T\mathbf hvωj′Th的偏导数:
∂E∂vωj′Th={σ(vωj′Th)−1,if ωj=ωOσ(vωj′Th),if ωj∈Wneg=σ(vωj′Th)−tj(57)\begin{aligned} \frac{\partial E}{\partial{ \mathbf v_{\omega_j}'}^T\mathbf h} &= \begin{cases} \sigma({\mathbf v_{\omega_j}'}^T\mathbf h)-1 ,&\text{if }\space \omega_j=\omega_O \\ \sigma({\mathbf v_{\omega_j}'}^T\mathbf h),&\text {if}\space\omega_j\in W_{neg} \end{cases}\\ &=\sigma({\mathbf v_{\omega_j}'}^T\mathbf h)-t_j\tag{57} \end{aligned} ∂vωj′Th∂E={σ(vωj′Th)−1,σ(vωj′Th),ifωj=ωOifωj∈Wneg=σ(vωj′Th)−tj(57)
其中,当ωj\omega_jωj是一个正样本时,tj=1t_j=1tj=1;否则tj=0t_j=0tj=0。接下来我们计算E关于单词ωj\omega_jωj的输出向量的偏导数:
∂E∂vωj′=∂E∂vωj′Th⋅∂vωj′Th∂vωj′=(σ(vωj′Th)−tj)h(58)\frac{\partial E}{\partial \mathbf v_{\omega_j}'}=\frac{\partial E}{\partial {\mathbf v_{\omega_j}'}^T\mathbf h}\cdot \frac{\partial {\mathbf v_{\omega_j}'}^T\mathbf h}{\partial {\mathbf v_{\omega_j}'}}=\Big(\sigma({\mathbf v_{\omega_j}'}^T \mathbf h)-t_j\Big)\mathbf h \tag{58}∂vωj′∂E=∂vωj′Th∂E⋅∂vωj′∂vωj′Th=(σ(vωj′Th)−tj)h(58)
因此输出向量的更新公式为:
vωj′(new)=vωj′(old)−η(σ(vωj′Th)−tj)h(59){\mathbf v_{\omega_j}'}^{(new)}={\mathbf v_{\omega_j}'}^{(old)}-\eta\Big(\sigma({\mathbf v_{\omega_j}'}^T\mathbf h)-t_j\Big)\mathbf h\tag{59}vωj′(new)=vωj′(old)−η(σ(vωj′Th)−tj)h(59)
negative sampling的关键就是公式(59)的更新过程只应用于词汇表的子集{ωj∣ωj∈{ωO}⋃Wneg}\{\omega_j|\omega_j\in \{\omega_O\}\bigcup W_{neg}\}{ωj∣ωj∈{ωO}⋃Wneg},而并非应用于整个词汇表。
以上更新公式(59)的直观理解与公式(11)类似。公式(59)对两种应用模型CBOW和SG都适用。对于SG模型,我们每次更新一个上下文单词。
接着利用反向传播机制,计算E关于隐藏层输出h\mathbf hh的偏导数:
∂E∂h=∑ωj∈{ωO}⋃Wneg∂E∂vωj′Th⋅∂vωj′Th∂h\begin{aligned} \frac{\partial E}{\partial \mathbf h} &=\sum_{\omega_j \in\{\omega_O\}\bigcup W_{neg}}\frac{\partial E}{\partial {\mathbf v_{\omega_j}'}^T\mathbf h}\cdot \frac{\partial {\mathbf v_{\omega_j}'}^T\mathbf h}{\partial \mathbf h}\\ \end{aligned} ∂h∂E=ωj∈{ωO}⋃Wneg∑∂vωj′Th∂E⋅∂h∂vωj′Th
=∑ωj∈{ωO}⋃Wneg(σ(vωj′Th)−tj)vωj′:=EH(61)\begin{aligned} &=\sum_{\omega_j \in\{\omega_O\}\bigcup W_{neg}}\Big(\sigma({\mathbf v_{\omega_j}'}^T\mathbf h)-t_j\Big)\mathbf v_{\omega_j}':=EH\tag{61} \end{aligned} =ωj∈{ωO}⋃Wneg∑(σ(vωj′Th)−tj)vωj′:=EH(61)
将EH代入公式(23),我们就可以得到CBOW模型关于输入向量的更新公式;对于SG模型,我们需要计算出每个上下文单词的EH值,将EH值的和代入公式(35)就能够得到其输入向量的更新公式。
PS:明人不说暗话,阁下留下一赞可好?
《word2vec Parameter Learning Explained》论文学习笔记相关推荐
- word2vec Parameter Learning Explained 论文学习笔记
文章内容 1.word2vec模型:skipgram和CBOW 2.训练优化:hierarchical softmax 和 negative sampling. 一.CBOW模型 CBOW完成的事情是 ...
- 《word2vec Parameter Learning Explained》论文笔记
word2vec Parameter Learning Explained Xin Rong(ronxin@umich.edu)[致敬] arXiv:1411.2738v4 [cs.CL] 5 Jun ...
- Word2vec Parameter Learning Explained (UMich 2016)
Word2vec Parameter Learning Explained (UMich 2016)
- word2vec Parameter Learning Explained学习笔记
目录 原因: 看了几篇提及CBOW(Continuous Bag-of-Word)的综述,都没直接看懂.综述中都指向这篇文章的这张图. word2vec是一个预训练(pretrained)模型,在大量 ...
- word2vec Parameter Learning Explained
由word2vec获得的词向量代表可以捕获语义信息 1. Continuous Bag-of-Word 模型 现在只考虑仅一个词的上下文,也就是模型在给定一个词的上下文下仅预测一个目标,这很像一元模型 ...
- word2vec Parameter Learning Explained(Hierarchical Softmax,Negative Sampling)
主要思想是转化为Huffman tree 的随机游走,叶子结点词描述的概率,不需要遍历所有的V,只需要log(V)的时间 主要思想是从噪声分布来采取一些negative 样本
- 识别和追踪主题层次的影响力者(来自2018 Machine Learning 论文学习笔记)
本文作者:合肥工业大学 管理学院 钱洋 email:1563178220@qq.com . 以下内容是个人的论文阅读笔记,内容可能有不到之处,欢迎交流. 未经本人允许禁止转载. 文章目录 论文来源 论 ...
- 论文学习笔记 POSEIDON: Privacy-Preserving Federated Neural Network Learning
论文学习笔记 POSEIDON: Privacy-Preserving Federated Neural Network Learning NDSS 2021录用文章 目录 论文学习笔记 POSEID ...
- 论文学习笔记: Learning Multi-Scale Photo Exposure Correction(含pytorch代码复现)
论文学习笔记: Learning Multi-Scale Photo Exposure Correction--含pytorch代码复现 本章工作: 论文摘要 训练数据集 网络设计原理 补充知识:拉普 ...
最新文章
- 【jquery】用jsp写jquery的模板
- linux终端运行pytorch,Linux虚拟机测试pytorch运行
- apache ab压力测试工具-批量压测脚本
- linux 查看端口被哪个程序占用
- 2009-12-12
- 3.产品成本在完工和在制产品间分配
- 互联网 DBA 需要做那些事(转)
- 人工智能让边缘计算更有价值!
- (2) 第二章 WCF服务与数据契约 服务契约详解(三)- [ServiceContract]特性
- Java:从 Map 到 HashMap 的一步步实现!
- ReactNative调研结果
- python打印对象地址_Python打印对象的全部属性
- [WebAPI] - 使用 Ajax 提交 HTML Form Data 到 WebAPI 的方法
- 成功要素:富兰克林的13条必要美德! 与 狗熊掰棒子
- 模拟器安装 xposed
- 自组织映射(SOM)聚类分析Python第三方库实现<minisom>
- A. Shifting Stacks
- 搭建简单windows版NAS
- 如何使用plsql连接远程数据库
- Codeforces Round #715 (Div. 2) B. TMT Document——思路分析,清晰易懂
热门文章
- 计算机vb里代码里的双引号,在VB中使用字符串中的左双引号
- AI算法之Encoder-Decoder 和 Seq2Seq
- 关于获取电信光猫超级密码
- 【机器学习】Python详细实现基于欧式Euclidean、切比雪夫Chebyshew、曼哈顿Manhattan距离的Kmeans聚类
- Python Data mining - Enron Email Dataset
- 计算机科学与技术哪个方向好就业前景,【计算机科学与技术】专业就业前景以及未来的就业方向...
- 【传统CV】Anovel adaptive morphological approach for degraded character image segmentation
- (Modern Family S01E02) Part 6 JayGloria Jay给Manny修风扇
- 首页被绑定为www.7939.com的手工解除方法
- 什么是SaaS系统,SaaS系统有哪些优势