学习曲线

随训练集增大的训练准确率与交叉验证准确率,比较两个预测哪些已婚女性报告有婚外情的模型。

学习曲线

在收集更多数据之前,学习曲线回答两个问题:模型是否 过拟合(训练得分与交叉验证得分差距大),还是 欠拟合(两个得分都低且接近)?更多观测是否还有帮助(验证曲线是否仍在上升)?线条为重复交叉验证各折的均值;条带为 ±1 个标准差,其宽度显示换一种划分时估计会变动多少。

在同一任务上比较两个模型:一棵会记住训练集的深决策树,以及一个标准化的逻辑回归。数据为 Fair(1978)分析的 1974 年《Redbook》杂志调查中的 6,366 名已婚女性,由 statsmodels 提供(sm.datasets.fair);结果变量为是否报告有过婚外情(约三分之一),由婚姻评分、年龄、结婚年数、子女、宗教虔诚度、教育程度和职业预测。曲线来自 sklearn.model_selection.learning_curve,采用 5 × 2 重复分层划分。结论令人清醒:决策树的交叉验证准确率始终低于多数类基线,而逻辑回归的两条曲线平坦且重合——增加受访者没有帮助,更好的预测变量才有用。

代码及图中文字均为英文。

learning-curve.py · 47 行
下载 GitHub
import matplotlib.pyplot as plt
import numpy as np
import statsmodels.api as sm
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import RepeatedStratifiedKFold, learning_curve
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier

# Data: 6,366 married women (1974 survey); 1 = reports any extramarital affair
d = sm.datasets.fair.load_pandas().data
X = d.drop(columns="affairs")
y = (d["affairs"] > 0).astype(int)
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=2, random_state=0)
sizes = np.linspace(0.08, 1.0, 10)
models = {"Decision tree": DecisionTreeClassifier(random_state=0),
          "Logistic regression": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))}

plt.rcParams["figure.constrained_layout.use"] = False   # manual margins leave room for the subtitle
fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharey=True)
fig.subplots_adjust(left=0.08, right=0.97, top=0.80, bottom=0.12, wspace=0.12)
for ax, (name, model) in zip(axes, models.items()):
    n, train, valid = learning_curve(model, X, y, cv=cv, train_sizes=sizes, n_jobs=-1,
                                    shuffle=True, random_state=0)   # rows are sorted by outcome
    for scores, c, lab in [(train, "#8E8E93", "Training"), (valid, "#007AFF", "Cross-validated")]:
        m, s = scores.mean(axis=1), scores.std(axis=1)
        ax.fill_between(n, m - s, m + s, color=c, alpha=0.18, lw=0)
        ax.plot(n, m, color=c, lw=2.2, marker="o", ms=4)
        dy = 0.012 if lab == "Training" else -0.012
        ax.text(n[-1] + 80, m[-1] + dy, lab, va="center", fontsize=9.5, color=c, fontweight="semibold")
    gap = train.mean(axis=1)[-1] - valid.mean(axis=1)[-1]
    ax.annotate("", xy=(n[-1], valid.mean(axis=1)[-1]), xytext=(n[-1], train.mean(axis=1)[-1]),
                arrowprops=dict(arrowstyle="<->", color="#FF3B30", lw=1.2))
    ax.text(n[-1] - 100, valid.mean(axis=1)[-1] + (gap / 2 if gap > 0.05 else -0.04), f"gap {gap:.2f}",
            ha="right", va="center", fontsize=9.5, color="#FF3B30", fontweight="semibold")
    ax.set_title(name)
    ax.set_xlabel("Training observations")
    ax.set_xlim(0, n[-1] * 1.32)
axes[0].set_ylabel("Accuracy")
axes[0].set_ylim(0.5, 1.03)

fig.suptitle("The tree memorises its training set; the logistic regression plateaus with no gap",
             x=0.02, y=0.97, ha="left")
fig.text(0.02, 0.885, f"n = {len(y):,} women · majority-class baseline = {1 - y.mean():.2f} · "
         "5-fold × 2 repeats · lines = mean, bands = ±1 SD across folds",
         fontsize=9.5, color="#6E6E73")
plt.show()

图表使用了可选的 PyStatlas 样式:添加 plt.style.use("pystatlas.mplstyle") 即可完全还原其外观。

数据来源

  • Redbook survey of married women, 1974 (Fair's affairs data). Fair, R. C. (1978). A theory of extramarital affairs. Journal of Political Economy, 86(1), 45–61. https://doi.org/10.1086/260646
    Data from the 1974 Redbook magazine survey; distributed with statsmodels (sm.datasets.fair) with the author's permission.

这些示例用于说明统计方法。图表标题描述的是该图在这些特定数据中呈现的内容——这些数据往往样本较小、年代较早或并不完整——而不是作者对实质问题的立场。关于解释、背景和数据质量,请以此处引用的原始来源为准。

如何引用

PyStatlas 由 Thierry Warin 创建并维护。如果它对你的工作有所帮助,敬请引用。

Warin, T. (2026). Learning curve. In PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences. https://warint.github.io/pystatlas/machine-learning/learning-curve/

@misc{warin2026statlas,
  author       = {Warin, Thierry},
  title        = {PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences},
  year         = {2026},
  howpublished = {\url{https://warint.github.io/pystatlas/}},
  note         = {Source code: https://github.com/warint/pystatlas}
}

更多机器学习图表