第四次作业:猫狗大战挑战赛
VGG网络
VGG是由Simonyan 和Zisserman在文献《Very Deep Convolutional Networks for Large Scale Image Recognition》中提出卷积神经网络模型,是2014年的 ImageNet图像分类与定位挑战赛的第二名。VGG网络影响巨大,一直到今天仍然很多人在使用。
可以看到VGG网络是一个非常庞大的网络,训练难度也很高。这个作业为参加 Kaggle 于2013年举办的猫狗大战比赛的训练赛,判断一张输入图像是“猫”还是“狗”,使用在 ImageNet 上预训练 的 VGG 网络进行测试。因为原网络的分类结果是1000类,所以这里进行迁移学习,对原网络进行 fine-tune (即固定前面若干层,作为特征提取器,只重新训练最后两层)。使用Google Colab平台实现,之后按照比赛规定的格式输出,上传结果在线评测。
代码实现
1.载入库并判断是否有GPU
import numpy as np
import matplotlib.pyplot as plt
import os
import torch
import torch.nn as nn
import torchvision
from torchvision import models,transforms,datasets
import time
import json
# 判断是否存在GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s ' % torch.cuda.is_available())
2.载入数据并对数据进行处理
# 获取数据
! unzip /content/drive/MyDrive/cat_dog.zip
# 对图像数据归一化
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 = './cat_dog' # 图片文件路径
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
# 数据分为训练集和有效性测试集
loader_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)
3.导入VGG模型并构建训练模型
# 导入VGG模型
model_vgg = models.vgg16(pretrained=True)
model_vgg_new = model_vgg;
# 这里需要设置 required_grad=False
for param in model_vgg_new.parameters():
param.requires_grad = False
# 我们的目标是使用预训练好的模型,把最后的 nn.Linear 层由1000类,替换为2类
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)
# print(model_vgg_new.classifier)
'''
第一步:创建损失函数和优化器
损失函数 NLLLoss() 的 输入 是一个对数概率向量和一个目标标签.
它不会为我们计算对数概率,适合最后一层是log_softmax()的网络.
'''
criterion = nn.NLLLoss()
# 学习率
lr = 0.001
# 随机梯度下降
optimizer_vgg = torch.optim.Adam(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
max_acc = 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
if epoch_acc>max_acc:
max_acc = epoch_acc
torch.save(model, '/content/drive/MyDrive/Colab_Notebooks/model_best_new.pth')
tqdm.write("\n Got A Nice Model Acc:{:.8f}".format(max_acc))
tqdm.write('\nepoch: {} \tLoss: {:.8f} Acc: {:.8f}'.format(epoch,epoch_loss, epoch_acc))
time.sleep(0.1)
torch.save(model, '/content/drive/MyDrive/Colab_Notebooks/model.pth')
tqdm.write("Got A Nice Model")
# 模型训练
train_model(model_vgg_new,loader_train,size=dset_sizes['train'], epochs=5,
optimizer=optimizer_vgg)
本程序对于优化器,由初始的SGD改为Adam优化器,训练效果一般。
4.构建测试程序
import torch
import numpy as np
from torchvision import transforms,datasets
from tqdm import tqdm
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
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,
])
dsets_mine = datasets.ImageFolder(r"_test", vgg_format)
loader_test = torch.utils.data.DataLoader(dsets_mine, batch_size=1, shuffle=False, num_workers=0)
model_vgg_new = torch.load(r'/content/drive/MyDrive/Colab_Notebooks/model_best_new.pth')
model_vgg_new = model_vgg_new.to(device)
有一个值得注意的是pytorch Dataset 里面常用的ImageFolder的用法:
ImageFolder(root,transform=None,target_transform=None,loader=
default_loader)
所有的文件按文件夹保存好,每个文件夹下面存贮同一类别的图片,文件夹的名字为分类的名字,这里用的_test文件夹里的test测试集
dic = {}
def test(model,dataloader,size):
model.eval()
predictions = np.zeros(size)
cnt = 0
for inputs,_ in tqdm(dataloader):
inputs = inputs.to(device)
outputs = model(inputs)
_,preds = torch.max(outputs.data,1)
#这里是切割路径,因为dset中的数据不是按1-2000顺序排列的
key = dsets_mine.imgs[cnt][0].split("\\")[-1].split('.')[0]
dic[key] = preds[0]
cnt = cnt +1
test(model_vgg_new,loader_test,size=2000)
5.保存及提交测试结果
with open("result.csv",'a+') as f:
for key in range(2000):
f.write("{},{}\n".format(key,dic["_test/test/"+str(key)]))
研习社网站得到的测试结果为98.1,结果不错,但还有很大的进步空间,可以进一步改造模型,或者更直接的方法,多跑几遍,多试几遍的方法,提高正确率。
总结
1.适当扩充训练集,总训练图像为20000张,测试集为2000张。
2.适当更换了优化器。
3.目前能力还很可惜,需要学习的地方还很多。