【pytorch速成】Pytorch图像分类从模型自定义到测试
文章首發于微信公眾號《與有三學AI》
【pytorch速成】Pytorch圖像分類從模型自定義到測試
前面已跟大家介紹了Caffe和TensorFlow,鏈接如下。
【caffe速成】caffe圖像分類從模型自定義到測試
【tensorflow速成】Tensorflow圖像分類從模型自定義到測試
今天說說Pytorch。
作者&編輯?|?言有三??
?
01 什么是 Pytorch
一句話總結 Pytorch = Python + Torch。
Torch 是紐約大學的一個機器學習開源框架,幾年前在學術界非常流行,包括 Lecun等大佬都在使用。但是由于使用的是一種絕大部分人絕對沒有聽過的 Lua 語言,導致很多人都被嚇退。后來隨著 Python 的生態越來越完善,Facebook 人工智能研究院推出了Pytorch并開源。Pytorch不是簡單的封裝 Torch并提供Python接口,而是對Tensor以上的所有代碼進行了重構,同TensorFlow一樣,增加了自動求導。
后來Caffe2全部并入Pytorch,如今已經成為了非常流行的框架。很多最新的研究如風格化、GAN 等大多數采用Pytorch源碼,這也是我們必須要講解它的原因。
1.1 特點
(1)動態圖計算。TensorFlow從靜態圖發展到了動態圖機制Eager Execution,pytorch則一開始就是動態圖機制。動態圖機制的好處就是隨時隨地修改,隨處debug,沒有類似編譯的過程。
(2)簡單。相比TensorFlow中Tensor、Variable、Session等概念充斥,數據讀取接口頻繁更新,tf.nn、tf.layers、tf.contrib各自重復,Pytorch則是從Tensor到Variable再到nn.Module,最新的Pytorch已經將Tensor和Variable合并,這分別就是從數據張量到網絡的抽象層次的遞進。有人調侃TensorFlow的設計是“make it complicated”,那么 Pytorch的設計就是“keep it simple”。
1.2 重要概念
(1)Tensor/Variable
每一個框架都有基本的數據結構,Caffe是blob,TensorFlow和Pytorch都是Tensor,都是高維數組。Pytorch中的Tensor使用與Numpy的數組非常相似,兩者可以互轉且共享內存。
tensor包括cpu和gpu兩種類型,如torch.FloatTensortorch.cuda.FloatTensorvirable,就分別表示cpu和gpu下的32位浮點數。
tensor包含一些屬性。data,即Tensor內容;Grad,是與data對應的梯度;requires_grad,是否容許進行反向傳播的學習,更多的可以去查看API。
(2)nn.module
抽象好的網絡數據結構,可以表示為網絡的一層,也可以表示為一個網絡結構,這是一個基類。在實際使用過程中,經常會定義自己的網絡,并繼承nn.Module。具體的使用,我們看下面的網絡定義吧。
(3)torchvision包,包含了目前流行的數據集,模型結構和常用的圖片轉換工具
?
02 Pytorch 訓練
安裝咱們就不說了,接下來的任務就是開始訓練模型。訓練模型包括數據準備、模型定義、結果保存與分析。
2.1 數據讀取
前面已經介紹了Caffe和TensorFlow的數據讀取,兩者的輸入都是圖片list,但是讀取操作過程差異非常大,Pytorch與這兩個又有很大的差異。這一次,直接利用文件夾作為輸入,這是 Pytorch更加方便的做法。數據讀取的完整代碼如下:
data_dir = '../../../../datas/head/' ? ?
? ?data_transforms = {
? ? ? ?'train': transforms.Compose([
? ? ? ? ? ?transforms.RandomSizedCrop(48),
? ? ? ? ? ?transforms.RandomHorizontalFlip(),
? ? ? ? ? ?transforms.ToTensor(),
? ? ? ? ? ?transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
? ? ? ?]),
? ? ? ?'val': transforms.Compose([
? ? ? ? ? ?transforms.Scale(64),
? ? ? ? ? ?transforms.CenterCrop(48),
? ? ? ? ? ?transforms.ToTensor(),
? ? ? ? ? ?transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
? ? ? ?]),
? ?}
? ?image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?data_transforms[x]) for x in ['train', 'val']}
? ?dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? batch_size=16,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? shuffle=True,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? num_workers=4) for x in ['train', 'val']}
下面一個一個解釋,完整代碼請移步 Git 工程。
?
(1)datasets.ImageFolder
Pytorch的torchvision模塊中提供了一個dataset 包,它包含了一些基本的數據集如mnist、coco、imagenet和一個通用的數據加載器ImageFolder。
它會以這樣的形式組織數據,具體的請到Git工程中查看。
root/left/1.png
root/left/2.png
root/left/3.png
root/right/1.png
root/right/2.png
root/right/3.png
imagefolder有3個成員變量。
self.classes:用一個list保存類名,就是文件夾的名字。
self.class_to_idx:類名對應的索引,可以理解為 0、1、2、3 等。
self.imgs:保存(imgpath,class),是圖片和類別的數組。
不同文件夾下的圖,會被當作不同的類,天生就用于圖像分類任務。
(2)Transforms
這一點跟Caffe非常類似,就是定義了一系列數據集的預處理和增強操作。到此,數據接口就定義完畢了,接下來在訓練代碼中看如何使用迭代器進行數據讀取就可以了,包括 scale、減均值等。
(3)torch.utils.data.DataLoader
這就是創建了一個 batch,生成真正網絡的輸入。關于更多 Pytorch 的數據讀取方法,請自行學習。
2.2 模型定義
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class simpleconv3(nn.Module):`
def __init__(self):
? ? ? ?super(simpleconv3,self).__init__()
? ? ? ?self.conv1 = nn.Conv2d(3, 12, 3, 2)
? ? ? ?self.bn1 = nn.BatchNorm2d(12)
? ? ? ?self.conv2 = nn.Conv2d(12, 24, 3, 2)
? ? ? ?self.bn2 = nn.BatchNorm2d(24)
? ? ? ?self.conv3 = nn.Conv2d(24, 48, 3, 2)
? ? ? ?self.bn3 = nn.BatchNorm2d(48)
? ? ? ?self.fc1 = nn.Linear(48 * 5 * 5 , 1200)
? ? ? ?self.fc2 = nn.Linear(1200 , 128)
? ? ? ?self.fc3 = nn.Linear(128 , 2)
def forward(self , x):
? ? ? ?x = F.relu(self.bn1(self.conv1(x)))
? ? ? ?x = F.relu(self.bn3(self.conv3(x)))
? ? ? ?x = x.view(-1 , 48 * 5 * 5)?
? ? ? ?x = F.relu(self.fc1(x))
? ? ? ?x = F.relu(self.fc2(x))
? ? ? ?x = self.fc3(x)
? ? ? ?return x
我們的例子都是采用一個簡單的3層卷積 + 2層全連接層的網絡結構。根據上面的網絡結構的定義,需要做以下事情。
(1)simpleconv3(nn.Module)
繼承nn.Module,前面已經說過,Pytorch的網絡層是包含在nn.Module 里,所以所有的網絡定義,都需要繼承該網絡層,并實現super方法,如下:
super(simpleconv3,self).__init__()
這個就當作一個標準執行就可以了。
(2)網絡結構的定義都在nn包里,舉例說明:
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
完整的接口如上,定義的第一個卷積層如下:
nn.Conv2d(3, 12, 3, 2)
即輸入通道為3,輸出通道為12,卷積核大小為3,stride=2,其他的層就不一一介紹了,大家可以自己去看nn的API。
(3)forward
backward方法不需要自己實現,但是forward函數是必須要自己實現的,從上面可以看出,forward 函數也是非常簡單,串接各個網絡層就可以了。
對比Caffe和TensorFlow可以看出,Pytorch的網絡定義更加簡單,初始化方法都沒有顯示出現,因為 Pytorch已經提供了默認初始化。
如果我們想實現自己的初始化,可以這么做:
init.xavier_uniform(self.conv1.weight)init.constant(self.conv1.bias, 0.1)
它會對conv1的權重和偏置進行初始化。如果要對所有conv層使用 xavier 初始化呢?可以定義一個函數:
def weights_init(m): ? ?
? ?if isinstance(m, nn.Conv2d):
? ? ? ?xavier(m.weight.data)
? ? ? ?xavier(m.bias.data) ?
? ?net = Net() ??
? ?net.apply(weights_init)
?
03 模型訓練
網絡定義和數據加載都定義好之后,就可以進行訓練了,老規矩先上代碼:
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
? ? ? ?for epoch in range(num_epochs):
? ? ? ? ? ?print('Epoch {}/{}'.format(epoch, num_epochs - 1))
? ? ? ? ? ?for phase in ['train', 'val']:
? ? ? ? ? ? ? ?if phase == 'train':
? ? ? ? ? ? ? ? ? ?scheduler.step()
? ? ? ? ? ? ? ? ? ?model.train(True) ?
? ? ? ? ? ? ? ?else:
? ? ? ? ? ? ? ? ? ?model.train(False) ?
? ? ? ? ? ? ? ? ? ?running_loss = 0.0 ? ? ? ? ? ? ? ?running_corrects = 0.0
? ? ? ? ? ? ? ?for data in dataloders[phase]:
? ? ? ? ? ? ? ? ? ?inputs, labels = data
? ? ? ? ? ? ? ? ? ?if use_gpu:
? ? ? ? ? ? ? ? ? ? ? ?inputs = Variable(inputs.cuda())
? ? ? ? ? ? ? ? ? ? ? ?labels = Variable(labels.cuda())
? ? ? ? ? ? ? ? ? ?else:
? ? ? ? ? ? ? ? ? ? ? ?inputs, labels = Variable(inputs), Variable(labels)
? ? ? ? ? ? ? ? ? ?optimizer.zero_grad()
? ? ? ? ? ? ? ? ? ?outputs = model(inputs)
? ? ? ? ? ? ? ? ? ?_, preds = torch.max(outputs.data, 1)
? ? ? ? ? ? ? ? ? ?loss = criterion(outputs, labels)
? ? ? ? ? ? ? ? ? ?if phase == 'train':
? ? ? ? ? ? ? ? ? ? ? ?loss.backward()
? ? ? ? ? ? ? ? ? ? ? ?optimizer.step()
? ? ? ? ? ? ? ? ? ?running_loss += loss.data.item()
? ? ? ? ? ? ? ? ? ?running_corrects += torch.sum(preds == labels).item()
? ? ? ? ? ? ? ?epoch_loss = running_loss / dataset_sizes[phase]
? ? ? ? ? ? ? ?epoch_acc = running_corrects / dataset_sizes[phase]
? ? ? ? ? ? ? ?if phase == 'train':
? ? ? ? ? ? ? ? ? ?writer.add_scalar('data/trainloss', epoch_loss, epoch)
? ? ? ? ? ? ? ? ? ?writer.add_scalar('data/trainacc', epoch_acc, epoch)
? ? ? ? ? ? ? ?else:
? ? ? ? ? ? ? ? ? ?writer.add_scalar('data/valloss', epoch_loss, epoch)
? ? ? ? ? ? ? ? ? ?writer.add_scalar('data/valacc', epoch_acc, epoch)
? ? ? ? ? ? ? ?print('{} Loss: {:.4f} Acc: {:.4f}'.format(
? ? ? ? ? ? ? ?phase, epoch_loss, epoch_acc))
? ? ? ?writer.export_scalars_to_json("./all_scalars.json")
? ? ? ?writer.close()
? ? ? ?return model
分析一下上面的代碼,外層循環是epoches,然后利用 for data in dataloders[phase] 循環取一個epoch 的數據,并塞入variable,送入model。需要注意的是,每一次forward要將梯度清零,即optimizer.zero_grad(),因為梯度會記錄前一次的狀態,然后計算loss進行反向傳播。
loss.backward()
optimizer.step()
下面可以分別得到預測結果和loss,每一次epoch 完成計算。
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects / dataset_sizes[phase]
_, preds = torch.max(outputs.data, 1)
loss = criterion(outputs, labels)
可視化是非常重要的,鑒于TensorFlow的可視化非常方便,我們選擇了一個開源工具包,tensorboardx,安裝方法為pip install tensorboardx,使用非常簡單。
第一步,引入包定義創建:
from tensorboardX import SummaryWriter
writer = SummaryWriter()
第二步,記錄變量,如train階段的 loss,writer.add_scalar('data/trainloss', epoch_loss, epoch)。
按照以上操作就完成了,完整代碼可以看配套的Git 項目,我們看看訓練中的記錄。Loss和acc的曲線圖如下:
?
網絡的收斂沒有Caffe和TensorFlow好,大家可以自己去調試調試參數了,隨便折騰吧。
?
04 Pytorch 測試
上面已經訓練好了模型,接下來的目標就是要用它來做inference了,同樣給出代碼。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import torchvision
from torchvision import datasets, models, transforms
import time
import os
from PIL import Image
import sys
import torch.nn.functional as F
from net import simpleconv3
data_transforms = ?transforms.Compose([
? ? ? ? ? ? ? ?transforms.Resize(48),
? ? ? ? ? ? ? ?transforms.ToTensor(),
? ? ? ? ? ? ? ?transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])])
net = simpleconv3()
modelpath = sys.argv[1]
net.load_state_dict(torch.load(modelpath,map_location=lambda storage,loc: storage))
imagepath = sys.argv[2]
image = Image.open(imagepath)
imgblob = data_transforms(image).unsqueeze(0)
imgblob = Variable(imgblob)
torch.no_grad()
predict = F.softmax(net(imgblob))
print(predict)
從上面的代碼可知,做了幾件事:
定義網絡并使用torch.load和load_state_dict載入模型。
用PIL的Image包讀取圖片,這里沒有用OpenCV,因為Pytorch默認的圖片讀取工具就是PIL的Image,它會將圖片按照RGB的格式,歸一化到 0~1 之間。讀取圖片之后,必須轉化為Tensor變量。
evaluation的時候,必須設置torch.no_grad(),然后就可以調用 softmax 函數得到結果了。
?
05 總結
本節講了如何用 Pytorch 完成一個分類任務,并學習了可視化以及使用訓練好的模型做測試。
?
同時,在我的知乎專欄也會開始同步更新這個模塊,歡迎來交流
https://zhuanlan.zhihu.com/c_151876233
注:部分圖片來自網絡
—END—
本系列完整文章:
第一篇:【caffe速成】caffe圖像分類從模型自定義到測試
第二篇:【tensorflow速成】Tensorflow圖像分類從模型自定義到測試
第三篇:【pytorch速成】Pytorch圖像分類從模型自定義到測試
第四篇:【paddlepaddle速成】paddlepaddle圖像分類從模型自定義到測試
第五篇:【Keras速成】Keras圖像分類從模型自定義到測試
第六篇:【mxnet速成】mxnet圖像分類從模型自定義到測試
第七篇:【cntk速成】cntk圖像分類從模型自定義到測試
第八篇:【chainer速成】chainer圖像分類從模型自定義到測試
第九篇:【DL4J速成】Deeplearning4j圖像分類從模型自定義到測試
第十篇:【MatConvnet速成】MatConvnet圖像分類從模型自定義到測試
第十一篇:【Lasagne速成】Lasagne/Theano圖像分類從模型自定義到測試
第十二篇:【darknet速成】Darknet圖像分類從模型自定義到測試
感謝各位看官的耐心閱讀,不足之處希望多多指教。后續內容將會不定期奉上,歡迎大家關注有三公眾號 有三AI!
?
?
總結
以上是生活随笔為你收集整理的【pytorch速成】Pytorch图像分类从模型自定义到测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【技术综述】如何Finetune一个小网
- 下一篇: 【paddlepaddle速成】padd