class AveragePostprocessor(BasePostprocessor): """A postprocessor that convert a score to the average of of all previous scores. """ def __init__(self): self.meter = AverageMeter() def fit_partial(self, score): """Fits the postprocessor to the (next) timestep's score. Args: score (float): Input score. Returns: object: self. """ self.meter.update(score) return self def transform_partial(self, score=None): """Gets the current average. This method should be used immediately after the fit_partial method with same score. Args: score (float): The input score. Returns: float: Transformed score. """ return self.meter.get()
class ZScorePostprocessor(BasePostprocessor): """A postprocessor that normalize the score via Z-score normalization. """ def __init__(self): self.variance_meter = VarianceMeter() self.average_meter = AverageMeter() def fit_partial(self, score): """Fits the postprocessor to the (next) timestep's score. Args: score (float): Input score. Returns: object: self. """ self.variance_meter.update(score) self.average_meter.update(score) return self def transform_partial(self, score): """Applies postprocessing to the score. Args: score (float): The input score. Returns: float: Transformed score. """ zscore = (score - self.average_meter.get()) / \ np.sqrt(self.variance_meter.get()) return zscore
class GaussianTailProbabilityCalibrator(BasePostprocessor): """Assuming that the scores follow normal distribution, this class provides an interface to convert the scores into probabilities via Q-function, i.e., the tail function of Gaussian distribution :cite:`ahmad2017unsupervised`. Args: running_statistics (bool): Whether to calculate the mean and variance through running window. The window size is defined by the `window_size` parameter. window_size (int): The size of window for running average and std. Ignored if `running_statistics` parameter is False. """ def __init__(self, running_statistics=True, window_size=6400): self.running_statistics = running_statistics self.window_size = window_size if self.running_statistics: self.avg_meter = RunningStatistic(AverageMeter, self.window_size) self.var_meter = RunningStatistic(VarianceMeter, self.window_size) else: self.avg_meter = AverageMeter() self.var_meter = RunningStatistic(VarianceMeter, self.window_size) def fit_partial(self, score): """Fits particular (next) timestep's score to train the postprocessor. Args: score (float): Input score. Returns: object: self. """ self.avg_meter.update(score) self.var_meter.update(score) return self def transform_partial(self, score): """Transforms given score. Args: score (float): Input score. Returns: float: Processed score. """ mean = self.avg_meter.get() var = self.var_meter.get() if var > 0: std = np.sqrt(var) else: std = 1.0 return 1 - self._qfunction(score, mean, std) def _qfunction(self, x, mean, std): """ Given the normal distribution specified by the mean and standard deviation args, return the probability of getting samples > x. Implementation is adapted from the https://github.com/ish-vlad/Conformal-Anomaly-Detection/blob/22769b8d3cede7fabd978a36cdd2853255e450ac/scripts/nab_module/nab/detectors/gaussian/windowedGaussian_detector.py This is the Q-function: the tail probability of the normal distribution. """ # Calculate the Q function with the complementary error function, explained # here: # http://www.gaussianwaves.com/2012/07/q-function-and-error-functions z = (x - mean) / std return 0.5 * math.erfc(z / math.sqrt(2))
def __init__(self, running_statistics=True, window_size=6400): self.running_statistics = running_statistics self.window_size = window_size if self.running_statistics: self.avg_meter = RunningStatistic(AverageMeter, self.window_size) self.var_meter = RunningStatistic(VarianceMeter, self.window_size) else: self.avg_meter = AverageMeter() self.var_meter = RunningStatistic(VarianceMeter, self.window_size)
def __init__(self, substracted_statistic="mean", absolute=True): self.absolute = absolute self.variance_meter = VarianceMeter() if substracted_statistic == "median": self.sub_meter = MedianMeter() elif substracted_statistic == "mean": self.sub_meter = AverageMeter() else: raise ValueError( "Unknown substracted_statistic value! Please choose median or mean." )
class StandardAbsoluteDeviation(BaseModel): """The model that assigns the deviation from the mean (or median) and divides with the standard deviation. This model is based on the 3-Sigma rule described in :cite:`hochenbaum2017automatic`. substracted_statistic (str): The statistic to be substracted for scoring. It is either "mean" or "median". (Default="mean"). absolute (bool): Whether to output score's absolute value. (Default=True). """ def __init__(self, substracted_statistic="mean", absolute=True): self.absolute = absolute self.variance_meter = VarianceMeter() if substracted_statistic == "median": self.sub_meter = MedianMeter() elif substracted_statistic == "mean": self.sub_meter = AverageMeter() else: raise ValueError( "Unknown substracted_statistic value! Please choose median or mean." ) def fit_partial(self, X, y=None): """Fits the model to next instance. Args: X (np.float array of shape (1,)): The instance to fit. Note that this model is univariate. y (int): Ignored since the model is unsupervised (Default=None). Returns: object: Returns the self. """ assert len(X) == 1 # Only for time series self.variance_meter.update(X) self.sub_meter.update(X) return self def score_partial(self, X): """Scores the anomalousness of the next instance. Args: X (np.float array of shape (1,)): The instance to score. Higher scores represent more anomalous instances whereas lower scores correspond to more normal instances. Returns: float: The anomalousness score of the input instance. """ sub = self.sub_meter.get() dev = self.variance_meter.get()**0.5 score = (X - sub) / (dev + 1e-10) return abs(score) if self.absolute else score
def __init__( self, metric_cls, window_size, ignore_nonempty_last=True, **kwargs): super().__init__() self.ignore_nonempty_last = ignore_nonempty_last self.window_size = window_size self.metric_cls = metric_cls self.metric = self._init_metric(**kwargs) self.score_meter = AverageMeter() self.step = 0 self.num_windows = 1
class WindowedMetric(BaseMetric): """A helper class to evaluate windowed metrics. The distributions of the streaming model scores often change due to model collapse (i.e. becoming closer to the always loss=0) or appearing nonstationarities. Thus, the metrics such as ROC or AUC scores may change drastically. To prevent their effect, this class creates windows of size `window_size`. After each `window_size`th object, a new instance of the `metric_cls` is being created. Lastly, the metrics from all windows are averaged :cite:`xstream,gokcesu2017online`. Args: metric_cls (class): The metric class to be windowed. window_size (int): The window size. ignore_nonempty_last (bool): Whether to ignore the score of the nonempty last window. Note that the empty last window is always ignored. """ def __init__( self, metric_cls, window_size, ignore_nonempty_last=True, **kwargs): super().__init__() self.ignore_nonempty_last = ignore_nonempty_last self.window_size = window_size self.metric_cls = metric_cls self.metric = self._init_metric(**kwargs) self.score_meter = AverageMeter() self.step = 0 self.num_windows = 1 def _init_metric(self, **kwargs): return self.metric_cls(**kwargs) def update(self, y_true, y_pred): """Updates the score with new true label and predicted score/label. Args: y_true : float The ground truth score for the incoming instance. y_pred : float The predicted score for the incoming instance. Returns: object: self. """ self.step += 1 self.metric.update(y_true, y_pred) if self.step % self.window_size == 0: self.num_windows += 1 score = self.metric.get() self.score_meter.update(score) self.metric = self._init_metric() return self def get(self): """Obtains the averaged score. Returns: float: The average score of the windows. """ if self.num_windows == 1: return self.metric.get() elif not self.ignore_nonempty_last and self.step % self.window_size != 0: return (self.metric.get() + self.score_meter.get() * (self.num_windows - 1)) / self.num_windows else: return self.score_meter.get()
def __init__(self): self.meter = AverageMeter()
def __init__(self): self.variance_meter = VarianceMeter() self.average_meter = AverageMeter()