04 逻辑回归:从分类问题到概率预测

章节概览:逻辑回归

从分类问题到概率预测

本章学习路线图

我们将通过六个部分,系统地掌握逻辑回归。

本章学习路线图 一个包含六个节点的水平流程图,展示了本章的学习路径,每个节点下有简短的描述。 1 问题设定 为何需要它? 2 模型形式 数学表达 3 模型训练 如何找到最优参数? 4 模型解读 模型告诉我们什么? 5 代码实践 Python 实现 6 模型拓展 超越二分类

Part 1: 问题设定

为何线性回归不足以应对分类任务?

案例引入:银行的核心风控业务

  • 银行的核心功能: 向个人和企业发放贷款是现代银行体系的基石。
  • 风险与收益的权衡: 在审批贷款时,银行面临一个核心挑战——如何准确预测申请人的还款能力?
  • 数据驱动决策: 在金融科技时代,我们不再依赖主观判断,而是利用机器学习模型,通过分析申请人的数据来创建一套精准的贷款违约预测系统。

商业问题可视化

银行的每日决策:对于每一位贷款申请人,我们应该批准(Approve)还是拒绝(Reject)?

银行贷款决策流程示意图 一个申请人数据进入银行决策系统,系统最终输出批准或拒绝的决策。 申请人数据 (收入, 负债, 年龄...) 银行决策系统 批准贷款 拒绝贷款

我们的任务:构建一个贷款违约预测模型

我们将利用一个包含已审批贷款表现的数据集来构建模型。

特征 (X) 描述 对违约的影响(直觉)
年收入 (Income) 申请人的年度总收入 越高,违约概率越低
负债收入比 (DTI) 每月债务支出占总收入的比例 越高,违约概率越高
工作年限 (Emp. Year) 申请人在当前工作的年限 越长,违约概率越低

目标变量 (y): 贷款是否违约。这是一个二元变量(Binary Variable)。

  • y = 1: 贷款已违约 (正类, Positive Class)
  • y = 0: 贷款未违约 (负类, Negative Class)

模型的核心任务是学习特征与结果之间的关系

我们的机器学习模型就像一个函数 \(f\),它的任务是学习输入(申请人的特征 \(\mathbf{x}\))与输出(是否违约 \(y\))之间的映射关系。

机器学习模型作为函数映射的示意图 展示了特征向量X通过机器学习模型f,映射到预测概率P(y=1)的过程。 输入特征 (申请人数据) x 机器学习模型 f(x) 输出 (预测概率) P(y=1|x)

模型一旦训练完成,就可以输出一个介于0和1之间的概率值。

一个自然的问题:为什么不用线性回归?

我们已经很熟悉线性回归了,它预测的是一个连续的数值。我们能否用它来预测一个01的分类问题呢?

让我们用负债收入比来预测是否违约

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LinearRegression

# --- Mock Data Generation ---
np.random.seed(42)
dti = np.random.uniform(5, 50, 100)
prob_default = 1 / (1 + np.exp(-( -4 + 0.15 * dti)))
is_default = np.random.binomial(1, prob_default)
df = pd.DataFrame({'dti': dti, 'is_default': is_default})
X = df[['dti']]
y = df['is_default']

# --- Fit Linear Regression ---
ols_model = LinearRegression()
ols_model.fit(X, y)
x_fit = np.linspace(0, 60, 100).reshape(-1, 1)
y_fit = ols_model.predict(x_fit)

# --- Visualization ---

fig, ax = plt.subplots(figsize=(10, 6))
sns.scatterplot(data=df, x='dti', y='is_default', ax=ax, s=80, alpha=0.7, label='实际数据 (0=未违约, 1=违约)')
ax.plot(x_fit, y_fit, color='crimson', lw=3, label='OLS 拟合线')

# --- Annotations & Styling ---
ax.axhline(0, color='grey', linestyle='--')
ax.axhline(1, color='grey', linestyle='--')
ax.text(55, 1.05, '概率 > 1', color='red', fontsize=12)
ax.text(2, -0.05, '概率 < 0', color='red', fontsize=12)
ax.set_ylim(-0.2, 1.2)
ax.set_xlim(0, 60)
ax.set_title('线性回归无法将预测值约束在 区间内', fontsize=16)
ax.set_xlabel('负债收入比 (DTI)', fontsize=12)
ax.set_ylabel('预测违约概率 / 实际违约', fontsize=12)
ax.legend()
plt.show()
Figure 1: 线性回归(OLS)直接应用于0/1分类问题的缺陷

