6.2 特征提取?

sklearn.feature_extraction模塊可用于從機器學習算法支持的格式中提取特征,這些特征由包含文本和圖像等格式的數據集構成。

**注意:**特征提取與特征選擇有很大不同:前者在于將任意數據(例如文本或圖像)轉換成可用于機器學習的數字特征。后者是應用于這些功能的機器學習技術。

6.2.1 從字典加載特征

DictVectorizer類可用于將以標準Python dict對象列表為表示形式的要素數組轉換為scikit-learn估計器使用的NumPy / SciPy形式。

盡管處理速度不是特別快,但是Python dict具有以下優點:易于使用,稀疏(缺少的特征不需要存儲)以及除了特征值之外還存儲特征名稱。

DictVectorizer通過實現one-of-K或者“獨熱”編碼來分類(又稱虛設,離散)特征。類別特征是成對的“屬性--值”形式,其中值被限制為不定序的符合要求的列表(例如主題標識符,對象類型,標簽,名稱等)。

以下示例中,“city”是分類屬性,而“temperature”是傳統的數字特征:

>>> measurements = [
...     {'city''Dubai''temperature'33.},
...     {'city''London''temperature'12.},
...     {'city''San Francisco''temperature'18.},
... ]

>>> from sklearn.feature_extraction import DictVectorizer
>>> vec = DictVectorizer()

>>> vec.fit_transform(measurements).toarray()
array([[ 1.,  0.,  0.33.],
       [ 0.,  1.,  0.12.],
       [ 0.,  0.,  1.18.]])

>>> vec.get_feature_names()
['city=Dubai''city=London''city=San Francisco''temperature']

自然語言處理模型進行訓練序列分類器的過程中,DictVectorizer 類在表示形式轉換時很有用,通常通過提取特定感興趣詞周圍的特征窗口來起作用。

例如,假設有第一個算法用來提取詞性(PoS)標簽,我們希望將其用作訓練序列分類器(例如分塊器)的補充標簽。以下命令可能是這樣的窗口:在 ‘The cat sat on the mat.’句子中的“ sat”一詞周圍提取特征:

>>> pos_window = [
...     {
...         'word-2''the',
...         'pos-2''DT',
...         'word-1''cat',
...         'pos-1''NN',
...         'word+1''on',
...         'pos+1''PP',
...     },
...     # in a real application one would extract many such dictionaries
... ]

可以將該描述矢量化為適合呈遞給分類器的稀疏二維矩陣(可能在通過管道傳遞到 text.TfidfTransformer中進行歸一化之后):

>>> vec = DictVectorizer()
>>> pos_vectorized = vec.fit_transform(pos_window)
>>> pos_vectorized
<1x6 sparse matrix of type '<... 'numpy.float64'>'
    with 6 stored elements in Compressed Sparse ... format>
>>> pos_vectorized.toarray()
array([[1.1.1.1.1.1.]])
>>> vec.get_feature_names()
['pos+1=PP''pos-1=NN''pos-2=DT''word+1=on''word-1=cat''word-2=the']

