diff --git a/paddle/fluid/API.spec b/paddle/fluid/API.spec index 8344a913e9bf818ab7608f181396a3e016b70351..7e0d5e60887b62dcf348cf58d6704ef594be1f45 100644 --- a/paddle/fluid/API.spec +++ b/paddle/fluid/API.spec @@ -183,7 +183,6 @@ paddle.fluid.layers.similarity_focus ArgSpec(args=['input', 'axis', 'indexes', ' paddle.fluid.layers.hash ArgSpec(args=['input', 'hash_size', 'num_hash', 'name'], varargs=None, keywords=None, defaults=(1, None)) paddle.fluid.layers.grid_sampler ArgSpec(args=['x', 'grid', 'name'], varargs=None, keywords=None, defaults=(None,)) paddle.fluid.layers.log_loss ArgSpec(args=['input', 'label', 'epsilon', 'name'], varargs=None, keywords=None, defaults=(0.0001, None)) -paddle.fluid.layers.yolov3_loss ArgSpec(args=['x', 'gtbox', 'anchors', 'class_num', 'ignore_thresh', 'name'], varargs=None, keywords=None, defaults=(None,)) paddle.fluid.layers.add_position_encoding ArgSpec(args=['input', 'alpha', 'beta', 'name'], varargs=None, keywords=None, defaults=(None,)) paddle.fluid.layers.bilinear_tensor_product ArgSpec(args=['x', 'y', 'size', 'act', 'name', 'param_attr', 'bias_attr'], varargs=None, keywords=None, defaults=(None, None, None, None)) paddle.fluid.layers.data ArgSpec(args=['name', 'shape', 'append_batch_size', 'dtype', 'lod_level', 'type', 'stop_gradient'], varargs=None, keywords=None, defaults=(True, 'float32', 0, VarType.LOD_TENSOR, True)) @@ -289,6 +288,7 @@ paddle.fluid.layers.generate_proposals ArgSpec(args=['scores', 'bbox_deltas', 'i paddle.fluid.layers.iou_similarity ArgSpec(args=['x', 'y', 'name'], varargs=None, keywords=None, defaults=(None,)) paddle.fluid.layers.box_coder ArgSpec(args=['prior_box', 'prior_box_var', 'target_box', 'code_type', 'box_normalized', 'name'], varargs=None, keywords=None, defaults=('encode_center_size', True, None)) paddle.fluid.layers.polygon_box_transform ArgSpec(args=['input', 'name'], varargs=None, keywords=None, defaults=(None,)) +paddle.fluid.layers.yolov3_loss ArgSpec(args=['x', 'gtbox', 'anchors', 'class_num', 'ignore_thresh', 'lambda_xy', 'lambda_wh', 'lambda_conf_obj', 'lambda_conf_noobj', 'lambda_class', 'name'], varargs=None, keywords=None, defaults=(None, None, None, None, None, None)) paddle.fluid.layers.accuracy ArgSpec(args=['input', 'label', 'k', 'correct', 'total'], varargs=None, keywords=None, defaults=(1, None, None)) paddle.fluid.layers.auc ArgSpec(args=['input', 'label', 'curve', 'num_thresholds', 'topk', 'slide_steps'], varargs=None, keywords=None, defaults=('ROC', 4095, 1, 1)) paddle.fluid.layers.exponential_decay ArgSpec(args=['learning_rate', 'decay_steps', 'decay_rate', 'staircase'], varargs=None, keywords=None, defaults=(False,)) diff --git a/paddle/fluid/operators/yolov3_loss_op.cc b/paddle/fluid/operators/yolov3_loss_op.cc index cf25e995054dce16e6f95540be385c0a09ef9ee2..f6c134e1b4df89522695593d428c008b1d8bc348 100644 --- a/paddle/fluid/operators/yolov3_loss_op.cc +++ b/paddle/fluid/operators/yolov3_loss_op.cc @@ -55,7 +55,8 @@ class Yolov3LossOp : public framework::OperatorWithKernel { framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( - framework::ToDataType(ctx.Input("X")->type()), ctx.GetPlace()); + framework::ToDataType(ctx.Input("X")->type()), + platform::CPUPlace()); } }; @@ -63,8 +64,11 @@ class Yolov3LossOpMaker : public framework::OpProtoAndCheckerMaker { public: void Make() override { AddInput("X", - "The input tensor of bilinear interpolation, " - "This is a 4-D tensor with shape of [N, C, H, W]"); + "The input tensor of YOLO v3 loss operator, " + "This is a 4-D tensor with shape of [N, C, H, W]." + "H and W should be same, and the second dimention(C) stores" + "box locations, confidence score and classification one-hot" + "key of each anchor box"); AddInput("GTBox", "The input tensor of ground truth boxes, " "This is a 3-D tensor with shape of [N, max_box_num, 5], " @@ -84,6 +88,20 @@ class Yolov3LossOpMaker : public framework::OpProtoAndCheckerMaker { "it will be parsed pair by pair."); AddAttr("ignore_thresh", "The ignore threshold to ignore confidence loss."); + AddAttr("lambda_xy", "The weight of x, y location loss.") + .SetDefault(1.0); + AddAttr("lambda_wh", "The weight of w, h location loss.") + .SetDefault(1.0); + AddAttr( + "lambda_conf_obj", + "The weight of confidence score loss in locations with target object.") + .SetDefault(1.0); + AddAttr("lambda_conf_noobj", + "The weight of confidence score loss in locations without " + "target object.") + .SetDefault(1.0); + AddAttr("lambda_class", "The weight of classification loss.") + .SetDefault(1.0); AddComment(R"DOC( This operator generate yolov3 loss by given predict result and ground truth boxes. @@ -119,6 +137,15 @@ class Yolov3LossOpMaker : public framework::OpProtoAndCheckerMaker { confidence score loss, and classification loss. The MSE loss is used for box location, and binary cross entropy loss is used for confidence score loss and classification loss. + + Final loss will be represented as follow. + + $$ + loss = \lambda_{xy} * loss_{xy} + \lambda_{wh} * loss_{wh} + + \lambda_{conf_obj} * loss_{conf_obj} + + \lambda_{conf_noobj} * loss_{conf_noobj} + + \lambda_{class} * loss_{class} + $$ )DOC"); } }; @@ -140,7 +167,8 @@ class Yolov3LossOpGrad : public framework::OperatorWithKernel { framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( - framework::ToDataType(ctx.Input("X")->type()), ctx.GetPlace()); + framework::ToDataType(ctx.Input("X")->type()), + platform::CPUPlace()); } }; diff --git a/paddle/fluid/operators/yolov3_loss_op.cu b/paddle/fluid/operators/yolov3_loss_op.cu deleted file mode 100644 index f901b10d38e486be874f75f39ef4afb4cb6a560a..0000000000000000000000000000000000000000 --- a/paddle/fluid/operators/yolov3_loss_op.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - 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. */ - -#define EIGEN_USE_GPU - -#include "paddle/fluid/operators/yolov3_loss_op.h" -#include "paddle/fluid/platform/cuda_primitives.h" - -namespace ops = paddle::operators; -REGISTER_OP_CUDA_KERNEL( - yolov3_loss, - ops::Yolov3LossKernel); -REGISTER_OP_CUDA_KERNEL( - yolov3_loss_grad, - ops::Yolov3LossGradKernel); diff --git a/paddle/fluid/operators/yolov3_loss_op.h b/paddle/fluid/operators/yolov3_loss_op.h index a2ed4440a74fe3dc48d8b82f5dbb4aa65445b0ae..f4ede9258977ba0b4981c71eaeefa9f4f35d1bbd 100644 --- a/paddle/fluid/operators/yolov3_loss_op.h +++ b/paddle/fluid/operators/yolov3_loss_op.h @@ -267,7 +267,9 @@ static void AddAllGradToInputGrad( const Tensor& pred_conf, const Tensor& pred_class, const Tensor& grad_x, const Tensor& grad_y, const Tensor& grad_w, const Tensor& grad_h, const Tensor& grad_conf_obj, const Tensor& grad_conf_noobj, - const Tensor& grad_class, const int class_num) { + const Tensor& grad_class, const int class_num, const float lambda_xy, + const float lambda_wh, const float lambda_conf_obj, + const float lambda_conf_noobj, const float lambda_class) { const int n = pred_x.dims()[0]; const int an_num = pred_x.dims()[1]; const int h = pred_x.dims()[2]; @@ -290,25 +292,27 @@ static void AddAllGradToInputGrad( for (int j = 0; j < an_num; j++) { for (int k = 0; k < h; k++) { for (int l = 0; l < w; l++) { - grad_t(i, j * attr_num, k, l) = grad_x_t(i, j, k, l) * - pred_x_t(i, j, k, l) * - (1.0 - pred_x_t(i, j, k, l)) * loss; + grad_t(i, j * attr_num, k, l) = + grad_x_t(i, j, k, l) * pred_x_t(i, j, k, l) * + (1.0 - pred_x_t(i, j, k, l)) * loss * lambda_xy; grad_t(i, j * attr_num + 1, k, l) = grad_y_t(i, j, k, l) * pred_y_t(i, j, k, l) * - (1.0 - pred_y_t(i, j, k, l)) * loss; - grad_t(i, j * attr_num + 2, k, l) = grad_w_t(i, j, k, l) * loss; - grad_t(i, j * attr_num + 3, k, l) = grad_h_t(i, j, k, l) * loss; + (1.0 - pred_y_t(i, j, k, l)) * loss * lambda_xy; + grad_t(i, j * attr_num + 2, k, l) = + grad_w_t(i, j, k, l) * loss * lambda_wh; + grad_t(i, j * attr_num + 3, k, l) = + grad_h_t(i, j, k, l) * loss * lambda_wh; grad_t(i, j * attr_num + 4, k, l) = grad_conf_obj_t(i, j, k, l) * pred_conf_t(i, j, k, l) * - (1.0 - pred_conf_t(i, j, k, l)) * loss; + (1.0 - pred_conf_t(i, j, k, l)) * loss * lambda_conf_obj; grad_t(i, j * attr_num + 4, k, l) += grad_conf_noobj_t(i, j, k, l) * pred_conf_t(i, j, k, l) * - (1.0 - pred_conf_t(i, j, k, l)) * loss; + (1.0 - pred_conf_t(i, j, k, l)) * loss * lambda_conf_noobj; for (int c = 0; c < class_num; c++) { grad_t(i, j * attr_num + 5 + c, k, l) = grad_class_t(i, j, k, l, c) * pred_class_t(i, j, k, l, c) * - (1.0 - pred_class_t(i, j, k, l, c)) * loss; + (1.0 - pred_class_t(i, j, k, l, c)) * loss * lambda_class; } } } @@ -326,6 +330,11 @@ class Yolov3LossKernel : public framework::OpKernel { auto anchors = ctx.Attr>("anchors"); int class_num = ctx.Attr("class_num"); float ignore_thresh = ctx.Attr("ignore_thresh"); + float lambda_xy = ctx.Attr("lambda_xy"); + float lambda_wh = ctx.Attr("lambda_wh"); + float lambda_conf_obj = ctx.Attr("lambda_conf_obj"); + float lambda_conf_noobj = ctx.Attr("lambda_conf_noobj"); + float lambda_class = ctx.Attr("lambda_class"); const int n = input->dims()[0]; const int h = input->dims()[2]; @@ -370,8 +379,10 @@ class Yolov3LossKernel : public framework::OpKernel { T loss_class = CalcBCEWithMask(pred_class, tclass, obj_mask_expand); auto* loss_data = loss->mutable_data({1}, ctx.GetPlace()); - loss_data[0] = loss_x + loss_y + loss_w + loss_h + loss_conf_obj + - loss_conf_noobj + loss_class; + loss_data[0] = + lambda_xy * (loss_x + loss_y) + lambda_wh * (loss_w + loss_h) + + lambda_conf_obj * loss_conf_obj + lambda_conf_noobj * loss_conf_noobj + + lambda_class * loss_class; } }; @@ -387,6 +398,11 @@ class Yolov3LossGradKernel : public framework::OpKernel { auto* input_grad = ctx.Output(framework::GradVarName("X")); auto* output_grad = ctx.Input(framework::GradVarName("Loss")); const T loss = output_grad->data()[0]; + float lambda_xy = ctx.Attr("lambda_xy"); + float lambda_wh = ctx.Attr("lambda_wh"); + float lambda_conf_obj = ctx.Attr("lambda_conf_obj"); + float lambda_conf_noobj = ctx.Attr("lambda_conf_noobj"); + float lambda_class = ctx.Attr("lambda_class"); const int n = input->dims()[0]; const int c = input->dims()[1]; @@ -448,7 +464,8 @@ class Yolov3LossGradKernel : public framework::OpKernel { input_grad->mutable_data({n, c, h, w}, ctx.GetPlace()); AddAllGradToInputGrad( input_grad, loss, pred_x, pred_y, pred_conf, pred_class, grad_x, grad_y, - grad_w, grad_h, grad_conf_obj, grad_conf_noobj, grad_class, class_num); + grad_w, grad_h, grad_conf_obj, grad_conf_noobj, grad_class, class_num, + lambda_xy, lambda_wh, lambda_conf_obj, lambda_conf_noobj, lambda_class); } }; diff --git a/python/paddle/fluid/layers/detection.py b/python/paddle/fluid/layers/detection.py index 4ac94981a7a47530fe6ae4d968212c62dd3e0a93..2bb9514803e97836ce908b597d857f4733a7ff88 100644 --- a/python/paddle/fluid/layers/detection.py +++ b/python/paddle/fluid/layers/detection.py @@ -20,6 +20,7 @@ from __future__ import print_function from .layer_function_generator import generate_layer_fn from .layer_function_generator import autodoc, templatedoc from ..layer_helper import LayerHelper +from ..framework import Variable from . import tensor from . import nn from . import ops @@ -45,6 +46,7 @@ __all__ = [ 'iou_similarity', 'box_coder', 'polygon_box_transform', + 'yolov3_loss', ] @@ -404,6 +406,102 @@ def polygon_box_transform(input, name=None): return output +@templatedoc(op_type="yolov3_loss") +def yolov3_loss(x, + gtbox, + anchors, + class_num, + ignore_thresh, + lambda_xy=None, + lambda_wh=None, + lambda_conf_obj=None, + lambda_conf_noobj=None, + lambda_class=None, + name=None): + """ + ${comment} + + Args: + x (Variable): ${x_comment} + gtbox (Variable): groud truth boxes, shoulb be in shape of [N, B, 5], + in the third dimenstion, class_id, x, y, w, h should + be stored and x, y, w, h should be relative valud of + input image. + anchors (list|tuple): ${anchors_comment} + class_num (int): ${class_num_comment} + ignore_thresh (float): ${ignore_thresh_comment} + lambda_xy (float|None): ${lambda_xy_comment} + lambda_wh (float|None): ${lambda_wh_comment} + lambda_conf_obj (float|None): ${lambda_conf_obj_comment} + lambda_conf_noobj (float|None): ${lambda_conf_noobj_comment} + lambda_class (float|None): ${lambda_class_comment} + name (string): the name of yolov3 loss + + Returns: + Variable: A 1-D tensor with shape [1], the value of yolov3 loss + + Raises: + TypeError: Input x of yolov3_loss must be Variable + TypeError: Input gtbox of yolov3_loss must be Variable" + TypeError: Attr anchors of yolov3_loss must be list or tuple + TypeError: Attr class_num of yolov3_loss must be an integer + TypeError: Attr ignore_thresh of yolov3_loss must be a float number + + Examples: + .. code-block:: python + + x = fluid.layers.data(name='x', shape=[10, 255, 13, 13], dtype='float32') + gtbox = fluid.layers.data(name='gtbox', shape=[10, 6, 5], dtype='float32') + anchors = [10, 13, 16, 30, 33, 23] + loss = fluid.layers.yolov3_loss(x=x, gtbox=gtbox, class_num=80 + anchors=anchors, ignore_thresh=0.5) + """ + helper = LayerHelper('yolov3_loss', **locals()) + + if not isinstance(x, Variable): + raise TypeError("Input x of yolov3_loss must be Variable") + if not isinstance(gtbox, Variable): + raise TypeError("Input gtbox of yolov3_loss must be Variable") + if not isinstance(anchors, list) and not isinstance(anchors, tuple): + raise TypeError("Attr anchors of yolov3_loss must be list or tuple") + if not isinstance(class_num, int): + raise TypeError("Attr class_num of yolov3_loss must be an integer") + if not isinstance(ignore_thresh, float): + raise TypeError( + "Attr ignore_thresh of yolov3_loss must be a float number") + + if name is None: + loss = helper.create_variable_for_type_inference(dtype=x.dtype) + else: + loss = helper.create_variable( + name=name, dtype=x.dtype, persistable=False) + + attrs = { + "anchors": anchors, + "class_num": class_num, + "ignore_thresh": ignore_thresh, + } + + if lambda_xy is not None and isinstance(lambda_xy, float): + self.attrs['lambda_xy'] = lambda_xy + if lambda_wh is not None and isinstance(lambda_wh, float): + self.attrs['lambda_wh'] = lambda_wh + if lambda_conf_obj is not None and isinstance(lambda_conf_obj, float): + self.attrs['lambda_conf_obj'] = lambda_conf_obj + if lambda_conf_noobj is not None and isinstance(lambda_conf_noobj, float): + self.attrs['lambda_conf_noobj'] = lambda_conf_noobj + if lambda_class is not None and isinstance(lambda_class, float): + self.attrs['lambda_class'] = lambda_class + + helper.append_op( + type='yolov3_loss', + inputs={'X': x, + "GTBox": gtbox}, + outputs={'Loss': loss}, + attrs=attrs) + return loss + + @templatedoc() def detection_map(detect_res, label, diff --git a/python/paddle/fluid/layers/nn.py b/python/paddle/fluid/layers/nn.py index a4efb166826d53e7e4d0a39173ff07d5ae9fcf8d..d3623464e9974246708206d2204ed52ae74e2d0f 100644 --- a/python/paddle/fluid/layers/nn.py +++ b/python/paddle/fluid/layers/nn.py @@ -164,7 +164,6 @@ __all__ = [ 'hash', 'grid_sampler', 'log_loss', - 'yolov3_loss', 'add_position_encoding', 'bilinear_tensor_product', ] @@ -8244,74 +8243,6 @@ def log_loss(input, label, epsilon=1e-4, name=None): return loss -@templatedoc(op_type="yolov3_loss") -def yolov3_loss(x, gtbox, anchors, class_num, ignore_thresh, name=None): - """ - ${comment} - - Args: - x (Variable): ${x_comment} - gtbox (Variable): groud truth boxes, shoulb be in shape of [N, B, 5], - in the third dimenstion, class_id, x, y, w, h should - be stored and x, y, w, h should be relative valud of - input image. - anchors (list|tuple): ${anchors_comment} - class_num (int): ${class_num_comment} - ignore_thresh (float): ${ignore_thresh_comment} - name (string): the name of yolov3 loss - - Returns: - Variable: A 1-D tensor with shape [1], the value of yolov3 loss - - Raises: - TypeError: Input x of yolov3_loss must be Variable - TypeError: Input gtbox of yolov3_loss must be Variable" - TypeError: Attr anchors of yolov3_loss must be list or tuple - TypeError: Attr class_num of yolov3_loss must be an integer - TypeError: Attr ignore_thresh of yolov3_loss must be a float number - - Examples: - .. code-block:: python - - x = fluid.layers.data(name='x', shape=[10, 255, 13, 13], dtype='float32') - gtbox = fluid.layers.data(name='gtbox', shape=[10, 6, 5], dtype='float32') - anchors = [10, 13, 16, 30, 33, 23] - loss = fluid.layers.yolov3_loss(x=x, gtbox=gtbox, class_num=80 - anchors=anchors, ignore_thresh=0.5) - """ - helper = LayerHelper('yolov3_loss', **locals()) - - if not isinstance(x, Variable): - raise TypeError("Input x of yolov3_loss must be Variable") - if not isinstance(gtbox, Variable): - raise TypeError("Input gtbox of yolov3_loss must be Variable") - if not isinstance(anchors, list) and not isinstance(anchors, tuple): - raise TypeError("Attr anchors of yolov3_loss must be list or tuple") - if not isinstance(class_num, int): - raise TypeError("Attr class_num of yolov3_loss must be an integer") - if not isinstance(ignore_thresh, float): - raise TypeError( - "Attr ignore_thresh of yolov3_loss must be a float number") - - if name is None: - loss = helper.create_variable_for_type_inference(dtype=x.dtype) - else: - loss = helper.create_variable( - name=name, dtype=x.dtype, persistable=False) - - helper.append_op( - type='yolov3_loss', - inputs={'X': x, - "GTBox": gtbox}, - outputs={'Loss': loss}, - attrs={ - "anchors": anchors, - "class_num": class_num, - "ignore_thresh": ignore_thresh, - }) - return loss - - def add_position_encoding(input, alpha, beta, name=None): """ **Add Position Encoding Layer** diff --git a/python/paddle/fluid/tests/unittests/test_yolov3_loss_op.py b/python/paddle/fluid/tests/unittests/test_yolov3_loss_op.py index 4562f8bd4962e1ab3d3fb30dc7125a1bec66ac12..3b6d58563f4b0041bd96ea35bc3ddd9d966c710d 100644 --- a/python/paddle/fluid/tests/unittests/test_yolov3_loss_op.py +++ b/python/paddle/fluid/tests/unittests/test_yolov3_loss_op.py @@ -148,11 +148,20 @@ def YoloV3Loss(x, gtbox, attrs): loss_class = bce(pred_cls * obj_mask_expand, tcls * obj_mask_expand, obj_mask_expand) - return loss_x + loss_y + loss_w + loss_h + loss_conf_obj + loss_conf_noobj + loss_class + return attrs['lambda_xy'] * (loss_x + loss_y) \ + + attrs['lambda_wh'] * (loss_w + loss_h) \ + + attrs['lambda_conf_obj'] * loss_conf_obj \ + + attrs['lambda_conf_noobj'] * loss_conf_noobj \ + + attrs['lambda_class'] * loss_class class TestYolov3LossOp(OpTest): def setUp(self): + self.lambda_xy = 1.0 + self.lambda_wh = 1.0 + self.lambda_conf_obj = 1.0 + self.lambda_conf_noobj = 1.0 + self.lambda_class = 1.0 self.initTestCase() self.op_type = 'yolov3_loss' x = np.random.random(size=self.x_shape).astype('float32') @@ -164,6 +173,11 @@ class TestYolov3LossOp(OpTest): "anchors": self.anchors, "class_num": self.class_num, "ignore_thresh": self.ignore_thresh, + "lambda_xy": self.lambda_xy, + "lambda_wh": self.lambda_wh, + "lambda_conf_obj": self.lambda_conf_obj, + "lambda_conf_noobj": self.lambda_conf_noobj, + "lambda_class": self.lambda_class, } self.inputs = {'X': x, 'GTBox': gtbox} @@ -182,7 +196,7 @@ class TestYolov3LossOp(OpTest): place, ['X'], 'Loss', no_grad_set=set("GTBox"), - max_relative_error=0.1) + max_relative_error=0.06) def initTestCase(self): self.anchors = [10, 13, 12, 12] @@ -190,6 +204,11 @@ class TestYolov3LossOp(OpTest): self.ignore_thresh = 0.5 self.x_shape = (5, len(self.anchors) // 2 * (5 + self.class_num), 7, 7) self.gtbox_shape = (5, 5, 5) + self.lambda_xy = 2.5 + self.lambda_wh = 0.8 + self.lambda_conf_obj = 1.5 + self.lambda_conf_noobj = 0.5 + self.lambda_class = 1.2 if __name__ == "__main__":