手把手教你用PyTorch從零搭建圖像分類模型

https://zhuanlan.zhihu.com/p/38236978

過去的這幾年,陸陸續續出現了不少深度學習框架。而在這些框架中,Facebook 發佈的 PyTorch 相對較新且很獨特的一個,由於靈活、迅速、簡單等特點,PyTorch 發展迅猛,受到很多人的青睞。


在 PyTorch 上,我們能夠很容易的自定義模型的層級,完全掌控訓練過程,包括梯度傳播。本文就手把手教你如何用 PyTorch 從零搭建一個完整的圖像分類器。


安裝 PyTorch

得益於預先內置的庫,PyTorch 安裝起來相當容易,在所有的系統上都能很好的運行。


在 Windows 系統上安裝

只有 CPU:

pip3 install download.Pytorch.org/wh

pip3 install torchvision


有GPU支持

pip3 install download.Pytorch.org/wh

pip3 install torchvision


在Linux系統上安裝

只有CPU:

pip3 install torch torchvision


有GPU支持

pip3 install download.Pytorch.org/wh

pip3 install torchvision


在OSX系統上安裝

只有CPU:

pip3 install torch torchvision


有GPU支持

按照PyTorch官網(pytorch.org/)上的詳細指令安裝。


注意:如果想親自實踐本文的教程,你應該有CUDA GPU。如果沒有,也沒關係!在colab.research.google.com 上可以免費使用一個基於雲的GPU。


卷積神經網絡簡介

我們本文要使用的模型爲卷積神經網絡(CNN),它主要就是由一些卷積層堆疊在一起,通常還會有一些正則層和激活層。卷積神經網絡的組成部分總結如下:


  • CNN—— 一堆卷積層。
  • 卷積層—— 能夠檢測一定的特徵,具有特定數量的通道。
  • 通道—— 能夠檢測圖像中的具體特徵。
  • 核/過濾器—— 每個通道中會被檢測到的特徵。它有固定的大小,通常爲3X3。


簡單來說,卷積層相當於一個特徵檢測層。每個卷積層有特定數目的通道,每個通道能夠檢測出圖像中的具體特徵。需要檢測的每個特徵常常被叫做核(kernel)或過濾器,它們都有固定大小,通常爲3X3。


定義模型架構

在PyTorch中,通過能擴展Module類的定製類來定義模型。模型的所有組件可以在torch.nn包中找到。因此,我們只需導入這個包就可以了。這裏我們會搭建一個簡單的CNN模型,用以分類來自CIFAR 10數據集的RGB圖像。該數據集包含了50000張訓練圖像和10000張測試圖像,所有圖像大小爲32 X 32。


  1. # 導入需要的包
  2. import torch
  3. import torch.nn as nn
  4. class SimpleNet(nn.Module):
  5. def __init__(self, num_classes=10):
  6. super(SimpleNet, self).__init__()
  7. self.conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=3, stride=1, padding=1)
  8. self.relu1 = nn.ReLU()
  9. self.conv2 = nn.Conv2d(in_channels=12, out_channels=12, kernel_size=3, stride=1, padding=1)
  10. self.relu2 = nn.ReLU()
  11. self.pool = nn.MaxPool2d(kernel_size=2)
  12. self.conv3 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=3, stride=1, padding=1)
  13. self.relu3 = nn.ReLU()
  14. self.conv4 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=3, stride=1, padding=1)
  15. self.relu4 = nn.ReLU()
  16. self.fc = nn.Linear(in_features=16 * 16 * 24, out_features=num_classes)
  17. def forward(self, input):
  18. output = self.conv1(input)
  19. output = self.relu1(output)
  20. output = self.conv2(output)
  21. output = self.relu2(output)
  22. output = self.pool(output)
  23. output = self.conv3(output)
  24. output = self.relu3(output)
  25. output = self.conv4(output)
  26. output = self.relu4(output)
  27. output = output.view(-1, 16 * 16 * 24)
  28. output = self.fc(output)
  29. return output

