上期介绍了3篇阿里的论文:推荐系统(八):阿里电商推荐算法论文导读(上),本期将继续介绍。
4. Learning Piece-wise Linear Models from Large Scale Data for Ad Click Prediction
4.1 算法介绍
当前业界常用的CTR预估方法、简介及其不足如下:
方法 | 简介 | 不足 |
---|---|---|
逻辑回归 | 使用了Sigmoid函数将函数值映射到0~1区间作为CTR的预估值。LR这种线性模型很容易并行化,处理上亿条训练样本不是问题。 | 线性模型的学习能力有限,需要引入大量的领域知识来人工设计特征以及特征之间的交叉组合来间接补充算法的非线性学习能力,非常消耗人力和机器资源,迁移性不够友好。 |
Kernel方法 | 将低维特征映射到高维特征空间 | 复杂度太高而不易实现 |
树模型 | 如Facebook的GBDT+LR算法,有效地解决了LR模型的特征组合问题 | 是对历史行为的记忆,缺乏推广性,树模型只能学习到历史数据中的特定规则,对于新规则缺乏推广性 |
FM模型 | 自动学习高阶属性的权值,不用通过人工的方式选取特征来做交叉 | FM模型只能拟合特定的非线性模式,常用的就是二阶FM |
深度神经网络 | 使用神经网络拟合数据之间的高阶非线性关系,非线性拟合能力足够强 | 适合数据规律的、具备推广性的网络结构业界依然在探索中,尤其是要做到端到端规模化上线,这里面的技术挑战依然很大 |
2011-2012年期间,阿里妈妈资深专家盖坤创新性地提出了MLR(mixed logistic regression)算法,引领了广告领域CTR预估算法的全新升级。MLR算法创造地提出并实现了直接在原始空间学习特征之间的非线性关系,基于数据自动发掘可推广的模式,相比于人工来说效率和精度均有了大幅提升。
MLR可以看做是对LR的一个自然推广,它采用分而治之的思路,用分片线性的模式来拟合高维空间的非线性分类面,其形式化表达如下:
其中是聚类参数,决定了空间的划分,是分类参数,决定空间内的预测。超参数分片数m可以较好地平衡模型的拟合与推广能力。当m=1时MLR就退化为普通的LR,m越大模型的拟合能力越强,但是模型参数规模随m线性增长,相应所需的训练样本也随之增长。
在实际中,MLR算法常用的形式如下,使用softmax作为分片函数:
在此情况下,MLR模型可以看作一个FOE模型:
采用负似然(neg-likelihood)损失函数:
由于加入了正则项,MLR算法变的不再是平滑的凸函数,梯度下降法不再适用,因此模型参数的更新使用LBFGS和OWLQN的结合。
MLR算法适合于工业级的大规模稀疏数据场景问题,如广告CTR预估。背后的优势体现在两个方面:
端到端的非线性学习:从模型端自动挖掘数据中蕴藏的非线性模式,省去了大量的人工特征设计,这 使得MLR算法可以端到端地完成训练,在不同场景中的迁移和应用非常轻松。
稀疏性:MLR在建模时引入了L1和L2,1范数正则,可以使得最终训练出来的模型具有较高的稀疏度, 模型的学习和在线预测性能更好。当然,这也对算法的优化求解带来了巨大的挑战。
4.2 算法实践
采用如下数据,预测一个人一年内是否能挣到50K的钱,完整代码如下:
import tensorflow as tf
from sklearn.metrics import roc_auc_score
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
def MLR(train_x, train_y, test_x, test_y):
x = tf.placeholder(tf.float32, shape=[None, 108])
y = tf.placeholder(tf.float32, shape=[None])
m = 2
learning_rate = 0.3
u = tf.Variable(tf.random_normal([108,m],0.0,0.5),name='u')
w = tf.Variable(tf.random_normal([108,m],0.0,0.5),name='w')
U = tf.matmul(x,u)
p1 = tf.nn.softmax(U)
W = tf.matmul(x,w)
p2 = tf.nn.sigmoid(W)
pred = tf.reduce_sum(tf.multiply(p1,p2),1)
cost1 = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=pred, labels=y))
cost = tf.add_n([cost1])
train_op = tf.train.FtrlOptimizer(learning_rate).minimize(cost)
result = []
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(0, 10000):
f_dict = {x:train_x, y:train_y}
_, cost_, predict_ = sess.run([train_op, cost, pred], feed_dict=f_dict)
auc = roc_auc_score(train_y, predict_)
if epoch % 100 == 0:
f_dict = {x:test_x, y:test_y}
_, cost_, predict_test = sess.run([train_op, cost, pred], feed_dict=f_dict)
test_auc = roc_auc_score(test_y, predict_test)
print('%d cost:%f, train_auc:%f test_auc:%f' % (epoch, cost_, auc, test_auc))
result.append([epoch,auc,test_auc])
pd.DataFrame(result, columns=['epoch','train_auc', 'test_auc']).to_csv("data/lr.csv")
plt.plot(np.array(result)[:,0], np.array(result)[:,1],label='train_auc')
plt.plot(np.array(result)[:,0],np.array(result)[:,2],label='test_auc')
plt.xlabel('epoch')
plt.ylabel('auc')
plt.grid()
plt.legend(['train_auc', 'test_auc'], loc = 'best')
train_data = pd.read_table('data/adult.data.txt', header=None, delimiter=',')
test_data = pd.read_table('data/adult.test.txt',header=None, delimiter=',')
all_columns = ['age','workclass','fnlwgt','education','education-num',
'marital-status','occupation','relationship','race','sex',
'capital-gain','capital-loss','hours-per-week','native-country','label','type']
continus_columns = ['age','fnlwgt','education-num','capital-gain','capital-loss','hours-per-week']
dummy_columns = ['workclass','education','marital-status','occupation','relationship','race','sex','native-country']
train_data['type'] = 1
test_data['type'] = 2
all_data = pd.concat([train_data,test_data],axis=0)
all_data.columns = all_columns
all_data = pd.get_dummies(all_data, columns=dummy_columns)
train_data = all_data[all_data['type']==1].drop(['type'],axis=1)
test_data = all_data[all_data['type']==2].drop(['type'],axis=1)
train_data['label'] = train_data['label'].map(lambda x:1 if x.strip() == '>50K' else 0)
test_data['label'] = test_data['label'].map(lambda x: 1 if x.strip() == '>50K.' else 0)
for col in continus_columns:
train_data[col] = StandardScaler().fit_transform(train_data[[col]])
test_data[col] = StandardScaler().fit_transform(test_data[[col]])
train_y = train_data['label']
train_x = train_data.drop(['label'],axis=1)
test_y = test_data['label']
test_x = test_data.drop(['label'],axis=1)
if __name__ == '__main__':
MLR(train_x, train_y, test_x, test_y)
训练个测试过程中的AUC曲线变化如下:
5. Deep Interest Network for Click-Through Rate Prediction
5.1 背景与原理
深度学习在CTR预估领域已经有了广泛的应用,常见的算法比如Wide&Deep,DeepFM等。这些方法一般的思路是:通过Embedding层,将高维离散特征转换为固定长度的连续特征,然后通过多个全联接层,最后通过一个sigmoid函数转化为0-1值,代表点击的概率。即Sparse Features -> Embedding Vector -> MLPs -> Sigmoid -> Output.
这种方法的优点在于:通过神经网络可以拟合高阶的非线性关系,同时减少了人工特征的工作量。
阿里研究者在线上数据中发现用户行为有两个特征:
Diversity:用户在浏览电商网站过程中显示出的兴趣是多样的。
Local activation:由于用户兴趣的多样性,只有部分历史数据会影响到当次推荐的物品是否被点击,而不是所有的历史记录。
这两种特性是密不可分的。针对上面提到的用户行为中存在的两种特性,阿里将其运用于自身的推荐系统中,推出了深度兴趣网路DIN,整体框架如下:
整个过程可以描述为:
1)检查用户历史行为数据;
2)mactching产生候选ads;
3)ranking计算每个候选ads的概率并排序;
4)记录用户在当前展示广告下的反应,作为label。
本文特征分为4个部分:用户特征,用户行为特征,广告特征,上下文特征,具体如下:
以下为基本模型与DIN模型的比较,Base Model首先把one-hot或multi-hot特征转换为特定长度的embedding,作为模型的输入,然后经过一个DNN的part,得到最终的预估值。特别地,针对multi-hot的特征,做了一次element-wise+的操作,这里其实就是sum-pooling,这样,不管特征中有多少个非0值,经过转换之后的长度都是一样的!
Base Model有一个很大的问题,它对用户的历史行为是同等对待的,没有做任何处理,这显然是不合理的。一个很显然的例子,离现在越近的行为,越能反映你当前的兴趣。因此,对用户历史行为基于Attention机制进行一个加权,这就是深度兴趣网络(Deep Interest Network)。
Attention机制简单的理解就是,针对不同的广告,用户历史行为与该广告的权重是不同的。这里的权重,就是Attention机制即上图中的Activation Unit所需要学习的。
在加入Activation Unit之后,用户的兴趣表示计算如下:
其中,表示behavior id 的嵌入向量,比如good_id,shop_id等。是所有behavior ids的加权和,表示的是用户兴趣;是候选广告的嵌入向量;是候选广告影响着每个behavior id的权重,也就是Local Activation。通过Activation Unit计算得出,这一块用函数去拟合,表示为。
模型使用的评价指标是GAUC,计算公式如下:
6. Deep Interest Evolution Network for Click-Through Rate Prediction
6.1 背景与算法
DIN模型将用户的历史行为来表示兴趣,但存在两个缺点:
1)用户的兴趣是不断进化的,而DIN抽取的用户兴趣之间是独立无关联的,没有捕获到兴趣的动态进化性。
2)通过用户的显式的行为来表达用户隐含的兴趣,这一准确性无法得到保证。
基于以上两点,阿里提出了深度兴趣演化网络DIEN来CTR预估的性能。DIEN模型的主要贡献点在于:
1)关注电商系统兴趣演化过程,提出新的网络结构以建模兴趣进化过程,该模型能更精确地表达用户兴趣和提高CTR预估率。
2)设计兴趣抽取层,通过计算一个辅助loss,以提升兴趣表达的准确性;
3)设计兴趣进化层,以更加准确表达用户兴趣的动态变化性。
DIEN的模型结构如下:
与DIN相比,可以看到,DIN和DIEN的最底层都是Embedding Layer,User profile, target AD和context feature的处理方式是一致的。不同的是,DIEN将user behavior组织成了序列数据的形式,并把简单的使用外积完成的activation unit变成了一个attention-based GRU网络。
兴趣抽取层(Interest Extractor Layer)
兴趣抽取层Interest Extractor Layer的主要目标是从embedding数据中提取出interest。但一个用户在某一时间的interest不仅与当前的behavior有关,也与之前的behavior相关,所以作者们使用GRU单元来提取interest。GRU单元的表达式如下:
其中表示提取出的用户兴趣,为说明兴趣表达的合理性,采用辅助loss。设计了一个二分类模型来计算兴趣抽取的准确性,我们将用户下一时刻真实的行为作为正例,负采样得到的行为作为负例,分别与抽取出的兴趣结合输入到设计的辅助网络中,得到预测结果,并通过logloss计算一个辅助的损失:
兴趣进化层(Interest Evolution Layer)
兴趣进化层的主要目标是刻画用户兴趣的进化过程。举个简单的例子:以用户对衣服的interest为例,随着季节和时尚风潮的不断变化,用户的interest也会不断变化。这种变化会直接影响用户的点击决策。建模用户兴趣的进化过程有两方面的好处:
1)追踪用户的interest可以使我们学习final interest的表达时包含更多的历史信息。
2)可以根据interest的变化趋势更好地进行CTR预测。
而interest在变化过程中遵循如下规律:
1)interest drift:用户在某一段时间的interest会有一定的集中性。比如用户可能在一段时间内不断买书,在另一段时间内不断买衣服。
2)interest individual:一种interest有自己的发展趋势,不同种类的interest之间很少相互影响,例如买书和买衣服的interest基本互不相关。
为了利用这两个时序特征,我们需要再增加一层GRU的变种,并加上attention机制以找到与target AD相关的interest。
attention的计算方式如下:
而Attention和GRU结合起来的机制有很多,文中介绍了一下三种:
GRU with attentional input (AIGRU): 这种方式将attention直接作用于输入,无需修改GRU的结构:
Attention based GRU(AGRU): 这种方式需要修改GRU的结构,此时hidden state的输出变为:
GRU with attentional update gate (AUGRU): 这种方式需要修改GRU的结构,此时hidden state的输出变为:
参考资料
[1] https://www.jianshu.com/p/627fc0d755b2
[2] https://www.jianshu.com/p/647669169f98
[3] https://www.jianshu.com/p/73b6f5d00f46
试上超然台上看,半壕春水一城花。烟雨暗千家。——苏轼《望江南·超然台作》