关于SimHash算法的实现及测试V4.0

@祁俊辉,2017年6月15日测试。

1  说明

  • 本程序衔接关于SimHash算法的实现及测试V3.0
  • 改进1:增加TF-IDF算法,用于计算词权重(本地新增100篇txt文本库);
  • 改进2:各个程序衔接,详情见流程图。

2  程序

目前项目中存在4个类,分别是分词“FenCi”,计算某个词在多少个文档中出现过“TxtComparison”,计算TF-IDF值“TF_IDF”,计算SimHash值及相似度比较“SimHash128Txt”。

2.1  FenCi

 1 /* 【函数作用】对指定txt文档分词,存入另一文档中
 2  * 【说明】1.该程序采用IK分词包(前向)进行分词
 3  * 【时间】祁俊辉->2017.6.9
 4  * */
 5 import java.io.*;
 6 
 7 import org.apache.lucene.analysis.Analyzer;
 8 import org.apache.lucene.analysis.TokenStream;
 9 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
10 import org.wltea.analyzer.lucene.IKAnalyzer;
11 
12 public class FenCi {
13     static String txt_ys = "./SimHash文档库/原始文档9.txt";
14     static String txt_fch = "./SimHash文档库/分词后文档9.txt";
15     static String txt_zh = "./SimHash文档库/最终文档9.txt";
16     
17     //输入字符串,输出分词后的字符串
18     public static String Seg(String sentence) throws IOException {
19         String text="";
20         //创建分词对象
21         Analyzer anal=new IKAnalyzer(true);
22         StringReader reader=new StringReader(sentence);
23         //分词
24         TokenStream ts=anal.tokenStream("", reader);
25         CharTermAttribute term=ts.getAttribute(CharTermAttribute.class);
26         //遍历分词数据
27         while(ts.incrementToken()){
28             text+=term.toString()+"/";
29         }
30         reader.close();
31         anal.close();
32         return text.trim()+"\n";
33     }
34 
35     public static void main(String[] args) {
36         File file_ys = new File(FenCi.txt_ys);//原始文件
37         File file_fch = new File(FenCi.txt_fch);//分词后文件
38         try {
39             FileReader fr = new FileReader(file_ys);
40             BufferedReader bufr = new BufferedReader(fr);
41             FileWriter fw = new FileWriter(file_fch);
42             BufferedWriter bufw = new BufferedWriter(fw);
43             String s = null;
44             int i = 0;
45             while((s = bufr.readLine()) != null) {
46                 i++;
47                 String s_fch = FenCi.Seg(s);
48                 System.out.println("第"+i+"行的原始数据:");
49                 System.out.println(s);
50                 System.out.println("第"+i+"行分词后的结果:");
51                 System.out.println(s_fch);
52                 bufw.write(s_fch);
53                 bufw.newLine();
54             }
55             bufr.close();
56             fr.close();
57             bufw.close();
58             fw.close();
59             System.out.println("文件处理完毕,已生成该文档的分词后文档!");
60         } catch (Exception e) {
61             e.printStackTrace();
62         }
63     }
64 }

2.2  TxtComparison

 1 /* 【函数作用】判断str字符串在多少个文档中出现过
 2  * 【说明】1.目前文件库有100篇文档,查找总耗时小于1秒
 3  * 【说明】2.调用时,用"TxtComparison.Txt(str)",返回值为(出现次数+1)
 4  * 【说明】3.之所以加1,是因为如果该词在文件库没有出现,则计算IDF值时会出错
 5  * 【时间】祁俊辉->2017.6.14
 6  * */
 7 import java.io.*;
 8 
 9 public class TxtComparison {
10     public static int Txt(String str) {
11         int text_num = 0;//定义总出现次数
12         for(int i=0; i<100; i++){//遍历100篇文档
13             String filename = "./SimHash文档库/文件库/参考文档";
14             filename += (i+".txt");//组合定义文件路径及文件名
15             File file_name = new File(filename);//创建文件路径
16             try {
17                 InputStreamReader isr = new InputStreamReader(new FileInputStream(file_name),"UTF-8");
18                 //编码方式UTF-8,防止中文乱码
19                 BufferedReader bufr = new BufferedReader(isr);
20                 String s = null;//临时存储每行数据
21                 boolean num = false;//临时存储目标文档是否含有指定字符串
22                 while((s = bufr.readLine()) != null) {//遍历目标文档每行
23                     //System.out.println(s);
24                     if(s.contains(str)){//如果含有指定字符串
25                         //System.out.println("第" + i + "篇文章中有");
26                         num = true;//标志位赋值
27                         break;//退出(节省时间)
28                     }
29                 }
30                 bufr.close();
31                 isr.close();
32                 if(num) {//根据标志位状态,让总出现次数增加
33                     text_num ++;
34                 }
35             } catch (Exception e) {
36                 e.printStackTrace();
37             }
38         }
39         return text_num+1;//返回总出现次数+1
40     }
41 }

