PyTorch進階之路(三):使用logistic迴歸實現影象分類

選自Medium

作者:

Aakash N S

Aakash N S

機器之心編譯

參與:

前段時間,機器之心已經編譯介紹了「PyTorch:Zero to GANs」系列的前兩篇文章,參閱《PyTorch 進階之路:一、二》,其中講解了張量、梯度、線性迴歸和梯度下降等基礎知識。本文是該系列的第三篇,將介紹如何使用 logistic 迴歸實現影象分類。

在本教程中,我們將使用我們已有的關於 線性迴歸的知識來求解一類非常不同的問題:影象分類。我們將使用著名的 MNIST 手寫數字資料庫作為我們的訓練資料集。其中含有 28×28 畫素的灰度手寫數字影象(0 到 9),並且每張影象都帶有指示該影象的數字的標籤。下面是一些來自該資料集的樣本:

PyTorch進階之路(三):使用logistic迴歸實現影象分類

圖源:http://yann。lecun。com/exdb/mnist/

Panda

如果你想一邊閱讀一邊執行程式碼,你可以透過下面的連結找到本教程的 Jupyter Notebook:

https://jvn。io/aakashns/a1b40b04f5174a18bd05b17e3dffb0f0

你可以克隆這個筆記,使用 conda安裝必要的依賴包,然後透過在終端執行以下命令來啟動 Jupyter:

pip install jovian ——upgrade # Install the jovian library

jovian clone a1b40b04f5174a18bd05b17e3dffb0f0 # Download notebook

cd 03-logistic-regression # Enter the created directory

conda env update # Install the dependencies

conda activate 03-logistic-regression # Activate virtual env

jupyter notebook # Start Jupyter

如果你的 conda 版本更舊一些,你也許需要執行 sourceactivate 03-logistic-regression 來啟用環境。對以上步驟的更詳細的解釋可參閱本教程的前一篇文章。

Panda

我們首先匯入 torch 和 torchvision。torchvision 包含一些用於操作影象資料的實用程式。其中還有輔助工具類,可用於自動下載和匯入 MNIST 等常用資料集。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

第一次執行該語句時,資料會被下載到筆記本旁邊的 data/ 目錄並建立一個 PyTorchDataset。在後續執行時,因為資料已經下載完成,所以這個下載步驟會跳過。我們檢查一下資料集的大小:

這個資料集中有 60000 張可用於訓練模型的影象。另外還有一個額外的測試集,包含 10000 張影象;你可以透過向 MNIST 類傳遞 train=False 來建立它。

我們看看訓練集的一個樣本元素:

這是一個數據對——包含一張 28×28 的影象和一個標籤。這個影象是PIL。Image。Image 類的一個物件,而 PIL。Image。Image 又是 Python 影象處理庫 Pillow 的一分子。我們可以使用 matplotlib在 Jupyter 中檢視影象,事實上這是 Python 的資料科學繪圖製圖庫。

除了匯入 matplotlib,我們還加上了一個特殊的語句 %matplotlib inline,這是為了指示 Jupyter 我們希望在筆記內繪圖。如果沒有這一行,Jupyter 將會以彈窗形式展示影象。以 % 開頭的指令被稱為 IPython 魔法命令,可用於配置 Jupyter 自身的行為。你可在下列連結找到完整的魔法命令列表:

https://ipython。readthedocs。io/en/stable/interactive/magics。html

我們試試檢視幾張資料集中的影象。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

PyTorch進階之路(三):使用logistic迴歸實現影象分類

很明顯這些影象的尺寸很小,有時候甚至人眼都難以辨認具體數字。但看看這些影象是有用的,而我們目前只有一個問題:PyTorch 不知道如何處理這些影象。我們需要將這些影象轉換成張量。要做到這一點,我們可以在建立資料集時指定一個變換。

在載入影象時,PyTorch 資料集讓我們可以指定一個或多個應用於這些影象的變換函式。torchvision。transforms包含很多這種預定義的函式,而我們將使用 ToTensor 變換將這些影象轉換成 PyTorch 張量。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

現在影象轉換成了 1×28×28 的張量。第一個維度用於跟蹤顏色通道。因為 MNIST 資料集中的影象是灰度影象,所以只有一個通道。某些資料集的影象有顏色,這時會有三個通道:紅綠藍(RGB)。我們來看看這個張量中的一些樣本值:

PyTorch進階之路(三):使用logistic迴歸實現影象分類

這些值的取值範圍是 0 到 1,其中 0 表示黑色,1 表示白色,介於兩者之間的值表示不同程度的灰。我們還可以使用 plt。imshow 將張量繪製成影象形式。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

注意,我們只需要將這個 28×28 的矩陣傳遞給 plt。imshow,而不帶顏色通道。我們也可以傳遞一個顏色分佈圖(cmap=‘gray’),以指示我們想得到灰度影象。

系統設定

在構建真實世界的機器學習模型時,一種常見做法是將資料分為三部分:

訓練集——用於訓練模型,即計算損失以及使用梯度下降調整模型的權重

驗證集——用於在訓練時驗證模型,調整超引數(學習速率等)以及選取最佳版本的模型

測試集——用於比較不同的模型或不同型別的建模方法,並報告模型的最終準確度

MNIST 資料集有 60000 張訓練影象和 10000 張測試影象。這個測試集經過了標準化,因此不同的研究者可以基於同樣的影象集報告他們的模型的結果。其中沒有預定義的驗證集,我們必須手動地將那60000 張影象分為訓練資料集和驗證資料集。

我們定義一個函式,讓其可以隨機地選取一定份額的影象作為驗證集。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

split_indices 可隨機地混洗陣列索引 0,1,。。n-1,並從中分出所需的比例作為驗證集。在建立驗證集之前混洗索引是很重要的,因為訓練影象通常是按目標標籤排序的,即先是 0 的影象,然後是 1 的影象,2 的影象……如果我們選擇最後 20% 的影象作為驗證集,則該驗證集將僅包含 8 和 9 的影象,而訓練集中又沒有 8 和 9 的影象。使用這樣的訓練集是不可能得到好模型的,也不可能在驗證集(或真實資料集)上取得好的表現。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

我們已經隨機混洗了索引,並選擇了一小部分(20%)作為驗證集。現在我們可以使用 SubsetRandomSampler 為它們中的每一個建立PyTorch 資料載入器,它可從給定的索引列表隨機地採用元素,建立分批資料。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

探索資料

現在我們已經準備好了資料載入器,我們可以定義我們的模型了。

正如我們線上性迴歸時做的那樣,我們可以使用 nn。Linear 建立模型,而不是手動地定義和初始化這些矩陣。

因為 nn。Linear需要每個訓練樣本都是一個向量,所以每個 1×28×28 的影象張量都需要展平成大小為 784(28×28)的向量,之後再傳遞給模型。

每張影象的輸出都是一個大小為 10 的向量,該向量的每個元素都指示了該影象屬於某個具體標籤(0 到 9)的機率。為影象預測得到的標籤即為機率最高的標籤。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

當然,在引數數量方面,這個模型比我們之前的模型要大很多。我們看看其中的權重和偏置。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

PyTorch進階之路(三):使用logistic迴歸實現影象分類

儘管這裡總共有 7850 個引數,但概念上沒有什麼變化。我們試試使用我們的模型生成一些輸出。我們將從我們的資料集取出第一批 100 張影象,再將它們傳遞進我們的模型。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

這會有一個報錯,因為我們的輸入資料形狀不對。我們的影象的形狀是 1×28×28,但我們需要它們是大小為 784的向量,也就是說我們需要將其展平。我們將使用張量的 。reshape方法,這讓我們可以有效地將每張影象「看作是」展平的向量,同時又不會真正改變底層資料。

為了將這種額外的功能納入我們的模型中,我們需要透過擴充套件 PyTorch 的 nn。Module 類定義一個定製模型。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

在 __init__ 構造器方法中,我們使用 nn。Linear 對權重和偏置進行了例項化。在 forward 方法(在我們將一批輸入傳入模型時呼叫)中,我們將輸入張量展開,然後將其傳遞給 self。linear。

xb。reshape(-1, 28*28) 的意思是我們想要 xb 張量的二維檢視,其中沿第二個維度的長度是28×28(即 784)。。reshape 的一個引數可以設定為 -1(在這裡是第一個維度),以讓PyTorch 根據原始張量的形狀自動找到它。

