背景
特征之间是关联的,线性模型中需要人工的特征组合,表达能力受限
-
one-hot编码带来特征空间暴增,数据稀疏,需要的参数量暴增
在数据很稀疏的情况下,满足xi,xj都不为0的情况非常少,这样将导致wij无法通过训练得出。
FM
特点:
可以自动进行特征组合
数据稀疏的情况下表现依然良好
做法:
-
利用矩阵分解的思想,引入辅助V矩阵,对W进行建模
实现:
w0 = tf.Variable(tf.zeros([1]))
w = tf.Variable(tf.zeros([p]))
v = tf.Variable(tf.random_normal([k, p], mean=0, stddev=0.01))
linear_terms = tf.add(w0, tf.reduce_sum(tf.multiply(w, x), 1, keep_dims=True)) # n * 1
pair_interactions = 0.5 * tf.reduce_sum(
tf.subtract(
tf.pow(tf.matmul(x, tf.transpose(v)), 2),
tf.matmul(tf.pow(x, 2), tf.transpose(tf.pow(v, 2)))
), axis=1, keep_dims=True)
y_hat = tf.add(linear_terms, pair_interactions)
lambda_w = tf.constant(0.001, name='lambda_w')
lambda_v = tf.constant(0.001, name='lambda_v')
l2_norm = tf.reduce_sum(
tf.add(
tf.multiply(lambda_w, tf.pow(w, 2)),
tf.multiply(lambda_v, tf.pow(v, 2))
)
)
error = tf.reduce_mean(tf.square(y - y_hat))
loss = tf.add(error, l2_norm)
注意:线性项和二次项是直接相加的(标量)
FFM
背景:
- one-hot类型变量会导致严重的数据特征稀疏
做法:
引入field的概念,同一类经过One-Hot编码生成的特征都可以放到同一个field
在FFM中,每一维特征 xi,针对其它特征的每一种field fj,都会学习一个隐向量 。因此,隐向量不仅与特征相关,也与field相关。也就是说,“Day=26/11/15”这个特征与“Country”特征和“Ad_type"特征进行关联的时候使用不同的隐向量,这与“Country”和“Ad_type”的内在差异相符。
不足:
FFM的二次参数有 nfk 个,远多于FM模型的 nk个。
此外,由于隐向量与field相关,FFM二次项并不能够化简,其预测复杂度是 O(kn^2)。
DeepFM
背景:
- FM,FFM实际应用中受限于计算复杂度,一般也就只考虑到 2 阶交叉特征
优点/做法:
不需要人工特征工程
FM 提取低阶组合特征,Deep 提取高阶组合特征,同时学习低阶和高阶的组合特征
FM 模块和 Deep 模块共享 Feature Embedding 部分,参数量少,可以更快的训练
one-hot 之前的特征维度,称之为field_size。
one-hot 之后的特征维度,称之为feature_size。
Embedding层作用:
Embedding 矩阵也就是隐向量 V
输入层是onehot的,特别稀疏,直接全连接的话参数量太大,所以先降维再全连接
FM部分
线性项(Addition Unit反映的是 1 阶的特征):
交叉项(内积单元反映的是 2 阶的组合特征对于预测结果的影响):
注意:此处是向量,fm的输出维度是field_size(一阶) + embedding_size(二阶)
DNN部分
output部分
注意是concat,不是add
# model
self.embeddings = tf.nn.embedding_lookup(self.weights['feature_embeddings'], self.feat_index) # N * F * K
feat_value = tf.reshape(self.feat_value,shape=[-1,self.field_size,1])
self.embeddings = tf.multiply(self.embeddings,feat_value)
print(self.embeddings.shape) #none, field_size, embedding_size
print(self.weights['feature_bias'].shape) #feature_size, 1
# first order term #none, field_size
self.y_first_order = tf.nn.embedding_lookup(self.weights['feature_bias'],self.feat_index)
self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order,feat_value),2)
self.y_first_order = tf.nn.dropout(self.y_first_order,self.dropout_keep_fm[0])
# second order term # None * K
# sum-square-part
self.summed_features_emb = tf.reduce_sum(self.embeddings,1) # None * k
self.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K
# squre-sum-part
self.squared_features_emb = tf.square(self.embeddings)
self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1) # None * K
# second order
self.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square,self.squared_sum_features_emb)
self.y_second_order = tf.nn.dropout(self.y_second_order,self.dropout_keep_fm[1])
# Deep component
self.y_deep = tf.reshape(self.embeddings,shape=[-1, self.field_size * self.embedding_size])
self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[0])
for i in range(0,len(self.deep_layers)):
self.y_deep = tf.add(tf.matmul(self.y_deep,self.weights["layer_%d" %i]), self.weights["bias_%d"%i])
self.y_deep = self.deep_layers_activation(self.y_deep)
self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[i+1])
print("y_deep:" + str(self.y_deep.shape))
#----DeepFM---------
self.out = tf.add(tf.matmul(concat_input, self.weights['concat_projection']), self.weights['concat_bias'])
参考: