系列文章目录
循环神经网络的从零开始实现
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
独热编码
回想一下,在train_iter
中,每个词元都表示为一个数字索引,将这些索引直接输入神经网络可能会使学习变得困难。
我们通常将每个词元表示为更具表现力的特征向量。
最简单的表示称为独热编码(one-hot encoding)。
简言之,将每个索引映射为相互不同的单位向量:假设词表中不同词元的数目为 N N N(即len(vocab)
),词元索引的范围为 0 0 0到 N − 1 N-1 N−1。
如果词元的索引是整数 i i i,那么我们将创建一个长度为 N N N的全 0 0 0向量,并将第 i i i处的元素设置为 1 1 1。此向量是原始词元的一个独热向量。索引为 0 0 0和 2 2 2的独热向量如下所示:
F.one_hot(torch.tensor([0, 2]), len(vocab))
这里的len(vocab)的值为28是因为相比26个英文字母多了('<unk>', 0), (' ', 1)
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]])
我们每次采样的小批量数据形状是二维张量:(批量大小,时间步数)。
one_hot
函数将这样一个小批量数据转换成三维张量,张量的最后一个维度等于词表大小(len(vocab)
)。
我们经常转换输入的维度,以便获得形状为(时间步数,批量大小,词表大小)的输出。
这将使我们能够更方便地通过最外层的维度,一步一步地更新小批量数据的隐状态。
X = torch.arange(10).reshape((2, 5))
F.one_hot(X.T, 28).shape
torch.Size([5, 2, 28]) #第一个维度变成了时间 ,然后是批量,然后是样本的特征长度
为什么上面要进行转置?转置之后我们在取数据时,更加简单。
转置为了更方便按照时间顺序使用 x t x_t xt,也就是按照时间步来使用。
初始化模型参数
接下来,我们[初始化循环神经网络模型的模型参数]。
隐藏单元数num_hiddens
是一个可调的超参数。
当训练语言模型时,输入和输出来自相同的词表。
因此,它们具有相同的维度,即词表的大小。
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size
#输入本来是词或者字母,但是现在变成了vocab然后放入比如说MLP,(之前我们对一张图片输入到MLP也就是把图片变成一维的向量,这个向量的长度也就是图片像素点的个数),那输入的size就是vocab_size。
#输出,因为我们这是个分类问题,所以输出就是对vacab里每个字母的概率值,所以他的大小也是vocab_size
def normal(shape):
return torch.randn(size=shape, device=device) * 0.01
# 隐藏层参数 注意这里W_xh为什么xh是而不是hx,和之前的博客中的参数下标不一致,这是因为在代码中进行矩阵运算时候,把x放在了W的前面进行计算。
W_xh = normal((num_inputs, num_hiddens))
W_hh = normal((num_hiddens, num_hiddens))
b_h = torch.zeros(num_hiddens, device=device)
# 输出层参数
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
# 附加梯度
params = [W_xh, W_hh, b_h, W_hq, b_q]
for param in params