Machine Learning HW3 report:图像分类(Hongyi Lee)

发布于:2025-09-04 ⋅ 阅读:(18) ⋅ 点赞:(0)

任务:使用CNN把食物图片分为11类(不能使用预训练的模型)。此任务很耗时,一次训练至少1h,所以要利用好Kaggle notebook中Save Version功能,并行训练节省时间。

基准

  1. Simple : 0.50099
  2. Medium : 0.73207 Training Augmentation + Train Longer
  3. Strong : 0.81872 Training Augmentation + Model Design + Train Looonger (+ Cross Validation + Ensemble)
  4. Boss : 0.88446 Training Augmentation + Model Design +Test Time Augmentation + Train Looonger (+ Cross Validation + Ensemble)

结果

在这里插入图片描述

优化思路

  1. Residual Connection(残差连接)
  2. 用checkpoint保存模型后继续训练
  3. 使用已存在的模型:Visit torchvision.models for a list of model structures, or go to timm for the latest model structures
  4. data augmentation (数据增强): Modify the image data so non-identical inputs are given to the model each epoch, to prevent overfitting of the model; Visit torchvision.transforms for a list of choices and their corresponding effect. Diversity is encouraged! Usually, stacking multiple transformations leads to better results.
  5. 使用mixup实现数据增强,不过需要改写torch.utils.Dataset, getitem(),写自己的CrossEntropyLoss支持多个标签
  6. Test Time Augmentation
  7. Cross Validation: uses different portions of the data to validate and train a model on different iterations
  8. 融合现有的训练和测试数据,增加训练数据比例(Currently, train : validation ~ 3 : 1)
  9. Ensemble:Average of logits or probability或Voting
  10. spatial transformer layer
  11. Image Normalization与batch Normalization

原代码中的错误及其他改进

  1. 如果直接运行sample code,不能正确显示训练进度条报错如下。这个错误是由于 Jupyter widgets 的版本不兼容导致的。查了多种解决方式都无效。最后将原代码中from tqdm.auto import tqdm改为from tqdm import tqdm后解决了,tqdm.auto本来是用来自动识别当前运行环境,但在这里反而出错,只能说此功能还不够完善。

Failed to load model class ‘HBoxModel’ from module ‘@jupyter-widgets/controls’ Error: Module @jupyter-widgets/controls, version ^1.5.0 is not registered, however, 2.0.0 is at …

  1. 加入以下代码,显示训练时间、最佳准确率。
#在训练开始前加入
import time
total_start = time.time()

#在训练完成后加入
total_time = time.time() - total_start
print(f'\nTotal time: {int(total_time // 60)} min {int(total_time % 60)} sec',end=', ')
print(f'best accuracy: {best_acc:.5f}')

实验过程

  1. 直接运行sample code,epoch = 24,耗时55min,效果不错,best acc = 0.69254,Kaggle private score = 0.70066,逼近了Medium水平。
  2. data augmentation,修改test_tfm,增加中间三行代码,Total time: 53 min 51 sec, best accuracy: 0.47737
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.RandomResizedCrop(128), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)
    transforms.RandomHorizontalFlip(), # 水平翻转,默认50%概率
    transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度
    transforms.ToTensor(),
])
  1. test_tfm加入Image Standarlization,Total time: 54 min 58 sec, best accuracy: 0.44407
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.RandomResizedCrop(128), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)
    transforms.RandomHorizontalFlip(), # 水平翻转,默认50%概率
    transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 图像标准化
]) 
  1. 观察后发现了前两次实验效果变差的原因:应该调整train_tfm而非test_tfm!重新进行上述实验。将test_tfm改为原样,Total time: 56 min 11 sec, best accuracy: 0.70634,private score:0.70333。奇怪的是比起改动前几乎没有提升效果,此外发现训练时accuracy达到0.81210,存在Overfitting。
# 原代码如下:
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.Resize((128, 128)),
    # You may add some transforms here.
    # 增强数据
     transforms.RandomChoice(transforms=[ # 随机选择以下两种操作的一种
        # Apply TrivialAugmentWide data augmentation method
        transforms.TrivialAugmentWide(), # 在训练过程中自动为每张图像选择一种随机增强操作

        # Return original image
        transforms.Lambda(lambda x: x),
    ],
                            p=[0.95, 0.1]), # 每个转换操作被选择的概率分布
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
])

