这是作者在CSDN上的第二篇博客,关于阅读塔里克·拉希德的著作《Python神经网络编程(Make Your Own Neural Network)》之后的读书笔记。跟诸位大牛相比,笔者阅历尚浅、经验不足,笔记中若有错误,还需继续修正与增删。欢迎大家的批评与指正。
以下文章是笔者整理出的关于神经网络构建的清晰方法步骤,欢迎大家阅读:How To Make Your Own Neural Network?——根据《Python神经网络编程(Make Your Own Neural Network)》一书整理。
神经网络是一种模拟人脑的神经网络,以期能够实现类人工智能的机器学习技术。
人工智能的机制,即通过复制生物大脑工作的机制来构建人工大脑:真正的大脑具有神经元,而不是逻辑门。真正的大脑具有更优雅更有机的推理,而不是冰冷的、非黑即白的、绝对的传统算法。
人工智能只不过是用更高级、更复杂的数学指令,告诉计算机怎么做,怎么模拟人类行为,让计算机“佯装”理解人类的感情。
Truth:无需太高深的数学思想,仅凭高中数学,就可以打造出一个专家级别的“神经网络”。
“人工智能时代存在一个人类价值体现方式变革的问题。”换句话说,如果我们依旧指望课本里的那些知识求生存,不求创新与探索,那么对知识掌握得再好,也只是拾人牙慧,只能湮没于滚滚的历史车轮之下。
本书揭示了神经网络背后的概念,并介绍了如何通过Python实现神经网络。
全书分为3个章节以及2个附录:
“从你身边所有的小事情中,找到灵感。”
计算机的核心是计算器。计算机缺乏的是“智能”。
问题 | 计算机 | 人类 |
---|---|---|
快速地对成千上万的大数字进行乘法运算 | easy | hard |
在一大群人的照片中查找面孔 | hard | easy |
神经网络中学习的核心过程是持续细化误差值。注意代数法和“改进法”的不同点,并明白为什么我们会选择看似不怎么聪明的“改进法”。
我们可以使用直线将不同性质的事物分开,唯一需要调整的是斜率,这就是简单的分类器。
使用朴素的调整方法会出现一个问题,即改进后的模型只与最后一次训练样本最匹配,“有效地”忽略了所有以前的训练样本。解决这个问题的的一种好方法是使用学习率。
如果数据本身不是由单一线性过程支配,那么一个简单的线性分类器不能对数据进行划分。解决方案是使用多个线性分类器来划分,这也是神经网络的核心思想。
神经网络的一个重要的设计特征来源于对这个局限性的理解。
相比于传统的计算机系统,生物大脑对损坏和不完善的信号具有难以置信的弹性(即模糊性),这就是神经元的神奇之处,也是我们构建神经网络的生物模型。
多 对 多 的 I / O 方 式 多对多的I/O方式 多对多的I/O方式
计算神经网络的输出值只需要简单的数学方法,然而随着层数以及节点数的增多,简单的计算会显得无比繁琐。
通过神经网络向前馈送信号所需的大量运算可以表示为矩阵乘法。矩阵乘法又称为点乘(dot product)或内积(inner product),不管神经网络的规模如何,使用矩阵乘法可以更简洁地进行书写,并且允许计算机高速高效地进行计算。
两个矩阵需要互相兼容:第一个矩阵中的列数目=第二个矩阵中的行数目。
W ⋅ I = X W·I=X W⋅I=X
W是权重矩阵,I是输入矩阵,X是输入到第二层的结果矩阵。
O = s i g m o i d ( X ) O=sigmoid(X) O=sigmoid(X)
O是应用激活函数后的输出矩阵。
三层神经网络的第一层为输入层,第二层为隐藏层,最后一层为输出层。
不管有多少层神经网络,我们都“一视同仁”,即:
- 组合输入信号;
- 应用链接权重调节这些输入信号;
- 应用激活函数生成这些层的输出信号。
将神经网络的输出值与训练样本的输出值比较以计算出误差,使用误差值来调整神将网络——最重要的事情
在神经网络中,我们在两件事情上使用了权重。第一件事情,向前馈送信号;第二件事情,反向传播误差。
需要使用误差来指导在网络内部如何调整链接权重。问题在于,在距离输出层相对较远的层中,我们如何更新链接权重?
训练样本数据只告诉我们最终输出节点的输出应该为多少,而没有告诉我们任何其他层节点的输出应该是多少。我们需要找到获取每一个节点误差的可行方法。
第一个隐藏层节点的误差是与这个节点前向连接所有链接中分割误差的和。输入层同理。
前向馈送信号和反向传播误差都可以使用矩阵计算而变得高效,其基本思想是:将过程矢量化。
本节的问题:如何调整输入层第一个节点和隐藏层第二个节点之间链路的权重,以使得输出层第三个节点的输出增加0.5呢?
梯度下降法是求解函数最小值一种很好的办法,当函数不能轻易使用数学代数求解时适用。在梯度下降法中,我们可以使用更小的步子朝着实际的最小值方向迈进,优化答案,直到我们对于所得到的精度感到满意为止。
虽然一次更新只是一个相当小的变化量,但权重经过成百上千次的迭代,最终会确定下来,达到一种布局,这样训练有素的神经网络就会生成与训练样本中相同的输出。
并不是所有使用神经网络的尝试都能够成功。但我们可以通过改进训练数据、初始权重、设计良好的输出方案来解决。
“纸上得来终觉浅,绝知此事须躬行。”
“不积跬步,无以至千里。”
Python是一种简单易学的计算机语言,是一种合适的入门语言。
我们将使用一个预先打包的解决方案,称为IPython。IPython中包含了Python编程语言以及几种常见的数字和数据绘图扩展包,这包括了我们需要的工具。IPython有一个显著的优势,如交互式Notebook。
写出Python代码的平台——Notebook;
神经网络快速运转的基础——自动化工作;
代码的“解释师”——注释;
可重用的计算机指令——函数;
数学矩阵的超强载体——数组;
神经网络解决图像识别问题的基础——绘制数组;
神经网络的主体躯干——对象。
Notebook是交互式的,这意味着它等待你提出要求,提出要做的事情,然后,Notebook执行这些命令,并在Notebook中给出答案。如果你想做的事情相对复杂,那么将这个问题分解为几个部分比较合理。对于IPython而言,我们称这些部分为单元格(cell)。
print("Hello World!")
Hello World!
x = 10
print(x)
print(x + 5)
y = x + 7
print(y)
print(z)
10
15
17
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in
6 print(y)
7
----> 8 print(z)
NameError: name 'z' is not defined
计算机的潜力在于,只使用一组很短的指令就快速地执行大量的工作。
这是神经网络得以快速运转的基础。
list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
注:当我们以交互的方式与Python一起工作时,关键字"print"是可选的。
for n in range(10):
print("The square of",n, "is", n * n)
pass
print("done")
The square of 0 is 0
The square of 1 is 1
The square of 2 is 4
The square of 3 is 9
The square of 4 is 16
The square of 5 is 25
The square of 6 is 36
The square of 7 is 49
The square of 8 is 64
The square of 9 is 81
done
注:在Python中,缩进的使用是有意识地显示哪些指令在其他指令的管辖之下,因此缩进很重要。
有意义的代码注释可以使得代码的含义更加清晰。
# 下面输出的是2的立方数
print(2 ** 3)
8
包括Python在内,计算机语言都尽量使得创建可重用的计算机指令变得容易。这就用到了函数。
# 函数:接受两个数字作为输入,输出它们的平均数
def avg(x, y):
print("第一个输入值是", x)
print("第二个输入值是", y)
a = (x + y) / 2.0
print("平均数是", a)
return a
Python的优点:
一些计算机语言可能要让你明确这是什么类型的对象,但是Python不会;Python只会在你试图滥用变量时报错。
avg(2, 4)
第一个输入值是 2
第二个输入值是 4
平均数是 3.0
3.0
数组只是数值表格,非常便于使用。就像表格一样,可以根据行数和列数来指示特定的单元。
当我们要编码神经网络时,将使用数组来表示输入信号、权重、输出信号的矩阵,以及在信号前馈或误差反向传播时的信号和误差矩阵。
import numpy
a = numpy.zeros([3, 2])
print(a)
[[0. 0.]
[0. 0.]
[0. 0.]]
a[0, 0] = 1
a[0, 1] = 2
a[1, 0] = 9
a[2, 1] = 12
print(a)
[[ 1. 2.]
[ 9. 0.]
[ 0. 12.]]
print(a[0, 1])
v = a[1, 0]
print(v)
2.0
9.0
可视化数组有助于我们快速获取数组的一般意义。绘制二维数字数组的一种方式是将它们视为二维平面,根据数组中单元格的值对单元格进行着色。
绘制数组是神经网络解决图像识别问题(如手写数字识别)的基础。
import matplotlib.pyplot
%matplotlib inline
具有相同值的数组单元颜色也相同。
matplotlib.pyplot.imshow(a, interpolation = "nearest")
对象类似于可重用函数,但相比简单的函数,对象可以做的事情要多得多。对象是类的实例。对象函数被称为方法(method)。
神经网络需要接受某些输入,进行一些计算并产生输出,同时我们可以训练神经网络。这些动作、训练和生成的答案,是神经网络的原生函数,即神经网络对象的函数。
神经网络内部有数据,也就是链接权重,这些数据是属于神经网络的。这就是我们把神经网络构建为对象的原因。
# class for a dog object
class Dog:
# 内部数据的初始化方法
def __init__(self, petname, temp):
self.name = petname
self.temperature = temp
# 获得当前状态
def status(self):
print("dog name is", self.name)
print("dog temperature is", self.temperature)
pass
# 设置温度
def setTemperature(self, temp):
self.temperature = temp
pass
# 狗可以叫
def bark(self):
print("woof!")
pass
pass
类和对象之间的区别:
类只是定义,对象是所定义类的真正实例。(菜谱与菜品)
# 从狗class创造一个新的狗object
lassie = Dog('Lassie', 37)
lassie.status()
lassie.setTemperature(40)
lassie.status()
dog name is Lassie
dog temperature is 37
dog name is Lassie
dog temperature is 40
本节的任务是制作一个3层神经网络,完成对其类(class)的定义。
神经网络的三个函数:
神经网络的四个参数:
下一步是创建网络的节点和链接。网络中最重要的部分是链接权重,我们使用权重计算前馈信号、反向传播误差,并且在试图改进网络时优化链接权重本身。
简单的初始随机权重的确定:
比较复杂的初始随机权重的确定(正态概率分布)8:
平均值为0,标准方差为节点传入链接数目的开方,即 1 传 入 链 接 数 目 \cfrac{1}{\sqrt{传入链接数目}} 传入链接数目1
先编写简单的查询函数。
注:由于我们可能希望实验、调整甚至完全改变激活函数,因此当神经网络对象初始化时,在神经网络对象内部只定义一次S函数。11
在训练神经网络的过程中有两个阶段:第一阶段是计算输出(如同query()所做的事情),第二阶段是反向传播误差,告知如何优化链接权重。我们已经完成了第一阶段的任务。
我们即将接近神经网络工作的核心,即基于所计算输出与目标输出之间的误差,改进权重。
两个部分:
注:*乘法是正常的对应元素的乘法,·点乘是矩阵点积。
当前代码可用于创建、训练和查询3层神经网络,进行几乎任何任务。
下一步将进行特定任务,学习识别手写数字。
# 神经网络class定义
class neuralNetwork():
# 初始化神经网络
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 设置输入、隐藏、输出层的节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 链接权重矩阵,wih与who
# 矩形数组中的权重是w_i_j,即从i节点到下一层的j节点的链接
# w11 w21
# w12 w22 etx
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.onodes, -0.5), (self.onodes, self.hnodes))
# 学习率
self.lr = learningrate
# 激活函数是sigmoid函数(S函数)
self.activation_function = lambda x: scipy.special.expit(x)
pass
# 对神经网络进行训练
def train(self, inputs_list, targets_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
targets = numpy.array(targets_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
# 输出层误差为(target - actual)
output_errors = targets - final_outputs
# 隐藏层误差为按权重比例分割输出层误差后,在隐藏层节点的重组值
hidden_errors = numpy.dot(self.who.T, output_errors)
# 更新隐藏层与输出层之间链接的权重
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输入层与隐藏层之间链接的权重
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# 对神经网络进行查询
def query(self, inputs_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
return final_outputs
让计算机准确区分图像中包含的内容,称之为图像识别问题。
注:cmap="Greys"参数:选择灰度调色板,以更好地显示手写字符。
神经网络能够正确区分它从来没有见过的手写字符,这说明我们的实验成功了。
此时改变文件名,这样就可以指向具有60000条记录的完整的训练数据集,以及具有10000条记录的测试数据集。这有效提升了神经网络的性能。
当前神经网络的甜蜜点:5个世代,0.1学习率
隐藏层是学习发生的场所,隐藏层节点前后的链接权重具有学习能力。
注:随着增加隐藏层节点的数量,结果有所改善,但不显著。明显的缺点是:训练网络所用的时间显著增加了。必须在可容忍的运行时间内选择某个数目的隐藏层节点。
# python notebook for Make Your Own Neural Network
# 代码用于一个3层的神经网络,以及学习手写数字识别的数据集
# Xiangtan, 2019/9/26
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# 绘制数字矩形数组的库
import matplotlib.pyplot
# 确保绘制的图像在这个notebook内部,而不是外部窗口
%matplotlib inline
# 神经网络class定义
class neuralNetwork():
# 初始化神经网络
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 设置输入、隐藏、输出层的节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 链接权重矩阵,wih与who
# 矩形数组中的权重是w_i_j,即从i节点到下一层的j节点的链接
# w11 w21
# w12 w22 etx
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.onodes, -0.5), (self.onodes, self.hnodes))
# 学习率
self.lr = learningrate
# 激活函数是sigmoid函数(S函数)
self.activation_function = lambda x: scipy.special.expit(x)
pass
# 对神经网络进行训练
def train(self, inputs_list, targets_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
targets = numpy.array(targets_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
# 输出层误差为(target - actual)
output_errors = targets - final_outputs
# 隐藏层误差为按权重比例分割输出层误差后,在隐藏层节点的重组值
hidden_errors = numpy.dot(self.who.T, output_errors)
# 更新隐藏层与输出层之间链接的权重
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输入层与隐藏层之间链接的权重
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# 对神经网络进行查询
def query(self, inputs_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.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.1
# 创建神经网络的一个实例
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
# 加载书写数字识别的训练数据CSV文件为一个列表
training_data_file = open(r"E:\Neural Network\mnist_dataset\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 训练神经网络
# 世代是指训练数据集被用来测试的次数
epochs = 5
for e in range(epochs):
# 遍历训练数据集中的所有记录
for record in training_data_list:
# 在','逗号处分割记录
all_values = record.split(',')
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 创建目标输出值(除了期望值标签对应的是0.99,全部为0.01)
targets = numpy.zeros(output_nodes) + 0.01
# all_values[0]是此记录的目标标签
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
pass
pass
# 加载书写数字识别的测试数据CSV文件为一个列表
test_data_file = open(r"E:\Neural Network\mnist_dataset\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])
# print(correct_label, "correct label")
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 查询网络
outputs = n.query(inputs)
# 最大值的索引对应的标签
label = numpy.argmax(outputs)
# print(label, "network's answer")
# 添加正确(1)或不正确(0)到列表中
if (label == correct_label):
# 网络的答案匹配正确的答案,计分板加1
scorecard.append(1)
else:
# 网络的答案不匹配正确的答案,计分板加0
scorecard.append(0)
pass
pass
# 计算性能分数,即正确答案的得分
scorecard_array = numpy.asarray(scorecard)
print("performance =", scorecard_array.sum() / scorecard_array.size)
运行结果:
performance = 0.9755
“寓教于乐。”
不使用MNIST测试集对网络进行测试,而是使用自己创建的图像数据对网络进行测试。
神经网络将它们所学到的知识分布在几条链接权重上:
方法步骤:
代码:
# python notebook for Make Your Own Neural Network
# 代码用于一个3层的神经网络,以及学习手写数字识别的数据集
# 此版本用书写数字识别的数据集进行训练,然后测试我们自己的手写数字图像
# Xiangtan, 2019/9/28
# 从PNG图像文件中加载数据的助手
import imageio
# glob帮助你使用模式匹配选择多个文件
import glob
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# 绘制数字矩形数组的库
import matplotlib.pyplot
# 确保绘制的图像在这个notebook内部,而不是外部窗口
%matplotlib inline
# 神经网络class定义
class neuralNetwork():
# 初始化神经网络
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 设置输入、隐藏、输出层的节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 链接权重矩阵,wih与who
# 矩形数组中的权重是w_i_j,即从i节点到下一层的j节点的链接
# w11 w21
# w12 w22 etx
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.onodes, -0.5), (self.onodes, self.hnodes))
# 学习率
self.lr = learningrate
# 激活函数是sigmoid函数(S函数)
self.activation_function = lambda x: scipy.special.expit(x)
pass
# 对神经网络进行训练
def train(self, inputs_list, targets_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
targets = numpy.array(targets_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
# 输出层误差为(target - actual)
output_errors = targets - final_outputs
# 隐藏层误差为按权重比例分割输出层误差后,在隐藏层节点的重组值
hidden_errors = numpy.dot(self.who.T, output_errors)
# 更新隐藏层与输出层之间链接的权重
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输入层与隐藏层之间链接的权重
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# 对神经网络进行查询
def query(self, inputs_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.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.1
# 创建神经网络的一个实例
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
# 加载书写数字识别的训练数据CSV文件为一个列表
training_data_file = open(r"E:\Neural Network\mnist_dataset\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 训练神经网络
# 世代是指训练数据集被用来测试的次数
epochs = 5
for e in range(epochs):
# 遍历训练数据集中的所有记录
for record in training_data_list:
# 在','逗号处分割记录
all_values = record.split(',')
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 创建目标输出值(除了期望值标签对应的是0.99,全部为0.01)
targets = numpy.zeros(output_nodes) + 0.01
# all_values[0]是此记录的目标标签
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
pass
pass
# 我们自己的图像测试数据集
our_own_dataset = []
# 加载PNG图像数据为测试数据集
for image_file_name in glob.glob(r'E:\Neural Network\mnist_dataset\test_my_own_*.png'):
print("loading ...", image_file_name)
# 使用文件名来设置正确的标签
label = int(image_file_name[-5:-4])
# 从PNG文件中加载数据为一个数组
img_array = imageio.imread(image_file_name, as_gray = True)
# 重塑数组,从28x28的方块数组变成很长的一串784个数值,将值取反
img_data = 255.0 - img_array.reshape(784)
# 然后将图像数据缩放到0.01与1.0之间
img_data = (img_data / 255.0 * 0.99) + 0.01
print(numpy.min(img_data))
print(numpy.max(img_data))
# 将标签和图像数据加入测试数据集
record = numpy.append(label, img_data)
our_own_dataset.append(record)
pass
# 用我们自己的图像测试神经网络
# 计分板,初始化为空
scorecard = []
# 遍历测试数据集中的所有记录
for record in our_own_dataset:
# 正确答案是第一个值
correct_label = int(record[0])
print(correct_label, "correct label")
# 数据为剩余的值
inputs = record[1:]
# 查询网络
outputs = n.query(inputs)
# 最大值的索引对应标签
label = numpy.argmax(outputs)
print(label, "network's answer")
# 添加正确(1)或不正确(0)到列表中
if (label == correct_label):
print("match!")
scorecard.append(1)
else:
print("no match!")
scorecard.append(0)
pass
pass
print(scorecard)
# 计算性能分数,即正确答案的得分
scorecard_array = numpy.asarray(scorecard)
print("performance =", scorecard_array.sum() / scorecard_array.size)
运行结果:
loading ... E:\Neural Network\mnist_dataset\test_my_own_2.png
0.01
1.0
loading ... E:\Neural Network\mnist_dataset\test_my_own_3.png
0.01
1.0
loading ... E:\Neural Network\mnist_dataset\test_my_own_4.png
0.01
0.93011767
loading ... E:\Neural Network\mnist_dataset\test_my_own_5.png
0.01
0.86800003
loading ... E:\Neural Network\mnist_dataset\test_my_own_6.png
0.01
1.0
loading ... E:\Neural Network\mnist_dataset\test_my_own_noisy_6.png
0.14588237
0.77482355
2 correct label
3 network's answer
no match!
3 correct label
3 network's answer
match!
4 correct label
4 network's answer
match!
5 correct label
5 network's answer
match!
6 correct label
6 network's answer
match!
6 correct label
6 network's answer
match!
[0, 1, 1, 1, 1, 1]
performance = 0.8333333333333334
本节问题:在神经网络的“眼中”,图像应该是怎样的?
虽然黑盒子(神经网络)已经学会如何求解问题,但是其所学习到的知识常常不能转化为对问题的理解和智慧。
馈送一个标签到输出节点,通过已受训练的网络反向输入信号,直到输入节点弹出一个图像,这就是向后查询。
# python notebook for Make Your Own Neural Network
# 代码用于一个3层的神经网络,以及学习手写数字识别的数据集
# 此版本询问神经网络图像应该是怎样的,给定一个标签
# Xiangtan, 2019/9/28
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()和它的逆函数logit()
import scipy.special
# 绘制数字矩形数组的库
import matplotlib.pyplot
# 确保绘制的图像在这个notebook内部,而不是外部窗口
%matplotlib inline
# 神经网络class定义
class neuralNetwork():
# 初始化神经网络
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 设置输入、隐藏、输出层的节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 链接权重矩阵,wih与who
# 矩形数组中的权重是w_i_j,即从i节点到下一层的j节点的链接
# w11 w21
# w12 w22 etx
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.onodes, -0.5), (self.onodes, self.hnodes))
# 学习率
self.lr = learningrate
# 激活函数是sigmoid函数(S函数),它的逆函数是对数函数
self.activation_function = lambda x: scipy.special.expit(x)
self.inverse_activation_function = lambda x: scipy.special.logit(x)
pass
# 对神经网络进行训练
def train(self, inputs_list, targets_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
targets = numpy.array(targets_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
# 输出层误差为(target - actual)
output_errors = targets - final_outputs
# 隐藏层误差为按权重比例分割输出层误差后,在隐藏层节点的重组值
hidden_errors = numpy.dot(self.who.T, output_errors)
# 更新隐藏层与输出层之间链接的权重
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输入层与隐藏层之间链接的权重
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# 对神经网络进行查询
def query(self, inputs_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
return final_outputs
# 向后查询神经网络
# 我们对于每一项使用相同的术语
# eg target是网络右侧的值,尽管在此用于输入
# eg hidden_output是网络中间节点发往右侧的信号
def backquery(self, targets_list):
# 将目标列表转置为垂直数组
final_outputs = numpy.array(targets_list, ndmin = 2).T
# 计算进入输出层的信号
final_inputs = self.inverse_activation_function(final_outputs)
# 计算隐藏层发出的信号
hidden_outputs = numpy.dot(self.who.T, final_inputs)
# 缩小到0.01到0.99
hidden_outputs -= numpy.min(hidden_outputs)
hidden_outputs /= numpy.max(hidden_outputs)
hidden_outputs *= 0.98
hidden_outputs += 0.01
# 计算进入隐藏层的信号
hidden_inputs = self.inverse_activation_function(hidden_outputs)
# 计算输入层发出的信号
inputs = numpy.dot(self.wih.T, hidden_inputs)
#缩小到0.01到0.99
inputs -= numpy.min(inputs)
inputs /= numpy.max(inputs)
inputs *= 0.98
inputs += 0.01
return inputs
# 输入、隐藏、输出层的节点数
input_nodes = 784
hidden_nodes = 200
output_nodes = 10
# 学习率
learning_rate = 0.1
# 创建神经网络的一个实例
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
# 加载书写数字识别的训练数据CSV文件为一个列表
training_data_file = open(r"E:\Neural Network\mnist_dataset\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 训练神经网络
# 世代是指训练数据集被用来测试的次数
epochs = 5
for e in range(epochs):
# 遍历训练数据集中的所有记录
for record in training_data_list:
# 在','逗号处分割记录
all_values = record.split(',')
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 创建目标输出值(除了期望值标签对应的是0.99,全部为0.01)
targets = numpy.zeros(output_nodes) + 0.01
# all_values[0]是此记录的目标标签
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
pass
pass
# 加载书写数字识别的测试数据CSV文件为一个列表
test_data_file = open(r"E:\Neural Network\mnist_dataset\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])
# print(correct_label, "correct label")
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 查询网络
outputs = n.query(inputs)
# 最大值的索引对应的标签
label = numpy.argmax(outputs)
# print(label, "network's answer")
# 添加正确(1)或不正确(0)到列表中
if (label == correct_label):
# 网络的答案匹配正确的答案,计分板加1
scorecard.append(1)
else:
# 网络的答案不匹配正确的答案,计分板加0
scorecard.append(0)
pass
pass
# 计算性能分数,即正确答案的得分
scorecard_array = numpy.asarray(scorecard)
print("performance =", scorecard_array.sum() / scorecard_array.size)
performance = 0.9745
我们是在询问神经网络——对于答案“0”,最理想的问题是什么。
# 向后运行网络,给出一个标签,查看它绘制出怎样的图像
# 测试标签
label = 9
# 创建对于这个标签的输出信号
targets = numpy.zeros(output_nodes) + 0.01
# all_values[0]是此记录的目标标签
targets[label] = 0.99
print(targets)
# 获取图像数据
image_data = n.backquery(targets)
# 绘制图像
matplotlib.pyplot.imshow(image_data.reshape(28, 28), cmap = 'Greys', interpolation = 'None')
[0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.99]
关于创造更多的变化类型作为样本的一个很酷的想法,就是利用已有的样本,通过顺时针或逆时针旋转它们,比如说旋转10度,创建新的样本。
注:如果旋转的角度过大,神经网络的性能会出现下降。由于旋转较大的角度意味着创建了实际上不能代表数字的图像,这可以理解。
# python notebook for Make Your Own Neural Network
# 代码用于一个3层的神经网络,以及学习手写数字识别的数据集
# 此版本创建了额外的训练样本通过旋转每个原始样本10度(顺时针与逆时针)
# Xiangtan, 2019/9/28
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# scipy.ndimage用于旋转图像数组
import scipy.ndimage
# 神经网络class定义
class neuralNetwork():
# 初始化神经网络
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 设置输入、隐藏、输出层的节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 链接权重矩阵,wih与who
# 矩形数组中的权重是w_i_j,即从i节点到下一层的j节点的链接
# w11 w21
# w12 w22 etx
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.onodes, -0.5), (self.onodes, self.hnodes))
# 学习率
self.lr = learningrate
# 激活函数是sigmoid函数(S函数)
self.activation_function = lambda x: scipy.special.expit(x)
pass
# 对神经网络进行训练
def train(self, inputs_list, targets_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
targets = numpy.array(targets_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算输出层发出的信号
final_outputs = self.activation_function(final_inputs)
# 输出层误差为(target - actual)
output_errors = targets - final_outputs
# 隐藏层误差为按权重比例分割输出层误差后,在隐藏层节点的重组值
hidden_errors = numpy.dot(self.who.T, output_errors)
# 更新隐藏层与输出层之间链接的权重
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输入层与隐藏层之间链接的权重
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# 对神经网络进行查询
def query(self, inputs_list):
# 将inputs_list转换为2维数组
inputs = numpy.array(inputs_list, ndmin = 2).T
# 计算进入隐藏层的信号
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算隐藏层发出的信号
hidden_outputs = self.activation_function(hidden_inputs)
# 计算进入输出层的信号
final_inputs = numpy.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.01
# 创建神经网络的一个实例
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
# 加载书写数字识别的训练数据CSV文件为一个列表
training_data_file = open(r"E:\Neural Network\mnist_dataset\mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 训练神经网络
# 世代是指训练数据集被用来测试的次数
epochs = 10
for e in range(epochs):
# 遍历训练数据集中的所有记录
for record in training_data_list:
# 在','逗号处分割记录
all_values = record.split(',')
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 创建目标输出值(除了期望值标签对应的是0.99,全部为0.01)
targets = numpy.zeros(output_nodes) + 0.01
# all_values[0]是此记录的目标标签
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
# 创造旋转图像
# 逆时针方向旋转10度
inputs_plus10_img = scipy.ndimage.interpolation.rotate(inputs.reshape(28, 28), 10, cval = 0.01, reshape = False)
n.train(inputs_plus10_img.reshape(784), targets)
# 顺时针方向旋转10度
inputs_minus10_img = scipy.ndimage.interpolation.rotate(inputs.reshape(28, 28), -10, cval = 0.01, reshape = False)
n.train(inputs_minus10_img.reshape(784), targets)
pass
pass
# 加载书写数字识别的测试数据CSV文件为一个列表
test_data_file = open(r"E:\Neural Network\mnist_dataset\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])
# print(correct_label, "correct label")
# 对输入值进行缩放和移位
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 查询网络
outputs = n.query(inputs)
# 最大值的索引对应的标签
label = numpy.argmax(outputs)
# print(label, "network's answer")
# 添加正确(1)或不正确(0)到列表中
if (label == correct_label):
# 网络的答案匹配正确的答案,计分板加1
scorecard.append(1)
else:
# 网络的答案不匹配正确的答案,计分板加0
scorecard.append(0)
pass
pass
# 计算性能分数,即正确答案的得分
scorecard_array = numpy.asarray(scorecard)
print("performance =", scorecard_array.sum() / scorecard_array.size)
performance = 0.9781
神经网络使图像识别以及广泛的其他各类难题,都获得了空前的进步。求解这类难题的早期动力的一个关键性部分是生物大脑。
今天,在人工智能中,神经网络是一些神奇的应用程序成功的关键部分。而事实上,神经网络背后的核心思想其实是非常简单的。
2019/10/7 16:42完成编辑
不得不说,《Python神经网络编程(Make Your Own Neural Network)》是一部杰出的作品,该书短小精炼,仅200多页,我用了大约6天的时间完成阅读。用时并不长的阅读却着实让我受益匪浅。如果要问我学到了什么,那便是七个字:什么是神经网络。这部作品成功揭开了“神经网络”的神秘面纱,非常通俗易懂地讲述了它的原理。原以为触不可及的“神经网络”原来如此的简单。作者是相当成功的。
与第一篇博客:编码:隐匿在计算机软硬件背后的语言(Code:The Hidden Language of Computer Hardware and Software)相比较,本篇博客所包含的信息量要少很多,前者审视了计算机技术的总体发展脉络,后者则是聚焦于神经网络这一主题。但正因为局限于神经网络这一主题,本篇博客又是详细的,不似前者重于轮廓勾勒而轻于细致描绘,它包含了很多细节性的内容,如公式、图表以及代码等等。
阅读与代码的编写在9月28日结束,然而本篇博客完成于10月7日,即十一假期的最后一天,总共历时约10天。因为假期的存在所以写作的速度比较缓慢,这一点是值得笔者反思的。
笔者另外计划完成一篇新博客,内容将不再以《Python神经网络编程(Make Your Own Neural Network)》一书的目录为序,而着重介绍神经网络(neural network)的构建方法流程(包括原理与实现两部分),敬请期待。
pass:标志循环的结束,下一行就回到正常的缩进,不再是循环的一部分(可省略)。 ↩︎
NumPy:Python的一种开源的数值计算扩展。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的多(该结构也可以用来表示矩阵(matrix))。 ↩︎
numpy.zeros():指定一个长度为2的正整数列表(分别指定了行与列),创建一个零数组。 ↩︎
matplotlib.pyplot:包含图形绘制功能的模块。 ↩︎
%matplotlib inline:要求在Notebook上绘制图形,不在独立的外部窗口中绘制图形。 ↩︎
matplotlib.pyplot.imshow():创建绘图的指令,第一个参数是我们要绘制的数组,后面有可选的其他参数。 ↩︎
numpy.random.rand(rows, columns):生成一个数组,数组中元素为0~1的随机值,数组大小为rows乘以columns。 ↩︎
numpy.random.normal():以正态分布的方式采样,参数分别为分布中心值、标准方差和numpy数组的大小。
pow(x, y):返回 xy(x的y次方)的值。 ↩︎
numpy.dot():将两个矩阵进行点乘运算。 ↩︎
scipy.special:该模块包含sigmoid函数(S函数)expit()和它的逆函数logit()。 ↩︎ ↩︎
lambda:python使用lambda来创建匿名函数。所谓匿名,意即不再使用def语句这样标准的形式定义一个函数。本书代码中,这个函数接受了x,返回scipy.special.expit(x)。 ↩︎
numpy.array(object, ndmin):构造指定样式的矩阵(数组)。object指定原数组,ndmin指定结果数组应具有的最小维数。详情请见这里。array()类型对象后加.T表示数组的转置。 ↩︎
numpy.asfarray():将文本字符串转换成实数,并创建这些数字的数组。
.reshape((x, y)):确保数字列表每x个元素折返一次,形成x*y的方形矩阵。 ↩︎
numpy.argmax():发现数组中的最大值,并返回它的位置(索引值)。 ↩︎
imageio.imread():从图像文件(PNG或JPG)中读取数据。参数“as_gray = True”会将图像变成灰度图。 ↩︎
ndimage.interpolation.rotate():将数组转过一个给定的角度。参数“reshape=False”防止将图像压扁,保持原有形状。
参数“cval=0.01”表示用来填充数组元素的值为0.01。 ↩︎