本节可参看线性回归的从零开始实现,两者流程类似,故本节不再详述。
在上一节中已经自定义实现的函数,本节中直接使用作者提供的 d2lzh_pytorch模块。

3.6.1 获取和读取数据

  1. # 获取数据集
  2. DATA_SETS_PATH = "~/My-Project/Python学习/PyTorch学习/知乎马卡斯扬-动手学深度学习PyTorch版/Data-Sets"
  3. # 训练集
  4. training_set = torchvision.datasets.FashionMNIST(root=DATA_SETS_PATH, train=True, download=True, transform=torchvision.transforms.ToTensor())
  5. # 测试集
  6. testing_set = torchvision.datasets.FashionMNIST(root=DATA_SETS_PATH, train=False, download=True, transform=torchvision.transforms.ToTensor())
  7. # 读取小批次数据
  8. batch_size = 256
  9. # 创建读取小批次数据的迭代器
  10. training_set_iter = torch.utils.data.DataLoader(training_set, batch_size=batch_size, shuffle=True, num_workers=10)
  11. testing_set_iter = torch.utils.data.DataLoader(testing_set, batch_size=batch_size, shuffle=True, num_workers=10)

3.6.2 初始化模型参数

softmax可以看成神经网络。用到的特征是一张 2828 的灰度图,因此输入层每一个元素对应一个像素点的灰度值。因为共有 10 个类别,输出层用 10 个单元表示各个类的置信度。所有表示权重的有 2828 个,偏差有 10 个。

  1. # 初始化模型参数
  2. feature_size = 28 * 28
  3. labels_size = 10
  4. elem_type = torch.float32
  5. w = torch.normal(0, 0.01, (feature_size, labels_size), dtype=elem_type, requires_grad=True)
  6. b = torch.zeros(labels_size, dtype=elem_type, requires_grad=True)

3.6.3 实现 softmax 运算

在实现 softmax 运算之前,我们首先要知道如何对 Tensor 的元素进行按维度求和。
dim 参数指定求和时所要沿着的方向,这和 NumPy 十分类似。缺省为对所有元素求和, dim=0 表示沿 x 轴(横向)对其他维度上所有元素求和, dim=1 表示沿 y 轴(纵向)对其他维度上所有元素求和。

  1. # 实现 softmax 运算
  2. # 对多维 Tensor 按维度操作
  3. x = torch.tensor([[1, 2, 3],
  4. [4, 5, 6]])
  5. print(x.sum(dim=0, keepdim=True))
  6. print(x.sum(dim=1, keepdim=True))

运行结果

  1. tensor([[5, 7, 9]])
  2. tensor([[ 6],
  3. [15]])

以此为基础,我们就可以自己实现 softmax 的运算了。
在我们的表示输出层的Tensor 中, a[i][j] 表示第 i 个样本第 j 类的概率。

  1. def softmax(x):
  2. """
  3. 对 x 进行 softmax 运算
  4. Args:
  5. x: 原始的输出结果
  6. Returns:
  7. Raises:
  8. """
  9. x_exp = x.exp()
  10. sum = x_exp.sum(dim=1, keepdim=True)
  11. return x_exp / sum
  12. x = torch.rand((2, 5))
  13. x_prob = softmax(x)
  14. print(x_prob, x_prob.sum(dim=1))

运行结果

  1. tensor([[0.2539, 0.1704, 0.2625, 0.1335, 0.1797],
  2. [0.1366, 0.1897, 0.2679, 0.1926, 0.2132]]) tensor([1.0000, 1.0000])

3.6.4 定义模型

对输入特征进行 softmax 运算即可。

  1. # 定义模型
  2. def net(x):
  3. """
  4. 以 x 为输入特征建立 softmax 模型
  5. Args:
  6. x: 特征
  7. Returns:
  8. Raises:
  9. """
  10. return softmax(torch.mm(x.view((-1, feature_size)), w) + b)

3.6.5 定义损失函数

定义损失函数前,我们需要先找到输出层中 正确标签对应的概率
gather() 方法可以实现按指定索引位置查找元素。 y_output.gather(1, y.view(-1, 1)) 中, 1 用于指定要沿着的维度,y.view(-1, 1)是一个表示描述索引位置的Tensor

  1. # 定义损失函数
  2. # 用 gather 方法就可以方便地找到正确标签位置上的概率
  3. y_output = torch.tensor([[0.1, 0.3, 0.6],
  4. [0.3, 0.2, 0.5]])
  5. y = torch.tensor([0, 2])
  6. print(y_output.gather(1, y.view(-1, 1)))

