目录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)