在 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)=log?1+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∣∣2?v?=v12?+v22?+...+vn2??v?
這最初是為信息檢索(作為搜索引擎結果的排名函數)開發的術語加權方案,也已在文檔分類和聚類中找到了很好的用途。
以下部分包含進一步的說明和示例,表現了如何精確計算tf-idfs,以及如何在scikit-learn中計算tf-idfs。TfidfTransformer
和 TfidfVectorizer
與將idf定義為以下內容的標準教科書符號略有不同
idf(t)=log?n1+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)=log?ndf(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∣∣2?v?=v12?+v22?+...+vn2??v?
例如,我們可以如下計算 counts
數組中第一個文檔中第一項的tf-idf:
n=6n = 6n=6
df(t)term1=6df(t)_{term1} = 6df(t)term1?=6
df(t)term1=log?ndf(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)=log?1+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 TfidfVectorizervectorizer = 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 Pipelinepipeline = 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, TfidfTransformerhasher = 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 reclass StemmedCountVectorizer(BaseEstimator, TransformerMixin):def __init__(self, max_features=1000, stop_words=None):self.max_features = max_featuresself.stop_words = stop_words or []self.stemmer = PorterStemmer()self.vectorizer = Nonedef 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 selfdef 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_inputmodel = 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 聚類等方法,我們能夠有效地識別圖像中的連續區域,為后續的分析和處理提供堅實的基礎。