题目:
- 在本次练习中,你将使用逻辑回归和神经网络来识别手写数字(从0到9)。
- 今天,自动手写数字识别被广泛使用,从识别信封上的邮政编码到识别银行支票上的金额。这个练习将向你展示如何将你所学的方法用于此分类任务。
- 在第一部分中,将扩展以前的逻辑回归,并将其应用于one-vs-all分类。
- 关于数据:本次的数据是以.mat格式储存的,mat格式是matlab的数据存储格式,按照矩阵保存,与numpy数据格式兼容,适合于各种数学运算,因此这次主要使用numpy进行运算。
- ex3data1中有5000个训练样例,其中每个训练样例是一个20像素×20像素灰度图像的数字,每个像素由一个浮点数表示,该浮点数表示该位置的灰度强度。每个20×20像素的网格被展开成一个400维的向量。这些每个训练样例都变成数据矩阵X中的一行。这就得到了一个5000×400矩阵X,其中每一行都是手写数字图像的训练样例。训练集的第二部分是一个包含训练集标签的5000维向量y,“0”的数字标记为“10”,而“1”到“9”的数字按自然顺序标记为“1”到“9”。
编程实现
1.Visualizing the data
加载数据集。这里的数据为MATLAB的格式,所以要使用SciPy.io的loadmat函数。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
# 这里的数据为MATLAB的格式,所以要使用SciPy.io的loadmat函数。
def load_data(path):
data = loadmat(path)
X = data['X']
y = data['y']
return X,y
X, y = load_data('D:BaiduNetdiskDownloaddata_setsex3data1.mat')
# unique() 统计list中的不同值时,返回的是array;用来看下有几类标签
print(np.unique(y))
X.shape, y.shape
其中有5000个训练样本,每个样本是20*20像素的数字的灰度图像。每个像素代表一个浮点数,表示该位置的灰度强度。20×20的像素网格被展开成一个400维的向量。在我们的数据矩阵X中,每一个样本都变成了一行,这给了我们一个5000×400矩阵X,每一行都是一个手写数字图像的训练样本。
def plot_an_image(X):
"""
随机打印一个数字
"""
pick_one = np.random.randint(0, 5000) # np.random.randint(a,b):用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n:a<=n<b,即[a,b)
image = X[pick_one, :]
fig, ax = plt.subplots(figsize=(1, 1))
ax.matshow(image.reshape((20, 20)), cmap='gray_r') # reshape()是数组对象中的方法,用于改变数组的形状。参数cmap='gray_r'是白底黑字的
plt.xticks([]) # 去除刻度,美观
plt.yticks([])
plt.show()
print('this should be {}'.format(y[pick_one]))
plot_an_image(X)
#定义可视化函数
def plot_100_image(X):
"""
随机画100个数字
"""
sample_idx = np.random.choice(np.arange(X.shape[0]), 100) # 随机选100个样本
sample_images = X[sample_idx, :] # (100,400)
fig, ax_array = plt.subplots(nrows=10, ncols=10, sharey=True, sharex=True, figsize=(8, 8))
for row in range(10):
for column in range(10):
ax_array[row, column].matshow(sample_images[10 * row + column].reshape((20, 20)),
cmap='gray_r')
plt.xticks([])
plt.yticks([])
plt.show()
plot_100_image(X)
2.Regularized Cost function
正则化逻辑回归的代价函数如下:
[J( heta)=frac1m sum_{i=1}^m left( - y^{left(i
ight)} log left( h_ heta left( x^{left( i
ight)}
ight)
ight) - left( 1-y^{left( i
ight)}
ight) log left( 1- h_ heta left( x^{left( i
ight)}
ight)
ight)
ight) + frac {lambda}{2m} sum_{j=1}^n heta_j^2
]
注意: 不惩罚第一项( heta_0)
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 定义带正则项的代价函数
def regularized_cost(theta, X, y, l):
"""
don't penalize theta_0
args:
X: feature matrix, (m, n+1) # 插入了x0=1
y: target vector, (m, )
l: lambda constant for regularization
"""
thetaReg = theta[1:] # 将theta的第二列到最后一列的内容赋给theta
first = (-y*np.log(sigmoid(X@theta))) + (y-1)*np.log(1-sigmoid(X@theta))
reg = (thetaReg@thetaReg)*l / (2*len(X))
return np.mean(first) + reg
3.Regularized gradient
因为我们未对({ heta }_{0})进行正则化,所以梯度下降算法将分两种情形:
[frac{partial }{partial heta_0} J( heta)=frac1m sum_{i=1}^m left( h_ heta left( x^{ left(i
ight) }
ight) -y^{ left(i
ight) }
ight) x_{0}^{left(i
ight)}
]
[frac{partial }{partial heta_j} J( heta)=frac1m sum_{i=1}^m left( h_ heta left( x^{ left(i
ight) }
ight) -y^{ left(i
ight) }
ight) x_{j}^{left(i
ight)} + frac lambda m heta_j,其中j=1,2,cdots,n
]
# 定义正则化梯度值(导数值)
def regularized_gradient(theta, X, y, l):
"""
don't penalize theta_0
args:
l: lambda constant for regularization
return:
a vector of gradient
"""
thetaReg = theta[1:]
first = (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y)
# 这里插入一维0,使得对theta_0不惩罚,方便计算
reg = np.concatenate([np.array([0]), (l / len(X)) * thetaReg]) # 将具有相同结构的array序列结合成一个array
return first + reg
4.One-vs-all Classification
这部分我们将实现一对多分类通过训练多个正则化logistic回归分类器,每个对应数据集中K类中的一个。利用for循环对每种数字习得一个带正则的逻辑回归分类器,然后将10个分类器的参数组成一个参数矩阵all_theta返回。
from scipy.optimize import minimize
#计算K个类别分别训练出来的参数theta
def one_vs_all(X, y, l, K):
"""generalized logistic regression
args:
X: feature matrix, (m, n+1) # with incercept x0=1
y: target vector, (m, )
l: lambda constant for regularization
K: numbel of labels
return: trained parameters
"""
all_theta = np.zeros((K, X.shape[1])) # (10, 401)
for i in range(1, K+1):
theta = np.zeros(X.shape[1])
y_i = np.array([1 if label == i else 0 for label in y]) # 如果y对应的值和i相等就返回1,否则返回0
# minimize是局部最优的解法
# fun: 求最小值的目标函数
# x0:变量的初始猜测值,如果有多个变量,需要给每个变量一个初始猜测值
# args:是传递给优化函数的参数
# method:求极值的方法
# jac:计算梯度向量的方法
# options:A dictionary of solver options.
ret = minimize(fun=regularized_cost, x0=theta, args=(X, y_i, l), method='TNC',
jac=regularized_gradient, options={'disp': True})
all_theta[i-1,:] = ret.x
return all_theta
对于这个任务,我们有10个可能的类,并且由于logistic回归只能一次在2个类之间进行分类,每个分类器在“类别 i”和“不是 i”之间决定。 我们将把分类器训练包含在一个函数中,该函数计算10个分类器中的每个分类器的最终权重,并将权重返回shape为(k, (n+1))数组,其中 n 是参数数量。
def predict_all(X, all_theta):
# compute the class probability for each class on each training instance
# 这里的h共5000行,10列,每行代表一个样本,每列是预测对应数字的概率。
h = sigmoid(X @ all_theta.T) # 注意的这里的all_theta需要转置
# create array of the index with the maximum probability
# Returns the indices of the maximum values along an axis.
h_argmax = np.argmax(h, axis=1) # 返回h矩阵中每一行的最大索引
# because our array was zero-indexed we need to add one for the true label prediction
h_argmax = h_argmax + 1 #概率最大对应的index加1就是我们分类器最终预测出来的类别
return h_argmax
这里的h共5000行,10列,每行代表一个样本,每列是预测对应数字的概率,是一个5000乘10的预测概率矩阵。我们取概率最大对应的index加1就是我们分类器最终预测出来的类别。返回的h_argmax是一个array,包含5000个样本对应的预测值。
5.Learning θ parameters
raw_X, raw_y = load_data('D:BaiduNetdiskDownloaddata_setsex3data1.mat')
X = np.insert(raw_X, 0, 1, axis=1) # 为X添加了一列常数项 1,(5000, 401)
y = raw_y.flatten() # 这里消除了一个维度,方便后面的计算 or .reshape(-1) (5000,)
all_theta = one_vs_all(X, y, 1, 10)
all_theta # 每一行是一个分类器的一组参数
6.Evaluating logistic regression
y_pred = predict_all(X, all_theta)
accuracy = np.mean(y_pred == y)
print ('accuracy = {0}%'.format(accuracy * 100))
总结
有K个分类器,每个分类要针对其中一种情况进行训练。也就是每个分类分别当做1,不是这个分类的当做0,进行上次作业中的逻辑回归的二分类任务。