一、 首先构建代码框架
首先来构建一个神经网络的类。大致有三个函数。
- 初始化函数–用来设定输入层、隐含层、输出层的数量
- 训练函数–用来学习给定样本数据,优化权值
- 查询函数–给定输入,从输出的节点获得答案
这个是一个大致的框架,后续可以逐渐添加更多的函数。
1、代码框架
class neuralnetwork():
def __init__():
pass
def train():
pass
def query():
pass
2、初始化网络
设置网络的输入层节点、隐含层节点、输出层节点的数量。这些节点数量定义了一个神经网络的框架。通过构建一个通用性的一般代码。这样新建神经网络可以直接调用这个类。
下面是初始化函数:`
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
self.innodes = inputnodes
self.hinodes = hiddennodes
self.outnodes = outputnodes
self.lr = learningrate
下面让我们使用定义的神经网络类,来创建一个三节点,学习率为0.5的神经网络对象。
input_nodes = 3
hidden_nodes = 3
output_nodes = 3
learningrate = 0.5
二、权重
接下来是创建网络的节点与链接。神经网络中,最重要的部分就是链接权重,用这些权重来计算前馈信号、反向传播误差,并且进行权重的更新。
神经网络每两层之间的链接权重可以用一个矩阵来表示:
[
w
1
,
1
w
2
,
1
w
3
,
1
w
1
,
2
w
2
,
2
⋯
w
1
,
3
w
2
,
3
⋯
⋯
]
\left[ \begin{array}{c} \begin{matrix} w_{1,1}& w_{2,1}& w_{3,1}\\ w_{1,2}& w_{2,2}& \cdots\\ w_{1,3}& w_{2,3}& \cdots\\ \end{matrix}\\ \cdots\\ \end{array} \right]
⎣⎢⎢⎡w1,1w1,2w1,3w2,1w2,2w2,3w3,1⋯⋯⋯⎦⎥⎥⎤
首先利用python的数据处理工具numpy进行权值初始化:
numpy.random.rand(rows,columns)
上面的代码可以生成一个大小为(rows,clumns)的随机矩阵,每个权值大小为0-1之间。考虑到权值可以是负数,因此可以在上面的基础上减去一个0.5.
具体实现过程如下:
import numpy
self.winhi = (numpy.random.rand(self.hinodes, self.innodes)-0.5) # 输入层到隐含层之间的链接权重
self.whiout = (numpy.random.rand(self.outnodes, self.hinodes)-0.5) # 隐含层到输出层之间的链接权重
使用复杂一点的权重
利用正态概论分布来创建一个初始化矩阵,平均值为0,标准方差为出入链接数目的开平方。利用numpy很容易实现这个功能。
# 三个参数:正态分布中心,标准方差,numpy数组大小
self.winhi = numpy.random.normal(0.0, pow(self.hinodes, -0.5), (self.hinodes, self.innodes))
# 其中0.0是正态分布的中心点,pow(self.hinodes, -0.5)就是对这个隐含层节点数进行开平方。
# 最后一个参数就是我们希望得到的numpy数组的大小。
self.whiout = numpy.random.normal(0.0, pow(self.outnodes, -0.5),(self.outnodes, self.hinodes))
三、查询网络函数
query()函数接收到神经网络的输入,然后返回输出。也就是对每两层之间的数据进行处理。
这个功能实现并不复杂,利用下面的矩阵运算就可以得到输出:
W
h
i
d
d
e
n
=
W
i
n
p
u
t
_
h
i
d
d
e
n
⋅
I
W_{hidden}=W_{input\_hidden}\cdot I
Whidden=Winput_hidden⋅I
应用python的代码来实现上述语句:
hidden_inputs = numpy.dot(self.winhi, inputs)
# 使用矩阵点乘运算
经过上述步骤进行运算以后,就可以输入到隐含层进行激活函数的计算:
O
h
i
d
d
e
n
=
s
i
g
m
o
i
d
(
X
h
i
d
d
e
n
)
O_{hidden}=sigmoid\left( X_{hidden} \right)
Ohidden=sigmoid(Xhidden)
python中有一个自带的s函数,在Scipy Python库中有一组特殊的函数,S函数称为expit()函数。
利用下面语句可以实现函数的调用。
import scipy.special
接着我们就可以在查询query()函数中添加一个激活函数。
self.activation_function = lambda x:scipy.special.expit(x)
# lambda用来简单定义一个函数:自变量x,然后返回scipy.special.expit(x)
输出信号直接调用:self.activation_function()即可
总结
# 信号进入隐含层
hidden_inputs = numpy.dot(self.winhi, inputs)
# 经过隐含层的激活函数
hidden_outputs = self.activation_funcition(hidden_inputs)
# 信号从隐含层传递到输出层
final_outputs = numpy.dot(self.whiout, hidden_outputs)
# 信号经过输出层的激活函数
final_outputs = self.activation_function(final_inputs)
经过以上运算以后,就可以得到神经网络的前向传递的输出信号。
计算隐含层和输出层的信号
四、训练函数
1、简单测试一下
到现在为止,我们已经搭建起了神经网络的基本框架,接下来就是训练神经网络的过程。训练分为两个阶段:第一阶段,计算输出,就是直接调用query()函数,第二阶段是,反向传播,更新权重。
我们可以先随便创建一个神经网络对象,测试一下上面所得到的代码。使用一些随机输入查询一下网络是如何工作的。
下面代码将新建一个三层网络,每层三个节点,使用随机的输入来查询网络。
input_nodes = 3
hidden_nodes = 3
output_nodes = 3
# 在此处学习率没有意义,只是为了测试,不设置将会报错
learnintrate = 0.5
# 创建一个对象
n = neuralNetwork(input_nodes, hidden_nodes, ouput_nodes, learning_rate)
n.query([1,0.5,-1.5])
# 输出结果:array([[0.45],[0.44],[0.49]])
2、训练部分
- 第一阶段:query()计算输出
- 第二阶段:将计算的输出,与所需要的输出进行对比,使用误差差值来指导网络进行权重的更新。
与train()函数在query()函数基础上加入了一个target_list。
def train(self,inputs_list,targets_list):
targets = numpy.array(targets_list,ndmin=2).T
# ndmin指的是维度,2表示数组是二维
# 输入为[1,2,3]转化为numpy类型的[[1],[2],[3]]
计算误差
利用下面的公式我们可以计算出隐含层节点反向传播的误差。
e r r o r s h i d d e n = w e i g h t s T h i d d e n _ o u t p u t ⋅ e r r o r s o u t p u t errors_{hidden}={weights^T}_{hidden\_output}\cdot errors_{output} errorshidden=weightsThidden_output⋅errorsoutput
# 输出误差
output_error = target - final_outputs
# 由输出误差可以通过与链接权重点乘得到隐含层的误差
hidden_errors = numpy.dot(self.whiout.T, output_errors)
前面通过推算,我们得到了更新节点j和其下一层节点k之间的链接权重的矩阵形式的表达式。
Δ
w
j
,
k
=
α
∗
E
k
∗
s
i
g
m
o
i
d
(
O
k
)
∗
(
1
−
s
i
g
m
o
i
d
(
O
k
)
)
⋅
O
j
T
\varDelta w_{j,k}=\alpha *E_k*sigmoid\left( O_k \right) *\left( 1-sigmoid\left( O_k \right) \right) \cdot O_{j}^{T}
Δwj,k=α∗Ek∗sigmoid(Ok)∗(1−sigmoid(Ok))⋅OjT
α
\alpha
α表示学习率。
E
k
E_k
Ek是表示输出的误差矩阵。
具体实现的代码如下:
# 对隐含层和输出层之间的权重进行更新
self.whiout +=self.lr*numpy.dot((output_errors*final_outputs*(1-final_outputs)), numpy.transpose(hidden_outputs))
# final_outputs已经经过了sigmoid运算,所以可以直接调用。
# 对输入层和隐含层之间的权重进行更新
self.winhi +=self.lr*numpy.dot((hidden_errors*hidden_outputs*(1-hidden_outputs)), numpy.transpose(inputs))
五、综合的代码
# 调用
import numpy
import scipy.special
class neuralNetwork():
# 初始化函数
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
self.innodes = inputnodes
self.hinodes = hiddennodes
self.outnodes = outputnodes
self.lr = learningrate
# 链接权重
# 三个参数:正态分布中心,标准方差,numpy数组大小
self.winhi = numpy.random.normal(0.0, pow(self.hinodes, -0.5), (self.hinodes, self.innodes))
# 其中0.0是正态分布的中心点,pow(self.hinodes, -0.5)就是对这个隐含层节点数进行开平方。
# 最后一个参数就是我们希望得到的numpy数组的大小。
self.whiout = numpy.random.normal(0.0, pow(self.outnodes, -0.5),(self.outnodes, self.hinodes))
# 激活函数
self.activation_function = lambda x: scipy.special.expit(x)
pass
def train(self, inputs_list, targets_list):
# 把输入列表转化为numpy型的2维
inputs = numpy.array(inputs_list,ndmin=2).T
targets = numpy.array(targets_list,ndmin=2).T
# 计算隐含层输出
hidden_inputs = numpy.dot(self.winhi, inputs)
hidden_outputs = self.activation_function(hidden_inputs)
# 计算输出层输出
final_inputs = numpy.dot(self.whiout, hidden_outputs)
final_outputs = self.activation_function(final_inputs)
# 计算输出层的误差
output_errors = targets - final_outputs
hidden_errors = numpy.dot(self.whiout.T, output_errors)
# 更新链接权重
self.whiout +=self.lr*numpy.dot((output_errors*final_outputs*(1- final_outputs)), numpy.transpose(hidden_outputs))
# final_outputs已经经过了sigmoid运算,所以可以直接调用。
# 对输入层和隐含层之间的权重进行更新
self.winhi +=self.lr*numpy.dot((hidden_errors*hidden_outputs*(1-hidden_outputs)), numpy.transpose(inputs))
# 输入的是一个数列例如[1,2,3,4]
def query(self, inputs_list):
hidden_inputs = numpy.dot(self.winhi, inputs)
# 经过隐含层的激活函数
hidden_outputs = self.activation_funcition(hidden_inputs)
# 信号从隐含层传递到输出层
final_outputs = numpy.dot(self.whiout, hidden_outputs)
# 信号经过输出层的激活函数
final_outputs = self.activation_function(final_inputs)
return final_outputs
上面这些代码已经足够用来创建,训练和查询三层神经网络。如果要新建更为复杂的网络就需要在上面代码的基础上进行改造。
下一节,我们来利用上面的代码进行一个小项目,对手写的数字进行识别。