查看全课大纲(第 96 / 101 节)
- 1.数据分析基本概念
- 2.学习数据分析的一般路线
- 3.数据分析的流程
- 4.数据类型
- 5.环境部署(1)
- 6.环境部署(2)
- 7.课程介绍
- 8.TXT文件操作
- 9.JSON文件操作
- 10.CSV文件操作
- 11.Excel文件操作
- 12.数据库及SQL常用语法
- 13.数据库基本操作
- 14.数据库多表连接
- 15.实战:欧洲职业足球数据库分析
- 16.爬虫简介
- 17.URL管理模块
- 18.网页下载模块
- 19.网页解析模块(1)
- 20.网页解析模块(2)
- 21.Scrapy简介
- 22.Scrapy使用步骤(1)
- 23.Scrapy使用步骤(2)
- 24.Scrapy使用步骤(3)
- 25.Scrapy使用步骤(4)
- 26.实战:获取国内城市空气质量指数数据
- 27.NumPy和SciPy介绍
- 28.多维数组
- 29.多维数组操作
- 30.NumPy的常用方法
- 31.向量化介绍
- 32.向量化及通用函数
- 33.实战:2016美国大选分析
- 34.数据结构-Series
- 35.数据结构-DataFrame
- 36.数据结构-Index
- 37.Series的索引操作
- 38.DataFrame的索引操作
- 39.索引操作总结
- 40.运算与对齐
- 41.函数应用操作(1) -- map
- 42.函数应用操作 (2) -- apply applymap
- 43.文件读写操作
- 44.排序操作
- 45.数据清洗--处理缺失数据
- 46.数据清洗--处理重复数据
- 47.数据清洗--替换数据
- 48.常用统计方法(1) -- describe quantile
- 49.常用统计方法(2) -- sum mean median count
- 50.常用统计方法(3) -- max min idxmax idxmin
- 51.常用统计方法(4) -- mad var std cumsum
- 52.实战:全球食品数据分析
- 53.层级索引
- 54.分组与聚合介绍
- 55.分组操作(1) -- GroupBy对象及常用聚合操作
- 56.分组操作(2) -- 自定义分组及聚合操作
- 57.透视表介绍
- 58.透视表操作
- 59.数据规整(1) -- 数据合并concat
- 60.数据规整(2) -- 数据连接merge
- 61.数据重构(3) -- 数据重构stack unstack
- 62.实战:互联网电影资料库分析
- 63.探索性数据分析EDA介绍
- 64.EDA的目的
- 65.EDA常用工具
- 66.Matplotlib绘图基本介绍
- 67.Matplotlib画布
- 68.散点图和柱状图的绘制
- 69.直方图的绘制
- 70.矩阵绘图
- 71.子图的使用
- 72.Matplotlib颜色、标记、线型
- 73.Matplotlib坐标刻度、标签、图例、标题
- 74.Seaborn介绍
- 75.数据集分布可视化(1) -- 单变量分布、双变量分布
- 76.数据集分布可视化(2) -- 变量关系可视化
- 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
- 78.交互式数据可视化工具Bokeh介绍
- 79.Bokeh绘制散点图、柱状图、盒子图、弦图
- 80.Bokeh绘制常用图形元素
- 81.3D绘图 -- mplot3d
- 82.3D曲线可视化
- 83.3D散点图可视化
- 84.3D柱状图可视化
- 85.Pandas绘图
- 86.实战:Lending Club借贷数据探索性分析及可视化
- 87.机器学习介绍及应用场景
- 88.机器学习建模介绍 (1) -- 分类
- 89.机器学习建模介绍 (2) -- 回归
- 90.机器学习建模介绍 (3) -- 聚类
- 91.机器学习分类
- 92.机器学习工具scikit-learn
- 93.使用scikit-learn的流程
- 94.数据集准备及划分
- 95.模型选择
- 96.数据预处理及特征工程
- 97.过拟合与欠拟合
- 98.模型调参介绍
- 99.模型调参方法
- 100.模型测试及评价
- 101.实战:通过移动设备行为数据预测性别和年龄
数据预处理及特征工程
约 13 分钟
数据预处理及特征工程
小象实战讲义 · Python数据分析实战
在机器学习建模之前,我们通常需要对原始数据进行一系列的处理和转换,以使其更适合模型学习。本节将介绍数据预处理中的一个核心环节——数值型特征的归一化,并讲解其目的、方法以及在 scikit-learn 中的实现。
💡 核心导读
- 为何归一化:消除不同特征间的量纲差异,可能提升模型性能并加速训练。
- 如何归一化:掌握最大最小归一化(Min-Max Scaling)的原理与公式。
- 实践要点:学习使用
sklearn.preprocessing.MinMaxScaler,并理解在训练集与测试集上正确应用归一化的流程。 - 效果验证:通过 K 近邻(KNN)分类器的对比实验,直观感受归一化对模型性能的影响。
数值型特征与归一化
在数据分析与机器学习中,我们遇到的许多特征,如长度、宽度、像素值、价格等,都是以数字形式表示的,这类特征被称为数值型特征。然而,不同特征的数值范围(量纲)可能存在巨大差异。例如,在预测房价时,“房屋年龄”可能以年为单位(0-50),而“房屋面积”则以平方米为单位(50-200)。这种量纲差异可能会影响某些模型的训练效果。
为了解决这个问题,我们常常对数值型特征进行归一化(Normalization),也称为特征缩放(Feature Scaling)。其中,最大最小归一化是最常用的一种方法。它的目标是将原始数据线性变换,映射到[0, 1](或[-1, 1])的区间内。
归一化的作用主要有两方面:
- 可能提升模型性能:对于像线性回归、支持向量机(SVM)、K近邻(KNN)以及神经网络等模型,当所有特征处于相近的数值范围时,模型往往能更快地收敛并可能取得更好的性能。但请注意,归一化并不总是保证性能提升,其效果依赖于具体的数据集和模型。
- 加速模型训练:统一的数值范围有助于优化算法(如梯度下降)更稳定、高效地工作,从而缩短训练时间。
最大最小归一化的原理与实现
最大最小归一化的数学公式如下:
其中:
- 是原始特征中的某个值。
- 和 分别是该特征列(或数据集)中的最小值和最大值。
- 是归一化后的新值,其范围在 0 到 1 之间。
原理示例:假设某特征列数据为 [2, 3, 4, 4.5],其中最小值为2,最大值为4.5。
- 对于最大值4.5:
- 对于中间值4:
- 对于最小值2:
这样,所有原始数据都被映射到了[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)的选择也会影响结果,这属于模型超参数调优的范畴,我们将在后续课程中深入探讨。
📝 动手练一练
- 概念辨析:为什么在测试集上进行归一化时,只能使用
scaler.transform(),而不能再次使用scaler.fit_transform()?如果分别对训练集和测试集进行fit_transform,可能会导致什么问题?
👉 点击查看参考答案
在测试集上再次使用 fit_transform 会基于测试集数据重新计算最小值和最大值,从而得到一套与训练集不同的缩放规则。这破坏了模型训练与测试时数据变换的一致性,导致模型在测试集上的预测是基于一种“未曾见过”的数据分布进行的,其结果将失去可比性和可靠性,无法客观评估模型泛化能力。更严重的是,如果上线部署,新来的数据也会被错误地变换。
- 动手实验:将上面代码中的 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 数据库