2. 猫和狗

传送门:https://www.lintcode.com/ai/cats-and-dogs/overview

题目描述

给出一张猫或狗的图片,识别出这是猫还是狗。

这种识别具有很重要的意义,比如:

Web服务为了进行保护,会防止一些计算机进行恶意访问或信息爬取,进而设立一些验证问题,这些验证问题对于人来说很容易做,但是对于计算机这很困难。这样的方法称为CAPTCHA(完全自动公开的图灵测试)或HIP(人类交互证明)。 HIP有很多用处,例如减少垃圾邮件,防止暴力破解密码等。

比较有名的Asirra(用于限制访问的动物图像识别)就是一个HIP,它会让用户识别图片信息,比如识别出图片中是猫还是狗。对于人来说这很容易,但是对于计算机很困难。以下是Asirra的一个例子:

寻找流浪宠物为其提供住所的网站——Petfinder.com,向微软研究院提供了超过三百万张猫和狗的图像,这些图片由美国各地成千上万的动物收容所手动分类。

对于要入侵的计算机,随机猜测一般是最简单的攻击方法。图片识别并不容易,因为图片之间不同的的背景,角度,姿势,亮度等都存在着巨大的差异,很难识别。

不过随着机器学习——尤其是神经网络的发展,这项工作精度可以达到60%以上。而60%分类器就已经能将12幅图像的猜测概率从1/4096提高到1/459。

这道题目会给大家上万张的图片作为训练集,你能在猫狗之间分辨出它们的差异吗?

小提示

  • 本题是一个图像识别中的二分类问题
  • 建议先把不同尺寸的图片想办法转换成同样格式的输入,例如把图片变成一样的分辨率,可以采用一些图片尺寸放缩的方法,例如:双线性插值或临近取样插值等
  • 统一输入后可以采用卷积神经网络,至于卷积神经网络的细节架构可以参考一些经典的模型,比如LeNet或VGGNet等
  • 最后使用该CNN(卷积神经网络)进行预测

先修技能

  • 掌握初步的图像处理能力,如转化灰度图像,彩色图像的表示。
  • 掌握卷积神经网络及相关的技巧,如SoftMax、ReLu等, 或者SVM等较强的分类器。

术语解释

  • 卷积神经网络:在机器学习中,卷积神经网络是一种深度前馈人工神经网络,已成功地应用于图像识别。卷积神经网络是一种前馈神经网络,人工神经元可以响应周围单元,可以大型图像处理。卷积神经网络包括卷积层和池化层。卷积神经网络是近年发展起来,并引起广泛重视的一种高效识别方法。

  • 灰度图像:灰度数字图像是每个像素只有一个采样颜色的图像。这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑白两种颜色,灰度图像在黑色与白色之间还有许多级的颜色深度。

  • 双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。双线性插值作为数值分析中的一种插值算法,广泛应用在信号处理,数字图像和视频处理等方面。

评价

目标

给出一张猫或狗的图片,识别出这是猫还是狗?

评价

本题对于提交文件,将使用 准确率(accuracy) 作为评判标准。

准确率(accuracy)是用来衡量算法预测结果的准确程度,具体指测试集中算法预测正确的数量占总数的比例。

Accuracy=\frac{TP+TN}{TP+FN+FP+TN}Accuracy=​TP+FN+FP+TN​​TP+TN​​

TP(True Positive)是将正类预测为正类的结果数目

FP(False Positive)是将负类预测为正类的结果数目

TN(True Negative)是将负类预测为负类的结果数目

FN(False Negative)是将正类预测为负类的结果数目

这是一个图像分类问题,在图像问题的处理上,我们通常使用卷积神经网络,一方面,相较于全连接层有更少的参数;另一方面,卷积操作能够很好的兼顾图像上所存在的局部数据关联信息。我们将整个项目分块解耦,分为Dataset、Model、Train、Test部分。当然这些都不是严格的代码规范,只是本人自行的代码风格,各位读者请自便。鉴于自己随意定义的网络效果实在太差,因此我们使用VGG11模型:

Dataset.py 该部分主要用于实现数据加载,框架要求继承DataSet类,并覆写__len__返回数据总个数,__getitem__指定索引返回数据和结果。

DogVSCatDataSet.py

#编写自己的数据集类
from torch.utils.data import Dataset
import cv2
import os

class DogCatSet(Dataset):
    def __init__(self,file_path):#在初始化完成 文件名-类别标签映射 猫-0 狗-1
        self.file_list = os.listdir(file_path)
        self.image_label = []
        for file_name in self.file_list:
            parts = file_name.split(".")
            if parts[0] == "cat":
                self.image_label.append((os.path.join(file_path,file_name),0))
            elif parts[0] == "dog":
                self.image_label.append((os.path.join(file_path,file_name),1))

    def __len__(self):
        return len(self.image_label)

    def __getitem__(self, item):
        image_path , label = self.image_label[item]
        img = cv2.imread(image_path)
        img = cv2.resize(img, (448, 448), interpolation=cv2.INTER_AREA)
        return img,label

[注]:VGG系列网络接受的输入是224×224×3,但是本题的图片width和height大多都在400左右浮动,因此我们统一resize到448×448,需要调整最后全连接层的输入尺度,另外,最终的全连接层部分从4096尺度直接变换到10的尺度跨越过大,我们加入一个中间尺度256作为缓冲。

Model.py 主要是定义模型

VGGModel.py

import torch.nn as nn

