1、DeepFM的原理
1.1 DeepFM介绍
CTR预估中,FM考虑到了特征之间也是有联系的,以电视节目来说,女生就可能比较偏爱言情剧,男生比较偏爱历史正剧,因此在特征组合起来考虑能够在一定程度上提高推荐系统的性能。有些特征易于理解的,可以通过领域专家人工标记,但是难以理解的特征组合就需要通过机器学习分析数据之间的关联得到。
简单的线性模型只能得到一维的特征,FM通过点积和隐向量的形式能够得到二阶特征组合,深度学习模型可以得到高阶high-order的特征组合,因此论文把DNN模型引入了FM模型中,根据FM得到的特征隐向量,通过DNN得到其高阶的特征组合。
1.2 DeepFM模型结构
1.2.1 深度模型结构
深度排序模型可以理解为在传统的算法基础上在加上一个DNN的模型,使其能够得到高维的组合特征,它的结构可以分为两类:并行结构和串行结构。串行结构的算法主要有,FNN,FNM,AFM等等,并行结构的主要算法就是wide&deep模型和本章所介绍的DeepFM模型
1)串行结构
2)并行结构
对于并行结构,之前谷歌提出过wide&deep模型,其FM function部分为LR,但是线性模型只有一阶的特征,高阶的特征需要人工组合,因此在特征非常多的情况下,特征工程量会非常大,因此DeepFM在此基础上发展而来,把LR转变成了FM,可以自动进行特征的组合。
1.2.2 DeepFM模型结构
1、 DeepFM模型包含FM和DNN两部分,FM模型可以抽取low-order特征,DNN可以抽取high-order特征。
2、无需Wide&Deep模型人工特征工程。由于输入仅为原始特征,而且FM和DNN共享输入向量特征,DeepFM模型训练速度很快。
DeepFM中的FM和DNN的部分共享权重参数,假设特征为i,为一阶1-order的权重向量,其特征之间的交互隐向量为,输入到FM模型中就是特征组合的二阶表示,输入到DNN部分就是特征组合的高阶表示。
其最后的输出为:
其中的+号为concat连接并不是叠加。
1.2.2.1 FM部分模型
FM部分模型详细参考FM。在DeepFM是以神经网络的视角来看FM的模型,对于一个样本来说在Output Units层的输出变成了一个向量,而非直接预估的数值。在论文中还有一个与FM的差别就是,FM部分把偏置给去除了,其表达式为:
1.2.2.1 Deep部分模型
这是一个简单的前向反馈神经网络,用于学习显示高维的交叉特征,推荐系统的输入是高维稀疏,连续和类别混合,在Field上的数据,因此需要转化成低维稠密的向量才能进入DNN模型进行学习。
作者设计了一个子网络来学习低维连续稠密的数据,其方法为:
1)不管每个Field的特征是否相同,但embedding后其特征向量维度均为k
2)利用FM中隐向量来作为子网络的初始化权重来获得Field的隐向量。每个Field具有一个隐向量,并输入这个特征想到到DNN来获取最后的向量。
因此输入DNN模型中的初始向量为Field的域向量,其表示为,其中n为Field的个数,为第i个Field的嵌入向量。这个向量就作为DNN模型的输入向量。
2 tensorflow实现
2.1 对DeepFM中Field和特征的理解
我个人的理解感觉论文中的特征就是one-hot编码之后的特征,而Field就相当于一个列,举个例子,比如在FM所展现的例子所示:
“中国机长”,“陈情令”,“大秦帝国”就是三个特征,但他们属于同一个Field,就是“节目名”。
正如论文中所说,Field可以分为分类和连续两种类型,分类类型要经过one-hot编码,而连续类型就是自己本身的数值value。这样,每个样本都可以表示为,其中如果是category类型的,那么通过one-hot编码,就是个高维稀疏的矩阵,每一列都是一个特征;若是连续类型的Field就是它的特征,其value为原来的特征值,如上图中的“喜爱程度”。
2.2实现代码
2.2.1数据预处理
首先对数据进行预处理,观察数据的类型,根据数据类型分成“连续特征”,”离散特征“,”label“列,并建立特征词典,对不同的特征进行编号,连续特征一整列都使用相同的特征编号,代码如下
def get_feature_dict(df,num_col):
'''
特征向量字典,其格式为{field:{特征:编号}}
:param df:
:return: {field:{特征:编号}}
'''
feature_dict={}
total_feature=0
df.drop('rate',axis=1,inplace=True)
for col in df.columns:
if col in num_col:
feature_dict[col]=total_feature
total_feature += 1
else:
unique_feature = df[col].unique()
feature_dict[col]=dict(zip(unique_feature,range(total_feature,total_feature+len(unique_feature))))
total_feature += len(unique_feature)
return feature_dict,total_feature
def get_data(df,feature_dict):
'''
返回二位数组,xi表示特征的索引,xv表示特征的值,y表示标签值
:param df:
:return:xi,xv,y
'''
y = df[['rate']].values
dd = df.drop('rate',axis=1)
df_index = dd.copy()
df_value = dd.copy()
for col in df_index.columns:
if col in num_col:
df_index[col] = feature_dict[col]
else:
df_index[col] = df_index[col].map(feature_dict[col])
df_value[col] = 1.0
xi=df_index.values.tolist()
xv=df_value.values.tolist()
return xi,xv,y
2.2.2Embedding处理
参考资料
1、论文
2、https://zhuanlan.zhihu.com/p/67795161
3、https://www.jianshu.com/p/6f1c2643d31b
4、https://zhuanlan.zhihu.com/p/57873613