第一次個人編程作業


論文查重

orz只能說柯老板柯老師題目選的妙啊,直接開始遞歸式學習

  • 先附上作業github鏈接:GitHub鏈接
  • 論文查重程序是test.py,其他程序負責測試數據生成、單元測試等工作

使用語言以及代碼原理

看到題目的第一反應就是要去看NLP(Natural Language Processing 自然語言處理)相關的知識,既然要用到模型,那從個人角度,python相對寫的方便一些,因為可以直接安裝對應的庫,碼量上也會少一些

人生苦短,我用python

我遞歸式地看了一些模型,例如TF-IDF,TextRank,word2vec,Bert等等,也查了一些論文查重的原理,最終選擇用jieba分詞+gensim模型計算相似度的方式。

不了解jieba和gensim的同學可以先看看這邊:

文本的相似度計算公式如下:

\[P_總 = \sum_{i=1}^np_iw_i \]

  • 所有文本都只保留漢字
  • 其中pi指的是分段文本的相似度,wi指分段文本的權重(在比對文本文字的占比)

總的代碼設計圖如下:

接下來將對各個模塊進行介紹

模塊介紹

代碼主要分為計算模塊字符串處理模塊兩部分

1.計算模塊

計算模塊為cal_sentence_weightcal_similarity_tfidf兩個函數。

cal_sentence_weight為計算分段文本的權重,容易實現這里不再贅述

cal_similarity_tfidf函數介紹如下:

  • 對已被處理過的原文本我們利用gensim庫建立語料庫和模型,模型這邊我目前采用的是TF-IDF模型

  • TF-IDF原理

  • 代碼部分如下:

    # 對原始文本用gensim庫中的doc2bow和corpora進行處理,采用tfidf模型
    # 生成詞典
    dictionary = corpora.Dictionary(orig_items)
    # 通過doc2bow稀疏向量生成語料庫
    corpus = [dictionary.doc2bow(item) for item in orig_items]
    # 通過TF模型算法,計算出tf值
    tf = models.TfidfModel(corpus)
    # 通過token2id得到特征數(字典里面的鍵的個數)
    num_features = len(dictionary.token2id.keys())
    # 計算稀疏矩陣相似度,建立一個索引
    index = similarities.MatrixSimilarity(tf[corpus], num_features=num_features)
    
  • 建立好相似度索引后,我們對於處理過的待比對文本我們做如下操作:對每個分段文本我們計算它在原文本被分段文本的最大相似度(相似度越大說明和這個文本越接近),進行加權累加獲得總文本的相似度。

    為什么這么取呢?因為在debug過程中我發現同樣的分段處理,比對文本分出來的段的內容不一定和原文本一一對應,例如dis后綴的文本直接亂序排(同學們可以試着用全文字向量的方式去計算它們的相似度,dis后綴的文本全是1.0也就是完全一樣hhhhh,所以可以直接斷定是亂序排列了),如果一一對應有可能出現下標超界的錯誤,所以改用取最大相似度,這能保證這個分段文本能找到與他最相似的文本,一定程度上可以保證一一對應的關系。

    同時對於相似度低於0.25%的我采取舍棄處理,判定他是與語料庫中任何一個文本都不相似的。

    具體代碼如下:

    ans = 0.0
    for i in range(0,len(orig_sim_items)):
        #把每個分好詞的句子建立成新的稀疏向量並代入模型計算相似度
        orig_sim_vec = dictionary.doc2bow(orig_sim_items[i])
        sim = index[tf[orig_sim_vec]]
        sim_max = max(sim)
        if sim_max < 0.0025:#對於相似度低於0.25%的句子我們直接視為不相關
            continue
        ans += max(sim) * array[i]#顯然我們這里要取最高相似度而不是一一對應,可能會有下標超界的錯誤
    

    查重計算原理這邊有參照這篇README

