09 Softmax 回歸 + 損失函數(shù) + 圖片分類數(shù)據(jù)集【動手學(xué)深度學(xué)習(xí)v2

----未完待續(xù)----
softmax回歸

- softmax回歸是機器學(xué)習(xí)中非常重要而且經(jīng)典的模型,他的名字雖然叫回歸,但實際上是一個分類問題
分類與回歸
- 回歸是估計一個連續(xù)值
- 分類是預(yù)測一個連續(xù)的類別
- 分類問題舉例




從回歸到多類分類

- 分類問題從單輸出變成了多輸出,輸出的個數(shù)等于類別的個數(shù)
從回歸到多類分類

- 類別是一個數(shù),也可能是一個字符串
- 一位有效編碼:剛好有一個位置有效的編碼方式(有效的那一位為 1 ,其它位上全部置 0 )

- 不關(guān)心置信度的值是多少,只關(guān)心正確類別的置信度的值要遠(yuǎn)遠(yuǎn)高于其他非正確類置信度,使得正確的類別能夠與其他類別拉開距離
- 雖然關(guān)心的是一個相對值,但是需要把這些值放在一個合適的區(qū)間

- 希望是輸出能夠盡量是一個概率
- y^:一個長為 n 的向量,每個元素都非負(fù),而且這些元素加起來的和為 1
- exp(o):e 的 o 次方,e 是一個指數(shù)常數(shù)。指數(shù)的好處是:不管是什么值都能將它變成非負(fù)
- 通過 softmax 將輸出 o 變成了概率
softmax 和交叉熵?fù)p失

- 一般來說使用真實概率與預(yù)測概率的區(qū)別來作為損失
- 假設(shè) p 和 q 是兩個離散概率
- 上圖中第二式的化簡:根據(jù)前面的一位有效編碼,yi中只有一位有效元素為1,其他都為0
- 對分類問題來講,不關(guān)心對于非正確類的預(yù)測值,只關(guān)心對于正確類的預(yù)測值及其置信度的大小
總結(jié)
- softmax 回歸是一個多分類分類模型
- 使用 softmax 操作子得到每個類的預(yù)測置信度(使得每個類的置信度是一個概率,非負(fù)且和為1)
- 使用交叉熵來衡量預(yù)測和標(biāo)號的區(qū)別(作為損失函數(shù))
損失函數(shù)

- 損失函數(shù)用來衡量預(yù)測值和真實值之間的區(qū)別
常用的損失函數(shù)
1、L2 Loss(均方損失)

- 藍(lán)色曲線表示 y = 0 的時候變化預(yù)測值 y‘ 的函數(shù),這是一個二次函數(shù)
- 綠色曲線是它的似然函數(shù),即exp( - l ),它的似然函數(shù)是一個高斯分布
- 橙色曲線表示損失函數(shù)的梯度,它是一條過原點的直線
- 在梯度下降的時候是根據(jù)負(fù)梯度方向來更新梯度,所以它的導(dǎo)數(shù)決定如何更新參數(shù)

- 當(dāng) y 和 y‘ 離得比較遠(yuǎn)(橫軸到零點的距離越遠(yuǎn)),梯度越大,對參數(shù)的更新越多,更新的幅度越大,反之亦然
2、L2 Loss(絕對值損失函數(shù))

- 藍(lán)色曲線表示 y = 0 的時候變化預(yù)測值 y‘ 的函數(shù)
- 綠色曲線是它的似然函數(shù),即exp( - l )
- 橙色曲線表示損失函數(shù)的梯度,當(dāng) 距離 > 1 的時候他的值為1,當(dāng) 距離 < 1 的時候他的值為-1
- 絕對值函數(shù)在0點處不可導(dǎo)
- 在梯度下降的時候是根據(jù)負(fù)梯度方向來更新梯度,它的梯度永遠(yuǎn)是常數(shù),所以就算 y 和 y‘ 離得比較遠(yuǎn),參數(shù)更新的幅度也不會太大,會帶來穩(wěn)定性上的好處
- 它的缺點是在 0 點處不可導(dǎo),另外在 0 點處有一個 -1 到 1 的劇烈變化,不平滑性導(dǎo)致預(yù)測值和真實值靠的比較近的時候,也就是優(yōu)化到了末期的時候這個地方可能就變得不那么穩(wěn)定
Huber's Robust Loss(Huber魯棒損失)

