学习笔记(循环神经网络RNN)

发布时间:2026/6/13 19:19:53

学习笔记(循环神经网络RNN) 文章目录一、循环神经网络概念二、RNN基本结构1.输入层Input Layer2.循环隐藏层Recurrent Hidden Layer3. 输出层Output Layer三、RNN数学原理四、LSTM长短时记忆网络五、使用 RNN 模型来处理序列数据并进行分类六、LSTM实现时间序列预测参考文章一、循环神经网络概念循环神经网络Recurrent Neural Networks, RNN是一类神经网络架构专门用于处理序列数据能够捕捉时间序列或有序数据的动态信息能够处理序列数据如文本、时间序列或音频。RNN 在自然语言处理NLP、语音识别、时间序列预测等任务中有着广泛的应用。RNN 的关键特性是其能够保持隐状态hidden state使得网络能够记住先前时间步的信息这对于处理序列数据至关重要。循环神经网络其核心特点是神经元之间存在循环连接使得网络能够利用内部状态隐藏状态来记忆先前的输入信息从而捕捉数据中的时间依赖关系它通过在每个时间步共享参数并递归地更新隐藏状态理论上可以处理任意长度的序列但传统RNN存在梯度消失或梯度爆炸问题因此实际应用中常采用LSTM、GRU等改进变体来增强长程记忆能力。二、RNN基本结构循环神经网络的结构核心在于循环连接与参数共享网络在每个时间步接收当前输入和前一时刻的隐藏状态通过相同的权重矩阵计算当前隐藏状态并输出这种链式结构使信息能在时间维度上传递形成记忆效果其典型结构包括输入层、循环隐藏层含自连接神经元和输出层隐藏层状态作为内部记忆存储历史信息而展开的等效结构则呈现为多个相同单元按时间步串联的深层网络形态。1.输入层Input Layer功能与作用1.接收序列数据中的单个时间步time step的输入向量通常表示为 xt。2.将外部输入转换为适合网络处理的维度但不执行非线性变换仅做线性映射或维度调整。3.对于不同模态的数据输入层处理方式各异文本需经过嵌入层Embedding将离散的词/字符转为稠密向量时间序列数据如股价、传感器数据可直接输入数值向量语音信号则通常先经过特征提取如MFCC再输入。2.循环隐藏层Recurrent Hidden Layer核心机制这是RNN区别于前馈网络如CNN、MLP的根本所在。隐藏层不仅接收当前输入还接收前一时刻自身的输出即隐藏状态形成循环反馈。隐藏层之间存在循环连接使得网络能够维护一个“记忆”状态这一状态包含了过去的信息。这使得RNN能够理解序列中的上下文信息。3. 输出层Output Layer功能定位将当前时刻的隐藏状态转换为任务所需的输出格式输出内容取决于具体应用场景。三、RNN数学原理给出一个典型的RNN:在图中有一条单向流动的信息流是从输入单元到达隐藏单元的与此同时另一条单向流动的信息流从隐藏单元到达输出单元。在某些情况下RNNs会打破后者的限制引导信息从输出单元返回隐藏单元这些被称为“Back Projections”并且隐藏层的输入还包括上一隐藏层的状态即隐藏层内的节点可以自连也可以互连。这实际上就是LSTM右侧为计算时便于理解记忆而产开的结构。简单说x为输入层o为输出层s为隐含层而t指第几次的计算V,W,U为权重其中计算第t次的隐含层状态时为下图为直观表达按照上图所示可知道RNN网络前向传播过程中满足下面的公式其代价函数可以是重构的误差也可以是交叉熵四、LSTM长短时记忆网络由于RNN模型如果需要实现长期记忆的话需要将当前的隐含态的计算与前n次的计算挂钩即:那样的话计算量会呈指数式增长导致模型训练的时间大幅增加因此RNN模型一般不直接用来进行长期记忆计算。另外传统RNN处理不了长期依赖问题这是个致命伤但LSTM解决了这个问题。做 LSTM 是一种 RNN 特殊的类型可以学习长期依赖信息。LSTM 由 Hochreiter Schmidhuber (1997) 提出并在近期被 Alex Graves 进行了改良和推广。对于很多问题LSTM 都取得相当巨大的成功并得到了广泛的使用。LSTM 通过刻意的设计来避免长期依赖问题。记住长期的信息在实践中是 LSTM 的默认行为而非需要付出很大代价才能获得的能力所有 RNN 都具有一种重复神经网络模块的链式的形式。在标准的RNN中这个重复的 模块只有一个非常简单的结构例如一个 tanh层。标准 RNN 中的重复模块包含单一的层。LSTM 同样是这样的结构但是重复的模块拥有一个不同的结构。不同于 单一神经网络层这里是有四个以一种非常特殊的方式进行交互。LSTM 中的重复模块包含四个交互的层。在上面的图例中每一条黑线传输着一整个向量从一个节点的输出到其他节点的输入。粉色的圈代表 pointwise 的操作诸如向量的和而黄色的矩阵就是学习到的神经网络层。合在一起的线表示向量的连接分开的线表示内容被复制然后分发到不同的位置。LSTM 的关键就是细胞状态cell水平线在图上方贯穿运行。细胞状态类似于传送带。直接在整个链上运行只有一些少量的线性交互。信息在上面流传保持不变会很容易。LSTM 有通过精心设计的称作为“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。他们包含一个 sigmoid 神经网络层和一个 pointwise 乘法操作。Sigmoid 层输出 0 到 1 之间的数值描述每个部分有多少量可以通过。0 代表“不许任何量通过”1 就指“允许任意量通过”LSTM中有3个控制门输入门输出门记忆门。1forget gate选择忘记过去某些信息2input gate记忆现在的某些信息3 将过去与现在的记忆进行合并4output gate输出公式总结五、使用 RNN 模型来处理序列数据并进行分类以下是一个简单的 PyTorch 实现例子使用 RNN 模型来处理序列数据并进行分类。定义 RNN 模型import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import numpy as class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleRNN, self).__init__() # 定义 RNN 层 self.rnn nn.RNN(input_size, hidden_size, batch_firstTrue) # 定义全连接层 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch_size, seq_len, input_size) out, _ self.rnn(x) # out: (batch_size, seq_len, hidden_size) # 取序列最后一个时间步的输出作为模型的输出 out out[:, -1, :] # (batch_size, hidden_size) out self.fc(out) # 全连接层 return out np创建训练数据为了训练 RNN我们生成一些随机的序列数据。这里的目标是将每个序列的最后一个值作为分类的目标。# 生成一些随机序列数据 num_samples 1000 seq_len 10 input_size 5 output_size 2 # 假设二分类问题 # 随机生成输入数据 (batch_size, seq_len, input_size) X torch.randn(num_samples, seq_len, input_size) # 随机生成目标标签 (batch_size, output_size) Y torch.randint(0, output_size, (num_samples,)) # 创建数据加载器 dataset TensorDataset(X, Y) train_loader DataLoader(dataset, batch_size32, shuffleTrue)定义损失函数与优化器# 模型实例化 model SimpleRNN(input_sizeinput_size, hidden_size64, output_sizeoutput_size) # 定义损失函数和优化器 criterion nn.CrossEntropyLoss() # 多分类交叉熵损失 optimizer optim.Adam(model.parameters(), lr0.001)训练模型num_epochs 10 for epoch in range(num_epochs): model.train() # 设置模型为训练模式 total_loss 0 correct 0 total 0 for inputs, labels in train_loader: # 前向传播 outputs model(inputs) loss criterion(outputs, labels) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() # 计算准确率 _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() accuracy 100 * correct / total print(fEpoch [{epoch1}/{num_epochs}], Loss: {total_loss / len(train_loader):.4f}, Accuracy: {accuracy:.2f}%)测试模型# 测试模型 model.eval() # 设置模型为评估模式 with torch.no_grad(): total 0 correct 0 for inputs, labels in train_loader: outputs model(inputs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() accuracy 100 * correct / total print(fTest Accuracy: {accuracy:.2f}%)可视化# 可视化损失 plt.plot(losses, labelTraining Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(RNN Training Loss Over Epochs) plt.legend() plt.show()结果输出模型在200轮训练中损失从约1.7快速收敛至接近0表明模型成功拟合了数据测试时输入hello预测输出为elloh与目标序列完全一致说明该简单字符映射任务已被模型记忆掌握。六、LSTM实现时间序列预测基于 PyTorch 构建了一个 LSTM 时间序列预测模型使用滑动窗口将正弦波数据切分为输入序列60步和目标序列10步通过两层 LSTM 网络提取时序特征并直接输出未来10步的预测值。模型定义import torch import torch.nn as nn import torch.optim as optim import numpy as np import matplotlib.pyplot as plt from torch.utils.data import Dataset, DataLoader # 设置中文字体 plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] False device torch.device(cuda if torch.cuda.is_available() else cpu) # 滑动窗口 Dataset class TimeSeriesDataset(Dataset): def __init__(self, series, input_len, output_len): self.data torch.tensor(series, dtypetorch.float32) self.input_len input_len self.output_len output_len def __len__(self): return len(self.data) - self.input_len - self.output_len 1 def __getitem__(self, idx): x self.data[idx : idx self.input_len] y self.data[idx self.input_len : idx self.input_len self.output_len] return x.unsqueeze(-1), y # 模型定义 class LSTMForecaster(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_len, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0, ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size, output_len) def forward(self, x): output, (h_n, c_n) self.lstm(x) last output[:, -1, :] last self.dropout(last) return self.fc(last)数据准备# 数据准备 t np.linspace(0, 200, 10000) series np.sin(t) 0.1 * np.random.randn(len(t)) INPUT_LEN 60 OUTPUT_LEN 10 split int(len(series) * 0.8) train_data series[:split] val_data series[split:] train_dataset TimeSeriesDataset(train_data, INPUT_LEN, OUTPUT_LEN) val_dataset TimeSeriesDataset(val_data, INPUT_LEN, OUTPUT_LEN) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse)模型训练# 模型训练 model LSTMForecaster(input_size1, hidden_size128, num_layers2, output_lenOUTPUT_LEN).to(device) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-3) def train_epoch_ts(model, loader, optimizer, criterion): model.train() total_loss 0.0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * x.size(0) return total_loss / len(loader.dataset) def eval_epoch_ts(model, loader, criterion): model.eval() total_loss 0.0 with torch.no_grad(): for x, y in loader: x, y x.to(device), y.to(device) pred model(x) total_loss criterion(pred, y).item() * x.size(0) return total_loss / len(loader.dataset) train_losses [] val_losses [] for epoch in range(1, 31): train_loss train_epoch_ts(model, train_loader, optimizer, criterion) val_loss eval_epoch_ts(model, val_loader, criterion) train_losses.append(train_loss) val_losses.append(val_loss) print(fEpoch {epoch:2d}/30 | Train MSE: {train_loss:.6f} | Val MSE: {val_loss:.6f})结果可视化# 可视化 fig, axes plt.subplots(2, 2, figsize(14, 10)) # 1. 训练曲线 ax1 axes[0, 0] ax1.plot(train_losses, labelTrain MSE, colorblue) ax1.plot(val_losses, labelVal MSE, colorred) ax1.set_xlabel(Epoch) ax1.set_ylabel(MSE Loss) ax1.set_title(Training Validation Loss) ax1.legend() ax1.grid(True, alpha0.3) # 2. 验证集单样本预测 vs 真实值 ax2 axes[0, 1] model.eval() with torch.no_grad(): # 取验证集第一个样本 x_sample, y_true val_dataset[0] x_input x_sample.unsqueeze(0).to(device) y_pred model(x_input).cpu().numpy().flatten() y_true y_true.numpy().flatten() # 输入序列 input_seq x_sample.numpy().flatten() input_t np.arange(len(input_seq)) pred_t np.arange(len(input_seq), len(input_seq) OUTPUT_LEN) ax2.plot(input_t, input_seq, labelInput (历史), colorgray, alpha0.7) ax2.plot(pred_t, y_true, labelTrue (真实), colorgreen, markero) ax2.plot(pred_t, y_pred, labelPredicted (预测), colorred, markerx, linestyle--) ax2.axvline(xlen(input_seq)-0.5, colorblack, linestyle:, alpha0.5) ax2.set_xlabel(Time Step) ax2.set_ylabel(Value) ax2.set_title(Single Sample: Input → True vs Predicted) ax2.legend() ax2.grid(True, alpha0.3) # 3. 验证集连续多步预测滚动预测 ax3 axes[1, 0] model.eval() with torch.no_grad(): # 取验证集前 500 个点做连续预测 n_show 500 true_vals val_data[:n_show] # 用模型做滚动预测 predictions [] # 先用真实数据的前 INPUT_LEN 步作为初始输入 current_input torch.tensor(val_data[:INPUT_LEN], dtypetorch.float32).unsqueeze(0).unsqueeze(-1).to(device) # 每隔 OUTPUT_LEN 步预测一次 pred_points [] pred_values [] for i in range(0, n_show - INPUT_LEN - OUTPUT_LEN, OUTPUT_LEN): x torch.tensor(val_data[i:iINPUT_LEN], dtypetorch.float32).unsqueeze(0).unsqueeze(-1).to(device) pred model(x).cpu().numpy().flatten() for j, p in enumerate(pred): pred_points.append(i INPUT_LEN j) pred_values.append(p) ax3.plot(np.arange(n_show), true_vals, labelTrue, colorgreen, alpha0.7) ax3.scatter(pred_points, pred_values, labelPredicted, colorred, s10, alpha0.8) ax3.set_xlabel(Time Step) ax3.set_ylabel(Value) ax3.set_title(Rolling Prediction on Validation Set) ax3.legend() ax3.grid(True, alpha0.3) # 4. 验证集预测误差分布 ax4 axes[1, 1] model.eval() all_errors [] with torch.no_grad(): for x, y in val_loader: x, y x.to(device), y.to(device) pred model(x) errors (pred - y).cpu().numpy().flatten() all_errors.extend(errors) ax4.hist(all_errors, bins50, colorsteelblue, edgecolorblack, alpha0.7) ax4.axvline(x0, colorred, linestyle--, labelZero Error) ax4.set_xlabel(Prediction Error) ax4.set_ylabel(Frequency) ax4.set_title(Prediction Error Distribution (Validation)) ax4.legend() ax4.grid(True, alpha0.3) plt.tight_layout() plt.savefig(/mnt/agents/output/lstm_forecast_visualization.png, dpi150, bbox_inchestight) plt.show() print(可视化已保存)结果输出模型在30轮训练后成功收敛训练 MSE 从 0.138 降至 0.013验证 MSE 从 0.025 降至 0.011验证损失始终低于训练损失且差距很小说明无过拟合但第26轮出现明显反弹训练损失突增至0.022提示学习率偏大导致优化不稳定改进反向为引入学习率衰减机制以平滑训练。参考文章https://blog.csdn.net/2401_85327249/article/details/142249393https://zhuanlan.zhihu.com/p/652712909https://zhuanlan.zhihu.com/p/123211148https://blog.csdn.net/mary19831/article/details/129570030https://jishuzhan.net/article/1977119352873877506https://www.runoob.com/pytorch/pytorch-recurrent-neural-network.html

相关新闻