2.字符串處理模塊

  • 由函數create_jieba_list負責該模塊。

  • 顯然相比全文本直接分詞比對計算相似度,分段后再進行分詞和計算得出來的相似度會更為精確。然而具體該如何分段又成了問題。

  • 試過了好幾種方法,去除空格回車+句號分割啊,每15個字算一段啊,逗號分割啊啥的,目前得出的結論是:

  • 在遍歷的過程中只記錄漢字,遇到句號分割為一個段

  • 具體代碼如下:jieba.lcut可以把文本分詞后存為list

    def create_jieba_list(test_data):#將文本分句並且每個句子進行分詞
        test_sentence = []
        s = ""
        for i in range(0,len(test_data)):
            if  '\u4e00' <= test_data[i] <= '\u9fff':#只記錄漢字
                s += test_data[i]
            elif test_data[i] == '。':#以句號作為分句標准
                #print(s)
                if s != "":#防止出現一連串符號
                    test_sentence.append(s)
                    s = ""
        if s != "":#可能還有文本需要加上
            #print(s)
            test_sentence.append(s)
            s = ""
        test_items = [[i for i in jieba.lcut(item)] for item in test_sentence]
        return test_items
    

模塊具體有何獨到之處的話emmm……分段分詞勉強算吧,老面向網絡編程了= =

運行結果

這里直接放單元測試版本(只含作業樣例)的了(頭禿):

開始單元測試……
正在載入orig_0.8_add.txt
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.595 seconds.
Prefix dict has been built successfully.
orz查重結果為0.84
已結束測試
開始單元測試……
正在載入orig_0.8_del.txt
..orz查重結果為0.71
已結束測試
開始單元測試……
正在載入orig_0.8_dis_1.txt
.orz查重結果為0.95
已結束測試
開始單元測試……
正在載入orig_0.8_dis_10.txt
orz查重結果為0.80
已結束測試
開始單元測試……
正在載入orig_0.8_dis_15.txt
..orz查重結果為0.57
已結束測試
開始單元測試……
正在載入orig_0.8_dis_3.txt
.orz查重結果為0.91
已結束測試
開始單元測試……
正在載入orig_0.8_dis_7.txt
.orz查重結果為0.87
已結束測試
開始單元測試……
正在載入orig_0.8_mix.txt
.orz查重結果為0.86
已結束測試
開始單元測試……
正在載入orig_0.8_rep.txt
.orz查重結果為0.71
已結束測試
開始單元測試……
正在載入orig.txt
orz查重結果為1.00
已結束測試
.
----------------------------------------------------------------------
Ran 10 tests in 3.555s

OK

算是比較符合預期的,dis系列的文本的相似度隨着亂序程度的增大而減少(僅個人猜測,目前來看估計有進行別的修改操作),其他的文本也沒有太低或者太高

性能分析與優化

性能分析

Python有幾個性能分析工具,對於這次作業我用的是pycharm自帶的Profile工具:

  • Profile工具有一點不是很好,就是當我們函數調用關系復雜的時候他的函數調用圖就非常縮略了orz:

​ 就只能放大來看函數用時和占比orz。

運行時間是比較符合我預期的,沒有太慢但是也沒有多快(測試組發的所有樣例都在這個時間上下):

貼一下時間消耗較大的函數以及他們的調用次數的圖片:

emmmm感覺分析的不夠全,因為沒有內存統計這塊,所以我用了intel性能分析工具

大家感興趣的話可以裝一裝,學生申請和安裝intel全家桶

國內關於intel全家桶除Vtune外性能分析工具介紹的博客並不是很多,很難找,所以只能掛個官方文檔大🔥看看了:

intel性能分析工具官方文檔,左上角Profiler&Analyzers都是

  • Vtune跑的時候我是一臉懵逼的……因為他直接轉成c了= =,具體截圖就不放了,挺裂開的……
  • 這是關於Vtune的介紹Intel Vtune Profiler
  • 所以我直接去跑APS快照了:

可以看到我們的內存消耗也不是很大,符合自己的預期

然后發現了很奇葩的事情……就是我這個代碼已經用了多線程了,甚至還有向量化(撓頭),所以后續的優化方向基本有個底了= =,看看多線程這邊能不能調一些參數。

