README.md 13.8 KB
Newer Older
1
# 点击率预估
S
Superjom 已提交
2

S
Superjom 已提交
3 4 5
以下是本例目录包含的文件以及对应说明:

```
S
fix PR  
Superjom 已提交
6 7 8
├── README.md               # 本教程markdown 文档
├── dataset.md              # 数据集处理教程
├── images                  # 本教程图片目录
S
Superjom 已提交
9 10
│   ├── lr_vs_dnn.jpg
│   └── wide_deep.png
S
fix PR  
Superjom 已提交
11 12 13 14 15 16
├── infer.py                # 预测脚本
├── network_conf.py         # 模型网络配置
├── reader.py               # data reader
├── train.py                # 训练脚本
└── utils.py                # helper functions
└── avazu_data_processer.py # 示例数据预处理脚本
S
Superjom 已提交
17 18
```

S
Superjom 已提交
19
## 背景介绍
S
Superjom 已提交
20

S
fix PR  
Superjom 已提交
21 22
CTR(Click-Through Rate,点击率预估)\[[1](https://en.wikipedia.org/wiki/Click-through_rate)\]
是对用户点击一个特定链接的概率做出预测,是广告投放过程中的一个重要环节。精准的点击率预估对在线广告系统收益最大化具有重要意义。
S
Superjom 已提交
23

S
fix PR  
Superjom 已提交
24
当有多个广告位时,CTR 预估一般会作为排序的基准,比如在搜索引擎的广告系统里,当用户输入一个带商业价值的搜索词(query)时,系统大体上会执行下列步骤来展示广告:
S
Superjom 已提交
25

S
Superjom 已提交
26
1.  获取与用户搜索词相关的广告集合
S
Superjom 已提交
27 28
2.  业务规则和相关性过滤
3.  根据拍卖机制和 CTR 排序
S
Superjom 已提交
29
4.  展出广告
S
Superjom 已提交
30 31

可以看到,CTR 在最终排序中起到了很重要的作用。
S
Superjom 已提交
32

S
Superjom 已提交
33
### 发展阶段
S
Superjom 已提交
34 35
在业内,CTR 模型经历了如下的发展阶段:

S
Superjom 已提交
36
-   Logistic Regression(LR) / GBDT + 特征工程
S
Superjom 已提交
37 38 39
-   LR + DNN 特征
-   DNN + 特征工程

S
Superjom 已提交
40
在发展早期时 LR 一统天下,但最近 DNN 模型由于其强大的学习能力和逐渐成熟的性能优化,
S
Superjom 已提交
41 42 43
逐渐地接过 CTR 预估任务的大旗。


S
Superjom 已提交
44
### LR vs DNN
S
Superjom 已提交
45

S
Superjom 已提交
46
下图展示了 LR 和一个 \(3x2\) 的 DNN 模型的结构:
S
Superjom 已提交
47

S
Superjom 已提交
48 49
<p align="center">
<img src="images/lr_vs_dnn.jpg" width="620" hspace='10'/> <br/>
S
Superjom 已提交
50
Figure 1. LR 和 DNN 模型结构对比
S
Superjom 已提交
51
</p>
S
Superjom 已提交
52

S
Superjom 已提交
53
LR 的蓝色箭头部分可以直接类比到 DNN 中对应的结构,可以看到 LR 和 DNN 有一些共通之处(比如权重累加),
S
fix PR  
Superjom 已提交
54
但前者的模型复杂度在相同输入维度下比后者可能低很多(从某方面讲,模型越复杂,越有潜力学习到更复杂的信息);
S
Superjom 已提交
55
如果 LR 要达到匹敌 DNN 的学习能力,必须增加输入的维度,也就是增加特征的数量,
S
Superjom 已提交
56 57
这也就是为何 LR 和大规模的特征工程必须绑定在一起的原因。

S
fix PR  
Superjom 已提交
58
LR 对于 DNN 模型的优势是对大规模稀疏特征的容纳能力,包括内存和计算量等方面,工业界都有非常成熟的优化方法;
S
Superjom 已提交
59 60
而 DNN 模型具有自己学习新特征的能力,一定程度上能够提升特征使用的效率,
这使得 DNN 模型在同样规模特征的情况下,更有可能达到更好的学习效果。
S
Superjom 已提交
61

S
Superjom 已提交
62
本文后面的章节会演示如何使用 PaddlePaddle 编写一个结合两者优点的模型。
S
Superjom 已提交
63

S
Superjom 已提交
64

S
Superjom 已提交
65
## 数据和任务抽象
S
Superjom 已提交
66

S
Superjom 已提交
67
我们可以将 `click` 作为学习目标,任务可以有以下几种方案:
S
Superjom 已提交
68

S
Superjom 已提交
69
1.  直接学习 click,0,1 作二元分类
S
Superjom 已提交
70
2.  Learning to rank, 具体用 pairwise rank(标签 1>0)或者 listwise rank
S
Superjom 已提交
71
3.  统计每个广告的点击率,将同一个 query 下的广告两两组合,点击率高的>点击率低的,做 rank 或者分类
S
Superjom 已提交
72

S
Superjom 已提交
73
我们直接使用第一种方法做分类任务。
S
Superjom 已提交
74

S
fix PR  
Superjom 已提交
75
我们使用 Kaggle 上 `Click-through rate prediction` 任务的数据集\[[2](https://www.kaggle.com/c/avazu-ctr-prediction/data)\] 来演示本例中的模型。
S
Superjom 已提交
76

S
Superjom 已提交
77 78 79 80 81 82 83 84 85 86
具体的特征处理方法参看 [data process](./dataset.md)

本教程中演示模型的输入格式如下:

```
# <dnn input ids> \t <lr input sparse values> \t click
1 23 190 \t 230:0.12 3421:0.9 23451:0.12 \t 0
23 231 \t 1230:0.12 13421:0.9 \t 1
```

S
fix PR  
Superjom 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
详细的格式描述如下:

- `dnn input ids` 采用 one-hot 表示,只需要填写值为1的ID(注意这里不是变长输入)
- `lr input sparse values` 使用了 `ID:VALUE` 的表示,值部分最好规约到值域 `[-1, 1]`

此外,模型训练时需要传入一个文件描述 dnn 和 lr两个子模型的输入维度,文件的格式如下:

```
dnn_input_dim: <int>
lr_input_dim: <int>
```

其中, `<int>` 表示一个整型数值。

本目录下的 `avazu_data_processor.py` 可以对下载的演示数据集\[[2](#参考文档)\] 进行处理,具体使用方法参考如下说明:
S
Superjom 已提交
102 103 104 105 106 107 108 109 110

```
usage: avazu_data_processer.py [-h] --data_path DATA_PATH --output_dir
                               OUTPUT_DIR
                               [--num_lines_to_detect NUM_LINES_TO_DETECT]
                               [--test_set_size TEST_SET_SIZE]
                               [--train_size TRAIN_SIZE]

PaddlePaddle CTR example
S
Superjom 已提交
111

S
Superjom 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124
optional arguments:
  -h, --help            show this help message and exit
  --data_path DATA_PATH
                        path of the Avazu dataset
  --output_dir OUTPUT_DIR
                        directory to output
  --num_lines_to_detect NUM_LINES_TO_DETECT
                        number of records to detect dataset's meta info
  --test_set_size TEST_SET_SIZE
                        size of the validation dataset(default: 10000)
  --train_size TRAIN_SIZE
                        size of the trainset (default: 100000)
```
S
Superjom 已提交
125

S
fix PR  
Superjom 已提交
126 127 128 129 130 131
- `data_path` 是待处理的数据路径
- `output_dir` 生成数据的输出路径
- `num_lines_to_detect` 预先扫描数据生成ID的个数,这里是扫描的文件行数
- `test_set_size` 生成测试集的行数
- `train_size` 生成训练姐的行数

S
Superjom 已提交
132
## Wide & Deep Learning Model
S
Superjom 已提交
133

S
Superjom 已提交
134
谷歌在 16 年提出了 Wide & Deep Learning 的模型框架,用于融合适合学习抽象特征的 DNN 和 适用于大规模稀疏特征的 LR 两种模型的优点。
S
Superjom 已提交
135 136


S
Superjom 已提交
137
### 模型简介
S
Superjom 已提交
138

S
Superjom 已提交
139
Wide & Deep Learning Model\[[3](#参考文献)\] 可以作为一种相对成熟的模型框架使用,
S
Superjom 已提交
140
在 CTR 预估的任务中工业界也有一定的应用,因此本文将演示使用此模型来完成 CTR 预估的任务。
S
Superjom 已提交
141

S
Superjom 已提交
142
模型结构如下:
S
Superjom 已提交
143

S
Superjom 已提交
144 145 146 147
<p align="center">
<img src="images/wide_deep.png" width="820" hspace='10'/> <br/>
Figure 2. Wide & Deep Model
</p>
S
Superjom 已提交
148

S
Superjom 已提交
149 150
模型左边的 Wide 部分,可以容纳大规模系数特征,并且对一些特定的信息(比如 ID)有一定的记忆能力;
而模型右边的 Deep 部分,能够学习特征间的隐含关系,在相同数量的特征下有更好的学习和推导能力。
S
Superjom 已提交
151 152


S
Superjom 已提交
153
### 编写模型输入
S
Superjom 已提交
154

S
Superjom 已提交
155
模型只接受 3 个输入,分别是
S
Superjom 已提交
156

S
Superjom 已提交
157 158 159
-   `dnn_input` ,也就是 Deep 部分的输入
-   `lr_input` ,也就是 Wide 部分的输入
-   `click` , 点击与否,作为二分类模型学习的标签
S
Superjom 已提交
160

S
Superjom 已提交
161
```python
S
Superjom 已提交
162 163 164
dnn_merged_input = layer.data(
    name='dnn_input',
    type=paddle.data_type.sparse_binary_vector(data_meta_info['dnn_input']))
S
Superjom 已提交
165

S
Superjom 已提交
166 167 168
lr_merged_input = layer.data(
    name='lr_input',
    type=paddle.data_type.sparse_binary_vector(data_meta_info['lr_input']))
S
Superjom 已提交
169

S
Superjom 已提交
170 171 172
click = paddle.layer.data(name='click', type=dtype.dense_vector(1))
```

S
Superjom 已提交
173
### 编写 Wide 部分
S
Superjom 已提交
174

S
Superjom 已提交
175 176 177
Wide 部分直接使用了 LR 模型,但激活函数改成了 `RELU` 来加速

```python
S
Superjom 已提交
178 179 180 181 182 183
def build_lr_submodel():
    fc = layer.fc(
        input=lr_merged_input, size=1, name='lr', act=paddle.activation.Relu())
    return fc
```

S
Superjom 已提交
184
### 编写 Deep 部分
S
Superjom 已提交
185

S
Superjom 已提交
186
Deep 部分使用了标准的多层前向传导的 DNN 模型
S
Superjom 已提交
187 188

```python
S
Superjom 已提交
189 190 191
def build_dnn_submodel(dnn_layer_dims):
    dnn_embedding = layer.fc(input=dnn_merged_input, size=dnn_layer_dims[0])
    _input_layer = dnn_embedding
S
Superjom 已提交
192
    for i, dim in enumerate(dnn_layer_dims[1:]):
S
Superjom 已提交
193 194 195 196
        fc = layer.fc(
            input=_input_layer,
            size=dim,
            act=paddle.activation.Relu(),
S
Superjom 已提交
197
            name='dnn-fc-%d' % i)
S
Superjom 已提交
198 199 200 201
        _input_layer = fc
    return _input_layer
```

S
Superjom 已提交
202
### 两者融合
S
Superjom 已提交
203

S
Superjom 已提交
204 205
两个 submodel 的最上层输出加权求和得到整个模型的输出,输出部分使用 `sigmoid` 作为激活函数,得到区间 (0,1) 的预测值,
来逼近训练数据中二元类别的分布,并最终作为 CTR 预估的值使用。
S
Superjom 已提交
206 207

```python
S
Superjom 已提交
208 209 210 211 212 213 214
# 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',
S
Superjom 已提交
215
        # use sigmoid function to approximate ctr, wihch is a float value between 0 and 1.
S
Superjom 已提交
216 217 218 219
        act=paddle.activation.Sigmoid())
    return fc
```

S
Superjom 已提交
220
### 训练任务的定义
S
Superjom 已提交
221
```python
S
Superjom 已提交
222 223 224 225 226 227 228 229 230
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)
S
Superjom 已提交
231

S
Superjom 已提交
232 233 234

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

S
Superjom 已提交
235
params = paddle.parameters.create(classification_cost)
S
Superjom 已提交
236

S
Superjom 已提交
237
optimizer = paddle.optimizer.Momentum(momentum=0)
S
Superjom 已提交
238

S
Superjom 已提交
239 240
trainer = paddle.trainer.SGD(
    cost=classification_cost, parameters=params, update_equation=optimizer)
S
Superjom 已提交
241

S
Superjom 已提交
242
dataset = AvazuDataset(train_data_path, n_records_as_test=test_set_size)
S
Superjom 已提交
243

S
Superjom 已提交
244 245 246 247 248
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))
S
Superjom 已提交
249

S
Superjom 已提交
250 251 252 253 254 255
        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))
S
Superjom 已提交
256 257


S
Superjom 已提交
258 259 260 261 262 263 264 265
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)
```
S
Superjom 已提交
266 267 268
## 运行训练和测试
训练模型需要如下步骤:

S
fix PR  
Superjom 已提交
269
1. 准备训练数据
S
Superjom 已提交
270 271
    1.[Kaggle CTR](https://www.kaggle.com/c/avazu-ctr-prediction/data) 下载 train.gz
    2. 解压 train.gz 得到 train.txt
S
Superjom 已提交
272 273
    3. `mkdir -p output; python avazu_data_processer.py --data_path train.txt --output_dir output --num_lines_to_detect 1000 --test_set_size 100` 生成演示数据
2. 执行 `python train.py --train_data_path ./output/train.txt --test_data_path ./output/test.txt --data_meta_file ./output/data.meta.txt --model_type=0` 开始训练
S
Superjom 已提交
274 275 276 277 278

上面第2个步骤可以为 `train.py` 填充命令行参数来定制模型的训练过程,具体的命令行参数及用法如下

```
usage: train.py [-h] --train_data_path TRAIN_DATA_PATH
S
Superjom 已提交
279
                [--test_data_path TEST_DATA_PATH] [--batch_size BATCH_SIZE]
S
Superjom 已提交
280
                [--num_passes NUM_PASSES]
S
Superjom 已提交
281 282
                [--model_output_prefix MODEL_OUTPUT_PREFIX] --data_meta_file
                DATA_META_FILE --model_type MODEL_TYPE
S
Superjom 已提交
283 284 285 286 287 288 289

PaddlePaddle CTR example

optional arguments:
  -h, --help            show this help message and exit
  --train_data_path TRAIN_DATA_PATH
                        path of training dataset
S
Superjom 已提交
290 291
  --test_data_path TEST_DATA_PATH
                        path of testing dataset
S
Superjom 已提交
292 293 294 295
  --batch_size BATCH_SIZE
                        size of mini-batch (default:10000)
  --num_passes NUM_PASSES
                        number of passes to train
S
Superjom 已提交
296 297 298 299 300 301 302 303 304 305
  --model_output_prefix MODEL_OUTPUT_PREFIX
                        prefix of path for model to store (default:
                        ./ctr_models)
  --data_meta_file DATA_META_FILE
                        path of data meta info file
  --model_type MODEL_TYPE
                        model type, classification: 0, regression 1 (default
                        classification)
```

S
Superjom 已提交
306 307 308 309 310 311 312
- `train_data_path` : 训练集的路径
- `test_data_path` : 测试集的路径
- `num_passes`: 模型训练多少轮
- `data_meta_file`: 参考[数据和任务抽象](### 数据和任务抽象)的描述。
- `model_type`: 模型分类或回归


S
Superjom 已提交
313 314 315 316 317 318 319 320 321
## 用训好的模型做预测
训好的模型可以用来预测新的数据, 预测数据的格式为

```
# <dnn input ids> \t <lr input sparse values>
1 23 190 \t 230:0.12 3421:0.9 23451:0.12
23 231 \t 1230:0.12 13421:0.9
```

S
fix PR  
Superjom 已提交
322 323
这里与训练数据的格式唯一不同的地方,就是没有标签,也就是训练数据中第3列 `click` 对应的数值。

S
Superjom 已提交
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
`infer.py` 的使用方法如下

```
usage: infer.py [-h] --model_gz_path MODEL_GZ_PATH --data_path DATA_PATH
                --prediction_output_path PREDICTION_OUTPUT_PATH
                [--data_meta_path DATA_META_PATH] --model_type MODEL_TYPE

PaddlePaddle CTR example

optional arguments:
  -h, --help            show this help message and exit
  --model_gz_path MODEL_GZ_PATH
                        path of model parameters gz file
  --data_path DATA_PATH
                        path of the dataset to infer
  --prediction_output_path PREDICTION_OUTPUT_PATH
                        path to output the prediction
  --data_meta_path DATA_META_PATH
                        path of trainset's meta info, default is ./data.meta
  --model_type MODEL_TYPE
                        model type, classification: 0, regression 1 (default
                        classification)
S
Superjom 已提交
346
```
S
Superjom 已提交
347

S
Superjom 已提交
348 349 350 351 352 353
- `model_gz_path_model`:用 `gz` 压缩过的模型路径
- `data_path` : 需要预测的数据路径
- `prediction_output_paht`:预测输出的路径
- `data_meta_file` :参考[数据和任务抽象](### 数据和任务抽象)的描述。
- `model_type` :分类或回归

S
Superjom 已提交
354 355 356 357 358 359 360 361
示例数据可以用如下命令预测

```
python infer.py --model_gz_path <model_path> --data_path output/infer.txt --prediction_output_path predictions.txt --data_meta_path data.meta.txt
```

最终的预测结果位于 `predictions.txt`

S
Superjom 已提交
362
## 参考文献
S
Superjom 已提交
363
1. <https://en.wikipedia.org/wiki/Click-through_rate>
S
Superjom 已提交
364 365
2. <https://www.kaggle.com/c/avazu-ctr-prediction/data>
3. Cheng H T, Koc L, Harmsen J, et al. [Wide & deep learning for recommender systems](https://arxiv.org/pdf/1606.07792.pdf)[C]//Proceedings of the 1st Workshop on Deep Learning for Recommender Systems. ACM, 2016: 7-10.