Bootstrap

多类别分类——吴恩达课程作业python代码实现

一、模型建立

对于二元分类的数据模型如下;
在这里插入图片描述
使用两种符号表示两个不同的数据集。
对于二元分类,上一篇博客已经给出分析:二元分类原理分析,及代码实现过程
运用逻辑回归,很好地实现了分类的功能。
对于一个多分类问题,以三元分类为例,建立数据模型如下,
在这里插入图片描述
使用三种符号,表示三种不同类别的数据集

现在已经知道如何进行二元分类,可以使用逻辑回归,对于直线,同时可以将数据集一分为二为正类和负类。用一对多的分类思想,我们可以将其用在多类分类问题上。有时这个方法也被称为"一对余"(one-vs-rest)方法。

二、一对多分类方法(one-vs-all)

在这里插入图片描述
以三元分类为例,有一个训练集,好比上图表示的有3 个类别,我们用三角形表示 ? = 1,方框表示? = 2,叉叉表示 ? = 3。通过使用一个训练集,将其分成3 个二元分类问题。
  先从用三角形代表的类别1 开始,实际上可以创建一个,新的"伪"训练集,类型2 和类型3 定为负类,类型1 设定为正类,创建一个新的训练集,如下图所示的那样,要拟合出一个合适的分类器。
  在这里插入图片描述
  这里的三角形是正样本,而圆形代表负样本。可以这样想,设置三角形的值为1,圆形的值为0,下面来训练一个标准的逻辑回归分类器,这样我们就得到一个正边界。为了能实现这样的转变,我们将多个类中的一个类标记为正向类(? = 1),然后将其他所有类都标记为负向类,这个模型记作 h θ 1 ( x ) h_{\theta1}(x) hθ1(x);接着,类似地选择另一个类标记为正向类(? = 2),再将其它类都标记为负向类,将这个模型记作 h θ 2 ( x ) h_{\theta2}(x) hθ2(x);依此类推。
  最后得到一系列的模型简记为:
  在这里插入图片描述
在这里插入图片描述
最后,在需要做预测时,将所有的分类机都运行一遍,然后对每一个输入变量,都选择最高可能性的输出变量。

三、分类器实现

以数字字迹识别为例,讨论多类别分类问题。

1,加载数据集(Dateset),可视化

首先,加载数据集。这里的数据为MATLAB的格式,所以要使用SciPy.io的loadmat函数。

def load_data(path):
    data = loadmat(path)
    X = data['X']
    y = data['y']
    return X,y

X, y = load_data('ex3data1.mat')
print(np.unique(y))  # 看下有几类标签
# [ 1  2  3  4  5  6  7  8  9 10]
X.shape, y.shape
# ((5000, 400), (5000, 1))

控制台输出数据中的类别
在这里插入图片描述
其中有5000个训练样本,每个样本是20*20像素的数字的灰度图像。每个像素代表一个浮点数,表示该位置的灰度强度。20×20的像素网格被展开成一个400维的向量。在我们的数据矩阵X中,每一个样本都变成了一行,这给了我们一个5000×400矩阵X,每一行都是一个手写数字图像的训练样本。
由Y数组,查看得到数据集分为,[1,2,3,4,5,6,7,8,9,10]十个类别。
随机查看1个训练样本,查看100个训练样本。

def plot_an_image(X):
    """
    随机打印一个数字
    """
    pick_one = np.random.randint(0, 5000)
    image = X[pick_one, :]
    fig, ax = plt.subplots(figsize=(1, 1))
    ax.matshow(image.reshape((20, 20)), cmap='gray_r')
    plt.xticks([])  # 去除刻度,美观
    plt.yticks([])
    plt.show()
    print('this should be {}'.format(y[pick_one]))


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()


在这里插入图片描述
在这里插入图片描述

2,向量化逻辑回归

使用多个one-vs-all(一对多)logistic回归模型来构建一个多类别分类器。由于有10个类,需要训练10个独立的分类器。为了提高训练效率,重要的是向量化

2.1向量化正则化的代价函数

正则化的logistic回归的代价函数是:
J ( θ ) = − 1 m [ ∑ i = 1 m y ( i ) log ⁡ ( h θ ( x ( i ) ) − ( 1 − y ( i ) ) log ⁡ ( 1 − h θ ( x ( i ) ) ) ] + λ 2 m ∑ j = 1 n θ j 2 J(\theta)=-\frac{1}{m}[\sum_{i=1}^{m}y^{(i)}\log(h_\theta(x^{(i)})-(1-y^{(i)})\log(1-h_\theta(x^{(i)}))]+\frac{\lambda}{2m}\sum_{j=1}^{n}\theta_j^2 J(θ)=m1[i=1my(i)log(hθ(x(i))(1y(i))log(1hθ(x(i)))]+2mλj=1nθj2
对于每个样本 i i i要计算, h θ ( x ( i ) ) h_\theta(x^{(i)}) hθ(x(i)), h θ ( x ( i ) ) = g ( θ T x ( i ) ) h_\theta(x^{(i)})=g(\theta^Tx^{(i)}) hθ(x(i))=g(θTx(i)), g ( z ) g(z) g(z)是sigmoid函数
事实上我们可以对所有的样本用矩阵乘法来快速的计算。
定义X,与 θ \theta θ
在这里插入图片描述
通过计算X θ \theta θ
在这里插入图片描述
在最后一个等式中,我们用到了一个定理,如果 a a a b b b都是向量,那么 a T b = b T a a^Tb=b^Ta aTb=bTa 这样我们就可以用一行代码计算出所有的样本。

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:]
    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

2.2向量化梯度

未正则化逻辑回归代价函数的梯度为:
∂ ∂ / θ j J ( θ ) = − 1 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \frac{\partial }{\partial /\theta_j}J(\theta)=-\frac{1}{m}\sum_{i=1}^{m} (h_\theta(x^{(i)})-y^{(i)})x_j^{(i)} /θjJ(θ)=m1i=1m(hθ(x(i))y(i))xj(i)
使用向量化的方法表示
在这里插入图片描述
其中,在这里插入图片描述
注意, h θ ( x ( i ) ) − y ( i ) h_\theta(x^{(i)})-y^{(i)} hθ(x(i))y(i)是一个标量,令 β i = h θ ( x ( i ) ) − y ( i ) \beta_i=h_\theta(x^{(i)})-y^{(i)} βi=hθ(x(i))y(i)
得到:
在这里插入图片描述
正则化后的梯度下降算法表示如下,不惩罚 θ 0 \theta_0 θ0
在这里插入图片描述
代码实现

def regularized_gradient(theta, X, y, l):
    """
    don't penalize theta_0
    args:
        l: lambda constant
    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])
    return first + reg

3,一对多分类器(one-vs-all)

这部分通过训练多个正则化logistic回归分类器实现一对多分类,每个分类器对应数据集中K类中的一个。
对于这个任务,我们有10个可能的类,并且由于logistic回归只能一次在2个类之间进行分类,每个分类器在“类别 i”和“不是 i”之间决定。 我们将把分类器训练包含在一个函数中,该函数计算10个分类器中的每个分类器的最终权重,并将权重返回shape为(k, (n+1))数组,其中 n 是参数数量。

from scipy.optimize import minimize

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])
    
        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

这里需要注意的几点:首先,我们为X添加了一列常数项 1 ,以计算截距项(常数项)。 其次,我们将y从类标签转换为每个分类器的二进制值(要么是类i,要么不是类i)。 最后,我们使用SciPy的较新优化API来最小化每个分类器的代价函数。 如果指定的话,API将采用目标函数,初始参数集,优化方法和jacobian(渐变)函数。 然后将优化程序找到的参数分配给参数数组。

4,One-vs-all Prediction

在训练多类别分类器之后,现在可以用它来预测给定图像中包含的数字。对于每个输入,您应该使用以下方法计算它属于每个类的“概率” 训练的Logistic回归分类器。One-vs-all预测函数将选择相应的逻辑回归分类器输出概率最高的类和ret。 将类标签(1、2、…或K)作为输入示例的预测。
实现向量化代码的一个更具挑战性的部分是正确地写入所有的矩阵,保证维度正确。

def predict_all(X, all_theta):
    # compute the class probability for each class on each training instance   
    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)
    # because our array was zero-indexed we need to add one for the true label prediction
    h_argmax = h_argmax + 1
    
    return h_argmax

这里的h共5000行,10列,每行代表一个样本,每列是预测对应数字的概率。我们取概率最大对应的index加1就是我们分类器最终预测出来的类别。返回的h_argmax是一个array,包含5000个样本对应的预测值。

raw_X, raw_y = load_data('ex3data1.mat')
X = np.insert(raw_X, 0, 1, axis=1) # (5000, 401)
y = raw_y.flatten()  # 这里消除了一个维度,方便后面的计算 or .reshape(-1) (5000,)

all_theta = one_vs_all(X, y, 1, 10)
all_theta  # 每一行是一个分类器的一组参数
y_pred = predict_all(X, all_theta)
accuracy = np.mean(y_pred == y)
print ('accuracy = {0}%'.format(accuracy * 100))

在这里插入图片描述
通过预测的y值与实际y值进行比对,得出结果在这次练习中的多类别分类器的准确率是0.9446。

;