提要
本文带大家从原理到实践,手动构建一个能识别手写数字的神经网络
。
我们的要做的是,训练出一个人工神经网络(ANN),使它能够识别手写数字(如下图所示)。
人工神经网络(Artificial Neural Network,简称ANN),也被称为神经网络(NN),是受生物神经网络(Biological Neural Networks)启发的计算系统。
神经网络是一种重要的人工智能技术,其在图像识别、自然语言处理、医疗和金融等领域得到了广泛应用。
这些手写数字,来自大名鼎鼎的 MNIST 手写数字图像集。
MNIST 是机器学习领域最有名的数据集之一,也称作计算机视觉领域的 hello world 数据集。
由于人工神经网络(Artificial Neural Network)的灵感来源是生物神经网络(Biological Neural Networks),那么接下来,我将按照以下思路讲解:
生物神经网络一般指,生物的大脑神经元、细胞、触突等结构组成的一个大型网络结构,用来帮助生物进行思考和行动。
【神经元】
神经元即神经元细胞,是神经系统最基本的结构和功能单位。
神经元包括胞体和突起两大部分,突起又分为轴突和树突。
下面从功能角度,对生物神经元进行进行介绍:
【神经网络】
生物神经系统由大量神经元构成,每个神经元都可能连接到数千个其他神经元,这会形成一个复杂的神经网络。这个网络使得我们可以感知环境、思考、记忆、学习等。我们的每一个思想、感觉和行动都是由这种神经元间的连接和交流驱动的。
【知识学习】
1949 年 Hebb 提出了突触学习的模型(Hebb定律)。该理论认为,神经网络是具有可塑性的,重复性的经验可以加强或削弱神经元间的连接,即:学习,在脑分子层次上,导致的是神经元间突触连接的变化。这使得我们的大脑可以适应新的信息、经历和学习。
神经网络是一种对生物神经网络组织结构和运行机制的抽象、简化和模拟,以期能够实现类人工智能的机器学习技术。
下面还是从功能的角度,以对比的方式,描述人工神经网络如何实现对生物神经网络的抽象、简化和模拟。
我们先对前面所讲的,生物神经元、神经网络最核心的特性,进行提炼:
所以人工神经元的抽象模型,如下图所示:
接下来,我们从运行的角度,描述人工神经网络是如何工作的。
我们使用一个有3层、每层3个神经元的神经网络,讲解神经信号在神经网络中的正向传播过程。
第一步,计算隐藏层各神经元的神经信号输入。
第二步:计算隐藏层各神经元的神经信号输出。
第三步:输出层各神经元的神经信号输入为
第四步:输出层各神经元的神经信号输出为
以上就是神经信号在神经网络中的正向传播过程。
从这一节开始,就进入了神经网络的训练过程,即神经网络积累知识的过程。
上一节描述了,神经信号如何输入神经网络并得到输出的过程。从我们要做的事情“训练出一个人工神经网络,使它能够识别手写数字”角度说,我们最终的期望就是,把一张手写数字7的图片输入神经网络,神经网络的输出是数字7,把一张手写数字3的图片输入神经网络,神经网络的输出是数字3。
所谓“训练”,就是事先准备好一堆有正确答案的数据(比如:图1是3,图2是6,图2是9 ...),把这些数据和答案输入神经网络,让神经网络自己从这堆数据中把数据中潜藏的“知识”提炼出来,这些提炼出来的知识表现为神经网络中确定下来的权重。
由于还未经过训练的神经网络无法完成我们期望它做的事情(例如:输入手写数字3,输出是6;输入手写数字1,输出是9 ...)。也就是说,对于给定神经网络的输入,我们期望神经网络应该输出的结果 和 神经网络的实际输出结果之间存在误差。
所以,针对这个误差,我们要做的工作是:
我们先解决第一个问题:将神经网络的输出误差反向传播到神经网络中的各个神经元上。
计算隐藏层各神经元的误差,采用的是按照权重的比例,将误差反向传播到隐藏层的结点上。
在完成了第一件事(即,将神经网络的输出误差反向传播到神经网络中的各个神经元上)后
我们再完成第二件事情(即,找到某种方式,能让我们利用这个误差来指导我们调整神经网络中的权重,以达到最小化神经网络输出误差的目的),就可以实现对神经网络的训练。
既然要让神经网络的输出误差最小,那么我们需要先构建神经网络的输出误差函数。
直接求误差函数的最小值很难,所以我们采用梯度下降法,以小步迭代的方式,找到目标函数(即我们的)的最小值。
下面使用 Python 把上述过程实现出来。
NeuralNetwork.py:神经网络
import scipy.special
class NeuralNetwork:
def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate) -> None:
self.input_nodes = input_nodes
self.hidden_nodes = hidden_nodes
self.output_nodes = output_nodes
self.weight_input_hidden = np.random.normal(0.0, pow(self.hidden_nodes, -0.5), (self.hidden_nodes, self.input_nodes))
self.weight_hidden_output = np.random.normal(0.0, pow(self.output_nodes, -0.5), (self.output_nodes, self.hidden_nodes))
self.learning_rate = learning_rate
self.activation_function = lambda x: scipy.special.expit(x)
pass
def train(self, input_list, target_list):
inputs = np.array(input_list, ndmin=2).T
targets = np.array(target_list, ndmin=2).T
hidden_inputs = np.dot(self.weight_input_hidden, inputs)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = np.dot(self.weight_hidden_output, hidden_outputs)
final_outputs = self.activation_function(final_inputs)
output_errors = targets - final_outputs
hidden_errors = np.dot(self.weight_hidden_output.T, output_errors)
self.weight_hidden_output += self.learning_rate * np.dot(output_errors * final_outputs * (1.0 - final_outputs), np.transpose(hidden_outputs))
self.weight_input_hidden += self.learning_rate * np.dot(hidden_errors * hidden_outputs * (1.0 - hidden_outputs), np.transpose(inputs))
pass
def query(self, input_list):
inputs = np.array(input_list, ndmin=2).T
hidden_inputs = np.dot(self.weight_input_hidden, inputs)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = np.dot(self.weight_hidden_output, hidden_outputs)
final_outputs = self.activation_function(final_inputs)
return final_outputs
main.py:神经网络训练与测试:
import numpy as np
import matplotlib.pyplot
import pylab
from NeuralNetwork import *
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.1
n = NeuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
training_data_file = open("mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
epochs = 5
total_progress = 5 * len(training_data_list)
current_progress = 0
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(all_values[0])] = 0.99
n.train(inputs, targets)
current_progress += 1
print("\rtraining progress: {:3}%".format((current_progress / total_progress) * 100), end="")
pass
pass
test_data_file = open("mnist_test.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])
inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
outputs = n.query(inputs)
label = np.argmax(outputs)
if (label == correct_label):
scorecard.append(1)
else:
scorecard.append(0)
pass
scorecard_array = np.asarray(scorecard)
print ("\nperformance = ", scorecard_array.sum() / scorecard_array.size)
参考:
《Python神经网络编程》 MNIST in CSV:https://pjreddie.com/projects/mnist-in-csv/ THE MNIST DATABASE of handwritten digits:http://yann.lecun.com/exdb/mnist/ NN-SVG:http://alexlenail.me/NN-SVG/LeNet.html NeuroMorpho.Org:https://neuromorpho.org/