• 软工作业:第一次个人项目作业


    这个作业属于哪个课程 软件工程
    这个作业要求在哪里 作业要求
    这个作业的目标 第一次个人项目编程

    作业Github地址

    • GitHub链接
    • 可运行main.exe已放在Release上,需包含文件夹dict

    作业算法实现

    通过查阅资料发现很多办法解决

    • simhash+海明距离计算:可以处理大数据量的文本
    • 余弦相似度计算:短文本效率较高,但文本超过3000时候,simhash的效率高于其
    • 编辑距离计算:耗时长+计算不准确
    • jaccard系数计算:耗时较simhash长

    分词有jieba,HanLP,jsseg

    本项目采用jieba的c++版本,相似使用了simhash+海明距离进行计算。

    cppjieba:

    其中一开始遇到分词时只能分到一个个词(很明显效果不好);后面在其githun上发现是编码问题,要转化为UTF-8编码。

    std::string string_To_UTF8(const std::string& str)
    {
    	int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
    
    	wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴 
    	ZeroMemory(pwBuf, nwLen * 2 + 2);
    
    	::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);
    
    	int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
    
    	char* pBuf = new char[nLen + 1];
    	ZeroMemory(pBuf, nLen + 1);
    
    	::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
    
    	std::string retStr(pBuf);
    
    	delete[]pwBuf;
    	delete[]pBuf;
    
    	pwBuf = NULL;
    	pBuf = NULL;
    
    	return retStr;
    }
    

    转化编码后可实现正常的分词

    Simhash

    1. 分词:使用jieba分词,项目使用了jiaba的TD-IDF来进行关键词提取
    2. hash:使用c++hash库中hash函数进行计算
    3. 加权:提取的关键词(前50个),对各自的hash的64位二进制位进行加权
    4. 合并:把第三步的所有序列进行计算,最后得到一个64位01串
    5. 降维:每一位进行降维,大于0的输出1,小于0的输出0,最后得到一个64位的二进制

    海明距离

    最后得到的两个文本的64位二进制进行异或操作,最后输出的01串的1的个数即为海明距离。

    海明距离用来判断相似的话比较高效,但是相似度(0~1)的话不太准确。

    我用的是某个类似正态分布的函数来海明距离转化为一个0~1之间的数来表示相似度。

    [y=frac{1}{sqrt{2*pi*0.16}}e^{(-frac{(0.01x*0.1)^2}{2*(0.0459)^2})} ]

    结果输出

    接口与模块实现

    我只用了一个类来实现,其中这个类包含了jieba类。

    整体流程在SimHasher::isSimilarity

    void Simhasher::isSimilarity(uint32_t topk, unsigned int n)
    {
    	std::vector<uint64_t> nums;
    	for (auto& p : mFiles)
    	{
    		std::vector<std::pair<uint64_t, int>> fw;
    		fw = Participle(p.second, topk);
    		nums.push_back(CalculaterSimhash(fw));
    	}
    
    	//两两比较(当然现在只有两个文件)
    	for (size_t i = 0; i < nums.size() - 1; ++i)
    	{
    		for (size_t j = i + 1; j < nums.size(); ++j)
    		{
    			std::pair<bool, double> flag = CalcularSimilarity(nums[i], nums[j], n);
    			if (flag.first)
    			{
    				std::string str = "文件1:" + mFiles[i].first + " 与 文件二:" + mFiles[j].first + " 相似,相似度:";
    				char logStr[100]{};
    				sprintf_s(logStr, "%.2f", flag.second);
    				str += logStr;
    				std::cout << str << std::endl;
    				mLog << str << std::endl;
    			}
    			else
    			{
    				std::string str = "文件1:" + mFiles[i].first + " 与 文件二:" + mFiles[j].first + " 不相似,相似度:";
    				char logStr[100]{};
    				sprintf_s(logStr, "%0.2f", flag.second);
    				str += logStr;
    				std::cout << str << std::endl;
    				mLog << str << std::endl;
    			}
    		}
    	}
    }
    

    流程图:

    性能改进

    下面是所有分词进行计算词频的性能,发现有部分性能消耗在这。

    后序发现可以用TD-IDF来得到关键词与其权重,后序实现性能主要在其库上。

    单元测试

    下面图展示了两个单元测试的代码以及测试的结果。

    VS上代码覆盖率需要企业版/专业版,我做的时间比较匆忙,暂时只能贴上一个结果是文本相似的代码覆盖率的截图。

    可以看到基本覆盖到,有些情况我在单元测试时考虑了,如:文件打不开(不存在),文本不相似。

    异常处理

    1. 参数输入错误

      输入的文本的数,过少直接退出

      if (argc < 4)
      {
      	std::cout << "输入参数过少
      ";
      	return 0;
      }
      
    2. 文件打不开,也是直接显示并退出

      文件不存在以及打不开话,会直接退出,这个单元测试对应了这种场景。

    PSP表格

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

    总结

    1. 时间来说,果然还是比预期慢,对于算法基础这部分还是得进行学习,以及对于c++部分学习仍然有不足之处。
    2. 在做项目当中,遇到了各种奇怪的问题,其中不乏花费了一定的时间来解决,遇到问题应该分析好问题再去解决可能会更高效。
    3. 单元测试,仍有不太了解的地方,应该要继续学习。
    4. 在项目实践过程中,也学习到了很多东西,如git,性能分析等,这些也是一种很好的积累。

    作者:Ligo丶

    出处:https://www.cnblogs.com/Ligo-Z/

    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。

  • 相关阅读:
    「干货分享」我所在团队的竞品分析模板--附下载
    框架用多了真的会死人的,spring-cloud全家桶与mybitais 集成完整示例(附下载)
    聊聊区块链,虽然我不挖矿!
    从厕所排队引发的产品设计方案思考
    基于spring-boot和docker-java实现对docker容器的动态管理和监控[附完整源码下载]
    Spring-boot原理(附带实现一个spring-boot-starter实例和代码下载)
    Zookeeper+websocket实现对分布式服务器的实时监控(附源码下载)
    「干货分享」模块化编程和maven配置实践一则
    【干货分享】大话团队的GIT分支策略进化史
    项目协作管理平台-teambition和tapd--深度体验
  • 原文地址:https://www.cnblogs.com/Ligo-Z/p/13727098.html
Copyright © 2020-2023  润新知