“86fd748808dee2448bf368f3b1389f91ec6e9d29”上不存在“develop/doc/api”
提交 9312f5b4 编写于 作者: S Superjom

init doc and model

上级 6f06bb62
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org5589824">1. 背景介绍</a>
<ul>
<li><a href="#org39b4382">1.1. LR vs DNN</a></li>
</ul>
</li>
<li><a href="#org5b2aafc">2. 数据和任务抽象</a></li>
<li><a href="#org17c7e05">3. 特征提取</a>
<ul>
<li><a href="#orga3db7a6">3.1. ID 类特征</a></li>
</ul>
</li>
<li><a href="#org5ce3921">4. 模型实现</a>
<ul>
<li><a href="#orgbb98635">4.1. DNN 简单模型</a></li>
<li><a href="#org41335f7">4.2. long wide 复杂模型</a></li>
</ul>
</li>
<li><a href="#orgb8efca9">5. 写在最后</a></li>
</ul>
</div>
</div>
<a id="org5589824"></a>
# 背景介绍
CTR(Click-through rate) 是用来表示用户点击一个特定链接的概率,
通常被用来衡量一个在线广告系统的有效性。
当有多个广告位时,CTR 预估一般会作为排序的基准。
比如在百度的搜索广告系统,当用户输入一个带商业价值的搜索词(query)时,系统大体上会执行下列步骤:
1. 召回满足 query 的广告集合
2. 业务规则和相关性过滤
3. 根据拍卖机制和 CTR 排序
4. 展出
可以看到,CTR 在最终排序中起到了很重要的作用。
在业内,CTR 模型经历了如下的发展阶段:
- Logistic Regression(LR) + 特征工程
- LR + DNN 特征
- DNN + 特征工程
在发展早期是 LR 一统天下,但最近 DNN 模型由于其强大的学习能力和逐渐成熟的性能优化,
逐渐地接过 CTR 预估任务的大旗。
<a id="org39b4382"></a>
## LR vs DNN
下图展示了 LR 和一个 \(3x2\) 的 NN 模型的结构:
![img](背景介绍/LR vs DNN_2017-05-22_10-09-02.jpg)
LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看到 LR 和 NN 有一些共通之处(比如权重累加),
但前者的模型复杂度在相同输入维度下比后者可能第很多(从某方面讲,模型越复杂,越有潜力学习到更复杂的信息)。
如果 LR 要达到匹敌 NN 的学习能力,必须增加输入的维度,也就是增加特征的数量(作为输入),
这也就是为何 LR 和大规模的特征工程必须绑定在一起的原因。
而 NN 模型具有自己学习新特征的能力,一定程度上能够提升特征使用的效率,
这使得 NN 模型在同样规模特征的情况下,更有可能达到更好的学习效果。
本文会演示,如何使用 NN 模型来完成 CTR 预估的任务。
<a id="org5b2aafc"></a>
# 数据和任务抽象
我们可以将 \`click\` 作为学习目标,具体任务可以有以下几种方案:
1. 直接学习 click,0,1 作二元分类,或 pairwise rank(标签 1>0)
2. 统计每个广告的点击率,将同一个 query 下的广告两两组合,点击率高的>点击率低的
这里,我们直接使用第一种方法做分类任务。
我们使用 Kaggle 上 \`Click-through rate prediction\` 任务的数据集来演示模型。
各个字段内容如下:
- id: ad identifier
- click: 0/1 for non-click/click
- hour: format is YYMMDDHH, so 14091123 means 23:00 on Sept. 11, 2014 UTC.
- C1 &#x2013; anonymized categorical variable
- banner<sub>pos</sub>
- site<sub>id</sub>
- site<sub>domain</sub>
- site<sub>category</sub>
- app<sub>id</sub>
- app<sub>domain</sub>
- app<sub>category</sub>
- device<sub>id</sub>
- device<sub>ip</sub>
- device<sub>model</sub>
- device<sub>type</sub>
- device<sub>conn</sub><sub>type</sub>
- C14-C21 &#x2013; anonymized categorical variables
<a id="org17c7e05"></a>
# 特征提取
下面我们会简单演示几种特征的提取方式。
原始数据中的特征可以分为以下几类:
1. ID 类特征(稀疏,数量多)
- id
- site<sub>id</sub>
- app<sub>id</sub>
- device<sub>id</sub>
2. 类别类特征(稀疏,但数量有限)
- C1
- site<sub>category</sub>
- device<sub>type</sub>
- C14-C21
3. 数值型特征
- hour (可以转化成数值,也可以按小时为单位转化为类别)
<a id="orga3db7a6"></a>
## ID 类特征
ID 类特征的特点是稀疏数据,但量比较大,直接使用 One-hot 表示时维度过大。
一般会作如下处理:
<a id="org5ce3921"></a>
# 模型实现
<a id="orgbb98635"></a>
## DNN 简单模型
<a id="org41335f7"></a>
## long wide 复杂模型
<a id="orgb8efca9"></a>
# 写在最后
<https://en.wikipedia.org/wiki/Click-through_rate>
* 背景介绍
CTR(Click-through rate) 是用来表示用户点击一个特定链接的概率,
通常被用来衡量一个在线广告系统的有效性。
当有多个广告位时,CTR 预估一般会作为排序的基准。
比如在百度的搜索广告系统,当用户输入一个带商业价值的搜索词(query)时,系统大体上会执行下列步骤:
1. 召回满足 query 的广告集合
2. 业务规则和相关性过滤
3. 根据拍卖机制和 CTR 排序
4. 展出
可以看到,CTR 在最终排序中起到了很重要的作用。
在业内,CTR 模型经历了如下的发展阶段:
- Logistic Regression(LR) + 特征工程
- LR + DNN 特征
- DNN + 特征工程
在发展早期时 LR 一统天下,但最近 DNN 模型由于其强大的学习能力和逐渐成熟的性能优化,
逐渐地接过 CTR 预估任务的大旗。
** LR vs DNN
下图展示了 LR 和一个 \(3x2\) 的 NN 模型的结构:
#+DOWNLOADED: file:/Users/superjom/project/paddle_models/ctr/img/LR vs DNN.jpg @ 2017-05-22 10:09:02
[[file:背景介绍/LR vs DNN_2017-05-22_10-09-02.jpg]]
LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看到 LR 和 NN 有一些共通之处(比如权重累加),
但前者的模型复杂度在相同输入维度下比后者可能第很多(从某方面讲,模型越复杂,越有潜力学习到更复杂的信息)。
如果 LR 要达到匹敌 NN 的学习能力,必须增加输入的维度,也就是增加特征的数量(作为输入),
这也就是为何 LR 和大规模的特征工程必须绑定在一起的原因。
而 NN 模型具有自己学习新特征的能力,一定程度上能够提升特征使用的效率,
这使得 NN 模型在同样规模特征的情况下,更有可能达到更好的学习效果。
LR 对于 NN 模型的优势是对大规模稀疏特征的容纳能力,包括内存和计算量等,工业界都有非常成熟的优化方法。
本文后面的章节会演示如何使用 Paddle 编写一个结合两者优点的模型。
* 数据和任务抽象
我们可以将 `click` 作为学习目标,具体任务可以有以下几种方案:
1. 直接学习 click,0,1 作二元分类,或 pairwise rank(标签 1>0)
2. 统计每个广告的点击率,将同一个 query 下的广告两两组合,点击率高的>点击率低的
这里,我们直接使用第一种方法做分类任务。
我们使用 Kaggle 上 `Click-through rate prediction` 任务的数据集来演示模型。
各个字段内容如下:
- id: ad identifier
- click: 0/1 for non-click/click
- hour: format is YYMMDDHH, so 14091123 means 23:00 on Sept. 11, 2014 UTC.
- C1 -- anonymized categorical variable
- banner_pos
- site_id
- site_domain
- site_category
- app_id
- app_domain
- app_category
- device_id
- device_ip
- device_model
- device_type
- device_conn_type
- C14-C21 -- anonymized categorical variables
* 特征提取
下面我们会简单演示几种特征的提取方式。
原始数据中的特征可以分为以下几类:
1. ID 类特征(稀疏,数量多)
- id
- site_id
- app_id
- device_id
2. 类别类特征(稀疏,但数量有限)
- C1
- site_category
- device_type
- C14-C21
3. 数值型特征
- hour (可以转化成数值,也可以按小时为单位转化为类别)
** 类别类特征
类别类特征的提取方法有以下两种:
1. One-hot 表示作为特征
2. 类似词向量,用一个 Embedding Table 将每个类别映射到对应的向量
** ID 类特征
ID 类特征的特点是稀疏数据,但量比较大,直接使用 One-hot 表示时维度过大。
一般会作如下处理:
1. 确定表示的最大维度 N
2. newid = id % N
3. 用 newid 作为类别类特征使用
上面的方法尽管存在一定的碰撞概率,但能够处理任意数量的 ID 特征,并保留一定的效果[2]。
** 数值型特征
一般会做如下处理:
- 归一化,直接作为特征输入模型
- 用区间分割处理成类别类特征,稀疏化表示,模糊细微上的差别
** 处理方法实现
接下来我们演示具体方法的实现,为了简洁,我们只对一下几个特征作处理:
- site_category
- device_type
- id
- site_id
- hour
#+BEGIN_SRC python
import sys
class CategoryFeatureGenerator(object):
'''
Generator category features.
'''
def __init__(self):
self.dic = {}
self.counter = 0
def register(self, key):
if key not in self.dic:
self.dic[key] = self.counter
self.counter += 1
def lookup(self, key):
return self.dic[key]
#+END_SRC
* Wide & Deep Learning Model
谷歌在 16 年提出了 Wide & Deep Learning 的模型框架,用于融合 适合学习抽象特征的 DNN 和 适用于大规模系数特征的 LR 两种模型的优点。
** 模型简介
Wide & Deep Learning Model 可以作为一种相对成熟的模型框架使用,
在 CTR 预估的任务中工业界也有一定的应用,因此本文将演示使用此模型来完成 CTR 预估的任务。
模型结构如下:
[ pic ]
** 编写 LR 部分
** 编写 DNN 部分
** 两者融合
* 写在最后
- [1] https://en.wikipedia.org/wiki/Click-through_rate
- [2] Strategies for Training Large Scale Neural Network Language Models
- https://www.kaggle.com/c/avazu-ctr-prediction/data
import sys
import csv
import numpy as np
'''
The fields of the dataset are:
0. id: ad identifier
1. click: 0/1 for non-click/click
2. hour: format is YYMMDDHH, so 14091123 means 23:00 on Sept. 11, 2014 UTC.
3. C1 -- anonymized categorical variable
4. banner_pos
5. site_id
6. site_domain
7. site_category
8. app_id
9. app_domain
10. app_category
11. device_id
12. device_ip
13. device_model
14. device_type
15. device_conn_type
16. C14-C21 -- anonymized categorical variables
We will treat following fields as categorical features:
- C1
- banner_pos
- site_category
- app_category
- device_type
- device_conn_type
and some other features as id features:
- id
- site_id
- app_id
- device_id
The `hour` field will be treated as a continuous feature and will be transformed
to one-hot representation which has 24 bits.
'''
feature_dims = {}
categorial_features = ('C1 banner_pos site_category app_category ' +
'device_type device_conn_type').split()
id_features = 'id site_id app_id device_id'.split()
def get_all_field_names(mode=0):
'''
@mode: int
0 for train, 1 for test
@return: list of str
'''
return categorial_features + ['hour'] + id_features + ['click'] \
if mode == 0 else []
class CategoryFeatureGenerator(object):
'''
Generator category features.
'''
def __init__(self):
self.dic = {'unk': 0}
self.counter = 1
def register(self, key):
if key not in self.dic:
self.dic[key] = self.counter
self.counter += 1
def size(self):
return len(self.dic)
def gen(self, key):
if key not in self.dic:
res = self.dic['unk']
else:
res = self.dic[key]
return [res]
def __repr__(self):
return '<CategoryFeatureGenerator %d>' % len(self.dic)
class IDfeatureGenerator(object):
def __init__(self, max_dim):
self.max_dim = max_dim
def gen(self, key):
return [hash(key) % self.max_dim]
def size(self):
return self.max_dim
class ContinuousFeatureGenerator(object):
def __init__(self, n_intervals):
self.min = sys.maxint
self.max = sys.minint
self.n_intervals = n_intervals
def register(self, val):
self.min = min(self.minint, val)
self.max = max(self.maxint, val)
def gen(self, val):
self.len_part = (self.max - self.min) / self.n_intervals
return (val - self.min) / self.len_part
fields = {}
for key in categorial_features:
fields[key] = CategoryFeatureGenerator()
for key in id_features:
fields[key] = IDfeatureGenerator(10000)
field_index = dict(
(key, id) for id, key in enumerate(['dnn_input', 'lr_input', 'click']))
def detect_dataset(path, topn, id_fea_space=10000):
'''
Parse the first `topn` records to collect information of this dataset.
NOTE the records should be randomly shuffled first.
'''
# create categorical statis objects.
with open(path, 'rb') as csvfile:
reader = csv.DictReader(csvfile)
for row_id, row in enumerate(reader):
if row_id > topn:
break
for key in categorial_features:
fields[key].register(row[key])
for key, item in fields.items():
feature_dims[key] = item.size()
for key in id_features:
feature_dims[key] = id_fea_space
feature_dims['hour'] = 24
feature_dims['click'] = 1
feature_dims['dnn_input'] = np.sum(
feature_dims[key] for key in categorial_features + ['hour']) + 10
feature_dims['lr_input'] = np.sum(feature_dims[key] for key in id_features) + 10
return feature_dims
def concat_sparse_vectors(inputs, dims):
'''
concaterate sparse vectors into one
@inputs: list
list of sparse vector
@dims: list of int
dimention of each sparse vector
'''
res = []
assert len(inputs) == len(dims)
start = 0
for no, vec in enumerate(inputs):
for v in vec:
res.append(v + start)
start += dims[no]
return res
class AvazuDataset(object):
TRAIN_MODE = 0
TEST_MODE = 1
def __init__(self, train_path, test_path=None):
self.train_path = train_path
self.test_path = test_path
# task model: 0 train, 1 test
self.mode = 0
def train(self):
self.mode = self.TRAIN_MODE
return self._parse(self.train_path)
def test(self):
self.mode = self.TEST_MODE
return self._parse(self.test_path)
def _parse(self, path):
with open(path, 'rb') as csvfile:
reader = csv.DictReader(csvfile)
categorial_dims = [
feature_dims[key] for key in categorial_features + ['hour']
]
id_dims = [feature_dims[key] for key in id_features]
for row_id, row in enumerate(reader):
record = []
for key in categorial_features:
record.append(fields[key].gen(row[key]))
record.append([int(row['hour'][-2:])])
dense_input = concat_sparse_vectors(record, categorial_dims)
record = []
for key in id_features:
record.append(fields[key].gen(row[key]))
sparse_input = concat_sparse_vectors(record, id_dims)
record = [dense_input, sparse_input]
if self.mode == self.TRAIN_MODE:
record.append(list((int(row['click']), )))
# print record
yield record
if __name__ == '__main__':
path = 'train.txt'
print detect_dataset(path, 400000)
filereader = AvazuDataset(path)
for no, rcd in enumerate(filereader.train()):
print no, rcd
if no > 1000: break
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import paddle.v2 as paddle
from paddle.v2 import layer
from paddle.v2 import data_type as dtype
from data_provider import categorial_features, id_features, field_index, detect_dataset, AvazuDataset, get_all_field_names
id_features_space = 10000
dnn_layer_dims = [128, 64, 32, 1]
train_data_path = './train.txt'
data_meta_info = detect_dataset(train_data_path, 10000)
logging.warning('detect categorical fields in dataset %s' % train_data_path)
for key, item in data_meta_info.items():
logging.warning(' - {}\t{}'.format(key, item))
paddle.init(use_gpu=False, trainer_count=1)
# ==============================================================================
# input layers
# ==============================================================================
dnn_merged_input = layer.data(
name='dnn_input',
type=paddle.data_type.sparse_binary_vector(data_meta_info['dnn_input']))
lr_merged_input = layer.data(
name='lr_input',
type=paddle.data_type.sparse_binary_vector(data_meta_info['lr_input']))
click = paddle.layer.data(name='click', type=dtype.dense_vector(1))
# ==============================================================================
# network structure
# ==============================================================================
def build_dnn_submodel(dnn_layer_dims):
dnn_embedding = layer.fc(input=dnn_merged_input, size=dnn_layer_dims[0])
# dnn_embedding = layer.embedding(
# input=dnn_merged_input,
# size=128)
# average_layer = layer.pooling(input=dnn_embedding,
# pooling_type=paddle.pooling.Avg())
# _input_layer = average_layer
_input_layer = dnn_embedding
for no, dim in enumerate(dnn_layer_dims[1:]):
fc = layer.fc(
input=_input_layer,
size=dim,
act=paddle.activation.Relu(),
name='dnn-fc-%d' % no)
_input_layer = fc
return _input_layer
# config LR submodel
def build_lr_submodel():
fc = layer.fc(
input=lr_merged_input, size=1, name='lr', act=paddle.activation.Relu())
return fc
# conbine DNN and LR submodels
def combine_submodels(dnn, lr):
merge_layer = layer.concat(input=[dnn, lr])
fc = layer.fc(
input=merge_layer,
size=1,
name='output',
# use sigmoid function to approximate ctr rate, a float value between 0 and 1.
act=paddle.activation.Sigmoid())
return fc
dnn = build_dnn_submodel(dnn_layer_dims)
lr = build_lr_submodel()
output = combine_submodels(dnn, lr)
# ==============================================================================
# cost and train period
# ==============================================================================
classification_cost = paddle.layer.multi_binary_label_cross_entropy_cost(
input=output, label=click)
params = paddle.parameters.create(classification_cost)
optimizer = paddle.optimizer.Momentum(momentum=0)
trainer = paddle.trainer.SGD(
cost=classification_cost, parameters=params, update_equation=optimizer)
dataset = AvazuDataset(train_data_path)
step = 0
def event_hander(event):
global step
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(dataset.train, buf_size=500), batch_size=5),
feeding=field_index,
num_passes=1)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册