5.特徵提取

5.特徵提取

有不少特徵提取技術能夠應用到文本數據上,但在深刻學習以前,先思考特徵的意義。爲何須要這些特徵?它們又如何發揮做用?數據集中一般包含不少數據。通常狀況下,數據集的行和列是數據集的不一樣特徵或屬性,每行或者每一個觀測值都是特殊的值。在機器學習術語中,特徵是獨一無二的,是數據集中每一個觀測值或數據的可度量的屬性或性質。特徵一般具備數據的性質,多是絕對值或是列表中每一個分類進行二進制編碼的分類特徵,這一過程爲一位有效(one-hot)編碼過程。特徵的特區和選擇過程即便一門科學,也是一門藝術,這個過程也稱爲特徵提取或特徵工程。html

一般狀況下,爲獲取洞見,把提取到的特徵送入機器學習算法以學習能夠應用到新數據特徵上的模式。由於每一個算法的核心是數學上的優化操做,當算法從數據的觀測值上學習模式時,是一個最小化偏差和錯誤的過程,因此這些算法通常都指望特徵是數值向量的形式。所以,處理文本數據增長的挑戰就是如何轉換文本數據並從中提取數值特徵。python

如今,看一些與文本數據有關的特徵提取概念的技術。算法

向量空間模型 是處理文本數據很是有用概念和模型,並在信息索引與文檔排序中普遍使用。向量空間模型也稱爲詞向量模型,定義爲文本文檔轉換與表示的數學或代數模型,做爲造成向量維度的特定詞項的數字向量。數學上定義以下,假設在文檔向量空間 VS 中有一個文檔 D。每一個文檔維度和列數量將是向量空間中所有文檔中不一樣詞項或單詞的總數量。api

所以,向量空間能夠表示爲:網絡

VS = {W1, W2, ..., Wn}app

其中,n 是所有文檔中不一樣單詞的數量。如今,能夠吧文檔 D 在向量空間標示爲:機器學習

D = { wD1, wD2,..., wDn}分佈式

其中,wDn 表示文檔 D 中第 n 個詞的權重。這個權重是一個數量值,能夠表示任何事,但是文檔中單詞的頻率、平均的出現頻率,或者是 TF-IDF 權重。ide

下面見介紹和實現以下特徵提取技術:函數

  • 詞袋模型。
  • TF-IDF 模型。
  • 高級詞向量模型。

對於特徵提取,須要記住的一個關鍵問題是,一旦創建一個使用一些轉換和數學操做的特徵提取器,就須要確保重新文檔提取特徵時重用一樣的過程,不須要對新文檔從新創建整個算法。對於每項技術,都將使用一個例子進行說明。請注意,對於例子都將使用 nltk、genism 和 scikit-learn 等函數庫。

特徵提取的實現能夠分爲兩個塊。

feature_extractors.py  摺疊源碼
# -*- coding: utf-8 -*-
"""
Created on Sat Aug 27 04:03:12 2016
@author: DIP
"""
 
from  sklearn.feature_extraction.text  import  CountVectorizer
 
def  bow_extractor(corpus, ngram_range = ( 1 , 1 )):
     
     vectorizer  =  CountVectorizer(min_df = 1 , ngram_range = ngram_range)
     features  =  vectorizer.fit_transform(corpus)
     return  vectorizer, features
     
     
from  sklearn.feature_extraction.text  import  TfidfTransformer
 
def  tfidf_transformer(bow_matrix):
     
     transformer  =  TfidfTransformer(norm = 'l2' ,
                                    smooth_idf = True ,
                                    use_idf = True )
     tfidf_matrix  =  transformer.fit_transform(bow_matrix)
     return  transformer, tfidf_matrix
     
     
from  sklearn.feature_extraction.text  import  TfidfVectorizer
 
def  tfidf_extractor(corpus, ngram_range = ( 1 , 1 )):
     
     vectorizer  =  TfidfVectorizer(min_df = 1 ,
                                  norm = 'l2' ,
                                  smooth_idf = True ,
                                  use_idf = True ,
                                  ngram_range = ngram_range)
     features  =  vectorizer.fit_transform(corpus)
     return  vectorizer, features
     
 
import  numpy as np   
     
def  average_word_vectors(words, model, vocabulary, num_features):
     
     feature_vector  =  np.zeros((num_features,),dtype = "float64" )
     nwords  =  0.
     
     for  word  in  words:
         if  word  in  vocabulary:
             nwords  =  nwords  +  1.
             feature_vector  =  np.add(feature_vector, model[word])
     
     if  nwords:
         feature_vector  =  np.divide(feature_vector, nwords)
         
     return  feature_vector
     
    
def  averaged_word_vectorizer(corpus, model, num_features):
     vocabulary  =  set (model.index2word)
     features  =  [average_word_vectors(tokenized_sentence, model, vocabulary, num_features)
                     for  tokenized_sentence  in  corpus]
     return  np.array(features)
     
     
def  tfidf_wtd_avg_word_vectors(words, tfidf_vector, tfidf_vocabulary, model, num_features):
     
     word_tfidfs  =  [tfidf_vector[ 0 , tfidf_vocabulary.get(word)]
                    if  tfidf_vocabulary.get(word)
                    else  0  for  word  in  words]   
     word_tfidf_map  =  {word:tfidf_val  for  word, tfidf_val  in  zip (words, word_tfidfs)}
     
     feature_vector  =  np.zeros((num_features,),dtype = "float64" )
     vocabulary  =  set (model.index2word)
     wts  =  0.
     for  word  in  words:
         if  word  in  vocabulary:
             word_vector  =  model[word]
             weighted_word_vector  =  word_tfidf_map[word]  *  word_vector
             wts  =  wts  +  word_tfidf_map[word]
             feature_vector  =  np.add(feature_vector, weighted_word_vector)
     if  wts:
         feature_vector  =  np.divide(feature_vector, wts)
         
     return  feature_vector
     
def  tfidf_weighted_averaged_word_vectorizer(corpus, tfidf_vectors,
                                    tfidf_vocabulary, model, num_features):
                                        
     docs_tfidfs  =  [(doc, doc_tfidf)
                    for  doc, doc_tfidf
                    in  zip (corpus, tfidf_vectors)]
     features  =  [tfidf_wtd_avg_word_vectors(tokenized_sentence, tfidf, tfidf_vocabulary,
                                    model, num_features)
                     for  tokenized_sentence, tfidf  in  docs_tfidfs]
     return  np.array(features)

包括後面創建分類器時使用的通用函數。在

feature_extraction_demo.py  摺疊源碼
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 25 00:09:56 2016
@author: DIP
"""
 
CORPUS  =  [
'the sky is blue' ,
'sky is blue and sky is beautiful' ,
'the beautiful sky is so blue' ,
'i love blue cheese'
]
 
new_doc  =  [ 'loving this blue sky today' ]
 
import  pandas as pd
 
def  display_features(features, feature_names):
     df  =  pd.DataFrame(data = features,
                       columns = feature_names)
     print  df
 
 
from  feature_extractors  import  bow_extractor   
     
bow_vectorizer, bow_features  =  bow_extractor(CORPUS)
features  =  bow_features.todense()
print  features
 
new_doc_features  =  bow_vectorizer.transform(new_doc)
new_doc_features  =  new_doc_features.todense()
print  new_doc_features
 
feature_names  =  bow_vectorizer.get_feature_names()
print  feature_names
 
display_features(features, feature_names)
display_features(new_doc_features, feature_names)
 
 
import  numpy as np
from  feature_extractors  import  tfidf_transformer
feature_names  =  bow_vectorizer.get_feature_names()
     
tfidf_trans, tdidf_features  =  tfidf_transformer(bow_features)
features  =  np. round (tdidf_features.todense(),  2 )
display_features(features, feature_names)
 
nd_tfidf  =  tfidf_trans.transform(new_doc_features)
nd_features  =  np. round (nd_tfidf.todense(),  2 )
display_features(nd_features, feature_names)
 
 
 
import  scipy.sparse as sp
from  numpy.linalg  import  norm
feature_names  =  bow_vectorizer.get_feature_names()
 
# compute term frequency
tf  =  bow_features.todense()
tf  =  np.array(tf, dtype = 'float64' )
 
# show term frequencies
display_features(tf, feature_names)
 
# build the document frequency matrix
df  =  np.diff(sp.csc_matrix(bow_features, copy = True ).indptr)
df  =  1  +  df  # to smoothen idf later
 
# show document frequencies
display_features([df], feature_names)
 
# compute inverse document frequencies
total_docs  =  1  +  len (CORPUS)
idf  =  1.0  +  np.log( float (total_docs)  /  df)
 
# show inverse document frequencies
display_features([np. round (idf,  2 )], feature_names)
 
# compute idf diagonal matrix 
total_features  =  bow_features.shape[ 1 ]
idf_diag  =  sp.spdiags(idf, diags = 0 , m = total_features, n = total_features)
idf  =  idf_diag.todense()
 
# print the idf diagonal matrix
print  np. round (idf,  2 )
 
# compute tfidf feature matrix
tfidf  =  tf  *  idf
 
# show tfidf feature matrix
display_features(np. round (tfidf,  2 ), feature_names)
 
# compute L2 norms
norms  =  norm(tfidf, axis = 1 )
 
# print norms for each document
print  np. round (norms,  2 )
 
# compute normalized tfidf
norm_tfidf  =  tfidf  /  norms[:,  None ]
 
# show final tfidf feature matrix
display_features(np. round (norm_tfidf,  2 ), feature_names)
  
 
# compute new doc term freqs from bow freqs
nd_tf  =  new_doc_features
nd_tf  =  np.array(nd_tf, dtype = 'float64' )
 
# compute tfidf using idf matrix from train corpus
nd_tfidf  =  nd_tf * idf
nd_norms  =  norm(nd_tfidf, axis = 1 )
norm_nd_tfidf  =  nd_tfidf  /  nd_norms[:,  None ]
 
# show new_doc tfidf feature vector
display_features(np. round (norm_nd_tfidf,  2 ), feature_names)
 
 
from  feature_extractors  import  tfidf_extractor
     
tfidf_vectorizer, tdidf_features  =  tfidf_extractor(CORPUS)
display_features(np. round (tdidf_features.todense(),  2 ), feature_names)
 
nd_tfidf  =  tfidf_vectorizer.transform(new_doc)
display_features(np. round (nd_tfidf.todense(),  2 ), feature_names)   
 
 
import  gensim
import  nltk
 
TOKENIZED_CORPUS  =  [nltk.word_tokenize(sentence)
                     for  sentence  in  CORPUS]
tokenized_new_doc  =  [nltk.word_tokenize(sentence)
                     for  sentence  in  new_doc]                       
 
model  =  gensim.models.Word2Vec(TOKENIZED_CORPUS,
                                size = 10 ,
                                window = 10 ,
                                min_count = 2 ,
                                sample = 1e - 3 )
 
 
from  feature_extractors  import  averaged_word_vectorizer
 
 
avg_word_vec_features  =  averaged_word_vectorizer(corpus = TOKENIZED_CORPUS,
                                                  model = model,
                                                  num_features = 10 )
print  np. round (avg_word_vec_features,  3 )
 
nd_avg_word_vec_features  =  averaged_word_vectorizer(corpus = tokenized_new_doc,
                                                     model = model,
                                                     num_features = 10 )
print  np. round (nd_avg_word_vec_features,  3 )
 
               
from  feature_extractors  import  tfidf_weighted_averaged_word_vectorizer
 
corpus_tfidf  =  tdidf_features
vocab  =  tfidf_vectorizer.vocabulary_
wt_tfidf_word_vec_features  =  tfidf_weighted_averaged_word_vectorizer(corpus = TOKENIZED_CORPUS,
                                                                      tfidf_vectors = corpus_tfidf,
                                                                      tfidf_vocabulary = vocab,
                                                                      model = model,
                                                                      num_features = 10 )
print  np. round (wt_tfidf_word_vec_features,  3 )
 
nd_wt_tfidf_word_vec_features  =  tfidf_weighted_averaged_word_vectorizer(corpus = tokenized_new_doc,
                                                                      tfidf_vectors = nd_tfidf,
                                                                      tfidf_vocabulary = vocab,
                                                                      model = model,
                                                                      num_features = 10 )
print  np. round (nd_wt_tfidf_word_vec_features,  3 )

中經過一些實際的雷子使用一樣的函數說明每項技術如何工做。將使用 CORPUS 變量中描述的如下文檔提取特徵,並創建一些向量化模型。爲說明如何重新文檔中提取特徵(做爲測試數據集的一部分),將在下面的代碼段中使用 new_doc 變量中獨立的文檔。

CORPUS  =  [
'the sky is blue' ,
'sky is blue and sky is beautiful' ,
'the beautiful sky is so blue' ,
'i love blue cheese'
]
 
new_doc  =  [ 'loving this blue sky today' ]

詞袋模型

詞袋模型也許是從文本文檔中提取特徵最簡單但又最有效的技術。這個模型的本質是將文本文檔轉化成向量,從而將每一個文檔轉化成一個向量,這個向量表示在文檔空間中所有不一樣的單詞在該文檔中出現的機率。所以,根據前面的數學定義,這裏的例子向量記爲 D,每一個單詞的權重和該詞在文檔中出現的頻率相等。

有意思的事情是能夠爲單個單詞出現頻率和 n 元分詞出現頻率創建一樣的模型,該模型就是 n 元分詞詞袋模型,它計算不一樣的 n 元分詞在每一個文檔中的出現頻率。

下面的代碼片斷給出了一個函數,實現了基於詞袋的特徵提取模塊,該模塊也接受 ngram_range 參數做爲 n 元分詞的特徵。

from  sklearn.feature_extraction.text  import  CountVectorizer
 
def  bow_extractor(corpus, ngram_range = ( 1 , 1 )):
     
     vectorizer  =  CountVectorizer(min_df = 1 , ngram_range = ngram_range)
     features  =  vectorizer.fit_transform(corpus)
     return  vectorizer, features

上面的函數使用 CountVectorizer 類,能夠在 http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html 地址訪問詳細的 API(應用程序接口)文檔,根據你要提取的特徵類型,該函數有一系列不一樣參數用於調優。咱們使用默認的配置,這對大多數場景都是足夠的,其中 min_df 設置爲 1 表示的整個文檔空間中最小頻率爲 1 的詞項都會被考慮。能夠設置 ngram_range 爲不一樣的參數值,如(1,3),將創建包括全部 unigram、bigram 和 trigram 的向量空間。下面的代碼片斷顯示函數在樣本餘料,即 4 個訓練文檔和 1 個測試文檔的執行狀況。

In [ 57 ]: bow_vectorizer, bow_features  =  bow_extractor(CORPUS)
 
In [ 58 ]: features  =  bow_features.todense()
 
In [ 59 ]:  print (features)
[[ 0  0  1  0  1  0  1  0  1 ]
  [ 1  1  1  0  2  0  2  0  0 ]
  [ 0  1  1  0  1  0  1  1  1 ]
  [ 0  0  1  1  0  1  0  0  0 ]]
In [ 62 ]: new_doc_features  =  bow_vectorizer.transform(new_doc)
 
In [ 63 ]: new_doc_features  =  new_doc_features.todense()
 
In [ 64 ]:  print (new_doc_features)
[[ 0  0  1  0  0  0  1  0  0 ]]
In [ 65 ]: feature_names  =  bow_vectorizer.get_feature_names()
 
In [ 66 ]:  print (feature_names)
[ 'and' 'beautiful' 'blue' 'cheese' 'is' 'love' 'sky' 'so' 'the' ]

上述輸出顯示每一個文檔如何轉換爲向量。每行表明語料庫中的一個文檔,咱們對兩個語料庫均執行相同操做。使用 CORPUS 變量中的文檔創建了向量生成器。用它來提取特徵,使用這個創建的變量生成器從全新的文檔中提取特徵。向量的每一列描述的單詞在 feature_names 變量中描述,每列的值是改詞在文檔中的頻率。第一次看到它時可能難於理解,所以準備了下面的函數,它有助於更好地理解特徵向量。

import  pandas as pd
 
def  display_features(features, feature_names):
     df  =  pd.DataFrame(data = features,
                       columns = feature_names)
     print (df)

如今能夠將特徵名字和向量送入這個函數,以一種比較容易理解的結構查看特徵矩陣,以下所示:

In [ 71 ]: display_features(features, feature_names)
    and   beautiful  blue  cheese   is   love  sky  so  the
0     0           0      1        0    1      0     1    0     1
1     1           1      1        0    2      0     2    0     0
2     0           1      1        0    1      0     1    1     1
3     0           0      1        1    0      1     0    0     0
  
In [ 72 ]: display_features(new_doc_features, feature_names)
    and   beautiful  blue  cheese   is   love  sky  so  the
0     0           0      1        0    0      0     1    0     0

這使得事情變得更加清楚。考慮一下 CORPUS 的第二個文檔,在上面的第一個表的第 1 行中表示。能夠看到,'sky is blue and sky is beautiful' 這句話,特徵 sky 的值爲 2,beautiful 值爲 1 ,等等。文檔中未出現的單詞的值爲 0 。請注意,對於新的文檔變量 new_doc,這句話中沒有 today、this 或 loving 這些單詞,所以沒有這些詞的特徵。前面提到過這個緣由,就是特徵提取過程、模型和單詞老是基於訓練數據,將不隨着新聞的變化或受其影響,這將用於後面的測試或其餘語料的預測。獲取已經猜到這是由於一個模型老是基於訓練數據進行 訓練,除非從新創建模型,不然模型不會受到新文檔的影響。所以這個模型的特徵老是受限於訓練語料的文檔向量空間。

已經瞭解瞭如何從文本數據中提取基於向量的有意義的特徵,在從前看來這是不可能的。試着使用上面的函數,把 ngram_range 參數設置爲(1,3),觀察輸出結果。

In [ 87 ]: bow_vectorizer, bow_features  =  bow_extractor(CORPUS, ( 1 , 3 ))
 
In [ 88 ]: features  =  bow_features.todense()
 
In [ 89 ]: new_doc_features  =  bow_vectorizer.transform(new_doc)
 
In [ 90 ]: new_doc_features  =  new_doc_features.todense()
 
In [ 91 ]: feature_names  =  bow_vectorizer.get_feature_names()
 
In [ 92 ]: display_features(features, feature_names)
     and   and  sky   and  sky  is   beautiful  beautiful sky  beautiful sky  is      ...      so blue  the  the beautiful  the beautiful sky  the sky  the sky  is
0     0         0            0           0               0                  0      ...             0     1               0                   0         1            1
1     1         1            1           1               0                  0      ...             0     0               0                   0         0            0
2     0         0            0           1               1                  1      ...             1     1               1                   1         0            0
3     0         0            0           0               0                  0      ...             0     0               0                   0         0            0
 
[ 4  rows x  32  columns]
 
 
In [ 93 ]: display_features(new_doc_features, feature_names)
    and   and  sky   and  sky  is   beautiful  beautiful sky  beautiful sky  is      ...      so blue  the  the beautiful  the beautiful sky  the sky  the sky  is
0     0         0            0           0               0                  0      ...             0     0               0                   0         0            0
 
[ 1  rows x  32  columns]

IT-IDF 模型

詞袋模型還不錯,但向量徹底依賴於單詞出現的絕對頻率。這存在一些潛在的問題,語料庫所有文檔中出現次數較多的單詞將會擁有較高的頻率,這些詞將會影響其餘一些出現不如這些詞頻繁但對於文檔分類更有意義和有效的單詞。這就是 TF-IDF 的來源。TF-IDF 表明的是詞頻,逆文檔頻率,是兩個度量的組合:詞頻和逆文檔頻率。該技術最初做爲顯示搜索引擎用戶查詢結果排序函數的一個度量,如今已經成爲信息檢索和文本特徵提取的一部分。

如今正是定義 TF-IDF,開始實現以前,看一下它的數學表示。數學上,TF-IDF 是兩個度量的乘積,能夠表示爲 tƒidƒ  × idƒ , 其中詞頻()和逆文檔頻率(idƒ)是兩個度量。

詞頻有  表示,由詞袋模型計算得出。任何文檔的詞頻是該詞在特定文檔出現的原始頻率值。數學上,詞頻能夠表示爲 tf(ω, D) = ƒ ωD,其中 ƒωD 表示單詞 ω 在文檔 D 中的頻率,這就是詞頻()。有一些其餘的詞頻表示沒有出現過。有時,也能夠經過對數運算或頻率平均值將原始頻率標準化。將在具體實現中使用原始頻率。

逆文檔頻率由 idƒ 表示,是每一個單詞的文檔頻率的逆。該值由語料庫中所有文檔數量除以每一個單詞的文檔頻率,而後對結果應用對數運算變換其比例。在這裏的實現中,將對每一個單詞的文檔頻率加 1,意味着詞彙表中每一個單詞至少包含在一個語料庫文檔中。這是爲了不爲 0 除的錯誤,平滑逆文檔頻率。也對 idƒ 的計算結果加 1,避免被忽略單詞擁有 0 值的 idƒ。數學上,idƒ 實現表示以下:

其中,idƒ(t) 表示單詞 t 的 idƒ, C 表示語料庫中文檔的總數量,dƒ(t) 表示 包含單詞 t 的文檔數量頻率。

所以,詞頻-逆文檔頻率能夠經過把兩個度量乘在一塊兒來計算。最終將要使用的 TF-IDF 度量是 tƒidƒ 矩陣的歸一化版本,矩陣是 tƒ 和 idƒ 的乘積。將 tƒidƒ 矩陣除以矩陣的 L2 範數來進行矩陣歸一化,L2 範數也稱爲歐幾里得範數,它是每一個單詞 tƒidƒ 權重平方和的平方根。數學上,將最終的 tƒidƒ 特徵向量表示爲 :

,

其中|| tƒidƒ || 表示 tƒidƒ 矩陣的歐幾里得 L2 範數。

下面的代碼片斷是考慮已經有了前面的詞袋特徵向量的狀況下,得到基於 tƒidƒ 的特徵向量的具體實現:

from  sklearn.feature_extraction.text  import  TfidfTransformer
 
def  tfidf_transformer(bow_matrix):
     
     transformer  =  TfidfTransformer(norm = 'l2' ,
                                    smooth_idf = True ,
                                    use_idf = True )
     tfidf_matrix  =  transformer.fit_transform(bow_matrix)
     return  transformer, tfidf_matrix

能夠看到,在參數中使用了 L2 範數選項,而且對一些單詞可能存在 idƒ 爲 0 的狀況以增長權重的方式對 idƒ 進行平滑處理,而沒有忽略它們。下面的代碼片斷觀察這個函數的執行狀況:

import  numpy as np
from  feature_extractors  import  tfidf_transformer
feature_names  =  bow_vectorizer.get_feature_names()
In [ 100 ]: tfidf_trans, tdidf_features  =  tfidf_transformer(bow_features)
 
In [ 101 ]: features  =  np. round (tdidf_features.todense(),  2 )
 
In [ 102 ]: display_features(features, feature_names)
     and   beautiful  blue  cheese     is   love   sky    so   the
0   0.00        0.00   0.40     0.00   0.49   0.00   0.49   0.00   0.60
1   0.44        0.35   0.23     0.00   0.56   0.00   0.56   0.00   0.00
2   0.00        0.43   0.29     0.00   0.35   0.00   0.35   0.55   0.43
3   0.00        0.00   0.35     0.66   0.00   0.66   0.00   0.00   0.00
In [ 103 ]: nd_tfidf  =  tfidf_trans.transform(new_doc_features)
 
In [ 104 ]: nd_features  =  np. round (nd_tfidf.todense(),  2 )
 
In [ 105 ]: display_features(nd_features, feature_names)
    and   beautiful  blue  cheese    is   love   sky   so  the
0   0.0         0.0   0.63      0.0   0.0    0.0   0.77   0.0   0.0

上面的輸出顯示了所有例子文檔的 tƒidƒ 特徵向量。使用 TfidfTransformer 類有助有計算每一個文檔基於前面方程描述的 tƒidƒ 值。

如今,將看看該類內部如何工做。也會看到如何實現前面描述的數學方程以計算基於 tƒidƒ 的特徵向量。將載入必要的依賴,並經過重用詞袋模型特徵計算樣例語料的單詞頻率(TF),該詞頻也能夠做爲訓練語料 CORPUS 的詞頻。

import  scipy.sparse as sp
from  numpy.linalg  import  norm
feature_names  =  bow_vectorizer.get_feature_names()
In [ 113 ]: tf  =  bow_features.todense()
 
In [ 114 ]: tf  =  np.array(tf, dtype = 'float64' )
 
In [ 115 ]: display_features(tf, feature_names)
    and   beautiful  blue  cheese    is   love  sky   so  the
0   0.0         0.0    1.0      0.0   1.0    0.0   1.0   0.0   1.0
1   1.0         1.0    1.0      0.0   2.0    0.0   2.0   0.0   0.0
2   0.0         1.0    1.0      0.0   1.0    0.0   1.0   1.0   1.0
3   0.0         0.0    1.0      1.0   0.0    1.0   0.0   0.0   0.0

將基於出現某單詞的文檔數量計算每一個單詞的文檔頻率(DF)。下面的代碼片斷顯示如何從詞袋模型特徵矩陣得到 DF。

In [ 120 ]: df  =  np.diff(sp.csc_matrix(bow_features, copy = True ).indptr)
 
In [ 121 ]: df  =  1  +  df
 
In [ 122 ]: display_features([df], feature_names)
    and   beautiful  blue  cheese   is   love  sky  so  the
0     2           3      5        2    4      2     4    2     3

上述輸出向咱們展現了每一個單詞的文檔頻率(DF),可使用 CORPUS 中的文檔來驗證它。假設有一個文檔,其中全部單詞都出現一次,請記住,已經對每一個詞頻增長 1  以平滑 idƒ 值,避免被 0 除的錯誤。所以,若是驗證 CORPUS,將會看到 blue 出現 4 (+1)次,sky 出現 3(+1)次,考慮到使用了(+1)來進行平滑。

如今擁有了文檔頻率,就可使用前面的公式計算逆文檔頻率(idƒ)。記住,對語料庫中文檔的總數加 1,由於早先假設平滑 idƒ 包含全部單詞至少 1 次。

In [ 123 ]: total_docs  =  1  +  len (CORPUS)
 
In [ 124 ]: idf  =  1.0  +  np.log( float (total_docs)  /  df)
 
In [ 125 ]: display_features([np. round (idf,  2 )], feature_names)
     and   beautiful  blue  cheese     is   love   sky    so   the
0   1.92        1.51    1.0     1.92   1.22   1.92   1.22   1.92   1.51
In [ 132 ]: total_features  =  bow_features.shape[ 1 ]
 
In [ 133 ]: idf_diag  =  sp.spdiags(idf, diags = 0 , m = total_features, n = total_features)
 
In [ 134 ]: idf  =  idf_diag.todense()
 
In [ 135 ]:  print (np. round (idf,  2 ))
[[ 1.92  0.    0.    0.    0.    0.    0.    0.    0.   ]
  [ 0.    1.51  0.    0.    0.    0.    0.    0.    0.   ]
  [ 0.    0.    1.    0.    0.    0.    0.    0.    0.   ]
  [ 0.    0.    0.    1.92  0.    0.    0.    0.    0.   ]
  [ 0.    0.    0.    0.    1.22  0.    0.    0.    0.   ]
  [ 0.    0.    0.    0.    0.    1.92  0.    0.    0.   ]
  [ 0.    0.    0.    0.    0.    0.    1.22  0.    0.   ]
  [ 0.    0.    0.    0.    0.    0.    0.    1.92  0.   ]
  [ 0.    0.    0.    0.    0.    0.    0.    0.    1.51 ]]

如今看到了 idƒ 矩陣,這個矩陣是基於數學公式計算獲得的,把它轉換爲對角矩陣。當計算詞頻乘積時,這將很是有用。

既然有了 tƒ 和 idƒ,就可使用急診乘積計算 tƒidƒ 特徵矩陣了,如如下代碼所示:

In [ 136 ]: tfidf  =  tf  *  idf
 
In [ 137 ]: display_features(np. round (tfidf,  2 ), feature_names)
     and   beautiful  blue  cheese     is   love   sky    so   the
0   0.00        0.00    1.0     0.00   1.22   0.00   1.22   0.00   1.51
1   1.92        1.51    1.0     0.00   2.45   0.00   2.45   0.00   0.00
2   0.00        1.51    1.0     0.00   1.22   0.00   1.22   1.92   1.51
3   0.00        0.00    1.0     1.92   0.00   1.92   0.00   0.00   0.00

如今已經獲得了 tƒidƒ 特徵矩陣,可是要等一等,如今尚未結束。若是你還記得前面描述的方程,就會知道還須要把它除以 L2 範數。下面的代碼片斷計算每一個文檔的 tƒidƒ 範數,使用這個範數除以 tƒidƒ 權重獲得最終想要的 tƒidƒ 矩陣:

In [ 138 ]: norms  =  norm(tfidf, axis = 1 )
 
In [ 139 ]:  print (np. round (norms,  2 ))
[ 2.5   4.35  3.5   2.89 ]
 
In [ 140 ]: norm_tfidf  =  tfidf  /  norms[:,  None ]
 
In [ 141 ]: display_features(np. round (norm_tfidf,  2 ), feature_names)
     and   beautiful  blue  cheese     is   love   sky    so   the
0   0.00        0.00   0.40     0.00   0.49   0.00   0.49   0.00   0.60
1   0.44        0.35   0.23     0.00   0.56   0.00   0.56   0.00   0.00
2   0.00        0.43   0.29     0.00   0.35   0.00   0.35   0.55   0.43
3   0.00        0.00   0.35     0.66   0.00   0.66   0.00   0.00   0.00

比較上面獲得的 CORPUS 矩陣中文檔的 tƒidƒ 特徵矩陣和前面使用 TfidfTransformer 獲得的特徵矩陣。注意,它們是相同的,所以驗證了咱們的數學實現時正確的,事實上, scikit-learn 的 TfidfTransformer 採用一樣的數學實現,在後臺進行了一些優化。如今,假設咱們想計算新文檔 new_doc 基於 tƒidƒ 的特徵矩陣,可使用下面的代碼片斷計算它。

在計算詞頻前,複用 new_doc_features 詞袋向量:

In [ 197 ]:  # compute new doc term freqs from bow freqs
 
In [ 198 ]: nd_tf  =  new_doc_features
 
In [ 199 ]: nd_tf  =  np.array(nd_tf, dtype = 'float64' )
 
In [ 200 ]:  # compute tfidf using idf matrix from train corpus
 
In [ 201 ]: nd_tfidf  =  nd_tf * idf
 
In [ 202 ]: nd_norms  =  norm(nd_tfidf, axis = 1 )
 
In [ 203 ]: norm_nd_tfidf  =  nd_tfidf  /  nd_norms[:,  None ]
 
In [ 204 ]:  # show new_doc tfidf feature vector
 
In [ 205 ]: display_features(np. round (norm_nd_tfidf,  2 ), feature_names)
    and   beautiful  blue  cheese    is   love   sky   so  the
0   0.0         0.0   0.63      0.0   0.0    0.0   0.77   0.0   0.0

上面的輸出描述了 new_doc 基於 tƒidƒ 的特徵向量,能夠看到它與經過 TfidfTransformer 計算獲得的相同。

掌握了  tƒidƒ 計算內部的工做原理以後,將實現一個通用的函數,能夠直接從原始文檔中計算文檔基於  tƒidƒ 的特徵向量。下面的代碼描述了相同的過程。

from  sklearn.feature_extraction.text  import  TfidfVectorizer
 
def  tfidf_extractor(corpus, ngram_range = ( 1 , 1 )):
     vectorizer  =  TfidfVectorizer(min_df = 1 ,
                                  norm = 'l2' ,
                                  smooth_idf = True ,
                                  use_idf = True ,
                                  ngram_range = ngram_range)
     features  =  vectorizer.fit_transform(corpus)
     return  vectorizer, features

上面的函數直接使用了 TfidfVectorizer,經過把原始文檔做爲輸入,在內部計算詞頻和逆文檔頻率,直接計算 tƒidƒ 向量,避免了使用 CountVectorizer 計算基於詞袋模型的詞頻。它也支持將 n 元分詞加入特徵向量中。能夠在下面的片斷中看到函數的執行狀況:

In [ 212 ]: tfidf_vectorizer, tdidf_features  =  tfidf_extractor(CORPUS)
 
In [ 213 ]: display_features(np. round (tdidf_features.todense(),  2 ), feature_names)
     and   beautiful  blue  cheese     is   love   sky    so   the
0   0.00        0.00   0.40     0.00   0.49   0.00   0.49   0.00   0.60
1   0.44        0.35   0.23     0.00   0.56   0.00   0.56   0.00   0.00
2   0.00        0.43   0.29     0.00   0.35   0.00   0.35   0.55   0.43
3   0.00        0.00   0.35     0.66   0.00   0.66   0.00   0.00   0.00
In [ 217 ]: nd_tfidf  =  tfidf_vectorizer.transform(new_doc)
 
In [ 218 ]: display_features(np. round (nd_tfidf.todense(),  2 ), feature_names)
    and   beautiful  blue  cheese    is   love   sky   so  the
0   0.0         0.0   0.63      0.0   0.0    0.0   0.77   0.0   0.0

從上面的輸出能夠看到  tƒidƒ 特徵向量與前面獲得的一致。到此要結束使用  tƒidƒ 進行特徵提取的討論了。

高級詞向量模型

爲從文檔中提取特徵,有各類各樣的方法建立更高級的詞向量模型。這裏將討論一些詞向量模型,它們使用了主流的谷歌 word2vec 算法。word2vec 模型由谷歌公司 2013 年發佈,它是一個基於神經網絡的實現,使用 CBOW (Continuous Bag of Words) 和 skip-gram 兩種結構學習單詞的分佈式向量表示。word2vec 模型對於其餘神經網絡實現運行速度更快,並且不須要手工標籤來建立單詞的意義標識。

在代碼實現中,將使用 gensim 庫,該庫是 word2vec 的 Python 實現,提供了幾個高級的接口使得建模很是容易。基本思想是提供一些文檔語料做爲輸入,會獲得詞向量特徵做爲輸出。在模型內部,簡歷基於輸入文檔的詞彙表,經過前面提到的各類技術學習單詞的向量表示,一旦學習完成,就創建了一個可用於從文檔中提取單詞的向量的模型。使用如平均值和 tƒidƒ 加權等方法,可使用詞向量計算文檔的平均向量。能夠在 http://radimrehurek.com/gensim/models/word2vec.html 上得到有關 gensim 庫接口更詳細的信息。

在訓練語料簡歷模型時,將主要關注下面的參數。

  • size:該參數用於設定詞向量的緯度,能夠是幾十到幾千。能夠嘗試不一樣的緯度,已得到最好的效果。
  • window:該參數用於設定語境或窗口尺寸,指定了訓練時對算法來講可算作上下文的單詞窗口長度。
  • min_count:該參數指定單詞表中單詞在預料中出現的最小次數。這個參數有助於去除一些文檔中出現次數較少的不重要的單詞。
  • sample:該參數用於對單詞出現的頻率進行下采樣,其理想值在 0.01 到 0.0001 之間。

創建模型以後,將基於一些加權策略來定義和實現兩種詞向量與文檔結合的技術。接下來將實現下面兩個技術:

  • 平均詞向量。
  • TF-IDF 加權詞向量。

再進一步實現以前,使用訓練語料簡歷 word2vec 模型,開始特徵提取的過程。下面代碼顯示瞭如何實現:

import  gensim
import  nltk
 
TOKENIZED_CORPUS  =  [nltk.word_tokenize(sentence)
                     for  sentence  in  CORPUS]
tokenized_new_doc  =  [nltk.word_tokenize(sentence)
                     for  sentence  in  new_doc]                       
 
model  =  gensim.models.Word2Vec(TOKENIZED_CORPUS,
                                size = 10 ,
                                window = 10 ,
                                min_count = 2 ,
                                sample = 1e - 3 )

如你所見,使用前面描述的參數創建了模型。能夠嘗試調整這些參數,也能夠查看文檔其餘參數,以改變結構類型、worker 數量等,訓練不一樣的模型。如今已經有了本身的模型,能夠開始實現特徵提取技術:

平均詞向量

上面的模型爲單詞表中每一個單詞建立一個向量表示,能夠輸入下面的代碼來查看它們。

In [ 5 ]:  print (model[ 'sky' ])
/ usr / local / bin / ipython: 1 : DeprecationWarning: Call to deprecated `__getitem__` (Method will be removed  in  4.0 . 0 , use  self .wv.__getitem__() instead).
   #!/usr/local/bin/python
0.0146156   - 0.03722425   0.02307606   0.03794555   0.04357427   0.02248405
  - 0.04318777  - 0.0192292   - 0.00593164   0.01981338 ]
 
In [ 6 ]:  print (model[ 'blue' ])
/ usr / local / bin / ipython: 1 : DeprecationWarning: Call to deprecated `__getitem__` (Method will be removed  in  4.0 . 0 , use  self .wv.__getitem__() instead).
   #!/usr/local/bin/python
[ - 0.04851234  - 0.03277206   0.02654879   0.04666627  - 0.0460027    0.01473446
  - 0.01312309   0.02642985   0.0062849   - 0.04868636 ]

基於前面指定的參數尺寸,每一個單詞向量的長度爲10。可是當咱們處理語句和文檔時,必須執行一些合併和聚合操做,以確保不管文本長度、單詞數量等狀況如何,最後特徵的維度是相同的。這個技術中,使用平均加權詞向量,對於每一個文檔將提取文檔中全部單詞,得到單詞表中每一個單詞的詞向量。將所有詞向量加在一塊兒,除以單詞表中匹配單詞的總數,最後獲得文檔的平均詞向量表示結果。上述描述可使用如下數學公式表示:

其中 AVW(D) 表示文檔 D 的平均詞向量,文檔 D 中包括單詞 ω1,ω2,..., ωn, ωv(ω) 表示的是單詞 ω 的詞向量。

該算法的爲代碼描述以下:

model : =  the word2vec model we built
vocabulary : =  unique_words(model)
document : =  [words]
matched_word_count : =  0
vector : =  []
 
 
for  word  in  words:
   if  word  in  vocabulary:
     vector : =  vector  +  model[word]
     matched_word_count : =  matched_word_count  + 1
averaged_word_vector : =  vector  /  matched_word_count

這個僞代碼以一種較好的、易於累計額的方式顯示了操做流程。如今,要使用下面的代碼在 Python 中實現這個算法。

import  numpy as np   
     
def  average_word_vectors(words, model, vocabulary, num_features):
     feature_vector  =  np.zeros((num_features,),dtype = "float64" )
     nwords  =  0.
     for  word  in  words:
         if  word  in  vocabulary:
             nwords  =  nwords  +  1.
             feature_vector  =  np.add(feature_vector, model[word])
     
     if  nwords:
         feature_vector  =  np.divide(feature_vector, nwords)
     return  feature_vector
     
    
def  averaged_word_vectorizer(corpus, model, num_features):
     vocabulary  =  set (model.index2word)
     features  =  [average_word_vectors(tokenized_sentence, model, vocabulary, num_features)
                     for  tokenized_sentence  in  corpus]
     return  np.array(features)

你必定對 average_word_vectors() 函數很是熟悉,它是前面使用僞代碼表示的算法的完整試下。也建立了一個通用的函數 averaged_word_vectorizer() 來實現語料庫中多個文檔平均詞向量的計算。下面代碼展現了咱們這個函數在示例語料庫上的執行狀況。

 

from  feature_extractors  import  averaged_word_vectorizer
 
avg_word_vec_features  =  averaged_word_vectorizer(corpus = TOKENIZED_CORPUS,
                                                  model = model,
                                                  num_features = 10 )
In [ 33 ]:  print (np. round (avg_word_vec_features,  3 ))
[[  0.005  - 0.034   0.011   0.024   0.012   0.009  - 0.034   0.007  - 0.016  - 0.012 ]
  0.009  - 0.033   0.023   0.011   0.012   0.02   - 0.028   0.002  - 0.021  - 0.015 ]
  0.012  - 0.034   0.017   0.022   0.004   0.001  - 0.029   0.008  - 0.022  - 0.011 ]
  [ - 0.049  - 0.033   0.027   0.047  - 0.046   0.015  - 0.013   0.026   0.006  - 0.049 ]]

警告

若是出現以下錯誤:

...
AttributeError:  'Word2Vec'  object  has no attribute  'index2word'

則經過下述方法解決,目前暫不知緣由:

# 導出
model_name  =  "300features_40minwords_10context"
model.save(model_name)
# 導入
model  =  Word2Vec.load( "300features_40minwords_10context" )

修改代碼:

def  averaged_word_vectorizer(corpus, model, num_features):
     vocabulary  =  set (model.wv.index2word)
     features  =  [average_word_vectors(tokenized_sentence, model, vocabulary, num_features)
                     for  tokenized_sentence  in  corpus]
     return  np.array(features)

繼續執行上述代碼。

nd_avg_word_vec_features  =  averaged_word_vectorizer(corpus = tokenized_new_doc,
                                                      model = model,
                                                      num_features = 10 )
In [ 38 ]:  print (nd_avg_word_vec_features)
[[ - 0.01694837  - 0.03499815   0.02481242   0.04230591  - 0.00121422   0.01860925
   - 0.02815543   0.00360032   0.00017663  - 0.01443649 ]]

TF-IDF 加權平均詞向量

前面的向量生成器基於模型單詞表中的單詞,簡單地對任何文檔中相關的詞向量進行求和,經過除以匹配的單詞的數量計算一個簡單的平均值。下面使用單詞的 TF-IDF 評分對每一個匹配的詞向量進行加權,對它們進行求和並處以文檔中匹配的單詞數量。將獲得每一個文檔的一個 TF-IDF 加權平均詞向量。

上述描述可使用下面的數學公式表示:

其中 TWA(D) 表示 TF-IDF 文檔 D 加權平均詞向量,文檔 D 中包括單詞 w1, w2,..., wn, wv(w) 表示的是單詞 w 的詞向量 tƒidƒ(w) 是單詞 w 的 TF-IDF 權重。下面的代碼段在 Python 中實現了這個算法,所以可使用它提取特徵。

def  tfidf_wtd_avg_word_vectors(words, tfidf_vector, tfidf_vocabulary, model, num_features):
     word_tfidfs  =  [tfidf_vector[ 0 , tfidf_vocabulary.get(word)]
                    if  tfidf_vocabulary.get(word)
                    else  0  for  word  in  words]   
     word_tfidf_map  =  {word:tfidf_val  for  word, tfidf_val  in  zip (words, word_tfidfs)}
     feature_vector  =  np.zeros((num_features,),dtype = "float64" )
     vocabulary  =  set (model.index2word)
     wts  =  0.
     for  word  in  words:
         if  word  in  vocabulary:
             word_vector  =  model[word]
             weighted_word_vector  =  word_tfidf_map[word]  *  word_vector
             wts  =  wts  +  word_tfidf_map[word]
             feature_vector  =  np.add(feature_vector, weighted_word_vector)
     if  wts:
         feature_vector  =  np.divide(feature_vector, wts)
     return  feature_vector
     
def  tfidf_weighted_averaged_word_vectorizer(corpus, tfidf_vectors, vocab
                                    tfidf_vocabulary, model, num_features):    
     docs_tfidfs  =  [(doc, doc_tfidf)
                    for  doc, doc_tfidf
                    in  zip (corpus, tfidf_vectors)]
     features  =  [tfidf_wtd_avg_word_vectors(tokenized_sentence, tfidf, tfidf_vocabulary,
                                    model, num_features)
                     for  tokenized_sentence, tfidf  in  docs_tfidfs]
     return  np.array(features)

tfidf_wtd_avg_word_vectors() 函數幫助咱們得到每一個文檔的 TF-IDF 加權平均詞向量。也建立一個函數 tfidf_weighted_averaged_word_vectorizer() 實現語料庫中多個文檔 TF-IDF 加權平均詞向量的計算。使用下面代碼看看實現的這個函數在示例語料庫上的執行狀況:

vocab  =  tfidf_vectorizer.vocabulary_
wt_tfidf_word_vec_features  =  tfidf_weighted_averaged_word_vectorizer(corpus = TOKENIZED_CORPUS,
     tfidf_vectors = corpus_tfidf,
     tfidf_vocabulary = vocab,
     model = model,
     num_features = 10 )
In [ 65 ]:  print (np. round (wt_tfidf_word_vec_features,  3 ))
[[  0.009  - 0.034   0.009   0.024   0.015   0.006  - 0.035   0.007  - 0.018  - 0.009 ]
  0.014  - 0.033   0.021   0.007   0.022   0.025  - 0.031  - 0.002  - 0.022  - 0.012 ]
  0.016  - 0.034   0.016   0.022   0.005  - 0.003  - 0.029   0.008  - 0.024  - 0.009 ]
  [ - 0.049  - 0.033   0.027   0.047  - 0.046   0.015  - 0.013   0.026   0.006  - 0.049 ]]
nd_wt_tfidf_word_vec_features  =  tfidf_weighted_averaged_word_vectorizer(corpus = tokenized_new_doc,
     tfidf_vectors = nd_tfidf,
     tfidf_vocabulary = vocab,
     model = model,
     num_features = 10 )nd_tfidf
In [ 69 ]:  print (np. round (nd_wt_tfidf_word_vec_features,  3 ))
[[ - 0.014  - 0.035   0.025   0.042   0.003   0.019  - 0.03    0.001  - 0.     - 0.011 ]]

從上面的結果,看到了咱們如何將每一個文檔轉換爲 TF-IDF 加權平衡詞向量。在實現基於 TF-IDF 的文檔特徵提取時,也使用到了以前得到的 TF-IDF 權重和單詞表。

相關文章
相關標籤/搜索