ckadirt commited on
Commit
ca01182
·
verified ·
1 Parent(s): 4d7feec

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. rtme/lib/python3.10/site-packages/babel/locale-data/sv.dat +3 -0
  3. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/LICENSE +27 -0
  4. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/METADATA +28 -0
  5. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/RECORD +25 -0
  6. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/REQUESTED +0 -0
  7. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/WHEEL +5 -0
  8. rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/top_level.txt +1 -0
  9. rtme/lib/python3.10/site-packages/send2trash/__init__.py +21 -0
  10. rtme/lib/python3.10/site-packages/send2trash/__main__.py +33 -0
  11. rtme/lib/python3.10/site-packages/send2trash/compat.py +25 -0
  12. rtme/lib/python3.10/site-packages/send2trash/plat_gio.py +23 -0
  13. rtme/lib/python3.10/site-packages/send2trash/plat_other.py +218 -0
  14. rtme/lib/python3.10/site-packages/torchmetrics/classification/f_beta.py +1221 -0
  15. rtme/lib/python3.10/site-packages/torchmetrics/classification/group_fairness.py +326 -0
  16. rtme/lib/python3.10/site-packages/torchmetrics/classification/hamming.py +529 -0
  17. rtme/lib/python3.10/site-packages/torchmetrics/classification/hinge.py +380 -0
  18. rtme/lib/python3.10/site-packages/torchmetrics/classification/jaccard.py +485 -0
  19. rtme/lib/python3.10/site-packages/torchmetrics/classification/logauc.py +507 -0
  20. rtme/lib/python3.10/site-packages/torchmetrics/classification/matthews_corrcoef.py +416 -0
  21. rtme/lib/python3.10/site-packages/torchmetrics/classification/negative_predictive_value.py +522 -0
  22. rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_fixed_recall.py +515 -0
  23. rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall.py +1086 -0
  24. rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall_curve.py +692 -0
  25. rtme/lib/python3.10/site-packages/torchmetrics/classification/ranking.py +431 -0
  26. rtme/lib/python3.10/site-packages/torchmetrics/classification/recall_fixed_precision.py +514 -0
  27. rtme/lib/python3.10/site-packages/torchmetrics/classification/roc.py +596 -0
  28. rtme/lib/python3.10/site-packages/torchmetrics/classification/sensitivity_specificity.py +375 -0
  29. rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity.py +513 -0
  30. rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity_sensitivity.py +375 -0
  31. rtme/lib/python3.10/site-packages/torchmetrics/classification/stat_scores.py +562 -0
  32. rtme/lib/python3.10/site-packages/torchmetrics/clustering/__init__.py +44 -0
  33. rtme/lib/python3.10/site-packages/torchmetrics/clustering/adjusted_rand_score.py +127 -0
  34. rtme/lib/python3.10/site-packages/torchmetrics/clustering/calinski_harabasz_score.py +128 -0
  35. rtme/lib/python3.10/site-packages/torchmetrics/clustering/cluster_accuracy.py +148 -0
  36. rtme/lib/python3.10/site-packages/torchmetrics/clustering/davies_bouldin_score.py +138 -0
  37. rtme/lib/python3.10/site-packages/torchmetrics/clustering/dunn_index.py +129 -0
  38. rtme/lib/python3.10/site-packages/torchmetrics/clustering/fowlkes_mallows_index.py +122 -0
  39. rtme/lib/python3.10/site-packages/torchmetrics/clustering/homogeneity_completeness_v_measure.py +329 -0
  40. rtme/lib/python3.10/site-packages/torchmetrics/clustering/mutual_info_score.py +127 -0
  41. rtme/lib/python3.10/site-packages/torchmetrics/clustering/normalized_mutual_info_score.py +127 -0
  42. rtme/lib/python3.10/site-packages/torchmetrics/clustering/rand_score.py +125 -0
  43. rtme/lib/python3.10/site-packages/torchmetrics/detection/__init__.py +32 -0
  44. rtme/lib/python3.10/site-packages/torchmetrics/detection/_deprecated.py +63 -0
  45. rtme/lib/python3.10/site-packages/torchmetrics/detection/_mean_ap.py +988 -0
  46. rtme/lib/python3.10/site-packages/torchmetrics/detection/diou.py +195 -0
  47. rtme/lib/python3.10/site-packages/torchmetrics/detection/giou.py +190 -0
  48. rtme/lib/python3.10/site-packages/torchmetrics/detection/iou.py +297 -0
  49. rtme/lib/python3.10/site-packages/torchmetrics/functional/image/d_lambda.py +152 -0
  50. rtme/lib/python3.10/site-packages/torchmetrics/functional/image/dists.py +215 -0
.gitattributes CHANGED
@@ -5333,3 +5333,4 @@ rtme/lib/python3.10/site-packages/babel/locale-data/so.dat filter=lfs diff=lfs m
5333
  rtme/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-310.pyc filter=lfs diff=lfs merge=lfs -text
5334
  rtme/lib/python3.10/site-packages/babel/locale-data/vi.dat filter=lfs diff=lfs merge=lfs -text
5335
  rtme/lib/python3.10/site-packages/babel/locale-data/to.dat filter=lfs diff=lfs merge=lfs -text
 
 
5333
  rtme/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-310.pyc filter=lfs diff=lfs merge=lfs -text
5334
  rtme/lib/python3.10/site-packages/babel/locale-data/vi.dat filter=lfs diff=lfs merge=lfs -text
5335
  rtme/lib/python3.10/site-packages/babel/locale-data/to.dat filter=lfs diff=lfs merge=lfs -text
5336
+ rtme/lib/python3.10/site-packages/babel/locale-data/sv.dat filter=lfs diff=lfs merge=lfs -text
rtme/lib/python3.10/site-packages/babel/locale-data/sv.dat ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:453bd0da58bd70a1c811b565edf23c10d56b99c09ad601119374bd3b66c72a60
3
+ size 198967
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/LICENSE ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pycparser -- A C parser in Python
2
+
3
+ Copyright (c) 2008-2022, Eli Bendersky
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification,
7
+ are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+ * Neither the name of the copyright holder nor the names of its contributors may
15
+ be used to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
27
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/METADATA ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: pycparser
3
+ Version: 2.22
4
+ Summary: C parser in Python
5
+ Home-page: https://github.com/eliben/pycparser
6
+ Author: Eli Bendersky
7
+ Author-email: eliben@gmail.com
8
+ Maintainer: Eli Bendersky
9
+ License: BSD-3-Clause
10
+ Platform: Cross Platform
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ License-File: LICENSE
21
+
22
+
23
+ pycparser is a complete parser of the C language, written in
24
+ pure Python using the PLY parsing library.
25
+ It parses C code into an AST and can serve as a front-end for
26
+ C compilers or analysis tools.
27
+
28
+
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/RECORD ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pycparser-2.22.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
2
+ pycparser-2.22.dist-info/LICENSE,sha256=DIRjmTaep23de1xE_m0WSXQV_PAV9cu1CMJL-YuBxbE,1543
3
+ pycparser-2.22.dist-info/METADATA,sha256=3XOB8nggH4ijl17DCjUhk7g6qioMJLprUlEkwYgZvW8,943
4
+ pycparser-2.22.dist-info/RECORD,,
5
+ pycparser-2.22.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pycparser-2.22.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
7
+ pycparser-2.22.dist-info/top_level.txt,sha256=c-lPcS74L_8KoH7IE6PQF5ofyirRQNV4VhkbSFIPeWM,10
8
+ pycparser/__init__.py,sha256=hrf-AyuVYNHQGTD0Nv2bywxoTN3N1ZCs03m-9-QDS14,2918
9
+ pycparser/_ast_gen.py,sha256=0JRVnDW-Jw-3IjVlo8je9rbAcp6Ko7toHAnB5zi7h0Q,10555
10
+ pycparser/_build_tables.py,sha256=4d_UkIxJ4YfHTVn6xBzBA52wDo7qxg1B6aZAJYJas9Q,1087
11
+ pycparser/_c_ast.cfg,sha256=ld5ezE9yzIJFIVAUfw7ezJSlMi4nXKNCzfmqjOyQTNo,4255
12
+ pycparser/ast_transforms.py,sha256=GTMYlUgWmXd5wJVyovXY1qzzAqjxzCpVVg0664dKGBs,5691
13
+ pycparser/c_ast.py,sha256=HWeOrfYdCY0u5XaYhE1i60uVyE3yMWdcxzECUX-DqJw,31445
14
+ pycparser/c_generator.py,sha256=yi6Mcqxv88J5ue8k5-mVGxh3iJ37iD4QyF-sWcGjC-8,17772
15
+ pycparser/c_lexer.py,sha256=RSUjq0SRH8dkvwrQslBIZY2AXOrpQpe-oO1udJXotZk,17186
16
+ pycparser/c_parser.py,sha256=WUnIHNydl32QBuRUqrqk-F2lyB6WRP4BUYFELqVETyw,74282
17
+ pycparser/lextab.py,sha256=Nc3I0_D8Xlf-BOpfOKkEvFw-rPuFPPwAjkcLubwTCU4,8554
18
+ pycparser/ply/__init__.py,sha256=q4s86QwRsYRa20L9ueSxfh-hPihpftBjDOvYa2_SS2Y,102
19
+ pycparser/ply/cpp.py,sha256=UtC3ylTWp5_1MKA-PLCuwKQR8zSOnlGuGGIdzj8xS98,33282
20
+ pycparser/ply/ctokens.py,sha256=MKksnN40TehPhgVfxCJhjj_BjL943apreABKYz-bl0Y,3177
21
+ pycparser/ply/lex.py,sha256=rCMi0yjlZmjH5SNXj_Yds1VxSDkaG2thS7351YvfN-I,42926
22
+ pycparser/ply/yacc.py,sha256=eatSDkRLgRr6X3-hoDk_SQQv065R0BdL2K7fQ54CgVM,137323
23
+ pycparser/ply/ygen.py,sha256=2JYNeYtrPz1JzLSLO3d4GsS8zJU8jY_I_CR1VI9gWrA,2251
24
+ pycparser/plyparser.py,sha256=8tLOoEytcapvWrr1JfCf7Dog-wulBtS1YrDs8S7JfMo,4875
25
+ pycparser/yacctab.py,sha256=B6ck8QEPnRi04VSxKEL6xHaP8sEEsTbWtwsjfKHABgM,209738
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/REQUESTED ADDED
File without changes
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.37.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ pycparser
rtme/lib/python3.10/site-packages/send2trash/__init__.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
2
+
3
+ # This software is licensed under the "BSD" License as described in the "LICENSE" file,
4
+ # which should be included with this package. The terms are also available at
5
+ # http://www.hardcoded.net/licenses/bsd_license
6
+
7
+ import sys
8
+
9
+ from send2trash.exceptions import TrashPermissionError # noqa: F401
10
+
11
+ if sys.platform == "darwin":
12
+ from send2trash.mac import send2trash
13
+ elif sys.platform == "win32":
14
+ from send2trash.win import send2trash
15
+ else:
16
+ try:
17
+ # If we can use gio, let's use it
18
+ from send2trash.plat_gio import send2trash
19
+ except ImportError:
20
+ # Oh well, let's fallback to our own Freedesktop trash implementation
21
+ from send2trash.plat_other import send2trash # noqa: F401
rtme/lib/python3.10/site-packages/send2trash/__main__.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # encoding: utf-8
2
+ # Copyright 2017 Virgil Dupras
3
+
4
+ # This software is licensed under the "BSD" License as described in the "LICENSE" file,
5
+ # which should be included with this package. The terms are also available at
6
+ # http://www.hardcoded.net/licenses/bsd_license
7
+
8
+ from __future__ import print_function
9
+
10
+ import sys
11
+
12
+ from argparse import ArgumentParser
13
+ from send2trash import send2trash
14
+
15
+
16
+ def main(args=None):
17
+ parser = ArgumentParser(description="Tool to send files to trash")
18
+ parser.add_argument("files", nargs="+")
19
+ parser.add_argument("-v", "--verbose", action="store_true", help="Print deleted files")
20
+ args = parser.parse_args(args)
21
+
22
+ for filename in args.files:
23
+ try:
24
+ send2trash(filename)
25
+ if args.verbose:
26
+ print("Trashed «" + filename + "»")
27
+ except OSError as e:
28
+ print(str(e), file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
rtme/lib/python3.10/site-packages/send2trash/compat.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Virgil Dupras
2
+
3
+ # This software is licensed under the "BSD" License as described in the "LICENSE" file,
4
+ # which should be included with this package. The terms are also available at
5
+ # http://www.hardcoded.net/licenses/bsd_license
6
+
7
+ import sys
8
+ import os
9
+
10
+ PY3 = sys.version_info[0] >= 3
11
+ if PY3:
12
+ text_type = str
13
+ binary_type = bytes
14
+ if os.supports_bytes_environ:
15
+ # environb will be unset under Windows, but then again we're not supposed to use it.
16
+ environb = os.environb
17
+ else:
18
+ text_type = unicode # noqa: F821
19
+ binary_type = str
20
+ environb = os.environ
21
+
22
+ try:
23
+ from collections.abc import Iterable as iterable_type
24
+ except ImportError:
25
+ from collections import Iterable as iterable_type # noqa: F401
rtme/lib/python3.10/site-packages/send2trash/plat_gio.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Virgil Dupras
2
+
3
+ # This software is licensed under the "BSD" License as described in the "LICENSE" file,
4
+ # which should be included with this package. The terms are also available at
5
+ # http://www.hardcoded.net/licenses/bsd_license
6
+
7
+ from gi.repository import GObject, Gio
8
+ from send2trash.exceptions import TrashPermissionError
9
+ from send2trash.util import preprocess_paths
10
+
11
+
12
+ def send2trash(paths):
13
+ paths = preprocess_paths(paths)
14
+ for path in paths:
15
+ try:
16
+ f = Gio.File.new_for_path(path)
17
+ f.trash(cancellable=None)
18
+ except GObject.GError as e:
19
+ if e.code == Gio.IOErrorEnum.NOT_SUPPORTED:
20
+ # We get here if we can't create a trash directory on the same
21
+ # device. I don't know if other errors can result in NOT_SUPPORTED.
22
+ raise TrashPermissionError("")
23
+ raise OSError(e.message)
rtme/lib/python3.10/site-packages/send2trash/plat_other.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Virgil Dupras
2
+
3
+ # This software is licensed under the "BSD" License as described in the "LICENSE" file,
4
+ # which should be included with this package. The terms are also available at
5
+ # http://www.hardcoded.net/licenses/bsd_license
6
+
7
+ # This is a reimplementation of plat_other.py with reference to the
8
+ # freedesktop.org trash specification:
9
+ # [1] http://www.freedesktop.org/wiki/Specifications/trash-spec
10
+ # [2] http://www.ramendik.ru/docs/trashspec.html
11
+ # See also:
12
+ # [3] http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
13
+ #
14
+ # For external volumes this implementation will raise an exception if it can't
15
+ # find or create the user's trash directory.
16
+
17
+ from __future__ import unicode_literals
18
+
19
+ import errno
20
+ import sys
21
+ import os
22
+ import shutil
23
+ import os.path as op
24
+ from datetime import datetime
25
+ import stat
26
+
27
+ try:
28
+ from urllib.parse import quote
29
+ except ImportError:
30
+ # Python 2
31
+ from urllib import quote
32
+
33
+ from send2trash.compat import text_type, environb
34
+ from send2trash.util import preprocess_paths
35
+ from send2trash.exceptions import TrashPermissionError
36
+
37
+ try:
38
+ fsencode = os.fsencode # Python 3
39
+ fsdecode = os.fsdecode
40
+ except AttributeError:
41
+
42
+ def fsencode(u): # Python 2
43
+ return u.encode(sys.getfilesystemencoding())
44
+
45
+ def fsdecode(b):
46
+ return b.decode(sys.getfilesystemencoding())
47
+
48
+ # The Python 3 versions are a bit smarter, handling surrogate escapes,
49
+ # but these should work in most cases.
50
+
51
+ FILES_DIR = b"files"
52
+ INFO_DIR = b"info"
53
+ INFO_SUFFIX = b".trashinfo"
54
+
55
+ # Default of ~/.local/share [3]
56
+ XDG_DATA_HOME = op.expanduser(environb.get(b"XDG_DATA_HOME", b"~/.local/share"))
57
+ HOMETRASH_B = op.join(XDG_DATA_HOME, b"Trash")
58
+ HOMETRASH = fsdecode(HOMETRASH_B)
59
+
60
+ uid = os.getuid()
61
+ TOPDIR_TRASH = b".Trash"
62
+ TOPDIR_FALLBACK = b".Trash-" + text_type(uid).encode("ascii")
63
+
64
+
65
+ def is_parent(parent, path):
66
+ path = op.realpath(path) # In case it's a symlink
67
+ if isinstance(path, text_type):
68
+ path = fsencode(path)
69
+ parent = op.realpath(parent)
70
+ if isinstance(parent, text_type):
71
+ parent = fsencode(parent)
72
+ return path.startswith(parent)
73
+
74
+
75
+ def format_date(date):
76
+ return date.strftime("%Y-%m-%dT%H:%M:%S")
77
+
78
+
79
+ def info_for(src, topdir):
80
+ # ...it MUST not include a ".." directory, and for files not "under" that
81
+ # directory, absolute pathnames must be used. [2]
82
+ if topdir is None or not is_parent(topdir, src):
83
+ src = op.abspath(src)
84
+ else:
85
+ src = op.relpath(src, topdir)
86
+
87
+ info = "[Trash Info]\n"
88
+ info += "Path=" + quote(src) + "\n"
89
+ info += "DeletionDate=" + format_date(datetime.now()) + "\n"
90
+ return info
91
+
92
+
93
+ def check_create(dir):
94
+ # use 0700 for paths [3]
95
+ if not op.exists(dir):
96
+ os.makedirs(dir, 0o700)
97
+
98
+
99
+ def trash_move(src, dst, topdir=None, cross_dev=False):
100
+ filename = op.basename(src)
101
+ filespath = op.join(dst, FILES_DIR)
102
+ infopath = op.join(dst, INFO_DIR)
103
+ base_name, ext = op.splitext(filename)
104
+
105
+ counter = 0
106
+ destname = filename
107
+ while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)):
108
+ counter += 1
109
+ destname = base_name + b" " + text_type(counter).encode("ascii") + ext
110
+
111
+ check_create(filespath)
112
+ check_create(infopath)
113
+
114
+ with open(op.join(infopath, destname + INFO_SUFFIX), "w") as f:
115
+ f.write(info_for(src, topdir))
116
+ destpath = op.join(filespath, destname)
117
+ if cross_dev:
118
+ shutil.move(fsdecode(src), fsdecode(destpath))
119
+ else:
120
+ os.rename(src, destpath)
121
+
122
+
123
+ def find_mount_point(path):
124
+ # Even if something's wrong, "/" is a mount point, so the loop will exit.
125
+ # Use realpath in case it's a symlink
126
+ path = op.realpath(path) # Required to avoid infinite loop
127
+ while not op.ismount(path): # Note ismount() does not always detect mounts
128
+ path = op.split(path)[0]
129
+ return path
130
+
131
+
132
+ def find_ext_volume_global_trash(volume_root):
133
+ # from [2] Trash directories (1) check for a .Trash dir with the right
134
+ # permissions set.
135
+ trash_dir = op.join(volume_root, TOPDIR_TRASH)
136
+ if not op.exists(trash_dir):
137
+ return None
138
+
139
+ mode = os.lstat(trash_dir).st_mode
140
+ # vol/.Trash must be a directory, cannot be a symlink, and must have the
141
+ # sticky bit set.
142
+ if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX):
143
+ return None
144
+
145
+ trash_dir = op.join(trash_dir, text_type(uid).encode("ascii"))
146
+ try:
147
+ check_create(trash_dir)
148
+ except OSError:
149
+ return None
150
+ return trash_dir
151
+
152
+
153
+ def find_ext_volume_fallback_trash(volume_root):
154
+ # from [2] Trash directories (1) create a .Trash-$uid dir.
155
+ trash_dir = op.join(volume_root, TOPDIR_FALLBACK)
156
+ # Try to make the directory, if we lack permission, raise TrashPermissionError
157
+ try:
158
+ check_create(trash_dir)
159
+ except OSError as e:
160
+ if e.errno == errno.EACCES:
161
+ raise TrashPermissionError(e.filename)
162
+ raise
163
+ return trash_dir
164
+
165
+
166
+ def find_ext_volume_trash(volume_root):
167
+ trash_dir = find_ext_volume_global_trash(volume_root)
168
+ if trash_dir is None:
169
+ trash_dir = find_ext_volume_fallback_trash(volume_root)
170
+ return trash_dir
171
+
172
+
173
+ # Pull this out so it's easy to stub (to avoid stubbing lstat itself)
174
+ def get_dev(path):
175
+ return os.lstat(path).st_dev
176
+
177
+
178
+ def send2trash(paths):
179
+ paths = preprocess_paths(paths)
180
+ for path in paths:
181
+ if isinstance(path, text_type):
182
+ path_b = fsencode(path)
183
+ elif isinstance(path, bytes):
184
+ path_b = path
185
+ else:
186
+ raise TypeError("str, bytes or PathLike expected, not %r" % type(path))
187
+
188
+ if not op.exists(path_b):
189
+ raise OSError(errno.ENOENT, "File not found: %s" % path)
190
+ # ...should check whether the user has the necessary permissions to delete
191
+ # it, before starting the trashing operation itself. [2]
192
+ if not os.access(path_b, os.W_OK):
193
+ raise OSError(errno.EACCES, "Permission denied: %s" % path)
194
+
195
+ path_dev = get_dev(path_b)
196
+ # If XDG_DATA_HOME or HOMETRASH do not yet exist we need to stat the
197
+ # home directory, and these paths will be created further on if needed.
198
+ trash_dev = get_dev(op.expanduser(b"~"))
199
+
200
+ # if the file to be trashed is on the same device as HOMETRASH we
201
+ # want to move it there.
202
+ if path_dev == trash_dev:
203
+ topdir = XDG_DATA_HOME
204
+ dest_trash = HOMETRASH_B
205
+ else:
206
+ topdir = find_mount_point(path_b)
207
+ trash_dev = get_dev(topdir)
208
+ if trash_dev != path_dev:
209
+ raise OSError("Couldn't find mount point for %s" % path)
210
+ dest_trash = find_ext_volume_trash(topdir)
211
+ try:
212
+ trash_move(path_b, dest_trash, topdir)
213
+ except OSError as error:
214
+ # Cross link errors default back to HOMETRASH
215
+ if error.errno == errno.EXDEV:
216
+ trash_move(path_b, HOMETRASH_B, XDG_DATA_HOME, cross_dev=True)
217
+ else:
218
+ raise
rtme/lib/python3.10/site-packages/torchmetrics/classification/f_beta.py ADDED
@@ -0,0 +1,1221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.stat_scores import BinaryStatScores, MulticlassStatScores, MultilabelStatScores
22
+ from torchmetrics.functional.classification.f_beta import (
23
+ _binary_fbeta_score_arg_validation,
24
+ _fbeta_reduce,
25
+ _multiclass_fbeta_score_arg_validation,
26
+ _multilabel_fbeta_score_arg_validation,
27
+ )
28
+ from torchmetrics.metric import Metric
29
+ from torchmetrics.utilities.enums import ClassificationTask
30
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
31
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
32
+
33
+ if not _MATPLOTLIB_AVAILABLE:
34
+ __doctest_skip__ = [
35
+ "BinaryFBetaScore.plot",
36
+ "MulticlassFBetaScore.plot",
37
+ "MultilabelFBetaScore.plot",
38
+ "BinaryF1Score.plot",
39
+ "MulticlassF1Score.plot",
40
+ "MultilabelF1Score.plot",
41
+ ]
42
+
43
+
44
+ class BinaryFBetaScore(BinaryStatScores):
45
+ r"""Compute `F-score`_ metric for binary tasks.
46
+
47
+ .. math::
48
+ F_{\beta} = (1 + \beta^2) * \frac{\text{precision} * \text{recall}}
49
+ {(\beta^2 * \text{precision}) + \text{recall}}
50
+
51
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
52
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
53
+ positives and false negatives respectively. If this case is encountered a score of `zero_division`
54
+ (0 or 1, default is 0) is returned.
55
+
56
+ As input to ``forward`` and ``update`` the metric accepts the following input:
57
+
58
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor or float tensor of shape ``(N, ...)``. If preds is a floating
59
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
60
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
61
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
62
+
63
+ As output to ``forward`` and ``compute`` the metric returns the following output:
64
+
65
+ - ``bfbs`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``multidim_average`` argument:
66
+
67
+ - If ``multidim_average`` is set to ``global`` the output will be a scalar tensor
68
+ - If ``multidim_average`` is set to ``samplewise`` the output will be a tensor of shape ``(N,)`` consisting of
69
+ a scalar value per sample.
70
+
71
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
72
+ which the reduction will then be applied over instead of the sample dimension ``N``.
73
+
74
+ Args:
75
+ beta: Weighting between precision and recall in calculation. Setting to 1 corresponds to equal weight
76
+ threshold: Threshold for transforming probability to binary {0,1} predictions
77
+ multidim_average:
78
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
79
+
80
+ - ``global``: Additional dimensions are flatted along the batch dimension
81
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
82
+ The statistics in this case are calculated over the additional dimensions.
83
+
84
+ ignore_index:
85
+ Specifies a target value that is ignored and does not contribute to the metric calculation
86
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
87
+ Set to ``False`` for faster computations.
88
+ zero_division: Should be `0` or `1`. The value returned when
89
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
90
+
91
+ Example (preds is int tensor):
92
+ >>> from torch import tensor
93
+ >>> from torchmetrics.classification import BinaryFBetaScore
94
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
95
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
96
+ >>> metric = BinaryFBetaScore(beta=2.0)
97
+ >>> metric(preds, target)
98
+ tensor(0.6667)
99
+
100
+ Example (preds is float tensor):
101
+ >>> from torchmetrics.classification import BinaryFBetaScore
102
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
103
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
104
+ >>> metric = BinaryFBetaScore(beta=2.0)
105
+ >>> metric(preds, target)
106
+ tensor(0.6667)
107
+
108
+ Example (multidim tensors):
109
+ >>> from torchmetrics.classification import BinaryFBetaScore
110
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
111
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
112
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
113
+ >>> metric = BinaryFBetaScore(beta=2.0, multidim_average='samplewise')
114
+ >>> metric(preds, target)
115
+ tensor([0.5882, 0.0000])
116
+
117
+ """
118
+
119
+ is_differentiable: bool = False
120
+ higher_is_better: Optional[bool] = True
121
+ full_state_update: bool = False
122
+ plot_lower_bound: float = 0.0
123
+ plot_upper_bound: float = 1.0
124
+
125
+ def __init__(
126
+ self,
127
+ beta: float,
128
+ threshold: float = 0.5,
129
+ multidim_average: Literal["global", "samplewise"] = "global",
130
+ ignore_index: Optional[int] = None,
131
+ validate_args: bool = True,
132
+ zero_division: float = 0,
133
+ **kwargs: Any,
134
+ ) -> None:
135
+ super().__init__(
136
+ threshold=threshold,
137
+ multidim_average=multidim_average,
138
+ ignore_index=ignore_index,
139
+ validate_args=False,
140
+ **kwargs,
141
+ )
142
+ if validate_args:
143
+ _binary_fbeta_score_arg_validation(beta, threshold, multidim_average, ignore_index, zero_division)
144
+ self.validate_args = validate_args
145
+ self.zero_division = zero_division
146
+ self.beta = beta
147
+
148
+ def compute(self) -> Tensor:
149
+ """Compute metric."""
150
+ tp, fp, tn, fn = self._final_state()
151
+ return _fbeta_reduce(
152
+ tp,
153
+ fp,
154
+ tn,
155
+ fn,
156
+ self.beta,
157
+ average="binary",
158
+ multidim_average=self.multidim_average,
159
+ zero_division=self.zero_division,
160
+ )
161
+
162
+ def plot(
163
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
164
+ ) -> _PLOT_OUT_TYPE:
165
+ """Plot a single or multiple values from the metric.
166
+
167
+ Args:
168
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
169
+ If no value is provided, will automatically call `metric.compute` and plot that result.
170
+ ax: An matplotlib axis object. If provided will add plot to that axis
171
+
172
+ Returns:
173
+ Figure object and Axes object
174
+
175
+ Raises:
176
+ ModuleNotFoundError:
177
+ If `matplotlib` is not installed
178
+
179
+ .. plot::
180
+ :scale: 75
181
+
182
+ >>> from torch import rand, randint
183
+ >>> # Example plotting a single value
184
+ >>> from torchmetrics.classification import BinaryFBetaScore
185
+ >>> metric = BinaryFBetaScore(beta=2.0)
186
+ >>> metric.update(rand(10), randint(2,(10,)))
187
+ >>> fig_, ax_ = metric.plot()
188
+
189
+ .. plot::
190
+ :scale: 75
191
+
192
+ >>> from torch import rand, randint
193
+ >>> # Example plotting multiple values
194
+ >>> from torchmetrics.classification import BinaryFBetaScore
195
+ >>> metric = BinaryFBetaScore(beta=2.0)
196
+ >>> values = [ ]
197
+ >>> for _ in range(10):
198
+ ... values.append(metric(rand(10), randint(2,(10,))))
199
+ >>> fig_, ax_ = metric.plot(values)
200
+
201
+ """
202
+ return self._plot(val, ax)
203
+
204
+
205
+ class MulticlassFBetaScore(MulticlassStatScores):
206
+ r"""Compute `F-score`_ metric for multiclass tasks.
207
+
208
+ .. math::
209
+ F_{\beta} = (1 + \beta^2) * \frac{\text{precision} * \text{recall}}
210
+ {(\beta^2 * \text{precision}) + \text{recall}}
211
+
212
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
213
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
214
+ positives and false negatives respectively. If this case is encountered for any class, the metric for that class
215
+ will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be affected in turn.
216
+
217
+ As input to ``forward`` and ``update`` the metric accepts the following input:
218
+
219
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
220
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
221
+ probabilities/logits into an int tensor.
222
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
223
+
224
+ As output to ``forward`` and ``compute`` the metric returns the following output:
225
+
226
+ - ``mcfbs`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
227
+ ``multidim_average`` arguments:
228
+
229
+ - If ``multidim_average`` is set to ``global``:
230
+
231
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
232
+ - If ``average=None/'none'``, the shape will be ``(C,)``
233
+
234
+ - If ``multidim_average`` is set to ``samplewise``:
235
+
236
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
237
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
238
+
239
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
240
+ which the reduction will then be applied over instead of the sample dimension ``N``.
241
+
242
+ Args:
243
+ beta: Weighting between precision and recall in calculation. Setting to 1 corresponds to equal weight
244
+ num_classes: Integer specifying the number of classes
245
+ average:
246
+ Defines the reduction that is applied over labels. Should be one of the following:
247
+
248
+ - ``micro``: Sum statistics over all labels
249
+ - ``macro``: Calculate statistics for each label and average them
250
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
251
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
252
+ top_k:
253
+
254
+ Number of highest probability or logit score predictions considered to find the correct label.
255
+ Only works when ``preds`` contain probabilities/logits.
256
+ multidim_average:
257
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
258
+
259
+ - ``global``: Additional dimensions are flatted along the batch dimension
260
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
261
+ The statistics in this case are calculated over the additional dimensions.
262
+
263
+ ignore_index:
264
+ Specifies a target value that is ignored and does not contribute to the metric calculation
265
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
266
+ Set to ``False`` for faster computations.
267
+ zero_division: Should be `0` or `1`. The value returned when
268
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
269
+
270
+ Example (preds is int tensor):
271
+ >>> from torch import tensor
272
+ >>> from torchmetrics.classification import MulticlassFBetaScore
273
+ >>> target = tensor([2, 1, 0, 0])
274
+ >>> preds = tensor([2, 1, 0, 1])
275
+ >>> metric = MulticlassFBetaScore(beta=2.0, num_classes=3)
276
+ >>> metric(preds, target)
277
+ tensor(0.7963)
278
+ >>> mcfbs = MulticlassFBetaScore(beta=2.0, num_classes=3, average=None)
279
+ >>> mcfbs(preds, target)
280
+ tensor([0.5556, 0.8333, 1.0000])
281
+
282
+ Example (preds is float tensor):
283
+ >>> from torchmetrics.classification import MulticlassFBetaScore
284
+ >>> target = tensor([2, 1, 0, 0])
285
+ >>> preds = tensor([[0.16, 0.26, 0.58],
286
+ ... [0.22, 0.61, 0.17],
287
+ ... [0.71, 0.09, 0.20],
288
+ ... [0.05, 0.82, 0.13]])
289
+ >>> metric = MulticlassFBetaScore(beta=2.0, num_classes=3)
290
+ >>> metric(preds, target)
291
+ tensor(0.7963)
292
+ >>> mcfbs = MulticlassFBetaScore(beta=2.0, num_classes=3, average=None)
293
+ >>> mcfbs(preds, target)
294
+ tensor([0.5556, 0.8333, 1.0000])
295
+
296
+ Example (multidim tensors):
297
+ >>> from torchmetrics.classification import MulticlassFBetaScore
298
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
299
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
300
+ >>> metric = MulticlassFBetaScore(beta=2.0, num_classes=3, multidim_average='samplewise')
301
+ >>> metric(preds, target)
302
+ tensor([0.4697, 0.2706])
303
+ >>> mcfbs = MulticlassFBetaScore(beta=2.0, num_classes=3, multidim_average='samplewise', average=None)
304
+ >>> mcfbs(preds, target)
305
+ tensor([[0.9091, 0.0000, 0.5000],
306
+ [0.0000, 0.3571, 0.4545]])
307
+
308
+ """
309
+
310
+ is_differentiable: bool = False
311
+ higher_is_better: Optional[bool] = True
312
+ full_state_update: bool = False
313
+ plot_lower_bound: float = 0.0
314
+ plot_upper_bound: float = 1.0
315
+ plot_legend_name: str = "Class"
316
+
317
+ def __init__(
318
+ self,
319
+ beta: float,
320
+ num_classes: int,
321
+ top_k: int = 1,
322
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
323
+ multidim_average: Literal["global", "samplewise"] = "global",
324
+ ignore_index: Optional[int] = None,
325
+ validate_args: bool = True,
326
+ zero_division: float = 0,
327
+ **kwargs: Any,
328
+ ) -> None:
329
+ super().__init__(
330
+ num_classes=num_classes,
331
+ top_k=top_k,
332
+ average=average,
333
+ multidim_average=multidim_average,
334
+ ignore_index=ignore_index,
335
+ validate_args=False,
336
+ **kwargs,
337
+ )
338
+ if validate_args:
339
+ _multiclass_fbeta_score_arg_validation(
340
+ beta, num_classes, top_k, average, multidim_average, ignore_index, zero_division
341
+ )
342
+ self.validate_args = validate_args
343
+ self.zero_division = zero_division
344
+ self.beta = beta
345
+
346
+ def compute(self) -> Tensor:
347
+ """Compute metric."""
348
+ tp, fp, tn, fn = self._final_state()
349
+ return _fbeta_reduce(
350
+ tp,
351
+ fp,
352
+ tn,
353
+ fn,
354
+ self.beta,
355
+ average=self.average,
356
+ multidim_average=self.multidim_average,
357
+ zero_division=self.zero_division,
358
+ )
359
+
360
+ def plot(
361
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
362
+ ) -> _PLOT_OUT_TYPE:
363
+ """Plot a single or multiple values from the metric.
364
+
365
+ Args:
366
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
367
+ If no value is provided, will automatically call `metric.compute` and plot that result.
368
+ ax: An matplotlib axis object. If provided will add plot to that axis
369
+
370
+ Returns:
371
+ Figure object and Axes object
372
+
373
+ Raises:
374
+ ModuleNotFoundError:
375
+ If `matplotlib` is not installed
376
+
377
+ .. plot::
378
+ :scale: 75
379
+
380
+ >>> from torch import randint
381
+ >>> # Example plotting a single value per class
382
+ >>> from torchmetrics.classification import MulticlassFBetaScore
383
+ >>> metric = MulticlassFBetaScore(num_classes=3, beta=2.0, average=None)
384
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
385
+ >>> fig_, ax_ = metric.plot()
386
+
387
+ .. plot::
388
+ :scale: 75
389
+
390
+ >>> from torch import randint
391
+ >>> # Example plotting a multiple values per class
392
+ >>> from torchmetrics.classification import MulticlassFBetaScore
393
+ >>> metric = MulticlassFBetaScore(num_classes=3, beta=2.0, average=None)
394
+ >>> values = []
395
+ >>> for _ in range(20):
396
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
397
+ >>> fig_, ax_ = metric.plot(values)
398
+
399
+ """
400
+ return self._plot(val, ax)
401
+
402
+
403
+ class MultilabelFBetaScore(MultilabelStatScores):
404
+ r"""Compute `F-score`_ metric for multilabel tasks.
405
+
406
+ .. math::
407
+ F_{\beta} = (1 + \beta^2) * \frac{\text{precision} * \text{recall}}
408
+ {(\beta^2 * \text{precision}) + \text{recall}}
409
+
410
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
411
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
412
+ positives and false negatives respectively. If this case is encountered for any label, the metric for that label
413
+ will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be affected in turn.
414
+
415
+ As input to ``forward`` and ``update`` the metric accepts the following input:
416
+
417
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
418
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
419
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
420
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``.
421
+
422
+ As output to ``forward`` and ``compute`` the metric returns the following output:
423
+
424
+ - ``mlfbs`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
425
+ ``multidim_average`` arguments:
426
+
427
+ - If ``multidim_average`` is set to ``global``:
428
+
429
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
430
+ - If ``average=None/'none'``, the shape will be ``(C,)``
431
+
432
+ - If ``multidim_average`` is set to ``samplewise``:
433
+
434
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
435
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
436
+
437
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
438
+ which the reduction will then be applied over instead of the sample dimension ``N``.
439
+
440
+ Args:
441
+ beta: Weighting between precision and recall in calculation. Setting to 1 corresponds to equal weight
442
+ num_labels: Integer specifying the number of labels
443
+ threshold: Threshold for transforming probability to binary (0,1) predictions
444
+ average:
445
+ Defines the reduction that is applied over labels. Should be one of the following:
446
+
447
+ - ``micro``: Sum statistics over all labels
448
+ - ``macro``: Calculate statistics for each label and average them
449
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
450
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
451
+
452
+ multidim_average:
453
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
454
+
455
+ - ``global``: Additional dimensions are flatted along the batch dimension
456
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
457
+ The statistics in this case are calculated over the additional dimensions.
458
+
459
+ ignore_index:
460
+ Specifies a target value that is ignored and does not contribute to the metric calculation
461
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
462
+ Set to ``False`` for faster computations.
463
+ zero_division: Should be `0` or `1`. The value returned when
464
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
465
+
466
+ Example (preds is int tensor):
467
+ >>> from torch import tensor
468
+ >>> from torchmetrics.classification import MultilabelFBetaScore
469
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
470
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
471
+ >>> metric = MultilabelFBetaScore(beta=2.0, num_labels=3)
472
+ >>> metric(preds, target)
473
+ tensor(0.6111)
474
+ >>> mlfbs = MultilabelFBetaScore(beta=2.0, num_labels=3, average=None)
475
+ >>> mlfbs(preds, target)
476
+ tensor([1.0000, 0.0000, 0.8333])
477
+
478
+ Example (preds is float tensor):
479
+ >>> from torchmetrics.classification import MultilabelFBetaScore
480
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
481
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
482
+ >>> metric = MultilabelFBetaScore(beta=2.0, num_labels=3)
483
+ >>> metric(preds, target)
484
+ tensor(0.6111)
485
+ >>> mlfbs = MultilabelFBetaScore(beta=2.0, num_labels=3, average=None)
486
+ >>> mlfbs(preds, target)
487
+ tensor([1.0000, 0.0000, 0.8333])
488
+
489
+ Example (multidim tensors):
490
+ >>> from torchmetrics.classification import MultilabelFBetaScore
491
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
492
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
493
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
494
+ >>> metric = MultilabelFBetaScore(num_labels=3, beta=2.0, multidim_average='samplewise')
495
+ >>> metric(preds, target)
496
+ tensor([0.5556, 0.0000])
497
+ >>> mlfbs = MultilabelFBetaScore(num_labels=3, beta=2.0, multidim_average='samplewise', average=None)
498
+ >>> mlfbs(preds, target)
499
+ tensor([[0.8333, 0.8333, 0.0000],
500
+ [0.0000, 0.0000, 0.0000]])
501
+
502
+ """
503
+
504
+ is_differentiable: bool = False
505
+ higher_is_better: Optional[bool] = True
506
+ full_state_update: bool = False
507
+ plot_lower_bound: float = 0.0
508
+ plot_upper_bound: float = 1.0
509
+ plot_legend_name: str = "Label"
510
+
511
+ def __init__(
512
+ self,
513
+ beta: float,
514
+ num_labels: int,
515
+ threshold: float = 0.5,
516
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
517
+ multidim_average: Literal["global", "samplewise"] = "global",
518
+ ignore_index: Optional[int] = None,
519
+ validate_args: bool = True,
520
+ zero_division: float = 0,
521
+ **kwargs: Any,
522
+ ) -> None:
523
+ super().__init__(
524
+ num_labels=num_labels,
525
+ threshold=threshold,
526
+ average=average,
527
+ multidim_average=multidim_average,
528
+ ignore_index=ignore_index,
529
+ validate_args=False,
530
+ **kwargs,
531
+ )
532
+ if validate_args:
533
+ _multilabel_fbeta_score_arg_validation(
534
+ beta, num_labels, threshold, average, multidim_average, ignore_index, zero_division
535
+ )
536
+ self.validate_args = validate_args
537
+ self.zero_division = zero_division
538
+ self.beta = beta
539
+
540
+ def compute(self) -> Tensor:
541
+ """Compute metric."""
542
+ tp, fp, tn, fn = self._final_state()
543
+ return _fbeta_reduce(
544
+ tp,
545
+ fp,
546
+ tn,
547
+ fn,
548
+ self.beta,
549
+ average=self.average,
550
+ multidim_average=self.multidim_average,
551
+ multilabel=True,
552
+ zero_division=self.zero_division,
553
+ )
554
+
555
+ def plot(
556
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
557
+ ) -> _PLOT_OUT_TYPE:
558
+ """Plot a single or multiple values from the metric.
559
+
560
+ Args:
561
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
562
+ If no value is provided, will automatically call `metric.compute` and plot that result.
563
+ ax: An matplotlib axis object. If provided will add plot to that axis
564
+
565
+ Returns:
566
+ Figure and Axes object
567
+
568
+ Raises:
569
+ ModuleNotFoundError:
570
+ If `matplotlib` is not installed
571
+
572
+ .. plot::
573
+ :scale: 75
574
+
575
+ >>> from torch import rand, randint
576
+ >>> # Example plotting a single value
577
+ >>> from torchmetrics.classification import MultilabelFBetaScore
578
+ >>> metric = MultilabelFBetaScore(num_labels=3, beta=2.0)
579
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
580
+ >>> fig_, ax_ = metric.plot()
581
+
582
+ .. plot::
583
+ :scale: 75
584
+
585
+ >>> from torch import rand, randint
586
+ >>> # Example plotting multiple values
587
+ >>> from torchmetrics.classification import MultilabelFBetaScore
588
+ >>> metric = MultilabelFBetaScore(num_labels=3, beta=2.0)
589
+ >>> values = [ ]
590
+ >>> for _ in range(10):
591
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
592
+ >>> fig_, ax_ = metric.plot(values)
593
+
594
+ """
595
+ return self._plot(val, ax)
596
+
597
+
598
+ class BinaryF1Score(BinaryFBetaScore):
599
+ r"""Compute F-1 score for binary tasks.
600
+
601
+ .. math::
602
+ F_{1} = 2\frac{\text{precision} * \text{recall}}{(\text{precision}) + \text{recall}}
603
+
604
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
605
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
606
+ positives and false negatives respectively. If this case is encountered a score of `zero_division`
607
+ (0 or 1, default is 0) is returned.
608
+
609
+ As input to ``forward`` and ``update`` the metric accepts the following input:
610
+
611
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, ...)``. If preds is a floating point
612
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per
613
+ element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
614
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
615
+
616
+ As output to ``forward`` and ``compute`` the metric returns the following output:
617
+
618
+ - ``bf1s`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``multidim_average`` argument:
619
+
620
+ - If ``multidim_average`` is set to ``global``, the metric returns a scalar value.
621
+ - If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of a scalar
622
+ value per sample.
623
+
624
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
625
+ which the reduction will then be applied over instead of the sample dimension ``N``.
626
+
627
+ Args:
628
+ threshold: Threshold for transforming probability to binary {0,1} predictions
629
+ multidim_average:
630
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
631
+
632
+ - ``global``: Additional dimensions are flatted along the batch dimension
633
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
634
+ The statistics in this case are calculated over the additional dimensions.
635
+
636
+ ignore_index:
637
+ Specifies a target value that is ignored and does not contribute to the metric calculation
638
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
639
+ Set to ``False`` for faster computations.
640
+ zero_division: Should be `0` or `1`. The value returned when
641
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
642
+
643
+ Example (preds is int tensor):
644
+ >>> from torch import tensor
645
+ >>> from torchmetrics.classification import BinaryF1Score
646
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
647
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
648
+ >>> metric = BinaryF1Score()
649
+ >>> metric(preds, target)
650
+ tensor(0.6667)
651
+
652
+ Example (preds is float tensor):
653
+ >>> from torchmetrics.classification import BinaryF1Score
654
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
655
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
656
+ >>> metric = BinaryF1Score()
657
+ >>> metric(preds, target)
658
+ tensor(0.6667)
659
+
660
+ Example (multidim tensors):
661
+ >>> from torchmetrics.classification import BinaryF1Score
662
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
663
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
664
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
665
+ >>> metric = BinaryF1Score(multidim_average='samplewise')
666
+ >>> metric(preds, target)
667
+ tensor([0.5000, 0.0000])
668
+
669
+ """
670
+
671
+ is_differentiable: bool = False
672
+ higher_is_better: Optional[bool] = True
673
+ full_state_update: bool = False
674
+ plot_lower_bound: float = 0.0
675
+ plot_upper_bound: float = 1.0
676
+
677
+ def __init__(
678
+ self,
679
+ threshold: float = 0.5,
680
+ multidim_average: Literal["global", "samplewise"] = "global",
681
+ ignore_index: Optional[int] = None,
682
+ validate_args: bool = True,
683
+ zero_division: float = 0,
684
+ **kwargs: Any,
685
+ ) -> None:
686
+ super().__init__(
687
+ beta=1.0,
688
+ threshold=threshold,
689
+ multidim_average=multidim_average,
690
+ ignore_index=ignore_index,
691
+ validate_args=validate_args,
692
+ zero_division=zero_division,
693
+ **kwargs,
694
+ )
695
+
696
+ def plot(
697
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
698
+ ) -> _PLOT_OUT_TYPE:
699
+ """Plot a single or multiple values from the metric.
700
+
701
+ Args:
702
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
703
+ If no value is provided, will automatically call `metric.compute` and plot that result.
704
+ ax: An matplotlib axis object. If provided will add plot to that axis
705
+
706
+ Returns:
707
+ Figure object and Axes object
708
+
709
+ Raises:
710
+ ModuleNotFoundError:
711
+ If `matplotlib` is not installed
712
+
713
+ .. plot::
714
+ :scale: 75
715
+
716
+ >>> from torch import rand, randint
717
+ >>> # Example plotting a single value
718
+ >>> from torchmetrics.classification import BinaryF1Score
719
+ >>> metric = BinaryF1Score()
720
+ >>> metric.update(rand(10), randint(2,(10,)))
721
+ >>> fig_, ax_ = metric.plot()
722
+
723
+ .. plot::
724
+ :scale: 75
725
+
726
+ >>> from torch import rand, randint
727
+ >>> # Example plotting multiple values
728
+ >>> from torchmetrics.classification import BinaryF1Score
729
+ >>> metric = BinaryF1Score()
730
+ >>> values = [ ]
731
+ >>> for _ in range(10):
732
+ ... values.append(metric(rand(10), randint(2,(10,))))
733
+ >>> fig_, ax_ = metric.plot(values)
734
+
735
+ """
736
+ return self._plot(val, ax)
737
+
738
+
739
+ class MulticlassF1Score(MulticlassFBetaScore):
740
+ r"""Compute F-1 score for multiclass tasks.
741
+
742
+ .. math::
743
+ F_{1} = 2\frac{\text{precision} * \text{recall}}{(\text{precision}) + \text{recall}}
744
+
745
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
746
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
747
+ positives and false negatives respectively. If this case is encountered for any class, the metric for that class
748
+ will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be affected in turn.
749
+
750
+ As input to ``forward`` and ``update`` the metric accepts the following input:
751
+
752
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
753
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
754
+ probabilities/logits into an int tensor.
755
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
756
+
757
+ As output to ``forward`` and ``compute`` the metric returns the following output:
758
+
759
+ - ``mcf1s`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
760
+ ``multidim_average`` arguments:
761
+
762
+ - If ``multidim_average`` is set to ``global``:
763
+
764
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
765
+ - If ``average=None/'none'``, the shape will be ``(C,)``
766
+
767
+ - If ``multidim_average`` is set to ``samplewise``:
768
+
769
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
770
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
771
+
772
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
773
+ which the reduction will then be applied over instead of the sample dimension ``N``.
774
+
775
+ Args:
776
+ preds: Tensor with predictions
777
+ target: Tensor with true labels
778
+ num_classes: Integer specifying the number of classes
779
+ average:
780
+ Defines the reduction that is applied over labels. Should be one of the following:
781
+
782
+ - ``micro``: Sum statistics over all labels
783
+ - ``macro``: Calculate statistics for each label and average them
784
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
785
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
786
+ top_k:
787
+ Number of highest probability or logit score predictions considered to find the correct label.
788
+ Only works when ``preds`` contain probabilities/logits.
789
+ multidim_average:
790
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
791
+
792
+ - ``global``: Additional dimensions are flatted along the batch dimension
793
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
794
+ The statistics in this case are calculated over the additional dimensions.
795
+
796
+ ignore_index:
797
+ Specifies a target value that is ignored and does not contribute to the metric calculation
798
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
799
+ Set to ``False`` for faster computations.
800
+ zero_division: Should be `0` or `1`. The value returned when
801
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
802
+
803
+ Example (preds is int tensor):
804
+ >>> from torch import tensor
805
+ >>> from torchmetrics.classification import MulticlassF1Score
806
+ >>> target = tensor([2, 1, 0, 0])
807
+ >>> preds = tensor([2, 1, 0, 1])
808
+ >>> metric = MulticlassF1Score(num_classes=3)
809
+ >>> metric(preds, target)
810
+ tensor(0.7778)
811
+ >>> mcf1s = MulticlassF1Score(num_classes=3, average=None)
812
+ >>> mcf1s(preds, target)
813
+ tensor([0.6667, 0.6667, 1.0000])
814
+
815
+ Example (preds is float tensor):
816
+ >>> from torchmetrics.classification import MulticlassF1Score
817
+ >>> target = tensor([2, 1, 0, 0])
818
+ >>> preds = tensor([[0.16, 0.26, 0.58],
819
+ ... [0.22, 0.61, 0.17],
820
+ ... [0.71, 0.09, 0.20],
821
+ ... [0.05, 0.82, 0.13]])
822
+ >>> metric = MulticlassF1Score(num_classes=3)
823
+ >>> metric(preds, target)
824
+ tensor(0.7778)
825
+ >>> mcf1s = MulticlassF1Score(num_classes=3, average=None)
826
+ >>> mcf1s(preds, target)
827
+ tensor([0.6667, 0.6667, 1.0000])
828
+
829
+ Example (multidim tensors):
830
+ >>> from torchmetrics.classification import MulticlassF1Score
831
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
832
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
833
+ >>> metric = MulticlassF1Score(num_classes=3, multidim_average='samplewise')
834
+ >>> metric(preds, target)
835
+ tensor([0.4333, 0.2667])
836
+ >>> mcf1s = MulticlassF1Score(num_classes=3, multidim_average='samplewise', average=None)
837
+ >>> mcf1s(preds, target)
838
+ tensor([[0.8000, 0.0000, 0.5000],
839
+ [0.0000, 0.4000, 0.4000]])
840
+
841
+ """
842
+
843
+ is_differentiable: bool = False
844
+ higher_is_better: Optional[bool] = True
845
+ full_state_update: bool = False
846
+ plot_lower_bound: float = 0.0
847
+ plot_upper_bound: float = 1.0
848
+ plot_legend_name: str = "Class"
849
+
850
+ def __init__(
851
+ self,
852
+ num_classes: int,
853
+ top_k: int = 1,
854
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
855
+ multidim_average: Literal["global", "samplewise"] = "global",
856
+ ignore_index: Optional[int] = None,
857
+ validate_args: bool = True,
858
+ zero_division: float = 0,
859
+ **kwargs: Any,
860
+ ) -> None:
861
+ super().__init__(
862
+ beta=1.0,
863
+ num_classes=num_classes,
864
+ top_k=top_k,
865
+ average=average,
866
+ multidim_average=multidim_average,
867
+ ignore_index=ignore_index,
868
+ validate_args=validate_args,
869
+ zero_division=zero_division,
870
+ **kwargs,
871
+ )
872
+
873
+ def plot(
874
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
875
+ ) -> _PLOT_OUT_TYPE:
876
+ """Plot a single or multiple values from the metric.
877
+
878
+ Args:
879
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
880
+ If no value is provided, will automatically call `metric.compute` and plot that result.
881
+ ax: An matplotlib axis object. If provided will add plot to that axis
882
+
883
+ Returns:
884
+ Figure object and Axes object
885
+
886
+ Raises:
887
+ ModuleNotFoundError:
888
+ If `matplotlib` is not installed
889
+
890
+ .. plot::
891
+ :scale: 75
892
+
893
+ >>> from torch import randint
894
+ >>> # Example plotting a single value per class
895
+ >>> from torchmetrics.classification import MulticlassF1Score
896
+ >>> metric = MulticlassF1Score(num_classes=3, average=None)
897
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
898
+ >>> fig_, ax_ = metric.plot()
899
+
900
+ .. plot::
901
+ :scale: 75
902
+
903
+ >>> from torch import randint
904
+ >>> # Example plotting a multiple values per class
905
+ >>> from torchmetrics.classification import MulticlassF1Score
906
+ >>> metric = MulticlassF1Score(num_classes=3, average=None)
907
+ >>> values = []
908
+ >>> for _ in range(20):
909
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
910
+ >>> fig_, ax_ = metric.plot(values)
911
+
912
+ """
913
+ return self._plot(val, ax)
914
+
915
+
916
+ class MultilabelF1Score(MultilabelFBetaScore):
917
+ r"""Compute F-1 score for multilabel tasks.
918
+
919
+ .. math::
920
+ F_{1} = 2\frac{\text{precision} * \text{recall}}{(\text{precision}) + \text{recall}}
921
+
922
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
923
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
924
+ positives and false negatives respectively. If this case is encountered for any label, the metric for that label
925
+ will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be affected in turn.
926
+
927
+ As input to ``forward`` and ``update`` the metric accepts the following input:
928
+
929
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``.
930
+ If preds is a floating point tensor with values outside [0,1] range we consider the input to be logits and
931
+ will auto apply sigmoid per element. Additionally, we convert to int tensor with thresholding using the value
932
+ in ``threshold``.
933
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``.
934
+
935
+ As output to ``forward`` and ``compute`` the metric returns the following output:
936
+
937
+ - ``mlf1s`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
938
+ ``multidim_average`` arguments:
939
+
940
+ - If ``multidim_average`` is set to ``global``:
941
+
942
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
943
+ - If ``average=None/'none'``, the shape will be ``(C,)``
944
+
945
+ - If ``multidim_average`` is set to ``samplewise``:
946
+
947
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
948
+ - If ``average=None/'none'``, the shape will be ``(N, C)```
949
+
950
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
951
+ which the reduction will then be applied over instead of the sample dimension ``N``.
952
+
953
+ Args:
954
+ num_labels: Integer specifying the number of labels
955
+ threshold: Threshold for transforming probability to binary (0,1) predictions
956
+ average:
957
+ Defines the reduction that is applied over labels. Should be one of the following:
958
+
959
+ - ``micro``: Sum statistics over all labels
960
+ - ``macro``: Calculate statistics for each label and average them
961
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
962
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
963
+
964
+ multidim_average:
965
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
966
+
967
+ - ``global``: Additional dimensions are flatted along the batch dimension
968
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
969
+ The statistics in this case are calculated over the additional dimensions.
970
+
971
+ ignore_index:
972
+ Specifies a target value that is ignored and does not contribute to the metric calculation
973
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
974
+ Set to ``False`` for faster computations.
975
+ zero_division: Should be `0` or `1`. The value returned when
976
+ :math:`\text{TP} + \text{FP} = 0 \wedge \text{TP} + \text{FN} = 0`.
977
+
978
+ Example (preds is int tensor):
979
+ >>> from torch import tensor
980
+ >>> from torchmetrics.classification import MultilabelF1Score
981
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
982
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
983
+ >>> metric = MultilabelF1Score(num_labels=3)
984
+ >>> metric(preds, target)
985
+ tensor(0.5556)
986
+ >>> mlf1s = MultilabelF1Score(num_labels=3, average=None)
987
+ >>> mlf1s(preds, target)
988
+ tensor([1.0000, 0.0000, 0.6667])
989
+
990
+ Example (preds is float tensor):
991
+ >>> from torchmetrics.classification import MultilabelF1Score
992
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
993
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
994
+ >>> metric = MultilabelF1Score(num_labels=3)
995
+ >>> metric(preds, target)
996
+ tensor(0.5556)
997
+ >>> mlf1s = MultilabelF1Score(num_labels=3, average=None)
998
+ >>> mlf1s(preds, target)
999
+ tensor([1.0000, 0.0000, 0.6667])
1000
+
1001
+ Example (multidim tensors):
1002
+ >>> from torchmetrics.classification import MultilabelF1Score
1003
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
1004
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
1005
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
1006
+ >>> metric = MultilabelF1Score(num_labels=3, multidim_average='samplewise')
1007
+ >>> metric(preds, target)
1008
+ tensor([0.4444, 0.0000])
1009
+ >>> mlf1s = MultilabelF1Score(num_labels=3, multidim_average='samplewise', average=None)
1010
+ >>> mlf1s(preds, target)
1011
+ tensor([[0.6667, 0.6667, 0.0000],
1012
+ [0.0000, 0.0000, 0.0000]])
1013
+
1014
+ """
1015
+
1016
+ is_differentiable: bool = False
1017
+ higher_is_better: Optional[bool] = True
1018
+ full_state_update: bool = False
1019
+ plot_lower_bound: float = 0.0
1020
+ plot_upper_bound: float = 1.0
1021
+ plot_legend_name: str = "Label"
1022
+
1023
+ def __init__(
1024
+ self,
1025
+ num_labels: int,
1026
+ threshold: float = 0.5,
1027
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
1028
+ multidim_average: Literal["global", "samplewise"] = "global",
1029
+ ignore_index: Optional[int] = None,
1030
+ validate_args: bool = True,
1031
+ zero_division: float = 0,
1032
+ **kwargs: Any,
1033
+ ) -> None:
1034
+ super().__init__(
1035
+ beta=1.0,
1036
+ num_labels=num_labels,
1037
+ threshold=threshold,
1038
+ average=average,
1039
+ multidim_average=multidim_average,
1040
+ ignore_index=ignore_index,
1041
+ validate_args=validate_args,
1042
+ zero_division=zero_division,
1043
+ **kwargs,
1044
+ )
1045
+
1046
+ def plot(
1047
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
1048
+ ) -> _PLOT_OUT_TYPE:
1049
+ """Plot a single or multiple values from the metric.
1050
+
1051
+ Args:
1052
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
1053
+ If no value is provided, will automatically call `metric.compute` and plot that result.
1054
+ ax: An matplotlib axis object. If provided will add plot to that axis
1055
+
1056
+ Returns:
1057
+ Figure and Axes object
1058
+
1059
+ Raises:
1060
+ ModuleNotFoundError:
1061
+ If `matplotlib` is not installed
1062
+
1063
+ .. plot::
1064
+ :scale: 75
1065
+
1066
+ >>> from torch import rand, randint
1067
+ >>> # Example plotting a single value
1068
+ >>> from torchmetrics.classification import MultilabelF1Score
1069
+ >>> metric = MultilabelF1Score(num_labels=3)
1070
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
1071
+ >>> fig_, ax_ = metric.plot()
1072
+
1073
+ .. plot::
1074
+ :scale: 75
1075
+
1076
+ >>> from torch import rand, randint
1077
+ >>> # Example plotting multiple values
1078
+ >>> from torchmetrics.classification import MultilabelF1Score
1079
+ >>> metric = MultilabelF1Score(num_labels=3)
1080
+ >>> values = [ ]
1081
+ >>> for _ in range(10):
1082
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
1083
+ >>> fig_, ax_ = metric.plot(values)
1084
+
1085
+ """
1086
+ return self._plot(val, ax)
1087
+
1088
+
1089
+ class FBetaScore(_ClassificationTaskWrapper):
1090
+ r"""Compute `F-score`_ metric.
1091
+
1092
+ .. math::
1093
+ F_{\beta} = (1 + \beta^2) * \frac{\text{precision} * \text{recall}}
1094
+ {(\beta^2 * \text{precision}) + \text{recall}}
1095
+
1096
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
1097
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
1098
+ positives and false negatives respectively. If this case is encountered for any class/label, the metric for that
1099
+ class/label will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be
1100
+ affected in turn.
1101
+
1102
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
1103
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
1104
+ :class:`~torchmetrics.classification.BinaryFBetaScore`,
1105
+ :class:`~torchmetrics.classification.MulticlassFBetaScore` and
1106
+ :class:`~torchmetrics.classification.MultilabelFBetaScore` for the specific details of each argument influence
1107
+ and examples.
1108
+
1109
+ Legcy Example:
1110
+ >>> from torch import tensor
1111
+ >>> target = tensor([0, 1, 2, 0, 1, 2])
1112
+ >>> preds = tensor([0, 2, 1, 0, 0, 1])
1113
+ >>> f_beta = FBetaScore(task="multiclass", num_classes=3, beta=0.5)
1114
+ >>> f_beta(preds, target)
1115
+ tensor(0.3333)
1116
+
1117
+ """
1118
+
1119
+ def __new__( # type: ignore[misc]
1120
+ cls: type["FBetaScore"],
1121
+ task: Literal["binary", "multiclass", "multilabel"],
1122
+ beta: float = 1.0,
1123
+ threshold: float = 0.5,
1124
+ num_classes: Optional[int] = None,
1125
+ num_labels: Optional[int] = None,
1126
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
1127
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
1128
+ top_k: Optional[int] = 1,
1129
+ ignore_index: Optional[int] = None,
1130
+ validate_args: bool = True,
1131
+ zero_division: float = 0,
1132
+ **kwargs: Any,
1133
+ ) -> Metric:
1134
+ """Initialize task metric."""
1135
+ task = ClassificationTask.from_str(task)
1136
+ assert multidim_average is not None # noqa: S101 # needed for mypy
1137
+ kwargs.update({
1138
+ "multidim_average": multidim_average,
1139
+ "ignore_index": ignore_index,
1140
+ "validate_args": validate_args,
1141
+ "zero_division": zero_division,
1142
+ })
1143
+ if task == ClassificationTask.BINARY:
1144
+ return BinaryFBetaScore(beta, threshold, **kwargs)
1145
+ if task == ClassificationTask.MULTICLASS:
1146
+ if not isinstance(num_classes, int):
1147
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
1148
+ if not isinstance(top_k, int):
1149
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
1150
+ return MulticlassFBetaScore(beta, num_classes, top_k, average, **kwargs)
1151
+ if task == ClassificationTask.MULTILABEL:
1152
+ if not isinstance(num_labels, int):
1153
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
1154
+ return MultilabelFBetaScore(beta, num_labels, threshold, average, **kwargs)
1155
+ raise ValueError(f"Task {task} not supported!")
1156
+
1157
+
1158
+ class F1Score(_ClassificationTaskWrapper):
1159
+ r"""Compute F-1 score.
1160
+
1161
+ .. math::
1162
+ F_{1} = 2\frac{\text{precision} * \text{recall}}{(\text{precision}) + \text{recall}}
1163
+
1164
+ The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0 \wedge \text{TP} + \text{FN} \neq 0`
1165
+ where :math:`\text{TP}`, :math:`\text{FP}` and :math:`\text{FN}` represent the number of true positives, false
1166
+ positives and false negatives respectively. If this case is encountered for any class/label, the metric for that
1167
+ class/label will be set to `zero_division` (0 or 1, default is 0) and the overall metric may therefore be
1168
+ affected in turn.
1169
+
1170
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
1171
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
1172
+ :class:`~torchmetrics.classification.BinaryF1Score`, :class:`~torchmetrics.classification.MulticlassF1Score` and
1173
+ :class:`~torchmetrics.classification.MultilabelF1Score` for the specific details of each argument influence and
1174
+ examples.
1175
+
1176
+ Legacy Example:
1177
+ >>> from torch import tensor
1178
+ >>> target = tensor([0, 1, 2, 0, 1, 2])
1179
+ >>> preds = tensor([0, 2, 1, 0, 0, 1])
1180
+ >>> f1 = F1Score(task="multiclass", num_classes=3)
1181
+ >>> f1(preds, target)
1182
+ tensor(0.3333)
1183
+
1184
+ """
1185
+
1186
+ def __new__( # type: ignore[misc]
1187
+ cls: type["F1Score"],
1188
+ task: Literal["binary", "multiclass", "multilabel"],
1189
+ threshold: float = 0.5,
1190
+ num_classes: Optional[int] = None,
1191
+ num_labels: Optional[int] = None,
1192
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
1193
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
1194
+ top_k: Optional[int] = 1,
1195
+ ignore_index: Optional[int] = None,
1196
+ validate_args: bool = True,
1197
+ zero_division: float = 0,
1198
+ **kwargs: Any,
1199
+ ) -> Metric:
1200
+ """Initialize task metric."""
1201
+ task = ClassificationTask.from_str(task)
1202
+ assert multidim_average is not None # noqa: S101 # needed for mypy
1203
+ kwargs.update({
1204
+ "multidim_average": multidim_average,
1205
+ "ignore_index": ignore_index,
1206
+ "validate_args": validate_args,
1207
+ "zero_division": zero_division,
1208
+ })
1209
+ if task == ClassificationTask.BINARY:
1210
+ return BinaryF1Score(threshold, **kwargs)
1211
+ if task == ClassificationTask.MULTICLASS:
1212
+ if not isinstance(num_classes, int):
1213
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
1214
+ if not isinstance(top_k, int):
1215
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
1216
+ return MulticlassF1Score(num_classes, top_k, average, **kwargs)
1217
+ if task == ClassificationTask.MULTILABEL:
1218
+ if not isinstance(num_labels, int):
1219
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
1220
+ return MultilabelF1Score(num_labels, threshold, average, **kwargs)
1221
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/group_fairness.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The PyTorch Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ import torch
18
+ from torch import Tensor
19
+ from typing_extensions import Literal
20
+
21
+ from torchmetrics.functional.classification.group_fairness import (
22
+ _binary_groups_stat_scores,
23
+ _compute_binary_demographic_parity,
24
+ _compute_binary_equal_opportunity,
25
+ )
26
+ from torchmetrics.functional.classification.stat_scores import _binary_stat_scores_arg_validation
27
+ from torchmetrics.metric import Metric
28
+ from torchmetrics.utilities import rank_zero_warn
29
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
30
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
31
+
32
+ if not _MATPLOTLIB_AVAILABLE:
33
+ __doctest_skip__ = ["BinaryFairness.plot"]
34
+
35
+
36
+ class _AbstractGroupStatScores(Metric):
37
+ """Create and update states for computing group stats tp, fp, tn and fn."""
38
+
39
+ tp: Tensor
40
+ fp: Tensor
41
+ tn: Tensor
42
+ fn: Tensor
43
+
44
+ def _create_states(self, num_groups: int) -> None:
45
+ default = lambda: torch.zeros(num_groups, dtype=torch.long)
46
+ self.add_state("tp", default(), dist_reduce_fx="sum")
47
+ self.add_state("fp", default(), dist_reduce_fx="sum")
48
+ self.add_state("tn", default(), dist_reduce_fx="sum")
49
+ self.add_state("fn", default(), dist_reduce_fx="sum")
50
+
51
+ def _update_states(self, group_stats: list[tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]]) -> None:
52
+ for group, stats in enumerate(group_stats):
53
+ tp, fp, tn, fn = stats
54
+ self.tp[group] += tp
55
+ self.fp[group] += fp
56
+ self.tn[group] += tn
57
+ self.fn[group] += fn
58
+
59
+
60
+ class BinaryGroupStatRates(_AbstractGroupStatScores):
61
+ r"""Computes the true/false positives and true/false negatives rates for binary classification by group.
62
+
63
+ Related to `Type I and Type II errors`_.
64
+
65
+ Accepts the following input tensors:
66
+
67
+ - ``preds`` (int or float tensor): ``(N, ...)``. If preds is a floating point tensor with values outside
68
+ [0,1] range we consider the input to be logits and will auto apply sigmoid per element. Additionally,
69
+ we convert to int tensor with thresholding using the value in ``threshold``.
70
+ - ``target`` (int tensor): ``(N, ...)``.
71
+ - ``groups`` (int tensor): ``(N, ...)``. The group identifiers should be ``0, 1, ..., (num_groups - 1)``.
72
+
73
+ The additional dimensions are flatted along the batch dimension.
74
+
75
+ Args:
76
+ num_groups: The number of groups.
77
+ threshold: Threshold for transforming probability to binary {0,1} predictions.
78
+ ignore_index: Specifies a target value that is ignored and does not contribute to the metric calculation
79
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
80
+ Set to ``False`` for faster computations.
81
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
82
+
83
+ Returns:
84
+ The metric returns a dict with a group identifier as key and a tensor with the tp, fp, tn and fn rates as value.
85
+
86
+ Example (preds is int tensor):
87
+ >>> from torchmetrics.classification import BinaryGroupStatRates
88
+ >>> target = torch.tensor([0, 1, 0, 1, 0, 1])
89
+ >>> preds = torch.tensor([0, 1, 0, 1, 0, 1])
90
+ >>> groups = torch.tensor([0, 1, 0, 1, 0, 1])
91
+ >>> metric = BinaryGroupStatRates(num_groups=2)
92
+ >>> metric(preds, target, groups)
93
+ {'group_0': tensor([0., 0., 1., 0.]), 'group_1': tensor([1., 0., 0., 0.])}
94
+
95
+ Example (preds is float tensor):
96
+ >>> from torchmetrics.classification import BinaryGroupStatRates
97
+ >>> target = torch.tensor([0, 1, 0, 1, 0, 1])
98
+ >>> preds = torch.tensor([0.11, 0.84, 0.22, 0.73, 0.33, 0.92])
99
+ >>> groups = torch.tensor([0, 1, 0, 1, 0, 1])
100
+ >>> metric = BinaryGroupStatRates(num_groups=2)
101
+ >>> metric(preds, target, groups)
102
+ {'group_0': tensor([0., 0., 1., 0.]), 'group_1': tensor([1., 0., 0., 0.])}
103
+
104
+ """
105
+
106
+ is_differentiable: bool = False
107
+ higher_is_better: bool = False
108
+ full_state_update: bool = False
109
+ plot_lower_bound: float = 0.0
110
+ plot_upper_bound: float = 1.0
111
+
112
+ def __init__(
113
+ self,
114
+ num_groups: int,
115
+ threshold: float = 0.5,
116
+ ignore_index: Optional[int] = None,
117
+ validate_args: bool = True,
118
+ **kwargs: Any,
119
+ ) -> None:
120
+ super().__init__()
121
+
122
+ if validate_args:
123
+ _binary_stat_scores_arg_validation(threshold, "global", ignore_index)
124
+
125
+ if not isinstance(num_groups, int) and num_groups < 2:
126
+ raise ValueError(f"Expected argument `num_groups` to be an int larger than 1, but got {num_groups}")
127
+ self.num_groups = num_groups
128
+ self.threshold = threshold
129
+ self.ignore_index = ignore_index
130
+ self.validate_args = validate_args
131
+
132
+ self._create_states(self.num_groups)
133
+
134
+ def update(self, preds: Tensor, target: Tensor, groups: Tensor) -> None:
135
+ """Update state with predictions, target and group identifiers.
136
+
137
+ Args:
138
+ preds: Tensor with predictions.
139
+ target: Tensor with true labels.
140
+ groups: Tensor with group identifiers. The group identifiers should be ``0, 1, ..., (num_groups - 1)``.
141
+
142
+ """
143
+ group_stats = _binary_groups_stat_scores(
144
+ preds, target, groups, self.num_groups, self.threshold, self.ignore_index, self.validate_args
145
+ )
146
+
147
+ self._update_states(group_stats)
148
+
149
+ def compute(
150
+ self,
151
+ ) -> dict[str, Tensor]:
152
+ """Compute tp, fp, tn and fn rates based on inputs passed in to ``update`` previously."""
153
+ results = torch.stack((self.tp, self.fp, self.tn, self.fn), dim=1)
154
+
155
+ return {f"group_{i}": group / group.sum() for i, group in enumerate(results)}
156
+
157
+
158
+ class BinaryFairness(_AbstractGroupStatScores):
159
+ r"""Computes `Demographic parity`_ and `Equal opportunity`_ ratio for binary classification problems.
160
+
161
+ Accepts the following input tensors:
162
+
163
+ - ``preds`` (int or float tensor): ``(N, ...)``. If preds is a floating point tensor with values outside
164
+ [0,1] range we consider the input to be logits and will auto apply sigmoid per element. Additionally,
165
+ we convert to int tensor with thresholding using the value in ``threshold``.
166
+ - ``groups`` (int tensor): ``(N, ...)``. The group identifiers should be ``0, 1, ..., (num_groups - 1)``.
167
+ - ``target`` (int tensor): ``(N, ...)``.
168
+
169
+ The additional dimensions are flatted along the batch dimension.
170
+
171
+ This class computes the ratio between positivity rates and true positives rates for different groups.
172
+ If more than two groups are present, the disparity between the lowest and highest group is reported.
173
+ A disparity between positivity rates indicates a potential violation of demographic parity, and between
174
+ true positive rates indicates a potential violation of equal opportunity.
175
+
176
+ The lowest rate is divided by the highest, so a lower value means more discrimination against the numerator.
177
+ In the results this is also indicated as the key of dict is {metric}_{identifier_low_group}_{identifier_high_group}.
178
+
179
+ Args:
180
+ num_groups: The number of groups.
181
+ task: The task to compute. Can be either ``demographic_parity`` or ``equal_opportunity`` or ``all``.
182
+ threshold: Threshold for transforming probability to binary {0,1} predictions.
183
+ ignore_index: Specifies a target value that is ignored and does not contribute to the metric calculation
184
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
185
+ Set to ``False`` for faster computations.
186
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
187
+
188
+ Returns:
189
+ The metric returns a dict where the key identifies the metric and groups with the lowest and highest true
190
+ positives rates as follows: {metric}__{identifier_low_group}_{identifier_high_group}.
191
+ The value is a tensor with the disparity rate.
192
+
193
+ Example (preds is int tensor):
194
+ >>> from torchmetrics.classification import BinaryFairness
195
+ >>> target = torch.tensor([0, 1, 0, 1, 0, 1])
196
+ >>> preds = torch.tensor([0, 1, 0, 1, 0, 1])
197
+ >>> groups = torch.tensor([0, 1, 0, 1, 0, 1])
198
+ >>> metric = BinaryFairness(2)
199
+ >>> metric(preds, target, groups)
200
+ {'DP_0_1': tensor(0.), 'EO_0_1': tensor(0.)}
201
+
202
+ Example (preds is float tensor):
203
+ >>> from torchmetrics.classification import BinaryFairness
204
+ >>> target = torch.tensor([0, 1, 0, 1, 0, 1])
205
+ >>> preds = torch.tensor([0.11, 0.84, 0.22, 0.73, 0.33, 0.92])
206
+ >>> groups = torch.tensor([0, 1, 0, 1, 0, 1])
207
+ >>> metric = BinaryFairness(2)
208
+ >>> metric(preds, target, groups)
209
+ {'DP_0_1': tensor(0.), 'EO_0_1': tensor(0.)}
210
+
211
+ """
212
+
213
+ is_differentiable: bool = False
214
+ higher_is_better: bool = False
215
+ full_state_update: bool = False
216
+ plot_lower_bound: float = 0.0
217
+ plot_upper_bound: float = 1.0
218
+
219
+ def __init__(
220
+ self,
221
+ num_groups: int,
222
+ task: Literal["demographic_parity", "equal_opportunity", "all"] = "all",
223
+ threshold: float = 0.5,
224
+ ignore_index: Optional[int] = None,
225
+ validate_args: bool = True,
226
+ **kwargs: Any,
227
+ ) -> None:
228
+ super().__init__()
229
+
230
+ if task not in ["demographic_parity", "equal_opportunity", "all"]:
231
+ raise ValueError(
232
+ f"Expected argument `task` to either be ``demographic_parity``,"
233
+ f"``equal_opportunity`` or ``all`` but got {task}."
234
+ )
235
+
236
+ if validate_args:
237
+ _binary_stat_scores_arg_validation(threshold, "global", ignore_index)
238
+
239
+ if not isinstance(num_groups, int) and num_groups < 2:
240
+ raise ValueError(f"Expected argument `num_groups` to be an int larger than 1, but got {num_groups}")
241
+ self.num_groups = num_groups
242
+ self.task = task
243
+ self.threshold = threshold
244
+ self.ignore_index = ignore_index
245
+ self.validate_args = validate_args
246
+
247
+ self._create_states(self.num_groups)
248
+
249
+ def update(self, preds: Tensor, target: Tensor, groups: Tensor) -> None:
250
+ """Update state with predictions, groups, and target.
251
+
252
+ Args:
253
+ preds: Tensor with predictions.
254
+ target: Tensor with true labels.
255
+ groups: Tensor with group identifiers. The group identifiers should be ``0, 1, ..., (num_groups - 1)``.
256
+
257
+ """
258
+ if self.task == "demographic_parity":
259
+ if target is not None:
260
+ rank_zero_warn("The task demographic_parity does not require a target.", UserWarning)
261
+ target = torch.zeros(preds.shape)
262
+
263
+ group_stats = _binary_groups_stat_scores(
264
+ preds, target, groups, self.num_groups, self.threshold, self.ignore_index, self.validate_args
265
+ )
266
+
267
+ self._update_states(group_stats)
268
+
269
+ def compute(
270
+ self,
271
+ ) -> dict[str, torch.Tensor]:
272
+ """Compute fairness criteria based on inputs passed in to ``update`` previously."""
273
+ if self.task == "demographic_parity":
274
+ return _compute_binary_demographic_parity(self.tp, self.fp, self.tn, self.fn)
275
+
276
+ if self.task == "equal_opportunity":
277
+ return _compute_binary_equal_opportunity(self.tp, self.fp, self.tn, self.fn)
278
+
279
+ if self.task == "all":
280
+ return {
281
+ **_compute_binary_demographic_parity(self.tp, self.fp, self.tn, self.fn),
282
+ **_compute_binary_equal_opportunity(self.tp, self.fp, self.tn, self.fn),
283
+ }
284
+ return None
285
+
286
+ def plot(
287
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
288
+ ) -> _PLOT_OUT_TYPE:
289
+ """Plot a single or multiple values from the metric.
290
+
291
+ Args:
292
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
293
+ If no value is provided, will automatically call `metric.compute` and plot that result.
294
+ ax: An matplotlib axis object. If provided will add plot to that axis
295
+
296
+ Returns:
297
+ Figure object and Axes object
298
+
299
+ Raises:
300
+ ModuleNotFoundError:
301
+ If `matplotlib` is not installed
302
+
303
+ .. plot::
304
+ :scale: 75
305
+
306
+ >>> from torch import ones, rand, randint
307
+ >>> # Example plotting a single value
308
+ >>> from torchmetrics.classification import BinaryFairness
309
+ >>> metric = BinaryFairness(2)
310
+ >>> metric.update(rand(50), randint(2, (50,)), ones(50).long())
311
+ >>> fig_, ax_ = metric.plot()
312
+
313
+ .. plot::
314
+ :scale: 75
315
+
316
+ >>> from torch import ones, rand, randint
317
+ >>> # Example plotting multiple values
318
+ >>> from torchmetrics.classification import BinaryFairness
319
+ >>> metric = BinaryFairness(2)
320
+ >>> values = [ ]
321
+ >>> for _ in range(10):
322
+ ... values.append(metric(rand(50), randint(2, (50,) ), ones(50).long()))
323
+ >>> fig_, ax_ = metric.plot(values)
324
+
325
+ """
326
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/classification/hamming.py ADDED
@@ -0,0 +1,529 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.stat_scores import BinaryStatScores, MulticlassStatScores, MultilabelStatScores
22
+ from torchmetrics.functional.classification.hamming import _hamming_distance_reduce
23
+ from torchmetrics.metric import Metric
24
+ from torchmetrics.utilities.enums import ClassificationTask
25
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
26
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
27
+
28
+ if not _MATPLOTLIB_AVAILABLE:
29
+ __doctest_skip__ = [
30
+ "BinaryHammingDistance.plot",
31
+ "MulticlassHammingDistance.plot",
32
+ "MultilabelHammingDistance.plot",
33
+ ]
34
+
35
+
36
+ class BinaryHammingDistance(BinaryStatScores):
37
+ r"""Compute the average `Hamming distance`_ (also known as Hamming loss) for binary tasks.
38
+
39
+ .. math::
40
+ \text{Hamming distance} = \frac{1}{N \cdot L} \sum_i^N \sum_l^L 1(y_{il} \neq \hat{y}_{il})
41
+
42
+ Where :math:`y` is a tensor of target values, :math:`\hat{y}` is a tensor of predictions,
43
+ and :math:`\bullet_{il}` refers to the :math:`l`-th label of the :math:`i`-th sample of that
44
+ tensor.
45
+
46
+ As input to ``forward`` and ``update`` the metric accepts the following input:
47
+
48
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, ...)``. If preds is a floating point
49
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per
50
+ element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
51
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
52
+
53
+
54
+ As output to ``forward`` and ``compute`` the metric returns the following output:
55
+
56
+ - ``bhd`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``multidim_average`` arguments:
57
+
58
+ - If ``multidim_average`` is set to ``global``, the metric returns a scalar value.
59
+ - If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of a
60
+ scalar value per sample.
61
+
62
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
63
+ which the reduction will then be applied over instead of the sample dimension ``N``.
64
+
65
+ Args:
66
+ threshold: Threshold for transforming probability to binary {0,1} predictions
67
+ multidim_average:
68
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
69
+
70
+ - ``global``: Additional dimensions are flatted along the batch dimension
71
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
72
+ The statistics in this case are calculated over the additional dimensions.
73
+
74
+ ignore_index:
75
+ Specifies a target value that is ignored and does not contribute to the metric calculation
76
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
77
+ Set to ``False`` for faster computations.
78
+
79
+ Example (preds is int tensor):
80
+ >>> from torch import tensor
81
+ >>> from torchmetrics.classification import BinaryHammingDistance
82
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
83
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
84
+ >>> metric = BinaryHammingDistance()
85
+ >>> metric(preds, target)
86
+ tensor(0.3333)
87
+
88
+ Example (preds is float tensor):
89
+ >>> from torchmetrics.classification import BinaryHammingDistance
90
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
91
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
92
+ >>> metric = BinaryHammingDistance()
93
+ >>> metric(preds, target)
94
+ tensor(0.3333)
95
+
96
+ Example (multidim tensors):
97
+ >>> from torchmetrics.classification import BinaryHammingDistance
98
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
99
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
100
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
101
+ >>> metric = BinaryHammingDistance(multidim_average='samplewise')
102
+ >>> metric(preds, target)
103
+ tensor([0.6667, 0.8333])
104
+
105
+ """
106
+
107
+ is_differentiable: bool = False
108
+ higher_is_better: bool = False
109
+ full_state_update: bool = False
110
+ plot_lower_bound: float = 0.0
111
+ plot_upper_bound: float = 1.0
112
+
113
+ def compute(self) -> Tensor:
114
+ """Compute metric."""
115
+ tp, fp, tn, fn = self._final_state()
116
+ return _hamming_distance_reduce(tp, fp, tn, fn, average="binary", multidim_average=self.multidim_average)
117
+
118
+ def plot(
119
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
120
+ ) -> _PLOT_OUT_TYPE:
121
+ """Plot a single or multiple values from the metric.
122
+
123
+ Args:
124
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
125
+ If no value is provided, will automatically call `metric.compute` and plot that result.
126
+ ax: An matplotlib axis object. If provided will add plot to that axis
127
+
128
+ Returns:
129
+ Figure object and Axes object
130
+
131
+ Raises:
132
+ ModuleNotFoundError:
133
+ If `matplotlib` is not installed
134
+
135
+ .. plot::
136
+ :scale: 75
137
+
138
+ >>> # Example plotting a single value
139
+ >>> from torch import rand, randint
140
+ >>> from torchmetrics.classification import BinaryHammingDistance
141
+ >>> metric = BinaryHammingDistance()
142
+ >>> metric.update(rand(10), randint(2,(10,)))
143
+ >>> fig_, ax_ = metric.plot()
144
+
145
+ .. plot::
146
+ :scale: 75
147
+
148
+ >>> # Example plotting multiple values
149
+ >>> from torch import rand, randint
150
+ >>> from torchmetrics.classification import BinaryHammingDistance
151
+ >>> metric = BinaryHammingDistance()
152
+ >>> values = [ ]
153
+ >>> for _ in range(10):
154
+ ... values.append(metric(rand(10), randint(2,(10,))))
155
+ >>> fig_, ax_ = metric.plot(values)
156
+
157
+ """
158
+ return self._plot(val, ax)
159
+
160
+
161
+ class MulticlassHammingDistance(MulticlassStatScores):
162
+ r"""Compute the average `Hamming distance`_ (also known as Hamming loss) for multiclass tasks.
163
+
164
+ .. math::
165
+ \text{Hamming distance} = \frac{1}{N \cdot L} \sum_i^N \sum_l^L 1(y_{il} \neq \hat{y}_{il})
166
+
167
+ Where :math:`y` is a tensor of target values, :math:`\hat{y}` is a tensor of predictions,
168
+ and :math:`\bullet_{il}` refers to the :math:`l`-th label of the :math:`i`-th sample of that
169
+ tensor.
170
+
171
+ As input to ``forward`` and ``update`` the metric accepts the following input:
172
+
173
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
174
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
175
+ probabilities/logits into an int tensor.
176
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
177
+
178
+ As output to ``forward`` and ``compute`` the metric returns the following output:
179
+
180
+ - ``mchd`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
181
+ ``multidim_average`` arguments:
182
+
183
+ - If ``multidim_average`` is set to ``global``:
184
+
185
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
186
+ - If ``average=None/'none'``, the shape will be ``(C,)``
187
+
188
+ - If ``multidim_average`` is set to ``samplewise``:
189
+
190
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
191
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
192
+
193
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
194
+ which the reduction will then be applied over instead of the sample dimension ``N``.
195
+
196
+ Args:
197
+ num_classes: Integer specifying the number of classes
198
+ average:
199
+ Defines the reduction that is applied over labels. Should be one of the following:
200
+
201
+ - ``micro``: Sum statistics over all labels
202
+ - ``macro``: Calculate statistics for each label and average them
203
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
204
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
205
+ top_k:
206
+ Number of highest probability or logit score predictions considered to find the correct label.
207
+ Only works when ``preds`` contain probabilities/logits.
208
+ multidim_average:
209
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
210
+
211
+ - ``global``: Additional dimensions are flatted along the batch dimension
212
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
213
+ The statistics in this case are calculated over the additional dimensions.
214
+
215
+ ignore_index:
216
+ Specifies a target value that is ignored and does not contribute to the metric calculation
217
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
218
+ Set to ``False`` for faster computations.
219
+
220
+ Example (preds is int tensor):
221
+ >>> from torch import tensor
222
+ >>> from torchmetrics.classification import MulticlassHammingDistance
223
+ >>> target = tensor([2, 1, 0, 0])
224
+ >>> preds = tensor([2, 1, 0, 1])
225
+ >>> metric = MulticlassHammingDistance(num_classes=3)
226
+ >>> metric(preds, target)
227
+ tensor(0.1667)
228
+ >>> mchd = MulticlassHammingDistance(num_classes=3, average=None)
229
+ >>> mchd(preds, target)
230
+ tensor([0.5000, 0.0000, 0.0000])
231
+
232
+ Example (preds is float tensor):
233
+ >>> from torchmetrics.classification import MulticlassHammingDistance
234
+ >>> target = tensor([2, 1, 0, 0])
235
+ >>> preds = tensor([[0.16, 0.26, 0.58],
236
+ ... [0.22, 0.61, 0.17],
237
+ ... [0.71, 0.09, 0.20],
238
+ ... [0.05, 0.82, 0.13]])
239
+ >>> metric = MulticlassHammingDistance(num_classes=3)
240
+ >>> metric(preds, target)
241
+ tensor(0.1667)
242
+ >>> mchd = MulticlassHammingDistance(num_classes=3, average=None)
243
+ >>> mchd(preds, target)
244
+ tensor([0.5000, 0.0000, 0.0000])
245
+
246
+ Example (multidim tensors):
247
+ >>> from torchmetrics.classification import MulticlassHammingDistance
248
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
249
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
250
+ >>> metric = MulticlassHammingDistance(num_classes=3, multidim_average='samplewise')
251
+ >>> metric(preds, target)
252
+ tensor([0.5000, 0.7222])
253
+ >>> mchd = MulticlassHammingDistance(num_classes=3, multidim_average='samplewise', average=None)
254
+ >>> mchd(preds, target)
255
+ tensor([[0.0000, 1.0000, 0.5000],
256
+ [1.0000, 0.6667, 0.5000]])
257
+
258
+ """
259
+
260
+ is_differentiable: bool = False
261
+ higher_is_better: bool = False
262
+ full_state_update: bool = False
263
+ plot_lower_bound: float = 0.0
264
+ plot_upper_bound: float = 1.0
265
+ plot_legend_name: str = "Class"
266
+
267
+ def compute(self) -> Tensor:
268
+ """Compute metric."""
269
+ tp, fp, tn, fn = self._final_state()
270
+ return _hamming_distance_reduce(tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average)
271
+
272
+ def plot(
273
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
274
+ ) -> _PLOT_OUT_TYPE:
275
+ """Plot a single or multiple values from the metric.
276
+
277
+ Args:
278
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
279
+ If no value is provided, will automatically call `metric.compute` and plot that result.
280
+ ax: An matplotlib axis object. If provided will add plot to that axis
281
+
282
+ Returns:
283
+ Figure object and Axes object
284
+
285
+ Raises:
286
+ ModuleNotFoundError:
287
+ If `matplotlib` is not installed
288
+
289
+ .. plot::
290
+ :scale: 75
291
+
292
+ >>> # Example plotting a single value per class
293
+ >>> from torch import randint
294
+ >>> from torchmetrics.classification import MulticlassHammingDistance
295
+ >>> metric = MulticlassHammingDistance(num_classes=3, average=None)
296
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
297
+ >>> fig_, ax_ = metric.plot()
298
+
299
+ .. plot::
300
+ :scale: 75
301
+
302
+ >>> # Example plotting a multiple values per class
303
+ >>> from torch import randint
304
+ >>> from torchmetrics.classification import MulticlassHammingDistance
305
+ >>> metric = MulticlassHammingDistance(num_classes=3, average=None)
306
+ >>> values = []
307
+ >>> for _ in range(20):
308
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
309
+ >>> fig_, ax_ = metric.plot(values)
310
+
311
+ """
312
+ return self._plot(val, ax)
313
+
314
+
315
+ class MultilabelHammingDistance(MultilabelStatScores):
316
+ r"""Compute the average `Hamming distance`_ (also known as Hamming loss) for multilabel tasks.
317
+
318
+ .. math::
319
+ \text{Hamming distance} = \frac{1}{N \cdot L} \sum_i^N \sum_l^L 1(y_{il} \neq \hat{y}_{il})
320
+
321
+ Where :math:`y` is a tensor of target values, :math:`\hat{y}` is a tensor of predictions,
322
+ and :math:`\bullet_{il}` refers to the :math:`l`-th label of the :math:`i`-th sample of that
323
+ tensor.
324
+
325
+ As input to ``forward`` and ``update`` the metric accepts the following input:
326
+
327
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor or float tensor of shape ``(N, C, ...)``. If preds is a
328
+ floating point tensor with values outside [0,1] range we consider the input to be logits and will auto
329
+ apply sigmoid per element. Additionally, we convert to int tensor with thresholding using the value in
330
+ ``threshold``.
331
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``.
332
+
333
+ As output to ``forward`` and ``compute`` the metric returns the following output:
334
+
335
+ - ``mlhd`` (:class:`~torch.Tensor`): A tensor whose returned shape depends on the ``average`` and
336
+ ``multidim_average`` arguments:
337
+
338
+ - If ``multidim_average`` is set to ``global``:
339
+
340
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
341
+ - If ``average=None/'none'``, the shape will be ``(C,)``
342
+
343
+ - If ``multidim_average`` is set to ``samplewise``:
344
+
345
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
346
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
347
+
348
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
349
+ which the reduction will then be applied over instead of the sample dimension ``N``.
350
+
351
+ Args:
352
+ num_labels: Integer specifying the number of labels
353
+ threshold: Threshold for transforming probability to binary (0,1) predictions
354
+ average:
355
+ Defines the reduction that is applied over labels. Should be one of the following:
356
+
357
+ - ``micro``: Sum statistics over all labels
358
+ - ``macro``: Calculate statistics for each label and average them
359
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
360
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
361
+
362
+ multidim_average:
363
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
364
+
365
+ - ``global``: Additional dimensions are flatted along the batch dimension
366
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
367
+ The statistics in this case are calculated over the additional dimensions.
368
+
369
+ ignore_index:
370
+ Specifies a target value that is ignored and does not contribute to the metric calculation
371
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
372
+ Set to ``False`` for faster computations.
373
+
374
+ Example (preds is int tensor):
375
+ >>> from torch import tensor
376
+ >>> from torchmetrics.classification import MultilabelHammingDistance
377
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
378
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
379
+ >>> metric = MultilabelHammingDistance(num_labels=3)
380
+ >>> metric(preds, target)
381
+ tensor(0.3333)
382
+ >>> mlhd = MultilabelHammingDistance(num_labels=3, average=None)
383
+ >>> mlhd(preds, target)
384
+ tensor([0.0000, 0.5000, 0.5000])
385
+
386
+ Example (preds is float tensor):
387
+ >>> from torchmetrics.classification import MultilabelHammingDistance
388
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
389
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
390
+ >>> metric = MultilabelHammingDistance(num_labels=3)
391
+ >>> metric(preds, target)
392
+ tensor(0.3333)
393
+ >>> mlhd = MultilabelHammingDistance(num_labels=3, average=None)
394
+ >>> mlhd(preds, target)
395
+ tensor([0.0000, 0.5000, 0.5000])
396
+
397
+ Example (multidim tensors):
398
+ >>> from torchmetrics.classification import MultilabelHammingDistance
399
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
400
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
401
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
402
+ >>> metric = MultilabelHammingDistance(num_labels=3, multidim_average='samplewise')
403
+ >>> metric(preds, target)
404
+ tensor([0.6667, 0.8333])
405
+ >>> mlhd = MultilabelHammingDistance(num_labels=3, multidim_average='samplewise', average=None)
406
+ >>> mlhd(preds, target)
407
+ tensor([[0.5000, 0.5000, 1.0000],
408
+ [1.0000, 1.0000, 0.5000]])
409
+
410
+ """
411
+
412
+ is_differentiable: bool = False
413
+ higher_is_better: bool = False
414
+ full_state_update: bool = False
415
+ plot_lower_bound: float = 0.0
416
+ plot_upper_bound: float = 1.0
417
+ plot_legend_name: str = "Label"
418
+
419
+ def compute(self) -> Tensor:
420
+ """Compute metric."""
421
+ tp, fp, tn, fn = self._final_state()
422
+ return _hamming_distance_reduce(
423
+ tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average, multilabel=True
424
+ )
425
+
426
+ def plot(
427
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
428
+ ) -> _PLOT_OUT_TYPE:
429
+ """Plot a single or multiple values from the metric.
430
+
431
+ Args:
432
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
433
+ If no value is provided, will automatically call `metric.compute` and plot that result.
434
+ ax: An matplotlib axis object. If provided will add plot to that axis
435
+
436
+ Returns:
437
+ Figure and Axes object
438
+
439
+ Raises:
440
+ ModuleNotFoundError:
441
+ If `matplotlib` is not installed
442
+
443
+ .. plot::
444
+ :scale: 75
445
+
446
+ >>> # Example plotting a single value
447
+ >>> from torch import rand, randint
448
+ >>> from torchmetrics.classification import MultilabelHammingDistance
449
+ >>> metric = MultilabelHammingDistance(num_labels=3)
450
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
451
+ >>> fig_, ax_ = metric.plot()
452
+
453
+ .. plot::
454
+ :scale: 75
455
+
456
+ >>> # Example plotting multiple values
457
+ >>> from torch import rand, randint
458
+ >>> from torchmetrics.classification import MultilabelHammingDistance
459
+ >>> metric = MultilabelHammingDistance(num_labels=3)
460
+ >>> values = [ ]
461
+ >>> for _ in range(10):
462
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
463
+ >>> fig_, ax_ = metric.plot(values)
464
+
465
+ """
466
+ return self._plot(val, ax)
467
+
468
+
469
+ class HammingDistance(_ClassificationTaskWrapper):
470
+ r"""Compute the average `Hamming distance`_ (also known as Hamming loss).
471
+
472
+ .. math::
473
+ \text{Hamming distance} = \frac{1}{N \cdot L} \sum_i^N \sum_l^L 1(y_{il} \neq \hat{y}_{il})
474
+
475
+ Where :math:`y` is a tensor of target values, :math:`\hat{y}` is a tensor of predictions,
476
+ and :math:`\bullet_{il}` refers to the :math:`l`-th label of the :math:`i`-th sample of that
477
+ tensor.
478
+
479
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
480
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
481
+ :class:`~torchmetrics.classification.BinaryHammingDistance`,
482
+ :class:`~torchmetrics.classification.MulticlassHammingDistance` and
483
+ :class:`~torchmetrics.classification.MultilabelHammingDistance` for the specific details of each argument influence
484
+ and examples.
485
+
486
+ Legacy Example:
487
+ >>> from torch import tensor
488
+ >>> target = tensor([[0, 1], [1, 1]])
489
+ >>> preds = tensor([[0, 1], [0, 1]])
490
+ >>> hamming_distance = HammingDistance(task="multilabel", num_labels=2)
491
+ >>> hamming_distance(preds, target)
492
+ tensor(0.2500)
493
+
494
+ """
495
+
496
+ def __new__( # type: ignore[misc]
497
+ cls: type["HammingDistance"],
498
+ task: Literal["binary", "multiclass", "multilabel"],
499
+ threshold: float = 0.5,
500
+ num_classes: Optional[int] = None,
501
+ num_labels: Optional[int] = None,
502
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
503
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
504
+ top_k: Optional[int] = 1,
505
+ ignore_index: Optional[int] = None,
506
+ validate_args: bool = True,
507
+ **kwargs: Any,
508
+ ) -> Metric:
509
+ """Initialize task metric."""
510
+ task = ClassificationTask.from_str(task)
511
+ assert multidim_average is not None # noqa: S101 # needed for mypy
512
+ kwargs.update({
513
+ "multidim_average": multidim_average,
514
+ "ignore_index": ignore_index,
515
+ "validate_args": validate_args,
516
+ })
517
+ if task == ClassificationTask.BINARY:
518
+ return BinaryHammingDistance(threshold, **kwargs)
519
+ if task == ClassificationTask.MULTICLASS:
520
+ if not isinstance(num_classes, int):
521
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
522
+ if not isinstance(top_k, int):
523
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
524
+ return MulticlassHammingDistance(num_classes, top_k, average, **kwargs)
525
+ if task == ClassificationTask.MULTILABEL:
526
+ if not isinstance(num_labels, int):
527
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
528
+ return MultilabelHammingDistance(num_labels, threshold, average, **kwargs)
529
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/hinge.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ import torch
18
+ from torch import Tensor
19
+ from typing_extensions import Literal
20
+
21
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
22
+ from torchmetrics.functional.classification.hinge import (
23
+ _binary_confusion_matrix_format,
24
+ _binary_hinge_loss_arg_validation,
25
+ _binary_hinge_loss_tensor_validation,
26
+ _binary_hinge_loss_update,
27
+ _hinge_loss_compute,
28
+ _multiclass_confusion_matrix_format,
29
+ _multiclass_hinge_loss_arg_validation,
30
+ _multiclass_hinge_loss_tensor_validation,
31
+ _multiclass_hinge_loss_update,
32
+ )
33
+ from torchmetrics.metric import Metric
34
+ from torchmetrics.utilities.enums import ClassificationTaskNoMultilabel
35
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
36
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
37
+
38
+ if not _MATPLOTLIB_AVAILABLE:
39
+ __doctest_skip__ = ["BinaryHingeLoss.plot", "MulticlassHingeLoss.plot"]
40
+
41
+
42
+ class BinaryHingeLoss(Metric):
43
+ r"""Compute the mean `Hinge loss`_ typically used for Support Vector Machines (SVMs) for binary tasks.
44
+
45
+ .. math::
46
+ \text{Hinge loss} = \max(0, 1 - y \times \hat{y})
47
+
48
+ Where :math:`y \in {-1, 1}` is the target, and :math:`\hat{y} \in \mathbb{R}` is the prediction.
49
+
50
+ As input to ``forward`` and ``update`` the metric accepts the following input:
51
+
52
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)``. Preds should be a tensor containing
53
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input
54
+ to be logits and will auto apply sigmoid per element.
55
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
56
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
57
+ 1 always encodes the positive class.
58
+
59
+ .. tip::
60
+ Additional dimension ``...`` will be flattened into the batch dimension.
61
+
62
+ As output to ``forward`` and ``compute`` the metric returns the following output:
63
+
64
+ - ``bhl`` (:class:`~torch.Tensor`): A tensor containing the hinge loss.
65
+
66
+ Args:
67
+ squared:
68
+ If True, this will compute the squared hinge loss. Otherwise, computes the regular hinge loss.
69
+ ignore_index:
70
+ Specifies a target value that is ignored and does not contribute to the metric calculation
71
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
72
+ Set to ``False`` for faster computations.
73
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
74
+
75
+ Example:
76
+ >>> from torchmetrics.classification import BinaryHingeLoss
77
+ >>> preds = torch.tensor([0.25, 0.25, 0.55, 0.75, 0.75])
78
+ >>> target = torch.tensor([0, 0, 1, 1, 1])
79
+ >>> bhl = BinaryHingeLoss()
80
+ >>> bhl(preds, target)
81
+ tensor(0.6900)
82
+ >>> bhl = BinaryHingeLoss(squared=True)
83
+ >>> bhl(preds, target)
84
+ tensor(0.6905)
85
+
86
+ """
87
+
88
+ is_differentiable: bool = True
89
+ higher_is_better: bool = False
90
+ full_state_update: bool = False
91
+ plot_lower_bound: float = 0.0
92
+ plot_upper_bound: float = 1.0
93
+
94
+ measures: Tensor
95
+ total: Tensor
96
+
97
+ def __init__(
98
+ self,
99
+ squared: bool = False,
100
+ ignore_index: Optional[int] = None,
101
+ validate_args: bool = True,
102
+ **kwargs: Any,
103
+ ) -> None:
104
+ super().__init__(**kwargs)
105
+ if validate_args:
106
+ _binary_hinge_loss_arg_validation(squared, ignore_index)
107
+ self.validate_args = validate_args
108
+ self.squared = squared
109
+ self.ignore_index = ignore_index
110
+
111
+ self.add_state("measures", default=torch.tensor(0.0), dist_reduce_fx="sum")
112
+ self.add_state("total", default=torch.tensor(0), dist_reduce_fx="sum")
113
+
114
+ def update(self, preds: Tensor, target: Tensor) -> None:
115
+ """Update metric state."""
116
+ if self.validate_args:
117
+ _binary_hinge_loss_tensor_validation(preds, target, self.ignore_index)
118
+ preds, target = _binary_confusion_matrix_format(
119
+ preds, target, threshold=0.0, ignore_index=self.ignore_index, convert_to_labels=False
120
+ )
121
+ measures, total = _binary_hinge_loss_update(preds, target, self.squared)
122
+ self.measures += measures
123
+ self.total += total
124
+
125
+ def compute(self) -> Tensor:
126
+ """Compute metric."""
127
+ return _hinge_loss_compute(self.measures, self.total)
128
+
129
+ def plot(
130
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
131
+ ) -> _PLOT_OUT_TYPE:
132
+ """Plot a single or multiple values from the metric.
133
+
134
+ Args:
135
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
136
+ If no value is provided, will automatically call `metric.compute` and plot that result.
137
+ ax: An matplotlib axis object. If provided will add plot to that axis
138
+
139
+ Returns:
140
+ Figure object and Axes object
141
+
142
+ Raises:
143
+ ModuleNotFoundError:
144
+ If `matplotlib` is not installed
145
+
146
+ .. plot::
147
+ :scale: 75
148
+
149
+ >>> # Example plotting a single value
150
+ >>> from torch import rand, randint
151
+ >>> from torchmetrics.classification import BinaryHingeLoss
152
+ >>> metric = BinaryHingeLoss()
153
+ >>> metric.update(rand(10), randint(2,(10,)))
154
+ >>> fig_, ax_ = metric.plot()
155
+
156
+ .. plot::
157
+ :scale: 75
158
+
159
+ >>> # Example plotting multiple values
160
+ >>> from torch import rand, randint
161
+ >>> from torchmetrics.classification import BinaryHingeLoss
162
+ >>> metric = BinaryHingeLoss()
163
+ >>> values = [ ]
164
+ >>> for _ in range(10):
165
+ ... values.append(metric(rand(10), randint(2,(10,))))
166
+ >>> fig_, ax_ = metric.plot(values)
167
+
168
+ """
169
+ return self._plot(val, ax)
170
+
171
+
172
+ class MulticlassHingeLoss(Metric):
173
+ r"""Compute the mean `Hinge loss`_ typically used for Support Vector Machines (SVMs) for multiclass tasks.
174
+
175
+ The metric can be computed in two ways. Either, the definition by Crammer and Singer is used:
176
+
177
+ .. math::
178
+ \text{Hinge loss} = \max\left(0, 1 - \hat{y}_y + \max_{i \ne y} (\hat{y}_i)\right)
179
+
180
+ Where :math:`y \in {0, ..., \mathrm{C}}` is the target class (where :math:`\mathrm{C}` is the number of classes),
181
+ and :math:`\hat{y} \in \mathbb{R}^\mathrm{C}` is the predicted output per class. Alternatively, the metric can
182
+ also be computed in one-vs-all approach, where each class is valued against all other classes in a binary fashion.
183
+
184
+ As input to ``forward`` and ``update`` the metric accepts the following input:
185
+
186
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
187
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
188
+ the input to be logits and will auto apply softmax per sample.
189
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
190
+ ground truth labels, and therefore only contain values in the [0, n_classes-1] range (except if `ignore_index`
191
+ is specified).
192
+
193
+ .. tip::
194
+ Additional dimension ``...`` will be flattened into the batch dimension.
195
+
196
+ As output to ``forward`` and ``compute`` the metric returns the following output:
197
+
198
+ - ``mchl`` (:class:`~torch.Tensor`): A tensor containing the multi-class hinge loss.
199
+
200
+ Args:
201
+ num_classes: Integer specifying the number of classes
202
+ squared:
203
+ If True, this will compute the squared hinge loss. Otherwise, computes the regular hinge loss.
204
+ multiclass_mode:
205
+ Determines how to compute the metric
206
+ ignore_index:
207
+ Specifies a target value that is ignored and does not contribute to the metric calculation
208
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
209
+ Set to ``False`` for faster computations.
210
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
211
+
212
+ Example:
213
+ >>> from torchmetrics.classification import MulticlassHingeLoss
214
+ >>> preds = torch.tensor([[0.25, 0.20, 0.55],
215
+ ... [0.55, 0.05, 0.40],
216
+ ... [0.10, 0.30, 0.60],
217
+ ... [0.90, 0.05, 0.05]])
218
+ >>> target = torch.tensor([0, 1, 2, 0])
219
+ >>> mchl = MulticlassHingeLoss(num_classes=3)
220
+ >>> mchl(preds, target)
221
+ tensor(0.9125)
222
+ >>> mchl = MulticlassHingeLoss(num_classes=3, squared=True)
223
+ >>> mchl(preds, target)
224
+ tensor(1.1131)
225
+ >>> mchl = MulticlassHingeLoss(num_classes=3, multiclass_mode='one-vs-all')
226
+ >>> mchl(preds, target)
227
+ tensor([0.8750, 1.1250, 1.1000])
228
+
229
+ """
230
+
231
+ is_differentiable: bool = True
232
+ higher_is_better: bool = False
233
+ full_state_update: bool = False
234
+ plot_lower_bound: float = 0.0
235
+ plot_upper_bound: float = 1.0
236
+ plot_legend_name: str = "Class"
237
+
238
+ measures: Tensor
239
+ total: Tensor
240
+
241
+ def __init__(
242
+ self,
243
+ num_classes: int,
244
+ squared: bool = False,
245
+ multiclass_mode: Literal["crammer-singer", "one-vs-all"] = "crammer-singer",
246
+ ignore_index: Optional[int] = None,
247
+ validate_args: bool = True,
248
+ **kwargs: Any,
249
+ ) -> None:
250
+ super().__init__(**kwargs)
251
+ if validate_args:
252
+ _multiclass_hinge_loss_arg_validation(num_classes, squared, multiclass_mode, ignore_index)
253
+ self.validate_args = validate_args
254
+ self.num_classes = num_classes
255
+ self.squared = squared
256
+ self.multiclass_mode = multiclass_mode
257
+ self.ignore_index = ignore_index
258
+
259
+ self.add_state(
260
+ "measures",
261
+ default=torch.tensor(0.0)
262
+ if self.multiclass_mode == "crammer-singer"
263
+ else torch.zeros(
264
+ num_classes,
265
+ ),
266
+ dist_reduce_fx="sum",
267
+ )
268
+ self.add_state("total", default=torch.tensor(0), dist_reduce_fx="sum")
269
+
270
+ def update(self, preds: Tensor, target: Tensor) -> None:
271
+ """Update metric state."""
272
+ if self.validate_args:
273
+ _multiclass_hinge_loss_tensor_validation(preds, target, self.num_classes, self.ignore_index)
274
+ preds, target = _multiclass_confusion_matrix_format(preds, target, self.ignore_index, convert_to_labels=False)
275
+ measures, total = _multiclass_hinge_loss_update(preds, target, self.squared, self.multiclass_mode)
276
+ self.measures += measures
277
+ self.total += total
278
+
279
+ def compute(self) -> Tensor:
280
+ """Compute metric."""
281
+ return _hinge_loss_compute(self.measures, self.total)
282
+
283
+ def plot(
284
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
285
+ ) -> _PLOT_OUT_TYPE:
286
+ """Plot a single or multiple values from the metric.
287
+
288
+ Args:
289
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
290
+ If no value is provided, will automatically call `metric.compute` and plot that result.
291
+ ax: An matplotlib axis object. If provided will add plot to that axis
292
+
293
+ Returns:
294
+ Figure object and Axes object
295
+
296
+ Raises:
297
+ ModuleNotFoundError:
298
+ If `matplotlib` is not installed
299
+
300
+ .. plot::
301
+ :scale: 75
302
+
303
+ >>> # Example plotting a single value per class
304
+ >>> from torch import randint, randn
305
+ >>> from torchmetrics.classification import MulticlassHingeLoss
306
+ >>> metric = MulticlassHingeLoss(num_classes=3)
307
+ >>> metric.update(randn(20, 3), randint(3, (20,)))
308
+ >>> fig_, ax_ = metric.plot()
309
+
310
+ .. plot::
311
+ :scale: 75
312
+
313
+ >>> # Example plotting a multiple values per class
314
+ >>> from torch import randint, randn
315
+ >>> from torchmetrics.classification import MulticlassHingeLoss
316
+ >>> metric = MulticlassHingeLoss(num_classes=3)
317
+ >>> values = []
318
+ >>> for _ in range(20):
319
+ ... values.append(metric(randn(20, 3), randint(3, (20,))))
320
+ >>> fig_, ax_ = metric.plot(values)
321
+
322
+ """
323
+ return self._plot(val, ax)
324
+
325
+
326
+ class HingeLoss(_ClassificationTaskWrapper):
327
+ r"""Compute the mean `Hinge loss`_ typically used for Support Vector Machines (SVMs).
328
+
329
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
330
+ ``task`` argument to either ``'binary'`` or ``'multiclass'``. See the documentation of
331
+ :class:`~torchmetrics.classification.BinaryHingeLoss` and :class:`~torchmetrics.classification.MulticlassHingeLoss`
332
+ for the specific details of each argument influence and examples.
333
+
334
+ Legacy Example:
335
+ >>> from torch import tensor
336
+ >>> target = tensor([0, 1, 1])
337
+ >>> preds = tensor([0.5, 0.7, 0.1])
338
+ >>> hinge = HingeLoss(task="binary")
339
+ >>> hinge(preds, target)
340
+ tensor(0.9000)
341
+
342
+ >>> target = tensor([0, 1, 2])
343
+ >>> preds = tensor([[-1.0, 0.9, 0.2], [0.5, -1.1, 0.8], [2.2, -0.5, 0.3]])
344
+ >>> hinge = HingeLoss(task="multiclass", num_classes=3)
345
+ >>> hinge(preds, target)
346
+ tensor(1.5551)
347
+
348
+ >>> target = tensor([0, 1, 2])
349
+ >>> preds = tensor([[-1.0, 0.9, 0.2], [0.5, -1.1, 0.8], [2.2, -0.5, 0.3]])
350
+ >>> hinge = HingeLoss(task="multiclass", num_classes=3, multiclass_mode="one-vs-all")
351
+ >>> hinge(preds, target)
352
+ tensor([1.3743, 1.1945, 1.2359])
353
+
354
+ """
355
+
356
+ def __new__( # type: ignore[misc]
357
+ cls: type["HingeLoss"],
358
+ task: Literal["binary", "multiclass"],
359
+ num_classes: Optional[int] = None,
360
+ squared: bool = False,
361
+ multiclass_mode: Optional[Literal["crammer-singer", "one-vs-all"]] = "crammer-singer",
362
+ ignore_index: Optional[int] = None,
363
+ validate_args: bool = True,
364
+ **kwargs: Any,
365
+ ) -> Metric:
366
+ """Initialize task metric."""
367
+ task = ClassificationTaskNoMultilabel.from_str(task)
368
+ kwargs.update({"ignore_index": ignore_index, "validate_args": validate_args})
369
+ if task == ClassificationTaskNoMultilabel.BINARY:
370
+ return BinaryHingeLoss(squared, **kwargs)
371
+ if task == ClassificationTaskNoMultilabel.MULTICLASS:
372
+ if not isinstance(num_classes, int):
373
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
374
+ if multiclass_mode not in ("crammer-singer", "one-vs-all"):
375
+ raise ValueError(
376
+ f"`multiclass_mode` is expected to be one of 'crammer-singer' or 'one-vs-all' but "
377
+ f"`{multiclass_mode}` was passed."
378
+ )
379
+ return MulticlassHingeLoss(num_classes, squared, multiclass_mode, **kwargs)
380
+ raise ValueError(f"Unsupported task `{task}`")
rtme/lib/python3.10/site-packages/torchmetrics/classification/jaccard.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.confusion_matrix import (
22
+ BinaryConfusionMatrix,
23
+ MulticlassConfusionMatrix,
24
+ MultilabelConfusionMatrix,
25
+ )
26
+ from torchmetrics.functional.classification.jaccard import (
27
+ _jaccard_index_reduce,
28
+ _multiclass_jaccard_index_arg_validation,
29
+ _multilabel_jaccard_index_arg_validation,
30
+ )
31
+ from torchmetrics.metric import Metric
32
+ from torchmetrics.utilities.enums import ClassificationTask
33
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
34
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
35
+
36
+ if not _MATPLOTLIB_AVAILABLE:
37
+ __doctest_skip__ = ["BinaryJaccardIndex.plot", "MulticlassJaccardIndex.plot", "MultilabelJaccardIndex.plot"]
38
+
39
+
40
+ class BinaryJaccardIndex(BinaryConfusionMatrix):
41
+ r"""Calculate the Jaccard index for binary tasks.
42
+
43
+ The `Jaccard index`_ (also known as the intersection over union or jaccard similarity coefficient) is an statistic
44
+ that can be used to determine the similarity and diversity of a sample set. It is defined as the size of the
45
+ intersection divided by the union of the sample sets:
46
+
47
+ .. math:: J(A,B) = \frac{|A\cap B|}{|A\cup B|}
48
+
49
+ As input to ``forward`` and ``update`` the metric accepts the following input:
50
+
51
+ - ``preds`` (:class:`~torch.Tensor`): A int or float tensor of shape ``(N, ...)``. If preds is a floating point
52
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per element.
53
+ Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
54
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
55
+
56
+ .. tip::
57
+ Additional dimension ``...`` will be flattened into the batch dimension.
58
+
59
+ As output to ``forward`` and ``compute`` the metric returns the following output:
60
+
61
+ - ``bji`` (:class:`~torch.Tensor`): A tensor containing the Binary Jaccard Index.
62
+
63
+ Args:
64
+ threshold: Threshold for transforming probability to binary (0,1) predictions
65
+ ignore_index:
66
+ Specifies a target value that is ignored and does not contribute to the metric calculation
67
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
68
+ Set to ``False`` for faster computations.
69
+ zero_division:
70
+ Value to replace when there is a division by zero. Should be `0` or `1`.
71
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
72
+
73
+ Example (preds is int tensor):
74
+ >>> from torch import tensor
75
+ >>> from torchmetrics.classification import BinaryJaccardIndex
76
+ >>> target = tensor([1, 1, 0, 0])
77
+ >>> preds = tensor([0, 1, 0, 0])
78
+ >>> metric = BinaryJaccardIndex()
79
+ >>> metric(preds, target)
80
+ tensor(0.5000)
81
+
82
+ Example (preds is float tensor):
83
+ >>> from torchmetrics.classification import BinaryJaccardIndex
84
+ >>> target = tensor([1, 1, 0, 0])
85
+ >>> preds = tensor([0.35, 0.85, 0.48, 0.01])
86
+ >>> metric = BinaryJaccardIndex()
87
+ >>> metric(preds, target)
88
+ tensor(0.5000)
89
+
90
+ """
91
+
92
+ is_differentiable: bool = False
93
+ higher_is_better: bool = True
94
+ full_state_update: bool = False
95
+ plot_lower_bound: float = 0.0
96
+ plot_upper_bound: float = 1.0
97
+
98
+ def __init__(
99
+ self,
100
+ threshold: float = 0.5,
101
+ ignore_index: Optional[int] = None,
102
+ validate_args: bool = True,
103
+ zero_division: float = 0,
104
+ **kwargs: Any,
105
+ ) -> None:
106
+ super().__init__(
107
+ threshold=threshold, ignore_index=ignore_index, normalize=None, validate_args=validate_args, **kwargs
108
+ )
109
+ self.zero_division = zero_division
110
+
111
+ def compute(self) -> Tensor:
112
+ """Compute metric."""
113
+ return _jaccard_index_reduce(self.confmat, average="binary", zero_division=self.zero_division)
114
+
115
+ def plot( # type: ignore[override]
116
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
117
+ ) -> _PLOT_OUT_TYPE:
118
+ """Plot a single or multiple values from the metric.
119
+
120
+ Args:
121
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
122
+ If no value is provided, will automatically call `metric.compute` and plot that result.
123
+ ax: An matplotlib axis object. If provided will add plot to that axis
124
+
125
+ Returns:
126
+ Figure object and Axes object
127
+
128
+ Raises:
129
+ ModuleNotFoundError:
130
+ If `matplotlib` is not installed
131
+
132
+ .. plot::
133
+ :scale: 75
134
+
135
+ >>> # Example plotting a single value
136
+ >>> from torch import rand, randint
137
+ >>> from torchmetrics.classification import BinaryJaccardIndex
138
+ >>> metric = BinaryJaccardIndex()
139
+ >>> metric.update(rand(10), randint(2,(10,)))
140
+ >>> fig_, ax_ = metric.plot()
141
+
142
+ .. plot::
143
+ :scale: 75
144
+
145
+ >>> # Example plotting multiple values
146
+ >>> from torch import rand, randint
147
+ >>> from torchmetrics.classification import BinaryJaccardIndex
148
+ >>> metric = BinaryJaccardIndex()
149
+ >>> values = [ ]
150
+ >>> for _ in range(10):
151
+ ... values.append(metric(rand(10), randint(2,(10,))))
152
+ >>> fig_, ax_ = metric.plot(values)
153
+
154
+ """
155
+ return self._plot(val, ax)
156
+
157
+
158
+ class MulticlassJaccardIndex(MulticlassConfusionMatrix):
159
+ r"""Calculate the Jaccard index for multiclass tasks.
160
+
161
+ The `Jaccard index`_ (also known as the intersection over union or jaccard similarity coefficient) is an statistic
162
+ that can be used to determine the similarity and diversity of a sample set. It is defined as the size of the
163
+ intersection divided by the union of the sample sets:
164
+
165
+ .. math:: J(A,B) = \frac{|A\cap B|}{|A\cup B|}
166
+
167
+ As input to ``forward`` and ``update`` the metric accepts the following input:
168
+
169
+ - ``preds`` (:class:`~torch.Tensor`): A int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
170
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
171
+ probabilities/logits into an int tensor.
172
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
173
+
174
+ .. tip::
175
+ Additional dimension ``...`` will be flattened into the batch dimension.
176
+
177
+ As output to ``forward`` and ``compute`` the metric returns the following output:
178
+
179
+ - ``mcji`` (:class:`~torch.Tensor`): A tensor containing the Multi-class Jaccard Index.
180
+
181
+ Args:
182
+ num_classes: Integer specifying the number of classes
183
+ ignore_index:
184
+ Specifies a target value that is ignored and does not contribute to the metric calculation
185
+ average:
186
+ Defines the reduction that is applied over labels. Should be one of the following:
187
+
188
+ - ``micro``: Sum statistics over all labels
189
+ - ``macro``: Calculate statistics for each label and average them
190
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
191
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
192
+
193
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
194
+ Set to ``False`` for faster computations.
195
+ zero_division:
196
+ Value to replace when there is a division by zero. Should be `0` or `1`.
197
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
198
+
199
+ Example (pred is integer tensor):
200
+ >>> from torch import tensor
201
+ >>> from torchmetrics.classification import MulticlassJaccardIndex
202
+ >>> target = tensor([2, 1, 0, 0])
203
+ >>> preds = tensor([2, 1, 0, 1])
204
+ >>> metric = MulticlassJaccardIndex(num_classes=3)
205
+ >>> metric(preds, target)
206
+ tensor(0.6667)
207
+
208
+ Example (pred is float tensor):
209
+ >>> from torchmetrics.classification import MulticlassJaccardIndex
210
+ >>> target = tensor([2, 1, 0, 0])
211
+ >>> preds = tensor([[0.16, 0.26, 0.58],
212
+ ... [0.22, 0.61, 0.17],
213
+ ... [0.71, 0.09, 0.20],
214
+ ... [0.05, 0.82, 0.13]])
215
+ >>> metric = MulticlassJaccardIndex(num_classes=3)
216
+ >>> metric(preds, target)
217
+ tensor(0.6667)
218
+
219
+ """
220
+
221
+ is_differentiable: bool = False
222
+ higher_is_better: bool = True
223
+ full_state_update: bool = False
224
+ plot_lower_bound: float = 0.0
225
+ plot_upper_bound: float = 1.0
226
+ plot_legend_name: str = "Class"
227
+
228
+ def __init__(
229
+ self,
230
+ num_classes: int,
231
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
232
+ ignore_index: Optional[int] = None,
233
+ validate_args: bool = True,
234
+ zero_division: float = 0,
235
+ **kwargs: Any,
236
+ ) -> None:
237
+ super().__init__(
238
+ num_classes=num_classes, ignore_index=ignore_index, normalize=None, validate_args=False, **kwargs
239
+ )
240
+ if validate_args:
241
+ _multiclass_jaccard_index_arg_validation(num_classes, ignore_index, average)
242
+ self.validate_args = validate_args
243
+ self.average = average
244
+ self.zero_division = zero_division
245
+
246
+ def compute(self) -> Tensor:
247
+ """Compute metric."""
248
+ return _jaccard_index_reduce(
249
+ self.confmat, average=self.average, ignore_index=self.ignore_index, zero_division=self.zero_division
250
+ )
251
+
252
+ def plot( # type: ignore[override]
253
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
254
+ ) -> _PLOT_OUT_TYPE:
255
+ """Plot a single or multiple values from the metric.
256
+
257
+ Args:
258
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
259
+ If no value is provided, will automatically call `metric.compute` and plot that result.
260
+ ax: An matplotlib axis object. If provided will add plot to that axis
261
+
262
+ Returns:
263
+ Figure object and Axes object
264
+
265
+ Raises:
266
+ ModuleNotFoundError:
267
+ If `matplotlib` is not installed
268
+
269
+ .. plot::
270
+ :scale: 75
271
+
272
+ >>> # Example plotting a single value per class
273
+ >>> from torch import randint
274
+ >>> from torchmetrics.classification import MulticlassJaccardIndex
275
+ >>> metric = MulticlassJaccardIndex(num_classes=3, average=None)
276
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
277
+ >>> fig_, ax_ = metric.plot()
278
+
279
+ .. plot::
280
+ :scale: 75
281
+
282
+ >>> # Example plotting a multiple values per class
283
+ >>> from torch import randint
284
+ >>> from torchmetrics.classification import MulticlassJaccardIndex
285
+ >>> metric = MulticlassJaccardIndex(num_classes=3, average=None)
286
+ >>> values = []
287
+ >>> for _ in range(20):
288
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
289
+ >>> fig_, ax_ = metric.plot(values)
290
+
291
+ """
292
+ return self._plot(val, ax)
293
+
294
+
295
+ class MultilabelJaccardIndex(MultilabelConfusionMatrix):
296
+ r"""Calculate the Jaccard index for multilabel tasks.
297
+
298
+ The `Jaccard index`_ (also known as the intersection over union or jaccard similarity coefficient) is an statistic
299
+ that can be used to determine the similarity and diversity of a sample set. It is defined as the size of the
300
+ intersection divided by the union of the sample sets:
301
+
302
+ .. math:: J(A,B) = \frac{|A\cap B|}{|A\cup B|}
303
+
304
+ As input to ``forward`` and ``update`` the metric accepts the following input:
305
+
306
+ - ``preds`` (:class:`~torch.Tensor`): A int tensor or float tensor of shape ``(N, C, ...)``. If preds is a
307
+ floating point tensor with values outside [0,1] range we consider the input to be logits and will auto apply
308
+ sigmoid per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
309
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
310
+
311
+ .. tip::
312
+ Additional dimension ``...`` will be flattened into the batch dimension.
313
+
314
+ As output to ``forward`` and ``compute`` the metric returns the following output:
315
+
316
+ - ``mlji`` (:class:`~torch.Tensor`): A tensor containing the Multi-label Jaccard Index loss.
317
+
318
+ Args:
319
+ num_classes: Integer specifying the number of labels
320
+ threshold: Threshold for transforming probability to binary (0,1) predictions
321
+ ignore_index:
322
+ Specifies a target value that is ignored and does not contribute to the metric calculation
323
+ average:
324
+ Defines the reduction that is applied over labels. Should be one of the following:
325
+
326
+ - ``micro``: Sum statistics over all labels
327
+ - ``macro``: Calculate statistics for each label and average them
328
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
329
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
330
+
331
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
332
+ Set to ``False`` for faster computations.
333
+ zero_division:
334
+ Value to replace when there is a division by zero. Should be `0` or `1`.
335
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
336
+
337
+ Example (preds is int tensor):
338
+ >>> from torch import tensor
339
+ >>> from torchmetrics.classification import MultilabelJaccardIndex
340
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
341
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
342
+ >>> metric = MultilabelJaccardIndex(num_labels=3)
343
+ >>> metric(preds, target)
344
+ tensor(0.5000)
345
+
346
+ Example (preds is float tensor):
347
+ >>> from torchmetrics.classification import MultilabelJaccardIndex
348
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
349
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
350
+ >>> metric = MultilabelJaccardIndex(num_labels=3)
351
+ >>> metric(preds, target)
352
+ tensor(0.5000)
353
+
354
+ """
355
+
356
+ is_differentiable: bool = False
357
+ higher_is_better: bool = True
358
+ full_state_update: bool = False
359
+ plot_lower_bound: float = 0.0
360
+ plot_upper_bound: float = 1.0
361
+ plot_legend_name: str = "Label"
362
+
363
+ def __init__(
364
+ self,
365
+ num_labels: int,
366
+ threshold: float = 0.5,
367
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
368
+ ignore_index: Optional[int] = None,
369
+ validate_args: bool = True,
370
+ zero_division: float = 0,
371
+ **kwargs: Any,
372
+ ) -> None:
373
+ super().__init__(
374
+ num_labels=num_labels,
375
+ threshold=threshold,
376
+ ignore_index=ignore_index,
377
+ normalize=None,
378
+ validate_args=False,
379
+ **kwargs,
380
+ )
381
+ if validate_args:
382
+ _multilabel_jaccard_index_arg_validation(num_labels, threshold, ignore_index, average)
383
+ self.validate_args = validate_args
384
+ self.average = average
385
+ self.zero_division = zero_division
386
+
387
+ def compute(self) -> Tensor:
388
+ """Compute metric."""
389
+ return _jaccard_index_reduce(self.confmat, average=self.average, zero_division=self.zero_division)
390
+
391
+ def plot( # type: ignore[override]
392
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
393
+ ) -> _PLOT_OUT_TYPE:
394
+ """Plot a single or multiple values from the metric.
395
+
396
+ Args:
397
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
398
+ If no value is provided, will automatically call `metric.compute` and plot that result.
399
+ ax: An matplotlib axis object. If provided will add plot to that axis
400
+
401
+ Returns:
402
+ Figure and Axes object
403
+
404
+ Raises:
405
+ ModuleNotFoundError:
406
+ If `matplotlib` is not installed
407
+
408
+ .. plot::
409
+ :scale: 75
410
+
411
+ >>> # Example plotting a single value
412
+ >>> from torch import rand, randint
413
+ >>> from torchmetrics.classification import MultilabelJaccardIndex
414
+ >>> metric = MultilabelJaccardIndex(num_labels=3)
415
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
416
+ >>> fig_, ax_ = metric.plot()
417
+
418
+ .. plot::
419
+ :scale: 75
420
+
421
+ >>> # Example plotting multiple values
422
+ >>> from torch import rand, randint
423
+ >>> from torchmetrics.classification import MultilabelJaccardIndex
424
+ >>> metric = MultilabelJaccardIndex(num_labels=3)
425
+ >>> values = [ ]
426
+ >>> for _ in range(10):
427
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
428
+ >>> fig_, ax_ = metric.plot(values)
429
+
430
+ """
431
+ return self._plot(val, ax)
432
+
433
+
434
+ class JaccardIndex(_ClassificationTaskWrapper):
435
+ r"""Calculate the Jaccard index for multilabel tasks.
436
+
437
+ The `Jaccard index`_ (also known as the intersection over union or jaccard similarity coefficient) is an statistic
438
+ that can be used to determine the similarity and diversity of a sample set. It is defined as the size of the
439
+ intersection divided by the union of the sample sets:
440
+
441
+ .. math:: J(A,B) = \frac{|A\cap B|}{|A\cup B|}
442
+
443
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
444
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
445
+ :class:`~torchmetrics.classification.BinaryJaccardIndex`,
446
+ :class:`~torchmetrics.classification.MulticlassJaccardIndex` and
447
+ :class:`~torchmetrics.classification.MultilabelJaccardIndex` for the specific details of each argument influence
448
+ and examples.
449
+
450
+ Legacy Example:
451
+ >>> from torch import randint, tensor
452
+ >>> target = randint(0, 2, (10, 25, 25))
453
+ >>> pred = tensor(target)
454
+ >>> pred[2:5, 7:13, 9:15] = 1 - pred[2:5, 7:13, 9:15]
455
+ >>> jaccard = JaccardIndex(task="multiclass", num_classes=2)
456
+ >>> jaccard(pred, target)
457
+ tensor(0.9660)
458
+
459
+ """
460
+
461
+ def __new__( # type: ignore[misc]
462
+ cls: type["JaccardIndex"],
463
+ task: Literal["binary", "multiclass", "multilabel"],
464
+ threshold: float = 0.5,
465
+ num_classes: Optional[int] = None,
466
+ num_labels: Optional[int] = None,
467
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
468
+ ignore_index: Optional[int] = None,
469
+ validate_args: bool = True,
470
+ **kwargs: Any,
471
+ ) -> Metric:
472
+ """Initialize task metric."""
473
+ task = ClassificationTask.from_str(task)
474
+ kwargs.update({"ignore_index": ignore_index, "validate_args": validate_args})
475
+ if task == ClassificationTask.BINARY:
476
+ return BinaryJaccardIndex(threshold, **kwargs)
477
+ if task == ClassificationTask.MULTICLASS:
478
+ if not isinstance(num_classes, int):
479
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
480
+ return MulticlassJaccardIndex(num_classes, average, **kwargs)
481
+ if task == ClassificationTask.MULTILABEL:
482
+ if not isinstance(num_labels, int):
483
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
484
+ return MultilabelJaccardIndex(num_labels, threshold, average, **kwargs)
485
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/logauc.py ADDED
@@ -0,0 +1,507 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, List, Optional, Sequence, Tuple, Type, Union
15
+
16
+ from torch import Tensor
17
+ from typing_extensions import Literal
18
+
19
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
20
+ from torchmetrics.classification.roc import BinaryROC, MulticlassROC, MultilabelROC
21
+ from torchmetrics.functional.classification.logauc import (
22
+ _binary_logauc_compute,
23
+ _reduce_logauc,
24
+ _validate_fpr_range,
25
+ )
26
+ from torchmetrics.metric import Metric
27
+ from torchmetrics.utilities.enums import ClassificationTask
28
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
29
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
30
+
31
+ if not _MATPLOTLIB_AVAILABLE:
32
+ __doctest_skip__ = ["BinaryLogAUC.plot", "MulticlassLogAUC.plot", "MultilabelLogAUC.plot"]
33
+
34
+
35
+ class BinaryLogAUC(BinaryROC):
36
+ r"""Compute the `Log AUC`_ score for binary classification tasks.
37
+
38
+ The score is computed by first computing the ROC curve, which then is interpolated to the specified range of false
39
+ positive rates (FPR) and then the log is taken of the FPR before the area under the curve (AUC) is computed. The
40
+ score is commonly used in applications where the positive and negative are imbalanced and a low false positive rate
41
+ is of high importance.
42
+
43
+ As input to ``forward`` and ``update`` the metric accepts the following input:
44
+
45
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)`` containing probabilities or logits for
46
+ each observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
47
+ sigmoid per element.
48
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` containing ground truth labels, and
49
+ therefore only contain {0,1} values (except if `ignore_index` is specified). The value 1 always encodes the
50
+ positive class.
51
+
52
+ As output to ``forward`` and ``compute`` the metric returns the following output:
53
+
54
+ - ``logauc`` (:class:`~torch.Tensor`): A single scalar with the logauc score.
55
+
56
+ Additional dimension ``...`` will be flattened into the batch dimension.
57
+
58
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
59
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
60
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
61
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
62
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
63
+
64
+ Args:
65
+ fpr_range: 2-element tuple with the lower and upper bound of the false positive rate range to compute the log
66
+ AUC score.
67
+ thresholds:
68
+ Can be one of:
69
+
70
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
71
+ all the data. Most accurate but also most memory consuming approach.
72
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
73
+ 0 to 1 as bins for the calculation.
74
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
75
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
76
+ bins for the calculation.
77
+
78
+ ignore_index:
79
+ Specifies a target value that is ignored and does not contribute to the metric calculation
80
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
81
+ Set to ``False`` for faster computations.
82
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
83
+
84
+ Example:
85
+ >>> from torch import tensor
86
+ >>> from torchmetrics.classification import BinaryLogAUC
87
+ >>> preds = tensor([0.75, 0.05, 0.05, 0.05, 0.05])
88
+ >>> target = tensor([1, 0, 0, 0, 0])
89
+ >>> metric = BinaryLogAUC()
90
+ >>> metric(preds, target)
91
+ tensor(1.)
92
+
93
+ """
94
+
95
+ is_differentiable: bool = False
96
+ higher_is_better: bool = True
97
+ full_state_update: bool = False
98
+ plot_lower_bound: float = 0.0
99
+ plot_upper_bound: float = 1.0
100
+
101
+ def __init__(
102
+ self,
103
+ fpr_range: Tuple[float, float] = (0.001, 0.1),
104
+ thresholds: Optional[Union[int, List[float], Tensor]] = None,
105
+ ignore_index: Optional[int] = None,
106
+ validate_args: bool = False,
107
+ **kwargs: Any,
108
+ ) -> None:
109
+ super().__init__(thresholds=thresholds, ignore_index=ignore_index, validate_args=validate_args, **kwargs)
110
+ if validate_args:
111
+ _validate_fpr_range(fpr_range)
112
+ self.fpr_range = fpr_range
113
+
114
+ def compute(self) -> Tensor: # type: ignore[override]
115
+ """Computes the log AUC score."""
116
+ fpr, tpr, _ = super().compute()
117
+ return _binary_logauc_compute(fpr, tpr, fpr_range=self.fpr_range)
118
+
119
+ def plot( # type: ignore[override]
120
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
121
+ ) -> _PLOT_OUT_TYPE:
122
+ """Plot a single or multiple values from the metric.
123
+
124
+ Args:
125
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
126
+ If no value is provided, will automatically call `metric.compute` and plot that result.
127
+ ax: An matplotlib axis object. If provided will add plot to that axis
128
+
129
+ Returns:
130
+ Figure and Axes object
131
+
132
+ Raises:
133
+ ModuleNotFoundError:
134
+ If `matplotlib` is not installed
135
+
136
+ .. plot::
137
+ :scale: 75
138
+
139
+ >>> # Example plotting a single
140
+ >>> import torch
141
+ >>> from torchmetrics.classification import BinaryLogAUC
142
+ >>> metric = BinaryLogAUC()
143
+ >>> metric.update(torch.rand(20,), torch.randint(2, (20,)))
144
+ >>> fig_, ax_ = metric.plot()
145
+
146
+ .. plot::
147
+ :scale: 75
148
+
149
+ >>> # Example plotting multiple values
150
+ >>> import torch
151
+ >>> from torchmetrics.classification import BinaryLogAUC
152
+ >>> metric = BinaryLogAUC()
153
+ >>> values = [ ]
154
+ >>> for _ in range(10):
155
+ ... values.append(metric(torch.rand(20,), torch.randint(2, (20,))))
156
+ >>> fig_, ax_ = metric.plot(values)
157
+
158
+ """
159
+ return self._plot(val, ax)
160
+
161
+
162
+ class MulticlassLogAUC(MulticlassROC):
163
+ r"""Compute the `Log AUC`_ score for multiclass classification tasks.
164
+
165
+ The score is computed by first computing the ROC curve, which then is interpolated to the specified range of false
166
+ positive rates (FPR) and then the log is taken of the FPR before the area under the curve (AUC) is computed. The
167
+ score is commonly used in applications where the positive and negative are imbalanced and a low false positive rate
168
+ is of high importance.
169
+
170
+ As input to ``forward`` and ``update`` the metric accepts the following input:
171
+
172
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)`` containing probabilities or logits
173
+ for each observation. If preds has values outside [0,1] range we consider the input to be logits and will auto
174
+ apply softmax per sample.
175
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` containing ground truth labels, and
176
+ therefore only contain values in the [0, n_classes-1] range (except if `ignore_index` is specified).
177
+
178
+ As output to ``forward`` and ``compute`` the metric returns the following output:
179
+
180
+ - ``logauc`` (:class:`~torch.Tensor`): If `average=None|"none"` then a 1d tensor of shape (n_classes, ) will
181
+ be returned with logauc score per class. If `average="macro"` then a single scalar is returned.
182
+
183
+ Additional dimension ``...`` will be flattened into the batch dimension.
184
+
185
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
186
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
187
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
188
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
189
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
190
+
191
+ Args:
192
+ num_classes: Integer specifying the number of classes
193
+ fpr_range: 2-element tuple with the lower and upper bound of the false positive rate range to compute the log
194
+ AUC score.
195
+ average:
196
+ Defines the reduction that is applied over classes. Should be one of the following:
197
+
198
+ - ``"macro"``: Calculate score for each class and average them
199
+ - ``"weighted"``: calculates score for each class and computes weighted average using their support
200
+ - ``"none"`` or ``None``: calculates score for each class and applies no reduction
201
+
202
+ thresholds:
203
+ Can be one of:
204
+
205
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
206
+ all the data. Most accurate but also most memory consuming approach.
207
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
208
+ 0 to 1 as bins for the calculation.
209
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
210
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
211
+ bins for the calculation.
212
+
213
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
214
+ Set to ``False`` for faster computations.
215
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
216
+
217
+ Example:
218
+ >>> from torch import tensor
219
+ >>> from torchmetrics.classification import MulticlassLogAUC
220
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
221
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
222
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
223
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
224
+ >>> target = tensor([0, 1, 3, 2])
225
+ >>> metric = MulticlassLogAUC(num_classes=5, average="macro", thresholds=None)
226
+ >>> metric(preds, target)
227
+ tensor(0.4000)
228
+ >>> metric = MulticlassLogAUC(num_classes=5, average=None, thresholds=None)
229
+ >>> metric(preds, target)
230
+ tensor([1., 1., 0., 0., 0.])
231
+
232
+ """
233
+
234
+ is_differentiable: bool = False
235
+ higher_is_better: bool = True
236
+ full_state_update: bool = False
237
+ plot_lower_bound: float = 0.0
238
+ plot_upper_bound: float = 1.0
239
+ plot_legend_name: str = "Class"
240
+
241
+ def __init__(
242
+ self,
243
+ num_classes: int,
244
+ fpr_range: Tuple[float, float] = (0.001, 0.1),
245
+ average: Optional[Literal["macro", "none"]] = None,
246
+ thresholds: Optional[Union[int, List[float], Tensor]] = None,
247
+ ignore_index: Optional[int] = None,
248
+ validate_args: bool = True,
249
+ **kwargs: Any,
250
+ ) -> None:
251
+ super().__init__(
252
+ num_classes=num_classes,
253
+ thresholds=thresholds,
254
+ average=None,
255
+ ignore_index=ignore_index,
256
+ validate_args=validate_args,
257
+ **kwargs,
258
+ )
259
+ if validate_args:
260
+ _validate_fpr_range(fpr_range)
261
+ self.fpr_range = fpr_range
262
+ self.average2 = average # self.average is already used by parent class
263
+
264
+ def compute(self) -> Tensor: # type: ignore[override]
265
+ """Computes the log AUC score."""
266
+ fpr, tpr, _ = super().compute()
267
+ return _reduce_logauc(fpr, tpr, fpr_range=self.fpr_range, average=self.average2)
268
+
269
+ def plot( # type: ignore[override]
270
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
271
+ ) -> _PLOT_OUT_TYPE:
272
+ """Plot a single or multiple values from the metric.
273
+
274
+ Args:
275
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
276
+ If no value is provided, will automatically call `metric.compute` and plot that result.
277
+ ax: An matplotlib axis object. If provided will add plot to that axis
278
+
279
+ Returns:
280
+ Figure and Axes object
281
+
282
+ Raises:
283
+ ModuleNotFoundError:
284
+ If `matplotlib` is not installed
285
+
286
+ .. plot::
287
+ :scale: 75
288
+
289
+ >>> # Example plotting a single
290
+ >>> import torch
291
+ >>> from torchmetrics.classification import MulticlassLogAUC
292
+ >>> metric = MulticlassLogAUC(num_classes=3)
293
+ >>> metric.update(torch.randn(20, 3), torch.randint(3,(20,)))
294
+ >>> fig_, ax_ = metric.plot()
295
+
296
+ .. plot::
297
+ :scale: 75
298
+
299
+ >>> # Example plotting multiple values
300
+ >>> import torch
301
+ >>> from torchmetrics.classification import MulticlassLogAUC
302
+ >>> metric = MulticlassLogAUC(num_classes=3)
303
+ >>> values = [ ]
304
+ >>> for _ in range(10):
305
+ ... values.append(metric(torch.randn(20, 3), torch.randint(3, (20,))))
306
+ >>> fig_, ax_ = metric.plot(values)
307
+
308
+ """
309
+ return self._plot(val, ax)
310
+
311
+
312
+ class MultilabelLogAUC(MultilabelROC):
313
+ r"""Compute the `Log AUC`_ score for multiclass classification tasks.
314
+
315
+ The score is computed by first computing the ROC curve, which then is interpolated to the specified range of false
316
+ positive rates (FPR) and then the log is taken of the FPR before the area under the curve (AUC) is computed. The
317
+ score is commonly used in applications where the positive and negative are imbalanced and a low false positive rate
318
+ is of high importance.
319
+
320
+ As input to ``forward`` and ``update`` the metric accepts the following input:
321
+
322
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)`` containing probabilities or logits
323
+ for each observation. If preds has values outside [0,1] range we consider the input to be logits and will auto
324
+ apply sigmoid per element.
325
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)`` containing ground truth labels, and
326
+ therefore only contain {0,1} values (except if `ignore_index` is specified).
327
+
328
+ As output to ``forward`` and ``compute`` the metric returns the following output:
329
+
330
+ - ``logauc`` (:class:`~torch.Tensor`): If `average=None|"none"` then a 1d tensor of shape (num_labels, ) will
331
+ be returned with logauc score per class. If `average="macro"` then a single scalar is returned.
332
+
333
+ Additional dimension ``...`` will be flattened into the batch dimension.
334
+
335
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
336
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
337
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
338
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
339
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
340
+
341
+ Args:
342
+ num_labels: Integer specifying the number of labels
343
+ fpr_range: 2-element tuple with the lower and upper bound of the false positive rate range to compute the log
344
+ AUC score.
345
+ average:
346
+ Defines the reduction that is applied over labels. Should be one of the following:
347
+
348
+ - ``"macro"``: Calculate the score for each label and average them
349
+ - ``"none"`` or ``None``: calculates score for each label and applies no reduction
350
+ thresholds:
351
+ Can be one of:
352
+
353
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
354
+ all the data. Most accurate but also most memory consuming approach.
355
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
356
+ 0 to 1 as bins for the calculation.
357
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
358
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
359
+ bins for the calculation.
360
+
361
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
362
+ Set to ``False`` for faster computations.
363
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
364
+
365
+ Example:
366
+ >>> from torch import tensor
367
+ >>> from torchmetrics.classification import MultilabelLogAUC
368
+ >>> preds = tensor([[0.75, 0.05, 0.35],
369
+ ... [0.45, 0.75, 0.05],
370
+ ... [0.05, 0.55, 0.75],
371
+ ... [0.05, 0.65, 0.05]])
372
+ >>> target = tensor([[1, 0, 1],
373
+ ... [0, 0, 0],
374
+ ... [0, 1, 1],
375
+ ... [1, 1, 1]])
376
+ >>> metric = MultilabelLogAUC(num_labels=3, average="macro", thresholds=None)
377
+ >>> metric(preds, target)
378
+ tensor(0.3945)
379
+ >>> metric = MultilabelLogAUC(num_labels=3, average=None, thresholds=None)
380
+ >>> metric(preds, target)
381
+ tensor([0.5000, 0.0000, 0.6835])
382
+
383
+ """
384
+
385
+ is_differentiable: bool = False
386
+ higher_is_better: bool = True
387
+ full_state_update: bool = False
388
+ plot_lower_bound: float = 0.0
389
+ plot_upper_bound: float = 1.0
390
+ plot_legend_name: str = "Label"
391
+
392
+ def __init__(
393
+ self,
394
+ num_labels: int,
395
+ fpr_range: Tuple[float, float] = (0.001, 0.1),
396
+ average: Optional[Literal["macro", "none"]] = None,
397
+ thresholds: Optional[Union[int, List[float], Tensor]] = None,
398
+ ignore_index: Optional[int] = None,
399
+ validate_args: bool = True,
400
+ **kwargs: Any,
401
+ ) -> None:
402
+ if validate_args:
403
+ _validate_fpr_range(fpr_range)
404
+ self.fpr_range = fpr_range
405
+ self.average2 = average # self.average is already used by parent class
406
+ super().__init__(
407
+ num_labels=num_labels,
408
+ thresholds=thresholds,
409
+ ignore_index=ignore_index,
410
+ validate_args=validate_args,
411
+ **kwargs,
412
+ )
413
+
414
+ def compute(self) -> Tensor: # type: ignore[override]
415
+ """Computes the log AUC score."""
416
+ fpr, tpr, _ = super().compute()
417
+ return _reduce_logauc(fpr, tpr, fpr_range=self.fpr_range, average=self.average2)
418
+
419
+ def plot( # type: ignore[override]
420
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
421
+ ) -> _PLOT_OUT_TYPE:
422
+ """Plot a single or multiple values from the metric.
423
+
424
+ Args:
425
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
426
+ If no value is provided, will automatically call `metric.compute` and plot that result.
427
+ ax: An matplotlib axis object. If provided will add plot to that axis
428
+
429
+ Returns:
430
+ Figure and Axes object
431
+
432
+ Raises:
433
+ ModuleNotFoundError:
434
+ If `matplotlib` is not installed
435
+
436
+ .. plot::
437
+ :scale: 75
438
+
439
+ >>> # Example plotting a single
440
+ >>> import torch
441
+ >>> from torchmetrics.classification import MultilabelLogAUC
442
+ >>> metric = MultilabelLogAUC(num_labels=3)
443
+ >>> metric.update(torch.rand(20,3), torch.randint(2, (20,3)))
444
+ >>> fig_, ax_ = metric.plot()
445
+
446
+ .. plot::
447
+ :scale: 75
448
+
449
+ >>> # Example plotting multiple values
450
+ >>> import torch
451
+ >>> from torchmetrics.classification import MultilabelLogAUC
452
+ >>> metric = MultilabelLogAUC(num_labels=3)
453
+ >>> values = [ ]
454
+ >>> for _ in range(10):
455
+ ... values.append(metric(torch.rand(20,3), torch.randint(2, (20,3))))
456
+ >>> fig_, ax_ = metric.plot(values)
457
+
458
+ """
459
+ return self._plot(val, ax)
460
+
461
+
462
+ class LogAUC(_ClassificationTaskWrapper):
463
+ r"""Compute the `Log AUC`_ score for multiclass classification tasks.
464
+
465
+ The score is computed by first computing the ROC curve, which then is interpolated to the specified range of false
466
+ positive rates (FPR) and then the log is taken of the FPR before the area under the curve (AUC) is computed. The
467
+ score is commonly used in applications where the positive and negative are imbalanced and a low false positive rate
468
+ is of high importance.
469
+
470
+ This module is a simple wrapper to get the task specific versions of this metric, which is done by setting the
471
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
472
+ :class:`~torchmetrics.classification.BinaryLogAUC`, :class:`~torchmetrics.classification.MulticlassLogAUC` and
473
+ :class:`~torchmetrics.classification.MultilabelLogAUC` for the specific details of each argument influence and
474
+ examples.
475
+
476
+ """
477
+
478
+ def __new__( # type: ignore[misc]
479
+ cls: Type["LogAUC"],
480
+ task: Literal["binary", "multiclass", "multilabel"],
481
+ thresholds: Optional[Union[int, List[float], Tensor]] = None,
482
+ fpr_range: Optional[Tuple[float, float]] = (0.001, 0.1),
483
+ num_classes: Optional[int] = None,
484
+ num_labels: Optional[int] = None,
485
+ ignore_index: Optional[int] = None,
486
+ validate_args: bool = True,
487
+ **kwargs: Any,
488
+ ) -> Metric:
489
+ """Initialize task metric."""
490
+ task = ClassificationTask.from_str(task)
491
+ kwargs.update({
492
+ "thresholds": thresholds,
493
+ "fpr_range": fpr_range,
494
+ "ignore_index": ignore_index,
495
+ "validate_args": validate_args,
496
+ })
497
+ if task == ClassificationTask.BINARY:
498
+ return BinaryLogAUC(**kwargs)
499
+ if task == ClassificationTask.MULTICLASS:
500
+ if not isinstance(num_classes, int):
501
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
502
+ return MulticlassLogAUC(num_classes, **kwargs)
503
+ if task == ClassificationTask.MULTILABEL:
504
+ if not isinstance(num_labels, int):
505
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
506
+ return MultilabelLogAUC(num_labels, **kwargs)
507
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/matthews_corrcoef.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.confusion_matrix import (
22
+ BinaryConfusionMatrix,
23
+ MulticlassConfusionMatrix,
24
+ MultilabelConfusionMatrix,
25
+ )
26
+ from torchmetrics.functional.classification.matthews_corrcoef import _matthews_corrcoef_reduce
27
+ from torchmetrics.metric import Metric
28
+ from torchmetrics.utilities.enums import ClassificationTask
29
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
30
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
31
+
32
+ if not _MATPLOTLIB_AVAILABLE:
33
+ __doctest_skip__ = [
34
+ "BinaryMatthewsCorrCoef.plot",
35
+ "MulticlassMatthewsCorrCoef.plot",
36
+ "MultilabelMatthewsCorrCoef.plot",
37
+ ]
38
+
39
+
40
+ class BinaryMatthewsCorrCoef(BinaryConfusionMatrix):
41
+ r"""Calculate `Matthews correlation coefficient`_ for binary tasks.
42
+
43
+ This metric measures the general correlation or quality of a classification.
44
+
45
+ As input to ``forward`` and ``update`` the metric accepts the following input:
46
+
47
+ - ``preds`` (:class:`~torch.Tensor`): A int tensor or float tensor of shape ``(N, ...)``. If preds is a floating
48
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
49
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
50
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
51
+
52
+ .. tip::
53
+ Additional dimension ``...`` will be flattened into the batch dimension.
54
+
55
+ As output to ``forward`` and ``compute`` the metric returns the following output:
56
+
57
+ - ``bmcc`` (:class:`~torch.Tensor`): A tensor containing the Binary Matthews Correlation Coefficient.
58
+
59
+ Args:
60
+ threshold: Threshold for transforming probability to binary (0,1) predictions
61
+ ignore_index:
62
+ Specifies a target value that is ignored and does not contribute to the metric calculation
63
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
64
+ Set to ``False`` for faster computations.
65
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
66
+
67
+ Example (preds is int tensor):
68
+ >>> from torch import tensor
69
+ >>> from torchmetrics.classification import BinaryMatthewsCorrCoef
70
+ >>> target = tensor([1, 1, 0, 0])
71
+ >>> preds = tensor([0, 1, 0, 0])
72
+ >>> metric = BinaryMatthewsCorrCoef()
73
+ >>> metric(preds, target)
74
+ tensor(0.5774)
75
+
76
+ Example (preds is float tensor):
77
+ >>> from torchmetrics.classification import BinaryMatthewsCorrCoef
78
+ >>> target = tensor([1, 1, 0, 0])
79
+ >>> preds = tensor([0.35, 0.85, 0.48, 0.01])
80
+ >>> metric = BinaryMatthewsCorrCoef()
81
+ >>> metric(preds, target)
82
+ tensor(0.5774)
83
+
84
+ """
85
+
86
+ is_differentiable: bool = False
87
+ higher_is_better: bool = True
88
+ full_state_update: bool = False
89
+ plot_lower_bound: float = 0.0
90
+ plot_upper_bound: float = 1.0
91
+
92
+ def __init__(
93
+ self,
94
+ threshold: float = 0.5,
95
+ ignore_index: Optional[int] = None,
96
+ validate_args: bool = True,
97
+ **kwargs: Any,
98
+ ) -> None:
99
+ super().__init__(threshold, ignore_index, normalize=None, validate_args=validate_args, **kwargs)
100
+
101
+ def compute(self) -> Tensor:
102
+ """Compute metric."""
103
+ return _matthews_corrcoef_reduce(self.confmat)
104
+
105
+ def plot( # type: ignore[override]
106
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
107
+ ) -> _PLOT_OUT_TYPE:
108
+ """Plot a single or multiple values from the metric.
109
+
110
+ Args:
111
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
112
+ If no value is provided, will automatically call `metric.compute` and plot that result.
113
+ ax: An matplotlib axis object. If provided will add plot to that axis
114
+
115
+ Returns:
116
+ Figure object and Axes object
117
+
118
+ Raises:
119
+ ModuleNotFoundError:
120
+ If `matplotlib` is not installed
121
+
122
+ .. plot::
123
+ :scale: 75
124
+
125
+ >>> from torch import rand, randint
126
+ >>> # Example plotting a single value
127
+ >>> from torchmetrics.classification import BinaryMatthewsCorrCoef
128
+ >>> metric = BinaryMatthewsCorrCoef()
129
+ >>> metric.update(rand(10), randint(2,(10,)))
130
+ >>> fig_, ax_ = metric.plot()
131
+
132
+ .. plot::
133
+ :scale: 75
134
+
135
+ >>> from torch import rand, randint
136
+ >>> # Example plotting multiple values
137
+ >>> from torchmetrics.classification import BinaryMatthewsCorrCoef
138
+ >>> metric = BinaryMatthewsCorrCoef()
139
+ >>> values = [ ]
140
+ >>> for _ in range(10):
141
+ ... values.append(metric(rand(10), randint(2,(10,))))
142
+ >>> fig_, ax_ = metric.plot(values)
143
+
144
+ """
145
+ return self._plot(val, ax)
146
+
147
+
148
+ class MulticlassMatthewsCorrCoef(MulticlassConfusionMatrix):
149
+ r"""Calculate `Matthews correlation coefficient`_ for multiclass tasks.
150
+
151
+ This metric measures the general correlation or quality of a classification.
152
+
153
+ As input to ``forward`` and ``update`` the metric accepts the following input:
154
+
155
+ - ``preds`` (:class:`~torch.Tensor`): A int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
156
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
157
+ probabilities/logits into an int tensor.
158
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
159
+
160
+ .. tip::
161
+ Additional dimension ``...`` will be flattened into the batch dimension.
162
+
163
+ As output to ``forward`` and ``compute`` the metric returns the following output:
164
+
165
+ - ``mcmcc`` (:class:`~torch.Tensor`): A tensor containing the Multi-class Matthews Correlation Coefficient.
166
+
167
+ Args:
168
+ num_classes: Integer specifying the number of classes
169
+ ignore_index:
170
+ Specifies a target value that is ignored and does not contribute to the metric calculation
171
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
172
+ Set to ``False`` for faster computations.
173
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
174
+
175
+ Example (pred is integer tensor):
176
+ >>> from torch import tensor
177
+ >>> from torchmetrics.classification import MulticlassMatthewsCorrCoef
178
+ >>> target = tensor([2, 1, 0, 0])
179
+ >>> preds = tensor([2, 1, 0, 1])
180
+ >>> metric = MulticlassMatthewsCorrCoef(num_classes=3)
181
+ >>> metric(preds, target)
182
+ tensor(0.7000)
183
+
184
+ Example (pred is float tensor):
185
+ >>> from torchmetrics.classification import MulticlassMatthewsCorrCoef
186
+ >>> target = tensor([2, 1, 0, 0])
187
+ >>> preds = tensor([[0.16, 0.26, 0.58],
188
+ ... [0.22, 0.61, 0.17],
189
+ ... [0.71, 0.09, 0.20],
190
+ ... [0.05, 0.82, 0.13]])
191
+ >>> metric = MulticlassMatthewsCorrCoef(num_classes=3)
192
+ >>> metric(preds, target)
193
+ tensor(0.7000)
194
+
195
+ """
196
+
197
+ is_differentiable: bool = False
198
+ higher_is_better: bool = True
199
+ full_state_update: bool = False
200
+ plot_lower_bound: float = 0.0
201
+ plot_upper_bound: float = 1.0
202
+ plot_legend_name: str = "Class"
203
+
204
+ def __init__(
205
+ self,
206
+ num_classes: int,
207
+ ignore_index: Optional[int] = None,
208
+ validate_args: bool = True,
209
+ **kwargs: Any,
210
+ ) -> None:
211
+ super().__init__(num_classes, ignore_index, normalize=None, validate_args=validate_args, **kwargs)
212
+
213
+ def compute(self) -> Tensor:
214
+ """Compute metric."""
215
+ return _matthews_corrcoef_reduce(self.confmat)
216
+
217
+ def plot( # type: ignore[override]
218
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
219
+ ) -> _PLOT_OUT_TYPE:
220
+ """Plot a single or multiple values from the metric.
221
+
222
+ Args:
223
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
224
+ If no value is provided, will automatically call `metric.compute` and plot that result.
225
+ ax: An matplotlib axis object. If provided will add plot to that axis
226
+
227
+ Returns:
228
+ Figure object and Axes object
229
+
230
+ Raises:
231
+ ModuleNotFoundError:
232
+ If `matplotlib` is not installed
233
+
234
+ .. plot::
235
+ :scale: 75
236
+
237
+ >>> from torch import randint
238
+ >>> # Example plotting a single value per class
239
+ >>> from torchmetrics.classification import MulticlassMatthewsCorrCoef
240
+ >>> metric = MulticlassMatthewsCorrCoef(num_classes=3)
241
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
242
+ >>> fig_, ax_ = metric.plot()
243
+
244
+ .. plot::
245
+ :scale: 75
246
+
247
+ >>> from torch import randint
248
+ >>> # Example plotting a multiple values per class
249
+ >>> from torchmetrics.classification import MulticlassMatthewsCorrCoef
250
+ >>> metric = MulticlassMatthewsCorrCoef(num_classes=3)
251
+ >>> values = []
252
+ >>> for _ in range(20):
253
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
254
+ >>> fig_, ax_ = metric.plot(values)
255
+
256
+ """
257
+ return self._plot(val, ax)
258
+
259
+
260
+ class MultilabelMatthewsCorrCoef(MultilabelConfusionMatrix):
261
+ r"""Calculate `Matthews correlation coefficient`_ for multilabel tasks.
262
+
263
+ This metric measures the general correlation or quality of a classification.
264
+
265
+ As input to ``forward`` and ``update`` the metric accepts the following input:
266
+
267
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
268
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
269
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
270
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
271
+
272
+ .. tip::
273
+ Additional dimension ``...`` will be flattened into the batch dimension.
274
+
275
+ As output to ``forward`` and ``compute`` the metric returns the following output:
276
+
277
+ - ``mlmcc`` (:class:`~torch.Tensor`): A tensor containing the Multi-label Matthews Correlation Coefficient.
278
+
279
+ Args:
280
+ num_labels: Integer specifying the number of labels
281
+ threshold: Threshold for transforming probability to binary (0,1) predictions
282
+ ignore_index:
283
+ Specifies a target value that is ignored and does not contribute to the metric calculation
284
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
285
+ Set to ``False`` for faster computations.
286
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
287
+
288
+ Example (preds is int tensor):
289
+ >>> from torch import tensor
290
+ >>> from torchmetrics.classification import MultilabelMatthewsCorrCoef
291
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
292
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
293
+ >>> metric = MultilabelMatthewsCorrCoef(num_labels=3)
294
+ >>> metric(preds, target)
295
+ tensor(0.3333)
296
+
297
+ Example (preds is float tensor):
298
+ >>> from torchmetrics.classification import MultilabelMatthewsCorrCoef
299
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
300
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
301
+ >>> metric = MultilabelMatthewsCorrCoef(num_labels=3)
302
+ >>> metric(preds, target)
303
+ tensor(0.3333)
304
+
305
+ """
306
+
307
+ is_differentiable: bool = False
308
+ higher_is_better: bool = True
309
+ full_state_update: bool = False
310
+ plot_lower_bound: float = 0.0
311
+ plot_upper_bound: float = 1.0
312
+ plot_legend_name: str = "Label"
313
+
314
+ def __init__(
315
+ self,
316
+ num_labels: int,
317
+ threshold: float = 0.5,
318
+ ignore_index: Optional[int] = None,
319
+ validate_args: bool = True,
320
+ **kwargs: Any,
321
+ ) -> None:
322
+ super().__init__(num_labels, threshold, ignore_index, normalize=None, validate_args=validate_args, **kwargs)
323
+
324
+ def compute(self) -> Tensor:
325
+ """Compute metric."""
326
+ return _matthews_corrcoef_reduce(self.confmat)
327
+
328
+ def plot( # type: ignore[override]
329
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
330
+ ) -> _PLOT_OUT_TYPE:
331
+ """Plot a single or multiple values from the metric.
332
+
333
+ Args:
334
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
335
+ If no value is provided, will automatically call `metric.compute` and plot that result.
336
+ ax: An matplotlib axis object. If provided will add plot to that axis
337
+
338
+ Returns:
339
+ Figure object and Axes object
340
+
341
+ Raises:
342
+ ModuleNotFoundError:
343
+ If `matplotlib` is not installed
344
+
345
+ .. plot::
346
+ :scale: 75
347
+
348
+ >>> from torch import rand, randint
349
+ >>> # Example plotting a single value
350
+ >>> from torchmetrics.classification import MultilabelMatthewsCorrCoef
351
+ >>> metric = MultilabelMatthewsCorrCoef(num_labels=3)
352
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
353
+ >>> fig_, ax_ = metric.plot()
354
+
355
+ .. plot::
356
+ :scale: 75
357
+
358
+ >>> from torch import rand, randint
359
+ >>> # Example plotting multiple values
360
+ >>> from torchmetrics.classification import MultilabelMatthewsCorrCoef
361
+ >>> metric = MultilabelMatthewsCorrCoef(num_labels=3)
362
+ >>> values = [ ]
363
+ >>> for _ in range(10):
364
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
365
+ >>> fig_, ax_ = metric.plot(values)
366
+
367
+ """
368
+ return self._plot(val, ax)
369
+
370
+
371
+ class MatthewsCorrCoef(_ClassificationTaskWrapper):
372
+ r"""Calculate `Matthews correlation coefficient`_ .
373
+
374
+ This metric measures the general correlation or quality of a classification.
375
+
376
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
377
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
378
+ :class:`~torchmetrics.classification.BinaryMatthewsCorrCoef`,
379
+ :class:`~torchmetrics.classification.MulticlassMatthewsCorrCoef` and
380
+ :class:`~torchmetrics.classification.MultilabelMatthewsCorrCoef` for the specific details of each argument influence
381
+ and examples.
382
+
383
+ Legacy Example:
384
+ >>> from torch import tensor
385
+ >>> target = tensor([1, 1, 0, 0])
386
+ >>> preds = tensor([0, 1, 0, 0])
387
+ >>> matthews_corrcoef = MatthewsCorrCoef(task='binary')
388
+ >>> matthews_corrcoef(preds, target)
389
+ tensor(0.5774)
390
+
391
+ """
392
+
393
+ def __new__( # type: ignore[misc]
394
+ cls: type["MatthewsCorrCoef"],
395
+ task: Literal["binary", "multiclass", "multilabel"],
396
+ threshold: float = 0.5,
397
+ num_classes: Optional[int] = None,
398
+ num_labels: Optional[int] = None,
399
+ ignore_index: Optional[int] = None,
400
+ validate_args: bool = True,
401
+ **kwargs: Any,
402
+ ) -> Metric:
403
+ """Initialize task metric."""
404
+ task = ClassificationTask.from_str(task)
405
+ kwargs.update({"ignore_index": ignore_index, "validate_args": validate_args})
406
+ if task == ClassificationTask.BINARY:
407
+ return BinaryMatthewsCorrCoef(threshold, **kwargs)
408
+ if task == ClassificationTask.MULTICLASS:
409
+ if not isinstance(num_classes, int):
410
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
411
+ return MulticlassMatthewsCorrCoef(num_classes, **kwargs)
412
+ if task == ClassificationTask.MULTILABEL:
413
+ if not isinstance(num_labels, int):
414
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
415
+ return MultilabelMatthewsCorrCoef(num_labels, threshold, **kwargs)
416
+ raise ValueError(f"Not handled value: {task}")
rtme/lib/python3.10/site-packages/torchmetrics/classification/negative_predictive_value.py ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.stat_scores import BinaryStatScores, MulticlassStatScores, MultilabelStatScores
22
+ from torchmetrics.functional.classification.negative_predictive_value import _negative_predictive_value_reduce
23
+ from torchmetrics.metric import Metric
24
+ from torchmetrics.utilities.enums import ClassificationTask
25
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
26
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
27
+
28
+ if not _MATPLOTLIB_AVAILABLE:
29
+ __doctest_skip__ = [
30
+ "BinaryNegativePredictiveValue.plot",
31
+ "MulticlassNegativePredictiveValue.plot",
32
+ "MultilabelNegativePredictiveValue.plot",
33
+ ]
34
+
35
+
36
+ class BinaryNegativePredictiveValue(BinaryStatScores):
37
+ r"""Compute `Negative Predictive Value`_ for binary tasks.
38
+
39
+ .. math:: \text{Negative Predictive Value} = \frac{\text{TN}}{\text{TN} + \text{FP}}
40
+
41
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
42
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
43
+ encountered a score of 0 is returned.
44
+
45
+ As input to ``forward`` and ``update`` the metric accepts the following input:
46
+
47
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, ...)``. If preds is a floating point
48
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per
49
+ element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
50
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
51
+
52
+ As output to ``forward`` and ``compute`` the metric returns the following output:
53
+
54
+ - ``npv`` (:class:`~torch.Tensor`): If ``multidim_average`` is set to ``global``, the metric returns a scalar value.
55
+ If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of a scalar value
56
+ per sample.
57
+
58
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
59
+ which the reduction will then be applied over instead of the sample dimension ``N``.
60
+
61
+ Args:
62
+ threshold: Threshold for transforming probability to binary {0,1} predictions
63
+ multidim_average:
64
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
65
+
66
+ - ``global``: Additional dimensions are flatted along the batch dimension
67
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
68
+ The statistics in this case are calculated over the additional dimensions.
69
+
70
+ ignore_index:
71
+ Specifies a target value that is ignored and does not contribute to the metric calculation
72
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
73
+ Set to ``False`` for faster computations.
74
+
75
+ Example (preds is int tensor):
76
+ >>> from torch import tensor
77
+ >>> from torchmetrics.classification import BinaryNegativePredictiveValue
78
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
79
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
80
+ >>> metric = BinaryNegativePredictiveValue()
81
+ >>> metric(preds, target)
82
+ tensor(0.6667)
83
+
84
+ Example (preds is float tensor):
85
+ >>> from torchmetrics.classification import BinaryNegativePredictiveValue
86
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
87
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
88
+ >>> metric = BinaryNegativePredictiveValue()
89
+ >>> metric(preds, target)
90
+ tensor(0.6667)
91
+
92
+ Example (multidim tensors):
93
+ >>> from torchmetrics.classification import BinaryNegativePredictiveValue
94
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
95
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
96
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
97
+ >>> metric = BinaryNegativePredictiveValue(multidim_average='samplewise')
98
+ >>> metric(preds, target)
99
+ tensor([0.0000, 0.2500])
100
+
101
+ """
102
+
103
+ plot_lower_bound: float = 0.0
104
+ plot_upper_bound: float = 1.0
105
+
106
+ def compute(self) -> Tensor:
107
+ """Compute metric."""
108
+ tp, fp, tn, fn = self._final_state()
109
+ return _negative_predictive_value_reduce(
110
+ tp, fp, tn, fn, average="binary", multidim_average=self.multidim_average
111
+ )
112
+
113
+ def plot(
114
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
115
+ ) -> _PLOT_OUT_TYPE:
116
+ """Plot a single or multiple values from the metric.
117
+
118
+ Args:
119
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
120
+ If no value is provided, will automatically call `metric.compute` and plot that result.
121
+ ax: An matplotlib axis object. If provided will add plot to that axis
122
+
123
+ Returns:
124
+ Figure object and Axes object
125
+
126
+ Raises:
127
+ ModuleNotFoundError:
128
+ If `matplotlib` is not installed
129
+
130
+ .. plot::
131
+ :scale: 75
132
+
133
+ >>> from torch import rand, randint
134
+ >>> # Example plotting a single value
135
+ >>> from torchmetrics.classification import BinaryNegativePredictiveValue
136
+ >>> metric = BinaryNegativePredictiveValue()
137
+ >>> metric.update(rand(10), randint(2,(10,)))
138
+ >>> fig_, ax_ = metric.plot()
139
+
140
+ .. plot::
141
+ :scale: 75
142
+
143
+ >>> from torch import rand, randint
144
+ >>> # Example plotting multiple values
145
+ >>> from torchmetrics.classification import BinaryNegativePredictiveValue
146
+ >>> metric = BinaryNegativePredictiveValue()
147
+ >>> values = [ ]
148
+ >>> for _ in range(10):
149
+ ... values.append(metric(rand(10), randint(2,(10,))))
150
+ >>> fig_, ax_ = metric.plot(values)
151
+
152
+ """
153
+ return self._plot(val, ax)
154
+
155
+
156
+ class MulticlassNegativePredictiveValue(MulticlassStatScores):
157
+ r"""Compute `Negative Predictive Value`_ for multiclass tasks.
158
+
159
+ .. math:: \text{Negative Predictive Value} = \frac{\text{TN}}{\text{TN} + \text{FP}}
160
+
161
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
162
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
163
+ encountered for any class, the metric for that class will be set to 0 and the overall metric may therefore be
164
+ affected in turn.
165
+
166
+ As input to ``forward`` and ``update`` the metric accepts the following input:
167
+
168
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
169
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
170
+ probabilities/logits into an int tensor.
171
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
172
+
173
+ As output to ``forward`` and ``compute`` the metric returns the following output:
174
+
175
+ - ``npv`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
176
+ arguments:
177
+
178
+ - If ``multidim_average`` is set to ``global``:
179
+
180
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
181
+ - If ``average=None/'none'``, the shape will be ``(C,)``
182
+
183
+ - If ``multidim_average`` is set to ``samplewise``:
184
+
185
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
186
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
187
+
188
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
189
+ which the reduction will then be applied over instead of the sample dimension ``N``.
190
+
191
+ Args:
192
+ num_classes: Integer specifying the number of classes
193
+ average:
194
+ Defines the reduction that is applied over labels. Should be one of the following:
195
+
196
+ - ``micro``: Sum statistics over all labels
197
+ - ``macro``: Calculate statistics for each label and average them
198
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
199
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
200
+
201
+ top_k:
202
+ Number of highest probability or logit score predictions considered to find the correct label.
203
+ Only works when ``preds`` contain probabilities/logits.
204
+ multidim_average:
205
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
206
+
207
+ - ``global``: Additional dimensions are flatted along the batch dimension
208
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
209
+ The statistics in this case are calculated over the additional dimensions.
210
+
211
+ ignore_index:
212
+ Specifies a target value that is ignored and does not contribute to the metric calculation
213
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
214
+ Set to ``False`` for faster computations.
215
+
216
+ Example (preds is int tensor):
217
+ >>> from torch import tensor
218
+ >>> from torchmetrics.classification import MulticlassNegativePredictiveValue
219
+ >>> target = tensor([2, 1, 0, 0])
220
+ >>> preds = tensor([2, 1, 0, 1])
221
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3)
222
+ >>> metric(preds, target)
223
+ tensor(0.8889)
224
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, average=None)
225
+ >>> metric(preds, target)
226
+ tensor([0.6667, 1.0000, 1.0000])
227
+
228
+ Example (preds is float tensor):
229
+ >>> from torchmetrics.classification import MulticlassNegativePredictiveValue
230
+ >>> target = tensor([2, 1, 0, 0])
231
+ >>> preds = tensor([[0.16, 0.26, 0.58],
232
+ ... [0.22, 0.61, 0.17],
233
+ ... [0.71, 0.09, 0.20],
234
+ ... [0.05, 0.82, 0.13]])
235
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3)
236
+ >>> metric(preds, target)
237
+ tensor(0.8889)
238
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, average=None)
239
+ >>> metric(preds, target)
240
+ tensor([0.6667, 1.0000, 1.0000])
241
+
242
+ Example (multidim tensors):
243
+ >>> from torchmetrics.classification import MulticlassNegativePredictiveValue
244
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
245
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
246
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, multidim_average='samplewise')
247
+ >>> metric(preds, target)
248
+ tensor([0.7833, 0.6556])
249
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, multidim_average='samplewise', average=None)
250
+ >>> metric(preds, target)
251
+ tensor([[1.0000, 0.6000, 0.7500],
252
+ [0.8000, 0.5000, 0.6667]])
253
+
254
+ """
255
+
256
+ plot_lower_bound: float = 0.0
257
+ plot_upper_bound: float = 1.0
258
+ plot_legend_name: str = "Class"
259
+
260
+ def compute(self) -> Tensor:
261
+ """Compute metric."""
262
+ tp, fp, tn, fn = self._final_state()
263
+ return _negative_predictive_value_reduce(
264
+ tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average, top_k=self.top_k
265
+ )
266
+
267
+ def plot(
268
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
269
+ ) -> _PLOT_OUT_TYPE:
270
+ """Plot a single or multiple values from the metric.
271
+
272
+ Args:
273
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
274
+ If no value is provided, will automatically call `metric.compute` and plot that result.
275
+ ax: An matplotlib axis object. If provided will add plot to that axis
276
+
277
+ Returns:
278
+ Figure object and Axes object
279
+
280
+ Raises:
281
+ ModuleNotFoundError:
282
+ If `matplotlib` is not installed
283
+
284
+ .. plot::
285
+ :scale: 75
286
+
287
+ >>> from torch import randint
288
+ >>> # Example plotting a single value per class
289
+ >>> from torchmetrics.classification import MulticlassNegativePredictiveValue
290
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, average=None)
291
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
292
+ >>> fig_, ax_ = metric.plot()
293
+
294
+ .. plot::
295
+ :scale: 75
296
+
297
+ >>> from torch import randint
298
+ >>> # Example plotting a multiple values per class
299
+ >>> from torchmetrics.classification import MulticlassNegativePredictiveValue
300
+ >>> metric = MulticlassNegativePredictiveValue(num_classes=3, average=None)
301
+ >>> values = []
302
+ >>> for _ in range(20):
303
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
304
+ >>> fig_, ax_ = metric.plot(values)
305
+
306
+ """
307
+ return self._plot(val, ax)
308
+
309
+
310
+ class MultilabelNegativePredictiveValue(MultilabelStatScores):
311
+ r"""Compute `Negative Predictive Value`_ for multilabel tasks.
312
+
313
+ .. math:: \text{Negative Predictive Value} = \frac{\text{TN}}{\text{TN} + \text{FP}}
314
+
315
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
316
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
317
+ encountered for any label, the metric for that label will be set to 0 and the overall metric may therefore be
318
+ affected in turn.
319
+
320
+ As input to ``forward`` and ``update`` the metric accepts the following input:
321
+
322
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
323
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
324
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
325
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
326
+
327
+ As output to ``forward`` and ``compute`` the metric returns the following output:
328
+
329
+ - ``npv`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
330
+ arguments:
331
+
332
+ - If ``multidim_average`` is set to ``global``
333
+
334
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
335
+ - If ``average=None/'none'``, the shape will be ``(C,)``
336
+
337
+ - If ``multidim_average`` is set to ``samplewise``
338
+
339
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
340
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
341
+
342
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
343
+ which the reduction will then be applied over instead of the sample dimension ``N``.
344
+
345
+ Args:
346
+ num_labels: Integer specifying the number of labels
347
+ threshold: Threshold for transforming probability to binary (0,1) predictions
348
+ average:
349
+ Defines the reduction that is applied over labels. Should be one of the following:
350
+
351
+ - ``micro``: Sum statistics over all labels
352
+ - ``macro``: Calculate statistics for each label and average them
353
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
354
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
355
+
356
+ multidim_average: Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
357
+
358
+ - ``global``: Additional dimensions are flatted along the batch dimension
359
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
360
+ The statistics in this case are calculated over the additional dimensions.
361
+
362
+ ignore_index:
363
+ Specifies a target value that is ignored and does not contribute to the metric calculation
364
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
365
+ Set to ``False`` for faster computations.
366
+
367
+ Example (preds is int tensor):
368
+ >>> from torch import tensor
369
+ >>> from torchmetrics.classification import MultilabelNegativePredictiveValue
370
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
371
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
372
+ >>> metric = MultilabelNegativePredictiveValue(num_labels=3)
373
+ >>> metric(preds, target)
374
+ tensor(0.5000)
375
+ >>> mls = MultilabelNegativePredictiveValue(num_labels=3, average=None)
376
+ >>> mls(preds, target)
377
+ tensor([1.0000, 0.5000, 0.0000])
378
+
379
+ Example (preds is float tensor):
380
+ >>> from torchmetrics.classification import MultilabelNegativePredictiveValue
381
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
382
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
383
+ >>> metric = MultilabelNegativePredictiveValue(num_labels=3)
384
+ >>> metric(preds, target)
385
+ tensor(0.5000)
386
+ >>> mls = MultilabelNegativePredictiveValue(num_labels=3, average=None)
387
+ >>> mls(preds, target)
388
+ tensor([1.0000, 0.5000, 0.0000])
389
+
390
+ Example (multidim tensors):
391
+ >>> from torchmetrics.classification import MultilabelNegativePredictiveValue
392
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
393
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
394
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
395
+ >>> metric = MultilabelNegativePredictiveValue(num_labels=3, multidim_average='samplewise')
396
+ >>> metric(preds, target)
397
+ tensor([0.0000, 0.1667])
398
+ >>> mls = MultilabelNegativePredictiveValue(num_labels=3, multidim_average='samplewise', average=None)
399
+ >>> mls(preds, target)
400
+ tensor([[0.0000, 0.0000, 0.0000],
401
+ [0.0000, 0.0000, 0.5000]])
402
+
403
+ """
404
+
405
+ plot_lower_bound: float = 0.0
406
+ plot_upper_bound: float = 1.0
407
+ plot_legend_name: str = "Label"
408
+
409
+ def compute(self) -> Tensor:
410
+ """Compute metric."""
411
+ tp, fp, tn, fn = self._final_state()
412
+ return _negative_predictive_value_reduce(
413
+ tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average, multilabel=True
414
+ )
415
+
416
+ def plot(
417
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
418
+ ) -> _PLOT_OUT_TYPE:
419
+ """Plot a single or multiple values from the metric.
420
+
421
+ Args:
422
+ val: Either a single result from calling ``metric.forward`` or ``metric.compute`` or a list of these
423
+ results. If no value is provided, will automatically call `metric.compute` and plot that result.
424
+ ax: An matplotlib axis object. If provided will add plot to that axis
425
+
426
+ Returns:
427
+ Figure object and Axes object
428
+
429
+ Raises:
430
+ ModuleNotFoundError:
431
+ If `matplotlib` is not installed
432
+
433
+ .. plot::
434
+ :scale: 75
435
+
436
+ >>> from torch import rand, randint
437
+ >>> # Example plotting a single value
438
+ >>> from torchmetrics.classification import MultilabelNegativePredictiveValue
439
+ >>> metric = MultilabelNegativePredictiveValue(num_labels=3)
440
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
441
+ >>> fig_, ax_ = metric.plot()
442
+
443
+ .. plot::
444
+ :scale: 75
445
+
446
+ >>> from torch import rand, randint
447
+ >>> # Example plotting multiple values
448
+ >>> from torchmetrics.classification import MultilabelNegativePredictiveValue
449
+ >>> metric = MultilabelNegativePredictiveValue(num_labels=3)
450
+ >>> values = [ ]
451
+ >>> for _ in range(10):
452
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
453
+ >>> fig_, ax_ = metric.plot(values)
454
+
455
+ """
456
+ return self._plot(val, ax)
457
+
458
+
459
+ class NegativePredictiveValue(_ClassificationTaskWrapper):
460
+ r"""Compute `Negative Predictive Value`_.
461
+
462
+ .. math:: \text{Negative Predictive Value} = \frac{\text{TN}}{\text{TN} + \text{FP}}
463
+
464
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
465
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0`. If this case is
466
+ encountered for any class/label, the metric for that class/label will be set to 0 and the overall metric may
467
+ therefore be affected in turn.
468
+
469
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
470
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
471
+ :class:`~torchmetrics.classification.BinaryNegativePredictiveValue`,
472
+ :class:`~torchmetrics.classification.MulticlassNegativePredictiveValue`
473
+ and :class:`~torchmetrics.classification.MultilabelNegativePredictiveValue` for the specific details of each
474
+ argument influence and examples.
475
+
476
+ Legacy Example:
477
+ >>> from torch import tensor
478
+ >>> preds = tensor([2, 0, 2, 1])
479
+ >>> target = tensor([1, 1, 2, 0])
480
+ >>> nvp = NegativePredictiveValue(task="multiclass", average='macro', num_classes=3)
481
+ >>> nvp(preds, target)
482
+ tensor(0.6667)
483
+ >>> nvp = NegativePredictiveValue(task="multiclass", average='micro', num_classes=3)
484
+ >>> nvp(preds, target)
485
+ tensor(0.6250)
486
+
487
+ """
488
+
489
+ def __new__( # type: ignore[misc]
490
+ cls: type["NegativePredictiveValue"],
491
+ task: Literal["binary", "multiclass", "multilabel"],
492
+ threshold: float = 0.5,
493
+ num_classes: Optional[int] = None,
494
+ num_labels: Optional[int] = None,
495
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
496
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
497
+ top_k: Optional[int] = 1,
498
+ ignore_index: Optional[int] = None,
499
+ validate_args: bool = True,
500
+ **kwargs: Any,
501
+ ) -> Metric:
502
+ """Initialize task metric."""
503
+ task = ClassificationTask.from_str(task)
504
+ assert multidim_average is not None # noqa: S101 # needed for mypy
505
+ kwargs.update({
506
+ "multidim_average": multidim_average,
507
+ "ignore_index": ignore_index,
508
+ "validate_args": validate_args,
509
+ })
510
+ if task == ClassificationTask.BINARY:
511
+ return BinaryNegativePredictiveValue(threshold, **kwargs)
512
+ if task == ClassificationTask.MULTICLASS:
513
+ if not isinstance(num_classes, int):
514
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
515
+ if not isinstance(top_k, int):
516
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
517
+ return MulticlassNegativePredictiveValue(num_classes, top_k, average, **kwargs)
518
+ if task == ClassificationTask.MULTILABEL:
519
+ if not isinstance(num_labels, int):
520
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
521
+ return MultilabelNegativePredictiveValue(num_labels, threshold, average, **kwargs)
522
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_fixed_recall.py ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.precision_recall_curve import (
22
+ BinaryPrecisionRecallCurve,
23
+ MulticlassPrecisionRecallCurve,
24
+ MultilabelPrecisionRecallCurve,
25
+ )
26
+ from torchmetrics.functional.classification.precision_fixed_recall import _precision_at_recall
27
+ from torchmetrics.functional.classification.recall_fixed_precision import (
28
+ _binary_recall_at_fixed_precision_arg_validation,
29
+ _binary_recall_at_fixed_precision_compute,
30
+ _multiclass_recall_at_fixed_precision_arg_compute,
31
+ _multiclass_recall_at_fixed_precision_arg_validation,
32
+ _multilabel_recall_at_fixed_precision_arg_compute,
33
+ _multilabel_recall_at_fixed_precision_arg_validation,
34
+ )
35
+ from torchmetrics.metric import Metric
36
+ from torchmetrics.utilities.data import dim_zero_cat
37
+ from torchmetrics.utilities.enums import ClassificationTask
38
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
39
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
40
+
41
+ if not _MATPLOTLIB_AVAILABLE:
42
+ __doctest_skip__ = [
43
+ "BinaryPrecisionAtFixedRecall.plot",
44
+ "MulticlassPrecisionAtFixedRecall.plot",
45
+ "MultilabelPrecisionAtFixedRecall.plot",
46
+ ]
47
+
48
+
49
+ class BinaryPrecisionAtFixedRecall(BinaryPrecisionRecallCurve):
50
+ r"""Compute the highest possible precision value given the minimum recall thresholds provided.
51
+
52
+ This is done by first calculating the precision-recall curve for different thresholds and the find the precision for
53
+ a given recall level.
54
+
55
+ As input to ``forward`` and ``update`` the metric accepts the following input:
56
+
57
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)``. Preds should be a tensor containing
58
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input
59
+ to be logits and will auto apply sigmoid per element.
60
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
61
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
62
+ 1 always encodes the positive class.
63
+
64
+ .. tip::
65
+ Additional dimension ``...`` will be flattened into the batch dimension.
66
+
67
+ As output to ``forward`` and ``compute`` the metric returns the following output:
68
+
69
+ - ``precision`` (:class:`~torch.Tensor`): A scalar tensor with the maximum precision for the given recall level
70
+ - ``threshold`` (:class:`~torch.Tensor`): A scalar tensor with the corresponding threshold level
71
+
72
+ .. note::
73
+ The implementation both supports calculating the metric in a non-binned but accurate version and a
74
+ binned version that is less accurate but more memory efficient. Setting the `thresholds` argument to ``None``
75
+ will activate the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting
76
+ the `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory
77
+ of size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
78
+
79
+ Args:
80
+ min_recall: float value specifying minimum recall threshold.
81
+ thresholds:
82
+ Can be one of:
83
+
84
+ - If set to ``None``, will use a non-binned approach where thresholds are dynamically calculated from
85
+ all the data. Most accurate but also most memory consuming approach.
86
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
87
+ 0 to 1 as bins for the calculation.
88
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
89
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
90
+ bins for the calculation.
91
+
92
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
93
+ Set to ``False`` for faster computations.
94
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
95
+
96
+ Example:
97
+ >>> from torch import tensor
98
+ >>> from torchmetrics.classification import BinaryPrecisionAtFixedRecall
99
+ >>> preds = tensor([0, 0.5, 0.7, 0.8])
100
+ >>> target = tensor([0, 1, 1, 0])
101
+ >>> metric = BinaryPrecisionAtFixedRecall(min_recall=0.5, thresholds=None)
102
+ >>> metric(preds, target)
103
+ (tensor(0.6667), tensor(0.5000))
104
+ >>> metric = BinaryPrecisionAtFixedRecall(min_recall=0.5, thresholds=5)
105
+ >>> metric(preds, target)
106
+ (tensor(0.6667), tensor(0.5000))
107
+
108
+ """
109
+
110
+ is_differentiable: bool = False
111
+ higher_is_better: Optional[bool] = None
112
+ full_state_update: bool = False
113
+ plot_lower_bound: float = 0.0
114
+ plot_upper_bound: float = 1.0
115
+
116
+ def __init__(
117
+ self,
118
+ min_recall: float,
119
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
120
+ ignore_index: Optional[int] = None,
121
+ validate_args: bool = True,
122
+ **kwargs: Any,
123
+ ) -> None:
124
+ super().__init__(thresholds, ignore_index, validate_args=False, **kwargs)
125
+ if validate_args:
126
+ _binary_recall_at_fixed_precision_arg_validation(min_recall, thresholds, ignore_index)
127
+ self.validate_args = validate_args
128
+ self.min_recall = min_recall
129
+
130
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
131
+ """Compute metric."""
132
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
133
+ return _binary_recall_at_fixed_precision_compute(
134
+ state, self.thresholds, self.min_recall, reduce_fn=_precision_at_recall
135
+ )
136
+
137
+ def plot( # type: ignore[override]
138
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
139
+ ) -> _PLOT_OUT_TYPE:
140
+ """Plot a single or multiple values from the metric.
141
+
142
+ Args:
143
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
144
+ If no value is provided, will automatically call `metric.compute` and plot that result.
145
+ ax: An matplotlib axis object. If provided will add plot to that axis
146
+
147
+ Returns:
148
+ Figure object and Axes object
149
+
150
+ Raises:
151
+ ModuleNotFoundError:
152
+ If `matplotlib` is not installed
153
+
154
+ .. plot::
155
+ :scale: 75
156
+
157
+ >>> from torch import rand, randint
158
+ >>> # Example plotting a single value
159
+ >>> from torchmetrics.classification import BinaryPrecisionAtFixedRecall
160
+ >>> metric = BinaryPrecisionAtFixedRecall(min_recall=0.5)
161
+ >>> metric.update(rand(10), randint(2,(10,)))
162
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
163
+
164
+ .. plot::
165
+ :scale: 75
166
+
167
+ >>> from torch import rand, randint
168
+ >>> # Example plotting multiple values
169
+ >>> from torchmetrics.classification import BinaryPrecisionAtFixedRecall
170
+ >>> metric = BinaryPrecisionAtFixedRecall(min_recall=0.5)
171
+ >>> values = [ ]
172
+ >>> for _ in range(10):
173
+ ... # we index by 0 such that only the maximum recall value is plotted
174
+ ... values.append(metric(rand(10), randint(2,(10,)))[0])
175
+ >>> fig_, ax_ = metric.plot(values)
176
+
177
+ """
178
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
179
+ return self._plot(val, ax)
180
+
181
+
182
+ class MulticlassPrecisionAtFixedRecall(MulticlassPrecisionRecallCurve):
183
+ r"""Compute the highest possible precision value given the minimum recall thresholds provided.
184
+
185
+ This is done by first calculating the precision-recall curve for different thresholds and the find the precision for
186
+ a given recall level.
187
+
188
+ As input to ``forward`` and ``update`` the metric accepts the following input:
189
+
190
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
191
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
192
+ the input to be logits and will auto apply softmax per sample.
193
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
194
+ ground truth labels, and therefore only contain values in the [0, n_classes-1] range (except if `ignore_index`
195
+ is specified).
196
+
197
+ .. tip::
198
+ Additional dimension ``...`` will be flattened into the batch dimension.
199
+
200
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 2 tensors or 2 lists containing:
201
+
202
+ - ``precision`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the maximum precision for the
203
+ given recall level per class
204
+ - ``threshold`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the corresponding threshold
205
+ level per class
206
+
207
+ .. note::
208
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
209
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to ``None`` will activate the
210
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
211
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
212
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
213
+
214
+ Args:
215
+ num_classes: Integer specifying the number of classes
216
+ min_recall: float value specifying minimum recall threshold.
217
+ thresholds:
218
+ Can be one of:
219
+
220
+ - If set to ``None``, will use a non-binned approach where thresholds are dynamically calculated from
221
+ all the data. Most accurate but also most memory consuming approach.
222
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
223
+ 0 to 1 as bins for the calculation.
224
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
225
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
226
+ bins for the calculation.
227
+
228
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
229
+ Set to ``False`` for faster computations.
230
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
231
+
232
+ Example:
233
+ >>> from torch import tensor
234
+ >>> from torchmetrics.classification import MulticlassPrecisionAtFixedRecall
235
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
236
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
237
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
238
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
239
+ >>> target = tensor([0, 1, 3, 2])
240
+ >>> metric = MulticlassPrecisionAtFixedRecall(num_classes=5, min_recall=0.5, thresholds=None)
241
+ >>> metric(preds, target) # doctest: +NORMALIZE_WHITESPACE
242
+ (tensor([1.0000, 1.0000, 0.2500, 0.2500, 0.0000]),
243
+ tensor([7.5000e-01, 7.5000e-01, 5.0000e-02, 5.0000e-02, 1.0000e+06]))
244
+ >>> mcrafp = MulticlassPrecisionAtFixedRecall(num_classes=5, min_recall=0.5, thresholds=5)
245
+ >>> mcrafp(preds, target) # doctest: +NORMALIZE_WHITESPACE
246
+ (tensor([1.0000, 1.0000, 0.2500, 0.2500, 0.0000]),
247
+ tensor([7.5000e-01, 7.5000e-01, 0.0000e+00, 0.0000e+00, 1.0000e+06]))
248
+
249
+ """
250
+
251
+ is_differentiable: bool = False
252
+ higher_is_better: Optional[bool] = None
253
+ full_state_update: bool = False
254
+ plot_lower_bound: float = 0.0
255
+ plot_upper_bound: float = 1.0
256
+ plot_legend_name: str = "Class"
257
+
258
+ def __init__(
259
+ self,
260
+ num_classes: int,
261
+ min_recall: float,
262
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
263
+ ignore_index: Optional[int] = None,
264
+ validate_args: bool = True,
265
+ **kwargs: Any,
266
+ ) -> None:
267
+ super().__init__(
268
+ num_classes=num_classes, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
269
+ )
270
+ if validate_args:
271
+ _multiclass_recall_at_fixed_precision_arg_validation(num_classes, min_recall, thresholds, ignore_index)
272
+ self.validate_args = validate_args
273
+ self.min_recall = min_recall
274
+
275
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
276
+ """Compute metric."""
277
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
278
+ return _multiclass_recall_at_fixed_precision_arg_compute(
279
+ state, self.num_classes, self.thresholds, self.min_recall, reduce_fn=_precision_at_recall
280
+ )
281
+
282
+ def plot( # type: ignore[override]
283
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
284
+ ) -> _PLOT_OUT_TYPE:
285
+ """Plot a single or multiple values from the metric.
286
+
287
+ Args:
288
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
289
+ If no value is provided, will automatically call `metric.compute` and plot that result.
290
+ ax: An matplotlib axis object. If provided will add plot to that axis
291
+
292
+ Returns:
293
+ Figure object and Axes object
294
+
295
+ Raises:
296
+ ModuleNotFoundError:
297
+ If `matplotlib` is not installed
298
+
299
+ .. plot::
300
+ :scale: 75
301
+
302
+ >>> from torch import rand, randint
303
+ >>> # Example plotting a single value per class
304
+ >>> from torchmetrics.classification import MulticlassPrecisionAtFixedRecall
305
+ >>> metric = MulticlassPrecisionAtFixedRecall(num_classes=3, min_recall=0.5)
306
+ >>> metric.update(rand(20, 3).softmax(dim=-1), randint(3, (20,)))
307
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
308
+
309
+ .. plot::
310
+ :scale: 75
311
+
312
+ >>> from torch import rand, randint
313
+ >>> # Example plotting a multiple values per class
314
+ >>> from torchmetrics.classification import MulticlassPrecisionAtFixedRecall
315
+ >>> metric = MulticlassPrecisionAtFixedRecall(num_classes=3, min_recall=0.5)
316
+ >>> values = []
317
+ >>> for _ in range(20):
318
+ ... # we index by 0 such that only the maximum recall value is plotted
319
+ ... values.append(metric(rand(20, 3).softmax(dim=-1), randint(3, (20,)))[0])
320
+ >>> fig_, ax_ = metric.plot(values)
321
+
322
+ """
323
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
324
+ return self._plot(val, ax)
325
+
326
+
327
+ class MultilabelPrecisionAtFixedRecall(MultilabelPrecisionRecallCurve):
328
+ r"""Compute the highest possible precision value given the minimum recall thresholds provided.
329
+
330
+ This is done by first calculating the precision-recall curve for different thresholds and the find the precision for
331
+ a given recall level.
332
+
333
+ As input to ``forward`` and ``update`` the metric accepts the following input:
334
+
335
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
336
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
337
+ the input to be logits and will auto apply sigmoid per element.
338
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
339
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
340
+ 1 always encodes the positive class.
341
+
342
+ .. tip::
343
+ Additional dimension ``...`` will be flattened into the batch dimension.
344
+
345
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 2 tensors or 2 lists containing:
346
+
347
+ - ``precision`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the maximum precision for the
348
+ given recall level per class
349
+ - ``threshold`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the corresponding threshold
350
+ level per class
351
+
352
+ .. note::
353
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
354
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to ``None`` will activate the
355
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
356
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
357
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
358
+
359
+ Args:
360
+ num_labels: Integer specifying the number of labels
361
+ min_recall: float value specifying minimum recall threshold.
362
+ thresholds:
363
+ Can be one of:
364
+
365
+ - If set to ``None``, will use a non-binned approach where thresholds are dynamically calculated from
366
+ all the data. Most accurate but also most memory consuming approach.
367
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
368
+ 0 to 1 as bins for the calculation.
369
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
370
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
371
+ bins for the calculation.
372
+
373
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
374
+ Set to ``False`` for faster computations.
375
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
376
+
377
+ Example:
378
+ >>> from torch import tensor
379
+ >>> from torchmetrics.classification import MultilabelPrecisionAtFixedRecall
380
+ >>> preds = tensor([[0.75, 0.05, 0.35],
381
+ ... [0.45, 0.75, 0.05],
382
+ ... [0.05, 0.55, 0.75],
383
+ ... [0.05, 0.65, 0.05]])
384
+ >>> target = tensor([[1, 0, 1],
385
+ ... [0, 0, 0],
386
+ ... [0, 1, 1],
387
+ ... [1, 1, 1]])
388
+ >>> metric = MultilabelPrecisionAtFixedRecall(num_labels=3, min_recall=0.5, thresholds=None)
389
+ >>> metric(preds, target)
390
+ (tensor([1.0000, 0.6667, 1.0000]), tensor([0.7500, 0.5500, 0.3500]))
391
+ >>> mlrafp = MultilabelPrecisionAtFixedRecall(num_labels=3, min_recall=0.5, thresholds=5)
392
+ >>> mlrafp(preds, target)
393
+ (tensor([1.0000, 0.6667, 1.0000]), tensor([0.7500, 0.5000, 0.2500]))
394
+
395
+ """
396
+
397
+ is_differentiable: bool = False
398
+ higher_is_better: Optional[bool] = None
399
+ full_state_update: bool = False
400
+ plot_lower_bound: float = 0.0
401
+ plot_upper_bound: float = 1.0
402
+ plot_legend_name: str = "Label"
403
+
404
+ def __init__(
405
+ self,
406
+ num_labels: int,
407
+ min_recall: float,
408
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
409
+ ignore_index: Optional[int] = None,
410
+ validate_args: bool = True,
411
+ **kwargs: Any,
412
+ ) -> None:
413
+ super().__init__(
414
+ num_labels=num_labels, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
415
+ )
416
+ if validate_args:
417
+ _multilabel_recall_at_fixed_precision_arg_validation(num_labels, min_recall, thresholds, ignore_index)
418
+ self.validate_args = validate_args
419
+ self.min_recall = min_recall
420
+
421
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
422
+ """Compute metric."""
423
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
424
+ return _multilabel_recall_at_fixed_precision_arg_compute(
425
+ state, self.num_labels, self.thresholds, self.ignore_index, self.min_recall, reduce_fn=_precision_at_recall
426
+ )
427
+
428
+ def plot( # type: ignore[override]
429
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
430
+ ) -> _PLOT_OUT_TYPE:
431
+ """Plot a single or multiple values from the metric.
432
+
433
+ Args:
434
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
435
+ If no value is provided, will automatically call `metric.compute` and plot that result.
436
+ ax: An matplotlib axis object. If provided will add plot to that axis
437
+
438
+ Returns:
439
+ Figure object and Axes object
440
+
441
+ Raises:
442
+ ModuleNotFoundError:
443
+ If `matplotlib` is not installed
444
+
445
+ .. plot::
446
+ :scale: 75
447
+
448
+ >>> from torch import rand, randint
449
+ >>> # Example plotting a single value
450
+ >>> from torchmetrics.classification import MultilabelPrecisionAtFixedRecall
451
+ >>> metric = MultilabelPrecisionAtFixedRecall(num_labels=3, min_recall=0.5)
452
+ >>> metric.update(rand(20, 3), randint(2, (20, 3)))
453
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
454
+
455
+ .. plot::
456
+ :scale: 75
457
+
458
+ >>> from torch import rand, randint
459
+ >>> # Example plotting multiple values
460
+ >>> from torchmetrics.classification import MultilabelPrecisionAtFixedRecall
461
+ >>> metric = MultilabelPrecisionAtFixedRecall(num_labels=3, min_recall=0.5)
462
+ >>> values = [ ]
463
+ >>> for _ in range(10):
464
+ ... # we index by 0 such that only the maximum recall value is plotted
465
+ ... values.append(metric(rand(20, 3), randint(2, (20, 3)))[0])
466
+ >>> fig_, ax_ = metric.plot(values)
467
+
468
+ """
469
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
470
+ return self._plot(val, ax)
471
+
472
+
473
+ class PrecisionAtFixedRecall(_ClassificationTaskWrapper):
474
+ r"""Compute the highest possible recall value given the minimum precision thresholds provided.
475
+
476
+ This is done by first calculating the precision-recall curve for different thresholds and the find the recall for
477
+ a given precision level.
478
+
479
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
480
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
481
+ :class:`~torchmetrics.classification.BinaryPrecisionAtFixedRecall`,
482
+ :class:`~torchmetrics.classification.MulticlassPrecisionAtFixedRecall` and
483
+ :class:`~torchmetrics.classification.MultilabelPrecisionAtFixedRecall` for the specific details of each argument
484
+ influence and examples.
485
+
486
+ """
487
+
488
+ def __new__( # type: ignore[misc]
489
+ cls: type["PrecisionAtFixedRecall"],
490
+ task: Literal["binary", "multiclass", "multilabel"],
491
+ min_recall: float,
492
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
493
+ num_classes: Optional[int] = None,
494
+ num_labels: Optional[int] = None,
495
+ ignore_index: Optional[int] = None,
496
+ validate_args: bool = True,
497
+ **kwargs: Any,
498
+ ) -> Metric:
499
+ """Initialize task metric."""
500
+ task = ClassificationTask.from_str(task)
501
+ if task == ClassificationTask.BINARY:
502
+ return BinaryPrecisionAtFixedRecall(min_recall, thresholds, ignore_index, validate_args, **kwargs)
503
+ if task == ClassificationTask.MULTICLASS:
504
+ if not isinstance(num_classes, int):
505
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
506
+ return MulticlassPrecisionAtFixedRecall(
507
+ num_classes, min_recall, thresholds, ignore_index, validate_args, **kwargs
508
+ )
509
+ if task == ClassificationTask.MULTILABEL:
510
+ if not isinstance(num_labels, int):
511
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
512
+ return MultilabelPrecisionAtFixedRecall(
513
+ num_labels, min_recall, thresholds, ignore_index, validate_args, **kwargs
514
+ )
515
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall.py ADDED
@@ -0,0 +1,1086 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.stat_scores import BinaryStatScores, MulticlassStatScores, MultilabelStatScores
22
+ from torchmetrics.functional.classification.precision_recall import (
23
+ _precision_recall_reduce,
24
+ )
25
+ from torchmetrics.metric import Metric
26
+ from torchmetrics.utilities.enums import ClassificationTask
27
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
28
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
29
+
30
+ if not _MATPLOTLIB_AVAILABLE:
31
+ __doctest_skip__ = [
32
+ "BinaryPrecision.plot",
33
+ "MulticlassPrecision.plot",
34
+ "MultilabelPrecision.plot",
35
+ "BinaryRecall.plot",
36
+ "MulticlassRecall.plot",
37
+ "MultilabelRecall.plot",
38
+ ]
39
+
40
+
41
+ class BinaryPrecision(BinaryStatScores):
42
+ r"""Compute `Precision`_ for binary tasks.
43
+
44
+ .. math:: \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}}
45
+
46
+ Where :math:`\text{TP}` and :math:`\text{FP}` represent the number of true positives and false positives
47
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0`. If this case is
48
+ encountered a score of `zero_division` (0 or 1, default is 0) is returned.
49
+
50
+ As input to ``forward`` and ``update`` the metric accepts the following input:
51
+
52
+ - ``preds`` (:class:`~torch.Tensor`): A int or float tensor of shape ``(N, ...)``. If preds is a floating point
53
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per
54
+ element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
55
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
56
+
57
+ As output to ``forward`` and ``compute`` the metric returns the following output:
58
+
59
+ - ``bp`` (:class:`~torch.Tensor`): If ``multidim_average`` is set to ``global``, the metric returns a scalar
60
+ value. If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of a
61
+ scalar value per sample.
62
+
63
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
64
+ which the reduction will then be applied over instead of the sample dimension ``N``.
65
+
66
+ Args:
67
+ threshold: Threshold for transforming probability to binary {0,1} predictions
68
+ multidim_average:
69
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
70
+
71
+ - ``global``: Additional dimensions are flatted along the batch dimension
72
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
73
+ The statistics in this case are calculated over the additional dimensions.
74
+
75
+ ignore_index:
76
+ Specifies a target value that is ignored and does not contribute to the metric calculation
77
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
78
+ Set to ``False`` for faster computations.
79
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FP} = 0`.
80
+
81
+ Example (preds is int tensor):
82
+ >>> from torch import tensor
83
+ >>> from torchmetrics.classification import BinaryPrecision
84
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
85
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
86
+ >>> metric = BinaryPrecision()
87
+ >>> metric(preds, target)
88
+ tensor(0.6667)
89
+
90
+ Example (preds is float tensor):
91
+ >>> from torchmetrics.classification import BinaryPrecision
92
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
93
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
94
+ >>> metric = BinaryPrecision()
95
+ >>> metric(preds, target)
96
+ tensor(0.6667)
97
+
98
+ Example (multidim tensors):
99
+ >>> from torchmetrics.classification import BinaryPrecision
100
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
101
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
102
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
103
+ >>> metric = BinaryPrecision(multidim_average='samplewise')
104
+ >>> metric(preds, target)
105
+ tensor([0.4000, 0.0000])
106
+
107
+ """
108
+
109
+ is_differentiable: bool = False
110
+ higher_is_better: Optional[bool] = True
111
+ full_state_update: bool = False
112
+ plot_lower_bound: float = 0.0
113
+ plot_upper_bound: float = 1.0
114
+
115
+ def compute(self) -> Tensor:
116
+ """Compute metric."""
117
+ tp, fp, tn, fn = self._final_state()
118
+ return _precision_recall_reduce(
119
+ "precision",
120
+ tp,
121
+ fp,
122
+ tn,
123
+ fn,
124
+ average="binary",
125
+ multidim_average=self.multidim_average,
126
+ zero_division=self.zero_division,
127
+ )
128
+
129
+ def plot(
130
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
131
+ ) -> _PLOT_OUT_TYPE:
132
+ """Plot a single or multiple values from the metric.
133
+
134
+ Args:
135
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
136
+ If no value is provided, will automatically call `metric.compute` and plot that result.
137
+ ax: An matplotlib axis object. If provided will add plot to that axis
138
+
139
+ Returns:
140
+ Figure object and Axes object
141
+
142
+ Raises:
143
+ ModuleNotFoundError:
144
+ If `matplotlib` is not installed
145
+
146
+ .. plot::
147
+ :scale: 75
148
+
149
+ >>> from torch import rand, randint
150
+ >>> # Example plotting a single value
151
+ >>> from torchmetrics.classification import BinaryPrecision
152
+ >>> metric = BinaryPrecision()
153
+ >>> metric.update(rand(10), randint(2,(10,)))
154
+ >>> fig_, ax_ = metric.plot()
155
+
156
+ .. plot::
157
+ :scale: 75
158
+
159
+ >>> from torch import rand, randint
160
+ >>> # Example plotting multiple values
161
+ >>> from torchmetrics.classification import BinaryPrecision
162
+ >>> metric = BinaryPrecision()
163
+ >>> values = [ ]
164
+ >>> for _ in range(10):
165
+ ... values.append(metric(rand(10), randint(2,(10,))))
166
+ >>> fig_, ax_ = metric.plot(values)
167
+
168
+ """
169
+ return self._plot(val, ax)
170
+
171
+
172
+ class MulticlassPrecision(MulticlassStatScores):
173
+ r"""Compute `Precision`_ for multiclass tasks.
174
+
175
+ .. math:: \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}}
176
+
177
+ Where :math:`\text{TP}` and :math:`\text{FP}` represent the number of true positives and false positives
178
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0`. If this case is
179
+ encountered for any class, the metric for that class will be set to `zero_division` (0 or 1, default is 0) and
180
+ the overall metric may therefore be affected in turn.
181
+
182
+ As input to ``forward`` and ``update`` the metric accepts the following input:
183
+
184
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
185
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
186
+ probabilities/logits into an int tensor.
187
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``.
188
+
189
+
190
+ As output to ``forward`` and ``compute`` the metric returns the following output:
191
+
192
+ - ``mcp`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
193
+ arguments:
194
+
195
+ - If ``multidim_average`` is set to ``global``:
196
+
197
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
198
+ - If ``average=None/'none'``, the shape will be ``(C,)``
199
+
200
+ - If ``multidim_average`` is set to ``samplewise``:
201
+
202
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
203
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
204
+
205
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
206
+ which the reduction will then be applied over instead of the sample dimension ``N``.
207
+
208
+ Args:
209
+ num_classes: Integer specifying the number of classes
210
+ average:
211
+ Defines the reduction that is applied over labels. Should be one of the following:
212
+
213
+ - ``micro``: Sum statistics over all labels
214
+ - ``macro``: Calculate statistics for each label and average them
215
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
216
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
217
+ top_k:
218
+ Number of highest probability or logit score predictions considered to find the correct label.
219
+ Only works when ``preds`` contain probabilities/logits.
220
+ multidim_average:
221
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
222
+
223
+ - ``global``: Additional dimensions are flatted along the batch dimension
224
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
225
+ The statistics in this case are calculated over the additional dimensions.
226
+
227
+ ignore_index:
228
+ Specifies a target value that is ignored and does not contribute to the metric calculation
229
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
230
+ Set to ``False`` for faster computations.
231
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FP} = 0`.
232
+
233
+ Example (preds is int tensor):
234
+ >>> from torch import tensor
235
+ >>> from torchmetrics.classification import MulticlassPrecision
236
+ >>> target = tensor([2, 1, 0, 0])
237
+ >>> preds = tensor([2, 1, 0, 1])
238
+ >>> metric = MulticlassPrecision(num_classes=3)
239
+ >>> metric(preds, target)
240
+ tensor(0.8333)
241
+ >>> mcp = MulticlassPrecision(num_classes=3, average=None)
242
+ >>> mcp(preds, target)
243
+ tensor([1.0000, 0.5000, 1.0000])
244
+
245
+ Example (preds is float tensor):
246
+ >>> from torchmetrics.classification import MulticlassPrecision
247
+ >>> target = tensor([2, 1, 0, 0])
248
+ >>> preds = tensor([[0.16, 0.26, 0.58],
249
+ ... [0.22, 0.61, 0.17],
250
+ ... [0.71, 0.09, 0.20],
251
+ ... [0.05, 0.82, 0.13]])
252
+ >>> metric = MulticlassPrecision(num_classes=3)
253
+ >>> metric(preds, target)
254
+ tensor(0.8333)
255
+ >>> mcp = MulticlassPrecision(num_classes=3, average=None)
256
+ >>> mcp(preds, target)
257
+ tensor([1.0000, 0.5000, 1.0000])
258
+
259
+ Example (multidim tensors):
260
+ >>> from torchmetrics.classification import MulticlassPrecision
261
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
262
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
263
+ >>> metric = MulticlassPrecision(num_classes=3, multidim_average='samplewise')
264
+ >>> metric(preds, target)
265
+ tensor([0.3889, 0.2778])
266
+ >>> mcp = MulticlassPrecision(num_classes=3, multidim_average='samplewise', average=None)
267
+ >>> mcp(preds, target)
268
+ tensor([[0.6667, 0.0000, 0.5000],
269
+ [0.0000, 0.5000, 0.3333]])
270
+
271
+ """
272
+
273
+ is_differentiable: bool = False
274
+ higher_is_better: Optional[bool] = True
275
+ full_state_update: bool = False
276
+ plot_lower_bound: float = 0.0
277
+ plot_upper_bound: float = 1.0
278
+ plot_legend_name: str = "Class"
279
+
280
+ def compute(self) -> Tensor:
281
+ """Compute metric."""
282
+ tp, fp, tn, fn = self._final_state()
283
+ return _precision_recall_reduce(
284
+ "precision",
285
+ tp,
286
+ fp,
287
+ tn,
288
+ fn,
289
+ average=self.average,
290
+ multidim_average=self.multidim_average,
291
+ top_k=self.top_k,
292
+ zero_division=self.zero_division,
293
+ )
294
+
295
+ def plot(
296
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
297
+ ) -> _PLOT_OUT_TYPE:
298
+ """Plot a single or multiple values from the metric.
299
+
300
+ Args:
301
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
302
+ If no value is provided, will automatically call `metric.compute` and plot that result.
303
+ ax: An matplotlib axis object. If provided will add plot to that axis
304
+
305
+ Returns:
306
+ Figure object and Axes object
307
+
308
+ Raises:
309
+ ModuleNotFoundError:
310
+ If `matplotlib` is not installed
311
+
312
+ .. plot::
313
+ :scale: 75
314
+
315
+ >>> from torch import randint
316
+ >>> # Example plotting a single value per class
317
+ >>> from torchmetrics.classification import MulticlassPrecision
318
+ >>> metric = MulticlassPrecision(num_classes=3, average=None)
319
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
320
+ >>> fig_, ax_ = metric.plot()
321
+
322
+ .. plot::
323
+ :scale: 75
324
+
325
+ >>> from torch import randint
326
+ >>> # Example plotting a multiple values per class
327
+ >>> from torchmetrics.classification import MulticlassPrecision
328
+ >>> metric = MulticlassPrecision(num_classes=3, average=None)
329
+ >>> values = []
330
+ >>> for _ in range(20):
331
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
332
+ >>> fig_, ax_ = metric.plot(values)
333
+
334
+ """
335
+ return self._plot(val, ax)
336
+
337
+
338
+ class MultilabelPrecision(MultilabelStatScores):
339
+ r"""Compute `Precision`_ for multilabel tasks.
340
+
341
+ .. math:: \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}}
342
+
343
+ Where :math:`\text{TP}` and :math:`\text{FP}` represent the number of true positives and false positives
344
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0`. If this case is
345
+ encountered for any label, the metric for that label will be set to `zero_division` (0 or 1, default is 0) and
346
+ the overall metric may therefore be affected in turn.
347
+
348
+ As input to ``forward`` and ``update`` the metric accepts the following input:
349
+
350
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor or float tensor of shape ``(N, C, ...)``.
351
+ If preds is a floating point tensor with values outside [0,1] range we consider the input to be logits and
352
+ will auto apply sigmoid per element. Additionally, we convert to int tensor with thresholding using the value
353
+ in ``threshold``.
354
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``.
355
+
356
+ As output to ``forward`` and ``compute`` the metric returns the following output:
357
+
358
+ - ``mlp`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
359
+ arguments:
360
+
361
+ - If ``multidim_average`` is set to ``global``:
362
+
363
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
364
+ - If ``average=None/'none'``, the shape will be ``(C,)``
365
+
366
+ - If ``multidim_average`` is set to ``samplewise``:
367
+
368
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
369
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
370
+
371
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
372
+ which the reduction will then be applied over instead of the sample dimension ``N``.
373
+
374
+ Args:
375
+ num_labels: Integer specifying the number of labels
376
+ threshold: Threshold for transforming probability to binary (0,1) predictions
377
+ average:
378
+ Defines the reduction that is applied over labels. Should be one of the following:
379
+
380
+ - ``micro``: Sum statistics over all labels
381
+ - ``macro``: Calculate statistics for each label and average them
382
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
383
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
384
+
385
+ multidim_average:
386
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
387
+
388
+ - ``global``: Additional dimensions are flatted along the batch dimension
389
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
390
+ The statistics in this case are calculated over the additional dimensions.
391
+
392
+ ignore_index:
393
+ Specifies a target value that is ignored and does not contribute to the metric calculation
394
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
395
+ Set to ``False`` for faster computations.
396
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FP} = 0`.
397
+
398
+ Example (preds is int tensor):
399
+ >>> from torch import tensor
400
+ >>> from torchmetrics.classification import MultilabelPrecision
401
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
402
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
403
+ >>> metric = MultilabelPrecision(num_labels=3)
404
+ >>> metric(preds, target)
405
+ tensor(0.5000)
406
+ >>> mlp = MultilabelPrecision(num_labels=3, average=None)
407
+ >>> mlp(preds, target)
408
+ tensor([1.0000, 0.0000, 0.5000])
409
+
410
+ Example (preds is float tensor):
411
+ >>> from torchmetrics.classification import MultilabelPrecision
412
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
413
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
414
+ >>> metric = MultilabelPrecision(num_labels=3)
415
+ >>> metric(preds, target)
416
+ tensor(0.5000)
417
+ >>> mlp = MultilabelPrecision(num_labels=3, average=None)
418
+ >>> mlp(preds, target)
419
+ tensor([1.0000, 0.0000, 0.5000])
420
+
421
+ Example (multidim tensors):
422
+ >>> from torchmetrics.classification import MultilabelPrecision
423
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
424
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
425
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
426
+ >>> metric = MultilabelPrecision(num_labels=3, multidim_average='samplewise')
427
+ >>> metric(preds, target)
428
+ tensor([0.3333, 0.0000])
429
+ >>> mlp = MultilabelPrecision(num_labels=3, multidim_average='samplewise', average=None)
430
+ >>> mlp(preds, target)
431
+ tensor([[0.5000, 0.5000, 0.0000],
432
+ [0.0000, 0.0000, 0.0000]])
433
+
434
+ """
435
+
436
+ is_differentiable: bool = False
437
+ higher_is_better: Optional[bool] = True
438
+ full_state_update: bool = False
439
+ plot_lower_bound: float = 0.0
440
+ plot_upper_bound: float = 1.0
441
+ plot_legend_name: str = "Label"
442
+
443
+ def compute(self) -> Tensor:
444
+ """Compute metric."""
445
+ tp, fp, tn, fn = self._final_state()
446
+ return _precision_recall_reduce(
447
+ "precision",
448
+ tp,
449
+ fp,
450
+ tn,
451
+ fn,
452
+ average=self.average,
453
+ multidim_average=self.multidim_average,
454
+ multilabel=True,
455
+ zero_division=self.zero_division,
456
+ )
457
+
458
+ def plot(
459
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
460
+ ) -> _PLOT_OUT_TYPE:
461
+ """Plot a single or multiple values from the metric.
462
+
463
+ Args:
464
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
465
+ If no value is provided, will automatically call `metric.compute` and plot that result.
466
+ ax: An matplotlib axis object. If provided will add plot to that axis
467
+
468
+ Returns:
469
+ Figure object and Axes object
470
+
471
+ Raises:
472
+ ModuleNotFoundError:
473
+ If `matplotlib` is not installed
474
+
475
+ .. plot::
476
+ :scale: 75
477
+
478
+ >>> from torch import rand, randint
479
+ >>> # Example plotting a single value
480
+ >>> from torchmetrics.classification import MultilabelPrecision
481
+ >>> metric = MultilabelPrecision(num_labels=3)
482
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
483
+ >>> fig_, ax_ = metric.plot()
484
+
485
+ .. plot::
486
+ :scale: 75
487
+
488
+ >>> from torch import rand, randint
489
+ >>> # Example plotting multiple values
490
+ >>> from torchmetrics.classification import MultilabelPrecision
491
+ >>> metric = MultilabelPrecision(num_labels=3)
492
+ >>> values = [ ]
493
+ >>> for _ in range(10):
494
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
495
+ >>> fig_, ax_ = metric.plot(values)
496
+
497
+ """
498
+ return self._plot(val, ax)
499
+
500
+
501
+ class BinaryRecall(BinaryStatScores):
502
+ r"""Compute `Recall`_ for binary tasks.
503
+
504
+ .. math:: \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}}
505
+
506
+ Where :math:`\text{TP}` and :math:`\text{FN}` represent the number of true positives and false negatives
507
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FN} \neq 0`. If this case is
508
+ encountered a score of `zero_division` (0 or 1, default is 0) is returned.
509
+
510
+ As input to ``forward`` and ``update`` the metric accepts the following input:
511
+
512
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor or float tensor of shape ``(N, ...)``. If preds is a
513
+ floating point tensor with values outside [0,1] range we consider the input to be logits and will auto apply
514
+ sigmoid per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
515
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
516
+
517
+ As output to ``forward`` and ``compute`` the metric returns the following output:
518
+
519
+ - ``br`` (:class:`~torch.Tensor`): If ``multidim_average`` is set to ``global``, the metric returns a scalar
520
+ value. If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of
521
+ a scalar value per sample.
522
+
523
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
524
+ which the reduction will then be applied over instead of the sample dimension ``N``.
525
+
526
+ Args:
527
+ threshold: Threshold for transforming probability to binary {0,1} predictions
528
+ multidim_average:
529
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
530
+
531
+ - ``global``: Additional dimensions are flatted along the batch dimension
532
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
533
+ The statistics in this case are calculated over the additional dimensions.
534
+
535
+ ignore_index:
536
+ Specifies a target value that is ignored and does not contribute to the metric calculation
537
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
538
+ Set to ``False`` for faster computations.
539
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FN} = 0`.
540
+
541
+ Example (preds is int tensor):
542
+ >>> from torch import tensor
543
+ >>> from torchmetrics.classification import BinaryRecall
544
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
545
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
546
+ >>> metric = BinaryRecall()
547
+ >>> metric(preds, target)
548
+ tensor(0.6667)
549
+
550
+ Example (preds is float tensor):
551
+ >>> from torchmetrics.classification import BinaryRecall
552
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
553
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
554
+ >>> metric = BinaryRecall()
555
+ >>> metric(preds, target)
556
+ tensor(0.6667)
557
+
558
+ Example (multidim tensors):
559
+ >>> from torchmetrics.classification import BinaryRecall
560
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
561
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
562
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
563
+ >>> metric = BinaryRecall(multidim_average='samplewise')
564
+ >>> metric(preds, target)
565
+ tensor([0.6667, 0.0000])
566
+
567
+ """
568
+
569
+ is_differentiable: bool = False
570
+ higher_is_better: Optional[bool] = True
571
+ full_state_update: bool = False
572
+ plot_lower_bound: float = 0.0
573
+ plot_upper_bound: float = 1.0
574
+
575
+ def compute(self) -> Tensor:
576
+ """Compute metric."""
577
+ tp, fp, tn, fn = self._final_state()
578
+ return _precision_recall_reduce(
579
+ "recall",
580
+ tp,
581
+ fp,
582
+ tn,
583
+ fn,
584
+ average="binary",
585
+ multidim_average=self.multidim_average,
586
+ zero_division=self.zero_division,
587
+ )
588
+
589
+ def plot(
590
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
591
+ ) -> _PLOT_OUT_TYPE:
592
+ """Plot a single or multiple values from the metric.
593
+
594
+ Args:
595
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
596
+ If no value is provided, will automatically call `metric.compute` and plot that result.
597
+ ax: An matplotlib axis object. If provided will add plot to that axis
598
+
599
+ Returns:
600
+ Figure object and Axes object
601
+
602
+ Raises:
603
+ ModuleNotFoundError:
604
+ If `matplotlib` is not installed
605
+
606
+ .. plot::
607
+ :scale: 75
608
+
609
+ >>> from torch import rand, randint
610
+ >>> # Example plotting a single value
611
+ >>> from torchmetrics.classification import BinaryRecall
612
+ >>> metric = BinaryRecall()
613
+ >>> metric.update(rand(10), randint(2,(10,)))
614
+ >>> fig_, ax_ = metric.plot()
615
+
616
+ .. plot::
617
+ :scale: 75
618
+
619
+ >>> from torch import rand, randint
620
+ >>> # Example plotting multiple values
621
+ >>> from torchmetrics.classification import BinaryRecall
622
+ >>> metric = BinaryRecall()
623
+ >>> values = [ ]
624
+ >>> for _ in range(10):
625
+ ... values.append(metric(rand(10), randint(2,(10,))))
626
+ >>> fig_, ax_ = metric.plot(values)
627
+
628
+ """
629
+ return self._plot(val, ax)
630
+
631
+
632
+ class MulticlassRecall(MulticlassStatScores):
633
+ r"""Compute `Recall`_ for multiclass tasks.
634
+
635
+ .. math:: \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}}
636
+
637
+ Where :math:`\text{TP}` and :math:`\text{FN}` represent the number of true positives and false negatives
638
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FN} \neq 0`. If this case is
639
+ encountered for any class, the metric for that class will be set to `zero_division` (0 or 1, default is 0) and
640
+ the overall metric may therefore be affected in turn.
641
+
642
+ As input to ``forward`` and ``update`` the metric accepts the following input:
643
+
644
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``
645
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
646
+ probabilities/logits into an int tensor.
647
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
648
+
649
+ As output to ``forward`` and ``compute`` the metric returns the following output:
650
+
651
+ - ``mcr`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
652
+ arguments:
653
+
654
+ - If ``multidim_average`` is set to ``global``:
655
+
656
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
657
+ - If ``average=None/'none'``, the shape will be ``(C,)``
658
+
659
+ - If ``multidim_average`` is set to ``samplewise``:
660
+
661
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
662
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
663
+
664
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
665
+ which the reduction will then be applied over instead of the sample dimension ``N``.
666
+
667
+ Args:
668
+ num_classes: Integer specifying the number of classes
669
+ average:
670
+ Defines the reduction that is applied over labels. Should be one of the following:
671
+
672
+ - ``micro``: Sum statistics over all labels
673
+ - ``macro``: Calculate statistics for each label and average them
674
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
675
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
676
+ top_k:
677
+ Number of highest probability or logit score predictions considered to find the correct label.
678
+ Only works when ``preds`` contain probabilities/logits.
679
+ multidim_average:
680
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
681
+
682
+ - ``global``: Additional dimensions are flatted along the batch dimension
683
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
684
+ The statistics in this case are calculated over the additional dimensions.
685
+
686
+ ignore_index:
687
+ Specifies a target value that is ignored and does not contribute to the metric calculation
688
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
689
+ Set to ``False`` for faster computations.
690
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FN} = 0`.
691
+
692
+ Example (preds is int tensor):
693
+ >>> from torch import tensor
694
+ >>> from torchmetrics.classification import MulticlassRecall
695
+ >>> target = tensor([2, 1, 0, 0])
696
+ >>> preds = tensor([2, 1, 0, 1])
697
+ >>> metric = MulticlassRecall(num_classes=3)
698
+ >>> metric(preds, target)
699
+ tensor(0.8333)
700
+ >>> mcr = MulticlassRecall(num_classes=3, average=None)
701
+ >>> mcr(preds, target)
702
+ tensor([0.5000, 1.0000, 1.0000])
703
+
704
+ Example (preds is float tensor):
705
+ >>> from torchmetrics.classification import MulticlassRecall
706
+ >>> target = tensor([2, 1, 0, 0])
707
+ >>> preds = tensor([[0.16, 0.26, 0.58],
708
+ ... [0.22, 0.61, 0.17],
709
+ ... [0.71, 0.09, 0.20],
710
+ ... [0.05, 0.82, 0.13]])
711
+ >>> metric = MulticlassRecall(num_classes=3)
712
+ >>> metric(preds, target)
713
+ tensor(0.8333)
714
+ >>> mcr = MulticlassRecall(num_classes=3, average=None)
715
+ >>> mcr(preds, target)
716
+ tensor([0.5000, 1.0000, 1.0000])
717
+
718
+ Example (multidim tensors):
719
+ >>> from torchmetrics.classification import MulticlassRecall
720
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
721
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
722
+ >>> metric = MulticlassRecall(num_classes=3, multidim_average='samplewise')
723
+ >>> metric(preds, target)
724
+ tensor([0.5000, 0.2778])
725
+ >>> mcr = MulticlassRecall(num_classes=3, multidim_average='samplewise', average=None)
726
+ >>> mcr(preds, target)
727
+ tensor([[1.0000, 0.0000, 0.5000],
728
+ [0.0000, 0.3333, 0.5000]])
729
+
730
+ """
731
+
732
+ is_differentiable: bool = False
733
+ higher_is_better: Optional[bool] = True
734
+ full_state_update: bool = False
735
+ plot_lower_bound: float = 0.0
736
+ plot_upper_bound: float = 1.0
737
+ plot_legend_name: str = "Class"
738
+
739
+ def compute(self) -> Tensor:
740
+ """Compute metric."""
741
+ tp, fp, tn, fn = self._final_state()
742
+ return _precision_recall_reduce(
743
+ "recall",
744
+ tp,
745
+ fp,
746
+ tn,
747
+ fn,
748
+ average=self.average,
749
+ multidim_average=self.multidim_average,
750
+ top_k=self.top_k,
751
+ zero_division=self.zero_division,
752
+ )
753
+
754
+ def plot(
755
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
756
+ ) -> _PLOT_OUT_TYPE:
757
+ """Plot a single or multiple values from the metric.
758
+
759
+ Args:
760
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
761
+ If no value is provided, will automatically call `metric.compute` and plot that result.
762
+ ax: An matplotlib axis object. If provided will add plot to that axis
763
+
764
+ Returns:
765
+ Figure object and Axes object
766
+
767
+ Raises:
768
+ ModuleNotFoundError:
769
+ If `matplotlib` is not installed
770
+
771
+ .. plot::
772
+ :scale: 75
773
+
774
+ >>> from torch import randint
775
+ >>> # Example plotting a single value per class
776
+ >>> from torchmetrics.classification import MulticlassRecall
777
+ >>> metric = MulticlassRecall(num_classes=3, average=None)
778
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
779
+ >>> fig_, ax_ = metric.plot()
780
+
781
+ .. plot::
782
+ :scale: 75
783
+
784
+ >>> from torch import randint
785
+ >>> # Example plotting a multiple values per class
786
+ >>> from torchmetrics.classification import MulticlassRecall
787
+ >>> metric = MulticlassRecall(num_classes=3, average=None)
788
+ >>> values = []
789
+ >>> for _ in range(20):
790
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
791
+ >>> fig_, ax_ = metric.plot(values)
792
+
793
+ """
794
+ return self._plot(val, ax)
795
+
796
+
797
+ class MultilabelRecall(MultilabelStatScores):
798
+ r"""Compute `Recall`_ for multilabel tasks.
799
+
800
+ .. math:: \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}}
801
+
802
+ Where :math:`\text{TP}` and :math:`\text{FN}` represent the number of true positives and false negatives
803
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FN} \neq 0`. If this case is
804
+ encountered for any label, the metric for that label will be set to `zero_division` (0 or 1, default is 0) and
805
+ the overall metric may therefore be affected in turn.
806
+
807
+ As input to ``forward`` and ``update`` the metric accepts the following input:
808
+
809
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
810
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
811
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
812
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
813
+
814
+ As output to ``forward`` and ``compute`` the metric returns the following output:
815
+
816
+ - ``mlr`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
817
+ arguments:
818
+
819
+ - If ``multidim_average`` is set to ``global``:
820
+
821
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
822
+ - If ``average=None/'none'``, the shape will be ``(C,)``
823
+
824
+ - If ``multidim_average`` is set to ``samplewise``:
825
+
826
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
827
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
828
+
829
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
830
+ which the reduction will then be applied over instead of the sample dimension ``N``.
831
+
832
+ Args:
833
+ num_labels: Integer specifying the number of labels
834
+ threshold: Threshold for transforming probability to binary (0,1) predictions
835
+ average:
836
+ Defines the reduction that is applied over labels. Should be one of the following:
837
+
838
+ - ``micro``: Sum statistics over all labels
839
+ - ``macro``: Calculate statistics for each label and average them
840
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
841
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
842
+
843
+ multidim_average:
844
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
845
+
846
+ - ``global``: Additional dimensions are flatted along the batch dimension
847
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
848
+ The statistics in this case are calculated over the additional dimensions.
849
+
850
+ ignore_index:
851
+ Specifies a target value that is ignored and does not contribute to the metric calculation
852
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
853
+ Set to ``False`` for faster computations.
854
+ zero_division: Should be `0` or `1`. The value returned when :math:`\text{TP} + \text{FN} = 0`.
855
+
856
+ Example (preds is int tensor):
857
+ >>> from torch import tensor
858
+ >>> from torchmetrics.classification import MultilabelRecall
859
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
860
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
861
+ >>> metric = MultilabelRecall(num_labels=3)
862
+ >>> metric(preds, target)
863
+ tensor(0.6667)
864
+ >>> mlr = MultilabelRecall(num_labels=3, average=None)
865
+ >>> mlr(preds, target)
866
+ tensor([1., 0., 1.])
867
+
868
+ Example (preds is float tensor):
869
+ >>> from torchmetrics.classification import MultilabelRecall
870
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
871
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
872
+ >>> metric = MultilabelRecall(num_labels=3)
873
+ >>> metric(preds, target)
874
+ tensor(0.6667)
875
+ >>> mlr = MultilabelRecall(num_labels=3, average=None)
876
+ >>> mlr(preds, target)
877
+ tensor([1., 0., 1.])
878
+
879
+ Example (multidim tensors):
880
+ >>> from torchmetrics.classification import MultilabelRecall
881
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
882
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
883
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
884
+ >>> metric = MultilabelRecall(num_labels=3, multidim_average='samplewise')
885
+ >>> metric(preds, target)
886
+ tensor([0.6667, 0.0000])
887
+ >>> mlr = MultilabelRecall(num_labels=3, multidim_average='samplewise', average=None)
888
+ >>> mlr(preds, target)
889
+ tensor([[1., 1., 0.],
890
+ [0., 0., 0.]])
891
+
892
+ """
893
+
894
+ is_differentiable: bool = False
895
+ higher_is_better: Optional[bool] = True
896
+ full_state_update: bool = False
897
+ plot_lower_bound: float = 0.0
898
+ plot_upper_bound: float = 1.0
899
+ plot_legend_name: str = "Label"
900
+
901
+ def compute(self) -> Tensor:
902
+ """Compute metric."""
903
+ tp, fp, tn, fn = self._final_state()
904
+ return _precision_recall_reduce(
905
+ "recall",
906
+ tp,
907
+ fp,
908
+ tn,
909
+ fn,
910
+ average=self.average,
911
+ multidim_average=self.multidim_average,
912
+ multilabel=True,
913
+ zero_division=self.zero_division,
914
+ )
915
+
916
+ def plot(
917
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
918
+ ) -> _PLOT_OUT_TYPE:
919
+ """Plot a single or multiple values from the metric.
920
+
921
+ Args:
922
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
923
+ If no value is provided, will automatically call `metric.compute` and plot that result.
924
+ ax: An matplotlib axis object. If provided will add plot to that axis
925
+
926
+ Returns:
927
+ Figure object and Axes object
928
+
929
+ Raises:
930
+ ModuleNotFoundError:
931
+ If `matplotlib` is not installed
932
+
933
+ .. plot::
934
+ :scale: 75
935
+
936
+ >>> from torch import rand, randint
937
+ >>> # Example plotting a single value
938
+ >>> from torchmetrics.classification import MultilabelRecall
939
+ >>> metric = MultilabelRecall(num_labels=3)
940
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
941
+ >>> fig_, ax_ = metric.plot()
942
+
943
+ .. plot::
944
+ :scale: 75
945
+
946
+ >>> from torch import rand, randint
947
+ >>> # Example plotting multiple values
948
+ >>> from torchmetrics.classification import MultilabelRecall
949
+ >>> metric = MultilabelRecall(num_labels=3)
950
+ >>> values = [ ]
951
+ >>> for _ in range(10):
952
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
953
+ >>> fig_, ax_ = metric.plot(values)
954
+
955
+ """
956
+ return self._plot(val, ax)
957
+
958
+
959
+ class Precision(_ClassificationTaskWrapper):
960
+ r"""Compute `Precision`_.
961
+
962
+ .. math:: \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}}
963
+
964
+ Where :math:`\text{TP}` and :math:`\text{FP}` represent the number of true positives and false positives
965
+ respectively. The metric is only proper defined when :math:`\text{TP} + \text{FP} \neq 0`. If this case is
966
+ encountered for any class/label, the metric for that class/label will be set to 0 and the overall metric may
967
+ therefore be affected in turn.
968
+
969
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
970
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
971
+ :class:`~torchmetrics.classification.BinaryPrecision`, :class:`~torchmetrics.classification.MulticlassPrecision` and
972
+ :class:`~torchmetrics.classification.MultilabelPrecision` for the specific details of each argument influence and
973
+ examples.
974
+
975
+ Legacy Example:
976
+ >>> from torch import tensor
977
+ >>> preds = tensor([2, 0, 2, 1])
978
+ >>> target = tensor([1, 1, 2, 0])
979
+ >>> precision = Precision(task="multiclass", average='macro', num_classes=3)
980
+ >>> precision(preds, target)
981
+ tensor(0.1667)
982
+ >>> precision = Precision(task="multiclass", average='micro', num_classes=3)
983
+ >>> precision(preds, target)
984
+ tensor(0.2500)
985
+
986
+ """
987
+
988
+ def __new__( # type: ignore[misc]
989
+ cls: type["Precision"],
990
+ task: Literal["binary", "multiclass", "multilabel"],
991
+ threshold: float = 0.5,
992
+ num_classes: Optional[int] = None,
993
+ num_labels: Optional[int] = None,
994
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
995
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
996
+ top_k: Optional[int] = 1,
997
+ ignore_index: Optional[int] = None,
998
+ validate_args: bool = True,
999
+ **kwargs: Any,
1000
+ ) -> Metric:
1001
+ """Initialize task metric."""
1002
+ assert multidim_average is not None # noqa: S101 # needed for mypy
1003
+ kwargs.update({
1004
+ "multidim_average": multidim_average,
1005
+ "ignore_index": ignore_index,
1006
+ "validate_args": validate_args,
1007
+ })
1008
+ task = ClassificationTask.from_str(task)
1009
+ if task == ClassificationTask.BINARY:
1010
+ return BinaryPrecision(threshold, **kwargs)
1011
+ if task == ClassificationTask.MULTICLASS:
1012
+ if not isinstance(num_classes, int):
1013
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
1014
+ if not isinstance(top_k, int):
1015
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
1016
+ return MulticlassPrecision(num_classes, top_k, average, **kwargs)
1017
+ if task == ClassificationTask.MULTILABEL:
1018
+ if not isinstance(num_labels, int):
1019
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
1020
+ return MultilabelPrecision(num_labels, threshold, average, **kwargs)
1021
+ raise ValueError(f"Task {task} not supported!")
1022
+
1023
+
1024
+ class Recall(_ClassificationTaskWrapper):
1025
+ r"""Compute `Recall`_.
1026
+
1027
+ .. math:: \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}}
1028
+
1029
+ Where :math:`\text{TP}` and :math:`\text{FN}` represent the number of true positives and
1030
+ false negatives respectively. The metric is only proper defined when :math:`\text{TP} + \text{FN} \neq 0`. If this
1031
+ case is encountered for any class/label, the metric for that class/label will be set to 0 and the overall metric may
1032
+ therefore be affected in turn.
1033
+
1034
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
1035
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
1036
+ :class:`~torchmetrics.classification.BinaryRecall`,
1037
+ :class:`~torchmetrics.classification.MulticlassRecall` and :class:`~torchmetrics.classification.MultilabelRecall`
1038
+ for the specific details of each argument influence and examples.
1039
+
1040
+ Legacy Example:
1041
+ >>> from torch import tensor
1042
+ >>> preds = tensor([2, 0, 2, 1])
1043
+ >>> target = tensor([1, 1, 2, 0])
1044
+ >>> recall = Recall(task="multiclass", average='macro', num_classes=3)
1045
+ >>> recall(preds, target)
1046
+ tensor(0.3333)
1047
+ >>> recall = Recall(task="multiclass", average='micro', num_classes=3)
1048
+ >>> recall(preds, target)
1049
+ tensor(0.2500)
1050
+
1051
+ """
1052
+
1053
+ def __new__( # type: ignore[misc]
1054
+ cls: type["Recall"],
1055
+ task: Literal["binary", "multiclass", "multilabel"],
1056
+ threshold: float = 0.5,
1057
+ num_classes: Optional[int] = None,
1058
+ num_labels: Optional[int] = None,
1059
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
1060
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
1061
+ top_k: Optional[int] = 1,
1062
+ ignore_index: Optional[int] = None,
1063
+ validate_args: bool = True,
1064
+ **kwargs: Any,
1065
+ ) -> Metric:
1066
+ """Initialize task metric."""
1067
+ task = ClassificationTask.from_str(task)
1068
+ assert multidim_average is not None # noqa: S101 # needed for mypy
1069
+ kwargs.update({
1070
+ "multidim_average": multidim_average,
1071
+ "ignore_index": ignore_index,
1072
+ "validate_args": validate_args,
1073
+ })
1074
+ if task == ClassificationTask.BINARY:
1075
+ return BinaryRecall(threshold, **kwargs)
1076
+ if task == ClassificationTask.MULTICLASS:
1077
+ if not isinstance(num_classes, int):
1078
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
1079
+ if not isinstance(top_k, int):
1080
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
1081
+ return MulticlassRecall(num_classes, top_k, average, **kwargs)
1082
+ if task == ClassificationTask.MULTILABEL:
1083
+ if not isinstance(num_labels, int):
1084
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
1085
+ return MultilabelRecall(num_labels, threshold, average, **kwargs)
1086
+ return None # type: ignore[return-value]
rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall_curve.py ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, List, Optional, Union
15
+
16
+ import torch
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.functional.classification.auroc import _reduce_auroc
22
+ from torchmetrics.functional.classification.precision_recall_curve import (
23
+ _adjust_threshold_arg,
24
+ _binary_precision_recall_curve_arg_validation,
25
+ _binary_precision_recall_curve_compute,
26
+ _binary_precision_recall_curve_format,
27
+ _binary_precision_recall_curve_tensor_validation,
28
+ _binary_precision_recall_curve_update,
29
+ _multiclass_precision_recall_curve_arg_validation,
30
+ _multiclass_precision_recall_curve_compute,
31
+ _multiclass_precision_recall_curve_format,
32
+ _multiclass_precision_recall_curve_tensor_validation,
33
+ _multiclass_precision_recall_curve_update,
34
+ _multilabel_precision_recall_curve_arg_validation,
35
+ _multilabel_precision_recall_curve_compute,
36
+ _multilabel_precision_recall_curve_format,
37
+ _multilabel_precision_recall_curve_tensor_validation,
38
+ _multilabel_precision_recall_curve_update,
39
+ )
40
+ from torchmetrics.metric import Metric
41
+ from torchmetrics.utilities.compute import _auc_compute_without_check
42
+ from torchmetrics.utilities.data import dim_zero_cat
43
+ from torchmetrics.utilities.enums import ClassificationTask
44
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
45
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE, plot_curve
46
+
47
+ if not _MATPLOTLIB_AVAILABLE:
48
+ __doctest_skip__ = [
49
+ "BinaryPrecisionRecallCurve.plot",
50
+ "MulticlassPrecisionRecallCurve.plot",
51
+ "MultilabelPrecisionRecallCurve.plot",
52
+ ]
53
+
54
+
55
+ class BinaryPrecisionRecallCurve(Metric):
56
+ r"""Compute the precision-recall curve for binary tasks.
57
+
58
+ The curve consist of multiple pairs of precision and recall values evaluated at different thresholds, such that the
59
+ tradeoff between the two values can been seen.
60
+
61
+ As input to ``forward`` and ``update`` the metric accepts the following input:
62
+
63
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)``. Preds should be a tensor containing
64
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input
65
+ to be logits and will auto apply sigmoid per element.
66
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
67
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
68
+ 1 always encodes the positive class.
69
+
70
+ .. tip::
71
+ Additional dimension ``...`` will be flattened into the batch dimension.
72
+
73
+ As output to ``forward`` and ``compute`` the metric returns the following output:
74
+
75
+ - ``precision`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d
76
+ tensor of size ``(n_thresholds+1, )`` with precision values (length may differ between classes). If `thresholds`
77
+ is set to something else, then a single 2d tensor of size ``(n_classes, n_thresholds+1)`` with precision values
78
+ is returned.
79
+ - ``recall`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d tensor
80
+ of size ``(n_thresholds+1, )`` with recall values (length may differ between classes). If `thresholds` is set to
81
+ something else, then a single 2d tensor of size ``(n_classes, n_thresholds+1)`` with recall values is returned.
82
+ - ``thresholds`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d
83
+ tensor of size ``(n_thresholds, )`` with increasing threshold values (length may differ between classes). If
84
+ `threshold` is set to something else, then a single 1d tensor of size ``(n_thresholds, )`` is returned with
85
+ shared threshold values for all classes.
86
+
87
+ .. note::
88
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
89
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
90
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
91
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
92
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
93
+
94
+ Args:
95
+ thresholds:
96
+ Can be one of:
97
+
98
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
99
+ all the data. Most accurate but also most memory consuming approach.
100
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
101
+ 0 to 1 as bins for the calculation.
102
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
103
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
104
+ bins for the calculation.
105
+
106
+ ignore_index:
107
+ Specifies a target value that is ignored and does not contribute to the metric calculation
108
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
109
+ Set to ``False`` for faster computations.
110
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
111
+
112
+ Example:
113
+ >>> from torchmetrics.classification import BinaryPrecisionRecallCurve
114
+ >>> preds = torch.tensor([0, 0.5, 0.7, 0.8])
115
+ >>> target = torch.tensor([0, 1, 1, 0])
116
+ >>> bprc = BinaryPrecisionRecallCurve(thresholds=None)
117
+ >>> bprc(preds, target) # doctest: +NORMALIZE_WHITESPACE
118
+ (tensor([0.5000, 0.6667, 0.5000, 0.0000, 1.0000]),
119
+ tensor([1.0000, 1.0000, 0.5000, 0.0000, 0.0000]),
120
+ tensor([0.0000, 0.5000, 0.7000, 0.8000]))
121
+ >>> bprc = BinaryPrecisionRecallCurve(thresholds=5)
122
+ >>> bprc(preds, target) # doctest: +NORMALIZE_WHITESPACE
123
+ (tensor([0.5000, 0.6667, 0.6667, 0.0000, 0.0000, 1.0000]),
124
+ tensor([1., 1., 1., 0., 0., 0.]),
125
+ tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000]))
126
+
127
+ """
128
+
129
+ is_differentiable: bool = False
130
+ higher_is_better: Optional[bool] = None
131
+ full_state_update: bool = False
132
+
133
+ preds: List[Tensor]
134
+ target: List[Tensor]
135
+ confmat: Tensor
136
+
137
+ def __init__(
138
+ self,
139
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
140
+ ignore_index: Optional[int] = None,
141
+ validate_args: bool = True,
142
+ **kwargs: Any,
143
+ ) -> None:
144
+ super().__init__(**kwargs)
145
+ if validate_args:
146
+ _binary_precision_recall_curve_arg_validation(thresholds, ignore_index)
147
+
148
+ self.ignore_index = ignore_index
149
+ self.validate_args = validate_args
150
+
151
+ thresholds = _adjust_threshold_arg(thresholds)
152
+ if thresholds is None:
153
+ self.thresholds = thresholds
154
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
155
+ self.add_state("target", default=[], dist_reduce_fx="cat")
156
+ else:
157
+ self.register_buffer("thresholds", thresholds, persistent=False)
158
+ self.add_state(
159
+ "confmat", default=torch.zeros(len(thresholds), 2, 2, dtype=torch.long), dist_reduce_fx="sum"
160
+ )
161
+
162
+ def update(self, preds: Tensor, target: Tensor) -> None:
163
+ """Update metric states."""
164
+ if self.validate_args:
165
+ _binary_precision_recall_curve_tensor_validation(preds, target, self.ignore_index)
166
+ preds, target, _ = _binary_precision_recall_curve_format(preds, target, self.thresholds, self.ignore_index)
167
+ state = _binary_precision_recall_curve_update(preds, target, self.thresholds)
168
+ if isinstance(state, Tensor):
169
+ self.confmat += state
170
+ else:
171
+ self.preds.append(state[0])
172
+ self.target.append(state[1])
173
+
174
+ def compute(self) -> tuple[Tensor, Tensor, Tensor]:
175
+ """Compute metric."""
176
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
177
+ return _binary_precision_recall_curve_compute(state, self.thresholds)
178
+
179
+ def plot(
180
+ self,
181
+ curve: Optional[tuple[Tensor, Tensor, Tensor]] = None,
182
+ score: Optional[Union[Tensor, bool]] = None,
183
+ ax: Optional[_AX_TYPE] = None,
184
+ ) -> _PLOT_OUT_TYPE:
185
+ """Plot a single curve from the metric.
186
+
187
+ Args:
188
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
189
+ automatically call `metric.compute` and plot that result.
190
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
191
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
192
+ area under the curve.
193
+ ax: An matplotlib axis object. If provided will add plot to that axis
194
+
195
+ Returns:
196
+ Figure and Axes object
197
+
198
+ Raises:
199
+ ModuleNotFoundError:
200
+ If `matplotlib` is not installed
201
+
202
+ .. plot::
203
+ :scale: 75
204
+
205
+ >>> from torch import rand, randint
206
+ >>> from torchmetrics.classification import BinaryPrecisionRecallCurve
207
+ >>> preds = rand(20)
208
+ >>> target = randint(2, (20,))
209
+ >>> metric = BinaryPrecisionRecallCurve()
210
+ >>> metric.update(preds, target)
211
+ >>> fig_, ax_ = metric.plot(score=True)
212
+
213
+ """
214
+ curve_computed = curve or self.compute()
215
+ # switch order as the standard way is recall along x-axis and precision along y-axis
216
+ curve_computed = (curve_computed[1], curve_computed[0], curve_computed[2])
217
+
218
+ score = (
219
+ _auc_compute_without_check(curve_computed[0], curve_computed[1], direction=-1.0)
220
+ if not curve and score is True
221
+ else None
222
+ )
223
+ return plot_curve(
224
+ curve_computed, score=score, ax=ax, label_names=("Recall", "Precision"), name=self.__class__.__name__
225
+ )
226
+
227
+
228
+ class MulticlassPrecisionRecallCurve(Metric):
229
+ r"""Compute the precision-recall curve for multiclass tasks.
230
+
231
+ The curve consist of multiple pairs of precision and recall values evaluated at different thresholds, such that the
232
+ tradeoff between the two values can been seen.
233
+
234
+ For multiclass the metric is calculated by iteratively treating each class as the positive class and all other
235
+ classes as the negative, which is referred to as the one-vs-rest approach. One-vs-one is currently not supported by
236
+ this metric.
237
+
238
+ As input to ``forward`` and ``update`` the metric accepts the following input:
239
+
240
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor containing
241
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input to
242
+ be logits and will auto apply softmax per sample.
243
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
244
+ ground truth labels, and therefore only contain values in the [0, n_classes-1] range (except if `ignore_index`
245
+ is specified).
246
+
247
+ .. tip::
248
+ Additional dimension ``...`` will be flattened into the batch dimension.
249
+
250
+ As output to ``forward`` and ``compute`` the metric returns the following output:
251
+
252
+ - ``precision`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds+1, )`` with precision values
253
+ - ``recall`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds+1, )`` with recall values
254
+ - ``thresholds`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds, )`` with increasing threshold values
255
+
256
+ .. note::
257
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
258
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
259
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
260
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
261
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
262
+
263
+ Args:
264
+ num_classes: Integer specifying the number of classes
265
+ thresholds:
266
+ Can be one of:
267
+
268
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
269
+ all the data. Most accurate but also most memory consuming approach.
270
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
271
+ 0 to 1 as bins for the calculation.
272
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
273
+ - If set to a 1D `tensor` of floats, will use the indicated thresholds in the tensor as
274
+ bins for the calculation.
275
+
276
+ average:
277
+ If aggregation of curves should be applied. By default, the curves are not aggregated and a curve for
278
+ each class is returned. If `average` is set to ``"micro"``, the metric will aggregate the curves by one hot
279
+ encoding the targets and flattening the predictions, considering all classes jointly as a binary problem.
280
+ If `average` is set to ``"macro"``, the metric will aggregate the curves by first interpolating the curves
281
+ from each class at a combined set of thresholds and then average over the classwise interpolated curves.
282
+ See `averaging curve objects`_ for more info on the different averaging methods.
283
+ ignore_index:
284
+ Specifies a target value that is ignored and does not contribute to the metric calculation
285
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
286
+ Set to ``False`` for faster computations.
287
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
288
+
289
+ Example:
290
+ >>> from torchmetrics.classification import MulticlassPrecisionRecallCurve
291
+ >>> preds = torch.tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
292
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
293
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
294
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
295
+ >>> target = torch.tensor([0, 1, 3, 2])
296
+ >>> mcprc = MulticlassPrecisionRecallCurve(num_classes=5, thresholds=None)
297
+ >>> precision, recall, thresholds = mcprc(preds, target)
298
+ >>> precision # doctest: +NORMALIZE_WHITESPACE
299
+ [tensor([0.2500, 1.0000, 1.0000]), tensor([0.2500, 1.0000, 1.0000]), tensor([0.2500, 0.0000, 1.0000]),
300
+ tensor([0.2500, 0.0000, 1.0000]), tensor([0., 1.])]
301
+ >>> recall
302
+ [tensor([1., 1., 0.]), tensor([1., 1., 0.]), tensor([1., 0., 0.]), tensor([1., 0., 0.]), tensor([nan, 0.])]
303
+ >>> thresholds
304
+ [tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]),
305
+ tensor(0.0500)]
306
+ >>> mcprc = MulticlassPrecisionRecallCurve(num_classes=5, thresholds=5)
307
+ >>> mcprc(preds, target) # doctest: +NORMALIZE_WHITESPACE
308
+ (tensor([[0.2500, 1.0000, 1.0000, 1.0000, 0.0000, 1.0000],
309
+ [0.2500, 1.0000, 1.0000, 1.0000, 0.0000, 1.0000],
310
+ [0.2500, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
311
+ [0.2500, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
312
+ [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000]]),
313
+ tensor([[1., 1., 1., 1., 0., 0.],
314
+ [1., 1., 1., 1., 0., 0.],
315
+ [1., 0., 0., 0., 0., 0.],
316
+ [1., 0., 0., 0., 0., 0.],
317
+ [0., 0., 0., 0., 0., 0.]]),
318
+ tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000]))
319
+
320
+ """
321
+
322
+ is_differentiable: bool = False
323
+ higher_is_better: Optional[bool] = None
324
+ full_state_update: bool = False
325
+
326
+ preds: List[Tensor]
327
+ target: List[Tensor]
328
+ confmat: Tensor
329
+
330
+ def __init__(
331
+ self,
332
+ num_classes: int,
333
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
334
+ average: Optional[Literal["micro", "macro"]] = None,
335
+ ignore_index: Optional[int] = None,
336
+ validate_args: bool = True,
337
+ **kwargs: Any,
338
+ ) -> None:
339
+ super().__init__(**kwargs)
340
+ if validate_args:
341
+ _multiclass_precision_recall_curve_arg_validation(num_classes, thresholds, ignore_index, average)
342
+
343
+ self.num_classes = num_classes
344
+ self.average = average
345
+ self.ignore_index = ignore_index
346
+ self.validate_args = validate_args
347
+
348
+ thresholds = _adjust_threshold_arg(thresholds)
349
+ if thresholds is None:
350
+ self.thresholds = thresholds
351
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
352
+ self.add_state("target", default=[], dist_reduce_fx="cat")
353
+ else:
354
+ self.register_buffer("thresholds", thresholds, persistent=False)
355
+ self.add_state(
356
+ "confmat",
357
+ default=torch.zeros(len(thresholds), num_classes, 2, 2, dtype=torch.long),
358
+ dist_reduce_fx="sum",
359
+ )
360
+
361
+ def update(self, preds: Tensor, target: Tensor) -> None:
362
+ """Update metric states."""
363
+ if self.validate_args:
364
+ _multiclass_precision_recall_curve_tensor_validation(preds, target, self.num_classes, self.ignore_index)
365
+ preds, target, _ = _multiclass_precision_recall_curve_format(
366
+ preds, target, self.num_classes, self.thresholds, self.ignore_index, self.average
367
+ )
368
+ state = _multiclass_precision_recall_curve_update(
369
+ preds, target, self.num_classes, self.thresholds, self.average
370
+ )
371
+ if isinstance(state, Tensor):
372
+ self.confmat += state
373
+ else:
374
+ self.preds.append(state[0])
375
+ self.target.append(state[1])
376
+
377
+ def compute(self) -> Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]:
378
+ """Compute metric."""
379
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
380
+ return _multiclass_precision_recall_curve_compute(state, self.num_classes, self.thresholds, self.average)
381
+
382
+ def plot(
383
+ self,
384
+ curve: Optional[Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]] = None,
385
+ score: Optional[Union[Tensor, bool]] = None,
386
+ ax: Optional[_AX_TYPE] = None,
387
+ ) -> _PLOT_OUT_TYPE:
388
+ """Plot a single or multiple values from the metric.
389
+
390
+ Args:
391
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
392
+ automatically call `metric.compute` and plot that result.
393
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
394
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
395
+ area under the curve.
396
+ ax: An matplotlib axis object. If provided will add plot to that axis
397
+
398
+ Returns:
399
+ Figure and Axes object
400
+
401
+ Raises:
402
+ ModuleNotFoundError:
403
+ If `matplotlib` is not installed
404
+
405
+ .. plot::
406
+ :scale: 75
407
+
408
+ >>> from torch import randn, randint
409
+ >>> from torchmetrics.classification import MulticlassPrecisionRecallCurve
410
+ >>> preds = randn(20, 3).softmax(dim=-1)
411
+ >>> target = randint(3, (20,))
412
+ >>> metric = MulticlassPrecisionRecallCurve(num_classes=3)
413
+ >>> metric.update(preds, target)
414
+ >>> fig_, ax_ = metric.plot(score=True)
415
+
416
+ """
417
+ curve_computed = curve or self.compute()
418
+ # switch order as the standard way is recall along x-axis and precision along y-axis
419
+ curve_computed = (curve_computed[1], curve_computed[0], curve_computed[2])
420
+ score = (
421
+ _reduce_auroc(curve_computed[0], curve_computed[1], average=None, direction=-1.0)
422
+ if not curve and score is True
423
+ else None
424
+ )
425
+ return plot_curve(
426
+ curve_computed, score=score, ax=ax, label_names=("Recall", "Precision"), name=self.__class__.__name__
427
+ )
428
+
429
+
430
+ class MultilabelPrecisionRecallCurve(Metric):
431
+ r"""Compute the precision-recall curve for multilabel tasks.
432
+
433
+ The curve consist of multiple pairs of precision and recall values evaluated at different thresholds, such that the
434
+ tradeoff between the two values can been seen.
435
+
436
+ As input to ``forward`` and ``update`` the metric accepts the following input:
437
+
438
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor containing
439
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input to
440
+ be logits and will auto apply sigmoid per element.
441
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``. Target should be a tensor containing
442
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified).
443
+
444
+ .. tip::
445
+ Additional dimension ``...`` will be flattened into the batch dimension.
446
+
447
+ As output to ``forward`` and ``compute`` the metric returns the following a tuple of either 3 tensors or
448
+ 3 lists containing:
449
+
450
+ - ``precision`` (:class:`~torch.Tensor` or :class:`~List`): if `thresholds=None` a list for each label is returned
451
+ with an 1d tensor of size ``(n_thresholds+1, )`` with precision values (length may differ between labels). If
452
+ `thresholds` is set to something else, then a single 2d tensor of size ``(n_labels, n_thresholds+1)`` with
453
+ precision values is returned.
454
+ - ``recall`` (:class:`~torch.Tensor` or :class:`~List`): if `thresholds=None` a list for each label is returned
455
+ with an 1d tensor of size ``(n_thresholds+1, )`` with recall values (length may differ between labels). If
456
+ `thresholds` is set to something else, then a single 2d tensor of size ``(n_labels, n_thresholds+1)`` with recall
457
+ values is returned.
458
+ - ``thresholds`` (:class:`~torch.Tensor` or :class:`~List`): if `thresholds=None` a list for each label is
459
+ returned with an 1d tensor of size ``(n_thresholds, )`` with increasing threshold values (length may differ
460
+ between labels). If `threshold` is set to something else, then a single 1d tensor of size ``(n_thresholds, )``
461
+ is returned with shared threshold values for all labels.
462
+
463
+ .. note::
464
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
465
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
466
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
467
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
468
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
469
+
470
+ Args:
471
+ preds: Tensor with predictions
472
+ target: Tensor with true labels
473
+ num_labels: Integer specifying the number of labels
474
+ thresholds:
475
+ Can be one of:
476
+
477
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
478
+ all the data. Most accurate but also most memory consuming approach.
479
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
480
+ 0 to 1 as bins for the calculation.
481
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
482
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
483
+ bins for the calculation.
484
+
485
+ ignore_index:
486
+ Specifies a target value that is ignored and does not contribute to the metric calculation
487
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
488
+ Set to ``False`` for faster computations.
489
+
490
+ Example:
491
+ >>> from torchmetrics.classification import MultilabelPrecisionRecallCurve
492
+ >>> preds = torch.tensor([[0.75, 0.05, 0.35],
493
+ ... [0.45, 0.75, 0.05],
494
+ ... [0.05, 0.55, 0.75],
495
+ ... [0.05, 0.65, 0.05]])
496
+ >>> target = torch.tensor([[1, 0, 1],
497
+ ... [0, 0, 0],
498
+ ... [0, 1, 1],
499
+ ... [1, 1, 1]])
500
+ >>> mlprc = MultilabelPrecisionRecallCurve(num_labels=3, thresholds=None)
501
+ >>> precision, recall, thresholds = mlprc(preds, target)
502
+ >>> precision # doctest: +NORMALIZE_WHITESPACE
503
+ [tensor([0.5000, 0.5000, 1.0000, 1.0000]), tensor([0.5000, 0.6667, 0.5000, 0.0000, 1.0000]),
504
+ tensor([0.7500, 1.0000, 1.0000, 1.0000])]
505
+ >>> recall # doctest: +NORMALIZE_WHITESPACE
506
+ [tensor([1.0000, 0.5000, 0.5000, 0.0000]), tensor([1.0000, 1.0000, 0.5000, 0.0000, 0.0000]),
507
+ tensor([1.0000, 0.6667, 0.3333, 0.0000])]
508
+ >>> thresholds # doctest: +NORMALIZE_WHITESPACE
509
+ [tensor([0.0500, 0.4500, 0.7500]), tensor([0.0500, 0.5500, 0.6500, 0.7500]), tensor([0.0500, 0.3500, 0.7500])]
510
+ >>> mlprc = MultilabelPrecisionRecallCurve(num_labels=3, thresholds=5)
511
+ >>> mlprc(preds, target) # doctest: +NORMALIZE_WHITESPACE
512
+ (tensor([[0.5000, 0.5000, 1.0000, 1.0000, 0.0000, 1.0000],
513
+ [0.5000, 0.6667, 0.6667, 0.0000, 0.0000, 1.0000],
514
+ [0.7500, 1.0000, 1.0000, 1.0000, 0.0000, 1.0000]]),
515
+ tensor([[1.0000, 0.5000, 0.5000, 0.5000, 0.0000, 0.0000],
516
+ [1.0000, 1.0000, 1.0000, 0.0000, 0.0000, 0.0000],
517
+ [1.0000, 0.6667, 0.3333, 0.3333, 0.0000, 0.0000]]),
518
+ tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000]))
519
+
520
+ """
521
+
522
+ is_differentiable: bool = False
523
+ higher_is_better: Optional[bool] = None
524
+ full_state_update: bool = False
525
+
526
+ preds: List[Tensor]
527
+ target: List[Tensor]
528
+ confmat: Tensor
529
+
530
+ def __init__(
531
+ self,
532
+ num_labels: int,
533
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
534
+ ignore_index: Optional[int] = None,
535
+ validate_args: bool = True,
536
+ **kwargs: Any,
537
+ ) -> None:
538
+ super().__init__(**kwargs)
539
+ if validate_args:
540
+ _multilabel_precision_recall_curve_arg_validation(num_labels, thresholds, ignore_index)
541
+
542
+ self.num_labels = num_labels
543
+ self.ignore_index = ignore_index
544
+ self.validate_args = validate_args
545
+
546
+ thresholds = _adjust_threshold_arg(thresholds)
547
+ if thresholds is None:
548
+ self.thresholds = thresholds
549
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
550
+ self.add_state("target", default=[], dist_reduce_fx="cat")
551
+ else:
552
+ self.register_buffer("thresholds", thresholds, persistent=False)
553
+ self.add_state(
554
+ "confmat",
555
+ default=torch.zeros(len(thresholds), num_labels, 2, 2, dtype=torch.long),
556
+ dist_reduce_fx="sum",
557
+ )
558
+
559
+ def update(self, preds: Tensor, target: Tensor) -> None:
560
+ """Update metric states."""
561
+ if self.validate_args:
562
+ _multilabel_precision_recall_curve_tensor_validation(preds, target, self.num_labels, self.ignore_index)
563
+ preds, target, _ = _multilabel_precision_recall_curve_format(
564
+ preds, target, self.num_labels, self.thresholds, self.ignore_index
565
+ )
566
+ state = _multilabel_precision_recall_curve_update(preds, target, self.num_labels, self.thresholds)
567
+ if isinstance(state, Tensor):
568
+ self.confmat += state
569
+ else:
570
+ self.preds.append(state[0])
571
+ self.target.append(state[1])
572
+
573
+ def compute(self) -> Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]:
574
+ """Compute metric."""
575
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
576
+ return _multilabel_precision_recall_curve_compute(state, self.num_labels, self.thresholds, self.ignore_index)
577
+
578
+ def plot(
579
+ self,
580
+ curve: Optional[Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]] = None,
581
+ score: Optional[Union[Tensor, bool]] = None,
582
+ ax: Optional[_AX_TYPE] = None,
583
+ ) -> _PLOT_OUT_TYPE:
584
+ """Plot a single or multiple values from the metric.
585
+
586
+ Args:
587
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
588
+ automatically call `metric.compute` and plot that result.
589
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
590
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
591
+ area under the curve.
592
+ ax: An matplotlib axis object. If provided will add plot to that axis
593
+
594
+ Returns:
595
+ Figure and Axes object
596
+
597
+ Raises:
598
+ ModuleNotFoundError:
599
+ If `matplotlib` is not installed
600
+
601
+ .. plot::
602
+ :scale: 75
603
+
604
+ >>> from torch import rand, randint
605
+ >>> from torchmetrics.classification import MultilabelPrecisionRecallCurve
606
+ >>> preds = rand(20, 3)
607
+ >>> target = randint(2, (20,3))
608
+ >>> metric = MultilabelPrecisionRecallCurve(num_labels=3)
609
+ >>> metric.update(preds, target)
610
+ >>> fig_, ax_ = metric.plot(score=True)
611
+
612
+ """
613
+ curve_computed = curve or self.compute()
614
+ # switch order as the standard way is recall along x-axis and precision along y-axis
615
+ curve_computed = (curve_computed[1], curve_computed[0], curve_computed[2])
616
+ score = (
617
+ _reduce_auroc(curve_computed[0], curve_computed[1], average=None, direction=-1.0)
618
+ if not curve and score is True
619
+ else None
620
+ )
621
+ return plot_curve(
622
+ curve_computed, score=score, ax=ax, label_names=("Recall", "Precision"), name=self.__class__.__name__
623
+ )
624
+
625
+
626
+ class PrecisionRecallCurve(_ClassificationTaskWrapper):
627
+ r"""Compute the precision-recall curve.
628
+
629
+ The curve consist of multiple pairs of precision and recall values evaluated at different thresholds, such that the
630
+ tradeoff between the two values can been seen.
631
+
632
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
633
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
634
+ :class:`~torchmetrics.classification.BinaryPrecisionRecallCurve`,
635
+ :class:`~torchmetrics.classification.MulticlassPrecisionRecallCurve` and
636
+ :class:`~torchmetrics.classification.MultilabelPrecisionRecallCurve` for the specific details of each argument
637
+ influence and examples.
638
+
639
+ Legacy Example:
640
+ >>> pred = torch.tensor([0, 0.1, 0.8, 0.4])
641
+ >>> target = torch.tensor([0, 1, 1, 0])
642
+ >>> pr_curve = PrecisionRecallCurve(task="binary")
643
+ >>> precision, recall, thresholds = pr_curve(pred, target)
644
+ >>> precision
645
+ tensor([0.5000, 0.6667, 0.5000, 1.0000, 1.0000])
646
+ >>> recall
647
+ tensor([1.0000, 1.0000, 0.5000, 0.5000, 0.0000])
648
+ >>> thresholds
649
+ tensor([0.0000, 0.1000, 0.4000, 0.8000])
650
+
651
+ >>> pred = torch.tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
652
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
653
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
654
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
655
+ >>> target = torch.tensor([0, 1, 3, 2])
656
+ >>> pr_curve = PrecisionRecallCurve(task="multiclass", num_classes=5)
657
+ >>> precision, recall, thresholds = pr_curve(pred, target)
658
+ >>> precision
659
+ [tensor([0.2500, 1.0000, 1.0000]), tensor([0.2500, 1.0000, 1.0000]), tensor([0.2500, 0.0000, 1.0000]),
660
+ tensor([0.2500, 0.0000, 1.0000]), tensor([0., 1.])]
661
+ >>> recall
662
+ [tensor([1., 1., 0.]), tensor([1., 1., 0.]), tensor([1., 0., 0.]), tensor([1., 0., 0.]), tensor([nan, 0.])]
663
+ >>> thresholds
664
+ [tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]), tensor([0.0500, 0.7500]),
665
+ tensor(0.0500)]
666
+
667
+ """
668
+
669
+ def __new__( # type: ignore[misc]
670
+ cls: type["PrecisionRecallCurve"],
671
+ task: Literal["binary", "multiclass", "multilabel"],
672
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
673
+ num_classes: Optional[int] = None,
674
+ num_labels: Optional[int] = None,
675
+ ignore_index: Optional[int] = None,
676
+ validate_args: bool = True,
677
+ **kwargs: Any,
678
+ ) -> Metric:
679
+ """Initialize task metric."""
680
+ task = ClassificationTask.from_str(task)
681
+ kwargs.update({"thresholds": thresholds, "ignore_index": ignore_index, "validate_args": validate_args})
682
+ if task == ClassificationTask.BINARY:
683
+ return BinaryPrecisionRecallCurve(**kwargs)
684
+ if task == ClassificationTask.MULTICLASS:
685
+ if not isinstance(num_classes, int):
686
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
687
+ return MulticlassPrecisionRecallCurve(num_classes, **kwargs)
688
+ if task == ClassificationTask.MULTILABEL:
689
+ if not isinstance(num_labels, int):
690
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
691
+ return MultilabelPrecisionRecallCurve(num_labels, **kwargs)
692
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/ranking.py ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ import torch
18
+ from torch import Tensor
19
+
20
+ from torchmetrics.functional.classification.ranking import (
21
+ _multilabel_confusion_matrix_arg_validation,
22
+ _multilabel_confusion_matrix_format,
23
+ _multilabel_coverage_error_update,
24
+ _multilabel_ranking_average_precision_update,
25
+ _multilabel_ranking_loss_update,
26
+ _multilabel_ranking_tensor_validation,
27
+ _ranking_reduce,
28
+ )
29
+ from torchmetrics.metric import Metric
30
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
31
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
32
+
33
+ if not _MATPLOTLIB_AVAILABLE:
34
+ __doctest_skip__ = [
35
+ "MultilabelCoverageError.plot",
36
+ "MultilabelRankingAveragePrecision.plot",
37
+ "MultilabelRankingLoss.plot",
38
+ ]
39
+
40
+
41
+ class MultilabelCoverageError(Metric):
42
+ """Compute `Multilabel coverage error`_.
43
+
44
+ The score measure how far we need to go through the ranked scores to cover all true labels. The best value is equal
45
+ to the average number of labels in the target tensor per sample.
46
+
47
+ As input to ``forward`` and ``update`` the metric accepts the following input:
48
+
49
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
50
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
51
+ the input to be logits and will auto apply sigmoid per element.
52
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``. Target should be a tensor
53
+ containing ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified).
54
+
55
+ .. tip::
56
+ Additional dimension ``...`` will be flattened into the batch dimension.
57
+
58
+ As output to ``forward`` and ``compute`` the metric returns the following output:
59
+
60
+ - ``mlce`` (:class:`~torch.Tensor`): A tensor containing the multilabel coverage error.
61
+
62
+ Args:
63
+ num_labels: Integer specifying the number of labels
64
+ ignore_index:
65
+ Specifies a target value that is ignored and does not contribute to the metric calculation
66
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
67
+ Set to ``False`` for faster computations.
68
+
69
+ Example:
70
+ >>> from torch import rand, randint
71
+ >>> from torchmetrics.classification import MultilabelCoverageError
72
+ >>> preds = rand(10, 5)
73
+ >>> target = randint(2, (10, 5))
74
+ >>> mlce = MultilabelCoverageError(num_labels=5)
75
+ >>> mlce(preds, target)
76
+ tensor(3.9000)
77
+
78
+ """
79
+
80
+ higher_is_better: bool = False
81
+ is_differentiable: bool = False
82
+ full_state_update: bool = False
83
+ plot_lower_bound: float = 0.0
84
+ plot_upper_bound: float = 1.0
85
+ plot_legend_name: str = "Label"
86
+
87
+ def __init__(
88
+ self,
89
+ num_labels: int,
90
+ ignore_index: Optional[int] = None,
91
+ validate_args: bool = True,
92
+ **kwargs: Any,
93
+ ) -> None:
94
+ super().__init__(**kwargs)
95
+ if validate_args:
96
+ _multilabel_confusion_matrix_arg_validation(num_labels, threshold=0.0, ignore_index=ignore_index)
97
+ self.validate_args = validate_args
98
+ self.num_labels = num_labels
99
+ self.ignore_index = ignore_index
100
+ self.add_state("measure", torch.tensor(0.0), dist_reduce_fx="sum")
101
+ self.add_state("total", torch.tensor(0.0), dist_reduce_fx="sum")
102
+
103
+ def update(self, preds: Tensor, target: Tensor) -> None:
104
+ """Update metric states."""
105
+ if self.validate_args:
106
+ _multilabel_ranking_tensor_validation(preds, target, self.num_labels, self.ignore_index)
107
+ preds, target = _multilabel_confusion_matrix_format(
108
+ preds, target, self.num_labels, threshold=0.0, ignore_index=self.ignore_index, should_threshold=False
109
+ )
110
+ measure, num_elements = _multilabel_coverage_error_update(preds, target)
111
+
112
+ if not isinstance(self.measure, Tensor):
113
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
114
+ if not isinstance(self.total, Tensor):
115
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
116
+
117
+ self.measure += measure
118
+ self.total += num_elements
119
+
120
+ def compute(self) -> Tensor:
121
+ """Compute metric."""
122
+ if not isinstance(self.measure, Tensor):
123
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
124
+ if not isinstance(self.total, Tensor):
125
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
126
+
127
+ return _ranking_reduce(self.measure, int(self.total.item()))
128
+
129
+ def plot(
130
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
131
+ ) -> _PLOT_OUT_TYPE:
132
+ """Plot a single or multiple values from the metric.
133
+
134
+ Args:
135
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
136
+ If no value is provided, will automatically call `metric.compute` and plot that result.
137
+ ax: An matplotlib axis object. If provided will add plot to that axis
138
+
139
+ Returns:
140
+ Figure object and Axes object
141
+
142
+ Raises:
143
+ ModuleNotFoundError:
144
+ If `matplotlib` is not installed
145
+
146
+ .. plot::
147
+ :scale: 75
148
+
149
+ >>> from torch import rand, randint
150
+ >>> # Example plotting a single value
151
+ >>> from torchmetrics.classification import MultilabelCoverageError
152
+ >>> metric = MultilabelCoverageError(num_labels=3)
153
+ >>> metric.update(rand(20, 3), randint(2, (20, 3)))
154
+ >>> fig_, ax_ = metric.plot()
155
+
156
+ .. plot::
157
+ :scale: 75
158
+
159
+ >>> from torch import rand, randint
160
+ >>> # Example plotting multiple values
161
+ >>> from torchmetrics.classification import MultilabelCoverageError
162
+ >>> metric = MultilabelCoverageError(num_labels=3)
163
+ >>> values = [ ]
164
+ >>> for _ in range(10):
165
+ ... values.append(metric(rand(20, 3), randint(2, (20, 3))))
166
+ >>> fig_, ax_ = metric.plot(values)
167
+
168
+ """
169
+ return self._plot(val, ax)
170
+
171
+
172
+ class MultilabelRankingAveragePrecision(Metric):
173
+ """Compute label ranking average precision score for multilabel data [1].
174
+
175
+ The score is the average over each ground truth label assigned to each sample of the ratio of true vs. total labels
176
+ with lower score. Best score is 1.
177
+
178
+ As input to ``forward`` and ``update`` the metric accepts the following input:
179
+
180
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
181
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
182
+ the input to be logits and will auto apply sigmoid per element.
183
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``. Target should be a tensor
184
+ containing ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified).
185
+
186
+ .. tip::
187
+ Additional dimension ``...`` will be flattened into the batch dimension.
188
+
189
+ As output to ``forward`` and ``compute`` the metric returns the following output:
190
+
191
+ - ``mlrap`` (:class:`~torch.Tensor`): A tensor containing the multilabel ranking average precision.
192
+
193
+ Args:
194
+ num_labels: Integer specifying the number of labels
195
+ ignore_index:
196
+ Specifies a target value that is ignored and does not contribute to the metric calculation
197
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
198
+ Set to ``False`` for faster computations.
199
+
200
+ Example:
201
+ >>> from torch import rand, randint
202
+ >>> from torchmetrics.classification import MultilabelRankingAveragePrecision
203
+ >>> preds = rand(10, 5)
204
+ >>> target = randint(2, (10, 5))
205
+ >>> mlrap = MultilabelRankingAveragePrecision(num_labels=5)
206
+ >>> mlrap(preds, target)
207
+ tensor(0.7744)
208
+
209
+ """
210
+
211
+ higher_is_better: bool = True
212
+ is_differentiable: bool = False
213
+ full_state_update: bool = False
214
+ plot_lower_bound: float = 0.0
215
+ plot_upper_bound: float = 1.0
216
+ plot_legend_name: str = "Label"
217
+
218
+ def __init__(
219
+ self,
220
+ num_labels: int,
221
+ ignore_index: Optional[int] = None,
222
+ validate_args: bool = True,
223
+ **kwargs: Any,
224
+ ) -> None:
225
+ super().__init__(**kwargs)
226
+ if validate_args:
227
+ _multilabel_confusion_matrix_arg_validation(num_labels, threshold=0.0, ignore_index=ignore_index)
228
+ self.validate_args = validate_args
229
+ self.num_labels = num_labels
230
+ self.ignore_index = ignore_index
231
+ self.add_state("measure", torch.tensor(0.0), dist_reduce_fx="sum")
232
+ self.add_state("total", torch.tensor(0.0), dist_reduce_fx="sum")
233
+
234
+ def update(self, preds: Tensor, target: Tensor) -> None:
235
+ """Update metric states."""
236
+ if self.validate_args:
237
+ _multilabel_ranking_tensor_validation(preds, target, self.num_labels, self.ignore_index)
238
+ preds, target = _multilabel_confusion_matrix_format(
239
+ preds, target, self.num_labels, threshold=0.0, ignore_index=self.ignore_index, should_threshold=False
240
+ )
241
+ if not isinstance(self.measure, Tensor):
242
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
243
+ if not isinstance(self.total, Tensor):
244
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
245
+
246
+ measure, num_elements = _multilabel_ranking_average_precision_update(preds, target)
247
+ self.measure += measure
248
+ self.total += num_elements
249
+
250
+ def compute(self) -> Tensor:
251
+ """Compute metric."""
252
+ if not isinstance(self.measure, Tensor):
253
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
254
+ if not isinstance(self.total, Tensor):
255
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
256
+
257
+ return _ranking_reduce(self.measure, int(self.total.item()))
258
+
259
+ def plot(
260
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
261
+ ) -> _PLOT_OUT_TYPE:
262
+ """Plot a single or multiple values from the metric.
263
+
264
+ Args:
265
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
266
+ If no value is provided, will automatically call `metric.compute` and plot that result.
267
+ ax: An matplotlib axis object. If provided will add plot to that axis
268
+
269
+ Returns:
270
+ Figure object and Axes object
271
+
272
+ Raises:
273
+ ModuleNotFoundError:
274
+ If `matplotlib` is not installed
275
+
276
+ .. plot::
277
+ :scale: 75
278
+
279
+ >>> from torch import rand, randint
280
+ >>> # Example plotting a single value
281
+ >>> from torchmetrics.classification import MultilabelRankingAveragePrecision
282
+ >>> metric = MultilabelRankingAveragePrecision(num_labels=3)
283
+ >>> metric.update(rand(20, 3), randint(2, (20, 3)))
284
+ >>> fig_, ax_ = metric.plot()
285
+
286
+ .. plot::
287
+ :scale: 75
288
+
289
+ >>> from torch import rand, randint
290
+ >>> # Example plotting multiple values
291
+ >>> from torchmetrics.classification import MultilabelRankingAveragePrecision
292
+ >>> metric = MultilabelRankingAveragePrecision(num_labels=3)
293
+ >>> values = [ ]
294
+ >>> for _ in range(10):
295
+ ... values.append(metric(rand(20, 3), randint(2, (20, 3))))
296
+ >>> fig_, ax_ = metric.plot(values)
297
+
298
+ """
299
+ return self._plot(val, ax)
300
+
301
+
302
+ class MultilabelRankingLoss(Metric):
303
+ """Compute the label ranking loss for multilabel data [1].
304
+
305
+ The score is corresponds to the average number of label pairs that are incorrectly ordered given some predictions
306
+ weighted by the size of the label set and the number of labels not in the label set. The best score is 0.
307
+
308
+ As input to ``forward`` and ``update`` the metric accepts the following input:
309
+
310
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
311
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
312
+ the input to be logits and will auto apply sigmoid per element.
313
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``. Target should be a tensor
314
+ containing ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified).
315
+
316
+ .. tip::
317
+ Additional dimension ``...`` will be flattened into the batch dimension.
318
+
319
+ As output to ``forward`` and ``compute`` the metric returns the following output:
320
+
321
+ - ``mlrl`` (:class:`~torch.Tensor`): A tensor containing the multilabel ranking loss.
322
+
323
+ Args:
324
+ preds: Tensor with predictions
325
+ target: Tensor with true labels
326
+ num_labels: Integer specifying the number of labels
327
+ ignore_index:
328
+ Specifies a target value that is ignored and does not contribute to the metric calculation
329
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
330
+ Set to ``False`` for faster computations.
331
+
332
+ Example:
333
+ >>> from torch import rand, randint
334
+ >>> from torchmetrics.classification import MultilabelRankingLoss
335
+ >>> preds = rand(10, 5)
336
+ >>> target = randint(2, (10, 5))
337
+ >>> mlrl = MultilabelRankingLoss(num_labels=5)
338
+ >>> mlrl(preds, target)
339
+ tensor(0.4167)
340
+
341
+ """
342
+
343
+ higher_is_better: bool = False
344
+ is_differentiable: bool = False
345
+ full_state_update: bool = False
346
+ plot_lower_bound: float = 0.0
347
+ plot_upper_bound: float = 1.0
348
+ plot_legend_name: str = "Label"
349
+
350
+ def __init__(
351
+ self,
352
+ num_labels: int,
353
+ ignore_index: Optional[int] = None,
354
+ validate_args: bool = True,
355
+ **kwargs: Any,
356
+ ) -> None:
357
+ super().__init__(**kwargs)
358
+ if validate_args:
359
+ _multilabel_confusion_matrix_arg_validation(num_labels, threshold=0.0, ignore_index=ignore_index)
360
+ self.validate_args = validate_args
361
+ self.num_labels = num_labels
362
+ self.ignore_index = ignore_index
363
+ self.add_state("measure", torch.tensor(0.0), dist_reduce_fx="sum")
364
+ self.add_state("total", torch.tensor(0.0), dist_reduce_fx="sum")
365
+
366
+ def update(self, preds: Tensor, target: Tensor) -> None:
367
+ """Update metric states."""
368
+ if self.validate_args:
369
+ _multilabel_ranking_tensor_validation(preds, target, self.num_labels, self.ignore_index)
370
+ preds, target = _multilabel_confusion_matrix_format(
371
+ preds, target, self.num_labels, threshold=0.0, ignore_index=self.ignore_index, should_threshold=False
372
+ )
373
+ if not isinstance(self.measure, Tensor):
374
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
375
+ if not isinstance(self.total, Tensor):
376
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
377
+
378
+ measure, num_elements = _multilabel_ranking_loss_update(preds, target)
379
+ self.measure += measure
380
+ self.total += num_elements
381
+
382
+ def compute(self) -> Tensor:
383
+ """Compute metric."""
384
+ if not isinstance(self.measure, Tensor):
385
+ raise TypeError(f"Expected 'self.measure' to be of type Tensor, but got {type(self.measure)}.")
386
+ if not isinstance(self.total, Tensor):
387
+ raise TypeError(f"Expected 'self.total' to be of type Tensor, but got {type(self.total)}.")
388
+
389
+ return _ranking_reduce(self.measure, int(self.total.item()))
390
+
391
+ def plot(
392
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
393
+ ) -> _PLOT_OUT_TYPE:
394
+ """Plot a single or multiple values from the metric.
395
+
396
+ Args:
397
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
398
+ If no value is provided, will automatically call `metric.compute` and plot that result.
399
+ ax: An matplotlib axis object. If provided will add plot to that axis
400
+
401
+ Returns:
402
+ Figure object and Axes object
403
+
404
+ Raises:
405
+ ModuleNotFoundError:
406
+ If `matplotlib` is not installed
407
+
408
+ .. plot::
409
+ :scale: 75
410
+
411
+ >>> from torch import rand, randint
412
+ >>> # Example plotting a single value
413
+ >>> from torchmetrics.classification import MultilabelRankingLoss
414
+ >>> metric = MultilabelRankingLoss(num_labels=3)
415
+ >>> metric.update(rand(20, 3), randint(2, (20, 3)))
416
+ >>> fig_, ax_ = metric.plot()
417
+
418
+ .. plot::
419
+ :scale: 75
420
+
421
+ >>> from torch import rand, randint
422
+ >>> # Example plotting multiple values
423
+ >>> from torchmetrics.classification import MultilabelRankingLoss
424
+ >>> metric = MultilabelRankingLoss(num_labels=3)
425
+ >>> values = [ ]
426
+ >>> for _ in range(10):
427
+ ... values.append(metric(rand(20, 3), randint(2, (20, 3))))
428
+ >>> fig_, ax_ = metric.plot(values)
429
+
430
+ """
431
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/classification/recall_fixed_precision.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.precision_recall_curve import (
22
+ BinaryPrecisionRecallCurve,
23
+ MulticlassPrecisionRecallCurve,
24
+ MultilabelPrecisionRecallCurve,
25
+ )
26
+ from torchmetrics.functional.classification.recall_fixed_precision import (
27
+ _binary_recall_at_fixed_precision_arg_validation,
28
+ _binary_recall_at_fixed_precision_compute,
29
+ _multiclass_recall_at_fixed_precision_arg_compute,
30
+ _multiclass_recall_at_fixed_precision_arg_validation,
31
+ _multilabel_recall_at_fixed_precision_arg_compute,
32
+ _multilabel_recall_at_fixed_precision_arg_validation,
33
+ )
34
+ from torchmetrics.metric import Metric
35
+ from torchmetrics.utilities.data import dim_zero_cat
36
+ from torchmetrics.utilities.enums import ClassificationTask
37
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
38
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
39
+
40
+ if not _MATPLOTLIB_AVAILABLE:
41
+ __doctest_skip__ = [
42
+ "BinaryRecallAtFixedPrecision.plot",
43
+ "MulticlassRecallAtFixedPrecision.plot",
44
+ "MultilabelRecallAtFixedPrecision.plot",
45
+ ]
46
+
47
+
48
+ class BinaryRecallAtFixedPrecision(BinaryPrecisionRecallCurve):
49
+ r"""Compute the highest possible recall value given the minimum precision thresholds provided.
50
+
51
+ This is done by first calculating the precision-recall curve for different thresholds and the find the recall for
52
+ a given precision level.
53
+
54
+ As input to ``forward`` and ``update`` the metric accepts the following input:
55
+
56
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)``. Preds should be a tensor containing
57
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input
58
+ to be logits and will auto apply sigmoid per element.
59
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
60
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
61
+ 1 always encodes the positive class.
62
+
63
+ .. tip::
64
+ Additional dimension ``...`` will be flattened into the batch dimension.
65
+
66
+ As output to ``forward`` and ``compute`` the metric returns the following output:
67
+
68
+ - ``recall`` (:class:`~torch.Tensor`): A scalar tensor with the maximum recall for the given precision level
69
+ - ``threshold`` (:class:`~torch.Tensor`): A scalar tensor with the corresponding threshold level
70
+
71
+ .. note::
72
+ The implementation both supports calculating the metric in a non-binned but accurate version and a
73
+ binned version that is less accurate but more memory efficient. Setting the `thresholds` argument to ``None``
74
+ will activate the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting
75
+ the `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory
76
+ of size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
77
+
78
+ Args:
79
+ min_precision: float value specifying minimum precision threshold.
80
+ thresholds:
81
+ Can be one of:
82
+
83
+ - If set to ``None``, will use a non-binned approach where thresholds are dynamically calculated from
84
+ all the data. Most accurate but also most memory consuming approach.
85
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
86
+ 0 to 1 as bins for the calculation.
87
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
88
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
89
+ bins for the calculation.
90
+
91
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
92
+ Set to ``False`` for faster computations.
93
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
94
+
95
+ Example:
96
+ >>> from torch import tensor
97
+ >>> from torchmetrics.classification import BinaryRecallAtFixedPrecision
98
+ >>> preds = tensor([0, 0.5, 0.7, 0.8])
99
+ >>> target = tensor([0, 1, 1, 0])
100
+ >>> metric = BinaryRecallAtFixedPrecision(min_precision=0.5, thresholds=None)
101
+ >>> metric(preds, target)
102
+ (tensor(1.), tensor(0.5000))
103
+ >>> metric = BinaryRecallAtFixedPrecision(min_precision=0.5, thresholds=5)
104
+ >>> metric(preds, target)
105
+ (tensor(1.), tensor(0.5000))
106
+
107
+ """
108
+
109
+ is_differentiable: bool = False
110
+ higher_is_better: Optional[bool] = None
111
+ full_state_update: bool = False
112
+ plot_lower_bound: float = 0.0
113
+ plot_upper_bound: float = 1.0
114
+
115
+ def __init__(
116
+ self,
117
+ min_precision: float,
118
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
119
+ ignore_index: Optional[int] = None,
120
+ validate_args: bool = True,
121
+ **kwargs: Any,
122
+ ) -> None:
123
+ super().__init__(thresholds, ignore_index, validate_args=False, **kwargs)
124
+ if validate_args:
125
+ _binary_recall_at_fixed_precision_arg_validation(min_precision, thresholds, ignore_index)
126
+ self.validate_args = validate_args
127
+ self.min_precision = min_precision
128
+
129
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
130
+ """Compute metric."""
131
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
132
+ return _binary_recall_at_fixed_precision_compute(state, self.thresholds, self.min_precision)
133
+
134
+ def plot( # type: ignore[override]
135
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
136
+ ) -> _PLOT_OUT_TYPE:
137
+ """Plot a single or multiple values from the metric.
138
+
139
+ Args:
140
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
141
+ If no value is provided, will automatically call `metric.compute` and plot that result.
142
+ ax: An matplotlib axis object. If provided will add plot to that axis
143
+
144
+ Returns:
145
+ Figure object and Axes object
146
+
147
+ Raises:
148
+ ModuleNotFoundError:
149
+ If `matplotlib` is not installed
150
+
151
+ .. plot::
152
+ :scale: 75
153
+
154
+ >>> from torch import rand, randint
155
+ >>> # Example plotting a single value
156
+ >>> from torchmetrics.classification import BinaryRecallAtFixedPrecision
157
+ >>> metric = BinaryRecallAtFixedPrecision(min_precision=0.5)
158
+ >>> metric.update(rand(10), randint(2,(10,)))
159
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
160
+
161
+ .. plot::
162
+ :scale: 75
163
+
164
+ >>> from torch import rand, randint
165
+ >>> # Example plotting multiple values
166
+ >>> from torchmetrics.classification import BinaryRecallAtFixedPrecision
167
+ >>> metric = BinaryRecallAtFixedPrecision(min_precision=0.5)
168
+ >>> values = [ ]
169
+ >>> for _ in range(10):
170
+ ... # we index by 0 such that only the maximum recall value is plotted
171
+ ... values.append(metric(rand(10), randint(2,(10,)))[0])
172
+ >>> fig_, ax_ = metric.plot(values)
173
+
174
+ """
175
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
176
+ return self._plot(val, ax)
177
+
178
+
179
+ class MulticlassRecallAtFixedPrecision(MulticlassPrecisionRecallCurve):
180
+ r"""Compute the highest possible recall value given the minimum precision thresholds provided.
181
+
182
+ This is done by first calculating the precision-recall curve for different thresholds and the find the recall for
183
+ a given precision level.
184
+
185
+ For multiclass the metric is calculated by iteratively treating each class as the positive class and all other
186
+ classes as the negative, which is referred to as the one-vs-rest approach. One-vs-one is currently not supported by
187
+ this metric.
188
+
189
+ As input to ``forward`` and ``update`` the metric accepts the following input:
190
+
191
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
192
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
193
+ the input to be logits and will auto apply softmax per sample.
194
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
195
+ ground truth labels, and therefore only contain values in the [0, n_classes-1] range (except if `ignore_index`
196
+ is specified).
197
+
198
+ .. tip::
199
+ Additional dimension ``...`` will be flattened into the batch dimension.
200
+
201
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 2 tensors or 2 lists containing:
202
+
203
+ - ``recall`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the maximum recall for the
204
+ given precision level per class
205
+ - ``threshold`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the corresponding threshold
206
+ level per class
207
+
208
+ .. note::
209
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
210
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to ``None`` will activate the
211
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
212
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
213
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
214
+
215
+ Args:
216
+ num_classes: Integer specifying the number of classes
217
+ min_precision: float value specifying minimum precision threshold.
218
+ thresholds:
219
+ Can be one of:
220
+
221
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
222
+ all the data. Most accurate but also most memory consuming approach.
223
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
224
+ 0 to 1 as bins for the calculation.
225
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
226
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
227
+ bins for the calculation.
228
+
229
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
230
+ Set to ``False`` for faster computations.
231
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
232
+
233
+ Example:
234
+ >>> from torch import tensor
235
+ >>> from torchmetrics.classification import MulticlassRecallAtFixedPrecision
236
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
237
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
238
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
239
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
240
+ >>> target = tensor([0, 1, 3, 2])
241
+ >>> metric = MulticlassRecallAtFixedPrecision(num_classes=5, min_precision=0.5, thresholds=None)
242
+ >>> metric(preds, target)
243
+ (tensor([1., 1., 0., 0., 0.]), tensor([7.5000e-01, 7.5000e-01, 1.0000e+06, 1.0000e+06, 1.0000e+06]))
244
+ >>> mcrafp = MulticlassRecallAtFixedPrecision(num_classes=5, min_precision=0.5, thresholds=5)
245
+ >>> mcrafp(preds, target)
246
+ (tensor([1., 1., 0., 0., 0.]), tensor([7.5000e-01, 7.5000e-01, 1.0000e+06, 1.0000e+06, 1.0000e+06]))
247
+
248
+ """
249
+
250
+ is_differentiable: bool = False
251
+ higher_is_better: Optional[bool] = None
252
+ full_state_update: bool = False
253
+ plot_lower_bound: float = 0.0
254
+ plot_upper_bound: float = 1.0
255
+ plot_legend_name: str = "Class"
256
+
257
+ def __init__(
258
+ self,
259
+ num_classes: int,
260
+ min_precision: float,
261
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
262
+ ignore_index: Optional[int] = None,
263
+ validate_args: bool = True,
264
+ **kwargs: Any,
265
+ ) -> None:
266
+ super().__init__(
267
+ num_classes=num_classes, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
268
+ )
269
+ if validate_args:
270
+ _multiclass_recall_at_fixed_precision_arg_validation(num_classes, min_precision, thresholds, ignore_index)
271
+ self.validate_args = validate_args
272
+ self.min_precision = min_precision
273
+
274
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
275
+ """Compute metric."""
276
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
277
+ return _multiclass_recall_at_fixed_precision_arg_compute(
278
+ state, self.num_classes, self.thresholds, self.min_precision
279
+ )
280
+
281
+ def plot( # type: ignore[override]
282
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
283
+ ) -> _PLOT_OUT_TYPE:
284
+ """Plot a single or multiple values from the metric.
285
+
286
+ Args:
287
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
288
+ If no value is provided, will automatically call `metric.compute` and plot that result.
289
+ ax: An matplotlib axis object. If provided will add plot to that axis
290
+
291
+ Returns:
292
+ Figure object and Axes object
293
+
294
+ Raises:
295
+ ModuleNotFoundError:
296
+ If `matplotlib` is not installed
297
+
298
+ .. plot::
299
+ :scale: 75
300
+
301
+ >>> from torch import rand, randint
302
+ >>> # Example plotting a single value per class
303
+ >>> from torchmetrics.classification import MulticlassRecallAtFixedPrecision
304
+ >>> metric = MulticlassRecallAtFixedPrecision(num_classes=3, min_precision=0.5)
305
+ >>> metric.update(rand(20, 3).softmax(dim=-1), randint(3, (20,)))
306
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
307
+
308
+ .. plot::
309
+ :scale: 75
310
+
311
+ >>> from torch import rand, randint
312
+ >>> # Example plotting a multiple values per class
313
+ >>> from torchmetrics.classification import MulticlassRecallAtFixedPrecision
314
+ >>> metric = MulticlassRecallAtFixedPrecision(num_classes=3, min_precision=0.5)
315
+ >>> values = []
316
+ >>> for _ in range(20):
317
+ ... # we index by 0 such that only the maximum recall value is plotted
318
+ ... values.append(metric(rand(20, 3).softmax(dim=-1), randint(3, (20,)))[0])
319
+ >>> fig_, ax_ = metric.plot(values)
320
+
321
+ """
322
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
323
+ return self._plot(val, ax)
324
+
325
+
326
+ class MultilabelRecallAtFixedPrecision(MultilabelPrecisionRecallCurve):
327
+ r"""Compute the highest possible recall value given the minimum precision thresholds provided.
328
+
329
+ This is done by first calculating the precision-recall curve for different thresholds and the find the recall for
330
+ a given precision level.
331
+
332
+ As input to ``forward`` and ``update`` the metric accepts the following input:
333
+
334
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
335
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
336
+ the input to be logits and will auto apply sigmoid per element.
337
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
338
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
339
+ 1 always encodes the positive class.
340
+
341
+ .. tip::
342
+ Additional dimension ``...`` will be flattened into the batch dimension.
343
+
344
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 2 tensors or 2 lists containing:
345
+
346
+ - ``recall`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the maximum recall for the
347
+ given precision level per class
348
+ - ``threshold`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_classes, )`` with the corresponding threshold
349
+ level per class
350
+
351
+ .. note::
352
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
353
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to ```None``` will activate
354
+ the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the
355
+ `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
356
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
357
+
358
+ Args:
359
+ num_labels: Integer specifying the number of labels
360
+ min_precision: float value specifying minimum precision threshold.
361
+ thresholds:
362
+ Can be one of:
363
+
364
+ - If set to ``None``, will use a non-binned approach where thresholds are dynamically calculated from
365
+ all the data. Most accurate but also most memory consuming approach.
366
+ - If set to an ``int`` (larger than 1), will use that number of thresholds linearly spaced from
367
+ 0 to 1 as bins for the calculation.
368
+ - If set to an ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
369
+ - If set to an 1d :class:`~torch.Tensor` of floats, will use the indicated thresholds in the tensor as
370
+ bins for the calculation.
371
+
372
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
373
+ Set to ``False`` for faster computations.
374
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
375
+
376
+ Example:
377
+ >>> from torch import tensor
378
+ >>> from torchmetrics.classification import MultilabelRecallAtFixedPrecision
379
+ >>> preds = tensor([[0.75, 0.05, 0.35],
380
+ ... [0.45, 0.75, 0.05],
381
+ ... [0.05, 0.55, 0.75],
382
+ ... [0.05, 0.65, 0.05]])
383
+ >>> target = tensor([[1, 0, 1],
384
+ ... [0, 0, 0],
385
+ ... [0, 1, 1],
386
+ ... [1, 1, 1]])
387
+ >>> metric = MultilabelRecallAtFixedPrecision(num_labels=3, min_precision=0.5, thresholds=None)
388
+ >>> metric(preds, target)
389
+ (tensor([1., 1., 1.]), tensor([0.0500, 0.5500, 0.0500]))
390
+ >>> mlrafp = MultilabelRecallAtFixedPrecision(num_labels=3, min_precision=0.5, thresholds=5)
391
+ >>> mlrafp(preds, target)
392
+ (tensor([1., 1., 1.]), tensor([0.0000, 0.5000, 0.0000]))
393
+
394
+ """
395
+
396
+ is_differentiable: bool = False
397
+ higher_is_better: Optional[bool] = None
398
+ full_state_update: bool = False
399
+ plot_lower_bound: float = 0.0
400
+ plot_upper_bound: float = 1.0
401
+ plot_legend_name: str = "Label"
402
+
403
+ def __init__(
404
+ self,
405
+ num_labels: int,
406
+ min_precision: float,
407
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
408
+ ignore_index: Optional[int] = None,
409
+ validate_args: bool = True,
410
+ **kwargs: Any,
411
+ ) -> None:
412
+ super().__init__(
413
+ num_labels=num_labels, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
414
+ )
415
+ if validate_args:
416
+ _multilabel_recall_at_fixed_precision_arg_validation(num_labels, min_precision, thresholds, ignore_index)
417
+ self.validate_args = validate_args
418
+ self.min_precision = min_precision
419
+
420
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
421
+ """Compute metric."""
422
+ state = (dim_zero_cat(self.preds), dim_zero_cat(self.target)) if self.thresholds is None else self.confmat
423
+ return _multilabel_recall_at_fixed_precision_arg_compute(
424
+ state, self.num_labels, self.thresholds, self.ignore_index, self.min_precision
425
+ )
426
+
427
+ def plot( # type: ignore[override]
428
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
429
+ ) -> _PLOT_OUT_TYPE:
430
+ """Plot a single or multiple values from the metric.
431
+
432
+ Args:
433
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
434
+ If no value is provided, will automatically call `metric.compute` and plot that result.
435
+ ax: An matplotlib axis object. If provided will add plot to that axis
436
+
437
+ Returns:
438
+ Figure object and Axes object
439
+
440
+ Raises:
441
+ ModuleNotFoundError:
442
+ If `matplotlib` is not installed
443
+
444
+ .. plot::
445
+ :scale: 75
446
+
447
+ >>> from torch import rand, randint
448
+ >>> # Example plotting a single value
449
+ >>> from torchmetrics.classification import MultilabelRecallAtFixedPrecision
450
+ >>> metric = MultilabelRecallAtFixedPrecision(num_labels=3, min_precision=0.5)
451
+ >>> metric.update(rand(20, 3), randint(2, (20, 3)))
452
+ >>> fig_, ax_ = metric.plot() # the returned plot only shows the maximum recall value by default
453
+
454
+ .. plot::
455
+ :scale: 75
456
+
457
+ >>> from torch import rand, randint
458
+ >>> # Example plotting multiple values
459
+ >>> from torchmetrics.classification import MultilabelRecallAtFixedPrecision
460
+ >>> metric = MultilabelRecallAtFixedPrecision(num_labels=3, min_precision=0.5)
461
+ >>> values = [ ]
462
+ >>> for _ in range(10):
463
+ ... # we index by 0 such that only the maximum recall value is plotted
464
+ ... values.append(metric(rand(20, 3), randint(2, (20, 3)))[0])
465
+ >>> fig_, ax_ = metric.plot(values)
466
+
467
+ """
468
+ val = val or self.compute()[0] # by default we select the maximum recall value to plot
469
+ return self._plot(val, ax)
470
+
471
+
472
+ class RecallAtFixedPrecision(_ClassificationTaskWrapper):
473
+ r"""Compute the highest possible recall value given the minimum precision thresholds provided.
474
+
475
+ This is done by first calculating the precision-recall curve for different thresholds and the find the recall for
476
+ a given precision level.
477
+
478
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
479
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
480
+ :class:`~torchmetrics.classification.BinaryRecallAtFixedPrecision`,
481
+ :class:`~torchmetrics.classification.MulticlassRecallAtFixedPrecision` and
482
+ :class:`~torchmetrics.classification.MultilabelRecallAtFixedPrecision` for the specific details of each argument
483
+ influence and examples.
484
+
485
+ """
486
+
487
+ def __new__( # type: ignore[misc]
488
+ cls: type["RecallAtFixedPrecision"],
489
+ task: Literal["binary", "multiclass", "multilabel"],
490
+ min_precision: float,
491
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
492
+ num_classes: Optional[int] = None,
493
+ num_labels: Optional[int] = None,
494
+ ignore_index: Optional[int] = None,
495
+ validate_args: bool = True,
496
+ **kwargs: Any,
497
+ ) -> Metric:
498
+ """Initialize task metric."""
499
+ task = ClassificationTask.from_str(task)
500
+ if task == ClassificationTask.BINARY:
501
+ return BinaryRecallAtFixedPrecision(min_precision, thresholds, ignore_index, validate_args, **kwargs)
502
+ if task == ClassificationTask.MULTICLASS:
503
+ if not isinstance(num_classes, int):
504
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
505
+ return MulticlassRecallAtFixedPrecision(
506
+ num_classes, min_precision, thresholds, ignore_index, validate_args, **kwargs
507
+ )
508
+ if task == ClassificationTask.MULTILABEL:
509
+ if not isinstance(num_labels, int):
510
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
511
+ return MultilabelRecallAtFixedPrecision(
512
+ num_labels, min_precision, thresholds, ignore_index, validate_args, **kwargs
513
+ )
514
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/roc.py ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, List, Optional, Union
15
+
16
+ from torch import Tensor
17
+ from typing_extensions import Literal
18
+
19
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
20
+ from torchmetrics.classification.precision_recall_curve import (
21
+ BinaryPrecisionRecallCurve,
22
+ MulticlassPrecisionRecallCurve,
23
+ MultilabelPrecisionRecallCurve,
24
+ )
25
+ from torchmetrics.functional.classification.auroc import _reduce_auroc
26
+ from torchmetrics.functional.classification.roc import (
27
+ _binary_roc_compute,
28
+ _multiclass_roc_compute,
29
+ _multilabel_roc_compute,
30
+ )
31
+ from torchmetrics.metric import Metric
32
+ from torchmetrics.utilities.compute import _auc_compute_without_check
33
+ from torchmetrics.utilities.data import dim_zero_cat
34
+ from torchmetrics.utilities.enums import ClassificationTask
35
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
36
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE, plot_curve
37
+
38
+ if not _MATPLOTLIB_AVAILABLE:
39
+ __doctest_skip__ = ["BinaryROC.plot", "MulticlassROC.plot", "MultilabelROC.plot"]
40
+
41
+
42
+ class BinaryROC(BinaryPrecisionRecallCurve):
43
+ r"""Compute the Receiver Operating Characteristic (ROC) for binary tasks.
44
+
45
+ The curve consist of multiple pairs of true positive rate (TPR) and false positive rate (FPR) values evaluated at
46
+ different thresholds, such that the tradeoff between the two values can be seen.
47
+
48
+ As input to ``forward`` and ``update`` the metric accepts the following input:
49
+
50
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, ...)``. Preds should be a tensor containing
51
+ probabilities or logits for each observation. If preds has values outside [0,1] range we consider the input
52
+ to be logits and will auto apply sigmoid per element.
53
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
54
+ ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified). The value
55
+ 1 always encodes the positive class.
56
+
57
+ .. tip::
58
+ Additional dimension ``...`` will be flattened into the batch dimension.
59
+
60
+ As output to ``forward`` and ``compute`` the metric returns a tuple of 3 tensors containing:
61
+
62
+ - ``fpr`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds+1, )`` with false positive rate values
63
+ - ``tpr`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds+1, )`` with true positive rate values
64
+ - ``thresholds`` (:class:`~torch.Tensor`): A 1d tensor of size ``(n_thresholds, )`` with decreasing threshold
65
+ values
66
+
67
+ .. note::
68
+ The implementation both supports calculating the metric in a non-binned but accurate version and a
69
+ binned version that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will
70
+ activate the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the
71
+ `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
72
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
73
+
74
+ .. attention::
75
+ The outputted thresholds will be in reversed order to ensure that they correspond to both fpr and
76
+ tpr which are sorted in reversed order during their calculation, such that they are monotome increasing.
77
+
78
+ Args:
79
+ thresholds:
80
+ Can be one of:
81
+
82
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
83
+ all the data. Most accurate but also most memory consuming approach.
84
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
85
+ 0 to 1 as bins for the calculation.
86
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
87
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
88
+ bins for the calculation.
89
+
90
+ ignore_index:
91
+ Specifies a target value that is ignored and does not contribute to the metric calculation
92
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
93
+ Set to ``False`` for faster computations.
94
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
95
+
96
+ Example:
97
+ >>> from torch import tensor
98
+ >>> from torchmetrics.classification import BinaryROC
99
+ >>> preds = tensor([0, 0.5, 0.7, 0.8])
100
+ >>> target = tensor([0, 1, 1, 0])
101
+ >>> metric = BinaryROC(thresholds=None)
102
+ >>> metric(preds, target) # doctest: +NORMALIZE_WHITESPACE
103
+ (tensor([0.0000, 0.5000, 0.5000, 0.5000, 1.0000]),
104
+ tensor([0.0000, 0.0000, 0.5000, 1.0000, 1.0000]),
105
+ tensor([1.0000, 0.8000, 0.7000, 0.5000, 0.0000]))
106
+ >>> broc = BinaryROC(thresholds=5)
107
+ >>> broc(preds, target) # doctest: +NORMALIZE_WHITESPACE
108
+ (tensor([0.0000, 0.5000, 0.5000, 0.5000, 1.0000]),
109
+ tensor([0., 0., 1., 1., 1.]),
110
+ tensor([1.0000, 0.7500, 0.5000, 0.2500, 0.0000]))
111
+
112
+ """
113
+
114
+ is_differentiable: bool = False
115
+ higher_is_better: Optional[bool] = None
116
+ full_state_update: bool = False
117
+ plot_lower_bound: float = 0.0
118
+ plot_upper_bound: float = 1.0
119
+
120
+ def compute(self) -> tuple[Tensor, Tensor, Tensor]:
121
+ """Compute metric."""
122
+ state = [dim_zero_cat(self.preds), dim_zero_cat(self.target)] if self.thresholds is None else self.confmat
123
+ return _binary_roc_compute(state, self.thresholds) # type: ignore[arg-type]
124
+
125
+ def plot(
126
+ self,
127
+ curve: Optional[tuple[Tensor, Tensor, Tensor]] = None,
128
+ score: Optional[Union[Tensor, bool]] = None,
129
+ ax: Optional[_AX_TYPE] = None,
130
+ ) -> _PLOT_OUT_TYPE:
131
+ """Plot a single or multiple values from the metric.
132
+
133
+ Args:
134
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
135
+ automatically call `metric.compute` and plot that result.
136
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
137
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
138
+ area under the curve.
139
+ ax: An matplotlib axis object. If provided will add plot to that axis
140
+
141
+ Returns:
142
+ Figure and Axes object
143
+
144
+ Raises:
145
+ ModuleNotFoundError:
146
+ If `matplotlib` is not installed
147
+
148
+ .. plot::
149
+ :scale: 75
150
+
151
+ >>> from torch import rand, randint
152
+ >>> from torchmetrics.classification import BinaryROC
153
+ >>> preds = rand(20)
154
+ >>> target = randint(2, (20,))
155
+ >>> metric = BinaryROC()
156
+ >>> metric.update(preds, target)
157
+ >>> fig_, ax_ = metric.plot(score=True)
158
+
159
+ """
160
+ curve_computed = curve or self.compute()
161
+ score = (
162
+ _auc_compute_without_check(curve_computed[0], curve_computed[1], 1.0)
163
+ if not curve and score is True
164
+ else None
165
+ )
166
+ return plot_curve(
167
+ curve_computed,
168
+ score=score,
169
+ ax=ax,
170
+ label_names=("False positive rate", "True positive rate"),
171
+ name=self.__class__.__name__,
172
+ )
173
+
174
+
175
+ class MulticlassROC(MulticlassPrecisionRecallCurve):
176
+ r"""Compute the Receiver Operating Characteristic (ROC) for binary tasks.
177
+
178
+ The curve consist of multiple pairs of true positive rate (TPR) and false positive rate (FPR) values evaluated at
179
+ different thresholds, such that the tradeoff between the two values can be seen.
180
+
181
+ For multiclass the metric is calculated by iteratively treating each class as the positive class and all other
182
+ classes as the negative, which is referred to as the one-vs-rest approach. One-vs-one is currently not supported by
183
+ this metric.
184
+
185
+ As input to ``forward`` and ``update`` the metric accepts the following input:
186
+
187
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
188
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
189
+ the input to be logits and will auto apply softmax per sample.
190
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``. Target should be a tensor containing
191
+ ground truth labels, and therefore only contain values in the [0, n_classes-1] range (except if `ignore_index`
192
+ is specified).
193
+
194
+ .. tip::
195
+ Additional dimension ``...`` will be flattened into the batch dimension.
196
+
197
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 3 tensors or 3 lists containing
198
+
199
+ - ``fpr`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d tensor of
200
+ size ``(n_thresholds+1, )`` with false positive rate values (length may differ between classes). If `thresholds`
201
+ is set to something else, then a single 2d tensor of size ``(n_classes, n_thresholds+1)`` with false positive rate
202
+ values is returned.
203
+ - ``tpr`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d tensor of
204
+ size ``(n_thresholds+1, )`` with true positive rate values (length may differ between classes). If `thresholds` is
205
+ set to something else, then a single 2d tensor of size ``(n_classes, n_thresholds+1)`` with true positive rate
206
+ values is returned.
207
+ - ``thresholds`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each class is returned with an 1d
208
+ tensor of size ``(n_thresholds, )`` with decreasing threshold values (length may differ between classes). If
209
+ `threshold` is set to something else, then a single 1d tensor of size ``(n_thresholds, )`` is returned with shared
210
+ threshold values for all classes.
211
+
212
+ .. note::
213
+ The implementation both supports calculating the metric in a non-binned but accurate version and a
214
+ binned version that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will
215
+ activate the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the
216
+ `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
217
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
218
+
219
+ .. attention::
220
+ Note that outputted thresholds will be in reversed order to ensure that they correspond to both fpr
221
+ and tpr which are sorted in reversed order during their calculation, such that they are monotome increasing.
222
+
223
+ Args:
224
+ num_classes: Integer specifying the number of classes
225
+ thresholds:
226
+ Can be one of:
227
+
228
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
229
+ all the data. Most accurate but also most memory consuming approach.
230
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
231
+ 0 to 1 as bins for the calculation.
232
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
233
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
234
+ bins for the calculation.
235
+
236
+ average:
237
+ If aggregation of curves should be applied. By default, the curves are not aggregated and a curve for
238
+ each class is returned. If `average` is set to ``"micro"``, the metric will aggregate the curves by one hot
239
+ encoding the targets and flattening the predictions, considering all classes jointly as a binary problem.
240
+ If `average` is set to ``"macro"``, the metric will aggregate the curves by first interpolating the curves
241
+ from each class at a combined set of thresholds and then average over the classwise interpolated curves.
242
+ See `averaging curve objects`_ for more info on the different averaging methods.
243
+ ignore_index:
244
+ Specifies a target value that is ignored and does not contribute to the metric calculation
245
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
246
+ Set to ``False`` for faster computations.
247
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
248
+
249
+ Example:
250
+ >>> from torch import tensor
251
+ >>> from torchmetrics.classification import MulticlassROC
252
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
253
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
254
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
255
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
256
+ >>> target = tensor([0, 1, 3, 2])
257
+ >>> metric = MulticlassROC(num_classes=5, thresholds=None)
258
+ >>> fpr, tpr, thresholds = metric(preds, target)
259
+ >>> fpr # doctest: +NORMALIZE_WHITESPACE
260
+ [tensor([0., 0., 1.]), tensor([0., 0., 1.]), tensor([0.0000, 0.3333, 1.0000]),
261
+ tensor([0.0000, 0.3333, 1.0000]), tensor([0., 1.])]
262
+ >>> tpr
263
+ [tensor([0., 1., 1.]), tensor([0., 1., 1.]), tensor([0., 0., 1.]), tensor([0., 0., 1.]), tensor([0., 0.])]
264
+ >>> thresholds # doctest: +NORMALIZE_WHITESPACE
265
+ [tensor([1.0000, 0.7500, 0.0500]), tensor([1.0000, 0.7500, 0.0500]),
266
+ tensor([1.0000, 0.7500, 0.0500]), tensor([1.0000, 0.7500, 0.0500]), tensor([1.0000, 0.0500])]
267
+ >>> mcroc = MulticlassROC(num_classes=5, thresholds=5)
268
+ >>> mcroc(preds, target) # doctest: +NORMALIZE_WHITESPACE
269
+ (tensor([[0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
270
+ [0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
271
+ [0.0000, 0.3333, 0.3333, 0.3333, 1.0000],
272
+ [0.0000, 0.3333, 0.3333, 0.3333, 1.0000],
273
+ [0.0000, 0.0000, 0.0000, 0.0000, 1.0000]]),
274
+ tensor([[0., 1., 1., 1., 1.],
275
+ [0., 1., 1., 1., 1.],
276
+ [0., 0., 0., 0., 1.],
277
+ [0., 0., 0., 0., 1.],
278
+ [0., 0., 0., 0., 0.]]),
279
+ tensor([1.0000, 0.7500, 0.5000, 0.2500, 0.0000]))
280
+
281
+ """
282
+
283
+ is_differentiable: bool = False
284
+ higher_is_better: Optional[bool] = None
285
+ full_state_update: bool = False
286
+ plot_lower_bound: float = 0.0
287
+ plot_upper_bound: float = 1.0
288
+ plot_legend_name: str = "Class"
289
+
290
+ def compute(self) -> Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]:
291
+ """Compute metric."""
292
+ state = [dim_zero_cat(self.preds), dim_zero_cat(self.target)] if self.thresholds is None else self.confmat
293
+ return _multiclass_roc_compute(state, self.num_classes, self.thresholds, self.average) # type: ignore[arg-type]
294
+
295
+ def plot(
296
+ self,
297
+ curve: Optional[Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]] = None,
298
+ score: Optional[Union[Tensor, bool]] = None,
299
+ ax: Optional[_AX_TYPE] = None,
300
+ labels: Optional[list[str]] = None,
301
+ ) -> _PLOT_OUT_TYPE:
302
+ """Plot a single or multiple values from the metric.
303
+
304
+ Args:
305
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
306
+ automatically call `metric.compute` and plot that result.
307
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
308
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
309
+ area under the curve.
310
+ ax: An matplotlib axis object. If provided will add plot to that axis
311
+ labels: a list of strings, if provided will be added to the plot to indicate the different classes
312
+
313
+ Returns:
314
+ Figure and Axes object
315
+
316
+ Raises:
317
+ ModuleNotFoundError:
318
+ If `matplotlib` is not installed
319
+
320
+ .. plot::
321
+ :scale: 75
322
+
323
+ >>> from torch import randn, randint
324
+ >>> from torchmetrics.classification import MulticlassROC
325
+ >>> preds = randn(20, 3).softmax(dim=-1)
326
+ >>> target = randint(3, (20,))
327
+ >>> metric = MulticlassROC(num_classes=3)
328
+ >>> metric.update(preds, target)
329
+ >>> fig_, ax_ = metric.plot(score=True)
330
+
331
+ """
332
+ curve_computed = curve or self.compute()
333
+ score = (
334
+ _reduce_auroc(curve_computed[0], curve_computed[1], average=None) if not curve and score is True else None
335
+ )
336
+ return plot_curve(
337
+ curve_computed,
338
+ score=score,
339
+ ax=ax,
340
+ label_names=("False positive rate", "True positive rate"),
341
+ name=self.__class__.__name__,
342
+ labels=labels,
343
+ )
344
+
345
+
346
+ class MultilabelROC(MultilabelPrecisionRecallCurve):
347
+ r"""Compute the Receiver Operating Characteristic (ROC) for binary tasks.
348
+
349
+ The curve consist of multiple pairs of true positive rate (TPR) and false positive rate (FPR) values evaluated at
350
+ different thresholds, such that the tradeoff between the two values can be seen.
351
+
352
+ As input to ``forward`` and ``update`` the metric accepts the following input:
353
+
354
+ - ``preds`` (:class:`~torch.Tensor`): A float tensor of shape ``(N, C, ...)``. Preds should be a tensor
355
+ containing probabilities or logits for each observation. If preds has values outside [0,1] range we consider
356
+ the input to be logits and will auto apply sigmoid per element.
357
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``. Target should be a tensor
358
+ containing ground truth labels, and therefore only contain {0,1} values (except if `ignore_index` is specified).
359
+
360
+ .. tip::
361
+ Additional dimension ``...`` will be flattened into the batch dimension.
362
+
363
+ As output to ``forward`` and ``compute`` the metric returns a tuple of either 3 tensors or 3 lists containing
364
+
365
+ - ``fpr`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each label is returned with an 1d tensor of
366
+ size ``(n_thresholds+1, )`` with false positive rate values (length may differ between labels). If `thresholds` is
367
+ set to something else, then a single 2d tensor of size ``(n_labels, n_thresholds+1)`` with false positive rate
368
+ values is returned.
369
+ - ``tpr`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each label is returned with an 1d tensor of
370
+ size ``(n_thresholds+1, )`` with true positive rate values (length may differ between labels). If `thresholds` is
371
+ set to something else, then a single 2d tensor of size ``(n_labels, n_thresholds+1)`` with true positive rate
372
+ values is returned.
373
+ - ``thresholds`` (:class:`~torch.Tensor`): if `thresholds=None` a list for each label is returned with an 1d
374
+ tensor of size ``(n_thresholds, )`` with decreasing threshold values (length may differ between labels). If
375
+ `threshold` is set to something else, then a single 1d tensor of size ``(n_thresholds, )`` is returned with shared
376
+ threshold values for all labels.
377
+
378
+ .. note::
379
+ The implementation both supports calculating the metric in a non-binned but accurate version and a
380
+ binned version that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will
381
+ activate the non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the
382
+ `thresholds` argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
383
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
384
+
385
+ .. attention::
386
+ The outputted thresholds will be in reversed order to ensure that they correspond to both fpr and tpr
387
+ which are sorted in reversed order during their calculation, such that they are monotome increasing.
388
+
389
+ Args:
390
+ num_labels: Integer specifying the number of labels
391
+ thresholds:
392
+ Can be one of:
393
+
394
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
395
+ all the data. Most accurate but also most memory consuming approach.
396
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
397
+ 0 to 1 as bins for the calculation.
398
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
399
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
400
+ bins for the calculation.
401
+
402
+ ignore_index:
403
+ Specifies a target value that is ignored and does not contribute to the metric calculation
404
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
405
+ Set to ``False`` for faster computations.
406
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
407
+
408
+ Example:
409
+ >>> from torch import tensor
410
+ >>> from torchmetrics.classification import MultilabelROC
411
+ >>> preds = tensor([[0.75, 0.05, 0.35],
412
+ ... [0.45, 0.75, 0.05],
413
+ ... [0.05, 0.55, 0.75],
414
+ ... [0.05, 0.65, 0.05]])
415
+ >>> target = tensor([[1, 0, 1],
416
+ ... [0, 0, 0],
417
+ ... [0, 1, 1],
418
+ ... [1, 1, 1]])
419
+ >>> metric = MultilabelROC(num_labels=3, thresholds=None)
420
+ >>> fpr, tpr, thresholds = metric(preds, target)
421
+ >>> fpr # doctest: +NORMALIZE_WHITESPACE
422
+ [tensor([0.0000, 0.0000, 0.5000, 1.0000]),
423
+ tensor([0.0000, 0.5000, 0.5000, 0.5000, 1.0000]),
424
+ tensor([0., 0., 0., 1.])]
425
+ >>> tpr # doctest: +NORMALIZE_WHITESPACE
426
+ [tensor([0.0000, 0.5000, 0.5000, 1.0000]),
427
+ tensor([0.0000, 0.0000, 0.5000, 1.0000, 1.0000]),
428
+ tensor([0.0000, 0.3333, 0.6667, 1.0000])]
429
+ >>> thresholds # doctest: +NORMALIZE_WHITESPACE
430
+ [tensor([1.0000, 0.7500, 0.4500, 0.0500]),
431
+ tensor([1.0000, 0.7500, 0.6500, 0.5500, 0.0500]),
432
+ tensor([1.0000, 0.7500, 0.3500, 0.0500])]
433
+ >>> mlroc = MultilabelROC(num_labels=3, thresholds=5)
434
+ >>> mlroc(preds, target) # doctest: +NORMALIZE_WHITESPACE
435
+ (tensor([[0.0000, 0.0000, 0.0000, 0.5000, 1.0000],
436
+ [0.0000, 0.5000, 0.5000, 0.5000, 1.0000],
437
+ [0.0000, 0.0000, 0.0000, 0.0000, 1.0000]]),
438
+ tensor([[0.0000, 0.5000, 0.5000, 0.5000, 1.0000],
439
+ [0.0000, 0.0000, 1.0000, 1.0000, 1.0000],
440
+ [0.0000, 0.3333, 0.3333, 0.6667, 1.0000]]),
441
+ tensor([1.0000, 0.7500, 0.5000, 0.2500, 0.0000]))
442
+
443
+ """
444
+
445
+ is_differentiable: bool = False
446
+ higher_is_better: Optional[bool] = None
447
+ full_state_update: bool = False
448
+ plot_lower_bound: float = 0.0
449
+ plot_upper_bound: float = 1.0
450
+ plot_legend_name: str = "Label"
451
+
452
+ def compute(self) -> Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]:
453
+ """Compute metric."""
454
+ state = [dim_zero_cat(self.preds), dim_zero_cat(self.target)] if self.thresholds is None else self.confmat
455
+ return _multilabel_roc_compute(state, self.num_labels, self.thresholds, self.ignore_index) # type: ignore[arg-type]
456
+
457
+ def plot(
458
+ self,
459
+ curve: Optional[Union[tuple[Tensor, Tensor, Tensor], tuple[List[Tensor], List[Tensor], List[Tensor]]]] = None,
460
+ score: Optional[Union[Tensor, bool]] = None,
461
+ ax: Optional[_AX_TYPE] = None,
462
+ labels: Optional[list[str]] = None,
463
+ ) -> _PLOT_OUT_TYPE:
464
+ """Plot a single or multiple values from the metric.
465
+
466
+ Args:
467
+ curve: the output of either `metric.compute` or `metric.forward`. If no value is provided, will
468
+ automatically call `metric.compute` and plot that result.
469
+ score: Provide a area-under-the-curve score to be displayed on the plot. If `True` and no curve is provided,
470
+ will automatically compute the score. The score is computed by using the trapezoidal rule to compute the
471
+ area under the curve.
472
+ ax: An matplotlib axis object. If provided will add plot to that axis
473
+ labels: a list of strings, if provided will be added to the plot to indicate the different classes
474
+
475
+ Returns:
476
+ Figure and Axes object
477
+
478
+ Raises:
479
+ ModuleNotFoundError:
480
+ If `matplotlib` is not installed
481
+
482
+ .. plot::
483
+ :scale: 75
484
+
485
+ >>> from torch import rand, randint
486
+ >>> from torchmetrics.classification import MultilabelROC
487
+ >>> preds = rand(20, 3)
488
+ >>> target = randint(2, (20,3))
489
+ >>> metric = MultilabelROC(num_labels=3)
490
+ >>> metric.update(preds, target)
491
+ >>> fig_, ax_ = metric.plot(score=True)
492
+
493
+ """
494
+ curve_computed = curve or self.compute()
495
+ score = (
496
+ _reduce_auroc(curve_computed[0], curve_computed[1], average=None) if not curve and score is True else None
497
+ )
498
+ return plot_curve(
499
+ curve_computed,
500
+ score=score,
501
+ ax=ax,
502
+ label_names=("False positive rate", "True positive rate"),
503
+ name=self.__class__.__name__,
504
+ labels=labels,
505
+ )
506
+
507
+
508
+ class ROC(_ClassificationTaskWrapper):
509
+ r"""Compute the Receiver Operating Characteristic (ROC).
510
+
511
+ The curve consist of multiple pairs of true positive rate (TPR) and false positive rate (FPR) values evaluated at
512
+ different thresholds, such that the tradeoff between the two values can be seen.
513
+
514
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
515
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
516
+ :class:`~torchmetrics.classification.BinaryROC`,
517
+ :class:`~torchmetrics.classification.MulticlassROC` and
518
+ :class:`~torchmetrics.classification.MultilabelROC` for the specific details of each argument
519
+ influence and examples.
520
+
521
+ Legacy Example:
522
+ >>> from torch import tensor
523
+ >>> pred = tensor([0.0, 1.0, 2.0, 3.0])
524
+ >>> target = tensor([0, 1, 1, 1])
525
+ >>> roc = ROC(task="binary")
526
+ >>> fpr, tpr, thresholds = roc(pred, target)
527
+ >>> fpr
528
+ tensor([0., 0., 0., 0., 1.])
529
+ >>> tpr
530
+ tensor([0.0000, 0.3333, 0.6667, 1.0000, 1.0000])
531
+ >>> thresholds
532
+ tensor([1.0000, 0.9526, 0.8808, 0.7311, 0.5000])
533
+
534
+ >>> pred = tensor([[0.75, 0.05, 0.05, 0.05],
535
+ ... [0.05, 0.75, 0.05, 0.05],
536
+ ... [0.05, 0.05, 0.75, 0.05],
537
+ ... [0.05, 0.05, 0.05, 0.75]])
538
+ >>> target = tensor([0, 1, 3, 2])
539
+ >>> roc = ROC(task="multiclass", num_classes=4)
540
+ >>> fpr, tpr, thresholds = roc(pred, target)
541
+ >>> fpr
542
+ [tensor([0., 0., 1.]), tensor([0., 0., 1.]), tensor([0.0000, 0.3333, 1.0000]), tensor([0.0000, 0.3333, 1.0000])]
543
+ >>> tpr
544
+ [tensor([0., 1., 1.]), tensor([0., 1., 1.]), tensor([0., 0., 1.]), tensor([0., 0., 1.])]
545
+ >>> thresholds # doctest: +NORMALIZE_WHITESPACE
546
+ [tensor([1.0000, 0.7500, 0.0500]),
547
+ tensor([1.0000, 0.7500, 0.0500]),
548
+ tensor([1.0000, 0.7500, 0.0500]),
549
+ tensor([1.0000, 0.7500, 0.0500])]
550
+
551
+ >>> pred = tensor([[0.8191, 0.3680, 0.1138],
552
+ ... [0.3584, 0.7576, 0.1183],
553
+ ... [0.2286, 0.3468, 0.1338],
554
+ ... [0.8603, 0.0745, 0.1837]])
555
+ >>> target = tensor([[1, 1, 0], [0, 1, 0], [0, 0, 0], [0, 1, 1]])
556
+ >>> roc = ROC(task='multilabel', num_labels=3)
557
+ >>> fpr, tpr, thresholds = roc(pred, target)
558
+ >>> fpr
559
+ [tensor([0.0000, 0.3333, 0.3333, 0.6667, 1.0000]),
560
+ tensor([0., 0., 0., 1., 1.]),
561
+ tensor([0.0000, 0.0000, 0.3333, 0.6667, 1.0000])]
562
+ >>> tpr
563
+ [tensor([0., 0., 1., 1., 1.]),
564
+ tensor([0.0000, 0.3333, 0.6667, 0.6667, 1.0000]),
565
+ tensor([0., 1., 1., 1., 1.])]
566
+ >>> thresholds
567
+ [tensor([1.0000, 0.8603, 0.8191, 0.3584, 0.2286]),
568
+ tensor([1.0000, 0.7576, 0.3680, 0.3468, 0.0745]),
569
+ tensor([1.0000, 0.1837, 0.1338, 0.1183, 0.1138])]
570
+
571
+ """
572
+
573
+ def __new__( # type: ignore[misc]
574
+ cls: type["ROC"],
575
+ task: Literal["binary", "multiclass", "multilabel"],
576
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
577
+ num_classes: Optional[int] = None,
578
+ num_labels: Optional[int] = None,
579
+ ignore_index: Optional[int] = None,
580
+ validate_args: bool = True,
581
+ **kwargs: Any,
582
+ ) -> Metric:
583
+ """Initialize task metric."""
584
+ task = ClassificationTask.from_str(task)
585
+ kwargs.update({"thresholds": thresholds, "ignore_index": ignore_index, "validate_args": validate_args})
586
+ if task == ClassificationTask.BINARY:
587
+ return BinaryROC(**kwargs)
588
+ if task == ClassificationTask.MULTICLASS:
589
+ if not isinstance(num_classes, int):
590
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
591
+ return MulticlassROC(num_classes, **kwargs)
592
+ if task == ClassificationTask.MULTILABEL:
593
+ if not isinstance(num_labels, int):
594
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
595
+ return MultilabelROC(num_labels, **kwargs)
596
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/sensitivity_specificity.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, Optional, Union
15
+
16
+ from torch import Tensor
17
+ from typing_extensions import Literal
18
+
19
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
20
+ from torchmetrics.classification.precision_recall_curve import (
21
+ BinaryPrecisionRecallCurve,
22
+ MulticlassPrecisionRecallCurve,
23
+ MultilabelPrecisionRecallCurve,
24
+ )
25
+ from torchmetrics.functional.classification.sensitivity_specificity import (
26
+ _binary_sensitivity_at_specificity_arg_validation,
27
+ _binary_sensitivity_at_specificity_compute,
28
+ _multiclass_sensitivity_at_specificity_arg_validation,
29
+ _multiclass_sensitivity_at_specificity_compute,
30
+ _multilabel_sensitivity_at_specificity_arg_validation,
31
+ _multilabel_sensitivity_at_specificity_compute,
32
+ )
33
+ from torchmetrics.metric import Metric
34
+ from torchmetrics.utilities.data import dim_zero_cat as _cat
35
+ from torchmetrics.utilities.enums import ClassificationTask
36
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
37
+
38
+ if not _MATPLOTLIB_AVAILABLE:
39
+ __doctest_skip__ = [
40
+ "BinarySensitivityAtSpecificity.plot",
41
+ "MulticlassSensitivityAtSpecificity.plot",
42
+ "MultilabelSensitivityAtSpecificity.plot",
43
+ ]
44
+
45
+
46
+ class BinarySensitivityAtSpecificity(BinaryPrecisionRecallCurve):
47
+ r"""Compute the highest possible sensitivity value given the minimum specificity thresholds provided.
48
+
49
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
50
+ find the sensitivity for a given specificity level.
51
+
52
+ Accepts the following input tensors:
53
+
54
+ - ``preds`` (float tensor): ``(N, ...)``. Preds should be a tensor containing probabilities or logits for each
55
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
56
+ sigmoid per element.
57
+ - ``target`` (int tensor): ``(N, ...)``. Target should be a tensor containing ground truth labels, and therefore
58
+ only contain {0,1} values (except if `ignore_index` is specified).
59
+
60
+ Additional dimension ``...`` will be flattened into the batch dimension.
61
+
62
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
63
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
64
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
65
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
66
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
67
+
68
+ Args:
69
+ min_specificity: float value specifying minimum specificity threshold.
70
+ thresholds:
71
+ Can be one of:
72
+
73
+ - ``None``, will use a non-binned approach where thresholds are dynamically calculated from
74
+ all the data. It is the most accurate but also the most memory-consuming approach.
75
+ - ``int`` (larger than 1), will use that number of thresholds linearly spaced from
76
+ 0 to 1 as bins for the calculation.
77
+ - ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
78
+ - 1d ``tensor`` of floats, will use the indicated thresholds in the tensor as
79
+ bins for the calculation.
80
+
81
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
82
+ Set to ``False`` for faster computations.
83
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
84
+
85
+ Returns:
86
+ (tuple): a tuple of 2 tensors containing:
87
+
88
+ - sensitivity: an scalar tensor with the maximum sensitivity for the given specificity level
89
+ - threshold: an scalar tensor with the corresponding threshold level
90
+
91
+ Example:
92
+ >>> from torchmetrics.classification import BinarySensitivityAtSpecificity
93
+ >>> from torch import tensor
94
+ >>> preds = tensor([0, 0.5, 0.4, 0.1])
95
+ >>> target = tensor([0, 1, 1, 1])
96
+ >>> metric = BinarySensitivityAtSpecificity(min_specificity=0.5, thresholds=None)
97
+ >>> metric(preds, target)
98
+ (tensor(1.), tensor(0.1000))
99
+ >>> metric = BinarySensitivityAtSpecificity(min_specificity=0.5, thresholds=5)
100
+ >>> metric(preds, target)
101
+ (tensor(0.6667), tensor(0.2500))
102
+
103
+ """
104
+
105
+ is_differentiable: bool = False
106
+ higher_is_better: Optional[bool] = None
107
+ full_state_update: bool = False
108
+ plot_lower_bound: float = 0.0
109
+ plot_upper_bound: float = 1.0
110
+
111
+ def __init__(
112
+ self,
113
+ min_specificity: float,
114
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
115
+ ignore_index: Optional[int] = None,
116
+ validate_args: bool = True,
117
+ **kwargs: Any,
118
+ ) -> None:
119
+ super().__init__(thresholds, ignore_index, validate_args=False, **kwargs)
120
+ if validate_args:
121
+ _binary_sensitivity_at_specificity_arg_validation(min_specificity, thresholds, ignore_index)
122
+ self.validate_args = validate_args
123
+ self.min_specificity = min_specificity
124
+
125
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
126
+ """Compute metric."""
127
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
128
+ return _binary_sensitivity_at_specificity_compute(state, self.thresholds, self.min_specificity)
129
+
130
+
131
+ class MulticlassSensitivityAtSpecificity(MulticlassPrecisionRecallCurve):
132
+ r"""Compute the highest possible sensitivity value given the minimum specificity thresholds provided.
133
+
134
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
135
+ find the sensitivity for a given specificity level.
136
+
137
+ For multiclass the metric is calculated by iteratively treating each class as the positive class and all other
138
+ classes as the negative, which is referred to as the one-vs-rest approach. One-vs-one is currently not supported by
139
+ this metric.
140
+
141
+ Accepts the following input tensors:
142
+
143
+ - ``preds`` (float tensor): ``(N, C, ...)``. Preds should be a tensor containing probabilities or logits for each
144
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
145
+ softmax per sample.
146
+ - ``target`` (int tensor): ``(N, ...)``. Target should be a tensor containing ground truth labels, and therefore
147
+ only contain values in the [0, n_classes-1] range (except if `ignore_index` is specified).
148
+
149
+ Additional dimension ``...`` will be flattened into the batch dimension.
150
+
151
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
152
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
153
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
154
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
155
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
156
+
157
+ Args:
158
+ num_classes: Integer specifying the number of classes
159
+ min_specificity: float value specifying minimum specificity threshold.
160
+ thresholds:
161
+ Can be one of:
162
+
163
+ - ``None``, will use a non-binned approach where thresholds are dynamically calculated from
164
+ all the data. It is the most accurate but also the most memory-consuming approach.
165
+ - ``int`` (larger than 1), will use that number of thresholds linearly spaced from
166
+ 0 to 1 as bins for the calculation.
167
+ - ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
168
+ - 1d ``tensor`` of floats, will use the indicated thresholds in the tensor as
169
+ bins for the calculation.
170
+
171
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
172
+ Set to ``False`` for faster computations.
173
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
174
+
175
+ Returns:
176
+ (tuple): a tuple of either 2 tensors or 2 lists containing
177
+
178
+ - sensitivity: an 1d tensor of size (n_classes, ) with the maximum sensitivity for the given
179
+ specificity level per class
180
+ - thresholds: an 1d tensor of size (n_classes, ) with the corresponding threshold level per class
181
+
182
+
183
+ Example:
184
+ >>> from torchmetrics.classification import MulticlassSensitivityAtSpecificity
185
+ >>> from torch import tensor
186
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
187
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
188
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
189
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
190
+ >>> target = tensor([0, 1, 3, 2])
191
+ >>> metric = MulticlassSensitivityAtSpecificity(num_classes=5, min_specificity=0.5, thresholds=None)
192
+ >>> metric(preds, target)
193
+ (tensor([1., 1., 0., 0., 0.]), tensor([0.7500, 0.7500, 1.0000, 1.0000, 1.0000]))
194
+ >>> metric = MulticlassSensitivityAtSpecificity(num_classes=5, min_specificity=0.5, thresholds=5)
195
+ >>> metric(preds, target)
196
+ (tensor([1., 1., 0., 0., 0.]), tensor([0.7500, 0.7500, 1.0000, 1.0000, 1.0000]))
197
+
198
+ """
199
+
200
+ is_differentiable: bool = False
201
+ higher_is_better: Optional[bool] = None
202
+ full_state_update: bool = False
203
+ plot_lower_bound: float = 0.0
204
+ plot_upper_bound: float = 1.0
205
+ plot_legend_name: str = "Class"
206
+
207
+ def __init__(
208
+ self,
209
+ num_classes: int,
210
+ min_specificity: float,
211
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
212
+ ignore_index: Optional[int] = None,
213
+ validate_args: bool = True,
214
+ **kwargs: Any,
215
+ ) -> None:
216
+ super().__init__(
217
+ num_classes=num_classes, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
218
+ )
219
+ if validate_args:
220
+ _multiclass_sensitivity_at_specificity_arg_validation(
221
+ num_classes, min_specificity, thresholds, ignore_index
222
+ )
223
+ self.validate_args = validate_args
224
+ self.min_specificity = min_specificity
225
+
226
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
227
+ """Compute metric."""
228
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
229
+ return _multiclass_sensitivity_at_specificity_compute(
230
+ state, self.num_classes, self.thresholds, self.min_specificity
231
+ )
232
+
233
+
234
+ class MultilabelSensitivityAtSpecificity(MultilabelPrecisionRecallCurve):
235
+ r"""Compute the highest possible sensitivity value given the minimum specificity thresholds provided.
236
+
237
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
238
+ find the sensitivity for a given specificity level.
239
+
240
+ Accepts the following input tensors:
241
+
242
+ - ``preds`` (float tensor): ``(N, C, ...)``. Preds should be a tensor containing probabilities or logits for each
243
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
244
+ sigmoid per element.
245
+ - ``target`` (int tensor): ``(N, C, ...)``. Target should be a tensor containing ground truth labels, and therefore
246
+ only contain {0,1} values (except if `ignore_index` is specified).
247
+
248
+ Additional dimension ``...`` will be flattened into the batch dimension.
249
+
250
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
251
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
252
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
253
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
254
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
255
+
256
+ Args:
257
+ num_labels: Integer specifying the number of labels
258
+ min_specificity: float value specifying minimum specificity threshold.
259
+ thresholds:
260
+ Can be one of:
261
+
262
+ - ``None``, will use a non-binned approach where thresholds are dynamically calculated from
263
+ all the data. It is the most accurate but also the most memory-consuming approach.
264
+ - ``int`` (larger than 1), will use that number of thresholds linearly spaced from
265
+ 0 to 1 as bins for the calculation.
266
+ - ``list`` of floats, will use the indicated thresholds in the list as bins for the calculation
267
+ - 1d ``tensor`` of floats, will use the indicated thresholds in the tensor as
268
+ bins for the calculation.
269
+
270
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
271
+ Set to ``False`` for faster computations.
272
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
273
+
274
+ Returns:
275
+ (tuple): a tuple of either 2 tensors or 2 lists containing
276
+
277
+ - sensitivity: an 1d tensor of size ``(n_classes, )`` with the maximum sensitivity for the given
278
+ specificity level per class
279
+ - thresholds: an 1d tensor of size ``(n_classes, )`` with the corresponding threshold level per class
280
+
281
+ Example:
282
+ >>> from torchmetrics.classification import MultilabelSensitivityAtSpecificity
283
+ >>> from torch import tensor
284
+ >>> preds = tensor([[0.75, 0.05, 0.35],
285
+ ... [0.45, 0.75, 0.05],
286
+ ... [0.05, 0.55, 0.75],
287
+ ... [0.05, 0.65, 0.05]])
288
+ >>> target = tensor([[1, 0, 1],
289
+ ... [0, 0, 0],
290
+ ... [0, 1, 1],
291
+ ... [1, 1, 1]])
292
+ >>> metric = MultilabelSensitivityAtSpecificity(num_labels=3, min_specificity=0.5, thresholds=None)
293
+ >>> metric(preds, target)
294
+ (tensor([0.5000, 1.0000, 0.6667]), tensor([0.7500, 0.5500, 0.3500]))
295
+ >>> metric = MultilabelSensitivityAtSpecificity(num_labels=3, min_specificity=0.5, thresholds=5)
296
+ >>> metric(preds, target)
297
+ (tensor([0.5000, 1.0000, 0.6667]), tensor([0.7500, 0.5000, 0.2500]))
298
+
299
+ """
300
+
301
+ is_differentiable: bool = False
302
+ higher_is_better: Optional[bool] = None
303
+ full_state_update: bool = False
304
+ plot_lower_bound: float = 0.0
305
+ plot_upper_bound: float = 1.0
306
+ plot_legend_name: str = "Label"
307
+
308
+ def __init__(
309
+ self,
310
+ num_labels: int,
311
+ min_specificity: float,
312
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
313
+ ignore_index: Optional[int] = None,
314
+ validate_args: bool = True,
315
+ **kwargs: Any,
316
+ ) -> None:
317
+ super().__init__(
318
+ num_labels=num_labels, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
319
+ )
320
+ if validate_args:
321
+ _multilabel_sensitivity_at_specificity_arg_validation(num_labels, min_specificity, thresholds, ignore_index)
322
+ self.validate_args = validate_args
323
+ self.min_specificity = min_specificity
324
+
325
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
326
+ """Compute metric."""
327
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
328
+ return _multilabel_sensitivity_at_specificity_compute(
329
+ state, self.num_labels, self.thresholds, self.ignore_index, self.min_specificity
330
+ )
331
+
332
+
333
+ class SensitivityAtSpecificity(_ClassificationTaskWrapper):
334
+ r"""Compute the highest possible sensitivity value given the minimum specificity thresholds provided.
335
+
336
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
337
+ find the sensitivity for a given specificity level.
338
+
339
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
340
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
341
+ :class:`~torchmetrics.classification.BinarySensitivityAtSpecificity`,
342
+ :class:`~torchmetrics.classification.MulticlassSensitivityAtSpecificity` and
343
+ :class:`~torchmetrics.classification.MultilabelSensitivityAtSpecificity` for the specific details of each argument
344
+ influence and examples.
345
+
346
+ """
347
+
348
+ def __new__( # type: ignore[misc]
349
+ cls: type["SensitivityAtSpecificity"],
350
+ task: Literal["binary", "multiclass", "multilabel"],
351
+ min_specificity: float,
352
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
353
+ num_classes: Optional[int] = None,
354
+ num_labels: Optional[int] = None,
355
+ ignore_index: Optional[int] = None,
356
+ validate_args: bool = True,
357
+ **kwargs: Any,
358
+ ) -> Metric:
359
+ """Initialize task metric."""
360
+ task = ClassificationTask.from_str(task)
361
+ if task == ClassificationTask.BINARY:
362
+ return BinarySensitivityAtSpecificity(min_specificity, thresholds, ignore_index, validate_args, **kwargs)
363
+ if task == ClassificationTask.MULTICLASS:
364
+ if not isinstance(num_classes, int):
365
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
366
+ return MulticlassSensitivityAtSpecificity(
367
+ num_classes, min_specificity, thresholds, ignore_index, validate_args, **kwargs
368
+ )
369
+ if task == ClassificationTask.MULTILABEL:
370
+ if not isinstance(num_labels, int):
371
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
372
+ return MultilabelSensitivityAtSpecificity(
373
+ num_labels, min_specificity, thresholds, ignore_index, validate_args, **kwargs
374
+ )
375
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity.py ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.classification.stat_scores import BinaryStatScores, MulticlassStatScores, MultilabelStatScores
22
+ from torchmetrics.functional.classification.specificity import _specificity_reduce
23
+ from torchmetrics.metric import Metric
24
+ from torchmetrics.utilities.enums import ClassificationTask
25
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
26
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
27
+
28
+ if not _MATPLOTLIB_AVAILABLE:
29
+ __doctest_skip__ = ["BinarySpecificity.plot", "MulticlassSpecificity.plot", "MultilabelSpecificity.plot"]
30
+
31
+
32
+ class BinarySpecificity(BinaryStatScores):
33
+ r"""Compute `Specificity`_ for binary tasks.
34
+
35
+ .. math:: \text{Specificity} = \frac{\text{TN}}{\text{TN} + \text{FP}}
36
+
37
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
38
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
39
+ encountered a score of 0 is returned.
40
+
41
+ As input to ``forward`` and ``update`` the metric accepts the following input:
42
+
43
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, ...)``. If preds is a floating point
44
+ tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid per
45
+ element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
46
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
47
+
48
+ As output to ``forward`` and ``compute`` the metric returns the following output:
49
+
50
+ - ``bs`` (:class:`~torch.Tensor`): If ``multidim_average`` is set to ``global``, the metric returns a scalar value.
51
+ If ``multidim_average`` is set to ``samplewise``, the metric returns ``(N,)`` vector consisting of a scalar value
52
+ per sample.
53
+
54
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
55
+ which the reduction will then be applied over instead of the sample dimension ``N``.
56
+
57
+ Args:
58
+ threshold: Threshold for transforming probability to binary {0,1} predictions
59
+ multidim_average:
60
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
61
+
62
+ - ``global``: Additional dimensions are flatted along the batch dimension
63
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
64
+ The statistics in this case are calculated over the additional dimensions.
65
+
66
+ ignore_index:
67
+ Specifies a target value that is ignored and does not contribute to the metric calculation
68
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
69
+ Set to ``False`` for faster computations.
70
+
71
+ Example (preds is int tensor):
72
+ >>> from torch import tensor
73
+ >>> from torchmetrics.classification import BinarySpecificity
74
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
75
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
76
+ >>> metric = BinarySpecificity()
77
+ >>> metric(preds, target)
78
+ tensor(0.6667)
79
+
80
+ Example (preds is float tensor):
81
+ >>> from torchmetrics.classification import BinarySpecificity
82
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
83
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
84
+ >>> metric = BinarySpecificity()
85
+ >>> metric(preds, target)
86
+ tensor(0.6667)
87
+
88
+ Example (multidim tensors):
89
+ >>> from torchmetrics.classification import BinarySpecificity
90
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
91
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
92
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
93
+ >>> metric = BinarySpecificity(multidim_average='samplewise')
94
+ >>> metric(preds, target)
95
+ tensor([0.0000, 0.3333])
96
+
97
+ """
98
+
99
+ plot_lower_bound: float = 0.0
100
+ plot_upper_bound: float = 1.0
101
+
102
+ def compute(self) -> Tensor:
103
+ """Compute metric."""
104
+ tp, fp, tn, fn = self._final_state()
105
+ return _specificity_reduce(tp, fp, tn, fn, average="binary", multidim_average=self.multidim_average)
106
+
107
+ def plot(
108
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
109
+ ) -> _PLOT_OUT_TYPE:
110
+ """Plot a single or multiple values from the metric.
111
+
112
+ Args:
113
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
114
+ If no value is provided, will automatically call `metric.compute` and plot that result.
115
+ ax: An matplotlib axis object. If provided will add plot to that axis
116
+
117
+ Returns:
118
+ Figure object and Axes object
119
+
120
+ Raises:
121
+ ModuleNotFoundError:
122
+ If `matplotlib` is not installed
123
+
124
+ .. plot::
125
+ :scale: 75
126
+
127
+ >>> from torch import rand, randint
128
+ >>> # Example plotting a single value
129
+ >>> from torchmetrics.classification import BinarySpecificity
130
+ >>> metric = BinarySpecificity()
131
+ >>> metric.update(rand(10), randint(2,(10,)))
132
+ >>> fig_, ax_ = metric.plot()
133
+
134
+ .. plot::
135
+ :scale: 75
136
+
137
+ >>> from torch import rand, randint
138
+ >>> # Example plotting multiple values
139
+ >>> from torchmetrics.classification import BinarySpecificity
140
+ >>> metric = BinarySpecificity()
141
+ >>> values = [ ]
142
+ >>> for _ in range(10):
143
+ ... values.append(metric(rand(10), randint(2,(10,))))
144
+ >>> fig_, ax_ = metric.plot(values)
145
+
146
+ """
147
+ return self._plot(val, ax)
148
+
149
+
150
+ class MulticlassSpecificity(MulticlassStatScores):
151
+ r"""Compute `Specificity`_ for multiclass tasks.
152
+
153
+ .. math:: \text{Specificity} = \frac{\text{TN}}{\text{TN} + \text{FP}}
154
+
155
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
156
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
157
+ encountered for any class, the metric for that class will be set to 0 and the overall metric may therefore be
158
+ affected in turn.
159
+
160
+ As input to ``forward`` and ``update`` the metric accepts the following input:
161
+
162
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
163
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
164
+ probabilities/logits into an int tensor.
165
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
166
+
167
+ As output to ``forward`` and ``compute`` the metric returns the following output:
168
+
169
+ - ``mcs`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
170
+ arguments:
171
+
172
+ - If ``multidim_average`` is set to ``global``:
173
+
174
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
175
+ - If ``average=None/'none'``, the shape will be ``(C,)``
176
+
177
+ - If ``multidim_average`` is set to ``samplewise``:
178
+
179
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
180
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
181
+
182
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
183
+ which the reduction will then be applied over instead of the sample dimension ``N``.
184
+
185
+ Args:
186
+ num_classes: Integer specifying the number of classes
187
+ average:
188
+ Defines the reduction that is applied over labels. Should be one of the following:
189
+
190
+ - ``micro``: Sum statistics over all labels
191
+ - ``macro``: Calculate statistics for each label and average them
192
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
193
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
194
+
195
+ top_k:
196
+ Number of highest probability or logit score predictions considered to find the correct label.
197
+ Only works when ``preds`` contain probabilities/logits.
198
+ multidim_average:
199
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
200
+
201
+ - ``global``: Additional dimensions are flatted along the batch dimension
202
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
203
+ The statistics in this case are calculated over the additional dimensions.
204
+
205
+ ignore_index:
206
+ Specifies a target value that is ignored and does not contribute to the metric calculation
207
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
208
+ Set to ``False`` for faster computations.
209
+
210
+ Example (preds is int tensor):
211
+ >>> from torch import tensor
212
+ >>> from torchmetrics.classification import MulticlassSpecificity
213
+ >>> target = tensor([2, 1, 0, 0])
214
+ >>> preds = tensor([2, 1, 0, 1])
215
+ >>> metric = MulticlassSpecificity(num_classes=3)
216
+ >>> metric(preds, target)
217
+ tensor(0.8889)
218
+ >>> mcs = MulticlassSpecificity(num_classes=3, average=None)
219
+ >>> mcs(preds, target)
220
+ tensor([1.0000, 0.6667, 1.0000])
221
+
222
+ Example (preds is float tensor):
223
+ >>> from torchmetrics.classification import MulticlassSpecificity
224
+ >>> target = tensor([2, 1, 0, 0])
225
+ >>> preds = tensor([[0.16, 0.26, 0.58],
226
+ ... [0.22, 0.61, 0.17],
227
+ ... [0.71, 0.09, 0.20],
228
+ ... [0.05, 0.82, 0.13]])
229
+ >>> metric = MulticlassSpecificity(num_classes=3)
230
+ >>> metric(preds, target)
231
+ tensor(0.8889)
232
+ >>> mcs = MulticlassSpecificity(num_classes=3, average=None)
233
+ >>> mcs(preds, target)
234
+ tensor([1.0000, 0.6667, 1.0000])
235
+
236
+ Example (multidim tensors):
237
+ >>> from torchmetrics.classification import MulticlassSpecificity
238
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
239
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
240
+ >>> metric = MulticlassSpecificity(num_classes=3, multidim_average='samplewise')
241
+ >>> metric(preds, target)
242
+ tensor([0.7500, 0.6556])
243
+ >>> mcs = MulticlassSpecificity(num_classes=3, multidim_average='samplewise', average=None)
244
+ >>> mcs(preds, target)
245
+ tensor([[0.7500, 0.7500, 0.7500],
246
+ [0.8000, 0.6667, 0.5000]])
247
+
248
+ """
249
+
250
+ plot_lower_bound: float = 0.0
251
+ plot_upper_bound: float = 1.0
252
+ plot_legend_name: str = "Class"
253
+
254
+ def compute(self) -> Tensor:
255
+ """Compute metric."""
256
+ tp, fp, tn, fn = self._final_state()
257
+ return _specificity_reduce(tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average)
258
+
259
+ def plot(
260
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
261
+ ) -> _PLOT_OUT_TYPE:
262
+ """Plot a single or multiple values from the metric.
263
+
264
+ Args:
265
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
266
+ If no value is provided, will automatically call `metric.compute` and plot that result.
267
+ ax: An matplotlib axis object. If provided will add plot to that axis
268
+
269
+ Returns:
270
+ Figure object and Axes object
271
+
272
+ Raises:
273
+ ModuleNotFoundError:
274
+ If `matplotlib` is not installed
275
+
276
+ .. plot::
277
+ :scale: 75
278
+
279
+ >>> from torch import randint
280
+ >>> # Example plotting a single value per class
281
+ >>> from torchmetrics.classification import MulticlassSpecificity
282
+ >>> metric = MulticlassSpecificity(num_classes=3, average=None)
283
+ >>> metric.update(randint(3, (20,)), randint(3, (20,)))
284
+ >>> fig_, ax_ = metric.plot()
285
+
286
+ .. plot::
287
+ :scale: 75
288
+
289
+ >>> from torch import randint
290
+ >>> # Example plotting a multiple values per class
291
+ >>> from torchmetrics.classification import MulticlassSpecificity
292
+ >>> metric = MulticlassSpecificity(num_classes=3, average=None)
293
+ >>> values = []
294
+ >>> for _ in range(20):
295
+ ... values.append(metric(randint(3, (20,)), randint(3, (20,))))
296
+ >>> fig_, ax_ = metric.plot(values)
297
+
298
+ """
299
+ return self._plot(val, ax)
300
+
301
+
302
+ class MultilabelSpecificity(MultilabelStatScores):
303
+ r"""Compute `Specificity`_ for multilabel tasks.
304
+
305
+ .. math:: \text{Specificity} = \frac{\text{TN}}{\text{TN} + \text{FP}}
306
+
307
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
308
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
309
+ encountered for any label, the metric for that label will be set to 0 and the overall metric may therefore be
310
+ affected in turn.
311
+
312
+ As input to ``forward`` and ``update`` the metric accepts the following input:
313
+
314
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
315
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
316
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
317
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
318
+
319
+ As output to ``forward`` and ``compute`` the metric returns the following output:
320
+
321
+ - ``mls`` (:class:`~torch.Tensor`): The returned shape depends on the ``average`` and ``multidim_average``
322
+ arguments:
323
+
324
+ - If ``multidim_average`` is set to ``global``
325
+
326
+ - If ``average='micro'/'macro'/'weighted'``, the output will be a scalar tensor
327
+ - If ``average=None/'none'``, the shape will be ``(C,)``
328
+
329
+ - If ``multidim_average`` is set to ``samplewise``
330
+
331
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N,)``
332
+ - If ``average=None/'none'``, the shape will be ``(N, C)``
333
+
334
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
335
+ which the reduction will then be applied over instead of the sample dimension ``N``.
336
+
337
+ Args:
338
+ num_labels: Integer specifying the number of labels
339
+ threshold: Threshold for transforming probability to binary (0,1) predictions
340
+ average:
341
+ Defines the reduction that is applied over labels. Should be one of the following:
342
+
343
+ - ``micro``: Sum statistics over all labels
344
+ - ``macro``: Calculate statistics for each label and average them
345
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
346
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
347
+
348
+ multidim_average: Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
349
+
350
+ - ``global``: Additional dimensions are flatted along the batch dimension
351
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
352
+ The statistics in this case are calculated over the additional dimensions.
353
+
354
+ ignore_index:
355
+ Specifies a target value that is ignored and does not contribute to the metric calculation
356
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
357
+ Set to ``False`` for faster computations.
358
+
359
+ Example (preds is int tensor):
360
+ >>> from torch import tensor
361
+ >>> from torchmetrics.classification import MultilabelSpecificity
362
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
363
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
364
+ >>> metric = MultilabelSpecificity(num_labels=3)
365
+ >>> metric(preds, target)
366
+ tensor(0.6667)
367
+ >>> mls = MultilabelSpecificity(num_labels=3, average=None)
368
+ >>> mls(preds, target)
369
+ tensor([1., 1., 0.])
370
+
371
+ Example (preds is float tensor):
372
+ >>> from torchmetrics.classification import MultilabelSpecificity
373
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
374
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
375
+ >>> metric = MultilabelSpecificity(num_labels=3)
376
+ >>> metric(preds, target)
377
+ tensor(0.6667)
378
+ >>> mls = MultilabelSpecificity(num_labels=3, average=None)
379
+ >>> mls(preds, target)
380
+ tensor([1., 1., 0.])
381
+
382
+ Example (multidim tensors):
383
+ >>> from torchmetrics.classification import MultilabelSpecificity
384
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
385
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
386
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
387
+ >>> metric = MultilabelSpecificity(num_labels=3, multidim_average='samplewise')
388
+ >>> metric(preds, target)
389
+ tensor([0.0000, 0.3333])
390
+ >>> mls = MultilabelSpecificity(num_labels=3, multidim_average='samplewise', average=None)
391
+ >>> mls(preds, target)
392
+ tensor([[0., 0., 0.],
393
+ [0., 0., 1.]])
394
+
395
+ """
396
+
397
+ plot_lower_bound: float = 0.0
398
+ plot_upper_bound: float = 1.0
399
+ plot_legend_name: str = "Label"
400
+
401
+ def compute(self) -> Tensor:
402
+ """Compute metric."""
403
+ tp, fp, tn, fn = self._final_state()
404
+ return _specificity_reduce(
405
+ tp, fp, tn, fn, average=self.average, multidim_average=self.multidim_average, multilabel=True
406
+ )
407
+
408
+ def plot(
409
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
410
+ ) -> _PLOT_OUT_TYPE:
411
+ """Plot a single or multiple values from the metric.
412
+
413
+ Args:
414
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
415
+ If no value is provided, will automatically call `metric.compute` and plot that result.
416
+ ax: An matplotlib axis object. If provided will add plot to that axis
417
+
418
+ Returns:
419
+ Figure object and Axes object
420
+
421
+ Raises:
422
+ ModuleNotFoundError:
423
+ If `matplotlib` is not installed
424
+
425
+ .. plot::
426
+ :scale: 75
427
+
428
+ >>> from torch import rand, randint
429
+ >>> # Example plotting a single value
430
+ >>> from torchmetrics.classification import MultilabelSpecificity
431
+ >>> metric = MultilabelSpecificity(num_labels=3)
432
+ >>> metric.update(randint(2, (20, 3)), randint(2, (20, 3)))
433
+ >>> fig_, ax_ = metric.plot()
434
+
435
+ .. plot::
436
+ :scale: 75
437
+
438
+ >>> from torch import rand, randint
439
+ >>> # Example plotting multiple values
440
+ >>> from torchmetrics.classification import MultilabelSpecificity
441
+ >>> metric = MultilabelSpecificity(num_labels=3)
442
+ >>> values = [ ]
443
+ >>> for _ in range(10):
444
+ ... values.append(metric(randint(2, (20, 3)), randint(2, (20, 3))))
445
+ >>> fig_, ax_ = metric.plot(values)
446
+
447
+ """
448
+ return self._plot(val, ax)
449
+
450
+
451
+ class Specificity(_ClassificationTaskWrapper):
452
+ r"""Compute `Specificity`_.
453
+
454
+ .. math:: \text{Specificity} = \frac{\text{TN}}{\text{TN} + \text{FP}}
455
+
456
+ Where :math:`\text{TN}` and :math:`\text{FP}` represent the number of true negatives and false positives
457
+ respectively. The metric is only proper defined when :math:`\text{TN} + \text{FP} \neq 0`. If this case is
458
+ encountered for any class/label, the metric for that class/label will be set to 0 and the overall metric may
459
+ therefore be affected in turn.
460
+
461
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
462
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
463
+ :class:`~torchmetrics.classification.BinarySpecificity`, :class:`~torchmetrics.classification.MulticlassSpecificity`
464
+ and :class:`~torchmetrics.classification.MultilabelSpecificity` for the specific details of each argument influence
465
+ and examples.
466
+
467
+ Legacy Example:
468
+ >>> from torch import tensor
469
+ >>> preds = tensor([2, 0, 2, 1])
470
+ >>> target = tensor([1, 1, 2, 0])
471
+ >>> specificity = Specificity(task="multiclass", average='macro', num_classes=3)
472
+ >>> specificity(preds, target)
473
+ tensor(0.6111)
474
+ >>> specificity = Specificity(task="multiclass", average='micro', num_classes=3)
475
+ >>> specificity(preds, target)
476
+ tensor(0.6250)
477
+
478
+ """
479
+
480
+ def __new__( # type: ignore[misc]
481
+ cls: type["Specificity"],
482
+ task: Literal["binary", "multiclass", "multilabel"],
483
+ threshold: float = 0.5,
484
+ num_classes: Optional[int] = None,
485
+ num_labels: Optional[int] = None,
486
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
487
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
488
+ top_k: Optional[int] = 1,
489
+ ignore_index: Optional[int] = None,
490
+ validate_args: bool = True,
491
+ **kwargs: Any,
492
+ ) -> Metric:
493
+ """Initialize task metric."""
494
+ task = ClassificationTask.from_str(task)
495
+ assert multidim_average is not None # noqa: S101 # needed for mypy
496
+ kwargs.update({
497
+ "multidim_average": multidim_average,
498
+ "ignore_index": ignore_index,
499
+ "validate_args": validate_args,
500
+ })
501
+ if task == ClassificationTask.BINARY:
502
+ return BinarySpecificity(threshold, **kwargs)
503
+ if task == ClassificationTask.MULTICLASS:
504
+ if not isinstance(num_classes, int):
505
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
506
+ if not isinstance(top_k, int):
507
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
508
+ return MulticlassSpecificity(num_classes, top_k, average, **kwargs)
509
+ if task == ClassificationTask.MULTILABEL:
510
+ if not isinstance(num_labels, int):
511
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
512
+ return MultilabelSpecificity(num_labels, threshold, average, **kwargs)
513
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity_sensitivity.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, Optional, Union
15
+
16
+ from torch import Tensor
17
+ from typing_extensions import Literal
18
+
19
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
20
+ from torchmetrics.classification.precision_recall_curve import (
21
+ BinaryPrecisionRecallCurve,
22
+ MulticlassPrecisionRecallCurve,
23
+ MultilabelPrecisionRecallCurve,
24
+ )
25
+ from torchmetrics.functional.classification.specificity_sensitivity import (
26
+ _binary_specificity_at_sensitivity_arg_validation,
27
+ _binary_specificity_at_sensitivity_compute,
28
+ _multiclass_specificity_at_sensitivity_arg_validation,
29
+ _multiclass_specificity_at_sensitivity_compute,
30
+ _multilabel_specificity_at_sensitivity_arg_validation,
31
+ _multilabel_specificity_at_sensitivity_compute,
32
+ )
33
+ from torchmetrics.metric import Metric
34
+ from torchmetrics.utilities.data import dim_zero_cat as _cat
35
+ from torchmetrics.utilities.enums import ClassificationTask
36
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
37
+
38
+ if not _MATPLOTLIB_AVAILABLE:
39
+ __doctest_skip__ = [
40
+ "BinarySpecificityAtSensitivity.plot",
41
+ "MulticlassSpecificityAtSensitivity.plot",
42
+ "MultilabelSpecificityAtSensitivity.plot",
43
+ ]
44
+
45
+
46
+ class BinarySpecificityAtSensitivity(BinaryPrecisionRecallCurve):
47
+ r"""Compute the highest possible specificity value given the minimum sensitivity thresholds provided.
48
+
49
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
50
+ find the specificity for a given sensitivity level.
51
+
52
+ Accepts the following input tensors:
53
+
54
+ - ``preds`` (float tensor): ``(N, ...)``. Preds should be a tensor containing probabilities or logits for each
55
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
56
+ sigmoid per element.
57
+ - ``target`` (int tensor): ``(N, ...)``. Target should be a tensor containing ground truth labels, and therefore
58
+ only contain {0,1} values (except if `ignore_index` is specified).
59
+
60
+ Additional dimension ``...`` will be flattened into the batch dimension.
61
+
62
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
63
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
64
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
65
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
66
+ size :math:`\mathcal{O}(n_{thresholds})` (constant memory).
67
+
68
+ Args:
69
+ min_sensitivity: float value specifying minimum sensitivity threshold.
70
+ thresholds:
71
+ Can be one of:
72
+
73
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
74
+ all the data. Most accurate but also most memory consuming approach.
75
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
76
+ 0 to 1 as bins for the calculation.
77
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
78
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
79
+ bins for the calculation.
80
+
81
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
82
+ Set to ``False`` for faster computations.
83
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
84
+
85
+ Returns:
86
+ (tuple): a tuple of 2 tensors containing:
87
+
88
+ - specificity: an scalar tensor with the maximum specificity for the given sensitivity level
89
+ - threshold: an scalar tensor with the corresponding threshold level
90
+
91
+ Example:
92
+ >>> from torchmetrics.classification import BinarySpecificityAtSensitivity
93
+ >>> from torch import tensor
94
+ >>> preds = tensor([0, 0.5, 0.4, 0.1])
95
+ >>> target = tensor([0, 1, 1, 1])
96
+ >>> metric = BinarySpecificityAtSensitivity(min_sensitivity=0.5, thresholds=None)
97
+ >>> metric(preds, target)
98
+ (tensor(1.), tensor(0.4000))
99
+ >>> metric = BinarySpecificityAtSensitivity(min_sensitivity=0.5, thresholds=5)
100
+ >>> metric(preds, target)
101
+ (tensor(1.), tensor(0.2500))
102
+
103
+ """
104
+
105
+ is_differentiable: bool = False
106
+ higher_is_better: Optional[bool] = None
107
+ full_state_update: bool = False
108
+ plot_lower_bound: float = 0.0
109
+ plot_upper_bound: float = 1.0
110
+
111
+ def __init__(
112
+ self,
113
+ min_sensitivity: float,
114
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
115
+ ignore_index: Optional[int] = None,
116
+ validate_args: bool = True,
117
+ **kwargs: Any,
118
+ ) -> None:
119
+ super().__init__(thresholds, ignore_index, validate_args=False, **kwargs)
120
+ if validate_args:
121
+ _binary_specificity_at_sensitivity_arg_validation(min_sensitivity, thresholds, ignore_index)
122
+ self.validate_args = validate_args
123
+ self.min_sensitivity = min_sensitivity
124
+
125
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
126
+ """Compute metric."""
127
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
128
+ return _binary_specificity_at_sensitivity_compute(state, self.thresholds, self.min_sensitivity)
129
+
130
+
131
+ class MulticlassSpecificityAtSensitivity(MulticlassPrecisionRecallCurve):
132
+ r"""Compute the highest possible specificity value given the minimum sensitivity thresholds provided.
133
+
134
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
135
+ find the specificity for a given sensitivity level.
136
+
137
+ For multiclass the metric is calculated by iteratively treating each class as the positive class and all other
138
+ classes as the negative, which is referred to as the one-vs-rest approach. One-vs-one is currently not supported by
139
+ this metric.
140
+
141
+ Accepts the following input tensors:
142
+
143
+ - ``preds`` (float tensor): ``(N, C, ...)``. Preds should be a tensor containing probabilities or logits for each
144
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
145
+ softmax per sample.
146
+ - ``target`` (int tensor): ``(N, ...)``. Target should be a tensor containing ground truth labels, and therefore
147
+ only contain values in the [0, n_classes-1] range (except if `ignore_index` is specified).
148
+
149
+ Additional dimension ``...`` will be flattened into the batch dimension.
150
+
151
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
152
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
153
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
154
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
155
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{classes})` (constant memory).
156
+
157
+ Args:
158
+ num_classes: Integer specifying the number of classes
159
+ min_sensitivity: float value specifying minimum sensitivity threshold.
160
+ thresholds:
161
+ Can be one of:
162
+
163
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
164
+ all the data. Most accurate but also most memory consuming approach.
165
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
166
+ 0 to 1 as bins for the calculation.
167
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
168
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
169
+ bins for the calculation.
170
+
171
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
172
+ Set to ``False`` for faster computations.
173
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
174
+
175
+ Returns:
176
+ (tuple): a tuple of either 2 tensors or 2 lists containing
177
+
178
+ - specificity: an 1d tensor of size (n_classes, ) with the maximum specificity for the given
179
+ sensitivity level per class
180
+ - thresholds: an 1d tensor of size (n_classes, ) with the corresponding threshold level per class
181
+
182
+
183
+ Example:
184
+ >>> from torchmetrics.classification import MulticlassSpecificityAtSensitivity
185
+ >>> from torch import tensor
186
+ >>> preds = tensor([[0.75, 0.05, 0.05, 0.05, 0.05],
187
+ ... [0.05, 0.75, 0.05, 0.05, 0.05],
188
+ ... [0.05, 0.05, 0.75, 0.05, 0.05],
189
+ ... [0.05, 0.05, 0.05, 0.75, 0.05]])
190
+ >>> target = tensor([0, 1, 3, 2])
191
+ >>> metric = MulticlassSpecificityAtSensitivity(num_classes=5, min_sensitivity=0.5, thresholds=None)
192
+ >>> metric(preds, target)
193
+ (tensor([1., 1., 0., 0., 0.]), tensor([7.5000e-01, 7.5000e-01, 5.0000e-02, 5.0000e-02, 1.0000e+06]))
194
+ >>> metric = MulticlassSpecificityAtSensitivity(num_classes=5, min_sensitivity=0.5, thresholds=5)
195
+ >>> metric(preds, target)
196
+ (tensor([1., 1., 0., 0., 0.]), tensor([7.5000e-01, 7.5000e-01, 0.0000e+00, 0.0000e+00, 1.0000e+06]))
197
+
198
+ """
199
+
200
+ is_differentiable: bool = False
201
+ higher_is_better: Optional[bool] = None
202
+ full_state_update: bool = False
203
+ plot_lower_bound: float = 0.0
204
+ plot_upper_bound: float = 1.0
205
+ plot_legend_name: str = "Class"
206
+
207
+ def __init__(
208
+ self,
209
+ num_classes: int,
210
+ min_sensitivity: float,
211
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
212
+ ignore_index: Optional[int] = None,
213
+ validate_args: bool = True,
214
+ **kwargs: Any,
215
+ ) -> None:
216
+ super().__init__(
217
+ num_classes=num_classes, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
218
+ )
219
+ if validate_args:
220
+ _multiclass_specificity_at_sensitivity_arg_validation(
221
+ num_classes, min_sensitivity, thresholds, ignore_index
222
+ )
223
+ self.validate_args = validate_args
224
+ self.min_sensitivity = min_sensitivity
225
+
226
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
227
+ """Compute metric."""
228
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
229
+ return _multiclass_specificity_at_sensitivity_compute(
230
+ state, self.num_classes, self.thresholds, self.min_sensitivity
231
+ )
232
+
233
+
234
+ class MultilabelSpecificityAtSensitivity(MultilabelPrecisionRecallCurve):
235
+ r"""Compute the highest possible specificity value given the minimum sensitivity thresholds provided.
236
+
237
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
238
+ find the specificity for a given sensitivity level.
239
+
240
+ Accepts the following input tensors:
241
+
242
+ - ``preds`` (float tensor): ``(N, C, ...)``. Preds should be a tensor containing probabilities or logits for each
243
+ observation. If preds has values outside [0,1] range we consider the input to be logits and will auto apply
244
+ sigmoid per element.
245
+ - ``target`` (int tensor): ``(N, C, ...)``. Target should be a tensor containing ground truth labels, and therefore
246
+ only contain {0,1} values (except if `ignore_index` is specified).
247
+
248
+ Additional dimension ``...`` will be flattened into the batch dimension.
249
+
250
+ The implementation both supports calculating the metric in a non-binned but accurate version and a binned version
251
+ that is less accurate but more memory efficient. Setting the `thresholds` argument to `None` will activate the
252
+ non-binned version that uses memory of size :math:`\mathcal{O}(n_{samples})` whereas setting the `thresholds`
253
+ argument to either an integer, list or a 1d tensor will use a binned version that uses memory of
254
+ size :math:`\mathcal{O}(n_{thresholds} \times n_{labels})` (constant memory).
255
+
256
+ Args:
257
+ num_labels: Integer specifying the number of labels
258
+ min_sensitivity: float value specifying minimum sensitivity threshold.
259
+ thresholds:
260
+ Can be one of:
261
+
262
+ - If set to `None`, will use a non-binned approach where thresholds are dynamically calculated from
263
+ all the data. Most accurate but also most memory consuming approach.
264
+ - If set to an `int` (larger than 1), will use that number of thresholds linearly spaced from
265
+ 0 to 1 as bins for the calculation.
266
+ - If set to an `list` of floats, will use the indicated thresholds in the list as bins for the calculation
267
+ - If set to an 1d `tensor` of floats, will use the indicated thresholds in the tensor as
268
+ bins for the calculation.
269
+
270
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
271
+ Set to ``False`` for faster computations.
272
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
273
+
274
+ Returns:
275
+ (tuple): a tuple of either 2 tensors or 2 lists containing
276
+
277
+ - specificity: an 1d tensor of size (n_classes, ) with the maximum specificity for the given
278
+ sensitivity level per class
279
+ - thresholds: an 1d tensor of size (n_classes, ) with the corresponding threshold level per class
280
+
281
+ Example:
282
+ >>> from torchmetrics.classification import MultilabelSpecificityAtSensitivity
283
+ >>> from torch import tensor
284
+ >>> preds = tensor([[0.75, 0.05, 0.35],
285
+ ... [0.45, 0.75, 0.05],
286
+ ... [0.05, 0.55, 0.75],
287
+ ... [0.05, 0.65, 0.05]])
288
+ >>> target = tensor([[1, 0, 1],
289
+ ... [0, 0, 0],
290
+ ... [0, 1, 1],
291
+ ... [1, 1, 1]])
292
+ >>> metric = MultilabelSpecificityAtSensitivity(num_labels=3, min_sensitivity=0.5, thresholds=None)
293
+ >>> metric(preds, target)
294
+ (tensor([1.0000, 0.5000, 1.0000]), tensor([0.7500, 0.6500, 0.3500]))
295
+ >>> metric = MultilabelSpecificityAtSensitivity(num_labels=3, min_sensitivity=0.5, thresholds=5)
296
+ >>> metric(preds, target)
297
+ (tensor([1.0000, 0.5000, 1.0000]), tensor([0.7500, 0.5000, 0.2500]))
298
+
299
+ """
300
+
301
+ is_differentiable: bool = False
302
+ higher_is_better: Optional[bool] = None
303
+ full_state_update: bool = False
304
+ plot_lower_bound: float = 0.0
305
+ plot_upper_bound: float = 1.0
306
+ plot_legend_name: str = "Label"
307
+
308
+ def __init__(
309
+ self,
310
+ num_labels: int,
311
+ min_sensitivity: float,
312
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
313
+ ignore_index: Optional[int] = None,
314
+ validate_args: bool = True,
315
+ **kwargs: Any,
316
+ ) -> None:
317
+ super().__init__(
318
+ num_labels=num_labels, thresholds=thresholds, ignore_index=ignore_index, validate_args=False, **kwargs
319
+ )
320
+ if validate_args:
321
+ _multilabel_specificity_at_sensitivity_arg_validation(num_labels, min_sensitivity, thresholds, ignore_index)
322
+ self.validate_args = validate_args
323
+ self.min_sensitivity = min_sensitivity
324
+
325
+ def compute(self) -> tuple[Tensor, Tensor]: # type: ignore[override]
326
+ """Compute metric."""
327
+ state = (_cat(self.preds), _cat(self.target)) if self.thresholds is None else self.confmat
328
+ return _multilabel_specificity_at_sensitivity_compute(
329
+ state, self.num_labels, self.thresholds, self.ignore_index, self.min_sensitivity
330
+ )
331
+
332
+
333
+ class SpecificityAtSensitivity(_ClassificationTaskWrapper):
334
+ r"""Compute the highest possible specificity value given the minimum sensitivity thresholds provided.
335
+
336
+ This is done by first calculating the Receiver Operating Characteristic (ROC) curve for different thresholds and the
337
+ find the specificity for a given sensitivity level.
338
+
339
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
340
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
341
+ :class:`~torchmetrics.classification.BinarySpecificityAtSensitivity`,
342
+ :class:`~torchmetrics.classification.MulticlassSpecificityAtSensitivity` and
343
+ :class:`~torchmetrics.classification.MultilabelSpecificityAtSensitivity` for the specific details of each argument
344
+ influence and examples.
345
+
346
+ """
347
+
348
+ def __new__( # type: ignore[misc]
349
+ cls: type["SpecificityAtSensitivity"],
350
+ task: Literal["binary", "multiclass", "multilabel"],
351
+ min_sensitivity: float,
352
+ thresholds: Optional[Union[int, list[float], Tensor]] = None,
353
+ num_classes: Optional[int] = None,
354
+ num_labels: Optional[int] = None,
355
+ ignore_index: Optional[int] = None,
356
+ validate_args: bool = True,
357
+ **kwargs: Any,
358
+ ) -> Metric:
359
+ """Initialize task metric."""
360
+ task = ClassificationTask.from_str(task)
361
+ if task == ClassificationTask.BINARY:
362
+ return BinarySpecificityAtSensitivity(min_sensitivity, thresholds, ignore_index, validate_args, **kwargs)
363
+ if task == ClassificationTask.MULTICLASS:
364
+ if not isinstance(num_classes, int):
365
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
366
+ return MulticlassSpecificityAtSensitivity(
367
+ num_classes, min_sensitivity, thresholds, ignore_index, validate_args, **kwargs
368
+ )
369
+ if task == ClassificationTask.MULTILABEL:
370
+ if not isinstance(num_labels, int):
371
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
372
+ return MultilabelSpecificityAtSensitivity(
373
+ num_labels, min_sensitivity, thresholds, ignore_index, validate_args, **kwargs
374
+ )
375
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/classification/stat_scores.py ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, Callable, List, Optional, Union
15
+
16
+ import torch
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.classification.base import _ClassificationTaskWrapper
21
+ from torchmetrics.functional.classification.stat_scores import (
22
+ _binary_stat_scores_arg_validation,
23
+ _binary_stat_scores_compute,
24
+ _binary_stat_scores_format,
25
+ _binary_stat_scores_tensor_validation,
26
+ _binary_stat_scores_update,
27
+ _multiclass_stat_scores_arg_validation,
28
+ _multiclass_stat_scores_compute,
29
+ _multiclass_stat_scores_format,
30
+ _multiclass_stat_scores_tensor_validation,
31
+ _multiclass_stat_scores_update,
32
+ _multilabel_stat_scores_arg_validation,
33
+ _multilabel_stat_scores_compute,
34
+ _multilabel_stat_scores_format,
35
+ _multilabel_stat_scores_tensor_validation,
36
+ _multilabel_stat_scores_update,
37
+ )
38
+ from torchmetrics.metric import Metric
39
+ from torchmetrics.utilities.data import dim_zero_cat
40
+ from torchmetrics.utilities.enums import ClassificationTask
41
+
42
+
43
+ class _AbstractStatScores(Metric):
44
+ tp: Union[List[Tensor], Tensor]
45
+ fp: Union[List[Tensor], Tensor]
46
+ tn: Union[List[Tensor], Tensor]
47
+ fn: Union[List[Tensor], Tensor]
48
+
49
+ # define common functions
50
+ def _create_state(
51
+ self,
52
+ size: int,
53
+ multidim_average: Literal["global", "samplewise"] = "global",
54
+ ) -> None:
55
+ """Initialize the states for the different statistics."""
56
+ default: Union[Callable[[], list], Callable[[], Tensor]]
57
+ if multidim_average == "samplewise":
58
+ default = list
59
+ dist_reduce_fx = "cat"
60
+ else:
61
+ default = lambda: torch.zeros(size, dtype=torch.long)
62
+ dist_reduce_fx = "sum"
63
+
64
+ self.add_state("tp", default(), dist_reduce_fx=dist_reduce_fx)
65
+ self.add_state("fp", default(), dist_reduce_fx=dist_reduce_fx)
66
+ self.add_state("tn", default(), dist_reduce_fx=dist_reduce_fx)
67
+ self.add_state("fn", default(), dist_reduce_fx=dist_reduce_fx)
68
+
69
+ def _update_state(self, tp: Tensor, fp: Tensor, tn: Tensor, fn: Tensor) -> None:
70
+ """Update states depending on multidim_average argument."""
71
+ if self.multidim_average == "samplewise":
72
+ self.tp.append(tp) # type: ignore[union-attr]
73
+ self.fp.append(fp) # type: ignore[union-attr]
74
+ self.tn.append(tn) # type: ignore[union-attr]
75
+ self.fn.append(fn) # type: ignore[union-attr]
76
+ else:
77
+ self.tp = self.tp + tp if not isinstance(self.tp, list) else [*self.tp, tp]
78
+ self.fp = self.fp + fp if not isinstance(self.fp, list) else [*self.fp, fp]
79
+ self.tn = self.tn + tn if not isinstance(self.tn, list) else [*self.tn, tn]
80
+ self.fn = self.fn + fn if not isinstance(self.fn, list) else [*self.fn, fn]
81
+
82
+ def _final_state(self) -> tuple[Tensor, Tensor, Tensor, Tensor]:
83
+ """Aggregate states that are lists and return final states."""
84
+ tp = dim_zero_cat(self.tp)
85
+ fp = dim_zero_cat(self.fp)
86
+ tn = dim_zero_cat(self.tn)
87
+ fn = dim_zero_cat(self.fn)
88
+ return tp, fp, tn, fn
89
+
90
+
91
+ class BinaryStatScores(_AbstractStatScores):
92
+ r"""Compute true positives, false positives, true negatives, false negatives and the support for binary tasks.
93
+
94
+ Related to `Type I and Type II errors`_.
95
+
96
+ As input to ``forward`` and ``update`` the metric accepts the following input:
97
+
98
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, ...)``. If preds is a floating
99
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
100
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
101
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
102
+
103
+
104
+ As output to ``forward`` and ``compute`` the metric returns the following output:
105
+
106
+ - ``bss`` (:class:`~torch.Tensor`): A tensor of shape ``(..., 5)``, where the last dimension corresponds
107
+ to ``[tp, fp, tn, fn, sup]`` (``sup`` stands for support and equals ``tp + fn``). The shape
108
+ depends on the ``multidim_average`` parameter:
109
+
110
+ - If ``multidim_average`` is set to ``global``, the shape will be ``(5,)``
111
+ - If ``multidim_average`` is set to ``samplewise``, the shape will be ``(N, 5)``
112
+
113
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
114
+ which the reduction will then be applied over instead of the sample dimension ``N``.
115
+
116
+ Args:
117
+ threshold: Threshold for transforming probability to binary {0,1} predictions
118
+ multidim_average:
119
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
120
+
121
+ - ``global``: Additional dimensions are flatted along the batch dimension
122
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
123
+ The statistics in this case are calculated over the additional dimensions.
124
+
125
+ ignore_index:
126
+ Specifies a target value that is ignored and does not contribute to the metric calculation
127
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
128
+ Set to ``False`` for faster computations.
129
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
130
+
131
+ Example (preds is int tensor):
132
+ >>> from torch import tensor
133
+ >>> from torchmetrics.classification import BinaryStatScores
134
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
135
+ >>> preds = tensor([0, 0, 1, 1, 0, 1])
136
+ >>> metric = BinaryStatScores()
137
+ >>> metric(preds, target)
138
+ tensor([2, 1, 2, 1, 3])
139
+
140
+ Example (preds is float tensor):
141
+ >>> from torchmetrics.classification import BinaryStatScores
142
+ >>> target = tensor([0, 1, 0, 1, 0, 1])
143
+ >>> preds = tensor([0.11, 0.22, 0.84, 0.73, 0.33, 0.92])
144
+ >>> metric = BinaryStatScores()
145
+ >>> metric(preds, target)
146
+ tensor([2, 1, 2, 1, 3])
147
+
148
+ Example (multidim tensors):
149
+ >>> from torchmetrics.classification import BinaryStatScores
150
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
151
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
152
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
153
+ >>> metric = BinaryStatScores(multidim_average='samplewise')
154
+ >>> metric(preds, target)
155
+ tensor([[2, 3, 0, 1, 3],
156
+ [0, 2, 1, 3, 3]])
157
+
158
+ """
159
+
160
+ is_differentiable: bool = False
161
+ higher_is_better: Optional[bool] = None
162
+ full_state_update: bool = False
163
+
164
+ def __init__(
165
+ self,
166
+ threshold: float = 0.5,
167
+ multidim_average: Literal["global", "samplewise"] = "global",
168
+ ignore_index: Optional[int] = None,
169
+ validate_args: bool = True,
170
+ **kwargs: Any,
171
+ ) -> None:
172
+ zero_division = kwargs.pop("zero_division", 0)
173
+ super(_AbstractStatScores, self).__init__(**kwargs)
174
+ if validate_args:
175
+ _binary_stat_scores_arg_validation(threshold, multidim_average, ignore_index, zero_division)
176
+ self.threshold = threshold
177
+ self.multidim_average = multidim_average
178
+ self.ignore_index = ignore_index
179
+ self.validate_args = validate_args
180
+ self.zero_division = zero_division
181
+
182
+ self._create_state(size=1, multidim_average=multidim_average)
183
+
184
+ def update(self, preds: Tensor, target: Tensor) -> None:
185
+ """Update state with predictions and targets."""
186
+ if self.validate_args:
187
+ _binary_stat_scores_tensor_validation(preds, target, self.multidim_average, self.ignore_index)
188
+ preds, target = _binary_stat_scores_format(preds, target, self.threshold, self.ignore_index)
189
+ tp, fp, tn, fn = _binary_stat_scores_update(preds, target, self.multidim_average)
190
+ self._update_state(tp, fp, tn, fn)
191
+
192
+ def compute(self) -> Tensor:
193
+ """Compute the final statistics."""
194
+ tp, fp, tn, fn = self._final_state()
195
+ return _binary_stat_scores_compute(tp, fp, tn, fn, self.multidim_average)
196
+
197
+
198
+ class MulticlassStatScores(_AbstractStatScores):
199
+ r"""Computes true positives, false positives, true negatives, false negatives and the support for multiclass tasks.
200
+
201
+ Related to `Type I and Type II errors`_.
202
+
203
+ As input to ``forward`` and ``update`` the metric accepts the following input:
204
+
205
+ - ``preds`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)`` or float tensor of shape ``(N, C, ..)``.
206
+ If preds is a floating point we apply ``torch.argmax`` along the ``C`` dimension to automatically convert
207
+ probabilities/logits into an int tensor.
208
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, ...)``
209
+
210
+
211
+ As output to ``forward`` and ``compute`` the metric returns the following output:
212
+
213
+ - ``mcss`` (:class:`~torch.Tensor`): A tensor of shape ``(..., 5)``, where the last dimension corresponds
214
+ to ``[tp, fp, tn, fn, sup]`` (``sup`` stands for support and equals ``tp + fn``). The shape
215
+ depends on ``average`` and ``multidim_average`` parameters:
216
+
217
+ - If ``multidim_average`` is set to ``global``:
218
+
219
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(5,)``
220
+ - If ``average=None/'none'``, the shape will be ``(C, 5)``
221
+
222
+ - If ``multidim_average`` is set to ``samplewise``:
223
+
224
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N, 5)``
225
+ - If ``average=None/'none'``, the shape will be ``(N, C, 5)``
226
+
227
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
228
+ which the reduction will then be applied over instead of the sample dimension ``N``.
229
+
230
+ Args:
231
+ num_classes: Integer specifying the number of classes
232
+ average:
233
+ Defines the reduction that is applied over labels. Should be one of the following:
234
+
235
+ - ``micro``: Sum statistics over all labels
236
+ - ``macro``: Calculate statistics for each label and average them
237
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
238
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
239
+ top_k:
240
+ Number of highest probability or logit score predictions considered to find the correct label.
241
+ Only works when ``preds`` contain probabilities/logits.
242
+ multidim_average:
243
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
244
+
245
+ - ``global``: Additional dimensions are flatted along the batch dimension
246
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
247
+ The statistics in this case are calculated over the additional dimensions.
248
+
249
+ ignore_index:
250
+ Specifies a target value that is ignored and does not contribute to the metric calculation
251
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
252
+ Set to ``False`` for faster computations.
253
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
254
+
255
+ Example (preds is int tensor):
256
+ >>> from torch import tensor
257
+ >>> from torchmetrics.classification import MulticlassStatScores
258
+ >>> target = tensor([2, 1, 0, 0])
259
+ >>> preds = tensor([2, 1, 0, 1])
260
+ >>> metric = MulticlassStatScores(num_classes=3, average='micro')
261
+ >>> metric(preds, target)
262
+ tensor([3, 1, 7, 1, 4])
263
+ >>> mcss = MulticlassStatScores(num_classes=3, average=None)
264
+ >>> mcss(preds, target)
265
+ tensor([[1, 0, 2, 1, 2],
266
+ [1, 1, 2, 0, 1],
267
+ [1, 0, 3, 0, 1]])
268
+
269
+ Example (preds is float tensor):
270
+ >>> from torchmetrics.classification import MulticlassStatScores
271
+ >>> target = tensor([2, 1, 0, 0])
272
+ >>> preds = tensor([[0.16, 0.26, 0.58],
273
+ ... [0.22, 0.61, 0.17],
274
+ ... [0.71, 0.09, 0.20],
275
+ ... [0.05, 0.82, 0.13]])
276
+ >>> metric = MulticlassStatScores(num_classes=3, average='micro')
277
+ >>> metric(preds, target)
278
+ tensor([3, 1, 7, 1, 4])
279
+ >>> mcss = MulticlassStatScores(num_classes=3, average=None)
280
+ >>> mcss(preds, target)
281
+ tensor([[1, 0, 2, 1, 2],
282
+ [1, 1, 2, 0, 1],
283
+ [1, 0, 3, 0, 1]])
284
+
285
+ Example (multidim tensors):
286
+ >>> from torchmetrics.classification import MulticlassStatScores
287
+ >>> target = tensor([[[0, 1], [2, 1], [0, 2]], [[1, 1], [2, 0], [1, 2]]])
288
+ >>> preds = tensor([[[0, 2], [2, 0], [0, 1]], [[2, 2], [2, 1], [1, 0]]])
289
+ >>> metric = MulticlassStatScores(num_classes=3, multidim_average="samplewise", average='micro')
290
+ >>> metric(preds, target)
291
+ tensor([[3, 3, 9, 3, 6],
292
+ [2, 4, 8, 4, 6]])
293
+ >>> mcss = MulticlassStatScores(num_classes=3, multidim_average="samplewise", average=None)
294
+ >>> mcss(preds, target)
295
+ tensor([[[2, 1, 3, 0, 2],
296
+ [0, 1, 3, 2, 2],
297
+ [1, 1, 3, 1, 2]],
298
+ [[0, 1, 4, 1, 1],
299
+ [1, 1, 2, 2, 3],
300
+ [1, 2, 2, 1, 2]]])
301
+
302
+ """
303
+
304
+ is_differentiable: bool = False
305
+ higher_is_better: Optional[bool] = None
306
+ full_state_update: bool = False
307
+
308
+ def __init__(
309
+ self,
310
+ num_classes: Optional[int] = None,
311
+ top_k: int = 1,
312
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
313
+ multidim_average: Literal["global", "samplewise"] = "global",
314
+ ignore_index: Optional[int] = None,
315
+ validate_args: bool = True,
316
+ **kwargs: Any,
317
+ ) -> None:
318
+ zero_division = kwargs.pop("zero_division", 0)
319
+ super(_AbstractStatScores, self).__init__(**kwargs)
320
+ if validate_args:
321
+ _multiclass_stat_scores_arg_validation(
322
+ num_classes, top_k, average, multidim_average, ignore_index, zero_division
323
+ )
324
+ self.num_classes = num_classes
325
+ self.top_k = top_k
326
+ self.average = average
327
+ self.multidim_average = multidim_average
328
+ self.ignore_index = ignore_index
329
+ self.validate_args = validate_args
330
+ self.zero_division = zero_division
331
+
332
+ self._create_state(
333
+ size=1 if (average == "micro" and top_k == 1) else (num_classes or 1), multidim_average=multidim_average
334
+ )
335
+
336
+ def update(self, preds: Tensor, target: Tensor) -> None:
337
+ """Update state with predictions and targets."""
338
+ if self.validate_args:
339
+ _multiclass_stat_scores_tensor_validation(
340
+ preds, target, self.num_classes, self.multidim_average, self.ignore_index
341
+ )
342
+ preds, target = _multiclass_stat_scores_format(preds, target, self.top_k)
343
+ num_classes = self.num_classes if self.num_classes is not None else 1
344
+ tp, fp, tn, fn = _multiclass_stat_scores_update(
345
+ preds, target, num_classes, self.top_k, self.average, self.multidim_average, self.ignore_index
346
+ )
347
+ self._update_state(tp, fp, tn, fn)
348
+
349
+ def compute(self) -> Tensor:
350
+ """Compute the final statistics."""
351
+ tp, fp, tn, fn = self._final_state()
352
+ return _multiclass_stat_scores_compute(tp, fp, tn, fn, self.average, self.multidim_average)
353
+
354
+
355
+ class MultilabelStatScores(_AbstractStatScores):
356
+ r"""Compute true positives, false positives, true negatives, false negatives and the support for multilabel tasks.
357
+
358
+ Related to `Type I and Type II errors`_.
359
+
360
+ As input to ``forward`` and ``update`` the metric accepts the following input:
361
+
362
+ - ``preds`` (:class:`~torch.Tensor`): An int or float tensor of shape ``(N, C, ...)``. If preds is a floating
363
+ point tensor with values outside [0,1] range we consider the input to be logits and will auto apply sigmoid
364
+ per element. Additionally, we convert to int tensor with thresholding using the value in ``threshold``.
365
+ - ``target`` (:class:`~torch.Tensor`): An int tensor of shape ``(N, C, ...)``
366
+
367
+ As output to ``forward`` and ``compute`` the metric returns the following output:
368
+
369
+ - ``mlss`` (:class:`~torch.Tensor`): A tensor of shape ``(..., 5)``, where the last dimension corresponds
370
+ to ``[tp, fp, tn, fn, sup]`` (``sup`` stands for support and equals ``tp + fn``). The shape
371
+ depends on ``average`` and ``multidim_average`` parameters:
372
+
373
+ - If ``multidim_average`` is set to ``global``:
374
+
375
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(5,)``
376
+ - If ``average=None/'none'``, the shape will be ``(C, 5)``
377
+
378
+ - If ``multidim_average`` is set to ``samplewise``:
379
+
380
+ - If ``average='micro'/'macro'/'weighted'``, the shape will be ``(N, 5)``
381
+ - If ``average=None/'none'``, the shape will be ``(N, C, 5)``
382
+
383
+ If ``multidim_average`` is set to ``samplewise`` we expect at least one additional dimension ``...`` to be present,
384
+ which the reduction will then be applied over instead of the sample dimension ``N``.
385
+
386
+ Args:
387
+ num_labels: Integer specifying the number of labels
388
+ threshold: Threshold for transforming probability to binary (0,1) predictions
389
+ average:
390
+ Defines the reduction that is applied over labels. Should be one of the following:
391
+
392
+ - ``micro``: Sum statistics over all labels
393
+ - ``macro``: Calculate statistics for each label and average them
394
+ - ``weighted``: calculates statistics for each label and computes weighted average using their support
395
+ - ``"none"`` or ``None``: calculates statistic for each label and applies no reduction
396
+
397
+ multidim_average:
398
+ Defines how additionally dimensions ``...`` should be handled. Should be one of the following:
399
+
400
+ - ``global``: Additional dimensions are flatted along the batch dimension
401
+ - ``samplewise``: Statistic will be calculated independently for each sample on the ``N`` axis.
402
+ The statistics in this case are calculated over the additional dimensions.
403
+
404
+ ignore_index:
405
+ Specifies a target value that is ignored and does not contribute to the metric calculation
406
+ validate_args: bool indicating if input arguments and tensors should be validated for correctness.
407
+ Set to ``False`` for faster computations.
408
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
409
+
410
+ Example (preds is int tensor):
411
+ >>> from torch import tensor
412
+ >>> from torchmetrics.classification import MultilabelStatScores
413
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
414
+ >>> preds = tensor([[0, 0, 1], [1, 0, 1]])
415
+ >>> metric = MultilabelStatScores(num_labels=3, average='micro')
416
+ >>> metric(preds, target)
417
+ tensor([2, 1, 2, 1, 3])
418
+ >>> mlss = MultilabelStatScores(num_labels=3, average=None)
419
+ >>> mlss(preds, target)
420
+ tensor([[1, 0, 1, 0, 1],
421
+ [0, 0, 1, 1, 1],
422
+ [1, 1, 0, 0, 1]])
423
+
424
+ Example (preds is float tensor):
425
+ >>> from torchmetrics.classification import MultilabelStatScores
426
+ >>> target = tensor([[0, 1, 0], [1, 0, 1]])
427
+ >>> preds = tensor([[0.11, 0.22, 0.84], [0.73, 0.33, 0.92]])
428
+ >>> metric = MultilabelStatScores(num_labels=3, average='micro')
429
+ >>> metric(preds, target)
430
+ tensor([2, 1, 2, 1, 3])
431
+ >>> mlss = MultilabelStatScores(num_labels=3, average=None)
432
+ >>> mlss(preds, target)
433
+ tensor([[1, 0, 1, 0, 1],
434
+ [0, 0, 1, 1, 1],
435
+ [1, 1, 0, 0, 1]])
436
+
437
+ Example (multidim tensors):
438
+ >>> from torchmetrics.classification import MultilabelStatScores
439
+ >>> target = tensor([[[0, 1], [1, 0], [0, 1]], [[1, 1], [0, 0], [1, 0]]])
440
+ >>> preds = tensor([[[0.59, 0.91], [0.91, 0.99], [0.63, 0.04]],
441
+ ... [[0.38, 0.04], [0.86, 0.780], [0.45, 0.37]]])
442
+ >>> metric = MultilabelStatScores(num_labels=3, multidim_average='samplewise', average='micro')
443
+ >>> metric(preds, target)
444
+ tensor([[2, 3, 0, 1, 3],
445
+ [0, 2, 1, 3, 3]])
446
+ >>> mlss = MultilabelStatScores(num_labels=3, multidim_average='samplewise', average=None)
447
+ >>> mlss(preds, target)
448
+ tensor([[[1, 1, 0, 0, 1],
449
+ [1, 1, 0, 0, 1],
450
+ [0, 1, 0, 1, 1]],
451
+ [[0, 0, 0, 2, 2],
452
+ [0, 2, 0, 0, 0],
453
+ [0, 0, 1, 1, 1]]])
454
+
455
+ """
456
+
457
+ is_differentiable: bool = False
458
+ higher_is_better: Optional[bool] = None
459
+ full_state_update: bool = False
460
+
461
+ def __init__(
462
+ self,
463
+ num_labels: int,
464
+ threshold: float = 0.5,
465
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "macro",
466
+ multidim_average: Literal["global", "samplewise"] = "global",
467
+ ignore_index: Optional[int] = None,
468
+ validate_args: bool = True,
469
+ **kwargs: Any,
470
+ ) -> None:
471
+ zero_division = kwargs.pop("zero_division", 0)
472
+ super(_AbstractStatScores, self).__init__(**kwargs)
473
+ if validate_args:
474
+ _multilabel_stat_scores_arg_validation(
475
+ num_labels, threshold, average, multidim_average, ignore_index, zero_division
476
+ )
477
+ self.num_labels = num_labels
478
+ self.threshold = threshold
479
+ self.average = average
480
+ self.multidim_average = multidim_average
481
+ self.ignore_index = ignore_index
482
+ self.validate_args = validate_args
483
+ self.zero_division = zero_division
484
+
485
+ self._create_state(size=num_labels, multidim_average=multidim_average)
486
+
487
+ def update(self, preds: Tensor, target: Tensor) -> None:
488
+ """Update state with predictions and targets."""
489
+ if self.validate_args:
490
+ _multilabel_stat_scores_tensor_validation(
491
+ preds, target, self.num_labels, self.multidim_average, self.ignore_index
492
+ )
493
+ preds, target = _multilabel_stat_scores_format(
494
+ preds, target, self.num_labels, self.threshold, self.ignore_index
495
+ )
496
+ tp, fp, tn, fn = _multilabel_stat_scores_update(preds, target, self.multidim_average)
497
+ self._update_state(tp, fp, tn, fn)
498
+
499
+ def compute(self) -> Tensor:
500
+ """Compute the final statistics."""
501
+ tp, fp, tn, fn = self._final_state()
502
+ return _multilabel_stat_scores_compute(tp, fp, tn, fn, self.average, self.multidim_average)
503
+
504
+
505
+ class StatScores(_ClassificationTaskWrapper):
506
+ r"""Compute the number of true positives, false positives, true negatives, false negatives and the support.
507
+
508
+ This function is a simple wrapper to get the task specific versions of this metric, which is done by setting the
509
+ ``task`` argument to either ``'binary'``, ``'multiclass'`` or ``'multilabel'``. See the documentation of
510
+ :class:`~torchmetrics.classification.BinaryStatScores`, :class:`~torchmetrics.classification.MulticlassStatScores`
511
+ and :class:`~torchmetrics.classification.MultilabelStatScores` for the specific details of each argument influence
512
+ and examples.
513
+
514
+ Legacy Example:
515
+ >>> from torch import tensor
516
+ >>> preds = tensor([1, 0, 2, 1])
517
+ >>> target = tensor([1, 1, 2, 0])
518
+ >>> stat_scores = StatScores(task="multiclass", num_classes=3, average='micro')
519
+ >>> stat_scores(preds, target)
520
+ tensor([2, 2, 6, 2, 4])
521
+ >>> stat_scores = StatScores(task="multiclass", num_classes=3, average=None)
522
+ >>> stat_scores(preds, target)
523
+ tensor([[0, 1, 2, 1, 1],
524
+ [1, 1, 1, 1, 2],
525
+ [1, 0, 3, 0, 1]])
526
+
527
+ """
528
+
529
+ def __new__( # type: ignore[misc]
530
+ cls: type["StatScores"],
531
+ task: Literal["binary", "multiclass", "multilabel"],
532
+ threshold: float = 0.5,
533
+ num_classes: Optional[int] = None,
534
+ num_labels: Optional[int] = None,
535
+ average: Optional[Literal["micro", "macro", "weighted", "none"]] = "micro",
536
+ multidim_average: Optional[Literal["global", "samplewise"]] = "global",
537
+ top_k: Optional[int] = 1,
538
+ ignore_index: Optional[int] = None,
539
+ validate_args: bool = True,
540
+ **kwargs: Any,
541
+ ) -> Metric:
542
+ """Initialize task metric."""
543
+ task = ClassificationTask.from_str(task)
544
+ assert multidim_average is not None # noqa: S101 # needed for mypy
545
+ kwargs.update({
546
+ "multidim_average": multidim_average,
547
+ "ignore_index": ignore_index,
548
+ "validate_args": validate_args,
549
+ })
550
+ if task == ClassificationTask.BINARY:
551
+ return BinaryStatScores(threshold, **kwargs)
552
+ if task == ClassificationTask.MULTICLASS:
553
+ if not isinstance(num_classes, int):
554
+ raise ValueError(f"`num_classes` is expected to be `int` but `{type(num_classes)} was passed.`")
555
+ if not isinstance(top_k, int):
556
+ raise ValueError(f"`top_k` is expected to be `int` but `{type(top_k)} was passed.`")
557
+ return MulticlassStatScores(num_classes, top_k, average, **kwargs)
558
+ if task == ClassificationTask.MULTILABEL:
559
+ if not isinstance(num_labels, int):
560
+ raise ValueError(f"`num_labels` is expected to be `int` but `{type(num_labels)} was passed.`")
561
+ return MultilabelStatScores(num_labels, threshold, average, **kwargs)
562
+ raise ValueError(f"Task {task} not supported!")
rtme/lib/python3.10/site-packages/torchmetrics/clustering/__init__.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from torchmetrics.clustering.adjusted_mutual_info_score import AdjustedMutualInfoScore
15
+ from torchmetrics.clustering.adjusted_rand_score import AdjustedRandScore
16
+ from torchmetrics.clustering.calinski_harabasz_score import CalinskiHarabaszScore
17
+ from torchmetrics.clustering.cluster_accuracy import ClusterAccuracy
18
+ from torchmetrics.clustering.davies_bouldin_score import DaviesBouldinScore
19
+ from torchmetrics.clustering.dunn_index import DunnIndex
20
+ from torchmetrics.clustering.fowlkes_mallows_index import FowlkesMallowsIndex
21
+ from torchmetrics.clustering.homogeneity_completeness_v_measure import (
22
+ CompletenessScore,
23
+ HomogeneityScore,
24
+ VMeasureScore,
25
+ )
26
+ from torchmetrics.clustering.mutual_info_score import MutualInfoScore
27
+ from torchmetrics.clustering.normalized_mutual_info_score import NormalizedMutualInfoScore
28
+ from torchmetrics.clustering.rand_score import RandScore
29
+
30
+ __all__ = [
31
+ "AdjustedMutualInfoScore",
32
+ "AdjustedRandScore",
33
+ "CalinskiHarabaszScore",
34
+ "ClusterAccuracy",
35
+ "CompletenessScore",
36
+ "DaviesBouldinScore",
37
+ "DunnIndex",
38
+ "FowlkesMallowsIndex",
39
+ "HomogeneityScore",
40
+ "MutualInfoScore",
41
+ "NormalizedMutualInfoScore",
42
+ "RandScore",
43
+ "VMeasureScore",
44
+ ]
rtme/lib/python3.10/site-packages/torchmetrics/clustering/adjusted_rand_score.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.adjusted_rand_score import adjusted_rand_score
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["AdjustedRandScore.plot"]
27
+
28
+
29
+ class AdjustedRandScore(Metric):
30
+ r"""Compute `Adjusted Rand Score`_ (also known as Adjusted Rand Index).
31
+
32
+ .. math::
33
+ ARS(U, V) = (\text{RS} - \text{Expected RS}) / (\text{Max RS} - \text{Expected RS})
34
+
35
+ The adjusted rand score :math:`\text{ARS}` is in essence the :math:`\text{RS}` (rand score) adjusted for chance.
36
+ The score ensures that completely randomly cluster labels have a score close to zero and only a perfect match will
37
+ have a score of 1 (up to a permutation of the labels). The adjusted rand score is symmetric, therefore swapping
38
+ :math:`U` and :math:`V` yields the same adjusted rand score.
39
+
40
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
41
+ be available in practice since clustering is generally used for unsupervised learning.
42
+
43
+ As input to ``forward`` and ``update`` the metric accepts the following input:
44
+
45
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
46
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
47
+
48
+ As output of ``forward`` and ``compute`` the metric returns the following output:
49
+
50
+ - ``adj_rand_score`` (:class:`~torch.Tensor`): Scalar tensor with the adjusted rand score
51
+
52
+ Args:
53
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
54
+
55
+ Example::
56
+ >>> import torch
57
+ >>> from torchmetrics.clustering import AdjustedRandScore
58
+ >>> metric = AdjustedRandScore()
59
+ >>> metric(torch.tensor([0, 0, 1, 1]), torch.tensor([0, 0, 1, 1]))
60
+ tensor(1.)
61
+ >>> metric(torch.tensor([0, 0, 1, 1]), torch.tensor([0, 1, 0, 1]))
62
+ tensor(-0.5000)
63
+
64
+ """
65
+
66
+ is_differentiable = True
67
+ higher_is_better = None
68
+ full_state_update: bool = False
69
+ plot_lower_bound: float = -0.5
70
+ plot_upper_bound: float = 1.0
71
+ preds: List[Tensor]
72
+ target: List[Tensor]
73
+
74
+ def __init__(self, **kwargs: Any) -> None:
75
+ super().__init__(**kwargs)
76
+
77
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
78
+ self.add_state("target", default=[], dist_reduce_fx="cat")
79
+
80
+ def update(self, preds: Tensor, target: Tensor) -> None:
81
+ """Update state with predictions and targets."""
82
+ self.preds.append(preds)
83
+ self.target.append(target)
84
+
85
+ def compute(self) -> Tensor:
86
+ """Compute mutual information over state."""
87
+ return adjusted_rand_score(dim_zero_cat(self.preds), dim_zero_cat(self.target))
88
+
89
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
90
+ """Plot a single or multiple values from the metric.
91
+
92
+ Args:
93
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
94
+ If no value is provided, will automatically call `metric.compute` and plot that result.
95
+ ax: An matplotlib axis object. If provided will add plot to that axis
96
+
97
+ Returns:
98
+ Figure and Axes object
99
+
100
+ Raises:
101
+ ModuleNotFoundError:
102
+ If `matplotlib` is not installed
103
+
104
+ .. plot::
105
+ :scale: 75
106
+
107
+ >>> # Example plotting a single value
108
+ >>> import torch
109
+ >>> from torchmetrics.clustering import AdjustedRandScore
110
+ >>> metric = AdjustedRandScore()
111
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
112
+ >>> fig_, ax_ = metric.plot(metric.compute())
113
+
114
+ .. plot::
115
+ :scale: 75
116
+
117
+ >>> # Example plotting multiple values
118
+ >>> import torch
119
+ >>> from torchmetrics.clustering import AdjustedRandScore
120
+ >>> metric = AdjustedRandScore()
121
+ >>> values = [ ]
122
+ >>> for _ in range(10):
123
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
124
+ >>> fig_, ax_ = metric.plot(values)
125
+
126
+ """
127
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/calinski_harabasz_score.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.calinski_harabasz_score import calinski_harabasz_score
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["CalinskiHarabaszScore.plot"]
27
+
28
+
29
+ class CalinskiHarabaszScore(Metric):
30
+ r"""Compute Calinski Harabasz Score (also known as variance ratio criterion) for clustering algorithms.
31
+
32
+ .. math::
33
+ CHS(X, L) = \frac{B(X, L) \cdot (n_\text{samples} - n_\text{labels})}{W(X, L) \cdot (n_\text{labels} - 1)}
34
+
35
+ where :math:`B(X, L)` is the between-cluster dispersion, which is the squared distance between the cluster centers
36
+ and the dataset mean, weighted by the size of the clusters, :math:`n_\text{samples}` is the number of samples,
37
+ :math:`n_\text{labels}` is the number of labels, and :math:`W(X, L)` is the within-cluster dispersion e.g. the
38
+ sum of squared distances between each samples and its closest cluster center.
39
+
40
+ This clustering metric is an intrinsic measure, because it does not rely on ground truth labels for the evaluation.
41
+ Instead it examines how well the clusters are separated from each other. The score is higher when clusters are dense
42
+ and well separated, which relates to a standard concept of a cluster.
43
+
44
+ As input to ``forward`` and ``update`` the metric accepts the following input:
45
+
46
+ - ``data`` (:class:`~torch.Tensor`): float tensor with shape ``(N,d)`` with the embedded data. ``d`` is the
47
+ dimensionality of the embedding space.
48
+ - ``labels`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with cluster labels
49
+
50
+ As output of ``forward`` and ``compute`` the metric returns the following output:
51
+
52
+ - ``chs`` (:class:`~torch.Tensor`): A tensor with the Calinski Harabasz Score
53
+
54
+ Args:
55
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
56
+
57
+ Example::
58
+ >>> from torch import randn, randint
59
+ >>> from torchmetrics.clustering import CalinskiHarabaszScore
60
+ >>> data = randn(20, 3)
61
+ >>> labels = randint(3, (20,))
62
+ >>> metric = CalinskiHarabaszScore()
63
+ >>> metric(data, labels)
64
+ tensor(2.2128)
65
+
66
+ """
67
+
68
+ is_differentiable: bool = True
69
+ higher_is_better: bool = True
70
+ full_state_update: bool = False
71
+ plot_lower_bound: float = 0.0
72
+ data: List[Tensor]
73
+ labels: List[Tensor]
74
+
75
+ def __init__(self, **kwargs: Any) -> None:
76
+ super().__init__(**kwargs)
77
+
78
+ self.add_state("data", default=[], dist_reduce_fx="cat")
79
+ self.add_state("labels", default=[], dist_reduce_fx="cat")
80
+
81
+ def update(self, data: Tensor, labels: Tensor) -> None:
82
+ """Update metric state with new data and labels."""
83
+ self.data.append(data)
84
+ self.labels.append(labels)
85
+
86
+ def compute(self) -> Tensor:
87
+ """Compute the Calinski Harabasz Score over all data and labels."""
88
+ return calinski_harabasz_score(dim_zero_cat(self.data), dim_zero_cat(self.labels))
89
+
90
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
91
+ """Plot a single or multiple values from the metric.
92
+
93
+ Args:
94
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
95
+ If no value is provided, will automatically call `metric.compute` and plot that result.
96
+ ax: An matplotlib axis object. If provided will add plot to that axis
97
+
98
+ Returns:
99
+ Figure and Axes object
100
+
101
+ Raises:
102
+ ModuleNotFoundError:
103
+ If `matplotlib` is not installed
104
+
105
+ .. plot::
106
+ :scale: 75
107
+
108
+ >>> # Example plotting a single value
109
+ >>> import torch
110
+ >>> from torchmetrics.clustering import CalinskiHarabaszScore
111
+ >>> metric = CalinskiHarabaszScore()
112
+ >>> metric.update(torch.randn(20, 3), torch.randint(3, (20,)))
113
+ >>> fig_, ax_ = metric.plot(metric.compute())
114
+
115
+ .. plot::
116
+ :scale: 75
117
+
118
+ >>> # Example plotting multiple values
119
+ >>> import torch
120
+ >>> from torchmetrics.clustering import CalinskiHarabaszScore
121
+ >>> metric = CalinskiHarabaszScore()
122
+ >>> values = [ ]
123
+ >>> for _ in range(10):
124
+ ... values.append(metric(torch.randn(20, 3), torch.randint(3, (20,))))
125
+ >>> fig_, ax_ = metric.plot(values)
126
+
127
+ """
128
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/cluster_accuracy.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Any, Optional, Sequence, Union
15
+
16
+ import torch
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.classification import multiclass_confusion_matrix
20
+ from torchmetrics.functional.clustering.cluster_accuracy import _cluster_accuracy_compute
21
+ from torchmetrics.metric import Metric
22
+ from torchmetrics.utilities.imports import (
23
+ _MATPLOTLIB_AVAILABLE,
24
+ _TORCH_LINEAR_ASSIGNMENT_AVAILABLE,
25
+ )
26
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
27
+
28
+ if not _MATPLOTLIB_AVAILABLE:
29
+ __doctest_skip__ = ["ClusterAccuracy.plot"]
30
+
31
+ if not _TORCH_LINEAR_ASSIGNMENT_AVAILABLE:
32
+ __doctest_skip__ = ["ClusterAccuracy", "ClusterAccuracy.plot"]
33
+
34
+
35
+ class ClusterAccuracy(Metric):
36
+ r"""Compute `Cluster Accuracy`_ between predicted and target clusters.
37
+
38
+ .. math::
39
+
40
+ \text{Cluster Accuracy} = \max_g \frac{1}{N} \sum_{n=1}^N \mathbb{1}_{g(p_n) = t_n}
41
+
42
+ Where :math:`g` is a function that maps predicted clusters :math:`p` to target clusters :math:`t`, :math:`N` is the
43
+ number of samples, :math:`p_n` is the predicted cluster for sample :math:`n`, :math:`t_n` is the target cluster for
44
+ sample :math:`n`, and :math:`\mathbb{1}` is the indicator function. The function :math:`g` is determined by solving
45
+ the linear sum assignment problem.
46
+
47
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
48
+ be available in practice since clustering in generally is used for unsupervised learning.
49
+
50
+ As input to ``forward`` and ``update`` the metric accepts the following input:
51
+
52
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
53
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
54
+
55
+ As output of ``forward`` and ``compute`` the metric returns the following output:
56
+
57
+ - ``acc_score`` (:class:`~torch.Tensor`): A tensor with the Cluster Accuracy score
58
+
59
+ Args:
60
+ num_classes: number of classes
61
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
62
+
63
+ Raises:
64
+ RuntimeError:
65
+ If ``torch_linear_assignment`` is not installed. To install, run ``pip install torchmetrics[clustering]``.
66
+ ValueError
67
+ If ``num_classes`` is not a positive integer
68
+
69
+ Example::
70
+ >>> import torch
71
+ >>> from torchmetrics.clustering import ClusterAccuracy
72
+ >>> preds = torch.tensor([0, 0, 1, 1])
73
+ >>> target = torch.tensor([1, 1, 0, 0])
74
+ >>> metric = ClusterAccuracy(num_classes=2)
75
+ >>> metric(preds, target)
76
+ tensor(1.)
77
+
78
+ """
79
+
80
+ is_differentiable: bool = False
81
+ higher_is_better: bool = True
82
+ full_state_update: bool = False
83
+ plot_lower_bound: float = 0.0
84
+ plot_upper_bound: float = 1.0
85
+ confmat: Tensor
86
+
87
+ def __init__(self, num_classes: int, **kwargs: Any) -> None:
88
+ super().__init__(**kwargs)
89
+ if not _TORCH_LINEAR_ASSIGNMENT_AVAILABLE:
90
+ raise RuntimeError(
91
+ "Missing `torch_linear_assignment`. Please install it with `pip install torchmetrics[clustering]`."
92
+ )
93
+
94
+ if not isinstance(num_classes, int) or num_classes <= 0:
95
+ raise ValueError("Argument `num_classes` should be a positive integer")
96
+ self.add_state(
97
+ "confmat", default=torch.zeros((num_classes, num_classes), dtype=torch.int64), dist_reduce_fx="sum"
98
+ )
99
+ self.num_classes = num_classes
100
+
101
+ def update(self, preds: Tensor, target: Tensor) -> None:
102
+ """Update the confusion matrix with the new predictions and targets."""
103
+ self.confmat += multiclass_confusion_matrix(preds, target, num_classes=self.num_classes)
104
+
105
+ def compute(self) -> Tensor:
106
+ """Computes the clustering accuracy."""
107
+ return _cluster_accuracy_compute(self.confmat)
108
+
109
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
110
+ """Plot a single or multiple values from the metric.
111
+
112
+ Args:
113
+ val: Either a single result from calling ``metric.forward`` or ``metric.compute``
114
+ or a list of these results. If no value is provided, will automatically call `metric.compute`
115
+ and plot that result.
116
+ ax: An matplotlib axis object. If provided will add plot to that axis
117
+
118
+ Returns:
119
+ Figure and Axes object
120
+
121
+ Raises:
122
+ ModuleNotFoundError:
123
+ If `matplotlib` is not installed
124
+
125
+ .. plot::
126
+ :scale: 75
127
+
128
+ >>> # Example plotting a single value
129
+ >>> import torch
130
+ >>> from torchmetrics.clustering import ClusterAccuracy
131
+ >>> metric = ClusterAccuracy(num_classes=4)
132
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
133
+ >>> fig_, ax_ = metric.plot(metric.compute())
134
+
135
+ .. plot::
136
+ :scale: 75
137
+
138
+ >>> # Example plotting multiple values
139
+ >>> import torch
140
+ >>> from torchmetrics.clustering import ClusterAccuracy
141
+ >>> metric = ClusterAccuracy(num_classes=4)
142
+ >>> values = [ ]
143
+ >>> for _ in range(10):
144
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
145
+ >>> fig_, ax_ = metric.plot(values)
146
+
147
+ """
148
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/davies_bouldin_score.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.davies_bouldin_score import davies_bouldin_score
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["DaviesBouldinScore.plot"]
27
+
28
+
29
+ class DaviesBouldinScore(Metric):
30
+ r"""Compute `Davies-Bouldin Score`_ for clustering algorithms.
31
+
32
+ Given the following quantities:
33
+
34
+ .. math::
35
+ S_i = \left( \frac{1}{T_i} \sum_{j=1}^{T_i} ||X_j - A_i||^2_2 \right)^{1/2}
36
+
37
+ where :math:`T_i` is the number of samples in cluster :math:`i`, :math:`X_j` is the :math:`j`-th sample in cluster
38
+ :math:`i`, and :math:`A_i` is the centroid of cluster :math:`i`. This quantity is the average distance between all
39
+ the samples in cluster :math:`i` and its centroid. Let
40
+
41
+ .. math::
42
+ M_{i,j} = ||A_i - A_j||_2
43
+
44
+ e.g. the distance between the centroids of cluster :math:`i` and cluster :math:`j`. Then the Davies-Bouldin score
45
+ is defined as:
46
+
47
+ .. math::
48
+ DB = \frac{1}{n_{clusters}} \sum_{i=1}^{n_{clusters}} \max_{j \neq i} \left( \frac{S_i + S_j}{M_{i,j}} \right)
49
+
50
+ This clustering metric is an intrinsic measure, because it does not rely on ground truth labels for the evaluation.
51
+ Instead it examines how well the clusters are separated from each other. The score is higher when clusters are dense
52
+ and well separated, which relates to a standard concept of a cluster.
53
+
54
+ As input to ``forward`` and ``update`` the metric accepts the following input:
55
+
56
+ - ``data`` (:class:`~torch.Tensor`): float tensor with shape ``(N,d)`` with the embedded data. ``d`` is the
57
+ dimensionality of the embedding space.
58
+ - ``labels`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with cluster labels
59
+
60
+ As output of ``forward`` and ``compute`` the metric returns the following output:
61
+
62
+ - ``chs`` (:class:`~torch.Tensor`): A tensor with the Calinski Harabasz Score
63
+
64
+ Args:
65
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
66
+
67
+ Example::
68
+ >>> from torch import randn, randint
69
+ >>> from torchmetrics.clustering import DaviesBouldinScore
70
+ >>> data = randn(10, 3)
71
+ >>> labels = randint(3, (10,))
72
+ >>> metric = DaviesBouldinScore()
73
+ >>> metric(data, labels)
74
+ tensor(1.2540)
75
+
76
+ """
77
+
78
+ is_differentiable: bool = True
79
+ higher_is_better: bool = True
80
+ full_state_update: bool = False
81
+ plot_lower_bound: float = 0.0
82
+ data: List[Tensor]
83
+ labels: List[Tensor]
84
+
85
+ def __init__(self, **kwargs: Any) -> None:
86
+ super().__init__(**kwargs)
87
+
88
+ self.add_state("data", default=[], dist_reduce_fx="cat")
89
+ self.add_state("labels", default=[], dist_reduce_fx="cat")
90
+
91
+ def update(self, data: Tensor, labels: Tensor) -> None:
92
+ """Update metric state with new data and labels."""
93
+ self.data.append(data)
94
+ self.labels.append(labels)
95
+
96
+ def compute(self) -> Tensor:
97
+ """Compute the Davies Bouldin Score over all data and labels."""
98
+ return davies_bouldin_score(dim_zero_cat(self.data), dim_zero_cat(self.labels))
99
+
100
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
101
+ """Plot a single or multiple values from the metric.
102
+
103
+ Args:
104
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
105
+ If no value is provided, will automatically call `metric.compute` and plot that result.
106
+ ax: An matplotlib axis object. If provided will add plot to that axis
107
+
108
+ Returns:
109
+ Figure and Axes object
110
+
111
+ Raises:
112
+ ModuleNotFoundError:
113
+ If `matplotlib` is not installed
114
+
115
+ .. plot::
116
+ :scale: 75
117
+
118
+ >>> # Example plotting a single value
119
+ >>> import torch
120
+ >>> from torchmetrics.clustering import DaviesBouldinScore
121
+ >>> metric = DaviesBouldinScore()
122
+ >>> metric.update(torch.randn(20, 3), torch.randint(0, 2, (20,)))
123
+ >>> fig_, ax_ = metric.plot(metric.compute())
124
+
125
+ .. plot::
126
+ :scale: 75
127
+
128
+ >>> # Example plotting multiple values
129
+ >>> import torch
130
+ >>> from torchmetrics.clustering import DaviesBouldinScore
131
+ >>> metric = DaviesBouldinScore()
132
+ >>> values = [ ]
133
+ >>> for _ in range(10):
134
+ ... values.append(metric(torch.randn(20, 3), torch.randint(0, 2, (20,))))
135
+ >>> fig_, ax_ = metric.plot(values)
136
+
137
+ """
138
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/dunn_index.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.dunn_index import dunn_index
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["DunnIndex.plot"]
27
+
28
+
29
+ class DunnIndex(Metric):
30
+ r"""Compute `Dunn Index`_.
31
+
32
+ .. math::
33
+ DI_m = \frac{\min_{1\leq i<j\leq m} \delta(C_i,C_j)}{\max_{1\leq k\leq m} \Delta_k}
34
+
35
+ Where :math:`C_i` is a cluster of tensors, :math:`C_j` is a cluster of tensors,
36
+ and :math:`\delta(C_i,C_j)` is the intercluster distance metric for :math:`m` clusters.
37
+
38
+ This clustering metric is an intrinsic measure, because it does not rely on ground truth labels for the evaluation.
39
+ Instead it examines how well the clusters are separated from each other. The score is higher when clusters are dense
40
+ and well separated, which relates to a standard concept of a cluster.
41
+
42
+ As input to ``forward`` and ``update`` the metric accepts the following input:
43
+
44
+ - ``data`` (:class:`~torch.Tensor`): float tensor with shape ``(N,d)`` with the embedded data. ``d`` is the
45
+ dimensionality of the embedding space.
46
+ - ``labels`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with cluster labels
47
+
48
+ As output of ``forward`` and ``compute`` the metric returns the following output:
49
+
50
+ - ``dunn_index`` (:class:`~torch.Tensor`): A tensor with the Dunn Index
51
+
52
+ Args:
53
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
54
+
55
+ Example::
56
+ >>> import torch
57
+ >>> from torchmetrics.clustering import DunnIndex
58
+ >>> data = torch.tensor([[0, 0], [0.5, 0], [1, 0], [0.5, 1]])
59
+ >>> labels = torch.tensor([0, 0, 0, 1])
60
+ >>> dunn_index = DunnIndex(p=2)
61
+ >>> dunn_index(data, labels)
62
+ tensor(2.)
63
+
64
+ """
65
+
66
+ is_differentiable: bool = True
67
+ higher_is_better: bool = True
68
+ full_state_update: bool = False
69
+ plot_lower_bound: float = 0.0
70
+ data: List[Tensor]
71
+ labels: List[Tensor]
72
+
73
+ def __init__(self, p: float = 2, **kwargs: Any) -> None:
74
+ super().__init__(**kwargs)
75
+ self.p = p
76
+
77
+ self.add_state("data", default=[], dist_reduce_fx="cat")
78
+ self.add_state("labels", default=[], dist_reduce_fx="cat")
79
+
80
+ def update(self, data: Tensor, labels: Tensor) -> None:
81
+ """Update state with predictions and targets."""
82
+ self.data.append(data)
83
+ self.labels.append(labels)
84
+
85
+ def compute(self) -> Tensor:
86
+ """Compute mutual information over state."""
87
+ return dunn_index(dim_zero_cat(self.data), dim_zero_cat(self.labels), self.p)
88
+
89
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
90
+ """Plot a single or multiple values from the metric.
91
+
92
+ Args:
93
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
94
+ If no value is provided, will automatically call `metric.compute` and plot that result.
95
+ ax: An matplotlib axis object. If provided will add plot to that axis
96
+
97
+ Returns:
98
+ Figure and Axes object
99
+
100
+ Raises:
101
+ ModuleNotFoundError:
102
+ If `matplotlib` is not installed
103
+
104
+ .. plot::
105
+ :scale: 75
106
+
107
+ >>> # Example plotting a single value
108
+ >>> import torch
109
+ >>> from torchmetrics.clustering import DunnIndex
110
+ >>> data = torch.tensor([[0, 0], [0.5, 0], [1, 0], [0.5, 1]])
111
+ >>> labels = torch.tensor([0, 0, 0, 1])
112
+ >>> metric = DunnIndex(p=2)
113
+ >>> metric.update(data, labels)
114
+ >>> fig_, ax_ = metric.plot(metric.compute())
115
+
116
+ .. plot::
117
+ :scale: 75
118
+
119
+ >>> # Example plotting multiple values
120
+ >>> import torch
121
+ >>> from torchmetrics.clustering import DunnIndex
122
+ >>> metric = DunnIndex(p=2)
123
+ >>> values = [ ]
124
+ >>> for _ in range(10):
125
+ ... values.append(metric(torch.randn(50, 3), torch.randint(0, 2, (50,))))
126
+ >>> fig_, ax_ = metric.plot(values)
127
+
128
+ """
129
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/fowlkes_mallows_index.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering import fowlkes_mallows_index
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["FowlkesMallowsIndex.plot"]
27
+
28
+
29
+ class FowlkesMallowsIndex(Metric):
30
+ r"""Compute `Fowlkes-Mallows Index`_.
31
+
32
+ .. math::
33
+ FMI(U,V) = \frac{TP}{\sqrt{(TP + FP) * (TP + FN)}}
34
+
35
+ Where :math:`TP` is the number of true positives, :math:`FP` is the number of false positives, and :math:`FN` is
36
+ the number of false negatives.
37
+
38
+ As input to ``forward`` and ``update`` the metric accepts the following input:
39
+
40
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
41
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
42
+
43
+ As output of ``forward`` and ``compute`` the metric returns the following output:
44
+
45
+ - ``fmi`` (:class:`~torch.Tensor`): A tensor with the Fowlkes-Mallows index.
46
+
47
+ Args:
48
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
49
+
50
+ Example::
51
+ >>> import torch
52
+ >>> from torchmetrics.clustering import FowlkesMallowsIndex
53
+ >>> preds = torch.tensor([2, 2, 0, 1, 0])
54
+ >>> target = torch.tensor([2, 2, 1, 1, 0])
55
+ >>> fmi = FowlkesMallowsIndex()
56
+ >>> fmi(preds, target)
57
+ tensor(0.5000)
58
+
59
+ """
60
+
61
+ is_differentiable: bool = True
62
+ higher_is_better: Optional[bool] = True
63
+ full_state_update: bool = False
64
+ plot_lower_bound: float = 0.0
65
+ plot_upper_bound: float = 1.0
66
+ preds: List[Tensor]
67
+ target: List[Tensor]
68
+
69
+ def __init__(self, **kwargs: Any) -> None:
70
+ super().__init__(**kwargs)
71
+
72
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
73
+ self.add_state("target", default=[], dist_reduce_fx="cat")
74
+
75
+ def update(self, preds: Tensor, target: Tensor) -> None:
76
+ """Update state with predictions and targets."""
77
+ self.preds.append(preds)
78
+ self.target.append(target)
79
+
80
+ def compute(self) -> Tensor:
81
+ """Compute Fowlkes-Mallows index over state."""
82
+ return fowlkes_mallows_index(dim_zero_cat(self.preds), dim_zero_cat(self.target))
83
+
84
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
85
+ """Plot a single or multiple values from the metric.
86
+
87
+ Args:
88
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
89
+ If no value is provided, will automatically call `metric.compute` and plot that result.
90
+ ax: An matplotlib axis object. If provided will add plot to that axis
91
+
92
+ Returns:
93
+ Figure and Axes object
94
+
95
+ Raises:
96
+ ModuleNotFoundError:
97
+ If `matplotlib` is not installed
98
+
99
+ .. plot::
100
+ :scale: 75
101
+
102
+ >>> # Example plotting a single value
103
+ >>> import torch
104
+ >>> from torchmetrics.clustering import FowlkesMallowsIndex
105
+ >>> metric = FowlkesMallowsIndex()
106
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
107
+ >>> fig_, ax_ = metric.plot(metric.compute())
108
+
109
+ .. plot::
110
+ :scale: 75
111
+
112
+ >>> # Example plotting multiple values
113
+ >>> import torch
114
+ >>> from torchmetrics.clustering import FowlkesMallowsIndex
115
+ >>> metric = FowlkesMallowsIndex()
116
+ >>> values = [ ]
117
+ >>> for _ in range(10):
118
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
119
+ >>> fig_, ax_ = metric.plot(values)
120
+
121
+ """
122
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/homogeneity_completeness_v_measure.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.homogeneity_completeness_v_measure import (
20
+ completeness_score,
21
+ homogeneity_score,
22
+ v_measure_score,
23
+ )
24
+ from torchmetrics.metric import Metric
25
+ from torchmetrics.utilities.data import dim_zero_cat
26
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
27
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
28
+
29
+ if not _MATPLOTLIB_AVAILABLE:
30
+ __doctest_skip__ = ["HomogeneityScore.plot", "CompletenessScore.plot", "VMeasureScore.plot"]
31
+
32
+
33
+ class HomogeneityScore(Metric):
34
+ r"""Compute `Homogeneity Score`_.
35
+
36
+ The homogeneity score is a metric to measure the homogeneity of a clustering. A clustering result satisfies
37
+ homogeneity if all of its clusters contain only data points which are members of a single class. The metric is not
38
+ symmetric, therefore swapping ``preds`` and ``target`` yields a different score.
39
+
40
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
41
+ be available in practice since clustering in generally is used for unsupervised learning.
42
+
43
+ As input to ``forward`` and ``update`` the metric accepts the following input:
44
+
45
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
46
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
47
+
48
+ As output of ``forward`` and ``compute`` the metric returns the following output:
49
+
50
+ - ``rand_score`` (:class:`~torch.Tensor`): A tensor with the Rand Score
51
+
52
+ Args:
53
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
54
+
55
+ Example:
56
+ >>> import torch
57
+ >>> from torchmetrics.clustering import HomogeneityScore
58
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
59
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
60
+ >>> metric = HomogeneityScore()
61
+ >>> metric(preds, target)
62
+ tensor(0.4744)
63
+
64
+ """
65
+
66
+ is_differentiable: bool = True
67
+ higher_is_better: bool = True
68
+ full_state_update: bool = False
69
+ plot_lower_bound: float = 0.0
70
+ plot_upper_bound: float = 1.0
71
+ preds: List[Tensor]
72
+ target: List[Tensor]
73
+
74
+ def __init__(self, **kwargs: Any) -> None:
75
+ super().__init__(**kwargs)
76
+
77
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
78
+ self.add_state("target", default=[], dist_reduce_fx="cat")
79
+
80
+ def update(self, preds: Tensor, target: Tensor) -> None:
81
+ """Update state with predictions and targets."""
82
+ self.preds.append(preds)
83
+ self.target.append(target)
84
+
85
+ def compute(self) -> Tensor:
86
+ """Compute rand score over state."""
87
+ return homogeneity_score(dim_zero_cat(self.preds), dim_zero_cat(self.target))
88
+
89
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
90
+ """Plot a single or multiple values from the metric.
91
+
92
+ Args:
93
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
94
+ If no value is provided, will automatically call `metric.compute` and plot that result.
95
+ ax: An matplotlib axis object. If provided will add plot to that axis
96
+
97
+ Returns:
98
+ Figure and Axes object
99
+
100
+ Raises:
101
+ ModuleNotFoundError:
102
+ If `matplotlib` is not installed
103
+
104
+ .. plot::
105
+ :scale: 75
106
+
107
+ >>> # Example plotting a single value
108
+ >>> import torch
109
+ >>> from torchmetrics.clustering import HomogeneityScore
110
+ >>> metric = HomogeneityScore()
111
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
112
+ >>> fig_, ax_ = metric.plot(metric.compute())
113
+
114
+ .. plot::
115
+ :scale: 75
116
+
117
+ >>> # Example plotting multiple values
118
+ >>> import torch
119
+ >>> from torchmetrics.clustering import HomogeneityScore
120
+ >>> metric = HomogeneityScore()
121
+ >>> values = [ ]
122
+ >>> for _ in range(10):
123
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
124
+ >>> fig_, ax_ = metric.plot(values)
125
+
126
+ """
127
+ return self._plot(val, ax)
128
+
129
+
130
+ class CompletenessScore(Metric):
131
+ r"""Compute `Completeness Score`_.
132
+
133
+ A clustering result satisfies completeness if all the data points that are members of a given class are elements of
134
+ the same cluster. The metric is not symmetric, therefore swapping ``preds`` and ``target`` yields a different score.
135
+
136
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
137
+ be available in practice since clustering in generally is used for unsupervised learning.
138
+
139
+ As input to ``forward`` and ``update`` the metric accepts the following input:
140
+
141
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
142
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
143
+
144
+ As output of ``forward`` and ``compute`` the metric returns the following output:
145
+
146
+ - ``rand_score`` (:class:`~torch.Tensor`): A tensor with the Rand Score
147
+
148
+ Args:
149
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
150
+
151
+ Example:
152
+ >>> import torch
153
+ >>> from torchmetrics.clustering import CompletenessScore
154
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
155
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
156
+ >>> metric = CompletenessScore()
157
+ >>> metric(preds, target)
158
+ tensor(0.4744)
159
+
160
+ """
161
+
162
+ is_differentiable: bool = True
163
+ higher_is_better: bool = True
164
+ full_state_update: bool = False
165
+ plot_lower_bound: float = 0.0
166
+ plot_upper_bound: float = 1.0
167
+ preds: List[Tensor]
168
+ target: List[Tensor]
169
+
170
+ def __init__(self, **kwargs: Any) -> None:
171
+ super().__init__(**kwargs)
172
+
173
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
174
+ self.add_state("target", default=[], dist_reduce_fx="cat")
175
+
176
+ def update(self, preds: Tensor, target: Tensor) -> None:
177
+ """Update state with predictions and targets."""
178
+ self.preds.append(preds)
179
+ self.target.append(target)
180
+
181
+ def compute(self) -> Tensor:
182
+ """Compute rand score over state."""
183
+ return completeness_score(dim_zero_cat(self.preds), dim_zero_cat(self.target))
184
+
185
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
186
+ """Plot a single or multiple values from the metric.
187
+
188
+ Args:
189
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
190
+ If no value is provided, will automatically call `metric.compute` and plot that result.
191
+ ax: An matplotlib axis object. If provided will add plot to that axis
192
+
193
+ Returns:
194
+ Figure and Axes object
195
+
196
+ Raises:
197
+ ModuleNotFoundError:
198
+ If `matplotlib` is not installed
199
+
200
+ .. plot::
201
+ :scale: 75
202
+
203
+ >>> # Example plotting a single value
204
+ >>> import torch
205
+ >>> from torchmetrics.clustering import CompletenessScore
206
+ >>> metric = CompletenessScore()
207
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
208
+ >>> fig_, ax_ = metric.plot(metric.compute())
209
+
210
+ .. plot::
211
+ :scale: 75
212
+
213
+ >>> # Example plotting multiple values
214
+ >>> import torch
215
+ >>> from torchmetrics.clustering import CompletenessScore
216
+ >>> metric = CompletenessScore()
217
+ >>> values = [ ]
218
+ >>> for _ in range(10):
219
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
220
+ >>> fig_, ax_ = metric.plot(values)
221
+
222
+ """
223
+ return self._plot(val, ax)
224
+
225
+
226
+ class VMeasureScore(Metric):
227
+ r"""Compute `V-Measure Score`_.
228
+
229
+ The V-measure is the harmonic mean between homogeneity and completeness:
230
+
231
+ ..math::
232
+ v = \frac{(1 + \beta) * homogeneity * completeness}{\beta * homogeneity + completeness}
233
+
234
+ where :math:`\beta` is a weight parameter that defines the weight of homogeneity in the harmonic mean, with the
235
+ default value :math:`\beta=1`. The V-measure is symmetric, which means that swapping ``preds`` and ``target`` does
236
+ not change the score.
237
+
238
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
239
+ be available in practice since clustering in generally is used for unsupervised learning.
240
+
241
+ As input to ``forward`` and ``update`` the metric accepts the following input:
242
+
243
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
244
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
245
+
246
+ As output of ``forward`` and ``compute`` the metric returns the following output:
247
+
248
+ - ``rand_score`` (:class:`~torch.Tensor`): A tensor with the Rand Score
249
+
250
+ Args:
251
+ beta: Weight parameter that defines the weight of homogeneity in the harmonic mean
252
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
253
+
254
+ Example::
255
+ >>> import torch
256
+ >>> from torchmetrics.clustering import VMeasureScore
257
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
258
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
259
+ >>> metric = VMeasureScore(beta=2.0)
260
+ >>> metric(preds, target)
261
+ tensor(0.4744)
262
+
263
+ """
264
+
265
+ is_differentiable: bool = True
266
+ higher_is_better: bool = True
267
+ full_state_update: bool = False
268
+ plot_lower_bound: float = 0.0
269
+ plot_upper_bound: float = 1.0
270
+ preds: List[Tensor]
271
+ target: List[Tensor]
272
+
273
+ def __init__(self, beta: float = 1.0, **kwargs: Any) -> None:
274
+ super().__init__(**kwargs)
275
+ if not (isinstance(beta, float) and beta > 0):
276
+ raise ValueError(f"Argument `beta` should be a positive float. Got {beta}.")
277
+ self.beta = beta
278
+
279
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
280
+ self.add_state("target", default=[], dist_reduce_fx="cat")
281
+
282
+ def update(self, preds: Tensor, target: Tensor) -> None:
283
+ """Update state with predictions and targets."""
284
+ self.preds.append(preds)
285
+ self.target.append(target)
286
+
287
+ def compute(self) -> Tensor:
288
+ """Compute rand score over state."""
289
+ return v_measure_score(dim_zero_cat(self.preds), dim_zero_cat(self.target), beta=self.beta)
290
+
291
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
292
+ """Plot a single or multiple values from the metric.
293
+
294
+ Args:
295
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
296
+ If no value is provided, will automatically call `metric.compute` and plot that result.
297
+ ax: An matplotlib axis object. If provided will add plot to that axis
298
+
299
+ Returns:
300
+ Figure and Axes object
301
+
302
+ Raises:
303
+ ModuleNotFoundError:
304
+ If `matplotlib` is not installed
305
+
306
+ .. plot::
307
+ :scale: 75
308
+
309
+ >>> # Example plotting a single value
310
+ >>> import torch
311
+ >>> from torchmetrics.clustering import VMeasureScore
312
+ >>> metric = VMeasureScore()
313
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
314
+ >>> fig_, ax_ = metric.plot(metric.compute())
315
+
316
+ .. plot::
317
+ :scale: 75
318
+
319
+ >>> # Example plotting multiple values
320
+ >>> import torch
321
+ >>> from torchmetrics.clustering import VMeasureScore
322
+ >>> metric = VMeasureScore()
323
+ >>> values = [ ]
324
+ >>> for _ in range(10):
325
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
326
+ >>> fig_, ax_ = metric.plot(values)
327
+
328
+ """
329
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/mutual_info_score.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.mutual_info_score import mutual_info_score
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["MutualInfoScore.plot"]
27
+
28
+
29
+ class MutualInfoScore(Metric):
30
+ r"""Compute `Mutual Information Score`_.
31
+
32
+ .. math::
33
+ MI(U,V) = \sum_{i=1}^{|U|} \sum_{j=1}^{|V|} \frac{|U_i\cap V_j|}{N}
34
+ \log\frac{N|U_i\cap V_j|}{|U_i||V_j|}
35
+
36
+ Where :math:`U` is a tensor of target values, :math:`V` is a tensor of predictions,
37
+ :math:`|U_i|` is the number of samples in cluster :math:`U_i`, and :math:`|V_i|` is the number of samples in
38
+ cluster :math:`V_i`. The metric is symmetric, therefore swapping :math:`U` and :math:`V` yields the same mutual
39
+ information score.
40
+
41
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
42
+ be available in practice since clustering in generally is used for unsupervised learning.
43
+
44
+ As input to ``forward`` and ``update`` the metric accepts the following input:
45
+
46
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
47
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
48
+
49
+ As output of ``forward`` and ``compute`` the metric returns the following output:
50
+
51
+ - ``mi_score`` (:class:`~torch.Tensor`): A tensor with the Mutual Information Score
52
+
53
+ Args:
54
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
55
+
56
+ Example::
57
+ >>> import torch
58
+ >>> from torchmetrics.clustering import MutualInfoScore
59
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
60
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
61
+ >>> mi_score = MutualInfoScore()
62
+ >>> mi_score(preds, target)
63
+ tensor(0.5004)
64
+
65
+ """
66
+
67
+ is_differentiable: bool = True
68
+ higher_is_better: Optional[bool] = True
69
+ full_state_update: bool = False
70
+ plot_lower_bound: float = 0.0
71
+ preds: List[Tensor]
72
+ target: List[Tensor]
73
+
74
+ def __init__(self, **kwargs: Any) -> None:
75
+ super().__init__(**kwargs)
76
+
77
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
78
+ self.add_state("target", default=[], dist_reduce_fx="cat")
79
+
80
+ def update(self, preds: Tensor, target: Tensor) -> None:
81
+ """Update state with predictions and targets."""
82
+ self.preds.append(preds)
83
+ self.target.append(target)
84
+
85
+ def compute(self) -> Tensor:
86
+ """Compute mutual information over state."""
87
+ return mutual_info_score(dim_zero_cat(self.preds), dim_zero_cat(self.target))
88
+
89
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
90
+ """Plot a single or multiple values from the metric.
91
+
92
+ Args:
93
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
94
+ If no value is provided, will automatically call `metric.compute` and plot that result.
95
+ ax: An matplotlib axis object. If provided will add plot to that axis
96
+
97
+ Returns:
98
+ Figure and Axes object
99
+
100
+ Raises:
101
+ ModuleNotFoundError:
102
+ If `matplotlib` is not installed
103
+
104
+ .. plot::
105
+ :scale: 75
106
+
107
+ >>> # Example plotting a single value
108
+ >>> import torch
109
+ >>> from torchmetrics.clustering import MutualInfoScore
110
+ >>> metric = MutualInfoScore()
111
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
112
+ >>> fig_, ax_ = metric.plot(metric.compute())
113
+
114
+ .. plot::
115
+ :scale: 75
116
+
117
+ >>> # Example plotting multiple values
118
+ >>> import torch
119
+ >>> from torchmetrics.clustering import MutualInfoScore
120
+ >>> metric = MutualInfoScore()
121
+ >>> values = [ ]
122
+ >>> for _ in range(10):
123
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
124
+ >>> fig_, ax_ = metric.plot(values)
125
+
126
+ """
127
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/normalized_mutual_info_score.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Literal, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.clustering.mutual_info_score import MutualInfoScore
20
+ from torchmetrics.functional.clustering.normalized_mutual_info_score import (
21
+ _validate_average_method_arg,
22
+ normalized_mutual_info_score,
23
+ )
24
+ from torchmetrics.utilities.data import dim_zero_cat
25
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
26
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
27
+
28
+ if not _MATPLOTLIB_AVAILABLE:
29
+ __doctest_skip__ = ["NormalizedMutualInfoScore.plot"]
30
+
31
+
32
+ class NormalizedMutualInfoScore(MutualInfoScore):
33
+ r"""Compute `Normalized Mutual Information Score`_.
34
+
35
+ .. math::
36
+ NMI(U,V) = \frac{MI(U,V)}{M_p(U,V)}
37
+
38
+ Where :math:`U` is a tensor of target values, :math:`V` is a tensor of predictions, :math:`M_p(U,V)` is the
39
+ generalized mean of order :math:`p` of :math:`U` and :math:`V`, and :math:`MI(U,V)` is the mutual information score
40
+ between clusters :math:`U` and :math:`V`. The metric is symmetric, therefore swapping :math:`U` and :math:`V` yields
41
+ the same mutual information score.
42
+
43
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
44
+ be available in practice since clustering in generally is used for unsupervised learning.
45
+
46
+ As input to ``forward`` and ``update`` the metric accepts the following input:
47
+
48
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
49
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
50
+
51
+ As output of ``forward`` and ``compute`` the metric returns the following output:
52
+
53
+ - ``nmi_score`` (:class:`~torch.Tensor`): A tensor with the Normalized Mutual Information Score
54
+
55
+ Args:
56
+ average_method: Method used to calculate generalized mean for normalization
57
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
58
+
59
+ Example::
60
+ >>> import torch
61
+ >>> from torchmetrics.clustering import NormalizedMutualInfoScore
62
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
63
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
64
+ >>> nmi_score = NormalizedMutualInfoScore("arithmetic")
65
+ >>> nmi_score(preds, target)
66
+ tensor(0.4744)
67
+
68
+ """
69
+
70
+ is_differentiable: bool = True
71
+ higher_is_better: Optional[bool] = None
72
+ full_state_update: bool = False
73
+ plot_lower_bound: float = 0.0
74
+ plot_upper_bound: float = 0.0
75
+ preds: List[Tensor]
76
+ target: List[Tensor]
77
+
78
+ def __init__(
79
+ self, average_method: Literal["min", "geometric", "arithmetic", "max"] = "arithmetic", **kwargs: Any
80
+ ) -> None:
81
+ super().__init__(**kwargs)
82
+ _validate_average_method_arg(average_method)
83
+ self.average_method = average_method
84
+
85
+ def compute(self) -> Tensor:
86
+ """Compute normalized mutual information over state."""
87
+ return normalized_mutual_info_score(dim_zero_cat(self.preds), dim_zero_cat(self.target), self.average_method)
88
+
89
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
90
+ """Plot a single or multiple values from the metric.
91
+
92
+ Args:
93
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
94
+ If no value is provided, will automatically call `metric.compute` and plot that result.
95
+ ax: An matplotlib axis object. If provided will add plot to that axis
96
+
97
+ Returns:
98
+ Figure and Axes object
99
+
100
+ Raises:
101
+ ModuleNotFoundError:
102
+ If `matplotlib` is not installed
103
+
104
+ .. plot::
105
+ :scale: 75
106
+
107
+ >>> # Example plotting a single value
108
+ >>> import torch
109
+ >>> from torchmetrics.clustering import NormalizedMutualInfoScore
110
+ >>> metric = NormalizedMutualInfoScore()
111
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
112
+ >>> fig_, ax_ = metric.plot(metric.compute())
113
+
114
+ .. plot::
115
+ :scale: 75
116
+
117
+ >>> # Example plotting multiple values
118
+ >>> import torch
119
+ >>> from torchmetrics.clustering import NormalizedMutualInfoScore
120
+ >>> metric = NormalizedMutualInfoScore()
121
+ >>> values = [ ]
122
+ >>> for _ in range(10):
123
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
124
+ >>> fig_, ax_ = metric.plot(values)
125
+
126
+ """
127
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/clustering/rand_score.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.functional.clustering.rand_score import rand_score
20
+ from torchmetrics.metric import Metric
21
+ from torchmetrics.utilities.data import dim_zero_cat
22
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE
23
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
24
+
25
+ if not _MATPLOTLIB_AVAILABLE:
26
+ __doctest_skip__ = ["RandScore.plot"]
27
+
28
+
29
+ class RandScore(Metric):
30
+ r"""Compute `Rand Score`_ (alternatively known as Rand Index).
31
+
32
+ .. math::
33
+ RS(U, V) = \text{number of agreeing pairs} / \text{number of pairs}
34
+
35
+ The number of agreeing pairs is every :math:`(i, j)` pair of samples where :math:`i \in U` and :math:`j \in V`
36
+ (the predicted and true clusterings, respectively) that are in the same cluster for both clusterings. The metric is
37
+ symmetric, therefore swapping :math:`U` and :math:`V` yields the same rand score.
38
+
39
+ This clustering metric is an extrinsic measure, because it requires ground truth clustering labels, which may not
40
+ be available in practice since clustering in generally is used for unsupervised learning.
41
+
42
+ As input to ``forward`` and ``update`` the metric accepts the following input:
43
+
44
+ - ``preds`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with predicted cluster labels
45
+ - ``target`` (:class:`~torch.Tensor`): single integer tensor with shape ``(N,)`` with ground truth cluster labels
46
+
47
+ As output of ``forward`` and ``compute`` the metric returns the following output:
48
+
49
+ - ``rand_score`` (:class:`~torch.Tensor`): A tensor with the Rand Score
50
+
51
+ Args:
52
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
53
+
54
+ Example::
55
+ >>> import torch
56
+ >>> from torchmetrics.clustering import RandScore
57
+ >>> preds = torch.tensor([2, 1, 0, 1, 0])
58
+ >>> target = torch.tensor([0, 2, 1, 1, 0])
59
+ >>> metric = RandScore()
60
+ >>> metric(preds, target)
61
+ tensor(0.6000)
62
+
63
+ """
64
+
65
+ is_differentiable = True
66
+ higher_is_better = None
67
+ full_state_update: bool = False
68
+ plot_lower_bound: float = 0.0
69
+ preds: List[Tensor]
70
+ target: List[Tensor]
71
+
72
+ def __init__(self, **kwargs: Any) -> None:
73
+ super().__init__(**kwargs)
74
+
75
+ self.add_state("preds", default=[], dist_reduce_fx="cat")
76
+ self.add_state("target", default=[], dist_reduce_fx="cat")
77
+
78
+ def update(self, preds: Tensor, target: Tensor) -> None:
79
+ """Update state with predictions and targets."""
80
+ self.preds.append(preds)
81
+ self.target.append(target)
82
+
83
+ def compute(self) -> Tensor:
84
+ """Compute rand score over state."""
85
+ return rand_score(dim_zero_cat(self.preds), dim_zero_cat(self.target))
86
+
87
+ def plot(self, val: Union[Tensor, Sequence[Tensor], None] = None, ax: Optional[_AX_TYPE] = None) -> _PLOT_OUT_TYPE:
88
+ """Plot a single or multiple values from the metric.
89
+
90
+ Args:
91
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
92
+ If no value is provided, will automatically call `metric.compute` and plot that result.
93
+ ax: An matplotlib axis object. If provided will add plot to that axis
94
+
95
+ Returns:
96
+ Figure and Axes object
97
+
98
+ Raises:
99
+ ModuleNotFoundError:
100
+ If `matplotlib` is not installed
101
+
102
+ .. plot::
103
+ :scale: 75
104
+
105
+ >>> # Example plotting a single value
106
+ >>> import torch
107
+ >>> from torchmetrics.clustering import RandScore
108
+ >>> metric = RandScore()
109
+ >>> metric.update(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,)))
110
+ >>> fig_, ax_ = metric.plot(metric.compute())
111
+
112
+ .. plot::
113
+ :scale: 75
114
+
115
+ >>> # Example plotting multiple values
116
+ >>> import torch
117
+ >>> from torchmetrics.clustering import RandScore
118
+ >>> metric = RandScore()
119
+ >>> values = [ ]
120
+ >>> for _ in range(10):
121
+ ... values.append(metric(torch.randint(0, 4, (10,)), torch.randint(0, 4, (10,))))
122
+ >>> fig_, ax_ = metric.plot(values)
123
+
124
+ """
125
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/detection/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from torchmetrics.detection.panoptic_qualities import ModifiedPanopticQuality, PanopticQuality
15
+ from torchmetrics.utilities.imports import _TORCHVISION_AVAILABLE
16
+
17
+ __all__ = ["ModifiedPanopticQuality", "PanopticQuality"]
18
+
19
+ if _TORCHVISION_AVAILABLE:
20
+ from torchmetrics.detection.ciou import CompleteIntersectionOverUnion
21
+ from torchmetrics.detection.diou import DistanceIntersectionOverUnion
22
+ from torchmetrics.detection.giou import GeneralizedIntersectionOverUnion
23
+ from torchmetrics.detection.iou import IntersectionOverUnion
24
+ from torchmetrics.detection.mean_ap import MeanAveragePrecision
25
+
26
+ __all__ += [
27
+ "CompleteIntersectionOverUnion",
28
+ "DistanceIntersectionOverUnion",
29
+ "GeneralizedIntersectionOverUnion",
30
+ "IntersectionOverUnion",
31
+ "MeanAveragePrecision",
32
+ ]
rtme/lib/python3.10/site-packages/torchmetrics/detection/_deprecated.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Collection
2
+ from typing import Any
3
+
4
+ from torchmetrics.detection import ModifiedPanopticQuality, PanopticQuality
5
+ from torchmetrics.utilities.prints import _deprecated_root_import_class
6
+
7
+
8
+ class _ModifiedPanopticQuality(ModifiedPanopticQuality):
9
+ """Wrapper for deprecated import.
10
+
11
+ >>> from torch import tensor
12
+ >>> preds = tensor([[[0, 0], [0, 1], [6, 0], [7, 0], [0, 2], [1, 0]]])
13
+ >>> target = tensor([[[0, 1], [0, 0], [6, 0], [7, 0], [6, 0], [255, 0]]])
14
+ >>> pq_modified = _ModifiedPanopticQuality(things = {0, 1}, stuffs = {6, 7})
15
+ >>> pq_modified(preds, target)
16
+ tensor(0.7667, dtype=torch.float64)
17
+
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ things: Collection[int],
23
+ stuffs: Collection[int],
24
+ allow_unknown_preds_category: bool = False,
25
+ **kwargs: Any,
26
+ ) -> None:
27
+ _deprecated_root_import_class("ModifiedPanopticQuality", "detection")
28
+ super().__init__(
29
+ things=things, stuffs=stuffs, allow_unknown_preds_category=allow_unknown_preds_category, **kwargs
30
+ )
31
+
32
+
33
+ class _PanopticQuality(PanopticQuality):
34
+ """Wrapper for deprecated import.
35
+
36
+ >>> from torch import tensor
37
+ >>> preds = tensor([[[[6, 0], [0, 0], [6, 0], [6, 0]],
38
+ ... [[0, 0], [0, 0], [6, 0], [0, 1]],
39
+ ... [[0, 0], [0, 0], [6, 0], [0, 1]],
40
+ ... [[0, 0], [7, 0], [6, 0], [1, 0]],
41
+ ... [[0, 0], [7, 0], [7, 0], [7, 0]]]])
42
+ >>> target = tensor([[[[6, 0], [0, 1], [6, 0], [0, 1]],
43
+ ... [[0, 1], [0, 1], [6, 0], [0, 1]],
44
+ ... [[0, 1], [0, 1], [6, 0], [1, 0]],
45
+ ... [[0, 1], [7, 0], [1, 0], [1, 0]],
46
+ ... [[0, 1], [7, 0], [7, 0], [7, 0]]]])
47
+ >>> panoptic_quality = _PanopticQuality(things = {0, 1}, stuffs = {6, 7})
48
+ >>> panoptic_quality(preds, target)
49
+ tensor(0.5463, dtype=torch.float64)
50
+
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ things: Collection[int],
56
+ stuffs: Collection[int],
57
+ allow_unknown_preds_category: bool = False,
58
+ **kwargs: Any,
59
+ ) -> None:
60
+ _deprecated_root_import_class("PanopticQuality", "detection")
61
+ super().__init__(
62
+ things=things, stuffs=stuffs, allow_unknown_preds_category=allow_unknown_preds_category, **kwargs
63
+ )
rtme/lib/python3.10/site-packages/torchmetrics/detection/_mean_ap.py ADDED
@@ -0,0 +1,988 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import logging
15
+ from collections.abc import Sequence
16
+ from typing import Any, Callable, List, Literal, Optional, Union
17
+
18
+ import numpy as np
19
+ import torch
20
+ import torch.distributed as dist
21
+ from torch import IntTensor, Tensor
22
+
23
+ from torchmetrics.detection.helpers import _fix_empty_tensors, _input_validator
24
+ from torchmetrics.metric import Metric
25
+ from torchmetrics.utilities.data import _cumsum
26
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE, _PYCOCOTOOLS_AVAILABLE, _TORCHVISION_AVAILABLE
27
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
28
+
29
+ if not _MATPLOTLIB_AVAILABLE:
30
+ __doctest_skip__ = ["MeanAveragePrecision.plot"]
31
+
32
+ if not _TORCHVISION_AVAILABLE or not _PYCOCOTOOLS_AVAILABLE:
33
+ __doctest_skip__ = ["MeanAveragePrecision.plot", "MeanAveragePrecision"]
34
+
35
+ log = logging.getLogger(__name__)
36
+
37
+
38
+ def compute_area(inputs: list[Any], iou_type: Literal["bbox", "segm"] = "bbox") -> Tensor:
39
+ """Compute area of input depending on the specified iou_type.
40
+
41
+ Default output for empty input is :class:`~torch.Tensor`
42
+
43
+ """
44
+ import pycocotools.mask as mask_utils
45
+ from torchvision.ops import box_area
46
+
47
+ if len(inputs) == 0:
48
+ return Tensor([])
49
+
50
+ if iou_type == "bbox":
51
+ return box_area(torch.stack(inputs))
52
+ if iou_type == "segm":
53
+ inputs = [{"size": i[0], "counts": i[1]} for i in inputs]
54
+ return torch.tensor(mask_utils.area(inputs).astype("float"))
55
+
56
+ raise Exception(f"IOU type {iou_type} is not supported")
57
+
58
+
59
+ def compute_iou(
60
+ det: list[Any],
61
+ gt: list[Any],
62
+ iou_type: Literal["bbox", "segm"] = "bbox",
63
+ ) -> Tensor:
64
+ """Compute IOU between detections and ground-truth using the specified iou_type."""
65
+ from torchvision.ops import box_iou
66
+
67
+ if iou_type == "bbox":
68
+ return box_iou(torch.stack(det), torch.stack(gt))
69
+ if iou_type == "segm":
70
+ return _segm_iou(det, gt)
71
+ raise Exception(f"IOU type {iou_type} is not supported")
72
+
73
+
74
+ class BaseMetricResults(dict):
75
+ """Base metric class, that allows fields for pre-defined metrics."""
76
+
77
+ def __getattr__(self, key: str) -> Tensor:
78
+ """Get a specific metric attribute."""
79
+ # Using this you get the correct error message, an AttributeError instead of a KeyError
80
+ if key in self:
81
+ return self[key]
82
+ raise AttributeError(f"No such attribute: {key}")
83
+
84
+ def __setattr__(self, key: str, value: Tensor) -> None:
85
+ """Set a specific metric attribute."""
86
+ self[key] = value
87
+
88
+ def __delattr__(self, key: str) -> None:
89
+ """Delete a specific metric attribute."""
90
+ if key in self:
91
+ del self[key]
92
+ raise AttributeError(f"No such attribute: {key}")
93
+
94
+
95
+ class MAPMetricResults(BaseMetricResults):
96
+ """Class to wrap the final mAP results."""
97
+
98
+ __slots__ = ("classes", "map", "map_50", "map_75", "map_large", "map_medium", "map_small")
99
+
100
+
101
+ class MARMetricResults(BaseMetricResults):
102
+ """Class to wrap the final mAR results."""
103
+
104
+ __slots__ = ("mar_1", "mar_10", "mar_100", "mar_large", "mar_medium", "mar_small")
105
+
106
+
107
+ class COCOMetricResults(BaseMetricResults):
108
+ """Class to wrap the final COCO metric results including various mAP/mAR values."""
109
+
110
+ __slots__ = (
111
+ "map",
112
+ "map_50",
113
+ "map_75",
114
+ "map_large",
115
+ "map_medium",
116
+ "map_per_class",
117
+ "map_small",
118
+ "mar_1",
119
+ "mar_10",
120
+ "mar_100",
121
+ "mar_100_per_class",
122
+ "mar_large",
123
+ "mar_medium",
124
+ "mar_small",
125
+ )
126
+
127
+
128
+ def _segm_iou(det: list[tuple[np.ndarray, np.ndarray]], gt: list[tuple[np.ndarray, np.ndarray]]) -> Tensor:
129
+ """Compute IOU between detections and ground-truths using mask-IOU.
130
+
131
+ Implementation is based on pycocotools toolkit for mask_utils.
132
+
133
+ Args:
134
+ det: A list of detection masks as ``[(RLE_SIZE, RLE_COUNTS)]``, where ``RLE_SIZE`` is (width, height) dimension
135
+ of the input and RLE_COUNTS is its RLE representation;
136
+
137
+ gt: A list of ground-truth masks as ``[(RLE_SIZE, RLE_COUNTS)]``, where ``RLE_SIZE`` is (width, height) dimension
138
+ of the input and RLE_COUNTS is its RLE representation;
139
+
140
+ """
141
+ import pycocotools.mask as mask_utils
142
+
143
+ det_coco_format = [{"size": i[0], "counts": i[1]} for i in det]
144
+ gt_coco_format = [{"size": i[0], "counts": i[1]} for i in gt]
145
+
146
+ return torch.tensor(mask_utils.iou(det_coco_format, gt_coco_format, [False for _ in gt]))
147
+
148
+
149
+ class MeanAveragePrecision(Metric):
150
+ r"""Compute the `Mean-Average-Precision (mAP) and Mean-Average-Recall (mAR)`_ for object detection predictions.
151
+
152
+ .. math::
153
+ \text{mAP} = \frac{1}{n} \sum_{i=1}^{n} AP_i
154
+
155
+ where :math:`AP_i` is the average precision for class :math:`i` and :math:`n` is the number of classes. The average
156
+ precision is defined as the area under the precision-recall curve. If argument `class_metrics` is set to ``True``,
157
+ the metric will also return the mAP/mAR per class.
158
+
159
+ As input to ``forward`` and ``update`` the metric accepts the following input:
160
+
161
+ - ``preds`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
162
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict
163
+
164
+ - boxes: (:class:`~torch.FloatTensor`) of shape ``(num_boxes, 4)`` containing ``num_boxes`` detection
165
+ boxes of the format specified in the constructor.
166
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
167
+ - scores: :class:`~torch.FloatTensor` of shape ``(num_boxes)`` containing detection scores for the boxes.
168
+ - labels: :class:`~torch.IntTensor` of shape ``(num_boxes)`` containing 0-indexed detection classes for
169
+ the boxes.
170
+ - masks: :class:`~torch.bool` of shape ``(num_boxes, image_height, image_width)`` containing boolean masks.
171
+ Only required when `iou_type="segm"`.
172
+
173
+ - ``target`` (:class:`~List`) A list consisting of dictionaries each containing the key-values
174
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
175
+
176
+ - boxes: :class:`~torch.FloatTensor` of shape ``(num_boxes, 4)`` containing ``num_boxes`` ground truth
177
+ boxes of the format specified in the constructor.
178
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
179
+ - labels: :class:`~torch.IntTensor` of shape ``(num_boxes)`` containing 0-indexed ground truth
180
+ classes for the boxes.
181
+ - masks: :class:`~torch.bool` of shape ``(num_boxes, image_height, image_width)`` containing boolean masks.
182
+ Only required when `iou_type="segm"`.
183
+
184
+ As output of ``forward`` and ``compute`` the metric returns the following output:
185
+
186
+ - ``map_dict``: A dictionary containing the following key-values:
187
+
188
+ - map: (:class:`~torch.Tensor`)
189
+ - map_small: (:class:`~torch.Tensor`)
190
+ - map_medium:(:class:`~torch.Tensor`)
191
+ - map_large: (:class:`~torch.Tensor`)
192
+ - mar_1: (:class:`~torch.Tensor`)
193
+ - mar_10: (:class:`~torch.Tensor`)
194
+ - mar_100: (:class:`~torch.Tensor`)
195
+ - mar_small: (:class:`~torch.Tensor`)
196
+ - mar_medium: (:class:`~torch.Tensor`)
197
+ - mar_large: (:class:`~torch.Tensor`)
198
+ - map_50: (:class:`~torch.Tensor`) (-1 if 0.5 not in the list of iou thresholds)
199
+ - map_75: (:class:`~torch.Tensor`) (-1 if 0.75 not in the list of iou thresholds)
200
+ - map_per_class: (:class:`~torch.Tensor`) (-1 if class metrics are disabled)
201
+ - mar_100_per_class: (:class:`~torch.Tensor`) (-1 if class metrics are disabled)
202
+ - classes (:class:`~torch.Tensor`)
203
+
204
+ For an example on how to use this metric check the `torchmetrics mAP example`_.
205
+
206
+ .. attention::
207
+ The ``map`` score is calculated with @[ IoU=self.iou_thresholds | area=all | max_dets=max_detection_thresholds ]
208
+ **Caution:** If the initialization parameters are changed, dictionary keys for mAR can change as well.
209
+ The default properties are also accessible via fields and will raise an ``AttributeError`` if not available.
210
+
211
+ .. important::
212
+ This metric is following the mAP implementation of `pycocotools`_ a standard implementation for the mAP metric
213
+ for object detection.
214
+
215
+ .. hint::
216
+ This metric requires you to have `torchvision` version 0.8.0 or newer installed
217
+ (with corresponding version 1.7.0 of torch or newer). This metric requires `pycocotools`
218
+ installed when iou_type is `segm`. Please install with ``pip install torchvision`` or
219
+ ``pip install torchmetrics[detection]``.
220
+
221
+ Args:
222
+ box_format:
223
+ Input format of given boxes. Supported formats are ``[`xyxy`, `xywh`, `cxcywh`]``.
224
+ iou_type:
225
+ Type of input (either masks or bounding-boxes) used for computing IOU.
226
+ Supported IOU types are ``["bbox", "segm"]``.
227
+ If using ``"segm"``, masks should be provided (see :meth:`update`).
228
+ iou_thresholds:
229
+ IoU thresholds for evaluation. If set to ``None`` it corresponds to the stepped range ``[0.5,...,0.95]``
230
+ with step ``0.05``. Else provide a list of floats.
231
+ rec_thresholds:
232
+ Recall thresholds for evaluation. If set to ``None`` it corresponds to the stepped range ``[0,...,1]``
233
+ with step ``0.01``. Else provide a list of floats.
234
+ max_detection_thresholds:
235
+ Thresholds on max detections per image. If set to `None` will use thresholds ``[1, 10, 100]``.
236
+ Else, please provide a list of ints.
237
+ class_metrics:
238
+ Option to enable per-class metrics for mAP and mAR_100. Has a performance impact.
239
+ kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
240
+
241
+ Raises:
242
+ ModuleNotFoundError:
243
+ If ``torchvision`` is not installed or version installed is lower than 0.8.0
244
+ ModuleNotFoundError:
245
+ If ``iou_type`` is equal to ``segm`` and ``pycocotools`` is not installed
246
+ ValueError:
247
+ If ``class_metrics`` is not a boolean
248
+ ValueError:
249
+ If ``preds`` is not of type (:class:`~List[Dict[str, Tensor]]`)
250
+ ValueError:
251
+ If ``target`` is not of type ``List[Dict[str, Tensor]]``
252
+ ValueError:
253
+ If ``preds`` and ``target`` are not of the same length
254
+ ValueError:
255
+ If any of ``preds.boxes``, ``preds.scores`` and ``preds.labels`` are not of the same length
256
+ ValueError:
257
+ If any of ``target.boxes`` and ``target.labels`` are not of the same length
258
+ ValueError:
259
+ If any box is not type float and of length 4
260
+ ValueError:
261
+ If any class is not type int and of length 1
262
+ ValueError:
263
+ If any score is not type float and of length 1
264
+
265
+ Example:
266
+ >>> from torch import tensor
267
+ >>> from torchmetrics.detection import MeanAveragePrecision
268
+ >>> preds = [
269
+ ... dict(
270
+ ... boxes=tensor([[258.0, 41.0, 606.0, 285.0]]),
271
+ ... scores=tensor([0.536]),
272
+ ... labels=tensor([0]),
273
+ ... )
274
+ ... ]
275
+ >>> target = [
276
+ ... dict(
277
+ ... boxes=tensor([[214.0, 41.0, 562.0, 285.0]]),
278
+ ... labels=tensor([0]),
279
+ ... )
280
+ ... ]
281
+ >>> metric = MeanAveragePrecision()
282
+ >>> metric.update(preds, target)
283
+ >>> from pprint import pprint
284
+ >>> pprint(metric.compute())
285
+ {'classes': tensor(0, dtype=torch.int32),
286
+ 'map': tensor(0.6000),
287
+ 'map_50': tensor(1.),
288
+ 'map_75': tensor(1.),
289
+ 'map_large': tensor(0.6000),
290
+ 'map_medium': tensor(-1.),
291
+ 'map_per_class': tensor(-1.),
292
+ 'map_small': tensor(-1.),
293
+ 'mar_1': tensor(0.6000),
294
+ 'mar_10': tensor(0.6000),
295
+ 'mar_100': tensor(0.6000),
296
+ 'mar_100_per_class': tensor(-1.),
297
+ 'mar_large': tensor(0.6000),
298
+ 'mar_medium': tensor(-1.),
299
+ 'mar_small': tensor(-1.)}
300
+
301
+ """
302
+
303
+ is_differentiable: bool = False
304
+ higher_is_better: Optional[bool] = True
305
+ full_state_update: bool = True
306
+ plot_lower_bound: float = 0.0
307
+ plot_upper_bound: float = 1.0
308
+
309
+ detections: List[Tensor]
310
+ detection_scores: List[Tensor]
311
+ detection_labels: List[Tensor]
312
+ groundtruths: List[Tensor]
313
+ groundtruth_labels: List[Tensor]
314
+
315
+ def __init__(
316
+ self,
317
+ box_format: str = "xyxy",
318
+ iou_type: Literal["bbox", "segm"] = "bbox",
319
+ iou_thresholds: Optional[list[float]] = None,
320
+ rec_thresholds: Optional[list[float]] = None,
321
+ max_detection_thresholds: Optional[list[int]] = None,
322
+ class_metrics: bool = False,
323
+ **kwargs: Any,
324
+ ) -> None:
325
+ super().__init__(**kwargs)
326
+ if not _PYCOCOTOOLS_AVAILABLE:
327
+ raise ModuleNotFoundError(
328
+ "`MAP` metric requires that `pycocotools` installed."
329
+ " Please install with `pip install pycocotools` or `pip install torchmetrics[detection]`"
330
+ )
331
+ if not _TORCHVISION_AVAILABLE:
332
+ raise ModuleNotFoundError(
333
+ "`MeanAveragePrecision` metric requires that `torchvision` is installed."
334
+ " Please install with `pip install torchmetrics[detection]`."
335
+ )
336
+
337
+ allowed_box_formats = ("xyxy", "xywh", "cxcywh")
338
+ allowed_iou_types = ("segm", "bbox")
339
+ if box_format not in allowed_box_formats:
340
+ raise ValueError(f"Expected argument `box_format` to be one of {allowed_box_formats} but got {box_format}")
341
+ self.box_format = box_format
342
+ self.iou_thresholds = iou_thresholds or torch.linspace(0.5, 0.95, round((0.95 - 0.5) / 0.05) + 1).tolist()
343
+ self.rec_thresholds = rec_thresholds or torch.linspace(0.0, 1.00, round(1.00 / 0.01) + 1).tolist()
344
+ max_det_threshold, _ = torch.sort(IntTensor(max_detection_thresholds or [1, 10, 100]))
345
+ self.max_detection_thresholds = max_det_threshold.tolist()
346
+ if iou_type not in allowed_iou_types:
347
+ raise ValueError(f"Expected argument `iou_type` to be one of {allowed_iou_types} but got {iou_type}")
348
+ if iou_type == "segm" and not _PYCOCOTOOLS_AVAILABLE:
349
+ raise ModuleNotFoundError("When `iou_type` is set to 'segm', pycocotools need to be installed")
350
+ self.iou_type = iou_type
351
+ self.bbox_area_ranges = {
352
+ "all": (float(0**2), float(1e5**2)),
353
+ "small": (float(0**2), float(32**2)),
354
+ "medium": (float(32**2), float(96**2)),
355
+ "large": (float(96**2), float(1e5**2)),
356
+ }
357
+
358
+ if not isinstance(class_metrics, bool):
359
+ raise ValueError("Expected argument `class_metrics` to be a boolean")
360
+
361
+ self.class_metrics = class_metrics
362
+ self.add_state("detections", default=[], dist_reduce_fx=None)
363
+ self.add_state("detection_scores", default=[], dist_reduce_fx=None)
364
+ self.add_state("detection_labels", default=[], dist_reduce_fx=None)
365
+ self.add_state("groundtruths", default=[], dist_reduce_fx=None)
366
+ self.add_state("groundtruth_labels", default=[], dist_reduce_fx=None)
367
+
368
+ def update(self, preds: list[dict[str, Tensor]], target: list[dict[str, Tensor]]) -> None:
369
+ """Update state with predictions and targets."""
370
+ _input_validator(preds, target, iou_type=self.iou_type)
371
+
372
+ for item in preds:
373
+ detections = self._get_safe_item_values(item)
374
+
375
+ self.detections.append(detections) # type: ignore[arg-type]
376
+ self.detection_labels.append(item["labels"])
377
+ self.detection_scores.append(item["scores"])
378
+
379
+ for item in target:
380
+ groundtruths = self._get_safe_item_values(item)
381
+ self.groundtruths.append(groundtruths) # type: ignore[arg-type]
382
+ self.groundtruth_labels.append(item["labels"])
383
+
384
+ def _move_list_states_to_cpu(self) -> None:
385
+ """Move list states to cpu to save GPU memory."""
386
+ for key in self._defaults:
387
+ current_val = getattr(self, key)
388
+ current_to_cpu = []
389
+ if isinstance(current_val, Sequence):
390
+ for cur_v in current_val:
391
+ # Cannot handle RLE as Tensor
392
+ if not isinstance(cur_v, tuple):
393
+ cur_v = cur_v.to("cpu")
394
+ current_to_cpu.append(cur_v)
395
+ setattr(self, key, current_to_cpu)
396
+
397
+ def _get_safe_item_values(self, item: dict[str, Any]) -> Union[Tensor, tuple]:
398
+ import pycocotools.mask as mask_utils
399
+ from torchvision.ops import box_convert
400
+
401
+ if self.iou_type == "bbox":
402
+ boxes = _fix_empty_tensors(item["boxes"])
403
+ if boxes.numel() > 0:
404
+ boxes = box_convert(boxes, in_fmt=self.box_format, out_fmt="xyxy")
405
+ return boxes
406
+ if self.iou_type == "segm":
407
+ masks = []
408
+ for i in item["masks"].cpu().numpy():
409
+ rle = mask_utils.encode(np.asfortranarray(i))
410
+ masks.append((tuple(rle["size"]), rle["counts"]))
411
+ return tuple(masks)
412
+ raise Exception(f"IOU type {self.iou_type} is not supported")
413
+
414
+ def _get_classes(self) -> list:
415
+ """Return a list of unique classes found in ground truth and detection data."""
416
+ if len(self.detection_labels) > 0 or len(self.groundtruth_labels) > 0:
417
+ return torch.cat(self.detection_labels + self.groundtruth_labels).unique().tolist()
418
+ return []
419
+
420
+ def _compute_iou(self, idx: int, class_id: int, max_det: int) -> Tensor:
421
+ """Compute the Intersection over Union (IoU) between bounding boxes for the given image and class.
422
+
423
+ Args:
424
+ idx:
425
+ Image Id, equivalent to the index of supplied samples
426
+ class_id:
427
+ Class Id of the supplied ground truth and detection labels
428
+ max_det:
429
+ Maximum number of evaluated detection bounding boxes
430
+
431
+ """
432
+ # if self.iou_type == "bbox":
433
+ gt = self.groundtruths[idx]
434
+ det = self.detections[idx]
435
+
436
+ gt_label_mask = (self.groundtruth_labels[idx] == class_id).nonzero().squeeze(1)
437
+ det_label_mask = (self.detection_labels[idx] == class_id).nonzero().squeeze(1)
438
+
439
+ if len(gt_label_mask) == 0 or len(det_label_mask) == 0:
440
+ return Tensor([])
441
+
442
+ gt = [gt[i] for i in gt_label_mask]
443
+ det = [det[i] for i in det_label_mask]
444
+
445
+ if len(gt) == 0 or len(det) == 0:
446
+ return Tensor([])
447
+
448
+ # Sort by scores and use only max detections
449
+ scores = self.detection_scores[idx]
450
+ scores_filtered = scores[self.detection_labels[idx] == class_id]
451
+ inds = torch.argsort(scores_filtered, descending=True)
452
+
453
+ # TODO Fix (only for masks is necessary)
454
+ det = [det[i] for i in inds]
455
+ if len(det) > max_det:
456
+ det = det[:max_det]
457
+
458
+ return compute_iou(det, gt, self.iou_type).to(self.device)
459
+
460
+ def __evaluate_image_gt_no_preds(
461
+ self, gt: Tensor, gt_label_mask: Tensor, area_range: tuple[int, int], num_iou_thrs: int
462
+ ) -> dict[str, Any]:
463
+ """Evaluate images with a ground truth but no predictions."""
464
+ # GTs
465
+ gt = [gt[i] for i in gt_label_mask]
466
+ num_gt = len(gt)
467
+ areas = compute_area(gt, iou_type=self.iou_type).to(self.device)
468
+ ignore_area = (areas < area_range[0]) | (areas > area_range[1])
469
+ gt_ignore, _ = torch.sort(ignore_area.to(torch.uint8))
470
+ gt_ignore = gt_ignore.to(torch.bool)
471
+
472
+ # Detections
473
+ num_det = 0
474
+ det_ignore = torch.zeros((num_iou_thrs, num_det), dtype=torch.bool, device=self.device)
475
+
476
+ return {
477
+ "dtMatches": torch.zeros((num_iou_thrs, num_det), dtype=torch.bool, device=self.device),
478
+ "gtMatches": torch.zeros((num_iou_thrs, num_gt), dtype=torch.bool, device=self.device),
479
+ "dtScores": torch.zeros(num_det, dtype=torch.float32, device=self.device),
480
+ "gtIgnore": gt_ignore,
481
+ "dtIgnore": det_ignore,
482
+ }
483
+
484
+ def __evaluate_image_preds_no_gt(
485
+ self,
486
+ det: Tensor,
487
+ idx: int,
488
+ det_label_mask: Tensor,
489
+ max_det: int,
490
+ area_range: tuple[int, int],
491
+ num_iou_thrs: int,
492
+ ) -> dict[str, Any]:
493
+ """Evaluate images with a prediction but no ground truth."""
494
+ # GTs
495
+ num_gt = 0
496
+
497
+ gt_ignore = torch.zeros(num_gt, dtype=torch.bool, device=self.device)
498
+
499
+ # Detections
500
+
501
+ det = [det[i] for i in det_label_mask]
502
+ scores = self.detection_scores[idx]
503
+ scores_filtered = scores[det_label_mask]
504
+ scores_sorted, dtind = torch.sort(scores_filtered, descending=True)
505
+
506
+ det = [det[i] for i in dtind]
507
+ if len(det) > max_det:
508
+ det = det[:max_det]
509
+ num_det = len(det)
510
+ det_areas = compute_area(det, iou_type=self.iou_type).to(self.device)
511
+ det_ignore_area = (det_areas < area_range[0]) | (det_areas > area_range[1])
512
+ ar = det_ignore_area.reshape((1, num_det))
513
+ det_ignore = torch.repeat_interleave(ar, num_iou_thrs, 0)
514
+
515
+ return {
516
+ "dtMatches": torch.zeros((num_iou_thrs, num_det), dtype=torch.bool, device=self.device),
517
+ "gtMatches": torch.zeros((num_iou_thrs, num_gt), dtype=torch.bool, device=self.device),
518
+ "dtScores": scores_sorted.to(self.device),
519
+ "gtIgnore": gt_ignore.to(self.device),
520
+ "dtIgnore": det_ignore.to(self.device),
521
+ }
522
+
523
+ def _evaluate_image(
524
+ self, idx: int, class_id: int, area_range: tuple[int, int], max_det: int, ious: dict
525
+ ) -> Optional[dict]:
526
+ """Perform evaluation for single class and image.
527
+
528
+ Args:
529
+ idx:
530
+ Image Id, equivalent to the index of supplied samples.
531
+ class_id:
532
+ Class Id of the supplied ground truth and detection labels.
533
+ area_range:
534
+ List of lower and upper bounding box area threshold.
535
+ max_det:
536
+ Maximum number of evaluated detection bounding boxes.
537
+ ious:
538
+ IoU results for image and class.
539
+
540
+ """
541
+ gt = self.groundtruths[idx]
542
+ det = self.detections[idx]
543
+ gt_label_mask = (self.groundtruth_labels[idx] == class_id).nonzero().squeeze(1)
544
+ det_label_mask = (self.detection_labels[idx] == class_id).nonzero().squeeze(1)
545
+
546
+ # No Gt and No predictions --> ignore image
547
+ if len(gt_label_mask) == 0 and len(det_label_mask) == 0:
548
+ return None
549
+
550
+ num_iou_thrs = len(self.iou_thresholds)
551
+
552
+ # Some GT but no predictions
553
+ if len(gt_label_mask) > 0 and len(det_label_mask) == 0:
554
+ return self.__evaluate_image_gt_no_preds(gt, gt_label_mask, area_range, num_iou_thrs)
555
+
556
+ # Some predictions but no GT
557
+ if len(gt_label_mask) == 0 and len(det_label_mask) > 0:
558
+ return self.__evaluate_image_preds_no_gt(det, idx, det_label_mask, max_det, area_range, num_iou_thrs)
559
+
560
+ gt = [gt[i] for i in gt_label_mask]
561
+ det = [det[i] for i in det_label_mask]
562
+ if len(gt) == 0 and len(det) == 0:
563
+ return None
564
+ if isinstance(det, dict):
565
+ det = [det]
566
+ if isinstance(gt, dict):
567
+ gt = [gt]
568
+
569
+ areas = compute_area(gt, iou_type=self.iou_type).to(self.device)
570
+
571
+ ignore_area = torch.logical_or(areas < area_range[0], areas > area_range[1])
572
+
573
+ # sort dt highest score first, sort gt ignore last
574
+ ignore_area_sorted, gtind = torch.sort(ignore_area.to(torch.uint8))
575
+ # Convert to uint8 temporarily and back to bool, because "Sort currently does not support bool dtype on CUDA"
576
+
577
+ ignore_area_sorted = ignore_area_sorted.to(torch.bool).to(self.device)
578
+
579
+ gt = [gt[i] for i in gtind]
580
+ scores = self.detection_scores[idx]
581
+ scores_filtered = scores[det_label_mask]
582
+ scores_sorted, dtind = torch.sort(scores_filtered, descending=True)
583
+ det = [det[i] for i in dtind]
584
+ if len(det) > max_det:
585
+ det = det[:max_det]
586
+ # load computed ious
587
+ ious = ious[idx, class_id][:, gtind] if len(ious[idx, class_id]) > 0 else ious[idx, class_id]
588
+
589
+ num_iou_thrs = len(self.iou_thresholds)
590
+ num_gt = len(gt)
591
+ num_det = len(det)
592
+ gt_matches = torch.zeros((num_iou_thrs, num_gt), dtype=torch.bool, device=self.device)
593
+ det_matches = torch.zeros((num_iou_thrs, num_det), dtype=torch.bool, device=self.device)
594
+ gt_ignore = ignore_area_sorted
595
+ det_ignore = torch.zeros((num_iou_thrs, num_det), dtype=torch.bool, device=self.device)
596
+
597
+ if torch.numel(ious) > 0:
598
+ for idx_iou, t in enumerate(self.iou_thresholds):
599
+ for idx_det, _ in enumerate(det):
600
+ m = MeanAveragePrecision._find_best_gt_match(t, gt_matches, idx_iou, gt_ignore, ious, idx_det)
601
+ if m == -1:
602
+ continue
603
+ det_ignore[idx_iou, idx_det] = gt_ignore[m]
604
+ det_matches[idx_iou, idx_det] = 1
605
+ gt_matches[idx_iou, m] = 1
606
+
607
+ # set unmatched detections outside of area range to ignore
608
+ det_areas = compute_area(det, iou_type=self.iou_type).to(self.device)
609
+ det_ignore_area = (det_areas < area_range[0]) | (det_areas > area_range[1])
610
+ ar = det_ignore_area.reshape((1, num_det))
611
+ det_ignore = torch.logical_or(
612
+ det_ignore, torch.logical_and(det_matches == 0, torch.repeat_interleave(ar, num_iou_thrs, 0))
613
+ )
614
+
615
+ return {
616
+ "dtMatches": det_matches.to(self.device),
617
+ "gtMatches": gt_matches.to(self.device),
618
+ "dtScores": scores_sorted.to(self.device),
619
+ "gtIgnore": gt_ignore.to(self.device),
620
+ "dtIgnore": det_ignore.to(self.device),
621
+ }
622
+
623
+ @staticmethod
624
+ def _find_best_gt_match(
625
+ threshold: int, gt_matches: Tensor, idx_iou: float, gt_ignore: Tensor, ious: Tensor, idx_det: int
626
+ ) -> int:
627
+ """Return id of best ground truth match with current detection.
628
+
629
+ Args:
630
+ threshold:
631
+ Current threshold value.
632
+ gt_matches:
633
+ Tensor showing if a ground truth matches for threshold ``t`` exists.
634
+ idx_iou:
635
+ Id of threshold ``t``.
636
+ gt_ignore:
637
+ Tensor showing if ground truth should be ignored.
638
+ ious:
639
+ IoUs for all combinations of detection and ground truth.
640
+ idx_det:
641
+ Id of current detection.
642
+
643
+ """
644
+ previously_matched = gt_matches[idx_iou] # type: ignore[index]
645
+ # Remove previously matched or ignored gts
646
+ remove_mask = previously_matched | gt_ignore
647
+ gt_ious = ious[idx_det] * ~remove_mask
648
+ match_idx = gt_ious.argmax().item()
649
+ if gt_ious[match_idx] > threshold: # type: ignore[index]
650
+ return match_idx # type: ignore[return-value]
651
+ return -1
652
+
653
+ def _summarize(
654
+ self,
655
+ results: dict,
656
+ avg_prec: bool = True,
657
+ iou_threshold: Optional[float] = None,
658
+ area_range: str = "all",
659
+ max_dets: int = 100,
660
+ ) -> Tensor:
661
+ """Perform evaluation for single class and image.
662
+
663
+ Args:
664
+ results:
665
+ Dictionary including precision, recall and scores for all combinations.
666
+ avg_prec:
667
+ Calculate average precision. Else calculate average recall.
668
+ iou_threshold:
669
+ IoU threshold. If set to ``None`` it all values are used. Else results are filtered.
670
+ area_range:
671
+ Bounding box area range key.
672
+ max_dets:
673
+ Maximum detections.
674
+
675
+ """
676
+ area_inds = [i for i, k in enumerate(self.bbox_area_ranges.keys()) if k == area_range]
677
+ mdet_inds = [i for i, k in enumerate(self.max_detection_thresholds) if k == max_dets]
678
+ if avg_prec:
679
+ # dimension of precision: [TxRxKxAxM]
680
+ prec = results["precision"]
681
+ # IoU
682
+ if iou_threshold is not None:
683
+ threshold = self.iou_thresholds.index(iou_threshold)
684
+ prec = prec[threshold, :, :, area_inds, mdet_inds]
685
+ else:
686
+ prec = prec[:, :, :, area_inds, mdet_inds]
687
+ else:
688
+ # dimension of recall: [TxKxAxM]
689
+ prec = results["recall"]
690
+ if iou_threshold is not None:
691
+ threshold = self.iou_thresholds.index(iou_threshold)
692
+ prec = prec[threshold, :, :, area_inds, mdet_inds]
693
+ else:
694
+ prec = prec[:, :, area_inds, mdet_inds]
695
+
696
+ return torch.tensor([-1.0]) if len(prec[prec > -1]) == 0 else torch.mean(prec[prec > -1])
697
+
698
+ def _calculate(self, class_ids: list) -> tuple[MAPMetricResults, MARMetricResults]:
699
+ """Calculate the precision and recall for all supplied classes to calculate mAP/mAR.
700
+
701
+ Args:
702
+ class_ids:
703
+ List of label class Ids.
704
+
705
+ """
706
+ img_ids = range(len(self.groundtruths))
707
+ max_detections = self.max_detection_thresholds[-1]
708
+ area_ranges = self.bbox_area_ranges.values()
709
+
710
+ ious = {
711
+ (idx, class_id): self._compute_iou(idx, class_id, max_detections)
712
+ for idx in img_ids
713
+ for class_id in class_ids
714
+ }
715
+
716
+ eval_imgs = [
717
+ self._evaluate_image(img_id, class_id, area, max_detections, ious) # type: ignore[arg-type]
718
+ for class_id in class_ids
719
+ for area in area_ranges
720
+ for img_id in img_ids
721
+ ]
722
+
723
+ num_iou_thrs = len(self.iou_thresholds)
724
+ num_rec_thrs = len(self.rec_thresholds)
725
+ num_classes = len(class_ids)
726
+ num_bbox_areas = len(self.bbox_area_ranges)
727
+ num_max_det_thresholds = len(self.max_detection_thresholds)
728
+ num_imgs = len(img_ids)
729
+ precision = -torch.ones((num_iou_thrs, num_rec_thrs, num_classes, num_bbox_areas, num_max_det_thresholds))
730
+ recall = -torch.ones((num_iou_thrs, num_classes, num_bbox_areas, num_max_det_thresholds))
731
+ scores = -torch.ones((num_iou_thrs, num_rec_thrs, num_classes, num_bbox_areas, num_max_det_thresholds))
732
+
733
+ # move tensors if necessary
734
+ rec_thresholds_tensor = torch.tensor(self.rec_thresholds)
735
+
736
+ # retrieve E at each category, area range, and max number of detections
737
+ for idx_cls, _ in enumerate(class_ids):
738
+ for idx_bbox_area, _ in enumerate(self.bbox_area_ranges):
739
+ for idx_max_det_thresholds, max_det in enumerate(self.max_detection_thresholds):
740
+ recall, precision, scores = MeanAveragePrecision.__calculate_recall_precision_scores(
741
+ recall,
742
+ precision,
743
+ scores,
744
+ idx_cls=idx_cls,
745
+ idx_bbox_area=idx_bbox_area,
746
+ idx_max_det_thresholds=idx_max_det_thresholds,
747
+ eval_imgs=eval_imgs,
748
+ rec_thresholds=rec_thresholds_tensor,
749
+ max_det=max_det,
750
+ num_imgs=num_imgs,
751
+ num_bbox_areas=num_bbox_areas,
752
+ )
753
+
754
+ return precision, recall # type: ignore[return-value]
755
+
756
+ def _summarize_results(self, precisions: Tensor, recalls: Tensor) -> tuple[MAPMetricResults, MARMetricResults]:
757
+ """Summarizes the precision and recall values to calculate mAP/mAR.
758
+
759
+ Args:
760
+ precisions:
761
+ Precision values for different thresholds
762
+ recalls:
763
+ Recall values for different thresholds
764
+
765
+ """
766
+ results = {"precision": precisions, "recall": recalls}
767
+ map_metrics = MAPMetricResults()
768
+ last_max_det_threshold = self.max_detection_thresholds[-1]
769
+ map_metrics.map = self._summarize(results, True, max_dets=last_max_det_threshold)
770
+ if 0.5 in self.iou_thresholds:
771
+ map_metrics.map_50 = self._summarize(results, True, iou_threshold=0.5, max_dets=last_max_det_threshold)
772
+ else:
773
+ map_metrics.map_50 = torch.tensor([-1])
774
+ if 0.75 in self.iou_thresholds:
775
+ map_metrics.map_75 = self._summarize(results, True, iou_threshold=0.75, max_dets=last_max_det_threshold)
776
+ else:
777
+ map_metrics.map_75 = torch.tensor([-1])
778
+ map_metrics.map_small = self._summarize(results, True, area_range="small", max_dets=last_max_det_threshold)
779
+ map_metrics.map_medium = self._summarize(results, True, area_range="medium", max_dets=last_max_det_threshold)
780
+ map_metrics.map_large = self._summarize(results, True, area_range="large", max_dets=last_max_det_threshold)
781
+
782
+ mar_metrics = MARMetricResults()
783
+ for max_det in self.max_detection_thresholds:
784
+ mar_metrics[f"mar_{max_det}"] = self._summarize(results, False, max_dets=max_det)
785
+ mar_metrics.mar_small = self._summarize(results, False, area_range="small", max_dets=last_max_det_threshold)
786
+ mar_metrics.mar_medium = self._summarize(results, False, area_range="medium", max_dets=last_max_det_threshold)
787
+ mar_metrics.mar_large = self._summarize(results, False, area_range="large", max_dets=last_max_det_threshold)
788
+
789
+ return map_metrics, mar_metrics
790
+
791
+ @staticmethod
792
+ def __calculate_recall_precision_scores(
793
+ recall: Tensor,
794
+ precision: Tensor,
795
+ scores: Tensor,
796
+ idx_cls: int,
797
+ idx_bbox_area: int,
798
+ idx_max_det_thresholds: int,
799
+ eval_imgs: list,
800
+ rec_thresholds: Tensor,
801
+ max_det: int,
802
+ num_imgs: int,
803
+ num_bbox_areas: int,
804
+ ) -> tuple[Tensor, Tensor, Tensor]:
805
+ num_rec_thrs = len(rec_thresholds)
806
+ idx_cls_pointer = idx_cls * num_bbox_areas * num_imgs
807
+ idx_bbox_area_pointer = idx_bbox_area * num_imgs
808
+ # Load all image evals for current class_id and area_range
809
+ img_eval_cls_bbox = [eval_imgs[idx_cls_pointer + idx_bbox_area_pointer + i] for i in range(num_imgs)]
810
+ img_eval_cls_bbox = [e for e in img_eval_cls_bbox if e is not None]
811
+ if not img_eval_cls_bbox:
812
+ return recall, precision, scores
813
+
814
+ det_scores = torch.cat([e["dtScores"][:max_det] for e in img_eval_cls_bbox])
815
+
816
+ # different sorting method generates slightly different results.
817
+ # mergesort is used to be consistent as Matlab implementation.
818
+ # Sort in PyTorch does not support bool types on CUDA (yet, 1.11.0)
819
+ dtype = torch.uint8 if det_scores.is_cuda and det_scores.dtype is torch.bool else det_scores.dtype
820
+ # Explicitly cast to uint8 to avoid error for bool inputs on CUDA to argsort
821
+ inds = torch.argsort(det_scores.to(dtype), descending=True)
822
+ det_scores_sorted = det_scores[inds]
823
+
824
+ det_matches = torch.cat([e["dtMatches"][:, :max_det] for e in img_eval_cls_bbox], axis=1)[:, inds] # type: ignore[call-overload]
825
+ det_ignore = torch.cat([e["dtIgnore"][:, :max_det] for e in img_eval_cls_bbox], axis=1)[:, inds] # type: ignore[call-overload]
826
+ gt_ignore = torch.cat([e["gtIgnore"] for e in img_eval_cls_bbox])
827
+ npig = torch.count_nonzero(gt_ignore == False) # noqa: E712
828
+ if npig == 0:
829
+ return recall, precision, scores
830
+ tps = torch.logical_and(det_matches, torch.logical_not(det_ignore))
831
+ fps = torch.logical_and(torch.logical_not(det_matches), torch.logical_not(det_ignore))
832
+
833
+ tp_sum = _cumsum(tps, dim=1, dtype=torch.float)
834
+ fp_sum = _cumsum(fps, dim=1, dtype=torch.float)
835
+ for idx, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
836
+ tp_len = len(tp)
837
+ rc = tp / npig
838
+ pr = tp / (fp + tp + torch.finfo(torch.float64).eps)
839
+ prec = torch.zeros((num_rec_thrs,))
840
+ score = torch.zeros((num_rec_thrs,))
841
+
842
+ recall[idx, idx_cls, idx_bbox_area, idx_max_det_thresholds] = rc[-1] if tp_len else 0
843
+
844
+ # Remove zigzags for AUC
845
+ diff_zero = torch.zeros((1,), device=pr.device)
846
+ diff = torch.ones((1,), device=pr.device)
847
+ while not torch.all(diff == 0):
848
+ diff = torch.clamp(torch.cat(((pr[1:] - pr[:-1]), diff_zero), 0), min=0)
849
+ pr += diff
850
+
851
+ inds = torch.searchsorted(rc, rec_thresholds.to(rc.device), right=False)
852
+ num_inds = inds.argmax() if inds.max() >= tp_len else num_rec_thrs
853
+ inds = inds[:num_inds]
854
+ prec[:num_inds] = pr[inds]
855
+ score[:num_inds] = det_scores_sorted[inds]
856
+ precision[idx, :, idx_cls, idx_bbox_area, idx_max_det_thresholds] = prec
857
+ scores[idx, :, idx_cls, idx_bbox_area, idx_max_det_thresholds] = score
858
+
859
+ return recall, precision, scores
860
+
861
+ def compute(self) -> dict:
862
+ """Compute metric."""
863
+ classes = self._get_classes()
864
+ precisions, recalls = self._calculate(classes)
865
+ map_val, mar_val = self._summarize_results(precisions, recalls) # type: ignore[arg-type]
866
+
867
+ # if class mode is enabled, evaluate metrics per class
868
+ map_per_class_values: Tensor = torch.tensor([-1.0])
869
+ mar_max_dets_per_class_values: Tensor = torch.tensor([-1.0])
870
+ if self.class_metrics:
871
+ map_per_class_list = []
872
+ mar_max_dets_per_class_list = []
873
+
874
+ for class_idx, _ in enumerate(classes):
875
+ cls_precisions = precisions[:, :, class_idx].unsqueeze(dim=2)
876
+ cls_recalls = recalls[:, class_idx].unsqueeze(dim=1)
877
+ cls_map, cls_mar = self._summarize_results(cls_precisions, cls_recalls)
878
+ map_per_class_list.append(cls_map.map)
879
+ mar_max_dets_per_class_list.append(cls_mar[f"mar_{self.max_detection_thresholds[-1]}"])
880
+
881
+ map_per_class_values = torch.tensor(map_per_class_list, dtype=torch.float)
882
+ mar_max_dets_per_class_values = torch.tensor(mar_max_dets_per_class_list, dtype=torch.float)
883
+
884
+ metrics = COCOMetricResults()
885
+ metrics.update(map_val)
886
+ metrics.update(mar_val)
887
+ metrics.map_per_class = map_per_class_values
888
+ metrics[f"mar_{self.max_detection_thresholds[-1]}_per_class"] = mar_max_dets_per_class_values
889
+ metrics.classes = torch.tensor(classes, dtype=torch.int)
890
+ return metrics
891
+
892
+ def _apply(self, fn: Callable) -> torch.nn.Module: # type: ignore[override]
893
+ """Custom apply function.
894
+
895
+ Excludes the detections and groundtruths from the casting when the iou_type is set to `segm` as the state is
896
+ no longer a tensor but a tuple.
897
+
898
+ """
899
+ if self.iou_type == "segm":
900
+ this = super()._apply(fn, exclude_state=("detections", "groundtruths"))
901
+ else:
902
+ this = super()._apply(fn)
903
+ return this
904
+
905
+ def _sync_dist(self, dist_sync_fn: Optional[Callable] = None, process_group: Optional[Any] = None) -> None:
906
+ """Custom sync function.
907
+
908
+ For the iou_type `segm` the detections and groundtruths are no longer tensors but tuples. Therefore, we need
909
+ to gather the list of tuples and then convert it back to a list of tuples.
910
+
911
+ """
912
+ super()._sync_dist(dist_sync_fn=dist_sync_fn, process_group=process_group) # type: ignore[arg-type]
913
+
914
+ if self.iou_type == "segm":
915
+ self.detections = self._gather_tuple_list(self.detections, process_group) # type: ignore[arg-type]
916
+ self.groundtruths = self._gather_tuple_list(self.groundtruths, process_group) # type: ignore[arg-type]
917
+
918
+ @staticmethod
919
+ def _gather_tuple_list(
920
+ list_to_gather: list[Union[tuple, Tensor]], process_group: Optional[Any] = None
921
+ ) -> list[Any]:
922
+ """Gather a list of tuples over multiple devices."""
923
+ world_size = dist.get_world_size(group=process_group)
924
+ dist.barrier(group=process_group)
925
+
926
+ list_gathered = [None for _ in range(world_size)]
927
+ dist.all_gather_object(list_gathered, list_to_gather, group=process_group)
928
+
929
+ return [list_gathered[rank][idx] for idx in range(len(list_gathered[0])) for rank in range(world_size)] # type: ignore[arg-type,index]
930
+
931
+ def plot(
932
+ self, val: Optional[Union[dict[str, Tensor], Sequence[dict[str, Tensor]]]] = None, ax: Optional[_AX_TYPE] = None
933
+ ) -> _PLOT_OUT_TYPE:
934
+ """Plot a single or multiple values from the metric.
935
+
936
+ Args:
937
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
938
+ If no value is provided, will automatically call `metric.compute` and plot that result.
939
+ ax: An matplotlib axis object. If provided will add plot to that axis
940
+
941
+ Returns:
942
+ Figure object and Axes object
943
+
944
+ Raises:
945
+ ModuleNotFoundError:
946
+ If `matplotlib` is not installed
947
+
948
+ .. plot::
949
+ :scale: 75
950
+
951
+ >>> from torch import tensor
952
+ >>> from torchmetrics.detection.mean_ap import MeanAveragePrecision
953
+ >>> preds = [dict(
954
+ ... boxes=tensor([[258.0, 41.0, 606.0, 285.0]]),
955
+ ... scores=tensor([0.536]),
956
+ ... labels=tensor([0]),
957
+ ... )]
958
+ >>> target = [dict(
959
+ ... boxes=tensor([[214.0, 41.0, 562.0, 285.0]]),
960
+ ... labels=tensor([0]),
961
+ ... )]
962
+ >>> metric = MeanAveragePrecision()
963
+ >>> metric.update(preds, target)
964
+ >>> fig_, ax_ = metric.plot()
965
+
966
+ .. plot::
967
+ :scale: 75
968
+
969
+ >>> # Example plotting multiple values
970
+ >>> import torch
971
+ >>> from torchmetrics.detection.mean_ap import MeanAveragePrecision
972
+ >>> preds = lambda: [dict(
973
+ ... boxes=torch.tensor([[258.0, 41.0, 606.0, 285.0]]) + torch.randint(10, (1,4)),
974
+ ... scores=torch.tensor([0.536]) + 0.1*torch.rand(1),
975
+ ... labels=torch.tensor([0]),
976
+ ... )]
977
+ >>> target = [dict(
978
+ ... boxes=torch.tensor([[214.0, 41.0, 562.0, 285.0]]),
979
+ ... labels=torch.tensor([0]),
980
+ ... )]
981
+ >>> metric = MeanAveragePrecision()
982
+ >>> vals = []
983
+ >>> for _ in range(20):
984
+ ... vals.append(metric(preds(), target))
985
+ >>> fig_, ax_ = metric.plot(vals)
986
+
987
+ """
988
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/detection/diou.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The PyTorch Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.detection.iou import IntersectionOverUnion
20
+ from torchmetrics.functional.detection.diou import _diou_compute, _diou_update
21
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE, _TORCHVISION_AVAILABLE
22
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
23
+
24
+ if not _TORCHVISION_AVAILABLE:
25
+ __doctest_skip__ = ["DistanceIntersectionOverUnion", "DistanceIntersectionOverUnion.plot"]
26
+ elif not _MATPLOTLIB_AVAILABLE:
27
+ __doctest_skip__ = ["DistanceIntersectionOverUnion.plot"]
28
+
29
+
30
+ class DistanceIntersectionOverUnion(IntersectionOverUnion):
31
+ r"""Computes Distance Intersection Over Union (`DIoU`_).
32
+
33
+ As input to ``forward`` and ``update`` the metric accepts the following input:
34
+
35
+ - ``preds`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
36
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
37
+
38
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes``
39
+ detection boxes of the format specified in the constructor.
40
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
41
+ - ``labels`` (:class:`~torch.Tensor`): integer tensor of shape ``(num_boxes)`` containing 0-indexed detection
42
+ classes for the boxes.
43
+
44
+ - ``target`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
45
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
46
+
47
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes`` ground
48
+ truth boxes of the format specified in the constructor.
49
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
50
+ - ``labels`` (:class:`~torch.Tensor`): integer tensor of shape ``(num_boxes)`` containing 0-indexed ground truth
51
+ classes for the boxes.
52
+
53
+ As output of ``forward`` and ``compute`` the metric returns the following output:
54
+
55
+ - ``diou_dict``: A dictionary containing the following key-values:
56
+
57
+ - diou: (:class:`~torch.Tensor`) with overall diou value over all classes and samples.
58
+ - diou/cl_{cl}: (:class:`~torch.Tensor`), if argument ``class_metrics=True``
59
+
60
+ Args:
61
+ box_format:
62
+ Input format of given boxes. Supported formats are ``['xyxy', 'xywh', 'cxcywh']``.
63
+ iou_thresholds:
64
+ Optional IoU thresholds for evaluation. If set to `None` the threshold is ignored.
65
+ class_metrics:
66
+ Option to enable per-class metrics for IoU. Has a performance impact.
67
+ respect_labels:
68
+ Ignore values from boxes that do not have the same label as the ground truth box. Else will compute Iou
69
+ between all pairs of boxes.
70
+ kwargs:
71
+ Additional keyword arguments, see :ref:`Metric kwargs` for more info.
72
+
73
+ Example:
74
+ >>> import torch
75
+ >>> from torchmetrics.detection import DistanceIntersectionOverUnion
76
+ >>> preds = [
77
+ ... {
78
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
79
+ ... "scores": torch.tensor([0.236, 0.56]),
80
+ ... "labels": torch.tensor([4, 5]),
81
+ ... }
82
+ ... ]
83
+ >>> target = [
84
+ ... {
85
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
86
+ ... "labels": torch.tensor([5]),
87
+ ... }
88
+ ... ]
89
+ >>> metric = DistanceIntersectionOverUnion()
90
+ >>> metric(preds, target)
91
+ {'diou': tensor(0.8611)}
92
+
93
+ Raises:
94
+ ModuleNotFoundError:
95
+ If torchvision is not installed with version 0.13.0 or newer.
96
+
97
+ """
98
+
99
+ is_differentiable: bool = False
100
+ higher_is_better: Optional[bool] = True
101
+ full_state_update: bool = True
102
+
103
+ _iou_type: str = "diou"
104
+ _invalid_val: float = -1.0
105
+
106
+ def __init__(
107
+ self,
108
+ box_format: str = "xyxy",
109
+ iou_threshold: Optional[float] = None,
110
+ class_metrics: bool = False,
111
+ respect_labels: bool = True,
112
+ **kwargs: Any,
113
+ ) -> None:
114
+ if not _TORCHVISION_AVAILABLE:
115
+ raise ModuleNotFoundError(
116
+ f"Metric `{self._iou_type.upper()}` requires that `torchvision` is installed."
117
+ " Please install with `pip install torchmetrics[detection]`."
118
+ )
119
+ super().__init__(box_format, iou_threshold, class_metrics, respect_labels, **kwargs)
120
+
121
+ @staticmethod
122
+ def _iou_update_fn(*args: Any, **kwargs: Any) -> Tensor:
123
+ return _diou_update(*args, **kwargs)
124
+
125
+ @staticmethod
126
+ def _iou_compute_fn(*args: Any, **kwargs: Any) -> Tensor:
127
+ return _diou_compute(*args, **kwargs)
128
+
129
+ def plot(
130
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
131
+ ) -> _PLOT_OUT_TYPE:
132
+ """Plot a single or multiple values from the metric.
133
+
134
+ Args:
135
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
136
+ If no value is provided, will automatically call `metric.compute` and plot that result.
137
+ ax: An matplotlib axis object. If provided will add plot to that axis
138
+
139
+ Returns:
140
+ Figure object and Axes object
141
+
142
+ Raises:
143
+ ModuleNotFoundError:
144
+ If `matplotlib` is not installed
145
+
146
+ .. plot::
147
+ :scale: 75
148
+
149
+ >>> # Example plotting single value
150
+ >>> import torch
151
+ >>> from torchmetrics.detection import DistanceIntersectionOverUnion
152
+ >>> preds = [
153
+ ... {
154
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
155
+ ... "scores": torch.tensor([0.236, 0.56]),
156
+ ... "labels": torch.tensor([4, 5]),
157
+ ... }
158
+ ... ]
159
+ >>> target = [
160
+ ... {
161
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
162
+ ... "labels": torch.tensor([5]),
163
+ ... }
164
+ ... ]
165
+ >>> metric = DistanceIntersectionOverUnion()
166
+ >>> metric.update(preds, target)
167
+ >>> fig_, ax_ = metric.plot()
168
+
169
+ .. plot::
170
+ :scale: 75
171
+
172
+ >>> # Example plotting multiple values
173
+ >>> import torch
174
+ >>> from torchmetrics.detection import DistanceIntersectionOverUnion
175
+ >>> preds = [
176
+ ... {
177
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
178
+ ... "scores": torch.tensor([0.236, 0.56]),
179
+ ... "labels": torch.tensor([4, 5]),
180
+ ... }
181
+ ... ]
182
+ >>> target = lambda : [
183
+ ... {
184
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]) + torch.randint(-10, 10, (1, 4)),
185
+ ... "labels": torch.tensor([5]),
186
+ ... }
187
+ ... ]
188
+ >>> metric = DistanceIntersectionOverUnion()
189
+ >>> vals = []
190
+ >>> for _ in range(20):
191
+ ... vals.append(metric(preds, target()))
192
+ >>> fig_, ax_ = metric.plot(vals)
193
+
194
+ """
195
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/detection/giou.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The PyTorch Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, Optional, Union
16
+
17
+ from torch import Tensor
18
+
19
+ from torchmetrics.detection.iou import IntersectionOverUnion
20
+ from torchmetrics.functional.detection.giou import _giou_compute, _giou_update
21
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE, _TORCHVISION_AVAILABLE
22
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
23
+
24
+ if not _TORCHVISION_AVAILABLE:
25
+ __doctest_skip__ = ["GeneralizedIntersectionOverUnion", "GeneralizedIntersectionOverUnion.plot"]
26
+ elif not _MATPLOTLIB_AVAILABLE:
27
+ __doctest_skip__ = ["GeneralizedIntersectionOverUnion.plot"]
28
+
29
+
30
+ class GeneralizedIntersectionOverUnion(IntersectionOverUnion):
31
+ r"""Compute Generalized Intersection Over Union (`GIoU`_).
32
+
33
+ As input to ``forward`` and ``update`` the metric accepts the following input:
34
+
35
+ - ``preds`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
36
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
37
+
38
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes``
39
+ detection boxes of the format specified in the constructor.
40
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
41
+ - ``labels`` (:class:`~torch.Tensor`): integer tensor of shape ``(num_boxes)`` containing 0-indexed detection
42
+ classes for the boxes.
43
+
44
+ - ``target`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
45
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
46
+
47
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes`` ground
48
+ truth boxes of the format specified in the constructor.
49
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
50
+ - ``labels`` (:class:`~torch.Tensor`): integer tensor of shape ``(num_boxes)`` containing 0-indexed ground truth
51
+ classes for the boxes.
52
+
53
+ As output of ``forward`` and ``compute`` the metric returns the following output:
54
+
55
+ - ``giou_dict``: A dictionary containing the following key-values:
56
+
57
+ - giou: (:class:`~torch.Tensor`) with overall giou value over all classes and samples.
58
+ - giou/cl_{cl}: (:class:`~torch.Tensor`), if argument ``class metrics=True``
59
+
60
+ Args:
61
+ box_format:
62
+ Input format of given boxes. Supported formats are ``[`xyxy`, `xywh`, `cxcywh`]``.
63
+ iou_thresholds:
64
+ Optional IoU thresholds for evaluation. If set to `None` the threshold is ignored.
65
+ class_metrics:
66
+ Option to enable per-class metrics for IoU. Has a performance impact.
67
+ respect_labels:
68
+ Ignore values from boxes that do not have the same label as the ground truth box. Else will compute Iou
69
+ between all pairs of boxes.
70
+ kwargs:
71
+ Additional keyword arguments, see :ref:`Metric kwargs` for more info.
72
+
73
+ Example:
74
+ >>> import torch
75
+ >>> from torchmetrics.detection import GeneralizedIntersectionOverUnion
76
+ >>> preds = [
77
+ ... {
78
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
79
+ ... "scores": torch.tensor([0.236, 0.56]),
80
+ ... "labels": torch.tensor([4, 5]),
81
+ ... }
82
+ ... ]
83
+ >>> target = [
84
+ ... {
85
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
86
+ ... "labels": torch.tensor([5]),
87
+ ... }
88
+ ... ]
89
+ >>> metric = GeneralizedIntersectionOverUnion()
90
+ >>> metric(preds, target)
91
+ {'giou': tensor(0.8613)}
92
+
93
+ Raises:
94
+ ModuleNotFoundError:
95
+ If torchvision is not installed with version 0.8.0 or newer.
96
+
97
+ """
98
+
99
+ is_differentiable: bool = False
100
+ higher_is_better: Optional[bool] = True
101
+ full_state_update: bool = True
102
+
103
+ _iou_type: str = "giou"
104
+ _invalid_val: float = -1.0
105
+
106
+ def __init__(
107
+ self,
108
+ box_format: str = "xyxy",
109
+ iou_threshold: Optional[float] = None,
110
+ class_metrics: bool = False,
111
+ respect_labels: bool = True,
112
+ **kwargs: Any,
113
+ ) -> None:
114
+ super().__init__(box_format, iou_threshold, class_metrics, respect_labels, **kwargs)
115
+
116
+ @staticmethod
117
+ def _iou_update_fn(*args: Any, **kwargs: Any) -> Tensor:
118
+ return _giou_update(*args, **kwargs)
119
+
120
+ @staticmethod
121
+ def _iou_compute_fn(*args: Any, **kwargs: Any) -> Tensor:
122
+ return _giou_compute(*args, **kwargs)
123
+
124
+ def plot(
125
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
126
+ ) -> _PLOT_OUT_TYPE:
127
+ """Plot a single or multiple values from the metric.
128
+
129
+ Args:
130
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
131
+ If no value is provided, will automatically call `metric.compute` and plot that result.
132
+ ax: An matplotlib axis object. If provided will add plot to that axis
133
+
134
+ Returns:
135
+ Figure object and Axes object
136
+
137
+ Raises:
138
+ ModuleNotFoundError:
139
+ If `matplotlib` is not installed
140
+
141
+ .. plot::
142
+ :scale: 75
143
+
144
+ >>> # Example plotting single value
145
+ >>> import torch
146
+ >>> from torchmetrics.detection import GeneralizedIntersectionOverUnion
147
+ >>> preds = [
148
+ ... {
149
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
150
+ ... "scores": torch.tensor([0.236, 0.56]),
151
+ ... "labels": torch.tensor([4, 5]),
152
+ ... }
153
+ ... ]
154
+ >>> target = [
155
+ ... {
156
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
157
+ ... "labels": torch.tensor([5]),
158
+ ... }
159
+ ... ]
160
+ >>> metric = GeneralizedIntersectionOverUnion()
161
+ >>> metric.update(preds, target)
162
+ >>> fig_, ax_ = metric.plot()
163
+
164
+ .. plot::
165
+ :scale: 75
166
+
167
+ >>> # Example plotting multiple values
168
+ >>> import torch
169
+ >>> from torchmetrics.detection import GeneralizedIntersectionOverUnion
170
+ >>> preds = [
171
+ ... {
172
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
173
+ ... "scores": torch.tensor([0.236, 0.56]),
174
+ ... "labels": torch.tensor([4, 5]),
175
+ ... }
176
+ ... ]
177
+ >>> target = lambda : [
178
+ ... {
179
+ ... "boxes": torch.tensor([[300.00, 100.00, 335.00, 150.00]]) + torch.randint(-10, 10, (1, 4)),
180
+ ... "labels": torch.tensor([5]),
181
+ ... }
182
+ ... ]
183
+ >>> metric = GeneralizedIntersectionOverUnion()
184
+ >>> vals = []
185
+ >>> for _ in range(20):
186
+ ... vals.append(metric(preds, target()))
187
+ >>> fig_, ax_ = metric.plot(vals)
188
+
189
+ """
190
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/detection/iou.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The PyTorch Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections.abc import Sequence
15
+ from typing import Any, List, Optional, Union
16
+
17
+ import torch
18
+ from torch import Tensor
19
+
20
+ from torchmetrics.detection.helpers import _fix_empty_tensors, _input_validator
21
+ from torchmetrics.functional.detection.iou import _iou_compute, _iou_update
22
+ from torchmetrics.metric import Metric
23
+ from torchmetrics.utilities.data import dim_zero_cat
24
+ from torchmetrics.utilities.imports import _MATPLOTLIB_AVAILABLE, _TORCHVISION_AVAILABLE
25
+ from torchmetrics.utilities.plot import _AX_TYPE, _PLOT_OUT_TYPE
26
+
27
+ if not _TORCHVISION_AVAILABLE:
28
+ __doctest_skip__ = ["IntersectionOverUnion", "IntersectionOverUnion.plot"]
29
+ elif not _MATPLOTLIB_AVAILABLE:
30
+ __doctest_skip__ = ["IntersectionOverUnion.plot"]
31
+
32
+
33
+ class IntersectionOverUnion(Metric):
34
+ r"""Computes Intersection Over Union (IoU).
35
+
36
+ As input to ``forward`` and ``update`` the metric accepts the following input:
37
+
38
+ - ``preds`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
39
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
40
+
41
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes``
42
+ detection boxes of the format specified in the constructor.
43
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
44
+ - labels: ``IntTensor`` of shape ``(num_boxes)`` containing 0-indexed detection classes for
45
+ the boxes.
46
+
47
+ - ``target`` (:class:`~List`): A list consisting of dictionaries each containing the key-values
48
+ (each dictionary corresponds to a single image). Parameters that should be provided per dict:
49
+
50
+ - ``boxes`` (:class:`~torch.Tensor`): float tensor of shape ``(num_boxes, 4)`` containing ``num_boxes`` ground
51
+ truth boxes of the format specified in the constructor.
52
+ By default, this method expects ``(xmin, ymin, xmax, ymax)`` in absolute image coordinates.
53
+ - ``labels`` (:class:`~torch.Tensor`): integer tensor of shape ``(num_boxes)`` containing 0-indexed ground truth
54
+ classes for the boxes.
55
+
56
+ As output of ``forward`` and ``compute`` the metric returns the following output:
57
+
58
+ - ``iou_dict``: A dictionary containing the following key-values:
59
+
60
+ - iou: (:class:`~torch.Tensor`)
61
+ - iou/cl_{cl}: (:class:`~torch.Tensor`), if argument ``class metrics=True``
62
+
63
+ Args:
64
+ box_format:
65
+ Input format of given boxes. Supported formats are ``[`xyxy`, `xywh`, `cxcywh`]``.
66
+ iou_thresholds:
67
+ Optional IoU thresholds for evaluation. If set to `None` the threshold is ignored.
68
+ class_metrics:
69
+ Option to enable per-class metrics for IoU. Has a performance impact.
70
+ respect_labels:
71
+ Ignore values from boxes that do not have the same label as the ground truth box. Else will compute Iou
72
+ between all pairs of boxes.
73
+ kwargs:
74
+ Additional keyword arguments, see :ref:`Metric kwargs` for more info.
75
+
76
+ Example::
77
+
78
+ >>> import torch
79
+ >>> from torchmetrics.detection import IntersectionOverUnion
80
+ >>> preds = [
81
+ ... {
82
+ ... "boxes": torch.tensor([
83
+ ... [296.55, 93.96, 314.97, 152.79],
84
+ ... [298.55, 98.96, 314.97, 151.79]]),
85
+ ... "labels": torch.tensor([4, 5]),
86
+ ... }
87
+ ... ]
88
+ >>> target = [
89
+ ... {
90
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
91
+ ... "labels": torch.tensor([5]),
92
+ ... }
93
+ ... ]
94
+ >>> metric = IntersectionOverUnion()
95
+ >>> metric(preds, target)
96
+ {'iou': tensor(0.8614)}
97
+
98
+ Example::
99
+
100
+ The metric can also return the score per class:
101
+
102
+ >>> import torch
103
+ >>> from torchmetrics.detection import IntersectionOverUnion
104
+ >>> preds = [
105
+ ... {
106
+ ... "boxes": torch.tensor([
107
+ ... [296.55, 93.96, 314.97, 152.79],
108
+ ... [298.55, 98.96, 314.97, 151.79]]),
109
+ ... "labels": torch.tensor([4, 5]),
110
+ ... }
111
+ ... ]
112
+ >>> target = [
113
+ ... {
114
+ ... "boxes": torch.tensor([
115
+ ... [300.00, 100.00, 315.00, 150.00],
116
+ ... [300.00, 100.00, 315.00, 150.00]
117
+ ... ]),
118
+ ... "labels": torch.tensor([4, 5]),
119
+ ... }
120
+ ... ]
121
+ >>> metric = IntersectionOverUnion(class_metrics=True)
122
+ >>> metric(preds, target)
123
+ {'iou': tensor(0.7756), 'iou/cl_4': tensor(0.6898), 'iou/cl_5': tensor(0.8614)}
124
+
125
+ Raises:
126
+ ModuleNotFoundError:
127
+ If torchvision is not installed with version 0.8.0 or newer.
128
+
129
+ """
130
+
131
+ is_differentiable: bool = False
132
+ higher_is_better: Optional[bool] = True
133
+ full_state_update: bool = True
134
+
135
+ groundtruth_labels: List[Tensor]
136
+ iou_matrix: List[Tensor]
137
+ _iou_type: str = "iou"
138
+ _invalid_val: float = -1.0
139
+
140
+ def __init__(
141
+ self,
142
+ box_format: str = "xyxy",
143
+ iou_threshold: Optional[float] = None,
144
+ class_metrics: bool = False,
145
+ respect_labels: bool = True,
146
+ **kwargs: Any,
147
+ ) -> None:
148
+ super().__init__(**kwargs)
149
+
150
+ if not _TORCHVISION_AVAILABLE:
151
+ raise ModuleNotFoundError(
152
+ f"Metric `{self._iou_type.upper()}` requires that `torchvision` is installed."
153
+ " Please install with `pip install torchmetrics[detection]`."
154
+ )
155
+
156
+ allowed_box_formats = ("xyxy", "xywh", "cxcywh")
157
+ if box_format not in allowed_box_formats:
158
+ raise ValueError(f"Expected argument `box_format` to be one of {allowed_box_formats} but got {box_format}")
159
+
160
+ self.box_format = box_format
161
+ self.iou_threshold = iou_threshold
162
+
163
+ if not isinstance(class_metrics, bool):
164
+ raise ValueError("Expected argument `class_metrics` to be a boolean")
165
+ self.class_metrics = class_metrics
166
+
167
+ if not isinstance(respect_labels, bool):
168
+ raise ValueError("Expected argument `respect_labels` to be a boolean")
169
+ self.respect_labels = respect_labels
170
+
171
+ self.add_state("groundtruth_labels", default=[], dist_reduce_fx=None)
172
+ self.add_state("iou_matrix", default=[], dist_reduce_fx=None)
173
+
174
+ @staticmethod
175
+ def _iou_update_fn(*args: Any, **kwargs: Any) -> Tensor:
176
+ return _iou_update(*args, **kwargs)
177
+
178
+ @staticmethod
179
+ def _iou_compute_fn(*args: Any, **kwargs: Any) -> Tensor:
180
+ return _iou_compute(*args, **kwargs)
181
+
182
+ def update(self, preds: list[dict[str, Tensor]], target: list[dict[str, Tensor]]) -> None:
183
+ """Update state with predictions and targets."""
184
+ _input_validator(preds, target, ignore_score=True)
185
+
186
+ for p_i, t_i in zip(preds, target):
187
+ det_boxes = self._get_safe_item_values(p_i["boxes"])
188
+ gt_boxes = self._get_safe_item_values(t_i["boxes"])
189
+ self.groundtruth_labels.append(t_i["labels"])
190
+
191
+ iou_matrix = self._iou_update_fn(det_boxes, gt_boxes, self.iou_threshold, self._invalid_val) # N x M
192
+ if self.respect_labels:
193
+ if det_boxes.numel() > 0 and gt_boxes.numel() > 0:
194
+ label_eq = p_i["labels"].unsqueeze(1) == t_i["labels"].unsqueeze(0) # N x M
195
+ else:
196
+ label_eq = torch.eye(iou_matrix.shape[0], dtype=bool, device=iou_matrix.device) # type: ignore[call-overload]
197
+ iou_matrix[~label_eq] = self._invalid_val
198
+ self.iou_matrix.append(iou_matrix)
199
+
200
+ def _get_safe_item_values(self, boxes: Tensor) -> Tensor:
201
+ from torchvision.ops import box_convert
202
+
203
+ boxes = _fix_empty_tensors(boxes)
204
+ if boxes.numel() > 0:
205
+ boxes = box_convert(boxes, in_fmt=self.box_format, out_fmt="xyxy")
206
+ return boxes
207
+
208
+ def _get_gt_classes(self) -> list:
209
+ """Returns a list of unique classes found in ground truth and detection data."""
210
+ if len(self.groundtruth_labels) > 0:
211
+ return torch.cat(self.groundtruth_labels).unique().tolist()
212
+ return []
213
+
214
+ def compute(self) -> dict:
215
+ """Computes IoU based on inputs passed in to ``update`` previously."""
216
+ score = torch.cat([mat[mat != self._invalid_val] for mat in self.iou_matrix], 0).mean()
217
+ results: dict[str, Tensor] = {f"{self._iou_type}": score}
218
+ if torch.isnan(score): # if no valid boxes are found
219
+ results[f"{self._iou_type}"] = torch.tensor(0.0, device=score.device)
220
+ if self.class_metrics:
221
+ gt_labels = dim_zero_cat(self.groundtruth_labels)
222
+ classes = gt_labels.unique().tolist() if len(gt_labels) > 0 else []
223
+ for cl in classes:
224
+ masked_iou, observed = torch.zeros_like(score), torch.zeros_like(score)
225
+ for mat, gt_lab in zip(self.iou_matrix, self.groundtruth_labels):
226
+ scores = mat[:, gt_lab == cl]
227
+ masked_iou += scores[scores != self._invalid_val].sum()
228
+ observed += scores[scores != self._invalid_val].numel()
229
+ results.update({f"{self._iou_type}/cl_{cl}": masked_iou / observed})
230
+ return results
231
+
232
+ def plot(
233
+ self, val: Optional[Union[Tensor, Sequence[Tensor]]] = None, ax: Optional[_AX_TYPE] = None
234
+ ) -> _PLOT_OUT_TYPE:
235
+ """Plot a single or multiple values from the metric.
236
+
237
+ Args:
238
+ val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
239
+ If no value is provided, will automatically call `metric.compute` and plot that result.
240
+ ax: An matplotlib axis object. If provided will add plot to that axis
241
+
242
+ Returns:
243
+ Figure object and Axes object
244
+
245
+ Raises:
246
+ ModuleNotFoundError:
247
+ If `matplotlib` is not installed
248
+
249
+ .. plot::
250
+ :scale: 75
251
+
252
+ >>> import torch
253
+ >>> from torchmetrics.detection import IntersectionOverUnion
254
+ >>> preds = [
255
+ ... {
256
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
257
+ ... "scores": torch.tensor([0.236, 0.56]),
258
+ ... "labels": torch.tensor([4, 5]),
259
+ ... }
260
+ ... ]
261
+ >>> target = [
262
+ ... {
263
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]),
264
+ ... "labels": torch.tensor([5]),
265
+ ... }
266
+ ... ]
267
+ >>> metric = IntersectionOverUnion()
268
+ >>> metric.update(preds, target)
269
+ >>> fig_, ax_ = metric.plot()
270
+
271
+ .. plot::
272
+ :scale: 75
273
+
274
+ >>> # Example plotting multiple values
275
+ >>> import torch
276
+ >>> from torchmetrics.detection import IntersectionOverUnion
277
+ >>> preds = [
278
+ ... {
279
+ ... "boxes": torch.tensor([[296.55, 93.96, 314.97, 152.79], [298.55, 98.96, 314.97, 151.79]]),
280
+ ... "scores": torch.tensor([0.236, 0.56]),
281
+ ... "labels": torch.tensor([4, 5]),
282
+ ... }
283
+ ... ]
284
+ >>> target = lambda : [
285
+ ... {
286
+ ... "boxes": torch.tensor([[300.00, 100.00, 315.00, 150.00]]) + torch.randint(-10, 10, (1, 4)),
287
+ ... "labels": torch.tensor([5]),
288
+ ... }
289
+ ... ]
290
+ >>> metric = IntersectionOverUnion()
291
+ >>> vals = []
292
+ >>> for _ in range(20):
293
+ ... vals.append(metric(preds, target()))
294
+ >>> fig_, ax_ = metric.plot(vals)
295
+
296
+ """
297
+ return self._plot(val, ax)
rtme/lib/python3.10/site-packages/torchmetrics/functional/image/d_lambda.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ import torch
17
+ from torch import Tensor
18
+ from typing_extensions import Literal
19
+
20
+ from torchmetrics.functional.image.uqi import universal_image_quality_index
21
+ from torchmetrics.utilities.distributed import reduce
22
+
23
+
24
+ def _spectral_distortion_index_update(preds: Tensor, target: Tensor) -> tuple[Tensor, Tensor]:
25
+ """Update and returns variables required to compute Spectral Distortion Index.
26
+
27
+ Args:
28
+ preds: Low resolution multispectral image
29
+ target: High resolution fused image
30
+
31
+ """
32
+ if preds.dtype != target.dtype:
33
+ raise TypeError(
34
+ f"Expected `ms` and `fused` to have the same data type. Got ms: {preds.dtype} and fused: {target.dtype}."
35
+ )
36
+ if len(preds.shape) != 4:
37
+ raise ValueError(
38
+ f"Expected `preds` and `target` to have BxCxHxW shape. Got preds: {preds.shape} and target: {target.shape}."
39
+ )
40
+ if preds.shape[:2] != target.shape[:2]:
41
+ raise ValueError(
42
+ "Expected `preds` and `target` to have same batch and channel sizes."
43
+ f"Got preds: {preds.shape} and target: {target.shape}."
44
+ )
45
+ return preds, target
46
+
47
+
48
+ def _spectral_distortion_index_compute(
49
+ preds: Tensor,
50
+ target: Tensor,
51
+ p: int = 1,
52
+ reduction: Literal["elementwise_mean", "sum", "none"] = "elementwise_mean",
53
+ ) -> Tensor:
54
+ """Compute Spectral Distortion Index (SpectralDistortionIndex_).
55
+
56
+ Args:
57
+ preds: Low resolution multispectral image
58
+ target: High resolution fused image
59
+ p: a parameter to emphasize large spectral difference
60
+ reduction: a method to reduce metric score over labels.
61
+
62
+ - ``'elementwise_mean'``: takes the mean (default)
63
+ - ``'sum'``: takes the sum
64
+ - ``'none'``: no reduction will be applied
65
+
66
+ Example:
67
+ >>> from torch import rand
68
+ >>> preds = rand([16, 3, 16, 16])
69
+ >>> target = rand([16, 3, 16, 16])
70
+ >>> preds, target = _spectral_distortion_index_update(preds, target)
71
+ >>> _spectral_distortion_index_compute(preds, target)
72
+ tensor(0.0234)
73
+
74
+ """
75
+ length = preds.shape[1]
76
+
77
+ m1 = torch.zeros((length, length), device=preds.device)
78
+ m2 = torch.zeros((length, length), device=preds.device)
79
+
80
+ for k in range(length):
81
+ num = length - (k + 1)
82
+ if num == 0:
83
+ continue
84
+ stack1 = target[:, k : k + 1, :, :].repeat(num, 1, 1, 1)
85
+ stack2 = torch.cat([target[:, r : r + 1, :, :] for r in range(k + 1, length)], dim=0)
86
+ score = [
87
+ s.mean() for s in universal_image_quality_index(stack1, stack2, reduction="none").split(preds.shape[0])
88
+ ]
89
+ m1[k, k + 1 :] = torch.stack(score, 0)
90
+
91
+ stack1 = preds[:, k : k + 1, :, :].repeat(num, 1, 1, 1)
92
+ stack2 = torch.cat([preds[:, r : r + 1, :, :] for r in range(k + 1, length)], dim=0)
93
+ score = [
94
+ s.mean() for s in universal_image_quality_index(stack1, stack2, reduction="none").split(preds.shape[0])
95
+ ]
96
+ m2[k, k + 1 :] = torch.stack(score, 0)
97
+ m1 = m1 + m1.T
98
+ m2 = m2 + m2.T
99
+
100
+ diff = torch.pow(torch.abs(m1 - m2), p)
101
+ # Special case: when number of channels (L) is 1, there will be only one element in M1 and M2. Hence no need to sum.
102
+ if length == 1:
103
+ output = torch.pow(diff, (1.0 / p))
104
+ else:
105
+ output = torch.pow(1.0 / (length * (length - 1)) * torch.sum(diff), (1.0 / p))
106
+ return reduce(output, reduction)
107
+
108
+
109
+ def spectral_distortion_index(
110
+ preds: Tensor,
111
+ target: Tensor,
112
+ p: int = 1,
113
+ reduction: Literal["elementwise_mean", "sum", "none"] = "elementwise_mean",
114
+ ) -> Tensor:
115
+ """Calculate `Spectral Distortion Index`_ (SpectralDistortionIndex_) also known as D_lambda.
116
+
117
+ Metric is used to compare the spectral distortion between two images.
118
+
119
+ Args:
120
+ preds: Low resolution multispectral image
121
+ target: High resolution fused image
122
+ p: Large spectral differences
123
+ reduction: a method to reduce metric score over labels.
124
+
125
+ - ``'elementwise_mean'``: takes the mean (default)
126
+ - ``'sum'``: takes the sum
127
+ - ``'none'``: no reduction will be applied
128
+
129
+ Return:
130
+ Tensor with SpectralDistortionIndex score
131
+
132
+ Raises:
133
+ TypeError:
134
+ If ``preds`` and ``target`` don't have the same data type.
135
+ ValueError:
136
+ If ``preds`` and ``target`` don't have ``BxCxHxW shape``.
137
+ ValueError:
138
+ If ``p`` is not a positive integer.
139
+
140
+ Example:
141
+ >>> from torch import rand
142
+ >>> from torchmetrics.functional.image import spectral_distortion_index
143
+ >>> preds = rand([16, 3, 16, 16])
144
+ >>> target = rand([16, 3, 16, 16])
145
+ >>> spectral_distortion_index(preds, target)
146
+ tensor(0.0234)
147
+
148
+ """
149
+ if not isinstance(p, int) or p <= 0:
150
+ raise ValueError(f"Expected `p` to be a positive integer. Got p: {p}.")
151
+ preds, target = _spectral_distortion_index_update(preds, target)
152
+ return _spectral_distortion_index_compute(preds, target, p, reduction)
rtme/lib/python3.10/site-packages/torchmetrics/functional/image/dists.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright The Lightning team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # Below is a derivative work based on the original work:
16
+ # https://github.com/dingkeyan93/DISTS
17
+ # with the following license:
18
+ #
19
+ # MIT License
20
+ # Copyright (c) 2020 Keyan Ding
21
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ # of this software and associated documentation files (the "Software"), to deal
23
+ # in the Software without restriction, including without limitation the rights
24
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ # copies of the Software, and to permit persons to whom the Software is
26
+ # furnished to do so, subject to the following conditions:
27
+ # The above copyright notice and this permission notice shall be included in all
28
+ # copies or substantial portions of the Software.
29
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ # SOFTWARE.
36
+ from pathlib import Path
37
+ from typing import List, Optional
38
+
39
+ import numpy as np
40
+ import torch
41
+ import torch.nn as nn
42
+ from torch import Tensor
43
+ from torch.nn.functional import conv2d
44
+ from typing_extensions import Literal
45
+
46
+ from torchmetrics.utilities.imports import _TORCHVISION_AVAILABLE
47
+
48
+ if not _TORCHVISION_AVAILABLE:
49
+ __doctest_skip__ = ["deep_image_structure_and_texture_similarity"]
50
+
51
+ _PATH_WEIGHT_DISTS = Path(__file__).resolve().parent / "dists_models" / "weights.pt"
52
+
53
+
54
+ class L2pooling(nn.Module):
55
+ """L2 pooling layer."""
56
+
57
+ filter: Tensor
58
+
59
+ def __init__(self, filter_size: int = 5, stride: int = 2, channels: int = 3) -> None:
60
+ super().__init__()
61
+ self.padding = (filter_size - 2) // 2
62
+ self.stride = stride
63
+ self.channels = channels
64
+ a = np.hanning(filter_size)[1:-1]
65
+ g = torch.Tensor(a[:, None] * a[None, :])
66
+ g = g / torch.sum(g)
67
+ self.register_buffer("filter", g[None, None, :, :].repeat(self.channels, 1, 1, 1))
68
+
69
+ def forward(self, tensor: Tensor) -> Tensor:
70
+ """Forward pass of the layer."""
71
+ tensor = tensor**2
72
+ out = conv2d(tensor, self.filter, stride=self.stride, padding=self.padding, groups=tensor.shape[1])
73
+ return (out + 1e-12).sqrt()
74
+
75
+
76
+ class DISTSNetwork(torch.nn.Module):
77
+ """DISTS network."""
78
+
79
+ alpha: Tensor
80
+ beta: Tensor
81
+ mean: Tensor
82
+ std: Tensor
83
+
84
+ def __init__(self, load_weights: bool = True) -> None:
85
+ super().__init__()
86
+
87
+ if _TORCHVISION_AVAILABLE:
88
+ from torchvision import models
89
+ else:
90
+ raise ModuleNotFoundError(
91
+ "DISTS requires torchvision to be installed. Please install it with `pip install torchvision`."
92
+ )
93
+
94
+ vgg_pretrained_features = models.vgg16(pretrained=True).features
95
+ self.stage1 = torch.nn.Sequential()
96
+ self.stage2 = torch.nn.Sequential()
97
+ self.stage3 = torch.nn.Sequential()
98
+ self.stage4 = torch.nn.Sequential()
99
+ self.stage5 = torch.nn.Sequential()
100
+ for x in range(4):
101
+ self.stage1.add_module(str(x), vgg_pretrained_features[x])
102
+ self.stage2.add_module(str(4), L2pooling(channels=64))
103
+ for x in range(5, 9):
104
+ self.stage2.add_module(str(x), vgg_pretrained_features[x])
105
+ self.stage3.add_module(str(9), L2pooling(channels=128))
106
+ for x in range(10, 16):
107
+ self.stage3.add_module(str(x), vgg_pretrained_features[x])
108
+ self.stage4.add_module(str(16), L2pooling(channels=256))
109
+ for x in range(17, 23):
110
+ self.stage4.add_module(str(x), vgg_pretrained_features[x])
111
+ self.stage5.add_module(str(23), L2pooling(channels=512))
112
+ for x in range(24, 30):
113
+ self.stage5.add_module(str(x), vgg_pretrained_features[x])
114
+
115
+ for param in self.parameters():
116
+ param.requires_grad = False
117
+
118
+ self.register_buffer("mean", torch.tensor([0.485, 0.456, 0.406]).view(1, -1, 1, 1))
119
+ self.register_buffer("std", torch.tensor([0.229, 0.224, 0.225]).view(1, -1, 1, 1))
120
+
121
+ self.chns = [3, 64, 128, 256, 512, 512]
122
+ self.register_parameter("alpha", nn.Parameter(torch.randn(1, sum(self.chns), 1, 1)))
123
+ self.register_parameter("beta", nn.Parameter(torch.randn(1, sum(self.chns), 1, 1)))
124
+ self.alpha.data.normal_(0.1, 0.01)
125
+ self.beta.data.normal_(0.1, 0.01)
126
+ if load_weights:
127
+ if not _PATH_WEIGHT_DISTS.exists():
128
+ raise FileNotFoundError(f"The weights file is not found in {_PATH_WEIGHT_DISTS}")
129
+ weights = torch.load(str(_PATH_WEIGHT_DISTS))
130
+ self.alpha.data = weights["alpha"]
131
+ self.beta.data = weights["beta"]
132
+
133
+ def forward_once(self, x: Tensor) -> List[Tensor]:
134
+ """Forward pass of the network."""
135
+ h = (x - self.mean) / self.std
136
+ h = self.stage1(h)
137
+ h_relu1_2 = h
138
+ h = self.stage2(h)
139
+ h_relu2_2 = h
140
+ h = self.stage3(h)
141
+ h_relu3_3 = h
142
+ h = self.stage4(h)
143
+ h_relu4_3 = h
144
+ h = self.stage5(h)
145
+ h_relu5_3 = h
146
+ return [x, h_relu1_2, h_relu2_2, h_relu3_3, h_relu4_3, h_relu5_3]
147
+
148
+ def forward(self, x: Tensor, y: Tensor, require_grad: bool = False) -> Tensor:
149
+ """Computes DISTS score between two images."""
150
+ if require_grad:
151
+ feats0 = self.forward_once(x)
152
+ feats1 = self.forward_once(y)
153
+ else:
154
+ with torch.inference_mode():
155
+ feats0 = self.forward_once(x)
156
+ feats1 = self.forward_once(y)
157
+ dist1, dist2, c1, c2 = 0, 0, 1e-6, 1e-6
158
+ w_sum = self.alpha.sum() + self.beta.sum()
159
+ alpha = torch.split(self.alpha / w_sum, self.chns, dim=1)
160
+ beta = torch.split(self.beta / w_sum, self.chns, dim=1)
161
+ for k in range(len(self.chns)):
162
+ x_mean = feats0[k].mean([2, 3], keepdim=True)
163
+ y_mean = feats1[k].mean([2, 3], keepdim=True)
164
+ s1 = (2 * x_mean * y_mean + c1) / (x_mean**2 + y_mean**2 + c1)
165
+ dist1 = dist1 + (alpha[k] * s1).sum(1, keepdim=True)
166
+
167
+ x_var = ((feats0[k] - x_mean) ** 2).mean([2, 3], keepdim=True)
168
+ y_var = ((feats1[k] - y_mean) ** 2).mean([2, 3], keepdim=True)
169
+ xy_cov = (feats0[k] * feats1[k]).mean([2, 3], keepdim=True) - x_mean * y_mean
170
+ s2 = (2 * xy_cov + c2) / (x_var + y_var + c2)
171
+ dist2 = dist2 + (beta[k] * s2).sum(1, keepdim=True)
172
+
173
+ return 1 - (dist1 + dist2).squeeze()
174
+
175
+
176
+ def _dists_update(preds: Tensor, target: Tensor) -> Tensor:
177
+ dists = DISTSNetwork().to(preds.device)
178
+ return dists(preds, target, require_grad=preds.requires_grad)
179
+
180
+
181
+ def _dists_compute(scores: Tensor, reduction: Optional[Literal["sum", "mean", "none"]]) -> Tensor:
182
+ if reduction == "sum":
183
+ return scores.sum()
184
+ if reduction == "mean":
185
+ return scores.mean()
186
+ if reduction is None or reduction == "none":
187
+ return scores
188
+ raise ValueError(f"Argument {reduction} is not valid. Choose 'sum', 'mean' or 'none'., but got {reduction}")
189
+
190
+
191
+ def deep_image_structure_and_texture_similarity(
192
+ preds: Tensor, target: Tensor, reduction: Optional[Literal["sum", "mean", "none"]] = None
193
+ ) -> Tensor:
194
+ """Calculates `Deep Image Structure and Texture Similarity`_ (DISTS) score.
195
+
196
+ Args:
197
+ preds: Predicted image tensor.
198
+ target: Target image tensor.
199
+ reduction: Reduction method for the output.
200
+
201
+ Returns:
202
+ DISTS Similarity score between the two images.
203
+
204
+ Example:
205
+ >>> from torch import rand
206
+ >>> preds = rand(5, 3, 256, 256)
207
+ >>> target = rand(5, 3, 256, 256)
208
+ >>> deep_image_structure_and_texture_similarity(preds, target)
209
+ tensor([0.1285, 0.1344, 0.1356, 0.1277, 0.1276], grad_fn=<RsubBackward1>)
210
+ >>> deep_image_structure_and_texture_similarity(preds, target, reduction='mean')
211
+ tensor(0.1308, grad_fn=<MeanBackward0>)
212
+
213
+ """
214
+ scores = _dists_update(preds, target)
215
+ return _dists_compute(scores, reduction)