循环神经网络

本节介绍循环神经网络,下图展示了如何基于循环神经网络实现语言模型。我们的目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。循环神经网络引入一个隐藏变量 H H H,用 H t H_{t} Ht​表示 H H H在时间步 t t t的值。 H t H_{t} Ht​的计算基于 X t X_{t} Xt​和 H t − 1 H_{t-1} Ht−1​,可以认为 H t H_{t} Ht​记录了到当前字符为止的序列信息,利用 H t H_{t} Ht​对序列的下一个字符进行预测。

循环神经网络的构造

我们先看循环神经网络的具体构造。假设 X t ∈ R n × d \boldsymbol{X}_t \in \mathbb{R}^{n \times d} Xt​∈Rn×d是时间步 t t t的小批量输入, H t ∈ R n × h \boldsymbol{H}_t \in \mathbb{R}^{n \times h} Ht​∈Rn×h是该时间步的隐藏变量,则:

H t = ϕ ( X t W x h + H t − 1 W h h + b h ) . \boldsymbol{H}_t = \phi(\boldsymbol{X}_t \boldsymbol{W}_{xh} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hh} + \boldsymbol{b}_h). Ht​=ϕ(Xt​Wxh​+Ht−1​Whh​+bh​).

其中, W x h ∈ R d × h \boldsymbol{W}_{xh} \in \mathbb{R}^{d \times h} Wxh​∈Rd×h, W h h ∈ R h × h \boldsymbol{W}_{hh} \in \mathbb{R}^{h \times h} Whh​∈Rh×h, b h ∈ R 1 × h \boldsymbol{b}_{h} \in \mathbb{R}^{1 \times h} bh​∈R1×h, ϕ \phi ϕ函数是非线性激活函数。由于引入了 H t − 1 W h h \boldsymbol{H}_{t-1} \boldsymbol{W}_{hh} Ht−1​Whh​, H t H_{t} Ht​能够捕捉截至当前时间步的序列的历史信息,就像是神经网络当前时间步的状态或记忆一样。由于 H t H_{t} Ht​的计算基于 H t − 1 H_{t-1} Ht−1​,上式的计算是循环的,使用循环计算的网络即循环神经网络(recurrent neural network)。

在时间步 t t t,输出层的输出为:

O t = H t W h q + b q . \boldsymbol{O}_t = \boldsymbol{H}_t \boldsymbol{W}_{hq} + \boldsymbol{b}_q. Ot​=Ht​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。

从零开始实现循环神经网络

我们先尝试从零开始实现一个基于字符级循环神经网络的语言模型,这里我们使用周杰伦的歌词作为语料,首先我们读入数据:

import torch
import torch.nn as nn
import time
import math
import sys
sys.path.append("/home/kesci/input")
import d2l_jay9460 as d2l
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

one-hot向量

我们需要将字符表示成向量,这里采用one-hot向量。假设词典大小是 N N N,每次字符对应一个从 0 0 0到 N − 1 N-1 N−1的唯一的索引,则该字符的向量是一个长度为 N N N的向量,若字符的索引是 i i i,则该向量的第 i i i个位置为 1 1 1,其他位置为 0 0 0。下面分别展示了索引为0和2的one-hot向量,向量长度等于词典大小。

def one_hot(x, n_class, dtype=torch.float32):result = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device)  # shape: (n, n_class)result.scatter_(1, x.long().view(-1, 1), 1)  # result[i, x[i, 0]] = 1,和gather差不多,在指定位置填充1return resultx = torch.tensor([0, 2])
x_one_hot = one_hot(x, vocab_size)
print(x_one_hot)
print(x_one_hot.shape)
print(x_one_hot.sum(axis=1))
tensor([[1., 0., 0.,  ..., 0., 0., 0.],[0., 0., 1.,  ..., 0., 0., 0.]])
torch.Size([2, 1027])
tensor([1., 1.])

我们每次采样的小批量的形状是(批量大小, 时间步数)。下面的函数将这样的小批量变换成数个形状为(批量大小, 词典大小)的矩阵,矩阵个数等于时间步数。也就是说,时间步 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为词向量大小,即one-hot向量长度(词典大小)。

def to_onehot(X, n_class):return [one_hot(X[:, i], n_class) for i in range(X.shape[1])]X = torch.arange(10).view(2, 5)
inputs = to_onehot(X, vocab_size)
# 一列为单位的处理数据
print(len(inputs), inputs[0].shape)
5 torch.Size([2, 1027])

初始化模型参数

