# 替换原来的Agg配置
import matplotlib
matplotlib.use('TkAgg', force=True)  # 改用TkAgg GUI后端

# import matplotlib
# matplotlib.use('Agg', force=True)
# print('强制设置后:', matplotlib.get_backend())

import torch
import matplotlib.pyplot as plt #  画图的

import random  #随机

#生成变量X与Y


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()

#按批取数据,例如一次取16个

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

#计算损失函数,并根据学习率下降梯度

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)

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

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)

训练50轮,看50遍数据

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))

print("真实的函数值是", true_w, true_b)
print("训练得到的参数值是", w_0, b_0)

#可视化

idx = 0
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()

Q:感觉似懂非懂,张量这一块,比如我有X,Y,根据初始化w和b计算pre_y与y之间的loss,然后根据梯度下降,调整w和b

A:你的理解完全正确!整个流程就是一个“找参数”的过程。

你现在的困惑点可能在于:这中间的数据(张量)到底经历了什么变化? 为什么一堆数进去,最后就能算出怎么改参数?

为了帮你彻底打通“任督二脉”,我们不再看枯燥的代码,而是把张量看作“各种形状的积木”。我们来跟踪一下,在一个 batch(16条数据)的训练循环里,这些积木的形状是怎么变的。

我们可以把这个过程想象成一个**“大漏斗”**:

第一阶段:前向传播(积木变小,漏斗向下)

目标:从一堆数据,浓缩成一个代表“错误程度”的数字。

  1. 输入 X (Data):

    • 形状: [16, 4] (16行样本,4列特征) 。

      +1

    • 形象: 一个 16x4 的 Excel 表格。

  2. 参数 w (Weights) 和 b (Bias):

    • 形状: w 是 [4, 1] (4个权重,对应4个特征),b 是 [1] (1个偏置) 。

    • 形象: w 是一条竖着的积木,b 是一个小方块。

  3. 第一步运算:矩阵乘法 x @ w

    • 操作: 用 16x4 的表格 乘以 4x1 的条。

    • 形状变化: [16, 4] * [4, 1] = [16, 1]

    • 结果: 得到了 16 个初步预测值。这一步把 4 个特征“压缩”成了 1 个预测值。

  4. 第二步运算:加偏置 + b

    • 操作: 给这 16 个数每一个都加上 b。

    • 结果 pred_y 形状依然是 [16, 1]。这是模型猜出来的 16 个答案。

  5. 第三步运算:算损失 loss

    • 操作: 拿猜出来的 pred_y (16个) 和真实的 batch_y (16个) 做减法,取绝对值,然后求和取平均

      +1

    • 形状变化关键点: [16, 1] vs [16, 1] → 算出一个平均数。

    • 结果 loss 形状变成了 [] (标量,0维张量)

    • 理解: 无论你有多少数据,最后必须浓缩成唯一个数字(比如 loss = 3.5)。这个数字告诉你:“总的来说,你这次考得很差。”


第二阶段:反向传播(原路返回,指哪打哪)

目标:利用那个唯一的 loss,倒回去告诉 w 和 b 该怎么改。

这就是你困惑的 loss.backward()

  1. 起点: 唯一的数字 loss (比如 3.5)。

  2. PyTorch 的问题: “为了让这 3.5 变小,当初那 4 个 w 分别贡献了多少错误?”

  3. 梯度张量 (w.grad) 的诞生:

    • 因为 w 的形状是 [4, 1],所以算出来的梯度 w.grad 形状必须也是 [4, 1]

    • 它是怎么算出来的?PyTorch 沿着刚才的路径倒推(链式法则)。它发现 w1​ 乘的是 x1​,所以 w1​ 的梯度跟 x1​ 的数据有关。

    • 关键理解: grad 就像是 w 的影子。w 长什么样,grad 就长什么样。


第三阶段:参数更新(微调旋钮)

目标:修改 w 和 b。

  • 代码: w_0 -= w_0.grad * lr

  • 实质: 这是一个点对点的减法。

    • w0​ (形状 [4, 1]) 减去 w0​.grad (形状 [4, 1])。

    • 比如 w0​ 的第一个数是 8.0,对应的梯度是 0.1(意思是这变大导致了错误),学习率是 0.01。

    • 新 w0​=8.0−(0.1×0.01)=7.999。


总结你的困惑

“根据初始化w和b计算pre_y与y之间的loss”

是的,这是一个数据降维的过程(从矩阵 -> 向量 -> 一个标量)。

“然后根据梯度下降,调整w和b”

是的,这是一个形状匹配的过程。 虽然 loss 只是一个数,但因为 PyTorch 记住了它是怎么算出来的,所以它能“反向”分裂出和 w 一样形状的梯度矩阵,告诉每个权重参数该怎么变。

可视化建议: 你看 PDF 第 3 页的那张图 :

  • 左边蓝色的方块是 X(输入)。

  • 中间黄色的圆圈是 w(参数)。

  • 最右边红色的圆圈是 loss

  • 前向传播是从左往右走到红球。

  • 反向传播是从红球出发,沿着线回到黄球,告诉黄球该怎么变。

更多推荐