三、深度学习基础(续)
换个思路 —— 调整误差表面
原因
计算损失函数的逻辑链:权重 wi → wi*xi → 与 之间的距离 e → 求和得到损失
xi值越大,L对权重wi的变化越敏感,wi一变L就变了,那么这个维度上梯度就很陡峭。若另一个维度上xi值很小,L对wi不太敏感,那么梯度较小。这会导致不同维度方向的斜率与坡度非常不同,误差表面崎岖,不好训练。
除了上述的调整学习率,还可以直接改变误差表面,让各维度上的特征数值在相近的范围里。
General —— 特征归一化
缩小各个特征维度的数值差距,比如z-score标准化。可以在各个层对输入做归一化。
- 优点:制造好训练的误差表面,使得损失收敛更快,训练更稳定。
- 经验:若选择sigmoid,推荐对 z = wx 也做归一化。
Special —— 批量归一化
对全部数据归一化计算量太大(e.g.上百万个μ和σ),故只对一个批量的样本做归一化。
- 适用:批量大小比较大的时候,这时批量分布更接近整体分布。
- 经验:①sigmoid不加批量归一化,训练会很困难。②做批量归一化后,误差表面会光滑,学习率可以大一些。
- 步骤:通常有三阶段(x、z*2),第三阶段用β和γ调整分布,使隐藏层输出的均值不是0,减少0的负面影响。
- 测试阶段:不用批量,用训练阶段的移动平均。p是一个超参数。
-
奏效原因:解决内部协变量偏移问题
我们在计算B更新到B'的梯度时,前一层的参数是A,或者前一层的输出是a。那当前一层从A变成A'的时候,其输出就从a变成a'。但是我们计算这个梯度的时候,是根据a算出来的,所以这个更新的方向也许适合用在a上,但不适合用在a'上。
每次都做批量归一化的话,就会让a与a'的分布比较接近,也许会对训练有所帮助。
四、卷积神经网络
以下知识都是基于图像分类任务的。
4.1 基本流程
-
图像描述:三维张量,宽×高×通道,比如RGB就是红绿蓝3个通道
-
图像输入:把图像调整成统一尺寸,再“拉直”张量,变成向量
-
全连接网络:每个神经元跟输入的每个维度都有一个权重,容易过拟合
-
模型输出:softmax,得到
4.2 简化成卷积网络
① 感受野:检测特定的模式不需要整张图像
感受野可大可小,可重叠可平行,可长方形可正方形,可单通道可全通道。
-
一般感受野会看全部通道,即深度 = 通道数,宽×高 → 卷积核
-
一般同一个感受野会有一组神经元去守备这个范围
-
一个感受野挪到下一个的距离为步幅,是一个超参数,一般设为1or2(希望重叠,识别图片每个角落)
-
边界缺少输入,就做填充,一般是0
② 滤波器:同样的模式可能会出现在图像的不同区域
在图片不同位置去检测同一个模式时,可以做参数共享,即两个神经元的感受野不同、参数权重完全一样,这些参数称为滤波器(核大小 × 通道数)。
- 因为输入不同,就算共用参数,神经元们的输出也不会是一样的。
- 神经元是有偏置的,滤波器也是有偏置的。
例如右下图,一个6×6×1的图像,经滤波器1就能检测出对角全为1的模式(数值为3)。
不同滤波器是不同参数,如果有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。
- 事实上,在第八轮训练时,训练集的准确率已达到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。
-
验证集与训练集的表现相似,说明过拟合问题得到了缓解。
-
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。
- ResNet的确可以提高模型表现,即使在数据增强效果一般的情况下。
训练结果汇总
| 评估指标 | baseline | ResNet | 数据增强 | ResNet + 数据增强 |
|---|---|---|---|---|
| 验证集准确率 | 59.11% | 59.30% | 51.70% | 60.36% |
| 验证集损失 | 1.2219 | 1.2785 | 1.3631 | 1.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是时代的新浪潮,无论身处什么行业都要坚定地迎风而上呀~