餘弦類似度,又稱爲餘弦類似性,是經過計算兩個向量的夾角餘弦值來評估他們的類似度。餘弦類似度將向量根據座標值,繪製到向量空間中。用向量空間中兩個向量夾角的餘弦值做爲衡量兩個個體間差別的大小。餘弦值越接近1,就代表夾角越接近0度,也就是兩個向量越類似,反之越接近0就表示兩個向量類似度越低,這就叫"餘弦類似性"。java
先簡單的重溫一下高中數學知識,餘弦定理git
這個公式你們不知道還有沒有印象呢?沒有的話咱們看下下面的圖程序員
此時a=(xa,ya)
,b=(xb,0)
,那麼怎麼計算各邊長的長度呢?github
此時將各邊長代入上圖的公式當中,最後能夠得出最終的計算公式bash
那麼在咱們的文本類似度計算中,都有哪些步驟呢?maven
你好,我是小王,我是個程序員」
,將會分割成你好/我/是/小王/我/是/個/程序員
。第二句:你好,我是設計師
,將會分紅你好/我/是/設計師
你好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)
。這裏使用ikanalyzer來實現一個簡單的分詞功能ide
<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;
}
}
複製代碼
下面是主要的代碼邏輯,相關步驟已註釋在代碼裏面post
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%的類似度。
參考圖例:
公衆號博文同步Github倉庫,有興趣的朋友能夠幫忙給個Star哦,碼字不易,感謝支持。
《如何優化代碼中大量的if/else,switch/case?》
《如何提升使用Java反射的效率?》
《Java日誌正確使用姿式》
關注「深夜裏的程序猿」,分享最乾的乾貨