项目原理
基于词频:统计文章中词频,构建词频特征向量,利用特征向量夹角的余弦值表示文本的相似度。两篇文章最大相似度为1,特征向量夹角为0°。
基于词频的文本相似度检测步骤:
- 文本1和文本2
- 分词—去停用词
- 统计两篇文章的词频
- 词频向量1和词频向量2
- 相似度的计算
- 分词:例:“我是一个学生” 分词后为“我//是//一个//学生”
- 去停用词 分词后可发现有很多词是没有实际含义的功能性词,例如“我” 、“怎么办”、“总之”。因此分词后不能直接对词频进行统计,应先去掉停用词后,再对词频进行统计。
- 词频 单词在文章中出现的次数,词频越大,可以认为该词越重要
- 词频向量在构建词频向量时,需要考虑向量的意义,也必须保障向量的一致性,即两个文本向量每一维的值应该代表的是同一个词的词频。
例:
文档1 今天//有事//,//没办法//去//学校//上课//了
文档2 真想//去//学校//上课//,//但是//今天//有事//,//去不了//学校//了
去掉停用词后
文档1中的词频:[有事1,没办法1,去1,学校1,上课1]
文档2中的词频:[真想1,去1,学校2,上课1,有事1,去不了1]
直接用上述词频构建每一个文本的词频向量是无意义的,因为每一维表示的意思均不同,因此需构建一致的词频向量。
将所有的有效词结合起来构建[学校,去,真想,上课,有事,去不了,没办法]
计算词频向量:
文档1 :[1,1,0,1,1,0,1] 文档2:[2,1,1,1,1,1,0] - 向量相似度 采用余弦相似度
在TextSimilarity类中对各个接口进行声明
class TextSimilarity
{
public:typedef std::unordered_map<std::string, double> WordFrep;//词频typedef std::unordered_set<std::string> WordSet;//词表typedef std::pair<std::string, double> PSI;//键值对TextSimilarity(const char* dictPach);//构造函数TextSimilarity(Config& cfg);void printStopWordSet();//打印停用词表void printWordFrep(const WordFrep& wordFreq);//打印词频double getTextSimilarity(const char* file1, const char* file2);//提供获取文本相似度的接口//private: //获取停用词表void getStopWordSet(const char*file);//获取IDF(逆文档率),表示每个词重要性的权重void getIDF(const char* file);//统计词频: word num(map中序遍历是有序的,本身是无序的)//项目本身需要Value有序,但map不支持,所以使用无序的map,效率较高void getWordFrep(const char* file, WordFrep& wordFreq);//统计相对词频,单词词频/文章单词总数void getNormalizedWordFrep(WordFrep& wordFreq);//统计加入IDF权重的词频void getTfIdf(WordFrep& wordFreq, WordFrep& outTfIdf);//GBK转UTF8std::string GBK2UTF8(const std::string& gbk);//UTF8转GBKstd::string UTF82GBK(const std::string& utf8);//根据Value值进行排序void sortReverseByValue(const WordFrep& wordFreq, std::vector<PSI>& outSortedWordFreq);//构建统一的词表void getWordCode(std::vector<PSI>& inSortWordFreq, WordSet& wordCode);//根据词表,创建词频向量void getVector(WordSet& wordCode, WordFrep& wordFreq, std::vector<double>& outVec);//计算文本相似度double getCosine(std::vector<double>& vec1, std::vector<double>& vec2);//分词对象成员std::string DICT_PATH;//词典路径cppjieba::Jieba _jieba;//分词对象//停用词表,只需要存放停用词,用string(字符串)来存储,给一个哈希表WordSet _stopWordSet;int _maxWordNum;WordFrep _Idf;
};
成员变量有5个,分别是分词的词典路径,分词对心,停用词表,构建统一词表的最大个数_maxWordNum,以IDF方式计算的词频_Idf;
IDF(Inverse Document Frequency): 逆文档率,它表示每一个词重要性的权重,一个词越少见,它的值就越大,反之,一个词越常见,它的值就越小。
下面分别实现各个接口的功能:
//构造函数:
TextSimilarity::TextSimilarity(const char* dictPach):DICT_PATH(dictPach), _jieba(DICT_PATH + "/jieba.dict.utf8", DICT_PATH + "/hmm_model.utf8", DICT_PATH + "/user.dict.utf8", DICT_PATH + "/idf.utf8", DICT_PATH + "/stop_words.utf8"), _maxWordNum(20)//统一的词表最大的个数,初始化为20
{ std::string stopFileDir = DICT_PATH + "/stop_words.utf8";getStopWordSet(stopFileDir.c_str());getIDF((DICT_PATH + "/idf.utf8").c_str());
}
//获取停用词,填充在对象_stopWordSet中
void TextSimilarity::getStopWordSet(