1. 配環境
主要需要torch
和torchvision
這兩個包
在官網下載torch-1.13.0+cpu-cp39-cp39-win_amd64.whl
和torchvision-0.10.0%2Bcpu-cp39-cp39-win_amd64.whl
,將其匯入Python
直譯器Anaconda的Scripts資料夾下,命令列輸入pip install torch-1.13.0+cpu-cp39-cp39-win_amd64.whl
和pip install torchvision-0.10.0%2Bcpu-cp39-cp39-win_amd64.whl
2. 導包
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
3. 下載資料
## 下載資料集
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
)
## 下載測試集
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),
)
4. 建立資料載入器
batch_size = 64
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)
for X, y in test_dataloader:
# N = batch_size 即批次大小
# C = Channels 即通道數 (R, G, B)
# H, W 為高寬
print(f"Shape of X [N, C, H, W]: {X.shape}")
print(f"Shape of y: {y.shape} {y.dtype}")
break
5. 建立模型
## 使用gpu or cpu 來訓練
device = (
"cuda"
if torch.cuda.is_available()
else "cpu"
)
print(f"Using {device} device")
## 定義模型類
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
## self.flatten = nn.Flatten() 是一個將輸入展平的層,用於將
## 輸入的影像資料展平為一維向量。對於 MNIST 資料集中的影像,其
## 大小為 28x28,經過展平層後,輸入的大小變為 784。
self.flatten = nn.Flatten()
## self.linear_relu_stack 是一個包含多個線性層和啟用函式 ReLU
## 的序列(nn.Sequential)。這個序列由三個線性層組成,分別是輸入
## 層到隱藏層的線性層、隱藏層到隱藏層的線性層和隱藏層到輸出層的線
## 性層。這些線性層透過 nn.Linear 定義,指定輸入和輸出的維度。激
## 活函式 ReLU 在每個線性層之後被應用。
self.linear_relu_stack = nn.Sequential(
nn.Linear(28 * 28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
## 在 forward 方法中,首先將輸入資料 x 透過 self.flatten 層展平為一
## 維向量。然後將展平後的資料傳入 self.linear_relu_stack 序列,得到
## 輸出 logits。
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork().to(device)
print(model)
6. 訓練模型
## 定義損失函式和最佳化器
## loss_fn = nn.CrossEntropyLoss() 建立了一個交叉熵損失函式的例項。交叉
## 熵損失函式通常用於多分類問題,對於每個樣本,它計算模型的輸出與真實標籤之
## 間的交叉熵損失。在這裡,我們的模型輸出是一個包含對每個類別的預測機率的張
## 量,而真實標籤是類別的索引。損失函式會根據這些預測機率和真實標籤計算損失值。
loss_fn = nn.CrossEntropyLoss()
## optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) 建立了一
## 個隨機梯度下降(SGD)最佳化器的例項。最佳化器用於更新模型的引數,以最小化損
## 失函式。在這裡,我們將模型的引數傳遞給最佳化器,透過 model.parameters()
## 獲取模型的可學習引數。lr=1e-3 指定了學習率,它控制每次引數更新的步長。
## 學習率越大,引數更新得越快,但可能會導致不穩定的訓練過程;學習率越小,
## 引數更新得越慢,但可能需要更多的訓練時間。
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
## 定義訓練過程
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train()
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
## 計算預測誤差
pred = model(X)
loss = loss_fn(pred, y)
## 反向傳播
loss.backward()
optimizer.step()
optimizer.zero_grad()
if batch % 100 == 0:
loss, current = loss.item(), (batch + 1) * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
7. 測試模型
## 定義測試過程
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
8. 多次迭代訓練
pochs = 5
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
print("over")
9. 儲存和載入模型
## 儲存訓練模型
torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")
## 載入模型
model = NeuralNetwork().to(device)
model.load_state_dict(torch.load("model.pth"))
10. 測試效果
classes = [
"T-shirt/top",
"Trouser",
"Pullover",
"Dress",
"Coat",
"Sandal",
"Shirt",
"Sneaker",
"Bag",
"Ankle boot",
]
model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
x = x.to(device)
pred = model(x)
predicted, actual = classes[pred[0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')