NLP入門(一)詞袋模型及句子相似度


  本文作為筆者NLP入門系列文章第一篇,以后我們就要步入NLP時代。
  本文將會介紹NLP中常見的詞袋模型(Bag of Words)以及如何利用詞袋模型來計算句子間的相似度(余弦相似度,cosine similarity)。
  首先,讓我們來看一下,什么是詞袋模型。我們以下面兩個簡單句子為例:

sent1 = "I love sky, I love sea."
sent2 = "I like running, I love reading."

  通常,NLP無法一下子處理完整的段落或句子,因此,第一步往往是分句和分詞。這里只有句子,因此我們只需要分詞即可。對於英語句子,可以使用NLTK中的word_tokenize函數,對於中文句子,則可使用jieba模塊。故第一步為分詞,代碼如下:

from nltk import word_tokenize
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]

輸出的結果如下:

[['I', 'love', 'sky', ',', 'I', 'love', 'sea', '.'], ['I', 'like', 'running', ',', 'I', 'love', 'reading', '.']]

  分詞完畢。下一步是構建語料庫,即所有句子中出現的單詞及標點。代碼如下:

all_list = []
for text in texts:
    all_list += text
corpus = set(all_list)
print(corpus)

輸出如下:

{'love', 'running', 'reading', 'sky', '.', 'I', 'like', 'sea', ','}

  可以看到,語料庫中一共是8個單詞及標點。接下來,對語料庫中的單詞及標點建立數字映射,便於后續的句子的向量表示。代碼如下:

corpus_dict = dict(zip(corpus, range(len(corpus))))
print(corpus_dict)

輸出如下:

{'running': 1, 'reading': 2, 'love': 0, 'sky': 3, '.': 4, 'I': 5, 'like': 6, 'sea': 7, ',': 8}

  雖然單詞及標點並沒有按照它們出現的順序來建立數字映射,不過這並不會影響句子的向量表示及后續的句子間的相似度。
  下一步,也就是詞袋模型的關鍵一步,就是建立句子的向量表示。這個表示向量並不是簡單地以單詞或標點出現與否來選擇0,1數字,而是把單詞或標點的出現頻數作為其對應的數字表示,結合剛才的語料庫字典,句子的向量表示的代碼如下:

# 建立句子的向量表示
def vector_rep(text, corpus_dict):
    vec = []
    for key in corpus_dict.keys():
        if key in text:
            vec.append((corpus_dict[key], text.count(key)))
        else:
            vec.append((corpus_dict[key], 0))

    vec = sorted(vec, key= lambda x: x[0])

    return vec

vec1 = vector_rep(texts[0], corpus_dict)
vec2 = vector_rep(texts[1], corpus_dict)
print(vec1)
print(vec2)

輸出如下:

[(0, 2), (1, 0), (2, 0), (3, 1), (4, 1), (5, 2), (6, 0), (7, 1), (8, 1)]
[(0, 1), (1, 1), (2, 1), (3, 0), (4, 1), (5, 2), (6, 1), (7, 0), (8, 1)]

讓我們稍微逗留一會兒,來看看這個向量。在第一句中I出現了兩次,在預料庫字典中,I對應的數字為5,因此在第一句中5出現2次,在列表中的元組即為(5,2),代表單詞I在第一句中出現了2次。以上的輸出可能並不那么直觀,真實的兩個句子的代表向量應為:

[2, 0, 0, 1, 1, 2, 0, 1, 1]
[1, 1, 1, 0, 1, 2, 1, 0, 1]

  OK,詞袋模型到此結束。接下來,我們會利用剛才得到的詞袋模型,即兩個句子的向量表示,來計算相似度。
  在NLP中,如果得到了兩個句子的向量表示,那么,一般會選擇用余弦相似度作為它們的相似度,而向量的余弦相似度即為兩個向量的夾角的余弦值。其計算的Python代碼如下:

from math import sqrt
def similarity_with_2_sents(vec1, vec2):
    inner_product = 0
    square_length_vec1 = 0
    square_length_vec2 = 0
    for tup1, tup2 in zip(vec1, vec2):
        inner_product += tup1[1]*tup2[1]
        square_length_vec1 += tup1[1]**2
        square_length_vec2 += tup2[1]**2

    return (inner_product/sqrt(square_length_vec1*square_length_vec2))


cosine_sim = similarity_with_2_sents(vec1, vec2)
print('兩個句子的余弦相似度為: %.4f。'%cosine_sim)

輸出結果如下:

兩個句子的余弦相似度為: 0.7303。

  這樣,我們就通過句子的詞袋模型,得到了它們間的句子相似度。
  當然,在實際的NLP項目中,如果需要計算兩個句子的相似度,我們只需調用gensim模塊即可,它是NLP的利器,能夠幫助我們處理很多NLP任務。下面為用gensim計算兩個句子的相似度的代碼:

sent1 = "I love sky, I love sea."
sent2 = "I like running, I love reading."

from nltk import word_tokenize
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]
print(texts)

from gensim import corpora
from gensim.similarities import Similarity

#  語料庫
dictionary = corpora.Dictionary(texts)

# 利用doc2bow作為詞袋模型
corpus = [dictionary.doc2bow(text) for text in texts]
similarity = Similarity('-Similarity-index', corpus, num_features=len(dictionary))
print(similarity)
# 獲取句子的相似度
new_sensence = sent1
test_corpus_1 = dictionary.doc2bow(word_tokenize(new_sensence))

cosine_sim = similarity[test_corpus_1][1]
print("利用gensim計算得到兩個句子的相似度: %.4f。"%cosine_sim)

輸出結果如下:

[['I', 'love', 'sky', ',', 'I', 'love', 'sea', '.'], ['I', 'like', 'running', ',', 'I', 'love', 'reading', '.']]
Similarity index with 2 documents in 0 shards (stored under -Similarity-index)
利用gensim計算得到兩個句子的相似度: 0.7303。

注意,如果在運行代碼時出現以下warning:

gensim\utils.py:1209: UserWarning: detected Windows; aliasing chunkize to chunkize_serial
  warnings.warn("detected Windows; aliasing chunkize to chunkize_serial")

gensim\matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int32 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):

如果想要去掉這些warning,則在導入gensim模塊的代碼前添加以下代碼即可:

import warnings
warnings.filterwarnings(action='ignore',category=UserWarning,module='gensim')
warnings.filterwarnings(action='ignore',category=FutureWarning,module='gensim')

  本文到此結束,感謝閱讀!如果不當之處,請速聯系筆者,歡迎大家交流!祝您好運~

注意:本人現已開通微信公眾號: Python爬蟲與算法(微信號為:easy_web_scrape), 歡迎大家關注哦~~


免責聲明!

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



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