天天看點

SOFTMAX回歸的從零開始實作SOFTMAX回歸的從零開始實作

《動手學深度學習pytorch》部分學習筆記,僅用作自己複習。

SOFTMAX回歸的從零開始實作

import torch
import torchvision
import numpy as np
import sys
sys.path.append("..") # 為了導⼊上層⽬目錄的d2lzh_pytorch
import d2lzh_pytorch as d2l
           

擷取和讀取資料

使用Fashion-MNIST資料集,并設定批量大小為256。

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 第一步:擷取資料,指定訓練和測試集大小
           

初始化模型參數

跟線性回歸中的例子⼀樣,我們将使用向量表示每個樣本。已知每個樣本輸⼊是⾼和寬均為28像素的圖像。模型的輸入向量的⻓度是 28x28=784:該向量的每個元素對應圖像中每個像素。由于圖像有10個類别,單層神經網絡輸出層的輸出個數為10,是以softmax回歸的權重和偏差參數分别為 784x10和 1x10的矩陣。

# 第二步:指定輸入輸出次元,初始化模型參數
num_inputs = 784
num_outputs = 10
W = torch.tensor(np.random.normal(0, 0.01, (num_inputs,num_outputs)), dtype=torch.float)
b = torch.zeros(num_outputs, dtype=torch.float)
           

同之前⼀樣,我們需要模型參數梯度。

# 第三部:指定需要梯度的學習參數
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
           

在介紹如何定義softmax回歸之前,我們先描述一下對如何對多元 Tensor 按次元操作。

X = torch.tensor([[1, 2, 3], [4, 5, 6]]) # 給定⼀個 Tensor 矩陣 X 
# 可以隻對其中同⼀列( dim=0 )或同⼀行( dim=1 )的元素求和,并在結果中保留⾏和列這兩個次元(keepdim=True )。
print(X.sum(dim=0, keepdim=True))
print(X.sum(dim=1, keepdim=True))
           

輸出:

tensor([[5, 7, 9]])

tensor([[ 6],

[15]])

定義softmax運算。

# 矩陣 X 的行數是樣本數,列數是輸出個數。
def softmax(X):
    # 為了表達樣本預測各個輸出的機率,softmax運算會先通過 exp 函數對每個元素做指數運算
    X_exp = X.exp()
    # 再對 exp 矩陣同⾏元素求和
    partition = X_exp.sum(dim=1, keepdim=True)
    # 最後令矩陣每⾏各元素與該⾏元素之和相除
    return X_exp / partition # 這里應⽤了廣播機制
# 最終得到的矩陣每行元素和為1且非負。是以,該矩陣每行都是合法的機率分布。
# softmax運算的輸出矩陣中的任意⼀行元素代表了一個樣本在各個輸出類别上的預測機率。
           

可以看到,對于随機輸入,我們将每個元素變成了非負數,且每一行和為1。

X = torch.rand((2, 5))
X_prob = softmax(X)
print(X_prob, X_prob.sum(dim=1))
           

輸出:tensor([[0.2206, 0.1520, 0.1446, 0.2690, 0.2138],[0.1540, 0.2290, 0.1387, 0.2019, 0.2765]]) tensor([1., 1.])

定義模型

有了softmax運算,我們可以定義上節描述的softmax回歸模型了。

# 第四部:定義模型
def net(X):
  return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
  # 這里通過 view 函數将每張原始圖像改成長度為 num_inputs 的向量。
           

定義損失函數

上⼀節中,我們介紹了softmax回歸使用的交叉熵損失函數。與3.4節(softmax回歸)數學表述中标簽類别離散值從1開始逐一遞增不同,在代碼中,标簽類别的離散值是從0開始逐⼀遞增的。

# 第五步:定義損失函數
# 變量 y_hat 是2個樣本在3個類别的預測機率
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
# 變量 y 是這2個樣本的标簽類别。
y = torch.LongTensor([0, 2])
# 為了得到标簽的預測機率,我們可以使用 gather 函數。(我們得到了2個樣本的标簽的預測機率。)
y_hat.gather(1, y.view(-1, 1))
           

輸出:tensor([[0.1000],

[0.5000]])

下⾯面實作了3.4節(softmax回歸)中介紹的交叉熵損失函數。

