【技术专题】TensorFlow2 Python深度学习 - 循环神经网络(RNN)

0 阅读16分钟

大家好,我是锋哥。最近连载更新《TensorFlow2 Python深度学习》技术专题。

QQ截图20260306194834.jpg 本课程主要讲解基于TensorFlow2的Python深度学习知识,包括深度学习概述,TensorFlow2框架入门知识,以及卷积神经网络(CNN),循环神经网络(RNN),生成对抗网络(GAN),模型保存与加载等。同时也配套视频教程 《2026版 TensorFlow2 Python深度学习 视频教程》

循环神经网络(RNN)简介

一,什么是RNN

RNN(Recurrent Neural Network)是一类用于处理序列数据的神经网络。首先我们要明确什么是序列数据,摘取百度百科词条:时间序列数据是指在不同时间点上收集到的数据,这类数据反映了某一事物、现象等随时间的变化状态或程度。这是时间序列数据的定义,当然这里也可以不是时间,比如文字序列,但总归序列数据有一个特点——后面的数据跟前面的数据有关系。

RNN是神经网络的一种,类似的还有深度神经网络DNN,卷积神经网络CNN,生成对抗网络GAN等等。RNN对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,利用了RNN的这种能力,使深度学习模型在解决语音识别、语言模型、机器翻译以及时序分析等NLP领域的问题时有所突破。

举几个具有序列特性的例子:

  • 拿人类的某句话来说,也就是人类的自然语言,是不是符合某个逻辑或规则的字词拼凑排列起来的,这就是符合序列特性。
  • 语音,我们发出的声音,每一帧每一帧的衔接起来,才凑成了我们听到的话,这也具有序列特性。
  • 股票,随着时间的推移,会产生具有顺序的一系列数字,这些数字也是具有序列特性。

二、为什么要发明RNN

我们先来看一个NLP很常见的问题,命名实体识别,举个例子,现在有两句话:

第一句话:I like eating apple!(我喜欢吃苹果!)

第二句话:The Apple is a great company!(苹果真是一家很棒的公司!)

现在的任务是要给apple打Label,我们都知道第一个apple是一种水果,第二个apple是苹果公司,假设我们现在有大量的已经标记好的数据以供训练模型,当我们使用全连接的神经网络时,我们做法是把apple这个单词的特征向量输入到我们的模型中(如下图),在输出结果时,让我们的label里,正确的label概率最大,来训练模型,但我们的语料库中,有的apple的label是水果,有的label是公司,这将导致,模型在训练的过程中,预测的准确程度,取决于训练集中哪个label多一些,这样的模型对于我们来说完全没有作用。问题就出在了我们没有结合上下文去训练模型,而是单独的在训练apple这个单词的label,这也是全连接神经网络模型所不能做到的,于是就有了我们的循环神经网络。

image.png

三、RNN的基础知识

1、循环核介绍

循环核具有记忆力,通过不同时刻的参数共享,实现了对时间序列的信息提取

image.png

2、循环核按时间步展开

按时间步展开,就是把循环核按照时间轴方向展开。每个时刻记忆体状态信息ht被刷新,记忆体周围的参数矩阵wxh、whh和why是固定不变的。要训练优化的就是这些参数矩阵。训练完成后,使用效果最好的参数矩阵,执行前向传播,输出预测结果。循环神经网络,就是借助循环核提取时间特征后,送入全连接网络,实现连续数据的预测。

3、记忆体

循环核按照时间步展开后,可以发现,循环核是由多个记忆体构成,记忆体是循环神经网络储存历史状态信息的载体,每个记忆体都可以设定相应的个数,这个个数决定了记忆体可以存储历史状态信息的能力,记忆体个数越多,训练效果越好,但是由于记忆体的个数决定了参数矩阵的维度,因此记忆体个数越多,需要训练的参数量就越多,所需要消耗的资源就越大,训练时间就越长,因此需酌情评估。图中的例子中记忆体的个数为3,这个记忆体的个数,决定了ht的维度,进一步决定了Wxh、Whh以及Why的维度。

