RNN、LSTM、GRU比较及部分部分代码实现与讲解

原创 2018年04月14日 22:22:24

RNN(Recurrent Neural Network), LSTM(Long Short-Term Memory)与GRU(Gated Recurrent Unit)都是自然语言处理领域常见的深度学习模型。本文是一个关于这些模型的笔记,依次简单介绍了RNN, LSTM和GRU。在学习了大量的语言样本,从而建立一个自然语言的模型之后,可以实现下列两种功能。

  • 可以为一个句子打分,通过分值来评估句子的语法和语义的正确性。这个功能在机器翻译系统中非常有用。
  • 可以造句,能够模仿样本中语言的文风造出类似的句子。

RNN

RNN的定义

在传统的神经网络中,输入是相互独立的,但是在RNN中则不是这样。一条语句可以被视为RNN的一个输入样本,句子中的字或者词之间是有关系的,后面字词的出现要依赖于前面的字词。RNN被称为并发的(recurrent),是因为它以同样的方式处理句子中的每个字词,并且对后面字词的计算依赖于前面的字词。一个典型的RNN如下图所示。
这里写图片描述
图中左边是RNN的一个基本模型,右边是模型展开之后的样子。展开是为了与输入样本匹配。假若输入时汉语句子,每个句子最长不超过20(包含标点符号),则把模型展开20次。

  • x t  xt代表输入序列中的第t t步元素,例如语句中的一个汉字。一般使用一个one-hot向量来表示,向量的长度是训练所用的汉字的总数(或称之为字典大小),而唯一为1的向量元素代表当前的汉字。
  • s t  st代表第t t步的隐藏状态,其计算公式为s t =tanh(Ux t +Ws t1 ) st=tanh(Uxt+Wst−1)。也就是说,当前的隐藏状态由前一个状态和当前输入计算得到。考虑每一步隐藏状态的定义,可以把s t  st视为一块内存,它保存了之前所有步骤的输入和隐藏状态信息。s 1  s−1是初始状态,被设置为全0。
  • o t  ot是第t t步的输出。可以把它看作是对第t+1 t+1步的输入的预测,计算公式为:o t =softmax(Vs t ) ot=softmax(Vst)。可以通过比较o t  otx t+1  xt+1之间的误差来训练模型。
  • U,V,W U,V,W是RNN的参数,并且在展开之后的每一步中依然保持不变。这就大大减少了RNN中参数的数量。

假设我们要训练的中文样本中一共使用了3000个汉字,每个句子中最多包含50个字符,则RNN中每个参数的类型可以定义如下。

  • x t R 3000  xt∈R3000,第t t步的输入,是一个one-hot向量,代表3000个汉字中的某一个。
  • o t R 3000  ot∈R3000,第t t步的输出,类型同x t  xt
  • s t R 50  st∈R50,第t t步的隐藏状态,是一个包含50个元素的向量。RNN展开后每一步的隐藏状态是不同的。
  • UR 503000  U∈R50∗3000,在展开后的每一步都是相同的。
  • VR 300050  V∈R3000∗50,在展开后的每一步都是相同的。
  • WR 5050  W∈R50∗50,在展开后的每一步都是相同的。

其中x t  xt是输入,U,V,W U,V,W是参数,s t  st是由输入和参数计算所得到的隐藏状态,而o t  ot则是输出。s t  sto t  ot的计算公式已经给出,为清晰起见,重新写出。

  • s t =tanh(Ux t +Ws t1 ) st=tanh(Uxt+Wst−1)
  • o t =softmax(Vs t ) ot=softmax(Vst)

RNN的训练

为了训练网络,必须对其进行训练。需要计算预测字和输入字之间的误差来修改网络中的参数,进而优化模型。使用cross-entropy损失函数来计算误差。假设输入文本中有N N个字(总字数,N N个字中间可能有重复出现的),而字典大小为C C,则正确输入y y和预测输出o o之间的总误差可以用如下的公式来表示。

L(y,o)=1N Σ nN y n logo n  L(y,o)=−1NΣn∈Nynlogon

训练的目的是找到合适的U,V,W U,V,W,使得误差函数的取值最小。按照深度学习的传统做法,使用随机梯度下降法SGD(Stochastic Gradient Descent),也就是要求出误差函数对U,V,W U,V,W的偏导数LU ,LV ,LW  ∂L∂U,∂L∂V,∂L∂W。传统深度学习算法在对参数求导时,使用了后向传播(Backpropagation)算法,但是在RNN中,因为要考虑到时序因素,所以使用的是“经历时间的后向传播算法”,BPTT(Backpropagation Through Time)。下面通过例子展示它与传统的算法存在着不同。

为方便描述,在误差函数L L的基础上,我们重新定义了一个函数。

E t (y t ,o t )=y t logo t  Et(yt,ot)=−ytlogot

那么,根据之前的定义,可以知道L LE t  Et之间存在着下述关系。
L=1N Σ tN E t  L=1NΣt∈NEt

不失一般性,考虑E 3  E3V V的求导。

E 3 V =E 3 o 3  o 3 V  ∂E3∂V=∂E3∂o3∂o3∂V

根据o 3  o3的定义,它的值取决于V Vs 3  s3的乘积,而s 3  s3的取值与V V无关,所以上面式子成立。值得注意的是,E 3  E3的运算结果是一个one-hot向量,V V是一个矩阵。所以E 3 V  ∂E3∂V是一个矩阵的形式,初步展开如下。

E 3 =⎡ ⎣ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢  y (1) 3 logo (1) 3   ......  y (3000) 3 logo (3000) 3   ⎤ ⎦ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ E 3 V =⎡ ⎣ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢  (y (1) 3 logo (1) 3 )/V  ......  (y (3000) 3 logo (3000) 3 )/V  ⎤ ⎦ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥  E3=[ −y3(1)logo3(1)  ......  −y3(3000)logo3(3000) ]⇒∂E3∂V=[ ∂(−y3(1)logo3(1))/∂V  ......  ∂(−y3(3000)logo3(3000))/∂V ]

从上面式子可以看出,误差函数对参数V V的求导与输入的序列特性没有关系。但是,对参数W W的求导则不同。首先,求偏导的式子应该如下定义。

E 3 W =E 3 o 3  o 3 s 3  s 3 W  ∂E3∂W=∂E3∂o3∂o3∂s3∂s3∂W

但是s 3 W  ∂s3∂W的计算则比较复杂。因为根据s 3  s3的定义s 3 =tanh(Ux t +Ws 2 ) s3=tanh(Uxt+Ws2)s 2  s2也是一个与输入W W有关的函数。所以有下列式子。

s 3 W =s 3 W +s 3 s 2  s 2 W  ∂s3W=∂s3W+∂s3s2∂s2W

依次类推下去,可以有下列等式。

E 3 W =E 3 o 3  o 3 s 3  (s 3 W +s 3 s 2  s 2 W +s 3 s 2  s 2 s 1  s 1 W ) ∂E3∂W=∂E3∂o3∂o3∂s3(∂s3∂W+∂s3∂s2∂s2∂W+∂s3∂s2∂s2∂s1∂s1∂W)

从上面式子可知,在对W W求导时,需要从当前步骤的输入回溯到之前所有步骤,因此不可避免有偏导数的反馈消失问题。

LSTM

LSTM是为了解决RNN中的反馈消失问题而被提出的模型,它也可以被视为RNN的一个变种。与RNN相比,增加了3个门(gate):input门,forget门和output门,门的作用就是为了控制之前的隐藏状态、当前的输入等各种信息,确定哪些该丢弃,哪些该保留,如下图所示。
这里写图片描述
LSTM的隐藏状态g g的计算公式与RNN的类似:g=tanh(U g x t +W g s t1 ) g=tanh(Ugxt+Wgst−1)。但是这个隐藏状态的输出却受到了各种门的控制。内部存储用c c来表示,它由前一步的内部存储和当前的隐藏状态计算得出,并且受到input门和forget门的控制。前者确定当前隐藏状态中需要保留的信息,后者确定前一步的内部存储中需要保留的信息:c t =c t1 f+gi ct=ct−1∘f+g∘i。LSTM的输出则使用s t  st来表示,并且受输出门的控制:s t =tanh(c t )o st=tanh(ct)∘o。综上所述,第t步的LSTM中输出信息的计算公式如下。

  • i=σ(U i x t +W i s t1 ) i=σ(Uixt+Wist−1)
  • f=σ(U f x t +W f s t1 ) f=σ(Ufxt+Wfst−1)
  • o=σ(U o x t +W o s t1 ) o=σ(Uoxt+Wost−1)
  • g=tanh(U g x t +W g s t1 ) g=tanh(Ugxt+Wgst−1)
  • c t =c t1 f+gi ct=ct−1∘f+g∘i
  • s t =tanh(c t )o st=tanh(ct)∘o

