您的位置:首页 > 理论基础 > 计算机网络

循环神经网络(RNN, Recurrent Neural Networks)学习笔记:源码分析(一)

2016-11-17 21:16 786 查看
       前面帖子给出了RNN的基础理论,里面也提到了神牛Mikolov,这个帖子就基于此牛开源出的一个语言建模工具箱(RNN Language Modeling Tookit)进行代码走读,会加速理解RNN算法及利用RNN进行语言建模,代码在github上的链接在这里:https://github.com/mspandit/rnnlm。btw:大致把github上几个RNN的代码看了一遍,感觉还是Mikolov这个最容易上手,很适合初学者入门使用,当然,我没有测试这个project的性能,因为我的精力主要是基于ML做语音增强相关的工作,暂时顾不上LM方面。

        另外,对这个代码的分析也参考了这位同学的blog,http://blog.csdn.net/a635661820/article/details/44755847。

首先来一张大牛论文中抽象出的RNN网络结构图,这个图跟前面帖子中的图稍有不同,无非就是前面基础理论中的输出层只是y(t),而LM中神牛增加了一个class层c(t),存放所有单词的类别。





#defineMAX_STRING
100#ifndef
WEIGHTTYPE#defineWEIGHTTYPE
double#endif//real用于rnn中神经元的激活值,误差值的数值精度typedefWEIGHTTYPE
real;
// NN weights//direct_t表示最大熵模型中输入层到输出层权值的数值精度typedefWEIGHTTYPE
direct_t;
// ME weights//rnn中神经元结构,两部分//ac表示激活值,er表示误差值,er用在网络学习时structneuron
{ real
ac;
//actual value stored in neuron
real er;//error value in neuron, used by learning algorithm};//神经突触(权值),这里是表示网络层与层之间参数权值的结构//其实就是浮点类型,只是包上了一层,这样更形象structsynapse
{ real
weight;
//weight of synapse};//这是一个word的结构定义structvocab_word
{ //cn表示这个word在train_file中出现的频数int
cn;
//这个表示word本身,是字符串,但长度不能超过MAX_STRING,可调
char
word[MAX_STRING];//这个应该是在概率分布时表示当前词在历史下的条件概率
//但是后面的代码中我没看到怎么使用这个定义,感觉可以忽略
real prob;//这个表示当前词所属的类别,训练时要把所有的word进行分组归类(分组的依据暂不清楚)int
class_index;};//PRIMES[]这个数组存的都是质数,用来做散列函数constunsigned
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数组长度constunsigned
int
PRIMES_SIZE=sizeof(PRIMES)/sizeof(PRIMES[0]);//最大阶数,这个是用来限制最大熵模型的N元模型特征的,N不能无穷大,这里最大是20constint
MAX_NGRAM_ORDER=20;//文件存储类型,TEXT表示ASCII存储,对存储网络权值时,有点浪费空间//BINARY表示二进制方式存储,对网络权值进行存储时,能更省空间,但是不便于阅读enumFileTypeEnum
{TEXT,BINARY,
COMPRESSED};
//COMPRESSED not yet implementedclassCRnnLM{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会输出更详细的信息
intdebug_mode;int
version;
//用来指示存储模型参数时用TEXT, 还是用BINARY
intfiletype;//控制开关,use_lmprob为0时表示不使用
//为1时表示使用了其他语言模型,并会将RNN和其他语言模型插值
intuse_lmprob;//上面所说的将RNN和其他语言模型插值使用的插值系数
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的实际容量
intvocab_size;//记录train_file有多少word
int
train_words;//指示当前所训练的词在train_file中的位置(序号)
int
train_cur_pos;int
counter;
//one_iter==1的话,只会训练一遍
intone_iter;//对train_file最大的训练遍数
int
maxIter;//表示每训练anti_k个word,会将网络信息保存到rnnlm_fileint
anti_k;
//L2正规化因子
//实际在用的时候,是用的beta*alphareal
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;//隐层的大小
intlayer1_size;//压缩层的大小
int
layerc_size;//输出层的大小
int
layer2_size;//表示输出层到输出层直接连接的权值数组的大小
long
longdirect_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;struct
neuron
*neu0;//neurons in input layer
struct
neuron*neu1;//neurons in hidden layer
struct
neuron*neuc;//neurons in copressed layer
struct
neuron*neu2;//neurons in output layer
struct
synapse*syn0;//weights between input and hidden layerstruct
synapse
*syn1;//weights between hidden and output layer (or hidden and compression if compression>0)struct
synapse
*sync;//weights between output and compression layerdirect_t
*syn_d;//direct parameters between input and output layer (similar to Maximum Entropy model parameters)//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;
CRnnLM()
//constructor initializes variables
{ version=10;filetype=TEXT;use_lmprob=0;lambda=0.75;gradient_cutoff=15;dynamic=0;train_file[0]=0;valid_file[0]=0;test_file[0]=0;rnnlm_file[0]=0;alpha_set=0;train_file_set=0;alpha=0.1;beta=0.0000001;//beta=0.00000;
alpha_divide=0;logp=0;llogp=-100000000;iter=0;min_improvement=1.003;train_words=0;train_cur_pos=0;vocab_max_size=100;vocab_size=0;vocab=(structvocab_word
*)calloc(vocab_max_size,sizeof(structvocab_word));layer1_size=30;direct_size=0;direct_order=0;bptt=0;bptt_block=10;bptt_history=NULL;bptt_hidden=NULL;bptt_syn0=NULL;gen=0;independent=0;neu0=NULL;neu1=NULL;neuc=NULL;neu2=NULL;syn0=NULL;syn1=NULL;sync=NULL;syn_d=NULL;syn_db=NULL;//backup
neu0b=NULL;neu1b=NULL;neucb=NULL;neu2b=NULL;neu1b2=NULL;syn0b=NULL;syn1b=NULL;syncb=NULL;//
rand_seed=1;class_size=100;old_classes=0;one_iter=0;maxIter=0;debug_mode=1;srand(rand_seed);//word映射为哈希的值小于100000000
vocab_hash_size=100000000;//动态分配内存,calloc会自动将申请的内存初始化为0,但这里奇怪申请这么大空间,这里没对vocab_hash做检查vocab_hash=(int*)calloc(vocab_hash_size,sizeof(int));}
~CRnnLM()//destructor, deallocates memory
{ //析构,省略
} //返回值类型为real且范围在[min, max]的数real
random(real min,real max);void
setTrainFile(char*str);void
setValidFile(char*str);void
setTestFile(char*str);//设置模型保存文件,即该文件用来存储模型的信息,以及各类参数
void
setRnnLMFile(char*str);void
setLMProbFile(char*str){strcpy(lmprob_file,str);}
void
setFileType(intnewt)
{filetype=newt;}void
setClassSize(intnewSize){class_size=newSize;}void
setOldClasses(intnewVal)
{old_classes=newVal;}void
setLambda(realnewLambda){lambda=newLambda;}void
setGradientCutoff(realnewGradient){gradient_cutoff=newGradient;}void
setDynamic(realnewD)
{dynamic=newD;}void
setGen(realnewGen)
{gen=newGen;}void
setIndependent(intnewVal)
{independent=newVal;}void
setLearningRate(realnewAlpha){alpha=newAlpha;}void
setRegularization(realnewBeta){beta=newBeta;}void
setMinImprovement(realnewMinImprovement){min_improvement=newMinImprovement;}void
setHiddenLayerSize(intnewsize){layer1_size=newsize;}void
setCompressionLayerSize(intnewsize){layerc_size=newsize;}void
setDirectSize(longlong
newsize)
{direct_size=newsize;}void
setDirectOrder(intnewsize){direct_order=newsize;}void
setBPTT(intnewval)
{bptt=newval;}void
setBPTTBlock(intnewval)
{bptt_block=newval;}void
setRandSeed(intnewSeed){rand_seed=newSeed;srand(rand_seed);}void
setDebugMode(intnewDebug){debug_mode=newDebug;}void
setAntiKasparek(intnewAnti){anti_k=newAnti;}void
setOneIter(intnewOneIter){one_iter=newOneIter;}void
setMaxIter(intnewMaxIter){maxIter=newMaxIter;}//返回单词的哈希值
int
getWordHash(char*word);//从文件中读取一个单词到word
void
readWord(char*word,FILE
*fin);
//查找word,找到返回word在vocab中的索引,没找到返回-1int
searchVocab(char*word);//读取当前文件指针所指的单词,并返回该单词在vocab中的索引
int
readWordIndex(FILE*fin);//将word添加到vocab中,并且返回刚添加word在vocab中的索引int
addWordToVocab(char*word);//从train_file中读数据,相关数据会装入vocab,vocab_hash//这里假设vocab是空的
void
learnVocabFromTrainFile();//train_file will be used to construct vocabulary//保存当前的权值,以及神经元信息值
void
saveWeights();//saves current weights and unit activations//上面是暂存当前权值及神经元值,这里是从前面存下的数据中恢复
void
restoreWeights();//restores current weights and unit activations from backup copy//void saveWeights2(); //allows 2. copy to be
stored, useful for dynamic rescoring of nbest lists//void restoreWeights2();
//保存隐层神经元的ac值
voidsaveContext();//恢复隐层神经元的ac值
void
restoreContext();//保存隐层神经元的ac值
void
saveContext2();//恢复隐层神经元的ac值
void
restoreContext2();//初始化网络
voidinitNet();//保存网络的所有信息到rnnlm_file
void
saveNet();//从文件流中读取一个字符使其ascii等于delim
//随后文件指针指向delim的下一个
voidgoToDelimiter(int delim,FILE
*fi);
//从rnnlm_file中读取网络的所有信息void
restoreNet();
//清除神经元的ac,er值
voidnetFlush();//隐层神经元(论文中的状态层s(t))的ac值置1
//s(t-1),即输入层layer1_size那部分的ac值置1
//bptt+history清0void
netReset();
//will erase just hidden layer state + bptt history + maxent history (called at end of sentences in the independent mode)//网络前向,计算概率分布
void
computeNet(int last_word,int
word);
//反传误差,更新网络权值
voidlearnNet(int last_word,int
word);
//将隐层神经元的ac值复制到输入层后layer1_size那部分
voidcopyHiddenLayerToInput();//训练网络
voidtrainNet();void
useLMProb(intuse)
{use_lmprob=use;}//测试网络
voidtestNet();void
testNbest();
void
testGen();//矩阵和向量相乘
//1.type == 0时,计算的是神经元ac值,相当于计算srcmatrix × srcvec, 其中srcmatrix是(to-from)×(to2-from2)的矩阵//srcvec是(to2-from2)×1的列向量,得到的结果是(to-from)×1的列向量,该列向量的值存入dest中的ac值//2.type
== 1, 计算神经元的er值,即(srcmatrix)^T × srcvec,T表示转置,转置后是(to2-from2)×(to-from),srcvec是(to-from)×1的列向量void
matrixXvector(structneuron
*dest,
structneuron
*srcvec,
struct
synapse*srcmatrix,int matrix_width,int
from,int to,int from2,int
to2,int type);};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: