PyTorch 是 Facebook 開發和維護的一個開源的神經網路庫,近來的發展勢頭相當強勁,也有越來越多的開發者為其撰寫教程,本文也是其中之一。這是「PyTorch: Zero to GANs」系列教程的第二篇,介紹了在 PyTorch 中實現線性回歸和梯度下降的基本方法。

選自medium,作者:Aakash N S,機器之心編譯,參與:Panda。

這篇文章將討論機器學習的一大基本演算法:線性回歸。我們將創建一個模型,使其能根據一個區域的平均溫度、降雨量和濕度(輸入變數或特徵)預測蘋果和橙子的作物產量(目標變數)。訓練數據如下:

在線性回歸模型中,每個目標變數的估計方式都是作為輸入變數的一個加權和,另外還會有某個常量偏移(也被稱為偏置量):

yield_apple = w11 * temp + w12 * rainfall + w13 * humidity + b1

yield_orange = w21 * temp + w22 * rainfall + w23 * humidity + b2

可視化地看,這意味著蘋果或橙子的產量是溫度、降雨量或濕度的線性函數或平面函數:

因為我們只能展示三個維度,所以此處沒有給出濕度

線性回歸的「學習」部分是指通過檢視訓練數據找到一組權重(w11、w12…w23)和偏置 b1 和 b2),從而能根據新數據得到準確的預測結果(即使用一個新地區的平均溫度、降雨量和濕度預測蘋果和橙子的產量)。為了得到更好的結果,這個過程會對權重進行許多次調整,其中會用到一種名為「梯度下降」的優化技術。

系統設置

如果你想一邊閱讀一邊運行代碼,你可以通過下面的鏈接找到本教程的 Jupyter Notebook:

jvn.io/aakashns/e556978

你可以克隆這個筆記,安裝必要的依賴包,然後通過在終端運行以下命令來啟動 Jupyter:Share Jupyter notebooks instantly?

jvn.io圖標你可以克隆這個筆記,安裝必要的依賴包,然後通過在終端運行以下命令來啟動 Jupyter:

$ pip install jovian --upgrade # Install the jovian library
$ jovian clone e556978bda9343f3b30b3a9fd2a25012 # Download notebook & dependencies
$ cd 02-linear-regression # Enter the created directory
$ conda env update # Install the dependencies
$ conda activate 02-linear-regression # Activate virtual environment
$ jupyter notebook # Start Jupyter

如果你的 conda 版本更舊一些,你也許需要運行 source activate 02-linear-regression 來激活環境。對以上步驟的更詳細的解釋可參閱本教程的前一篇文章。

首先我們導入 Numpy 和 PyTorch:

訓練數據

訓練數據可以使用兩個矩陣表示:輸入矩陣和目標矩陣;其中每個矩陣的每一行都表示一個觀察結果,每一列都表示一個變數。

我們已經分開了輸入變數和目標變數,因為我們將分別操作它們。另外,我們創建的是 numpy 數組,因為這是常用的操作訓練數據的方式:將某些 CSV 文件讀取成 numpy 數組,進行一些處理,然後再將它們轉換成 PyTorch 張量,如下所示:

從頭開始構建線性回歸模型

權重和偏置(w11、w12…w23、b1 和 b2)也可表示成矩陣,並初始化為隨機值。w 的第一行和 b 的第一個元素用於預測第一個目標變數,即蘋果的產量;對應的第二個則用於預測橙子的產量。

torch.randn 會創建一個給定形狀的張量,其中的元素隨機選取自一個均值為 0 且標準差為 1 的正態分布。

該模型實際上就是一個簡單的函數:執行輸入 x 和權重 w 的矩陣乘法,再加上偏置 b(每個觀察都會重複該計算)。

我們可將該模型定義為:

@ 表示 PyTorch 中的矩陣乘法,.t 方法會返回一個張量的轉置。

通過將輸入數據傳入模型而得到的矩陣是目標變數的一組預測結果。

接下來比較我們的模型的預測結果與實際的目標。

可以看到,我們的模型的預測結果與目標變數的實際值之間差距巨大。很顯然,這是由於我們的模型的初始化使用了隨機權重和偏置,我們可不能期望這些隨機值就剛好合適。

損失函數

在我們改進我們的模型之前,我們需要一種評估模型表現優劣的方法。我們可以使用以下方法比較模型預測和實際目標:

  • 計算兩個矩陣(preds 和 targets)之間的差異;
  • 求這個差異矩陣的所有元素的平方以消除其中的負值;
  • 計算所得矩陣中元素的平均值。

結果是一個數值,稱為均方誤差(MSE)。

torch.sum 返回一個張量中所有元素的和,.numel 方法則返回一個張量中元素的數量。我們來計算一下我們模型的當前預測的均方誤差:

我們解讀一下這個結果:平均而言,預測結果中每個元素與實際目標之間的差距大約為 215(46194 的平方根)。考慮到我們所要預測的數值的範圍本身只有 50-200,所以這個結果實在相當糟糕。我們稱這個結果為損失(loss),因為它指示了模型在預測目標變數方面的糟糕程度。損失越低,模型越好。

計算梯度

使用 PyTorch,我們可以根據權重和偏置自動計算 loss 的梯度和導數,因為它們已將 requires_grad 設置為 True。

這些梯度存儲在各自張量的 .grad 屬性中。注意,根據權重矩陣求得的 loss 的導數本身也是一個矩陣,且具有相同的維度。

這個損失是我們的權重和偏置的一個二次函數,而我們的目標是找到能使得損失最低的權重集。如果我們繪製出任意單個權重或偏置元素下的損失的圖表,我們會得到類似下圖的結果。通過微積分,我們可以了解到梯度表示損失的變化率,即與權重和偏置相關的損失函數的斜率。

如果梯度元素為正數,則:

  • 稍微增大元素的值會增大損失。
  • 稍微減小元素的值會降低損失。
作為權重的函數的 MSE 損失(藍線表示梯度)

如果梯度元素為負數,則:

  • 稍微增大元素的值會降低損失。
  • 稍微減小元素的值會增大損失。
作為權重的函數的 MSE 損失(綠線表示梯度)

通過改變一個權重元素而造成的損失的增減正比於該元素的損失的梯度值。這就是我們用來提升我們的模型的優化演算法的基礎。

在我們繼續之前,我們通過調用 .zero() 方法將梯度重置為零。我們需要這麼做的原因是 PyTorch 會累積梯度,也就是說,我們下一次在損失上調用 .backward 時,新的梯度值會被加到已有的梯度值上,這可能會導致意外結果出現。

使用梯度下降調整權重和偏置

我們將使用梯度下降優化演算法來降低損失和改善我們的模型,步驟如下:

  1. 生成預測
  2. 計算損失
  3. 根據權重和偏置計算梯度
  4. 按比例減去少量梯度來調整權重
  5. 將梯度重置為零

下面我們一步步地實現:

注意,這裡的預測結果和之前的一樣,因為我們還未對我們的模型做出任何修改。損失和梯度也是如此。

最後,使用上面計算得到的梯度更新權重和偏置。

上面需要注意的幾點:

  • 我們使用 torch.no_grad 指示 PyTorch 我們在更新權重和偏置時不應該跟蹤、計算或修改梯度。
  • 我們為梯度乘上了一個非常小的數值(這個案例中為 10^-5),以確保我們不會改變權重太多,因為我們只想在梯度的下降方向上邁出一小步。這個數值是這個演算法的學習率(learning rate)。
  • 在更新權重之後,我們將梯度重置為零,以免影響後續計算。

現在我們來看看新的權重和偏置:

使用新的權重和偏置,模型的損失應更低。

只是簡單地使用梯度下降來稍微調整權重和偏置,我們就已經實現了損失的顯著下降。

多次訓練

為了進一步降低損失,我們可以多次使用梯度重複調整權重和偏置的過程。一次迭代被稱為一個 epoch。我們訓練模型 100 epoch 看看。

再次驗證,現在損失應該會更低:

可以看到,現在的損失比我們開始時低了很多。我們看看模型的預測結果,並將其與目標比較一下。

現在的預測結果已非常接近目標變數;而且通過訓練模型更多 epoch,我們還能得到甚至更好的結果。

使用 PyTorch 內置的線性回歸

上面的模型和訓練過程是使用基本的矩陣運算實現的。但因為這是一種非常常用的模式,所以 PyTorch 配備了幾個內置函數和類,讓人能很輕鬆地創建和訓練模型。

