编程作业3.1:Multi-class classification(One-vs-all)

题目:

  • 在本次练习中,你将使用逻辑回归神经网络来识别手写数字(从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:\BaiduNetdiskDownload\data_sets\ex3data1.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(\theta)=\frac1m \sum_{i=1}^m \left( - y^{\left(i \right)} log \left( h_\theta \left( x^{\left( i\right)} \right) \right) - \left( 1-y^{\left( i\right)} \right) log \left( 1- h_\theta \left( x^{\left( i\right)} \right) \right) \right) + \frac {\lambda}{2m} \sum_{j=1}^n \theta_j^2 \]

注意: 不惩罚第一项\(\theta_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

因为我们未对\({\theta }_{0}\)进行正则化,所以梯度下降算法将分两种情形:

\[\frac{\partial }{\partial \theta_0} J(\theta)=\frac1m \sum_{i=1}^m \left( h_\theta \left( x^{ \left(i\right) } \right) -y^{ \left(i\right) } \right) x_{0}^{\left(i\right)} \]

\[\frac{\partial }{\partial \theta_j} J(\theta)=\frac1m \sum_{i=1}^m \left( h_\theta \left( x^{ \left(i\right) } \right) -y^{ \left(i\right) } \right) x_{j}^{\left(i\right)} + \frac \lambda m \theta_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:\BaiduNetdiskDownload\data_sets\ex3data1.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,进行上次作业中的逻辑回归的二分类任务。

参考资料

minimize官方文档

吴恩达机器学习作业Python实现(三):多类分类和前馈神经网络

吴恩达|机器学习作业3.0.逻辑回归解决多元分类

posted @ 2020-02-21 22:58  Ylxxxxx  阅读(659)  评论(0编辑  收藏  举报