W~J~T~E
一、基本方法
在做自然語言處理的過程中,我們經常會遇到需要找出相似語句的場景,或者找出句子的近似表達,那么求句子相似度方法有哪些呢?
- 編輯距離計算
- 傑卡德系數計算
- TF 計算
- TFIDF 計算
- Word2Vec 計算
1)Word2Vec:其實就是將每一個詞轉換為向量的過程
這里我們可以直接下載訓練好的 Word2Vec 模型,模型的鏈接地址為:news_12g_baidubaike_20g_novel_90g_embedding_64.bin,提取碼:l9r4
import gensim import jieba import numpy as np from scipy.linalg import norm model_file = './word2vec/news_12g_baidubaike_20g_novel_90g_embedding_64.bin' model = gensim.models.KeyedVectors.load_word2vec_format(model_file, binary=True) def vector_similarity(s1, s2): def sentence_vector(s): words = jieba.lcut(s) v = np.zeros(64) for word in words: v += model[word] v /= len(words) return v v1, v2 = sentence_vector(s1), sentence_vector(s2) return np.dot(v1, v2) / (norm(v1) * norm(v2)) s1 = '你在干嘛' s2 = '你正做什么' print(vector_similarity(s1, s2)) strings = [ '你在干什么', '你在干啥子', '你在做什么', '你好啊', '我喜歡吃香蕉' ] target = '你在干啥' for string in strings: print(string, vector_similarity(string, target))
在獲取Sentence Vector 的時候,我們首先對句子進行分詞,然后對分好的每一個詞獲取其對應的 Vector,然后將所有 Vector 相加並求平均,這樣就可得到 Sentence Vector 了,然后再計算其夾角余弦值即可
代碼示例結果:
0.6701133967824016 你在干什么 0.8785495016487205 你在干啥子 0.9789649689827054 你在做什么 0.8781992402695276 你好啊 0.5174225914249864 我喜歡吃香蕉 0.5829908414506211
可以看到相近的語句相似度都能到 0.8 以上,而不同的句子相似度都不足 0.6,這個區分度就非常大了,可以說有了 Word2Vec 我們可以結合一些語義信息來進行一些判斷,效果明顯也好很多
2)傑卡德系數
傑卡德系數,英文叫做 Jaccard index,又稱為 Jaccard 相似系數,用於比較有限樣本集之間的相似性與差異性。Jaccard 系數值越大,樣本相似度越高
實際上它的計算方式非常簡單,就是兩個樣本的交集除以並集得到的數值,當兩個樣本完全一致時,結果為 1,當兩個樣本完全不同時,結果為 0
下面我們來看看代碼:
from sklearn.feature_extraction.text import CountVectorizer import numpy as np def jaccard_similarity(s1, s2): def add_space(s): return ' '.join(s) # 將字中間加入空格 s1, s2 = add_space(s1), add_space(s2) # 轉化為TF矩陣 cv = CountVectorizer(tokenizer=lambda s: s.split()) corpus = [s1, s2] vectors = cv.fit_transform(corpus).toarray() print(cv.get_feature_names()) print(vectors) # 求交集 numerator = np.sum(np.min(vectors, axis=0)) print(np.min(vectors, axis=0)) # 求並集 denominator = np.sum(np.max(vectors, axis=0)) print(np.max(vectors, axis=0)) # 計算傑卡德系數 return 1.0 * numerator / denominator s1 = '你在干嘛呢' s2 = '你在干什么呢' print(jaccard_similarity(s1, s2))
這里值得學習的有 CountVectorizer 的用法,通過它的 fit_transform() 方法我們可以將字符串轉化為詞頻矩陣
例如這里有兩句話“你在干嘛呢”和“你在干什么呢”,首先 CountVectorizer 會計算出不重復的有哪些字,會得到一個字的列表,結果為:(cv.get_feature_names())
['么', '什', '你', '呢', '嘛', '在', '干']
接下來通過轉化之后,vectors 變量就變成了:
[[0 0 1 1 1 1 1] [1 1 1 1 0 1 1]]
交集大小和並集大小,然后作商即可:
0.5714285714285714
這個數值越大,代表兩個字符串越接近,否則反之,因此我們也可以使用這個方法,並通過設置一個相似度閾值來進行篩選
3)TF 計算
第三種方案就是直接計算 TF 矩陣中兩個向量的相似度了,實際上就是求解兩個向量夾角的余弦值:點乘積除以二者的模長,公式如下
cosθ=a·b/|a|*|b|
方法2中我們已經獲得了 TF 矩陣,下面我們只需要求解兩個向量夾角的余弦值就好了,代碼如下:
代碼如下:
from sklearn.feature_extraction.text import CountVectorizer import numpy as np from scipy.linalg import norm def tf_similarity(s1, s2): def add_space(s): return ' '.join(s) # 將字中間加入空格 s1, s2 = add_space(s1), add_space(s2) # 轉化為TF矩陣 cv = CountVectorizer(tokenizer=lambda s: s.split()) corpus = [s1, s2] vectors = cv.fit_transform(corpus).toarray() # 計算TF系數 return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1])) s1 = '你在干嘛呢' s2 = '你在干什么呢' print(tf_similarity(s1, s2))
在這里我們使用了 np.dot() 方法獲取了向量的點乘積,然后通過 norm() 方法獲取了向量的模長,經過計算得到二者的 TF 系數,結果如下:
0.7302967433402214
4)TFIDF 計算
另外除了計算 TF 系數我們還可以計算 TFIDF 系數,TFIDF 實際上就是在詞頻 TF 的基礎上再加入 IDF 的信息,IDF 稱為逆文檔頻率
下面我們還是借助於 Sklearn 中的模塊 TfidfVectorizer 來實現,代碼如下:
from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np from scipy.linalg import norm def tfidf_similarity(s1, s2): def add_space(s): return ' '.join(s) # 將字中間加入空格 s1, s2 = add_space(s1), add_space(s2) # 轉化為TF矩陣 cv = TfidfVectorizer(tokenizer=lambda s: s.split()) corpus = [s1, s2] vectors = cv.fit_transform(corpus).toarray() # 計算TF系數 return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1])) s1 = '你在干嘛呢' s2 = '你在干什么呢' print(tfidf_similarity(s1, s2))
代碼示例結果:
0.5803329846765686
所以通過 TFIDF 系數我們也可以進行相似度的計算
5)編輯距離
編輯距離,英文叫做 Edit Distance,又稱 Levenshtein 距離,是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數
如果它們的距離越大,說明它們越是不同。許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符
例如我們有兩個字符串:string 和 setting,如果我們想要把 string 轉化為 setting,需要這么兩步:
- 第一步,在 s 和 t 之間加入字符 e
- 第二步,把 r 替換成 t
所以它們的編輯距離差就是 2,這就對應着二者要進行轉化所要改變(添加、替換、刪除)的最小步數。
那么用 Python 怎樣來實現呢,我們可以直接使用 distance 庫:
import distance def edit_distance(s1, s2): return distance.levenshtein(s1, s2) s1 = 'string' s2 = 'setting' print(edit_distance(s1, s2))
代碼示例結果:2
這樣如果我們想要獲取相似的文本的話可以直接設定一個編輯距離的閾值來實現,如設置編輯距離為 2,下面是一個樣例:
import distance def edit_distance(s1, s2): return distance.levenshtein(s1, s2) strings = [ '你在干什么', '你在干啥子', '你在做什么', '你好啊', '我喜歡吃香蕉' ] target = '你在干啥' results = list(filter(lambda x: edit_distance(x, target) <= 2, strings)) print(results)
代碼示例結果:
['你在干什么', '你在干啥子']
通過這種方式我們可以大致篩選出類似的句子,但是發現一些句子例如“你在做什么” 就沒有被識別出來,但他們的意義確實是相差不大的
因此,編輯距離並不是一個好的方式,但是簡單易用
二、感謝
本文參考:https://cuiqingcai.com/6101.html
感謝,知識分享推動世界進步!