🧪 MNIST先导课程实验报告

课程名称 课程4:梯度爆炸,梯度消失,优化器 日期
学生姓名 班级/学号

🎯一、实验目的

  1. 使用课程2中的参数, 比对各种优化器的收敛速度
  2. 发生梯度爆炸时,各种优化器是否能收敛?
  3. 发生梯度震荡时,各种优化器是否能收敛?
  4. 发生梯度消失时,各种优化器是否能收敛?

🔧 二、实验参数设置

参数名 符号 说明
输入线索 clue
目标值 target
学习率 η
初始权重 w
初始偏置 b
优化器类型 optimizer
  • 实验1:使用 课程 2 中的数据 (称为基线数据): clue=2.0, target=5.0,η=0.1,W=1.2, B=0.8, 观察不同优化器对收敛的影响
  • 实验2:固定 clue=2.0, target=5.0,η=0.1,调整 W 和 B, 观察不同优化器对收敛的影响. 特别地, 设置1-3组 W 和 B为极端值
  • 实验3:固定 clue=2.0, target=5.0,η=0.1,设置1-3组 W 和 B为极端值,观察不同优化器对收敛的影响
  • 实验4:固定初始参数(如 w=0.5, b=-0.5),改变学习率(η=0.01, 0.1, 0.2),观察不同优化器对收敛的影响
  • 实验5:将输入 clue 调整至 比 target 稍大, 引发 SGD 情况下的梯度爆炸, 切换到其他优化器, 观察是否能消除梯度爆炸
  • 实验6:将输入 clue 调整至 比 target 超出很多 (例如50,100,500), 引发 SGD 情况下的梯度爆炸, 切换到其他优化器, 观察是否能消除梯度爆炸
  • 实验7:clue 过小 (如 0.0001),引发梯度消失, 观察各种优化器的收敛速度。

四、代码运行结果

⚠️注意:

  1. 不要用 Early stop 机制, 要反复尝试, 某些情况下, 你能发现损失函数在越过停止点后, 仍能反弹.
  2. 如果发生上述情况, 请记录到 <五、观察与分析> 部分
  3. 如果损失函数和梯度未反弹,请把停止轮次设在停止点附近, 以便让曲线更漂亮.
    例如, 如果在曲线中, 发现: 轮次为 30 时, Loss<0.0001, 那么把总轮次设置为 35 或 40
  4. 最大轮次可以设为 500, 如果总轮次超过最大值后, 损失函数仍未降低到 0.0001, 停止迭代, 也记录到 <五、观察与分析> 部分
  5. 表格按如下方式填写
    • 如果不收敛, 第二列填 ❌

    • 如果正常收敛, 第二列填 ✅

    • 如果快速收敛, 第二列填 ⚡

    • 如果收敛但是有反弹, 第二列填 ℹ️

    • 如果趋向收敛但是收敛太慢, 第二列填 🐛

    • 如果收敛, 第三列写上停止点的轮次, 否则写 Nan

    • 第四列填写观察到的现象

      优化器 是否收敛 停止点轮次 说明
      SGD Nan
      Momentum 20 收敛
      Adagrad ℹ️ 30 收敛但有反弹
      RMSprop 40
      Adam 🐛 趋向收敛, 但收敛太慢
      自定义优化器 1 🐛 趋向收敛, 但收敛太慢
      自定义优化器 2 🐛 趋向收敛, 但收敛太慢

梯度震荡

点击放大显示 梯度震荡

梯度消失

点击放大显示 梯度消失

损失回弹

点击放大显示 损失回弹

正常收敛

点击放大显示 正常收敛

📊 4.1 基线数据

参数:线索clue=\(3\),目标T=\(2\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)

各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD 29
Momentum 22 损失在22轮后有小幅回升
Adagrad 40 收敛较慢
RMSprop 4 下降过快
Adam 7 梯度反复震荡

📊 4.2 不同的 W 和 B