num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size
# num_inputs: d
# num_hiddens: h, 隐藏单元的个数是超参数
# num_outputs: qdef get_params():def _one(shape):param = torch.zeros(shape, device=device, dtype=torch.float32)nn.init.normal_(param, 0, 0.01)return torch.nn.Parameter(param)# 隐藏层参数W_xh = _one((num_inputs, num_hiddens)) #1027*256W_hh = _one((num_hiddens, num_hiddens))#256*256b_h = torch.nn.Parameter(torch.zeros(num_hiddens, device=device))#1*256,广播机制# 输出层参数W_hq = _one((num_hiddens, num_outputs))b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device))return (W_xh, W_hh, b_h, W_hq, b_q)

定义模型

函数rnn用循环的方式依次完成循环神经网络每个时间步的计算。

def rnn(inputs, state, params):# inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵,比如我们下面使用35个batch,预测32个字符 W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:H = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(H, W_hh) + b_h)# 隐藏层Y = torch.matmul(H, W_hq) + b_q# 这里使用了广播机制outputs.append(Y)return outputs, (H,)# H默认变成了元组

函数init_rnn_state初始化隐藏变量,这里的返回值是一个元组。

def init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device), )#初始化隐藏数据

做个简单的测试来观察输出结果的个数(时间步数),以及第一个时间步的输出层输出的形状和隐藏状态的形状。

print(X.shape)
print(num_hiddens)
print(vocab_size)
state = init_rnn_state(X.shape[0], num_hiddens, device)
inputs = to_onehot(X.to(device), vocab_size)
params = get_params()
outputs, state_new = rnn(inputs, state, params)
print(len(inputs), inputs[0].shape)
print(len(outputs), outputs[0].shape)
print(len(state), state[0].shape)
print(len(state_new), state_new[0].shape)
torch.Size([2, 5])
256
1027
5 torch.Size([2, 1027])
5 torch.Size([2, 1027])
1 torch.Size([2, 256])
1 torch.Size([2, 256])

裁剪梯度

循环神经网络中较容易出现梯度衰减或梯度爆炸,这会导致网络几乎无法训练。裁剪梯度(clip gradient)是一种应对梯度爆炸的方法。假设我们把所有模型参数的梯度拼接成一个向量 g \boldsymbol{g} g,并设裁剪的阈值是 θ \theta θ。裁剪后的梯度

min ⁡ ( θ ∥ g ∥ , 1 ) g \min\left(\frac{\theta}{\|\boldsymbol{g}\|}, 1\right)\boldsymbol{g} min(∥g∥θ​,1)g

的 L 2 L_2 L2​范数不超过 θ \theta θ。

def grad_clipping(params, theta, device):norm = torch.tensor([0.0], device=device)for param in params:norm += (param.grad.data ** 2).sum()# 先求和再说,刚好绝对值可以取sqrtnorm = norm.sqrt().item()if norm > theta:for param in params:param.grad.data *= (theta / norm)

定义预测函数

以下函数基于前缀prefix(含有数个字符的字符串)来预测接下来的num_chars个字符。这个函数稍显复杂,其中我们将循环神经单元rnn设置成了函数参数,这样在后面小节介绍其他循环神经网络时能重复使用这个函数。

def predict_rnn(prefix, num_chars, rnn, params, init_rnn_state,num_hiddens, vocab_size, device, idx_to_char, char_to_idx):
# prefix : input
# num_chars:创作的长度
# rnn: 网络计算
# params:参数计算
# init_rnn_state:隐藏层参数
# num_hiddens:隐藏层单元数
# vocab_size:1027
# device :是否是用cpu,gpu
# idx_to_char和char_to_idx 字典state = init_rnn_state(1, num_hiddens, device)output = [char_to_idx[prefix[0]]]   # output记录prefix加上预测的num_chars个字符for t in range(num_chars + len(prefix) - 1):# 将上一时间步的输出作为当前时间步的输入X = to_onehot(torch.tensor([[output[-1]]], device=device), 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(Y[0].argmax(dim=1).item())return ''.join([idx_to_char[i] for i in output])

我们先测试一下predict_rnn函数。我们将根据前缀“分开”创作长度为10个字符(不考虑前缀长度)的一段歌词。因为模型参数为随机值,所以预测结果也是随机的。

predict_rnn('分开', 10, rnn, params, init_rnn_state, num_hiddens, vocab_size,device, idx_to_char, char_to_idx)
'分开所皮丘雨牛边彻藏暴涌'

困惑度

我们通常使用困惑度(perplexity)来评价语言模型的好坏。回忆一下“softmax回归”一节中交叉熵损失函数的定义。困惑度是对交叉熵损失函数做指数运算后得到的值。特别地,

  • 最佳情况下,模型总是把标签类别的概率预测为1,此时困惑度为1;
  • 最坏情况下,模型总是把标签类别的概率预测为0,此时困惑度为正无穷;
  • 基线情况下,模型总是预测所有类别的概率都相同,此时困惑度为类别个数。

显然,任何一个有效模型的困惑度必须小于类别个数。在本例中,困惑度必须小于词典大小vocab_size

定义模型训练函数

跟之前章节的模型训练函数相比,这里的模型训练函数有以下几点不同:

  1. 使用困惑度评价模型。
  2. 在迭代模型参数前裁剪梯度。
  3. 对时序数据采用不同采样方法将导致隐藏状态初始化的不同。
def train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens,vocab_size, device, 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):
# rnn:循环神经网络
# get_params:初始化参数的函数
# init_rnn_state:初始化隐藏层状态
# num_hiddens:隐藏层单元数
# vocab_size:1027
# corpus_indices:字符集合
# idx_to_char和char_to_idx 字典
# num_epochs:训练次数
# num_steps: 步长if is_random_iter:data_iter_fn = d2l.data_iter_randomelse:data_iter_fn = d2l.data_iter_consecutiveparams = get_params()loss = nn.CrossEntropyLoss()#交叉熵损失函数for epoch in range(num_epochs):if not is_random_iter:  # 如使用相邻采样,在epoch开始时初始化隐藏状态state = init_rnn_state(batch_size, num_hiddens, device)#
# 相邻的两个随机小批量在原始序列上的位置相毗邻。这时候,可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态
# 从而使下一个小批量的输出也取决于当前小批量的输入,并如此循环下去。
# 这对实现循环神经网络造成了两方面影响:一方面,在训练模型时,
#只需要在每一个迭代周期开始时初始化隐藏状态;另一方面,当多个相邻小批量通过传递隐藏状态串联起来时,模型参数的梯度计算将依赖所有串联起来的小批量序列l_sum, n, start = 0.0, 0, time.time()data_iter = data_iter_fn(corpus_indices, batch_size, num_steps, device)for X, Y in data_iter:# print(Y)if is_random_iter:  # 如使用随机采样,在每个小批量更新前初始化隐藏状态#在随机采样照片那个,每个样本是原始序列上任意截取的一段序列。相邻的两个随机小批量在原始序列上的位置不一定相毗邻。因此,无法用一个小批量的最终时间步的#隐藏状态来初始化下一个小批量的隐藏状态。在训练模型时,每次随机采样前都需要重新初始化隐藏状态state = init_rnn_state(batch_size, num_hiddens, device)else:  # 否则需要使用detach函数从计算图分离隐藏状态for s in state:s.detach_()# 这里相邻采样只能更新这一次的举证,不能更新上一次和上上上次的# inputs是num_steps个形状为(batch_size, vocab_size)的矩阵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)# print(outputs[0].shape)# print(outputs[0].sum())outputs = torch.cat(outputs, dim=0)# Y的形状是(batch_size, num_steps),转置后再变成形状为# (num_steps * batch_size,)的向量,这样跟输出的行一一对应# print(outputs.shape)y = torch.flatten(Y.T)# print(y.shape)# print(y)# # 使用交叉熵损失计算平均分类误差# print("------------------")l = loss(outputs, y.long())# 刚好是索引位置嘛,比如outputs的维度1020*1027,y的维度# 梯度清0# print(l)if params[0].grad is not None:for param in params:param.grad.data.zero_()l.backward()grad_clipping(params, clipping_theta, device)  # 裁剪梯度d2l.sgd(params, lr, 1)  # 因为误差已经取过均值,梯度不用再做平均l_sum += l.item() * y.shape[0]# print(l_sum)n += y.shape[0]# print(n)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_hiddens, vocab_size, device, idx_to_char, char_to_idx))

训练模型并创作歌词

现在我们可以训练模型了。首先,设置模型超参数。我们将根据前缀“分开”和“不分开”分别创作长度为50个字符(不考虑前缀长度)的一段歌词。我们每过50个迭代周期便根据当前训练的模型创作一段歌词。

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_hiddens,vocab_size, device, 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 67.929526, time 0.63 sec- 分开 我不要这 你不我 别怪我 我不就 我爱我 我不就 我爱我 我不就 我爱我 我不就 我爱我 我不就 - 不分开 我不要 不要我 我不想 我爱我 我不就 我爱我 我不就 我爱我 我不就 我爱我 我不就 我爱我 我
epoch 100, perplexity 10.348854, time 0.62 sec- 分开 有使用双截棍 哼哼哈兮 快使用双截棍 哼哼哈兮 快使用双截棍 哼哼哈兮 快使用双截棍 哼哼哈兮 快- 不分开吗  爱有你的我都 思可以 我想很 我满就这样牵 你的手不主 这色的美丽  让在你在不堡 你满 你又
epoch 150, perplexity 2.904706, time 0.83 sec- 分开 有我都在你 那场悲剧 是我将上 我的手爸可 我的可美 你想是你 没有之空 你的手空 我想一定 我不- 不分开扫 我不能再想 我不 我不 我不要再想你 不知不觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉
epoch 200, perplexity 1.605697, time 0.61 sec- 分开 有愿像人 我有一人热粥 配者几敌 我已好好生活 我知好好你活我不觉 我才没这 随想是通这人人一起鳅- 不分开期 我叫你爸 你打我妈 这样跟吗干嘛这样 我以女神之名许愿 思念像底格里阳Baby  我的世界已狂风
epoch 250, perplexity 1.322629, time 0.61 sec- 分开 有在病不子 白色都烛 让暖怕日出 白色蜡烛 温暖了空屋 白色蜡烛 温暖了空屋 白色蜡烛 温暖了空屋- 不分开期把的胖女巫 用拉丁文念咒语啦啦呜 她养的黑猫笑起来像哭 啦啦啦呜 一只是悬发 白色蜡烛 温暖了空屋

接下来采用相邻采样训练模型并创作歌词。

train_and_predict_rnn(rnn, get_params, init_rnn_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)
epoch 50, perplexity 59.524168, time 0.62 sec- 分开 我想要这不爱 我有 这不么 快人我 我不要的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可- 不分开 我想要这我想要你的手  不知你的让我 我有你的爱写 我有 这不么 快人我 我不要的可爱女人 坏坏的
epoch 100, perplexity 6.701895, time 0.60 sec- 分开 我想要这样牵 你不着你里 几天的乌丽 我的完美 在小村外的溪边河口默默等著我 娘子依旧每日折一枝杨- 不分开 我想道这些不着你的 不放再一个会慢 一壶好酒 再来一碗热粥 配上几斤的牛肉 我说店小二 三两银够不
epoch 150, perplexity 1.990894, time 0.64 sec- 分开 这候我 谁怪眼 一九了依旧每一铁一枝杨柳 在小村外的溪边河口 默默的在等著我 家乡的爹娘早已苍老了- 不分开觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我该好好生
epoch 200, perplexity 1.279820, time 0.61 sec- 分开 问候我 谁是神枪手 巫师 他念么 有词的 对酋长下诅咒 还我骷髅头 这故事 告诉我 印地安的传说 - 不分开觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我该好好生
epoch 250, perplexity 1.213395, time 0.65 sec- 分开 我想就这样牵 我的能有主 出在一美在 娘有掌怕走 三分拽斗牛 有分球 它在空中停留 所有人看着我 - 不分开觉 你已经离开我 将知不觉 我跟了这节奏 后知后觉 又过了一个秋 哼哼哈兮 习使用双截棍 哼哼哈兮

循环神经网络的简介实现

定义模型

我们使用Pytorch中的nn.RNN来构造循环神经网络。在本节中,我们主要关注nn.RNN的以下几个构造函数参数:

  • input_size - The number of expected features in the input x 输入x的特征数量
  • hidden_size – The number of features in the hidden state h # 隐藏状态h的特征数量
  • nonlinearity – The non-linearity to use. Can be either ‘tanh’ or ‘relu’. Default: ‘tanh’
  • batch_first – If True, then the input and output tensors are provided as (batch_size, num_steps, input_size). Default: False

这里的batch_first决定了输入的形状,我们使用默认的参数False,对应的输入形状是 (num_steps, batch_size, input_size)。# 为了并行计算

forward函数的参数为:

  • input of shape (num_steps, batch_size, input_size): tensor containing the features of the input sequence.
  • h_0 of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the initial hidden state for each element in the batch. Defaults to zero if not provided. If the RNN is bidirectional, num_directions should be 2, else it should be 1.

forward函数的返回值是:

  • output of shape (num_steps, batch_size, num_directions * hidden_size): tensor containing the output features (h_t) from the last layer of the RNN, for each t.
  • h_n of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the hidden state for t = num_steps.

现在我们构造一个nn.RNN实例,并用一个简单的例子来看一下输出的形状。

rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps, batch_size = 35, 2
X = torch.rand(num_steps, batch_size, vocab_size)
state = None
Y, state_new = rnn_layer(X, state)
print(Y.shape, state_new.shape)
torch.Size([35, 2, 256]) torch.Size([1, 2, 256])

我们定义一个完整的基于循环神经网络的语言模型。

class RNNModel(nn.Module):# rnn_layer就是一个rnn的实例def __init__(self, rnn_layer, vocab_size):super(RNNModel, self).__init__()self.rnn = rnn_layerself.hidden_size = rnn_layer.hidden_size * (2 if rnn_layer.bidirectional else 1) # 判断是否是双向rnnself.vocab_size = vocab_sizeself.dense = nn.Linear(self.hidden_size, vocab_size)def forward(self, inputs, state):# inputs.shape: (batch_size, num_steps)X = to_onehot(inputs, vocab_size)X = torch.stack(X)  # X.shape: (num_steps, batch_size, vocab_size)hiddens, state = self.rnn(X, state)# 隐藏层的输出当做后一层的输入hiddens = hiddens.view(-1, hiddens.shape[-1])  # hiddens.shape: (num_steps * batch_size, hidden_size)output = self.dense(hiddens)# 输出层return output, state

类似的,我们需要实现一个预测函数,与前面的区别在于前向计算和初始化隐藏状态。

def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,char_to_idx):state = Noneoutput = [char_to_idx[prefix[0]]]  # output记录prefix加上预测的num_chars个字符for t in range(num_chars + len(prefix) - 1):X = torch.tensor([output[-1]], device=device).view(1, 1)(Y, state) = model(X, state)  # 前向计算不需要传入模型参数if t < len(prefix) - 1:output.append(char_to_idx[prefix[t + 1]])else:output.append(Y.argmax(dim=1).item())return ''.join([idx_to_char[i] for i in output])

使用权重为随机值的模型来预测一次。

model = RNNModel(rnn_layer, vocab_size).to(device)
predict_rnn_pytorch('分开', 10, model, vocab_size, device, idx_to_char, char_to_idx)
'分开忧夜的模熟哪娇空静哪'

接下来实现训练函数,这里只使用了相邻采样。

def 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):
# model:模型
# num_hiddens:隐藏层单元loss = nn.CrossEntropyLoss()optimizer = torch.optim.Adam(model.parameters(), lr=lr)# 亚当算法 model.to(device)#使用那个cpu或者gpu计算for epoch in range(num_epochs):l_sum, n, start = 0.0, 0, time.time()data_iter = d2l.data_iter_consecutive(corpus_indices, batch_size, num_steps, device) # 相邻采样state = Nonefor X, Y in data_iter:if state is not None:# 使用detach函数从计算图分离隐藏状态if isinstance (state, tuple): # LSTM, state:(h, c)  state[0].detach_()state[1].detach_()else: state.detach_()(output, state) = model(X, state) # output.shape: (num_steps * batch_size, vocab_size)y = torch.flatten(Y.T)l = loss(output, y.long())print(output[0])print(y[0])print("-------------------------------")optimizer.zero_grad()l.backward()grad_clipping(model.parameters(), clipping_theta, device)# 裁剪梯度optimizer.step()l_sum += l.item() * y.shape[0]n += y.shape[0]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_pytorch(prefix, pred_len, model, vocab_size, device, idx_to_char,char_to_idx))

训练模型。

num_epochs, batch_size, lr, clipping_theta = 250, 32, 1e-3, 1e-2
pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
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)
tensor([-1.7584, -0.4641, -0.3777,  ...,  0.6463, -1.3786, -0.4561],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 4.7430,  5.2293,  5.4438,  ...,  0.6300, -3.0760,  1.8260],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.5158, -2.0698, -1.7847,  ...,  0.8035, -0.1805, -0.4423],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-5.9679, -1.9825,  0.7899,  ...,  9.8641, -4.0722,  3.8454],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.1644,  2.3594,  6.1861,  ..., -0.6128, -2.0043, -1.5041],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.4182, -1.2745, -4.1077,  ...,  0.4688, -6.3582,  0.0124],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-4.5373, -0.2817, -1.2998,  ...,  0.6093, -0.2337, -2.7635],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 2.9567,  0.6980, -1.0951,  ..., -0.4968, -1.1878, -0.7095],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.7736, -0.4756, -0.3824,  ...,  0.6237, -1.4469, -0.4902],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 4.2874,  4.9127,  7.0734,  ...,  1.5729, -2.8770,  1.7808],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-6.6567, -1.9609, -1.1750,  ...,  0.5156, -1.3099, -0.5078],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.3009, -1.3911,  1.2750,  ...,  9.2531, -4.0673,  3.3710],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-4.9152,  2.1322,  4.5285,  ...,  2.3088, -3.2799, -2.3142],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-3.7047, -1.0695, -4.2319,  ...,  1.1391, -6.3597,  0.5378],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.4523, -0.3997, -1.4624,  ...,  1.4762, -2.1132, -2.1540],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.6108, -0.6677, -0.7884,  ...,  0.4010, -1.3026, -0.9695],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.7879, -0.4774, -0.3910,  ...,  0.6410, -1.4873, -0.4941],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 3.6031,  4.3565,  6.3373,  ...,  2.8811, -2.7559,  1.8911],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-4.7759, -2.3441, -1.3547,  ...,  1.5224, -0.6612, -0.1565],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.2193, -0.8321,  1.4073,  ...,  9.4418, -4.6530,  3.4882],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.6279,  1.8462,  6.4795,  ...,  1.1060, -1.5530, -1.8384],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.2396, -1.0237, -4.8945,  ...,  1.1494, -5.9388,  0.0232],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-4.0550, -0.0047, -1.5027,  ...,  2.2899, -0.5440, -3.0277],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 2.6631, -0.3339, -2.0424,  ..., -1.0071, -0.1642, -1.0633],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8029, -0.4822, -0.4039,  ...,  0.6417, -1.5244, -0.5068],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 6.1317,  4.4450,  7.0393,  ..., -0.4241, -2.0476,  2.0711],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.2094, -3.0263, -0.7831,  ...,  1.6189, -0.8268, -0.4679],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.8191, -1.7748,  0.4464,  ..., 10.1853, -3.9914,  2.8143],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.9854,  1.2491,  5.8663,  ...,  3.6424, -2.6451, -2.5774],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-3.4342, -0.9451, -4.1290,  ...,  0.3313, -5.9646,  0.4998],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.0426, -0.0281, -0.8910,  ...,  1.9864, -1.4816, -3.0482],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.2015, -0.8595, -2.2171,  ...,  0.2362, -0.7098, -1.2711],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8197, -0.4845, -0.4205,  ...,  0.6330, -1.5576, -0.5156],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 4.1249,  4.5818,  5.0191,  ...,  1.6688, -3.1338,  1.8107],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.1193, -3.2272, -1.4109,  ...,  1.7739, -0.4322, -0.7021],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-5.9856, -1.5483,  0.7515,  ..., 10.3214, -4.6812,  3.3453],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.5395,  0.9476,  5.4764,  ...,  1.7341, -1.7372, -2.5551],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.3174, -1.3061, -4.1042,  ...,  0.7366, -6.3892,  0.2016],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.4719, -0.6833, -2.0536,  ...,  1.8806, -0.8241, -2.7156],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.6031,  0.1380, -1.1338,  ..., -1.2378, -0.8098, -0.5107],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8322, -0.4823, -0.4224,  ...,  0.6021, -1.5819, -0.5151],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 5.0508,  4.8556,  5.2128,  ...,  0.5135, -3.4589,  1.9873],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-4.7205e+00, -2.5941e+00, -7.0756e-01,  ...,  7.6498e-01,-8.2312e-01, -2.8844e-03], grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.5877, -1.2486,  1.3838,  ...,  9.6258, -4.2197,  3.5546],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.9750,  1.1547,  4.6897,  ...,  1.9401, -2.3884, -2.3837],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-3.8514, -1.4306, -4.1055,  ...,  0.6419, -6.1652,  0.4970],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-4.9981, -0.7876, -1.7796,  ...,  2.2957, -1.0302, -2.5552],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.3461,  0.0482, -1.6990,  ..., -1.0304, -0.7100, -1.1010],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8512, -0.4770, -0.4234,  ...,  0.5899, -1.6069, -0.5046],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 4.7845,  5.0922,  5.6661,  ...,  0.9959, -3.4858,  2.1965],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.3246, -2.9068, -1.1464,  ...,  0.9542, -0.4090, -0.3565],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.8036, -1.4507,  1.1085,  ..., 10.1311, -4.2515,  3.5997],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-4.0890,  1.2440,  5.1431,  ...,  1.8449, -2.1528, -2.4826],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.1043, -1.1641, -4.1506,  ...,  0.0859, -6.3302,  0.6900],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.2739, -0.9872, -2.0206,  ...,  2.4924, -0.9547, -2.8917],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.8941, -0.2192, -1.5055,  ..., -0.7366, -0.8116, -0.7962],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8667, -0.4733, -0.4286,  ...,  0.5794, -1.6266, -0.5030],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 5.2921,  5.1390,  5.7012,  ...,  0.4327, -2.8576,  2.1775],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.1073, -3.3362, -1.0866,  ...,  1.1022, -0.0459, -0.3580],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.6286, -1.6743,  1.0363,  ..., 10.2252, -4.3558,  3.4797],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.3960,  1.2583,  5.1069,  ...,  1.4001, -1.9322, -2.5487],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.1415, -1.2760, -4.1167,  ...,  0.3745, -6.3824,  0.5944],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.1891, -0.9169, -2.0215,  ...,  1.9192, -0.9390, -2.9330],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.6999, -0.2525, -1.8449,  ..., -0.9385, -0.7500, -0.9959],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8794, -0.4752, -0.4324,  ...,  0.5821, -1.6456, -0.5048],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 5.1635,  4.9867,  5.6412,  ...,  0.7313, -3.0908,  2.0201],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-5.0669, -2.9052, -1.0781,  ...,  0.5366, -0.2813, -0.2317],grad_fn=<SelectBackward>)
tensor(991.)
-------------------------------
tensor([-6.6410, -1.4956,  1.0214,  ..., 10.0448, -4.4518,  3.5077],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-3.6515,  1.3111,  5.2460,  ...,  1.5358, -2.1820, -2.5101],grad_fn=<SelectBackward>)
tensor(146.)
-------------------------------
tensor([-4.1462, -1.1848, -4.1859,  ...,  0.4818, -6.5522,  0.5482],grad_fn=<SelectBackward>)
tensor(798.)
-------------------------------
tensor([-5.1094, -0.7168, -2.0975,  ...,  1.9004, -0.9764, -2.8939],grad_fn=<SelectBackward>)
tensor(260.)
-------------------------------
tensor([ 3.7587, -0.4151, -1.7637,  ..., -0.7762, -0.6003, -0.9677],grad_fn=<SelectBackward>)
tensor(104.)
-------------------------------
tensor([-1.8922, -0.4809, -0.4364,  ...,  0.5823, -1.6616, -0.5090],grad_fn=<SelectBackward>)
tensor(208.)
-------------------------------
tensor([ 5.2607,  4.9714,  5.8125,  ...,  0.6461, -3.1313,  2.1894],grad_fn=<SelectBackward>)
tensor(104.)

总结

当我们再训练网络的时候可能希望保持一部分的网络参数不变,只对其中一部分的参数进行调整;或者值训练部分分支网络,并不让其梯度对主网络的梯度造成影响,这时候我们就需要使用detach()函数来切断一些分支的反向传播
detach():
返回一个新的Variable,从当前计算图中分离下来的,但是仍指向原变量的存放位置,不同之处只是requires_grad为false,得到的这个Variable永远不需要计算其梯度,不具有grad。
即使之后重新将它的requires_grad置为true,它也不会具有梯度grad
.这样我们就会继续使用这个新的Variable进行计算,后面当我们进行反向传播时,到该调用detach()的Variable就会停止,不能再继续向前进行传播
detach_()
将一个Variable从创建它的图中分离,并把它设置成叶子variable
.其实就相当于变量之间的关系本来是x -> m -> y,这里的叶子variable是x,但是这个时候对m进行了.detach_()操作,其实就是进行了两个操作:

1.将m的grad_fn的值设置为None,这样m就不会再与前一个节点x关联,这里的关系就会变成x, m -> y,此时的m就变成了叶子结点。
2.然后会将m的requires_grad设置为False,这样对y进行backward()时就不会求m的梯度。

其实detach()和detach_()很像,两个的区别就是detach_()是对本身的更改,detach()则是生成了一个新的variable
。比如x -> m -> y中如果对m进行detach(),后面如果反悔想还是对原来的计算图进行操作还是可以的
。但是如果是进行了detach_(),那么原来的计算图也发生了变化,就不能反悔了。

困惑度

