- PyTorch深度學習實戰
- (美)伊萊·史蒂文斯 (意)盧卡·安蒂加 (德)托馬斯·菲曼
- 4962字
- 2022-02-25 15:14:39
2.1 一個識別圖像主體的預訓練網絡
作為我們對深度學習的第1次嘗試,我們將運行一個非常先進的深度神經網絡,它是在對象識別任務中預先訓練過的。有許多預先訓練過的網絡可以通過源代碼庫訪問。研究人員通常會在發表論文的同時發布他們的源代碼,而且代碼通常帶有通過在參考數據集上訓練模型而獲得的權重。例如,使用其中一個模型可以使我們輕松地為下一個Web服務配備圖像識別功能。
我們即將在這里探討的預訓練網絡是已經在ImageNet數據集的子集上訓練過的。ImageNet是一個由斯坦福大學維護的包含1400多萬幅圖像的非常大的數據集。所有圖像都用來自WordNet數據集的名詞層次結構標記,而WordNet數據集又是一個大型的英語詞匯數據庫。
ImageNet數據集和其他公共數據集一樣,源于學術競賽。競賽是機構和公司的研究人員經?;ハ嗵魬鸬膫鹘y競爭場所。其中,ImageNet大規模視覺識別挑戰賽(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)自2010年成立以來廣受歡迎。這個特殊的競賽基于幾個任務,每年可以有所不同,如圖像分類(識別圖像類別)、目標定位(在圖像中識別物體的位置)、目標檢測(識別和標記圖像中的對象)、場景分類(對圖像中的情形進行分類)和場景分析(將圖像分割成與語義類別相關的區域,如牛、房子、奶酪和帽子等)等。
具體來說,圖像分類任務包括獲取一個輸入圖像,并從1000個類別中生成5個標簽的列表,列表按置信度排序,描述圖像的內容。
ILSVRC的訓練集由120萬幅圖像組成,每幅圖像用1000個名詞中的一個來標記,如“dog”,這些名詞被稱為圖像的類(class)。從這個意義上講,我們將交替使用術語標簽(lable)和類。在圖2.1中,我們可以看到一些ImageNet圖像。

圖2.1 ImageNet圖像的一個小樣本
我們最終能夠拍攝自己的圖像并將其輸入預訓練模型中,如圖2.2所示。模型將為該圖像生成一個預測的標簽列表,我們可以檢查該列表以查看模型認為我們的圖像是什么。模型對有些圖像的預測是準確的,而有些則不是。

圖2.2 推理的過程
輸入的圖像將首先被預處理成一個多維數組類torch.Tensor的實例。它是一個具有高度和寬度的RGB圖像,因此這個張量將有3個維度:RGB通道和2個特定大小的空間圖像維度。我們將在第3章詳細討論張量是什么,但現在,可以把它想象成一個浮點數的向量或矩陣。我們的模型將把處理過的輸入圖像傳入預先訓練過的網絡中,以獲得每個類的分數。根據權重,最高的分數對應最可能的類。然后將每個類一對一地映射到標簽上。該輸出被包含在一個含有1000個元素的torch.Tensor張量中,每個元素表示與該類相關的分數。在做這些之前,我們需要先了解網絡本身,看看它的底層結構,并了解如何在模型使用數據之前準備數據。
2.1.1 獲取一個預先訓練好的網絡用于圖像識別
如前所述,現在我們將使用在ImageNet上訓練過的網絡。首先,讓我們看看TorchVision項目,該項目包含一些表現優異的、關于計算機視覺的神經網絡架構,如AlexNet、ResNet和Inception-v3等。它還可以方便地訪問像ImageNet這樣的數據集和其他工具,以加快PyTorch的計算機視覺應用程序運行的速度。我們將在本書做進一步探討?,F在,讓我們加載并運行這2個網絡:首先是AlexNet,它是在圖像識別方面早期具有突破性的網絡之一;然后是殘差網絡,簡稱ResNet,它在2015年的ILSVRC中獲勝。如果在第1章你還沒有啟動和運行PyTorch,現在是時候做這些事情了。
在torchvision.models中可以找到預定義的模型(參見源碼code/p1ch2/2_pre_trained_networks.ipynb):
# In[1]
from torchvision import models
我們可以看看實際的模型:
# In[2]
dir(models)
# Out[2]:
['AlexNet',
'DenseNet',
'Inception3',
'ResNet',
'SqueezeNet',
'VGG',
...
'alexnet',
'densenet',
'densenet121',
...
'resnet',
'resnet101',
'resnet152',
...
]
首字母大寫的名稱指的是實現了許多流行模型的Python類,它們的體系結構不同,即輸入和輸出之間操作的編排不同。首字母小寫的名稱指的是一些便捷函數,它們返回這些類實例化的模型,有時使用不同的參數集。例如,resnet101表示返回一個有101層網絡的ResNet實例,resnet152表示返回一個有152層網絡的ResNet實例,以此類推。下面我們將注意力轉向AlexNet。
2.1.2 AlexNet
AlexNet架構在2012年的ILSVRC中以較大的優勢勝出,前5名的測試錯誤率(也就是說,正確的標簽必須在前5名中)為15.4%。相比之下,沒有深度網絡的次好作品僅占26.2%。這是計算機視覺歷史上的一個關鍵時刻:此刻,社區開始意識到深度學習在視覺任務中的潛力。隨之而來的是不斷的改進,更現代的架構和訓練方法使得前5名的錯誤率低至3%。
按照現在的標準,與先進的模型相比,AlexNet是一個相當小的網絡。但它非常適合讓我們著眼于神經網絡的例子,通過它我們可以學習在一個新的圖像上運行一個訓練好的模型。
AlexNet架構如圖2.3所示,并不是說我們已理解了AlexNet架構的所有組成部分,但是我們可以預見該架構的幾個方面。首先每個塊包含一系列乘法和加法運算函數,以及我們將在第5章中介紹的一些其他函數。我們可以將每個塊看作一個過濾器,一個接收一幅或多幅圖像作為輸入并生成其他圖像作為輸出的函數。這種做法是在訓練期間,基于訓練時所看到的例子和所期望的輸出決定的。

圖2.3 AlexNet架構
在圖2.3中,輸入圖像從左側進入并依次經過5個過濾器,每個過濾器生成一些輸出圖像。經過每個過濾器后,圖像會被縮小。在過濾器堆棧中,最后一個過濾器產生的圖像被排列成一個擁有4096個元素的一維向量,并被分類以產生1000個輸出,每個輸出對應一個類。
為了在輸入圖像上運行AlexNet架構,我們可以創建一個AlexNet類的實例,如下列代碼所示。
# In[3]:
alexnet = models.AlexNet()
此時,alexnet是一個可以運行AlexNet架構的對象?,F在,我們還不需要了解這個架構的細節。alexNet僅是一個不透明的對象,可以像函數一樣調用它。通過向alexnet提供一些精確的輸入數據(我們很快會看到這些輸入數據應該是什么樣的),我們將在網絡中運行一個正向傳播(forward pass)。也就是說,輸入將經過一組神經元,其輸出將被傳遞給下一組神經元,直到得到最后的輸出。實際上,如果我們有一個真實類型的input對象,我們可以使用output=alexnet(input)運行正向傳播。
但如果我們這樣做,我們將通過整個網絡提供的數據來產生輸出……糟糕!這是因為網絡沒有初始化:它的權重,即輸入的相加和相乘所依據的數字沒有經過任何訓練。網絡本身就是一塊白板,或者說是隨機的白板,我們現在要做的就是從頭訓練它或加載之前訓練好的網絡。
至此,我們回到models模塊。我們已經知道首字母大寫的名稱對應實現了許多流行模型的Python類,而首字母小寫的名稱是指用預定義的層和單元數實例化模型的函數,可以選擇性地下載和加載預先訓練好的權重。請注意,使用這些函數沒有什么必要:通過它們只是為了方便地使用與預訓練好的網絡的構建方式相匹配的層和單元來實例化模型。
2.1.3 ResNet
現在我們將使用resnet101來實例化一個具有101層的卷積神經網絡。客觀地說,在2015年ResNet出現之前,在如此深的網絡中達到穩定的訓練是極其困難的。ResNet提出了一個技巧使之變為可能,并在這一年一舉擊敗了好幾個深度學習測試基準。
現在讓我們創建一個網絡實例。我們將傳遞一個參數,指示函數下載resnet101在ImageNet數據集上訓練好的權重。
# In[4]:
resnet = models.resnet101(pretrained=True)
下載期間,我們可以花點兒時間來“欣賞”一下resnet101的4450萬個參數,竟有如此多的參數需要自動優化。
2.1.4 準備運行
通過前面的步驟我們獲得了什么呢?出于好奇,我們來看看resnet101是什么樣子的。我們可以通過輸出返回模型的值的方式來實現這一點。它為我們提供了在圖2.3中看到的相同信息的文本表示,并提供了關于網絡結構的詳細信息。目前可能信息量有些過大,但隨著本書的推進,我們將逐漸理解這段代碼告訴我們的信息。
# In[5]:
resnet
# Out[5]:
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3),
bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True,
track_running_stats=True)
(relu): ReLU(inplace)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1,
ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
...
)
)
(avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
(fc): Linear(in_features=2048, out_features=1000, bias=True)
)
我們在這里看到的是許多模塊(modules),每行一個。請注意,它們與Python模塊沒有任何關系:它們是獨立的操作,是神經網絡的構建模塊。它們在其他深度學習框架中也被稱為層(layers)。
如果我們向下滾動,會看到許多Bottleneck模塊一個接一個地重復出現,總共有101個,包括卷積和其他模塊。這是典型的計算機視覺深度神經網絡的結構:過濾器和非線性函數或多或少地順序級聯,以層(fc)結束,為1000個輸出類(out_features)中的每個類生成分數。
可以像調用函數一樣調用resnet變量,將一幅或多幅圖像作為輸入,并為1000個ImageNet類生成對等數量的分數。然而,在此之前我們必須對輸入的圖像進行預處理,使其大小正確,使其值(顏色)大致處于相同的數值范圍。為此,TorchVision模塊提供了轉換操作,允許我們快速定義基本預處理函數的管道。
# In[6]:
from torchvision import transforms
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)])
在本例中,我們定義了一個預處理函數,將輸入圖像縮放到256×256個像素,圍繞中心將圖像裁剪為224×224個像素,并將其轉換為一個張量,對其RGB分量(紅色、綠色和藍色)進行歸一化處理,使其具有定義的均值和標準差。張量是一種PyTorch多維數組,在本例中,是一個包含顏色、高度和寬度的三維數組。如果我們想讓網絡產生有意義的答案,那么這些轉換就需要與訓練期間向網絡提供的內容相匹配。在7.1.3小節中,當開始制作自己的圖像識別模型時,我們再更深入地討論轉換。
現在我們抓取我們非常喜歡的一幅狗的圖像,如從GitHub代碼庫中下載一張名為bobby.jpg的圖片,對其進行預處理,然后查看ResNet對它識別的結果。我們可以使用一個Python的圖像操作模塊Pillow從本地文件系統加載一幅圖像。
# In[7]:
from PIL import Image
img = Image.open("../data/p1ch2/bobby.jpg")
如果我們使用的是Jupyter Notebook,需要做以下操作來查看圖像,圖像將在下面的<PIL.JpegImagePlugin…>代碼之后顯示。
# In[8]:
img
# Out[8]:
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1280x720 at
0x1B1601360B8>
否則,我們可以調用show()方法,它將彈出一個帶有查看器的窗口,顯示圖2.4所示的圖像:
>>> img.show()

圖2.4 我們非常喜歡的一幅狗的圖像
接下來,我們可以通過預處理管道傳遞圖像:
# In[9]:
img_t = preprocess(img)
然后我們可以按照網絡期望的方式對輸入的張量進行重塑、裁剪和歸一化處理。
# In[10]:
import torch
batch_t = torch.unsqueeze(img_t, 0)
現在可以運行我們的模型了。
2.1.5 運行模型
在深度學習中,在新數據上運行訓練過的模型的過程被稱為推理(inference)。為了進行推理,我們需要將網絡置于eval模式。
# In[11]:
resnet.eval()
# Out[11]:
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3),
bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True,
track_running_stats=True)
(relu): ReLU(inplace)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1,
ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
...
)
)
(avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
(fc): Linear(in_features=2048, out_features=1000, bias=True)
)
如果我們忘記這樣做,那么一些預先訓練過的模型,如批歸一化(Batch Normalization)和Dropout將不會產生有意義的答案,這僅僅是因為它們內部工作的方式?,F在eval設置好了,我們準備進行推理。
# In[12]:
out = resnet(batch_t)
out
# Out[12]:
tensor([[ -3.4803, -1.6618, -2.4515, -3.2662, -3.2466, -1.3611,
-2.0465, -2.5112, -1.3043, -2.8900, -1.6862, -1.3055,
...
2.8674, -3.7442, 1.5085, -3.2500, -2.4894, -0.3354,
0.1286, -1.1355, 3.3969, 4.4584]])
一組涉及4450萬個參數的驚人操作才剛剛開始。最終產生了一個擁有1000個分數的向量,每個ImageNet類對應一個分數,而且這個過程并沒有花費多久時間。
我們現在需要找出得分高的類的標簽,這將告訴我們模型從圖像中得到了什么。如果標簽符合人類對圖像的描述,就太棒了!這就意味著一切正常。如果不是,那么要么是在訓練過程中出現了什么問題,要么是圖像與模型期望的完全不同,以至于模型無法正確處理它,或是存在其他類似的問題。
要查看預測標簽的列表,我們需要加載一個文本文件,按照訓練中呈現給網絡的順序列出標簽,然后我們從網絡中產生最高得分的索引處挑選出標簽。幾乎所有用于圖像識別的模型的輸出形式都與我們即將使用的輸出形式類似。
讓我們為ImageNet數據集類加載一個包含1000個標簽的文件。
# In[13]:
with open('../data/p1ch2/imagenet_classes.txt') as f:
labels = [line.strip() for line in f.readlines()]
此時,我們需要確定與我們之前獲得的out張量中最高分對應的索引。我們可以使用PyTorch的max()函數來做到這一點,它可以輸出一個張量中的最大值以及最大值所在的索引。
# In[14]:
_, index = torch.max(out, 1)
現在我們可以使用索引來訪問標簽。在這里,索引不是一個普通的Python數字,而是一個擁有單元素的一維張量,如tensor([207])。因此我們需要使用index[0]獲得實際的數字作為標簽列表的索引。我們還可以使用torch.nn.functional.softmax()將輸出歸一化到[0,1],然后除以總和。這就給了我們一些大致類似于模型在其預測中的置信度,在本例中,模型有約96%的把握認為它看到的是一只金毛獵犬。
# In[15]:
percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
labels[index[0]], percentage[index[0]].item()
# Out[15]:
('golden retriever', 96.29334259033203)
哦,哪一個好呢?
由于該模型產生了分數,我們還可以找出第2好、第3好等是什么。為此,我們可以使用sort()函數,它將值按升序或降序排列,并提供排序值在原始數組中的索引。
# In[16]:
_, indices = torch.sort(out, descending=True)
[(labels[idx], percentage[idx].item()) for idx in indices[0][:5]]
# Out[16]:
[('golden retriever', 96.29334259033203),
('Labrador retriever', 2.80812406539917),
('cocker spaniel, English cocker spaniel, cocker', 0.28267428278923035),
('redbone', 0.2086310237646103),
('tennis ball', 0.11621569097042084)]
我們看到前4個答案是狗,之后的結果就變得有趣起來。第5個答案是網球,這是因為在圖片中狗的附近有很多的網球,因此模型將狗錯誤地識別成網球,此時,對于模型而言,它認為在這種場景中只有0.1%的概率將網球識別為其他的東西。這是一個很好的例子,說明了人類和神經網絡看待世界在方式上的根本差異,也說明了我們的數據當中很容易混入一些奇怪的、微妙的偏置。
放松一下,我們可以繼續用一些隨機圖像來檢測網絡,看看它會產生什么結果。該網絡成功與否在很大程度上取決于受試者是否在訓練集中表現良好。如果我們提交一幅包含訓練集之外的對象的圖像,網絡很可能會以相當高的可信度得出錯誤的答案。通過實驗了解模型對未知數據的反應是很有用的。
我們剛剛運行的網絡從狗的例子中學會了從大量現實世界的對象中識別狗?,F在我們來看看不同的體系結構如何完成其他類型的任務,從圖像生成開始。