相信大家都在淘宝购买过相关的商品。
比如某一天用户购买了一台台式主机后,淘宝会立刻给该用户推送与电脑相关的商品,例如电脑显示屏,键盘和鼠标等等。
这些推荐的物品完全是淘宝根据大量的点击数据去学习到的。
看完这部剧之后,还觉得不过瘾怎么办?
往下翻,你就会惊喜的发现一系列的推荐:
是不是感觉推荐的电影都很符合自己的口味?
<隐秘的角落>属于悬疑类的连续剧,而下面推荐的相关剧集也大多属于这个风格。
除了上述领域,推荐系统还广泛的应用在生活中的方方面面,可以说是机器学习在工业界中最成功的落地场景之一了。
美国著名的电影和电视节目提供商Netflix曾经发起了奖金为百万美元的推荐系统比赛,旨在提升推荐系统的准确度。
在广告点击率预估的场景,性能提高了1%的模型往往可以为公司带来巨大的收入。
算法原理介绍
传统的推荐系统以协同过滤为代表,而随着深度学习的快速发展,越来越多的研究者尝试应用深度学习技术到推荐系统中。
在此我们主要介绍其中的Wide&Deep网络。
总体流程
在此我们以APP商店中的推荐系统为例,其整体流程可以如下图所示
推荐系统流程图
给定一个查询,这个查询可能是用户相关的特征,推荐系统首先会从数据库中检索到查询相关的APP,由于APP的数量非常巨大,因此我们可以取最相关的100个检索结果作为候选APP,这一过程通常叫作 粗排 。
然后将候选的100个APP送入排序模型中,此处的排序模型就是我们下面将要介绍的 Wide&Deep模型 ,这一过程也被叫作 精排 。
排序完成后,我们可以将点击概率最高的APP放置于用户最容易注意到的地方。 无论用户是否点击了我们的推荐结果,我们都可以构造一个新的日志文件。 在累积了一定数量的日志文件后,就可以继续微调排序模型,提高模型的准确程度
Wide&Deep模型
Wide&Deep模型自从被提出后就引起来非常大的关注。
正如其名,
此模型主要可以分成两个部分,Wide部分和Deep部分
。
Wide模型
它的设计目的是 为了记住数据中特定的特征组合方式 。 例如前面介绍购买电脑的场景,购买了电脑主机的用户,再次购买显示器,键盘等物件的概率特别高。 因此可以将用户最近是否购买了电脑作为输入模型的输入,也就是特征。
假设 当前wide网络只有一个特征 ,当该特征取1时,y=wx可以得到y=0.9(假设w的值是0.9),当该特征取0时,y=0。 y输出值越大,会增大模型对应用户点击概率的估计。
回到我们APP应用推荐的场景中,可以看到图右侧的Cross Product Transformation,就是我们的wide部分的输入。 Cross Product Transformation是指特征交叉,即将User Installed App和Impression APP进行组合。
例如用户手机已经安装了微信,且当前的待估计的APP为QQ,那么这个组合特征就是(User Installed App=’微信’, Impression APP=’QQ’)。
Deep模型
介绍完Wide部分,我们重新回来再看下Deep部分。 Deep部分的设计是为了模型具有较好的泛化能力 ,在输入的数据没有在训练集中出现时,它依然能够保持相关性较好的输出 。
在下图中, Deep模型输入都是一些含义不是非常明显的特征 ,例如设备类型,用户统计数据等类别特征(Categorical Features)。 类别特征一般属于高维特征。
例如手机的种类可能存在成千上万个,因此我们通常把这些类别特征通过 嵌入(Embedding) 的方式, 映射成低维空间的参数向量 。 这个向量可以被认为表示了原先这个类别特征的信息。 对于连续特征,其数值本身就具备一定的含义,因此可以直接将和其他嵌入向量进行拼接。
2Wide&Deep模型图
在拼接完成后,可以得到大致为1200维度的向量 。 将其作为三层全连接层网络的输入,并且选择Relu作为激活函数,其中每层的输出维度分别为[1024, 512, 256]。
输出层
在广告点击率预估的场景中, 模型的输出是一个0~1之间的值 , 表示当前候选APP被点击的概率 。 因此可以采用逻辑回归函数,将Wide&Deep部分的输出压缩到0~1在之间。
首先, Deep部分的输出是一个256维度的向量 ,可以通过一个线性变换将其映射为维度为1的值,然后 和Wide部分的输出进行求和,将求和后的结果输入到逻辑回归函数中。
代码实践
在介绍完模型之后,现在开始动手实践了,由于谷歌公司并没有将其在论文中使用的APP商店数据集进行公开,因此我们现在
采用Criteo公司发布在Kaggle的广告点击率预估数据集Criteo
。
数据处理
在模型定义前,我们需要对数据进行预处理。
数据处理的目的是将数据集中的特征取值映射为数值id,并且去除一些出现次数过少的特征值
,避免特征值出现次数过少,导致当特征值对应的参数向量的更新次数过少,影响模型的精度。
Criteo数据集由13类连续特征和26列类别特征,已经通过哈希方式映射为了32位数值。
对应的标签(label)的取值为0和1。
还记得之前提到过的低维嵌入过程吗?
映射操作是为了模型中的嵌入向量查找而准备的。
例如,输入数据中的A,B,C被映射为了0,1,2。
而0,1,2分别表示在嵌入矩阵中(Embedding Table)的第0行,第1行和第2行。
将Criteo数据集下载后解压 ,可以看到train.txt和test.txt文件。 查看train.txt中的文件, 可以看到其中某些行存在缺失 。
我们可以调用
model_zoo/wide_and_deep/src/preprocess_data.py
进行数据的下载和处理, 对于缺失值,可以将其标记为OOV(Out Of Vocabulary)对应的id
模型定义
介绍完数据处理后,我们在此开始 定义模型 。 模型的核心代码在mindspore仓库下
model_zoo/wide_and_deep/src/wide_and_deep.py
在MindSpore中,网络的定义方式和Pytorch比较接近,先定义定义的操作,然后再construct函数中对调用对应的操作对输入进行处理。
首先我们再回忆下 Wide&Deep网络 ,它由一个Wide部分和Deep部分。 其中 Wide部分是一个线性网络 。
在实现上,我们将线性网络中的权重视为维度为1,通过嵌入矩阵查找的方式即可获得输入x对应的权重,然后将其和输入的mask相乘,将结果求和。
值得注意的是, 我们将连续特征和类别特征进行等同处理,因此这里的mask是为了将连续特征和类别特征进行区而设计的 。 连续特征mask中的值即为连续特征值,类别特征mask中的值为1 。 Wide部分的核心代码如下所示,我们定义个名为self.wide_w的权重,它的形状为[词表大小,1]。
class WideDeepModel(nn.Cell):
def __init__(self, config):
super(WideDeepModel