基於Elastic Search的搜索廣告召回方案

如果你對搜索廣告,競價排序,或者Elastic Search技術感興趣,讀讀這篇文章或許多少能有所收穫。作者不是計算廣告領域的專家,如果作爲讀者的你是這個方面的專家發現本文淺薄,希望留下你寶貴的意見。
因爲ES版本升級很快,很多功能支持程度也伴隨版本的升級而改變,本文內容基於Elastic Search 5.4.1實現。

什麼是搜索廣告

舉個最常見的例子,當我們在淘寶上購物搜索時候,例如輸入“貓糧”

在搜索結果的第一個,你會看到有個小小的廣告二字,這條返回結果就是搜索廣告的“傑作”了。


不同的運營平臺會提供給商家後臺採買關鍵詞,設置出價和匹配模式等。當用戶發起搜索時,根據規則,首先召回採買關鍵詞的商家,然後對這些召回商家排序,返回廣告商家。

一般來說,這類廣告的收費模式都是按照點擊收費(CPC),所以排序肯定不能按照單純的價高者得。因爲即使商家出價再高,但是由於相關度和商家質量問題,而無人點擊,平臺依然沒有任何營收,既浪費了平臺流量,也沒有給商家貢獻轉化。普遍來說,對於CPC廣告,排序一般基於商戶出價Bid * 預估CTR(點擊率)。排序在計算廣告中佔據着舉足輕重的地位,提高AUC,CTR等指標,也讓無數青年才俊掉了不少頭髮。不過排序並不是本文介紹的重點,如果你感興趣,可以搜索LR,GBDT,FM,OCPC等關鍵詞,相信你會有很多的收穫。如果有機會,筆者也希望可以寫機器學習相關的文章,本文主要介紹搜索廣告的召回部分的實現。

文檔

每一條商戶關鍵詞的出價是一個文檔,JSON描述如下:

{
    "id":123456
    "weight": 201, 
    "biding": "天潤酸奶", 
    "lon": 117.60715739693345, 
    "shopId": 400, 
    "matchMode": "SpitContain", 
    "lat": 27.555006197000644, 
    "open": true
}

其中 id 代表推廣計劃ID,weight 是商家出價,biding 是商戶出價的關鍵詞,lon,lat 描述商戶地理座標,open 描述店鋪當前狀態。matchMode 是商戶設置的匹配模式,匹配模式 的含義是,只有在用戶搜索詞和出價的關鍵詞 之間的匹配滿足一定條件的時候,纔會生效(不能僅僅一直想完全一樣的情況哦)。在不同業務場景下,文檔需要的數據是不同的。

筆者提供了一個簡單的Python程序可以生成一些測試文檔,並索引到ES中,需要的朋友可以到這裏下載 測試數據生成器,該程序會生成50萬商家的2500萬條採買記錄,關鍵詞詞庫含有2萬條關鍵詞。

匹配模式

筆者定義了四種搜索詞和關鍵詞匹配模式:



具體定義如下:
設定 爲用戶搜索(Query),爲商戶出價的關鍵詞(Keyword)

精準匹配:Q = K
例:糯米飯(Q) = 糯米飯(K)

精準包含:Q 是 K的子串
例:芒果(Q)= 芒果糯米飯(K)

短語包含:T 是 K的子串,其中 T 是 Q 的任意分詞詞項(Term)
例:芒果糯米飯(Q) = 芒果西米露(K),芒果糯米飯 的分詞詞項:芒果、糯米飯,其中 芒果 是關鍵詞 芒果西米露 的子串。

模糊匹配:S是K的子串,其中S是T的同義詞
例:麻辣黃(Q)= 雞米飯(K),麻辣黃悶雞 分詞爲:麻辣、黃悶雞黃悶雞的同義詞爲黃燜雞(S),而S是K的子串。

關於匹配模式的工作模式可以舉個例子

所以召回要解決兩個問題:

a. 支持基於關鍵詞文檔的全文檢索

b. 支持匹配模式

基於Elastic Search的搜索召回

召回的所有邏輯也可以使用Lucene拓展編寫,好處是可以高度整合業務邏輯到索引,缺點是開發成本高。本文將採用Elastic Search作爲搜索召回引擎,ES的默認配置,遠遠不能實現我們的需要的功能,所以需要做一些額外工作。

