第四次作业:CNN实战

本博客为OUC2022秋季软件工程第四次作业——CNN实战。 

目录

使用VGG模型进行猫狗大战

1. 下载数据

2. 数据处理

3. 创建 VGG Model

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

5. 训练并测试全连接层

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

AI研习社“猫狗大战”测试

1、下载测试集

2、读取图片并进行测试

3、结果写入csv文件 

4、提交测试结果 

想法和思考


使用VGG模型进行猫狗大战

引入库,并判断是否存在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())

第四次作业:CNN实战_第1张图片

1. 下载数据

通过wget下载数据集并解压。

! wget http://fenggao-image.stor.sinaapp.com/dogscats.zip
! unzip dogscats.zip

第四次作业:CNN实战_第2张图片

2. 数据处理

datasets 是 torchvision 中的一个包,可以用做加载图像数据。它可以以多线程(multi-thread)的形式从硬盘中读取数据,使用 mini-batch 的形式,在网络训练中向 GPU 输送。在使用CNN处理图像时,需要进行预处理。图片将被整理成  的大小,同时还将进行归一化处理。

torchvision 支持对输入数据进行一些复杂的预处理/变换 (normalization, cropping, flipping, jittering 等)。

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

第四次作业:CNN实战_第3张图片

运行以下代码,查看dsets属性,可以看到,0表示cats,1表示dogs。 

# 通过下面代码可以查看 dsets 的一些属性

print(dsets['train'].classes)
print(dsets['train'].class_to_idx)
print(dsets['train'].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['valid'], batch_size=5, shuffle=False, num_workers=6)


'''
valid 数据一共有2000张图,每个batch是5张,因此,下面进行遍历一共会输出到 400
同时,把第一个 batch 保存到 inputs_try, labels_try,分别查看
'''
count = 1
for data in loader_valid:
    print(count, end='\n')
    if count == 1:
        inputs_try,labels_try = data
    count +=1

print(labels_try)
print(inputs_try.shape)

第四次作业:CNN实战_第4张图片

 定义一个可以显示图片的函数。

# 显示图片的小程序

def imshow(inp, title=None):
#   Imshow for Tensor.
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = np.clip(std * inp + mean, 0,1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated

第四次作业:CNN实战_第5张图片

我们可以看到,图片可以成功显示。 

# 显示 labels_try 的5张图片,即valid里第一个batch的5张图片
out = torchvision.utils.make_grid(inputs_try)
imshow(out, title=[dset_classes[x] for x in labels_try])

3. 创建 VGG Model

torchvision中集成了很多在 ImageNet (120万张训练数据) 上预训练好的通用的CNN模型,可以直接下载使用。

在本课程中,我们直接使用预训练好的 VGG 模型。同时,为了展示 VGG 模型对本数据的预测结果,还下载了 ImageNet 1000 个类的 JSON 文件。

在这部分代码中,对输入的5个图片利用VGG模型进行预测,同时,使用softmax对结果进行处理,随后展示了识别结果。可以看到,识别结果是比较非常准确的。

!wget https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json

第四次作业:CNN实战_第6张图片

model_vgg = models.vgg16(pretrained=True)

with open('./imagenet_class_index.json') as f:
    class_dict = json.load(f)
dic_imagenet = [class_dict[str(i)][1] for i in range(len(class_dict))]

inputs_try , labels_try = inputs_try.to(device), labels_try.to(device)
model_vgg = model_vgg.to(device)

outputs_try = model_vgg(inputs_try)

print(outputs_try)
print(outputs_try.shape)

'''
可以看到结果为5行,1000列的数据,每一列代表对每一种目标识别的结果。
但是我也可以观察到,结果非常奇葩,有负数,有正数,
为了将VGG网络输出的结果转化为对每一类的预测概率,我们把结果输入到 Softmax 函数
'''
m_softm = nn.Softmax(dim=1)
probs = m_softm(outputs_try)
vals_try,pred_try = torch.max(probs,dim=1)

print( 'prob sum: ', torch.sum(probs,1))
print( 'vals_try: ', vals_try)
print( 'pred_try: ', pred_try)

print([dic_imagenet[i] for i in pred_try.data])
imshow(torchvision.utils.make_grid(inputs_try.data.cpu()), 
       title=[dset_classes[x] for x in labels_try.data.cpu()])

第四次作业:CNN实战_第7张图片

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

VGG 模型如下图所示,注意该网络由三种元素组成:

  • 卷积层(CONV)是发现图像中局部的 pattern
  • 全连接层(FC)是在全局上建立特征的关联
  • 池化(Pool)是给图像降维以提高特征的 invariance

我们的目标是使用预训练好的模型,因此,需要把最后的 nn.Linear 层由1000类,替换为2类。为了在训练中冻结前面层的参数,需要设置 required_grad=False。这样,反向传播训练梯度时,前面层的权重就不会自动更新了。训练中,只会更新最后一层的参数。

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)

第四次作业:CNN实战_第8张图片

5. 训练并测试全连接层

包括三个步骤:第1步,创建损失函数和优化器;第2步,训练模型;第3步,测试模型。

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

损失函数 NLLLoss() 的 输入 是一个对数概率向量和一个目标标签. 
它不会为我们计算对数概率,适合最后一层是log_softmax()的网络. 
'''
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)

第四次作业:CNN实战_第9张图片

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['valid'])

第四次作业:CNN实战_第10张图片

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

主观分析就是把预测的结果和相对应的测试图像输出出来看看,一般有四种方式:

  • 随机查看一些预测正确的图片
  • 随机查看一些预测错误的图片
  • 预测正确,同时具有较大的probability的图片
  • 预测错误,同时具有较大的probability的图片
  • 最不确定的图片,比如说预测概率接近0.5的图片
# 单次可视化显示的图片个数
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['valid'][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])

# 类似的思路,可以显示错误分类的图片,这里不再重复代码

第四次作业:CNN实战_第11张图片

AI研习社“猫狗大战”测试

1、下载测试集

下载AI研习社给定的数据集,将test文件夹压缩成zip格式并将测试集解压导入colab文件夹,解压。

!unzip "/content/drive/MyDrive/猫狗大战/test.zip" -d "/content/test/test/"

第四次作业:CNN实战_第12张图片

2、读取图片并进行测试

使用ImageFolder读取测试图片,加载完数据集后进行测试。

dsets_mine = datasets.ImageFolder(r"/content/test", vgg_format)
 
loader_test = torch.utils.data.DataLoader(dsets_mine, batch_size=1, shuffle=False, num_workers=0)
from tqdm import tqdm
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)    
        #这里是切割路径
        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)

第四次作业:CNN实战_第13张图片

3、结果写入csv文件 

之后,我们自行写一段代码,将测试集的测试结果写入一个csv文件,代码如下。

with open("测试集.csv",'a+') as f:
    for key in range(2000):
        #导入测试集路径
        f.write("{},{}\n".format(key,dic["/content/test/test/test/"+str(key)]))
print("Successfully Writing!")

第四次作业:CNN实战_第14张图片

4、提交测试结果 

此时,“测试集.csv”文件已经成功导出,我们在提交页面提交该文件。

第四次作业:CNN实战_第15张图片

 评分96,感觉差不多合格。

想法和思考

下面说一下关于提高准确率的一些思路。

一是换更加准确的模型,比如我们可以尝试AI研习社提供的用TensorFlow实现的Baseline模型并加之修改;

二是调整参数,比如学习率等参数;

三是使用数据加强,比如将训练集的图片经过不同角度的旋转或是翻转再进行训练,提高拟合度。

你可能感兴趣的:(cnn,人工智能,神经网络)