Coverage for src/bob/learn/em/wccn.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-06-16 14:34 +0200

1#!/usr/bin/env python 

2# @author: Tiago de Freitas Pereira 

3 

4import dask 

5 

6# Dask doesn't have an implementation for `pinv` 

7from scipy.linalg import pinv 

8from sklearn.base import BaseEstimator, TransformerMixin 

9 

10 

11class WCCN(TransformerMixin, BaseEstimator): 

12 """ 

13 Trains a linear machine to perform Within-Class Covariance Normalization (WCCN) 

14 WCCN finds the projection matrix W that allows us to linearly project the data matrix X to another (sub) space such that: 

15 

16 .. math:: 

17 (1/N) S_{w} = W W^T 

18 

19 where :math:`W` is an upper triangular matrix computed using Cholesky Decomposition: 

20 

21 .. math:: 

22 W = cholesky([(1/K) S_{w} ]^{-1}) 

23 

24 

25 where: 

26 - :math:`K` the number of classes 

27 - :math:`S_w` the within-class scatter; it also has dimensions ``(X.shape[0], X.shape[0])`` and is defined as :math:`S_w = \\sum_{k=1}^K \\sum_{n \\in C_k} (x_n-m_k)(x_n-m_k)^T`, with :math:`C_k` being a set representing all samples for class k. 

28 - :math:`m_k` the class *k* empirical mean, defined as :math:`m_k = \\frac{1}{N_k}\\sum_{n \\in C_k} x_n` 

29 

30 

31 References: 

32 - 1. Within-class covariance normalization for SVM-based speaker recognition, Andrew O. Hatch, Sachin Kajarekar, and Andreas Stolcke, In INTERSPEECH, 2006. 

33 - 2. http://en.wikipedia.org/wiki/Cholesky_decomposition" 

34 

35 """ 

36 

37 def __init__(self, pinv=False, **kwargs): 

38 super().__init__(**kwargs) 

39 self.pinv = pinv 

40 

41 def fit(self, X, y): 

42 

43 # CHECKING THE TYPES 

44 if isinstance(X, dask.array.Array): 

45 import dask.array as numerical_module 

46 

47 from dask.array.linalg import cholesky, inv 

48 else: 

49 import numpy as numerical_module 

50 

51 from scipy.linalg import cholesky, inv 

52 

53 X = numerical_module.array(X) 

54 

55 possible_labels = set(y) 

56 y_ = numerical_module.array(y) 

57 

58 n_classes = len(possible_labels) 

59 

60 # 1. compute the means for each label 

61 mu_l = numerical_module.array( 

62 [ 

63 numerical_module.mean( 

64 X[numerical_module.where(y_ == label)[0]], axis=0 

65 ) 

66 for label in possible_labels 

67 ] 

68 ) 

69 

70 # 2. Compute Sw 

71 Sw = numerical_module.zeros((X.shape[1], X.shape[1]), dtype=float) 

72 

73 for label in possible_labels: 

74 indexes = numerical_module.where(y_ == label)[0] 

75 X_l_mu_l = X[indexes] - mu_l[label] 

76 

77 Sw += X_l_mu_l.T @ X_l_mu_l 

78 

79 # 3. Compute inv 

80 scaled_Sw = (1 / n_classes) * Sw 

81 inv_scaled_Sw = pinv(scaled_Sw) if self.pinv else inv(scaled_Sw) 

82 

83 # 3. Computes the Cholesky decomposition 

84 self.weights = cholesky( 

85 inv_scaled_Sw, lower=True 

86 ) # Setting lower true to have the same implementation as in the previous code 

87 self.input_subtract = 0 

88 self.input_divide = 1.0 

89 

90 return self 

91 

92 def transform(self, X): 

93 

94 return [ 

95 ((x - self.input_subtract) / self.input_divide) @ self.weights 

96 for x in X 

97 ]