数据不平衡
定义
在图像分类任务中,数据不平衡是一个普遍存在的问题。数据不平衡指的是训练集中不同类别的样本数量差异较大,其中某些类别的样本数量明显少于其他类别。这会导致模型在训练和测试过程中对于少数类别的识别能力较弱,从而降低整体性能。
解决方法
以下是一些解决数据不平衡问题的方法:
- 重新采样:过采样或欠采样来平衡数据集。
- 类别权重调整:调整样本的权重,关注少数类别。
- 集成方法:使用随机森林、Boosting等方法整合多个模型的预测结果。
- 自定义损失函数:设计适合处理不平衡数据的损失函数,如Focal Loss、样本加权的交叉熵等。
实战
今年的kaggle竞赛提供的deepfake数据集,分为训练集和验证集。其中训练集有524429张图片,正样本有425043个,负样本有99386个,比例近似为4.3:1;验证集有147363张图片,正样本有88281个,负样本有59082个,比例近似为1.5:1。可见训练集存在较严重的数据不平衡问题,而验证集存在轻微的数据不平衡。
我尝试使用过采样方法,即对训练集的负样本进行重复采样。代码如下
import pandas as pd
import numpy as np
train_label = pd.read_csv('/kaggle/input/deepfake/phase1/trainset_label.txt')
val_label = pd.read_csv('/kaggle/input/deepfake/phase1/valset_label.txt')
train_label['path'] = '/kaggle/input/deepfake/phase1/trainset/' + train_label['img_name']
val_label['path'] = '/kaggle/input/deepfake/phase1/valset/' + val_label['img_name']
# 统计target列中1和0的数量
count_1 = train_label['target'].value_counts()[1]
count_0 = train_label['target'].value_counts()[0]
print(count_0, count_1)
# 计算target为1和0的比例的倒数
ratio_1 = (count_1 + count_0) / count_1
ratio_0 = (count_1 + count_0) / count_0
print(ratio_0, ratio_1)
# 初始化一个空的numpy数组
result = np.array([])
# 遍历,设置样本权重
for index, row in train_label.iterrows():
if row['target'] == 1:
result = np.append(result, ratio_1)
elif row['target'] == 0:
result = np.append(result, ratio_0)
'''
这个weights是所有样本(整个待采样数据集)中每个样本的权重。
每个权重值则是抽选该样本的可能性。这里的权重值大小没有要求所有加和为1。可以预见的是,同一个类别的样本的权重值应当都设为 该类别占比的倒数。
比如阳性占比20%,其权重应设为 1 / 20% = 5 ;对应的阴性类样本的权重应为1 / 80 % = 1.25。
'''
weights = torch.from_numpy(result)
# 定义抽样器,传入准备好的权重数组,抽样总数,并选择放回抽样
sampler = torch.utils.data.sampler.WeightedRandomSampler(weights, weights.shape[0], replacement=True)
train_loader = torch.utils.data.DataLoader(
FFDIDataset(train_label['path'], train_label['target'],
transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 设置了sampler,就不必使用shuffle了,它会根据权重随机抽取样本
), batch_size=bs_value, sampler = sampler, shuffle=False, num_workers=4, pin_memory=True
)
在kaggle上训练全部数据太慢了,为了快速验证是否有效,我只选择了前3万数据进行训练,跑了10轮,然后在全部验证集上进行预测,对比结果发现:没有用过采样的准确率为69.6%,用了过采样的准确率为97.85%,效果很显著。但是,我用过采样方法在全部数据上进行训练,跑了3轮,准确率为97.77%,而之前在全部数据上训练3轮的准确率为97.96%,这么一看,准确率略有下降了。。。也许训练次数多一点,效果就好点也说不定。
结论
kaggle的免费gpu时长不多,未能做更多的实验。从现有的实验结果来看,在数据量比较少时,过采样方法是明显有效的;数据量较大时,效果可能不明显了。
ema指数移动平均
定义
在深度学习中,指数移动平均(Exponential Moving Average, EMA)是一种常用的技术,用于平滑模型权重的更新。EMA的主要思想是给予最近的权重更高的权重,而较旧的权重则逐渐降低其影响。EMA在图像分类中的作用主要是提高模型的鲁棒性和稳定性,增强泛化能力,并在一定程度上提升模型在测试数据上的表现(如准确率和FID分数)。
在训练深度学习模型时,权重常常会出现抖动,导致模型在不同的训练批次上表现不稳定。通过EMA,可以对权重进行平滑处理,减小这种抖动,使模型更加稳定。具体来说,EMA通过计算过去几个时刻权重的加权平均来实现这一目标。
实战
# 创建训练模型的副本。衰减系数 decay决定了模型参数更新的速度,值越接近1意味着更新速度越慢,反之则越快。 (一般设为0.9-0.999)
ema_model = timm.utils.ModelEmaV2(model, decay=0.9)
# 训练
for i, (input, target) in enumerate(train_loader):
# 当你将 Tensor 数据从 CPU 移动到 GPU 时,可以通过设置 non_blocking=True 来启用异步传输模式,从而提高计算效率。
input = input.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
# compute output
output = model(input)
loss = criterion(output, target)
# measure accuracy and record loss
losses.update(loss.item(), input.size(0))
acc = (output.argmax(1).view(-1) == target.float().view(-1)).float().mean() * 100
top1.update(acc, input.size(0))
# compute gradient and do SGD step
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 更新 EMA 模型的参数
ema_model.update(model)
# 验证或预测,用 ema_model.module 替代原model
val_acc = validate(val_loader, ema_model.module, criterion)
注意,在训练轮次较多的情况下,ema才能发挥作用。由于我把kaggle的免费gpu使用时间用光了,没能验证是否有效,只跑了3轮,看不出效果,有机会尝试训练更多轮次看看。