运行结果

  1. tensor([[0.1000],
  2. [0.5000]])

基于此,我们可以实现损失函数,最后返回一个标量

  1. def cross_entropy(y_output, y):
  2. """
  3. 计算交叉熵损失函数的值
  4. Args:
  5. y_output: 预测的标签值
  6. y: 正确的标签值
  7. Returns:
  8. 损失的值
  9. Raises:
  10. """
  11. return - torch.log(y_output.gather(1, y.view(-1, 1))).sum()

3.6.6 计算分类的准确性

argmax给出最大元素的索引值,可以指定维度。 .float() 将True /False 的布尔乐行转换为 1/0 的浮点数。
accuracy 用于计算某次分类后的准确性,evaluate_accuracy用于计算对整个数据集分类后的准确性。

  1. # 计算分类的准确率
  2. def accuracy(y_output, y):
  3. """
  4. 计算分类的准确率
  5. Args:
  6. y_output: 预测的标签值
  7. y: 正确的标签值
  8. Returns:
  9. 准确率
  10. Raises:
  11. """
  12. return (y_output.argmax(dim=1) == y).float().mean().item()
  13. print(accuracy(y_output, y))
  14. # 已在 d2lzh_pytorch 中的 evaluate_accuracy 实现
  15. def evaluate_accuracy(data_iter, net):
  16. """
  17. 计算分类的准确率
  18. Args:
  19. data_iter: 迭代器
  20. net: 模型
  21. Returns:
  22. 正确率
  23. Raises:
  24. """
  25. # d2lzh_pytorch.use_svg_display()
  26. # 按样本数量建立子图
  27. sum, n = 0.0, 0
  28. for x, y in data_iter:
  29. sum += (net(x).argmax(dim=1) == y).float().sum().item()
  30. n += y.shape[0]
  31. return sum / n
  32. print(evaluate_accuracy(testing_set_iter, net))

一开始随机初始化值后,分类的准确率近似3.6 softmax 回归的从零开始实现 - 图1

  1. 0.5
  2. 0.1144

3.6.7 训练模型

流程和之前都是一样的,不再赘述。

  1. # 训练模型
  2. iterate_num = 10
  3. loss_func = cross_entropy
  4. optimizer = d2l.sgd
  5. for i in range(iterate_num):
  6. for x, y in training_set_iter:
  7. y_output = net(x)
  8. current_lose = loss_func(y_output, y)
  9. current_lose.backward()
  10. optimizer([w, b], 0.1, batch_size)
  11. w.grad.data.zero_()
  12. b.grad.data.zero_()
  13. train_acc = accuracy(y_output, y)
  14. test_acc = evaluate_accuracy(testing_set_iter, net)
  15. print("第{0}次迭代后的损失为{1:.4f}, 训练准确率为{2:.4f}, 测试准确率为{3:.4f}".format(
  16. i + 1, current_lose, train_acc, test_acc))

运行结果

  1. 1次迭代后的损失为66.6867, 训练准确率为0.7188, 测试准确率为0.7890
  2. 2次迭代后的损失为45.0898, 训练准确率为0.8542, 测试准确率为0.8126
  3. 3次迭代后的损失为44.6852, 训练准确率为0.8229, 测试准确率为0.8123
  4. 4次迭代后的损失为32.5271, 训练准确率为0.9167, 测试准确率为0.8237
  5. 5次迭代后的损失为36.5115, 训练准确率为0.8542, 测试准确率为0.8264
  6. 6次迭代后的损失为38.3955, 训练准确率为0.8438, 测试准确率为0.8319
  7. 7次迭代后的损失为38.3066, 训练准确率为0.8229, 测试准确率为0.8301
  8. 8次迭代后的损失为40.7758, 训练准确率为0.8750, 测试准确率为0.8299
  9. 9次迭代后的损失为31.7146, 训练准确率为0.8542, 测试准确率为0.8321
  10. 10次迭代后的损失为25.3807, 训练准确率为0.9062, 测试准确率为0.8353

3.6.8 预测结果的可视化

  1. # 预测结果的可视化
  2. x, y = iter(testing_set_iter).next()
  3. true_labels = d2l.get_fashion_mnist_labels(y.numpy())
  4. predict_labels = d2l.get_fashion_mnist_labels(net(x).argmax(dim=1).numpy())
  5. titles = [true + '\n' + pred for true, pred in zip(true_labels, predict_labels)]
  6. d2l.show_fashion_mnist(x[0:9], titles[0:9])
  7. plt.show()

图片.png3.6 softmax 回归的从零开始实现.py