# ===================================================================== # 用「梯度下降」拟合一条直线 —— 神经网络最基础的原理演示 # --------------------------------------------------------------------- # 场景:已知 100 套房子的「面积」和「成交价」,想自动学出一个估价公式: # 价格 = w * 面积 + b # 其中 w 是「每平米单价」(直线的斜率),b 是「基价」(直线的截距)。 # # 神经网络学习一件事,永远只有三步,循环往复: # ① 预测:用当前的 w、b 算一个预测值 h # ② 算误差:拿预测值 h 和真实值 y 比较,得到一个「损失 loss」 # ③ 调参:沿「让 loss 变小的方向」微调 w 和 b # 下面每一行代码,你都能对应到这三步中的某一步。 # ===================================================================== import random import matplotlib.pyplot as plt # ---------- ① 初始化参数 ---------- # 一开始我们「随便猜」w 和 b,random.random() 会得到 [0,1) 之间的随机数。 # 注意:真实值是 w=3、b=10,我们故意从很远的 0~1 开始猜, # 这样才能看出「梯度下降」是如何一步步把错误答案纠正过来的。 w = random.random() # 猜一个「每平米单价」 b = random.random() # 猜一个「基价」 # ---------- ② 造一批「带噪声」的训练数据 ---------- _x = [i / 100 for i in range(100)] # 面积:0.00 ~ 0.99,共 100 个点 _y = [3 * e + 10 + (random.random() - 0.5) for e in _x] # 真实价格 = 3*面积 + 10 + 噪声 # 上面 _y 为什么要加 (random.random() - 0.5)? # 现实数据永远有误差(同面积房子价格不会一模一样)。这个式子 = 噪声,范围 [-0.5, 0.5], # 均值恰好是 0,所以「整体上」数据仍然服从 y = 3x + 10,最后学出来 w≈3、b≈10。 # 如果只加 random.random()(均值 0.5),那真实截距会变成 10.5,初学者容易困惑。 # ---------- ③ 开始训练 ---------- # 原来的代码是 `while True:` —— 这是个「死循环」,程序永远停不下来,所以跑不动。 # 我们改成「固定轮数」:一轮(epoch) = 把 100 个数据点全部看一遍。 # 300 轮足够让 w、b 收敛了。 EPOCHS = 300 # 训练轮数 LR = 0.01 # 学习率(learning rate):每次调参数的「步子大小」 plt.ion() # 打开 matplotlib 的「交互模式」:画图时程序不卡住,能继续往下跑 # (这个只需要开一次,所以放在循环外面) for epoch in range(EPOCHS): # 内层 for:把 100 个样本逐个过一遍,每看一个样本就微调一次 w 和 b。 # (这种做法叫「随机梯度下降 SGD」,是训练神经网络的常见方式) for x, y in zip(_x, _y): # —— 第 1 步:预测 —— h = w * x + b # 用当前的 w、b 算出「预测价格」 # —— 第 2 步:算误差(损失函数)—— loss = (y - h) ** 2 # 差的平方:预测偏离真实越远,loss 越大 # 平方有两点好处:① 正负误差都算数;② 好求导 # —— 第 3 步:求「梯度」,也就是 loss 对 w、b 的偏导数 —— # 为什么是这两个式子?用链式法则对 loss=(y-h)^2 求导: # h = w*x + b,所以 dh/dw = x,dh/db = 1 # d(loss)/dw = 2*(y-h) * d(y-h)/dw = 2*(y-h) * (-x) = -2*x*(y-h) # d(loss)/db = 2*(y-h) * d(y-h)/db = 2*(y-h) * (-1) = -2*(y-h) # 梯度告诉我们:w、b 往哪个方向挪,loss 会「上升」。所以我们要「反着」挪。 dw = -2 * x * (y - h) # loss 对 w 的偏导数 db = -2 * (y - h) # loss 对 b 的偏导数 # —— 第 4 步:沿「让 loss 变小」的方向更新参数 —— # w -= dw * LR 的意思是:w 朝 dw 的反方向走一小步,步长 = LR * |dw|。 # 直观理解(只看 b): # 预测偏低时 h0,于是 db<0,b -= 负数 → b 变大 → 预测抬高 ✅ # 预测偏高时 h>y,(y-h)<0,于是 db>0,b -= 正数 → b 变小 → 预测压低 ✅ w -= dw * LR # 学习率控制每步走多大 b -= db * LR # —— 每结束一轮,画一次图、打印一次进度 —— # plt.cla() 清空上一帧,避免线条越叠越多;plt.pause() 让画面刷新出来。 plt.cla() plt.plot(_x, _y, 'r', label='真实数据') # 红点:真实房价 plt.plot(_x, [w * e + b for e in _x], label='拟合直线') # 蓝线:我们学到的直线 plt.legend() plt.pause(0.01) print(f'epoch:{epoch:3d} w:{w:.3f} b:{b:.3f} loss:{loss:.4f}') # 训练结束后:关掉交互模式,让窗口保留,方便观察最终结果 plt.ioff() plt.show() print(f'\n训练完成!真实值 w=3, b=10;学到的 w≈{w:.3f}, b≈{b:.3f}')