最小哈希法
最小哈希原理介紹
- MinHash是基於Jaccard Index相似度(海量數據不可行)的算法,一種降維的方法A,B 兩個集合:A = {s1, s3, s6, s8, s9} B = {s3, s4, s7, s8, s10}
- MinHash的基本原理:在A∪B這個大的隨機域里,選中的元素落在A∩B這個區域的概率,這個概率就等於Jaccard的相似度
最小哈希:
|
S1 |
S2 |
S3 |
A |
1 |
0 |
0 |
B |
0 |
1 |
0 |
C |
0 |
0 |
0 |
D |
1 |
0 |
1 |
行的隨機排列轉換(也稱置換運算)
|
S1 |
S2 |
S3 |
B |
0 |
1 |
0 |
D |
1 |
0 |
1 |
A |
1 |
0 |
0 |
C |
0 |
0 |
0 |
哈希值:排列轉換后的行排列次序下第一個列值為1的行的行號,例如h(S1)=D,h(S2)=B
兩個集合經隨機排列之后得到的兩個最小哈希值相等的概率等於這兩個集合的Jaccard相似度。
問題:
對於上百萬甚至數十億的行選擇一個隨機排列轉換極其消耗時間,怎么辦?
- 隨機選擇n個哈希函數h1,h2…
- 對每列C進行如下操作:
a) 如果c在第r行為0,則什么也不做;
b) 否則,如果c在第r行為1,那么對於每個i=1,2,…,n,將SIG(i,c)置為原來的SIG(i,c)和h(r)中的較小值
行 |
S1 |
S2 |
S3 |
S4 |
x+1 mod 5 |
3x+1 mod 5 |
0 |
1 |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
2 |
4 |
2 |
0 |
1 |
0 |
1 |
3 |
2 |
3 |
1 |
0 |
1 |
1 |
4 |
0 |
4 |
0 |
0 |
1 |
0 |
0 |
3 |
計算最小哈希簽名矩陣:
|
S1 |
S2 |
S3 |
S4 |
h1 |
1 |
3 |
0 |
1 |
h2 |
0 |
2 |
0 |
0 |
計算Jaccard相似度:
SIM(S1,S4)=1.0;SIM(S1,S3)=1/3;SIM(S1,S2)=0
真實SIM(S1,S4)=2/3;SIM(S1,S3)=1/4;SIM(S1,S2)=0
相關論文:
1.《Google News Personalization: Scalable Online Collaborative Filtering》
根據用戶的歷史點擊數據,進行新聞推薦;采用最小哈希聚類的協同過濾算法
2.《The Link-Prediction Problem for Social Networks》
比較社交網絡鏈接預測問題的各種算法
計算好友相似度的流程:
- 找到N個哈希函數,對每個用戶好友集合生成一組Minhash(N個)
- 對於一個用戶,按Minhash相同個數做排序,給出推薦候選集
- 計算用戶跟被備選集的Jaccard Index
- 按Jaccard結果排序,給出TopN進行推薦
哈希函數生成和哈希值計算:
- 輸入向量Vector轉換為bytes
- numHashFunctions--預設生成hash函數的個數(假定為10個)
-
for (int i = 0; i < numHashFunctions; i++) { for (Vector.Element ele : featureVector) { int value = (int) ele.get(); bytesToHash[0] = (byte) (value >> 24); bytesToHash[1] = (byte) (value >> 16); bytesToHash[2] = (byte) (value >> 8); bytesToHash[3] = (byte) value; int hashIndex = hashFunction[i].hash(bytesToHash); //計算哈希函數值 //只保留最小哈希值 if (minHashValues[i] > hashIndex) { minHashValues[i] = hashIndex; } } }
- 采用Mersenne Twister算法構造偽隨機生成器
- Random random = new MersenneTwisterRNG(new FastRandomSeedGenerator());
-
org.uncommons.maths.random. MersenneTwisterRNG.MersenneTwisterRNG( SeedGenerator seedGenerator)
-
- Mersenne Twister(馬特賽特旋轉演算法),是偽隨機數發生器之一,其主要作用是生成偽隨機數。
Mersenne Twister算法的原理:Mersenne Twister算法是利用線性反饋移位寄存器(LFSR)產生隨機數的,LFSR的反饋函數是寄存器中某些位的簡單異或,這些位也稱之為抽頭序列。一個n位的LFSR能夠在重復之前產生2^n-1位長的偽隨機序列。只有具有一定抽頭序列的LFSR才能通過所有2^n-1個內部狀態,產生2^n - 1位長的偽隨機序列,這個輸出的序列就稱之為m序列。為了使LFSR成為最大周期的LFSR,由抽頭序列加上常數1形成的多項式必須是本原多項式。一個n階本原多項式是不可約多項式,它能整除x^(2*n-1)+1而不能整除x^d+1,其中d能整除2^n-1。例如(32,7,5,3,2,1,0)是指本原多項式x^32+x^7+x^5+x^3+x^2+x+1,把它轉化為最大周期LFSR就是在LFSR第32,7,5,2,1位抽頭。利用上述兩種方法產生周期為m的偽隨機序列后,只需要將產生的偽隨機序列除以序列的周期,就可以得到(0,1)上均勻分布的偽隨機序列了。Mersenne Twister有以下優點:隨機性好,在計算機上容易實現,占用內存較少(mt19937的C程式碼執行僅需624個字的工作區域),產生隨機數的速度快、周期長,可達到2^19937-1,且具有623維均勻分布的性質,對於一般的應用來說,足夠大了,序列關聯比較小,能通過很多隨機性測試。
- Random random = new MersenneTwisterRNG(new FastRandomSeedGenerator());
- 四種哈希函數生成器
- MAX_INT_SMALLER_TWIN_PRIME = 2147482949;為什么選這個值?
- 它是整型范圍內最大孿生素數(相差為2的兩個數都是質數的情況)的較小值;哈希用素數取模沖突小
- seedA、seedB、seedC是采用MersenneTwisterRNG隨機生成器生成的0~11均勻分布的隨機數
- 第一種:LinearHash
@Override public int hash(byte[] bytes) { long hashValue = 31; for (long byteVal : bytes) { hashValue *= seedA * byteVal; hashValue += seedB; } return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME)); }
- 第二種:PolynomialHash
@Override public int hash(byte[] bytes) { long hashValue = 31; for (long byteVal : bytes) { hashValue *= seedA * (byteVal >> 4); hashValue += seedB * byteVal + seedC; } return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME)); }
- 第三種:MurmurHashWrapper
@Override public int hash(byte[] bytes) { long hashValue = MurmurHash.hash64A(bytes, seed); return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME)); }
- 第四種:MurmurHash3Wrapper
@Override public int hash(byte[] bytes) { long hashValue = MurmurHash3.murmurhash3_x86_32(bytes, 0, bytes.length, seed); return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME)); }
- MAX_INT_SMALLER_TWIN_PRIME = 2147482949;為什么選這個值?