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

这个作业为 Kaggle 于2013年举办的猫狗大战比赛,判断一张输入图像是“猫”还是“狗”。
下面是使用在 ImageNet 上预训练 的 VGG 网络进行训练和测试的过程编码。

这里我们使用Google Colab来编写下面的代码。
关于Colab的使用之后有时间我再编写博客并贴上链接,下面先写深度学习代码。

1. 导入需要的包,检查使用设备

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())

当使用的是GPU时,输出结果如下所示:
在这里插入图片描述

2. 导入数据集并修改数据集目录结构

数据集下载链接为:https://static.leiphone.com/cat_dog.rar
里面包含三个文件夹如下图所示:
在这里插入图片描述

test:比赛用的测试集(2000张图片,图片名为0 ~ 1999)
train:训练集(10000张猫,10000张狗,图片名分别为cat_0 ~ cat_9999,dog_0 ~ dog_9999)。
val:测试集(1000张猫,1000张狗,图片名分别为cat_0 ~ cat_999,dog_0 ~ dog_999)。

! wget -P ./drive/MyDrive/ https://static.leiphone.com/cat_dog.rar	#下载到指定目录
! unrar x cat_dog.rar ./drive/MyDrive/	#解压缩到指定目录

输出结果如下,图片被解压缩出来,并保持原有的目录结构不变:
在这里插入图片描述

下面这一步是为了第三步用datasets.ImageFolder()加载数据方便,所以改变了解压缩之后的目录结构。
/train/val中添加分别添加了文件夹/cats/dogs,并把猫和狗的照片分类分别放入两个文件夹中。这里如果不这样修改,datasets.ImageFolder()无法正常使用,需要使用其他方式加载数据。

%%shell
for filename in `ls ./drive/MyDrive/cat_dog/train`
do
   if [ ${filename: 0:3} == "cat" ] 
   then
      mkdir -p ./drive/MyDrive/cat_dog/train/cats
      mv -i ./drive/MyDrive/cat_dog/train/$filename ./drive/MyDrive/cat_dog/train/cats/$filename
   fi
done

for filename in `ls ./drive/MyDrive/cat_dog/train`
do
   if [ ${filename: 0:3} == "dog" ] 
   then
      mkdir -p ./drive/MyDrive/cat_dog/train/dogs
      mv -i ./drive/MyDrive/cat_dog/train/$filename ./drive/MyDrive/cat_dog/train/dogs/$filename
   fi
done

for filename in `ls ./drive/MyDrive/cat_dog/val`
do
   if [ ${filename: 0:3} == "cat" ] 
   then
      mkdir -p ./drive/MyDrive/cat_dog/val/cats
      mv -i ./drive/MyDrive/cat_dog/val/$filename ./drive/MyDrive/cat_dog/val/cats/$filename
   fi
done

for filename in `ls ./drive/MyDrive/cat_dog/val`
do
   if [ ${filename: 0:3} == "dog" ] 
   then
      mkdir -p ./drive/MyDrive/cat_dog/val/dogs
      mv -i ./drive/MyDrive/cat_dog/val/$filename ./drive/MyDrive/cat_dog/val/dogs/$filename
   fi
done

for filename in `ls ./drive/MyDrive/cat_dog/test`
do
    mkdir -p ./drive/MyDrive/cat_dog/test/img
    mv -i ./drive/MyDrive/cat_dog/test/$filename ./drive/MyDrive/cat_dog/test/img/$filename
done

此时的目录结构如下图所示:
在这里插入图片描述

3. 数据处理

#数据标准化
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 

#图像预处理成224*224*3大小
vgg_format = transforms.Compose([
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
            ])

data_dir = './drive/MyDrive/cat_dog'
dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), vgg_format)
         for x in ['train', 'val']}

dset_sizes = {x: len(dsets[x]) for x in ['train', 'val']}
dset_classes = dsets['train'].classes
# 通过下面代码可以查看 dsets 的一些属性
print(dsets['val'].classes)
print(dsets['val'].class_to_idx)
print(dsets['val'].imgs[:5])
print('dset_sizes: ', dset_sizes)

输出结果为:
在这里插入图片描述
分别加载训练用的数据集和测试用的数据集。

loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['val'], batch_size=5, shuffle=False, num_workers=6)

4. 创建VGG Model

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

5. 修改最后一层,冻结前面层的参数

print(model_vgg)

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)

print(model_vgg_new.classifier)

6. 训练并测试全连接层

这里将训练的学习率定为0.0001,epochs=100。
尝试过将学习率定为更小的值,发现到损失值曲线收敛的很慢,故这里选取lr=0.0001。
epochs=1时其实准确率就能达到94%以上,有较好的效果了。这里设定为100,希望能训练出更好的模型。

'''
第一步:创建损失函数和优化器

损失函数 NLLLoss() 的 输入 是一个对数概率向量和一个目标标签. 
它不会为我们计算对数概率,适合最后一层是log_softmax()的网络. 
'''
criterion = nn.NLLLoss()

# 学习率
lr = 0.0001

# 随机梯度下降
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('epoch: ', epoch)
        print('Loss: {:.8f} Acc: {:.8f}'.format(
                     epoch_loss, epoch_acc))
        # 测试准确率足够大时保存模型
        if epoch_acc > 0.97900000:
          torch.save(model, './drive/MyDrive/model/model_' + str(epoch_acc)[2:] + '_' + str(epoch) + '.pth')        
        
