import torch
import torchvision
import numpy as np
import sys
import random
import torchvision.transforms as transforms
sys.path.append('..')
import d2lzh_pytorch as d2l
獲取和讀取數據
我們將使用Fahsion_MNIST數據集,並設置批量大小為256
batch_size= 256
mnist_train= torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',download=True,train=True,transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',download=True,train=False,transform=transforms.ToTensor())
if sys.platform.startswith('win'):
num_worker=0 # 表示不用額外的進程來加速讀取數據
else:
num_worker=4
train_iter = torch.utils.data.DataLoader(mnist_train,batch_size=batch_size,shuffle=True,num_workers=num_worker)
test_iter = torch.utils.data.DataLoader(mnist_test,batch_size=batch_size,shuffle=False,num_workers=num_worker)
初始化模型參數
與線性回歸中的例子一樣,我們將使用向量表示每個樣本。已知每個樣本輸入是高和寬均為像素28的圖像,模型輸入向量的長度為28*28=784;該向量的每個元素對應圖中每個元素。由於圖像有10個類別,單層神經網絡輸出層的輸出個數為10,因此softmax回歸的權重和偏差參數分別是784x10 和1x10的矩陣。
num_inputs= 784
num_outputs = 10
def set_seed(seed=9699): # seed的數值可以隨意設置,本人不清楚有沒有推薦數值
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
#根據文檔,torch.manual_seed(seed)應該已經為所有設備設置seed
#但是torch.cuda.manual_seed(seed)在沒有gpu時也可調用,這樣寫沒什么壞處
torch.cuda.manual_seed(seed)
#cuDNN在使用deterministic模式時(下面兩行),可能會造成性能下降(取決於model)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
set_seed(9699) # 保證同一個隨機種子產生的訓練結果一致
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)
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], requires_grad=True)
實現softmax運算
'''
在實現softmax回歸之前,我們先描述回下對多維Tensor按照維度進行操作。
跟之前pandas的里面對於DataFrame的操作一樣,對於axis或者維度的進行操作
下面給定一個Tensor矩陣X,可以對於其中同一列dim=0或者同一行的元素進行操作,
並在結果中保留行和列兩個維度(keepdim=True)
'''
X= torch.tensor([[1,2,3],[4,5,6]])
print(X)
print('沿着列進行操作\n',X.sum(dim=0,keepdim=True))
print('沿着行進行操作\n',X.sum(dim=1,keepdim=True))
tensor([[1, 2, 3],
[4, 5, 6]])
沿着列進行操作
tensor([[5, 7, 9]])
沿着行進行操作
tensor([[ 6],
[15]])
'''
1. 先規定X矩陣的中行數為樣本數,列數為輸出個數。為了表示樣本預測各個輸出的概率,
2. softmax運算會先通過exp函數對每個元素做指數運算,在對exp矩陣同行元素求和
3. 然后令矩陣每行元素與該行元素之和相除。
4. 這樣,最終得到的矩陣每行元素和為1且非負,符合概率分布。
5. softmax運算的輸出矩陣中的任一行元素代表了一個樣本在各個輸出類別上個預測概率
'''
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1,keepdim=True)
return X_exp/partition
X = torch.rand((2,5))
X_prob = softmax(X)
print(X_prob)
print(X_prob.sum(dim=1))
tensor([[0.2524, 0.1500, 0.2120, 0.1873, 0.1982],
[0.2157, 0.1602, 0.3160, 0.1392, 0.1689]])
tensor([1., 1.])
定義模型
有了softmax運算,我們就可以定義上節描述的回歸模型了。這個通過view函數將每張原始圖像改成長度為num_inputs的向量
def net(X):
return softmax(torch.mm(X.view(-1,num_inputs),W)+b)
定義損失函數
上節中,我們介紹了softmax回歸使用的交叉熵損失函數。為了得到標簽的預測概率,我們可以使用father函數。在下面的例子中,變量y_hat是2個樣本在3個類別的預測概率,變量y是兩個樣本的標簽類別。使用gather函數,我們得到了2個樣本的標簽預測概率
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))
# 這里需要單獨講一下,y.view(-1,1)是將y變形成2行1列的tensor
# 然后從y_hat中去取第一行中的第一個元素和第二行中的第三個元素
tensor([[0.1000],
[0.5000]])
def cross_entropy(y_hat,y):
return - torch.log(y_hat.gather(1,y.view(-1,1)))
cross_entropy(y_hat,y)
tensor([[2.3026],
[0.6931]])
計算分類准確率
-
給定一個類別的預測概率分布y_hat,我們預測概率最大的類別作為輸出類別。如果它與真實的類別y一致,說明預測正確,分類准確率=正確預測數量/總預測量只比
-
為了演示准確率的計算,下面定義准確率定義函數
def accuracy(y_hat,y):
return (y_hat.argmax(dim=1)==y).float().mean().item()
print(accuracy(y_hat,y))
0.5
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
# print(evaluate_accuracy(test_iter,net))
# 初始化模型后對測試集的准確率
訓練模型
softmax回歸於實現線性回歸類似,同樣適用小批量隨機梯度下降來優化模型的損失函數。在訓練模型時,迭代周期數num_epochs和學習率lr都是可以調節的超參數。
def sgd(params,lr,batch_size):
for param in params:
param.data -=lr* param.grad/batch_size
num_epochs, lr = 5, 0.1
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:
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.7850, train acc 0.750, test acc 0.786
epoch 2, loss 0.5705, train acc 0.814, test acc 0.810
epoch 3, loss 0.5254, train acc 0.825, test acc 0.814
epoch 4, loss 0.5017, train acc 0.832, test acc 0.822
epoch 5, loss 0.4854, train acc 0.836, test acc 0.826
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])
小結
- 獲取並讀取數據,拆分數據集
- 定義模型和損失函數並使用優化算法訓練模型
- 評估模型