监督学习:逻辑回归

"Supervised Machine Learning: Logistic Regression"

Posted by Summer on February 22, 2025

1 逻辑回归

逻辑回归的特点: 解决二进制问题, 输出标签y只有0或者1

1.1 逻辑回归的公式

逻辑回归的公式也叫 sigmoid 函数

\[g(z) = \frac{1}{1+e^{-z}}\]

在 Logistic 回归的情况下,z(sigmoid 函数的输入)是线性回归模型的输出。

  • 在单个示例的情况下, z是 scalar。
  • 在多个示例的情况下, z可能是由m个值组成的向量,每个示例一个值。

python代码实现:

def sigmoid(z):
    """
    Compute the sigmoid of z

    Args:
        z (ndarray): A scalar, numpy array of any size.

    Returns:
        g (ndarray): sigmoid(z), with the same shape as z
         
    """

    g = 1/(1+np.exp(-z))
   
    return g

1.2 决策边界(Decision boundary)

决策边界实质上是函数输入z等于0时的曲线

决策边界

对于函数

\[g(z) = \frac{1}{1+e^{-z}}\]

在这里 $z=\mathbf{w} \cdot \mathbf{x}$ is the vector dot product:

如果 $f_{\mathbf{w},b}(x) >= 0.5$, 预测 $y=1$

如果 $f_{\mathbf{w},b}(x) < 0.5$, 预测 $y=0$

1.3 逻辑回归的成本函数

逻辑回归成本函数1

如果与线性回归一样, 使用平方成本函数, 会产生很多局部最小值

因此, 为了避免产生局部最小值, 我们需要一个新的成本函数. 这个新的成本函数可以使得函数再次变为凸的.

逻辑回归的损失(loss)函数:

\[\begin{equation*} loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = \begin{cases} - \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) & \text{if $y^{(i)}=1$}\\ \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) & \text{if $y^{(i)}=0$} \end{cases} \end{equation*}\]
  • 等式中 $f_{\mathbf{w},b}(\mathbf{x}^{(i)})$ 是模型的预测, $y^{(i)}$ 是模型的目标值

  • 其中 $f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = g(\mathbf{w} \cdot\mathbf{x}^{(i)}+b)$ ,在这里 $g$ 是逻辑回归函数(sigmoid function)

  • $f_{\mathbf{w},b}(\mathbf{x}^{(i)})$ 距离 $y^{(i)}$ 越远, 损失越大, 损失函数越大

或者,损失函数还可以写成另一个等式

\[loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = (-y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\]

注意在这个等式中 , $y^{(i)}$只能取到01

逻辑回归的成本(cost)函数

\[J(\mathbf{w},b) = \frac{1}{m} \sum_{i=0}^{m-1} \left[ loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) \right]\]

其中:

  • $loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)})$ 是单个数据点的损失函数, 也就是:

    \[loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = -y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\]
  • 其中 m 是数据集中的训练样本数,并且: \(\begin{align*} f_{\mathbf{w},b}(\mathbf{x^{(i)}}) &= g(z^{(i)}) \\ z^{(i)} &= \mathbf{w} \cdot \mathbf{x}^{(i)}+ b \\ g(z^{(i)}) &= \frac{1}{1+e^{-z^{(i)}}} \end{align*}\)

python代码实现

def compute_cost_logistic(X, y, w, b):
    """
    Computes cost

    Args:
      X (ndarray (m,n)): Data, m examples with n features
      y (ndarray (m,)) : target values
      w (ndarray (n,)) : model parameters  
      b (scalar)       : model parameter
      
    Returns:
      cost (scalar): cost
    """

    m = X.shape[0]
    cost = 0.0
    for i in range(m):
        z_i = np.dot(X[i],w) + b
        f_wb_i = sigmoid(z_i)
        cost +=  -y[i]*np.log(f_wb_i) - (1-y[i])*np.log(1-f_wb_i)
             
    cost = cost / m
    return cost

1.4 逻辑回归的梯度下降

逻辑回归的梯度下降公式:

\[\begin{align*} &\text{重复以下公式, 直到收敛:} \; \lbrace \\ & \; \; \;w_j = w_j - \alpha \frac{\partial J(\mathbf{w},b)}{\partial w_j} \; & \text{这里 j= 0..n-1} \\ & \; \; \; \; \;b = b - \alpha \frac{\partial J(\mathbf{w},b)}{\partial b} \\ &\rbrace \end{align*}\]

其中,每次迭代对所有不同$j$的$w_j$同时更新, 在这里

\[\begin{align*} \frac{\partial J(\mathbf{w},b)}{\partial w_j} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} \\ \frac{\partial J(\mathbf{w},b)}{\partial b} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)}) \end{align*}\]
  • m 是数据集中的训练样本数
  • $f_{\mathbf{w},b}(x^{(i)})$ 是模型的预测, while $y^{(i)}$ 是目标
  • 在 Logistic 回归模型中 $z = \mathbf{w} \cdot \mathbf{x} + b$ $f_{\mathbf{w},b}(x) = g(z)$ 这里的 $g(z)$ 是 sigmoid 函数:
    \(g(z) = \frac{1}{1+e^{-z}}\)

python代码实现

def compute_gradient_logistic(X, y, w, b): 
    """
    Computes the gradient for linear regression 
 
    Args:
      X (ndarray (m,n): Data, m examples with n features
      y (ndarray (m,)): target values
      w (ndarray (n,)): model parameters  
      b (scalar)      : model parameter
    Returns
      dj_dw (ndarray (n,)): The gradient of the cost w.r.t. the parameters w. 
      dj_db (scalar)      : The gradient of the cost w.r.t. the parameter b. 
    """
    m,n = X.shape
    dj_dw = np.zeros((n,))             #(n,)
    dj_db = 0.

    for i in range(m):
        f_wb_i = sigmoid(np.dot(X[i],w) + b)       #(n,)(n,)=scalar
        err_i  = f_wb_i  - y[i]            #scalar
        for j in range(n):
            dj_dw[j] = dj_dw[j] + err_i * X[i,j]      #scalar
        dj_db = dj_db + err_i
    dj_dw = dj_dw/m             #(n,)
    dj_db = dj_db/m                                   #scalar
        
    return dj_db, dj_dw  

1.5 使用scikit-learn完成逻辑函数回归

scikit-learn是一个基于 Python 的开源机器学习库,它提供了丰富且高效的工具,在实际项目的机器学习算法中有巨大应用

本节只做基础介绍, 未来打算开一个专题引入scikit-learn

# 引入数据集
import numpy as np

X = np.array([[0.5, 1.5], [1,1], [1.5, 0.5], [3, 0.5], [2, 2], [1, 2.5]])
y = np.array([0, 0, 0, 1, 1, 1])
# 训练模型
from sklearn.linear_model import LogisticRegression

lr_model = LogisticRegression()
lr_model.fit(X, y)
# 进行预测
y_pred = lr_model.predict(X)

print("Prediction on training set:", y_pred)
#检查精度
print("Accuracy on training set:", lr_model.score(X, y))

2 过拟合 与 正则化

2.1 过拟合

过拟合是说, 模型在训练数据上表现特别好,可在新数据上却表现不佳,这是因为模型把训练数据里的噪声和细节都学习了,从而失去了泛化能力。

在处理数据时, 我们通常会遇到如下几个情况:

6.1 过拟合

  • 未拟合(underfit):不能很好地适应训练集,有较大的偏差(high bias)
  • 恰到好处(just right)
  • 过拟合(overfit):有较大的方差(high variance)

现在, 我们关心的是为什么会出现过拟合这种情况

  1. 模型复杂度偏高:像深度神经网络、高阶多项式等复杂的模型,具有很强的拟合能力,它们能够精确地匹配训练数据中的每一个细节。
  2. 训练数据存在噪声或数量不足:如果训练数据中包含大量噪声,或者数据量较少,模型就可能会把噪声误认为是有用的特征。
  3. 训练时间过长:在训练过程中,如果模型在训练数据上不断优化,就可能会逐渐记住噪声的模式。

