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

1#!/usr/bin/env python 

2# @author: Yannick Dayer <yannick.dayer@idiap.ch> 

3# @date: Thu 21 Oct 2021 14:14:07 UTC+02 

4 

5"""Implements the linear scoring function.""" 

6 

7import logging 

8 

9from typing import Union 

10 

11import numpy as np 

12 

13from .gmm import GMMMachine, GMMStats 

14 

15logger = logging.getLogger(__name__) 

16 

17EPSILON = np.finfo(float).eps 

18 

19 

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. 

28 

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] 

32 

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. 

45 

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, :, :] 

61 

62 if ubm.trainer == "map": 

63 ubm = ubm.ubm 

64 

65 if isinstance(test_stats, GMMStats): 

66 test_stats = [test_stats] 

67 

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) 

76 

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)