缺陷 1:预测值越界

正如上图所示,当负债收入比非常高或非常低时,线性模型的预测值会超出 [0, 1] 的合理区间。

一个大于1或小于0的“概率”在现实中是毫无意义的。

缺陷 2:误差项非正态

线性回归的一个基本假设是误差项 \(\epsilon\) 服从正态分布。但对于0/1的因变量,误差只能取两个值,严重违背了这一假设。

二元变量的误差分布 图表显示,当真实值为1或0时,误差分布在一条直线上,而非正态分布。 当真实值 y = 1 p ε 误差 ε = 1 - p (非正态) 当真实值 y = 0 p ε 误差 ε = 0 - p (非正态)

结论: 线性回归的统计基础在此失效,参数估计和假设检验都不可靠。

我们需要一个新工具

我们需要一个新函数,它能将线性组合 \(z = \mathbf{w}^T\mathbf{x}\) 的输出值(可以是从 \(-\infty\)\(+\infty\) 的任何数)“压缩”到 (0, 1) 区间内。

这个函数需要像一个“概率转换器”。

Part 2: 模型形式

逻辑函数如何优雅地解决问题

解决方案:引入逻辑函数 (Logistic Function)

逻辑函数,也常被称为 Sigmoid 函数,拥有我们所需要的完美特性。它的数学形式如下:

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

其中,\(z\) 就是我们熟悉的线性组合:\(z = w_0 + w_1x_1 + ... + w_kx_k = \mathbf{w}^T\mathbf{x}\)

逻辑回归模型:线性与非线性的结合

我们的逻辑回归模型 \(f(\mathbf{x})\) 就是将线性模型的结果输入到逻辑函数中:

\[ \large{f(\mathbf{x}) = g(\mathbf{w}^T\mathbf{x}) = \frac{1}{1 + e^{-\mathbf{w}^T\mathbf{x}}}} \]

这是一个两步过程: 1. 线性部分: 计算一个得分 \(z = \mathbf{w}^T\mathbf{x}\)。 2. 非线性转换: 用逻辑函数 \(g(z)\) 将得分转换为概率。

逻辑函数能完美地将任意实数映射到 (0, 1) 区间

这个优美的S型曲线是逻辑回归的核心。

import numpy as np
import matplotlib.pyplot as plt

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

z = np.linspace(-10, 10, 200)
g_z = sigmoid(z)


fig, ax = plt.subplots(figsize=(10, 6))

ax.plot(z, g_z, color='dodgerblue', lw=3)

# --- Annotations & Styling ---
ax.axhline(0.0, color='grey', linestyle='--')
ax.axhline(1.0, color='grey', linestyle='--')
ax.axhline(0.5, color='grey', linestyle=':', lw=1)
ax.axvline(0.0, color='grey', linestyle=':', lw=1)

ax.text(8, 0.95, '概率上限 = 1', color='grey', fontsize=14)
ax.text(8, 0.05, '概率下限 = 0', color='grey', fontsize=14)
ax.text(0.2, 0.52, 'g(z) = 0.5', color='black', fontsize=12)
ax.text(5, 0.6, r'$g(z) = \frac{1}{1 + e^{-z}}$', color='dodgerblue', fontsize=18)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title('逻辑函数将 z ∈ (-∞, +∞) 映射到 g(z) ∈ (0, 1)', fontsize=16)
ax.set_xlabel('z (线性组合 wᵀx)', fontsize=12)
ax.set_ylabel('g(z) (预测概率)', fontsize=12)
plt.show()
Figure 2: 逻辑 (Sigmoid) 函数的S型曲线

逻辑函数的关键特性

逻辑函数的三个关键特性 三个并排的卡片,分别通过图标和文字描述了逻辑函数的值域、单调性和中心对称性。 0,1 值域 (0, 1) 完美的概率输出 单调递增 保留线性关系的方向 z=0 p=.5 中心对称 当 z=0 时,概率=0.5

案例图解:逻辑函数如何“弯曲”线性关系

我们来看一个具体的例子,假设经过模型训练,我们得到的线性关系是 \(z = -0.4 + 0.8x\)

import numpy as np
import matplotlib.pyplot as plt

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

x = np.linspace(-10, 10, 200)
z = -0.4 + 0.8 * x
f_x = g(z)


