本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:逻辑回归是机器学习中基础且重要的二分类算法,通过线性模型结合Sigmoid函数实现概率预测。本文基于Coursera华盛顿大学机器学习课程的编程实践,详细解析逻辑回归的代码实现,涵盖数据预处理、模型初始化、梯度上升优化、损失函数计算与预测函数设计等关键步骤。通过本项目,学习者可深入理解逻辑回归原理,并掌握其在Python中的实际编程技巧,同时了解过拟合处理及优化算法的扩展应用。
逻辑回归代码

1. 逻辑回归算法原理详解

逻辑回归(Logistic Regression)虽名为“回归”,实则是一种广泛应用于二分类问题的 线性分类模型 。其核心思想是利用线性回归的输出作为输入,通过Sigmoid函数将其映射为[0,1]之间的概率值,从而实现对样本属于某一类别的概率预测。

该算法因其 结构简单、计算高效、易于解释 等优点,被广泛应用于金融风控、广告点击率预估、医学诊断等领域。逻辑回归不仅能输出分类结果,还能给出预测的概率,便于进行风险评估与决策分析。

本章将从逻辑回归的基本概念出发,逐步深入其数学建模过程,并探讨其在实际应用中的优势与局限性。

2. Sigmoid函数与线性预测模型的构建

逻辑回归的核心在于将线性模型的输出通过一个非线性函数进行映射,从而将线性输出转化为概率值。这个非线性函数就是Sigmoid函数。本章将从Sigmoid函数的基本数学形式与图像特性出发,深入探讨其在机器学习中的数学意义与计算优势。随后,我们将构建线性模型的基础表达式,理解其在分类任务中的几何解释。最后,我们将把线性模型与Sigmoid函数进行结合,揭示其在概率预测与分类判断中的逻辑机制。

2.1 Sigmoid函数的数学表达与图像特性

Sigmoid函数是逻辑回归中最为关键的组成部分之一。它将线性模型的输出映射到(0, 1)区间,使得输出可以被解释为属于某一类别的概率。

2.1.1 函数定义与输出范围

Sigmoid函数的数学定义如下:

\sigma(z) = \frac{1}{1 + e^{-z}}

其中,$ z $ 是输入值,可以是任意实数。Sigmoid函数的主要作用是将任意实数压缩到 $ (0, 1) $ 的范围内,这使得它可以被用于表示概率值。

函数输出范围分析:

输入值 $ z $ 输出值 $ \sigma(z) $ 说明
$ z \to -\infty $ $ \sigma(z) \to 0 $ 当输入趋近于负无穷时,输出趋近于0
$ z = 0 $ $ \sigma(z) = 0.5 $ 中点输出,表示等概率
$ z \to +\infty $ $ \sigma(z) \to 1 $ 当输入趋近于正无穷时,输出趋近于1

Sigmoid函数具有平滑、连续、可导等优良数学性质,这为后续的梯度下降优化提供了良好的基础。

2.1.2 导数特性及其在梯度计算中的作用

为了在模型训练中使用梯度下降法,我们需要计算Sigmoid函数的导数。Sigmoid函数的一个重要性质是其导数可以用函数本身来表达:

\sigma’(z) = \sigma(z)(1 - \sigma(z))

这一性质使得在反向传播过程中计算梯度时,能够快速获得导数结果,而不需要额外的复杂计算。

代码演示:Sigmoid函数及其导数的实现

import numpy as np
import matplotlib.pyplot as plt

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

def sigmoid_derivative(z):
    s = sigmoid(z)
    return s * (1 - s)

# 生成输入数据
z = np.linspace(-10, 10, 200)

# 计算输出
s = sigmoid(z)
s_prime = sigmoid_derivative(z)

# 绘图
plt.figure(figsize=(10, 5))
plt.plot(z, s, label='Sigmoid')
plt.plot(z, s_prime, label="Derivative of Sigmoid")
plt.title('Sigmoid Function and Its Derivative')
plt.xlabel('z')
plt.ylabel('σ(z)')
plt.legend()
plt.grid(True)
plt.show()

代码逻辑分析:

  1. 函数定义:
    - sigmoid(z) :根据公式实现Sigmoid函数。
    - sigmoid_derivative(z) :根据导数公式实现导数计算。

  2. 数据准备:
    - 使用 np.linspace(-10, 10, 200) 生成从 -10 到 10 的200个点作为输入值。

  3. 绘图逻辑:
    - 使用 matplotlib 分别绘制Sigmoid函数及其导数曲线。
    - 图像展示了Sigmoid函数的S形曲线和其导数的最大值出现在原点附近。

图像解读:

  • Sigmoid函数曲线呈现出典型的“S”形状,输出范围在(0, 1)之间。
  • 导数曲线呈现钟形分布,最大值出现在 $ z = 0 $ 附近,说明在该区域梯度最大,模型参数更新最快。
  • 随着 $ |z| $ 增大,导数逐渐趋近于0,这可能导致梯度消失问题,尤其是在深层网络中。

