# 序列模型

# 训练

import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
T = 1000
time = torch.arange(1,T + 1,dtype=torch.float32)
x = torch.sin(0.01*time) + torch.normal(0,0.2,(T,))
d2l.plot(time,[x],'time', 'x',xlim=[1,1000],figsize=(6,3))

# 初始化一个用于存储所有特征向量的矩阵。
# 每一行将代表一个时间窗口内的观测值序列。初始填充 0 只是为了预分配内存。
tau = 4
# 9996 * 4
features = torch.zeros(T-tau,tau)
"""
features类似于这样
1 2 3 4
2 3 4 5
3 4 5 6
"""
for i in range(tau):
    features[:,i] = x[i: T - tau + i]
"""
x[tau:]: 从原始序列 x 中提取从索引 tau (在这里是 4) 开始到末尾的所有元素。这些元素是紧跟在每个特征窗口之后的那个值。
.reshape((-1, 1)): 将提取出来的一维切片重塑(reshape)成一个列向量(二维张量,形状为 (N, 1))。
-1: 表示让 PyTorch 自动计算该维度的大小。因为原始切片长度为 T - tau,所以这里会自动变成 T - tau 行。
1: 表示结果张量应该有 1 列。
"""
labels = x[tau:].reshape((-1,1))
features
tensor([[ 0.1444, -0.2229,  0.1308,  0.0739],
        [-0.2229,  0.1308,  0.0739,  0.3058],
        [ 0.1308,  0.0739,  0.3058,  0.1263],
        ...,
        [-0.6506, -0.6614, -0.6828, -0.0962],
        [-0.6614, -0.6828, -0.0962, -0.3576],
        [-0.6828, -0.0962, -0.3576, -0.6742]])
batch_size,n_train = 16,600
# 只有前 n_train 个样本用于训练
train_iter = d2l.load_array((features[:n_train],labels[:n_train]),batch_size,is_train=True)
def init_weights(m):
  #  m 预期是 PyTorch 模块(nn.Module)或其子层。
"""
				如果 m 是一个线性层:
        - nn.init: PyTorch 中用于进行参数初始化的子模块。
        - xavier_uniform_: 调用 Xavier (也称 Glorot) 均匀分布初始化方法。
          这是一种常用的权重初始化策略,旨在帮助网络训练时信号(梯度)更好地传播,
          使得各层激活值的方差大致保持不变。
        - 下划线 `_` 表示这是一个 in-place(原地)操作,它会直接修改传入的张量。
        - m.weight: 访问线性层 m 的权重参数(一个张量)。
        注意:这里只初始化了权重 (weight),没有初始化偏置 (bias)。通常偏置会初始化为 0。
"""
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
        
def get_net():
    net = nn.Sequential(
      # 输入维度是 4,输出维度是 10
        nn.Linear(4, 10),
      # 对上一层的输出(10 个单元)进行非线性变换,计算 max (0, x)。
        nn.ReLU(),
      # 输入特征维度为 10 (in_features=10),接收来自 ReLU 层的输出。
      # 输出特征维度为 1 (out_features=1)。
        nn.Linear(10, 1),
    )
    
    """
    这是 nn.Module 提供的一个方法,它会递归地将函数 fn
       应用到模型自身以及模型包含的每一个子模块(submodule)上。
     - init_weights: 就是我们上面定义的那个权重初始化函数。
     - 效果:
       1. apply 将 init_weights 应用到 net 本身(但 net 不是 nn.Linear,所以没效果)。
       2. apply 将 init_weights 应用到第一个 nn.Linear(4, 10) 层。这时 init_weights 内部的 if 条件满足,该层的权重被 Xavier 初始化。
       3. apply 将 init_weights 应用到 nn.ReLU() 层。if 条件不满足,无操作。
       4. apply 将 init_weights 应用到第二个 nn.Linear(10, 1) 层。if 条件满足,该层的权重被 Xavier 初始化。
    # - 目的:确保网络中所有线性层的权重都按照我们定义的 init_weights 函数进行了初始化。
    """
    net.apply(init_weights)
    return net
