关于word2vec的一些相关问题整理 & 思考

1.简述word2vec基本思想,并简要描述CBOW和Skip-gram模型

word2vec的基本思想是一个词的意思, 可以由这个词的上下文来表示。 相似词拥有相似的上下文, 这也就是所谓的离散分布假设(distributional hypothesis),论文中的做法是通过神经语言模型训练每个词并将其映射成k维实值向量(k一般为模型中的超参数),在高维空间中可以通过词之间的距离来判断语义相似度。

CBOW(Continuous Bag of Words,连续词袋模型)采用给定上下文信息来预测一个词的方法来训练神经网络,而Skip-gram与 CBOW 相反, 其采用给定一个词来预测上下文的方法来训练神经网络。CBOW模型直观上很好理解, 而Skip-gram模型类似于给你若干个词,让你扩展成一句话。另一个相关的模型是训练句子向量用的 skip-thought,给定一个句子,去生成前一个句子和后一个句子。

1.1 word2vec是如何训练模型的,用的什么语料库,如何处理扩充的语料?

1.2 什么情况用CBOW,什么情况用Skip-gram?

1.3 说明word2vec两种训练方式各自的优势是什么?哪种更好,为什么?

2.简述word2vec中的两个trick,这两个trick的作用是什么?

  • 第一个trick是hierarchical softmax,其作用是为了近似模拟softmax计算;
  • 第二个trick是negative sampling,它是用来提高训练速度并且改善所得到词向量的质量的一种方法。不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。

2.1. 为什么要做层次softmax?

从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值,其计算复杂度为O(V)O(V)VV是词表总长度,而从数据结构角度来思考,一般查找的问题可以优化到O(log2V)O(log_2V),因此论文设计了层次softmax来近似模拟softmax计算,将计算复杂度降至O(log2V)O(log_2V)

2.2 hierarchical softmax的哈弗曼树怎么构造的?叶子结点,根结点,非叶子结点都代表了什么?训练过程是怎么样的?

(1). hierarchical softmax的哈弗曼树怎么构造的?

hierarchical softmax的哈弗曼树是根据单词在语料库中的词频来构造的,哈夫曼树的构造过程与数据结构中哈夫曼树的构造过程一致。假设词表中有n个单词,则构造出来的哈夫曼树有n个叶子结点,假设这n个单词的词频分别为w1,w2,,wnw_1,w_2,…,w_n

  1. w1,w2,,wnw_1,w_2,…,w_n当做𝑛棵树(每棵树1个结点)组成的森林。
  2. 选择根结点权值最小的两棵树,合并,获得一棵新树,且新树的根结点权值为其左、右子树根结点权值之和。词频大的结点作为左孩子结点,词频小的作为右孩子结点。
  3. 从森林中删除被选中的树,保留新树。
  4. 重复2、3步,直至森林中只剩下一棵树为止。

(2). 叶子结点,根结点,非叶子结点都代表了什么?

叶子结点代表词表中的每一个单词,根结点代表投影后的词向量,即隐藏层输出的向量,非叶子结点代表从根结点到某个单词路径上的中间结点,在数据结构中的含义是其所有子树对应结点之和。

(3). hierarchical softmax的训练过程是怎么样的?

hierarchical softmax建立哈夫曼树的核心在于,将多分类问题,拆解成多个二分类问题。 简单示例如下:

img

如上图所示,我们可以沿着哈夫曼树从根节点一直走到我们的叶子结点的词w2w_2。与神经网络模型相比,我们的哈夫曼树的所有内部节点就类似之前神经网络隐藏层的神经元。其中,根结点的词向量对应我们投影后的词向量,而所有叶子结点就类似于之前神经网络softmax输出层的神经元,叶子结点的个数就是词汇表的大小。在哈夫曼树中,隐藏层到输出层的softmax映射不是一下子完成的,而是沿着霍夫曼树一步步完成的,这也是hierarchical softmax取名的由来。

以CBOW模型为例,在word2vec原论文中,采用了二元逻辑回归的方法,即**规定沿着左子树走,那么就是负类(哈夫曼树编码为1),沿着右子树走,那么就是正类(哈夫曼树编码0)。**判别正类和负类的方法是使用sigmoid函数,即为:
σ(xwθ)=11+exwθ \sigma\left(\mathbf{x}_{w}^{\top} \theta\right)=\frac{1}{1+e^{-\mathbf{x}_{w}^{\top} \theta}}
其中xw\mathbf{x}_{w}是当前内部结点的词向量,而θ\theta则是我们需要通过训练样本训练得到的逻辑回归的模型参数。

根据二叉树的特性,对于词典 D\mathcal{D} 中的任意词ww,Huffman树中必存在一条从根结点到词ww对应结点的路径pwp^w(且这条路径是唯一的)。路径pwp^w上存在(lw1l^w - 1个分支,将每个分支看做一次二分类,每一次分类就产生一个概率,将这些概率乘起来,就是所需的p(wContext(w))p( w|Context(w))

故在这种建模方式下,条件概率p(wContext(w))p( w|Context(w))的公式可以拆解为:
p(wContext(w))=j=2lwp(djwxw,θj1w) p(w | \text {Context}(w))=\prod_{j=2}^{l^{w}} p\left(d_{j}^{w} | \mathbf{x}_{w}, \theta_{j-1}^{w}\right)
其中

  • djw{0,1},2jlwd_{j}^{w} \in\{0,1\}, 2 \leq j \leq l^w表示路径pwp^w中第jj个结点对应的编码(根结点不对应编码),也就相当于上述图示中的黑色边;
  • xw\mathbf{x}_{w}表示隐藏层输出的向量,也就是根结点对应的词向量;
  • θjwRm,1jlw1\theta_{j}^{w} \in \mathbb{R}^{m}, 1 \leq j \leq l^w - 1表示路径pwp^w中第jj非叶子结点对应的参数向量。


p(djwxw,θj1w)={σ(xwθj1w),djw=01σ(xwθj1w),djw=1 p\left(d_{j}^{w} | \mathbf{x}_{w}, \theta_{j-1}^{w}\right)=\left\{\begin{array}{ll} {\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right),} & {d_{j}^{w}=0} \\ {1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right),} & {d_{j}^{w}=1} \end{array}\right.
考虑到dd只有0和1两种取值,我们可以用指数形式方便地将其写到一起:
p(djwxw,θj1w)=[σ(xwθj1w)]1djw[1σ(xwθj1w)]djw p\left(d_{j}^{w} | \mathbf{x}_{w}, \theta_{j-1}^{w}\right)=\left[\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]^{1-d_{j}^{w}} \cdot\left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]^{d_{j}^{w}}
再对条件概率(目标函数)取对数似然,则有:
L=wClogj=2lw{[σ(xwθj1w)]1djw[1σ(xwθj1w)]djw}=wCj=2lw{(1djw)log[σ(xwθj1w)]+djwlog[1σ(xwθj1w)]} \begin{aligned} \mathcal{L} &=\sum_{w \in \mathcal{C}} \log \prod_{j=2}^{l^{w}}\left\{\left[\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]^{1-d_{j}^{w}} \cdot\left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]^{d_{j}^{w}}\right\} \\ &=\sum_{w \in \mathcal{C}} \sum_{j=2}^{l^{w}}\left\{\left(1-d_{j}^{w}\right) \cdot \log \left[\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]+d_{j}^{w} \cdot \log \left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]\right\} \end{aligned}
其中 C\mathcal{C} 表示语料库,为方便梯度推导,将上式中双重求和符号下花括号中的内容简记为L(w,j)\mathcal{L}(w, j),即为:
L(w,j)=(1djw)log[σ(xwθj1w)]+djwlog[1σ(xwθj1w)] \mathcal{L}(w, j) = \left(1-d_{j}^{w}\right) \cdot \log \left[\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]+d_{j}^{w} \cdot \log \left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]
上式则为CBOW模型的目标函数,因此下面的目标则是如何将这个函数最大化,即最大化似然函数。word2vec中采用的是随机梯度上升法

随机梯度上升法的做法是:每取一个样本(Context(w),w)(Context(w), w),就对目标函数中的所有(相关)参数做一次刷新。观察目标函数 L\mathcal{L} 易知,该函数中的参数包括向量xwθj1wwCj=2,...,lwx_w,\theta_{j-1}^w,w \in \mathcal{C},j = 2, ... ,l^w。因此,先给出函数L(w,j)\mathcal{L}(w, j)关于这些向量的梯度。

  • 考虑L(w,j)\mathcal{L}(w, j)关于θj1w\theta_{j-1}^w的梯度:
    L(w,j)θj1w=θj1w{(1djw)log[σ(xwθj1w)]+djwlog[1σ(xwθj1w)]}=(1djw)[1σ(xwθj1w)]xwdjwσ(xwθj1w)xw     (logsigmod)={(1djw)[1σ(xwθj1w)]djwσ(xwθj1w)}xw     ()=[1djwσ(xwθj1w)]xw \begin{aligned} \frac{\partial \mathcal{L}(w, j)}{\partial \theta_{j-1}^{w}} &=\frac{\partial}{\partial \theta_{j-1}^{w}}\left\{\left(1-d_{j}^{w}\right) \cdot \log \left[\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]+d_{j}^{w} \cdot \log \left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]\right\} \\ &=\left(1-d_{j}^{w}\right)\left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right] \mathbf{x}_{w}-d_{j}^{w} \sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right) \mathbf{x}_{w} \ \ \ \ \ (利用log形式的sigmod函数的导函数)\\ &=\left\{\left(1-d_{j}^{w}\right)\left[1-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right]-d_{j}^{w} \sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right\} \mathbf{x}_{w} \ \ \ \ \ (合并同类项)\\ &=\left[1-d_{j}^{w}-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right] \mathbf{x}_{w} \end{aligned}

    • θj1w\theta_{j-1}^w的更新公式为:
      θj1w:=θj1w+η[1djwσ(xwθj1w)]xw \theta_{j-1}^{w}:=\theta_{j-1}^{w}+\eta\left[1-d_{j}^{w}-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right] \mathbf{x}_{w}
      其中η\eta表示学习率
  • 考虑L(w,j)\mathcal{L}(w, j)关于xw\mathbf{x}_{w}的梯度,L(w,j)\mathcal{L}(w, j)中关于变量xwx_wθj1w\theta_{j-1}^w对称的(即两者可交换位置),故:
    L(w,j)xw=[1djwσ(xwθj1w)]θj1w \frac{\partial \mathcal{L}(w, j)}{\partial \mathbf{x}_{w}}=\left[1-d_{j}^{w}-\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j-1}^{w}\right)\right] \theta_{j-1}^{w}
    不过 xw\mathbf{x}_{w} 是上下文的词向量的和,不是上下文单个词的词向量。怎么把这个更新量应用到单个词的词向量上去呢?word2vec采取的是直接将 xw\mathbf{x}_{w} 的更新量整个应用到每个单词的词向量上去,这个也很好理解,既然xw\mathbf{x}_{w}本身就是Context(w)Context(w)中各个单词词向量的累加,那么求完梯度之后也应该将其贡献到每个分量上去:
    v(w~):=v(w~)+ηj=2lwL(w,j)xw,w~ Context (w) \mathrm{v}(\widetilde{w}):=\mathrm{v}(\widetilde{w})+\eta \sum_{j=2}^{l^{w}} \frac{\partial \mathcal{L}(w, j)}{\partial \mathrm{x}_{w}}, \quad \widetilde{w} \in \text { Context }(w)
    其中v(w~)\mathrm{v}(\widetilde{w})代表上下文中某一个单词的词向量。考虑到每个单词的上下文单词数量可能不太一致,需要思考的是,这里是否采用平均贡献更合理?即使用公式:
    v(w~):=v(w~)+ηContext(w)j=2lwL(w,j)xw,w~ Context (w) \mathbf{v}(\widetilde{w}):=\mathbf{v}(\widetilde{w})+\frac{\eta}{| \text {Context}(w) |} \sum_{j=2}^{l^{w}} \frac{\partial \mathcal{L}(w, j)}{\partial \mathbf{x}_{w}}, \quad \widetilde{w} \in \text { Context }(w)
    其中Context(w)| \text {Context}(w) |表示Context(w)\text {Context}(w)中词的个数。其实**“平均贡献”**与word2vec中的公式,仅仅只是学习率的不同,因此在数据量充分的情况下也没有很大的区别。

    以样本(Context(w),w)(Context(w), w)为例,给出CBOW模型中采用梯度上升法更新各个参数的伪代码:
    ![](https://img-blog.csdnimg.cn/20200331094614110.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpdWppYW4yMDE1MDgwOA==,size_16,color_FFFFFF,t_70
    注意,步骤3.3和步骤3.4不能交换次序,即θj1w\theta_{j-1}^w应等贡献到e后再做更新。如果梯度收敛,则结束梯度迭代,当2c(c表示窗口大小)个xix_i词向量和中心词所对应的参数 θ\theta 的变化量分别小于某一个阈值,即停止更新,

对于Skip-gram模型,其推导过程与CBOW模型基本一致,其目标函数相对于CBOW模型,多了一层求和uContext(w)\sum_{u \in Context(w)} ,因此,言简意赅的说明Hierarchical Softmax是如何训练的,可总结如下:

利用哈夫曼树(最优二叉树)将多分类问题拆解成多个二分类问题,即将条件概率p(wContext(w))p(w | \text {Context}(w))拆解为:
p(wContext(w))=j=2lwp(djwxw,θj1w) p(w | \text {Context}(w))=\prod_{j=2}^{l^{w}} p\left(d_{j}^{w} | \mathbf{x}_{w}, \theta_{j-1}^{w}\right)
其中向量xwθj1wwCj=2,...,lwx_w,\theta_{j-1}^w,w \in \mathcal{C},j = 2, ... ,l^w为待更新的参数,利用极大似然的方法获得待优化的目标函数,之后再利用随机梯度上升法更新参数。

(4).哈夫曼树二分类,左右子树怎么分的,为什么左为负1,右为正0,能不能换?

关于Huffman树和Hufman编码,有两个约定:

(1) 将权值大的结点作为左孩子结点,权值小的作为右孩子结点;

(2) 左孩子结点编码为1, 右孩子结点编码为0。

在word2vec源码中将权值较大的孩子结点编码为1,较小的孩子结点编码为0。

这个只是一个约定,方便之后计算概率,也可以约定左边为0,右边为1,推导过程都是类似的。

(5). 最终所有叶子结点的概率和是否等于1?

用数学归纳法证明,以三层满二叉树为例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k963VIqA-1585618808649)(/Users/adherer/Desktop/找工作相关/找工作基础知识复习整理/NLP相关基础/word2vec相关资料/概率为1.png)]
其所有叶结点的概率之和为:
p11(p21+p22)+p12(p23+p24)=p11+p12=1 p_{11}* (p_{21} + p_{22}) + p_{12}* (p_{23} + p_{24}) = p_{11} + p_{12} = 1
当二叉树扩展到n层时,计算方式也是一样的,其本质上就相当于根结点往左右孩子结点的概率的连乘“裂变”。一个具体例子如下:

在这里插入图片描述
(6). 为什么要用哈夫曼树?有什么好处?

使用哈夫曼树的好处在于:

  • 降低了计算量,平均计算复杂度从O(V)O(|V|)降到了O(log(V))O(log(|V|)),其中V|V|为词表长度(其实这个严格来说不算是哈夫曼树的优点,但是用了哈夫曼树确实降低了计算复杂度);
  • 利用哈夫曼树可以很方便的按照词频构造的,因此高频词可以在较短时间内找到(本质上是哈夫曼树可以构成“优先队列”,这样,按照词频降序建立哈夫曼树,保证了高频词接近根结点,这样高频词计算少,低频词计算多,贪心优化思想)。

(7). hierarchical softmax的缺点是什么?

遇到生僻词的时候,在哈夫曼树中也要走很久,计算量也很大。

2.3 负采样的基本思想是什么?负采样的模型参数是如何更新的?其采样方法是怎么样的?

(1). 负采样的基本思想是什么?

比如我们有一个训练样本,中心词是ww,它周围上下文共有2c2c(c为窗口大小)个词,记为context(w)context(w),那么就得到一个正例的训练样本(context(w),w)(context(w), w),通过Negative Sampling采样,我们得到neg个和ww不同的中心词wi,i=1,2,,negw_i, i=1, 2, \cdots, neg,这样context(w)context(w)wiw_i就组成了neg个并不真实存在的负例。利用这一个正例和neg个负例,我们进行二元逻辑回归,得到负采样对应每个词wiw_i对应的模型参数θi\theta_i,和每个词的词向量。

(2). 负采样的模型参数是如何更新的?

Negative Sampling也是采用了二元逻辑回归来求解模型参数,通过负采样,我们得到了neg个负例(context(w),wi)  i=1,2,,neg(context(w), w_i)\ \ i=1, 2, \cdots, neg。为了统一描述,我们将正例定义为(context(w),w0)(context(w), w_0)。下面以CBOW模型为例简单讲解一下。

