Task03: 过拟合、欠拟合及其解决方案;梯度消失、梯度爆炸;循环神经网络进阶
目录
Task03: 过拟合、欠拟合及其解决方案;梯度消失、梯度爆炸;循环神经网络进阶
1、过拟合、欠拟合及其解决方案
1.1 训练误差和泛化误差
训练误差(training error):模型在训练数据集上表现出的误差
泛化误差(generalization error):模型在任意一个测试数据样本上表现出的误差的期望,常常通过测试数据集上的误差来近似。
计算训练误差和泛化误差可以使用之前介绍过的损失函数,例如线性回归用到的平方损失函数和softmax回归用到的交叉熵损失函数。
机器学习模型应关注降低泛化误差。
1.2 模型选择
验证数据集
从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用一次。不可以使用测试数据选择模型,如调参。由于无法从训练误差估计泛化误差,因此也不应只依赖训练数据选择模型。鉴于此,我们可以预留一部分在训练数据集和测试数据集以外的数据来进行模型选择。这部分数据被称为验证数据集,简称验证集(validation set)。例如,我们可以从给定的训练集中随机选取一小部分作为验证集,而将剩余部分作为真正的训练集。
K折交叉验证
由于验证数据集不参与模型训练,当训练数据不够用时,预留大量的验证数据显得太奢侈。一种改善的方法是K折交叉验证(K-fold cross-validation)。在K折交叉验证中,我们把原始训练数据集分割成K个不重合的子数据集,然后我们做K次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他K-1个子数据集来训练模型。在这K次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这K次训练误差和验证误差分别求平均。
1.3 过拟合和欠拟合
模型训练中经常出现的两类典型问题:
- 一类是模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting);
- 另一类是模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。 在实践中,我们要尽可能同时应对欠拟合和过拟合。虽然有很多因素可能导致这两种拟合问题,在这里我们重点讨论两个因素:模型复杂度和训练数据集大小。
模型复杂度
为了解释模型复杂度,我们以多项式函数拟合为例。给定一个由标量数据特征x和对应的标量标签y组成的训练数据集,多项式函数拟合的目标是找一个K阶多项式函数:

来近似 y。在上式中,wk是模型的权重参数,b是偏差参数。与线性回归相同,多项式函数拟合也使用平方损失函数。特别地,一阶多项式函数拟合又叫线性函数拟合。
给定训练数据集,模型复杂度和误差之间的关系:

训练数据集的大小
影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。此外,泛化误差不会随训练数据集里样本数量增加而增大。因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。
1.4 权重衰减
权重衰减等价于 L2范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。
L2 范数正则化(regularization):
L2范数正则化在模型原损失函数基础上添加L2范数惩罚项,从而得到训练所需要最小化的函数。L2范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。以线性回归中的线性回归损失函数为例
![]()
其中w1,w2是权重参数,b是偏差参数,样本i的输入为x1(i),x2(i),标签为y(i),样本数为n。将权重参数用向量w=[w1,w2]表示,带有L2范数惩罚项的新损失函数为:
![]()
其中超参数λ>0。当权重参数均为0时,惩罚项最小。当λ较大时,惩罚项在损失函数中的比重较大,这通常会使学到的权重参数的元素较接近0。当λλ设为0时,惩罚项完全不起作用。上式中L2范数平方|w|2展开后得到w12+w22。 有了L2范数惩罚项后,在小批量随机梯度下降中,我们将线性回归一节中权重w1和w2的迭代方式更改为:

可见,L2范数正则化令权重w1和w2先自乘小于1的数,再减去不含惩罚项的梯度。因此,L2范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。
pytorch简洁实现:
# wd为正则化的超参数
def fit_and_plot_pytorch(wd):
# 对权重参数衰减。权重名称一般是以weight结尾
net = nn.Linear(num_inputs, 1)
#初始化参数
nn.init.normal_(net.weight, mean=0, std=1)
nn.init.normal_(net.bias, mean=0, std=1)
#使用随机梯度下降
optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 对权重参数衰减
optimizer_b = torch.optim.SGD(params=[net.bias], lr=lr) # 不对偏差参数衰减
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
l = loss(net(X), y).mean()
optimizer_w.zero_grad()
optimizer_b.zero_grad()
l.backward()
# 对两个optimizer实例分别调用step函数,从而分别更新权重和偏差
optimizer_w.step()
optimizer_b.step()
train_ls.append(loss(net(train_features), train_labels).mean().item())
test_ls.append(loss(net(test_features), test_labels).mean().item())
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('L2 norm of w:', net.weight.data.norm().item())
不进行权重衰减:出现过拟合的现象
fit_and_plot_pytorch(0)

