PyTorch 實戰:使用卷積神經網路對照片進行分類
本文任務
我們接下來需要用CIFAR-10資料集進行分類,步驟如下:
-
使用torchvision 載入並預處理CIFAR-10資料集
-
定義網路
-
定義損失函式和優化器
-
訓練網路並更新網路引數
-
測試網路
對卷積不瞭解的同學建議先閱讀
ofollow,noindex">10分鐘理解深度學習中的~卷積~
注意:文章末尾含有專案jupyter notebook實戰教程下載可供大家課後實戰操作
一、CIFAR-10資料載入及預處理
CIFAR-10
是一個常用的彩色圖片資料集,它有 10 個類別,分別是 airplane、automobile、bird、cat、deer、dog、frog、horse、ship和 truck
。每張圖片都是 3*32*32
,也就是 三通道彩色圖片,解析度 32*32
。
import torchvision as tv import torchvision.transforms as transforms from torchvision.transforms import ToPILImage import torch as t #可以把Tensor轉化為Image,方便視覺化 show = ToPILImage() #先偽造一個圖片的Tensor,用ToPILImage顯示 fake_img = t.randn(3, 32, 32) #顯示圖片 show(fake_img)
第一次執行torchvision會自動下載CIFAR-10資料集,大約163M。這裡我將資料直接放到專案 data資料夾
中。
cifar_dataset = tv.datasets.CIFAR10(root='data', train=True, download=True ) imgdata, label = cifar_dataset[90] print('label: ', label) print('imgdata的型別:',type(imgdata)) imgdata
執行結果
Files already downloaded and verified label:2 imgdata的型別: <class 'PIL.Image.Image'>
注意,資料集中的照片資料是以 PIL.Image.Image類
形式儲存的,在我們載入資料時,要注意將其轉化為 Tensor類
。
def dataloader(train): transformer = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=(0.5, 0.5, 0.5), std = (0.5, 0.5, 0.5)) ]) cifar_dataset = tv.datasets.CIFAR10(root='data',#下載的資料集所在的位置 train=train,#是否為訓練集。 download=True, #設定為True,不用再重新下載資料 transform=transformer ) loader = t.utils.data.DataLoader( cifar_dataset, batch_size=4, shuffle=True, #打亂順序 num_workers=2 #worker數為2 ) return loader classes=('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') #訓練集和測試集的載入器 trainloader = dataloader(train=True) testloader = dataloader(train=False)
執行結果
Files already downloaded and verified Files already downloaded and verified
DataLoader是一個可迭代的物件,它將dataset返回的每一條資料樣本拼接成一個batch,並提供多執行緒加速優化和資料打亂等操作。當程式對 cirfar_dataset
的所有資料遍歷完一遍, 對Dataloader也完成了一次迭代。
dataiter = iter(trainloader) #返回四張照片及其label images, labels = dataiter.next() #列印多張照片 show(tv.utils.make_grid(images))
#顯示images中的第三張照片
show(images[2])
二、定義網路
最早的卷積神經網路LeNet為例,學習卷積神經網路。
2.1 第一個convolutions層
圖中顯示是單通道照片,但是由於我們的資料集中的照片是三通道照片。所以
該層輸入的是 三通道圖片
,圖片長寬均為32,那麼通過kernel_size=5的卷積核卷積後的尺寸為(32-5+1)=28
同時要注意,第一個convolution中,圖片由 三通道變為6通道
, 所以在此卷積過程中,in_channels=3, out_channels=6
nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5)
2.2 第一subsampling層
該層輸入資料是6通道,輸出還為6通道,但是圖片的長寬從28變為14,我們可以使用池化層來實現尺寸縮小一倍。這裡我們使用MaxPool2d(2, 2)
nn.MaxPool2d(kernel_size=2, stride=2)
2.3 第二個convolutions層
該層輸入的是6通道資料,輸出為16通道資料,且圖片長寬從14變為10。這裡我們使用
nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
2.4 全連線層作用
在此之前的卷積層和池化層都屬於特徵工程層,用於從資料中抽取特徵。而之後的多個全連線層,功能類似於機器學習中的模型,用於學習特徵資料中的規律,並輸出預測結果。
2.5 第一全連線層full connection
第二個convolutions層輸出的 資料形狀為 (16, 5, 5) 的陣列
,是一個三維資料。
而在全連線層中,我們需要將其 展平為一個一維資料(樣子類似於列表,長度為16\*5\*5)
nn.Linear(in_features=16*5*5, out_features=120) #根據圖中,該輸出為120
2.6 第二全連線層
該層的輸入是一維陣列,長度為120,輸出為一維陣列,長度為84.
nn.Linear(in_features=120, out_features=84) #根據圖中,該輸出為84
2.7 第三全連線層
該層的輸入是一維陣列,長度為84,輸出為一維陣列,長度為10,該層網路定義如下
nn.Linear(in_features=84, out_features=10) #根據圖中,該輸出為10
注意:
這裡的長度10的列表,可以看做輸出的label序列。例如理想情況下
output = [1, 0, 0, 0, 0, 0, 0 ,0, 0 ,0]
該output表示 input資料
經過該神經網路運算得到的 預測結果
顯示的 類別是 第一類
同理,理想情況下
output2 = [0, 1, 0, 0, 0, 0, 0 ,0, 0 ,0]
該output2表示 input資料
經過該神經網路運算得到的 預測結果
顯示的 類別是 第二類
根據前面對LeNet網路的解讀,現在我們用pytorch來定義LeNet網路結構
import torch import torch.nn as nn class LeNet(nn.Module): def __init__(self): #Net繼承nn.Module類,這裡初始化呼叫Module中的一些方法和屬性 nn.Module.__init__(self) #定義特徵工程網路層,用於從輸入資料中進行抽象提取特徵 self.feature_engineering = nn.Sequential( nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5), #kernel_size=2, stride=2,正好可以將圖片長寬尺寸縮小為原來的一半 nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5), nn.MaxPool2d(kernel_size=2, stride=2) ) #分類器層,將self.feature_engineering中的輸出的資料進行擬合 self.classifier = nn.Sequential( nn.Linear(in_features=16*5*5, out_features=120), nn.Linear(in_features=120, out_features=84), nn.Linear(in_features=84, out_features=10), ) def forward(self, x): #在Net中改寫nn.Module中的forward方法。 #這裡定義的forward不是呼叫,我們可以理解成資料流的方向,給net輸入資料inpput會按照forward提示的流程進行處理和操作並輸出資料 x = self.feature_engineering(x) x = x.view(-1, 16*5*5) x = self.classifier(x) return x
例項化神經網路LeNet
net = LeNet() net
執行結果
LeNet( (feature_engineering): Sequential( (0): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) (1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (classifier): Sequential( (0): Linear(in_features=400, out_features=120, bias=True) (1): Linear(in_features=120, out_features=84, bias=True) (2): Linear(in_features=84, out_features=10, bias=True) ) )
我們隨機傳入一批照片(batch_size=4) ,將其輸入給net,看輸出的結果是什麼情況。
注意:
pytorch中輸入的資料必須是batch資料(批資料)
dataiter = iter(trainloader) #返回四張照片及其label images, labels = dataiter.next() outputs = net(images) outputs
執行結果
tensor([[ 0.1963,0.0203,0.0887, -0.0789, -0.0027, -0.0429, -0.1119,0.0080, 0.0007, -0.0901], [ 0.2260,0.0246,0.0498, -0.0188,0.0207, -0.0541, -0.0943,0.0431, -0.0204, -0.1023], [ 0.2168,0.0280,0.0463, -0.0055, -0.0017, -0.0504, -0.0897,0.0385, -0.0229, -0.1030], [ 0.2025,0.0579,0.0527, -0.0038, -0.0300, -0.0474, -0.0952,0.0698, -0.0145, -0.0620]], grad_fn=<ThAddmmBackward>)
t.max(input, dim)
-
input:傳入的tensor
-
dim: tensor的方向。dim=1表示按照行方向計算最大值
t.max(outputs, dim=1)
執行結果
(tensor([0.1963, 0.2260, 0.2168, 0.2025], grad_fn=<MaxBackward0>), tensor([0, 0, 0, 0]))
上述的操作,找到了outputs中四個最大的值,及其對應的index(該index可以理解為label)
三、定義損失函式和優化器
神經網路強大之處就在於 反向傳播
,通過比較 預測結果
與 真實結果
, 修整 網路引數
。
這裡的 比較
就是 損失函式
,而 修整網路引數
就是 優化器
。
這樣充分利用了每個訓練資料,使得網路的擬合和預測能力大大提高。
from torch import optim #定義交叉熵損失函式 criterion = nn.CrossEntropyLoss() #隨機梯度下降SGD優化器 optimizer = optim.SGD(params = net.parameters(), lr = 0.001)
四、訓練網路
所有網路的訓練的流程都是類似的,不斷執行(輪):
-
給網路輸入資料
-
前向傳播+反向傳播
-
更新網路引數
遍歷完一遍資料集稱為一個epoch,這裡我們進行 2個epoch
輪次的訓練。
epochs = 10 average_loss_series = [] for epoch in range(epochs): running_loss = 0.0 for i, data in enumerate(trainloader): inputs, labels = data #inputs, labels = Variable(inputs), Variable(labels) #梯度清零 optimizer.zero_grad() #forward+backward outputs = net(inputs) #對比預測結果和labels,計算loss loss = criterion(outputs, labels) #反向傳播 loss.backward() #更新引數 optimizer.step() #列印log running_loss += loss.item() if i % 2000 == 1999: #每2000個batch列印一次訓練狀態 average_loss = running_loss/2000 print("[{0},{1}] loss:{2}".format(epoch+1, i+1, average_loss)) average_loss_series.append(average_loss) running_loss = 0.0
執行結果
[1,2000] loss:2.284719424366951 [1,4000] loss:2.1300598658323286 [1,6000] loss:2.0143098856806754 [1,8000] loss:1.9478365245759488 [1,10000] loss:1.9135449583530426 [1,12000] loss:1.8653237966001033 [2,2000] loss:1.8014366626143457 [2,4000] loss:1.737443323969841 [2,6000] loss:1.6933535016775132 [2,8000] loss:1.6476907352507115 [2,10000] loss:1.6234023304879666 [2,12000] loss:1.5863604183495044 [3,2000] loss:1.5544855180978776 [3,4000] loss:1.539060534775257 [3,6000] loss:1.5500386973917484 [3,8000] loss:1.5407403408288955 [3,10000] loss:1.493699783280492 [3,12000] loss:1.4957395897060632 [4,2000] loss:1.4730096785128117 [4,4000] loss:1.4749664356559515 [4,6000] loss:1.4479290856420994 [4,8000] loss:1.445657522082329 [4,10000] loss:1.4586472637057304 [4,12000] loss:1.4320134285390378 [5,2000] loss:1.406113230422139 [5,4000] loss:1.4196837954670192 [5,6000] loss:1.3951636335104705 [5,8000] loss:1.3933502195328473 [5,10000] loss:1.3908299638181925 [5,12000] loss:1.3908768535405398 [6,2000] loss:1.3397984126955271 [6,4000] loss:1.3737898395806551 [6,6000] loss:1.360704499706626 [6,8000] loss:1.3652801268100738 [6,10000] loss:1.334371616870165 [6,12000] loss:1.312294240474701 [7,2000] loss:1.3097571679353714 [7,4000] loss:1.3236577164530754 [7,6000] loss:1.310647354334593 [7,8000] loss:1.3016219032108785 [7,10000] loss:1.2931814943552018 [7,12000] loss:1.2910259604007006 [8,2000] loss:1.2796987656354903 [8,4000] loss:1.2650054657310248 [8,6000] loss:1.2713083022236824 [8,8000] loss:1.258927255064249 [8,10000] loss:1.275728213787079 [8,12000] loss:1.2612977192252874 [9,2000] loss:1.2273035216629504 [9,4000] loss:1.25000972096622 [9,6000] loss:1.2236297953873874 [9,8000] loss:1.2251979489773512 [9,10000] loss:1.2623697004914283 [9,12000] loss:1.2501848887503146 [10,2000] loss:1.2257770787626505 [10,4000] loss:1.2277075409144163 [10,6000] loss:1.2050671626776457 [10,8000] loss:1.2159633481949568 [10,10000] loss:1.210464821562171 [10,12000] loss:1.2225491935014725
五、測試網路
5.1 列印誤差曲線
%matplotlib inline import matplotlib.pyplot as plt x = range(0, 60) plt.figure() plt.plot(x, average_loss_series)
5.2 檢視訓練的準確率
我們使用測試集檢驗訓練的神經網路的效能。
def correct_rate(net, testloader): correct = 0 total = 0 for data in testloader: images, labels = data outputs = net(images) _, predicted = t.max(outputs.data, 1) total += labels.size(0) correct += (predicted==labels).sum() return 100*correct/total correct = correct_rate(net, testloader) print('10000張測試集中準確率為: {}%'.format(correct))
執行結果
10000張測試集中準確率為: 57%
資料集一共有10種照片,且每種照片數量相等。所以理論上,我們猜測對每一張照片的概率為10%。
而通過我們神經網路LeNet預測的準確率達到 57%
,證明網路確實學習到了規律。
往期文章