文字相似度計算之餘弦定理

深夜裡的程式猿發表於2019-05-13

前言

餘弦相似度,又稱為餘弦相似性,是通過計算兩個向量的夾角餘弦值來評估他們的相似度。餘弦相似度將向量根據座標值,繪製到向量空間中。用向量空間中兩個向量夾角的餘弦值作為衡量兩個個體間差異的大小。餘弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,反之越接近0就表示兩個向量相似度越低,這就叫"餘弦相似性"。

正文

重溫餘弦定理

先簡單的重溫一下高中數學知識,餘弦定理

文字相似度計算之餘弦定理

這個公式大家不知道還有沒有印象呢?沒有的話我們看下下面的圖

文字相似度計算之餘弦定理

此時a=(xa,ya),b=(xb,0),那麼怎麼計算各邊長的長度呢?

文字相似度計算之餘弦定理

此時將各邊長代入上圖的公式當中,最後可以得出最終的計算公式

文字相似度計算之餘弦定理

文字相似度計算步驟

那麼在我們的文字相似度計算中,都有哪些步驟呢?

  • 分詞,比如有兩行文字,第一句:你好,我是小王,我是個程式設計師”,將會分割成你好/我/是/小王/我/是/個/程式設計師。第二句:你好,我是設計師,將會分成你好/我/是/設計師
  • 統計詞頻,其實就是統計所有語句中的每個詞在當前句子出現的次數,第一句:你好1,我2,是2,小王1,個1,程式設計師1,設計師0,第二句你好1,我1,是1,小王0,個0,程式設計師0,設計師1
  • 組合詞頻向量,第一句(1,2,2,1,1,1,0),第二句(1,1,1,0,0,0,1)
  • 將資料代入上面的公式計算相似度

maven 引入ikanalyzer依賴

這裡使用ikanalyzer來實現一個簡單的分詞功能

 <dependency>
    <groupId>com.janeluo</groupId>
    <artifactId>ikanalyzer</artifactId>
    <version>2012_u6</version>
</dependency>
複製程式碼

IKUtils分詞工具類,程式碼比簡單,唯一一個方法返回的是語句分詞的List物件

/**
 * 分詞相關工具類
 * @author wangzh
 */
public class IKUtils {

    /**
     * 以List的格式返回文字分詞的結果
     * @param text
     * @return
     */
    public static List<String> divideText(String text){
        if(null == text || "".equals(text.trim())){
            return null;
        }
        List<String> resultList = new ArrayList<>();
        StringReader re = new StringReader(text);
        IKSegmenter ik = new IKSegmenter(re, true);
        Lexeme lex = null;
        try {
            while ((lex = ik.next()) != null) {
                resultList.add(lex.getLexemeText());
            }
        } catch (Exception e) {
            //TODO
        }
        return resultList;
    }

}
複製程式碼

下面是主要的程式碼邏輯,相關步驟已註釋在程式碼裡面

public class Analysis {
    public static void main(String[] args) {
        Map<String,int[]> resultMap = new HashMap<>();
        //測試文字
        String text1 = "你好,我是小王,我是個程式設計師";
        String text2 = "你好,我是設計師";
        //統計
        statistics(resultMap, IKUtils.divideText(text1),1);
        statistics(resultMap, IKUtils.divideText(text2),0);
        //計算類
        final Calculation calculation = new Calculation();
        resultMap.forEach((k,v)->{
            int[] arr = resultMap.get(k);
            calculation.setNumerator(calculation.getNumerator() + arr[0] * arr[1]);
            calculation.setElementA(calculation.getElementA() + arr[0] * arr[0]);
            calculation.setElementB(calculation.getElementB() + arr[1] * arr[1]);
        });

       System.out.println("文字相似度:" + calculation.result());
    }

    /**
     * 組合詞頻向量
     
     * @param words
     * @param direction
     * @return
     */
    private static void statistics(Map<String,int[]> map,List<String> words ,int direction){
        if(null == words || words.size() == 0){
            return ;
        }
        int[] in = null;
        boolean flag = direction(direction);
        for (String word : words){
            int[] wordD = map.get(word);
            if(null == wordD){
                if(flag){
                    in = new int[]{1,0};
                }else {
                    in = new int[]{0,1};
                }
                map.put(word,in);
            }else{
                if(flag){
                    wordD[0]++;
                }else{
                    wordD[1]++;
                }
            }
        }
    }
    
    //判斷不同句子
    private static boolean direction(int direction){
        return direction == 1?true:false;
    }

}
複製程式碼

用於計算餘弦相似度的類

public class Calculation{

    private  double elementA;
    private  double elementB;
    private  double numerator;

    public double result(){
        return numerator / Math.sqrt(elementA * elementB);
    }
    //省略get/set
}
複製程式碼

輸出結果:

文字相似度:0.7216878364870323
複製程式碼

從結果可以看出這兩句話大致上還是比較相似的。用通俗一點的話來說就是有72%的相似度。

參考圖例:

www.jianshu.com/p/f4606ae11…


公眾號博文同步Github倉庫,有興趣的朋友可以幫忙給個Star哦,碼字不易,感謝支援。

github.com/PeppaLittle…

推薦閱讀

如何優化程式碼中大量的if/else,switch/case?
如何提高使用Java反射的效率?
Java日誌正確使用姿勢

有收穫的話,就點個贊吧

關注「深夜裡的程式猿」,分享最乾的乾貨

文字相似度計算之餘弦定理

相關文章