关于SimHash算法的实现及测试V3.0
@祁俊辉,2017年6月9日测试。
1 说明
本文章衔接关于SimHash算法的实现及测试V2.0;
本文章与利用IK Analyzer分词(txt输入输出)相结合;
本程序先使用利用IK Analyzer分词(txt输入输出)分词程序对文件分词后输出分词后的文件,然后使用本程序读取该文件,再进行SimHash计算与对比。
2 程序
1 import java.io.*; 2 import java.math.BigInteger; 3 import java.security.MessageDigest; 4 5 /* 【算法】SimHash->128位 6 * 【说明】1.继上一篇文章SimHash_128,新增文件读取 7 * 【说明】2.经IK分词后,将分词后的文件存储,改程序进行读取 8 * 【时间】祁俊辉->2017.6.8 9 * */ 10 public class SimHash_128_txt { 11 //以下两个文档与之前程序中的字符串一样,检测准确性 12 static String s5 = Txt_read("E:/SimHash文档库/对比文档5.txt"); 13 static String s6 = Txt_read("E:/SimHash文档库/对比文档6.txt"); 14 //以下两个文档为新增文档,两段话,检测相似度 15 static String s9 = Txt_read("E:/SimHash文档库/对比文档9.txt"); 16 static String s10 = Txt_read("E:/SimHash文档库/对比文档10.txt"); 17 /* 函数名:Txt_read(String name) 18 * 功能:读取name文件的内容,name为txt文件名 19 * */ 20 static String Txt_read(String name){ 21 String s = ""; 22 File file = new File(name);//原始文件 23 try { 24 FileReader fr = new FileReader(file); 25 BufferedReader bufr = new BufferedReader(fr); 26 String s_x = null; 27 //int i = 0; 28 while((s_x = bufr.readLine()) != null) { 29 //i++; 30 //System.out.println("第"+i+"行的原始数据:"); 31 s += s_x; 32 } 33 bufr.close(); 34 fr.close(); 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } 38 return s; 39 } 40 /* 函数名:MD5_Hash(String str) 41 * 功能:计算字符串str的128位hash值,并将其以String型返回 42 * */ 43 static String MD5_Hash(String str){ 44 try{ 45 // 生成一个MD5加密计算摘要 46 MessageDigest md = MessageDigest.getInstance("MD5"); 47 // 计算md5函数 48 //System.out.println("字符串:"+str); 49 //System.out.println("字符串的MD5_Hash:"+md.digest(str.getBytes())); 50 // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符 51 // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值 52 return new BigInteger(1,md.digest(str.getBytes("UTF-8"))).toString(2); 53 }catch(Exception e){ 54 e.printStackTrace(); 55 return str; 56 } 57 } 58 /* 函数名:First_FC(String str) 59 * 功能:1.先创建一个存储SimHash值的128数组,并初始化为0 60 * 功能:2.将str字符串分词,并存入临时数组 61 * 功能:3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中 62 * 功能:4.将数组中的SimHash值降维,并以字符串形式返回 63 * */ 64 static String First_FC(String str){ 65 //1.先创建一个存储SimHash值的128数组,并初始化为0 66 int Hash_SZ[] = new int[128]; 67 for(int i=0;i<Hash_SZ.length;i++) 68 Hash_SZ[i]=0; 69 //2.将str字符串分词,并存入临时数组 70 String[] newstr = str.split("/"); 71 //下面的for循环是为了增加前后文关联性//即12/23/34/45··· 72 /*for(int i=0;i<newstr.length-1;i++){ 73 newstr[i]=newstr[i]+newstr[i+1]; 74 }*/ 75 //相应的,若增加上面的关联性语句,下面一行的代码要改为 76 //for(int i=0;i<newstr.length-1;i++){ 77 //3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中 78 for(int i=0;i<newstr.length;i++){//循环传入字符串的每个词 79 String str_hash=MD5_Hash(newstr[i]);//先计算每一个词的Hash值(128位) 80 //MD5哈希计算时,二进制转换若最高位为7以下,也就是转换成二进制最高位为0,会不存储该0,导致后面程序出错 81 //这里主要是为了保证它是128位的二进制 82 if(str_hash.length() < 128){ 83 int que = 128 - str_hash.length(); 84 for(int j=0;j<que;j++){ 85 str_hash = "0" + str_hash; 86 } 87 } 88 //System.out.println(str_hash);//输出该词的128位MD5哈希值 89 char str_hash_fb[]=str_hash.toCharArray();//将该词的哈希值转为数组,方便检查 90 //System.out.println(Integer.toBinaryString(str_hash)); 91 //对每个词的Hash值(32位)求j位是1还是0,1的话加上该词的权重,0的话减去该词的权重 92 for(int j=0;j<Hash_SZ.length;j++){ 93 if(str_hash_fb[j] == '1'){ 94 Hash_SZ[j]++;//Hash_SZ中,0是最高位,依次排低 95 }else{ 96 Hash_SZ[j]--; 97 } 98 } 99 } 100 //4.将数组中的SimHash值降维,并以字符串形式返回 101 String SimHash_number="";//存储SimHash值 102 for(int i=0;i<Hash_SZ.length;i++){//从高位到低位 103 System.out.print(Hash_SZ[i]+" ");//输出未降维的串 104 if(Hash_SZ[i]<=0)//小于等于0,就取0 105 SimHash_number += "0"; 106 else//大于0,就取1 107 SimHash_number += "1"; 108 } 109 System.out.println("");//换行 110 return SimHash_number; 111 } 112 /* 函数名:HMJL(String a,String b) 113 * 功能:a、b都是以String存储的二进制数,计算他们的海明距离,并将其返回 114 * */ 115 static int HMJL(String a,String b){ 116 char[] FW1 = a.toCharArray();//将a每一位都存入数组中 117 char[] FW2 = b.toCharArray();//将b每一位都存入数组中 118 int haiming=0; 119 if(FW1.length == FW2.length){//确保a和b的位数是相同的 120 for(int i=0;i<FW1.length;i++){ 121 if(FW1[i] != FW2[i])//如果该位不同,海明距离加1 122 haiming++; 123 } 124 } 125 return haiming; 126 } 127 128 public static void main(String[] args) { 129 String a5 = First_FC(s5); 130 String a6 = First_FC(s6); 131 System.out.println("【s5】的SimHash值为:"+a5); 132 System.out.println("【s6】的SimHash值为:"+a6); 133 System.out.println("【s5】和【s6】的海明距离为:"+HMJL(a5,a6)+",相似度为:"+(100-HMJL(a5,a6)*100/128)+"%"); 134 135 String a9 = First_FC(s9); 136 String a10 = First_FC(s10); 137 System.out.println("【s9】的SimHash值为:"+a9); 138 System.out.println("【s10】的SimHash值为:"+a10); 139 System.out.println("【s9】和【s10】的海明距离为:"+HMJL(a9,a10)+",相似度为:"+(100-HMJL(a9,a10)*100/128)+"%"); 140 } 141 }
3 测试结果
3.1 第一个测试
测试时使用关于SimHash算法的实现及测试V2.0中的S5和S6字符串(因为这两个字符串相对较长),将这两个字符串写入“E:/SimHash文档库/对比文档5”和“E:/SimHash文档库/对比文档6”两个txt文件中,主要是为了测试改动后的程序与之前的程序结果是否一致。
- E:/SimHash文档库/对比文档5:电视剧/小时代/由/郭敬明/的/同名/小说/改编/而/成/故事/以/经济/飞速/发展/的/上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照
- E:/SimHash文档库/对比文档6:电视剧/大时代/由/郭敬明/的/同名/小说/改编/而/成/该剧情/以/经济/飞速/发展/的/大上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照
未降维时两个文档的SimHash值为(太长,截取了一部分):
降维后的SimHash值为(太长,截取了一部分):
计算两个文档之间的海明距离:
结果与关于SimHash算法的实现及测试V2.0中的测试结果完全相同,说明程序改动过之后没有错误出现。
注:最开始测试的时候其实是有错误的,原因是在进行读取文件时字符串初始化为null,导致出错。
3.2 第二个测试
考虑到多段文本的相似度检测,故从网上摘抄了两段新闻,稍加改动后写入“E:/SimHash文档库/原始文档9”和“E:/SimHash文档库/原始文档10”两个txt文件中,利用利用IK Analyzer分词(txt输入输出)分词程序对文件分词,输出“E:/SimHash文档库/对比文档9”和“E:/SimHash文档库/对比文档10”两个txt文件,这两个文件的内容分别为:
- E:/SimHash文档库/对比文档9:第一代/蝙蝠侠/去世/去世/前/和/白血病/对/抗了/一段时间/据/台湾/报道/美国/男星/亚当/威斯特/adam/west/在/美国/时间/9日/惊闻/逝世/享年/88岁/发言/人在/10日/出面/证实/此事/并且/称/他/去世/前/和/白血病/对/抗了/一段时间/但/最终/宣告/不治/在/洛杉矶/的/家中/过世/<!--这里有一个分段符-->根据/报道/称/亚当/威斯特/的/子女/也/发声明/指出/我们/的/爸爸/一直/将/自己/视为/可以/带给/粉丝/正/能量/的/光明/骑士/他/永远/的/我们/的/英雄/据悉/他/是/在/洛杉矶/的/家中/过世/当时/所有/的/家人/都/陪/在他/的/身边/其中/包括/他/的/妻子/以及/6名/子女/5名/孙子/以及/2名/曾孙/
- E:/SimHash文档库/对比文档10:第一代/蝙蝠侠/去世/去世/前/和/白血病/对/抗了/很长/时间/据/台湾/报道/美国/男星/亚当/威斯特/adam/west/在/美国/时间/9日/惊闻/逝世/享年/88岁/发言/人在/10日/出面/证实/此事/并/声称/他/去世/前/和/白血病/对/抗了/一段时间/但/最终/宣告/不治/在/洛杉矶/的/家中/过世/<!--这里有一个分段符-->根据/报道/称/亚当/威斯特/的/子女/也/发声明/指出/我们/的/爸爸/一直/将/自己/视为/可以/带给/粉丝/正/能量/的/光明/骑士/他/永远/的/我们/的/英雄/据悉/他/是/在/洛杉矶/的/家中/过世/当时/所有/的/家人/都/陪伴/他/的/身边/其中/包括/他/的/妻子/以及/6名/子女/5名/孙子/以及/2名/曾孙/
未降维时两个文档的SimHash值为(太长,截取了一部分):
降维后的SimHash值为(太长,截取了一部分):
计算两个文档之间的海明距离:
结果表明,测试结果与人眼直观观测的结果相同。
作者:祁俊辉
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。