从前面的第一篇文章,我们得知单层全连接网络是没法识别手写数字的。为此我们在本篇文章使用经典的全连接神经网络来识别一下,看看识别的效果如何。
什么是经典的全连接神经网络呢?经典的全连接神经网络来包含四层网络:输入层、两个隐含层和输出层
输入层:将数据输入给神经网络。这个是我们在数据加载需要处理的任务,把数据处理成模型需要的类型与结构。
隐含层:增加网络深度和复杂度,隐含层的节点数是可以调整的,节点数越多,神经网络表示能力越强,参数量也会增加。
输出层:输出网络计算结果,输出层的节点数是固定的。如果是回归问题,节点数量为需要回归的数字数量。如果是分类问题,则是分类标签的数量。
隐含层引入非线性激活函数Sigmoid是为了增加神经网络的非线性能力。
Sigmoid是早期神经网络模型中常见的非线性变换函数。
1、新增文件load_data.py,定义load_data函数,函数内容如下:
- #数据处理部分之前的代码,加入部分数据处理的库
- import gzip
- import json
- import random
- import numpy as np
- import paddle
-
-
- def load_data(mode='train'):
- datafile = './../work/mnist.json.gz'
- print('loading mnist dataset from {} ......'.format(datafile))
- # 加载json数据文件
- data = json.load(gzip.open(datafile))
- print('mnist dataset load done')
-
- # 读取到的数据区分训练集,验证集,测试集
- train_set, val_set, eval_set = data
- if mode=='train':
- # 获得训练数据集
- imgs, labels = train_set[0], train_set[1]
- elif mode=='valid':
- # 获得验证数据集
- imgs, labels = val_set[0], val_set[1]
- elif mode=='eval':
- # 获得测试数据集
- imgs, labels = eval_set[0], eval_set[1]
- else:
- raise Exception("mode can only be one of ['train', 'valid', 'eval']")
- print("训练数据集数量: ", len(imgs))
-
- # 校验数据
- imgs_length = len(imgs)
-
- assert len(imgs) == len(labels), \
- "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(labels))
-
- # 获得数据集长度
- imgs_length = len(imgs)
-
- # 定义数据集每个数据的序号,根据序号读取数据
- index_list = list(range(imgs_length))
- # 读入数据时用到的批次大小
- BATCHSIZE = 100
-
- # 定义数据生成器
- def data_generator():
- if mode == 'train':
- # 训练模式下打乱数据
- random.shuffle(index_list)
- imgs_list = []
- labels_list = []
- for i in index_list:
- # 将数据处理成希望的类型
- img = np.array(imgs[i]).astype('float32')
- label = np.array(labels[i]).astype('float32')
- imgs_list.append(img)
- labels_list.append(label)
- if len(imgs_list) == BATCHSIZE:
- # 获得一个batchsize的数据,并返回
- yield np.array(imgs_list), np.array(labels_list)
- # 清空数据读取列表
- imgs_list = []
- labels_list = []
-
- # 如果剩余数据的数目小于BATCHSIZE,
- # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
- if len(imgs_list) > 0:
- yield np.array(imgs_list), np.array(labels_list)
- return data_generator
2、mnist.json.gz
该数据集可以从mnist.json.gz - 飞桨AI Studio 上面下载
3、此次涉及到Python的生成器,这个知识点需要理解一下
1、新建文件FullyConnectedNeuralNetwork,定义网络结构:
- #经典的全连接神经网络来包含四层网络:输入层、两个隐含层和输出层
- import paddle
- import paddle.nn.functional as F
- from paddle.nn import Linear
- class MNIST(paddle.nn.Layer):
- def __init__(self):
- super(MNIST, self).__init__()
- #定义两层全连接隐藏层,输出维度是10,当前设定隐含节点数为10,可根据任务调整
- self.fc1 = Linear(in_features=784,out_features=10)
- self.fc2 = Linear(in_features=10, out_features=10)
- #定义一层全连接输出层,输出维度是1
- self.fc3 = Linear(in_features=10, out_features=1)
- def forward(self, inputs):
- outputs1 = self.fc1(inputs)
- outputs1 = F.sigmoid(outputs1)
- outputs2 = self.fc2(outputs1)
- outputs2 = F.sigmoid(outputs2)
- outputs_final = self.fc3(outputs2)
- return outputs_final
2、从网络结构中我们可以看到两个全连接的隐含层和一个输出层,每个隐含层的结果都会调用sigmoid函数进行激活。
1、定义训练过程并保存训练参数:
- import paddle
- import paddle.nn.functional as F
- from load_data import load_data
- from FullyConnectedNeuralNetwork import MNIST
- # 训练配置,并启动训练过程
- def train(model):
- model = MNIST()
- model.train()
- #调用加载数据的函数
- train_loader = load_data('train')
- opt = paddle.optimizer.SGD(learning_rate=0.001, parameters=model.parameters())
- EPOCH_NUM = 10
- for epoch_id in range(EPOCH_NUM):
- for batch_id, data in enumerate(train_loader()):
- #准备数据,变得更加简洁
- images, labels = data
- images = paddle.to_tensor(images)
- labels = paddle.to_tensor(labels)
-
- #前向计算的过程
- predits = model(images)
-
- #计算损失,取一个批次样本损失的平均值
- loss = F.square_error_cost(predits, labels)
- avg_loss = paddle.mean(loss)
-
- #每训练了200批次的数据,打印下当前Loss的情况
- if batch_id % 200 == 0:
- print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
-
- #后向传播,更新参数的过程
- avg_loss.backward()
- opt.step()
- opt.clear_grad()
-
- # 保存模型
- paddle.save(model.state_dict(), './mnist.pdparams')
- # 创建模型
- model = MNIST()
- # 启动训练过程
- train(model)
1、定义文件HandWriteNumEval.py:
- import numpy as np
- import paddle
- from FullyConnectedNeuralNetwork import MNIST
-
- # 定义预测过程
- model = MNIST()
- params_file_path = 'mnist.pdparams'
- # 加载模型参数
- param_dict = paddle.load(params_file_path)
- model.load_dict(param_dict)
- model.eval()
-
- # 加载测试集 batch_size 设为 1
- test_loader = paddle.io.DataLoader(paddle.vision.datasets.MNIST(mode='test'))
- print(len(test_loader.dataset))
- success = 0
- error = 0
- for data in test_loader.dataset:
- #image = norm_img(data[0]).astype('float32')
- image = data[0]
- image = np.array(image).reshape(1, -1).astype(np.float32)
- # 图像归一化,保持和数据集的数据范围一致
- image = 1 - image / 255
- label = data[1].astype('int32')[0]
- result = model(paddle.to_tensor(image))
- result = result.numpy().astype('int32')[0][0];
- if (label == result) :
- success = success + 1
- else:
- error = error + 1
-
- # 预测输出取整,即为预测的数字,打印结果
- print("本次预测的正确的数量是{}, 错误的数量是{}".format(success, error))
2、输出结果如下,成功率大概在10%左右:
10000
本次预测的正确的数量是1010, 错误的数量是8990
1、经典的全连接神经网络增加了激活函数之后比单层神经网络的预测准确率提高了很多
2、这个预测准确率还是没法满足工业上的应用