"""
# 创建一个损失函数的实例。
# - nn.MSELoss: PyTorch 提供的计算均方误差(Mean Squared Error)的类。
#   这是回归任务中最常用的损失函数之一。它计算的是模型预测值与真实目标值之间差值的平方的均值。
#   计算公式通常为:L = mean((prediction - target)^2)
# - reduction='none': 这是 MSELoss 的一个重要参数,用于控制损失计算的输出方式。
#   - 'mean' (默认值): 计算批次中所有样本损失的平均值,返回一个标量(scalar)。
#   - 'sum': 计算批次中所有样本损失的总和,返回一个标量。
#   - 'none': 不进行最终的求和或求平均操作。它会返回一个与输入/目标张量形状相同(或可广播)的张量,其中每个元素是对应样本的平方误差损失值。这样做通常是为了后续可能需要对每个样本的损失进行不同的处理(比如加权、分析等),或者在某些特定的训练流程中需要原始的、未聚合的损失值。如果需要得到最终的平均损失,可以在得到 `output_loss` 后再手动调用 `.mean()` 方法。
"""
loss = nn.MSELoss(reduction='none')
def train(net, train_iter, loss,epochs, lr):
  """
    创建一个优化器 (Optimizer) 实例。
    - torch.optim.Adam: 使用 Adam 优化算法。Adam 是一种常用的、高效的梯度下降优化算法,
      它能自适应地调整每个参数的学习率。
    - net.parameters(): 获取模型 `net` 中所有需要训练的参数 (权重和偏置)。优化器需要知道要更新哪些参数。
    - lr=lr: 将函数参数传入的学习率 `lr` 设置给 Adam 优化器。
  """
    trainer = torch.optim.Adam(net.parameters(), lr=lr)
    
    
    # 外层循环:遍历指定的训练轮数 (epochs)。
    # 每完成一轮循环,意味着模型完整地看过了一遍所有的训练数据。
    for epoch in range(epochs):
      # 内层循环:遍历训练数据迭代器 `train_iter`。
      # 在每次迭代中,`train_iter` 会提供一批 (batch) 训练数据。
      # - X: 当前批次的特征数据 (张量)。
      # - y: 当前批次对应的真实标签 (张量)。
        for X, y in train_iter:
          # 清空优化器中记录的关于模型参数的梯度。
            # - PyTorch 默认会累积梯度,所以在计算当前批次的梯度之前,
            #   必须将上一批次计算得到的梯度清零,以避免干扰。这是非常重要的一步。
            trainer.zero_grad()
            
            """
            # 执行核心计算:
            # 1. net(X): 前向传播 (Forward Pass)。将当前批次的特征 `X` 输入网络 `net`,得到模型的预测输出。
            # 2. loss(predictions, targets): 计算损失。使用传入的 `loss` 函数,计算模型预测值 `net(X)` 与真实标签 `y` 之间的差异(损失)。
            # 3. l = ...: 将计算得到的损失赋值给变量 `l`。
 *重要提示*:因为我们之前定义的 `loss` 使用了 `reduction='none'`,
            #            所以这里的 `l` 不是一个单一的数值(标量),而是一个包含了
            #            批次中每个样本单独损失值的张量。
            """
            l = loss(net(X), y)
            
            """
            执行反向传播 (Backward Pass / Backpropagation)。
            1. l.sum(): 由于 `l` 是包含每个样本损失的张量,我们需要一个标量值来启动整个计算图的反向传播。`.sum()` 将当前批次所有样本的损失加起来,得到一个标量值(代表批次总损失)。(如果 loss 使用的是 'mean' 或 'sum',则可以直接调用 l.backward())
            2. .backward(): PyTorch 的自动微分引擎会根据这个最终的标量损失值,自动计算损失相对于模型中所有可训练参数(`net.parameters()` 中`requires_grad=True` 的参数)的梯度。
            """
            l.sum().backward()
            
            
            """
            # 更新模型参数。
            # - 优化器 (`trakiner` / `optimizer`) 根据 `.backward()` 计算得到的梯度,
            #   以及它自身的更新规则 (Adam 算法) 和学习率 (`lr`),来调整模型 `net` 的权重和偏置。
            """
            trakiner.step()
            
        # 使用当前的 `net` 模型,在 ** 整个 ** `train_iter` 数据集上
        #   重新进行一次前向传播和损失计算(但不进行反向传播或参数更新),然后返回
        #   ** 平均 ** 损失值。这有助于观察模型在训练集上的整体表现随着 epoch 的进行如何变化。
        print('epoch: %d, loss: %.3f' % (epoch + 1, d2l.evaluate_loss(net,train_iter,loss)))
net = get_net()
train(net,train_iter,loss,5,0.01)
epoch: 1, loss: 0.073
epoch: 2, loss: 0.051
epoch: 3, loss: 0.052
epoch: 4, loss: 0.051
epoch: 5, loss: 0.051

# 预测

"""
张量feature: (T - tau, tau)
张量onestep_preds: (T - tau, 1)其中 onestep_preds[j] 是模型基于真实历史数据 [x[j], ..., x[j+tau-1]] 对下一个时间点 x[j+tau] 的预测值。
"""
onestep_preds = net(features)
d2l.plot(
  [time,time[tau:]],
 """
 - x.detach().numpy(): 原始时间序列数据 x,转换为 NumPy 数组用于绘图。
 		.detach() 用于从计算图中分离张量,防止梯度计算;.numpy() 将其转为 NumPy 格式。
 		onestep_preds.detach().numpy(): 单步预测结果,同样转换为 NumPy 数组。
 """
  [x.detach().numpy(),onestep_preds.detach().numpy()],
  'time', 'x',
  legend=['data','1-step  preds'],xlim=[1,1000],figsize=(6,3))