分类过拟合

基本的解决思路有以下三个:

  1. 收集更多的数据.适合有大量数据的情况
  2. 挑选合适的特征.适合数据量较少,特征量较多的情况.缺点是只使用了训练集的一部分数据,丢掉了另一部分的数据.
  3. 正则化(Regularization),减小参数,防止某一个特征产生过大的影响

在工程中, 研究者一般采用下面的方法:

  • 正则化(Regularization)
    • L1/L2正则化:通过在损失函数中添加与模型参数相关的惩罚项,来限制模型参数的大小。接下来我们将详细阐述的L2正则化会惩罚参数的平方和,其表达式为 ( L = L_{loss} + \lambda \sum w_i^2 ),其中 ( \lambda ) 是正则化系数,( w_i ) 是模型参数。
    • Dropout:是一种专门针对神经网络的正则化方法,它在训练过程中随机忽略一部分神经元,迫使网络学习更加鲁棒的特征。
  • 数据增强(Data Augmentation):通过对现有数据进行变换,如旋转、缩放、裁剪等,生成更多的训练样本,从而减少模型对特定模式的依赖。
  • 交叉验证(Cross - Validation):将训练数据分成多个子集,轮流使用其中一个子集作为验证集,其余子集作为训练集,这样可以更准确地评估模型的泛化能力。
  • 早停(Early Stopping):在训练过程中,监控模型在验证集上的性能,当性能不再提升时,提前停止训练,避免模型过度拟合。
  • 集成学习(Ensemble Learning):将多个不同的模型进行组合,如随机森林、梯度提升树等,通过模型之间的相互补充,降低过拟合的风险。

2.2 L2正则化(Regulation)

L2正则化方法,在回归模型中也叫Ridge回归(岭回归)

正则化

如上一节所述, L2正则化是对模型参数的惩罚(或者说缩小). 不过, 一般情况下我们不会缩小b, 因为缩小它对结果没什么变化.

\[J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2 + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\]
  1. 在成本函数加一个额外的正则化项,目的是缩小每一个参数的值
  2. 使用系数1/2主要是为了数学上的方便,这样在求导时会消去平方项前的2,得到简洁的结果。这样做尤其契合梯度下降算法.
  3. 一般不给$b$增加惩罚项,无论是否包括$b$实际上对结果影响都不大
  4. λ称为正则化参数,可以控制两个不同目标之间的取舍,进一步控制两项的平衡关系
    1. 第一个目标(与第一项有关):更好地拟合训练集数据
    2. 第二个目标(与正则化项有关):保持参数尽量的小
  5. 如果λ被设的太大的话,参数的惩罚程度过大,参数都会接近于0 →$h_w(x)$ = $w_0$ 这样会导致欠拟合; 当然, 太小的$\lambda$没有作用, 函数依然过拟合
  6. 因此,需要选择一个合适的正则化参数λ

首先确定$\lambda$的数量级, 从[1e-4, 1e-3, 1e-2, 0.1, 1, 10]中尝试

2.2.1 正则化的成本函数

正则化的成本函数是:

\(J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2 + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)
在这里: \(f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = \mathbf{w} \cdot \mathbf{x}^{(i)} + b\)

回顾一下,未正则化的是:

\[J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2\]

存在差异的项是, \(\frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)

2.2.2 正则化的逻辑回归成本函数

正则化的逻辑回归成本函数 \(J(\mathbf{w},b) = \frac{1}{m} \sum_{i=0}^{m-1} \left[ -y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) \right] + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\) 在这里: \(f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = sigmoid(\mathbf{w} \cdot \mathbf{x}^{(i)} + b)\)

回顾一下未正则化的表达式:

\[J(\mathbf{w},b) = \frac{1}{m}\sum_{i=0}^{m-1} \left[ (-y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\right]\]

差异项是 \(\frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)

def compute_cost_logistic_reg(X, y, w, b, lambda_ = 1):
    """
    Computes the cost over all examples
    Args:
    Args:
      X (ndarray (m,n): Data, m examples with n features
      y (ndarray (m,)): target values
      w (ndarray (n,)): model parameters  
      b (scalar)      : model parameter
      lambda_ (scalar): Controls amount of regularization
    Returns:
      total_cost (scalar):  cost 
    """

    m,n  = X.shape
    cost = 0.
    for i in range(m):
        z_i = np.dot(X[i], w) + b                                      #(n,)(n,)=scalar, see np.dot
        f_wb_i = sigmoid(z_i)                                          #scalar
        cost +=  -y[i]*np.log(f_wb_i) - (1-y[i])*np.log(1-f_wb_i)      #scalar
             
    cost = cost/m                                                      #scalar

    reg_cost = 0
    for j in range(n):
        reg_cost += (w[j]**2)                                          #scalar
    reg_cost = (lambda_/(2*m)) * reg_cost                              #scalar
    
    total_cost = cost + reg_cost                                       #scalar
    return total_cost                                                  #scalar

2.2.2 正则化的梯度下降(线性/逻辑)

重新定义函数$J_{(w,b)}$后, 照常操作梯度下降

线性和逻辑回归的梯度下降是一样的, 只是$f_{\mathbf{w},b}$的计算不同.

\[\begin{align*} \frac{\partial J(\mathbf{w},b)}{\partial w_j} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} + \frac{\lambda}{m} w_j \tag{1} \\ \frac{\partial J(\mathbf{w},b)}{\partial b} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)}) \tag{2} \end{align*}\]
  • m 是数据集中的训练样本数
  • $f_{\mathbf{w},b}(x^{(i)})$ 是模型的预测, $y^{(i)}$ 是目标

  • 线性 回归模型中 $f_{\mathbf{w},b}(x) = \mathbf{w} \cdot \mathbf{x} + b$
  • 逻辑 回归模型中 $z = \mathbf{w} \cdot \mathbf{x} + b$
    $f_{\mathbf{w},b}(x) = g(z)$
    where $g(z)$ is the sigmoid function:
    $g(z) = \frac{1}{1+e^{-z}}$

正则化后多出来的项是 \(\frac{\lambda}{m} w_j\).

下面我们尝试深入理解正则化后的 $w_j$ 式子

移项后我们发现 \(\begin{align*} w_j &= w_j (1-\alpha\frac{\lambda}{m} ) - \alpha\frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} \end{align*}\)

其中第二项是通常的更新项 $\alpha$通常是0.01左右,$\lambda$通常是1-10,假设m为50,那么$\alpha\frac{\lambda}{m}$为0.0002.因此我们知道$\alpha\frac{\lambda}{m}$一般是大于0的小正数,因此$(1-\alpha\frac{\lambda}{m} )$通常是接近1但小于1的正数.每一次迭代$w_j$都会减小一点

def compute_gradient_linear_reg(X, y, w, b, lambda_): 
    """
    Computes the gradient for linear regression 
    Args:
      X (ndarray (m,n): Data, m examples with n features
      y (ndarray (m,)): target values
      w (ndarray (n,)): model parameters  
      b (scalar)      : model parameter
      lambda_ (scalar): Controls amount of regularization
      
    Returns:
      dj_dw (ndarray (n,)): The gradient of the cost w.r.t. the parameters w. 
      dj_db (scalar):       The gradient of the cost w.r.t. the parameter b. 
    """
    m,n = X.shape           #(number of examples, number of features)
    dj_dw = np.zeros((n,))
    dj_db = 0.

    for i in range(m):                             
        err = (np.dot(X[i], w) + b) - y[i]                 
        for j in range(n):                         
            dj_dw[j] = dj_dw[j] + err * X[i, j]               
        dj_db = dj_db + err                        
    dj_dw = dj_dw / m                                
    dj_db = dj_db / m   
    
    for j in range(n):
        dj_dw[j] = dj_dw[j] + (lambda_/m) * w[j]

    return dj_db, dj_dw