文章目录
1.预备知识:深度神经网络(DNN)
DNN输入固定数量(如下图中为3个)的数据,前向传播输出output,与真实值求loss后反向传播更新参数,进行模型训练。如下图所示,我们每次输入固定数量的X1、X2、X3数据,训练得到正确的output输出。
2.RNN出现的意义与基本结构
假定我们正在做一个温度预测的时间序列任务,模型根据n-1天的历史温度数据,预测第n天的温度。
任务如下:
- 第一天温度为:26,此时通过该数据预测第二天温度
- 第二天温度为:27,此时用第一天和第二天的温度数据预测第三天温度,即通过数据序列[26,27]预测第三天温度
- 第三天温度为:29,参考上一条,此时通过数据序列[26,27,29]预测第四天温度。
- 第四天温度为:30,通过数据序列[26,27,29,30]预测第五天温度。
- 第五天温度为:32,通过数据序列[26,27,29,30,32]预测第六天温度。
…
我们只取前5天的数据作为训练样本进行解析,即我们拥有的训练数据为[26,27,29,30,32]。
传统的深度神经网络(DNN),输入尺寸大小固定。我们统一使用2天的温度数据预测第3天的温度,得到的训练数据inputs和labels如下(暂时不考虑测试集)。
input | label |
---|---|
[26,27] | 29 |
[27,29] | 30 |
[29,30] | 32 |
按照传统的DNN,我们已经完成训练数据的制作和神经网络大致模型的构建。但是这里要提出一个疑问,某一天的温度是否和两天前的环境温度不存在联系。
我们都知道温度是渐变的,每一天的温度都与该天温度的前m天温度息息相关(m为非固定值,需要我们去学习,所以更加不可以使用DNN这种输入数据量固定的模型)。
按照直观感受:
- 相较于使用数据[27,29]去拟合数据30,用前三天数据[26,27,29]去拟合第四天的温度(数据30)效果会更好。
- 相较于使用数据[29,30]去拟合数据32,用前四天数据[26,27,29,30]去拟合第五天的温度(数据32)效果会更好。
此时输入数据量不再固定,而是递增的,DNN不适应这种情况。我们提出一种新的模型,模型每次都只输入一个数据,数据量为n时输入n次,输入过程中进行信息积累,这样我们就解决了输入量不固定且数据间有序列关系的问题。
rnn网络结构:
一般单层神经网络架构:
rnn单层神经网络结构:
时间步展开的rnn网络结构:
3.根据输入和输出数量的网络结构分类
3.1 N vs N(输入和输出序列等长)
如下图所示,这个结构是rnn最基础的,每输入一个数据,输出一个对应的output。因为输入序列与输出序列等长,所以用途较为狭小,可用作生成等长度的诗句。
以“落木千山天远大,澄江一道月分明”诗句为例,进行解析。
首先进行分词,这里我们可以使用结巴分词。
import jieba
verse1=jieba.lcut("落木千山天远大", cut_all=False)
verse2=jieba.lcut("澄江一道月分明", cut_all=False)
分割后的内容为:
verse1=['落木', '千山', '天', '远大']
verse2=['澄江', '一道', '月', '分明']
编码后:
{'落木': 0, '千山': 1, '天': 2, '远大': 3}
{'澄江': 0, '一道': 1, '月': 2, '分明': 3}
我们使用pytorch的Embedding模块对verse1进行编码(verse2作为label,无需编码):
nn.Embedding(voc_size, embedding_size, sparse=True)
Embedding后生成的verse1的词向量为(pytorch的Embedding词向量是随机生成的,如果想要产生更有内联性的词向量,需要自己训练参数,再将其导入):
'落木':[-0.0482, -0.9568, 0.7512]
'一道':[ 0.0399, 0.1087, -2.0304]
'月':[0.1200, 0.2021, 1.7677]
'分明':[-0.4733, -0.7433, 2.7065]
此时我们已经得到输入数据和labels:
input | labels |
---|---|
[-0.0482, -0.9568, 0.7512] | 0(‘落木’对应’澄江’,'澄江’编码为0) |
[ 0.0399, 0.1087, -2.0304] | 1(‘千山’对应’一道’,'一道’编码为0) |
[ 0.0399, 0.1087, -2.0304] | 2(’ 天 ‘对应’月’ , '月’编码为0) |
[-0.4733, -0.7433, 2.7065] | 3(‘远大’对应’分明’,'分明’编码为0) |
构建神经网络,此处关键点在于每次输入hidden后,我们输出新的一个hidden用于下一次的输入:
class RNN(nn.Module):
#模型初始化
def __init__(self,hidden_size,embedding_size,output_size):
super(RNN,self).__init__()
self.hidden = nn.Linear(hidden_size+embedding_size, hidden_size)
self.out=nn.Linear(hidden_size+embedding_size, output_size)
self.softmax = nn.Softmax(dim=-1)
#前向传播层
def forward(self,inputs,hidden):
middle=torch.cat((inputs,hidden),-1)#拼接输入诗词和hidden
hidden=self.hidden(middle)
output=self.softmax(self.out(middle))
return output,hidden
进行模型训练并检测结果:
#设置300个epoch训练模型
for epoch in range(300):
for j in range(voc_size):
output,hidden=rnn(inputs[j],hidden)
optimizer.zero_grad()
loss = criterion(output.unsqueeze(0), labels[j].unsqueeze(0))
loss.backward(retain_graph=True)
optimizer.step()
#效果检测
result=[]
for i in range(voc_size):
output,hidden=rnn(inputs[i],hidden)
_,idx=output.max(0)
result.append(verse2[idx])
print(result)
得出结果如下,可以看出模型预测结果正确(这里训练集和测试集统一,只是作为一个测试例子):
['澄江', '一道', '月', '分明']
完整代码如下:
import torch.nn as nn
import torch
import jieba
import torch.optim as optim#优化器
from torch.autograd import Variable
class RNN(nn.Module):
#模型初始化
def __init__(self,hidden_size,embedding_size,output_size):
super(RNN,self).__init__()
self.hidden = nn.Linear(hidden_size+embedding_size, hidden_size)
self.out=nn.Linear(hidden_size+embedding_size, output_size)
self.softmax = nn.Softmax(dim=-1)
#前向传播层
def forward(self,inputs,hidden):
middle=torch.cat((inputs,hidden),-1)#拼接输入诗词和hidden
hidden=self.hidden(middle)
output=self.softmax(self.out(middle))
return output,hidden
#产生数据单元
def make_data(voc_size,embedding_size):
embedding = nn.Embedding(voc_size, embedding_size, sparse=True)
inputs=[]
labels=[]
for i in range(voc_size):
inputs.append(embedding(torch.tensor(i)))
labels.append(i)
return inputs,Variable(torch.LongTensor(labels))
if __name__=="__main__":
verse1=jieba.lcut("落木千山天远大", cut_all=False)
verse2=jieba.lcut("澄江一道月分明", cut_all=False)
voc_size=len