- 當(dāng)真實值和預(yù)測值相差較大時,他是一個絕對誤差,而當(dāng)他們相差較小時是一個均方誤差
- 減去 1 / 2 的作用是使得分段函數(shù)的曲線能夠連起來
- 藍(lán)色曲線表示 y = 0 的時候變化預(yù)測值 y‘ 的函數(shù),在 -1 到 1 之間是一個比較平滑的二次函數(shù),在這個區(qū)間外是一條直線
- 綠色曲線是它的似然函數(shù),即exp( - l ),和高斯分布有點類似,但是不想絕對值誤差那樣在 0 點處有一個很尖的地方
- 橙色曲線表示損失函數(shù)的梯度,當(dāng) 距離 > 1 的時候他的值為 -1 或者 1,是一個常數(shù),當(dāng)距離 < 1 的時候是一個漸變的過程,這樣的好處是當(dāng)預(yù)測值和真實值差得比較遠(yuǎn)的時候,參數(shù)值的更新比較均勻,而當(dāng)預(yù)測值和真實值相差比較近的時候,即在參數(shù)更新的末期,梯度會越來越小,保證優(yōu)化過程是比較平滑的,不會出現(xiàn)數(shù)值上的劇烈變化
圖片分類數(shù)據(jù)集
MNIST數(shù)據(jù)集(對手寫數(shù)字的識別)是圖像分類中廣泛使用的數(shù)據(jù)集之一,但是作為基準(zhǔn)數(shù)據(jù)集過于簡單,因此使用類似但是更復(fù)雜的Fashion-MNIST數(shù)據(jù)集
1、導(dǎo)包
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
- torchvision:pytorch對于機器學(xué)習(xí)實現(xiàn)的一個庫
- from torch.utils import data:方便讀取數(shù)據(jù)的一些小批量的函數(shù)
- transforms:對數(shù)據(jù)進行操作的模塊
- from d2l import torch as d2l:將一些函數(shù)實現(xiàn)好之后存在 d2l 中
- d2l.use_svg_display():使用 svg 來顯示圖片,清晰度會更高一些
2、通過框架中的內(nèi)置函數(shù)將Fashion-MNIST數(shù)據(jù)集下載并讀取到內(nèi)存中
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../data",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data",train=False,transform=trans,download=True)
len(mnist_train),len(mnist_test)
- trans = transforms.ToTensor():將圖片轉(zhuǎn)換成pytorch中的tensor,通過ToTensor實例將圖像數(shù)據(jù)從PIL類型變換成32位浮點數(shù)格式,并除以255使得所有像素的數(shù)值均在 0 到 1 之間
- train=True:代表下載的是訓(xùn)練集數(shù)據(jù)集
- download=True:可以將數(shù)據(jù)集加載好然后放在指定的目錄下就可以不用download了
- train=False:代表下載的是測試集數(shù)據(jù)集
- 報錯:<urlopen error [WinError 10060] 由于連接方在一段時間后沒有正確答復(fù)或連接的主機沒有反應(yīng),連接嘗試失敗。>,科學(xué)上網(wǎng)之后可以解決這個問題
輸出:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Using downloaded and verified file: ../data\FashionMNIST\raw\train-images-idx3-ubyte.gz
Extracting ../data\FashionMNIST\raw\train-images-idx3-ubyte.gz to ../data\FashionMNIST\raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Using downloaded and verified file: ../data\FashionMNIST\raw\train-labels-idx1-ubyte.gz
Extracting ../data\FashionMNIST\raw\train-labels-idx1-ubyte.gz to ../data\FashionMNIST\raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ../data\FashionMNIST\raw\t10k-images-idx3-ubyte.gz
100.0%
Extracting ../data\FashionMNIST\raw\t10k-images-idx3-ubyte.gz to ../data\FashionMNIST\raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ../data\FashionMNIST\raw\t10k-labels-idx1-ubyte.gz
119.3%
Extracting ../data\FashionMNIST\raw\t10k-labels-idx1-ubyte.gz to ../data\FashionMNIST\raw
(60000, 10000)
mnist_train[0][0].shape
輸出:
torch.Size([1, 28, 28])
- torch.Size([1, 28, 28]):因為它是一個黑白圖片,所以channel數(shù)是 1
3、定義兩個可視化數(shù)據(jù)集的函數(shù)
def get_fashion_mnist_labels(labels): ?#@save
? ? """返回Fashion-MNIST數(shù)據(jù)集的文本標(biāo)簽。"""
? ? 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_images(imgs, num_rows, num_cols, titles=None, scale=1.5): ?#@save
? ? """Plot a list of images."""
? ? figsize = (num_cols * scale, num_rows * scale)
? ? _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
? ? axes = axes.flatten()
? ? for i, (ax, img) in enumerate(zip(axes, imgs)):
? ? ? ? if torch.is_tensor(img):
? ? ? ? ? ? # 圖片張量
? ? ? ? ? ? ax.imshow(img.numpy())
? ? ? ? else:
? ? ? ? ? ? # PIL圖片
? ? ? ? ? ? ax.imshow(img)
? ? ? ? ax.axes.get_xaxis().set_visible(False)
? ? ? ? ax.axes.get_yaxis().set_visible(False)
? ? ? ? if titles:
? ? ? ? ? ? ax.set_title(titles[i])
? ? return axes
4、幾個樣本的圖像及其相應(yīng)的標(biāo)簽
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));
- next:拿到第一個小批量
輸出:

- X.reshape(18, 28, 28):18張28 * 28的圖片
- 2, 9:2 行 9 列
- titles=get_fashion_mnist_labels(y):圖片的名稱顯示其對應(yīng)的標(biāo)號
5、讀取一小批量數(shù)據(jù),大小為batch_size
batch_size = 256
def get_dataloader_workers(): ?#@save
? ? """使用4個進程來讀取數(shù)據(jù)。"""
? ? return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,num_workers=get_dataloader_workers())
timer = d2l.Timer()
for X, y in train_iter:
? ? continue
f'{timer.stop():.2f} sec'
- 定義一個函數(shù) get_dataloader_workers() 每次返回 4 ,使用 4 個進程數(shù)來進行讀取,可以根據(jù) cpu 的大小來尋阿澤一個小一點的數(shù)或者大一點的數(shù)
- shuffle=True:指定是否打亂順序
- num_workers=get_dataloader_workers():分配多少個進程,這里給定的是 4
- timer = d2l.Timer():timer 用來測試速度
輸出:
'4.00 sec'
- 讀取一次數(shù)據(jù)的時間是 4 秒
- 經(jīng)常會遇到一個性能問題:模型訓(xùn)練速度比較快,但是數(shù)據(jù)讀不過來,訓(xùn)練之前可以看一下數(shù)據(jù)讀取的速度,一般要比模型的訓(xùn)練速度要快,這是一個常見瓶頸
6、整合 load_data_fashion_mnist() 函數(shù)
def load_data_fashion_mnist(batch_size, resize=None): ?#@save
? ? """下載Fashion-MNIST數(shù)據(jù)集,然后將其加載到內(nèi)存中。"""
? ? trans = [transforms.ToTensor()]
? ? if resize:
? ? ? ? trans.insert(0, transforms.Resize(resize))
? ? trans = transforms.Compose(trans)
? ? mnist_train = torchvision.datasets.FashionMNIST(
? ? ? ? root="../data", train=True, transform=trans, download=True)
? ? mnist_test = torchvision.datasets.FashionMNIST(
? ? ? ? root="../data", train=False, transform=trans, download=True)
? ? return (data.DataLoader(mnist_train, batch_size, shuffle=True,
? ? ? ? ? ? ? ? ? ? ? ? ? ? num_workers=get_dataloader_workers()),
? ? ? ? ? ? data.DataLoader(mnist_test, batch_size, shuffle=False,
? ? ? ? ? ? ? ? ? ? ? ? ? ? num_workers=get_dataloader_workers()))
- resize:是否將圖片變得更大,后續(xù)可能需要更大的圖片
softmax回歸的從零開始實現(xiàn)
1、導(dǎo)包
import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
- batch_size = 256:每次隨機讀取256張圖片
- d2l.load_data_fashion_mnist(batch_size):調(diào)用存儲在 d2l 中的讀取數(shù)據(jù)集的函數(shù)(上面已經(jīng)實現(xiàn)了)
- train_iter, test_iter:返回訓(xùn)練集和測試集的迭代器
2、展平每個圖像,將他們視為長度為 784 的向量,因為數(shù)據(jù)集有 10 個類別,所以網(wǎng)絡(luò)的輸出維度是 10
num_inputs = 784
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
- 每張圖片都是 28 *28,通道數(shù)為 1.是一個三維的輸入,但是對于 softmax 回歸來講,它的輸入需要是一個向量,所以需要將圖片拉長成為一個向量
- 這個“拉長”的操作會損失很多的空間信息,這里留給卷積神經(jīng)網(wǎng)絡(luò)來提取特征信息
- W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True):定義權(quán)重 W,將他初始為一個高斯隨機分布的值,均值為 0,方差為 0.01.它的形狀是行數(shù)等于輸入的個數(shù),列數(shù)等于輸出的個數(shù),因為要計算梯度,所以requires_grad=True
- b = torch.zeros(num_outputs, requires_grad=True):定義偏移量 b:它的長度等于輸出的個數(shù),因為要計算梯度,所以requires_grad=True
3、回顧:給定一個矩陣 X ,對所有元素求和
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
- 0:將 shape 中的第 0 個元素變成 1,這就變成了一個行向量
- 1:將 shape 中的第 1 個元素變成 1,這就變成了一個列向量
輸出:
(tensor([[5., 7., 9.]]),
tensor([[ 6.],[15.]]))
4、實現(xiàn) softmax