在逻辑回归中,我们的正例应该期望满足:
P(context(w0),wi)=σ(xw0Tθwi),yi=1,i=0 P(context(w_0), w_i) = \sigma(x_{w_0}^T\theta^{w_i}) ,y_i=1, i=0
我们的负例期望满足:
P(context(w0),wi)=1σ(xw0Tθwi),yi=0,i=1,2,..neg P(context(w_0), w_i) =1- \sigma(x_{w_0}^T\theta^{w_i}), y_i = 0, i=1,2,..neg
根据极大似然,我们希望最大化:
i=0negP(context(w0),wi)=σ(xw0Tθw0)i=1neg(1σ(xw0Tθwi)) \prod_{i=0}^{neg}P(context(w_0), w_i) = \sigma(x_{w_0}^T\theta^{w_0})\prod_{i=1}^{neg}(1- \sigma(x_{w_0}^T\theta^{w_i}))
由于其标签只有0,1,因此上式可以写成:
g(w)=i=0negσ(xw0Tθwi)yi(1σ(xw0Tθwi))1yi g(w) = \prod_{i=0}^{neg} \sigma(x_{w_0}^T\theta^{w_i})^{y_i}(1- \sigma(x_{w_0}^T\theta^{w_i}))^{1-y_i}
因此,对于一个给定的语料库 C\mathcal{C} ,我们可以得到整体的优化目标为:
G=wCg(w) G = \prod_{w \in \mathcal{C}} g(w)
此时对应的对数似然函数(目标函数)为:
L=logG=logwCg(w)=wClogg(w)=wClogi=0negσ(xw0Tθwi)yi(1σ(xw0Tθwi))1yi=wCi=0negyilog(σ(xw0Tθwi))+(1yi)log(1σ(xw0Tθwi)) \begin{aligned} \mathcal{L} &=\log G=\log \prod_{w \in \mathcal{C}} g(w)=\sum_{w \in \mathcal{C}} \log g(w) \\ &=\sum_{w \in \mathcal{C}} \log \prod_{i=0}^{neg} \sigma(x_{w_0}^T\theta^{w_i})^{y_i}(1- \sigma(x_{w_0}^T\theta^{w_i}))^{1-y_i} \\ &=\sum_{w \in \mathcal{C}} \sum\limits_{i=0}^{neg}y_i log(\sigma(x_{w_0}^T\theta^{w_i})) + (1-y_i) log(1- \sigma(x_{w_0}^T\theta^{w_i})) \end{aligned}
和hierarchical softmax类似,负采样同样采用随机梯度上升法更新参数,目标函数 L\mathcal{L} 中的参数包括向量xw0θwii=0,1,2,,negx_{w_0},\theta^{w_i},i=0, 1, 2, \cdots, neg。因此,先给出函数L\mathcal{L}关于这些向量的梯度。

  • 首先我们计算θwi\theta^{w_i}的梯度:

Lθwi=yi(1σ(xw0Tθwi))xw0(1yi)σ(xw0Tθwi)xw0=(yiσ(xw0Tθwi))xw0 \begin{aligned} \frac{\partial L}{\partial \theta^{w_i} } &= y_i(1- \sigma(x_{w_0}^T\theta^{w_i}))x_{w_0}-(1-y_i)\sigma(x_{w_0}^T\theta^{w_i})x_{w_0} \\ & = (y_i -\sigma(x_{w_0}^T\theta^{w_i})) x_{w_0} \end{aligned}

  • 之后按照同样的方法计算xw0x_{w_0}的梯度如下:

Lxw0=i=0neg(yiσ(xw0Tθwi))θwi \frac{\partial L}{\partial x_{w_0} } = \sum\limits_{i=0}^{neg}(y_i -\sigma(x_{w_0}^T\theta^{w_i}))\theta^{w_i}

之后与hierarchical softmax类似,可得v(w~),w~inContext(w)\mathrm{v}(\widetilde{w}), \widetilde{w} in Context(w)的更新公式为:
v(w~):=v(w~)+ηj=2lwL(w,j)xw,w~ Context (w) \mathrm{v}(\widetilde{w}):=\mathrm{v}(\widetilde{w})+\eta \sum_{j=2}^{l^{w}} \frac{\partial \mathcal{L}(w, j)}{\partial \mathrm{x}_{w}}, \quad \widetilde{w} \in \text { Context }(w)
(3). 负采样的采样方法是怎么样的?

