数据流:生成 → 打乱 → 分批。
计算图:前向 → 损失 → 反向。
优化 loop:初始化 → 迭代更新 → 收敛。

一、头文件

import torch
import matplotlib.pyplot as plt #画图
import random #随机

二、线性回归数据生成函数

def create_data(w,b,data_num): #生成数据
    x = torch.normal(0,1,(data_num,len(w)))
    y = torch.matmul(x,w)+b #matmul表示矩阵相乘

    noise = torch.normal(0,0.01,y.shape) #噪声要加到y上
    y += noise

    return x,y

num=500

true_w=torch.tensor([8.1,2,2,4])
true_b=torch.tensor(1.1)

X,Y = create_data(true_w,true_b,num)

#生产的数据画图
plt.scatter(X[:,3],Y,1)
plt.show()

1. 数据生成函数:

def create_data(w,b,data_num): #生成数据
    x = torch.normal(0,1,(data_num,len(w)))
    y = torch.matmul(x,w)+b #matmul表示矩阵相乘

    noise = torch.normal(0,0.01,y.shape) #噪声要加到y上
    y += noise

    return x,y
行号代码功能输出形状
2torch.normal(0,1,(data_num,len(w)))生成特征矩阵(500, 4)
3torch.matmul(x,w)+b计算线性关系(500,)
4torch.normal(0,0.01,y.shape)生成噪声(500,)
5y += noise添加噪声(500,)

2、参数设置:

num=500

true_w=torch.tensor([8.1,2,2,4])
true_b=torch.tensor(1.1)

参数详解

参数含义维度
num500样本数量标量
true_w[8.1, 2, 2, 4]4 个特征的真实权重(4,)
true_b1.1真实偏置标量

权重解读:

特征 1 权重:8.1  ← 影响最大
特征 2 权重:2.0
特征 3 权重:2.0
特征 4 权重:4.0  ← 影响次大
偏置:1.1

3、生成数据:

X,Y = create_data(true_w,true_b,num)

4、数据形状验证:

plt.scatter(X[:,3],Y,1)
plt.show()

参数详解

代码含义说明
X[:,3]第 4 个特征的所有样本值形状 (500,)
Y所有标签值形状 (500,)
1散点大小点的大小为 1 像素

为什么选 X[:,3]

  • 索引从 0 开始,[:,3] 表示第 4 个特征
  • 第 4 个特征权重为 4.0,影响较大,散点图线性关系更明显
特征权重可视化效果
特征 18.1斜率最大,关系最明显
特征 22.0斜率较小
特征 32.0斜率较小
特征 44.0斜率适中,适合展示

5、完整的维度转换图:

┌─────────────────────────────────────────────────────────┐
│                    输入参数                              │
│  true_w: torch.tensor([8.1, 2, 2, 4])  shape: (4,)      │
│  true_b: torch.tensor(1.1)              shape: ()       │
│  num: 500                                               │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│              生成特征矩阵 X                              │
│  X = torch.normal(0, 1, (500, 4))                       │
│  X.shape = (500, 4)                                     │
│  ┌──────────────────────────────┐                       │
│  │ 特征 1  特征 2  特征 3  特征 4  │                       │
│  │  x11    x12    x13    x14   │ ← 样本 1               │
│  │  x21    x22    x23    x24   │ ← 样本 2               │
│  │  ...    ...    ...    ...   │                        │
│  │  x500,1 x500,2 x500,3 x500,4│ ← 样本 500             │
│  └──────────────────────────────┘                       │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│              计算线性关系                                │
│  y = X @ w + b                                          │
│  y[i] = 8.1*X[i,0] + 2*X[i,1] + 2*X[i,2] + 4*X[i,3] + 1.1│
│  y.shape = (500,)                                       │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│              添加噪声                                    │
│  noise ~ N(0, 0.01²)                                    │
│  y = y + noise                                          │
│  y.shape = (500,)                                       │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│              可视化                                      │
│  plt.scatter(X[:,3], Y, 1)                              │
│  横轴:第 4 个特征 (权重=4.0)                              │
│  纵轴:标签 Y                                           │
│  预期斜率 ≈ 4.0                                         │
└─────────────────────────────────────────────────────────┘

