- 產品
- 產品解決方案
- 行業解決方案
- 案例
- 數據資產入表
- 賦能中心
- 伙伴
- 關于
時間:2022-04-21來源:狗子還好嗎瀏覽數:581次

文章作者:楊旭東 阿里巴巴 算法專家
出品平臺:DataFunTalk
導讀:為什么需要冷啟動?通常推薦系統通過協同過濾、矩陣分解或是深度學習模型來生成推薦候選集,這些召回算法一般都依賴于用戶-物品行為矩陣。在真實的推薦系統中,會有源源不斷的新用戶、新物品加入,這些新加入系統的用戶和物品由于缺乏足夠豐富的歷史交互行為數據,常常不能獲得準確的推薦內容,或被準確推薦給合適的用戶。這就是所謂的推薦冷啟動問題。冷啟動對推薦系統來說是一個挑戰,究其原因是因為現有的推薦算法,無論是召回、粗排還是精排模塊,都對新用戶、新物品不友好,它們往往過度依賴系統收集到的用戶行為數據,而新用戶和新物品的行為數據是很少的。這就導致新物品能夠獲得的展現機會是偏少的;新用戶的興趣也無法被準確建模。
對于某些業務來說,及時推薦新物品,讓新物品獲得足夠的曝光量對于平臺的生態建設和長期受益來說都是很重要的。比如,在新聞資訊的時效性很強,如不能及時獲得展現機會其新聞價值就會大大降低;自媒體UGC平臺如果不能讓新發布的內容及時獲得足夠數量的展現就會影響內容創作者的積極性,從而影響平臺在未來能夠收納的高質量內容的數量;相親交友平臺如果不能讓新加入的用戶獲得足夠多的關注,那么就可能不會有源源不斷的新用戶加入,從而讓平臺失去活躍性。
綜上,冷啟動問題在推薦系統中至關重要,那么如何解決冷啟動問題呢?
01如何解決冷啟動問題解決推薦系統的冷啟動問題的算法(或策略)我總結為:“泛、快、遷、少” 四字口訣。?

