117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
#copyright (c) 2019 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.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import numpy as np
|
|
|
|
import paddle.fluid as fluid
|
|
|
|
|
|
def BalanceLoss(pred,
|
|
gt,
|
|
mask,
|
|
balance_loss=True,
|
|
main_loss_type="DiceLoss",
|
|
negative_ratio=3,
|
|
return_origin=False,
|
|
eps=1e-6):
|
|
"""
|
|
The BalanceLoss for Differentiable Binarization text detection
|
|
args:
|
|
pred (variable): predicted feature maps.
|
|
gt (variable): ground truth feature maps.
|
|
mask (variable): masked maps.
|
|
balance_loss (bool): whether balance loss or not, default is True
|
|
main_loss_type (str): can only be one of ['CrossEntropy','DiceLoss',
|
|
'Euclidean','BCELoss', 'MaskL1Loss'], default is 'DiceLoss'.
|
|
negative_ratio (int|float): float, default is 3.
|
|
return_origin (bool): whether return unbalanced loss or not, default is False.
|
|
eps (float): default is 1e-6.
|
|
return: (variable) balanced loss
|
|
"""
|
|
positive = gt * mask
|
|
negative = (1 - gt) * mask
|
|
|
|
positive_count = fluid.layers.reduce_sum(positive)
|
|
positive_count_int = fluid.layers.cast(positive_count, dtype=np.int32)
|
|
negative_count = min(
|
|
fluid.layers.reduce_sum(negative), positive_count * negative_ratio)
|
|
negative_count_int = fluid.layers.cast(negative_count, dtype=np.int32)
|
|
|
|
if main_loss_type == "CrossEntropy":
|
|
loss = fluid.layers.cross_entropy(input=pred, label=gt, soft_label=True)
|
|
loss = fluid.layers.reduce_mean(loss)
|
|
elif main_loss_type == "Euclidean":
|
|
loss = fluid.layers.square(pred - gt)
|
|
loss = fluid.layers.reduce_mean(loss)
|
|
elif main_loss_type == "DiceLoss":
|
|
loss = DiceLoss(pred, gt, mask)
|
|
elif main_loss_type == "BCELoss":
|
|
loss = fluid.layers.sigmoid_cross_entropy_with_logits(pred, label=gt)
|
|
elif main_loss_type == "MaskL1Loss":
|
|
loss = MaskL1Loss(pred, gt, mask)
|
|
else:
|
|
loss_type = [
|
|
'CrossEntropy', 'DiceLoss', 'Euclidean', 'BCELoss', 'MaskL1Loss'
|
|
]
|
|
raise Exception("main_loss_type in BalanceLoss() can only be one of {}".
|
|
format(loss_type))
|
|
|
|
if not balance_loss:
|
|
return loss
|
|
|
|
positive_loss = positive * loss
|
|
negative_loss = negative * loss
|
|
negative_loss = fluid.layers.reshape(negative_loss, shape=[-1])
|
|
negative_loss, _ = fluid.layers.topk(negative_loss, k=negative_count_int)
|
|
balance_loss = (fluid.layers.reduce_sum(positive_loss) +
|
|
fluid.layers.reduce_sum(negative_loss)) / (
|
|
positive_count + negative_count + eps)
|
|
|
|
if return_origin:
|
|
return balance_loss, loss
|
|
return balance_loss
|
|
|
|
|
|
def DiceLoss(pred, gt, mask, weights=None, eps=1e-6):
|
|
"""
|
|
DiceLoss function.
|
|
"""
|
|
|
|
assert pred.shape == gt.shape
|
|
assert pred.shape == mask.shape
|
|
if weights is not None:
|
|
assert weights.shape == mask.shape
|
|
mask = weights * mask
|
|
intersection = fluid.layers.reduce_sum(pred * gt * mask)
|
|
|
|
union = fluid.layers.reduce_sum(pred * mask) + fluid.layers.reduce_sum(
|
|
gt * mask) + eps
|
|
loss = 1 - 2.0 * intersection / union
|
|
assert loss <= 1
|
|
return loss
|
|
|
|
|
|
def MaskL1Loss(pred, gt, mask, eps=1e-6):
|
|
"""
|
|
Mask L1 Loss
|
|
"""
|
|
loss = fluid.layers.reduce_sum((fluid.layers.abs(pred - gt) * mask)) / (
|
|
fluid.layers.reduce_sum(mask) + eps)
|
|
loss = fluid.layers.reduce_mean(loss)
|
|
return loss
|