系統召回太慢?上 Milvus × PaddleRec 雙劍合璧大法!

語言: CN / TW / HK

作者簡介 李雲梅,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/PaddlePaddle/PaddleRec

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/aistudio/projectdetail/2250360?contributionType=1&shared=1

下面將分別從數據、模型實現與訓練、模型測試來介紹本項目的具體實現,以及如何使用訓練好的模型和 Milvus 搭建一個召回服務。

數據介紹

本文使用的原始數據集來自論文 ComiRec 提供的 AmazonBook 數據集。 本項目直接使用了 PaddleRec 中提供的數據下載和數據處理方式,具體可參考 GitHub 上 PaddleRec 項目中的 dataset 下的 AmazonBook: https://github.com/PaddlePaddle/PaddleRec/tree/release/2.1.0/datasets/AmazonBook

得到的訓練數據集格式如下:

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/bootcamp),用户在應用 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.


Arch Meetup 深圳站開始報名啦,點擊查看活動議程!

GitHub @Milvus-io|CSDN @Zilliz Planet|Bilibili @Zilliz-Planet

Zilliz 以重新定義數據科學為願景,致力於打造一家全球領先的開源技術創新公司,並通過開源和雲原生解決方案為企業解鎖非結構化數據的隱藏價值。

Zilliz 構建了 Milvus 向量數據庫,以加快下一代數據平台的發展。Milvus 數據庫是 LF AI & Data 基金會的畢業項目,能夠管理大量非結構化數據集,在新葯發現、推薦系統、聊天機器人等方面具有廣泛的應用。