• 第四次作业:猫狗大战挑战赛


    一、 fine-tuning

    由于数据集的限制,我们可以使用预训练的模型,来重新fine-tuning(微调)。

    使用卷积网络作为特征提取器,冻结卷积操作层,这是因为卷积层提取的特征对于许多任务都有用处,使用新的数据集训练新定义的全连接层。

    何时以及如何Fine-tune

    决定如何使用迁移学习的因素有很多,这是最重要的只有两个:新数据集的大小、以及新数据和原数据集的相似程度。有一点一定记住:网络前几层学到的是通用特征,后面几层学到的是与类别相关的特征。这里有使用的四个场景:

    1、新数据集比较小且和原数据集相似。因为新数据集比较小,如果fine-tune可能会过拟合;又因为新旧数据集类似,我们期望他们高层特征类似,可以使用预训练网络当做特征提取器,用提取的特征训练线性分类器。

    2、新数据集大且和原数据集相似。因为新数据集足够大,可以fine-tune整个网络。

    3、新数据集小且和原数据集不相似。新数据集小,最好不要fine-tune,和原数据集不类似,最好也不使用高层特征。这时可是使用前面层的特征来训练SVM分类器。

    4、新数据集大且和原数据集不相似。因为新数据集足够大,可以重新训练。但是实践中fine-tune预训练模型还是有益的。新数据集足够大,可以fine-tine整个网络。

    我们这次的作业属于数据集与原来相似而且数据集很小的情况,可以使用预训练网络当做特征提取器,用提取的特征训练线性分类器。

    二、代码重点

    数据处理

    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    
    vgg_format = transforms.Compose([
                    transforms.CenterCrop(224), 
        			#.中心裁剪:transforms.CenterCrop
    				#class torchvision.transforms.CenterCrop(size)
    				#功能:依据给定的size从中心裁剪
    				#参数:
    				#size- (sequence or int),若为sequence,则为(h,w),若为int,则(size,size)
                    transforms.ToTensor(),
                    normalize,
                ])
    
    data_dir = './dogscats'
    
    dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), vgg_format)
             for x in ['train', 'valid']}
    
    dset_sizes = {x: len(dsets[x]) for x in ['train', 'valid']}
    dset_classes = dsets['train'].classes
    

    修改全连接层,冻结卷积层的参数

    for param in model_vgg_new.parameters():
        param.requires_grad = False #训练时不更改参数
    model_vgg_new.classifier._modules['6'] = nn.Linear(4096, 2) #全连接输出两类猫或者狗
    model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim = 1) # 数据处理
    

    创建损失函数和优化器,训练模型

    criterion = nn.NLLLoss() #设置损失函数
    
    lr = 0.001 # 学习率
    
    optimizer_vgg = torch.optim.SGD(model_vgg_new.classifier[6].parameters(),lr = lr) # 随机梯度下降
    
    #训练模型 (模板 建议直接背诵)
    def train_model(model,dataloader,size,epochs=1,optimizer=None):
        model.train()
        
        for epoch in range(epochs):
            running_loss = 0.0
            running_corrects = 0
            count = 0
            for inputs,classes in dataloader:
                inputs = inputs.to(device)
                classes = classes.to(device)
                outputs = model(inputs)
                loss = criterion(outputs,classes)           
                optimizer = optimizer
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
                _,preds = torch.max(outputs.data,1)
                # statistics
                running_loss += loss.data.item()
                running_corrects += torch.sum(preds == classes.data)
                count += len(inputs)
                print('Training: No. ', count, ' process ... total: ', size)
            epoch_loss = running_loss / size
            epoch_acc = running_corrects.data.item() / size
            print('Loss: {:.4f} Acc: {:.4f}'.format(
                         epoch_loss, epoch_acc))
    

    三、代码优化

    1.数据处理

    vgg_format = transforms.Compose([
                    #transforms.CenterCrop(224), 
        			transforms.Resize((224,224)),
        #这里选择缩放而不是中心裁剪,因为简单地选择重心裁剪会让图像的一些特征直接丢失,严重的情况下直接无法捕捉到物体(cat or dog),这样的情况下卷积也没有什么作用了
                    transforms.ToTensor(),
                    normalize,
                ])
    

    下面的图片就可以看出使用缩放而不是中心裁剪的原因:原本图片的头被截去,很难判断这到底是还是。

    2.学习率

    learning rate在fine-turning中一般默认为10-3,但是在我的一些玄学甄选后发现0.0012的效果更好一些,所以我们选择lr=0.0012之后进行训练。

    lr 10-3 115*10-5 12*10-4
    accuracy 0.9645 0.9570 0.9680
    听说有函数可以选择到最合适的学习率,但是我没有找到这个函数。

    按表格自左到右顺序排列:

    3.优化器

    在开始的代码中我们选择了SGD作为优化器,但是在多种优化器的测试过后,我发现使用Adam情况下验证集准确率的效果最好。

    optimizer SGD RMSprop Adam
    accuracy 0.9680 0.9840 0.9880

    没有仔细实验的点:

    1.SGD函数存在一个函数变量动量(Momentum),选择不同的动量的大小可能也会获得更好的效果,但是没有经过准确的实验。

    2.RMSprop的一个参数alpha使用默认值,没有尝试其他数值。

    3.Adam函数的一个参数betas使用默认值,没有实验其他的情况。

    关于这些常见优化器的介绍:https://www.jianshu.com/p/1a1339c4acd7

    4. epoch(批次)

    在使用SGD作为优化器时,将批次设置为2后发现验证集的准确率得到了提升。

    但是在设置Adam作为优化器后我们同样对不同的epoch分别设置为1和2,结果得到提升很小。

    但是实验次数太少,不排除随机梯度下降导致的偶然性影响,默认情况下训练的批次越多,那么得到的模型对于训练的适应性就会越好,往往得到的准确率会更高,但是要注意可能会导致过拟合现象的产生。

    而且我们的训练集规模太小,epoch设置1和2的提升并不是很多。

    四、其他设想过的道路

    1.损失函数的选择

    criterion = nn.NLLLoss()
    

    一般的分类任务往往会选择交叉熵损失函数CrossEntropyLoss()

    但是老师这里选择了负对数似然函数NLLLoss()

    所以我开始选择使用交叉熵损失函数替换掉负对数似然,但是得到的效果并不好

    在查阅资料后我发现NLLLoss和CrossEntropyLoss做的工作很相似,但是有一点区别就是NLLLoss需要配合LogSoftmax,而在源码当中老师选择在全连接层的最后加入了这个函数。

    也就是说他们两个接近等价 CrossEntropyLoss = LogSoftmax + NLLLoss

    所以我的尝试没有到预期的效果。

    2. 冻结不同的参数

    在完成对于VGG的最后一层全连接层的训练后,查阅相关资料,我注意到了在Keras中对Resnet的更改,他选择在冻结大部分卷积层的前提下,选择训练最后一层卷积层和全连接层。

    理所当然的我设想在VGG中同样采取这样的尝试,但是发现VGG并没有直接提供对于提取特征层(features)处理的方法,所以可能需要模仿构建一个新的VGG?

    但是我没有对这一方面进行额外的尝试

    3.Dropout

    众所周知,Dropout对于处理过拟合有着很好的效果,但是一般情况下Dropout设置为0.5,VGG中也相同,所以没有更改。

    五、得分

    本地:

    研习社:

    还有排名:

    最后的得分是98.95
    欢迎推荐 转载请评论

  • 相关阅读:
    SQL Server数据库查询区分大小写、全半角——排序规则的应用
    C#中查询字符串中是否包含指定字符/字符串,使用IndexOf还是Contains?
    【WM6.5】三星I8000按键码及窗体消息发送的方法备忘
    UoBlog 支持 MetaWeblog Api,可以使用 Windows Live Writer 离线发表日志
    C#中如何获取一个字符串的实际字符数
    使用HttpWebRequest发送HTTP请求,同时支持GET/POST方式提交。
    c#.NET中开发可用于Web网页的ActiveX控件
    CorePlex开发手记:一、Winform窗体皮肤及简单换肤机制
    .NET中简易实现线程安全
    在C#中截取指定长度的中文字符串(效率提高2500倍)
  • 原文地址:https://www.cnblogs.com/clearlove777/p/13916521.html
Copyright © 2020-2023  润新知