在 sklearn.feature_extraction
模块中,DictVectorizer
是从字典(dict)中加载和提取特征的核心工具。它主要用于将包含特征名称和值的 Python 字典列表转换为机器学习算法所需的数值型数组或稀疏矩阵。
这种方法在处理结构化数据(如从 JSON、数据库记录或日志中提取的数据)时非常有用,特别是当数据包含类别型(categorical)特征时。
DictVectorizer 详解
DictVectorizer
是scikit-learn
中用于将字典列表(特征名称-值对)转换为数值型特征矩阵的工具,常用于将类别特征进行独热编码(One-Hot Encoding),特别适合处理结构化数据中的非数值特征。
核心思想
- 输入:一个由字典组成的列表,每个字典代表一个样本,键为特征名,值为特征值。
- 输出:一个稀疏矩阵(或数组),其中每一行对应一个样本,每一列对应一个特征。
- 处理方式:
- 对于数值型特征:直接保留其值。
- 对于类别型特征:自动进行 One-Hot 编码,即为每个类别创建一个二元特征列。
- 特征名称会作为新特征的列名(可通过
get_feature_names_out()
获取)。
主要参数
参数 | 类型 | 说明 |
---|---|---|
sparse |
bool, default=True | 是否返回稀疏矩阵。True 返回 scipy.sparse 矩阵,False 返回 numpy.ndarray 。 |
sort |
bool, default=True | 是否对特征名称进行排序。 |
dtype |
data-type, default=np.float64 | 输出矩阵的数据类型。 |
返回值
fit_transform(X)
或transform(X)
返回:- 类型:
scipy.sparse matrix
或numpy.ndarray
- 形状:
(n_samples, n_features)
- 每一列对应一个独热编码或数值特征。
- 类型:
简单示例代码
from sklearn.feature_extraction import DictVectorizer
# 示例数据:每个字典是一个样本
data = [
{'color': 'red', 'size': 'M', 'price': 100},
{'color': 'blue', 'size': 'L', 'price': 150},
{'color': 'red', 'size': 'S', 'price': 80},
]
# 创建 DictVectorizer 实例
vectorizer = DictVectorizer(sparse=False) # 设置 sparse=False 以返回普通数组便于查看
# 拟合并转换数据
X = vectorizer.fit_transform(data)
# 获取特征名称
feature_names = vectorizer.get_feature_names_out()
# 输出结果
print("特征名称:", feature_names)
print("转换后的特征矩阵:")
print(X)
输出结果:
特征名称: ['color=blue' 'color=red' 'price' 'size=L' 'size=M' 'size=S']
转换后的特征矩阵:
[[ 0. 1. 100. 0. 1. 0.]
[ 1. 0. 150. 1. 0. 0.]
[ 0. 1. 80. 0. 0. 1.]]
FeatureHasher 详解
FeatureHasher
是scikit-learn
中一个高效的特征向量化工具,它使用哈希技巧(Hashing Trick) 将特征映射到固定维度的向量空间中,特别适用于处理大规模、高维或动态变化的特征集合(如文本、日志、用户行为等),而无需保存完整的词汇表。
核心思想
- 哈希技巧(Hashing Trick):
- 不像
DictVectorizer
那样维护一个从特征名到索引的映射表(词汇表),而是使用哈希函数将特征名直接映射到固定大小的向量索引。 - 所有特征通过哈希函数
h(feature) % n_features
映射到[0, n_features)
的整数索引。
- 不像
- 优点:
- 内存高效:不保存特征名到索引的映射表,适合大规模数据。
- 速度快:无需拟合(
fit
无实际操作),可直接transform
。 - 支持在线学习:适用于流式数据。
- 缺点:
- 可能发生哈希冲突(不同特征映射到同一索引)。
- 无法逆向映射(不能知道哪个特征对应哪个维度)。
- 不可解释性较强。
主要参数
参数 | 类型 | 说明 |
---|---|---|
n_features |
int, default=1048576 (2^20) | 输出向量的维度(即哈希空间大小)。越大冲突越少,但占用内存越多。 |
input_type |
str, default=‘dict’ | 输入数据的格式,可选:'dict' , 'pair' , 'string' 。常用 'dict' 。 |
dtype |
data-type, default=np.float64 | 输出矩阵的数据类型。 |
alternate_sign |
bool, default=True | 是否对某些哈希值取负号以减少方差(尤其在稀疏数据中)。 |
返回值
fit_transform(X)
或transform(X)
返回:- 类型:
scipy.sparse matrix
(通常是csr_matrix
) - 形状:
(n_samples, n_features)
- 每行是一个样本的哈希后特征向量。
- 类型:
⚠️ 注意:
fit()
方法在FeatureHasher
中是空操作(no-op),因为不需要学习任何映射关系。
简单示例代码
from sklearn.feature_extraction import FeatureHasher
import numpy as np
# 示例数据:每个样本是一个字典,键为特征名,值为特征值(数值或计数)
data = [
{'color': 'red', 'size': 'M', 'price': 100},
{'color': 'blue', 'size': 'L', 'price': 150, 'color': 'blue'}, # color 出现两次
{'color': 'red', 'size': 'S', 'weight': 5},
]
# 创建 FeatureHasher 实例,指定输出维度为 10
hasher = FeatureHasher(n_features=10, input_type='dict', dtype=np.float64)
# 转换数据(fit 实际上什么都不做)
X = hasher.transform(data)
# 转为密集数组以便查看(实际使用中通常保持稀疏)
X_dense = X.toarray()
print("转换后的特征矩阵(形状: {}):".format(X_dense.shape))
print(X_dense)
输出结果(示例,可能因哈希种子不同而异)
转换后的特征矩阵(形状: (3, 10)):
[[ 0. 0. 0. 0. 0. 0. 0. 100. 1. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 150. -1. 0.]
[ 0. 0. 0. 5. 0. 0. 0. 0. 1. 0.]]
📌 说明:
- 特征如 ‘color=red’, ‘size=M’ 等被哈希到 0~9 的某个列。
- 数值特征(如 price)的值被直接累加到对应哈希列。
- alternate_sign=True 会导致某些特征值为负(如 -1.),用于方差控制。
应用场景
- 大规模文本处理:将词袋模型快速向量化,无需构建词汇表。
- 在线学习系统:数据流式到达,无法预先知道所有特征。
- 日志/行为数据处理:特征种类多且动态变化(如用户点击、设备信息)。
- 内存受限环境:避免存储庞大的特征词典。
注意事项
- 哈希冲突:不同特征可能映射到同一列,导致信息混叠。可通过增大 n_features 缓解。
- 不可逆性:无法从输出向量还原原始特征名。
- input_type 支持类型:
'dict'
:{feature: value}
字典(最常用)'pair'
:(feature, value)
元组列表'string'
: 字符串列表(每个字符串作为一个二元特征,值为1)
替代建议
虽然 FeatureHasher 高效,但在现代机器学习流程中,更推荐使用:
HashingVectorizer
(专用于文本)ColumnTransformer
+OneHotEncoder
(结构化数据)sklearn.preprocessing
中的其他工具,结合Pandas
进行预处理。
但 FeatureHasher
仍是在特定高性能、低内存场景下的有力工具。
sklearn中的文本特征提取
在
scikit-learn
(sklearn)中,文本特征提取是将非结构化的文本数据转换为机器学习算法可处理的数值型特征向量的关键步骤。由于计算机无法直接理解自然语言,必须通过特征提取技术将文本表示为向量形式,以便用于分类、聚类、回归等任务。
sklearn主要通过feature_extraction.text
模块实现文本特征提取。最常用的方法包括CountVectorizer
、TfidfVectorizer
和HashingVectorizer
。CountVectorizer
将文本集合转换为词频矩阵,统计每个词在文档中出现的次数,生成文档-词的稀疏矩阵。虽然简单直观,但它不考虑词语在整个语料库中的重要性。
为克服这一局限,TfidfVectorizer
引入了TF-IDF(词频-逆文档频率)机制。该方法不仅考虑词在文档中的出现频率(TF),还通过逆文档频率(IDF)降低在大量文档中频繁出现的通用词(如“the”、“is”)的权重,从而突出具有区分意义的关键词,提升模型性能。
此外,HashingVectorizer
采用哈希技巧将词语映射到固定维度的向量空间,无需存储词汇表,适合处理大规模或流式文本数据,具有内存高效的优势,但可能存在哈希冲突且不可逆。
这些方法通常支持文本预处理功能,如小写转换、停用词过滤、n-gram提取等。文本特征提取作为自然语言处理流程的前置步骤,直接影响后续模型的效果,是构建文本分析系统的基础环节。
1.词袋表示法
文本分析是机器学习算法的主要应用领域。但是,原始数据,符号序列不能直接输入到算法本身,因为大多数算法期望的是具有固定大小的数值特征向量,而不是具有可变长度的原始文本文档。
为了解决这个问题,scikit-learn提供了从文本内容中提取数字特征的最常用方法,即:
- 标记字符串并为每个可能的标记提供整数ID,例如通过使用空格和标点符号作为标记分隔符。
- 统计每个文档中标记出现的频次。
- 大多数样本/文档中采用通过减少重要性标记来进行标准化和加权。
在此方案中,特征和样本被定义如下:
- 每个单独的标记出现频率(已标准化或未标准化)都被视为特征。
- 给定文档中的所有标记频率的向量被认为是一个多元样本
因此,文档语料库可以由矩阵表示,每行对应一个文档,每列对应语料库中出现的标记(如单个词)。
我们将向量化称为将文本文档集合转换为数字特征向量的一般过程。这种特定的策略(标记化,计数和归一化)称为“词袋”或“n-gram袋”表示。通过单词出现来描述文档,而完全忽略文档中单词的相对位置信息。
2.稀疏性
由于大多数文档通常只会使用文档语料库中很小一部分的单词,因此生成的矩阵中很多特征值为零(通常超过总特征值的99%)。
例如,一个包含10,000个短文本文档(例如电子邮件)的集合将使用一个词汇表,该词汇表的总大小约包含100,000个不重复的单词,而每个文档将单独使用100至1000个不重复的单词。
为了能够将这样的矩阵存储在内存中并且还可以加快矩阵/向量的代数运算,通常使用例如 scipy.sparse
包中的稀疏表示来实现。
3.CountVectorizer的常见用法
核心思想
CountVectorizer
的核心思想是将文本数据转换为词频矩阵(Document-Term Matrix)。它通过统计每个词语在文档中出现的次数,将文本集合表示为数值型的稀疏矩阵。每一行代表一个文档,每一列对应词汇表中的一个词,矩阵中的值表示该词在对应文档中的出现频次。该方法基于“词袋模型”(Bag-of-Words),忽略词序,仅关注词频。
主要参数
参数 | 说明 |
---|---|
lowercase |
是否将文本转换为小写(默认 True ) |
stop_words |
指定停用词列表,如 'english' 可移除常见无意义词 |
ngram_range |
提取 n-gram 的范围,如 (1,2) 表示提取单个词和双词组合 |
max_features |
限制词汇表最大长度,保留最高频的词 |
min_df |
忽略出现在少于 min_df 个文档中的词(支持整数或比例) |
max_df |
忽略出现在超过 max_df 比例文档中的词(如 0.9 ) |
返回值
fit_transform()
返回一个 稀疏矩阵(scipy.sparse matrix),形状为(n_samples, n_features)
,表示文档-词频矩阵。get_feature_names_out()
返回词汇表,即所有特征词的数组。vocabulary_
属性返回词到索引的映射字典。
简单示例代码
from sklearn.feature_extraction.text import CountVectorizer
# 示例文本数据
corpus = [
'I love machine learning',
'I love coding in Python',
'Python is great for machine learning'
]
# 创建并配置向量化器
vectorizer = CountVectorizer(
lowercase=True,
stop_words='english', # 移除英文停用词如 'I', 'is', 'for'
ngram_range=(1, 1) # 只使用单个词
)
# 拟合并转换文本
X = vectorizer.fit_transform(corpus)
# 输出结果
print("词汇表:", vectorizer.get_feature_names_out())
print("词频矩阵:\n", X.toarray())
输出示例:
词汇表: ['coding' 'great' 'learning' 'love' 'machine' 'python']
词频矩阵:
[[0 0 1 1 1 0]
[1 0 0 1 0 1]
[0 1 1 0 1 1]]
4.停用词的使用
停用词是指诸如“和”,“这”,“他”之类的词,它们被认为在表示文本内容方面没有提供任何信息,可以将其删除以避免将其理解为参与预测的信息。然而有时候,类似的词对于预测很有用,例如在对写作风格或性格进行分类时。
我们提供的“英语”停用词列表中有几个已知问题。它并非旨在成为通用的“一刀切”解决方案,因为某些任务可能需要定制的解决方案。有关更多详细信息,请参见[NQY18]。
请谨慎选择停用词列表。流行的停用词列表可能包含对某些任务非常有用的词(例如计算机)。
您还应该确保停用词列表具有与矢量化器中使用的是相同的预处理和标记。单词"we’ve"被CountVectorizer的默认标记分配器分割成"we"和"ve",所以如果"we’ve"在停止词列表中,但"ve"不在,"ve"会被保留在转换后的文本中。我们的向量化器将尝试识别并警告某些不一致之处。
5.TF–IDF术语权重
TF-IDF(Term Frequency-Inverse Document Frequency)是一种用于信息检索和文本挖掘的常用加权技术,旨在评估一个词在文档中的重要程度。其核心思想是:一个词的重要性与它在文档中的出现频率成正比,与它在语料库中出现的文档数成反比。
数学思想
- TF(词频):衡量一个词在当前文档中出现的频繁程度。一个词在文档中出现越多,其重要性越高。
- IDF(逆文档频率):衡量一个词的普遍重要性。一个词在越多文档中出现,其区分能力越弱,权重越低。IDF 通过惩罚高频通用词(如 “the”, “is”)来提升稀有但具有区分性的词的权重。
结合两者,TF-IDF 能有效突出在当前文档中常见但在其他文档中少见的关键词。
数学计算过程
1. 词频(TF)
最常见的计算方式是频率法:
TF(t,d)=词 t 在文档 d 中出现的次数文档 d 的总词数 \mathrm{TF}(t, d) = \frac{\text{词 } t \text{ 在文档 } d \text{ 中出现的次数}}{\text{文档 } d \text{ 的总词数}} TF(t,d)=文档 d 的总词数词 t 在文档 d 中出现的次数
2. 逆文档频率(IDF)
通常使用对数形式以平滑权重:
IDF(t,D)=log(语料库中文档总数 N包含词 t 的文档数 ∣{d∈D:t∈d}∣) \mathrm{IDF}(t, D) = \log \left( \frac{\text{语料库中文档总数 } N}{\text{包含词 } t \text{ 的文档数 } |\{d \in D : t \in d\}|} \right) IDF(t,D)=log(包含词 t 的文档数 ∣{d∈D:t∈d}∣语料库中文档总数 N)
为避免除零,通常加1平滑:
IDF(t,D)=log(N1+∣{d∈D:t∈d}∣) \mathrm{IDF}(t, D) = \log \left( \frac{N}{1 + |\{d \in D : t \in d\}|} \right) IDF(t,D)=log(1+∣{d∈D:t∈d}∣N)
或使用更常见的平滑形式:
IDF(t,D)=log(1+N1+∣{d∈D:t∈d}∣)+1 \mathrm{IDF}(t, D) = \log \left( \frac{1 + N}{1 + |\{d \in D : t \in d\}|} \right) + 1 IDF(t,D)=log(1+∣{d∈D:t∈d}∣1+N)+1
4. TF-IDF 权重
在一个大型文本语料库中,有些高频出现的词(例如英语中“the”,“a”,“is”)几乎没有携带任何与文档内容相关的有用信息。如果我们将统计数据直接提供给分类器,那么这些高频出现的词会掩盖住那些我们关注但出现次数较少的词。
为了重新加权特征计数为适合分类器使用的浮点值,通常使用tf-idf变换。
Tf表示词频,而tf-idf表示词频乘以逆文档频率:tf−idf(t,d)=tf(t,d)×idf(t)tf-idf(t, d) = tf(t, d) \times idf(t)tf−idf(t,d)=tf(t,d)×idf(t)
使用TfidfTransformer的默认设置,TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)
词频,词语在给定文档中出现的次数乘以idf分量,其计算公式为:
idf(t)=log1+n1+df(t)+1idf(t) = \log \frac{1+n}{1+df(t)} + 1idf(t)=log1+df(t)1+n+1
其中nnn是文档集中的文档总数,df(t)df(t)df(t)是文档集中包含词语ttt的文档数量,然后将所得的tf-idf向量通过欧几里得范数归一化:
vnorm=v∣∣v∣∣2=vv12+v22+...+vn2v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v_1^2 + v_2^2 + ... + v_n^2}}vnorm=∣∣v∣∣2v=v12+v22+...+vn2v
这最初是为信息检索(作为搜索引擎结果的排名函数)开发的术语加权方案,也已在文档分类和聚类中找到了很好的用途。
以下部分包含进一步的说明和示例,表现了如何精确计算tf-idfs,以及如何在scikit-learn中计算tf-idfs。TfidfTransformer
和 TfidfVectorizer
与将idf定义为以下内容的标准教科书符号略有不同
idf(t)=logn1+df(t)idf(t) = \log \frac{n}{1 + df(t)}idf(t)=log1+df(t)n
在 TfidfTransformer
和设置了 smooth_idf=False
的 TfidfVectorizer
中,将“1”计数添加到idf中,而不是idf的分母中:
idf(t)=logndf(t)+1idf(t) = \log \frac{n}{df(t)} + 1idf(t)=logdf(t)n+1
该归一化由 TfidfTransformer
类实现:
>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer
TfidfTransformer(smooth_idf=False)
以下方统计为例。第一项100%的时间都出现,因此不是很有重要。另外两个特征只占不到50%的时间出现,因此可能更能代表文档的内容:
>>> counts = [[3, 0, 1],
... [2, 0, 0],
... [3, 0, 0],
... [4, 0, 0],
... [3, 2, 0],
... [3, 0, 2]]
...
>>> tfidf = transformer.fit_transform(counts)
>>> tfidf
<6x3 sparse matrix of type '<... 'numpy.float64'>'
with 9 stored elements in Compressed Sparse ... format>
>>> tfidf.toarray()
array([[0.81940995, 0. , 0.57320793],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[0.47330339, 0.88089948, 0. ],
[0.58149261, 0. , 0.81355169]])
每行都被正则化,使其适应欧几里得标准:
vnorm=v∣∣v∣∣2=vv12+v22+...+vn2v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v_1^2 + v_2^2 + ... + v_n^2}}vnorm=∣∣v∣∣2v=v12+v22+...+vn2v
例如,我们可以如下计算 counts
数组中第一个文档中第一项的tf-idf:
n=6n = 6n=6
df(t)term1=6df(t)_{term1} = 6df(t)term1=6
df(t)term1=logndf(t)+1=log(1)+1=1df(t)_{term1} = \log \frac{n}{df(t)} + 1 = \log(1) + 1 = 1df(t)term1=logdf(t)n+1=log(1)+1=1
tf-idfterm1=tf×idf=3×1=3tf\text{-}idf_{term1} = tf \times idf = 3 \times 1 = 3tf-idfterm1=tf×idf=3×1=3
现在,如果我们对文档中剩余的2个词语重复此计算,我们将得到:
tf-idfterm2=0×(log(6/1)+1)=0tf\text{-}idf_{term2} = 0 \times (\log(6/1) + 1) = 0tf-idfterm2=0×(log(6/1)+1)=0
tf-idfterm3=1×(log(6/2)+1)≈2.0986tf\text{-}idf_{term3} = 1 \times (\log(6/2) + 1) \approx 2.0986tf-idfterm3=1×(log(6/2)+1)≈2.0986
以及原始tf-idfs的向量:
tf-idfraw=[3,0,2.0986]tf\text{-}idf_{raw} = [3, 0, 2.0986]tf-idfraw=[3,0,2.0986]
然后,应用欧几里得(L2)范数,我们为文档1获得以下tf-idfs:
[3,0,2.0986]32+02+2.09862=[0.819,0,0.573]\frac{[3, 0, 2.0986]}{\sqrt{3^2 + 0^2 + 2.0986^2}} = [0.819, 0, 0.573]32+02+2.09862[3,0,2.0986]=[0.819,0,0.573]
此外,默认参数 smooth_idf=True
将“1”添加到分子和分母,类似于通过一个包含集合中的每个词语的附加文档从而避免除零错误。
idf(t)=log1+n1+df(t)+1idf(t) = \log \frac{1 + n}{1 + df(t)} + 1idf(t)=log1+df(t)1+n+1
使用此修改,文档1中第三项的tf-idf更改为1.8473:
tf-idfterm3=1×log(7/3)+1≈1.8473tf\text{-}idf_{term3} = 1 \times \log(7/3) + 1 \approx 1.8473tf-idfterm3=1×log(7/3)+1≈1.8473
并且L2归一化的tf-idf变为:
[3,0,1.8473]32+02+1.84732=[0.819,0,0.5243]\frac{[3, 0, 1.8473]}{\sqrt{3^2 + 0^2 + 1.8473^2}} = [0.819, 0, 0.5243]32+02+1.84732[3,0,1.8473]=[0.819,0,0.5243]
>>> transformer = TfidfTransformer()
>>> transformer.fit_transform(counts).toarray()
array([[0.85151335, 0. , 0.52433293],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[0.55422893, 0.83236428, 0. ],
[0.63035731, 0. , 0.77630514]])
通过调用fit
方法计算的每个特征的权重并存储在模型属性中:
>>> transformer.idf_
array([1. ..., 2.25..., 1.84...])
由于tf–idf通常用于文本特征,因此还有一个被称为TfidfVectorizer
的类,它在一个模型中结合了CountVectorizer
和 TfidfTransformer
的所有选项:
>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> vectorizer = TfidfVectorizer()
>>> vectorizer.fit_transform(corpus)
<4x9 sparse matrix of type '<... 'numpy.float64'>'
with 19 stored elements in Compressed Sparse ... format>
尽管tf–idf归一化通常非常有用,但是在某些情况下,二进制标记可能会提供更好的特征。这可以通过使用CountVectorizer的binary参数来实现。 特别是一些估计器(例如 Bernoulli Naive Bayes)显式的使用离散的布尔随机变量。还有,很短的文本可能带有嘈杂的tf–idf值,而二进制标志信息则更稳定。
5.解码文本文件
在 scikit-learn
(简称 sklearn)中,“解码文本文件” 通常不是直接的术语,但一般是指 从文本文件中读取原始文本数据,并将其转换为机器学习模型可以处理的数值特征矩阵 的过程。这个过程常称为 文本向量化(Text Vectorization
)。
1. 读取文本文件
使用 Python 标准库(如 open()、pathlib)或 sklearn.datasets.load_files 从磁盘加载文本文件:
from sklearn.datasets import load_files
# 假设文本文件按类别存放在不同子目录中
data = load_files('path/to/text/folders', encoding='utf-8')
texts = data.data # list of strings
labels = data.target
2. 文本向量化(关键“解码”步骤)
使用 CountVectorizer 或 TfidfVectorizer 将文本转换为数值特征:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(
encoding='utf-8',
decode_error='strict', # 或 'ignore', 'replace'
stop_words='english',
max_features=10000
)
X = vectorizer.fit_transform(texts) # 稀疏矩阵,shape: (n_samples, n_features)
3. 后续处理
- 可接分类器(如 LogisticRegression, SVM)
- 可做降维(如 TruncatedSVD)
- 可做聚类等无监督任务
核心工具:
- load_files() — 批量读取文本
- TfidfVectorizer / CountVectorizer — 文本 → 向量
- decode_error 参数 — 控制字符解码行为
6.词袋表示法的局限性
字母组合的集合(即单词)无法捕获短语和多单词表达,很大程度上忽略了任何单词顺序依赖性。此外,单词袋模型不会考虑潜在的拼写错误或单词派生。
N-grams 可以拯救我们!与其建立简单的字母组合(n=1),不如选择对成对的连续单词进行计数的二元组(n=2)。
还可以考虑选择n-grams字符的集合,一种可以抵抗拼写错误和派生的表示法。
6.1.忽略词序(Syntax Ignored)
示例句子:
- “狗咬人”
- “人咬狗”
BoW 向量(词汇表:[“人”, “咬”, “狗”])
💡 实际语义截然不同,但 BoW 无法区分。
6.2 无法捕捉语义相似性
示例句子:
- “这款车性能卓越”
- “这辆汽车表现优异”
BoW 向量(假设词汇表包含所有词):
句子1: [1, 1, 1, 0, 0, 0] # 车, 性能, 卓越, 汽车, 表现, 优异
句子2: [0, 0, 0, 1, 1, 1]
💡 语义相近,但向量正交(无重叠),相似度为0。
6.3 维度爆炸 & 稀疏性
假设词汇表大小 = 50,000
10,000 个文档 → 矩阵大小:10,000 × 50,000
每个句子平均仅含 20 个词 → 99.96% 的元素为 0
💡 存储和计算效率低,且稀疏性影响某些模型性能。
6.4无法处理一词多义 / 多词一义
示例:
- “苹果发布了新手机” → “苹果” = 公司
- “我吃了一个苹果” → “苹果” = 水果
BoW 表示:
两个“苹果”在向量中是同一个维度 → 语义混淆
💡 缺乏上下文感知能力。
6.5 忽略语法结构和修饰关系
示例:
- “不推荐购买” vs “强烈推荐购买”
BoW 向量(词汇表:[“不”, “推荐”, “购买”, “强烈”]):
句子1: [1, 1, 1, 0]
句子2: [0, 1, 1, 1]
💡 否定词“不”未被特殊处理,情感极性被错误忽略。
✅ 改进方法:
- N-gram 特征(部分解决词序)
- TF-IDF 加权(缓解高频无意义词)
- Word2Vec / BERT(语义嵌入)
- 使用语法分析或上下文模型(如 LSTM、Transformer)
7.使用哈希技巧对大型文本语料库进行矢量化处理
在处理大型文本语料库(如数百万文档、数千万唯一词项)时,传统的 CountVectorizer
或 TfidfVectorizer
会面临两个核心瓶颈:
- 内存爆炸:需要存储完整的词汇表(vocabulary)映射(词 → 索引),占用大量内存。
- 无法流式/增量处理:必须先遍历全部数据构建词汇表,再二次遍历进行向量化。
为解决这些问题,scikit-learn
提供了 HashingVectorizer
—— 基于 哈希技巧(Hashing Trick) 的无状态向量化器。
🎯 什么是“哈希技巧”?
哈希技巧是一种无需显式存储词汇表的特征映射方法:
- 对每个词(或 n-gram)应用哈希函数(如 MurmurHash3),直接映射到一个固定维度的向量索引。
- 例如:
hash("apple") % n_features → 12345
- 不存储“apple”这个词,也不维护词汇表字典。
- 哈希冲突(不同词映射到同一索引)通过增大
n_features
来缓解。
✅ 核心优势:内存恒定、支持流式处理、适合超大规模语料。
🛠️ 使用 HashingVectorizer
示例
from sklearn.feature_extraction.text import HashingVectorizer
# 初始化,指定维度(建议 2^18 ~ 2^22)
vectorizer = HashingVectorizer(
n_features=2**20, # 特征维度,2的幂次利于哈希分布
norm='l2', # L2归一化(可选)
alternate_sign=False, # 是否交替符号减少冲突影响(默认True)
stop_words='english', # 可选停用词
lowercase=True,
token_pattern=r'\b\w+\b' # 默认分词模式
)
# 示例文本
texts = [
"The quick brown fox jumps over the lazy dog",
"Never jump over the lazy dog quickly"
]
# 直接转换 → 稀疏矩阵
X = vectorizer.transform(texts)
print(type(X)) # <class 'scipy.sparse._matrix.csr_matrix'>
print(X.shape) # (2, 1048576) ← 固定维度,无需fit!
哈希冲突与维度选择
问题:哈希冲突
不同词可能映射到同一索引,导致特征值叠加(语义混淆)。
解决方案:
- 增大
n_features
:冲突概率 ≈ 1 / n_features,常用 2182^{18}218(262,144)到 2222^{22}222(4,194,304)。 - 启用
alternate_sign=True
(默认):对哈希值符号交替,使冲突在训练中部分抵消(类似 signed random projection)。
# 推荐配置
vectorizer = HashingVectorizer(
n_features=2**21, # 2百万维
alternate_sign=True, # 减少冲突影响
norm='l2'
)
与 CountVectorizer 对比
特性 | CountVectorizer | HashingVectorizer |
---|---|---|
是否需要 fit() | 是 | 否(无状态) |
是否存储词汇表 | 是(占用内存) | 否(内存恒定) |
支持流式/在线学习 | 否 | 是 |
可解释性(查看词-索引) | vocabulary_ 属性 | 不可逆,无法还原词 |
内存效率 | 低(随词汇增长) | 高(固定) |
适合数据规模 | 中小型语料 | 超大型语料、流式数据 |
🚀 实际应用场景
1. 大规模文本分类(如新闻分类、垃圾邮件检测)
from sklearn.linear_model import SGDClassifier
from sklearn.feature_extraction.text import HashingVectorizer
# 流式处理:逐批读取 + 在线学习
vectorizer = HashingVectorizer(n_features=2**20, alternate_sign=True)
clf = SGDClassifier(loss='log_loss')
for batch_texts, batch_labels in data_stream:
X_batch = vectorizer.transform(batch_texts)
clf.partial_fit(X_batch, batch_labels, classes=all_classes)
2. 与 Pipeline 结合
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('hash', HashingVectorizer(n_features=2**20, stop_words='english')),
('clf', SGDClassifier())
])
pipeline.fit(texts, labels) # 内部自动 transform
3. 与 TfidfTransformer 结合(近似 TF-IDF)
from sklearn.feature_extraction.text import HashingVectorizer, TfidfTransformer
hasher = HashingVectorizer(n_features=2**20, norm=None) # 先不归一化
tfidf = TfidfTransformer()
X_counts = hasher.transform(texts)
X_tfidf = tfidf.fit_transform(X_counts) # 近似 TF-IDF(需fit idf_)
注意:此时 TfidfTransformer 仍需 fit() 来计算 IDF,因此不是完全无状态。若需完全无状态,可跳过 IDF 或使用静态 IDF。
局限性与注意事项
- 不可逆性:无法从特征索引反推原始词 → 丧失可解释性。
- 无 IDF 权重(默认):HashingVectorizer 默认只做词频哈希,如需 TF-IDF 需额外接 TfidfTransformer(但会引入状态)。
- 哈希冲突不可避免:需靠增大维度缓解,不能完全消除。
- 不适合小数据集:小数据用 CountVectorizer 更精确、可解释。
最佳实践总结
- 数据量 > 100万文档 或 词汇 > 50万 → 优先选 HashingVectorizer
- 维度选择:从 2202^{20}220 开始,根据内存和精度调整
- 启用
alternate_sign=True
(默认) 以减轻冲突影响 - 流式学习:配合 SGDClassifier, PassiveAggressiveClassifier 等支持 partial_fit 的模型
- 如需 IDF:后接 TfidfTransformer,但注意其需 fit → 丧失部分流式能力
8.使用HashingVectorizer执行核外缩放
使用_HashingVectorizer_的一个有用的进步是执行核外扩展的能力。这意味着我们可以从不适合放入计算机主内存的数据中学习。
实施核外扩展的策略是以小批量方式将数据流传输到估计器。使用_HashingVectorizer_对其进行矢量化处理,以确保估计器的输入空间始终具有相同的维数。因此,任何时候使用的内存量都受微型批处理大小的限制。尽管使用这种方法对可以摄取的数据量没有限制,但是从实际的角度来看,学习时间通常会受到在这个任务上要花费的CPU时间的限制。
9.自定义矢量化器类
简述:自定义矢量化器类(Custom Vectorizer Class)
在 scikit-learn
中,若内置的 CountVectorizer
、TfidfVectorizer
或 HashingVectorizer
无法满足特定文本处理需求(如自定义分词、特殊归一化、领域特定特征等),可创建自定义矢量化器类。
自定义矢量化器需遵循 sklearn 的接口规范,以便无缝集成到 Pipeline、GridSearch 等工具中。
✅ 核心要求
继承
BaseEstimator
和TransformerMixin
BaseEstimator
:提供get_params()
/set_params()
→ 支持超参数调优TransformerMixin
:提供.fit_transform()
方法
实现
.fit(X, y=None)
和.transform(X)
方法.fit()
:学习数据分布(如词汇表、IDF值等),可返回self
.transform()
:将文本转换为数值矩阵(通常为scipy.sparse.csr_matrix
或numpy.ndarray
)
保持无状态或明确状态管理
- 仅在
.fit()
中学习并存储必要参数(如词汇表、统计量) .transform()
应基于.fit()
学到的状态进行转换
- 仅在
🧩 示例:自定义词频矢量化器(带词干化)
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.feature_extraction.text import CountVectorizer
from nltk.stem import PorterStemmer
import numpy as np
import re
class StemmedCountVectorizer(BaseEstimator, TransformerMixin):
def __init__(self, max_features=1000, stop_words=None):
self.max_features = max_features
self.stop_words = stop_words or []
self.stemmer = PorterStemmer()
self.vectorizer = None
def fit(self, raw_documents, y=None):
# 自定义预处理:词干化
stemmed_docs = [self._stem_text(doc) for doc in raw_documents]
# 使用内置 CountVectorizer 学习词汇表
self.vectorizer = CountVectorizer(
max_features=self.max_features,
stop_words=self.stop_words,
lowercase=True
)
self.vectorizer.fit(stemmed_docs)
return self
def transform(self, raw_documents):
stemmed_docs = [self._stem_text(doc) for doc in raw_documents]
return self.vectorizer.transform(stemmed_docs)
def _stem_text(self, text):
# 简单分词 + 词干化
words = re.findall(r'\b\w+\b', text.lower())
stemmed = [self.stemmer.stem(w) for w in words if w not in self.stop_words]
return " ".join(stemmed)
# 使用示例
texts = ["running runs ran", "jumping jumps jumped"]
vectorizer = StemmedCountVectorizer(max_features=5)
X = vectorizer.fit_transform(texts)
print(X.toarray())
# 输出示例:[[3 0] [0 3]] → "run" 和 "jump" 各出现3次
图像特征提取
图像特征提取(Image Feature Extraction) 是计算机视觉中的核心预处理步骤,指从原始图像像素数据中自动提取具有判别性、鲁棒性和紧凑性的数值表示(特征向量),以便用于后续的图像分类、目标检测、图像检索、聚类等机器学习任务。
📌 核心目标:将高维、冗余、非结构化的像素矩阵 → 低维、语义化、结构化的特征向量。
🎯 为什么需要特征提取?
- 原始像素(如 224×224×3 = 150,528 维)维度高、冗余大、对光照/平移/旋转敏感。
- 机器学习模型(如 SVM、随机森林)难以直接从原始像素学习有效模式。
- 特征提取可降维、去噪、增强语义表达、提升模型泛化能力。
🧩 特征提取方法分类
1. 手工设计特征(Traditional / Handcrafted Features)
由专家根据图像性质设计,无需训练:
方法 | 描述 | 适用场景 |
---|---|---|
颜色直方图 | 统计各颜色通道的像素分布 | 图像检索、场景分类 |
HOG(方向梯度直方图) | 统计局部区域的梯度方向分布,对形状敏感 | 行人检测、物体识别 |
SIFT / SURF | 检测关键点并提取局部不变描述子(尺度/旋转不变) | 图像匹配、拼接 |
LBP(局部二值模式) | 描述局部纹理模式 | 人脸识别、纹理分类 |
✅ 优点:可解释性强、计算快、小数据有效
❌ 缺点:泛化能力有限、依赖专家知识、难以适应复杂语义
2. 深度学习特征(Learned Features)
使用卷积神经网络(CNN)自动从数据中学习多层次特征:
- 浅层网络:提取边缘、角点、纹理(类似 Gabor 滤波器)
- 中层网络:提取部件、图案(如车轮、眼睛)
- 深层网络:提取语义概念(如“猫”、“汽车”)
📌 常用方式:
- 使用预训练 CNN(如 ResNet, VGG, EfficientNet)作为特征提取器
- 移除最后的分类层,使用倒数第二层(如
pooling
或fc
层)输出作为特征向量
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.applications.resnet50 import preprocess_input
model = ResNet50(weights='imagenet', include_top=False, pooling='avg')
# 输入图像预处理
img = load_and_preprocess_image('cat.jpg') # shape: (224, 224, 3)
img = np.expand_dims(img, axis=0)
# 提取特征
features = model.predict(img) # shape: (1, 2048)
✅ 优点:语义表达强、自动学习、适应复杂任务
❌ 缺点:需大量数据、计算资源高、黑盒性、可解释性差
📊特征提取 vs 特征选择 vs 特征工程
术语 | 含义 |
---|---|
特征提取 | 从原始数据生成新特征(如像素 → HOG / CNN向量) |
特征选择 | 从已有特征中挑选重要子集(如用 SelectKBest 选前100维) |
特征工程 | 人工构造新特征(如计算图像宽高比、颜色均值等) |
✅应用场景
- 图像分类(猫 vs 狗)
- 目标检测(YOLO、Faster R-CNN 中的区域特征)
- 人脸识别(提取人脸嵌入向量)
- 图像检索(用特征向量计算相似度)
- 医学图像分析(肿瘤区域特征提取)
传统方法适合资源受限或小样本场景;
深度学习方法适合大数据、高精度、端到端任务。
随着 CNN 和 Vision Transformer 的发展,自动特征学习已成为主流,但手工特征在特定领域(如工业检测、遥感)仍具价值。
1.补丁提取
extract_patches_2d
函数从存储为二维数组或沿第三轴显示颜色信息的三维数组的图像中提取色块。
extract_patches_2d
和 PatchExtractor
类简述
这两个工具均来自 sklearn.feature_extraction.image
模块,用于从 2D 图像中提取局部图像块(patches),常用于无监督特征学习(如字典学习、稀疏编码)、数据增强或 CNN 预处理。
🧩 1. extract_patches_2d
函数
📌 功能
从单张 2D 图像中提取所有可能的重叠或非重叠图像块(滑动窗口方式)。
⚙️ 参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
image |
array-like , shape (image_height, image_width) 或 (image_height, image_width, n_channels) |
— | 输入图像(灰度或彩色) |
patch_size |
tuple (patch_height, patch_width) |
— | 要提取的图像块尺寸 |
max_patches |
int 或 float 或 None |
None |
最大提取块数: • int :绝对数量• float ∈ (0,1] :占总可能块的比例• None :提取全部可能块 |
random_state |
int , RandomState instance , None |
None |
随机种子(仅当 max_patches 设置时生效) |
📤 返回值
patches
:ndarray
, shape(n_patches, patch_height, patch_width)
或(n_patches, patch_height, patch_width, n_channels)
- 提取的所有图像块堆叠成的数组
- 若输入为彩色图(含通道),输出保留通道维度
✅ 示例
from sklearn.feature_extraction.image import extract_patches_2d
import numpy as np
# 创建 4x4 灰度图像
image = np.arange(16).reshape(4, 4)
print("原始图像:\n", image)
# 提取 2x2 的所有 patches
patches = extract_patches_2d(image, (2, 2))
print("提取的 patches 形状:", patches.shape) # (9, 2, 2)
print("第一个 patch:\n", patches[0])
🧩2. PatchExtractor 类
📌功能
从多张图像组成的图像集合中提取 patches,可看作 extract_patches_2d 的批量/可复用版本,支持随机采样,适合大数据流式处理。
⚙️参数(构造函数 init)
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
patch_size | tuple (patch_height, patch_width) | None | 图像块尺寸(若为 None,需在 fit 时指定) |
max_patches | int 或 float 或 None | None | 同 extract_patches_2d,每张图最多提取块数 |
random_state | int, RandomState instance, None | None | 随机种子 |
🧰主要方法
fit(X, y=None)
- 作用:若
patch_size=None
,从第一张图像自动推断patch_size
(不推荐,建议显式指定) - 返回:self
transform(X)
- 作用:从图像集合 X 中提取 patches
- 输入 X:array-like, shape (n_images, img_height, img_width) 或 (n_images, img_height, img_width, n_channels)
- 返回:ndarray, shape (n_total_patches, patch_height, patch_width) 或 (n_total_patches, patch_height, patch_width, n_channels)
fit_transform(X, y=None)
- 等价于
fit(X).transform(X)
✅示例
from sklearn.feature_extraction.image import PatchExtractor
import numpy as np
# 创建 3 张 4x4 图像
images = np.array([np.arange(16).reshape(4, 4) + i*16 for i in range(3)])
# 初始化提取器:每张图最多随机提取 2 个 2x2 patches
extractor = PatchExtractor(patch_size=(2, 2), max_patches=2, random_state=42)
patches = extractor.fit_transform(images)
print("总 patches 数:", patches.shape[0]) # 3 images × 2 patches = 6
print("patches 形状:", patches.shape) # (6, 2, 2)
对比总结
特性 | extract_patches_2d | PatchExtractor |
---|---|---|
输入 | 单张图像 | 多张图像(图像集合) |
是否可复用 | 一次性函数 | 类,可保存配置并重复使用 |
是否支持随机采样 | 通过 max_patches + random_state | 同左 |
是否需要 .fit() | 否 | 仅当 patch_size=None 时需要 |
适用场景 | 单图分析、原型开发 | 批量处理、Pipeline、数据流 |
使用建议
- 小规模实验/单图分析 → 用 extract_patches_2d
- 大规模训练/集成到 sklearn Pipeline → 用 PatchExtractor
- 用于无监督学习(如字典学习)时,常配合 MiniBatchKMeans 或 SparseCoder
from sklearn.cluster import MiniBatchKMeans
from sklearn.feature_extraction.image import PatchExtractor
# 提取大量 patches 用于训练字典
extractor = PatchExtractor(patch_size=(8, 8), max_patches=1000, random_state=0)
patches = extractor.fit_transform(image_collection) # shape: (1000, 8, 8)
# 展平用于聚类
patches_flat = patches.reshape(patches.shape[0], -1) # (1000, 64)
kmeans = MiniBatchKMeans(n_clusters=64)
kmeans.fit(patches_flat) # 学习视觉词典
✅ 总结:
两者核心功能相同 —— 从图像中提取局部块,区别在于接口设计和适用规模。
PatchExtractor 更适合工程化、批量处理和 sklearn 生态集成。
2.图像的连接图
在 scikit-learn 库中,多个估计器可以利用特征或样本之间的连接信息来提升模型性能和结果解释性。这种连接信息特别适用于处理具有空间结构的数据,如图像、网格数据等。例如,在图像分割和聚类任务中,考虑像素间的邻近关系能够帮助算法识别出连续的区域,从而形成有意义的补丁。
Ward 聚类与连接性矩阵
Ward 聚类(Hierarchical clustering)
Ward 聚类是一种层次聚类方法,它通过最小化簇内方差来进行合并操作。在图像处理中,Ward 聚类可以将相邻像素聚类在一起,从而形成连续的区域或补丁。这种方法特别适合于图像分割任务,因为它能够保留图像的空间结构信息。
连接性矩阵的作用
为了实现上述功能,估计器需要使用“连接性”矩阵,该矩阵指出了哪些样本之间存在连接关系。对于图像数据而言,这种连接关系通常基于像素间的邻近性。例如,在二维图像中,每个像素与其周围的 8 个邻居像素相连;在三维图像中,每个体素与其周围的 26 个体素相连。
img_to_graph
函数
scikit-learn 提供了 img_to_graph
函数,用于从 2D 或 3D 图像生成连接性矩阵。该函数会根据图像的形状自动构建一个图,其中每个节点代表一个像素或体素,边则表示节点之间的连接关系。具体来说,img_to_graph
函数返回一个稀疏矩阵,矩阵中的非零元素表示两个像素或体素之间的连接权重。
参数说明
- img: 输入图像,可以是 2D 或 3D 数组。
- mask: 可选参数,用于指定图像中哪些部分需要被考虑。默认情况下,整个图像都会被包含在内。
- return_as: 返回类型,默认为
scipy.sparse.coo_matrix
,也可以选择其他稀疏矩阵格式。
示例代码
from sklearn.feature_extraction.image import img_to_graph
import numpy as np
# 创建一个简单的 2D 图像
image = np.array([[1, 2], [3, 4]])
# 使用 img_to_graph 函数生成连接性矩阵
connectivity_matrix = img_to_graph(image)
print("连接性矩阵:\n", connectivity_matrix.toarray())
grid_to_graph
函数
除了 img_to_graph 函数外,scikit-learn 还提供了 grid_to_graph 函数,用于为给定图像形状的图像建立连接矩阵。与 img_to_graph 不同的是,grid_to_graph 不需要输入实际的图像数据,只需提供图像的形状即可生成连接性矩阵。
参数说明
- n_x, n_y, n_z: 分别表示图像在 x、y 和 z 方向上的尺寸。对于 2D 图像,可以忽略 n_z 参数。
- mask: 可选参数,用于指定图像中哪些部分需要被考虑。
- return_as: 返回类型,默认为 scipy.sparse.coo_matrix。
示例代码
from sklearn.feature_extraction.image import grid_to_graph
# 定义图像形状
image_shape = (2, 2)
# 使用 grid_to_graph 函数生成连接性矩阵
connectivity_matrix = grid_to_graph(*image_shape)
print("连接性矩阵:\n", connectivity_matrix.toarray())
总结
通过使用 img_to_graph 和 grid_to_graph 函数生成连接性矩阵,我们可以为图像数据添加额外的结构信息,从而帮助聚类算法更好地理解和处理图像内容。这些工具在图像分割、目标检测和特征提取等任务中具有广泛的应用前景。结合 Ward 聚类等方法,我们能够有效地识别图像中的连续区域,为后续的分析和处理提供坚实的基础。