RNN(循环神经网络)原理

一、RNN基本思想与核心概念

1.1 为什么需要RNN?

传统神经网络(如全连接网络、CNN)无法处理序列数据,因为它们:

无记忆性:每个输入独立处理,忽略序列中元素的时间/顺序关系

固定输入尺寸:无法处理可变长度的序列

无法建模时间依赖:比如语言中"我今天吃了__"的预测需要前面信息

RNN的突破:引入"记忆"概念,让网络可以记住之前的信息来处理当前输入。

1.2 核心思想:参数共享与循环连接

python 复制代码
传统网络:y_t = f(x_t)
RNN:      y_t = f(x_t, h_{t-1})

h:隐藏状态(记忆单元)

t:时间步

每个时间步使用相同的权重参数

二、RNN基本原理与数学推导

2.1 基础RNN结构

python 复制代码
输入序列:X = [x_1, x_2, ..., x_T]
输出序列:Y = [y_1, y_2, ..., y_T]
隐藏状态:H = [h_1, h_2, ..., h_T]

前向传播公式:

python 复制代码
# 时间步 t 的计算
h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b_h)  # 更新隐藏状态
y_t = W_hy * h_t + b_y                         # 输出

# 或者写成矩阵形式
h_t = tanh( 
    [W_hh | W_xh] * [h_{t-1}; x_t] + b_h
)
y_t = W_hy * h_t + b_y

2.2 展开计算图(Unrolled)

python 复制代码
时间步1: x1 → h1 = f(W*h0 + U*x1) → y1
                     ↓
时间步2: x2 → h2 = f(W*h1 + U*x2) → y2
                     ↓
时间步3: x3 → h3 = f(W*h2 + U*x3) → y3

W: 隐藏状态权重 (h_t-1 → h_t)

U: 输入权重 (x_t → h_t)

V: 输出权重 (h_t → y_t)

关键:所有时间步共享相同的权重参数 W, U, V

2.3 RNN的三种基本模式

"""

  1. 一对多 (One-to-Many)

    输入:单一样本 → 输出:序列

    应用:图像描述生成

  2. 多对一 (Many-to-One)

    输入:序列 → 输出:单一样本

    应用:情感分析、文本分类

  3. 多对多 (Many-to-Many)

    输入:序列 → 输出:序列

    应用:机器翻译、序列标注

  4. 编码器-解码器 (Encoder-Decoder)

    输入:序列 → 编码 → 解码 → 输出序列

    应用:seq2seq任务

    """

    三、RNN的详细数学原理

    3.1 前向传播完整公式

    对于一个包含T个时间步的序列:

python 复制代码
# 初始化隐藏状态
h_0 = zeros()  # 通常初始化为0向量

# 对每个时间步 t = 1 to T
for t in range(1, T+1):
    # 组合输入和上一时刻隐藏状态
    a_t = W_hh * h_{t-1} + W_xh * x_t + b_h
    
    # 应用激活函数(通常用tanh)
    h_t = tanh(a_t)
    
    # 计算输出(如果需要)
    o_t = W_hy * h_t + b_y
    
    # 应用输出激活函数(如softmax用于分类)
    y_t = softmax(o_t)

3.2 激活函数的选择

python 复制代码
"""
tanh:  范围(-1, 1),最常用,梯度消失问题较轻
sigmoid: 范围(0, 1),用于门控机制
ReLU:   范围(0, ∞),可能梯度爆炸
"""

3.3 损失函数

对于序列预测任务,总损失是各时间步损失之和:

python 复制代码
L = Σ_{t=1}^T L_t(y_t, ŷ_t)

其中ŷ_t是真实标签,y_t是预测值。

四、RNN的训练:BPTT算法

4.1 BPTT(Backpropagation Through Time)原理

BPTT是标准反向传播在时间维度上的扩展:

python 复制代码
# 反向传播计算梯度
def bptt(self, x, y):
    """
    1. 前向传播计算所有h_t
    2. 计算总损失 L = Σ L_t
    3. 反向传播计算梯度
    """
    
    # 损失对W_hy的梯度
    dL/dW_hy = Σ_t (dL_t/dy_t * dy_t/dW_hy)
    
    # 损失对W_hh的梯度(复杂!)
    dL/dW_hh = Σ_t (dL/dh_t * dh_t/dW_hh)
    
    # 关键:dh_t/dW_hh 依赖于所有之前的时间步
    # 需要应用链式法则展开

4.2 梯度计算详细推导

考虑损失对W_hh的梯度:

python 复制代码
∂L/∂W_hh = Σ_{t=1}^T ∂L/∂h_t * ∂h_t/∂W_hh

但 ∂h_t/∂W_hh 依赖于 h_{t-1}, h_{t-2}, ..., h_1
因此需要展开:
∂h_t/∂W_hh = Σ_{k=1}^t (∂h_t/∂h_k * ∂h_k/∂W_hh)

其中 ∂h_t/∂h_k = Π_{i=k}^{t-1} ∂h_{i+1}/∂h_i
= Π_{i=k}^{t-1} W_hh^T * diag(tanh'(a_i))

4.3 梯度消失与梯度爆炸问题

梯度消失(Vanishing Gradient)

python 复制代码
# 原因:长期依赖梯度衰减
∂h_t/∂h_k = Π_{i=k}^{t-1} ∂h_{i+1}/∂h_i
         = Π_{i=k}^{t-1} W_hh^T * diag(tanh'(a_i))

# 如果 |W_hh| < 1 且 tanh'(a_i) ≤ 1
# 那么随着 (t-k) 增大,乘积趋近于0

梯度爆炸(Exploding Gradient)

python 复制代码
# 原因:梯度指数增长
# 如果 |W_hh| > 1,梯度会指数爆炸

# 解决方法:梯度裁剪(Gradient Clipping)
if grad_norm > threshold:
    grad = grad * (threshold / grad_norm)

五、RNN变体与改进

5.1 LSTM(长短期记忆网络)

python 复制代码
"""
解决梯度消失问题的经典方案
核心:引入门控机制和细胞状态
"""

# LSTM的三个门
i_t = σ(W_i * [h_{t-1}, x_t] + b_i)  # 输入门
f_t = σ(W_f * [h_{t-1}, x_t] + b_f)  # 遗忘门
o_t = σ(W_o * [h_{t-1}, x_t] + b_o)  # 输出门

# 候选细胞状态
C̃_t = tanh(W_C * [h_{t-1}, x_t] + b_C)

# 更新细胞状态(关键!)
C_t = f_t ⊙ C_{t-1} + i_t ⊙ C̃_t

# 更新隐藏状态
h_t = o_t ⊙ tanh(C_t)

"""
优势:
1. 遗忘门控制记忆保留
2. 细胞状态C_t的线性路径避免梯度消失
3. 门控机制实现长期依赖
"""

5.2 GRU(门控循环单元)

python 复制代码
"""
LSTM的简化版,只有两个门
计算效率更高
"""

# GRU的两个门
z_t = σ(W_z * [h_{t-1}, x_t])  # 更新门
r_t = σ(W_r * [h_{t-1}, x_t])  # 重置门

# 候选隐藏状态
h̃_t = tanh(W * [r_t ⊙ h_{t-1}, x_t])

# 更新隐藏状态
h_t = (1 - z_t) ⊙ h_{t-1} + z_t ⊙ h̃_t

"""
特点:
1. 更新门决定保留多少旧信息
2. 重置门决定使用多少历史信息
3. 参数比LSTM少,训练更快
"""

5.3 双向RNN(BiRNN)

python 复制代码
"""
同时考虑过去和未来的上下文
"""

# 前向RNN
h_t_forward = RNN_forward(x_t, h_{t-1}_forward)

# 反向RNN
h_t_backward = RNN_backward(x_t, h_{t+1}_backward)

# 组合两个方向的隐藏状态
h_t = [h_t_forward; h_t_backward]

"""
应用:需要完整上下文的任务
如:命名实体识别、语音识别
"""

六、PyTorch中的RNN实现

6.1 基础RNN层

python 复制代码
import torch
import torch.nn as nn

# 创建RNN层
rnn = nn.RNN(
    input_size=10,      # 输入特征维度
    hidden_size=20,     # 隐藏状态维度
    num_layers=2,       # RNN层数
    batch_first=True,   # 输入形状为(batch, seq, feature)
    bidirectional=False,# 是否双向
    nonlinearity='tanh' # 激活函数
)

# 输入数据
batch_size = 3
seq_len = 5
input_size = 10
x = torch.randn(batch_size, seq_len, input_size)

# 初始隐藏状态
h0 = torch.zeros(2, batch_size, 20)  # (num_layers, batch, hidden_size)

# 前向传播
output, hn = rnn(x, h0)
print(f"输出形状: {output.shape}")  # (3, 5, 20)
print(f"最后隐藏状态: {hn.shape}")    # (2, 3, 20)

6.2 LSTM实现

python 复制代码
# 创建LSTM层
lstm = nn.LSTM(
    input_size=10,
    hidden_size=20,
    num_layers=2,
    batch_first=True,
    bidirectional=True  # 双向LSTM
)

# 输入
x = torch.randn(batch_size, seq_len, input_size)

# 初始状态(LSTM需要两个状态)
h0 = torch.zeros(4, batch_size, 20)  # (num_layers*2, batch, hidden_size)
c0 = torch.zeros(4, batch_size, 20)  # 细胞状态

# 前向传播
output, (hn, cn) = lstm(x, (h0, c0))
print(f"双向LSTM输出形状: {output.shape}")  # (3, 5, 40) hidden_size*2

7、总结

RNN的核心贡献是引入了时间维度上的记忆机制,使得神经网络能够处理序列数据:

基本结构:通过隐藏状态传递历史信息

训练方法:BPTT算法在时间维度上反向传播

主要问题:梯度消失/爆炸,通过LSTM/GRU缓解

应用场景:NLP、时间序列、语音识别等序列任务

PyTorch实现:提供了RNN、LSTM、GRU等现成模块

相关推荐
代码AI弗森2 小时前
意图识别深度原理解析:从向量空间到语义流形
人工智能
姚华军2 小时前
RagFlow、Dify部署时,端口如何调整成指定端口
人工智能·dify·ragflow
老蒋新思维2 小时前
创客匠人峰会新视角:AI 时代知识变现的 “组织化转型”—— 从个人 IP 到 “AI+IP” 组织的增长革命
大数据·人工智能·网络协议·tcp/ip·创始人ip·创客匠人·知识变现
JoannaJuanCV2 小时前
自动驾驶—CARLA仿真(0)报错记录
人工智能·机器学习·自动驾驶
小白狮ww3 小时前
Matlab 教程:基于 RFUAV 系统使用 Matlab 处理无人机信号
开发语言·人工智能·深度学习·机器学习·matlab·无人机·rfuav
飞行增长手记3 小时前
GPT-5.2 全面升级:AI 进入“加速竞争期”,该如何跟上这波红利?
人工智能
多则惑少则明3 小时前
AI测试、大模型测试(六)AI agent简介与Prompt提示词
人工智能·prompt·ai测试·ai大模型测试
moonsims3 小时前
自主高性价比、高精度车规级姿态感知、倾角感知模组-应用消费级无人机、自动驾驶、机器人、智能制造、基础设施、智能穿戴等
人工智能
TMO Group 探谋网络科技3 小时前
AI Agent工作原理:如何连接数据、决策与行动,助力企业数字化转型?
大数据·人工智能·ai