2019 年 3 月

第 34 卷,第 3 期

本文章是由機器翻譯。

[測試回合]

使用 PyTorch 的類神經迴歸

藉由James McCaffrey

James McCaffrey迴歸問題的目標是預測單一數值。比方說,您可能想要預測一棟房子根據平方英尺、 年齡、 郵遞區號等的價格。在本文中我會示範如何建立類神經的迴歸模型使用 PyTorch 程式碼程式庫。若要了解這篇文章的走向,最好是要看看的示範程式**[圖 1**。  

使用 PyTorch 示範執行類神經的迴歸
[圖 1 使用 PyTorch 示範執行的類神經迴歸

示範程式會建立預測模型 Housing 波士頓的資料集,其目標是要預測其中一種接近波士頓 506 鄉鎮的中間值的房屋價格為基礎。資料來自於早期 1970 年代。每個資料項目,依此類推有 13 預測變數,例如 crime 索引的城鎮每屋,現身的房間內的平均數目。沒有只有一個輸出值,因為目標是要預測單一數值。

示範載入記憶體中的 404 的訓練項目和 102 的測試項目,並再建立 13-(10-10) 1 類神經網路。類神經網路會有兩個隱藏的處理層級,每個都有 10 個節點。輸入和輸出節點數目取決於資料,但隱藏層的數目和每個節點的數目必須由試驗和錯誤的可用參數。

示範訓練類神經網路,這表示的加權值,並定義類神經網路行為的偏差會計算使用定型資料,其中有已知正確的輸入和輸出值。定型之後, 此示範會計算對測試資料 (75.49 百分比,77 共 102 正確) 模型的精確度。測試精確度是粗略的程度,您所預期要在新的、 先前未見過的資料上執行的模型量值。

示範做出結論,提出的第一個測試城鎮的預測。13 未經處理輸入值為 (0.09266、 34.0,...8.67).已定型的類神經的迴歸模型之後,標準化 (經過縮放以便介於 0.0 到 1.0 之間的所有值都是) 使用資料,以便進行預測時必須使用正規化的資料,也就是示範 (0.00097、 0.34,...0.191501).此模型會預測中間的房屋價格為 $24,870.07,很接近實際的 $26,400 中間價格。

本文假設您有中繼或更佳的程式設計技能,使用 C 系列語言和機器學習服務基本的熟悉。完整的示範程式碼會顯示在這篇文章。原始程式碼和此示範所用的兩個資料檔案也會是可在隨附的下載項目。已移除所有一般錯誤檢查要保留的主要概念盡可能清楚。

安裝 PyTorch

安裝 PyTorch,牽涉到兩個主要步驟。第一次安裝 Python 和數個必要輔助的套件,例如 NumPy 和 SciPy,則您將 PyTorch 安裝為附加 Python 套件。

雖然可以安裝 Python 和個別執行 PyTorch 所需的套件,但最好是許多安裝 Python 散發套件。我們的示範中,我安裝 Anaconda3 5.2.0 發佈 (其中包含 Python 3.6.5) 和 PyTorch 1.0.0。如果您不熟悉 python,請注意,安裝和管理附加元件封裝相依性是重要的。

透過 Anaconda 散發套件安裝 Python 之後, PyTorch 套件可以安裝.whl (「 wheel 」) 檔案使用 pip 公用程式函式。PyTorch 是僅限 CPU 的版本和 GPU 版本。我使用僅限 CPU 的版本。

了解資料

波士頓住宿的資料集是來自以研究空氣侵害的 1978年撰寫一篇研究論文。您可以在網際網路上的許多位置中找到不同版本的資料集。第一個資料項目是:

0.00632, 18.00, 2.310, 0, 0.5380, 6.5750, 65.20
4.0900, 1, 296.0, 15.30, 396.90, 4.98, 24.00

每個資料項目有 14 的值,代表其中一個 506 鄉鎮波士頓附近。第一次 13 的數字是預測工具變數的值和最後一個值是中間的房屋價格 (除以 1000) 現身。簡單地說,13 預測變數是: 犯罪率城鎮、 大很多百分比、 產業,Charles River 侵害,平均的數字室,每個房屋、 家年齡資訊、 距離高速公路、 存取麻州波士頓市的相鄰的分區的百分比稅額速率,pupil 師生比黑色的居民的比例和低狀態居民的百分比。  

因為有 14 的變數,就無法以視覺化方式檢視資料集,但您可以從圖形中取得資料的約略的構念**[圖 2**。圖表會顯示城鎮分區的各項產業分列 102 的項目,在測試資料集的百分比做為中間值的房屋價格。

部分波士頓區域房屋資料集
[圖 2 部分波士頓區域房屋資料集

當使用類神經網路,您必須編碼非數值資料,如此大型的值,例如 pupil 老師的比例 20.0,不會拖垮較小的值,例如 0.538 侵害讀取,您應正規化數值資料。Charles River 變數是類別的值儲存為 0 (town 不相鄰) 或 1 (相鄰)。這些值為-1 和 + 1 已重新編碼的。其他 12 預測變數為數值。每個變數,計算最小值和最大的值,並再針對每個值 x,正規化為 (x-分鐘) / (max-min)。最小值-最大值正規化後,所有值都都介於 0.0 到 1.0 之間。

原始資料中的中間值的房屋值已經正規化除以 1000,因此範圍從 5.0 到 50.0,與最在大約 25.0 的值。我是價格除以 10,使所有的中間值的房屋價格自介於 0.5 至 5.0 版,其中大部分是大約 2.5 套用額外的正規化。

示範程式

完整的示範程式,以節省空間,少數稍加編輯所示**[圖 3**。縮排兩個空格,而不是一般的四個空格,以節省空間。請注意,使用 Python 和 ' \' 行接續字元。我使用 「 記事本 」 編輯我的程式,但我的同事大多偏好 Visual Studio 或 VS Code 中,這兩者都有適用於 Python 的絕佳支援。

圖 3 裝載示範程式波士頓

# boston_dnn.py
# Boston Area House Price dataset regression
# Anaconda3 5.2.0 (Python 3.6.5), PyTorch 1.0.0
import numpy as np
import torch as T  # non-standard alias
# ------------------------------------------------------------
def accuracy(model, data_x, data_y, pct_close):
  n_items = len(data_y)
  X = T.Tensor(data_x)  # 2-d Tensor
  Y = T.Tensor(data_y)  # actual as 1-d Tensor
  oupt = model(X)       # all predicted as 2-d Tensor
  pred = oupt.view(n_items)  # all predicted as 1-d
  n_correct = T.sum((T.abs(pred - Y) < T.abs(pct_close * Y)))
  result = (n_correct.item() * 100.0 / n_items)  # scalar
  return result 
# ------------------------------------------------------------
class Net(T.nn.Module):
  def __init__(self):
    super(Net, self).__init__()
    self.hid1 = T.nn.Linear(13, 10)  # 13-(10-10)-1
    self.hid2 = T.nn.Linear(10, 10)
    self.oupt = T.nn.Linear(10, 1)
    T.nn.init.xavier_uniform_(self.hid1.weight)  # glorot
    T.nn.init.zeros_(self.hid1.bias)
    T.nn.init.xavier_uniform_(self.hid2.weight)
    T.nn.init.zeros_(self.hid2.bias)
    T.nn.init.xavier_uniform_(self.oupt.weight)
    T.nn.init.zeros_(self.oupt.bias)
  def forward(self, x):
    z = T.tanh(self.hid1(x))
    z = T.tanh(self.hid2(z))
    z = self.oupt(z)  # no activation, aka Identity()
    return z
# ------------------------------------------------------------
def main():
  # 0. Get started
  print("\nBoston regression using PyTorch 1.0 \n")
  T.manual_seed(1);  np.random.seed(1)
  # 1. Load data
  print("Loading Boston data into memory ")
  train_file = ".\\Data\\boston_train.txt"
  test_file = ".\\Data\\boston_test.txt"
  train_x = np.loadtxt(train_file, delimiter="\t",
    usecols=range(0,13), dtype=np.float32)
  train_y = np.loadtxt(train_file, delimiter="\t",
    usecols=[13], dtype=np.float32)
  test_x = np.loadtxt(test_file, delimiter="\t",
    usecols=range(0,13), dtype=np.float32)
  test_y = np.loadtxt(test_file, delimiter="\t",
    usecols=[13], dtype=np.float32)
  # 2. Create model
  print("Creating 13-(10-10)-1 DNN regression model \n")
  net = Net()  # all work done above
  # 3. Train model
  net = net.train()  # set training mode
  bat_size = 10
  loss_func = T.nn.MSELoss()  # mean squared error
  optimizer = T.optim.SGD(net.parameters(), lr=0.01)
  n_items = len(train_x)
  batches_per_epoch = n_items // bat_size
  max_batches = 1000 * batches_per_epoch
  print("Starting training")
  for b in range(max_batches):
    curr_bat = np.random.choice(n_items, bat_size,
      replace=False)
    X = T.Tensor(train_x[curr_bat])
    Y = T.Tensor(train_y[curr_bat]).view(bat_size,1)
    optimizer.zero_grad()
    oupt = net(X)
    loss_obj = loss_func(oupt, Y)
    loss_obj.backward()
    optimizer.step()
    if b % (max_batches // 10) == 0:
      print("batch = %6d" % b, end="")
      print("  batch loss = %7.4f" % loss_obj.item(), end="")
      net = net.eval()
      acc = accuracy(net, train_x, train_y, 0.15)
      net = net.train()
      print("  accuracy = %0.2f%%" % acc)      
  print("Training complete \n")
  # 4. Evaluate model
  net = net.eval()  # set eval mode
  acc = accuracy(net, test_x, test_y, 0.15)
  print("Accuracy on test data = %0.2f%%" % acc)
  # 5. Save model - TODO
  # 6. Use model
  raw_inpt = np.array([[0.09266, 34, 6.09, 0, 0.433, 6.495, 18.4,
    5.4917, 7, 329, 16.1, 383.61, 8.67]], dtype=np.float32)
  norm_inpt = np.array([[0.000970, 0.340000, 0.198148, -1,
    0.098765, 0.562177, 0.159629, 0.396666, 0.260870, 0.270992,
    0.372340, 0.966488, 0.191501]], dtype=np.float32)
  X = T.Tensor(norm_inpt)
  y = net(X)
  print("For a town with raw input values: ")
  for (idx,val) in enumerate(raw_inpt[0]):
    if idx % 5 == 0: print("")
    print("%11.6f " % val, end="")
  print("\n\nPredicted median house price = $%0.2f" %
    (y.item()*10000))
if __name__=="__main__":
  main()

示範匯入整個 PyTorch 套件,並將其指派的別名是 t。替代方式是匯入模組和所需的函式。

示範定義 helper 函式呼叫精確度。使用迴歸模型時,沒有任何固有的定義,預測的精確度。您必須定義如何關閉預測的值必須為目標值才會計為正確的預測。示範程式會計算在 15%,則為 true 的值是否正確預測的中位數房屋價格。

示範程式的所有控制項邏輯都包含在單一的 main 函式。程式執行開始藉由設定全域 NumPy 和 PyTorch 的隨機種子,因此結果會是可重現。

將資料載入記憶體

示範載入使用 NumPy loadtxt 函式的記憶體中的資料:

train_x = np.loadtxt(train_file, delimiter="\t",
  usecols=range(0,13), dtype=np.float32)
train_y = np.loadtxt(train_file, delimiter="\t",
  usecols=[13], dtype=np.float32)
test_x = np.loadtxt(test_file, delimiter="\t",
  usecols=range(0,13), dtype=np.float32)
test_y = np.loadtxt(test_file, delimiter="\t",
  usecols=[13], dtype=np.float32)

程式碼會假設資料位於名為 Data 的子目錄。示範資料已前置處理過的分割成訓練和測試集。處理的資料不是在概念上非常困難的但幾乎都是相當耗時又惱人。我的同事有許多想要使用 pandas (Python 資料分析) 套件來操作資料。

定義類神經網路

示範程式定義的類別,名為繼承自 nn Net 中定義 13-(10-10) 1 類神經網路。模組的模組。您可以想像 Python __init__ 函式的類別建構函式。請注意,您未明確定義輸入的層因為輸入的值的形式傳送直接到第一個隱藏層。

網路具有 (13 * 10) + (10 * 10) + (10 * 1) = 240 的加權。每個加權會初始化為使用 Xavier 統一演算法較小的隨機值。網路上已經有 10 + 10 + 1 = 21 個偏差。每個偏差值初始化為零。

Net 類別的向前函式會定義圖層如何計算輸出。示範使用 tanh (雙曲線正切) 啟用兩個隱藏的層和輸出層上的無法啟動:

def forward(self, x):
  z = T.tanh(self.hid1(x))
  z = T.tanh(self.hid2(z))
  z = self.oupt(z)
  return z

隱藏的層啟用主要的替代做法是修正線性單元 (ReLU) 啟動過程中,但有許多其他函數。

PyTorch 是在相當低的層級的抽象概念的方式運作,因為有許多您可以使用的另一種設計模式。例如,而不是定義含有 __init__ 和轉送函式,以及使用 net 然後具現化類別 Net = Net(),您可以使用循序的函式,就像這樣:

net = T.nn.Sequential(
  T.nn.Linear(13,10),
  T.nn.Tanh(),
  T.nn.Linear(10,10),
  T.nn.Tanh(),
  T.nn.Linear(10,1))

循序方法更簡單,但請注意,您不需要直接控制的加權和偏差初始化的演算法。一旦您熟悉的程式庫,使用 PyTorch 時收到極大的彈性會是一項優點。

定型模型

定型模型的這七個陳述式開頭:

net = net.train()  # Set training mode
bat_size = 10
loss_func = T.nn.MSELoss()  # Mean squared error
optimizer = T.optim.SGD(net.parameters(), lr=0.01)
n_items = len(train_x)
batches_per_epoch = n_items // bat_size
max_batches = 1000 * batches_per_epoch

PyTorch 有兩種模式: 定型和評估。預設模式是訓練,但在我看來,最好明確地將設定模式。(通常稱為 「 迷你批次 」) 的批次大小是超參數。針對迴歸問題,表示平方的誤差最常見的損失函數。隨機梯度下降 (SGD) 演算法是最基本的技巧,並在許多情況下還會使用 Adam 演算法提供更好的結果。

示範程式會使用簡單的方法,進行批次處理的訓練項目。此示範中,有大約 400 訓練項目,因此如果批次大小為 10,平均瀏覽每個訓練項目一次 (這通常稱為的 epoch 中機器學習服務術語) 需要 400 / 10 = 40 的批次。因此,可訓練 epoch 1,000 的對等、 示範程式需要 1000 * 40 = 40,000 批次。

核心訓練陳述式是:

for b in range(max_batches):
  curr_bat = np.random.choice(n_items, bat_size,
    replace=False)
  X = T.Tensor(train_x[curr_bat])
  Y = T.Tensor(train_y[curr_bat]).view(bat_size,1)
  optimizer.zero_grad()
  oupt = net(X)
  loss_obj = loss_func(oupt, Y)
  loss_obj.backward()  # Compute gradients
  optimizer.step()     # Update weights and biases

選擇函式會從 404 可用的訓練項目選取 10 個隨機的索引。項目將從 NumPy 陣列轉為 PyTorch tensors。您可以想像的 tensor 可以有效率地處理 GPU (即使此示範不利用 GPU) 的多維度陣列。名字古怪的檢視函式的外觀重新安排一維的目標值到二維 tensor。將 NumPy 陣列轉換成 PyTorch tensors,以及處理陣列和 tensor 圖形是主要的挑戰,使用 PyTorch 時。

之後每隔 4,000 批次的示範程式會顯示所代表的意義值平方錯誤遺失目前的批次的 10 個訓練的項目,並預測精確度的模型中,使用 [整個 404 項目定型資料集上的 [目前的加權和偏差:

if b % (max_batches // 10) == 0:
  print("batch = %6d" % b, end="")
  print("  batch loss = %7.4f" % loss_obj.item(), end="")
  net = net.eval()
  acc = accuracy(net, train_x, train_y, 0.15)
  net = net.train()
  print("  accuracy = %0.2f%%" % acc)

"/ / 」 運算子是在 Python 中的整數除法。然後再呼叫程式定義的精確度函式,示範會設定成評估版模式網路。技術上來說,這並非必要,是因為定型和評估模式只讓不同的結果如果網路使用中輟或圖層的批次正規化。

評估與使用定型的模型

訓練完成之後,範例程式會評估預測模型的精確度的測試資料集上:

net = net.eval()  # set eval mode
acc = accuracy(net, test_x, test_y, 0.15)
print("Accuracy on test data = %0.2f%%" % acc)

Eval 函式會傳回它套用所在之模型的參考它可能已經呼叫沒有指派陳述式。

在大部分情況下,在定型模型之後您想要儲存以供稍後使用的模型。儲存已定型的 PyTorch 模型都是位元範圍以外的本文中,您還可以找到數個範例 PyTorch 文件中。

定型的迴歸模型的重點是使用它來進行預測。示範程式做出預測,使用第一個資料項目從 102 測試項目:

raw_inpt = np.array([[0.09266, 34, 6.09, 0, 0.433, 6.495, 18.4,
  5.4917, 7, 329, 16.1, 383.61, 8.67]], dtype=np.float32)
norm_inpt = np.array([[0.000970, 0.340000, 0.198148, -1,
  0.098765, 0.562177, 0.159629, 0.396666, 0.260870, 0.270992,
  0.372340, 0.966488, 0.191501]], dtype=np.float32)

當您有新的資料時,您必須記得將預測值正規化已正規化的定型資料的方式相同。最小值-最大值正規化,這表示您需要儲存的最小值和最大值已正規化每個變數。

示範結束時,會建立和顯示預測:

...
  X = T.Tensor(norm_inpt)
  y = net(X)
  print("Predicted = $%0.2f" % (y.item()*10000))
if __name__=="__main__":
  main()

為單一值的 tensor,會傳回預測的值。Item 函式用來存取值,因此不會顯示。

總結

PyTorch 程式庫會比其他替代方案 TensorFlow、 Keras 和 CNTK,特別是與範例程式碼較不成熟。但在我的同事之間 PyTorch 的使用非常快速地成長。我相信若要繼續這項趨勢,而且高品質的範例會越來越多您可以使用。


Dr。James McCaffrey適用於在美國華盛頓州雷德蒙的 Microsoft Research他參與開發數種主要的 Microsoft 產品包括 Internet Explorer 和 Bing。Dr。McCaffrey 要聯絡jamccaff@microsoft.com

感謝下列 Microsoft 技術專家檢閱這篇文章:Chris Lee, Ricky Loynd