Source code for decode.evaluation.utils

import math

import numpy as np
import seaborn as sns
import torch

from matplotlib import pyplot as plt
from matplotlib.ticker import AutoMinorLocator
from scipy import stats
from scipy.stats import gaussian_kde
import warnings


[docs]def kde_sorted(x: torch.Tensor, y: torch.Tensor, plot=False, ax=None, band_with=None, sub_sample: (None, int) = None, nan_inf_ignore=False): """ Computes a kernel density estimate. Ability to sub-sample for very large datasets. Args: x: y: plot: ax: band_with: sub_sample: nan_inf_ignore: """ if sub_sample: ix_sample = np.random.permutation(x.size(0)) ix_in, ix_out = ix_sample[:sub_sample], ix_sample[sub_sample:] else: ix_in = np.arange(x.size(0)) ix_out = np.zeros(0, dtype=int) xy = np.vstack([x, y]) xy_in = xy[:, ix_in] xy_out = xy[:, ix_out] if xy_in.size == 0: z = np.zeros(0) elif nan_inf_ignore: try: with warnings.catch_warnings(): warnings.simplefilter("ignore") if band_with: z = gaussian_kde(xy_in, bw_method=band_with)(xy_in) else: z = gaussian_kde(xy_in)(xy_in) except (np.linalg.LinAlgError, ValueError): # ToDo: replace by robust kde z = np.ones_like(xy_in[0]) * float('nan') else: if band_with: z = gaussian_kde(xy_in, bw_method=band_with)(xy_in) else: z = gaussian_kde(xy_in)(xy_in) # Sort the points by density, so that the most dense points are plotted last idx = z.argsort() x_in, y_in, z = xy_in[0, idx], xy_in[1, idx], z[idx] if plot: if ax is None: ax = plt.gca() if sub_sample: # get rid of nan xy_out = xy_out[:, np.prod((~np.isnan(xy_out)), 0).astype('bool')] ax.scatter(xy_out[0, :], xy_out[1, :], c='k', s=10, edgecolors='none') not_nan = (~np.isnan(x_in) * ~np.isnan(y_in)) ax.scatter(x_in[not_nan], y_in[not_nan], c=z[None, not_nan], s=10, edgecolors='none', cmap='RdBu_r') return z, x_in, y_in
[docs]class MetricMeter: """Computes and stores the average and current value""" def __init__(self, vals=torch.zeros((0,)), reduce_nan=True): self.val = None self.vals = vals self.reduce_nan = reduce_nan @property def count(self): return self.vals.numel() @property def std(self): if isinstance(self.vals, torch.Tensor): self.vals.std().item() else: return torch.tensor(self.vals).std().item() @property def mean(self): if isinstance(self.vals, torch.Tensor): return self.vals.mean().item() else: return torch.tensor(self.vals).mean().item() @property def avg(self): return self.mean
[docs] def hist(self, bins=50, range_hist=None, fit=stats.norm): # If no vals, return empty figure if self.vals.numel() <= 10: f = plt.figure() return f elif self.vals.unique().numel() == 1: f = plt.figure() plt.vlines(self.vals[0], 0, 1) return f if range_hist is not None: bins_ = np.linspace(range_hist[0], range_hist[1], bins + 1) else: # use 97 percent as range range_hist = np.percentile(self.vals, [1, 99]) # funnily percentile is in percent not 0...1 bins_ = np.linspace(*range_hist, bins + 1) vals = self.vals.view(-1).numpy() f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw={"height_ratios": (.1, .9)}) """Plot boxplot and histplot.""" sns.boxplot(vals, ax=ax_box) sns.distplot(vals, ax=ax_hist, kde=False, fit=fit, bins=bins_, norm_hist=True) """Get the fit values.""" if fit is not None: (mu, sigma) = stats.norm.fit(vals) plt.legend(["N $ (\mu$ = {0:.3g}, $\sigma^2$ = {1:.3g}$^2$)".format(mu, sigma)], frameon=False) # Cosmetics ax_box.set(yticks=[]) ax_box.xaxis.set_minor_locator(AutoMinorLocator()) ax_hist.xaxis.set_minor_locator(AutoMinorLocator()) sns.despine(ax=ax_hist) sns.despine(ax=ax_box, left=True) return f
[docs] def reset(self): """ Reset instance. :return: """ self.val = None self.vals = torch.zeros((0,))
[docs] def update(self, val): """ Update AverageMeter. :param val: value :return: None """ val = float(val) if math.isnan(val) and self.reduce_nan: return # convert to torch.tensor self.val = val self.vals = torch.cat((self.vals, torch.Tensor([val])), 0)
def __str__(self): if self.count >= 2: return "{:.3f} +/- {:.3f}".format(self.avg, self.std) else: return "{:.3f}".format(self.avg) def __neg__(self): m = MetricMeter(reduce_nan=self.reduce_nan) m.vals = -self.vals return m def __add__(self, other): m = MetricMeter(reduce_nan=self.reduce_nan) if isinstance(other, MetricMeter): m.vals = self.vals + other.vals else: m.vals = self.vals + other return m def __radd__(self, other): return self + other def __sub__(self, other): return -other + self def __rsub__(self, other): return -self + other def __mul__(self, other): m = MetricMeter(reduce_nan=self.reduce_nan) if isinstance(other, MetricMeter): m.vals = self.vals * other.vals else: m.vals = self.vals * other return m def __rmul__(self, other): return self * other def __pow__(self, other): m = MetricMeter(reduce_nan=self.reduce_nan) if isinstance(other, MetricMeter): raise ValueError("Power not implemented for both operands being MetricMeters.") else: m.vals = self.vals ** other return m def __truediv__(self, other): m = MetricMeter(reduce_nan=self.reduce_nan) if isinstance(other, MetricMeter): m.vals = self.vals / other.vals else: m.vals = self.vals / other return m