Neil Zhu,简书ID Not_GOD,University AI 创始人 & Chief Scientist,致力于推进世界人工智能化进程。制定并实施 UAI 中长期增长战略和目标,带领团队快速成长为人工智能领域最专业的力量。
作为行业领导者,他和UAI一起在2014年创建了TASA(中国最早的人工智能社团), DL Center(深度学习知识中心全球价值网络),AI growth(行业智库培训)等,为中国的人工智能人才建设输送了大量的血液和养分。此外,他还参与或者举办过各类国际性的人工智能峰会和活动,产生了巨大的影响力,书写了60万字的人工智能精品技术内容,生产翻译了全球第一本深度学习入门书《神经网络与深度学习》,生产的内容被大量的专业垂直公众号和媒体转载与连载。曾经受邀为国内顶尖大学制定人工智能学习规划和教授人工智能前沿课程,均受学生和老师好评。
函數打分(function scoring)小引
引言
任何搜索引擎,包括Elasticsearch,其關鍵在於打分。打分可以大致定義為尋找滿足某些條件的數據,並將數據根據其相關性進行排序後返回。相關性常常通過TF-IDF這樣的算法獲得,這些算法試著找出那些跟提交的查詢最為文本相似的文檔。儘管TF-IDF和它的小夥伴如BM25相當有效,還是有些時候需要其他的算法或者引入別的啟發式打分規則來給出最好的相關性。這裡Elasticsearch的script_score
和function_score
特性就有用武之地了。本文將介紹這些工具的使用。
常見的文本相似性不是最重要因素的例子是geo搜索。如果我們想要找到靠近某個給定地點的好咖啡店,對咖啡店進行根據搜索的文本相似性排序對用戶來說沒太大用處,但是通過地理位置的遠近來排序卻能夠給出一個較好的結果。
另一個例子是視頻分享網站的視頻查詢,其中搜索的結果應當將視頻的受歡迎度作為考慮的因素。如果一個流行明星上傳一個給定名稱的視頻,獲得了上百萬的觀看,這個視頻應該遠遠超過那些通過文本相似度獲得的結果。
入門
讓我們看看如何對此問題進行建模,使用文本相似性和視頻受歡迎程度的組合作為最終視頻結果排名的依據。最佳的方法當然是將這些描述轉換為數學公式了。在視頻網站,目標值主要有下面這些:
- 視頻的元數據(title, description, etc.)在標準的TF-IDF文本相關性的因素
- 給定視頻收到的likes數目
- 給定視頻的觀看數目
給定這些設定後,我們可以使用一個初等的公式,比如說score*(likes+views+1)
。注意我們已經設定likes
和views
的數目作為參數(最後加上1是避免出現都為0的情形)。使用乘法也是比較合理的,因為score
的絕對值不能被計數了。作為score
返回的特定的數目可能會隨著Elasticsearch不同的版本,查詢和shard而變化。當然這樣簡單的模型肯定會有一些明顯的問題,主要是對大數目的likes或者views的視頻他們的文本相似性低卻被匹配返回。我們需要削減大likes和views的值的影響。最簡單例子就是一個良好的文本匹配其view值為1肯定會比一個不好的文本匹配其view值很大(如1mm)這樣的結果肯定要處理。
降低更為流行的視頻的影響性的一種簡單方式是通過使用其對數log
來實現。我們把上面的公式改為:
score*log(likes+views+1)
這個公式讓算法更加合理,不會是的那些流行霸主視頻統治全場。此時該模型已經可以使用了,不過出了這個環境效果就不能保證了。讓我們通過實實在在的例子來看看調整後的結果。下面我們給出schema定義,一些測試數據,然後進行查詢。
# Create the index
curl -XPOST http://localhost:9200/searchtube
# Create the mappings
curl -XPUT http://localhost:9200/searchtube/video/_mapping -d '{
"video": {
"properties": {
"title": {
"type": "string",
"analyzer": "snowball"
},
"description": {
"type": "string",
"analyzer": "snowball"
},
"views": {
"type": "integer"
},
"likes": {
"type": "integer"
},
"created_at": {
"type": "date"
}
}
}
}'
# Create some docs
curl -XPUT http://localhost:9200/searchtube/video/1 -d '{
title: "Sick Sad World: Cold Breeze on the Interstate",
description: "Is your toll collector wearing pants, a skirt, or nothing but a smile? Cold Breeze on the Interstate, next on Sick, Sad World.",
views: 500,
likes:2,
created_at: "2014-04-22T08:00:00"
}'
curl -XPUT http://localhost:9200/searchtube/video/2 -d '{
title: "Sick Sad World: The Severed Pianist",
description: "When he turned up his nose at accordion lessons, they cut off his inheritance molto allegro. The Severed Pianist, next on Sick, Sad World.",
views: 6000,
likes: 100,
created_at: "2014-04-22T12:00:00"
}'
curl -XPUT http://localhost:9200/searchtube/video/3 -d '{
title: "Sick Sad World: Avant Garde Obstetrician",
description: "Meet the avant-garde obstetrician who has turned his cast offs into art work. Severed Umbilical cord sculpture next, on Sick, Sad World.",
views: 100,
likes: 130,
created_at: "2014-04-22T23:00:00"
}'
With this data set we can test out our formula with the query below.
curl -XPOST http://localhost:9200/searchtube/_search -d '
{
"query": {
"function_score": {
"query": {"match": {"_all": "severed"}},
"script_score": {
"script": "_score * log(doc['likes'].value + doc['views'].value + 1)"
}
}
}
}'
** [此處已經無法使用script_score],由於版本的問題。反正我自己試過若干次使用不了,若有成功者,請賜教。**
Elasticsearch中的下降函數(decay functions in Elasticsearch)
有些場景僅僅使用likes或者views是不夠的,但是肯定會基於已有的設置上進行相應的調整。在1小時內收到1000次觀看的視頻應該被看成“更熱門的”,相比較於在24小時內被觀看10000次的視頻。Elasticsearch提供了幾個decay函數來解決此類問題。
對我們的視頻數據,我們喜歡暫時在前一天最熱門的視頻,讓更近的視頻的分數更高一些。Elasticsearch有三種類型的模型來處理這種情形,gauss
、exp
和linear
。他們讓妳可以將一個值映射到模型曲線上的一個點。線性模型當是最簡單的。線性下降意味著,所有點均勻地按照時間進行遞減,時間越久則下降越大。而指數下降就同拋物線那樣,離原點越遠則下降的速度急劇變快。最後,高斯分佈有個S型的形狀,先緩慢後急劇最後緩慢的下降趨勢。下圖是三種不同的形狀,妳也可以在這兒看到3D的情形。
所有這幾個模型,我發現高斯模型是最為有用的。在我們的例子中,曲線頂端的緩慢下降意味著更多最近的視頻匯聚在一起,在一個視頻發佈後一小時內或者一個半小時內並不會有很大的變化。然而,我們希望開始急劇地來下降視頻,這就對應高斯分佈中的近線性下降的部分。最後,我們希望它能在某個點。
這樣就把我們的結果分成三個部分並且三部分之間有自然的轉換。簡單說,這就以為著非常近的視頻有一個分數懲罰,而稍微近的視頻有一個較大的分數懲罰,隨著年紀的贈長而變得越來越大,最老的視頻將獲的極大的懲罰。
曲線的形狀可以通過origin
、scale
、offset
和decay
進行控制。這三個變量是妳主要的工具。origin
和scale
可以定義區間的長度。如果希望我們趨勢視頻列表可以覆蓋整個一天24小時,那麼最好將origin
設置為當前的時間戳,scale
則設為24h。偏移offset可以來平鋪整個區間,例如可以設置其為1h,移除對當前視頻的懲罰。最後,decay
參數可以改變文檔基於其位置的層級。默認的decay
值為0.5
,更大的值會使得曲線更加陡峭,其效果也更加明顯。
現在我們可以整一個趨勢查詢,以時間近性來作為打分的因素。下面的查詢:
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"created_at": {
"origin": "2014-04-22T23:50:00",
"scale": "12h",
"offset": "1h",
"decay": 0.3
}
}
},
{
"gauss": {
"likes": {
"origin": 20000,
"scale": 20000
}
}
},
{
"gauss": {
"views": {
"origin": 20000,
"scale": 20000
}
}
}
]
}
}
}
妳會注意到,這個查詢會把"Sick Sad World: Avant Garde Obstetrician"作為第一個視頻返回,即使它只有100個觀看者和130喜歡,而下一條結果則擁有成千的觀看數和喜歡。這就是由於近性起到的作用。妳也許注意到我們不再使用script_score
查詢,並且使用高斯模型重新把likes和views作為查詢的附加指標。通過使用內設的高斯下降函數,我們的查詢應該比腳本查詢更加快速,同樣也可以調整這些曲線的形狀。
這類查詢可以被進一步的定製。通過使用附加的函數,我們可以改變成任意妳想要的函數,並整合更多的變量。另外,這些查詢可以用來進行地理查詢,幫助我們將地理點周圍的結果彙集起來。
欲尋求更加完備的關於函數打分查詢的資料和信息,請參考這裡