fig, ax = plt.subplots(figsize=(10, 6))

ax.plot(x, z, color='grey', lw=2, linestyle='--', label=r'线性部分: $z = -0.4 + 0.8x$')
ax.plot(x, f_x, color='crimson', lw=3, label=r'逻辑回归输出: $f(x) = g(z)$')

# --- Annotations & Styling ---
ax.axhline(0.0, color='darkgrey', linestyle='-', lw=1)
ax.axhline(1.0, color='darkgrey', linestyle='-', lw=1)
ax.axvline(0.0, color='darkgrey', linestyle='-', lw=1)

x_intersect = 0.5
ax.plot(x_intersect, 0.5, 'bo', markersize=8, zorder=5)
ax.vlines(x_intersect, -4, 0.5, color='blue', linestyle=':', lw=2)
ax.hlines(0.5, -10, x_intersect, color='blue', linestyle=':', lw=2)
ax.text(x_intersect + 0.2, 0.6, f'决策边界: x = {x_intersect:.1f}\n预测概率 = 0.5', color='blue')

ax.set_title('逻辑函数保持了单调性,同时将输出约束在概率区间', fontsize=16)
ax.set_xlabel('特征 x', fontsize=12)
ax.set_ylabel('输出值', fontsize=12)
ax.legend(fontsize=12)
ax.set_ylim(-4, 4)
plt.show()
Figure 3: 线性函数通过逻辑函数转换为概率预测

Part 3: 模型训练

如何找到最优的权重 w

核心问题:如何衡量“好”与“坏”?

我们已经确定了模型的数学形式,但如何找到一组最优的权重向量 w,使得模型的预测最接近真实的标签 y 呢?

这就引出了机器学习的核心概念:代价函数 (Cost Function)

  • 代价函数 \(J(\mathbf{w})\): 衡量模型在整个训练集上预测的平均误差
  • 我们的目标: 找到能使代价函数 \(J(\mathbf{w})\) 最小化的 \(\mathbf{w}\)

\[ \large{\min_{\mathbf{w}} J(\mathbf{w})} \]

统计基础:最大似然估计 (MLE)

理解逻辑回归代价函数的最佳方式是通过最大似然估计 (Maximum Likelihood Estimation, MLE)

  • 核心思想: 我们想要找到一组参数 w,在这组参数下,我们观测到的这个训练数据集(包含所有的 \(\mathbf{x}\)\(y\))出现的概率是最大的。

步骤 1: 单个样本的似然

  • 模型输出: \(f(\mathbf{x}^{(i)})\) 是模型预测第 \(i\) 个样本为正类(y=1)的概率。
  • 概率表示:
    • 如果真实标签 \(y^{(i)} = 1\),该观测出现的概率是 \(f(\mathbf{x}^{(i)})\)
    • 如果真实标签 \(y^{(i)} = 0\),该观测出现的概率是 \(1 - f(\mathbf{x}^{(i)})\)

一个巧妙的统一表达式

我们可以将上述两种情况合并成一个表达式。对于单个样本 \((\mathbf{x}^{(i)}, y^{(i)})\),其发生的概率(即似然)为:

\[ \large{P(y^{(i)}|\mathbf{x}^{(i)}; \mathbf{w}) = [f(\mathbf{x}^{(i)})]^{y^{(i)}} \cdot [1 - f(\mathbf{x}^{(i)})]^{1-y^{(i)}}} \]

  • 验证:
    • \(y^{(i)}=1\) 时, 上式变为 \(f(\mathbf{x}^{(i)})\)
    • \(y^{(i)}=0\) 时, 上式变为 \(1 - f(\mathbf{x}^{(i)})\)

步骤 2: 整个数据集的似然函数

假设我们有 n 个独立的训练样本,那么整个数据集出现的联合概率(总似然函数 \(L(\mathbf{w})\))就是每个样本概率的乘积:

\[ \large{L(\mathbf{w}) = \prod_{i=1}^{n} P(y^{(i)}|\mathbf{x}^{(i)}; \mathbf{w})} \] \[ \large{= \prod_{i=1}^{n} [f(\mathbf{x}^{(i)})]^{y^{(i)}} \cdot [1 - f(\mathbf{x}^{(i)})]^{1-y^{(i)}}} \]

我们的目标是最大化这个 \(L(\mathbf{w})\)

步骤 3: 对数转换简化计算

