之前手写神经网络学习效果已经很不错了,最高正确率略微超越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(back_final_outputs)
back_final_outputs /= np.max(back_final_outputs)
back_final_outputs = back_final_outputs * 0.98 + 0.01
return back_final_outputs
可视化:
把反向查询结果排列成28 * 28矩阵然后用matplotlib.pyplot.imshow()
绘图,colormap选择“gray”。也就是灰度图
interpolation=None,作用是把像素块分的很清楚,防止糊成一团
@staticmethod
def back_outputs_visualize(target, b_f_o):
# 在训练时对输入数据做了放缩处理,这里要进行相反的处理恢复原始数据形态
visual_array = (b_f_o.reshape(28, 28) - 0.01) * 255
plt.figure()
plt.imshow(visual_array, cmap="gray", interpolation=None)
plt.title("the number is :{}".format(target))
plt.show()
- 下面是我选取的8、6、3、0四个数字,这仅仅是50个隐层节点,数据只迭代一次得到的结果,可以看到效果还是很不错的。
完整代码:
因为隐藏节点多,数据量大,并且手写的没法用GPU加速,所以建议大家有条件可以去云上跑(笔记本真的烧CPU)
import numpy as np
import scipy.special
import scipy.ndimage.interpolation
import pandas as pd
import matplotlib.pyplot as plt
class NeuralNetwork:
def __init__(self, inputnodes, outputnodes,
hiddennodes, learningrate):
# 设置节点数量属性
self.innodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
self.lr = learningrate
# 按照正态分布设置初始权重矩阵
self.wih = np.random.normal(loc=0.0,
scale=1 / np.sqrt(self.innodes),
size=(self.hnodes, self.innodes))
self.who = np.random.normal(loc=0.0,
scale=1 / np.sqrt(self.hnodes),
size=(self.onodes, self.hnodes))
# 这里scipy.special.expit(),是sigmoid函数,用于正向激活,numpy中并未提供
self.activation_function = lambda x: scipy.special.expit(x)
# scipy.special.logit(), 用于反向激活
self.inverse_activation_function = lambda y: scipy.special.logit(y)
def train(self, input_list_1, target_list):
# 转置输入数据,以及目标数据
input_1 = np.array(input_list_1, ndmin=2)
input_1 = np.transpose(input_1, axes=(1, 0))
targets = np.array(target_list, ndmin=2)
targets = np.transpose(targets, axes=(1, 0))
# 计算隐藏层输出,以及最终输出
hidden_inputs_1 = np.dot(self.wih, input_1)
hidden_outputs_1 = self.activation_function(hidden_inputs_1)
final_inputs_1 = np.dot(self.who, hidden_outputs_1)
final_outputs_1 = self.activation_function(final_inputs_1)
# 计算输出层损失,并反向传播给隐藏层
output_errors = targets - final_outputs_1
hidden_errors = np.dot(np.transpose(self.who, axes=(1, 0)), output_errors)
# 对两组权重进行梯度下降,E(out - target) * sigmoid * ( 1 - sigmoid ) *(矩阵点积) O(hidden)
self.who += self.lr * np.dot((output_errors * final_outputs_1 * (1.0 - final_outputs_1)),
np.transpose(hidden_outputs_1, axes=(1, 0)))
self.wih += self.lr * np.dot((hidden_errors * hidden_outputs_1 * (1.0 - hidden_outputs_1)),
np.transpose(input_1, axes=(1, 0)))
def query(self, inputs_list_2):
# 把输入转化成矩阵并转置
inputs_2 = np.array(inputs_list_2, ndmin=2)
inputs_2 = np.transpose(inputs_2, axes=(1, 0))
# 计算输出结果
hidden_inputs_2 = np.dot(self.wih, inputs_2)
hidden_outputs_2 = self.activation_function(hidden_inputs_2)
final_inputs_2 = np.dot(self.who, hidden_outputs_2)
final_outputs_2 = self.activation_function(final_inputs_2)
return final_outputs_2
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(back_final_outputs)
back_final_outputs /= np.max(back_final_outputs)
back_final_outputs = back_final_outputs * 0.98 + 0.01
return back_final_outputs
@staticmethod
def back_outputs_visualize(target, b_f_o):
# 在训练时对输入数据做了放缩处理,这里要进行相反的处理恢复原始数据形态
visual_array = (b_f_o.reshape(28, 28) - 0.01) * 255
plt.figure()
plt.imshow(visual_array, cmap="gray", interpolation=None)
plt.title("the number is :{}".format(target))
plt.show()
# 读取数据到dataframe
train_data = pd.read_csv("MNIST_all/mnist_train.csv", header=None)
# 把label和input先分离
train_data_targets = train_data.iloc[:, 0].values
train_data_inputs = train_data.values
train_data_inputs = np.delete(train_data_inputs, obj=0, axis=1)
# 缩放input的数据范围,来适用于sigmoid
train_data_inputs = train_data_inputs / 255.0 + 0.01
# 把target转化为标记矩阵,然后转置
data_targets = np.zeros(shape=(len(train_data_targets), 10))
for t in range(len(train_data_targets)):
data_targets[t][train_data_targets[t]] = 0.99
train_data_targets = data_targets
# 确定每层的节点数量,学习率
input_nodes = 784
hidden_nodes = 500
output_nodes = 10
# 经测试发现,学习率最优值约在0.1到0.2之间
learn_rate = 0.15
# 创建NeuralNetwork实例,并逐条数据训练
DNN = NeuralNetwork(input_nodes, output_nodes,
hidden_nodes, learn_rate)
print("神经网络搭建完成,开始训练....")
number = 0
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)
number += 1
if number == 5: # 训练5次,这时候正确率差不多是峰值
break
print("训练完成,测试效果")
# 读取并处理测试数据集
test_data = pd.read_csv("MNIST_all/mnist_test.csv", header=None)
test_data_targets = test_data.iloc[:, 0].values
test_data_inputs = np.delete(test_data.values, obj=0, axis=1) / 255.0 + 0.01
# 查看预测效果
pre_right = 0
for each_test in range(len(test_data_targets)):
pre_target = DNN.query(test_data_inputs[each_test])
if pre_target.argmax() == test_data_targets[each_test]:
pre_right += 1
accuracy = pre_right / len(test_data_targets) * 100
print("正确率为:{0:.2f}%".format(accuracy))
# 进行几次反向输出实验
back_input_data = {0: [0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01],
1: [0.01, 0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01],
2: [0.01, 0.01, 0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01],
3: [0.01, 0.01, 0.01, 0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01],
4: [0.01, 0.01, 0.01, 0.01, 0.99, 0.01, 0.01, 0.01, 0.01, 0.01],
5: [0.01, 0.01, 0.01, 0.01, 0.01, 0.99, 0.01, 0.01, 0.01, 0.01],
6: [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.99, 0.01, 0.01, 0.01],
7: [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.99, 0.01, 0.01],
8: [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.99, 0.01],
9: [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.99]}
for b_i in back_input_data.items():
b_o = DNN.back_query(b_i[1])
DNN.back_outputs_visualize(b_i[0], b_o)