Repository: Lightning-AI/torchmetrics

Issue:
Metric base class does not check for extra kwargs
## 🐛 Bug

The `Metric` base class does not check for extra kwargs [here](https://github.com/Lightning-AI/metrics/blob/bc057d495703aff5c4d8d671c54060e08a677142/src/torchmetrics/metric.py#L94-L120).
Users can currently do something like `torchmetrics.MeanMetric(foo=True)` without issue.

### To Reproduce

```
import torchmetrics
torchmetrics.MeanMetric(your_favorite_word="lightning")
```


### Expected behavior

Raise an exception if invalid kwargs are passed

### Environment

Latest TorchMetrics version. No other dependencies matter.

### Additional context

[Slack thread](https://pytorch-lightning.slack.com/archives/C010R4A7189/p1672859410056429)


Discussion:

Resolving patch:
diff --git a/src/torchmetrics/metric.py b/src/torchmetrics/metric.py
--- a/src/torchmetrics/metric.py
+++ b/src/torchmetrics/metric.py
@@ -119,6 +119,10 @@ def __init__(
                 f"Expected keyword argument `sync_on_compute` to be a `bool` but got {self.sync_on_compute}"
             )
 
+        if kwargs:
+            kwargs_ = [f"`{a}`" for a in sorted(kwargs)]
+            raise ValueError(f"Unexpected keyword arguments: {', '.join(kwargs_)}")
+
         # initialize
         self._update_signature = inspect.signature(self.update)
         self.update: Callable = self._wrap_update(self.update)  # type: ignore


Test patch:
diff --git a/tests/integrations/test_lightning.py b/tests/integrations/test_lightning.py
--- a/tests/integrations/test_lightning.py
+++ b/tests/integrations/test_lightning.py
@@ -76,7 +76,7 @@ def __init__(self):
             for stage in ["train", "val", "test"]:
                 acc = BinaryAccuracy()
                 acc.reset = mock.Mock(side_effect=acc.reset)
-                ap = BinaryAveragePrecision(num_classes=1, pos_label=1)
+                ap = BinaryAveragePrecision()
                 ap.reset = mock.Mock(side_effect=ap.reset)
                 self.add_module(f"acc_{stage}", acc)
                 self.add_module(f"ap_{stage}", ap)
diff --git a/tests/unittests/bases/test_metric.py b/tests/unittests/bases/test_metric.py
--- a/tests/unittests/bases/test_metric.py
+++ b/tests/unittests/bases/test_metric.py
@@ -44,6 +44,12 @@ def test_error_on_wrong_input():
     with pytest.raises(ValueError, match="Expected keyword argument `compute_on_cpu` to be an `bool` bu.*"):
         DummyMetric(compute_on_cpu=None)
 
+    with pytest.raises(ValueError, match="Unexpected keyword arguments: `foo`"):
+        DummyMetric(foo=True)
+
+    with pytest.raises(ValueError, match="Unexpected keyword arguments: `bar`, `foo`"):
+        DummyMetric(foo=True, bar=42)
+
 
 def test_inherit():
     """Test that metric that inherits can be instanciated."""
diff --git a/tests/unittests/wrappers/test_minmax.py b/tests/unittests/wrappers/test_minmax.py
--- a/tests/unittests/wrappers/test_minmax.py
+++ b/tests/unittests/wrappers/test_minmax.py
@@ -118,7 +118,7 @@ def test_no_base_metric() -> None:
 
 def test_no_scalar_compute() -> None:
     """tests that an assertion error is thrown if the wrapped basemetric gives a non-scalar on compute."""
-    min_max_nsm = MinMaxMetric(BinaryConfusionMatrix(num_classes=2))
+    min_max_nsm = MinMaxMetric(BinaryConfusionMatrix())
 
     with pytest.raises(RuntimeError, match=r"Returned value from base metric should be a scalar .*"):
         min_max_nsm.compute()
diff --git a/tests/unittests/wrappers/test_multioutput.py b/tests/unittests/wrappers/test_multioutput.py
--- a/tests/unittests/wrappers/test_multioutput.py
+++ b/tests/unittests/wrappers/test_multioutput.py
@@ -83,7 +83,7 @@ def _multi_target_sk_accuracy(preds, target, num_outputs):
 
 
 @pytest.mark.parametrize(
-    "base_metric_class, compare_metric, preds, target, num_outputs, metric_kwargs",
+    "base_metric_class, compare_metric, preds, target, num_outputs",
     [
         (
             R2Score,
@@ -91,15 +91,13 @@ def _multi_target_sk_accuracy(preds, target, num_outputs):
             _multi_target_regression_inputs.preds,
             _multi_target_regression_inputs.target,
             num_targets,
-            {},
         ),
         (
-            MulticlassAccuracy,
+            partial(MulticlassAccuracy, num_classes=NUM_CLASSES, average="micro"),
             partial(_multi_target_sk_accuracy, num_outputs=2),
             _multi_target_classification_inputs.preds,
             _multi_target_classification_inputs.target,
             num_targets,
-            dict(num_classes=NUM_CLASSES, average="micro"),
         ),
     ],
 )
@@ -109,7 +107,7 @@ class TestMultioutputWrapper(MetricTester):
     @pytest.mark.parametrize("ddp", [True, False])
     @pytest.mark.parametrize("dist_sync_on_step", [True, False])
     def test_multioutput_wrapper(
-        self, base_metric_class, compare_metric, preds, target, num_outputs, metric_kwargs, ddp, dist_sync_on_step
+        self, base_metric_class, compare_metric, preds, target, num_outputs, ddp, dist_sync_on_step
     ):
         """Test that the multioutput wrapper properly slices and computes outputs along the output dimension for
         both classification and regression metrics."""
@@ -120,5 +118,5 @@ def test_multioutput_wrapper(
             _MultioutputMetric,
             compare_metric,
             dist_sync_on_step,
-            metric_args=dict(num_outputs=num_outputs, base_metric_class=base_metric_class, **metric_kwargs),
+            metric_args=dict(num_outputs=num_outputs, base_metric_class=base_metric_class),
         )