网络构成
我们的大脑接收眼睛观察传播来的数据后,会对其进行一层层的神经元去解析数据,然后得到我们对于所见的判断。然而我们对这个分析过程的了解以及脑部的研究较为浅,无法得知其脑部工作的原理。但是,我们是否可以对其进行部分抽象化为以下过程:
脑部在接收一系列数据的时候进行了一个函数式(function)的抽象,得到了其认知。
那么,我们的神经网络就是模拟这个过程,将输入信号,神经元的function处理,以及输出,都数字化。用统计与学习的方式,将这个过程一步步还原;
其全连接神经网络规则如下:
- 神经元按照层来布局。最左边的层叫做输入层,负责接收输入数据;最右边的层叫输出层,我们可以从这层获取神经网络输出数据。输入层和输出层之间的层叫做隐藏层,因为它们对于外部来说是不可见的。
- 同一层的神经元之间没有连接。
- 第N层的每个神经元和第N-1层的所有神经元相连(这就是full connected的含义),第N-1层神经元的输出就是第N层神经元的输入。
- 每个连接都有一个权值。
神经元
神经元是构成神经网络的基本单位:
一个神经元的组成为:
- 输 入:n维度向量x
-
线性加权:
- 激活函数: $H(x)$,要求非线性,容易求导数
- 输出 a
激活函数的选择
(1)sigmoid函数
(2)tanh函数
(3)relu函数
计算样例
下面我们来用最简单是sigmoid函数来尝试手算一个很简单的神经网络:
- 中间蓝色的神经元代表了隐层神经元,其左边的是W权值,b代表了其偏置项。
- X 为输入层
- 红色神经元代表输出,只有一个神经元代表这个神经网络只有一个输出
x1与z1的连线权值为$0.1$,x1的输出为$0.5$,所以x1到z1的输入为$0.1$*$0.5$,同理。x2到z1的输入为$-0.06$,所以根据计算公式,z1的输入为:$0.1 0.5 +0.2-(0.3)+0.01$,再将其进行激活函数处理,得到Z1的输出:$0.50224$
神经网络的训练
- 我们需要知道一个神经网络的每个连接上的权值.
- 我们可以说神经网络就是一个模型,这些权值就是模型的参数(即模型要学习的东西),
- 对于这个神经网络的连接方式,网络层数,每层的节点数,这些,我们是事先设置的,成为超参数.
目标函数与误差计算
在监督学习中,对于每一个样本,我们得到其特征$x$,标记$y$.我们通过模型$h(x)$计算得到输出值:,显然$y$是其真实值,
数学中常用的办法是将两个值的差的平方的$1/2$来代表其接近程度:
我们将$e$当做单个成本的误差,将所有的训练样本的误差值相加,得到其误差误差项$E$:
训练的目标,就是找到合适的$w$,使误差$E$最小;
训练方法:反向传播算法(Back Propagation)
简单的说,反向传播算法的过程就是:
- 给定的一个输入集$X$,取其中的一个向量$x$
- 先正向计算$x$的神经网络,得到其输出值$y$
- 训练向量$x$有人为标定的标签$l$(label),计算其预测值跟真实值之间的误差,
- 将误差项每一层地反向传播,得到每一层的误差,然后利用每一层的误差去更新每一层的权值去拟合模型
我们先给出各层的计算公式,然后再进行数学公式的推导(不擅长数学的可以跳过,等代码实现再回来看推导)
(1) 输出层的误差计算:
其中,
(2) 隐藏层的误差计算与传递
我们知道:
更新权重:
数学公式的推导:
我们取网络所有输出层节点的误差平方和作为目标函数:
,
用随机梯度下降算法对目标函数进行优化:
我们可以从上图得到,$W84$仅影响$a4$节点到$8$节点的输入值,设:$netj$为节点$j$的加权输入:
$Ed$是$netj$的函数,而$netj$是$Wji$的函数,根据链式求导法则:
$xji$是节点i到节点j的输出值,即$j$的输入值
输出层:
netj仅影响节点j的输出值,,所以Ed是yi的函数,yi是netj的函数:
计算第一项
将Ed带入公式,得到:
计算第二项:
综合,得:
将其推导代入 随机推导公式,得:
隐层:
我们定义节点j的所有直接下游节点的集合为:$Downstream(j)$,所以netj只能通过影响$Downstream(j)$来影响$Ed$,设Netk是节点J的下游输入,而$Ed$是netk的函数,而netk是netj的函数,所以我们用全导数公式:
因为:
自此,我们完成了公式的推导
代码:
import numpy as np
#定义tan函数以及tan函数的导数
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0-np.tanh(x)*np.tanh(x)
#定义logistich函数以及其导数
def logistic(x):
return 1/(1+np.exp(-x))
def logistic_derivatrive(x):
return logistic(x)*(1-logistic(x))
class NeturalNetwork(object):
def __init__(self,layers,activations='tanh'):
'''
:param layers: 一个list 包括每一层的神经元数
:param activations:激活函数
'''
if activations=='tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
if activations=='logistic':
self.activation = logistic
self.activation_deriv = logistic_derivatrive
self.weights = []
for i in range(1,len(layers)-1):
#i跟前一层的权重
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
#i层跟i+1层进行赋值 权重
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):
'''
:param X:
:param y:
:param learning_rate: 学习率
:param epochs: 学习步骤
:return:
'''
#二维矩阵
X = np.atleast_2d(X)
#ones 矩阵全是1 shape函数返回的是行列数(返回一个List)跟X一样维度的矩阵
temp = np.ones([X.shape[0],X.shape[1]+1])
#temp等于第一列到最后一列跟x一样的矩阵
temp [:,0:-1]=X
X= temp
Y=np.array(y)
#第几次循环
for k in range(epochs):
i = np.random.randint(X.shape[0])
#随机取一个数,代表第i行,对i行数据进行更新
a = [X[i]]
#形成这一行数据为输入的神经网络,dot代表内积
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])]
#开始往回算每一层的误差
#deltas是所有权重的误差列表
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)):
layers = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate*layers.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]))
return a
用手写训练集来测试我们的神经网络:
import numpy as np
from sklearn.datasets import load_digits
from sklearn.metrics import confusion_matrix,classification_report
from sklearn.preprocessing import LabelBinarizer
from neuralNetwork.nn import NeturalNetwork
from sklearn.cross_validation import train_test_split
digits =load_digits()
#1797张 8*8的手写数字图片
X = digits.data
Y = digits.target
#标准化
X -=X.min()
X /=X.max()
nn = NeturalNetwork([64,100,10],'logistic')
#分离测试集跟训练集
X_train,X_test,y_train,y_test = train_test_split(X,Y)
labels_train = LabelBinarizer().fit_transform(y_train)
labels_test = LabelBinarizer().fit_transform(y_test)
print("start fitting")
nn.fit(X_train,labels_train,epochs=3000)
print("end training")
predictions=[]
for i in range(X_test.shape[0]):
o = nn.predict(X_test[i])
predictions.append(np.argmax(o))
#10*10矩阵(分类是10) ,对角线表示预测的对,行是预测值,列是真实值
print(confusion_matrix(y_test,predictions))
'''
v[[43 0 0 0 0 0 0 0 0 0]
[ 0 37 0 0 0 0 1 0 0 8]
[ 0 1 38 3 0 0 0 0 0 0]
[ 0 0 1 47 0 1 0 1 0 0]
[ 0 0 0 0 47 0 0 0 0 1]
[ 0 0 0 0 0 48 1 0 0 0]
[ 0 2 0 0 0 0 38 0 0 0]
[ 0 0 0 0 1 0 0 37 0 1]
[ 1 7 0 1 0 4 1 0 26 6]
[ 0 0 0 4 0 0 0 0 0 43]]
'''
print(classification_report(y_test,predictions))
'''统计
precision recall f1-score support
0 0.98 1.00 0.99 43
1 0.79 0.80 0.80 46
2 0.97 0.90 0.94 42
3 0.85 0.94 0.90 50
4 0.98 0.98 0.98 48
5 0.91 0.98 0.94 49
6 0.93 0.95 0.94 40
7 0.97 0.95 0.96 39
8 1.00 0.57 0.72 46
9 0.73 0.91 0.81 47
avg / total 0.91 0.90 0.90 450
'''
用向量式编程的思想改进我们的代码:
# -*- coding:utf-8 -*-
#!/usr/bin/local/bin/python
import numpy as np
class FullConnectedLayer(object):
def __init__(self,input_size,
output_size,
learing_rate,
activator):
self.input_size = input_size
self.output_size = output_size
self.activator = activator
self.learning_rate = learing_rate
self.W = np.random.uniform(-0.1,0.1,(output_size,input_size))
self.b = np.zeros((output_size,1))
self.output = np.zeros((output_size,1))
def forward(self,input_array):
self.input = input_array
self.output = self.activator.forward(
np.dot(self.W,input_array)+self.b
)
def backward(self,delta_array):
self.delta = self.activator.backward(self.input) * np.dot(
self.W.T,delta_array
)
self.W_grad = np.dot(delta_array,self.input.T)
self.b_grad = delta_array
def update(self):
self.W += self.learning_rate * self.W_grad
self.b += self.learning_rate * self.b_grad