Datawhale X 李宏毅苹果书 AI夏令营:学习笔记三

148 阅读9分钟

三、深度学习基础(续)

换个思路 —— 调整误差表面

原因

计算损失函数的逻辑链:权重 wi → wi*xi → y^\hat{y}yy 之间的距离 e → 求和得到损失LL

xi值越大,L对权重wi的变化越敏感,wi一变L就变了,那么这个维度上梯度就很陡峭。若另一个维度上xi值很小,L对wi不太敏感,那么梯度较小。这会导致不同维度方向的斜率与坡度非常不同,误差表面崎岖,不好训练。

除了上述的调整学习率,还可以直接改变误差表面,让各维度上的特征数值在相近的范围里。

General —— 特征归一化

缩小各个特征维度的数值差距,比如z-score标准化。可以在各个层对输入做归一化。

  • 优点制造好训练的误差表面,使得损失收敛更快,训练更稳定。
  • 经验:若选择sigmoid,推荐对 z = wx 也做归一化。

Special —— 批量归一化

对全部数据归一化计算量太大(e.g.上百万个μ和σ),故只对一个批量的样本做归一化。

  • 适用:批量大小比较大的时候,这时批量分布更接近整体分布。
  • 经验:①sigmoid不加批量归一化,训练会很困难。②做批量归一化后,误差表面会光滑,学习率可以大一些。
  • 步骤:通常有三阶段(x、z*2),第三阶段用β和γ调整分布,使隐藏层输出的均值不是0,减少0的负面影响。 8948e59899e43c0dc3f1ca8c6a0078a.jpg
  • 测试阶段:不用批量,用训练阶段的移动平均。p是一个超参数c7d44314afd4aae751d8311d30a60df.png
pμt1+(1p)μtμtp\overline{\mu}_{t-1}+(1-p)\mu^{t} \rightarrow \overline{\mu}_t
  • 奏效原因:解决内部协变量偏移问题

    我们在计算B更新到B'的梯度时,前一层的参数是A,或者前一层的输出是a。那当前一层从A变成A'的时候,其输出就从a变成a'。但是我们计算这个梯度的时候,是根据a算出来的,所以这个更新的方向也许适合用在a上,但不适合用在a'上。

    每次都做批量归一化的话,就会让a与a'的分布比较接近,也许会对训练有所帮助。 9c179b425f91af710659242e2793193.jpg

四、卷积神经网络

以下知识都是基于图像分类任务的。

4.1 基本流程

  • 图像描述:三维张量,宽×高×通道,比如RGB就是红绿蓝3个通道

  • 图像输入:把图像调整成统一尺寸,再“拉直”张量,变成向量

  • 全连接网络:每个神经元跟输入的每个维度都有一个权重,容易过拟合

  • 模型输出:softmax,得到 y^\hat{y}

e9fabc97dfeb66c4e25a025ddcd86e6.jpg

4.2 简化成卷积网络

① 感受野:检测特定的模式不需要整张图像

感受野可大可小,可重叠可平行,可长方形可正方形,可单通道可全通道。

2ca11d6f62d0c454d636fad96adab6f.jpg

  • 一般感受野会看全部通道,即深度 = 通道数,宽×高 → 卷积核

  • 一般同一个感受野会有一组神经元去守备这个范围

  • 一个感受野挪到下一个的距离为步幅,是一个超参数,一般设为1or2(希望重叠,识别图片每个角落)

  • 边界缺少输入,就做填充,一般是0

    e7e83405721dea3372e6ac2bc07efcc.jpg

② 滤波器:同样的模式可能会出现在图像的不同区域

在图片不同位置去检测同一个模式时,可以做参数共享,即两个神经元的感受野不同、参数权重完全一样,这些参数称为滤波器(核大小 × 通道数)。

  • 因为输入不同,就算共用参数,神经元们的输出也不会是一样的。
  • 神经元是有偏置的,滤波器也是有偏置的。

例如右下图,一个6×6×1的图像,经滤波器1就能检测出对角全为1的模式(数值为3)。

5a692b35450c2f7f617e27bd0ff1319.jpg

不同滤波器是不同参数,如果有64个滤波器,最后就得到64组4×4的数值结果,称为特征映射(尺寸为4×4×64)。

