· 流程解析
关于DeepFM理论的说明,很多博客或论文已有说明,这里就不再赘述。下面主要是说一下模型如何使用以及 在整个过程中,算法对数据做了些什么事。
首先还是这张图:
一眼看过去,超级复杂是不是?确实,但是别慌,我们一小块一小块地看。
1、Sparse Feature框框
Sparse Feature是指离散型变量。比如现在我有数据:xx公司每个员工的姓名、年龄、岗位、收入的表格,那么年龄和岗位就属于离散型变量,而收入则称为连续型变量。这从字面意思也能够理解。
好,现在Sparse Feature框里表示的是将每个特征经过 one-hot编码后拼接在一起的 稀疏长向量,黄色的点表示 某对象在该特征的取值中属于该位置的值。
2、Dense Embedding框框
把这些离散型变量经过热编码后又想干嘛?这个长长的向量只有0,1两种取值,并且非常稀疏,如果直接采用权重去加和,将会丢失很多权重,这样会造成最终结果不准确。所以,得想个办法把这个稀疏向量变得稠密一些。在机器学习中关于对离散值的数据预处理有很多种方式,常见的有 数据分箱、 嵌入向量等,这个Dense Embedding就是指的将离散型变量嵌入为连续型变量。什么意思呢?
还是上面说的那个表格,比如年龄共有50个去重数值,岗位有100个去重数值,现在是两个特征。那么经过Embedding之后,年龄就变为了50 x m的矩阵,岗位就变成了100 x m的矩阵,这个m是指嵌入向量的维数,一般取4、8、16。下面的图示可能会比较直观一些:
最终表格就变成了这样:
也就是说,最终入模的数据表长这样:
比如"漩涡鸣人"的特征向量可能就是这样的:
(x1,x2,...,xm,x1,x2,...,xm,...,···,x1,x2,...,xn)
x1,x2,...,xm 表示他的年龄的Embedding向量,x1,x2,...,xm 表示他的岗位的Embedding向量,... 表示他的其他属性的Embedding向量,x1,x2,...,xn 表示他的收入等其他连续型特征的归一化或标准化后的值。
一句话,就是拼接起来。
实际上,在DeepFM中,对分析对象也作了Embedding的,这里为了讲明Embedding的原因和原理,故此展示。
3、FM Layer框框
先给出FM的公式:
看黑色线(Normal Connection)连接的两端,一边是Sparse Feature框框(独热编码的特征字段),一边是Addition结点:亦即公式中的
总而言之,这个框就表示FM的公式。
两个问题:
为什么二阶项输入的是Embedding向量而不是独热编码向量?其实不是他输入的是Embedding向量,而是这个输入的Embedding向量之积就等于后面两个求和的这一坨:仔细想想,xi,xj是独热编码向量,
为什么二阶项的权重要用其实按理说,当两个特征存在交叉,我们用两个特征的取值相乘,并赋予权重,才是正常思路。亦即可以这样表示二阶项:w·(x1·x2),注意这里的w为矩阵。而通常x1,x2的不同取值都是几十上百甚至上千,所以x1·x2的维数也非常大,导致w矩阵的规模也会很大,所以这里利用矩阵的" 奇异值分解"将w矩阵从高维分解为 两个低维的矩阵相乘,以提高迭代速度和节省空间。即w=
事实上,在Sparse Feature框和Dense Embedding框之间的连接是这样的:
结合代数学知识,再由这个网络结构就能得出 公式中后面两个求和项那一坨的值=两个特征的Embedding向量相乘。
4、Hidden Layer框框
由前述分析可知,FM的作用是为了考虑 特征之间的交互影响,那么按理说仅一个FM结构就足以预测结果了,为什么还要加上这个DNN结构呢?
实际上,FM的作用仅是考虑了特征之间的显式交互影响,或者说只考虑到第一个层面的交互影响。而DNN就不同了,你看它有三层结构,每层又有若干个结点,节点之间又是全连接,当输入特征之后,中间经过ReLu激活函数的嵌套又嵌套,都不知道把特征之间交叉乘了多少次了,完全无法描述,但这个"无法描述"它是有用的(因为最后都要去拟合target),也就是所说的隐式交互影响,或者说,是个黑盒。
5、Outputs Units框框
现在,对象的特征先经过one-hot编码变为稀疏长向量,再通过Embedding变为统一长度的稠密向量,然后在FM结构中显示交互作用,以及在DNN结构中隐式交互作用,最后水到渠成就该输出预测目标值了。即:
如果是分类任务,那么就将两者的值相加再输入Sigmoid函数输出类别概率大小。
如果是回归任务,那么就直接将两者的值相加。
· 建模演示
关于算法的代码实现细节,可参考GitHub上的源码。下面采用原作者使用的开源数据集测试:
1、DeepFM作分类
import pandas as pd
from sklearn.metrics import log_loss, roc_auc_score # 模型评估指标:对数损失函数、ROC_AUC
from sklearn.model_selection import train_test_split # 模型训练集和测试集划分
from sklearn.preprocessing import LabelEncoder, MinMaxScaler # 数据预处理:标签编码、最大最小标准化
from deepctr.models import * # CTR全部模型(也可只导入DeepFM)
from deepctr.feature_column import SparseFeat, DenseFeat, get_feature_names # 特征处理
data = pd.read_csv('./criteo_sample.txt')
sparse_features = ['C' + str(i) for i in range(1, 27)]
dense_features = ['I' + str(i) for i in range(1, 14)]
# 缺失值填充
data[sparse_features] = data[sparse_features].fillna('-1', )
data[dense_features] = data[dense_features].fillna(0, )
# 拟合目标为数据的标签列
target = ['label']
data
# 1.Label Encoding for sparse features,and do simple Transformation for dense features
for feat in sparse_features:
# 对离散特征先作了一个标签编码(自然数编码)
lbe = LabelEncoder()
data[feat] = lbe.fit_transform(data[feat])
# 对连续特征作最大最小标准化
mms = MinMaxScaler(feature_range=(0, 1))
data[dense_features] = mms.fit_transform(data[dense_features])
data
# 2.count #unique features for each sparse field,and record dense feature field name
fixlen_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].max() + 1, embedding_dim=4)
for i, feat in enumerate(sparse_features)] + [DenseFeat(feat, 1, )
for feat in dense_features]
# vocabulary_size:embedding向量矩阵的行数;embedding_dim:embedding向量的维数
dnn_feature_columns = fixlen_feature_columns
linear_feature_columns = fixlen_feature_columns
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)
理论上,vocabulary_size应为各字段取不同值的数量,但这里因为先对离散特征作了一个标签编码,也就是说特征取值最小为0,最大为它的不同值的数量-1,所以vocabulary_size才有此式。
# 3.generate input data for model
train, test = train_test_split(data, test_size=0.2, random_state=2020)
train_model_input = {name: train[name] for name in feature_names}
test_model_input = {name: test[name] for name in feature_names}
# 4.Define Model,train,predict and evaluate
model = DeepFM(linear_feature_columns, dnn_feature_columns, task='binary')
model.compile("adam", "binary_crossentropy",
metrics=['binary_crossentropy'], )
#adam:优化器的一种;binary_crossentropy:二值交叉熵(损失函数的一种)
history = model.fit(train_model_input, train[target].values,
batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
# batch_size:批梯度下降的每次样本数;epochs:迭代次数;validation_split:验证集比例;verbose:输出训练日志
pred_ans = model.predict(test_model_input, batch_size=256)
print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))
pd.DataFrame(pred_ans)
pred_ans表示的是预测该样本为正类的概率值,其顺序与数据输入顺序一致。
2、DeepFM作回归
import pandas as pd
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from deepctr.models import DeepFM
from deepctr.feature_column import SparseFeat,get_feature_names
data = pd.read_csv("./movielens_sample.txt")
data
sparse_features = ["movie_id", "user_id", "gender", "age", "occupation", "zip"]
target = ['rating']
# 1.Label Encoding for sparse features,and do simple Transformation for dense features
for feat in sparse_features:
lbe = LabelEncoder()
data[feat] = lbe.fit_transform(data[feat])
# 2.count #unique features for each sparse field
fixlen_feature_columns = [SparseFeat(feat, data[feat].max() + 1,embedding_dim=4)
for feat in sparse_features]
linear_feature_columns = fixlen_feature_columns
dnn_feature_columns = fixlen_feature_columns
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)
data
# 3.generate input data for model
train, test = train_test_split(data, test_size=0.2, random_state=2020)
train_model_input = {name:train[name].values for name in feature_names}
test_model_input = {name:test[name].values for name in feature_names}
# 4.Define Model,train,predict and evaluate
model = DeepFM(linear_feature_columns, dnn_feature_columns, task='regression')
model.compile("adam", "mse", metrics=['mse'], )
history = model.fit(train_model_input, train[target].values,
batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
pred_ans = model.predict(test_model_input, batch_size=256)
print("test MSE", round(mean_squared_error(test[target].values, pred_ans), 4))
注意这里的"task"参数设为"regression",即回归。
pd.DataFrame(pred_ans).head()
pred_ans表示的是预测该样本拟合的目标函数值,其顺序与数据输入顺序一致。
3、User和Item的Embedding向量提取
feature_columns = [SparseFeat('user_id',120,),SparseFeat('movie_id',60,)]
def get_embedding_weights(dnn_feature_columns,model):
embedding_dict = {}
for fc in dnn_feature_columns:
if hasattr(fc,'embedding_name'):
if fc.embedding_name is not None:
name = fc.embedding_name
else:
name = fc.name
embedding_dict[name] = model.get_layer("sparse_emb_"+name).get_weights()[0]
return embedding_dict
embedding_dict = get_embedding_weights(feature_columns,model)
user_id_emb = embedding_dict['user_id']
movie_id_emb = embedding_dict['movie_id']
pd.DataFrame(user_id_emb)
pd.DataFrame(movie_id_emb)
根据FM的公式可知,二阶项的数值越大,则拟合函数值也越大。
所以,在一般的推荐系统中,欲拟合的函数值为用户对产品的评分,评分越大,说明该用户越喜欢该产品,那么就可给该用户推荐该产品。而二阶项的数值又等于两特征的Embedding向量积,于是就 可把User-Embedding矩阵与Item-Embedding矩阵的转置相乘,得到User-Item"评分"矩阵,进而推荐产品。
· DeepFM的优点
DeepFM的FM和Deep部分共享相同的输入,可以提高训练效率,不需要额外的特征工程,用FM建模低阶的特征组合,用DNN建模高阶的特征组合,因此可以同时从原始特征中学习到高阶和低阶的特征交互。
DeepFM也是被广泛应用在点击率预测中的深度学习模型,主要关注如何学习用户行为(user behavior)的组合特征(feature interactions),从而最大化推荐系统CTR。
Original: https://blog.csdn.net/Jeremiah_/article/details/120740877
Author: Eureka丶
Title: DeepFM详细流程解析及建模演示
相关阅读
Title: 【已解决】ValueError: Cannot feed value of shape (1, 6) for Tensor h:0, which has shape (None, 7)涉及其他报错问题
1 问题分析
通过报错信息,我们可以分析出feed_dict的值与定义的输入数据张量格式不匹配,feed_dict的维度比如是3维的,那么shape是(32,32,3),而如果输入的数据维度是4维的,shape是(None,32,32,3)。所以,导致出现了维度不匹配的问题。
在我的程序中,本应输入一个7维的数据,但是看了下输入的数据只有6维,通过修改数据的格式解决了此问题。
note: 出现这种错误是唯独不匹配导致的错误,这个错误的根源要知道。
; 2 解决方法
2.1 通过修改输入的数据使数据的维度保持一致。
2.2 修改程序使其匹配
因为我的程序运行过程中出的错不需要改程序,所以这里借鉴一下别人出现的错误,方便大家理解。主要原因是维度不匹配的问题。
image = tf.reshape(image_data, [1, 32, 32, 3])
输出要经过np.sum函数,才能得到类别编号
file_label = np.sum(file_label)
另一位出现错误的解决方法
image形状为(64,64,3)。
您的输入占位符_x的形状为(?,64,64,3).
问题是您正在为占位符提供不同形状的值。
您必须为它提供(1,64,64,3)= 一批 1 张图像的值。
只需将您的价值重塑image为大小为 1 的批次。
image = array(img).reshape(1,64,64,3)
3 其他报错
除此之外,在跑程序的过程中,出现了其他的错误,都对应找到了解决方法,先走都放下面供大家参考。
3.1 报错:Ignore above cudart dlerror if you do not have a GPU set up on your machine
解决方法:去pycharm–>setting中下载TensorFlow不同的版本,因为TensorFlow分为不同的版本,有CPU版本的还有GPU版本的,看自己需要下载相应的版本。
其他解决办法1:
如果电脑没有GPU则忽略此错误提示并退回到CPU版本, 重新安装TensorFlow的CPU版本
其他解决办法2:
针对tensorflow-gpu版本使用时报错, 各种dll文件找不到的问题进行了总结。
其他解决方法3:
其他提到的是,Keras和TensorFlow-CPU或者TensorFlow-GPU的版本要一致,因为我下载的版本是一致的,都是2.8.0,所以不知道有没有可能是这个原因,可以试一下。
; 3.2 报错:AttributeError: module 'tensorflow' has no attribute 'reset_default_graph'
在memory.py文件里面修改内容:
把 tf.reset_default_graph()
改为:
tf.compat.v1.reset_default_graph()
如图所示:
其他解决方法1
import tensorflow as tf
tf.compat.v1.reset_default_graph()
这个解决方法是 在运行的程序中加入import的方法,解决此问题,我是在memory文件里面修改的文件。
3.3 报错:AttributeError: module 'tensorflow' has no attribute 'placeholder'
在源代码中, placeholder
前面是没有 .compat.v1
的,当我把所有memory文件中的无法识别的前面都加上 .compat.v1
便解决了。
其他解决方法1:
将语句 X=tf.placeholder("float") Y=tf.placeholder("float")
改为:X=tf.compat.v1.placeholder("float")
Y=tf.compat.v1.placeholder("float")
编译通过
作品将相应的语法改成加上了 .compat.v1
的方法,举一反三,我也是按照相同的思路改的。
; 3.4 报错:ValueError: Cannot feed value of shape (1, 6) for Tensor h:0, which has shape (None, 7)
这个问题就是刚开始提到的问题,此问题是数据和TensorFlow模型的维度不一样导致的。
Original: https://blog.csdn.net/chengxuyuan99/article/details/123872411
Author: 小瑞o
Title: 【已解决】ValueError: Cannot feed value of shape (1, 6) for Tensor h:0, which has shape (None, 7)涉及其他报错问题