公式中的变量i,f,o,g,c t  i,f,o,g,ct的数据类型与s t  st一样,是一个向量。圆点表示向量之间逐个元素相乘而得到一个新向量。这些式子具有以下特点。

  • 三个门input、forget、output具有相同的形式,只是参数不同。它们各自的参数U,W U,W都需要在对样本的训练过程中学习。
  • 隐藏状态g g的计算与RNN中的隐藏状态相同,但是不能直接使用,必须通过input门的约束,才能够作用到内部存储c t  ct之中。
  • 当前的内部存储的计算,不仅依赖于当前的隐藏状态,也依赖于前一步的内部存储c t1  ct−1,并且c t1  ct−1受forget门的约束。
  • 输出信息在c t  ct的基础上又施加了一层tanh函数,并且受到输出门的约束。
  • 如果input门全为1,forget门全为0,output门全为1的话,则LSTM与RNN相似,只是多了一层tanh函数的作用。

总之,门机制的存在,就使得LSTM能够显示地为序列中长距离的依赖建模,通过对门参数的学习,网络能够找到合适的内部存储行为。

GRU

GRU具有与LSTM类似的结构,但是更为简化,如下图所示。
这里写图片描述
GRU中状态与输出的计算包含以下步骤。

  • z=σ(U z x t +W z s t1 ) z=σ(Uzxt+Wzst−1)
  • r=σ(U r x t +W r s t1 ) r=σ(Urxt+Wrst−1)
  • h=tanh(U h x t +W h (s t1 r)) h=tanh(Uhxt+Wh(st−1∘r))
  • s t =(1z)h+zs t1  st=(1−z)∘h+z∘st−1

与LSTM相比,GRU存在着下述特点。

  • 门数不同。GRU只有两个门reset门r和update门z。
  • 在GRU中,r和z共同控制了如何从之前的隐藏状态(s t1  st−1)计算获得新的隐藏状态(s t  st),而取消了LSTM中的output门。
  • 如果reset门为1,而update门为0的话,则GRU完全退化为一个RNN。

LSTM与GRU的比较

经过实验,一般认为,LSTM和GRU之间并没有明显的优胜者。因为GRU具有较少的参数,所以训练速度快,而且所需要的样本也比较少。而LSTM具有较多的参数,比较适合具有大量样本的情况,可能会获得较优的模型。





RNN学习笔记(五)-RNN 代码实现

https://blog.csdn.net/rtygbwwwerr/article/details/51012699

1.语言模型(LM)简述

做过NLP任务的童鞋应该都知道什么是语言模型,简单来说,如果我们把一句话s看做是由若干个(n个)词w组成的集合,那么这句话生成的概率就为:
P(s)=p(w 1 ,w 2 ,...,w n )=p(w 1 )p(w 2 |w 1 )p(w 3 |w 1 ,w 2 )...p(w n |w 1 ,...,w n1 ) P(s)=p(w1,w2,...,wn)=p(w1)p(w2|w1)p(w3|w1,w2)...p(wn|w1,...,wn−1)
显然,右边的式子非常难以计算,它需要估算n个联合条件分布率。通常我们会做一些假设,比如,假设每个词的出现只依赖它的前一个词。那么上式就可以化简为:
P(s)=p(w 1 )p(w 2 |w 1 )p(w 3 |w 2 )...p(w n |w n1 ) P(s)=p(w1)p(w2|w1)p(w3|w2)...p(wn|wn−1)
如果依赖与前两个词:
P(s)=p(w 1 )p(w 2 |w 1 )p(w 3 |w 1 ,w 2 )...p(w n |w n2 ,w n1 ) P(s)=p(w1)p(w2|w1)p(w3|w1,w2)...p(wn|wn−2,wn−1)
以上分别被称为2-Gram和3-Gram,对应的可以定义出4-Gram,5-Gram等等。前面关联的词越多,模型就越精确,相应的训练成本就越大。
在这里,为了简单起见,我们将通过RNN来训练一个2-Gram。即通过RNN来估计出每对单词的条件概率p(w i |w j ) p(wi|wj)

2.RNN网络结构

训练样本(sentence)数为N s  Ns,目标词典的word数量为N w  Nw,每个单词使用one-hot表示(即该单词在词典中的Index),输入(出)层节点数N i =N o =N w  Ni=No=Nw其中隐层节点数为N h  Nh(通常为N_i的十分之一左右)。隐层节点激活函数为tanh,输出层节点激活函数为softmax。隐层存在一个递归边,每一时刻的输出乘以权值矩阵W后将作为下一时刻输入的一部分。
权值矩阵:
U:Input Layer->Hidden(t) Layer,大小为(N h ×N i  Nh×Ni
V:Hidden(t) Layer->Output Layer,大小为(N o ×N h  No×Nh
W:Hidden(t-1) Layer->Hidden(t) Layer,大小为(N h ×N h  Nh×Nh
f h  fh:隐层激活函数
f o  fo:输出层激活函数
s k (t) sk(t):t时刻第k个隐层节点的输出值
o k (t) ok(t):t时刻第k个输出层节点的输出值
y k  yk:第k个输出层节点的目标输出值(即学习signal)
这里写图片描述
如上图所示,整个网络可以在时间上无限延展,每个时刻t网络都有一个对应的输入向量x(t) x(t),一个隐层输出(状态)向量s(t) s(t),一个输出向量o(t) o(t)
输入向量x(t) x(t)和期望输出向量y(t) y(t)结构如下(假设输入的sentence由4个words组成):
这里写图片描述
可以看到,y刚好相对于x有一个时刻的偏移,即:y(t)=x(t+1) y(t)=x(t+1)
因此,t时刻的损失函数通过y(t) y(t)o(t) o(t)即可求出。具体计算方法参见下边的小节。

3.python代码分析

这里我们使用python的Numpy包来实现,类名为RNNNumpy。另外代码中有中文注释,如果pydev中运行报错,在py文件最开始加上“#coding=utf-8”

3.0 主程序中的关键代码

3.0.1 文件读取

按行读取文件,每一行为一个句子(sentence),并在每个句子的开头和结尾添加SENTENCE_START及SENTENCE_END word。

# with open('data/script.txt', 'rb') as f:
with open('data/script.txt', 'rb') as f:
    reader = csv.reader(f, skipinitialspace=True)
    reader.next()
    # Split full comments into sentences
    sentences = itertools.chain(*[nltk.sent_tokenize(x[0].decode('utf-8').lower()) for x in reader])
    # Append SENTENCE_START and SENTENCE_END
    sentences = ["%s %s %s" % (sentence_start_token, x, sentence_end_token) for x in sentences]
print "Parsed %d sentences." % (len(sentences))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.0.2 分词及词频信息统计

vocabulary_size:词典的大小,即N w  Nw。这里取训练语料经过分词后所有不重复词的数目;
tokenized_sentences:分词后的句子,每个句子由一个word的list组成,分词使用最简单的按空格
切分的方式,如果需要处理中文可以先使用中文专用的分词器切分语料;
word_freq:每个词在语料中出现的次数

# Tokenize the sentences into words
tokenized_sentences = [nltk.word_tokenize(sent) for sent in sentences]

# Count the word frequencies
word_freq = nltk.FreqDist(itertools.chain(*tokenized_sentences))
vocabulary_size = len(word_freq.items())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.0.3 去除低频词、word转index

vocab:只存储词典中出现频率排名前N w 1 Nw−1的词(相当于去掉了出现频率最低的词)
word_to_index:word到index的映射表

# Get the most common words and build index_to_word and word_to_index vectors
vocab = word_freq.most_common(vocabulary_size-1)
#将词典中所有的词(非重复)存储在index_to_word中
index_to_word = [x[0] for x in vocab]
#添加为登录词标记
index_to_word.append(unknown_token)
word_to_index = dict([(w,i) for i,w in enumerate(index_to_word)])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.0.4 去除停用词

# Replace all words not in our vocabulary with the unknown token
for i, sent in enumerate(tokenized_sentences):
    tokenized_sentences[i] = [w if w in word_to_index else unknown_token for w in sent]
  • 1
  • 2
  • 3

3.0.5 生成训练数据

X_train:即公式中的输入x,取每个sentence word index list的前T-1个元素,X_train中每一行存储的是每个句子从t=0时刻到t=T-2时刻的word index,T为该句子的长度;
y_train:即公式中的训练数据y y,取每个sentence word index list的后T-1个元素(除去第一个元素),y_train中每一行存储的是每个句子从t=1时刻到t=T-1时刻的word index;

# Create the training data
X_train = np.asarray([[word_to_index[w] for w in sent[:-1]] for sent in tokenized_sentences])
y_train = np.asarray([[word_to_index[w] for w in sent[1:]] for sent in tokenized_sentences])
  • 1
  • 2
  • 3

3.0.6 构建模型类

model = RNNNumpy(vocabulary_size, hidden_dim=_HIDDEN_DIM)
  • 1

3.0.7 模型训练

def train_with_sgd(model, X_train, y_train, learning_rate=0.005, nepoch=1, evaluate_loss_after=5):
    """
    Train RNN Model with SGD algorithm.

    Parameters
    ----------
    model : The model that will be trained
    X_train : input x
    y_train:期望输出值
    learning_rate:学习率
    nepoch:迭代次数
    evaluate_loss_after:loss值估计间隔,训练程序将每迭代evaluate_loss_after次进行一次loss值估计
    """
    # We keep track of the losses so we can plot them later
    losses = []
    num_examples_seen = 0
    #循环迭代训练,这个for循环每运行一次,就完成一次对所有数据的迭代
    for epoch in range(nepoch):
        # Optionally evaluate the loss
        if (epoch % evaluate_loss_after == 0):
            loss = model.calculate_loss(X_train, y_train)
            losses.append((num_examples_seen, loss))
            time = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
            print "%s: Loss after num_examples_seen=%d epoch=%d: %f" % (time, num_examples_seen, epoch, loss)
            # Adjust the learning rate if loss increases
            #如果当前一次loss值大于上一次,则调小学习率
            if (len(losses) > 1 and losses[-1][1] > losses[-2][1]):
                learning_rate = learning_rate * 0.5  
                print "Setting learning rate to %f" % learning_rate
            sys.stdout.flush()
            # ADDED! Saving model oarameters
            save_model_parameters_theano("./data/rnn-theano-%d-%d-%s.npz" % (model.hidden_dim, model.word_dim, time), model)
        # For each training example...
        #对所有训练数据执行一轮SGD算法迭代
        for i in range(len(y_train)):
            # One SGD step
            model.sgd_step(X_train[i], y_train[i], learning_rate)
            num_examples_seen += 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

3.0.8 辅助函数

辅助函数在utils.py中,这里不做过多解释,相信大家都能看懂:

def softmax(x):
    xt = np.exp(x - np.max(x))
    return xt / np.sum(xt)

def save_model_parameters_theano(outfile, model):
    U, V, W = model.U.get_value(), model.V.get_value(), model.W.get_value()
    np.savez(outfile, U=U, V=V, W=W)
    print "Saved model parameters to %s." % outfile

def load_model_parameters_theano(path, model):
    npzfile = np.load(path)
    U, V, W = npzfile["U"], npzfile["V"], npzfile["W"]
    model.hidden_dim = U.shape[0]
    model.word_dim = U.shape[1]
    model.U.set_value(U)
    model.V.set_value(V)
    model.W.set_value(W)
    print "Loaded model parameters from %s. hidden_dim=%d word_dim=%d" % (path, U.shape[0], U.shape[1])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.1 RNN实现类RNNNumpy

3.1.1 初始化

    def __init__(self, word_dim, hidden_dim=100, bptt_truncate=4):
        """
        Instantiates the RNN class.

        Parameters
        ----------
        word_dim : 输入词向量的维度,如果是one hot,显然应该等于Input Layer 的结点数
        hidden_dim : 隐层的结点数
        bptt_truncate:BPTT反向传播的时间范围
        """

        # Assign instance variables
        self.word_dim = word_dim
        self.hidden_dim = hidden_dim
        self.bptt_truncate = bptt_truncate

        # Randomly initialize the network parameters
        self.U = np.random.uniform(-np.sqrt(1./word_dim), np.sqrt(1./word_dim), (hidden_dim, word_dim))
        self.V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (word_dim, hidden_dim))
        self.W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (hidden_dim, hidden_dim))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3.1.2 前向传播

前向传播对应的公式如下:
f h (x)=tanh(x)=e x e x e x +e x   fh(x)=tanh(x)=ex−e−xex+e−x
f o (x,z)=softmax(x)=e x z  fo(x,z)=softmax(x)=exz
t时刻隐层的输入:z (h) (t)=Ux(t)+Ws(t1) z(h)(t)=U⋅x(t)+W⋅s(t−1)
t时刻隐层的输出:s(t)=f h (z (h) (t)) s(t)=fh(z(h)(t))
t时刻输出层的输入:z (o) (t)=Vs(t) z(o)(t)=V⋅s(t)
t时刻整个网络的输出:o(t)=f o (s(t),z),z= k=1 N o  e z (o) k (t)  o(t)=fo(s(t),z),其中z=∑k=1Noezk(o)(t)
t时刻输出层节点i的输出值:o i (t)=e z (o) i (t)  k=1 N o  e z (o) k (t)  =p(w t+1 =w i |w t ) oi(t)=ezi(o)(t)∑k=1Noezk(o)(t)=p(wt+1=wi|wt)

    def forward_propagation(self, x):
        """
        forward propagation algorithm.

        Parameters
        ----------
        x : 输入句子对应的词向量list,其长度等于句子中的word数目,每一个word的词向量作为一个时刻t的网络输入

        Returns
        -------
        [o, s]:
            o:每一时刻的网络输出值,其大小为T×N_o
                其中的一个元素o[t][i]含义为$p(w_{t+1}=w_i|w_t)$,即已知t时刻输入word $w_t$,的前提下,下一个单词为$w_i$的概率
            s:每一时刻Hidden Layer节点的输出值,其长度为T×N_h
                其中的一个元素s[t][i]表示t时刻隐层第i个节点的输出值
        """
        # The total number of time steps
        T = len(x)
        # During forward propagation we save all hidden states in s because need them later.
        # We add one additional element for the initial hidden, which we set to 0
        s = np.zeros((T + 1, self.hidden_dim))
        s[-1] = np.zeros(self.hidden_dim)
        # The outputs at each time step. Again, we save them for later.
        o = np.zeros((T, self.word_dim))
        # For each time step...
        for t in np.arange(T):
            # Note that we are indxing U by x[t]. This is the same as multiplying U with a one-hot vector.
            s[t] = np.tanh(self.U[:,x[t]] + self.W.dot(s[t-1]))
            o[t] = softmax(self.V.dot(s[t]))
        return [o, s]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3.1.3损失函数

这里使用cross-entropy损失函数,
L t =CEH(y(t),o(t))=E y [log(o(t))]= N w  i=0 y i (t)log(o i (t)) Lt=CEH(y(t),o(t))=Ey[−log⁡(o(t))]=−∑i=0Nwyi(t)log⁡(oi(t))
因为

y i ={10  y i (t)=w i else  yi={1yi(t)=wi0else

所以L t = N w 1 i=0 1{y i (t)=w i }log(o i (t)) Lt=−∑i=0Nw−11{yi(t)=wi}log⁡(oi(t)),容易知道,求和式中每一时刻只有一项非零。
对输入句子的loss等于每个时刻的loss值之和:
L s = T s  t=0 L t  Ls=∑t=0TsLt
最后,针对所有句子,再进行均值化处理:
L=E[L s ]=1N s   N s 1 j=0  T s 1 t=0  N w 1 i=0 1{y i (t)=w i }log(o (j) i (t)) L=E[Ls]=−1Ns∑j=0Ns−1∑t=0Ts−1∑i=0Nw−11{yi(t)=wi}log⁡(oi(j)(t))
=1N s   N s 1 j=0  T s 1 t=0 L jt  =−1Ns∑j=0Ns−1∑t=0Ts−1Ljt
这里的L jt  Ljt表示第j个句子在t时刻的loss值

    def calculate_total_loss(self, x, y):
        L = 0
        # For each sentence...
        for i in np.arange(len(y)):
            o, s = self.forward_propagation(x[i])
            # We only care about our prediction of the "correct" words
            correct_word_predictions = o[np.arange(len(y[i])), y[i]]
            # Add to the loss based on how off we were
            L += -1 * np.sum(np.log(correct_word_predictions))
        return L

    def calculate_loss(self, x, y):
        """
        loss function.

        Parameters
        ----------
        x : 输入句子对应的词向量list 矩阵,矩阵的行数等于输入句子的数目,而每一行存储的内容为各个句子对应的word index列表
        y : 训练值,即期望的输出word index 列表

        Returns
        avg loss:所有训练样本平均loss
        -------
        """
        # Divide the total loss by the number of training examples
        N = np.sum((len(y_i) for y_i in y))
        return self.calculate_total_loss(x,y)/N
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3.1.4反向传播及BPTT算法

δ (o) k (t) δk(o)(t)表示t时刻损失函数对output layer 节点的输入z (o) k (t) zk(o)(t)的导数,
δ (o) k (t)=L t z (o) k (t)  δk(o)(t)=∂Lt∂zk(o)(t)
=L t o k (t) o k (t)z (o) k (t)  =∂Lt∂ok(t)∂ok(t)∂zk(o)(t)
=L  t f  o (z (o) k (t)) =Lt′fo′(zk(o)(t))
=[ N w 1 i=0 1{y i (t)=w i }log(o k (t))]  [e z (o) k (t)  j=1 N o  e z (o) j (t)  ]   =[−∑i=0Nw−11{yi(t)=wi}log⁡(ok(t))]′[ezk(o)(t)∑j=1Noezj(o)(t)]′
=[1o k (t) ][e z (o) k (t) ]  [ j=1 N o  e z (o) j (t) ][e z (o) k (t) ][ j=1 N o  e z (o) j (t) ]  [ j=1 N o  e z (o) j (t) ] 2   =[−1ok(t)][ezk(o)(t)]′[∑j=1Noezj(o)(t)]−[ezk(o)(t)][∑j=1Noezj(o)(t)]′[∑j=1Noezj(o)(t)]2
=[1o k (t) ][e z (o) k (t) ][ j=1 N o  e z (o) j (t) ][e z (o) k (t) ][e z (o) k (t) ][ j=1 N o  e z (o) j (t) ] 2   =[−1ok(t)][ezk(o)(t)][∑j=1Noezj(o)(t)]−[ezk(o)(t)][ezk(o)(t)][∑j=1Noezj(o)(t)]2
=[1o k (t) ][e z (o) k (t)  j=1 N o  e z (o) j (t)  ] j=1 N o  e z (o) j (t) e z (o) k (t)  j=1 N o  e z (o) j (t)   =[−1ok(t)][ezk(o)(t)∑j=1Noezj(o)(t)]∑j=1Noezj(o)(t)−ezk(o)(t)∑j=1Noezj(o)(t)
=[1o k (t) ][o k (t)][1o k (t)] =[−1ok(t)][ok(t)][1−ok(t)]
=o k (t)1 =ok(t)−1
这里需要注意,由前边3.1.2可知,o k (t)=e z (o) k (t)  j=1 N o  e z (o) j (t)   ok(t)=ezk(o)(t)∑j=1Noezj(o)(t)
写成向量的形式为:
δ (o) (t)=o(t)1 δ(o)(t)=o(t)−1
正好对应了代码:

delta_o[np.arange(len(y)), y] -= 1.
  • 1

对于隐层节点k的输入z (h) k (t) zk(h)(t)求导:
δ (h) k (t)=L t z (h) k (t)  δk(h)(t)=∂Lt∂zk(h)(t)
= j L t o j (t) o j (t)z (o) j (t) z (o) j (t)z (h) k (t)  =∑j∂Lt∂oj(t)∂oj(t)∂zj(o)(t)∂zj(o)(t)∂zk(h)(t)
= j L t o j (t) o j (t)z (o) j (t) z (o) j (t)s k (t) s k (t)z (h) k (t)  =∑j∂Lt∂oj(t)∂oj(t)∂zj(o)(t)∂zj(o)(t)∂sk(t)∂sk(t)∂zk(h)(t)
= j δ (o) j (t)v jk f  h (z (h) k (t)) =∑jδj(o)(t)vjkfh′(zk(h)(t))
= j δ (o) j (t)v jk tanh  (z (h) k (t)) =∑jδj(o)(t)vjktanh′(zk(h)(t))
= j δ (o) j (t)v jk [1tanh(z (h) k (t)) 2 ] =∑jδj(o)(t)vjk[1−tanh(zk(h)(t))2]
= j δ (o) j (t)v jk [1s k (t) 2 ] =∑jδj(o)(t)vjk[1−sk(t)2]
=[δ (o) (t)V k ][1s k (t) 2 ] =[δ(o)(t)⋅Vk]∙[1−sk(t)2]
写成向量为:
δ (h) (t)=[δ (o) (t)V][1s(t) 2 ] δ(h)(t)=[δ(o)(t)⋅V]∙[1−s(t)2]
小圆点为内积,大圆点为entrywise product(阿达马乘积)
对应代码为:

delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
  • 1

同理,将隐层沿时间往后扩展一层,得:
δ (h) (t1)=[δ (h) (t)W][1s(t1) 2 ] δ(h)(t−1)=[δ(h)(t)⋅W]∙[1−s(t−1)2](注意这里的权值变成了W!)
对应代码:

delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
  • 1

首先,分别求出V,W,U的导数:
L t V =L t o(t) o(t)z (o) (t) z (o) (t)V  ∂Lt∂V=∂Lt∂o(t)∂o(t)∂z(o)(t)⊗∂z(o)(t)∂V
=δ (o) (t)s(t) =δ(o)(t)⊗s(t)
=[o(t)1]s(t) =[o(t)−1]⊗s(t)
对于一个sentence而言,需要对每个时刻(word)的导数进行求和:
ΔV= t L t V = t [o(t)1]s(t) ΔV=∑t∂Lt∂V=∑t[o(t)−1]⊗s(t)
*这里的圆圈表示outer product
对应代码为:

dLdV += np.outer(delta_o[t], s[t].T)
  • 1

L t W = t=T s  T s T r  L t z (h) (t) z (h) (t)W  ∂Lt∂W=∑t=TsTs−Tr∂Lt∂z(h)(t)∂z(h)(t)∂W
= t=T s  T s T r  δ (h) (t)s(t1) =∑t=TsTs−Trδ(h)(t)s(t−1)
= t=T s  T s T r  δ (h) (t)s(t1) =∑t=TsTs−Trδ(h)(t)s(t−1)
L t U = t=T s  T s T r  δ (h) (t)x(t) ∂Lt∂U=∑t=TsTs−Trδ(h)(t)x(t)
代码参考第二个for循环。注意,由于x(t)为0或1,所以代码中省略了乘法,直接取出非0元素。

注意到代码中有两个反向的for循环,意义如下:
最外层的for循环是对每个时刻t(即当前sentence的每一个word)进行迭代,而第二个for循环则是对隐层的bptt_truncate(即公式中的T r  Tr)次循环进行迭代。bptt_truncate是一个预定义的参数,代表了BP时,最多可以回溯的隐层深度。深度越大,越接近真实情况,但计算量和出现震荡的概率都会增大。

    def bptt(self, x, y):
        """
        BPTT algorithm.

        Parameters
        ----------
        x : 输入句子对应的词向量list,其长度等于句子中的word数目,每一个word的词向量作为一个时刻t的网络输入
        y : 训练值,即期望的输出word index 列表

        Returns
        dLdU:损失函数对U矩阵的导数
        dLdV:损失函数对V矩阵的导数
        dLdW:损失函数对W矩阵的导数
        -------
        """
        T = len(y)
        # Perform forward propagation
        o, s = self.forward_propagation(x)
        # We accumulate the gradients in these variables
        dLdU = np.zeros(self.U.shape)
        dLdV = np.zeros(self.V.shape)
        dLdW = np.zeros(self.W.shape)
        delta_o = o
        delta_o[np.arange(len(y)), y] -= 1.
        # For each output backwards...
        for t in np.arange(T)[::-1]:
            dLdV += np.outer(delta_o[t], s[t].T)
            # Initial delta calculation
            delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
            # Backpropagation through time (for at most self.bptt_truncate steps)
            for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:
                # print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)
                dLdW += np.outer(delta_t, s[bptt_step-1])              
                dLdU[:,x[bptt_step]] += delta_t
                # Update delta for next step
                delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
        return [dLdU, dLdV, dLdW]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

4.step by step in matlab


ADC-1.13.ARM裸机第十三部分

-
  • 1970年01月01日 08:00

RNN学习笔记(六)-GRU,LSTM 代码实现

RNN学习笔记(六)-GRU,LSTM 代码实现
  • rtygbwwwerr
  • rtygbwwwerr
  • 2016-04-22 18:07:27
  • 4080

台湾大学深度学习课程 学习笔记 Lecture 5-1: Gated RNN(LSTM与GRU介绍)

以下内容和图片均来自台湾大学深度学习课程。 课程地址:https://www.csie.ntu.edu.tw/~yvchen/f106-adl/syllabus.html Review首先复习...
  • sscc_learning
  • sscc_learning
  • 2018-01-10 14:18:06
  • 236

目前看到的最好的RNN、LSTM、GRU博客:Understanding LSTM Networks(翻译)

原文:http://www.jianshu.com/p/9dc9f41f0b29 本文译自 Christopher Olah 的博文 Recurrent Neu...
  • mmc2015
  • mmc2015
  • 2017-02-03 17:11:42
  • 1662

深度学习之六,基于RNN(GRU,LSTM)的语言模型分析与theano代码实现

引言前面已经介绍过RNN的基本结构,最基本的RNN在传统的BP神经网络上,增加了时序信息,也使得神经网络不再局限于固定维度的输入和输出这个束缚,但是从RNN的BPTT推导过程中,可以看到,传统RNN在...
  • u010223750
  • u010223750
  • 2016-05-26 21:49:55
  • 26392

RNN, LSTM, GRU 公式总结

RNN参考 RNN wiki 的描述,根据隐层 hth_t 接受的是上时刻的隐层(hidden layer) ht−1h_{t-1} 还是上时刻的输出(output layer)yt−1y_{t-1}...
  • zhangxb35
  • zhangxb35
  • 2017-04-11 17:09:28
  • 8304

RNN LSTM与GRU深度学习模型学习笔记

RNN(Recurrent Neural Network), LSTM(Long Short-Term Memory)与GRU(Gated Recurrent Unit)都是自然语言处理领域常见的深度...
  • u012033027
  • u012033027
  • 2017-01-09 20:39:48
  • 7344

循环神经网络教程 第四部分 用Python 和 Theano实现GRU/LSTM RNN

本教程的github代码在本文中,我们将了解LSTM(长期短期内存)网络和GRU(门控循环单元)。 LSTM是1997年由Sepp Hochreiter和JürgenSchmidhuber首次提出的,...
  • u013713117
  • u013713117
  • 2016-12-31 17:32:59
  • 1776

深度学习笔记——基于双向RNN(LSTM、GRU)和Attention Model的句子对匹配方法

本文主要是结合RNN和Attention Model做一些关于句子对匹配的模型总结。
  • mpk_no1
  • mpk_no1
  • 2017-08-06 23:06:07
  • 3260

LSTM与GRU的一些比较--论文笔记

reference:Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling1.概要:传统的RNN在训练...
  • meanme
  • meanme
  • 2015-10-01 13:29:52
  • 46563
收藏助手
不良信息举报
您举报文章:RNN、LSTM、GRU比较及部分部分代码实现与讲解
举报原因:
原因补充:

(最多只允许输入30个字)