注意這個模型不再有 。weight 和 。bias 屬性(因為它們現在是在。linear 屬性內),但有可返回包含權重和偏置的列表的 。parameters 方法,並且可被 PyTorch最佳化器使用。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

我們新的定製模型可以和之前一樣使用。我們來看看效果:

PyTorch進階之路(三):使用logistic迴歸實現影象分類

對於這 100 張輸入影象中的每一張,我們都會得到 10 個輸出,每個輸出對應一個類別。正如之前討論的那樣,這些輸出表示機率,因此每個輸出行的每個元素都應該在 0 到 1 之間且總和為 1,但很顯然這裡的情況並非如此。

為了將這些輸出行轉換成機率,我們可以使用 softmax 函式,其公式如下:

PyTorch進階之路(三):使用logistic迴歸實現影象分類

來自:Udacity

我們首先將輸出行中的每個元素 yi 替換成 e^yi,使得所有元素為正,然後我們用每個元素除以所有元素的和以確保所得結果之和為1。

儘管實現

softmax

函式很容易(你應該試試看!),但我們將使用 PyTorch 內提供的實現,因為它能很好地處理多維張量(在這裡是輸出行的列表)。

softmax 函式包含在 torch。nn。functional 軟體包中,並且需要我們指定 softmax 應用的維度。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

最後,我們只需選擇每個輸出行中機率最高的元素的索引,確定每張影象的預測標籤即可。這可使用torch。max 完成,它會返回沿張量的一個特定維度的最大元素和該最大元素的索引。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

上面打印出的數字是第一批訓練影象的預測標籤。我們將其與真實標籤比較一下。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

很明顯,預測標籤和真實標籤完全不同。這是因為我們開始時對權重和偏置進行了隨機初始化。我們需要訓練模型,使之能做出更好的預測,也就是要使用梯度下降調整權重。

訓練資料集和驗證資料集

和線性迴歸時一樣,我們需要一種評估模型表現的方法。一種自然的做法是找到標籤被正確預測的百分比,也就是預測的準確度。

== 運算子執行的是兩個同樣形狀的張量的逐元素的比較,並會返回一個同樣形狀的張量,其中0對應相等的元素,1 對應不相等的元素。將結果傳遞給 torch。sum,會返回預測正確的標籤的數量。最後,我們將其除以影象總數,即可得到準確度。

我們先計算一下在第一批資料上當前模型的準確度。很顯然,可以預料結果很差。

儘管準確度對我們(人類)而言是很好的評估模型的方法,但卻不能用作我們使用梯度下降最佳化模型的損失函式,原因如下:

這是不可微分的函式。torch。max 和 == 都是非連續的和不可微分的運算,因此我們不能使用準確度來計算與權重和偏置有關的梯度。

它沒有考慮模型預測的實際機率,所以不能提供足夠的反饋以實現逐漸遞進的改進。

由於這些原因,準確度雖然是很好的分類評估指標,但卻不是好的損失函式。分類問題常用的一種損失函式是交叉熵,它的公式如下:

PyTorch進階之路(三):使用logistic迴歸實現影象分類

儘管看起來複雜,但實際上相當簡單:

對於每個輸出行,選取正確標籤的預測機率。比如,如果一張影象的預測機率是 [0。1,0。3, 0。2, 。。。],而正確標籤是 1,則我們選取對應的元素 0。3 並忽略其餘元素。

然後,求所選機率的對數。如果機率高(即接近 1),則其對數是非常小的負值,接近 0。 如果機率低(即接近0),則其對數是非常大的負值。我們可以將結果乘上 -1,那麼糟糕預測的損失就是一個較大的正值了。

最後,在所有輸出行上取交叉熵的平均,得到一批資料的整體損失。

不同於準確度,交叉熵是一種連續且可微分的函式,並且能為模型的逐步改進提供良好的反饋(正確標籤的機率稍微高一點就會讓損失低一點)。這是很好的損失函式選擇。

PyTorch 提供了一種有效的且對張量友好的交叉熵實現,這是torch。nn。functional 軟體包的一分子。此外,它還能內部執行softmax,所以我們可以不將它們轉換成機率地直接傳入模型的輸出。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

