传统的全连接的前馈神经网络不考虑数据之间的关联性,网络的输出只和当前时刻网络的输入相关。然而在解决很多实际问题的时候我们发现,现实问题中存在着很多序列型的数据(文本、语音、视频以及跟时间有关的序列(股票走势,一个站点全年的货物量的预测)等),现实场景如室外的温度是随着气候的变化而周期性的变化的,以及我们的语言也需要通过上下文的关系来确认所表达的含义。
这些序列型的数据往往都是具有时序上的关联性的,既某一时刻网络的输出除了与当前时刻的输入相关之外,还与之前某一时刻或某几个时刻的输出相关。而前馈神经网络并不能处理好这种关联性,因为它没有记忆能力,所以前面时刻的输出不能传递到后面的时刻。
因此,就有了现在的循环神经网络,其本质是:拥有记忆的能力,并且会根据这些记忆的内容来进行推断。因此,它的输出就依赖于当前的输入和记忆。相比于前馈神经网络,该网络内部具有很强的记忆性,它可以利用内部的记忆来处理任意时序的输入序列。
RNN的目的就是用来处理序列数据的。在传统的神经网络模型中,是从输入层到隐含层再到输出层,层与层之间是全连接的,每层之间的节点是无连接的。但是这种普通的神经网络对于很多连续问题都无能无力。比如你要预测句子的下一个单词是什么,一般需要用到前面的单词,因为一个句子中前后单词并不是独立的。
RNN之所以称为循环神经网路,即一个序列当前的输出与前面的输出也有关。具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出。理论上,RNNs能够对任何长度的序列数据进行处理。但是在实践中,为了降低复杂性往往假设当前的状态只与前面的几个状态相关,下图便是一个典型的RNNs:
相比于词前馈神经网络模型,RNN可以考虑到词的先后顺序对预测的影响,RNN包括三个部分:输入层、隐藏层和输出层。相对于前馈神经网络,RNN可以接收上一个时间点的隐藏状态。
每个RNN层有一个循环核。一个循环核有多个记忆体。
time_step不影响参数的个数。
设 RNN层 输入向量的维度 为input_dim
RNN层神经元个数 为 units
则RNN层的参数个数为 input _ dim × units + units**2 + units
为了更直观,特在下图示例中标出。
以输入数据维度为5,记忆体个数为3,输出数据维度为5为例。神经网络包含一个隐藏层和一个输出层。
time_step = 10 # time_step不影响参数数量
input_dim = 5
units=3 # RNN层的神经元个数,也是记忆体的个数
output_dim = 5
model4 = Sequential()
# RNN层 5个神经元 输入数据维度为5
model4.add(layers.SimpleRNN(units=units, input_shape=(time_step,input_dim),activation='relu'))
# 输出层 一个神经元 输出数据维度为5
model4.add(layers.Dense(output_dim))
model4.summary()
但我们也有需要更多上下文(比如一篇文章)的情况。考虑试图预测“I grew up in France… I speak fluent French.”中最后一个词。最近信息显示下一个词可能是一门语言的名字,但是如果我们想要缩小选择范围,我们需要包含“法国”的那段上下文,从前面的信息推断后面的单词。相关信息与预测位置的间隔很大是完全有可能的。不幸的是,随着这种间隔的拉长,RNNs就会无法学习连接信息。
LSTMs与GRUs类似,目前非常流行。它与一般的RNNs结构本质上并没有什么不同,只是使用了不同的函数去去计算隐藏层的状态。在LSTMs中,i结构被称为cells,可以把cells看作是黑盒用以保存当前输入Xt,之前的保存的状态ht−1,这些cells更加一定的条件决定哪些cell抑制哪些cell兴奋。它们结合前面的状态、当前的记忆与当前的输入。已经证明,该网络结构在对长序列依赖问题中非常有效。
LSTM中第一步是决定哪些信息需要从单元状态中抛弃。这项决策是由一个称为“遗忘门限层”的sigmoid层决定的。它接收和,然后为单元状态中的每个数字计算一个0到1之间的数字。1表示“完全保留”,而0则表示“完全抛弃”。
我们来回顾一下那个语言模型的例子,试图根据前面所有的词语来预测下一个词。在这种问题中,单元状态可能包含当前主语的性别,所以可以使用正确的代词。当碰到一个新的主语时,我们希望它能够忘记旧主语的性别。
接下来我们需要决定在单元状态中需要存储哪些新信息。这分为两个部分。首先,一个叫做“输入门限层”的sigmoid层决定哪些值需要更新。接下来,一个tanh层创建一个向量,包含新候选值,这些值可以添加到这个状态中。下一步我们将会结合这两者来创建一个状态更新。
在语言模型的例子中,我们希望在单元状态中添加新主语的性别,来替换我们忘记的旧主语性别。
现在来更新旧单元状态了,输入到新单元状态。之前的步骤已经决定了需要做哪些事情,我们只需要实现这些事情就行了。
我们在旧状态上乘以,忘记之前决定需要忘记的。然后我们加上,这就是新的候选值,它的规模取决于我们决定每个状态值需要更新多少。
在语言模型的例子中,这里就是我们实际丢弃旧主语性别信息,根据之前步骤添加新信息的地方。
最后,我们需要决定需要输出什么。这个输出将会建立在单元状态的基础上,但是个过滤版本。首先,我们运行一个sigmoid层来决定单元状态中哪些部分需要输出。然后我们将单元状态输入到tanh函数(将值转换成-1到1之间)中,然后乘以输出的sigmoid门限值,所以我们只输出了我们想要输出的那部分。
对于语言模型例子来说,因为它只看到了一个主语,它可能想输出与动词相关的信息,为接下来出现的词做准备。比如,它可能输出主语是单数还是复数,那么我们知道接下来修饰动词的应该成对。
有一个快递站点,两年的货物量数据,预测后一个月的货物数据。
数据集:
货物量数据图如下:
在训练集上的表现
在测试集上的表现:(效果没有很好,因为重在熟悉数据处理的流程)
'''
任务:建立RNN网络,预测货物数据
1 完成数据预处理,将序列转化为可用于RNN输入的数据
2 对新的数据进行预测,可视化结果。
3 存储预测结果,并观测局部预测结果
tips: 模型结构,simpleRNN 输出有5个神经元 每次用前十个数据预测第十一个。
'''
import pandas as pd
import numpy as np
from keras.models import Sequential
from keras.layers import Dense,SimpleRNN
import matplotlib.pyplot as plt
#define the method to extract X and y
def extract_data(data,time_step):
X = []
y = []
for i in range(len(data)-time_step):
X.append([a for a in data[i:i+time_step]])
y.append(data[i+time_step])
X = np.array(X)
X = X.reshape(X.shape[0],X.shape[1],1)
y = np.array(y)
y = y.reshape(-1,1)
return X, y
#todo 输出的时候直接是数组的形式。array
def set_model():
model = Sequential()
#todo RNN层的神经元个数,也是记忆体的个数
model.add(SimpleRNN(units=160, input_shape=(time_step, 1), activation='relu'))
model.add(Dense(units=1, activation='linear'))
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()
return model
if __name__ == '__main__':
time_step = 8
data = pd.read_csv('D:\S\load3.csv')
data = data.loc[:, 'load'] # 注意符号的大小写
# 归一化处理
data_norm = data / max(data)
# define X and y
X, y = extract_data(data_norm, time_step)
model = set_model()
model.fit(X, y, batch_size=32, epochs=200)
# 逆归一化
y_train_predict = model.predict(X) * max(data)
y_train = y * max(data)
# 训练集的图像
fig2 = plt.figure(figsize=(8, 5)) # 创建一个画布
plt.title('close')
plt.xlabel('time')
plt.ylabel('load')
plt.plot(y_train_predict, label='predict')
plt.plot(y_train, label='train')
plt.legend()
plt.show()
# 读入测试数据
data_test = pd.read_csv('D:\\S\\test.csv')
test = data_test.loc[:, 'load']
test_X_norm = test / max(data) # 还是用原来的进行归一化进行处理。
test_X, test_y = extract_data(test_X_norm, time_step)
# make predict
test_y_predict = model.predict(test_X) * max(data)
test_y = test_y * max(data)
# 测试集的图像
fig3 = plt.figure(figsize=(8, 5)) # 创建一个画布
plt.title('close')
plt.xlabel('time')
plt.ylabel('load')
plt.plot(test_y_predict, label='predict')
plt.plot(test_y, label='real')
plt.legend()
plt.show()
#todo 合并预测的数据和实际的数据,写入csv文件里并比较 发现其延时性
result = np.concatenate((test_y_predict, test_y), axis=1)
# 注意加上括号。
result = pd.DataFrame(result, columns=['predict', 'real'])
result.to_csv('D:\S\save.csv')
给一定数量的文章,然后训练数据,通过连续20个字符预测下一个字符是什么。
建立的词汇表:
预测效果:
在这里插入代码片'''
LSTM 文本生成实战
1.通过搭建LSTM模型,实现基于文本序列的字符生成功能
2.使用文本加载模型,字典生成方法
3.对文本实现预处理,使用数据结构转换方法
4.实现新文本数据的预测
'''
#todo SimpleRNN层不擅长于处理较长的序列,而LSTM则相对于SimpleRNN更适合处理较长的序列
import numpy as np
import tensorflow as tf
import keras.utils.np_utils # 转换独热编码,不同的tensorflow版本可能函数不一样,版本之间差异 / to_category
from keras.models import Sequential
from keras.layers import Dense,LSTM
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 每一步都用数组来表示
# 提取数据 每slide一组来作为输入变量。后面的那个作为输出变量
def extract_data(data, slide):
'''
data :要处理的文本
slide: 步长
'''
x = []
y = []
for i in range(len(data) - slide):
x.append([a for a in data[i:i + slide]])
y.append(data[i + slide])
x = np.array(x)
x = x.reshape(x.shape[0], x.shape[1], 1)
y = np.array(y)
return x, y
# 把字符转换成数字。
# x.shape (1046, 20, 1)
def char_to_int_data(x, y, char_to_int):
'''
输出类型都是np.arraty()
'''
x_to_int = []
y_to_int = []
for i in range(len(x)):
x_to_int.append([char_to_int[char[0]] for char in x[i]])
y_to_int.append([char_to_int[char[0]] for char in y[i]])
x_to_int = np.array(x_to_int)
x_to_int = x_to_int.reshape(x_to_int.shape[0], x_to_int.shape[1], 1)
y_to_int = np.array(y_to_int)
return x_to_int, y_to_int
def data_processing(data, slide, num_letters, char_to_int):
'''
输入类型:
data: 原始数据
slide:用多少个字符去预测下一个字符
num_letters: 字典大小
char_to_int: 字符转化成数字的字典
new: 转换后的输入向量的 one-hot 编码
输出类型:
new : one-hot 编码后的array
output : 没有正则化的array
'''
char_data_x, char_data_y = extract_data(data, slide)
int_data_x, int_data_y = char_to_int_data(char_data_x, char_data_y, char_to_int)
Input = int_data_x
Output = int_data_y
Input_reshaped = Input.reshape(len(Input), slide)
new = keras.utils.np_utils.to_categorical(Input_reshaped, num_classes=num_letters)
return new, Output
def to_categorical(x,num_letters):
# 将x转换成独热编码 num_letters :类别数目
y = keras.utils.np_utils.to_categorical(x,num_letters)
return y
def reverse_categorical(x):
# 逆独热化
y = [np.argmax(i) for i in x]
return y
def label_to_char(x,int_to_char):
# 将独热码转换后的标签转化为 字符
y = [int_to_char[i] for i in x]
return y
def char_to_sentence(x):
result = ""
for i in range(len(x)):
result += x[i]
return result
def set_model():
model = Sequential()
model.add(LSTM(units=20, input_shape=(x_train.shape[1], x_train.shape[2]), activation='relu'))
model.add(Dense(units=num_letters, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()
return model
if __name__ == '__main__':
#todo 加载数据
data = open('D:\S\lstm.txt', encoding='utf-8').read()
# 移除换行符 和 空格
data = data.replace('\n', '').replace('\r', '')
data = data.casefold() # 变化为小写
letters = list(set(data))
#todo 建立 *字典映射 * int_to_char 和 char_to_int
#int_to_char 用于模型训练完将数字转化成字符或者汉字、
#char_to_int 用于语句转换成模型能够使用的数字序列。
int_to_char = {a: b for a, b in enumerate(letters)}
char_to_int = {b: a for a, b in enumerate(letters)}
#todo 初始化 步长 和 计算字典大小
time_step = 20
num_letters = len(char_to_int)
#todo 开始测试
x,y = data_processing(data,time_step,num_letters,char_to_int)
# 数据划分函数
# test_size 随机占比 random_sate 随机种子编号吗,每次能打得到同样的数字 填0或者不填的 每次不一样
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.1, random_state=10)
model = set_model()
# 训练的时候都用的是独热编码, 因此需要将y_test转化为成独热编码
y_train_category = to_categorical(y_train,num_letters)
y_test_category = to_categorical(y_test,num_letters)
model.fit(x_train, y_train_category, batch_size=64, epochs=100, validation_data=(x_test, y_test_category))
# 从独热编码 转换到 标签
# 独热编码 两种 一个是直接的标签第几个为1的 一个是 概率最大值对应的位置为标签
y_test_predict = model.predict(x_test)
y_test_predict_label = reverse_categorical(y_test_predict)
y_test_category_label = reverse_categorical(y_test_category)
#todo 对比标签和准确率
print(y_test_predict_label)
print(y_test_category_label)
accuracy_score = accuracy_score(y_test_predict_label, y_test_category_label)
print(accuracy_score)
# 新的测试信息
new_letters = "sometimes, we watchtv and listen to music at home.I love my family. "
new_letters = new_letters.casefold()
x_new, y_new = data_processing(new_letters, time_step, num_letters, char_to_int)
y_new_predict = model.predict(x_new)
#todo 展示预测出来的那句话sentence
y_new_label = reverse_categorical(y_new_predict)
y_new_char = label_to_char(y_new_label, int_to_char)
result = char_to_sentence(y_new_char)
print(result)
#todo 在预测集中逐个显示测试
for i in range(0, x.shape[0] - 20):
print(new_letters[i:i + 20], '---new char is---', y_new_char[i])
1.RNN原理
2.LSTM解释
3.RNN参数详解
4.RNN和LSTM框架
5.理解RNNs — 长短记忆网络(LSTMS)