1. 泛
即對新物品進行泛化,在屬性或主題上往更寬泛的概念上靠。比如,新上架一個商品,可以推薦給以往喜歡同品類的用戶,也就是從?“商品”?上推至 “品類”;新上線一個短視頻,可以推薦給關注了該視頻作者的用戶,也就是從“短視頻”上推至“作者”;新發布的一篇新聞資訊,可以推薦給喜歡同一主題用戶,比如把介紹“殲20”的文章推薦給一個軍事迷,也就是從“新聞資訊”上推至“主題”。本質上,這是一種基于內容的推薦(Content Based Recommandation)。
當然,為了更好的推薦效果,我們有時候需要同時上推至多個不同的“上位概念”,比如新商品除了上推至“品類”,還可以上推至 “品牌”、“店鋪”、“款式”、“顏色”等。上推的概念有時候是新物品天然就具有的,這種情況比較簡單,比如商品的各種屬性一般在商品發布的時候商家就填好了;也有些概念并不是本來就有,比如文章的主題,這篇文章是屬于“軍事”、“體育”、“美妝”等哪個主題是需要另外的算法來挖掘的。
除了在標簽或主題上的泛化,用某種算法得到用戶和物品的embedding向量,再通過向量的距離/相似度來做用戶和物品的興趣匹配也是一種很常用的手段。矩陣分解、深度神經網絡模型等算法都可以生成用戶和物品的embedding向量,然而常規的模型還是需要依賴用戶和物品的交互行為數據來建模,并不能很好地泛化到冷啟動的用戶和物品上。現在也有一些可以用來為冷啟動用戶和物品生成embedding向量的模型,比如下文要詳細介紹的DropoutNet。
上推或者泛化這種方法,雖然聽上去很簡單,也很好理解,不過,要往深了挖,也還是有很多工作可以做的。本質上,這是在利用物品的內容(屬性)信息來彌補該新物品缺少歷史交互行為的問題。比如,可以使用物品的多模態信息,如圖片、視頻等來做相關的推薦。例如,在相親平臺,可以給新用戶(這里看作被推薦的物品)的照片顏值打一個分,然后推薦給具有相關顏值偏好的用戶(這里指瀏覽推薦列表的用戶)。
2. 快
天下武功,唯快不破。所謂的冷啟動物品,也就是缺少歷史用戶交互行為的物品,那么一個很自然的思路就是更快地收集到新物品的交互行為,并在推薦系統里加以利用。常規的推薦算法模型和數據都是以天為單位來更新,基于實時處理系統可以做到分鐘級、甚至秒級的數據及模型更新。這類方法通常是基于強化學習/contextual bandit 類的算法。這里給兩篇參考文章,就不贅述了:《Contextual Bandit算法在推薦系統中的實現及應用》、《在生產環境的推薦系統中部署Contextual bandit算法的經驗和陷阱》。
3. 遷
遷移學習是一種通過調用不同場景中的數據來建立模型的方法。通過遷移學習可以將知識從源域遷移到目標域。比如,新開了某個業務,只有少量樣本,需要用其他場景的數據來建模。此時其他場景為源域,新業務場景為目標域。再比如,有些跨境電商平臺在不同國家有不同的站點,有些站點是新開的,只有很少的用戶交互行為數據,這個時候可以用其他比較成熟的其他國家的站點的交互行為數據來訓練模型,并用當前國家站點的少量樣本做fine-tune,也能起到不錯的冷啟動效果。使用遷移學習技術要注意的是源領域與目標領域需要具體一定的相關性,比如剛說的不同國家的站點可能賣的商品有很大一部分是重疊的。
4. 少
少樣本學習(few-shot learning)技術顧名思義是只使用少量監督數據訓練模型的技術。其中一直典型的少樣本學習方法是元學習(meta learning)。鑒于本文的目的不是介紹這些學習技術,這樣不再過多介紹,有興趣的同學可以參考一下:《基于元學習(Meta-Learning)的冷啟動推薦模型》。
本文主要介紹一種基于“泛化”的方法,具體地,我們會詳細介紹一種能應用于完全冷啟動場景的embedding學習模型:DropoutNet。原始的DropoutNet模型需要提供用戶和物品的embedding向量作為輸入監督信號,這些embedding向量通常來自其他的算法模型,如矩陣分解等;使得模型使用門檻增高。本文提出了一種端到端的訓練方式,直接使用用戶的交互行為作為訓練目標,大大降低了模型的使用門檻。
另外,為了使模型的學習更加高效,本文在常規二分類預估模型的pointwise損失函數的基礎上,增加了兩種新的損失函數:一種是專注于提升AUC指標的rank loss;另一種是用于改進召回效果的Support Vector Guided Softmax Loss。后者創新性地采用了一種稱之為“Negative Mining”的負采樣技術,在訓練過程中,自動從當前mini batch中采樣負樣本物品,從而擴大了樣本空間,能達到更好的學習效果。
因此,本文的貢獻主要有兩點,總結如下:
本文對原始DropoutNet模型進行了改造,直接使用用戶與物品的交互行為數據作為訓練目標進行端到端訓練,從而避免了需要使用其他模型提供用戶和物品的embedding作為監督信號。
文本創新性地提出了一種采用多種類型的損失函數的多任務學習框架,并在訓練過程中使用了Negative Mining的負采樣技術,在訓練過程中從當前mini batch中采樣負樣本,擴大了樣本空間,使得學習更加高效,同時適用于訓練數據量比較少的場景。
02DropoutNet模型解析NIPS 2017的文章《DropoutNet: Addressing Cold Start in Recommender Systems》介紹了一種既適用于頭部用戶和物品,也適用于中長尾的、甚至全新的用戶和物品的召回模型。
DropoutNet是一個典型的雙搭結構,用戶tower用來學習用戶的潛空間向量表示;對應地,物品tower用來學習物品的潛空間向量表示。當用戶對當前物品具有某種交互行為,比如點擊、購買時,模型的損失函數設計設定用戶的向量表示與物品的向量表示距離盡可能近;當給用戶展現了某物品,并且用戶沒有對該物品產生任何交互行為時,對應的用戶、物品pair構成一條負樣本,模型會盡量讓對應樣本中用戶的向量表示與物品的向量表示距離盡可能遠。
為了使模型適用于推薦系統的任何階段,既能用來學習頭部用戶與物品的向量表示,又能用來學習中長尾、甚至全新的用戶與物品的向量表示,DropoutNet把用戶和物品的特征都分為兩個部分:內容特征、偏好統計特征。內容特征相對比較穩定,不太會經常改變,并且一般在用戶注冊或者物品上線時就已經收集到對應的信息。另一方面偏好統計特征是基于交互行日志統計得到的特征,是動態的、會隨著時間的變化而變化。全新的用戶和物品由于沒有對應的交互行為,因而不會有偏好統計特征。