可以想象,如果圍繞文檔語料庫的每個單詞提取這樣的上下文,則生成的矩陣將非常寬(許多“one-hot-features"),并且大多數情況下大多數時間該值都為零。為了使生成的數據結構能夠適合內存,DictVectorizer類默認使用scipy.sparse矩陣而不是numpy.ndarray

6.2.2 特征哈希

FeatureHasher類是一種高速,低內存消耗的矢量化程序,它使用一種被稱為 特征哈希(或稱為“哈希技巧”)的技術。像矢量化程序那樣,與其構建訓練中遇到的特征的哈希表,不如讓FeatureHasher 實例將哈希函數應用于特征,以直接確定其在樣本矩陣中的列索引。結果是以可檢查性為代價提高了速度,減少了內存使用;哈希器無法記住輸入特征的外觀,并且沒有inverse_transform方法。

由于散列函數可能導致(不相關)特征之間的沖突,因此使用帶符號散列函數,并且存儲在輸出矩陣中的特征值的符號是由散列值的符號確定。這樣,沖突可能會抵消而不是累積誤差,并且任何輸出要素的值的預期均值為零。alternate_sign=True時,此機制默認已啟用,并且對于較小的哈希表(n_features < 10000)特別有用。對于較大的哈希表,可以將其禁用,以允許將輸出傳遞到要求非負輸入的估計器,例如 sklearn.naive_bayes.MultinomialNB 或者 sklearn.feature_selection.chi2

FeatureHasher根據構造函數參數 input_type,接受映射(如Python中的dict 及其在collections模塊中的變體),以及成對出現的(feature, value)或字符串。映射被視為 (feature, value) 對的列表,而單個字符串的隱含值為1,因此 ['feat1', 'feat2', 'feat3'] 被解釋為 [('feat1', 1), ('feat2', 1), ('feat3', 1)] 。如果一個特征在一個樣本中多次出現,則相關的值將被求和(('feat', 2)('feat', 3.5)成為('feat', 5.5))。 FeatureHasher 的輸出始終是CSR格式的scipy.sparse 矩陣。

特征散列可以用于文檔分類,但是與text.CountVectorizer不同,FeatureHasher不會進行字符分割或除Unicode-to-UTF-8編碼外的任何其他預處理; 請參閱下面的 使用散列技巧對大型文本語料庫進行矢量化處理,以獲取組合的標記器/哈希器。

例如,有一個詞級別的自然語言處理任務,需要從 (token, part_of_speech)鍵值對中提取特征。可以使用 Python 生成器函數來提取功能:

def token_features(token, part_of_speech):
    if token.isdigit():
        yield "numeric"
    else:
        yield "token={}".format(token.lower())
        yield "token,pos={},{}".format(token, part_of_speech)
    if token[0].isupper():
        yield "uppercase_initial"
    if token.isupper():
        yield "all_uppercase"
    yield "pos={}".format(part_of_speech)

然后, 可以使用以下命令構造要傳入FeatureHasher.transformraw_X

raw_X = (token_features(tok, pos_tagger(tok)) for tok in corpus)

并通過以下方式傳入哈希器:

hasher = FeatureHasher(input_type='string')
X = hasher.transform(raw_X)

得到一個scipy.sparse矩陣X

注意生成器使用的理解,在特征提取中引入了惰性:標記(tokens)僅根據哈希器的要求進行處理。

6.2.2.1 實施細節

FeatureHasher 使用的是MurmurHash3的32位變體,(由于scipy.sparse的限制)致使當前支持的最大特征數量為

哈希技巧的原始形式源自Weinberger等人用兩個單獨的哈希函數 分別確定特征的列索引和標記。當前實現基于假設:MurmurHash3的符號位與其他位獨立。

由于使用簡單的模數將哈希函數轉換為列索引,因此建議使用2的冪次方作為n_features參數。否則特征不會均勻地映射到列中。

參考文獻:

6.2.3 文本特征提取

6.2.3.1 詞袋表示法

文本分析是機器學習算法的主要應用領域。但是,原始數據,符號序列不能直接輸入到算法本身,因為大多數算法期望的是具有固定大小的數值特征向量,而不是具有可變長度的原始文本文檔。

為了解決這個問題,scikit-learn提供了從文本內容中提取數字特征的最常用方法,即:

  • 標記字符串并為每個可能的標記提供整數ID,例如通過使用空格和標點符號作為標記分隔符。
  • 統計每個文檔中標記出現的頻次。
  • 大多數樣本/文檔中采用通過減少重要性標記來進行標準化和加權。

在此方案中,特征和樣本被定義如下:

  • 每個單獨的標記出現頻率(已標準化或未標準化)都被視為特征
  • 給定文檔中的所有標記頻率的向量被認為是一個多元樣本

因此,文檔語料庫可以由矩陣表示,每行對應一個文檔,每列對應語料庫中出現的標記(如單個詞)。

我們將向量化稱為將文本文檔集合轉換為數字特征向量的一般過程。這種特定的策略(標記化,計數和歸一化)稱為“ 詞袋” 或“ n-gram袋”表示。通過單詞出現來描述文檔,而完全忽略文檔中單詞的相對位置信息。

6.2.3.2 稀疏性

由于大多數文檔通常只會使用文檔語料庫中很小一部分的單詞,因此生成的矩陣中很多特征值為零(通常超過總特征值的99%)。

例如,一個包含10,000個短文本文檔(例如電子郵件)的集合將使用一個詞匯表,該詞匯表的總大小約包含100,000個不重復的單詞,而每個文檔將單獨使用100至1000個不重復的單詞。

為了能夠將這樣的矩陣存儲在內存中并且還可以加快矩陣/向量的代數運算,通常使用例如scipy.sparse包中的稀疏表示來實現。

6.2.3.3 Vectorizer的常見用法

CountVectorizer 在單個類中實現標記化和出現計數:

>>> from sklearn.feature_extraction.text import CountVectorizer

該模型具有許多參數,但是參數默認值是相當合理的(有關詳細信息,請參見參考文檔):

>>> vectorizer = CountVectorizer()
>>> vectorizer
CountVectorizer()

讓我們用它來對一個由文本文檔組成的簡約文集進行標記和詞頻統計:

>>> corpus = [
...     'This is the first document.',
...     'This is the second second document.',
...     'And the third one.',
...     'Is this the first document?',
... ]
>>> X = vectorizer.fit_transform(corpus)
>>> X
<4x9 sparse matrix of type '<... 'numpy.int64'>'
    with 19 stored elements in Compressed Sparse ... format>

默認配置通過提取至少包含2個字母的單詞來提取字符串。執行這一步的特定功能可以被顯式調用:

>>> analyze = vectorizer.build_analyzer()
>>> analyze("This is a text document to analyze.") == (
...     ['this''is''text''document''to''analyze'])
True

在擬合過程中,分析器會找到并分配一個唯一的整數索引給每個詞語,該索引對應于所得矩陣中的一列。可以按以下方式檢索這些列的解釋:

>>> vectorizer.get_feature_names() == (
...     ['and''document''first''is''one',
...      'second''the''third''this'])
True

>>> X.toarray()
array([[011100101],
       [010102101],
       [100010110],
       [011100101]]...)

從特征名稱到列索引的逆映射存儲在矢量化器的vocabulary_屬性中:

>>> vectorizer.vocabulary_.get('document')
1

因此,將來在對transform方法進行調用時,訓練語料庫中未出現的單詞將被完全忽略:

>>> vectorizer.transform(['Something completely new.']).toarray()
array([[000000000]]...)

注意,在前一語料庫中,第一個文檔和最后一個文檔恰好具有相同的詞,因此被編碼為相等的向量。特別是,我們失去了最后一個文檔是疑問形式的信息。為了保留本地指令信息,除了提取 1-grams(個別詞)之外,我們還可以提取 2-grams 的單詞:

>>> bigram_vectorizer = CountVectorizer(ngram_range=(12),
...                                     token_pattern=r'\b\w+\b', min_df=1)
>>> analyze = bigram_vectorizer.build_analyzer()
>>> analyze('Bi-grams are cool!') == (
...     ['bi''grams''are''cool''bi grams''grams are''are cool'])
True

因此,此矢量化器提取的詞匯量要大得多,同時還可以解決本地定位模式中編碼的歧義:

>>> X_2 = bigram_vectorizer.fit_transform(corpus).toarray()
>>> X_2
array([[001111100000110000110],
       [001001100211101000110],
       [110000001000100111000],
       [001111010000110000101]]...)

特別是“Is this”的疑問句形式僅出現在最后的文檔中:

>>> feature_index = bigram_vectorizer.vocabulary_.get('is this')
>>> X_2[:, feature_index]     
array([0001]...)

6.2.3.3.1 停用詞的使用

停用詞是指諸如“和”,“這”,“他”之類的詞,它們被認為在表示文本內容方面沒有提供任何信息,可以將其刪除以避免將其理解為參與預測的信息。然而有時候,類似的詞對于預測很有用,例如在對寫作風格或性格進行分類時。

我們提供的“英語”停用詞列表中有幾個已知問題。它并非旨在成為通用的“一刀切”解決方案,因為某些任務可能需要定制的解決方案。有關更多詳細信息,請參見[NQY18]。

請謹慎選擇停用詞列表。流行的停用詞列表可能包含對某些任務非常有用的詞(例如計算機)

您還應該確保停用詞列表具有與矢量化器中使用的是相同的預處理和標記。單詞CountVectorizer的默認標記分配器分割成,所以如果在停止詞列表中,但不在,會被保留在轉換后的文本中。我們的向量化器將嘗試識別并警告某些不一致之處。

參考文獻

6.2.3.4 TF–IDF術語權重

在一個大型文本語料庫中,有些高頻出現的詞(例如英語中“the”, “a”, “is” )幾乎沒有攜帶任何與文檔內容相關的有用信息。 如果我們將統計數據直接提供給分類器,那么這些高頻出現的詞會掩蓋住那些我們關注但出現次數較少的詞。

為了重新加權特征計數為適合分類器使用的浮點值,通常使用tf–idf變換。

Tf表示詞頻,而tf–idf表示詞頻乘以 逆文檔頻率

使用TfidfTransformer的默認設置, TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False) 詞頻,詞語在給定文檔中出現的次數乘以idf分量,其計算公式為:

其中是文檔集中的文檔總數,是文檔集中包含詞語的文檔數量,然后將所得的tf-idf向量通過歐幾里得范數歸一化:

這最初是為信息檢索(作為搜索引擎結果的排名函數)開發的術語加權方案,也已在文檔分類和聚類中找到了很好的用途。

以下部分包含進一步的說明和示例,表現了如何精確計算tf-idfs,以及如何在scikit-learn中計算tf-idfs,TfidfTransformerTfidfVectorizer與將idf定義為以下內容的標準教科書符號略有不同

TfidfTransformer和設置了smooth_idf=FalseTfidfVectorizer 中,將“ 1”計數添加到idf中,而不是idf的分母中:

該歸一化由TfidfTransformer 類實現:

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer
TfidfTransformer(smooth_idf=False)

所有參數的詳細信息請參閱參考文檔

以下方統計為例。第一項100%的時間都出現,因此不是很有重要。另外兩個特征只占不到50%的時間出現,因此可能更能代表文檔的內容:

>>> counts = [[301],
...           [200],
...           [300],
...           [400],
...           [320],
...           [302]]
...
>>> 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.819409950.        , 0.57320793],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [0.473303390.880899480.        ],
       [0.581492610.        , 0.81355169]])

每行都被正則化,使其適應歐幾里得標準:

例如,我們可以如下計算counts數組中第一個文檔中第一項的tf-idf :

-

現在,如果我們對文檔中剩余的2個詞語重復此計算,我們將得到:

-

-

以及原始tf-idfs的向量:

-

然后,應用歐幾里得(L2)范數,我們為文檔1獲得以下tf-idfs:

此外,默認參數smooth_idf=True將“ 1”添加到分子和分母,類似于通過一個包含集合中的每個詞語的附加文檔從而避免除零錯誤。

使用此修改,文檔1中第三項的tf-idf更改為1.8473:

-

并且L2歸一化的tf-idf變為:

>>> transformer = TfidfTransformer()
>>> transformer.fit_transform(counts).toarray()
array([[0.851513350.        , 0.52433293],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [0.554228930.832364280.        ],
       [0.630357310.        , 0.77630514]])

通過調用fit方法計算的每個特征的權重并存儲在模型屬性中:

>>> transformer.idf_
array([1. ..., 2.25..., 1.84...])

由于tf–idf通常用于文本特征,因此還有一個被稱為TfidfVectorizer的類,它在一個模型中結合了CountVectorizerTfidfTransformer的所有選項:

>>> 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歸一化通常非常有用,但是在某些情況下,二進制標記可能會提供更好的特征。這可以通過使用CountVectorizerbinary參數來實現。 特別是一些估計器(例如 Bernoulli Naive Bayes)顯式的使用離散的布爾隨機變量。還有,很短的文本可能帶有嘈雜的tf–idf值,而二進制標志信息則更穩定。

通常,調整特征選取參數的最佳方法是使用帶交叉驗證的網格搜索,例如通過將特征提取器與分類器進行流水線化:

用于文本特征提取和評估的示例管道

6.2.3.5 解碼文本文件

文本由字符組成,但文件由字節組成。字節依照一定的編碼方式表示字符。要使用Python處理文本文件,必須將其字節解碼為稱為Unicode的字符集。常見編碼為ASCII,Latin-1(西歐),KOI8-R(俄語)以及通用編碼UTF-8和UTF-16。也存在許多其他的編碼。

