test01.py 5.0 KB

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