kaggle是一個(gè)為開(kāi)發(fā)商和數(shù)據(jù)科學(xué)家提供舉辦機(jī)器學(xué)習(xí)競(jìng)賽、托管數(shù)據(jù)庫(kù)、編寫(xiě)和分享代碼的平臺(tái),在這上面有非常多的好項(xiàng)目、好資源可供機(jī)器學(xué)習(xí)、深度學(xué)習(xí)愛(ài)好者學(xué)習(xí)之用。
碰巧最近入門(mén)了一門(mén)非常的深度學(xué)習(xí)框架:pytorch,所以今天我和大家一起用pytorch實(shí)現(xiàn)一個(gè)圖像識(shí)別領(lǐng)域的入門(mén)項(xiàng)目:貓狗圖像識(shí)別。
深度學(xué)習(xí)的基礎(chǔ)就是數(shù)據(jù),咱們先從數(shù)據(jù)談起。此次使用的貓狗分類圖像一共25000張,貓狗分別有12500張,我們先來(lái)簡(jiǎn)單的瞅瞅都是一些什么圖片。
我們從下載文件里可以看到有兩個(gè)文件夾:train和test,分別用于訓(xùn)練和測(cè)試。以train為例,打開(kāi)文件夾可以看到非常多的小貓圖片,圖片名字從0.jpg一直編碼到9999.jpg,一共有10000張圖片用于訓(xùn)練。
而test中的小貓只有2500張。仔細(xì)看小貓,可以發(fā)現(xiàn)它們姿態(tài)不一,有的站著,有的瞇著眼睛,有的甚至和其他可識(shí)別物體比如桶、人混在一起。
同時(shí),小貓們的圖片尺寸也不一致,有的是豎放的長(zhǎng)方形,有的是橫放的長(zhǎng)方形,但我們最終需要是合理尺寸的正方形。小狗的圖片也類似,在這里就不重復(fù)了。
緊接著我們了解一下特別適用于圖像識(shí)別領(lǐng)域的神經(jīng)網(wǎng)絡(luò):卷積神經(jīng)網(wǎng)絡(luò)。學(xué)習(xí)過(guò)神經(jīng)網(wǎng)絡(luò)的同學(xué)可能或多或少地聽(tīng)說(shuō)過(guò)卷積神經(jīng)網(wǎng)絡(luò)。這是一種典型的多層神經(jīng)網(wǎng)絡(luò),擅長(zhǎng)處理圖像特別是大圖像的相關(guān)機(jī)器學(xué)習(xí)問(wèn)題。
卷積神經(jīng)網(wǎng)絡(luò)通過(guò)一系列的方法,成功地將大數(shù)據(jù)量的圖像識(shí)別問(wèn)題不斷降維,最終使其能夠被訓(xùn)練。CNN最早由Yann LeCun提出并應(yīng)用在手寫(xiě)體識(shí)別上。
一個(gè)典型的CNN網(wǎng)絡(luò)架構(gòu)如下:
這是一個(gè)典型的CNN架構(gòu),由卷基層、池化層、全連接層組合而成。其中卷基層與池化層配合,組成多個(gè)卷積組,逐層提取特征,最終完成分類。
聽(tīng)到上述一連串的術(shù)語(yǔ)如果你有點(diǎn)蒙了,也別怕,因?yàn)檫@些復(fù)雜、抽象的技術(shù)都已經(jīng)在pytorch中一一實(shí)現(xiàn),我們要做的不過(guò)是正確的調(diào)用相關(guān)函數(shù),
我在粘貼代碼后都會(huì)做更詳細(xì)、易懂的解釋。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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 |
一個(gè)正常的CNN項(xiàng)目所需要的庫(kù)還是蠻多的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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)) |
這個(gè)稱為Resize的庫(kù)用于給圖像進(jìn)行縮放操作,本來(lái)是不需要親自定義的,因?yàn)閠ransforms.Resize已經(jīng)實(shí)現(xiàn)這個(gè)功能了,但是由于目前還未知的原因,我的庫(kù)里沒(méi)有提供這個(gè)函數(shù),所以我需要親自實(shí)現(xiàn)用來(lái)代替transforms.Resize。
如果你的torch里面已經(jīng)有了這個(gè)Resize函數(shù)就不用像我這樣了。
1
2
3
4
5
6
7
8
9
10
11
12
|
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是一個(gè)提供針對(duì)數(shù)據(jù)(這里指的是圖像)進(jìn)行轉(zhuǎn)化的操作庫(kù),Resize就是上上段代碼提供的那個(gè)類,主要用于把一張圖片縮放到某個(gè)尺寸,在這里我們把需求暫定為要把圖像縮放到84 x 84這個(gè)級(jí)別,這個(gè)就是可供調(diào)整的參數(shù),大家為部署好項(xiàng)目以后可以試著修改這個(gè)參數(shù),比如改成200 x 200,你就發(fā)現(xiàn)你可以去玩一盤(pán)游戲了~_~。
CenterCrop用于從中心裁剪圖片,目標(biāo)是一個(gè)長(zhǎng)寬都為84的正方形,方便后續(xù)的計(jì)算。
ToTenser()就比較重要了,這個(gè)函數(shù)的目的就是讀取圖片像素并且轉(zhuǎn)化為0-1的數(shù)字。
Normalize作為墊底的一步也很關(guān)鍵,主要用于把圖片數(shù)據(jù)集的數(shù)值轉(zhuǎn)化為標(biāo)準(zhǔn)差和均值都為0.5的數(shù)據(jù)集,這樣數(shù)據(jù)值就從原來(lái)的0到1轉(zhuǎn)變?yōu)?1到1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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() |
好了,最復(fù)雜的一步就是這里了。在這里,我們首先定義了一個(gè)Net類,它封裝了所以訓(xùn)練的步驟,包括卷積、池化、激活以及全連接操作。
__init__函數(shù)首先定義了所需要的所有函數(shù),這些函數(shù)都會(huì)在forward中調(diào)用。我們從conv1說(shuō)起。conv1實(shí)際上就是定義一個(gè)卷積層,3,6,5分別是什么意思?
3代表的是輸入圖像的像素?cái)?shù)組的層數(shù),一般來(lái)說(shuō)就是你輸入的圖像的通道數(shù),比如這里使用的小貓圖像都是彩色圖像,由R、G、B三個(gè)通道組成,所以數(shù)值為3;6代表的是我們希望進(jìn)行6次卷積,每一次卷積都能生成不同的特征映射數(shù)組,用于提取小貓和小狗的6種特征。
每一個(gè)特征映射結(jié)果最終都會(huì)被堆疊在一起形成一個(gè)圖像輸出,再作為下一步的輸入;5就是過(guò)濾框架的尺寸,表示我們希望用一個(gè)5 * 5的矩陣去和圖像中相同尺寸的矩陣進(jìn)行點(diǎn)乘再相加,形成一個(gè)值。
定義好了卷基層,我們接著定義池化層。池化層所做的事說(shuō)來(lái)簡(jiǎn)單,其實(shí)就是因?yàn)榇髨D片生成的像素矩陣實(shí)在太大了,我們需要用一個(gè)合理的方法在降維的同時(shí)又不失去物體特征,所以深度學(xué)習(xí)學(xué)者們想出了一個(gè)稱為池化的技術(shù),說(shuō)白了就是從左上角開(kāi)始,每四個(gè)元素(2 * 2)合并成一個(gè)元素,用這一個(gè)元素去代表四個(gè)元素的值,所以圖像體積一下子降為原來(lái)的四分之一。
再往下一行,我們又一次碰見(jiàn)了一個(gè)卷基層:conv2,和conv1一樣,它的輸入也是一個(gè)多層像素?cái)?shù)組,輸出也是一個(gè)多層像素?cái)?shù)組,不同的是這一次完成的計(jì)算量更大了,我們看這里面的參數(shù)分別是6,16,5。
之所以為6是因?yàn)閏onv1的輸出層數(shù)為6,所以這里輸入的層數(shù)就是6;16代表conv2的輸出層數(shù),和conv1一樣,16代表著這一次卷積操作將會(huì)學(xué)習(xí)小貓小狗的16種映射特征,特征越多理論上能學(xué)習(xí)的效果就越好,大家可以嘗試一下別的值,看看效果是否真的編變好。
conv2使用的過(guò)濾框尺寸和conv1一樣,所以不再重復(fù)。最后三行代碼都是用于定義全連接網(wǎng)絡(luò)的,接觸過(guò)神經(jīng)網(wǎng)絡(luò)的應(yīng)該就不再陌生了,主要是需要解釋一下fc1。
之前在學(xué)習(xí)的時(shí)候比較不理解的也是這一行,為什么是16 * 18 * 18呢?16很好理解,因?yàn)樽詈笠淮尉矸e生成的圖像矩陣的高度就是16層,那18 * 18是怎么來(lái)的呢?我們回過(guò)頭去看一行代碼
1
|
transforms.CenterCrop( 84 ) |
在這行代碼里我們把訓(xùn)練圖像裁剪成一個(gè)84 * 84的正方形尺寸,所以圖像最早輸入就是一個(gè)3 * 84 * 84的數(shù)組。經(jīng)過(guò)第一次5 * 5的卷積之后,我們可以得出卷積的結(jié)果是一個(gè)6 * 80 * 80的矩陣,這里的80就是因?yàn)槲覀兪褂昧艘粋€(gè)5 * 5的過(guò)濾框,當(dāng)它從左上角第一個(gè)元素開(kāi)始卷積后,過(guò)濾框的中心是從2到78,并不是從0到79,所以結(jié)果就是一個(gè)80 * 80的圖像了。
經(jīng)過(guò)一個(gè)池化層之后,圖像尺寸的寬和高都分別縮小到原來(lái)的1/2,所以變成40 * 40。
緊接著又進(jìn)行了一次卷積,和上一次一樣,長(zhǎng)寬都減掉4,變成36 * 36,然后應(yīng)用了最后一層的池化,最終尺寸就是18 * 18。
所以第一層全連接層的輸入數(shù)據(jù)的尺寸是16 * 18 * 18。三個(gè)全連接層所做的事很類似,就是不斷訓(xùn)練,最后輸出一個(gè)二分類數(shù)值。
net類的forward函數(shù)表示前向計(jì)算的整個(gè)過(guò)程。forward接受一個(gè)input,返回一個(gè)網(wǎng)絡(luò)輸出值,中間的過(guò)程就是一個(gè)調(diào)用init函數(shù)中定義的層的過(guò)程。
F.relu是一個(gè)激活函數(shù),把所有的非零值轉(zhuǎn)化成零值。此次圖像識(shí)別的最后關(guān)鍵一步就是真正的循環(huán)訓(xùn)練操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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
2
3
4
5
6
7
|
[ 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! |
在這里我們進(jìn)行了三次訓(xùn)練,每次訓(xùn)練都是批量獲取train_loader中的訓(xùn)練數(shù)據(jù)、梯度清零、計(jì)算輸出值、計(jì)算誤差、反向傳播并修正模型。我們以每2000次計(jì)算的平均誤差作為觀察值。可以看到每次訓(xùn)練,誤差值都在不斷變小,逐漸學(xué)習(xí)如何分類圖像。代碼相對(duì)性易懂,這里就不再贅述了。
1
2
3
4
5
6
7
8
9
10
11
|
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)) |
終于來(lái)到模型準(zhǔn)確度驗(yàn)證了,這也是開(kāi)篇提到的test文件夾的用途之所在。程序到這一步時(shí),net是一個(gè)已經(jīng)訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)了。傳入一個(gè)images矩陣,它會(huì)輸出相應(yīng)的分類值,我們拿到這個(gè)分類值與真實(shí)值做一個(gè)比較計(jì)算,就可以獲得準(zhǔn)確率。在我的計(jì)算機(jī)上當(dāng)前準(zhǔn)確率是66%,在你的機(jī)器上可能值有所不同但不會(huì)相差太大。
最后我們做一個(gè)小總結(jié)。在pytorch中實(shí)現(xiàn)CNN其實(shí)并不復(fù)雜,理論性的底層都已經(jīng)完成封裝,我們只需要調(diào)用正確的函數(shù)即可。當(dāng)前模型中的各個(gè)參數(shù)都沒(méi)有達(dá)到相對(duì)完美的狀態(tài),有興趣的小伙伴可以多調(diào)整參數(shù)跑幾次,訓(xùn)練結(jié)果不出意外會(huì)越來(lái)越好。
另外,由于在一篇文章中既要闡述CNN,又要貼項(xiàng)目代碼會(huì)顯得沒(méi)有重點(diǎn),我就沒(méi)有兩件事同時(shí)做,因?yàn)榫W(wǎng)上已經(jīng)有很多很好的解釋CNN的文章了,如果看了代碼依然是滿頭霧水的小伙伴可以先去搜關(guān)于CNN的文章,再回過(guò)頭來(lái)看項(xiàng)目代碼應(yīng)該會(huì)更加清晰。
第一次寫(xiě)關(guān)于自己的神經(jīng)網(wǎng)絡(luò)方面的文章,如有寫(xiě)得不好的地方請(qǐng)大家多多見(jiàn)諒。
以上這篇使用pytorch完成kaggle貓狗圖像識(shí)別方式就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/xcszjjh1991/article/details/79256576