一、RNN发展历史
1986年,Elman等人提出了用于处理序列数据的循环神经网络。如同卷积神经网络是专门用于处理二维数据信息(如图像)的神经网络,循环神经网络是专用于处理序列信息的神经网络。循环网络可以扩展到更长的序列,大多数循环神经网络可以处理可变长度的序列,循环神经网络的诞生解决了传统神经网络在处理序列信息方面的局限性。
因此RNN常用于需要考虑顺序或时间的问题,如自然语言处理 (NLP)、语音识别等;它们也被包含在一些流行的应用中,比如 Siri、语音搜索和 Google Translate。
二、RNN基础理论
RNN的特点相对于普通的神经网络区别在于其具有“记忆”的功能,它可以从先前的输入中获取信息,以影响当前的输入和输出;并且对序列的每个元素都执行这个相同的步骤。
2.1RNN结构
除此之外,由下图RNN的结构示意图可知, RNN的另一个显著特征是它们在每个网络层中共享相同的权重参数(如图中的输入层权重参数U,隐藏层参数W,输出层参数V),这充分反映了RNN在每个步骤执行相同任务的事实(意思就是宏观上看我们是在学习同一个任务,只不过,学习的方法是逐步推进,逐步更新权重参数罢了(所以不加以在隐藏层引入适当机制,很容易出现学了后面的忘了前面的情况,后面更新的权重参数直接把前面的权重灭了),最后RNN只留下一个学习完整个任务所得到的权重参数集合),只是使用不同的输入,这大大减少了我们需要学习的参数总数。
2.2 RNN的训练算法(也就是该网络如何进行误差传播,计算梯度,调整权重,降低误差,优化网络)
同时对于RNN,其误差传播以及权重更新利用的是随时间推移的反向传播算法 (BPTT),这与传统的反向传播略有不同,因为它特定于序列数据。BPTT的原理也是通过计算输出层与输入层之间的误差来训练自身,由此来适当地更新和拟合模型的权重参数;比较特殊的地方在于,BPTT会在每个时间步长对误差求和,因为RNN在每层共享权重参数,其更新需要每个时间步长的误差信息。
BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:
- 前向计算每个神经元的输出值;
- 反向计算每个神经元的误差项值,它是误差函数E对神经元j的加权输入的偏导数;
- 计算每个权重的梯度。
最后再用随机梯度下降算法更新权重。
图1 RNN结构示意图
如图:
- 是输入层的输入;
- 是隐藏层的输出,其中是计算第一个隐藏层所需要的,通常初始化为全零;
- 是输出层的输出
由此结构,可以获得以下的RNN前向计算过程:
通过两个公式的循环,可以得到以下推导:
很显然,可以看到,当前时刻的输出包含了历史信息,这说明循环神经网络对历史信息进行了保存,同时这也给后面RNN的缺点埋下了伏笔。
三、RNN的缺点(这里的符号与前不同,h指代隐藏层,Wh指隐藏层权重,Wx指输入层权重,J指损失函数)
3.1梯度爆炸和梯度消失
RNN的致命问题就是梯度的爆炸和消失。一个基于梯度来进行优化的网络,如果梯度出现了问题,可想而知,这个网络的学习能力和表现都会受到严重的影响,梯度决定了一个网络在学习的过程中参数改变。梯度爆炸所指的是一个网络的梯度无限大。梯度消失所指就是一个网络的梯度无限接近于0 。梯度爆炸和消失不是RNN的“专利”,在其他的前馈网络中也会出现,当网络的层数过大,或者选择了不恰当的激活函数都会导致梯度问题在前馈网络中的出现。但是在RNN中尤为的明显。
出现这样的梯度问题,对于RNN或者其他的前馈神经网络而言意味着什么呢??我们知道梯度的传播是从最后输出层开始向着输入进行传递的。假设梯度在第N层的时候消失,意味着第N层之前的网络就失去了学习的能力,因为他们的梯度已经是0了,直接就鸡了,loss函数都找不到最优最小解了,梯度都等于0了,权重参数还咋更新?即便这个网络一共有很多层,但是也只有最后的N层有学习的能力。相反的,梯度爆炸带来的问题是在第N层时的梯度无穷大,模型是永远无法收敛的,loss直接爆nan,永远收敛不了你还想着loss能收敛到最小值?
3.1 梯度爆炸与梯度消失是怎么样产生的
只有找到问题发生的原因才可能针对性的处理。那么RNN中的梯度爆炸和消失是如何产生的呢?下面将给出数学推导。
首先我们先假设一个4层的RNN(也就是四个时间步咯),也就是说误差是从第四层开始朝前传播的,既然误差从最后一层开始向前传播,那么我们可以通过链式法则,写出总损失对于RNN第1层的梯度:(J是损失函数,h是隐藏层,这里只是指代一下。
我们可以根据上面的这个公式,推导出一个更加有一般性的表达式:(i表示的RNN的层数总数i,j表示RNN中的第j层)
这个是这一层隐藏层的输入与上一层隐藏层的关系
将这两个公式带入可得:
很明显,这个时候我们可以发现整个梯度完全取决于雅可比矩阵diag()的值,严谨一点来说就是取决于雅可比矩阵的最大特征值。只要这个最大特征值大于1,哪怕是1.01,他都会随着传播层数的增多,呈指数增长,出现所谓的梯度爆炸的现象。相反若这个最大特征值小于1,就会呈指数减小,随着误差传播得越来越远,梯度无限趋近于0,从而出现梯度消失的现象。(讲人话其实就是,RNN的梯度计算过程中,由于其本身的循环体制,其梯度取决于一个由其层数以及激活函数控制数值大小的雅可比矩阵,类似于函数,a就是雅可比矩阵的最大特征值,x就是RNN层数,要是a大于1,x越大那梯度就是指数级增长趋近于无穷(梯度爆炸);要是a小于1,x越大那梯度就是指数级减小趋近于0(梯度消失)
四、源码参考
下面就是我自己写的一点RNN的代码,这是适用于我自己跑的一个项目,大家有用的就拿去用吧,仅供参考啦。
import numpy as np
from numpy import savetxt
import pandas as pd
from pandas.plotting import register_matplotlib_converters
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.metrics import r2_score
import tensorflow as tf
from tensorflow import keras
from keras.optimizers import Adam,RMSprop
import os
import tensorflow as tf
tf.config.set_visible_devices(tf.config.list_physical_devices('GPU'), 'GPU')
register_matplotlib_converters()
#plt.rcParams["font.sans-serif"] = [""]# 指定默认字体
pd.set_option('display.max_columns', None) # 结果显示所有列
pd.set_option('display.max_rows', None) # 结果显示所行行
#>>>>>>>>>>>>数据预处理
#1.训练集(New-train)数据处理
source = 'New-train.csv'
df_train = pd.read_csv(source, index_col=None)
df_train = df_train[['设置你的数据表头']]
source = 'New-test.csv'
df_test = pd.read_csv(source, index_col=None)
df_test = df_test[['设置你的数据表头']]
train_size = int(len(df_train))
test_size = int(len(df_test))
train = df_train.iloc[0:train_size]
test = df_test.iloc[0:test_size]
print(len(train), len(test))
def training_data(X, y, time_steps=1):
Xs, ys = [], []
for i in range(len(X) - time_steps):
v = X.iloc[i:(i + time_steps)].values
Xs.append(v)
ys.append(y.iloc[i + time_steps])
return np.array(Xs), np.array(ys)
time_steps = 10
X_train, y_train = training_data(train.loc[:, '设置你的数据表头'], train.*, time_steps)
x_test, y_test = training_data(test.loc[:,'设置你的数据表头'], test.*, time_steps)
# 1.RNN模型定义(结构design)
def model_RNN(units):
model = keras.Sequential()
# 输入层
model.add(keras.layers.SimpleRNN(units=units, activation="relu", input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(keras.layers.Dropout(0.2))
# 输出层
model.add(keras.layers.Dense(1))
# 编译模型
model.compile(loss='mse', optimizer=Adam(learning_rate=0.001))
return model
def fit_model(model):
#早停机制,防止过拟合
early_stop = keras.callbacks.EarlyStopping(
monitor='val_loss',
min_delta=0.0,#min_delta=0.0 表示如果训练过程中的指标没有发生任何改善,即使改善非常微小,也会被视为没有显著改善
patience=2000)#表示如果在连续的 20 个 epoch 中,指标没有超过 min_delta 的改善,训练将被提前停止
history = model.fit( # 在调用model.fit()方法时,模型会根据训练数据进行参数更新,并在训练过程中逐渐优化模型的性能
X_train, y_train, # 当训练完成后,模型的参数就被更新为训练过程中得到的最优值
epochs=400, # 此时model已经是fit之后的model,直接model.predict即可(千万不要model=model.fit(),然后再model.predict)
validation_split=0.1,
batch_size=12600,
shuffle=False,
callbacks=[early_stop])
return history
RNN = model_RNN(64)
#这里只是写了训练模型的代码,预测的话要根据自己的数据结构以及想要的效果来写喔