第一次個人編程作業:論文查重
-
github項目鏈接:https://github.com/blacksheep107/041803101
1.計算模塊接口的設計與實現過程
-
類:
1.readToString 包含方法 String readToString(String fileName) 讀入文件名,把文件轉為字符串。 2.getCos 包含方法 double getCos(String s1, String s2) 計算兩個字符串的余弦值。 3.writeToAnswer 包含方法 void writeIntoAnswer(double sum,String ans) 將結果寫入ans文件。
-
流程圖
-
實現過程
大致的過程在類中已經說明,這里主要寫一下核心算法——余弦相似性的應用。
先貼上參考資料: TF-IDF與余弦相似性的應用
資料中是利用詞頻計算余弦相似性,這里不再贅述。講講踩的坑,我先嘗試用字頻方法寫了一下,結果相當不理想:只有rep的結果接近0.8,其他大部分都在0.98左右,dis的幾個文件結果甚至達到1。打開文件一看發現dis文本基本就是原文換個順序或者加個回車(順便一提一開始寫的是對比每行的重復率,發現dis文本的重復率都奇低,因為中間插了回車= =直接拉胯)。到這里我想着用詞頻判斷會不會好一點,於是又把字換成詞(感謝安利我hanlp的hxd)。but,情況沒有改善……我冷靜下來仔細想想,這整個文本去掉標點,還不計順序,詞頻和字頻的區別也確實不大……
總之待在坑里我想到另一個方法,原理還是算余弦,這次把所有詞的出現位置用Map<String,Vector<Integer> >記錄下來,於是每個詞在兩個文本中都對應有一個向量,也就是獲得了這個詞在兩個文本中的位置向量,接下來可以計算它們的余弦值了。 -
例:
句子A:普通的DISCO我們普通的搖 句子B:旁邊普通的路人在普通的瞧
如何計算以上兩個句子的余弦值?
-
第一步:分詞
在maven中配置hanlp,網上能搜到教程就不多說了,以及這里也踩到坑了,jdk版本太高居然還會配置失敗……附個pom.xml文件的圖吧
搞半天終於成功了,淚目。
-
第二步:列出所有詞,存入它們出現的位置,注意只判斷漢字
句子A:普通0 的1 我們2 普通3 的4 搖5
句子B:旁邊0 普通1 的2 路人3 在4 普通5 的6 瞧7 -
第三步:得到兩個文本中所有詞的位置向量
舉個栗子:
普通:句子A:[0,3] 句子B:[1,5]
- 第四步:計算兩個文本中“普通”的余弦值:cosθ=\(\frac {0×1+3×5}{\sqrt {0+3^2}×\sqrt {1^2+5^2}}\)≈0.98
余弦值可以表示兩個向量之間的夾角大小,夾角越小,也就是余弦值越大,兩個向量就越相似。(只是我這個不是二維的向量,其實我也不知道能不能叫向量……
使用TreeMap存儲詞位置向量也是因為要確保是兩個文本中相同的詞對比,如果某個詞只在一個文本中出現就不會被計算到。
- 第五步:計算所有詞的余弦值,同時記錄詞數。所有余弦值相加除以詞數就是最終結果了。
結果總算能看了。
2.計算模塊接口部分的性能改進
主要的改進思路都在實現過程↑里說了,也就是把詞頻向量改成詞位置向量。如果沒用詞位置而是字位置應該會更慢,也更耗內存。少踩了一個坑導致現在不知道性能改進怎么寫。
- 效能工具用了預培訓文檔推薦的JProfiler。
性能分析
- Overview
- Live memory
畢竟每個詞都要開兩個Vector
- CPU views
沒想到是matches消耗最大。主要是用來判斷漢字了,也就是兩個文本的每個字符都要判斷一遍……這樣想想也挺正常,但是這個好像無法避免啊……
3.計算模塊部分單元測試展示
新增了重復率0和1的測試,其他幾個用的是作業提供的樣例。主要測試getCos方法。
- 測試代碼
import org.junit.Assert;
import static org.junit.Assert.*;
public class mainTest {
@org.junit.Test
public void origAndOrig() {
String s1=readToString.readToString("testfile/orig.txt");
String s2=readToString.readToString("testfile/orig.txt");
double sum=getCos.getCos(s1,s2);
Assert.assertEquals(1.0,sum,0);
}
@org.junit.Test
public void completeDiff(){
String s1="乘風破浪會有時";
String s2="直掛雲帆濟滄海";
double sum=getCos.getCos(s1,s2);
Assert.assertEquals(0,sum,0);
}
@org.junit.Test
public void origAndAdd() {
String s1=readToString.readToString("testfile/orig.txt");
String s2=readToString.readToString("testfile/orig_0.8_add.txt");
double sum=getCos.getCos(s1,s2);
Assert.assertEquals(0.8,sum,0.2);
}
@org.junit.Test
public void origAndDel() {
String s1=readToString.readToString("testfile/orig.txt");
String s2=readToString.readToString("testfile/orig_0.8_del.txt");
double sum=getCos.getCos(s1,s2);
Assert.assertEquals(0.8,sum,0.2);
}
@org.junit.Test
public void origAndMix() {
String s1=readToString.readToString("testfile/orig.txt");
String s2=readToString.readToString("testfile/orig_0.8_mix.txt");
double sum=getCos.getCos(s1,s2);
Assert.assertEquals(0.8,sum,0.2);
}
后面的幾個代碼大同小異,就是改了個文件名,這里不放了。
- 測試結果
- 測試覆蓋率
核心方法getCos覆蓋率為100%,readIntoString沒覆蓋到的都是catch塊。
4.計算模塊部分異常處理說明
- 空文本異常
//測試
try{
throw new EmptyTextException("Empty Text!");
}catch(EmptyTextException e){
e.printStackTrace();
}
public class EmptyTextException extends Exception{
public EmptyTextException(){
super();
}
public EmptyTextException(String message){
super(message);
}
public EmptyTextException(String message,Throwable cause){
super(message,cause);
}
public EmptyTextException(Throwable cause){
super(cause);
}
}
- 測試結果
5.PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 120 | 100 |
· Estimate | · 估計這個任務需要多少時間 | 60 | 60 |
Development | 開發 | 420 | 1440 |
Analysis | · 需求分析 (包括學習新技術) | 60 | 360 |
· Design Spec | · 生成設計文檔 | 60 | 120 |
· Design Review | · 設計復審 | 20 | 20 |
· Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 20 | 20 |
· Design | · 具體設計 | 60 | 45 |
· Coding | · 具體編碼 | 180 | 300 |
· Code Review | · 代碼復審 | 30 | 15 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 60 |
Reporting | 報告 | 60 | 90 |
· Test Report | · 測試報告 | 40 | 30 |
· Size Measurement | · 計算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后總結, 並提出過程改進計划 | 30 | 45 |
· 合計 | 1230 | 2715 |
6.遇到的問題
-
問題1
創建測試的方法培訓文檔里講的有點模糊,點擊類名(我找了半天Solve在哪,原來是就是它的類名……),alt+enter就可以創建測試了。
-
問題2
照着預培訓文檔里面寫,結果編譯器報不能比較浮點數……? -
解決方法:應該用assertEquals(expected, actual, delta) 比較浮點數大小,只要期望值和聲明值之間的差值小等於delta,那么就斷言相等。Math.abs(expected - actual) <= delta
這樣寫就沒問題了
-
問題3
導jar包是最費勁的……好幾次導出來結果命令窗口中執行出錯。
提示沒有主清單解決方法:用WinRAR打開jar包,找到這個文件
打開添加Main-Class: main,注意冒號后有空格。
還有這個問題
至今沒明白具體原因,搜索到的答案基本都是說路徑配置出錯,然而我編譯器里就運行的好好的……這種情況一般是導出的時候沒設置好,就猜+搜+搞,莫名其妙就行了。
7.總結
我變禿了,也變強了
- 第一次用jprofiler,第一次用git,第一次配置maven……學到了很多東西,主要是增強了查資料的能力……整個過程走下來寫代碼反而是最輕松的,學習新東西是最難的,單一個git的用法就研究了一下午。找到的教程總是走到一半就出問題,然后又去百度解決方法,怎么導jar包,怎么配置maven,怎么用git push,於是瀏覽器常常掛着好幾個窗口。在過程中也意識到自己跟別人的差距,在別人眼里可能是常識的東西我得搞幾小時,好在最后都順利解決了。
- 編碼過程中同樣遇到了一些困難,因為Java語言還不太熟練,寫代碼的時候下意識的想用C的東西,有時候寫到一半開始面向百度編程。比如存詞向量的存儲,開始寫了個map<string,vector
>offset1,編譯器直接報錯,在編譯器的提示下蒙出了正確寫法Map<String, Vector > Offset1=new TreeMap<String,Vector >();
……害,積累的東西太少,現在開始吃虧了,好在這也是一個積累的過程,今后的學習和工作中總會派上用場吧。
“你的負擔將變成禮物,你受的苦將照亮你的路。”