1. 程式人生 > >【閱讀筆記】Applying Deep Learning To Airbnb Search

【閱讀筆記】Applying Deep Learning To Airbnb Search

Applying Deep Learning To Airbnb Search

Airbnb Inc.
[email protected]
2018年10月25日

ABSTRACT

最初使用 gradient boosted decision tree model 來做 search ranking ,搜尋效果從剛開始的上升逐漸趨於穩定。本文討論如何突破趨於平穩的效果。本文的目的不是講述模型上的突破,而是如何建立一個神經網路模型在一個實際的產品上。
KEYWORDS: Search ranking, Deep learning, e-commerce

INTRODUCTION

Airbnb 搜尋排名的打分函式最早的版本是手動設計的,後來使用 GBDT(gradient boosted decision tree) 模型替換手動設計的打分函式,房屋預定得到了大幅度的提升,接著迭代優化了很多次。經過很長一段時間的迭代優化實驗,發現線上預定房屋的收益達到了瓶頸。所以,在這個時候想嘗試一些新的突破。
Fig.1
完整的模型生態是預測 guest 預定房間,預測 host 接受,預測 guest 給出5星好評的整個流程。本文討論的是對可選房間進行排序的問題。如圖1所示,典型的 guest 搜尋 session 是 guest 搜尋若干次,偶爾點選來檢視房間細節,成功的 session 是 guest 預定預定某個 listing。這一過程被記錄在 log 中。利用 log 訓練新模型,使得離線效果有所提升,再使用 A/B test 線上測試,看指標是否有明顯的提升,然後再上線新模型。
本文概述:首先概述模型架構演變的情況; 其次是一些工程方面的注意事項;然後描述了一些使用的工具和超引數探索;最後總結回顧。

MODEL EVOLUTION

我們模型的演變是一個漸進的過程,圖2展示了離線指標 NDCG 和預定數隨著模型演變而增長的過程。
Fig.2

Simple NN

Andrej Karpathy 對於 model architecture 有一個建議: don’t be a hero。
我們第一個模型是 a simple single hidden layer NN with 32 fully connected ReLU activations (minimizing the L2 regression loss where booked listings are assigned a utility of 1.0 and listings that are not booked a utility of 0.0)。結果是與 GBDT model 效果差不多。這個過程驗證了神經網路上線的流程的可行性。

Lambdarank NN

當我們將 NN 與 Lambdarank 背後的想法結合起來時,我們的第一個突破就來了。Lambdarank 為我們提供了一種直接針對 NDCG 優化 NN 的方法。這涉及到簡單 NN 的基於迴歸的公式的兩個關鍵改進:

  • 損失函式轉變為交叉熵。
  • 通過對 NDCG 影響的差異來權衡 pairwise loss。 例如,從第2位調整為第1位將優先於從第10位移動到第9位。
def apply_discount(x): 
    '''Apply positional discount curve''' 
    return np.log(2.0)/np.log(2.0 + x)

def compute_weights(logit_op, session): 
    '''Compute loss weights based on delta ndcg. 
    logit_op is a [BATCH_SIZE, NUM_SAMPLES] shaped tensor 
    corresponding to the output layer of the network. 
    Each row corresponds to a search and each column a listing in the 
    search result. Column 0 is the booked listing, while columns 1 through 
    NUM_SAMPLES - 1 the not-booked listings. '''
    logit_vals = session.run(logit_op)
    ranks = NUM_SAMPLES - 1 - logit_vals.argsort(axis=1)
    discounted_non_booking = apply_discount(ranks[:, 1:])
    discounted_booking = apply_discount(np.expand_dims(ranks[:, 0], axis=1))
    discounted_weights = np.abs(discounted_booking - discounted_non_booking)
    return discounted_weight

# Compute the pairwise loss 
pairwise_loss = tf.nn.sigmoid_cross_entropy_with_logits(targets=tf.ones_like(logit_op[:, 0]), logits=logit_op[:, 0] - logit_op[:, i:] ) 
# Compute the lambdarank weights based on delta ndcg 
weights = compute_weights(logit_op, session) 
#Multiply pairwise loss by lambdarank weights 
loss = tf.reduce_mean(tf.multiply(pairwise_loss, weights))

Decision Tree/Factorization Machine NN

For the FM model we took the final prediction as a feature into the NN. From the GBDT model, we took the index of the leaf node activated per tree as a categorical feature.
在這裡插入圖片描述

Deep NN

Typical configuration of the network: an input layer with a total of 195 features after expanding categorical features to embeddings, feeding the first hidden layer with 127 fully connected ReLUs, and then the second hidden layer with 83 fully connected ReLUs.
為DNN提供的 feature 大多是用最少的特徵工程得到的簡單的屬性,如價格,便利設施數目種類,歷史預訂計數等。還有少量的其他評價模型輸出的特徵。
隨著訓練資料量的增加,我們明顯的減少了 generalization gap。
Fig.3

FAILED MODELS

Listing ID

把每個 listing 變成一個 embedding 作為特徵訓練模型,發現過擬合了。這是因為 Airbnb 獨特的性質,就算是最受歡迎的房間一年也只能預定365次,對於某些需要 embeding 的特徵數某些值的資料量受到了很大的限制(我的理解就是噪聲太大,導致 embeding 的結果不準確)。

Multi-task learning

把任務分為兩個(對某個 listing 瀏覽多久和是否預定)進行多工學習。線上效果不好。對看頁面多久的理解是我們要繼續的課題。

FEATURE ENGINEERING

Feature normalization

剛開始時,我麼用與 GBDT 相同的特徵訓練 NN,效果很差。因為樹模型對特徵的大小關係敏感,而神經網路需要進行歸一化,我們對正態分佈進行中心歸一化( f e a t u r e μ σ \frac{feature-\mu}{\sigma} ),對冪律分佈進行 log 歸一化( l o g ( 1 + f e a t u r e 1 + m e a n ) log(\frac{1+feature}{1+mean}) )。

Feature distribution

除了將特徵對映到受限制的數值範圍外,我們還確保其中大部分分佈平滑。 為什麼要沉迷於分佈的平滑? 以下是我們的一些原因。

發現錯誤

在處理數以億計的特徵樣本時,我們如何驗證它們中的一小部分沒有錯誤? 範圍檢查很有用但有限。 我們發現分佈的平滑性是發現錯誤的寶貴工具,因為錯誤的分佈通常與典型的分佈不同。 舉個例子,我們在某些地區的價格記錄中,存在與市價明顯不一致的錯誤。 這是因為在這些地區,對於超過28天的期間,記錄的價格是每月價格而不是每日價格。 這些錯誤表現為分佈圖上的尖峰。

有利於泛化

解釋 DNN 的泛化能力是研究前沿的複雜話題。我們發現在我們構建的 DNN 中,輸出層的分佈會逐漸變得越來越平滑。 圖8顯示了最終輸出層的分佈,而圖9和圖10顯示了隱藏層的一些樣本。 為了顯示隱藏層中的值,我們忽略了零值並且進行 l o g ( 1 + v a l u e ) log(1 + value) 變換。這些分佈圖給予我們 DNN 泛化能力的直覺。當建立一個以數百個特徵為基礎的模型時,所有特徵的組合空間不可思議的大,並且在訓練期間,而且覆蓋了一小部分組合特徵。來自較低層的平滑分佈確保了上層可以正確的輸出。 將這種直覺一直延伸到輸入層,我們盡最大努力確保輸入功能具有平滑的分佈。
我們而且發現下面的技術可用做模型魯棒性檢查:縮放測試集中給定特徵的所有值,例如價格為2x,3x,4x等,並觀察 NDCG 的變化。我們發現模型的效能非常穩定。
Fig.4
為了使地理特徵分佈更平滑,通過計算與中心點的偏移量來表徵地理特徵資訊。

Checking feature completeness.

某些特徵分佈的不平滑,會導致模型的學習資訊缺失。圖 12(a)展示的是原始房屋佔用分佈,(b)展示的是(房屋佔用 / 居住時長)歸一化後的分佈,分佈不太符合正常理解,調查發現列表中有一些房屋有最低的住宿要求,可能延長到幾個月。然而,開始我們沒有新增最低的居住時長特徵。所以,我們考慮新增最低居住時長作為模型的一個特徵。
Fig.5

High cardinality categorical features

低數量類別的特徵可以使用 one-hot 編碼,對於高數量類別特徵(例如郵編)利用一個雜湊函式對映成一個數字。類別特徵對映成 embedding,輸入神經網路模型中,訓練過程中,通過反向傳播來學習這些位置偏好資訊。

SYSTEM ENGINEERING

我們目前的 pipeline :一個訪客的搜尋查詢通過 Java 服務端返回檢索結果和分數;Thrift 來儲存查詢日誌,Spark 來處理訓練資料,TensorFlow 訓練模型,各個工具都是使用 Scala 和 Java 來編寫,模型上傳到 Java 服務端給訪客提供搜尋服務。

Protobufs and Datasets

最開始使用訓練 GBDT 的 CSV 格式,輸入給 TensorFlow 模型的 feed_dict,後來發現我們的 GPU 利用率只有 25%,大部分的訓練時間花費在解析 CSV 資料。後來使用 Protobufs 格式的資料集來訓練,速度提升了 17 倍,GPU 利用率提升到 90%。

Refactoring static features

我們業務中有一些特徵變化不大,比如位置、房間臥室的數量等,為了減少每次重複讀取磁碟消耗時間,我們將它們組合起來為其建立一個索引,通過 list 的 id 來檢索。

Java NN library.

在 2017 年我們打算開始將 TensorFlow 運用到生產環境的時候,發現沒有基於 Java 的高效的技術棧。多個語言之間切換導致產生服務延遲。所以,我們在 Java 上自己建立了自己的神經網路打分函式庫。

HYPERPARAMETERS

像 GBDT 中的超引數樹的個數、正則化等一樣,神經網路也許多超引數。下面是我們調超引數的一些經驗分享:

Dropout

Dropout 對神經網路防止過擬合是必不可少的,但是在我們的實際應用中,嘗試了多種正則化,都導致離線評估效果下降。所以,我們在訓練資料集中隨機複製一些無效的場景,是一種類似資料增強(data augmentation)的技術,來彌補這種缺失。另外,考慮到特定特徵分佈,我們手工添加了一些噪聲資料,離線評估的 NDCG 提高大約 1%,但是線上統計評估並沒有顯著的提升。

Initialization

第一個模型所有權重和 embeddings 都初始化為零,效果非常差。現在選擇 Xavier 來初始化所有的神經網路權重,使用 random uniform 初始化所有的 embeddings,其分佈區間在{-1,1}之間。

Learning rate

對於我們的資料,發現使用 Adam 優化演算法的預設引數很難提升效果,最後選擇了 LazyAdamOptimizer,當訓練 embeddings 時,速度非常快。

Batch size

改變 batch size 對訓練速度影響非常大,但是它對模型的確切影響是很難把握的。在我們使用的 LazyAdamOptimizer 優化器中,剔除學習率的影響外,我們選擇 batch size 的大小為 200 時,對我們目前的模型來說是最好的。

FEATURE IMPORTANCE

估計特徵重要性和模型可解釋性對於模型的實際應用有很重要的意義。特徵重要性可以指導我們更好的迭代模型。神經網路最大的優勢是解決特徵之間非線性組合。這同時導致瞭解哪些特徵對模型效果提升起關鍵作用這件事情變得困難了。下面分享一下我們在神經網路特徵重要性方面的一些探索:

Score Decomposition

在神經網路中,分析特徵的重要性很困難,容易讓人產生混亂。我們最初的做法是獲取神經網路產生的最終得分,並嘗試將其分解為各個節點貢獻得分。但是,在檢視結果之後發現這個想法在邏輯上有個錯誤:沒有一個清晰的方法可以將特定輸入節點和經過非線性啟用函式(ReLU 等)後的影響分開。

Ablation Test

另一種簡單想法是一次次刪減、替換特徵,重新訓練然後觀察模型的效能,同時也可以考慮特徵缺失導致效能成比例下降來衡量特徵的重要性程度。然而,通過這種方法評估特徵重要性有點困難,因為一些冗餘的特徵缺失,神經網路模型是可以彌補這種缺失的。

Permutation test

受隨機森林模型特徵重要性排序的啟發,這一次我們嘗試複雜一點的方法。在測試集上隨機的置換特徵,然後觀察測試上模型的效能。我們期望的是越重要的特徵,越會影響模型的效能。經試驗測試發現好多無意義的結果,比如: 列表中房屋的數量特徵對於預測房屋預定的概率影響非常大,但是僅僅測試這個特徵,其實是無意義的,因為房屋的數量還跟房屋的價格有關聯。

TopBot

TopBot 是我們自己設計的分析特徵重要性的工具,它可以依據排序自上向下分析。圖 14 展示瞭如何判斷特徵重要性,從圖中可以看出 price 特徵比較重要,review count 特徵不是特別重要。
Fig.6

RETROSPECTIVE

在無處不在的深度學習成功案例中,最初我們很樂觀的認為用深度學習直接取代 GBDT 模型就可以帶來巨大的收益。所以最初的討論都是圍繞其他保持不變的情況下,僅替換當前的 GBDT 模型為神經網路模型,看能帶來多大的收益。但這麼做使我們陷入了絕望的低谷,沒有得到任何的收益。隨著時間的推移,我們意識到僅僅替換模型不夠,還需要對特徵處理加以細化,並且需要重新思考整個模型系統的設計。(像 GBDT 這樣的模型受限於規模,易於操作,效能方面也表現不錯,可以用來處理中等大小的問題。)
基於我們嘗試的經驗,我們極力向大家推薦深度學習。這不僅僅是因為深度學習線上獲得的強大收益,它還改變了我們未來的技術路線圖。早期的機器學習主要精力花費在特徵工程上,轉移到深度學習後,特徵組合的計算交給神經網路隱層來處理,我們有更多的精力思考更深層次的問題,比如:改進我們的優化目標。目前的搜尋排名是否滿足所有使用者的需求?經過兩年探索,我們邁出了第一步,深度學習在搜尋上的應用才剛剛開始。