SL Scikit-Learn Learn机器学习教程

Lesson 04

特征提取:把原始数据变成模型能用的数字

模型只认数字。本章讲如何把类别、文本和图像转成特征矩阵,以及如何用 ColumnTransformer 和 Pipeline 防止数据泄漏。

  • 预计时长55 分钟
  • 难度进阶
  • 前置章节第 01–03 章

本章细分学习地图

特征提取是建模前最关键的环节。数据进模型之前必须全部变成数值,且预处理方式必须在 Pipeline 内完成以防泄漏。

模块拆细学习单元完成后应产出掌握标准
类别编码OneHot、Ordinal、高基数处理。编码前后对比表。能根据类别特点选编码方式。
数值缩放StandardScaler、MinMaxScaler、RobustScaler。缩放前后分布对比。能说清缩放对距离模型和线性模型的影响。
文本特征BoW、停用词、词干、TF-IDF。文本分类 Pipeline。能构建从原始文本到预测的完整流程。
图像特征像素展平、CNN 激活提取。图像分类示例。了解 sklearn 处理图像的边界。
组合预处理ColumnTransformer + Pipeline 防泄漏。混合类型数据 Pipeline。能解释为什么 fit 只能在训练集上调用。

学习目标

特征提取流程

1原始数据
2编码 + 缩放
3特征矩阵 X
4模型训练

本章完成度

已完成 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}")
sklearn 的图像边界

像素展平只适用于极小的图像(如 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))
为什么必须用 Pipeline

如果在拆分前对全部数据做 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 处理混合类型数据并避免泄漏。