由于对论文实现的代码难读懂、难修改,因此想重新过一遍 pytorch
学习内容
1.Pytorch 代码学习
学习时间
2024.02.08 — 2024.02.21
学习笔记
Sample
Working with data
pytorch 中的 data,可以从某些包中下载,也可以自己准备,可以使用 dataloader 来封装数据,此处以 torchvision 中的包举例,使用方式如下:
from torchvision import datasets
# 从开放数据集下载训练数据,transform 和 target_transform 用于修改 samples 与 labels
training_data = datasets.FashionMNIST(
root = "data",
train = True,
download = True,
transform = ToTensor(),
)
# 从开放数据集下载测试数据
test_data = datasets.FashionMNIST(
root = "data",
train = False,
download = True,
transform = ToTensor(),
)
out:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/26421880 [00:00<?, ?it/s]
0%| | 65536/26421880 [00:00<01:11, 366389.07it/s]
1%| | 229376/26421880 [00:00<00:38, 687630.18it/s]
4%|3 | 950272/26421880 [00:00<00:11, 2202823.76it/s]
15%|#4 | 3833856/26421880 [00:00<00:02, 7637882.18it/s]
33%|###2 | 8683520/26421880 [00:00<00:01, 14589224.17it/s]
55%|#####4 | 14516224/26421880 [00:01<00:00, 20633893.19it/s]
76%|#######6 | 20119552/26421880 [00:01<00:00, 24023791.16it/s]
97%|#########7| 25755648/26421880 [00:01<00:00, 26323438.74it/s]
100%|##########| 26421880/26421880 [00:01<00:00, 18304165.24it/s]
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
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/29515 [00:00<?, ?it/s]
100%|##########| 29515/29515 [00:00<00:00, 329618.67it/s]
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
0%| | 0/4422102 [00:00<?, ?it/s]
1%|1 | 65536/4422102 [00:00<00:11, 364394.28it/s]
5%|5 | 229376/4422102 [00:00<00:06, 685703.15it/s]
19%|#9 | 851968/4422102 [00:00<00:01, 2446943.99it/s]
39%|###9 | 1736704/4422102 [00:00<00:00, 3650327.29it/s]
100%|##########| 4422102/4422102 [00:00<00:00, 6114444.02it/s]
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
0%| | 0/5148 [00:00<?, ?it/s]
100%|##########| 5148/5148 [00:00<00:00, 33218887.68it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
然后把数据放在 dataloader 中封装起来,设定每个 batch 为 64
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:
print(f"Shape of X [N, C, H, W]: {X.shape}")
print(f"Shape of y: {y.shape} {y.dtype}")
break
out:
Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])
Shape of y: torch.Size([64]) torch.int64
Creating Models
使用 nn.Module 创建网络,并在 __init__ 中定义 layers
# Get cpu, gpu or mps device for training.
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
print(f"Using {device} device")
# Define model
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork().to(device)
print(model)
out:
Using cuda device
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
Optimizing the Model Parameters
训练模型需要 loss 与 optimizer,定义:
loss_fn = nn.CrossEntropyLoss()
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)
# Compute prediction error
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
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}]")
test 函数来计算精度:
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")
定义迭代数
epochs = 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("Done!")
out:
Epoch 1
-------------------------------
loss: 2.303494 [ 64/60000]
loss: 2.294637 [ 6464/60000]
loss: 2.277102 [12864/60000]
loss: 2.269977 [19264/60000]
loss: 2.254235 [25664/60000]
loss: 2.237146 [32064/60000]
loss: 2.231055 [38464/60000]
loss: 2.205037 [44864/60000]
loss: 2.203240 [51264/60000]
loss: 2.170889 [57664/60000]
Test Error:
Accuracy: 53.9%, Avg loss: 2.168588
Epoch 2
-------------------------------
loss: 2.177787 [ 64/60000]
loss: 2.168083 [ 6464/60000]
loss: 2.114910 [12864/60000]
loss: 2.130412 [19264/60000]
loss: 2.087473 [25664/60000]
loss: 2.039670 [32064/60000]
loss: 2.054274 [38464/60000]
loss: 1.985457 [44864/60000]
loss: 1.996023 [51264/60000]
loss: 1.917241 [57664/60000]
Test Error:
Accuracy: 60.2%, Avg loss: 1.920374
Epoch 3
-------------------------------
loss: 1.951705 [ 64/60000]
loss: 1.919516 [ 6464/60000]
loss: 1.808730 [12864/60000]
loss: 1.846550 [19264/60000]
loss: 1.740618 [25664/60000]
loss: 1.698733 [32064/60000]
loss: 1.708889 [38464/60000]
loss: 1.614436 [44864/60000]
loss: 1.646475 [51264/60000]
loss: 1.524308 [57664/60000]
Test Error:
Accuracy: 61.4%, Avg loss: 1.547092
Epoch 4
-------------------------------
loss: 1.612695 [ 64/60000]
loss: 1.570870 [ 6464/60000]
loss: 1.424730 [12864/60000]
loss: 1.489542 [19264/60000]
loss: 1.367256 [25664/60000]
loss: 1.373464 [32064/60000]
loss: 1.376744 [38464/60000]
loss: 1.304962 [44864/60000]
loss: 1.347154 [51264/60000]
loss: 1.230661 [57664/60000]
Test Error:
Accuracy: 62.7%, Avg loss: 1.260891
Epoch 5
-------------------------------
loss: 1.337803 [ 64/60000]
loss: 1.313278 [ 6464/60000]
loss: 1.151837 [12864/60000]
loss: 1.252142 [19264/60000]
loss: 1.123048 [25664/60000]
loss: 1.159531 [32064/60000]
loss: 1.175011 [38464/60000]
loss: 1.115554 [44864/60000]
loss: 1.160974 [51264/60000]
loss: 1.062730 [57664/60000]
Test Error:
Accuracy: 64.6%, Avg loss: 1.087374
Done!
Saving Models
保存模型的一种常见方法是序列化内部状态字典(包含模型参数)
torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")
out:
Saved PyTorch Model State to model.pth
Loading Models
将模型的状态字典加载
model = NeuralNetwork().to(device)
model.load_state_dict(torch.load("model.pth"))
out:
<All keys matched successfully>
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}"')
out:
Predicted: "Ankle boot", Actual: "Ankle boot"
Tensors
Initializing a Tensor
张量,形如矩阵,跟 numpy 中的 ndarrays 类似,且可以进行可微优化
可以直接赋值创建:
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
也可以用 numpy 数组赋值:
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
可以从其它 tensor 赋值,且保留原属性
x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")
out:
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.8823, 0.9150],
[0.3829, 0.9593]])
随机或连续值:
shape 是一个元组,用于表示张量的维度
shape = (2, 3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
out:
Random Tensor:
tensor([[0.3904, 0.6009, 0.2566],
[0.7936, 0.9408, 0.1332]])
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
Attributes of a Tensor
张量的属性可以表示其 shape,datatype,device
tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
out:
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
Operations on Tensors
张量可以进行矩阵的各种操作,张量在 cpu 上创建,通过方法 .to 移动到 gpu 上
# We move our tensor to the GPU if available
if torch.cuda.is_available():
tensor = tensor.to("cuda")
张量的切片等操作:
tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
out:
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
使用 .cat 进行张量的连接:
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
out:
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
张量运算:
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` returns the transpose of a tensor
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)
# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
out:
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
张量求和:
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
就地操作,一般有下标 _
表示,直接对张量本身操作并保存,不过就地操作不能保存导数,因此不建议使用
print(f"{tensor} \n")
tensor.add_(5)
print(tensor)
out:
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.]])
Bridge with Numpy
以下是将 numpy 数组与 tensor 关联起来的方法,会被同时修改:
t = torch.ones(5)
print(f"t:{t}")
n = t.numpy()
print(f"n:{n}")
out:
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
此时修改:
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
out:
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
Numpy array to Tensor
n = np.ones(5)
t = torch.from_numpy(n)
也会一起被修改:
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
out:
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
Datasets & DataLoaders
Loading a Dataset
两个与数据相关的类:torch.utils.data.DataLoader 和 torch.utils.data.Dataset,可以使用预加载的数据集与自己数据的数据集
以下参数分别表示为:
root
表示数据保存在哪里train
表示是训练集还是验证集download
下载不在 root 下的数据集transform
和target_transform
指定特征与标签转换
其中 ToTensor 可以将 rgb 图片或 ndarray 转换为 tensor
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
out:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/26421880 [00:00<?, ?it/s]
0%| | 65536/26421880 [00:00<01:12, 361149.71it/s]
1%| | 229376/26421880 [00:00<00:38, 678951.19it/s]
4%|3 | 950272/26421880 [00:00<00:11, 2180701.53it/s]
15%|#4 | 3833856/26421880 [00:00<00:02, 7578931.78it/s]
38%|###7 | 9928704/26421880 [00:00<00:00, 16941643.61it/s]
58%|#####7 | 15302656/26421880 [00:01<00:00, 25068182.24it/s]
70%|####### | 18513920/26421880 [00:01<00:00, 22779451.04it/s]
92%|#########1| 24248320/26421880 [00:01<00:00, 30575619.30it/s]
100%|##########| 26421880/26421880 [00:01<00:00, 19280270.92it/s]
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
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/29515 [00:00<?, ?it/s]
100%|##########| 29515/29515 [00:00<00:00, 326616.61it/s]
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
0%| | 0/4422102 [00:00<?, ?it/s]
1%|1 | 65536/4422102 [00:00<00:12, 361175.34it/s]
5%|5 | 229376/4422102 [00:00<00:06, 679890.46it/s]
20%|## | 884736/4422102 [00:00<00:01, 2016042.92it/s]
80%|######## | 3538944/4422102 [00:00<00:00, 6981211.45it/s]
100%|##########| 4422102/4422102 [00:00<00:00, 6060603.32it/s]
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
0%| | 0/5148 [00:00<?, ?it/s]
100%|##########| 5148/5148 [00:00<00:00, 37748736.00it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
Iterating and Visualizing the Dataset
可视化数据集:
labels_map = {
0: "T-Shirt",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(training_data), size=(1,)).item()
img, label = training_data[sample_idx]
figure.add_subplot(rows, cols, i)
plt.title(labels_map[label])
plt.axis("off")
plt.imshow(img.squeeze(), cmap="gray")
plt.show()
Creating a Custom Dataset for your files
构建一个数据集需要写三个函数:__init__ __len__ __getitem__
对于 FashionMNIST 数据集,图片和标签是分开存放的。使用例:
其中 torchvision.io.read_image()
可以将图片转换为张量
import os
import pandas as pd
from torchvision.io import read_image
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
__init__
__init__
函数在实例化 Dataset 时会运行一次,初始化的内容包括图像、注释文件与这两个 transform 的目录
标签数据集形如:
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
__len__
返回数据集中样本的数量
def __len__(self):
return len(self.img_labels)
__getitem__
从给定索引 idx 的数据集中加载并返回一个样本,读取存储中的图像,并转换为张量(通过 read_image
),并在 self.img_labels 中的 csv 数据中检索相应的标签,调用上面的 transform 函数,并返回张量与对应的标签
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
Preparing your data for training with DataLoaders
为了方便迭代,shuffle,batch 等操作提供的一个简易的方式,如下:
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
Iterate through the DataLoader
每个迭代都会返回一批图像与标签
# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")