导入所需模块:
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import time
import sys
对数据集的操作(读取数据集):
由于像素值为0到255的整数,所以刚好是uint8所能表示的范围,包括transforms.ToTensor()在内的一些关于图片的函数就默认输入的是uint8型,若不是,可能不会报错但可能得不到想要的结果。所以,如果用像素值(0-255整数)表示图片数据,那么一律将其类型设置成uint8,避免不必要的bug
通过torchvision的torchvision.datasets来下载这个数据集。第一次调用时数据集目录不存在,会自动从网上获取数据。
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
获取标签:
def get_fashion_mnist_labels(labels):
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
定义画图函数:
def use_svg_display():
# 用矢量图显示
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
# 设置图的尺寸
plt.rcParams['figure.figsize'] = figsize
def show_fashion_mnist(images, labels):
use_svg_display()
# 这里的_表示我们忽略(不使用)的变量
_, figs = plt.subplots(1, len(images), figsize=(12, 12))
for f, img, lbl in zip(figs, images, labels):
f.imshow(img.view((28, 28)).numpy())
f.set_title(lbl)
f.axes.get_xaxis().set_visible(False)
f.axes.get_yaxis().set_visible(False)
plt.show()
展示数据集中的前10个样本:
X, y = [], []
for i in range(10):
X.append(mnist_train[i][0])
y.append(mnist_train[i][1])
show_fashion_mnist(X, get_fashion_mnist_labels(y))
PyTorch的DataLoader允许使用多进程来加速数据读取
通过参数num_workers来设置4个进程读取数据:
batch_size = 256
if sys.platform.startswith('win'):
num_workers = 0 # 0表示不用额外的进程来加速读取数据
else:
num_workers = 4
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
初始化模型参数:
num_inputs = 784 # 784个样本
num_outputs = 10 # 分为10类
W = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_outputs)), dtype=torch.float)
b = torch.zeros(num_outputs, dtype=torch.float)
# 模型参数梯度回调
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
定义softmax运算:
softmax运算会先通过exp函数对每个元素做指数运算,再对exp矩阵同行元素求和,最后令矩阵每行各元素与该行元素之和相除。这样一来,最终得到的矩阵每行元素和为1且非负。因此,该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率。
def softmax(X):
#对于随机输入,每个元素变成了非负数,且每一行和为1
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True) # 按行求和
return X_exp / partition # 这里应用了广播机制,会自动对partition进行扩展到与X_exp尺度相同
定义模型:
def net(X): # 定义模型
return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
def cross_entropy(y_hat, y): # 交叉熵损失函数
return - torch.log(y_hat.gather(1, y.view(-1, 1)))
def accuracy(y_hat, y): # 定义准确率计算
return (y_hat.argmax(dim=1) == y).float().mean().item()
因为,模型中使用到的线性规划层:输出=输入*w+b
其中w是一个(输入,输出)维度的张量,b是一个(输出)维度的向量
根据矩阵运算,输入*w+b
的结果就是一个向量,该向量列数为输出(也就是类别数),而softmax做的,就是将该向量的元素进行归一化处理,使得其变为概率向量,这样,该模型的输出就可以代表样本为某一类别的概率,其和为1.
训练模型:
num_epochs, lr = 5, 0.1
def sgd(params, lr, batch_size): # 随机梯度下降算法
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
params=None, lr=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()
# 梯度清零
if optimizer is not None:
optimizer.zero_grad() # 这里我们没有用到优化器,所以直接对参数进行梯度清零
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
if optimizer is None:
sgd(params, lr, batch_size)
else:
optimizer.step() # 简洁实现将用到优化器这里
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)
注意,这里的每一个train_iter是以batch_size个样本为实例的,所以每一个train_iter包含多个(tensor, type)组合。
X, y = iter(test_iter).next()
true_labels = get_fashion_mnist_labels(y.numpy())
pred_labels = get_fashion_mnist_labels(net(X).argmax(dim=1).numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:9], titles[0:9])