广告ctr预估场景下的dnn调优实战

特征

  • DNN需要组合特征
    LR模型的时候,我们需要构造许多组合特征,比如UserID与ItemID的组合,许多做DNN的都宣称简化了特征工程,由隐层学习特征交叉,但是隐层进行特征组合的方式并没有明确的理论解释,并且通过隐层参数学习的方式进行隐式的特征组合并不能保证收敛到最优解,通过显示的构造组合特征能给DNN提供一些先验信息,从实战来看,DNN加上显示的组合特征效果会好很多。

  • 稀疏特征过滤
    训练数据中出现频次过少的离散特征往往容易引起过拟合,需要统计频次并做过滤。效果比较好的一种方法是,比如生成day这一天的特征时,使用 [day-delta_day_num, day-1]之间的特征统计值来过滤day这一天的稀疏特征,相比使用[day-delta_day_num, day]之间的统计值,auc明显提升。delta_day_num可以设成14天,过滤阈值20,具体数据可以根据业务场景和实验效果来定。

  • DNN特征划分Group
    和FFM类似,需要把特征划分成多个Group,每个Group里的特征做Embedding后Sum起来。划分方法,可以根据特征语义层面(用户、Item、组合)和数量、粒度进行划分。比如把用户相关特征划分成下面几个Group:

    用户细粒度Id: UserId用户画像:Age,Gender,收入,职业用户行为:近期浏览的ItemIds

模型

  • 每类特征设置不同Embedding Size
    特征包含的信息越丰富,越需要更大的Embedding Size来描述,特征包含信息的丰富程度可以通过特征的粒度和数量表现出来,具体的一个划分方式可以看下图。
    特征粒度方面,单特征相对算粗粒度,组合特征相对较细,组合的层次越深,粒度越细。比如UserId#ItemId的组合特征刻画了用户对商品的倾向,UserId#ItemId#Hour刻画了用户在某个时间对某个商品的倾向【比如外卖,举个例子,实际一般不会这么组合】。特征刻画的粒度越细,说明指代的越具体,包含的信息非常明确却单一,这类特征一般不需要再和其他特征进行组合,所以Embedding Size会更小。
    特征数量方面,一般数量越大,包含的信息越多,比如UserIdItemId可以达到上亿,一个UserId可以描述这个用户的很多信息,像AgeGender这类特征规模很小的特征所包含的信息相对有限。

  • 模型结构
    Wide&Deep最靠谱,DeepFM、DCN之类的效果都不大行。在Wide&Deep模型的基础上,没有将Wide部分单独拿出来,而是和Deep在一起,通过特征划分Group后一起Embedding,效果可以超过Wide&Deep,并且右边添加了一个基于离散特征统计的历史CTR的网络,可以降低模型的Variance,效果提升非常明显。总之,不要过分迷信一些灌水论文。

  • 减少多余训练参数
    二分类模型下,最后一层全连接的代码经常是下面这样:

    layer = ... # 倒数第二层weight = tf.get_variable('weight', [dim, 2], initializer=...)bias = tf.get_variable('bias', [2], initializer=...)logits = tf.matmul(layer, weight) + bias

    因为是二分类,连接label=0的所有边其实不需要学习,0就是最优参数,如果加入学习的话,实际上多了一些冗余参数,而且梯度下降必定无法保障它们收敛到这个最优解。改成下面这样,auc可以较大提升。

    layer = ... # 倒数第二层weight_positive = tf.get_variable('weight_positive', [dim, 1], initializer=...)bias_positive = tf.get_variable('bias_positive', [1], initializer=...)weight_neg = tf.get_variable('weight_negative', initializer=tf.constant(np.zeros((dim, 1), dtype=np.float32)), trainable=False)logits_positive = tf.matmul(layer, weight_positive) + bias_positivelogits_negative = tf.matmul(layer, weight_negative)logits = tf.concat([logits_negative, logits_positive], 1)
  • 选一个好的基线模型
    这些年DNN火起来后,大家都往DNN方向发展,很多团队宣称切换到了DNN,宣传文章写的也不错,但是从实际来看,真正把DNN用好的团队并不多。比如从GBDT切换到DNN的一些组,其实整个特征流程还是沿用的GBDT思路,用几百维连续特征来做DNN,或者简单加几个小规模离散特征,少数技术强悍的团队其实做的是百亿千亿特征、模型规模TGB级别的超大DNN,所以同样是Wide&Deep模型,不同的规模下其实天壤之别。DNN相对于GBDT来说,是非常容易做出成果的,主要还是把GBDT作为基线模型有点太简单了,其实在搜索推荐这类个性化很强的场景下把GBDT换成百亿千亿级别特征的超大规模LR或者FFM也会获得很大提升,所以如果要做DNN的话,推荐用FFM来做基线,实战来看DNN相对FFM要做出成果并没有那么简单。

