文本類似度 餘弦值類似度算法 VS L氏編輯距離(動態規劃)

本文由做者祝娜受權網易雲社區發佈。html

本文對兩種文本類似度算法進行比較。餘弦值類似度算法 VS 最小編輯距離法
一、L氏編輯距離(基於詞條空間)
編輯距離(Edit Distance),又稱Levenshtein距離,是指兩個字串之間,由一個轉成另外一個所需的最少編輯操做次數。許可的編輯操做包括將一個字符替換成另外一個字符,插入一個字符,刪除一個字符。java

算法實現步驟:python

1 設置n爲字符串s的長度。("我是個小仙女")
設置m爲字符串t的長度。("我不是個小仙女")
若是n等於0,返回m並退出。
若是m等於0,返回n並退出。
構造兩個向量v0[m+1] 和v1[m+1],串聯0..m之間全部的元素。
2 初始化 v0 to 0..m。
3 檢查 s (i from 1 to n) 中的每一個字符。
4 檢查 t (j from 1 to m) 中的每一個字符
5 若是 s[i] 等於 t[j],則編輯代價cost爲 0;
若是 s[i] 不等於 t[j],則編輯代價cost爲1。
6 設置單元v1[j]爲下面的最小值之一:
a、緊鄰該單元上方+1:v1[j-1] + 1
b、緊鄰該單元左側+1:v0[j] + 1
c、該單元對角線上方和左側+cost:v0[j-1] + cost
7 在完成迭代 (3, 4, 5, 6) 以後,v1[m]即是編輯距離的值。算法

咱們獲得最小編輯距離爲1那麼它們的類似度爲 (1-ld/(double)Math.max(str1.length(), str2.length()));設計模式

1 - 1/8=7/8.數組

其算法實現(java):服務器

public static float levenshtein(String str1,String str2) {  
    //計算兩個字符串的長度。  
    int len1 = str1.length();  
    int len2 = str2.length();  
    //創建上面說的數組,比字符長度大一個空間  
    int[][] dif = new int[len1 + 1][len2 + 1];  
    //賦初值,步驟B。  
    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;  
            }  
            //取三個值中最小的  
            dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1,  
                    dif[i - 1][j] + 1);  
        }  
    }  

    float similarity =1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length());  
    return similarity;
}

二、餘弦值(基於權值空間)學習

關於餘弦類似度能夠參照 百度詞條 餘弦類似度編碼

經過測量兩個向量之間的角的餘弦值來度量它們之間的類似性。0度角的餘弦值是1,而其餘任何角度的餘弦值都不大於1;而且其最小值是-1。從而兩個向量之間的角度的餘弦值肯定兩個向量是否大體指向相同的方向。因此,它一般用於文件比較。設計

算法步驟
預處理→文本特徵項選擇→加權→生成向量空間模型後計算餘弦。

具體步驟見附件: 餘弦類似度算法步驟解釋.docx
其算法實現(java):

public static double getSimilarity(String doc1, String doc2) { if (doc1 != null && doc1.trim().length() > 0 && doc2 != null && doc2.trim().length() > 0) {

MapAlgorithmMap = new HashMap();            // 將兩個字符串中的中文字符以及出現的總數封裝到,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);
                    }
                }
            }
        }

        Iteratoriterator = 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];
        }            double origin = denominator / Math.sqrt(sqdoc1 * sqdoc2);            if (String.valueOf(origin).equals("NaN")) {                return Double.valueOf("0");
        }
        BigDecimal bg = new BigDecimal(origin);            double f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();            return f1;
    } else {            throw new NullPointerException(" the Document is null or have not cahrs!!");
    }
}    public static boolean isHanZi(char ch) {        // 判斷是否漢字
    return (ch >= 0x4E00 && ch <= 0x9FA5);

}    /**
 * 根據輸入的Unicode字符,獲取它的GB2312編碼或者ascii編碼,
 *
 * @param ch
 *            輸入的GB2312中文字符或者ASCII字符(128個)
 * @return ch在GB2312中的位置,-1表示該字符不認識
 */
public static short getGB2312Id(char ch) {        try {            byte[] buffer = Character.toString(ch).getBytes("GB2312");            if (buffer.length != 2) {                // 正常狀況下buffer應該是兩個字節,不然說明ch不屬於GB2312編碼,故返回'?',此時說明不認識該字符
            return -1;
        }            int b0 = (buffer[0] & 0x0FF) - 161; // 編碼從A1開始,所以減去0xA1=161
        int b1 = (buffer[1] & 0x0FF) - 161; // 第一個字符和最後一個字符沒有漢字,所以每一個區只收16*6-2=94個漢字
        return (short) (b0 * 94 + b1);
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }        return -1;
}

