有贊搜索引擎實踐(算法篇)

## 1. 搜索算法整體架構

在上篇文章(工程篇)中, 咱們介紹了有贊搜索引擎的基本框架. 搜索引擎主要3個部件構成. 第一, hadoop集羣, 用於生成大規模搜索和實時索引; 第二,  ElasticSearch集羣, 提供分佈式搜索方案; 第三, 高級搜索集羣, 用於提供商業搜索的特殊功能. 

商業電商搜索因爲搜索的特殊性, 獨立的ElasticSearch集羣是沒法知足多樣的算法需求的, 咱們在搜索的各個部件上都有相應的算法插件, 用於構建商業電商搜索引擎的算法體系.

### 1.1 索引過程

建立索引過程從原始數據建立倒排索引的過程. 這個過程當中咱們對商品(doc)進行分析, 計算商品靜態分, 並對商品進行類似度計算. 商品的靜態分對於提高搜索引擎質量起到相當重要的做用, 至關於網頁搜索的pagerank, 想象一下若是沒有pagerank算法, 網頁搜索的質量會有多麼差. 在電商搜索中, 最多見的問題是類似商品太多, 必須在創建索引過程當中就對商品間的類似度進行預計算, 以便在檢索過程當中進行有效去重.

建立索引的過程以下.

step 1. 計算每一個doc的靜態分
step 2. 計算兩兩doc的類似度
step 3. 根據類似度和其餘信息對數據進行分庫
step 4. 創建ES索引

### 1.2 檢索過程

檢索過程是搜索引擎接收用戶的query進行一系列處理並返回相關結果的過程. 商業搜索引擎在檢索過程當中須要考慮2個因素: 1) 相關性 2) 重要性.

相關性是指返回結果和輸入query是否相關, 這是搜索引擎基本問題之一, 目前經常使用的算法有BM25和空間向量模型. 這個兩個算法ElasticSearch都支持, 通常商業搜索引擎都用BM25算法. BM25算法會計算每一個doc和query的相關性分, 咱們使用Dscore表示. 

重要性是指商品被信賴的程度, 咱們應該吧最被消費之信賴的商品返回給消費者, 而不是讓消費之本身鑑別. 尤爲是在商品充分競爭的電商搜索, 咱們必須賦予商品合理的重要性分數, 才能保證搜索結果的優質. 重要性分, 又叫作靜態分, 使用Tscore表示.

搜索引擎最終的排序依據是:
   
Score = Dscore * Tscore

即綜合考慮靜態分和動態分, 給用戶相關且重要的商品. 

檢索的過程大體抽象爲以下幾個步驟.

step 1. 對原始query進行query分析
step 2. 在as中根據query分析結果進行query重寫
step 3. 在as中使用重寫後的query檢索es
step 4. 在es查詢過程當中根據靜態分和動態分綜合排序
step 5. 在as中吧es返回的結果進行重排
step 6. 返回結果



下面幾章闡述幾個重點技術.

## 2. 商品靜態分計算技術

在電商搜索引擎裏面商品的靜態分是有網頁搜索裏面的pagerank同等的價值和重要性, 他們都是doc固有的和查詢query無關的價值度量. pagerank經過doc之間的投票關係進行運算, 相對而言商品的靜態分的因素會更多一些. 商品靜態計算過程和pagerank同樣須要解決以下2個問題:
1. 穩定性. pagerank能夠保證一個網站不會由於簡單連接堆砌能夠線性提高網站的排名. 一樣, 商品靜態分的計算不可讓商品能夠經過增長單一指標線性增長分值(好比刷單對搜索引擎的質量的影響).
2. 區分度. 在保證穩定性的基礎上商品靜態分要有足夠的區分度能夠保證一樣搜索的條件下, 排在前面的商品的質量比排在後面的商品的質量高. 

咱們假設商品的靜態分有3個決定性因素, 1.下單數, 2. 好評率 3. 發貨速度

靜態分咱們使用Tsocre表示, Tscore能夠寫成以下形式:

Tscore = a * f(下單數) + b * g(好評率) + c * h(發貨速度)

a,b,c是權重參數, 用於平衡各個指標的影響程度. f,g,h是表明函數用於把原始的指標轉化成合理的度量. 

首先, 咱們須要尋找合理的表明函數. 

1. 首先對各個指標取log. log的導數是一個減函數, 表示爲了得到更好的分數須要花費愈來愈多的代價.

2. 標準化. 標準化的目的讓各個度量能夠在同一區間內進行比較. 好比下單數的取值是0~10000, 而好評率的取值爲0~1. 這種狀況會影響到數據分析的結果和方便性, 爲了消除指標之間的量綱的影響, 需 要進行數據標準化處理, 以解決數據指標之間的可比性.最經常使用的標準化方法是z-score標準化方法. 

z-score 標準化方法

"機率論"告訴咱們對於知足正態分佈的數據來講, 均值先後3個z-score的範圍能夠覆蓋99%的數據. 經驗地, 咱們把>5個zscore 或者小於 -5個zscore的分數設置成5*zscore或者-5zscore.
特別說明的是, 咱們不建議使用min-max標準化方法. 這種方法又叫離差標準化, 是對原始數據的線性變換, 使結果值映射到[0-1]之間, 轉化函數以下:

這種方法很是不穩定, 假設一個奇異點是第二大的值的1000倍, 會讓大部分的值都集中在0~0.01, 一樣失去了歸一化的目的.

圖一是使用min-max歸一化後的數據分佈, 顯然大部分數據被"壓扁"在很小的範圍; 圖二使用log歸一化後的數據分佈, 因爲log緩解了增加速度, 能夠看出來已經有一個不錯的結果了, 圖三是在log的基礎上進行z-score歸一化, 能夠看出來, z-score讓數據變得很是平滑.
 


 
(圖一: min-max歸一化)
(圖二: log歸一化)
 
(圖三: log-zscore歸一化)

最後, 選擇合適的權重
通過log-zscore歸一化之後, 咱們基本上吧f,g,h的表示的表明函數說明清楚.  Tscore = a*f(下單數) + b*g(好評率) + c*h(發貨速度), 下一步就是肯定a,b,c的參數. 通常有兩個方法:

a) 專家法. 根據咱們的平常經驗動態調整權重參數;
b) 實驗法. 首先在專家的幫助下賦一個初始值, 而後改變單一變量的方法根據abtest的結果來動態調整參數.


## 3. 商品標題去重


商品標題去重在電商搜索中起到重要做用, 根據數據, 用戶經過搜索頁購買商品80%選擇搜索的前4頁. 商品標題的重複會致使重要的頁面沒有含金量, 極大下降了搜索的購買率. 

舉個例子:

Title1:美味/香蕉/包郵/廣東/高州/香蕉/banana//無/催熟劑/

Title2:美味/香蕉/廣東/高州/香蕉//非/粉蕉/包郵/

首先, 進行特徵向量化

這裏用到 "bag of word" 技術, 將詞彙表做爲空間向量的維度, 標題的每一個term的詞頻做爲這個feature的值. 以這個例子來講.
這個詞彙的維度爲:
美味(0), 香蕉(1), 包郵(2), 廣東(3), 高州(4), banana(5),無(6), 催熟劑(7),非(8),粉蕉(9)
位置:  0,1,2,3,4,5,6,7,8,9

Title1: 1,2,1,1,1,1,1,1,0,0
Title2: 1,2,1,1,1,0,0,0,1,1

這個每一個title都用一個固定長度的向量表示. 

再次, 計算兩兩類似度

類似度通常是經過計算兩個向量的距離實現的, 不失通常性, 在這裏咱們使用1-cosine(x,y)來表示兩個向量的距離. 這是一個"All Pair Similarity"的問題, 即須要兩兩比較, 複雜度在O(n^2). 在商品量巨大的時候單機很難處理. 咱們給出兩種方法用於實現"All Pair Similarity".

方法一: spark的矩陣運算.  

```
rddRows = sc.parallelize(["1 0 2 0 0 1", "0 0 4 2 0 0"])

rddRows.map(lambda x: Vectors.dense([float(each) for each in str(x).split(" ")]))
mat = RowMatrix(rddRows)

simsPerfect = mat.columnSimilarities()
```
方法二: map-reduce 線性方法. 這個方法參考論文"Pairwise Document Similarity in Large Collections with MapReduce". 能夠實現幾乎線性的時間複雜度. 相對於矩陣運算在大規模(10億以上)pair similarity 運算上面有優點. 
這個方法簡單的描述以下:
首先, 按照倒排索引的計算方式計算每一個term到doc的映射.
好比3個doc:
```
doc1 = 我 愛 北京 
doc2 = 我 北京 天安門
doc3 = 我 天安門
```
轉化爲倒排格式, 這個須要一次mapper reduce
```
我     -> doc1, doc2, doc3
愛     -> doc1
北京   -> doc1, doc2
天安門 -> doc2, doc3 
```
而後, 對於value只有一個元素的過濾掉, 對於value大於2個doc的兩兩組合:
```
doc1,doc2 <---- from: 我     -> doc1, doc2, doc3
doc1,doc3 <---- from: 我     -> doc1, doc2, doc3
doc2,doc3 <---- form: 我     -> doc1, doc2, doc3
doc1,doc2 <---- from: 北京   -> doc1, doc2
doc2,doc3 <---- from: 天安門 -> doc2, doc3
```

最後, 對於輸出進行聚合,value爲重複次數和兩個doc乘積開根號的比.
```
doc1,doc2 -> 2/(len(doc1)*len(doc2))^1/2 = 0.7
doc1,doc3 -> 1/(len(doc1)*len(doc3))^1/2 = 0.3
doc2,doc3 -> 2/(len(doc2)*len(doc3))^1/2 = 0.3
```
對於2個title1, title2, 若是X(title1, title2) > 0.7 則認爲title1和title2類似, 對於類似的兩個doc, 靜態分大的定義爲主doc, 靜態分小的定義爲輔doc. 主doc和輔doc分別建庫. 


