递归神经网络与循环神经网络联系
- CNN卷积神经网络和RNN递归神经网络都是由BP神经网络演化而来的。CNN卷积神经网络主要应用在图像识别领域;而RNN递归神经网络的改进版LSTM网络主要应用于自然语言处理中;
- 为什么我介绍RNN是叫——递归神经网络?这之前是一个困扰我很久的问题。因为在网上搜索RNN既有人说是递归神经网络,也有人说是循环神经网络,而且两个的区别也不明显,混淆很久,直到请教前辈——递归神经网络是从神经网络的运行状态划分的,由下面图示就可以了解到这种神经网络在运行时像是自行迭代;而循环神经网络可以看作是递归神经网络的特例,以示区别就把递归神经网络总称RNN,循环神经网络叫做RNNs。
一、RNN原理介绍
RNN神经网络为什么广泛应用于自然语言处理?因为它是依靠时间序列具有记忆功能的——这一刻的输出,除了考虑这一刻的输入外,还有一部分是来自上一刻的输出记忆。同时这一刻的输出也会有一部分做为“记忆”传给下一刻。
上图是RNN神经网络的结构示意图。上左图是简化过程,介绍从上右图说起:
- 上图包含
t-1、t、t+1
三个时刻的输入xt-1,xt,xt+1与输出ot-1,ot,ot+1; - 首先
t-1
时刻的输入xt-1,经过与权值U作用,预测得到st-1,st-1与权值V作用得到t-1
时刻的输出值ot-1,st-1与权值W作用得到t-1
时刻的状态记忆值传输到t
时刻; t
时刻的输入xt经权值U处理,与t-1
时刻的状态记忆值共同作用,预测得到st,st与权值V作用得到t
时刻的输出值ot,st与权值W作用得到t
时刻的状态记忆值传输到t+1
时刻;- 依此循环,当所有时刻的输入xn都注入训练后,得到最后时刻的输出on
形象的比喻——横向的传播就是游戏的主线,游戏过程中不断有纵向的分线任务X传入,每一个分线任务都对主线任务构成影响且都能生成一个预测结果O,最后一个输出O就是游戏的最终结果;
用RNN结合MNIST数据集做图片识别,输入的一个X就是MNIST数据集中图片的一行28个像素点数据的向量。在以下的数据维度说明中有更详细的介绍。
二、用RNN预测MNIST数据
之前介绍了BP神经网络、CNN卷积神经网络用MNIST数据集做图像识别,现在介绍用RNN循环神经网络也是用MNIST数据集做图像识别。这个识别原理也非常有趣。
RNN做图像识别原理:MNIST数据集中一张图片数据包含28*28的像素点。RNN是将一张图片数据的一行作为一个向量总体输入一个X中。也就是说,RNN有28个输入X,一个输入X有28个像素点。t=1时刻输入X1,得到中间状态S1,由S1得到预测值O1(现在的O1由于只考虑一张图片的一行数据而已,预测结果很不可靠),S1也作为一部分记忆值,再与图片下一行的输入X2相作用,得到中间状态S2,再得到预测值O2…循环迭代,直到将一张图片的28行全都代入,输出最后一个O28做为预测值。
2.1:MNIST数据集的读取
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data',one_hot = True)
batch_size = 100
batch_xs,batch_ys = mnist.train.next_batch(batch_size)
读取的数据先存入“mnist”数组中,并按照每100个图片作为一个批次,将训练集的图片数据存入batch_xs
数组,图片标签项存入batch_ys
。
2.2:输入数据维度说明
n_inputs = n_steps = 28
# batch_xs shape = [100,28,28]
X = batch_xs.reshape([batch_size,n_steps,n_inputs])
...
# X shape = (100batch,28steps,28inputs) ==> (100batch*28steps,28inputs)
X = tf.reshape(X,[-1,n_inputs])
# X_in shape ==> (100batch*28steps,128hidden)
X_in = tf.matmul(X,weights['in'])+biases['in']
# X_in shape ==> (100batch,28steps,128hidden)
X_in = tf.reshape(X_in,[-1,n_steps,n_hidden_number])
这里可以说是很烦人的地方了,输入数据的维度出错程序总是报错,因为批次的原因使得数据变成三维,而权重阈值计算是二维的
- 之前我们确定的每一个批次有100张图片数据,就相当于将100张
28*28
的图片叠放在一起,读取的就是一个三维立体的整体(长宽都是28,高100)。所以第3行代码先将读取的整批图片切割成[100,28,28]的单个图片; - 依据上面介绍的RNN做图像识别的原理出发,每个输入X都是一张图片的一行,再构造与图片行数相同个数的输入神经元就可以完成一张图片的识别。但现在是100张图片一起识别,就需要将三维立体的整体数据转化为二维才能与阈值、权重代入计算,第6行代码就是降维运算;
- 经过降维运算,RNN神经网络输入层28个神经元中每一个神经元输入的维度是
100*28
,经过第8行权值阈值处理后,得到X_in
。(隐藏层有128个神经元,所以X_in
此时维度是[100*28,128]
) - 再将得到的
X_in
切割成[100,28,128],继续后续cell核运算
2.3:RNN的核运算
首先说明:这个核运算是我为书写方便自创的
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(n_hidden_number,forget_bias = 1.0)
# LSTM cell is divided into two parts(c_state,m_state)
init_state = lstm_cell.zero_state(batch_size,dtype=tf.float32)
outputs,states = tf.nn.dynamic_rnn(lstm_cell,X_in,initial_state=init_state,time_major=False)
这个核运算也是基于TensorFlow框架。
- 首先创建RNN运算核——
lstm_cell
,选择的cell是BasicLSTMCell(tf.nn.rnn中有许多,这里不做介绍),参数是隐藏层神经元个数; - RNN的中间状态会得到两部分——一个是当前输出的
outputs
,另一个是传入下一时刻的记忆states
,RNN在init_state
中用c_state
、m_state
分别保存这两部分; - 用dynamic_rnn的方法,用输入值
X_in
进行核内运算,将输出分别存入相应数组中。
2.4:计算输出
result = tf.matmul(states[1],weights['out'])+biases['out']
- 在核内以
X_in
为输入,得到输出outputs
与states
,当所有行都代入计算得到最后的输出预测值。其中states[1] = outputs[-1]
,相当于最后一个输出值。
三、2018年内容:源码与效果展示
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2018/02/05;09:58
# -*- python3.5
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
mnist = input_data.read_data_sets('MNIST_data',one_hot = True)
training_iters = 50001
batch_size = 100 #批量大小
n_inputs = n_steps = 28
n_hidden_number = 128 #隐藏层神经元个数
n_outputs = 10 #输出层神经元个数
x = tf.placeholder(tf.float32,[None,n_steps,n_inputs])
Y = tf.placeholder(tf.float32,[None,n_outputs])
weights = {
# shape = (28,128)
'in':tf.Variable(tf.random_normal([n_inputs,n_hidden_number])),
# shape = (128,10)
'out':tf.Variable(tf.random_normal([n_hidden_number,n_outputs]))}
biases = {
# shape = (128,)
'in':tf.Variable(tf.constant(0.1,shape = [n_hidden_number,])),
# shape = (10,)
'out':tf.Variable(tf.constant(0.1,shape = [n_outputs,]))}
def RNN(X,weights,biases):
### 输入层到核运算 ###
# X shape = (100batch,28steps,28inputs) ==> (100batch*28steps,28inputs)
X = tf.reshape(X,[-1,n_inputs])
# X_in shape ==> (100batch*28steps,128hidden)
X_in = tf.matmul(X,weights['in'])+biases['in']
# X_in shape ==> (100batch,28steps,128hidden)
X_in = tf.reshape(X_in,[-1,n_steps,n_hidden_number])
### cell核内运算 ###
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(n_hidden_number,forget_bias = 1.0)
# LSTM cell is divided into two parts-->(c_state,m_state)
init_state = lstm_cell.zero_state(batch_size,dtype=tf.float32)
outputs,states = tf.nn.dynamic_rnn(lstm_cell,X_in,initial_state=init_state,time_major=False)
### 核内运算到输出层 ###
result = tf.matmul(states[1],weights['out'])+biases['out']
return result
prediction = RNN(x,weights,biases)
#二次代价函数:预测值与真实值的误差
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=prediction))
#梯度下降法:数据庞大,选用AdamOptimizer优化器
train_step = tf.train.AdamOptimizer(1e-3).minimize(loss)
#结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(Y,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
step = 0
while step*batch_size < training_iters:
batch_xs,batch_ys = mnist.train.next_batch(batch_size)
# batch_xs shape = [100,28,28]
batch_xs = batch_xs.reshape([batch_size,n_steps,n_inputs])
train_step.run(feed_dict={x:batch_xs,Y:batch_ys,})
if step%50 == 0:
train_accuracy = accuracy.eval(feed_dict={x:batch_xs,Y:batch_ys,})
print("step", step, "training accuracy", train_accuracy)
step += 1
效果展示:
四、2024年内容:
import numpy as np
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.utils import to_categorical
# 加载 MNIST 数据集
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 数据预处理:将像素值归一化并将标签转换为one-hot编码
X_train = X_train.astype("float32") / 255.0
X_test = X_test.astype("float32") / 255.0
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
# 构建RNN模型
model = Sequential([
LSTM(128, input_shape=(28, 28)), # 使用128个单元的LSTM层,输入形状为28步长、每步28维
Dense(64, activation="relu"), # 全连接层,包含64个神经元
Dense(10, activation="softmax") # 输出层,包含10个神经元,对应10个类别
])
# 编译模型
model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
# 训练模型
model.fit(X_train, y_train, epochs=10, batch_size=128, validation_data=(X_test, y_test))
# 评估模型
test_loss, test_accuracy = model.evaluate(X_test, y_test)
print(f"Test accuracy: {test_accuracy * 100:.2f}%")
代码详解:
数据预处理:
- 使用 mnist.load_data() 加载MNIST数据集。每个样本原本是一个28x28的灰度图像。
- 我们将每个图像视为一个28步长的序列,每一步有28个像素的输入。
- 将像素值归一化到 [0, 1] 范围,并使用 to_categorical() 将标签转换为 one-hot 编码。
RNN模型构建:
- 使用 LSTM(128, input_shape=(28, 28)) 构建一个具有128个单元的LSTM层。输入形状为 (28, 28),表示序列长度为28,每步的特征维数为28。
- 添加一个全连接层 Dense(64, activation=“relu”),包含64个神经元,用于特征提取。
- 最后添加一个输出层 Dense(10, activation=“softmax”),有10个神经元,对应10个类别。
模型编译:
- 使用 adam 作为优化器,categorical_crossentropy 作为损失函数,并以 accuracy 为评估指标。
模型训练和评估:
- 使用 model.fit 进行模型训练,设定训练周期为10,每个批次包含128个样本。
- 通过 model.evaluate 输出测试集上的准确率。
结果
- 通常情况下,使用10个周期的训练,LSTM在MNIST测试集上的准确率可以达到90%左右。