Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yejiaojiao/dev bcewithlogitsloss #5173

Merged
merged 35 commits into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3d494fd
first commit of op floor
JiaojiaoYe1994 May 25, 2021
5c47937
first commit of op acos
JiaojiaoYe1994 May 28, 2021
e18f95f
split acos from math_ops and add doctest
JiaojiaoYe1994 Jun 1, 2021
733ea0d
resolve conflicting files
JiaojiaoYe1994 Jun 2, 2021
ea6ca67
Merge branch 'master' into yejiaojiao/dev_acos
JiaojiaoYe1994 Jun 2, 2021
e5ae802
fix format bug
JiaojiaoYe1994 Jun 3, 2021
35208bf
implement BCEWithLogitsLoss Module and doctest
JiaojiaoYe1994 Jun 8, 2021
ad03594
fix bug in forward
JiaojiaoYe1994 Jun 9, 2021
dccea87
add test_bcewithlogitsloss
JiaojiaoYe1994 Jun 9, 2021
1b30011
add test_bcewithlogitsloss fix bugs
JiaojiaoYe1994 Jun 10, 2021
d278d21
modify doctest
JiaojiaoYe1994 Jun 11, 2021
d804378
modify doctest
JiaojiaoYe1994 Jun 11, 2021
62c333a
add reduce and size_average arguments
JiaojiaoYe1994 Jun 11, 2021
0c03808
rebuild test case and modify bugs in docstring
JiaojiaoYe1994 Jun 15, 2021
ab17028
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 15, 2021
0e28810
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 16, 2021
61c2ab4
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 16, 2021
3528f86
Merge branch 'yejiaojiao/dev_bcewithlogitsloss' of https://github.com…
JiaojiaoYe1994 Jun 16, 2021
f727c92
solve conficts
JiaojiaoYe1994 Jun 16, 2021
90b55cc
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 16, 2021
b83aab2
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 16, 2021
b1c38ec
auto format by CI
oneflow-ci-bot Jun 16, 2021
30021f4
rectify args explanation in docstring
JiaojiaoYe1994 Jun 16, 2021
77e5d0b
Merge branch 'yejiaojiao/dev_bcewithlogitsloss' of https://github.com…
JiaojiaoYe1994 Jun 16, 2021
396ba7a
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 16, 2021
13ff1ca
remove redundant check of pos_weight length
JiaojiaoYe1994 Jun 16, 2021
5a76f2a
Merge branch 'yejiaojiao/dev_bcewithlogitsloss' of https://github.com…
JiaojiaoYe1994 Jun 16, 2021
6643050
resolve conflicts
JiaojiaoYe1994 Jun 17, 2021
8f2bd1a
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 17, 2021
cf66ad8
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 17, 2021
c455d40
auto format by CI
oneflow-ci-bot Jun 17, 2021
001d782
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
JiaojiaoYe1994 Jun 17, 2021
2bdbd62
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 17, 2021
3566325
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 17, 2021
c7f0c51
Merge branch 'master' into yejiaojiao/dev_bcewithlogitsloss
oneflow-ci-bot Jun 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/experimental.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Experimental features
.. autofunction:: oneflow.experimental.nn.KLDivLoss
.. autofunction:: oneflow.experimental.nn.MSELoss
.. autofunction:: oneflow.experimental.nn.MarginRankingLoss
.. autofunction:: oneflow.experimental.nn.BCEWithLogitsLoss
.. autofunction:: oneflow.experimental.masked_fill
.. autofunction:: oneflow.experimental.Tensor.masked_fill
.. autofunction:: oneflow.experimental.sum
Expand Down
154 changes: 151 additions & 3 deletions oneflow/python/nn/modules/loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,8 @@ class MarginRankingLoss(Module):

For example:

.. code-block:: python
.. code-block:: python

>>> import oneflow.experimental as flow
>>> flow.enable_eager_execution()
>>> import numpy as np
Expand All @@ -565,7 +565,7 @@ class MarginRankingLoss(Module):
>>> out = m(x1, x2, target)
>>> out
tensor([8.2], dtype=oneflow.float32)

>>> m = flow.nn.MarginRankingLoss(margin = 10, reduction="mean")
>>> out = m(x1, x2, target)
>>> out
Expand Down Expand Up @@ -606,6 +606,154 @@ def forward(self, input1, input2, target):
return res.mean()


@oneflow_export("nn.BCEWithLogitsLoss")
@experimental_api
class BCEWithLogitsLoss(Module):
r"""This operator combines the `Sigmoid` and `BCELoss` together. For numerical stability,
we apply some math tricks instead of using `Sigmoid` layer with `BCELoss`.

The equation is:

if :attr:`reduction` = ``"none"``:

.. math::

out = -weight*[Pos\_weight*y*log\sigma({x}) + (1-y)*log(1-\sigma(x))]

if :attr:`reduction` = ``"mean"``:

.. math::

out = -\frac{weight}{n}\sum_{i=1}^n[Pos\_weight*y*log\sigma({x}) + (1-y)*log(1-\sigma(x))]

if :attr:`reduction` = ``"sum"``:

.. math::

out =k -weight*\sum_{i=1}^n[Pos\_weight*y*log\sigma({x}) + (1-y)*log(1-\sigma(x))]

Args:
weight (remote_blob_util, optional): The manual rescaling weight to the loss. Default: ``None``
JiaojiaoYe1994 marked this conversation as resolved.
Show resolved Hide resolved
size_average (bool, optional) – Deprecated (see :attr:`reduction`). Default: ``True``
reduce (bool, optional) – Deprecated (see :attr:`reduction`). Default: ``True``
reduction (str, optional): The reduce type, it can be one of ``"none"``, ``"mean"``, ``"sum"``.
``'none'``: no reduction will be applied, ``'mean'``: the sum of the output will be divided
by the number of elements in the output, ``'sum'``: the output will be summed. Default: ``"mean"``
pos_weight (remote_blob_util, optional): The manual rescaling weight to the positive examples.
Default: ``None``

Shape:
- Input: :math:`(N,*)` where `*` means, any number of additional dimensions
- Target: :math:`(N,*)`, same shape as the input
- Output: scalar. If :attr:`reduction` is ``"none"``, then :math:`(N,*)`, same shape as input.

For example:

.. code-block:: python

>>> import oneflow.experimental as flow
>>> flow.enable_eager_execution()
>>> import oneflow.typing as tp

>>> input = flow.Tensor([[1.2, 0.2, -0.3], [0.7, 0.6, -2], [0.7, 0.6, -2]], dtype=flow.float32)
>>> target = flow.Tensor([[0, 1, 0], [1, 0, 1], [1, 0, 1]], dtype=flow.float32)
>>> weight = flow.Tensor([[2, 2, 2], [2, 2, 2], [2, 2, 2]], dtype=flow.float32)
>>> pos_weight = flow.Tensor([1.2, 1.3, 1.4], dtype=flow.float32)

>>> m = flow.nn.BCEWithLogitsLoss(weight=weight, pos_weight=pos_weight, reduction="none")
>>> out = m(input, target)
>>> out
tensor([[2.9266, 1.5552, 1.1087],
[0.9676, 2.075 , 5.9554],
[0.9676, 2.075 , 5.9554]], dtype=oneflow.float32)

>>> m = flow.nn.BCEWithLogitsLoss(weight=weight, pos_weight=pos_weight, reduction="mean")
>>> out = m(input, target)
>>> out
tensor([2.6207], dtype=oneflow.float32)

>>> m = flow.nn.BCEWithLogitsLoss(weight=weight, pos_weight=pos_weight, reduction="sum")
>>> out = m(input, target)
>>> out
tensor([23.5865], dtype=oneflow.float32)


"""

def __init__(
self,
weight = None,
size_average: bool = True,
reduce: bool = True,
reduction: Optional[str] = "mean",
pos_weight = None,
) -> None:
super().__init__()
assert reduction in [
"sum",
"none",
"mean",
None,
], "only 'sum', 'mean' and None supported by now"
JiaojiaoYe1994 marked this conversation as resolved.
Show resolved Hide resolved

self.weight = weight
self.size_average = size_average
self.reduce = reduce
self.reduction = reduction
self.pos_weight = pos_weight
self._transpose_op = (
flow.builtin_op("transpose")
JiaojiaoYe1994 marked this conversation as resolved.
Show resolved Hide resolved
.Input("input")
.Output("output")
.Attr("perm", [])
.Build()
)

def forward(self, input, target):
if not (target.shape == input.shape):
raise ValueError("Target size ({}) must be the same as input size ({})".format(target.size(), input.size()))

_neg_input = flow.experimental.negative(input)
_max_val = flow.experimental.clip(_neg_input,0)
_neg_max_val = flow.experimental.negative(_max_val)

if self.pos_weight:
assert self.pos_weight.shape[0] == input.shape[-1], (
JiaojiaoYe1994 marked this conversation as resolved.
Show resolved Hide resolved
"The length of `pos_weight` must be equal to the number of classes. "
"Found the length of pos_weight {} vs classes {}".format(
self.pos_weight.shape[0], input.shape[-1]
)
)
_log_weight = ((self.pos_weight - 1) * target) + 1
_loss = (1 - target) * input + _log_weight * (
flow.experimental.log(
flow.experimental.exp(_neg_max_val) + flow.experimental.exp(_neg_input - _max_val)
)
+ _max_val
)
else:
_loss = (1 - target) * input + _max_val
_loss += flow.experimental.log(
flow.experimental.exp(_neg_max_val) + flow.experimental.exp(_neg_input - _max_val)
)

if self.weight is not None:
assert (
self.weight.shape == input.shape
), "The weight shape must be the same as Input shape"
_weighted_loss = self.weight * _loss
else:
_weighted_loss = _loss

if self.reduction == "mean":
return flow.experimental.mean(_weighted_loss)
elif self.reduction == "sum":
return flow.experimental.sum(_weighted_loss)
else:
# Do no reduction
return _weighted_loss


if __name__ == "__main__":
import doctest

Expand Down
1 change: 1 addition & 0 deletions oneflow/python/ops/nn_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -3787,6 +3787,7 @@ def bce_loss_job(input: tp.Numpy.Placeholder(shape=(2, 3)),


@oneflow_export("nn.BCEWithLogitsLoss")
@stable_api
def bce_with_logits_loss(
input: oneflow._oneflow_internal.BlobDesc,
target: oneflow._oneflow_internal.BlobDesc,
Expand Down
132 changes: 132 additions & 0 deletions oneflow/python/test/modules/test_bcewithlogitsloss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""
Copyright 2020 The OneFlow 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 unittest
from collections import OrderedDict

import numpy as np

import oneflow.experimental as flow
from test_util import GenArgList


def _np_bcewithlogitsloss(np_input, np_target, np_weight=None, np_pos_weight=None, reduction='none'):
_neg_input = np.negative(np_input)
_max_val = np.clip(_neg_input, 0, None)
_neg_max_val = np.negative(_max_val)

if np_pos_weight is not None:
assert np_pos_weight.shape[0] == np_input.shape[-1], (
"The length of `pos_weight` must be equal to the number of classes. "
"Found the length of pos_weight {} vs classes {}".format(
np_pos_weight.shape[0], np_input.shape[-1]
)
)
_log_weight = ((np_pos_weight - 1) * np_target) + 1
_loss = (1 - np_target) * np_input + _log_weight * (
np.log(
np.exp(_neg_max_val) + np.exp(_neg_input - _max_val)
)
+ _max_val
)
else:
_loss = (1 - np_target) * np_input + _max_val
_loss += np.log(
np.exp(_neg_max_val) + np.exp(_neg_input - _max_val)
)

if np_weight is not None:
assert (
np_weight.shape == np_input.shape
), "The weight shape must be the same as Input shape"
_weighted_loss = np_weight * _loss
else:
_weighted_loss = _loss

if reduction == "mean":
return _weighted_loss.mean()
elif reduction == "sum":
return _weighted_loss.sum()
else:
return _weighted_loss


def _np_bcewithlogitsloss_grad(np_input, np_target, np_weight, np_pos_weight):
# Use numpy to compute grad
elemcnt = np_target.size

np_bce_with_logits_grad_mean = -(np_weight / elemcnt) * (
(np_target - 1)
+ ((1 - np_pos_weight) * np_target - 1)
* (-np.exp(-np_input) / (1 + np.exp(-np_input)))
)
np_bce_with_logits_grad_sum = np_bce_with_logits_grad_mean * elemcnt

return {
"mean": np_bce_with_logits_grad_mean,
"sum": np_bce_with_logits_grad_sum,
"none": np_bce_with_logits_grad_sum,
}


def _test_bcewithlogitsloss_impl(test_case, device, shape, reduction):
x = np.random.randn(*shape).astype(np.float32)
y = np.random.randint(0, 2, [*shape]).astype(np.float32)
w = np.random.randn(*shape).astype(np.float32)
pw = np.random.randn([*shape][-1]).astype(np.float32)

input = flow.Tensor(x, dtype=flow.float32, requires_grad=True, device=flow.device(device))
target = flow.Tensor(y, dtype=flow.float32, device=flow.device(device))
weight = flow.Tensor(w, dtype=flow.float32, device=flow.device(device))
pos_weight = flow.Tensor(pw, dtype=flow.float32, device=flow.device(device))

bcewithlogits_loss = flow.nn.BCEWithLogitsLoss(weight=weight, pos_weight=pos_weight, reduction=reduction)
of_out = bcewithlogits_loss(input, target)
np_out = _np_bcewithlogitsloss(x, y, np_weight=w, np_pos_weight=pw, reduction=reduction)
test_case.assertTrue(np.allclose(of_out.numpy(), np_out, 1e-5, 1e-5))

# Backward test with np:
of_out = of_out.sum()
of_out.backward()
np_grad = _np_bcewithlogitsloss_grad(x, y, np_weight=w, np_pos_weight=pw,)[reduction]
test_case.assertTrue(np.allclose(input.grad.numpy(), np_grad, 1e-5, 1e-5))


@unittest.skipIf(
not flow.unittest.env.eager_execution_enabled(),
".numpy() doesn't work in lazy mode",
)
class TestBCEWithLogitsLossModule(flow.unittest.TestCase):
def test_bcewithlogitsloss(test_case):
arg_dict = OrderedDict()
arg_dict["test_fun"] = [
_test_bcewithlogitsloss_impl,
]

arg_dict["device"] = ["cpu", "cuda"]
arg_dict["shape"] = [
(3, 5),
(10, 9, 21),
(14, 22, 9, 21),
(3, 2, 4, 16, 5),
(1,),
]
arg_dict["reduction"] = ["none", "sum", "mean"]
for arg in GenArgList(arg_dict):
arg[0](test_case, *arg[1:])


if __name__ == "__main__":
unittest.main()