BP神经网络推导及python实现
1.基本模型推导
从简单到复杂,这里我们讨论一种单隐层前馈网络:
图片来源于周志华版机器学习
以下是该网络各最基本的参数:
d d : 输入层神经元
: 隐藏层神经元
l l : 输出层神经元
: 激励函数,这里采用sigmoid函数
vih v i h : 输入层第i个神经元连接隐层第h个神经元的权重
γh γ h : 隐层第h个神经元的阈值
αh α h : 隐层第h个神经元接受到的总输入
bh b h : 隐层第h个神经元的输出
ωhj ω h j : 隐层第h个神经元到输出层第j个神经元之间的连接权
βj β j : 输出层第j个神经元接收到的总输入
θj θ j : 输出层第j个神经元的阈值
yj^ y j ^ : 输出层第j个神经元的输出
Ek E k : 该网络在第k个例子上面的总均方误差
看上去不太直观?没关系,让我们关心一下数据的流动就好理解多了:
1.1 正向传播
- 输入数据一个向量 x=[x1,x2,⋯,xd] x = [ x 1 , x 2 , ⋯ , x d ] , 对应输入层各神经元接收到的输入
- 隐含层第 h h 个神经元接收输入层输入: , 得到 αh α h
- 隐含层第 h h 个神经元产生输出: , 得到 bh b h
- 这时隐含层各神经元的输出形成了新的向量 x1=[b1,b2,⋯,bq] x 1 = [ b 1 , b 2 , ⋯ , b q ]
- 输出层第 j j 个神经元接收到的输入: , 得到 βj β j
- 输出层第 j j 个神经元产生输出: , 得到 yj^ y j ^
- 得到结果即输出向量 y=[y1^,y2^,⋯,yl^] y = [ y 1 ^ , y 2 ^ , ⋯ , y l ^ ]
以上就是一个完整的输入输出流程了
1.2 代价函数
既然输出向量已经得到,那么需要做的就是计算代价函数了
特别提到一点, 在周志华机器学习一书中提到的代价函数用的是均方误差,不过我们却不采用这种而是采用交叉熵(croos entropy), 因为均方误差大部分情况会学的很慢,具体的可以参见下面的文章:
https://blog.csdn.net/bixiwen_liu/article/details/52922008
(不过我是觉得对于连续变量还是均方误差要好些)
具体式子如下:
这里可以看到和逻辑回归的代价函数可以说是非常之像了,只要把 l l 个神经元变成一个,那就是逻辑回归的
1.3 反向传播算法(BP)
BP实质上是一个采用梯度下降策略的迭代学习算法, 每一次迭代沿着负梯度的方向进行调整, 例如对
ωhj
ω
h
j
, 有:
而只考虑第 i i 个样例的话:
我们可以看到,最后的结果与 (yj−yj^) ( y j − y j ^ ) 有关,所以呢这就是使用 交叉熵更为优越的地方:
误差越大,下降速度越快!
所以我们只需要很少的迭代次数,它就会很好的work啦!
2.转为线代形式并模拟训练
要进行实现的话,首先要做的工作就是把以上的公式转化为线代形式啦
接下来, 我会用(m, n)的形式描述矩阵的形状是几行几列
2.1 已知输入X和输出Y
X: (m, n_in) (训练数据矩阵, 有m个样例, 每个样例有n_in个输入特征 , 也对应着输入神经元的个数)
y: (1, m) (训练集的输出集合)
为了神经网络能更好的训练我们将y转化为神经网络式(我自己想出来的词2333)的输出矩阵:
Y: (m, n_out) (输出集合, 有m个样例, 每个样例有n_out个数(非0即1), 对应着网络的输出结果, 由y映射而来)
X_ : (m, n_in+1) 给X的第一列补1
现在,已知的有X和Y
2.2 正向传播过程
定义
V
V
(n_hid, n_in+1) 为从输入层到隐藏层的参数矩阵
第一次传播:从输入层输入到隐藏层输出: : (m, n_hid) 其中
f
f
为激励函数
定义 为给
B
B
的第一列补1(加上偏置) (m, n_hid+1)
定义 (n_out, n_hid+1) 为隐藏层到输出层的参数矩阵
第二次传播:从隐藏层输出到最后的输出:
Ŷ =f(B_WT)
Y
^
=
f
(
B
_
W
T
)
(m, n_out)
2.3 代价函数与正则化
如果根据交叉熵加上正则来做的话,那么是下面的式子:
其中, Xcol X c o l 代表把这个矩阵化为只有一列的矩阵; [A;B] [ A ; B ] 代表对 A,B A , B 两个矩阵进行列拼接
2.4 反向传播(BackPropagation)
待填坑
3 python实现
以下是完整代码
import numpy as np
""" s型函数 """
def sigmoid(z):
return 1.0/(1.0+np.exp(-z))
""" s', 即s的导数 """
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
"""
params: 所有参数
n_in : 输入层神经元个数
n_hid : 隐藏层神经元个数
n_out : 输出层神经元个数
X: (m, n_out)
y: (m, 1)
lam: lambda
"""
def cost(params, n_in, n_hid, n_out, X, y, lam):
T1 = params[0: n_hid*(n_in+1)].reshape(n_hid, n_in+1) # 参数矩阵,相当于公式里的 V
T2 = params[n_hid*(n_in+1) : ].reshape(n_out, n_hid+1) # 参数矩阵,相当于公式里的 W
# 把 y 作 0-1映射为 Y
Y = np.zeros((X.shape[0], n_out))
for i in range(n_out):
Y[:, i] = np.int32(y == i).reshape(1, -1)
# 求正则项, 对参数矩阵去掉第一列(不需要对bias进行正则化), 再转化为1列的形式
T1_x = T1[:, 1:T1.shape[1]].reshape(-1,1)
T2_x = T2[:, 1:T2.shape[1]].reshape(-1,1)
regu = lam / 2 * np.dot( np.transpose(np.vstack((T1_x,T2_x))) , np.vstack((T1_x, T2_x)) )
# 正向传播 (每次先对输入阵补1, 然后求矩阵相乘, 最后求个sigmoid)
B = sigmoid(np.dot(np.hstack((np.ones((X.shape[0], 1)), X)), np.transpose(T1)))
h = sigmoid(np.dot(np.hstack((np.ones((X.shape[0], 1)), B)), np.transpose(T2)))
# 对实际的Y矩阵和预测出的h进行列化
Y.reshape(-1,1)
h.reshape(-1,1)
J = (-np.dot(np.transpose(Y), np.log(h)) - np.dot(np.transpose(1-Y), np.log(1-h)) + regu ) / X.shape[0]
return np.ravel(J)
"""
返回所有参数的梯度(1行,先是T1的梯度,再是T2的梯度)
"""
def gradient(params, n_in, n_hid, n_out, X, y, lam):
T1 = params[0: n_hid*(n_in+1)].reshape(n_hid, n_in+1).copy() # 参数矩阵,相当于公式里的 V
T2 = params[n_hid*(n_in+1) : ].reshape(n_out, n_hid+1).copy() # 参数矩阵,相当于公式里的 W
Y = np.zeros((X.shape[0], n_out))
for i in range(n_out):
Y[:, i] = np.int32(y == i).reshape(1, -1)
# 去掉T1和T2的第一列
T2_x = T2[:, 1:T2.shape[1]]
T1_grad = np.zeros(T1.shape) # 记为输入层到隐藏层的权重
T2_grad = np.zeros(T2.shape) # 记为隐藏层到输出层的权重
a1 = np.hstack((np.ones((X.shape[0], 1)), X)) # 第一层输入为X加上全为1的第一列
z2 = np.dot(a1, np.transpose(T1)) # 第二层输入
a2 = sigmoid(z2) # 第二层输出
a2 = np.hstack((np.ones((X.shape[0], 1)), a2)) # 第二层输出
z3 = np.dot(a2, np.transpose(T2)) # 第三层输入
h = sigmoid(z3) # 最终输出
error3 = np.zeros((X.shape[0], n_out))
error2 = np.zeros((X.shape[0], n_hid))
for i in range(X.shape[0]):
error3[i, :] = h[i, :] - Y[i, :]
T2_grad = T2_grad + np.dot(np.transpose(error3[i, :].reshape(1,-1)), a2[i,:].reshape(1,-1))
error2[i, :] = np.dot(error3[i, :].reshape(1,-1), T2_x) * sigmoid_prime(z2[i, :])
T1_grad = T1_grad + np.dot(np.transpose(error2[i, :].reshape(1,-1)), a1[i,:].reshape(1,-1))
T1[:, 0] = 0
T2[:, 0] = 0
"""把计算结果列化并加上正则化项"""
return np.ravel((np.vstack((T1_grad.reshape(-1,1),T2_grad.reshape(-1,1)))
+ lam*np.vstack((T1.reshape(-1,1),T2.reshape(-1,1)))) / X.shape[0])
# 初始化权重为sin(x) / 10的(n_out, n_in)大小的矩阵
def debug_init_weight(n_in, n_out):
W = np.zeros((n_out, n_in+1))
x = np.arange(1, n_out*(n_in+1)+1)
return np.sin(x).reshape(W.shape) / 10
# 随机初始化权重theta
def rand_init_weight(n_in, n_out):
W = np.zeros((n_out, n_in+1)) # 对应theta的权重
little = (6.0/ (n_out + n_in))**0.5
# 先产生(n_out, n_in+1)大小的随机矩阵,再乘以2乘以little之后减去little
return np.random.rand(n_out, n_in+1)*2*little - little
"""
预测, 模拟传播
"""
def predict(T1, T2, X):
m = X.shape[0]
n_out = T2.shape[0]
X = np.hstack((np.ones((m, 1)), X))
h1 = sigmoid(np.dot(X, np.transpose(T1)))
h1 = np.hstack((np.ones((m, 1)), h1))
h2 = sigmoid(np.dot(h1, np.transpose(T2)))
p = np.array(np.where(h2[0,:] == np.max(h2, axis=1)[0]))
for i in np.arange(1, m):
t = np.array(np.where(h2[i,:] == np.max(h2, axis=1)[i]))
p = np.vstack((p,t))
return p
from scipy import io as spio
from matplotlib import pyplot as plt
from scipy import optimize
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import time
# 加载mat文件
def loadmat_data(file_name):
return spio.loadmat(file_name)
# 显示100个数字
def display_data(imgData):
sum = 0
'''
显示100个数(若是一个一个绘制将会非常慢,可以将要画的数字整理好,放到一个矩阵中,显示这个矩阵即可)
- 初始化一个二维数组
- 将每行的数据调整成图像的矩阵,放进二维数组
- 显示即可
'''
m,n = imgData.shape
width = np.int32(np.round(np.sqrt(n)))
height = np.int32(n/width);
rows_count = np.int32(np.floor(np.sqrt(m)))
cols_count = np.int32(np.ceil(m/rows_count))
pad = 1
display_array = -np.ones((pad+rows_count*(height+pad),pad+cols_count*(width+pad)))
for i in range(rows_count):
for j in range(cols_count):
if sum >= m: #超过了行数,退出当前循环
break;
display_array[pad+i*(height+pad):pad+i*(height+pad)+height,pad+j*(width+pad):pad+j*(width+pad)+width] = imgData[sum,:].reshape(height,width,order="F") # order=F指定以列优先,在matlab中是这样的,python中需要指定,默认以行
sum += 1
if sum >= m: #超过了行数,退出当前循环
break;
plt.imshow(display_array,cmap='gray') #显示灰度图像
plt.axis('off')
plt.show()
def neuralNetwork(input_layer_size,hidden_layer_size,out_put_layer):
data_img = loadmat_data("data_digits.mat")
X = data_img['X']
y = data_img['y']
m,n = X.shape
## 随机显示几行数据
rand_indices = [t for t in [np.random.randint(x-x, m) for x in range(100)]] # 生成100个0-m的随机数
display_data(X[rand_indices,:]) # 显示100个数字
Lambda = 1
initial_Theta1 = rand_init_weight(input_layer_size,hidden_layer_size);
initial_Theta2 = rand_init_weight(hidden_layer_size,out_put_layer)
initial_nn_params = np.vstack((initial_Theta1.reshape(-1,1),initial_Theta2.reshape(-1,1))) #展开theta
start = time.time()
result = optimize.fmin_cg(nnCostFunction, initial_nn_params, fprime=gradient, args=(input_layer_size,hidden_layer_size,out_put_layer,X,y,Lambda), maxiter=100)
print (u'执行时间:',time.time()-start)
print (result)
'''可视化 Theta1'''
length = result.shape[0]
Theta1 = result[0:hidden_layer_size*(input_layer_size+1)].reshape(hidden_layer_size,input_layer_size+1)
Theta2 = result[hidden_layer_size*(input_layer_size+1):length].reshape(out_put_layer,hidden_layer_size+1)
display_data(Theta1[:,1:length])
display_data(Theta2[:,1:length])
'''预测'''
p = predict(Theta1,Theta2,X)
print (u"预测准确度为:%f%%"%np.mean(np.float64(p == y.reshape(-1,1))*100))
res = np.hstack((p,y.reshape(-1,1)))
训练网络
先这样, 线代推导部分以后填坑