一文读懂循环神经网络(RNN)—序列模型+损失函数的详解

目录

循环神经网络(RNN):定义与作用

1.什么是循环神经网络(RNN)?

2.RNN 的核心作用

3.典型应用场景

自回归模型(Autoregressive Model, AR)

1. 定义与核心假设

2. 应用场景

3. 特点

马尔可夫模型(Markov Model)

1. 定义与核心假设

2. 应用场景

3. 特点

因果关系(Causality)

1. 定义与核心思想

2. 与相关性的区别

3. 应用场景

4. 特点

自回归模型、马尔可夫模型、及因果关系之间关系

1.区别

2.联系

损失函数的作用

1. 衡量预测误差(让模型知道 “错了多少”)

2. 指导模型参数优化(让模型知道 “如何改正”)

3. 定义学习目标(让模型明确 “学什么”)

4. 评估模型泛化能力

完整代码

实验结果

1.真实数据图

2.单步预测对比图

3.多步预测对比图

4.不同步数的预测对比图


循环神经网络(RNN):定义与作用

1.什么是循环神经网络(RNN)?

循环神经网络(Recurrent Neural Network, RNN)是一种专门处理序列数据的神经网络模型,其核心特点是引入 “记忆” 机制—— 网络在处理当前输入时,会结合之前的输入信息(即 “历史状态”),从而捕捉序列数据中的时序依赖关系

与传统的前馈神经网络(如 CNN、MLP)不同,RNN 的结构中存在循环连接隐藏层的输出不仅取决于当前输入,还取决于上一时刻的隐藏状态。这种设计使其天然适合处理具有时序性的数据(如文本、语音、时间序列等)。

简单来说,RNN 可以理解为 “同一个神经网络模块在时间维度上重复执行”,每一步的输出都会传递到下一步作为 “记忆”,形成对序列的动态建模。

2.RNN 的核心作用

RNN 的设计初衷是解决序列数据的建模问题,其核心作用包括:

  1. 捕捉时序依赖关系
    序列数据的关键特征是 “上下文关联”(如文本中前一个词影响后一个词,股票价格受前几日走势影响)。RNN 通过循环结构将历史信息编码到隐藏状态中,使模型能利用上下文信息进行预测或决策。
    例如:在文本生成任务中,RNN 可以根据前几个词预测下一个词;在股票预测中,可根据过去 30 天的价格预测明天的走势。

  2. 处理变长序列输入
    前馈神经网络要求输入长度固定(如 MLP 需要固定维度的特征),而 RNN 能处理长度不固定的序列(如一句话可以是 5 个词,也可以是 10 个词)。通过在时间维度上迭代,无论序列多长,RNN 都能逐 step 处理。

  3. 支持序列到序列(Sequence-to-Sequence)任务
    RNN 可灵活处理 “输入序列” 与 “输出序列” 长度不同的场景,例如:

    • 机器翻译(输入是中文句子,输出是英文句子,长度可能不同);
    • 语音识别(输入是音频序列,输出是文本序列);
    • 文本摘要(输入是长文本,输出是短摘要)。
3.典型应用场景

RNN 及其改进模型(如 LSTM、GRU)在以下领域有广泛应用:

  • 自然语言处理(NLP):文本分类、情感分析、机器翻译、命名实体识别、文本生成(如写诗、写代码)等。
  • 时间序列预测:股票价格预测、天气预测、电力负荷预测、设备故障预警等。
  • 语音处理:语音识别、语音合成、说话人识别等。
  • 视频分析:动作识别、视频字幕生成(输入是视频帧序列,输出是文字)等。

自回归模型(Autoregressive Model, AR)

自回归模型是序列建模中最基础的方法之一,核心思想是序列过去的观测值预测未来的值

