Skip to content

add available device to regression tests #3335 #3394

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
66755ad
add available device to test_canberra_metric.py
BanzaiTokyo Apr 24, 2025
9229e3b
add _double_dtype ad dtype when transfrring errors to device
BanzaiTokyo Apr 24, 2025
2f6320a
available devices in test_fractional_absolute_error.py, test_fraction…
BanzaiTokyo Apr 24, 2025
557f549
when transferring to device use dtype
BanzaiTokyo Apr 24, 2025
0130773
add available device to tests
BanzaiTokyo Apr 24, 2025
94a002b
use self._double_dtype instead of torch.double
BanzaiTokyo Apr 24, 2025
2631377
use self._double_dtype when moving to device in epoch_metric.py
BanzaiTokyo Apr 24, 2025
d5b9e5a
removes unnecessary tests
BanzaiTokyo Apr 24, 2025
f99b643
rollbacks changes in epoch_metric.py
BanzaiTokyo Apr 24, 2025
e24ce01
redo test_integration
BanzaiTokyo Apr 24, 2025
3dbbe1e
redo test_integration
BanzaiTokyo Apr 24, 2025
1cf59fa
casting of eps in _update
BanzaiTokyo Apr 24, 2025
6f0599d
more conversions to torch
BanzaiTokyo Apr 24, 2025
35527d5
in _torch_median move output to cpu if mps (torch.kthvalue is not sup…
BanzaiTokyo Apr 25, 2025
c13837e
fixing test_degenerated_sample
BanzaiTokyo Apr 25, 2025
c85dab1
fixing test_degenerated_sample
BanzaiTokyo Apr 25, 2025
c662c44
rename upper case variables
BanzaiTokyo Apr 25, 2025
e471064
change range to 3
BanzaiTokyo Apr 25, 2025
37a0469
rewrite test_compute
BanzaiTokyo Apr 25, 2025
71af57e
rewrite test_fractional_bias
BanzaiTokyo Apr 25, 2025
d59cb6f
remove prints
BanzaiTokyo Apr 25, 2025
da2e75d
rollback eps in canberra_metric.py
BanzaiTokyo Apr 25, 2025
0a2f6d4
rollback test_epoch_metric.py because the changes are moved to a sepa…
BanzaiTokyo Apr 25, 2025
d1ef2d4
Merge branch 'master' into regression_tests_add_available_device
BanzaiTokyo Apr 25, 2025
667332d
set sum_of_errors as _double_dtype
BanzaiTokyo Apr 28, 2025
713aab9
Merge branch 'master' into regression_tests_add_available_device
BanzaiTokyo Apr 28, 2025
579d035
use torch instead of numpy where possible in test_canberra_metric.py
BanzaiTokyo Apr 28, 2025
cab29ca
Merge branch 'master' into regression_tests_add_available_device
BanzaiTokyo Apr 29, 2025
e6c96de
remove double_dtype from metrics
BanzaiTokyo Apr 29, 2025
346e0e1
takes into account PR comments
BanzaiTokyo May 2, 2025
ded98cf
refactor integration tests for fractional bias and fractional absolut…
BanzaiTokyo May 2, 2025
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
4 changes: 4 additions & 0 deletions ignite/metrics/regression/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def _check_output_types(output: Tuple[torch.Tensor, torch.Tensor]) -> None:


def _torch_median(output: torch.Tensor) -> float:
# torch.kthvalue used later is not supported on MPS
if output.device.type == "mps":
output = output.cpu()

output = output.view(-1)
len_ = len(output)

Expand Down
2 changes: 1 addition & 1 deletion ignite/metrics/regression/fractional_bias.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class FractionalBias(_BaseRegression):

@reinit__is_reduced
def reset(self) -> None:
self._sum_of_errors = torch.tensor(0.0, dtype=torch.double, device=self._device)
self._sum_of_errors = torch.tensor(0.0, dtype=self._double_dtype, device=self._device)
self._num_examples = 0

def _update(self, output: Tuple[torch.Tensor, torch.Tensor]) -> None:
Expand Down
9 changes: 8 additions & 1 deletion ignite/metrics/regression/pearson_correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,12 @@ def compute(self) -> float:
y_var = self._sum_of_y_squares / n - y_mean * y_mean
y_var = torch.clamp(y_var, min=0.0)

r = cov / torch.clamp(torch.sqrt(y_pred_var * y_var), min=self.eps)
denom = y_pred_var * y_var
denom = torch.clamp(denom, min=self.eps)
denom = torch.sqrt(denom)
r = cov / denom

if torch.isnan(r):
return 0.0
Comment on lines +122 to +128
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change ? Have you checked with the original implementation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to make sure I ran the tests once again. Without this snippet, on MPS a division by zero produces NaN:

FAILED tests/ignite/metrics/regression/test_pearson_correlation.py::test_degenerated_sample[mps] - assert 0.0 ± 1.0e-12 == nan
  
  comparison failed
  Obtained: nan
  Expected: 0.0 ± 1.0e-12


return float(r.item())
92 changes: 46 additions & 46 deletions tests/ignite/metrics/regression/test_canberra_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,83 +20,83 @@ def test_wrong_input_shapes():
m.update((torch.rand(4, 1), torch.rand(4)))


def test_compute():
a = np.random.randn(4)
b = np.random.randn(4)
c = np.random.randn(4)
d = np.random.randn(4)
ground_truth = np.random.randn(4)
def test_compute(available_device):
a = torch.randn(4)
b = torch.randn(4)
c = torch.randn(4)
d = torch.randn(4)
ground_truth = torch.randn(4)

m = CanberraMetric()
m = CanberraMetric(device=available_device)
assert m._device == torch.device(available_device)

canberra = DistanceMetric.get_metric("canberra")

m.update((torch.from_numpy(a), torch.from_numpy(ground_truth)))
np_sum = (np.abs(ground_truth - a) / (np.abs(a) + np.abs(ground_truth))).sum()
m.update((a, ground_truth))
np_sum = (torch.abs(ground_truth - a) / (torch.abs(a) + torch.abs(ground_truth))).sum()
assert m.compute() == pytest.approx(np_sum)
assert canberra.pairwise([a, ground_truth])[0][1] == pytest.approx(np_sum)
assert canberra.pairwise([a.cpu().numpy(), ground_truth.cpu().numpy()])[0][1] == pytest.approx(np_sum)

m.update((torch.from_numpy(b), torch.from_numpy(ground_truth)))
np_sum += ((np.abs(ground_truth - b)) / (np.abs(b) + np.abs(ground_truth))).sum()
m.update((b, ground_truth))
np_sum += ((torch.abs(ground_truth - b)) / (torch.abs(b) + torch.abs(ground_truth))).sum()
assert m.compute() == pytest.approx(np_sum)
v1 = np.hstack([a, b])
v2 = np.hstack([ground_truth, ground_truth])
assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)

m.update((torch.from_numpy(c), torch.from_numpy(ground_truth)))
np_sum += ((np.abs(ground_truth - c)) / (np.abs(c) + np.abs(ground_truth))).sum()
m.update((c, ground_truth))
np_sum += ((torch.abs(ground_truth - c)) / (torch.abs(c) + torch.abs(ground_truth))).sum()
assert m.compute() == pytest.approx(np_sum)
v1 = np.hstack([v1, c])
v2 = np.hstack([v2, ground_truth])
assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)

m.update((torch.from_numpy(d), torch.from_numpy(ground_truth)))
np_sum += (np.abs(ground_truth - d) / (np.abs(d) + np.abs(ground_truth))).sum()
m.update((d, ground_truth))
np_sum += (torch.abs(ground_truth - d) / (torch.abs(d) + torch.abs(ground_truth))).sum()
assert m.compute() == pytest.approx(np_sum)
v1 = np.hstack([v1, d])
v2 = np.hstack([v2, ground_truth])
assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)


def test_integration():
def _test(y_pred, y, batch_size):
def update_fn(engine, batch):
idx = (engine.state.iteration - 1) * batch_size
y_true_batch = np_y[idx : idx + batch_size]
y_pred_batch = np_y_pred[idx : idx + batch_size]
return torch.from_numpy(y_pred_batch), torch.from_numpy(y_true_batch)

