-
Notifications
You must be signed in to change notification settings - Fork 130
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
mean_absolute_error with ignore_nan
option
#193
Merged
Merged
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
65f3ffe
mean_squared_error_ignore_nan in cpu
mottodora 8df5a30
implement backward
mottodora a7a66ab
version compatibility
mottodora 843251d
fix tests
mottodora 620a784
add documents
mottodora 72a6ab4
fix comments
mottodora 5ef3ad6
copy mean absolute error from chainer repo
mottodora 454f567
fix tests
mottodora 3aab1f8
add comments
mottodora 6ed7cc9
Merge branch 'mse-ignore-nan' into mae-ignore-nan
mottodora c977c0b
implement ignore_nan option
mottodora 8fbdfa9
add documents
mottodora 734916b
fix tests
mottodora c115fcb
add documents
mottodora 1477476
Merge branch 'mse-ignore-nan' into mae-ignore-nan
mottodora f5ad230
fix
mottodora 761d5f9
fix double backward tests
mottodora 93f36cc
Merge branch 'mse-ignore-nan' into mae-ignore-nan
mottodora 273252f
add double backward test in gpu
mottodora a5c27ba
Merge branch 'mse-ignore-nan' into mae-ignore-nan
mottodora 0ed63ee
fix double backward test
mottodora 6c1acd9
fix
mottodora 2c26c00
add tests
mottodora aae32c3
Merge branch 'mse-ignore-nan' into mae-ignore-nan
mottodora 204b7d6
add tests
mottodora File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
from chainer_chemistry.functions.matmul import matmul # NOQA | ||
from chainer_chemistry.functions.mean_squared_error import mean_squared_error # NOQA | ||
from chainer_chemistry.functions.mean_squared_error import MeanSquaredError # NOQA | ||
from chainer_chemistry.functions.mean_absolute_error import mean_absolute_error # NOQA | ||
from chainer_chemistry.functions.mean_absolute_error import MeanAbsoluteError # NOQA |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import numpy | ||
|
||
import chainer | ||
from chainer.backends import cuda | ||
from chainer import function_node | ||
from chainer.utils import type_check | ||
|
||
|
||
class MeanAbsoluteError(function_node.FunctionNode): | ||
|
||
"""Mean absolute error function.""" | ||
|
||
def __init__(self, ignore_nan=False): | ||
# TODO(mottodora): implement task weight calculation | ||
self.ignore_nan = ignore_nan | ||
|
||
def check_type_forward(self, in_types): | ||
type_check.expect(in_types.size() == 2) | ||
type_check.expect( | ||
in_types[0].dtype == numpy.float32, | ||
in_types[1].dtype == numpy.float32, | ||
in_types[0].shape == in_types[1].shape | ||
) | ||
|
||
def forward_cpu(self, inputs): | ||
self.retain_inputs((0, 1)) | ||
x0, x1 = inputs | ||
diff = (inputs[0] - inputs[1]).ravel() | ||
# TODO(mottodora): add reduce option | ||
if self.ignore_nan: | ||
diff[numpy.isnan(diff)] = 0. | ||
return numpy.array(abs(diff).sum() / diff.size, dtype=diff.dtype), | ||
|
||
def forward_gpu(self, inputs): | ||
self.retain_inputs((0, 1)) | ||
cupy = cuda.cupy | ||
diff = (inputs[0] - inputs[1]).ravel() | ||
if self.ignore_nan: | ||
diff[cupy.isnan(diff)] = 0. | ||
return abs(diff).sum() / diff.dtype.type(diff.size), | ||
|
||
def backward(self, indexes, gy): | ||
x0, x1 = self.get_retained_inputs() | ||
xp = cuda.get_array_module(x0) | ||
diff = x0 - x1 | ||
if self.ignore_nan: | ||
diff = chainer.functions.where(xp.isnan(diff.array), | ||
xp.zeros_like(diff.array), diff) | ||
coeff = gy[0] * gy[0].data.dtype.type(1. / diff.size) | ||
coeff = chainer.functions.broadcast_to(coeff, diff.shape) | ||
gx0 = coeff * chainer.functions.sign(diff) | ||
return gx0, -gx0 | ||
|
||
|
||
def mean_absolute_error(x0, x1, ignore_nan=False): | ||
"""Mean absolute error function. | ||
|
||
This function computes mean absolute error between two variables. The mean | ||
is taken over the minibatch. | ||
|
||
Args: | ||
x0 (:class:`~chainer.Variable` or :class:`numpy.ndarray` or \ | ||
:class:`cupy.ndarray`): Input variable. | ||
x1 (:class:`~chainer.Variable` or :class:`numpy.ndarray` or \ | ||
:class:`cupy.ndarray`): Input variable. | ||
ignore_nan (bool): If `True`, this function compute mean absolute error | ||
ignoring NaNs. The arithmetic mean is the sum of the non-NaN | ||
elements along the axis divided by the number of whole elements. | ||
|
||
Returns: | ||
~chainer.Variable: | ||
A variable holding an array representing the mean absolute | ||
error of two inputs. | ||
""" | ||
return MeanAbsoluteError(ignore_nan).apply((x0, x1))[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import numpy | ||
|
||
from chainer import cuda | ||
from chainer import function_node | ||
import chainer.functions | ||
from chainer.utils import type_check | ||
|
||
|
||
class MeanSquaredError(function_node.FunctionNode): | ||
|
||
"""Mean squared error (a.k.a. Euclidean loss) function.""" | ||
|
||
def __init__(self, ignore_nan=False): | ||
# TODO(mottodora): implement task weight calculation | ||
self.ignore_nan = ignore_nan | ||
|
||
def check_type_forward(self, in_types): | ||
type_check.expect(in_types.size() == 2) | ||
type_check.expect( | ||
in_types[0].dtype == numpy.float32, | ||
in_types[1].dtype == numpy.float32, | ||
in_types[0].shape == in_types[1].shape | ||
) | ||
|
||
def forward_cpu(self, inputs): | ||
self.retain_inputs((0, 1)) | ||
diff = (inputs[0] - inputs[1]).ravel() | ||
# TODO(mottodora): add reduce option | ||
if self.ignore_nan: | ||
diff[numpy.isnan(diff)] = 0. | ||
return numpy.array(diff.dot(diff) / diff.size, dtype=diff.dtype), | ||
|
||
def forward_gpu(self, inputs): | ||
cupy = cuda.cupy | ||
self.retain_inputs((0, 1)) | ||
diff = (inputs[0] - inputs[1]).ravel() | ||
# TODO(mottodora): add reduce option | ||
if self.ignore_nan: | ||
diff[cupy.isnan(diff)] = 0. | ||
return diff.dot(diff) / diff.dtype.type(diff.size), | ||
|
||
def backward(self, indexes, gy): | ||
x0, x1 = self.get_retained_inputs() | ||
xp = cuda.get_array_module(x0) | ||
ret = [] | ||
diff = x0 - x1 | ||
if self.ignore_nan: | ||
diff = chainer.functions.where(xp.isnan(diff.array), | ||
xp.zeros_like(diff.array), diff) | ||
gy0 = chainer.functions.broadcast_to(gy[0], diff.shape) | ||
gx0 = gy0 * diff * (2. / diff.size) | ||
if 0 in indexes: | ||
ret.append(gx0) | ||
if 1 in indexes: | ||
ret.append(-gx0) | ||
return ret | ||
|
||
|
||
def mean_squared_error(x0, x1, ignore_nan=False): | ||
"""Mean squared error function. | ||
This function computes mean squared error between two variables. The mean | ||
is taken over the minibatch. Note that the error is not scaled by 1/2. | ||
|
||
Args: | ||
x0 (:class:`~chainer.Variable` or :class:`numpy.ndarray` or \ | ||
:class:`cupy.ndarray`): Input variable. | ||
x1 (:class:`~chainer.Variable` or :class:`numpy.ndarray` or \ | ||
:class:`cupy.ndarray`): Input variable. | ||
ignore_nan (bool): If `True`, this function compute mean squared error | ||
ignoring NaNs. The arithmetic mean is the sum of the non-NaN | ||
elements along the axis divided by the number of whole elements. | ||
|
||
Returns: | ||
~chainer.Variable: | ||
A variable holding an array representing the mean squared | ||
error of two inputs. | ||
""" | ||
return MeanSquaredError(ignore_nan).apply((x0, x1))[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import numpy | ||
import pytest | ||
|
||
import chainer | ||
from chainer import cuda | ||
from chainer import gradient_check | ||
|
||
import chainer_chemistry | ||
|
||
|
||
@pytest.fixture | ||
def inputs(): | ||
x0 = numpy.random.uniform(-1, 1, (4, 3)).astype(numpy.float32) | ||
# Add sufficient margin to prevent computational error | ||
diff = numpy.random.uniform(-1, 1, (4, 3)).astype(numpy.float32) | ||
diff[abs(diff) < 0.01] = 0.5 | ||
x1 = x0 + diff | ||
x2 = numpy.asarray([[0.3, numpy.nan, 0.2], | ||
[numpy.nan, 0.1, 0.5], | ||
[0.9, 0.7, numpy.nan], | ||
[0.2, -0.3, 0.4]]).astype(numpy.float32) | ||
return x0, x1, x2 | ||
|
||
|
||
@pytest.fixture | ||
def grads(): | ||
gy = numpy.random.uniform(-1, 1, ()).astype(numpy.float32) | ||
ggx0 = numpy.random.uniform(-1, 1, (4, 3)).astype(numpy.float32) | ||
ggx1 = numpy.random.uniform(-1, 1, (4, 3)).astype(numpy.float32) | ||
return gy, ggx0, ggx1 | ||
|
||
|
||
def check_forward(inputs): | ||
x0_data, x1_data, _ = inputs | ||
x0 = chainer.Variable(x0_data) | ||
x1 = chainer.Variable(x1_data) | ||
loss = chainer_chemistry.functions.mean_absolute_error(x0, x1) | ||
loss_value = cuda.to_cpu(loss.data) | ||
assert loss.dtype == numpy.float32 | ||
assert loss_value.shape == () | ||
|
||
loss_expect = numpy.zeros(()) | ||
x0_data = cuda.to_cpu(x0_data) | ||
x1_data = cuda.to_cpu(x1_data) | ||
for i in numpy.ndindex(x0_data.shape): | ||
loss_expect += abs((x0_data[i] - x1_data[i])) | ||
loss_expect /= x0_data.size | ||
assert numpy.allclose(loss_value, loss_expect) | ||
|
||
|
||
def check_forward_ignore_nan(inputs): | ||
x0_data, _, x2_data = inputs | ||
x0 = chainer.Variable(x0_data) | ||
x2 = chainer.Variable(x2_data) | ||
loss = chainer_chemistry.functions.mean_absolute_error(x0, x2, | ||
ignore_nan=True) | ||
loss_value = cuda.to_cpu(loss.data) | ||
assert loss.dtype == numpy.float32 | ||
assert loss_value.shape == () | ||
|
||
loss_expect = numpy.zeros(()) | ||
x0_data = cuda.to_cpu(x0_data) | ||
x2_data = cuda.to_cpu(x2_data) | ||
nan_mask = numpy.invert(numpy.isnan(x2_data)).astype(x2_data.dtype) | ||
for i in numpy.ndindex(x0_data.shape): | ||
loss_expect += abs(x0_data[i] - | ||
numpy.nan_to_num(x2_data[i])) * nan_mask[i] | ||
loss_expect /= x0_data.size | ||
assert numpy.allclose(loss_value, loss_expect) | ||
|
||
|
||
def check_forward_ignore_nan_with_nonnan_value(inputs): | ||
x0_data, x1_data, _ = inputs | ||
x0 = chainer.Variable(x0_data) | ||
x1 = chainer.Variable(x1_data) | ||
loss = chainer_chemistry.functions.mean_absolute_error(x0, x1, | ||
ignore_nan=True) | ||
loss_value = cuda.to_cpu(loss.data) | ||
assert loss.dtype == numpy.float32 | ||
assert loss_value.shape == () | ||
|
||
loss_expect = numpy.zeros(()) | ||
x0_data = cuda.to_cpu(x0_data) | ||
x1_data = cuda.to_cpu(x1_data) | ||
nan_mask = numpy.invert(numpy.isnan(x1_data)).astype(x1_data.dtype) | ||
for i in numpy.ndindex(x0_data.shape): | ||
loss_expect += abs(x0_data[i] - | ||
numpy.nan_to_num(x1_data[i])) * nan_mask[i] | ||
loss_expect /= x0_data.size | ||
assert numpy.allclose(loss_value, loss_expect) | ||
|
||
|
||
def test_forward_cpu(inputs): | ||
check_forward(inputs) | ||
check_forward_ignore_nan(inputs) | ||
check_forward_ignore_nan_with_nonnan_value(inputs) | ||
|
||
|
||
def test_forward_gpu(inputs): | ||
x0, x1, x2 = inputs | ||
check_forward((cuda.to_gpu(x0), cuda.to_gpu(x1), None)) | ||
check_forward_ignore_nan((cuda.to_gpu(x0), None, cuda.to_gpu(x2))) | ||
|
||
|
||
def check_backward(inputs): | ||
x0_data, x1_data, _ = inputs | ||
gradient_check.check_backward( | ||
chainer_chemistry.functions.mean_absolute_error, | ||
(x0_data, x1_data), None, eps=1e-2) | ||
|
||
|
||
def check_backward_ignore_nan(inputs): | ||
x0_data, _, x2_data = inputs | ||
|
||
def func(x0, x1): | ||
return chainer_chemistry.functions.mean_absolute_error(x0, x1, | ||
ignore_nan=True) | ||
gradient_check.check_backward(func, (x0_data, x2_data), None, eps=1e-2) | ||
|
||
|
||
def test_backward_cpu(inputs): | ||
check_backward(inputs) | ||
check_backward_ignore_nan(inputs) | ||
|
||
|
||
@pytest.mark.gpu | ||
def test_backward_gpu(inputs): | ||
x0, x1, x2 = inputs | ||
check_backward((cuda.to_gpu(x0), cuda.to_gpu(x1), None)) | ||
check_backward_ignore_nan((cuda.to_gpu(x0), None, cuda.to_gpu(x2))) | ||
|
||
|
||
def check_double_backward(inputs, grads): | ||
x0, x1, _ = inputs | ||
gy, ggx0, ggx1 = grads | ||
gradient_check.check_double_backward( | ||
chainer_chemistry.functions.mean_absolute_error, | ||
(x0, x1), gy, (ggx0, ggx1)) | ||
|
||
|
||
def check_double_backward_ignore_nan(inputs, grads): | ||
x0, _, x2 = inputs | ||
gy, ggx0, ggx1 = grads | ||
|
||
def func(x0, x1): | ||
return chainer_chemistry.functions.mean_absolute_error(x0, x1, | ||
ignore_nan=True) | ||
gradient_check.check_double_backward(func, (x0, x2), gy, (ggx0, ggx1)) | ||
|
||
|
||
if __name__ == '__main__': | ||
pytest.main([__file__, '-v']) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please put
@pytest.mark.gpu