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
« 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
4import dask
6# Dask doesn't have an implementation for `pinv`
7from scipy.linalg import pinv
8from sklearn.base import BaseEstimator, TransformerMixin
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:
16 .. math::
17 (1/N) S_{w} = W W^T
19 where :math:`W` is an upper triangular matrix computed using Cholesky Decomposition:
21 .. math::
22 W = cholesky([(1/K) S_{w} ]^{-1})
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`
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"
35 """
37 def __init__(self, pinv=False, **kwargs):
38 super().__init__(**kwargs)
39 self.pinv = pinv
41 def fit(self, X, y):
43 # CHECKING THE TYPES
44 if isinstance(X, dask.array.Array):
45 import dask.array as numerical_module
47 from dask.array.linalg import cholesky, inv
48 else:
49 import numpy as numerical_module
51 from scipy.linalg import cholesky, inv
53 X = numerical_module.array(X)
55 possible_labels = set(y)
56 y_ = numerical_module.array(y)
58 n_classes = len(possible_labels)
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 )
70 # 2. Compute Sw
71 Sw = numerical_module.zeros((X.shape[1], X.shape[1]), dtype=float)
73 for label in possible_labels:
74 indexes = numerical_module.where(y_ == label)[0]
75 X_l_mu_l = X[indexes] - mu_l[label]
77 Sw += X_l_mu_l.T @ X_l_mu_l
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)
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
90 return self
92 def transform(self, X):
94 return [
95 ((x - self.input_subtract) / self.input_divide) @ self.weights
96 for x in X
97 ]