如何设计一个比较两篇文章相似性的算法?
假如我们想得到更多的局部信息,如相似片段、相似百分比,那又该如何去做?
任何idea都可以分享
如果是话题是否相似,一般是关键词匹配的方法
想了一种基于统计模型的算法,不知道实际效果如何:
首先收集足够多的样本,分词,统计各个词的频度(文章中出现次数 / 总词数),
然后计算每个词的平均频度(频度和 / 文章数)和频度方差((频度 - 平均值) ^ 2 / (文章数 - 1))
即将每个词的出现频度建模为一个高斯随机变量。
然后对要比较的文章先分词,统计频度,然后计算对数似然比:
ln(p(A,B独立) / p(A,B同类))
= ln(p(A)P(B) / p(A,B同类))
= lnp(A) + lnp(B) - lnp(A,B同类)
将概率p用似然值P替代,根据高斯变量的特性:
lnP(A) = -1/2 * ∑((D_Ak - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k))
lnP(B) = -1/2 * ∑((D_Bk - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k))
lnP(A,B同类) = - 1/2 * ∑((D_Ak - D_Bk)^2 / Sigma_0k + ln(2 * PI * Sigma_0k))
其中D_Ak是A中词k的频度,D_Bk是B中词k的频度,D_k是平均频度,Sigma_k
是方差,而Sigma_0k是某组参数,代表同类文章 中的词频方差(或者说接受多接
近的词频为同类),可以用人工标记训练的方法得到,也可以简单点所有的都设为
一个常数,或者设为Sigma_k的一个倍数。
则对数似然比可以写为:
ln(p(A,B独立) / p(A,B同类))
= -1/2 * ∑((D_Ak - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k)) -
1/2 * ∑((D_Bk - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k)) +
1/2 * ∑((D_Ak - D_Bk)^2 / Sigma_0k + ln(2 * PI * Sigma_0k))
= -1/2 * ∑((D_Ak - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k) +
(D_Bk - D_k)^2 / Sigma_k + ln(2 * PI * Sigma_k) -
(D_Ak - D_Bk)^2 / Sigma_0k - ln(2 * PI * Sigma_0k))
计算出的这个结果就可以当做两篇文章的相似度来看。值越小,两篇文章相
似度越高;否则相似度越低。
和直接计算∑(D_Ak - D_Bk)^2的方法比起来,充分考虑了不同词的关键性
作用不同,比较关键的词会有比较高的加权。
余弦定理和新闻的分类似乎是两件八杆子打不着的事,但是它们确有紧密的联系。
具体说,新闻的分类很大程度上依靠余弦定理。
Google 的新闻是自动分类和整理的。所谓新闻的分类无非是要把相似的新闻放
到一类中。计算机其实读不懂新闻,它只能快速计算。这就要求我们设计一个算
法来算出任意两篇新闻的相似性。为了做到这一点,我们需要想办法用一组数字
来描述一篇新闻。
我们来看看怎样找一组数字,或者说一个向量来描述一篇新闻。回忆一下我们在
“如何度量网页相关性”一文中介绍的TF/IDF 的概念。对于一篇新闻中的所有实词,
我们可以计算出它们的单文本词汇频率/逆文本频率值(TF/IDF)。不难想象,和新
闻主题有关的那些实词频率 高,TF/IDF 值很大。我们按照这些实词在词汇表的位
置对它们的 TF/IDF 值排序。比如,词汇表有六万四千个词,分别为
- 单词编号 汉字词
- ------------------
- 1 阿
- 2 啊
- 3 阿斗
- 4 阿姨
- ...
- 789 服装
- ....
- 64000 做作
在一篇新闻中,这 64,000 个词的 TF/IDF 值分别为
- 单词编号 TF/IDF 值
- ==============
- 1 0
- 2 0.0034
- 3 0
- 4 0.00052
- 5 0
- ...
- 789 0.034
- ...
- 64000 0.075
如果单词表中的某个次在新闻中没有出现,对应的值为零,那么这 64,000 个数,
组成一个64,000维的向量。我们就用这个向量来代表这篇新闻,并成为新闻的特
征向量。如果两篇新闻的特征向量相近,则对应的新闻内容相似,它们应当归在一
类,反之亦然。
学过向量代数的人都知道,向量实际上是多维空间中有方向的线段。如果两个向量
的方向一致,即夹角接近零,那么这两个向量就相近。而要确定两个向量方向是否
一致,这就要用到余弦定理计算向量的夹角了。
余弦定理对我们每个人都不陌生,它描述了三角形中任何一个夹角和三个边的关系,
换句话说,给定三角形的三条边,我们可以用余弦定理求出三角形各个角的角度。
假定三角形的三条边为 a, b 和 c,对应的三个角为 A, B 和 C,那么角 A 的余弦 --
如果我们将三角形的两边 b 和 c 看成是两个向量,那么上述公式等价
其中分母表示两个向量 b 和 c 的长度,分子表示两个向量的内积。举一个具体的
例子,假如新闻 X 和新闻 Y 对应向量分别是
x1,x2,...,x64000 和 y1,y2,...,y64000,
那么它们夹角的余弦等
当两条新闻向量夹角的余弦等于一时,这两条新闻完全重复(用这个办法可以删除
重复的网页);当夹角的余弦接近于一时,两条新闻相似,从而可以归成一类;夹
角的余弦越小,两条新闻越不相关。
如果我们并不关注具体的相似片段的话,可以用hash的办法来解决,
simhash算法貌似是一个简明有效的算法。算法的原理是这样的:
simhash算法的输入是一个向量,输出是一个f位的签名值。为了陈述
方便,假设输入的是一个文档的特征集合,每个特征有一定的权重。
比如特征可以是文档中的词,其权重可以是这个词出现的次数。
simhash算法如下:
1. 将一个f维的向量V初始化为0;f位的二进制数S初始化为0;
2. 对每一个特征:用传统的hash算法对该特征产生一个f位的签名b。
对i=1到f:
如果b的第i位为1,则V的第i个元素加上特征的权重;
否则,V的第i个元素减去该特征的权重。
3. 如果V的第i个元素大于0,则S的第i位为1,否则为0;
4. 输出S作为签名。
通过计算两篇文章的签名的海明距离得出相似度。
如图:
以上的所有算法我们都只关注文章的全局信息,忽略了文章的局部信息,
如果我们现在需要知道文章的具体相似部分、相似片段、相似百分比,
就像论文查重所做的那样,那么应该使用什么样的算法?
我觉得可以采用神经网络中hebb rule的方法。
对于任意一片文章我们都可以抽象成一个列数为Q的特征向量P=[p1,p2,p2,.....pq]
存在权重矩阵W使得Wp=a,a是一个输出向量因此可以发现一组关系为{p1,t1}{p2,t2}....{p_q,t_q}
换言之,当网络的输入参数为p=p_q时,输出应当为a=t_q
hebb rule:
W_new = W_old + αf_i(a_iq)g_j(p_jq)
推算可得W_new = W_old+t_qp_q_tranpose
(抱歉下标什么的写不出来……)
当初始W矩阵为零时可得
W=p cross t_transpose
其中P=[p1,p2,p3,p4,p5...pq] t=[t1,t2,t3,t4,t5....tq]
a=Wp_k=t_k
以下是一个例子
public static Vector hardlims(Vector v){ double[] a = new double[v.size()]; for(int i =0;i<v.size();i++){ a[i]=v.get(i)>0?1:-1; } Vector res = new DenseVector(a); return res; } public static void main(String[] args){ String v1= "你好吗,我好"; String v2= "你好吗,我不舒服"; String v3 = "我好"; String v4 = "我不舒服"; Matrix W = null; double[] d1= {1,1,1,1,-1}; double[] d2 = {1,1,1,-1,1}; double[] d3 = {0,0,1,1,-1}; double[] d4 = {0,0,1,-1,1}; Vector p1 = new DenseVector(d1); Vector p2 = new DenseVector(d2); //W=ΣPT=p1p1+p2p2,在这里由于预测输出值就是我们所要判定的文章特征向量所以t=p W = p1.cross(p1).plus((p2.cross(p2))); Vector p3 = new DenseVector(d3); Vector p4 = new DenseVector(d4); //a=hardlims(Wp) Vector a1 = W.times(p1); Vector a2 = W.times(p2); Vector a3 = W.times(p3); Vector a4 = W.times(p4); System.out.println(hardlims(a1)); System.out.println(hardlims(a2)); System.out.println(hardlims(a3)); System.out.println(hardlims(a4)); }
输出结果:
{0:1.0,1:1.0,2:1.0,3:1.0,4:-1.0}
{0:1.0,1:1.0,2:1.0,3:-1.0,4:1.0}
{0:1.0,1:1.0,2:1.0,3:1.0,4:-1.0}
{0:1.0,1:1.0,2:1.0,3:-1.0,4:1.0}
可以看出输入"我好"会自动对应找出"你好吗,我好",输入"我不舒服"会自动找出"你好吗,我不舒服"
http://www.dewen.io/q/6668/%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%AF%94%E8%BE%83%E4%B8%A4%E7%AF%87%E6%96%87%E7%AB%A0%E7%9B%B8%E4%BC%BC%E6%80%A7%E7%9A%84%E7%AE%97%E6%B3%95%EF%BC%9F