PyTorch 学习笔记 1 —— Quick Start

这是 PyTorch 学习笔记 的第一篇博客,学了一点点皮毛,先记录下来!


1. 环境检查

首先确认电脑是否有 GPU,有 GPU 记得安装对应版本的 CUDA 和支持 GPU 版本的 Pytorch,参考 PyTorch 环境搭建:Win11 + mx450,使用下面的 code 检查 GPU 是否可用:

import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor


print(torch.__version__)
print(torch.cuda.is_available())

打印结果为 True,说明可以使用 GPU,接着指定使用 GPU(如果没有 GPU 或者安装的 PyTorch 版本不支持 GPU,则会自动设置 device 为 cpu):

# Get cup or gpu devices for trianing
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")

2. 数据集下载与预处理

2.1 Download dataset

使用 torchvision 库中的 dataset 模块在官网下载数据集:

# Download training data frm open datasets
training_data = datasets.FashionMNIST(
	root="data",
	train=True,
	download=True,
	transform=ToTensor()
)

# download test data from open datasets
test_data = datasets.FashionMNIST(
	root="data",
	train=False,
	download=True,
	transform=ToTensor()
)

其中,参数含义如下:

  • root is the path where the train/test data is stored,
  • train specifies training or test dataset,
  • download=True downloads the data from the internet if it’s not available at root.
  • transform and target_transform specify the feature and label transformations

执行代码后会把数据集下载到当前路径的 ./data/ 目录下,FashionMNIST 继承了 MNIST,MNIST 的构造函数中,指定 download 为 True 则会执行 self.download(),首先检查数据集是否已经在当前目录,如果在则不下载(可自行查看源代码)


2.2 读取数据集

使用 torch.util.data 中的 DataLoader 类读取数据集,指定 batchsize=64:

# create data loaders
batch_size = 64
train_dataloader = DataLoader(dataset=training_data, batch_size=batch_size)
test_dataloader = DataLoader(dataset=test_data, batch_size=batch_size)

print(len(train_dataloader))			# 938
print(len(train_dataloader.dataset))	# 60000

for X, y in test_dataloader:
	print(f'Shape of X [N, C, H, W]: {X.shape}')	# torch.Size([64, 1, 28, 28])
	print(f'Shape of y: {y.shape} {y.dtype}')		# torch.Size([64])
	break

DataLoader 对象可迭代,训练集总样本数目为 60000,batchsize=64,则可划分为 60000/64 ≈938 个 batch,每个 batch 有 64 个样本,每个样本的 shape 为 (1, 28, 28)


3. 模型构建

创建一个简单的分类 NLP 模型,

# Define model
class NeuralNetwork(nn.Module):
	def __init__(self):
		super(NeuralNetwork, self).__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)

注意,这里创建的 NeuralNetwork 对象调用了 .to(device),这样做的目的是,让模型能够在 GPU 上跑,实际上所有数据也需要调用 .to(device)

模型的构造函数中有模型的结构,forward() 为前向传播函数,通过举例说明每个层的作用:

nn.Flatten() 可以创建Flatten的对象 flatten,flatten 可以将输入的 matrix 拉伸为 vector(默认将维度从 1 到 -1 向量化):

input_image = torch.rand(3, 28, 28)
print(input_image.size())	# torch.Size([3, 28, 28])

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())	# torch.Size([3, 784])

nn.Linear() 创建 lenear 层,可以进行矩阵运算,in_featuresout_features 分别为进行矩阵相乘使用的矩阵的行数和列数,可以理解为输入特征维数和输出特征维数:

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())		# torch.Size([3, 20])

nn.ReLU() 为 ReLU 激活函数,将输入中所有负元素置为零,不改变输入输出的特征维数:

print(f'Before ReLU: {hidden1}\n\n')
relu = nn.ReLU()
hidden1 = relu(hidden1)
# hidden1 = nn.ReLU()(hidden1)
print(f'After ReLU: {hidden1}')

nn.Sequential() 可以将不同的小的模块拼接成更大的网络:

squ_models = nn.Sequential(
	flatten,
	layer1,
	nn.ReLU(),
	nn.Linear(20, 10)
)

4. 模型训练与测试

4.1 train model

# Train model
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model.parameters(), lr=0.3)