multistep_preds = torch.zeros(T)
"""
	多步预测通常是在训练数据之后进行的。为了启动预测过程,我们需要提供模型所需的初始历史数据。这里假设模型是在前 n_train 个点上训练的。
	
	预测第 n_train + tau 个点的值,需要用到从 n_train 到 n_train + tau - 1 的数据。所以我们将真实数据 x 中直到 n_train + tau - 1 的部分(共 n_train + tau 个点)复制到 multistep_preds 中作为“种子”或初始条件。
"""
multistep_preds[:n_train + tau] = x[:n_train + tau]
# 循环从第一个需要真正预测的时间点 (n_train + tau) 开始,一直到序列末尾 (T-1)。
for i in range(n_train + tau, T):
  # 使用模型进行预测,关键在于输入是 * 之前存储在 multistep_preds 中的数据 *
    multistep_preds[i] = net(multistep_preds[i-tau:i].reshape((1,-1)))
  """
  	multistep_preds[i - tau : i]: 从 multistep_preds 张量中提取预测当前点 i所需要的历史数据,即从 i-tau 到 i-1 的 tau 个值。
  	
  	*核心区别*:当 i > n_train + tau 时,这个切片中将包含至少一个先前由模型 *自己预测* 并存入 multistep_preds 的值,而不是真实的 x 值。
  	
  	.reshape((1, -1)): 将提取出的 1D 切片(长度 tau)重塑为 2D 张量,形状为 (1, tau)。这是因为神经网络 net 通常期望输入是一个批次 (batch),即使批次大小仅为 1。-1 会自动推断出列数应为 tau。
    逻辑:这是一个自回归(auto-regressive)的过程。模型预测未来的一个点,然后这个预测值又被用作未来预测的输入。这种方式模拟了真实的预测场景,即我们不知道未来的真实值,只能依赖模型的预测来滚动向前。这种方法的缺点是预测误差会累积。
  """
d2l.plot([time,time[tau:],time[n_train + tau:]],[x.detach().numpy(),onestep_preds.detach().numpy(),multistep_preds[n_train + tau:].detach().numpy()],'time','x',legend=['data','1-step  preds','multistep pred'],xlim=[1,1000],figsize=(6,3))

max_steps = 64
"""
形状分析: (行数, 列数)
			行数 = T - tau - max_steps + 1: 这个行数代表了我们可以从原始序列 x 中选取多少个不同的起始“窗口”。对于每个起始点 j(从 0 开始),我们需要 tau 个历史点(x[j] 到 x[j+tau-1])并且还需要能进行 max_steps 的预测。最后一步预测对应原始序列的 j + tau + max_steps - 1 这个时间点。这个时间点必须小于总长度 T,即 j + tau + max_steps - 1 < T,所以 j < T - tau - max_steps + 1。因此,有效的起始点 j 的数量就是 T - tau - max_steps + 1。
			列数 = tau + max_steps: 这个列数表明,对于每个起始窗口(每一行),我们将存储 tau 个初始的历史真实值,并且存储接下来由模型生成的 max_steps 个预测值。
			逻辑: 创建一个足够大的矩阵,用来存放所有起始窗口的初始历史数据以及它们各自对应的、直到 max_steps 步的未来预测序列。
"""
features  = torch.zeros((T - tau - max_steps + 1,tau + max_steps))
for i  in range(tau):
    features[:,i] = x[i: T - tau - max_steps + i + 1]
"""
		这行代码是进行自回归预测并填充 features 矩阵后续的 max_steps 列。
		features[:, i - tau : i]: 对于要计算的第 i 列(i >= tau),这会选取它前面 tau 列的所有行数据。这构成了预测第 i 时刻(相对于每个窗口起始点)所需的输入特征。例如,计算第 tau 列(即 1 步预测)时,它使用第 0 到 tau-1 列(真实历史);计算第 tau+1 列(即 2 步预测)时,它使用第 1 到 tau 列(其中第 tau 列是上一步的预测值)。
		net(...): 将这些大小为 (行数, tau) 的特征数据块输入到神经网络 net 中。模型的输出形状将是 (行数, 1)。
		.reshape(-1): 将网络输出从 (行数, 1) 变形为 (行数,),即一个一维张量。
		features[:, i] = ...: 将这些预测结果赋值给 features 矩阵的第 i 列。
		逻辑: 这个循环逐列(即逐个预测时间步)地生成预测。对于 features 矩阵中的每一行(代表一个起始窗口),它都在用模型基于该行之前的 tau 个值(无论是真实历史还是先前的预测)来预测下一个值,并将结果存储在该行的相应列中。这个过程对所有行并行执行,直到生成了 max_steps 步的预测。
"""
for i in range(tau, tau + max_steps):
    features[:,i] = net(features[:,i-tau:i]).reshape(-1)
steps = (1, 4, 16,64)
d2l.plot([time[tau + i - 1:T - max_steps + i] for i in steps],
         [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))

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