main.py 8.4 KB
Newer Older
D
dengkaipeng 已提交
1
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
Y
Yang Zhang 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import division
Y
Yang Zhang 已提交
16
from __future__ import print_function
Y
Yang Zhang 已提交
17 18 19 20 21 22 23

import argparse
import contextlib
import os

import numpy as np

D
dengkaipeng 已提交
24 25
from paddle import fluid
from paddle.fluid.optimizer import Momentum
D
dengkaipeng 已提交
26
from paddle.io import DataLoader
Y
Yang Zhang 已提交
27

28 29
from hapi.model import Model, Input, set_device
from hapi.distributed import DistributedBatchSampler
D
dengkaipeng 已提交
30
from hapi.vision.transforms import Compose, BatchCompose
D
dengkaipeng 已提交
31

D
dengkaipeng 已提交
32 33
from modeling import yolov3_darknet53, YoloLoss
from coco import COCODataset
D
dengkaipeng 已提交
34
from coco_metric import COCOMetric
D
dengkaipeng 已提交
35
from transforms import *
D
dengkaipeng 已提交
36
from utils import print_arguments
D
dengkaipeng 已提交
37

D
dengkaipeng 已提交
38
NUM_MAX_BOXES = 50
Y
Yang Zhang 已提交
39 40


D
dengkaipeng 已提交
41
def make_optimizer(step_per_epoch, parameter_list=None):
Y
Yang Zhang 已提交
42
    base_lr = FLAGS.lr
D
dengkaipeng 已提交
43
    warm_up_iter = 1000
Y
Yang Zhang 已提交
44 45
    momentum = 0.9
    weight_decay = 5e-4
D
dengkaipeng 已提交
46
    boundaries = [step_per_epoch * e for e in [200, 250]]
L
LielinJiang 已提交
47
    values = [base_lr * (0.1**i) for i in range(len(boundaries) + 1)]
Y
Yang Zhang 已提交
48
    learning_rate = fluid.layers.piecewise_decay(
L
LielinJiang 已提交
49
        boundaries=boundaries, values=values)
Y
Yang Zhang 已提交
50 51
    learning_rate = fluid.layers.linear_lr_warmup(
        learning_rate=learning_rate,
Y
Yang Zhang 已提交
52 53 54 55
        warmup_steps=warm_up_iter,
        start_lr=0.0,
        end_lr=base_lr)
    optimizer = fluid.optimizer.Momentum(
Y
Yang Zhang 已提交
56
        learning_rate=learning_rate,
Y
Yang Zhang 已提交
57 58 59 60 61 62 63
        regularization=fluid.regularizer.L2Decay(weight_decay),
        momentum=momentum,
        parameter_list=parameter_list)
    return optimizer


def main():
D
dengkaipeng 已提交
64 65
    device = set_device(FLAGS.device)
    fluid.enable_dygraph(device) if FLAGS.dynamic else None
L
LielinJiang 已提交
66 67 68 69 70 71 72

    inputs = [
        Input(
            [None, 1], 'int64', name='img_id'), Input(
                [None, 2], 'int32', name='img_shape'), Input(
                    [None, 3, None, None], 'float32', name='image')
    ]
L
LielinJiang 已提交
73

L
LielinJiang 已提交
74 75 76 77 78 79 80 81 82 83 84 85
    labels = [
        Input(
            [None, NUM_MAX_BOXES, 4], 'float32', name='gt_bbox'), Input(
                [None, NUM_MAX_BOXES], 'int32', name='gt_label'), Input(
                    [None, NUM_MAX_BOXES], 'float32', name='gt_score')
    ]

    if not FLAGS.eval_only:  # training mode
        train_transform = Compose([
            ColorDistort(), RandomExpand(), RandomCrop(), RandomFlip(),
            NormalizeBox(), PadBox(), BboxXYXY2XYWH()
        ])
L
LielinJiang 已提交
86

D
dengkaipeng 已提交
87
        train_collate_fn = BatchCompose([RandomShape(), NormalizeImage()])
L
LielinJiang 已提交
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
        dataset = COCODataset(
            dataset_dir=FLAGS.data,
            anno_path='annotations/instances_train2017.json',
            image_dir='train2017',
            with_background=False,
            mixup=True,
            transform=train_transform)
        batch_sampler = DistributedBatchSampler(
            dataset, batch_size=FLAGS.batch_size, shuffle=True, drop_last=True)
        loader = DataLoader(
            dataset,
            batch_sampler=batch_sampler,
            places=device,
            num_workers=FLAGS.num_workers,
            return_list=True,
            collate_fn=train_collate_fn)
    else:  # evaluation mode
        eval_transform = Compose([
            ResizeImage(target_size=608), NormalizeBox(), PadBox(),
            BboxXYXY2XYWH()
        ])
L
LielinJiang 已提交
109

D
dengkaipeng 已提交
110
        eval_collate_fn = BatchCompose([NormalizeImage()])
L
LielinJiang 已提交
111 112 113 114 115 116
        dataset = COCODataset(
            dataset_dir=FLAGS.data,
            anno_path='annotations/instances_val2017.json',
            image_dir='val2017',
            with_background=False,
            transform=eval_transform)
D
dengkaipeng 已提交
117
        # batch_size can only be 1 in evaluation for YOLOv3
D
dengkaipeng 已提交
118
        # prediction bbox is a LoDTensor
L
LielinJiang 已提交
119 120 121 122 123 124 125 126 127
        batch_sampler = DistributedBatchSampler(
            dataset, batch_size=1, shuffle=False, drop_last=False)
        loader = DataLoader(
            dataset,
            batch_sampler=batch_sampler,
            places=device,
            num_workers=FLAGS.num_workers,
            return_list=True,
            collate_fn=eval_collate_fn)
D
dengkaipeng 已提交
128

D
dengkaipeng 已提交
129
    pretrained = FLAGS.eval_only and FLAGS.weights is None
L
LielinJiang 已提交
130 131 132 133
    model = yolov3_darknet53(
        num_classes=dataset.num_classes,
        model_mode='eval' if FLAGS.eval_only else 'train',
        pretrained=pretrained)
D
dengkaipeng 已提交
134

D
dengkaipeng 已提交
135
    if FLAGS.pretrain_weights and not FLAGS.eval_only:
L
LielinJiang 已提交
136 137
        model.load(
            FLAGS.pretrain_weights, skip_mismatch=True, reset_optimizer=True)
D
dengkaipeng 已提交
138

L
LielinJiang 已提交
139 140
    optim = make_optimizer(
        len(batch_sampler), parameter_list=model.parameters())
D
dengkaipeng 已提交
141

L
LielinJiang 已提交
142 143 144 145 146 147
    model.prepare(
        optim,
        YoloLoss(num_classes=dataset.num_classes),
        inputs=inputs,
        labels=labels,
        device=FLAGS.device)
D
dengkaipeng 已提交
148 149 150 151 152 153 154 155 156 157 158

    # NOTE: we implement COCO metric of YOLOv3 model here, separately
    # from 'prepare' and 'fit' framework for follwing reason:
    # 1. YOLOv3 network structure is different between 'train' and
    # 'eval' mode, in 'eval' mode, output prediction bbox is not the
    # feature map used for YoloLoss calculating
    # 2. COCO metric behavior is also different from defined Metric
    # for COCO metric should not perform accumulate in each iteration
    # but only accumulate at the end of an epoch
    if FLAGS.eval_only:
        if FLAGS.weights is not None:
D
dengkaipeng 已提交
159
            model.load(FLAGS.weights, reset_optimizer=True)
D
dengkaipeng 已提交
160
        preds = model.predict(loader, stack_outputs=False)
D
dengkaipeng 已提交
161
        _, _, _, img_ids, bboxes = preds
Y
Yang Zhang 已提交
162

L
LielinJiang 已提交
163 164
        anno_path = os.path.join(FLAGS.data,
                                 'annotations/instances_val2017.json')
D
dengkaipeng 已提交
165 166 167 168 169 170
        coco_metric = COCOMetric(anno_path=anno_path, with_background=False)
        for img_id, bbox in zip(img_ids, bboxes):
            coco_metric.update(img_id, bbox)
        coco_metric.accumulate()
        coco_metric.reset()
        return
Y
Yang Zhang 已提交
171

D
dengkaipeng 已提交
172 173
    if FLAGS.resume is not None:
        model.load(FLAGS.resume)
Y
Yang Zhang 已提交
174

D
dengkaipeng 已提交
175 176
    save_dir = FLAGS.save_dir or 'yolo_checkpoint'

D
dengkaipeng 已提交
177 178
    model.fit(train_data=loader,
              epochs=FLAGS.epoch - FLAGS.no_mixup_epoch,
D
dengkaipeng 已提交
179
              save_dir=os.path.join(save_dir, "mixup"),
D
dengkaipeng 已提交
180
              save_freq=10)
Y
Yang Zhang 已提交
181

D
dengkaipeng 已提交
182
    # do not use image mixup transfrom in the last FLAGS.no_mixup_epoch epoches
D
dengkaipeng 已提交
183 184 185
    dataset.mixup = False
    model.fit(train_data=loader,
              epochs=FLAGS.no_mixup_epoch,
D
dengkaipeng 已提交
186
              save_dir=os.path.join(save_dir, "no_mixup"),
D
dengkaipeng 已提交
187
              save_freq=5)
Y
Yang Zhang 已提交
188 189 190


if __name__ == '__main__':
D
dengkaipeng 已提交
191 192
    parser = argparse.ArgumentParser("Yolov3 Training on VOC")
    parser.add_argument(
L
LielinJiang 已提交
193 194 195
        "--data",
        type=str,
        default='dataset/voc',
D
dengkaipeng 已提交
196
        help="path to dataset directory")
D
dengkaipeng 已提交
197 198
    parser.add_argument(
        "--device", type=str, default='gpu', help="device to use, gpu or cpu")
Y
Yang Zhang 已提交
199 200
    parser.add_argument(
        "-d", "--dynamic", action='store_true', help="enable dygraph mode")
D
dengkaipeng 已提交
201 202
    parser.add_argument(
        "--eval_only", action='store_true', help="run evaluation only")
Y
Yang Zhang 已提交
203 204
    parser.add_argument(
        "-e", "--epoch", default=300, type=int, help="number of epoch")
D
dengkaipeng 已提交
205
    parser.add_argument(
L
LielinJiang 已提交
206 207 208
        "--no_mixup_epoch",
        default=30,
        type=int,
D
dengkaipeng 已提交
209
        help="number of the last N epoch without image mixup")
Y
Yang Zhang 已提交
210
    parser.add_argument(
L
LielinJiang 已提交
211 212 213 214 215
        '--lr',
        '--learning-rate',
        default=0.001,
        type=float,
        metavar='LR',
Y
Yang Zhang 已提交
216
        help='initial learning rate')
Y
Yang Zhang 已提交
217
    parser.add_argument(
D
dengkaipeng 已提交
218 219
        "-b", "--batch_size", default=8, type=int, help="batch size")
    parser.add_argument(
L
LielinJiang 已提交
220 221 222 223 224
        "-j",
        "--num_workers",
        default=4,
        type=int,
        help="reader worker number")
Y
Yang Zhang 已提交
225
    parser.add_argument(
L
LielinJiang 已提交
226 227 228 229
        "-p",
        "--pretrain_weights",
        default=None,
        type=str,
Y
Yang Zhang 已提交
230
        help="path to pretrained weights")
D
dengkaipeng 已提交
231
    parser.add_argument(
L
LielinJiang 已提交
232
        "-r", "--resume", default=None, type=str, help="path to model weights")
D
dengkaipeng 已提交
233
    parser.add_argument(
L
LielinJiang 已提交
234 235 236 237
        "-w",
        "--weights",
        default=None,
        type=str,
D
dengkaipeng 已提交
238
        help="path to weights for evaluation")
D
dengkaipeng 已提交
239 240 241 242 243 244
    parser.add_argument(
        "-s",
        "--save_dir",
        default=None,
        type=str,
        help="directory path for checkpoint saving, default ./yolo_checkpoint")
Y
Yang Zhang 已提交
245
    FLAGS = parser.parse_args()
D
dengkaipeng 已提交
246
    print_arguments(FLAGS)
Y
Yang Zhang 已提交
247
    assert FLAGS.data, "error: must provide data path"
Y
Yang Zhang 已提交
248
    main()