头条项目推荐的相关技术(十):排序模型进阶与总结
1. 写在前面
这里是有关于一个头条推荐项目的学习笔记,主要是整理工业上的推荐系统用到的一些常用技术, 这是第八篇, 上一篇文章介绍了一些深度学习和TensorFlow框架相关的知识, 有了这些知识,我们就可以基于TensorFlow框架搭建深度学习模型去完成之前的推荐以及预测任务了,所以这篇文章主要是整理这个搭建过程,这个东西我也是刚学到,其实之前最想学的就是这块内容,也就是这种深度学习模型是如何搭建在分布式的环境中,以及如何用Tensorflow Serving进行模型的部署任务, 而这,就是这篇文章主要整理的内容。主要如下:
- TRRecords与黑马训练数据存储
- 深度学习与排序模型发展(排序模型发展,类别型特征对推荐系统的重要性,分桶与特征交叉)
- 黑马排序模型进阶 - FTRL模型
- 黑马排序模型进阶 - Wide&Deep模型
- WDL模型导出与TensorFlow Serving模型部署
- 排序模型在线测试
- 全面总结下
这篇内容全是想学的新东西,这也是最后一篇文章了, 坚持下哈哈。
Ok let’s go!
2. TRRecords与黑马训练数据存储
之前在整理排序那篇文章的时候,我们知道了如果是想用TensorFlow进行模型训练的话,是需要把训练样本准备成TFrecords文件的, 那么这种应该怎么去准备呢?
整体训练流程(线下训练):
- Spark 原始数据整合 -> Spark/TF生成TFRecord -> TF数据并行训练 -> Tensorflow Serving线下评估 -> CPU线上预测。
离线spark构造好的样本数据 ----> tfrecords文件
- tfrecords不断增加数量
- 半年之后,产生很多新的点击行为, 又要构造样本 --> tfrecords2
训练样本会越来越多,不断的增加tfrecords文件。
2.1 什么是TFRecords文件
TFRecords其实是一种二进制文件,虽然它不如其他格式好理解,但是它能更好的利用内存,更方便复制和移动,并且不需要单独的标签文件。
TFRecords文件包含了tf.train.Example
协议内存块(protocol buffer)(协议内存块包含了字段 Features
)。可以获取你的数据, 将数据填入到Example
协议内存块(protocol buffer),将协议内存块序列化为一个字符串, 并且通过tf.python_io.TFRecordWriter
写入到TFRecords文件。
文件格式 *.tfrecords
2.2 Example结构解析
tf.train.Example
协议内存块(protocol buffer)(协议内存块包含了字段 Features
),Features
包含了一个Feature
字段,Features
中包含要写入的数据、并指明数据类型。这是一个样本的结构,批数据需要循环存入这样的结构
example = tf.train.Example(features=tf.train.Features(feature={"features": tf.train.Feature(bytes_list=tf.train.BytesList(value=[features])),"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),}))
存储的时候按照上面的这种格式进行封装,也就是把特征和label封装到一个字典中去。
- tf.train.Example(features=None)
- 写入tfrecords文件
- features:tf.train.Features类型的特征实例
- return:example格式协议块
- tf.train.Features(feature=None)
- 构建每个样本的信息键值对
- feature:字典数据,key为要保存的名字
- value为tf.train.Feature实例
- return:Features类型
- tf.train.Feature(options)
- options:例如
- bytes_list=tf.train. BytesList(value=[Bytes])
- int64_list=tf.train. Int64List(value=[Value])
- 支持存入的类型如下
- tf.train.Int64List(value=[Value]): 比如特征是Int类别的,可以用这种形式去保存
- tf.train.BytesList(value=[Bytes]):如果是一个数组,可以把这个数组变成bytes形式存储
- tf.train.FloatList(value=[value]): 如果特征是float类型的,可以用这种形式去保存
- options:例如
这种结构是不是很好的解决了数据和标签(训练的类别标签)或者其他属性数据存储在同一个文件中呢。 下面看使用步骤,基本上是固定死的。
生成TR Record Writer
writer = tf.python_io.TFRecordWriter(path, options=None)
path: TFRecord文件的存放路径
option: TFRecordOptions对象, 定义TFRecord文件保存的压缩格式tf.train.Feature生成协议信息
一个协议信息特征是将原始数据编码成特定的格式,内层feature是一个字典值,它是将某个特征类型列表编码成特定的feature格式,而该字典键用于读取TFRecords文件时索引得到不同的数据。某个类型列表可能包含零值或多个值。列表类型一般有BytesList, FloatList, Int64List.tf.train.ByteList(value=[value]) # value转化为字符串(二进制)列表 tf.train.FloatList(value=[value]) # value转化为浮点数列表 tf.train.Int64List(value=[value]) # value转化为整型列表
其中, value是你要保存的数据。 内层feature编码方式:
feature_internal = {"width": tf.train.Feature(int64_list=tf.train.Int64List(value=[width])),"weights": tf.train.Feature(int64_list=tf.train.FloatList(value=[weights])),"image_raw": tf.train.Feature(int64_list=tf.train.BytesList(value=[image_raw])), }
外层features再将内存字典编码:
features_extren = tf.train.Features(feature_internel)
tf.train.Feature
这个接口可以编码封装列表类型和字典类型,内层使用的是tf.train.Feature
, 外层使用的是tf.train.Features
tf.train.Example将features编码数据封装成特定的PB协议格式
example = tf.train.Example(features_extren)
将example数据系列化为字符串
example_str = example.SerializeToString()
将系列化为字符串的example数据写入协议缓冲区
writer.write(example_str)
从第二步到第五步,循环对于每一个样本进行存储。
2.3 案例: 推荐数据存入TFRecords文件
这里就看一个真实数据保存成TFRecords的例子去看看怎么玩这个东西。数据处理部分,下面这个是之前电商推荐项目里面的一个数据:
一开始数据是左边6列,左边的affairs是label,后面5列是特征。 当时是通过VectorAssembler()函数将后面的5列拼接成一个向量,也就是features特征,这个才能用LR模型去进行预测。 而这里要做的就是看看如何把这个数据写成tfrecords文件。
首先构造存储实例, tf.python_io.TFRecordWriter(path)
- 写入tfrecords文件
- path: TFRecords文件的路径
- return: 写文件
- method
- write(record): 向文件中写入一个example
- close(): 关闭文件写入器
存储的代码如下:
import tensorflow as tf
def writhe_to_tfrecords(click_batch, feature_batch):"""将数据存进tfrecords, 方便管理每个样本的属性:param clict_batch: 目标值:param feature_batch: 特征值:return None"""# 1. 构造tfrecords的存储实例writer = tf.python_io.TFRecordWriter("./train_ctr_201905.tfrecords")# 2. 循环将每个样本写入到文件当中for i in range(len(click_batch)):click = click_batch[i]feature = feature_batch[i].toString() # 这里需要转成字符串# 绑定每个样本的属性example = tf.train.Example(features=tf.train.Features(feature={"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[click])),"feature": tf.train.Feature(bytes_list=tf.train.BytesList(value=[feature])),}))# 文件需要关闭writer.close()return None
上面这就是主要的写入逻辑了。下面这块是固定写法,因为真实写的时候得开启会话,并且开启线程啥的。
# 开启会话打印内容
with tf.Session() as sess:# 创建线程协调器coord = tf.train.Coordinator()# 开启子线程去读取数据# 返回子线程实例threads = tf.train.start_queue_runners(sess=sess, coord=coord)# 存入数据,调用上面的函数 第0列label和最后一列的featurewrite_to_tfrecords(df.iloc[:,0], df.iloc[:, 6])# 关闭子线程, 回收coord.request_stop()coord.join(threads)
这个东西存好了之后,就能使用深度学习模型去进行训练了,和spark就没有啥关系了。所以接下来,先看看深度学习下推荐算法的发展,然后看看如何训练深度学习模型。
3. 深度学习与排序模型发展
3.1 模型发展
CTR/CVR预估经历了从传统机器学习模型到深度学习模型的过渡。下面先简单介绍下传统机器学习模型(GBDT、LR、到深度模型及应用,然后再详细介绍在深度学习模型的迭代。
3.1.1 LR模型
LR可以视作单层单节点的“DNN”, 是一种宽而不深的结构,所有的特征直接作用在最后的输出结果上。模型优点是简单、可控性好,但是效果的好坏直接取决于特征工程的程度,需要非常精细的连续型、离散型、时间型等特征处理及特征组合。通常通过正则化等方式控制过拟合。
3.1.2 FM & FFM
FM可以看做带特征交叉的LR,如下图所示:
模型覆盖了LR的宽模型结构,同时也引入了交叉特征,增加模型的非线性,提升模型容量,能捕捉更多的信息,对于CTR预估等复杂场景有更好的捕捉。
- 特征交叉
- 例子:年龄:[1990,2000],[2000,2010]
- 性别:male, female
- 交叉特征:male and [1990,2000],female and [1990,2000] ,male and [2000,2010], female and [2000, 2010]
特征交叉的问题: 如果是简单的进行组合,会造成特征列数量过多,大量特征列
- 解决方式: 对于交叉特征采用哈希分桶
3.1.3 DNN
实验表明从线性的LR到具备非线性交叉的FM,到具备Field信息交叉的FFM,模型复杂度(模型容量)的提升,带来的都是结果的提升。而LR和FM/FFM可以视作简单的浅层神经网络模型,基于下面一些考虑,我们在把CTR模型切换到深度学习神经网络模型:
- 通过改进模型结构,加入深度结构,利用端到端的结构挖掘高阶非线性特征,以及浅层模型无法捕捉的潜在模式。
- 对于某些ID类特别稀疏的特征,可以在模型中学习到保持分布关系的稠密表达(embedding)。
- 充分利用图片和文本等在简单模型中不好利用的信息。
3.1.4 Wide & Deep
首先尝试的是Google提出的经典模型Wide & Deep Model,模型包含Wide和Deep两个部分(LR+DNN的结合)
- 其中Wide部分可以很好地学习样本中的高频部分,在LR中使用到的特征可以直接在这个部分使用,但对于没有见过的ID类特征,模型学习能力较差,同时合理的人工特征工程对于这个部分的表达有帮助。
- 根据人工经验、业务背景,将我们认为有价值的、显而易见的特征及特征组合,喂入Wide侧。
- Deep部分可以补充学习样本中的长尾部分,同时提高模型的泛化能力。Wide和Deep部分在这个端到端的模型里会联合训练。
- 通过embedding将tag向量化,变tag的精确匹配,为tag向量的模糊查找,使自己具备了良好的“扩展”能力。
为啥在深度学习模型时代,还必须用LR这种模型呢? 为啥不全替换成神经网络呢? 原因之一:
- 推荐系统的数据跟不上(信息量跟不上), 不像图片,文本的这种数据,表达的信息特别丰富,一般推荐数据中,特征稀疏是一块,然后还是一些预处理之后的信息,信息也会有所损失等。所以,如果单靠神经网络的话,往往效果不是那么的好,必须依赖这一些简单的模型去记忆一些重要特征或者重要信息。
3.2 类别特征在推荐系统中的作用 重点知识
深度学习的这一波热潮,发源于CNN在图像识别上所取得的巨大成功,后来才扩展到推荐、搜索等领域。但是实际上,推荐系统中所使用的深度学习与计算机视觉中用到的深度学习有很大不同。其中一个重要不同,就是图像都是稠密特征,而推荐、搜索中大量用到的是稀疏的类别/ID类特征。
优点:
- LR, DNN在底层还是一个线性模型,但是实际工业中,标签y与特征x之间较少存在线性关系,而往往是分段的。以“点击率~ 历史曝光次数”之间的关系为例,之前曝光过1、2次的时候,“点击率~ 历史曝光次数”之间一般是正相关的,再多曝光1、2次,用户由于好奇,没准就点击了;但是,如果已经曝光过8、9次了,由于用户已经失去了新鲜感,越多曝光,用户越不可能再点,这时“点击率~历史曝光次数”就表现出负相关性。因此,categorical特征相比于numeric特征,更加符合现实场景。
- 推荐、搜索一般都是基于用户、商品的标签画像系统,而标签大多数都是categorical的
- 稀疏的类别/ID类特征,可以稀疏地存储、传输、运算,提升运算效率。
缺点:
稀疏的categorical/ID类特征,也有着单个特征表达能力弱、特征组合爆炸、分布不均匀导致受训程度不均匀的缺点。但是对于CRT预估和排序学习的领域, 目前深度学习尚未在自动特征挖掘上对人工特征工程形成的碾压之势,因此人工特征工程依然很重要。为此,一系列的新技术被提出来:
算法上,FTRL这样的算法,充分利用输入的稀疏性在线更新模型,训练出的模型也是稀疏的,便于快速预测。
特征预处理
- 特征归一化: 深度网络的学习几乎都是基于反向传播,而此类梯度优化的方法对于特征的尺度非常敏感。 因此,需要对特征进行归一化或者标准化以促使模型更好的收敛。
- 特征离散化
- 工业界一般很少直接用连续值作为特征,而是将特征离散化后再输入到模型中。一方面是因为离散化特征对于异常值有更好的鲁棒性,其次是可以为特征引入非线性的能力。并且,离散化可以更好的进行Embedding,主要有如下离散化方法:
- 等频分桶: 按样本频率进行等频切分,缺失值可以选择给一个默认桶值或单独设置分桶
- 工业界一般很少直接用连续值作为特征,而是将特征离散化后再输入到模型中。一方面是因为离散化特征对于异常值有更好的鲁棒性,其次是可以为特征引入非线性的能力。并且,离散化可以更好的进行Embedding,主要有如下离散化方法:
- 特征组合:基于业务场景对基础特征进行组合,形成更丰富的行为表征,为模型提供先验信息,可以加速模型的收敛速度。典型示例如下:
- 用户性别与类目之间的交叉特征,能够刻画不同性别的用户在类目上的偏好差异,比如男性用户可能会较少关注“丽人”相关的商户
- 时间与类目之间的交叉特征,能够刻画出不同类目商品在时间上的差异,例如,酒吧在夜间会更容易被点击
- 多考虑Embedding: 通过Embedding去表征用户的个性化偏好和商户的精准画像
增加模型的DNN层级也会增加效果
3.3 分组与特征交叉
3.3.1 通过分桶将连续型特征变成类别型特征 为啥要转成类别性特征?要知道
有时候,连续特征与标签不一定是线性关系。例如,年龄和输入 — 一个人的收入在其职业生涯早期阶段会增长,然后再某一阶段,增长速度缓慢,最后,在退休后减少。在这种情况下,使用原始age
作为实值性特征列也许并非理想之选,因为模型只能学习以下三种情况之一:
- 收入始终随着年龄的增长而以某一速率增长(正相关)
- 收入始终随着年龄的增长而以某一速率减少(负相关)
- 无论年龄多大,收入都保持不变(不想关)
如果我们要分别学习收入与各个年龄段之间的精细关系,则可以采用分桶技巧。分桶是将整个连续特征范围分割为一组连续分桶,然后根据所在的分桶将原数据值转换为分桶ID(作为类别特征)的过程。因此,我们可以针对age
将bucketized_column
定义为:
age_buckets = tf.feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
通过这个例子,其实我突然也明白了为啥特征离散化之后会对异常值有更好的鲁棒性了,就想上面的这个例子,收入和年龄往往会有三个阶段的变化,年轻的时候一般是正比增长,中年可能恒定,而老年可能往下降的趋势。 那我们如果把年龄分成3个桶之后,就更容易学习这种规律。而异常值属于什么样的呢? 类似于马爸爸那种, 到了35之后,才突然财产猛增,这属于中年猛增型,但一般这属于个例情况,不是普遍的规律性,所以通过分桶之后,把所有中年的分成一个桶,那么模型在学习这个参数的时候,这种个例情况模型就可能就不那么受影响,毕竟这个桶大部分的人不是这样子,模型在权衡(分桶之后相当于用其他普通的例子把这种异常个例情况往回拉一下子,使得模型学习参数不会抖动的那么厉害),如果纯数值型的话,模型遇到这种样本,可能会受到很大影响,毕竟求梯度的时候,这种样本可能使模型跳跃式更新参数。所以分桶之后,还会有抗噪声的优势,当然,自己的理解哈。
3.3.2 通过特征组合学习复杂关系(非线性关系)
单独使用各个基准特征可能不足以解释数据。例如,对于不同的职业,受教育程度和标签(收入超过5万美元)之间的相关性可能各不相同。因此,如果我们仅学习education="Bachelors"
和education=Masters
的单个模型权重,则无法捕获每个受教育程度 - 职业和组合(例如,区分education="Bachelors" AND occupation="Exec-managerial"
和 education="Bachelors" AND occupation="Craft-repair"
)
比如,IT行业,如果是本科,或者高中毕业,只要技术好,收入应该也可以, 而像金融,法律这种,如果是本科或者高中学历就不大行了,所以影响收入的,可能是职业+受教育程度
要了解各个特征组合之间的差异, 我们可以向模型中添加组合特征列:
education_x_occupation = tf.feature_column.crossed_column(['education', 'occupation'], hash_bucket_size=1000)
# hash_bucket_size 防止特征类别过多
# 输入的这个交叉列必须是类别的才行
我们还可以针对两个以上的列创建一个crossed_column
。每个组成列可以是类别基准特征列(SparseColumn
), 分桶实值特征列了,也可以是其他CrossColumn
。例如:
agg_buckets_x_education_x_occupation = tf.feature_column.crossed_column([age_buckets, "eduction", "occupation", hash_bucket_size=1000)
分桶也好,特征交叉也好,都是提高非线性关系数据的占比, 上一篇那个美国普查数据分类可以做下分桶交叉特征,即把年龄进行分桶,然后教育和职业交叉一下子,然后年龄,教育,职业交叉一下子,auc会有所提高。
4. 排序模型进阶FTRL 新知识
4.1 问题引出
在实际项目的时候,经常会遇到训练数据非常大导致一些算法实际上不能操作的问题。比如在推荐行业中,因为DSP的请求数据量特别大,一个星期的数据往往有上百G,这种级别的数据在训练的时候,直接套用一些算法框架是没办法训练的,基本上在特征工程的阶段就一筹莫展。通常采用采样、截断的方式获取更小的数据集,或者使用大数据集群的方式进行训练,但是这两种方式在作者看来目前存在下面问题:
- 采样数据或者截断数据的方式,非常的依赖前期的数据分析以及经验。
- 大数据集群的方式,目前spark原生支持的机器学习模型比较少;使用第三方的算法模型的话,需要spark集群的2.3以上;而且spark训练出来的模型往往比较复杂,实际线上运行的时候,对内存以及QPS的压力比较大。
- 模型更新周期慢,不能有效反映线上的变化,最快小时级别,一般是天级别甚至周级别。
- 模型参数少,预测的效果差;模型参数多线上predict的时候需要内存大,QPS无法保证。
采取措施:
- 采用On-line-learning的算法。
- 采用一些优化的方法,在保证精度的前提下,尽量获取稀疏解,从而降低模型参数的数量。
4.2 OnlineLearning优化算法
在工业界,越来越多的业务需要大规模机器学习,不单参与训练的数据量大,模型特征量的规模也大。例如点击率预估,训练数据量在TB量级,特征量在亿这个量级,业内常用LR(Logistic Regression)和FM(Factorization Machines)为点击率预估建模。对LR、FM这类模型的参数学习,传统的学习算法是batch learning算法,它无法有效地处理大规模的数据集,也无法有效地处理大规模的在线数据流。这时,有效且高效的online learning算法显得尤为重要。
SGD算法是常用的online learning算法,它能学习出不错的模型,但学出的模型不是稀疏的。为此,学术界和工业界都在研究这样一种online learning算法,它能学习出有效的且稀疏的模型。
FTRL(Follow the Regularized Leader)算法正是这样一种算法,融合了RDA算法能产生稀疏模型的特性和SGD算法能产生更有效模型的特性。它在处理诸如LR之类的带非光滑正则化项(例如1范数,做模型复杂度控制和稀疏化)的凸优化问题上性能非常出色,国内各大互联网公司都已将该算法应用到实际产品中。
比较出名的在线最优化的方法有:
- TG(Truncated Gradient)
- FOBOS(Forward-Backward Splitting)
- RDA(Regularized Dual Averaging)
- FTRL(Follow the Regularized Leader)
4.3 Follow The Regularized Leader(FTRL)
这是一种获得稀疏模型并且防止过拟合的优化方法。思想其实并不复杂, 就是每次找到让之前所有目标函数(损失函数+正则)之和最小的参数。 该算法在处理如逻辑回归之类的非光滑正则项(L1正则)的凸优化问题上表现出色,在计算精度和特征稀疏性上做到了很好的trade-off, 而且在工程上做了大量优化,性能优异。
W ( t + 1 ) = argmin W { G ( 1 : t ) ⋅ W + λ 1 ∥ W ∥ 1 + λ 2 2 ∥ W ∥ 2 2 + 1 2 ∑ s = 1 t σ ( s ) ∥ W − W ( s ) ∥ 2 2 } W^{(t+1)}=\operatorname{argmin}_{W}\left\{G^{(1: t)} \cdot W+\lambda_{1}\|W\|_{1}+\frac{\lambda_{2}}{2}\|W\|_{2}^{2}+\frac{1}{2} \sum_{s=1}^{t} \sigma^{(s)}\left\|W-W^{(s)}\right\|_{2}^{2}\right\} W(t+1)=argminW{G(1:t)⋅W+λ1∥W∥1+2λ2∥W∥22+21s=1∑tσ(s)∥∥∥W−W(s)∥∥∥22}
具体公式符号解释在下面。这里面用了L1正则化和L2正则化:
- L1正则化能够获得稀疏解, 比如10000个特征,10000个权重,稀疏解的含义是让一部分权重直接为0
- L2正则化降低模型复杂度,防止过拟合: 减少权重值的大小
模型解的稀疏性在机器学习中是非常重要的,尤其是工程应用领域。稀疏的模型解会大大减少预测时的内存和时间复杂度。
4.4 FTRL算法与SGD算法的联系
SGD算法的迭代计算公式如下:
w t + 1 = w t − η t g t \mathbf{w}_{t+1}=\mathbf{w}_{t}-\eta_{t} \mathbf{g}_{t} wt+1=wt−ηtgt
其中 t t t为迭代轮数, w \mathbf{w} w是模型参数, g \mathbf{g} g是loss function关于 w \mathbf{w} w的梯度, 而 η \eta η是学习率,它随着迭代轮数增多而递减。
FTRL算法的迭代公式:
w t + 1 = arg min w ( ∑ s = 1 t g s ⋅ w + 1 2 ∑ s = 1 t σ s ∥ w − w s ∥ 2 2 + λ 1 ∥ w ∥ 1 ) \mathbf{w}_{t+1}=\underset{\mathbf{w}}{\arg \min }\left(\sum_{s=1}^{t} \mathbf{g}_{s} \cdot \mathbf{w}+\frac{1}{2} \sum_{s=1}^{t} \sigma_{s}\left\|\mathbf{w}-\mathbf{w}_{s}\right\|_{2}^{2}+\lambda_{1}\|\mathbf{w}\|_{1}\right) wt+1=wargmin(s=1∑tgs⋅w+21s=1∑tσs∥w−ws∥22+λ1∥w∥1)
其中 t t t为迭代轮数, w \mathbf{w} w是模型参数, σ s \sigma_{s} σs定义成 ∑ s = 1 t σ s = 1 η t \sum_{s=1}^{t} \sigma_{s}=\frac{1}{\eta_{t}} ∑s=1tσs=ηt1, λ 1 \lambda_1 λ1是L1正则化系数。
上面公式中, a r g m i n arg min argmin算子的内容由3项组成,最后一项是L1正则(当然也可以加上L2正则), 很明显,L1正则是为了获取稀疏模型,如果令 λ 1 = 0 \lambda_1=0 λ1=0, 也就是说不要正则项,上面的式子就变成下面公式:
w t + 1 = arg min w ( ∑ s = 1 t g s ⋅ w + 1 2 ∑ s = 1 t σ s ∥ w − w s ∥ 2 2 ) \mathbf{w}_{t+1}=\underset{\mathbf{w}}{\arg \min }\left(\sum_{s=1}^{t} \mathbf{g}_{s} \cdot \mathbf{w}+\frac{1}{2} \sum_{s=1}^{t} \sigma_{s}\left\|\mathbf{w}-\mathbf{w}_{s}\right\|_{2}^{2}\right) wt+1=wargmin(s=1∑tgs⋅w+21s=1∑tσs∥w−ws∥22)
这是FTRL算法的核心,但更重要的是,这个公式直接等价于上面的SGD迭代计算公式。下面看下为啥等价,这里学到了。
首先,记 ∑ s = 1 t g s ⋅ w + 1 2 ∑ s = 1 t σ s ∥ w − w s ∥ 2 2 = f ( w ) \sum_{s=1}^{t} \mathbf{g}_{s} \cdot \mathbf{w}+\frac{1}{2} \sum_{s=1}^{t} \sigma_{s}\left\|\mathbf{w}-\mathbf{w}_{s}\right\|_{2}^{2}=f(\mathbf{w)} ∑s=1tgs⋅w+21∑s=1tσs∥w−ws∥22=f(w), 显然,这是一个凸函数, 所以存在极值。将其对 w \mathbf{w} w求梯度,得到:
∂ f ( w ) ∂ w = ∑ s = 1 t g s + ∑ s = 1 t σ s ( w − w s ) \frac{\partial f(\mathbf{w})}{\partial \mathbf{w}}=\sum_{s=1}^{t} \mathbf{g}_{s}+\sum_{s=1}^{t} \sigma_{s}\left(\mathbf{w}-\mathbf{w}_{s}\right) ∂w∂f(w)=s=1∑tgs+s=1∑tσs(w−ws)
梯度是个向量, 令上面式子等于0,这时候能得到极值,正是 W t + 1 \mathbf{W}_{t+1} Wt+1。
∑ s = 1 t g s + ∑ s = 1 t σ s ( w t + 1 − w s ) = 0 \sum_{s=1}^{t} \mathbf{g}_{s}+\sum_{s=1}^{t} \sigma_{s}\left(\mathbf{w}_{t+1}-\mathbf{w}_{s}\right)=0 s=1∑tgs+s=1∑tσs(wt+1−ws)=0
这个式子化简下, 将含有 W t + 1 \mathbf{W}_{t+1} Wt+1的项放到左边,剩下的放到右边:
( ∑ s = 1 t σ s ) w t + 1 = ∑ s = 1 t σ s w s − ∑ s = 1 t g s \left(\sum_{s=1}^{t} \sigma_{s}\right) \mathbf{w}_{t+1}=\sum_{s=1}^{t} \sigma_{s} \mathbf{w}_{s}-\sum_{s=1}^{t} \mathbf{g}_{s} (s=1∑tσs)wt+1=s=1∑tσsws−s=1∑tgs
把 ∑ s = 1 t σ s = 1 η t \sum_{s=1}^{t} \sigma_{s}=\frac{1}{\eta_{t}} ∑s=1tσs=ηt1代入进去化简:
1 η t w t + 1 = 1 η t w s − ∑ s = 1 t g s \frac{1}{\eta_{t}} \mathbf{w}_{t+1}=\frac{1}{\eta_{t}}\mathbf{w}_{s}-\sum_{s=1}^{t} \mathbf{g}_{s} ηt1wt+1=ηt1ws−s=1∑tgs
用 t − 1 t-1 t−1替换 t t t, 让他出来 w t \mathbf{w}_t wt,得
1 η t − 1 w t = 1 η t − 1 w s − ∑ s = 1 t − 1 g s \frac{1}{\eta_{t-1}} \mathbf{w}_{t}=\frac{1}{\eta_{t-1}} \mathbf{w}_{s}-\sum_{s=1}^{t-1} \mathbf{g}_{s} ηt−11wt=ηt−11ws−s=1∑t−1gs
上面两个式子相减得, 快出来SGD那个公式的样子了:
1 η t w t + 1 − 1 η t − 1 w t = ( 1 η t − 1 η t − 1 ) w t − g t \frac{1}{\eta_{t}} \mathbf{w}_{t+1}-\frac{1}{\eta_{t-1}} \mathbf{w}_{t}=\left(\frac{1}{\eta_{t}}-\frac{1}{\eta_{t-1}}\right) \mathbf{w}_{t}-\mathbf{g}_{t} ηt1wt+1−ηt−11wt=(ηt1−ηt−11)wt−gt
再合并同类项,化简 w t \mathbf{w}_t wt, 就能得到SGD的那个更新公式啦。也就是说,FTRL去掉正则项之后,就变成了SGD的更新公式。即FTRL左边两项承担了SGD算法的功能,而最右边的一项承担的是得到稀疏模型的功能。因为有这样的一种数学含义在背后,所以才好放心的下结论说,FTRL算法融合了RDA算法能产生稀疏模型的特性和SGD算法能产生更有效模型的特性,也就是说能学习出有效的且稀疏的模型。这个太powerful了。这个是参考的下面的第二篇博客。
4.5 离线数据训练FTRL模型
整理完了有关于FTRL的理论,下面说一下具体咋用了, 这里是用前面的那个离线数据集玩下TensorFlow的FTRL模型。先看怎么用:
算法参数
- lambda1:L1正则系数,参考值:10 ~ 15
- lambda2:L2正则系数,参考值:10 ~ 15
- alpha:FTRL参数,参考值:0.1
- beta:FTRL参数,参考值:1.0
- batchSize: mini-batch的大小,参考值:10000
性能评测, 设置参数:
- lambda1=15, lambda2=15, alpha=0.1, beta=1.0
使用FTRL算法训练模型:
classifiry = tf.estimator.LinearClassifier(feature_columns=feature_cl, optimizer=tr.train.FtrlOptimizer(learning_rate=0.01, l1_regularization_strength=10, l2_regularization_strength=15,))
但是会发现, 加上正则化之后效果并不一定能显著提升, 这也是在FTRL更适合大量的稀疏特征和大量数据场景。
下面就是用离线数据训练FTRL模型, 当时处理好的数据长下面这个样子来:
这里用的feature是121列, 1列channel_id, articlevector是100维, user_weights是10维, article_weights是10维。 这里的121列有两种处理方式:
- 给每个值指定一个类型
- 后面这3种特征, 计算下平均值, 作为一个特征先
这里为了减少代码量,采用了后面这种方式。并且它这里已经把上面的这个数据以feature和label的方式写入了tfrecords文件, 如何写入见上面的2.3,只不过我那里用了一个电商的数据集。所以下面这段代码的逻辑是从tfrecords中解析出数据,然后根据前面介绍的estimators里面的准备数据的流程去准备数据。具体代码如下:
FEATURE_COLUMNS = ['channel_id', 'vector', 'user_weights', 'article_weights']@staticmethod
# 这个模型注意,在提供给模型的时候,不能加参数,如果放到类里面的话,也不能加self, 那么就只能声明成静态方法
def read_ctr_records(): # 定义转换函数, 输入时序列化的def parse_tfrecords_function(example_proto):"""解析每个样本的example"""# 定义解析格式: parse_single_examplefeatures = {"label": tf.FixedLenFeature([], tf.int64), # 这里必须与保存的时候类型一致,名称一致"feature": tf.FixedLenFeature([], tf.string)}# 这个东西是个字典parsed_features = tf.parse_single_example(example_proto, features)# 修改其中的特征类型和形状# decode_raw相当于对解析数据指定类型feature = tf.decode_raw(parsed_features['feature'], tf.float64)feature = tf.reshape(tf.cast(feature, tf.float32), [1, 121])# 特征顺序 1 channel_id, 100 article_vector, 10 user_weights, 10 article_weights# 1 channel_id类别型特征, 100维文章向量求平均值当连续特征, 10用户权重求平均当连续特征,article权重也是# slice这个用法,第一个[]里面是上面切片的起始位置, 而第二个[]是切片的长度,也就是个数# 由于feature是[1,121], 所以第1个维度的起始位置是0, 终止位置是1,# 而channel_id, 特征这一列,0开始,然后切片数量是1,# article_vector 1开始,切100个 后面的类推# 所以 [第1个维度切片起始位置,第2个维度切片起始位置], [第1个维度切片个数,第二个维度切片个数]channel_id = tf.cast(tf.slice(feature, [0, 0], [1, 1]), tf.int32)vector = tf.reduce_sum(tf.slice(feature, [0, 1], [1, 100]), axis=1)user_weights = tf.reduce_sum(tf.slice(feature, [0, 101], [1, 110]), axis=1)article_weights = tf.reduce_sum(tf.slice(feature, [0, 111], [1, 10]), axis=1)# 解析labellabel = tf.cast(parsed_features['label'], tf.float32)# 解析完毕之后, 构造字典,名称 - tensortensor_list = [channel_id, vector, user_weights, article_weights]feature_dict = dict(zip(FEATURE_COLUMNS, tensor_list))return feature_dict, labeldataset = tf.data.TFRecordDataset(["./train_ctr_20190605.tfrecords"])dataset = dataset.map(parse_tfrecords_function) # 一个样本一个样本的走dataset = dataset.batch(64)dataset = dataset.repeat() # 我这里不写,而是在训练的时候指定steps也可以, 但这个和epoch是不一样的, epoch是所有样本过一遍模型叫做一个epoch, 而1个steps是一个batch过一遍模型return dataset
这里新学习的一个东西是如何解析写好的tfrecords文件的数据。
解析example:tf.parse_single_example(examplt_proto, features)
- features={“label”: tf.FixedLenFeature([], tf.int64), “feature”: tf.FixedLenFeature([], tf.string)}
- 针对每一个样本指定,string类型需要解析
- tf.decode_raw(parsed_features[‘feature’], tf.float64)
接下来,使用模型对特征列进行指定:
def train_eval(self):"""训练模型:return: """# 离散分类article_id = tf.feature_column.categorical_column_with_identity('channel_id', num_buckets=25)# 连续特征vector = tf.feature_column.numeric_column('vector')user_weights = tf.feature_column.numeric_column('user_weights')article_weights = tf.feature_column.numeric_column('article_weights')feature_columns = [article_id, vector, user_weights, article_weights]
模型训练以及评估:
# 下面这段代码在上面的函数里面# FTRL模型用的时候,就是指定这个优化器即可
classify = tf.estimator.LinearClassifier(feature_columns=feature_columns, optimizer=tf.train.FtrlOptimizer(learning_rate=0.1,l1_regularization_strength=10, l2_regularization_strength=10))
classify.train(read_ctr_records, steps=10000) # 这里指定了steps
result = classify.evaluate(read_ctr_records)
这就是FTRL模型如何使用了,我发现,这东西用起来也挺简单的,原来就是个优化器呀。 主要还是数据的读取与处理那块。下面就是深度学习模型W&D了。 关于理论部分,这里可能不会过多的整理了,之前整理过。
5. 排序模型进阶Wide&Deep 重点
5.1 基础介绍
首先,Wide&Deep模型长下面这个样子:
原来W&D还能这么玩啊, 可以同时序列CTR和CVR, 也就是点击率和转换率,最终的损失是这两个损失相加的形式。
Wide部分的输入特征:
- 原始的离散特征
- 离散特征之间做组合
- 不输入有连续值特征的, 至少在W&D的paper里面是这样使用的
Deep部分的输入特征:
- raw input+embedding 处理
- 对非连续值之外的特征做embedding处理, 这里都是策略特征, 就是乘以个embedding-matrix。在TensorFlow里面的接口是: tf.feature_column.embedding_column, 默认trainanble=True.
- 对连续型特征的处理是: 将其按照累计分布函数P(X<=x), 压缩至[0,1]内
注: 训练, Wide部分用FTRL来训练, Deep部分用AdaGrad来训练
Wide&Deep在TensorFlow里面的API接口为:tf.estimator.DNNLinearCombinedClassifier
- estimator = tf.estimator.DNNLinearCombinedClassifier()
- model_dir = “”,
- linear_feature_columns=wide_columns
- dnn_feature_columns=deep_columns
- dnn_hidden_units=[]: dnn层的网络结构
这里还记录下推荐系统里面常用的名词:之前一直懵逼,什么CTR,CVR
- impression:用户观察到曝光的产品, 曝光量
- click:用户对impression的点击行为, 点击量
- conversion:用户点击之后对物品的购买行为, 转换量
- CTR:从impression到click的比例, c l i c k / i m p r e s s i o n click/impression click/impression, 点击率
- CVR:从click到conversion的比例, c o n v e r s i o n / c l i c k conversion / click conversion/click, 转换率, 衡量CPA广告效果的指标
- CTCVR:从impression到conversion的比例 c o n v e r s i o n / i m p r e s s i o n conversion / impression conversion/impression
- pCTR:p(click=1 | impression), 点击概率
- pCVR: p(conversion=1 | click=1,impression), 转换概率
- pCTCVR: p(conversion=1,click=1 |impression) = p(click=1 | impression)
以电子商务平台为例,用户的购买行为一般遵循以下的顺序决策模式:impression-click-conversion,即用户先观察到系统推荐的产品,然后会对自己感兴趣的商品进行点击,进而产生购买行为。
- CPC (Cost Per Click): 按点击计费(平均点击价格)
- CPA (Cost Per Action): 按成果数计费
5.2 用上面的数据玩一下
这里直接用上面的数据走一下,可以直接下train_eval函数,看看怎么用。逻辑和上面基本上一样,只不过这里的输入特征会划分一下, 放入wide层的特征和放入deep层的特征。
def train_eval():# 指定wide和deep两边的feature_column# wide, channel_id 如果就是一个具体的数字# nums_buckets必须指定,也就是取值个数channel_id = tf.feature_column.categorical_column_with_identity('channel_id', num_buckets=25)wide_columns = [channel_id]# deep ID必须embedding结果,数值型列vector = tf.feature_column.numeric_column('vector')user_weights = tf.feature_column.numeric_column('user_weights')article_weights = tf.feature_column.numeric_column('article_weights')# channel_id必须进行embeddingdeep_column = [tf.feature_column.embedding_column(channel_id, diminsion=25), vector, user_weights, article_weights]# 这里完事之后,就指定模型,训练预测即可estimator = tf.estimator.DNNLinearCombinedClassifier(model_dir="./ckpt/wide_and_deep",linear_feature_columns=wide_columns,dnn_feature_columns=deep_columns,dnn_hidden_units=[1024, 512, 256])estimator.train(input_fn=read_ctr_records, steps=10000)eval_result = estimator.evaluate(input_fn=read_ctr_records, steps=10000)
6. WDL模型导出
这里的逻辑就是把上面的模型导出一个固定的格式,这里注意不是上面那种普通的ckpt格式的了,不同用途的模型,有不同的导出格式。导出之后,再用TensorFlow Serving进行模型部署,这样就可以进行线上预测了。
6.1 线上预估
线上流量是模型效果的试金石。离线训练好的模型只有参与到线上真实流量预估,才能发挥其价值。在演化的过程中,我们开发了一套稳定可靠的线上预估体系,提高了模型迭代的效率。
基于TF Serving的模型服务:TF Serving是TensorFlow官方提供的一套用于在线实时预估的框架。 它突出优点是: 和TensorFlow无缝衔接,具有很好的扩展性。 使用TF Serving可以快速支持RNN, LSTM, GAN等多种网络结构,而不需要额外开发代码,有利于模型快速实验和迭代。
6.2 SavedModel
目的: 导入savemodel的模型格式
TensorFlow的模型格式有很多种, 针对不同的场景可以使用不同的格式, 只要符合规范的模型都可以轻易的部署到在线服务或移动设备上,这里列举下:
- Checkpoint: 用于保存模型的权重,主要用于模型训练过程中参数的备份和模型训练热启动
- SavedModel: 使用save_model接口导出的模型文件, 包含模型Graph和权限,可以直接用于上线, TensorFlowestimator和Keras模型推荐使用这种模型格式
- 实现:
- 指定Serving模型的输入特征列类型,用于预测时候输入的列的类型指定
- 指定模型的特征列输入函数以及example,用于预测的时候输入的整体数据格式
tf.feature_column.make_parse_example_spec(columns)
tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec):
指定输入的特征列,数据格式
导出代码如下:
# 定义导出模型的输入特征列
wide_columns = [tf.feature_column.categorical_column_with_identity('channel_id', num_bucket=25)
deep_columns = [tf.feature_column.embedding_column(tf.feature_column.categorical_column_with_identity('channel_id', num_buckets=25), dimension=25),tf.feature_column.numeric_column('vector'),tf.feature_column.numeric_column('user_weights'),tf.feature_column.numeric_column('article_weights')]columns = wide_columns + deep_columns# 模型的特征列输入函数指定, 按照example构造
# 下面写法是固定的,换了模型的时候,只需要修改上面的特征列信息即可
feature_spec = tf.feature_column.make_parse_example_spec(columns)
serving_input_receiver_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec)estimator.export_savedmodel("./serving_model/wdl/", serving_input_receiver_fn)
这里是以时间戳命名进行保存的:
6.3 TensorFlow Serving
TensorFlow Serving是一种灵活的高性能服务系统,适用于机器学习模型,专为生产环境而设计。TensorFlow Serving可以轻松部署新算法和实验,同时保持相同的服务器架构和API。TensorFlow Serving提供与TensorFlow模型的开箱即用集成,但可以轻松扩展以提供其他类型的模型和数据。
- 训练模型与部署环境(模型)进行隔离
- 热更新
- 第一个版本: 部署起来
- 第二个版本: 重新训练的更好的模型,保存到原来的部署的路径即可
- TensorFlow Serving会根据文件夹的时间戳, 自动替换和使用最新的模型
- 模型的热更新是非常重要的
安装TensorFlow Serving:
- 见官网https://www.tensorflow.org/serving/setup
- 使用Docker安装进行,首先你的电脑当中已经安装过docker容器(建议的一种方式)
使用步骤如下:
获取最新TF Serving docker镜像
docker pull tensorflow/serving
查看docker镜像
docker images
运行 tf serving(即创建一个docker容器来运行)
docker run -p 8501:8501 -p 8500:8500 --mount type=bind,source=/home/ubuntu/detectedmodel/commodity,target=/models/commodity -e MODEL_NAME=commodity -t tensorflow/serving
说明:
-p 8501:8501
为端口映射,-p
主机端口:docker容器程序(tf serving)使用端口,访问主机8501端口就相当于访问了tf serving程序的8501端口- tf serving 使用8501端口对外提供HTTP服务,使用8500对外提供gRPC服务,这里同时开放了两个端口的使用
--mount type=bind,source=/home/ubuntu/detectedmodel/commodity,target=/models/commodity
为文件映射,将主机(source)的模型文件映射到docker容器程序(target)的位置,以便tf serving使用模型,target参数为/models/我的模型名字, 这里的source就是保存的模型所在的路径目录。前面的mount和type固定写法-e MODEL_NAME=commodity
设置了一个环境变量,名为MODEL_NAME,此变量被tf serving读取,用来按名字寻找模型,与上面target参数中我的模型对应, 写法固定, 这里再次指定模型名字tensorflow/serving
为镜像名
案例: 上面的wdl模型服务运行
docker run -p 8501:8501 -p 8500:8500 --mount type=bind,source=/root/toutiao_project/reco_sys/server/models/serving_model/wdl,target=/models/wdl -e MODEL_NAME=wdl -t tensorflow/serving
这样就把上面保存的模型给部署完毕了, 原来模型部署就一行代码的事啊。
查看是否运行
itcast:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ed3a36a07ba8 tensorflow/serving "/usr/bin/tf_serving…" About a minute ago Up About a minute 0.0.0.0:8500-8501->8500-8501/tcp vigorous_goodall
这样, 就把WDL模型部署起来了, 那么究竟如何使用呢? 下面就是重点啦。
7. 排序模型在线测试
这里的任务就是看看如何使用上面部署的WDL模型完成在线预测。
7.1 排序模型在线预测添加
目的:编写tf serving客户端程序调用serving模型服务
步骤:
- 用户特征与文章特征合并
- serving服务端的example样本结构构造
- 模型服务调用对接
7.1.1 用户特征与文章特征合并
这一步和之前构造LR特征那里的逻辑是一样的,读取用户特征和文章特征,这里是选择了1个用户, 4篇文章进行的测试。
hbu = HBaseUtils(poolll)
# 排序
# 1、读取用户特征中心特征
try:user_feature = eval(hbu.get_table_row('ctr_feature_user','{}'.format(1115629498121846784).encode(),'channel:{}'.format(18).encode()))# logger.info("{} INFO get user user_id:{} channel:{} profile data".format(# datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))
except Exception as e:user_feature = []
if user_feature:# 2、读取文章特征中心特征result = []# examplesexamples = []for article_id in [17749, 17748, 44371, 44368]:try:article_feature = eval(hbu.get_table_row('ctr_feature_article','{}'.format(article_id).encode(),'article:{}'.format(article_id).encode()))except Exception as e:article_feature = [0.0] * 111
用了用户id, channel_id, 四篇文章的特征,下面就是组合到一起,构建example结构的训练样本
7.1.2 serving服务端的example样本结构构造
这些代码是接上面, 也就是要写到for循环里面,去构造4个样本
# 构造每一篇文章与用户的example结构,训练样本顺序千万要注意要和模型那里训练的时候特征一致。
channel_id = int(article_feature[0])
# 求出后面若干向量的平均值
vector = np.mean(article_feature[11:])
# 第三个用户权重特征
user_feature = np.mean(user_feature)
# 第四个文章权重特征
article_feature = np.mean(article_feature[1:11])# 组建example 一次是一个样本
# 这里的这个顺序,一定要与模型导出的那个顺序一致, 且名称要一模一样,否则模型会报找不到特征错误
example = tf.train.Example(features=tf.train.Features(feature={"channel_id": tf.train.Feature(int64_list=tf.train.Int64List(value=[channel_id])),"vector": tf.train.Feature(float_list=tf.train.FloatList(value=[vector])),'user_weigths': tf.train.Feature(float_list=tf.train.FloatList(value=[user_feature])),'article_weights': tf.train.Feature(float_list=tf.train.FloatList(value=[article_feature])),}))# 所有的样本,放到一个列表中
examples.append(example)
7.1.3 模型服务调用对接
导入包:
from tensorflow_serving.apis import prediction_service_pb2_grpc
: 与grpc建立stubPredictionServiceStub
: 建立stub,负责调用的接口,方法和参数约定Classify
: 发送请求,设置延时时间 secs timeout
from tensorflow_serving.apis import classification_pb2
: 调用服务,提供请求参数
# 这里要通过grpc和本地的8500端口对接
with grpc.insecure_channel('127.0.0.1:8500') as channel:# 这玩意构造好之后就可以发送请求了stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)# 获取测试数据集,并转换成 Example 实例# 准备 RPC 请求,指定模型名称, 指定样本, 这里是核心request = classification_pb2.ClassificationRequest()request.model_spec.name = 'wdl' # 这里指定模型名称request.input.example_list.examples.extend(examples) # 封装好的example列表# 获取结果 通过stub的classify去发送请求,也就是对模型说,我想预测examples了# 然后发出去, 模型预测完毕之后,返回结果来response接收即可response = stub.Classify(request, 10.0) # 10秒不回复,就停止了# 这里会将模型预测好的结果返回回来print(response)
这里就完事了, 不过这里要好好看看,面试的时候有被问过的,所以如果把这个项目写上, 要好好的把所有东西都得重新学一遍。这一遍肯定是不够的。
8. 项目总结
到这里终于把这个黑马头条推荐的项目走完了一遍,并通过了10篇文章进行整理过程,现在回顾一下, 内容和信息还是非常庞大的,导致我现在基本上忘掉了很多细节,所以接下来就是结合着这10篇笔记,重新手撸这个项目,来熟悉每个技术以及每个细节,真正的把推荐系统的流程给掌握熟, 总的来说感觉人家讲的还是非常不错的,都是业界必备的技术,当然,可能真实情况中不会基于pyspark框架,不会用python,而是直接用Scala, java语言写。这些也是后面打算学习的知识, 用了大约半个月的时间,把这个项目走完了, 接下来用导图的方式拎起来,然后再重新回味一下了。
参考:
- 黑马头条推荐系统项目
- 理解FTRL算法
头条项目推荐的相关技术(十):排序模型进阶与总结相关推荐
- 头条项目推荐的相关技术(四):离线文章画像的增量更新及离线文章相似度计算
1. 写在前面 这里是有关于一个头条推荐项目的学习笔记,主要是整理工业上的推荐系统用到的一些常用技术, 这是第四篇, 上一篇文章整理了离线文章画像的计算过程,主要包括TFIDF和TextRank两种技 ...
- 头条项目推荐的相关技术(一): 数据库的迁移与定时迁移技术
1. 写在前面 这里是有关于一个头条推荐项目的学习笔记,主要是整理工业上的推荐系统用到的一些常用技术, 这是第一篇, 首先是数据库的迁移技术.在实际生产环境中,我们要处理的数据可能来自各个地方,业务数 ...
- Python黑马头条推荐系统第五天 头条排序模型进阶-神经网络
黑马头条排序模型进阶 7.1 神经网络基础与原理 学习目标 目标 了解感知机结构.作用以及优缺点 了解tensorflow playground的使用 说明感知机与神经网络的联系 说明神经网络的组成 ...
- 排序模型进阶-FMFTRL
日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 5.8 排序模型进阶-FM&FTRL 学习目标 目标 ...
- 排序模型进阶-WideDeepWDL模型导出
8.5 排序模型进阶-Wide&Deep 学习目标 目标 无 应用 无 8.5.1 wide&deep Wide部分的输入特征: raw input features and tran ...
- 推荐算法炼丹笔记:排序模型CTR点击率预估系列
微信公众号:炼丹笔记 CTR点击率预估系列家谱 炼丹之前,先放一张CTR预估系列的家谱,让脉络更加清晰. (一)FiBiNET:结合特征重要性和双线性特征交互进行CTR预估 1.1 背景 本文发表在 ...
- 如何构建优质的推荐系统服务?| 技术头条
作者丨gongyouliu 来源 | 大数据与人工智能(ID:ai-big-data) 任何一个优质的软件服务都必须考虑高性能.高可用(HighAvailability).可伸缩.可拓展.安全性等5大 ...
- 乐优商城练手项目相关技术总结
FastDFS(分布式文件系统) 为什么要使用FastDFS? 如果说文件上传的服务将资源都存放到本地的话,最终都会受限于磁盘大小.那么当然可以在本地配置多台文件服务器来存放文件,这个时候需要用Ngi ...
- 表面缺陷检测数据集汇总及其相关项目推荐
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 最近,有许多朋友都在关注缺陷检测领域,今天来看看缺陷检测. 目前, 基于机器视觉的表面 缺陷装备已经在 ...
最新文章
- 01 手把手带你构建大规模分布式服务--高并发、高可用架构系列,高质量原创好文!...
- python.freelycode.com-优化Pandas代码执行速度入门指南
- php和python交互-PHP与Python进行数据交互
- 【转】DE2_TV例程的几点说明
- /etc/resolv.conf 的更改在重启后丢失的解决办法
- 【(Python解释器、Pycharm)安装教程】【使用PyCharm编写第一个Python程序】
- ASP.NET基础代码备忘
- Oracle收购Sun消化不良 Sun赢利困难 MySQL将何去何从
- c++ 插入排序算法
- 微信客户端抽奖转盘效果
- LCD12864 液晶显示-汉字及自定义显示(并口)
- 人脸检测-人脸对齐-人脸识别原理及方法
- 最小公倍数求法 (3种代码思路供参考 ) --(C语言实现)-- 详解
- stlink固件版本低,但升级失败的解决办法
- LeetCode:14. Longest Commen Prefix(Easy)
- c语言搬山游戏,C语言实例:搬山游戏
- 2021-2027全球与中国智能办公室系统市场现状及未来发展趋势
- 无法从命令行或调试器启动服务,必须首先安装Windows服务(使用installutil.exe),然后用ServerExplorer、Windows服务器管理工具或NET START命令启动它
- 开发类似斗鱼虎牙的运营级直播平台多少钱
- 判断边界由1组成的最大正方形面积