import matplotlib.pyplot as plt import torch import torchvision from d2l import torch as d2l from IPython import display d2l.use_svg_display() """ # 1, 读取数据集 # 1.1,通过ToTensor实例将图像数据从PIL类型变换为32位浮点数格式。 # 并除以255使得所有像素数值均在0~1之间 trans = torchvision.transforms.ToTensor() mnist_train_dataset = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True) mnist_test_dataset = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True) print(mnist_train_dataset[0][0].shape) def get_fashion_mnist_labels(labels): text_labels = ['t-shrit', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [text_labels[int(i)] for i in labels] # 1.2, 创建一个函数来可视化这些样本 def show_dataset_images(imgs, num_rows, num_cols, titles=None, scale=1.5): figsize = (num_cols*scale, num_rows*scale) _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize) axes = axes.flatten() for i, (ax, img) in enumerate(zip(axes, imgs)): if torch.is_tensor(img): # 图片张量 #ax.imshow(img.numpy()) plt.imshow(img.numpy()) else: # PIL图片 #ax.imshow(img) plt.imshow(img) ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) if titles: ax.set_title(titles[i]) return axes X, y = next(iter(torch.utils.data.DataLoader(mnist_train_dataset, batch_size=18))) show_dataset_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y)); batch_size1 = 256 """ # 2, 读取小批量 def get_dataloader_workers(): """使用4个进程来读取数据""" return 4 # 2.1, 为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器, # train_iter = torch.utils.data.DataLoader(mnist_train_dataset, batch_size1, shuffle=True, num_workers=get_dataloader_workers()) def load_data_fashion_mnist(batch_size, resize=None): #@save trans = [torchvision.transforms.ToTensor()] if resize: trans.insert(0, torchvision.transforms.Resize(resize)) trans = torchvision.transforms.Compose(trans) mnist_train_dataset = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True) mnist_test_dataset = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True) return (torch.utils.data.DataLoader(mnist_train_dataset, batch_size, shuffle=True, num_workers=get_dataloader_workers()), torch.utils.data.DataLoader(mnist_test_dataset, batch_size, shuffle=False, num_workers=get_dataloader_workers())) # 2.2 测试加载batch_size=32的样本。 train_iter1, test_iter1 = load_data_fashion_mnist(32, resize=64) for X1, Y1 in train_iter1: print(X1.shape, X1.dtype, Y1.shape, Y1.dtype) break # 3, Softmax回归从零开始实现 # 3.1 使用Fashion-MNIST数据集,并设置数据迭代器的批量大小为256. batch_size2 = 256 train_dataset_iterator, test_dataset_iterator = d2l.load_data_fashion_mnist(batch_size2, resize=None) # 4, 初始化模型参数 # 4.1 数据集中每个样本是28x28的图像,将该图像展平为一维向量为784长度, # 由于有10个类别,那么权重将是784x10的矩阵,偏置将构成一个1x10的行向量, # 我们仍然使用正态分布初始化权重Weight,偏置bais初始化为0. num_inputs = 784 num_outputs = 10 weight = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True) bais = torch.zeros(num_outputs, requires_grad=True) # 5, 定义softmax操作 # 5.1 使用sum运算符沿着Tensor的指定维度计算和。 t1 = torch.tensor([[1,2,3],[4,5,6]]) print(t1.sum(0, keepdim=True)) print(t1.sum(1, keepdim=True)) # 5.2 实现softmax函数,规范化结果。 def softmax(Predicted_value): exp_value = torch.exp(Predicted_value) SUM_partition = exp_value.sum(1, keepdim=True) return exp_value / SUM_partition # 5.3 随机输入,验证计算结果 predict_value1 = torch.normal(0, 1, (2,10)) normalize_value = softmax(predict_value1) print(normalize_value) print(normalize_value.sum(1, keepdim=True)) # 6,定义模型, 将输入层数据input_X,使用reshape函数将每张原始图像展平为向量。 def network(input_X): return softmax(torch.matmul(input_X.reshape((-1, weight.shape[0])), weight) + bais) # 7,定义损失函数,实现交叉熵损失函数 def cross_entropy(y_hat, y): return -torch.log(y_hat[range(len(y_hat)), y]) # 8, 分类精度,我们使用argmax获得每行中最大元素的索引来获得预测类别。然后我们将预测类别与真实y元素进行比较。由 # 于等式运算符“==”对数据类型很敏感,因此我们将y_hat的数据类型转换为与y的数据类型一致。结果是一 # 个包含0(错)和1(对)的张量。最后,我们求和会得到正确预测的数量 def accuracy(y_hat, y): print(y_hat.shape) if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: y_predicted_indices = y_hat.argmax(axis=1) print(y_predicted_indices) compare_result = y_predicted_indices.type(y.dtype) == y print(compare_result) return float(compare_result.type(y.dtype).sum()) # 8.1, 使用两个张量简单测试一下accuracy()函数。 test_y_hat = torch.tensor([(0.05, 0.05, 0.1, 0.15, 0.05, 0.45, 0.05, 0.02, 0.03, 0.05), (0.05, 0.45, 0.1, 0.15, 0.05, 0.05, 0.05, 0.02, 0.03, 0.05), (0.05, 0.05, 0.1, 0.15, 0.05, 0.15, 0.05, 0.37, 0.03, 0.05), (0.05, 0.05, 0.1, 0.15, 0.05, 0.15, 0.05, 0.01, 0.39, 0.05)]) test_y = torch.tensor([(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)]) test_y_index = torch.tensor([(5, 2, 7, 9)]) print(accuracy(test_y_hat, test_y_index)) def evaluate_accuracy(net, data_iter): if isinstance(net, torch.nn.Module): net.eval() metric = Accumulator(2) with torch.no_grad(): for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0]/metric[1] # 8.2, 构建多个变量累加的Accumulator类,在上面的evaluate_accuracy函数中,我们在 # Accumulator实例中创建了2个变量,分别用于存储正确预测的数量和预测的总数量,当我们 # 遍历数据集时,两者都将随着时间的推移而累加。 list1 = [x + y for x, y in [(1, 2),(3, 4),(5, 6)]] print(list1) list2 = [4, 5, 6, 7] list1 = [m + n for m, n in zip(list1, list2)] print(list1) class Accumulator: #@save def __init__(self, n): self.data = [0.0] * n def add(self, *args): self.data = [a + float(b) for a, b in zip(self.data, args)] def reset(self): self.data = [0.0] * len(self.data) def __getitem__(self, idx): return self.data[idx] # 9, 训练, 定义一个函数来训练一个迭代周期,updater是更新模型参数的常用函数,它接受 # 批量大小作为参数。它可以是d2l.sgd函数,也可以是框架的内置优化函数。 # 9.1 使用numel()函数统计张量Tensor中的元素element的个数。 x = torch.randn(3,3) print("number elements of x is ",x.numel()) y = torch.randn(3,10,5) print("number elements of y is ",y.numel()) def train_epoch_ch3(net, train_iter, updater): if isinstance(net, torch.nn.Module): net.train() metric = Accumulator(3) for X, y in train_iter: # 计算梯度并更新参数 y_hat = net(X) loss_value = cross_entropy(y_hat, y) if isinstance(updater, torch.optim.Optimizer): # 使用PyTorch内置的优化器和损失函数 updater.zero_grad() loss_value.mean().backward() updater.step() else: # 使用定制的优化器和损失函数 loss_value.sum().backward() updater(X.shape[0]) metric.add(float(loss_value.sum()), accuracy(y_hat, y), y.numel()) print("number elements of y is ", y.numel()) # 返回训练损失和训练精度 print("number is ", metric[2]) return metric[0] / metric[2], metric[1] / metric[2] # 9.2, 定义一个在动画中绘制数据的实用程序类Animator. class Animator: #@save def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None, xscale='linear', yscale='linear', fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1, figsize=(3.5, 2.5)): # 增量地绘制多条线 if legend is None: legend = [] d2l.use_svg_display() self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize) if nrows * ncols == 1: self.axes = [self.axes, ] # 使用lambda函数捕获参数 self.config_axes = lambda: d2l.set_axes( self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend) self.X, self.Y, self.fmts = None, None, fmts def add(self, x, y): # Add multiple data points into the figure if not hasattr(y, "__len__"): y = [y] n = len(y) if not hasattr(x, "__len__"): x = [x] * n if not self.X: self.X = [[] for _ in range(n)] if not self.Y: self.Y = [[] for _ in range(n)] for i, (a, b) in enumerate(zip(x, y)): if a is not None and b is not None: self.X[i].append(a) self.Y[i].append(b) self.axes[0].cla() for x, y, fmt in zip(self.X, self.Y, self.fmts): self.axes[0].plot(x, y, fmt) self.config_axes() display.display(self.fig) plt.draw() plt.pause(0.001) display.clear_output(wait=True) # 9.3, 实现训练函数,它会在train_iter访问的训练数据集上训练一个模型net,该训练函数 # 将会运行多个迭代周期(由num_epochs指定),在每个迭代周期接受时,利用test_iter访问到 # 的测试数据集对模型进行评估,然后利用Animator类来可视化训练进度。 def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9], legend=['train loss', 'train acc', 'test acc']) for epoch in range(num_epochs): train_metrics = train_epoch_ch3(net, train_iter, updater) test_acc = evaluate_accuracy(net, test_iter) animator.add(epoch+1, train_metrics+(test_acc,)) train_loss, train_acc = train_metrics assert train_loss < 0.5, train_loss assert train_acc <= 1 and train_acc > 0.7, train_acc assert test_acc <= 1 and test_acc > 0.7, test_acc # 9.4, 定义小批量随机梯度下降函数来优化模型的损失函数,注意updater是更新模型参数的常用函数,它接受 # 批量大小作为参数,它可以是d2l.sgd,也可以是Pytorch框架自带的内置优化函数。 """ def sgd(params, lr, batch_size): # 为了和原书保持一致,这里除以了batch_size,但是应该是不用除的,因为一般用PyTorch计算loss时就默认已经 # 沿batch维求了平均了。 for param in params: param.data = param.data - lr * param.grad / batch_size # 注意这里更改param时用的param.data """ learning_rate = 0.1 def updater(batch_size): return d2l.sgd([weight, bais], learning_rate, batch_size) num_epochs = 10 train_ch3(network, train_dataset_iterator, test_dataset_iterator, cross_entropy, num_epochs, updater) plt.show() # 10, 使用训练好的模型对图像进行分类预测。 def predict_ch3(net, test_iter, n=6): print("use model to predict data:") for X, y in test_iter: break y_predicted = net(X) y_predicted_indices = y_predicted.argmax(axis=1) print(y_predicted_indices) compare_result = y_predicted_indices.type(y.dtype) == y print(compare_result) trues = d2l.get_fashion_mnist_labels(y) preds = d2l.get_fashion_mnist_labels(y_predicted_indices) titles = [true + '\n' + pred for true, pred in zip(trues, preds)] d2l.show_images( X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n]) predict_ch3(network, test_dataset_iterator)
----------------------------------------------------------------------
一,训练过程epoch的损失函数变化和训练准确度变化:
二,使用训练后模型预测结果
tensor([9, 2, 1, 1, 6, 1, 4, 6, 5, 7, 4, 5, 5, 3, 4, 1, 2, 6, 8, 0, 6, 7, 7, 7,
1, 2, 6, 0, 9, 6, 8, 8, 3, 3, 8, 0, 7, 5, 7, 9, 6, 1, 6, 9, 6, 7, 6, 1,
2, 6, 6, 6, 5, 6, 2, 2, 8, 4, 8, 0, 7, 7, 8, 5, 1, 1, 6, 4, 7, 8, 7, 0,
6, 6, 2, 3, 1, 2, 8, 4, 1, 8, 5, 9, 5, 0, 3, 2, 0, 6, 5, 3, 6, 7, 1, 8,
0, 1, 6, 2, 3, 6, 7, 6, 7, 8, 5, 9, 9, 4, 2, 5, 7, 0, 5, 2, 8, 4, 7, 8,
0, 0, 9, 9, 3, 0, 8, 4, 1, 5, 4, 1, 9, 1, 8, 6, 6, 1, 2, 5, 1, 6, 0, 0,
1, 6, 1, 3, 2, 6, 6, 2, 1, 3, 5, 0, 4, 7, 9, 3, 7, 2, 3, 9, 0, 9, 4, 7,
4, 2, 6, 5, 2, 1, 2, 1, 3, 0, 9, 1, 0, 9, 3, 8, 7, 9, 9, 4, 4, 7, 1, 2,
1, 6, 3, 2, 8, 3, 6, 1, 1, 0, 2, 9, 2, 6, 0, 7, 9, 8, 4, 1, 8, 4, 1, 3,
1, 6, 7, 4, 8, 5, 6, 0, 7, 7, 6, 6, 7, 0, 7, 8, 9, 2, 9, 0, 5, 1, 4, 2,
5, 4, 9, 6, 2, 8, 6, 4, 6, 4, 9, 7, 4, 5, 5, 4])
tensor([ True, True, True, True, True, True, True, True, True, True,
True, True, False, True, True, True, True, False, True, True,
False, False, True, False, True, False, True, True, True, False,
True, True, True, True, True, True, True, True, True, True,
True, True, False, False, True, True, False, True, True, False,
False, False, True, False, True, True, True, True, True, True,
True, True, True, True, True, True, False, False, False, True,
True, True, False, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, False, True,
True, True, True, False, True, True, True, True, True, True,
True, True, True, True, True, True, True, False, True, True,
True, True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, False, True, True, True,
True, False, True, True, True, True, True, False, True, False,
False, False, True, False, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True, True,
False, True, True, True, True, True, True, True, True, True,
True, True, True, False, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True, True,
True, True, True, True, True, False, True, True, True, True,
True, True, True, True, True, True, True, True, True, False,
True, True, False, True, True, True, True, False, True, True,
True, True, True, True, True, True, True, True, True, False,
True, False, True, False, False, True, True, True, False, False,
True, True, False, True, True, False])