本章学习路线图
我们将通过六个部分,系统地掌握逻辑回归。
Part 1: 问题设定
为何线性回归不足以应对分类任务?
案例引入:银行的核心风控业务
- 银行的核心功能: 向个人和企业发放贷款是现代银行体系的基石。
- 风险与收益的权衡: 在审批贷款时,银行面临一个核心挑战——如何准确预测申请人的还款能力?
- 数据驱动决策: 在金融科技时代,我们不再依赖主观判断,而是利用机器学习模型,通过分析申请人的数据来创建一套精准的贷款违约预测系统。
商业问题可视化
银行的每日决策:对于每一位贷款申请人,我们应该批准(Approve)还是拒绝(Reject)?
我们的任务:构建一个贷款违约预测模型
我们将利用一个包含已审批贷款表现的数据集来构建模型。
年收入 (Income) |
申请人的年度总收入 |
越高,违约概率越低 |
负债收入比 (DTI) |
每月债务支出占总收入的比例 |
越高,违约概率越高 |
工作年限 (Emp. Year) |
申请人在当前工作的年限 |
越长,违约概率越低 |
目标变量 (y): 贷款是否违约。这是一个二元变量(Binary Variable)。
y = 1
: 贷款已违约 (正类, Positive Class)
y = 0
: 贷款未违约 (负类, Negative Class)
模型的核心任务是学习特征与结果之间的关系
我们的机器学习模型就像一个函数 \(f\),它的任务是学习输入(申请人的特征 \(\mathbf{x}\))与输出(是否违约 \(y\))之间的映射关系。
模型一旦训练完成,就可以输出一个介于0和1之间的概率值。
一个自然的问题:为什么不用线性回归?
我们已经很熟悉线性回归了,它预测的是一个连续的数值。我们能否用它来预测一个0
或1
的分类问题呢?
让我们用负债收入比
来预测是否违约
。
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()
缺陷 1:预测值越界
正如上图所示,当负债收入比
非常高或非常低时,线性模型的预测值会超出 [0, 1]
的合理区间。
一个大于1或小于0的“概率”在现实中是毫无意义的。
缺陷 2:误差项非正态
线性回归的一个基本假设是误差项 \(\epsilon\) 服从正态分布。但对于0/1
的因变量,误差只能取两个值,严重违背了这一假设。
ε = 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()
逻辑函数的关键特性
案例图解:逻辑函数如何“弯曲”线性关系
我们来看一个具体的例子,假设经过模型训练,我们得到的线性关系是 \(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()
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=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\)。
结论: 这个损失函数的设计非常合理:预测越准确,损失越小;预测越离谱,损失就越大。
最小化代价函数:梯度下降法
我们已经定义了需要最小化的山谷(代价函数 \(J(\mathbf{w})\)),现在需要一个算法来帮助我们走到谷底。这个算法就是梯度下降法 (Gradient Descent)。
核心思想: 在当前位置,寻找最陡峭的下坡方向,然后迈出一步。重复此过程。
梯度下降的更新规则
- “最陡峭的下坡方向”是代价函数梯度的负方向 \((-\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\) 个特征值。
这个梯度的形式和我们在线性回归中学到的几乎完全一样!
逻辑回归的梯度下降完整算法
初始化: 随机初始化权重向量 \(\mathbf{w}\) (例如,全零向量)。
迭代更新: 重复以下步骤直到收敛:
对于 \(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\))
收敛: 当 \(\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()
解读模型:系数的意义
逻辑回归不仅能做预测,它强大的解释性也是其在经济金融领域广受欢迎的重要原因。
- 系数的符号: 如果 \(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\) 的值)。
如何汇报一个统一的边际效应?
因为边际效应因人而异,我们通常汇报两种汇总统计量:
- 均值处的边际效应 (MEM): 先计算所有特征的均值 \(\bar{\mathbf{x}}\),然后计算在这一点上的边际效应。
- 平均边际效应 (AME): 为每一个样本点计算其边际效应,然后取所有边际效应的平均值。
AME (Average Marginal Effect) 通常被认为是更稳健和有代表性的度量。
Part 5: Python 实践
使用 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 (续): 数据预览
让我们看一下准备好的数据。
步骤 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% 置信区间。
根据系数报告得出初步结论
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
==============================================================================
边际效应让我们的解释变得清晰易懂
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): 逻辑回归优化的代价函数,值越小越好。
超越二分类:多分类问题
我们的贷款违约预测是一个典型的二分类 (Binary Classification) 问题。但在现实中,我们经常遇到需要分到两个以上类别的问题。
- 例子: 一家风投机构预测初创公司的最终命运:1) IPO上市, 2) 被收购, 3) 破产。这三个结果是互相排斥的。
- 解决方案: 使用Softmax 回归。
Softmax 回归:逻辑回归的泛化形式
Softmax 回归是逻辑回归在处理K个互斥类别时的推广。
z₁=w₁ᵀx
得分 z₂=w₂ᵀx
得分 z₃=w₃ᵀx
Softmax
概率 p₁
概率 p₂
概率 p₃
当类别数 K=2 时,Softmax 回归就退化为标准的逻辑回归。
总结:本章核心要点
核心概念
- 逻辑函数: 将线性输出映射为
(0, 1)
区间的概率。
- 代价函数: 基于最大似然估计推导出的对数损失。
- 优化方法: 使用梯度下降法找到最优参数。
模型解读
- 决策边界: \(w^T x = 0\) 是一个超平面,用于划分预测类别。
- 边际效应: 提供了比原始系数更直观的商业解释。
Python 实现
statsmodels
: 专注于推断和解释。
scikit-learn
: 专注于预测和性能。