目錄python
\[ \begin{aligned} o_2 &= x_1 w_{12} + x_2 w_{22} + x_3 w_{32} + x_4 w_{42} + b_2 \end{aligned} \]算法
\[ \begin{aligned} o_3 &= x_1 w_{13} + x_2 w_{23} + x_3 w_{33} + x_4 w_{43} + b_3 \end{aligned} \]網絡
既然分類問題須要獲得離散的預測輸出,一個簡單的辦法是將輸出值\(o_i\)看成預測類別是\(i\)的置信度,並將值最大的輸出所對應的類做爲預測輸出,即輸出 \(\underset{i}{\arg\max} o_i\)。例如,若是\(o_1,o_2,o_3\)分別爲\(0.1,10,0.1\),因爲\(o_2\)最大,那麼預測類別爲2,其表明貓。app
softmax運算符(softmax operator)解決了以上兩個問題。它經過下式將輸出值變換成值爲正且和爲1的機率分佈:框架
\[ \hat{y}_1, \hat{y}_2, \hat{y}_3 = \text{softmax}(o_1, o_2, o_3) \]dom
其中svg
\[ \hat{y}1 = \frac{ \exp(o_1)}{\sum_{i=1}^3 \exp(o_i)},\quad \hat{y}2 = \frac{ \exp(o_2)}{\sum_{i=1}^3 \exp(o_i)},\quad \hat{y}3 = \frac{ \exp(o_3)}{\sum_{i=1}^3 \exp(o_i)}. \]函數
容易看出\(\hat{y}_1 + \hat{y}_2 + \hat{y}_3 = 1\)且\(0 \leq \hat{y}_1, \hat{y}_2, \hat{y}_3 \leq 1\),所以\(\hat{y}_1, \hat{y}_2, \hat{y}_3\)是一個合法的機率分佈。這時候,若是\(\hat{y}_2=0.8\),無論\(\hat{y}_1\)和\(\hat{y}_3\)的值是多少,咱們都知道圖像類別爲貓的機率是80%。此外,咱們注意到學習
\[ \underset{i}{\arg\max} o_i = \underset{i}{\arg\max} \hat{y}_i \]測試
所以softmax運算不改變預測類別輸出。
\[ \boldsymbol{W} = \begin{bmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \\ w_{31} & w_{32} & w_{33} \\ w_{41} & w_{42} & w_{43} \end{bmatrix},\quad \boldsymbol{b} = \begin{bmatrix} b_1 & b_2 & b_3 \end{bmatrix}, \]
設高和寬分別爲2個像素的圖像樣本\(i\)的特徵爲
\[ \boldsymbol{x}^{(i)} = \begin{bmatrix}x_1^{(i)} & x_2^{(i)} & x_3^{(i)} & x_4^{(i)}\end{bmatrix}, \]
輸出層的輸出爲
\[ \boldsymbol{o}^{(i)} = \begin{bmatrix}o_1^{(i)} & o_2^{(i)} & o_3^{(i)}\end{bmatrix}, \]
預測爲狗、貓或雞的機率分佈爲
\[ \boldsymbol{\hat{y}}^{(i)} = \begin{bmatrix}\hat{y}_1^{(i)} & \hat{y}_2^{(i)} & \hat{y}_3^{(i)}\end{bmatrix}. \]
softmax迴歸對樣本\(i\)分類的矢量計算表達式爲
\[ \begin{aligned} \boldsymbol{o}^{(i)} &= \boldsymbol{x}^{(i)} \boldsymbol{W} + \boldsymbol{b},\\ \boldsymbol{\hat{y}}^{(i)} &= \text{softmax}(\boldsymbol{o}^{(i)}). \end{aligned} \]
\[ \begin{aligned} \boldsymbol{O} &= \boldsymbol{X} \boldsymbol{W} + \boldsymbol{b},\\ \boldsymbol{\hat{Y}} &= \text{softmax}(\boldsymbol{O}), \end{aligned} \]
其中的加法運算使用了廣播機制,\(\boldsymbol{O}, \boldsymbol{\hat{Y}} \in \mathbb{R}^{n \times q}\)且這兩個矩陣的第\(i\)行分別爲樣本\(i\)的輸出\(\boldsymbol{o}^{(i)}\)和機率分佈\(\boldsymbol{\hat{y}}^{(i)}\)。
對於樣本\(i\),咱們構造向量\(\boldsymbol{y}^{(i)}\in \mathbb{R}^{q}\) ,使其第\(y^{(i)}\)(樣本\(i\)類別的離散數值)個元素爲1,其他爲0。這樣咱們的訓練目標能夠設爲使預測機率分佈\(\boldsymbol{\hat y}^{(i)}\)儘量接近真實的標籤機率分佈\(\boldsymbol{y}^{(i)}\)。
\[ \begin{aligned}Loss = |\boldsymbol{\hat y}^{(i)}-\boldsymbol{y}^{(i)}|^2/2\end{aligned} \]
然而,想要預測分類結果正確,咱們其實並不須要預測機率徹底等於標籤機率。例如,在圖像分類的例子裏,若是\(y^{(i)}=3\),那麼咱們只須要\(\hat{y}^{(i)}_3\)比其餘兩個預測值\(\hat{y}^{(i)}_1\)和\(\hat{y}^{(i)}_2\)大就好了。即便\(\hat{y}^{(i)}_3\)值爲0.6,無論其餘兩個預測值爲多少,類別預測均正確。而平方損失則過於嚴格,例如\(\hat y^{(i)}_1=\hat y^{(i)}_2=0.2\)比\(\hat y^{(i)}_1=0, \hat y^{(i)}_2=0.4\)的損失要小不少,雖然二者都有一樣正確的分類預測結果。
改善上述問題的一個方法是使用更適合衡量兩個機率分佈差別的測量函數。其中,交叉熵(cross entropy)是一個經常使用的衡量方法:
\[ H\left(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}\right ) = -\sum_{j=1}^q y_j^{(i)} \log \hat y_j^{(i)}, \]
其中帶下標的\(y_j^{(i)}\)是向量\(\boldsymbol y^{(i)}\)中非0即1的元素,須要注意將它與樣本\(i\)類別的離散數值,即不帶下標的\(y^{(i)}\)區分。在上式中,咱們知道向量\(\boldsymbol y^{(i)}\)中只有第\(y^{(i)}\)個元素\(y^{(i)}{y^{(i)}}\)爲1,其他全爲0,因而\(H(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}) = -\log \hat y_{y^{(i)}}^{(i)}\)。也就是說,交叉熵只關心對正確類別的預測機率,由於只要其值足夠大,就能夠確保分類結果正確。固然,遇到一個樣本有多個標籤時,例如圖像裏含有不止一個物體時,咱們並不能作這一步簡化。但即使對於這種狀況,交叉熵一樣只關心對圖像中出現的物體類別的預測機率。
假設訓練數據集的樣本數爲\(n\),交叉熵損失函數定義爲
\[ \ell(\boldsymbol{\Theta}) = \frac{1}{n} \sum_{i=1}^n H\left(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}\right ), \]
其中\(\boldsymbol{\Theta}\)表明模型參數。一樣地,若是每一個樣本只有一個標籤,那麼交叉熵損失能夠簡寫成
\[ \ell(\boldsymbol{\Theta}) = -(1/n) \sum_{i=1}^n \log \hat y_{y^{(i)}}^{(i)} \]
從另外一個角度來看,咱們知道最小化\(\ell(\boldsymbol{\Theta})\)等價於最大化\(\exp(-n\ell(\boldsymbol{\Theta}))=\prod_{i=1}^n \hat y_{y^{(i)}}^{(i)}\),即最小化交叉熵損失函數等價於最大化訓練數據集全部標籤類別的聯合預測機率。
在訓練好softmax迴歸模型後,給定任同樣本特徵,就能夠預測每一個輸出類別的機率。一般,咱們把預測機率最大的類別做爲輸出類別。若是它與真實類別(標籤)一致,說明此次預測是正確的。在實驗中,將使用準確率(accuracy)來評價模型的表現。它等於正確預測數量與總預測數量之比。
在介紹softmax迴歸的實現前先引入一個多類圖像分類數據集。它將在後面的章節中被屢次使用,以方便咱們觀察比較算法之間在模型精度和計算效率上的區別。圖像分類數據集中最經常使用的是手寫數字識別數據集MNIST。但大部分模型在MNIST上的分類精度都超過了95%。爲了更直觀地觀察算法之間的差別,咱們將使用一個圖像內容更加複雜的數據集Fashion-MNIST。
我這裏咱們會使用torchvision包,它是服務於PyTorch深度學習框架的,主要用來構建計算機視覺模型。torchvision主要由如下幾部分構成:
# import needed package %matplotlib inline from IPython import display import matplotlib.pyplot as plt import torch import torchvision import torchvision.transforms as transforms import time import sys sys.path.append("/home/kesci/input") import d2lzh1981 as d2l
mnist_train = torchvision.datasets.FashionMNIST(root='/home/kesci/input/FashionMNIST2065', train=True, download=True, transform=transforms.ToTensor()) mnist_test = torchvision.datasets.FashionMNIST(root='/home/kesci/input/FashionMNIST2065', train=False, download=True, transform=transforms.ToTensor())
# show result print(type(mnist_train)) print(len(mnist_train), len(mnist_test))
# 咱們能夠經過下標來訪問任意一個樣本 feature, label = mnist_train[0] print(feature.shape, label) # Channel x Height x Width
若是不作變換輸入的數據是圖像,咱們能夠看一下圖片的類型參數:
mnist_PIL = torchvision.datasets.FashionMNIST(root='/home/kesci/input/FashionMNIST2065', train=True, download=True) PIL_feature, label = mnist_PIL[0] print(PIL_feature)
# 本函數已保存在d2lzh包中方便之後使用 def get_fashion_mnist_labels(labels): text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [text_labels[int(i)] for i in labels] def show_fashion_mnist(images, labels): d2l.use_svg_display() # 這裏的_表示咱們忽略(不使用)的變量 _, figs = plt.subplots(1, len(images), figsize=(12, 12)) for f, img, lbl in zip(figs, images, labels): f.imshow(img.view((28, 28)).numpy()) f.set_title(lbl) f.axes.get_xaxis().set_visible(False) f.axes.get_yaxis().set_visible(False) plt.show()
X, y = [], [] for i in range(10): X.append(mnist_train[i][0]) # 將第i個feature加到X中 y.append(mnist_train[i][1]) # 將第i個label加到y中 show_fashion_mnist(X, get_fashion_mnist_labels(y))
# 讀取數據 batch_size = 256 num_workers = 4 train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers) test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
import torch import torchvision import numpy as np import sys sys.path.append("/home/kesci/input") import d2lzh1981 as d2l
batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, root='/home/kesci/input/FashionMNIST2065')
num_inputs = 784 # 28*28 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)
X = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(X.sum(dim=0, keepdim=True)) # dim爲0,按照相同的列求和,並在結果中保留列特徵 print(X.sum(dim=1, keepdim=True)) # dim爲1,按照相同的行求和,並在結果中保留行特徵 print(X.sum(dim=0, keepdim=False)) # dim爲0,按照相同的列求和,不在結果中保留列特徵 print(X.sum(dim=1, keepdim=False)) # dim爲1,按照相同的行求和,不在結果中保留行特徵
\[ \hat{y}_j = \frac{ \exp(o_j)}{\sum_{i=1}^3 \exp(o_i)} \]
def softmax(X): X_exp = X.exp() partition = X_exp.sum(dim=1, keepdim=True) # print("X size is ", X_exp.size()) # print("partition size is ", partition, partition.size()) return X_exp / partition # 這裏應用了廣播機制
\[ \begin{aligned} \boldsymbol{o}^{(i)} &= \boldsymbol{x}^{(i)} \boldsymbol{W} + \boldsymbol{b},\\ \boldsymbol{\hat{y}}^{(i)} &= \text{softmax}(\boldsymbol{o}^{(i)}). \end{aligned} \]
def net(X): return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
\[ H\left(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}\right ) = -\sum_{j=1}^q y_j^{(i)} \log \hat y_j^{(i)}, \]
\[ \ell(\boldsymbol{\Theta}) = \frac{1}{n} \sum_{i=1}^n H\left(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}\right ), \]
\[ \ell(\boldsymbol{\Theta}) = -(1/n) \sum_{i=1}^n \log \hat y_{y^{(i)}}^{(i)} \]
a = torch.Tensor([[1,2],[3,4]]) b = orch.gather(a,1,torch.LongTensor([[0,0],[1,0]]))
torch.gather(input, dim, index, out=None)中的dim表示的就是第幾維度,在這個二維例子中,若是dim=0,那麼它表示的就是你接下來的操做是對於第一維度進行的,也就是行;若是dim=1,那麼它表示的就是你接下來的操做是對於第二維度進行的,也就是列。
上面例子中,[0,0]就是第一行對應元素的下標,也就是對應的是[1,1]; [1,0]就是第二行對應元素的下標,也就是對應的是[4,3]。
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))
def cross_entropy(y_hat, y): return - torch.log(y_hat.gather(1, y.view(-1, 1)))
咱們模型訓練完了進行模型預測的時候,會用到咱們這裏定義的準確率。
def accuracy(y_hat, y): return (y_hat.argmax(dim=1) == y).float().mean().item()
# 本函數已保存在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
num_epochs, lr = 5, 0.1 # 本函數已保存在d2lzh_pytorch包中方便之後使用 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() 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)
如今咱們的模型訓練完了,能夠進行一下預測,咱們的這個模型訓練的到底準確不許確。
如今就能夠演示如何對圖像進行分類了。給定一系列圖像(第三行圖像輸出),咱們比較一下它們的真實標籤(第一行文本輸出)和模型預測結果(第二行文本輸出)。
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])
# 加載各類包或者模塊 import torch from torch import nn from torch.nn import init import numpy as np import sys sys.path.append("/home/kesci/input") import d2lzh1981 as d2l
batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, root='/home/kesci/input/FashionMNIST2065')
num_inputs = 784 num_outputs = 10 class LinearNet(nn.Module): def __init__(self, num_inputs, num_outputs): super(LinearNet, self).__init__() self.linear = nn.Linear(num_inputs, num_outputs) def forward(self, x): # x 的形狀: (batch, 1, 28, 28) y = self.linear(x.view(x.shape[0], -1)) return y # net = LinearNet(num_inputs, num_outputs) class FlattenLayer(nn.Module): def __init__(self): super(FlattenLayer, self).__init__() def forward(self, x): # x 的形狀: (batch, *, *, ...) return x.view(x.shape[0], -1) from collections import OrderedDict net = nn.Sequential( # FlattenLayer(), # LinearNet(num_inputs, num_outputs) OrderedDict([ ('flatten', FlattenLayer()), ('linear', nn.Linear(num_inputs, num_outputs))]) # 或者寫成咱們本身定義的 LinearNet(num_inputs, num_outputs) 也能夠 )
init.normal_(net.linear.weight, mean=0, std=0.01) init.constant_(net.linear.bias, val=0)
loss = nn.CrossEntropyLoss() # 下面是他的函數原型 # class torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
optimizer = torch.optim.SGD(net.parameters(), lr=0.1) # 下面是函數原型 # class torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)
num_epochs = 5 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)