这个作业属于哪个课程 | 广工2023软件工程 |
---|---|
这个作业要求在哪里 | 个人项目作业-论文查重 |
这个作业的目标 | 设计一个论文查重算法 |
其他参考文献 | · 从0到1,了解NLP中的文本相似度 · 一文详解 MD5 信息摘要算法 · 使用simhash计算文本相似度 |
文章目录
一、仓库地址
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 15 |
· Estimate | 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | 1180 | 1410 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 240 |
· Design Spec | · 生成设计文档 | 40 | 60 |
· Design Review | · 设计复审 | 5 | 15 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
· Design | · 具体设计 | 200 | 200 |
· Coding | · 具体编码 | 500 | 550 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 300 |
Reporting | 报告 | 80 | 100 |
· Test Repor | · 测试报告 | 40 | 60 |
· Size Measurement | · 计算工作量 | 20 | 25 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 15 |
·合计 | 1275 | 1525 |
三、模块接口的设计与实现过程
3.1 设计原理
对于大文本计算时,simhash比较文本相似度速度快。为了在爬取网页时用于快速去重,Google发明了一种快速衡量两个文本集相似度的算法:simhash。
simhash中使用了一种局部敏感型的hash算法。所谓局部敏感性hash,与传统hash算法不同的是(如MD5,当原始文本越是相似,其hash数值差异越大),simhash中的hash对于越是相似的内容产生的签名越相近。计算出simhash值后,再计算hash值的汉明距离,结合杰卡德系数,即可得到文本的相似性。
3.2 算法的实现过程
-
第一步,分词——本项目使用hanlp分词的手段:
对文本进行分词操作,同时返回当前词组在文本内容中的权重 -
第二步,计算hash——本项目使用MD5算法,将词语转换成32位数字字母混合码,再转化成128位hash:
对于每一个得到的词组做hash,将词语表示为用01表示的bit位,需要保证每个hash结果的位数相同。
MD5是一种信息摘要算法。一个字符串或文件或压缩包,执行md5后,可以生成一个固定长度为128bit的串。而且这个串基本上是唯一的。
-
第三步,加权——利用HanLP.extractKeyword方法过滤关键词并得到词频:
根据每个词组对应的权重,对hash值做加权计算(bit为1则取为1做乘积,bit为0则取为-1做乘积) -
第四步,纵向相加:
将上述得到的加权向量结果,进行纵向相加实现降维 -
第五步,归一化、降维:
将最终降维向量,对于每一位大于0则取为1,否则取为0,这样就能得到最终的simhash的指纹签名 -
第六步,相似度比较——本项目结合汉明距离和杰卡德系数得到相似度
——关于杰卡德系数的参考博客
3.3 项目的结构
项目分为主类和测试类两个大类,其中主类还放置了两个包,一个是存放着异常处理类的enceptions包,另一个是存放计算相关的工具类和处理文件的工具类的utils包。
- 项目的结构:
- 模块设计:
- 算法的关键之处:
1.使用hanlp分词手段;
2.使用MD5算法获取hash值;
3.计算海明距离并使用杰拉德系数计算文本相似度。
四、模块接口的性能改进
4.1概览
内存大概占用270MB。
4.2 所有对象视图
4.3 调用消耗
以MainTest.getSimilarity2为例
从方法调用中可以看出,在进行相似度计算时,calculateSimHash使用了56.6%的资源,其中wordHash和hashWeight的遍历就分别占用了38%和17.8%,遍历共占用了56.5%的资源;hanlp的文本分析使用了42.4%的资源,其中关键字提取占用了33.1%,统计词频占用了8.5%。
4.4 性能改进
- 减少遍历次数:由上述分析,计算simhash值过程中大量使用了遍历,占用了许多资源。因此减少遍历次数可以提升算法效率。
- 减少hash码位数:本项目中使用MD5算法将词语转化为不重复的唯一的128位二进制编码,但关键词太多会导致消耗大量存储空间存储数值。因此我认为可以减少哈希码位数,减少存储空间的浪费。
- 改变分词方法:本项目中hanlp分词方法占用了42.4%的资源,如果使用其他较为简单的分词方法,将减少资源的消耗。
4.5 消耗最大函数展示
static final int HASH_BIT = 128;
/**
* 采用MD5算法对关键词进行hash,得到的hash值使用16进制解析,再利用算法取128位二进制数作为hash值
* @param word 词语
* @return 128位二进制hash值
*/
public static String wordHash(String word) throws HashException {
//如果传入词语为null或“”或“ ”
if (word == null || StrUtil.isBlank(word) || StrUtil.isEmpty(word)) {
throw new HashException("词语为空");
}
try {
// 采用MD5算法进行hash
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(word.getBytes(StandardCharsets.UTF_8));
// hash值转为32位16进制的散列值
StringBuilder hash = new StringBuilder();
for (byte b : digest.digest()) {
hash.append(String.format("%02x", b));
}
// 16进制的散列值转为128位二进制码
StringBuilder finalHash = new StringBuilder();
String strTemp;
for (int i = 0; i < hash.length(); i++) {
// 每一位16进制数加上0000,最后截取后4位,得到便是这位数的二进制
strTemp = "0000" + Integer.toBinaryString(Integer.parseInt(hash.substring(i, i + 1), 16));
finalHash.append(strTemp.substring(strTemp.length() - 4));
}
// 不为128则为hash异常
if (finalHash.length() != HASH_BIT) {
throw new HashException("hash值长度不为128");
}
return finalHash.toString();
} catch (NoSuchAlgorithmException e) {
throw new HashException("MD5算法异常");
}
}
五、模块部分单元测试展示
共有15个测试,全部通过。
5.1 单元测试覆盖率
类覆盖率和方法覆盖率100%,行覆盖率89%
5.2 部分测试展示
- 测试写入文件:测试在结果文件写入内容
- 测试读取不存在的文件,找不到指定文件应该抛出异常
- 测试读取文件并查看分词结果
- 测试MD5算法计算hash值
- 测试加权算法
- 测试计算simHash
- 测试计算句子相似度
- 测试计算文本相似度
- 测试6个测试样例
测试用例测试结果写入write.txt:
六、模块部分异常处理说明
6.1 FileAnalyseException 测试解析文件异常
- 转字符串为空或关键词太少
6.2 HashException 测试哈希异常
- hash位数不足128位
6.3 NotExistFileException 测试文件不存在异常
- 读取不存在的文件
七、传入命令行参数运行程序
- 以下是6个测试用例的比对结果:
- 结果写入write.txt