# 3.6 softmax回歸的從零開始實現
這一節我們來動手實現softmax回歸。首先導入本節實現所需的包或模塊。
``` python
import torch
import torchvision
import numpy as np
import sys
sys.path.append("..") # 為了導入上層目錄的d2lzh_pytorch
import d2lzh_pytorch as d2l
```
## 3.6.1 獲取和讀取數據
我們將使用Fashion-MNIST數據集,并設置批量大小為256。
``` python
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
```
## 3.6.2 初始化模型參數
跟線性回歸中的例子一樣,我們將使用向量表示每個樣本。已知每個樣本輸入是高和寬均為28像素的圖像。模型的輸入向量的長度是 `$ 28 \times 28 = 784 $`:該向量的每個元素對應圖像中每個像素。由于圖像有10個類別,單層神經網絡輸出層的輸出個數為10,因此softmax回歸的權重和偏差參數分別為`$ 784 \times 10 $`和`$ 1 \times 10 $`的矩陣。
``` python
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)
```
同之前一樣,我們需要模型參數梯度。
``` python
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
```
## 3.6.3 實現softmax運算
在介紹如何定義softmax回歸之前,我們先描述一下對如何對多維`Tensor`按維度操作。在下面的例子中,給定一個`Tensor`矩陣`X`。我們可以只對其中同一列(`dim=0`)或同一行(`dim=1`)的元素求和,并在結果中保留行和列這兩個維度(`keepdim=True`)。
``` python
X = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(X.sum(dim=0, keepdim=True))
print(X.sum(dim=1, keepdim=True))
```
輸出:
```
tensor([[5, 7, 9]])
tensor([[ 6],
[15]])
```
下面我們就可以定義前面小節里介紹的softmax運算了。在下面的函數中,矩陣`X`的行數是樣本數,列數是輸出個數。為了表達樣本預測各個輸出的概率,softmax運算會先通過`exp`函數對每個元素做指數運算,再對`exp`矩陣同行元素求和,最后令矩陣每行各元素與該行元素之和相除。這樣一來,最終得到的矩陣每行元素和為1且非負。因此,該矩陣每行都是合法的概率分布。softmax運算的輸出矩陣中的任意一行元素代表了一個樣本在各個輸出類別上的預測概率。
``` python
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition # 這里應用了廣播機制
```
可以看到,對于隨機輸入,我們將每個元素變成了非負數,且每一行和為1。
``` python
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.])
```
## 3.6.4 定義模型
有了softmax運算,我們可以定義上節描述的softmax回歸模型了。這里通過`view`函數將每張原始圖像改成長度為`num_inputs`的向量。
``` python
def net(X):
return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
```
## 3.6.5 定義損失函數
上一節中,我們介紹了softmax回歸使用的交叉熵損失函數。為了得到標簽的預測概率,我們可以使用`gather`函數。在下面的例子中,變量`y_hat`是2個樣本在3個類別的預測概率,變量`y`是這2個樣本的標簽類別。通過使用`gather`函數,我們得到了2個樣本的標簽的預測概率。與3.4節(softmax回歸)數學表述中標簽類別離散值從1開始逐一遞增不同,在代碼中,標簽類別的離散值是從0開始逐一遞增的。
``` python
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y = torch.LongTensor([0, 2])
y_hat.gather(1, y.view(-1, 1))
```
輸出:
```
tensor([[0.1000],
[0.5000]])
```
下面實現了3.4節(softmax回歸)中介紹的交叉熵損失函數。
``` python
def cross_entropy(y_hat, y):
return - torch.log(y_hat.gather(1, y.view(-1, 1)))
```
## 3.6.6 計算分類準確率
給定一個類別的預測概率分布`y_hat`,我們把預測概率最大的類別作為輸出類別。如果它與真實類別`y`一致,說明這次預測是正確的。分類準確率即正確預測數量與總預測數量之比。
為了演示準確率的計算,下面定義準確率`accuracy`函數。其中`y_hat.argmax(dim=1)`返回矩陣`y_hat`每行中最大元素的索引,且返回結果與變量`y`形狀相同。相等條件判斷式`(y_hat.argmax(dim=1) == y)`是一個類型為`ByteTensor`的`Tensor`,我們用`float()`將其轉換為值為0(相等為假)或1(相等為真)的浮點型`Tensor`。
``` python
def accuracy(y_hat, y):
return (y_hat.argmax(dim=1) == y).float().mean().item()
```
讓我們繼續使用在演示`gather`函數時定義的變量`y_hat`和`y`,并將它們分別作為預測概率分布和標簽。可以看到,第一個樣本預測類別為2(該行最大元素0.6在本行的索引為2),與真實標簽0不一致;第二個樣本預測類別為2(該行最大元素0.5在本行的索引為2),與真實標簽2一致。因此,這兩個樣本上的分類準確率為0.5。
``` python
print(accuracy(y_hat, y))
```
輸出:
```
0.5
```
類似地,我們可以評價模型`net`在數據集`data_iter`上的準確率。
``` python
# 本函數已保存在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。
``` python
print(evaluate_accuracy(test_iter, net))
```
輸出:
```
0.0681
```
## 3.6.7 訓練模型
訓練softmax回歸的實現跟[“線性回歸的從零開始實現”](linear-regression-scratch.md)一節介紹的線性回歸中的實現非常相似。我們同樣使用小批量隨機梯度下降來優化模型的損失函數。在訓練模型時,迭代周期數`num_epochs`和學習率`lr`都是可以調的超參數。改變它們的值可能會得到分類更準確的模型。
``` python
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
```
## 3.6.8 預測
訓練完成后,現在就可以演示如何對圖像進行分類了。給定一系列圖像(第三行圖像輸出),我們比較一下它們的真實標簽(第一行文本輸出)和模型預測結果(第二行文本輸出)。
``` python
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回歸的步驟和它非常相似:獲取并讀取數據、定義模型和損失函數并使用優化算法訓練模型。事實上,絕大多數深度學習模型的訓練都有著類似的步驟。
-----------
> 注:本節除了代碼之外與原書基本相同,[原書傳送門](https://zh.d2l.ai/chapter_deep-learning-basics/softmax-regression-scratch.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