rnnlm源码分析(一)

系列前言
参考文献:

  1. RNNLM – Recurrent Neural Network  Language Modeling Toolkit(点此阅读)
  2. Recurrent neural network based language model(点此阅读)
  3. EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读)
  4. Strategies for Training Large Scale Neural Network  Language Models(点此阅读)
  5. STATISTICAL LANGUAGE MODELS BASED ON NEURAL  NETWORKS(点此阅读)
  6. A guide to recurrent neural networks and backpropagation(点此阅读)
  7. A Neural Probabilistic Language Model(点此阅读)
  8. Learning Long-Term Dependencies with Gradient Descent is Difficult(点此阅读)
  9. Can Artificial Neural Networks Learn Language Models?(点此阅读)



最近学习完系列神经网络语言模型后,最好的更为深刻的理解方式就是阅读源码,看一看究竟怎么实现的,认真的分析过源码,并且画出内部的数据结构图之后,才觉的了解的更透彻,甚至感觉自己能换一种语言重写这个开源工具。而且从阅读源码的过程中学到了很多实现技巧。当然自己的理解很多地方是比较狭隘的,可能有许多错误,而且某些地方是感觉那么回事,也没太仔细的思考,可能很多就没想清楚。而有些地方只有几行代码,却无奈的坐在那里想了一个下午╮(╯▽╰)╭,连吃饭、走路的时候也在想,我想这是快走火入魔了吧,哈哈。由于我在代码中注释很多,几乎是每行一注释,很多想仔细说的也在源码注释里面,所以内容主要以代码注释为主,外加对源码内部数据结构的图解。并且原谅我喜欢把参考内容放到最上面,养成习惯了,上面的8篇文章一开始读的话最好读第5篇,其他的内容都会在第5篇中大概的提到。另外为了懒得跑到另一篇文章去看图解,所以图解可能会反复出现在每篇文章中。


前言完毕了,第一篇本来是想直接把rnnlmlib.h直接放上来,然后介绍整个网络轮廓,大概看一下相关接口函数功能,但是那样内容发现有点长了,看上去都容易打瞌睡。于是把rnnlmlib.h分成两部分,第一篇文章介绍其中的成员变量,因为理解这些成语变量才能清楚的知道rnnlm toolkit命令参数的具体含义;第二篇介绍其中的成员函数,不涉及具体实现(那是后续文章的事儿),只是大概知道函数什么功能即可,估计第二篇内容稍短。


我先把整个网络的图放上来,然后可以对应着看,下面的图是对应源代码所抽象出来的,我会把实际论文的模型图放上来做参照对比。
《rnnlm源码分析(一)》





下面的图是论文中的图,可以很明显的注意到实现和模型图还是有点差别,实现的时候把w(t)和s(t-1)、y(t)和c(t)合并成一个层了.



《rnnlm源码分析(一)》





这里的图的输出层是经过分解的,为了加速。我后面的文章会介绍到和未分解的图和分解的计算。第一篇的目的就在于大概的了解真个网络的结构,宏观的看一看。下面是rnnlmlib.h文件的内容,虽然直接把成员变量切出来让类不完整,但没办法,成员函数在第二篇中介绍。
rnnlmlib.h内容如下:
///////////////////////////////////////////////////////////////////////
  

//
  

// Recurrent neural network based statistical language modeling toolkit
  

// Version 0.4a
  

// (c) 2010-2012 Tomas Mikolov (tmikolov@gmail.com)
  

// (c) 2013 Cantab Research Ltd (info@cantabResearch.com)
  

//
  

///////////////////////////////////////////////////////////////////////
  

  

  

//这里的作用是防止rnnlmlib.h重复被include
  

//如果程序第一次包含rnnlmlib.h,将会把#ifndef到文件最后一行的#endif之间的内容都执行
  

//如果程序不是第一次包含rnnlmlib.h,则该文件的内容会被跳过
  

#ifndef _RNNLMLIB_H_        
  

#define _RNNLMLIB_H_        
  

  

//最大字符串的长度                      
  

#define MAX_STRING 100
  

  

//防止WEIGHTTYPE被重复定义
  

#ifndef WEIGHTTYPE      
  

//权重类型,这里可以手动更改为float   
  

#define WEIGHTTYPE double
  

#endif
  

  

//real用于rnn中神经元的激活值,误差值类型
  

typedef
 WEIGHTTYPE real;    
// NN weights
  

  

  

//direct_t表示最大熵模型中输入层到输出层权值类型
  

typedef
 WEIGHTTYPE direct_t;    
// ME weights
  

  

  

//rnn中神经元结构,两部分
  

//ac表示激活值,er表示误差值,er用在网络学习时
  