在上面的代碼中,我們首先定義了一個新的類,叫做SimpleNet,它會擴展nn.Module類。在這個類的構造函數中,我們指明瞭神經網絡的全部層。我們的神經網絡結構爲——ReLU層——卷積層——ReLU層——池化層——卷積層——ReLU層——卷積層——ReLU層——線性層。


我們挨個講解它們。


卷積層

nn.Conv2d(in_channels=3, out_channels=12, kernel_size=3, stride=1, padding=1)


因爲我們的輸入爲有 3 個通道(紅-綠-藍)的 RGB 圖像,我們指明 in_channels 的數量爲 3。接着我們想將 12 特徵的檢測器應用在圖像上,所以我們指明 out_channels 的數量爲 12。這裏我們使用標準大小爲 3X3 的核。步幅設定爲 1,後面一直是這樣,除非你計劃縮減圖像的維度。將步幅設置爲 1,卷積會一次變爲 1 像素。最後,我們設定填充(padding)爲 1:這樣能確保我們的圖像以0填充,從而保持輸入和輸出大小一致。


基本上,你不用太擔心目前的步幅和填充大小,重點關注 in_channels 和 out_channels 就好了。


注意這一層的 out_channels 會作爲下一層的 in_channels,如下所示:


nn.Conv2d(in_channels=12, out_channels=12, kernel_size=3, stride=1, padding=1)


ReLU

這是標準的 ReLU 激活函數,它基本上會將所有輸入進來的特徵變爲 0 或更大的值。簡單說,當你用 ReLU 處理輸入特徵時,任何小於 0 的數字都會被變爲 0,其餘值保持不變。



MaxPool2d

這一層會通過將 kernel_size 設置爲 2、將圖像的寬和高減少 2 倍來降低圖像的維度。它的基本操作就是在圖像的 2X2 區域內取像素最大值,用它來表示整個區域,因此 4 像素就會變成只有 1 個。



線性層

我們的神經網絡的最後一層爲線性層。這是個標準的全連接層,它會計算每個類的分值——在我們這個例子中是 10 個類。



注意:我們在將最後一個卷積 -ReLU 層中的特徵圖譜輸入圖像前,必須把整個圖譜壓平。最後一層有 24 個輸出通道,由於 2X2 的最大池化,在這時我們的圖像就變成了16 X 16(32/2 = 16)。我們壓平後的圖像的維度會是16 x 16 x 24,實現代碼如下:


output = output.view(-1, 16 * 16 * 24)


在我們的線性層中,我們必須指明 input_features 的數目同樣爲 16 x 16 x 24,out_features 的數目應和我們所希望的類的數量一致。


注意在 PyTorch 中定義模型的簡單規則。在構造函數中定義層級,在前饋函數中傳遞所有輸入。


希望以上能幫你對如何在 PyTorch 中定義模型有了基本的理解。


模塊化

上面的代碼雖然酷,但是還不夠很酷——如果我們想洗個非常深的神經網絡,代碼會看着非常臃腫。而讓代碼保持乾淨整潔的關鍵就是模塊化。在上面的例子中,我們可以將卷積和 ReLU放在一個單獨的模塊中,將模塊的大部分堆疊在我們的 SimpleNet中。


要做到這點,我們首先以如下方式定義一個新模塊:

  1. class Unit(nn.Module):
  2. def __init__(self, in_channels, out_channels):
  3. super(Unit, self).__init__()
  4. self.conv = nn.Conv2d(in_channels=in_channels, kernel_size=3, out_channels=out_channels, stride=1, padding=1)
  5. self.bn = nn.BatchNorm2d(num_features=out_channels)
  6. self.relu = nn.ReLU()
  7. def forward(self, input):
  8. output = self.conv(input)
  9. output = self.bn(output)
  10. output = self.relu(output)
  11. return output


如上所示,這個單元包含了卷積層-規範層 -ReLU 層。

不想我們所說的第一個例子,這裏我們將 BatchNorm2d 放在了 ReLU 前面。規範層會將所有輸入標準化爲具有零平均值和單位變異數。它會大幅提高 CNN 模型的準確率。


