实践再次提醒我:基础是创新的源泉。道理就像「没有体力,什么战术都是扯」,一样简单直接。
有必要从头学习pytorch,清理死角夯实基础。点滴总结记录在案《滴水穿石pytorch》系列。
欢迎交流,不吝赐教~
学习资料:https://pytorch.org/tutorials/beginner/basics/intro.html
Machine Learning Workflow
通常机器学习包含几个主要流程:
- 训练:处理数据、创建网络、优化网络参数,最后生成并保存模型;
- 推理:数据导入模型,经过前向计算和必要的后处理后,输出推理结果。
Working with Data
Pytorch 中有两个和数据相关的原语(Primitives:通常表示一组不可分割的操作,应被当作整体对待):
- Dataset:数据的集合,包含样本、标签等信息,见 torch.utils.data.Dataloader
- Dataloader:数据集的迭代器,支持对数据的操作,如 download、transform、split 等,见 torch.utils.data.Dataset
通常,我们直接使用 domain-specific libraries,如 TorchVision 的 from torchvision import datasets
,它包含了很多著名的数据集如 Imagenet、CIFAR、COCO(详细清单)。
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
train_data = datasets.FashionMnist(root="data", train=True, download=True, transform=ToTensor())
test_data = datasets.FashionMnist(root="data", train=False, download=True, transform=ToTensor())
train_dataloader = DataLoader(train_data, batch_size=10)
test_dataloader = DataLoader(test_data, batch_size=10)
from 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
Creating Models
nn.Module 是 pytorch 的一个重要基类,可用于创建 neural network。
通常在 init 函数中定义网络的 layers,并在 forward 函数中定义数据如何经过前向传播过程。.to(device) 接口可以将计算从 CPU 转移至 GPU 以加速。
device = 'cuda' if torch.cuda.is_available() else 'cpu'
class DemoNN(nn.Module):
def __init__(self):
super(DemoNN, self).__init__()
self.flatten = nn.Flatten() # 拉平向量的layer
self.linear = nn.Sequential( # 定义MLP(multi-layer perceptron)
nn.Linear(28*28, 512), # 全连接层:输入dim=784,输出dim=512,默认包含bias
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear(x)
return logits
model = DemoNN().to(device)
print(model)
Optimizing the Model Parameters
模型训练本质是不断调参的过程,最终生成的模型等价于一个复杂的拟合函数,归纳了训练数据中隐藏的规律。调参过程需要 loss function 和 optimizer 参与。
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
注意,模型的参数(model.parameters)作为 optimizer 函数的入参之一传入。
Common Training
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
mode.train()
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
# prediction
pred = model(X)
# loss
loss = loss_fn(pred, y)
# back propagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
# logging
if batch % 100 == 0:
loss = loss.item() # get tensor value
current = batch * len(X)
print(f'loss: {loss:>7f} [{current:>5d}/{size:>5d}]')
Common Evaluation
def eval(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader) # catch the difference with above
model.eval()
test_loss, correct = 0, 0
with torch.no_grad(): # Important! No update on gradients!
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")
Saving Models
最常见的保存 pytorch 模型的方式:仅保存状态字典(state dictionary),它包含了 model parameters。由于未保存完整的网络结构,因此,加载时需要先重建 model structure。
# 从 model 获取 state dict
state_dict = model.state_dict()
# 使用 torch.save 将内存中的 state dict 保存至文件
torch.save(state_dict, 'model.pth')
Loading Models
以下是加载 state dictionary 模型的方式,加载完成后即可开始推理。
# 创建 model
model = NeuralNetwork()
# 使用 torch.load 从文件加载 state dict 至内存中
state_dict = torch.load('model.pth')
# 将内存中的 state dict 赋值到 model parameters 上
model.load_state_dict(state_dict)