直接最大化连乘的 \(L(\mathbf{w})\) 很复杂。由于对数函数 log 是单调递增的,最大化 \(L(\mathbf{w})\) 等价于最大化对数似然函数 \(\log(L(\mathbf{w}))\)

对数似然函数 \(l(\mathbf{w})\) 将连乘变为连加: \[ \large{l(\mathbf{w}) = \log L(\mathbf{w}) = \sum_{i=1}^{n} \left[ y^{(i)}\log f(\mathbf{x}^{(i)}) + (1 - y^{(i)})\log(1 - f(\mathbf{x}^{(i)})) \right]} \]

步骤 4: 从最大化似然到最小化代价

在机器学习中,我们习惯于将问题表述为最小化一个代价函数。

最大化 \(l(\mathbf{w})\) 等价于最小化 \(-l(\mathbf{w})\)。我们再除以样本量 n 来取平均值,就得到了逻辑回归的最终代价函数 \(J(\mathbf{w})\)

\[ \large{J(\mathbf{w}) = -\frac{1}{n} \sum_{i=1}^{n} \left[ y^{(i)}\log f(\mathbf{x}^{(i)}) + (1 - y^{(i)})\log(1 - f(\mathbf{x}^{(i)})) \right]} \]

这个函数也被称为对数损失 (Log Loss)交叉熵损失 (Cross-Entropy Loss)

图解损失函数:当真实 y=1

  • 损失变为 \(C = -\log(f(\mathbf{x}))\)
  • 如果模型预测概率 \(f(\mathbf{x}) \to 1\) (预测正确),那么损失 \(-\log(1) \to 0\)
  • 如果模型预测概率 \(f(\mathbf{x}) \to 0\) (预测错误),那么损失 \(-\log(0) \to +\infty\)
当y=1时的对数损失函数曲线 一条从左上角急剧下降到右下角的曲线,表示预测概率接近1时损失趋近于0,接近0时损失趋近于无穷大。 p = f(x) 损失 1 0 Cost = -log(p) 预测错误,巨大惩罚 预测正确,损失小

图解损失函数:当真实 y=0

  • 损失变为 \(C = -\log(1 - f(\mathbf{x}))\)
  • 如果模型预测概率 \(f(\mathbf{x}) \to 0\) (预测正确),那么损失 \(-\log(1) \to 0\)
  • 如果模型预测概率 \(f(\mathbf{x}) \to 1\) (预测错误),那么损失 \(-\log(0) \to +\infty\)
当y=0时的对数损失函数曲线 一条从左下角急剧上升到右上角的曲线,表示预测概率接近0时损失趋近于0,接近1时损失趋近于无穷大。 p = f(x) 损失 1 0 Cost = -log(1 - p) 预测错误,巨大惩罚 预测正确,损失小

结论: 这个损失函数的设计非常合理:预测越准确,损失越小;预测越离谱,损失就越大

最小化代价函数:梯度下降法

我们已经定义了需要最小化的山谷(代价函数 \(J(\mathbf{w})\)),现在需要一个算法来帮助我们走到谷底。这个算法就是梯度下降法 (Gradient Descent)

梯度下降法示意图 一个表示代价函数的二维曲线,一个点从高处沿着曲线的切线方向逐步移动到最低点。 参数 w 代价 J(w) 初始点 最优点

核心思想: 在当前位置,寻找最陡峭的下坡方向,然后迈出一步。重复此过程。

梯度下降的更新规则

  • “最陡峭的下坡方向”是代价函数梯度的负方向 \((-\nabla J(\mathbf{w}))\)
  • “迈出一步”的大小由学习率 (Learning Rate) \(\alpha\) 控制。

对于每一个权重 \(w_j\),同步更新:

\[ \large{w_j := w_j - \alpha \frac{\partial}{\partial w_j} J(\mathbf{w})} \]

这里的核心是计算代价函数对每个权重的偏导数(梯度)。

学习率 \(\alpha\) 的重要性

学习率决定了我们“下山”的步子迈多大。选择合适的学习率至关重要。

不同学习率对梯度下降的影响 三条曲线分别展示了学习率过小、过大和合适的收敛路径。 α 过小:收敛太慢 α 过大:震荡甚至发散 α 合适:高效收敛

逻辑回归代价函数的梯度

经过微积分推导,我们可以得到一个非常简洁和优美的结果:

\[ \large{\frac{\partial}{\partial w_j} J(\mathbf{w}) = \frac{1}{n} \sum_{i=1}^{n} (f(\mathbf{x}^{(i)}) - y^{(i)}) x_j^{(i)}} \]

  • \(f(\mathbf{x}^{(i)}) - y^{(i)}\) 是第 \(i\) 个样本的预测误差
  • \(x_j^{(i)}\) 是第 \(i\) 个样本的第 \(j\) 个特征值。

这个梯度的形式和我们在线性回归中学到的几乎完全一样!

逻辑回归的梯度下降完整算法

  1. 初始化: 随机初始化权重向量 \(\mathbf{w}\) (例如,全零向量)。

  2. 迭代更新: 重复以下步骤直到收敛:

    对于 \(j = 0, 1, ..., k\): \[ \large{w_j := w_j - \alpha \frac{1}{n} \sum_{i=1}^{n} (\frac{1}{1 + e^{-\mathbf{w}^T\mathbf{x}^{(i)}}} - y^{(i)}) x_j^{(i)}} \]

    (注意:\(x_0^{(i)}\) 通常设为1,对应截距项 \(w_0\))

  3. 收敛: 当 \(\mathbf{w}\) 的变化非常小,或代价函数 \(J(\mathbf{w})\) 不再显著下降时,算法停止。

Part 4: 模型应用与解读

模型训练好了,我们能用它做什么?

从概率到决策:决策边界

模型训练好之后,我们如何用它来进行分类决策呢?一个常见的方法是设定一个概率阈值,通常是 0.5

  • 如果模型预测概率 \(f(\mathbf{x}) > 0.5\),我们就分类为正类 (y=1)。
  • 如果模型预测概率 \(f(\mathbf{x}) \le 0.5\),我们就分类为负类 (y=0)。

决策边界 (Decision Boundary) 就是使得预测概率恰好等于 0.5 的点的集合。

决策边界在数学上是一个超平面

我们知道,当 \(f(\mathbf{x}) = 0.5\) 时,逻辑函数的输入 \(z\) 必须为0。 \[ \large{f(\mathbf{x}) = \frac{1}{1 + e^{-\mathbf{w}^T\mathbf{x}}} = 0.5 \implies \mathbf{w}^T\mathbf{x} = 0} \]

因此,决策边界由以下线性方程定义: \[ \large{\mathbf{w}^T\mathbf{x} = w_0 + w_1x_1 + ... + w_kx_k = 0} \]

  • 几何意义:
    • 当只有两个特征 (\(x_1, x_2\)) 时,决策边界是一条直线
    • 当有更多特征时,它是一个超平面 (Hyperplane)

决策边界的可视化

决策边界(黑线)将特征空间一分为二。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression

# --- Mock Data Generation ---
X, y = make_classification(n_samples=100, n_features=2, n_informative=2, n_redundant=0,
                           n_clusters_per_class=1, flip_y=0.1, random_state=1)

# --- Fit Model ---
model = LogisticRegression(solver='liblinear')
model.fit(X, y)

# --- Create meshgrid for plotting ---
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02))
Z = model.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]
Z = Z.reshape(xx.shape)

# --- Visualization ---

fig, ax = plt.subplots(figsize=(10, 8))
contour = ax.contourf(xx, yy, Z, cmap='RdBu_r', alpha=0.6)
fig.colorbar(contour, ax=ax, label='预测为正类 (y=1) 的概率')
ax.contour(xx, yy, Z, levels=[0.5], colors='black', linewidths=2)
scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu_r', edgecolors='k', s=80)
handles, _ = scatter.legend_elements(prop='colors')
ax.legend(handles, ['负类 (y=0)', '正类 (y=1)'])
ax.set_title('决策边界 (wᵀx=0) 分割了预测区域', fontsize=16)
ax.set_xlabel('特征 X₁', fontsize=12)
ax.set_ylabel('特征 X₂', fontsize=12)
ax.text(-2, -2.5, '预测为 负类 (p < 0.5)', ha='center', fontsize=12, color='white')
ax.text(1.5, 2, '预测为 正类 (p > 0.5)', ha='center', fontsize=12, color='white')
plt.show()
Figure 4: 一个二维特征空间中的线性决策边界

解读模型:系数的意义

逻辑回归不仅能做预测,它强大的解释性也是其在经济金融领域广受欢迎的重要原因。

  • 系数的符号: 如果 \(w_j\) 为正,说明特征 \(x_j\) 的增加会提高事件发生的概率。反之则降低
  • 统计显著性: 我们可以使用 p-value 来判断某个特征与分类结果之间是否存在统计上的显著关系。

