Coverage for src/bob/learn/em/linear_scoring.py: 93%
30 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-06-16 14:34 +0200
« prev ^ index » next coverage.py v7.0.5, created at 2023-06-16 14:34 +0200
1#!/usr/bin/env python
2# @author: Yannick Dayer <yannick.dayer@idiap.ch>
3# @date: Thu 21 Oct 2021 14:14:07 UTC+02
5"""Implements the linear scoring function."""
7import logging
9from typing import Union
11import numpy as np
13from .gmm import GMMMachine, GMMStats
15logger = logging.getLogger(__name__)
17EPSILON = np.finfo(float).eps
20def linear_scoring(
21 models_means: "Union[list[GMMMachine], np.ndarray[('n_models', 'n_gaussians', 'n_features'), float]]", # noqa: F821
22 ubm: GMMMachine,
23 test_stats: Union["list[GMMStats]", GMMStats],
24 test_channel_offsets: "np.ndarray[('n_test_stats', 'n_gaussians'), float]" = 0, # noqa: F821
25 frame_length_normalization: bool = False,
26) -> "np.ndarray[('n_models', 'n_test_stats'), float]": # noqa: F821
27 """Estimation of the LLR between a target model and the UBM for a test instance.
29 The Linear scoring is an approximation to the log-likelihood ratio (LLR) that was
30 shown to be as accurate and up to two orders of magnitude more efficient to
31 compute. [Glembek2009]
33 Parameters
34 ----------
35 models_means
36 The model(s) to score against. If a list of `GMMMachine` is given, the means
37 of each model are considered.
38 ubm:
39 The Universal Background Model. Accepts a `GMMMachine` object. If the
40 `GMMMachine` uses MAP, it's `ubm` attribute is used.
41 test_stats:
42 The instances to score.
43 test_channel_offsets
44 Offset values added to the test instances.
46 Returns
47 -------
48 Array of shape (n_models, n_probes)
49 The scores of each probe against each model.
50 """
51 if isinstance(models_means[0], GMMMachine):
52 models_means = np.array([model.means for model in models_means])
53 if not hasattr(models_means, "ndim"):
54 models_means = np.array(models_means)
55 if models_means.ndim < 2:
56 raise ValueError(
57 "models_means must be of shape `(n_models, n_gaussians, n_features)`."
58 )
59 if models_means.ndim == 2:
60 models_means = models_means[None, :, :]
62 if ubm.trainer == "map":
63 ubm = ubm.ubm
65 if isinstance(test_stats, GMMStats):
66 test_stats = [test_stats]
68 # All stats.sum_px [array of shape (n_test_stats, n_gaussians, n_features)]
69 sum_px = np.array([stat.sum_px for stat in test_stats])
70 # All stats.n [array of shape (n_test_stats, n_gaussians)]
71 n = np.array([stat.n for stat in test_stats])
72 # All stats.t [array of shape (n_test_stats,)]
73 t = np.array([stat.t for stat in test_stats])
74 # Offsets [array of shape (n_test_stats, `n_gaussians * n_features`)]
75 test_channel_offsets = np.array(test_channel_offsets)
77 # Compute A [array of shape (n_models, n_gaussians * n_features)]
78 a = (models_means - ubm.means) / ubm.variances
79 # Compute B [array of shape (n_gaussians * n_features, n_test_stats)]
80 b = sum_px[:, :, :] - (
81 n[:, :, None] * (ubm.means[None, :, :] + test_channel_offsets)
82 )
83 b = np.transpose(b, axes=(1, 2, 0))
84 # Apply normalization if needed.
85 if frame_length_normalization:
86 b = np.where(abs(t) <= EPSILON, 0, b[:, :] / t[None, :])
87 return np.tensordot(a, b, 2)