有关神经网络的基本概述和神经网络模型,这里就不仔细介绍了。具体详细介绍可参见斯坦福大学的大牛Ng等人的介绍神经网络概述及其模型。这里主要介绍反向传导算法(Backpropagation Algorithm)的具体推导,以及神经网络有关的简单示例和相关Python代码。
一 反向传导算法的推导过程
以人脑中的神经网络为启发,历史上出现过很多不同的版本,其中 最著名的版本的算法是1980年的backpropagation。backpropagation被使用在多层向前神经网络上。
假设我们有一个固定样本集 ,它包含 个样例。我们可以用批量梯度下降法来求解神经网络。具体来讲,对于单个样例 ,其代价函数为:
- 其中y是真实标签值,h是预测值。
- (注:这里相关变量取名是根据神经网络概述及其模型中的变量而来)
给定一个包含 个样例的数据集,我们可以定义整体代价函数为:
以上公式中的第一项 是一个均方差项。第二项是一个正则化项,其目的是减小权重的幅度,防止过拟合。
梯度下降法中每一次迭代都按照如下公式对参数 和 进行更新:
其中 是学习速率。其中关键步骤是计算偏导数。我们现在来讲一下反向传播算法,它是计算偏导数的一种有效方法。
首先来讲一下如何使用反向传播算法来计算 和 ,这两项是单个样例 的代价函数 的偏导数。一旦我们求出该偏导数,就可以推导出整体代价函数 的偏导数:
反向传播算法的思路如下:给定一个样例 ,我们首先进行“前向传导”运算,计算出网络中所有的激活值,包括 的输出值。之后,针对第 层的每一个节点 ,我们计算出其偏差 ,该残差表明了该节点对最终输出值的残差产生了多少影响。对于最终的输出节点,我们可以直接算出网络产生的激活值与实际值之间的差距,我们将这个差距定义为 (第 层表示输出层)。对于隐藏单元我们如何处理呢?我们将基于节点(译者注:第 层节点)残差的加权平均值计算,这些节点以 作为输入。看到这里,首先有疑问的是残差是什么,怎么样得到残差?残差其实是对z的偏导数。对于一个神经元,它和上一层的很多神经元相连接,这些神经元的输出经过一个加权,然后相加的结果就是Z,也就是说这z是神经元的真正输入,残差表示的就是最终的代价函数对网络中的一个个神经元输入的偏导。残差体现的是对于代价的贡献的敏感程度,对于一个大的残差,稍微给点输入,就不行了,导致最后的loss很大。z又是关于权重w的函数,所以,按照链式法则可以传递到w对代价函数的贡献敏感度上。
下面将给出反向传导算法的细节:
- 进行前馈传导计算,利用前向传导公式,得到 直到输出层 的激活值。
- 对于第 层(输出层)的每个输出单元 ,我们根据以下公式计算残差:
- 对 的各个层,第 层的第 个节点的残差计算方法如下:
- 计算我们需要的偏导数,计算方法如下:根据公式,其中Z = Wa+b,得到:
- 如果选择 ,也就是sigmoid函数,那么它的导数就是 ;如果选择tanh函数,那它的导数就是 。
那么,反向传播算法可表示为以下几个步骤:
- 进行前馈传导计算,利用前向传导公式,得到 直到输出层 的激活值。
- 对输出层(第 层),计算:
- 对于 的各层,计算:
- 计算最终需要的偏导数值:
-
5.最后不断更新:
-
- 更新的终止条件有:
- 1):权重的更新低于某个阈值;
- 2):预测的错误率低于某个阈值;
- 3):达到一定的循环次数,退出循环
二 简单示例
三 Python示例代码
<pre name="code" class="python">import numpy as np
#下面定义所使用的激活函数及其相应的导数的函数
#这里使用了正切函数和logistic函数
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0-np.tanh(x)*np.tanh(x)
def logistic(x):
return 1/(1+np.exp(-x))
def logistic_deriv(x):
return logistic(x)*(1-logistic(x))
class NeuralNetwork:#定义一个类
def __init__(self, layers, activation='tanh'):
if activation == 'logistic':
self.activation = logistic
self.activation_deriv = logistic_deriv
elif activation == 'tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
#对权重初始化。对每一层的权重都要初始化
self.weights = []
for i in range(1,len(layers)-1):
#对每一层的权重都要初始化初始值范围在-0.25~0.25之间,然后保存在weight中
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)
def fit(self, X, y, learning_rate=0.2, epochs=10000):#默认学习率即步长为0.2,循环最多的次数为1000
X = np.atleast_2d(X)#判断输入训练集是否为二维
temp = np.ones([X.shape[0],X.shape[1]+1])#列加1是因为最后一列要存入标签分类,这里标签都为1
temp[:,0:-1] = X
X = temp
y = np.array(y)#训练真实值
for k in range(epochs):#循环
i = np.random.randint(X.shape[0])#随机选取训练集中的一个
a = [X[i]]
#计算激活值
for l in range(len(self.weights)):
a.append(self.activation(np.dot(a[l], self.weights[l])))
error = y[i] - a[-1]#计算偏差
deltas = [error*self.activation_deriv(a[-1])]#输出层误差
#下面计算隐藏层
for l in range(len(a)-2,0,-1):
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
deltas.reverse()
#下面开始更新权重和偏向
for i in range(len(self.weights)):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate * layer.T.dot(delta)
#预测函数
def predict(self, x):
x = np.array(x)
temp = np.ones(x.shape[0]+1)
temp[0:-1] = x
a = temp
for l in range(0, len(self.weights)):
a = self.activation(np.dot(a, self.weights[l]))
for i in range(a):
if a[i]>0.5:
a[i] = 1
else:
a[i] = 0
return a
import numpy as np
nn = NeuralNetwork([2,2,1],'tanh')
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0,1,1,0])
nn.fit(X, y)
for i in [[0,0],[0,1],[1,0],[1,1]]:
print(i, nn.predict(i))
结果为:
([0, 0], array([ 0.0171341]))
([0, 1], array([ 0.99848996]))
([1, 0], array([ 0.99852684]))
([1, 1], array([ 0.04127888]))
可以设置阈值,比如当测试值>0.5是结果为1,否则为0。这样测试集结果与训练集相同,即全部正确