处理数据
Pytorch使用torch.utils.data.DataLoader
和torch.utils.data.Dataset
来处理数据,其中Dataset
用来存储数据集的样本和对应标签,DataLoader
根据输入的Dataset
和batch_size
生成可迭代对象
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
数据集
Pytorch中的torchtext
torchvision
torchaudio
提供数据集。其中,本次使用的torchvision.datasets
中提供了大量视觉方面的数据集,这里使用其中的FashionMNIST数据集,train
参数用来设置载入的是训练集还是测试集。
train_set = datasets.FashionMNIST(
root='./data/FashionMNIST',
train=True,
download=True,
transform=ToTensor()
)
test_set = datasets.FashionMNIST(
root='./data/FashionMNIST',
train=False,
download=True,
transform=ToTensor()
)
FashionMNIST训练集中有60000个数据,测试集中有10000个数据
每一条数据以元组的方式存储
元素第一项是以Tensor格式保存的图像,第二项是整数类型的标签
将数据集train_set
,batch_size
作为参数传送给DataLoader
,可以得到数据集的可迭代对象,支持自动批处理(batch_size)、打乱(shuffle)、多进程读取数据(num_workers)。
batch_size = 100
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=True)
for X, y in train_loader:
print(f'X shape:{X.shape}, X type:{type(X)}, len X:{len(X)}')
print(f'y shape:{y.shape}, y type:{type(y)}')
break
迭代DataLoader
可以得到处理好的一批次样本和标签,类型为Tensor,样本形状为[100,1,28,28],第一个维度是batch大小,第二个维度是图像的通道,这里是黑白图像只有一个通道,最后两个28是图像的宽高。标签的形状为[100],里面是100个整数类型的标签。
定义模型
Pytorch中的神经网络模型都可以从nn.Mudule
类中继承,利用nn.Sequential
函数可以快速搭建网络结构,本次实验的网络包含1个展平层nn.Faltten()
和3个线性层nn.Linear()
,线性层的参数为网络的输入和输出个数,线性层之间通过ReLU激活函数nn.ReLU()
。
网络的层数、每层的神经元个数都是超参数,需要根据问题的类型自行调整。
device = 'cuda' if torch.cuda.is_available() else 'cpu' # 选择GPU or CPU
#定义模型
class Network(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, 64),
nn.ReLU(),
nn.Linear(64, 10)
)
def forward(self, feature):
feature = self.flatten(feature)
pred = self.linear_relu_stack(feature)
return pred
network = Network().to(device)
print(network)
该网络最后有十个输出,因为FashionMNIST数据集共有十类,网络根据十个输出中的最大值决定是哪一类。
模型训练
训练模型首先要定义损失函数和优化器,对于分类问题一般使用交叉熵损失nn.CrossEntropyLoss
,该损失计算时会自动计算softmax,所以模型中不需要再加入softmax层。优化器这里使用torch.optim.SGD
优化器,将学习率lr
(learning rate)和要训练的网络的参数network.parameters
传递给优化器
lr = 0.001
#定义损失函数
loss_fn = nn.CrossEntropyLoss()
#定义优化器
optimizer = torch.optim.SGD(network.parameters(), lr=lr)
再定义一个函数来统计预测正确的个数,网络的输出应该是一个形状为[100,10]的Tensor,第二的维度中的最大值的下标是网络分类的结果,也就是preds.argmax(dim=1)
,其形状为[100],与labels 的形状相同。使用item()
可以将单元素Tensor直接转换成数值。
def get_num_correct(preds, labels):
#return preds.argmax(dim=1).eq(labels).sum().item() 也可以这么写
return (preds.argmax(dim=1) == labels).sum().item()
接下来开始训练模型,定义一个训练模型的函数,将要训练的网络、数据加载器、循环次数、损失函数、优化器作为参数输入。
训练时,函数会从train_loader
中遍历每一个batch的数据,送入网络进行预测,并将预测值与数据标签计算损失。接着计算梯度,Pytorch计算梯度时会自动将本次梯度与之前的梯度进行累加,因此计算之前需要先将梯度清零,然后利用backward
计算梯度,最后使用优化器更新网络参数。
#训练模型
def train(network, train_loader, epoch_num, loss_fn, optimizer):
for epoch in range(epoch_num):
total_loss = 0
total_correct = 0
for X, y in train_loader:
X, y = X.to(device), y.to(device)
# 前向通道 预测、计算loss
preds = network(X)
loss = loss_fn(preds, y)
# 反向通道 Backpropagation
optimizer.zero_grad() # 梯度清零
loss.backward() # 计算梯度
optimizer.step() # 更新参数
total_loss += loss.item()
total_correct += get_num_correct(preds, y)
accuracy = total_correct / len(train_loader.dataset)
print(f'epoch {epoch}, loss:{total_loss:.2f}, num_correct:{total_correct}, accuracy:{accuracy:.4f}')
epoch_num = 10
train(network, train_loader, epoch_num=epoch_num, loss_fn=loss_fn, optimizer=optimizer)
随着训练过程,模型的损失函数逐渐下降,预测的精确度逐渐上升
模型测试
最后需要测试模型在测试集上的表现,来确定模型的效果,定义一个函数
#测试模型
def test(network, test_loader, loss_fn):
network.eval() # 将模型设置为评估状态
test_loss, num_correct = 0, 0
batch_index = 0
with torch.no_grad():
for X, y in test_loader:
X, y = X.to(device), y.to(device)
preds = network(X)
loss = loss_fn(preds, y)
test_loss += loss.item()
num_correct += get_num_correct(preds, y)
print(f'batch {batch_index}, loss:{loss}')
batch_index += 1
print(f'test_loss:{test_loss}, num_correct:{num_correct}, accuracy:{num_correct/len(test_loader.dataset):.4f}')
test(network, test_loader=test_loader, loss_fn=loss_fn)
模型在测试集上的表现与训练集基本一致,根据训练集和测试集的表现,可以判断模型是否训练到位,以及是否发生过拟合。
参考来源:
[1]: https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html#optimizing-the-model-parameters