2.3  TF_IDF

 1 /* 【函数作用】计算分词后的文档各词的IF-IDF值
 2  * 【说明】1.目前文件库有100篇文档,查找总耗时小于1秒
 3  * 【说明】2.调用时,用"TxtComparison.Txt(str)",返回值为(出现次数+1)
 4  * 【说明】3.之所以加1,是因为如果该词在文件库没有出现,则计算IDF值时会出错
 5  * 【时间】祁俊辉->2017.6.14
 6  * */
 7 import java.io.BufferedWriter;
 8 import java.io.File;
 9 import java.io.FileWriter;
10 import java.io.IOException;
11 
12 public class TF_IDF {
13     
14     public static void main(String[] args) throws IOException {
15         //生成的最终文件
16         File file_zh = new File(FenCi.txt_zh);//最终文件
17         FileWriter fw = new FileWriter(file_zh);
18         BufferedWriter bufw = new BufferedWriter(fw);
19         //对分词后的文档进行处理
20         String s = SimHash128Txt.Txt_read(FenCi.txt_fch);
21         String[] WordArray = s.split("/");
22         
23         for(int i=0; i<WordArray.length; i++){//遍历每个词
24             /*第一步:计算TF值*/
25             int TF_Fre = 0;//定义频率为0
26             //计算每个词的频率
27             for(int j=0; j<WordArray.length; j++){
28                 if(WordArray[i].equals(WordArray[j])){
29                     TF_Fre++;
30                 }
31             }
32             //频率归一化
33             float TF = (float) (1.0*TF_Fre/WordArray.length);
34             //System.out.println(WordArray[i]+"的TF值为"+TF);
35             /*第二步:计算IDF值*/
36             //计算每个词在文件库出现的文件数
37             int IDF_a = TxtComparison.Txt(WordArray[i]);
38             //System.out.println(WordArray[i]+"在文件库中出现了"+IDF_a);
39             //总文件数  除以  该词在文件库中出现的次数
40             float IDF_b = (float) (101.0/IDF_a);
41             //System.out.println(IDF_b);
42             //IDF = log 2 (IDF_b)
43             float IDF = (float) (Math.log(IDF_b)/Math.log(2));
44             //System.out.println(WordArray[i]+"的IDF值为"+IDF);
45             /*第三步:计算TF-IDF值*/
46             float TFIDF = TF * IDF;
47             System.out.println(WordArray[i]+"的TF-IDF值为"+TFIDF);
48             /*第四步:将计算结果写入txt文件*/
49             bufw.write(WordArray[i] + "##" + TFIDF + "/");
50             bufw.newLine();
51         }
52         bufw.close();
53         fw.close();
54         System.out.println("文件处理完毕,已生成该文档的最终文档!");
55     }
56 }

