Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/sv.dat +3 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/LICENSE +27 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/METADATA +28 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/RECORD +25 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/REQUESTED +0 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/WHEEL +5 -0
- rtme/lib/python3.10/site-packages/pycparser-2.22.dist-info/top_level.txt +1 -0
- rtme/lib/python3.10/site-packages/send2trash/__init__.py +21 -0
- rtme/lib/python3.10/site-packages/send2trash/__main__.py +33 -0
- rtme/lib/python3.10/site-packages/send2trash/compat.py +25 -0
- rtme/lib/python3.10/site-packages/send2trash/plat_gio.py +23 -0
- rtme/lib/python3.10/site-packages/send2trash/plat_other.py +218 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/f_beta.py +1221 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/group_fairness.py +326 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/hamming.py +529 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/hinge.py +380 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/jaccard.py +485 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/logauc.py +507 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/matthews_corrcoef.py +416 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/negative_predictive_value.py +522 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_fixed_recall.py +515 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall.py +1086 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/precision_recall_curve.py +692 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/ranking.py +431 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/recall_fixed_precision.py +514 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/roc.py +596 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/sensitivity_specificity.py +375 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity.py +513 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/specificity_sensitivity.py +375 -0
- rtme/lib/python3.10/site-packages/torchmetrics/classification/stat_scores.py +562 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/__init__.py +44 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/adjusted_rand_score.py +127 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/calinski_harabasz_score.py +128 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/cluster_accuracy.py +148 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/davies_bouldin_score.py +138 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/dunn_index.py +129 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/fowlkes_mallows_index.py +122 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/homogeneity_completeness_v_measure.py +329 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/mutual_info_score.py +127 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/normalized_mutual_info_score.py +127 -0
- rtme/lib/python3.10/site-packages/torchmetrics/clustering/rand_score.py +125 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/__init__.py +32 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/_deprecated.py +63 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/_mean_ap.py +988 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/diou.py +195 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/giou.py +190 -0
- rtme/lib/python3.10/site-packages/torchmetrics/detection/iou.py +297 -0
- rtme/lib/python3.10/site-packages/torchmetrics/functional/image/d_lambda.py +152 -0
- 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)
|