2.2 线性模型的数学表达

线性模型是逻辑回归的基础组件之一。它通过将输入特征与权重进行线性组合,生成一个实数值,作为Sigmoid函数的输入。

2.2.1 输入特征与权重的线性组合

假设我们有 $ n $ 个输入特征 $ x_1, x_2, …, x_n $,以及对应的权重 $ w_1, w_2, …, w_n $,加上一个偏置项 $ b $,线性模型的输出可以表示为:

z = w_1x_1 + w_2x_2 + \cdots + w_nx_n + b = \mathbf{w}^T \mathbf{x} + b

其中:
- $ \mathbf{x} \in \mathbb{R}^{n \times 1} $ 是特征向量;
- $ \mathbf{w} \in \mathbb{R}^{n \times 1} $ 是权重向量;
- $ b \in \mathbb{R} $ 是偏置项;
- $ \mathbf{w}^T \mathbf{x} $ 表示向量的点积。

代码示例:线性模型的实现

import numpy as np

def linear_model(X, w, b):
    """
    线性模型:z = w.T * X + b
    :param X: 特征矩阵 (n_samples, n_features)
    :param w: 权重向量 (n_features, 1)
    :param b: 偏置项 (1, )
    :return: 输出 z (n_samples, 1)
    """
    return np.dot(X, w) + b

# 示例数据
X = np.array([[1, 2], [3, 4], [5, 6]])
w = np.array([[0.1], [0.2]])
b = 0.5

# 计算输出
z = linear_model(X, w, b)
print("线性模型输出 z:\n", z)

代码逻辑分析:

  1. 参数说明:
    - X 是一个形状为 (n_samples, n_features) 的二维数组,表示多个样本的特征。
    - w 是权重向量,形状为 (n_features, 1)
    - b 是标量偏置项。

  2. 实现逻辑:
    - 使用 np.dot(X, w) 实现矩阵乘法,得到每个样本的加权和。
    - 加上偏置 b ,得到最终的线性输出 z

  3. 输出结果:
    - 对于输入的三个样本,线性模型分别计算出对应的输出值。

结果示例:

线性模型输出 z:
 [[0.5 + 0.1*1 + 0.2*2 = 0.5 + 0.1 + 0.4 = 1.0]
 [0.5 + 0.1*3 + 0.2*4 = 0.5 + 0.3 + 0.8 = 1.6]
 [0.5 + 0.1*5 + 0.2*6 = 0.5 + 0.5 + 1.2 = 2.2]]

2.2.2 决策边界的概念与几何意义

在线性分类模型中,决策边界(Decision Boundary)是将不同类别样本分开的超平面。对于二分类问题,决策边界可以表示为:

\mathbf{w}^T \mathbf{x} + b = 0

当模型输出 $ z = \mathbf{w}^T \mathbf{x} + b > 0 $ 时,样本被预测为正类;反之,则为负类。

二维空间中的决策边界可视化:

import matplotlib.pyplot as plt
import numpy as np

# 权重和偏置
w = np.array([1, -1])
b = 0

# 生成网格数据
x1 = np.linspace(-5, 5, 100)
x2 = -(w[0] * x1 + b) / w[1]

# 绘图
plt.figure(figsize=(6,6))
plt.plot(x1, x2, color='red', label='Decision Boundary')
plt.axhline(0, color='black',linewidth=0.5)
plt.axvline(0, color='black',linewidth=0.5)
plt.grid(True)
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('Linear Decision Boundary in 2D')
plt.legend()
plt.show()

流程图说明:

graph LR
    A[输入特征 x1, x2] --> B[计算线性组合 w1*x1 + w2*x2 + b]
    B --> C{判断组合结果是否大于等于0}
    C -->|是| D[预测为正类]
    C -->|否| E[预测为负类]

该流程图清晰地展示了线性模型如何通过决策边界对输入样本进行分类。

2.3 线性模型与Sigmoid函数的结合

将线性模型的输出 $ z $ 作为Sigmoid函数的输入,我们便可以将线性输出转化为概率值,进而实现对类别的预测。

2.3.1 概率解释与分类预测逻辑

Sigmoid函数将线性输出映射为概率值:

P(y=1 | \mathbf{x}) = \sigma(z) = \frac{1}{1 + e^{-(\mathbf{w}^T \mathbf{x} + b)}}

该公式表示在给定特征 $ \mathbf{x} $ 的条件下,样本属于正类的概率。我们通常设定一个阈值(如0.5),当输出概率大于等于该阈值时,预测为正类;否则为负类。

代码示例:逻辑回归预测函数

def predict(X, w, b, threshold=0.5):
    """
    逻辑回归预测函数
    :param X: 特征矩阵 (n_samples, n_features)
    :param w: 权重向量 (n_features, 1)
    :param b: 偏置项 (1, )
    :param threshold: 分类阈值
    :return: 预测类别 (0 或 1)
    """
    z = linear_model(X, w, b)
    probabilities = sigmoid(z)
    predictions = (probabilities >= threshold).astype(int)
    return predictions

# 示例预测
X_test = np.array([[2, 3], [4, 5]])
w_test = np.array([[0.1], [0.2]])
b_test = 0.5

pred = predict(X_test, w_test, b_test)
print("预测结果:", pred)

代码分析:

  • 该函数首先调用线性模型计算 $ z $,然后使用Sigmoid函数将其转换为概率值。
  • 最后通过设定阈值(默认0.5)进行分类判断。
  • 输出结果为类别标签数组(0或1)。

2.3.2 模型输出的阈值判断机制

阈值判断机制决定了模型最终的分类决策。不同阈值会对分类结果产生显著影响。例如:

阈值 分类标准 适用场景
0.5 概率 ≥ 0.5 → 正类 默认设置,平衡精度与召回
0.3 概率 ≥ 0.3 → 正类 关注召回率,如医疗诊断
0.7 概率 ≥ 0.7 → 正类 关注精确率,如垃圾邮件过滤

图表展示不同阈值下的分类效果:

import matplotlib.pyplot as plt

thresholds = [0.3, 0.5, 0.7]
probabilities = np.linspace(0, 1, 100)

plt.figure(figsize=(10, 4))
for t in thresholds:
    decision = (probabilities >= t).astype(int)
    plt.plot(probabilities, decision + t * 0.05, label=f'Threshold={t}')

plt.xlabel('Probability')
plt.ylabel('Decision (0 or 1)')
plt.title('Threshold-based Classification')
plt.legend()
plt.grid(True)
plt.show()

该图展示了不同阈值下模型的分类边界,直观体现了阈值对分类结果的影响。

本章从Sigmoid函数的数学特性出发,逐步推导了线性模型的数学表达式,并将其与Sigmoid函数结合,形成了完整的逻辑回归分类模型。通过对阈值判断机制的讨论,我们进一步理解了模型如何从概率输出转化为实际类别预测。这些内容为后续章节中损失函数的设计与参数优化奠定了理论基础。

3. 损失函数与参数优化方法

逻辑回归的模型性能在很大程度上依赖于损失函数的设计与优化策略的选择。本章将深入剖析逻辑回归中对数似然损失函数的构建逻辑,探讨参数优化方法的数学原理,并结合实际应用场景,分析不同优化策略(如梯度上升法、随机梯度下降、L1/L2正则化)的优劣与实现细节。

3.1 对数似然损失函数的数学推导

在逻辑回归中,损失函数的设计目标是最大化模型对训练数据的似然估计。为此,我们采用对数似然函数作为损失函数的基础,并通过数学推导将其转化为一个便于优化的形式。

3.1.1 基于最大似然估计的损失函数构建

假设我们有训练数据集 $ D = { (x^{(1)}, y^{(1)}), (x^{(2)}, y^{(2)}), \dots, (x^{(n)}, y^{(n)}) } $,其中 $ x^{(i)} \in \mathbb{R}^d $ 是输入特征向量,$ y^{(i)} \in {0, 1} $ 是类别标签。逻辑回归模型的输出是一个概率值,表示输入样本属于类别 1 的概率:

P(y=1|x; \theta) = h_\theta(x) = \frac{1}{1 + e^{-\theta^T x}}

对应的,类别为 0 的概率为:

P(y=0|x; \theta) = 1 - h_\theta(x)

因此,对于每一个样本 $ (x^{(i)}, y^{(i)}) $,其似然函数可以表示为:

L(\theta) = \prod_{i=1}^n [h_\theta(x^{(i)})]^{y^{(i)}} [1 - h_\theta(x^{(i)})]^{1 - y^{(i)}}

为了便于优化,通常对似然函数取对数,得到对数似然函数:

\ell(\theta) = \log L(\theta) = \sum_{i=1}^n \left[ y^{(i)} \log h_\theta(x^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(x^{(i)})) \right]

由于我们希望最小化损失函数,因此将负的对数似然作为损失函数:

J(\theta) = -\frac{1}{n} \sum_{i=1}^n \left[ y^{(i)} \log h_\theta(x^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(x^{(i)})) \right]

该损失函数也被称为 交叉熵损失函数(Cross-Entropy Loss) ,是逻辑回归中最常用的损失函数形式。

3.1.2 损失函数的简化与向量化表达

在实际编程实现中,我们希望将损失函数表达为向量形式以提高计算效率。设:

  • $ X \in \mathbb{R}^{n \times d} $:输入特征矩阵,每行一个样本;
  • $ y \in \mathbb{R}^{n \times 1} $:标签向量;
  • $ \theta \in \mathbb{R}^{d \times 1} $:参数向量。

模型输出的预测概率向量为:

\hat{y} = h_\theta(X) = \frac{1}{1 + e^{-X\theta}}

则损失函数可以表示为:

J(\theta) = -\frac{1}{n} \left( y^T \log \hat{y} + (1 - y)^T \log(1 - \hat{y}) \right)

这一向量化表达方式在 NumPy 或 PyTorch 等库中可以高效实现,避免了使用循环结构。

示例代码:交叉熵损失函数的实现
import numpy as np

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

def compute_loss(X, y, theta):
    m = len(y)
    h = sigmoid(X @ theta)
    epsilon = 1e-15  # 防止log(0)溢出
    h = np.clip(h, epsilon, 1 - epsilon)  # 限制概率范围
    loss = -(1/m) * (y.T @ np.log(h) + (1 - y).T @ np.log(1 - h))
    return loss
代码解析:
  • sigmoid(z) :计算Sigmoid函数,用于将线性输出映射为概率值。
  • h = sigmoid(X @ theta) :计算每个样本的预测概率。
  • np.clip() :防止取对数时因数值下溢导致错误。
  • loss :根据交叉熵公式计算损失值。

3.2 梯度上升法的基本原理

在逻辑回归中,我们通过优化算法不断调整参数 $ \theta $,以最小化损失函数 $ J(\theta) $。梯度下降法是一种常用的优化方法,其核心思想是沿着损失函数的负梯度方向更新参数。

3.2.1 参数更新的数学表达式

为了最小化损失函数 $ J(\theta) $,梯度下降法的参数更新规则如下:

\theta_j := \theta_j - \alpha \frac{\partial J(\theta)}{\partial \theta_j}

其中:

  • $ \alpha $ 是学习率(Learning Rate),控制参数更新的步长;
  • $ \frac{\partial J(\theta)}{\partial \theta_j} $ 是损失函数对参数 $ \theta_j $ 的偏导数。

对于逻辑回归的交叉熵损失函数,其梯度表达式为:

\nabla_\theta J(\theta) = \frac{1}{n} X^T (h_\theta(X) - y)

因此,参数更新可以写成:

\theta := \theta - \alpha \cdot \frac{1}{n} X^T (h_\theta(X) - y)

3.2.2 学习率的选择与收敛性分析

学习率 $ \alpha $ 是梯度下降法中最重要的超参数之一。其选择直接影响算法的收敛速度与稳定性:

学习率大小 收敛情况 风险
过小 收敛慢 需要更多迭代
过大 振荡或发散 无法收敛
适中 稳定收敛 最佳选择

在实际应用中,建议采用学习率衰减策略,例如:

\alpha_t = \frac{\alpha_0}{1 + \gamma t}

其中 $ t $ 是迭代次数,$ \gamma $ 是衰减速率。

示例代码:梯度下降更新逻辑
def gradient_descent(X, y, theta, alpha, num_iters):
    m = len(y)
    loss_history = []

    for i in range(num_iters):
        h = sigmoid(X @ theta)
        gradient = (1/m) * X.T @ (h - y)
        theta -= alpha * gradient
        loss = compute_loss(X, y, theta)
        loss_history.append(loss)

        if i % 100 == 0:
            print(f"Iteration {i}: Loss = {loss[0]:.6f}")
    return theta, loss_history
代码解析:
  • gradient :计算损失函数关于参数的梯度;
  • theta -= alpha * gradient :参数更新;
  • loss_history :记录每轮迭代的损失值,用于后续可视化分析。

3.3 随机梯度下降(SGD)优化方法

当数据集规模较大时,传统的批量梯度下降(Batch Gradient Descent)计算代价较高。随机梯度下降(Stochastic Gradient Descent, SGD)通过每次仅使用一个样本进行参数更新,显著降低了计算复杂度。

3.3.1 批量梯度下降与SGD的对比

项目 批量梯度下降 随机梯度下降(SGD)
每次更新使用样本数 全部样本 单个样本
收敛速度 稳定,但较慢 快速但有波动
计算开销
适用场景 小规模数据集 大规模数据集

3.3.2 SGD在大规模数据中的优势与实现策略

SGD 的核心思想是每次从训练集中随机选取一个样本进行梯度计算与参数更新。其更新公式为:

\theta_j := \theta_j - \alpha \cdot (h_\theta(x^{(i)}) - y^{(i)}) \cdot x_j^{(i)}

实现时,可以采用“洗牌”(shuffle)策略以避免样本顺序对训练过程的影响。

示例代码:SGD 实现逻辑
def stochastic_gradient_descent(X, y, theta, alpha, num_epochs):
    m = len(y)
    loss_history = []

    for epoch in range(num_epochs):
        for i in range(m):
            # 随机选择一个样本
            idx = np.random.randint(m)
            xi = X[idx:idx+1]
            yi = y[idx:idx+1]

            # 预测与梯度计算
            h = sigmoid(xi @ theta)
            gradient = xi.T @ (h - yi)

            # 参数更新
            theta -= alpha * gradient

        # 每个epoch计算一次损失
        loss = compute_loss(X, y, theta)
        loss_history.append(loss)

        if epoch % 10 == 0:
            print(f"Epoch {epoch}: Loss = {loss[0]:.6f}")
    return theta, loss_history
代码解析:
  • np.random.randint(m) :每次随机选择一个样本;
  • xi @ theta :单样本预测;
  • xi.T @ (h - yi) :单样本梯度;
  • theta -= alpha * gradient :参数更新;
  • loss_history :记录每个 epoch 的损失值。

3.4 L1/L2正则化技术的应用

正则化是防止模型过拟合的重要手段。逻辑回归中常用的正则化方法包括 L1 正则化(Lasso)和 L2 正则化(Ridge),它们通过在损失函数中添加惩罚项来限制参数的大小。

3.4.1 正则化的数学表达与作用机制

正则化后的损失函数形式如下:

  • L1 正则化
    $$
    J(\theta) = -\frac{1}{n} \sum_{i=1}^n \left[ y^{(i)} \log h_\theta(x^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(x^{(i)})) \right] + \lambda \sum_{j=1}^d |\theta_j|
    $$

  • L2 正则化
    $$
    J(\theta) = -\frac{1}{n} \sum_{i=1}^n \left[ y^{(i)} \log h_\theta(x^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(x^{(i)})) \right] + \lambda \sum_{j=1}^d \theta_j^2
    $$

其中 $ \lambda $ 是正则化系数,控制正则项对损失函数的影响程度。

3.4.2 L1正则化与特征选择

L1 正则化倾向于产生稀疏的参数向量,即某些参数会被压缩为零,从而实现特征选择功能。这在高维特征空间中非常有用,有助于提升模型的可解释性。

3.4.3 L2正则化与模型泛化能力提升

L2 正则化通过对参数的平方进行惩罚,限制参数值的大小,从而减少模型的方差,提高泛化能力。它适用于特征之间存在相关性的情况,能够缓解过拟合现象。

示例代码:L2正则化逻辑实现
def compute_loss_with_l2(X, y, theta, lambda_):
    m = len(y)
    h = sigmoid(X @ theta)
    epsilon = 1e-15
    h = np.clip(h, epsilon, 1 - epsilon)
    loss = -(1/m) * (y.T @ np.log(h) + (1 - y).T @ np.log(1 - h))
    l2_penalty = (lambda_ / (2*m)) * np.sum(theta[1:] ** 2)  # 不惩罚偏置项
    total_loss = loss + l2_penalty
    return total_loss
代码解析:
  • l2_penalty :L2 正则化项,仅对权重参数进行惩罚,不包含偏置项;
  • total_loss :原始损失与正则项之和;
  • lambda_ :正则化强度参数。
流程图:L2正则化训练流程
graph TD
    A[开始] --> B[初始化参数]
    B --> C[前向传播计算预测值]
    C --> D[计算损失函数]
    D --> E[添加L2正则化项]
    E --> F[反向传播计算梯度]
    F --> G[参数更新]
    G --> H{是否收敛?}
    H -- 否 --> C
    H -- 是 --> I[结束]

4. 逻辑回归模型的代码实现与结构解析

在本章中,我们将基于前几章对逻辑回归算法的数学原理和优化方法的理解,深入探讨如何在 Python 中实现一个完整的逻辑回归模型。通过代码实现,不仅能够加深对算法原理的理解,还能为后续的模型调优、问题诊断与工程化部署提供坚实基础。

本章将从数据预处理、模型初始化、预测函数设计到整体代码结构进行系统性讲解,并通过实际代码演示和逻辑分析,帮助读者掌握构建逻辑回归模型的全流程。

4.1 数据预处理流程设计

数据预处理是机器学习建模的第一步,也是影响模型性能的重要因素。在逻辑回归中,数据预处理主要包括数据读取、缺失值处理、特征标准化与归一化等环节。

4.1.1 数据读取与格式解析

逻辑回归模型通常处理结构化数据,如 CSV 文件、Excel 表格或数据库中的数据。使用 Python 的 pandas 库可以方便地完成数据读取与初步解析。

import pandas as pd

# 读取CSV数据
df = pd.read_csv('data.csv')

# 查看前5行数据
print(df.head())
字段名 含义 数据类型
age 年龄 int
income 年收入 float
label 是否购买产品 int (0/1)

逻辑分析:

  • pd.read_csv 是读取 CSV 文件的标准方法。
  • 数据字段的类型需根据实际数据进行调整,例如年龄字段为整数,收入为浮点数。
  • label 字段为模型的目标变量,通常为二分类(0 或 1)。

4.1.2 缺失值处理策略

缺失值是数据中常见的问题,处理不当会影响模型训练的稳定性。常见的处理方式包括删除缺失值、填充均值/中位数或使用插值法。

# 检查缺失值
print(df.isnull().sum())

# 填充缺失值为列均值
df.fillna(df.mean(), inplace=True)

逻辑分析:

  • isnull().sum() 统计每列的缺失值数量。
  • fillna(df.mean()) 用每列的均值填充缺失值,适用于数值型数据。
  • 若缺失比例较高,可考虑删除相关样本或使用更复杂的填充策略(如 KNN 填充)。

4.1.3 特征标准化与归一化方法

特征尺度差异会显著影响逻辑回归中梯度下降的收敛速度,因此通常需要对特征进行标准化或归一化。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[['age', 'income']])
方法 特点 适用场景
标准化 减去均值,除以标准差 特征分布近似高斯时适用
归一化 缩放到 [0, 1] 区间 特征分布不均或有异常值时适用

逻辑分析:

  • StandardScaler 是 sklearn 中常用的标准化方法。
  • 标准化后,不同特征具有相同的尺度,有利于优化器快速收敛。
  • 对于稀疏数据或某些特定模型(如树模型),可能不需要标准化。

4.2 模型初始化方法选择

在训练逻辑回归模型之前,需要对模型参数(权重)进行初始化。初始化方式会影响模型的训练速度和最终性能。

4.2.1 零初始化与随机初始化对比

常见的参数初始化方法有零初始化和随机初始化。

import numpy as np

# 零初始化
weights_zero = np.zeros(shape=(X_scaled.shape[1], 1))

# 随机初始化
weights_random = np.random.randn(X_scaled.shape[1], 1) * 0.01
初始化方式 特点 优缺点分析
零初始化 所有参数初始化为 0 易陷入对称性问题,收敛慢
随机初始化 参数初始化为小随机数 打破对称性,收敛更快

逻辑分析:

  • 零初始化在逻辑回归中可能导致所有特征的梯度更新方向一致,影响收敛效率。
  • 随机初始化有助于打破对称性,尤其在神经网络中更为重要,在逻辑回归中也建议使用。

4.2.2 初始化对收敛速度的影响分析

初始化值的大小直接影响梯度下降的收敛速度。过大的初始值可能导致梯度爆炸,而过小则可能导致收敛缓慢。

# 使用不同初始化值进行训练(伪代码示意)
learning_rate = 0.01
for epoch in range(100):
    weights = weights - learning_rate * gradient

逻辑分析:

  • 初始权重值越大,初始梯度可能越强,但容易震荡。
  • 初始值越小,梯度变化平缓,收敛更稳定。
  • 在实际代码中,可以使用 np.random.randn 并乘以一个小数(如 0.01)控制初始范围。

4.3 预测函数的设计与分类判断

预测函数是逻辑回归模型的核心组成部分之一,它将线性组合结果通过 Sigmoid 函数映射为概率输出,并根据阈值进行分类。

4.3.1 概率输出与类别映射逻辑

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

def predict_prob(X, weights):
    z = np.dot(X, weights)
    return sigmoid(z)

def predict(X, weights, threshold=0.5):
    prob = predict_prob(X, weights)
    return (prob >= threshold).astype(int)

逻辑分析:

  • sigmoid 函数将线性输出映射到 [0, 1] 区间,表示属于类别 1 的概率。
  • predict_prob 计算每个样本的预测概率。
  • predict 函数根据设定的阈值(默认 0.5)将概率转换为 0 或 1 的分类结果。

4.3.2 预测结果的评估指标初步介绍

在模型预测后,需要评估其性能。常见的评估指标包括准确率、精确率、召回率、F1 分数等。

指标 公式表达 含义
准确率 (TP + TN) / (TP + FP + TN + FN) 正确预测占总样本比例
精确率 TP / (TP + FP) 预测为正类中实际为正的比例
召回率 TP / (TP + FN) 实际正类中被正确预测的比例
F1 分数 2 * (精确率 * 召回率) / (精确率 + 召回率) 精确率与召回率的调和平均

逻辑分析:

  • 准确率适用于类别平衡的场景。
  • 在类别不平衡时,应优先关注精确率和召回率。
  • F1 分数是两者的综合衡量,适用于多数分类问题。

4.4 逻辑回归完整代码结构解析

在本节中,我们将展示一个完整的逻辑回归代码结构,并对其模块划分、向量化操作、执行流程进行详细解析。

4.4.1 模块划分与函数设计

逻辑回归代码通常包括以下几个模块:

