第一次个人编程作业

第一次个人编程作业

这是一个Github链接

零、面向开源,春暖花开

问:如何从零开始造火箭?
答:去别的火箭厂搬个火箭。
看看搬来的代码是什么…………py文件,哦是用python写的。打开vscode,ctrl c+v,运行代码……
嗯?怎么报错了?没有python解释器?难道vscode不是自带python解释器的?原来vscode不是IDE,啊这……
下载好python解释器,修改环境变量,下载python插件,用了一个世纪的时间终于ok,运行代码!
嗯?第三方库不能用?
又是一个世纪过去……哦,原来python要用pip指令来安装库。
安装完第三方库,运行!
踩了若干个坑之后……
ohhhhhhhhhhhh!有结果了!
分析性能,性能怎么分析?pycharm好像可以,下载!
吃一堑长一智,选择好python解释器,下载好插件,ctrl c+v, 运行!
又踩了若干个坑(包括但不局限于内存测试、单元测试、用git将代码上传到github仓库)之后,本菜鸡终于完成了第一次编程作业,泪目。

一、计算模块接口的设计与实现过程

1.总体流程

实话实说,既然代码都是直接搬来的,在设计和实现方面我也没什么好说的(我爬_(°ω°」∠)_)。
代码的核心是使用向量空间模型(Vector Space Model, VSM)计算相似度。先用jieba分词器对读入的文本进行分词处理,然后使用TF-IDF算法进行文本特征选择,计算出文本的特征向量,最后对得到的特征向量用余弦相似度算法计算出两个文本的相似度。

VSM模型介绍
TF-IDF算法介绍

2.计算模块

(1)分词处理

#使用jieba分词器对文本text1、text2进行分词
words1 = [word.word for word in pesg.cut(text1) if word.flag[0] not in ['u', 'x', 'w']]
words2 = [word.word for word in pesg.cut(text2) if word.flag[0] not in ['u', 'x', 'w']]

(2)使用TF-IDF算法计算文本的特征向量

(俺很想注释,但俺没看懂……)

def tfidf_rep(self, sents):
    sent_list = []
    df_dict = {}
    tfidf_list = []
    for sent in sents:
        tmp = {}
        for word in sent:
            if word not in tmp:
                tmp[word] = 1
            else:
                tmp[word] += 1
        tmp = {word:word_count/sum(tmp.values()) for word, word_count in tmp.items()}
        for word in set(sent):
            if word not in df_dict:
                df_dict[word] = 1
            else:
                df_dict[word] += 1
        sent_list.append(tmp)
    df_dict = {word :math.log(len(sents)/df+1) for word, df in df_dict.items()}
    words = list(df_dict.keys())
    for sent in sent_list:
        tmp = []
        for word in words:
            tmp.append(sent.get(word, 0))
        tfidf_list.append(tmp)
    return tfidf_list

(3)使用余弦相似度算法计算相似度

def cosine_sim(self, vector1, vector2):
    #向量1和向量2的内积
    cos1 = np.sum(vector1 * vector2)
    #求向量1与向量1的内积,再开方
    cos21 = np.sqrt(sum(vector1 ** 2))
    #求向量2与向量2的内积,再开方
    cos22 = np.sqrt(sum(vector2 ** 2))
    #计算相似度
    similarity = cos1 / float(cos21 * cos22)
    return similarity

3.运行结果

测试结果显示,文本相似度都非常高emmmmm
因为在基于VSM的算法中,文本的内部结构对于相似度计算的影响不大,所以所有测试样例中的乱序现象不会降低相似度的计算结果。另外,算法较为简陋,没有对停用词和标点进行处理,或许也是文本相似度结果这么高的原因。(也是性能改进的方向)

4.性能测试

(1)时间

使用pycharm自带的性能测试工具Profile对代码进行分析:

统计图如下:

函数调用图如下:

最耗时的函数及其耗时:

main函数耗时竟然高达6秒Orz,或许可以通过消去标点或者优化代码等方法来提速吧(瞎猜Orz)

