# Source code for bob.measure.calibration

#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Thu May 16 11:41:49 CEST 2013

"""Measures for calibration"""

import math
import numpy

[docs]def cllr(negatives, positives):
"""Cost of log likelihood ratio as defined by the Bosaris toolkit

Computes the 'cost of log likelihood ratio' (:math:C_{llr}) measure as
given in the Bosaris toolkit

Parameters:

negatives (array): 1D float array that contains the scores of the
"negative" (noise, non-class) samples of your classifier.

positives (array): 1D float array that contains the scores of the
"positive" (signal, class) samples of your classifier.

Returns:

float: The computed :math:C_{llr} value.

"""
sum_pos, sum_neg = 0., 0.
for pos in positives:
sum_pos += math.log(1. + math.exp(-pos), 2.)
for neg in negatives:
sum_neg += math.log(1. + math.exp(neg), 2.)
return (sum_pos / len(positives) + sum_neg / len(negatives)) / 2.

[docs]def min_cllr(negatives, positives):
"""Minimum cost of log likelihood ratio as defined by the Bosaris toolkit

Computes the 'minimum cost of log likelihood ratio' (:math:C_{llr}^{min})
measure as given in the bosaris toolkit

Parameters:

negatives (array): 1D float array that contains the scores of the
"negative" (noise, non-class) samples of your classifier.

positives (array): 1D float array that contains the scores of the
"positive" (signal, class) samples of your classifier.

Returns:

float: The computed :math:C_{llr}^{min} value.

"""

from bob.math import pavx

# first, sort both scores
neg = sorted(negatives)
pos = sorted(positives)
N = len(neg)
P = len(pos)
I = N + P
# now, iterate through both score sets and add a 0 for negative and 1 for
# positive scores
n, p = 0, 0
ideal = numpy.zeros(I)
neg_indices = [0] * N
pos_indices = [0] * P
for i in range(I):
if p < P and (n == N or neg[n] > pos[p]):
pos_indices[p] = i
p += 1
ideal[i] = 1
else:
neg_indices[n] = i
n += 1

# compute the pool adjacent violaters method on the ideal LLR scores
popt = numpy.ndarray(ideal.shape, dtype=numpy.float)
pavx(ideal, popt)

# disable runtime warnings for a short time since log(0) will raise a warning
old_warn_setup = numpy.seterr(divide='ignore')
# ... compute logs
posterior_log_odds = numpy.log(popt) - numpy.log(1. - popt)
log_prior_odds = math.log(float(P) / float(N))
# ... activate old warnings
numpy.seterr(**old_warn_setup)

llrs = posterior_log_odds - log_prior_odds

#  for i in range(I):
#    llrs[i] += float(i)*1e-6/float(I)

# unmix positive and negative scores
new_neg = numpy.zeros(N)
for n in range(N):
new_neg[n] = llrs[neg_indices[n]]
new_pos = numpy.zeros(P)
for p in range(P):
new_pos[p] = llrs[pos_indices[p]]

# compute cllr of these new 'optimal' LLR scores
return cllr(new_neg, new_pos)