class VGG11(nn.Module):

    def __init__(self):
        super(VGG11,self).__init__()
        self.conv3_64 = nn.Conv2d(3,64,3,1,1)
        self.conv64_128 = nn.Conv2d(64,128,3,1,1)
        self.conv128_256 = nn.Conv2d(128,256,3,1,1)
        self.conv256_256 = nn.Conv2d(256, 256, 3, 1, 1)
        self.conv256_512 = nn.Conv2d(256, 512, 3, 1, 1)
        self.conv512_512 = nn.Conv2d(512, 512, 3, 1, 1)
        self.fc1 = nn.Linear(512*14*14,4096)
        self.fc2 = nn.Linear(4096,10)
        self.MaxPool = nn.MaxPool2d(2, 2)
        self.softmax = nn.Softmax()
        self.relu = nn.ReLU()

    def forward(self, x ):# 224*224*3
        x = x.permute(0, 3, 1, 2)

        x = self.MaxPool(self.relu(self.conv3_64(x))) # 448*448*3 -> 448*448*64 ->224*224*64

        x = self.MaxPool(self.relu(self.conv64_128(x))) # 224*224*64 -> 224*224*128 ->112*112*128

        x = self.relu(self.conv128_256(x)) # 112*112*128 -> 112*112*256
        x = self.MaxPool(self.relu(self.conv256_256(x))) # 112*112*256 -> 112*112*256 -> 56*56*256

        x = self.relu(self.conv256_512(x)) # 56*56*256 -> 56*56*512
        x = self.MaxPool(self.relu(self.conv512_512(x))) # 56*56*512 -> 56*56*512 -> 28*28*512


        x = self.relu(self.conv512_512(x)) # 28*28*512 -> 28*28*512
        x = self.MaxPool(self.relu(self.conv512_512(x))) # 28*28*512 -> 28*28*512 -> 14*14*512
        x = x.view(-1, 512*14*14)

        x = self.relu(self.fc1(x))
        x = self.softmax(self.fc2(x))

        return x

踩坑1:PyTorch的卷积输入要求顺序为channel * height * width,而opencv读取的数据格式为height * width * channels,因此我们需要使用permute交换一下维度。

Train.py 主要是定义训练过程,输出损失值、准确度、保存网络等等,另外,优化器、损失函数的定义也在这里。

DogVSCat_VGG11_Train.py

import torch

from VGGModel import VGG11
vgg11 = VGG11().cuda()

from DogVSCatDataSet import DogCatSet
from torch.utils.data import DataLoader

#Dataloader接受Dataset对象并迭代产生一个batch的数据
train_set = DogCatSet("./train/train")
train_loader = DataLoader(train_set,batch_size=32,shuffle=True,num_workers=4)

#定义损失函数与优化器
import torch.nn as nn
loss_function = nn.CrossEntropyLoss()

import torch.optim as optim
optimizer = optim.SGD(vgg11.parameters(),lr=1e-3)

#迭代产生数据
for epoch in range(2000):

    loss_sum = 0
    Accuracy_sum = 0
    count = 0

    for idx , batch_data in enumerate(train_loader):
        train = batch_data[0].float().cuda() #batch_size * x.size()
        train.grad_requires = True
        truth_label = batch_data[1].long().cuda()

        out = vgg11(train)
        loss = loss_function(out,truth_label)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        loss_sum += loss
        count +=1
        #带有梯度的Tensor如果转换为numpy将会丢失梯度
        out_label = out.argmax(dim=1)
        Result1 = out_label.cpu().numpy()
        Result2 = truth_label.cpu().numpy()

        Accuracy_sum += (Result1==Result2).sum()/len(Result1)

        print("Epoch : {} , batch_id : {} ,Accuracy : {}".format(epoch,idx,(Result1==Result2).sum()/len(Result1)))

    print("Epoch : {} , Loss : {} ,Accuracy : {}".format(epoch,loss_sum/count,Accuracy_sum/count))

torch.save(vgg11,"./vgg11_dogVScat.pkl")

踩坑2:PyTorch的CrossEntropyLoss交叉熵损失函数,接收的truth_label不需要是one-hot编码,只需要给出类别的索引即可。

Test.py 主要是读取保存在本地的网络文件-pkl,用于测试


#读取本地模型
import torch
model = torch.load("./vgg11_dogVScat.pkl").cuda()

#读取测试集
import os
file_list = os.listdir("./test/test")
file_list.sort()

#读取测试文件 推理后写回CSV
import csv
import cv2

# 1. 创建文件对象
f = open('./result.csv','w',encoding='utf-8')
# 2. 基于文件对象构建 csv写入对象
csv_writer = csv.writer(f)
# 3. 构建列表头
csv_writer.writerow(["id","label"])

#构建计数器 用于组织batch
images = []
i = 1
count = 0

for filename in file_list:

    img = cv2.imread(os.path.join("./test/test",filename))
    img = cv2.resize(img, (224, 224), interpolation=cv2.INTER_AREA)
    img_tensor = torch.from_numpy(img)
    img_tensor = img_tensor.expand((1,224,224,3))
    count += 1
    #将数据进行拼接
    if len(images) == 0:
        images = img_tensor
    else:
        images = torch.cat((images,img_tensor))

    if count%32 == 0:#每组织出来32个数据 就进行一次推理
        images = images.float().cuda()
        print(images.shape)
        out = model(images)
        out_label = out.argmax(dim=1)
        Result = out_label.cpu().numpy()

        for row in Result:
            csv_writer.writerow([i, row])
            i = i + 1

        images = images.cpu()
        images = []

if len(images) != 0:
    images = images.float().cuda()
    out = model(images)
    out_label = out.argmax(dim=1)
    Result = out_label.cpu().numpy()

    for row in Result:
        csv_writer.writerow([i, row])
        i = i + 1

# 5. 关闭文件
f.close()

 

你可能感兴趣的:(深度学习实战-PyTorch)