对神经网络基础的理解
目录
一、《python神经网络编程》
二、一些粗浅的认识
1) 神经网络也是一种拟合
2)神经网络不是真的大脑
3)网络构建需要反复迭代
三、数字图像识别的实现思路
1)建立一个神经网络类
2)权重更新的具体实现
3)网络测试
四、总结
一、《python神经网络编程》
最近学习了一本书《python神经网络编程》,该书通过对一个数字识别案例的深入详细讲解,可以让读者对神经网络的思想有更加清晰的理解,明白计算机神经网络是如何工作的。在没有真正接触神经网络之前,总以为这是非常深奥的理论,也不明白神经网络是如何模拟人的大脑进行学习和判断的,难以理解计算机能够模拟人的大脑。《python神经网络编程》确实是一本很好的入门教材,它让读者能够真正踏入人工智能的门槛,奠定深入研究的基础。
二、一些粗浅的认识
1) 神经网络也是一种拟合
在数学中有很多拟合方法,比如线性拟合、多项式拟合等等。在这些拟合中,我们同样需要有确定的已知数据,然后通过这些拟合方法我们可以得到一个确定的数学解析表达式,并通过这个表达式来预测未知的结果。而神经网络的核心思想,我想也是一样的,只是这个实现过程相对复杂。我们最终能得到的是一个预测网络,也称模型,而不是一个精确的数学方程表达式。我们使用已知输入和输出的大量数据来训练神经网络,就是在让这个网络的输出结果逐渐逼近已知的输出结果。这个网络在训练的过程中,最终建立起了输入与输出之间的对应关系。但是这个对应关系我们无法用一个清晰的数学表达式来描述,可是我们可以使用这个网络来进行预测。正如对于图片中的数字而言,我们人看到图片中的数字就可以知道图中的数字是多少,因为在我们认识数字之前经过了学习,很多次有人告诉我们这个图形对应的数字就是这个数字。就像神经网络不能用一个准确的数学公式来描述输入与输出之间的关系一样,我们人类其实至今也还是不知道我们大脑学习的本质是什么。所以神经网络得到的拟合结果是一个黑盒子,我们针对特定的问题建立了一个黑盒子,然后训练了这个黑盒子,我们只知道给这个黑盒子一个输入,他能给出一个可信的结果。
2)神经网络不是真的大脑
神经网络只是借用了人类大脑神经元的形式。计算机神经网络是否真的能实现动物大脑的能力,我想还是难以断定。我们以前总是认为计算机的工作都是确定的,给它一个输入,我们必然能够准确预测它的输出结果。其实现在的神经网络也是一样的,虽然它是个黑盒子,但是给它相同的输入,它必然能够得到相同的结果,即使我们不知道它具体的计算逻辑,因为在这个网络中所有的参数经过训练后已经确定,计算机计算的每一步都是确定的。但计算机神经网络这种模糊拟合的实现,让我们认识到,当大量简单计算累计到一定数量时,在庞大的信息传递中是否就存在不可预测性。我们不理解自己为什么会思考,或许在不久的将来我们也无法确信庞大计算机系统的运行的真是我们人类设计的程序。
3)网络构建需要反复迭代
在构建神经网络过程中,输入与输出的节点数量需要根据实际问题进行分析。我们需要根据解决的实际问题,分析如何将问题的输入数字化,并设计相应的输入节点。而对于输出也是一样,我们需要分析如何用输出的数字信息来表达我们想要的结果形式,并设计相应的输出节点。我们常见的简单的神经网络通常是3层,包括输入、输出和中间的隐藏层,而隐藏层的层数以及每层的节点数该如何设计,这需要通过不断的尝试。较少的层数和节点数能够获得较高的计算效率,但是性能较低,误差较大,较多的层数和节点数能够获得较高的精度,但是计算效率低。因此,针对具体的问题需要综合考虑,反复迭代后确定网络构建形式。
三、数字图像识别的实现思路
1)建立一个神经网络类
《python神经网络编程》书中给出了一个实现数字识别的例子。在Python中首先定义一个3层神经网络类neuralNetwork。在类的初始化函数中确定网络的输入层节点数、隐藏层节点数、输出层节点数和学习率4个参数。并确定使用的激活函数。输入层与隐藏层、隐藏层和输出层之间的初始权重矩阵随机生成。
训练神经网络时,先根据训练数据的输入,正向逐层计算,最后得到网络的输出结果。然后计算网络输出结果与实际结果的误差值,然后再将误差进行反向传播,更新权重矩阵。这样,一组数据的训练就完成。
网络输出就是利用测试数据对网络进行一次正向输出的过程,最后根据输出数据给出网络的图像识别结果,具体实现后面再叙述。
class neuralNetwork:
# 初始化函数
def __int__(self, inputnodes, hiddennodes, outputnodes, learningrate):
self.inodes = inputnodes # 输入节点数
self.hnodes = hiddennodes # 隐藏节点数
self.onodes = outputnodes # 输出节点数
self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes)) # 随机生成初始的输入层到隐藏层的权重矩阵
self.who = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.onodes, self.hnodes)) # 随机生成初始的隐藏层到输出层的权重矩阵
self.learn = learningrate # 权重更新率
self.active_function = lambda x: scipy.special.expit(x) # 选择需要使用的激活函数
pass
# 定义训练函数
def train(self, input_list, target_list): # 提供已知的输入和输出结果
inputs = numpy.array(input_list, ndmin=2).T # 将输入转换为二维矩阵形式
targets = numpy.array(target_list, ndmin=2).T
hidden_inputs = numpy.dot(self.wih, inputs) # 计算隐藏层的输入值,输入层到隐藏层的权重矩阵乘以输入
hidden_outputs = self.active_function(hidden_inputs) # 计算隐藏层的输出值,将隐藏层的输入值带入激活函数
final_inputs = numpy.dot(self.who, hidden_outputs) # 计算输出层的输入值,隐藏层到输出层的权重矩阵乘以隐藏层的输出值
final_outputs = self.active_function(final_inputs) # 计算输出层的输出结果,将输出层的输入值带入激活函数
output_errors = targets - final_outputs # 计算误差值,已知的输出结果-输出层的输出值
hidden_errors = numpy.dot(self.who.T, output_errors) # 传递误差
# 更新权重矩阵
self.who += self.learn * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)),
numpy.transpose(hidden_outputs))
self.wih += self.learn * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)),
numpy.transpose(inputs))
pass
# 定义输出函数
def query(self, input_list):
inputs = numpy.array(input_list, ndmin=2).T
hidden_inputs = numpy.dot(self.wih, inputs)
hidden_outputs = self.active_function(hidden_inputs)
final_inputs = numpy.dot(self.who, hidden_outputs)
final_outputs = self.active_function(final_inputs)
return final_outputs
2)权重更新的具体实现
权重矩阵反复更新,就是神经网络不断学习,获得正确拟合结果的过程。权重更新根据严格的数学推导公式,程序实现过程不能直观反映具体的实现思想。它的核心就是利用每次训练的计算误差来修正权重矩阵。
(1)误差计算
在训练神经网络过程中计算误差时,程序中我们直接将实际结果与计算结果相减(output_errors = targets - final_outputs),得到一个误差向量。但是理论分析时,直接相减计算误差的方法并不合适,我们通常使用差的平方来表示,所有节点的总误差如下:
(2)误差传递
我们可以直接计算出输出层的误差,但是最终的误差是由前面的计算逐层、逐节点计算累计得到的,该如何将最终的误差反向分配到每个节点上呢?通常我们认为节点之间连接权重越大的链路,计算引入的误差也越大,因此,我们也同样通过权重来分配误差。针对本例而言,我们已经得到了输出层的误差,还需要计算隐藏层的误差。输入层不需要计算,因为默认输入层输入与输出是相同的,不需要使用激活函数。那么隐藏层的输出误差计算如下:
此处需要注意的是,误差反向传播计算的矩阵正好是正向计算时隐藏层到输出层权重矩阵的转置,至于为什么是转置,我们手动简单计算一下就知道了。同时需要注意的是,通过上式计算得到的总误差并不是输出层的总误差() 。因为,更新权重矩阵时,我们其实并不关心过程中的误差大小,而是更关心是哪个链路导致的误差更大,只要能体现出各链路误差的相对大小就可以。
(3)更新权重
在此例中使用跟新权重的方法是梯度下降法,这也是使用非常广泛的一种方法。我们需要建立起权重与误差之间的关系。我们以最后的输出层为例,输出层的输入是隐藏层的输出乘以隐藏层和输出层之间的权重矩阵:
将此结果代入激活函数,则输出层的结果为:
sigmoid函数为激活函数,形式为:
则每个节点的输出误差为:
将上述公式联合,然后对权重w求导,就可以得到误差相对于权重的导数(斜率):
权重更新公式为:
上述公式中,我们实际使用时并不关心常系数2,因此在代码实现时省略了。
3)网络测试
网络测试的具体实现可参见代码。
# 测试网络
scorecard = []
i = 0
for record in test_data_list:
i += 1
if(i != 10): # 可调整数字,逐张识别
continue
pass
all_values = record.split(',')
correct_label = int(all_values[0])
inputs = (numpy.asarray(all_values[1:], numpy.float32) / 255.0 * 0.99) + 0.01
image_array = numpy.asarray(all_values[1:], numpy.int32).reshape((28, 28))
fig = matplotlib.pyplot.figure(figsize=(5, 5))
matplotlib.pyplot.imshow(image_array, cmap='Greys', interpolation='None')
matplotlib.pyplot.show()
outputs = n.query(inputs) # 网络识别的结果
label = numpy.argmax(outputs) # 输出结果中的最大值
print("输入数字:", all_values[0], "识别结果:", label) # 打印出实际值
if(label == correct_label):
scorecard.append(1)
else:
scorecard.append(0)
pass
pass
scorecard_array = numpy.asarray(scorecard)
print("performance = ", scorecard_array.sum() / scorecard_array.size)
四、总结
《python神经网络编程》与书中的示例简明阐述了神经网络的核心思想和基本架构,任何复杂的神经网络均可基于此构建。我们可以通过使用不同的网络层数、节点数、激活函数以及链路连接方式等,构建不同的适用于各种应用的神经网络模型。或许正式因为神经网络内部的不可描述性,对网络结构的各种优化,为研究神经网络开辟了广阔空间。