6、核心知识点总结:

┌─────────────────────────────────────────────────────────┐
│                    核心知识点                            │
├─────────────────────────────────────────────────────────┤
│  1. torch.normal() - 正态分布采样                       │
│  2. torch.matmul() - 矩阵乘法                           │
│  3. 广播机制 - 标量 + 向量                              │
│  4. 张量索引 - X[:,3] 选取特定特征                       │
│  5. 线性回归模型 - y = Xw + b + ε                       │
│  6. 数据可视化 - matplotlib 散点图                       │
│  7. 合成数据 - 已知真实参数的监督学习数据                │
└─────────────────────────────────────────────────────────┘

三、取数据函数

def data_provider(data,label,batchsize): #每次访问这个函数,都能提供一批数据
    length = len(label)
    indices = list(range(length))

    #我不能按顺序取,需把数据打乱
    random.shuffle(indices)

    for each in range(0,length,batchsize):
        get_indices = indices[each:each+batchsize]
        get_data = data[get_indices]
        get_label = label[get_indices]

        yield get_data,get_label #有存档点的return

batchsize = 16

#打印取得的数据
# for batch_x,batch_y in data_provider(X,Y,batchsize):
#     print(batch_x,batch_y)
#     break

1、代码功能总览:

在典型的深度学习训练循环中,这段代码位于数据加载阶段。它实现了随机小批量梯度下降(Stochastic Mini-batch Gradient Descent) 所需的数据流控制,是连接数据集与模型训练的桥梁。

功能模块代码实现目的
数据打乱random.shuffle(indices)打破样本间的相关性,防止模型记忆顺序
分批处理range(0, length, batchsize)将大数据集切分为小块,适应显存限制
惰性加载yield生成器模式,节省内存,不用一次性加载所有数据
索引提取data[get_indices]利用高级索引获取对应的数据块

2、逐行深度解析

1. 函数定义
def data_provider(data, label, batchsize): 
  • data: 输入特征矩阵(通常是 PyTorch Tensor 或 NumPy 数组),形状如 (总样本数,特征数)
  • label: 标签向量(通常是 PyTorch Tensor 或 NumPy 数组),形状如 (总样本数,)
  • batchsize: 批量大小,即每次训练迭代使用的样本数量(例如 16, 32, 64)。
  • 注释#每次访问这个函数,都能提供一批数据 —— 这描述的是生成器的特性。
2. 获取长度与创建索引
length = len(label)
indices = list(range(length))
  • length: 获取数据集的总样本数。
    • 注意:通常 len(data) 和 len(label) 应该相等。
  • indices: 创建一个顺序列表 [0, 1, 2, ..., length-1]
    • 作用:我们不直接打乱 data 本身(因为复制大数据很耗内存),而是打乱索引,通过索引去取数据。
3. 打乱索引(Shuffle)
#我不能按顺序取,需把数据打乱
random.shuffle(indices)
  • random.shuffle: 原地打乱列表顺序。
  • 为什么要打乱?
    1. 打破相关性:如果数据是按类别排序的(如前 500 个是猫,后 500 个是狗),不打乱会导致模型在一个 epoch 内只学到一种类别。
    2. 加速收敛:随机梯度下降(SGD)需要样本的随机性来跳出局部最优解。
    3. 评估泛化性:确保每个 batch 的数据分布接近整体分布。
4. 循环分批
for each in range(0, length, batchsize):
  • range(0, length, batchsize): 生成起始索引序列。
    • 例如:length=500batchsize=16
    • 生成:0, 16, 32, ..., 496
  • each: 当前 batch 的起始位置。
5. 获取当前批次的索引
get_indices = indices[each:each+batchsize]
  • 切片操作:从打乱后的 indices 中截取一段。
  • 边界处理:如果是最后一个 batch,each+batchsize 可能超过 length。Python 切片会自动处理边界,只取到列表末尾,因此最后一个 batch 的大小可能小于 batchsize
