文本相似度

应用

现今,文本分类在生活中有非常多的应用:
  我们经常使用的百度,每次输入关键词或关键句,搜索系统匹配与输入相似的文本,反馈给我们想要看到的词条;
  或是使用的翻译工具,利用语句中每个词的语法和语义来分析,文本相似度直接影响到了翻译语句的准确性;
  再就是一些论文检测,通过对两份文本提取的关键词进行相似度分析,得出文本相似度,以检测是否存在文章抄袭的可能。

原理

大体上文本分类原理可以分为:
  1. 基于词频:一般用于句子段落这些较大粒度文本。(粒度:数据细化和综合程度)
  2. 基于语义:一般用于词语或句子等较小粒度文本。 
  
笔者这里主要介绍基于词频的方式。

流程


首先对文本进行分词,同时去除一些无关紧要的停用词;
再对我们分好的词进行出现次数的统计,提取合适数量的高频词;
通过高频词构建文本向量;
最后通过余弦相似度算法得出两个文本的相似度。

实现

  分词

我们要先明确为什么要分词?
  因为通常我们要处理的文本都是中文的,它不像英文分词就简单的以空格为分界符,而在我们中文中词是最小的能独立表达意义的单位,但词语之间没有明确的界限,所以一定要先分词,这种做法就类似于我们高中语文上的句读。

那么现在问题是怎么分词呢?
  分词方法有三:
         1. 基于字典分词;
         2. 基于词频统计分词;
         3. 基于字标记分词。
   然而大佬无处不在,在github上就可以找到很多大佬写的分词库,通过引用这些库及其方法就可以实现我们的分词了。

这里分享几个库,当然也可以自行查找。
   Jieba (C++, Java, python)https://github.com/fxsjy/jieba
   HanLP (Java)https://github.com/hankcs/HanLP
   FudanNLP (Java)https://github.com/FudanNLP/fnlp
   LTP (C++, Java, python)https://github.com/HIT-SCIR/ltp

笔者这里使用的是第一个Jieba库,在使用时我们要注意很重要的一点:
   Jieba库使用的是UTF-8的编码,不同编译器的可能会使用不同的编码格式,如果不转码强行使用就会出现乱码情况。

像我这里使用的VS2013它是GBK的编码格式,所以需要进行转换。转换的方式是通过Windows提供的两个接口:
  由于没有直接将GBK转换为UTF-8的接口,所以需要GBK先转为UTF-16,再由UTF-6转为UTF-8。

//  GBK转UTF16
int MultiByteToWideChar(
	UINT CodePage,	// 指定执行转换的字符集。可指定为:CP_ACP:ANSI字符集。 CP_UTF8:使用UTF-8转换。
	DWORD dwFlags,	// 一组位标记用以指出是否未转换成预作或宽字符(若组合形式存在)
	_In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,	// 指向将被转换字符串的字符。.
	int cbMultiByte,	// 指定由参数lpMultiByteStr指向的字符串中字节的个数。
	LPWSTR lpWideCharStr,	// 指向接收被转换字符串的缓冲区。
	int cchWideChar	// 指定由参数lpWideCharStr指向的缓冲区的宽字符个数。
);
函数功能:一个字符串到一个宽字符的字符串的映射。
返回值:函数运行成功,并且cchWideChar不为零,返回值是由lpWideCharStr指向的缓冲区中写入的宽字符数;如果函数运行成功,并且cchWideChar为零,返回值是接收到待转换字符串的缓冲区所需求的宽字符数大小。
//  UTF16转UTF8
int WideCharToMultiByte(
	UINT CodePage,	 //指定执行转换的代码页
	DWORD dwFlags,	//允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
	_In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,  //指定要转换为宽字节字符串的缓冲区
	int cchWideChar,	//指定由参数lpWideCharStr指向的缓冲区的字符个数
	LPSTR lpMultiByteStr,	//指向接收被转换字符串的缓冲区
	int cbMultiByte,	 //指定由参数lpMultiByteStr指向的缓冲区最大值
	LPCCH lpDefaultChar,	//遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
	LPBOOL lpUsedDefaultChar	 //至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
);
函数功能:映射一个unicode字符串到一个多字节字符串。
返回值:如果函数运行成功,并且cchMultiByte不为零,返回值是由 lpMultiByteStr指向的缓冲区中写入的字节数;如果函数运行成功,并且cchMultiByte为零,返回值是接收到待转换字符串的缓冲区所必需的字节数。

转换之后就需要分词,分词依然是通过Jieba库所提供的Cut方法。

例:
  seg_list = jieba.cut(“他来到了网易杭研大厦”) ; # 默认是精确模式
  print(", ".join(seg_list));
 结果: 他, 来到, 了, 网易, 杭研, 大厦

这里分词有了,但是这些词一定都是有意义的吗?或是说通过这些词的分析得出来的结论是准确的吗?
  我们可以想想,以前写过的作文,或是生活中的用语。是不是除了要表达真实含义的语句之外,我们还要有些代词(我,你,我们 …),或是些语气助词(啊,呀…),还有介词,副词,连接词等等。这些词一定会有且不占少数,但它们对于我们文章的却没有什么实际意义。因此我们要去掉这些词。

具体实现:Jieba中有STOP_WORD_PATH.c_str()停用词文件,我们可以将我们的分词与文件中的词比较,相同的就去掉。

 词频

通常我们认为一个文本中出现频率高的词,极大的能概括我们文本所要表达的意思。

具体实现:使用一个神器—> C++的STL容器中unordered_map。
     调用map中的count方法来统计我们的词频;
     map中的KV值刚好能对应我们的词和词频;
     其底层是哈希桶实现的,所以查找起来也比较快。
将统计好的[词,词频] 放入数组中,通过sort方法,依据词频进行排序。再取出一定范围内的高频词用于构建词频向量。###  词频向量
假使有两个句子:

句1: 我喜欢吃米饭,不喜欢吃面
句2: 我不喜欢吃米饭,也不喜欢吃面

列出所有词:

我 喜欢 吃 米饭 不 面 也

句1中的词频:[0:1, 1:2, 2:2, 3:1, 4:1, 5:1, 6:0]
句2中的词频:[0:1, 1:2, 2:2, 3:1, 4:2, 5:1, 6:1]

句1词频向量:[1, 2, 2, 1, 1, 1, 0]
句2词频向量:[1, 2, 2, 1, 2, 1, 1]

 向量相似度

这里介绍几种计算向量相似度的方法:
  欧几里得距离
  余弦相似度
  jaccard系数(类似余弦相似度)
  曼哈顿距离(类似欧几里得距离)
  
我呢是用余弦相似度计算的:当两个向量的夹角越小就代表越相似,所以我们可以通过计算两个向量的夹角余弦值来评估他们的相似度。
在这里插入图片描述

效果展示:

在这里插入图片描述

项目代码实现:

https://github.com/Timecur/TestSimilarity

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
namespace ServiceRanking { /// <summary> /// Summary description for TF_IDFLib. /// </summary> public class TFIDFMeasure { private string[] _docs; private string[][] _ngramDoc; private int _numDocs=0; private int _numTerms=0; private ArrayList _terms; private int[][] _termFreq; private float[][] _termWeight; private int[] _maxTermFreq; private int[] _docFreq; public class TermVector { public static float ComputeCosineSimilarity(float[] vector1, float[] vector2) { if (vector1.Length != vector2.Length) throw new Exception("DIFER LENGTH"); float denom=(VectorLength(vector1) * VectorLength(vector2)); if (denom == 0F) return 0F; else return (InnerProduct(vector1, vector2) / denom); } public static float InnerProduct(float[] vector1, float[] vector2) { if (vector1.Length != vector2.Length) throw new Exception("DIFFER LENGTH ARE NOT ALLOWED"); float result=0F; for (int i=0; i < vector1.Length; i++) result += vector1[i] * vector2[i]; return result; } public static float VectorLength(float[] vector) { float sum=0.0F; for (int i=0; i < vector.Length; i++) sum=sum + (vector[i] * vector[i]); return (float)Math.Sqrt(sum); } } private IDictionary _wordsIndex=new Hashtable() ; public TFIDFMeasure(string[] documents) { _docs=documents; _numDocs=documents.Length ; MyInit(); } private void GeneratNgramText() { } private ArrayList GenerateTerms(string[] docs) { ArrayList uniques=new ArrayList() ; _ngramDoc=new string[_numDocs][] ; for (int i=0; i < docs.Length ; i++) { Tokeniser tokenizer=new Tokeniser() ; string[] words=tokenizer.Partition(docs[i]); for (int j=0; j < words.Length ; j++) if (!uniques.Contains(words[j]) ) uniques.Add(words[j]) ; } return uniques; } private static object

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值