def cross_entropy(y_hat, y):
  return - torch.log(y_hat.gather(1, y.view(-1, 1)))
           

計算分類準确率

給定⼀個類别的預測機率分布 y_hat ,我們把預測機率最⼤的類别作為輸出類别。如果它與真實類别 y ⼀緻,說明這次預測是正确的。分類準确率即正确預測數量與總預測數量之比。

# 為了示範準确率的計算,下面定義準确率 accuracy 函數。
def accuracy(y_hat, y):
  # 其中 y_hat.argmax(dim=1) 傳回矩陣 y_hat 每行中最大元素的索引,且傳回結果與變量y形狀相同。
  # 相等條件判斷式(y_hat.argmax(dim=1) == y) 是⼀個類型為 ByteTensor 的 Tensor 。
  # 我們⽤ float() 将其轉換為值為0(相等為假)或1(相等為真)的浮點型 Tensor 。
  return (y_hat.argmax(dim=1) == y).float().mean().item()
           

類似地,我們可以評價模型 net 在資料集 data_iter 上的準确率。

# 本函數已儲存在d2lzh_pytorch包中⽅便以後使⽤用。
def evaluate_accuracy(data_iter, net):
  acc_sum, n = 0.0, 0
  for X, y in data_iter:
    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
    n += y.shape[0]
return acc_sum / n
           

因為我們随機初始化了模型 net ,是以這個随機模型的準确率應該接近于類别個數10的倒數即0.1。

print(evaluate_accuracy(test_iter, net)) 
           

輸出:0.0681 

訓練模型

訓練softmax回歸的實作跟“線性回歸的從零開始實作” 一節介紹的線性回歸中的實作非常相似。我們同樣使用⼩批量随機梯度下降來優化模型的損失函數。在訓練模型時,疊代周期數 num_epochs 和學習率 lr 都是可以調的超參數。改變它們的值可能會得到分類更準确的模型。

num_epochs, lr = 5, 0.1
# 本函數已儲存在d2lzh包中⽅友善便以後使⽤用
def train_ch3(net, train_iter, test_iter, loss, num_epochs,batch_size,params=None, lr=None, optimizer=None):
  for epoch in range(num_epochs):
    train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
    for X, y in train_iter:
      y_hat = net(X)
      l = loss(y_hat, y).sum()
      # 梯度清零
      if optimizer is not None:
        optimizer.zero_grad()
      elif params is not None and params[0].grad is not None:
        for param in params:
        param.grad.data.zero_()
      l.backward() # 反向傳播
      if optimizer is None:
        d2l.sgd(params, lr, batch_size) # 梯度下降
      else:
        optimizer.step() # “softmax回歸的簡潔實作”⼀節将⽤用到
      train_l_sum += l.item()
      train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
      n += y.shape[0]
    test_acc = evaluate_accuracy(test_iter, net)
    print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'% (epoch + 1, train_l_sum / n, train_acc_sum / n,test_acc))
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs,
batch_size, [W, b], lr)
           

輸出:

epoch 1, loss 0.7878, train acc 0.749, test acc 0.794

epoch 2, loss 0.5702, train acc 0.814, test acc 0.813

epoch 3, loss 0.5252, train acc 0.827, test acc 0.819

epoch 4, loss 0.5010, train acc 0.833, test acc 0.824

epoch 5, loss 0.4858, train acc 0.836, test acc 0.815

預測

訓練完成後,現在就可以示範如何對圖像進⾏分類了。給定一系列列圖像(第三行圖像輸出),我們比較⼀下它們的真實标簽(第⼀行⽂本輸出)和模型預測結果(第⼆行⽂本輸出)。

X, y = iter(test_iter).next()
true_labels = d2l.get_fashion_mnist_labels(y.numpy())
pred_labels = d2l.get_fashion_mnist_labels(net(X).argmax(dim=1).numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels,pred_labels)]
d2l.show_fashion_mnist(X[0:9], titles[0:9])
           
SOFTMAX回歸的從零開始實作SOFTMAX回歸的從零開始實作

 ⼩結

可以使⽤softmax回歸做多類别分類。與訓練線性回歸相比,你會發現訓練softmax回歸的步驟和它非常相似:擷取并讀取資料、定義模型和損失函數并使用優化算法訓練模型。事實上,絕大多數深度學習模型的訓練都有着類似的步驟。