进行权重衰减:
fit_and_plot_pytorch(3)

1.5 丢弃法
多层感知机中神经网络图描述了一个单隐藏层的多层感知机。其中输入个数为4,隐藏单元个数为5,且隐藏单元hi(i=1,…,5)的计算表达式为
![]()
这里ϕ是激活函数,x1,…,x4是输入,隐藏单元i的权重参数为w1i,…,w4i,偏差参数为bi。当对该隐藏层使用丢弃法时,该层的隐藏单元将有一定概率被丢弃掉。设丢弃概率为p,那么有p的概率hi会被清零,有1−p的概率hi会除以1−p做拉伸。丢弃概率是丢弃法的超参数。具体来说,设随机变量ξi为0和1的概率分别为p和1−p。使用丢弃法时我们计算新的隐藏单元h′i
![]()
由于E(ξi)=1−pp,因此

即丢弃法不改变其输入的期望值。让我们对之前多层感知机的神经网络中的隐藏层使用丢弃法,一种可能的结果如图所示,其中h2和h5被清零。这时输出值的计算不再依赖h2和h5,在反向传播时,与这两个隐藏单元相关的权重的梯度均为0。由于在训练中隐藏层神经元的丢弃是随机的,即h1,…,h5都有可能被清零,输出层的计算无法过度依赖h1,…,h5中的任一个,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。在测试模型时,我们为了拿到更加确定性的结果,一般不使用丢弃法

代码实现:
def dropout(X, drop_prob):
X = X.float()
#保证丢弃率为0~1
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃
if keep_prob == 0:
return torch.zeros_like(X)
#生成一个随机矩阵,与保留率进行比较,决定留下的元素
mask = (torch.rand(X.shape) < keep_prob).float()
#返回一个拉伸
return mask * X / keep_prob
2、梯度消失、梯度爆炸
2.1 定义
深度模型有关数值稳定性的典型问题是消失(vanishing)和爆炸(explosion)。
当神经网络的层数较多时,模型的数值稳定性容易变差。
假设一个层数为L的多层感知机的第l层H(l)的权重参数为W(l),输出层H(L)的权重参数为W(L)。为了便于讨论,不考虑偏差参数,且设所有隐藏层的激活函数为恒等映射(identity mapping)ϕ(x)=x。给定输入X,多层感知机的第l层的输出H(l)=XW(1)W(2)…W(l)。此时,如果层数l较大,H(l)的计算可能会出现衰减或爆炸。举个例子,假设输入和所有层的权重参数都是标量,如权重参数为0.2和5,多层感知机的第30层输出为输入X分别与0.230≈1×10−21(消失)和530≈9×1020(爆炸)的乘积。当层数较多时,梯度的计算也容易出现消失或爆炸。
2.2 随机初始化模型参数
在神经网络中,通常需要随机初始化模型参数。这样做的原因:
假设输出层只保留一个输出单元o1(删去o2和o3以及指向它们的箭头),且隐藏层使用相同的激活函数。如果将每个隐藏单元的参数都初始化为相等的值,那么在正向传播时每个隐藏单元将根据相同的输入计算出相同的值,并传递至输出层。在反向传播中,每个隐藏单元的参数梯度值相等。因此,这些参数在使用基于梯度的优化算法迭代后值依然相等。之后的迭代也是如此。在这种情况下,无论隐藏单元有多少,隐藏层本质上只有1个隐藏单元在发挥作用。因此,我们通常将神经网络的模型参数,特别是权重参数,进行随机初始化。

