关于人工智能:多任务学习模型之ESMM介绍与实现

41次阅读

共计 5903 个字符,预计需要花费 15 分钟才能阅读完成。

简介:本文介绍的是阿里巴巴团队发表在 SIGIR’2018 的论文《Entire Space Multi-Task Model: An Effective 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 Effective 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 工作。具体构造如下:

网络结构上有两点值得强调:

共享 Embedding。CVR-task 和 CTR-task 应用雷同的特色和特色 embedding,即两者从 Concatenate 之后才学习各自独享的参数;

隐式学习 pCVR。这里 pCVR 仅是网络中的一个 variable,没有显示的监督信号。
具体地,反映在指标函数中:

代码实现

基于 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_name
   dnn_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_name
   dnn_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_dict

loss 计算:

留神:计算 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_name
   ctr_tower_name = self._ctr_tower_cfg.tower_name
   cvr_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_loss

   ctr_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_loss
   return self._loss_dict

note: 这里 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_name
  ctr_tower_name = self._ctr_tower_cfg.tower_name
  cvr_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 metric
    ctcvr_label_name = cvr_label_name + '_ctcvr'
    cvr_dtype = self._labels[cvr_label_name].dtype
    self._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 metric
    cvr_label_masked_name = cvr_label_name + '_masked'
    ctr_mask = self._labels[ctr_label_name] > 0
    self._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 metric
    metric_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 工作的成果较难同时晋升。

参考文献

Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conversion Rate
阿里 CVR 预估模型之 ESMM
EasyRec-ESMM 应用介绍多任务学习模型之 ESMM 介绍与实现
注:本文图片及公示均援用自论文:Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conversion Rate。

原文链接
本文为阿里云原创内容,未经容许不得转载。

正文完
 0