百度飞桨(paddlepaddle)是百度的开源深度学习平台,今天就利用paddle来编写入门级的手写数字模型.
- # 下载原始的 MNIST 数据集并进行解压
- wget https://paddle-imagenet-models-name.bj.bcebos.com/data/mnist.tar
- tar -xf mnist.tar
数据集的目录格式如下:
- mnist.
- ├── train
- │ └── imgs
- │ ├── 0
- │ ├── 1
- │ ├── 2
- │ ├── 3
- │ ├── 4
- │ ├── 5
- │ ├── 6
- │ ├── 7
- │ ├── 8
- │ └── 9
- └── val
- └── imgs
- ├── 0
- ├── 1
- ├── 2
- ├── 3
- ├── 4
- ├── 5
- ├── 6
- ├── 7
- ├── 8
- └── 9
train和val目录下均有一个标签文件label.txt
- class MyDataSet(Dataset):
-
- def __init__(self, data_dir, label_path, tansform=None):
- super(MyDataSet, self).__init__()
- self.data_list = []
-
- with open(label_path, encoding='utf-8') as f:
-
- for line in f.readlines():
- image_path, label = line.split('\t')
- image_path = os.path.join(data_dir, image_path)
- self.data_list.append([image_path, label])
-
- self.tansform = tansform
-
- def __getitem__(self, index):
-
- image_path, label = self.data_list[index]
- image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
- image = image.astype('float32')
-
- # 应用图像变换
- if self.tansform is not None:
- self.tansform(image)
-
- label = int(label)
- return image, label
-
- def __len__(self):
- return len(self.data_list)

定义一个MyNet:
- class MyNet(nn.Layer):
- def __init__(self, num_classes=10):
- super().__init__()
- self.num_classes = num_classes
-
- # 定义
- self.conv1 = nn.Conv2D(1, 6, 3, stride=1, padding=1)
- self.conv2 = nn.Conv2D(6, 16, 5, stride=1, padding=0)
- self.relu = nn.ReLU()
-
- self.features = nn.Sequential(
- self.conv1,
- self.relu,
- nn.MaxPool2D(2, 2),
- self.conv2,
- self.relu,
- nn.MaxPool2D(2, 2))
-
- if num_classes > 0:
- self.linear = nn.Sequential(
- nn.Linear(400, 120),
- nn.Linear(120, 84),
- nn.Linear(84, num_classes)
- )
-
- def forward(self, x):
- x = self.features(x)
- if self.num_classes > 0:
- x = paddle.flatten(x, 1)
- x = self.linear(x)
- return x
- # 定义一个网络
- model = MyNet()
- # 可视化模型组网结构和参数
- params_info = paddle.summary(model, (1, 1, 28, 28))
- print(params_info)
- total_epoch = 5
- batch_size = 16
-
- # transform = F.normalize(mean=[127.5], std=[127.5], data_format=['CHW'])
- transform = Normalize(mean=[127.5], std=[127.5], data_format=['CHW'])
-
- # 训练集
- data_dir_train = './mnist/train'
- label_path_train = './mnist/train/label.txt'
-
-
- # 加载数据
- train_dataset = MyDataSet(data_dir_train, label_path_train, transform)
- val_dataset = MyDataSet(data_dir_val, label_path_val, transform)
- print(f'训练图片张数:{len(train_dataset)} 测试集图张数:{len(val_dataset)}')
- train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
-
- optim = paddle.optimizer.Adam(parameters=model.parameters())
- # 设置损失函数
- loss_fn = paddle.nn.CrossEntropyLoss()
- for epoch in range(total_epoch):
- for batch_id, data in enumerate(train_loader):
-
- x_data = data[0] # 训练数据
- y_data = data[1] # 训练数据标签
- # print(y_data)
- # print(y_data.shape)
- # 增加维度
- x_data = paddle.unsqueeze(x_data, axis=1)
- predicts = model(x_data) # 预测结果
- # print(f'predicts:{predicts} predicts.shape={predicts.shape}')
- y_data = paddle.unsqueeze(y_data, axis=1)
- # 计算损失 等价于 prepare 中loss的设置
- loss = loss_fn(predicts, y_data)
-
- # 计算准确率 等价于 prepare 中metrics的设置
- acc = paddle.metric.accuracy(predicts, y_data)
-
- # 下面的反向传播、打印训练信息、更新参数、梯度清零都被封装到 Model.fit() 中
- # 反向传播
- loss.backward()
- # print("epoch: {}, batch_id: {}, loss is: {}, acc is: {}".format(epoch, batch_id + 1, loss.numpy(),
- # acc.numpy()))
-
- if (batch_id + 1) % 100 == 0:
- print("epoch: {}, batch_id: {}, loss is: {}, acc is: {}".format(epoch, batch_id + 1, loss.numpy(),
- acc.numpy()))
- write_to_log("epoch: {}, batch_id: {}, loss is: {}, acc is: {}".format(epoch, batch_id + 1, loss.numpy(),
- acc.numpy()))
- # 更新参数
- optim.step()
- # 梯度清零
- optim.clear_grad()
- paddle.save(model.state_dict(), f'./mynet/mynet.ep{epoch}.pdparams')
注意输入的数据的维度要与网络结构保持一致
下面来看一下模型的效果:

输出结果: