From 6b5858522b4229f7b2490286bd9becfcb3d612ee Mon Sep 17 00:00:00 2001 From: Manuel Guenther <manuel.guenther@idiap.ch> Date: Wed, 6 May 2015 11:41:12 +0200 Subject: [PATCH] Added LDA algorithm and tests --- bob/bio/base/algorithm/LDA.py | 183 ++++++++++++++++++++++ bob/bio/base/algorithm/PCA.py | 6 +- bob/bio/base/algorithm/__init__.py | 1 + bob/bio/base/config/algorithm/lda.py | 10 ++ bob/bio/base/config/algorithm/pca_lda.py | 11 ++ bob/bio/base/test/data/lda_model.hdf5 | Bin 0 -> 2344 bytes bob/bio/base/test/data/lda_projected.hdf5 | Bin 0 -> 2184 bytes bob/bio/base/test/data/lda_projector.hdf5 | Bin 0 -> 16712 bytes bob/bio/base/test/test_algorithms.py | 147 +++++++++-------- setup.py | 2 + 10 files changed, 288 insertions(+), 72 deletions(-) create mode 100644 bob/bio/base/algorithm/LDA.py create mode 100644 bob/bio/base/config/algorithm/lda.py create mode 100644 bob/bio/base/config/algorithm/pca_lda.py create mode 100644 bob/bio/base/test/data/lda_model.hdf5 create mode 100644 bob/bio/base/test/data/lda_projected.hdf5 create mode 100644 bob/bio/base/test/data/lda_projector.hdf5 diff --git a/bob/bio/base/algorithm/LDA.py b/bob/bio/base/algorithm/LDA.py new file mode 100644 index 00000000..bd3940f4 --- /dev/null +++ b/bob/bio/base/algorithm/LDA.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Manuel Guenther <Manuel.Guenther@idiap.ch> + +import bob.io.base +import bob.learn.linear + +import numpy +import scipy.spatial + +from .Algorithm import Algorithm + +import logging +logger = logging.getLogger("bob.bio.base") + +class LDA (Algorithm): + """Tool for computing linear discriminant analysis (so-called Fisher faces)""" + + def __init__( + self, + lda_subspace_dimension = 0, # if set, the LDA subspace will be truncated to the given number of dimensions; by default it is limited to the number of classes in the training set + pca_subspace_dimension = None, # if set, a PCA subspace truncation is performed before applying LDA; might be integral or float + distance_function = scipy.spatial.distance.euclidean, + is_distance_function = True, + uses_variances = False, + **kwargs # parameters directly sent to the base class + ): + """Initializes the LDA tool with the given configuration""" + + # call base class constructor and register that the LDA tool performs projection and need the training features split by client + Algorithm.__init__( + self, + performs_projection = True, + split_training_features_by_client = True, + + lda_subspace_dimension = lda_subspace_dimension, + pca_subspace_dimension = pca_subspace_dimension, + distance_function = str(distance_function), + is_distance_function = is_distance_function, + uses_variances = uses_variances, + + **kwargs + ) + + # copy information + self.pca_subspace = pca_subspace_dimension + self.lda_subspace = lda_subspace_dimension + if self.pca_subspace and isinstance(self.pca_subspace, int) and self.lda_subspace and self.pca_subspace < self.lda_subspace: + raise ValueError("The LDA subspace is larger than the PCA subspace size. This won't work properly. Please check your setup!") + + self.machine = None + self.distance_function = distance_function + self.factor = -1 if is_distance_function else 1. + self.uses_variances = uses_variances + + + def _check_feature(self, feature): + """Checks that the features are appropriate""" + if not isinstance(feature, numpy.ndarray) or len(feature.shape) != 1 or feature.dtype != numpy.float64: + raise ValueError("The given feature is not appropriate") + + + def _arrange_data(self, training_files): + """Arranges the data to train the LDA projection matrix""" + data = [] + for client_files in training_files: + # at least two files per client are required! + if len(client_files) < 2: + logger.warn("Skipping one client since the number of client files is only %d", len(client_files)) + continue + data.append(numpy.vstack([feature.flatten() for feature in client_files])) + + # Returns the list of lists of arrays + return data + + + def _train_pca(self, training_set): + """Trains and returns a LinearMachine that is trained using PCA""" + data_list = [feature for client in training_set for feature in client] + data = numpy.vstack(data_list) + + logger.info(" -> Training Linear Machine using PCA") + t = bob.learn.linear.PCATrainer() + machine, eigen_values = t.train(data) + + if isinstance(self.pca_subspace, float): + cummulated = numpy.cumsum(eigen_values) / numpy.sum(eigen_values) + for index in range(len(cummulated)): + if cummulated[index] > self.pca_subspace: + self.pca_subspace = index + break + self.pca_subspace = index + + if self.lda_subspace and self.pca_subspace <= self.lda_subspace: + logger.warn(" ... Extending the PCA subspace dimension from %d to %d", self.pca_subspace, self.lda_subspace + 1) + self.pca_subspace = self.lda_subspace + 1 + else: + logger.info(" ... Limiting PCA subspace to %d dimensions", self.pca_subspace) + + # limit number of pcs + machine.resize(machine.shape[0], self.pca_subspace) + return machine + + + def _perform_pca(self, machine, training_set): + """Perform PCA on data of the training set""" + return [numpy.vstack([machine(feature) for feature in client_features]) for client_features in training_set] + + + def train_projector(self, training_features, projector_file): + """Generates the LDA projection matrix from the given features (that are sorted by identity)""" + # check data + [self._check_feature(feature) for client_features in training_features for feature in client_features] + + # arrange LDA training data + data = self._arrange_data(training_features) + + # train PCA of wanted + if self.pca_subspace: + # train on all training features + pca_machine = self._train_pca(training_features) + # project only the features that are used for training + logger.info(" -> Projecting training data to PCA subspace") + data = self._perform_pca(pca_machine, data) + + logger.info(" -> Training Linear Machine using LDA") + trainer = bob.learn.linear.FisherLDATrainer(strip_to_rank = (self.lda_subspace == 0)) + self.machine, self.variances = trainer.train(data) + if self.lda_subspace: + self.machine.resize(self.machine.shape[0], self.lda_subspace) + self.variances = self.variances.copy() + self.variances.resize(self.lda_subspace) + + if self.pca_subspace: + # compute combined PCA/LDA projection matrix + combined_matrix = numpy.dot(pca_machine.weights, self.machine.weights) + # set new weight matrix (and new mean vector) of novel machine + self.machine = bob.learn.linear.Machine(combined_matrix) + self.machine.input_subtract = pca_machine.input_subtract + + hdf5 = bob.io.base.HDF5File(projector_file, "w") + hdf5.set("Eigenvalues", self.variances) + hdf5.create_group("/Machine") + hdf5.cd("/Machine") + self.machine.save(hdf5) + + + def load_projector(self, projector_file): + """Reads the LDA projection matrix from file""" + # read LDA projector + hdf5 = bob.io.base.HDF5File(projector_file) + self.variances = hdf5.read("Eigenvalues") + hdf5.cd("/Machine") + self.machine = bob.learn.linear.Machine(hdf5) + + + def project(self, feature): + """Projects the data using the stored covariance matrix""" + self._check_feature(feature) + # Projects the data + return self.machine(feature) + + + def enroll(self, enroll_features): + """Enrolls the model by storing all given input vectors""" + assert len(enroll_features) + [self._check_feature(feature) for feature in enroll_features] + # just store all the features + return numpy.vstack(enroll_features) + + + def score(self, model, probe): + """Computes the distance of the model to the probe using the distance function""" + # return the negative distance (as a similarity measure) + if len(model.shape) == 2: + # we have multiple models, so we use the multiple model scoring + return self.score_for_multiple_models(model, probe) + elif self.uses_variances: + # single model, single probe (multiple probes have already been handled) + return self.factor * self.distance_function(model, probe, self.variances) + else: + # single model, single probe (multiple probes have already been handled) + return self.factor * self.distance_function(model, probe) diff --git a/bob/bio/base/algorithm/PCA.py b/bob/bio/base/algorithm/PCA.py index f9141a17..0866f319 100644 --- a/bob/bio/base/algorithm/PCA.py +++ b/bob/bio/base/algorithm/PCA.py @@ -47,8 +47,8 @@ class PCA (Algorithm): def _check_feature(self, feature): - """Checks that the features are apropriate""" - if not isinstance(feature, numpy.ndarray) or len(feature.shape) != 1: + """Checks that the features are appropriate""" + if not isinstance(feature, numpy.ndarray) or len(feature.shape) != 1 or feature.dtype != numpy.float64: raise ValueError("The given feature is not appropriate") @@ -103,8 +103,8 @@ class PCA (Algorithm): def enroll(self, enroll_features): """Enrolls the model by storing all given input vectors""" - [self._check_feature(feature) for feature in enroll_features] assert len(enroll_features) + [self._check_feature(feature) for feature in enroll_features] # just store all the features return numpy.vstack(enroll_features) diff --git a/bob/bio/base/algorithm/__init__.py b/bob/bio/base/algorithm/__init__.py index e5890597..0866bd1e 100644 --- a/bob/bio/base/algorithm/__init__.py +++ b/bob/bio/base/algorithm/__init__.py @@ -1,2 +1,3 @@ from .Algorithm import Algorithm from .PCA import PCA +from .LDA import LDA diff --git a/bob/bio/base/config/algorithm/lda.py b/bob/bio/base/config/algorithm/lda.py new file mode 100644 index 00000000..58dff6ed --- /dev/null +++ b/bob/bio/base/config/algorithm/lda.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import bob.bio.base +import scipy.spatial + +algorithm = bob.bio.base.algorithm.LDA( + subspace_dimension = 50, + distance_function = scipy.spatial.distance.euclidean, + is_distance_function = True +) diff --git a/bob/bio/base/config/algorithm/pca_lda.py b/bob/bio/base/config/algorithm/pca_lda.py new file mode 100644 index 00000000..8bf7c4a3 --- /dev/null +++ b/bob/bio/base/config/algorithm/pca_lda.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import bob.bio.base +import scipy.spatial + +algorithm = bob.bio.base.algorithm.LDA( + subspace_dimension = 50, + pca_subspace_dimension = 100, + distance_function = scipy.spatial.distance.euclidean, + is_distance_function = True +) diff --git a/bob/bio/base/test/data/lda_model.hdf5 b/bob/bio/base/test/data/lda_model.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..55b34d75582aac2d9b51898ca1dd5fc7c705a4c1 GIT binary patch literal 2344 zcmeD5aB<`1lHy_j0S*oZ76t(@6Gr@p0u4@x2#gPtPk=HQp>zk7Ucm%mFfxE31A_!q zTo7tLy1I}cS62q0N|^aD8mf)KfCa+hfC-G!BPs+uTpa^I9*%(e8kR~=K+_p4FcOQ3 z5-WimSbFq;Nsvi1GO$6+f*Q!kpaC|CkqIKe3N;rO%?wQWAeDj&_6(4;>%d^b&0zAM z8KRLDC<>BiVuBdR0aee;m;g=cC!jV!C?TjRAUy#$Jwq87GEn4c%?bANcLC*SsN)zE zp#Gq>y`$!jhQMeD480I|En8yUdQ-#U_=834u6aQY5AP}55&fO!aM0jNdOnYsLuKQ+ zwSkUa4uz)n*S<W_bda&^J0<KI?7*Vh&(oxt>TqeTrf2SwV28yzVppYRB{^I$f7m{K zuc3okXM`*3vTTQ$hi;et_~YPEsmZPLcdMJj`x==)6X(V|I7j6%oodf^*tM=Z;I~1B zL+aVa*&6Tj9CFJQSH3XLa7a7)@X6P@2nV65i>f{Nq8t?59n)C%ra4^tBe^2PG0<U( TxW}F<%WQ``bJ`fQrE?tsjSY$# literal 0 HcmV?d00001 diff --git a/bob/bio/base/test/data/lda_projected.hdf5 b/bob/bio/base/test/data/lda_projected.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..c5a800eb1fa3f2c8d0aa9abef1b30a86c6edc343 GIT binary patch literal 2184 zcmeD5aB<`1lHy_j0S*oZ76t(@6Gr@pf({Od2#gPtPk=HQp>zk7Ucm%mFfxE31A_!q zTo7tLy1I}cS62q0N|^aD8mf)KfCa*WIs+y=N{^5b@Njhu0C_b6>R(tYJpoN;uwY0m zDoU&ba$xDv113Qx&B(w8F$-!SBQzy5GC~Acq2|J9W}rMsMo__?0g_f77)-bsO#U-N z6tV(ELGnyYOke{zpz4_!6QD_318O3K5`vlnQXFv8Gn9d$0!5zQoM1nH7f_CdI+j5J z>Mwe`W7NLU5Eu=C!5;#*_~R~l=eavbrO!C^$|ukveyjD0)QB*L&d){{Kb=uOV3Iid JfVzvh0|4mnM3Ddh literal 0 HcmV?d00001 diff --git a/bob/bio/base/test/data/lda_projector.hdf5 b/bob/bio/base/test/data/lda_projector.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..f3ad8115a6a505d8f2375c5b7d140ca4d38a813c GIT binary patch literal 16712 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 RRsC1*YyP{);eVIU{{Xm(fsX(H literal 0 HcmV?d00001 diff --git a/bob/bio/base/test/test_algorithms.py b/bob/bio/base/test/test_algorithms.py index 609c332e..b9525276 100644 --- a/bob/bio/base/test/test_algorithms.py +++ b/bob/bio/base/test/test_algorithms.py @@ -33,6 +33,8 @@ import sys _mac_os = sys.platform == 'darwin' +import scipy.spatial + import bob.io.base import bob.learn.linear import bob.io.base.test_utils @@ -101,7 +103,7 @@ def test_pca(): assert numpy.allclose(pca1.machine.weights[:,i], pca2.machine.weights[:,i], atol=1e-5) or numpy.allclose(pca1.machine.weights[:,i], - pca2.machine.weights[:,i], atol=1e-5) finally: - os.remove(temp_file) + if os.path.exists(temp_file): os.remove(temp_file) # generate and project random feature feature = utils.random_array(200, 0., 255., seed=84) @@ -120,7 +122,6 @@ def test_pca(): assert abs(pca1.score(model, probe) - reference_score) < 1e-5, "The scores differ: %3.8f, %3.8f" % (pca1.score(model, probe), reference_score) assert abs(pca1.score_for_multiple_probes(model, [probe, probe]) - reference_score) < 1e-5 - # test the calculation of the subspace dimension based on percentage of variance pca3 = bob.bio.base.algorithm.PCA(.9) try: @@ -131,7 +132,81 @@ def test_pca(): pca3.load_projector(temp_file) assert pca3.machine.shape[1] == 140 finally: - os.remove(temp_file) + if os.path.exists(temp_file): os.remove(temp_file) + + +def test_lda(): + temp_file = bob.io.base.test_utils.temporary_filename() + # assure that the configurations are loadable + lda1 = bob.bio.base.load_resource("lda", "algorithm") + assert isinstance(lda1, bob.bio.base.algorithm.LDA) + assert isinstance(lda1, bob.bio.base.algorithm.Algorithm) + lda2 = bob.bio.base.load_resource("pca+lda", "algorithm") + assert isinstance(lda2, bob.bio.base.algorithm.LDA) + assert isinstance(lda2, bob.bio.base.algorithm.Algorithm) + + assert lda1.performs_projection + assert lda1.requires_projector_training + assert lda1.use_projected_features_for_enrollment + assert lda1.split_training_features_by_client + assert not lda1.requires_enroller_training + + # generate a smaller PCA subspcae + lda3 = bob.bio.base.algorithm.LDA(5, 10, scipy.spatial.distance.seuclidean, True, True) + + # create random training set + train_set = utils.random_training_set_by_id(200, count=20, minimum=0., maximum=255.) + # train the projector + reference_file = pkg_resources.resource_filename('bob.bio.base.test', 'data/lda_projector.hdf5') + try: + # train projector + lda3.train_projector(train_set, temp_file) + assert os.path.exists(temp_file) + + if regenerate_refs: shutil.copy(temp_file, reference_file) + + # check projection matrix + lda1.load_projector(reference_file) + lda3.load_projector(temp_file) + + assert numpy.allclose(lda1.variances, lda3.variances, atol=1e-5) + assert lda3.machine.shape == (200, 5) + assert lda1.machine.shape == lda3.machine.shape + # ... rotation direction might change, hence either the sum or the difference should be 0 + for i in range(5): + assert numpy.allclose(lda1.machine.weights[:,i], lda3.machine.weights[:,i], atol=1e-5) or numpy.allclose(lda1.machine.weights[:,i], - lda3.machine.weights[:,i], atol=1e-5) + + finally: + if os.path.exists(temp_file): os.remove(temp_file) + + # generate and project random feature + feature = utils.random_array(200, 0., 255., seed=84) + projected = lda1.project(feature) + assert projected.shape == (5,) + _compare(projected, pkg_resources.resource_filename('bob.bio.base.test', 'data/lda_projected.hdf5'), lda1.write_feature, lda1.read_feature) + + # enroll model from random features + enroll = utils.random_training_set(5, 5, 0., 255., seed=21) + model = lda1.enroll(enroll) + _compare(model, pkg_resources.resource_filename('bob.bio.base.test', 'data/lda_model.hdf5'), lda1.write_model, lda1.read_model) + + # compare model with probe + probe = lda1.read_probe(pkg_resources.resource_filename('bob.bio.base.test', 'data/lda_projected.hdf5')) + reference_score = -233.30450012 + assert abs(lda1.score(model, probe) - reference_score) < 1e-5, "The scores differ: %3.8f, %3.8f" % (lda1.score(model, probe), reference_score) + assert abs(lda1.score_for_multiple_probes(model, [probe, probe]) - reference_score) < 1e-5 + + # test the calculation of the subspace dimension based on percentage of variance + lda4 = bob.bio.base.algorithm.LDA(pca_subspace_dimension=.9) + try: + # train projector + lda4.train_projector(train_set, temp_file) + assert os.path.exists(temp_file) + assert lda4.pca_subspace == 132 + lda4.load_projector(temp_file) + assert lda4.machine.shape[1] == 19 + finally: + if os.path.exists(temp_file): os.remove(temp_file) """ @@ -200,72 +275,6 @@ def test_pca(): - def test04_lda(self): - # read input - feature = facereclib.utils.load(self.input_dir('linearize.hdf5')) - # assure that the config file is loadable - tool = self.config('lda') - self.assertTrue(isinstance(tool, facereclib.tools.LDA)) - # assure that the config file is loadable - tool = self.config('pca+lda') - self.assertTrue(isinstance(tool, facereclib.tools.LDA)) - - # here we use a reduced tool, using the scaled Euclidean distance (mahalanobis) from scipy - import scipy.spatial - tool = facereclib.tools.LDA(5, 10, scipy.spatial.distance.seuclidean, True, True) - self.assertTrue(tool.performs_projection) - self.assertTrue(tool.requires_projector_training) - self.assertTrue(tool.use_projected_features_for_enrollment) - self.assertTrue(tool.split_training_features_by_client) - - # train the projector - t = tempfile.mkstemp('pca+lda.hdf5', prefix='frltest_')[1] - tool.train_projector(facereclib.utils.tests.random_training_set_by_id(feature.shape, count=20, minimum=0., maximum=255.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('pca+lda_projector.hdf5')) - - # load the projector file - tool.load_projector(self.reference_dir('pca+lda_projector.hdf5')) - # compare the resulting machines - f = bob.io.base.HDF5File(t) - new_variances = f.read("Eigenvalues") - f.cd("/Machine") - new_machine = bob.learn.linear.Machine(f) - del f - self.assertEqual(tool.m_machine.shape, new_machine.shape) - self.assertTrue(numpy.abs(tool.m_variances - new_variances < 1e-5).all()) - # ... rotation direction might change, hence either the sum or the difference should be 0 - for i in range(5): - self.assertTrue(numpy.abs(tool.m_machine.weights[:,i] - new_machine.weights[:,i] < 1e-5).all() or numpy.abs(tool.m_machine.weights[:,i] + new_machine.weights[:,i] < 1e-5).all()) - os.remove(t) - - # project feature - projected = tool.project(feature) - self.compare(projected, 'pca+lda_feature.hdf5') - self.assertTrue(len(projected.shape) == 1) - - # enroll model - model = tool.enroll([projected]) - self.compare(model, 'pca+lda_model.hdf5') - self.assertTrue(model.shape == (1,5)) - - # score - sim = tool.score(model, projected) - self.assertAlmostEqual(sim, 0.) - - # test the calculation of the subspace dimension based on percentage of variance, - # and the usage of a different way to compute the final score in case of multiple features per model - tool = facereclib.tools.LDA(5, .9, multiple_model_scoring = 'median') - tool.train_projector(facereclib.utils.tests.random_training_set_by_id(feature.shape, count=20, minimum=0., maximum=255.), t) - self.assertEqual(tool.m_pca_subspace, 334) - tool.load_projector(t) - os.remove(t) - projected = tool.project(feature) - model = tool.enroll([projected, projected]) - self.assertTrue(model.shape == (2,5)) - self.assertAlmostEqual(tool.score(model, projected), 0.) - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [projected, projected]), 0.) def test05_bic(self): diff --git a/setup.py b/setup.py index 90f3419b..6a0ee5e7 100644 --- a/setup.py +++ b/setup.py @@ -121,6 +121,8 @@ setup( 'bob.bio.algorithm': [ 'dummy = bob.bio.base.test.dummy.algorithm:algorithm', # for test purposes only 'pca = bob.bio.base.config.algorithm.pca:algorithm', + 'lda = bob.bio.base.config.algorithm.lda:algorithm', + 'pca+lda = bob.bio.base.config.algorithm.lda:algorithm', ], }, -- GitLab