第一次個人編程作業


第一次個人編程作業——論文查重

林榮勝 031802318

一、作業提交與作業鏈接

二、計算模塊接口的設計與實現過程

1)余弦相似度

  • 流程圖
    流程圖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系數公式
  • 流程圖
    流程圖2
  • 同樣的,再次以一個簡單的例子來講解該算法
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

七、總結

經歷過這次作業才知道做項目不僅僅只是寫完代碼就完事了,還要考慮測試、異常、寫文檔,這些才是真正花時間最多的事情!這幾天也一直在修改代碼的規范,這才知道,規范的代碼和封裝好的代碼在測試的時候有多么方便!不能使用停用詞表還是蠻可惜的,總覺得用了它好像結果更真實,更符合實際。


免責聲明!

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



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