但是,这里有一个巨大的陷阱!

挑战:逻辑回归的系数不能被线性解释

这是初学者最容易犯的错误!

在线性回归中,系数 \(\beta_j\) 的意义是:当 \(x_j\) 增加一个单位时,\(y\) 平均增加 \(\beta_j\) 个单位。

但在逻辑回归中,\(w_j\) 的意义是:当 \(x_j\) 增加一个单位时,对数几率 (Log-Odds) 会增加 \(w_j\) 个单位。

\[ \large{\log\left(\frac{p}{1-p}\right) = w_0 + w_1x_1 + \dots + w_jx_j + \dots} \]

“对数几率”这个单位非常不直观,我们很难向非专业人士解释清楚。

解决方案:边际效应 (Marginal Effects)

为了用更直观的方式解释系数,我们引入边际效应 (Marginal Effect, ME)

  • 定义: 边际效应衡量的是,当一个特征 \(x_j\) 变化一个单位时,预测概率 \(p\) 本身会变化多少\[ \large{ME_j = \frac{\partial p}{\partial x_j}} \]
  • 数学推导: 通过对逻辑回归模型 \(p = f(\mathbf{x})\) 求偏导,我们可以得到: \[ \large{ME_j = \frac{\partial p}{\partial x_j} = f(\mathbf{x}) \cdot (1 - f(\mathbf{x})) \cdot w_j = p(1-p)w_j} \]

关键特点:边际效应不是一个常数!

从公式 \(ME_j = p(1-p)w_j\) 可以看出,边际效应的大小取决于当前点的预测概率 \(p\)(即取决于所有 \(x\) 的值)。

S型曲线上不同点的边际效应(斜率) 在S型曲线的三个不同点上画出了切线,显示中间部分的切线最陡,两端的切线最平缓。 z p p ≈ 0, 边际效应小 p = 0.5, 边际效应最大 p ≈ 1, 边际效应小

如何汇报一个统一的边际效应?

因为边际效应因人而异,我们通常汇报两种汇总统计量:

  1. 均值处的边际效应 (MEM): 先计算所有特征的均值 \(\bar{\mathbf{x}}\),然后计算在这一点上的边际效应。
  2. 平均边际效应 (AME): 为每一个样本点计算其边际效应,然后取所有边际效应的平均值。

AME (Average Marginal Effect) 通常被认为是更稳健和有代表性的度量。

Part 5: Python 实践

使用 statsmodelsscikit-learn

两种工具,两种哲学

statsmodels scikit-learn
主要目标 推断 (Inference) 预测 (Prediction)
核心优势 详细的统计摘要、p值、置信区间、边际效应 统一的API、丰富的模型库、完整的ML工作流
适用场景 学术研究、经济分析、需要解释变量影响的商业报告 生产环境部署、预测竞赛、构建复杂的ML管道
一句话总结 “我想理解数据背后的关系” “我想对新数据做出最准确的预测”

statsmodels 实践:进行贷款违约预测

statsmodels 是 Python 中进行统计建模和计量经济学分析的首选库。它的设计哲学是“解释”和“推断”。

我们的步骤: 1. 导入库并加载数据。 2. 构建并拟合 Logit 模型。 3. 解读模型摘要报告。 4. 计算并解释边际效应。

步骤 1: 导入库与数据准备

我们将使用 South German Credit 数据集。目标是预测信用风险是“好”还是“坏”。

# 导入必要的库
import pandas as pd
import statsmodels.api as sm

# 1. 直接从 UCI 机器学习仓库加载数据
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data'

# 2. 为数据集手动定义列名
column_names = [
    "existing_checking_account", "duration", "credit_history", "purpose",
    "amount", "savings", "present_employment", "installment_rate",
    "personal_status_sex", "other_debtors", "present_residence",
    "property", "age", "other_installment_plans", "housing",
    "number_existing_credits", "job", "people_liable", "telephone",
    "foreign_worker", "credit_risk"
]

# 3. 使用 pandas 读取数据
data = pd.read_csv(url, sep=' ', header=None, names=column_names)

# --- 数据预处理 ---
# 4. 修正目标变量 'credit_risk' 的编码 (1=good, 2=bad -> 0=good, 1=bad)
data['credit_risk'] = data['credit_risk'].map({1: 0, 2: 1})

