首页
/ ML-For-Beginners 回归单元实战:用逻辑回归预测南瓜颜色的二分类模型

ML-For-Beginners 回归单元实战:用逻辑回归预测南瓜颜色的二分类模型

2026-09-06 14:55:46作者:仰钰奇

本篇指南基于 ML-For-Beginners 课程仓库回归单元(2-Regression)的最后一课,完整讲解如何用 Scikit-learn 的逻辑回归(Logistic Regression)解决二分类问题:给定一批美国南瓜的市场数据,预测每个南瓜的颜色属于"白色"还是"非白色"。文章覆盖从数据清洗、Seaborn 可视化、特征与标签编码,到模型训练、混淆矩阵解读与 ROC/AUC 评估的全流程,读者可以按步骤复现一个可运行的二分类流水线。

1. 为什么在"回归"单元讲二分类

逻辑回归是经典机器学习中用于预测二分类类别的核心技术。典型问题形式是:"这颗糖果含巧克力吗?""这种病传染吗?""这个客户会选这个产品吗?"——答案都是"是/否"。

逻辑回归虽然名字里带"回归",但按 Scikit-learn 的模块划分它本质上是一种基于线性方法的分类算法,课程把它放在回归单元里只是出于术语上的便利。在 ML-For-Beginners 中,下一课组(4-Classification)会继续介绍其他分类方法,本篇则是回归单元的收官课。

线性回归与逻辑回归的差异示意

与线性回归相比,逻辑回归有几个重要区别:

  • 预测对象不同:线性回归输出连续值(例如根据产地和收获时间预测南瓜价格涨幅);逻辑回归输出一个二分类预测("白色或非白色")。
  • 变量不要求强相关:线性回归更喜欢强相关的变量,逻辑回归则相反——变量之间不必对齐。这对本课数据很合适,因为南瓜各特征之间的相关性并不算强。
  • 需要大量干净数据:数据越多,逻辑回归的准确度越高。本课数据集只有约 1000 多行有效数据,对这项任务并不算理想,这是评估结果时需要记住的前提。

1.1 其他类型的逻辑回归

除二分类外,逻辑回归还有两类变体:

  • 多项(Multinomial):存在多个无序类别,例如"橙色、白色、条纹色";
  • 有序(Ordinal):类别之间存在逻辑顺序,例如南瓜按有限个尺寸排序(mini、sm、med、lg、xl、xxl)。

1.2 定义问题:白色 vs 非白色

课程使用的数据集是美国南瓜批发市场数据,仓库中为 US-pumpkins.csv(共 1757 行、26 列)。从实际数据看,Color 列只出现 ORANGEWHITESTRIPED 三种取值;其中 STRIPED(条纹)样本很少,且一旦移除空值后基本消失,因此课程将其简化为二分类问题:

  • 分类目标:White(白色)或 Not White(非白色);
  • 趣味背景:白色南瓜常被称为"幽灵南瓜"(ghost pumpkins),不易雕刻,不如橙色南瓜常见,所以问题也可以表述为"Ghost 还是 Not Ghost"。

课程配套的起点笔记本是 notebook.ipynb,其中已预置了加载数据的第一步代码:

import pandas as pd
import numpy as np

full_pumpkins = pd.read_csv('../data/US-pumpkins.csv')

full_pumpkins.head()

另外,本课同时提供 R 语言版本,包括 lesson_4.htmllesson_4.Rmdlesson_4-R.ipynb,适合 R 学习者对照学习。

2. 清洗数据:选列与去空值

逻辑回归依赖干净的数据,第一步是从完整数据框中挑选有用列并删除缺失值:

columns_to_select = ['City Name', 'Package', 'Variety', 'Origin', 'Item Size', 'Color']
pumpkins = full_pumpkins.loc[:, columns_to_select]

pumpkins.dropna(inplace=True)

清洗后可以通过 pumpkins.info 查看数据框的新结构(列名、非空数量、类型)。保留下来的 Color 就是本模型要预测的目标类别。

3. 可视化:用 Seaborn 绘制分类图

