之前手写神经网络学习效果已经很不错了,最高正确率略微超越97%。本文文章中我们对数据做进一步处理,从而继续强化模型的能力。同时我们也将进行反向查询,看看神经网络到底学到了什么
之前一篇:https://blog.csdn.net/CxsGhost/article/details/104794125
——————————————————————————————————————————————————
我们知道MNIST数据集是“手写体数字”,那么既然是手写,就难免会有歪歪扭扭的可能,而这样的数据输入到我们的神经网络中,往往并不会有很好的效果,于是我们可以考虑:用一组“歪歪扭扭”的图像,继续训练强化我们的网络
旋转图像:
看下面的示例,第一张图是原始数据,后两张图像分别是,逆时针和顺时针旋转10度后得到的图像,将这些作为新的数据用来训练,会大大提高模型的能力。
但是一定注意旋转角度不能太大,否则整个数据即将变得混乱不堪,一般±10度足可以了
如何旋转:
scipy库中为我们提供了这样的函数,可以很简单做到import scipy.ndimage.interpolation.rotate()
scipy.ndimage.interpolation.rotate(each_input.reshape(28, 28), 10, cval=0.01, reshape=False).reshape(1, -1)
上面的代码是把图像顺时针旋转10度,实际上我们是对构成图像的矩阵进行偏转
参数介绍:
- 第一个位置参数,是我们要操作的矩阵,因为在进行训练时数据是784个数字的一行矩阵,这里要先重新排列为28 * 28再转,转完再reshape回来。
- 第二个位置参数angle,代表旋转的角度,这里我们设为10度
- 第三个命名参数cval,简单说就是旋转后边缘部分难免会有空缺,这个固定值用来填充,设置0.01即可
- 第四个命名参数reshape,这里的reshape不同于numpy中对数组的操作,指定为False可以更“柔和的旋转”,或者说防止图像出现看起来“断层”“被截断”的感觉
更多参数详细和标准的解释参见官方文档:scipy.ndimage.interpolation.rotate()
代码:
下面的代码直接替换之前基础版网络的训练部分即可,就是在原始数据输入训练完成后,紧接着旋转然后再次训练
while True:
# 对原始数据,旋转后的数据依次进行训练
for each_train_data in zip(train_data_inputs, train_data_targets):
each_input = each_train_data[0]
each_target = each_train_data[1]
DNN.train(each_input, each_target)
# 把图像旋转后再次进行训练
each_input_plus10 = scipy.ndimage.interpolation.rotate(each_input.reshape(28, 28),
10, cval=0.01, reshape=False).reshape(1, -1)
DNN.train(each_input_plus10, each_target)
each_input_minus10 = scipy.ndimage.interpolation.rotate(each_input.reshape(28, 28),
-10, cval=0.01, reshape=False).reshape(1, -1)
DNN.train(each_input_minus10, each_target)
——————————————————————————————————————————————————
反向查询:
当训练完成后,正常的思路就是正向输入一个图片,然后神经网络将会告诉你是几。但是如果我们从输出端输入一组数据,然后让神经网络进行逆向输出,是否也能得到一组合适的图形呢
代码:
- 和正向输入的计算思路一样,只需要把矩阵和输入的位置重新调整一下即可
- 不同之处在于,我们要调整每个节点的激活函数。也就是把sigmoid函数x和y的关系颠倒一下,如下图的推导
这个函数称为:logit,在scipy中也有提供。下面的代码加在__init__()中
self.inverse_activation_function = lambda y: scipy.special.logit(y)
- 下面的代码添加了一个实例方法,用来反向查询
可以看到在每次从反向激活函数输出后,都进行数据处理,1.归一化 2. 保底处理
首先进行归一化是没什么问题的。因为数据可能存在极小的值导致出现inf,所以要加0.01保底
但是只单纯加上0.01又有超出1的可能,所以先乘0.98,再加0.01无论如何不会超出1,还能保底
“保底”如果不做的话,会导致反向查询输出结果无法绘制图像
def back_query(self, back_inputs_list):
back_inputs = np.array(back_inputs_list, ndmin=2)
# 计算输出层的反向输出,以及隐藏层的反向输入,并缩放范围至sigmoid函数的范围内!!!
back_output = self.inverse_activation_function(back_inputs)
back_hidden_inputs = np.dot(back_output, self.who)
back_hidden_inputs -= np.min(back_hidden_inputs)
back_hidden_inputs /= np.max(back_hidden_inputs) - np.min(back_hidden_inputs)
# 防止从反向激活函数输出为-inf
back_hidden_inputs = back_hidden_inputs * 0.98 + 0.01
# 计算隐藏层的反向输出,及输入层的反向输入,最终反向输出
back_hidden_outputs = self.inverse_activation_function(back_hidden_inputs)
back_final_outputs = np.dot(back_hidden_outputs, self.wih)
back_final_outputs -= np.min