多任务学习模型之ESMM介绍与实现
簡介:本文介紹的是阿里巴巴團隊發表在 SIGIR’2018 的論文《Entire Space Multi-Task Model: An E?ective Approach for Estimating Post-Click Conversion Rate》。文章基于 Multi-Task Learning (MTL) 的思路,提出一種名為ESMM的CVR預估模型,有效解決了真實場景中CVR預估面臨的數據稀疏以及樣本選擇偏差這兩個關鍵問題。后續還會陸續介紹MMoE,PLE,DBMTL等多任務學習模型。
多任務學習背景
目前工業中使用的推薦算法已不只局限在單目標(ctr)任務上,還需要關注后續的轉換鏈路,如是否評論、收藏、加購、購買、觀看時長等目標。
本文介紹的是阿里巴巴團隊發表在 SIGIR’2018 的論文《Entire Space Multi-Task Model: An E?ective Approach for Estimating Post-Click Conversion Rate》。文章基于 Multi-Task Learning (MTL) 的思路,提出一種名為ESMM的CVR預估模型,有效解決了真實場景中CVR預估面臨的數據稀疏以及樣本選擇偏差這兩個關鍵問題。后續還會陸續介紹MMoE,PLE,DBMTL等多任務學習模型。
論文介紹
CVR預估面臨兩個關鍵問題:
1. Sample Selection Bias (SSB)
轉化是在點擊之后才“有可能”發生的動作,傳統CVR模型通常以點擊數據為訓練集,其中點擊未轉化為負例,點擊并轉化為正例。但是訓練好的模型實際使用時,則是對整個空間的樣本進行預估,而非只對點擊樣本進行預估。即訓練數據與實際要預測的數據來自不同分布,這個偏差對模型的泛化能力構成了很大挑戰,導致模型上線后,線上業務效果往往一般。
2. Data Sparsity (DS)
CVR預估任務的使用的訓練數據(即點擊樣本)遠小于CTR預估訓練使用的曝光樣本。僅使用數量較小的樣本進行訓練,會導致深度模型擬合困難。
一些策略可以緩解這兩個問題,例如從曝光集中對unclicked樣本抽樣做負例緩解SSB,對轉化樣本過采樣緩解DS等。但無論哪種方法,都沒有從實質上解決上面任一個問題。
由于點擊=>轉化,本身是兩個強相關的連續行為,作者希望在模型結構中顯示考慮這種“行為鏈關系”,從而可以在整個空間上進行訓練及預測。這涉及到CTR與CVR兩個任務,因此使用多任務學習(MTL)是一個自然的選擇,論文的關鍵亮點正在于“如何搭建”這個MTL。
首先需要重點區分下,CVR預估任務與CTCVR預估任務。
- CVR = 轉化數/點擊數。是預測“假設item被點擊,那么它被轉化”的概率。CVR預估任務,與CTR沒有絕對的關系。一個item的ctr高,cvr不一定同樣會高,如標題黨文章的瀏覽時長往往較低。這也是不能直接使用全部樣本訓練CVR模型的原因,因為無法確定那些曝光未點擊的樣本,假設他們被點擊了,是否會被轉化。如果直接使用0作為它們的label,會很大程度上誤導CVR模型的學習。
- CTCVR = 轉換數/曝光數。是預測“item被點擊,然后被轉化”的概率。
其中x,y,z分別表示曝光,點擊,轉換。注意到,在全部樣本空間中,CTR對應的label為click,而CTCVR對應的label為click & conversion,這兩個任務是可以使用全部樣本的。因此,ESMM通過學習CTR,CTCVR兩個任務,再根據上式隱式地學習CVR任務。具體結構如下:
網絡結構上有兩點值得強調:
具體地,反映在目標函數中:
代碼實現
基于EasyRec推薦算法框架,我們實現了ESMM算法,具體實現可移步至github:EasyRec-ESMM。
EasyRec介紹:EasyRec是阿里云計算平臺機器學習PAI團隊開源的大規模分布式推薦算法框架,EasyRec 正如其名字一樣,簡單易用,集成了諸多優秀前沿的推薦系統論文思想,并且有在實際工業落地中取得優良效果的特征工程方法,集成訓練、評估、部署,與阿里云產品無縫銜接,可以借助 EasyRec 在短時間內搭建起一套前沿的推薦系統。作為阿里云的拳頭產品,現已穩定服務于數百個企業客戶。
模型前饋網絡:
def build_predict_graph(self):"""Forward function.Returns:self._prediction_dict: Prediction result of two tasks."""# 此處從Concatenate后的tensor(all_fea)開始,省略其生成邏輯cvr_tower_name = self._cvr_tower_cfg.tower_namednn_model = dnn.DNN(self._cvr_tower_cfg.dnn,self._l2_reg,name=cvr_tower_name,is_training=self._is_training)cvr_tower_output = dnn_model(all_fea)cvr_tower_output = tf.layers.dense(inputs=cvr_tower_output,units=1,kernel_regularizer=self._l2_reg,name='%s/dnn_output' % cvr_tower_name)ctr_tower_name = self._ctr_tower_cfg.tower_namednn_model = dnn.DNN(self._ctr_tower_cfg.dnn,self._l2_reg,name=ctr_tower_name,is_training=self._is_training)ctr_tower_output = dnn_model(all_fea)ctr_tower_output = tf.layers.dense(inputs=ctr_tower_output,units=1,kernel_regularizer=self._l2_reg,name='%s/dnn_output' % ctr_tower_name)tower_outputs = {cvr_tower_name: cvr_tower_output,ctr_tower_name: ctr_tower_output}self._add_to_prediction_dict(tower_outputs)return self._prediction_dictloss計算:
注意:計算CVR的指標時需要mask掉曝光數據。
def build_loss_graph(self):"""Build loss graph.Returns:self._loss_dict: Weighted loss of ctr and cvr."""cvr_tower_name = self._cvr_tower_cfg.tower_namectr_tower_name = self._ctr_tower_cfg.tower_namecvr_label_name = self._label_name_dict[cvr_tower_name]ctr_label_name = self._label_name_dict[ctr_tower_name]ctcvr_label = tf.cast(self._labels[cvr_label_name] * self._labels[ctr_label_name], tf.float32)cvr_loss = tf.keras.backend.binary_crossentropy(ctcvr_label, self._prediction_dict['probs_ctcvr'])cvr_loss = tf.reduce_sum(cvr_losses, name="ctcvr_loss")# The weight defaults to 1.self._loss_dict['weighted_cross_entropy_loss_%s' %cvr_tower_name] = self._cvr_tower_cfg.weight * cvr_lossctr_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.cast(self._labels[ctr_label_name], tf.float32),logits=self._prediction_dict['logits_%s' % ctr_tower_name]), name="ctr_loss")self._loss_dict['weighted_cross_entropy_loss_%s' %ctr_tower_name] = self._ctr_tower_cfg.weight * ctr_lossreturn self._loss_dictnote: 這里loss是 weighted_cross_entropy_loss_ctr + weighted_cross_entropy_loss_cvr, EasyRec框架會自動對self._loss_dict中的內容進行加和。
metric計算:
注意:計算CVR的指標時需要mask掉曝光數據。
def build_metric_graph(self, eval_config):"""Build metric graph.Args:eval_config: Evaluation configuration.Returns:metric_dict: Calculate AUC of ctr, cvr and ctrvr."""metric_dict = {}cvr_tower_name = self._cvr_tower_cfg.tower_namectr_tower_name = self._ctr_tower_cfg.tower_namecvr_label_name = self._label_name_dict[cvr_tower_name]ctr_label_name = self._label_name_dict[ctr_tower_name]for metric in self._cvr_tower_cfg.metrics_set:# CTCVR metricctcvr_label_name = cvr_label_name + '_ctcvr'cvr_dtype = self._labels[cvr_label_name].dtypeself._labels[ctcvr_label_name] = self._labels[cvr_label_name] * tf.cast(self._labels[ctr_label_name], cvr_dtype)metric_dict.update(self._build_metric_impl(metric,loss_type=self._cvr_tower_cfg.loss_type,label_name=ctcvr_label_name,num_class=self._cvr_tower_cfg.num_class,suffix='_ctcvr'))# CVR metriccvr_label_masked_name = cvr_label_name + '_masked'ctr_mask = self._labels[ctr_label_name] > 0self._labels[cvr_label_masked_name] = tf.boolean_mask(self._labels[cvr_label_name], ctr_mask)pred_prefix = 'probs' if self._cvr_tower_cfg.loss_type == LossType.CLASSIFICATION else 'y'pred_name = '%s_%s' % (pred_prefix, cvr_tower_name)self._prediction_dict[pred_name + '_masked'] = tf.boolean_mask(self._prediction_dict[pred_name], ctr_mask)metric_dict.update(self._build_metric_impl(metric,loss_type=self._cvr_tower_cfg.loss_type,label_name=cvr_label_masked_name,num_class=self._cvr_tower_cfg.num_class,suffix='_%s_masked' % cvr_tower_name))for metric in self._ctr_tower_cfg.metrics_set:# CTR metricmetric_dict.update(self._build_metric_impl(metric,loss_type=self._ctr_tower_cfg.loss_type,label_name=ctr_label_name,num_class=self._ctr_tower_cfg.num_class,suffix='_%s' % ctr_tower_name))return metric_dict實驗及不足
我們基于開源AliCCP數據,進行了大量實驗,實驗部分請期待下一篇文章。實驗發現,ESMM的蹺蹺板現象較為明顯,CTR與CVR任務的效果較難同時提升。
參考文獻
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的多任务学习模型之ESMM介绍与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Quick BI V4.0功能“炸弹”来
- 下一篇: Chipscope信号无法找到的解决方案