# 模型训练
train_model(model_vgg_new,loader_train,size=dset_sizes['train'], epochs=100, 
            optimizer=optimizer_vgg)

下面是训练过程截图:
在这里插入图片描述
保存的模型如 下图所示:
在这里插入图片描述

选取一个刚刚保存下来的模型,导入测试数据集进行测试:

model_vgg_new = torch.load('./drive/MyDrive/model/model_9804_60.pth');
model_vgg_new = model_vgg_new.to(device)

def test_model(model,dataloader,size):
    model.eval()
    predictions = np.zeros(size)
    all_classes = np.zeros(size)
    all_proba = np.zeros((size,2))
    i = 0
    running_loss = 0.0
    running_corrects = 0
    for inputs,classes in dataloader:
        inputs = inputs.to(device)
        classes = classes.to(device)
        outputs = model(inputs)
        loss = criterion(outputs,classes)           
        _,preds = torch.max(outputs.data,1)
        # statistics
        running_loss += loss.data.item()
        running_corrects += torch.sum(preds == classes.data)
        predictions[i:i+len(classes)] = preds.to('cpu').numpy()
        all_classes[i:i+len(classes)] = classes.to('cpu').numpy()
        all_proba[i:i+len(classes),:] = outputs.data.to('cpu').numpy()
        i += len(classes)
        print('Testing: No. ', i, ' 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))
    return predictions, all_proba, all_classes
  
predictions, all_proba, all_classes = test_model(model_vgg_new,loader_valid,size=dset_sizes['val'])

测试结果如下图所示:
在这里插入图片描述

7.可视化模型预测结果(主观分析)

随机选择一些正确的预测结果和相对应的图片输出出来,比对一下看看结果。

n_view = 8	# 单次可视化显示的图片个数
correct = np.where(predictions==all_classes)[0]
from numpy.random import random, permutation
idx = permutation(correct)[:n_view]
print('random correct idx: ', idx)
loader_correct = torch.utils.data.DataLoader([dsets['val'][x] for x in idx],
                  batch_size = n_view,shuffle=True)
for data in loader_correct:
    inputs_cor,labels_cor = data
# Make a grid from batch
out = torchvision.utils.make_grid(inputs_cor)
imshow(out, title=[l.item() for l in labels_cor])

输出结果如下图所示。可以看到有些图片只有腿,计算机都能准确识别出来是猫还是狗,这种学习方法还是很强的。
在这里插入图片描述

8. 处理未分类过的待测试数据并提交到练习赛

/cat_dog/test/img中的图片是未分类过的,编码是从0~1999,共2000张图片,要从第6步训练并保存下来的model中选取准确率较高的model对待测试数据进行分类,并按规定将预测数据保存为下图所示格式。
在这里插入图片描述
代码如下图所示。
注意:文件读取顺序并不是按0.jpg,1.jpg一直顺延这样的顺序,实际上的顺序是0.jpg,10.jpg,11.jpg这样的顺序一直顺延。因此代码中我截取了文件名中的数字作为索引,这样就能无视读取顺序,将文件与预测值对应起来。

data_dir = './drive/MyDrive/cat_dog/test'
dsets = {'test': datasets.ImageFolder(data_dir, vgg_format)}
dset_sizes = len(dsets['test'])

loader_test = torch.utils.data.DataLoader(dsets['test'], batch_size=1, num_workers=6)

model_vgg_new = torch.load('./drive/MyDrive/model/model_9804_60.pth');
model_vgg_new = model_vgg_new.to(device)

import csv
def test_model(model,dataloader,size):
    model.eval()
    predictions = np.zeros(size)
    i = 0
    for inputs,classes in dataloader:
        inputs = inputs.to(device)
        classes = classes.to(device)
        outputs = model(inputs)         
        _,preds = torch.max(outputs.data,1)
        # statistics
        predictions[int(dsets['test'].imgs[i][0][33:-4])] = preds.to('cpu').numpy()
        i += 1
        print('Testing: No. ', i, ' process ... total: ', size)   
    csvFile = open("./drive/MyDrive/cat_dog_result/result.csv", "w")
    writer = csv.writer(csvFile)
    for i in range(dset_sizes):
      writer.writerow([i, predictions[i]])  
    print("Save successfully!!!")
    return predictions
  
predictions = test_model(model_vgg_new,loader_test,size=dset_sizes)

成功运行并保存成功后显示如下图所示。
在这里插入图片描述
将保存的文件下载下来并提交到AI研习社网站,网址如下:
https://god.yanxishe.com/41
可以得到预测结果的准确率和排名:
在这里插入图片描述

9. 总结

1.训练时增加epochs会训练出更好的结果,但是训练的更好的程度并不是随着epochs的增加无限增加的,故epochs选取适当的值即可,无需过大,当然也不能过小。
2.学习率不能定得过小,过小的话损失值曲线收敛的很慢,在有限的epochs中不能训练出很好地结果。学习率如果过大开始时loss下降得快,但后面会产生无法拟合的现象。因此学习率应该选取合适的值不应该过大或过小。
3.进一步改进需要采用更好的模型,如使用其他深度学习网络,增加网络深度,可能会产生更好的效果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值