Skip to content
Snippets Groups Projects
metrics.py 3.23 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bobholamovic's avatar
    Bobholamovic committed
    from functools import partial
    
    import numpy as np
    
    Bobholamovic's avatar
    Bobholamovic committed
    from sklearn import metrics
    
    
    class AverageMeter:
    
    Bobholamovic's avatar
    Bobholamovic committed
        def __init__(self, callback=None, calc_avg=True):
    
    Bobholamovic's avatar
    Bobholamovic committed
            super().__init__()
    
    Bobholamovic's avatar
    Bobholamovic committed
            if callback is not None:
    
    Bobholamovic's avatar
    Bobholamovic committed
                self.calculate = callback
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.calc_avg = calc_avg
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.reset()
    
    
    Bobholamovic's avatar
    Bobholamovic committed
        def calculate(self, *args):
    
    Bobholamovic's avatar
    Bobholamovic committed
            if len(args) == 1:
    
    Bobholamovic's avatar
    Bobholamovic committed
                return args[0]
            else:
                raise NotImplementedError
    
        def reset(self):
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.val = 0
            self.sum = 0
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.count = 0
    
    Bobholamovic's avatar
    Bobholamovic committed
            if self.calc_avg:
                self.avg = 0
    
    Bobholamovic's avatar
    Bobholamovic committed
    
        def update(self, *args, n=1):
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.val = self.calculate(*args)
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.sum += self.val * n
            self.count += n
    
    Bobholamovic's avatar
    Bobholamovic committed
            if self.calc_avg:
                self.avg = self.sum / self.count
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    Bobholamovic's avatar
    Bobholamovic committed
        def __repr__(self):
    
    Bobholamovic's avatar
    Bobholamovic committed
            if self.calc_avg:
                return "val: {} avg: {} cnt: {}".format(self.val, self.avg, self.count)
            else:
                return "val: {} cnt: {}".format(self.val, self.count)
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    Bobholamovic's avatar
    Bobholamovic committed
    # These metrics only for numpy arrays
    
    Bobholamovic's avatar
    Bobholamovic committed
    class Metric(AverageMeter):
        __name__ = 'Metric'
    
        def __init__(self, n_classes=2, mode='separ', reduction='binary'):
    
    Bobholamovic's avatar
    Bobholamovic committed
            self._cm = AverageMeter(partial(metrics.confusion_matrix, labels=np.arange(n_classes)), False)
            self.mode = mode
    
    Bobholamovic's avatar
    Bobholamovic committed
            if reduction == 'binary' and n_classes != 2:
    
    Bobholamovic's avatar
    Bobholamovic committed
                raise ValueError("Binary reduction only works in 2-class cases.")
    
    Bobholamovic's avatar
    Bobholamovic committed
            self.reduction = reduction
    
    Bobholamovic's avatar
    Bobholamovic committed
            super().__init__(None, mode!='accum')
    
    Bobholamovic's avatar
    Bobholamovic committed
        
    
    Bobholamovic's avatar
    Bobholamovic committed
        def _calculate_metric(self, cm):
    
    Bobholamovic's avatar
    Bobholamovic committed
            raise NotImplementedError
    
    
    Bobholamovic's avatar
    Bobholamovic committed
        def calculate(self, pred, true, n=1):
            self._cm.update(true.ravel(), pred.ravel())
            if self.mode == 'accum':
                cm = self._cm.sum
            elif self.mode == 'separ':
                cm = self._cm.val
    
    Bobholamovic's avatar
    Bobholamovic committed
            else:
                raise ValueError("Invalid working mode")
    
    Bobholamovic's avatar
    Bobholamovic committed
            if self.reduction == 'none':
                # Do not reduce size
    
    Bobholamovic's avatar
    Bobholamovic committed
                return self._calculate_metric(cm)
    
    Bobholamovic's avatar
    Bobholamovic committed
            elif self.reduction == 'mean':
                # Micro averaging
    
    Bobholamovic's avatar
    Bobholamovic committed
                return self._calculate_metric(cm).mean()
            elif self.reduction == 'binary':
    
    Bobholamovic's avatar
    Bobholamovic committed
                # The pos_class be 1
    
    Bobholamovic's avatar
    Bobholamovic committed
                return self._calculate_metric(cm)[1]
    
    Bobholamovic's avatar
    Bobholamovic committed
            else:
                raise ValueError("Invalid reduction type")
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    Bobholamovic's avatar
    Bobholamovic committed
        def reset(self):
            super().reset()
            # Reset the confusion matrix
            self._cm.reset()
    
    Bobholamovic's avatar
    Bobholamovic committed
    
        def __repr__(self):
    
    Bobholamovic's avatar
    Bobholamovic committed
            return self.__name__+" "+super().__repr__()
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    class Precision(Metric):
        __name__ = 'Prec.'
    
    Bobholamovic's avatar
    Bobholamovic committed
        def _calculate_metric(self, cm):
    
    Bobholamovic's avatar
    Bobholamovic committed
            return np.nan_to_num(np.diag(cm)/cm.sum(axis=0))
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    class Recall(Metric):
        __name__ = 'Recall'
    
    Bobholamovic's avatar
    Bobholamovic committed
        def _calculate_metric(self, cm):
    
    Bobholamovic's avatar
    Bobholamovic committed
            return np.nan_to_num(np.diag(cm)/cm.sum(axis=1))
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    class Accuracy(Metric):
        __name__ = 'OA'
    
        def __init__(self, n_classes=2, mode='separ'):
    
    Bobholamovic's avatar
    Bobholamovic committed
            super().__init__(n_classes=n_classes, mode=mode, reduction='none')
    
    Bobholamovic's avatar
    Bobholamovic committed
            
        def _calculate_metric(self, cm):
    
    Bobholamovic's avatar
    Bobholamovic committed
            return np.nan_to_num(np.diag(cm).sum()/cm.sum())
    
    Bobholamovic's avatar
    Bobholamovic committed
    
    
    class F1Score(Metric):
        __name__ = 'F1'
    
    Bobholamovic's avatar
    Bobholamovic committed
        def _calculate_metric(self, cm):
    
    Bobholamovic's avatar
    Bobholamovic committed
            prec = np.nan_to_num(np.diag(cm)/cm.sum(axis=0))
            recall = np.nan_to_num(np.diag(cm)/cm.sum(axis=1))
            return np.nan_to_num(2*(prec*recall) / (prec+recall))