参数:线索clue=\(3\),目标T=\(2\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(12\), 偏置b=\(-8\)

各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD 🐛 69 收敛过慢
Momentum 59 损失有多次回弹
Adagrad Nan 收敛极慢,停止学习
RMSprop 🐛🐛 200+ 收敛过慢
Adam 400+ 收敛超级慢

📊 4.3 W 和 B 为极端值

参数:线索clue=\(3\),目标T=\(2\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(-120\), 偏置b=\(-800\)

各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD 110
Momentum ℹ️ 166 收敛但有反弹
Adagrad Nan 梯度消失
RMSprop Nan 收敛缓慢
Adam Nan 收敛缓慢

📊 4.4 不同的学习率对比

参数:线索clue=\(3\),目标T=\(2\),学习率 \(\eta\)=\(0.1\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)

各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD 1
Momentum ℹ️ 5 收敛但有反弹
Adagrad 4
RMSprop 3
Adam ℹ️ 4 收敛但有反弹

📊 4.5 Clue 比 target 略大

参数:线索clue=\(10\),目标T=\(8\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)
各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD Nan 梯度震荡
Momentum ℹ️ 71 收敛但有反弹
Adagrad Nan 收敛缓慢
RMSprop 6
Adam 69

📊 4.6 Clue 超大

参数:线索clue=\(100\),目标T=\(10\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)
各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD Nan 梯度爆炸
Momentum Nan 梯度爆炸(但是比SGD好一点)
Adagrad Nan 收敛过慢
RMSprop 43
Adam Nan 收敛过慢(但是比Adagrad好一点)

📊 4.7 Clue 过小, 正常学习率 0.1

参数:线索clue=\(0.01\),目标T=\(2\),学习率 \(\eta\)=\(0.01\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)
各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD 🐛 237
Momentum 🐛 194
Adagrad Nan 收敛过慢
RMSprop 25
Adam 🐛 264

📊 4.8 Clue 过小 (例如 0.001), 超小学习率 0.0001

参数:线索clue=\(0.01\),目标T=\(2\),学习率 \(\eta\)=\(0.0001\), 初始值:权重W=\(1.2\), 偏置b=\(0.8\)
各个优化器的损失变化曲线和梯度变化曲线
填写如下对比表格.

优化器 是否收敛 停止点轮次 说明
SGD Nan 收敛过慢
Momentum Nan 收敛过慢
Adagrad Nan 梯度消失
RMSprop Nan 收敛过慢
Adam Nan 收敛过慢

🎉 五、观察与分析

  1. 基线数据下, 观察到什么现象? 评估一下各个优化器?RMSprop 较优
  2. 不同 W 和 B 时, 观察到什么现象? 评估一下各个优化器?Momentum 较优
  3. 极端的 W 和 B , 观察到什么现象? 评估一下各个优化器?SGD 较优
  4. 不同的学习率, 观察到什么现象? 评估一下各个优化器?RMSprop 较优
    特别是, 如果学习率过小, 哪个优化器能较快收敛?
  5. Clue 比 target 略大时, 观察到什么现象? 评估一下各个优化器?RMSprop 较优
  6. Clue 为极端值, 观察到什么现象? 评估一下各个优化器?RMSprop 较优
  7. Clue 过小, 观察到什么现象? 评估一下各个优化器?RSMprop 较优
  8. 学习率过小,什么优化器都没用

✨ 六、实验结论

优化器可以更快的收敛,但是要根据实际情况选择合适的优化器。


💫 七、思考题

大多数情况下选择Adam优化器


八、附录:实验代码

(可附上本次实验的核心代码片段,或说明代码存放位置)


from datetime import datetime
from pydraw import pydraw
import optimizer
def training_model_4  (nbof_epoch:int, clue:float, target:float, lr:float, w:float, b:float):

    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    desc = f"clue={clue}, t={target}, lr={lr}, w={w}, b={b}"

    title = ""
    title += "| 轮次 "
    title += r"| $\boxed{S5}$ 权重W= "
    title += r"| $\boxed{S6}$ 偏置B= "
    title += r"| $\boxed{S0}$ 预测值pred= "
    title += r"| $\boxed{S1}$ 损失Loss= "
    title += r"| $\boxed{S2}$ 输出的梯度 $G_{pred}$= "
    title += r"| $\boxed{S3}$ 权重的梯度 $G_w$= "
    title += r"| $\boxed{S4}$ 偏置的梯度 $G_b$= "

    print (title)
    #print (f"|{'轮次':^5} | {'权重W=':^8} | {'偏置B':^8} | {'预测值':^7} | {'损失':^10} | {'输出梯度G':^8} | {'梯度w':^10} | {'梯度b':^10} |")
    print (f"|{'-'*8}|{'-'*12}|{'-'*12}|{'-'*12}|{'-'*14}|{'-'*14}|{'-'*14}|{'-'*14}|")

    hintstr = r"|        |$W-\eta \times G_w$|$B-\eta \times G_b$|$W\times clue+B$|$(pred-T)^2$|$2 \times (pred-T)$  |$G_{pred} \times clue$ |$G_{pred}$      |"
    print (hintstr)

    # opt = optimizer.SGD (lr=lr)
    # opt = optimizer.Momentum (lr=lr, beta=0.9)
    # opt = optimizer.Adagrad (lr=lr)
    # opt = optimizer.RMSprop (lr=lr)
    opt = optimizer.Adam (lr=lr)

    draw = pydraw ()
    draw.xlabel = "Epoch"
    draw.ylabel = "Value"
    draw.title = "课程 2 (y = W * X + B), 优化器 - " + opt.name
    #draw.title = "课程 2 (y = W * X + B) - 正常梯度下降"
    #draw.title = "课程 2 (y = W * X + B) - 梯度消失"
    
    # 设置图例显示名称(可选)
    draw.set_label("loss", "Loss")
    draw.set_label("grad_pred", "Gradient of pred")
    draw.set_label("grad_w", "Gradient of w")

    found = None
    
    for epoch in range (nbof_epoch):
        # 预测
        pred = w * clue + b
        
        # 损失和梯度
        loss =  (pred - target) ** 2
        grad_pred = 2 *  (pred - target)
        grad_w = grad_pred * clue
        grad_b = grad_pred
        
        # 显示
        #if epoch % 2 == 0 or epoch < 5:
        print (f"| {epoch:^6} | {w:^10.4f} | {b:^10.4f} | {pred:^10.4f} "
                  f"| {loss:^12.4f} | {grad_pred:^12.4f} | {grad_w:^12.4f} | {grad_b:^12.4f} |")
        
        draw.add (loss, name="loss")
        draw.add (grad_pred, name="grad_pred")
        draw.add (grad_w, name="grad_w")

        # 在特定点添加注释
        if found is None and loss < 0.0001:
            draw.annotate ("Loss<0.0001", xy=(epoch, loss))
            found = epoch

        # 更新
        #w -= lr * grad_w
        #b -= lr * grad_b

        # 更新
        step_w, step_b = opt.compute_step (grad_w, grad_b)
        w -= step_w
        b -= step_b

    # 添加底部描述
    draw.description = desc + f", Loss<0.0001 on epoch={found}, final loss={loss:.8f}" + ", " + timestamp

    # draw.export ("doc/media-class4/training_curves_cluebig_SGD.png")
    draw.draw ()

clue = 0.01      # 线索, 即预测的初始值
#clue = 0.1         # 线索, 即预测的初始值
target = 2       # 我心中的目标值
#learn_rate = 0.027   # 学习率, 神奇的震荡, 固定值
#learn_rate = 0.028   # 学习率, 神奇的震荡, 渐增
#learn_rate = 0.029   # 学习率, 神奇的震荡, 快增
learn_rate = 0.001   # 学习率
w = 1.2         # 权重
#w = -1.3            # 权重
b = 0.8            # 偏置

print (f"权重和偏置的训练表格(线索clue={clue},目标T={target},学习率={learn_rate}, 权重W={w}, 偏置B={b})")
print ("")
training_model_2  (500, clue, target, learn_rate, w, b)