循环神经网络
以循环神经网络实现语言模型为例。
下面分析构造。假设是时间步的小批量输入,是该时间步的隐藏变量,则
对于每一个字符和每一个隐藏变量都用一个向量来表示,这里的和分别表示两个向量的长度。,由此知,根据加法的广播机制,三项相加结果为的矩阵。是非线性激活函数。
在时间步,输出层的输出为:
one-hot向量
我们需要将字符表示成向量,这里采用one-hot向量。假设词典大小是,每次字符对应一个从0到的唯一的索引,则该字符的向量是一个长度为 的向量,若字符的索引是 $i i $个位置为 1 ,其他位置为 0 。下面分别展示了索引为0和2的one-hot向量,向量长度等于词典大小。
def one_hot(x, n_class, dtype=torch.float32):
# x是一个一维的向量,每个元素都是一个字符的索引
# n_class是字典的大小,vocab_size
# dtype为返回向量的数值类型,size为(n,n_class)
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
return result
- scatter_()函数
我们每次采样的小批量的形状是(批量大小, 时间步数)。下面的函数将这样的小批量变换成数个形状为(批量大小, 词典大小)的矩阵,矩阵个数等于时间步数。也就是说,时间步的输入为 ,其中 为批量大小, 为词向量大小,即one-hot向量长度(词典大小)。
def to_onehot(X, n_class):
# X: 一个小批量
# 输出为(批量大小, 词典大小)
return [one_hot(X[:, i], n_class) for i in range(X.shape[1])]
定义模型
函数rnn用循环的方式依次完成循环神经网络每个时间步的计算。
def rnn(inputs, state, params):
# inputs是长度为num_steps的列表,每个元素是(batch_size, vocab_size)的矩阵
# state提供状态的初始值,是一个元组,在rnn中就是隐藏状态
# inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state # rnn中只有一个状态要维护,就是隐藏状态
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,)
# 返回h是因为后面要用相邻采样进行训练,对于相邻采样而言,当前这个batch的最后状态就作为下一个状态的初始值
- X.to(device)
裁剪梯度
循环神经网络中较容易出现梯度衰减或梯度爆炸,这是因为梯度的传播方式是通过时间反向传播,梯度是幂的形式,指数就是num_steps,随着时间步数增加,就容易出现这些问题。裁剪梯度(clip gradient)是一种应对梯度爆炸的方法。假设我们把所有模型参数的梯度拼接成一个向量 g ,并设裁剪的阈值是 θ 。裁剪后的梯度
的范数不超过。
代码展示:
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)
定义预测函数
以下函数基于前缀prefix(含有数个字符的字符串)来预测接下来的num_chars个字符。
代码展示:
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]]] # output记录prefix加上预测的num_chars个字符
for t in range(num_chars + len(prefix) - 1):
# 将上一时间步的输出作为当前时间步的输入
# 这里的batch_size为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()) # 这里用Y[0]是因为Y输出的是一个列表,取出Y中第一个元素
return ''.join([idx_to_char[i] for i in output])
torch.flatten()函数(转自GhostintheCode,这个博主介绍的很详细,可进去看看例子):
#展平一个连续范围的维度,输出类型为Tensor
torch.flatten(input, start_dim=0, end_dim=-1) → Tensor
#Parameters:input (Tensor) – 输入为Tensor
#start_dim (int) – 展平的开始维度
#end_dim (int) – 展平的最后维度
#一个3x2x2的三维张量
>>> t = torch.tensor([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]],
[[9, 10],
[11, 12]]])
#当开始维度为0,最后维度为-1,展开为一维
>>> torch.flatten(t)
tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
#当开始维度为0,最后维度为-1,展开为3x4,也就是说第一维度不变,后面的压缩
>>> torch.flatten(t, start_dim=1)
tensor([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
————————————————
版权声明:本文为CSDN博主「GhostintheCode」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/GhostintheCode/article/details/102530451
循环神经网络的简洁实现
我们使用Pytorch中的nn.RNN来构造循环神经网络。在本节中,我们主要关注nn.RNN的以下几个构造函数参数:
- input_size - The number of expected features in the input x (整型,int)
- hidden_size – The number of features in the hidden state h(整型,int)
- 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)。
一个完整的基于循环神经网络的语言模型
class RNNModel(nn.Module):
def __init__(self, rnn_layer, vocab_size):
super(RNNModel, self).__init__()
self.rnn = rnn_layer
# 这里取1
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)
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
来源:CSDN
作者:阿光light
链接:https://blog.csdn.net/weixin_42098609/article/details/104300867