系列前言
参考文献:
- RNNLM - Recurrent Neural Network Language Modeling Toolkit(点此阅读)
- Recurrent neural network based language model(点此阅读)
- EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读)
- Strategies for Training Large Scale Neural Network Language Models(点此阅读)
- STATISTICAL LANGUAGE MODELS BASED ON NEURAL NETWORKS(点此阅读)
- A guide to recurrent neural networks and backpropagation(点此阅读)
- A Neural Probabilistic Language Model(点此阅读)
- Learning Long-Term Dependencies with Gradient Descent is Difficult(点此阅读)
- Can Artificial Neural Networks Learn Language Models?(点此阅读)
这篇主要介绍一个网络前向计算的函数,内容量也挺大的。在此之前,解释一下rnn的输出层分解,和从神经网络的角度去看最大熵模型。先看一下原论文中最"标准"的rnn结构,这个结构是最原始的,后面会有系列的扩展,详见参考文献的第3篇。
上图是最原始的循环神经网络的结构,关于它的前向计算和学习算法我在rnnlm原理以及bptt数学推导这篇文章有详细的写过。简要在写一下。上面这个网络的输出层有|V|维,在整个前向计算完毕后,我们得到的结果就是预测词的概率分布,即yt = P(wt+1 | wt,st-1), wt+1是要预测的词.
这是我从前篇文章截图来的,由于网络输出层部分计算量很大,特别是当|V|很大时,计算量会更大。于是Mikolov提出了将输出层分解,并不是在所有单词上计算概率的分布,而是在某一类单词中计算概率分布,这样就会大大降低计算量。于是有了如下图:
现在的计算预测词的概率分布变成了如下:
其中,yV'(t)表示在V中的部分词语,至于这个部分是哪一部分,得看怎么把这些单词分类了,ci表示类别, wt+1表示期望词。这样在前向计算概率分布时,就先计算在期望词的类别的概率分布,然后在当前预测词所在类别的词语上计算概率分布,这个待会会在源代码中看到。
void CRnnLM::computeNet(int last_word, int word){ //last_word表示当前输入层所在的词 //word表示要预测的词 int a, b, c; real val; double sum; //sum is used for normalization: it's better to have larger precision as many numbers are summed together here //将last_word对应的神经元ac值为1,也可以看做是对该词的1-of-V的编码 if (last_word!=-1) neu0[last_word].ac=1; //下面计算输入到隐层的部分 for (a=0; a<layer1_size; a++) neu1[a].ac=0; for (a=0; a<layerc_size; a++) neuc[a].ac=0; //这里计算的是s(t-1)与syn0的乘积 matrixXvector(neu1, neu0, syn0, layer0_size, 0, layer1_size, layer0_size-layer1_size, layer0_size, 0); //这里计算将last_word编码后的向量(大小是vocab_size,分量只有一个为1,其余为0)与syn0的乘积 for (b=0; b<layer1_size; b++) { a=last_word; if (a!=-1) neu1[b].ac += neu0[a].ac * syn0[a+b*layer0_size].weight; } //这里计算将上面隐层所得到的输入(ac值)经过sigmoid函数的映射结果 for (a=0; a<layer1_size; a++) { //为数值稳定,将ac值大小限制在[-50,50] //论文中有提到模型的参数小一些泛化的结果好一些 if (neu1[a].ac>50) neu1[a].ac=50; if (neu1[a].ac<-50) neu1[a].ac=-50; val=-neu1[a].ac; //fasterexp函数在fasexp.h中实现,应该比math.h中的exp快吧 neu1[a].ac=1/(1+fasterexp(val)); //sigmoid函数即1/(1+e^(-x)) } if (layerc_size>0) { //计算隐层到压缩层的结果 matrixXvector(neuc, neu1, syn1, layer1_size, 0, layerc_size, 0, layer1_size, 0); //和上面类似,这里计算的是压缩层 for (a=0; a<layerc_size; a++) { if (neuc[a].ac>50) neuc[a].ac=50; //for numerical stability if (neuc[a].ac<-50) neuc[a].ac=-50; //for numerical stability val=-neuc[a].ac; neuc[a].ac=1/(1+fasterexp(val)); } } //1->2 class //输出层class_size部分ac值置0 for (b=vocab_size; b<layer2_size; b++) neu2[b].ac=0; //计算压缩层到class层(输出层的一部分) if (layerc_size>0) { matrixXvector(neu2, neuc, sync, layerc_size, vocab_size, lay