因為交叉熵是正確標籤的預測機率的負對數在所有訓練樣本上的平均,所以解讀所得數字(比如 2。23)的一種方式是將 e^-2。23(大約為 0。1)視為正確標籤的平均的預測機率。損失越低,模型越好。

模型

我們將使用 optim。SGD 最佳化器來在訓練過程中更新權重和偏置,但會使用更高的學習率 1e-3。

批大小和學習率等引數需要在訓練機器學習模型之前選取,它們也被稱為超引數。要在合理的時間內訓練出一個準確的模型,選擇合適的超引數是至關重要的,這也是一個活躍的研究和實驗領域。你可以隨意嘗試不同的學習率,看看其對訓練過程的影響。

評估指標和損失函式

現在我們已經定義好了資料載入器、模型、損失函式和最佳化器;萬事俱備,就等訓練了。這個訓練過程幾乎與線性迴歸的完全一樣。但是,我們需要給我們之前定義的fit 函式配置引數,以在每輪 epoch 結束時使用驗證集評估模型的準確度和損失。

我們先定義一個函式 loss_batch,其作用是:

計算一批資料的損失

如果提供了最佳化器,則選擇性地執行梯度下降更新步驟

使用預測結果和實際目標選擇性地計算一個指標(比如準確度)

PyTorch進階之路(三):使用logistic迴歸實現影象分類

最佳化器是一個可選引數,作用是確保我們可以重複使用 loss_batch,以便在驗證集上計算損失。我們還可返回批的長度作為結果的一部分,因為在為整個資料集組合損失/指標時,這會很有用。

接下來我們定義一個函式 evaluate,用於計算在驗證集上的整體損失(如果有提供,也計算一個指標)。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

如果該函式的作用不很清晰直觀,你可以試試分開單獨執行每個語句,然後看看結果。我們還希望重新定義accuracy 以直接操作整個輸出批,因此我們可以將其用作 fit 中的一個指標。

注意,我們無需將 softmax 用於輸出,因為它不會改變結果的相對順序。這是因為 e^x 是一個遞增函式(即如果 y1 > y2,則 e^y1 > e^y2),並且在對值求平均得到 softmax 之後同樣成立。

我們看看使用初始的權重和偏置的模型在驗證集上的表現。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

初始準確度低於 10%,這樣的結果對隨機初始化的模型而言是可以預期的(因為隨機猜測時,得到正確標籤的可能性是十分之一)。還要注意,我們使用了 。format 方法,讓訊息字串僅列印至小數點後四位數。

我們可以使用 loss_batch 和 evaluate 相當輕鬆地定義fit 函式。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

我們現在可以訓練模型了。試試訓練 5 epoch,看看結果。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

結果很不錯!僅僅 5 epoch 訓練,我們的模型就在驗證集上達到了超過 80% 的準確度。我們多訓練幾個 epoch,看看能不能進一步提升結果。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

儘管多訓練幾個 epoch 確實能讓準確度持續提升,但每 epoch 提升的程度也越來越小。使用折線圖能更清楚地看出這一點。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

從上圖可以相當清楚地看到,即使訓練很長時間,該模型可能也無法超越 90% 的準確度閾值。一個可能的原因是學習率太高了。有可能模型的引數在損失最低的最優引數集周圍跳變。你可以嘗試降低學習率,然後再訓練看看。

更可能的原因是模型本身不夠強大。還記得我們的初始假設嗎?我們假設其輸出(在這個案例中是類別機率)是輸入(畫素強度)的線性函式,是透過執行輸入與權重矩陣的矩陣乘法再加上偏置而得到的。這是相當弱的假設,因為影象中的畫素強度與其表示的數字之間的關係可能不是線性的。儘管這樣的假設在MNIST 這樣的簡單資料集上表現不錯(我們得到了 85% 的準確度),但對於識別日常物體和動物等複製任務而言,我們需要更復雜精細的模型,才能近似影象畫素和標籤之間的非線性關係。

最佳化器

儘管我們現在已經跟蹤了模型的整體準確度,但也可瞭解一下模型在某些樣本影象上的表現。我們使用預定義的10000 張影象的測試資料集中的影象測試一下我們的模型。我們先使用ToTensor 變換重新建立測試資料集。

下面是一張來自該資料集的樣本影象。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

我們定義一個輔助函式 predict_image,使其返回單張影象張量的預測標籤。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

img。unsqueeze 會直接在1x28x28 張量的起始處增加另一個維度,使其成為一個 1×1×28×28 的張量,模型將其視為一個包含單張影象的批。

我們用一些影象來試試看。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

PyTorch進階之路(三):使用logistic迴歸實現影象分類

PyTorch進階之路(三):使用logistic迴歸實現影象分類

PyTorch進階之路(三):使用logistic迴歸實現影象分類

識別模型表現較差的地方有助於我們改進模型,具體做法包括收集更多訓練資料、增加/降低模型的複雜度、修改超引數。

最後,我們看看模型在測試集上的整體損失和準確度。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

可以預計準確度/損失結果與在驗證集上時類似。如果不一致,我們可能需要與測試集(通常來自真實世界資料)的資料和分佈近似的更好的驗證集。

訓練模型

因為我們已經訓練了模型很長時間並且實現了不錯的準確度,所以為了之後能複用該模型以及避免重新開始再訓練,我們可以將權重和偏置矩陣儲存到磁碟。以下是儲存模型的方法。

。state_dict 方法會返回一個 OrderedDict,其中包含對映到該模型的適當屬性的所有權重和偏置。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

要載入該模型的權重,我們可以例項化 MnistModel 類的一個新物件,並使用 。load_state_dict 方法。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

就像進行完整性檢查一樣,我們在測試集上驗證一下該模型是否有與之前一樣的損失和準確度。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

使用單張影象進行測試

最後,我們可以使用 jovian 庫儲存和提交我們的成果。

PyTorch進階之路(三):使用logistic迴歸實現影象分類

jovian 會將筆記上傳到 https://jvn。io,並會獲取其 Python 環境併為該筆記建立一個可分享的連結。你可以使用該連結共享你的成果,讓任何人都能使用jovian 克隆命令輕鬆復現它。jovian 還有一個強大的評論介面,讓你和其他人都能討論和點評你的筆記的各個部分。

儲存和載入模型

我們在本教程中建立了一個相當複雜的訓練和評估流程。下面列出了我們介紹過的主題:

用 PyTorch 處理影象(使用 MNIST 資料集)

將資料集分成訓練集、驗證集和測試集

透過擴充套件 nn。Module 類建立有自定義邏輯的 PyTorch 模型

使用 softmax 解讀模型輸出,並選取預測得到的標籤

為分類問題選取優良的評估指標(準確度)和損失函式(交叉熵)

設定一個訓練迴圈,並且也能使用驗證集評估模型

在隨機選取的樣本上手動地測試模型

儲存和載入模型檢查點以避免從頭再訓練

其中有很多地方可以試驗,我建議你使用 Jupyter 的互動性質試試各種不同的引數。這裡有一些想法:

試試更小或更大的驗證集,看對模型有何影響。

試試改變學習率,看能否用更少的 epoch 實現同樣的準確度。

試試改變批大小,看批大小過高或過低時會怎樣。

修改 fit 函式,以跟蹤在訓練集上的整體損失和準確度,將其與驗證損失/準確度比較一下。你能解釋結果更高或更低的原因嗎?

使用資料的一個小子集進行訓練,看是否能達到相近的準確度?

試試為不同的資料集構建模型,比如 CIFAR10 或 CIFAR100 資料集。

下面是一些進一步閱讀的參考資料:

想學習相關的數學知識?參考 Coursera 上的機器學習課程:https://www。coursera。org/lecture/machine-learning/classification-wlPeP。本系列教程使用的大部分影象都取自該教程。

本筆記中定義的訓練迴圈的靈感來自 FastAI 開發筆記:https://github。com/fastai/fastai_docs/blob/master/dev_nb/001a_nn_basics。ipynb,其中包含豐富的其它有用的知識(如果你能閱讀和理解程式碼)。

深入理解 softmax和交叉熵,請參閱 DeepNotes 上的這篇部落格:https://deepnotes。io/softmax-crossentropy。

想知道驗證集為何很重要以及如何建立一個好驗證集嗎?參閱 FastAI 的 RachelThomas 的這篇部落格:https://www。fast。ai/2017/11/13/validation-sets。

原文連結:https://medium。com/jovian-io/image-classification-using-logistic-regression-in-pytorch-ebb96cc9eb79

提交和上傳筆記