您的位置:首页 > 其它

Jensen-Shannon散度

2015-07-10 16:18 363 查看
        文本匹配有很多种算法,常见的如余弦相似度,杰卡德距离,TFIDF等,这些方法网上资料很多,这里就不在讲了,前段时间做工程的时候用到了JS散度,网上一查关于JS散度的资料太少,并且大多都是英文的,且没有找到算法源码,实在没办法,本人就花了一个小时左右写了一个JS散度的JAVA源码供大家参考使用。

先介绍JS散度吧,JS散度是KL散度的一种变种,其基于KL散度,但是区别于KL散度有两点: (1), JS散度的最终值域范围是[0,1],相同为0,相反为1.(例如,str1=毛主席 ,str2=毛主席, 那么因为str1与str2每个字都是相同的,故而JS(str1,str2)=0,而
str3=周总理,那么JS(str1,str3)=1)。(2),对称性(主要区别点),即JS(str1,str2)=JS(str2,str1),(因为JS相对与KL,它引入了一个变量,设为M,则M=1/2(P+Q))。

废话不多说,我们就直接上源码了,有什么问题或者不清楚的话在交流。

//注:本代码在于计算两段英文文章的JS散度,童鞋们可以根据自己的需要进行修改

package VirtualPaper;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Map;

import java.util.Set;

//以字符串中每个汉字所属拼音作为一个整体,js距离是相同的则为0,不相同的则为1

public class JensenShannonUnit {

//得到字符串的概率分布--以一个汉字拼音作为一个整体
public HashMap<String,Double> getDistribution(String str){
HashMap<String,Integer> map = new HashMap<String,Integer>();      //存储子字符串,及其出现的次数
HashMap<String,Double> mappro = new HashMap<String,Double>();        //存储子字符串的概率分布

String[] str11 = str.split(" ");
int total = str11.length;                           //字符串数组的长度(含有的子字符串的个数)

for(String word : str11){                   //将字母存入HashMap中
if(map.containsKey(word)){
int value = map.get(word);
map.put(word, ++value);
}else
map.put(word, 1);
}

Set<String> set = map.keySet();
for(String word:set){
int value = map.get(word);
double probility = value*1.0/total;
mappro.put(word, probility);
}

//System.out.println(mappro);
return mappro;
}

//得到1/2(P+Q)的分布
public Map<String,Double> getM(Map<String,Double> map1,Map<String,Double> map2){
Map<String,Double> map = new HashMap<String,Double>();           //存储1/2(P+Q)的概率分布

Set<String> set1 = map1.keySet();
Set<String> set2 = map2.keySet();
//将所有的字符添加至一个 set里面
Set<String> set = new HashSet<String>();
set.addAll(set1);
set.addAll(set2);

for(String word:set){             //得到value1和value2的平均值
double i = 0;          //P的单词概率
double j = 0;
  //Q的单词概率
if(map1.containsKey(word))
i = map1.get(word);
if(map2.containsKey(word))
j = map2.get(word);
double min = (i+j)/2;
map.put(word, min);
}

return map;
}

//得到两个HashMap的KL距离  map1为P或Q,map2为M=1/2(P+Q)
public double getKLDistance(Map<String,Double> P,Map<String,Double> M){
double KLD = 0;
Set<String> set1 = P.keySet();
//Set<Character> set2 = map2.keySet();
for(String ch:set1){
double i = P.get(ch);
double j = M.get(ch);
double m = i*(Math.log(i/j)/Math.log(2));    //以底为2的对数函数
KLD+=m;
}
return KLD;
}

public double getJensenDistance(String str1,String str2){
Map<String,Double> P = getDistribution(str1);
Map<String,Double> Q = getDistribution(str2);
Map<String,Double> M = getM(P, Q);

double KLDP = getKLDistance(P, M); 
double KLDQ = getKLDistance(Q, M);
double jsd = (KLDP+KLDQ)/2;

return jsd;
}

}

有问题的话 大家多讨论,也顺便贴出较简单的Cos相似度和杰卡德系数

package VirtualPaper;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.Iterator;

import java.util.List;

import java.util.Set;

import java.util.TreeMap;

public class JaccardAndCosUnit {

//计算两个字符串的Jaccard系数--一个中文字体的拼音作为一个单元
public double getJaccard(String str1,String str2){
String[] str11=str1.split(" ");
String[] str22=str2.split(" ");
Set<String> intersection=new HashSet<String>();
Set<String> union=new HashSet<String>();

for(int i=0;i<str11.length;i++){
for(int j=0;j<str22.length;j++){
if(str11[i].equals(str22[j]))
intersection.add(str11[i]);
else{
union.add(str11[i]);
union.add(str22[j]);
}
}
}

/* System.out.println("交集大小:"+intersection.size());
System.out.println("并集大小:"+union.size());*/
return (double)intersection.size()/union.size();
}

//计算余弦相似度  向量值表示出现的次数
//得到字符串的向量表示
public List<Integer> getVectors(Set<String> set,String str){
List<Integer> list=new ArrayList<Integer>();         //存储向量值
TreeMap<String,Integer> treemap=new TreeMap<String,Integer>(); 
for(String word:set){
treemap.put(word, 0);
}

String[] str11 = str.split(" ");
for(int i=0;i<str11.length;i++){
int frequency=treemap.get(str11[i]);
treemap.put(str11[i],frequency+1);
}

Iterator<Integer> iter=treemap.values().iterator();
while(iter.hasNext()){
list.add(iter.next());
}
return list;
}
//计算余弦相似度
public double compcos(List<Integer> list1,List<Integer> list2){
double ab=0;             //存储两个向量的积
for(int i=0;i<list1.size();i++){
ab+=list1.get(i)*list2.get(i);
}
double a=0;
for(Integer in:list1){
a+=Math.pow(in, 2);
}
double b=0;
for(Integer in:list2){
b+=Math.pow(in, 2);
}
return ab/Math.sqrt(a*b);      //返回余弦相似度
}

//计算两个字符串的余弦相似度
public double getCosstr(String str1,String str2){
Set<String> hashset=new HashSet<String>();  //存储两个字符串的子字符串并集合
String[] str11 = str1.split(" ");
String[] str22 = str2.split(" ");
for(int i=0;i<str11.length;i++){
hashset.add(str11[i]);
}
for(int i=0;i<str22.length;i++){
hashset.add(str22[i]);
}
//将字符串用向量进行表示
List<Integer> list1=getVectors(hashset,str1);
List<Integer> list2=getVectors(hashset,str2);
//返回两个向量的余弦相似度
return compcos(list1,list2);
}

}

欢迎大家支出错误!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息