在RNN(循环神经网络)当中主要的是有RNN Cell构成的,RNN Cell是同一个Linear层,是各个阶段共享的;RNN Cell主要将输入的Xt和ht-1经过线性变化后,然后再融合,并通过激活函数得到ht,从而将ht作为下一个RNN Cell的输入,然后一直到最后输出hidden结果。
使用RNN Cell训练ohlol——>hello的模型:
(1)首先应该注意,RNN Cell的输入必须是由数字构成的向量
ps:
一般在做自然语言处理的时候,比如遇到字符级别的数据,需要先根据所得的字符,构造一个词典,这里出现哪些字符或词,然后给每一个字符或者词分配一个索引(索引可以按照字典的顺序,从0依次开始;也可以随机分配)
(2)构造完词典后,将所得字符串的每一个字符都分配完索引,然后将该结果转换成向量;即除了索引的地方为1之外,其余词的地方都为0,构造一个多维向量。(构造的词典有几个词,这里的向量就有几维)
(3)然后将所得结果,经过Softmax之后预测出每一个词所得到的概率,并于最终想要的结果之间计算损失。这里采用交叉熵损失。
Pytorch实现:
# -*- coding: utf-8 -*-
# @Time : 2022/2/2 14:27
# @Author : CH339
# @FileName: Test2_2_1.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/weixin_56068397/article/
'''
RNNCell:ohlol——>hello
'''
import torch
# 参数准备
input_size = 4
hidden_size = 4
batch_size = 1
# 准备数据
# 提供词典,按照字典顺序排序
data = ['e','h','l','o']
# ohlol输入数据的下标
input_data = [3,1,2,3,2]
# 输出的下标:hello
out_data = [1,0,2,2,3]
# 定义独热编码
one_hot = [[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]]
# 将输入的数据转化成独热编码的形式(取出下标)
x_one_hot = [one_hot[x] for x in input_data]
inputs = torch.Tensor(x_one_hot).view(-1,batch_size,input_size)
labels = torch.LongTensor(out_data).view(-1,1)
# 定义模型类
class Model(torch.nn.Module):
def __init__(self,input_size,hidden_size,batch_size):
super(Model, self).__init__()
self.batch_size = batch_size
self.input_size = input_size
self.hidden_size = hidden_size
self.rnncell = torch.nn.RNNCell(input_size=self.input_size,hidden_size=self.hidden_size)
def forward(self,input,hidden):
hidden = self.rnncell(input,hidden)
return hidden
# 初始化h0
def init_hidden(self):
return torch.zeros(self.batch_size,self.hidden_size)
net = Model(input_size,hidden_size,batch_size)
# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(net.parameters(),lr=0.1)
for epoch in range(100):
loss = 0
# 梯度清零
optimizer.zero_grad()
# 定义h0
hidden = net.init_hidden()
print("预测的结果:",end=' ')
for input,label in zip(inputs,labels):
# RNN进行训练
hidden = net(input,hidden)
# 这里输出时有多个结果,所以这里是张量进行计算
loss += criterion(hidden,label)
# 获得概率最大的索引
_,idx = hidden.max(dim=1)
print(data[idx.item()],end=' ')
# 反向传播
loss.backward()
# 权重更新
optimizer.step()
print('Epoch[%d/15] loss=%.4f'%(epoch+1,loss.item()))
经过100个Epoch,我们可以看到,到所有样本迭代到10次时,已经能实现hello字符的转化;也可以通过增加迭代的次数使得loss降低。
注意:
本次实现采用的是将所得字符转化的数字向量转换成one-hot编码;但是one-hot编码却有缺点:(1)当数据较多时向量维度较大(2)向量中多为0,向量较为稀疏(3)硬编码;
因此我们可以通过增加Embedding层(嵌入层),设法将向量改变成(1)低维度(2)稠密(3)可以从数据当中学习到。
使用Embedding代码实现:
# -*- coding: utf-8 -*-
# @Time : 2022/2/2 16:18
# @Author : CH339
# @FileName: Test2_2_2.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/weixin_56068397/article/
'''
使用Embedding层实现RNN
'''
import torch
# 参数准备
num_class = 4
input_size = 4
hidden_size = 8
embedding_size = 10
# RNN层数
num_layers = 2
batch_size = 1
seq_len = 5
# 准备数据
# 提供词典,按照字典顺序排序
data = ['e','h','l','o']
# ohlol输入数据的下标
input_data = [[3,1,2,3,2]]
# 输出的下标:hello
out_data = [1,0,2,2,3]
inputs = torch.LongTensor(input_data)
labels = torch.LongTensor(out_data)
# 定义模型类
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
# 嵌入层
self.emb = torch.nn.Embedding(input_size,embedding_size)
# 循环层
self.rnn = torch.nn.RNN(input_size=embedding_size,hidden_size=hidden_size,num_layers=num_layers,batch_first=True)
# 全连接层
self.fc = torch.nn.Linear(hidden_size,num_class)
def forward(self,x):
hidden = torch.zeros(num_layers,x.size(0),hidden_size)
x = self.emb(x)
x,_ = self.rnn(x,hidden)
x = self.fc(x)
return x.view(-1,num_class)
net = Model()
# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(net.parameters(),lr=0.05)
for epoch in range(100):
# 梯度清零
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs,labels)
# 反向传播
loss.backward()
# 权重更新
optimizer.step()
_,idx = outputs.max(dim=1)
idx = idx.data.numpy()
print("预测结果:",''.join([data[x] for x in idx]),end=' ')
print('Epoch[%d/15] loss=%.3f'%(epoch+1,loss.item()))
从训练结果就可以看出,通过将one-hot编码改成使用embedding层后,不仅在少量Epoch就可以得到最终结果,同时也可以降低预测时的loss。
如上图所示,经过5轮就可以得出结果。
并且经过60轮后,loss降低至0.001。