mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
4274 字
11 分钟
循环神经网络

一、序列处理的需求与挑战#

序列数据的特点#

  1. 可变长度:自然语言句子、语音信号、时间序列数据等长度不固定
  2. 上下文依赖:序列中元素的意义取决于其前后元素
  3. 记忆需求:理解当前输入需要参考历史信息

前馈神经网络的局限性#

  • 固定输入大小:难以处理可变长度序列
  • 无记忆能力:相同输入总是产生相同输出,无法根据历史上下文调整
  • 槽位填充问题(Slot Filling)
    • 任务:从自然语言中提取特定参数(如目的地、出发时间等)
    • 示例输入:“我想在6月1日从台北抵达上海”
    • 需要识别:
      • “台北” → 出发地(Place of Departure)
      • “上海” → 目的地(Place of Destination)
      • “6月1日” → 时间(Time)
    • 挑战:仅看单词”上海”或”台北”无法判断其角色,必须结合上下文中的动词”从”和”抵达”来理解
    • 应用场景:智能客服、订票系统、语音助手

二、RNN基本概念与架构#

核心思想:带记忆的网络#

  • 记忆元:存储网络的历史状态
  • 隐状态:记忆元中的值,表示对之前输入序列的”记忆”
  • 循环连接:当前时刻的隐状态作为下一时刻的输入之一

简单循环网络 (Simple RNN/Elman Network)#

Simple RNN架构示意图

数学模型#

ht=σ(Wxhxt+Whhht1+bh)\pmb{h}^t = \sigma(\pmb{W}_{xh}\pmb{x}^t + \pmb{W}_{hh}\pmb{h}^{t-1} + \pmb{b}_h)yt=softmax(Whyht+by)\pmb{y}^t = \text{softmax}(\pmb{W}_{hy}\pmb{h}^t + \pmb{b}_y)

参数说明

  • xt\pmb{x}^t:时间步tt的输入向量
  • ht\pmb{h}^t:时间步tt的隐状态向量
  • yt\pmb{y}^t:时间步tt的输出向量
  • Wxh\pmb{W}_{xh}:输入到隐状态的权重矩阵
  • Whh\pmb{W}_{hh}:隐状态到隐状态的循环权重矩阵
  • σ\sigma:激活函数(如tanh、ReLU) RNN时间展开结构图

PyTorch实现示例#

import torch
import torch.nn as nn
# 定义简单RNN模型
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
# RNN层:input_size -> hidden_size
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
# 输出层:hidden_size -> output_size
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x shape: (batch_size, seq_len, input_size)
# h0 shape: (1, batch_size, hidden_size)
h0 = torch.zeros(1, x.size(0), self.hidden_size)
# RNN前向传播
out, hn = self.rnn(x, h0) # out: (batch_size, seq_len, hidden_size)
# 取最后一个时间步的输出
out = self.fc(out[:, -1, :]) # (batch_size, output_size)
return out
# 使用示例
input_size = 10 # 输入特征维度
hidden_size = 20 # 隐状态维度
output_size = 5 # 输出维度
seq_len = 15 # 序列长度
batch_size = 32 # 批次大小
model = SimpleRNN(input_size, hidden_size, output_size)
x = torch.randn(batch_size, seq_len, input_size)
output = model(x) # shape: (32, 5)
print(f"输出形状: {output.shape}")

序列的向量表示#

独热编码 (One-hot Encoding)#

apple=[1,0,0,0,0]\text{apple} = [1, 0, 0, 0, 0]^\topbag=[0,1,0,0,0]\text{bag} = [0, 1, 0, 0, 0]^\top

缺点:维度灾难,无法表示语义相似性

词嵌入 (Word Embedding)#

  • 将单词映射到低维连续向量空间
  • 语义相似的单词在向量空间中距离相近

三、RNN的变体架构#

双向循环神经网络 (Bidirectional RNN)#

动机#

当前时刻的输出可能依赖于整个序列的信息(过去和未来)

结构#

同时运行两个RNN:

  1. 前向RNN:从左到右处理序列,隐状态记为ht\overrightarrow{\pmb{h}}^t
  2. 反向RNN:从右到左处理序列,隐状态记为ht\overleftarrow{\pmb{h}}^t

输出#

每个时间步的最终隐状态:

ht=[ht;ht]\pmb{h}^t = [\overrightarrow{\pmb{h}}^t; \overleftarrow{\pmb{h}}^t]

应用#