2.4  SimHash128Txt

  1 /* 【算法】SimHash->128位
  2  * 【说明】1.加上权重(TF-IDF值)
  3  * 【说明】2.经IK分词后,将分词后的文件存储,该程序进行读取
  4  * 【时间】祁俊辉->2017.6.15
  5  * */
  6 
  7 import java.io.*;
  8 import java.math.BigInteger;
  9 import java.security.MessageDigest;
 10 
 11 public class SimHash128Txt {
 12     //以下两个文档为新增文档,两段话,检测相似度
 13     static String s9 = SimHash128Txt.Txt_read("./SimHash文档库/最终文档9.txt");
 14     static String s10 = SimHash128Txt.Txt_read("./SimHash文档库/最终文档10.txt");
 15     /* 函数名:Txt_read(String name)
 16      * 功能:读取name文件的内容,name为txt文件名
 17      * */
 18     static String Txt_read(String name){
 19         String s = "";
 20         File file = new File(name);//原始文件
 21         try {
 22             FileReader fr = new FileReader(file);
 23             BufferedReader bufr = new BufferedReader(fr);
 24             String s_x = null;
 25             //int i = 0;
 26             while((s_x = bufr.readLine()) != null) {
 27                 //i++;
 28                 //System.out.println("第"+i+"行的原始数据:");
 29                 s += s_x;
 30             }
 31             bufr.close();
 32             fr.close();
 33         } catch (Exception e) {
 34             e.printStackTrace();
 35         }
 36         return s;
 37     }
 38     /* 函数名:MD5_Hash(String str)
 39      * 功能:计算字符串str的128位hash值,并将其以String型返回
 40      * */
 41     static String MD5_Hash(String str){
 42         try{
 43             // 生成一个MD5加密计算摘要
 44             MessageDigest md = MessageDigest.getInstance("MD5");
 45             // 计算md5函数
 46             //System.out.println("字符串:"+str);
 47             //System.out.println("字符串的MD5_Hash:"+md.digest(str.getBytes()));
 48             // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
 49             // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
 50             return new BigInteger(1,md.digest(str.getBytes("UTF-8"))).toString(2);
 51         }catch(Exception e){
 52             e.printStackTrace();
 53             return str;
 54         }
 55     }
 56     /* 函数名:First_FC(String str)
 57      * 功能:1.先创建一个存储SimHash值的128数组,并初始化为0
 58      * 功能:2.将str字符串分词,并存入临时数组
 59      * 功能:3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中
 60      * 功能:4.将数组中的SimHash值降维,并以字符串形式返回
 61      * */
 62     static String First_FC(String str){
 63         //1.先创建一个存储SimHash值的128数组,并初始化为0
 64         float Hash_SZ[] = new float[128];
 65         for(int i=0;i<Hash_SZ.length;i++)
 66             Hash_SZ[i]=0;
 67         //2.将str字符串分词,并将词和权重存入临时数组
 68         String[] word_and_tfidf = str.split("/");//先分割
 69         String[] word = new String[word_and_tfidf.length];//创建一个词数组,大小为词个数
 70         String[] tfidf = new String[word_and_tfidf.length];//创建一个权重数组,大小为词个数
 71         //分割词,存入数组
 72         for(int i=0; i<word_and_tfidf.length; i++){
 73             String[] linshi = word_and_tfidf[i].split("##");
 74             word[i] = linshi[0];
 75             tfidf[i] = linshi[1];
 76             //System.out.println(word[i]+" "+tfidf[i]);
 77         }
 78         //3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中
 79         for(int i=0;i<word.length;i++){//循环传入字符串的每个词
 80             String str_hash = SimHash128Txt.MD5_Hash(word[i]);//先计算每一个词的Hash值(128位)
 81             //MD5哈希计算时,二进制转换若最高位为7以下,也就是转换成二进制最高位为0,会不存储该0,导致后面程序出错
 82             //这里主要是为了保证它是128位的二进制
 83             if(str_hash.length() < 128){
 84                 int que = 128 - str_hash.length();
 85                 for(int j=0;j<que;j++){
 86                     str_hash = "0" + str_hash;
 87                 }
 88             }
 89             //System.out.println(str_hash);//输出该词的128位MD5哈希值
 90             char str_hash_fb[]=str_hash.toCharArray();//将该词的哈希值转为数组,方便检查
 91             //对每个词的Hash值(128位)求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] += Float.parseFloat(tfidf[i]);//Hash_SZ中,0是最高位,依次排低
 95                 }else{
 96                     Hash_SZ[j] -= Float.parseFloat(tfidf[i]);
 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 a9 = SimHash128Txt.First_FC(s9);
130         String a10 = SimHash128Txt.First_FC(s10);
131         System.out.println("【s9】的SimHash值为:"+a9);
132         System.out.println("【s10】的SimHash值为:"+a10);
133         System.out.println("【s9】和【s10】的海明距离为:" + SimHash128Txt.HMJL(a9,a10) + ",相似度为:" + (100-SimHash128Txt.HMJL(a9,a10)*100/128)+"%");
134     }
135 }

3  测试结果

3.1  文件说明

100篇文档(现代小说):

生成的文档(1篇待比较文本有3个文档:原始、分词后、最终):

3.2  输出结果

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