这是我的第二场kaggle竞赛(20th top5% 银牌),其实我觉得还能取得更好的名次的,由于实验室机子有限,还有一些想法都没有实验。不过这次比赛比上次比赛学到了更多东西,下面我将把在这次比赛中的感受和心得分享给大家。
由第一部分可知,该比赛数据集严重不均衡,所以我们做了一下几方面尝试,以及验证了该方案是否对结果有提升。
image_transform = Compose([
RandomCrop(dsize),
RandomHorizontalFlip(),
ToTensor(),
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
RandomErasing(probability=0, sh=0.4, r1=0.3)
])
RandomErasing:
class RandomErasing(object):
'''
Class that performs Random Erasing in Random Erasing Data Augmentation by Zhong et al.
-------------------------------------------------------------------------------------
probability: The probability that the operation will be performed.
sl: min erasing area
sh: max erasing area
r1: min aspect ratio
mean: erasing value
usage (only for train data): transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
transforms.RandomErasing(probability = args.p (0), sh = args.sh (0.4), r1 = args.r1 (0.3), ),
])
-------------------------------------------------------------------------------------
'''
def __init__(self, probability=0.5, sl=0.02, sh=0.4, r1=0.3, mean=None):
if mean is None:
mean = [0.485, 0.456, 0.406]
self.probability = probability
self.mean = mean
self.sl = sl
self.sh = sh
self.r1 = r1
def __call__(self, img):
if random.uniform(0, 1) > self.probability:
return img
for attempt in range(100):
area = img.size()[1] * img.size()[2]
target_area = random.uniform(self.sl, self.sh) * area
aspect_ratio = random.uniform(self.r1, 1 / self.r1)
h = int(round(math.sqrt(target_area * aspect_ratio)))
w = int(round(math.sqrt(target_area / aspect_ratio)))
if w < img.size()[2] and h < img.size()[1]:
x1 = random.randint(0, img.size()[1] - h)
y1 = random.randint(0, img.size()[2] - w)
if img.size()[0] == 3:
img[0, x1:x1 + h, y1:y1 + w] = self.mean[0]
img[1, x1:x1 + h, y1:y1 + w] = self.mean[1]
img[2, x1:x1 + h, y1:y1 + w] = self.mean[2]
else:
img[0, x1:x1 + h, y1:y1 + w] = self.mean[0]
return img
return img
mix up:
l = np.random.beta(mixup_alpha, mixup_alpha)
index = torch.randperm(inputs.size(0))
inputs_a, inputs_b = inputs, inputs[index]
targets_a, targets_b = targets, targets[index]
mixed_images = l * inputs_a + (1 - l) * inputs_b
outputs = self.model(mixed_images)
loss = reduce_loss(l * criterion(outputs, targets_a) + (1 - l) * criterion(outputs, targets_b))
测试集:5倍的TTA:采用FiveCrop的预处理手段。
def load_transform_image(item, root, dsize, aspect_ratio, tta_index):
image = load_image(item, root, aspect_ratio)
w, h = image.size
if tta_index==0:
image = F.center_crop(image, dsize)
elif tta_index==1:
i = 0
j = w//2 - dsize//2
image = F.crop(image, i, j, dsize, dsize)
elif tta_index==2:
i = h - dsize
j = w//2 - dsize//2
image = F.crop(image, i, j, dsize, dsize)
elif tta_index==3:
i = h//2 - dsize//2
j = 0
image = F.crop(image, i, j, dsize, dsize)
elif tta_index==4:
i = h//2 - dsize//2
j = w - dsize
image = F.crop(image, i, j, dsize, dsize)
image_transform = Compose([
RandomHorizontalFlip(),
ToTensor(),
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
image = image_transform(image)
return image
RandomCropIfNeeded(SIZE * 2, SIZE * 2),
Resize(SIZE, SIZE)
HorizontalFlip(p=0.5),
OneOf([
RandomBrightness(0.1, p=1),
RandomContrast(0.1, p=1),
], p=0.3),
ShiftScaleRotate(shift_limit=0.1, scale_limit=0.0, rotate_limit=15, p=0.3),
IAAAdditiveGaussianNoise(p=0.3),
class RandomCropIfNeeded(RandomCrop):
def __init__(self, height, width, always_apply=False, p=1.0):
super(RandomCrop, self).__init__(always_apply, p)
self.height = height
self.width = width
def apply(self, img, h_start=0, w_start=0, **params):
h, w, _ = img.shape
return F.random_crop(img, min(self.height, h), min(self.width, w), h_start, w_start)
top9:采用了RandomResizedCropV2的预处理。note: 与torchvision提供的RandomResizedCrop接口稍微有点区别,官方的采用的CenterCrop+Resize实现,而作者采用的是RandomCrop+Resize。代码如下:
class RandomResizedCropV2(T.RandomResizedCrop):
@staticmethod
def get_params(img, scale, ratio):
# ...
# fallback
w = min(img.size[0], img.size[1])
i = random.randint(0, img.size[1] - w)
j = random.randint(0, img.size[0] - w)
return i, j, w, w
def train_transform(size):
return T.Compose([
RandomResizedCropV2(size, scale=(0.7, 1.0), ratio=(4/5, 5/4)),
T.RandomHorizontalFlip(),
T.ToTensor(),
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
RandomErasing(probability=0.3, sh=0.3),
])
def test_transform(size):
return T.Compose([
RandomResizedCropV2(size, scale=(0.7, 1.0), ratio=(4/5, 5/4)),
T.RandomHorizontalFlip(),
T.ToTensor(),
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
首先将训练集分成6折,然后使用第一折的数据进行单模型的训练和验证,从而确定不同种类模型的性能。由于kernel-only的规则,kaggle官方kernel运行时间不能超过九小时,所以就选择的是复杂度适中的模型。但有人实验证明,网络越深,其效果越好。
backbone
resnet50, cbam_resnet50,seresnext50,airnext50, resnet101, densenet121,inceptionv3
对这些模型做过测试后,基于运行时间和网络性能选择了最终的三个backbone分别是带有attention机制的 cbam_resnet50,seresnext50,airnext50。
backbone的改进-引入multiScale机制
受SSD的启发,我们引入了multiScale机制,即将网络中间层的feature map经过global average pooling后concat到最后的全连接层,这样做能使得feature map得到更好的复用。这一操作使得cv和LB均提高了0.005左右。
label correlation-引入图卷积网络GCN
由于该比赛是一个multi-label classification,所以不同类别之间具有一定的相关性,具体的来说,有的类别一旦出现,另一个类别有很大概率也会出现。所以为了让网络能学到这种相关性,我们参考了ML-GCN,然后设计了基于该任务的GCN网络。但是得到的效果却没有提升。我们分析了原因可能是类别基数太大(1103类),而ML-GCN所采用的数据集coco和voc分别是80类和20类,这样生成的adjacent matrix太过于稀疏,我们分别统计了一下三种数据集adjacent matrix的稀疏程度,计算方式是用矩阵中非零值的元素个数(阈值τ设置的0.2)除以矩阵的大小。结果如下:
voc:21 / (20x20) = 5.25%
coco: 311 / (80x80) = 4.86%
imet: 649 / (1103x1103) = 0.05%
因此GCN网络对这种太过于稀疏的adjacent matrix,并不能学到类别间的相关性。
Culture and tags separately
由于所有的类别都基于这两大类,所以一个最直观的想法是在用CNN提取完特征后,设立两路的fc层,一路用来识别culture的398类,另一路用来识别tag的705类。然后就能得到两种loss:culture loss和tag loss,将这两种loss加权后就能得到最终的loss。但是效果与单路fc层效果差不多,这也是我没太理解的地方。
由第三部分可知,我们一共选择了三个模型:cbam_resnet50,airnext50和seresnext50进行fine tuning,引入了multiScale机制,并进行6折的交叉验证。在三个模型中,
相同的训练策略有:
不同的训练的策略有:
其它的训练策略:
top1:link。
top4:link。
top6:link。
top9:link。
another a good solution:link。