经典全连接神经网络和卷积神经网络
实验实例为:https://aistudio.baidu.com/projectdetail/4822979
一、实验目的
- 比较经典全连接神经网络和卷积神经网络
- 基础:修改模型输入、增加调用训练全连接模型代码、输出Loss值进行比对
- 拓展:微调超参数,比较结果、增加全连接层层数,比较运行时间和效果
二、实验环境
AI Studio 2.3.2、Python 3
三、实验内容
1.分别训练“全连接网络模型”和“卷积神经网络模型”,比较经典全连接神经网络和卷积神经网络的损失变化
(1)对数据集进行加载并区分,定义相关参数
# 加载数据
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
data = json.load(gzip.open(datafile))
print('mnist dataset load done')
# 读取到的数据区分训练集,验证集,测试集
train_set, val_set, eval_set = data
# 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
IMG_ROWS = 28
IMG_COLS = 28
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']")
(2)校验数据,定义数据生成器
#校验数据
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))
# 定义数据集每个数据的序号, 根据序号读取数据
index_list = list(range(imgs_length))
# 读入数据时用到的batchsize
BATCHSIZE = 100
注意以下这部分的代码,因为现在先训练“全连接网络模型”,所以要注释掉。
#img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
#label = np.reshape(labels[i], [1]).astype('float32')
需要注释的原因:全连接神经网络输入的是28*28的像素值,而卷积模型直接在导入的图片上进行处理,所以后续使用卷积神经网络结构时不用注释这两行代码。
(3)区别两种模型结构
①全连接神经网络结构
# 定义多层全连接神经网络
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)
# 定义网络的前向计算,隐含层激活函数为sigmoid,输出层不使用激活函数
def forward(self, inputs):
# inputs = paddle.reshape(inputs, [inputs.shape[0], 784])
outputs1 = self.fc1(inputs)
outputs1 = F.sigmoid(outputs1)
outputs2 = self.fc2(outputs1)
outputs2 = F.sigmoid(outputs2)
outputs_final = self.fc3(outputs2)
return outputs_final
②卷积神经网络结构
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
# 定义一层全连接层,输出维度是1
self.fc = Linear(in_features=980, out_features=1)
# 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
# 卷积层激活函数使用Relu,全连接层不使用激活函数
def forward(self, inputs):
x = self.conv1(inputs)
x = F.relu(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = F.relu(x)
x = self.max_pool2(x)
x = paddle.reshape(x, [x.shape[0], -1])
x = self.fc(x)
return x
(4)激活函数
Sigmoid是早期神经网络模型中常见的非线性变换函数,通过如下代码,绘制出Sigmoid的函数曲线。
def sigmoid(x):
# 直接返回sigmoid函数
return 1. / (1. + np.exp(-x))
# param:起点,终点,间距
x = np.arange(-8, 8, 0.2)
y = sigmoid(x)
plt.plot(x, y)
plt.show()
(5)训练模型
(这里不附完整代码了,直接展示运行结果。想学习完整代码可以点进文章开头的链接亲自动手运行一下项目)
①全连接神经网络运行效果
直观图:
直观图:
(6)输出Loss值进行对比
通过(5)的结果对比可知,卷积神经网络的训练时间要远大于全连接神经网络,但卷积神经网络的损失值下降更快,且最终的损失值更小。
2. 微调超参数,比较运行时间和效果
以卷积神经网络结构为例:
①将训练批次由200调整为150
运行结果:
每个批次训练数量减少后,运行时间略微减少,但损失率有所升高。
直观图:
②将学习率由0.01调整为0.05
运行结果:
将学习率改为0.05后,运行时间基本没变,但损失率大大增加,可见该学习率较为低效。
直观图:
四、实验小结
关于超参数的认识:
超参数通常分为三类:网格参数、优化参数、正则化参数
网格参数:网络中层与层之间的交互方式、卷积核数量、卷积核尺寸、网络层数和激活函数等。
优化参数:学习率、批样本数量、不同优化器以及损失函数
正则化:权重衰减系数、丢弃比率
调整这些参数主要是寻找最优解和正则化之间的关系。网格模型优化调整的目的为了找到全局最优解,而正则项又希望尽量拟合到最优。两者通常情况下,存在一定的对立,但是二者目标一致,即最小化期望风险。最优解用来增加模型复杂度,正则项用来约束模型复杂度。