1. 程式人生 > >【應用演算法】資訊流-推薦系統的去重策略

【應用演算法】資訊流-推薦系統的去重策略

聊兩個問題,它們看似和推薦系統沒有必然關係,但實際上,

在你構建自己的推薦系統的時候,不可避免地會遇到這兩個問題。

去重是剛需

在推薦系統中,有一個剛需就是去重,那麼說在哪些地方有去重的需求呢?

主要是在兩個地方:一個是內容源去重,另一個是不重複給使用者推薦。

先說說內容源的去重,這部分以前幾年的圖文資訊流推薦為典型的例子。

如果一個平臺自己不生產內容,只是做內容搬運和聚合分發,那麼從大量第三方的內容生產處抓取內容,就難免遇到相似甚至重複的內容。這就需要對內容做一個重複檢測了。

對內容做重複檢測,直觀的思路是分詞,然後提取關鍵詞,再兩兩計算詞向量之間的距離,距離小於一定閾值後就判定為重複。然而,這對於海量內容,比如幾千萬以上的內容來說簡直就是災難。

其實,內容源去重並不是僅在推薦系統中才首次出現,這早在搜尋引擎時代就是一個剛需了,搜尋引擎把整個網際網路的網頁都下載到自己的伺服器上,這時,重複冗餘的內容就需要被檢測出來。

另一個需求是在內容閱讀類推薦場景下,給使用者推薦的內容不要重複,推薦過的內容就不再出現在推薦候選集中。

在你刷一個資訊流產品時,不斷看到重複的內容,想必不是使用感很好的一件事。因為以抓取作為主要內容來源的資訊流產品,不同於社交網站上使用者自發產生內容,除非遇到使用者惡意傳送,否則後者是不容易重複的。

以上兩個場景,需要在你打造自己的推薦系統時予以考慮和應對。

今天就介紹兩種最常見的去重演算法,兩者有相通之處也有不同的之處。

Simhash

內容重複檢測,是搜尋引擎公司最先遇到的,所以 Google 在 07 年公開了他們內部的內容重複檢測演算法,這個演算法簡單有效,甚至造福了今天的資訊流推薦產品。

對於很長的內容,如果只是檢測絕對重複,也就是說完全一模一樣的那種情況,那麼使用 MD5 這樣的資訊指紋方法非常高效,無需再去分詞、提取關鍵詞和計算關鍵詞向量之間的距離。

我們直接將原始的內容對映為一個短字串,這個短字串就是原始內容的指紋,雖然不是絕對保證和原始內容一一對映,但是不同內容能得到相同指紋的概率非常小。

只是這種資訊指紋的方法有個非常明顯的壞處就是,哪怕原始內容改一個字,得到的資訊指紋就會截然不同。

這就沒法愉快地玩耍了,你一定希望的是隻要主要內容不變,就算一些不太重要的詞句不同,也仍然可以得到相近甚至相同的指紋。這才能更加靈活地進行內容重複檢測。是否有這樣的演算法?有,就是 Simhash。

Simhash 核心思想也是為每個內容生成一個整數表示的指紋,然後用這個指紋去做重複或者相似的檢測。下面這個示意圖說明了 Simhash 如何把一個原始內容表示成一個整數指紋。

好,現在詳細說明一下這個過程。

1. 首先,對原始內容分詞,並且計算每個詞的權重;

2. 對每個詞雜湊成一個整數,並且把這個整數對應的二進位制序列中的 0 變成 -1,1 還是 1,得到一個 1 和 -1 組成的向量;

3. 把每個詞雜湊後的向量乘以詞的權重,得到一個新的加權向量;

4. 把每個詞的加權向量相加,得到一個最終向量,這個向量中每個元素有正有負;

5. 把最終這個向量中元素為正的替換成 1,為負的替換成 0,這個向量變成一個二進位制位序列,也就是最終變成了一個整數。

最終這個整數就代表了原始的內容。這個 Simhash 奇妙在哪呢?