優化方面

  • 。。。目前算法和代碼上沒有做什么優化,查了很多博客很多都是在介紹模型emmmm,前面主要是在找比較好的模型或者是一些比較暴力的字符串匹配方法(例如字向量,當然只這么做余弦相似度計算基本0.9以上甚至1.0)還有原算法上分段的憑據。
  • 目前是准備往多線程方向看看能不能在循環上處理加個速調調參數這樣orz。

2020.9.14(更新)

昨晚組內有個同學問我相似度計算的問題,然后我發現這段代碼同樣可以作用於LDALSI模型,然后效果都不是很好,有丶高:

正在載入orig.txt
orzLDA模型查重結果為1.00
正在載入orig_0.8_add.txt
..orzLDA模型查重結果為0.99
正在載入orig_0.8_del.txt
orzLDA模型查重結果為0.93
正在載入orig_0.8_dis_1.txt
..orzLDA模型查重結果為1.00
正在載入orig_0.8_dis_3.txt
orzLDA模型查重結果為0.99
正在載入orig_0.8_dis_7.txt
..orzLDA模型查重結果為0.99
正在載入orig_0.8_dis_10.txt
.orzLDA模型查重結果為0.97
正在載入orig_0.8_dis_15.txt
.orzLDA模型查重結果為0.90
正在載入orig_0.8_mix.txt
orzLDA模型查重結果為0.98
正在載入orig_0.8_rep.txt
..orzLDA模型查重結果為0.94
正在載入orig.txt
.orzLSI模型查重結果為1.00
正在載入orig_0.8_add.txt
orzLSI模型查重結果為0.96
正在載入orig_0.8_del.txt
..orzLSI模型查重結果為0.87
正在載入orig_0.8_dis_1.txt
.orzLSI模型查重結果為0.99
正在載入orig_0.8_dis_3.txt
orzLSI模型查重結果為0.97
正在載入orig_0.8_dis_7.txt
..orzLSI模型查重結果為0.96
正在載入orig_0.8_dis_10.txt
.orzLSI模型查重結果為0.93
正在載入orig_0.8_dis_15.txt
orzLSI模型查重結果為0.82
正在載入orig_0.8_mix.txt
..orzLSI模型查重結果為0.95
正在載入orig_0.8_rep.txt
.
----------------------------------------------------------------------
Ran 30 tests in 31.319s

OK
orzLSI模型查重結果為0.88

相比之下還是TF-IDF靠譜點= =所以單元測試和總代碼繼續保留TFIDF模型的計算

計算模塊部分單元測試展示

我本來寫了個“測試程序”順序執行了所有的樣例,結果網上沖浪的時候發現了unittest模塊一口老血吐了出來,馬上轉用unittest了

不過使用unittest前要把我們整個計算相似度的程序封裝成一個函數,這樣寫unittest測試程序會比較好寫。

test.py->test_as_function.py:

import jieba
import sys
import test#引用一下計算程序,不然代碼屬實太長
from gensim import corpora, models, similarities
import os
import time
def orig_solve(orig_position):
    orig = open(orig_position, 'r', encoding='UTF-8')
    orig_text = orig.read()
    orig.close()
    # 1.將原始文本分句並每句用jieba_lcut分詞
    orig_items = test.create_jieba_list(orig_text)
    return orig_items

def text_solve(text_position):
    text = open(text_position, 'r', encoding='UTF-8')
    test_text = text.read()
    text.close()
    # 1.將原始文本分句並每句用jieba_lcut分詞
    text_items = test.create_jieba_list(test_text)
    array = test.cal_sentence_weight(test_text)
    return text_items,array
def write_dir(ans,ans_position):
    str1 = str('0.2f' %ans)
    ans_text = open(ans_position,'w',encoding='UTF-8')
    ans_text.write(str1)
    ans_text.close()
    #print('0')
def solve(orig_position,text_position,ans_position):
    orig_items = orig_solve(orig_position)
    text_items,array = text_solve(text_position)
    ans = test.cal_similarity(orig_items,text_items,array)
    write_dir(ans,ans_position)
    print('orz查重結果為%.2f' %ans)

測試代碼如下(展示tf-idf類):

import unittest
import test_as_function  # 引用一下計算程序,不然代碼屬實太長
from BeautifulReport import BeautifulReport