如今對兩種計算類似度的算法進行比較:

編輯距離類似度運行結果:

'第六章 字符編碼' 與 '第一章 設計模式' 的類似度爲:0.07159507274627686

'第六章 字符編碼' 與 '第二章 python學習' 的類似度爲:0.06676656007766724

'第六章 字符編碼' 與 '第三章 python簡介' 的類似度爲:0.08275055885314941

'第六章 字符編碼' 與 '第四章 輸入輸出' 的類似度爲:0.1878122091293335

'第六章 字符編碼' 與 '第五章 數據類型和變量' 的類似度爲:0.20358151197433472

'第六章 字符編碼' 與 '第六章 字符編碼' 的類似度爲:1.0

'第六章 字符編碼' 與 '第七章 list' 的類似度爲:0.20995670557022095

runtime:989毫秒

L編輯距離的時間 算法採用矩陣的方式,計算兩個字符串之間的變化步驟,會遍歷兩個文本中的每個字符兩兩比較,能夠推斷出時間複雜度至少爲 document1.length × document2.length

cos類似度運行結果:

'第六章 字符編碼' 與 '第一章 設計模式' 的類似度爲:0.5

'第六章 字符編碼' 與 '第二章 python學習' 的類似度爲:0.59

'第六章 字符編碼' 與 '第三章 python簡介' 的類似度爲:0.68

'第六章 字符編碼' 與 '第四章 輸入輸出' 的類似度爲:0.62

'第六章 字符編碼' 與 '第五章 數據類型和變量' 的類似度爲:0.72

'第六章 字符編碼' 與 '第六章 字符編碼' 的類似度爲:1.0

'第六章 字符編碼' 與 '第七章 list' 的類似度爲:0.59

runtime:400毫秒

使用餘弦定理計算文本效率相對比較高:其算法複雜度大體爲:document1.length + document2.length。

可是同時也能夠看出 餘弦類似度獲得的結果相對比較高一些。使用分詞或者過濾掉一些經常使用詞會對結果的準確性更有利。

使用分詞的方法在本文中沒有展開。可是若是去掉文章裏的「的、了、吧,呢、啊」等能夠提升結果的準確率。固然同時也能夠提升判斷的閥值。

運行結果:

'第六章 字符編碼' 與 '第一章 設計模式' 的類似度爲:0.37

'第六章 字符編碼' 與 '第二章 python學習' 的類似度爲:0.48

'第六章 字符編碼' 與 '第三章 python簡介' 的類似度爲:0.57

'第六章 字符編碼' 與 '第四章 輸入輸出' 的類似度爲:0.56

'第六章 字符編碼' 與 '第五章 數據類型和變量' 的類似度爲:0.67

'第六章 字符編碼' 與 '第六章 字符編碼' 的類似度爲:1.0

'第六章 字符編碼' 與 '第七章 list' 的類似度爲:0.48

runtime:519毫秒

看以看出準確度有了必定的提升。

番外:

L編輯距離動態計算法,調用python腳本實現,腳本文件

author = 'victor'

-- coding:utf-8 --

import sys

import Levenshtein

if name == "__main__":

if(len(sys.argv) < 3):

    print('Usage: python myratiodetect.py str1 str2')

    exit(-1)

str1 = sys.argv[1]

str2 = sys.argv[2]

r = Levenshtein.ratio(str1, str2)

print(r)

exit(0)

本地運行的前提爲:已經適應pip安裝了:python_Levenshtein,因此其對服務器的依賴比較大,若是工程環境遷移了的話,會比較受影響。

程序的運行結果:

'第六章 字符編碼' 與 '第一章 設計模式' 的類似度爲:0.157063851181

'第六章 字符編碼' 與 '第二章 python學習' 的類似度爲:0.165801038753

'第六章 字符編碼' 與 '第三章 python簡介' 的類似度爲:0.194563908481

'第六章 字符編碼' 與 '第四章 輸入輸出' 的類似度爲:0.268671351528

'第六章 字符編碼' 與 '第五章 數據類型和變量' 的類似度爲:0.300997688969

'第六章 字符編碼' 與 '第六章 字符編碼' 的類似度爲:1.0

'第六章 字符編碼' 與 '第七章 list' 的類似度爲:0.296406739228

runtime:2247毫秒

運行速度.....比較慢..2333

參考:

https://www.2cto.com/kf/20140...

http://wdhdmx.iteye.com/blog/...

http://blog.sina.com.cn/s/blo...

http://blog.sina.com.cn/s/blo...

更多網易技術、產品、運營經驗分享請訪問網易雲社區。
文章來源: 網易雲社區

相關文章
相關標籤/搜索