這個作業屬於哪個課程 | <班級鏈接> |
---|---|
這個作業要求在哪里 | <作業要求的鏈接> |
這個作業的目標 | 個人項目作業: 簡單論文查重算法 |
頁面導航
- GitHub鏈接
- 原理介紹
- 基本配置
- 算法實現
- 程序運行
- 模塊接口的性能展示
- 模塊部分單元測試展示
- 測試覆蓋率
- 測試文件異常
- 空文件
- 文件路徑異常
- PSP表格
GitHub鏈接
https://github.com/yzqctf/3119005483/tree/main/Final commit
原理介紹
相比於在NLP領域更為專業的查重算法,其考慮的方面更為細致更接近人們日常生活中對抄襲的認值;本篇所實現的查重算法是相對簡單的,主要是對兩個方法的實現:
- 基於simHash的海明距離 + Jaccard Similarity;
- 基於余弦相似度。
因為在大文本的度量中,相比於simHash,余弦相似度方案的性能表現更差;而在小文本的度量中,余弦相似度的准確率更好,因此針對不同應用場景應該采用不同的方案。
simHash + Jaccard Similarity
simHash
傳統的Hash算法只負責將原始內容盡量均勻隨機地映射為一個簽名值,原理上僅相當於偽隨機數產生算法。即便是兩個原始內容只相差一個字節,所產生的簽名也很可能差別很大,所以傳統的Hash是無法在簽名的維度上來衡量原內容的相似度。而SimHash本身屬於一種局部敏感hash,其主要思想是降維,將高維的特征向量轉化成一個f位的指紋(fingerprint),通過算出兩個指紋的海明距離(hamming distince)來確定兩篇文章的相似度,海明距離越小,相似度越低(根據 Detecting Near-Duplicates for Web Crawling 論文中所說),一般海明距離為3就代表兩篇文章相同。
simhash也有其局限性,在處理小於500字的短文本時,simhash的表現並不是很好,所以在使用simhash前一定要注意這個細節。
simHash算法分為5個步驟: 1. 分詞;2. hash;3. 加勸;4. 合並;5. 降維
-
分詞:現在有很多可供使用的包來進行文本的分詞,本篇所使用的分詞器是IKAnalysis,需要安裝 IKAnalyzer2012_u6.jar包,具體信息可看這篇[博客] (http://lxw1234.com/archives/2015/07/422.htm#10006-weixin-1-52626-6b3bffd01fdde4900130bc5a2751b6d1) 以及其相關介紹
-
hash:通過hash函數計算各個特征向量(這里為划分好的詞)的hash值,hash值為二進制數01組成的n-bit簽名。
-
加權:權重:就是詞頻;把第2步生成的hash值從左至右與權重進行運算;如果該bit的數值為1,則將權重賦給該位;如果該bit的數值為0,則將權重的負值賦給該位。example:"我",hash = 101011,weight(詞頻) = 5;則加權后的結果為:5 -5 5 -5 5 5;
-
合並:經過上述的三個步驟,我們可以得到全部詞(word)的加權hash值,此時需要將全部的加權后的hash值進行累加;
-
降維:將第四步計算出來的序列串變為01串;具體規則:如果該位的數值>0,則置為1;反之則置為0.
-
海明距離(Hamming Distance):在信息編碼中,兩個合法代碼對應位上編碼不同的位數稱為碼距,又稱海明距離;
Jaccard Similarity
給定兩個文本或兩句話,把兩句話中出現的單詞取交集和並集,交集和並集的大小之商即為Jaccard Similarity:
J(A, B) = |A∩B| / |A∪B|
A = [0, 1, 2, 5, 6, 8, 9]
B = [0, 2, 3, 4, 5, 7, 9]
|A∩B| = [0, 2, 5, 9] = 4;
|A∪B| = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 10;
所以 J(A, B) = 4 / 10 = 0.4
基於余弦相似度
余弦相似性通過測量兩個向量的夾角的余弦值來度量它們之間的相似性。0度角的余弦值是1,而其他任何角度的余弦值都不大於1;並且其最小值是-1。從而兩個向量之間的角度的余弦值確定兩個向量是否大致指向相同的方向。兩個向量有相同的指向時,余弦相似度的值為1;兩個向量夾角為90°時,余弦相似度的值為0;兩個向量指向完全相反的方向時,余弦相似度的值為-1。這結果是與向量的長度無關的,僅僅與向量的指向方向相關。余弦相似度通常用於正空間,因此給出的值為-1到1之間
具體流程:
(1)找出兩篇文章的關鍵詞;
(2)每篇文章各取出若干個關鍵詞,合並成一個集合,計算每篇文章對於這個集合中的詞的詞頻
(3)生成兩篇文章各自的詞頻向量;
(4)計算兩個向量的余弦相似度,值越大就表示越相似。
基本配置
簡單查重算法的實現並不是很困難,真正的難點反而在於如何處理在導入jar包后的“程序包不存在”,打包為jar包時“程序包不存在”(即便他運行是沒有問題的),命令行運行時的“找不到主類”諸如此類的配置問題。就我本次經歷而言,在這上面所花費的時間近乎80%。所以這里也是對經歷過的問題進行記錄,方便自己和別人遇到時進行方案選擇。(本篇基於maven開發)
- 導入jar包后“程序包不存在”:需要在自己的項目里添加libraries,並將jar包導入該文件夾下,然后rebuild整個project即可;具體可參考此博客
- 封裝jar包時“程序包不存在”:一般人問題可能出在這常見;小白:注意看自己的META-INF內是否存在自己所需要的包的路徑,如果沒有,就將這個META-INF包給刪除,重新進入File->project structure->Artifacts->jar->from modules中進行設置,記得添加啟動類即可。經過上述兩種措施,一般而言是可以完成基本問題的。至於網上一般建議的
mvn idea:idea
還是需要看具體情況。
算法實現
工程結構圖
其中main是程序的入口,也即啟動類;CalculateSimilar類是實現余弦相似度的類;
SimHash是將文本轉換為simhash值,HammingDistance計算了文本間的海明距離並完成了Jaccard Similarity的計算。
關鍵代碼
public static double getSimilarity(String simHash1, String simHash2){
//海明距離
int dis = getHammingDistance(simHash1,simHash2);
//System.out.println(dis);
//一般來說,海明距離小於3即可認為文本之間相似度高,
//使用Jaccard進行相似度計算, 因為dis就是simhash之間不同,也即差集的大小,所以
//交集
int intersection = simHash1.length()-dis;
//並集
int union = dis+simHash1.length();
double sim = 0.01*(100*intersection/union);
return sim;
}
int sumA = 0;
int sumB = 0;
int sumAB = 0;
for (int i=0;i < words1.size(); i++){
int a = FA.get(i);
int b = FB.get(i);
sumAB += a*b;
sumA += a*a;
sumB += b*b;
}
double A = Math.sqrt(sumA);
double B = Math.sqrt(sumB);
BigDecimal AB = BigDecimal.valueOf(A).multiply(BigDecimal.valueOf(B));
程序運行
運行結果
模塊接口的性能展示
內存消耗
從上圖可以知道,此程序運行時內存消耗最多的是IKAnalysis的分詞操作,其占 (13454+5664)/39947 = 47.86%
CPUf負載
堆內存消耗情況
模塊部分單元測試展示
由於測試單元過多,這里只展示其中比較重要的幾個單元測試:1. 空文件;2. 大文件與小文件測試;3. 路徑輸入錯誤;4. 參數異常錯誤;5. 同一文件
//測試代碼是否能正常運行,小文本:29K
public void main() throws IOException {
String f1 = "D:\\學習資料\\softwareProject\\orig.txt";
String f2 = "D:\\學習資料\\softwareProject\\orig_0.8_add.txt";
String f3 = "D:\\學習資料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
main.getAnalysisResult(f1,f2,f3);
}
//相對較大文本165K
public void testLargeTxt() throws IOException{
String f1 = "D:\\學習資料\\softwareProject\\測試文本\\orig_0.8_dis_15.txt";
String f2 = "D:\\學習資料\\softwareProject\\測試文本\\orig.txt";
String f3 = "D:\\學習資料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
main.getAnalysisResult(f1,f2,f3);
}
//測試文本為空的情況
public void testNullTxt() throws IOException{
String f1 = "D:\\學習資料\\softwareProject\\測試文本\\orig.txt";
String f2 = "D:\\學習資料\\softwareProject\\測試文本\\null.txt";
String f3 = "D:\\學習資料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
main.getAnalysisResult(f1,f2,f3);
}
//同一文件
public void testTheSame() throws IOException{
String f1 = "D:\\學習資料\\softwareProject\\測試文本\\orig.txt";
String f2 = "D:\\學習資料\\softwareProject\\測試文本\\orig.txt";
String f3 = "D:\\學習資料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
main.getAnalysisResult(f1,f2,f3);
}
//路徑有誤
public void testTheRoute() throws IOException{
String f1 = "D:\\學習資料\\softwareProject\\測試文本\\orig.txt";
String f2 = "D:\\softwareProject\\測試文本\\orig.txt";
String f3 = "D:\\學習資料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
main.getAnalysisResult(f1,f2,f3);
}
//輸入參數有誤
if (args.length>3){
System.out.println("一次只能比較兩個文本哦");
System.exit(1);
}else if (args.length<=0){
System.out.println("請輸入待比較文件");
System.exit(1);
}
代碼覆蓋率
啟動類main的method之所以沒有達到100%,是因為我用了兩個方式產生了分支,因此只有66%的覆蓋率。
使用codacy對代碼進行分析
codacy分析才是重中之重,雖然這個只能免費體驗七天,不過真的很強大。
可以看出來總共29個問題,潛在的隱患還是非常多的;
不出意料的是復雜度0%,還真是簡易查重算法[苦澀];代碼復制17%,可能一些比較公共的部分寫法比較格式化,17%應該算是重復度比較低的吧?也不清楚其判斷方式是什么。
進入安全界面
問題很多意味着改進的方式也很多,每一點細看的話將會學到很多東西,還需要繼續加強自己的代碼編寫能力。
PSP表
*PSP2.1* | *Personal Software Process Stages* | *預估耗時(分鍾)* | *實際耗時(分鍾)* |
---|---|---|---|
Planning | 計划 | 20 | 17 |
· Estimate | · 估計這個任務需要多少時間 | 120 | 70 |
Development | 開發 | 240 | 240 |
· Analysis | · 需求分析 (包括學習新技術) | 300 | 200 |
· Design Spec | · 生成設計文檔 | 30 | 15 |
· Design Review | · 設計復審 | 30 | 60 |
· Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 60 | 20 |
· Design | · 具體設計 | 30 | 30 |
· Coding | · 具體編碼 | 180 | 120 |
· Code Review | · 代碼復審 | 120 | 240 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 120 |
Reporting | 報告 | 60 | 60 |
· Test Repor | · 測試報告 | 60 | 40 |
· Size Measurement | · 計算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后總結, 並提出過程改進計划 | 30 | 120 |
· 合計 | 1370 | 1382 |
附注:對於PSP表其實不是很了解,比如說開發是指什么,跟下面的項目屬於什么關系,這個不是很清楚,所以在填的時候就是憑着感覺寫的,還有關於修改代碼以及提交修改這點,在本次實驗過程中執行的並不是很好,修改過的點不記得上傳,所以看似是只提交了一次,但是實際上在本地修改了幾遍,這點也不好。本次實驗的過程中最主要的認知是:項目開發過程中一些輔助的手段和項目的規范性一定要掌握,比如導包、配置依賴,git命令上傳等等一些不涉及到具體編碼但是跟具體開發息息相關的操作。磨刀不誤砍柴工嘛。