README.md 7.7 KB
Newer Older
W
wwhu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
图像分类
=======================

这里将介绍如何在PaddlePaddle下使用AlexNet、VGG、GoogLeNet和ResNet模型进行图像分类。图像分类问题的描述和这四种模型的介绍可以参考[PaddlePaddle book](https://github.com/PaddlePaddle/book/tree/develop/03.image_classification)

## 训练模型

### 初始化

在初始化阶段需要导入所用的包,并对PaddlePaddle进行初始化。

```python
import gzip
W
wwhu 已提交
14
import paddle.v2.dataset.flowers as flowers
W
wwhu 已提交
15 16 17 18 19 20
import paddle.v2 as paddle
import reader
import vgg
import resnet
import alexnet
import googlenet
W
wwhu 已提交
21

W
wwhu 已提交
22 23 24 25 26 27 28 29 30 31 32

# PaddlePaddle init
paddle.init(use_gpu=False, trainer_count=1)
```

### 定义参数和输入

设置算法参数(如数据维度、类别数目和batch size等参数),定义数据输入层`image`和类别标签`lbl`

```python
DATA_DIM = 3 * 224 * 224
W
wwhu 已提交
33
CLASS_DIM = 102
W
wwhu 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
BATCH_SIZE = 128

image = paddle.layer.data(
    name="image", type=paddle.data_type.dense_vector(DATA_DIM))
lbl = paddle.layer.data(
    name="label", type=paddle.data_type.integer_value(CLASS_DIM))
```

### 获得所用模型

这里可以选择使用AlexNet、VGG、GoogLeNet和ResNet模型中的一个模型进行图像分类。通过调用相应的方法可以获得网络最后的Softmax层。

1. 使用AlexNet模型

指定输入层`image`和类别数目`CLASS_DIM`后,可以通过下面的代码得到AlexNet的Softmax层。

```python
out = alexnet.alexnet(image, class_dim=CLASS_DIM)
```

2. 使用VGG模型

根据层数的不同,VGG分为VGG13、VGG16和VGG19。使用VGG16模型的代码如下:

```python
out = vgg.vgg16(image, class_dim=CLASS_DIM)
```

类似地,VGG13和VGG19可以分别通过`vgg.vgg13``vgg.vgg19`方法获得。

3. 使用GoogLeNet模型

GoogLeNet在训练阶段使用两个辅助的分类器强化梯度信息并进行额外的正则化。因此`googlenet.googlenet`共返回三个Softmax层,如下面的代码所示:

```python
out, out1, out2 = googlenet.googlenet(image, class_dim=CLASS_DIM)
loss1 = paddle.layer.cross_entropy_cost(
    input=out1, label=lbl, coeff=0.3)
paddle.evaluator.classification_error(input=out1, label=lbl)
loss2 = paddle.layer.cross_entropy_cost(
    input=out2, label=lbl, coeff=0.3)
paddle.evaluator.classification_error(input=out2, label=lbl)
extra_layers = [loss1, loss2]
```

对于两个辅助的输出,这里分别对其计算损失函数并评价错误率,然后将损失作为后文SGD的extra_layers。

4. 使用ResNet模型

ResNet模型可以通过下面的代码获取:

```python
out = resnet.resnet_imagenet(image, class_dim=CLASS_DIM)
```

### 定义损失函数

```python
cost = paddle.layer.classification_cost(input=out, label=lbl)
```

### 创建参数和优化方法

```python
# Create parameters
parameters = paddle.parameters.create(cost)

# Create optimizer
optimizer = paddle.optimizer.Momentum(
    momentum=0.9,
    regularization=paddle.optimizer.L2Regularization(rate=0.0005 *
                                                     BATCH_SIZE),
    learning_rate=0.001 / BATCH_SIZE,
    learning_rate_decay_a=0.1,
    learning_rate_decay_b=128000 * 35,
    learning_rate_schedule="discexp", )
```

W
wwhu 已提交
112 113 114 115 116
通过 `learning_rate_decay_a` (简写$a$) 、`learning_rate_decay_b` (简写$b$) 和 `learning_rate_schedule` 指定学习率调整策略,这里采用离散指数的方式调节学习率,计算公式如下, $n$ 代表已经处理过的累计总样本数,$lr_{0}$ 即为参数里设置的 `learning_rate`

$$  lr = lr_{0} * a^ {\lfloor \frac{n}{ b}\rfloor} $$


W
wwhu 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
### 定义数据读取

首先以[花卉数据](http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html)为例说明如何定义输入。下面的代码定义了花卉数据训练集和验证集的输入:

```python
train_reader = paddle.batch(
    paddle.reader.shuffle(
        flowers.train(),
        buf_size=1000),
    batch_size=BATCH_SIZE)
test_reader = paddle.batch(
    flowers.valid(),
    batch_size=BATCH_SIZE)
```

若需要使用其他数据,则需要先建立图像列表文件。`reader.py`定义了这种文件的读取方式,它从图像列表文件中解析出图像路径和类别标签。

图像列表文件是一个文本文件,其中每一行由一个图像路径和类别标签构成,二者以跳格符(Tab)隔开。类别标签用整数表示,其最小值为0。下面给出一个图像列表文件的片段示例:
W
wwhu 已提交
135

W
wwhu 已提交
136 137 138 139 140 141 142 143 144 145
```
dataset_100/train_images/n03982430_23191.jpeg    1
dataset_100/train_images/n04461696_23653.jpeg    7
dataset_100/train_images/n02441942_3170.jpeg 8
dataset_100/train_images/n03733281_31716.jpeg    2
dataset_100/train_images/n03424325_240.jpeg  0
dataset_100/train_images/n02643566_75.jpeg   8
```

训练时需要分别指定训练集和验证集的图像列表文件。这里假设这两个文件分别为`train.list``val.list`,数据读取方式如下:
W
wwhu 已提交
146 147 148 149 150 151 152 153 154 155

```python
train_reader = paddle.batch(
    paddle.reader.shuffle(
        reader.test_reader('train.list'),
        buf_size=1000),
    batch_size=BATCH_SIZE)
test_reader = paddle.batch(
    reader.train_reader('val.list'),
    batch_size=BATCH_SIZE)
W
wwhu 已提交
156
```
W
wwhu 已提交
157

W
wwhu 已提交
158 159
### 定义事件处理程序
```python
W
wwhu 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
# End batch and end pass event handler
def event_handler(event):
    if isinstance(event, paddle.event.EndIteration):
        if event.batch_id % 1 == 0:
            print "\nPass %d, Batch %d, Cost %f, %s" % (
                event.pass_id, event.batch_id, event.cost, event.metrics)
    if isinstance(event, paddle.event.EndPass):
        with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
            parameters.to_tar(f)

        result = trainer.test(reader=test_reader)
        print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
```

### 定义训练方法

对于AlexNet、VGG和ResNet,可以按下面的代码定义训练方法:

```python
# Create trainer
trainer = paddle.trainer.SGD(
    cost=cost,
    parameters=parameters,
    update_equation=optimizer)
```

GoogLeNet有两个额外的输出层,因此需要指定`extra_layers`,如下所示:

```python
# Create trainer
trainer = paddle.trainer.SGD(
    cost=cost,
    parameters=parameters,
    update_equation=optimizer,
    extra_layers=extra_layers)
```

### 开始训练

```python
trainer.train(
    reader=train_reader, num_passes=200, event_handler=event_handler)
```
W
wwhu 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

## 应用模型
模型训练好后,可以使用下面的代码预测给定图片的类别。

```python
# load parameters
with gzip.open('params_pass_10.tar.gz', 'r') as f:
    parameters = paddle.parameters.Parameters.from_tar(f)

def load_image(file):
    im = Image.open(file)
    im = im.resize((224, 224), Image.ANTIALIAS)
    im = np.array(im).astype(np.float32)
    # The storage order of the loaded image is W(widht),
    # H(height), C(channel). PaddlePaddle requires
    # the CHW order, so transpose them.
    im = im.transpose((2, 0, 1))  # CHW
    # In the training phase, the channel order of CIFAR
    # image is B(Blue), G(green), R(Red). But PIL open
    # image in RGB mode. It must swap the channel order.
    im = im[(2, 1, 0), :, :]  # BGR
    im = im.flatten()
    im = im / 255.0
    return im

file_list = [line.strip() for line in open(image_list_file)]
test_data = [(load_image(image_file),) for image_file in file_list]
probs = paddle.infer(
    output_layer=out, parameters=parameters, input=test_data)
lab = np.argsort(-probs)
for file_name, result in zip(file_list, lab):
    print "Label of %s is: %d" % (file_name, result[0])
```

首先从文件中加载训练好的模型(代码里以第10轮迭代的结果为例),然后读取`image_list_file`中的图像。`image_list_file`是一个文本文件,每一行为一个图像路径。`load_image`是一个加载图像的函数。代码使用`paddle.infer`判断`image_list_file`中每个图像的类别,并进行输出。