如何使用Pytorch完成圖像分類任務(wù)詳解
概述:
本文將通過(guò)組織自己的訓(xùn)練數(shù)據(jù),使用Pytorch深度學(xué)習(xí)框架來(lái)訓(xùn)練自己的模型,最終實(shí)現(xiàn)自己的圖像分類!本篇文章以識(shí)別陽(yáng)臺(tái)為例子,進(jìn)行講述。
一. 數(shù)據(jù)準(zhǔn)備
深度學(xué)習(xí)的基礎(chǔ)就是數(shù)據(jù),完成圖像分類,當(dāng)然數(shù)據(jù)也必不可少。先使用爬蟲爬取陽(yáng)臺(tái)圖片1200張以及非陽(yáng)臺(tái)圖片1200張,圖片的名字從0.jpg一直編到2400.jpg,把爬取的圖片放置在同一個(gè)文件夾中命名為image(如下圖1所示)。
圖1
針對(duì)百度圖片的爬蟲代碼也放上,方便大家使用,代碼可以爬取任意自定義的圖片:
import requests import os import urllib class Spider_baidu_image(): def __init__(self): self.url = 'http://image.baidu.com/search/acjson?' self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.\ 3497.81 Safari/537.36'} self.headers_image = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.\ 3497.81 Safari/537.36', 'Referer': 'http://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=result&fr=&sf=1&fmq=1557124645631_R&pv=&ic=&nc=1&z=&hd=1&latest=0©right=0&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&sid=&word=%E8%83%A1%E6%AD%8C'} self.keyword = input("請(qǐng)輸入搜索圖片關(guān)鍵字:") self.paginator = int(input("請(qǐng)輸入搜索頁(yè)數(shù),每頁(yè)30張圖片:")) def get_param(self): """ 獲取url請(qǐng)求的參數(shù),存入列表并返回 :return: """ keyword = urllib.parse.quote(self.keyword) params = [] for i in range(1, self.paginator + 1): params.append( 'tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord={}&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=&hd=1&latest=0©right=0&word={}&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&force=&cg=star&pn={}&rn=30&gsm=78&1557125391211='.format( keyword, keyword, 30 * i)) return params def get_urls(self, params): """ 由url參數(shù)返回各個(gè)url拼接后的響應(yīng),存入列表并返回 :return: """ urls = [] for i in params: urls.append(self.url + i) return urls def get_image_url(self, urls): image_url = [] for url in urls: json_data = requests.get(url, headers=self.headers).json() json_data = json_data.get('data') for i in json_data: if i: image_url.append(i.get('thumbURL')) return image_url def get_image(self, image_url): """ 根據(jù)圖片url,在本地目錄下新建一個(gè)以搜索關(guān)鍵字命名的文件夾,然后將每一個(gè)圖片存入。 :param image_url: :return: """ cwd = os.getcwd() file_name = os.path.join(cwd, self.keyword) if not os.path.exists(self.keyword): os.mkdir(file_name) for index, url in enumerate(image_url, start=1): with open(file_name + '\\{}.jpg'.format(index), 'wb') as f: f.write(requests.get(url, headers=self.headers_image).content) if index != 0 and index % 30 == 0: print('{}第{}頁(yè)下載完成'.format(self.keyword, index / 30)) def __call__(self, *args, **kwargs): params = self.get_param() urls = self.get_urls(params) image_url = self.get_image_url(urls) self.get_image(image_url) if __name__ == '__main__': spider = Spider_baidu_image() spider()
每個(gè)圖片要加上對(duì)應(yīng)的標(biāo)簽,那么在txt文檔當(dāng)中,選取圖片的名稱,在其后加上標(biāo)簽。如果是陽(yáng)臺(tái),則標(biāo)簽為1,如果不是陽(yáng)臺(tái),則標(biāo)簽為0。在2400張圖片中,分成兩個(gè)txt文檔為訓(xùn)練集和驗(yàn)證集“train.txt”和“val.txt”(如下圖2,3所示)
圖2
圖3
通過(guò)觀察自己爬取的圖片,可以發(fā)現(xiàn)陽(yáng)臺(tái)各式各樣,有的半開(kāi)放,有的是封閉式的,有的甚至和其他可識(shí)別物體花,草混在一起。同時(shí),圖片尺寸也不一致,有的是豎放的長(zhǎng)方形,有的是橫放的長(zhǎng)方形,但我們最終需要是合理尺寸的正方形。所以我們使用Resize的庫(kù)用于給圖像進(jìn)行縮放操作,我這里把圖片縮放到84*84的級(jí)別。除縮放操作以外還需對(duì)數(shù)據(jù)進(jìn)行預(yù)處理:
torchvision.transforms是pytorch中的圖像預(yù)處理包
一般用Compose把多個(gè)步驟整合到一起:
比如說(shuō)
transforms.Compose([ transforms.CenterCrop(84), transforms.ToTensor(), ])
這樣就把兩個(gè)步驟整合到一起
CenterCrop用于從中心裁剪圖片,目標(biāo)是一個(gè)長(zhǎng)寬都為84的正方形,方便后續(xù)的計(jì)算。除CenterCrop外補(bǔ)充一個(gè)RandomCrop是在一個(gè)隨機(jī)的位置進(jìn)行裁剪。
ToTenser()這個(gè)函數(shù)的目的就是讀取圖片像素并且轉(zhuǎn)化為0-1的數(shù)字(進(jìn)行歸一化操作)。
代碼如下:
data_transforms = { 'train': transforms.Compose([ transforms.Resize(84), transforms.CenterCrop(84), # 轉(zhuǎn)換成tensor向量 transforms.ToTensor(), # 對(duì)圖像進(jìn)行歸一化操作 # [0.485, 0.456, 0.406],RGB通道的均值與標(biāo)準(zhǔn)差 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), 'val': transforms.Compose([ transforms.Resize(84), transforms.CenterCrop(84), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), }
解決對(duì)圖像的處理過(guò)后,想要開(kāi)始訓(xùn)練網(wǎng)絡(luò)模型,首先要解決的就是圖像數(shù)據(jù)的讀入,Pytorch使用DataLoader來(lái)實(shí)現(xiàn)圖像數(shù)據(jù)讀入,代碼如下:
class my_Data_Set(nn.Module): def __init__(self, txt, transform=None, target_transform=None, loader=None): super(my_Data_Set, self).__init__() # 打開(kāi)存儲(chǔ)圖像名與標(biāo)簽的txt文件 fp = open(txt, 'r') images = [] labels = [] # 將圖像名和圖像標(biāo)簽對(duì)應(yīng)存儲(chǔ)起來(lái) for line in fp: line.strip('\n') line.rstrip() information = line.split() images.append(information[0]) labels.append(int(information[1])) self.images = images self.labels = labels self.transform = transform self.target_transform = target_transform self.loader = loader # 重寫這個(gè)函數(shù)用來(lái)進(jìn)行圖像數(shù)據(jù)的讀取 def __getitem__(self, item): # 獲取圖像名和標(biāo)簽 imageName = self.images[item] label = self.labels[item] # 讀入圖像信息 image = self.loader(imageName) # 處理圖像數(shù)據(jù) if self.transform is not None: image = self.transform(image) return image, label # 重寫這個(gè)函數(shù),來(lái)看數(shù)據(jù)集中含有多少數(shù)據(jù) def __len__(self): return len(self.images) # 生成Pytorch所需的DataLoader數(shù)據(jù)輸入格式 train_dataset = my_Data_Set('train.txt', transform=data_transforms['train'], loader=Load_Image_Information) test_dataset = my_Data_Set('val.txt', transform=data_transforms['val'], loader=Load_Image_Information) train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=10, shuffle=True)
可驗(yàn)證是否生成了DataLoader格式數(shù)據(jù):
# 驗(yàn)證是否生成DataLoader格式數(shù)據(jù) for data in train_loader: inputs, labels = data print(inputs) print(labels) for data in test_loader: inputs, labels = data print(inputs) print(labels)
二.定義一個(gè)卷積神經(jīng)網(wǎng)絡(luò)
卷積神經(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)練。卷積神經(jīng)網(wǎng)絡(luò)(CNN)最早由Yann LeCun提出并應(yīng)用在手寫體識(shí)別上。
一個(gè)典型的CNN網(wǎng)絡(luò)架構(gòu)如下圖4:
圖4
首先導(dǎo)入Python需要的庫(kù):
import torch from torch.utils.data import DataLoader from torchvision import transforms, datasets import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.autograd import Variable from torch.utils.data import Dataset import numpy as np import os from PIL import Image import warnings import matplotlib.pyplot as plt warnings.filterwarnings("ignore") plt.ion()
定義一個(gè)卷積神經(jīng)網(wǎng)絡(luò):
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, 10) 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()
我們首先定義了一個(gè)Net類,它封裝了所以訓(xùn)練的步驟,包括卷積、池化、激活以及全連接操作。
__init__函數(shù)首先定義了所需要的所有函數(shù),這些函數(shù)都會(huì)在forward中調(diào)用。從conv1說(shuō)起,conv1實(shí)際上就是定義一個(gè)卷積層,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í)又不失去物體特征,所以使用池化的技術(shù),每四個(gè)元素合并成一個(gè)元素,用這一個(gè)元素去代表四個(gè)元素的值,所以圖像體積會(huì)降為原來(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ù)。
對(duì)于fc1,16很好理解,因?yàn)樽詈笠淮尉矸e生成的圖像矩陣的高度就是16層,前面我們把訓(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)練操作。
#訓(xùn)練 cirterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.5) for epoch in range(50): running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data inputs, labels = Variable(inputs), Variable(labels) optimizer.zero_grad() # 優(yōu)化器清零 outputs = net(inputs) loss = cirterion(outputs, labels) loss.backward() optimizer.step() #優(yōu)化 running_loss += loss.item() if i % 200 == 199: print('[%d %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) running_loss = 0.0 print('finished training!')
在這里我們進(jìn)行了50次訓(xùn)練,每次訓(xùn)練都是批量獲取train_loader中的訓(xùn)練數(shù)據(jù)、梯度清零、計(jì)算輸出值、計(jì)算誤差、反向傳播并修正模型。我們以每200次計(jì)算的平均誤差作為觀察值。
下面進(jìn)行測(cè)試環(huán)節(jié):
#測(cè)試 correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images, labels = data outputs = net(Variable(images)) _, predicted = torch.max(outputs.data, dim=1) total += labels.size(0) correct += (predicted == labels).sum() print('Accuracy of the network on the 400 test images: %d %%' % (100 * correct / total))
最后會(huì)得到一個(gè)識(shí)別的準(zhǔn)確率。
三.完整代碼如下:
import torch from torch.utils.data import DataLoader from torchvision import transforms, datasets import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.autograd import Variable from torch.utils.data import Dataset import numpy as np import os from PIL import Image import warnings import matplotlib.pyplot as plt warnings.filterwarnings("ignore") plt.ion() data_transforms = { 'train': transforms.Compose([ transforms.Resize(84), transforms.CenterCrop(84), # 轉(zhuǎn)換成tensor向量 transforms.ToTensor(), # 對(duì)圖像進(jìn)行歸一化操作 # [0.485, 0.456, 0.406],RGB通道的均值與標(biāo)準(zhǔn)差 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), 'val': transforms.Compose([ transforms.Resize(84), transforms.CenterCrop(84), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } def Load_Image_Information(path): # 圖像存儲(chǔ)路徑 image_Root_Dir= r'C:/Users/wbl/Desktop/pythonProject1/image/' # 獲取圖像的路徑 iamge_Dir = os.path.join(image_Root_Dir, path) # 以RGB格式打開(kāi)圖像 # Pytorch DataLoader就是使用PIL所讀取的圖像格式 return Image.open(iamge_Dir).convert('RGB') class my_Data_Set(nn.Module): def __init__(self, txt, transform=None, target_transform=None, loader=None): super(my_Data_Set, self).__init__() # 打開(kāi)存儲(chǔ)圖像名與標(biāo)簽的txt文件 fp = open(txt, 'r') images = [] labels = [] # 將圖像名和圖像標(biāo)簽對(duì)應(yīng)存儲(chǔ)起來(lái) for line in fp: line.strip('\n') line.rstrip() information = line.split() images.append(information[0]) labels.append(int(information[1])) self.images = images self.labels = labels self.transform = transform self.target_transform = target_transform self.loader = loader # 重寫這個(gè)函數(shù)用來(lái)進(jìn)行圖像數(shù)據(jù)的讀取 def __getitem__(self, item): # 獲取圖像名和標(biāo)簽 imageName = self.images[item] label = self.labels[item] # 讀入圖像信息 image = self.loader(imageName) # 處理圖像數(shù)據(jù) if self.transform is not None: image = self.transform(image) return image, label # 重寫這個(gè)函數(shù),來(lái)看數(shù)據(jù)集中含有多少數(shù)據(jù) def __len__(self): return len(self.images) # 生成Pytorch所需的DataLoader數(shù)據(jù)輸入格式 train_dataset = my_Data_Set('train.txt', transform=data_transforms['train'], loader=Load_Image_Information) test_dataset = my_Data_Set('val.txt', transform=data_transforms['val'], loader=Load_Image_Information) train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=10, shuffle=True) ''' # 驗(yàn)證是否生成DataLoader格式數(shù)據(jù) for data in train_loader: inputs, labels = data print(inputs) print(labels) for data in test_loader: inputs, labels = data print(inputs) print(labels) ''' 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, 10) 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() #訓(xùn)練 cirterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.5) for epoch in range(50): running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data inputs, labels = Variable(inputs), Variable(labels) optimizer.zero_grad() # 優(yōu)化器清零 outputs = net(inputs) loss = cirterion(outputs, labels) loss.backward() optimizer.step() #優(yōu)化 running_loss += loss.item() if i % 200 == 199: print('[%d %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) running_loss = 0.0 print('finished training!') #測(cè)試 correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images, labels = data outputs = net(Variable(images)) _, predicted = torch.max(outputs.data, dim=1) total += labels.size(0) correct += (predicted == labels).sum() print('Accuracy of the network on the 400 test images: %d %%' % (100 * correct / total))
總結(jié)
到此這篇關(guān)于如何使用Pytorch完成圖像分類任務(wù)的文章就介紹到這了,更多相關(guān)Pytorch圖像分類任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python使用re模塊正則提取字符串中括號(hào)內(nèi)的內(nèi)容示例
這篇文章主要介紹了Python使用re模塊正則提取字符串中括號(hào)內(nèi)的內(nèi)容,結(jié)合實(shí)例形式分析了Python使用re模塊進(jìn)行針對(duì)括號(hào)內(nèi)容的正則匹配操作,并簡(jiǎn)單解釋了相關(guān)修正符與正則語(yǔ)句的用法,需要的朋友可以參考下2018-06-06python groupby 函數(shù) as_index詳解
今天小編就為大家分享一篇python groupby 函數(shù) as_index詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12python標(biāo)準(zhǔn)庫(kù) datetime的astimezone設(shè)置時(shí)區(qū)遇到的坑及解決
這篇文章主要介紹了python標(biāo)準(zhǔn)庫(kù) datetime的astimezone設(shè)置時(shí)區(qū)遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Python常見(jiàn)數(shù)據(jù)結(jié)構(gòu)之棧與隊(duì)列用法示例
這篇文章主要介紹了Python常見(jiàn)數(shù)據(jù)結(jié)構(gòu)之棧與隊(duì)列用法,結(jié)合實(shí)例形式簡(jiǎn)單介紹了數(shù)據(jù)結(jié)構(gòu)中棧與隊(duì)列的概念、功能及簡(jiǎn)單使用技巧,需要的朋友可以參考下2019-01-01Python比較文件夾比另一同名文件夾多出的文件并復(fù)制出來(lái)的方法
這篇文章主要介紹了Python比較文件夾比另一同名文件夾多出的文件并復(fù)制出來(lái)的方法,涉及Python針對(duì)文件與文件夾的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03