大規模文字相似度計算

楊雄文發表於2018-07-09

1,背景

      對於文字相似度計算,背景其實沒必要提的,使用得多的地方有推薦和搜尋等,所以精通個性化推薦的同學比我熟得多,儘管本人也做過兩年半的個性化推薦。而特別寫這篇文章的原因在於,在我們當前一個實際的業務需求中,因資料量太大,之前用過的常規content-based演算法已無法支撐,除非以大大降低精度與覆蓋率為代價(例如,為了單節點能計算,犧牲“熱詞”)。
      協同過濾方面的經典演算法,所謂item-based、user-based、content-based等計算方法基本上是一樣的,例如,item-based基本矩陣:
1
其中I表示Item,U表示User,1表示使用者行為過的商品,實際中一般替換成一個評分或分值,即使用者行為表示成三元組。對於content-based,User換成Word,評分換成權重,即三元組換成。
      說一下我們的具體應用。淘系的商品遠超1億,假設N億吧。在我們的一個重要產品應用中,挑選了大約共1000萬的“精品”,覆蓋各類目,主要用於給使用者推薦。我們需要做一個普通商品到精品的對映,即全集N億×1000萬,當使用者搜到一個值得懷疑的商品時,例如後臺判斷是假貨、劣質、炒信等,我們向使用者推薦一個從標題上儘可能一致的精品。這樣既能符合使用者的查詢意圖,又能得到高品質的寶貝,可以極大提高使用者滿意度。為了讓表述更直觀,展示3個實際對映結果如下(左邊是非精品庫商品,右邊是文字最接近的精品庫商品。當前版本用到的關鍵屬性很少,相對標題最終權重低,說明示例只備註標題):
L:正品環保大號桌面化妝品收納盒抽屜式置物架塑料防水梳妝盒整理箱
R:桌面化妝品收納盒辦公桌上首飾創意收納箱抽屜式衛生間防水置物架
2
L:2件包郵 日系抹胸純棉蕾絲 雪紡小碎花小胸聚攏文胸套裝可愛少女
R:日系碎花甜美少女可愛蕾絲內衣內褲中厚聚攏文胸套裝性感女士胸罩
3
L:義大利經典上色烤漆油畫箱多功能手提油 畫架原木色工具箱寫生便
R:櫸木製義大利油畫箱便攜實木寫生油畫架多功能手提畫箱美術工具箱
4

2,計算量

原始資料量:N億 × 1000萬。按原始資料量直接計算一看就是不行的,假設我們按照1000個計算節點算,則每個節點需要計算的商品相似度pair是N萬億,這個還不算乘上每個商品詞的數量。而實際上,在odps上處理過資料傾斜的同學應該比較清楚,單節點join產生的pair超過10億就已經超慢了以至於幾乎不可行,N萬億就不用談了。
計算量削減一:
對於這種應用中的文字相似度計算,跟Item-based很不一樣的地方是:Item-based一般是所有資料一起計算的,當然也可以分行業分類目計算,但就沒法得到所謂的“啤酒與尿布”的推薦了。我們這裡提到的文字相似度不一樣,明顯可以分類目做,而且應該分類目做,因為就是為了得到標題儘可能一致的結果,跨類目可以說不是同一樣東西了。即分類目計算,或者說帶限制類目的文字相似度計算。
計算量削減二:
即使限制類目做文字相似度計算,我們發現,最大類目的笛卡爾積也有約xxxx萬×x萬,哪怕僅僅只計算這一個類目的商品,按1000個節點,每個節點處理的商品pair也有近120億,遠遠超出了可承受範圍,更不用說全部類目同時計算了。所以必須要進一步限制,而實際上可以進一步限制。例如都是“連衣裙”類目,一件叫“歐版修身印花連衣裙”,一件叫“韓版鏤空針織連衣裙”,就不需要計算了,很明顯根本不相似,因為它們僅僅在類目下熱詞匹配中了。即我們可以將僅僅匹配到了類目下熱詞的pair去掉
      對於削減二,需要特別說明的是(細節下文中會說):
      常規content-based演算法為了計算可行,並不選擇需要計算pair,而且直接排除熱詞,即熱詞根本不會參與到後面的相似度計算中。如果隨便取個類目下閾值,例如500之類的,對淘系商品來說,熱詞就太多了,直接去掉,必將同時大大降低精準度和覆蓋率。本文的做法對於所有選出來的pair,熱詞也全部參與計算,這樣才能保證相似度結果是精準的。這也是與常規content-based過程最大的不同。

3,計算過程

3.1 相似度簡要說明

相似度計算有很多方式,歐氏距離、餘弦、Jaccard等,這裡僅簡單提下最常見的兩種:
Cosine距離:
5
Jaccard距離:
6

3.2 計算步驟

常規content-based步驟:
      1,分詞,詞ID化(MR細節方面的考慮,也可以不ID化)
      2,對分詞結果,計算詞的權重
      3,對詞的權重歸一化
      4,計算文件對的相似度
      5,對相似度結果排序,輸出前top N
本文計算步驟:
      1,分詞,詞ID化
      2,按cate_id,word對商品計數
      3,用2的結果過濾只在熱詞上匹配上的i2i對,得到需要計算的i2i池子
      4,對分詞結果,計算詞的權重
      5,對詞的權重歸一化
      6,計算文件對的相似度
      7,對相似度結果排序,輸出前top N
      其中,2,3步和4,5步可以並行。即
                              7

3.3 計算步驟細節說明

對上述的7個步驟,做不同程度的說明,簡單的過程就簡單描述了。
步驟一,分詞,詞ID化:
輸入:< item_id,cate_id,title,property_name>
輸出:< item_id,cate_id,word,word_type,is_fine>
分詞直接採用aliws,分詞後得到5元組:< item_id,cate_id,word,word_type,is_fine>。其中,word_type表示詞型別,因為我們除了計算title外,還計算了商品的關鍵屬性,title取1,關鍵屬性取2。is_fine是對於我們的具體應用來的,0表示普通商品,1表示精品。
步驟二,對cate_id,word對商品計數:
輸入:< item_id,cate_id,word,word_type,is_fine>
輸出:< cate_id,word,cnt1,cnt2>
按cate_id, word為組合key來分發,統計cate_id, word下的精品庫商品和普通商品數量。得到4元組< cate_id,word,cnt1,cnt2>。其中,cnt1是精品庫商品數量,cnt2是普通商品數量。
步驟三,用2的結果過濾只在熱詞上匹配上的i2i對,得到需要計算的i2i池子:
輸入:< item_id,cate_id,word,word_type,is_fine> 和 < cate_id,word,cnt1,cnt2>
輸出:< item_id1, item_id2>
其中,< item_id1, item_id2>即實際需要計算的i2i_pair,item_id1是非精品商品id,item_id2精品商品id。
這一步重點說明:

  • 作用:協同過濾裡不管是Item-based還是Content-based等都有個特點,即稀疏性,真正需要發生相似度計算的地方是在發生碰撞的地方,例如圖1中的I3和I5算相似度時只需要考慮到U1。即後面相似度的全部計算量就是總的碰撞次數,而這一步就是用來控制這個總計算量的。
  • i2i_pair選擇:前面已經詳述如果不做計算量削減真實計算量是不可行的。重申一下,這個選擇做到兩點,1)將僅僅匹配到了類目下熱詞的pair去掉;2)所有選出來的pair,包含的所有詞參與計算,包括熱詞。第1點靠去掉無意義的計算(根本上不相似)保證整體計算可行,第2點保證計算精準性而保障最後排序結果。
  • 總計算量:不做任何限制,分類目計算的總計算量由步驟二步的輸出表決定,即8。我們需要限制的就是這一部分,限制太鬆則不必要的計算增多但提高最終召回率(能找到最佳近似的商品佔比);限制太緊則誤殺的熱詞增多會導致最終召回率降低。限制的方式很多,例如cnt1,cnt2分別小於某閾值,cnt1cnt2小於某閾值,或兩個條件並用,更合理的是按照類目彈性限制。我們先嚐試了限制cnt1cnt2的方式,取一個閾值讓總計算量控制在2000億級別,這樣按1000個節點計算,每個節點2億碰撞,可行。這樣最終覆蓋率大概在90%。

步驟四,對分詞結果,計算詞的權重:
輸入:< item_id, cate_id, word, word_type, is_fine>
輸出:< item_id, cate_id, word, weight, is_fine>
權重計算採用傳統TF-IDF,裡面需要調的主要有title和屬性權重區分、文件頻率(DF)過低的詞如何處理、DF過高的是否排除等。這些點一般是根據實際結果調整,比我熟的人太多,不細說了。
步驟五,對詞的權重歸一化:
輸入:< item_id, cate_id, word, weight, is_fine>
輸出:< item_id, cate_id, word, weight_norm, is_fine>
輸出的weight_norm是歸一化後的權重。weight_norm=weight / sqrt(sum(weight))。
步驟六,計算文件對的相似度:
輸入:< item_id1, item_id2> 和 < item_id, cate_id, word, weight_norm, is_fine>
輸出:< item_id1, item_id2, score>
score是最終cosine相似度分數。真正的計算量發生在這一步,耗時約5個小時。
過程簡單描述為:
MR1:< item_id1, item_id2>與5元組join,按item_id1為key分發,得< item_id1,item_id2,word1, weight1>
MR2:< item_id1,item_id2,word1,weight1>與5元組join,按item_id2和word組合key分發,得< item_id1,item_id2,weight1,weight2>
MR3:< item_id1,item_id2,weight1,weight2>按item_id1和item_id2組合key分發,得< item_id1,item_id2,sum(weight1*weight2)>
寫成sql就是2個join接上一個sum() group by。
步驟七,對相似度結果排序,輸出前top N:
輸入:< item_id1, item_id2, score>
輸出:< item_id1, item_id_list>

4,小結

本文針對實際業務中的一個具體應用(規模N億×1000萬),對文字相似度計算的方法和過程做了分析和自己的改造,在能夠完成整體計算的同時,兼顧結果的精準性和覆蓋率。對計算過程做了相對詳細的說明。當前對映結果基本符合需求,但可以改進的細節也不少,後續主要會就下面幾點做優化:
1,對i2i_pair選擇上計算量的限制,按類目細化。
2,詞的權重優化,例如詞型別權重的區分,不同域詞權重的區分,文件詞頻方面的處理等。
3,得到其它相似度計算結果(當前其實也計算了Jaccard相似度,結果上跟cosine區別不是很明顯),採用模型融合的方式,優化最終結果。
4,影響最終最終覆蓋率的另一個關鍵因素是精品庫本身對類目下商品的覆蓋情況,所以將進一步優化精品庫。


相關文章