6. 提取数据与标签
get_data = data[get_indices]
get_label = label[get_indices]
  • 高级索引(Fancy Indexing)
    • 如果 data 是 PyTorch Tensor 或 NumPy Array,这行代码有效。它会根据 get_indices 列表中的数字,提取对应的行。
    • 例如get_indices = [5, 100, 3],则 get_data 包含原数据中第 5、100、3 行的数据。
7. 生成器返回(Yield)
yield get_data, get_label #有存档点的 return
  • yield vs return:这是本代码的核心
    • return:函数结束,返回值,下次调用从头开始。
    • yield:函数暂停,返回值,保留当前状态(局部变量、循环位置)。下次调用时,从 yield 下一行继续执行。
  • 好处
    1. 节省内存:不需要一次性生成所有 batch 的列表,用多少生成多少。
    2. 流式处理:适合处理无法一次性装入内存的大数据集。

四、定义一个模型和loss

def fun(x,w,b):
    pred_y = torch.matmul(x,w)+b
    return pred_y

def maeLoss(pre_y,y):
    return torch.sum(abs(pre_y-y))/len(y)

定义了一个线性回归模型的前向传播函数和一个平均绝对误差(MAE)损失函数。这是深度学习训练流程中两个最核心的组件:模型定义损失计算

1、代码功能总览

函数名功能对应数学概念作用阶段
fun(x, w, b)模型预测假设函数 h(x)=wx+b前向传播 (Forward)
maeLoss(pre_y, y)计算损失损失函数 两数相减的绝对值\hat{y} - y

整体流程:输入数据 x → 通过 fun 得到预测值 pred_y → 通过 maeLoss 对比真实值 y 计算误差 → 用于反向传播更新参数。

2、函数 1:fun(x, w, b) 深度解析

def fun(x,w,b):
    pred_y = torch.matmul(x,w)+b
    return pred_y
1. 参数与维度
参数含义典型形状 (Shape)说明
x输入特征(batch_size, num_features)例如 (16, 4) 表示 16 个样本,4 个特征
w权重向量(num_features,)(num_features, 1)例如 (4,)
b偏置标量()(1,)例如 1.0
2. 核心运算:torch.matmul(x, w) + b
  • torch.matmul(x, w):
    • 执行矩阵 - 向量乘法
    • 计算每个样本的加权和:i∑​xi​⋅wi​ 。
    • 输出形状:(batch_size,)
  • + b:
    • 利用 PyTorch 的广播机制 (Broadcasting)
    • 将标量 b 加到结果向量的每一个元素上。
  • 数学公式: 

3、函数 2:maeLoss(pre_y, y) 深度解析

def maeLoss(pre_y,y):
    return torch.sum(abs(pre_y-y))/len(y)
1. 功能定义
  • 这是 Mean Absolute Error (MAE),也称为 L1 Loss
  • 它计算预测值与真实值之间绝对误差的平均值
2. 逐步拆解
代码片段含义数学表达形状变化
pre_y - y计算残差(误差)y^​i​−yi​(batch_size,)
abs(...)取绝对值$\hat{y}_i - y_i
torch.sum(...)求和$\sum_{i=1}^{n}\hat{y}_i - y_i
/ len(y)求平均n1​∑...标量 (1,)
3. 数学公式

4. MAE 的特性(重要)
特性说明影响
鲁棒性对**异常值(Outliers)**不敏感如果数据中有个别极大的误差,MAE 不会像 MSE 那样被剧烈放大
梯度梯度恒为 1 或 -1(除了 0 点)收敛速度稳定,但在接近最优解时可能震荡,不如 MSE 精细
可导性在 0 处不可导(次梯度)PyTorch 的 autograd 会自动处理次梯度,通常不影响训练

五、随机梯度下降

def sgd(paras,lr): #随机梯度下降,更新参数
    with torch.no_grad(): #属于这句代码的部分不计算梯度
        for para in paras:
            para -= para.grad*lr #不能写成para=para-para.grad*lr
            para.grad.zero_() #使用过的梯度归零

PyTorch 训练的三个黄金法则

  1. 更新时要闭眼with torch.no_grad(),更新参数时不要记录计算图。
  2. 修改要原地para -=,确保模型持有的参数引用被实际修改。
  3. 梯度要清零para.grad.zero_(),防止梯度累加污染下一次迭代。

1、代码功能总览

    函数名功能输入输出阶段
    sgd手动优化器paras: 参数列表 [w, b]<br>lr: 学习率无(原地修改参数)反向传播后

    数学原理: 


    2、逐行深度解析

    第 1 行:函数定义
    def sgd(paras,lr): #随机梯度下降,更新参数
    • paras: 一个包含所有需要更新的参数的列表。
      • 通常形式:[w, b] 或 net.parameters()
      • 这些参数必须是 requires_grad=True 的张量。
    • lr学习率(Learning Rate)
      • 控制每次更新的步长。
      • 太大:可能越过最优解,导致发散。
      • 太小:收敛速度极慢。
    第 2 行:停止梯度追踪
    with torch.no_grad(): #属于这句代码的部分不计算梯度
    • 核心作用:临时禁用 PyTorch 的自动求导引擎(Autograd)
    • 为什么要这样做?
      1. 节省内存:参数更新操作(减法和乘法)不需要计算梯度。如果不禁用,PyTorch 会尝试构建更新操作的计算图,消耗大量显存。
      2. 防止图无限增长:如果不禁用,更新操作会被记录为计算图的一部分。下一个 epoch 的 backward() 会沿着这个图一直回传到初始状态,导致计算图无限变大,最终内存溢出(OOM)。
      3. 语义明确:我们只关心 Loss 对 参数 的梯度,不关心 新参数 对 旧参数 的梯度。
    第 4 行:参数更新(关键!)
    para -= para.grad*lr #不能写成 para=para-para.grad*lr
    • 数学含义
    • para.grad: 存储了上一次 loss.backward() 计算出的梯度。
    • -= (原地操作) vs = (赋值操作)
      • ✅ para -= ... (正确)
        • 这是原地修改(In-place Operation)
        • 它直接修改了 para 指向的内存数据
        • 因为模型(Model)内部持有的也是这个内存地址的引用,所以模型中的参数同步被更新了
      • ❌ para = ... (错误)
        • 这是变量重绑定(Rebinding)
        • para - ... 计算出一个新张量
        • para = 将局部变量 para 指向了这个新张量。
        • 后果:模型内部持有的旧张量没有被修改。下一次前向传播时,模型依然使用旧的参数,训练无效。
      • 注释解读:代码中的注释 #不能写成 para=para-para.grad*lr 非常精准,指出了新手最容易犯的错误。
    第 5 行:梯度清零
    para.grad.zero_() #使用过的梯度归零
    • 核心作用:将参数的 .grad 属性重置为 0。
    • 为什么要清零?
      • PyTorch 的梯度默认是**累加(Accumulate)**的。
      • 如果不清零,下一次 loss.backward() 计算出的梯度会加到现有的梯度上。
      • 后果:梯度会越来越大,参数更新方向错误,模型无法收敛。
      • 场景:每个 batch 更新一次参数,所以每个 batch 后必须清零。

    六、训练

    lr=0.03
    w_0=torch.normal(0,0.01,true_w.shape,requires_grad=True) #这个w需要计算梯度
    b_0=torch.tensor(0.01,requires_grad=True)
    print(w_0,b_0)
    
    epochs=50 #训练的轮数
    
    for epoch in range(epochs):
        data_loss=0
        for batch_x,batch_y in data_provider(X,Y,batchsize):
            pred_y = fun(batch_x,w_0,b_0)
            loss = maeLoss(pred_y,batch_y)
            loss.backward()
            sgd([w_0,b_0],lr)
            data_loss += loss
    
        print("epoch %03d:loss:%.6f"%(epoch,data_loss)) #打印每一轮的loss,方便查看
    
    print("真实的函数值是:",true_w,true_b)
    print("训练得到的参数值是:",w_0,b_0)

    通过迭代优化,让初始化的随机参数 w_0, b_0 逐渐逼近真实的参数 true_w, true_b。

    1、代码功能总览

    代码块功能关键操作
    参数初始化设置模型初始状态torch.normal, requires_grad=True
    训练循环执行迭代优化外层 Epoch,内层 Batch
    核心四步完成一次参数更新前向 → 损失 → 反向 → 更新
    结果验证评估训练效果对比 真实值 vs 训练值

    2、逐行深度解析

    1. 参数初始化
    lr=0.03
    w_0=torch.normal(0,0.01,true_w.shape,requires_grad=True) #这个 w 需要计算梯度
    b_0=torch.tensor(0.01,requires_grad=True)
    print(w_0,b_0)
    • lr=0.03学习率。控制参数更新的步长。
      • 太大:损失函数震荡,不收敛。
      • 太小:训练速度极慢。
      • 0.03 是经验值,针对此数据集通常有效。
    • w_0 初始化:
      • torch.normal(0, 0.01, ...): 从均值为 0,标准差为 0.01 的正态分布初始化。
      • 为什么要随机初始化? 如果全初始化为 0,所有神经元对称,无法学习不同特征。
      • requires_grad=True关键标志。告诉 PyTorch 追踪对这个张量的所有操作,以便后续计算梯度。没有它,backward() 无效。
    • b_0 初始化: 初始化为 0.01 的标量,同样需要梯度。
    • print: 打印初始参数,用于对比训练后的变化。
    2. 训练循环结构
    epochs=50 #训练的轮数
    
    for epoch in range(epochs):
        data_loss=0
        for batch_x,batch_y in data_provider(X,Y,batchsize):
            # ... 训练步骤 ...
    • epochs=50轮数。表示将整个数据集遍历 50 次。
      • 太少:模型欠拟合(没学好)。
      • 太多:模型过拟合(死记硬背)且浪费时间。
    • 外层循环 (epoch): 控制训练的整体进度。
    • 内层循环 (batch): 通过 data_provider 每次获取一个小批量数据。
      • 这是**随机梯度下降(SGD)**的体现:每次只用一部分数据更新参数,而不是全部。
    • data_loss=0: 用于累计当前 epoch 的总损失,以便评估该轮训练的整体效果。
    3. 核心训练四步走
            pred_y = fun(batch_x,w_0,b_0)   # 1. 前向传播
            loss = maeLoss(pred_y,batch_y)  # 2. 计算损失
            loss.backward()                 # 3. 反向传播
            sgd([w_0,b_0],lr)               # 4. 参数更新
            data_loss += loss               # 5. 记录损失

    这是深度学习的标准范式,每一步都至关重要:

    1. 前向传播 (fun): 用当前参数计算预测值 y^​ 。
    2. 计算损失 (maeLoss): 衡量预测值 y^​ 与真实值 y 的差距。
    3. 反向传播 (loss.backward()): 自动计算损失函数对每个参数 (w_0b_0) 的梯度,并存入 .grad 属性。
    4. 参数更新 (sgd): 利用梯度更新参数(w = w - lr * grad),并清零梯度。
    5. 记录损失: 累加 loss 用于监控收敛情况。
    4. 监控与打印
        print("epoch %03d:loss:%.6f"%(epoch,data_loss)) #打印每一轮的 loss,方便查看
    • %03d: epoch 编号占 3 位,不足补 0(如 001, 050)。
    • %.6f: 损失值保留 6 位小数。
    • 目的: 观察 loss 是否随 epoch 增加而逐渐下降。如果下降,说明模型正在学习;如果不变或上升,说明学习率或模型有问题。
    • ⚠️ 潜在问题data_loss 是张量累加的结果。最好使用 data_loss += loss.item() 将其转换为 Python 浮点数,否则打印时会显示 tensor(...) 且可能占用额外内存。
    5. 结果验证
    print("真实的函数值是:",true_w,true_b)
    print("训练得到的参数值是:",w_0,b_0)
    • 目的: 因为数据是合成的(我们知道 true_w),所以可以直接对比。
    • 预期结果: 训练后的 w_0 应非常接近 true_w(例如 [8.1, 2, 2, 4]),b_0 接近 1.1
    • 意义: 验证算法的正确性。如果在真实场景中,我们没有 true_w,只能通过测试集损失来评估。

    七、可视化

    idx = 3 #0-3
    plt.plot(X[:,idx].detach().numpy(),X[:,idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
    plt.scatter(X[:,idx],Y,1)
    plt.show()

    用于可视化训练后的线性回归模型,将学习到的线性关系实际数据点绘制在同一张图上,直观展示模型的拟合效果。

    1、代码功能总览

    代码行功能输出
    idx = 3选择第 4 个特征进行可视化标量 3
    plt.plot(...)绘制模型预测的直线回归线
    plt.scatter(...)绘制实际数据点散点图
    plt.show()显示图像可视化窗口

    核心目的:验证训练后的参数 w_0, b_0 是否能正确描述特征与标签之间的关系。


    2、逐行深度解析

    第 1 行:选择特征索引

    idx = 3
    • 含义:选择第 4 个特征(索引从 0 开始:0, 1, 2, 3)。
    • 为什么选第 4 个特征?
      • 回顾真实权重:true_w = [8.1, 2, 2, 4]
      • 第 4 个特征权重为 4.0,影响较大,线性关系明显,适合展示。
      • 第 1 个特征权重 8.1 更大,但可能斜率太陡,可视化效果不如第 4 个特征适中。

    第 2 行:绘制模型预测线

    plt.plot(X[:,idx].detach().numpy(), X[:,idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())

    这是本代码的核心,绘制的是训练后模型学到的线性关系。

    1. 结构拆解
    部分代码含义形状
    X 轴X[:,idx].detach().numpy()第 4 个特征的所有样本值(500,)
    斜率w_0[idx].detach().numpy()第 4 个特征的学习权重标量
    截距b_0.detach().numpy()学习到的偏置标量
    Y 轴X[:,idx] * w_0[idx] + b_0模型预测值(仅考虑该特征)(500,)
    2. 为什么要用 .detach() 和 .numpy()
    方法作用必要性
    .detach()从计算图中分离张量,停止梯度追踪✅ 必须。matplotlib 无法处理需要梯度的张量
    .numpy()将 PyTorch 张量转换为 NumPy 数组✅ 必须。matplotlib 只接受 NumPy 数组或列表

    转换流程

    PyTorch Tensor (requires_grad=True) 
        ↓ .detach()
    PyTorch Tensor (requires_grad=False) 
        ↓ .numpy()
    NumPy Array 
        ↓
    matplotlib 可以绘制
    
    3. 数学公式

    绘制的直线方程为: 

    注意:这是一个简化视图。真实模型是:

    由于其他特征的存在,散点不会完美落在直线上,但应呈现线性趋势


    第 3 行:绘制实际数据点

    plt.scatter(X[:,idx], Y, 1)
    参数含义说明
    X[:,idx]X 轴数据第 4 个特征的值(PyTorch 张量可直接用于 scatter)
    YY 轴数据真实标签值
    1点的大小s=1,点非常小,适合密集数据

    ⚠️ 潜在问题

    • X[:,idx] 是 PyTorch 张量,某些版本的 matplotlib 可能警告或报错。
    • 建议:统一转换为 NumPy,plt.scatter(X[:,idx].numpy(), Y.numpy(), 1)

    第 4 行:显示图像

    plt.show()
    • 渲染并显示图像窗口。
    • 在 Jupyter Notebook 中会直接内嵌显示。
    • 在脚本中会弹出独立窗口。

    3、核心知识点总结

    ┌─────────────────────────────────────────────────────────┐
    │                    核心知识点                            │
    ├─────────────────────────────────────────────────────────┤
    │  1. .detach() - 从计算图分离张量                         │
    │  2. .numpy() - PyTorch 转 NumPy                         │
    │  3. plt.plot() - 绘制连续曲线(直线)                    │
    │  4. plt.scatter() - 绘制离散点                          │
    │  5. 张量索引 - X[:,idx] 选取特定特征列                   │
    │  6. 模型可视化 - 验证训练效果的重要手段                  │
    │  7. 单特征视图 - 高维模型的降维展示技巧                  │
    └─────────────────────────────────────────────────────────┘
    

    更多推荐