a. 中文和行業詞庫的擴展(本文采用美食詞彙)

b. 同義詞模糊匹配支持

c. 基於匹配模式的過濾器

1. 中文索引和行業詞庫擴展

這裏我們使用到了大名鼎鼎的 IK - Analyzer 插件,IK的目錄中存在

config/custom/mydict.dic

文件,把相關的行業詞彙放入其中即可。驗證如下:

curl -XGET 'localhost:9200/_analyze?pretty' -d '{
    "analyzer":"ik_smart",
    "text":"附近哪裏有黃燜雞米飯或者騰衝大救駕"
}'

分詞結果如下

{
  "tokens" : [
    #省略若干.......
    {
      "token" : "黃燜雞米飯",
      "start_offset" : 5,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "或者",
      "start_offset" : 10,
      "end_offset" : 12,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "騰衝大救駕",
      "start_offset" : 12,
      "end_offset" : 17,
      "type" : "CN_WORD",
      "position" : 5
    }
  ]
}

可見 黃燜雞米飯 和 騰衝大救駕 已經作爲單獨的詞彙被識別出來了。

2. 同義詞匹配支持

ES是支持同義詞邏輯的,不過需要一些配置,這個配置可以在創建索引的時候指定。

curl -XPUT 'http://localhost:9200/search_ad_index' -d '{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonym_filter": {
          "type": "synonym", 
          "synonyms_path":"analysis/synonym.txt"
        }
      },
      "analyzer": {
        "ik_syno": {
          "type":"custom",
          "tokenizer": "ik_smart",
          "search_analyzer": "ik_smart",
          "filter": [
            "lowercase",
            "my_synonym_filter" 
          ]
        }
      }
    }
  }
}'

同時還要把同義詞庫定義在如下文件

config/analysis/synonym.txt

爲了說明問題,本文定義了一個很簡單的同義詞庫

黃悶雞,黃夢雞,huangmenjimifan,huangmenji,黃燜雞,黃燜雞米飯
Dongyingong,冬陰功,冬陰功湯

然後在 biding 字段,配置支持同義詞的Analyzer

curl -XPOST 'http://localhost:9200/search_ad_index/shop_keyword/_mapping' -d '
{
    "properties": {
        "biding": {
                "type": "text",
                "analyzer": "ik_syno",
                "search_analyzer": "ik_syno"
        }
    }
}'

現在我們索引一條文檔,然後測試一下同義詞是否生效

curl -XPOST 'localhost:9200/search_ad_index/shop_keyword/1' -d '{
    "weight" : 201,
    "biding" : "黃燜雞米飯",
    "lon" : 117.60715739693345,
    "shopId" : 400,
    "matchMode" : "SpitContain",
    "lat" : 27.555006197000644,
    "open" : true
}'

搜索腳本如下:

curl -XPOST 'localhost:9200/search_ad_index/shop_keyword/_search?pretty' -d '{
  "query":{
    "match":{
      "biding":"huangmenji"
    }
  }
}'

召回結果如下:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.58874476,
    "hits" : [
      {
        "_index" : "search_ad_index",
        "_type" : "shop_keyword",
        "_id" : "1",
        "_score" : 0.58874476,
        "_source" : {
          "weight" : 201,
          "biding" : "黃燜雞米飯",
          "lon" : 117.60715739693345,
          "shopId" : 400,
          "matchMode" : "SpitContain",
          "lat" : 27.555006197000644,
          "open" : true
        }
      }
    ]
  }
}

我們在同義詞庫定義了,huangmenji = 黃燜雞 = 黃燜雞米飯
用huangmenji做搜索詞,召回了 biding = 黃燜雞米飯 的文檔,說明同義詞已經被ES支持了。

3. 基於匹配模式的過濾器

既然ES已經支持了同義詞和行業詞彙分詞,那麼已經滿足了匹配模式中最廣泛的模糊匹配,基於模糊匹配的返回結果,把不滿足匹配模式的文檔過濾掉,就獲得滿足業務的結果了。筆者用一個例子說明 多匹配模式 的支持過程。

首先,使用ES的模糊搜索,獲得所有匹配的文檔,如 標記①所示,簡化文檔記錄爲:
商戶ID,商戶出價關鍵詞,商戶設置的匹配模式。例如第一條記錄商戶1002,Bid了關鍵詞芒果西米露,但是只有在用戶搜索和關鍵詞精準匹配的時候,才生效。

基於①返回的文檔,可以在內存中過濾,只不過筆者不希望ES返回太多文檔,增加網絡負擔也可能將有用的文檔截取掉,所以筆者用ES的Java Plugin實現了一個 PostFilter,可以在 ES 匹配文檔後返回結果前,過濾掉不符合規則的文檔,基於性能的考慮使用Java語言實現原生的Plugin。

這個PostFilter需要傳入用戶搜索 Q ,和基於用戶搜索的分詞列表 T_s ,他是分詞項 T 的數組。PostFilter的僞代碼如下:

IF doc.matchMode is 精準匹配 Then
    return Q equalTo doc.biding
ELSE IF doc.matchMode is 精準包含 Then 
    return doc.biding substring Q
ELSE IF doc.matchMode is 分詞包含 Then 
    return doc.biding substring T
ELSE
    return true

②展示了過濾器工作的結果,那些不滿足商戶匹配模式的關鍵詞條目被打分爲 0, 將被過濾掉,而滿足條件的被打分 1, 將在結果中保留。

③是經過過濾器後,返回的最終文檔集合。

筆者把 PostFilter 的代碼託管在 GitHub 上,可以在這裏找到: MatchModePostFilter 需要實驗的朋友,可以 Maven package 生成 .zip 文件,解壓後放入 ES_HOME/plugins目錄下即可生效。

將可以實現功能的上文所云,濃縮到一條搜索腳本如下:

POST /search_ad_index/_search?pretty

{
    "size":100,
    "query": {
        "bool": {
            "must": {
                "match": {
                    "biding": "麻辣香鍋冒菜" #用戶查詢
                }
            }, 
            "filter": [  #其他業務過濾器,可以自己定義
                {
                    "range": {
                        "lat": {
                            "gt": 31.5, 
                            "lt": 32.6
                        }
                    }
                }, 
                {
                    "range": {
                        "lon": {
                            "gt": 118.3, 
                            "lt": 119.4
                        }
                    }
                }, 
                {
                    "term": {
                        "open": true
                    }
                }
            ]
        }
    }, 
    "post_filter": {
        "script": {
            "script": {
                "inline": "match_mode_scoring", #指定原生腳本名
                "lang": "native", 
                "params": {
                    "query": "麻辣香鍋冒菜",  #用戶查詢
                    "tokens": "麻辣香鍋;冒菜" #用戶查詢分詞
                }
            }
        }
    }
}

部分返回結果如下:

           {
                "_index": "fuzzy_search_ad",
                "_type": "shop_keyword",
                "_id": "1",
                "_score": 12.347956,
                "_source": {
                    "weight": 139,
                    "biding": "麻辣香鍋冒菜",
                    "lon": 118.31,
                    "shopId": 122,
                    "matchMode": "Exact",
                    "lat": 31.51,
                    "open": true
                }
            },
            {
                "_index": "fuzzy_search_ad",
                "_type": "shop_keyword",
                "_id": "2660337",
                "_score": 6.4009247,
                "_source": {
                    "weight": 338,
                    "biding": "蛋蛋麻辣香鍋",
                    "lon": 119.21255552940255,
                    "shopId": 53206,
                    "matchMode": "Fuzzy",
                    "lat": 31.71452073144111,
                    "open": true
                }
            },
            {
                "_index": "fuzzy_search_ad",
                "_type": "shop_keyword",
                "_id": "3895216",
                "_score": 6.3706484,
                "_source": {
                    "weight": 196,
                    "biding": "蛋蛋麻辣香鍋",
                    "lon": 118.58495088376293,
                    "shopId": 77904,
                    "matchMode": "Fuzzy",
                    "lat": 31.94751527254444,
                    "open": true
                }
            }

結束語

到這裏,關於搜索廣告文檔召回就介紹到這裏了。基於ES或者Lucence(ES的倒排索引實現)我們可以很輕易的實現倒排索引,如果深入挖掘還能實現很多制定化的需求。

傳送門:https://zhuanlan.zhihu.com/p/28390635

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章