深度学习:不调TensorFlow,自己写神经网络学习MNIST数据集(2)—旋转图像继续强化模型(正确率达98%+)

之前手写神经网络学习效果已经很不错了,最高正确率略微超越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)
原创文章 41 获赞 156 访问量 1万+

猜你喜欢

转载自blog.csdn.net/CxsGhost/article/details/104829332