scikit-learn 特征提取

2023-02-20 14:35 更新

sklearn.feature_extraction模塊可用于從機(jī)器學(xué)習(xí)算法支持的格式中提取特征,這些特征由包含文本和圖像等格式的數(shù)據(jù)集構(gòu)成。

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

6.2.1 從字典加載特征

DictVectorizer類可用于將以標(biāo)準(zhǔn)Python dict對象列表為表示形式的要素?cái)?shù)組轉(zhuǎn)換為scikit-learn估計(jì)器使用的NumPy / SciPy形式。

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

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

以下示例中,“city”是分類屬性,而“temperature”是傳統(tǒng)的數(shù)字特征:

>>> 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']

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

例如,假設(shè)有第一個算法用來提取詞性(PoS)標(biāo)簽,我們希望將其用作訓(xùn)練序列分類器(例如分塊器)的補(bǔ)充標(biāo)簽。以下命令可能是這樣的窗口:在 ‘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中進(jìn)行歸一化之后):

>>> 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"),并且大多數(shù)情況下大多數(shù)時間該值都為零。為了使生成的數(shù)據(jù)結(jié)構(gòu)能夠適合內(nèi)存,DictVectorizer類默認(rèn)使用scipy.sparse矩陣而不是numpy.ndarray。

6.2.2 特征哈希

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

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

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

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

例如,有一個詞級別的自然語言處理任務(wù),需要從 (token, part_of_speech)鍵值對中提取特征??梢允褂?Python 生成器函數(shù)來提取功能:

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)

然后, 可以使用以下命令構(gòu)造要傳入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。

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

6.2.2.1 實(shí)施細(xì)節(jié)

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

哈希技巧的原始形式源自Weinberger等人用兩個單獨(dú)的哈希函數(shù) 分別確定特征的列索引和標(biāo)記。當(dāng)前實(shí)現(xiàn)基于假設(shè):MurmurHash3的符號位與其他位獨(dú)立。

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

參考文獻(xiàn):

6.2.3 文本特征提取

6.2.3.1 詞袋表示法

文本分析是機(jī)器學(xué)習(xí)算法的主要應(yīng)用領(lǐng)域。但是,原始數(shù)據(jù),符號序列不能直接輸入到算法本身,因?yàn)榇蠖鄶?shù)算法期望的是具有固定大小的數(shù)值特征向量,而不是具有可變長度的原始文本文檔。

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

  • 標(biāo)記字符串并為每個可能的標(biāo)記提供整數(shù)ID,例如通過使用空格和標(biāo)點(diǎn)符號作為標(biāo)記分隔符。
  • 統(tǒng)計(jì)每個文檔中標(biāo)記出現(xiàn)的頻次。
  • 大多數(shù)樣本/文檔中采用通過減少重要性標(biāo)記來進(jìn)行標(biāo)準(zhǔn)化和加權(quán)。

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

  • 每個單獨(dú)的標(biāo)記出現(xiàn)頻率(已標(biāo)準(zhǔn)化或未標(biāo)準(zhǔn)化)都被視為特征。
  • 給定文檔中的所有標(biāo)記頻率的向量被認(rèn)為是一個多元樣本

因此,文檔語料庫可以由矩陣表示,每行對應(yīng)一個文檔,每列對應(yīng)語料庫中出現(xiàn)的標(biāo)記(如單個詞)。

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

6.2.3.2 稀疏性

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

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

為了能夠?qū)⑦@樣的矩陣存儲在內(nèi)存中并且還可以加快矩陣/向量的代數(shù)運(yùn)算,通常使用例如scipy.sparse包中的稀疏表示來實(shí)現(xiàn)。

6.2.3.3 Vectorizer的常見用法

CountVectorizer 在單個類中實(shí)現(xiàn)標(biāo)記化和出現(xiàn)計(jì)數(shù):

>>> from sklearn.feature_extraction.text import CountVectorizer

該模型具有許多參數(shù),但是參數(shù)默認(rèn)值是相當(dāng)合理的(有關(guān)詳細(xì)信息,請參見參考文檔):

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

讓我們用它來對一個由文本文檔組成的簡約文集進(jìn)行標(biāo)記和詞頻統(tǒng)計(jì):

>>> 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>

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

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

在擬合過程中,分析器會找到并分配一個唯一的整數(shù)索引給每個詞語,該索引對應(yīng)于所得矩陣中的一列??梢园匆韵路绞綑z索這些列的解釋:

>>> 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方法進(jìn)行調(diào)用時,訓(xùn)練語料庫中未出現(xiàn)的單詞將被完全忽略:

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

注意,在前一語料庫中,第一個文檔和最后一個文檔恰好具有相同的詞,因此被編碼為相等的向量。特別是,我們失去了最后一個文檔是疑問形式的信息。為了保留本地指令信息,除了提取 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”的疑問句形式僅出現(xiàn)在最后的文檔中:

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

6.2.3.3.1 停用詞的使用

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

我們提供的“英語”停用詞列表中有幾個已知問題。它并非旨在成為通用的“一刀切”解決方案,因?yàn)槟承┤蝿?wù)可能需要定制的解決方案。有關(guān)更多詳細(xì)信息,請參見[NQY18]。

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

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

參考文獻(xiàn)

6.2.3.4 TF–IDF術(shù)語權(quán)重

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

為了重新加權(quán)特征計(jì)數(shù)為適合分類器使用的浮點(diǎn)值,通常使用tf–idf變換。

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

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

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

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

以下部分包含進(jìn)一步的說明和示例,表現(xiàn)了如何精確計(jì)算tf-idfs,以及如何在scikit-learn中計(jì)算tf-idfs,TfidfTransformerTfidfVectorizer與將idf定義為以下內(nèi)容的標(biāo)準(zhǔn)教科書符號略有不同

TfidfTransformer和設(shè)置了smooth_idf=FalseTfidfVectorizer 中,將“ 1”計(jì)數(shù)添加到idf中,而不是idf的分母中:

該歸一化由TfidfTransformer 類實(shí)現(xiàn):

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

所有參數(shù)的詳細(xì)信息請參閱參考文檔

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

>>> 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]])

每行都被正則化,使其適應(yīng)歐幾里得標(biāo)準(zhǔn):

例如,我們可以如下計(jì)算counts數(shù)組中第一個文檔中第一項(xiàng)的tf-idf :

-

現(xiàn)在,如果我們對文檔中剩余的2個詞語重復(fù)此計(jì)算,我們將得到:

-

-

以及原始tf-idfs的向量:

-

然后,應(yīng)用歐幾里得(L2)范數(shù),我們?yōu)槲臋n1獲得以下tf-idfs:

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

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

-

并且L2歸一化的tf-idf變?yōu)?

>>> 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]])

通過調(diào)用fit方法計(jì)算的每個特征的權(quán)重并存儲在模型屬性中:

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

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

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

通常,調(diào)整特征選取參數(shù)的最佳方法是使用帶交叉驗(yàn)證的網(wǎng)格搜索,例如通過將特征提取器與分類器進(jìn)行流水線化:

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

6.2.3.5 解碼文本文件

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

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

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

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

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

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

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

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

>>> 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

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

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

6.2.3.6 應(yīng)用與實(shí)例

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

特別是在有監(jiān)督的環(huán)境中,它可以與快速且可擴(kuò)展的線性模型成功地組合以訓(xùn)練文檔分類器,例如:

無監(jiān)督的環(huán)境中,可以通過應(yīng)用諸如K-means之類的聚類算法將類似的文檔分組在一起:

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

6.2.3.7 詞袋表示法的局限性

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

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

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

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

>>> 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èi)的字符(每邊都用空格填充)創(chuàng)建n-gram。char分析儀還可以創(chuàng)建跨單詞的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對于使用空格進(jìn)行單詞分離的語言特別有用,因?yàn)樵谶@種情況下,它產(chǎn)生的噪聲特征比原始char變體少得多。對于這樣的語言,它可以增加使用這些特征訓(xùn)練的分類器的預(yù)測精度和收斂速度,同時保留關(guān)于拼寫錯誤和單詞派生的穩(wěn)健性。

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

為了處理自然語言理解的更廣泛的任務(wù),應(yīng)該考慮句子和段落的局部結(jié)構(gòu)。于是,很多這樣的模型將被視為目前不在 scikit-learn范圍內(nèi)的“結(jié)構(gòu)化輸出”問題。

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

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

  • 語料庫越大,詞匯量就越大,內(nèi)存使用也越多,
  • 擬合需要根據(jù)原始數(shù)據(jù)集的大小成比例分配中間數(shù)據(jù)結(jié)構(gòu)的大小.
  • 建立單詞映射需要對數(shù)據(jù)集進(jìn)行全面遍歷,因此不可能以嚴(yán)格的在線方式擬合文本分類器。
  • vocabulary_大的pickling 和 un-pickling的矢量化器程序可能非常慢(通常比pickling / un-pickling 例如相同大小NumPy的數(shù)組平面數(shù)據(jù)結(jié)構(gòu)慢得多)
  • 將矢量化任務(wù)分成多個并行子任務(wù)是不容易的,因?yàn)樵?code style="font-size: 14px; word-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin: 0 2px; color: #1e6bb8; background-color: rgba(27,31,35,.05); font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; word-break: break-all;">vocabulary_屬性必須是具有細(xì)粒度同步障礙的共享狀態(tài):從標(biāo)記字符串到特征索引的映射取決于每個標(biāo)記首次出現(xiàn)的順序,因此必須共享這些資源,這有可能損害并行子任務(wù)的性能,使其比順序變體慢。

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

這種組合是在HashingVectorizer類中實(shí)現(xiàn)的,一個與CountVectorizer大部分API兼容的轉(zhuǎn)換器類。 HashingVectorizer是無狀態(tài)的,這意味著您不必對它調(diào)用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個非零特征標(biāo)記:這比之前CountVectorizer在同一樣本語料庫中提取的19個非零特征標(biāo)記少 。差異來自由于n_features參數(shù)值較低引起的哈希函數(shù)沖突。

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

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

讓我們使用默認(rèn)設(shè)置再試一次:

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

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

HashingVectorizer還帶有以下限制:

  • 無法反轉(zhuǎn)模型(沒有 inverse_transform 方法),也不訪問特征的原始字符串表示形式,因?yàn)閳?zhí)行映射的哈希函數(shù)具有單向性。
  • 不提供IDF加權(quán),因?yàn)檫@會在模型中引入有狀態(tài)性。如果需要,可以在管道中為它附加一個TfidfTransformer

6.2.3.9 使用HashingVectorizer執(zhí)行核外縮放

使用HashingVectorizer的一個有用的進(jìn)步是執(zhí)行核外擴(kuò)展的能力。這意味著我們可以從不適合放入計(jì)算機(jī)主內(nèi)存的數(shù)據(jù)中學(xué)習(xí)。

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

有關(guān)文本分類任務(wù)中核外縮放的完整示例,請參見文本文檔的核外分類。

6.2.3.10 自定義矢量化器類

通過將一個callable傳遞給vectorizer構(gòu)造函數(shù),可以自定義行為:

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

特別是命名

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

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

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

一些提示和技巧:

  • 如果文檔是由外部程序包預(yù)先標(biāo)記的,則將它們存儲在文件(或字符串)中,并用空格分隔標(biāo)記并傳遞參數(shù) analyzer=str.split
  • scikit-learn代碼庫中不包含花式標(biāo)記級分析,例如詞干,詞組去除,復(fù)合分割,基于詞性的過濾等,但可以通過自定義標(biāo)簽生成器或分析器來添加。這是一個使用NLTK的標(biāo)記器和詞條 分解器的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())  

(請注意,這不會過濾掉標(biāo)點(diǎn)符號。)

例如,以下示例將某些英國拼寫轉(zhuǎn)換為美國拼寫:

>>> 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']

用于其他樣式的預(yù)處理;示例包括詞干,詞形化或規(guī)范化數(shù)字標(biāo)記,后者說明如下:

使用“光譜共聚類”算法對文檔進(jìn)行聚類

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

6.2.4 圖像特征提取

6.2.4.1 補(bǔ)丁提取

extract_patches_2d函數(shù)從存儲為二維數(shù)組或沿第三軸顯示顏色信息的三維數(shù)組的圖像中提取色塊。為了用所有修補(bǔ)程序重建圖像,請使用 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],
       [12, 15, 18, 21],
       [24, 27, 30, 33],
       [36, 39, 42, 45]])

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

       [[15, 18],
        [27, 30]]])
>>> patches = image.extract_patches_2d(one_image, (2, 2))
>>> patches.shape
(9, 2, 2, 3)
>>> patches[4, :, :, 0]
array([[15, 18],
       [27, 30]])

現(xiàn)在讓我們嘗試通過對重疊區(qū)域求平均來從補(bǔ)丁中重建原始圖像:

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

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

>>> 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 中的多個估計(jì)器可以使用特征或樣本之間的連接信息。例如Ward聚類(Hierarchical clustering)只能將圖像的相鄰像素聚類在一起,從而形成連續(xù)的補(bǔ)?。?/p>

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

這些矩陣可用于在使用連通性信息的估計(jì)器中強(qiáng)加連接,例如Ward聚類(層次聚類),而且還以構(gòu)建預(yù)計(jì)算的內(nèi)核或相似性矩陣。

示例:


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號