MXNet深度循环神经网络----含有2个隐藏层的循环神经网络(程序)
《动手学深度学习》第六章 第9节的练习题,个人解答。
在深度学习应用里,我们通常会用到含有多个隐藏层的循环神经网络,也称作深度循环神经网络。下图演示了一个有 L L L个隐藏层的深度循环神经网络,每个隐藏状态不断传递至当前层的下一时间步和当前时间步的下一层。
具体来说,在时间步 t t t里,设小批量输入 X t ∈ R n × d \boldsymbol{X}_t \in \mathbb{R}^{n \times d} Xt∈Rn×d(样本数为 n n n,输入个数为 d d d),第 ℓ \ell ℓ隐藏层( ℓ = 1 , … , L \ell=1,\ldots,L ℓ=1,…,L)的隐藏状态为 H t ( ℓ ) ∈ R n × h \boldsymbol{H}_t^{(\ell)} \in \mathbb{R}^{n \times h} Ht(ℓ)∈Rn×h(隐藏单元个数为 h h h),输出层变量为 O t ∈ R n × q \boldsymbol{O}_t \in \mathbb{R}^{n \times q} Ot∈Rn×q(输出个数为 q q q),且隐藏层的激活函数为 ϕ \phi ϕ。第1隐藏层的隐藏状态和之前的计算一样:
H t ( 1 ) = ϕ ( X t W x h ( 1 ) + H t − 1 ( 1 ) W h h ( 1 ) + b h ( 1 ) ) , \boldsymbol{H}_t^{(1)} = \phi(\boldsymbol{X}_t \boldsymbol{W}_{xh}^{(1)} + \boldsymbol{H}_{t-1}^{(1)} \boldsymbol{W}_{hh}^{(1)} + \boldsymbol{b}_h^{(1)}), Ht(1)=ϕ(XtWxh(1)+Ht−1(1)Whh(1)+bh(1)),
其中权重 W x h ( 1 ) ∈ R d × h \boldsymbol{W}_{xh}^{(1)} \in \mathbb{R}^{d \times h} Wxh(1)∈Rd×h、 W h h ( 1 ) ∈ R h × h \boldsymbol{W}_{hh}^{(1)} \in \mathbb{R}^{h \times h} Whh(1)∈Rh×h和偏差 b h ( 1 ) ∈ R 1 × h \boldsymbol{b}_h^{(1)} \in \mathbb{R}^{1 \times h} bh(1)∈R1×h分别为第1隐藏层的模型参数。
当 1 < ℓ ≤ L 1 < \ell \leq L 1<ℓ≤L时,第 ℓ \ell ℓ隐藏层的隐藏状态的表达式为
H t ( ℓ ) = ϕ ( H t ( ℓ − 1 ) W x h ( ℓ ) + H t − 1 ( ℓ ) W h h ( ℓ ) + b h ( ℓ ) ) , \boldsymbol{H}_t^{(\ell)} = \phi(\boldsymbol{H}_t^{(\ell-1)} \boldsymbol{W}_{xh}^{(\ell)} + \boldsymbol{H}_{t-1}^{(\ell)} \boldsymbol{W}_{hh}^{(\ell)} + \boldsymbol{b}_h^{(\ell)}), Ht(ℓ)=ϕ(Ht(ℓ−1)Wxh(ℓ)+Ht−1(ℓ)Whh(ℓ)+bh(ℓ)),
其中权重 W x h ( ℓ ) ∈ R h × h \boldsymbol{W}_{xh}^{(\ell)} \in \mathbb{R}^{h \times h} Wxh(ℓ)∈Rh×h、 W h h ( ℓ ) ∈ R h × h \boldsymbol{W}_{hh}^{(\ell)} \in \mathbb{R}^{h \times h} Whh(ℓ)∈Rh×h和偏差 b h ( ℓ ) ∈ R 1 × h \boldsymbol{b}_h^{(\ell)} \in \mathbb{R}^{1 \times h} bh(ℓ)∈R1×h分别为第 ℓ \ell ℓ隐藏层的模型参数。
最终,输出层的输出只需基于第 L L L隐藏层的隐藏状态:
O t = H t ( L ) W h q + b q , \boldsymbol{O}_t = \boldsymbol{H}_t^{(L)} \boldsymbol{W}_{hq} + \boldsymbol{b}_q, Ot=Ht(L)Whq+bq,
其中权重 W h q ∈ R h × q \boldsymbol{W}_{hq} \in \mathbb{R}^{h \times q} Whq∈Rh×q和偏差 b q ∈ R 1 × q \boldsymbol{b}_q \in \mathbb{R}^{1 \times q} bq∈R1×q为输出层的模型参数。
同多层感知机一样,隐藏层个数 L L L和隐藏单元个数 h h h都是超参数。此外,如果将隐藏状态的计算换成门控循环单元或者长短期记忆的计算,我们可以得到深度门控循环神经网络。
小结
- 在深度循环神经网络中,隐藏状态的信息不断传递至当前层的下一时间步和当前时间步的下一层。
练习
- 将“循环神经网络的从零开始实现”一节中的模型改为含有2个隐藏层的循环神经网络。观察并分析实验现象。
import d2lzh as d2l
import math
from mxnet import autograd, nd
from mxnet.gluon import loss as gloss
import time
(corpus_indices, char_to_idx, idx_to_char,
vocab_size) = d2l.load_data_jay_lyrics()
X = nd.reshape(nd.arange(10),(2, 5))
def to_onehot(X, size): # 本函数已保存在d2lzh包中方便以后使用
return [nd.one_hot(x, size) for x in X.T]
# 初始化模型参数
num_inputs, num_hiddens1, num_hiddens2, num_outputs = vocab_size, 256,128, vocab_size
ctx = d2l.try_gpu()
print('will use', ctx)
def get_params():
def _one(shape):
return nd.random.normal(scale=0.01, shape=shape, ctx=ctx)
# 隐藏层1参数
W_xh1 = _one((num_inputs, num_hiddens1))
W_hh1 = _one((num_hiddens1, num_hiddens1))
b_h1 = nd.zeros(num_hiddens1, ctx=ctx)
# 隐藏层2参数
W_xh2 = _one((num_hiddens1, num_hiddens2))
W_hh2 = _one((num_hiddens2, num_hiddens2))
b_h2 = nd.zeros(num_hiddens2, ctx=ctx)
# 输出层参数
W_hq = _one((num_hiddens2, num_outputs))
b_q = nd.zeros(num_outputs, ctx=ctx)
# 附上梯度
params = [W_xh1, W_hh1, b_h1,W_xh2, W_hh2, b_h2, W_hq, b_q]
for param in params:
param.attach_grad()
return params
will use gpu(0)
# 定义模型
def init_rnn_state(batch_size, num_hiddens1, num_hiddens2, ctx):
return (nd.zeros(shape=(batch_size, num_hiddens1), ctx=ctx),
nd.zeros(shape=(batch_size, num_hiddens2), ctx=ctx),)
def rnn(inputs, state, params):
# inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵
W_xh1, W_hh1, b_h1, W_xh2, W_hh2, b_h2,W_hq, b_q = params
H1,H2, = state
outputs = []
for X in inputs:
H1 = nd.tanh(nd.dot(X, W_xh1) + nd.dot(H1, W_hh1) + b_h1)
H2 = nd.tanh(nd.dot(H1, W_xh2) + nd.dot(H2, W_hh2) + b_h2)
Y = nd.dot(H2, W_hq) + b_q
outputs.append(Y)
return outputs, (H1,H2,)
init_rnn_state??
state = init_rnn_state(X.shape[0], num_hiddens1,num_hiddens2, ctx) #返回由一个形状为(批量大小, 隐藏单元个数)的值为0的NDArray组成的元组
inputs = to_onehot(X.as_in_context(ctx), vocab_size)
params = get_params()
outputs, state_new = rnn(inputs, state, params) # num_outputs = vocab_size这是自己设置的
len(outputs), outputs[0].shape, state_new[0].shape,state_new[1].shape
(5, (2, 1027), (2, 256), (2, 128))
# 定义预测函数
# 本函数已保存在d2lzh包中方便以后使用
def predict_rnn(prefix, num_chars, rnn, params, init_rnn_state,
num_hiddens1,num_hiddens2, vocab_size, ctx, idx_to_char, char_to_idx):
state = init_rnn_state(1, num_hiddens1, num_hiddens2,ctx)
output = [char_to_idx[prefix[0]]]
for t in range(num_chars + len(prefix) - 1):
# 将上一时间步的输出作为当前时间步的输入
X = to_onehot(nd.array([output[-1]], ctx=ctx), vocab_size)
# 计算输出和更新隐藏状态
(Y, state) = rnn(X, state, params)
# 下一个时间步的输入是prefix里的字符或者当前的最佳预测字符
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(int(Y[0].argmax(axis=1).asscalar()))
return ''.join([idx_to_char[i] for i in output])
predict_rnn('分开', 10, rnn, params, init_rnn_state, num_hiddens1, num_hiddens2, vocab_size,
ctx, idx_to_char, char_to_idx)
'分开坦始膀灌泣寄担够事同'
# 裁剪梯度
# 本函数已保存在d2lzh包中方便以后使用
def grad_clipping(params, theta, ctx):
norm = nd.array([0], ctx)
for param in params:
norm += (param.grad ** 2).sum()
norm = norm.sqrt().asscalar()
if norm > theta:
for param in params:
param.grad[:] *= theta / norm
# 定义模型训练函数
# 本函数已保存在d2lzh包中方便以后使用
def train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens1,
num_hiddens2,vocab_size, ctx, corpus_indices, idx_to_char,
char_to_idx, is_random_iter, num_epochs, num_steps,
lr, clipping_theta, batch_size, pred_period,
pred_len, prefixes):
if is_random_iter:
data_iter_fn = d2l.data_iter_random
else:
data_iter_fn = d2l.data_iter_consecutive
params = get_params()
loss = gloss.SoftmaxCrossEntropyLoss()
for epoch in range(num_epochs):
if not is_random_iter: # 如使用相邻采样,在epoch开始时初始化隐藏状态
state = init_rnn_state(batch_size, num_hiddens1,num_hiddens2, ctx)
l_sum, n, start = 0.0, 0, time.time()
data_iter = data_iter_fn(corpus_indices, batch_size, num_steps, ctx)
for X, Y in data_iter:
if is_random_iter: # 如使用随机采样,在每个小批量更新前初始化隐藏状态
state = init_rnn_state(batch_size, num_hiddens1,num_hiddens2, ctx)
else: # 否则需要使用detach函数从计算图分离隐藏状态
for s in state:
s.detach()
with autograd.record():
inputs = to_onehot(X, vocab_size)
# outputs有num_steps个形状为(batch_size, vocab_size)的矩阵
(outputs, state) = rnn(inputs, state, params)
# 拼接之后形状为(num_steps * batch_size, vocab_size)
outputs = nd.concat(*outputs, dim=0)
# Y的形状是(batch_size, num_steps),转置后再变成长度为
# batch * num_steps 的向量,这样跟输出的行一一对应
# y = Y.T.reshape((-1,)) #当前版本不支持,改为下面
y = nd.reshape(Y.T,(-1,))
# 使用交叉熵损失计算平均分类误差
l = loss(outputs, y).mean()
l.backward()
grad_clipping(params, clipping_theta, ctx) # 裁剪梯度
d2l.sgd(params, lr, 1) # 因为误差已经取过均值,梯度不用再做平均
l_sum += l.asscalar() * y.size
n += y.size
if (epoch + 1) % pred_period == 0:
print('epoch %d, perplexity %f, time %.2f sec' % (
epoch + 1, math.exp(l_sum / n), time.time() - start))
for prefix in prefixes:
print(' -', predict_rnn(
prefix, pred_len, rnn, params, init_rnn_state,
num_hiddens1,num_hiddens2, vocab_size, ctx, idx_to_char, char_to_idx))
num_epochs, num_steps, batch_size, lr, clipping_theta = 250, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens1,num_hiddens2,
vocab_size, ctx, corpus_indices, idx_to_char,
char_to_idx, True, num_epochs, num_steps, lr,
clipping_theta, batch_size, pred_period, pred_len,
prefixes)
epoch 50, perplexity 80.401249, time 1.36 sec
- 分开 我坏的让我 你 你 你 我 你的起棍 哼哼哈兮 快使用双截棍 哼知了兮 快使用双截棍 哼知了兮
- 不分开柳 我想 我不么 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏
epoch 100, perplexity 25.571746, time 1.35 sec
- 分开 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我
- 不分开 我想想你的经笑 你想想你的经笑 你 想带你 爱爱我 我想我这样很着我 说说 你你你 我不能再想 我
epoch 150, perplexity 11.613603, time 1.33 sec
- 分开 我不想再想 我不能再想 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我
- 不分开 我 想带你骑单车 我 想带你看着我 想这 你想很久了难? 我说店你你 我 我 你的睡过 我 想带你
epoch 200, perplexity 6.026414, time 1.35 sec
- 分开 我可不 我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱
- 不分开不 我想开 是你的话 我知道 语子默一切 慢轻在不口 仙人在美索 你后在你生 没有你看 我不能不
epoch 250, perplexity 3.780973, time 1.30 sec
- 分开起的美找 秃著我的只迹依你的手 爱有人了球 说开了其球 还思寄 废沉默 娘子的人板球 说穿人了
- 不分开不想单你像泪了过见 一朵莫危护在我妈 我不要再想 我不要再想 我不要再想 我不要再想 我不要再想 我