Datawhale X 李宏毅苹果书AI夏令营 Task02笔记

1.自适应学习率


1.1AdaGrad


       AdaGrad是典型的自适应学习率方法,其能够根据梯度大小自动调整学习率。AdaGrad 可以做到梯度比较的时候,学习率就减小,梯度比较的时候,学习率就放大

       梯度下降更新某个参数 的过程为

                                                                                                                             (1)

       在第 t 个迭代的值减掉在第 t 个迭代参数 i 算出来的梯度

                                                                                                                            (2)

       代表在第 t 个迭代,即时, 参数损失 L 的微分,学习率是固定的

       现在要有一个随着参数定制化的学习率,即把原来学习率 η 变成\frac{\eta }{\delta _{t}^{i}}

                                                                                                                    (3)

        的上标为 i,这代表参数 σ 与 i 相关,不同的参数的 σ 不同。的下标为 t,这代表参数 σ 与迭代相关,不同的迭代也会有不同的 σ。学习率从 η 改成\frac{n}{\delta _{t}^{i}}的时候,学习率就变得参数相关

       参数相关的一个常见的类型是算梯度的均方根。参数的更新过程为

                                                                                                                       (4)

       其中 是初始化参数。而 的计算过程为

                                                                                                                (5)

       其中是梯度。将的值代入更新的公式可知的值是 +1 或 −1。第一次在更新参数,从 更新到 的时候,要么是加上 η,要么是减掉 η,跟梯度的大小无关,这个是第一步的情况。

       第二次更新参数过程为

                                                                                                                       (6)

       其中是过去所有计算出来的梯度的平方的平均再开根号,即均方根。同样的操作反复继续下去,第 t + 1 次更新参数的时候,\frac{n}{\delta _{t}^{i}}当作是新的学习率来更新参数。

1.2RMSProp

       MSprop 第一步跟 Adagrad 的方法是相同的,即

                                                                                                                  (7)

       第二步更新过程为

                                                                                                                       (8)

                                                                                              (9)

       其中 0 < α < 1,其是一个可以调整的超参数。计算 的方法跟 AdaGrad 算均方根不一样,在 RMSprop 里面,可以自己调整现在的这个梯度的重要性。如果 α 设很小趋近于 0,代表相较于之前算出来的梯度而言,比较重要;如果 α 设很大趋近于 1,代表比较不重要,之前算出来的梯度比较重要。

       同样的过程就反复继续下去。

       RMSProp 通过 α 可以决定,相较于之前存在里面的的重要性有多大。如果使用 RMSprop,就可以动态调整这一项。下图中黑线是误差表面,球就从 A 走到 B,AB 段的路很平坦,g 很小,更新参数的时候,我们会走比较大的步伐。走动BC 段后梯度变大了,AdaGrad 反应比较慢,而 RMSprop 会把 α 设小一点,让新的、刚看到的梯度的影响比较大,很快地让 \delta _{t}^{i} 的值变大,很快地让步伐变小,RMSprop 可以很快地“踩刹车”。如果走到 CD 段,CD 段是平坦的地方,可以调整 α,让其比较看重最近算出来的梯度,梯度一变小,\delta _{t}^{i}的值就变小了,走的步伐就变大了。

