在大型平台上,推薦與使用者正在瀏覽的項目相似的內容是幫助使用者發現相關內容的關鍵技術。例如:
- Airbnb 推薦相似的住宿房源
- Amazon 推薦相似的商品
- Expedia 推薦相似的旅遊體驗

本章設計一個類似 Airbnb、Vrbo 等度假租賃網站的「相似房源」功能。當使用者點擊某個房源時,系統會推薦一組相似的房源。
需求釐清#
以下是候選人與面試官之間的典型對話。
候選人: 我可以假設商業目標是增加預訂量嗎?
面試官: 是的。
候選人:「相似性」如何定義?推薦的房源是否應與使用者正在瀏覽的房源相似?
面試官: 沒錯。兩個房源若位於相同社區、城市、價格區間等,即視為相似。
候選人: 推薦結果是否會根據使用者進行個人化?
面試官: 我們希望此功能同時適用於登入與匿名使用者。實務上會區分兩類使用者並對登入者進行個人化,但為簡化起見,假設兩者同等對待。
候選人: 平台上有多少房源?
面試官: 500 萬個房源。
候選人: 訓練資料集如何建構?
面試官: 好問題。本次面試中,假設僅使用使用者與房源的互動資料。模型不利用使用者屬性(如年齡、地點)或房源屬性(如價格、地點)。
候選人: 新房源多久後能出現在相似房源推薦中?
面試官: 假設新房源在發布一天後即可出現在推薦結果中。這段時間系統會收集新房源的互動資料。
問題摘要:設計度假租賃平台的「相似房源」功能。
- 輸入:使用者正在瀏覽的房源
- 輸出:依使用者可能點擊的機率排序的相似房源清單
- 推薦結果對匿名與登入使用者相同
- 平台約有 500 萬個房源,新房源一天後可出現在推薦中
- 商業目標:增加預訂量
將問題轉化為機器學習任務#
定義 ML 目標#
使用者依序點擊的房源通常具有相似特徵(如同一城市、相近價格區間)。基於此觀察,ML 目標定義為:在給定使用者正在瀏覽的房源的條件下,準確預測使用者接下來會點擊哪個房源。
系統的輸入與輸出#
如圖 9.2 所示,「相似房源」系統以使用者正在瀏覽的房源為輸入,輸出依點擊機率排序的房源清單。

選擇合適的 ML 類別#
多數推薦系統依賴使用者的歷史互動來理解其長期興趣,但這類系統可能不適合解決相似房源問題。原因在於:
- 近期瀏覽的房源比很久以前瀏覽的房源更具參考價值
- 此場景下,基於會話的推薦系統(session-based recommendation system)更為合適
許多電商與旅遊預訂平台(如 Airbnb)更依賴短期興趣進行推薦。當高品質推薦更依賴近期互動而非長期興趣時,基於會話的推薦往往可替代傳統推薦系統。
基於會話的推薦系統#
基於會話的推薦系統旨在根據使用者近期瀏覽的項目序列,預測下一個項目。其特點為:
- 使用者興趣具情境依賴性,且變化快速
- 好的推薦高度依賴使用者最近的互動,而非泛化興趣

基於會話 vs. 傳統推薦系統的比較:
| 面向 | 傳統推薦系統 | 基於會話的推薦系統 |
|---|---|---|
| 使用者興趣 | 情境無關,變化緩慢 | 動態,變化快速 |
| 目標 | 學習使用者的泛化興趣 | 理解使用者的短期興趣 |
建構基於會話推薦系統的常用技術是利用使用者瀏覽歷史中項目的共現關係(co-occurrence)來學習項目嵌入(item embedding)。例如:
- Instagram 學習帳號嵌入以驅動「探索」功能
- Airbnb 學習房源嵌入以驅動相似房源功能
- Word2Vec 使用類似方法學習有意義的詞嵌入
本章將「相似房源」問題框架為基於會話的推薦任務。訓練模型將每個房源映射為一個嵌入向量,使得在使用者瀏覽歷史中頻繁共現的兩個房源,其嵌入向量在嵌入空間中彼此接近。
推薦相似房源時,在嵌入空間中搜尋與目前瀏覽房源最接近的房源。如圖 9.4 所示,每個房源被映射至 $2 \mathrm{D}$ 空間。要為 $L_t$ 推薦相似房源,選擇嵌入最接近的前 3 個房源。

資料準備#
資料工程#
可用的資料包含:
- 使用者資料(Users)
- 房源資料(Listings)
- 使用者與房源互動資料(User-listing interactions)
使用者資料#
簡化的使用者資料 schema 如下表所示。
| ID | Username | Age | Gender | City | Country | Language | Time zone |
|---|
Table 9.1: 使用者資料 schema
房源資料#
房源資料包含各房源的屬性,如價格、床位數、房東 ID 等。
| ID | Host ID | Price | Sq ft | Rate | Type | City | Beds | Max guests |
|---|---|---|---|---|---|---|---|---|
| 1 | 135 | 135 | 1060 | 4.97 | Entire place | NYC | 3 | 4 |
| 2 | 81 | 80 | 830 | 4.6 | Private room | SF | 1 | 2 |
| 3 | 64 | 65 | 2540 | 5.0 | Shared room | Boston | 4 | 6 |
Table 9.2: 房源資料
使用者與房源互動資料#
此表儲存使用者與房源的互動紀錄,如曝光、點擊與預訂。
| ID | User ID | Listing ID | Position of the listing in the displayed list | Interaction type | Source | Timestamp |
|---|---|---|---|---|---|---|
| 2 | 18 | 26 | 2 | Click | Search feature | 1655121925 |
| 3 | 5 | 18 | 5 | Book | Similar listing feature | 1655135257 |
Table 9.3: 使用者與房源互動資料
特徵工程#
如「將問題轉化為 ML 任務」章節所述,模型僅利用使用者的瀏覽歷史進行訓練,不使用房源價格、使用者年齡等其他資訊。
本章中,瀏覽歷史稱為「搜尋會話」(search session)。搜尋會話是一連串被點擊的房源 ID 序列,最終以一個被預訂的房源結束,中間無中斷。圖 9.5 展示了一個搜尋會話的範例:使用者從點擊 $L_1$ 開始,最終預訂了 $L_{20}$。

在特徵工程步驟中,我們從互動資料中擷取搜尋會話。Table 9.4 展示了搜尋會話的簡單範例。
| Session ID | Clicked listing IDs | Eventually booked listing ID |
|---|---|---|
| 1 | 1,5,4,9 | 26 |
| 2 | 6,8,9,21,6,13,6 | 5 |
| 3 | 5,9 | 11 |
Table 9.4: 搜尋會話資料
模型開發#
模型選擇#
神經網路是學習嵌入的標準方法。選擇合適的網路架構取決於多項因素:
- 任務複雜度
- 訓練資料量
- 超參數(如神經元數量、層數、啟動函數等)
常見做法是透過實驗選擇表現最佳的架構。本案例選擇淺層神經網路架構來學習房源嵌入。
模型訓練#
如圖 9.6 所示,對於給定的輸入房源,模型的任務是預測該房源上下文(context)內的房源。

訓練流程如下:
- 將房源嵌入初始化為隨機向量
- 使用滑動窗口法逐步讀取搜尋會話
- 隨著窗口滑動,更新中心房源的嵌入,使其與窗口內其他房源的嵌入相似,並與窗口外房源的嵌入不相似
- 模型利用這些嵌入預測給定房源的上下文
為適應新房源,我們每日以新建構的訓練資料重新訓練模型。
建構資料集#
本案例使用一種名為「負採樣」(negative sampling) 的技術來建構資料集,這是學習嵌入的常用方法。
建構訓練資料時,從搜尋會話中建立正樣本對與負樣本對:
- 正樣本對:預期應有相似嵌入的房源對
- 負樣本對:預期應有不相似嵌入的房源對
具體流程:
- 對每個會話,使用滑動窗口法讀取房源
- 以窗口中的中心房源與其上下文房源建立正樣本對
- 以中心房源與隨機採樣的房源建立負樣本對
- 正樣本對標記為 1,負樣本對標記為 0
圖 9.7 展示了透過滑動搜尋會話產生正負樣本對的過程。

