查看全课大纲(第 96 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

数据预处理及特征工程

约 13 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

数据预处理及特征工程

小象实战讲义 · Python数据分析实战

在机器学习建模之前,我们通常需要对原始数据进行一系列的处理和转换,以使其更适合模型学习。本节将介绍数据预处理中的一个核心环节——数值型特征的归一化,并讲解其目的、方法以及在 scikit-learn 中的实现。

💡 核心导读

  • 为何归一化:消除不同特征间的量纲差异,可能提升模型性能并加速训练。
  • 如何归一化:掌握最大最小归一化(Min-Max Scaling)的原理与公式。
  • 实践要点:学习使用 sklearn.preprocessing.MinMaxScaler,并理解在训练集与测试集上正确应用归一化的流程。
  • 效果验证:通过 K 近邻(KNN)分类器的对比实验,直观感受归一化对模型性能的影响。

数值型特征与归一化

在数据分析与机器学习中,我们遇到的许多特征,如长度、宽度、像素值、价格等,都是以数字形式表示的,这类特征被称为数值型特征。然而,不同特征的数值范围(量纲)可能存在巨大差异。例如,在预测房价时,“房屋年龄”可能以年为单位(0-50),而“房屋面积”则以平方米为单位(50-200)。这种量纲差异可能会影响某些模型的训练效果。

为了解决这个问题,我们常常对数值型特征进行归一化(Normalization),也称为特征缩放(Feature Scaling)。其中,最大最小归一化是最常用的一种方法。它的目标是将原始数据线性变换,映射到[0, 1](或[-1, 1])的区间内。

归一化的作用主要有两方面:

  1. 可能提升模型性能:对于像线性回归、支持向量机(SVM)、K近邻(KNN)以及神经网络等模型,当所有特征处于相近的数值范围时,模型往往能更快地收敛并可能取得更好的性能。但请注意,归一化并不总是保证性能提升,其效果依赖于具体的数据集和模型。
  2. 加速模型训练:统一的数值范围有助于优化算法(如梯度下降)更稳定、高效地工作,从而缩短训练时间。

最大最小归一化的原理与实现

最大最小归一化的数学公式如下:

xnew=x−min⁡(X)max⁡(X)−min⁡(X)x_{\text{new}} = \frac{x - \min(X)}{\max(X) - \min(X)}

其中:

  • xx 是原始特征中的某个值。
  • min⁡(X)\min(X) 和 max⁡(X)\max(X) 分别是该特征列(或数据集)中的最小值和最大值。
  • xnewx_{\text{new}} 是归一化后的新值,其范围在 0 到 1 之间。

原理示例:假设某特征列数据为 [2, 3, 4, 4.5],其中最小值为2,最大值为4.5。

  • 对于最大值4.5:(4.5−2)/(4.5−2)=1(4.5 - 2) / (4.5 - 2) = 1
  • 对于中间值4:(4−2)/(4.5−2)=0.8(4 - 2) / (4.5 - 2) = 0.8
  • 对于最小值2:(2−2)/(4.5−2)=0(2 - 2) / (4.5 - 2) = 0

这样,所有原始数据都被映射到了[0, 1]区间。

在 Python 中,我们可以使用 scikit-learn 库的 MinMaxScaler 轻松实现归一化。下面我们通过一个完整的示例来演示。

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

# 构造一个简单的模拟数据集(替代原课中的鸢尾花数据集)
# 假设有两个特征:特征A范围约[0, 10],特征B范围约[0, 100]
np.random.seed(42)  # 固定随机种子以保证示例可复现
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
labels = np.random.choice([0, 1], size=n_samples)  # 随机生成二分类标签

X = np.column_stack([feature_a, feature_b])
y = labels

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)

