github地址:https://github.com/yingnothing/first.git
个人项目-论文查重
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/ |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13229 |
GitHub链接 | https://github.com/yingnothing/first.git |
这个作业的目标 | 实现论文查重算法 |
需求分析
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
- 原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
- 抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:> >
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 320 | 360 |
Development | 开发 | 200 | 235 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 60 |
· Design Spec | · 生成设计文档 | 10 | 10 |
· Design Review | · 设计复审 | 5 | 5 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 15 |
· Design | · 具体设计 | 20 | 20 |
· Coding | · 具体编码 | 60 | 70 |
· Code Review | · 代码复审 | 10 | 10 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 35 |
Reporting | 报告 | 100 | 105 |
· Test Repor | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 20 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
· 合计 | 320 | 360 |
三、算法与接口设计
流程图
1、接口部分
2、算法分析
1).读取文件
def read_file(file_path):with open(file_path, 'r', encoding='utf-8') as file:return file.read()
read_file函数将读取file_path文件里面的内容并作为返回值返回出去
2).文本预处理
def preprocess(text):text = re.sub(r'[^\w\s]', '', text) # 去除标点符号words = list(jieba.cut(text)) # 使用 jieba 进行中文分词return words
preprocess函数将对文本进行预处理,实现逻辑如下
text = re.sub(r'[^\w\s]', '', text)
- 作用: 这是用来去除文本中的标点符号和特殊字符,只保留字母、数字和空白字符(如空格、换行等)。
- 详细解释:
re
是 Python 的正则表达式模块,re.sub()
用于替换匹配到的内容。- 正则表达式
r'[^\w\s]'
:\w
:表示匹配所有字母、数字和下划线(a-z
,A-Z
,0-9
,_
),也包括中文字符。\s
:表示匹配所有空白字符,包括空格、制表符、换行符等。[^...]
:表示取反,匹配除\w
和\s
之外的所有字符,也就是标点符号和特殊符号。
- 替换效果: 将所有标点符号和特殊字符替换为空字符串(即删除它们),只保留字母、数字和空白字符
words = list(jieba.cut(text))
jieba.cut(text)
是 jieba
的分词函数,它会将输入的 text
(处理后的中文文本)切分成一个生成器对象,生成器可以迭代出每一个分词结果。
list()
用于将生成器对象转换为一个列表,以便后续使用。
3).统计词的出现次数,生成词频向量
def get_word_frequency_vector(words):# 调用Counterword_freq = Counter(words)return word_freq
计算每个词出现的次数,生成词频向量
4).计算生成的两组词频向量的余弦相似度
def calculate_cosine_similarity(vector1, vector2):# 计算余弦相似度dot_product = sum(vector1[word] * vector2.get(word, 0) for word in vector1)magnitude1 = math.sqrt(sum([count ** 2 for count in vector1.values()]))magnitude2 = math.sqrt(sum([count ** 2 for count in vector2.values()]))if magnitude1 == 0 or magnitude2 == 0:return 0.0return dot_product / (magnitude1 * magnitude2)
通过公式:
计算出余弦相似度
5).将余弦相似度转化为重复率
def calculate_repetition_rate(cosine_similarity):return round(cosine_similarity * 100, 2)
计算重复率,通过round方法四舍五入计算到2位小数
三、性能分析
使用 cProfile 来分析程序性能,记录函数的耗时情况。
表格列的含义:
- ncalls:
- 表示函数被调用的次数。
- tottime:
- 函数本身执行的时间,不包括它调用其他函数所花的时间。这个值用于识别函数自身的性能问题。
- percall:
tottime / ncalls
,即函数每次调用的平均执行时间。
- cumtime:
- 累计时间,表示该函数自身的执行时间加上它调用的所有其他函数的执行时间。
- 这个值非常有用,可以识别出哪些函数占用了总执行时间的大部分。
- filename
- 该函数的定义位置,包括文件名、行号和函数名称。
表格中的信息按累计时间(cumulative time)排序,从耗时最多的函数开始,帮助识别程序中的性能瓶颈
- 最耗时的函数:
- 表中最上面的函数
preprocess
函数调用了 2 次,但它的累计时间为 1.383 秒,表明这是代码中最耗时的部分。 - 这也说明了
preprocess
函数是代码的性能瓶颈,处理两个文本的预处理耗费了较多时间。
- 表中最上面的函数
- 细化的耗时函数:
cut
函数在文件__init__.py
第 289 行被调用 11120 次,累计时间为 1.381 秒,几乎占据了preprocess
的大部分时间。__cut_DAG
这个函数是cut
的一部分,调用了 10306 次,它的累计时间是 1.374 秒,也很接近总时间,表明这个函数是cut
内部的一个重要步骤。
- 初始化和加载模型:
initialize
和marshal.load
这两个函数分别花费了 1.225 秒 和 1.219 秒,它们是模型加载时的初始化步骤。- 模型加载是处理文本的一部分,这些函数只执行一次,但耗时较多。
结论:
- 程序中的性能瓶颈主要在 文本预处理(preprocess),特别是在使用
jieba
库进行分词时。 cut
和__cut_DAG
是最耗时的操作,它们在分词过程中被频繁调用。优化这些部分可能会显著改善性能。- 模型初始化 (
initialize
和marshal.load
) 的时间也比较长(大约 1.2 秒)。这是加载分词模型时的开销,虽然只发生一次,但对总执行时间影响较大。
可以进行的优化:
缓存分词结果:如果文本较大,可以考虑对分词结果进行缓存,避免每次处理文本都重新加载模型并进行分词。
并行化:在处理大量文本时,可以考虑使用多线程或多进程来并行处理文本,减少单次分词的时间消耗。
模型预加载:如果分词模型加载时间较长,可以在程序启动时预先加载模型,减少后续分词过程中的加载时间。
四、异常处理:
def read_file(file_path):try:with open(file_path, 'r', encoding='utf-8') as file:return file.read()except Exception as e:print(f"Error reading file: {str(e)}")sys.exit(1)
当读取文件的路径不存在时则会捕获异常并退出程序
def write_output(file_path, result):try:# 提取目录路径directory = os.path.dirname(file_path)# 如果目录不存在,则创建目录if directory and not os.path.exists(directory):os.makedirs(directory, exist_ok=True)# 尝试写入结果到文件with open(file_path, 'w', encoding='utf-8') as file:file.write(f"{result}%")except OSError as e:# 捕获由于无效路径或文件名导致的错误print(f"Error: Could not write to file '{file_path}'. Reason: {str(e)}")sys.exit(1)
当作为存取输出的文件不存在时会自动创建文件,如果路径错误则会捕获异常并退出程序
五、测试代码覆盖率
使用 Python 的 unittest 模块进行单元测试,具体代码在github上面,这里只放测试结果
通过对preprocess,get_word_frequency_vector,calculate_cosine_similarity,calculate_repetition_rate函数进行测试,其中calculate_cosine_similarity函数要注意测试余弦值等于0的情况。如图所示结果符合预期,且代码覆盖率达到100%
最后:
生成 HTML 格式的覆盖率报告:
代码无任何错误或警告