第一次個人編程作業——論文查重
林榮勝 031802318
一、作業提交與作業鏈接
二、計算模塊接口的設計與實現過程
1)余弦相似度
- 流程圖
- 接下來以一個簡單的例子來講解如何實現
txt0 = '我在福州'
txt1 = '福州大學有計算機學院'
txt2 = '福州大學在有計算機專業'
txt3 = '福州大學有很多專業'
test = '我在福州大學上計算機專業'
all_txt = []
all_txt.append(txt0)
all_txt.append(txt1)
all_txt.append(txt2)
all_txt.append(txt3)
all_txt_list = []
for txt in all_txt:
#jieba分詞
txt_list = [word for word in jieba.cut(txt)]
all_txt_list.append(txt_list)
#jieba分詞
test_list = [word for word in jieba.cut(test)]
- 調用jieba庫進行了中文詞語的分割,print一下all_txt_list和test_list,分詞的效果很直觀
[['我', '在', '福州'], ['福州大學', '有', '計算機', '學院'], ['福州大學', '在', '有', '計算機專業'], ['福州大學', '有', '很多', '專業']]
['我', '在', '福州大學', '上', '計算機專業']
- 接下來建立詞袋模型和TF-IDF模型
#基於上面的四句語句建立詞典,每個詞對應不同的特征數
dictionary = corpora.Dictionary(all_txt_list)
print(dictionary.token2id)
#基於詞典,建立稀疏向量集,即語料庫
corpus = [dictionary.doc2bow(txt) for txt in all_txt_list]
print(corpus)
#建立測試語句向量
txt_test_vec = dictionary.doc2bow(test_list)
print(txt_test_vec)
#建立TF-IDF模型,處理稀疏向量集
tfidf = models.TfidfModel(corpus)
- 三個print的結果為
{'在': 0, '我': 1, '福州': 2, '學院': 3, '有': 4, '福州大學': 5, '計算機': 6, '計算機專業': 7, '專業': 8, '很多': 9}
[[(0, 1), (1, 1), (2, 1)], [(3, 1), (4, 1), (5, 1), (6, 1)], [(0, 1), (4, 1), (5, 1), (7, 1)], [(4, 1), (5, 1), (8, 1), (9, 1)]]
[(0, 1), (1, 1), (5, 1), (7, 1)]
- 稀疏矩陣相似度計算
#對稀疏向量建立索引
index = similarities.SparseMatrixSimilarity(tfidf[corpus], num_features=len(dictionary.keys()))
#進行相似度的計算
sim = index[tfidf[txt_test_vec]]
print(sim)
- 得到最終四句話與測試語句的相似度
[0.5503141 0.01968957 0.73873365 0.01968957]
- 至於相似度如何計算,查了一下官網的API,只說明了采用余弦相似度,但與自己計算出來的cos結果總有略微的差別,可能API使用了什么黑科技吧,以后查到了原因再補充,這里先給出余弦計算公式
基於上述的例子,對此次的論文測試樣例進行編寫代碼測試
- 由於論文中包含大量的標點符號、數字、空格、換行等,所以先進行數據預處理,將這些非中文的字符全部刪掉,得到結果如下(其中myself_test.txt是關於毛概的,myself_test2.txt是關於計算機導論的)
- 從結果來看,對於改論文的抄襲論文,該算法都以接近1的相似度來判斷,對於我自己做的兩個測試集,也有60%+的相似度。這很明顯,該算法計算的相似度不切合實際的文本相似度。兩個與《活着》毫不相關的文本,相似度自然不可能達到60%+!
- 找了找網上的代碼,發現很多文本處理,在預處理階段,都進行了停用詞的刪除。不過,作業要求不能讀取其他文件,那就自己先測試測試,看看有沒有用……(停用詞表可以從我的Github上下載,還包括了中文停用詞表、百度停用詞表、四川大學停用詞表,點擊這里下載)
#進行停用詞的刪除也很簡單,循環一下
stop_words = open('hit_stopwords.txt', 'r', encoding='utf-8').read()
temp = [word for word in jieba.cut(file)]
result = []
for item in temp:
if item not in stop_words:
result.append(item)
-
再看看測試結果,只能說停用詞表牛逼,確實有用。也就是說,如果能在停用詞表的基礎上,再增加中文詞庫中一些對判斷文本相似度沒用的詞語,比如他、今天、明天、后天等,這些詞都不在哈工大停用詞表中。當然,增加停用詞表只能夠適用於大文本的相似度檢測,對於上面舉的例子語句,經過分詞后再刪除停用詞,很可能都把字典或者語料庫刪光了。
-
因為不能讀取文件,所以上面的方法就pass掉了,重新找一個度量的算法,於是乎,查到了篇博客,講的很好參考博客鏈接。在這篇博客的啟發下,決定選取Jaccard相似度
2)Jaccadr相似度
- 傑卡德相似度,嚴格來說應該叫傑卡德系數(百度百科上面這樣寫的)。從定義的角度上來說,就是A與B交集的大小與A與B並集的大小的比值,用於比較有限樣本集之間的相似性與差異性,系數值越高,文本相似度越高
- Jaccadr系數公式
- 針對文本相似度這一應用場景,選擇算法基於廣義的Jaccadr系數公式
- 流程圖
- 同樣的,再次以一個簡單的例子來講解該算法
txt0 = '我在福州大學'
txt1 = '我在數計學院'
#對txt0和txt1進行簡單的空格分詞,轉換為TF矩陣為
['在', '大', '學', '州', '我', '數', '福', '計', '院']
#對於TF矩陣,txt0和txt1對應的向量分別為
[[1 1 1 1 1 0 1 0 0]
[1 0 1 0 1 1 0 1 1]]
#根據廣義的Jaccadr相似度計算
a = 數組每一列的最小值相加 = 1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 = 3
b = 數組每一列的最大值相加 = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 9
result = a / b = 0.3333333
#文本相似度為0.3333333
-
對於論文的查重,當然不能簡單進行地進行空格分詞,於是加入jieba,並進行測試,得到結果如下。確實,該方法比余弦相似度得出的結果更具有可信度
-
再試試加入停用詞,相比之下,我認為加入停用詞的測試結果更貼合實際,相似度都有所減小。有的抄襲論文測試點相似度只有0.41、0.43等,但由於算法中是使用jieba分詞,這些測試點是在原文基礎上進行的字符串變化,因而有很多詞語不是正常地被分詞(實際生活中的抄襲論文應該也不會像測試點這么特殊,大體上詞序是正確的,能正常地分詞)。對於我自己生成的無關測試點,測試的結果就很不錯。
- 所有方法的源代碼將會放在Github中,其中作業提交代碼為main.py,采用傑卡德無停用詞算法。
三、計算模塊接口部分的性能改進
1)文本相似度
- 兩種算法的具體實現和最終計算結果在前面已經展示過了。在我心中的認可度為:傑卡德+停用詞 > 傑卡德 > 余弦+停用詞 > 余弦
2)時間花費
- 為了方便,我將主要步驟放在同一個函數中,使用line_profiler比較傑卡德與余弦兩種算法的時間花費(均未加停用詞)
- 余弦算法時間花費,總時間為0.925s,花費最多時間的步驟為jieba分詞
- 傑卡德算法時間花費,總時間約為0.67s,花費最多時間的步驟為字符串拼接
從結果可以看出傑卡德更快!
3)內存占用
- 同樣地,將算法主要步驟放在同一個函數中,使用memory_profiler比較兩種算法的內存占用(均未加停用詞)
- 余弦內存占用169.5MiB(MiB略大於MB,為2的20次方B)
- 傑卡德內存占用167.965MiB
從結果可以看出傑卡德占用內存更少!
四、計算模塊部分單元測試展示
1)測試代碼
import unittest
from cosine import CosSimilarity
from jaccard import JaccardSimilarity
from diy_exception import NoWordError, SimIsOneError, DeleteAllWordsError
file = open('orig.txt', 'r', encoding='utf-8').read()
test = {}
test['orig_0.8_add.txt'] = open('orig_0.8_add.txt', 'r', encoding='utf-8').read()
test['orig_0.8_del.txt'] = open('orig_0.8_del.txt', 'r', encoding='utf-8').read()
test['orig_0.8_dis_1.txt'] = open('orig_0.8_dis_1.txt', 'r', encoding='utf-8').read()
test['orig_0.8_dis_3.txt'] = open('orig_0.8_dis_3.txt', 'r', encoding='utf-8').read()
test['orig_0.8_dis_7.txt'] = open('orig_0.8_dis_7.txt', 'r', encoding='utf-8').read()
test['orig_0.8_dis_10.txt'] = open('orig_0.8_dis_10.txt', 'r', encoding='utf-8').read()
test['orig_0.8_dis_15.txt'] = open('orig_0.8_dis_15.txt', 'r', encoding='utf-8').read()
test['orig_0.8_mix.txt'] = open('orig_0.8_mix.txt', 'r', encoding='utf-8').read()
test['orig_0.8_rep.txt'] = open('orig_0.8_rep.txt', 'r', encoding='utf-8').read()
test['myself_test.txt'] = open('myself_test.txt', 'r', encoding='utf-8').read()
test['myself_test2.txt'] = open('myself_test2.txt', 'r', encoding='utf-8').read()
# 同時測試兩種算法對於測試樣例計算的相似度
class Testsim(unittest.TestCase):
def test_cosine(self):
cos = CosSimilarity()
print('開始測試cosine!')
print('-------------------------------------------')
for key in test.keys():
result = cos.cos_similarity(file, test[key])
print('測試樣本為:%s,相似度為:%.2f' % (key, result))
print('-------------------------------------------')
print('cosine測試結束!')
def test_jaccard(self):
jac = JaccardSimilarity()
print('開始測試jaccard!')
print('-------------------------------------------')
for key in test.keys():
result = jac.jaccard_similarity(file, test[key])
print('測試樣本為:%s,相似度為:%.2f' % (key, result))
print('-------------------------------------------')
print('jaccard測試結束!')
if __name__ == '__main__':
unittest.main()
2)代碼覆蓋率截圖
3)測試數據的思路
- 此次測試了11個數據樣本,其中9個為作業提供的抄襲論文樣本,另外2個為與論文不相關的文本。在此次測試中,並沒有加入停用詞表,如果想要測試加入停用詞表的結果,可以將我上傳到Github的代碼的停用詞表相關注釋解注釋。同時,此次測試均設置不拋出異常
五、計算模塊部分異常處理說明
1)自定義異常代碼
class NoWordError(Exception):
def __init__(self):
print('該文件沒有文字!')
class SimIsOneError(Exception):
def __init__(self):
print('兩個文件是不同的文件!相似度不可能為1!')
class DeleteAllWordsError(Exception):
def __init__(self):
print('該文件被你在刪除停用詞的時候都刪光了!')
2)異常解釋以及各個異常測試樣例
1、NoWordError
- 該異常為讀入文件沒有任何字符,即空文件時,報出的異常
- 異常的測試樣例設置為myself_test3.txt,測試結果如下
2、SimIsOneError
- 該異常為兩個不是相同的文件,相似度卻為1時,報出的異常
- 異常的測試樣例設置為orig_0.8_add.txt(其實使用余弦相似度計算該樣本結果時,結果並不為1,但是結果與1十分接近,保留2位小數時,采取四舍五入后,計算結果記為1),測試結果如下
3、DeleteAllWordError
- 該異常為在刪除停用詞后,文本為空時,報出的異常
- 異常的測試樣例設置為myself_test4_txt,測試結果如下
六、PSP表格分析
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 60 | 60 |
Estimate | 估計這個任務需要多少時間 | 60 | 30 |
Development | 開發 | 600 | 900 |
Analysis | 需求分析(包括學習新技術) | 120 | 180 |
Design Spec | 生成設計文檔 | 60 | 120 |
Design Review | 設計復審 | 20 | 20 |
Coding Standard | 代碼規范( 為目前的開發制定合適的規范) | 20 | 10 |
Design | 具體設計 | 60 | 60 |
Coding | 具體編碼 | 40 | 100 |
Code Review | 代碼復審 | 30 | 30 |
Test | 測試(自我測試,修改代碼,提交修改) | 60 | 200 |
Reporting | 報告 | 150 | 300 |
Test Repor | 測試報告 | 30 | 60 |
Size Measurement | 計算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后總結,並提出過程改進計划 | 30 | 40 |
合計 | 1350 | 2120 |