其实对于这个模型,我们还有很多优化的方向。
有一些关键问题:
-
计算分类准确率,观测模型训练效果。
(交叉熵损失函数只能作为优化目标,无法直接准确衡量模型的训练效果。准确率可以直接衡量训练效果,但由于其离散性质,不适合做为损失函数优化神经网络。) -
检查模型训练过程,识别潜在问题。
(如果模型的损失或者评估指标表现异常,通常需要打印模型每一层的输入和输出来定位问题,分析每一层的内容来获取错误的原因。) -
加入校验或测试,更好评价模型效果。
(理想的模型训练结果是在训练集和验证集上均有较高的准确率,如果训练集上的准确率高于验证集,说明网络训练程度不够;如果验证集的准确率高于训练集,可能是发生了过拟合现象。通过在优化目标中加入正则化项的办法,解决过拟合的问题。) -
加入正则化项,避免模型过拟合。
(飞桨框架支持为整体参数加入正则化项,这是通常的做法。此外,飞桨框架也支持为某一层或某一部分的网络单独加入正则化项,以达到精细调整参数训练的效果。若测试集表现不好) -
可视化分析。
(用户不仅可以通过打印或使用matplotlib库作图,飞桨还提供了更专业的可视化分析工具VisualDL,提供便捷的可视化分析方法。)
评估分类分准率
分类准确率accuracy:测量直观,但是不适合作为loss,公平比较两种损失函数的优劣。
实现方案:
在forward函数中加入acc计算并返回结果。
训练过程中取得该批次样本的acc
打印acc
if label is not None:acc = fluid.layers.accuracy(input=x, label=label)return x, accelse:return x······#前向计算的过程,同时拿到模型输出值和分类准确率predict, acc = model(image, label)
检查模型训练过程
若模型报错或loss不下降。
实现方案:
在forward函数中,打印模型每一层的参数和输出
打印尺寸和内容值。
通过尺寸验证网络结构是否正确,通过内容值验证数据分布是否合理。
在合适时机打印即可
# 选择是否打印神经网络每层的参数尺寸和输出尺寸,验证网络结构是否设置正确if check_shape:# 打印每层网络设置的超参数-卷积核尺寸,卷积步长,卷积padding,池化核尺寸print(\"\\n########## print network layer\'s superparams ##############\")print(\"conv1-- kernel_size:{}, padding:{}, stride:{}\".format(self.conv1.weight.shape, self.conv1._padding, self.conv1._stride))print(\"conv2-- kernel_size:{}, padding:{}, stride:{}\".format(self.conv2.weight.shape, self.conv2._padding, self.conv2._stride))print(\"pool1-- pool_type:{}, pool_size:{}, pool_stride:{}\".format(self.pool1._pool_type, self.pool1._pool_size, self.pool1._pool_stride))print(\"pool2-- pool_type:{}, poo2_size:{}, pool_stride:{}\".format(self.pool2._pool_type, self.pool2._pool_size, self.pool2._pool_stride))print(\"fc-- weight_size:{}, bias_size_{}, activation:{}\".format(self.fc.weight.shape, self.fc.bias.shape, self.fc._act))# 打印每层的输出尺寸print(\"\\n########## print shape of features of every layer ###############\")print(\"inputs_shape: {}\".format(inputs.shape))print(\"outputs1_shape: {}\".format(outputs1.shape))print(\"outputs2_shape: {}\".format(outputs2.shape))print(\"outputs3_shape: {}\".format(outputs3.shape))print(\"outputs4_shape: {}\".format(outputs4.shape))print(\"outputs5_shape: {}\".format(outputs5.shape))# 选择是否打印训练过程中的参数和输出内容,可用于训练过程中的调试if check_content:# 打印卷积层的参数-卷积核权重,权重参数较多,此处只打印部分参数print(\"\\n########## print convolution layer\'s kernel ###############\")print(\"conv1 params -- kernel weights:\", self.conv1.weight[0][0])print(\"conv2 params -- kernel weights:\", self.conv2.weight[0][0])# 创建随机数,随机打印某一个通道的输出值idx1 = np.random.randint(0, outputs1.shape[1])idx2 = np.random.randint(0, outputs3.shape[1])# 打印卷积-池化后的结果,仅打印batch中第一个图像对应的特征print(\"\\nThe {}th channel of conv1 layer: \".format(idx1), outputs1[0][idx1])print(\"The {}th channel of conv2 layer: \".format(idx2), outputs3[0][idx2])print(\"The output of last layer:\", outputs5[0], \'\\n\')
若打印的参数一直不变,说明参数没有加入梯度下降模型。
加入校验或测试,评价模型效果
校验:未参与训练,决策模型超参数
测试:未参与训练和校验,评估模型效果
实现方案:
加载参数,eval状态
读取校验的样本集
根据模型预测计算评估指标,注意不同批次评估结果取平均。
一定要先保存
with fluid.dygraph.guard():print(\'start evaluation .......\')#加载模型参数model = MNIST()model_state_dict, _ = fluid.load_dygraph(\'mnist\')model.load_dict(model_state_dict)model.eval()eval_loader = load_data(\'eval\')acc_set = []avg_loss_set = []for batch_id, data in enumerate(eval_loader()):x_data, y_data = dataimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)prediction, acc = model(img, label)loss = fluid.layers.cross_entropy(input=prediction, label=label)avg_loss = fluid.layers.mean(loss)acc_set.append(float(acc.numpy()))avg_loss_set.append(float(avg_loss.numpy()))#计算多个batch的平均损失和准确率acc_val_mean = np.array(acc_set).mean()avg_loss_val_mean = np.array(avg_loss_set).mean()print(\'loss={}, acc={}\'.format(avg_loss_val_mean, acc_val_mean))
差别不大只不过数据不用训练数据,用校验的数据
过拟合的原理和解决方案
过拟合:模型在训练集数据表现良好,测试集表现差(泛化能力差)原因:模型过于复杂,或学习能力敏感,数据较少,或噪音多。
一般测试误差先降低后上升。
回归问题模型:
图1 过拟合 图3 欠拟合
分类问题模型:
过拟合成因
- 训练数据存在噪音->数据清洗和修正
- 强大模型(表示空间大)+训练数据少=在训练数据上表现良好的候选假设太多
可以限制模型表示能力or更多训练数据
加入正则化项
为了防止过拟合,我们一般限制模型表示能力。给参数加入反胃。
正则化项
- 防止模型过度拟合
- 优化目标中加入正则化项,惩罚参数
- 模型在参数大小和训练集loss间取得平衡->在预测时效果最好
实现方案:
在优化目标中整体加入;
对某一层的参数加入。
#各种优化算法均可以加入正则化项,避免过拟合,参数regularization_coeff调节正则化项的权重#optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, regularization=fluid.regularizer.L2Decay(regularization_coeff=0.1),parameter_list=model.parameters()))optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01, regularization=fluid.regularizer.L2Decay(regularization_coeff=0.1),parameter_list=model.parameters())
*regularization_coeff为权重系数,参数和loss谁更小的权重
可视化
使用PLT库制作变化曲线
Matplotlib库
2D
实现方案:
- 引入PLT库
- 批次编号作为x轴,记录在列表iters
- 该批次的训练损失作为y轴,记录在loses
- 训练后将数据以参数灌入plt.plot
#画出训练过程中Loss的变化曲线plt.figure()plt.title(\"train loss\", fontsize=24)plt.xlabel(\"iter\", fontsize=14)plt.ylabel(\"loss\", fontsize=14)plt.plot(iters, losses,color=\'red\',label=\'train loss\')plt.grid()plt.show()
Visual DL分析工具
- 创建LogWriter对象,设置数据存放路径
from visualdl import LogWriterlog_writer = LogWriter(\"./log\")
- 训练过程中插入作图语句
#每训练一百批次的数据,打印loss情况if batch_id % 100 == 0:print(\"epoch: {}, batch: {}, loss is: {}, acc is {}\".format(epoch_id, batch_id, avg_loss.numpy(), avg_acc.numpy()))log_writer.add_scalar(tag = \'acc\', step = iter, value = avg_acc.numpy())log_writer.add_scalar(tag = \'loss\', step = iter, value = avg_loss.numpy())iter = iter + 100
- 命令行输入 $visualdl –logdir ./log
$ visualdl –logdir ./log –port 8080 -
打开浏览器,查看作图结果。
查阅的网址在第三步的启动命令后会打印出来(如http://127.0.0.1:8080/),将该网址输入浏览器地址栏刷新页面的效果如下图所示。除了右侧对数据点的作图外,左侧还有一个控制板,可以调整诸多作图的细节。