@祁俊輝,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個文檔:原始、分詞后、最終):