偶然看到了【飞桨论文复现挑战赛】,抱着 划水 提升自己的态度,报名了一个推荐赛道的赛题。因为本身已经参加工作了,实际空闲时间不是太多,只能晚上下班或者周末和各位参赛大佬卷上一卷,划划水~
DLRM算法原理
1.模型结构
DeepLearningRecommendationModelforPersonalizationandRecommendationSystems,DLRM是FaceBook于2019年提出的CTR预估算法,推荐或广告相关同学可以阅读一下原论文,也是非常经典的一篇。
论文链接https://arxiv.org/pdf/1906.00091v1.pdf,
除了DLRM模型本身的经典结构,FaceBook还对线上推断做了非常多的工程方面的优化,感兴趣的同学可以去找一下相关博客。
2.实验部分
不得不说,Facebook大佬发文章就NB,DLRM网络结构简单干净,没有任何调参,简简单单的SGD+lr=0.1就打败了DCN。原文所说,“DLRMvsDCNwithoutextensivetuningandnoregularizationisused.”太强了!
4.数据集
本项目采用PaddleRec所提供的Criteo数据集进行复现。
PaddleRec介绍
PaddleRec涵盖了推荐系统的各个阶段,包括内容理解、匹配、召回、排序、多任务、重排序等,但这里我们只关注CTR预估,即排序阶段.该部分在models/rank/路径下,已经实现了deepfm、dnn、ffm、fm等经典CTR算法,每类算法包含静态图和动态图两种训练方式。我们一般选择动态图复现,因为和PyTorch及Tensorflow2等语法上更接近,调试也更方便。
# Step 1, 训练模型
python -u tools/trainer.py -m models/rank/dnn/config.yaml
# Step 2, 预测推断
python -u tools/infer.py -m models/rank/dnn/config.yaml
|--models
|--rank
|--dlrm # 本项目核心代码
|--data # 采样小数据集
|--config.yaml # 采样小数据集模型配置
|--config_bigdata.yaml # Kaggle Criteo 全量数据集模型配置
|--criteo_reader.py # dataset加载类
|--dygraph_model.py # PaddleRec 动态图模型训练类
|--net.py # dlrm 核心算法代码,包括 dlrm 组网等
|--tools # PaddleRec 工具类
如何基于PaddleRec
快速复现
class MLPLayer(nn.Layer):
def __init__(self, input_shape, units_list=None, l2=0.01, last_action=None, **kwargs):
super(MLPLayer, self).__init__(**kwargs)
if units_list is None:
units_list = [128, 128, 64]
units_list = [input_shape] + units_list
self.units_list = units_list
self.l2 = l2
self.mlp = []
self.last_action = last_action
# 堆叠多层 dense 层
for i, unit in enumerate(units_list[:-1]):
if i != len(units_list) - 1:
dense = paddle.nn.Linear(in_features=unit,
out_features=units_list[i + 1],
weight_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Normal(std=1.0 / math.sqrt(unit))))
self.mlp.append(dense)
# ReLU激活函数
relu = paddle.nn.ReLU()
self.mlp.append(relu)
# BatchNorm加速训练
norm = paddle.nn.BatchNorm1D(units_list[i + 1])
self.mlp.append(norm)
else:
dense = paddle.nn.Linear(in_features=unit,
out_features=units_list[i + 1],
weight_attr=paddle.nn.initializer.Normal(std=1.0 / math.sqrt(unit)))
self.mlp.append(dense)
if last_action is not None:
relu = paddle.nn.ReLU()
self.mlp.append(relu)
def forward(self, inputs):
outputs = inputs
for n_layer in self.mlp:
outputs = n_layer(outputs)
return outputs
class DLRMLayer(nn.Layer):
def __init__(self,
dense_feature_dim,
bot_layer_sizes,
sparse_feature_number,
sparse_feature_dim,
top_layer_sizes,
num_field,
sync_mode=None):
super(DLRMLayer, self).__init__()
self.dense_feature_dim = dense_feature_dim
self.bot_layer_sizes = bot_layer_sizes
self.sparse_feature_number = sparse_feature_number
self.sparse_feature_dim = sparse_feature_dim
self.top_layer_sizes = top_layer_sizes
self.num_field = num_field
# 定义 DLRM 模型的 Bot-MLP 层
self.bot_mlp = MLPLayer(input_shape=dense_feature_dim,
units_list=bot_layer_sizes,
last_action="relu")
# 定义 DLRM 模型的 Top-MLP 层
self.top_mlp = MLPLayer(input_shape=int(num_field * (num_field + 1) / 2) + sparse_feature_dim,
units_list=top_layer_sizes)
# 定义 DLRM 模型的 Embedding 层
self.embedding = paddle.nn.Embedding(num_embeddings=self.sparse_feature_number,
embedding_dim=self.sparse_feature_dim,
sparse=True,
weight_attr=paddle.ParamAttr(
name="SparseFeatFactors",
initializer=paddle.nn.initializer.Uniform()))
def forward(self, sparse_inputs, dense_inputs):
# (batch_size, sparse_feature_dim)
x = self.bot_mlp(dense_inputs)
# interact dense and sparse feature
batch_size, d = x.shape
sparse_embs = []
for s_input in sparse_inputs:
emb = self.embedding(s_input)
emb = paddle.reshape(emb, shape=[-1, self.sparse_feature_dim])
sparse_embs.append(emb)
# 拼接数值型特征和 Embedding 特征
T = paddle.reshape(paddle.concat(x=sparse_embs + [x], axis=1), (batch_size, -1, d))
# 进行 vector-wise 特征交叉
Z = paddle.bmm(T, paddle.transpose(T, perm=[0, 2, 1]))
Zflat = paddle.triu(Z, 1) + paddle.tril(paddle.ones_like(Z) * MIN_FLOAT, 0)
Zflat = paddle.reshape(paddle.masked_select(Zflat,
paddle.greater_than(Zflat, paddle.ones_like(Zflat) * MIN_FLOAT)),
(batch_size, -1))
R = paddle.concat([x] + [Zflat], axis=1)
# 交叉特征输入 Top-MLP 进行 CTR 预测
y = self.top_mlp(R)
return y
################# Step 1, git clone code ################
# 当前处于 /home/aistudio 目录, 代码存放在 /home/work/rank/DLRM-Paddle 中
import os
if not os.path.isdir('work/rank/DLRM-Paddle'):
if not os.path.isdir('work/rank'):
!mkdir work/rank
# 国内访问或 git clone 较慢, 利用 hub.fastgit.org 加速
!cd work/rank && git clone https://hub.fastgit.org/Andy1314Chen/DLRM-Paddle.git
################# Step 2, download data ################
# 当前处于 /home/aistudio 目录,数据存放在 /home/data/criteo 中
import os
os.makedirs('data/criteo', exist_ok=True)
# Download data
if not os.path.exists('data/criteo/slot_test_data_full.tar.gz') or not os.path.exists('data/criteo/slot_train_data_full.tar.gz'):
!cd data/criteo && wget https://paddlerec.bj.bcebos.com/datasets/criteo/slot_test_data_full.tar.gz
!cd data/criteo && tar xzvf slot_test_data_full.tar.gz
!cd data/criteo && wget https://paddlerec.bj.bcebos.com/datasets/criteo/slot_train_data_full.tar.gz
!cd data/criteo && tar xzvf slot_train_data_full.tar.gz
################## Step 3, train model ##################
# 启动训练脚本 (需注意当前是否是 GPU 环境, 非 GPU 环境请修改 config_bigdata.yaml 配置中 use_gpu 为 False)
!cd work/rank/DLRM-Paddle && sh run.sh config_bigdata
项目总结
1.基于PaddleRec可以快速进行推荐算法的复现,让你更加专注模型的细节,提升复现效率。
2.PaddleRec提供了通用的训练/推理逻辑,如需增加一些特殊功能,例如,如何提高数据加载速度?如何在训练过程中设置easy_stopping?等。可以直接修改tools/trainer.py和tools/infer.py。
3.有了PaddleRec,论文复现更加强调熟读论文、读懂论文,知道创新点在哪里?核心参数是什么?
参考资料
关注公众号,获取更多技术内容~