首页
/ ML-For-Beginners 回归模块指南:基于南瓜价格数据集构建线性与逻辑回归模型

ML-For-Beginners 回归模块指南:基于南瓜价格数据集构建线性与逻辑回归模型

2026-09-05 19:19:51作者:何将鹤

本文基于 ML-For-Beginners 课程库的 2-Regression 模块,系统讲解如何用 Scikit-learn 完成回归建模的完整流程:从配置本地机器学习环境、清洗和可视化 USDA 南瓜市场价格数据,到训练线性回归、多项式回归和逻辑回归模型,并掌握 RMSE、决定系数、混淆矩阵与 ROC 曲线等核心评估手段。读完本篇,你可以独立复现该模块四节课的全部实操,并理解每一步背后的数学原理与代码实现。

万圣节南瓜灯照片,ML-For-Beginners 回归模块的主题配图

模块主题与数据集背景

回归(Regression)模型的核心能力是发现变量之间的关系,并据此预测连续数值——例如长度、温度、年龄或价格。在分析数据点的过程中,模型会逐步揭示特征与目标之间的关联规律。

ML-For-Beginners 选择了北美南瓜市场作为贯穿本模块的实战案例:在北美,南瓜常在万圣节被雕刻成"杰克灯"(Jack-o'-lanterns),围绕这种蔬菜的市场数据天然适合用回归模型来分析。模块的原始设定文档位于 Regression 模块 README,其中明确了学习主线——理解线性回归与逻辑回归的区别,并掌握何时应该选择其中哪一种

整个模块共享一份真实公开数据集 US-pumpkins.csv

  • 数据来源是美国农业部(USDA)发布的 Specialty Crops Terminal Markets Standard Reports 终端市场标准报告,属于公共领域数据;
  • 数据集包含 1758 行(约 1757 条记录)美国各城市南瓜终端市场报价,字段包括 City Name(城市)、Package(包装规格)、Variety(品种)、Grade(等级)、Date(日期)、Low Price/High Price(低/高报价)、Origin(产地)、Item Size(大小)、Color(颜色)等 20 多个列;
  • 数据原始形态较为"混乱":存在大量空值、字符串与数值混杂、以及像 Package 这样"bushel"(蒲式耳)、"bins"(箱)、"sacks"(袋)混合的规格列——这正是本模块刻意保留的:课程要教你把一份不规整的真实数据一步步加工成可以建模的结构。

ML-For-Beginners 回归模块速记图(Sketchnote),概括了回归方法要点

模块结构与四节课程

按照 模块 README 中的课程目录,本模块共四节,每节都配有可运行的 notebook.ipynb、R 语言版本讲义和课后作业:

课程 主题 核心内容
1-Tools 工具准备 Python、VS Code、Jupyter Notebook、Scikit-learn 安装与首个线性回归模型
2-Data 数据管理 Pandas 数据清洗、蒲式耳价格归一化、Matplotlib/Seaborn 可视化
3-Linear 线性与多项式回归 最小二乘法原理、相关性分析、One-hot 编码、四种模型对比
4-Logistic 逻辑回归 二分类预测南瓜颜色、特征编码、混淆矩阵、ROC/AUC 评估

每节课后还附有 作业文件(如 Linear 课程要求自选题材复现线性/多项式回归并论证选型理由),以及 R 语言版讲义,方便不同技术栈的读者对照学习。

第一课:搭建机器学习环境并训练首个模型

回归建模的第一步是把本地开发环境配置到位。Tools 课程给出的四步准备流程是:

  1. 安装 Python:确保本机有 Python 环境(多数系统已自带)。由于不同工具链可能要求不同 Python 版本,建议在虚拟环境(venv)中工作,避免版本冲突;
  2. 安装 Visual Studio Code:本课程所有笔记本都在 VS Code 中运行,需先配置好 Python 开发支持;
  3. 安装 Scikit-learn:注意必须使用 Python 3,Apple M1 芯片的 Mac 有额外的安装说明;
  4. 安装 Jupyter Notebook:数据科学家最常用 .ipynb 笔记本文件作为交互式工作环境,代码块与 Markdown 说明可以交错书写,非常适合实验性、研究性的机器学习任务。

环境就绪后,课程立刻用 Scikit-learn 内置的糖尿病数据集完成了一次最小闭环,这也是理解 Scikit-learn "load → split → fit → predict" 工作流的最佳样本。糖尿病数据集包含 442 条样本、10 个特征变量(age 年龄、bmi 体质指数、bp 平均血压、s1 T-细胞等),目标值 y 是疾病进展程度(disease progression)——这正是"给定患者指标组合,预测一个数值"的典型回归场景。

完整代码如下(源自 Tools 课程 notebook 教程):

# 1. 导入依赖
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets, linear_model, model_selection

# 2. 加载糖尿病数据集。return_X_y=True 表示返回 (X, y) 元组:
#    X 为 442x10 的特征矩阵,y 为回归目标
X, y = datasets.load_diabetes(return_X_y=True)
print(X.shape)   # (442, 10)
print(X[0])

# 3. 只取第 3 列(BMI)作为特征,并 reshape 成 2D 数组供绘图/建模
X = X[:, 2]
X = X.reshape((-1, 1))

# 4. 按 67%/33% 划分训练集与测试集
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.33)

# 5. 训练线性回归模型:fit() 是几乎所有 ML 库中训练模型的通用入口
model = linear_model.LinearRegression()
model.fit(X_train, y_train)

# 6. 用测试集生成预测值
y_pred = model.predict(X_test)

# 7. 绘图:散点 + 回归线
plt.scatter(X_test, y_test, color='black')
plt.plot(X_test, y_pred, color='blue', linewidth=3)
plt.xlabel('Scaled BMIs')
plt.ylabel('Disease Progression')
plt.title('A Graph Plot Showing Diabetes Progression Against BMI')
plt.show()

几个值得注意的工程细节:

  • X.reshape((-1, 1)) 中的 -1 让 NumPy 自动推断行数,把一维特征向量变成 N×1 的二维矩阵——线性回归的输入必须是二维数组,每一行对应一条样本的特征向量;
  • train_test_splittest_size=0.33 表示 33% 数据留作验证,确保模型评估基于训练时未见过的数据
  • model.fit() / model.predict() 构成了 Scikit-learn 估算器(estimator)API 的核心约定,后续课程中的所有模型(包括逻辑回归)都沿用这套接口。

课程同时点出了回归方法选择的第一性原则:想预测数值(例如根据年龄估计身高),用线性回归;想预测类别归属(例如判断某种菜系是否为素食),用逻辑回归。这一区分贯穿整个模块,也决定了后面两节课的走向。该课作业 assignment.md 要求改用 Scikit-learn 内置的 Linnerud 数据集(20 名中年男性的三组锻炼数据与三组生理指标),自行描述如何建模"腰围与仰卧起坐次数"的关系,是检验是否理解多目标回归的好练习。

第二课:把"混乱"的南瓜数据清洗成可建模结构

Data 课程处理的核心问题是:如何为"预测给定月份南瓜的售价"这一回归问题构造数据。打开 US-pumpkins.csv 会立刻发现原始数据的问题——空值、字符串与数值混杂、包装规格不统一。课程给出的完整清洗流水线是:

第一步:读取数据并检查缺失。

import pandas as pd
pumpkins = pd.read_csv('../data/US-pumpkins.csv')
pumpkins.head()              # 查看前 5 行

pumpkins.isnull().sum()      # 统计各列缺失值数量

第二步:只保留任务需要的列。 使用 loc 按行、列索引从原 DataFrame 中提取子集(: 表示所有行):

columns_to_select = ['Package', 'Low Price', 'High Price', 'Date']
pumpkins = pumpkins.loc[:, columns_to_select]

第三步:构造目标列。 用低/高报价的平均值作为单条记录的价格,并把日期(美式 MM/DD/YYYY 格式)转换为月份:

price = (pumpkins['Low Price'] + pumpkins['High Price']) / 2
month = pd.DatetimeIndex(pumpkins['Date']).month

第四步:解决"蒲式耳规格不一致"问题。 这是本课最有代表性的数据洞察:南瓜的计价单位五花八门——按 1 1/9 蒲式耳箱、1/2 蒲式耳、按个(EACH)、按箱(PER BIN)出售。原始数据中凡 Unit of Sale 为 'EACH' 或 'PER BIN' 的记录,其 Package 也相应按英寸/箱/个计价。为了统一,课程用字符串过滤只保留按蒲式耳计价的记录:

pumpkins = pumpkins[pumpkins['Package'].str.contains('bushel', case=True, regex=True)]

过滤后只剩约 415 行数据。由于蒲式耳本身是体积单位(一蒲式耳番茄约 56 磅,而菠菜只有约 20 磅),不同规格箱(1 1/9 bushel 与 1/2 bushel)对应的价格不能直接比较,需要按规格归一化为"每蒲式耳价格":

new_pumpkins = pd.DataFrame({'Month': month, 'Package': pumpkins['Package'],
                             'Low Price': pumpkins['Low Price'],
                             'High Price': pumpkins['High Price'], 'Price': price})

new_pumpkins.loc[new_pumpkins['Package'].str.contains('1 1/9'), 'Price'] = price / (1 + 1/9)
new_pumpkins.loc[new_pumpkins['Package'].str.contains('1/2'), 'Price'] = price / (1/2)

课程还留了一个分析性思考:为什么按半蒲式耳出售的南瓜(多为小型南瓜)单价明显更贵?——因为一个半蒲式耳箱里能装下的小南瓜数量远多于大南瓜,"空心"利用率更高。这一案例说明:理解数据的业务含义与清洗技术同等重要

可视化:从 Matplotlib 到 Seaborn

清洗完成后,课程用两组工具库做探索性可视化,其目的不只是"画得好看",而是判断数据是否适合某种机器学习方法——例如散点若近似落在一条直线上,就是线性回归的良好候选信号。

Matplotlib 部分先画散点图(按月份分布的价格点),再画按月均价的分组柱状图:

import matplotlib.pyplot as plt

# 基础散点:展示性有限,只是把点铺开
plt.scatter(price, month)
plt.show()

# 按月取均值的柱状图:信息量显著更高
new_pumpkins.groupby(['Month'])['Price'].mean().plot(kind='bar')
plt.ylabel("Pumpkin Price")

柱状图揭示了一个清晰结论:南瓜均价在 9 月和 10 月达到峰值——与万圣节采购季节吻合。

Seaborn 是构建在 Matplotlib 之上、专为统计可视化设计的库,能直接接收 Pandas DataFrame 并套用美观的默认样式,代码量大幅减少:

import seaborn as sns

# 关系图(散点):直接传列名与 DataFrame
sns.relplot(x="Price", y="Month", data=new_pumpkins)

# 切换为折线图,自动带置信区间阴影
sns.relplot(x="Price", y="Month", kind="line", data=new_pumpkins)

# 分类柱状图:自动完成分组聚合
sns.catplot(x="Month", y="Price", data=new_pumpkins, kind="bar")

# 相关性热图:一次看清所有数值列两两之间的相关强度
correlations = new_pumpkins[['Month', 'Low Price', 'High Price', 'Price']].corr()
sns.heatmap(correlations, annot=True, cmap="coolwarm")

热图给出了一个反直觉但关键的教学点:Low PriceHigh Price 几乎完全相关,而 Month 与价格的线性相关却很弱——尽管柱状图明明显示出 9、10 月的季节性峰值。原因是相关系数只度量直线型关系,无法捕捉季节性这类非线性模式。因此课程强调:在决定建模特征之前,应同时查看热图与分组图,两种视角互为补充。

第三课:线性回归、多项式回归与特征工程

Linear 课程是全模块数学浓度最高的一课,围绕"预测南瓜价格随时间如何变化"训练了四个逐步改进的模型,最终把模型质量提升到 97% 的决定系数。

最小二乘回归的数学基础

线性回归要画出的是一条"最佳拟合线"(line of best fit),其数学表达为:

Y = a + bX

其中 X 是解释变量(explanatory variable),Y 是因变量(dependent variable),b 是斜率,a 是 Y 轴截距(即 X = 0Y 的取值)。这条线由**最小二乘法(Least-Squares Regression)**确定:对每个数据点,测量它到回归线的垂直距离(残差 residual),然后把所有残差平方求和,寻找使这个总和最小的那条线。

残差为什么要平方?课程给出两个理由:

  1. 幅度优先于方向:-5 与 +5 的误差应同等对待,平方使所有值变为正数;
  2. 惩罚离群点:平方放大了大误差的权重,迫使直线更贴近离群较远的点。

映射回南瓜问题:"预测某月每蒲式耳南瓜价格"中,X 是价格、Y 是销售月份(课程中的建模取向)。

相关性:先找值得建模的关系

建模前先验证相关性。课程加载了预清洗好的 new_pumpkins 数据框(含 IDMonthDayOfYearVarietyCityPackageLow PriceHigh PricePrice 列),其中 DayOfYear 由如下表达式计算:

day_of_year = pd.to_datetime(pumpkins['Date']).apply(lambda dt: (dt - datetime(dt.year, 1, 1)).days)

直接计算全量数据的相关系数,结果并不理想:

print(new_pumpkins['Month'].corr(new_pumpkins['Price']))        # 约 -0.15
print(new_pumpkins['DayOfYear'].corr(new_pumpkins['Price']))    # 约 -0.17

全量数据中 Month/DayOfYear 与价格的相关性都很弱,因为不同品种的价格区间形成了明显的簇。课程用按品种着色的散点图验证了这一点:

ax = None
colors = ['red', 'blue', 'green', 'yellow']
for i, var in enumerate(new_pumpkins['Variety'].unique()):
    df = new_pumpkins[new_pumpkins['Variety'] == var]
    ax = df.plot.scatter('DayOfYear', 'Price', ax=ax, c=colors[i], label=var)

结论:品种对价格的影响远大于销售日期。于是聚焦到单个品种 PIE TYPE 重新计算相关性,得到约 -0.27——此时训练预测模型才有意义。线性回归对缺失值不鲁棒,所以先做 pie_pumpkins.dropna(inplace=True) 清洗(或用列均值填充)。

简单线性回归的完整训练流程

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

# 分离特征与目标,注意 reshape(-1, 1) 保证输入是 N×1 二维数组
X = pie_pumpkins['DayOfYear'].to_numpy().reshape(-1, 1)
y = pie_pumpkins['Price']

# 训练/测试集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# 训练
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)

# 系数解读:lin_reg.coef_ 约 -0.017(每天约降 2 美分)
# 截距解读:lin_reg.intercept_ 约 21(年初的基准价格)

# 用 RMSE 评估
pred = lin_reg.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, pred))
print(f'RMSE: {rmse:3.3} ({rmse/np.mean(pred)*100:3.3}%)')

# 决定系数(coefficient of determination)
score = lin_reg.score(X_train, y_train)
print('Model determination: ', score)

评估结果的解读要点:

  • RMSE(均方根误差)约为 2 美元,占预测均值的 ~17%——对于"单变量线性"而言精度不够;
  • 决定系数score)约 0.06:取值为 0 表示模型完全没用到输入信息(退化为输出均值的"最劣线性预测器"),为 1 表示完美预测。0.06 说明单靠日期解释不了多少价格波动。

多项式回归:拟合非线性关系

当变量间关系无法用直线表达时(价格显然会随季节波动),可以尝试多项式回归——它创建一条曲线来拟合非线性数据。Scikit-learn 的管道 API(pipeline)可以把"特征变换 + 回归"串成单一估算器:

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

pipeline = make_pipeline(PolynomialFeatures(2), LinearRegression())
pipeline.fit(X_train, y_train)

pred = pipeline.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, pred))
print(f'RMSE: {rmse:3.3} ({rmse/np.mean(pred)*100:3.3}%)')

score = pipeline.score(X_train, y_train)
print('Model determination: ', score)

PolynomialFeatures(2) 表示加入输入数据的全部二阶多项式:单变量时只是 DayOfYear²(得到一个带极值的抛物线);若是两个变量 X、Y,则会扩展出 XY。画平滑拟合曲线时用 np.linspace 生成均匀取值的输入范围,而不是直接画无序的测试散点(否则会画出锯齿线):

X_range = np.linspace(X_test.min(), X_test.max(), 100).reshape(-1, 1)
y_range = pipeline.predict(X_range)

plt.scatter(X_test, y_test)
plt.plot(X_range, y_range)

多项式模型把 RMSE 略降到 2.73(17.0%)、决定系数提升到 0.08,改善有限——课程由此引出真正有效的方向:把更多特征放进模型。有趣的是拟合出的曲线最低点出现在万圣节前后,与"节日后需求回落"的直觉吻合。

类别特征编码:从 8% 到 97% 的关键一跃

要让同一个模型预测所有品种的价格,必须处理 Variety 这类非数值列。课程对比了两种编码方式:

  • 数值编码(按品种建索引表):不推荐用于线性回归,因为回归会把索引的数值本身当作连续量乘系数,而索引序号与价格之间显然不存在线性关系;
  • One-hot 编码(独热编码):把 Variety 列拆成每品种一列的 0/1 矩阵,回归模型会为每个品种学到独立的系数,代表该品种的"附加价格"。
pd.get_dummies(new_pumpkins['Variety'])

只把 One-hot 化的品种作为特征训练线性回归,决定系数就跳到 ~77%。再把 MonthCityPackage 等数值与类别特征用 join 合并进同一特征矩阵:

X = pd.get_dummies(new_pumpkins['Variety']) \
      .join(new_pumpkins['Month']) \
      .join(pd.get_dummies(new_pumpkins['City'])) \
      .join(pd.get_dummies(new_pumpkins['Package']))
y = new_pumpkins['Price']

全特征线性模型的 RMSE 降至 2.84(10.5%),决定系数达 0.94

组合拳:全特征 + 多项式回归

最终把 One-hot 类别特征、数值特征与多项式管道组合起来,完整代码见 Linear 课程

# 训练数据(同上一节组合特征)
X = pd.get_dummies(new_pumpkins['Variety']) \
      .join(new_pumpkins['Month']) \
      .join(pd.get_dummies(new_pumpkins['City'])) \
      .join(pd.get_dummies(new_pumpkins['Package']))
y = new_pumpkins['Price']

# 训练/测试划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# 搭建并训练管道
pipeline = make_pipeline(PolynomialFeatures(2), LinearRegression())
pipeline.fit(X_train, y_train)

# 预测与评估
pred = pipeline.predict(X_test)
rmse = mean_squared_error(y_test, pred, squared=False)
print(f'RMSE: {rmse:3.3} ({rmse/pred.mean()*100:3.3}%)')

score = pipeline.score(X_train, y_train)
print('Model determination: ', score)

课程给出的四个模型横向对比表,是理解"特征工程比模型复杂度更重要"的最好证据:

模型 RMSE 决定系数
DayOfYear 线性 2.77(17.2%) 0.07
DayOfYear 多项式 2.73(17.0%) 0.08
Variety 线性 5.24(19.7%) 0.77
全特征线性 2.84(10.5%) 0.94
全特征多项式 2.23(8.25%) 0.97

从单变量线性到全特征多项式,模型质量从 7% 提升到 97%——其中最大的贡献来自正确选择并编码特征,而非单纯增加模型阶数。该课作业(Build a Model)要求自选数据集完整复现这套流程,并在笔记本中论证技术选型与精度结论。

第四课:逻辑回归——用"回归"做二分类

Logistic 课程回答了一个常见的命名困惑:逻辑回归虽然名字里带"回归",本质上是一种基于线性方法的分类技术(课程原文也注明"仅为语言习惯才把它归入回归章节")。它用于发现模式并预测二元类别——例如"这个南瓜是白色还是非白色"(白色南瓜因不易雕刻,俗称"ghost pumpkin")。

任务定义与数据前提

  • 南瓜数据集里的 Color 列存在 ORANGEWHITE,以及少量 striped。因样本太少,striped 在去空后自然消失,任务被简化为二分类:White or Not White
  • 与线性回归"变量越相关效果越好"相反,逻辑回归不要求变量强相关,对弱相关数据也能工作;
  • 但逻辑回归依赖大量干净数据,本数据集约千行数据并非理想规模,评估时需注意这一点。

数据准备只保留分类任务需要的列并去空:

columns_to_select = ['City Name', 'Package', 'Variety', 'Origin', 'Item Size', 'Color']
pumpkins = full_pumpkins.loc[:, columns_to_select]
pumpkins.dropna(inplace=True)

先用 Seaborn 的 catplotVariety × Color 画计数图,直观查看品种与颜色的分布关系:

import seaborn as sns

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

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

特征与标签编码

机器学习算法只吃数字,字符串类别必须编码。课程演示了两种编码器的分工:

  1. 有序编码器(OrdinalEncoder):适合存在自然顺序的变量,如 Item Size。每个类别映射为其在序列中的序号:
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)
  1. 独热编码器(OneHotEncoder):适合无自然顺序的名义变量(城市、包装、品种、产地)。每个类别变成一个 0/1 二元列:
from sklearn.preprocessing import OneHotEncoder

categorical_features = ['City Name', 'Package', 'Variety', 'Origin']
categorical_encoder = OneHotEncoder(sparse_output=False)
  1. 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)
  1. 标签用 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_features.assign(Color=encoded_label)

编码后还可以用 Seaborn 的箱线图(kind="box")按品种分行比较 Item SizeColor 的分布,或用 swarmplot 观察二值类别的散点分布(数据点过多时需调小 size 参数以保证可读性)。

Sigmoid:逻辑回归的数学核心

🧮 逻辑回归基于**最大似然(maximum likelihood)**思想,核心是 Sigmoid 函数——一条 S 形曲线,把任意实数映射到 0 与 1 之间。若函数输出大于 0.5,该样本被判为类别 '1',否则为 '0'。这就是"线性组合 + 非线性映射"如何产出一个概率化分类边界的完整逻辑。

训练模型与评估指标

建模过程沿用了第一节课建立的 Scikit-learn 范式,只是把 LinearRegression 换成 LogisticRegression

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

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)

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

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

课程给出的实际运行结果(约 200 条测试样本):

              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

F1-score:  0.7457627118644068

报告里出现了明显的类别不平衡0(非白色)166 条,1(白色)仅 33 条,因此白色类别的召回率只有 0.67。这正是引入混淆矩阵的动机:

from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, predictions)
# array([[162,   4],
#        [ 11,  22]])

Scikit-learn 中混淆矩阵的行是真实标签、列是预测标签,四个格子对应:

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

由此可以精确复现分类报告中的指标,理解每个数字的来源:

  • Precision = TP / (TP + FP) = 22 / (22 + 4) ≈ 0.846:模型判为白色的样本里,真正是白色的比例;
  • Recall = TP / (TP + FN) = 22 / (22 + 11) ≈ 0.667:真实白色南瓜被找出来的比例;
  • F1-score = 2 × precision × recall / (precision + recall):两者的调和平均,最好为 1、最劣为 0;
  • Support:每个标签在测试集中的真实样本数;
  • Accuracy = (TP + TN) / (TP + TN + FP + FN):整体判对比例(此处 0.92,但被多数类抬高了,不能单独作为依据);
  • Macro Avg:各标签指标的不加权平均,忽略类别不平衡;Weighted Avg:按各标签 support 加权的平均,反映不平衡的真实影响。

课程留了一个关键思考题:如果业务上希望减少漏报(false negative,比如漏检白色南瓜),应该盯住哪个指标?答案是 Recall——这体现了"选评估指标取决于业务代价"的通用原则。

ROC 曲线与 AUC

最后一步用 ROC 曲线(Receiving 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()

再用 roc_auc_score 计算曲线下面积:

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

AUC 取值 0~1,越接近 1 越好(100% 正确的模型 AUC 为 1)。0.975 的 AUC 说明在约千行小数据集上,这个逻辑回归模型已经"相当不错"——尽管混淆矩阵显示白色类别召回偏低,两者并不矛盾:AUC 度量的是排序能力(把正例排在负例前面的概率),而分类报告度量的是固定 0.5 阈值下的硬分类表现。本课作业(Retrying this regression)要求回到完整南瓜数据(清洗与标准化后)重建逻辑回归模型,正是检验能否把"数据规模"这一变量控制起来的练习。

模块要点总结与学习路径

回顾 回归模块 的完整脉络,可以提炼出四条可迁移的建模经验:

  1. 环境与范式先行:Python + 虚拟环境 + VS Code + Jupyter 笔记本 + Scikit-learn 的组合,配合 fit/predict/score 的统一估算器 API,是传统机器学习(classic ML)的标准工作流;本模块刻意回避神经网络与深度学习(课程说明这部分内容留给 AI for Beginners 课程线);
  2. 数据质量决定模型上限:蒲式耳归一化、按品种聚焦、去空值、One-hot 与有序编码——南瓜案例中每次数据处理的改进都直接体现在决定系数从 0.06 到 0.97 的跃升上;
  3. 先看图再建模:散点判断线性趋势、柱状图发现季节性、热图度量线性相关强度——三种视图互补,相关系数捕捉不到非线性模式;
  4. 按问题类型选模型:预测连续数值用线性/多项式回归(目标:最小化误差,看 RMSE 与决定系数);预测类别归属用逻辑回归(本质是分类,看混淆矩阵、Precision/Recall、F1 与 ROC/AUC,并警惕类别不平衡)。

模块所有代码都可以在各课程目录下的 notebook.ipynb 中直接打开运行(例如 Tools 笔记本Data 笔记本Linear 笔记本Logistic 笔记本),需要 R 语言对照的读者可查阅各课 solution/R 目录下的 .Rmd 讲义(如 lesson_3.Rmd)。完成四节课与对应作业后,你已经具备了进入下一阶段——分类(Classification)模块——的全部前置能力。

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