zoukankan      html  css  js  c++  java
  • 关于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()+"
    ";
    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  输出结果

  • 相关阅读:
    leetcode 673. 最长递增子序列的个数 java
    leetcode 148. 排序链表 java
    leetcode 98. 验证二叉搜索树 java
    leetcode 29. 两数相除 java
    leetcode 234. 回文链表 java
    Valid Palindrome LeetCode Java
    Single Number II LeetCode Java
    Single Number LeetCode java
    Search in Rotated Sorted Array II LeetCode Java
    Search in Rotated Sorted Array leetcode java
  • 原文地址:https://www.cnblogs.com/qijunhui/p/8448869.html
Copyright © 2011-2022 走看看