定義好上面的單元后,我們現在將它們堆疊在一起。

  1. class Unit(nn.Module):
  2. def __init__(self,in_channels,out_channels):
  3. super(Unit,self).__init__()
  4. self.conv = nn.Conv2d(in_channels=in_channels,kernel_size=3,out_channels=out_channels,stride=1,padding=1)
  5. self.bn = nn.BatchNorm2d(num_features=out_channels)
  6. self.relu = nn.ReLU()
  7. def forward(self,input):
  8. output = self.conv(input)
  9. output = self.bn(output)
  10. output = self.relu(output)
  11. return output
  12. class SimpleNet(nn.Module):
  13. def __init__(self,num_classes=10):
  14. super(SimpleNet,self).__init__()
  15. #Create 14 layers of the unit with max pooling in between
  16. self.unit1 = Unit(in_channels=3,out_channels=32)
  17. self.unit2 = Unit(in_channels=32, out_channels=32)
  18. self.unit3 = Unit(in_channels=32, out_channels=32)
  19. self.pool1 = nn.MaxPool2d(kernel_size=2)
  20. self.unit4 = Unit(in_channels=32, out_channels=64)
  21. self.unit5 = Unit(in_channels=64, out_channels=64)
  22. self.unit6 = Unit(in_channels=64, out_channels=64)
  23. self.unit7 = Unit(in_channels=64, out_channels=64)
  24. self.pool2 = nn.MaxPool2d(kernel_size=2)
  25. self.unit8 = Unit(in_channels=64, out_channels=128)
  26. self.unit9 = Unit(in_channels=128, out_channels=128)
  27. self.unit10 = Unit(in_channels=128, out_channels=128)
  28. self.unit11 = Unit(in_channels=128, out_channels=128)
  29. self.pool3 = nn.MaxPool2d(kernel_size=2)
  30. self.unit12 = Unit(in_channels=128, out_channels=128)
  31. self.unit13 = Unit(in_channels=128, out_channels=128)
  32. self.unit14 = Unit(in_channels=128, out_channels=128)
  33. self.avgpool = nn.AvgPool2d(kernel_size=4)
  34. #Add all the units into the Sequential layer in exact order
  35. self.net = nn.Sequential(self.unit1, self.unit2, self.unit3, self.pool1, self.unit4, self.unit5, self.unit6
  36. ,self.unit7, self.pool2, self.unit8, self.unit9, self.unit10, self.unit11, self.pool3,
  37. self.unit12, self.unit13, self.unit14, self.avgpool)
  38. self.fc = nn.Linear(in_features=128,out_features=num_classes)
  39. def forward(self, input):
  40. output = self.net(input)
  41. output = output.view(-1,128)
  42. output = self.fc(output)
  43. return output


我們的整個神經網絡出來了,它有14個卷積層、14個ReLU層、14個規範層、4個池化層和1個線性層組成,總共62個層!


注意我們把除了全連接層以外的所有層放入一個有序類中,讓代碼更緊湊些。這會進一步簡化前饋函數中的代碼。

self.net = nn.Sequential(self.unit1, self.unit2, self.unit3, self.pool1, self.unit4, self.unit5, self.unit6, self.unit7, self.pool2, self.unit8, self.unit9, self.unit10, self.unit11, self.pool3,self.unit12, self.unit13, self.unit14, self.avgpool)


此外,最後一個單元后面的AvgPooling層會計算每個通道中的所有函數的平均值。該單元的輸出有128個通道,在池化3次後,我們的32 X 32圖像變成了4 X 4。我們以核大小爲4使用AvgPool2D,將我們的特徵圖譜調整爲1X1X128。

  1. self.avgpool = nn.AvgPool2d(kernel_size=4)
  2. 因此,線性層會有1X1X128=128個輸入特徵。
  3. self.fc = nn.Linear(in_features=128,out_features=num_classes)
  4. 我們同樣會壓平神經網絡的輸出,讓它有128個特徵。
  5. output = output.view(-1,128)


加載和增強數據

得益於torchvision包,數據加載在PyTorch中非常容易。比如,我們加載本文所用的CIFAR10 數據集。


首先,我們需要3個額外的導入語句。

  1. from torchvision.datasets import CIFAR10
  2. from torchvision.transforms import transforms
  3. from torch.utils.data import DataLoader


要加載數據集,我們按照如下步驟操作:

定義即將應用在圖像上的轉換

用torchvision加載數據集

創建DataLoader的實例來保存照片

代碼如下所示:

  1. # 定義訓練集的轉換,隨機翻轉圖像,剪裁圖像,應用平均和標準正常化方法
  2. train_transformations = transforms.Compose([
  3. transforms.RandomHorizontalFlip(),
  4. transforms.RandomCrop(32,padding=4),
  5. transforms.ToTensor(),
  6. transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))
  7. ])
  8. # 加載訓練集
  9. train_set =CIFAR10(root="./data",train=True,transform=train_transformations,download=True)
  10. # 爲訓練集創建加載程序
  11. train_loader = DataLoader(train_set,batch_size=32,shuffle=True,num_workers=4)


首先,我們用 transform.Compose 輸入轉換的一個數組。RandomHorizontalFlip 會隨機水平翻轉照片。RandomCrop 隨機剪裁照片。下面是水平剪裁的示例:



最後,兩個最重要的步驟:ToTensor 將圖像轉換爲 PyTorch 能夠使用的格式;Normalize會讓所有像素範圍處於-1到+1之間。注意,在聲明轉換時,ToTensor 和 Normalize 必須和前面定義的順序一致。主要是因爲在輸入圖像上也應用了其它的轉換,比如 PIL 圖像處理。


數據增強能幫助模型正確地分類圖像,不用考慮圖像的展示角度。


接着,我們用 CIFAR10 類加載訓練集,最終我們爲訓練集創建一個加載程序,指定批次大小爲32張圖像。


在測試集中重複此步驟,只是轉換隻包括 ToTensor 和 Normalize。我們在測試集中不用其它類型的轉換。


  1. # 定義測試集的轉換
  2. test_transformations = transforms.Compose([
  3. transforms.ToTensor(),
  4. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  5. ])
  6. # 加載測試集,注意這裏的train設爲false
  7. test_set = CIFAR10(root="./data", train=False, transform=test_transformations, download=True)
  8. # 爲測試集創建加載程序,注意這裏的shuffle設爲false
  9. test_loader = DataLoader(test_set, batch_size=32, shuffle=False, num_workers=4)

你首次運行此代碼時,大約會有 179MB 的數據集加載到你的系統中。


訓練模型

用 PyTorch 訓練神經網絡非常清晰明確,你能區安全控制控制訓練過程。我們一步一步解釋。


以如下命令導入 Adam 優化器:

from torch.optim import Adam


第一步:初始化模型,創建優化器和損失函數


  1. from torch.optim import Adam
  2. # 檢查GPU是否可用
  3. cuda_avail = torch.cuda.is_available()
  4. # 創建模型,優化器和損失函數
  5. model = SimpleNet(num_classes=10)
  6. # 若GPU可用,將模型移往GPU
  7. if cuda_avail:
  8. model.cuda()
  9. # 定義優化器和損失函數
  10. optimizer = Adam(model.parameters(), lr=0.001, weight_decay=0.0001)
  11. loss_fn = nn.CrossEntropyLoss()


第二步:寫一個函數調整學習率


創建一個學習率調整函數,每30個週期將學習率除以10。

  1. # Create a learning rate adjustment function that divides the learning rate by 10 every 30 epochs
  2. def adjust_learning_rate(epoch):
  3. lr = 0.001
  4. if epoch > 180:
  5. lr = lr / 1000000
  6. elif epoch > 150:
  7. lr = lr / 100000
  8. elif epoch > 120:
  9. lr = lr / 10000
  10. elif epoch > 90:
  11. lr = lr / 1000
  12. elif epoch > 60:
  13. lr = lr / 100
  14. elif epoch > 30:
  15. lr = lr / 10
  16. for param_group in optimizer.param_groups:
  17. param_group["lr"] = lr

該函數會在每30個週期後將學習率除以10.


