文本相似度计算基本方法小结
在計(jì)算文本相似項(xiàng)發(fā)現(xiàn)方面,有以下一些可參考的方法。這些概念和方法會(huì)幫助我們開拓思路。
?
相似度計(jì)算方面
Jaccard相似度:集合之間的Jaccard相似度等于交集大小與并集大小的比例。適合的應(yīng)用包括文檔文本相似度以及顧客購物習(xí)慣的相似度計(jì)算等。
Shingling:k-shingle是指文檔中連續(xù)出現(xiàn)的任意k個(gè)字符。如果將文檔表示成其k-shingle集合,那么就可以基于集合之間的 Jaccard相似度來計(jì)算文檔之間的文本相似度。有時(shí),將shingle哈希成更短的位串非常有用,可以基于這些哈希值的集合來表示文檔。
最小哈希:集合上的最小哈希函數(shù)基于全集上的排序轉(zhuǎn)換來定義。給定任意一個(gè)排列轉(zhuǎn)換,集合的最小哈希值為在排列轉(zhuǎn)換次序下出現(xiàn)的第一個(gè)集合元素。
最小哈希簽名:可以選出多個(gè)排列轉(zhuǎn)換,然后在每個(gè)排列轉(zhuǎn)換下計(jì)算集合的最小哈希值,這些最小哈希值序列構(gòu)成集合的最小哈希簽名。給定兩個(gè)集合,產(chǎn)生相同哈希值的排列轉(zhuǎn)換所占的期望比率正好等于集合之間的Jaccard相似度。
高效最小哈希:由于實(shí)際不可能產(chǎn)生隨機(jī)的排列轉(zhuǎn)換,因此通常會(huì)通過下列方法模擬一個(gè)排列轉(zhuǎn)換:選擇一個(gè)隨機(jī)哈希函數(shù),利用該函數(shù)對集合中所有的元素進(jìn)行哈希操作,其中得到的最小值看成是集合的最小哈希值。
簽名的局部敏感哈希:該技術(shù)可以允許我們避免計(jì)算所有集合對或其最小哈希簽名對之間的相似度。給定集合的簽名,我們可以將它們劃分成行條,然后僅僅計(jì)算至少有一個(gè)行條相等的集合對之間的相似度。通過合理選擇行條大小,可以消除不滿足相似度閾值的大部分集合對之間的比較。
?
向量空間距離方面
歐式距離:n維空間下的歐式距離,是兩個(gè)點(diǎn)在各維上差值的平方和的算數(shù)平方根。適合歐式空間的另一個(gè)距離是曼哈頓距離,指兩個(gè)點(diǎn)各維度的差的絕對值之和。
Jaccard距離:1減去Jaccard相似度也是一個(gè)距離測度。
余弦距離:向量空間下兩個(gè)向量的夾角大小。
編輯距離:該距離測度應(yīng)用于字符串,指的是通過需要的插入、刪除操作將一個(gè)字符串處理成另一個(gè)字符串的操作次數(shù)。編輯距離還可以通過兩個(gè)字符串長度之和減去兩者最長公共子序列長度的兩倍來計(jì)算。
海明距離:應(yīng)用于向量空間。兩個(gè)向量之間的海明距離計(jì)算的是它們之間不相同的位置個(gè)數(shù)。
?
索引輔助方面
字符索引:如果將集合表示成字符串,且需要達(dá)到的相似度閾值接近1。那么就可以將每個(gè)字符串按照其頭部的一小部分字母建立索引。需要索引的前綴的長度大概等于整個(gè)字符串的長度乘以給定的最大的Jaccard距離。
位置索引:我們不僅可以給出索引字符串前綴中的字符,也可以索引其在前綴中的位置。如果兩個(gè)字符串共有的一個(gè)字符并不出現(xiàn)在雙方的第一個(gè)位置,那么我們就知道要么存在某些前面的字符出現(xiàn)在并集但不出現(xiàn)在交集中,那么在兩個(gè)字符串中存在一個(gè)更前面的公共字符。這樣的話,我們就可以減少需要比較的字符串對數(shù)目。
后綴索引:我們不僅可以索引字符串前綴中的字符及其位置,還可以索引當(dāng)前字符后綴的長度,即字符串中該字符之后的位置數(shù)量。由于相同字符但是后綴長度不同意味著有額外的字符必須出現(xiàn)在并集但不出現(xiàn)在交集中,因此上述結(jié)構(gòu)能夠進(jìn)一步減少需要比較的字符串?dāng)?shù)目。
?
總結(jié)
以上的一些概念和方法可以配合使用,可以基本滿足許多場景下的相似度計(jì)算。相似度計(jì)算又可以為相關(guān)推薦做基礎(chǔ)。怎么做好詞的粒度切分,怎么劃定閾值,選擇何種距離測算,如何優(yōu)化實(shí)現(xiàn)方法還是要下很多功夫的。
?
兩個(gè)例子
Levenshtein其實(shí)是編輯距離,下面計(jì)算編輯距離的方法是把兩個(gè)String串里的字/詞當(dāng)成一個(gè)矩陣來比較和計(jì)算。
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public class LevenshteinDis {? ??? ????public static void main(String[] args) {? ????????// 要比較的兩個(gè)字符串? ????????String str1 =?"相似度計(jì)算方法";? ????????String str2 =?"文本相似項(xiàng)發(fā)現(xiàn)";? ????????levenshtein(str1, str2);? ????}? ??? ????public static void levenshtein(String str1, String str2) {? ??? ????????int len1 = str1.length();? ????????int len2 = str2.length();? ??? ????????int[][] dif =?new int[len1 +?1][len2 +?1];? ??? ????????for (int a =?0; a <= len1; a++) {? ????????????dif[a][0] = a;? ????????}? ????????for (int a =?0; a <= len2; a++) {? ????????????dif[0][a] = a;? ????????}? ??????????? ????????int temp;? ????????for (int i =?1; i <= len1; i++) {? ????????????for (int j =?1; j <= len2; j++) {? ????????????????if (str1.charAt(i -?1) == str2.charAt(j -?1)) {? ????????????????????temp =?0;? ????????????????}?else {? ????????????????????temp =?1;? ????????????????}? ????????????????// 取三個(gè)值中最小的? ????????????????dif[i][j] = min(dif[i -?1][j -?1] + temp, dif[i][j -?1] +?1,? ????????????????????????dif[i -?1][j] +?1);? ????????????}? ????????}? ????????System.out.println("字符串\"" + str1 +?"\"與\"" + str2 +?"\"的比較");? ????????System.out.println("差異步驟:" + dif[len1][len2]);? ????????// 計(jì)算相似度? ????????float similarity =?1 - (float) dif[len1][len2]? ????????????????/ Math.max(str1.length(), str2.length());? ????????System.out.println("相似度:" + similarity);? ????}? ??? ????private static int min(int... is) {? ????????int min = Integer.MAX_VALUE;? ????????for (int i : is) {? ????????????if (min > i) {? ????????????????min = i;? ????????????}? ????????}? ????????return min;? ????}? ??? } |
下面是余弦距離計(jì)算的例子:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | public class CosineSimilarAlgorithm {? ????public static double getSimilarity(String doc1, String doc2) {? ????????if (doc1 !=?null && doc1.trim().length() >?0 && doc2 !=?null? ????????????????&& doc2.trim().length() >?0) {? ??????????????? ????????????Map<Integer,?int[]> AlgorithmMap =?new HashMap<Integer,?int[]>();? ??????????????? ????????????//將兩個(gè)字符串中的中文字符以及出現(xiàn)的總數(shù)封裝到,AlgorithmMap中? ????????????for (int i =?0; i < doc1.length(); i++) {? ????????????????char d1 = doc1.charAt(i);? ????????????????if(isHanZi(d1)){? ????????????????????int charIndex = getGB2312Id(d1);? ????????????????????if(charIndex != -1){? ????????????????????????int[] fq = AlgorithmMap.get(charIndex);? ????????????????????????if(fq !=?null && fq.length ==?2){? ????????????????????????????fq[0]++;? ????????????????????????}else {? ????????????????????????????fq =?new int[2];? ????????????????????????????fq[0] =?1;? ????????????????????????????fq[1] =?0;? ????????????????????????????AlgorithmMap.put(charIndex, fq);? ????????????????????????}? ????????????????????}? ????????????????}? ????????????}? ??? ????????????for (int i =?0; i < doc2.length(); i++) {? ????????????????char d2 = doc2.charAt(i);? ????????????????if(isHanZi(d2)){? ????????????????????int charIndex = getGB2312Id(d2);? ????????????????????if(charIndex != -1){? ????????????????????????int[] fq = AlgorithmMap.get(charIndex);? ????????????????????????if(fq !=?null && fq.length ==?2){? ????????????????????????????fq[1]++;? ????????????????????????}else {? ????????????????????????????fq =?new int[2];? ????????????????????????????fq[0] =?0;? ????????????????????????????fq[1] =?1;? ????????????????????????????AlgorithmMap.put(charIndex, fq);? ????????????????????????}? ????????????????????}? ????????????????}? ????????????}? ??????????????? ????????????Iterator<Integer> iterator = AlgorithmMap.keySet().iterator();? ????????????double sqdoc1 =?0;? ????????????double sqdoc2 =?0;? ????????????double denominator =?0;?? ????????????while(iterator.hasNext()){? ????????????????int[] c = AlgorithmMap.get(iterator.next());? ????????????????denominator += c[0]*c[1];? ????????????????sqdoc1 += c[0]*c[0];? ????????????????sqdoc2 += c[1]*c[1];? ????????????}? ??????????????? ????????????return denominator / Math.sqrt(sqdoc1*sqdoc2);? ????????}?else {? ????????????throw new NullPointerException(? ????????????????????" the Document is null or have not cahrs!!");? ????????}? ????}? ??? ????public static boolean isHanZi(char ch) {? ????????// 判斷是否漢字? ????????return (ch >=?0x4E00 && ch <=?0x9FA5);? ??? ????}? ??? ????/** ?????* 根據(jù)輸入的Unicode字符,獲取它的GB2312編碼或者ascii編碼, ?????*? ?????* @param ch ?????*??????????? 輸入的GB2312中文字符或者ASCII字符(128個(gè)) ?????* @return ch在GB2312中的位置,-1表示該字符不認(rèn)識(shí) ?????*/? ????public static short getGB2312Id(char ch) {? ????????try {? ????????????byte[] buffer = Character.toString(ch).getBytes("GB2312");? ????????????if (buffer.length !=?2) {? ????????????????// 正常情況下buffer應(yīng)該是兩個(gè)字節(jié),否則說明ch不屬于GB2312編碼,故返回'?',此時(shí)說明不認(rèn)識(shí)該字符? ????????????????return -1;? ????????????}? ????????????int b0 = (int) (buffer[0] &?0x0FF) -?161;?// 編碼從A1開始,因此減去0xA1=161? ????????????int b1 = (int) (buffer[1] &?0x0FF) -?161;?// 第一個(gè)字符和最后一個(gè)字符沒有漢字,因此每個(gè)區(qū)只收16*6-2=94個(gè)漢字? ????????????return (short) (b0 *?94 + b1);? ????????}?catch (UnsupportedEncodingException e) {? ????????????e.printStackTrace();? ????????}? ????????return -1;? ????}? } |
來自:http://blog.csdn.net/pelick/article/details/8741346
轉(zhuǎn)載于:https://www.cnblogs.com/qtccf/p/4562254.html
總結(jié)
以上是生活随笔為你收集整理的文本相似度计算基本方法小结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BZOJ 3698(XWW的难题-上下界
- 下一篇: 11.PS-橡皮擦工具组