词性标注、命名实体识别等需要全局上下文的任务

深层循环神经网络#

深层RNN架构图

结构#

堆叠多个RNN层,低层的输出作为高层的输入

每层计算#

hlt=σ(Wxhlhl1t+Whhlhlt1+bhl)\pmb{h}_l^t = \sigma(\pmb{W}_{xh}^l\pmb{h}_{l-1}^t + \pmb{W}_{hh}^l\pmb{h}_l^{t-1} + \pmb{b}_h^l)
  • ll:层索引
  • hlt\pmb{h}_l^t:第ll层在时间tt的隐状态

Jordan网络#

Jordan网络结构图

与Elman网络的区别#

将输出层的值(而非隐藏层的值)存储到记忆元中

记忆更新#

ht=σ(Wxhxt+Wyhyt1+bh)\pmb{h}^t = \sigma(\pmb{W}_{xh}\pmb{x}^t + \pmb{W}_{yh}\pmb{y}^{t-1} + \pmb{b}_h)

稳定性考量#

  • Jordan Network的优势:输出层 yt1\pmb{y}^{t-1} 有明确的训练目标(Target)约束
  • Elman Network的特点:隐状态 ht1\pmb{h}^{t-1} 是网络自己学习出来的,没有直接的监督信号
  • 历史观点:早期研究认为Jordan Network可能更稳定,因为其反馈信号更”可控”
  • 现状:尽管有这个理论优势,实践中Elman Network(Simple RNN)仍然是主流选择

四、长短期记忆网络 (LSTM)#

LSTM的提出动机#

  • RNN的梯度问题:简单RNN存在梯度消失/爆炸问题
  • LSTM核心思想:通过门控机制有选择地保留、更新和输出信息

LSTM单元结构#

LSTM单元结构 包含三个门和一个记忆单元:

  1. 输入门:控制新信息的流入
  2. 遗忘门:控制旧信息的遗忘
  3. 输出门:控制信息的输出
  4. 记忆单元:长期记忆的存储 LSTM运算原理

LSTM的数学公式#

xt\pmb{x}^t为当前输入,ht1\pmb{h}^{t-1}为前一时刻隐状态,ct1\pmb{c}^{t-1}为前一时刻记忆单元值。

1. 计算门控信号和候选值#

ft=σ(Wf[ht1;xt]+bf)(遗忘门)\pmb{f}^t = \sigma(\pmb{W}_f[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_f) \quad \text{(遗忘门)}it=σ(Wi[ht1;xt]+bi)(输入门)\pmb{i}^t = \sigma(\pmb{W}_i[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_i) \quad \text{(输入门)}ot=σ(Wo[ht1;xt]+bo)(输出门)\pmb{o}^t = \sigma(\pmb{W}_o[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_o) \quad \text{(输出门)}c~t=tanh(Wc[ht1;xt]+bc)(候选记忆值)\tilde{\pmb{c}}^t = \tanh(\pmb{W}_c[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_c) \quad \text{(候选记忆值)}

2. 更新记忆单元#

ct=ftct1+itc~t\pmb{c}^t = \pmb{f}^t \odot \pmb{c}^{t-1} + \pmb{i}^t \odot \tilde{\pmb{c}}^t
  • \odot:逐元素相乘
  • 遗忘门决定保留多少旧记忆,输入门决定加入多少新信息

梯度的”高速公路”(Gradient Highway)

  • 关键机制:当遗忘门 ft1\pmb{f}^t \approx 1 时,ct1\pmb{c}^{t-1} 的信息几乎原封不动地ct\pmb{c}^t
  • 与Simple RNN的对比
    • Simple RNN:ht=σ(Whhht1+)\pmb{h}^t = \sigma(\pmb{W}_{hh}\pmb{h}^{t-1} + \dots),每次都要乘以权重矩阵 Whh\pmb{W}_{hh}
    • LSTM:ct=ftct1+\pmb{c}^t = \pmb{f}^t \odot \pmb{c}^{t-1} + \dots,通过加法传递信息
  • 为什么能解决梯度消失
    • 在反向传播时,梯度可以通过加法操作直接传回,不会像连续乘法那样指数衰减
    • 这就像在崎岖的误差表面上修建了一条”高速公路”,让梯度能够长距离传播而不衰减
    • 也称为**CEC(Constant Error Carousel,恒定误差传送带)**机制
  • 直观理解:加法保持梯度稳定,乘法容易导致梯度消失或爆炸

3. 计算当前隐状态#

ht=ottanh(ct)\pmb{h}^t = \pmb{o}^t \odot \tanh(\pmb{c}^t)

LSTM示例分析#

LSTM运算示例 考虑一个简化的LSTM:

  • 规则:
    • x2=1x_2=1时,将x1x_1的值写入记忆单元
    • x2=1x_2=-1时,重置记忆单元为0
    • x3=1x_3=1时,输出记忆单元的值
时间 输入(x1,x2,x3) 记忆单元c 输出
1 (3, 1, 0) 3 0
2 (4, 1, 0) 7 0
3 (2, 0, 0) 7 0
4 (1, 0, 1) 7 7

PyTorch实现示例#

import torch
import torch.nn as nn
# 定义LSTM模型
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super(LSTMModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# LSTM层
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
batch_first=True)
# 输出层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隐状态和记忆单元
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
# LSTM前向传播
# out: (batch_size, seq_len, hidden_size)
# hn, cn: (num_layers, batch_size, hidden_size)
out, (hn, cn) = self.lstm(x, (h0, c0))
# 取最后一个时间步的输出
out = self.fc(out[:, -1, :])
return out
# 使用示例:情感分析任务
input_size = 100 # 词嵌入维度
hidden_size = 128 # LSTM隐状态维度
num_layers = 2 # LSTM层数
output_size = 2 # 二分类(正面/负面)
seq_len = 50 # 句子长度
batch_size = 32
model = LSTMModel(input_size, hidden_size, num_layers, output_size)
x = torch.randn(batch_size, seq_len, input_size)
output = model(x) # shape: (32, 2)
print(f"输出形状: {output.shape}")

多层LSTM架构#

在实际应用中,我们经常使用多层(堆叠)LSTM来增强模型的表达能力:

  • 层次特征提取:低层LSTM学习基础特征,高层LSTM学习更抽象的特征
  • 增强表达能力:多层结构可以捕获更复杂的序列模式
  • 常用层数:通常使用2-4层,过深可能导致过拟合和训练困难

在PyTorch中,通过num_layers参数即可轻松实现多层LSTM(如上面代码示例中的num_layers=2)。

多层LSTM

如图所示,多层LSTM将前一层的输出序列作为下一层的输入序列,每一层都有自己独立的隐状态和记忆单元。第ll层在时间步tt的计算依赖于:

  • 前一层(第l1l-1层)在同一时间步tt的输出
  • 本层在前一时间步t1t-1的隐状态和记忆单元

这种堆叠结构使得网络能够学习到更深层次的序列表示,在复杂任务(如机器翻译、语音识别)中表现更优。

五、门控循环单元 (GRU)#

GRU的设计理念#

  • LSTM的简化版:将输入门和遗忘门合并为更新门
  • 两个门:重置门和更新门
  • 没有单独的记忆单元:隐状态ht\pmb{h}^t同时承担记忆和输出的功能

GRU的数学公式#

1. 计算门控信号#

zt=σ(Wz[ht1;xt]+bz)(更新门)\pmb{z}^t = \sigma(\pmb{W}_z[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_z) \quad \text{(更新门)}rt=σ(Wr[ht1;xt]+br)(重置门)\pmb{r}^t = \sigma(\pmb{W}_r[\pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_r) \quad \text{(重置门)}

2. 计算候选隐状态#

h~t=tanh(Wh[rtht1;xt]+bh)\tilde{\pmb{h}}^t = \tanh(\pmb{W}_h[\pmb{r}^t \odot \pmb{h}^{t-1}; \pmb{x}^t] + \pmb{b}_h)

3. 更新隐状态#

ht=(1zt)ht1+zth~t\pmb{h}^t = (1 - \pmb{z}^t) \odot \pmb{h}^{t-1} + \pmb{z}^t \odot \tilde{\pmb{h}}^t

GRU vs LSTM对比#

特性LSTMGRU
门数量3个(输入、遗忘、输出)2个(重置、更新)
参数数量较多(约RNN的4倍)较少(约RNN的3倍)
记忆单元有独立记忆单元无,隐状态同时负责记忆
训练速度较慢较快

PyTorch实现示例#

import torch
import torch.nn as nn
# 定义GRU模型
class GRUModel(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super(GRUModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# GRU层
self.gru = nn.GRU(input_size, hidden_size, num_layers,
batch_first=True)
# 输出层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隐状态(GRU不需要记忆单元c)
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
# GRU前向传播
out, hn = self.gru(x, h0) # out: (batch_size, seq_len, hidden_size)
# 取最后一个时间步的输出
out = self.fc(out[:, -1, :])
return out
# 使用示例
model = GRUModel(input_size=100, hidden_size=128,
num_layers=2, output_size=2)
x = torch.randn(32, 50, 100)
output = model(x) # shape: (32, 2)
print(f"输出形状: {output.shape}")

六、RNN的训练#

损失函数#

对于序列标注任务,总损失是各个时间步损失之和:

L=t=1TLtL = \sum_{t=1}^{T} L^tLt=iyitlny^it(交叉熵损失)L^t = -\sum_{i} y_i^t \ln \hat{y}_i^t \quad \text{(交叉熵损失)}

随时间反向传播 (BPTT)#

  • 原理:将RNN在时间上展开,视为深度前馈网络,应用标准反向传播
  • 梯度计算:需要计算损失对每个时间步参数的梯度,并沿时间反向累积

RNN的训练难题#

RNN训练困难的本质在于其崎岖的误差表面(Rugged Error Surface),而不仅仅是梯度数值的大小问题。

1. 梯度消失#

  • 在反向传播时,梯度需要连续乘以相同的权重矩阵
  • 如果权重矩阵的特征值小于1,梯度会指数衰减到0
  • 后果:难以学习长期依赖关系
  • 误差表面特征:极其平坦的区域,梯度几乎为0

2. 梯度爆炸#

  • 如果权重矩阵的特征值大于1,梯度会指数增长
  • 后果:训练不稳定,参数更新过大
  • 误差表面特征:陡峭的悬崖,梯度突然变得极大

3. 误差表面的崎岖性(核心问题)#

  • 形象比喻:RNN的误差表面就像一片平坦的沙漠中突然出现悬崖
    • 在平坦区域:无论怎么推球(更新参数),球几乎不动(梯度消失)
    • 遇到悬崖:轻轻一推,球就飞出去了(梯度爆炸)
  • 训练困境
    • 平坦区域梯度小,学习极慢,难以找到下降方向
    • 陡峭区域梯度大,一步就可能跳过最优解,甚至飞出有效范围
  • 为什么难训练:不是因为梯度小(这只是”软”问题),而是因为误差表面极度不规则,导致优化算法难以稳定前进

解决梯度问题的技巧#

RNN 训练过程中的学习曲线与裁剪技巧

1. 梯度裁剪(Gradient Clipping)#

if g>threshold:gthresholdgg\text{if } \|\pmb{g}\| > \text{threshold}: \quad \pmb{g} \leftarrow \frac{\text{threshold}}{\|\pmb{g}\|} \pmb{g}
  • 目的:专门用于解决梯度爆炸问题,而非梯度消失
  • 原理:当遇到误差表面的”悬崖”时,限制梯度的步长,防止参数更新过大而飞出有效范围
  • 效果:稳定训练过程,避免因单次更新过大导致的训练崩溃
  • 注意:梯度裁剪无法解决梯度消失问题(平坦区域的问题需要其他方法)

2. 使用LSTM/GRU#

  • LSTM的加法更新创建了梯度传播的”高速公路”
  • 缓解梯度消失问题

3. 更好的参数初始化#

  • 对循环权重矩阵使用单位矩阵初始化
  • 结合ReLU激活函数

七、RNN的应用类型#

多对一 (Many-to-One)#

情感分析示例

  • 输入:一个序列
  • 输出:一个标签或向量
  • 示例:情感分析、文档分类
  • 实现:通常取最后一个时间步的隐状态hT\pmb{h}^T作为序列表示

多对多:输入输出等长#

关键术语抽取

  • 输入:长度为NN的序列
  • 输出:长度为NN的序列
  • 示例:词性标注、命名实体识别
  • 实现:每个时间步都产生一个输出

结构化学习(Structured Learning)#

虽然RNN能够利用上下文信息,但其输出层(Softmax)在每个时间步通常是独立选择最大概率的标签。

潜在问题

  • 可能产生不合理的标签序列
  • 例如在BIO标注中:B-PER I-LOC O
    • I-LOC(地点的内部标签)紧跟在B-PER(人名的开始标签)后面
    • 这在逻辑上是不合理的(I标签应该跟随对应的B标签)

解决方案

  • CRF层(Conditional Random Field):在RNN输出层之上添加CRF层,对标签之间的转移关系进行建模
  • Viterbi算法:在所有可能的输出序列中寻找全局最优解,而非贪心地选择每步的局部最优
  • 效果:通过约束标签转移规则,确保输出序列在结构上是合理的

序列到序列 (Sequence-to-Sequence, Seq2Seq)#

语音识别示例

  • 输入:长度为NN的序列
  • 输出:长度为MM的序列(MNM \neq N
  • 示例:机器翻译、语音识别

编码器-解码器架构#

  1. 编码器:将输入序列编码为固定长度的上下文向量c\pmb{c}

    c=fencoder(x1,x2,...,xN)\pmb{c} = f_{\text{encoder}}(\pmb{x}^1, \pmb{x}^2, ..., \pmb{x}^N)

    通常取编码器最后一个时间步的隐状态:c=hN\pmb{c} = \pmb{h}_N

  2. 解码器:以c\pmb{c}为初始状态,逐步生成输出序列

    h0dec=c\pmb{h}_0^{\text{dec}} = \pmb{c} htdec=fdecoder(ht1dec,yt1)\pmb{h}_t^{\text{dec}} = f_{\text{decoder}}(\pmb{h}_{t-1}^{\text{dec}}, \pmb{y}_{t-1}) yt=softmax(Wyhtdec+by)\pmb{y}_t = \text{softmax}(\pmb{W}_y\pmb{h}_t^{\text{dec}} + \pmb{b}_y)
  3. 训练技巧

    • 教师强制:训练时,将真实的前一个词作为解码器输入
    • 束搜索:推断时,维护多个候选序列,选择概率最高的
    • 注意力机制:让解码器可以访问编码器的所有隐状态

PyTorch实现示例#

import torch
import torch.nn as nn
# 编码器
class Encoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers=1):
super(Encoder, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.embedding = nn.Embedding(input_size, hidden_size)
self.lstm = nn.LSTM(hidden_size, hidden_size, num_layers, batch_first=True)
def forward(self, x):
# x: (batch_size, seq_len)
embedded = self.embedding(x) # (batch_size, seq_len, hidden_size)
outputs, (hidden, cell) = self.lstm(embedded)
return hidden, cell # 返回最后的隐状态和记忆单元
# 解码器
class Decoder(nn.Module):
def __init__(self, output_size, hidden_size, num_layers=1):
super(Decoder, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.embedding = nn.Embedding(output_size, hidden_size)
self.lstm = nn.LSTM(hidden_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x, hidden, cell):
# x: (batch_size, 1) - 单个时间步
embedded = self.embedding(x) # (batch_size, 1, hidden_size)
output, (hidden, cell) = self.lstm(embedded, (hidden, cell))
prediction = self.fc(output.squeeze(1)) # (batch_size, output_size)
return prediction, hidden, cell
# Seq2Seq模型
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder):
super(Seq2Seq, self).__init__()
self.encoder = encoder
self.decoder = decoder
def forward(self, src, trg, teacher_forcing_ratio=0.5):
# src: (batch_size, src_len)
# trg: (batch_size, trg_len)
batch_size = src.shape[0]
trg_len = trg.shape[1]
trg_vocab_size = self.decoder.fc.out_features
# 存储解码器输出
outputs = torch.zeros(batch_size, trg_len, trg_vocab_size)
# 编码器处理输入序列
hidden, cell = self.encoder(src)
# 解码器的第一个输入(通常是<SOS>标记)
input = trg[:, 0].unsqueeze(1)
for t in range(1, trg_len):
output, hidden, cell = self.decoder(input, hidden, cell)
outputs[:, t, :] = output
# 教师强制:以一定概率使用真实标签作为下一个输入
teacher_force = torch.rand(1).item() < teacher_forcing_ratio
input = trg[:, t].unsqueeze(1) if teacher_force else output.argmax(1).unsqueeze(1)
return outputs
# 使用示例
input_vocab_size = 1000 # 源语言词汇表大小
output_vocab_size = 1000 # 目标语言词汇表大小
hidden_size = 256
num_layers = 2
encoder = Encoder(input_vocab_size, hidden_size, num_layers)
decoder = Decoder(output_vocab_size, hidden_size, num_layers)
model = Seq2Seq(encoder, decoder)
# 示例输入
src = torch.randint(0, input_vocab_size, (32, 10)) # (batch_size, src_len)
trg = torch.randint(0, output_vocab_size, (32, 15)) # (batch_size, trg_len)
output = model(src, trg) # shape: (32, 15, 1000)
print(f"输出形状: {output.shape}")

连接主义时序分类 (CTC)#

CTC 训练

  • 专门用于语音识别等任务:输入序列长度 >> 输出序列长度
  • 核心思想:允许模型输出扩展序列,包含重复标签和空白符
  • 折叠规则
    1. 合并重复的相同标签
    2. 移除空白符
  • 示例
    • 模型输出:"h null null i null s null"
    • 折叠后:"his"

八、高级主题与扩展#

注意力机制 (Attention Mechanism)#

  • 动机:Seq2Seq模型中,编码器的单个上下文向量可能信息不足
  • 核心思想:解码器的每个时间步可以”关注”编码器的不同部分
  • 计算步骤
    1. 计算注意力分数
    2. 计算注意力权重
    3. 计算上下文向量
    4. 解码器输入

序列到序列自编码器#

  • 目标:学习序列的稠密向量表示
  • 结构:编码器将输入序列编码为向量,解码器从该向量重构原始序列
  • 应用:无监督学习序列表示、文本生成、数据压缩

层次RNN#

  • 结构:在不同时间尺度上运行多个RNN
  • 应用:文档建模、视频理解、多尺度时间序列分析

递归神经网络(Recursive Neural Network)#

与循环神经网络的区别#

  • Recurrent NN(循环):处理时间序列(线性结构),按时间步依次处理输入
  • Recursive NN(递归):处理树状结构(Tree Structure),按树的层次结构处理输入

应用场景#

  • 句法分析(Syntactic Parsing):句子可以被解析为语法树
  • 情感分析:利用句子的语法结构进行情感判断
  • 语义组合:自底向上地组合词语和短语的语义

工作原理#

  1. 将句子解析为语法树(如:名词短语、动词短语等)
  2. 从叶子节点(单词)开始,逐层向上编码
  3. 每个父节点的表示由其子节点通过神经网络组合得到
  4. 最终根节点的表示包含了整个句子的语义信息

优势#

  • 对于符合语法逻辑的复杂长句,树状结构比线性扫描更能捕捉句子的层次关系
  • 可以更好地处理嵌套结构和长距离依赖

局限#

  • 需要预先进行句法分析,增加了计算复杂度
  • 对句法分析的质量依赖较大

九、实践建议#

架构选择指南#

  1. 简单任务/短序列:可以尝试简单RNN
  2. 长序列/长期依赖:优先选择LSTM或GRU
  3. 需要全局上下文:使用双向RNN
  4. 计算资源有限:考虑GRU(参数更少)
  5. 需要最佳性能:尝试LSTM并调整超参数

训练技巧#

  1. 梯度裁剪:几乎总是需要的,特别是对于RNN和LSTM
  2. Dropout:在RNN中应用需要小心,通常在时间步之间或层之间应用
  3. 批量归一化:可以应用在RNN的输入或隐状态上
  4. 学习率调度:使用学习率衰减或预热策略
  5. 早停:监控验证集损失,防止过拟合

超参数调优#

  • 隐状态维度:50-1000之间,取决于任务复杂度
  • 层数:1-4层,更深不一定更好
  • Dropout率:0.2-0.5
  • 学习率:0.001-0.1,配合学习率调度
  • 优化器:Adam通常是不错的选择

十、总结#

循环神经网络是处理序列数据的强大工具,其核心是通过循环连接和隐状态赋予网络记忆能力。然而,简单RNN存在梯度消失/爆炸问题,难以学习长期依赖。LSTM和GRU通过门控机制有效解决了这些问题,成为实际应用中的主流选择。

RNN及其变体已广泛应用于:

  • 自然语言处理:机器翻译、文本生成、情感分析
  • 语音处理:语音识别、语音合成
  • 时间序列分析:股票预测、气象预报

随着注意力机制和Transformer架构的发展,RNN在某些领域已被取代,但在许多序列建模任务中,RNN及其变体仍然是有效且常用的工具。


致谢#

本笔记在学习李宏毅老师的深度学习课程(LeeDL)时完成。感谢李宏毅老师深入浅出的讲解,让复杂的循环神经网络概念变得易于理解。课程中的精彩示例和直观解释为本文提供了重要的参考和启发。

相关资源:

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

循环神经网络
https://castorice.xin/posts/循环神经网络/
作者
castorice
发布于
2025-09-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时