当前位置: 首页 > news >正文

深度学习基本模块:RNN 循环神经网络

循环神经网络(RNN)是一种专门用于处理序列数据的神经网络架构。与处理空间数据的卷积神经网络(Conv2D)不同,RNN通过引入循环连接使网络具有"记忆"能力,能够利用之前的信息来影响当前的输出,非常适合处理音频波形、频谱。

一、RNN介绍

1.1 结构

  • 输入层:序列数据,通常为形状为(batch_size, seq_len, input_size)的张量
示例:音频频谱处理:将音频转换为频谱图后,seq_len对应时间帧数,input_size对应每个时间帧的频率维度(如梅尔频带数),梅尔频谱图特征形状为 (batch_size, 128, 100) 表示:
-128个时间帧(seq_len=128-每个时间帧有100个梅尔频带特征(input_size=100)原始波形处理:直接处理音频波形时,seq_len对应采样点数,input_size对应特征维度(如单声道为1,立体声为2),16kHz音频的2秒片段形状为 (batch_size, 32000, 1) 表示:
-32000个采样点(seq_len=32000-单声道音频(input_size=1
  • RNN层

    • 循环单元:包含一个或多个RNN单元,每个单元包含以下可学习参数:
      • 输入到隐藏的权重WxhW_{xh}Wxh,形状:(hidden_size, input_size)
      • 隐藏到隐藏的权重WhhW_{hh}Whh,形状:(hidden_size, hidden_size)
      • 偏置bhb_hbh,形状:(hidden_size,)
    • 隐藏状态hth_tht,形状:(batch_size, hidden_size):存储网络的状态信息,在时间步之间传递
  • 激活函数

    • 隐藏层激活:通常使用TanhReLUtanh将输出压缩到[-1,1]范围,有助于缓解梯度爆炸;ReLU计算高效,但可能导致梯度消失。
    • 输出层激活:根据任务选择(Softmax用于分类,线性激活用于回归)

1.2 参数

  • input_size:每个时间步输入的特征数量。对于音频频谱,通常是频率维度(如梅尔频带数)
  • hidden_size:隐藏状态的维度,决定RNN的记忆容量和表征能力
  • num_layers:堆叠的RNN层数,增加层数可提高模型复杂度但也会增加计算量
  • nonlinearity:激活函数选择,Tanh适合大多数情况,RELU在某些场景可能表现更好
  • bias:是否在计算中添加可学习的偏置项
  • batch_first:输入张量的维度顺序。True: (batch, seq, feature)False: (seq, batch, feature)
  • dropout:在多层RNN中应用dropout防止过拟合,0表示不使用dropout
  • bidirectional:是否使用双向RNN,True时会同时考虑前向和后向序列信息

1.3 输入输出维度

  • 输入数据维度(batch_size, seq_len, input_size)(当 batch_first=True 时)
  • 输出序列维度(batch_size, seq_len, hidden_size * num_directions)(当 batch_first=True 时)
  • 最终隐藏状态(num_layers * num_directions, batch_size, hidden_size)

1.4 计算过程

ht=tanh⁡(Wxh×xt+Whh×ht−1+bh)h_t = \tanh(W_{xh} \times x_t + W_{hh} \times h_{t-1} + b_h)ht=tanh(Wxh×xt+Whh×ht1+bh)

其中:

  • hth_tht:当前时间步的隐藏状态(也是该时间步的输出)
  • ht−1h_{t-1}ht1:上一时间步的隐藏状态
  • xtx_txt:当前时间步的输入
  • WxhW_{xh}Wxh:输入到隐藏的权重矩阵
  • WhhW_{hh}Whh:隐藏到隐藏的权重矩阵
  • bhb_hbh:偏置项
  • tanh⁡\tanhtanh:激活函数

对于多层RNN(num_layers > 1):
ht(l)=tanh⁡(Wxh(l)ht(l−1)+Whh(l)ht−1(l)+bh(l))h_t^{(l)} = \tanh(W_{xh}^{(l)} h_t^{(l-1)} + W_{hh}^{(l)} h_{t-1}^{(l)} + b_h^{(l)})ht(l)=tanh(Wxh(l)ht(l1)+Whh(l)ht1(l)+bh(l))

其中
ht(0)=xth_t^{(0)} = x_tht(0)=xt

1.5 计算过程可视化

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Circle, Arrow# 创建画布
fig, ax = plt.subplots(figsize=(12, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 5)
ax.axis('off')
plt.title('RNN Computation Process', fontsize=16, pad=20)# 颜色定义
input_color = '#FFD700'  # 金色
hidden_color = '#1E90FF'  # 道奇蓝
active_color = '#FF4500'  # 橙红色
arrow_color = '#8B0000'  # 深红色# 初始化节点
time_steps = 3
input_nodes = []
hidden_nodes = []
input_texts = []
hidden_texts = []# 初始隐藏状态
h_init = Circle((0.5, 2.5), 0.3, facecolor='lightgray', edgecolor='black')
ax.add_patch(h_init)
ax.text(0.5, 2.5, 'h_{-1}', ha='center', va='center', fontsize=10)# 创建节点
for t in range(time_steps):# 输入节点input_circle = Circle((2.5 + t * 2.5, 4), 0.3, facecolor=input_color, edgecolor='black', alpha=0.7)ax.add_patch(input_circle)input_nodes.append(input_circle)input_text = ax.text(2.5 + t * 2.5, 4, f'x_{t}', ha='center', va='center', fontsize=10)input_texts.append(input_text)# 隐藏状态节点hidden_circle = Circle((2.5 + t * 2.5, 2.5), 0.3, facecolor=hidden_color, edgecolor='black', alpha=0.7)ax.add_patch(hidden_circle)hidden_nodes.append(hidden_circle)hidden_text = ax.text(2.5 + t * 2.5, 2.5, f'h_{t}', ha='center', va='center', fontsize=10)hidden_texts.append(hidden_text)# 时间步标签ax.text(2.5 + t * 2.5, 4.8, f'Time Step {t}', ha='center', fontsize=10)# 绘制连接线
arrows = []
arrow_labels = []# 输入到隐藏的连接
for t in range(time_steps):arrow = Arrow(2.5 + t * 2.5, 3.7, 0, -0.9, width=0.1, color='gray', alpha=0.3)ax.add_patch(arrow)arrows.append(arrow)label = ax.text(2.7 + t * 2.5, 3.2, '$W_{xh}$', fontsize=10, alpha=0.3)arrow_labels.append(label)# 隐藏到隐藏的连接
for t in range(time_steps):if t == 0:# 初始隐藏状态到第一个隐藏状态arrow = Arrow(0.8, 2.5, 1.5, 0, width=0.1, color='gray', alpha=0.3)ax.add_patch(arrow)arrows.append(arrow)label = ax.text(1.5, 2.7, '$W_{hh}$', fontsize=10, alpha=0.3)arrow_labels.append(label)else:# 隐藏状态之间的连接arrow = Arrow(2.5 + (t - 1) * 2.5, 2.5, 2.5, 0, width=0.1, color='gray', alpha=0.3)ax.add_patch(arrow)arrows.append(arrow)label = ax.text(2.5 + (t - 1) * 2.5 + 1.25, 2.7, '$W_{hh}$', fontsize=10, alpha=0.3)arrow_labels.append(label)# 添加公式
formula_text = ax.text(5, 1, '', fontsize=14, ha='center')# 动画更新函数
def update(frame):# 重置所有颜色for node in input_nodes + hidden_nodes:node.set_alpha(0.7)if node.get_facecolor() != active_color:node.set_facecolor(input_color if node in input_nodes else hidden_color)for arrow in arrows:arrow.set_alpha(0.3)arrow.set_color('gray')for label in arrow_labels:label.set_alpha(0.3)# 根据帧数更新if frame == 0:# 初始状态formula_text.set_text('Initialization: $h_{-1} = 0$')h_init.set_facecolor(active_color)h_init.set_alpha(1.0)elif frame <= time_steps:t = frame - 1# 激活当前输入input_nodes[t].set_facecolor(active_color)input_nodes[t].set_alpha(1.0)# 激活输入到隐藏的连接arrows[t].set_alpha(1.0)arrows[t].set_color(arrow_color)arrow_labels[t].set_alpha(1.0)# 激活隐藏状态hidden_nodes[t].set_facecolor(active_color)hidden_nodes[t].set_alpha(1.0)# 激活隐藏到隐藏的连接if t == 0:arrows[time_steps].set_alpha(1.0)arrows[time_steps].set_color(arrow_color)arrow_labels[time_steps].set_alpha(1.0)else:arrows[time_steps + t].set_alpha(1.0)arrows[time_steps + t].set_color(arrow_color)arrow_labels[time_steps + t].set_alpha(1.0)# 显示公式formula_text.set_text(f'Compute $h_{t}$: $h_{t} = \\tanh(W_{{xh}} x_{t} + W_{{hh}} h_{t - 1} + b_h)$')return input_nodes + hidden_nodes + arrows + arrow_labels + [formula_text, h_init]# 创建动画
animation = FuncAnimation(fig, update, frames=range(time_steps + 1),interval=1500, blit=True)plt.tight_layout()
animation.save('rnn_core_animation.gif', writer='pillow', fps=1, dpi=100)
plt.show()

在这里插入图片描述

二、代码示例

通过两层RNN处理一段音频频谱,打印每层的输出形状、参数形状,并可视化特征图。

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import librosa
import numpy as np# 定义 RNN 模型
class RNNModel(nn.Module):def __init__(self, input_size):super(RNNModel, self).__init__()self.rnn1 = nn.RNN(input_size, 128, batch_first=True)self.rnn2 = nn.RNN(128, 64, batch_first=True)def forward(self, x):h_out1, _ = self.rnn1(x)h_out2, _ = self.rnn2(h_out1)return h_out1, h_out2  # 返回两层的输出# 读取音频文件并处理
file_path = 'test.wav'
waveform, sample_rate = librosa.load(file_path, sr=16000, mono=True)# 选取 3 秒的数据
start_sample = int(1.5 * sample_rate)
end_sample = int(4.5 * sample_rate)
audio_segment = waveform[start_sample:end_sample]# 转换为频谱
n_fft = 512
hop_length = 256
spectrogram = librosa.stft(audio_segment, n_fft=n_fft, hop_length=hop_length)
spectrogram_db = librosa.amplitude_to_db(np.abs(spectrogram))
spectrogram_tensor = torch.tensor(spectrogram_db, dtype=torch.float32).unsqueeze(0)
spectrogram_tensor = spectrogram_tensor.permute(0, 2, 1)  # 调整为 (batch_size, seq_len, input_size)
print(f"Spectrogram tensor shape: {spectrogram_tensor.shape}")# 创建 RNN 模型实例
input_size = spectrogram_tensor.shape[2]
model = RNNModel(input_size)# 前向传播
rnn_output1, rnn_output2 = model(spectrogram_tensor)
print(f"RNN Layer 1 output shape: {rnn_output1.shape}")
print(f"RNN Layer 2 output shape: {rnn_output2.shape}")# 打印每层的参数形状
print(f"RNN Layer 1 weights shape: {model.rnn1.weight_ih_l0.shape}")
print(f"RNN Layer 1 hidden weights shape: {model.rnn1.weight_hh_l0.shape}")
print(f"RNN Layer 1 bias shape: {model.rnn1.bias_ih_l0.shape}")print(f"RNN Layer 2 weights shape: {model.rnn2.weight_ih_l0.shape}")
print(f"RNN Layer 2 hidden weights shape: {model.rnn2.weight_hh_l0.shape}")
print(f"RNN Layer 2 bias shape: {model.rnn2.bias_ih_l0.shape}")# 可视化原始频谱
plt.figure(figsize=(10, 4))
plt.imshow(spectrogram_db, aspect='auto', origin='lower', cmap='inferno')
plt.title("Original Spectrogram")
plt.xlabel("Time Frames")
plt.ylabel("Frequency Bins")
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()# 可视化 RNN 输出的特征图
plt.figure(figsize=(10, 4))# 绘制第一层 RNN 输出的特征图
plt.subplot(2, 1, 1)
plt.imshow(rnn_output1[0].detach().numpy().T, aspect='auto', origin='lower', cmap='inferno')  # 转置
plt.title("RNN Layer 1 Output Feature Map")
plt.xlabel("Time Steps")
plt.ylabel("Hidden State Dimensions")
plt.colorbar(label='Hidden State Value')# 绘制第二层 RNN 输出的特征图
plt.subplot(2, 1, 2)
plt.imshow(rnn_output2[0].detach().numpy().T, aspect='auto', origin='lower', cmap='inferno')  # 转置
plt.title("RNN Layer 2 Output Feature Map")
plt.xlabel("Time Steps")
plt.ylabel("Hidden State Dimensions")
plt.colorbar(label='Hidden State Value')plt.tight_layout()
plt.show()
Spectrogram tensor shape: torch.Size([1, 188, 257])
RNN Layer 1 output shape: torch.Size([1, 188, 128])
RNN Layer 2 output shape: torch.Size([1, 188, 64])
RNN Layer 1 weights shape: torch.Size([128, 257])
RNN Layer 1 hidden weights shape: torch.Size([128, 128])
RNN Layer 1 bias shape: torch.Size([128])
RNN Layer 2 weights shape: torch.Size([64, 128])
RNN Layer 2 hidden weights shape: torch.Size([64, 64])
RNN Layer 2 bias shape: torch.Size([64])

在这里插入图片描述
在这里插入图片描述

三、RNN的梯度消失与长期依赖问题

循环神经网络(RNN)在处理序列数据时面临两个核心问题:梯度消失问题和长期依赖问题。这些问题的根源在于RNN的结构和训练机制。

3.1 梯度消失问题

RNN通过时间反向传播(BPTT)算法进行训练,梯度需要沿着时间步反向传播。当序列较长时,梯度在反向传播过程中会指数级地减小或增大。

考虑RNN的计算公式:
ht=tanh⁡(Wxhxt+Whhht−1+bh)h_t = \tanh(W_{xh}x_t + W_{hh}h_{t-1} + b_h)ht=tanh(Wxhxt+Whhht1+bh)

在反向传播时,需要计算损失函数LLL对参数WhhW_{hh}Whh的梯度:
∂L∂Whh=∑t=1T∂L∂hT∂hT∂ht∂ht∂Whh\frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^T \frac{\partial L}{\partial h_T} \frac{\partial h_T}{\partial h_t} \frac{\partial h_t}{\partial W_{hh}}WhhL=t=1ThTLhthTWhhht

关键项是∂hT∂ht\frac{\partial h_T}{\partial h_t}hthT,它可以通过链式法则展开:
∂hT∂ht=∏k=tT−1∂hk+1∂hk=∏k=tT−1Whh⊤⋅diag(tanh⁡′(zk))\frac{\partial h_T}{\partial h_t} = \prod_{k=t}^{T-1} \frac{\partial h_{k+1}}{\partial h_k} = \prod_{k=t}^{T-1} W_{hh}^\top \cdot \text{diag}(\tanh'(z_k))hthT=k=tT1hkhk+1=k=tT1Whhdiag(tanh(zk))

其中
zk=Wxhxk+Whhhk−1+bhz_k = W_{xh}x_k + W_{hh}h_{k-1} + b_hzk=Wxhxk+Whhhk1+bh

由于tanh⁡\tanhtanh的导数tanh⁡′(z)=1−tanh⁡2(z)\tanh'(z) = 1 - \tanh^2(z)tanh(z)=1tanh2(z)的值域为(0,1](0, 1](0,1],且WhhW_{hh}Whh通常初始化为小随机数,这个连乘积会指数级衰减:
∣∏k=tT−1∂hk+1∂hk∣≤∣Whh∣T−t⋅(max⁡tanh⁡′)T−t\left| \prod_{k=t}^{T-1} \frac{\partial h_{k+1}}{\partial h_k} \right| \leq \left| W_{hh} \right|^{T-t} \cdot (\max \tanh')^{T-t}k=tT1hkhk+1WhhTt(maxtanh)Tt
T−tT-tTt很大时,这个值趋近于0,导致早期时间步的梯度消失。

影响

  • 早期时间步的参数无法有效更新:网络难以学习长序列中早期时间步的重要信息
  • 训练过程缓慢且不稳定:梯度太小导致参数更新幅度极小
  • 模型无法捕捉长期模式:只能记住短期信息,难以学习长序列中的依赖关系

3.2 长期依赖问题

即使没有梯度消失问题,RNN也难以有效利用序列中相距较远的信息。这是因为隐藏状态的表示能力有限,信息在多次变换中逐渐"稀释"。

考虑信息从时间步t传递到时间步T的过程:
hT=f(hT−1,xT)=f(f(hT−2,xT−1),xT)=⋯=f(⋯f(ht,xt+1)⋯ ,xT)h_T = f(h_{T-1}, x_T) = f(f(h_{T-2}, x_{T-1}), x_T) = \cdots = f(\cdots f(h_t, x_{t+1}) \cdots, x_T)hT=f(hT1,xT)=f(f(hT2,xT1),xT)==f(f(ht,xt+1),xT)

每次变换fff都会对信息进行非线性转换和压缩,经过多次变换后,早期信息hth_ththTh_ThT的影响变得微弱且难以区分。

示例在语言建模中,考虑句子:"The clouds in the sky are [...] color." 要预测最后一个词"blue",需要记住开头的"clouds"信息。标准RNN很难保持这种长距离依赖。

3.3 梯度爆炸问题

与梯度消失相反,当权重矩阵WhhW_{hh}Whh的特征值大于 1 时,梯度在反向传播过程中会指数级增长。这种现象被称为梯度爆炸。

在 RNN 的反向传播过程中,梯度的计算可以表示为:
∣∏k=tT−1∂hk+1∂hk∣≤∣Whh∣T−t\left| \prod_{k=t}^{T-1} \frac{\partial h_{k+1}}{\partial h_k} \right| \leq \left| W_{hh} \right|^{T-t}k=tT1hkhk+1WhhTt

如果∥Whh∥>1\| W_{hh} \| > 1Whh>1,则梯度的范数会指数增长,导致以下问题:

  • 参数更新过大:由于梯度过大,参数更新可能会超出合理范围,导致模型无法收敛。
  • 训练不稳定:模型的训练过程可能变得不稳定,导致损失函数波动较大。
  • 可能产生 NaN 值:在极端情况下,过大的参数更新可能导致数值溢出,从而产生 NaN 值。

为了解决梯度爆炸问题,可以采取以下措施:

  • 梯度裁剪:通过限制梯度的大小,确保参数更新不会过大。常用的方法是将梯度的 L2 范数限制在一个预设的阈值之内。例如,如果梯度的范数超过阈值,则按比例缩放梯度。
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
    
  • 权重正则化:通过约束权重矩阵的范数,防止权重过大。常见的正则化方法包括 L2 正则化(权重衰减)和 L1 正则化。

文章转载自:

http://MOXkIjPu.nqqLt.cn
http://49aqN8Uu.nqqLt.cn
http://KX19IEI4.nqqLt.cn
http://UTGgFycN.nqqLt.cn
http://680UouVz.nqqLt.cn
http://2RqsKBiV.nqqLt.cn
http://4LYb5Evj.nqqLt.cn
http://T67jhAq5.nqqLt.cn
http://d8pul3Ax.nqqLt.cn
http://WKLQUdM3.nqqLt.cn
http://oHGHNgTa.nqqLt.cn
http://PZnepVN9.nqqLt.cn
http://MmOO1Hy0.nqqLt.cn
http://3kKPGacS.nqqLt.cn
http://AQOohUjI.nqqLt.cn
http://IfORZ4pv.nqqLt.cn
http://M7JJh9Li.nqqLt.cn
http://hTTv6R7G.nqqLt.cn
http://O6rE3ZeJ.nqqLt.cn
http://peOdDxt4.nqqLt.cn
http://raE00L6H.nqqLt.cn
http://e25fj1Kb.nqqLt.cn
http://HDpzRHvd.nqqLt.cn
http://MSjmoCnz.nqqLt.cn
http://frK85A7X.nqqLt.cn
http://3pni0BFE.nqqLt.cn
http://0NYoR5eM.nqqLt.cn
http://g1Jbbrjd.nqqLt.cn
http://GenA1ae8.nqqLt.cn
http://OiImi4AI.nqqLt.cn
http://www.dtcms.com/a/387112.html

相关文章:

  • 【深度学习】PixelShuffle处理操作
  • 10.1 - 遗传算法(旅行商问题C#求解)
  • Java 集合入门:从基础到实战的完整知识指南
  • 《过山车大亨3 完整版》PSXbox版下月推出 预告片赏
  • P1107题解
  • 多目标数据关联算法MATLAB实现
  • 战略推理AI Agents:组装LLM+因果推断+SHAP
  • 【CVPR 2016】基于高效亚像素卷积神经网络的实时单幅图像与视频超分辨率
  • 基于STM32的LED实战 -- 流水灯、呼吸灯、流水呼吸灯
  • 【数据结构】——队列,栈(基于链表或数组实现)
  • 任天堂官网更新!“任亏券”不支持兑换NS2专用游戏
  • 大模型数据整理器打包及填充、Flash Attention 2解析(97)
  • 48v转12v芯片48v转5v电源芯片AH7691D
  • Oracle Database 23ai 内置 SQL 防火墙启用
  • MySQL 31 误删数据怎么办?
  • 微前端面试题及详细答案 88道(09-18)-- 核心原理与实现方式
  • VBA技术资料MF362:将窗体控件添加到字典
  • 【Leetcode】高频SQL基础题--1321.餐馆营业额变化增长
  • Redis 中 Intset 的内存结构与存储机制详解
  • uniapp打包前端项目
  • cka解题思路1.32-3
  • 如何解决模型的过拟合问题?
  • 2025牛客周赛108场e题
  • 【课堂笔记】复变函数-2
  • 25、优化算法与正则化技术:深度学习的调优艺术
  • qt QCategoryAxis详解
  • 云游戏时代,游戏盾如何保障新型业务的流畅体验
  • 【Block总结】LRSA,用于语义分割的低分辨率自注意力|TPAMI 2025
  • PY32MD310单片机介绍 电机控制专用,内置三相半桥栅极驱动器
  • Ubuntu服务器挖矿病毒清理