(2)空间

大佬们用的内存分析工具我暂时用不来,使用如下两行代码来分析内存的使用以及消耗的CPU时间(虽说上面已经在时间方面进行了测试,但是既然知道了这个方法,不如也用一下)

# 性能分析
print(u'当前进程的内存使用:%.4f MB' % (psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024) )
print(u'当前进程的使用的CPU时间:%.4f s' % (psutil.Process(os.getpid()).cpu_times().user) )

运行结果如下:

很奇怪,为什么这里显示的时间就比profile分析出来的时间少多了,或许是因为这里计算的是进程占用的CPU时间而不是实际时间?

(3)代码覆盖率

使用coverage来进行测试:

结果中列出了许多项,可以看到最后一行的main.py,其覆盖率为100% 。

二、计算模块接口部分的性能改进

前面已经说过,可以从去除停用词和标点、优化代码等方面来改进性能。由于种种原因(人太菜Orz,花在写代码方面的时间太少,用在解决各种小问题的时间太多),暂未改进。

三、计算模块部分单元测试展示

单元测试中使用的测试数据全部是测试组提供的样例数据。先亮出单元测试的结果:

先前曾进行过测试,耗时一栏在8s左右,人都傻了。之后又进行了一次单元测试,测试结果的耗时在2s左右,猜测是因为计算机上的进程太多,导致单元测试用的时间较长。

单元测试代码

这里使用的是python自带的标准单元测试库——unittest库。看了大佬的博客后,我选择使用BeautifulReport报告库,可以生成html格式的测试结果报告。
Unittest-测试运行:查看测试结果

单元测试内部函数test_inside:

#!/usr/bin/env python3
# coding: utf-8
import jieba.posseg as pesg
import math
import numpy as np
import sys
import os

class SimVsm:
    '''比较相似度'''
    def distance(self, text1, text2):
        words1 = [word.word for word in pesg.cut(text1) if word.flag[0] not in ['u', 'x', 'w']]
        words2 = [word.word for word in pesg.cut(text2) if word.flag[0] not in ['u', 'x', 'w']]
        tfidf_reps = self.tfidf_rep([words1, words2])
        return self.cosine_sim(np.array(tfidf_reps[0]), np.array(tfidf_reps[1]))

    '''对句子进行tfidf向量表示'''
    def tfidf_rep(self, sents):
        sent_list = []
        df_dict = {}
        tfidf_list = []
        for sent in sents:
            tmp = {}
            for word in sent:
                if word not in tmp:
                    tmp[word] = 1
                else:
                    tmp[word] += 1
            tmp = {word:word_count/sum(tmp.values()) for word, word_count in tmp.items()}
            for word in set(sent):
                if word not in df_dict:
                    df_dict[word] = 1
                else:
                    df_dict[word] += 1
            sent_list.append(tmp)
        df_dict = {word :math.log(len(sents)/df+1) for word, df in df_dict.items()}
        words = list(df_dict.keys())
        for sent in sent_list:
            tmp = []
            for word in words:
                tmp.append(sent.get(word, 0))
            tfidf_list.append(tmp)
        return tfidf_list

    '''余弦相似度计算相似度'''
    def cosine_sim(self, vector1, vector2):
        cos1 = np.sum(vector1 * vector2)
        cos21 = np.sqrt(sum(vector1 ** 2))
        cos22 = np.sqrt(sum(vector2 ** 2))
        similarity = cos1 / float(cos21 * cos22)
        return similarity


def test(s_position, d_position, ans_position):

    f1 = open(s_position, "rt", encoding = 'UTF-8')
    f2 = open(d_position, "rt", encoding = 'UTF-8')
    f3 = open(ans_position, "a+", encoding = 'UTF-8')

    txt1 = f1.read()
    txt2 = f2.read()

    simer = SimVsm()
    sim = simer.distance(txt1, txt2)
    print('查重结果为%.2f'%sim)

    f3.write("sim_0.8\orig.txt, ")
    f3.write(d_position)
    f3.write(str("  Similarity: %.2f"%sim)+'\n')
    f1.close()
    f2.close()
    f3.close()

单元测试函数test_vsm:

import unittest
import test_inside
from BeautifulReport import BeautifulReport

class MyTestCase(unittest.TestCase):
    def setUp(self):
        print("开始单元测试:")

    def tearDown(self):
        print("测试结束")

    def test_self(self):
        print("正在载入orig.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig.txt', 'ans.txt')

    def test_add(self):
        print("正在载入orig_0.8_add.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_add.txt', 'ans.txt')

    def test_del(self):
        print("正在载入orig_0.8_del.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_del.txt', 'ans.txt')

    def test_dis_1(self):
        print("正在载入orig_0.8_dis_1.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_1.txt', 'ans.txt')

    def test_dis_3(self):
        print("正在载入orig_0.8_dis_3.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_3.txt', 'ans.txt')

    def test_dis_7(self):
        print("正在载入orig_0.8_dis_7.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_7.txt', 'ans.txt')

    def test_dis_10(self):
        print("正在载入orig_0.8_dis_10.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_10.txt', 'ans.txt')

    def test_dis_15(self):
        print("正在载入orig_0.8_dis_15.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_dis_15.txt', 'ans.txt')

    def test_mix(self):
        print("正在载入orig_0.8_mix.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_mix.txt', 'ans.txt')

    def test_rep(self):
        print("正在载入orig_0.8_rep.txt")
        test_inside.test('sim_0.8\orig.txt', 'sim_0.8\orig_0.8_rep.txt', 'ans.txt')

if __name__ == '__main__':
    #unittest.main()
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('test_self'))
    suite.addTest(MyTestCase('test_add'))
    suite.addTest(MyTestCase('test_del'))
    suite.addTest(MyTestCase('test_dis_1'))
    suite.addTest(MyTestCase('test_dis_3'))
    suite.addTest(MyTestCase('test_dis_7'))
    suite.addTest(MyTestCase('test_dis_10'))
    suite.addTest(MyTestCase('test_dis_15'))
    suite.addTest(MyTestCase('test_mix'))
    suite.addTest(MyTestCase('test_rep'))
    runner = BeautifulReport(suite)
    runner.report\
    (
        description = "论文查重单元测试报告", #报告描述
        filename = 'sim_vsm.html', #生成的报告文件名
        log_path = '.'  #报告路径
    )

测试覆盖率

使用coverage来进行测试:

结果中列出了许多项,可以看到最后两行的test_inside.py和test_vsm.py,其覆盖率都为100% 。

四、计算模块部分异常处理

经过一番百度+看了大佬的博客后,发现计算模块可能出现异常的情况:空文本、无汉字文本等。
具体异常处理模块暂无Orz

五、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60 70
· Estimate · 估计这个任务需要多少时间 60 60
Development 开发 240 180
· Analysis · 需求分析 (包括学习新技术) 900 1500
· Design Spec · 生成设计文档 60 50
· Design Review · 设计复审 30 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 20
· Design · 具体设计 240 180
· Coding · 具体编码 300 360
· Code Review · 代码复审 60 75
· Test · 测试(自我测试,修改代码,提交修改) 180 150
Reporting 报告 45 30
· Test Report · 测试报告 30 60
· Size Measurement · 计算工作量 30 15
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 90
· 合计 2295 2860

六、总结

一次从零开始的编程体验,从一开始vscode都不会用,到最后勉强完成作业,尽管惨不忍睹,但还是学到了不少东西。程序成功运行时我天真地以为作业就快要完成了,但实事告诉我,这才完成了一小部分。性能测试、单元测试、异常处理、性能优化等等都是要面对的事。路漫漫其修远兮,吾将上下而求索。

posted @ 2020-09-17 18:30  小破药罐  阅读(212)  评论(0编辑  收藏  举报