四、文檔的局部敏感哈希算法(LSH)
即使可以使用最小哈希將大文檔壓縮成小的簽名並同時保持任意對文檔之間的預期相似度,但是高效尋找具有最大相似度的文檔對仍然是不可能的。主要原因在於,即使文檔本身的數目並不是很大,但是需要比較的文檔對的數目可能很大。
例如:假定有100萬篇文檔,每篇文檔使用的簽名的長度爲250,則每篇文檔需要250*4 bytes來表示簽名。所有100萬篇文檔的簽名數據佔用1GB空間。這個數字小於普通臺式機的內存大小。然而,有C2 20 即約5000億個文檔對需要比較。如果計算每兩篇文檔簽名之間的相似度需要花費1微秒,那麼這臺計算機需要大約6天才能計算所有的相似度。
如果我們的目標是計算每對文檔的相似度,那麼即使採用並行機制來減小實耗時間,也沒有辦法來減少計算量。但是,實際中往往需要得到那些最相似或者相似度超過某個下界的文檔對。如果是這樣,我們只需要關注那些可能的相似對,而不需要研究所有的文檔對。
目前對這類問題的處理存在着一個稱爲局部敏感哈希(locality-sensitive hashing, LSH)或鄰近搜索(near-neighbor search)的一般性理論。
4.1 面向minhasing signature的LSH
LSH的一般做法是對目標項進行多次哈希處理,使得相似項會比不相似項更可能哈希到同一個桶中。然後將至少有一次哈希到同一個桶中的文檔對看成是候選對(candidate pair),我們只檢查這些候選對的相似度。這裏會出現僞正例和僞反例。
假設我們已經計算出了目標項的最小哈希簽名矩陣SIG,
其中一個有效的處理方法是將簽名矩陣SIG劃分成b個行條(band),每個行條由R/b = r 行組成。對於每個行條r,存在一個哈希函數能夠將行條中的列向量映射到某個大數目範圍的桶中。可以對所有的行條使用相同的哈希函數,但是每個行條卻需要使用一個獨立的桶數組。
現在進行代碼測試:
首先增加兩個哈希函數:
int h3(int r)
{
return (2*r+1)%5;
}
int h4(int r)
{
return (r+2)%5;
}
//如何設置由函數作爲數組元素的數組
int (*hashf[4])(int);
void inithash()
{
hashf[0]=h1;
hashf[1]=h2;
hashf[2]=h3;
hashf[3]=h4;
}
在上一節的代碼中增加如下代碼:來計算候選對
//used to get candidate
const int B=2;
int candidate[B][setN];
int RL[setN][setN];
//計算候選對RL[i][j]
void getCandidate(int R)//get candidate for LSH
{
int row = 0;
if(R%B != 0)
{
return;
}
row = R/B;
for(int i=0;i<B;i++)
{
for(int m=0;m<setN;m++)
{
for(int r =2*i;r<(R/B+2*i);r++)//row(0,R/B=2),then 2,4, from 2*i, to R/B+2*i
{
candidate[i][m]+=h1(SIG[r][m]-1);
}
}
for(int m=0;m<setN;m++)
{
for(int k=m+1;k<setN;k++)
{
if(candidate[i][m]==candidate[i][k] && RL[m][k]==0)//if candidae equal, then candidata pair m,k= 1
{
RL[m][k]=1;
}
}
}
}
}
修改函數countForSIM
template<int C,int map[][C]>
void countForSIM(int R, bool flag=false)//flag used to control if it will use the LSH
{
reset(x);
reset(xpy);
for(int r=0;r<R;r++)
{
for(int m = 0;m<C;m++)
{
for(int j=m+1;j<C;j++)
{
if(flag==true)//if it will use LSH
{
if(RL[m][j]==0)
{
continue;
}
}
if(map[r][m]==1 && map[r][j]==1)
{
x[m][j]++;
xpy[m][j]++;
}
else if(map[r][m]==1 || map[r][j]==1)
{
xpy[m][j]++;
}
}
}
}
}
調用如下的測試代碼:
cout<<"original set................................."<<endl;
printM<setN,matrix>(5);
initSIG();
//printM<setN,SIG>(2);
inithash();
minHash(setN,4,5);
cout<<"hash signature set..........................."<<endl;
printM<setN,SIG>(4);
countForSIM<setN,matrix>(5);
getSIM1();
cout<<"SIM1........................................."<<endl;
printMd<setN,SIM1>(setN);
getCandidate(4);
cout<<"candidate........................................."<<endl;
printM<setN,candidate>(2);
cout<<"RL........................................."<<endl;
printM<setN,RL>(setN);
countForSIM<setN,SIG>(4,true);
getSIM2();
cout<<"SIM2........................................."<<endl;
printMd<setN,SIM2>(setN);
得到如下的結果:
整個測試的可執行文件參看如下鏈接:http://pan.baidu.com/s/1D7daY