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


猫狗大战挑战赛

提交结果截图

VGG模型代码解读

首先,我们对给的样例代码进行解读。可以看到样例代码使用了VGG模型

数据集加载

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.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
oader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['valid'], batch_size=5, shuffle=False, num_workers=6)

这段代码的作用为加载数据集,vgg_format为对图片的处理,加载的数据从原图中心截取224*224,并进行正则化和归一化。代码使用了torchvision的datasets.ImageFolder方法来加载数据。
在加载完成后,使用DataLoader将dataset转换为可迭代的数据,catch_size规定了迭代时每次分组的大小。

构建模型

model_vgg = models.vgg16(pretrained=True)
model_vgg = model_vgg.to(device)

model_vgg_new = model_vgg
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)
model_vgg_new = model_vgg_new.to(device)

样例并没有选择重新定义一个模型,然后训练。样例使用了VGG16的预训练模型,事实上,对于猫狗大战这样训练数据集较大的深度学习工作来说,重新训练网络较为费时。而另一方面,虽然不同的任务所进行的细节不相同,但在选定模型后,其内部的参数许多与实际完成的任务内容无关,可以使用预训练模型确定无关的参数而只训练与具体任务有关的参数,这样可以降低训练的复杂度,同时也有较好的效果。虽然之前就知道有冻结参数的方法,但这是我第一次实际进行操作。设置param.requires_grad = False表示不再接受梯度改变参数,并将模型的最后两层设为分类类别数为2和输出分类结果。

训练模型

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))
train_model(model_vgg_new,loader_train,size=dset_sizes['train'], epochs=1, 
            optimizer=optimizer_vgg)

正常的训练模型,区别在于使用训练模型并冻结了参数,使得训练过程中仅修改模型最后两层的参数。

思考与优化

样例代码展示了如何使用预训练模型来降低模型训练复杂度,这样训练出来的模型有较好的效果且比重新训练节省时间。但这样的模型还有一些改进的地方,主要有以下几点:1. 用于训练的数据可以优化,包括训练数据的使用量和数据的处理,虽然很难说怎样的图片处理方式更好,但仅使用数据集中少部分图片做训练效果应该不如使用大量的数据做训练。 2. 模型的选用,VGG模型效果不错,但我想试试别的模型。 3. 训练的方式,冻结参数降低了训练的复杂性,但同时也使得模型被冻结参数的部分与实际任务不太相符,与完全冻结相比,也许可以在训练时前面的预训练模型的参数做少量修改。

基于以上思考,我使用ResNet18作为模型,并修改了训练方式,对预训练模型的参数做较小的修改。同时,秉承着大就是美,多就是好的原则,在训练中选择将给定数据集20000张图片喂给模型。
参考博客地址

以下为代码

import torch
import torchvision
import torchvision.transforms as transforms
import pandas as pd
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import re

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

transform = transforms.Compose(
    [transforms.Resize(256),
     transforms.CenterCrop(size=224),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 使用ImageFolder时注意数据集的要求
train_data = torchvision.datasets.ImageFolder('cat_dog/train', transform=transform)
test_data = torchvision.datasets.ImageFolder('cat_dog/test', transform=transform)
train_loader = torch.utils.data.DataLoader(train_data, batch_size=32, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=32, shuffle=False)

# 训练模型
def train(net, train_iter, test_iter, criterion, optimizer, num_epochs):
    net = net.to(device)
    print("training on", device)
    for epoch in range(num_epochs):
        net.train()  # 训练模式
        train_loss_sum, train_acc_sum, n, batch_count = 0.0, 0.0, 0, 0
        for i, (inputs, labels) in enumerate(train_iter):
            print(i)
            inputs, labels = inputs.to(device), labels.to(device)
            optimizer.zero_grad()  # 梯度清零
            labels_hat = net(inputs)
            loss = criterion(labels_hat, labels)
            loss.backward()
            optimizer.step()

            train_loss_sum += loss.cpu().item()
            train_acc_sum += (labels_hat.argmax(dim=1) == labels).sum().cpu().item()
            n += labels.shape[0]
            batch_count += 1
            
        print('epoch %d, loss %.4f, train acc %.3f'
              % (epoch + 1, train_loss_sum / batch_count, train_acc_sum / n))

# 使用ResNet18预训练模型
pretrained_net = torchvision.models.resnet18(pretrained=True)
num_ftrs = pretrained_net.fc.in_features
pretrained_net.fc = nn.Linear(num_ftrs, 2)

# 对预训练模型进行调整
output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())
lr = 0.01
# 在ResNet18预训练模型上进行微调,对于预训练好的参数,使用较小的学习率来微调,而对于全连接层中的随机初始化参数,需要更大的学习率从头训练。
optimizer = optim.SGD([{'params': feature_params},
                       {'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
                      lr=lr, weight_decay=0.001)

loss = torch.nn.CrossEntropyLoss()
# 训练模型
train(pretrained_net, train_loader, test_loader, loss, optimizer, num_epochs=5)

res = []
for i, (inputs, labels) in enumerate(test_loader):
        labels_hat = pretrained_net(inputs).argmax(dim=1)
        for label in labels_hat:
            res.append(label)

# 正则匹配数字,获取图片名称
pattern = re.compile(r'[0-9]+')
def getImg(path):
    s = re.findall(pattern, path)[0]
    return int(s)

imgs = [getImg(x[0]) for x in test_data.imgs]
# 分类结果与图片对应
d = {}
for i in range(len(imgs)):
    d[imgs[i]] = int(res[i])
ans = np.zeros((len(res))).astype(int)
for k, v in d.items():
    ans[k] = v
# 保存结果
ans = pd.DataFrame(ans)
ans.to_csv('ans.csv')

总结与感悟

训练的时候虽然用的是预训练模型,但实际训练的时间还是比我想的长。之前的练习使用的数据量比较小,网络结果也比较简单,所以训练得快,但在实际应用中大量数据和较深的网络模型是常见的,所以如何加速模型训练十分重要。在自己写代码的时候,主要碰到两个问题:1. 对如何加载数据不熟悉,我现在才知道使用dataset加载数据,数据集中的数据与文件夹内文件的顺序并不相同,而是以类似于将文件名视为字符串进行排序。当然了,这很合理。2. 不知道该如何调整预训练模型的参数,我是看了别人写的博客才知道该怎么调的。深度学习涉及到的理论非常多,想要把技术问题从头到尾弄明白还是挺难的。有的时候会使用一个模型来解决问题,可对于模型的架构、数据的处理还是缺少理解。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM