Lesson 04
特征提取:把原始数据变成模型能用的数字
模型只认数字。本章讲如何把类别、文本和图像转成特征矩阵,以及如何用 ColumnTransformer 和 Pipeline 防止数据泄漏。
本章细分学习地图
特征提取是建模前最关键的环节。数据进模型之前必须全部变成数值,且预处理方式必须在 Pipeline 内完成以防泄漏。
| 模块 | 拆细学习单元 | 完成后应产出 | 掌握标准 |
|---|---|---|---|
| 类别编码 | OneHot、Ordinal、高基数处理。 | 编码前后对比表。 | 能根据类别特点选编码方式。 |
| 数值缩放 | StandardScaler、MinMaxScaler、RobustScaler。 | 缩放前后分布对比。 | 能说清缩放对距离模型和线性模型的影响。 |
| 文本特征 | BoW、停用词、词干、TF-IDF。 | 文本分类 Pipeline。 | 能构建从原始文本到预测的完整流程。 |
| 图像特征 | 像素展平、CNN 激活提取。 | 图像分类示例。 | 了解 sklearn 处理图像的边界。 |
| 组合预处理 | ColumnTransformer + Pipeline 防泄漏。 | 混合类型数据 Pipeline。 | 能解释为什么 fit 只能在训练集上调用。 |
学习目标
特征提取流程
本章完成度
已完成 0/4
特征提取的所有 fit(学均值、学词表、学类别映射)只能在训练集上做,测试集只能 transform。违反这一点就会产生数据泄漏。
类别编码
模型看不懂"红/绿/蓝"这样的字符串,必须转成数字。两种主流方式:
OneHotEncoder
每个类别变成一个 0/1 列。不引入顺序关系,但高基数类别会产生大量稀疏列。
OrdinalEncoder
把类别映射成整数 0, 1, 2…。适合有自然顺序的特征(如学历:小学 < 高中 < 本科)。
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
import numpy as np
colors = np.array([["红"], ["绿"], ["蓝"], ["红"]])
# OneHot:无序类别
ohe = OneHotEncoder(sparse_output=False)
print(ohe.fit_transform(colors))
# [[1, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]
# Ordinal:有序类别
sizes = np.array([["S"], ["M"], ["L"], ["XL"]])
oe = OrdinalEncoder(categories=[["S", "M", "L", "XL"]])
print(oe.fit_transform(sizes))
# [[0], [1], [2], [3]]
| 场景 | 推荐编码 | 注意事项 |
|---|---|---|
| 无序少量类别(颜色、城市) | OneHotEncoder | 类别多时列数爆炸,考虑 max_categories。 |
| 有序类别(等级、学历) | OrdinalEncoder | 需手动指定 categories 顺序。 |
| 高基数(邮编、用户 ID) | TargetEncoder 或哈希 | 直接 OneHot 会过拟合。 |
数值缩放
不同特征量纲不同时,距离模型(KNN/SVM/KMeans)和带正则的线性模型都需要先缩放。
| 缩放器 | 做法 | 适用场景 |
|---|---|---|
| StandardScaler | 减均值除标准差,变成均值 0、方差 1。 | 大多数场景的默认选择。 |
| MinMaxScaler | 线性缩放到 [0, 1]。 | 需要非负特征时(如神经网络输入)。 |
| RobustScaler | 用中位数和分位数缩放,抗离群点。 | 数据含大量极端值。 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
X = np.array([[1, 10000], [2, 20000], [3, 50000]])
print("StandardScaler:")
print(StandardScaler().fit_transform(X))
print("\nMinMaxScaler:")
print(MinMaxScaler().fit_transform(X))
文本特征
文本不能直接作为特征矩阵。最常用的做法是把每个文档转成词频向量(BoW)或 TF-IDF 权重向量。
CountVectorizer (BoW)
统计每个词出现的次数,生成稀疏矩阵。常见预处理:转小写、去停用词。
TfidfVectorizer (TF-IDF)
在 BoW 基础上降低高频通用词的权重,突出区分性强的词。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
docs = ["机器学习很有趣", "深度学习是机器学习的子集", "自然语言处理需要文本特征"]
labels = [0, 0, 1]
pipe = Pipeline([
("tfidf", TfidfVectorizer()),
("clf", MultinomialNB()),
])
pipe.fit(docs, labels)
print(pipe.predict(["机器学习的应用"])) # [0]
中文分词通常用 jieba;英文可设置 stop_words="english"。词干化(stemming)把 running/runs 归为 run,减少词表大小。
图像特征
scikit-learn 不是图像处理框架,但可以处理简单场景:把图像展平成像素向量,或用预训练 CNN 提取高层特征。
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
# 手写数字:8x8 像素展平成 64 维特征
X, y = load_digits(return_X_y=True)
print(f"形状: {X.shape}") # (1797, 64)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
model = SVC(kernel="rbf", random_state=42)
model.fit(X_train, y_train)
print(f"准确率: {model.score(X_test, y_test):.3f}")
像素展平只适用于极小的图像(如 8x8 / 28x28)。真实图像任务(如 ImageNet 分类)应使用深度学习框架(PyTorch / TensorFlow)。
ColumnTransformer 与 Pipeline
真实数据通常同时包含数值列和类别列。ColumnTransformer 让你对不同列施加不同预处理,再拼合成统一特征矩阵。
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
# 模拟混合类型数据
df = pd.DataFrame({
"age": [25, 32, 47, 51, 23],
"income": [30000, 50000, 80000, 120000, 28000],
"city": ["北京", "上海", "北京", "深圳", "上海"],
"bought": [0, 1, 1, 1, 0],
})
X = df[["age", "income", "city"]]
y = df["bought"]
preprocessor = ColumnTransformer([
("num", StandardScaler(), ["age", "income"]),
("cat", OneHotEncoder(handle_unknown="ignore"), ["city"]),
])
pipe = Pipeline([
("prep", preprocessor),
("clf", LogisticRegression(random_state=42)),
])
pipe.fit(X, y)
print("预测:", pipe.predict(X))
如果在拆分前对全部数据做 fit_transform,测试集的统计量(均值、词频)就泄漏进了训练过程。Pipeline 保证 fit 只在训练集执行,transform 在两边分别执行。
练习题
第 1 题:类别特征"学历"有三个值:高中、本科、硕士。应该用 OneHotEncoder 还是 OrdinalEncoder?为什么?
参考思路:学历有自然顺序(高中 < 本科 < 硕士),适合 OrdinalEncoder 并手动指定类别顺序。OneHot 也可以用但会丢失顺序信息。
第 2 题:为什么不能在 train_test_split 之前对整个数据集做 fit_transform?
参考思路:fit 会学到全局统计量(如均值、词表),测试集信息泄漏进训练过程,导致评估指标偏高、上线后性能下降。正确做法是先拆分再 fit,或用 Pipeline 自动管理。
检查点
离开本章前,请确认:
- 能区分 OneHotEncoder 和 OrdinalEncoder 的适用场景。
- 能选择合适的缩放器(Standard / MinMax / Robust)。
- 能用 TfidfVectorizer 把文本变成特征矩阵。
- 能用 ColumnTransformer + Pipeline 处理混合类型数据并避免泄漏。