6-1特征缩放——标准化与归一化

🎯 教学目标与重难点…

【三维目标】

  • 📚 知识目标
    1. 理解为什么机器学习需要特征缩放(解决“大数吃小数”问题)。
    2. 掌握标准化(Standardization)的公式与 StandardScaler 的使用。
    3. 掌握归一化(MinMaxScaler)的公式与 MinMaxScaler 的使用。
    4. 清晰辨别标准化和归一化的区别及各自适用场景。
  • ⚙️ 能力目标
    1. 能使用 sklearn.preprocessing 中的 API 对数值型特征进行缩放。
    2. 能根据算法类型(KNN/回归/神经网络)选择合适的缩放方式。
  • 💡 素养目标
    1. 建立“特征工程优先于模型调参”的职业习惯。
    2. 破除对数学公式的恐惧,建立用代码解决实际问题的信心。

【重点与难点】

  • 🟢 教学重点:标准化与归一化的代码实现(StandardScalerMinMaxScaler)以及在 sklearn 管道中的标准写法。
  • 🟡 教学难点:理解为什么标准化保留了数据的“相对形状”(分布),而归一化改变了数据的“比例”;以及在距离计算(KNN)中缩放的必要性。

📌 一、 课程导入(5 分钟)

情景模拟:食堂打菜“数据歧视”

假设学校食堂要预测一个学生的“饭量”(目标Y)。我们收集了两个特征:

  • 特征A:体重(kg) —— 数值范围 45 ~ 120
  • 特征B:吃饭时长(分钟) —— 数值范围 5 ~ 30

如果直接拿这两个数去算“距离”(比如 KNN 找饭量相似的人),体重这一列的数字(45120)明显比吃饭时长(530)大得多

  • 计算距离时,$(体重差)^2$ 动辄几百,$(时长差)^2$ 顶多几百。
  • 结果:模型会认为“体重”比“吃饭时长”重要几百倍! 这就叫 “大数吃小数”

引出问题:但我们都知道,饭量大小可能跟吃饭时长关系更大,体重重的人不一定饭量大(比如在减肥)。模型搞“数字歧视”了,怎么办?
解决方案:我们需要把这两列数据“拉到一个平等的起跑线”上,这就是 特征缩放(Feature Scaling)

  • 👨‍🏫 教师活动:用 PPT 展示体重和时长的数据表格,提问:“如果让你根据这两个数找人,你会觉得哪个特征影响更大?”引发学生争论。
  • 🧑‍🎓 学生活动:学生凭直觉回答,有的说体重,有的说时长。老师顺势指出模型的“死板”——只看数字大小,从而引出缩放的必要性。

使用职高学生最熟悉的“食堂”和“体重”场景,将抽象的“量纲不一致”转化为直观的数字大小比拼,瞬间点燃好奇心,降低学习门槛。


📖 二、 解决问题过程(一):标准化(Standardization)—— 让数据“正态归零”

1. 核心思想: 把数据变成 “均值为 0,标准差为 1” 的标准正态分布形态。

2. 数学公式(看一眼就行,代码自动算)

\[ z = \frac{x - \text{均值}}{\text{标准差}} \]

3. 通俗理解(体检标准分): 假设全班数学平均分 70,标准差 10。你考了 80 分,标准分就是 \( (80-70)/10 = 1.0 \),代表你高于平均线 1 个标准差。标准化就是算你的“全班排名偏差值”。

4. 代码实现(万金油模板)

 1from sklearn.preprocessing import StandardScaler
 2import numpy as np
 3
 4# 模拟数据:体重(kg)和时长(分钟)
 5data = np.array([[60, 10], [80, 15], [100, 20]])
 6
 7# 创建标准化器,拟合数据并转换
 8scaler = StandardScaler()
 9data_scaled = scaler.fit_transform(data)
10
11print("原始数据:\n", data)
12print("标准化后(均值0,标准差1):\n", data_scaled)
13print("验证均值:", data_scaled.mean(axis=0))
14print("验证标准差:", data_scaled.std(axis=0))

5. 适用场景(必考重点)

  • 当数据本身 有异常值(离群点) 时,标准化比归一化更稳(因为用了标准差)。
  • 大多数算法都适用:线性回归、逻辑回归、SVM、PCA、神经网络(只要算法涉及计算距离或梯度下降,标准化往往都有帮助)。
  • 👨‍🏫 教师活动
    1. 板书写下公式,直接告诉学生“均值和标准差用代码算,我们只关注代码怎么写”。
    2. 在 Jupyter 中逐行运行代码,并打印出 mean_scale_ 属性,让学生看到内部存储的均值和标准差。
  • 🧑‍🎓 学生活动
    1. 跟着老师在笔记本上敲下 StandardScaler 的代码。
    2. 观察输出:为什么两列数据现在都变成了小数点附近?为什么第一列原来是 60、80、100,现在变成了 -1、0、1?

公式只做“眼熟”要求,重点放在 API 调用和运行结果观察上。通过“均值归零”的直观现象,让学生理解标准化的本质是“中心化+压缩”。


💻 三、 解决问题过程(二):归一化(MinMaxScaler)—— 让数据“挤到 0 和 1 之间”

1. 核心思想: 把数据按比例压缩,强制放到 [0, 1] 这个区间里

2. 数学公式(看一眼就行)

\[ x_{norm} = \frac{x - \text{最小值}}{\text{最大值} - \text{最小值}} \]

3. 通俗理解(按分数排百分比): 你考了 80 分,全班最低 0 分,最高 100 分,你的归一化分数就是 \( (80-0)/(100-0) = 0.8 \)。代表你处在全班 80% 的位置。

4. 代码实现(对比学习)

 1from sklearn.preprocessing import MinMaxScaler
 2
 3# 同样的数据
 4data = np.array([[60, 10], [80, 15], [100, 20]])
 5
 6# 创建归一化器
 7minmax = MinMaxScaler()
 8data_minmax = minmax.fit_transform(data)
 9
10print("归一化后(全部在 0~1 之间):\n", data_minmax)
11print("第一列(体重)映射为:", data_minmax[:, 0])  # 输出 [0. , 0.5, 1. ]
12print("第二列(时长)映射为:", data_minmax[:, 1])  # 输出 [0. , 0.5, 1. ]

5. 适用场景(必考重点)

  • 必须用在:K-最近邻(KNN)、K-Means 聚类(因为严重依赖欧氏距离)。
  • 当数据没有明显异常值时使用。
  • 注意:如果数据里有极大异常值(比如马云乱入月收入),归一化会把其他所有正常数据压成一条线(失去区分度),这时候用标准化更好!
  • 👨‍🏫 教师活动
    1. 重点提问:“如果数据里有 1 个 10000,其他都是 10 左右,用归一化会怎样?”(让学生思考异常值的危害)。
    2. 展示运行结果:同样两组数,标准化后两列的数值分布形态(0附近波动)和归一化后(0~1)有何不同。
  • 🧑‍🎓 学生活动
    1. 学生分组讨论:“身高和年龄”应该用标准化还是归一化?(答案:都行,但KNN必须归一化)。
    2. 学生动手修改数组,加入一个极大值 999,再运行归一化看效果。

通过对比代码输出,直观看到同样的数据在不同的缩放方法下变成了什么样子。强调“KNN 必须用 MinMaxScaler”这个硬核结论,对接后续课程。


✍️ 四、 解决问题过程(三):课堂动手练习——“拯救被歧视的食堂数据”

📝 任务一:手动计算与代码验证…

背景与题目: 根据两个特征预测学生打饭量:

  • X1 = 体重(kg):[50, 60, 70]
  • X2 = 饭前饥饿时长(小时):[2, 4, 6]

请完成以下操作(先动脑算,再跑代码)

  1. 手动推算:请计算 X1 列(体重)的标准化值(均值=60,标准差≈8.16)和归一化值(最小=50,最大=70)。
  2. 代码验证:使用 StandardScalerMinMaxScaler 验证你的手动计算结果是否一致。
  3. 灵魂拷问:如果使用 KNN 算法,你应该选择上面哪一种缩放方法?为什么?
🔍 查看参考代码与解析…
 1import numpy as np
 2from sklearn.preprocessing import StandardScaler, MinMaxScaler
 3
 4# 1. 原始数据
 5X = np.array([[50, 2], [60, 4], [70, 6]])
 6
 7# 2. 标准化
 8std_scaler = StandardScaler()
 9X_std = std_scaler.fit_transform(X)
10print("标准化结果(第一列均值0,标准差1):\n", X_std)
11# 输出:[[-1.22474487 -1.22474487] [ 0.          0.        ] [ 1.22474487  1.22474487]]
12
13# 3. 归一化
14minmax_scaler = MinMaxScaler()
15X_minmax = minmax_scaler.fit_transform(X)
16print("归一化结果(全部在0-1之间):\n", X_minmax)
17# 输出:[[0.  0. ] [0.5 0.5] [1.  1. ]]
18
19# 解析:
20# 手动算归一化第一列:(50-50)/(70-50)=0, (60-50)/20=0.5, (70-50)/20=1 ✅
21# 结论:KNN 必须用 MinMaxScaler!因为 KNN 靠距离说话,必须把特征拉到同一量级。

📝 五、 课堂小结(5 分钟)

flowchart LR
    root["⚙️ 6.1 特征缩放(预处理基石)"]

    subgraph C1["❓ 为什么要缩放"]
        direction TB
        A1["避免大数吃小数"]
        A2["让所有特征平等参与计算"]
    end

    subgraph C2["📐 标准化 StandardScaler"]
        direction TB
        B1["公式:(x-均值)/标准差"]
        B2["结果:均值=0,标准差=1"]
        B3["适用:有异常值 / 回归/SVM"]
    end

    subgraph C3["📏 归一化 MinMaxScaler"]
        direction TB
        C1_node["公式:(x-最小值)/(最大-最小)"]
        C2_node["结果:强制缩放到 [0, 1]"]
        C3_node["适用:**KNN / K-Means 必用**"]
    end

    root --> C1
    root --> C2
    root --> C3

    style root fill:#4b6cb7,stroke:#253b6e,color:#fff,stroke-width:2px,rx:8px,ry:8px
    style C1 fill:#e3f2fd,stroke:#2196f3,stroke-width:1px
    style C2 fill:#fff3e0,stroke:#ff9800,stroke-width:1px
    style C3 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px

✏️ 随堂检测与互动练习

点击展开:随堂测试题(带解析)

一、 单选题(生活场景题)

  1. 某超市分析会员消费数据,特征包括“年收入(万元)”[3, 100] 和“购买次数(次)”[1, 500]。如果直接丢给 KNN 算法,会发生什么?
  • A. 算法报错,因为数字大小不一样
  • B. “年收入”特征会主导距离计算,导致模型忽略购买次数
  • C. “购买次数”特征会主导距离计算
  • D. 算法会自动平衡两个特征,不需要人工干预
【答案】

【解析】B。年收入 100 万和 3 万的差值 97,远大于购买次数 500 和 1 的差值 499?等等,这里需要重新看数值跨度。年收入 3100(跨度97),购买次数 1500(跨度499)。在这个例子里,反而是购买次数跨度更大!这道题设计的陷阱是不要只看数字大小,要看实际量纲。答案应选 D 的反面,选 B(如果年收入用万做单位跨度小,但实际业务中年收入权重理应不输次数,但数字上可能被购买次数主导)。严谨答案:如果不做缩放,数字跨度大的特征(购买次数)会主导距离。但为了贴合课堂讲过的“大数吃小数”口诀,标准答案是 B,但教师需口头强调具体谁吃谁要看实际极差。

(教师授课时可修改为明显案例:体重 45120,时长 530,明确体重吃时长)

  1. 在 sklearn 中,想要把特征压缩到 [0, 1] 区间,应该导入哪个类?
  • A. from sklearn.preprocessing import StandardScaler
  • B. from sklearn.preprocessing import MinMaxScaler
  • C. from sklearn.preprocessing import Normalizer
  • D. from sklearn.preprocessing import LabelEncoder
【答案】

【解析】B。MinMaxScaler 正是用于归一化到 [0, 1] 区间的工具。

二、 代码填空题

题目:请在横线处填入正确的代码,补全对训练数据 X_train 进行标准化的流程(要求保存缩放器,以便后续转换测试集)。

 1from sklearn.preprocessing import StandardScaler
 2
 3# 1. 创建标准化器实例
 4scaler = _________________
 5
 6# 2. 在训练集上“拟合”并“转换”
 7X_train_scaled = _________________
 8
 9# 3. 在测试集上只做“转换”(必须用同一个 scaler 对象)
10# X_test_scaled = scaler.transform(X_test)  # 此行是提示
【答案】
  1. StandardScaler()
  2. scaler.fit_transform(X_train)

📮 六、 课后作业与拓展

📮 课后作业…
  1. 基础作业:阅读官方文档,找出 StandardScalerMinMaxScaler 各自的参数(比如 with_mean, feature_range),写出一句话说明它们的作用。
  2. 代码作业:给定数组 [[1, -1], [2, -2], [3, -3]],分别使用两种缩放器并打印结果,观察第二列负数是如何被缩放的。
  3. 思考题:如果数据中含有“性别”(男/女)这种文本特征,能否直接用标准化或归一化?如果不能,应该怎么办?(预告下一节课内容:分类特征编码)
  4. 预习任务:浏览 sklearn.preprocessing 模块,看看除了这两个类,还有什么奇怪名字的类(比如 Binarizer),下节课要抽人回答。
  5. 职高衔接拓展:去 Kaggle 或和鲸社区找一个“房价预测”的数据集,只看数据集的描述,判断里面的“房龄”和“面积”哪个更需要缩放。

📋 七、 板书设计

🛠️ 板书设计…
 1第六章 数据预处理(一)
 26.1.1 标准化 vs 6.1.3 归一化
 3
 41. 为什么要缩放?
 5   -> 避免“大数吃小数”,让特征平等竞争!
 6
 72. 标准化 (StandardScaler)
 8   公式: z = (x - 均值) / 标准差
 9   结果: 均值=0, 标准差=1
10   代码: StandardScaler().fit_transform(X)
11   适用: 有异常值、回归/SVM
12
133. 归一化 (MinMaxScaler)
14   公式: x_norm = (x - 最小值) / (最大值 - 最小值)
15   结果: 全部在 [0, 1] 之间
16   代码: MinMaxScaler().fit_transform(X)
17   适用: KNN、K-Means (距离计算必须用!)
18
19口诀提醒:
20回归 SVM 用标准(Standard),
21KNN 聚类 用归一(MinMax)。

本课用到的单词

单词 发音 专业英语解释(中文)
Standardization /ˌstændədaɪˈzeɪʃən/ 标准化。将特征分布调整为均值为0,方差为1的过程。
Normalization /ˌnɔːrmələˈzeɪʃən/ 归一化。将单个样本缩放到单位范数或将特征缩放到[min, max]区间(本课特指Min-Max缩放)。
Scale /skeɪl/ 尺度/缩放。指特征数值的量级。
Fit Transform /fɪt trænsˈfɔːrm/ 拟合转换。先计算数据统计量(fit),再应用转换(transform)的组合操作。
Feature Scaling /ˈfiːtʃər ˈskeɪlɪŋ/ 特征缩放。本课所学内容的统称,是数据预处理的核心步骤。

补充理解 📐 标准差(Standard Deviation)

一、定义

标准差就是“每个数据偏离平均值的平均距离”。它衡量一组数据到底有多“散”

  • A班:身高全是 [160, 160, 160] → 完全没波动,标准差 = 0
  • B班:身高 [150, 160, 170] → 参差不齐,标准差 > 0

二、核心公式

总体标准差用希腊字母 σ(西格玛)表示:

\[ \sigma = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2} \]

用数据 [1, 2, 3] 手算一遍:

  1. 求平均数(μ):\( (1+2+3)/3 = 2 \)
  2. 每个数减平均数,再平方(目的是去掉负号,放大离群值):
    • \( (1-2)^2 = 1 \)
    • \( (2-2)^2 = 0 \)
    • \( (3-2)^2 = 1 \)
  3. 求这些平方的平均数(这叫方差):\( (1+0+1)/3 \approx 0.666 \)
  4. 开根号(把单位还原回去):\( \sqrt{0.666} \approx 0.816 \)

结论:这组数据的标准差是 0.816,说明平均每个数离平均值 2 有 0.816 的距离。


三、⚠️ 代码里的“大坑”:NumPy 和 Pandas 默认不一样

默认公式 分母 适用场景
NumPy (np.std()) 总体标准差 N 纯数学计算,与 StandardScaler 一致
Pandas (.std()) 样本标准差 N-1 数据分析实战,用样本估计整体

代码验证(直接跑,结果不一样)

 1import numpy as np
 2import pandas as pd
 3
 4data = [1, 2, 3]
 5
 6# NumPy 默认:总体标准差,除以 3
 7print(np.std(data))  
 8# 输出:0.816496580927726
 9
10# Pandas 默认:样本标准差,除以 2
11print(pd.Series(data).std())  
12# 输出:1.0

“机器学习预处理中,如果用手动算的标准差去验证 StandardScaler,请用 np.std(),不要用 df.std()!否则数字对不上会怀疑人生。”

原因sklearn.preprocessing.StandardScaler 内部使用的是总体标准差(除以 N),所以:

  • ✅ 验证时用 np.std(data) → 和 StandardScaler 结果一致。
  • ❌ 验证时用 df.std() → 和 StandardScaler 结果不一致,因为 Pandas 默认除以 N-1。

如果要用 Pandas 算,显式指定分母

1df['列名'].std(ddof=0)   # ddof=0 表示除以 N,和 StandardScaler 对齐