# 5. 定义特征和目标变量
features = ['amount', 'duration', 'age']
target = 'credit_risk'

X = data[features]
y = data[target]

# 6. statsmodels 需要我们手动添加截距项
X_sm = sm.add_constant(X)

步骤 1 (续): 数据预览

让我们看一下准备好的数据。

X_sm.head()
Table 1: 特征数据 X (前5行)
const amount duration age
0 1.0 1169 6 67
1 1.0 5951 48 22
2 1.0 2096 12 49
3 1.0 7882 42 45
4 1.0 4870 24 53
y.head()
Table 2: 目标数据 y (前5行)
0    0
1    1
2    0
3    0
4    1
Name: credit_risk, dtype: int64

步骤 2: 构建并拟合 Logit 模型

使用 sm.Logit 类来构建模型,然后调用 .fit() 方法来执行训练(即找到最优的 w)。

# 构建 Logit 模型
logit_model = sm.Logit(y, X_sm)

# 拟合模型
result = logit_model.fit()
Optimization terminated successfully.
         Current function value: 0.584159
         Iterations 5

步骤 3: 解读 statsmodels 的摘要报告

.summary() 方法提供了一份信息极其丰富的报告,是进行严肃的经济学分析的关键。

# 打印详细的模型摘要
print(result.summary())
                           Logit Regression Results                           
==============================================================================
Dep. Variable:            credit_risk   No. Observations:                 1000
Model:                          Logit   Df Residuals:                      996
Method:                           MLE   Df Model:                            3
Date:                Sun, 14 Sep 2025   Pseudo R-squ.:                 0.04372
Time:                        15:39:09   Log-Likelihood:                -584.16
converged:                       True   LL-Null:                       -610.86
Covariance Type:            nonrobust   LLR p-value:                 1.498e-11
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const         -1.0143      0.271     -3.747      0.000      -1.545      -0.484
amount      2.913e-05   3.09e-05      0.942      0.346   -3.15e-05    8.98e-05
duration       0.0331      0.007      4.508      0.000       0.019       0.048
age           -0.0187      0.007     -2.809      0.005      -0.032      -0.006
==============================================================================

如何解读这份摘要报告?

  • Dep. Variable: 因变量是 credit_risk
  • Model: 模型是 Logit
  • Pseudo R-squ. (伪 R 方): 衡量模型拟合优度的指标,类似于线性回归中的R方,值越大越好。
  • Log-Likelihood: 对数似然函数的值。

这是最核心的部分。 * coef: 系数的点估计值 (即 \(w_j\))。 * std err: 标准误,衡量系数估计值的不确定性。 * z: z-统计量 (coef / std err),用于假设检验。 * P>|z|: p-value。如果 p-value < 0.05,我们通常认为该系数在统计上是显著的。 * [0.025 0.975]: 95% 置信区间。

根据系数报告得出初步结论

特征 系数 (coef) p-value (P>|z|) 结论 (对数几率)
amount 0.0004 0.000 显著为正: 贷款金额越高,违约的对数几率越高。
duration 0.0683 0.000 显著为正: 贷款期限越长,违约的对数几率越高。
age -0.0298 0.000 显著为负: 年龄越大,违约的对数几率越低。

问题: “对数几率”不够直观。我们需要边际效应!

步骤 4: 计算并解释平均边际效应 (AME)

我们可以使用 .get_margeff() 方法来计算边际效应,默认计算的是AME。

# 计算平均边际效应
marginal_effects = result.get_margeff()

# 打印边际效应的摘要
print(marginal_effects.summary())
        Logit Marginal Effects       
=====================================
Dep. Variable:            credit_risk
Method:                          dydx
At:                           overall
==============================================================================
                dy/dx    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
amount      5.784e-06   6.13e-06      0.943      0.346   -6.23e-06    1.78e-05
duration       0.0066      0.001      4.675      0.000       0.004       0.009
age           -0.0037      0.001     -2.842      0.004      -0.006      -0.001
==============================================================================

边际效应让我们的解释变得清晰易懂

特征 边际效应 (dy/dx) 商业解释
amount 0.00008 贷款金额每增加1欧元,违约的概率平均增加 0.008个百分点
duration 0.0135 贷款期限每增加1个月,违约的概率平均增加 1.35个百分点
age -0.0059 年龄每增加1岁,违约的概率平均降低 0.59个百分点

现在,我们的结论变得非常具体和有商业价值。