struct
 neuron {  

    real ac;        
//actual value stored in neuron
  

    real er;        
//error value in neuron, used by learning algorithm
  

};  

  

  

//突触,这里是表示网络层与层之间参数权值的结构
  

//其实就是浮点类型,只是包上了一层,这样更形象                
  

struct
 synapse {  

    real weight;    
//weight of synapse
  

};  

  

  

//这是一个word的结构定义
  

struct
 vocab_word {  

      

    
//cn表示这个word在train_file中出现的频数
  

    
int
 cn;  

      

    
//这个表示word本身,是字符串,但长度不能超过100
  

    
char
 word[MAX_STRING];  

      

    
//这个应该是在概率分布时表示当前词在历史下的条件概率
  

    
//但是后面的代码中我没看到怎么使用这个定义,感觉可以忽略
  

    real prob;  

      

    
//这个表示当前词所在哪个类别
  

    
int
 class_index;  

};  

  

  

  

//PRIMES[]这个数组装都是质数,质数的用处是来做散列函数的
  

//对散列函数了解不多,个人理解可以使散列函数更少的冲突吧
  

const
 unsigned 
int
 PRIMES[]={108641969, 116049371, 125925907, 133333309, 145678979, 175308587, 197530793, 234567803, 251851741, 264197411, 330864029, 399999781,  

407407183, 459258997, 479012069, 545678687, 560493491, 607407037, 629629243, 656789717, 716048933, 718518067, 725925469, 733332871, 753085943, 755555077,  

782715551, 790122953, 812345159, 814814293, 893826581, 923456189, 940740127, 953085797, 985184539, 990122807};  

  

//PRIMES数组长度,这个用法可以积累一下,以后自己的程序也可以使用
  

const
 unsigned 
int
 PRIMES_SIZE=
sizeof
(PRIMES)/
sizeof
(PRIMES[0]);  

  

  

//最大阶数,这个是用来限制最大熵模型的N元模型特征的,N不能无穷大,这里最大是20
  

const
 
int
 MAX_NGRAM_ORDER=20;  

  

  

//文件存储类型,TEXT表示ASCII存储,对存储网络权值时,有点浪费空间
  

//BINARY表示二进制方式存储,对网络权值进行存储时,能更省空间,但是不便于阅读
  

enum
 FileTypeEnum {TEXT, BINARY, COMPRESSED};       
//COMPRESSED not yet implemented
  

  

  

//这个类就是RNN的结构定义
  

class
 CRnnLM{  

protected
:  

      

    
////训练数据集的文件名
  

    
char
 train_file[MAX_STRING];          

      

    
//验证数据集的文件名
  

    
char
 valid_file[MAX_STRING];                  

      

    
//测试数据集的文件名
  

    
char
 test_file[MAX_STRING];           

      

    
//RNN训练好后的模型所存储的文件
  

    
char
 rnnlm_file[MAX_STRING];  

      

    
//其他语言模型对测试数据的生成文件,比如用SRILM
  

    
char
 lmprob_file[MAX_STRING];  

      

      

    
//随机种子,不同的rand_seed,可以导致网络权值初始化为不同的随机数
  

    
int
 rand_seed;  

      

    
//debug_mode分为两个级别,debug_mode>0会输出一些基本信息
  

    
//debug_mode>1会输出更详细的信息
  

    
int
 debug_mode;  

      

    
//rnn toolkit的版本号
  

    
int
 version;  

      

    
//用来指示存储模型参数时用TEXT, 还是用BINARY
  

    
int
 filetype;  

      

    
//控制开关,use_lmprob为0时表示不使用
  

    
//为1时表示使用了其他语言模型,并会将RNN和其他语言模型插值
  

    
int
 use_lmprob;  

      

    
//上面所说的插值系数
  

    real lambda;  

      

    
//防止误差过大增长,用gradient_cutoff进行限制
  

    
//gradient_cutoff的使用在矩阵相乘那个函数里面可以看到
  

    real gradient_cutoff;  

      

    
//dynamic如果大于0表示在测试时,边测试边学习 
  

    real dynamic;  

      

    
//学习率
  

    real alpha;  

      

    
//训练初始的学习率
  

    real starting_alpha;  

      

    
//变量控制开关,为0表明不将alpha减半,具体见代码
  

    
int
 alpha_divide;  

      

    
//logp表示累计对数概率,即logp = log10w1 + log10w2 + log10w3…
  

    
//llogp是last logp,即上一个logp
  

    
double
 logp, llogp;   

      

    
//最小增长倍数
  

    
float
 min_improvement;  

      

    
//iter表示整个训练文件的训练次数
  

    
int
 iter;         

      

    
//vocab_max_size表示vocab最大容量,但是在代码中这个是动态增加的
  

    
int
 vocab_max_size;  

      

    
//表示vocab的实际容量
  

    
int
 vocab_size;  

      

    
//记录train_file有多少word
  

    
int
 train_words;          

      

    
//指示当前所训练的词在train_file是第几个
  

    
int
 train_cur_pos;  

    
int
 counter;  

      

    
//one_iter==1的话,只会训练一遍
  

    
int
 one_iter;  

      

    
//对train_file最大的训练遍数
  

    
int
 maxIter;  

      

    
//表示每训练anti_k个word,会将网络信息保存到rnnlm_file
  

    
int
 anti_k;       

      

      

    
//L2正规化因子
  

    
//实际在用的时候,是用的beta*alpha
  

    real beta;  

      

    
//指定单词所要分类别
  

    
int
 class_size;  

      

    
//class_words[i-1][j-1]表示第i类别中的第j个词在vocab中的下标
  

    
int
 **class_words;  

      

    
//class_cn[i-1]表示第i个类别中有多少word
  

    
int
 *class_cn;  

      

    
//class_max_cn[i-1]表示第i类别最多有多少word
  

    
int
 *class_max_cn;  

      

    
//old_classes大于0时用一种分类词的算法,否则用另一种
  

    
int
 old_classes;  

      

      

    
//vocab里面存放的是不会重复的word,类型为vocab_word
  

    
struct
 vocab_word *vocab;  

      

    
//选择排序,将vocab[1]到vocab[vocab_size-1]按照他们出现的频数从大到小排序
  

    
void
 sortVocab();  

      

    
//里面存放word在vocab中的下标,这些下标是通过哈希函数映射来的
  

    
int
 *vocab_hash;  

      

    
//vocab_hash的大小
  

    
int
 vocab_hash_size;  

      

    
//输入层的大小
  

    
int
 layer0_size;  

      

    
//隐层的大小
  

    
int
 layer1_size;  

      

    
//压缩层的大小
  

    
int
 layerc_size;  

      

    
//输出层的大小
  

    
int
 layer2_size;  

      

    
//表示输出层到输出层直接连接的权值数组的大小
  

    
long
 
long
 direct_size;  

      

    
//最大熵模型所用特征的阶数
  

    
int
 direct_order;  

      

    
//history从下标0开始存放的是wt, wt-1,wt-2…
  

    
int
 history[MAX_NGRAM_ORDER];         

      

    
//bptt<=1的话,就是常规的bptt,即只从st展开到st-1
  

    
int
 bptt;  

      

    
//每训练bptt_block个单词时,才会使用BPTT(或设置indenpendt不等于0,在句子结束时也可以进行BPTT)
  

    
int
 bptt_block;  

      

    
//bptt_history从下标0开始存放的是wt,wt-1,wt-2…
  

    
int
 *bptt_history;        

      

    
//bptt_hidden从下标0开始存放的是st,st-1,st-2…
  

    neuron *bptt_hidden;      

      

    
//隐层到输入层的权值,这个使用在BPTT时的
  

    
struct
 synapse *bptt_syn0;  

      

    
int
 gen;  

      

    
//independent非0,即表示要求每个句子独立训练
  

    
//如果independent==0,表面上一个句子对下一个句子的训练时算作历史信息的
  

    
//这控制还得看句子与句子之间的相关性如何了
  

    
int
 independent;  

      

    
//下面就只用用源码中的英文注释了,懒得敲了,感觉英文注释的很清楚
  

    
//neurons in input layer
  

    
struct
 neuron *neu0;          

      

    
//neurons in hidden layer
  

    
struct
 neuron *neu1;          

      

    
//neurons in hidden layer
  

    
struct
 neuron *neuc;          

      

    
//neurons in output layer
  

    
struct
 neuron *neu2;          

      

    
//weights between input and hidden layer
  

    
struct
 synapse *syn0;         

      

    
//weights between hidden and output layer (or hidden and compression if compression>0)
  

    
struct
 synapse *syn1;         

      

    
//weights between hidden and compression layer
  

    
struct
 synapse *sync;         

      

    
//direct parameters between input and output layer (similar to Maximum Entropy model parameters)
  

    direct_t *syn_d;          

      

    
//backup used in training:
  

    
struct
 neuron *neu0b;  

    
struct
 neuron *neu1b;  

    
struct
 neuron *neucb;  

    
struct
 neuron *neu2b;  

      

    
struct
 synapse *syn0b;  

    
struct
 synapse *syn1b;  

    
struct
 synapse *syncb;  

    direct_t *syn_db;  

      

    
//backup used in n-bset rescoring:
  

    
struct
 neuron *neu1b2;  
      
      

    
public
:  
          

        
int
 alpha_set, train_file_set;  

};  
  
#endif  


    原文作者:a635661820
    原文地址: https://blog.csdn.net/a635661820/article/details/44755847
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