PyTorch的默认随机初始化¶
随机初始化模型参数的方法有很多。在线性回归的简洁实现中,我们使用torch.nn.init.normal_()使模型net的权重参数采用正态分布的随机初始化方式。不过,PyTorch中nn.Module的模块参数都采取了较为合理的初始化策略(不同类型的layer具体采样的哪一种初始化方法的可参考源代码),因此一般不用我们考虑。
Xavier随机初始化
还有一种比较常用的随机初始化方法叫作Xavier随机初始化。 假设某全连接层的输入个数为a,输出个数为b,Xavier随机初始化将使该层中权重参数的每个元素都随机采样于均匀分布。

它的设计主要考虑到,模型参数初始化后,每层输出的方差不该受该层输入个数影响,且每层梯度的方差也不该受该层输出个数影响。
2.3 考虑环境因素
协变量偏移
这里我们假设,虽然输入的分布可能随时间而改变,但是标记函数,即条件分布P(y∣x)不会改变。虽然这个问题容易理解,但在实践中也容易忽视。
想想区分猫和狗的一个例子。我们的训练数据使用的是猫和狗的真实的照片,但是在测试时,我们被要求对猫和狗的卡通图片进行分类。

测试数据:

显然,这不太可能奏效。训练集由照片组成,而测试集只包含卡通。在一个看起来与测试集有着本质不同的数据集上进行训练,而不考虑如何适应新的情况,这是不是一个好主意。不幸的是,这是一个非常常见的陷阱。
统计学家称这种协变量变化是因为问题的根源在于特征分布的变化(即协变量的变化)。数学上,我们可以说P(x)改变了,但P(y∣x)保持不变。尽管它的有用性并不局限于此,当我们认为x导致y时,协变量移位通常是正确的假设。
标签偏移
当我们认为导致偏移的是标签P(y)上的边缘分布的变化,但类条件分布是不变的P(x∣y)时,就会出现相反的问题。当我们认为y导致x时,标签偏移是一个合理的假设。例如,通常我们希望根据其表现来预测诊断结果。在这种情况下,我们认为诊断引起的表现,即疾病引起的症状。有时标签偏移和协变量移位假设可以同时成立。例如,当真正的标签函数是确定的和不变的,那么协变量偏移将始终保持,包括如果标签偏移也保持。有趣的是,当我们期望标签偏移和协变量偏移保持时,使用来自标签偏移假设的方法通常是有利的。这是因为这些方法倾向于操作看起来像标签的对象,这(在深度学习中)与处理看起来像输入的对象(在深度学习中)相比相对容易一些。
病因(要预测的诊断结果)导致 症状(观察到的结果)。
训练数据集,数据很少只包含流感p(y)的样本。
而测试数据集有流感p(y)和流感q(y),其中不变的是流感症状p(x|y)。
概念偏移
另一个相关的问题出现在概念转换中,即标签本身的定义发生变化的情况。这听起来很奇怪,毕竟猫就是猫。的确,猫的定义可能不会改变,但我们能不能对软饮料也这么说呢?事实证明,如果我们周游美国,按地理位置转移数据来源,我们会发现,即使是如图所示的这个简单术语的定义也会发生相当大的概念转变。

如果我们要建立一个机器翻译系统,分布P(y∣x)可能因我们的位置而异。这个问题很难发现。另一个可取之处是P(y∣x)通常只是逐渐变化。
3、循环神经网络进阶
3.1 门控循环单位(GRU)
当时间步数较大或者时间步较小时,循环神经网络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。通常由于这个原因,循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。
门控循环神经网络(gated recurrent neural network)的提出,正是为了更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可以学习的门来控制信息的流动。其中,门控循环单元(gated recurrent unit,GRU)是一种常用的门控循环神经网络 [1, 2]。
它引入了重置门(reset gate)和更新门(update gate)的概念,从而修改了循环神经网络中隐藏状态的计算方式。
3.1.1 重置门和更新门
如图所示,门控循环单元中的重置门和更新门的输入均为当前时间步输入Xt与上一时间步隐藏状态Ht−1,输出由激活函数为sigmoid函数的全连接层计算得到。

