编程练习3:多类分类与神经网络
1. 多级分类
在本练习中,您将使用逻辑回归和神经网络来识别手写数字(从0到 9)。如今,自动手写数字识别被广泛使用 - 从识别邮件信封上的邮政编码(邮政编码)到识别银行支票上的金额。本练习将向您展示您学习的方法如何用于此分类任务。
1.1 导入数据集
ex3data1.mat中获得一个包含5000个手写数字训练示例的数据集。.mat格式表示数据已保存为原生Octave / MATLAB矩阵格式,而不是像csv文件那样的文本(ASCII)格式。可以使用loadmat命令将这些矩阵直接读入程序。
ex3data1.mat中有5000个训练示例,其中每个训练示例是数字的20×20像素灰度图像。每个像素由浮点数表示,该浮点数表示该位置处的灰度强度。20×20像素网格被“展开”成400维向量。这些训练示例中的每一个在我们的数据矩阵X中变成单行。这给出了5000×400矩阵X,其中每行是手写数字图像的训练示例。训练集的第二部分是5000维向量y,其包含训练集的标签。为了使事物与Octave / MATLAB索引更加兼容,在没有零索引的情况下,我们将数字零映射到值10。因此,“0”数字标记为“10”,而数字“1”至“9”按其自然顺序标记为“1”至“9”。
# 导入数据
path = r'C:\Users\Administrator\Desktop\ML\machine-learning-ex3\ex3\ex3data1.mat'
data = loadmat(path)
print(data['X'].shape) # 有5000个样本,X每个样本是20*20的灰度强度,y对应着数字
print(data['y'].shape)
运行结果:
(5000, 400)
(5000, 1)
在原始数据上进行一些处理。为X插入全为1的一列,作为x0;用m和n存储X的行数和列数,分别为5000和400+1。
X = data['X']
y = data['y']
m = X.shape[0]
X = np.insert(X, 0, values=np.ones(m), axis=1) # 插入全为1的一列
n = X.shape[1] # n存储每个样本的长度,即400+1=401
1.2 可视化数据
编写代码随机选择从X中选择100行并将这些行传递给displayData函数。此功能将每行映射到20×20像素的灰度图像,并将图像一起显示。
# 可视化数据
def displayData(X):
fig, ax = plt.subplots(nrows=10, ncols=10, sharey=True, sharex=True) # 生成10行10列的画布,坐标轴共享
pick_one = np.random.randint(0, 5000, (100, )) # 返回100个随机整型数作为图片序号
for row in range(10):
for col in range(10):
ax[row, col].matshow(X[pick_one[10 * row + col]].reshape((20, 20)), cmap='gray_r') # 每个图上展示一个灰度数字
plt.xticks([]) # 去除坐标
plt.yticks([])
plt.show()
displayData(X)
运行结果:
1.3 向量化Logistic回归
1.3.1 向量化成本函数
# 定义Sigmoid函数
def Sigmoid(z):
return 1 / (1 + np.exp(-z))
# 定义代价函数
def CostFunction(theta, X, y, lr):
theta = np.mat(theta)
X = np.mat(X)
y = np.mat(y)
first = np.multiply(-y, np.log(Sigmoid(X * theta.T)))
second = np.multiply((1 - y), np.log(1 - Sigmoid(X * theta.T)))
reg = (lr / (2 * len(X))) * np.sum(np.power(theta[:, 1:theta.shape[1]], 2))
return np.sum(first - second) / len(X) + reg
1.3.2 向量化梯度下降
# 梯度下降
def GradientDescent(theta, X, y, lr):
theta = np.mat(theta)
X = np.mat(X)
y = np.mat(y)
parameters = int(theta.ravel().shape[1])
error = Sigmoid(X * theta.T) - y
grad = ((X.T * error) / len(X)).T + ((lr / len(X)) * theta)
grad[0, 0] = np.sum(np.multiply(error, X[:, 0])) / len(X) # theta0不需要正则化
return np.array(grad).ravel()
1.4 一对多分类
使用scipy.optimize中的minimize()函数优化参数,其具体用法可参考官方文档。逻辑回归只能分类一对一,所以我们需要为每个类别建立一个分类器,有10个数字就需要建立10个分类器,每个分类器在“类别 i”和“不是 i”之间决定。我们使用for循环,循环10次,每次循环都优化出分类器i的参数,每个分类器的参数有401个,一共10个,所以最后的分类器的总参数是10×401的。
代码中的y_i说明一下,原始数据中的标签是0~9的数字,我们不能将它作为逻辑回归的y。假设当前的分类器是1,即它要识别数字1的类别,遍历循环原始数据y,将标签为1的地方置为1,其余置为0,最后形成y_i,所以y_i 和y的大小是一样的,都是5000×1的。
def one_vs_all(X, y, num_labels, lr):
theta_all = np.zeros((num_labels, n)) # theta_all是10*401的
for i in range(1, num_labels + 1): # 10个分类器,每个分类器进行一次minimize
theta_i = np.zeros(n)
y_i = np.array([1 if label == i else 0 for label in y]) # 为每个分类器设置输出标签
y_i = np.reshape(y_i, (m, 1))
theta_i = theta_i.reshape(theta_i.shape[0], 1)
fmin = minimize(CostFunction, theta_i, args=(X, y_i, lr), method='TNC', jac=GradientDescent)
theta_all[i - 1, :] = fmin.x
return theta_all
我们检查一下各变量的维度是否正确
theta_all = np.zeros((10, n))
theta_i = np.zeros(n)
y_0 = np.array([1 if label == 0 else 0 for label in data['y']])
y_0 = np.reshape(y_0, (m, 1))
print(m)
print(n)
print(theta_all.shape)
print(theta_i.shape)
print(y_0.shape)
运行结果:
5000
401
(10, 401)
(401,)
(5000, 1)
查看最终优化好的参数结果
all_theta = one_vs_all(X, y, 10, 1)
print(all_theta)
运行结果:
[[-2.38328033e+00 0.00000000e+00 0.00000000e+00 ... 1.30457922e-03
-7.80639914e-10 0.00000000e+00]
[-3.18290523e+00 0.00000000e+00 0.00000000e+00 ... 4.46195054e-03
-5.08719807e-04 0.00000000e+00]
[-4.79633922e+00 0.00000000e+00 0.00000000e+00 ... -2.87372925e-05
-2.47558329e-07 0.00000000e+00]
...
[-7.98781514e+00 0.00000000e+00 0.00000000e+00 ... -8.94548286e-05
7.21318113e-06 0.00000000e+00]
[-4.57247837e+00 0.00000000e+00 0.00000000e+00 ... -1.33506912e-03
9.98853660e-05 0.00000000e+00]
[-5.40125592e+00 0.00000000e+00 0.00000000e+00 ... -1.16431637e-04
7.86344031e-06 0.00000000e+00]]
1.5 检测正确率
# 实现预测
def predict_all(X, theta_all):
X = np.mat(X)
theta_all = np.mat(theta_all)
h = Sigmoid(X * theta_all.T) # 求出概率
h_argmax = np.argmax(h, axis=1) # 求出最大概率对应的索引
return h_argmax+1
y_pred = predict_all(X, theta_all)
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, y)]
accuracy = np.sum(correct) / 5000.0
print('accuracy = {0}%'.format(accuracy * 100))
运行结果:
accuracy = 94.46%
完整代码:
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat # 将数据从MATLAB格式加载到python
from scipy.optimize import minimize
# 导入数据
path = r'C:\Users\Administrator\Desktop\ML\machine-learning-ex3\ex3\ex3data1.mat'
data = loadmat(path)
print(data['X'].shape) # 有5000个样本,X每个样本是20*20的灰度强度,y对应着数字
print(data['y'].shape)
X = data['X']
y = data['y']
# 可视化数据
def displayData(X):
fig, ax = plt.subplots(nrows=10, ncols=10, sharey=True, sharex=True) # 生成10行10列的画布,坐标轴共享
pick_one = np.random.randint(0, 5000, (100, )) # 返回100个随机整型数作为图片序号
for row in range(10):
for col in range(10):
ax[row, col].matshow(X[pick_one[10 * row + col]].reshape((20, 20)), cmap='gray_r') # 每个图上展示一个灰度数字
plt.xticks([]) # 去除坐标
plt.yticks([])
plt.show()
displayData(X)
m = X.shape[0]
X = np.insert(X, 0, values=np.ones(m), axis=1) # 插入全为1的一列
n = X.shape[1] # n存储每个样本的长度,即400+1=401
# 定义Sigmoid函数
def Sigmoid(z):
return 1 / (1 + np.exp(-z))
# 定义代价函数
def CostFunction(theta, X, y, lr):
theta = np.mat(theta)
X = np.mat(X)
y = np.mat(y)
first = np.multiply(-y, np.log(Sigmoid(X * theta.T)))
second = np.multiply((1 - y), np.log(1 - Sigmoid(X * theta.T)))
reg = (lr / (2 * len(X))) * np.sum(np.power(theta[:, 1:theta.shape[1]], 2))
return np.sum(first - second) / len(X) + reg
# 梯度下降
def GradientDescent(theta, X, y, lr):
theta = np.mat(theta)
X = np.mat(X)
y = np.mat(y)
parameters = int(theta.ravel().shape[1])
error = Sigmoid(X * theta.T) - y
grad = ((X.T * error) / len(X)).T + ((lr / len(X)) * theta)
grad[0, 0] = np.sum(np.multiply(error, X[:, 0])) / len(X)
return np.array(grad).ravel()
# 实现一对多分类
def one_vs_all(X, y, num_labels, lr):
theta_all = np.zeros((num_labels, n)) # theta_all是10*401的
for i in range(1, num_labels+1): # 10个分类器,每个分类器进行一次minimize
theta_i = np.zeros(n)
y_i = np.array([1 if label == i else 0 for label in y]) # 为每个分类器设置输出标签
y_i = np.reshape(y_i, (m, 1))
theta_i = theta_i.reshape(theta_i.shape[0], 1)
fmin = minimize(CostFunction, theta_i, args=(X, y_i, lr), method='TNC', jac=GradientDescent)
theta_all[i - 1, :] = fmin.x
return theta_all
theta_all = one_vs_all(X, y, 10, 1)
# 实现预测
def predict_all(X, theta_all):
X = np.mat(X)
theta_all = np.mat(theta_all)
h = Sigmoid(X * theta_all.T) # 求出概率
h_argmax = np.argmax(h, axis=1) # 求出最大概率对应的索引
return h_argmax+1
y_pred = predict_all(X, theta_all)
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, y)]
accuracy = np.sum(correct) / 5000.0
print('accuracy = {0}%'.format(accuracy * 100))
2. 神经网络
在本练习的前一部分中,您实现了多类逻辑回归以识别手写数字。然而,逻辑回归不能形成更复杂的假设,因为它只是一个线性分类器。在这部分练习中,您将实现一个神经网络来重新使用与以前相同的训练集识别手写数字。神经网络将能够表示形成非线性假设的复杂模型。本周,您将使用我们已经训练过的神经网络中的参数。您的目标是实现前馈传播算法以使用我们的权重进行预测。
import numpy as np
from scipy.io import loadmat
path1 = r'C:\Users\Administrator\Desktop\ML\machine-learning-ex3\ex3\ex3data1.mat'
data = loadmat(path1)
X = data['X'] # 5000 x 400
y = data['y'] # 5000 x 1
X = np.insert(X, 0, 1, axis=1) # 5000 x (400+1)
path2 = r'C:\Users\Administrator\Desktop\ML\machine-learning-ex3\ex3\ex3weights.mat' # 导入模型参数
weights = loadmat(path2)
theta1 = weights['Theta1'] # 25 x 401
theta2 = weights['Theta2'] # 10 x 26
# 定义Sigmoid函数
def Sigmoid(z):
return 1/(1+np.exp(-z))
# 定义前向传播
def forward(X, theta1, theta2):
z1 = Sigmoid(np.dot(X, theta1.T)) # 计算z1
z1 = np.insert(z1, 0, 1, axis=1) # 为z1添加偏置项
z2 = Sigmoid(np.dot(z1, theta2.T))
return z2
theta_all = forward(X, theta1, theta2)
print(theta_all.shape) # 5000 x 10
# 实现预测
def predict_all(theta_all):
theta_all = np.mat(theta_all)
h_argmax = np.argmax(theta_all, axis=1) # 求出最大概率对应的索引
return h_argmax+1
y_pred = predict_all(theta_all)
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, y)]
accuracy = np.sum(correct) / 5000.0
print('accuracy = {0}%'.format(accuracy * 100))
运行结果:
accuracy = 97.52%
predict_all()预测函数和一对多逻辑回归略有不同。在神经网络模型中我们不需要h = Sigmoid(X * theta_all.T) 求出概率这一步,因为神经网络的输出结果不是优化好的参数,而是直接的结果,也就是概率。可以看出,神经网络模型的代码量更小,速度也更快。我们可以直观的对比一下:逻辑回归在计算时花费了7.694649 Seconds,而神经网络只用了0.056066 Seconds,相差了大约150倍的速度。