class TestForAllTextTfIdf(unittest.TestCase):
    @classmethod
    def setUp(self):
        print("開始單元測試……")
    @classmethod
    def tearDown(self):
        print("已結束測試")

    def test_self_tfidf(self):
        print("正在載入orig.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig.txt', 'ans.txt')

    def test_add_tfidf(self):
        print("正在載入orig_0.8_add.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt','sim_0.8\orig_0.8_add.txt','ans.txt')

    def test_del_tfidf(self):
        print("正在載入orig_0.8_del.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_del.txt', 'ans.txt')

    def test_dis_1_tfidf(self):
        print("正在載入orig_0.8_dis_1.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_1.txt', 'ans.txt')

    def test_dis_3_tfidf(self):
        print("正在載入orig_0.8_dis_3.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_3.txt', 'ans.txt')

    def test_dis_7_tfidf(self):
        print("正在載入orig_0.8_dis_7.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_7.txt', 'ans.txt')

    def test_dis_10_tfidf(self):
        print("正在載入orig_0.8_dis_10.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_10.txt', 'ans.txt')

    def test_dis_15_tfidf(self):
        print("正在載入orig_0.8_dis_15.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_15.txt', 'ans.txt')

    def test_mix_tfidf(self):
        print("正在載入orig_0.8_mix.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_mix.txt', 'ans.txt')

    def test_rep_tfidf(self):
        print("正在載入orig_0.8_rep.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_rep.txt', 'ans.txt')

    def test_rep_tfidf_NoChineseError(self):
        print("正在載入orig_NoChinese.txt")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_rep.txt', 'ans.txt')

    def test_rep_tfidf_TextSameError(self):
        print("正在載入")

    def test_My_database1_add(self):
        print("正在載入My_database1\\orig_add.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_add.txt', 'ans.txt')

    def test_My_database1_del(self):
        print("正在載入My_database1\\orig_del.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_del.txt', 'ans.txt')

    def test_My_database1_dis_1(self):
        print("正在載入My_database1\\orig_dis_1.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_dis_1.txt', 'ans.txt')

    def test_My_database1_dis_3(self):
        print("正在載入My_database1\\orig_dis_3.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_dis_3.txt', 'ans.txt')

    def test_My_database1_dis_7(self):
        print("正在載入My_database1\\orig_dis_7.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_dis_7.txt', 'ans.txt')

    def test_My_database1_dis_10(self):
        print("正在載入My_database1\\orig_dis_10.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_dis_10.txt', 'ans.txt')

    def test_My_database1_dis_15(self):
        print("正在載入My_database1\\orig_dis_15.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_dis_15.txt', 'ans.txt')

    def test_My_database1_mix(self):
        print("正在載入My_database1\\orig_mix.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_mix.txt', 'ans.txt')

    def test_My_database1_rep(self):
        print("正在載入My_database1\\orig_rep.txt")
        test_as_function.solve_tfidf('My_database1\orig.txt', 'My_database1\orig_rep.txt', 'ans.txt')


if __name__ == '__main__':
    #unittest.main()
    suite = unittest.TestSuite()
    suite.addTest(TestForAllTextTfIdf('test_self_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_add_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_del_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_dis_1_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_dis_3_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_dis_7_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_dis_10_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_dis_15_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_mix_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_rep_tfidf'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_add'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_del'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_dis_1'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_dis_3'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_dis_7'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_dis_10'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_dis_15'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_mix'))
    suite.addTest(TestForAllTextTfIdf('test_My_database1_rep'))
    runner = BeautifulReport(suite)
    runner.report(
        description='論文查重測試報告',  # => 報告描述
        filename='nlp_TFIDF.html',  # => 生成的報告文件名
        log_path='.'  # => 報告路徑
    )

emmm目前是作業的那幾組只動了20%數據的文本加上我自己生成的測試數據,基本是對着測試組的數據“照虎畫貓”(有意這么寫,不是不會成語orz),自己的測試數據原文本是用狗屁不通文章生成器生成的兩個文章放在一起,兩萬多字。

orz不知道為啥Pycharm自帶的unittest框架看不到每個樣例具體的通過結果和運行時間,只好動用黑科技——BeautifulReport了,這個模塊可以生成樣例測試html格式的報告,報告內詳細介紹了每個樣例的運行細節以及對應時間。

這篇Unittest-測試運行:查看測試結果介紹了三種看報告結果的方法,推薦大家試一下(不過后面兩種要用的包pip都裝不了得自己解壓到sites-package目錄下hhhh)

測試覆蓋率截圖:

然后是自己生成的數據

很顯然dis系列(也就是亂序)的文本並不能如預期一樣亂序次數越多查重率越低,測試組的數據應該是用了其他方法,我這個亂序文本的生成程序做的還不夠有代表性

當然當前的測試還有缺點:

  • 應該還有其他生成相似文章的方式,加入這些樣例測試覆蓋率會更廣
  • 亂序程序的亂序程度有點大,多次亂序相似度不存在大小關系,需要再修改方法

代碼覆蓋率截圖:

測試數據生成程序

按照作業樣例txt命名的字面意思以及我自己的文本操作,推測出幾個相似文本構建方式,我可以實現相對簡單的測試數據生成程序:

  • 加式文本:每隔固定長度我們增加固定個數的隨機漢字。這樣生成的數據代表性肯定沒作業樣例強,因為在看orig_0.8_add.txt的時候其實可以看到測試組的同學很用心的把增加的字加在了詞之間,破壞了詞的結構。這樣對我們比對來說相似度不會太高也不會太低。

  • 刪式文本:每隔固定長度我們刪除固定個數的隨機漢字。

  • 亂序文本:三個模式:1.逗號分割句子讓句子亂序若干次 2.句號分割讓句子亂序若干次 3.每隔固定長度字符讓段落亂序若干次

    以上三個模式都把符號算入計數范疇。

    關於數組亂序有一篇相對比較好的博客,他的第三種方法挺好的,O(n)的復雜度,大佬估計想得到orz:如何讓一個數組亂序

  • 混合文本:增刪結合

  • 代表(rep)文本:增刪改結合

    然后我們要做的就是讀入我們的文本就行啦!

    我github鏈接上也有這個代碼,create_sample.py就是

    import random
    def add(test_text):#增字,目前只支持每隔固定距離添加固定長度隨機漢字
        a,b = eval(input("請輸入划分段的字數以及要添加隨機漢字的長度,用逗號隔開:"))
        s = ""
        cnt = 0
        for i in range(0,len(test_text)):
            s += test_text[i]
            cnt += 1
            if cnt == a:
                cnt = 0
                for j in range(0,b):
                    s+=chr(random.randint(0x4e00, 0x9fbf))
        return s
    
    def delete(test_text):#刪字,目前只支持每隔固定距離刪除固定長度隨機漢字
        a, b = eval(input("請輸入划分段的字數以及要刪除隨機漢字的長度,用逗號隔開:"))
        s = ""
        cnt = 0
        for i in range(0, len(test_text)):
            if cnt == a:
                cnt = 0
                for j in range(0, b):
                    i += 1
                    if i == len(test_text):
                        break
            if i == len(test_text):
                break
            s += test_text[i]
            cnt += 1
        return s
    
    def swap(a,b):
        temp = a
        a = b
        b = temp
        return a,b
    
    def chaos_sort(str1):
        str1_len = len(str1)
        list1 = list(str1)
        for i in range(0,str1_len):
            k = random.randint(0, str1_len-i-1)
            #print(list1[k],list1[str1_len-i-1])
            list1[k],list1[str1_len-i-1] = swap(list1[k],list1[str1_len-i-1])
            #print(list1[k], list1[str1_len-i-1])
        str1 = ""
        for i in range(0, str1_len):
            str1+=list1[i]
        #print(str1)
        return str1
    
    def dis(test_text):#亂序,目前只支持逗號分割,句號分割以及每隔一段距離n次亂序的操作
        a,b = eval(input("請輸入划分模式(1表示逗號分割,2表示句號分割,3表示自己定分段字數)以及每次亂序的次數:"))
        s = ""
        temp = ""
        if a == 1 or a == 2:
            if a == 1:
                str1 = ','
            elif a == 2:
                str1 = '。'
            for i in range(0, len(test_text)):
                temp += test_text[i]
                if test_text[i] == str1:
                    for j in range(0,b):
                        temp = chaos_sort(temp)
                    #print(temp)
                    s += temp
                    temp = ""
            if temp != "" :
                for j in range(0, b):
                    chaos_sort(temp)
                s += temp
                temp = ""
        else:
            c = eval(input("請輸入分段長度:"))
            cnt = 0
            for i in range(0, len(test_text)):
                temp += test_text[i]
                cnt += 1
                if cnt == c:
                    for j in range(0,b):
                        chaos_sort(temp)
                    print(temp)
                    s += temp
                    cnt = 0
                    temp = ""
            if temp != "" :
                for j in range(0, b):
                    chaos_sort(temp)
                s += temp
                temp = ""
        return s
    def mix(test_text):#增刪結合
        str1 = add(test_text)
        str1 = delete(str1)
        return str1
    def rep(test_text):#增刪亂序結合
        str1 = add(test_text)
        #print(str1)
        str1 = delete(str1)
        #print(str1)
        str1 = dis(str1)
        return str1
    if __name__ == '__main__':
        test = open("My_database1\\orig.txt",'r',encoding = 'UTF-8')
        test_text = test.read()
        test.close()
        str1 = input("請輸入創造相似文本的模式(add,del,dis,mix,rep):")
        str1.upper()
        if str1 == "ADD":
            change_text = add(test_text)
        elif str1 == "DEL":
            change_text = delete(test_text)
        elif str1 == "DIS":
            change_text = dis(test_text)
      elif str1 =="MIX":
            change_text = mix(test_text)
        else:
            change_text = rep(test_text)
        #print(change_text)
        change = open("My_database1\\orig_rep.txt",'w',encoding = 'UTF-8')
        change.write(change_text)
        change.close()
        print("已按%s模式生成相應文本:D",str1)
        print(0)
    
    

異常處理

說實話我一開始看到要寫這個的時候是一臉懵逼的……畢竟只有文本查重,但是自己思考+向柯老師詢問后,還是有幾個異常要寫的orz

  • 不一樣的文本相似度卻是100%
  • 一樣的文本相似度卻不是100%
  • 某些文本沒有漢字
  • 處理過程中可能會有下標超界的情況

因為第四個python自帶,所以我對前三個異常進行了編寫:

class TextSameError(Exception):  # 文本不同相似度卻100%

    def __init__(self):
        print("文本不一樣為什么會完全一樣捏?")

    def __str__(self, *args, **kwargs):
        return "再檢查一下代碼哦"


class TextDifferentError(Exception):  # 文本一致相似度不是100%

    def __init__(self):
        print("自己和自己比,怎么會不一樣呢?")

    def __str__(self, *args, **kwargs):
        return "再檢查一下代碼哦"


class NoChineseError(Exception):  # 比對文本壓根沒有漢字,相似度直接判0


    def __init__(self):
        print("漢字都沒有比對🔨呢")
        ans_txt = open(sys.argv[3], 'w', encoding='UTF-8')
        sim = str(0.00)
        ans_txt.write(sim)
        ans_txt.close()
        print("0")

    def __str__(self, *args, **kwargs):
        return "找篇有漢字的來吧"

2020.09.16更新

具體用法:

# 計算權重
    if sum == 0:
        raise NoChineseError
    else:
        for i in range(0, len(array)):
            array[i] = array[i] * 1.0 / sum
        return array
try:
    ans += max(sim) * array[i]  # 顯然我們這里要取最高相似度而不是一一對應,可能會有下標超界的錯誤
except IndexError:
    print("orz下標超界了")
else:
    continue
    if abs(ans - 1.00) <= 0.000001 :
        raise TextSameError
    else:
        ans_txt = open(sys.argv[3], 'w', encoding='UTF-8')
        sim = str('%.2f' % ans)
        ans_txt.write(sim)
        ans_txt.close()
    print(0)

異常測試樣例:

# 異常處理,IndexError和TextDifferentError是在調試過程中使用的,目前程序已經沒有這個問題
    def test_NoChineseError_tfidf(self):
        print("開始無漢字異常測試QAQ:")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_NoChinese.txt', 'ans.txt')

    def test_TextSameError_tfidf(self):
        print("開始文本相同異常測試QAQ:")
        test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_copy.txt', 'ans.txt')

測試結果如下(orz下標超界是因為原來的異常處理是作用於test.py文件的,要寫到命令行傳參的文件上所以會報錯):

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
開始單元測試……
開始無漢字異常測試QAQ:
Loading model cost 0.581 seconds.
Prefix dict has been built successfully.
E漢字都沒有比對🔨呢
已結束測試
開始單元測試……
開始文本相同異常測試QAQ:
文本不一樣為什么會完全一樣捏?
已結束測試
E
======================================================================
ERROR: test_NoChineseError_tfidf (__main__.TestForAllTextTfIdf)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "G:/software/031802126/unit_test.py", line 92, in test_NoChineseError_tfidf
    test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_NoChinese.txt', 'ans.txt')
  File "G:\software\031802126\test_as_function.py", line 95, in solve_tfidf
    text_items,array = text_solve(text_position)
  File "G:\software\031802126\test_as_function.py", line 83, in text_solve
    array = test.cal_sentence_weight(test_text)
  File "G:\software\031802126\test.py", line 82, in cal_sentence_weight
    raise NoChineseError
  File "G:\software\031802126\test.py", line 34, in __init__
    ans_txt = open(sys.argv[3], 'w', encoding='UTF-8')
IndexError: list index out of range

======================================================================
ERROR: test_TextSameError_tfidf (__main__.TestForAllTextTfIdf)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "G:/software/031802126/unit_test.py", line 96, in test_TextSameError_tfidf
    test_as_function.solve_tfidf('sim_0.8\orig.txt', 'sim_0.8\orig_copy.txt', 'ans.txt')
  File "G:\software\031802126\test_as_function.py", line 98, in solve_tfidf
    raise test.TextSameError
test.TextSameError: 再檢查一下代碼哦

----------------------------------------------------------------------
Ran 2 tests in 0.811s

FAILED (errors=2)

總結

  • 感覺自己學習能力上還是要有待提高……很多模型看了理解不來,像word2vec,LDA這些我看了都挺懵逼的,也不知道怎么用,如果能理解這些模型的話程序應用應該會更精確一些
  • 很久沒打python了,碼了這么多感覺自己熟練度上還是沒落下。不過打C的時候會習慣性加冒號和不寫分號
  • 當我把注意力放在編寫需求程序以外的部分的時候,我才發現軟工實踐還有這么多東西要做,我們要編寫的代碼不僅僅只是我們的需求程序,我們還需要寫測試程序還有測試數據生成程序,當然如果項目大點應該還要寫點其他的。這讓我意識到軟工實踐的魅力所在,它要求我們完成的是一個流程,而不是單個子任務,每個板塊都是密不可分的。
  • 和以往寫算法題目不一樣,debug並不只是在某個代碼段輸出對應值來找到代碼漏洞那么簡單,我們可以用相應的模塊載入測試數據來衡量程序的嚴謹性和效果
  • 代碼還不夠規范hhhhh,以后盡量按PEP8標准來寫(下划線聲明變量選手表示很折磨)

PSP表格

Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning(計划) 192 200
Estimate(估計時間) 192 100
Development(開發) 1968 1680
Analysis(需求分析(包括學習新技術)) 144 100
Design Spec(生成設計文檔) 120 84
Design Review(設計復審) 96 68
Coding Standard(代碼規范 ) 72 51
Design(具體設計) 240 168
Coding(具體編碼) 864 804
Code Review(代碼復審) 168 118
Test(測試(自我測試,修改代碼,提交修改)) 312 400
Reporting(報告) 216 500
Test Report(測試報告) 72 380
Size Measurement(計算工作量) 48 50
Postmortem & Process Improvement Plan(事后總結, 並提出過程改進計划) 72 70
Total(合計) 2400 2593


免責聲明!

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



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