2.学习率调度


       如下图所示简单的误差表面,我们都训练不起来,加上自适应学习率以后,使用AdaGrad 方法优化的结果如图 所示。一开始优化的时候很顺利,在左转的时候,有 Ada-Grad 以后,可以再继续走下去,走到非常接近终点的位置。走到 BC 段时,因为横轴方向的梯度很小,所以学习率会自动变大,步伐就可以变大,从而不断前进。接下来的问题走到图中红圈的地方,快走到终点的时候突然“爆炸”了。是把过去所有的梯度拿来作平均。在 AB段梯度很大,但在 BC 段,纵轴的方向梯度很小,因此纵轴方向累积了很小的,累积到一定程度以后,步伐就变很大,但有办法修正回来。因为步伐很大,其会走到梯度比较大的地方。走到梯度比较大的地方后,会慢慢变大,更新的步伐大小会慢慢变小,从而回到原来的路线。

       通过学习率调度可以解决这个问题。之前的学习率调整方法中 η 是一个固定的值,而在学习率调度中 η 跟时间有关,如式(10)所示。学习率调度中最常见的策略是学习率衰减,也称为学习率退火

                                                                                                                  (10)

       随着参数的不断更新,让 η 越来越小,如果加上学习率下降,可以很平顺地走到终点,如下图所示,在红圈的地方,虽然步伐很大,但 η 变得非常小,步伐乘上 η 就变小了,就可以慢慢地走到终点。

                        

       除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法是让学习率 先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。残差网络[8] 里面是有预热的,在残差网络里面,学习率先设置成 0.01,再设置成 0.1,并且其论文还特别说明,一开始用 0.1 反而训练不好。除了残差网络,BERT 和 Transformer 的训练也都使用了预热。

3.优化总结

       所以我们从最原始的梯度下降,进化到这一个版本

                                                                                                                (11)

       其中是动量。这个是目前优化的完整的版本,这种优化器除了 Adam 以外,还有各种变形。但其实各种变形是使用不同的方式来计算,或者是使用不同的学习率调度的方式。

4.分类与回归


       分类回归是深度学习最常见的两种问题。

4.1分类与回归的关系


       回归是输入一个向量 x,输出 yˆ,我们希望 yˆ 跟某一个标签 y 越接近越好,y 是要学习的目标。而分类当作回归来看,输入 x 后,输出仍然是一个标量 yˆ,要让它跟正确答案的那个类越接近越好。yˆ 是一个数字,我们可以把类也变成数字。实际上,在做分类的问题的时候,比较常见的做法是用独热向量表示类。

       如果有三个类,标签 y 就是一个三维的向量,比如类 1 是,类 2 是 ,类3是.如果每个类都用一个独热向量来表示,就没有类 1 跟类 2 比较接近,类 1 跟类 3 比较远的问题。如果用独热向量计算距离的话,类两两之间的距离都是一样的。如果目标 y 是一个向量,比如 y 是有三个元素的向量,网络也要输出三个数字才行。如图所示,输出三个数值就是把本来输出一个数值的方法,重复三次。把乘上三个不同的权重,加上偏置,得到 yˆ1;再把乘上另外三个权重,再加上另外一个偏置得到 yˆ2;把再乘上另外一组权重,再加上另外一个偏置得到 yˆ3。输入一个特征向量,产生 跟目标越接近越好。

4.2带有softmax的分类

       按照上述的设定,分类实际过程是:输入 x,乘上 W,加上 b,通过激活函数 σ,乘上W′,再加上 b′ 得到向量 yˆ。但实际做分类的时候,往往会把 yˆ 通过 softmax 函数得到 y′,才去计算 y′ 跟 yˆ 之间的距离。

       softmax 的计算如式(12)所示,先把所有的 y 取一个指数,,再对其做归一化得到 y′。

                                                                               (12)

       下图是softmax 的块,输入,产生。比如,取完指数的时候,exp(3) = 20、exp(1) = 2.7 和 exp(−3) = 0.05,做完归一化后,就变成 0.88、0.12 跟 0。−3取完指数,再做归一化以后,会变成趋近于 0 的值。所以 softmax 除了归一化,让 y′1、y′2 和y′3,变成 0 到 1 之间,和为 1 以外,它还会让大的值跟小的值的差距更大。

       上图考虑了三个类的状况,两个类也可以直接套 softmax 函数。但一般有两个类的时候,我们不套 softmax,而是直接取 sigmoid。当只有两个类的时候,sigmoid 和 softmax 是等价的。

4.3分类损失


       当我们把 x 输入到一个网络里面产生 yˆ 后,通过 softmax 得到 y′,再去计算 y′ 跟 y 之间的距离 e,如下图所示

       计算 y′ 跟 y 之间的距离不只一种做法,可以是如式 (13) 所示的均方误差,即把 y 里面每一个元素拿出来,计算它们的平方和当作误差。

                                                                                                                    (13)

       但是如式(14)所示的交叉熵更常用,当 yˆ 跟 y′ 相同时,可以最小化交叉熵的值,此时均方误差也是最小的。最小化交叉熵其实就是最大化似然

                                                                                                                      (14)

       接下来从优化的角度来说明相较于均方误差,交叉熵是被更常用在分类上。如图 3.35 所示,有一个三类的分类,网络先输出,在通过 softmax 以后,产生。假设正确答案是,要计算之间的距离 e,e 可以是均方误差或交叉熵。假设 y1 的变化是从-10 到 10,y2 的变化也是从-10 到 10,y3 就固定设成-1000。因为 y3 的值很小,通过 softmax 以后,y′3 非常趋近于 0,它跟正确答案非常接近,且它对结果影响很少。总之,我们假设 y3 设一个定值,只看 y1 跟 y2 有变化的时候,对损失 e 的影响。

       下图是分别在 e 为均方误差和交叉熵时,y1、y2 的变化对损失的影响,对误差表面的影响,红色代表损失大,蓝色代表损失小。如果 y1 很大,y2 很小,代表 y′1 会很接近 1,y′2会很接近 0。所以不管 e 取均方误差或交叉熵,如果 y1 大、y2 小,损失都是小的;如果 y1小,y2 大,y′1 是 0,y′2 是 1,这个时候损失会比较大。

       上图中左上角损失大,右下角损失小,所以期待最后在训练的时候,参数可以“走”到右下角的地方。假设参数优化开始的时候,对应的损失都是左上角。如果选择交叉熵,如图(a) 所示,左上角圆圈所在的点有斜率的,所以可以通过梯度,一路往右下的地方“走”;如果选均方误差,如图(b) 所示,左上角圆圈就卡住了,均方误差在这种损失很大的地方,它是非常平坦的,其梯度是非常小趋近于 0 的。如果初始时在圆圈的位置,离目标非常远,其梯度又很小,无法用梯度下降顺利地“走”到右下角。

       因此做分类时,选均方误差的时候,如果没有好的优化器,有非常大的可能性会训练不起来。如果用 Adam,虽然图(b) 中圆圈的梯度很小,但 Adam 会自动调大学习率,还有机会走到右下角,不过训练的过程比较困难。总之,改变损失函数可以改变优化的难度。

实践:HW3(CNN)卷积神经网络-图像分类


1.获得的数据集和代码文件


       先进入魔搭社区,创建一个实例,启动,然后点击进入JupyterLab。接着,点击“Terminal”打开命令行窗口,输入以下代码并按下回车键。稍等片刻,数据集和代码文件(notebook)将会自动下载,大约需要一分钟。

git clone https://www.modelscope.cn/datasets/Datawhale/LeeDL-HW3-CNN.git


       初始化追踪器:stale 和 best_acc 用于追踪训练过程中的损失和准确率。stale 表示连续没有改进的轮数,当 stale 大于设定的阈值 patience 时,提前停止训练。

       训练阶段:在训练阶段,首先确保模型处于训练模式,然后遍历训练数据加载器 train_loader 中的每个批次。对于每个批次,将图像数据 imgs 和对应的标签 labels 传递给模型,计算输出 logits。然后计算交叉熵损失 loss,并清除上一步中参数中存储的梯度。计算参数的梯度,并进行梯度裁剪以稳定训练。最后更新模型参数。

       验证阶段:在验证阶段,首先确保模型处于评估模式,然后遍历验证数据加载器 valid_loader 中的每个批次。对于每个批次,将图像数据 imgs 和对应的标签 labels 传递给模型,计算输出 logits。计算损失 loss 和准确率 acc。

       打印训练和验证信息:在训练和验证阶段,打印当前轮次的损失和准确率。在验证阶段,如果当前轮次的准确率高于最佳准确率 best_acc,则更新 best_acc 和保存模型。

       保存模型:在训练过程中,如果找到更好的模型,则保存模型参数。


2.一键运行代码

 初始化追踪器,这些不是参数,不应该被更改
stale = 0
best_acc = 0
 
for epoch in range(n_epochs):
    # ---------- 训练阶段 ----------
    # 确保模型处于训练模式
    model.train()
 
    # 这些用于记录训练过程中的信息
    train_loss = []
    train_accs = []
 
    for batch in tqdm(train_loader):
        # 每个批次包含图像数据及其对应的标签
        imgs, labels = batch
        # imgs = imgs.half()
        # print(imgs.shape,labels.shape)
 
        # 前向传播数据。(确保数据和模型位于同一设备上)
        logits = model(imgs.to(device))
 
        # 计算交叉熵损失。
        # 在计算交叉熵之前不需要应用softmax,因为它会自动完成。
        loss = criterion(logits, labels.to(device))
 
        # 清除上一步中参数中存储的梯度
        optimizer.zero_grad()
 
        # 计算参数的梯度
        loss.backward()
 
        # 为了稳定训练,限制梯度范数
        grad_norm = nn.utils.clip_grad_norm_(model.parameters(), max_norm=10)
 
        # 使用计算出的梯度更新参数
        optimizer.step()
 
        # 计算当前批次的准确率
        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()
 
        # 记录损失和准确率
        train_loss.append(loss.item())
        train_accs.append(acc)
 
    train_loss = sum(train_loss) / len(train_loss)
    train_acc = sum(train_accs) / len(train_accs)
 
    # 打印信息
    print(f"[ 训练 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")
 
    # ---------- 验证阶段 ----------
    # 确保模型处于评估模式,以便某些模块如dropout能够正常工作
    model.eval()
 
    # 这些用于记录验证过程中的信息
    valid_loss = []
    valid_accs = []
 
    # 按批次迭代验证集
    for batch in tqdm(valid_loader):
        # 每个批次包含图像数据及其对应的标签
        imgs, labels = batch
        # imgs = imgs.half()
 
        # 我们在验证阶段不需要梯度。
        # 使用 torch.no_grad() 加速前向传播过程。
        with torch.no_grad():
            logits = model(imgs.to(device))
 
        # 我们仍然可以计算损失(但不计算梯度)。
        loss = criterion(logits, labels.to(device))
 
        # 计算当前批次的准确率
        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()
 
        # 记录损失和准确率
        valid_loss.append(loss.item())
        valid_accs.append(acc)
        # break
 
    # 整个验证集的平均损失和准确率是所记录值的平均
    valid_loss = sum(valid_loss) / len(valid_loss)
    valid_acc = sum(valid_accs) / len(valid_accs)
 
    # 打印信息
    print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
 
    # 更新日志
    if valid_acc > best_acc:
        with open(f"./{_exp_name}_log.txt", "a"):
            print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> 最佳")
    else:
        with open(f"./{_exp_name}_log.txt", "a"):
            print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
 
    # 保存模型
    if valid_acc > best_acc:
        print(f"在第 {epoch} 轮找到最佳模型,正在保存模型")
        torch.save(model.state_dict(), f"{_exp_name}_best.ckpt")  # 只保存最佳模型以防止输出内存超出错误
        best_acc = valid_acc
        stale = 0
    else:
        stale += 1
        if stale > patience:
            print(f"连续 {patience} 轮没有改进,提前停止")
            break


       在运行过程中会遇到

       以下是我运行后的结果

3.关闭实例


       运行完成后记得关闭实例。

4.“泛化”模型的推导


声明 


        本学习笔记为此笔记发布者使用,以便完成Datawhale AI 夏令营第五期Task02打卡任务,同时记录自己的学习过程。该笔记在原文的基础上进行删改,对个别部分用黑体或者其他字体着重强调以便及时记忆理解。本笔记并不做任何以盈利为目的的用途,仅供个人学习使用,若有侵权,请第一时间告知本人,侵删。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值