赞
踩
RNN(Recurrent Neural Network,循环神经网络)是一种具有“记忆”功能的神经网络,它特别适合处理序列数据,如文本、语音、时间序列等。RNN通过在其内部结构中引入循环,使得网络能够捕捉序列中的时间依赖性和动态性。
x_t
和前一个时间步的隐藏状态 h_{t-1}
,然后输出一个当前时间步的隐藏状态 h_t
和一个可选的输出 y_t
。隐藏状态 h_t
包含了到目前为止观察到的序列的信息,它可以被看作是网络的“记忆”。RNN 的公式表示
在 RNN 中,隐藏状态 h_t
和输出 y_t
的计算通常使用以下公式:
h_t = f(W_xh * x_t + W_hh * h_{t-1} + b_h)
y_t = g(W_hy * h_t + b_y)
其中:
f
和 g
是激活函数,如 sigmoid、tanh 或 ReLU。W_xh
、W_hh
和 W_hy
是权重矩阵。b_h
和 b_y
是偏置项。RNN 的问题
尽管 RNN 在处理序列数据方面非常有效,但它也面临一些问题:
RNN 的变体
为了解决这些问题,研究人员提出了 RNN 的多种变体,其中最著名的是 LSTM(Long Short-Term Memory,长短期记忆)和 GRU(Gated Recurrent Unit,门控循环单元)。这些变体通过引入门控机制和内部状态来改进 RNN 的性能。
LSTM:LSTM 通过引入输入门、遗忘门和输出门来控制信息的流动,并使用内部状态来存储长期信息。这使得 LSTM 能够更好地学习长期依赖关系。
GRU:GRU 是 LSTM 的一个简化版本,它只包含重置门和更新门,但仍然能够捕获长期依赖关系。GRU 的计算效率通常比 LSTM 更高。
应用场景
RNN 及其变体在许多领域都有广泛的应用,包括:
H = ϕ ( X W x h + b h ) , \boldsymbol{H} = \phi(\boldsymbol{X} \boldsymbol{W}_{xh} + \boldsymbol{b}_h), H=ϕ(XWxh+bh),
其中隐藏层权重参数 W x h ∈ R d × h \boldsymbol{W}_{xh} \in \mathbb{R}^{d \times h} Wxh∈Rd×h,隐藏层偏差参数 b h ∈ R 1 × h \boldsymbol{b}_h \in \mathbb{R}^{1 \times h} bh∈R1×h, h h h为隐藏单元个数。上式相加的两项形状不同,因此将按照广播机制相加。把隐藏变量 H \boldsymbol{H} H作为输出层的输入,且设输出个数为 q q q(如分类问题中的类别数),输出层的输出为
O = H W h q + b q , \boldsymbol{O} = \boldsymbol{H} \boldsymbol{W}_{hq} + \boldsymbol{b}_q, O=HWhq+bq,
其中输出变量 O ∈ R n × q \boldsymbol{O} \in \mathbb{R}^{n \times q} O∈Rn×q, 输出层权重参数 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。如果是分类问题,我们可以使用 softmax ( O ) \text{softmax}(\boldsymbol{O}) softmax(O)来计算输出类别的概率分布。
import torch
X, W_xh = torch.randn(3, 1), torch.randn(1, 4)
H, W_hh = torch.randn(3, 4), torch.randn(4, 4)
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)
输出:
tensor([[ 5.2633, -3.2288, 0.6037, -1.3321],
[ 9.4012, -6.7830, 1.0630, -0.1809],
[ 7.0355, -2.2361, 0.7469, -3.4667]])
X
和H
按列(维度1)连结,连结后的矩阵形状为(3, 5)。可见,连结后矩阵在维度1的长度为矩阵X
和H
在维度1的长度之和(
1
+
4
1+4
1+4)。然后,将矩阵W_xh
和W_hh
按行(维度0)连结,连结后的矩阵形状为(5, 4)。最后将两个连结后的矩阵相乘,得到与上面代码输出相同的形状为(3, 4)的矩阵。torch.matmul(torch.cat((X, H), dim=1), torch.cat((W_xh, W_hh), dim=0))
输出:
tensor([[ 5.2633, -3.2288, 0.6037, -1.3321],
[ 9.4012, -6.7830, 1.0630, -0.1809],
[ 7.0355, -2.2361, 0.7469, -3.4667]])
import torch
import random
import zipfile
with zipfile.ZipFile('../../data/jaychou_lyrics.txt.zip') as zin:
with zin.open('jaychou_lyrics.txt') as f:
corpus_chars = f.read().decode('utf-8')
corpus_chars[:40]
输出:
'想要有直升机\n想要和你飞到宇宙去\n想要和你融化在一起\n融化在宇宙里\n我每天每天每'
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
vocab_size
,即词典中不同字符的个数,又称词典大小。idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
vocab_size # 1027
之后,将训练数据集中每个字符转化为索引,并打印前20个字符及其对应的索引。
corpus_indices = [char_to_idx[char] for char in corpus_chars]
sample = corpus_indices[:20]
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
print('indices:', sample)
输出:
chars: 想要有直升机 想要和你飞到宇宙去 想要和
indices: [250, 164, 576, 421, 674, 653, 357, 250, 164, 850, 217, 910, 1012, 261, 275, 366, 357, 250, 164, 850]
参数:
corpus_indices
: 文本序列的索引表示。通常,每个字符都被映射到一个唯一的索引。batch_size
: 每个批次中样本的数量。num_steps
: 每个样本中字符的数量(即序列长度)。device
: 用于存储张量的设备(如 CPU 或 GPU)。默认为 None,函数内部会根据是否有可用的 GPU 来自动选择。计算样本数量:
num_examples = (len(corpus_indices) - 1) // num_steps
: 由于每个样本都需要num_steps
个字符,且最后一个字符后面没有下一个字符,所以可用的样本数量是总字符数减去1后除以num_steps
的整数部分。计算轮次大小:
epoch_size = num_examples // batch_size
: 一个轮次(epoch)中可以包含的批次数量。打乱样本索引:
random.shuffle(example_indices)
来打乱样本索引,这样每次迭代时都可以从随机位置抽取样本。定义内部函数 _data
:
num_steps
的序列。选择设备:
device
为None,则根据是否有可用的 GPU 自动选择设备。迭代生成批次:
batch_size
个样本的索引,并使用_data
函数从corpus_indices
中提取相应的序列。X
是输入序列,其中每个序列都是长度为num_steps
的字符索引列表。Y
是目标序列,其中每个序列是X
中对应序列的下一个字符的索引列表。torch.tensor
将这些索引列表转换为张量,并指定数据类型为torch.float32
和所选的设备。yield
关键字将张量对(X
, Y
)作为生成器的一部分返回。def data_iter_random(corpus_indices, batch_size, num_steps, device=None): # 减1是因为输出的索引x是相应输入的索引y加1 num_examples = (len(corpus_indices) - 1) // num_steps epoch_size = num_examples // batch_size example_indices = list(range(num_examples)) random.shuffle(example_indices) # 返回从pos开始的长为num_steps的序列 def _data(pos): return corpus_indices[pos: pos + num_steps] if device is None: device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') for i in range(epoch_size): # 每次读取batch_size个随机样本 i = i * batch_size batch_indices = example_indices[i: i + batch_size] X = [_data(j * num_steps) for j in batch_indices] Y = [_data(j * num_steps + 1) for j in batch_indices] yield torch.tensor(X, dtype=torch.float32, device=device), torch.tensor(Y, dtype=torch.float32, device=device)
my_seq = list(range(30))
for X, Y in data_iter_random(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
输出:
corpus_indices
: 文本序列的索引表示。batch_size
: 每个批次中样本的数量。num_steps
: 每个样本中字符的数量(即序列长度)。device
: 用于存储张量的设备(如 CPU 或 GPU)。默认为 None,函数内部会根据是否有可用的 GPU 来自动选择。选择设备:
如果 device
为 None,则根据是否有可用的 GPU 自动选择设备。
转换数据类型和设备:
将 corpus_indices
转换为 PyTorch 张量,并指定数据类型为 torch.float32
和所选的设备。
确定批次长度:
由于我们想要从整个 corpus_indices
中创建连续的批次,我们首先确保 corpus_indices
的长度是 batch_size
的整数倍。如果不是,我们只取前 batch_size * batch_len
个索引,其中 batch_len
是 corpus_indices
的长度除以 batch_size
的整数部分。
重新塑造数据:
将索引张量重新塑造为二维的 (batch_size, batch_len)
形状,其中 batch_len
是每个批次中的时间步数(不是 num_steps
)。
确定轮次大小:
因为每个批次中的序列是连续的,并且我们希望每个样本的长度为 num_steps
,所以我们从每个批次中可以得到的最大样本数是 (batch_len - 1) // num_steps
。
迭代生成批次:
对于每个轮次(epoch)中的每个步骤,我们从重新塑造的二维张量 indices
中选择连续的 num_steps
长的序列作为输入 X
,并将这些序列向右移动一个位置来得到目标 Y
(即每个输入序列的下一个字符)。
返回数据:
使用 yield
关键字将张量对 (X, Y)
作为生成器的一部分返回。
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
corpus_indices = torch.tensor(corpus_indices, dtype=torch.float32, device=device)
data_len = len(corpus_indices)
batch_len = data_len // batch_size
indices = corpus_indices[0: batch_size*batch_len].view(batch_size, batch_len)
epoch_size = (batch_len - 1) // num_steps
for i in range(epoch_size):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
同样的设置下,打印相邻采样每次读取的小批量样本的输入X
和标签Y
。相邻的两个随机小批量在原始序列上的位置相毗邻。
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
输出:
X: tensor([[ 0., 1., 2., 3., 4., 5.],
[15., 16., 17., 18., 19., 20.]])
Y: tensor([[ 1., 2., 3., 4., 5., 6.],
[16., 17., 18., 19., 20., 21.]])
X: tensor([[ 6., 7., 8., 9., 10., 11.],
[21., 22., 23., 24., 25., 26.]])
Y: tensor([[ 7., 8., 9., 10., 11., 12.],
[22., 23., 24., 25., 26., 27.]])
import time
import math
import numpy as np
import torch
from torch import nn, optim
import torch.nn.functional as F
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()
vocab_size
),每个字符已经同一个从0到
N
−
1
N-1
N−1的连续整数值索引一一对应。如果一个字符的索引是整数
i
i
i, 那么我们创建一个全0的长为
N
N
N的向量,并将其位置为
i
i
i的元素设成1。该向量就是对原字符的one-hot向量。下面分别展示了索引为0和2的one-hot向量,向量长度等于词典大小。def one_hot(x, n_class, dtype=torch.float32):
# X shape: (batch), output shape: (batch, n_class)
x = x.long()
res = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device)
res.scatter_(1, x.view(-1, 1), 1)
return res
x = torch.tensor([0, 2])
one_hot(x, vocab_size)
def to_onehot(X, n_class):
# X shape: (batch, seq_len), output: seq_len elements of (batch, 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
:输入层的大小,等于词汇表大小(vocab_size
)。num_hiddens
:隐藏层的大小,这里设置为 256。num_outputs
:输出层的大小,也等于词汇表大小(vocab_size
),因为对于每个时间步,我们都要预测一个词(通常是使用 one-hot 编码或者词嵌入)。device
:用于指定参数应该存储在哪个设备上(CPU 或 GPU)。打印设备:
print('will use', device)
打印将要使用的设备。get_params
函数:
nn.ParameterList
。W_xh
:从输入层到隐藏层的权重矩阵,大小为 (num_inputs, num_hiddens)
。W_hh
:从上一时间步的隐藏状态到当前时间步的隐藏状态的权重矩阵,大小为 (num_hiddens, num_hiddens)
。b_h
:隐藏层的偏置向量,大小为 (num_hiddens,)
。W_hq
:从隐藏层到输出层的权重矩阵,大小为 (num_hiddens, num_outputs)
。b_q
:输出层的偏置向量,大小为 (num_outputs,)
。torch.nn.Parameter
包裹以确保它们可以作为模型的参数被优化。num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size print('will use', device) def get_params(): def _one(shape): ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32) return torch.nn.Parameter(ts, requires_grad=True) # 隐藏层参数 W_xh = _one((num_inputs, num_hiddens)) W_hh = _one((num_hiddens, num_hiddens)) b_h = torch.nn.Parameter(torch.zeros(num_hiddens, device=device, requires_grad=True)) # 输出层参数 W_hq = _one((num_hiddens, num_outputs)) b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device, requires_grad=True)) return nn.ParameterList([W_xh, W_hh, b_h, W_hq, b_q])
init_rnn_state
函数来返回初始化的隐藏状态。它返回由一个形状为(批量大小, 隐藏单元个数)的值为0的NDArray
组成的元组。使用元组是为了更便于处理隐藏状态含有多个NDArray
的情况。def init_rnn_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device), )
下面的rnn
函数定义了在一个时间步里如何计算隐藏状态和输出。这里的激活函数使用了tanh函数。
def rnn(inputs, state, params):
# inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
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,)
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(outputs), outputs[0].shape, state_new[0].shape)
输出:
5 torch.Size([2, 1027]) torch.Size([2, 256])
函数参数:
prefix
: 一个字符串,作为预测的起始字符或字符序列。num_chars
: 要预测的字符数量(不包括 prefix
中的字符)。rnn
: RNN 模型,它应该是一个函数,接受输入、隐藏状态和参数,并返回输出和新的隐藏状态。params
: RNN 模型的参数。init_rnn_state
: 一个函数,用于初始化 RNN 的隐藏状态。num_hiddens
: 隐藏层的大小。vocab_size
: 词汇表的大小。device
: 设备(CPU 或 GPU),用于指定在哪里执行计算。idx_to_char
: 一个字典,将索引映射到字符。char_to_idx
: 一个字典,将字符映射到索引。初始化:
init_rnn_state
函数初始化 RNN 的隐藏状态。output
,用于存储预测的字符索引。首先,将 prefix
的第一个字符的索引添加到 output
中。预测循环:
num_chars + len(prefix) - 1
次迭代(包括 prefix
中的字符和要预测的字符),执行以下操作:
prefix
之内,那么将 prefix
中的下一个字符的索引添加到 output
中。否则,从 RNN 的输出中选择概率最高的字符索引,并添加到 output
中。返回结果:
idx_to_char
字典将 output
中的索引转换为字符,并将这些字符连接成一个字符串返回。def predict_rnn(prefix, num_chars, rnn, params, init_rnn_state,
num_hiddens, vocab_size, device, idx_to_char, char_to_idx):
state = init_rnn_state(1, num_hiddens, device)
output = [char_to_idx[prefix[0]]]
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(int(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)
输出:
'分开西圈绪升王凝瓜必客映'
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()
norm = norm.sqrt().item()
if norm > theta:
for param in params:
param.grad.data *= (theta / norm)
显然,任何一个有效模型的困惑度必须小于类别个数。在本例中,困惑度必须小于词典大小vocab_size
。
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): if is_random_iter: data_iter_fn = d2l.data_iter_random else: data_iter_fn = d2l.data_iter_consecutive params = 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: if is_random_iter: # 如使用随机采样,在每个小批量更新前初始化隐藏状态 state = init_rnn_state(batch_size, num_hiddens, device) else: # 否则需要使用detach函数从计算图分离隐藏状态, 这是为了 # 使模型参数的梯度计算只依赖一次迭代读取的小批量序列(防止梯度计算开销太大) for s in state: s.detach_() 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 = torch.cat(outputs, dim=0) # Y的形状是(batch_size, num_steps),转置后再变成长度为 # batch * num_steps 的向量,这样跟输出的行一一对应 y = torch.transpose(Y, 0, 1).contiguous().view(-1) # 使用交叉熵损失计算平均分类误差 l = loss(outputs, y.long()) # 梯度清0 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] 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(prefix, pred_len, rnn, params, init_rnn_state, num_hiddens, vocab_size, device, idx_to_char, char_to_idx))
import time
import math
import numpy as np
import torch
from torch import nn, optim
import torch.nn.functional as F
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()
PyTorch中的nn
模块提供了循环神经网络的实现。下面构造一个含单隐藏层、隐藏单元个数为256的循环神经网络层rnn_layer
。
num_hiddens = 256
# rnn_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens) # 已测试
rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps = 35
batch_size = 2
state = None
X = torch.rand(num_steps, batch_size, vocab_size)
Y, state_new = rnn_layer(X, state)
print(Y.shape, len(state_new), state_new[0].shape)
输出:
torch.Size([35, 2, 256]) 1 torch.Size([2, 256])
如果
rnn_layer
是nn.LSTM
实例,那么上面的输出是什么?
class RNNModel(nn.Module): def __init__(self, rnn_layer, vocab_size): super(RNNModel, self).__init__() self.rnn = rnn_layer self.hidden_size = rnn_layer.hidden_size * (2 if rnn_layer.bidirectional else 1) self.vocab_size = vocab_size self.dense = nn.Linear(self.hidden_size, vocab_size) self.state = None def forward(self, inputs, state): # inputs: (batch, seq_len) # 获取one-hot向量表示 X = d2l.to_onehot(inputs, self.vocab_size) # X是个list Y, self.state = self.rnn(torch.stack(X), state) # 全连接层会首先将Y的形状变成(num_steps * batch_size, num_hiddens),它的输出 # 形状为(num_steps * batch_size, vocab_size) output = self.dense(Y.view(-1, Y.shape[-1])) return output, self.state
def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char, char_to_idx): state = None output = [char_to_idx[prefix[0]]] # output会记录prefix加上输出 for t in range(num_chars + len(prefix) - 1): X = torch.tensor([output[-1]], device=device).view(1, 1) if state is not None: if isinstance(state, tuple): # LSTM, state:(h, c) state = (state[0].to(device), state[1].to(device)) else: state = state.to(device) (Y, state) = model(X, state) if t < len(prefix) - 1: output.append(char_to_idx[prefix[t + 1]]) else: output.append(int(Y.argmax(dim=1).item())) return ''.join([idx_to_char[i] for i in output])
使用权重为随机值的模型来预测一次。
函数参数:
prefix
: 预测的起始字符序列。num_chars
: 要预测的字符数量。model
: 已训练的 RNN 模型。vocab_size
: 词汇表的大小(字符的数量)。device
: 用于存储张量的设备(如 CPU 或 GPU)。idx_to_char
: 将索引映射到字符的字典。char_to_idx
: 将字符映射到索引的字典。初始化:
state
: 用于存储 RNN 的内部状态(如 LSTM 的隐藏状态和细胞状态)。output
: 初始化为 prefix
的第一个字符的索引。预测循环:
num_chars + len(prefix) - 1
次。这是因为我们要先遍历 prefix
的所有字符(除了最后一个),然后再预测 num_chars
个字符。X
是当前要输入到 RNN 的字符的索引(作为张量)。state
不是 None
,则将其移动到指定的 device
上。对于 LSTM,state
是一个元组,包含隐藏状态和细胞状态;对于其他 RNN 变体,它可能只是一个隐藏状态。X
和 state
进行预测,得到新的输出 Y
和新的 state
。prefix
的一部分(即 t < len(prefix) - 1
),则将 prefix
的下一个字符的索引添加到 output
中。否则,从 Y
中选择概率最高的字符的索引,并将其添加到 output
中。返回结果:
idx_to_char
字典将 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): loss = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr) model.to(device) state = None 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) # 相邻采样 for X, Y in data_iter: if state is not None: # 使用detach函数从计算图分离隐藏状态, 这是为了 # 使模型参数的梯度计算只依赖一次迭代读取的小批量序列(防止梯度计算开销太大) if isinstance (state, tuple): # LSTM, state:(h, c) state = (state[0].detach(), state[1].detach()) else: state = state.detach() (output, state) = model(X, state) # output: 形状为(num_steps * batch_size, vocab_size) # Y的形状是(batch_size, num_steps),转置后再变成长度为 # batch * num_steps 的向量,这样跟输出的行一一对应 y = torch.transpose(Y, 0, 1).contiguous().view(-1) l = loss(output, y.long()) optimizer.zero_grad() l.backward() # 梯度裁剪 d2l.grad_clipping(model.parameters(), clipping_theta, device) optimizer.step() l_sum += l.item() * y.shape[0] n += y.shape[0] try: perplexity = math.exp(l_sum / n) except OverflowError: perplexity = float('inf') if (epoch + 1) % pred_period == 0: print('epoch %d, perplexity %f, time %.2f sec' % ( epoch + 1, perplexity, 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)
nn
模块提供了循环神经网络层的实现。nn.RNN
实例在前向计算后会分别返回输出和隐藏状态。该前向计算并不涉及输出层计算。https://www.kaggle.com/competitions/california-house-prices
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。