基于PaddlePaddle的点击率的深度学习方法尝试

  • 时间:
  • 浏览:1

前面在团队内累积享点击率相关的有些文章时,输出了一篇常见计算广告点击率预估算法总结,想看 有些广告点击率的文章,从最经典的Logistic Regression到Factorization Machined,FFM,FNN,PNN到今年的DeepFM,还有文章底下那么讲的gbdt+lr例如,无缘无故想找时间实践下,正好这次在学习paddle的你会在它的models目录下想看 了DeepFM的实现,不可能 你会对DeepFM有过比较完整的描述,这里稍微复习一下:

DeepFM更有意思的地方是WDL和FM结合了,虽然过多 把PNN和WDL结合了,PNN即将FM用神经网络的法律方法构造了一遍,作为wide的补充,原始的Wide and Deep,Wide的累积过多 LR,构造线性关系,Deep累积建模更高阶的关系,过多在Wide and Deep中还前要做有些价值形式的东西,如Cross Column的工作,而朋友儿知道FM是都上能建模二阶关系达到Cross column的效果,DeepFM过多 把FM和NN结合,不需要再对价值形式做诸如Cross Column的工作了,这名 是我感觉最吸引人的地方,虽然FM的累积感觉过多 PNN的一次描述,这里只描述下价值形式图,PNN的累积前面都描述, FM累积:

Deep累积:

DeepFM相对于FNN、PNN,不需要都上能利用其Deep累积建模更高阶信息(二阶以上),而相对于Wide and Deep不需要都上能减少价值形式工程的累积工作,wide累积例如FM建模一、二阶价值形式间关系, 否是NN和FM的原本更完美的结合方向,另外不同的是如下图,DeepFM的wide和deep累积共享embedding向量空间,wide和deep均都上能更新embedding累积,虽说wide累积纯是PNN的工作,但感觉还是蛮有意思的。

本文相关代码累积完整都是来自于paddlepaddle/model, 我这里走一遍流程,学习下,另外你会了解算法原理的都上能仔细再看看底下的文章,今天朋友儿来paddlepaddle上做下实验,来从代码程度学习下DeepFM为什实现的:

数据集说明

criteo Display Advertising Challenge,数据主要来criteolab一周的业务数据,用来预测用户在访问页面时,否是会点击某广告。

wget --no-check-certificate https://s3-eu-west-1.amazonaws.com/criteo-labs/dac.tar.gz

tar zxf dac.tar.gz

rm -f dac.tar.gz

mkdir raw

mv ./*.txt raw/

数据一阵一阵大, 大约4.26G,慢慢等吧,数据下载完成你会,解压出train.csv,test.csv,其中训练集45840617条样本数,测试集45840617条样本,数据量还是蛮大的。 数据主要有三累积组成:

label: 广告否是被点击;

连续性价值形式: 1-13,为各维度下的统计信息,连续性价值形式;

离散型价值形式:有些被脱敏处理的类目价值形式

Overview

整个项目主要由2个累积组成:

数据处理

这里数据处理主要包括原本累积:

连续值价值形式值处理:

滤除统计次数95%以上的数据,原本都上能滤除大累积异值数据,这里的处理法律方法和你会我在1号店做相关工作时一致,代码底下不可能 做了这累积工作,直接给出了这累积的价值形式阈值;

归一化处理,这里andnew ng的课程有张图很明显,表明不同的价值形式的值域范围,会使得模型寻优走『之』字形,原本会增加收敛的计算和时间;

离散价值形式值处理:

one-hot: 对应价值形式值映射到指定维度的必须原本值为1的稀疏变量;

embedding: 对应价值形式值映射到指定的价值形式维度上;

具体朋友儿来研究下代码:

class ContinuousFeatureGenerator:

"""

Normalize the integer features to [0, 1] by min-max normalization

"""

def __init__(self, num_feature):

self.num_feature = num_feature

self.min = [sys.maxint] * num_feature

self.max = [-sys.maxint] * num_feature

def build(self, datafile, continous_features):

with open(datafile, 'r') as f:

for line in f:

features = line.rstrip('\n').split('\t')

for i in range(0, self.num_feature):

val = features[continous_features[i]]

if val != '':

val = int(val)

if val > continous_clip[i]:

val = continous_clip[i]

self.min[i] = min(self.min[i], val)

self.max[i] = max(self.max[i], val)

def gen(self, idx, val):

if val == '':

return 0.0

val = float(val)

return (val - self.min[idx]) / (self.max[idx] - self.min[idx])

连续价值形式是在1-13的位置,读取文件,不可能 值大于对应维度的价值形式值的95%阈值,则该价值形式值置为该阈值,并计算价值形式维度的最大、最小值,在gen时归一化处理。

class CategoryDictGenerator:

"""

Generate dictionary for each of the categorical features

"""

def __init__(self, num_feature):

self.dicts = []

self.num_feature = num_feature

for i in range(0, num_feature):

self.dicts.append(collections.defaultdict(int))

def build(self, datafile, categorial_features, cutoff=0):

with open(datafile, 'r') as f:

for line in f:

features = line.rstrip('\n').split('\t')

for i in range(0, self.num_feature):

if features[categorial_features[i]] != '':

self.dicts[i][features[categorial_features[i]]] += 1

for i in range(0, self.num_feature):

self.dicts[i] = filter(lambda x: x[1] >= cutoff,

self.dicts[i].items())

self.dicts[i] = sorted(self.dicts[i], key=lambda x: (-x[1], x[0]))

vocabs, _ = list(zip(*self.dicts[i]))

self.dicts[i] = dict(zip(vocabs, range(1, len(vocabs) + 1)))

self.dicts[i][''] = 0

def gen(self, idx, key):

if key not in self.dicts[idx]:

res = self.dicts[idx]['']

else:

res = self.dicts[idx][key]

return res

def dicts_sizes(self):

return map(len, self.dicts)

类目价值形式的处理相对比较麻烦,前要遍历,咋样让 得到对应维度上所有再次出現值的所有情况汇报,对打上对应id,为后续类目价值形式赋予id。这累积耗时好大,慢慢等吧,另外强烈希望paddlepaddle的小伙伴能在输出处理期间打印下提示信息,算了,我你会有时间看看都上能提提pr。

经过底下的价值形式处理你会,训练集的值变为:

reader

paddle底下reader的文件,自由度很高,有些人都上能写生成器,咋样让 使用batch的api,完成向网络传入batchsize大小的数据:

class Dataset:

def _reader_creator(self, path, is_infer):

def reader():

with open(path, 'r') as f:

for line in f:

features = line.rstrip('\n').split('\t')

dense_feature = map(float, features[0].split(','))

sparse_feature = map(int, features[1].split(','))

if not is_infer:

label = [float(features[2])]

yield [dense_feature, sparse_feature

] + sparse_feature + [label]

else:

yield [dense_feature, sparse_feature] + sparse_feature

return reader

def train(self, path):

return self._reader_creator(path, False)

def test(self, path):

return self._reader_creator(path, False)

def infer(self, path):

return self._reader_creator(path, True)

主要逻辑在兑入文件,咋样让 yield对应的网络数据的输入格式

模型构造

模型构造,DeepFM在paddlepaddle底下比较简单,不可能 有专门的fm层,这名 据我所知在TensorFlow或MXNet底下那么专门的fm层,咋样让 值得注意的是,在paddlepaddle底下的fm层,只建模二阶关系,前要换成入fc才是完整的fm,实现代码如下:

def fm_layer(input, factor_size, fm_param_attr):

first_order = paddle.layer.fc(

input=input, size=1, act=paddle.activation.Linear())

second_order = paddle.layer.factorization_machine(

input=input,

factor_size=factor_size,

act=paddle.activation.Linear(),

param_attr=fm_param_attr)

out = paddle.layer.addto(

input=[first_order, second_order],

act=paddle.activation.Linear(),

bias_attr=False)

return out

咋样让 过多 构造DeepFM,这里根据下面的代码画出前面的图,除去数据处理的累积,过多 DeepFM的网络价值形式:

def DeepFM(factor_size, infer=False):

dense_input = paddle.layer.data(

name="dense_input",

type=paddle.data_type.dense_vector(dense_feature_dim))

sparse_input = paddle.layer.data(

name="sparse_input",

type=paddle.data_type.sparse_binary_vector(sparse_feature_dim))

sparse_input_ids = [

paddle.layer.data(

name="C" + str(i),

type=s(sparse_feature_dim))

for i in range(1, 27)

]

dense_fm = fm_layer(

dense_input,

factor_size,

fm_param_attr=paddle.attr.Param(name="DenseFeatFactors"))

sparse_fm = fm_layer(

sparse_input,

factor_size,

fm_param_attr=paddle.attr.Param(name="SparseFeatFactors"))

def embedding_layer(input):

return paddle.layer.embedding(

input=input,

size=factor_size,

param_attr=paddle.attr.Param(name="SparseFeatFactors"))

sparse_embed_seq = map(embedding_layer, sparse_input_ids)

sparse_embed = paddle.layer.concat(sparse_embed_seq)

fc1 = paddle.layer.fc(

input=[sparse_embed, dense_input],

size=80,

act=paddle.activation.Relu())

fc2 = paddle.layer.fc(input=fc1, size=80, act=paddle.activation.Relu())

fc3 = paddle.layer.fc(input=fc2, size=80,            act=paddle.activation.Relu())

predict = paddle.layer.fc(

input=[dense_fm, sparse_fm, fc3],

size=1,

act=paddle.activation.Sigmoid())

if not infer:

label = paddle.layer.data(

name="label", type=paddle.data_type.dense_vector(1))

cost = paddle.layer.multi_binary_label_cross_entropy_cost(

input=predict, label=label)

paddle.evaluator.classification_error(

name="classification_error", input=predict, label=label)

paddle.evaluator.auc(name="auc", input=predict, label=label)

return cost

else:

return predict

其中,主要包括原本累积,原本是多个fc组成的deep累积,第3个是sparse fm累积,咋样让 是dense fm累积,如图:

这里蛮简单的,具体的api去查下文档就都上能了,这里稍微说明一下的是,sparse feature这块有两累积一块是embedding的处理,这里是先生成对应的id,咋样让 用id来做embedding,用作底下fc的输出,咋样让 sparse_input是onehot表示用来作为fm的输出,fm来计算一阶和二阶隐变量关系。

模型训练

数据量过多,单机上跑是那么问题报告 ,都上能正常运行成功,在我实物机器上,都上能运行成功,咋样让 有原本问题报告 :

fm不可能 处理的价值形式为稀疏表示,而paddlepaddle在这块的FM层的支持必须在cpu上,传输速率很快,分析意味着着虽然完整都是fm的传输速率的问题报告 ,不可能 deepfm有设计多个fc,应该是这里的传输速率影响, 在paddlepaddle github上有提原本issue,得知暂时paddlepaddle必须把累积贴到 gpu底下跑,给了原本处理方案把所有的sparse改成dense,发现在这里gpu显存hold不住;

我的机器太渣,不可能 有开发任务必须长期占用;

过多综上,我打算研究下在百度云上为什通过k8s来布置paddlepaddle的分布式集群。

文档https://cloud.baidu.com/doc/CCE/GettingStarted.html#.E9.85.8D.E7.BD.AEpaddlecloud

研究来研究去,第一步加卡主了,告诉我为什回事,那个页面过多 出不来...出师未捷身先死,提了个issue: https://github.com/PaddlePaddle/cloud/issues/542,等底下处理了再来更新分布式训练的累积。

单机的训练那么那些大的问题报告 ,由底下所说,不可能 fm的sparse不支持gpu,过多很快,拉的百度云上16核的机器,大约36s/80 batch,总共样本800多w,原本epoch预计原本小时,MMP,等吧,分布式的必要性就在这里。

另外有在paddlepaddle底下提原本issue:

https://github.com/PaddlePaddle/Paddle/issues/7010,说把sparse转成dense一句话都上能直接在gpu上跑起来,这名 看起来不值得去尝试,sparse整个维度还是挺高的,期待对sparse op 有更好的处理方案,更期待在不需要都上能把单层单层的贴到 gpu,多设备一块儿跑,这方面,TensorFlow和MXNet要好过多。

这里我遇到原本问题报告 ,我使用paddle的docker镜像的你会,都上能很稳定的占用16个cpu的大累积计算力,咋样让 我在云主机上有些人装的你会,cpu占用率很低,不可能 是和我环境配置一阵一阵问题报告 ,这名 问题报告 不大,你会为了不污染环境主要用docker来做相关的开发工作,过多这里问题报告 不大。

cpu占有率有比较明显的跳动,这里从主观上比TensorFlow稳定性要差有些,不排除是sparse op的影响,印象中,TensorFlow cpu的占用率很稳定。

到发这篇文章位置,跑到1780个batch,基本能达到auc为0.8左右,loss为0.208左右。

预测

预测代码和前一篇将paddle底下的demo一样,只前要,重新定义一下网络,咋样让 绑定好模型训练得到的参数,咋样让 传入数据即可完成inference,paddle,有专门的Inference接口,倘若传入output_layer,和训练学习到的parameters,就都上能很容易的新建原本模型的前向inference网络。

def infer():

args = parse_args()

paddle.init(use_gpu=False, trainer_count=1)

model = DeepFM(args.factor_size, infer=True)

parameters = paddle.parameters.Parameters.from_tar(

gzip.open(args.model_gz_path, 'r'))

inferer = paddle.inference.Inference(

output_layer=model, parameters=parameters)

dataset = reader.Dataset()

infer_reader = paddle.batch(dataset.infer(args.data_path),  batch_size=800)

with open(args.prediction_output_path, 'w') as out:

for id, batch in enumerate(infer_reader()):

res = inferer.infer(input=batch)

predictions = [x for x in itertools.chain.from_iterable(res)]

out.write('\n'.join(map(str, predictions)) + '\n')

总结

照例总结一下,DeemFM是17年角度学习在点击率预估、推荐这块的新的法律方法,一阵一阵例如于deep and wide的思想,将传统的fm来nn化,利用神经网络强大的建模能力来挖掘数据中的有效信息,paddlepaddle在这块有现成的deepfm模型,单机部署起来比较容易,分布式,这里我按照百度云上的教程还未成功,后续会持续关注。另外,不可能 最近在做大规模机器学习框架相关的工作,越发虽然别说心智性性性心智成熟是什么是什么期的句子是什么是什么的,仅仅不需要都上能work的框架就很不错了,而比较好用的如现在的TensorFlow\MXNet,开发起来真的难换成难,你会光是做调包侠时那么体验,现在深入到这块的工作时,才知道其中的难度,也从原本角度刚开始了了审视现在的各种大规模机器学习框架,比如TensorFlow、MXNet,在角度学习的支持上,虽然很棒,咋样让 完整都是瓶颈,对于大规模海量的feature,尤其是sparse op的支持上,大约现在还未想看 一阵一阵好的支持,就比如这里的FM,不可能 朋友儿完整都是吐槽为什那么慢,没做框架你会,我也会吐槽,咋样让 刚开始了了接触了有些的你会,才知道FM,主要focus在sparse相关的数据对象,而这累积数据没能在gpu上完成比较高性能的计算,所之底下经过paddle的开发者解释sparse相关的计算不支持gpu的你会,才感同身受,原本好的大规模机器学习框架前要要不须同目标来评价,不可能 需求是大规律数据,那稳定性、可扩展性是重点,不可能 是更多算法、模型的支持,不可能 现在的TensorFlow、MXNet才是标杆,多么希望现在大规模机器学习框架不需要都上能多元化的发展,有角度学习支持力度大的,完整都是传统算法上,把数据量、训练规模、并行化加速并做到极致的,原本的发展才或许称得上百花齐放,虽然朋友儿不前要过多不同长相的TensorFlow、MXNet锤子,有你会朋友儿就前要把镰刀而已,希望大规模机器学习框架的发展,不应该仅仅像TensorFlow、MXNet一样,希望有原本专注把做大规模、大数据量、极致并行化加速作为roadmap的新标杆,加油。

作者:想飞的石头