使用pytorch完成kaggle貓狗圖像識別

 kaggle是一個爲開發商和數據科學家提供舉辦機器學習競賽、託管數據庫、編寫和分享代碼的平臺,在這上面有很是多的好項目、好資源可供機器學習、深度學習愛好者學習之用。碰巧最近入門了一門很是的深度學習框架:pytorch(若是你對pytorch不甚瞭解,請點擊這裏),因此今天我和你們一塊兒用pytorch實現一個圖像識別領域的入門項目:貓狗圖像識別。python

深度學習的基礎就是數據,我們先從數據談起。這次使用的貓狗分類圖像一共25000張,貓狗分別有12500張,咱們先來簡單的瞅瞅都是一些什麼圖片。數據庫

 咱們從下載文件裏能夠看到有兩個文件夾:train和test,分別用於訓練和測試。以train爲例,打開文件夾能夠看到很是多的小貓圖片,圖片名字從數組

0.jpg一直編碼到9999.jpg,一共有10000張圖片用於訓練。而test中的小貓只有2500張。仔細看小貓,能夠發現它們姿態不一,有的站着,有的眯着眼網絡

睛,有的甚至和其餘可識別物體好比桶、人混在一塊兒。同時,小貓們的圖片尺寸也不一致,有的是豎放的長方形,有的是橫放的長方形,但咱們最終需架構

要是合理尺寸的正方形。小狗的圖片也相似,在這裏就不重複了。框架

 緊接着咱們瞭解一下特別適用於圖像識別領域的神經網絡:卷積神經網絡。學習過神經網絡的同窗可能或多或少地據說過卷積神經網絡。這是一機器學習

種典型的多層神經網絡,擅長處理圖像特別是大圖像的相關機器學習問題。卷積神經網絡經過一系列的方法,成功地將大數據量的圖像識別問題不斷函數

降維,最終使其可以被訓練。CNN最先由Yann LeCun提出並應用在手寫體識別上。一個典型的CNN網絡架構以下:學習

 

 這是一個典型的CNN架構,由卷基層、池化層、全鏈接層組合而成。其中卷基層與池化層配合,組成多個卷積組,逐層提取特徵,最終完成分類。測試

聽到上述一連串的術語若是你有點蒙了,也別怕,由於這些複雜、抽象的技術都已經在pytorch中一一實現,咱們要作的不過是正確的調用相關函數,

我在粘貼代碼後都會作更詳細、易懂的解釋。

 

 

import os
import shutil
import torch
import collections
from torchvision import transforms,datasets
from __future__ import print_function, division
import os
import torch
import pylab
import pandas as pd
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils
 
# Ignore warnings
import warnings
warnings.filterwarnings("ignore")
 
plt.ion()   # interactive mode

  

一個正常的CNN項目所須要的庫仍是蠻多的。

 

import math
from PIL import Image
 
class Resize(object):
    """Resize the input PIL Image to the given size.
    Args:
        size (sequence or int): Desired output size. If size is a sequence like
            (h, w), output size will be matched to this. If size is an int,
            smaller edge of the image will be matched to this number.
            i.e, if height > width, then image will be rescaled to
            (size * height / width, size)
        interpolation (int, optional): Desired interpolation. Default is
            ``PIL.Image.BILINEAR``
    """
 
    def __init__(self, size, interpolation=Image.BILINEAR):
        # assert isinstance(size, int) or (isinstance(size, collections.Iterable) and len(size) == 2)
        self.size = size
        self.interpolation = interpolation
 
    def __call__(self, img):
        w,h = img.size
        
        min_edge = min(img.size)
        rate = min_edge / self.size
        
        new_w = math.ceil(w / rate)
        new_h = math.ceil(h / rate)
        
        return img.resize((new_w,new_h))

  

這個稱爲Resize的庫用於給圖像進行縮放操做,原本是不須要親自定義的,由於transforms.Resize已經實現這個功能了,可是因爲目前還未知的緣由,
個人庫裏沒有提供這個函數,因此我須要親自實現用來代替transforms.Resize。若是你的torch裏面已經有了這個Resize函數就不用像我這樣了。

data_transform = transforms.Compose([
    Resize(84),
    transforms.CenterCrop(84),
    transforms.ToTensor(),
    transforms.Normalize(mean = [0.5,0.5,0.5],std = [0.5,0.5,0.5])
])
 
train_dataset = datasets.ImageFolder(root = 'train/',transform = data_transform)
train_loader = torch.utils.data.DataLoader(train_dataset,batch_size = 4,shuffle = True,num_workers = 4)
 
test_dataset = datasets.ImageFolder(root = 'test/',transform = data_transform)
test_loader = torch.utils.data.DataLoader(test_dataset,batch_size = 4,shuffle = True,num_workers = 4)

  

transforms是一個提供針對數據(這裏指的是圖像)進行轉化的操做庫,Resize就是上上段代碼提供的那個類,主要用於把一張圖片縮放到某個尺寸,
在這裏咱們把需求暫定爲要把圖像縮放到84 x 84這個級別,這個就是可供調整的參數,你們爲部署好項目之後能夠試着修改這個參數,好比改爲

200 x 200,你就發現你能夠去玩一盤遊戲了~_~。CenterCrop用於從中心裁剪圖片,目標是一個長寬都爲84的正方形,方便後續的計算。ToTenser()

就比較重要了,這個函數的目的就是讀取圖片像素而且轉化爲0-1的數字。Normalize做爲墊底的一步也很關鍵,主要用於把圖片數據集的數值轉化爲

標準差和均值都爲0.5的數據集,這樣數據值就從原來的0到1轉變爲-1到1。

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        
        self.conv1 = nn.Conv2d(3,6,5)
        self.pool = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(6,16,5)
        self.fc1 = nn.Linear(16 * 18 * 18,800)
        self.fc2 = nn.Linear(800,120)
        self.fc3 = nn.Linear(120,2)
        
    def forward(self,x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,16 * 18 * 18)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        
        return x
 
net = Net()

  

 好了,最複雜的一步就是這裏了。在這裏,咱們首先定義了一個Net類,它封裝了因此訓練的步驟,包括卷積、池化、激活以及全鏈接操做。
 __init__函數首先定義了所須要的全部函數,這些函數都會在forward中調用。咱們從conv1提及。conv1實際上就是定義一個卷積層,3,6,5分別是

什麼意思?3表明的是輸入圖像的像素數組的層數,通常來講就是你輸入的圖像的通道數,好比這裏使用的小貓圖像都是彩色圖像,由R、G、B三個通

道組成,因此數值爲3;6表明的是咱們但願進行6次卷積,每一次卷積都能生成不一樣的特徵映射數組,用於提取小貓和小狗的6種特徵。每個特徵映

射結果最終都會被堆疊在一塊兒造成一個圖像輸出,再做爲下一步的輸入;5就是過濾框架的尺寸,表示咱們但願用一個5 * 5的矩陣去和圖像中相同尺寸

的矩陣進行點乘再相加,造成一個值。定義好了卷基層,咱們接着定義池化層。池化層所作的事說來簡單,其實就是由於大圖片生成的像素矩陣實在太

大了,咱們須要用一個合理的方法在降維的同時又不失去物體特徵,因此深度學習學者們想出了一個稱爲池化的技術,說白了就是從左上角開始,每四

個元素(2 * 2)合併成一個元素,用這一個元素去表明四個元素的值,因此圖像體積一會兒降爲原來的四分之一。再往下一行,咱們又一次遇見了一個卷

基層:conv2,和conv1同樣,它的輸入也是一個多層像素數組,輸出也是一個多層像素數組,不一樣的是這一次完成的計算量更大了,咱們看這裏面的參

數分別是6,16,5。之因此爲6是由於conv1的輸出層數爲6,因此這裏輸入的層數就是6;16表明conv2的輸出層數,和conv1同樣,16表明着這一次卷

積操做將會學習小貓小狗的16種映射特徵,特徵越多理論上能學習的效果就越好,你們能夠嘗試一下別的值,看看效果是否真的編變好。conv2使用的

過濾框尺寸和conv1同樣,因此再也不重複。最後三行代碼都是用於定義全鏈接網絡的,接觸過神經網絡的應該就再也不陌生了,主要是須要解釋一下fc1。

以前在學習的時候比較不理解的也是這一行,爲何是16 * 18 * 18呢?16很好理解,由於最後一次卷積生成的圖像矩陣的高度就是16層,那18 * 18是

怎麼來的呢?咱們回過頭去看一行代碼

transforms.CenterCrop(84)

  

在這行代碼裏咱們把訓練圖像裁剪成一個84 * 84的正方形尺寸,因此圖像最先輸入就是一個3 * 84 * 84的數組。通過第一次5 * 5的卷積以後,

咱們能夠得出卷積的結果是一個6 * 80 * 80的矩陣,這裏的80就是由於咱們使用了一個5 * 5的過濾框,當它從左上角第一個元素開始卷積後,

過濾框的中心是從2到78,並非從0到79,因此結果就是一個80 * 80的圖像了。通過一個池化層以後,圖像尺寸的寬和高都分別縮小到原來的

1/2,因此變成40 * 40。緊接着又進行了一次卷積,和上一次同樣,長寬都減掉4,變成36 * 36,而後應用了最後一層的池化,最終尺寸就是

18 * 18。因此第一層全鏈接層的輸入數據的尺寸是16 * 18 * 18。三個全鏈接層所作的事很相似,就是不斷訓練,最後輸出一個二分類數值。

net類的forward函數表示前向計算的整個過程。forward接受一個input,返回一個網絡輸出值,中間的過程就是一個調用init函數中定義的層的過程。

F.relu是一個激活函數,把全部的非零值轉化成零值。這次圖像識別的最後關鍵一步就是真正的循環訓練操做。

import torch.optim as optim
 
cirterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr = 0.0001,momentum = 0.9)
 
for epoch in range(3):
    running_loss = 0.0
    
    for i,data in enumerate(train_loader,0):
        inputs,labels = data
        inputs,labels = Variable(inputs),Variable(labels)
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = cirterion(outputs,labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.data[0]
        
        if i % 2000 == 1999:
            print('[%d %5d] loss: %.3f' % (epoch + 1,i + 1,running_loss / 2000))
            running_loss = 0.0
 
print('finished training!')

  

[1  2000] loss: 0.691
[1  4000] loss: 0.687
[2  2000] loss: 0.671
[2  4000] loss: 0.657
[3  2000] loss: 0.628
[3  4000] loss: 0.626
finished training!

  

 在這裏咱們進行了三次訓練,每次訓練都是批量獲取train_loader中的訓練數據、梯度清零、計算輸出值、計算偏差、反向傳播並修正模型。咱們以每

2000次計算的平均偏差做爲觀察值。能夠看到每次訓練,偏差值都在不斷變小,逐漸學習如何分類圖像。代碼相對性易懂,這裏就再也不贅述了。

 

correct = 0
total = 0
 
for data in test_loader:
    images,labels = data
    outputs = net(Variable(images))
    _,predicted = torch.max(outputs.data,1)
    total += labels.size(0)
    correct += (predicted == labels).sum()
 
print('Accuracy of the network on the 5000 test images: %d %%' % (100 * correct / total))

  

  終於來到模型準確度驗證了,這也是開篇提到的test文件夾的用途之所在。程序到這一步時,net是一個已經訓練好的神經網絡了。傳入一個
images矩陣,它會輸出相應的分類值,咱們拿到這個分類值與真實值作一個比較計算,就能夠得到準確率。在個人計算機上當前準確率是66%,

在你的機器上可能值有所不一樣但不會相差太大。

最後咱們作一個小總結。在pytorch中實現CNN其實並不複雜,理論性的底層都已經完成封裝,咱們只須要調用正確的函數便可。當前模型中的

各個參數都沒有達到相對完美的狀態,有興趣的小夥伴能夠多調整參數跑幾回,訓練結果不出意外會愈來愈好。另外,因爲在一篇文章中既要

闡述CNN,又要貼項目代碼會顯得沒有重點,我就沒有兩件事同時作,由於網上已經有不少很好的解釋CNN的文章了,若是看了代碼依然是滿頭

霧水的小夥伴能夠先去搜關於CNN的文章,再回過頭來看項目代碼應該會更加清晰。第一次寫關於本身的神經網絡方面的文章,若有寫得很差的

 

附另一個:

https://blog.csdn.net/weixin_41278720/article/details/80546535
相關文章
相關標籤/搜索