因为需要使用 lstm,lstm的代码,和官方教程解释得不是很详细,故对 theano lstm进行一些分析理解。
整体框架理解
这份代码实现的功能是利用RNN(LSTM)对IMDB每部电影的评论页面的评论进行情感分类。
这里的数据集比较特殊。一个包含所有句子的二重列表,列表的每个元素也为一个列表。
大小为2的tuple,train[0][n] 代表一个句子(对于词库索引的List),train[1][n]为该句子的情感分类标签。
举个简单的栗子,
把整个过程中数据的维度的变化画出来,我觉得更利于理解程序的流程(结合代码看体验更佳)。
各个函数分析
def get_minibatches_idx(n, minibatch_size, shuffle=False):
返回值:zip(range(len(minibatches)), minibatches)
该函数得到了 shuffle 后的个数为 (n/batch_size)+1个batch,每个batch为minibatch_size的索引
def init_params(options):
初始化词向量的空间 n_words(10000)*dim_proj(128) 和
logistic regression 的参数U(dim_proj*ydim),b(ydim=2)
调用param_init_lstm,初始化了lstm相关的参数
def param_init_lstm(options, params, prefix=’lstm’):
这里LSTM的初始化也比较特殊,由于LSTM中,input gate,forget gate,output gate,input node(Candidate
memory cell)的表达式的形式都是一样(如下图所示),源代码的作者利用一个[dim,(dim_proj*4)]的
2D矩阵表示Wi,Wf,Wo,Wc(如上面流程图中所示)。这些参数先通过rand随机初始化,再利用svd得到正交基矩阵。
LSTM的内部结构
def lstm_layer(tparams, state_below, options, prefix=’lstm’, mask=None):
这个函数是lstm实现的关键,具体的流程已经在上面的流程图中画出来了。这个函数有几点需要注意的地方
1.在step_函数中,c = m_[:, None] * c + (1. - m_)[:, None] * c_
该语句,中m_[:,None],slicing with None对array m_来说增加了一个维度,因为这里c是一个[dim_proj,batch]
的矩阵,而m_是一个向量(同一time step 不同batch sample的mask),m_[:,None]扩展到了[dim_proj,batch]
另外,这个递推公式,保证了当m_中为0时,第一项为0,c会一直保持前面时刻(句子的最后一个词)c的状态,
当m_中元素为1,c保持计算的结果不变。意思就是,对于长度小于maxlen的句子,会补零,但是在这些0位置处,
memory cell的状态采用了句子的最后一个单词计算的状态memory cell c进行填充。
2.scan函数的使用
scan的计算过程会将参数sequences中的矩阵,按照axis=0(第一维度,纵向)进行切片,每次只取一个
time step 的序列数据,即不同句子的相同位置(time step)的单词进行运算。序列维度为
[step,batch,dim_proj],具体的参考上面的流程图。关于theano中scan函数的详细用法,参考文末的链接。
3.scan函数的返回值有两个,第一个是_step的返回值(多个time step组成的),还有一个updates,updates
的作用是来更新_step中返回的shared variable,但是_setp没有这样的变量返回,故这个东西在此处没有用处。
def build_model(tparams, options):
这个函数里面,难理解的应该就是一数据的一些转换。将索引的矩阵先通过flatten换成1D的向量,再转换
成词向量矩阵2D,最后reshape成[step,batch,dim_proj]的3D矩阵。调用lstm_layer,得到proj,再通过mean
pooling运算得到h矩阵。并求得网络的cost
def pred_probs(f_pred_prob, prepare_data, data, iterator, verbose=False):
用于预测一些新的example,输出预测结果为[n_sample,y_dim]的矩阵
def pred_error(f_pred, prepare_data, data, iterator, verbose=False):
计算误差,传入函数的地址作为参数,f_pred(预测的函数,输出结果为一个n_sample的向量)
prepare_data : 补零,加mask
def train_lstm(…)
这个函数train_lstm中设置了一些参数,例如
1.隐含层的个数 dim_proj=128 #既是词向量的维度,又是LSTM中Hideden Units的个数
2.优化方法 optimizer=adadelta
还有一些其他的训练参数就不一一列举了
options=locals().copy(),它将函数中所有参数复制,保存为一个词典
利用build_lstm求得cost,再结合tparams,利用tensor.grad求得梯度(Theano这点真的很赞!)
后面程序的结构,用f_grad_shared传出cost,再用f_update,更新网络。
这两个函数(f_grad_shared,f_update)都是optimizer函数,即adadelta的输出。
最后,就是一些很繁琐的数据检查,和利用early_stop训练等等。
参考链接
1.官方教程
2.原作者的一些FAQ
3.Physcal大神解析
4.word Embeddings
5.scan函数的用法
6.flatten函数的用法
Note:
非常关键的代码解析,链接为http://www.cnblogs.com/neopenx/p/4806006.html
AdaDelta自适应学习率:http://www.cnblogs.com/neopenx/p/4768388.html