您的位置:首页 > 编程语言

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

2016-04-22 18:07 731 查看

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网络



1.2 LSTM网络

略。

2.代码实现

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

zrhstz(o)(t)ot=σ(xtUz+st−1Wz)=σ(xtUr+st−1Wr)=tanh(xtUh+(st−1⊙r)Wh)=(1−z)⊙h+z⊙st−1=stV+c=softmax(z(o)(t))

softmax(x)′=softmax(x)[1−softmax(x)]

输出层节点的输入值z(o)k(t)导数如下:

δ(o)k(t)=∂Lt∂z(o)k(t)

=ok(t)−1

写成向量形式为:

δ(o)(t)=o(t)−1

∂Lt∂V=δ(o)k(t)∂z(o)k(t)∂V=[ok(t)−1]⊙st

可以看到,这一层的导数与常规RNN是一致的。

从隐层开始,导数将有所不同,我们先来看下单个GRU网络节点结构:



这里,先对符号做一下约定:

iz(t)=xtUz+s(t−1)Wz:t时刻update gate 对应的输入

ir(t)=xtUr+s(t−1)Wr:t时刻rest gate 对应的输入

ih(t)=xtUh+(s(t−1)⊙r(t))Wh:t时刻隐单元对应的输入

io(t)=(1−z(t))⊙h(t)+z(t)⊙s(t−1):t时刻output gate对应的输入

f(io(t))=io(t)=s(t)=(1−z(t))⊙h(t)+z(t)⊙s(t−1)

δo(t)=∂Lt∂io(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)⋅V⋅1

δz(t)

=δo(t)∂io(t)∂z(t)∂z(t)∂iz(t)

=δo(t)⊙[s(t−1)−h(t)]⊙σ(iz(t))′

这里我们设:

f(x)=0.2∗x+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)⊙(1−z(t))⊙[1−tanh(ih(t))2]

=δo(t)⊙(1−z(t))⊙[1−h(t)2]

δr(t)

=δh(t)∂ih(t)∂r(t)∂r(t)∂ir(t)

=[δh(t)⊙s(t−1)]⋅Wh

根据复合函数的求导法则,前一时刻的输出门误差δo(t−1)由四部分的偏导数组成(对应图中的4条蓝线)

δo(t−1)=∂Lt∂io(t−1)

=δz(t)∂iz(t)∂s(t−1)+δr(t)∂ir(t)∂s(t−1)+δh(t)∂ih(t)∂s(t−1)+δo(t)∂io(t)∂s(t−1)

=δz(t)⋅Wz+δr(t)⋅Wr+(δh(t)⊙r(t))⋅Wh+δo(t)⊙z(t)

得到了δo(t−1)依次带入前边的公式,即可求出δz(t−1),δr(t−1),δh(t−1)

而对于t时刻的输入x(t)求导,可得:

δx(t)=∂Lt∂x(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(l−1)(t)=x(l)(t)

所以就有:

δ(l−1)o(t)=δ(l)x(t)=δ(l)z(t)⋅U(l)z+δ(l)r(t)⋅U(l)r+δ(l)h(t)⋅U(l)h

以此为基础,可以按上边的方法推导出δ(l−1)z(t),δ(l−1)r(t),δ(l−1)h(t)

的计算公式,这里就不一一推导了。

Output Layer的梯度:

ΔV(t)=∂Lt∂V=δ(o)(t)∂z(o)(t)∂V=[o(t)−1]⊙s(t)

Δc(t)=∂Lt∂c=δ(o)(t)∂z(o)(t)∂c=o(t)−1

Hidden Layer:

ΔWr(t)=δr(t)∂ir(t)∂Wr=δr(t)⊙s(t−1)

ΔWh(t)=δh(t)∂ih(t)∂Wh=δh(t)⊙s(t−1)⊙r(t)

ΔWz(t)=δz(t)∂iz(t)∂Wz=δz(t)⊙s(t−1)

Δ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)=E⋅word(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.所以E⋅word(t)的结果相当于取出E的第i列。在使用numpy的时候,为了实现的方便,使用了一个lookup Table,输入的为非零向量的索引i,通过取E矩阵的第i列直接得出word的Embedding向量x:

x = self.E[:,word];


bias项的梯度(上边的公式中省略了,例如:iz(t)=xtUz+st−1Wz+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]


把整个网络展开(单层):

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息