机器学习笔记(一)——逻辑回归

逻辑回归

Hypothesis function 预测函数

h θ ( x ) = 1 1 + e − x ∗ θ T (1) h_{\theta}(x) = \frac{1}{1+e^{-\bold{x}*\theta^T}} \tag{1} hθ(x)=1+exθT1(1)
机器学习笔记(一)——逻辑回归_第1张图片

代价函数

Cost ⁡ ( h θ ( x ) , y ) = { − log ⁡ ( h θ ( x ) )  if  y = 1 − log ⁡ ( 1 − h θ ( x ) )  if  y = 0 (2) \operatorname{Cost}\left(h_{\theta}(x), y\right)=\left\{\begin{aligned} -\log \left(h_{\theta}(x)\right) & \text { if } y=1 \\ -\log \left(1-h_{\theta}(x)\right) & \text { if } y=0 \end{aligned}\right. \tag{2} Cost(hθ(x),y)={log(hθ(x))log(1hθ(x)) if y=1 if y=0(2)

Cost ⁡ ( h θ ( x ) , y ) = − y × log ⁡ ( h θ ( x ) ) − ( 1 − y ) × log ⁡ ( 1 − h θ ( x ) ) (3) \operatorname{Cost}\left(h_{\theta}(x), y\right)=-y \times \log \left(h_{\theta}(x)\right)-(1-y) \times \log \left(1-h_{\theta}(x)\right) \tag{3} Cost(hθ(x),y)=y×log(hθ(x))(1y)×log(1hθ(x))(3)

\qquad 其图如下,也就是说当 y = 1 y=1 y=1时, h θ ( x ) h_{\theta}(x) hθ(x)越接近于1, C o s t Cost Cost越小。当 y = 0 y=0 y=0时, h θ ( x ) h_{\theta}(x) hθ(x)越接近于0, C o s t Cost Cost越小。换句话说,在使用这样的代价函数进行模型的训练时,会让 y = 0 y=0 y=0时的 h θ ( x ) h_\theta(x) hθ(x)趋于 0 0 0,会让 y = 1 y=1 y=1时的 h θ ( x ) h_\theta(x) hθ(x)趋于 1 1 1。也就是说,对于最终训练得到的模型, h θ ( x ) h_\theta(x) hθ(x)越大,表明它被判别为这一类的可能性越大。

机器学习笔记(一)——逻辑回归_第2张图片
\qquad 最终的代价函数定义为如下,其中m代表总的x的样本量。
J ( θ ) = 1 m ∑ i = 1 m [ − y ( i ) log ⁡ ( h θ ( x ( i ) ) ) − ( 1 − y ( i ) ) log ⁡ ( 1 − h θ ( x ( i ) ) ) ] (4) J(\theta)=\frac{1}{m} \sum_{i=1}^{m}\left[-y^{(i)} \log \left(h_{\theta}\left(x^{(i)}\right)\right)-\left(1-y^{(i)}\right) \log \left(1-h_{\theta}\left(x^{(i)}\right)\right)\right] \tag{4} J(θ)=m1i=1m[y(i)log(hθ(x(i)))(1y(i))log(1hθ(x(i)))](4)
\qquad J ( θ ) J(\theta) J(θ)的偏导数的最终推导结果如下,其中i表示第i个数据,j表示该数据的第j维的值。
∂ ∂ θ j J ( θ ) = ∂ ∂ θ j [ − 1 m ∑ i = 1 m [ − y ( i ) log ⁡ ( 1 + e − θ T x ( i ) ) − ( 1 − y ( i ) ) log ⁡ ( 1 + e θ T x ( i ) ) ] ] = 1 m ∑ i = 1 m [ h θ ( x ( i ) ) − y ( i ) ] x j ( i ) (5) \begin{aligned} \frac{\partial}{\partial \theta_{j}} J(\theta)=&\frac{\partial}{\partial \theta_{j}}\left[-\frac{1}{m} \sum_{i=1}^{m}\left[-y^{(i)} \log \left(1+e^{-\theta^{T} x^{(i)}}\right)-\left(1-y^{(i)}\right) \log \left(1+e^{\theta^{T} x^{(i)}}\right)\right]\right]\\ =&\frac{1}{m} \sum_{i=1}^{m}\left[h_{\theta}\left(x^{(i)}\right)-y^{(i)}\right] x_{j}^{(i)} \end{aligned} \tag{5} θjJ(θ)==θj[m1i=1m[y(i)log(1+eθTx(i))(1y(i))log(1+eθTx(i))]]m1i=1m[hθ(x(i))y(i)]xj(i)(5)
\qquad 对于正则化后的逻辑回归模型,定义公式如下。其中m仍然表示样本的总数,n表示 θ \theta θ的维度,需要注意的是 θ \bold{\theta} θ θ 1 \theta_1 θ1开始进行正则化。
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 (6) J(\theta)=\frac{1}{m} \sum_{i=1}^{m}\left[-y^{(i)} \log \left(h_{\theta}\left(x^{(i)}\right)\right)-\left(1-y^{(i)}\right) \log \left(1-h_{\theta}\left(x^{(i)}\right)\right)\right]+\frac{\lambda}{2 m} \sum_{j=1}^{n} \theta_{j}^{2} \tag{6} J(θ)=m1i=1m[y(i)log(hθ(x(i)))(1y(i))log(1hθ(x(i)))]+2mλj=1nθj2(6)

梯度下降算法

\qquad 在编写代码时,提倡使用向量化的方法,将N个参数同时进行更新。要最小化代价函数,通过求导,得出的梯度下降算法如下,其中的i,j的定义与(6)式相同。
R e p e a t   u n t i l   c o n v e r g e n c e   { θ 0 : = θ 0 − α 1 m ∑ i = 1 m ( ( h θ ( x ( i ) ) − y ( i ) ) ⋅ x 0 ( i ) ) θ j : = θ j − α 1 m ∑ i = 1 m ( ( h θ ( x ( i ) ) − y ( i ) ) ⋅ x j ( i ) + λ m θ j )  for  j = 1 , 2 , … n } (7) \begin{aligned} Repeat \, until \, convergence\,\{ &\theta_{0}:=\theta_{0}-\alpha \frac{1}{m} \sum_{i=1}^{m}\left(\left(h_{\theta}\left(x^{(i)}\right)-y^{(i)}\right) \cdot x_{0}^{(i)}\right) \\ &\theta_{j}:=\theta_{j}-\alpha \frac{1}{m} \sum_{i=1}^{m}\left(\left(h_{\theta}\left(x^{(i)}\right)-y^{(i)}\right) \cdot x_{j}^{(i)}+\frac{\lambda}{m} \theta_{j}\right) \\ & \text { for } j=1,2, \ldots n \\ &\} \end{aligned} \tag{7} Repeatuntilconvergence{θ0:=θ0αm1i=1m((hθ(x(i))y(i))x0(i))θj:=θjαm1i=1m((hθ(x(i))y(i))xj(i)+mλθj) for j=1,2,n}(7)

逻辑回归用于多分类问题

\qquad 对于逻辑回归用于多分类问题时,由于逻辑回归只能一次在2个类之间进行分类,因此我们需要多类分类的策略。每个分类器在“类别 i i i”和“不是 i i i”之间决定。我们将把分类器训练包含在一个函数中,该函数计算 N N N个分类器中的每个分类器的最终权重,得到最后的结果。

\qquad 例如:对于一个 N N N分类问题来说,我们要制定并训练出 N N N个分类器。也就是说,我们需要先将原样本的y标签变化为N份针对不同分类器的y标签。对于从 i = 1 , … , N i=1,\dots,N i=1,,N类的y,我们都以 i i i为主体转化出N份的y标签,值只有1和0,他们分别对应是第 i i i类和不是第 i i i类。

\qquad 训练完成这 N N N个分类器,得到不同分类器下的 θ \theta θ值,并加他们都添加到 a l l _ t h e t a all\_theta all_theta当中。之后,我们就可以进行逻辑 N N N类回归预测。对于输入的样本值,我们要在 N N N个分类器中进行预测,最终最高 h θ ( x ) h_\theta(x) hθ(x)值对应的那一类就是我们最终预测的结果。

代码实例

\qquad 接下来,我将用逻辑回归来解决经典的MNIST手写数字识别问题,下面是它详细的代码介绍。

\qquad 首先载入数据集:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
import numpy as np

# 逻辑回归做多类别预测
data = loadmat('ex3data1.mat')
print(data['X'].shape,data['y'].shape)

\qquad 定义sigmoid函数( h θ h_\theta hθ函数)和 C o s t Cost Cost函数:

#sigmoid 函数
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

#代价函数
def cost(theta, X, y, learningRate):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    #sigmoid函数里套 X * theta.T 就是 H函数   (5000,400) * (400,1)
    #有关*的注释:
    #a*b 如果a和b都是narray 且 规格相同,按普通乘法来,如果他们都是matrix,必须按矩阵乘法的规格来
    #因此这里X是(5000,400) theta 则是(400,1) 相乘之后是 (5000,1)
    #y标签里的值是1-10 10代表0
    #np.multiply 两矩阵规格相同时,计算得到其内积
    first = np.multiply(-y, np.log(sigmoid(X * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid(X * theta.T)))
    #len(X) 5000-也就是X的第一维的长度 这里表示5000 \theta 正则化前就是乘以1/2m
    #theta[:,1:theta.shape[1]]表示从\theta1开始正则化, \theta0不参与正则化
    reg = (learningRate / (2 * len(X))) * np.sum(np.power(theta[:,1:theta.shape[1]], 2))
    return np.sum(first - second) / len(X) + reg    #first - second 后要求sum

\qquad 举例非向量化用for循环求梯度:

def gradient_with_loop(theta, X, y, learningRate):
    theta = np.matrix(theta)
    #X 本身是(400,)的一个向量,我们要把它转为列向量(转换为矩阵,它就变成了(1,400)的列向量)
    # 把一维的向量转换为列向量--->意味着把它转换为矩阵
    X = np.matrix(X)
    y = np.matrix(y)

    #取[1] 也就是400
    parameters = int(theta.ravel().shape[1])
    grad = np.zeros(parameters)

    #(5000,400) * (400,1) ->(5000,1)
    error = sigmoid(X * theta.T) - y

    for i in range(parameters):
        # 在error外面乘x_j^i i是代表第几个X 因为这里是 np.multiply 
        # 这里 就是将上一步得到的error 每行和 X[,i]的每行相乘
        # 与公式是一致的 至于这个代码里的i是指\theta 对应的i 也就是公式中的j 因为x是有400维的
        # 公式当中x^(i)_{j} i 是指第几个数据 他都是400维  j是指400中的具体第几个维度
        # 所以对于不同的\theta_{j} 它是对应不同维度(400)的x
        # 这里的error (5000,1) X[:,i] 也是(5000,1) 它的每一行 
        # 5000行的每个值都是乘了5000个X的第i个 (5000,i) i代表\theta的下标
        term = np.multiply(error, X[:, i])

        if (i == 0):
            grad[i] = np.sum(term) / len(X)
        else:
            grad[i] = (np.sum(term) / len(X)) + ((learningRate / len(X)) * theta[:, i])

    return grad

\qquad 举例向量化的梯度函数:
\qquad 梯度函数是计算 θ \theta θ的梯度,它的大小与 θ \theta θ的大小是一样的(X的维度)。在进行梯度下降的时候是要把所有变量都要走一遍 所以要sum所有/m。我们看到动态的梯度/参数在变化是因为有大量的样本,一个样本是过一遍,而不是因为epoch的变化(不要混淆)。

#向量化的梯度函数
def gradient(theta, X, y, learningRate):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)

    parameters = int(theta.ravel().shape[1])
    # X * theta.T ->(5000,1) x^(i) 第 i个数据与 \theta向量相乘 theta.T是(400,1) \theta原来是(1,400)
    error = sigmoid(X * theta.T) - y

    #(400,5000) * (5000,1) -> (400,1) 第n个x 的error 乘以 第n个 x 的x_j^{i} 并加和 (1,400)
    #X.T * error X是行向量,(400,5000) * (5000,1) -> (400,1) 转置 (1,400)
    #grad也是(1,400)--->每个\theta的gradient嘛
    grad = ((X.T * error) / len(X)).T + ((learningRate / len(X)) * theta)

    #在进行梯度下降的时候是要把所有变量都要走一遍 所以要sum所有的/m

    # intercept gradient is not regularized
    # 特殊处理一下 拦截 (0,0) 也就是\theta 0
    grad[0, 0] = np.sum(np.multiply(error, X[:, 0])) / len(X)

    return np.array(grad).ravel()

\qquad 现在我们已经定义了代价函数和梯度函数,现在是构建分类器的时候了。对于这个任务,我们有 10 10 10个可能的类,并且由于逻辑回归只能一次在 2 2 2个类之间进行分类,我们需要多类分类的策略。在本练习中,我们的任务是实现一对一全分类方法,其中具有k个不同类的标签就有k个分类器,每个分类器在“类别 i i i”和“不是 i i i”之间决定。
\qquad 我们将把分类器训练包含在一个函数中,该函数计算 10 10 10个分类器中的每个分类器的最终权重,并将权重返回为 k ∗ ( n + 1 ) k*(n+1) k(n+1)数组,其中n是参数数量。 权重返回是 k ∗ ( n + 1 ) k*(n+1) k(n+1)规格。

\qquad 计算all_theta, 10 10 10个分类器的 θ \theta θ的结果如下:

\qquad 注意,minimize函数输入的参数,cost代价函数(要变小)和gradient(更新规则)都需要。

from scipy.optimize import minimize

def one_vs_all(X, y, num_labels, learning_rate):
    rows = X.shape[0]
    params = X.shape[1]

    # k X (n + 1) array for the parameters of each of the k classifiers
    # k个分类器 有k*(n+1)个权重
    all_theta = np.zeros((num_labels, params + 1))

    # insert a column of ones at the beginning for the intercept term
    # 给X在最前面插入了一个 1 与 \theta_0相加->代表截距/常数项
    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    # labels are 1-indexed instead of 0-indexed
    # 1-10 对于5000个y里 如果它==i,就变1,!=i就变0
    for i in range(1, num_labels + 1):
        theta = np.zeros(params + 1)
        # 意思就是:对于5000个不同类别的y 类别变成 第 i类和不是第i类
        y_i = np.array([1 if label == i else 0 for label in y])
        y_i = np.reshape(y_i, (rows, 1))

        # minimize the objective function
        fmin = minimize(fun=cost, x0=theta, args=(X, y_i, learning_rate), method='TNC', jac=gradient)
        all_theta[i - 1, :] = fmin.x
        # 第 i 种分类的 (n+1)个\theta值

    return all_theta

\qquad 定义预测函数,对于这一步,我们将计算每个类的类概率,对于每个训练样本(使用当然的向量化代码),并将输出类标签为具有最高概率的类。

def predict_all(X, all_theta):
    rows = X.shape[0]
    params = X.shape[1]
    num_labels = all_theta.shape[0]

    # same as before, insert ones to match the shape
    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    # convert to matrices
    X = np.matrix(X)
    all_theta = np.matrix(all_theta)

    # compute the class probability for each class on each training instance
    h = sigmoid(X * all_theta.T)
    # 对于第i类来说,判断是第i类或者不是第i类,h(x)值越大,说明是第i类的可能性越大

    # create array of the index with the maximum probability
    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

\qquad 进行预测。这里需要注意的几点:首先,我们为 θ \theta θ添加了一个额外的参数(与训练数据一列),以计算截距项(常数项)。其次,我们将y从类标签转换为每个分类器的二进制值(要么是 i i i类,要么不是 i i i类)。最后,我们使用SciPy的较新优化API来最小化每个分类器的代价函数。 如果指定的话,API将采用目标函数,初始参数集,优化方法和jacobian(渐变)函数。然后将优化程序找到的参数分配给参数数组。
\qquad 实现向量化代码的一个更具挑战性的部分是正确地写入所有的矩阵,保证维度正确。注意, θ \theta θ是一维数组,因此当它被转换为计算梯度的代码中的矩阵时,它变为 ( 1 × 401 ) (1×401) (1×401)矩阵。我们还检查y中的类标签,以确保它们看起来与想象的一致。

all_theta = one_vs_all(data['X'], data['y'], 10, 1)
y_pred = predict_all(data['X'], all_theta)
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, data['y'])]
accuracy = (sum(map(int, correct)) / float(len(correct)))
print ('accuracy = {0}%'.format(accuracy * 100))

\qquad 最终,使用逻辑回归完成MNIST分类达到的准确度为:94.46%。

你可能感兴趣的:(机器学习笔记,python,机器学习)