那么DropoutNet是如何使模型適用于學習全新的物品和用戶向量表示的呢?
其實思路非常簡單,借鑒了深度學習中的dropout的思想,對輸入的部分特征按照一定概率強行置為0,即所謂的input dropout。注意這里的dropout不是作用在神經網絡模型的神經元上,而是直接作用在input節點上。具體地,用戶和物品的偏好統計特征在學習過程中都有一定的概率被置0,而內容維度的特征則不會進行dropout操作。
根據論文的介紹,DropoutNet借鑒了降噪自動編碼機(denoising autoencoder)的思想,即訓練模型接受被corrupted的輸入來重建原始的輸入,也就是學習一個模型使其能夠在部分輸入特征缺失的情況下仍然能夠得到比較精確的向量表示,具體地,模型是要使得在輸入被corrupted的情況下學習到的用戶向量與物品向量的相關性分盡可能接近輸入在沒有被corrupted的情況下學習到的用戶向量與物品向量的相關性分。
目標函數為:

其中,
是模型學到的用戶向量表示,
是模型學到的物品向量表示;**
和
分別是外部輸入的、作為監督信號的用戶和物品向量表示,一般是通過其他模型學習得到**。
為了使模型適用于用戶冷啟動場景,訓練過程中對用戶的偏好統計特征進行dropout:

DrouputNet模型的學習過程如算法1所示:

DropoutNet模型的一大弊端是需要提供用戶和物品的embedding向量作為監督信號。模型通過dropout的方式mask掉一部分輸入特征,并試圖通過部分輸入特征學習到能夠重建用戶與物品embedding向量相似度的向量表示,原理類似于降噪自動編碼機。這就意味著我們需要另一個模型來學習用戶與物品的embedding向量,從整個流程來看需要分兩階段來完成學習目標:第一階段訓練一個模型得到用戶與物品的embedding向量,第二階段訓練DropoutNet模型得到更加robust的向量表示,并且能夠適用于全新的冷啟動用戶和物品。
為了簡化訓練流程,我們提出了一種端到端訓練的方式,在新的訓練方式下,不再需要提供用于和物品的embedding向量作為監督信號,取而代之,我們使用用戶與物品的交互行為作為監督信號。比如,類似于點擊率預估模型,如果用戶點擊了某物品,則該用戶與物品構成正樣本;那些展現給用戶但卻沒有被點擊的商品構建成負樣本。通過損失函數的設計,可以使模型學習到正樣本的用戶與物品向量表示的相似度盡可能高,負樣本的用戶與物品的向量表示的相似度盡可能低。
例如,可以使用如下的損失函數:

其中,y∈{0,1}?是模型擬合的目標;V+?表示與用戶?u 有交互行為的物品;V-?表示與用戶u 沒有交互行為的物品。
04在線負采樣 & 損失函數作為一個推薦系統召回階段的模型,如果只是使用曝光日志來構建訓練樣本是不夠的,因為通常情況下用戶只能被展現一小部分物品,平臺上大部分物品可能從未對當前用戶曝光過,如果這些未曝光的物品不與當前用戶構建成樣本,則模型只能探索到潛在樣本空間的很小一部分,使得模型的泛化性能較弱。
樣本負采樣是召回模型常用的技術,也是保證模型效果的關鍵。負采樣有多種方法,可以參考Facebook的論文 《Embedding-based Retrieval in Facebook Search》,這里不再贅述。下面僅從實現的角度來談談具體如何做樣本負采樣。
樣本負采樣通常有兩種做法,如下表所示。

另一種更討巧的實現方式是從當前mini-batch中采樣。因為訓練數據需要全局混洗(shuffle)之后再用來訓練模型,這樣每個mini-batch中的樣本集都是隨機采樣得到的,當我們從mini-batch中采樣負樣本時,理論上相當于是對全局樣本進行了負采樣。這種方式實現起來比較簡單,本文就是采用這種在線采樣的方法。在線樣本負采樣也有不同的實現方式。比如可以用一個全局共享內存來維護待采樣的物品集,這種方式的一個缺點是實現起來比較復雜。一般情況下,我們都會收集匯聚多天的用戶行為日志用來構建樣本,樣本的總量是很大的,無法全部放入內存中。同一個物品出現在多天的樣本中時,對應的統計特征也是不同的,稍有處理不當就可能發生特征穿越的問題。
具體地,訓練過程中,用戶、物品特征執行完網絡的forward階段后,得到了用戶embedding、物品embedding,接下來我們通過對物品embedding矩陣(batch_size * embedding_size)做一個按行偏移操作(row-wise roll),把矩陣的行(對應物品embedding)整體向下移動 N 行,被移出矩陣的 N 行再重現插入到矩陣最前面的 N行,相當于是在一個循環隊列中依次往一個方向移動了 N 步。這樣就得到了一個負樣本的用戶物品 pair
,重復上述操作 M 次就得到了 M 個負樣本 pair。

改造后的DropoutNet網絡如上圖所示。
首先,計算用戶語義向量與正樣本物品的余弦相似度,記為
;然后計算用戶語義向量與 N 個負樣本物品的余弦相似度分別記為
,對這?N+1?個相似度分數做softmax變換,得到用戶對物品的偏好概率;最后損失函數為用戶對正樣本物品的偏好概率的負對數,如下:

更進一步,我們參考了論文《Support Vector Guided Softmax Loss for Face Recognition》的思路,在實現softmax損失函數過程中引入了最大間隔和支持向量的做法,通過在訓練過程中使用“削弱正確”和“放大錯誤”的方式,強迫模型在訓練時挑戰更加困難的任務,使得模型更高魯棒,在預測階段可以輕松做出正確判斷。
基于負采樣的support vector guided softmax loss的tensorflow實現代碼如下:
def softmax_loss_with_negative_mining(user_emb, item_emb, labels, num_negative_samples=4, embed_normed=False, weights=1.0, gamma=1.0, margin=0, t=1): """Compute the softmax loss based on the cosine distance explained below. Given mini batches for `user_emb` and `item_emb`, this function computes for each element in `user_emb` the cosine distance between it and the corresponding `item_emb`, and additionally the cosine distance between `user_emb` and some other elements of `item_emb` (referred to a negative samples). The negative samples are formed on the fly by shifting the right side (`item_emb`). Then the softmax loss will be computed based on these cosine distance. Args: user_emb: A `Tensor` with shape [batch_size, embedding_size]. The embedding of user. item_emb: A `Tensor` with shape [batch_size, embedding_size]. The embedding of item. labels: a `Tensor` with shape [batch_size]. e.g. click or not click in the session. It's values must be 0 or 1. num_negative_samples: the num of negative samples, should be in range [1, batch_size). embed_normed: bool, whether input embeddings l2 normalized weights: `weights` acts as a coefficient for the loss. If a scalar is provided, then the loss is simply scaled by the given value. If `weights` is a tensor of shape `[batch_size]`, then the loss weights apply to each corresponding sample. gamma: smooth coefficient of softmax margin: the margin between positive pair and negative pair t: coefficient of support vector guided softmax loss Return: support vector guided softmax loss of positive labels """ batch_size = get_shape_list(item_emb)[0] assert 0 < num_negative_samples < batch_size, '`num_negative_samples` should be in range [1, batch_size)' if not embed_normed: user_emb = tf.nn.l2_normalize(user_emb, axis=-1) item_emb = tf.nn.l2_normalize(item_emb, axis=-1) vectors = [item_emb] for i in range(num_negative_samples): shift = tf.random_uniform([], 1, batch_size, dtype=tf.int32) neg_item_emb = tf.roll(item_emb, shift, axis=0) vectors.append(neg_item_emb) # all_embeddings's shape: (batch_size, num_negative_samples + 1, vec_dim) all_embeddings = tf.stack(vectors, axis=1) mask = tf.greater(labels, 0) mask_user_emb = tf.boolean_mask(user_emb, mask) mask_item_emb = tf.boolean_mask(all_embeddings, mask) if isinstance(weights, tf.Tensor): weights = tf.boolean_mask(weights, mask) # sim_scores's shape: (num_of_pos_label_in_batch_size, num_negative_samples + 1) sim_scores = tf.keras.backend.batch_dot( mask_user_emb, mask_item_emb, axes=(1, 2)) pos_score = tf.slice(sim_scores, [0, 0], [-1, 1]) neg_scores = tf.slice(sim_scores, [0, 1], [-1, -1]) loss = support_vector_guided_softmax_loss( pos_score, neg_scores, margin=margin, t=t, smooth=gamma, weights=weights) return lossdef support_vector_guided_softmax_loss(pos_score, neg_scores, margin=0, t=1, smooth=1.0, threshold=0, weights=1.0): """Refer paper: Support Vector Guided Softmax Loss for Face Recognition (https://128.84.21.199/abs/1812.11317).""" new_pos_score = pos_score - margin cond = tf.greater_equal(new_pos_score - neg_scores, threshold) mask = tf.where(cond, tf.zeros_like(cond, tf.float32), tf.ones_like(cond, tf.float32)) # I_k new_neg_scores = mask * (neg_scores * t + t - 1) + (1 - mask) * neg_scores logits = tf.concat([new_pos_score, new_neg_scores], axis=1) if 1.0 != smooth: logits *= smooth loss = tf.losses.sparse_softmax_cross_entropy( tf.zeros_like(pos_score, dtype=tf.int32), logits, weights=weights) # set rank loss to zero if a batch has no positive sample. loss = tf.where(tf.is_nan(loss), tf.zeros_like(loss), loss) return loss源代碼:github.com/alibaba/Easy
05Pairwise RankingPointwise, pairwise和listwise是LTR(Learning to Rank)領域為人熟知的三種優化目標,早在深度學習時代之前,做IR的研究者就已經發展了一系列基本方法,比較經典的工作可以參考《Learning to Rank Using Gradient Descent》和《Learning to Rank- From Pairwise Approach to Listwise Approach》這兩篇。
Pairwise的重要意義在于讓模型的訓練目標和模型實際的任務之間盡量統一。對于一個排序任務,真實的目標是讓正樣本的預估分數比負樣本的高,對應了AUC這樣的指標。在pairwise的經典論文RankNet中,pairwise的優化目標被寫成了,


這里
代表模型預估樣本?i?比?j?更“相關”的概率,其中
是兩條樣本模型pointwise輸出logit的差值。直觀上理解,優化
就是在提高模型對于任意正樣本分數比任意負樣本分數高的概率,也即AUC, 所以這種形式的pairwise loss也被稱為AUC loss。
同樣,為了方便實現,以及減少離線構建pair樣本的工作量,我們選擇了 In-batch Random Pairing的方式在訓練過程中,從 mini batch 內構建pair來計算 pairwise rank loss。具體實現代碼如下:
def pairwise_loss(labels, logits): pairwise_logits = tf.expand_dims(logits, -1) - tf.expand_dims(logits, 0) logging.info('[pairwise_loss] pairwise logits: {}'.format(pairwise_logits)) pairwise_mask = tf.greater( tf.expand_dims(labels, -1) - tf.expand_dims(labels, 0), 0) logging.info('[pairwise_loss] mask: {}'.format(pairwise_mask)) pairwise_logits = tf.boolean_mask(pairwise_logits, pairwise_mask) logging.info('[pairwise_loss] after masking: {}'.format(pairwise_logits)) pairwise_pseudo_labels = tf.ones_like(pairwise_logits) loss = tf.losses.sigmoid_cross_entropy(pairwise_pseudo_labels, pairwise_logits) # set rank loss to zero if a batch has no positive sample. loss = tf.where(tf.is_nan(loss), tf.zeros_like(loss), loss) return los源代碼:github.com/alibaba/Easy
06模型實現開源代碼我們在阿里云機器學習PAI團隊開源的推薦算法框架?EasyRec?中發布了DropoutNet的源代碼。使用文檔請查看:easyrec.readthedocs.io/。
EasyRec是一個易于使用的推薦算法模型訓練框架,它內置了很多最先進的推薦算法模型,包括適用于推薦系統召回、排序和冷啟動階段的各種算法。可以跑在本地、DLC、MaxCompute、DataScience等多個平臺上,支持從各種存儲媒介(local、hdfs、maxcompute table、oss、kafka)中加載各種格式類型(text、csv、table、tfrecord)的訓練和評估數據。EasyRec支持多種類型的特征,損失函數,優化器及評估指標,支持大規模并行訓練。使用EasyRec,只需要配置config文件,通過命令調用的方式就可以實現訓練、評估、導出、推理等功能,無需進行代碼開發,幫您快速搭建推廣搜算法。
歡迎加入【EasyRec推薦算法交流群】,釘釘群號 : 32260796。
EasyRec Github代碼倉庫:
github.com/alibaba/EasyRec
07參考資料DropoutNet 論文
Support Vector Guided Softmax Loss for Face Recognition
Embedding-based Retrieval in Facebook Search
Learning to Rank Using Gradient Descent
Learning to Rank- From Pairwise Approach to Listwise Approach
EasyRec DropoutNet 模型使用指南
上一篇:數字鄉村治理平臺規劃方案...