声明
这是作者在CSDN上的第三篇博客,是继《Python神经网络编程(Make Your Own Neural Network)》读书笔记之后整理出的关于神经网络构建的清晰方法步骤。本文仅专注于如何构建网络,而未提及其他的引导性部分。
跟诸位大牛相比,笔者阅历尚浅、经验不足,笔记中若有错误,还需继续修正与增删。欢迎大家的批评与指正。
原理篇
一、模仿神经元的三层神经网络模型
二、神经网络向前馈送信号
2.1 步骤
- 组合输入信号;
- 应用链接权重调节这些输入信号;
- 应用激活函数生成这些层的输出信号。
2.2 公式
-
输入矩阵I:
I = [ i 1 i 2 i 3 ] I=\begin{bmatrix} i_1\\ i_2\\ i_3 \end{bmatrix} I=⎣⎡i1i2i3⎦⎤ -
输入层到隐藏层的权重矩阵Winput_hidden(隐藏层到输出层类似,Whidden_output):
W i n p u t _ h i d d e n = [ w 1 , 1 w 2 , 1 w 3 , 1 w 1 , 2 w 2 , 2 w 3 , 2 w 1 , 3 w 2 , 3 w 3 , 3 ] W_{input\_hidden}=\begin{bmatrix} w_{1,1} & w_{2,1} & w_{3,1}\\ w_{1,2} & w_{2,2} & w_{3,2}\\ w_{1,3} & w_{2,3} & w_{3,3} \end{bmatrix} Winput_hidden=⎣⎡w1,1w1,2w1,3w2,1w2,2w2,3w3,1w3,2w3,3⎦⎤ -
输入层到隐藏层的组合调节输入矩阵Xhidden(隐藏层到输出层类似,Xoutput):
X h i d d e n = W i n p u t _ h i d d e n ⋅ I X_{hidden}=W_{input\_hidden}·I Xhidden=Winput_hidden⋅I -
隐藏层输出矩阵Ohidden(输出层类似,Ooutput):
O h i d d e n = s i g m o i d ( X h i d d e n ) O_{hidden}=sigmoid(X_{hidden}) Ohidden=sigmoid(Xhidden)
三、神经网络向后传播误差,更新权重
3.1 思想
不等分误差:按权重比例分割误差
梯度下降(gradient descent):梯度下降法是求解函数最小值一种很好的办法,当函数不能轻易使用数学代数求解时适用。在梯度下降法中,我们可以使用更小的步子朝着实际的最小值方向迈进,优化答案,直到我们对于所得到的精度感到满意为止。
3.2 公式
- 更新后的隐藏层误差errorhidden:
e r r o r h i d d e n = W h i d d e n _ o u t p u t T ⋅ e r r o r o u t p u t error_{hidden}=W_{hidden\_output}^T·error_{output} errorhidden=Whidden_outputT⋅erroroutput - 更新隐藏层到输出层的权重wj,k:
∂
E
∂
w
j
,
k
=
−
(
t
k
−
o
k
)
⋅
s
i
g
m
o
i
d
(
∑
j
w
j
,
k
⋅
o
j
)
(
1
−
s
i
g
m
o
i
d
(
∑
j
w
j
,
k
⋅
o
j
)
)
⋅
o
j
\frac{\partial E}{\partial w_{j,k}}=-(t_k-o_k)·sigmoid(\sum_{j}w_{j,k}·o_j)(1-sigmoid(\sum_{j}w_{j,k}·o_j))·o_j
∂wj,k∂E=−(tk−ok)⋅sigmoid(j∑wj,k⋅oj)(1−sigmoid(j∑wj,k⋅oj))⋅oj
分析:- 第一部分:(目标值tk-实际值ok)
- 第二部分:整体为后一层(输出层)节点k的输出ok,sigmoid中的求和表达式就是进入最后一层节点的信号xk
- 第三部分:前一隐藏层节点j的输出oj
- 更新输入层到隐藏层的权重wj,k(与上类似): ∂ E ∂ w j , k = − e j ⋅ s i g m o i d ( ∑ i w i , j ⋅ o i ) ( 1 − s i g m o i d ( ∑ i w i , j ⋅ o i ) ) ⋅ o i \frac{\partial E}{\partial w_{j,k}}=-e_j·sigmoid(\sum_{i}w_{i,j}·o_i)(1-sigmoid(\sum_{i}w_{i,j}·o_i))·o_i ∂wj,k∂E=−ej⋅sigmoid(i∑wi,j⋅oi)(1−sigmoid(i∑wi,j⋅oi))⋅oi
- 更新后的权重wj,k: n e w w j , k = o l d w j , k − α ⋅ ∂ E ∂ w j , k , α 是 学 习 率 new\ w_{j,k}=old\ w_{j,k}- \alpha ·\frac{\partial E}{\partial w_{j,k}},\ \alpha是学习率 new wj,k=old wj,k−α⋅∂wj,k∂E, α是学习率 Δ w j , k = α ⋅ E k ⋅ O k ( 1 − O k ) ⋅ O j T \Delta w_{j,k}=\alpha ·E_k·O_k(1-O_k)·O_j^T Δwj,k=α⋅Ek⋅Ok(1−Ok)⋅OjT
四、神经网络内部数据的要求
4.1 输入值
0.01~1.00
4.2 输出值
0.01~0.99
4.3 随机初始权重
− 1 ( 传 入 链 接 ) ∼ + 1 ( 传 入 链 接 ) \frac{-1}{\sqrt{(传入链接)}} \thicksim \frac{+1}{\sqrt{(传入链接)}} (传入链接)−1∼(传入链接)+1
实现篇
五、神经网络相关的Python要点
- 编写Python代码的平台——Notebook;
- 神经网络快速运转的基础——自动化工作;
- 代码的“解释师”——注释;
- 可重用的计算机指令——函数;
- 数学矩阵的超强载体——数组;
- 神经网络解决图像识别问题的基础——绘制数组;
- 神经网络的主体躯干——对象。
六、神经网络的三函数
- 初始化函数__init__()
- 训练函数train()
- 查询函数query()
七、神经网络的四参数
- 输入层节点数input_nodes
- 隐藏层节点数hidden_nodes
- 输出层节点数output_nodes
- 学习率learning_rate
八、神经网络需要导入的模块
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# 绘制数字矩形数组的库
import matplotlib.pyplot
# 确保绘制的图像在这个notebook内部,而不是外部窗口
%matplotlib inline
九、神经网络class
9.1 代码
当前代码可用于创建、训练和查询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
9.2 代码解析
- 第16行:
numpy.random.normal():以正态分布的方式采样,参数分别为分布中心值、标准方差和numpy数组的大小。
pow(x, y):返回 xy(x的y次方)的值。
原理:4.3 随机初始权重 - 第23行:
lambda:python使用lambda来创建匿名函数。所谓匿名,意即不再使用def语句这样标准的形式定义一个函数。本行代码中,这个函数接受了x,返回scipy.special.expit(x)。 - 第31行:
numpy.array(object, ndmin):构造指定样式的矩阵(数组)。object指定原数组,ndmin指定结果数组应具有的最小维数。详情请见这里。array()类型对象后加.T表示数组的转置。 - 第35行:
numpy.dot():将两个矩阵进行点乘运算。
原理:2.2第3点 输入层到隐藏层的组合调节输入矩阵Xhidden:
X h i d d e n = W i n p u t _ h i d d e n ⋅ I X_{hidden}=W_{input\_hidden}·I Xhidden=Winput_hidden⋅I - 第47行:
原理:3.2第1点 更新后的隐藏层误差errorhidden:
e r r o r h i d d e n = W h i d d e n _ o u t p u t T ⋅ e r r o r o u t p u t error_{hidden}=W_{hidden\_output}^T·error_{output} errorhidden=Whidden_outputT⋅erroroutput - 第50行:
numpy.transpose():矩阵转置。
原理:3.2 第4点更新后的权重wj,k: n e w w j , k = o l d w j , k + α ∗ E k ∗ O k ∗ ( 1 − O k ) ⋅ O j T new\ w_{j,k}=old\ w_{j,k}+\alpha *E_k*O_k*(1-O_k)·O_j^T new wj,k=old wj,k+α∗Ek∗Ok∗(1−Ok)⋅OjT
着重注意星乘*与点乘·的区别。
十、对神经网络的基础改进
10.1 学习率
甜蜜点:0.2
10.2 世代数
甜蜜点:5个世代,0.1学习率
10.3 隐藏层节点数
甜蜜点:200
十一、神经网络训练与测试的完整代码
11.1 创建神经网络实例
# 输入、隐藏、输出层的节点数
input_nodes = 784
hidden_nodes = 200
output_nodes = 10
# 学习率
learning_rate = 0.1
# 创建神经网络的一个实例
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
11.2 训练神经网络
# 加载书写数字识别的训练数据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
11.3 测试神经网络
# 加载书写数字识别的测试数据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)
11.4 运行结果
performance = 0.9755
11.5 代码解析
- 11.2第20行:
numpy.asfarray():将文本字符串转换成实数,并创建这些数字的数组。
.reshape((x, y)):确保数字列表每x个元素折返一次,形成x*y的方形矩阵。 - 11.2第23行:
numpy.zeros():指定一个长度为2的正整数列表(分别指定了行与列),创建一个零数组。 - 11.3第28行:
numpy.argmax():发现数组中的最大值,并返回它的位置(索引值)。
高级篇
十二、用神经网络测试自己的手写数字
12.1 导入模块
# 从PNG图像文件中加载数据的助手
import imageio
# glob帮助你使用模式匹配选择多个文件
import glob
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# 绘制数字矩形数组的库
import matplotlib.pyplot
# 确保绘制的图像在这个notebook内部,而不是外部窗口
%matplotlib inline
12.2 神经网络class(同实现篇)
12.3 创建神经网络实例(同实现篇)
12.4 训练神经网络(同实现篇)
12.5 用我们自己的图像测试神经网络
# 我们自己的图像测试数据集
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)
12.6 代码解析
- 第12行:
imageio.imread():从图像文件(PNG或JPG)中读取数据。参数“as_gray = True”会将图像变成灰度图。
12.7 运行结果
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
十三、向后查询:神经网络“眼中”的图像
13.1 导入模块(同实现篇)
13.2 神经网络class
# 神经网络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
注:新增第24行的逆函数(对数函数)以及向后查询函数backquery(),其余无变动。
13.3 创建神经网络实例(同实现篇)
13.4 训练神经网络(同实现篇)
13.5 给定标签,查看绘制出怎样的图像
# 向后运行网络,给出一个标签,查看它绘制出怎样的图像
# 测试标签
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')
13.6 代码解析
- 第16行:
matplotlib.pyplot.imshow():创建绘图的指令,第一个参数是我们要绘制的数组,后面有可选的其他参数。
interpolation参数:告诉Python不要为了让绘图看起来更加平滑而混合颜色(缺省设置)。
cmap="Greys"参数:选择灰度调色板,以更好地显示手写字符。
13.7 运行结果
[0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.99]
<matplotlib.image.AxesImage at 0x20eec9d45f8>
十四、创建新的训练数据:旋转图像
14.1 导入模块
import numpy
# 导入scipy.special模块以使用sigmoid函数(S函数)expit()
import scipy.special
# scipy.ndimage用于旋转图像数组
import scipy.ndimage
14.2 神经网络class(同实现篇)
14.3 创建神经网络实例(同实现篇)
14.4 使用旋转图像创造出的新训练数据训练神经网络
甜蜜点:角度10,10个世代
# 加载书写数字识别的训练数据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
14.5 代码解析
- 第31行:
ndimage.interpolation.rotate():将数组转过一个给定的角度。参数“reshape=False”防止将图像压扁,保持原有形状。
参数“cval=0.01”表示用来填充数组元素的值为0.01。
14.6 测试神经网络(同实现篇)
14.7 运行结果
performance = 0.9781
2019/10/7 21:06完成编辑