提交 62d9503a 编写于 作者: S Superjom

finish code

上级 b3e717be
......@@ -2,29 +2,29 @@
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org5589824">1. 背景介绍</a>
<li><a href="#org7f839ad">1. 背景介绍</a>
<ul>
<li><a href="#org39b4382">1.1. LR vs DNN</a></li>
<li><a href="#org731851a">1.1. LR vs DNN</a></li>
</ul>
</li>
<li><a href="#org5b2aafc">2. 数据和任务抽象</a></li>
<li><a href="#org17c7e05">3. 特征提取</a>
<li><a href="#orgc2a75e1">2. 数据和任务抽象</a></li>
<li><a href="#org7f7e5a5">3. Wide &amp; Deep Learning Model</a>
<ul>
<li><a href="#orga3db7a6">3.1. ID 类特征</a></li>
<li><a href="#org218a889">3.1. 模型简介</a></li>
<li><a href="#org222c8b8">3.2. 编写模型输入</a></li>
<li><a href="#org12bc870">3.3. 编写 Wide 部分</a></li>
<li><a href="#org4c61b9b">3.4. 编写 Deep 部分</a></li>
<li><a href="#orged7c312">3.5. 两者融合</a></li>
<li><a href="#orgb6ad56e">3.6. 训练任务的定义</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>
<li><a href="#orga0bd27d">4. 写在最后</a></li>
</ul>
</div>
</div>
<a id="org5589824"></a>
<a id="org7f839ad"></a>
# 背景介绍
......@@ -47,17 +47,17 @@ CTR(Click-through rate) 是用来表示用户点击一个特定链接的概率
- LR + DNN 特征
- DNN + 特征工程
在发展早期 LR 一统天下,但最近 DNN 模型由于其强大的学习能力和逐渐成熟的性能优化,
在发展早期 LR 一统天下,但最近 DNN 模型由于其强大的学习能力和逐渐成熟的性能优化,
逐渐地接过 CTR 预估任务的大旗。
<a id="org39b4382"></a>
<a id="org731851a"></a>
## LR vs DNN
下图展示了 LR 和一个 \(3x2\) 的 NN 模型的结构:
![img](背景介绍/LR vs DNN_2017-05-22_10-09-02.jpg)
![img](背景介绍/lr-vs-dnn_2017-05-25_10-36-48.jpg)
LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看到 LR 和 NN 有一些共通之处(比如权重累加),
但前者的模型复杂度在相同输入维度下比后者可能第很多(从某方面讲,模型越复杂,越有潜力学习到更复杂的信息)。
......@@ -68,10 +68,12 @@ LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看
而 NN 模型具有自己学习新特征的能力,一定程度上能够提升特征使用的效率,
这使得 NN 模型在同样规模特征的情况下,更有可能达到更好的学习效果。
本文会演示,如何使用 NN 模型来完成 CTR 预估的任务
LR 对于 NN 模型的优势是对大规模稀疏特征的容纳能力,包括内存和计算量等,工业界都有非常成熟的优化方法
本文后面的章节会演示如何使用 Paddle 编写一个结合两者优点的模型。
<a id="org5b2aafc"></a>
<a id="orgc2a75e1"></a>
# 数据和任务抽象
......@@ -82,80 +84,150 @@ LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看
这里,我们直接使用第一种方法做分类任务。
我们使用 Kaggle 上 \`Click-through rate prediction\` 任务的数据集来演示模型。
我们使用 Kaggle 上 \`Click-through rate prediction\` 任务的数据集[1] 来演示模型。
具体的特征处理方法参看 [data process](./dataset.md)
<a id="org7f7e5a5"></a>
# Wide & Deep Learning Model
谷歌在 16 年提出了 Wide & Deep Learning 的模型框架,用于融合 适合学习抽象特征的 DNN 和 适用于大规模系数特征的 LR 两种模型的优点。
<a id="org218a889"></a>
## 模型简介
Wide & Deep Learning Model 可以作为一种相对成熟的模型框架使用,
在 CTR 预估的任务中工业界也有一定的应用,因此本文将演示使用此模型来完成 CTR 预估的任务。
各个字段内容如下:
模型结构如下:
- 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
![img](Wide & Deep Learning Model/wide-deep_2017-05-25_10-24-26.png)
模型左边的 Wide 部分,可以容纳大规模系数特征,并且对一些特定的信息(比如 ID)有一定的记忆能力;
而模型右边的 Deep 部分,能够学习特征间的隐含关系,在相同数量的特征下有更好的学习和推导能力。
<a id="org17c7e05"></a>
# 特征提取
<a id="org222c8b8"></a>
下面我们会简单演示几种特征的提取方式。
## 编写模型输入
原始数据中的特征可以分为以下几类:
模型只接受 3 个输入,分别是
1. ID 类特征(稀疏,数量多)
- id
- site<sub>id</sub>
- app<sub>id</sub>
- device<sub>id</sub>
- \`dnn<sub>input</sub>\` ,也就是 Deep 部分的输入
- \`lr<sub>input</sub>\` ,也就是 Wide 部分的输入
- \`click\` , 点击与否,作为二分类模型学习的标签
2. 类别类特征(稀疏,但数量有限)
- C1
- site<sub>category</sub>
- device<sub>type</sub>
- C14-C21
dnn_merged_input = layer.data(
name='dnn_input',
type=paddle.data_type.sparse_binary_vector(data_meta_info['dnn_input']))
3. 数值型特征
- hour (可以转化成数值,也可以按小时为单位转化为类别)
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))
<a id="orga3db7a6"></a>
## ID 类特征
<a id="org12bc870"></a>
ID 类特征的特点是稀疏数据,但量比较大,直接使用 One-hot 表示时维度过大。
## 编写 Wide 部分
一般会作如下处理:
def build_lr_submodel():
fc = layer.fc(
input=lr_merged_input, size=1, name='lr', act=paddle.activation.Relu())
return fc
<a id="org5ce3921"></a>
<a id="org4c61b9b"></a>
# 模型实现
## 编写 Deep 部分
def build_dnn_submodel(dnn_layer_dims):
dnn_embedding = layer.fc(input=dnn_merged_input, size=dnn_layer_dims[0])
_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
<a id="orgbb98635"></a>
## DNN 简单模型
<a id="orged7c312"></a>
## 两者融合
<a id="org41335f7"></a>
# 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
## long wide 复杂模型
<a id="orgb6ad56e"></a>
<a id="orgb8efca9"></a>
## 训练任务的定义
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, n_records_as_test=test_set_size)
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
logging.warning("Pass %d, Samples %d, Cost %f" % (
event.pass_id, event.batch_id * batch_size, event.cost))
if event.batch_id % 1000 == 0:
result = trainer.test(
reader=paddle.batch(dataset.test, batch_size=1000),
feeding=field_index)
logging.warning("Test %d-%d, Cost %f" % (event.pass_id, event.batch_id,
result.cost))
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(dataset.train, buf_size=500),
batch_size=batch_size),
feeding=field_index,
event_handler=event_handler,
num_passes=100)
<a id="orga0bd27d"></a>
# 写在最后
<https://en.wikipedia.org/wiki/Click-through_rate>
- [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>
[1] <https://www.kaggle.com/c/avazu-ctr-prediction/data>
#+title: 使用 Wide & Deep neural model 进行 CTR 预估
* 背景介绍
CTR(Click-through rate) 是用来表示用户点击一个特定链接的概率,
通常被用来衡量一个在线广告系统的有效性。
......@@ -24,8 +25,8 @@ CTR(Click-through rate) 是用来表示用户点击一个特定链接的概率
** 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]]
#+DOWNLOADED: file:/Users/superjom/project/paddle_models/ctr/img/lr-vs-dnn.jpg @ 2017-05-25 10:36:48
[[file:背景介绍/lr-vs-dnn_2017-05-25_10-36-48.jpg]]
LR 部分和蓝色箭头部分可以直接类比到 NN 中的结构,可以看到 LR 和 NN 有一些共通之处(比如权重累加),
但前者的模型复杂度在相同输入维度下比后者可能第很多(从某方面讲,模型越复杂,越有潜力学习到更复杂的信息)。
......@@ -48,121 +49,131 @@ LR 对于 NN 模型的优势是对大规模稀疏特征的容纳能力,包括
这里,我们直接使用第一种方法做分类任务。
我们使用 Kaggle 上 `Click-through rate prediction` 任务的数据集来演示模型。
我们使用 Kaggle 上 `Click-through rate prediction` 任务的数据集[1] 来演示模型。
各个字段内容如下:
具体的特征处理方法参看 [[./dataset.md][data process]]
- 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]。
* Wide & Deep Learning Model
谷歌在 16 年提出了 Wide & Deep Learning 的模型框架,用于融合 适合学习抽象特征的 DNN 和 适用于大规模系数特征的 LR 两种模型的优点。
** 模型简介
Wide & Deep Learning Model 可以作为一种相对成熟的模型框架使用,
在 CTR 预估的任务中工业界也有一定的应用,因此本文将演示使用此模型来完成 CTR 预估的任务。
** 数值型特征
一般会做如下处理:
模型结构如下:
- 归一化,直接作为特征输入模型
- 用区间分割处理成类别类特征,稀疏化表示,模糊细微上的差别
#+DOWNLOADED: file:/Users/superjom/project/paddle_models/ctr/img/wide-deep.png @ 2017-05-25 10:24:26
[[file:Wide & Deep Learning Model/wide-deep_2017-05-25_10-24-26.png]]
** 处理方法实现
接下来我们演示具体方法的实现,为了简洁,我们只对一下几个特征作处理:
模型左边的 Wide 部分,可以容纳大规模系数特征,并且对一些特定的信息(比如 ID)有一定的记忆能力;
而模型右边的 Deep 部分,能够学习特征间的隐含关系,在相同数量的特征下有更好的学习和推导能力。
** 编写模型输入
- site_category
- device_type
- id
- site_id
- hour
模型只接受 3 个输入,分别是
- `dnn_input` ,也就是 Deep 部分的输入
- `lr_input` ,也就是 Wide 部分的输入
- `click` , 点击与否,作为二分类模型学习的标签
#+BEGIN_SRC python
import sys
class CategoryFeatureGenerator(object):
'''
Generator category features.
'''
def __init__(self):
self.dic = {}
self.counter = 0
dnn_merged_input = layer.data(
name='dnn_input',
type=paddle.data_type.sparse_binary_vector(data_meta_info['dnn_input']))
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]
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))
#+END_SRC
* Wide & Deep Learning Model
谷歌在 16 年提出了 Wide & Deep Learning 的模型框架,用于融合 适合学习抽象特征的 DNN 和 适用于大规模系数特征的 LR 两种模型的优点。
** 模型简介
Wide & Deep Learning Model 可以作为一种相对成熟的模型框架使用,
在 CTR 预估的任务中工业界也有一定的应用,因此本文将演示使用此模型来完成 CTR 预估的任务。
模型结构如下:
[ pic ]
** 编写 LR 部分
** 编写 DNN 部分
** 编写 Wide 部分
#+BEGIN_SRC python
def build_lr_submodel():
fc = layer.fc(
input=lr_merged_input, size=1, name='lr', act=paddle.activation.Relu())
return fc
#+END_SRC
** 编写 Deep 部分
#+BEGIN_SRC python
def build_dnn_submodel(dnn_layer_dims):
dnn_embedding = layer.fc(input=dnn_merged_input, size=dnn_layer_dims[0])
_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
#+END_SRC
** 两者融合
#+BEGIN_SRC python
# 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
#+END_SRC
** 训练任务的定义
#+BEGIN_SRC python
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, n_records_as_test=test_set_size)
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
logging.warning("Pass %d, Samples %d, Cost %f" % (
event.pass_id, event.batch_id * batch_size, event.cost))
if event.batch_id % 1000 == 0:
result = trainer.test(
reader=paddle.batch(dataset.test, batch_size=1000),
feeding=field_index)
logging.warning("Test %d-%d, Cost %f" % (event.pass_id, event.batch_id,
result.cost))
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(dataset.train, buf_size=500),
batch_size=batch_size),
feeding=field_index,
event_handler=event_handler,
num_passes=100)
#+END_SRC
* 写在最后
- [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
[1] https://www.kaggle.com/c/avazu-ctr-prediction/data
......@@ -63,6 +63,9 @@ def get_all_field_names(mode=0):
class CategoryFeatureGenerator(object):
'''
Generator category features.
Register all records by calling `register` first, then call `gen` to generate
one-hot representation for a record.
'''
def __init__(self):
......@@ -70,6 +73,9 @@ class CategoryFeatureGenerator(object):
self.counter = 1
def register(self, key):
'''
Register record.
'''
if key not in self.dic:
self.dic[key] = self.counter
self.counter += 1
......@@ -78,6 +84,9 @@ class CategoryFeatureGenerator(object):
return len(self.dic)
def gen(self, key):
'''
Generate one-hot representation for a record.
'''
if key not in self.dic:
res = self.dic['unk']
else:
......@@ -90,11 +99,22 @@ class CategoryFeatureGenerator(object):
class IDfeatureGenerator(object):
def __init__(self, max_dim):
'''
@max_dim: int
Size of the id elements' space
'''
self.max_dim = max_dim
def gen(self, key):
'''
Generate one-hot representation for records
'''
return [hash(key) % self.max_dim]
def gen_cross_fea(self, fea1, fea2):
key = str(fea1) + str(fea2)
return self.gen(key)
def size(self):
return self.max_dim
......@@ -152,7 +172,8 @@ def detect_dataset(path, topn, id_fea_space=10000):
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
feature_dims['lr_input'] = np.sum(feature_dims[key]
for key in id_features) + 10
return feature_dims
......@@ -180,21 +201,22 @@ class AvazuDataset(object):
TRAIN_MODE = 0
TEST_MODE = 1
def __init__(self, train_path, test_path=None):
def __init__(self, train_path, n_records_as_test=-1):
self.train_path = train_path
self.test_path = test_path
self.n_records_as_test = n_records_as_test
# task model: 0 train, 1 test
self.mode = 0
def train(self):
self.mode = self.TRAIN_MODE
return self._parse(self.train_path)
return self._parse(
self.train_path, skip_n_lines=self.n_records_as_test)
def test(self):
self.mode = self.TEST_MODE
return self._parse(self.test_path)
return self._parse(self.train_path, top_n_lines=self.n_records_as_test)
def _parse(self, path):
def _parse(self, path, skip_n_lines=-1, top_n_lines=-1):
with open(path, 'rb') as csvfile:
reader = csv.DictReader(csvfile)
......@@ -204,6 +226,11 @@ class AvazuDataset(object):
id_dims = [feature_dims[key] for key in id_features]
for row_id, row in enumerate(reader):
if skip_n_lines > 0 and row_id < skip_n_lines:
continue
if top_n_lines > 0 and row_id > top_n_lines:
break
record = []
for key in categorial_features:
record.append(fields[key].gen(row[key]))
......@@ -218,10 +245,7 @@ class AvazuDataset(object):
record = [dense_input, sparse_input]
if self.mode == self.TRAIN_MODE:
record.append(list((int(row['click']), )))
# print record
yield record
......
......@@ -10,19 +10,20 @@ from data_provider import categorial_features, id_features, field_index, detect_
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)
data_meta_info = detect_dataset(train_data_path, 500000)
batch_size = 1000 * 11
test_set_size = 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)
paddle.init(use_gpu=False, trainer_count=11)
# ==============================================================================
# input layers
# ==============================================================================
dnn_merged_input = layer.data(
name='dnn_input',
type=paddle.data_type.sparse_binary_vector(data_meta_info['dnn_input']))
......@@ -40,13 +41,6 @@ click = paddle.layer.data(name='click', type=dtype.dense_vector(1))
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(
......@@ -94,17 +88,26 @@ 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
dataset = AvazuDataset(train_data_path, n_records_as_test=test_set_size)
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
logging.warning("Pass %d, Samples %d, Cost %f" % (
event.pass_id, event.batch_id * batch_size, event.cost))
def event_hander(event):
global step
if event.batch_id % 1000 == 0:
result = trainer.test(
reader=paddle.batch(dataset.test, batch_size=1000),
feeding=field_index)
logging.warning("Test %d-%d, Cost %f" % (event.pass_id, event.batch_id,
result.cost))
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(dataset.train, buf_size=500), batch_size=5),
paddle.reader.shuffle(dataset.train, buf_size=500),
batch_size=batch_size),
feeding=field_index,
num_passes=1)
event_handler=event_handler,
num_passes=100)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册