目录
自回归模型(Autoregressive Model, AR)
循环神经网络(RNN):定义与作用
1.什么是循环神经网络(RNN)?
循环神经网络(Recurrent Neural Network, RNN)是一种专门处理序列数据的神经网络模型,其核心特点是引入 “记忆” 机制—— 网络在处理当前输入时,会结合之前的输入信息(即 “历史状态”),从而捕捉序列数据中的时序依赖关系。
与传统的前馈神经网络(如 CNN、MLP)不同,RNN 的结构中存在循环连接:隐藏层的输出不仅取决于当前输入,还取决于上一时刻的隐藏状态。这种设计使其天然适合处理具有时序性的数据(如文本、语音、时间序列等)。
简单来说,RNN 可以理解为 “同一个神经网络模块在时间维度上重复执行”,每一步的输出都会传递到下一步作为 “记忆”,形成对序列的动态建模。
2.RNN 的核心作用
RNN 的设计初衷是解决序列数据的建模问题,其核心作用包括:
捕捉时序依赖关系
序列数据的关键特征是 “上下文关联”(如文本中前一个词影响后一个词,股票价格受前几日走势影响)。RNN 通过循环结构将历史信息编码到隐藏状态中,使模型能利用上下文信息进行预测或决策。
例如:在文本生成任务中,RNN 可以根据前几个词预测下一个词;在股票预测中,可根据过去 30 天的价格预测明天的走势。处理变长序列输入
前馈神经网络要求输入长度固定(如 MLP 需要固定维度的特征),而 RNN 能处理长度不固定的序列(如一句话可以是 5 个词,也可以是 10 个词)。通过在时间维度上迭代,无论序列多长,RNN 都能逐 step 处理。支持序列到序列(Sequence-to-Sequence)任务
RNN 可灵活处理 “输入序列” 与 “输出序列” 长度不同的场景,例如:
- 机器翻译(输入是中文句子,输出是英文句子,长度可能不同);
- 语音识别(输入是音频序列,输出是文本序列);
- 文本摘要(输入是长文本,输出是短摘要)。
3.典型应用场景
RNN 及其改进模型(如 LSTM、GRU)在以下领域有广泛应用:
- 自然语言处理(NLP):文本分类、情感分析、机器翻译、命名实体识别、文本生成(如写诗、写代码)等。
- 时间序列预测:股票价格预测、天气预测、电力负荷预测、设备故障预警等。
- 语音处理:语音识别、语音合成、说话人识别等。
- 视频分析:动作识别、视频字幕生成(输入是视频帧序列,输出是文字)等。
自回归模型(Autoregressive Model, AR)
自回归模型是序列建模中最基础的方法之一,核心思想是用序列过去的观测值预测未来的值。
1. 定义与核心假设
定义:自回归模型假设序列在时刻t的值
可以由其前p个时刻的值(
)线性(或非线性)表示。 例如,p阶自回归模型(AR (p))的数学形式为:
其中,c是常数项,
是模型参数,
是随机误差项(通常假设为白噪声)。
核心假设:序列的未来值仅依赖于过去固定长度的历史值,且这种依赖关系是稳定的(如平稳性假设)。
2. 应用场景
- 时间序列预测:如股票价格、气温、GDP 增速等连续型序列的短期预测。
- 信号处理:如语音信号的建模与生成。
3. 特点
- 优点:模型简单、可解释性强,适合捕捉序列的短期相关性。
- 缺点:需要预先确定阶数p(选择不当会导致欠拟合或过拟合);无法建模长期依赖(p过大会导致参数爆炸);仅捕捉相关性,不区分因果。
马尔可夫模型(Markov Model)
马尔可夫模型以 “马尔可夫性质” 为核心,简化了序列的依赖关系,重点关注状态的转移规律。
1. 定义与核心假设
马尔可夫性质:未来的状态只依赖于当前状态,而与 “当前状态之前的历史状态” 无关。用条件概率表示为:
这一性质也称为 “无记忆性”。
扩展:
- 一阶马尔可夫模型:依赖当前状态(如上述公式)。
- 高阶马尔可夫模型:依赖前k个状态(
),但本质仍是对历史依赖的简化。
- 常见形式:马尔可夫链(离散状态)、隐马尔可夫模型(HMM,状态不可观测)等。
2. 应用场景
- 自然语言处理:如词性标注(每个词的词性仅依赖前一个词的词性)。
- 语音识别:HMM 用于建模语音信号的状态转移。
- 状态预测:如用户行为序列(浏览→加购→购买)的状态转移预测。
3. 特点
- 优点:通过简化依赖关系(“仅依赖当前”)降低了计算复杂度,适合建模离散状态的序列。
- 缺点:马尔可夫性质是强假设,现实中很多序列的未来状态依赖更远的历史(如长文本的语义依赖);同样仅捕捉相关性,不涉及因果。
因果关系(Causality)
因果关系关注序列中变量之间的 “因→果” 机制 ,即一个变量的变化是否 “导致” 另一个变量的变化,而非单纯的相关性。
1. 定义与核心思想
- 定义:若改变变量A(干预)会导致变量B的分布发生变化,且这种变化无法通过其他变量解释,则称A是B的原因(
)。
- 在序列中:因果关系通常具有时间方向性(原因在前,结果在后),例如 “降雨(t时刻)导致销量下降(
时刻)”。
2. 与相关性的区别
- 相关性:变量之间的统计关联(如 “冰淇淋销量” 与 “溺水事故” 正相关),但可能是巧合或第三方变量(如 “高温”)导致的。
- 因果关系:需要通过干预、反事实推理等验证(如控制高温,观察冰淇淋销量是否影响溺水事故)。
3. 应用场景
- 政策评估:如 “补贴政策(因)是否提高了就业率(果)”。
- 序列干预:如 “调整广告投放(t时刻)是否提升未来销量(\(t+1\)时刻)”。
4. 特点
- 优点:揭示变量间的本质依赖,可用于指导干预(如通过改变 “因” 来控制 “果”)。
- 缺点:难以直接从观测数据中推断(需实验或复杂的因果推断方法,如 Do-Calculus、工具变量等);建模复杂,计算成本高。
自回归模型、马尔可夫模型、及因果关系之间关系
1.区别
维度 自回归模型(AR) 马尔可夫模型 因果关系 核心假设 依赖前p个历史值 依赖当前状态(无记忆性) 原因导致结果(时间方向性 + 干预依赖性) 历史依赖 依赖固定长度的过去(p阶) 依赖最近的k个状态(通常\(k=1\)) 依赖真实的 “因→果” 机制(无固定长度限制) 目标 预测未来值(基于相关性) 建模状态转移(基于相关性) 揭示 “因→果” 机制(指导干预) 是否涉及因果 否(仅捕捉相关性) 否(仅捕捉状态关联) 是(核心是因果机制) 计算复杂度 中等(依赖p) 低(简化依赖关系) 高(需验证干预效果) 典型应用 股票价格预测、气温预测 词性标注、用户行为状态转移 政策评估、干预效果分析
2.联系
自回归模型与马尔可夫模型的关联: 一阶自回归模型(AR (1))可看作一阶马尔可夫模型的特例(依赖前 1 个时刻的值);高阶马尔可夫模型可看作自回归模型的简化(仅保留最近k个状态)。
与因果关系的关联: 自回归模型和马尔可夫模型捕捉的相关性可能包含因果关系(如 “过去的收入→当前的消费”),但也可能只是虚假关联;而因果关系是对这种关联的 “本质解释”,是更底层的规律。
损失函数的作用
损失函数(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)