本课引入基于 Matplotlib 的 Seaborn 库(sns),它是课程中第一次使用的统计可视化库。例如用 catplot 按南瓜品种(Variety)对比颜色(Color)的分布,并为每种颜色指定固定色板:

import seaborn as sns

palette = {
    'ORANGE': 'orange',
    'WHITE': 'wheat',
}

sns.catplot(
    data=pumpkins, y="Variety", hue="Color", kind="count",
    palette=palette,
)

南瓜品种与颜色的分类计数图

从图中可以直接观察到 ColorVariety 之间的关联:某些品种(如 HOWDEN WHITE TYPE)几乎全是白色,而 HOWDEN TYPE 则以橙色为主——这种结构性差异正是逻辑回归能够学到"颜色规律"的原因。

✅ 思考题:观察这张分类计数图,你还能设想哪些有趣的探索方向?

4. 数据预处理:特征与标签编码

南瓜数据集的所有列都是字符串。人类直观理解类别型数据,但机器不是——机器学习算法只擅长处理数字。因此**编码(encoding)**是预处理阶段中至关重要的一步:在不丢失信息的前提下把类别数据转成数值数据,好的编码方式直接决定模型质量的上限。

课程给出了两种主要编码器:

4.1 有序编码器(OrdinalEncoder)

适合有序变量——类别之间存在逻辑顺序,例如 Item Size 列。它把每个类别映射为序号。实际数据中该列共 7 个取值,从最小到最大依次为 sml, med, med-lge, lge, xlge, jbo, exjbo,与下面的类别定义一致:

from sklearn.preprocessing import OrdinalEncoder

item_size_categories = [['sml', 'med', 'med-lge', 'lge', 'xlge', 'jbo', 'exjbo']]
ordinal_features = ['Item Size']
ordinal_encoder = OrdinalEncoder(categories=item_size_categories)

✅ 思考题:对 Item Size 使用有序编码器有什么好处?(提示:保留了尺寸间"更小→更大"的序关系,且参数量远少于独热编码。)

4.2 独热编码器(OneHotEncoder)

适合名义变量——类别之间没有顺序,例如 City NamePackageVarietyOrigin(实际数据中品种有 10 种、产地有 24 种)。独热编码把每个类别展开为一个二值列:属于该类别为 1,否则为 0。

from sklearn.preprocessing import OneHotEncoder

categorical_features = ['City Name', 'Package', 'Variety', 'Origin']
categorical_encoder = OneHotEncoder(sparse_output=False)

4.3 用 ColumnTransformer 合并多路编码

ColumnTransformer 把多个编码器组合成一步,并各自作用于对应列:

from sklearn.compose import ColumnTransformer

ct = ColumnTransformer(transformers=[
    ('ord', ordinal_encoder, ordinal_features),
    ('cat', categorical_encoder, categorical_features)
    ])

ct.set_output(transform='pandas')
encoded_features = ct.fit_transform(pumpkins)

注意 ct.set_output(transform='pandas'):它让输出仍是带列名的 DataFrame,后续可以直接引用 ord__Item Size 这类带前缀的编码列。

4.4 标签编码与合并

标签用 Scikit-learn 的 LabelEncoder 规范化为 0 到 n_classes-1(这里即 0 和 1)的整数:

from sklearn.preprocessing import LabelEncoder

label_encoder = LabelEncoder()
encoded_label = label_encoder.fit_transform(pumpkins['Color'])

最后把编码后的特征与标签合并进新的数据框 encoded_pumpkins

encoded_pumpkins = encoded_features.assign(Color=encoded_label)

5. 分析变量间关系:箱体分类图与蜂群图

预处理完成后,课程建议先"看图建模",直观判断特征对标签的区分力。再次使用 catplot,把 Item Size(使用编码后的数值列)、Variety(保持原始文本作分面行)和 Color 画成横向箱体图:

palette = {
    'ORANGE': 'orange',
    'WHITE': 'wheat',
}
pumpkins['Item Size'] = encoded_pumpkins['ord__Item Size']

g = sns.catplot(
    data=pumpkins,
    x="Item Size", y="Color", row='Variety',
    kind="box", orient="h",
    sharex=False, margin_titles=True,
    height=1.8, aspect=4, palette=palette,
)
g.set(xlabel="Item Size", ylabel="").set(xlim=(0,6))
g.set_titles(row_template="{row_name}")

南瓜尺寸、品种与颜色的箱体分类图

由于 Color 是二分类变量,课程还推荐用**蜂群图(swarm plot)**展示两个颜色取值下尺寸分布的形态:

palette = {
    0: 'orange',
    1: 'wheat'
}
sns.swarmplot(x="Color", y="ord__Item Size", data=encoded_pumpkins, palette=palette)

注意:上述代码可能产生警告——Seaborn 在单张蜂群图中渲染如此多的数据点会失败或互相叠压。可以调小 size 参数缓解,但需权衡图形的可读性。

5.1 数学原理:Sigmoid 函数与极大似然

🧮 Show Me The Math

逻辑回归基于**极大似然(maximum likelihood)**思想,并使用 Sigmoid 函数。Sigmoid 曲线呈"S"形,把一个任意实数映射到 0 与 1 之间,其曲线也叫"逻辑曲线":

Sigmoid 函数

其中曲线中点位于 x=0 处,L 是曲线的最大值,k 控制曲线的陡峭程度。函数输出大于 0.5 时,样本被划入二分类中的"1"类;否则归为"0"类。

6. 构建模型:训练与初步评估

在 Scikit-learn 中构建二分类模型非常直接。

第 1 步:选定进入模型的变量,并用 train_test_split() 划分训练集与测试集(留出 20% 作测试集,random_state=0 保证结果可复现):

from sklearn.model_selection import train_test_split

X = encoded_pumpkins[encoded_pumpkins.columns.difference(['Color'])]
y = encoded_pumpkins['Color']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

第 2 步:调用 fit() 训练,对测试集做 predict(),并打印分类报告与 F1 分数:

from sklearn.metrics import f1_score, classification_report
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)
predictions = model.predict(X_test)

print(classification_report(y_test, predictions))
print('Predicted labels: ', predictions)
print('F1-score: ', f1_score(y_test, predictions))

课程的运行结果(测试集约 199 条)如下:

                   precision    recall  f1-score   support

                0       0.94      0.98      0.96       166
                1       0.85      0.67      0.75        33

    accuracy                                0.92       199
    macro avg           0.89      0.82      0.85       199
    weighted avg        0.92      0.92      0.92       199

    Predicted labels:  [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 ... 1 1]
    F1-score:  0.7457627118644068

考虑到有效数据仅约 1000 行,这个结果并不算差:0 类(非白色)的精确率/召回率都很高,1 类(白色)的召回率只有 0.67,说明少数类的漏报是主要短板。

7. 深入理解:混淆矩阵

分类报告给出了各指标数值,但要真正理解模型行为,**混淆矩阵(confusion matrix)**更直观:

from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, predictions)

课程的输出:

array([[162,   4],
       [ 11,  22]])

在 Scikit-learn 中,行(轴 0)是真实标签,列(轴 1)是预测标签,对应关系为:

预测 0 预测 1
实际 0 TN=162 FP=4
实际 1 FN=11 TP=22

以"预测南瓜是否白色"为例:

  • 预测"非白色"且实际就是非白色 → 真负(TN),左上角数字;
  • 预测"白色"但实际非白色 → 假负(FN),课程将其解释为左下角数字;
  • 预测"非白色"但实际是白色 → 假正(FP),右上角数字;
  • 预测"白色"且实际就是白色 → 真正(TP),右下角数字。

TP、TN 越大、FP、FN 越小,模型越好。

7.1 从混淆矩阵手算 Precision 与 Recall

课程用矩阵中的数字验证了分类报告里的精确率与召回率:

Precision = tp / (tp + fp) = 22 / (22 + 4)  = 0.8461538461538461
Recall    = tp / (tp + fn) = 22 / (22 + 11) = 0.6666666666666666

✅ Q:根据混淆矩阵,模型表现如何?A:不算差——真负数量可观,但存在少量假负。 ✅ 思考题:如果你想减少模型的假负数量,应该重点盯住哪个指标?(提示:Recall。)

课程随后用 TP/TN/FP/FN 重新定义了报告中的每个术语:

  • Precision(精确率):TP/(TP + FP),被检索到的实例中相关实例的占比(哪些标签被正确标出);
  • Recall(召回率):TP/(TP + FN),被检索到的相关实例占比(无论标记好坏);
  • f1-score:(2 × precision × recall) / (precision + recall),精确率与召回率的调和平均,最好为 1、最差为 0;
  • Support:每个标签实际出现的次数;
  • Accuracy:(TP + TN)/(TP + TN + FP + FN),样本中被正确预测的百分比;
  • Macro Avg:对各标签指标取不加权平均,不考虑类别不平衡;
  • Weighted Avg:按各标签的 support 加权平均,考虑类别不平衡。

8. 评估模型:ROC 曲线与 AUC

最后一个可视化是 ROC 曲线(Receiver Operating Characteristic),用于从"真阳率 vs 假阳率"的角度审视分类器输出:

from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

y_scores = model.predict_proba(X_test)
fpr, tpr, thresholds = roc_curve(y_test, y_scores[:,1])

fig = plt.figure(figsize=(6, 6))
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr, tpr)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.show()

几点细节值得注意:

  • model.predict_proba(X_test) 返回每个类别的预测概率,y_scores[:,1] 取"1 类(白色)"的概率作为打分输入 roc_curve
  • ROC 曲线横轴是假阳率(FPR)、纵轴是真阳率(TPR),虚线是从 (0,0) 到 (1,1) 的随机猜测基准线;
  • 曲线的陡峭程度以及它与基准线之间围成的面积是关键:理想曲线应尽早向上凸起并越过基准线。本课模型起初存在少量假阳,随后正确地向上越过基准线。

模型 ROC 曲线与基准线

最后用 Scikit-learn 的 roc_auc_score 计算曲线下面积(AUC):

auc = roc_auc_score(y_test, y_scores[:,1])
print(auc)

课程输出的结果是 0.9749908725812341。AUC 取值在 0 到 1 之间,100% 正确的模型 AUC 为 1,因此 0.975 说明这个在仅千行数据上训练的逻辑回归模型"相当不错"。

9. 练习与延伸:用完整数据重做这个回归

课程课后作业(assignment.md)的要求是:

课上你只使用了南瓜数据的一个子集。现在回到原始数据,把全部数据清洗、标准化之后,重新构建一个逻辑回归模型。

评估标准是:提交一个笔记本,模型讲解充分且表现良好(优秀)、能基本运行(合格)、或模型表现不佳/缺失(需改进)。

在动手之前,可以先利用仓库中的原始数据了解全貌:US-pumpkins.csv 有 26 列,除本课选用的 6 列外,还包含 TypeSub VarietyGradeDateLow Price/High PriceOrigin DistrictEnvironmentCropRepack 等字段,Color 列存在 ORANGEWHITESTRIPED 三种取值,且大量行带有空值——这正是"先用 dropna() 清洗、再选列"这一流程的由来。

课程给出的自研延伸建议是:找一份适合二分类分析的数据集,用逻辑回归从零建模,记录学到了什么。学习路径上,本课结束后即可进入仓库的下一课组 4-Classification(入口),那里会系统讲解如何迭代改进分类模型的各项得分。


本篇小结:围绕 2-Regression/4-Logistic/README.md 这一课,本文完整复现了 ML-For-Beginners 中逻辑回归的实战流程——从南瓜数据的选列清洗、Seaborn 分类图与蜂群图,到 OrdinalEncoder/OneHotEncoder/ColumnTransformer/LabelEncoder 的编码流水线、LogisticRegression 训练,再到混淆矩阵手算 Precision/Recall 与 ROC/AUC 评估。所有代码均可在配套笔记本 notebook.ipynb 中对照运行,R 语言版本见 solution/R/

登录后查看全文
热门项目推荐
相关项目推荐