1. 程式人生 > >機器學習---文本特征提取之詞袋模型(Machine Learning Text Feature Extraction Bag of Words)

機器學習---文本特征提取之詞袋模型(Machine Learning Text Feature Extraction Bag of Words)

from 就是 mat 關聯關系 關系 們的 維度 進行 class

假設有一段文本:"I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends." 那麽怎麽提取這段文本的特征呢?

一個簡單的方法就是使用詞袋模型(bag of words model)。選定文本內一定的詞放入詞袋,統計詞袋內所有詞出現的頻率(忽略語法和單詞出現的順序),把詞頻(term frequency)用向量的形式表示出來。

詞頻統計可以用scikit-learn的CountVectorizer實現:

text1="I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends.
" from sklearn.feature_extraction.text import CountVectorizer CV=CountVectorizer() words=CV.fit_transform([text1]) #這裏註意要把文本字符串變為列表進行輸入 print(words)

首先CountVectorizer將文本映射成字典,字典的鍵是文本內的詞,值是詞的索引,然後對字典進行學習,將其轉換成詞頻矩陣並輸出:

  (0, 3)        1
  (0, 4)        1
  (0, 0)        1
  (0, 11)       1
  (0, 2)        1
  (0, 10)       1
  (0, 7)        2
  (0, 8)        2
  (0, 9)        1
  (0, 6)        1
  (0, 1)        1
  (0, 5)        1
(0, 7)        2  代表第7個詞"Huzihu"出現了2次。

註:CountVectorizer類會把文本全部轉換成小寫,然後將文本詞塊化(tokenize)。文本詞塊化是把句子分割成詞塊(token)或有意義的字母序列的過程。詞塊大多是單詞,但它們也可能是一些短語,如標點符號和詞綴。CountVectorizer類通過正則表達式用空格分割句子,然後抽取長度大於等於2的字母序列。(摘自:http://lib.csdn.net/article/machinelearning/42813)

我們一般提取文本特征是用於文檔分類,那麽就需要知道各個文檔之間的相似程度。可以通過計算文檔特征向量之間的

歐氏距離(Euclidean distance)來進行比較。

讓我們添加另外兩段文本,看看這三段文本之間的相似程度如何。

文本二:"My cousin has a cute dog. He likes sleeping and eating. He is friendly to others."

文本三:"We all need to make plans for the future, otherwise we will regret when we‘re old."

text1="I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends."
text2="My cousin has a cute dog. He likes sleeping and eating. He is friendly to others."
text3= "We all need to make plans for the future, otherwise we will regret when we‘re old."

corpus=[text1,text2,text3] #把三個文檔放入語料庫

from sklearn.feature_extraction.text import CountVectorizer
CV=CountVectorizer()
words=CV.fit_transform(corpus)
words_frequency=words.todense()  #用todense()轉化成矩陣
print(CV.get_feature_names()) 
print(words_frequency)

此時分別輸出的是特征名稱和由每個文本的詞頻向量組成的矩陣:

[all, and, are, cat, cousin, cute, dog, eating, for, friendly, friends, future, good, has, have, he, his, huzihu, is, likes, make, my, name, need, old, others, otherwise, plans, re, really, regret, sleeping, the, to, we, when, will]
[[0 1 1 ..., 1 0 0]
 [0 1 0 ..., 0 0 0]
 [1 0 0 ..., 3 1 1]]

可以看到,矩陣第一列,其中前兩個數都為0,最後一個數為1,代表"all"在前兩個文本中都未出現過,而在第三個文本中出現了一次。

接下來,我們就可以用sklearn中的euclidean_distances來計算這三個文本特征向量之間的距離了。

from sklearn.metrics.pairwise import euclidean_distances
for i,j in ([0,1],[0,2],[1,2]):
    dist=euclidean_distances(words_frequency[i],words_frequency[j])
    print("文本{}和文本{}特征向量之間的歐氏距離是:{}".format(i+1,j+1,dist))

輸出如下:

文本1和文本2特征向量之間的歐氏距離是:[[ 5.19615242]]
文本1和文本3特征向量之間的歐氏距離是:[[ 6.08276253]]
文本2和文本3特征向量之間的歐氏距離是:[[ 6.164414]]

可以看到,文本一和文本二之間最相似。

現在思考一下,應該選什麽樣的詞放入詞袋呢?有一些詞並不能提供多少有用的信息,比如:the, be, you, he...這些詞被稱為停用詞(stop words)。由於文本內包含的詞的數量非常之多(詞袋內的每一個詞都是一個維度),因此我們需要盡量減少維度,去除這些噪音,以便更好地計算和擬合。

可以在創建CountVectorizer實例時添加stop_words="english"參數來去除這些停用詞。

另外,也可以下載NLTK(Natural Language Toolkit)自然語言工具包,使用其裏面的停用詞。

下面,我們就用NLTK來試一試(使用之前,請大家先下載安裝:pip install NLTK):

text1="I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends."
text2="My cousin has a cute dog. He likes sleeping and eating. He is friendly to others."
text3= "We all need to make plans for the future, otherwise we will regret when we‘re old."

corpus=[text1,text2,text3]

from nltk.corpus import stopwords
noise=stopwords.words("english")

from sklearn.feature_extraction.text import CountVectorizer
CV=CountVectorizer(stop_words=noise)
words=CV.fit_transform(corpus)
words_frequency=words.todense()
print(CV.get_feature_names())
print(words_frequency)

輸出:

[cat, cousin, cute, dog, eating, friendly, friends, future, good, huzihu, likes, make, name, need, old, others, otherwise, plans, really, regret, sleeping]
[[1 0 1 ..., 1 0 0]
 [0 1 1 ..., 0 0 1]
 [0 0 0 ..., 0 1 0]]

可以看到,此時詞袋裏的詞減少了。通過查看words_frequncy.shape,我們發現特征向量的維度也由原來的37變為了21。

還有一個需要考慮的情況,比如說文本中出現的friendly和friends意思相近,可以看成是一個詞。但是由於之前把這兩個詞分別算成是兩個不同的特征,這就可能導致文本分類出現偏差。解決辦法是對單詞進行詞幹提取(stemming),再把詞幹放入詞袋。

下面用NLTK中的SnowballStemmer來提取詞幹(註意:需要先用正則表達式把文本中的詞提取出來,也就是進行詞塊化,再提取詞幹,因此在用CountVectorizer時可以把tokenizer參數設為自己寫的function):

text1="I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends."
text2="My cousin has a cute dog. He likes sleeping and eating. He is friendly to others."
text3= "We all need to make plans for the future, otherwise we will regret when we‘re old."

corpus=[text1,text2,text3]

from nltk import RegexpTokenizer
from nltk.stem.snowball import SnowballStemmer

def stemming(token):
    stemming=SnowballStemmer("english")
    stemmed=[stemming.stem(each) for each in token]
    return stemmed

def tokenize(text):
    tokenizer=RegexpTokenizer(r\w+)  #設置正則表達式規則
    tokens=tokenizer.tokenize(text)
    stems=stemming(tokens)
    return stems

from nltk.corpus import stopwords
noise=stopwords.words("english")

from sklearn.feature_extraction.text import CountVectorizer
CV=CountVectorizer(stop_words=noise,tokenizer=tokenize,lowercase=False)

words=CV.fit_transform(corpus)
words_frequency=words.todense()
print(CV.get_feature_names())
print(words_frequency)

輸出:

[cat, cousin, cute, dog, eat, friend, futur, good, huzihu, like, make, name, need, old, otherwis, plan, realli, regret, sleep]
[[1 0 1 ..., 1 0 0]
 [0 1 1 ..., 0 0 1]
 [0 0 0 ..., 0 1 0]]

可以看到,friendly和friends在提取詞幹後都變成了friend。而others提取詞幹後變為other,other屬於停用詞,被移除了,因此現在詞袋特征向量維度變成了19。

此外,還需註意的是詞形的變化。比如說單復數:"foot"和"feet",過去式和現在進行時:"understood"和"understanding",主動和被動:"eat"和"eaten",等等。這些詞都應該被視為同一個特征。解決的辦法是進行詞形還原(lemmatization)。這裏就不演示了,可以用NLTK中的WordNetLemmatizer來進行詞形還原(from nltk.stem.wordnet import WordNetLemmatizer)。

詞幹提取和詞形還原的區別可參見:https://www.neilx.com/blog/?p=1425。

最後,再想一下,我們在對文檔進行分類時,假如某個詞在文檔中都有出現,那麽這個詞就無法給分類帶來多少有用的信息。因此,對於出現頻率高的詞和頻率低的詞,我們應該區分對待,它們的重要性是不一樣的。解決的辦法就是用TF-IDF(term frequncy, inverse document frequency)來給詞進行加權。TF-IDF會根據單詞在文本中出現的頻率進行加權,出現頻率高的詞,加權系數就低,反之,出現頻率低的詞,加權系數就高。可以用sklearn的TfidfVectorizer來實現。

下面,我們把CountVectorizer換成TfidfVectorizer(包括之前使用過的提取詞幹和去除停用詞),再來計算一下這三個文本之間的相似度:

text1="I have a cat, his name is Huzihu. Huzihu is really cute and friendly. We are good friends."
text2="My cousin has a cute dog. He likes sleeping and eating. He is friendly to others."
text3= "We all need to make plans for the future, otherwise we will regret when we‘re old."

corpus=[text1,text2,text3]

from nltk import RegexpTokenizer
from nltk.stem.snowball import SnowballStemmer

def stemming(token):
    stemming=SnowballStemmer("english")
    stemmed=[stemming.stem(each) for each in token]
    return stemmed

def tokenize(text):
    tokenizer=RegexpTokenizer(r\w+)  #設置正則表達式規則
    tokens=tokenizer.tokenize(text)
    stems=stemming(tokens)
    return stems

from nltk.corpus import stopwords
noise=stopwords.words("english")

from sklearn.feature_extraction.text import TfidfVectorizer
CV=TfidfVectorizer(stop_words=noise,tokenizer=tokenize,lowercase=False)

words=CV.fit_transform(corpus)
words_frequency=words.todense()
print(CV.get_feature_names())
print(words_frequency)

from sklearn.metrics.pairwise import euclidean_distances
for i,j in ([0,1],[0,2],[1,2]):
    dist=euclidean_distances(words_frequency[i],words_frequency[j])
    print("文本{}和文本{}特征向量之間的歐氏距離是:{}".format(i+1,j+1,dist))

輸出:

[cat, cousin, cute, dog, eat, friend, futur, good, huzihu, like, make, name, need, old, otherwis, plan, realli, regret, sleep]
[[ 0.30300252  0.          0.23044123 ...,  0.30300252  0.          0.        ]
 [ 0.          0.40301621  0.30650422 ...,  0.          0.          0.40301621]
 [ 0.          0.          0.         ...,  0.          0.37796447  0.        ]]
文本1和文本2特征向量之間的歐氏距離是:[[ 1.25547312]]
文本1和文本3特征向量之間的歐氏距離是:[[ 1.41421356]]
文本2和文本3特征向量之間的歐氏距離是:[[ 1.41421356]]

可以看到,現在特征值不再是0和1了,而是加權之後的值。雖然我們只用了很短的文本進行測試,但還是能看出來,經過一系列優化後,計算出的結果更準確了。

詞袋模型的缺點: 1. 無法反映詞之間的關聯關系。例如:"Humans like cats."和"Cats like humans"具有相同的特征向量。

2. 無法捕捉否定關系。例如:"I will not eat noodles today."和"I will eat noodles today."盡管意思相反,但是從特征向量來看它們非常相似。不過這個問題可以通過設置n-gram來解決(比如可以在用sklearn創建CountVectorizer實例時加上ngram_range參數)。

機器學習---文本特征提取之詞袋模型(Machine Learning Text Feature Extraction Bag of Words)