1. 定义与核心假设
  • 定义:自回归模型假设序列在时刻t的值X_t可以由其前p个时刻的值(X_{t-1}, X_{t-2}, ..., X_{t-p})线性(或非线性)表示。 例如,p阶自回归模型(AR (p))的数学形式为:X_t = c + \phi_1 X_{t-1} + \phi_2 X_{t-2} + ... + \phi_p X_{t-p} + \varepsilon_t

        其中,c是常数项,\phi_1,...,\phi_p是模型参数,\varepsilon_t是随机误差项(通常假设为白噪声)。

  • 核心假设序列的未来值仅依赖于过去固定长度的历史值,且这种依赖关系是稳定的(如平稳性假设)。

2. 应用场景
  • 时间序列预测:如股票价格、气温、GDP 增速等连续型序列的短期预测。
  • 信号处理:如语音信号的建模与生成。
3. 特点
  • 优点:模型简单、可解释性强,适合捕捉序列的短期相关性。
  • 缺点:需要预先确定阶数p(选择不当会导致欠拟合或过拟合);无法建模长期依赖(p过大会导致参数爆炸);仅捕捉相关性,不区分因果。

马尔可夫模型(Markov Model)

马尔可夫模型以 “马尔可夫性质” 为核心,简化了序列的依赖关系,重点关注状态的转移规律

1. 定义与核心假设
  • 马尔可夫性质:未来的状态只依赖于当前状态,而与 “当前状态之前的历史状态” 无关。用条件概率表示为:P(X_t | X_{t-1}, X_{t-2}, ..., X_1) = P(X_t | X_{t-1})这一性质也称为 “无记忆性”。

  • 扩展

    • 一阶马尔可夫模型:依赖当前状态(如上述公式)。
    • 高阶马尔可夫模型:依赖前k个状态(P(X_t | X_{t-1}, ..., X_{t-k})),但本质仍是对历史依赖的简化。
    • 常见形式:马尔可夫链(离散状态)、隐马尔可夫模型(HMM,状态不可观测)等。
2. 应用场景
  • 自然语言处理:如词性标注(每个词的词性仅依赖前一个词的词性)。
  • 语音识别:HMM 用于建模语音信号的状态转移。
  • 状态预测:如用户行为序列(浏览→加购→购买)的状态转移预测。
3. 特点
  • 优点:通过简化依赖关系(“仅依赖当前”)降低了计算复杂度,适合建模离散状态的序列。
  • 缺点:马尔可夫性质是强假设,现实中很多序列的未来状态依赖更远的历史(如长文本的语义依赖);同样仅捕捉相关性,不涉及因果。

因果关系(Causality)

因果关系关注序列中变量之间的 “因→果” 机制 ,即一个变量的变化是否 “导致” 另一个变量的变化,而非单纯的相关性

1. 定义与核心思想
  • 定义:若改变变量A(干预)会导致变量B的分布发生变化,且这种变化无法通过其他变量解释,则称A是B的原因(A \to B)。
  • 在序列中:因果关系通常具有时间方向性(原因在前,结果在后),例如 “降雨(t时刻)导致销量下降(t+1时刻)”。
2. 与相关性的区别
  • 相关性:变量之间的统计关联(如 “冰淇淋销量” 与 “溺水事故” 正相关),但可能是巧合或第三方变量(如 “高温”)导致的。
  • 因果关系:需要通过干预、反事实推理等验证(如控制高温,观察冰淇淋销量是否影响溺水事故)。
3. 应用场景
  • 政策评估:如 “补贴政策(因)是否提高了就业率(果)”。
  • 序列干预:如 “调整广告投放(t时刻)是否提升未来销量(\(t+1\)时刻)”。
4. 特点
  • 优点:揭示变量间的本质依赖,可用于指导干预(如通过改变 “因” 来控制 “果”)。
  • 缺点:难以直接从观测数据中推断(需实验或复杂的因果推断方法,如 Do-Calculus、工具变量等);建模复杂,计算成本高。

自回归模型、马尔可夫模型、及因果关系之间关系

1.区别
维度自回归模型(AR)马尔可夫模型因果关系
核心假设依赖前p个历史值依赖当前状态(无记忆性)原因导致结果(时间方向性 + 干预依赖性)
历史依赖依赖固定长度的过去(p阶)依赖最近的k个状态(通常\(k=1\))依赖真实的 “因→果” 机制(无固定长度限制)
目标预测未来值(基于相关性)建模状态转移(基于相关性)揭示 “因→果” 机制(指导干预)
是否涉及因果否(仅捕捉相关性)否(仅捕捉状态关联)是(核心是因果机制)
计算复杂度中等(依赖p)低(简化依赖关系)高(需验证干预效果)
典型应用股票价格预测、气温预测词性标注、用户行为状态转移政策评估、干预效果分析
2.联系
  1. 自回归模型与马尔可夫模型的关联: 一阶自回归模型(AR (1))可看作一阶马尔可夫模型的特例(依赖前 1 个时刻的值);高阶马尔可夫模型可看作自回归模型的简化(仅保留最近k个状态)。

  2. 与因果关系的关联: 自回归模型和马尔可夫模型捕捉的相关性可能包含因果关系(如 “过去的收入→当前的消费”),但也可能只是虚假关联;而因果关系是对这种关联的 “本质解释”,是更底层的规律。

损失函数的作用

损失函数(Loss Function) 是模型训练的核心组件,其核心作用是量化模型预测结果与真实标签之间的差异,为模型参数的优化提供 “方向”。具体来说,损失函数的作用可以从以下几个方面详细理解:

1. 衡量预测误差(让模型知道 “错了多少”

损失函数最基本的功能是计算 “模型预测值” 与 “真实值” 之间的差距。

  • 对于分类问题(如识别图片中的动物),可能用交叉熵损失衡量类别预测的偏差;
  • 对于回归问题(如预测房价、温度),可能用均方误差(MSE) 衡量数值预测的偏差。
    示例:若模型预测房价为 500 万,真实房价为 550 万,MSE 损失会计算两者差值的平方(50²=2500),数值越大说明误差越大。

2. 指导模型参数优化(让模型知道 “如何改正”

损失函数的数值大小直接反映模型的 “好坏”:损失越小,模型预测越接近真实情况。
在训练中,算法(如梯度下降)通过计算损失函数对模型参数的偏导数(梯度),指导参数向 “降低损失” 的方向更新。

  • 若损失函数对某个参数的梯度为正,说明增大该参数会导致损失上升,因此需要减小参数
  • 若梯度为负,则需要增大参数
    类比:损失函数如同 “地形图”,模型参数是当前位置,梯度是 “下坡方向”,训练过程就是沿着梯度找到 “山谷”(损失最小点)。

3. 定义学习目标(让模型明确 “学什么”

不同的损失函数对应不同的学习目标,选择合适的损失函数是解决问题的关键:

  • 若任务重视减少 “极端误差”(如预测股票波动),可使用平均绝对误差(MAE),它对异常值更稳健;
  • 若任务需要模型对正确预测更 “自信”(如疾病诊断),交叉熵损失会惩罚 “模棱两可” 的预测。
    反例:若用 MSE 训练分类模型,可能导致对错误分类的惩罚不够,模型性能下降。

4. 评估模型泛化能力

训练过程中,除了训练集的损失,通常还会监控验证集损失

  • 若训练损失下降但验证损失上升,说明模型可能过拟合(只记住训练数据,泛化能力差);
  • 若两者都很高且下降缓慢,说明模型可能欠拟合(能力不足,无法捕捉数据规律)。
    通过观察损失变化,可调整模型结构(如增加层数)或训练策略(如正则化)。

完整代码

"""
文件名: 8.1 时间序列预测(基于多层感知机)
作者: 墨尘
日期: 2025/7/14
项目名: dl_env
备注: 实现基于MLP的时间序列预测,包括单步预测和多步预测,展示滞后特征的使用及误差累积现象
"""

import torch
from torch import nn
from d2l import torch as d2l  # 提供数据加载、绘图等工具
# 手动显示图像相关库
import matplotlib.pyplot as plt  # 绘图库
import matplotlib.text as text  # 用于修改文本绘制(解决符号显示问题)


# -------------------------- 核心解决方案:解决文本显示问题 --------------------------
def replace_minus(s):
    """
    解决Matplotlib中Unicode减号(U+2212)显示异常的问题
    参数:
        s: 待处理的字符串或其他对象
    返回:
        处理后的字符串(替换减号)或原始对象
    """
    if isinstance(s, str):  # 仅处理字符串类型
        return s.replace('\u2212', '-')  # 将特殊减号替换为普通减号
    return s  # 非字符串直接返回

# 重写matplotlib的Text类的set_text方法,全局解决减号显示问题
original_set_text = text.Text.set_text  # 保存原始方法
def new_set_text(self, s):
    s = replace_minus(s)  # 先处理减号
    return original_set_text(self, s)  # 调用原始方法设置文本
text.Text.set_text = new_set_text  # 应用重写后的方法


# -------------------------- 字体配置(确保中文和数学符号正常显示)--------------------------
plt.rcParams["font.family"] = ["SimHei"]  # 设置中文字体(支持中文显示)
plt.rcParams["text.usetex"] = True  # 使用LaTeX渲染文本(提升数学符号美观度)
plt.rcParams["axes.unicode_minus"] = True  # 确保负号正确显示(避免方块)
plt.rcParams["mathtext.fontset"] = "cm"  # 数学符号使用Computer Modern字体
d2l.plt.rcParams.update(plt.rcParams)  # 让d2l库的绘图工具继承配置


def train(net, train_iter, loss, epochs, lr):
    """
    模型训练函数

    参数:
        net: 待训练的神经网络
        train_iter: 训练数据迭代器
        loss: 损失函数(此处为MSE)
        epochs: 训练轮数
        lr: 学习率
    """
    trainer = torch.optim.Adam(net.parameters(), lr=lr)  # 使用Adam优化器(适合序列预测)
    for epoch in range(epochs):
        for X, y in train_iter:  # 遍历训练数据
            trainer.zero_grad()  # 清空梯度(避免累积)
            l = loss(net(X), y)  # 计算预测损失
            l.sum().backward()  # 损失求和并反向传播(计算梯度)
            trainer.step()  # 更新模型参数
        # 每轮结束后评估训练损失
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')


if __name__ == '__main__':
    # -------------------------- 1. 生成时间序列数据 --------------------------
    T = 1000  # 时间序列长度(1000个时间步)
    time = torch.arange(1, T + 1, dtype=torch.float32)  # 时间轴(1到1000)
    # 生成带噪声的正弦序列:信号为sin(0.01*time),噪声为均值0、标准差0.2的高斯噪声
    x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
    # 可视化原始数据
    d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
    plt.show(block=True)  # 显示图像并阻塞程序,等待关闭窗口


    # -------------------------- 2. 构建特征和标签(滞后特征)--------------------------
    tau = 4  # 滞后步长:使用前4个时间步(t-3, t-2, t-1, t)预测t+1
    # 特征矩阵:形状为(T - tau, tau),每行是前4个时间步的值
    features = torch.zeros((T - tau, tau))
    for i in range(tau):  # 填充特征矩阵的每一列
        # 第i列 = x[i : T - tau + i],即第i个滞后特征
        features[:, i] = x[i: T - tau + i]
    # 标签:形状为(T - tau, 1),每个值是对应特征行的下一个时间步(t+1)
    labels = x[tau:].reshape((-1, 1))


    # -------------------------- 3. 准备训练数据 --------------------------
    batch_size = 16  # 批量大小
    n_train = 600  # 训练集大小(前600个样本)
    # 构建训练迭代器:使用前n_train个特征和标签
    train_iter = d2l.load_array(
        (features[:n_train], labels[:n_train]),  # 训练数据(特征,标签)
        batch_size,
        is_train=True  # 标记为训练集(打乱顺序)
    )


    # -------------------------- 4. 定义模型(多层感知机)--------------------------
    def init_weights(m):
        """初始化模型权重:Xavier均匀初始化(适合线性层)"""
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)  # 避免权重过大或过小导致梯度问题

    def get_net():
        """定义简单的多层感知机(MLP)"""
        net = nn.Sequential(
            nn.Linear(tau, 10),  # 输入层:tau=4个特征→10个隐藏单元
            nn.ReLU(),  # 激活函数:引入非线性
            nn.Linear(10, 1)  # 输出层:10个隐藏单元→1个预测值
        )
        net.apply(init_weights)  # 应用权重初始化
        return net


    # -------------------------- 5. 训练模型 --------------------------
    loss = nn.MSELoss(reduction='none')  # 损失函数:均方误差(MSE),不自动求和
    net = get_net()  # 初始化模型
    print("开始训练模型...")
    train(net, train_iter, loss, epochs=5, lr=0.01)  # 训练5轮,学习率0.01


    # -------------------------- 6. 单步预测(使用真实滞后特征)--------------------------
    onestep_preds = net(features)  # 对所有特征(包括训练集和测试集)进行预测
    # 可视化:真实数据 vs 单步预测
    d2l.plot(
        [time, time[tau:]],  # x轴:完整时间轴 vs 从tau开始的时间轴(预测起始点)
        [x.detach().numpy(), onestep_preds.detach().numpy()],  # y轴:真实值 vs 预测值
        'time', 'x',
        legend=['data', '1-step preds'],  # 图例
        xlim=[1, 1000], figsize=(6, 3)
    )
    plt.show(block=True)


    # -------------------------- 7. 多步预测(使用预测值作为输入)--------------------------
    multistep_preds = torch.zeros(T)  # 存储多步预测结果
    # 前n_train + tau个值使用真实数据(训练集及之前的真实值)
    multistep_preds[: n_train + tau] = x[: n_train + tau]
    # 从n_train + tau开始,用模型预测结果作为输入继续预测
    for i in range(n_train + tau, T):
        # 用前tau个预测值(multistep_preds[i-tau:i])作为输入,预测第i步
        multistep_preds[i] = net(multistep_preds[i - tau:i].reshape((1, -1)))

    # 可视化:真实数据 vs 单步预测 vs 多步预测
    d2l.plot(
        [time, time[tau:], time[n_train + tau:]],  # x轴:完整时间轴 vs 单步预测轴 vs 多步预测轴
        [x.detach().numpy(), onestep_preds.detach().numpy(),
         multistep_preds[n_train + tau:].detach().numpy()],  # y轴对应值
        'time', 'x',
        legend=['data', '1-step preds', 'multistep preds'],  # 图例
        xlim=[1, 1000], figsize=(6, 3)
    )
    plt.show(block=True)


    # -------------------------- 8. 不同步数的多步预测对比 --------------------------
    max_steps = 64  # 最大预测步数
    # 特征矩阵:形状为(T - tau - max_steps + 1, tau + max_steps)
    # 前tau列是真实滞后特征,后面max_steps列是不同步数的预测结果
    features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
    # 填充前tau列(真实滞后特征)
    for i in range(tau):
        features[:, i] = x[i: i + T - tau - max_steps + 1]
    # 填充后面的max_steps列(预测特征)
    for i in range(tau, tau + max_steps):
        # 用前tau个特征(真实或预测)预测第i步,结果作为第i列
        features[:, i] = net(features[:, i - tau:i]).reshape(-1)

    # 可视化1步、4步、16步、64步预测结果
    steps = (1, 4, 16, 64)  # 对比的预测步数
    d2l.plot(
        # x轴:每个步数对应的时间范围
        [time[tau + i - 1: T - max_steps + i] for i in steps],
        # y轴:对应步数的预测结果
        [features[:, (tau + i - 1)].detach().numpy() for i in steps],
        'time', 'x',
        legend=[f'{i}-step preds' for i in steps],  # 图例:不同步数
        xlim=[5, 1000], figsize=(6, 3)
    )
    plt.show(block=True)

实验结果

1.真实数据图

2.单步预测对比图

3.多步预测对比图

4.不同步数的预测对比图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨尘游子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值