词典 D\mathcal{D} 中的词在语料 C\mathcal{C} 中出现的次数有高有低,对于那些高频词,被选为负样本的概率就应该比较大;反之,对于那些低频词,其被选中的概率就应该比较小。这就是我们对采样过程的一个大致要求,本质上就是一个带权采样问题

首先在语料库中计算每个词的词频,即某个词的count/语料库中所有单词总count(源码中分子分母都用的是count四分之三次方,并非直接用count),即为被采样到的概率:
len(w)=[ counter (w)]34uD[ counter (u)]34 \operatorname{len}(w)=\frac{[\text { counter }(w)]^{\frac{3}{4}}}{\sum_{u \in \mathcal{D}}[\text { counter }(u)]^{\frac{3}{4}}}
在word2vec里的具体做法是,经过概率的计算,可将所有词映射到一条线上,这条线是非等距划分的N份(因为N个词的概率不同,N为词汇表的长度),再对同样的线进行等距划分成M份,其中M >> N,在采样时,每次随机生成一个[1,M-1]之间的数,在对应到该小块所对应的单词区域,选取该单词(当选到正例单词自己时,跳过),具体如图:

img

在源码中,M的取值为10810^8

2.4 负采样中的采样到的负样本是否更新?如果更新的话,是怎么样的更新方式?

2.5 为什么利用层次softmax和负采样能够提升速度和效率?

2.6 在实际使用过程中,负抽样使用多还是层级softmax多,为什么?

3. 源码的一些细节

3.1 源码中是如何进行负采样的?是否可能采样到同一个负样本?如何进行层次化softmax的?sigmod在源码里面的计算方法是什么?

(1). 源码中是如何进行负采样的?是否可能采样到同一个负样本?

源码中进行负采样的代码如下(添加注释):

// NEGATIVE SAMPLING
// 如果采用负采样优化
// 遍历生成正负样本(1个正样本+negative个负样本)
if (negative > 0) for (d = 0; d < negative + 1; d++) {
		if (d == 0) {
      	// 第一次循环处理的是目标单词,即正样本
      	target = word;
        label = 1;
    } else {
        // 随机抽取负样本,table_size默认值为1e8,也就是上述理论部分提到的M
    		next_random = next_random * (unsigned long long)25214903917 + 11;
        target = table[(next_random >> 16) % table_size];
        if (target == 0) target = next_random % (vocab_size - 1) + 1;
        if (target == word) continue;
        label = 0;
    }
  	// 下面是参数更新部分,之后再详细解读
  	// syn1neg数组存着Negative Sampling的参数,每个词在syn1neg数组中对应一个辅助向量(这是什么?)
		// 此时的l2为syn1neg中目标单词向量的起始位置,layer1_size为word embedding size
    l2 = target * layer1_size;
    f = 0;
    // f为输入向量neu1与辅助向量的内积
    for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];
    if (f > MAX_EXP) g = (label - 1) * alpha;
    else if (f < -MAX_EXP) g = (label - 0) * alpha;
    else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;
    for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];
    for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];
}

上述随机抽取负样本的做法是参考C语言中伪随机函数rand的实现:

《The GNU C Library》里说的,大概如下:

公式: Y=(a*X+c)mod m

其中:a,c,m都是常数,一种取值是:

a = 0x5DEECE66D = 25214903917,c = 0xb = 11,m = 2^48,用C语言代码实现即为:

__int64 rand ()
{
    static __int64 r = 0;
    const __int64 a = 25214903917;
    const __int64 c = 11;
    const __int64 m = 1 << 48;
    r = (r * a + c) % m;
    return r;
}

有可能采样到同一个负样本,负采样的方法与之前理论部分的说明是一致的

(2). 源码中如何进行层次softmax的?

在源码中CreateBinaryTree()函数封装了构建哈夫曼树的过程,其利用统计到的词频构建哈夫曼树,出现频率越高的词其二叉树上的路径越短,即二进制编码越短。随后其前向传播的过程以及更新各个参数的过程,与前面伪代码一致。需要注意的是,在word2vec源码中,为了能够加快计算,作者在开始的时候存储了一份sigmod的值,因此,对于σ(xwθjw)\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j}^{w}\right)需要从expTable中查询到对应的值。

(3). sigmod在源码里面的计算方法是什么?

源码中在预处理部分,实现了sigmoid函数值的近似计算。 在利用神经网络模型对样本进行预测的过程中,需要对其进行预测,此时,需要使用到sigmoid函数,sigmoid函数的具体形式为:
σ(x)=11+ex=ex1+ex \sigma \left ( x \right )=\frac{1}{1+e^{-x}}=\frac{e^x}{1+e^x}
如果每一次都请求计算sigmoid值,对性能将会有一定的影响,当sigmoid的值对精度的要求并不是非常严格时,可以采用近似计算,将一个极小区间内的值统一缓存成一个值(微元法的思想),在word2vec中,将区间[−6, 6)(设置的参数MAX_EXP为6)等距离划分成EXP_TABLE_SIZE(EXP_TABLE_SIZE默认值为1000)等份,并将每个区间中的sigmoid值计算好存入到数组expTable中,需要使用时,直接从数组中查找。计算sigmoid值的代码如下所示:

expTable = (real *)malloc((EXP_TABLE_SIZE + 1) * sizeof(real));// 申请EXP_TABLE_SIZE+1个空间
// 计算sigmoid值
for (i = 0; i < EXP_TABLE_SIZE; i++) {
  	expTable[i] = exp((i / (real)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table
  	expTable[i] = expTable[i] / (expTable[i] + 1);                   // Precompute f(x) = x / (x + 1)
}

之后在hierarchical softmax和负采样操作中,对于σ(xwθjw)\sigma\left(\mathbf{x}_{w}^{\top} \theta_{j}^{w}\right)需要从expTable中查询到对应的值,代码如下:

for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1[c + l2];// 映射层到输出层,送入sigmod函数前的结果

if (f <= -MAX_EXP) continue;
else if (f >= MAX_EXP) continue;
else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]; // sigmoid结果

最后这块查表可能初看起来比较难懂,但是变换一下形式就很容易看明白:

(int)(((f + MAX_EXP) / (MAX_EXP * 2)) * EXP_TABLE_SIZE)

上式应该就很清晰了,相当于在expTable查找对应索引,并获取缓存的sigmoid的值。

3.2 word2vec滑动窗口大小以及负采样个数的参数设置?

(1). word2vec滑动窗口大小设置

word2vec滑动窗口大小默认设置为5,但是在实际训练过程中,有如下操作:

// next_random:用来辅助生成随机数
unsigned long long next_random = (long long)id;
...
next_random = next_random * (unsigned long long)25214903917 + 11;
b = next_random % window;

// 以cbow模型为例,//一个词的窗口为[setence_position - window + b, sentence_position + window - b]
//因此窗口总长度为 2*window - 2*b + 1
for (a = b; a < window * 2 + 1 - b; a++) {
		...
}

实际上在训练过程中,滑动窗口有一定的减小,由之前的2*window减为2*(window - b),b[0,window)b \in [0, window)

(2). 负采样个数的设置

负样本数量默认为5,常用值为3-10,0表示不采用负采样

5.word2vec三层结构很简单,但是为什么效果这么好,word2vec激活函数是什么?

6.word2vec的参数数量是多少?

word2vec的参数集中在输入层到隐层的权重矩阵和隐层到输出层的权重矩阵,输入层到隐层的权重矩阵的参数量为Vembedding sizeV * embedding\ size,而隐层到输出层有两种优化方式,对于hierarchical softmax,整棵哈夫曼树的参数量为(V1)dim(θ)(V - 1) * dim(\theta)dim(θ)dim(\theta) 表示每经过一次二分类时对应参数的维度;而对于负采样的优化方式,

待补充

7.word2vec的优点和缺点是什么?

优点:

  1. 由于 word2vec 会考虑上下文,跟之前的 Embedding 方法相比,效果要更好(但不如 18 年之后的方法);
  2. 网络结构简单,而且word embedding维度比较低,训练速度比较快;
  3. 通用性很强,可以用在各种 NLP 任务中。

缺点:

  1. 由于词和向量是一对一的关系,所以多义词的问题无法解决;
  2. word2vec训练模式下,考虑的context很小,没有用到word在全局的信息统计。

8.word2vec如果有oov怎么解决?

主流思路有以下几种:

  • UNK处理,在训练过程中,把训练集中所有出现频率小于某个阈值的词都标记为UNK,那么UNK就会学到了自己的embedding,也有一定的语义信息,但是可能效果也会不好,因为UNK代表的单词可能比较多;

    • 还可能是UNK没有对应的embedding,这样的话一般有两种处理方式

      a. 把UNK都初始化成0的向量

      b. 每次都把UNK初始化成一个新的随机向量

      都初始化成0向量,会使得UNK都共享相同的语义信息,所以更多时候倾向于对UNK直接随机,因为本身 每个UNK都不同,随机更符合我们对UNK基于最大熵的估计。

  • Character Model,即把所有的OOV词,拆成字符。比如 Jessica,变成*J,*e,s,s,i,c,a。其中是Begin,Middle,End的标记。这样处理的好处就是消灭了全部的OOV。坏处就是文本序列变得非常长,对于性能敏感的系统,这是难以接受的维度增长。

    • 相似的做法还有利用Wordpiece Model的方法进行分词,这是sub-word粒度的分词手段
  • 扩大词表,终极解决办法。通常情况不使用大词表,一方面是因为训练数据的多样性有限,另一方面是softmax的计算速度受限。而对于上述两种情况,也有针对性解决办法。对于第一种情况,扩大语料范围。对于第二种情况,相关的加速策略可以将词表扩大10倍而GPU上的预测速度只降低一半(从5W词到50W词)。

9.word2vec如何得到词向量?怎么衡量学到的词向量的好坏?

(1). word2vec如何得到词向量?

以CBOW模型为例,输入层到隐层的权重矩阵设为W,训练结束之后**任何一个单词的one-hot表示乘以这个矩阵都将得到自己的word embedding。**Skip-gram模型也是一样的,这里需要注意的是,如果采取负采样的优化方式,会得到两套embedding,也就是输入层到隐层的权重矩阵和隐层到输出层的权重矩阵。但是一般选取输入层到隐层的权重矩阵作为单词的embedding,也有说法是选择两套embedding的平均作为单词最终的embedding,这个也没啥理论依据,都是通过实验验证得来;而如果采取hierarchical softmax的优化方式,那么只得到一套embedding,也就是输入层到隐层的权重矩阵,hierarchical softmax构建的哈夫曼树的叶结点对应的参数一般不作为word embedding,因为其参数不对称(单词在树中所在层次不一样),做词向量不是好选择。

(2). 怎么衡量学到的词向量的好坏?

词向量的评价大体上可以分成两种方式:

第一种是把词向量融入现有系统中,看对系统性能的提升;

  • 直接用于神经网络模型的输入层。将训练好的词向量作为输入,用前馈网络和卷积网络或者循环神经网络等完成了词性标注、语义角色标注、句法分析和情感分析等一系列任务。
  • 作为辅助特征扩充现有模型。比如将词向量作为额外的特征加入到接近 state of the art 的方法中,进一步提高了命名实体识别和短语识别的效果。

第二种是直接从语言学的角度对词向量进行分析,如相似度、语义偏移等。

Mikolov论文中使用类比(analogy)的方式来评测。如已知 a 之于 b 犹如 c 之于 d。现在给出 a、b、c,看C(a)C(b)+C(c)C(a)−C(b)+C(c)最接近的词是否是 d。与此同时,Mikolov在论文中也对比了词法关系(名词单复数 good-better:rough-rougher、动词第三人称单数、形容词比较级最高级等)和语义关系(clothing-shirt:dish-bowl)。

这些实验结果中最容易理解的是:语料越大,词向量就越好。其它的实验由于缺乏严格控制条件进行对比,谈不上哪个更好哪个更差。不过这里的两个语言学分析都非常有意思,尤其是向量之间存在这种线性平移的关系,可能会是词向量发展的一个突破口。

关于如何产生一个好的词向量,也可以参考博客及其提及的论文:

《How to Generate a Good Word Embedding?》导读

参考:

word2vec概述

Word2Vec原理之层次Softmax算法

cs224n: natural language processing with deep learning lecture notes: part i word vectors i: introduction, svd and word2vec

word2vec原理(二) 基于Hierarchical Softmax的模型

Hierarchical softmax(分层softmax)简单描述

机器学习算法实现解析——word2vec源码解析

Deep Learning in NLP (一)词向量和语言模型

word2vec算出的词向量怎么衡量好坏?

NLP 研究主流目前如何处理 out of vocabulary words?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章