print("训练集原始数据 (前5行):")
print(X_train[:5])
print("\n训练集特征范围:")
print(f"特征A: [{X_train[:, 0].min():.2f}, {X_train[:, 0].max():.2f}]")
print(f"特征B: [{X_train[:, 1].min():.2f}, {X_train[:, 1].max():.2f}]")
训练集原始数据 (前5行):
[[ 4.31945019 68.42330265]
 [ 2.12339111 94.88855373]
 [ 7.31993942 29.21446485]
 [ 1.81824967 96.56320331]
 [ 6.01115012 59.24145689]]

训练集特征范围:
特征A: [1.56, 9.70]
特征B: [4.65, 96.56]

可以看到,特征A和特征B的原始数值范围差异很大。接下来,我们使用 MinMaxScaler 进行归一化。

# 每个代码块自包含:重建数据(随机数流与首块一致,保证数据相同)
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

np.random.seed(42)
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
X = np.column_stack([feature_a, feature_b])
y = np.random.choice([0, 1], size=n_samples)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)

# 初始化MinMaxScaler,默认将数据缩放到[0, 1]区间
scaler = MinMaxScaler()

# 关键步骤1:在训练集上进行拟合和转换
# fit_transform 会先计算训练集的最小值、最大值,然后进行转换
X_train_scaled = scaler.fit_transform(X_train)

print("训练集归一化后数据 (前5行):")
print(X_train_scaled[:5])
print("\n训练集归一化后特征范围:")
print(f"特征A: [{X_train_scaled[:, 0].min():.2f}, {X_train_scaled[:, 0].max():.2f}]")
print(f"特征B: [{X_train_scaled[:, 1].min():.2f}, {X_train_scaled[:, 1].max():.2f}]")
训练集归一化后数据 (前5行):
[[0.3390212  0.69385919]
 [0.06919902 0.98178108]
 [0.70768094 0.26729672]
 [0.03170734 1.        ]
 [0.54687453 0.59396766]]

训练集归一化后特征范围:
特征A: [0.00, 1.00]
特征B: [0.00, 1.00]

现在,两个特征都被归一化到了[0, 1]区间。重要:对于测试集,我们必须使用从训练集学习到的转换规则(即同一个 scaler 对象),而不能重新拟合。

# 每个代码块自包含:重建数据并用训练集拟合 scaler(与上一块等价)
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

np.random.seed(42)
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
X = np.column_stack([feature_a, feature_b])
y = np.random.choice([0, 1], size=n_samples)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)

scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 回顾:scaler 在训练集上拟合

# 关键步骤2:在测试集上仅进行转换(使用训练集得到的scaler)
X_test_scaled = scaler.transform(X_test)

print("测试集原始数据:")
print(X_test)
print("\n测试集使用训练集scaler归一化后数据:")
print(X_test_scaled)
print("\n注意:测试集归一化后数值可能超出[0,1],如果测试集极值超出训练集范围。")
测试集原始数据:
[[ 8.66176146 51.42344384]
 [ 0.20584494 60.75448519]
 [ 1.5599452  78.51759614]
 [ 0.58083612 19.96737822]
 [ 5.98658484 36.63618433]]

测试集使用训练集scaler归一化后数据:
[[ 8.72545980e-01  5.08913598e-01]
 [-1.66403254e-01  6.10428262e-01]
 [-2.96355408e-05  8.03677459e-01]
 [-1.20329384e-01  1.66695423e-01]
 [ 5.43856276e-01  3.48039412e-01]]

注意:测试集归一化后数值可能超出[0,1],如果测试集极值超出训练集范围。

注意第2、4个样本:其归一化后的特征A值为负数(-0.166 和 -0.120),第3个样本的特征A约为 -0.00003——这是因为它们的原始特征A值低于训练集中的最小值。这正是“测试集归一化后数值可能超出[0,1]”的实例。这通常提示两种可能:1) 训练数据不足,未能覆盖全部数据分布;2) 该异常值可能是噪声数据,需要考虑清洗。

归一化效果可视化与模型对比

为了直观展示归一化前后的数据分布变化,我们可以进行可视化。同时,我们使用 K 近邻(KNN)分类器来对比归一化对模型性能的影响。

# 每个代码块自包含:重建数据与归一化结果
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import matplotlib
matplotlib.use('Agg')  # 使用无界面后端,保证代码可运行
import matplotlib.pyplot as plt

np.random.seed(42)
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
X = np.column_stack([feature_a, feature_b])
y = np.random.choice([0, 1], size=n_samples)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)
X_train_scaled = MinMaxScaler().fit_transform(X_train)

# 可视化归一化前后的训练集数据(仅使用前两个特征)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))

# 归一化前
axes[0].scatter(X_train[:, 0], X_train[:, 1], c='blue', alpha=0.7)
axes[0].set_xlabel('Feature A (Original)')
axes[0].set_ylabel('Feature B (Original)')
axes[0].set_title('Before Normalization')
axes[0].grid(True, linestyle='--', alpha=0.5)

# 归一化后
axes[1].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c='red', alpha=0.7)
axes[1].set_xlabel('Feature A (Normalized)')
axes[1].set_ylabel('Feature B (Normalized)')
axes[1].set_title('After Normalization')
axes[1].grid(True, linestyle='--', alpha=0.5)
axes[1].set_xlim(-0.1, 1.1)
axes[1].set_ylim(-0.1, 1.1)

plt.tight_layout()
# 在讲义中,我们通常不实际保存图片,此处仅作演示,读者可自行使用 plt.show() 或在 Notebook 中运行查看
# plt.savefig('normalization_comparison.png')
plt.close(fig)  # 释放图形资源
print("归一化前后数据分布对比图已生成。")
归一化前后数据分布对比图已生成。

接下来,我们使用 KNN 模型进行性能对比。KNN 算法基于样本间的距离(如欧氏距离)进行分类,因此特征的量纲对其影响显著。

# 每个代码块自包含:重建数据、归一化并对比 KNN 性能
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier

np.random.seed(42)
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
X = np.column_stack([feature_a, feature_b])
y = np.random.choice([0, 1], size=n_samples)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)

scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 使用 K=1 的 KNN 模型
knn_model = KNeighborsClassifier(n_neighbors=1)

# 在未归一化的数据上训练和测试
knn_model.fit(X_train, y_train)
score_original = knn_model.score(X_test, y_test)

# 在归一化的数据上训练和测试
knn_model.fit(X_train_scaled, y_train)
score_scaled = knn_model.score(X_test_scaled, y_test)

print(f"KNN (K=1) 在未归一化数据上的测试准确率: {score_original:.2%}")
print(f"KNN (K=1) 在归一化数据上的测试准确率: {score_scaled:.2%}")
KNN (K=1) 在未归一化数据上的测试准确率: 40.00%
KNN (K=1) 在归一化数据上的测试准确率: 20.00%

在这个示例中,归一化后模型的准确率反而下降了(40% → 20%)。这并不意外:本例的标签是随机生成的,特征与标签之间没有真实关联,归一化只是让距离计算更均衡,无法凭空创造不存在的规律——正如核心导读所说,归一化并不保证性能提升,其效果依赖于数据本身。在真实数据中(特征与标签确有关联、且大量纲特征在距离计算中“淹没”了小量纲特征的贡献时),对 KNN 这类距离敏感的模型,归一化通常是重要且常用的预处理步骤;但任何预处理的效果,都应像这样通过对比实验来验证。另外,K 值(n_neighbors)的选择也会影响结果,这属于模型超参数调优的范畴,我们将在后续课程中深入探讨。

📝 动手练一练

  1. 概念辨析:为什么在测试集上进行归一化时,只能使用 scaler.transform(),而不能再次使用 scaler.fit_transform()?如果分别对训练集和测试集进行 fit_transform,可能会导致什么问题?
👉 点击查看参考答案

在测试集上再次使用 fit_transform 会基于测试集数据重新计算最小值和最大值,从而得到一套与训练集不同的缩放规则。这破坏了模型训练与测试时数据变换的一致性,导致模型在测试集上的预测是基于一种“未曾见过”的数据分布进行的,其结果将失去可比性和可靠性,无法客观评估模型泛化能力。更严重的是,如果上线部署,新来的数据也会被错误地变换。

  1. 动手实验:将上面代码中的 KNN 模型的 n_neighbors 参数改为 3 或 5,重新运行模型训练和评估,观察归一化对性能的影响(提升、持平还是下降)。这与本节主实验的结论一致吗?思考这可能说明什么?
👉 点击查看参考答案
# 读者可尝试修改参数并运行(每个代码块自包含)
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier

np.random.seed(42)
n_samples = 20
feature_a = np.random.uniform(0, 10, n_samples)
feature_b = np.random.uniform(0, 100, n_samples)
X = np.column_stack([feature_a, feature_b])
y = np.random.choice([0, 1], size=n_samples)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=10)
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

knn_model_k3 = KNeighborsClassifier(n_neighbors=3)
knn_model_k3.fit(X_train, y_train)
score_orig_k3 = knn_model_k3.score(X_test, y_test)

knn_model_k3.fit(X_train_scaled, y_train)
score_scaled_k3 = knn_model_k3.score(X_test_scaled, y_test)
print(f"K=3, 原始数据准确率: {score_orig_k3:.2%}, 归一化后准确率: {score_scaled_k3:.2%}")
K=3, 原始数据准确率: 60.00%, 归一化后准确率: 20.00%

运行结果显示,K=3 时原始数据准确率 60%、归一化后 20%——在这份随机标签的模拟数据上,归一化带来的依然是“下降”。结合正文的分析可以得出:归一化的效果并非绝对,它依赖于具体的模型算法、参数以及数据本身的特性(尤其是特征与标签之间是否存在真实关联)。实践中,我们需要通过这样的对比实验来验证每个预处理步骤的必要性。

本章小结

本节我们深入探讨了数据预处理中的关键步骤——数值型特征的归一化。我们理解了归一化旨在消除量纲差异,可能提升模型性能与训练效率。重点掌握了最大最小归一化的原理、scikit-learn 中的 MinMaxScaler 使用方法,以及必须在训练集上拟合缩放器,并将其一致地应用于测试集这一核心实践原则。

📋 行动清单

  • 在你的下一个机器学习项目中,尝试对数值型特征进行归一化,并对比模型在归一化前后的性能差异。
  • 深入阅读 scikit-learn 官方文档中 preprocessing 模块的其他功能,如标准化(StandardScaler)、鲁棒缩放(RobustScaler)等。
  • 思考:如果你的数据集中同时存在数值型特征和分类型特征(如文字标签),该如何进行统一的预处理?这引出了我们下一节将要讨论的特征工程的其他话题。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 本节课件:数据预处理及特征工程
    PDF · 327KB
    下载
  • 全套课件打包(第1-5章)
    ZIP · 12.8MB
    下载
  • 全套课件打包(第6-8章)
    ZIP · 15MB
    下载
  • 实战数据集:AppleStore 应用商城分析
    ZIP · 329KB
    下载
  • 实战数据集:女性服装电商分析
    ZIP · 2.8MB
    下载
  • Python 数据分析环境搭建指南
    PDF · 2MB
    下载
  • Scrapy 安装教程
    PDF · 12.7MB
    下载
  • 附加实战项目:AppleStore 应用商城数据分析
    ZIP · 0.3MB · ipynb + CSV 数据
    下载
  • 附加实战项目:银行电话营销数据分析
    ZIP · 0.4MB · ipynb + CSV 数据
    下载
  • 附加实战项目:女性服装电商评论数据分析
    ZIP · 2.7MB · ipynb + CSV 数据
    下载
  • 附加实战项目:美国化学学会杂志数据分析
    ZIP · 34.2MB · ipynb + SQLite 数据库
    下载