class LogisticRegression:
    def __init__(self, learning_rate=0.01, n_iter=1000, threshold=0.5):
        self.learning_rate = learning_rate
        self.n_iter = n_iter
        self.threshold = threshold
        self.weights = None

    def fit(self, X, y):
        m, n = X.shape
        self.weights = np.zeros((n, 1))
        for _ in range(self.n_iter):
            y_pred = self._sigmoid(np.dot(X, self.weights))
            gradient = np.dot(X.T, (y_pred - y)) / m
            self.weights -= self.learning_rate * gradient

    def predict_prob(self, X):
        return self._sigmoid(np.dot(X, self.weights))

    def predict(self, X):
        return (self.predict_prob(X) >= self.threshold).astype(int)

    def _sigmoid(self, z):
        return 1 / (1 + np.exp(-z))

逻辑分析:

  • __init__ 初始化学习率、迭代次数和分类阈值。
  • fit 方法执行模型训练,采用梯度下降更新权重。
  • predict_prob 返回预测概率。
  • predict 返回最终分类结果。
  • _sigmoid 是私有方法,用于封装 Sigmoid 函数。

4.4.2 向量化操作的实现技巧

向量化操作是提升模型训练效率的关键手段,避免使用 for 循环,改用 NumPy 矩阵运算。

# 向量化实现梯度下降
gradient = np.dot(X.T, (y_pred - y)) / m

逻辑分析:

  • np.dot(X.T, error) 实现了对每个特征的梯度计算。
  • 向量化使得计算更加简洁高效,适合大规模数据处理。
  • 避免 Python 原生 for 循环,提升执行速度。

4.4.3 代码执行流程与调试技巧

完整的代码执行流程如下:

graph TD
    A[数据读取] --> B[缺失值处理]
    B --> C[特征标准化]
    C --> D[模型初始化]
    D --> E[训练过程]
    E --> F[预测输出]
    F --> G[性能评估]

调试技巧:

  • 使用 print 输出每轮迭代的损失值,观察收敛趋势。
  • 添加断言( assert )检查输入数据维度是否匹配。
  • 使用 pdb 进行逐行调试,分析参数更新是否合理。
# 添加损失值输出用于调试
loss = -np.mean(y * np.log(y_pred) + (1 - y) * np.log(1 - y_pred))
print(f"Iteration {_}, Loss: {loss}")

逻辑分析:

  • 损失值下降趋势可用于判断模型是否收敛。
  • 若损失值不下降,可能是学习率设置不当或数据未标准化。
  • 调试时关注 weights 是否更新合理,梯度是否为 NaN。

本章从数据预处理、模型初始化、预测函数设计到完整代码结构,系统性地展示了逻辑回归模型的实现过程。通过实际代码与流程图的结合,帮助读者掌握模型构建的每个细节,并为后续模型调优与工程部署打下坚实基础。

5. 逻辑回归的实战应用与问题诊断

5.1 逻辑回归在实际分类任务中的应用

5.1.1 应用场景与数据集描述

逻辑回归广泛应用于各类二分类问题,如金融领域的信用评分、医疗诊断中的疾病预测、电商中的用户购买预测等。为了展示逻辑回归的实际应用,我们以经典的鸢尾花数据集(Iris)中的一类二分类任务为例进行说明。

我们选取Iris数据集中两个类别(Setosa 和 Versicolor),并使用两个特征(萼片长度和萼片宽度)进行二分类建模。通过 sklearn.datasets 库加载数据集,并进行可视化展示。

from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data[(iris.target == 0) | (iris.target == 1), :2]  # 取前两个特征
y = iris.target[(iris.target == 0) | (iris.target == 1)]

# 数据可视化
plt.scatter(X[y == 0, 0], X[y == 0, 1], color='red', label='Setosa')
plt.scatter(X[y == 1, 0], X[y == 1, 1], color='blue', label='Versicolor')
plt.xlabel('Sepal Length')
plt.ylabel('Sepal Width')
plt.legend()
plt.title('Dataset Visualization')
plt.show()

参数说明
- X :输入特征,形状为 (n_samples, n_features)
- y :目标变量,二分类标签(0或1)
- matplotlib :用于可视化数据分布

5.1.2 模型训练与预测流程演示

我们使用 sklearn.linear_model.LogisticRegression 模块来训练逻辑回归模型,并进行预测。以下是完整的训练与预测流程:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 模型初始化与训练
model = LogisticRegression()
model.fit(X_train, y_train)

# 模型预测
y_pred = model.predict(X_test)

# 模型评估
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy:.2f}")

执行逻辑说明
- 使用 train_test_split 将数据划分为训练集与测试集(80%训练,20%测试)
- 初始化逻辑回归模型并使用训练集进行拟合
- 使用测试集进行预测,并通过准确率评估模型性能

5.2 过拟合与欠拟合的识别与应对

5.2.1 过拟合与欠拟合的表现特征

模型状态 训练集表现 测试集表现 特征表现
正常拟合 高准确率 高准确率 模型能泛化
过拟合 高准确率 低准确率 模型过于复杂,记忆了噪声
欠拟合 低准确率 低准确率 模型太简单,无法捕捉规律

在逻辑回归中,过拟合通常表现为模型在训练集上表现很好,但在测试集上显著下降。欠拟合则表现为在训练集和测试集上都表现较差。

5.2.2 学习曲线与验证曲线的使用

我们可以使用 sklearn.model_selection 中的 learning_curve validation_curve 来辅助诊断过拟合/欠拟合问题。

from sklearn.model_selection import learning_curve
import numpy as np

# 生成学习曲线
train_sizes, train_scores, test_scores = learning_curve(
    estimator=model,
    X=X, y=y,
    train_sizes=np.linspace(0.1, 1.0, 10),
    cv=5,
    scoring='accuracy'
)

# 计算平均得分
train_mean = np.mean(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)

# 绘制学习曲线
plt.plot(train_sizes, train_mean, color='blue', label='Training Accuracy')
plt.plot(train_sizes, test_mean, color='green', label='Validation Accuracy')
plt.xlabel('Training Set Size')
plt.ylabel('Accuracy')
plt.title('Learning Curve')
plt.legend()
plt.grid()
plt.show()

参数说明
- train_sizes :不同训练集大小的取值
- train_scores test_scores :对应训练集和验证集的准确率
- cv=5 :5折交叉验证

5.3 模型性能评估与调优策略

5.3.1 准确率、精确率、召回率与F1分数

在分类任务中,除了准确率,我们还需关注其他评估指标,尤其是数据不平衡的情况下。以下是各指标的定义:

指标 定义 公式
准确率 正确预测占总样本的比例 $ \frac{TP + TN}{TP + TN + FP + FN} $
精确率 预测为正类中真实为正的比例 $ \frac{TP}{TP + FP} $
召回率 真实正类中被正确预测的比例 $ \frac{TP}{TP + FN} $
F1分数 精确率与召回率的调和平均 $ 2 \times \frac{Precision \times Recall}{Precision + Recall} $
from sklearn.metrics import classification_report

# 打印详细分类报告
print(classification_report(y_test, y_pred))

输出示例:

              precision    recall  f1-score   support

           0       1.00      1.00      1.00        10
           1       1.00      1.00      1.00        10

    accuracy                           1.00        20
   macro avg       1.00      1.00      1.00        20
weighted avg       1.00      1.00      1.00        20

5.3.2 ROC曲线与AUC值的计算与意义

ROC曲线展示了不同阈值下模型的真阳性率(TPR)与假阳性率(FPR)之间的关系,AUC值则是曲线下面积,用来衡量模型整体性能。

from sklearn.metrics import roc_curve, auc

# 获取预测概率
y_score = model.predict_proba(X_test)[:, 1]

# 计算FPR、TPR、阈值
fpr, tpr, thresholds = roc_curve(y_test, y_score)
roc_auc = auc(fpr, tpr)

# 绘制ROC曲线
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()

5.4 实战中的常见问题与解决方案

5.4.1 数据不平衡问题处理

当数据集中某一类样本数量远多于另一类时,模型容易偏向多数类。例如在欺诈检测中,欺诈样本可能仅占1%,此时准确率并不能反映模型真实性能。

解决方案
- 使用 class_weight='balanced' 自动调整类别权重
- 采用过采样或欠采样技术(如SMOTE)
- 使用F1、AUC等对不平衡数据更敏感的指标

# 使用类别权重平衡
model_balanced = LogisticRegression(class_weight='balanced')
model_balanced.fit(X_train, y_train)

5.4.2 特征共线性对模型的影响

特征共线性指的是特征之间存在高度相关性,这可能导致模型参数估计不稳定,甚至影响模型收敛。

检测方法
- 使用相关系数矩阵分析特征间相关性
- 使用方差膨胀因子(VIF)评估多重共线性

解决方法
- 移除高度相关特征
- 使用正则化(如L1/L2正则化)缓解影响

5.4.3 收敛失败与优化策略调整

在训练过程中,有时模型无法收敛,提示“ConvergenceWarning”。常见原因包括特征未标准化、学习率设置不当或迭代次数不足。

解决方案
- 对特征进行标准化处理(如StandardScaler)
- 增加 max_iter 参数值
- 调整正则化强度(C参数)

from sklearn.preprocessing import StandardScaler

# 特征标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 调整最大迭代次数
model = LogisticRegression(max_iter=1000)
model.fit(X_train_scaled, y_train)

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:逻辑回归是机器学习中基础且重要的二分类算法,通过线性模型结合Sigmoid函数实现概率预测。本文基于Coursera华盛顿大学机器学习课程的编程实践,详细解析逻辑回归的代码实现,涵盖数据预处理、模型初始化、梯度上升优化、损失函数计算与预测函数设计等关键步骤。通过本项目,学习者可深入理解逻辑回归原理,并掌握其在Python中的实际编程技巧,同时了解过拟合处理及优化算法的扩展应用。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