性能

  • 大规模离散特征Embedding
    稀疏特征的id做embedding时,由于TensorFlow内部使用一个shape=[id_num, embedding_size]的Variable做参数,需要把id映射成[0, id_num)间的一个数字。如果id量非常小的话,可以在特征提取后把id排序一遍生成从0开始的连续id值,但在工业界场景下id往往是用murmur hash生成的uint64 id,量级往往是百万到千亿级别,很难做排序。TensorFlow内部有一个Hash Table可以将uint64映射成从0开始的连续id,但可能将不同的id映射到embedding_variable的同一行,所以建议把embedding_variable的行数和num_oov_buckets设置的大一点,减小一点冲突。当然,最优方案应该是使用Map结构来实现Embedding Variable,现在官方并没有人做,我已经修改TensorFlow底层代码实现了一个,支持千亿离散特征的embedding,在公司内已经应用,这一块也可以参考阿里发布的TensorFlowRS。

    embedding_variable = tf.get_variable('emb_var',                                     [2*id_num+2, embedding_size],                                     initializer=...)hash_table = tf.contrib.lookup.index_table_from_tensor(mapping=tf.constant([0]),                                                       num_oov_buckets=2*id_num,                                                       dtype=tf.int64)sparse_ids = hash_table.lookup(origin_sparse_ids)embedding = tf.nn.embedding_lookup_sparse(embedding_variable,                                          sparse_ids,                                          None,                                          partition_strategy="mod")
  • Sparse Embedding性能
    使用

    def embedding_lookup_sparse_with_distributed_aggregation(params,                                                         sp_ids,                                                         sp_weights,                                                         partition_strategy="mod",                                                         name=None,                                                         combiner=None,                                                         max_norm=None)

    代替

    def embedding_lookup_sparse(params,                            sp_ids,                            sp_weights,                            partition_strategy="mod",                            name=None,                            combiner=None,                            max_norm=None)

    。后者在ps端lookup出许多embedding后传给worker,在worker端做聚合,前者在ps端做多个embedding的聚合后传给worker,通信量会小很多。

  • 不要使用TensorFlow Feature Columns
    TensorFlow Feature Columns的性能很差,建议把特征相关的所有工作,包括离散化、组合等操作都放在单独的特征抽取工具里面,TensorFlow只包含模型部分代码。

  • QueueRunner批量读数据
    使用read_up_to接口批量读数据,性能提升非常大。

    reader = tf.TFRecordReader()_, serialized_example = reader.read(filename_queue)_, serialized_example = reader.read_up_to(filename_queue, 1000)
  • 使用DataSet接口读数据
    QueueRunner读数据时不能精确一轮一轮的读,很难做worker之间的barrier,TensorFlow DataSet可以实现精确读取一轮,在worker精确同步时比较有用TensorFlow实现Barrier。而且DataSet的性能和QueueRunner差不多,主要是几个接口的使用顺序要注意。

    def _parse_function(examples_proto):    features = {}    features['label'] = tf.FixedLenFeature([], tf.float32)    features['feature'] = ...    instance = tf.parse_example(examples_proto, features)    label = instance['label']    feature = instance['feature']    return label, feature
    
    dataset = tf.data.TFRecordDataset(file_name_list)dataset = dataset.prefetch(buffer_size=batch_size*100)dataset = dataset.shuffle(buffer_size=batch_size*10)dataset = dataset.batch(batch_size)dataset = dataset.map(_parse_function, num_parallel_calls=4)iterator = dataset.make_initializable_iterator()
  • GPU vs CPU
    现在很多做算法的言必GPU,其实很多场景下并不合适。CTR模型训练场景下,主要耗时操作是Embedding Lookup,不适合GPU,全连接层又很小,CPU足够应付。整体来看,P40Intel® Xeon® Processor E5-2650 v4 (30M Cache, 2.20 GHz)快5%左右,但价格贵很多。2.7GHz的CPU性能可以提升30%,所以从性价比来看,推荐主频更快的CPU。这个一定要分应用场景,在场景下去做正确的决定,而不是人云亦云。

其他

  • 训练千亿特征TGB级别参数的超大模型
  • 将单机无法加载的超大模型做线上预测服务
  • 秒级在线深度学习架构

参考

(0)

相关推荐

  • 深度学习尝鲜-DeepFM模型原理

    一.背景 精排模型是推荐系统中效果产出最重要的模块,深度排序模型是推荐领域应用最广迭代最快的技术领域.近年来各大公司纷纷抛弃了原有的传统机器学习模型转向深度排序模型的研究和应用,提出了非常多结合工业应 ...

  • FM: 推荐算法中的瑞士军刀

    前言 自从我上次在知乎回答了问题<机器学习中较为简单的算法有哪些?>,很多同学私信我询问我FM算法在推荐系统中的应用细节,索性今天就专门写一篇文章,仔细聊一聊FM这把"推荐算法中 ...

  • 【NLP】详聊NLP中的阅读理解(MRC)

    机器阅读理解,笔者认为他是NLP中最有意思的任务了.机器阅读一份文档之后,可以"理解"其中的内容,并可以回答关于这份文档的问题.听上去,有一种很强的"人工智能" ...

  • 久别重逢话双塔

    回归前言 知乎最近应该搞了一个"返航计划",以唤醒流失作者,算是给我的回归增加了一个契机. 促使我下定决心回归的最后一根稻草是,唉,最近比较烦.比较烦.比较烦.人家梁朝伟心烦的时候 ...

  • 谈谈工业界落地能力最强的机器学习算法

    尽管BERT为代表的预训练模型大肆流行,但是身处工业界才会知道它落地有多难,尤其是QPS动辄几百的在线推荐.搜索系统,哪怕在大厂也很难在线上系统见到它们. 今天就想反其道而行之,谈谈工业界搜索.推荐. ...

  • TF之pix2pix之dataset:基于TF利用自己的数据集训练pix2pix模型之DIY自己的数据集

    TF之pix2pix之dataset:基于TF利用自己的数据集训练pix2pix模型之DIY自己的数据集 转换图像并合并 1.A 类图像将挖去中心像素后得到B 类图像 2.生成并列图像样本的全过程

  • CTR学习笔记&代码实现2-深度ctr模型 MLP->Wide&Deep

    背景 这一篇我们从基础的深度ctr模型谈起.我很喜欢Wide&Deep的框架感觉之后很多改进都可以纳入这个框架中.Wide负责样本中出现的频繁项挖掘,Deep负责样本中未出现的特征泛化.而后续 ...

  • 【NLP实战系列】Tensorflow命名实体识别实战

    实战是学习一门技术最好的方式,也是深入了解一门技术唯一的方式.因此,NLP专栏计划推出一个实战专栏,让有兴趣的同学在看文章之余也可以自己动手试一试. 本篇介绍自然语言处理中一种非常重要的任务:命名实体 ...

  • 深入理解YouTube推荐系统算法

    去年天池-安泰杯跨境电商智能算法大赛是我初次接触推荐相关的比赛,通过比赛让我对推荐系统有了较为浅显的认识,赛后也是打算系统的学习这方面的内容,此后我也会将[推荐系统]作为一个系列板块进行更新,主打经典 ...

  • Youtube推荐中的深度神经网络应用

    Overall 从上述链接中可以看到,之前读的文章都是最近两年的.今天则给大家介绍一篇稍微久远点的,2016年的论文,追本溯源,或许能更好的理解推荐算法的变化和设计的初衷. 论文[1]中的Youtub ...

  • 万变不离其宗:用统一框架理解向量化召回

    前言 常读我的文章的同学会注意到,我一直强调.推崇,不要孤立地学习算法,而是要梳理算法的脉络+框架,唯有如此,才能真正融会贯通,变纸面上的算法为你的算法,而不是狗熊掰棒子,被层出不穷的新文章.新算法搞 ...

  • 推荐粗排(召回)工程实践之双塔DNN模型

    粗排作用 在 推荐精排模型之经典排序模型 一文中我们介绍了工业推荐系统中主要包括召回.粗排.精排.重排这四个环节.粗排主要是为了进一步减少召回的Item数目,减轻精排压力,同时不损失线上效果.如果不要 ...

  • 【NLP实战】如何基于Tensorflow搭建一个聊天机器人

    实战是学习一门技术最好的方式,也是深入了解一门技术唯一的方式.因此,NLP专栏计划推出一个实战专栏,让有兴趣的同学在看文章之余也可以自动动手试一试. 本篇介绍如何基于tensorflow快速搭建一个基 ...