def softmax(X):
? ? X_exp = torch.exp(X)
? ? partition = X_exp.sum(1, keepdim=True)
? ? return X_exp / partition ?# 這里應(yīng)用了廣播機制
- torch.exp(X):將 X 的每一個元素做指數(shù)運算
- X_exp.sum(1, keepdim=True):按照 shape 中的第一個元素求和,即按照每一行來進行求和
5、將每一個元素變成一個非負(fù)數(shù)。根據(jù)概率原理,每行的總和為 1
X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
X_prob, X_prob.sum(1)
- X = torch.normal(0, 1, (2, 5)):創(chuàng)建一個均值為 0,方差為 1 的2行5列的矩陣 X
輸出:
(tensor([[0.3740, 0.1609, 0.1182, 0.0572, 0.2897],
[0.2156, 0.2721, 0.1700, 0.3240, 0.0184]]),
tensor([1., 1.]))
6、實現(xiàn) softmax 回歸模型
def net(X):
? ? return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
7、實現(xiàn)交叉熵?fù)p失
補充:創(chuàng)建一個數(shù)據(jù) y_hat,其中包含 2 個樣本在 3 個類別的預(yù)測概率,使用 y 作為 y_hat 的索引
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
- y_hat[[0, 1], y]給定一個預(yù)測值,然后拿出給定標(biāo)號的真實類別的預(yù)測值是多少。對應(yīng)[0][0],[1][2]
輸出:
tensor([0.1000, 0.5000])
8、實現(xiàn)交叉熵?fù)p失函數(shù)
def cross_entropy(y_hat, y):
? ? return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
輸出:
tensor([2.3026, 0.6931])
9、將預(yù)測類別和真實 y 元素進行比較
def accuracy(y_hat, y): ?#@save
? ? """計算預(yù)測正確的數(shù)量。"""
? ? if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
? ? ? ? y_hat = y_hat.argmax(axis=1)
? ? cmp = y_hat.type(y.dtype) == y
? ? return float(cmp.type(y.dtype).sum())
accuracy(y_hat, y) / len(y)
輸出:
0.5
10、評估在任意模型 net 的準(zhǔn)確率
def evaluate_accuracy(net, data_iter): ?#@save
? ? """計算在指定數(shù)據(jù)集上模型的精度。"""
? ? if isinstance(net, torch.nn.Module):
? ? ? ? net.eval() ?# 將模型設(shè)置為評估模式
? ? metric = Accumulator(2) ?# 正確預(yù)測數(shù)、預(yù)測總數(shù)
? ? for X, y in data_iter:
? ? ? ? metric.add(accuracy(net(X), y), y.numel())
? ? return metric[0] / metric[1]
11、Accumulator實例中創(chuàng)建了 2 個變量,用于分別儲存正確預(yù)測的數(shù)量和預(yù)測的總數(shù)量
class Accumulator: ?#@save
? ? """在`n`個變量上累加。"""
? ? def __init__(self, n):
? ? ? ? self.data = [0.0] * n
? ? def add(self, *args):
? ? ? ? self.data = [a + float(b) for a, b in zip(self.data, args)]
? ? def reset(self):
? ? ? ? self.data = [0.0] * len(self.data)
? ? def __getitem__(self, idx):
? ? ? ? return self.data[idx]
evaluate_accuracy(net, test_iter)
輸出:
0.1274
12、softmax回歸的訓(xùn)練