neural network神经网络识别手写字体

(持续更新中…)

第一部分:神经网络

目的:实现字体识别,比如下图

neural network神经网络识别手写字体
需要让电脑识别出是数字7.

想要知道上面如何操作,先简单讲解一下神经网络
neural network神经网络识别手写字体
上图展示了神经网络的框架,有一个输入层(input layer),一个隐藏层(hidden layer)和一个输出层(output layer),每个层都有两个节点(nodes). 数据X\mathbf{X}经过输入层之后,与连接权W\mathbf{W}点乘(矩阵相乘)作为隐藏层输入,即Ihidden_input=WXI_{hidden\_input}=WX,输入之后经过**函数(activation function)作为隐藏层的输出,即Ohidden_output=sigmoid(WX)O_{hidden\_output}=sigmoid(WX),这里选择了sigmoid函数作为**函数。接着又将此输出与隐藏层和输出层之间的连接权点乘作为输出层的输入,最后再将此输入放入**函数中作为最后的output.至此一个简单的含有一个隐藏层的神经网络结束。
总结起来就是
neural network神经网络识别手写字体
上图显示的是三个层都是三个节点的情况。OkO_k表示最后的输出。下图表示对0-9的数字的识别。输出层是0-9共10个节点。比如我们的真实值是5,那么在十个输出节点中,在标签为5的节点得到最大的概率,表示识别正确,而9的例子表示有两个比较大的输出,但我们通过四舍五入或者直接通过np.argmax()提取最大数的索引值,因此我们只需要关注最大的值即可。
neural network神经网络识别手写字体

需要注意以下几点:

1)因为输出选择的**函数依然是sigmoid函数,如下图
neural network神经网络识别手写字体
由图可知道,sigmoid函数的输出在(0,1)之间,因此如果WX(图中的x=WXx=\text{WX})比较大,即输入或者连接权比较大的话,那么这会使得网络饱和(或说使得**函数饱和)。因为当|x|很大的时候,函数变化平缓,梯度基本不变,这样对于后续训练改变权重非常不利。也正因为输出在(0,1)之间,不包括0和1,因此训练的目标值(即预期值)也设置在0.010.990.01-0.99之间。同样,输入也将通过数学方法–缩放和位移(如除法和移动)改变使得在(0,1)之间(可以等于1,不能等于0)(常见的输入范围设置为0.010.990.01-0.99或者-1.0到1.0之间)。
2)输入不能为零,权重也不能为零。如果权重为零,当进行BP反馈的时候权重将无法得到学习,对网络达不到训练的目的。
3)学习率也在(0,1)之间,可以取1.从下面的权重迭代更新公式可以看出,实际上学习率就是步长的一种显示形式,如果学习率很大,那么势必会在使用梯度下降方法中造成训练结果误差大的局势。
neural network神经网络识别手写字体
4)由于最后的误差E实际上是参数权重W的函数,所以观察如何改变权重是的误差最小是我们的最终目的
neural network神经网络识别手写字体
最后,代码如下,并做详细分析

第二部分:程序实现

数据集来自—MNIST数据