特征映射可以看成一张图片,作为下一个卷积层的输入,下一个卷积层的滤波器尺寸需为3×3×64(通道来自特征映射的64)。

  • 感受野可以一直保持3×3:在第二个卷积层的3×3时,对应原图像已经为5×5。故,网络叠得越深,滤波器看的范围会越来越大。

③ 下采样:适当缩减图像不影响模式检测

下采样(downsampling)后图像主体依然可识别,而图像大小仅为原来的1/4。

汇聚:类似一种操作符、行为命令,例如最大汇聚(max pooling)、平均汇聚(mean pooling)。

  • 图像变小,而通道数不变
  • 减少运算量
  • 不是所有任务都需要汇聚(例如下围棋),因为会丢失信息

4.3 总结

卷积层的优势

全连接层卷积层(感受野+滤波器)
弹性大弹性小
可以看整张图像,也可看小范围(把某些权重设置为0)增加了感受野和滤波器的限制
容易过拟合不容易过拟合
可以完成各种任务专为图像设计的,迁移任务需考虑相似性

典型的图像识别网络架构

卷积网络的应用:下围棋

下围棋可以看成一个分类问题,通过网络去预测下一步应该落子的最佳位置,即一个有19×19(棋盘大小)个类别的分类问题。

比如,黑子为1,白子为-1,无子为0,把棋盘表示成向量输入,而滤波器的大小和个数是需要考虑的下棋策略。

卷积网络的缺点

不能处理图像放大、缩小、旋转的问题。因此,图像识别任务中通常需要数据增强。

五、实践:CNN图像分类(续)

baseline 的验证集准确率为59.11%,loss为1.2219。

5.2 优化尝试

① 调整为ResNet

残差网络(ResNet)通过引入跳跃连接(skip connections),有效解决了深度网络中的梯度消失和梯度爆炸问题。这些残差连接允许信息在网络中直接跨层传递,使得极深的网络也能顺利训练。这不仅提高了网络的稳定性,还加快了收敛速度,从而进一步提升模型的整体表现。此外,残差结构在多个现代深度网络中得到了广泛应用,证明了其在处理深度网络优化挑战中的有效性。

模型处修改:

import torch.nn as nn
import torch.nn.functional as F

class BasicBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.downsample = downsample
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        identity = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        
        if self.downsample is not None:
            identity = self.downsample(x)
        
        out += identity
        out = self.relu(out)
        return out

class ResNetClassifier(nn.Module):
    def __init__(self, block, layers, num_classes=11):
        super(ResNetClassifier, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)

    def _make_layer(self, block, out_channels, blocks, stride=1):
        downsample = None
        if stride != 1 or self.in_channels != out_channels:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channels, out_channels, kernel_size=1, stride=stride),
                nn.BatchNorm2d(out_channels),
            )

        layers = []
        layers.append(block(self.in_channels, out_channels, stride, downsample))
        self.in_channels = out_channels
        for _ in range(1, blocks):
            layers.append(block(out_channels, out_channels))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

配置处修改:

model = ResNetClassifier(BasicBlock, [2, 2, 2, 2]).to(device)

最佳模型在第5轮找到,验证集准确率为59.30%(优于baseline),loss为1.2785。

image.png

  • 事实上,在第八轮训练时,训练集的准确率已达到83.18%,而验证集准确率下降到57.70%,应该是出现了过拟合状况。

② 数据增强

根据GPT-4o的建议,以及卷积网络的缺陷,数据预处理处修改:

train_tfm = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.2),           # 20%概率水平翻转
    transforms.RandomRotation(degrees=10),            # 随机旋转 ±10°
    transforms.RandomResizedCrop(size=(128, 128),     # 随机裁剪并缩放至128*128
                                 scale=(0.9, 1.0)),
    transforms.ColorJitter(brightness=0.1,            # 颜色抖动
                           contrast=0.1,
                           saturation=0.1,
                           hue=0.1),
    transforms.ToTensor(),
])

最佳模型在第7轮找到,验证集准确率为51.70%(差于baseline),loss为1.3631。