具体来说,假设隐藏单元个数为h,给定时间步 t 的小批量输入Xt∈Rn×d(样本数为n,输入个数为d)和上一时间步隐藏状态Ht−1∈Rn×h。重置门Rt∈Rn×h和更新门Zt∈Rn×h的计算如下:

其中Wxr,Wxz∈Rd×h和Whr,Whz∈Rh×h是权重参数,br,bz∈R1×h是偏差参数。
3.1.2 候选隐藏状态
接下来,门控循环单元将计算候选隐藏状态来辅助稍后的隐藏状态计算。如图所示,我们将当前时间步重置门的输出与上一时间步隐藏状态做按元素乘法(符号为⊙)。
如果重置门中元素值接近0,那么意味着重置对应隐藏状态元素为0,即丢弃上一时间步的隐藏状态。
如果重置门中元素值接近1,那么表示保留上一时间步的隐藏状态。
然后,将按元素乘法的结果与当前时间步的输入连结,再通过含激活函数tanh的全连接层计算出候选隐藏状态,其所有元素的值域为[−1,1]。

具体来说,时间步t的候选隐藏状态H~t∈Rn×h的计算为
![]()
其中Wxh∈Rd×h和Whh∈Rh×h是权重参数,bh∈R1×h是偏差参数。从上面这个公式可以看出,重置门控制了上一时间步的隐藏状态如何流入当前时间步的候选隐藏状态。而上一时间步的隐藏状态可能包含了时间序列截至上一时间步的全部历史信息。因此,重置门可以用来丢弃与预测无关的历史信息。
如果把 看作一个黑盒的话,重置门决定前时刻对黑盒输入值的影响程度,而更新门决定前时刻对黑盒输出值的影响程度。
3.1.3 隐藏状态
最后,时间步t的隐藏状态Ht∈Rn×h的计算使用当前时间步的更新门Zt来对上一时间步的隐藏状态Ht−1和当前时间步的候选隐藏状态H~t做组合:

值得注意的是,更新门可以控制隐藏状态应该如何被包含当前时间步信息的候选隐藏状态所更新,如图所示。假设更新门在时间步t′到t(t′<t)之间一直近似1。那么,在时间步t′到t之间的输入信息几乎没有流入时间步t的隐藏状态Ht。实际上,这可以看作是较早时刻的隐藏状态Ht′−1一直通过时间保存并传递至当前时间步t。这个设计可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时间序列中时间步距离较大的依赖关系。
如果把重置门设为1,更新门设为0,那么GRU就是标准的RNN了。
对门控循环单元的设计稍作总结:
-
重置门有助于捕捉时间序列里短期的依赖关系;
-
更新门有助于捕捉时间序列里长期的依赖关系。
3.1.4 实现代码