# 修改后:
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.Resize((128, 128)),
    # You may add some transforms here.
    # 增强数据
    transforms.RandomResizedCrop(size=128, scale=(0.8, 1.0)), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)
    transforms.RandomRotation(degrees=30),                # 随机旋转±30度
    transforms.RandomHorizontalFlip(),                    # 水平翻转,默认50%概率
    transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
])
  1. train_tfm最后一步加入Image Standarlization。Total time: 58 min 18 sec, best accuracy: 0.34147,准确率暴跌。发现训练时acc = 0.81429,非常高,说明测试时出了问题
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.Resize((128, 128)),
    # You may add some transforms here.
    # 增强数据
    transforms.RandomResizedCrop(size=128, scale=(0.8, 1.0)), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)
    transforms.RandomRotation(degrees=30),                # 随机旋转±30度
    transforms.RandomHorizontalFlip(),                    # 水平翻转,默认50%概率
    transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 图像标准化
])
  1. 找到了上次实验问题:测试时缺少与训练时一致的标准化操作,这种现象被称为 “预处理不一致偏差”,原因在于训练和测试数据分布不一致。修改train_tfm,加上相同的标准化处理,重新训练。Total time: 57 min 52 sec, best accuracy: 0.72379,private:0.73866,达到了Medium。
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 测试时图像必须也标准化!
])
  1. 把epoch数量从24改为250,patience改为20(即连续20次无进步,退出训练)。训练34个epoch后,best acc已经达到0.72761,最终训练了102个epoch,Total time: 248 min 38 sec, best accuracy: 0.74015,private:0.74466。相比而言,多训练了5h,只提升了百分之一左右,效率很低。
  2. 残差连接。重构神经网络结构,换为残差连接(实现如代码所示),按照主流设计最后只用一层全连接神经网络(原代码用了三层),epoch设为150。24个epoch后acc = 0.66807。最终训练了63个epoch,Total time: 164 min 2 sec, best accuracy: 0.70428,private:0.71200
#残差链接:主流设计:两个卷积层 + 一次残差相加
class ResidualBlock(nn.Module):
    def __init__(self,in_channel,out_channel,stride=1):
        super().__init__()
        self.relu = nn.ReLU()  # 定义实例。nn.ReLU(x)是错的!!nn.ReLU是类
        self.conv1 = nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=stride,padding=1) #第一个卷积层,可能特征图长宽会改变
        self.bn1 = nn.BatchNorm2d(out_channel)
        self.conv2 = nn.Conv2d(out_channel,out_channel,kernel_size=3,stride=1,padding=1)  #第二个卷积层
        self.bn2 = nn.BatchNorm2d(out_channel)

        self.shortcut = nn.Sequential() #residual connect, 维数相同则与输入相同
        if stride != 1 or in_channel != out_channel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channel,out_channel,kernel_size=1,stride=stride),
                nn.BatchNorm2d(out_channel)
            )
    def forward(self, x):
        residual = self.shortcut(x)
        x = self.relu(self.bn1(self.conv1(x))) #F.rulu 和nn.ReLU不一样!!
        x = self.bn2(self.conv2(x))
        x += residual
        return self.relu(x)

#新的神经网络架构
        # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
        # torch.nn.MaxPool2d(kernel_size, stride, padding)
        # input 維度 [3, 128, 128]
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]

            #第一层池化,减少参数量,后面的残差连接无需池化    
            ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, size
            ResidualBlock(128,256,2), #[256, 16, 16]
            ResidualBlock(256,512,2), #[512, 8, 8]
            ResidualBlock(512,1024,2),  #[1024, 4, 4]
        )
        self.fc = nn.Linear(1024*4*4, 11) # 11个类别。后面不能加ReLu!

# 原代码最后两层都是全连接网络
self.fc = nn.Sequential(
	nn.Linear(512*4*4, 1024),
	nn.ReLU(),
	nn.Linear(1024, 512),
	nn.ReLU(),
	nn.Linear(512, 11)
)
  1. 全局平均池化:在self.cnn的最后加上两行代码,把4×4的特征图取平均压缩为1×1,再降维。24个epoch后acc 0.71644,最终训练了118个epoch,Total time: 286 min 29 sec, best accuracy: 0.76066,private:0.77533。有大幅度提升。
self.cnn = nn.Sequential(
	nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]
	nn.BatchNorm2d(64),
	nn.ReLU(),
	nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]

	#第一层池化,减少参数量,后面的残差连接无需池化    
	ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, size
	ResidualBlock(128,256,2), #[256, 16, 16]
	ResidualBlock(256,512,2), #[512, 8, 8]
	ResidualBlock(512,1024,2),  #[1024, 4, 4]
	nn.AdaptiveAvgPool2d((1, 1)), # [1024, 1, 1] → 全局平均池化
	nn.Flatten()                # [1024]
)
  1. 增加层数,epoch = 24,Total time: 64 min 55 sec, best accuracy: 0.71367,private:0.70133。相同epoch时,比起调整前反而有细微退步。
self.cnn = nn.Sequential(
	nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]
	nn.BatchNorm2d(64),
	nn.ReLU(),
	nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]

	#第一层池化,减少参数量,后面的残差连接无需池化    
	ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, size
	ResidualBlock(128,128,1), #[128, 32, 32]
	ResidualBlock(128,256,2), #[256, 16, 16]
	ResidualBlock(256,256,1), #[256, 16, 16]
	ResidualBlock(256,512,2), #[512, 8, 8]
	ResidualBlock(512,512,1), #[512, 8, 8]
	ResidualBlock(512,1024,2),  #[1024, 4, 4]
	nn.AdaptiveAvgPool2d((1, 1)), # [1024, 1, 1] → 全局平均池化
	nn.Flatten()                # [1024]
)
  1. 增加dropout:self.cnn最后加入nn.Dropout(0.3)。Total time: 74 min 47 sec, best accuracy: 0.71584,private:0.70866提升不大
  2. 把训练文件和测试文件合并、打乱,重新划分,比例从3:1改为4:1。把神经网络结构改回第八次的结构,epoch = 24。Total time: 60 min 36 sec, best accuracy: 0.72798,进步明显
path = "/kaggle/input/ml2023spring-hw3/train"  
all_files = ([os.path.join(path,x) for x in os.listdir(path) if x.endswith(".jpg")])  
path = "/kaggle/input/ml2023spring-hw3/valid"  
all_files += ([os.path.join(path,x) for x in os.listdir(path) if x.endswith(".jpg")])  
# shuffle and re-divide  
random.shuffle(all_files) #random seed已经固定,可以复现  
split_idx = int(len(all_files) * 0.8)  
train_files = all_files[:split_idx]  
valid_files = all_files[split_idx:]  
  
# Construct train and valid datasets.  
# The argument "loader" tells how torchvision reads the data.  
train_set = FoodDataset("/kaggle/input/ml2023spring-hw3/train", tfm=train_tfm, files = train_files)  
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)  
valid_set = FoodDataset("/kaggle/input/ml2023spring-hw3/valid", tfm=test_tfm, files = valid_files)  
valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)
  1. Test Time Augmentation。修改部分较多,具体见代码。从这个实验开始,改为colab平台上运行(因为恰好有colab pro+账号,GPU算力更高,选择L4 GPU),但在kaggle上运行也完全没问题。Total time: 51 min 7 sec, best accuracy: 0.73161,private:0.72266
#1.需要修改test_tfm
test_tfm = transforms.Lambda(lambda x: x)  # Return original image

#2. designed for test time augmentation  
tta_tfm = transforms.Compose([  
    transforms.Resize((128, 128)),  
    transforms.RandomHorizontalFlip(p=0.5),  # 水平翻转  
    transforms.ColorJitter(brightness=0.2, contrast=0.2),  # 随机调整亮度和对比度  
    transforms.RandomRotation(30),  # 随机旋转  
    transforms.ToTensor(),  
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),  # 测试时图像必须也作相同标准化处理!  
])

#3. 利用继承的思想实现test time augmentation
class TTADataset(FoodDataset):  
    def __init__(self, *args, TTA_tfm=tta_tfm, n_aug=5, **kwargs):  
        """  
        n_aug 增强次数(不含原图),default = 5  
        """        super().__init__(*args, **kwargs)  
        self.n_aug = n_aug  
        self.tfm = TTA_tfm  
        self.ori_tfm = transforms.Compose([  # 处理原图像  
            transforms.Resize((128, 128)),  
            transforms.ToTensor(),  
            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 测试时图像必须也标准化!  
        ])  
  
    def __getitem__(self, index):  
        # 获取原始图像(此时还不是tensor)  
        image, label = super().__getitem__(index)  # 调用父类方法  
        tta_images = [self.ori_tfm(image)]  
        for _ in range(self.n_aug):  
            tta_images.append(self.tfm(image))  
        # return tta_images, label  # 返回list时,会出问题!!DataLoader 自动把每一位 TTA 样本合并成 batch 了!  
        tta_images = torch.stack(tta_images)  #转化为tensor [n_aug+1, 3, 128, 128]  
        return tta_images, label
        
#4. 重写valid_set   
valid_set = TTADataset("/kaggle/input/ml2023spring-hw3/valid", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm, files=valid_files)  
valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)

#5. 修改训练阶段validation逻辑
for batch in tqdm(valid_loader):  
  
    # A batch consists of image data and corresponding labels.  
    imgs, labels = batch  
	with torch.no_grad():  
		if imgs.ndim == 5:  # [B, N, C, H, W], 启用了TTA
			B, N, C, H, W = imgs.shape  
			tta_images = imgs.view(B * N, C, H, W).to(device) # 首先改变形状,计算出全部结果  
			outputs = model(tta_images)  # shape: [B * N, num_classes]  
			outputs = outputs.view(B, N, -1)  #恢复原来的形状 shape: [B, N, num_classes]  
			# 加权平均预测  
			weights = torch.tensor([0.5] + [(0.5 / (N - 1))] * (N - 1), device=device)  # [N],权重,原图0.5,其余被平分(默认是5张)  
			weighted_preds = outputs * weights.view(1, -1, 1)  #广播乘法[B, N, C]               
			logits = weighted_preds.sum(dim=1)  #加权融合 shape: [B, num_classes]         
			else:  
			logits = model(imgs.to(device))
			
#6. 改写测试集  
test_set = TTADataset("/kaggle/input/ml2023spring-hw3/test", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm)  
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)

#7. 修改test逻辑
#改测试逻辑  
with torch.no_grad():  
    for batch in tqdm(test_loader):  
        imgs, _ = batch  # imgs可能是普通tensor或TTA列表  
  
        if imgs.ndim == 5:  # TTA模式  
            B, N, C, H, W = imgs.shape  
            tta_images = imgs.view(B * N, C, H, W).to(device) # 首先改变形状,计算出全部结果  
  
            outputs = model(tta_images)  # shape: [B * N, num_classes]  
            outputs = outputs.view(B, N, -1)  #恢复原来的形状 shape: [B, N, num_classes]  
            # 加权平均预测  
            weights = torch.tensor([0.5] + [(0.5 / (N - 1))] * (N - 1), device=device)  # [N],权重,原图0.5,其余被平分(默认是5张)  
            weighted_preds = outputs * weights.view(1, -1, 1)  #广播乘法 broadcasting: [B, N, C]            test_pred = weighted_preds.sum(dim=1)  #加权融合 shape: [B, num_classes]        else:  # 普通模式  
            test_pred = model_best(imgs.to(device))  
  
        # 获取预测标签  
        test_label = np.argmax(test_pred.cpu().numpy(), axis=1)  
        prediction.extend(test_label.tolist())
  1. 查阅资料后,发现上次实验有问题:TTA一般只用于test阶段,而在validation阶段不应该使用,必须保证 validation 的结果能真实反映模型对“原始数据”的泛化能力,只有在完全相同的条件下(都不用TTA) 比较模型,才能公平地判断出哪个模型的本体更强。 把validation阶段重新修改回原来的逻辑,即不执行上次实验的第5、6,并如下修改了代码。把epoch数量从24改为500,patience改为49,继续使用colab上L4 GPU(A100 GPU、L4 GPU在此任务中表现相近,但A100计算单元消耗速度快得多)。
    最终训练了412个epoch,Total time: 577 min 56 sec, best accuracy: 0.80946,private: 0.78866。此次出现了Overfitting,train accuracy达到了99%;此外,patience设置过大,30要更为合理。
#TTADataset中初始化函数添加了mode = "no_tta",self.mode = mode  
#修改__getitem__逻辑,使其能返回未经tta增强的image
class TTADataset(FoodDataset):  
    def __init__(self, *args, TTA_tfm=tta_tfm, n_aug=5, mode = "no_tta", **kwargs):  
        """  
        n_aug 增强次数(不含原图),default = 5  
        mode 模式,启用tta或者不启用  
        """        super().__init__(*args, **kwargs)  
        self.n_aug = n_aug  
        self.tfm = TTA_tfm  
        self.mode = mode  
        self.ori_tfm = transforms.Compose([  # 处理原图像  
            transforms.Resize((128, 128)),  
            transforms.ToTensor(),  
            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 测试时图像必须也标准化!  
        ])  
  
    def __getitem__(self, index):  
        image, label = super().__getitem__(index)  # 调用父类方法  
        if self.mode != "no_tta":  
            # 此时为原始图像  
            tta_images = [self.ori_tfm(image)]  
            for _ in range(self.n_aug):  
                tta_images.append(self.tfm(image))  
            # return tta_images, label  # 返回list时,会出问题!!DataLoader 自动把每一位 TTA 样本合并成 batch 了!  
            tta_images = torch.stack(tta_images)  #转化为tensor [n_aug+1, 3, 128, 128]  
            return tta_images, label  
        image = self.ori_tfm(image)  
        return image, label
        
#改validation、test,如果在kaggle上运行,要改路径
valid_set = TTADataset("/content/valid", tfm=test_tfm, files = valid_files)
test_set = TTADataset("/content/test", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm,mode = "tta")

易错

  1. nn.ReLu不能直接调用!PyTorch 中 nn.ReLU 是一个类,不是函数,所以不能像普通函数那样直接 nn.ReLU(x) 使用。
#方法1 实例化
import torch
import torch.nn as nn

x = torch.tensor([-1.0, 0.0, 2.0])
relu = nn.ReLU()   # 实例化
y = relu(x)        # 调用实例
print(y)           # tensor([0., 0., 2.])


#方法2 F.relu(函数,直接可用)
import torch
import torch.nn.functional as F

x = torch.tensor([-1.0, 0.0, 2.0])
y = F.relu(x)  # 直接调用函数
print(y)       # tensor([0., 0., 2.])
  1. 实现的Dataset继承类中__getitem__返回的image(或其他东西)类型要是tensor,绝对不能是list,否则训练时DataLoader合并batch时会出错

相关技术

图像归一化(Normalization)与标准化(Standardlization)

  1. 区别。归一化:将数据按比例缩放至特定范围(如[0,1]或[-1,1]);标准化:将数据转换为均值为0、标准差为1的分布
  2. Pytorch中实现。归一化:transforms.ToTensor() # 将[0,255]线性映射到[0,1](Min-Max Normalization),标准化:transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])注意,PyTorch的Normalize()实际实现的是标准化
  3. 作用。归一化:统一特征尺度,加速梯度收敛等;标准化:提升模型泛化性,增强鲁棒性等。
  4. 易错。如果训练时数据进行了归一化与标准化,那么测试时数据也必须进行相同的归一化与标准化

残差连接

主流设计:两个卷积层 + 一次残差相加
核心思想:
不是直接让网络学习一个期望的映射 H(x),而是让网络学习残差映射 F(x)=H(x)−x
公式上,假设输入是 x,输出是 y:
y=F(x)+x

  • F(x):经过卷积层、BN、激活函数后的输出(残差)
  • x:直接加到输出上的原输入(shortcut/skip connection)

为什么有效?

  1. 减轻梯度消失:梯度可以直接沿着跳跃连接 x 反向传播,不经过很多非线性层。
  2. 学习残差更容易:通常残差 F(x) 比原映射 H(x)更接近零,网络只需学习微调,而不是整个复杂映射。
  3. 便于堆叠更多层:ResNet 可以轻松构建 50、101、甚至上百层的 CNN。

全局平均池化(Global Average Pooling, GAP)

这是一个非常简洁但极其强大的概念,在现代卷积神经网络(尤其是GoogLeNet、ResNet等)中扮演着关键角色。它将 每个通道的空间特征图 压缩成一个数值,即对每个通道做 空间维度的平均值

  • 输入:H × W × C 的特征图(高度 × 宽度 × 通道数)
  • 输出:1 × 1 × C 或直接 C 的向量
  • 常用于 网络末端替代全连接层
  • 巨大优势
    1. 极大减少参数量,防止过拟合:GAP本身零参数。它直接输出一个长度为 C(通道数)的向量,可以连接到最终的分类层(一个普通的全连接层,输入为C,输出为类别数)。参数量从亿级骤降到 C * num_classes,例如 512 * 1000 = 51.2万,减少了几个数量级。
    2. 增强模型鲁棒性:每个通道的特征图可以看作是对某个特定类别特征的“热度图”。GAP对空间信息进行整合,使得网络对输入物体的空间平移更加鲁棒。
    3. 原生支持任何输入尺寸:因为GAP不管输入特征图的高和宽是多少,它都直接取平均。这使得网络可以接受非固定尺寸的输入,而传统的FC层要求固定的输入维度。

Test Time Augmentation

在模型 推理阶段(测试阶段),对输入数据做多种数据增强(augmentation),然后将模型的多次预测结果进行融合(如平均或投票),以提高模型的鲁棒性和准确率。

  • 目的:缓解模型对输入分布的敏感性,让预测更稳健。
  • 区别于训练阶段数据增强
    • 训练时增强是为了增加数据量、防止过拟合
    • 测试时增强是为了提升预测稳定性和精度

总结

在hw3中,通过实现残差连接、TTA等技术,重新划分数据集并应用GAP,深化了对CNN的代码理解与实践能力,收获良多。不足之处在于没有尝试足够多网络架构,此外data augmentation做的也不够(做好了应该能较大幅度的提升准确率);如果使用ensemble技术,应该能较大地提高准确率。


网站公告

今日签到

点亮在社区的每一天
去签到