这篇论文中提到的 Hierarchical Decomposition 就是后来在训练 word2vec 模型时一个常见的技巧 Hierarchical Softmax [1]。所谓的 Hierarchical Decomposition,就是将原先用 softmax 做多分类分解成多个sigmoid,使得模型在输出层的计算从 O(∣V∣)O(|V|)O(∣V∣) 降低到了 O(log?∣V∣)O(\log{|V|})O(log∣V∣)。
在 NNLM [2] 那篇论文中,作者提了一些未来工作的方向,其中就包括了如何去加速模型的计算。那时候作者有两个 idea:一个是将NN分解成 sub-NN;一个便是考虑用树结构去表示条件概率,树上的结点表示给定上下文,NN计算得到的属于一个 word class 的概率,而叶子结点表示给定上下文,NN计算得到一个 word 的概率。而这篇论文便是第二个 idea 的具体实现。
Bengio 在[3] 中将NNLM模型改成了给定上下文,去计算某个 wiw_iwi? 的概率,而不在输出层一下输出全部 word 的概率,也就是将 NNLM的|V| outputs改成了single output,如下图。
上面模型的公式为:
g(v,wt?1,...,wt?n+1)=a′?tanh?(c+Wx+UFv′)+bvg(v,w_{t-1},...,w_{t-n+1}) = a' \cdot \tanh{(c + Wx+UF_v')} + b_v g(v,wt?1?,...,wt?n+1?)=a′?tanh(c+Wx+UFv′?)+bv?
其中 xxx 是上下文词向量的拼接,Fv′F_v'Fv′? 是当前待计算词的词向量,而 WWW, UUU, aaa, bbb, ccc 都是可训练的参数。可以计算下它的时间复杂度:首先对于 projection 层的计算是 N×DN \times DN×D;隐层的计算是 (N?1)×D×H(N - 1) \times D \times H(N?1)×D×H;输出层的计算是 V×H×(D+1)V \times H \times (D + 1)V×H×(D+1),那么总的时间复杂度为:
Q=N×D+(N?1)×D×H+V×H×(D+1)Q = N \times D + (N - 1) \times D \times H + V \times H \times (D + 1) Q=N×D+(N?1)×D×H+V×H×(D+1)
这里需要解释下为什么输出层的计算需要乘上一个VVV,因为由上面公式可知,在计算 |V| 个 wiw_iwi? 时,模型的projection 层和隐层关于上下文向量的映射时只需要计算一次的,也就是 c+Wxc + Wxc+Wx部分是参数共享的。而对于输出层来说,UFv′UF_v'UFv′? 和 bvb_vbv? 是针对每个wvw_vwv?的计算,在计算一个wiw_iwi?概率 时都要计算一次,这也是为什么 H×(D+1)H \times (D + 1)H×(D+1) 需要乘上 VVV。
而NNLM模型的计算复杂度为:
Q=N×D+N×D×H+H×VQ = N \times D + N \times D \times H + H \times V Q=N×D+N×D×H+H×V
不难看出,无论是NNML还是它的变种,其计算复杂度都由输出层的计算主导,而这也是作者想去改进的地方,希望能将 O(∣V∣)O(|V|)O(∣V∣) 改进为 O(log?∣V∣)O(\log{|V|})O(log∣V∣)。
作者的一个想法是,对于语言模型,不直接计算 P(wt∣wt?1,...,wt?n+1)P(w_t|w_{t-1},...,w_{t-n+1})P(wt?∣wt?1?,...,wt?n+1?),而是先计算下一个词是属于某一类的概率(将词分类),再计算下一个词属于这个类中某个单词的概率,那么就将条件概率的计算分解成了:
P(wt∣wt?1,...,wt?n+1)=P(wt∣b(wt),wt?1,...,wt?n+1)P(b(wt)∣wt?1,...,wt?n+1)P(w_t|w_{t-1},...,w_{t-n+1})=P(w_t|b(w_{t}),w_{t-1},...,w_{t-n+1})P(b(w_t)|w_{t-1},...,w_{t-n+1}) P(wt?∣wt?1?,...,wt?n+1?)=P(wt?∣b(wt?),wt?1?,...,wt?n+1?)P(b(wt?)∣wt?1?,...,wt?n+1?)
将NNML模型的输出层换成这种方式计算,假设我们有10000个词,将其分成100类,每一类中有100个词,隐层有h个神经元,原先输出层计算一个词的概率的计算复杂度为 h×10000h \times 10000h×10000,而现在分成两步预测,第一步的计算复杂度为h×100h \times 100h×100,同样第二步也为 h×100h \times 100h×100,由此可见复杂度缩小了50倍。这个思路就很像信息检索中建立索引来加快搜索的方式。
基于这个想法,作者提出了进一步的扩展,就是首先将词分成两类,计算下一个词属于其中一类的概率,再将这一类分成两类,计算下一个词属于其中一子类的概率,如此递归,就构建出了一棵二叉树,叶子结点为 wvw_vwv? 单词的概率,中间结点为 bj(v)b_j(v)bj?(v) 的概率。那么条件概率的计算可以分解为:
P(v∣wt?1,...,wt?n+1)=∏j=1mP(bj(v)∣b1(v),...,bj?1(v),wt?1,...,wt?n+1)P(v|w_{t-1},...,w_{t-n+1})= \prod_{j=1}^m{P(b_{j}(v)|b_{1}(v),...,b_{j-1}(v),w_{t-1},...,w_{t-n+1})} P(v∣wt?1?,...,wt?n+1?)=j=1∏m?P(bj?(v)∣b1?(v),...,bj?1?(v),wt?1?,...,wt?n+1?)
其中 mmm 为 wvw_vwv? 到根结点的距离。那么在计算下一个结点为当前结点的右子树的概率(这里假设 b = 1时为下一结点为当前结点的右子树),计算公式为:
P(b=1∣node,wt?1,...,wt?n+1)=sigmoid(αnode+β′.tanh(c+Wx+UNnode))P(b = 1 | node,w_{t-1},...,w_{t-n+1})=sigmoid(\alpha_{node} + \beta'.tanh(c+Wx+UN_{node})) P(b=1∣node,wt?1?,...,wt?n+1?)=sigmoid(αnode?+β′.tanh(c+Wx+UNnode?))
其中NnodeN_{node}Nnode? 为结点的特征向量,而这里每个结点的特征向量对于每一次计算下一个词的概率是共享的。因为 sigmoid函数的值域为 [0, 1],那么经过sigmoid判断下一个单词是属于当前结点的左子树还是右子树(小于0.5为左子树,大于0.5为左子树),如果为左子树,则用左子树根节点的向量代入到上式计算,直到到达叶子结点。由此可见,预测下一个单词的复杂度就是二叉树的平均高度 log?∣V∣\log{|V|}log∣V∣。另外,可以根据词频构建Huffman树,计算效率能更高。
总结
这篇 Hierarchical Decomposition 的技巧提高了模型的计算效率,将 H×VH \times VH×V 降低成了 H×log?∣V∣H \times \log{|V|}H×log∣V∣,而此时的瓶颈就成了隐层部分的计算 N×D×HN \times D \times HN×D×H,这也就成了后来 word2vec 的一个动机,对模型结构进行动刀,而不仅仅是加速 softmax 的计算。
参考文献
[1] Mikolov T, Sutskever I, Chen K, et al. Distributed representations of words and phrases and their compositionality[C]//Advances in neural information processing systems. 2013: 3111-3119.
[2] Bengio Y, Ducharme R, Vincent P, et al. A neural probabilistic language model[J]. Journal of machine learning research, 2003, 3(Feb): 1137-1155.
[3] Bengio Y, Senécal J S. Quick training of probabilistic neural nets by importance sampling[C]//AISTATS. 2003: 1-9.