特征提取
特征提取是将原始数据(如文本、图像、字典等)转换为机器学习算法可以处理的数值特征的过程。与特征选择不同,特征提取关注的是如何从非结构化或半结构化数据中构建有用的特征。sklearn 提供了丰富的特征提取工具,特别是在文本处理领域有着广泛的应用。
特征提取 vs 特征选择
理解两者的区别非常重要:
特征提取:将原始数据转换为数值特征。例如,将文本转换为词频向量,将图像转换为像素矩阵。
特征选择:从已有特征中选择子集。例如,从100个特征中选择最重要的10个。
特征提取是特征工程的起点,它决定了模型能够"看到"什么样的信息。
从字典提取特征
DictVectorizer 可以将字典形式的数据转换为特征矩阵,特别适合处理类别特征。
基本用法
from sklearn.feature_extraction import DictVectorizer
# 字典形式的原始数据
measurements = [
{'city': 'Dubai', 'temperature': 33.},
{'city': 'London', 'temperature': 12.},
{'city': 'San Francisco', 'temperature': 18.},
]
# 创建转换器
vec = DictVectorizer()
# 转换为特征矩阵
X = vec.fit_transform(measurements)
print("特征矩阵:")
print(X.toarray())
print("\n特征名称:")
print(vec.get_feature_names_out())
输出:
特征矩阵:
[[ 1. 0. 0. 33.]
[ 0. 1. 0. 12.]
[ 0. 0. 1. 18.]]
特征名称:
['city=Dubai' 'city=London' 'city=San Francisco' 'temperature']
工作原理:
- 数值特征(如
temperature)保持不变 - 类别特征(如
city)进行独热编码(One-Hot Encoding) - 结果默认为稀疏矩阵,节省内存
处理多值特征
当特征可以取多个值时(如电影的多个标签),DictVectorizer 也能处理:
movie_data = [
{'genre': ['action', 'thriller'], 'year': 2020},
{'genre': ['comedy'], 'year': 2021},
{'genre': ['drama', 'romance'], 'year': 2019},
]
vec = DictVectorizer()
X = vec.fit_transform(movie_data)
print("特征名称:", vec.get_feature_names_out())
print("\n特征矩阵:")
print(X.toarray())
实际应用场景
自然语言处理中的序列标注:
# 提取单词上下文作为特征
pos_window = [
{
'word-2': 'the',
'pos-2': 'DT', # 词性标签
'word-1': 'cat',
'pos-1': 'NN',
'word+1': 'on',
'pos+1': 'PP',
}
]
vec = DictVectorizer()
X = vec.fit_transform(pos_window)
print("上下文特征:", vec.get_feature_names_out())
特征哈希
FeatureHasher 使用哈希技巧(Hashing Trick)进行高效的特征提取,特别适合大规模数据。
为什么需要特征哈希?
传统的向量化方法(如 DictVectorizer)需要在训练时构建特征映射表,这在以下情况会有问题:
- 特征数量巨大(如百万级别的词汇表)
- 内存有限
- 需要在线学习(流式数据)
特征哈希通过哈希函数直接将特征映射到固定大小的向量空间,无需存储映射表。
基本用法
from sklearn.feature_extraction import FeatureHasher
# 创建哈希转换器
hasher = FeatureHasher(n_features=10, input_type='dict')
# 输入数据
data = [
{'apple': 1, 'banana': 2},
{'orange': 3, 'apple': 1},
{'banana': 1, 'grape': 4}
]
# 转换
X = hasher.transform(data)
print("哈希后的特征矩阵:")
print(X.toarray())
重要参数
| 参数 | 说明 |
|---|---|
n_features | 输出特征的数量,建议设为2的幂次方 |
input_type | 输入类型:'dict'、'pair' 或 'string' |
alternate_sign | 是否使用符号交替,减少哈希碰撞影响 |
哈希碰撞问题:
由于特征空间被压缩,不同的原始特征可能被哈希到相同的位置。alternate_sign=True(默认)可以通过正负号抵消来缓解这个问题:
# 理解符号交替的作用
hasher = FeatureHasher(n_features=8, alternate_sign=True)
X = hasher.transform([{'feature_a': 1, 'feature_b': 1}])
print("符号交替启用:", X.toarray())
hasher_no_sign = FeatureHasher(n_features=8, alternate_sign=False)
X_no_sign = hasher_no_sign.transform([{'feature_a': 1, 'feature_b': 1}])
print("符号交替禁用:", X_no_sign.toarray())
适用场景
- 大规模文本分类:词汇表太大,内存无法存储
- 在线学习:新特征不断出现,无法预先知道所有特征
- 高维稀疏数据:特征维度极高但每个样本只使用很少的特征
DictVectorizer vs FeatureHasher
| 特性 | DictVectorizer | FeatureHasher |
|---|---|---|
| 内存使用 | 较高(需存储映射表) | 低(固定大小) |
| 可解释性 | 高(可获取特征名称) | 低(无法反推原始特征) |
| 适合数据规模 | 中小规模 | 大规模 |
| 在线学习 | 不支持 | 支持 |
文本特征提取
文本特征提取是自然语言处理(NLP)的基础步骤。sklearn 提供了强大的文本向量化工具。
词袋模型(Bag of Words)
词袋模型是最基本的文本表示方法:
- 分词:将文本切分为词元(token)
- 计数:统计每个词的出现次数
- 归一化:对词频进行归一化处理
这种表示方法完全忽略了词的顺序,只关注词的出现频率。
CountVectorizer
CountVectorizer 实现了词袋模型,将文本转换为词频矩阵。
from sklearn.feature_extraction.text import CountVectorizer
# 示例语料
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
# 创建向量化器
vectorizer = CountVectorizer()
# 拟合并转换
X = vectorizer.fit_transform(corpus)
print("词汇表:", vectorizer.get_feature_names_out())
print("\n词频矩阵:")
print(X.toarray())
输出:
词汇表: ['and' 'document' 'first' 'is' 'one' 'second' 'the' 'third' 'this']
词频矩阵:
[[0 1 1 1 0 0 1 0 1]
[0 1 0 1 0 2 1 0 1]
[1 0 0 0 1 0 1 1 0]
[0 1 1 1 0 0 1 0 1]]
解读结果:
- 第一行
[0, 1, 1, 1, 0, 0, 1, 0, 1]表示第一个文档包含:document(1次)、first(1次)、is(1次)、the(1次)、this(1次) - 第二个文档中 "second" 出现了2次
重要参数
vectorizer = CountVectorizer(
# 分词相关
token_pattern=r'(?u)\b\w\w+\b', # 正则表达式匹配词元
stop_words='english', # 移除停用词
ngram_range=(1, 2), # 提取1-gram和2-gram
# 词汇表相关
max_features=1000, # 最多保留1000个特征
min_df=2, # 词至少在2个文档中出现
max_df=0.9, # 词在不超过90%的文档中出现
# 其他
binary=False, # 是否只记录是否出现(不计数)
lowercase=True, # 转换为小写
)
N-gram 特征
单个词(unigram)无法捕获短语信息。通过 N-gram 可以捕获连续词组:
# 使用 bi-gram
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2))
X = bigram_vectorizer.fit_transform(corpus)
print("Bi-gram 词汇表(前20个):")
print(bigram_vectorizer.get_feature_names_out()[:20])
何时使用 N-gram:
- 需要捕获短语(如 "not good")
- 需要保留一定的词序信息
- 但要注意:N-gram 会显著增加特征数量
TfidfVectorizer
词频计数存在一个问题:常见词(如 "the"、"is")在所有文档中都频繁出现,但对区分文档内容贡献不大。TF-IDF 通过降低常见词的权重来解决这个问题。
TF-IDF 公式:
其中:
- 是词 在文档 中的频率
- , 是文档总数, 是包含词 的文档数
from sklearn.feature_extraction.text import TfidfVectorizer
# 创建 TF-IDF 向量化器
tfidf = TfidfVectorizer()
# 拟合并转换
X = tfidf.fit_transform(corpus)
print("TF-IDF 矩阵(第一个文档):")
print(X[0].toarray())
print("\nIDF 权重:")
print(dict(zip(tfidf.get_feature_names_out(), tfidf.idf_)))
TF-IDF 的直觉理解:
- 在单个文档中出现次数多的词 → TF 高 → 权重高
- 在很多文档中都出现的词 → IDF 低 → 权重低
- 结果:能够区分文档的"重要"词会得到较高权重
TfidfVectorizer vs CountVectorizer + TfidfTransformer
两种方式等价:
from sklearn.feature_extraction.text import TfidfTransformer
# 方式1:直接使用 TfidfVectorizer
tfidf_vec = TfidfVectorizer()
X1 = tfidf_vec.fit_transform(corpus)
# 方式2:CountVectorizer + TfidfTransformer
count_vec = CountVectorizer()
X_counts = count_vec.fit_transform(corpus)
tfidf_trans = TfidfTransformer()
X2 = tfidf_trans.fit_transform(X_counts)
# 结果相同
import numpy as np
print(np.allclose(X1.toarray(), X2.toarray())) # True
停用词处理
停用词是指那些对文本内容贡献不大但出现频率很高的词,如 "the"、"is"、"and" 等。
# 使用内置停用词
vectorizer = CountVectorizer(stop_words='english')
# 自定义停用词
custom_stop_words = ['the', 'is', 'and', 'of', 'to']
vectorizer = CountVectorizer(stop_words=custom_stop_words)
注意事项:
sklearn 内置的英文停用词列表并不完美,某些任务可能需要自定义停用词。此外,对于某些任务(如作者风格分析),停用词可能很有价值。
中文文本处理
对于中文,需要先进行分词(可以使用 jieba 等分词工具):
import jieba
def chinese_tokenizer(text):
"""中文分词器"""
return jieba.lcut(text)
# 中文语料
corpus_chinese = [
'机器学习是人工智能的重要分支',
'深度学习使用神经网络进行学习',
'自然语言处理是人工智能的核心技术'
]
# 使用自定义分词器
vectorizer = CountVectorizer(tokenizer=chinese_tokenizer)
X = vectorizer.fit_transform(corpus_chinese)
print("中文词汇表:", vectorizer.get_feature_names_out())
实际应用示例:文本分类
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 假设有标注的文本数据
texts = [
"股市今日大涨,投资者信心增强",
"新手机发布,搭载最新处理器",
"足球比赛精彩激烈,球迷欢呼",
# ... 更多数据
]
labels = ["财经", "科技", "体育", ...]
# 划分数据
X_train, X_test, y_train, y_test = train_test_split(
texts, labels, test_size=0.2, random_state=42
)
# 创建管道
pipeline = Pipeline([
('tfidf', TfidfVectorizer(
max_features=5000,
ngram_range=(1, 2),
stop_words=None # 中文需要自定义停用词
)),
('clf', LogisticRegression(max_iter=1000))
])
# 训练和评估
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))
HashingVectorizer
HashingVectorizer 结合了特征哈希和文本向量化,适合大规模文本处理。
from sklearn.feature_extraction.text import HashingVectorizer
# 创建哈希向量化器
hash_vec = HashingVectorizer(
n_features=2**18, # 约26万个特征槽
alternate_sign=True,
norm='l2'
)
# 直接转换(无需 fit)
X = hash_vec.transform(corpus)
print(f"特征矩阵形状: {X.shape}")
print(f"稀疏度: {1 - X.nnz / (X.shape[0] * X.shape[1]):.4f}")
优势:
- 无需预先遍历数据构建词汇表
- 内存占用固定
- 支持在线学习(增量更新)
- 可并行化处理
劣势:
- 无法反推原始特征名
- 存在哈希碰撞
- 没有 IDF 加权
大规模文本处理
# 处理大量文本文件
def stream_text_files(directory):
"""流式读取文本文件"""
import os
for filename in os.listdir(directory):
with open(os.path.join(directory, filename), 'r') as f:
yield f.read()
# 使用 HashingVectorizer 处理流式数据
hash_vec = HashingVectorizer(n_features=2**20)
# 可以逐批处理,无需一次性加载所有数据
for batch_texts in stream_text_files('large_text_directory'):
X_batch = hash_vec.transform(batch_texts)
# 进行后续处理...
图像特征提取
sklearn 提供了基本的图像特征提取功能,主要用于简化版图像处理。
从图像提取像素特征
from sklearn.feature_extraction.image import extract_patches_2d
# 假设有一个 4x4 的灰度图像
import numpy as np
image = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
# 提取 2x2 的图像块
patches = extract_patches_2d(image, patch_size=(2, 2))
print(f"提取的图像块数量: {len(patches)}")
print(f"第一个图像块:\n{patches[0]}")
图像块重建
from sklearn.feature_extraction.image import reconstruct_from_patches_2d
# 从图像块重建图像
reconstructed = reconstruct_from_patches_2d(patches, image.shape)
print(np.array_equal(image, reconstructed)) # True
应用场景:
- 图像分割
- 特征点检测
- 数据增强(通过随机裁剪)
PatchExtractor
用于从图像集合中提取大量随机块:
from sklearn.feature_extraction.image import PatchExtractor
# 多张图像
images = np.random.rand(10, 32, 32) # 10张 32x32 的图像
# 提取器
extractor = PatchExtractor(patch_size=(8, 8), max_patches=10)
# 提取块
patches = extractor.transform(images)
print(f"提取的图像块形状: {patches.shape}")
实际应用:使用 sklearn 进行简单图像分类
from sklearn.datasets import load_digits
from sklearn.feature_extraction.image import PatchExtractor
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import numpy as np
# 加载手写数字数据集
digits = load_digits()
X, y = digits.data, digits.target
# 原始数据已经是展平的像素特征
# 每个图像是 8x8,展平后是 64 维
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练分类器
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
print(f"测试集准确率: {clf.score(X_test, y_test):.2%}")
注意:对于复杂的图像处理任务,建议使用专门的图像处理库(如 OpenCV、Pillow)或深度学习框架(如 PyTorch、TensorFlow)。
最佳实践
文本预处理流程
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
# 完整的文本分类管道
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('clf', LogisticRegression(max_iter=1000))
])
# 参数网格
param_grid = {
'tfidf__max_features': [1000, 5000, 10000],
'tfidf__ngram_range': [(1, 1), (1, 2)],
'tfidf__min_df': [1, 2, 5],
'clf__C': [0.1, 1, 10]
}
# 网格搜索
grid_search = GridSearchCV(pipeline, param_grid, cv=5, n_jobs=-1)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳得分: {grid_search.best_score_:.3f}")
选择合适的向量化器
| 场景 | 推荐方法 |
|---|---|
| 小规模文本、需要可解释性 | TfidfVectorizer |
| 大规模文本、内存有限 | HashingVectorizer |
| 流式数据、在线学习 | HashingVectorizer |
| 中文文本 | 自定义分词器 + TfidfVectorizer |
避免常见错误
错误1:数据泄露
# 错误:在整个数据集上 fit
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(all_texts) # 包含训练和测试数据
X_train, X_test = train_test_split(X, ...)
# 正确:使用 Pipeline
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('clf', LogisticRegression())
])
# 交叉验证时,每个 fold 独立进行特征提取
错误2:忽略中文字符编码
# 确保正确处理中文编码
with open('chinese_text.txt', 'r', encoding='utf-8') as f:
text = f.read()
错误3:不合理的参数设置
# 问题:max_features 太小或太大
# 太小:丢失重要信息
# 太大:维度灾难、计算成本高
# 建议:根据数据规模调整
# 小数据集(<1000文档):max_features=1000-5000
# 中等数据集(1000-10000文档):max_features=5000-10000
# 大数据集(>10000文档):max_features=10000-50000
小结
特征提取是机器学习流程中的关键步骤:
- 字典特征:使用
DictVectorizer处理类别特征 - 大规模数据:使用
FeatureHasher节省内存 - 文本特征:
CountVectorizer用于词频,TfidfVectorizer用于加权词频 - N-gram:捕获短语和词序信息,但会增加特征数量
- Pipeline:将特征提取与模型训练整合,避免数据泄露
- 参数调优:使用网格搜索找到最优的特征提取参数
文本特征提取是 sklearn 特征提取模块最强大的功能,广泛应用于文本分类、情感分析、信息检索等领域。掌握这些工具是进行自然语言处理的基础。