其中,nn.CrossEntropyLoss() 为交叉熵损失函数,optimizer 用来优化参数,使用方法为:

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

创建的 optimizer 对象将 [‘params’, ‘lr’, ‘momentum’, ‘dampening’, ‘weight_decay’, ‘nesterov’] 这 6 个参数存放到由 6 组键值对构成的字典 param_group 中,由于是传递列表,因此 optimizer 可以修改模型的参数

训练函数:

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)

		# Back propagation
		optimizer.zero_grad()
		loss.backward()
		optimizer.step()

		# print message for each 100 batches
		if batch % 100 == 0:
			loss, current = loss.item(), batch * len(X)
			print(f'loss: {loss:>7f} [{current:>5d} / {size:>5f}]')

首先调用了前面创建的 NeuralNetwork 模型 model 的 train(),第二行的 model.train() 的具体内容并不是真正的训练模型,而是将模型包括其中的 sub models 设置为 training mode(可以查看源代码,查看 NeuralNetwork 的父类 nn.Module 的 train 函数)

接着对 dataloader 中的每个 batch 中的数据,首先将 X 和 y 转为 GPU 上可训练的格式,先计算预测值 pred 和交叉熵损失 loss, loss 是 torch.Tensor 类型,使用方法如下:

>>> loss = nn.CrossEntropyLoss()
>>> input = torch.randn(3, 5, requires_grad=True)
>>> target = torch.empty(3, dtype=torch.long).random_(5)
>>> output = loss(input, target)
>>> output.backward()

反向传播

反向传播中,下面的结构很常见:

# Back propagation
optimizer.zero_grad()
loss.backward()
optimizer.step()

在反向传播时,首先调用 optimizer.zero_grad() ,它会遍历模型的所有参数,通过p.grad.detach_() 方法截断反向传播的梯度流,再通过 p.grad.zero_() 函数将每个参数的梯度值设为0,即上一次的梯度记录被清空。因为训练的过程通常使用mini-batch方法,所以如果不将梯度清零的话,梯度会与上一个batch的数据相关,因此该函数要写在反向传播和梯度下降之前。

接着调用 loss.backward() 计算模型的所有参数的梯度,此时模型参数不再为 0

最后,optimizer.step() 函数的作用是执行一次优化步骤,通过梯度下降法来更新模型的参数。因为梯度下降是基于梯度的,所以在执行 optimizer.step() 函数前,应先执行loss.backward() 函数来计算梯度。


4.2 test model

读取每一个 test_dataset 中的 batch,根据预测值计算 loss,打印信息:

model.eval()

# Test model
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}%, Average loss: {test_loss:>8f}\n')

model.eval(): Sets the module in evaluation mode. 即进入评估模式,在评估模式下,batchNorm层,dropout层等用于优化训练而添加的网络层会被关闭,从而使得评估时不会发生偏移。

在对模型进行评估时,应该配合使用 with torch.no_grad()model.eval() torch.no_grad() 设置模型的所有 tensor 参数的 requires_grad 属性为 False,此时模型的所有参数不会自动求导,模型的性能不会变化。


4.3 训练模型主函数

设置学习率 lr 为 0.3,训练了 5 个 epochs,模型的准确率达到 85% 左右:

if __name__ == '__main__':
	t1 = time.time()
	
	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!')

	t2 = time.time()
	print(f'Duration: {t2-t1:>0.2f} sec')

5. save and load models

保存模型

# Save models
torch.save(model.state_dict(), 'model.pth')
print('Saved PyTorch Model State to model.pth')

加载模型并测试

# Loading models
model = NeuralNetwork()
model.load_state_dict(torch.load('model.pth'))

# prediction
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():
	pred = model(X)
	predicted, actual = classes[pred[0].argmax(0)], classes[y]
	print(f'Predicted: "{predicted}", Actual: "{actual}"')

注意,直接在像 sublime 之类的编辑器上跑模型,输出有点多会导致卡顿,使用终端命令才是正确方式:

>>> python xxx.py

以上就是全部内容了!

studying…


REFERENCE:
1 . https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html

2 . with torch.no_grad() 用法详解

3 . 理解optimizer.zero_grad(), loss.backward(), optimizer.step()的作用及原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值