**注意:**編碼也可以稱為“字符集”,但這個術語的不太準確:單個字符集可以存在多種編碼。

scikit-learn中的文本特征提取器知道如何解碼文本文件,但是需要提前告知文件的編碼方式。

為此,CountVectorizer有一個encoding參數。 對于現在的文本文件,正確的編碼可能是UTF-8,因此這是默認編碼(encoding="utf-8")。

但是,如果您要加載的文本實際上未使用UTF-8編碼,則會得到一個UnicodeDecodeError。通過將decode_error參數設置為"ignore""replace",可以使矢量化器對解碼錯誤保持沉默以避免拋出解碼錯誤。有關bytes.decode更多詳細信息,請參見Python函數的文檔 (在Python提示符下鍵入help(bytes.decode))。

如果您在解碼文本時遇到問題,請嘗試以下操作:

  • 找出文本的實際編碼是什么。該文件可能帶有標頭或自述文件來告訴您編碼,或者您可以根據文本的來源推斷使用一些標準編碼。
  • 使用UNIX命令,您也許可以找到一般的編碼方式file。Python的 chardet模塊帶有一個名為chardetect.py的腳本,它將猜測特定的編碼,盡管它的猜測不一定是正確的。
  • 您可以嘗試使用UTF-8并且忽略錯誤。您可以使用bytes.decode(errors='replace')解碼字節字符串以將所有解碼錯誤替換為無意義的字符,或者在矢量化程序中設置decode_error='replace' 。這個操作可能會破壞特征的效用。
  • 真實文本可能來自各種使用不同編碼的來源,或者甚至采用與編碼時所用的編碼不同的解碼方式進行草率解碼。這在從Web檢索的文本中很常見。Python軟件包ftfy可以自動分類出一些解碼錯誤,因此您可以嘗試將未知文本解碼為latin-1 ,然后使用ftfy來修復錯誤。
  • 如果文本采用混合編碼,很難整理出來(20個新聞組數據集就是這種情況),則可以使用簡單的單字節編碼,例如latin-1。某些文本可能顯示不準確,但是至少相同的字節序列將始終表示相同的特征。

例如,以下代碼片段使用chardet (scikit-learn不附帶,必須單獨安裝)來確定三個文本的編碼。然后對文本進行矢量化處理,并顯示學習到的詞匯。輸出未在此處顯示。

>>> import chardet    # doctest: +SKIP
>>> text1 = b"Sei mir gegr\xc3\xbc\xc3\x9ft mein Sauerkraut"
>>> text2 = b"holdselig sind deine Ger\xfcche"
>>> text3 = b"\xff\xfeA\x00u\x00f\x00 \x00F\x00l\x00\xfc\x00g\x00e\x00l\x00n\x00 \x00d\x00e\x00s\x00 \x00G\x00e\x00s\x00a\x00n\x00g\x00e\x00s\x00,\x00 \x00H\x00e\x00r\x00z\x00l\x00i\x00e\x00b\x00c\x00h\x00e\x00n\x00,\x00 \x00t\x00r\x00a\x00g\x00 \x00i\x00c\x00h\x00 \x00d\x00i\x00c\x00h\x00 \x00f\x00o\x00r\x00t\x00"
>>> decoded = [x.decode(chardet.detect(x)['encoding'])
...            for x in (text1, text2, text3)]        # doctest: +SKIP
>>> v = CountVectorizer().fit(decoded).vocabulary_    # doctest: +SKIP
>>> for term in v: print(v)                           # doctest: +SKIP

(根據 chardet的版本,可能會返回第一個值錯誤的結果。)

有關Unicode和字符編碼的一般介紹,請參閱Joel Spolsky的 Absolute Minimum Every Software Developer Must Know About Unicode.

6.2.3.6 應用與實例

詞袋表示法非常簡單,但實際上卻很有用。

特別是在有監督的環境中,它可以與快速且可擴展的線性模型成功地組合以訓練文檔分類器,例如:

無監督的環境中,可以通過應用諸如K-means之類的聚類算法將類似的文檔分組在一起:

最后,可以通過放寬聚類的硬性約束條件,例如通過使用非負矩陣分解(NMF或NNMF)來發現語料庫的主要主題:

6.2.3.7 詞袋表示法的局限性

字母組合的集合(即單詞)無法捕獲短語和多單詞表達,很大程度上忽略了任何單詞順序依賴性。此外,單詞袋模型不會考慮潛在的拼寫錯誤或單詞派生。

N-grams 可以拯救我們!與其建立簡單的字母組合(n = 1),不如選擇對成對的連續單詞進行計數的二元組(n = 2)。

還可以考慮選擇n-grams字符的集合,一種可以抵抗拼寫錯誤和派生的表示法。

例如,假設我們要處理兩個文檔的語料庫: ['words', 'wprds']. 第二個文檔中包含的一個單詞“ words”拼寫錯誤。一個簡單的詞袋表示法會將這兩個文檔視為差異很大的文檔,可能有兩個特征存在差異。然而,2-gram字符表示法可以找到匹配文檔的8個特征中的4個,這可能有助于首選分類器做出更優決定:

>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(22))
>>> counts = ngram_vectorizer.fit_transform(['words''wprds'])
>>> ngram_vectorizer.get_feature_names() == (
...     [' w''ds''or''pr''rd''s ''wo''wp'])
True
>>> counts.toarray().astype(int)
array([[11101110],
       [11011101]])

在上面的示例中,使用了char_wb分析器,該分析器僅從單詞邊界內的字符(每邊都用空格填充)創建n-gram。char分析儀還可以創建跨單詞的n-gram:

>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(55))
>>> ngram_vectorizer.fit_transform(['jumpy fox'])
<1x4 sparse matrix of type '<... 'numpy.int64'>'
   with 4 stored elements in Compressed Sparse ... format>
>>> ngram_vectorizer.get_feature_names() == (
...     [' fox '' jump''jumpy''umpy '])
True

>>> ngram_vectorizer = CountVectorizer(analyzer='char', ngram_range=(55))
>>> ngram_vectorizer.fit_transform(['jumpy fox'])
<1x5 sparse matrix of type '<... 'numpy.int64'>'
    with 5 stored elements in Compressed Sparse ... format>
>>> ngram_vectorizer.get_feature_names() == (
...     ['jumpy''mpy f''py fo''umpy ''y fox'])
True

單詞邊界感知變體char_wb對于使用空格進行單詞分離的語言特別有用,因為在這種情況下,它產生的噪聲特征比原始char變體少得多。對于這樣的語言,它可以增加使用這些特征訓練的分類器的預測精度和收斂速度,同時保留關于拼寫錯誤和單詞派生的穩健性。

雖然可以通過提取n-gram而不是單個單詞來保留一些局部信息,但詞袋和n-gram袋會破壞文檔的大部分內部結構,從而破壞該內部結構所攜帶的大部分含義。

為了處理自然語言理解的更廣泛的任務,應該考慮句子和段落的局部結構。于是,很多這樣的模型將被視為目前不在 scikit-learn范圍內的“結構化輸出”問題。

6.2.3.8 使用哈希技巧對大型文本語料庫進行矢量化處理

上述向量化方案很簡單,但是它有從字符串標記到整數特征索引vocabulary_屬性)的內存映射,這一事實在處理大型數據集時會引起一些問題

  • 語料庫越大,詞匯量就越大,內存使用也越多,
  • 擬合需要根據原始數據集的大小成比例分配中間數據結構的大小.
  • 建立單詞映射需要對數據集進行全面遍歷,因此不可能以嚴格的在線方式擬合文本分類器。
  • vocabulary_大的pickling 和 un-pickling的矢量化器程序可能非常慢(通常比pickling / un-pickling 例如相同大小NumPy的數組平面數據結構慢得多)
  • 將矢量化任務分成多個并行子任務是不容易的,因為該vocabulary_屬性必須是具有細粒度同步障礙的共享狀態:從標記字符串到特征索引的映射取決于每個標記首次出現的順序,因此必須共享這些資源,這有可能損害并行子任務的性能,使其比順序變體慢。

通過組合由 sklearn.feature_extraction.FeatureHasher類實現的“哈希技巧”(Feature hashing)和CountVectorizer的文本預處理與標記化功能,可以克服這些限制。

這種組合是在HashingVectorizer類中實現的,一個與CountVectorizer大部分API兼容的轉換器類。 HashingVectorizer是無狀態的,這意味著您不必對它調用fit

>>> from sklearn.feature_extraction.text import HashingVectorizer
>>> hv = HashingVectorizer(n_features=10)
>>> hv.transform(corpus)
<4x10 sparse matrix of type '<... 'numpy.float64'>'
    with 16 stored elements in Compressed Sparse ... format>

能夠看到在向量輸出中提取了16個非零特征標記:這比之前CountVectorizer在同一樣本語料庫中提取的19個非零特征標記少 。差異來自由于n_features參數值較低引起的哈希函數沖突。

在實際設置中,該n_features參數可以保留為其默認值2 ** 20(大約一百萬個可能的特征)。如果內存或下游模型的大小是一個問題,則選擇一個較低的值2 ** 18可能會有所幫助,避免在典型的文本分類任務上引入過多的額外沖突。

請注意,維度并不影響操作CSR矩陣(LinearSVC(dual=True), Perceptron, SGDClassifier)算法訓練時CPU的運行時間,但是它會影響與CSC矩陣(LinearSVC(dual=False), Lasso(), etc)一起使用的算法。

讓我們使用默認設置再試一次:

>>> hv = HashingVectorizer()
>>> hv.transform(corpus)
<4x1048576 sparse matrix of type '<... 'numpy.float64'>'
    with 19 stored elements in Compressed Sparse ... format>

我們沒有再遇到沖突,但這是以輸出空間更大的緯度值為代價的。當然,使用的這19個詞語之外的其他詞語可能仍會相互沖突。

HashingVectorizer還帶有以下限制:

  • 無法反轉模型(沒有 inverse_transform 方法),也不訪問特征的原始字符串表示形式,因為執行映射的哈希函數具有單向性。
  • 不提供IDF加權,因為這會在模型中引入有狀態性。如果需要,可以在管道中為它附加一個TfidfTransformer

6.2.3.9 使用HashingVectorizer執行核外縮放

使用HashingVectorizer的一個有用的進步是執行核外擴展的能力。這意味著我們可以從不適合放入計算機主內存的數據中學習。

實施核外擴展的策略是以小批量方式將數據流傳輸到估計器。使用HashingVectorizer 對其進行矢量化處理,以確保估計器的輸入空間始終具有相同的維數。因此,任何時候使用的內存量都受微型批處理大小的限制。盡管使用這種方法對可以攝取的數據量沒有限制,但是從實際的角度來看,學習時間通常會受到在這個任務上要花費的CPU時間的限制。

有關文本分類任務中核外縮放的完整示例,請參見文本文檔的核外分類

6.2.3.10 自定義矢量化器類

通過將一個callable傳遞給vectorizer構造函數,可以自定義行為:

>>> def my_tokenizer(s):
...     return s.split()
...
>>> vectorizer = CountVectorizer(tokenizer=my_tokenizer)
>>> vectorizer.build_analyzer()(u"Some... punctuation!") == (
...     ['some...''punctuation!'])
True

特別是命名

  • preprocessor:一個將整個文檔作為輸入(作為單個字符串)的可調用的方法,,并仍然作為整個字符串返回文檔轉換后的可能的版本,。這可用于刪除HTML標記,將整個文檔小寫等。
  • tokenizer:一個從預處理器獲取輸出并將其拆分為標記的可調用方法,然后返回包含這些標記的列表。
  • analyzer:一個可替換預處理器和標記生成器的可調用程序。默認分析器都調用預處理器和標記器,但是自定義分析器將跳過此過程。N-gram提取和停用詞過濾是在分析器級別進行的,因此自定義分析器可能必須重現這些步驟。

(Lucene用戶可能會識別出這些名稱,但是請注意,scikit-learn概念可能不會一對一映射到Lucene概念上。)

為了使預處理,分詞器和分析器意識到模型參數可以從類派生并重寫 build_preprocessorbuild_tokenizerbuild_analyzer 工廠方法,而不是傳遞自定義函數。

一些提示和技巧:

  • 如果文檔是由外部程序包預先標記的,則將它們存儲在文件(或字符串)中,并用空格分隔標記并傳遞參數 analyzer=str.split
  • scikit-learn代碼庫中不包含花式標記級分析,例如詞干,詞組去除,復合分割,基于詞性的過濾等,但可以通過自定義標簽生成器或分析器來添加。這是一個使用NLTK的標記器和詞條 分解器的CountVectorizer
>>> from nltk import word_tokenize          
>>> from nltk.stem import WordNetLemmatizer 
>>> class LemmaTokenizer:
...     def __init__(self):
...         self.wnl = WordNetLemmatizer()
...     def __call__(self, doc):
...         return [self.wnl.lemmatize(t) for t in word_tokenize(doc)]
...
>>> vect = CountVectorizer(tokenizer=LemmaTokenizer())  

(請注意,這不會過濾掉標點符號。)

例如,以下示例將某些英國拼寫轉換為美國拼寫:

>>> import re
>>> def to_british(tokens):
...     for t in tokens:
...         t = re.sub(r"(...)our$"r"\1or", t)
...         t = re.sub(r"([bt])re$"r"\1er", t)
...         t = re.sub(r"([iy])s(e$|ing|ation)"r"\1z\2", t)
...         t = re.sub(r"ogue$""og", t)
...         yield t
...
>>> class CustomVectorizer(CountVectorizer):
...     def build_tokenizer(self):
...         tokenize = super().build_tokenizer()
...         return lambda doc: list(to_british(tokenize(doc)))
...
>>> print(CustomVectorizer().build_analyzer()(u"color colour"))
[...'color', ...'color']

用于其他樣式的預處理;示例包括詞干,詞形化或規范化數字標記,后者說明如下:

使用“光譜共聚類”算法對文檔進行聚類

在處理不使用顯式單詞分隔符(例如空格)的亞洲語言時,自定義矢量化程序也很有用。

6.2.4 圖像特征提取

6.2.4.1 補丁提取

extract_patches_2d函數從存儲為二維數組或沿第三軸顯示顏色信息的三維數組的圖像中提取色塊。為了用所有修補程序重建圖像,請使用 reconstruct_from_patches_2d。例如,讓我們使用3個顏色通道(例如RGB格式)生成一個4x4像素圖片:

>>> import numpy as np
>>> from sklearn.feature_extraction import image

>>> one_image = np.arange(4 * 4 * 3).reshape((443))
>>> one_image[:, :, 0]  # R channel of a fake RGB picture
array([[ 0,  3,  6,  9],
       [12151821],
       [24273033],
       [36394245]])

>>> patches = image.extract_patches_2d(one_image, (22), max_patches=2,
...     random_state=0)
>>> patches.shape
(2223)
>>> patches[:, :, :, 0]
array([[[ 0,  3],
        [1215]],

       [[1518],
        [2730]]])
>>> patches = image.extract_patches_2d(one_image, (22))
>>> patches.shape
(9223)
>>> patches[4, :, :, 0]
array([[1518],
       [2730]])

現在讓我們嘗試通過對重疊區域求平均來從補丁中重建原始圖像:

>>> reconstructed = image.reconstruct_from_patches_2d(patches, (443))
>>> np.testing.assert_array_equal(one_image, reconstructed)

PatchExtractor類的工作方式與 extract_patches_2d相同,只是它支持多圖像輸入。由于被實現為一個估計器,因此它可以在管道中使用。如下:

>>> five_images = np.arange(5 * 4 * 4 * 3).reshape(5443)
>>> patches = image.PatchExtractor(patch_size=(22)).transform(five_images)
>>> patches.shape
(45223)

6.2.4.2 圖像的連接圖

scikit-learn 中的多個估計器可以使用特征或樣本之間的連接信息。例如Ward聚類(Hierarchical clustering)只能將圖像的相鄰像素聚類在一起,從而形成連續的補丁:

為此,估計器使用“連接性”矩陣,給出了要連接的樣本。 img_to_graph函數從2D或3D圖像返回這樣一個矩陣。同樣,grid_to_graph為給定圖像形狀的圖像建立連接矩陣。

這些矩陣可用于在使用連通性信息的估計器中強加連接,例如Ward聚類(層次聚類),而且還以構建預計算的內核或相似性矩陣。

示例: