目录
1.背景论述
CNN(卷积神经网络)在图像处理有较好的优势,但是对于文本的识别,序列的预测,CNN就显得有点乏力了,而RNN能够很好解决这些问题,同时在自然语言处理,文本生成,文本分类,问答,机器翻译,Tag生成等方面应用广泛,本文重点简述RNN的原理,改进,代码实现。
2.RNN原理
分析上图每一个输出O,都是与一个隐变层和一个输入层有关的,而隐变层中的每个神经元又与上一个神经元存在关联,这样做使得每一个输出都与之前的输入产生了关联,利于预测下一个输出的值,同时这样大大增加了计算量,容易产生梯度爆炸现象。
循环神经网络每次循环都需要更新隐变状态和输出:
根据上式,不断将序列中的数据点传入网络,不管这个序列多长,不断循环下去就可以了。这里肯能你会有一个疑问,如果序列很长,不断循环下去是不是参数太多了,而且序列的长度如果不定,那么模型的参数一会多,一会少,这感觉是不是有点不靠谱。
事实上,这里采用了参数共享,也就是说上面隐变层虽然有五个神经元,但是实际上他们都是同一个神经元,从下图可以清楚的看出
左边就是循环神经网络实际的网络流,右边是将其展开的结果,可以看到网络中的具体循环网络,这也是循环网络名字的由来,同时根据循环神经网络的结构,可以看出其对于序列类型的数据具有天然的优势,因为网路本身就是一个序列结构,这也是所有循环神经网络最本质的结构。
3.RNN的改进
上述提出的这种结构具有记忆的特点,但是随后发现网络的记忆能力并没有我们想象的那么有效,因为伴随着记忆,就有遗忘的出现,比如我们总会更加清楚地记得最近发生的事情而遗忘很久之前的事情。正因为存在这些问题,导致循环神经网络在实际应用上一直没有办法取得很好的效果,为了解决这些问题,循环网络针对具体的问题,挑选一些特定的参数来解决此问题,但是这并不具有普适性,因为循环神经网络没有办法自己解决如何该挑选哪些参数。
早期循环神经网络的发展存在着长时依赖问题,后来依据循环神经网络的基本结构,有人提出了一些特定的变式,这些变式解决了长时依赖的问题。并在之后循环网络的使用中成为主流,目前循环神经网络最为流行的两种结构,LSTM和GRU
3.1 GRU模型
GRU也成为门控循环单元,相对于RNN的主要差别在于,关注的是一个序列,并且不是每一个观察值都同等的重要。实际就是给了记忆单元和输入单元一个参数权重,通过不断的训练,找出一个适合实际问题的参数。
如上图,如果我们只想要关注小狗的特征,我们可以增加相应小狗属性的权重。
GRU主要两个门控制:
更新门,作用为能关注的机制
重置门,作用为能遗忘的机制
GRU计算步骤:
3.2 LSTM模型
LSTM由三个门来控制,分别是输入门,遗忘门和输出门。输入门和输出门限制着输入和输出的大小,而遗忘门控制着记忆的保留读,对于一个任务,遗忘门能够自己学习到该保留多少以前的记忆,不需要人为进行干扰,所以具备长时记忆的功能。
遗忘门:将值朝零减少
输入门:决定是不是忽略掉输入的数据
输出门:决定是否使用隐状态
这是三个序列的LSTM结构,每个A中的参数都是相同的。其内部详细结构为
计算步骤为
4.RNN代码实现
这里举一个将RNN用到时间序列问题上的例子,因为对于时序数据,后米的数据会用到前面的数据,LSTM的记忆特性非常适合这种场景。相关数据已经绑定好资源了,或在评论区中自行下载
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
from torch import nn
from torch.autograd import Variable
data_csv = pd.read_csv('./data.csv', usecols=[1])
# plt.plot(data_csv)
# plt.show()
#数据预处理
data_csv = data_csv.dropna() #݄ 去掉na
dataset = data_csv.values
dataset = dataset.astype('float32')
max_value = np.max(dataset)
min_value = np.min(dataset)
scalar = max_value - min_value
dataset = list(map(lambda x: x / scalar, dataset))
def create_dataset(dataset, look_back=2):
dataX, dataY = [], []
for i in range(len(dataset) - look_back):
a = dataset[i:(i + look_back)]
dataX.append(a)
dataY.append(dataset[i + look_back])
return np.array(dataX), np.array(dataY)
#创建好输入输出
data_X, data_Y = create_dataset(dataset)
#划分好训练集集和测试集,百分之其实作为训练集
train_size = int(len(data_X) * 0.7)
test_size = len(data_X) - train_size
train_X = data_X[:train_size]
train_Y = data_Y[:train_size]
test_X = data_X[train_size:]
test_Y = data_Y[train_size:]
#改变一下数据维度
train_X = train_X.reshape(-1, 1, 2)
train_Y = train_Y.reshape(-1, 1, 1)
test_X = test_X.reshape(-1, 1, 2)
train_x = torch.from_numpy(train_X)
train_y = torch.from_numpy(train_Y)
test_x = torch.from_numpy(test_X)
#定义模型
class lstm_reg(nn.Module):
def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
super(lstm_reg, self).__init__()
self.rnn = nn.LSTM(input_size, hidden_size, num_layers) # rnn
self.reg = nn.Linear(hidden_size, output_size) # ୭ࢧ
def forward(self, x):
x, _ = self.rnn(x)
s, b, h = x.shape
x = x.view(s*b, h)
x = self.reg(x)
x = x.view(s, b, -1)
return x
net = lstm_reg(2, 4)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)
# 开始训练
for e in range(1000):
var_x = Variable(train_x)
var_y = Variable(train_y)
#向前传播
out = net(var_x)
loss = criterion(out, var_y)
#向后传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 100 == 0:
print('Epoch: {}, Loss: {:.5f}'.format(e + 1, loss.data[0]))
net = net.eval() # 转换成训练模式
data_X = data_X.reshape(-1, 1, 2)
data_X = torch.from_numpy(data_X)
var_data = Variable(data_X)
pred_test = net(var_data) #测试集的预测结果
#改变输出的格式
pred_test = pred_test.view(-1).data.numpy()
#画出实际结果和预测结果
plt.plot(pred_test, 'r', label='prediction')
plt.plot(dataset, 'b', label='real')
plt.legend(loc='best')
plt.show()
运行结果如下图
预测数据与真实数据的比较
这里蓝色的是真实的数据集,红色的为预测结果,我们能够看到,使用lstm能够得到比较相近的结果,预测的趋势也与真实的数据集相同,这个例子也说明了RNN对于时间序列有着非常好的性能,