rnnlm源码分析(七)
- 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?(点此阅读)
//反传误差,更新网络权值
void CRnnLM::learnNet(int last_word, int word)
{//word表示要预测的词,last_word表示当前输入层所在的词int a, b, c, t, step;real beta2, beta3;//alpha表示学习率,初始值为0.1, beta初始值为0.0000001;beta2=beta*alpha;//这里注释不懂,希望明白的朋友讲下~beta3=beta2*1; //beta3 can be possibly larger than beta2, as that is useful on small datasets (if the final model is to be interpolated wich backoff model) - todo in the futureif (word==-1) return;//compute error vectors,计算输出层的(只含word所在类别的所有词)误差向量for (c=0; c<class_cn[vocab[word].class_index]; c++) {a=class_words[vocab[word].class_index][c];neu2[a].er=(0-neu2[a].ac);}neu2[word].er=(1-neu2[word].ac); //word part//flush errorfor (a=0; a<layer1_size; a++) neu1[a].er=0;for (a=0; a<layerc_size; a++) neuc[a].er=0;//计算输出层的class部分的误差向量for (a=vocab_size; a<layer2_size; a++) {neu2[a].er=(0-neu2[a].ac);}neu2[vocab[word].class_index+vocab_size].er=(1-neu2[vocab[word].class_index+vocab_size].ac); //class part//计算特征所在syn_d中的下标,和上面一样,针对ME中word部分if (direct_size>0) { //learn direct connections between wordsif (word!=-1) {unsigned long long hash[MAX_NGRAM_ORDER];for (a=0; a<direct_order; a++) hash[a]=0;for (a=0; a<direct_order; a++) {b=0;if (a>0) if (history[a-1]==-1) break;hash[a]=PRIMES[0]*PRIMES[1]*(unsigned long long)(vocab[word].class_index+1);for (b=1; b<=a; b++) hash[a]+=PRIMES[(a*PRIMES[b]+b)%PRIMES_SIZE]*(unsigned long long)(history[b-1]+1);hash[a]=(hash[a]%(direct_size/2))+(direct_size)/2;}//更新ME中的权值部分,这部分是正对word的for (c=0; c<class_cn[vocab[word].class_index]; c++) {a=class_words[vocab[word].class_index][c];//这里的更新公式很容易推导,利用梯度上升,和RNN是一样的//详见我的这篇文章,另外这里不同的是权值是放在一维数组里面的,所以程序写法有点不同for (b=0; b<direct_order; b++) if (hash[b]) {syn_d[hash[b]]+=alpha*neu2[a].er - syn_d[hash[b]]*beta3;hash[b]++;hash[b]=hash[b]%direct_size;} else break;}}}//计算n元模型特征,这是对class计算的//learn direct connections to classesif (direct_size>0) { //learn direct connections between words and classesunsigned long long hash[MAX_NGRAM_ORDER];for (a=0; a<direct_order; a++) hash[a]=0;for (a=0; a<direct_order; a++) {b=0;if (a>0) if (history[a-1]==-1) break;hash[a]=PRIMES[0]*PRIMES[1];for (b=1; b<=a; b++) hash[a]+=PRIMES[(a*PRIMES[b]+b)%PRIMES_SIZE]*(unsigned long long)(history[b-1]+1);hash[a]=hash[a]%(direct_size/2);}//和上面一样,更新ME中权值部分,这是对class的for (a=vocab_size; a<layer2_size; a++) {for (b=0; b<direct_order; b++) if (hash[b]) {syn_d[hash[b]]+=alpha*neu2[a].er - syn_d[hash[b]]*beta3;hash[b]++;} else break;}}////含压缩层的情况,更新sync, syn1if (layerc_size>0) {//将输出层的误差传递到压缩层,即计算部分V × eo^T(符号含义表示见图)matrixXvector(neuc, neu2, sync, layerc_size, class_words[vocab[word].class_index][0], class_words[vocab[word].class_index][0]+class_cn[vocab[word].class_index], 0, layerc_size, 1);//这里把一维的权值转换为二维的权值矩阵好理解一些//注意这里的t相当于定位到了参数矩阵的行,下面的下标a相当于列t=class_words[vocab[word].class_index][0]*layerc_size;for (c=0; c<class_cn[vocab[word].class_index]; c++) {b=class_words[vocab[word].class_index][c];//这里的更新公式见我写的另一篇文章的rnn推导//并且每训练10次才L2正规化一次 if ((counter%10)==0) //regularization is done every 10. stepfor (a=0; a<layerc_size; a++) sync[a+t].weight+=alpha*neu2[b].er*neuc[a].ac - sync[a+t].weight*beta2;elsefor (a=0; a<layerc_size; a++) sync[a+t].weight+=alpha*neu2[b].er*neuc[a].ac;//参数矩阵下移动一行t+=layerc_size;}//将输出层的误差传递到压缩层,即计算部分V × eo^T(符号含义表示见图)//这里计算输出层中class部分到压缩层matrixXvector(neuc, neu2, sync, layerc_size, vocab_size, layer2_size, 0, layerc_size, 1); //propagates errors 2->c for classes//这里同样相当于定位的权值矩阵中的行,下面的a表示列c=vocab_size*layerc_size;//更新和上面公式是一样的,不同的是更新的权值数组中不同的部分for (b=vocab_size; b<layer2_size; b++) {if ((counter%10)==0) { for (a=0; a<layerc_size; a++) sync[a+c].weight+=alpha*neu2[b].er*neuc[a].ac - sync[a+c].weight*beta2; //weight c->2 update}else {for (a=0; a<layerc_size; a++) sync[a+c].weight+=alpha*neu2[b].er*neuc[a].ac; //weight c->2 update}//下一行c+=layerc_size;}//这里是误差向量,即论文中的e hj (t)for (a=0; a<layerc_size; a++) neuc[a].er=neuc[a].er*neuc[a].ac*(1-neuc[a].ac); //error derivation at compression layer//下面都是同理,将误差再往下传,计算syn1(二维) × ec^T, ec表示压缩层的误差向量matrixXvector(neu1, neuc, syn1, layer1_size, 0, layerc_size, 0, layer1_size, 1); //propagates errors c->1//更新syn1,相应的见公式for (b=0; b<layerc_size; b++) {for (a=0; a<layer1_size; a++) syn1[a+b*layer1_size].weight+=alpha*neuc[b].er*neu1[a].ac; //weight 1->c update}}else //无压缩层的情况,更新syn1{//下面的情况和上面类似,只是少了一个压缩层matrixXvector(neu1, neu2, syn1, layer1_size, class_words[vocab[word].class_index][0], class_words[vocab[word].class_index][0]+class_cn[vocab[word].class_index], 0, layer1_size, 1);t=class_words[vocab[word].class_index][0]*layer1_size;for (c=0; c<class_cn[vocab[word].class_index]; c++) {b=class_words[vocab[word].class_index][c];if ((counter%10)==0) //regularization is done every 10. stepfor (a=0; a<layer1_size; a++) syn1[a+t].weight+=alpha*neu2[b].er*neu1[a].ac - syn1[a+t].weight*beta2;elsefor (a=0; a<layer1_size; a++) syn1[a+t].weight+=alpha*neu2[b].er*neu1[a].ac;t+=layer1_size;}//matrixXvector(neu1, neu2, syn1, layer1_size, vocab_size, layer2_size, 0, layer1_size, 1); //propagates errors 2->1 for classesc=vocab_size*layer1_size;for (b=vocab_size; b<layer2_size; b++) {if ((counter%10)==0) { //regularization is done every 10. stepfor (a=0; a<layer1_size; a++) syn1[a+c].weight+=alpha*neu2[b].er*neu1[a].ac - syn1[a+c].weight*beta2; //weight 1->2 update}else {for (a=0; a<layer1_size; a++) syn1[a+c].weight+=alpha*neu2[b].er*neu1[a].ac; //weight 1->2 update}c+=layer1_size;}}////到这里,上面部分把到隐层的部分更新完毕/////这里就是最常规的RNN,即t时刻往前只展开了s(t-1)if (bptt<=1) { //bptt==1 -> normal BP//计算隐层的误差,即eh(t)for (a=0; a<layer1_size; a++) neu1[a].er=neu1[a].er*neu1[a].ac*(1-neu1[a].ac); //error derivation at layer 1//这部分更新隐层到word部分的权值//注意由于word部分是1-of-V编码//所以这里更新的循环式上有点不同,并且下面的更新都是每10次训练更新才进行一次L2正规化a=last_word;if (a!=-1) {if ((counter%10)==0) for (b=0; b<layer1_size; b++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac - syn0[a+b*layer0_size].weight*beta2;else//我们可以把这里的权值看做一个矩阵//b表示行,a表示列,非L2正规化的完整更新式如下:/*for (b=0; b<layer1_size; b++) {for (k=0; k<vocab_size; k++) {syn0[k+b*layer0_size].weight+=alpha*neu1[b].er*neu0[k].ac;}}*///但由于neu0[k]==1只有当k==a时,所以为了加快循环计算的速度,更新就变为如下了//下面的代码是类似的道理,其实这里的neu0[a].ac可以直接省略掉,因为本身值为1 for (b=0; b<layer1_size; b++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}//这部分更新隐层到s(t-1)的权值if ((counter%10)==0) {for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac - syn0[a+b*layer0_size].weight*beta2;}else {for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}}
下面是更新BPTT的部分, 还是先把BPTT算法部分的数据结构图上来,这样更方便对照:
else //BPTT{//BPTT部分权值就不在用syn0了,而是用bptt_syn0//神经元部分的存储用bptt_hidden//将隐层的神经元信息复制到bptt_hidden,这里对neu1做备份for (b=0; b<layer1_size; b++) bptt_hidden[b].ac=neu1[b].ac;for (b=0; b<layer1_size; b++) bptt_hidden[b].er=neu1[b].er;//bptt_block第一个条件是控制BPTT的,因为不能每训练一个word都用BPTT来深度更新参数//这样每进行一次训练计算量太大,过于耗时//word==0表示当前一个句子结束//或者当independent开关不为0时,下一个词是句子的结束,则进行BPTTif (((counter%bptt_block)==0) || (independent && (word==0))) {//step表示for (step=0; step<bptt+bptt_block-2; step++) {//计算隐层的误差向量,即eh(t)for (a=0; a<layer1_size; a++) neu1[a].er=neu1[a].er*neu1[a].ac*(1-neu1[a].ac); //error derivation at layer 1//更新的是输入层中vocab_size那部分权值//bptt_history下标从0开始依次存放的是wt, wt-1, wt-2...//比如当step == 0时,相当于正在进行普通的BPTT,这时bptt_history[step]表示当前输入word的在vocab中的索引a=bptt_history[step]; if (a!=-1)for (b=0; b<layer1_size; b++) {//由于输入层word部分是1-of-V编码,所以neu0[a].ac==1 所以这里后面没有乘以它//在step == 0时,bptt_syn0相当于原来的syn0bptt_syn0[a+b*layer0_size].weight+=alpha*neu1[b].er;}//为从隐层往状态层传误差做准备for (a=layer0_size-layer1_size; a<layer0_size; a++) neu0[a].er=0;//从隐层传递误差到状态层matrixXvector(neu0, neu1, syn0, layer0_size, 0, layer1_size, layer0_size-layer1_size, layer0_size, 1); //propagates errors 1->0//更新隐层到状态层的权值//之所以先计算误差的传递就是因为更新完权值会发生改变for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) {//neu0[a].er += neu1[b].er * syn0[a+b*layer0_size].weight;bptt_syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}//todo这里我理解是把论文中的s(t-1)复制到s(t),准备下一次循环for (a=0; a<layer1_size; a++) { //propagate error from time T-n to T-n-1//s(t-1)的误差来自于两部分(见图更明显):1.s(t)传过来的 2.t-1时刻的输出层传过来的,谢谢博友qq_16478429提出来的 neu1[a].er=neu0[a+layer0_size-layer1_size].er + bptt_hidden[(step+1)*layer1_size+a].er;}//历史中的s(t-2)复制到s(t-1)if (step<bptt+bptt_block-3)for (a=0; a<layer1_size; a++) {neu1[a].ac=bptt_hidden[(step+1)*layer1_size+a].ac;neu0[a+layer0_size-layer1_size].ac=bptt_hidden[(step+2)*layer1_size+a].ac;}
到这里,无奈再把代码打断一下,把上面两个关键循环按照step的增加走一下,并假设当前时刻为t,展开一下上面的循环:
step = 0:
第一个for循环完成:
neu1.er = neu0.er + btpp_hidden.er -> s(t) = s(t-1) + s(t-1)
第二个for循环完成:
neu1.ac = bptt_hidden.ac -> s(t) = s(t-1)
neu0.ac = bptt_hidden.ac -> s(t-1) = s(t-2)
step = 1:
第一个for循环完成:
neu1.er = neu0.er + btpp_hidden.er -> s(t-1) = s(t-2) + s(t-2)
第二个for循环完成:
neu1.ac = bptt_hidden.ac -> s(t-1) = s(t-2)
neu0.ac = bptt_hidden.ac -> s(t-2) = s(t-3)
通过上面列出的步骤,大概能知道原理,其中neu0,和bptt_hidden所对应的s(t-1)有不同,前者的er值隐层传下来的 后者的er值是在t-1时刻,压缩层传下来的
}//清空历史信息中的er值for (a=0; a<(bptt+bptt_block)*layer1_size; a++) {bptt_hidden[a].er=0;}//这里恢复neu1,因为neu1反复在循环中使用for (b=0; b<layer1_size; b++) neu1[b].ac=bptt_hidden[b].ac; //restore hidden layer after bptt//将BPTT后的权值改变作用到syn0上for (b=0; b<layer1_size; b++) { //copy temporary syn0//bptt完毕,将bptt_syn0的权值复制到syn0中来,这是复制状态层部分if ((counter%10)==0) {for (a=layer0_size-layer1_size; a<layer0_size; a++) {syn0[a+b*layer0_size].weight+=bptt_syn0[a+b*layer0_size].weight - syn0[a+b*layer0_size].weight*beta2;bptt_syn0[a+b*layer0_size].weight=0;}}else {for (a=layer0_size-layer1_size; a<layer0_size; a++) {syn0[a+b*layer0_size].weight+=bptt_syn0[a+b*layer0_size].weight;bptt_syn0[a+b*layer0_size].weight=0;}}//bptt完毕,将bptt_syn0的权值复制到syn0中来,这是word部分if ((counter%10)==0) {for (step=0; step<bptt+bptt_block-2; step++) if (bptt_history[step]!=-1) {syn0[bptt_history[step]+b*layer0_size].weight+=bptt_syn0[bptt_history[step]+b*layer0_size].weight - syn0[bptt_history[step]+b*layer0_size].weight*beta2;bptt_syn0[bptt_history[step]+b*layer0_size].weight=0;}}else {//因为输入的word层是1-of-V编码,并不是全部的权值改变//这样写可以加快计算速度,避免不必要的循环检索for (step=0; step<bptt+bptt_block-2; step++) if (bptt_history[step]!=-1) {syn0[bptt_history[step]+b*layer0_size].weight+=bptt_syn0[bptt_history[step]+b*layer0_size].weight;bptt_syn0[bptt_history[step]+b*layer0_size].weight=0;}}}}}
}//将隐层神经元的ac值复制到输出层后layer1_size那部分
void CRnnLM::copyHiddenLayerToInput()
{int a;for (a=0; a<layer1_size; a++) {neu0[a+layer0_size-layer1_size].ac=neu1[a].ac;}
}
最后仍然和前面一样,把整个函数完整的注释版贴在下面:
//反传误差,更新网络权值
void CRnnLM::learnNet(int last_word, int word)
{//word表示要预测的词,last_word表示当前输入层所在的词int a, b, c, t, step;real beta2, beta3;//alpha表示学习率,初始值为0.1, beta初始值为0.0000001;beta2=beta*alpha;//这里注释不懂,希望明白的朋友讲下~beta3=beta2*1; //beta3 can be possibly larger than beta2, as that is useful on small datasets (if the final model is to be interpolated wich backoff model) - todo in the futureif (word==-1) return;//compute error vectors,计算输出层的(只含word所在类别的所有词)误差向量for (c=0; c<class_cn[vocab[word].class_index]; c++) {a=class_words[vocab[word].class_index][c];neu2[a].er=(0-neu2[a].ac);}neu2[word].er=(1-neu2[word].ac); //word part//flush errorfor (a=0; a<layer1_size; a++) neu1[a].er=0;for (a=0; a<layerc_size; a++) neuc[a].er=0;//计算输出层的class部分的误差向量for (a=vocab_size; a<layer2_size; a++) {neu2[a].er=(0-neu2[a].ac);}neu2[vocab[word].class_index+vocab_size].er=(1-neu2[vocab[word].class_index+vocab_size].ac); //class part//计算特征所在syn_d中的下标,和上面一样,针对ME中word部分if (direct_size>0) { //learn direct connections between wordsif (word!=-1) {unsigned long long hash[MAX_NGRAM_ORDER];for (a=0; a<direct_order; a++) hash[a]=0;for (a=0; a<direct_order; a++) {b=0;if (a>0) if (history[a-1]==-1) break;hash[a]=PRIMES[0]*PRIMES[1]*(unsigned long long)(vocab[word].class_index+1);for (b=1; b<=a; b++) hash[a]+=PRIMES[(a*PRIMES[b]+b)%PRIMES_SIZE]*(unsigned long long)(history[b-1]+1);hash[a]=(hash[a]%(direct_size/2))+(direct_size)/2;}//更新ME中的权值部分,这部分是正对word的for (c=0; c<class_cn[vocab[word].class_index]; c++) {a=class_words[vocab[word].class_index][c];//这里的更新公式很容易推导,利用梯度上升,和RNN是一样的//详见我的这篇文章,另外这里不同的是权值是放在一维数组里面的,所以程序写法有点不同for (b=0; b<direct_order; b++) if (hash[b]) {syn_d[hash[b]]+=alpha*neu2[a].er - syn_d[hash[b]]*beta3;hash[b]++;hash[b]=hash[b]%direct_size;} else break;}}}//计算n元模型特征,这是对class计算的//learn direct connections to classesif (direct_size>0) { //learn direct connections between words and classesunsigned long long hash[MAX_NGRAM_ORDER];for (a=0; a<direct_order; a++) hash[a]=0;for (a=0; a<direct_order; a++) {b=0;if (a>0) if (history[a-1]==-1) break;hash[a]=PRIMES[0]*PRIMES[1];for (b=1; b<=a; b++) hash[a]+=PRIMES[(a*PRIMES[b]+b)%PRIMES_SIZE]*(unsigned long long)(history[b-1]+1);hash[a]=hash[a]%(direct_size/2);}//和上面一样,更新ME中权值部分,这是对class的for (a=vocab_size; a<layer2_size; a++) {for (b=0; b<direct_order; b++) if (hash[b]) {syn_d[hash[b]]+=alpha*neu2[a].er - syn_d[hash[b]]*beta3;hash[b]++;} else break;}}////含压缩层的情况,更新sync, syn1if (layerc_size>0) {//将输出层的误差传递到压缩层,即计算部分V × eo^T(符号含义表示见图)matrixXvector(neuc, neu2, sync, layerc_size, class_words[vocab[word].class_index][0], class_words[vocab[word].class_index][0]+class_cn[vocab[word].class_index], 0, layerc_size, 1);//这里把一维的权值转换为二维的权值矩阵好理解一些//注意这里的t相当于定位到了参数矩阵的行,下面的下标a相当于列t=class_words[vocab[word].class_index][0]*layerc_size;for (c=0; c<class_cn[vocab[word].class_index]; c++) {b=class_words[vocab[word].class_index][c];//这里的更新公式见我写的另一篇文章的rnn推导//并且每训练10次才L2正规化一次 if ((counter%10)==0) //regularization is done every 10. stepfor (a=0; a<layerc_size; a++) sync[a+t].weight+=alpha*neu2[b].er*neuc[a].ac - sync[a+t].weight*beta2;elsefor (a=0; a<layerc_size; a++) sync[a+t].weight+=alpha*neu2[b].er*neuc[a].ac;//参数矩阵下移动一行t+=layerc_size;}//将输出层的误差传递到压缩层,即计算部分V × eo^T(符号含义表示见图)//这里计算输出层中class部分到压缩层matrixXvector(neuc, neu2, sync, layerc_size, vocab_size, layer2_size, 0, layerc_size, 1); //propagates errors 2->c for classes//这里同样相当于定位的权值矩阵中的行,下面的a表示列c=vocab_size*layerc_size;//更新和上面公式是一样的,不同的是更新的权值数组中不同的部分for (b=vocab_size; b<layer2_size; b++) {if ((counter%10)==0) { for (a=0; a<layerc_size; a++) sync[a+c].weight+=alpha*neu2[b].er*neuc[a].ac - sync[a+c].weight*beta2; //weight c->2 update}else {for (a=0; a<layerc_size; a++) sync[a+c].weight+=alpha*neu2[b].er*neuc[a].ac; //weight c->2 update}//下一行c+=layerc_size;}//这里是误差向量,即论文中的e hj (t)for (a=0; a<layerc_size; a++) neuc[a].er=neuc[a].er*neuc[a].ac*(1-neuc[a].ac); //error derivation at compression layer//下面都是同理,将误差再往下传,计算syn1(二维) × ec^T, ec表示压缩层的误差向量matrixXvector(neu1, neuc, syn1, layer1_size, 0, layerc_size, 0, layer1_size, 1); //propagates errors c->1//更新syn1,相应的见公式for (b=0; b<layerc_size; b++) {for (a=0; a<layer1_size; a++) syn1[a+b*layer1_size].weight+=alpha*neuc[b].er*neu1[a].ac; //weight 1->c update}}else //无压缩层的情况,更新syn1{//下面的情况和上面类似,只是少了一个压缩层matrixXvector(neu1, neu2, syn1, layer1_size, class_words[vocab[word].class_index][0], class_words[vocab[word].class_index][0]+class_cn[vocab[word].class_index], 0, layer1_size, 1);t=class_words[vocab[word].class_index][0]*layer1_size;for (c=0; c<class_cn[vocab[word].class_index]; c++) {b=class_words[vocab[word].class_index][c];if ((counter%10)==0) //regularization is done every 10. stepfor (a=0; a<layer1_size; a++) syn1[a+t].weight+=alpha*neu2[b].er*neu1[a].ac - syn1[a+t].weight*beta2;elsefor (a=0; a<layer1_size; a++) syn1[a+t].weight+=alpha*neu2[b].er*neu1[a].ac;t+=layer1_size;}//matrixXvector(neu1, neu2, syn1, layer1_size, vocab_size, layer2_size, 0, layer1_size, 1); //propagates errors 2->1 for classesc=vocab_size*layer1_size;for (b=vocab_size; b<layer2_size; b++) {if ((counter%10)==0) { //regularization is done every 10. stepfor (a=0; a<layer1_size; a++) syn1[a+c].weight+=alpha*neu2[b].er*neu1[a].ac - syn1[a+c].weight*beta2; //weight 1->2 update}else {for (a=0; a<layer1_size; a++) syn1[a+c].weight+=alpha*neu2[b].er*neu1[a].ac; //weight 1->2 update}c+=layer1_size;}}////到这里,上面部分把到隐层的部分更新完毕/////这里就是最常规的RNN,即t时刻往前只展开了s(t-1)if (bptt<=1) { //bptt==1 -> normal BP//计算隐层的误差,即eh(t)for (a=0; a<layer1_size; a++) neu1[a].er=neu1[a].er*neu1[a].ac*(1-neu1[a].ac); //error derivation at layer 1//这部分更新隐层到word部分的权值//注意由于word部分是1-of-V编码//所以这里更新的循环式上有点不同,并且下面的更新都是每10次训练更新才进行一次L2正规化a=last_word;if (a!=-1) {if ((counter%10)==0) for (b=0; b<layer1_size; b++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac - syn0[a+b*layer0_size].weight*beta2;else//我们可以把这里的权值看做一个矩阵//b表示行,a表示列,非L2正规化的完整更新式如下:/*for (b=0; b<layer1_size; b++) {for (k=0; k<vocab_size; k++) {syn0[k+b*layer0_size].weight+=alpha*neu1[b].er*neu0[k].ac;}}*///但由于neu0[k]==1只有当k==a时,所以为了加快循环计算的速度,更新就变为如下了//下面的代码是类似的道理,其实这里的neu0[a].ac可以直接省略掉,因为本身值为1 for (b=0; b<layer1_size; b++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}//这部分更新隐层到s(t-1)的权值if ((counter%10)==0) {for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac - syn0[a+b*layer0_size].weight*beta2;}else {for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}}else //BPTT{//BPTT部分权值就不在用syn0了,而是用bptt_syn0//神经元部分的存储用bptt_hidden//将隐层的神经元信息复制到bptt_hidden,这里对neu1做备份for (b=0; b<layer1_size; b++) bptt_hidden[b].ac=neu1[b].ac;for (b=0; b<layer1_size; b++) bptt_hidden[b].er=neu1[b].er;//bptt_block第一个条件是控制BPTT的,因为不能每训练一个word都用BPTT来深度更新参数//这样每进行一次训练计算量太大,过于耗时//word==0表示当前一个句子结束//或者当independent开关不为0时,下一个词是句子的结束,则进行BPTTif (((counter%bptt_block)==0) || (independent && (word==0))) {//step表示for (step=0; step<bptt+bptt_block-2; step++) {//计算隐层的误差向量,即eh(t)for (a=0; a<layer1_size; a++) neu1[a].er=neu1[a].er*neu1[a].ac*(1-neu1[a].ac); //error derivation at layer 1//更新的是输入层中vocab_size那部分权值//bptt_history下标从0开始依次存放的是wt, wt-1, wt-2...//比如当step == 0时,相当于正在进行普通的BPTT,这时bptt_history[step]表示当前输入word的在vocab中的索引a=bptt_history[step]; if (a!=-1)for (b=0; b<layer1_size; b++) {//由于输入层word部分是1-of-V编码,所以neu0[a].ac==1 所以这里后面没有乘以它//在step == 0时,bptt_syn0相当于原来的syn0bptt_syn0[a+b*layer0_size].weight+=alpha*neu1[b].er;}//为从隐层往状态层传误差做准备for (a=layer0_size-layer1_size; a<layer0_size; a++) neu0[a].er=0;//从隐层传递误差到状态层matrixXvector(neu0, neu1, syn0, layer0_size, 0, layer1_size, layer0_size-layer1_size, layer0_size, 1); //propagates errors 1->0//更新隐层到状态层的权值//之所以先计算误差的传递就是因为更新完权值会发生改变for (b=0; b<layer1_size; b++) for (a=layer0_size-layer1_size; a<layer0_size; a++) {//neu0[a].er += neu1[b].er * syn0[a+b*layer0_size].weight;bptt_syn0[a+b*layer0_size].weight+=alpha*neu1[b].er*neu0[a].ac;}//todo这里我理解是把论文中的s(t-1)复制到s(t),准备下一次循环for (a=0; a<layer1_size; a++) { //propagate error from time T-n to T-n-1//后面为什么会加bptt_hidden呢neu1[a].er=neu0[a+layer0_size-layer1_size].er + bptt_hidden[(step+1)*layer1_size+a].er;}//历史中的s(t-2)复制到s(t-1)if (step<bptt+bptt_block-3)for (a=0; a<layer1_size; a++) {neu1[a].ac=bptt_hidden[(step+1)*layer1_size+a].ac;neu0[a+layer0_size-layer1_size].ac=bptt_hidden[(step+2)*layer1_size+a].ac;}}//清空历史信息中的er值for (a=0; a<(bptt+bptt_block)*layer1_size; a++) {bptt_hidden[a].er=0;}//这里恢复neu1,因为neu1反复在循环中使用for (b=0; b<layer1_size; b++) neu1[b].ac=bptt_hidden[b].ac; //restore hidden layer after bptt//将BPTT后的权值改变作用到syn0上for (b=0; b<layer1_size; b++) { //copy temporary syn0//bptt完毕,将bptt_syn0的权值复制到syn0中来,这是复制状态层部分if ((counter%10)==0) {for (a=layer0_size-layer1_size; a<layer0_size; a++) {syn0[a+b*layer0_size].weight+=bptt_syn0[a+b*layer0_size].weight - syn0[a+b*layer0_size].weight*beta2;bptt_syn0[a+b*layer0_size].weight=0;}}else {for (a=layer0_size-layer1_size; a<layer0_size; a++) {syn0[a+b*layer0_size].weight+=bptt_syn0[a+b*layer0_size].weight;bptt_syn0[a+b*layer0_size].weight=0;}}//bptt完毕,将bptt_syn0的权值复制到syn0中来,这是word部分if ((counter%10)==0) {for (step=0; step<bptt+bptt_block-2; step++) if (bptt_history[step]!=-1) {syn0[bptt_history[step]+b*layer0_size].weight+=bptt_syn0[bptt_history[step]+b*layer0_size].weight - syn0[bptt_history[step]+b*layer0_size].weight*beta2;bptt_syn0[bptt_history[step]+b*layer0_size].weight=0;}}else {//因为输入的word层是1-of-V编码,并不是全部的权值改变//这样写可以加快计算速度,避免不必要的循环检索for (step=0; step<bptt+bptt_block-2; step++) if (bptt_history[step]!=-1) {syn0[bptt_history[step]+b*layer0_size].weight+=bptt_syn0[bptt_history[step]+b*layer0_size].weight;bptt_syn0[bptt_history[step]+b*layer0_size].weight=0;}}}}}
}
rnnlm源码分析(七)相关推荐
- TeamTalk客户端源码分析七
TeamTalk客户端源码分析七 一,CBaseSocket类 二,select模型 三,样例分析:登录功能 上篇文章我们分析了network模块中的引用计数,智能锁,异步回调机制以及数据的序列化和反 ...
- 【转】ABP源码分析七:Setting 以及 Mail
本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration. Setting一般用于需要通过外部配置文件(或数据 ...
- spring boot 源码分析(七) 事件机制 之 SpringApplicationEvent
2019独角兽企业重金招聘Python工程师标准>>> 一.前言 前面的文章我们讲解了一下spring boot配置文件加载的相关源码分析,下面我们将从源码角度讲解一下spring ...
- Spring Core Container 源码分析七:注册 Bean Definitions
前言 原本以为,Spring 通过解析 bean 的配置,生成并注册 bean defintions 的过程不太复杂,比较简单,不用单独开辟一篇博文来讲述:但是当在分析前面两个章节有关 @Autowi ...
- Spring Security源码分析七:Spring Security 记住我
有这样一个场景--有个用户初访并登录了你的网站,然而第二天他又来了,却必须再次登录.于是就有了"记住我"这样的功能来方便用户使用,然而有一件不言自明的事情,那就是这种认证状态的&q ...
- rnnlm源码分析 一
系列前言 参考文献: RNNLM - Recurrent Neural Network Language Modeling Toolkit(点此阅读) Recurrent neural networ ...
- springfox源码_springfox 源码分析(七) 文档初始化
时间:2019-5-23 20:12:04 地点:家中 通过前面几篇文章对springfox的介绍,以及我们的学习准备工作,这篇我们将正式来探索springfox是如何初始化的 我们在学算法的时候,其 ...
- rnnlm源码分析 二
系列前言 参考文献: RNNLM - Recurrent Neural Network Language Modeling Toolkit(点此阅读) Recurrent neural networ ...
- rnnlm源码分析(二)
系列前言 参考文献: RNNLM - Recurrent Neural Network Language Modeling Toolkit(点此阅读) Recurrent neural networ ...
最新文章
- Storm Trident示例function, filter, projection
- jQuery滑动效果实例
- Asp.Net 2.0中的客户端脚本
- SPOJ 1812 LCS2 - Longest Common Substring II (后缀自动机)【两种做法】
- Linux traceroute路由跟踪
- Mysql 会导致锁表的语法
- 前端学习(2335):angular之内置结构指令ngif
- 前端学习(780):日期对象
- 「雕爷学编程」Arduino动手做(30)——光敏二极管模块
- 基于非递归算法的汉诺塔游戏之Python实现
- Tricks(二十九)—— 2^10000 的位数
- 获评优秀案例!IMG光线追踪技术实现卓越云游戏体验
- 黄文俊:Serverless小程序后端技术分享 1
- 【Linux网络编程】域名转IP后的一些深层(计算机底层)的思考
- [UVM]UVM TLM1.0 Interface归纳总结 --- 图解UVM TLM1.0 Interface
- win10玩武装突袭3一会就闪退的解决方法
- Mac 开启局域网smb文件共享(附全平台连接方法)
- Livox激光MID-360使用与fast-lio2激光SLAM建图
- 激流勇进,数据库替代比预想要快得多
- 如何把计算机软件卸载干净
热门文章
- [Wpf] . [Theme] 重构/Themes/Generic.xaml 创建一个Custom Control的典型做法
- 视频图像处理基础知识4(视频分辨率参考 行频 隔行扫描 逐行扫描)
- ruoyi-UI (若依)微服务版 vue前端使用及分析(2021-4-13更新)
- Fabric学习笔记——一、环境搭建(小白入门)
- 蚂蚁金服的“开放联盟链”如何影响现有公链
- 生活中正确购买物品你可能真的不会!
- 动态规划问题——招聘会
- 即便到愚人节,也千万别做的恶作剧!
- SQL 注入攻击:简介与原理
- 【无代码体验】使用鲸智搭开发《招聘管理系统》