區別於網頁搜索(網頁搜索直接將輔doc刪除), 咱們將主doc和輔doc分別建庫. 每一次搜索按比例分別搜主庫和輔庫, 並將結果融合返回. 這樣能夠保證結果的多樣性.

## 4. 店鋪去重

店鋪去重和商品標題去重有點不一樣. 因爲電商特定場景的須要, 不但願搜索結果一家獨大, 這樣會引起強烈的馬太效應. 店鋪去重不能使用如上的方法進行. 由於上面的方法的主要依據是文本類似, 在結果都相關的前提下, 進行適當的取捨. 可是店鋪去重不是這樣的特性. 

設想一下, 若是咱們根據店鋪是否相同, 把同一店鋪的商品分到主庫和從庫中, 以下圖所示.


A和B表明不一樣的店鋪. 

在搜索香蕉的時候, 的確能夠控制A店鋪結果的數量, 可是在搜索"梨"的時候就錯誤的吧B店鋪的梨排在前面了(假設A:梨比B:梨靜態分高).

實際上想達到店鋪去重的效果經過分桶搜索是很容易作的事情. 咱們假設每頁搜索20個結果, 咱們把索引庫分紅4個桶, 每一個商品對桶數取模獲得所在桶的編號. 這樣能夠保證同一店鋪的商品僅在一個桶裏面. 


搜索的過程每一個桶平均分攤搜索任務的25%, 並根據靜態分合併成一頁的結果. 這樣同一保證結果的相對順序, 又達到了店鋪去重的目的. 

如上圖所示, 搜索"香蕉", 雖然A店鋪有10個知足需求的結果, 可是每頁搜索醉倒只有5個結果能夠展現. 

## query分析與Query改寫技術

上面介紹了幾個創建索引過程當中幾項技術, 檢索過程當中的關鍵技術有不少. 其中最著名的是query分析技術. 咱們使用的query分析技術主要包括核心詞識別, 同義詞拓展, 品牌詞識別等等. query分析技術大部分都是NLP研究範圍, 本文就不詳細闡述不少理論知識. 咱們重點介紹同義詞拓展技術. 這個技術通常都須要根據本身的商品和和用戶日誌特定訓練, 沒法像分詞技術和品牌詞識別同樣有標準的庫能夠適用. 


同義詞拓展通常是經過分析用戶session日誌獲取. 若是一個用戶輸入"蘋果手機"沒有獲得想要的結果, 他接着輸入"iphone", 咱們在"蘋果手機"和"iphone"之間建立一個轉移關係. 基於統計, 咱們能夠把用戶query建立一個相互聯繫的權重圖.  



用戶輸入query "蘋果手機", 根據query分析, "蘋果手機"有 "iphone"*0.8, "iphone 6"*0.5 兩個同義詞. 0.8和0.5分別表示同義的程度. 咱們想要"蘋果手機", "iphone", "iphone 6" 3個query同時輸入, 而且按照同義的程度對不一樣的query賦予不一樣的權重.
ElasticSearch提供的BoostingQuery能夠支持這個需求. 參考:  https://www.elastic.co/guide/en/elasticsearch/guide/current/_boosting_query_clauses.html

原始query:
```
{
    "query" {
         "match": {
            "query":"蘋果手機"
        }
    }
}
```
改寫後的Query
```
{
  "query": {
    "should": [
        { "match": {
            "content": {
                "query": "蘋果手機",
                "boost": 10 
            }
        }},
        { "match": {
            "content": {
                "query": "iphone",
                "boost": 8
            }
        }},
        { "match": {
            "content": {
                "query": "iphone6",
                "boost": 5
            }
        }}
    ]
  }
}
```

其餘好比核心詞識別, 歧義詞糾正等方法差很少, 本文不作詳細闡述.

## 其餘

商業電商搜索算法另外兩個重要技術, 一個是類目體系創建和應用,另外一個是個性化技術. 這個兩項技術咱們還處在探索階段. 類目體系咱們主要使用機器學習的方法進行訓練, 個性化主要經過用戶畫像進行Query改寫來實現. 等咱們上線有效果在與你們分享.

## 小結

搜索算法是一個很是值得一個電商產品持續投入的技術. 一方面咱們技術人員要有良好的技術背景, 能夠借鑑不少成熟的技術, 避免重複造輪子; 另外一方面, 每一個產品的搜索都有自身的特色, 須要深刻研究產品的特性給出合理的解決方案. 本文給出的案例都具備表明性, 靈活的運用搜索的各方面的技術. 另外, 商業搜索很是看重投入產出比, 咱們也須要在衆多方案中尋找捷徑. 好比咱們在作類目體系時候, 沒有投入大量的人力資源用於標註數據, 而是經過爬蟲爬取其餘電商的數據進行參考, 從而節省了80%的人力資源. 因爲筆者能力有限, 文中的方案不保證是問題的最優解, 若是有指正, 請聯繫筆者(hongbin@youzan.com). 




 














相關文章
相關標籤/搜索