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

為老師講的手動實(shí)現(xiàn)代碼添加了更詳盡的注釋和可讀性加強(qiáng)!
1.數(shù)據(jù)讀取
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
import pandas as pd
def load_data_fashion_mnist(batch_size):
? ? '''技巧:及時(shí)封裝以前的函數(shù),避免出現(xiàn)代碼堆積,是一個(gè)值得學(xué)習(xí)的好習(xí)慣'''
? ? # 使用totensor將圖像從PIL類型轉(zhuǎn)換為32位數(shù)浮點(diǎn),并且會自動除以255歸一化至[0,1]
? ? trans=transforms.ToTensor()
? ? mnist_train=torchvision.datasets.FashionMNIST(root='./data',train=True,transform=trans,download=True) #train為True表示下載訓(xùn)練集
? ? mnist_test=torchvision.datasets.FashionMNIST(root='./data',train=False,transform=trans,download=True)
? ? return (data.DataLoader(mnist_train,batch_size,True,num_workers=4),data.DataLoader(mnist_test,batch_size,True,num_workers=4))
batch_size=256 #每次讀取256批次圖片
train_iter,test_iter=load_data_fashion_mnist(batch_size) #獲取兩個(gè)dataloader
2.展平圖像與初始化參數(shù)
視為28*28=784的向量,故輸入是784,由于數(shù)據(jù)集有10個(gè)類別,所以網(wǎng)絡(luò)輸出維度是10
num_inputs=784
num_outputs=10
W=torch.normal(0,0.01,size=(num_inputs,num_outputs),requires_grad=True) #權(quán)重矩陣784x10
b=torch.zeros(num_outputs,requires_grad=True) #偏差矩陣 10x1
3.定義softmax算符:
這里要掌握張量計(jì)算的方法
當(dāng)調(diào)?sum運(yùn)算符時(shí),我們可以指定保持在原始張量的軸數(shù),?不折疊求和的維度,如對(3,2)進(jìn)行按列求和,會得到(2,),設(shè)置keepdim為True就是(1,2)即一行,分別是兩列的和
def softmax(X):
? ? X_exp=torch.exp(X) #對每個(gè)元素進(jìn)行指數(shù)計(jì)算 這里X輸入是
? ? partition=X_exp.sum(1,keepdim=True) #這里是對行求和,獲得的是每行的和
? ? return X_exp/partition
4. 定義模型
1. 例如batch_size=256,則X這里是(256,784)代表256張圖片 每個(gè)圖片有28x28個(gè)像素(被展平成784)
2. 權(quán)重矩陣為(784,10)代表對每種輸出的各特征權(quán)重
3. 因此256x784@784x10==256x10 即每張圖片會輸出十個(gè)類別的置信度,如衣服20% T恤60%……大衣5%,對十個(gè)輸出會加上一個(gè)bias,通過廣播機(jī)制作用到所有圖片
def net(X):
? ? return softmax(torch.matmul(X.reshape((-1,W.shape[0])),W)+b)
定義交叉熵?fù)p失
交叉熵只在意真實(shí)值的預(yù)測概率,因此這里是抽取每一行對應(yīng)是真實(shí)類別的預(yù)測概率的那個(gè)值,拿出來求負(fù)對數(shù)
例如第一個(gè)樣本應(yīng)該是2類(設(shè)共三類,從0-2),并預(yù)測概率是0.7,第二個(gè)樣本應(yīng)該是1類,預(yù)測是1類概率為0.5,則交叉熵?fù)p失為
$$-log(0.7) 和 -log(0.5)$$
def cross_entropy(y_pred,y_true):
? ? return -torch.log(y_pred[range(len(y_pred)),y_true]) #這里必須用range,相當(dāng)于遍歷每行,對該行找對應(yīng)的那個(gè)類的預(yù)測概率
#如果用y_pred[:,y_true] 相當(dāng)于返回指定的列,而不是概率
計(jì)算準(zhǔn)確率
def accuracy(y_pred,y_true):
? ? '''計(jì)算準(zhǔn)確率'''
? ? if len(y_pred.shape)>1 and y_pred.shape[1]>1:
? ? ? ? '''如果其是二維數(shù)組,即行數(shù)大于一(多個(gè)樣本),且有多列(多分類)'''
? ? ? ? y_pred=y_pred.argmax(axis=1) #對每行獲取其最大值的索引
? ? result=(y_pred==y_true) #在對應(yīng)位置比較是否預(yù)測正確,比如[2,3,4],[2,3,5]結(jié)果就是[True,True,False]
? ? return result.sum()
評估在任意模型上的準(zhǔn)確率
def evaluate_accuracy(net,data_iter):
? ? '''計(jì)算模型在指定數(shù)據(jù)集上的模型精度'''
? ? if isinstance(net,torch.nn.Module):
? ? ? ? net.eval() #設(shè)置為評估模式
? ? metric=Accumulator(2) #正確預(yù)測數(shù),預(yù)測總數(shù)
? ? for X,y in data_iter: #這里y是對應(yīng)的類別標(biāo)簽,因此后面numel實(shí)際是返回了這批樣本個(gè)數(shù)
? ? ? ? metric.add(accuracy(net(X),y),y.numel()) #numel number of element 返回元素總數(shù),例如2x3矩陣返回就是6個(gè)元素
? ? ? ? #這里表示把這批樣本預(yù)測的正確率和個(gè)數(shù)累加到accumulator中
? ? return metric[0]/metric[1] #預(yù)測正確數(shù)除以預(yù)測總數(shù)
#--------------實(shí)現(xiàn)accumulator-------------------
class Accumulator:
? ? def __init__(self,n) -> None:
? ? ? ? self.data=[0.0]*n
? ? def add(self,*args):
? ? ? ? self.data=[a+float(b) for a,b in zip(self.data,args)] #其實(shí)就是準(zhǔn)確率相加 樣本個(gè)數(shù)相加
? ? ? ? #如 (0.5,6)+(0.2,8)-->(0.7,14)
? ? def reset(self):
? ? ? ? self.data=[0.0]*len(self.data)
? ? def __getitem__(self,idx):
? ? ? ? return self.data[idx] #定義方括號索引
回歸訓(xùn)練和可視化
def train_epoch_ch3(net, train_iter, loss, updater):
? ? """訓(xùn)練模型?個(gè)迭代周期(定義?第3章) """
? ? # 將模型設(shè)置為訓(xùn)練模式
? ? if isinstance(net, torch.nn.Module):
? ? ? ? net.train() #開啟訓(xùn)練模式
? ? # 累計(jì)訓(xùn)練損失總和、訓(xùn)練準(zhǔn)確度總和、樣本數(shù)
? ? metric = Accumulator(3)
? ? for X, y in train_iter:
? ? # 計(jì)算梯度并更新參數(shù)
? ? ? ? y_hat = net(X) #計(jì)算預(yù)測值
? ? ? ? l = loss(y_hat, y) #交叉熵?fù)p失函數(shù)計(jì)算損失
? ? if isinstance(updater, torch.optim.Optimizer):
? ? ? ? # 使?PyTorch內(nèi)置的優(yōu)化器和損失函數(shù)
? ? ? ? updater.zero_grad() #例如小批量隨機(jī)梯度下降,需要手動清空梯度
? ? ? ? l.mean().backward()
? ? ? ? updater.step()
? ? else:
? ? ? ? # 使?定制的優(yōu)化器和損失函數(shù)
? ? ? ? l.sum().backward()
? ? ? ? updater(X.shape[0]) #根據(jù)批次大小update
? ? metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
? ? # 返回訓(xùn)練損失和訓(xùn)練精度,即損失值除以訓(xùn)練樣本數(shù)和精度處于訓(xùn)練樣本數(shù)
? ? return metric[0] / metric[2], metric[1] / metric[2]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
? ? """訓(xùn)練模型(定義?第3章) """
? ? result=[] #記錄損失,訓(xùn)練集精度,驗(yàn)證集精度
? ? for epoch in range(num_epochs):
? ? ? ? train_metrics = train_epoch_ch3(net, train_iter, loss, updater) #返回訓(xùn)練損失和訓(xùn)練精度
? ? ? ? test_acc = evaluate_accuracy(net, test_iter) #在驗(yàn)證集上的精度
? ? ? ? result.append([train_metrics[0],train_metrics[1],test_acc])
? ? result_df=pd.DataFrame(result,columns=['train loss','train accuracy','test accuracy'],index=range(1,len(result)+1))
? ? result_df.plot(xlabel='epoch',title='Softmax Regression Result')
? ? return result_df
def stochastic_gradient_descent(params,eta,batch_size):
? ? '''小批量隨機(jī)梯度下降'''
? ? with torch.no_grad(): #更新時(shí)不參與梯度計(jì)算
? ? ? ? for param in params:
? ? ? ? ? ? param-=eta*param.grad/batch_size #和公式相同
? ? ? ? ? ? param.grad.zero_() #清除梯度(避免累計(jì)梯度)
? ? ?
def updater(batch_size):
? ? return stochastic_gradient_descent([W,b],lr,batch_size)
lr=0.1 ?
num_epochs=10
result=train_ch3(net,train_iter,test_iter,cross_entropy,num_epochs,updater)