關於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  輸出結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM