作者簡介
李雲梅,Zilliz 資料工程師,畢業於華中科技大學計算機系。加入 Zilliz 以來,致力於為開源向量資料庫 Milvus 探索解決方案,幫助使用者打造場景應用。深入關注自然語言處理技術和搜尋推薦系統,日常喜歡一個人貓著亂翻書。
「資料量太大了,召回怎麼這麼慢吶 ?」
「業務資料更新太快,動態更新跟不上啊 ?」
「部署好難 ? 」
做推薦系統工程的朋友們,你們是不是時常聽到諸如此類的抱怨?相信閱讀完這篇文章後,你可能會得到一些新思路、新方法。
在介紹具體專案之前,我們先來了解一下推薦系統。簡單來說,推薦系統就是根據使用者的個性化需求,在海量的資訊中確定提供給使用者什麼樣的具體內容。通常推薦系統分為兩個階段:「召回」和「排序」。「召回」是推薦系統的第一階段,主要根據使用者和商品部分特徵,從海量的物品庫裡,快速找出一部分使用者可能感興趣的物品,然後交給排序環節;而「排序」則是對所有召回的內容再次進行打分排序,選出得分最高的幾個結果並推薦給使用者。
想高效落地一個推薦系統?本文將以商品召回為例,介紹如何通過推薦召回演算法 MIND、百度飛槳生態下的大規模模型庫 PaddleRec 以及開源向量資料庫 Milvus,部署一個穩定易用的工業級推薦系統。PaddleRec 和 Milvus 的結合可以讓開發更簡單、部署更靈活,還可以快速進行模型效果驗證並提升迭代效率,實現快速召回的同時兼顧系統穩定性。
系統架構
本專案分為四個步驟:資料處理、模型訓練、模型測試、商品召回案例。在整個商品召回的過程中,該系統先從模型中讀取出訓練好的模型中的 item 向量,然後將 item 向量匯入 Milvus 中儲存起來。在召回階段,該系統將使用者的歷史點選序列通過 MIND 模型轉化得到四個使用者向量,代表使用者不同方面的興趣。然後,在 Milvus 庫中做商品向量的相似度搜尋,每個興趣向量得到 top_k 個相似商品,最後將四組商品按照相似度排序得到前 top_k 個商品,得到我們要召回的商品。
接下來,我將介紹本專案中所用到的主要元件:
MIND
MIND 演算法全稱為:Multi-Interest Network with Dynamic Routing for Recommendation at Tmall,是一個由阿里演算法團隊開發的推薦召回演算法。
在 MIND 誕生之前,大多現存的推薦系統模型都是用一個向量來表示一個使用者的多個興趣,但是這無法很好地表示使用者的多方面興趣,而 MIND 嘗試使用多個興趣向量去表示同一個使用者不同方向的興趣。
MIND 演算法提出了具有動態路由的多興趣網路,用於在召回階段處理使用者的不同興趣。具體來說,其設計了一個基於膠囊路由機制的多興趣提取器層,適用於聚類歷史行為和提取不同的興趣。
MIND 完整的網路結構如下圖所示:MIND 輸入使用者行為和使用者畫像特徵,輸出表示使用者興趣的向量。MIND 首先將來自輸入層 (Embedding Layer) 的 item 特徵通過嵌入層轉換為 embedding,接著每個 item 的 embedding 通過池化層 (Pooling Layer) 進一步平均。然後,使用者行為 embedding 將被送入多興趣提取層,產生興趣膠囊。最後,通過將興趣膠囊與使用者行為 embedding 連線起來,並通過幾個 ReLU 層轉換連線後的膠囊,獲得使用者表示向量。此外,在訓練過程中,還引入了一個額外的標籤感知注意力層 (Label-aware Attention) 來指導訓練過程。
PaddleRec
PaddleRec 是源於百度 PaddlePaddle 生態的大規模搜尋推薦模型庫,其目的在於為廣大使用者提供搭建推薦系統的一站式解決方案,讓廣大搜尋推薦領域的 AI 從業者,尤其是 AI 應用開發人員可以方便快捷地基於自己的業務搭建出推薦系統。
開篇提到,對於推薦系統從業者來說,要基於業務本身搭建自己的推薦系統,常常會遇到諸如易用性差、部署困難等問題。針對傳統的搭建推薦系統方式的缺點,PaddleRec 利用自身優勢解決了這類痛點,具體表現為:
- 易用性強:開源了召回、排序、融合、多工等多種型別的業內經典模型,能夠快速進行模型效果驗證並提升模型的迭代效率。PaddleRec 支援易用且效能極佳的分散式訓練能力,針對大規模稀疏場景極限優化,具有良好的水平擴充套件能力及加速比,使用者可以基於 K8s 快速搭建訓練環境。
- 支援部署:提供模型線上部署方案,即訓即用,兼顧靈活開發和高效能
此外,PaddleRec 在其專案中提供了各種經典推薦相關的模型,可以在 GitHub 專案中找到,具體可參考:https://github.com/PaddlePadd...
Milvus 資料庫
Milvus 是一款基於雲原生架構開發的開源向量資料庫,支援查詢和管理由機器學習模型或神經網路生成的向量資料。Milvus 在一流的近似最近鄰(ANN)搜尋庫(例如 Faiss、NMSLIB、Annoy)的功能基礎上進行擴充套件,具有按需擴充套件、流批一體和高可用等特點。Milvus 致力於簡化非結構化資料管理,並在不同的部署環境中提供一致的使用者體驗。
- 基於高效能的列式儲存和 Faiss、HNSWLib 等向量索引,Milvus 資料庫可以高效實現資料查詢,千萬級向量資料毫秒級召回。
- 基於雲原生設計,Milvus 資料庫可以輕鬆橫向擴充套件,能夠支援任意規模的儲存和計算。
- Milvus 幫助使用者關注非結構化資料的語意本身,使用者無需再關注資料持久化,負載均衡等複雜問題。
- Milvus 採用儲存與計算分離的架構設計。
基於上述特點,Milvus 可以很好地解決推薦系統中資料更新頻繁的問題,滿足召回階段對召回速度的要求,並且能夠兼顧系統穩定性。
這也是我們在本文介紹的召回系統中,針對海量向量的相似度檢索選擇使用 Milvus 而不是直接使用諸如 Faiss、Annoy 等近似最近鄰演算法,來儲存以及檢索向量的原因之一。
在 Milvus 開源社群中,你還可以找到更多 Milvus 的應用場景:以圖搜圖、智慧問答、相似文字檢索、視訊檢索……如果你在 AI 領域有向量檢索需求,引入 Milvus 會對你有所幫助。
系統實現
該專案的具體實現目前已經發布在 Baidu AI Studio 上,你可以在 AI Studio 平臺上啟動環境並直接執行該專案:https://aistudio.baidu.com/ai...
下面將分別從資料、模型實現與訓練、模型測試來介紹本專案的具體實現,以及如何使用訓練好的模型和 Milvus 搭建一個召回服務。
資料介紹
本文使用的原始資料集來自論文 ComiRec 提供的 AmazonBook 資料集。
本專案直接使用了 PaddleRec 中提供的資料下載和資料處理方式,具體可參考 GitHub 上 PaddleRec 專案中的 dataset 下的 AmazonBook: https://github.com/PaddlePadd...
得到的訓練資料集格式如下:
0,17978,0
0,901,1
0,97224,2
0,774,3
0,85757,4
其中每一列分別表示:
- uid: 使用者 id.
- item_id: 使用者點選的 item id
- time: 點選的順序(時間戳)
測試資料集格式如下:
user_id:487766 hist_item:17784 hist_item:126 hist_item:36 hist_item:124 hist_item:34 hist_item:1 hist_item:134 hist_item:6331 hist_item:141 hist_item:4336 hist_item:1373 eval_item:1062 eval_item:867 eval_item:62user_id:487793 hist_item:153428 hist_item:132997 hist_item:155723 hist_item:66546 hist_item:335397 hist_item:1926 eval_item:1122 eval_item:10105user_id:487820 hist_item:268524 hist_item:44318 hist_item:35153 hist_item:70847 eval_item:238318
其中每一列分別表示:
- uid: 使用者 id
- hist_item: 使用者點選的歷史 item id,多個 hist_item 是根據使用者歷史點選的時間戳排序的
- eval_item: 召回評估序列
模型實現與訓練
該步驟將使用 PaddleRec 基於 MIND 實現一個推薦系統中的召回模型,並使用 AmazonBook 的資料訓練模型。
模型輸入:
本專案中讀取原始訓練資料集的程式碼參考指令碼 /home/aistudio/recommend/model/mind/mind_reader.py
dygraph_model.py 使用如下程式碼處理資料,作為模型的輸入資料。該部分將上述原始資料中同一使用者的點選率按照時間戳排序,組合成一個序列。然後,從序列中隨機選取一個 item_id 作為 target_item,將序列 target_item 的前長度為 maxlen 的部分表示為模型輸入的 hist_item (長度不足用 0 補足),seq_len 為 hist_item 序列的實際長度。
def create_feeds_train(self, batch_data):
hist_item = paddle.to_tensor(batch_data[0], dtype="int64")
target_item = paddle.to_tensor(batch_data[1], dtype="int64")
seq_len = paddle.to_tensor(batch_data[2], dtype="int64")
return [hist_item, target_item, seq_len]
模型組網:
模型 MIND 的網路具體構造參考 /home/aistudio/recommend/model/mind/net.py
組網部分 net.py 的程式碼如下所示:
class Mind_Capsual_Layer(nn.Layer):
def __init__(self):
super(Mind_Capsual_Layer, self).__init__()
self.iters = iters
self.input_units = input_units
self.output_units = output_units
self.maxlen = maxlen
self.init_std = init_std
self.k_max = k_max
self.batch_size = batch_size
# B2I routing
self.routing_logits = self.create_parameter(
shape=[1, self.k_max, self.maxlen],
attr=paddle.ParamAttr(
name="routing_logits", trainable=False),
default_initializer=nn.initializer.Normal(
mean=0.0, std=self.init_std))
# bilinear mapping
self.bilinear_mapping_matrix = self.create_parameter(
shape=[self.input_units, self.output_units],
attr=paddle.ParamAttr(
name="bilinear_mapping_matrix", trainable=True),
default_initializer=nn.initializer.Normal(
mean=0.0, std=self.init_std))
class MindLayer(nn.Layer):
def label_aware_attention(self, keys, query):
weight = paddle.sum(keys * query, axis=-1, keepdim=True)
weight = paddle.pow(weight, self.pow_p) # [x,k_max,1]
weight = F.softmax(weight, axis=1)
output = paddle.sum(keys * weight, axis=1)
return output, weight
def forward(self, hist_item, seqlen, labels=None):
hit_item_emb = self.item_emb(hist_item) # [B, seqlen, embed_dim]
user_cap, cap_weights, cap_mask = self.capsual_layer(hit_item_emb, seqlen)
if not self.training:
return user_cap, cap_weights
target_emb = self.item_emb(labels)
user_emb, W = self.label_aware_attention(user_cap, target_emb)
return self.sampled_softmax(
user_emb, labels, self.item_emb.weight,
self.embedding_bias), W, user_cap, cap_weights, cap_mask
其中類 Mind_Capsual_Layer 定義了基於膠囊路由機制的使用者多興趣提取器層。函式 label_aware_attention() 實現了 MIND 演算法中標籤感知注意力這一技術。在類 MindLayer 的 forward() 函式中,對使用者特徵建模,構成使用者特徵權重向量。
模型優化:
本專案使用 Adam 演算法作為模型優化器,具體實現部分在指令碼 /home/aistudio/recommend/model/mind/dygraph_model.py
, 程式碼如下:
def create_optimizer(self, dy_model, config):
lr = config.get("hyper_parameters.optimizer.learning_rate", 0.001)
optimizer = paddle.optimizer.Adam(
learning_rate=lr, parameters=dy_model.parameters())
return optimizer
此外,PaddleRec 中將超引數都寫在 config.yaml 中,所以只需要對 config.yaml 一個檔案進行修改,就能夠清晰地對比模型效果,並快速進行模型效果驗證,極大地提升模型的迭代效率。在訓練模型的時候,模型效果較差可能是由於欠擬合或者過擬合引起。我們可以通過修改訓練的輪數,讓模型獲得更充分的訓練,以此來提高模型效果,而這裡僅需要改變 config.yaml 中的引數 epochs 來調整訓練訓練的輪次即可。此外,你還可以通過更改模型優化器 optimizer.class 或者是嘗試修改學習率(learning_rate)來除錯模型。config.yaml 中的部分引數如下:
runner:
use_gpu: True
use_auc: False
train_batch_size: 128
epochs: 20
print_interval: 10
model_save_path: "output_model_mind"
# hyper parameters of user-defined network
hyper_parameters:
# optimizer config
optimizer:
class: Adam
learning_rate: 0.005
模型訓練:
本專案中,將模型訓練的指令碼放在 /home/aistudio/recommend/model/trainer.py
中,直接執行以下命令即可開始訓練模型。
python -u trainer.py -m mind/config.yaml
模型測試:
該步驟將使用測試資料集,測試訓練生成的模型的召回率等特性。
測試時,會先從模型中讀出訓練時儲存的所有 item 的向量,隨後匯入 Milvus 資料庫中。接著,通過指令碼 /home/aistudio/recommend/model/mind/mind_infer_reader.py
讀取將測試資料集中的資料。
載入上述儲存的模型,並將測試資料集輸入模型,得到輸入的使用者的多興趣向量。對於每個使用者,模型將返回四個向量。最後,將得到的使用者向量在 Milvus 的 item 庫中搜尋得到最相似的 50 個 item,即為我們要向使用者推薦的向量。
通過執行以下命令來測試模型:
python -u infer.py -m mind/config.yaml -top_n 50
模型測試部分提供了 Recall@50、NDCG@50、HitRate@50 這幾個評測指標指標,可以根據這個值判斷模型的效果。由於本專案僅是作為演示和教程,所以訓練並不充足,導致這幾個評估值並不理想,在實際業務中,需要多訓練幾個 epoch,以保證模型的效果。相應的,訓練過程中也會儲存更多的模型引數,一般建議大家選擇最後儲存幾個模型進行測試,再根據測試和分析的結果選出最優的模型。此外,還可以通過更改使用不同的優化器和學習率等引數來訓練模型,多次訓練和測試,選出最優的模型應用於實際專案中。
召回服務
我們使用上述訓練的模型,並結合 Milvus 資料庫實現了一個推薦召回的服務。
在該召回服務中,使用 FASTAPI 對外提供服務,啟動後你可以通過 http 方式直接在終端執行命令,來實現召回服務。
執行以下命令,啟動召回服務:
uvicorn main:app
該服務一共提供了四個介面:
Item 向量匯入:啟動服務後,執行以下命令,該服務會讀取儲存在模型中的 item 向量並匯入 Milvus 資料庫的集合中。
curl -X 'POST' \ 'http://127.0.0.1:8000/rec/insert_data' \ -H 'accept: application/json' \ -d ''
召回:本介面提供最重要的召回服務。輸入任意使用者的 item 點選序列,召回該使用者下一個可能點選的 item。這裡可批量召回多個使用者的興趣 item。下面命令列中的 hist_item 是一個二維向量, 每一行表示任意一個使用者歷史點選的 item 序列,這裡的序列允許是變長序列。返回的結果也是一組二維向量,每一行分別對應輸入序列中的一個使用者,對其召回多個的 item id。
curl -X 'POST' \ 'http://127.0.0.1:8000/rec/recall' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "top_k": 50, "hist_item": [[43,23,65,675,3456,8654,123454,54367,234561],[675,3456,8654,123454,76543,1234,9769,5670,65443,123098,34219,234098]] }'
查詢 item 總量:執行以下命令,會返回 Milvus 資料庫中儲存的總 item 向量數。
curl -X 'POST' \ 'http://127.0.0.1:8000/rec/count' \ -H 'accept: application/json' \ -d ''
刪除:本介面用於刪除儲存在 Milvus 資料庫中的資料。
curl -X 'POST' \ 'http://127.0.0.1:8000/qa/drop' \ -H 'accept: application/json' \ -d ''
如果你在本地伺服器上啟動該召回服務,你還可以通過訪問 127.0.0.1:8000/docs 來檢視和訪問該召回服務提供的各個介面。如圖:
點選對應的介面,並輸入相應的引數,即可體驗相應的服務。例如在召回服務中,點選 rec/recall,然後點選 try it out,在 Request body 框中輸入相應的引數後點選 Execute 執行就可得到對應的結果:
系統實現
本專案選擇使用 PaddleRec 來實現演算法 MIND,是由於 PaddleRec 提供的訓練指令碼 trainner.py 和配置檔案 config.yaml 同樣適用於訓練其他模型,這使得模型訓練和部署起來非常簡單。此外,PaddleRec 專案中還提供了許多推薦領域的經典模型的具體實現,包括本專案中用到的 MIND 模型,可供我們參考使用。PaddleRec 的出現,使得我們在實現和訓練一個模型的過程中,只需要關注演算法本身而不用去過多的關注模型部署等。
Milvus 資料庫在向量相似度搜尋方面的高效能滿足了推薦系統的對召回速度的要求。同時,由於 Milvus 雲原生的特性,其在高可用和穩定性方面也能很好的滿足推薦系統的需求。除了在推薦領域, Milvus 資料庫還廣泛應用於計算機視覺(以圖搜圖,以圖搜視訊等),自然語言處理(智慧問答,文字相似搜尋)等領域。Milvus 資料庫在 GitHub 上開源了這些專案的具體實現(https://github.com/milvus-io/...),使用者在應用 Milvus 資料庫時可以直接參考這些專案是如何使用 Milvus 資料庫的,對 Milvus 資料庫新手來說入門也變得更加容易。
參考文獻
[1] Li C, Liu Z, Wu M, et al. Multi-interest network with dynamic routing for recommendation at Tmall[C]//Proceedings of the 28th ACM International Conference on Information and Knowledge Management. 2019: 2615-2623.
[2] Cen Y, Zhang J, Zou X, et al. Controllable multi-interest framework for recommendation[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 2942-2951.