入门机器学习(十)--课后作业解析-神经网络(Python实现)
对于这个练习,我们将处理手写数字数据集,这次使用反向传播的前馈神经网络。 我们将通过反向传播算法实现神经网络成本函数和梯度计算的非正则化和正则化版本。 我们还将实现随机权重初始化和使用网络进行预测的方法。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
data = loadmat('ex4data1.mat')
X = data['X']
y = data['y']
X, y#看下维度
我们也需要对我们的y标签进行一次one-hot 编码。 one-hot 编码将类标签n(k类)转换为长度为k的向量,其中索引n为“hot”(1),而其余为0。 Scikitlearn有一个内置的实用程序,我们可以使用这个。
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse=False)
y_onehot = encoder.fit_transform(y)
y_onehot.shape
y[0], y_onehot[0,:]
我们要为此练习构建的神经网络具有与我们的实例数据(400 +偏置单元)大小匹配的输入层,25个单位的隐藏层(带有偏置单元的26个),以及一个输出层, 10个单位对应我们的一个one-hot编码类标签。 有关网络架构的更多详细信息和图像,请参阅“练习”文件夹中的PDF。
我们需要实现的第一件是评估一组给定的网络参数的损失的代价函数。 源函数在练习文本中(看起来很吓人)。 以下是代价函数的代码。
sigmoid 函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
前向传播函数
(400 + 1) -> (25 + 1) -> (10)
def forward_propagate(X, theta1, theta2):
# INPUT:参数值theta,数据X
# OUTPUT:当前参数值下前项传播结果
# TODO:根据参数和输入的数据计算前项传播结果
# STEP1:获取样本个数
# your code here (appro ~ 1 lines)
m = len(X)
# STEP2:实现神经网络正向传播
# your code here (appro ~ 5 lines)
a1 = np.insert(X, 0, values=np.zeros(m), axis=1) #给X矩阵插入一lie零元素
z2 = a1 * theta1.T #或 z2 = np.dot(theta1,X.T)
a2 = np.insert(sigmoid(z2), 0, values=np.zeros(m), axis=1) #注意插入零元素
z3 = a2 * theta2.T
h = sigmoid(z3)
return a1, z2, a2, z3, h
代价函数
def cost(params, input_size, hidden_size, num_labels, X, y, lamda):
# INPUT:神经网络参数,输入层维度,隐藏层维度,训练数据及标签,正则化参数
# OUTPUT:当前参数值下的代价函数
# TODO:根据上面的公式计算代价函数
# STEP1:获取样本个数
# your code here (appro ~ 1 lines)
m = len(X)
# STEP2:将矩阵X,y转换为numpy型矩阵
# your code here (appro ~ 2 lines)
X = np.matrix(X)
y = np.matrix(y)
# STEP3:从params中获取神经网络参数,并按照输入层维度和隐藏层维度重新定义参数的维度
# your code here (appro ~ 2 lines)
#theta1 =np.matrix(np.reshape(params(401,25)))
#theta2 =np.matrix(np.reshape(params[10025:](26,10)))
theta1 = np.matrix(np.reshape(params[:25 * 401], (25, 401)))
theta2 = np.matrix(np.reshape(params[25 * 401:], (10, 26)))
# STEP4:调用前面写好的前项传播函数
# your code here (appro ~ 1 lines)
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
# STEP5:初始化代价函数
# your code here (appro ~ 1 lines)
J = 0
# STEP6:根据公式计算代价函数
for i in range(m): #遍历每个样本
# your code here (appro ~ 2 lines)
first_term = np.multiply(- y[i,:],np.log(h[i,:]))
second_term = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
J += np.sum(first_term - second_term)
J = J / m
# STEP7:计算代价函数的正则化部分
# your code here (appro ~ 1 lines)
#J += sum([i*i for i in params])*lamda/(2*m)
J += ((lamda) / (2 * m)) * (np.sum(np.power(theta1[:,1:], 2)) + np.sum(np.power(theta2[:,1:], 2)))
return J
这个Sigmoid函数我们以前使用过。 前向传播函数计算给定当前参数的每个训练实例的假设。 它的输出形状应该与y的一个one-hot编码相同。
# 初始化设置
input_size = 400
hidden_size = 25
num_labels = 10
lamda = 1
# 随机初始化完整网络参数大小的参数数组
params = (np.random.random(size=hidden_size * (input_size + 1) + num_labels * (hidden_size + 1)) - 0.5) * 0.25
m = X.shape[0]
X = np.matrix(X)
y = np.matrix(y)
# 将参数数组解开为每个层的参数矩阵
theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
theta1.shape, theta2.shape,X.shape,y.shape
接下来是反向传播算法。 反向传播参数更新计算将减少训练数据上的网络误差。 我们需要的第一件事是计算我们之前创建的Sigmoid函数的梯度的函数。
def backprop(params, input_size, hidden_size, num_labels, X, y, lamda):
# INPUT:神经网络参数,输入层维度,隐藏层维度,训练数据及标签,正则化参数
# OUTPUT:当前参数值下的代价函数
# TODO:根据上面的公式计算代价函数
# STEP1:获取样本个数
# your code here (appro ~ 1 lines)
m = X.shape[0] #或者len(X)
# STEP2:将矩阵X,y转换为numpy型矩阵
# your code here (appro ~ 2 lines)
X = np.matrix(X)
y = np.matrix(y)
# STEP3:从params中获取神经网络参数,并按照输入层维度和隐藏层维度重新定义参数的维度
# your code here (appro ~ 2 lines)
theta1 = np.matrix(np.reshape(params[:25 * 401], (25, 401)))
theta2 = np.matrix(np.reshape(params[25 * 401:], (10, 26)))
# STEP4:调用前面写好的前项传播函数
# your code here (appro ~ 1 lines)
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
# STEP5:初始化
# your code here (appro ~ 3 lines)
J = 0
delta1 = np.zeros((25,401))
delta2 = np.zeros((10,26))
# STEP6:计算代价函数(调用函数)
# your code here (appro ~ 1 lines)
for i in range(m): #遍历每个样本
# your code here (appro ~ 2 lines)
first_term = np.multiply(- y[i,:],np.log(h[i,:]))
second_term = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
J += np.sum(first_term - second_term)
J = J / m
# STEP7:实现反向传播(这里用到的公式请参考原版作业PDF的第5页)
for t in range(m): #遍历每个样本
a1t = a1[t,:] # (1, 401)
z2t = z2[t,:] # (1, 25)
a2t = a2[t,:] # (1, 26)
ht = h[t,:] # (1, 10)
yt = y[t,:] # (1, 10)
# your code here (appro ~ 5 lines)
d3t = ht - yt #(1,10)
z2t = sigmoid_gradient(np.insert(z2t, 0, values=np.ones(1))) #(1,26)
d2t = np.multiply((theta2.T * d3t.T).T,z2t) #(1,26)
delta1 = delta1 + (d2t[:,1:]).T * a1t
delta2 = delta2 + d3t.T * a2t
# STEP8:加入正则化
# your code here (appro ~ 1 lines)
delta1[:,1:] = (delta1[:,1:] + lamda*theta1[:,1:])/m
delta2[:,1:] = (delta2[:,1:] + lamda*theta2[:,1:])/m
# STEP9:将梯度矩阵转换为单个数组
grad = np.concatenate((np.ravel(delta1), np.ravel(delta2)))
return J, grad
反向传播计算的最难的部分(除了理解为什么我们正在做所有这些计算)是获得正确矩阵维度。 顺便说一下,你容易混淆了A * B与np.multiply(A,B)使用。 基本上前者是矩阵乘法,后者是元素乘法(除非A或B是标量值,在这种情况下没关系)。 无论如何,让我们测试一下,以确保函数返回我们期望的。
我们终于准备好训练我们的网络,并用它进行预测。 这与以往的具有多类逻辑回归的练习大致相似。
from scipy.optimize import minimize
# minimize the objective function
fmin = minimize(fun=backprop, x0=params, args=(input_size, hidden_size, num_labels, X, y_onehot, lamda),
method='TNC', jac=True, options={'maxiter': 250})
fmin
由于目标函数不太可能完全收敛,我们对迭代次数进行了限制。 我们的总代价已经下降到0.5以下,这是算法正常工作的一个很好的指标。 让我们使用它发现的参数,并通过网络转发,以获得一些预测。
让我们使用它找到的参数,并通过网络前向传播以获得预测。
X = np.matrix(X)
theta1 = np.matrix(np.reshape(fmin.x[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(fmin.x[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
y_pred = np.array(np.argmax(h, axis=1) + 1)
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))
我们已经成功地实施了一个基本的反向传播神经网络,并用它来分类手写数字图像!