如何設計一個比較兩篇文章相似性的算法?
假如我們想得到更多的局部信息,如相似片段、相似百分比,那又該如何去做?
任何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