关于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值为(太长,截取了一部分):

计算两个文档之间的海明距离:

结果表明,测试结果与人眼直观观测的结果相同。

posted @ 2018-02-14 20:40  祁俊辉  阅读(650)  评论(0编辑  收藏  举报