import numpy as np
# scipy.special for the sigmoid function expit()
import scipy.special   #导入这个是为了导入sigmoid函数,当然这个函数很简单,也可以自己手动定义
import matplotlib.pyplot as plt
import time   #为了输出这个代码跑了多长时间
start_time = time.time()
class neuralNetwork:
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        #set number of nodes in each input, hidden, output layer
        #设置节点数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        #link weight matrices, wih (w_input_hidden) and who (w_hidden_output)
        #随机初始化权重,当然权重在0到1之间(不含)。这里通过高斯函数产生权重
        #其中均值是零,方差是1/sqrt(连接数),当然连接数等于节点数。后一个是矩阵hnodes行,inodes列。
        self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes,self.inodes))
        self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes,self.hnodes))
        # learning rate
        self.lr = learningrate
        # activation function is the sigmoid function
        self.activation_function = lambda x:scipy.special.expit(x)
    def train(self,inputs_list, targets_list):
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(targets_list, ndmin=2).T  #设置目标值
        #隐层输入与输出
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        #输出
        final_inputs = np.dot(self.who,hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        #误差
        output_errors = targets - final_outputs
        hidden_errors = np.dot(self.who.T, output_errors)
        #权重更新
        self.who += self.lr*np.dot((output_errors*final_outputs*(1.0 - final_outputs)),np.transpose(hidden_outputs))
        self.wih += self.lr*np.dot((hidden_errors*hidden_outputs*(1.0-hidden_outputs)),np.transpose(inputs))
        pass
    
    def query(self, inputs_list):
        # convert inputs list to 2d array
        inputs = np.array(inputs_list, ndmin=2).T
        
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs
        
 #设置节点数,隐藏层为100,但实际上是不知道隐藏该放多少的,最好的办法就是试验,一次次试验直到想要的
 #输入节点是因为训练数据是28*28=784个像素的图,输出因为是数字,从0-9共10个。
input_nodes = 784
hidden_nodes = 100
output_nodes = 10

learning_rate = 0.3

#create instance of neural network 实例
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)
#导入训练数据
training_data_file = open('C:/Users/Lenovo/Desktop/mnist_train_100.csv','r')
training_data_list = training_data_file.readlines()
training_data_file.close()
#进行数据训练
for record in training_data_list:
    all_values = record.split(',')
    #输入数据数学处理,使其在0.01到1之间.颜色的范围是[0,255]
    inputs = (np.asfarray(all_values[1:])/255.0*0.99)+0.01
    #初始化目标值,使其在0.01到0.99之间
    targets = np.zeros(output_nodes) + 0.01
    targets[int(record[0])] = 0.99
    n.train(inputs,targets)
    pass
#对数据测试
if __name__ == '__main__':
    print('running...')
    #导入测试数据,可以尝试np.loadtxt
    test_data_file = open('C:/Users/Lenovo/Desktop/mnist_test_10.csv','r')
    test_data_list = test_data_file.readlines()
    test_data_file.close()
    #通过逗号分割数据
    all_values = test_data_list[0].split(',')
    correct_label = int(all_values[0])
    print('real value ==>>: ',correct_label)
    image_array = np.asfarray(all_values[1:]).reshape((28,28))
    plt.imshow(image_array,cmap='Greys')
    inputs = (np.asfarray(all_values[1:])/255.0*0.99) + 0.01
    outputs = n.query(inputs)
    label = np.argmax(outputs)  #argmax返回最大值的索引值
    print("network's answer ==>> ",label)
    end_time = time.time()
    print('running time',end_time-start_time)

1)一个测试数据

上面if _name_==…后是一个测试数据的情况
上面进行数据训练,当完成第一个数据的训练后会更新开始随机初始化的权重,于是接着开始第二个数据输入,再更新权重,第三个数据输入…这样权重被一次次更新。在最后一个数据训练结束之后,权重更新完毕。接着使用更新后的权重来进行数据的测试。
最后的结果为:
neural network神经网络识别手写字体
可见真实值是7,网络判断的值也是7.

2)多个测试数据

上面是进行的一个测试数据,如果有多个测试数据,可以将后面的部分改为:

if __name__ == '__main__':
    start_time = time.time()
    print('running...')
    test_data_file = open('C:/Users/Lenovo/Desktop/mnist_test_10.csv','r')
    test_data_list = test_data_file.readlines()
    test_data_file.close()
    scorecard = []
    for record in test_data_list:
        all_values = record.split(',')
        correct_label = int(all_values[0])
        print('real value ==>>: ',all_values[0])
#        image_array = np.asfarray(all_values[1:]).reshape((28,28))
#        plt.imshow(image_array,cmap='Greys')
        inputs = (np.asfarray(all_values[1:])/255.0*0.99) + 0.01
        outputs = n.query(inputs)
        label = np.argmax(outputs)  #argmax返回最大值的索引值
        print("network's answer ==>> ",label)
        if (label == correct_label):
            scorecard.append(1)
        else:
            scorecard.append(0)
    print('\n','########  scorecard  #########')
    print(scorecard)
    scorecard_array = np.asarray(scorecard)
    print('performance ==>> ',scorecard_array.sum()/scorecard_array.size)
    end_time = time.time()
    print('running time',end_time-start_time)

neural network神经网络识别手写字体
输出结果表示,有6个结果正确(1表示对的,0表示错),四个分类错误,正确率60%。
上面使用的训练数据是100个,所以导致准确率并不高,于是改用60000个训练数据,结果得到的准确率到0.9

尝试改进

1)调整学习率

上面的学习率是我们随意设置的0.3,在经过多次试验后得到的准确性和学习率的关系为
neural network神经网络识别手写字体
学习率太大,即步长太大是不利的。

2)多次运行
上面的训练数据只训练了一次,称为一个epoch (世代),现在下面的代码表示多次训练的过程

#进行数据训练
epochs = 3  #A
for e in range(epochs):   #B
    for record in training_data_list:
        all_values = record.split(',')
        inputs = (np.asfarray(all_values[1:])/255.0*0.99)+0.01
        targets = np.zeros(output_nodes) + 0.01
        targets[int(record[0])] = 0.99
        n.train(inputs,targets)
        pass

这里只需要改正训练数据部分即可,相当于在前代码中只增加了A,B部分,这样会得到更好的结果。但是并不是epochs越大越好,会出现过拟合,准确率和epochs关系如下:
neural network神经网络识别手写字体
另外,当我们把epochs调大之后,还可以通过降低学习率来调得更好的结果。
3)改变网络结构
隐藏层可以增加节点,也可以增加层数。上面的代码默认是100个节点,一个层数。下面的结果表示节点与性能的关系
neural network神经网络识别手写字体
比如我改到5个节点,那么准确率可能只有30%
最后我将上面涉及的代码附上:与上面不同之处在于增加了epochs

import numpy as np
# scipy.special for the sigmoid function expit()
import scipy.special
import matplotlib.pyplot as plt
import time

start_time = time.time()

class neuralNetwork:
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        #set number of nodes in each input, hidden, output layer
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        #link weight matrices, wih (w_input_hidden) and who (w_hidden_output)
        self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes,self.inodes))
        self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes,self.hnodes))
        # learning rate
        self.lr = learningrate
        # activation function is the sigmoid function
        self.activation_function = lambda x:scipy.special.expit(x)
    def train(self,inputs_list, targets_list):
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(targets_list, ndmin=2).T
        
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        
        final_inputs = np.dot(self.who,hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        
        output_errors = targets - final_outputs
        hidden_errors = np.dot(self.who.T, output_errors)
        
        self.who += self.lr*np.dot((output_errors*final_outputs*(1.0 - final_outputs)),np.transpose(hidden_outputs))
        self.wih += self.lr*np.dot((hidden_errors*hidden_outputs*(1.0-hidden_outputs)),np.transpose(inputs))
        pass
    
    def query(self, inputs_list):
        # convert inputs list to 2d array
        inputs = np.array(inputs_list, ndmin=2).T
        
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs
        
    
input_nodes = 784
hidden_nodes = 200
output_nodes = 10

learning_rate = 0.3

#create instance of neural network
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)

training_data_file = open('C:/Users/Lenovo/Desktop/mnist_train_100.csv','r')
training_data_list = training_data_file.readlines()
training_data_file.close()

epochs = 3
for e in range(epochs):
    for record in training_data_list:
        all_values = record.split(',')
        inputs = (np.asfarray(all_values[1:])/255.0*0.99)+0.01
        targets = np.zeros(output_nodes) + 0.01
        targets[int(record[0])] = 0.99
        n.train(inputs,targets)
        pass

if __name__ == '__main__':
    print('running...')
    test_data_file = open('C:/Users/Lenovo/Desktop/mnist_test_10.csv','r')
    test_data_list = test_data_file.readlines()
    test_data_file.close()
    scorecard = []
    for record in test_data_list:
        all_values = record.split(',')
        correct_label = int(all_values[0])
        print('real value ==>>: ',all_values[0])
#        image_array = np.asfarray(all_values[1:]).reshape((28,28))
#        plt.imshow(image_array,cmap='Greys')
        inputs = (np.asfarray(all_values[1:])/255.0*0.99) + 0.01
        outputs = n.query(inputs)
        label = np.argmax(outputs)  #argmax返回最大值的索引值
        print("network's answer ==>> ",label)
        if (label == correct_label):
            scorecard.append(1)
        else:
            scorecard.append(0)
    print('\n','########  scorecard  #########')
    print(scorecard)
    scorecard_array = np.asarray(scorecard)
    print('performance ==>> ',scorecard_array.sum()/scorecard_array.size)
    end_time = time.time()
    print('running time',end_time-start_time)

第三部分:手写数字识别

