一、步驟
1 加載數據集fashion_minst
2 搭建class NeuralNetwork模型
3 設置損失函數,優化器
4 編寫評估函數
5 編寫訓練函數
6 開始訓練
7 繪制損失,準確率曲線
二、代碼
導包,打印版本號:
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import sklearn
import pandas as pd
import os
import sys
import time
from tqdm.auto import tqdm
import torch
import torch.nn as nn
import torch.nn.functional as Fprint(sys.version_info)
for module in mpl, np, pd, sklearn, torch:print(module.__name__, module.__version__)device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu")
print(device)
torch的運算過程都是張量,也叫算子(tensor)
torchvision的包可以提供數據集,圖片就是datasets:
這里下載到data目錄,如果已有數據則不會下載。這段代碼可以實現數據向tensor的轉換:
做預處理的時候把圖片變成tensor,啥都沒寫的時候就不會轉換成tensor?
from torchvision import datasets
from torchvision.transforms import ToTensor
from torchvision import transforms# 定義數據集的變換
transform = transforms.Compose([
])
# fashion_mnist圖像分類數據集,衣服分類,60000張訓練圖片,10000張測試圖片
train_ds = datasets.FashionMNIST(root="data",train=True,download=True,transform=transform
)test_ds = datasets.FashionMNIST(root="data",train=False,download=True,transform=transform
)# torchvision 數據集里沒有提供訓練集和驗證集的劃分
# 當然也可以用 torch.utils.data.Dataset 實現人為劃分
type(train_ds[0]) # 元組,第一個元素是圖片,第二個元素是標簽
如果使用了數據類型變換:
img_tensor, label = train_ds[0]
img_tensor.shape #img這時是一個tensor,shape=(1, 28, 28)
在PyTorch中,DataLoader
是一個迭代器,它封裝了數據的加載和預處理過程,使得在訓練機器學習模型時可以方便地批量加載數據。DataLoader
主要負責以下幾個方面:
-
批量加載數據:
DataLoader
可以將數據集(Dataset)切分為更小的批次(batch),每次迭代提供一小批量數據,而不是單個數據點。這有助于模型學習數據中的統計依賴性,并且可以更高效地利用GPU等硬件的并行計算能力。 -
數據打亂:默認情況下,
DataLoader
會在每個epoch(訓練周期)開始時打亂數據的順序。這有助于模型訓練時避免陷入局部最優解,并且可以提高模型的泛化能力。 -
多線程數據加載:
DataLoader
支持多線程(通過參數num_workers
)來并行地加載數據,這可以顯著減少訓練過程中的等待時間,尤其是在處理大規模數據集時。 -
數據預處理:
DataLoader
可以與transforms
結合使用,對加載的數據進行預處理,如歸一化、標準化、數據增強等操作。 -
內存管理:
DataLoader
負責管理數據的內存使用,確保在訓練過程中不會耗盡內存資源。 -
易用性:
DataLoader
提供了一個簡單的接口,可以很容易地集成到訓練循環中。
# 從數據集到dataloader
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32, shuffle=True) #batch_size分批,shuffle洗牌
val_loader = torch.utils.data.DataLoader(test_ds, batch_size=32, shuffle=False)
這里每32個樣本就會算一次平均損失,更新一次w。
定義模型:繼承nn.Module
class NeuralNetwork(nn.Module):def __init__(self):super().__init__() # 繼承父類的初始化方法,子類有父類的屬性self.flatten = nn.Flatten() # 展平層self.linear_relu_stack = nn.Sequential(nn.Linear(784, 300), # in_features=784, out_features=300, 784是輸入特征數,300是輸出特征數nn.ReLU(), # 激活函數nn.Linear(300, 100),#隱藏層神經元數100nn.ReLU(), # 激活函數nn.Linear(100, 10),#輸出層神經元數10 )def forward(self, x): # 前向計算,前向傳播# x.shape [batch size, 1, 28, 28],1是通道數x = self.flatten(x) # print(f'x.shape--{x.shape}')# 展平后 x.shape [batch size, 784]logits = self.linear_relu_stack(x)# logits.shape [batch size, 10]return logits #沒有經過softmax,稱為logitsmodel = NeuralNetwork()
model的結構:第一層是展平層,然后激活,然后隱藏層,激活,輸出層
?在訓練之前需要測試一下模型能不能用,所以我們隨機一個或者從樣本拿一個,同尺寸就行:
#為了查看模型運算的tensor尺寸
x = torch.randn(32, 1, 28, 28)
print(x.shape)
logits = model(x) # 把x輸入到模型中,得到logits
print(logits.shape)
?然后開始訓練,pytorch的訓練需要自行實現,包括定義損失函數、優化器、訓練步,訓練
# 1. 定義損失函數 采用交叉熵損失
loss_fct = nn.CrossEntropyLoss() #內部先做softmax,然后計算交叉熵
# 2. 定義優化器 采用SGD
# Optimizers specified in the torch.optim package,隨機梯度下降
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
from sklearn.metrics import accuracy_score # sk里面有一個算子,可以計算準確率@torch.no_grad() # 裝飾器,禁止反向傳播,節省內存,就是不求導的意思
def evaluating(model, dataloader, loss_fct): # 評估函數,評估也要做一次向前計算,不需要求梯度loss_list = [] # 記錄損失pred_list = [] # 記錄預測label_list = [] # 記錄標簽for datas, labels in dataloader:#10000/32=312datas = datas.to(device) # 轉到GPUlabels = labels.to(device) # 轉到GPU 這兩行代碼torch必寫,把tensor放到GPU上# 前向計算logits = model(datas) # 進行前向計算loss = loss_fct(logits, labels) # 驗證集損失,loss尺寸是一個數值loss_list.append(loss.item()) # 記錄損失,item是把tensor轉換為數值preds = logits.argmax(axis=-1) # 驗證集預測,argmax返回最大值索引,-1就是最后一個維度print(f'評估中的preds.shape--{preds.shape}')pred_list.extend(preds.cpu().numpy().tolist())#將PyTorch張量轉換為NumPy數組。只有當張量在CPU上時,這個轉換才是合法的# print(preds.cpu().numpy().tolist())label_list.extend(labels.cpu().numpy().tolist())acc = accuracy_score(label_list, pred_list) # 計算準確率return np.mean(loss_list), acc
# 訓練
def training(model, train_loader, val_loader, epoch, loss_fct, optimizer, eval_step=500):#參數分別是模型,訓練集,驗證集,訓練epoch,損失函數,優化器,評估步數(500評估一次)record_dict = { # 記錄字典,用于記錄訓練過程中的信息"train": [],"val": []}global_step = 0 # 全局步數,記錄訓練的步數model.train() # 進入訓練模式,模型可以切換模式#tqdm是一個進度條庫with tqdm(total=epoch * len(train_loader)) as pbar: # 進度條 加入epoch等于10,就是所有樣本搞10次,不斷地把樣本帶進去學習,1875*10,60000/32=1875for epoch_id in range(epoch): # 訓練epoch次# trainingfor datas, labels in train_loader: #執行次數是60000/32=1875datas = datas.to(device) #datas尺寸是[batch_size,1,28,28]labels = labels.to(device) #labels尺寸是[batch_size]# 梯度清空optimizer.zero_grad() # 每次訓練前都要把梯度清空,不然會累加# 模型前向計算logits = model(datas)# 計算損失loss = loss_fct(logits, labels)# 梯度回傳,loss.backward()會計算梯度,loss對模型參數求導loss.backward()# 調整優化器,包括學習率的變動等,優化器的學習率會隨著訓練的進行而減小,更新w,boptimizer.step() #梯度是計算并存儲在模型參數的 .grad 屬性中,優化器使用這些存儲的梯度來更新模型參數preds = logits.argmax(axis=-1) # 訓練集預測acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy()) # 計算準確率,numpy可以,每個step都算一次loss = loss.cpu().item() # 損失轉到CPU,item()取值,一個數值# tensor如果只有一個值(標量),一維是向量,二維是矩陣,可以用item()取出值,如果有多個值,則需要用tolist()轉為列表# record# recordrecord_dict["train"].append({"loss": loss, "acc": acc, "step": global_step}) # 記錄訓練集信息,每一步的損失,準確率,步數# evaluatingif global_step % eval_step == 0:model.eval() # 進入評估模式,不會求梯度val_loss, val_acc = evaluating(model, val_loader, loss_fct)record_dict["val"].append({"loss": val_loss, "acc": val_acc, "step": global_step})model.train() # 進入訓練模式# udate stepglobal_step += 1 # 全局步數加1pbar.update(1) # 更新進度條pbar.set_postfix({"epoch": epoch_id}) # 設置進度條顯示信息return record_dictepoch = 20 #改為40
model = model.to(device)
record = training(model, train_loader, val_loader, epoch, loss_fct, optimizer, eval_step=1000)
#畫線要注意的是損失是不一定在零到1之間的
def plot_learning_curves(record_dict, sample_step=1000):# build DataFrametrain_df = pd.DataFrame(record_dict["train"]).set_index("step").iloc[::sample_step]val_df = pd.DataFrame(record_dict["val"]).set_index("step")last_step = train_df.index[-1] # 最后一步的步數# print(train_df.columns)print(train_df['acc'])print(val_df['acc'])# plotfig_num = len(train_df.columns) # 畫幾張圖,分別是損失和準確率fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))for idx, item in enumerate(train_df.columns):# print(train_df[item].values)axs[idx].plot(train_df.index, train_df[item], label=f"train_{item}")axs[idx].plot(val_df.index, val_df[item], label=f"val_{item}")axs[idx].grid() # 顯示網格axs[idx].legend() # 顯示圖例axs[idx].set_xticks(range(0, train_df.index[-1], 5000)) # 設置x軸刻度axs[idx].set_xticklabels(map(lambda x: f"{int(x/1000)}k", range(0, last_step, 5000))) # 設置x軸標簽axs[idx].set_xlabel("step")plt.show()plot_learning_curves(record) #橫坐標是 steps
# dataload for evaluatingmodel.eval() # 進入評估模式
loss, acc = evaluating(model, val_loader, loss_fct)
print(f"loss: {loss:.4f}\naccuracy: {acc:.4f}")