0.前言
同为计算机视觉任务,最经典的MNIST手写数字识别在文章Pytorch+CNN+MNIST实战已经讲得非常详细了,所以对于代码,长篇大论都是模板照套MNIST,故而只需要阐明与MNIST区别,升华境界。
1.猫狗分类数据集
1.1数据集下载(可选部分)
这个是原版的最全数据集,即共25000张图片,没有划分训练集和测试集。我做完一遍之后,划分出了一个精简版的,2000张图片,而且划分好了测试集和训练集。我比较建议用我的,一开始我用的是最全的那个数据集,但是坑:
- 那个里面的图片有3张是打不开的,导致报错,后来删除自己补了3张。
- 你是在用普通电脑训练还是大型机器?如果用的是普通电脑,我训练2000张图片的时候,10趟我测了需要3~4分钟。那么25000张图片我推测需要训练10趟(EPOCH)有可能需要50-60分钟。而且10趟并不一定保证训练得好,你是否确定你得机器吃得消。当然了,大型机器比如服务器忽略,应该比较快。
如果选择2000张的,下载地址见:https://download.csdn.net/download/qq_43391414/20023207(我设置了不需要积分),下载完成后,解压,然后直接进入章节2.2。
要全部数据集的下载地址见:https://www.microsoft.com/en-us/download/details.aspx?id=54765。 下载好了解压之后,其文件夹的样子为(原封不动): 点开cat如下: 点开dog也是类似,这里不做展示了。
1.2数据集分析
先贴几张图片
我们发现:
- 猫狗分类是彩色图片,所以是3个channel,MNIST是1个。
- 猫狗分类的图片大小不一,有长的,方的。而MNIST非常规整,都是28*28。这就导致了猫狗分类的输入大小是各不相同的,但是CNN的输入是要求固定大小的。这是一个麻烦,后面会给出解决办法。
2.猫狗分类数据集预处理
2.1训练集和测试集划分
建立好如下的文件夹 检查一下你下载的图片是否有打不开的图片,我有3个(不知道什么原因,估计是见鬼了。),其中两个是猫文件夹的666.jpg,和狗文件夹下的11702.jpg。
必须解决,否则后面读取错误。解决办法见https://blog.csdn.net/qq_43391414/article/details/118464005。
按照8:2划分训练集和测试集。
import os,shutil
def mymovefile(srcfile,dstfile):
if not os.path.isfile(srcfile):
print("src not exist!")
else:
fpath,fname=os.path.split(dstfile)
if not os.path.exists(fpath):
os.makedirs(fpath)
shutil.move(srcfile,dstfile)
test_rate=0.2
img_num=12500
test_num=int(img_num*test_rate)
import random
test_index = random.sample(range(0, img_num), test_num)
file_path=r"D:\Download\kagglecatsanddogs_3367a\PetImages"
tr="train"
te="test"
cat="Cat"
dog="Dog"
for i in range(len(test_index)):
srcfile=os.path.join(file_path,tr,cat,str(test_index[i])+".jpg")
dstfile=os.path.join(file_path,te,cat,str(test_index[i])+".jpg")
mymovefile(srcfile,dstfile)
srcfile=os.path.join(file_path,tr,dog,str(test_index[i])+".jpg")
dstfile=os.path.join(file_path,te,dog,str(test_index[i])+".jpg")
mymovefile(srcfile,dstfile)
运行以上代码,发现我们的test文件夹下已经有了2*test_num 个测试文件。 帮助:
- 上面就是一个移动文件的过程,然后随机从train中选取一些图片到test中即可,完成划分。划分比例8:2你可以自己改。
2.2训练集和测试集读取
如果是从2000张图片过来的,执行下面代码(全数据集的忽略):
tr="train"
te="test"
file_path=r"D:\lbq\lang\pythoncode\data\catsdogs"
我们还发现一个区别: 2. MNIST被很多官方的库收录,并直接提供下载和预处理(torchvision.datasets.MNIST ),所以相对简单,而猫狗分类不具备这个特点,需要我们单独对数据集进行预处理。
然而,其实猫狗分类的数据集预处理同样很简单,也有一个函数(torchvision.datasets.ImageFolder )可以直接搞定。
import numpy as np
from torchvision import transforms,datasets
transforms = transforms.Compose(
[
transforms.RandomResizedCrop(150),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
]
)
train_data = datasets.ImageFolder(os.path.join(file_path,tr), transforms)
test_data=datasets.ImageFolder(os.path.join(file_path,te), transforms)
帮助:
- 其中那个
RandomResizedCrop(150) 是用来把图片的每一个channel大小都变成(150,150) - mean=[0.485, 0.456, 0.406],有3个数的原因是猫狗分类是彩色图片,所以有3个channel,所以每一个channel上都有一个平均值。你可以翻一翻MNIST的transforms定义,其mean只有一个,因为只有一个channel。
- 同理,std也是。
- 上面的data已经把猫狗的图片都囊括了,而且标签已经自动变成了0和1。这就是
ImageFolder 的威力。
一些操作:
train_data的数据类型是DataSet,这个已经强调多遍,因为只有DataSet才可以被后面的DataLoader操作。
训练数据一共有20000张,10000张狗,10000张猫;测试数据有5000张,2500张狗,2500张猫。 第一张[0]训练图片的具体形况,前面是图片[0],后面是标签[1]。标签0代表猫,1代表狗。这是由于在file_path/train 的文件夹下Cat在Dog的前面,所以前者是0,后者是1。 每一张图片都是(3,150,150),3表示3个channel,猫狗分类的图片是彩色的。
3.剩余代码
网络架构:
图1 卷积层
|
图2 全连接层
|
剩余代码:
from torch.utils import data
batch_size=32
train_loader = data.DataLoader(train_data,batch_size=batch_size,shuffle=True,pin_memory=True)
test_loader = data.DataLoader(test_data,batch_size=batch_size)
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
class CNN(nn.Module):
def __init__(self):
super(CNN,self).__init__()
self.conv1=nn.Conv2d(3,20,5,5)
self.conv2=nn.Conv2d(20,50,4,1)
self.fc1=nn.Linear(50*6*6,200)
self.fc2=nn.Linear(200,2)
def forward(self,x):
x=F.relu(self.conv1(x))
x=F.max_pool2d(x,2,2)
x=F.relu(self.conv2(x))
x=F.max_pool2d(x,2,2)
x=x.view(-1,50*6*6)
x=F.relu(self.fc1(x))
x=self.fc2(x)
return F.log_softmax(x,dim=1)
lr=1e-4
device=torch.device("cuda" if torch.cuda.is_available() else "cpu" )
model=CNN().to(device)
optimizer=optim.Adam(model.parameters(),lr=lr)
def train(model,device,train_loader,optimizer,epoch,losses):
model.train()
for idx,(t_data,t_target) in enumerate(train_loader):
t_data,t_target=t_data.to(device),t_target.to(device)
pred=model(t_data)
loss=F.nll_loss(pred,t_target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if idx%10==0:
print("epoch:{},iteration:{},loss:{}".format(epoch,idx,loss.item()))
losses.append(loss.item())
def test(model,device,test_loader):
model.eval()
correct=0
with torch.no_grad():
for idx,(t_data,t_target) in enumerate(test_loader):
t_data,t_target=t_data.to(device),t_target.to(device)
pred=model(t_data)
pred_class=pred.argmax(dim=1)
correct+=pred_class.eq(t_target.view_as(pred_class)).sum().item()
acc=correct/len(test_data)
print("accuracy:{},average_loss:{}".format(acc,average_loss))
num_epochs=10
losses=[]
from time import *
begin_time=time()
for epoch in range(num_epochs):
train(model,device,train_loader,optimizer,epoch,losses)
end_time=time()
训练过程(截取部分):
test(model,device,test_loader)
测试结果:63.5%(最好的时候是,可能会有波动,我也只做了几次)。至于怎么调参,仿照开头那篇文章。
4.总结
正如开头说的那样,这一篇只是一个续集,和MNIST手写数字识别没有很大的不同。
- 我们修改了数据预处理的方法,因为官方数据集并没有猫狗分类,所以需要自己处理,划分训练和测试集(不过我的2000张已经划分好了)。
- 我们需要resize,因为有些图片大,有些图片小,不像MNIST那么规整。
- 我们需要修改网络架构,因为这里是彩色的,有3个channel,而且由于我们的训练资源有限,我们增大了卷积的步长,就是那个
self.conv1=nn.Conv2d(3,20,5,5) 的最后一个参数5,即卷一次,移动5个格子,不写默认是1格。这样做,可以快速把图片缩小,原来是150*150的图片,这样可以变成30*30.
|