第三步:寫出函數保存和評估模型


  1. def save_models(epoch):
  2. torch.save(model.state_dict(), "cifar10model_{}.model".format(epoch))
  3. print("Chekcpoint saved")
  4. def test():
  5. model.eval()
  6. test_acc = 0.0
  7. for i, (images, labels) in enumerate(test_loader):
  8. if cuda_avail:
  9. images = Variable(images.cuda())
  10. labels = Variable(labels.cuda())
  11. # Predict classes using images from the test set
  12. outputs = model(images)
  13. _, prediction = torch.max(outputs.data, 1)
  14. test_acc += torch.sum(prediction == labels.data)
  15. # Compute the average acc and loss over all 10000 test images
  16. test_acc = test_acc / 10000
  17. return test_acc


爲了能評估模型在測試集上準確度,我們迭代測試加載程序。在每一步,我們會把圖像和標籤移往GPU,在Variable中將它們封裝。將圖像傳入模型中以獲取預測值。選擇最大預測值,然後和實際類進行比較,以獲取準確率。最後,我們返回平均準確率。


第四步:寫出訓練函數

  1. def train(num_epochs):
  2. best_acc = 0.0
  3. for epoch in range(num_epochs):
  4. model.train()
  5. train_acc = 0.0
  6. train_loss = 0.0
  7. for i, (images, labels) in enumerate(train_loader):
  8. # 若GPU可用,將圖像和標籤移往GPU
  9. if cuda_avail:
  10. images = Variable(images.cuda())
  11. labels = Variable(labels.cuda())
  12. # 清除所有累積梯度
  13. optimizer.zero_grad()
  14. # 用來自測試集的圖像預測類
  15. outputs = model(images)
  16. # 根據實際標籤和預測值計算損失
  17. loss = loss_fn(outputs, labels)
  18. # 傳播損失
  19. loss.backward()
  20. # 根據計算的梯度調整參數
  21. optimizer.step()
  22. train_loss += loss.cpu().data[0] * images.size(0)
  23. _, prediction = torch.max(outputs.data, 1)
  24. train_acc += torch.sum(prediction == labels.data)
  25. # 調用學習率調整函數
  26. adjust_learning_rate(epoch)
  27. # 計算模型在50000張訓練圖像上的準確率和損失值
  28. train_acc = train_acc / 50000
  29. train_loss = train_loss / 50000
  30. # 用測試集評估
  31. test_acc = test()
  32. # 若測試準確率高於當前最高準確率,則保存模型
  33. if test_acc > best_acc:
  34. save_models(epoch)
  35. best_acc = test_acc
  36. # 打印度量
  37. print("Epoch {}, Train Accuracy: {} , TrainLoss: {} , Test Accuracy: {}".format(epoch, train_acc, train_loss,


上面的訓練函數雖然有註釋,但有些地方可能仍然會讓你感到很困惑。我們詳細解釋一下上面到底發生了什麼。


首先我們循環訓練集的加載程序:

  1. for i, (images,labels) in enumerate(train_loader):
  2. 接着,如果可以用GPU,我們就將圖像和標籤移往GPU:
  3. if cuda_avail:
  4. images = Variable(images.cuda())
  5. labels = Variable(labels.cuda())


下一行就是清除當前所有的累積梯度:

optimizer.zero_grad()

這很重要,因爲根據每個批次累積的梯度神經網絡的權重是可以調整的,在每個新的批次內梯度必須重新設置爲0,因此之前批次中的圖像不會將梯度傳播入新的批次。


在接下來的步驟中,我們將圖像傳入模型中。模型會返回預測值,然後我們將預測值和實際標籤輸入損失函數中。


我們調用 loss.backward() 來傳播梯度,然後根據傳播的梯度調用 optimizer.step() 來修正模型的參數。


這些就是訓練的主要步驟。

剩餘的代碼用於計算度量:

  1. train_loss += loss.cpu().data[0] * images.size(0)
  2. _, prediction = torch.max(outputs.data, 1)
  3. train_acc += torch.sum(prediction == labels.data)


這裏我們檢索實際損失,然後獲取最大預測類。最後,我們將所有批次中的正確預測值相加,把所得值添加入整個 train_acc 中。


更重要的是,我們會一直追蹤最高的準確率,如果當前測試準確率高於我們的最好成績,我們就調用保存模型的函數。


GitHub 完整代碼地址:


gist.github.com/johnola


運行此代碼 35 個週期後,你應該會得到超過 90% 的準確率。



用保存的模型進行推斷

模型經過訓練後,就可以用來對新的圖像進行推斷。


執行推斷過程的步驟如下:

  • 定義和初始化你在訓練階段構造的同一模型
  • 將保存的檢查點加載到模型中
  • 從文件系統中選擇一張圖像
  • 讓圖像通過模型,檢索最高預測值
  • 將預測的類數目轉換爲類名


我們用具有預訓練的 ImageNet 權重的 Squeeze 模型來解釋一下。它幾乎能讓我們選擇任何圖形,並獲取圖像的預測值。


Torchvision 提供預定義模型,涵蓋大部分主流架構。

首先,導入所有需要的包和類,創建Squeezenet模型的實例,

  1. # 導入需要的包
  2. import torch
  3. import torch.nn as nn
  4. from torchvision.transforms import transforms
  5. from torch.autograd import Variable
  6. from torchvision.models import squeezenet1_1
  7. import requests
  8. import shutil
  9. from io import open
  10. import os
  11. from PIL import Image
  12. import json
  13. model = squeezenet1_1(pretrained=True)
  14. model.eval()



注意,在上面的代碼中,通過將pretrained設爲True,Squeezenet模型在你首次運行函數時就會被下載。模型的大小隻有4.7 MB。


接着,創建一個預測函數,如下:

  1. def predict_image(image_path):
  2. print("Prediction in progress")
  3. image = Image.open(image_path)
  4. # Define transformations for the image, should (note that imagenet models are trained with image size 224)
  5. transformation = transforms.Compose([
  6. transforms.CenterCrop(224),
  7. transforms.ToTensor(),
  8. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  9. ])
  10. # 預處理圖像
  11. image_tensor = transformation(image).float()
  12. # 額外添加一個批次維度,因爲PyTorch將所有的圖像當做批次
  13. image_tensor = image_tensor.unsqueeze_(0)
  14. if torch.cuda.is_available():
  15. image_tensor.cuda()
  16. # 將輸入變爲變量
  17. input = Variable(image_tensor)
  18. # 預測圖像的類
  19. output = model(input)
  20. index = output.data.numpy().argmax()
  21. return index


上面的代碼包含了我們在訓練和評估模型階段所用的同樣組件。可以查看上面代碼中的註釋。


最後,在主函數中進行預測,我們從網上下載一張圖像,保存在硬盤上。我們同樣下載將所有類索引映射爲實際類名的類映射。這是因爲我們的模型會根據預測類名的編碼方式,返回預測類的索引,然後從索引-類映射中檢索實際的類名。


在這之後,我們用保存的圖像運行預測函數,用保存的類映射獲取正確的類名。

  1. if __name__ == "__main__":
  2. imagefile = "image.png"
  3. imagepath = os.path.join(os.getcwd(), imagefile)
  4. # Donwload image if it doesn't exist
  5. if not os.path.exists(imagepath):
  6. data = requests.get(
  7. "https://github.com/OlafenwaMoses/ImageAI/raw/master/images/3.jpg", stream=True)
  8. with open(imagepath, "wb") as file:
  9. shutil.copyfileobj(data.raw, file)
  10. del data
  11. index_file = "class_index_map.json"
  12. indexpath = os.path.join(os.getcwd(), index_file)
  13. # Donwload class index if it doesn't exist
  14. if not os.path.exists(indexpath):
  15. data = requests.get('https://github.com/OlafenwaMoses/ImageAI/raw/master/imagenet_class_index.json')
  16. with open(indexpath, "w", encoding="utf-8") as file:
  17. file.write(data.text)
  18. class_map = json.load(open(indexpath))
  19. # run prediction function annd obtain prediccted class index
  20. index = predict_image(imagepath)
  21. prediction = class_map[str(index)][1]
  22. print("Predicted Class ", prediction)


這是推斷過程的完整代碼:

  1. # Import needed packages
  2. import torch
  3. import torch.nn as nn
  4. from torchvision.transforms import transforms
  5. import matplotlib.pyplot as plt
  6. import numpy as np
  7. from torch.autograd import Variable
  8. from torchvision.models import squeezenet1_1
  9. import torch.functional as F
  10. import requests
  11. import shutil
  12. from io import open
  13. import os
  14. from PIL import Image
  15. import json
  16. """ Instantiate model, this downloads tje 4.7 mb squzzene the first time it is called.
  17. To use with your own model, re-define your trained networks ad load weights as below
  18. checkpoint = torch.load("pathtosavemodel")
  19. model = SimpleNet(num_classes=10)
  20. model.load_state_dict(checkpoint)
  21. model.eval()
  22. """
  23. model = squeezenet1_1(pretrained=True)
  24. model.eval()
  25. def predict_image(image_path):
  26. print("Prediction in progress")
  27. image = Image.open(image_path)
  28. # Define transformations for the image, should (note that imagenet models are trained with image size 224)
  29. transformation = transforms.Compose([
  30. transforms.CenterCrop(224),
  31. transforms.ToTensor(),
  32. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  33. ])
  34. # Preprocess the image
  35. image_tensor = transformation(image).float()
  36. # Add an extra batch dimension since pytorch treats all images as batches
  37. image_tensor = image_tensor.unsqueeze_(0)
  38. if torch.cuda.is_available():
  39. image_tensor.cuda()
  40. # Turn the input into a Variable
  41. input = Variable(image_tensor)
  42. # Predict the class of the image
  43. output = model(input)
  44. index = output.data.numpy().argmax()
  45. return index
  46. if __name__ == "__main__":
  47. imagefile = "image.png"
  48. imagepath = os.path.join(os.getcwd(), imagefile)
  49. # Donwload image if it doesn't exist
  50. if not os.path.exists(imagepath):
  51. data = requests.get(
  52. "https://github.com/OlafenwaMoses/ImageAI/raw/master/images/3.jpg", stream=True)
  53. with open(imagepath, "wb") as file:
  54. shutil.copyfileobj(data.raw, file)
  55. del data
  56. index_file = "class_index_map.json"
  57. indexpath = os.path.join(os.getcwd(), index_file)
  58. # Donwload class index if it doesn't exist
  59. if not os.path.exists(indexpath):
  60. data = requests.get('https://github.com/OlafenwaMoses/ImageAI/raw/master/imagenet_class_index.json')
  61. with open(indexpath, "w", encoding="utf-8") as file:
  62. file.write(data.text)
  63. class_map = json.load(open(indexpath))
  64. # run prediction function annd obtain prediccted class index
  65. index = predict_image(imagepath)
  66. prediction = class_map[str(index)][1]
  67. print("Predicted Class ", prediction)


上面所用的樣本圖像就是下面這張:



這張照片來自ImageAI。如果你想用自己搭建的網絡進行推斷,比如我們前面搭建的SimpleNet,你只需替換模型的加載部分:

  1. checkpoint = torch.load("pathtosavemodel")
  2. model = SimpleNet(num_classes=10)
  3. model.load_state_dict(checkpoint)
  4. model.eval()


注意,如果你的模型使用ImageNet訓練的,那麼你的num_classes必須爲1000而不是10.


代碼的所有其它部分維持一致,只有一點不同——如果我們以使用CIFAR10訓練的模型進行預測,那麼在轉換中,要將transforms.CenterCrop(224)改變爲transforms.Resize(32)。


不過,如果你的模型是用ImageNet訓練的,就不用改了。


結語

本文我們介紹瞭如何用PyTorch搭建一個圖像分類器,以及如何用訓練後的模型對其它數據做出預測。


關於PyTorch和TensorFlow的不同之處,可以參考我們的這篇文章:

zhuanlan.zhihu.com/p/37


參考資料:
heartbeat.fritz.ai/basi


發佈了27 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章