選擇損失函數#
損失函數衡量真實標籤與預測機率之間的差異。若兩個房源為正樣本對,其嵌入應接近;若為負樣本對,嵌入應遠離。計算步驟如下:
- 計算兩個嵌入之間的距離(例如點積)
- 使用 Sigmoid 函數將距離轉換為 0 到 1 之間的機率值
- 使用交叉熵(cross-entropy)作為分類損失,衡量預測機率與真實標籤之間的損失

損失函數公式如下:
$\text { loss }=\sum_{(c, p) \in D_p} \log \frac{1}{1+e^{-E_p \cdot E_c}}+\sum_{(c, n) \in D_n} \log \frac{1}{1+e^{E_n \cdot E_c}}$
其中:
- $c$ 為中心房源,$p$ 為正樣本房源(與 $c$ 在上下文中共現),$n$ 為負樣本房源(未與 $c$ 共現)
- $E_c$ 為中心房源 $c$ 的嵌入向量
- $E_n$ 為負樣本房源 $n$ 的嵌入向量
- $E_p$ 為正樣本房源 $p$ 的嵌入向量
- $D_p$ 為正樣本對集合 $\langle c, p\rangle$,代表嵌入向量被推近的(中心房源, 上下文房源)元組
- $D_n$ 為負樣本對集合 $\langle c, n\rangle$,代表嵌入向量被推遠的(中心房源, 隨機房源)元組
第一個求和計算正樣本對的損失,第二個求和計算負樣本對的損失。
改進損失函數以學習更好的嵌入#
上述損失函數是良好的起點,但有兩個不足之處:
- 未考慮最終預訂的房源:訓練時中心房源的嵌入只被推向上下文中的房源,但未被推向最終預訂的房源。這導致嵌入擅長預測鄰近的點擊房源,卻不擅長預測最終預訂的房源。
- 負樣本多來自不同區域:由於負樣本是隨機採樣的,主要包含不同區域的房源。但使用者通常只在特定區域(如舊金山)內搜尋,這可能導致嵌入對同區域房源的區分能力不足。
改進一:將最終預訂房源作為全域上下文(Global Context)
將最終預訂的房源視為訓練階段的全域上下文。隨著窗口滑動,部分房源會進出上下文集合,但最終預訂的房源始終保留在全域上下文中,用於更新中心房源的嵌入。

具體做法是將 $\langle$ 中心房源, 最終預訂房源 $\rangle$ 對加入訓練資料並標記為正樣本。這驅動模型在訓練時將最終預訂房源的嵌入推向會話中每個被點擊房源的嵌入,如圖 9.9 所示。
改進二:加入同區域負樣本對
隨著窗口滑動,從中心房源的同一社區中選取一個不在其上下文內的房源,標記為負樣本並加入訓練資料。
更新後的損失函數如下:
$\begin{aligned} \operatorname{loss}= & \sum_{(c, p) \in D_p} \log \frac{1}{1+e^{-E_c \cdot E_p}}+\sum_{(c, n) \in D_n} \log \frac{1}{1+e^{E_c \cdot E_n}}+ \ & \sum_{(c, b) \in D_{\mathrm{booked}}} \log \frac{1}{1+e^{-E_c \cdot E_b}}+\sum_{(c, n) \in D_{\mathrm{hard}}} \log \frac{1}{1+e^{E_c \cdot E_n}} \end{aligned}$
其中:
- $E_b$ 為最終預訂房源 $b$ 的嵌入向量
- $D_{\text {booked }}$ 為 $\langle c, b\rangle$ 對,代表嵌入向量被推近的(中心房源, 預訂房源)元組
- $D_{\text {hard }}$ 為困難負樣本對 $\langle c, n\rangle$,代表嵌入向量被推遠的(中心房源, 同區域負樣本房源)元組
各求和項的含義:
- 第一項:正樣本對的損失(如前所述)
- 第二項:負樣本對的損失(如前所述)
- 第三項:包含全域上下文的新增正樣本對損失,幫助模型將中心房源嵌入推向最終預訂房源嵌入
- 第四項:同區域新增負樣本對的損失,強制模型將它們的嵌入推遠
評估#
離線指標#
在模型開發階段,使用離線指標衡量模型輸出品質並比較新舊模型。評估學習到的嵌入的方法之一是測試其預測最終預訂房源的能力(基於使用者最後一次點擊)。
最終預訂房源的平均排名(Average Rank of Eventually Booked Listing)
圖 9.10 展示了一個使用者的搜尋會話範例:
- 會話共包含 7 個房源
- 第一個為使用者最先瀏覽的房源 $L_0$
- 接下來 5 個為使用者依序點擊的房源
- 最後一個 $L_6$ 為使用者最終預訂的房源

使用模型計算第一個點擊房源與其他房源在嵌入空間中的相似度,然後依相似度排序。最終預訂房源的位置代表新模型能將 $L_6$ 推薦到多高的排名。如圖 9.10 所示,新模型(第二列)成功將最終預訂房源 $L_6$ 排在第二位。
若模型能將最終預訂房源排在較高位置,表示學習到的嵌入能將其放在推薦清單的前面。在驗證資料集的所有會話中,對最終預訂房源的排名取平均,即可計算此指標的值。
線上指標#
根據需求,商業目標是增加預訂量。線上指標選項如下:
- 點擊率(Click-through Rate, CTR)
- 會話預訂率(Session Book Rate)
CTR:使用者看到推薦房源後實際點擊的比率。
$C T R=\frac{\text { Number of clicked listings }}{\text { Number of recommended listings }}$
此指標衡量使用者參與度。使用者點擊房源的頻率越高,某些被點擊的房源轉化為預訂的可能性就越大。但 CTR 無法直接衡量實際預訂量,因此需要「會話預訂率」作為補充。
會話預訂率:搜尋會話轉化為預訂的比率。
$\text { Session book Rate }=\frac{\text { Number of sessions turned into booking }}{\text { Total number of sessions }}$
此指標與商業目標直接相關。會話預訂率越高,平台產生的收入越多。
服務架構#
服務時,系統推薦與使用者正在瀏覽的房源相似的房源。圖 9.11 展示了 ML 系統設計的總覽。

以下詳細說明各主要元件。
訓練流程(Training Pipeline)#
訓練流程使用新房源與使用者互動資料微調模型,確保模型始終適應最新的互動與房源。
索引流程(Indexing Pipeline)#
利用訓練好的模型,可預先計算平台上所有房源的嵌入並儲存於索引表中,大幅加速預測流程。
索引流程負責建立與維護索引表:
- 當新房源嵌入可用時,將其加入索引表
- 當新訓練模型可用時,使用新模型重新計算所有嵌入並更新索引表
預測流程(Prediction Pipeline)#
預測流程推薦與使用者正在瀏覽房源相似的房源,由以下元件組成(如圖 9.11 所示):
- 嵌入擷取服務(Embedding Fetcher Service)
- 最近鄰搜尋服務(Nearest Neighbor Service)
- 重新排序服務(Re-ranking Service)
嵌入擷取服務#
此服務以目前瀏覽的房源為輸入,根據房源是否在訓練中出現過而有不同處理方式:
房源已在訓練中出現:
- 嵌入向量已學習完成,存於索引表中
- 直接從索引表擷取嵌入
房源未在訓練中出現(新房源):
- 模型未見過此房源,無法直接找到相似房源
- 使用啟發式方法處理:例如使用地理位置鄰近房源的嵌入作為替代
- 當新房源收集到足夠的互動資料後,訓練流程會透過微調模型學習其嵌入
最近鄰搜尋服務#
為推薦相似房源,需計算目前瀏覽房源的嵌入與平台上其他房源嵌入之間的相似度,並輸出嵌入空間中最近的鄰居房源。
平台有 500 萬個房源,逐一計算相似度耗時且可能拖慢服務速度。因此使用近似最近鄰方法(Approximate Nearest Neighbor, ANN)加速搜尋。
重新排序服務#
此服務根據使用者篩選條件與特定約束修改房源清單。例如:
- 移除超過使用者設定價格上限的房源
- 移除與目前瀏覽房源不在同一城市的房源
其他討論主題#
若面試尚有剩餘時間,可討論以下主題:
- 位置偏差(positional bias)是什麼,以及如何處理
- 基於會話的方法與隨機漫步(random walk) 的比較,以及如何使用帶重啟的隨機漫步(RWR)推薦相似房源
- 如何透過考慮使用者的長期興趣來個人化基於會話推薦系統的結果(in-session personalization)
- 鑑於季節性對度假租賃影響巨大,如何將季節性納入相似房源系統