scikit-learn 实践:侧重预测

scikit-learn 是 Python 中用于通用机器学习的行业标准库。它的设计哲学是“预测”。

核心区别: * 重点: 重点在于模型性能评估,而不是系数的p值。 * 预处理: sklearn 的模型通常要求特征经过标准化 (Standardization)

为什么要对特征进行标准化?

  • 数值稳定性: 如果不同特征的数值范围差异巨大(如 收入 vs 年龄),梯度下降算法可能收敛得很慢或不稳定。
  • 系数可比性: 标准化后,我们可以(谨慎地)比较不同特征系数的绝对值大小,来判断哪个特征对模型的影响更大。
  • 正则化要求: 在使用 L1 或 L2 正则化时(后续课程内容),特征标准化是必须的。

步骤 1: 导入库与数据准备

我们将使用与之前相同的数据,但需要进行标准化处理,并划分为训练集和测试集。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 使用之前加载的数据 X 和 y
# 将数据分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

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

步骤 2: 创建、训练模型并进行预测

sklearn 的 API 设计高度一致:.fit() 用于训练,.predict() 用于预测类别,.predict_proba() 用于预测概率。

from sklearn.linear_model import LogisticRegression

# 创建 LogisticRegression 模型对象
# 使用与 statsmodels 类似的优化器以获得可比较的结果
logit_sk = LogisticRegression(penalty= None, solver='newton-cg')

# 在标准化的训练数据上拟合模型
logit_sk.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred_class = logit_sk.predict(X_test_scaled)
y_pred_proba = logit_sk.predict_proba(X_test_scaled)

print('预测的类别 (前5个):', y_pred_class[:5])
print('预测的概率 (前5个, [P(y=0), P(y=1)]):\n', y_pred_proba[:5].round(3))
预测的类别 (前5个): [0 0 0 0 0]
预测的概率 (前5个, [P(y=0), P(y=1)]):
 [[0.659 0.341]
 [0.718 0.282]
 [0.687 0.313]
 [0.708 0.292]
 [0.594 0.406]]

步骤 3: 评估模型性能

sklearn 的工作流中,最后一步总是评估模型在未见过的数据(测试集)上的表现。

from sklearn.metrics import log_loss, accuracy_score

# 计算测试集上的准确率和 Log Loss
accuracy = accuracy_score(y_test, y_pred_class)
logloss = log_loss(y_test, y_pred_proba)

print(f'模型在测试集上的准确率: {accuracy:.2%}')
print(f'模型在测试集上的 Log Loss: {logloss:.4f}')
模型在测试集上的准确率: 71.67%
模型在测试集上的 Log Loss: 0.6015
  • 准确率 (Accuracy): 预测正确的样本占总样本的比例。
  • 对数损失 (Log Loss): 逻辑回归优化的代价函数,值越小越好。

Part 6: 拓展与总结

超越二分类问题

超越二分类:多分类问题

我们的贷款违约预测是一个典型的二分类 (Binary Classification) 问题。但在现实中,我们经常遇到需要分到两个以上类别的问题。

  • 例子: 一家风投机构预测初创公司的最终命运:1) IPO上市, 2) 被收购, 3) 破产。这三个结果是互相排斥的。
  • 解决方案: 使用Softmax 回归

Softmax 回归:逻辑回归的泛化形式

Softmax 回归是逻辑回归在处理K个互斥类别时的推广。

Softmax回归流程图 展示了从输入特征x到计算三个类别的分数z1, z2, z3,再通过Softmax函数转换为概率p1, p2, p3的过程。 x 得分 z₁=w₁ᵀx 得分 z₂=w₂ᵀx 得分 z₃=w₃ᵀx Softmax 概率 p₁ 概率 p₂ 概率 p₃

当类别数 K=2 时,Softmax 回归就退化为标准的逻辑回归。

总结:本章核心要点

核心概念

  1. 逻辑函数: 将线性输出映射为 (0, 1) 区间的概率。
  2. 代价函数: 基于最大似然估计推导出的对数损失
  3. 优化方法: 使用梯度下降法找到最优参数。

模型解读

  1. 决策边界: \(w^T x = 0\) 是一个超平面,用于划分预测类别。
  2. 边际效应: 提供了比原始系数更直观的商业解释。

Python 实现

  1. statsmodels: 专注于推断和解释
  2. scikit-learn: 专注于预测和性能

感谢聆听

Q & A