第四次作业:猫狗大战挑战赛
Part1 想法 x 解读
| 姓名 | ~ |
|---|---|
| 蹇浩航 | ImageNet,彳亍!在本次实验中使用了ImageNet预训练好的VGG模型,冻结了前面层的参数,只在训练中更新最后一层的参数,大大降低了训练的复杂度,最后所得结果还挺好,简单实战之后确实让我对VGG的理解又有了一些进步。 |
| 赵泽昊 | 最大的收获是,在图像处理阶段,旋转、位移、亮度调节等方法可以在样本数据有限时,无形中增加了样本数量,在一定程度上能够提高准确率。 |
| 丁浩喆 | 我们可以通过迁移学习大部分存在相关数据或任务的模型参数来应用到新的模型上并优化模型的学习效率。本次实验,由于我们自己引入的数据集,数据量相对于原数据集来说少,数据相似度低。所以我们根据新数据对较高层进行重新的训练。但是对于新知识的学习存在一定的缺陷,对大多数的内容不是十分理解的透彻,需要借助网上的博客教学来完成。但是,初步对VGG模型有了一定的认识,后续加深此类的学习。 |
| 郭升 | “猫狗大战”是一个典型的图像识别二分类问题,其需要搭建一个深度学习网络模型,利用所提供的带有标签(标签为猫或者狗)的猫狗图片对模型进行训练,用最终训练好的模型对不带标签的猫狗图片进行预测分类。而说到计算机视觉,卷积神经网络就不得不提,其在这种需要分类的作业中效果显然,因此这次仍然使用其进行分类,效果确实不错。 |
| 郭英来 | \'\'猫狗大站\'\'顾名思义,就是实现猫和狗的分类问题。大致的过程在处理数据集时,首先要对图像的大小进行统一缩放,之后将图片读取为像素矩阵,并转为卷积神经网络可以识别的形状。构建卷积神经网络,有卷积,有池化,有全连接,最后构建代价函数,优化器,通过迭代求出预测准确率。卷积神经网络在处理计算机视觉方面效果显然,因此在此实验中使用其进行分类效果显然。 |
Part2 实验过程
使用 VGG 模型进行猫狗大战
-
准备
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())
0x01 下载数据
Jeremy Howard 提供了数据的下载,猫和狗的图片放在单独的文件夹中, 同时还提供了一个Validation数据。
由于在colab上速度较慢,选用了新整理的数据集,训练集包含1800张图(猫的图片900张,狗的图片900张),测试集包含2000张图
0x02 数据处理
torchvision 支持对输入数据进行一些复杂的预处理/变换。
datasets 是 torchvision 中的一个包,可以用做加载图像数据。它可以多线程从硬盘中读取数据,使用 mini-batch 的形式,在网络训练中向 GPU 输送。在使用CNN处理图像时,需要进行预处理。图片将被整理成 \(224\times 224 \times 3\) 的大小,同时还将进行归一化处理。
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
# 查看 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)
# 显示图片的小程序
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
# 显示 labels_try 的5张图片,即valid里第一个batch的5张图片
out = torchvision.utils.make_grid(inputs_try)
imshow(out, title=[dset_classes[x] for x in labels_try])
0x03 创建 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
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()])
可以看出识别结果较准确
0x04 修改最后一层,冻结前面层的参数
在这个实验中是使用预训练好的模型,因此,需要把最后的 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)
0x05 训练并测试全连接层
三步:创建损失函数和优化器、训练模型、测试模型
\'\'\'
第一步:创建损失函数和优化器
损失函数 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)
测试
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\'])
0x06 可视化模型预测结果(主观分析)
把预测的结果和相对应的测试图像输出出来比较
# 单次可视化显示的图片个数
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])
0x07 上传结果评测
得分还行