sklearn之特征提取(文本特征)


1、引言

關於文本的提取有很多方法,本文主要探索下sklearn官方的文本特征提取功能。

 

2、文本特征提取

文本分析是機器學習算法的主要應用領域。 然而,原始數據,符號文字序列不能直接傳遞給算法,因為它們大多數要求具有固定長度的數字矩陣特征向量,而不是具有可變長度的原始文本文檔。

sklearn提供三種方法:

令牌化, 對每個可能的詞令牌分成字符串並賦予整數形的id,例如通過使用空格和標點符號作為令牌分隔符。

統計,每個詞令牌在文檔中的出現次數。

標准化,在大多數的文檔 / 樣本中,可以減少重要的次令牌的出現次數的權重。

總的來說,方法是把文本文檔集合轉化成特征向量,比如每一行是一個文檔,每一列是詞id。

由於詞的稀疏性,通常使用scipy.sparse 包中的稀疏實現。

 

3、使用方法

類CountVectorizer介紹

首先類 CountVectorizer 在單個類中實現了 tokenization (詞語切分)和 occurrence counting (出現頻數統計):

>>> from sklearn.feature_extraction.text import 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)

#對每一列進行index
>>> vectorizer.get_feature_names() == (
...     ['and', 'document', 'first', 'is', 'one',
...      'second', 'the', 'third', 'this'])
True

>>> X.toarray()           
array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
       [0, 1, 0, 1, 0, 2, 1, 0, 1],
       [1, 0, 0, 0, 1, 0, 1, 1, 0],
       [0, 1, 1, 1, 0, 0, 1, 0, 1]]...)

#還可以設置一個或兩個詞來進行分割,設置兩個詞的原因是部分語句要連讀才有區分度
>>> bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
...                                     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

 

TF-idf項加權

在一個大的文本語料庫中,一些單詞將出現很多次(例如 “the”, “a”, “is” 是英文),因此對文檔的實際內容沒有什么有意義的信息。 如果我們將直接計數數據直接提供給分類器,那么這些頻繁詞組會掩蓋住那些我們關注但很少出現的詞。

為了重新計算特征權重,並將其轉化為適合分類器使用的浮點值,因此使用 tf-idf 變換是非常常見的。

Tf表示 術語頻率,而 tf-idf 表示術語頻率乘以轉制文檔頻率:

 \text{tf-idf(t,d)}=\text{tf(t,d)} \times \text{idf(t)}.

術語頻率,一個術語在給定文檔中出現的次數乘以 idf 組件, 計算為

\text{idf}(t) = log{\frac{1 + n_d}{1+\text{df}(d,t)}} + 1,

其中 n_d 是文檔的總數,\text{df}(d,t) 是包含術語 t 的文檔數。 然后,所得到的 tf-idf 向量通過歐幾里得范數歸一化:

v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 + v{_2}^2 + \dots + v{_n}^2}}.

它源於一個詞權重的信息檢索方式(作為搜索引擎結果的評級函數),同時也在文檔分類和聚類中表現良好。

以下部分包含進一步說明和示例,說明如何精確計算 tf-idfs 以及如何在 scikit-learn 中計算 tf-idfs, TfidfTransformer 並 TfidfVectorizer 與定義 idf 的標准教科書符號略有不同

\text{idf}(t) = log{\frac{n_d}{1+\text{df}(d,t)}}.

在 TfidfTransformer 和 TfidfVectorizer 中 smooth_idf=False,將 “1” 計數添加到 idf 而不是 idf 的分母:

\text{idf}(t) = log{\frac{n_d}{\text{df}(d,t)}} + 1

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

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

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

v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 + v{_2}^2 + \dots + v{_n}^2}}

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

n_{d, {\text{term1}}} = 6

\text{df}(d, t)_{\text{term1}} = 6

\text{idf}(d, t)_{\text{term1}} = log \frac{n_d}{\text{df}(d, t)} + 1 = log(1)+1 = 1

\text{tf-idf}_{\text{term1}} = \text{tf} \times \text{idf} = 3 \times 1 = 3

現在,如果我們對文檔中剩下的2個術語重復這個計算,我們得到:

\text{tf-idf}_{\text{term2}} = 0 \times log(6/1)+1 = 0

\text{tf-idf}_{\text{term3}} = 1 \times log(6/2)+1 \approx 2.0986

和原始 tf-idfs 的向量:

\text{tf-idf}_raw = [3, 0, 2.0986].

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

\frac{[3, 0, 2.0986]}{\sqrt{\big(3^2 + 0^2 + 2.0986^2\big)}} = [ 0.819,  0,  0.573].

 

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

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

雖然tf-idf標准化通常非常有用,但是可能有一種情況是二元變量顯示會提供更好的特征。 這可以使用類 CountVectorizer 的 二進制 參數來實現。 特別地,一些估計器,諸如 伯努利朴素貝葉斯 顯式的使用離散的布爾隨機變量。 而且,非常短的文本很可能影響 tf-idf 值,而二進制出現信息更穩定。

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

 

詞語表示的限制

直接看例子

>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(2, 2))
>>> 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([[1, 1, 1, 0, 1, 1, 1, 0],
       [1, 1, 0, 1, 1, 1, 0, 1]])


>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(5, 5))
>>> 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=(5, 5))
>>> 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 的范圍之內。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM