算法介绍

左边deep network,右边FM,所以叫deepFM

包含两个部分:

  • Part1: FM(Factorization machines),因子分解机部分

在传统的一阶线性回归之上,加了一个二次项,可以表达两两特征的相互关系。

这里的公式可以简化,减少计算量,下图来至于网络。

  • Part2: Deep部分

deep部分是多层dnn网络。

算法实现

实现部分,用Keras实现一个DeepFM·清尘·《FM、FMM、DeepFM整理(pytorch)》

讲的比较清楚,这里引用keras实现来说明。

整体的网络结构:

特征编码

特征可以分为3类:

  • 连续型field,比如数字类型特征
  • 单值离散型特征,比如gender,可选为male、female
  • 多值离散型,比如tag,可以有多个

连续型field,可以拼接到一起,dense数据。

单值,多值field进行Onehot后,可见单值离散field对应的独热向量只有一位取1,而多值离散field对应的独热向量有多于一位取1,表示该field可以同时取多个特征值。

label shop_score gender=m gender=f interest=f interest=c
0 0.2 1 0 1 1
1 0.8 0 1 0 1

FM 部分

看公式:

先算 FM一次项:

  • 连续型field 可以用Dense(1)层实现
  • 单值离散型field 用Embedding(n,1), n是分类中值的个数
  • 多值离散型field可以同时取多个特征值,为了batch training,必须对样本进行补零padding。同样可以用Embedding实现,因为有多个Embedding,可以取下平均值。

然后计算FM二次项,这里理解比较费劲一点。

·清尘·《FM、FMM、DeepFM整理(pytorch)》 深入浅出的讲明白了这个过程,大家可以参见。

我们来看具体实现方面,这里的DeepFM模型CTR预估理论与实战 讲解更容易理解。

假设只有前面的C1和C2两个Category的特征,词典大小还是3和2。假设输入还是C1=2,C2=2(下标从1开始),则Embedding之后为V2=[e21,e22,e23,e24]和V5=[e51,e52,e53,e54]。

因为xi和xj同时不为零才需要计算,所以上面的公式里需要计算的只有i=2和j=5的情况。因此:

扩展到多个,比如C1,C2,C3,需要算内积

怎么用用矩阵乘法一次计算出来呢?我们可以看看这个

对应的代码就是

       square_of_sum = tf.square(reduce_sum(
concated_embeds_value, axis=1, keep_dims=True))
sum_of_square = reduce_sum(
concated_embeds_value * concated_embeds_value, axis=1, keep_dims=True)
cross_term = square_of_sum - sum_of_square
cross_term = 0.5 * reduce_sum(cross_term, axis=2, keep_dims=False)

其中concated_embeds_value是拼接起来的embeds_value。

Deep部分

DNN比较简单,FM的输入和DNN的输入都是同一个group_embedding_dict。

使用movielens 来测试

下载ml-100k 数据集

wget http://files.grouplens.org/datasets/movielens/ml-100k.zip
unzip ml-100k.zip

安装相关软件包,sklearn,deepctr

导入包:

import pandas
import pandas as pd
import sklearn
from sklearn.metrics import log_loss, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
import tensorflow as tf
from tqdm import tqdm from deepctr.models import DeepFM
from deepctr.feature_column import SparseFeat, VarLenSparseFeat, get_feature_names
import numpy as np

读取评分数据:

u_data = pd.read_csv("ml-100k/u.data", sep='\t', header=None)
u_data.columns = ['user_id', 'movie_id', 'rating', 'timestamp']

有评分的设置为1,随机采用未评分的

def neg_sample(u_data, neg_rate=1):
# 全局随机采样
item_ids = u_data['movie_id'].unique()
print('start neg sample')
neg_data = []
# 负采样
for user_id, hist in tqdm(u_data.groupby('user_id')):
# 当前用户movie
rated_movie_list = hist['movie_id'].tolist()
candidate_set = list(set(item_ids) - set(rated_movie_list))
neg_list_id = np.random.choice(candidate_set, size=len(rated_movie_list) * neg_rate, replace=True)
for id in neg_list_id:
neg_data.append([user_id, id, -1, 0])
u_data_neg = pd.DataFrame(neg_data)
u_data_neg.columns = ['user_id', 'movie_id', 'rating', 'timestamp']
u_data = pandas.concat([u_data, u_data_neg])
print('end neg sample')
return u_data

读取item数据

u_item = pd.read_csv("ml-100k/u.item", sep='|', header=None, error_bad_lines=False)
genres_columns = ['Action', 'Adventure',
'Animation',
'Children', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Fantasy',
'Film_Noir', 'Horror', 'Musical', 'Mystery', 'Romance', 'Sci-Fi',
'Thriller', 'War', 'Western'] u_item.columns = ['movie_id', 'title', 'release_date', 'video_date', 'url', 'unknown'] + genres_columns

处理genres并删除单独的genres列

     genres_list = []
for index, row in u_item.iterrows():
genres = []
for item in genres_columns:
if row[item]:
genres.append(item)
genres_list.append('|'.join(genres)) u_item['genres'] = genres_list
for item in genres_columns:
del u_item[item]

读取用户信息:

  # user id | age | gender | occupation(职业) | zip code(邮编,地区)
u_user = pd.read_csv("ml-100k/u.user", sep='|', header=None)
u_user.columns = ['user_id', 'age', 'gender', 'occupation', 'zip']

join到一起:

 data = pandas.merge(u_data, u_item, on="movie_id", how='left')
data = pandas.merge(data, u_user, on="user_id", how='left')
data.to_csv('ml-100k/data.csv', index=False)

处理特征:

sparse_features = ["movie_id", "user_id",
"gender", "age", "occupation", "zip", ] data[sparse_features] = data[sparse_features].astype(str)
target = ['rating'] # 评分
data['rating'] = [1 if int(x) >= 0 else 0 for x in data['rating']]

先特征编码:

for feat in sparse_features:
lbe = LabelEncoder()
data[feat] = lbe.fit_transform(data[feat])

处理genres特征,一个movie有多个genres,先拆分,然后编码为数字,注意是从1开始;由于每个movie的genres长度不一样,可以计算最大长度,位数不足的后面补零(pad_sequences,在post补0)

	 def split(x):
key_ans = x.split('|')
for key in key_ans:
if key not in key2index:
# Notice : input value 0 is a special "padding",so we do not use 0 to encode valid feature for sequence input
key2index[key] = len(key2index) + 1
return list(map(lambda x: key2index[x], key_ans)) key2index = {}
genres_list = list(map(split, data['genres'].values))
genres_length = np.array(list(map(len, genres_list)))
max_len = max(genres_length)
# Notice : padding=`post`
genres_list = pad_sequences(genres_list, maxlen=max_len, padding='post', )

构建deepctr的特征列,主要分为两类特征,一是定长的SparseFeat,稀疏的类别特征,二是可变长度的VarLenSparseFeat,像genres这样的包含多个的。

	   fixlen_feature_columns = [SparseFeat(feat, data[feat].nunique(), embedding_dim=4)
for feat in sparse_features] use_weighted_sequence = False
if use_weighted_sequence:
varlen_feature_columns = [VarLenSparseFeat(SparseFeat('genres', vocabulary_size=len(
key2index) + 1, embedding_dim=4), maxlen=max_len, combiner='mean',
weight_name='genres_weight')] # Notice : value 0 is for padding for sequence input feature
else:
varlen_feature_columns = [VarLenSparseFeat(SparseFeat('genres', vocabulary_size=len(
key2index) + 1, embedding_dim=4), maxlen=max_len, combiner='mean',
weight_name=None)] # Notice : value 0 is for padding for sequence input feature linear_feature_columns = fixlen_feature_columns + varlen_feature_columns
dnn_feature_columns = fixlen_feature_columns + varlen_feature_columns feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)

