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

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

在这篇文章里,我们将讨论GRU/LSTM的代码实现。在这里,我们仍然沿用RNN学习笔记(五)-RNN 代码实现里的例子,使用GRU/LSTM网络建立一个2-gram的语言模型。
项目源码:https://github.com/rtygbwwwerr/RNN
参考项目:https://github.com/dennybritz/rnn-tutorial-gru-lstm

1.网络结构

为了解决当词典中的words数量很大时,输入向量过长的问题,我们在输入层和隐层之间引入了Embedding Layer,通过该层,输入的one-hot将被转换为word的Embedding vector。

1.1 GRU网络

RNN学习笔记(六)-GRU,LSTM 代码实现_第1张图片

1.2 LSTM网络

略。

2.代码实现

这里我们重点讨论bptt部分(*:“ ”表示elemwise乘法运算)。对于GRU网络来说,有

zrhstz(o)(t)ot=σ(xtUz+st1Wz)=σ(xtUr+st1Wr)=tanh(xtUh+(st1r)Wh)=(1z)h+zst1=stV+c=softmax(z(o)(t))

softmax(x)=softmax(x)[1softmax(x)]
输出层节点的输入值 z(o)k(t) 导数如下:
δ(o)k(t)=Ltz(o)k(t)
=ok(t)1
写成向量形式为:
δ(o)(t)=o(t)1
LtV=δ(o)k(t)z(o)k(t)V=[ok(t)1]st
可以看到,这一层的导数与常规RNN是一致的。

从隐层开始,导数将有所不同,我们先来看下单个GRU网络节点结构:
RNN学习笔记(六)-GRU,LSTM 代码实现_第2张图片

这里,先对符号做一下约定:
iz(t)=xtUz+s(t1)Wz :t时刻update gate 对应的输入
ir(t)=xtUr+s(t1)Wr :t时刻rest gate 对应的输入
ih(t)=xtUh+(s(t1)r(t))Wh :t时刻隐单元对应的输入
io(t)=(1z(t))h(t)+z(t)s(t1) :t时刻output gate对应的输入
f(io(t))=io(t)=s(t)=(1z(t))h(t)+z(t)s(t1)

δo(t)=Ltio(t)=δ(o)(t)z(o)(t)s(t)s(t)io(t)=δ(o)(t)z(o)(t)s(t)f(io(t))io(t)
=δ(o)(t)V1

δz(t)
=δo(t)io(t)z(t)z(t)iz(t)
=δo(t)[s(t1)h(t)]σ(iz(t))
这里我们设:
f(x)=0.2x+0.5
σ(x)=f(x)010≤f(x)≤1f(x)<0f(x)>1

σ(x)=0.2000≤f(x)≤1f(x)<0f(x)>1

δh(t)
=δo(t)io(t)h(t)h(t)ih(t)
=δo(t)(1z(t))[1tanh(ih(t))2]
=δo(t)(1z(t))[1h(t)2]

δr(t)
=δh(t)ih(t)r(t)r(t)ir(t)
=[δh(t)s(t1)]Wh

根据复合函数的求导法则,前一时刻的输出门误差 δo(t1) 由四部分的偏导数组成(对应图中的4条蓝线)
δo(t1)=Ltio(t1)
=δz(t)iz(t)s(t1)+δr(t)ir(t)s(t1)+δh(t)ih(t)s(t1)+δo(t)io(t)s(t1)
=δz(t)Wz+δr(t)Wr+(δh(t)r(t))Wh+δo(t)z(t)
得到了 δo(t1) 依次带入前边的公式,即可求出 δz(t1),δr(t1),δh(t1)

而对于t时刻的输入 x(t) 求导,可得:
δx(t)=Ltx(t)
=δz(t)iz(t)x(t)+δr(t)ir(t)x(t)+δh(t)ih(t)x(t)
=δz(t)Uz+δr(t)Ur+δh(t)Uh
当存在多层GRU的时候,上一层的输出等于当前层的输入,即: s(l1)(t)=x(l)(t)
所以就有:
δ(l1)o(t)=δ(l)x(t)=δ(l)z(t)U(l)z+δ(l)r(t)U(l)r+δ(l)h(t)U(l)h

以此为基础,可以按上边的方法推导出 δ(l1)z(t),δ(l1)r(t),δ(l1)h(t)
的计算公式,这里就不一一推导了。

Output Layer的梯度:
ΔV(t)=LtV=δ(o)(t)z(o)(t)V=[o(t)1]s(t)
Δc(t)=Ltc=δ(o)(t)z(o)(t)c=o(t)1

Hidden Layer:
ΔWr(t)=δr(t)ir(t)Wr=δr(t)s(t1)
ΔWh(t)=δh(t)ih(t)Wh=δh(t)s(t1)r(t)
ΔWz(t)=δz(t)iz(t)Wz=δz(t)s(t1)

ΔUr(t)=δr(t)ir(t)Ur=δr(t)x(t)
ΔUh(t)=δh(t)ih(t)Uh=δh(t)x(t)
ΔUz(t)=δz(t)iz(t)Uz=δz(t)x(t)

对于Embedding层, E Nh×Ni的矩阵, E 的每一列可以看做词典中一个单词的Embedding向量表示
ix(t)=Eword(t)
ΔE(t)=δ(1)x(t)ix(t)E=δ(1)x(t)word(t)=δ(1)x(t)
word(t)为单词的one-hot向量,其中的分量只有一个为1,其余为0:
[w1,w2,...,wi,...,wNi]=[0,0,...,1,...,0] ,其中只有分量 wi=1 .所以 Eword(t) 的结果相当于取出 E 的第i列。在使用numpy的时候,为了实现的方便,使用了一个lookup Table,输入的为非零向量的索引 i ,通过取E矩阵的第i列直接得出word的Embedding向量 x :

 x = self.E[:,word];

bias项的梯度(上边的公式中省略了,例如:iz(t)=xtUz+st1Wz+bz
Δbr(t)=δr(t)ir(t)br=δr(t)
Δbh(t)=δh(t)ih(t)bh=δh(t)
Δbz(t)=δz(t)iz(t)bz=δz(t)

对应源码:

   def bptt(self, x, y):

        word = x
        T = len(y)
        o, s, z, r, h = self.forward_propagation(word)
        x = self.E[:,word];

        dLdE = np.zeros(self.E.shape)
        dLdU = np.zeros(self.U.shape)
        dLdV = np.zeros(self.V.shape)
        dLdW = np.zeros(self.W.shape)
        dLdb = np.zeros(self.b.shape)
        dLdc = np.zeros(self.c.shape)
        #self.bptt_truncate
        delta_o = o
        delta_o[np.arange(len(y)), y] -= 1.

        for t in np.arange(T)[::-1]:
            dLdV += np.outer(delta_o[t], s[t][0].T)
            dLdc += delta_o[t]
            delta_x = 0
            for l in np.arange(self.layer_num)[::-1]:
                # Initial delta calculation
                delta_ho = self.V.T.dot(delta_o[t])

                for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:

                    delta_func = map(delta_hard_sigmoid, z[bptt_step][l])
                    delta_hz =  delta_ho * (s[bptt_step - 1][l] - h[bptt_step][l]) * delta_func
                    delta_hh = delta_ho * (1 - z[bptt_step][l]) * (1-(h[bptt_step][l]**2))
                    delta_hr = self.W[l][1].T.dot(delta_hh * s[bptt_step - 1][l])
                    delta_x =  self.U[l][0].T.dot(delta_hz) + self.U[l][1].T.dot(delta_hr) + self.U[l][2].T.dot(delta_hh)

                    #dW^z
                    dLdW[l][0] += delta_hz * s[bptt_step-1][l]
                    #dW^r
                    dLdW[l][1] += delta_hr * s[bptt_step-1][l]
                    #dW^h
                    dLdW[l][2] += delta_hh * s[bptt_step-1][l] * r[bptt_step][l]

                    input =  (x[:, bptt_step] if l < 1 else (s[bptt_step][l - 1]))
                    #dU^z
                    dLdU[l][0] += delta_hz * input
                    #dU^r
                    dLdU[l][1] += delta_hr * input
                    #dU^h
                    dLdU[l][2] += delta_hh * input

                    #db^z
                    dLdb[l][0] += delta_hz
                    #db^r
                    dLdb[l][1] += delta_hr
                    #db^h
                    dLdb[l][2] += delta_hh

                    #while is the first hidden layer,you need to count the dE
                    if l < 1:
                        dLdE[:, word[t]] += delta_x

                    delta_ho = self.W[l][0].T.dot(delta_hz) +  self.W[l][1].T.dot(delta_hr) +  self.W[l][2].T.dot(delta_hh * r[bptt_step][l]) + delta_ho * z[bptt_step][l]



        return [dLdE, dLdU, dLdV, dLdW, dLdb, dLdc]

把整个网络展开(单层):
RNN学习笔记(六)-GRU,LSTM 代码实现_第3张图片

你可能感兴趣的:(机器学习,机器学习,GRU,神经网络,RNN)