第一次个人编程作业


part.Ⅰ GitHub

my github

part.Ⅱ PSP表格
PSP2.1 Personal Software
Process Stages
预估耗时
(分钟)
实际耗时
(分钟)
Planning 计划 40 50
· Estimate · 估计这个任务需要多少
时间
40 60
Development 开发 420 450
· Analysis · 需求分析 (包括学习新
技术)
280 300
· Design Spec · 生成设计文档 40 40
· Design Review · 设计复审 60 80
· Coding Standard · 代码规范 (为目前的开
发制定合适的规范)
60 60
· Design · 具体设计 100 120
· Coding · 具体编码 180 200
· Code Review · 代码复审 30 60
· Test · 测试(自我测试,修改
代码,提交修改)
40 60
Reporting 报告 70 80
· Test Repor · 测试报告 40 40
· Size Measurement · 计算工作量 30 30
· Postmortem & Process
Improvement Plan
· 事后总结, 并提出过程
改进计划
40 60
· 合计 1470 1670
part.Ⅲ 计算模块接口的设计与实现过程

在查阅一些资料后,我就决定使用余弦相似度来做。
基本思路是:如果这两句话的用词越相似,它们的内容就应该越相似。因此,可以从词频入手,计算它们的相似程度。
第一步,分词。

  句子A:我/喜欢/看/电视,不/喜欢/看/电影。

  句子B:我/不/喜欢/看/电视,也/不/喜欢/看/电影。

第二步,列出所有的词。

  我,喜欢,看,电视,电影,不,也。

第三步,计算词频。

  句子A:我 1,喜欢 2,看 2,电视 1,电影 1,不 1,也 0。

  句子B:我 1,喜欢 2,看 2,电视 1,电影 1,不 2,也 1。

第四步,写出词频向量。

  句子A:[1, 2, 2, 1, 1, 1, 0]

  句子B:[1, 2, 2, 1, 1, 2, 1]

余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫"余弦相似性"。

首先我是用jieba.lcut将两篇文章分词,并计算对应的词频向量,然而相似度算出来都是0.99+,于是我查看了分词的结果

原来是"我","了","的","在"这种词语频率太高了,我第一反应是降低这些词语的权重,后面查阅了一些资料,发现在NLP中,这些词被称为停用词,于是我从网上下载了一个停用词库,对于在停用词库里的词乘一个较小的比例系数,相似度结果有所好转。
然而,我看到了作业的一条提醒:凡提交的可执行文件、出现下列情况之一者,作业以0分计:
...
尝试读写其他文件
...
,所以这个想法只能放弃。
然后我又通过查资料找到一个jieba.analyse.textrank()函数,格式是keywords = jieba.analyse.textrank(content, topK=5, withWeight=True, allowPOS=('ns', 'n', 'vn', 'v')),
即关键词仅提取地名、名词、动名词、动词。结果挺符合我的期望,但当我import time库去计算程序运行时间的时候,结果时间都是5s以上,放弃+1.
最后我选择使用jieba.analyse.extract_tags()函数,它的allows参数默认为[""],不会过滤词性。而textrank()需要构造图,复杂度高,时间自然就长。虽然extract_tags()的效果会差亿点,不过时间却少了很多,总的来说差强人意。

流程图

函数介绍(没进行异常处理)

# 余弦相似度算法适合短文本,所以短文本的关键词数/总词数可以尽量大些
def wordsNum(num):
    if num<10:
        return num
    elif num<200:
        return ceil(num/2)
    else:
        return ceil(num/3)
def extractKeywords(path):
    s = open(path, 'r', encoding='utf-8')
    line = s.read()
    # punctuation是从zhon.hanzi导入的包含很多符号的字符串,punctuation += '\n '(有个空格),再用正则表达式替换就能得到一个纯中文字符串
    line = re.sub(r"[%s]+" % punctuation, "", line) 
    # 根据词语数量来确定提取关键词数量,用set去除重复元素
    wordSum = jieba.lcut(line)
    length = len(set(wordSum))
    # 利用jieba.analyse.extract_tags()函数,返回关键词及其权重(需要设置withWeight=True)
    keyWords = jieba.analyse.extract_tags(line, wordsNum(length), True)
    s.close()
    # 原来的keyWords是一个列表,里面的元素都是元组,转成字典有利用后续操作
    return dict(keyWords)
# 求两个关键词并集,先用集合去重,返回list是为了方便遍历
def mergeWords(d1, d2):
    wordSet = set(d1.keys()).union(d2.keys())
    return list(wordSet)
# 求关键词向量
def calVector(d, wordlist):
    length = len(wordlist)
    vector = [0] * length
    keys = d.keys()
    for i in range(length):
        if wordlist[i] in keys:
            vector[i] = d[wordlist[i]]
    return vector
# 求余弦相似度
def calCos(v1, v2):
    vectorLength = len(v1)
    B = sum(v1[i]*v2[i] for i in range(vectorLength))
    A1 = sum(i**2 for i in v1)
    A2 = sum(i**2 for i in v2)
    A = sqrt(A1*A2)
    return B/A
# 将结果写入指定文件
def saveData(path,data):
    with open(path, 'w') as file_object:
        file_object.write(format(data, ".2f"))
        file_object.close()
        print("写入"+path+"文件完成")
# 主函数
if __name__ == "__main__":
    # sys.argv[0] : main.py
    filePath1,filePath2,savePath = sys.argv[1],sys.argv[2],sys.argv[3]
    # 加上换行和空格,结合正则表达式可去除所有非中文的字符
    punctuation += '\n '
    # 分别提取两篇文本的关键词及其权重
    t1 = extractKeywords(filePath1)
    t2 = extractKeywords(filePath2)
    # 求出关键词并集
    words = mergeWords(t1, t2)
    # 分计算词向量
    v1 = calVector(t1, words)
    v2 = calVector(t2, words)
    # 计算余弦相似度
    cos = calCos(v1, v2)
    # 在指定路径存储数据
    saveData(savePath,cos)
    print('相似度为 = ' + format(cos, ".2f"))
    # 评测结束返回0
    sys.exit(0)
part.Ⅳ 计算模块接口部分的性能改进

使用pycharm的性能分析

时间花费最多的是提取关键词函数

总时间

part.Ⅴ 计算模块部分单元测试展示
import unittest
from mainfunc import maintest
# 部分
class MyTest(unittest.TestCase):

    orig = 'C:\image\sim_0.8\orig.txt'
    folderpath = 'C:\image\sim_0.8\\'

    def test_add(self):
        fileName = "orig_0.8_add.txt"
        cosValue = maintest.calCos(self.orig, self.folderpath+fileName)
        print(fileName+"的相似度为:"+format(cosValue, ".3f"))

    def test_dis_1(self):
        fileName = "orig_0.8_dis_1.txt"
        cosValue = maintest.calCos(self.orig, self.folderpath+fileName)
        print(fileName+"的相似度为:"+format(cosValue, ".3f"))

    def test_dis_3(self):
        fileName = "orig_0.8_dis_3.txt"
        cosValue = maintest.calCos(self.orig, self.folderpath+fileName)
        print(fileName+"的相似度为:"+format(cosValue, ".3f"))

    def test_rep(self):
        fileName = "orig_0.8_rep.txt"
        cosValue = maintest.calCos(self.orig, self.folderpath+fileName)
        print(fileName+"的相似度为:"+format(cosValue, ".3f"))

if __name__ == "__main__":
    unittest.main()

测试结果

part.Ⅵ 计算模块部分异常处理说明
我认为异常处理主要有三点:
1.命令行输入参数不正确
2.要读取的文件不存在
3.读取的文件是空文本
# 前两个我在主函数用try-exception处理,如果发生异常,就打印错误原因
    try:
        ...
    except Exception as e:
        print(e)
        print("请输入正确的参数")
    finally:
        # 程序结束,返回0
        sys.exit(0)
# 读取的文件是空文本,会出现除0的错误,就返回余弦相似度为0
try:
def calCos(v1, v2):
    try:
        vectorLength = len(v1)
        B = sum(v1[i]*v2[i] for i in range(vectorLength))
        A1 = sum(i**2 for i in v1)
        A2 = sum(i**2 for i in v2)
        A = sqrt(A1*A2)
        return B/A
    except ZeroDivisionError as e:
        print(e)
        return 0

1.命令行参数不正确

2.要读取的文件不存在

3.读取的文件是空文本

最终代码重用率为

part.Ⅶ 总结

一开始看到这个题目,就感觉到快,有催人跑的意思,所以我们现在正合适做这样的题。通过此次面向百度编程,我新认识了许多东西,github,性能分析,单元测试等等。很惭愧,too young too simple,sometimes naive!就做了一点微小的工作。最后用鹅城张牧之的一句话勉励一下自己:雄关漫道真如铁,而今迈步从头越。

posted @ 2020-09-17 22:27  家住海边所以浪  阅读(241)  评论(0编辑  收藏  举报