文字挖掘預處理之向量化與Hash Trick

劉建平Pinard發表於2017-04-10

    在文字挖掘的分詞原理中,我們講到了文字挖掘的預處理的關鍵一步:“分詞”,而在做了分詞後,如果我們是做文字分類聚類,則後面關鍵的特徵預處理步驟有向量化或向量化的特例Hash Trick,本文我們就對向量化和特例Hash Trick預處理方法做一個總結。

1. 詞袋模型

    在講向量化與Hash Trick之前,我們先說說詞袋模型(Bag of Words,簡稱BoW)。詞袋模型假設我們不考慮文字中詞與詞之間的上下文關係,僅僅只考慮所有詞的權重。而權重與詞在文字中出現的頻率有關。

    詞袋模型首先會進行分詞,在分詞之後,通過統計每個詞在文字中出現的次數,我們就可以得到該文字基於詞的特徵,如果將各個文字樣本的這些詞與對應的詞頻放在一起,就是我們常說的向量化。向量化完畢後一般也會使用TF-IDF進行特徵的權重修正,再將特徵進行標準化。 再進行一些其他的特徵工程後,就可以將資料帶入機器學習演算法進行分類聚類了。

    總結下詞袋模型的三部曲:分詞(tokenizing),統計修訂詞特徵值(counting)與標準化(normalizing)。

    與詞袋模型非常類似的一個模型是詞集模型(Set of Words,簡稱SoW),和詞袋模型唯一的不同是它僅僅考慮詞是否在文字中出現,而不考慮詞頻。也就是一個詞在文字在文字中出現1次和多次特徵處理是一樣的。在大多數時候,我們使用詞袋模型,後面的討論也是以詞袋模型為主。

    當然,詞袋模型有很大的侷限性,因為它僅僅考慮了詞頻,沒有考慮上下文的關係,因此會丟失一部分文字的語義。但是大多數時候,如果我們的目的是分類聚類,則詞袋模型表現的很好。

2. 詞袋模型之向量化

    在詞袋模型的統計詞頻這一步,我們會得到該文字中所有詞的詞頻,有了詞頻,我們就可以用詞向量表示這個文字。這裡我們舉一個例子,例子直接用scikit-learn的CountVectorizer類來完成,這個類可以幫我們完成文字的詞頻統計與向量化,程式碼如下:

    完整程式碼參見我的github:https://github.com/ljpzzz/machinelearning/blob/master/natural-language-processing/hash_trick.ipynb

from sklearn.feature_extraction.text import CountVectorizer  
vectorizer=CountVectorizer() corpus
=["I come to China to travel", "This is a car polupar in China", "I love tea and Apple ", "The work is to write some papers in science"] print vectorizer.fit_transform(corpus)

    我們看看對於上面4個文字的處理輸出如下:

  (0, 16)	1
  (0, 3)	1
  (0, 15)	2
  (0, 4)	1
  (1, 5)	1
  (1, 9)	1
  (1, 2)	1
  (1, 6)	1
  (1, 14)	1
  (1, 3)	1
  (2, 1)	1
  (2, 0)	1
  (2, 12)	1
  (2, 7)	1
  (3, 10)	1
  (3, 8)	1
  (3, 11)	1
  (3, 18)	1
  (3, 17)	1
  (3, 13)	1
  (3, 5)	1
  (3, 6)	1
  (3, 15)	1

    可以看出4個文字的詞頻已經統計出,在輸出中,左邊的括號中的第一個數字是文字的序號,第2個數字是詞的序號,注意詞的序號是基於所有的文件的。第三個數字就是我們的詞頻。

    我們可以進一步看看每個文字的詞向量特徵和各個特徵代表的詞,程式碼如下:

print vectorizer.fit_transform(corpus).toarray()
print vectorizer.get_feature_names()

    輸出如下:

[[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 2 1 0 0]
 [0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0]
 [1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
 [0 0 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1]]
[u'and', u'apple', u'car', u'china', u'come', u'in', u'is', u'love', u'papers', u'polupar', u'science', u'some', u'tea', u'the', u'this', u'to', u'travel', u'work', u'write']

    可以看到我們一共有19個詞,所以4個文字都是19維的特徵向量。而每一維的向量依次對應了下面的19個詞。另外由於詞"I"在英文中是停用詞,不參加詞頻的統計。

    由於大部分的文字都只會使用詞彙表中的很少一部分的詞,因此我們的詞向量中會有大量的0。也就是說詞向量是稀疏的。在實際應用中一般使用稀疏矩陣來儲存。

    將文字做了詞頻統計後,我們一般會通過TF-IDF進行詞特徵值修訂,這部分我們後面再講。

    向量化的方法很好用,也很直接,但是在有些場景下很難使用,比如分詞後的詞彙表非常大,達到100萬+,此時如果我們直接使用向量化的方法,將對應的樣本對應特徵矩陣載入記憶體,有可能將記憶體撐爆,在這種情況下我們怎麼辦呢?第一反應是我們要進行特徵的降維,說的沒錯!而Hash Trick就是非常常用的文字特徵降維方法。

3.  Hash Trick

    在大規模的文字處理中,由於特徵的維度對應分詞詞彙表的大小,所以維度可能非常恐怖,此時需要進行降維,不能直接用我們上一節的向量化方法。而最常用的文字降維方法是Hash Trick。說到Hash,一點也不神祕,學過資料結構的同學都知道。這裡的Hash意義也類似。

    在Hash Trick裡,我們會定義一個特徵Hash後對應的雜湊表的大小,這個雜湊表的維度會遠遠小於我們的詞彙表的特徵維度,因此可以看成是降維。具體的方法是,對應任意一個特徵名,我們會用Hash函式找到對應雜湊表的位置,然後將該特徵名對應的詞頻統計值累加到該雜湊表位置。如果用數學語言表示,假如雜湊函式$h$使第$i$個特徵雜湊到位置$j$,即$h(i)=j$,則第$i$個原始特徵的詞頻數值$\phi(i)$將累加到雜湊後的第$j$個特徵的詞頻數值$\bar{\phi}$上,即:$$\bar{\phi}(j) = \sum_{i\in \mathcal{J}; h(i) = j}\phi(i)$$

    其中$\mathcal{J}$是原始特徵的維度。

    但是上面的方法有一個問題,有可能兩個原始特徵的雜湊後位置在一起導致詞頻累加特徵值突然變大,為了解決這個問題,出現了hash Trick的變種signed hash trick,此時除了雜湊函式$h$,我們多了一個一個雜湊函式:$$\xi : \mathbb{N} \to {\pm 1}$$

    此時我們有$$\bar{\phi}(j) = \sum_{i\in \mathcal{J}; h(i) = j}\xi(i)\phi(i)$$

    這樣做的好處是,雜湊後的特徵仍然是一個無偏的估計,不會導致某些雜湊位置的值過大。

    當然,大家會有疑惑,這種方法來處理特徵,雜湊後的特徵是否能夠很好的代表雜湊前的特徵呢?從實際應用中說,由於文字特徵的高稀疏性,這麼做是可行的。如果大家對理論上為何這種方法有效,建議參考論文:Feature hashing for large scale multitask learning.這裡就不多說了。

    在scikit-learn的HashingVectorizer類中,實現了基於signed hash trick的演算法,這裡我們就用HashingVectorizer來實踐一下Hash Trick,為了簡單,我們使用上面的19維詞彙表,並雜湊降維到6維。當然在實際應用中,19維的資料根本不需要Hash Trick,這裡只是做一個演示,程式碼如下:

from sklearn.feature_extraction.text import HashingVectorizer 
vectorizer2=HashingVectorizer(n_features = 6,norm = None)
print vectorizer2.fit_transform(corpus)

    輸出如下:

  (0, 1)	2.0
  (0, 2)	-1.0
  (0, 4)	1.0
  (0, 5)	-1.0
  (1, 0)	1.0
  (1, 1)	1.0
  (1, 2)	-1.0
  (1, 5)	-1.0
  (2, 0)	2.0
  (2, 5)	-2.0
  (3, 0)	0.0
  (3, 1)	4.0
  (3, 2)	-1.0
  (3, 3)	1.0
  (3, 5)	-1.0

    大家可以看到結果裡面有負數,這是因為我們的雜湊函式$\xi$可以雜湊到1或者-1導致的。

    和PCA類似,Hash Trick降維後的特徵我們已經不知道它代表的特徵名字和意義。此時我們不能像上一節向量化時候可以知道每一列的意義,所以Hash Trick的解釋性不強。

4. 向量化與Hash Trick小結

    這裡我們對向量化與它的特例Hash Trick做一個總結。在特徵預處理的時候,我們什麼時候用一般意義的向量化,什麼時候用Hash Trick呢?標準也很簡單。

    一般來說,只要詞彙表的特徵不至於太大,大到記憶體不夠用,肯定是使用一般意義的向量化比較好。因為向量化的方法解釋性很強,我們知道每一維特徵對應哪一個詞,進而我們還可以使用TF-IDF對各個詞特徵的權重修改,進一步完善特徵的表示。

    而Hash Trick用大規模機器學習上,此時我們的詞彙量極大,使用向量化方法記憶體不夠用,而使用Hash Trick降維速度很快,降維後的特徵仍然可以幫我們完成後續的分類和聚類工作。當然由於分散式計算框架的存在,其實一般我們不會出現記憶體不夠的情況。因此,實際工作中我使用的都是特徵向量化。

    向量化與Hash Trick就介紹到這裡,下一篇我們討論TF-IDF。

 

(歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com) 

相關文章