自定义实现:
'''初始化参数'''
num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size
print('will use', device)
def get_params():
#权重参数初始化
def _one(shape):
#使用numpy生成随机正态分布,期望为0,方差为0.01
ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32) #正态分布
return torch.nn.Parameter(ts, requires_grad=True)
#打包
def _three():
# Wxh(X*H ), Whh(H*H), bh(全0初始化)
return (_one((num_inputs, num_hiddens)),
_one((num_hiddens, num_hiddens)),
torch.nn.Parameter(torch.zeros(num_hiddens, device=device, dtype=torch.float32), requires_grad=True))
W_xz, W_hz, b_z = _three() # 更新门参数
W_xr, W_hr, b_r = _three() # 重置门参数
W_xh, W_hh, b_h = _three() # 候选隐藏状态参数
# 输出层参数
W_hq = _one((num_hiddens, num_outputs))
b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device, dtype=torch.float32), requires_grad=True)
#直接返回11个参数的列表
return nn.ParameterList([W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q])
def init_gru_state(batch_size, num_hiddens, device): #隐藏状态初始化
return (torch.zeros((batch_size, num_hiddens), device=device), )
''' 定义GRU模型 '''
def gru(inputs, state, params):
W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
#更新门的计算
Z = torch.sigmoid(torch.matmul(X, W_xz) + torch.matmul(H, W_hz) + b_z)
#重置门的计算
R = torch.sigmoid(torch.matmul(X, W_xr) + torch.matmul(H, W_hr) + b_r)
#候选隐藏层
H_tilda = torch.tanh(torch.matmul(X, W_xh) + R * torch.matmul(H, W_hh) + b_h)
#隐藏层
H = Z * H + (1 - Z) * H_tilda
#神经元输出
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H,)
'''训练模型'''
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
'''预测'''
d2l.train_and_predict_rnn(gru, get_params, init_gru_state, num_hiddens,
vocab_size, device, corpus_indices, idx_to_char,
char_to_idx, False, num_epochs, num_steps, lr,
clipping_theta, batch_size, pred_period, pred_len,
prefixes)
简洁实现:
num_hiddens=256
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
lr = 1e-2 # 学习率
gru_layer = nn.GRU(input_size=vocab_size, hidden_size=num_hiddens)
model = d2l.RNNModel(gru_layer, vocab_size).to(device)
d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes)
3.2 长短期记忆(LSTM)
Long Short Term Memory networks(以下简称LSTMs),一种特殊的RNN网络,该网络设计出来是为了解决长依赖问题。该网络由 Hochreiter & Schmidhuber (1997)引入,并有许多人对其进行了改进和普及。
长依赖存在的问题
RNNs理论上是可以将以前的信息与当前的任务进行连接,例如使用以前的视频帧来帮助网络理解当前帧。如果RNNs能做到这一点,那将会是非常的有用。但是他们能做到这点吗?答案是不一定。
有时候我们需要利用近期的信息来执行来处理当前的任务。例如,考虑用一个语言模型通过利用以前的文字信息来预测下一个文字。如果我们需要预测“the clouds are in the sky”这句话的最后一个字,我们不需要其他的信息,通过前面的语境就能知道最后一个字应该是sky。在这种情况下,相关信息与需要该信息的位置距离较近,RNNs能够学习利用以前的信息来对当前任务进行相应的操作。如下图所示通过输入的信息x1、x2来预测出 h3.

假设现在有个更为复杂的任务,考虑到下面这句话“I grew up in France… I speak fluent French.”,现在需要语言模型通过现有以前的文字信息预测该句话的最后一个字。通过以前文字语境可以预测出最后一个字是某种语言,但是要猜测出French,要根据之前的France语境。这样的任务,不同之前,因为这次的有用信息与需要进行处理信息的地方之间的距离较远,这样容易导致RNNs不能学习到有用的信息,最终推导的任务可能失败。如下图所示。

LSTM 中引入了3个门,即输入门(input gate)、遗忘门(forget gate)和输出门(output gate),以及与隐藏状态形状相同的记忆细胞(某些文献把记忆细胞当成一种特殊的隐藏状态),从而记录额外的信息。
3.2.1 输入门、遗忘门和输出门
与门控循环单元中的重置门和更新门一样,如图所示,长短期记忆的门的输入均为当前时间步输入Xt与上一时间步隐藏状态Ht−1,输出由激活函数为sigmoid函数的全连接层计算得到。如此一来,这3个门元素的值域均为[0,1]。

具体来说,假设隐藏单元个数为h,给定时间步t的小批量输入Xt∈Rn×d(样本数为n,输入个数为d)和上一时间步隐藏状态Ht−1∈Rn×h。 时间步tt的输入门It∈Rn×h、遗忘门Ft∈Rn×h和输出门Ot∈Rn×h分别计算如下:

其中的Wxi,Wxf,Wxo∈Rd×h和Whi,Whf,Who∈Rh×h是权重参数,bi,bf,bo∈R1×h是偏差参数。
3.2.2 候选记忆细胞
接下来,长短期记忆需要计算候选记忆细胞C~t。它的计算与上面介绍的3个门类似,但使用了值域在[−1,1]的tanh函数作为激活函数,如图所示。

具体来说,时间步t的候选记忆细胞C~t∈Rn×h的计算为
![]()
3.2.3 记忆细胞
我们可以通过元素值域在[0,1]的输入门、遗忘门和输出门来控制隐藏状态中信息的流动,这一般也是通过使用按元素乘法(符号为⊙)来实现的。当前时间步记忆细胞Ct∈Rn×h的计算组合了上一时间步记忆细胞和当前时间步候选记忆细胞的信息,并通过遗忘门和输入门来控制信息的流动:
![]()
遗忘门控制上一时间步的记忆细胞Ct−1中的信息是否传递到当前时间步,而输入门则控制当前时间步的输入Xt通过候选记忆细胞C~t如何流入当前时间步的记忆细胞。如果遗忘门一直近似1且输入门一直近似0,过去的记忆细胞将一直通过时间保存并传递至当前时间步。这个设计可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时间序列中时间步距离较大的依赖关系。

3.2.4 隐藏状态
有了记忆细胞以后,接下来我们还可以通过输出门来控制从记忆细胞到隐藏状态Ht∈Rn×h的信息的流动:
![]()
这里的tanh函数确保隐藏状态元素值在-1到1之间。需要注意的是,当输出门近似1时,记忆细胞信息将传递到隐藏状态供输出层使用;当输出门近似0时,记忆细胞信息只自己保留。下图展示了长短期记忆中隐藏状态的计算。

3.2.5 实现代码

自定义实现
''' 定义LSTM模型 '''
def lstm(inputs, state, params):
[W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c, W_hq, b_q] = params
(H, C) = state
outputs = []
for X in inputs:
#输入门
I = torch.sigmoid(torch.matmul(X, W_xi) + torch.matmul(H, W_hi) + b_i)
# 遗忘门
F = torch.sigmoid(torch.matmul(X, W_xf) + torch.matmul(H, W_hf) + b_f)
# 输出门
O = torch.sigmoid(torch.matmul(X, W_xo) + torch.matmul(H, W_ho) + b_o)
# 候选记忆细胞
C_tilda = torch.tanh(torch.matmul(X, W_xc) + torch.matmul(H, W_hc) + b_c)
# 记忆细胞
C = F * C + I * C_tilda
# 隐藏状态
H = O * C.tanh()
#神经元输出
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H, C)
简洁实现
'''简洁实现'''
num_hiddens=256
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
lr = 1e-2 # 学习率
lstm_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens)
model = d2l.RNNModel(lstm_layer, vocab_size)
d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes)
3.3 深度循环神经网络
含有多个隐藏层的循环神经网络称作深度循环神经网络。下图演示了一个有L个隐藏层的深度循环神经网络,每个隐藏状态不断传递至当前层的下一时间步和当前时间步的下一层。

num_hiddens=256
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
lr = 1e-2 # 注意调整学习率
# num_layers 控制隐藏层的层数
#以下例子是定义了一个2层的深度循环神经网络
gru_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens,num_layers=2)
model = d2l.RNNModel(gru_layer, vocab_size).to(device)
d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes)
3.4 双向循环神经网络
上文介绍的GRU或者LSTM都是单向的,即位置(或时刻)i 的状态值只与从位置 0 到 位置 i 的输入有关,与从 位置 i+1 到结束的输入都没有关系,也就是只有”上文“。如何获得“下文”信息呢。可以让RNN反向再计算一遍,即从输入的结束位置走向开始位置,每个位置的状态值就是正向RNN和反向RNN在当前位置状态值的拼接。这种RNN称为双向RNN。

num_hiddens=128
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e-2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
lr = 1e-2 # 学习率
#bidirectional 参数决定是否使用双向循环神经网络
gru_layer = nn.GRU(input_size=vocab_size, hidden_size=num_hiddens,bidirectional=True)
model = d2l.RNNModel(gru_layer, vocab_size).to(device)
d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes)
参考资料:
http://zh.gluon.ai/chapter_recurrent-neural-networks/lstm.html
https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/jupyter/S45g56IgzHOEPGof2m51V
https://www.jianshu.com/p/95d5c461924c
https://blog.csdn.net/Uwr44UOuQcNsUQb60zk2/article/details/78888834
https://arxiv.org/pdf/1406.1078v3.pdf
http://www.bioinf.jku.at/publications/older/2604.pdf

3329

被折叠的 条评论
为什么被折叠?



