Repository: continualai/avalanche
Issue: torchmetrics v0.11 incompatiblity

Hello guys :),
so just two days ago, torchmetrics relrease a new version V0.11 ([see here](https://pypi.org/project/torchmetrics/#history)) which redefined their class torchmetrics.functional.accuracy to require definition of task type (see link : [Click Here](https://torchmetrics.readthedocs.io/en/stable/classification/accuracy.html#accuracy)).

this causes an error to be raised when using accuracy metrics , since it uses accuracy class without defining task positional argument.
for now I managed to make my current code work by specifying the previous version of torchmetrics as follows:
`!pip install torchmetrics==0.10.3`


Changed files:
avalanche/evaluation/metrics/topk_acc.py, tests/test_metrics.py

diff --git a/avalanche/evaluation/metrics/topk_acc.py b/avalanche/evaluation/metrics/topk_acc.py
index 9baf952a..b2af7922 100644
--- a/avalanche/evaluation/metrics/topk_acc.py
+++ b/avalanche/evaluation/metrics/topk_acc.py
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, List, Union, Dict
 
 import torch
 from torch import Tensor
+import torchmetrics
 from torchmetrics.functional import accuracy
 
 from avalanche.evaluation import Metric, GenericPluginMetric
@@ -20,6 +21,7 @@ from avalanche.evaluation.metrics.mean import Mean
 from avalanche.evaluation.metric_utils import phase_and_task
 
 from collections import defaultdict
+from packaging import version
 
 if TYPE_CHECKING:
     from avalanche.training.templates.common_templates import SupervisedTemplate
@@ -31,7 +33,7 @@ class TopkAccuracy(Metric[Dict[int, float]]):
     It is defined using torchmetrics.functional accuracy with top_k
     """
 
-    def __init__(self, top_k):
+    def __init__(self, top_k: int):
         """
         Creates an instance of the standalone Top-k Accuracy metric.
 
@@ -41,8 +43,11 @@ class TopkAccuracy(Metric[Dict[int, float]]):
 
         :param top_k: integer number to define the value of k.
         """
-        self._topk_acc_dict = defaultdict(Mean)
-        self.top_k = top_k
+        self._topk_acc_dict: Dict[int, Mean] = defaultdict(Mean)
+        self.top_k: int = top_k
+
+        self.__torchmetrics_requires_task = \\
+            version.parse(torchmetrics.__version__) >= version.parse('0.11.0')
 
     @torch.no_grad()
     def update(
@@ -80,12 +85,14 @@ class TopkAccuracy(Metric[Dict[int, float]]):
         if isinstance(task_labels, int):
             total_patterns = len(true_y)
             self._topk_acc_dict[task_labels].update(
-                accuracy(predicted_y, true_y, top_k=self.top_k), total_patterns
+                self._compute_topk_acc(predicted_y, true_y, top_k=self.top_k), 
+                total_patterns
             )
         elif isinstance(task_labels, Tensor):
             for pred, true, t in zip(predicted_y, true_y, task_labels):
-                self._topk_acc_dict[t.item()].update(
-                    accuracy(pred, true, top_k=self.top_k), 1
+                self._topk_acc_dict[int(t)].update(
+                    self._compute_topk_acc(pred, true, top_k=self.top_k),
+                    1
                 )
         else:
             raise ValueError(
@@ -93,6 +100,26 @@ class TopkAccuracy(Metric[Dict[int, float]]):
                 f"expected int/float or Tensor"
             )
         
+    def _compute_topk_acc(self, pred, gt, top_k):
+        if self.__torchmetrics_requires_task:
+            num_classes = int(torch.max(torch.as_tensor(gt))) + 1
+            pred_t = torch.as_tensor(pred)
+            if len(pred_t.shape) > 1:
+                num_classes = max(num_classes, pred_t.shape[1])
+            
+            return accuracy(
+                pred,
+                gt,
+                task="multiclass",
+                num_classes=num_classes,
+                top_k=self.top_k
+            )
+        else:
+            return accuracy(
+                pred,
+                gt,
+                top_k=self.top_k)
+        
     def result_task_label(self, task_label: int) -> Dict[int, float]:
         """
         Retrieves the running top-k accuracy.
diff --git a/tests/test_metrics.py b/tests/test_metrics.py
index 930cd8c1..4829b7a8 100644
--- a/tests/test_metrics.py
+++ b/tests/test_metrics.py
@@ -6,6 +6,7 @@ import torch
 
 from avalanche.evaluation.metrics import (
     TaskAwareAccuracy,
+    TopkAccuracy,
     AverageMeanClassAccuracy,
     MultiStreamAMCA,
     ClassAccuracy,
@@ -52,6 +53,69 @@ class GeneralMetricTests(unittest.TestCase):
         metric.reset()
         self.assertEqual(metric.result(), {})
 
+    def test_topk_accuracy(self):
+        test_y = torch.as_tensor([0, 1, 0, 2, 3, 0, 3])
+        test_out = torch.zeros(test_y.shape[0], 4, dtype=torch.float32)
+
+        # top-1. gt = 0
+        test_out[0][0] = 1.0
+
+        # top-3. gt = 1
+        test_out[1][0] = 1.0
+        test_out[1][1] = 0.8
+        test_out[1][2] = 0.9
+
+        # top-2. gt = 0
+        test_out[2][0] = 0.1
+        test_out[2][1] = 0.8
+        test_out[2][2] = 0.05
+
+        # top-4. gt = 2
+        test_out[3][0] = 0.1
+        test_out[3][1] = 0.8
+        test_out[3][2] = 0.07
+        test_out[3][3] = 0.2
+
+        # top-1. gt = 3
+        test_out[4][0] = 0.085
+        test_out[4][1] = 0.25
+        test_out[4][2] = 0.07
+        test_out[4][3] = 0.3
+
+        # top-2. gt = 0
+        test_out[5][0] = 0.085
+        test_out[5][1] = 0.075
+        test_out[5][2] = 0.1
+        test_out[5][3] = 0.0
+
+        # top-3. gt = 3
+        test_out[6][0] = 0.0
+        test_out[6][1] = 0.9
+        test_out[6][2] = 0.8
+        test_out[6][3] = 0.7
+
+        expected_per_k = [
+            2/7,  # top-1
+            4/7,  # top-2
+            6/7,  # top-3
+            1.0   # top-4
+        ]
+
+        for k in range(1, 5):
+            with self.subTest(k=k):
+                test_t_label = k % 2
+                metric = TopkAccuracy(k)
+                expected_result = expected_per_k[k-1]
+            
+                self.assertEqual(metric.result(), {})
+                metric.update(test_out, test_y, test_t_label)
+
+                self.assertAlmostEqual(
+                    expected_result,
+                    metric.result()[test_t_label])
+                metric.reset()
+                self.assertEqual(metric.result(), {})
+
     def test_accuracy_task_per_pattern(self):
         metric = TaskAwareAccuracy()
         self.assertEqual(metric.result(), {})