自己手写一个数字,比如下面的数字8
neural network神经网络识别手写字体
在A4纸上写完拍照另存为8.png/8.jpg都可以;然后使用windows自带的画图将图片切成2828像素的正方形(因为我们在上面的训练数据中或者MNIST数据集上找到的是2828的图片)。
然后需要经过一些处理:
使用scipy.misc将图片转换成像素大小(在高版本1.2.0的scipy中已经将misc去掉,换成了imageio.imread)
所以上面的if __name…部分变成了如下代码

#在文件头添加导入
import scipy.misc
#glob.glob对于批量导入文件非常有用,此处只有一张图片,故不用此函数
#import glob
import imageio
'''
...
'''
if __name__ == '__main__':
    start_time = time.time()
    print('running...')
    #读取文件目录
    image_file_name = (r'C:/Users/Lenovo/Desktop/8.png')
    #将图片变成像素数组
    img_array = scipy.misc.imread(image_file_name, flatten = True)
    #加载图片提示,如果图片比较多很实用
    #print('loading ... ',image_file_name)  
    #imageio.imread与scipy.misc.imread二择其一
#    img_array = imageio.imread(image_file_name, as_gray=True)
  #用255减是因为通常来说,0指黑色,255指白色,而MNIST数据集使用相反的方式表达
    img_data = 255.0 - img_array.reshape(784)
    img_data = (img_data/255.0*0.99) + 0.01
    #显示图片
    plt.imshow(img_data.reshape((28,28)),cmap='Greys')
    inputs = img_data
    outputs = n.query(inputs)
    print(outputs)
    #找到输出结果值
    label = np.argmax(outputs)  #argmax返回最大值的索引值
    print("network's answer ==>> ",label)
    end_time = time.time()
    print('running time',end_time-start_time)

以下为完整代码:

import numpy as np
# scipy.special for the sigmoid function expit()
import scipy.special
import matplotlib.pyplot as plt
import time

import scipy.misc
#import glob
import imageio

start_time = time.time()

class neuralNetwork:
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        #set number of nodes in each input, hidden, output layer
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        #link weight matrices, wih (w_input_hidden) and who (w_hidden_output)
        self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes,self.inodes))
        self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes,self.hnodes))
        # learning rate
        self.lr = learningrate
        # activation function is the sigmoid function
        self.activation_function = lambda x:scipy.special.expit(x)
    def train(self,inputs_list, targets_list):
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(targets_list, ndmin=2).T
        
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        
        final_inputs = np.dot(self.who,hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        
        output_errors = targets - final_outputs
        hidden_errors = np.dot(self.who.T, output_errors)
        
        self.who += self.lr*np.dot((output_errors*final_outputs*(1.0 - final_outputs)),np.transpose(hidden_outputs))
        self.wih += self.lr*np.dot((hidden_errors*hidden_outputs*(1.0-hidden_outputs)),np.transpose(inputs))
        pass
    
    def query(self, inputs_list):
        # convert inputs list to 2d array
        inputs = np.array(inputs_list, ndmin=2).T
        
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs
        
    
input_nodes = 784
hidden_nodes = 500
output_nodes = 10

learning_rate = 0.2

#create instance of neural network
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)

training_data_file = open('C:/Users/Lenovo/Desktop/mnist_train_100.csv','r')
training_data_list = training_data_file.readlines()
training_data_file.close()

epochs = 2
for e in range(epochs):
    for record in training_data_list:
        all_values = record.split(',')
        inputs = (np.asfarray(all_values[1:])/255.0*0.99)+0.01
        targets = np.zeros(output_nodes) + 0.01
        targets[int(record[0])] = 0.99
        n.train(inputs,targets)
        pass
if __name__ == '__main__':
    start_time = time.time()
    print('running...')
    image_file_name = (r'C:/Users/Lenovo/Desktop/8.png')
#    img_array = scipy.misc.imread(image_file_name, flatten = True)
    #print('loading ... ',image_file_name)
    img_array = imageio.imread(image_file_name, as_gray=True)
    img_data = 255.0 - img_array.reshape(784)
    img_data = (img_data/255.0*0.99) + 0.01
    plt.imshow(img_data.reshape((28,28)),cmap='Greys')
    inputs = img_data
    outputs = n.query(inputs)
    print(outputs)
    label = np.argmax(outputs)  #argmax返回最大值的索引值
    print("network's answer ==>> ",label)
    end_time = time.time()
    print('running time',end_time-start_time)

在将epochs设为2,hidden_nodes设置为500后得到识别结果
neural network神经网络识别手写字体
但是识别结果精度不高,因为运行多次会一直改变。