o
    .wixE                     @   s   d dl mZ d dlmZ d dlmZmZmZ d dlZd dlm	Z	 d dl
mZ d dlmZ d dlmZ d d	lmZ d d
lmZmZmZ d dlmZ d dlmZ esSdgZG dd deZdS )    )Sequence)deepcopy)AnyOptionalUnionN)Tensor)
ModuleList)MetricCollection)Metric)_MATPLOTLIB_AVAILABLE)_AX_TYPE_PLOT_OUT_TYPEplot_single_or_multi_val)rank_zero_warn)ClasswiseWrapperMetricTracker.plotc                       s  e Zd ZU dZeeee f ed< eee	f ed< d'deee	f deeee df ddf fddZ
edefd	d
Zd(ddZdededdfddZdededdfddZdefddZdefddZd(ddZd(ddZ	d)dededeeeeeeef eeeef f ed eeeedf f eeeeedf f eeeedf f f f fddZd eddfd!d"Z	dd#eeeee f  d$ee de fd%d&Z!  Z"S )*MetricTrackera  A wrapper class that can help keeping track of a metric or metric collection over time.

    The wrapper implements the standard ``.update()``, ``.compute()``, ``.reset()`` methods that just
    calls corresponding method of the currently tracked metric. However, the following additional methods are
    provided:

        -``MetricTracker.n_steps``: number of metrics being tracked
        -``MetricTracker.increment()``: initialize a new metric for being tracked
        -``MetricTracker.compute_all()``: get the metric value for all steps
        -``MetricTracker.best_metric()``: returns the best value

    Out of the box, this wrapper class fully supports that the base metric being tracked is a single `Metric`, a
    `MetricCollection` or another `MetricWrapper` wrapped around a metric. However, multiple layers of nesting, such
    as using a `Metric` inside a `MetricWrapper` inside a `MetricCollection` is not fully supported, especially the
    `.best_metric` method that cannot auto compute the best metric and index for such nested structures.

    Args:
        metric: instance of a ``torchmetrics.Metric`` or ``torchmetrics.MetricCollection``
            to keep track of at each timestep.
        maximize: either single bool or list of bool indicating if higher metric values are
            better (``True``) or lower is better (``False``).

    Example (single metric):
        >>> from torch import randint
        >>> from torchmetrics.wrappers import MetricTracker
        >>> from torchmetrics.classification import MulticlassAccuracy
        >>> tracker = MetricTracker(MulticlassAccuracy(num_classes=10, average='micro'))
        >>> for epoch in range(5):
        ...     tracker.increment()
        ...     for batch_idx in range(5):
        ...         tracker.update(randint(10, (100,)), randint(10, (100,)))
        ...     print(f"current acc={tracker.compute()}")
        current acc=0.1120000034570694
        current acc=0.08799999952316284
        current acc=0.12600000202655792
        current acc=0.07999999821186066
        current acc=0.10199999809265137
        >>> best_acc, which_epoch = tracker.best_metric(return_step=True)
        >>> best_acc  # doctest: +ELLIPSIS
        0.1260...
        >>> which_epoch
        2
        >>> tracker.compute_all()
        tensor([0.1120, 0.0880, 0.1260, 0.0800, 0.1020])

    Example (multiple metrics using MetricCollection):
        >>> from torch import randn
        >>> from torchmetrics.wrappers import MetricTracker
        >>> from torchmetrics import MetricCollection
        >>> from torchmetrics.regression import MeanSquaredError, ExplainedVariance
        >>> tracker = MetricTracker(MetricCollection([MeanSquaredError(), ExplainedVariance()]), maximize=[False, True])
        >>> for epoch in range(5):
        ...     tracker.increment()
        ...     for batch_idx in range(5):
        ...         tracker.update(randn(100), randn(100))
        ...     print(f"current stats={tracker.compute()}")  # doctest: +NORMALIZE_WHITESPACE
        current stats={'MeanSquaredError': tensor(2.3292), 'ExplainedVariance': tensor(-0.9516)}
        current stats={'MeanSquaredError': tensor(2.1370), 'ExplainedVariance': tensor(-1.0775)}
        current stats={'MeanSquaredError': tensor(2.1695), 'ExplainedVariance': tensor(-0.9945)}
        current stats={'MeanSquaredError': tensor(2.1072), 'ExplainedVariance': tensor(-1.1878)}
        current stats={'MeanSquaredError': tensor(2.0562), 'ExplainedVariance': tensor(-1.0754)}
        >>> from pprint import pprint
        >>> best_res, which_epoch = tracker.best_metric(return_step=True)
        >>> pprint(best_res)  # doctest: +ELLIPSIS
        {'ExplainedVariance': -0.951...,
         'MeanSquaredError': 2.056...}
        >>> which_epoch
        {'MeanSquaredError': 4, 'ExplainedVariance': 0}
        >>> pprint(tracker.compute_all())
        {'ExplainedVariance': tensor([-0.9516, -1.0775, -0.9945, -1.1878, -1.0754]),
         'MeanSquaredError': tensor([2.3292, 2.1370, 2.1695, 2.1072, 2.0562])}

    maximize_base_metricNmetricreturnc                    s  t    t|ttfstd| || _|d u r~t|tr6t|dd d u r1td|j	j
 d|j| _nt|tr}g | _| D ]:\} t dd d u rVtd| dt trrt jjtrr fddtt jjD }n jg}| j| qBnDt|ttfstdt|trtd	d
 |D stdt|trt|trt|t|krtdt|trt|tstd|| _d| _d S )Nz[Metric arg need to be an instance of a torchmetrics `Metric` or `MetricCollection` but got higher_is_betterzThe metric 'zb' does not have a 'higher_is_better' attribute. Please provide the `maximize` argument explicitly.zz' in the MetricCollection does not have a 'higher_is_better' attribute. Please provide the `maximize` argument explicitly.c                    s   g | ]} j qS  )r   ).0_mr   Z/home/ubuntu/sommelier/.venv/lib/python3.10/site-packages/torchmetrics/wrappers/tracker.py
<listcomp>   s    z*MetricTracker.__init__.<locals>.<listcomp>zBArgument `maximize` should either be a single bool or list of boolc                 s   s    | ]}t |tV  qd S N)
isinstancebool)r   r   r   r   r   	<genexpr>   s    z)MetricTracker.__init__.<locals>.<genexpr>z1Argument `maximize` is list but not type of bool.zOThe len of argument `maximize` should match the length of the metric collectionzLArgument `maximize` should be a single bool when `metric` is a single MetricF)super__init__r    r
   r	   	TypeErrorr   getattrAttributeError	__class____name__r   r   itemsr   r   num_classesintrangeextendr!   list
ValueErroralllen_increment_called)selfr   r   namem_higher_is_betterr(   r   r   r$   o   sF   




 $
zMetricTracker.__init__c                 C   s   t | d S )z=Returns the number of times the tracker has been incremented.   )r2   r4   r   r   r   n_steps   s   zMetricTracker.n_stepsc                 C   s   d| _ | t| j dS )zDCreate a new instance of the input metric that will be updated next.TN)r3   appendr   r   r9   r   r   r   	increment   s   zMetricTracker.incrementargskwargsc                 O   sF   |  d t| d ttfstdt| d  d| d |i |S )z1Call forward of the current metric being tracked.forwardCExpected the last item to be a Metric or MetricCollection, but got .)_check_for_incrementr    r
   r	   r%   typer4   r=   r>   r   r   r   r?      s   
zMetricTracker.forwardc                 O   sL   |  d t| d ttfstdt| d  d| d j|i | dS )z(Update the current metric being tracked.updater@   rA   rB   N)rC   r    r
   r	   r%   rD   rF   rE   r   r   r   rF      s   
zMetricTracker.updatec                 C   s@   |  d t| d ttfstdt| d  d| d  S )z1Call compute of the current metric being tracked.computer@   rA   rB   )rC   r    r
   r	   r%   rD   rG   r9   r   r   r   rG      s   
zMetricTracker.computec                    s   |  d dd t| D  z2t d tr& d  } fdd|D W S t d tr9tdd  D dW S tj ddW S  tyK     Y S w )	a  Compute the metric value for all tracked metrics.

        Return:
            By default will try stacking the results from all increments into a single tensor if the tracked base
            object is a single metric. If a metric collection is provided a dict of stacked tensors will be returned.
            If the stacking process fails a list of the computed results will be returned.

        Raises:
            ValueError:
                If `self.increment` have not been called before this method is called.

        compute_allc                 S   s    g | ]\}}|d kr|  qS )r   )rG   )r   ir   r   r   r   r      s     z-MetricTracker.compute_all.<locals>.<listcomp>r   c                    s*   i | ]  t j fd dD ddqS )c                    s   g | ]}|  qS r   r   r   rkr   r   r      s    z8MetricTracker.compute_all.<locals>.<dictcomp>.<listcomp>r   dimtorchstack)r   resrL   r   
<dictcomp>   s   * z-MetricTracker.compute_all.<locals>.<dictcomp>c                 S   s   g | ]	}t j|d dqS )r   rN   rP   rJ   r   r   r   r      s    rN   )	rC   	enumerater    dictkeysr/   rQ   rR   r%   )r4   rX   r   rS   r   rH      s   
zMetricTracker.compute_allc                 C   s:   t | d ttfstdt| d  d| d   dS )z'Reset the current metric being tracked.r@   rA   rB   Nr    r
   r	   r%   rD   resetr9   r   r   r   rZ      s   zMetricTracker.resetc                 C   s8   | D ]}t |ttfstdt| d|  qdS )z Reset all metrics being tracked.z?Expected all metrics to be Metric or MetricCollection, but got rB   NrY   )r4   r   r   r   r   	reset_all   s
   
zMetricTracker.reset_allFreturn_stepNNc                 C   s  |   }t|trtd |rdS dS t| jtrgt| jtsg| jr%tj	ntj
}z||d\}}|r;| | fW S | W S  ttfyf } ztd| dt |r[W Y d}~dS W Y d}~dS d}~ww t| jtrp| jnt|| jg }i i }}t| D ]P\}\}	}
z"|| rtj	ntj
}||
d}|d  |d  ||	< ||	< W q ttfy } ztd|	 d	| d
t d\||	< ||	< W Y d}~qd}~ww |r||fS |S )a  Return the highest metric out of all tracked.

        Args:
            return_step: If ``True`` will also return the step with the highest metric value.

        Returns:
            Either a single value or a tuple, depends on the value of ``return_step`` and the object being tracked.

            - If a single metric is being tracked and ``return_step=False`` then a single tensor will be returned
            - If a single metric is being tracked and ``return_step=True`` then a 2-element tuple will be returned,
              where the first value is optimal value and second value is the corresponding optimal step
            - If a metric collection is being tracked and ``return_step=False`` then a single dict will be returned,
              where keys correspond to the different values of the collection and the values are the optimal metric
              value
            - If a metric collection is being bracked and ``return_step=True`` then a 2-element tuple will be returned
              where each is a dict, with keys corresponding to the different values of th collection and the values
              of the first dict being the optimal values and the values of the second dict being the optimal step

            In addition the value in all cases may be ``None`` if the underlying metric does have a proper defined way
            of being optimal or in the case where a nested structure of metrics are being tracked.

        zEncountered nested structure. You are probably using a metric collection inside a metric collection, or a metric wrapper inside a metric collection, which is not supported by `.best_metric()` method. Returning `None` instead.r]   Nr   zDEncountered the following error when trying to get the best metric: z^this is probably due to the 'best' not being defined for this metric.Returning `None` instead.r8   zNEncountered the following error when trying to get the best metric for metric :z_ this is probably due to the 'best' not being defined for this metric.Returning `None` instead.)rH   r    r/   r   r   r
   r   r   rQ   maxminitemr0   RuntimeErrorUserWarningr2   rV   r*   )r4   r\   rT   fnvalueidxerrorr   rI   rM   voutr   r   r   best_metric   sZ   !


"

&	zMetricTracker.best_metricmethodc                 C   s   | j std| ddS )zSCheck that a metric that can be updated/used for computations has been initialized.`z9` cannot be called before `.increment()` has been called.N)r3   r0   )r4   rk   r   r   r   rC   6  s   z"MetricTracker._check_for_incrementvalaxc                 C   s2   |dur|n|   }t||| jjd\}}||fS )aq  Plot a single or multiple values from the metric.

        Args:
            val: Either a single result from calling `metric.forward` or `metric.compute` or a list of these results.
                If no value is provided, will automatically call `metric.compute` and plot that result.
            ax: An matplotlib axis object. If provided will add plot to that axis

        Returns:
            Figure and Axes object

        Raises:
            ModuleNotFoundError:
                If `matplotlib` is not installed

        .. plot::
            :scale: 75

            >>> # Example plotting a single value
            >>> import torch
            >>> from torchmetrics.wrappers import MetricTracker
            >>> from torchmetrics.classification import BinaryAccuracy
            >>> tracker = MetricTracker(BinaryAccuracy(), maximize=True)
            >>> for epoch in range(5):
            ...     tracker.increment()
            ...     for batch_idx in range(5):
            ...         tracker.update(torch.randint(2, (10,)), torch.randint(2, (10,)))
            >>> fig_, ax_ = tracker.plot()  # plot all epochs

        N)rn   r5   )rH   r   r(   r)   )r4   rm   rn   figr   r   r   plot;  s    
r   r   )r   N)F)#r)   
__module____qualname____doc__r   r!   r/   __annotations__r
   r	   r$   propertyr,   r:   r<   r   r?   rF   rG   rH   rZ   r[   floatr   tuplerW   strrj   rC   r   r   r   r   rp   __classcell__r   r   r7   r   r   !   sL   
 J2.


*
Sr   )collections.abcr   copyr   typingr   r   r   rQ   r   torch.nnr   torchmetrics.collectionsr	   torchmetrics.metricr
   torchmetrics.utilities.importsr   torchmetrics.utilities.plotr   r   r   torchmetrics.utilities.printsr   torchmetrics.wrappersr   __doctest_skip__r   r   r   r   r   <module>   s   