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