文本相似度

这是一个计算两篇文章的相似度的小工具

这个小工具的主要原理是基于词频的文本相似度的计算,主要的步骤分为下面几步

  1. 首先下载一个存放停用词的文本文件,然后将这些停用词存放在set中(因为停用词是一些不能表达文章意思的词汇)
  2. 利用jieba对两篇文章进行分词,并且将分好的这些词和对应的数目保存在map中(其中如果遇到停用词的话,就将不要将这个词放入map中)(为什么要进行分词,因为这个文本相似度的计算是基于词频的计算,而且一个词语可以表达一个完整的意思)
  3. 对map中的词进行排序,按照每个词出现的次数从大到小排序
  4. 在文章1中选出前n个的词(候选词),在文章2中选出前n个词(因为这些词在文章中出现的次数最多,可以基本的概括出文章的意思,所以如果一个文章中的候选词在另一个文章中也大量出现的话,代表这两篇文章的相似度高)
  5. 将这个两个文章的前n个词放入set中(因为两个文章选出来的词是可能会重复的,而set可以达到去重的效果)
  6. 然后在文章1 中查出这些词的数目,将每个词对应出现的次数保存在数组中,然后再文章2中查出这些词的数目,将每个此对应的数目保存在另一个数组中
  7. 利用余弦相似度的计算公式来计算出文章的相似度

TestSimilarity.cpp

#include"TestSimilarity.h"
#include "cppjieba/Jieba.hpp"
using namespace std;

//1、首先获取停用词
//2、对文件中的文章进行分词,计算出关键字的数量,并保存在
//3、对关键字按照数目进行排序
//4、按照顺序选出两个文件中的候选词
//5、用候选词的数目制作向量表
//6、根据余弦相似度的公式计算出相似度


string TextSimilarity::GBKToUTF8(string p)
{
	int len = MultiByteToWideChar(CP_ACP, 0, p.c_str(), -1, NULL, 0);
	wchar_t *uft16 = new wchar_t[len];
	MultiByteToWideChar(CP_ACP, 0, p.c_str(), -1, uft16, len);
	len = WideCharToMultiByte(CP_UTF8, 0, uft16, -1, NULL, 0, NULL, NULL);
	char *a = new char[len];
	WideCharToMultiByte(CP_UTF8, 0, uft16, -1, a, len, NULL, NULL);
	string str(a);
	return str;
}

string TextSimilarity::UTF8ToGBK(string p)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, p.c_str(), -1, NULL, 0);
	wchar_t *uft16 = new wchar_t[len];
	MultiByteToWideChar(CP_UTF8, 0, p.c_str(), -1, uft16, len);
	len = WideCharToMultiByte(CP_ACP, 0, uft16, -1, NULL, 0, NULL, NULL);
	char *a = new char[len];
	WideCharToMultiByte(CP_ACP, 0, uft16, -1, a, len, NULL, NULL);
	string str(a);
	return str;
}

//获取停用词
//1、打开文件,获取文件中的每一行的停用词
//2、将停用词保存在set中
void TextSimilarity::getStopWordTable(const char* stopWordFile)
{
	ifstream fin(stopWordFile);
	if (!fin.is_open())
	{
		cout << "open file2 error" << endl;
		return;
	}
	string ptr;
	while (!fin.eof())
	{
		//因为文件中没有停用词占一行,所以读取一个停用词
		getline(fin, ptr);
		//将读取的停用词保存在set中
		_stopWordSet.insert(ptr);
	}
	fin.close();
}
//构造函数
TextSimilarity::TextSimilarity(string dict)
	:DICT(dict)
	, DICT_PATH(dict + "/jieba.dict.utf8")
	, HMM_PATH(dict + "/hmm_model.utf8")
	, USER_DICT_PATH(dict + "/user.dict.utf8")
	, IDF_PATH(dict + "/idf.utf8")
	, STOP_WORD_PATH(dict + "/stop_words.utf8")
	, _jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH)
	, _maxWordNumber(15)//这里我设置的是选出每个文章中出现数目最多的前15个词,这个数目是根据分词后的词语的数目等因素确定的。
{
	//初始化停用词
	getStopWordTable(STOP_WORD_PATH.c_str());
}
//比较函数
bool cmpREserve(pair<string, int>lp, pair<string, int>rp)
{
	return lp.second > rp.second;
}

//对文件中的关键字按照数目进行排序
vector<std::pair<std::string, int>> TextSimilarity::sortByValueReverse(TextSimilarity::wordFreq& wf)
{
	//因为sort只能对序列式的数组进行排序,所以将map中的数据保存在二维数组中进行排序
	vector<std::pair<std::string, int>> wfvector(wf.begin(), wf.end());
	sort(wfvector.begin(), wfvector.end(), cmpREserve);
	return wfvector;
}
//分词、计算出词频
TextSimilarity::wordFreq TextSimilarity::getWordFreq(const char *file)
{
	ifstream fin(file);
	if (!fin.is_open())
	{
		cout << "open file1 error" << endl;
		return wordFreq();
	}
	string line;
	wordFreq wf;
	while (!fin.eof())
	{
		getline(fin, line);
		//首先将文件的格式转换为哦UTF8的格式,然后进行分词
		line = GBKToUTF8(line);
		vector<string> words;
		//分词、将关键字保存在word文档中
		_jieba.Cut(line, words, true);
		for (const auto & e : words)
		{
			//如果是当前词是停用词的话,直接跳过
			if (_stopWordSet.count(e))
				continue;
			else
			{
				//如果这个关键字已经保存的话,将他的数目++
				if (wf.count(e))
					wf[e]++;
				//如果这个关键字是第一次出现的话,将他的数目设置成1
				else
					wf[e] = 1;
			}
		}
	}
	return wf;
}

//选择候选词
void TextSimilarity::selectAimWords(std::vector<std::pair<std::string, int>>& wfvec, TextSimilarity::wordSet& wset)
{
	int len = wfvec.size();
	int num = _maxWordNumber;
	//如果这个文件中的关键字的数量小于候选词的规定数目的时候,此时将按照关键字的数目来选择候选词
	if (_maxWordNumber > len)
		num = len;
	//将候选词的名字保存在set中
	for (int i = 0; i < num; i++)
	{
		wset.insert(wfvec[i].first);
	}
}

//获取向量表
vector<double> TextSimilarity::getOneHot(TextSimilarity::wordSet& wset, TextSimilarity::wordFreq& wf)
{
	vector<double> str;
	//根据候选词数目的来将候选词的数目保存在向量表中
	for (auto & e : wset)
	{
                //如果这个词在文章中出现的话,将这个次数数目保存在数组中
		if (wf.count(e))
		{
			str.push_back(wf[e]);
		}
                //如果这个词没有出现的话,就将这个次数数目设置为0
		else
		{
			str.push_back(0);
		}
	}
	return str;
}

//计算相似度
double TextSimilarity::cosine(std::vector<double> oneHot1, std::vector<double> oneHot2)
{
	double ptr = 0.0;
	double deniminator = 0;
	double deniminator2 = 0;
	for (int i = 0; i < oneHot1.size(); i++)
	{
		ptr += (oneHot1[i] * oneHot2[i]);
	}
	for (int i = 0; i < oneHot1.size(); i++)
	{
		deniminator += (oneHot1[i] * oneHot1[i]);
	}
	for (int i = 0; i < oneHot1.size(); i++)
	{
		deniminator2 += (oneHot2[i] * oneHot2[i]);
	}
	return (ptr / (sqrt(deniminator) * sqrt(deniminator2)));
}


double TextSimilarity::getTextSimilarity(const char* file1, const char* file2)
{
	TextSimilarity wf("dict");
	std::unordered_map<std::string, int> ptr = wf.getWordFreq(file1);//获取文章1中的词语及其个数
	std::unordered_map<std::string, int> ptr2 = wf.getWordFreq(file2);//获取文章2中的词语的及其个数
	std::vector<std::pair<std::string, int>> wfvec = wf.sortByValueReverse(ptr);//将词语按照数目进行排序
	std::vector<std::pair<std::string, int>> wfvec2 = wf.sortByValueReverse(ptr2);
	std::unordered_set<std::string> str;//用来获取文章1和文章2中的候选词
	wf.selectAimWords(wfvec, str);
	wf.selectAimWords(wfvec2, str);
	std::vector<double> oneHont = wf.getOneHot(str, ptr);//在文章1中找出str中每个词出现的次数
	std::vector<double> oneHont2 = wf.getOneHot(str, ptr2);//在文章2中找出str每个词出现的次数
	return wf.cosine(oneHont, oneHont2);//根据余弦相似度计算出相似度
}

TestSimilarity.h

#pragma once

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <unordered_map>
#include <string>
#include <unordered_set>
#include <cppjieba/jieba.hpp>
#include<fstream>
#include<Windows.h>

class TextSimilarity
{
public:
	typedef std::unordered_map<std::string, int> wordFreq;
	typedef std::unordered_set<std::string> wordSet;
	TextSimilarity(std::string dict);
	double getTextSimilarity(const char* file1, const char* file2);
	void getStopWordTable(const char* stopWordFile);
	wordFreq getWordFreq(const char* file);
	std::string UTF8ToGBK(std::string str);
	std::string GBKToUTF8(std::string str);
	std::vector<std::pair<std::string, int>> sortByValueReverse(wordFreq& wf);
	void selectAimWords(std::vector<std::pair<std::string, int>>& wfvec, wordSet& wset);
	std::vector<double> getOneHot(wordSet& wset, wordFreq& wf);
	double cosine(std::vector<double> oneHot1, std::vector<double> oneHot2);
private:
	std::string DICT;
	std::string DICT_PATH;
	std::string HMM_PATH;
	std::string USER_DICT_PATH;
	std::string IDF_PATH;
	std::string STOP_WORD_PATH;
	cppjieba::Jieba _jieba;

	wordSet _stopWordSet;
	int _maxWordNumber;
};

test.cpp

#include"TestSimilarity.h"
using namespace std;
int main(int argc, char *argv[])
{
	TextSimilarity wf("dict");
	cout << "这两个文件的相似度为" << wf.getTextSimilarity(argv[1], argv[2]) << endl;
	return 0;
}


 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值