神经网络代码实现
- 读取数据:
因为要求y为下图的形式,所以要对y进行处理
import numpy as np
import pandas as pd
from scipy.io import loadmat
from sklearn.preprocessing import OneHotEncoder
path ='E:/Data/Ng/Coursera-ML-AndrewNg-Notes-master/code/ex4-NN back propagation/ex4data1.mat'
data = loadmat(path)
X = data['X']
y = data['y']
encoder = OneHotEncoder(sparse=False)
y_encoder = encoder.fit_transform(y) #可以直接利用OneHotEncoder进行转换,比如说y当中一共有10个不同的元素,5000列,那么转化以后每一个数字都单独为1列,比如1就是[1,0,0,...,0],最后形状为(5000,10)
- 定义一个sigmoid函数和前向传播函数
根据下列公式可进行编写
def sigmoid(z):
return 1/(1+np.exp(-z))
def forward(X,y_encoder,theta1,theta2):
"""计算前向传播的值
Params:
X:特征值(5000*400)
y_encoder:输出变量(5000*10)
theta1;theta2:初始化的值
return"""
m = X.shape[0] #获取样本总数,用于插入列的时候使用
a1 = np.insert(X,0,values=np.ones(m),axis=1) #插入一个x0=1的列,
z2 = a1 * theta1.T
#因为 上图中可以看到当输入的样本数为1的时候z2= θ1*a1,即θ的每一行都要和样本值相乘。但是这里我们输入的样本值为5000,θ1为(25,401)的矩阵,也就是说我们的5000个样本都要单独和25行θ值对应相乘,所以这里的θ要进行转置。
a2 = np.insert(sigmoid(z2),0,values=np.ones(m),axis=1)
z3 = a2 *theta2.T
h = sigmoid(z3)
return a1,z2,a2,z3,h
- 根据公式编写代价函数:
def cost(params,in_size,h_size,label_nums,X,y_encoder,l):
"""代价函数的前面部分
Params:
params:存放所有初始化theta1,theta2值的ndarray
in_size:特征值的数量
h_szie:隐藏层的项数
label_nums:总分类数
X:特征值数据
y_encoder:转换后的y
l:lambda
return
J:代价函数"""
m = X.shape[0] #获取样本总数
X = np.mat(X)
y_encoder = np.mat(y_encoder)
theta1 = np.mat(np.reshape(params[:h_size*(in_size+1)],(h_size,in_size+1))) #(25,401)
theta2 = np.mat(np.reshape(params[h_size*(in_size+1):],(label_nums,h_size+1))) #(10,26)
a1,z2,a2,z3,h = forward(X,y_encoder,theta1,theta2) #获取h
J= 0 #初始化代价函数
for i in range(m): #一个样本一个样本的计算代价函数
first_term = np.multiply(-y[i,:],np.log(h[i,:]))
second_term = np.multiply((1-y)[i,:],np.log(1-h[i,:]))
#注意神经网络的代价函数要求两次和,一次是m一次是k。这个循环是用来求m的,那么在循环里面要将k求好
J += np.sum(first_term-second_term) #累加求m,sum是在求k
J=J/m
J = J + (l/(2*m)*(np.sum(np.power(theta1[:,1:],2))+np.sum(np.power(theta2[:,1:],2)))
#加上正则优化项
return J
-
后向传播的偏导数项
后向传播比较难以理解,代码中使用的是一个一个样本计算的方法。
求代价函数的偏导数。先假设对一个样本一个权重求偏导:
假设神经网络有四层,中间两层为隐藏层
一般化可以这样表示:
矢量化,因为a此时不再只是一个数,而是所有样本的数,所以要将其转置。
再求前一层的偏导
代码实现:
def sigmoid_gradient(z): #g的偏导数项
return np.multiply(sigmoid(z),(1-sigmoid(z)))
def bp(params,in_size,h_size,label_nums,X,y_encoder,l):
"""计算所有的代价函数对于θ的偏导数项
Params:
params:存放所有初始化theta1,theta2值的ndarray
in_size:特征值的数量
h_szie:隐藏层的项数
label_nums:总分类数
X:特征值数据
y_encoder:转换后的y
l:lambda
return
J:代价函数
grad:梯度值""""""
m = X.shape[0]
X = np.mat(X)
y_encoder = np.mat(y_encoder)
theta1 = np.mat(np.reshape(params[:(in_size+1)*h_size],(h_size,in_size+1))) #(25,401)
theta2 = np.mat(np.reshape(params[(in_size+1)*h_size:],(label_nums,h_size+1)))#(10,26)
a1,z2,a2,z3,h=forward(X,y_encoder,theta1,theta2)
J=0
for i in range(m):
first = np.multiply(-y_encoder[i,:],np.log(h[i,:]))
second = np.multiply((1-y_encoder)[i,:],np.log(1-h[i,:]))
J += np.sum(first-second)
J = J/m
reg_term = (np.sum(np.power(theta1[:,1:],2)) + np.sum(np.power(theta2[:,1:],2)))*(float(l)/(2*m))
J += reg_term
delta2 = np.zeros(theta2.shape) #(25,401) #初始化梯度2
delta1 = np.zeros(theta1.shape) #(10,26) #初始化梯度1
for t in range(m): #一个样本一个样本的进行计算
a1t = a1[t,:] #(1,401) #拿出第一个样本的值
z2t = z2[t,:] #(1,25)
a2t = a2[t,:] #(1,26)
z3t = z3[t,:] #(1,10)
ht = h[t,:] #(1,10)
yt = y_encoder[t,:] #(1,10)
d3 = ht - yt #(1,10) #计算δ(3)
z2t = np.insert(z2t,0,values=np.ones(1))
d2 = np.multiply((theta2.T * d3.T).T,sigmoid_gradient(z2t) )
#注意这里是用的点乘,所以用multiply
delta1 = delta1 + d2[:,1:].T*a1t #偏导向1,因为第一列不用计算
delta2 = delta2 + d3.T*a2t #偏导项2
delta1 = delta1/m
delta2 = delta2/m
delta1[:,1:] = delta1[:,1:] + (theta1[:,1:]*l)/m #正则优化
delta2[:,1:] = delta2[:,1:] + (theta2[:,1:]*l)/m
grad = np.concatenate((np.ravel(delta1),np.ravel(delta2)))
return J,grad
if __name__ == '__main__':
in_size = 400
h_size=25
label_nums=10
l=1
params = (np.random.random(size = h_size*(in_size+1)+label_nums*(h_size+1))-0.5)*0.25
J,grad = bp(params,in_size,h_size,label_nums,X,y_encoder,l)
print(grad.shape)
结果如下:
- 利用库去拟合最优参数
from scipy.optimize import minimize
fmin = minimize(fun=bp,x0=params,args=(in_size,h_size,label_nums,X,y_encoder,l),method='TNC',jac=True,options={'maxiter':250})
fmin
- 通过h值来检测准确度
X = np.mat(X)
theta1 = np.mat(np.reshape(fmin.x[:(in_size+1)*h_size],(h_size,in_size+1))) #(25,401)
theta2 = np.mat(np.reshape(fmin.x[(in_size+1)*h_size:],(label_nums,h_size+1)))#(10,26)
a1,z2,a2,z3,h=forward(X,y_encoder,theta1,theta2)
y_pred = np.array(np.argmax(h,axis=1)+1) #利用h来获得结果,输出可能性最大的值的索引,
#因为我们的数字是从1开始到10
y_pred
- 测试精准度
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, y)]
accuracy = (sum(map(int, correct)) / float(len(correct)))
print ('accuracy = {0}%'.format(accuracy * 100))
每一个代价函数对于权重的偏导数由两个部分组成。
第一部分:z对于权重的偏导数
第二部分:代价函数对于z的偏导数
第一部分很好计算:
如果求z(i+1)对于权重 θ(i)的偏导,那就是a(i)
第二部分就比较难:
首先计算代价函数对于z的偏导可以利用链式求导法则,进行如下图的分解。
因此,又分为了两个部分。
第一部分:a对于z求偏导
第二部分:代价函数对于a求偏导
第一部分:a(i)对于z(i)求偏导,由sigmoid函数的特点可知=a(i) *(1-a(i))
第二部分:若知道了最后一层的h值,则可以算出δ=h-y
然后从后往前推
比如现在又一个三层的神经网络,我们通过前向传播求出了a(3)
因此δ(3)的值就等于a(3)-y
又通过下图可以知道,想要求出前面的一个代价函数关于z的偏导,就可以通过用δ(3) * θ(2)
然后将δ(3) * θ(2)和a(i) *(1-a(i))相乘就可以得出第二部分。然后再与第一部分相乘就可以得出第一个梯度。
然后令δ(3) * θ(2)*a(i) *(1-a(i))为δ(2) 按照同样的道理去计算出第二个梯度。
目前就只能理解到这里。。。