image.png

image.png

4、循环计算层

每个循环核构成一层循环计算层。循环计算层的层数是向输出方向增长的。

image.png

转载自:blog.csdn.net/qq_39439006…

通俗易懂理解循环神经网络(RNN)

🧠 用一个生活比喻来理解RNN

想象你在看电影时理解剧情的过程:

  • 传统神经网络:像只看电影的一帧画面,不知道前后剧情
  • 循环神经网络:像正常人看电影,能记住前面发生了什么,理解当前画面在整体剧情中的位置

📚 什么是RNN?简单说就是"有记忆的神经网络"

基本概念:

  • 普通神经网络:每次分析都是"从零开始",没有记忆
  • RNN:会"记住"之前看到的内容,用来帮助理解现在的内容

就像:

  • 读句子时,你理解每个词的意思都依赖于前面的词
  • "我今天吃了苹果" vs "我买了苹果手机"
  • 同样的"苹果",意思完全不同,需要前面的语境来理解

🔄 RNN如何工作?"时间旅行"的神经网络

核心机制:

输入 → [当前分析 + 之前记忆] → 输出 + 新记忆

具体过程:

  1. 收到新的输入(比如一个新词)
  2. 结合"刚才的记忆"来理解这个输入
  3. 产生输出(比如理解这个词的意思)
  4. 更新记忆,为下一个输入做准备

举个读句子的例子:

句子:"我爱" + "吃" + "苹果"

第一步:看到"我爱"

  • 记忆:空
  • 输出:这是一个关于喜好的句子
  • 新记忆:主题是"喜欢某物"

第二步:看到"吃"

  • 记忆:主题是"喜欢某物"
  • 输出:喜欢与吃相关的东西
  • 新记忆:喜欢+吃 → 可能是食物

第三步:看到"苹果"

  • 记忆:喜欢吃的食物
  • 输出:喜欢吃的苹果(水果)
  • 新记忆:完整的句子意思

🏗️ RNN的三种主要类型

  1. 基础RNN - 短期记忆型
  • 像金鱼,只能记住最近的一点信息
  • 简单但容易"忘记"太早的事情
  1. LSTM - 长期记忆高手 🎯
  • 像聪明的人,能选择性记住重要信息

  • 有三个"门"来控制记忆:

    • 输入门:决定什么新信息要记住
    • 忘记门:决定什么旧信息要忘记
    • 输出门:决定输出什么信息
  1. GRU - 简化版LSTM
  • 像LSTM的弟弟,简单但有效
  • 只有两个门,计算更快

🌟 RNN的实际应用场景

📝 文本相关:

  • 机器翻译:理解整个句子再翻译
  • 聊天机器人:记住对话历史
  • 文章生成:写小说时保持情节连贯

🎵 音频处理:

  • 语音识别:根据前后语音判断当前词
  • 音乐生成:创作连贯的旋律

⏰ 时间序列:

  • 股票预测:基于历史价格预测未来
  • 天气预报:基于过去天气预测未来

🆚 RNN vs 传统神经网络

传统神经网络RNN
每次输入独立处理考虑输入之间的关联
像看单张照片像看连续视频
适合图片分类适合语言、时间序列

💡 举个更生活的例子:聊天对话

你问AI:"今天天气怎么样?明天呢?"

普通神经网络回答:

  • "今天天气怎么样?" → "今天晴天"
  • "明天呢?" → "我不知道你在问什么"

RNN回答:

  • "今天天气怎么样?" → "今天晴天"
  • "明天呢?" → "明天也是晴天" (记得你在问天气)

⚠️ RNN的局限性

主要问题:记忆有限

  • 就像人记不住太长的故事细节
  • 处理很长序列时,会"忘记"开头的内容

解决方案:

  • LSTM/GRU:改善长序列记忆
  • 注意力机制:让模型能"回头看"重要部分

🎯 总结

RNN = 神经网络 + 记忆功能

  • 核心价值:能处理有顺序、有关联的数据
  • 关键思想:当前理解依赖于历史信息
  • 适用场景:任何需要"上下文"的任务

就像人类理解语言需要上下文一样,RNN让AI也能具备这种"联系前后文"的能力! 🚀

IMDB数据集数据集简介

1. 数据集概述

IMDB 数据集是一个二分类情感分析的经典基准数据集。它包含了来自互联网电影数据库(IMDB)的 50,000 条影评文本,其中 25,000 条用于训练,另外 25,000 条用于测试。

数据集的标签是二元的:

  • 0:代表负面评论
  • 1:代表正面评论

一个关键的特点是,训练集和测试集是平衡的,这意味着它们各自包含 25,000 条正面和 25,000 条负面评论。

2 . 数据预处理形式

在 TensorFlow 2 中,IMDB 数据集已经过预处理。原始评论文本中的单词已经被转换为整数索引,这些索引对应于在一个词汇表中该单词的频率排名。

例如:

  • 整数 1 通常代表数据集中最常出现的单词。
  • 整数 2 代表第二常见的单词,以此类推。
  • 整数 0 不代表任何特定单词,而是被用作 填充符
  • 整数 3 通常代表 “未知单词” ,即那些不在最常用词汇列表中的单词。

默认情况下,数据集被设置为仅保留词汇表中前 10,000 个最常用的单词(通过参数 num_words 控制)。这既有助于控制模型的复杂度,也减少了计算和内存的开销。

循环神经网络(SimpleRNN)示例

import tensorflow as tf
from keras import Input, layers
from keras.src.utils import pad_sequences
​
# 1. 加载 IMDB 数据集
max_features = 10000  # 使用词汇表中前 10000 个常见单词
maxlen = 100  # 每条评论的最大长度
​
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=max_features)
print(x_train.shape, x_test.shape)
print(x_train[0])
print(y_train)
​
# 2. 数据预处理:对每条评论进行填充,使其长度统一
x_train = pad_sequences(x_train, maxlen=maxlen)
x_test = pad_sequences(x_test, maxlen=maxlen)
​
# 3. 构建 RNN 模型
model = tf.keras.models.Sequential([
    Input(shape=(maxlen,)),
    layers.Embedding(input_dim=max_features, output_dim=128),  # 嵌入层,将单词索引映射为向量 output_dim  嵌入向量的维度(即每个输入词的嵌入表示的长度)
    layers.SimpleRNN(128),  # SimpleRNN 层:包含 128 个神经元,激活函数默认使用 tanh
    layers.Dense(1, activation='sigmoid')  # 输出层:用于二分类(正面或负面),激活函数为 sigmoid
])
​
# 4. 模型编译
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
​
# 5. 模型训练
history = model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test), verbose=1)
​
# 6. 模型评估
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc}")

运行结果:

image.png

循环神经网络(LSTM)示例

LSTM(长短期记忆网络,Long Short-Term Memory)是一种特殊类型的循环神经网络(RNN),用于处理和预测序列数据。它能够有效地解决标准RNN在长期依赖问题中的缺点,如梯度消失和梯度爆炸问题。LSTM的关键在于其特殊的结构,其中包括了三个“门”机制:输入门、遗忘门和输出门,这些门控制信息流的进入、遗忘和输出,允许模型更好地捕捉和保持长期的依赖关系。

LSTM的基本结构

LSTM单元的结构包括以下几部分:

  1. 输入门(Input Gate) :决定哪些新信息被写入到单元状态。
  2. 遗忘门(Forget Gate) :决定哪些信息会从单元状态中丢弃或保留。
  3. 输出门(Output Gate) :决定哪些信息将用于输出。
tf.keras.layers.LSTM(
    units,
    activation='tanh',
    recurrent_activation='sigmoid',
    use_bias=True,
    kernel_initializer='glorot_uniform',
    recurrent_initializer='orthogonal',
    bias_initializer='zeros',
    unit_forget_bias=True,
    dropout=0.0,
    recurrent_dropout=0.0,
    return_sequences=False,
    return_state=False,
    go_backwards=False,
    stateful=False,
    time_major=False,
    unroll=False,
    **kwargs
)

核心参数:

  1. units - 最重要的参数
  • 作用:定义LSTM层中记忆单元的数量
  • 通俗理解:LSTM的"脑容量"或"记忆力大小"
  • 影响:值越大,模型表达能力越强,但计算复杂度越高
  • 建议范围:32-1024,根据任务复杂度选择
  1. return_sequences - 输出控制
  • 作用:控制是否返回所有时间步的输出

  • 默认值False(只返回最后一个时间步的输出)

  • 使用场景

    • False:用于分类、情感分析等只需要最终结果的场景
    • True:用于序列标注、机器翻译等需要每个时间步输出的场景
  1. dropoutrecurrent_dropout - 正则化参数
  • dropout:输入单元的丢弃率,防止过拟合
  • recurrent_dropout:循环连接的丢弃率,防止循环过拟合
  • 建议值:0.2-0.5,根据数据量和模型复杂度调整
  1. activationrecurrent_activation - 激活函数
  • activation:主要计算的激活函数,默认'tanh'
  • recurrent_activation:门控单元的激活函数,默认'sigmoid'
  1. return_state - 状态返回
  • 作用:是否返回LSTM的隐藏状态和细胞状态
  • 使用场景:编码器-解码器结构、状态传递等高级应用
  1. stateful - 状态保持
  • 作用:批次间是否保持LSTM状态
  • 使用场景:处理超长序列需要分批时保持状态连续性
  1. unroll - 展开计算
  • 作用:是否将RNN展开为前馈网络
  • 优点:加速训练(适合短序列)
  • 缺点:内存消耗大(不适合长序列)

示例:

import tensorflow as tf
from keras import Input, layers
from keras.src.utils import pad_sequences
​
# 1. 加载 IMDB 数据集
max_features = 10000  # 使用词汇表中前 10000 个常见单词
maxlen = 100  # 每条评论的最大长度
​
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=max_features)
print(x_train.shape, x_test.shape)
print(x_train[0])
print(y_train)
​
# 2. 数据预处理:对每条评论进行填充,使其长度统一
x_train = pad_sequences(x_train, maxlen=maxlen)
x_test = pad_sequences(x_test, maxlen=maxlen)
​
# 3. 构建 RNN 模型
model = tf.keras.models.Sequential([
    Input(shape=(maxlen,)),
    layers.Embedding(input_dim=max_features, output_dim=128),  # 嵌入层,将单词索引映射为向量 output_dim  嵌入向量的维度(即每个输入词的嵌入表示的长度)
    layers.LSTM(units=64, dropout=0.2, recurrent_dropout=0.2),
    # LSTM 层:包含 64 个神经元,激活函数默认使用 tanh  dropout表示在每个时间步上丢弃20% recurrent_dropout 递归状态(即隐藏状态)的dropout比率为20%
    layers.Dense(1, activation='sigmoid')  # 输出层:用于二分类(正面或负面),激活函数为 sigmoid
])
​
# 4. 模型编译
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
​
# 5. 模型训练
history = model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test), verbose=1)
​
# 6. 模型评估
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc}")

运行结果:

image.png

循环神经网络(GRU)示例

GRU(门控循环单元,Gated Recurrent Unit)是一种用于处理序列数据的递归神经网络(RNN)模型。它是为了克服传统RNN在长时间序列中训练时遇到的梯度消失问题(即记忆力衰减)而提出的。GRU相较于LSTM(长短时记忆网络),结构更简单,计算效率更高,但能够实现类似的性能。

GRU的工作原理