封装训练数据,先shuffle(乱排)数据,然后生成dict input数据。

data = sklearn.utils.shuffle(data)
train_model_input = {name: data[name] for name in sparse_features} #
train_model_input["genres"] = genres_list

构建DeepFM模型,由于目标值是0,1,因此采用binary,损失函数用binary_crossentropy

model = DeepFM(linear_feature_columns, dnn_feature_columns, task='binary')

model.compile(optimizer=tf.keras.optimizers.Adam(), loss='binary_crossentropy',
metrics=['AUC', 'Precision', 'Recall'])
model.summary()

训练模型:

model.fit(train_model_input, data[target].values,
batch_size=256, epochs=20, verbose=2,
validation_split=0.2
)

开始训练:

Epoch 1/20
625/625 - 3s - loss: 0.5081 - auc: 0.8279 - precision: 0.7419 - recall: 0.7695 - val_loss: 0.4745 - val_auc: 0.8513 - val_precision: 0.7563 - val_recall: 0.7936
Epoch 2/20
625/625 - 2s - loss: 0.4695 - auc: 0.8538 - precision: 0.7494 - recall: 0.8105 - val_loss: 0.4708 - val_auc: 0.8539 - val_precision: 0.7498 - val_recall: 0.8127
Epoch 3/20
625/625 - 2s - loss: 0.4652 - auc: 0.8564 - precision: 0.7513 - recall: 0.8139 - val_loss: 0.4704 - val_auc: 0.8545 - val_precision: 0.7561 - val_recall: 0.8017
Epoch 4/20
625/625 - 2s - loss: 0.4624 - auc: 0.8579 - precision: 0.7516 - recall: 0.8146 - val_loss: 0.4724 - val_auc: 0.8542 - val_precision: 0.7296 - val_recall: 0.8526
Epoch 5/20
625/625 - 2s - loss: 0.4607 - auc: 0.8590 - precision: 0.7521 - recall: 0.8173 - val_loss: 0.4699 - val_auc: 0.8550 - val_precision: 0.7511 - val_recall: 0.8141
Epoch 6/20
625/625 - 2s - loss: 0.4588 - auc: 0.8602 - precision: 0.7545 - recall: 0.8165 - val_loss: 0.4717 - val_auc: 0.8542 - val_precision: 0.7421 - val_recall: 0.8265
Epoch 7/20
625/625 - 2s - loss: 0.4574 - auc: 0.8610 - precision: 0.7535 - recall: 0.8192 - val_loss: 0.4722 - val_auc: 0.8547 - val_precision: 0.7549 - val_recall: 0.8023
Epoch 8/20
625/625 - 2s - loss: 0.4561 - auc: 0.8619 - precision: 0.7543 - recall: 0.8201 - val_loss: 0.4717 - val_auc: 0.8548 - val_precision: 0.7480 - val_recall: 0.8185
Epoch 9/20
625/625 - 2s - loss: 0.4531 - auc: 0.8643 - precision: 0.7573 - recall: 0.8210 - val_loss: 0.4696 - val_auc: 0.8583 - val_precision: 0.7598 - val_recall: 0.8103
Epoch 10/20
625/625 - 2s - loss: 0.4355 - auc: 0.8768 - precision: 0.7787 - recall: 0.8166 - val_loss: 0.4435 - val_auc: 0.8769 - val_precision: 0.7756 - val_recall: 0.8293
Epoch 11/20
625/625 - 2s - loss: 0.4093 - auc: 0.8923 - precision: 0.7915 - recall: 0.8373 - val_loss: 0.4301 - val_auc: 0.8840 - val_precision: 0.7806 - val_recall: 0.8390
Epoch 12/20
625/625 - 2s - loss: 0.3970 - auc: 0.8988 - precision: 0.7953 - recall: 0.8497 - val_loss: 0.4286 - val_auc: 0.8867 - val_precision: 0.7903 - val_recall: 0.8299
Epoch 13/20
625/625 - 2s - loss: 0.3896 - auc: 0.9029 - precision: 0.8001 - recall: 0.8542 - val_loss: 0.4253 - val_auc: 0.8888 - val_precision: 0.7913 - val_recall: 0.8322
Epoch 14/20
625/625 - 2s - loss: 0.3825 - auc: 0.9067 - precision: 0.8038 - recall: 0.8584 - val_loss: 0.4205 - val_auc: 0.8917 - val_precision: 0.7885 - val_recall: 0.8506
Epoch 15/20
625/625 - 2s - loss: 0.3755 - auc: 0.9102 - precision: 0.8074 - recall: 0.8624 - val_loss: 0.4204 - val_auc: 0.8940 - val_precision: 0.7868 - val_recall: 0.8607
Epoch 16/20
625/625 - 2s - loss: 0.3687 - auc: 0.9136 - precision: 0.8117 - recall: 0.8653 - val_loss: 0.4176 - val_auc: 0.8956 - val_precision: 0.8097 - val_recall: 0.8236
Epoch 17/20
625/625 - 2s - loss: 0.3617 - auc: 0.9170 - precision: 0.8155 - recall: 0.8682 - val_loss: 0.4166 - val_auc: 0.8966 - val_precision: 0.8056 - val_recall: 0.8354
Epoch 18/20
625/625 - 2s - loss: 0.3553 - auc: 0.9201 - precision: 0.8188 - recall: 0.8716 - val_loss: 0.4168 - val_auc: 0.8977 - val_precision: 0.7996 - val_recall: 0.8492
Epoch 19/20
625/625 - 2s - loss: 0.3497 - auc: 0.9227 - precision: 0.8214 - recall: 0.8741 - val_loss: 0.4187 - val_auc: 0.8973 - val_precision: 0.8079 - val_recall: 0.8358
Epoch 20/20
625/625 - 2s - loss: 0.3451 - auc: 0.9248 - precision: 0.8244 - recall: 0.8753 - val_loss: 0.4210 - val_auc: 0.8982 - val_precision: 0.7945 - val_recall: 0.8617

最后我们测试下数据:

 pred_ans = model.predict(train_model_input, batch_size=256)
count = 0
for (i, j) in zip(pred_ans, data['rating'].values):
print(i, j)
count += 1
if count > 10:
break

输出如下:

[0.20468083] 0
[0.1988303] 0
[7.7236204e-05] 0
[0.9439401] 1
[0.76648283] 0
[0.80082995] 1
[0.7689271] 0
[0.8515004] 1
[0.93311656] 1
[0.40019292] 0
[0.60735244] 0

参考


作者:Jadepeng

出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

推荐算法之: DeepFM及使用DeepCTR测试的更多相关文章

  1. 将 Book-Crossing Dataset 书籍推荐算法中 CVS 格式测试数据集导入到MySQL数据库

    本文内容 最近看<写给程序员的数据挖掘指南>,研究推荐算法,书中的测试数据集是 Book-Crossing Dataset 提供的亚马逊用户对书籍评分的真实数据.推荐大家看本书,写得不错, ...

  2. 吃透论文——推荐算法不可不看的DeepFM模型

    大家好,我们今天继续来剖析一些推荐广告领域的论文. 今天选择的这篇叫做DeepFM: A Factorization-Machine based Neural Network for CTR Pred ...

  3. Mahout推荐算法API详解

    转载自:http://blog.fens.me/mahout-recommendation-api/ Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, ...

  4. 【笔记3】用pandas实现矩阵数据格式的推荐算法 (基于用户的协同)

    原书作者使用字典dict实现推荐算法,并且惊叹于18行代码实现了向量的余弦夹角公式. 我用pandas实现相同的公式只要3行. 特别说明:本篇笔记是针对矩阵数据,下篇笔记是针对条目数据. ''' 基于 ...

  5. 美团网基于机器学习方法的POI品类推荐算法

    美团网基于机器学习方法的POI品类推荐算法 前言 在美团商家数据中心(MDC),有超过100w的已校准审核的POI数据(我们一般将商家标示为POI,POI基础信息包括:门店名称.品类.电话.地址.坐标 ...

  6. 转】Mahout推荐算法API详解

    原博文出自于: http://blog.fens.me/mahout-recommendation-api/ 感谢! Posted: Oct 21, 2013 Tags: itemCFknnMahou ...

  7. SVD/SVD++实现推荐算法

    奇异值分解(Singular Value Decomposition,以下简称SVD)是在机器学习领域广泛应用的算法,它不仅可以用于降维算法中的特征分解,还可以用于推荐系统,以及自然语言处理等领域. ...

  8. [转]Mahout推荐算法API详解

    Mahout推荐算法API详解 Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, Pig, HBase, Sqoop, Mahout, Zookeepe ...

  9. 推荐算法之 slope one 算法

    1.示例引入 多个吃货在某美团的某家饭馆点餐,如下两道菜: 可乐鸡翅: 红烧肉: 顾客吃过后,会有相关的星级评分.假设评分如下: 评分 可乐鸡翅 红烧肉 小明 4 5 小红 4 3 小伟 2 3 小芳 ...

随机推荐

  1. IDAPython 安装和设置(windows+linux)

    安装步骤: 我采用的是IDA 6.8 windows安装: 机器上安装了Python,到Python的官网—http://www.python.org/getit/下载2.7的安装包.注意对应操作系统 ...

  2. Java 后端开发常用的 10 种第三方服务

    请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/itwanger 已收录,里面还有我精心为你准备的一线大厂面试题. 严格意义上 ...

  3. Python日志功能与处理逻辑

    前言 在应用程序执行过程中,我们希望通过规范格式输出程序执行的详细信息,这时我们需要用到日志功能.在Python语言中,有个內建模块logging能够很好的实现日志功能.整体来说,logging配置可 ...

  4. docker自己部署一个项目

    老祖宗的话说得好呀:实践出真知 自己打个简单的镜像运行  遇到了一堆破问题 学习docker主要在菜鸟教程  https://www.runoob.com/docker/docker-containe ...

  5. 如何下载jmeter旧版本

    推荐先用旧版本做好测试基本操作,因为高版本不适合做压力测试,需要证书,有点麻烦. 1.百度或直接打开jmeter官网:https://jmeter.apache.org/ 2.向下拖到Archives ...

  6. python ---倒酒!!

    #!/usr/bin/env python3# -*- coding: utf-8 -*-import numbersimport numpyimport math'''三个容器分别为12升.8升.5 ...

  7. 基础篇:JAVA资源之IO、字符编码、URL和Spring.Resource

    目录 1 JAVA.IO字节流 2 JAVA.IO字符流 3 乱码问题和字符流 4 字符集和字符编码的概念区分 5 URI概念的简单介绍 6 URL概念及与URL的区别 7 Spring.Resour ...

  8. React 服务端渲染方案完美的解决方案

    最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法.在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢? 什么是服务 ...

  9. Hadoop框架:HDFS读写机制与API详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.读写机制 1.数据写入 客户端访问NameNode请求上传文件: NameNode检查目标文件和目录是否已经存在: NameNode响应客 ...

  10. LVM的简单使用及常用的命令总结

    Lvm的简单使用及常用的命令总结 centos7中默认使用的是xfs文件系统,此文件系统的特点,可以另外查找资料,这里说一下对文件系统的扩容: 1.先看一下没扩容之前的分区大小 2.添加一块新磁盘,并 ...