看這個示意圖中,故意加了一個不太重要的詞“了”,它的權重是 1,對應的加權向量元素不是 1 就是 -1,在上述的第四步中,如果這個詞對應的向量缺少了,其實根本不影響最終得到那個整數,因為它很難改變最終向量元素的正負。這就是為什麼那些不太重要的詞不影響內容之間的重複檢測。

Simhash 為每一個內容生成一個整數指紋,其中的關鍵是把每個詞雜湊成一個整數,這一步常常採用 Jenkins 演算法。這裡簡單示意的整數只有 8 個二進位制位,實際上可能需要 64 個二進位制位的整數,甚至範圍更大。

得到每個內容的 Simhash 指紋後,可以兩兩計算漢明距離,比較二進位制位不同個數,其實就是計算兩個指紋的異或,異或結果中如果包含 3 個以下的 1,則認為兩條內容重複。

為了高效,也可以直接認為指紋相同才重複,視情況而定。

Bloomfilter

除了內容重複檢測,還有一個需求是防止已經推薦的內容被重複推薦。這個剛需和上述內容重複相比,最大的不同就是過濾物件不同,上述 Simhash 過濾物件是內容本身,而這裡則一般是內容的 ID。

內容的 ID 一般是用一個 UUID 表示,是一個不太長的字串或者整數。

對於這類形如模式串的去重,顯然可以用單獨專門的資料庫來儲存,為了高效,甚至可以為它建上索引。

但對於使用者量巨大的情況下,這個做法對儲存的消耗則不可小看。實際上,解決這類看一個字串在不在一個集合中的問題,有一個有點老但很好用的做法,就是 Bloomfilter,有時候也被稱為布隆過濾器。

布隆過濾器的原理也要用到雜湊函式。它包含兩部分:一個很長的二進位制位向量,和一系列雜湊函式。Bloomfilter 是一個很巧妙的設計,它先把原始要查詢的集合對映到一個長度為 m 的二進位制位向量上去,它對映的方法是:

1. 設計 n 個互相獨立的雜湊函式,準備一個長度為 m 的二進位制向量,最開始全是 0;

2. 每個雜湊函式把集合內的元素對映為一個不超過 m 的正整數 k,m 就是二進位制向量的長度;

3. 把這個二進位制向量中的第 k 個位置設定為 1;也就是一個元素會在二進位制向量中對應 n 個位置為 1。

看示意圖。

這個示意圖中,原始的模式串經過三個互相獨立的雜湊函式,對映到 8 位二進位制向量中的三個位置了。

原始的模式串集合經過這樣的處理後,就得到一個很大的二進位制向量。在應用階段時,假如來了一個模式串 s,需要查詢是否在這個集合中,也需要經過同樣的上述步驟。

每個雜湊函式對這個模式串 s 雜湊後都得到一個整數,看看這個整數在二進位制向量中所指示的位置是不是 1,如果每個雜湊函式所指示的位置都是 1,就說明模式串 s 已經在集合中了。

需要說明的是,Bloomfilter 也並不是百分之百保證的,有很小的概率把原本不存在集合中的模式串判斷為存在。這樣就會造成那些明明還沒有推薦給使用者的內容 ID 就再也不會推薦給使用者了,當然,這個小概率是可以承受的。

總結

介紹了兩種去重演算法。在推薦系統中,雖然我們十分關心推薦匹配的效果,但是別忘了,對原始內容的挖掘和清洗往往更加重要。這其中就包括對重複內容的檢測。

兩種去重策略都是犧牲一點誤傷的概率換得大幅度的效率提升,具體的做法都是要藉助雜湊函式。只是雜湊函式的結果在兩個演算法中有不同的處理手段,Simhash 是加權,Bloomfilter 則是用來做定址

有一個思考題,請你想一想,如果要從 Bloomfilter 中去掉一個元素,該怎麼做?有興趣,可以一起探討一下。

 

------------------------------------------------------

------------------------------------------------------

我的個人域名

 

期望和大家一起學習,共同進步,共勉,O(∩_∩)O謝謝

歡迎交流問題,可加個人QQ 469580884

或者,加我的群號 751925591,一起探討交流問題

不講虛的,只做實幹家

Talk is cheap,show me the code