pytorch-循环神经网络相关推荐

  1. Pytorch:循环神经网络-LSTM

    Pytorch: 循环神经网络:LSTM进行新闻分类 Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, Schoo ...

  2. Pytorch:循环神经网络与文本预处理

    Pytorch: 循环神经网络与文本数据预处理 Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School o ...

  3. 循环神经网络实现文本情感分类之Pytorch中LSTM和GRU模块使用

    循环神经网络实现文本情感分类之Pytorch中LSTM和GRU模块使用 1. Pytorch中LSTM和GRU模块使用 1.1 LSTM介绍 LSTM和GRU都是由torch.nn提供 通过观察文档, ...

  4. RNN 扫盲:循环神经网络解读及其 PyTorch 应用实现

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 来自 | 知乎 作者 | Lucas 地址 | https://z ...

  5. Pytorch实现基本循环神经网络RNN (3)

    1.47.Pytorch实现基本循环神经网络RNN (3) Recurrent Neural networks(Rumelhart, 1986)主要用来处理序列型数据,具有对以往数据的记忆功能.下图所 ...

  6. julia有 pytorch包吗_用 PyTorch 实现基于字符的循环神经网络 | Linux 中国

    导读:在过去的几周里,我花了很多时间用 PyTorch 实现了一个 char-rnn 的版本.我以前从未训练过神经网络,所以这可能是一个有趣的开始. 本文字数:7201,阅读时长大约: 9分钟 htt ...

  7. hot编码 字符one_用 PyTorch 实现基于字符的循环神经网络 | Linux 中国

    在过去的几周里,我花了很多时间用 PyTorch 实现了一个 char-rnn 的版本.我以前从未训练过神经网络,所以这可能是一个有趣的开始. 来源:https://linux.cn/article- ...

  8. Facebook开源 PyTorch版 fairseq,准确性最高、速度比循环神经网络快9倍

    今年5月,Facebook AI研究院(FAIR)发表了他们的研究成果fairseq,在fairseq中,他们使用了一种新型的卷积神经网络来做语言翻译,比循环神经网络的速度快了9倍,而且准确性也是现有 ...

  9. (pytorch-深度学习)使用pytorch框架nn.RNN实现循环神经网络

    使用pytorch框架nn.RNN实现循环神经网络 首先,读取周杰伦专辑歌词数据集. import time import math import numpy as np import torch f ...

  10. 使用PyTorch从零开始构建Elman循环神经网络

    摘要: 循环神经网络是如何工作的?如何构建一个Elman循环神经网络?在这里,教你手把手创建一个Elman循环神经网络进行简单的序列预测. 本文以最简单的RNNs模型为例:Elman循环神经网络,讲述 ...

最新文章

  1. 顺序Guass消去法求解线性方程组的代码实现
  2. 【集合论】Stirling 子集数 ( 斯特林子集数概念 | 放球模型 | Stirling 子集数递推公式 | 划分的二元关系 加细关系 )
  3. 列表list,元组tuple,字符串
  4. 更新IIS下WCF服务产生的两则问题
  5. C# 知识点笔记:IEnumerable的使用,利用反射动态调用方法
  6. 正确理解WPF中的TemplatedParent
  7. bootstrap table border粗细_Web前端开发(18)——Bootstrap响应式布局
  8. 日常笔记(持续更新)
  9. 中国移动java项目_Java Web开发-项目部分(中国移动科技综合管理系统)视频——私塾在线提供...
  10. python获取浏览器cookie_python3实现读取chrome浏览器cookie
  11. 集成灶哪个品牌性价比高质量好,过来人实话实说?
  12. jboot_jboot这些框架有意义吗
  13. 什么是Python中的Pip?
  14. Flutter系列(二)flutter项目打安装包
  15. 【Linux】SOCKET编程
  16. 触摸屏坏了有哪些现象_触摸屏常见的故障及解决方法(实用)
  17. 谈谈技术债务的定义,影响以及管理
  18. 输出一个贷款的迁徙率计算的代码
  19. 南昌大学航天杯第二届部分题解
  20. MariaDB+Keepalived 搭建双主HA数据库服务

热门文章

  1. 英语口语练习三十六之There's no need to,没有必要
  2. IOS开发之——硬件开发-蓝牙(06)
  3. 【面试重点系列】操作系统常见面试重点题(万字图解)
  4. Python元组转字典
  5. hibernate运行一段时候后出现假死现象
  6. 当心!你的身边有“间谍”!
  7. java 画树_JAVA画树
  8. 开发者应当了解的18套机器学习平台
  9. 实践:腾讯云IM搭建应用内类微信社交聊天模块
  10. 生日相同c语言结构体,生日相同(结构体)