2022-天池-AI Challengers 赛后记录 264/3691
1.前言
这是自己第一次参加的CV方向的比赛,是一种对抗性攻击的比赛,最后没有进决赛,成绩是90,264/3691,前50名98+的分数,卷不过,不过第一次,之后继续加油了。
这篇文章还学习了前排大佬的思路,尝试跑了一下,当作学习。
比赛地址:https://tianchi.aliyun.com/competition/entrance/531939/introduction
代码地址:https://github.com/Fanthers/data_science_competition/tree/main/%E5%A4%A9%E6%B1%A0/2022%20AI%20Challengers
比赛要求和数据:
官方提供了ResNet50和DensNet121标准网络(基于Pytorch),选手需要在线下基于CIFAR-10数据进行模型训练,再将两个模型提交到线上进行评测(线上的测试数据是不可见的,但是官方有说包含了部分CIFAR-10的测试集,所以不建议大家在线下使用CIFAR-10测试集进行训练),但是看前排的分数,大家普遍使用了测试集用于训练,如果不用很难进复赛,所以初赛不得不用!
2.比赛思路
• 官方baseline:官方baseline使用CIFAR-10的1w张测试集用于训练,直接提交到线上评测,分数在75左右!(可能我算力不行或者参数问题,最后只有70)
• 标签平滑技术+基于Albumentations库的数据增强。主要是高斯噪声,随机噪声,颜色,几何变换等,将1w张原始数据扩充到5w张,进行线上的评测,分数已经可以来到90+!
• 使用FGSM[3]生成5w对抗样本 ,线上分数可以来到0.94+!
• Albumentations 2w+FGSM3w 训练样本 0.96+!
后面两种自己也尝试跑通了,但是时间已过,无法提交查看成绩,很遗憾,分数是前排大佬的结果,可能自己提交,因为算力等方面会有稍许降低。下面给出一些重要的源代码,具体代码放在github地址了。
3.关键技术
标签平滑(Label Smoothing):
具体学习参考https://cloud.tencent.com/developer/article/1815786
confidence = 0.8
smoothing = 0.2
cls= 10
dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=False)
images = []
soft_labels = []
transform=get_train_transforms()
cnt=-1
for image, label in tqdm(dataset):
image = np.array(image)
#images.append(image)
soft_label = np.zeros(10)
soft_label[label] += confidence # an unnormalized soft label vector
for inx in range(soft_label.shape[0]):
if inx!=label:
soft_label[inx]=smoothing / (cls - 1)
基于Albumentations库的数据增强:
具体学习可参考https://zhuanlan.zhihu.com/p/107399127
import albumentations as A
def get_train_transforms():
return Compose(
[
Transpose(p=0.25),
GaussNoise(p=0.75),
OneOf([
# 模糊相关操作
MotionBlur(p=.75),
MedianBlur(blur_limit=3, p=0.5),
Blur(blur_limit=3, p=0.75),
], p=0.25),
ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=45, p=0.25),
OneOf([
# 畸变相关操作
OpticalDistortion(p=0.75),
GridDistortion(p=0.25),
PiecewiseAffine(p=0.75),
], p=0.25),
OneOf([
# 锐化、浮雕等操作
CLAHE(clip_limit=2),
Sharpen(),
Emboss(),
RandomBrightnessContrast(),
], p=0.25),
#
HueSaturationValue(hue_shift_limit=0.2, sat_shift_limit=0.2, val_shift_limit=0.2, p=0.5),
OneOf(
[
CoarseDropout(max_holes=4,
max_height=4,
max_width=4,
p=0.5),
Cutout(
num_holes=4,
max_h_size=4,
max_w_size=4,
p=0.5,)],
p=0.5)
]
)
当然了,直接使用高斯噪声和椒盐噪声也可以达到90的分数
class AddSaltPepprerNoise(object):
def __init__(self, density=0):
self.density = density
def __call__(self, img):
img = np.array(img)
h, w, c = img.shape
Nd = self.density
Sd = 1 - Nd
mask = np.random.choice((0, 1, 2), size=(h, w, 1), p=[Nd/2.0, Nd/2.0, Sd])
mask = np.repeat(mask, c, axis=2)
img[mask==0] = 0
img[mask == 1] = 255
img = Image.fromarray(img.astype('uint8')).convert('RGB')
return img
class AddGaussianNoise(object):
def __init__(self, mean=0.0, variance=1.0, amplitude=20.0):
self.mean = mean
self.variance = variance
self.amplitude = amplitude
def __call__(self, img):
img = np.array(img)
h, w, c = img.shape
N = self.amplitude * np.random.normal(loc=self.mean, scale=self.variance, size=(h, w, 1))
N = np.repeat(N, c, axis=2)
img = N + img
img[img > 255] = 255
img = Image.fromarray(img.astype('uint8')).convert('RGB')
return img
my_transform_gaussian_noise = AddGaussianNoise()
my_transform_saltpepper_noise = AddSaltPepprerNoise()
trans = torchvision.transforms.RandomChoice([my_transform_saltpepper_noise, my_transform_gaussian_noise])
FGSM:
具体学习可参考https://pytorch.org/tutorials/beginner/fgsm_tutorial.html#fast-gradient-sign-attack
核心代码:
def fgsm_attack(image, epsilon, data_grad):
# 收集数据梯度的元素符号
sign_data_grad = data_grad.sign()
# 通过调整输入图像的每个像素来创建扰动图像
perturbed_image = image + epsilon * sign_data_grad
# 添加剪切以维持[0,1]范围,对于标准化的情况不适用,先注释掉
# perturbed_image = torch.clamp(perturbed_image, 0, 1)
# 返回被扰动的图像
return perturbed_image
##########
def test(trainloader, model, epsilon):
accs = AverageMeter()
accs_adv = AverageMeter()
model.eval()
# print(len(trainloader))
for (inputs, soft_labels) in trainloader:
inputs, soft_labels = inputs.cuda(), soft_labels.cuda()
inputs.requires_grad = True # 获取输入梯度
targets = soft_labels.argmax(dim=1)
outputs = model(inputs)
#
loss = cross_entropy(outputs, soft_labels)
acc = accuracy(outputs, targets)
accs.update(acc[0].item(), inputs.size(0))
# Zero all existing gradients
model.zero_grad()
#
loss.backward()
# Collect datagrad
data_grad = inputs.grad.data
# Call FGSM Attack
perturbed_inputs = fgsm_attack(inputs, epsilon, data_grad)
outputs = model(perturbed_inputs)
acc = accuracy(outputs, targets)
accs_adv.update(acc[0].item(), inputs.size(0))
# --
save_adv_sample(perturbed_inputs, soft_labels)
# break
return accs.avg, accs_adv.avg