engine = Engine(update_fn)
@pytest.mark.parametrize("n_times", range(3))
@pytest.mark.parametrize(
"test_cases",
[
(torch.rand(size=(100,)), torch.rand(size=(100,)), 10),
(torch.rand(size=(100, 1)), torch.rand(size=(100, 1)), 20),
],
)
def test_integration(n_times, test_cases, available_device):
y_pred, y, batch_size = test_cases

m = CanberraMetric()
m.attach(engine, "cm")
def update_fn(engine, batch):
idx = (engine.state.iteration - 1) * batch_size
y_true_batch = y[idx : idx + batch_size]
y_pred_batch = y_pred[idx : idx + batch_size]
return y_pred_batch, y_true_batch

np_y = y.numpy().ravel()
np_y_pred = y_pred.numpy().ravel()
engine = Engine(update_fn)

canberra = DistanceMetric.get_metric("canberra")
m = CanberraMetric(device=available_device)
assert m._device == torch.device(available_device)

data = list(range(y_pred.shape[0] // batch_size))
cm = engine.run(data, max_epochs=1).metrics["cm"]
m.attach(engine, "cm")

assert canberra.pairwise([np_y_pred, np_y])[0][1] == pytest.approx(cm)
canberra = DistanceMetric.get_metric("canberra")

def get_test_cases():
test_cases = [
(torch.rand(size=(100,)), torch.rand(size=(100,)), 10),
(torch.rand(size=(100, 1)), torch.rand(size=(100, 1)), 20),
]
return test_cases
data = list(range(y_pred.shape[0] // batch_size))
cm = engine.run(data, max_epochs=1).metrics["cm"]

for _ in range(5):
# check multiple random inputs as random exact occurencies are rare
test_cases = get_test_cases()
for y_pred, y, batch_size in test_cases:
_test(y_pred, y, batch_size)
pred_np = y_pred.cpu().numpy().reshape(len(y_pred), -1)
true_np = y.cpu().numpy().reshape(len(y), -1)
expected = np.sum(canberra.pairwise(pred_np, true_np).diagonal())
assert expected == pytest.approx(cm)


def test_error_is_not_nan():
m = CanberraMetric()
def test_error_is_not_nan(available_device):
m = CanberraMetric(device=available_device)
assert m._device == torch.device(available_device)
m.update((torch.zeros(4), torch.zeros(4)))
assert not (torch.isnan(m._sum_of_errors).any() or torch.isinf(m._sum_of_errors).any()), m._sum_of_errors

Expand Down
101 changes: 45 additions & 56 deletions tests/ignite/metrics/regression/test_fractional_absolute_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,77 +28,66 @@ def test_wrong_input_shapes():
m.update((torch.rand(4, 1), torch.rand(4)))


def test_compute():
a = np.random.randn(4)
b = np.random.randn(4)
c = np.random.randn(4)
d = np.random.randn(4)
ground_truth = np.random.randn(4)
def test_compute(available_device):
a = torch.randn(4)
b = torch.randn(4)
c = torch.randn(4)
d = torch.randn(4)
ground_truth = torch.randn(4)

m = FractionalAbsoluteError()
m = FractionalAbsoluteError(device=available_device)
assert m._device == torch.device(available_device)

m.update((torch.from_numpy(a), torch.from_numpy(ground_truth)))
np_sum = (2 * np.abs((a - ground_truth)) / (np.abs(a) + np.abs(ground_truth))).sum()
np_len = len(a)
np_ans = np_sum / np_len
assert m.compute() == pytest.approx(np_ans)
total_error = 0.0
total_len = 0

m.update((torch.from_numpy(b), torch.from_numpy(ground_truth)))
np_sum += (2 * np.abs((b - ground_truth)) / (np.abs(b) + np.abs(ground_truth))).sum()
np_len += len(b)
np_ans = np_sum / np_len
assert m.compute() == pytest.approx(np_ans)
for pred in [a, b, c, d]:
m.update((pred, ground_truth))

m.update((torch.from_numpy(c), torch.from_numpy(ground_truth)))
np_sum += (2 * np.abs((c - ground_truth)) / (np.abs(c) + np.abs(ground_truth))).sum()
np_len += len(c)
np_ans = np_sum / np_len
assert m.compute() == pytest.approx(np_ans)
# Compute fractional absolute error in PyTorch
error = 2 * torch.abs(pred - ground_truth) / (torch.abs(pred) + torch.abs(ground_truth))
total_error += error.sum().item()
total_len += len(pred)

m.update((torch.from_numpy(d), torch.from_numpy(ground_truth)))
np_sum += (2 * np.abs((d - ground_truth)) / (np.abs(d) + np.abs(ground_truth))).sum()
np_len += len(d)
np_ans = np_sum / np_len
assert m.compute() == pytest.approx(np_ans)
expected = total_error / total_len
assert m.compute() == pytest.approx(expected)


def test_integration():
def _test(y_pred, y, batch_size):
def update_fn(engine, batch):
idx = (engine.state.iteration - 1) * batch_size
y_true_batch = np_y[idx : idx + batch_size]
y_pred_batch = np_y_pred[idx : idx + batch_size]
return torch.from_numpy(y_pred_batch), torch.from_numpy(y_true_batch)
@pytest.mark.parametrize("n_times", range(5))
@pytest.mark.parametrize(
"test_cases",
[
(torch.rand(size=(100,)), torch.rand(size=(100,)), 10),
(torch.rand(size=(100, 1)), torch.rand(size=(100, 1)), 20),
],
)
def test_integration_fractional_absolute_error(n_times, test_cases, available_device):
y_pred, y, batch_size = test_cases

engine = Engine(update_fn)
np_y = y.numpy().ravel()
np_y_pred = y_pred.numpy().ravel()

m = FractionalAbsoluteError()
m.attach(engine, "fab")
def update_fn(engine, batch):
idx = (engine.state.iteration - 1) * batch_size
y_true_batch = y[idx : idx + batch_size]
y_pred_batch = y_pred[idx : idx + batch_size]
return y_pred_batch, y_true_batch

np_y = y.numpy().ravel()
np_y_pred = y_pred.numpy().ravel()
engine = Engine(update_fn)

data = list(range(y_pred.shape[0] // batch_size))
fab = engine.run(data, max_epochs=1).metrics["fab"]
metric = FractionalAbsoluteError(device=available_device)
assert metric._device == torch.device(available_device)

np_sum = (2 * np.abs((np_y_pred - np_y)) / (np.abs(np_y_pred) + np.abs(np_y))).sum()
np_len = len(y_pred)
np_ans = np_sum / np_len
metric.attach(engine, "fab")

assert np_ans == pytest.approx(fab)
data = list(range(y_pred.shape[0] // batch_size))
fab = engine.run(data, max_epochs=1).metrics["fab"]

def get_test_cases():
test_cases = [
(torch.rand(size=(100,)), torch.rand(size=(100,)), 10),
(torch.rand(size=(100, 1)), torch.rand(size=(100, 1)), 20),
]
return test_cases
# Expected result using NumPy
np_sum = (2 * np.abs(np_y_pred - np_y) / (np.abs(np_y_pred) + np.abs(np_y))).sum()
expected = np_sum / len(np_y)

for _ in range(5):
# check multiple random inputs as random exact occurencies are rare
test_cases = get_test_cases()
for y_pred, y, batch_size in test_cases:
_test(y_pred, y, batch_size)
assert expected == pytest.approx(fab)


def _test_distrib_compute(device):
Expand Down
Loading
Loading