文本匹配算法综述
文本匹配,顧名思義,就是描述兩段文本之間的關系,是否指向同一語義;比如兩句話是否描述同一件事,或者兩句話是否是上下文/問題與答案的關系。例:
| 小寶寶生病怎么辦 | 狗寶寶生病怎么辦 |
| 明天天氣怎么樣 | 明天預報有雨 |
| 先帝創業未半而中道崩殂 | 今天下三分,益州疲弊,此誠危急存亡之秋也 |
文本匹配任務在自然語言處理中是非常重要的基礎任務之一,有很多應用場景;如信息檢索、問答系統、智能對話、文本鑒別、智能推薦、文本數據去重等,但文本匹配或者說自然語言處理仍然存在很多難點。
? ? ? ?如語言不規范,同一句話可以有多種表達方式;如“股市跳水、股市大跌、股市一片綠”
???????歧義,同一個詞語或句子在不同語境可能表達不同意思;如“割韭菜”,“領盒飯”,“能穿多少穿多少”,在不同語境下語義完全不同
? ? ? ? 不規范或錯誤的輸入;如 “?yyds”,“舉個栗子”
? ? ? ? 需要知識依賴;如豐田supra綽號“牛魔王”等。
????????
常見的文本匹配算法如下表,按傳統模型和深度模型簡單的分為兩類:
| 傳統模型 |
| 傳統模型 |
| 傳統模型 |
| 傳統模型 |
| 傳統模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
| 深度模型 |
接下來,讓我們通過一個例子,來具體看一下各個算法的實現過程及匹配效果(其中并沒有實現深度學習類算法)。
舉個栗子:
| 內蒙古錫林郭勒盟多倫縣縣醫院 | 多倫縣醫院 | 1(匹配) |
| 綿陽市四零四醫院 | 四川綿陽404醫院 | 1(匹配) |
| 鄧州市人民醫院 | 南召縣人民醫院 | 0(不匹配) |
1.Jaccard杰卡德相似系數
????????jaccard相似度是一種非常直觀的相似度計算方式,即兩句子分詞后詞語的交集中詞語數與并集中詞語數之比。
def jaccard(list1, list2):"""jaccard相似系數:param list1:第一句話的詞列表 :param list2: 第二句話的詞列表:return:相似度,float值 """list1, list2 = set(list1), set(list2) #去重intersection = list1.intersection(list2) # 交集union = list1.union(list2) # 并集Similarity = 1.0 * len(intersection) / len(union) #交集比并集return Similarity? a.分詞
????????內蒙古 錫林郭勒盟 多倫縣 縣醫院? ? /? ? 多倫縣 醫院
????????綿陽市? 四 零 四 醫院? ? ?/? ? ?四川 綿陽?404?醫院
????????鄧州市 人民 醫院? ? ? ?/? ? ? ?南召縣 人民 醫院
b.去重求交集--并集
? ? ? ? 多倫縣(交集)? ? --? ? ? 內蒙古、錫林郭勒盟、多倫縣、縣醫院、醫院(并集)
? ? ? ? 醫院(交集)? ? ? --? ? ? ? 綿陽市、四、零、醫院、四川、綿陽、404(并集)
? ? ? ? 人民、醫院(交集)? ? --? ? ?鄧州市、人民、醫院、南召縣(并集)
c.相似度
| 文本對 | 相似度 | 真實標簽 |
| 內蒙古 錫林郭勒盟 多倫縣 縣醫院? ? /? ? 多倫縣 醫院 | 1/5=0.2 | 1 |
| 綿陽市? 四 零 四 醫院? ? ?/? ? ?四川 綿陽?404?醫院 | 1/7 = 0.14 | 1 |
| 鄧州市 人民 醫院? ? ? ?/? ? ? ?南召縣 人民 醫院 | 2/4 = 0.5 | 0 |
????????
2.Levenshtein編輯距離
? ? ? ?一個句子轉換為另一個句子需要的編輯次數,編輯包括刪除、替換、添加,然后使用最長句子的長度歸一化得相似度。
def Levenshtein(text1, text2):"""Levenshtein編輯距離:param text1:字符串1:param text2:字符串2:return:相似度,float值"""import Levenshteindistance = Levenshtein.distance(text1, text2)Similarity = 1 - distance * 1.0 / max(len(text1), len(text2))return Similaritya.分詞-計字數
????????內 蒙 古 錫 林 郭 勒 盟 多 倫 縣 縣 醫 院 (14)? ?/? ? 多 倫 縣 醫 院(5)
????????綿 陽 市? 四 零 四 醫 院(8)? ? ?/? ? ?四 川 綿 陽?4 0 4?醫 院(9)
????????鄧 州 市 人 民 醫 院 (7)? ? ? /? ? ? ?南 召 縣 人 民?醫 院(7)
b.計算編輯距離
? ? ? ?內 蒙 古 錫 林 郭 勒 盟 多 倫 縣 縣 醫 院 ------->刪除內、蒙、古、錫、林、郭、勒、盟、縣
? ? ???綿 陽 市? 四 零 四 醫 院? ?------->加 四、川
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?------->刪除 市
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?------->替換 四(4)、零(0)、四(4)
? ? ? ?鄧 州 市 人 民 醫 院? ? ? ? ?------->替換鄧(南)、 州(召)、 市(縣)
| 文本對 | 距離 | 真實標簽 |
| 內蒙古 錫林郭勒盟 多倫縣 縣醫院? ? /? ? 多倫縣 醫院 | 0.357 | 1 |
| 綿陽市? 四 零 四 醫院? ? ?/? ? ?四川 綿陽?404?醫院 | 0.333 | 1 |
| 鄧州市 人民 醫院? ? ? ?/? ? ? ?南召縣 人民 醫院 | 0.571 | 0 |
3 simhash相似度
先計算兩句子的simhash二進制編碼,然后使用海明距離計算,最后使用兩句的最大simhash值歸一化得相似度。
def simhash_(text1, text2):"""simhash相似度:param s1: 字符串1:param s2: 字符串2:return: 相似度,float值"""from simhash import Simhashtext1_simhash = Simhash(text1, f=64) #計算simhash向量text2_simhash = Simhash(text2, f=64) #計算simhash向量distance = text1_simhash.distance(text2_simhash) #漢明距離Similarity = 1 - distance / max(len(bin(text1_simhash.value)), len(bin(text2_simhash.value))) #相似度return Similaritya.分詞
????????內蒙古 錫林郭勒盟 多倫縣 縣醫院? ? /? ? 多倫縣 醫院
????????綿陽市? 四 零 四 醫院? ? ?/? ? ?四川 綿陽?404?醫院
????????鄧州市 人民 醫院? ? ? ?/? ? ? ?南召縣 人民 醫院
b.計算詞權重(此處用tfidf計算,其他方法也可以)
| 內蒙古5 | 錫林郭勒盟5 | 多倫縣2 | 縣醫院5 | 多倫縣7 | 醫院1 | |||
| 綿陽市3 | 四6 | 零3 | 四6 | 醫院1 | 四川5 | 綿陽5 | 4045 | 醫院1 |
| ?鄧州市7 | 人民4 | 醫院1 | 南召縣7 | 人民4 | 醫院1 |
c.hash函數映射詞向量;先將詞映射到二進制編碼,然后用b步驟中的權重值替換1,b步驟中權重值的負數替換0
| 詞 | 二進制編碼 | 加權重 |
| 內蒙古 | 1111000 | -5 5 5 5 5 -5 -5 -5 |
| 錫林郭勒盟 | 1100000 | -5 5 5 -5 -5 -5 -5 -5 |
| 多倫縣 | 1111011 | -2 2 2 2 2 -2 2 2 |
| 縣醫院 | 10110010 | 5 -5 5 5 -5 -5 5 -5 |
| 多倫縣 | 1111011 | -7 7 7 7 7 -7 7 7 |
| 醫院 | 10010001 | 1 -1 -1 1 -1 -1 -1 1 |
| 綿陽市 | 10111111 | 3 -3 3 3 3 3 3 3 |
| 四 | 11100100 | 6 6 6 -6 -6 6 -6 -6 |
| 零 | 1011110 | -3 3 -3 3 3 3 3 -3 |
| 四 | 11100100 | 6 6 6 -6 -6 6 -6 -6 |
| 醫院 | 10010001 | 1 -1 -1 1 -1 -1 -1 1 |
| 四川 | 1100001 | -5 5 5 -5 -5 -5 -5 5 |
| 綿陽 | 111010 | -5 -5 5 5 5 -5 5 -5 |
| 404 | 1010 | -5 -5 -5 -5 5 -5 5 -5 |
| 醫院 | 10010001 | 1 -1 -1 1 -1 -1 -1 1 |
| 鄧州市 | 10101011 | 7 -7 7 -7 7 -7 7 7 |
| 人民 | 10001011 | 4 -4 -4 -4 4 -4 4 4 |
| 醫院 | 10010001 | 1 -1 -1 1 -1 -1 -1 1 |
| ?南召縣 | 11101010 | 7 7 7 -7 7 -7 7 -7 |
| 人民 | 10001011 | 4 -4 -4 -4 4 -4 4 4 |
| 醫院 | 10010001 | 1 -1 -1 1 -1 -1 -1 1 |
d.合并(將一段文本內的詞向量進行累加)
| 文本 | 合并向量 | simhash值 |
| 內蒙古錫林郭勒盟多倫縣縣醫院 | -7 7 17 7 -3 -17 -3 -13 | -1 1 1 1 -1 -1 -1 -1 |
| 多倫縣醫院 | -6 6 6 8 6? -8 6 8 | -1 1 1 1 1 -1 1 1 |
| 綿陽市四零四醫院 | 13 11 11 -5 -7 17 -7 -11 | 1 1 1 -1 -1 -1 -1 -1 |
| 四川 綿陽?404?醫院 | -14 -6 4 -4 4 -16 4 -4 | -1 -1 1 -1 1 -1 1 -1 |
| 鄧州市人民醫院? | 12 -12 2 -10 10 -12? ?10 12 | 1 -1 1 -1 1 -1 1 1? |
| 南召縣人民醫院 | 12 2 2 -10 10 -12 10 -2 | 1 1 1 -1 1 -1 1 -1 |
e海明距離判斷相似度
簡單的說,海明距離可以理解為,兩個二進制串之間相同位置不同的個數。舉個例子,[1,1,1,0,0,0]和[1,1,1,1,1,1]的海明距離就是3。
| 文本對 | 海明距離 | 真實標簽 |
| 內蒙古 錫林郭勒盟 多倫縣 縣醫院? ? /? ? 多倫縣 醫院 | 3 | 1 |
| 綿陽市? 四 零 四 醫院? ? ?/? ? ?四川 綿陽?404?醫院 | 4 | 1 |
| 鄧州市 人民 醫院? ? ? ?/? ? ? ?南召縣 人民 醫院 | 2 | 0 |
4 Bm25相似度
一句話概況其主要思想:對Query(待查詢語句)進行語素解析,生成語素qi;然后,對于每個搜索結果D,計算每個語素qi與D的相關性得分,最后,將qi相對于D的相關性得分進行加權求和,從而得到Query與D的相關性得分。
其中,Q表示Query,qi表示Q解析之后的一個語素(對中文而言,我們可以把對Query的分詞作為語素分析,每個詞看成語素qi。);d表示一個搜索結果文檔;Wi表示語素qi的權重,為詞語的逆文檔頻率;R(qi,d)表示語素qi與文檔d的相關性得分。
其中,N是待匹配總文本數
? ? ? ? ? ?n(qt)是出現當前詞的文本數
其中?f(qi, D)為單詞在當前候選文檔中的詞頻
????????k1、b為調節因子,通常設為k1=2,b=0.75
????????|D|為當前候選文本數(與目標文本匹配的總條數)
????????avgdl為語料庫中所有文檔的平均長度。
#Bm25 import math import jieba class BM25(object):def __init__(self, docs):#docs是一個包含所有文本的列表,每個元素是一個文本self.D = len(docs) #總文本數self.avgdl = sum([len(doc)+0.0 for doc in docs]) / self.D #平均文本長度self.docs = docs #文本庫列表self.f = [] # 列表的每一個元素是一個dict,dict存儲著一個文檔中每個詞的出現次數self.df = {} # 存儲每個詞及出現了該詞的文檔數量self.idf = {} # 存儲每個詞的idf值self.k1 = 1.5self.b = 0.75self.init()def init(self):for doc in self.docs: #對每個文本tmp = {} #定義一個字典存儲詞出現頻次for word in doc:tmp[word] = tmp.get(word, 0) + 1 # 存儲每個文檔中每個詞的出現次數self.f.append(tmp)for k in tmp.keys():self.df[k] = self.df.get(k, 0) + 1for k, v in self.df.items():self.idf[k] = math.log(self.D-v+0.5)-math.log(v+0.5) #計算idfdef sim(self, doc, index):score = 0for word in doc:if word not in self.f[index]:continued = len(self.docs[index])score += (self.idf[word]*self.f[index][word]*(self.k1+1)/ (self.f[index][word]+self.k1*(1-self.b+self.b*d/ self.avgdl)))return scoredef simall(self, doc):scores = []for index in range(self.D):score = self.sim(doc, index)scores.append(score)return scoresif __name__ == '__main__':sents1 = ["多倫縣醫院", #數據庫"四川綿陽404醫院","南召縣人民醫院"]sents2 = ["內蒙古錫林郭勒盟多倫縣縣醫院","綿陽市四零四醫院","鄧州市人民醫院"]#待匹配文本doc = []for sent in sents1:words = list(jieba.cut(sent))doc.append(words)print(doc)s = BM25(doc)print(s.f)print(s.idf)for k in sents2:print(s.simall(jieba.lcut(k))) #打印相似度匹配結果| -1.68 | -1.69 | -1.94 |
| -2.28 | -1.69 | -1.94 |
| -2.28 | -1.69 | -1.43 |
5. VSM算法
VSM算法的思路主要分為兩步:
(1) 用向量表示句子,用向量表示句子的方法很多,簡單的有onehot,詞頻法,基于語義的有word2vec/fastText/glove/bert/elmo等,本例中使用基于簡單的詞頻的向量化方式。
(2)計算兩向量的余弦距離(曼哈頓距離、歐幾里得距離、明式距離、切比雪夫距離)得相似度。
#tfidf_余弦 def sim_vecidf(self, s1, s2):"""詞向量通過idf加權平均后計算余弦距離"""v1, v2 = [], []# 1. 詞向量idf加權平均for s in jieba.cut(s1):idf_v = idf.get(s, 1)if s in voc:v1.append(1.0 * idf_v * voc[s])v1 = np.array(v1).mean(axis=0)for s in jieba.lcut(s2):idf_v = idf.get(s, 1)if s in voc:v2.append(1.0 * idf_v * voc[s])v2 = np.array(v2).mean(axis=0)# 2. 計算cosinesim = self.cosine(v1, v2)return sima.句子向量化
a1.取句子對的唯一詞元組
set(內蒙古 錫林郭勒盟 多倫縣 縣醫院??/?多倫縣 醫院) = (內蒙古 錫林郭勒盟 多倫縣 縣醫院 醫院)
set(綿陽市? 四 零 四 醫院??/? 四川 綿陽?404?醫院) = (綿陽市? 四 零 醫院 四川 綿陽?404 )
set(鄧州市 人民 醫院? ?/? ?南召縣 人民 醫院) = (鄧州市 人民 醫院??南召縣)
a2.根據每個句子在元組中的詞頻建立向量表示
| 句子 | 向量 |
| 內蒙古 錫林郭勒盟 多倫縣 縣醫院 | 1 1 1 1 0??? |
| 多倫縣 醫院 | 0 0 1 0 1 |
| 綿陽市? 四 零 四 醫院? | 1 2 1 1 0 0 0 |
| 四川 綿陽?404?醫院 | 0 0 0 1 1 1 1 |
| 鄧州市 人民 醫院 | 1 1 1 0 |
| ?南召縣 人民 醫院 | 0 1 1 1 |
b.計算余弦距離
? ? ? ??????????????????
| 句子 | 距離 |
| 內蒙古 錫林郭勒盟 多倫縣 縣醫院??/?多倫縣 醫院 | 0.3535 |
| 綿陽市? 四 零 四 醫院??/? 四川 綿陽?404?醫院 | 0.1889 |
| 鄧州市 人民 醫院? ?/? ?南召縣 人民 醫院 | 0.6666 |
6.BERT模型+余弦相似度
BERT是谷歌在2018年推出的深度語言表示模型,是關于語言理解的深度雙向transformers的預訓練模型,開啟了預訓練模型的新篇章。它可以學習文本的語義信息,通過向量形式的輸出可以用于下游任務。也就說,它自己已經在大規模預料上訓練好的參數,我們在用的時候只需要在這個基礎上訓練更新參數。bert模型可以解決多種自然語言處理問題,如單文本分類、語句對分類、序列標注等。在解決文本匹配任務時,有兩種思路,第一種直接把文本匹配任務作為語句對分類任務,模型輸入語句對,輸出是否匹配的標簽;第二種利用bert模型預訓練文本對的上下文嵌入向量,再通過余弦相似度等相似度計算方法驗證文本對是否匹配,在此基礎上還有很多基于bert模型的變種,篇幅有限不做一一講述。BERT模型詳情請轉:(2條消息) 圖解Transformer(完整版)_龍心塵-CSDN博客_transformer,在此不做贅述。接下來簡單介紹一下bert預訓練文本嵌入+余弦相似度的算法框架。
a.首先使用大量公域文本數據對BERT模型進行預訓練(或直接用谷歌預訓練好的模型)
b.將文本直接輸入模型
c.對模型輸出的語義向量C,或中間隱層向量,計算余弦相似度,得到匹配結果。
基于深度學習的匹配算法種類繁多,如基于CNN網絡、RNN網絡、LSTM網絡等及各種變種層出不窮,在此不一一列舉實現。
傳統的文本匹配方法主要關注文本間字與字,詞與詞的匹配關系,無法準確識別不同表達方式下不同文本的同一指向關系,即語義關系。因此在這一背景下,要對多源異構的海量地址數據進行匹配,傳統的文本匹配方法已不再適用,深度學習方法大行其道。但深度學習方法也有自身的局限性,比如對海量文本和算力的高要求等,都使得深度學習方法的普適性大打折扣,因此沒有最好的文本匹配算法,只有當前條件下最適合的文本匹配算法。
參考文獻:
傳統文本匹配算法詳解(附代碼) - 知乎 (zhihu.com)
文本匹配利器:從孿生網絡到Sentence-BERT綜述 (360doc.com)
(2條消息) SimHash算法原理_qq_33905939的博客-CSDN博客
一種基于深度學習BERT算法的短文本相似匹配的方法 - 百度文庫 (baidu.com)
深度學習文本匹配簡述 - ZhangHT97 - 博客園 (cnblogs.com)
文本匹配模型TextMatching - 簡書 (jianshu.com)
(2條消息) BERT模型的結構,特點和實踐_dream6104的專欄-CSDN博客_bert模型結構
總結
- 上一篇: 建立矩阵 reshape函数,pasca
- 下一篇: java 进度条插件_java进度条控件