首先從 PyTorch 導入 torch.nn 軟體包,其中包含了用於創建神經網路的效用程序類。

和之前一樣,我們將輸入和目標表示成矩陣形式。

我們這一次使用 15 個訓練樣本,以演示如何以小批量的形式處理大數據集。

數據集和數據載入器

我們將創建一個 TensorDataset,這讓我們可以讀取 inputs 和 targets 的行作為元組,並提供了 PyTorch 中用於處理許多不同類型的數據集的標準 API。

TensorDataset 讓我們可以使用數組索引表示法(上面代碼中的 [0:3])讀取一小部分訓練數據。它會返回一個元組(或配對),其中第一個元素包含所選行的輸入變數,第二個元素包含目標,

我們還將創建一個 DataLoader,它可以在訓練時將數據分成預定義大小的批次。它還能提供其它效用程序,如數據的混洗和隨機採樣。

數據載入器通常搭配 for-in 循環使用。舉個例子:

在每次迭代中,數據載入器都會返回一批給定批大小的數據。如果 shuffle 設為 True,則在創建批之前會對訓練數據進行混洗。混洗能幫助優化演算法的輸入隨機化,這能實現損失的更快下降。

nn.Linear

除了人工地實現權重和偏置的初始化,我們還可以使用 PyTorch 的 nn.Linear 類來定義模型,這能自動完成初始化。

PyTorch 模型還有一個很有用的 .parameters 方法,這能返回一個列表,其中包含了模型中所有的權重和偏置矩陣。對於我們的線性回歸模型,我們有一個權重矩陣和一個偏置矩陣。

我們可以使用之前一樣的方式來得到模型的預測結果:

損失函數

除了手動定義損失函數,我們也可使用內置的損失函數 mse_loss:

nn.functional 軟體包包含很多有用的損失函數和其它幾個效用程序。

我們計算一下我們模型的當前預測的損失。

優化器

除了以人工方式使用梯度操作模型的權重和偏置,我們也可使用優化器 optim.SGD。SGD 表示「隨機梯度下降」。之所以是「隨機」,原因是樣本是以批的形式選擇(通常會用到隨機混洗),而不是作為單獨一個數據組。

注意,這裡的 model.parameters() 是 optim.SGD 的一個參數,這樣優化器才知道在訓練步驟中應該修改哪些矩陣。另外,我們還可以指定一個學習率來控制參數每次的修改量。

訓練模型

我們現在已經準備好訓練模型了。我們將遵循實現梯度下降的同一過程:

  • 生成預測
  • 計算損失
  • 根據權重和偏置計算梯度
  • 按比例減去少量梯度來調整權重
  • 將梯度重置為零

唯一變化的是我們操作的是分批的數據,而不是在每次迭代中都處理整個訓練數據集。我們定義一個效用函數 fit,可訓練模型給定的 epoch 數量。

上面需要注意的幾點:

  • 我們使用之前定義的數據載入器來為每個迭代獲取數據批次。
  • 我們沒有手動更新參數(權重和偏置),而是使用了 opt.step 來執行更新,並使用了 opt.zero_grad 來將梯度重置為零。
  • 我們還添加了一個日誌語句,能夠顯示每第 10 個 epoch 的最後一批數據的損失,從而可讓我們跟蹤訓練進程。loss.item 會返回存儲在損失張量中的實際值。

訓練模型 100 epoch。

接下來使用我們的模型生成預測結果,再驗證它們與目標的接近程度。

實際上,這個預測結果非常接近我們的目標。現在,我們有了一個相當好的預測模型,可以根據一個地區的平均溫度、降雨量和濕度預測蘋果和橙子的產量。

進階閱讀

本教程覆蓋了很多基礎內容,包括線性回歸和梯度下降優化演算法。如果你想進一步探索這些主題,可參考以下資源:

  • 對導數和梯度下降的更詳盡的解釋可參考這些 Udacity 課程筆記

storage.googleapis.com/

  • 線性回歸工作方式的可視化動畫

hackernoon.com/visualiz

  • 想從數學方面理解矩陣微積分、線性回歸和梯度下降?那你不能錯過吳恩達的斯坦福大學 CS229 的課程筆記

github.com/Cleo-Stanfor

原文鏈接:medium.com/jovian-io/li


推薦閱讀:
相关文章