GRU通过引入两个重要的门控机制来控制信息的流动:

  1. 更新门(Update Gate) :决定了当前时刻的隐藏状态有多少部分应该被更新,多少部分保留旧的隐藏状态信息。这个门类似于LSTM中的遗忘门和输入门的结合。它通过一个Sigmoid激活函数控制当前输入和上一时刻的隐藏状态对当前时刻的影响。
  2. 重置门(Reset Gate) :控制遗忘多少旧的隐藏状态信息。它决定了当前时刻输入和过去隐藏状态的结合程度,从而决定了网络保留多少历史信息。通过一个Sigmoid激活函数来计算。

GRU的关键是利用这些门控机制在时间步之间传递信息,从而可以更好地捕获序列数据中的长期依赖性。

tf.keras.layers.GRU(
    units,
    activation='tanh',
    recurrent_activation='sigmoid',
    use_bias=True,
    kernel_initializer='glorot_uniform',
    recurrent_initializer='orthogonal',
    bias_initializer='zeros',
    dropout=0.0,
    recurrent_dropout=0.0,
    return_sequences=False,
    return_state=False,
    go_backwards=False,
    stateful=False,
    time_major=False,
    unroll=False,
    reset_after=True,
    **kwargs
)

核心参数:

  1. units - 核心参数
  • 作用:定义GRU层中隐藏单元的数量
  • 影响:决定模型的容量和复杂度,值越大表示记忆能力越强
  • 选择:通常根据任务复杂度在32-512之间选择
  1. activation - 激活函数
  • 作用:定义输出计算的激活函数
  • 默认值'tanh'
  • 功能:控制候选隐藏状态的计算
  1. recurrent_activation - 循环激活函数
  • 作用:定义门控机制的激活函数
  • 默认值'sigmoid'
  • 功能:控制更新门和重置门的计算
  1. return_sequences - 输出控制
  • 作用:控制是否返回所有时间步的输出

  • 默认值False(只返回最后一个时间步)

  • 应用

    • False:用于序列分类任务
    • True:用于序列标注或多层GRU堆叠
  1. return_state - 状态返回
  • 作用:是否返回最后的隐藏状态
  • 使用场景:编码器-解码器架构或需要状态传递的场景
  1. dropout - 输入丢弃率
  • 作用:输入单元的随机丢弃比例,防止过拟合
  • 范围:0.0-1.0,推荐0.2-0.5
  1. recurrent_dropout - 循环丢弃率
  • 作用:循环连接的随机丢弃比例
  • 功能:专门防止循环连接上的过拟合
  1. reset_after - 重置门位置
  • 作用:控制重置门的应用顺序
  • 默认值True(与CuDNN兼容的模式)
  • 影响:影响计算图和性能,通常保持默认

示例:

import tensorflow as tf
from keras import Input, layers
from keras.src.utils import pad_sequences
​
# 1. 加载 IMDB 数据集
max_features = 10000  # 使用词汇表中前 10000 个常见单词
maxlen = 100  # 每条评论的最大长度
​
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=max_features)
print(x_train.shape, x_test.shape)
print(x_train[0])
print(y_train)
​
# 2. 数据预处理:对每条评论进行填充,使其长度统一
x_train = pad_sequences(x_train, maxlen=maxlen)
x_test = pad_sequences(x_test, maxlen=maxlen)
​
# 3. 构建 RNN 模型
model = tf.keras.models.Sequential([
    Input(shape=(maxlen,)),
    layers.Embedding(input_dim=max_features, output_dim=128),  # 嵌入层,将单词索引映射为向量 output_dim  嵌入向量的维度(即每个输入词的嵌入表示的长度)
    layers.GRU(units=64, dropout=0.2, recurrent_dropout=0.2),
    # GRU 层:包含 64 个神经元,激活函数默认使用 tanh  dropout表示在每个时间步上丢弃20% recurrent_dropout 递归状态(即隐藏状态)的dropout比率为20%
    layers.Dense(1, activation='sigmoid')  # 输出层:用于二分类(正面或负面),激活函数为 sigmoid
])
​
# 4. 模型编译
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
​
# 5. 模型训练
history = model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test), verbose=1)
​
# 6. 模型评估
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc}")

运行结果:

image.png