提交 e61a44c2 编写于 作者: C ceci3

Merge branch 'develop' of https://github.com/PaddlePaddle/book into develop

#!/bin/bash
#This file is only used for continuous evaluation.
python train.py --enable_ce | python _ce.py
...@@ -3,10 +3,21 @@ ...@@ -3,10 +3,21 @@
本教程源代码目录在[book/fit_a_line](https://github.com/PaddlePaddle/book/tree/develop/01.fit_a_line), 初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书) 本教程源代码目录在[book/fit_a_line](https://github.com/PaddlePaddle/book/tree/develop/01.fit_a_line), 初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)
### 说明:
1.硬件环境要求:
本文可支持在CPU、GPU下运行
2. Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3. 文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/train.py)进行验证。
## 背景介绍 ## 背景介绍
给定一个大小为$n$的数据集 ${\{y_{i}, x_{i1}, ..., x_{id}\}}_{i=1}^{n}$,其中$x_{i1}, \ldots, x_{id}$是第$i$个样本$d$个属性上的取值,$y_i$是该样本待预测的目标。线性回归模型假设目标$y_i$可以被属性间的线性组合描述,即 给定一个大小为$n$的数据集 ${\{y_{i}, x_{i1}, ..., x_{id}\}}_{i=1}^{n}$,其中$x_{i1}, \ldots, x_{id}$是第$i$个样本$d$个属性上的取值,$y_i$是该样本待预测的目标。线性回归模型假设目标$y_i$可以被属性间的线性组合描述,即
$$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldots,n$$
<p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_1.png?raw=true" width=550><br/>
</p>
例如,在我们将要建模的房价预测问题里,$x_{ij}$是描述房子$i$的各种属性(比如房间的个数、周围学校和医院的个数、交通状况等),而 $y_i$是房屋的价格。 例如,在我们将要建模的房价预测问题里,$x_{ij}$是描述房子$i$的各种属性(比如房间的个数、周围学校和医院的个数、交通状况等),而 $y_i$是房屋的价格。
...@@ -25,7 +36,9 @@ $$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldo ...@@ -25,7 +36,9 @@ $$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldo
在波士顿房价数据集中,和房屋相关的值共有14个:前13个用来描述房屋相关的各种信息,即模型中的 $x_i$;最后一个值为我们要预测的该类房屋价格的中位数,即模型中的 $y_i$。因此,我们的模型就可以表示成: 在波士顿房价数据集中,和房屋相关的值共有14个:前13个用来描述房屋相关的各种信息,即模型中的 $x_i$;最后一个值为我们要预测的该类房屋价格的中位数,即模型中的 $y_i$。因此,我们的模型就可以表示成:
$$\hat{Y} = \omega_1X_{1} + \omega_2X_{2} + \ldots + \omega_{13}X_{13} + b$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_2.png?raw=true" width=350><br/>
</p>
$\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要学习的参数即:$\omega_1, \ldots, \omega_{13}, b$。 $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要学习的参数即:$\omega_1, \ldots, \omega_{13}, b$。
...@@ -33,13 +46,17 @@ $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要 ...@@ -33,13 +46,17 @@ $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要
对于线性回归模型来讲,最常见的损失函数就是均方误差(Mean Squared Error, [MSE](https://en.wikipedia.org/wiki/Mean_squared_error))了,它的形式是: 对于线性回归模型来讲,最常见的损失函数就是均方误差(Mean Squared Error, [MSE](https://en.wikipedia.org/wiki/Mean_squared_error))了,它的形式是:
$$MSE=\frac{1}{n}\sum_{i=1}^{n}{(\hat{Y_i}-Y_i)}^2$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_3.png?raw=true" width=200><br/>
</p>
即对于一个大小为$n$的测试集,$MSE$是$n$个数据预测结果误差平方的均值。 即对于一个大小为$n$的测试集,$MSE$是$n$个数据预测结果误差平方的均值。
对损失函数进行优化所采用的方法一般为梯度下降法。梯度下降法是一种一阶最优化算法。如果$f(x)$在点$x_n$有定义且可微,则认为$f(x)$在点$x_n$沿着梯度的负方向$-▽f(x_n)$下降的是最快的。反复调节$x$,使得$f(x)$接近最小值或者极小值,调节的方式为: 对损失函数进行优化所采用的方法一般为梯度下降法。梯度下降法是一种一阶最优化算法。如果$f(x)$在点$x_n$有定义且可微,则认为$f(x)$在点$x_n$沿着梯度的负方向$-▽f(x_n)$下降的是最快的。反复调节$x$,使得$f(x)$接近最小值或者极小值,调节的方式为:
$$x_n+1=x_n-λ▽f(x), n≧0$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_4.png?raw=true" width=250><br/>
</p>
其中λ代表学习率。这种调节的方法称为梯度下降法。 其中λ代表学习率。这种调节的方法称为梯度下降法。
...@@ -101,17 +118,17 @@ $$x_n+1=x_n-λ▽f(x), n≧0$$ ...@@ -101,17 +118,17 @@ $$x_n+1=x_n-λ▽f(x), n≧0$$
## 训练 ## 训练
`fit_a_line/trainer.py`演示了训练的整体过程。 `fit_a_line/train.py`演示了训练的整体过程。
### 配置数据提供器(Datafeeder) ### 配置数据提供器(Datafeeder)
首先我们引入必要的库: 首先我们引入必要的库:
```python ```python
from __future__ import print_function
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy import numpy
import math import math
import sys import sys
from __future__ import print_function
``` ```
我们通过uci_housing模块引入了数据集合[UCI Housing Data Set](http://paddlemodels.bj.bcebos.com/uci_housing/housing.data) 我们通过uci_housing模块引入了数据集合[UCI Housing Data Set](http://paddlemodels.bj.bcebos.com/uci_housing/housing.data)
...@@ -119,7 +136,7 @@ from __future__ import print_function ...@@ -119,7 +136,7 @@ from __future__ import print_function
其中,在uci_housing模块中封装了: 其中,在uci_housing模块中封装了:
1. 数据下载的过程。下载数据保存在~/.cache/paddle/dataset/uci_housing/housing.data。 1. 数据下载的过程。下载数据保存在~/.cache/paddle/dataset/uci_housing/housing.data。
2. [数据预处理](#数据预处理)的过程。 2. 数据预处理的过程。
接下来我们定义了用于训练的数据提供器。提供器每次读入一个大小为`BATCH_SIZE`的数据批次。如果用户希望加一些随机性,它可以同时定义一个批次大小和一个缓存大小。这样的话,每次数据提供器会从缓存中随机读取批次大小那么多的数据。 接下来我们定义了用于训练的数据提供器。提供器每次读入一个大小为`BATCH_SIZE`的数据批次。如果用户希望加一些随机性,它可以同时定义一个批次大小和一个缓存大小。这样的话,每次数据提供器会从缓存中随机读取批次大小那么多的数据。
...@@ -163,14 +180,18 @@ train_data = data[:offset] ...@@ -163,14 +180,18 @@ train_data = data[:offset]
test_data = data[offset:] test_data = data[offset:]
def reader(data):
for d in train_data:
yield d[:1], d[-1:]
train_reader = paddle.batch( train_reader = paddle.batch(
paddle.reader.shuffle( paddle.reader.shuffle(
train_data, buf_size=500), reader(train_data), buf_size=500),
batch_size=BATCH_SIZE) batch_size=BATCH_SIZE)
test_reader = paddle.batch( test_reader = paddle.batch(
paddle.reader.shuffle( paddle.reader.shuffle(
test_data, buf_size=500), reader(test_data), buf_size=500),
batch_size=BATCH_SIZE) batch_size=BATCH_SIZE)
### 配置训练程序 ### 配置训练程序
...@@ -196,13 +217,14 @@ avg_loss = fluid.layers.mean(cost) # 对方差求均值,得到平均损失 ...@@ -196,13 +217,14 @@ avg_loss = fluid.layers.mean(cost) # 对方差求均值,得到平均损失
在下面的 `SGD optimizer``learning_rate` 是学习率,与网络的训练收敛速度有关系。 在下面的 `SGD optimizer``learning_rate` 是学习率,与网络的训练收敛速度有关系。
```python ```python
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)
sgd_optimizer.minimize(avg_loss)
#克隆main_program得到test_program #克隆main_program得到test_program
#有些operator在训练和测试之间的操作是不同的,例如batch_norm,使用参数for_test来区分该程序是用来训练还是用来测试 #有些operator在训练和测试之间的操作是不同的,例如batch_norm,使用参数for_test来区分该程序是用来训练还是用来测试
#该api不会删除任何操作符,请在backward和optimization之前使用 #该api不会删除任何操作符,请在backward和optimization之前使用
test_program = main_program.clone(for_test=True) test_program = main_program.clone(for_test=True)
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)
sgd_optimizer.minimize(avg_loss)
``` ```
### 定义运算场所 ### 定义运算场所
...@@ -220,7 +242,7 @@ exe = fluid.Executor(place) ...@@ -220,7 +242,7 @@ exe = fluid.Executor(place)
[fluid.executor](http://www.paddlepaddle.org/documentation/docs/zh/develop/api_cn/fluid_cn.html#permalink-15-executor) [fluid.executor](http://www.paddlepaddle.org/documentation/docs/zh/develop/api_cn/fluid_cn.html#permalink-15-executor)
### 创建训练过程 ### 创建训练过程
训练需要有一个训练程序和一些必要参数,并构建了一个获取训练过程中测试误差的函数。必要参数有executor,program,reader,feeder,fetch_list,executor表示之前创建的执行器,program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用defalut_main_program,reader表示读取到的数据,feeder表示前向输入的变量,fetch_list表示用户想得到的变量或者命名的结果。 训练需要有一个训练程序和一些必要参数,并构建了一个获取训练过程中测试误差的函数。必要参数有executor,program,reader,feeder,fetch_list,executor表示之前创建的执行器,program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用default_main_program,reader表示读取到的数据,feeder表示前向输入的变量,fetch_list表示用户想得到的变量或者命名的结果。
```python ```python
num_epochs = 100 num_epochs = 100
...@@ -236,24 +258,6 @@ def train_test(executor, program, reader, feeder, fetch_list): ...@@ -236,24 +258,6 @@ def train_test(executor, program, reader, feeder, fetch_list):
count += 1 # 累加测试集中的样本数量 count += 1 # 累加测试集中的样本数量
return [x_d / count for x_d in accumulated] # 计算平均损失 return [x_d / count for x_d in accumulated] # 计算平均损失
```
可以直接输出损失值来观察`训练进程`:
```python
train_prompt = "train cost"
test_prompt = "test cost"
print("%s', out %f" % (train_prompt, out))
print("%s', out %f" % (test_prompt, out))
```
除此之外,还可以通过画图,来展现`训练进程`
```python
from paddle.utils.plot import ploter
plot_prompt = ploter(train_prompt, test_prompt)
``` ```
### 训练主循环 ### 训练主循环
...@@ -264,8 +268,11 @@ plot_prompt = ploter(train_prompt, test_prompt) ...@@ -264,8 +268,11 @@ plot_prompt = ploter(train_prompt, test_prompt)
%matplotlib inline %matplotlib inline
params_dirname = "fit_a_line.inference.model" params_dirname = "fit_a_line.inference.model"
feeder = fluid.DataFeeder(place=place, feed_list=[x, y]) feeder = fluid.DataFeeder(place=place, feed_list=[x, y])
naive_exe = fluid.Executor(place) exe.run(startup_program)
naive_exe.run(startup_program) train_prompt = "train cost"
test_prompt = "test cost"
from paddle.utils.plot import Ploter
plot_prompt = Ploter(train_prompt, test_prompt)
step = 0 step = 0
exe_test = fluid.Executor(place) exe_test = fluid.Executor(place)
...@@ -280,10 +287,12 @@ for pass_id in range(num_epochs): ...@@ -280,10 +287,12 @@ for pass_id in range(num_epochs):
avg_loss_value, = exe.run(main_program, avg_loss_value, = exe.run(main_program,
feed=feeder.feed(data_train), feed=feeder.feed(data_train),
fetch_list=[avg_loss]) fetch_list=[avg_loss])
if step % 10 == 0: # 每10个批次记录一下训练损失 if step % 10 == 0: # 每10个批次记录并输出一下训练损失
plot_prompt.append(train_prompt, step, avg_loss_value[0]) plot_prompt.append(train_prompt, step, avg_loss_value[0])
plot_prompt.plot() plot_prompt.plot()
if step % 100 == 0: # 每100批次记录一下测试损失 print("%s, Step %d, Cost %f" %
(train_prompt, step, avg_loss_value[0]))
if step % 100 == 0: # 每100批次记录并输出一下测试损失
test_metics = train_test(executor=exe_test, test_metics = train_test(executor=exe_test,
program=test_program, program=test_program,
reader=test_reader, reader=test_reader,
...@@ -291,6 +300,8 @@ for pass_id in range(num_epochs): ...@@ -291,6 +300,8 @@ for pass_id in range(num_epochs):
feeder=feeder) feeder=feeder)
plot_prompt.append(test_prompt, step, test_metics[0]) plot_prompt.append(test_prompt, step, test_metics[0])
plot_prompt.plot() plot_prompt.plot()
print("%s, Step %d, Cost %f" %
(test_prompt, step, test_metics[0]))
if test_metics[0] < 10.0: # 如果准确率达到要求,则停止训练 if test_metics[0] < 10.0: # 如果准确率达到要求,则停止训练
break break
...@@ -316,6 +327,24 @@ inference_scope = fluid.core.Scope() ...@@ -316,6 +327,24 @@ inference_scope = fluid.core.Scope()
``` ```
### 预测 ### 预测
保存图片
```python
def save_result(points1, points2):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
x1 = [idx for idx in range(len(points1))]
y1 = points1
y2 = points2
l1 = plt.plot(x1, y1, 'r--', label='predictions')
l2 = plt.plot(x1, y2, 'g--', label='GT')
plt.plot(x1, y1, 'ro-', x1, y2, 'g+-')
plt.title('predictions VS GT')
plt.legend()
plt.savefig('./image/prediction_gt.png')
```
通过fluid.io.load_inference_model,预测器会从`params_dirname`中读取已经训练好的模型,来对从未遇见过的数据进行预测。 通过fluid.io.load_inference_model,预测器会从`params_dirname`中读取已经训练好的模型,来对从未遇见过的数据进行预测。
```python ```python
...@@ -337,27 +366,7 @@ with fluid.scope_guard(inference_scope): ...@@ -337,27 +366,7 @@ with fluid.scope_guard(inference_scope):
results = infer_exe.run(inference_program, results = infer_exe.run(inference_program,
feed={feed_target_names[0]: numpy.array(infer_feat)}, feed={feed_target_names[0]: numpy.array(infer_feat)},
fetch_list=fetch_targets) # 进行预测 fetch_list=fetch_targets) # 进行预测
``` #打印预测结果和标签并可视化结果
保存图片
```python
def save_result(points1, points2):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
x1 = [idx for idx in range(len(points1))]
y1 = points1
y2 = points2
l1 = plt.plot(x1, y1, 'r--', label='predictions')
l2 = plt.plot(x1, y2, 'g--', label='GT')
plt.plot(x1, y1, 'ro-', x1, y2, 'g+-')
plt.title('predictions VS GT')
plt.legend()
plt.savefig('./image/prediction_gt.png')
```
打印预测结果和标签并可视化结果
```python
print("infer results: (House Price)") print("infer results: (House Price)")
for idx, val in enumerate(results[0]): for idx, val in enumerate(results[0]):
print("%d: %.2f" % (idx, val)) # 打印预测结果 print("%d: %.2f" % (idx, val)) # 打印预测结果
...@@ -366,8 +375,10 @@ def save_result(points1, points2): ...@@ -366,8 +375,10 @@ def save_result(points1, points2):
for idx, val in enumerate(infer_label): for idx, val in enumerate(infer_label):
print("%d: %.2f" % (idx, val)) # 打印标签值 print("%d: %.2f" % (idx, val)) # 打印标签值
save_result(results[0], infer_label) # 保存图片 save_result(results[0], infer_label) # 保存图片
``` ```
由于每次都是随机选择一个minibatch的数据作为当前迭代的训练数据,所以每次得到的预测结果会有所不同。
## 总结 ## 总结
在这章里,我们借助波士顿房价这一数据集,介绍了线性回归模型的基本概念,以及如何使用PaddlePaddle实现训练和测试的过程。很多的模型和技巧都是从简单的线性回归模型演化而来,因此弄清楚线性模型的原理和局限非常重要。 在这章里,我们借助波士顿房价这一数据集,介绍了线性回归模型的基本概念,以及如何使用PaddlePaddle实现训练和测试的过程。很多的模型和技巧都是从简单的线性回归模型演化而来,因此弄清楚线性模型的原理和局限非常重要。
...@@ -380,4 +391,4 @@ save_result(results[0], infer_label) # 保存图片 ...@@ -380,4 +391,4 @@ save_result(results[0], infer_label) # 保存图片
4. Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128. 4. Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128.
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://www.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
此差异已折叠。
### This file is only used for continuous evaluation test!
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import os
import sys
sys.path.append(os.environ['ceroot'])
from kpi import CostKpi
train_cost_kpi = CostKpi('train_cost', 0.02, 0, actived=True, desc='train cost')
test_cost_kpi = CostKpi('test_cost', 0.02, 0, actived=True, desc='test cost')
tracking_kpis = [train_cost_kpi, test_cost_kpi]
def parse_log(log):
for line in log.split('\n'):
fs = line.strip().split('\t')
print(fs)
if len(fs) == 3 and fs[0] == 'kpis':
print("-----%s" % fs)
kpi_name = fs[1]
kpi_value = float(fs[2])
yield kpi_name, kpi_value
def log_to_ce(log):
kpi_tracker = {}
for kpi in tracking_kpis:
kpi_tracker[kpi.name] = kpi
for (kpi_name, kpi_value) in parse_log(log):
print(kpi_name, kpi_value)
kpi_tracker[kpi_name].add_record(kpi_value)
kpi_tracker[kpi_name].persist()
if __name__ == '__main__':
log = sys.stdin.read()
log_to_ce(log)
01.fit_a_line/image/ranges.png

8.6 KB | W: | H:

01.fit_a_line/image/ranges.png

6.6 KB | W: | H:

01.fit_a_line/image/ranges.png
01.fit_a_line/image/ranges.png
01.fit_a_line/image/ranges.png
01.fit_a_line/image/ranges.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -45,10 +45,21 @@ ...@@ -45,10 +45,21 @@
本教程源代码目录在[book/fit_a_line](https://github.com/PaddlePaddle/book/tree/develop/01.fit_a_line), 初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。 本教程源代码目录在[book/fit_a_line](https://github.com/PaddlePaddle/book/tree/develop/01.fit_a_line), 初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。
### 说明:
1.硬件环境要求:
本文可支持在CPU、GPU下运行
2. Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3. 文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/train.py)进行验证。
## 背景介绍 ## 背景介绍
给定一个大小为$n$的数据集 ${\{y_{i}, x_{i1}, ..., x_{id}\}}_{i=1}^{n}$,其中$x_{i1}, \ldots, x_{id}$是第$i$个样本$d$个属性上的取值,$y_i$是该样本待预测的目标。线性回归模型假设目标$y_i$可以被属性间的线性组合描述,即 给定一个大小为$n$的数据集 ${\{y_{i}, x_{i1}, ..., x_{id}\}}_{i=1}^{n}$,其中$x_{i1}, \ldots, x_{id}$是第$i$个样本$d$个属性上的取值,$y_i$是该样本待预测的目标。线性回归模型假设目标$y_i$可以被属性间的线性组合描述,即
$$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldots,n$$
<p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_1.png?raw=true" width=550><br/>
</p>
例如,在我们将要建模的房价预测问题里,$x_{ij}$是描述房子$i$的各种属性(比如房间的个数、周围学校和医院的个数、交通状况等),而 $y_i$是房屋的价格。 例如,在我们将要建模的房价预测问题里,$x_{ij}$是描述房子$i$的各种属性(比如房间的个数、周围学校和医院的个数、交通状况等),而 $y_i$是房屋的价格。
...@@ -67,7 +78,9 @@ $$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldo ...@@ -67,7 +78,9 @@ $$y_i = \omega_1x_{i1} + \omega_2x_{i2} + \ldots + \omega_dx_{id} + b, i=1,\ldo
在波士顿房价数据集中,和房屋相关的值共有14个:前13个用来描述房屋相关的各种信息,即模型中的 $x_i$;最后一个值为我们要预测的该类房屋价格的中位数,即模型中的 $y_i$。因此,我们的模型就可以表示成: 在波士顿房价数据集中,和房屋相关的值共有14个:前13个用来描述房屋相关的各种信息,即模型中的 $x_i$;最后一个值为我们要预测的该类房屋价格的中位数,即模型中的 $y_i$。因此,我们的模型就可以表示成:
$$\hat{Y} = \omega_1X_{1} + \omega_2X_{2} + \ldots + \omega_{13}X_{13} + b$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_2.png?raw=true" width=350><br/>
</p>
$\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要学习的参数即:$\omega_1, \ldots, \omega_{13}, b$。 $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要学习的参数即:$\omega_1, \ldots, \omega_{13}, b$。
...@@ -75,13 +88,17 @@ $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要 ...@@ -75,13 +88,17 @@ $\hat{Y}$ 表示模型的预测结果,用来和真实值$Y$区分。模型要
对于线性回归模型来讲,最常见的损失函数就是均方误差(Mean Squared Error, [MSE](https://en.wikipedia.org/wiki/Mean_squared_error))了,它的形式是: 对于线性回归模型来讲,最常见的损失函数就是均方误差(Mean Squared Error, [MSE](https://en.wikipedia.org/wiki/Mean_squared_error))了,它的形式是:
$$MSE=\frac{1}{n}\sum_{i=1}^{n}{(\hat{Y_i}-Y_i)}^2$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_3.png?raw=true" width=200><br/>
</p>
即对于一个大小为$n$的测试集,$MSE$是$n$个数据预测结果误差平方的均值。 即对于一个大小为$n$的测试集,$MSE$是$n$个数据预测结果误差平方的均值。
对损失函数进行优化所采用的方法一般为梯度下降法。梯度下降法是一种一阶最优化算法。如果$f(x)$在点$x_n$有定义且可微,则认为$f(x)$在点$x_n$沿着梯度的负方向$-▽f(x_n)$下降的是最快的。反复调节$x$,使得$f(x)$接近最小值或者极小值,调节的方式为: 对损失函数进行优化所采用的方法一般为梯度下降法。梯度下降法是一种一阶最优化算法。如果$f(x)$在点$x_n$有定义且可微,则认为$f(x)$在点$x_n$沿着梯度的负方向$-▽f(x_n)$下降的是最快的。反复调节$x$,使得$f(x)$接近最小值或者极小值,调节的方式为:
$$x_n+1=x_n-λ▽f(x), n≧0$$ <p align="center">
<img src = "https://github.com/PaddlePaddle/book/blob/develop/01.fit_a_line/image/formula_fit_a_line_4.png?raw=true" width=250><br/>
</p>
其中λ代表学习率。这种调节的方法称为梯度下降法。 其中λ代表学习率。这种调节的方法称为梯度下降法。
...@@ -143,17 +160,17 @@ $$x_n+1=x_n-λ▽f(x), n≧0$$ ...@@ -143,17 +160,17 @@ $$x_n+1=x_n-λ▽f(x), n≧0$$
## 训练 ## 训练
`fit_a_line/trainer.py`演示了训练的整体过程。 `fit_a_line/train.py`演示了训练的整体过程。
### 配置数据提供器(Datafeeder) ### 配置数据提供器(Datafeeder)
首先我们引入必要的库: 首先我们引入必要的库:
```python ```python
from __future__ import print_function
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy import numpy
import math import math
import sys import sys
from __future__ import print_function
``` ```
我们通过uci_housing模块引入了数据集合[UCI Housing Data Set](http://paddlemodels.bj.bcebos.com/uci_housing/housing.data) 我们通过uci_housing模块引入了数据集合[UCI Housing Data Set](http://paddlemodels.bj.bcebos.com/uci_housing/housing.data)
...@@ -161,7 +178,7 @@ from __future__ import print_function ...@@ -161,7 +178,7 @@ from __future__ import print_function
其中,在uci_housing模块中封装了: 其中,在uci_housing模块中封装了:
1. 数据下载的过程。下载数据保存在~/.cache/paddle/dataset/uci_housing/housing.data。 1. 数据下载的过程。下载数据保存在~/.cache/paddle/dataset/uci_housing/housing.data。
2. [数据预处理](#数据预处理)的过程。 2. 数据预处理的过程。
接下来我们定义了用于训练的数据提供器。提供器每次读入一个大小为`BATCH_SIZE`的数据批次。如果用户希望加一些随机性,它可以同时定义一个批次大小和一个缓存大小。这样的话,每次数据提供器会从缓存中随机读取批次大小那么多的数据。 接下来我们定义了用于训练的数据提供器。提供器每次读入一个大小为`BATCH_SIZE`的数据批次。如果用户希望加一些随机性,它可以同时定义一个批次大小和一个缓存大小。这样的话,每次数据提供器会从缓存中随机读取批次大小那么多的数据。
...@@ -205,14 +222,18 @@ train_data = data[:offset] ...@@ -205,14 +222,18 @@ train_data = data[:offset]
test_data = data[offset:] test_data = data[offset:]
def reader(data):
for d in train_data:
yield d[:1], d[-1:]
train_reader = paddle.batch( train_reader = paddle.batch(
paddle.reader.shuffle( paddle.reader.shuffle(
train_data, buf_size=500), reader(train_data), buf_size=500),
batch_size=BATCH_SIZE) batch_size=BATCH_SIZE)
test_reader = paddle.batch( test_reader = paddle.batch(
paddle.reader.shuffle( paddle.reader.shuffle(
test_data, buf_size=500), reader(test_data), buf_size=500),
batch_size=BATCH_SIZE) batch_size=BATCH_SIZE)
### 配置训练程序 ### 配置训练程序
...@@ -238,13 +259,14 @@ avg_loss = fluid.layers.mean(cost) # 对方差求均值,得到平均损失 ...@@ -238,13 +259,14 @@ avg_loss = fluid.layers.mean(cost) # 对方差求均值,得到平均损失
在下面的 `SGD optimizer`,`learning_rate` 是学习率,与网络的训练收敛速度有关系。 在下面的 `SGD optimizer`,`learning_rate` 是学习率,与网络的训练收敛速度有关系。
```python ```python
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)
sgd_optimizer.minimize(avg_loss)
#克隆main_program得到test_program #克隆main_program得到test_program
#有些operator在训练和测试之间的操作是不同的,例如batch_norm,使用参数for_test来区分该程序是用来训练还是用来测试 #有些operator在训练和测试之间的操作是不同的,例如batch_norm,使用参数for_test来区分该程序是用来训练还是用来测试
#该api不会删除任何操作符,请在backward和optimization之前使用 #该api不会删除任何操作符,请在backward和optimization之前使用
test_program = main_program.clone(for_test=True) test_program = main_program.clone(for_test=True)
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)
sgd_optimizer.minimize(avg_loss)
``` ```
### 定义运算场所 ### 定义运算场所
...@@ -262,7 +284,7 @@ exe = fluid.Executor(place) ...@@ -262,7 +284,7 @@ exe = fluid.Executor(place)
[fluid.executor](http://www.paddlepaddle.org/documentation/docs/zh/develop/api_cn/fluid_cn.html#permalink-15-executor) [fluid.executor](http://www.paddlepaddle.org/documentation/docs/zh/develop/api_cn/fluid_cn.html#permalink-15-executor)
### 创建训练过程 ### 创建训练过程
训练需要有一个训练程序和一些必要参数,并构建了一个获取训练过程中测试误差的函数。必要参数有executor,program,reader,feeder,fetch_list,executor表示之前创建的执行器,program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用defalut_main_program,reader表示读取到的数据,feeder表示前向输入的变量,fetch_list表示用户想得到的变量或者命名的结果。 训练需要有一个训练程序和一些必要参数,并构建了一个获取训练过程中测试误差的函数。必要参数有executor,program,reader,feeder,fetch_list,executor表示之前创建的执行器,program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用default_main_program,reader表示读取到的数据,feeder表示前向输入的变量,fetch_list表示用户想得到的变量或者命名的结果。
```python ```python
num_epochs = 100 num_epochs = 100
...@@ -278,24 +300,6 @@ def train_test(executor, program, reader, feeder, fetch_list): ...@@ -278,24 +300,6 @@ def train_test(executor, program, reader, feeder, fetch_list):
count += 1 # 累加测试集中的样本数量 count += 1 # 累加测试集中的样本数量
return [x_d / count for x_d in accumulated] # 计算平均损失 return [x_d / count for x_d in accumulated] # 计算平均损失
```
可以直接输出损失值来观察`训练进程`:
```python
train_prompt = "train cost"
test_prompt = "test cost"
print("%s', out %f" % (train_prompt, out))
print("%s', out %f" % (test_prompt, out))
```
除此之外,还可以通过画图,来展现`训练进程`:
```python
from paddle.utils.plot import ploter
plot_prompt = ploter(train_prompt, test_prompt)
``` ```
### 训练主循环 ### 训练主循环
...@@ -306,8 +310,11 @@ plot_prompt = ploter(train_prompt, test_prompt) ...@@ -306,8 +310,11 @@ plot_prompt = ploter(train_prompt, test_prompt)
%matplotlib inline %matplotlib inline
params_dirname = "fit_a_line.inference.model" params_dirname = "fit_a_line.inference.model"
feeder = fluid.DataFeeder(place=place, feed_list=[x, y]) feeder = fluid.DataFeeder(place=place, feed_list=[x, y])
naive_exe = fluid.Executor(place) exe.run(startup_program)
naive_exe.run(startup_program) train_prompt = "train cost"
test_prompt = "test cost"
from paddle.utils.plot import Ploter
plot_prompt = Ploter(train_prompt, test_prompt)
step = 0 step = 0
exe_test = fluid.Executor(place) exe_test = fluid.Executor(place)
...@@ -322,10 +329,12 @@ for pass_id in range(num_epochs): ...@@ -322,10 +329,12 @@ for pass_id in range(num_epochs):
avg_loss_value, = exe.run(main_program, avg_loss_value, = exe.run(main_program,
feed=feeder.feed(data_train), feed=feeder.feed(data_train),
fetch_list=[avg_loss]) fetch_list=[avg_loss])
if step % 10 == 0: # 每10个批次记录一下训练损失 if step % 10 == 0: # 每10个批次记录并输出一下训练损失
plot_prompt.append(train_prompt, step, avg_loss_value[0]) plot_prompt.append(train_prompt, step, avg_loss_value[0])
plot_prompt.plot() plot_prompt.plot()
if step % 100 == 0: # 每100批次记录一下测试损失 print("%s, Step %d, Cost %f" %
(train_prompt, step, avg_loss_value[0]))
if step % 100 == 0: # 每100批次记录并输出一下测试损失
test_metics = train_test(executor=exe_test, test_metics = train_test(executor=exe_test,
program=test_program, program=test_program,
reader=test_reader, reader=test_reader,
...@@ -333,6 +342,8 @@ for pass_id in range(num_epochs): ...@@ -333,6 +342,8 @@ for pass_id in range(num_epochs):
feeder=feeder) feeder=feeder)
plot_prompt.append(test_prompt, step, test_metics[0]) plot_prompt.append(test_prompt, step, test_metics[0])
plot_prompt.plot() plot_prompt.plot()
print("%s, Step %d, Cost %f" %
(test_prompt, step, test_metics[0]))
if test_metics[0] < 10.0: # 如果准确率达到要求则停止训练 if test_metics[0] < 10.0: # 如果准确率达到要求则停止训练
break break
...@@ -358,6 +369,24 @@ inference_scope = fluid.core.Scope() ...@@ -358,6 +369,24 @@ inference_scope = fluid.core.Scope()
``` ```
### 预测 ### 预测
保存图片
```python
def save_result(points1, points2):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
x1 = [idx for idx in range(len(points1))]
y1 = points1
y2 = points2
l1 = plt.plot(x1, y1, 'r--', label='predictions')
l2 = plt.plot(x1, y2, 'g--', label='GT')
plt.plot(x1, y1, 'ro-', x1, y2, 'g+-')
plt.title('predictions VS GT')
plt.legend()
plt.savefig('./image/prediction_gt.png')
```
通过fluid.io.load_inference_model预测器会从`params_dirname`中读取已经训练好的模型来对从未遇见过的数据进行预测 通过fluid.io.load_inference_model预测器会从`params_dirname`中读取已经训练好的模型来对从未遇见过的数据进行预测
```python ```python
...@@ -379,27 +408,7 @@ with fluid.scope_guard(inference_scope): ...@@ -379,27 +408,7 @@ with fluid.scope_guard(inference_scope):
results = infer_exe.run(inference_program, results = infer_exe.run(inference_program,
feed={feed_target_names[0]: numpy.array(infer_feat)}, feed={feed_target_names[0]: numpy.array(infer_feat)},
fetch_list=fetch_targets) # 进行预测 fetch_list=fetch_targets) # 进行预测
``` #打印预测结果和标签并可视化结果
保存图片
```python
def save_result(points1, points2):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
x1 = [idx for idx in range(len(points1))]
y1 = points1
y2 = points2
l1 = plt.plot(x1, y1, 'r--', label='predictions')
l2 = plt.plot(x1, y2, 'g--', label='GT')
plt.plot(x1, y1, 'ro-', x1, y2, 'g+-')
plt.title('predictions VS GT')
plt.legend()
plt.savefig('./image/prediction_gt.png')
```
打印预测结果和标签并可视化结果
```python
print("infer results: (House Price)") print("infer results: (House Price)")
for idx, val in enumerate(results[0]): for idx, val in enumerate(results[0]):
print("%d: %.2f" % (idx, val)) # 打印预测结果 print("%d: %.2f" % (idx, val)) # 打印预测结果
...@@ -408,8 +417,10 @@ def save_result(points1, points2): ...@@ -408,8 +417,10 @@ def save_result(points1, points2):
for idx, val in enumerate(infer_label): for idx, val in enumerate(infer_label):
print("%d: %.2f" % (idx, val)) # 打印标签值 print("%d: %.2f" % (idx, val)) # 打印标签值
save_result(results[0], infer_label) # 保存图片 save_result(results[0], infer_label) # 保存图片
``` ```
由于每次都是随机选择一个minibatch的数据作为当前迭代的训练数据所以每次得到的预测结果会有所不同
## 总结 ## 总结
在这章里我们借助波士顿房价这一数据集介绍了线性回归模型的基本概念以及如何使用PaddlePaddle实现训练和测试的过程很多的模型和技巧都是从简单的线性回归模型演化而来因此弄清楚线性模型的原理和局限非常重要 在这章里我们借助波士顿房价这一数据集介绍了线性回归模型的基本概念以及如何使用PaddlePaddle实现训练和测试的过程很多的模型和技巧都是从简单的线性回归模型演化而来因此弄清楚线性模型的原理和局限非常重要
...@@ -422,7 +433,7 @@ save_result(results[0], infer_label) # 保存图片 ...@@ -422,7 +433,7 @@ save_result(results[0], infer_label) # 保存图片
4. Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128. 4. Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128.
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://www.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
</div> </div>
<!-- You can change the lines below now. --> <!-- You can change the lines below now. -->
......
此差异已折叠。
...@@ -14,11 +14,31 @@ ...@@ -14,11 +14,31 @@
from __future__ import print_function from __future__ import print_function
import sys
import argparse
import math
import numpy
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy
import math
import sys def parse_args():
parser = argparse.ArgumentParser("fit_a_line")
parser.add_argument(
'--enable_ce',
action='store_true',
help="If set, run the task with continuous evaluation logs.")
parser.add_argument(
'--use_gpu',
type=bool,
default=False,
help="Whether to use GPU or not.")
parser.add_argument(
'--num_epochs', type=int, default=100, help="number of epochs.")
args = parser.parse_args()
return args
# For training test cost # For training test cost
...@@ -50,37 +70,50 @@ def save_result(points1, points2): ...@@ -50,37 +70,50 @@ def save_result(points1, points2):
def main(): def main():
batch_size = 20 batch_size = 20
if args.enable_ce:
train_reader = paddle.batch( train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.uci_housing.train(), buf_size=500), paddle.dataset.uci_housing.train(), batch_size=batch_size)
test_reader = paddle.batch(
paddle.dataset.uci_housing.test(), batch_size=batch_size)
else:
train_reader = paddle.batch(
paddle.reader.shuffle(
paddle.dataset.uci_housing.train(), buf_size=500),
batch_size=batch_size) batch_size=batch_size)
test_reader = paddle.batch( test_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.uci_housing.test(), buf_size=500), paddle.reader.shuffle(
paddle.dataset.uci_housing.test(), buf_size=500),
batch_size=batch_size) batch_size=batch_size)
# feature vector of length 13 # feature vector of length 13
x = fluid.layers.data(name='x', shape=[13], dtype='float32') x = fluid.layers.data(name='x', shape=[13], dtype='float32')
y = fluid.layers.data(name='y', shape=[1], dtype='float32') y = fluid.layers.data(name='y', shape=[1], dtype='float32')
y_predict = fluid.layers.fc(input=x, size=1, act=None)
main_program = fluid.default_main_program() main_program = fluid.default_main_program()
startup_program = fluid.default_startup_program() startup_program = fluid.default_startup_program()
if args.enable_ce:
main_program.random_seed = 90
startup_program.random_seed = 90
y_predict = fluid.layers.fc(input=x, size=1, act=None)
cost = fluid.layers.square_error_cost(input=y_predict, label=y) cost = fluid.layers.square_error_cost(input=y_predict, label=y)
avg_loss = fluid.layers.mean(cost) avg_loss = fluid.layers.mean(cost)
test_program = main_program.clone(for_test=True)
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001) sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)
sgd_optimizer.minimize(avg_loss) sgd_optimizer.minimize(avg_loss)
test_program = main_program.clone(for_test=True)
# can use CPU or GPU # can use CPU or GPU
use_cuda = False use_cuda = args.use_gpu
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe = fluid.Executor(place) exe = fluid.Executor(place)
# Specify the directory to save the parameters # Specify the directory to save the parameters
params_dirname = "fit_a_line.inference.model" params_dirname = "fit_a_line.inference.model"
num_epochs = 100 num_epochs = args.num_epochs
# main train loop. # main train loop.
feeder = fluid.DataFeeder(place=place, feed_list=[x, y]) feeder = fluid.DataFeeder(place=place, feed_list=[x, y])
...@@ -124,6 +157,10 @@ def main(): ...@@ -124,6 +157,10 @@ def main():
fluid.io.save_inference_model(params_dirname, ['x'], [y_predict], fluid.io.save_inference_model(params_dirname, ['x'], [y_predict],
exe) exe)
if args.enable_ce and pass_id == args.num_epochs - 1:
print("kpis\ttrain_cost\t%f" % avg_loss_value[0])
print("kpis\ttest_cost\t%f" % test_metics[0])
infer_exe = fluid.Executor(place) infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope() inference_scope = fluid.core.Scope()
...@@ -160,4 +197,5 @@ def main(): ...@@ -160,4 +197,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
args = parse_args()
main() main()
#!/bin/bash
#This file is only used for continuous evaluation.
python train.py --enable_ce | python _ce.py
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
本教程源代码目录在[book/recognize_digits](https://github.com/PaddlePaddle/book/tree/develop/02.recognize_digits),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书) 本教程源代码目录在[book/recognize_digits](https://github.com/PaddlePaddle/book/tree/develop/02.recognize_digits),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)
### 说明: ###
1. 硬件环境要求:
本文可支持在CPU、GPU下运行
2. Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3. 文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/train.py)进行验证。
## 背景介绍 ## 背景介绍
当我们学习编程的时候,编写的第一个程序一般是实现打印"Hello World"。而机器学习(或深度学习)的入门教程,一般都是 [MNIST](http://yann.lecun.com/exdb/mnist/) 数据库上的手写识别问题。原因是手写识别属于典型的图像分类问题,比较简单,同时MNIST数据集也很完备。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如图1所示的手写数字图片和对应的标签。图片是28x28的像素矩阵,标签则对应着0~9的10个数字。每张图片都经过了大小归一化和居中处理。 当我们学习编程的时候,编写的第一个程序一般是实现打印"Hello World"。而机器学习(或深度学习)的入门教程,一般都是 [MNIST](http://yann.lecun.com/exdb/mnist/) 数据库上的手写识别问题。原因是手写识别属于典型的图像分类问题,比较简单,同时MNIST数据集也很完备。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如图1所示的手写数字图片和对应的标签。图片是28x28的像素矩阵,标签则对应着0~9的10个数字。每张图片都经过了大小归一化和居中处理。
...@@ -29,7 +37,7 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun ...@@ -29,7 +37,7 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun
- $Y$是输出:分类器的输出是10类数字(0-9),即$Y=\left ( y_0, y_1, \dots, y_9 \right )$,每一维$y_i$代表图片分类为第$i$类数字的概率。 - $Y$是输出:分类器的输出是10类数字(0-9),即$Y=\left ( y_0, y_1, \dots, y_9 \right )$,每一维$y_i$代表图片分类为第$i$类数字的概率。
- $Label$是图片的真实标签:$Label=\left ( l_0, l_1, \dots, l_9 \right )$也是10维,但只有一维为1,其他都为0。例如某张图片上的数字为2,则它的标签为$(0,0,1,0, \dot, 0)$ - $Label$是图片的真实标签:$Label=\left ( l_0, l_1, \dots, l_9 \right )$也是10维,但只有一维为1,其他都为0。例如某张图片上的数字为2,则它的标签为$(0,0,1,0, \dots, 0)$
### Softmax回归(Softmax Regression) ### Softmax回归(Softmax Regression)
...@@ -37,14 +45,19 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun ...@@ -37,14 +45,19 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun
输入层的数据$X$传到输出层,在激活操作之前,会乘以相应的权重 $W$ ,并加上偏置变量 $b$ ,具体如下: 输入层的数据$X$传到输出层,在激活操作之前,会乘以相应的权重 $W$ ,并加上偏置变量 $b$ ,具体如下:
$$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/01.gif?raw=true"><br/>
</p>
其中 $ \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}} $ 其中
<p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/02.gif?raw=true"><br/>
</p>
图2为softmax回归的网络图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。 图2为softmax回归的网络图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。
<p align="center"> <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/softmax_regression.png?raw=true" width=400><br/> <img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/softmax_regression.png?raw=true" width=200><br/>
图2. softmax回归网络结构图<br/> 图2. softmax回归网络结构图<br/>
</p> </p>
...@@ -52,7 +65,9 @@ $$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$ ...@@ -52,7 +65,9 @@ $$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$
在分类问题中,我们一般采用交叉熵代价损失函数(cross entropy loss),公式如下: 在分类问题中,我们一般采用交叉熵代价损失函数(cross entropy loss),公式如下:
$$ L_{cross-entropy}(label, y) = -\sum_i label_ilog(y_i) $$ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/03.gif?raw=true"><br/>
</p>
...@@ -98,7 +113,7 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层 ...@@ -98,7 +113,7 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层
- 局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即上图示例H和W所在的平面)是局部连接,但在深度上是全部连接。对于二维图像本身而言,也是局部像素关联较强。这种局部连接保证了学习后的过滤器能够对于局部的输入特征有最强的响应。局部连接的思想,也是受启发于生物学里面的视觉系统结构,视觉皮层的神经元就是局部接受信息的。 - 局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即上图示例H和W所在的平面)是局部连接,但在深度上是全部连接。对于二维图像本身而言,也是局部像素关联较强。这种局部连接保证了学习后的过滤器能够对于局部的输入特征有最强的响应。局部连接的思想,也是受启发于生物学里面的视觉系统结构,视觉皮层的神经元就是局部接受信息的。
- 权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。例如图4中计算$o[:,:,0]$的每个每个神经元的滤波器均相同,都为$W_0$,这样可以很大程度上减少参数。共享权重在一定程度上讲是有意义的,例如图片的底层边缘特征与特征在图中的具体位置无关。但是在一些场景中是无意的,比如输入的图片是人脸,眼睛和头发位于不同的位置,希望在不同的位置学到不同的特征 (参考[斯坦福大学公开课]( http://cs231n.github.io/convolutional-networks/))。请注意权重只是对于同一深度切片的神经元是共享的,在卷积层,通常采用多组卷积核提取不同特征,即对应不同深度切片的特征,不同深度切片的神经元权重是不共享。另外,偏重对同一深度切片的所有神经元都是共享的。 - 权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。例如图5中计算$o[:,:,0]$的每个每个神经元的滤波器均相同,都为$W_0$,这样可以很大程度上减少参数。共享权重在一定程度上讲是有意义的,例如图片的底层边缘特征与特征在图中的具体位置无关。但是在一些场景中是无意的,比如输入的图片是人脸,眼睛和头发位于不同的位置,希望在不同的位置学到不同的特征 (参考[斯坦福大学公开课]( http://cs231n.github.io/convolutional-networks/))。请注意权重只是对于同一深度切片的神经元是共享的,在卷积层,通常采用多组卷积核提取不同特征,即对应不同深度切片的特征,不同深度切片的神经元权重是不共享。另外,偏重对同一深度切片的所有神经元都是共享的。
通过介绍卷积计算过程及其特性,可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。 通过介绍卷积计算过程及其特性,可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。
...@@ -117,9 +132,17 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层 ...@@ -117,9 +132,17 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层
<a name="常见激活函数介绍"></a> <a name="常见激活函数介绍"></a>
### 常见激活函数介绍 ### 常见激活函数介绍
- sigmoid激活函数: $ f(x) = sigmoid(x) = \frac{1}{1+e^{-x}} $ - sigmoid激活函数:
- tanh激活函数: $ f(x) = tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} $ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/04.gif?raw=true"><br/>
</p>
- tanh激活函数:
<p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/05.gif?raw=true"><br/>
</p>
实际上,tanh函数只是规模变化的sigmoid函数,将sigmoid函数值放大2倍之后再向下平移1个单位:tanh(x) = 2sigmoid(2x) - 1 。 实际上,tanh函数只是规模变化的sigmoid函数,将sigmoid函数值放大2倍之后再向下平移1个单位:tanh(x) = 2sigmoid(2x) - 1 。
...@@ -160,13 +183,13 @@ PaddlePaddle在API中提供了自动加载[MNIST](http://yann.lecun.com/exdb/mni ...@@ -160,13 +183,13 @@ PaddlePaddle在API中提供了自动加载[MNIST](http://yann.lecun.com/exdb/mni
加载 PaddlePaddle 的 Fluid API 包。 加载 PaddlePaddle 的 Fluid API 包。
```python ```python
from __future__ import print_function # 将python3中的print特性导入当前版本
import os import os
from PIL import Image # 导入图像处理模块 from PIL import Image # 导入图像处理模块
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy import numpy
import paddle # 导入paddle模块 import paddle # 导入paddle模块
import paddle.fluid as fluid import paddle.fluid as fluid
from __future__ import print_function # 将python3中的print特性导入当前版本
``` ```
### Program Functions 配置 ### Program Functions 配置
...@@ -240,7 +263,7 @@ def convolutional_neural_network(): ...@@ -240,7 +263,7 @@ def convolutional_neural_network():
act="relu") act="relu")
conv_pool_1 = fluid.layers.batch_norm(conv_pool_1) conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
# 第二个卷积-池化层 # 第二个卷积-池化层
# 使用20个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu # 使用50个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu
conv_pool_2 = fluid.nets.simple_img_conv_pool( conv_pool_2 = fluid.nets.simple_img_conv_pool(
input=conv_pool_1, input=conv_pool_1,
filter_size=5, filter_size=5,
...@@ -373,15 +396,13 @@ place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() ...@@ -373,15 +396,13 @@ place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 调用train_program 获取预测值,损失值, # 调用train_program 获取预测值,损失值,
prediction, [avg_loss, acc] = train_program() prediction, [avg_loss, acc] = train_program()
# 输入的原始图像数据,大小为28*28*1 # 输入的原始图像数据,名称为img,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 标签层,名称为label,对应输入图片的类别标签 # 标签层,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值 # 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值
feeder = fluid.DataFeeder(feed_list=[img, label], place=place) feeder = fluid.DataFeeder(feed_list=['img', 'label'], place=place)
# 选择Adam优化器 # 选择Adam优化器
optimizer = fluid.optimizer.Adam(learning_rate=0.001) optimizer = optimizer_program()
optimizer.minimize(avg_loss) optimizer.minimize(avg_loss)
``` ```
...@@ -492,7 +513,7 @@ Pass 900, Batch 0, Cost 0.239809 ...@@ -492,7 +513,7 @@ Pass 900, Batch 0, Cost 0.239809
Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338 Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338
``` ```
训练之后,检查模型的预测准确度。用 MNIST 训练的时候,一般 softmax回归模型的分类准确率为约为 92.34%,多层感知器为97.66%,卷积神经网络可以达到 99.20%。 训练之后,检查模型的预测准确度。用 MNIST 训练的时候,一般 softmax回归模型的分类准确率约为 92.34%,多层感知器为97.66%,卷积神经网络可以达到 99.20%。
## 应用模型 ## 应用模型
...@@ -505,9 +526,13 @@ Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338 ...@@ -505,9 +526,13 @@ Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338
```python ```python
def load_image(file): def load_image(file):
# 读取图片文件,并将它转成灰度图
im = Image.open(file).convert('L') im = Image.open(file).convert('L')
# 将输入图片调整为 28*28 的高质量图
im = im.resize((28, 28), Image.ANTIALIAS) im = im.resize((28, 28), Image.ANTIALIAS)
# 将图片转换为numpy
im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32) im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32)
# 对数据作归一化处理
im = im / 255.0 * 2.0 - 1.0 im = im / 255.0 * 2.0 - 1.0
return im return im
...@@ -565,5 +590,4 @@ with fluid.scope_guard(inference_scope): ...@@ -565,5 +590,4 @@ with fluid.scope_guard(inference_scope):
10. Bishop, Christopher M. ["Pattern recognition."](http://users.isr.ist.utl.pt/~wurmd/Livros/school/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf) Machine Learning 128 (2006): 1-58. 10. Bishop, Christopher M. ["Pattern recognition."](http://users.isr.ist.utl.pt/~wurmd/Livros/school/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf) Machine Learning 128 (2006): 1-58.
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
此差异已折叠。
### This file is only used for continuous evaluation test!
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import os
import sys
sys.path.append(os.environ['ceroot'])
from kpi import CostKpi
from kpi import AccKpi
train_cost_kpi = CostKpi('train_cost', 0.02, 0, actived=True, desc='train cost')
test_cost_kpi = CostKpi('test_cost', 0.02, 0, actived=True, desc='test cost')
test_acc_kpi = AccKpi('test_acc', 0.02, 0, actived=True, desc='test acc')
tracking_kpis = [train_cost_kpi, test_cost_kpi, test_acc_kpi]
def parse_log(log):
for line in log.split('\n'):
fs = line.strip().split('\t')
print(fs)
if len(fs) == 3 and fs[0] == 'kpis':
kpi_name = fs[1]
kpi_value = float(fs[2])
yield kpi_name, kpi_value
def log_to_ce(log):
kpi_tracker = {}
for kpi in tracking_kpis:
kpi_tracker[kpi.name] = kpi
for (kpi_name, kpi_value) in parse_log(log):
print(kpi_name, kpi_value)
kpi_tracker[kpi_name].add_record(kpi_value)
kpi_tracker[kpi_name].persist()
if __name__ == '__main__':
log = sys.stdin.read()
log_to_ce(log)
...@@ -44,6 +44,14 @@ ...@@ -44,6 +44,14 @@
本教程源代码目录在[book/recognize_digits](https://github.com/PaddlePaddle/book/tree/develop/02.recognize_digits),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。 本教程源代码目录在[book/recognize_digits](https://github.com/PaddlePaddle/book/tree/develop/02.recognize_digits),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。
### 说明: ###
1. 硬件环境要求:
本文可支持在CPU、GPU下运行
2. Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3. 文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/train.py)进行验证。
## 背景介绍 ## 背景介绍
当我们学习编程的时候,编写的第一个程序一般是实现打印"Hello World"。而机器学习(或深度学习)的入门教程,一般都是 [MNIST](http://yann.lecun.com/exdb/mnist/) 数据库上的手写识别问题。原因是手写识别属于典型的图像分类问题,比较简单,同时MNIST数据集也很完备。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如图1所示的手写数字图片和对应的标签。图片是28x28的像素矩阵,标签则对应着0~9的10个数字。每张图片都经过了大小归一化和居中处理。 当我们学习编程的时候,编写的第一个程序一般是实现打印"Hello World"。而机器学习(或深度学习)的入门教程,一般都是 [MNIST](http://yann.lecun.com/exdb/mnist/) 数据库上的手写识别问题。原因是手写识别属于典型的图像分类问题,比较简单,同时MNIST数据集也很完备。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如图1所示的手写数字图片和对应的标签。图片是28x28的像素矩阵,标签则对应着0~9的10个数字。每张图片都经过了大小归一化和居中处理。
...@@ -71,7 +79,7 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun ...@@ -71,7 +79,7 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun
- $Y$是输出:分类器的输出是10类数字(0-9),即$Y=\left ( y_0, y_1, \dots, y_9 \right )$,每一维$y_i$代表图片分类为第$i$类数字的概率。 - $Y$是输出:分类器的输出是10类数字(0-9),即$Y=\left ( y_0, y_1, \dots, y_9 \right )$,每一维$y_i$代表图片分类为第$i$类数字的概率。
- $Label$是图片的真实标签:$Label=\left ( l_0, l_1, \dots, l_9 \right )$也是10维,但只有一维为1,其他都为0。例如某张图片上的数字为2,则它的标签为$(0,0,1,0, \dot, 0)$ - $Label$是图片的真实标签:$Label=\left ( l_0, l_1, \dots, l_9 \right )$也是10维,但只有一维为1,其他都为0。例如某张图片上的数字为2,则它的标签为$(0,0,1,0, \dots, 0)$
### Softmax回归(Softmax Regression) ### Softmax回归(Softmax Regression)
...@@ -79,14 +87,19 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun ...@@ -79,14 +87,19 @@ MNIST吸引了大量的科学家基于此数据集训练模型,1998年,LeCun
输入层的数据$X$传到输出层,在激活操作之前,会乘以相应的权重 $W$ ,并加上偏置变量 $b$ ,具体如下: 输入层的数据$X$传到输出层,在激活操作之前,会乘以相应的权重 $W$ ,并加上偏置变量 $b$ ,具体如下:
$$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/01.gif?raw=true"><br/>
</p>
其中 $ \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}} $ 其中
<p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/02.gif?raw=true"><br/>
</p>
图2为softmax回归的网络图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。 图2为softmax回归的网络图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。
<p align="center"> <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/softmax_regression.png?raw=true" width=400><br/> <img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/softmax_regression.png?raw=true" width=200><br/>
图2. softmax回归网络结构图<br/> 图2. softmax回归网络结构图<br/>
</p> </p>
...@@ -94,7 +107,9 @@ $$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$ ...@@ -94,7 +107,9 @@ $$ y_i = \text{softmax}(\sum_j W_{i,j}x_j + b_i) $$
在分类问题中,我们一般采用交叉熵代价损失函数(cross entropy loss),公式如下: 在分类问题中,我们一般采用交叉熵代价损失函数(cross entropy loss),公式如下:
$$ L_{cross-entropy}(label, y) = -\sum_i label_ilog(y_i) $$ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/03.gif?raw=true"><br/>
</p>
...@@ -140,7 +155,7 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层 ...@@ -140,7 +155,7 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层
- 局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即上图示例H和W所在的平面)是局部连接,但在深度上是全部连接。对于二维图像本身而言,也是局部像素关联较强。这种局部连接保证了学习后的过滤器能够对于局部的输入特征有最强的响应。局部连接的思想,也是受启发于生物学里面的视觉系统结构,视觉皮层的神经元就是局部接受信息的。 - 局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即上图示例H和W所在的平面)是局部连接,但在深度上是全部连接。对于二维图像本身而言,也是局部像素关联较强。这种局部连接保证了学习后的过滤器能够对于局部的输入特征有最强的响应。局部连接的思想,也是受启发于生物学里面的视觉系统结构,视觉皮层的神经元就是局部接受信息的。
- 权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。例如图4中计算$o[:,:,0]$的每个每个神经元的滤波器均相同,都为$W_0$,这样可以很大程度上减少参数。共享权重在一定程度上讲是有意义的,例如图片的底层边缘特征与特征在图中的具体位置无关。但是在一些场景中是无意的,比如输入的图片是人脸,眼睛和头发位于不同的位置,希望在不同的位置学到不同的特征 (参考[斯坦福大学公开课]( http://cs231n.github.io/convolutional-networks/))。请注意权重只是对于同一深度切片的神经元是共享的,在卷积层,通常采用多组卷积核提取不同特征,即对应不同深度切片的特征,不同深度切片的神经元权重是不共享。另外,偏重对同一深度切片的所有神经元都是共享的。 - 权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。例如图5中计算$o[:,:,0]$的每个每个神经元的滤波器均相同,都为$W_0$,这样可以很大程度上减少参数。共享权重在一定程度上讲是有意义的,例如图片的底层边缘特征与特征在图中的具体位置无关。但是在一些场景中是无意的,比如输入的图片是人脸,眼睛和头发位于不同的位置,希望在不同的位置学到不同的特征 (参考[斯坦福大学公开课]( http://cs231n.github.io/convolutional-networks/))。请注意权重只是对于同一深度切片的神经元是共享的,在卷积层,通常采用多组卷积核提取不同特征,即对应不同深度切片的特征,不同深度切片的神经元权重是不共享。另外,偏重对同一深度切片的所有神经元都是共享的。
通过介绍卷积计算过程及其特性,可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。 通过介绍卷积计算过程及其特性,可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。
...@@ -159,9 +174,17 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层 ...@@ -159,9 +174,17 @@ Softmax回归模型采用了最简单的两层神经网络,即只有输入层
<a name="常见激活函数介绍"></a> <a name="常见激活函数介绍"></a>
### 常见激活函数介绍 ### 常见激活函数介绍
- sigmoid激活函数: $ f(x) = sigmoid(x) = \frac{1}{1+e^{-x}} $ - sigmoid激活函数:
- tanh激活函数: $ f(x) = tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} $ <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/04.gif?raw=true"><br/>
</p>
- tanh激活函数:
<p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/02.recognize_digits/image/05.gif?raw=true"><br/>
</p>
实际上,tanh函数只是规模变化的sigmoid函数,将sigmoid函数值放大2倍之后再向下平移1个单位:tanh(x) = 2sigmoid(2x) - 1 。 实际上,tanh函数只是规模变化的sigmoid函数,将sigmoid函数值放大2倍之后再向下平移1个单位:tanh(x) = 2sigmoid(2x) - 1 。
...@@ -202,13 +225,13 @@ PaddlePaddle在API中提供了自动加载[MNIST](http://yann.lecun.com/exdb/mni ...@@ -202,13 +225,13 @@ PaddlePaddle在API中提供了自动加载[MNIST](http://yann.lecun.com/exdb/mni
加载 PaddlePaddle 的 Fluid API 包。 加载 PaddlePaddle 的 Fluid API 包。
```python ```python
from __future__ import print_function # 将python3中的print特性导入当前版本
import os import os
from PIL import Image # 导入图像处理模块 from PIL import Image # 导入图像处理模块
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy import numpy
import paddle # 导入paddle模块 import paddle # 导入paddle模块
import paddle.fluid as fluid import paddle.fluid as fluid
from __future__ import print_function # 将python3中的print特性导入当前版本
``` ```
### Program Functions 配置 ### Program Functions 配置
...@@ -282,7 +305,7 @@ def convolutional_neural_network(): ...@@ -282,7 +305,7 @@ def convolutional_neural_network():
act="relu") act="relu")
conv_pool_1 = fluid.layers.batch_norm(conv_pool_1) conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
# 第二个卷积-池化层 # 第二个卷积-池化层
# 使用20个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu # 使用50个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu
conv_pool_2 = fluid.nets.simple_img_conv_pool( conv_pool_2 = fluid.nets.simple_img_conv_pool(
input=conv_pool_1, input=conv_pool_1,
filter_size=5, filter_size=5,
...@@ -415,15 +438,13 @@ place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() ...@@ -415,15 +438,13 @@ place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 调用train_program 获取预测值,损失值, # 调用train_program 获取预测值,损失值,
prediction, [avg_loss, acc] = train_program() prediction, [avg_loss, acc] = train_program()
# 输入的原始图像数据,大小为28*28*1 # 输入的原始图像数据,名称为img,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 标签层,名称为label,对应输入图片的类别标签 # 标签层,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值 # 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值
feeder = fluid.DataFeeder(feed_list=[img, label], place=place) feeder = fluid.DataFeeder(feed_list=['img', 'label'], place=place)
# 选择Adam优化器 # 选择Adam优化器
optimizer = fluid.optimizer.Adam(learning_rate=0.001) optimizer = optimizer_program()
optimizer.minimize(avg_loss) optimizer.minimize(avg_loss)
``` ```
...@@ -534,7 +555,7 @@ Pass 900, Batch 0, Cost 0.239809 ...@@ -534,7 +555,7 @@ Pass 900, Batch 0, Cost 0.239809
Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338 Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338
``` ```
训练之后,检查模型的预测准确度。用 MNIST 训练的时候,一般 softmax回归模型的分类准确率为约为 92.34%,多层感知器为97.66%,卷积神经网络可以达到 99.20%。 训练之后,检查模型的预测准确度。用 MNIST 训练的时候,一般 softmax回归模型的分类准确率约为 92.34%,多层感知器为97.66%,卷积神经网络可以达到 99.20%。
## 应用模型 ## 应用模型
...@@ -547,9 +568,13 @@ Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338 ...@@ -547,9 +568,13 @@ Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338
```python ```python
def load_image(file): def load_image(file):
# 读取图片文件,并将它转成灰度图
im = Image.open(file).convert('L') im = Image.open(file).convert('L')
# 将输入图片调整为 28*28 的高质量图
im = im.resize((28, 28), Image.ANTIALIAS) im = im.resize((28, 28), Image.ANTIALIAS)
# 将图片转换为numpy
im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32) im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32)
# 对数据作归一化处理
im = im / 255.0 * 2.0 - 1.0 im = im / 255.0 * 2.0 - 1.0
return im return im
...@@ -607,8 +632,7 @@ with fluid.scope_guard(inference_scope): ...@@ -607,8 +632,7 @@ with fluid.scope_guard(inference_scope):
10. Bishop, Christopher M. ["Pattern recognition."](http://users.isr.ist.utl.pt/~wurmd/Livros/school/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf) Machine Learning 128 (2006): 1-58. 10. Bishop, Christopher M. ["Pattern recognition."](http://users.isr.ist.utl.pt/~wurmd/Livros/school/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf) Machine Learning 128 (2006): 1-58.
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
</div> </div>
<!-- You can change the lines below now. --> <!-- You can change the lines below now. -->
......
此差异已折叠。
...@@ -15,13 +15,28 @@ ...@@ -15,13 +15,28 @@
from __future__ import print_function from __future__ import print_function
import os import os
import argparse
from PIL import Image from PIL import Image
import numpy import numpy
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
BATCH_SIZE = 64
PASS_NUM = 5 def parse_args():
parser = argparse.ArgumentParser("mnist")
parser.add_argument(
'--enable_ce',
action='store_true',
help="If set, run the task with continuous evaluation logs.")
parser.add_argument(
'--use_gpu',
type=bool,
default=False,
help="Whether to use GPU or not.")
parser.add_argument(
'--num_epochs', type=int, default=5, help="number of epochs.")
args = parser.parse_args()
return args
def loss_net(hidden, label): def loss_net(hidden, label):
...@@ -69,6 +84,23 @@ def train(nn_type, ...@@ -69,6 +84,23 @@ def train(nn_type,
if use_cuda and not fluid.core.is_compiled_with_cuda(): if use_cuda and not fluid.core.is_compiled_with_cuda():
return return
startup_program = fluid.default_startup_program()
main_program = fluid.default_main_program()
if args.enable_ce:
train_reader = paddle.batch(
paddle.dataset.mnist.train(), batch_size=BATCH_SIZE)
test_reader = paddle.batch(
paddle.dataset.mnist.test(), batch_size=BATCH_SIZE)
startup_program.random_seed = 90
main_program.random_seed = 90
else:
train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=500),
batch_size=BATCH_SIZE)
test_reader = paddle.batch(
paddle.dataset.mnist.test(), batch_size=BATCH_SIZE)
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32') img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64') label = fluid.layers.data(name='label', shape=[1], dtype='int64')
...@@ -81,8 +113,7 @@ def train(nn_type, ...@@ -81,8 +113,7 @@ def train(nn_type,
prediction, avg_loss, acc = net_conf(img, label) prediction, avg_loss, acc = net_conf(img, label)
test_program = fluid.default_main_program().clone(for_test=True) test_program = main_program.clone(for_test=True)
optimizer = fluid.optimizer.Adam(learning_rate=0.001) optimizer = fluid.optimizer.Adam(learning_rate=0.001)
optimizer.minimize(avg_loss) optimizer.minimize(avg_loss)
...@@ -105,15 +136,8 @@ def train(nn_type, ...@@ -105,15 +136,8 @@ def train(nn_type,
exe = fluid.Executor(place) exe = fluid.Executor(place)
train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=500),
batch_size=BATCH_SIZE)
test_reader = paddle.batch(
paddle.dataset.mnist.test(), batch_size=BATCH_SIZE)
feeder = fluid.DataFeeder(feed_list=[img, label], place=place) feeder = fluid.DataFeeder(feed_list=[img, label], place=place)
exe.run(startup_program)
exe.run(fluid.default_startup_program())
main_program = fluid.default_main_program()
epochs = [epoch_id for epoch_id in range(PASS_NUM)] epochs = [epoch_id for epoch_id in range(PASS_NUM)]
lists = [] lists = []
...@@ -125,7 +149,7 @@ def train(nn_type, ...@@ -125,7 +149,7 @@ def train(nn_type,
feed=feeder.feed(data), feed=feeder.feed(data),
fetch_list=[avg_loss, acc]) fetch_list=[avg_loss, acc])
if step % 100 == 0: if step % 100 == 0:
print("Pass %d, Batch %d, Cost %f" % (step, epoch_id, print("Pass %d, Epoch %d, Cost %f" % (step, epoch_id,
metrics[0])) metrics[0]))
step += 1 step += 1
# test for epoch # test for epoch
...@@ -144,6 +168,11 @@ def train(nn_type, ...@@ -144,6 +168,11 @@ def train(nn_type,
model_filename=model_filename, model_filename=model_filename,
params_filename=params_filename) params_filename=params_filename)
if args.enable_ce:
print("kpis\ttrain_cost\t%f" % metrics[0])
print("kpis\ttest_cost\t%s" % avg_loss_val)
print("kpis\ttest_acc\t%s" % acc_val)
# find the best pass # find the best pass
best = sorted(lists, key=lambda list: float(list[1]))[0] best = sorted(lists, key=lambda list: float(list[1]))[0]
print('Best pass is %s, testing Avgcost is %s' % (best[0], best[1])) print('Best pass is %s, testing Avgcost is %s' % (best[0], best[1]))
...@@ -210,7 +239,10 @@ def main(use_cuda, nn_type): ...@@ -210,7 +239,10 @@ def main(use_cuda, nn_type):
if __name__ == '__main__': if __name__ == '__main__':
use_cuda = False args = parse_args()
BATCH_SIZE = 64
PASS_NUM = args.num_epochs
use_cuda = args.use_gpu
# predict = 'softmax_regression' # uncomment for Softmax # predict = 'softmax_regression' # uncomment for Softmax
# predict = 'multilayer_perceptron' # uncomment for MLP # predict = 'multilayer_perceptron' # uncomment for MLP
predict = 'convolutional_neural_network' # uncomment for LeNet5 predict = 'convolutional_neural_network' # uncomment for LeNet5
......
#!/bin/bash
#This file is only used for continuous evaluation.
export FLAGS_cudnn_deterministic=true
export CUDA_VISIBLE_DEVICES=0
python train.py --num_epochs 1 --use_gpu 1 --enable_ce | python _ce.py
...@@ -3,6 +3,18 @@ ...@@ -3,6 +3,18 @@
本教程源代码目录在[book/image_classification](https://github.com/PaddlePaddle/book/tree/develop/03.image_classification),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书) 本教程源代码目录在[book/image_classification](https://github.com/PaddlePaddle/book/tree/develop/03.image_classification),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)
### 说明: ###
1.硬件环境要求:
本文可支持在CPU、GPU下运行
2.Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3.文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/train.py)进行验证。
## 背景介绍 ## 背景介绍
图像相比文字能够提供更加生动、容易理解及更具艺术感的信息,是人们转递与交换信息的重要来源。在本教程中,我们专注于图像识别领域的一个重要问题,即图像分类。 图像相比文字能够提供更加生动、容易理解及更具艺术感的信息,是人们转递与交换信息的重要来源。在本教程中,我们专注于图像识别领域的一个重要问题,即图像分类。
...@@ -52,7 +64,7 @@ ...@@ -52,7 +64,7 @@
2). **特征编码**: 底层特征中包含了大量冗余与噪声,为了提高特征表达的鲁棒性,需要使用一种特征变换算法对底层特征进行编码,称作特征编码。常用的特征编码方法包括向量量化编码 \[[4](#参考文献)\]、稀疏编码 \[[5](#参考文献)\]、局部线性约束编码 \[[6](#参考文献)\]、Fisher向量编码 \[[7](#参考文献)\] 等。 2). **特征编码**: 底层特征中包含了大量冗余与噪声,为了提高特征表达的鲁棒性,需要使用一种特征变换算法对底层特征进行编码,称作特征编码。常用的特征编码方法包括向量量化编码 \[[4](#参考文献)\]、稀疏编码 \[[5](#参考文献)\]、局部线性约束编码 \[[6](#参考文献)\]、Fisher向量编码 \[[7](#参考文献)\] 等。
3). **空间特征约束**: 特征编码之后一般会经过空间特征约束,也称作**特征汇聚**。特征汇聚是指在一个空间范围内,对每一维特征取最大值或者平均值,可以获得一定特征不变形的特征表达。金字塔特征匹配是一种常用的特征聚会方法,这种方法提出将图像均匀分块,在分块内做特征汇聚。 3). **空间特征约束**: 特征编码之后一般会经过空间特征约束,也称作**特征汇聚**。特征汇聚是指在一个空间范围内,对每一维特征取最大值或者平均值,可以获得一定特征不变形的特征表达。金字塔特征匹配是一种常用的特征汇聚方法,这种方法提出将图像均匀分块,在分块内做特征汇聚。
4). **通过分类器分类**: 经过前面步骤之后一张图像可以用一个固定维度的向量进行描述,接下来就是经过分类器对图像进行分类。通常使用的分类器包括SVM(Support Vector Machine, 支持向量机)、随机森林等。而使用核方法的SVM是最为广泛的分类器,在传统图像分类任务上性能很好。 4). **通过分类器分类**: 经过前面步骤之后一张图像可以用一个固定维度的向量进行描述,接下来就是经过分类器对图像进行分类。通常使用的分类器包括SVM(Support Vector Machine, 支持向量机)、随机森林等。而使用核方法的SVM是最为广泛的分类器,在传统图像分类任务上性能很好。
...@@ -82,7 +94,7 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得 ...@@ -82,7 +94,7 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得
另外,在训练过程中由于每层参数不断更新,会导致下一次输入分布发生变化,这样导致训练过程需要精心设计超参数。如2015年Sergey Ioffe和Christian Szegedy提出了Batch Normalization (BN)算法 \[[14](#参考文献)\] 中,每个batch对网络中的每一层特征都做归一化,使得每层分布相对稳定。BN算法不仅起到一定的正则作用,而且弱化了一些超参数的设计。经过实验证明,BN算法加速了模型收敛过程,在后来较深的模型中被广泛使用。 另外,在训练过程中由于每层参数不断更新,会导致下一次输入分布发生变化,这样导致训练过程需要精心设计超参数。如2015年Sergey Ioffe和Christian Szegedy提出了Batch Normalization (BN)算法 \[[14](#参考文献)\] 中,每个batch对网络中的每一层特征都做归一化,使得每层分布相对稳定。BN算法不仅起到一定的正则作用,而且弱化了一些超参数的设计。经过实验证明,BN算法加速了模型收敛过程,在后来较深的模型中被广泛使用。
接下来我们主要介绍VGG,GoogleNet和ResNet网络结构。 接下来我们主要介绍VGG,GoogLeNet和ResNet网络结构。
### VGG ### VGG
...@@ -93,9 +105,9 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得 ...@@ -93,9 +105,9 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得
图6. 基于ImageNet的VGG16模型 图6. 基于ImageNet的VGG16模型
</p> </p>
### GoogleNet ### GoogLeNet
GoogleNet \[[12](#参考文献)\] 在2014年ILSVRC的获得了冠军,在介绍该模型之前我们先来了解NIN(Network in Network)模型 \[[13](#参考文献)\] 和Inception模块,因为GoogleNet模型由多组Inception模块组成,模型设计借鉴了NIN的一些思想。 GoogLeNet \[[12](#参考文献)\] 在2014年ILSVRC的获得了冠军,在介绍该模型之前我们先来了解NIN(Network in Network)模型 \[[13](#参考文献)\] 和Inception模块,因为GoogLeNet模型由多组Inception模块组成,模型设计借鉴了NIN的一些思想。
NIN模型主要有两个特点: NIN模型主要有两个特点:
...@@ -110,17 +122,17 @@ Inception模块如下图7所示,图(a)是最简单的设计,输出是3个卷 ...@@ -110,17 +122,17 @@ Inception模块如下图7所示,图(a)是最简单的设计,输出是3个卷
图7. Inception模块 图7. Inception模块
</p> </p>
GoogleNet由多组Inception模块堆积而成。另外,在网络最后也没有采用传统的多层全连接层,而是像NIN网络一样采用了均值池化层;但与NIN不同的是,GoogleNet在池化层后加了一个全连接层来映射类别数。除了这两个特点之外,由于网络中间层特征也很有判别性,GoogleNet在中间层添加了两个辅助分类器,在后向传播中增强梯度并且增强正则化,而整个网络的损失函数是这个三个分类器的损失加权求和。 GoogLeNet由多组Inception模块堆积而成。另外,在网络最后也没有采用传统的多层全连接层,而是像NIN网络一样采用了均值池化层;但与NIN不同的是,GoogLeNet在池化层后加了一个全连接层来映射类别数。除了这两个特点之外,由于网络中间层特征也很有判别性,GoogLeNet在中间层添加了两个辅助分类器,在后向传播中增强梯度并且增强正则化,而整个网络的损失函数是这个三个分类器的损失加权求和。
GoogleNet整体网络结构如图8所示,总共22层网络:开始由3层普通的卷积组成;接下来由三组子网络组成,第一组子网络包含2个Inception模块,第二组包含5个Inception模块,第三组包含2个Inception模块;然后接均值池化层、全连接层。 GoogLeNet整体网络结构如图8所示,总共22层网络:开始由3层普通的卷积组成;接下来由三组子网络组成,第一组子网络包含2个Inception模块,第二组包含5个Inception模块,第三组包含2个Inception模块;然后接均值池化层、全连接层。
<p align="center"> <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/image/googlenet.jpeg?raw=true" ><br/> <img src="https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/image/googlenet.jpeg?raw=true" ><br/>
图8. GoogleNet[12] 图8. GoogLeNet[12]
</p> </p>
上面介绍的是GoogleNet第一版模型(称作GoogleNet-v1)。GoogleNet-v2 \[[14](#参考文献)\] 引入BN层;GoogleNet-v3 \[[16](#参考文献)\] 对一些卷积层做了分解,进一步提高网络非线性能力和加深网络;GoogleNet-v4 \[[17](#参考文献)\] 引入下面要讲的ResNet设计思路。从v1到v4每一版的改进都会带来准确度的提升,介于篇幅,这里不再详细介绍v2到v4的结构。 上面介绍的是GoogLeNet第一版模型(称作GoogLeNet-v1)。GoogLeNet-v2 \[[14](#参考文献)\] 引入BN层;GoogLeNet-v3 \[[16](#参考文献)\] 对一些卷积层做了分解,进一步提高网络非线性能力和加深网络;GoogLeNet-v4 \[[17](#参考文献)\] 引入下面要讲的ResNet设计思路。从v1到v4每一版的改进都会带来准确度的提升,介于篇幅,这里不再详细介绍v2到v4的结构。
### ResNet ### ResNet
...@@ -164,11 +176,12 @@ Paddle API提供了自动加载cifar数据集模块 `paddle.dataset.cifar`。 ...@@ -164,11 +176,12 @@ Paddle API提供了自动加载cifar数据集模块 `paddle.dataset.cifar`。
让我们从导入 Paddle Fluid API 和辅助模块开始。 让我们从导入 Paddle Fluid API 和辅助模块开始。
```python ```python
from __future__ import print_function
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy import numpy
import sys import sys
from __future__ import print_function
``` ```
...@@ -209,7 +222,7 @@ def vgg_bn_drop(input): ...@@ -209,7 +222,7 @@ def vgg_bn_drop(input):
``` ```
1. 首先定义了一组卷积网络,即conv_block。卷积核大小为3x3,池化窗口大小为2x2,窗口滑动大小为2,groups决定每组VGG模块是几次连续的卷积操作,dropouts指定Dropout操作的概率。所使用的`img_conv_group`是在`paddle.networks`中预定义的模块,由若干组 Conv->BN->ReLu->Dropout 和 一组 Pooling 组成。 1. 首先定义了一组卷积网络,即conv_block。卷积核大小为3x3,池化窗口大小为2x2,窗口滑动大小为2,groups决定每组VGG模块是几次连续的卷积操作,dropouts指定Dropout操作的概率。所使用的`img_conv_group`是在`paddle.nets`中预定义的模块,由若干组 Conv->BN->ReLu->Dropout 和 一组 Pooling 组成。
2. 五组卷积操作,即 5个conv_block。 第一、二组采用两次连续的卷积操作。第三、四、五组采用三次连续的卷积操作。每组最后一个卷积后面Dropout概率为0,即不使用Dropout操作。 2. 五组卷积操作,即 5个conv_block。 第一、二组采用两次连续的卷积操作。第三、四、五组采用三次连续的卷积操作。每组最后一个卷积后面Dropout概率为0,即不使用Dropout操作。
...@@ -276,7 +289,7 @@ def layer_warp(block_func, input, ch_in, ch_out, count, stride): ...@@ -276,7 +289,7 @@ def layer_warp(block_func, input, ch_in, ch_out, count, stride):
3. 最后对网络做均值池化并返回该层。 3. 最后对网络做均值池化并返回该层。
注意:除第一层卷积层和最后一层全连接层之外,要求三组 `layer_warp` 总的含参层数能够被6整除,即 `resnet_cifar10` 的 depth 要满足 $(depth - 2) % 6 = 0$ 注意:除第一层卷积层和最后一层全连接层之外,要求三组 `layer_warp` 总的含参层数能够被6整除,即 `resnet_cifar10` 的 depth 要满足(depth-2)%6=0
```python ```python
def resnet_cifar10(ipt, depth=32): def resnet_cifar10(ipt, depth=32):
...@@ -294,7 +307,7 @@ def resnet_cifar10(ipt, depth=32): ...@@ -294,7 +307,7 @@ def resnet_cifar10(ipt, depth=32):
return predict return predict
``` ```
## Infererence Program 配置 ## Inference Program 配置
网络输入定义为 `data_layer` (数据层),在图像分类中即为图像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色图,因此输入数据大小为3072(3x32x32)。 网络输入定义为 `data_layer` (数据层),在图像分类中即为图像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色图,因此输入数据大小为3072(3x32x32)。
...@@ -358,7 +371,7 @@ test_reader = paddle.batch( ...@@ -358,7 +371,7 @@ test_reader = paddle.batch(
``` ```
### Trainer 程序的实现 ### Trainer 程序的实现
我们需要为训练过程制定一个main_program, 同样的,还需要为测试程序配置一个test_program。定义训练的 `place` ,并使用先前定义的优化器 `optimizer_func` 我们需要为训练过程制定一个main_program, 同样的,还需要为测试程序配置一个test_program。定义训练的 `place` ,并使用先前定义的优化器 `optimizer_program`
```python ```python
...@@ -525,11 +538,7 @@ with fluid.scope_guard(inference_scope): ...@@ -525,11 +538,7 @@ with fluid.scope_guard(inference_scope):
[inference_program, feed_target_names, [inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(params_dirname, exe) fetch_targets] = fluid.io.load_inference_model(params_dirname, exe)
# The input's dimension of conv should be 4-D or 5-D.
# Use inference_transpiler to speedup
inference_transpiler_program = inference_program.clone()
t = fluid.transpiler.InferenceTranspiler()
t.transpile(inference_transpiler_program, place)
# Construct feed as a dictionary of {feed_target_name: feed_target_data} # Construct feed as a dictionary of {feed_target_name: feed_target_data}
# and results will contain a list of data corresponding to fetch_targets. # and results will contain a list of data corresponding to fetch_targets.
...@@ -537,14 +546,6 @@ with fluid.scope_guard(inference_scope): ...@@ -537,14 +546,6 @@ with fluid.scope_guard(inference_scope):
feed={feed_target_names[0]: img}, feed={feed_target_names[0]: img},
fetch_list=fetch_targets) fetch_list=fetch_targets)
transpiler_results = exe.run(inference_transpiler_program,
feed={feed_target_names[0]: img},
fetch_list=fetch_targets)
assert len(results[0]) == len(transpiler_results[0])
for i in range(len(results[0])):
numpy.testing.assert_almost_equal(
results[0][i], transpiler_results[0][i], decimal=5)
# infer label # infer label
label_list = [ label_list = [
...@@ -557,7 +558,7 @@ with fluid.scope_guard(inference_scope): ...@@ -557,7 +558,7 @@ with fluid.scope_guard(inference_scope):
## 总结 ## 总结
传统图像分类方法由多个阶段构成,框架较为复杂,而端到端的CNN模型结构可一步到位,而且大幅度提升了分类准确率。本文我们首先介绍VGG、GoogleNet、ResNet三个经典的模型;然后基于CIFAR10数据集,介绍如何使用PaddlePaddle配置和训练CNN模型,尤其是VGG和ResNet模型;最后介绍如何使用PaddlePaddle的API接口对图片进行预测和特征提取。对于其他数据集比如ImageNet,配置和训练流程是同样的,大家可以自行进行实验。 传统图像分类方法由多个阶段构成,框架较为复杂,而端到端的CNN模型结构可一步到位,而且大幅度提升了分类准确率。本文我们首先介绍VGG、GoogLeNet、ResNet三个经典的模型;然后基于CIFAR10数据集,介绍如何使用PaddlePaddle配置和训练CNN模型,尤其是VGG和ResNet模型;最后介绍如何使用PaddlePaddle的API接口对图片进行预测和特征提取。对于其他数据集比如ImageNet,配置和训练流程是同样的,大家可以自行进行实验。
<a name="参考文献"></a> <a name="参考文献"></a>
## 参考文献 ## 参考文献
...@@ -596,7 +597,7 @@ with fluid.scope_guard(inference_scope): ...@@ -596,7 +597,7 @@ with fluid.scope_guard(inference_scope):
[17] Szegedy, C., Ioffe, S., Vanhoucke, V. [Inception-v4, inception-resnet and the impact of residual connections on learning](https://arxiv.org/abs/1602.07261). arXiv:1602.07261 (2016). [17] Szegedy, C., Ioffe, S., Vanhoucke, V. [Inception-v4, inception-resnet and the impact of residual connections on learning](https://arxiv.org/abs/1602.07261). arXiv:1602.07261 (2016).
[18] Everingham, M., Eslami, S. M. A., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, A. [The Pascal Visual Object Classes Challenge: A Retrospective]((http://link.springer.com/article/10.1007/s11263-014-0733-5)). International Journal of Computer Vision, 111(1), 98-136, 2015. [18] Everingham, M., Eslami, S. M. A., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, A. [The Pascal Visual Object Classes Challenge: A Retrospective](http://link.springer.com/article/10.1007/s11263-014-0733-5). International Journal of Computer Vision, 111(1), 98-136, 2015.
[19] He, K., Zhang, X., Ren, S., and Sun, J. [Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://arxiv.org/abs/1502.01852). ArXiv e-prints, February 2015. [19] He, K., Zhang, X., Ren, S., and Sun, J. [Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://arxiv.org/abs/1502.01852). ArXiv e-prints, February 2015.
...@@ -607,4 +608,4 @@ with fluid.scope_guard(inference_scope): ...@@ -607,4 +608,4 @@ with fluid.scope_guard(inference_scope):
[22] http://cs231n.github.io/classification/ [22] http://cs231n.github.io/classification/
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
此差异已折叠。
### This file is only used for continuous evaluation test!
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import os
import sys
sys.path.append(os.environ['ceroot'])
from kpi import CostKpi
from kpi import AccKpi
train_cost_kpi = CostKpi('train_cost', 0.02, 0, actived=True, desc='train cost')
train_acc_kpi = AccKpi('train_acc', 0.02, 0, actived=True, desc='train acc')
test_cost_kpi = CostKpi('test_cost', 0.02, 0, actived=True, desc='test cost')
test_acc_kpi = AccKpi('test_acc', 0.02, 0, actived=True, desc='test acc')
tracking_kpis = [train_cost_kpi, train_acc_kpi, test_cost_kpi, test_acc_kpi]
def parse_log(log):
for line in log.split('\n'):
fs = line.strip().split('\t')
print(fs)
if len(fs) == 3 and fs[0] == 'kpis':
kpi_name = fs[1]
kpi_value = float(fs[2])
yield kpi_name, kpi_value
def log_to_ce(log):
kpi_tracker = {}
for kpi in tracking_kpis:
kpi_tracker[kpi.name] = kpi
for (kpi_name, kpi_value) in parse_log(log):
print(kpi_name, kpi_value)
kpi_tracker[kpi_name].add_record(kpi_value)
kpi_tracker[kpi_name].persist()
if __name__ == '__main__':
log = sys.stdin.read()
log_to_ce(log)
...@@ -45,6 +45,18 @@ ...@@ -45,6 +45,18 @@
本教程源代码目录在[book/image_classification](https://github.com/PaddlePaddle/book/tree/develop/03.image_classification),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。 本教程源代码目录在[book/image_classification](https://github.com/PaddlePaddle/book/tree/develop/03.image_classification),初次使用请您参考[Book文档使用说明](https://github.com/PaddlePaddle/book/blob/develop/README.cn.md#运行这本书)。
### 说明: ###
1.硬件环境要求:
本文可支持在CPU、GPU下运行
2.Docker镜像支持的CUDA/cuDNN版本:
如果使用了Docker运行Book,请注意:这里所提供的默认镜像的GPU环境为 CUDA 8/cuDNN 5,对于NVIDIA Tesla V100等要求CUDA 9的 GPU,使用该镜像可能会运行失败。
3.文档和脚本中代码的一致性问题:
请注意:为使本文更加易读易用,我们拆分、调整了train.py的代码并放入本文。本文中代码与train.py的运行结果一致,可直接运行[train.py](https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/train.py)进行验证。
## 背景介绍 ## 背景介绍
图像相比文字能够提供更加生动、容易理解及更具艺术感的信息,是人们转递与交换信息的重要来源。在本教程中,我们专注于图像识别领域的一个重要问题,即图像分类。 图像相比文字能够提供更加生动、容易理解及更具艺术感的信息,是人们转递与交换信息的重要来源。在本教程中,我们专注于图像识别领域的一个重要问题,即图像分类。
...@@ -94,7 +106,7 @@ ...@@ -94,7 +106,7 @@
2). **特征编码**: 底层特征中包含了大量冗余与噪声,为了提高特征表达的鲁棒性,需要使用一种特征变换算法对底层特征进行编码,称作特征编码。常用的特征编码方法包括向量量化编码 \[[4](#参考文献)\]、稀疏编码 \[[5](#参考文献)\]、局部线性约束编码 \[[6](#参考文献)\]、Fisher向量编码 \[[7](#参考文献)\] 等。 2). **特征编码**: 底层特征中包含了大量冗余与噪声,为了提高特征表达的鲁棒性,需要使用一种特征变换算法对底层特征进行编码,称作特征编码。常用的特征编码方法包括向量量化编码 \[[4](#参考文献)\]、稀疏编码 \[[5](#参考文献)\]、局部线性约束编码 \[[6](#参考文献)\]、Fisher向量编码 \[[7](#参考文献)\] 等。
3). **空间特征约束**: 特征编码之后一般会经过空间特征约束,也称作**特征汇聚**。特征汇聚是指在一个空间范围内,对每一维特征取最大值或者平均值,可以获得一定特征不变形的特征表达。金字塔特征匹配是一种常用的特征聚会方法,这种方法提出将图像均匀分块,在分块内做特征汇聚。 3). **空间特征约束**: 特征编码之后一般会经过空间特征约束,也称作**特征汇聚**。特征汇聚是指在一个空间范围内,对每一维特征取最大值或者平均值,可以获得一定特征不变形的特征表达。金字塔特征匹配是一种常用的特征汇聚方法,这种方法提出将图像均匀分块,在分块内做特征汇聚。
4). **通过分类器分类**: 经过前面步骤之后一张图像可以用一个固定维度的向量进行描述,接下来就是经过分类器对图像进行分类。通常使用的分类器包括SVM(Support Vector Machine, 支持向量机)、随机森林等。而使用核方法的SVM是最为广泛的分类器,在传统图像分类任务上性能很好。 4). **通过分类器分类**: 经过前面步骤之后一张图像可以用一个固定维度的向量进行描述,接下来就是经过分类器对图像进行分类。通常使用的分类器包括SVM(Support Vector Machine, 支持向量机)、随机森林等。而使用核方法的SVM是最为广泛的分类器,在传统图像分类任务上性能很好。
...@@ -124,7 +136,7 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得 ...@@ -124,7 +136,7 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得
另外,在训练过程中由于每层参数不断更新,会导致下一次输入分布发生变化,这样导致训练过程需要精心设计超参数。如2015年Sergey Ioffe和Christian Szegedy提出了Batch Normalization (BN)算法 \[[14](#参考文献)\] 中,每个batch对网络中的每一层特征都做归一化,使得每层分布相对稳定。BN算法不仅起到一定的正则作用,而且弱化了一些超参数的设计。经过实验证明,BN算法加速了模型收敛过程,在后来较深的模型中被广泛使用。 另外,在训练过程中由于每层参数不断更新,会导致下一次输入分布发生变化,这样导致训练过程需要精心设计超参数。如2015年Sergey Ioffe和Christian Szegedy提出了Batch Normalization (BN)算法 \[[14](#参考文献)\] 中,每个batch对网络中的每一层特征都做归一化,使得每层分布相对稳定。BN算法不仅起到一定的正则作用,而且弱化了一些超参数的设计。经过实验证明,BN算法加速了模型收敛过程,在后来较深的模型中被广泛使用。
接下来我们主要介绍VGG,GoogleNet和ResNet网络结构。 接下来我们主要介绍VGG,GoogLeNet和ResNet网络结构。
### VGG ### VGG
...@@ -135,9 +147,9 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得 ...@@ -135,9 +147,9 @@ Alex Krizhevsky在2012年ILSVRC提出的CNN模型 \[[9](#参考文献)\] 取得
图6. 基于ImageNet的VGG16模型 图6. 基于ImageNet的VGG16模型
</p> </p>
### GoogleNet ### GoogLeNet
GoogleNet \[[12](#参考文献)\] 在2014年ILSVRC的获得了冠军,在介绍该模型之前我们先来了解NIN(Network in Network)模型 \[[13](#参考文献)\] 和Inception模块,因为GoogleNet模型由多组Inception模块组成,模型设计借鉴了NIN的一些思想。 GoogLeNet \[[12](#参考文献)\] 在2014年ILSVRC的获得了冠军,在介绍该模型之前我们先来了解NIN(Network in Network)模型 \[[13](#参考文献)\] 和Inception模块,因为GoogLeNet模型由多组Inception模块组成,模型设计借鉴了NIN的一些思想。
NIN模型主要有两个特点: NIN模型主要有两个特点:
...@@ -152,17 +164,17 @@ Inception模块如下图7所示,图(a)是最简单的设计,输出是3个卷 ...@@ -152,17 +164,17 @@ Inception模块如下图7所示,图(a)是最简单的设计,输出是3个卷
图7. Inception模块 图7. Inception模块
</p> </p>
GoogleNet由多组Inception模块堆积而成。另外,在网络最后也没有采用传统的多层全连接层,而是像NIN网络一样采用了均值池化层;但与NIN不同的是,GoogleNet在池化层后加了一个全连接层来映射类别数。除了这两个特点之外,由于网络中间层特征也很有判别性,GoogleNet在中间层添加了两个辅助分类器,在后向传播中增强梯度并且增强正则化,而整个网络的损失函数是这个三个分类器的损失加权求和。 GoogLeNet由多组Inception模块堆积而成。另外,在网络最后也没有采用传统的多层全连接层,而是像NIN网络一样采用了均值池化层;但与NIN不同的是,GoogLeNet在池化层后加了一个全连接层来映射类别数。除了这两个特点之外,由于网络中间层特征也很有判别性,GoogLeNet在中间层添加了两个辅助分类器,在后向传播中增强梯度并且增强正则化,而整个网络的损失函数是这个三个分类器的损失加权求和。
GoogleNet整体网络结构如图8所示,总共22层网络:开始由3层普通的卷积组成;接下来由三组子网络组成,第一组子网络包含2个Inception模块,第二组包含5个Inception模块,第三组包含2个Inception模块;然后接均值池化层、全连接层。 GoogLeNet整体网络结构如图8所示,总共22层网络:开始由3层普通的卷积组成;接下来由三组子网络组成,第一组子网络包含2个Inception模块,第二组包含5个Inception模块,第三组包含2个Inception模块;然后接均值池化层、全连接层。
<p align="center"> <p align="center">
<img src="https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/image/googlenet.jpeg?raw=true" ><br/> <img src="https://github.com/PaddlePaddle/book/blob/develop/03.image_classification/image/googlenet.jpeg?raw=true" ><br/>
图8. GoogleNet[12] 图8. GoogLeNet[12]
</p> </p>
上面介绍的是GoogleNet第一版模型(称作GoogleNet-v1)。GoogleNet-v2 \[[14](#参考文献)\] 引入BN层;GoogleNet-v3 \[[16](#参考文献)\] 对一些卷积层做了分解,进一步提高网络非线性能力和加深网络;GoogleNet-v4 \[[17](#参考文献)\] 引入下面要讲的ResNet设计思路。从v1到v4每一版的改进都会带来准确度的提升,介于篇幅,这里不再详细介绍v2到v4的结构。 上面介绍的是GoogLeNet第一版模型(称作GoogLeNet-v1)。GoogLeNet-v2 \[[14](#参考文献)\] 引入BN层;GoogLeNet-v3 \[[16](#参考文献)\] 对一些卷积层做了分解,进一步提高网络非线性能力和加深网络;GoogLeNet-v4 \[[17](#参考文献)\] 引入下面要讲的ResNet设计思路。从v1到v4每一版的改进都会带来准确度的提升,介于篇幅,这里不再详细介绍v2到v4的结构。
### ResNet ### ResNet
...@@ -206,11 +218,12 @@ Paddle API提供了自动加载cifar数据集模块 `paddle.dataset.cifar`。 ...@@ -206,11 +218,12 @@ Paddle API提供了自动加载cifar数据集模块 `paddle.dataset.cifar`。
让我们从导入 Paddle Fluid API 和辅助模块开始。 让我们从导入 Paddle Fluid API 和辅助模块开始。
```python ```python
from __future__ import print_function
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy import numpy
import sys import sys
from __future__ import print_function
``` ```
...@@ -251,7 +264,7 @@ def vgg_bn_drop(input): ...@@ -251,7 +264,7 @@ def vgg_bn_drop(input):
``` ```
1. 首先定义了一组卷积网络,即conv_block。卷积核大小为3x3,池化窗口大小为2x2,窗口滑动大小为2,groups决定每组VGG模块是几次连续的卷积操作,dropouts指定Dropout操作的概率。所使用的`img_conv_group`是在`paddle.networks`中预定义的模块,由若干组 Conv->BN->ReLu->Dropout 和 一组 Pooling 组成。 1. 首先定义了一组卷积网络,即conv_block。卷积核大小为3x3,池化窗口大小为2x2,窗口滑动大小为2,groups决定每组VGG模块是几次连续的卷积操作,dropouts指定Dropout操作的概率。所使用的`img_conv_group`是在`paddle.nets`中预定义的模块,由若干组 Conv->BN->ReLu->Dropout 和 一组 Pooling 组成。
2. 五组卷积操作,即 5个conv_block。 第一、二组采用两次连续的卷积操作。第三、四、五组采用三次连续的卷积操作。每组最后一个卷积后面Dropout概率为0,即不使用Dropout操作。 2. 五组卷积操作,即 5个conv_block。 第一、二组采用两次连续的卷积操作。第三、四、五组采用三次连续的卷积操作。每组最后一个卷积后面Dropout概率为0,即不使用Dropout操作。
...@@ -318,7 +331,7 @@ def layer_warp(block_func, input, ch_in, ch_out, count, stride): ...@@ -318,7 +331,7 @@ def layer_warp(block_func, input, ch_in, ch_out, count, stride):
3. 最后对网络做均值池化并返回该层。 3. 最后对网络做均值池化并返回该层。
注意:除第一层卷积层和最后一层全连接层之外,要求三组 `layer_warp` 总的含参层数能够被6整除,即 `resnet_cifar10` 的 depth 要满足 $(depth - 2) % 6 = 0$ 注意:除第一层卷积层和最后一层全连接层之外,要求三组 `layer_warp` 总的含参层数能够被6整除,即 `resnet_cifar10` 的 depth 要满足(depth-2)%6=0
```python ```python
def resnet_cifar10(ipt, depth=32): def resnet_cifar10(ipt, depth=32):
...@@ -336,7 +349,7 @@ def resnet_cifar10(ipt, depth=32): ...@@ -336,7 +349,7 @@ def resnet_cifar10(ipt, depth=32):
return predict return predict
``` ```
## Infererence Program 配置 ## Inference Program 配置
网络输入定义为 `data_layer` (数据层),在图像分类中即为图像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色图,因此输入数据大小为3072(3x32x32)。 网络输入定义为 `data_layer` (数据层),在图像分类中即为图像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色图,因此输入数据大小为3072(3x32x32)。
...@@ -400,7 +413,7 @@ test_reader = paddle.batch( ...@@ -400,7 +413,7 @@ test_reader = paddle.batch(
``` ```
### Trainer 程序的实现 ### Trainer 程序的实现
我们需要为训练过程制定一个main_program, 同样的,还需要为测试程序配置一个test_program。定义训练的 `place` ,并使用先前定义的优化器 `optimizer_func`。 我们需要为训练过程制定一个main_program, 同样的,还需要为测试程序配置一个test_program。定义训练的 `place` ,并使用先前定义的优化器 `optimizer_program`。
```python ```python
...@@ -567,11 +580,7 @@ with fluid.scope_guard(inference_scope): ...@@ -567,11 +580,7 @@ with fluid.scope_guard(inference_scope):
[inference_program, feed_target_names, [inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(params_dirname, exe) fetch_targets] = fluid.io.load_inference_model(params_dirname, exe)
# The input's dimension of conv should be 4-D or 5-D.
# Use inference_transpiler to speedup
inference_transpiler_program = inference_program.clone()
t = fluid.transpiler.InferenceTranspiler()
t.transpile(inference_transpiler_program, place)
# Construct feed as a dictionary of {feed_target_name: feed_target_data} # Construct feed as a dictionary of {feed_target_name: feed_target_data}
# and results will contain a list of data corresponding to fetch_targets. # and results will contain a list of data corresponding to fetch_targets.
...@@ -579,14 +588,6 @@ with fluid.scope_guard(inference_scope): ...@@ -579,14 +588,6 @@ with fluid.scope_guard(inference_scope):
feed={feed_target_names[0]: img}, feed={feed_target_names[0]: img},
fetch_list=fetch_targets) fetch_list=fetch_targets)
transpiler_results = exe.run(inference_transpiler_program,
feed={feed_target_names[0]: img},
fetch_list=fetch_targets)
assert len(results[0]) == len(transpiler_results[0])
for i in range(len(results[0])):
numpy.testing.assert_almost_equal(
results[0][i], transpiler_results[0][i], decimal=5)
# infer label # infer label
label_list = [ label_list = [
...@@ -599,7 +600,7 @@ with fluid.scope_guard(inference_scope): ...@@ -599,7 +600,7 @@ with fluid.scope_guard(inference_scope):
## 总结 ## 总结
传统图像分类方法由多个阶段构成,框架较为复杂,而端到端的CNN模型结构可一步到位,而且大幅度提升了分类准确率。本文我们首先介绍VGG、GoogleNet、ResNet三个经典的模型;然后基于CIFAR10数据集,介绍如何使用PaddlePaddle配置和训练CNN模型,尤其是VGG和ResNet模型;最后介绍如何使用PaddlePaddle的API接口对图片进行预测和特征提取。对于其他数据集比如ImageNet,配置和训练流程是同样的,大家可以自行进行实验。 传统图像分类方法由多个阶段构成,框架较为复杂,而端到端的CNN模型结构可一步到位,而且大幅度提升了分类准确率。本文我们首先介绍VGG、GoogLeNet、ResNet三个经典的模型;然后基于CIFAR10数据集,介绍如何使用PaddlePaddle配置和训练CNN模型,尤其是VGG和ResNet模型;最后介绍如何使用PaddlePaddle的API接口对图片进行预测和特征提取。对于其他数据集比如ImageNet,配置和训练流程是同样的,大家可以自行进行实验。
<a name="参考文献"></a> <a name="参考文献"></a>
## 参考文献 ## 参考文献
...@@ -638,7 +639,7 @@ with fluid.scope_guard(inference_scope): ...@@ -638,7 +639,7 @@ with fluid.scope_guard(inference_scope):
[17] Szegedy, C., Ioffe, S., Vanhoucke, V. [Inception-v4, inception-resnet and the impact of residual connections on learning](https://arxiv.org/abs/1602.07261). arXiv:1602.07261 (2016). [17] Szegedy, C., Ioffe, S., Vanhoucke, V. [Inception-v4, inception-resnet and the impact of residual connections on learning](https://arxiv.org/abs/1602.07261). arXiv:1602.07261 (2016).
[18] Everingham, M., Eslami, S. M. A., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, A. [The Pascal Visual Object Classes Challenge: A Retrospective]((http://link.springer.com/article/10.1007/s11263-014-0733-5)). International Journal of Computer Vision, 111(1), 98-136, 2015. [18] Everingham, M., Eslami, S. M. A., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, A. [The Pascal Visual Object Classes Challenge: A Retrospective](http://link.springer.com/article/10.1007/s11263-014-0733-5). International Journal of Computer Vision, 111(1), 98-136, 2015.
[19] He, K., Zhang, X., Ren, S., and Sun, J. [Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://arxiv.org/abs/1502.01852). ArXiv e-prints, February 2015. [19] He, K., Zhang, X., Ren, S., and Sun, J. [Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://arxiv.org/abs/1502.01852). ArXiv e-prints, February 2015.
...@@ -649,7 +650,7 @@ with fluid.scope_guard(inference_scope): ...@@ -649,7 +650,7 @@ with fluid.scope_guard(inference_scope):
[22] http://cs231n.github.io/classification/ [22] http://cs231n.github.io/classification/
<br/> <br/>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://paddlepaddleimage.cdn.bcebos.com/bookimage/camo.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">本教程</span><a xmlns:cc="http://creativecommons.org/ns#" href="http://book.paddlepaddle.org" property="cc:attributionName" rel="cc:attributionURL">PaddlePaddle</a> 创作,采用 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">知识共享 署名-相同方式共享 4.0 国际 许可协议</a>进行许可。
</div> </div>
<!-- You can change the lines below now. --> <!-- You can change the lines below now. -->
......
此差异已折叠。
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
from __future__ import print_function from __future__ import print_function
import os import os
import argparse
import paddle import paddle
import paddle.fluid as fluid import paddle.fluid as fluid
import numpy import numpy
...@@ -23,6 +24,20 @@ from vgg import vgg_bn_drop ...@@ -23,6 +24,20 @@ from vgg import vgg_bn_drop
from resnet import resnet_cifar10 from resnet import resnet_cifar10
def parse_args():
parser = argparse.ArgumentParser("image_classification")
parser.add_argument(
'--enable_ce',
action='store_true',
help='If set, run the task with continuous evaluation logs.')
parser.add_argument(
'--use_gpu', type=bool, default=0, help='whether to use gpu')
parser.add_argument(
'--num_epochs', type=int, default=1, help='number of epoch')
args = parser.parse_args()
return args
def inference_network(): def inference_network():
# The image is 32 * 32 with RGB representation. # The image is 32 * 32 with RGB representation.
data_shape = [3, 32, 32] data_shape = [3, 32, 32]
...@@ -48,31 +63,40 @@ def optimizer_program(): ...@@ -48,31 +63,40 @@ def optimizer_program():
def train(use_cuda, params_dirname): def train(use_cuda, params_dirname):
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
BATCH_SIZE = 128 BATCH_SIZE = 128
if args.enable_ce:
train_reader = paddle.batch(
paddle.dataset.cifar.train10(), batch_size=BATCH_SIZE)
test_reader = paddle.batch(
paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)
else:
test_reader = paddle.batch(
paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)
train_reader = paddle.batch( train_reader = paddle.batch(
paddle.reader.shuffle( paddle.reader.shuffle(
paddle.dataset.cifar.train10(), buf_size=128 * 100), paddle.dataset.cifar.train10(), buf_size=128 * 100),
batch_size=BATCH_SIZE) batch_size=BATCH_SIZE)
test_reader = paddle.batch(
paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)
feed_order = ['pixel', 'label'] feed_order = ['pixel', 'label']
main_program = fluid.default_main_program() main_program = fluid.default_main_program()
star_program = fluid.default_startup_program() start_program = fluid.default_startup_program()
if args.enable_ce:
main_program.random_seed = 90
start_program.random_seed = 90
predict = inference_network() predict = inference_network()
avg_cost, acc = train_network(predict) avg_cost, acc = train_network(predict)
# Test program # Test program
test_program = main_program.clone(for_test=True) test_program = main_program.clone(for_test=True)
optimizer = optimizer_program() optimizer = optimizer_program()
optimizer.minimize(avg_cost) optimizer.minimize(avg_cost)
exe = fluid.Executor(place) exe = fluid.Executor(place)
EPOCH_NUM = 1 EPOCH_NUM = args.num_epochs
# For training test cost # For training test cost
def train_test(program, reader): def train_test(program, reader):
...@@ -100,7 +124,7 @@ def train(use_cuda, params_dirname): ...@@ -100,7 +124,7 @@ def train(use_cuda, params_dirname):
main_program.global_block().var(var_name) for var_name in feed_order main_program.global_block().var(var_name) for var_name in feed_order
] ]
feeder = fluid.DataFeeder(feed_list=feed_var_list_loop, place=place) feeder = fluid.DataFeeder(feed_list=feed_var_list_loop, place=place)
exe.run(star_program) exe.run(start_program)
step = 0 step = 0
for pass_id in range(EPOCH_NUM): for pass_id in range(EPOCH_NUM):
...@@ -126,6 +150,12 @@ def train(use_cuda, params_dirname): ...@@ -126,6 +150,12 @@ def train(use_cuda, params_dirname):
fluid.io.save_inference_model(params_dirname, ["pixel"], fluid.io.save_inference_model(params_dirname, ["pixel"],
[predict], exe) [predict], exe)
if args.enable_ce and pass_id == EPOCH_NUM - 1:
print("kpis\ttrain_cost\t%f" % avg_loss_value[0])
print("kpis\ttrain_acc\t%f" % avg_loss_value[1])
print("kpis\ttest_cost\t%f" % avg_cost_test)
print("kpis\ttest_acc\t%f" % accuracy_test)
train_loop() train_loop()
...@@ -161,12 +191,6 @@ def infer(use_cuda, params_dirname=None): ...@@ -161,12 +191,6 @@ def infer(use_cuda, params_dirname=None):
[inference_program, feed_target_names, [inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(params_dirname, exe) fetch_targets] = fluid.io.load_inference_model(params_dirname, exe)
# The input's dimension of conv should be 4-D or 5-D.
# Use inference_transpiler to speedup
inference_transpiler_program = inference_program.clone()
t = fluid.transpiler.InferenceTranspiler()
t.transpile(inference_transpiler_program, place)
# Construct feed as a dictionary of {feed_target_name: feed_target_data} # Construct feed as a dictionary of {feed_target_name: feed_target_data}
# and results will contain a list of data corresponding to fetch_targets. # and results will contain a list of data corresponding to fetch_targets.
results = exe.run( results = exe.run(
...@@ -174,16 +198,6 @@ def infer(use_cuda, params_dirname=None): ...@@ -174,16 +198,6 @@ def infer(use_cuda, params_dirname=None):
feed={feed_target_names[0]: img}, feed={feed_target_names[0]: img},
fetch_list=fetch_targets) fetch_list=fetch_targets)
transpiler_results = exe.run(
inference_transpiler_program,
feed={feed_target_names[0]: img},
fetch_list=fetch_targets)
assert len(results[0]) == len(transpiler_results[0])
for i in range(len(results[0])):
numpy.testing.assert_almost_equal(
results[0][i], transpiler_results[0][i], decimal=5)
# infer label # infer label
label_list = [ label_list = [
"airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "airplane", "automobile", "bird", "cat", "deer", "dog", "frog",
...@@ -206,4 +220,6 @@ def main(use_cuda): ...@@ -206,4 +220,6 @@ def main(use_cuda):
if __name__ == '__main__': if __name__ == '__main__':
# For demo purpose, the training runs on CPU # For demo purpose, the training runs on CPU
# Please change accordingly. # Please change accordingly.
main(use_cuda=False) args = parse_args()
use_cuda = args.use_gpu
main(use_cuda)
#!/bin/bash
#This file is only used for continuous evaluation.
python train.py --enable_ce | python _ce.py
此差异已折叠。
此差异已折叠。
### This file is only used for continuous evaluation test!
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import os
import sys
sys.path.append(os.environ['ceroot'])
from kpi import CostKpi
train_cost_kpi = CostKpi('train_cost', 0.02, 0, actived=True, desc='train cost')
tracking_kpis = [train_cost_kpi]
def parse_log(log):
for line in log.split('\n'):
fs = line.strip().split('\t')
print(fs)
if len(fs) == 3 and fs[0] == 'kpis':
kpi_name = fs[1]
kpi_value = float(fs[2])
yield kpi_name, kpi_value
def log_to_ce(log):
kpi_tracker = {}
for kpi in tracking_kpis:
kpi_tracker[kpi.name] = kpi
for (kpi_name, kpi_value) in parse_log(log):
print(kpi_name, kpi_value)
kpi_tracker[kpi_name].add_record(kpi_value)
kpi_tracker[kpi_name].persist()
if __name__ == '__main__':
log = sys.stdin.read()
log_to_ce(log)
此差异已折叠。
此差异已折叠。
此差异已折叠。
#!/bin/bash
#This file is only used for continuous evaluation.
python train.py --enable_ce | python _ce.py
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
#!/bin/bash
#This file is only used for continuous evaluation.
export FLAGS_cudnn_deterministic=true
export CUDA_VISIBLE_DEVICES=0
python train_conv.py --use_gpu 1 --num_epochs=1 --enable_ce | python _ce.py
python train_dyn_rnn.py --use_gpu 1 --num_epochs=1 --enable_ce | python _ce.py
python train_stacked_lstm.py --use_gpu 1 --num_epochs=1 --enable_ce | python _ce.py
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
#!/bin/bash
#This file is only used for continuous evaluation.
python train.py --enable_ce | python _ce.py
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册