Commit 4e89bcb3 authored by Anjith GEORGE's avatar Anjith GEORGE

Merge branch 'correct-apcer-calculation' into 'master'

Correct apcer calculation

Closes #31

See merge request !60
parents 47b3f2d0 8f66c86e
Pipeline #30336 passed with stages
in 12 minutes and 22 seconds
...@@ -7,11 +7,8 @@ from bob.pad.base.algorithm import Algorithm ...@@ -7,11 +7,8 @@ from bob.pad.base.algorithm import Algorithm
import bob.learn.mlp import bob.learn.mlp
import bob.io.base import bob.io.base
from bob.bio.video.utils import FrameContainer import logging
from bob.pad.base.utils import convert_frame_cont_to_array logger = logging.getLogger(__name__)
from bob.core.log import setup
logger = setup("bob.pad.base")
class MLP(Algorithm): class MLP(Algorithm):
...@@ -42,11 +39,11 @@ class MLP(Algorithm): ...@@ -42,11 +39,11 @@ class MLP(Algorithm):
criterion to stop the training: if the difference criterion to stop the training: if the difference
between current and last loss is smaller than between current and last loss is smaller than
this number, then stop training. this number, then stop training.
""" """
Algorithm.__init__(self, Algorithm.__init__(self,
performs_projection=True, performs_projection=True,
requires_projector_training=True, requires_projector_training=True,
**kwargs) **kwargs)
self.hidden_units = hidden_units self.hidden_units = hidden_units
...@@ -54,32 +51,31 @@ class MLP(Algorithm): ...@@ -54,32 +51,31 @@ class MLP(Algorithm):
self.precision = precision self.precision = precision
self.mlp = None self.mlp = None
def train_projector(self, training_features, projector_file): def train_projector(self, training_features, projector_file):
"""Trains the MLP """Trains the MLP
Parameters Parameters
---------- ----------
training_features : :any:`list` of :py:class:`numpy.ndarray` training_features : :any:`list` of :py:class:`numpy.ndarray`
Data used to train the MLP. The real attempts are in training_features[0] and the attacks are in training_features[1] Data used to train the MLP. The real attempts are in training_features[0] and the attacks are in training_features[1]
projector_file : str projector_file : str
Filename where to save the trained model. Filename where to save the trained model.
""" """
# training is done in batch (i.e. using all training data) # training is done in batch (i.e. using all training data)
batch_size = len(training_features[0]) + len(training_features[1]) batch_size = len(training_features[0]) + len(training_features[1])
# The labels # The labels
label_real = numpy.zeros((len(training_features[0]), 2), dtype='float64') label_real = numpy.zeros((len(training_features[0]), 2), dtype='float64')
label_real[:, 0] = 1 label_real[:, 0] = 1
label_attack = numpy.zeros((len(training_features[1]), 2), dtype='float64') label_attack = numpy.zeros((len(training_features[1]), 2), dtype='float64')
label_attack[:, 1] = 0 label_attack[:, 1] = 0
real = numpy.array(training_features[0]) real = numpy.array(training_features[0])
attack = numpy.array(training_features[1]) attack = numpy.array(training_features[1])
X = numpy.vstack([real, attack]) X = numpy.vstack([real, attack])
Y = numpy.vstack([label_real, label_attack]) Y = numpy.vstack([label_real, label_attack])
# Building MLP architecture # Building MLP architecture
input_dim = real.shape[1] input_dim = real.shape[1]
shape = [] shape = []
...@@ -89,16 +85,16 @@ class MLP(Algorithm): ...@@ -89,16 +85,16 @@ class MLP(Algorithm):
# last layer contains two units: one for each class (i.e. real and attack) # last layer contains two units: one for each class (i.e. real and attack)
shape.append(2) shape.append(2)
shape = tuple(shape) shape = tuple(shape)
self.mlp = bob.learn.mlp.Machine(shape) self.mlp = bob.learn.mlp.Machine(shape)
self.mlp.output_activation = bob.learn.activation.Logistic() self.mlp.output_activation = bob.learn.activation.Logistic()
self.mlp.randomize() self.mlp.randomize()
trainer = bob.learn.mlp.BackProp(batch_size, bob.learn.mlp.CrossEntropyLoss(self.mlp.output_activation), self.mlp, train_biases=True) trainer = bob.learn.mlp.BackProp(batch_size, bob.learn.mlp.CrossEntropyLoss(self.mlp.output_activation), self.mlp, train_biases=True)
n_iter = 0 n_iter = 0
previous_cost = 0 previous_cost = 0
current_cost = 1 current_cost = 1
while (n_iter < self.max_iter) and (abs(previous_cost - current_cost) > self.precision): while (n_iter < self.max_iter) and (abs(previous_cost - current_cost) > self.precision):
previous_cost = current_cost previous_cost = current_cost
trainer.train(self.mlp, X, Y) trainer.train(self.mlp, X, Y)
current_cost = trainer.cost(self.mlp, X, Y) current_cost = trainer.cost(self.mlp, X, Y)
...@@ -107,14 +103,13 @@ class MLP(Algorithm): ...@@ -107,14 +103,13 @@ class MLP(Algorithm):
f = bob.io.base.HDF5File(projector_file, 'w') f = bob.io.base.HDF5File(projector_file, 'w')
self.mlp.save(f) self.mlp.save(f)
def project(self, feature): def project(self, feature):
"""Project the given feature """Project the given feature
Parameters Parameters
---------- ----------
feature : :py:class:`numpy.ndarray` feature : :py:class:`numpy.ndarray`
The feature to classify The feature to classify
Returns Returns
...@@ -126,18 +121,17 @@ class MLP(Algorithm): ...@@ -126,18 +121,17 @@ class MLP(Algorithm):
# feature = convert_frame_cont_to_array(feature) # feature = convert_frame_cont_to_array(feature)
return self.mlp(feature) return self.mlp(feature)
def score(self, toscore): def score(self, toscore):
"""Returns the probability of the real class. """Returns the probability of the real class.
Parameters Parameters
---------- ----------
toscore : :py:class:`numpy.ndarray` toscore : :py:class:`numpy.ndarray`
Returns Returns
------- -------
float float
probability of the authentication attempt to be real. probability of the authentication attempt to be real.
""" """
if toscore.ndim == 1: if toscore.ndim == 1:
return [toscore[0]] return [toscore[0]]
......
...@@ -5,22 +5,31 @@ ...@@ -5,22 +5,31 @@
import bob.measure import bob.measure
import numpy import numpy
from bob.measure import ( from bob.measure import far_threshold, eer_threshold, min_hter_threshold, farfrr
far_threshold, eer_threshold, min_hter_threshold) from bob.bio.base.score.load import four_column
from collections import defaultdict
import re
def calc_threshold(method, neg, pos): def calc_threshold(method, pos, negs, all_negs, far_value=None, is_sorted=False):
"""Calculates the threshold based on the given method. """Calculates the threshold based on the given method.
The scores should be sorted!
Parameters Parameters
---------- ----------
method : str method : str
One of ``bpcer20``, ``eer``, ``min-hter``. One of ``bpcer20``, ``eer``, ``min-hter``.
neg : array_like
The negative scores. They should be sorted!
pos : array_like pos : array_like
The positive scores. They should be sorted! The positive scores. They should be sorted!
negs : list
A list of array_like negative scores. Each item in the list corresponds to
scores of one PAI.
all_negs : array_like
An array of all negative scores. This can be calculated from negs as well but we
ask for it since you might have it already calculated.
far_value : None, optional
If method is far, far_value and all_negs are used to calculate the threshold.
is_sorted : bool, optional
If True, it means all scores are sorted and no sorting will happen.
Returns Returns
------- -------
...@@ -33,12 +42,15 @@ def calc_threshold(method, neg, pos): ...@@ -33,12 +42,15 @@ def calc_threshold(method, neg, pos):
If method is unknown. If method is unknown.
""" """
method = method.lower() method = method.lower()
if method == 'bpcer20': if "bpcer" in method:
threshold = far_threshold(neg, pos, 0.05, True) desired_apcer = 1 / float(method.replace("bpcer", ""))
elif method == 'eer': threshold = apcer_threshold(desired_apcer, pos, *negs, is_sorted=is_sorted)
threshold = eer_threshold(neg, pos, True) elif method == "far":
elif method == 'min-hter': threshold = far_threshold(all_negs, pos, far_value, is_sorted=is_sorted)
threshold = min_hter_threshold(neg, pos, True) elif method == "eer":
threshold = eer_threshold(all_negs, pos, is_sorted=is_sorted)
elif method == "min-hter":
threshold = min_hter_threshold(all_negs, pos, is_sorted=is_sorted)
else: else:
raise ValueError("Unknown threshold criteria: {}".format(method)) raise ValueError("Unknown threshold criteria: {}".format(method))
...@@ -63,11 +75,7 @@ def calc_pass_rate(threshold, attacks): ...@@ -63,11 +75,7 @@ def calc_pass_rate(threshold, attacks):
return (attacks >= threshold).mean() return (attacks >= threshold).mean()
def weighted_neg_error_rate_criteria(data, def weighted_neg_error_rate_criteria(data, weight, thres, beta=0.5, criteria="eer"):
weight,
thres,
beta=0.5,
criteria='eer'):
"""Given the single value for the weight parameter balancing between """Given the single value for the weight parameter balancing between
impostors and spoofing attacks and a threshold, calculates the error rates impostors and spoofing attacks and a threshold, calculates the error rates
and their relationship depending on the criteria (difference in case of and their relationship depending on the criteria (difference in case of
...@@ -100,26 +108,21 @@ def weighted_neg_error_rate_criteria(data, ...@@ -100,26 +108,21 @@ def weighted_neg_error_rate_criteria(data,
far_w = (1 - weight) * far_i + weight * far_s far_w = (1 - weight) * far_i + weight * far_s
if criteria == 'eer': if criteria == "eer":
if beta == 0.5: if beta == 0.5:
return abs(far_w - frr) return abs(far_w - frr)
else: else:
# return abs(far_w - frr) # return abs(far_w - frr)
return abs((1 - beta) * frr - beta * far_w) return abs((1 - beta) * frr - beta * far_w)
elif criteria == 'min-hter': elif criteria == "min-hter":
return (far_w + frr) / 2 return (far_w + frr) / 2
else: else:
return (1 - beta) * frr + beta * far_w return (1 - beta) * frr + beta * far_w
def recursive_thr_search(data, def recursive_thr_search(data, span_min, span_max, weight, beta=0.5, criteria="eer"):
span_min,
span_max,
weight,
beta=0.5,
criteria='eer'):
"""Recursive search for the optimal threshold given a criteria. It """Recursive search for the optimal threshold given a criteria. It
evaluates the full range of thresholds at 100 points, and computes the one evaluates the full range of thresholds at 100 points, and computes the one
which optimizes the threshold. In the next search iteration, it examines which optimizes the threshold. In the next search iteration, it examines
...@@ -148,29 +151,27 @@ def recursive_thr_search(data, ...@@ -148,29 +151,27 @@ def recursive_thr_search(data,
return span_max # or span_min, it doesn't matter return span_max # or span_min, it doesn't matter
else: else:
step_size = (span_max - span_min) / steps step_size = (span_max - span_min) / steps
thresholds = numpy.array( thresholds = numpy.array([(i * step_size) + span_min for i in range(steps + 1)])
[(i * step_size) + span_min for i in range(steps + 1)]) weighted_error_rates = numpy.array(
weighted_error_rates = numpy.array([ [
weighted_neg_error_rate_criteria(data, weight, thr, beta, criteria) weighted_neg_error_rate_criteria(data, weight, thr, beta, criteria)
for thr in thresholds for thr in thresholds
]) ]
selected_thres = thresholds[numpy.where( )
weighted_error_rates == min(weighted_error_rates) selected_thres = thresholds[
)] # all the thresholds which have minimum weighted error rate numpy.where(weighted_error_rates == min(weighted_error_rates))
thr = selected_thres[int( ] # all the thresholds which have minimum weighted error rate
selected_thres.size / 2 thr = selected_thres[
)] # choose the centrally positioned threshold int(selected_thres.size / 2)
return recursive_thr_search(data, thr - step_size, thr + step_size, ] # choose the centrally positioned threshold
weight, beta, criteria) return recursive_thr_search(
data, thr - step_size, thr + step_size, weight, beta, criteria
)
def weighted_negatives_threshold(licit_neg,
licit_pos,
spoof_neg, def weighted_negatives_threshold(
spoof_pos, licit_neg, licit_pos, spoof_neg, spoof_pos, weight, beta=0.5, criteria="eer"
weight, ):
beta=0.5,
criteria='eer'):
"""Calculates the threshold for achieving the given criteria between the """Calculates the threshold for achieving the given criteria between the
FAR_w and the FRR, given the single value for the weight parameter FAR_w and the FRR, given the single value for the weight parameter
balancing between impostors and spoofing attacks and a single value for the balancing between impostors and spoofing attacks and a single value for the
...@@ -197,10 +198,13 @@ def weighted_negatives_threshold(licit_neg, ...@@ -197,10 +198,13 @@ def weighted_negatives_threshold(licit_neg,
span_max = max( span_max = max(
numpy.append(licit_pos, spoof_pos) numpy.append(licit_pos, spoof_pos)
) # the max of the span where we will search for the threshold ) # the max of the span where we will search for the threshold
data = (licit_neg, licit_pos, spoof_neg, data = (
spoof_pos) # pack the data into a single list licit_neg,
return recursive_thr_search(data, span_min, span_max, weight, beta, licit_pos,
criteria) spoof_neg,
spoof_pos,
) # pack the data into a single list
return recursive_thr_search(data, span_min, span_max, weight, beta, criteria)
def epsc_weights(licit_neg, licit_pos, spoof_neg, spoof_pos, points=100): def epsc_weights(licit_neg, licit_pos, spoof_neg, spoof_pos, points=100):
...@@ -215,14 +219,16 @@ def epsc_weights(licit_neg, licit_pos, spoof_neg, spoof_pos, points=100): ...@@ -215,14 +219,16 @@ def epsc_weights(licit_neg, licit_pos, spoof_neg, spoof_pos, points=100):
return weights return weights
def epsc_thresholds(licit_neg, def epsc_thresholds(
licit_pos, licit_neg,
spoof_neg, licit_pos,
spoof_pos, spoof_neg,
points=100, spoof_pos,
criteria='eer', points=100,
omega=None, criteria="eer",
beta=None): omega=None,
beta=None,
):
"""Calculates the optimal thresholds for EPSC, for a range of the weight """Calculates the optimal thresholds for EPSC, for a range of the weight
parameter balancing between impostors and spoofing attacks, and for a range parameter balancing between impostors and spoofing attacks, and for a range
of the beta parameter balancing between real accesses and all the negatives of the beta parameter balancing between real accesses and all the negatives
...@@ -249,32 +255,37 @@ def epsc_thresholds(licit_neg, ...@@ -249,32 +255,37 @@ def epsc_thresholds(licit_neg,
if omega is None: if omega is None:
omega = numpy.array([(i * step_size) for i in range(points + 1)]) omega = numpy.array([(i * step_size) for i in range(points + 1)])
elif not isinstance(omega, list) and not isinstance( elif (
omega, tuple) and not isinstance(omega, numpy.ndarray): not isinstance(omega, list)
and not isinstance(omega, tuple)
and not isinstance(omega, numpy.ndarray)
):
omega = numpy.array([omega]) omega = numpy.array([omega])
else: else:
omega = numpy.array(omega) omega = numpy.array(omega)
if beta is None: if beta is None:
beta = numpy.array([(i * step_size) for i in range(points + 1)]) beta = numpy.array([(i * step_size) for i in range(points + 1)])
elif not isinstance(beta, list) and not isinstance( elif (
beta, tuple) and not isinstance(beta, numpy.ndarray): not isinstance(beta, list)
and not isinstance(beta, tuple)
and not isinstance(beta, numpy.ndarray)
):
beta = numpy.array([beta]) beta = numpy.array([beta])
else: else:
beta = numpy.array(beta) beta = numpy.array(beta)
thresholds = numpy.ndarray([beta.size, omega.size], 'float64') thresholds = numpy.ndarray([beta.size, omega.size], "float64")
for bindex, b in enumerate(beta): for bindex, b in enumerate(beta):
thresholds[bindex, :] = numpy.array([ thresholds[bindex, :] = numpy.array(
weighted_negatives_threshold( [
licit_neg, weighted_negatives_threshold(
licit_pos, licit_neg, licit_pos, spoof_neg, spoof_pos, w, b, criteria=criteria
spoof_neg, )
spoof_pos, for w in omega
w, ],
b, "float64",
criteria=criteria) for w in omega )
], 'float64')
return omega, beta, thresholds return omega, beta, thresholds
...@@ -291,13 +302,9 @@ def weighted_err(error_1, error_2, weight): ...@@ -291,13 +302,9 @@ def weighted_err(error_1, error_2, weight):
return (1 - weight) * error_1 + weight * error_2 return (1 - weight) * error_1 + weight * error_2
def error_rates_at_weight(licit_neg, def error_rates_at_weight(
licit_pos, licit_neg, licit_pos, spoof_neg, spoof_pos, omega, threshold, beta=0.5
spoof_neg, ):
spoof_pos,
omega,
threshold,
beta=0.5):
"""Calculates several error rates: FRR, FAR (zero-effort impostors), SFAR, """Calculates several error rates: FRR, FAR (zero-effort impostors), SFAR,
FAR_w, HTER_w for a given value of w. It returns the calculated threshold FAR_w, HTER_w for a given value of w. It returns the calculated threshold
as a last argument as a last argument
...@@ -317,11 +324,11 @@ negative samples (impostors and spoofing attacks). ...@@ -317,11 +324,11 @@ negative samples (impostors and spoofing attacks).
""" """
farfrr_licit = bob.measure.farfrr( farfrr_licit = bob.measure.farfrr(
licit_neg, licit_pos, licit_neg, licit_pos, threshold
threshold) # calculate test frr @ threshold (licit scenario) ) # calculate test frr @ threshold (licit scenario)
farfrr_spoof = bob.measure.farfrr( farfrr_spoof = bob.measure.farfrr(
spoof_neg, spoof_pos, spoof_neg, spoof_pos, threshold
threshold) # calculate test frr @ threshold (spoof scenario) ) # calculate test frr @ threshold (spoof scenario)
# we can take this value from farfrr_spoof as well, it doesn't matter # we can take this value from farfrr_spoof as well, it doesn't matter
frr = farfrr_licit[1] frr = farfrr_licit[1]
...@@ -335,8 +342,9 @@ negative samples (impostors and spoofing attacks). ...@@ -335,8 +342,9 @@ negative samples (impostors and spoofing attacks).
return (frr, far, sfar, far_w, wer_wb, hter_w, threshold) return (frr, far, sfar, far_w, wer_wb, hter_w, threshold)
def epsc_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, def epsc_error_rates(
omega, beta): licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, omega, beta
):
"""Calculates several error rates: FAR_w and WER_wb for the given weights """Calculates several error rates: FAR_w and WER_wb for the given weights
(omega and beta) and thresholds (the thresholds need to be computed first (omega and beta) and thresholds (the thresholds need to be computed first
using the method: epsc_thresholds() before passing to this method) using the method: epsc_thresholds() before passing to this method)
...@@ -368,13 +376,20 @@ def epsc_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, ...@@ -368,13 +376,20 @@ def epsc_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds,
WER_wb WER_wb
""" """
far_w_errors = numpy.ndarray((beta.size, omega.size), 'float64') far_w_errors = numpy.ndarray((beta.size, omega.size), "float64")
wer_wb_errors = numpy.ndarray((beta.size, omega.size), 'float64') wer_wb_errors = numpy.ndarray((beta.size, omega.size), "float64")
for bindex, b in enumerate(beta): for bindex, b in enumerate(beta):
errors = [ errors = [
error_rates_at_weight(licit_neg, licit_pos, spoof_neg, spoof_pos, error_rates_at_weight(
w, thresholds[bindex, windex], b) licit_neg,
licit_pos,
spoof_neg,
spoof_pos,
w,
thresholds[bindex, windex],
b,
)
for windex, w in enumerate(omega) for windex, w in enumerate(omega)
] ]
far_w_errors[bindex, :] = [errors[i][3] for i in range(len(errors))] far_w_errors[bindex, :] = [errors[i][3] for i in range(len(errors))]
...@@ -383,8 +398,9 @@ def epsc_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, ...@@ -383,8 +398,9 @@ def epsc_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds,
return far_w_errors, wer_wb_errors return far_w_errors, wer_wb_errors
def all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, def all_error_rates(
omega, beta): licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, omega, beta
):
"""Calculates several error rates: FAR_w and WER_wb for the given weights """Calculates several error rates: FAR_w and WER_wb for the given weights
(omega and beta) and thresholds (the thresholds need to be computed first (omega and beta) and thresholds (the thresholds need to be computed first
using the method: epsc_thresholds() before passing to this method) using the method: epsc_thresholds() before passing to this method)
...@@ -416,17 +432,24 @@ def all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, ...@@ -416,17 +432,24 @@ def all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds,
WER_wb WER_wb
""" """
frr_errors = numpy.ndarray((beta.size, omega.size), 'float64') frr_errors = numpy.ndarray((beta.size, omega.size), "float64")
far_errors = numpy.ndarray((beta.size, omega.size), 'float64') far_errors = numpy.ndarray((beta.size, omega.size), "float64")
sfar_errors = numpy.ndarray((beta.size, omega.size), 'float64') sfar_errors = numpy.ndarray((beta.size, omega.size), "float64")
far_w_errors = numpy.ndarray((beta.size, omega.size), 'float64') far_w_errors = numpy.ndarray((beta.size, omega.size), "float64")
wer_wb_errors = numpy.ndarray((beta.size, omega.size), 'float64') wer_wb_errors = numpy.ndarray((beta.size, omega.size), "float64")
hter_wb_errors = numpy.ndarray((beta.size, omega.size), 'float64') hter_wb_errors = numpy.ndarray((beta.size, omega.size), "float64")
for bindex, b in enumerate(beta): for bindex, b in enumerate(beta):
errors = [ errors = [
error_rates_at_weight(licit_neg, licit_pos, spoof_neg, spoof_pos, error_rates_at_weight(
w, thresholds[bindex, windex], b) licit_neg,
licit_pos,
spoof_neg,
spoof_pos,
w,
thresholds[bindex, windex],
b,
)
for windex, w in enumerate(omega) for windex, w in enumerate(omega)
] ]
frr_errors[bindex, :] = [errors[i][0] for i in range(len(errors))] frr_errors[bindex, :] = [errors[i][0] for i in range(len(errors))]
...@@ -436,20 +459,28 @@ def all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, ...@@ -436,20 +459,28 @@ def all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds,
wer_wb_errors[bindex, :] = [errors[i][4] for i in range(len(errors))] wer_wb_errors[bindex, :] = [errors[i][4] for i in range(len(errors))]
hter_wb_errors[bindex, :] = [errors[i][5] for i in range(len(errors))] hter_wb_errors[bindex, :] = [errors[i][5] for i in range(len(errors))]
return (frr_errors, far_errors, sfar_errors, far_w_errors, wer_wb_errors, return (
hter_wb_errors) frr_errors,
far_errors,
sfar_errors,
def calc_aue(licit_neg, far_w_errors,
licit_pos, wer_wb_errors,
spoof_neg, hter_wb_errors,
spoof_pos, )
thresholds,
omega,
beta, def calc_aue(
l_bound=0, licit_neg,
h_bound=1, licit_pos,
var_param='omega'): spoof_neg,
spoof_pos,
thresholds,
omega,
beta,
l_bound=0,
h_bound=1,
var_param="omega",
):
"""Calculates AUE of EPSC for the given thresholds and weights """Calculates AUE of EPSC for the given thresholds and weights
Keyword arguments: Keyword arguments:
...@@ -468,13 +499,15 @@ def calc_aue(licit_neg, ...@@ -468,13 +499,15 @@ def calc_aue(licit_neg,
from scipy import integrate from scipy import integrate
if var_param == 'omega': if var_param == "omega":
errors = all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, errors = all_error_rates(
thresholds, omega, beta) licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, omega, beta
)
weights = omega # setting the weights to the varying parameter weights = omega # setting the weights to the varying parameter
else: else:
errors = all_error_rates(licit_neg, licit_pos, spoof_neg, spoof_pos, errors = all_error_rates(
thresholds, omega, beta) licit_neg, licit_pos, spoof_neg, spoof_pos, thresholds, omega, beta
)
weights = beta # setting the weights to the varying parameter weights = beta # setting the weights to the varying parameter
wer_errors = errors[4].reshape(1, errors[4].size) wer_errors = errors[4].reshape(1, errors[4].size)
...@@ -482,8 +515,147 @@ def calc_aue(licit_neg, ...@@ -482,8 +515,147 @@ def calc_aue(licit_neg,
l_ind = numpy.where(weights >= l_bound)[0][0] l_ind = numpy.where(weights >= l_bound)[0][0]
h_ind = numpy.where(weights <= h_bound)[0][-1] h_ind = numpy.where(weights <= h_bound)[0][-1]
aue = integrate.cumtrapz(wer_errors, weights) aue = integrate.cumtrapz(wer_errors, weights)
aue = numpy.append( aue = numpy.append([0], aue) # for indexing purposes, aue is cumulative integration
[0], aue) # for indexing purposes, aue is cumulative integration
aue = aue[h_ind] - aue[l_ind] aue = aue[h_ind] - aue[l_ind]