image.png

  • 验证集与训练集的表现相似,说明过拟合问题得到了缓解。

  • loss相对较高、准确率相对较低,可能因为优化没有做好,或者数据增强过度,也可能是训练轮次不够(因为loss一直在降低,还未趋缓)。

  • 也尝试了一下如何增加对抗样本:

    !pip install torchattacks
    
    # 定义对抗攻击方法
    import torchattacks
    def adversarial_attack(model, x, y):
        # 确保模型处于评估模式
        model.eval()
        # 初始化PGD攻击器
        attack = torchattacks.PGD(model, eps=0.3, alpha=0.01, steps=40)
        # 生成对抗样本
        adv_perturbation = attack(x, y)
        return adv_perturbation
    

    训练处修改【损失=原样本loss+对抗样本loss,准确率=(原样本准确率+对抗样本准确率)/2】:

        for batch in tqdm(train_loader):
            # 每个批次包含图像数据及其对应的标签
            imgs, labels = batch
    
            # 生成对抗样本
            adv_imgs = adversarial_attack(model, imgs.to(device), labels.to(device))
    
            # 正常样本的前向传播
            logits = model(imgs.to(device))
            loss = criterion(logits, labels.to(device))
    
            # 对抗样本的前向传播
            adv_logits = model(adv_imgs.to(device))
            adv_loss = criterion(adv_logits, labels.to(device))
    
            # 合并损失
            total_loss = loss + adv_loss
    
            # 清除上一步中参数中存储的梯度
            optimizer.zero_grad()
    
            # 计算参数的梯度
            total_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()
            adv_acc = (adv_logits.argmax(dim=-1) == labels.to(device)).float().mean()
    
            # 记录损失和准确率
            train_loss.append(total_loss.item())
            train_accs.append((acc + adv_acc) / 2)
    

    但是效果很不好,还需要进一步学习和改进方法。

③ ResNet + 数据增强

基于上述结果分析,我将训练轮次改到12,并使用了ResNet+数据增强。

最佳模型在第11轮找到,验证集准确率为60.36%(优于baseline),loss为1.2234。

image.png

  • ResNet的确可以提高模型表现,即使在数据增强效果一般的情况下。

训练结果汇总

评估指标baselineResNet数据增强ResNet + 数据增强
验证集准确率59.11%59.30%51.70%60.36%
验证集损失1.22191.27851.36311.2234

从损失来看,后几列的loss都比baseline高,可能优化方法还可以进一步改进。

其他的优化方法

  • 数据增强

    • 几何变换:RandomRotation、RandomAffine、RandomHorizontalFlip、RandomCrop、RandomResizedCrop、Pad、RandomAffine

    • 颜色变换:ColorJitter

    • 噪声添加:高斯噪声、椒盐噪声

    • 高级技术:混合增强、随机擦除、对抗样本

  • 改变网络结构

    • AlexNet:作为深度学习在图像分类领域的开创性架构,AlexNet通过较大的卷积核和ReLU激活函数引领了深度学习的应用潮流。

    • VGG:使用多层3x3卷积核构建深层网络,既简化了网络设计又提升了模型的准确性,成为多个任务的基准模型。

    • Inception:引入多路径结构,使得网络能够在不同的尺度上并行提取特征,有效提高了计算效率和模型的表现力。

    • ResNet(残差网络):引入跳跃连接(skip connections),有效解决了深度网络中的梯度消失和梯度爆炸问题,使得训练非常深的网络成为可能。

  • 正则化技术

    • 如L2正则化、Dropout、Batch Normalization等,以防止过拟合。
  • 优化激活函数

    • 选择适当的激活函数,如ReLU、Leaky ReLU、Swish等。
  • 优化算法

    • 采用先进的优化算法如Adam、RMSprop或学习率调度器。(可以Adam + 学习率调度)
  • 约束初始化权重

    • 利用He或Xavier初始化,确保训练的稳定性。
  • 损失函数调整

  • 模型压缩

    • 通过剪枝、量化或知识蒸馏来减少模型复杂度,提高推理速度。
  • 混合精度训练

    • 结合半精度和单精度浮点数训练,加快训练速度并减少显存占用。
  • 硬件加速

    • 利用GPU、TPU等硬件加速器,以显著提升训练和推理的效率。

写在最后

今年的夏令营全部结束啦,真的很感谢Datawhale提供了一个开源的学习平台,能督促我在有限的时间里做高效的学习。之前我也接触过CNN等深度学习的内容,但只是知道概念、停留在“能用就行”,而通过这次学习我才开始理解它的底层逻辑和技术细节,并且动手实践了数据增强、对抗样本生成等任务,遇到过困难、有过解决方法、也留存了疑问与思考。希望在以后的学习和工作中,我能充分地用上这次学到的东西,作进一步的探究。AI是时代的新浪潮,无论身处什么行业都要坚定地迎风而上呀~