Spaces:
Configuration error
Configuration error
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. | |
# | |
# 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. | |
import paddle | |
import paddle.nn as nn | |
import paddle.nn.functional as F | |
from paddleseg.cvlibs import manager | |
class BCELoss(nn.Layer): | |
r""" | |
This operator combines the sigmoid layer and the :ref:`api_nn_loss_BCELoss` layer. | |
Also, we can see it as the combine of ``sigmoid_cross_entropy_with_logits`` | |
layer and some reduce operations. | |
This measures the element-wise probability error in classification tasks | |
in which each class is independent. | |
This can be thought of as predicting labels for a data-point, where labels | |
are not mutually exclusive. For example, a news article can be about | |
politics, technology or sports at the same time or none of these. | |
First this operator calculate loss function as follows: | |
.. math:: | |
Out = -Labels * \\log(\\sigma(Logit)) - (1 - Labels) * \\log(1 - \\sigma(Logit)) | |
We know that :math:`\\sigma(Logit) = \\frac{1}{1 + \\e^{-Logit}}`. By substituting this we get: | |
.. math:: | |
Out = Logit - Logit * Labels + \\log(1 + \\e^{-Logit}) | |
For stability and to prevent overflow of :math:`\\e^{-Logit}` when Logit < 0, | |
we reformulate the loss as follows: | |
.. math:: | |
Out = \\max(Logit, 0) - Logit * Labels + \\log(1 + \\e^{-\|Logit\|}) | |
Then, if ``weight`` or ``pos_weight`` is not None, this operator multiply the | |
weight tensor on the loss `Out`. The ``weight`` tensor will attach different | |
weight on every items in the batch. The ``pos_weight`` will attach different | |
weight on the positive label of each class. | |
Finally, this operator applies reduce operation on the loss. | |
If :attr:`reduction` set to ``'none'``, the operator will return the original loss `Out`. | |
If :attr:`reduction` set to ``'mean'``, the reduced mean loss is :math:`Out = MEAN(Out)`. | |
If :attr:`reduction` set to ``'sum'``, the reduced sum loss is :math:`Out = SUM(Out)`. | |
Note that the target labels ``label`` should be numbers between 0 and 1. | |
Args: | |
weight (Tensor | str, optional): A manual rescaling weight given to the loss of each | |
batch element. If given, it has to be a 1D Tensor whose size is `[N, ]`, | |
The data type is float32, float64. If type is str, it should equal to 'dynamic'. | |
It will compute weight dynamically in every step. | |
Default is ``'None'``. | |
pos_weight (float|str, optional): A weight of positive examples. If type is str, | |
it should equal to 'dynamic'. It will compute weight dynamically in every step. | |
Default is ``'None'``. | |
ignore_index (int64, optional): Specifies a target value that is ignored | |
and does not contribute to the input gradient. Default ``255``. | |
edge_label (bool, optional): Whether to use edge label. Default: False | |
Shapes: | |
logit (Tensor): The input predications tensor. 2-D tensor with shape: [N, *], | |
N is batch_size, `*` means number of additional dimensions. The ``logit`` | |
is usually the output of Linear layer. Available dtype is float32, float64. | |
label (Tensor): The target labels tensor. 2-D tensor with the same shape as | |
``logit``. The target labels which values should be numbers between 0 and 1. | |
Available dtype is float32, float64. | |
Returns: | |
A callable object of BCEWithLogitsLoss. | |
Examples: | |
.. code-block:: python | |
import paddle | |
paddle.disable_static() | |
logit = paddle.to_tensor([5.0, 1.0, 3.0], dtype="float32") | |
label = paddle.to_tensor([1.0, 0.0, 1.0], dtype="float32") | |
bce_logit_loss = paddle.nn.BCEWithLogitsLoss() | |
output = bce_logit_loss(logit, label) | |
print(output.numpy()) # [0.45618808] | |
""" | |
def __init__(self, | |
weight=None, | |
pos_weight=None, | |
ignore_index=255, | |
edge_label=False): | |
super().__init__() | |
self.weight = weight | |
self.pos_weight = pos_weight | |
self.ignore_index = ignore_index | |
self.edge_label = edge_label | |
self.EPS = 1e-10 | |
if self.weight is not None: | |
if isinstance(self.weight, str): | |
if self.weight != 'dynamic': | |
raise ValueError( | |
"if type of `weight` is str, it should equal to 'dynamic', but it is {}" | |
.format(self.weight)) | |
elif not isinstance(self.weight, paddle.Tensor): | |
raise TypeError( | |
'The type of `weight` is wrong, it should be Tensor or str, but it is {}' | |
.format(type(self.weight))) | |
if self.pos_weight is not None: | |
if isinstance(self.pos_weight, str): | |
if self.pos_weight != 'dynamic': | |
raise ValueError( | |
"if type of `pos_weight` is str, it should equal to 'dynamic', but it is {}" | |
.format(self.pos_weight)) | |
elif isinstance(self.pos_weight, float): | |
self.pos_weight = paddle.to_tensor( | |
self.pos_weight, dtype='float32') | |
else: | |
raise TypeError( | |
'The type of `pos_weight` is wrong, it should be float or str, but it is {}' | |
.format(type(self.pos_weight))) | |
def forward(self, logit, label): | |
""" | |
Forward computation. | |
Args: | |
logit (Tensor): Logit tensor, the data type is float32, float64. Shape is | |
(N, C), where C is number of classes, and if shape is more than 2D, this | |
is (N, C, D1, D2,..., Dk), k >= 1. | |
label (Tensor): Label tensor, the data type is int64. Shape is (N, C), where each | |
value is 0 or 1, and if shape is more than 2D, this is | |
(N, C, D1, D2,..., Dk), k >= 1. | |
""" | |
if len(label.shape) != len(logit.shape): | |
label = paddle.unsqueeze(label, 1) | |
mask = (label != self.ignore_index) | |
mask = paddle.cast(mask, 'float32') | |
# label.shape should equal to the logit.shape | |
if label.shape[1] != logit.shape[1]: | |
label = label.squeeze(1) | |
label = F.one_hot(label, logit.shape[1]) | |
label = label.transpose((0, 3, 1, 2)) | |
if isinstance(self.weight, str): | |
pos_index = (label == 1) | |
neg_index = (label == 0) | |
pos_num = paddle.sum(pos_index.astype('float32')) | |
neg_num = paddle.sum(neg_index.astype('float32')) | |
sum_num = pos_num + neg_num | |
weight_pos = 2 * neg_num / (sum_num + self.EPS) | |
weight_neg = 2 * pos_num / (sum_num + self.EPS) | |
weight = weight_pos * label + weight_neg * (1 - label) | |
else: | |
weight = self.weight | |
if isinstance(self.pos_weight, str): | |
pos_index = (label == 1) | |
neg_index = (label == 0) | |
pos_num = paddle.sum(pos_index.astype('float32')) | |
neg_num = paddle.sum(neg_index.astype('float32')) | |
sum_num = pos_num + neg_num | |
pos_weight = 2 * neg_num / (sum_num + self.EPS) | |
else: | |
pos_weight = self.pos_weight | |
label = label.astype('float32') | |
loss = paddle.nn.functional.binary_cross_entropy_with_logits( | |
logit, | |
label, | |
weight=weight, | |
reduction='none', | |
pos_weight=pos_weight) | |
loss = loss * mask | |
loss = paddle.mean(loss) / (paddle.mean(mask) + self.EPS) | |
label.stop_gradient = True | |
mask.stop_gradient = True | |
return loss | |