示例:经典的MNIST手写数字分类任务

关于MNIST数据集

手写字体MNIST数据集下载: THE MNIST DATABASE of handwritten digits

可以看到包括以下数据:

train-images-idx3-ubyte.gz: training set images (9912422 bytes)
train-labels-idx1-ubyte.gz: training set labels (28881 bytes)
t10k-images-idx3-ubyte.gz: test set images (1648877 bytes)
t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)

其中,训练数据包括60,000条,测试数据包括10,000条。每个数据包括一个28*28=784的图像数组和标签,获取MNIST数据集的代码如下:

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. reader = paddle.dataset.mnist.train()
  4. for image in reader():
  5. image = np.array(image)
  6. print(image[0].shape) # 数据内容 的形状为 28*28=784 的图像数组
  7. print(image[1]) # 标签
  8. # 获取第一张图片并显示
  9. first_image = image[0].reshape(28, 28)
  10. plt.imshow(first_image)
  11. break

📃 图像分类任务 - 图1

关于获取MNIST数据集的方法 paddle.dataset.mnist 参见:https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/data_cn/dataset_cn/mnist_cn.html

常规方式

以下示例,通过手动创建变量,定义网络的每个层,达到训练模型的目的:

  1. import paddle
  2. from paddle import fluid
  3. from functools import reduce
  4. import matplotlib.pyplot as plt
  5. import numpy as np
  6. # 定义输入的形状
  7. images = fluid.layers.data(name="pixel", shape=[1, 28, 28], dtype='float32')
  8. label = fluid.layers.data(name="label", shape=[1], dtype='int64')
  9. # 定义层
  10. conv_pool_1 = fluid.nets.simple_img_conv_pool(input=images, filter_size=5, num_filters=20, pool_size=2, pool_stride=2, act="relu")
  11. conv_pool_2 = fluid.nets.simple_img_conv_pool(input=conv_pool_1, filter_size=5, num_filters=50, pool_size=2, pool_stride=2, act="relu")
  12. SIZE = 10
  13. input_shape = conv_pool_2.shape
  14. param_shape = [reduce(lambda a, b: a * b, input_shape[1:], 1)] + [SIZE]
  15. scale = (2.0 / (param_shape[0] ** 2 * SIZE)) ** 0.5
  16. predict = fluid.layers.fc(input=conv_pool_2, size=SIZE, act="softmax", param_attr=fluid.param_attr.ParamAttr(
  17. initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=scale)
  18. ))
  19. # 损失函数及优化函数
  20. cost = fluid.layers.cross_entropy(input=predict, label=label)
  21. avg_cost = fluid.layers.mean(x=cost)
  22. opt = fluid.optimizer.AdamOptimizer(learning_rate=0.001, beta1=0.9, beta2=0.999)
  23. opt.minimize(avg_cost)
  24. # 加载数据
  25. reader = paddle.dataset.mnist.train()
  26. batched_reader = paddle.batch(reader, batch_size=200)
  27. # 执行器
  28. place = fluid.CPUPlace()
  29. exe = fluid.Executor(place)
  30. feeder = fluid.DataFeeder(feed_list=[images, label], place=place)
  31. exe.run(fluid.default_startup_program())
  32. # 开始训练
  33. losses = []
  34. for data in batched_reader():
  35. loss = exe.run(feed=feeder.feed(data), fetch_list=[avg_cost])
  36. losses.append(loss)
  37. print(loss)
  38. # 绘制损失值图像
  39. losses = np.array(losses)
  40. plt.plot(losses.flatten())

由于数据量巨大,而我是使用CPU进行的训练,足足训练了6min,输出结果:

  1. 运行时长: 632289毫秒
  1. [array([2.3050933], dtype=float32)]
  2. [array([2.1798933], dtype=float32)]
  3. [array([2.041367], dtype=float32)]
  4. [array([1.8695843], dtype=float32)]
  5. [array([1.7091033], dtype=float32)]
  6. [array([1.5944638], dtype=float32)]
  7. [array([1.3899304], dtype=float32)]
  8. [array([1.2142174], dtype=float32)]
  9. [array([0.93150413], dtype=float32)]
  10. [array([0.92134124], dtype=float32)]
  11. ......
  12. [array([0.02884364], dtype=float32)]
  13. [array([0.04368482], dtype=float32)]
  14. [array([0.02136619], dtype=float32)]
  15. [array([0.03527835], dtype=float32)]
  16. [array([0.01867639], dtype=float32)]
  17. [array([0.05465743], dtype=float32)]
  18. [array([0.00479105], dtype=float32)]
  19. [array([0.1301369], dtype=float32)]
  20. [array([0.04957756], dtype=float32)]
  21. [array([0.25675547], dtype=float32)]
  22. [array([0.13143504], dtype=float32)]

📃 图像分类任务 - 图2

网络模型

我们将其封装为网络模型,并将模型保存,方便调用:

  1. import paddle
  2. from paddle import fluid
  3. from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
  4. import matplotlib.pyplot as plt
  5. import numpy as np
  6. import json
  7. import gzip
  8. import random
  9. # 定义数据集读取器
  10. def load_data(mode='train'):
  11. # 读取本地数据文件
  12. datafile = './work/mnist.json.gz'
  13. print('loading mnist dataset from {} ......'.format(datafile))
  14. data = json.load(gzip.open(datafile))
  15. # 读取数据集中的训练集,验证集和测试集
  16. train_set, val_set, eval_set = data
  17. # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
  18. IMG_ROWS = 28
  19. IMG_COLS = 28
  20. # 根据输入mode参数决定使用训练集,验证集还是测试
  21. if mode == 'train':
  22. imgs = train_set[0]
  23. labels = train_set[1]
  24. elif mode == 'valid':
  25. imgs = val_set[0]
  26. labels = val_set[1]
  27. elif mode == 'eval':
  28. imgs = eval_set[0]
  29. labels = eval_set[1]
  30. # 验证图像数量和标签数量是否一致
  31. assert len(imgs) == len(labels), "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(labels))
  32. index_list = list(range(len(imgs)))
  33. # 读入数据时用到的batchsize
  34. BATCHSIZE = 100
  35. # 定义数据生成器
  36. def data_generator():
  37. # 训练模式下,打乱训练数据
  38. if mode == 'train':
  39. random.shuffle(index_list)
  40. # 每个batch中包含的数据
  41. imgs_list = []
  42. labels_list = []
  43. # 按照索引读取数据
  44. for i in index_list:
  45. # 读取图像和标签,转换其尺寸和类型
  46. img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
  47. label = np.reshape(labels[i], [1]).astype('int64')
  48. imgs_list.append(img)
  49. labels_list.append(label)
  50. # 如果当前数据缓存达到了batch size,就返回一个批次数据
  51. if len(imgs_list) == BATCHSIZE:
  52. yield np.array(imgs_list), np.array(labels_list)
  53. # 清空数据缓存列表
  54. imgs_list = []
  55. labels_list = []
  56. # 如果剩余数据的数目小于BATCHSIZE,
  57. # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
  58. if len(imgs_list) > 0:
  59. yield np.array(imgs_list), np.array(labels_list)
  60. return data_generator
  61. # 定义模型结构
  62. class MNIST(fluid.dygraph.Layer):
  63. def __init__(self):
  64. super(MNIST, self).__init__()
  65. # 定义一个卷积层,输出通道20,卷积核大小为5,步长为1,padding为2,使用relu激活函数
  66. self.conv1 = Conv2D(num_channels=1, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
  67. # 定义一个池化层,池化核为2,步长为2,使用最大池化方式
  68. self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
  69. # 定义一个卷积层,输出通道20,卷积核大小为5,步长为1,padding为2,使用relu激活函数
  70. self.conv2 = Conv2D(num_channels=20, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
  71. # 定义一个池化层,池化核为2,步长为2,使用最大池化方式
  72. self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
  73. # 定义一个全连接层,输出节点数为10
  74. self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
  75. # 定义网络的前向计算过程
  76. def forward(self, inputs):
  77. x = self.conv1(inputs)
  78. x = self.pool1(x)
  79. x = self.conv2(x)
  80. x = self.pool2(x)
  81. x = fluid.layers.reshape(x, [x.shape[0], -1])
  82. x = self.fc(x)
  83. return x
  84. # 配置在CPU还是GPU上训练
  85. use_gpu = False
  86. place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
  87. # 训练配置
  88. with fluid.dygraph.guard(place):
  89. model = MNIST()
  90. model.train()
  91. # 加载数据
  92. train_loader = load_data('train')
  93. # 异步读取数据
  94. # 定义DataLoader对象用于加载Python生成器产生的数据
  95. data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
  96. # 设置数据生成器
  97. data_loader.set_batch_generator(train_loader, places=place)
  98. #设置学习率
  99. optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01, parameter_list=model.parameters())
  100. # 定义一个损失值数组,用于在图像中直观展示
  101. losses = np.array([])
  102. # 设置训练次数
  103. EPOCH_NUM = 5
  104. # 开始训练
  105. for epoch_id in range(EPOCH_NUM):
  106. for batch_id, data in enumerate(train_loader()):
  107. #准备数据,变得更加简洁
  108. image_data, label_data = data
  109. image = fluid.dygraph.to_variable(image_data)
  110. label = fluid.dygraph.to_variable(label_data)
  111. #前向计算的过程
  112. predict = model(image)
  113. #计算损失,取一个批次样本损失的平均值
  114. loss = fluid.layers.cross_entropy(predict, label)
  115. avg_loss = fluid.layers.mean(loss)
  116. #每训练了100批次的数据,打印下当前Loss的情况
  117. if batch_id % 200 == 0:
  118. print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
  119. losses = np.append(losses, avg_loss.numpy())
  120. #后向传播,更新参数的过程
  121. avg_loss.backward()
  122. optimizer.minimize(avg_loss)
  123. model.clear_gradients()
  124. #保存模型参数
  125. fluid.save_dygraph(model.state_dict(), 'mnist')
  126. # 绘制损失
  127. plt.plot(losses)

📃 图像分类任务 - 图3

关于训练过程的说明:

  • 内层循环: 负责整个数据集的一次遍历,采用分批次方式(batch)。假设数据集样本数量为1000,一个批次有10个样本,则遍历一次数据集的批次数量是1000/10=100,即内层循环需要执行100次。
  • 外层循环: 定义遍历数据集的次数,通过参数EPOCH_NUM设置。