From b8f12e22d3299a930421e2aaa3f86be99dd4466a Mon Sep 17 00:00:00 2001 From: Manuel Guenther <manuel.guenther@idiap.ch> Date: Mon, 1 Jun 2015 11:09:30 +0200 Subject: [PATCH] Added PLDA; improved build system --- bob/bio/base/algorithm/PLDA.py | 163 +++++++ bob/bio/base/algorithm/__init__.py | 1 + bob/bio/base/config/algorithm/pca_plda.py | 9 + bob/bio/base/config/algorithm/plda.py | 8 + bob/bio/base/test/data/plda_enroller.hdf5 | Bin 0 -> 34248 bytes bob/bio/base/test/data/plda_model.hdf5 | Bin 0 -> 5280 bytes bob/bio/base/test/test_algorithms.py | 505 +++------------------- buildout.cfg | 37 +- requirements.txt | 15 + setup.py | 14 +- 10 files changed, 296 insertions(+), 456 deletions(-) create mode 100644 bob/bio/base/algorithm/PLDA.py create mode 100644 bob/bio/base/config/algorithm/pca_plda.py create mode 100644 bob/bio/base/config/algorithm/plda.py create mode 100644 bob/bio/base/test/data/plda_enroller.hdf5 create mode 100644 bob/bio/base/test/data/plda_model.hdf5 create mode 100644 requirements.txt diff --git a/bob/bio/base/algorithm/PLDA.py b/bob/bio/base/algorithm/PLDA.py new file mode 100644 index 00000000..d8661b7d --- /dev/null +++ b/bob/bio/base/algorithm/PLDA.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + +import bob.core +import bob.io.base +import bob.learn.linear +import bob.learn.em + +import numpy + +from .Algorithm import Algorithm +import logging +logger = logging.getLogger("bob.bio.base") + + +class PLDA (Algorithm): + """Tool chain for computing PLDA (over PCA-dimensionality reduced) features""" + + def __init__( + self, + subspace_dimension_of_f, # Size of subspace F + subspace_dimension_of_g, # Size of subspace G + subspace_dimension_pca = None, # if given, perform PCA on data and reduce the PCA subspace to the given dimension + plda_training_iterations = 200, # Maximum number of iterations for the EM loop + # TODO: refactor the remaining parameters! + INIT_SEED = 5489, # seed for initializing + INIT_F_METHOD = 'BETWEEN_SCATTER', + INIT_G_METHOD = 'WITHIN_SCATTER', + INIT_S_METHOD = 'VARIANCE_DATA', + multiple_probe_scoring = 'joint_likelihood' + ): + + """Initializes the local (PCA-)PLDA tool chain with the given file selector object""" + # call base class constructor and register that this class requires training for enrollment + Algorithm.__init__( + self, + requires_enroller_training = True, + + subspace_dimension_of_f = subspace_dimension_of_f, # Size of subspace F + subspace_dimension_of_g = subspace_dimension_of_g, # Size of subspace G + subspace_dimension_pca = subspace_dimension_pca, # if given, perform PCA on data and reduce the PCA subspace to the given dimension + plda_training_iterations = plda_training_iterations, # Maximum number of iterations for the EM loop + # TODO: refactor the remaining parameters! + INIT_SEED = INIT_SEED, # seed for initializing + INIT_F_METHOD = str(INIT_F_METHOD), + INIT_G_METHOD = str(INIT_G_METHOD), + INIT_S_METHOD =str(INIT_S_METHOD), + multiple_probe_scoring = multiple_probe_scoring, + multiple_model_scoring = None + ) + + self.subspace_dimension_of_f = subspace_dimension_of_f + self.subspace_dimension_of_g = subspace_dimension_of_g + self.subspace_dimension_pca = subspace_dimension_pca + self.plda_training_iterations = plda_training_iterations + self.score_set = {'joint_likelihood': 'joint_likelihood', 'average':numpy.average, 'min':min, 'max':max}[multiple_probe_scoring] + + # TODO: refactor + self.plda_trainer = bob.learn.em.PLDATrainer() + self.plda_trainer.init_f_method = INIT_F_METHOD + self.plda_trainer.init_g_method = INIT_G_METHOD + self.plda_trainer.init_sigma_method = INIT_S_METHOD + self.rng = bob.core.random.mt19937(INIT_SEED) + self.pca_machine = None + self.plda_base = None + + + + def _train_pca(self, training_set): + """Trains and returns a LinearMachine that is trained using PCA""" + data = numpy.vstack([feature for client in training_set for feature in client]) + + logger.info(" -> Training LinearMachine using PCA ") + trainer = bob.learn.linear.PCATrainer() + machine, _ = trainer.train(data) + # limit number of pcs + machine.resize(machine.shape[0], self.subspace_dimension_pca) + return machine + + def _perform_pca_client(self, client): + """Perform PCA on an array""" + return numpy.vstack([self.pca_machine(feature) for feature in client]) + + def _perform_pca(self, training_set): + """Perform PCA on data""" + return [self._perform_pca_client(client) for client in training_set] + + + def train_enroller(self, training_features, projector_file): + """Generates the PLDA base model from a list of arrays (one per identity), + and a set of training parameters. If PCA is requested, it is trained on the same data. + Both the trained PLDABase and the PCA machine are written.""" + + + # train PCA and perform PCA on training data + if self.subspace_dimension_pca is not None: + self.pca_machine = self._train_pca(training_features) + training_features = self._perform_pca(training_features) + + input_dimension = training_features[0].shape[1] + logger.info(" -> Training PLDA base machine") + + # train machine + self.plda_base = bob.learn.em.PLDABase(input_dimension, self.subspace_dimension_of_f, self.subspace_dimension_of_g) + bob.learn.em.train(self.plda_trainer, self.plda_base, training_features, self.plda_training_iterations, self.rng) + + # write machines to file + proj_hdf5file = bob.io.base.HDF5File(str(projector_file), "w") + if self.subspace_dimension_pca is not None: + proj_hdf5file.create_group('/pca') + proj_hdf5file.cd('/pca') + self.pca_machine.save(proj_hdf5file) + proj_hdf5file.create_group('/plda') + proj_hdf5file.cd('/plda') + self.plda_base.save(proj_hdf5file) + + + def load_enroller(self, projector_file): + """Reads the PCA projection matrix and the PLDA model from file""" + # read enroller (PCA and PLDA matrix) + hdf5 = bob.io.base.HDF5File(projector_file) + if hdf5.has_group("/pca"): + hdf5.cd('/pca') + self.pca_machine = bob.learn.linear.Machine(hdf5) + hdf5.cd('/plda') + self.plda_base = bob.learn.em.PLDABase(hdf5) + + + def enroll(self, enroll_features): + """Enrolls the model by computing an average of the given input vectors""" + plda_machine = bob.learn.em.PLDAMachine(self.plda_base) + # project features, if enabled + if self.pca_machine is not None: + enroll_features = self._perform_pca_client(enroll_features) + # enroll + self.plda_trainer.enroll(plda_machine, enroll_features) + return plda_machine + + + def read_model(self, model_file): + """Reads the model, which in this case is a PLDA-Machine""" + # read machine and attach base machine + plda_machine = bob.learn.em.PLDAMachine(bob.io.base.HDF5File(model_file), self.plda_base) + return plda_machine + + + def score(self, model, probe): + """Computes the PLDA score for the given model and probe""" + return self.score_for_multiple_probes(model, [probe]) + + + def score_for_multiple_probes(self, model, probes): + """This function computes the score between the given model and several given probe files. + In this base class implementation, it computes the scores for each probe file using the 'score' method, + and fuses the scores using the fusion method specified in the constructor of this class.""" + if self.pca_machine is not None: + probes = [self.pca_machine(probe) for probe in probes] + # forward + if self.score_set == 'joint_likelihood': + return model.log_likelihood_ratio(numpy.vstack(probes)) + else: + return self.score_set([model.log_likelihood_ratio(probe) for probe in probes]) diff --git a/bob/bio/base/algorithm/__init__.py b/bob/bio/base/algorithm/__init__.py index d22a30f2..6baf97fd 100644 --- a/bob/bio/base/algorithm/__init__.py +++ b/bob/bio/base/algorithm/__init__.py @@ -1,4 +1,5 @@ from .Algorithm import Algorithm from .PCA import PCA from .LDA import LDA +from .PLDA import PLDA from .BIC import BIC diff --git a/bob/bio/base/config/algorithm/pca_plda.py b/bob/bio/base/config/algorithm/pca_plda.py new file mode 100644 index 00000000..2ff3a8e6 --- /dev/null +++ b/bob/bio/base/config/algorithm/pca_plda.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import bob.bio.base + +algorithm = bob.bio.base.algorithm.PLDA( + subspace_dimension_of_f = 16, # Size of subspace F + subspace_dimension_of_g = 16, # Size of subspace G + subspace_dimension_pca = 150 # Size of the PCA subspace +) diff --git a/bob/bio/base/config/algorithm/plda.py b/bob/bio/base/config/algorithm/plda.py new file mode 100644 index 00000000..c3d362fb --- /dev/null +++ b/bob/bio/base/config/algorithm/plda.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import bob.bio.base + +algorithm = bob.bio.base.algorithm.PLDA( + subspace_dimension_of_f = 16, # Size of subspace F + subspace_dimension_of_g = 16 # Size of subspace G +) diff --git a/bob/bio/base/test/data/plda_enroller.hdf5 b/bob/bio/base/test/data/plda_enroller.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..c242d7151b2b01a09bfd38940b4387c41af945b1 GIT binary patch literal 34248 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 literal 0 HcmV?d00001 diff --git a/bob/bio/base/test/data/plda_model.hdf5 b/bob/bio/base/test/data/plda_model.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..d2d6285d520c1cfef63c46ca520ee4549a5bb652 GIT binary patch literal 5280 zcmeD5aB<`1lHy_j0S*oZ76t(@6Gr@pf(0TF5f~pPp8#brLg@}Dy@CnCU}OM61_lYJ zxFFPgbaf#?uC5F~l`!*RG*lad0Skm>023IcM^p%SxH<-aJiGzw>jo%2gB{AC5yi;B z22Q^a0+d1_DVdQGlHy_M9!4_*(>I6>%0(PtH#vYwRv-@~&cwt7R>}c12gG1zOn@kZ zss<|t5<*ZuNR6CgPACII159c_>0m#97e)qlh61Q7CJ5tq4mi?)N(uz=tBZh2HwfWZ z=Kz&X00l4}01Z?I1!g>ofKsq>se%>1Is>S52OEBM#LAMMt{~|}P{AIWG8`C8xEW0T zL;Z(4ePs}tzAA`yI!)9O3J+*?!T_V0q4|gO@F;+W2WkSs%x?o59<V^pi!V;hEyzhN zh6?0m#21(5##dyP#3!YeBmx-_iSpFU^o)|!6p$=PBqu*TCo?-WCo>~IKLyB7Pt46t zj5mUbC+FuCmz1Oy<pPDkATd5OFC{Y>ZV$MS0?NSBJwz4hB+Q%}Y#Px@y}$Q+eqwR| zGD~6q+D|<vKi%@(pCFK?;O*{vU~5>e!O#Ei?dSA2q&7BAw3`j3d#ZQL`T-PYNR$+O zlofLT7XPHXWK_p!2#kinXb6mkz-S1Jh5)@nfKa;*);@sI<hSb@hG;ui0~{5MkZvKo zeG6;npwr}sPX{!76i}ibv)!A4B2Q}${nE#ZA(}oQJyc@G8z4hukod*jAKU;92k3wr PG=eeHK?AL$o+$GH;R}|Q literal 0 HcmV?d00001 diff --git a/bob/bio/base/test/test_algorithms.py b/bob/bio/base/test/test_algorithms.py index b732b1c9..9d7f0bb1 100644 --- a/bob/bio/base/test/test_algorithms.py +++ b/bob/bio/base/test/test_algorithms.py @@ -27,11 +27,7 @@ import pkg_resources regenerate_refs = False -#seed_value = 5489 - -import sys -_mac_os = sys.platform == 'darwin' - +seed_value = 5489 import scipy.spatial @@ -279,446 +275,59 @@ def test_bic(): assert abs(bic1.score_for_multiple_probes(model, [probe, probe]) - reference_score) < 1e-5 -""" - def test01_gabor_jet(self): - # read input - extractor = facereclib.utils.tests.configuration_file('grid-graph', 'feature_extractor', 'features') - feature = extractor.read_feature(self.input_dir('graph_regular.hdf5')) - tool = self.config('gabor-jet') - self.assertFalse(tool.performs_projection) - self.assertFalse(tool.requires_enroller_training) - - # enroll - model = tool.enroll([feature]) - # execute the preprocessor - if regenerate_refs: - tool.save_model(model, self.reference_dir('graph_model.hdf5')) - reference = tool.read_model(self.reference_dir('graph_model.hdf5')) - self.assertEqual(len(model), 1) - for n in range(len(model[0])): - self.assertTrue((numpy.abs(model[0][n].abs - reference[0][n].abs) < 1e-5).all()) - self.assertTrue((numpy.abs(model[0][n].phase - reference[0][n].phase) < 1e-5).all()) - - # score - sim = tool.score(model, feature) - self.assertAlmostEqual(sim, 1.) - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [feature, feature]), 1.) - - # test averaging - tool = facereclib.tools.GaborJets( - "PhaseDiffPlusCanberra", - gabor_sigma = math.sqrt(2.) * math.pi, - multiple_feature_scoring = "average_model" - ) - model = tool.enroll([feature, feature]) - - # absoulte values must be identical - for n in range(len(model)): - self.assertTrue((numpy.abs(model[n].abs - reference[0][n].abs) < 1e-5).all()) - # phases might differ with 2 Pi - for n in range(len(model)): - for j in range(len(model[n].phase)): - self.assertTrue(abs(model[n].phase[j] - reference[0][n].phase[j]) < 1e-5 or abs(model[n].phase[j] - reference[0][n].phase[j] + 2*math.pi) < 1e-5 or abs(model[n].phase[j] - reference[0][n].phase[j] - 2*math.pi) < 1e-5) - - sim = tool.score(model, feature) - self.assertAlmostEqual(sim, 1.) - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [feature, feature]), 1.) - - - - def test02_lgbphs(self): - # read input - feature1 = facereclib.utils.load(self.input_dir('lgbphs_sparse.hdf5')) - feature2 = facereclib.utils.load(self.input_dir('lgbphs_no_phase.hdf5')) - tool = self.config('lgbphs') - self.assertFalse(tool.performs_projection) - self.assertFalse(tool.requires_enroller_training) - - # enroll model - model = tool.enroll([feature1]) - self.compare(model, 'lgbphs_model.hdf5') - - # score - sim = tool.score(model, feature2) - self.assertAlmostEqual(sim, 40960.) - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [feature2, feature2]), sim) - - - - - - - - def test06_gmm(self): - # read input - feature = facereclib.utils.load(self.input_dir('dct_blocks.hdf5')) - # assure that the config file is readable - tool = self.config('gmm') - self.assertTrue(isinstance(tool, facereclib.tools.UBMGMM)) - - # here, we use a reduced complexity for test purposes - tool = facereclib.tools.UBMGMM( - number_of_gaussians = 2, - k_means_training_iterations = 1, - gmm_training_iterations = 1, - INIT_SEED = seed_value, - ) - self.assertTrue(tool.performs_projection) - self.assertTrue(tool.requires_projector_training) - self.assertFalse(tool.use_projected_features_for_enrollment) - self.assertFalse(tool.split_training_features_by_client) - - # train the projector - t = tempfile.mkstemp('ubm.hdf5', prefix='frltest_')[1] - tool.train_projector(facereclib.utils.tests.random_training_set(feature.shape, count=5, minimum=-5., maximum=5.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('gmm_projector.hdf5')) - - # load the projector file - tool.load_projector(self.reference_dir('gmm_projector.hdf5')) - # compare GMM projector with reference - new_machine = bob.learn.em.GMMMachine(bob.io.base.HDF5File(t)) - self.assertTrue(tool.m_ubm.is_similar_to(new_machine)) - os.remove(t) - - # project the feature - projected = tool.project(feature) - if regenerate_refs: - projected.save(bob.io.base.HDF5File(self.reference_dir('gmm_feature.hdf5'), 'w')) - probe = tool.read_probe(self.reference_dir('gmm_feature.hdf5')) - self.assertTrue(projected.is_similar_to(probe)) - - # enroll model with the unprojected feature - model = tool.enroll([feature]) - if regenerate_refs: - model.save(bob.io.base.HDF5File(self.reference_dir('gmm_model.hdf5'), 'w')) - reference_model = tool.read_model(self.reference_dir('gmm_model.hdf5')) - self.assertTrue(model.is_similar_to(reference_model)) - - # score with projected feature and compare to the weird reference score ... - sim = tool.score(reference_model, probe) - self.assertAlmostEqual(sim, 0.25472347774) - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [probe, probe]), sim) - - - def test06a_gmm_regular(self): - # read input - feature = facereclib.utils.load(self.input_dir('dct_blocks.hdf5')) - # assure that the config file is readable - tool = self.config('ubm_gmm_regular_scoring') - self.assertTrue(isinstance(tool, facereclib.tools.UBMGMMRegular)) - - # here, we use a reduced complexity for test purposes - tool = facereclib.tools.UBMGMMRegular( - number_of_gaussians = 2, - k_means_training_iterations = 1, - gmm_training_iterations = 1, - INIT_SEED = seed_value - ) - self.assertFalse(tool.performs_projection) - self.assertTrue(tool.requires_enroller_training) - - # train the enroller - t = tempfile.mkstemp('ubm.hdf5', prefix='frltest_')[1] - tool.train_enroller(facereclib.utils.tests.random_training_set(feature.shape, count=5, minimum=-5., maximum=5.), t) - # assure that it is identical to the normal UBM projector - tool.load_enroller(self.reference_dir('gmm_projector.hdf5')) - - # enroll model with the unprojected feature - model = tool.enroll([feature]) - reference_model = tool.read_model(self.reference_dir('gmm_model.hdf5')) - self.assertTrue(model.is_similar_to(reference_model)) - - # score with unprojected feature and compare to the weird reference score ... - probe = tool.read_probe(self.input_dir('dct_blocks.hdf5')) - sim = tool.score(reference_model, probe) - - self.assertAlmostEqual(sim, 0.143875716) - - - def test07_isv(self): - # read input - feature = facereclib.utils.load(self.input_dir('dct_blocks.hdf5')) - # assure that the config file is readable - tool = self.config('isv') - self.assertTrue(isinstance(tool, facereclib.tools.ISV)) - - # Here, we use a reduced complexity for test purposes - tool = facereclib.tools.ISV( - number_of_gaussians = 2, - subspace_dimension_of_u = 160, - k_means_training_iterations = 1, - gmm_training_iterations = 1, - isv_training_iterations = 1, - INIT_SEED = seed_value - ) - 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) - self.assertFalse(tool.requires_enroller_training) - - # train the projector - t = tempfile.mkstemp('ubm.hdf5', prefix='frltest_')[1] - tool.train_projector(facereclib.utils.tests.random_training_set_by_id(feature.shape, count=5, minimum=-5., maximum=5.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('isv_projector.hdf5')) - - # load the projector file - tool.load_projector(self.reference_dir('isv_projector.hdf5')) - - # compare ISV projector with reference - hdf5file = bob.io.base.HDF5File(t) - hdf5file.cd('Projector') - projector_reference = bob.learn.em.GMMMachine(hdf5file) - self.assertTrue(tool.m_ubm.is_similar_to(projector_reference)) - - # compare ISV enroller with reference - hdf5file.cd('/') - hdf5file.cd('Enroller') - enroller_reference = bob.learn.em.ISVBase(hdf5file) - enroller_reference.ubm = projector_reference - if not _mac_os: - self.assertTrue(tool.m_isvbase.is_similar_to(enroller_reference)) - os.remove(t) - - # project the feature - projected = tool.project(feature) - if regenerate_refs: - tool.save_feature(projected, self.reference_dir('isv_feature.hdf5')) - - # compare the projected feature with the reference - projected_reference = tool.read_feature(self.reference_dir('isv_feature.hdf5')) - self.assertTrue(projected[0].is_similar_to(projected_reference)) - - # enroll model with the projected feature - model = tool.enroll([projected[0]]) - if regenerate_refs: - model.save(bob.io.base.HDF5File(self.reference_dir('isv_model.hdf5'), 'w')) - reference_model = tool.read_model(self.reference_dir('isv_model.hdf5')) - # compare the ISV model with the reference - self.assertTrue(model.is_similar_to(reference_model)) - - # check that the read_probe function reads the correct values - probe = tool.read_probe(self.reference_dir('isv_feature.hdf5')) - self.assertTrue(probe[0].is_similar_to(projected[0])) - self.assertEqual(probe[1].any(), projected[1].any()) - - # score with projected feature and compare to the weird reference score ... - sim = tool.score(model, probe) - self.assertAlmostEqual(sim, 0.002739667184506023) - - # score with a concatenation of the probe - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [probe, probe]), sim, places=5) - - - def test08_jfa(self): - # read input - feature = facereclib.utils.load(self.input_dir('dct_blocks.hdf5')) - # assure that the config file is readable - tool = self.config('jfa') - self.assertTrue(isinstance(tool, facereclib.tools.JFA)) - - # here, we use a reduced complexity for test purposes - tool = facereclib.tools.JFA( - number_of_gaussians = 2, - subspace_dimension_of_u = 2, - subspace_dimension_of_v = 2, - k_means_training_iterations = 1, - gmm_training_iterations = 1, - jfa_training_iterations = 1, - INIT_SEED = seed_value - ) - self.assertTrue(tool.performs_projection) - self.assertTrue(tool.requires_projector_training) - self.assertTrue(tool.use_projected_features_for_enrollment) - self.assertFalse(tool.split_training_features_by_client) - self.assertTrue(tool.requires_enroller_training) - - # train the projector - t = tempfile.mkstemp('ubm.hdf5', prefix='frltest_')[1] - tool.train_projector(facereclib.utils.tests.random_training_set(feature.shape, count=5, minimum=-5., maximum=5.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('jfa_projector.hdf5')) - - # load the projector file - tool.load_projector(self.reference_dir('jfa_projector.hdf5')) - # compare JFA projector with reference - new_machine = bob.learn.em.GMMMachine(bob.io.base.HDF5File(t)) - self.assertTrue(tool.m_ubm.is_similar_to(new_machine)) - os.remove(t) - - # project the feature - projected = tool.project(feature) - if regenerate_refs: - projected.save(bob.io.base.HDF5File(self.reference_dir('jfa_feature.hdf5'), 'w')) - # compare the projected feature with the reference - projected_reference = tool.read_feature(self.reference_dir('jfa_feature.hdf5')) - self.assertTrue(projected.is_similar_to(projected_reference)) - - # train the enroller - t = tempfile.mkstemp('enroll.hdf5', prefix='frltest_')[1] - tool.train_enroller(self.train_gmm_stats(self.reference_dir('jfa_feature.hdf5'), count=5, minimum=-5., maximum=5.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('jfa_enroller.hdf5')) - tool.load_enroller(self.reference_dir('jfa_enroller.hdf5')) - # compare JFA enroller with reference - enroller_reference = bob.learn.em.JFABase(bob.io.base.HDF5File(t)) - enroller_reference.ubm = new_machine - if not _mac_os: - self.assertTrue(tool.m_jfabase.is_similar_to(enroller_reference)) - os.remove(t) - - # enroll model with the projected feature - model = tool.enroll([projected]) - if regenerate_refs: - model.save(bob.io.base.HDF5File(self.reference_dir('jfa_model.hdf5'), 'w')) - # assert that the model is ok - reference_model = tool.read_model(self.reference_dir('jfa_model.hdf5')) - self.assertTrue(model.is_similar_to(reference_model)) - - # check that the read_probe function reads the requested data - probe = tool.read_probe(self.reference_dir('jfa_feature.hdf5')) - self.assertTrue(probe.is_similar_to(projected)) - - # score with projected feature and compare to the weird reference score ... - sim = tool.score(model, probe) - self.assertAlmostEqual(sim, 0.25473213400211353) - # score with a concatenation of the probe - # self.assertAlmostEqual(tool.score_for_multiple_probes(model, [probe, probe]), sim) - - - def test09_plda(self): - # read input - feature = facereclib.utils.load(self.input_dir('linearize.hdf5')) - # assure that the config file is readable - tool = self.config('pca+plda') - self.assertTrue(isinstance(tool, facereclib.tools.PLDA)) - - # here, we use a reduced complexity for test purposes - tool = facereclib.tools.PLDA( - subspace_dimension_of_f = 2, - subspace_dimension_of_g = 2, - subspace_dimension_pca = 10, - plda_training_iterations = 1, - INIT_SEED = seed_value, - ) - self.assertFalse(tool.performs_projection) - self.assertTrue(tool.requires_enroller_training) - - # train the projector - t = tempfile.mkstemp('pca+plda.hdf5', prefix='frltest_')[1] - tool.train_enroller(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+plda_enroller.hdf5')) - - # load the projector file - tool.load_enroller(self.reference_dir('pca+plda_enroller.hdf5')) - # compare the resulting machines - test_file = bob.io.base.HDF5File(t) - test_file.cd('/pca') - pca_machine = bob.learn.linear.Machine(test_file) - test_file.cd('/plda') - plda_machine = bob.learn.em.PLDABase(test_file) - # TODO: compare the PCA machines - #self.assertEqual(pca_machine, tool.m_pca_machine) - # TODO: compare the PLDA machines - #self.assertEqual(plda_machine, tool.m_plda_base_machine) - os.remove(t) - - # enroll model - model = tool.enroll([feature]) - if regenerate_refs: - model.save(bob.io.base.HDF5File(self.reference_dir('pca+plda_model.hdf5'), 'w')) - # TODO: compare the models with the reference - #reference_model = tool.read_model(self.reference_dir('pca+plda_model.hdf5')) - #self.assertEqual(model, reference_model) - - # score - sim = tool.score(model, feature) - self.assertAlmostEqual(sim, 0.) - # score with a concatenation of the probe - self.assertAlmostEqual(tool.score_for_multiple_probes(model, [feature, feature]), 0.) - - - def test10_ivector(self): - # NOTE: This test will fail when it is run solely. Please always run all Tool tests in order to assure that they work. - # read input - feature = facereclib.utils.load(self.input_dir('dct_blocks.hdf5')) - # assure that the config file is readable - tool = self.config('ivector') - self.assertTrue(isinstance(tool, facereclib.tools.IVector)) - - # here, we use a reduced complexity for test purposes - tool = facereclib.tools.IVector( - number_of_gaussians = 2, - subspace_dimension_of_t=2, # T subspace dimension - update_sigma = False, # TODO Do another test with True - tv_training_iterations = 1, # Number of EM iterations for the JFA training - variance_threshold = 1e-5, - INIT_SEED = seed_value - ) - self.assertTrue(tool.performs_projection) - self.assertTrue(tool.requires_projector_training) - self.assertTrue(tool.use_projected_features_for_enrollment) - self.assertFalse(tool.split_training_features_by_client) - self.assertFalse(tool.requires_enroller_training) - - # train the projector - t = tempfile.mkstemp('ubm.hdf5', prefix='frltest_')[1] - tool.train_projector(facereclib.utils.tests.random_training_set(feature.shape, count=5, minimum=-5., maximum=5.), t) - if regenerate_refs: - import shutil - shutil.copy2(t, self.reference_dir('ivector_projector.hdf5')) - - # load the projector file - tool.load_projector(self.reference_dir('ivector_projector.hdf5')) - - # compare ISV projector with reference - hdf5file = bob.io.base.HDF5File(t) - hdf5file.cd('Projector') - projector_reference = bob.learn.em.GMMMachine(hdf5file) - self.assertTrue(tool.m_ubm.is_similar_to(projector_reference)) - - # compare ISV enroller with reference - hdf5file.cd('/') - hdf5file.cd('Enroller') - enroller_reference = bob.learn.em.IVectorMachine(hdf5file) - enroller_reference.ubm = projector_reference - if not _mac_os: - self.assertTrue(tool.m_tv.is_similar_to(enroller_reference)) - os.remove(t) - - # project the feature - projected = tool.project(feature) - if regenerate_refs: - tool.save_feature(projected, self.reference_dir('ivector_feature.hdf5')) - - # compare the projected feature with the reference - projected_reference = tool.read_feature(self.reference_dir('ivector_feature.hdf5')) - self.assertTrue(numpy.allclose(projected,projected_reference)) - - # enroll model with the projected feature - # This is not yet supported - # model = tool.enroll([projected[0]]) - # if regenerate_refs: - # model.save(bob.io.HDF5File(self.reference_dir('ivector_model.hdf5'), 'w')) - #reference_model = tool.read_model(self.reference_dir('ivector_model.hdf5')) - # compare the IVector model with the reference - #self.assertTrue(model.is_similar_to(reference_model)) - - # check that the read_probe function reads the correct values - probe = tool.read_probe(self.reference_dir('ivector_feature.hdf5')) - self.assertTrue(numpy.allclose(probe,projected)) - - # score with projected feature and compare to the weird reference score ... - # This in not implemented yet - - # score with a concatenation of the probe - # This is not implemented yet -""" +def test_plda(): + temp_file = bob.io.base.test_utils.temporary_filename() + # assure that the configurations are loadable + plda1 = bob.bio.base.load_resource("plda", "algorithm") + assert isinstance(plda1, bob.bio.base.algorithm.PLDA) + assert isinstance(plda1, bob.bio.base.algorithm.Algorithm) + plda2 = bob.bio.base.load_resource("pca+plda", "algorithm") + assert isinstance(plda2, bob.bio.base.algorithm.PLDA) + assert isinstance(plda2, bob.bio.base.algorithm.Algorithm) + + assert not plda1.performs_projection + assert not plda1.requires_projector_training + assert not plda1.use_projected_features_for_enrollment + assert not plda1.split_training_features_by_client + assert plda1.requires_enroller_training + + # generate a smaller PCA subspcae + plda3 = bob.bio.base.algorithm.PLDA(subspace_dimension_of_f = 2, subspace_dimension_of_g = 2, subspace_dimension_pca = 10, plda_training_iterations = 1, INIT_SEED = seed_value) + + # 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/plda_enroller.hdf5') + try: + # train projector + plda3.train_enroller(train_set, temp_file) + assert os.path.exists(temp_file) + + if regenerate_refs: shutil.copy(temp_file, reference_file) + + # check projection matrix + plda1.load_enroller(reference_file) + plda3.load_enroller(temp_file) + + assert plda1.pca_machine.is_similar_to(plda3.pca_machine) + assert plda1.plda_base.is_similar_to(plda3.plda_base) + + 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) + + # enroll model from random features + reference = pkg_resources.resource_filename('bob.bio.base.test', 'data/plda_model.hdf5') + model = plda1.enroll([feature]) + # execute the preprocessor + if regenerate_refs: + plda1.write_model(model, reference) + reference = plda1.read_model(reference) + assert model.is_similar_to(reference) + + # compare model with probe + reference_score = 0. + assert abs(plda1.score(model, feature) - reference_score) < 1e-5, "The scores differ: %3.8f, %3.8f" % (plda1.score(model, feature), reference_score) + assert abs(plda1.score_for_multiple_probes(model, [feature, feature]) - reference_score) < 1e-5 diff --git a/buildout.cfg b/buildout.cfg index 6b701057..ea8f7790 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -5,15 +5,46 @@ [buildout] parts = scripts eggs = bob.bio.base -extensions = bob.buildout - -develop = . + gridtk +extensions = bob.buildout + mr.developer +auto-checkout = * +develop = src/bob.extension + src/bob.blitz + src/bob.core + src/bob.io.base + src/bob.learn.activation + src/bob.math + src/bob.learn.linear + src/bob.sp + src/bob.learn.em + src/bob.measure + src/bob.db.verification.utils + src/bob.db.atnt + src/bob.io.image + . + ; options for bob.buildout debug = true verbose = true newest = false +[sources] +bob.extension = git https://github.com/bioidiap/bob.extension +bob.blitz = git https://github.com/bioidiap/bob.blitz +bob.core = git https://github.com/bioidiap/bob.core +bob.io.base = git https://github.com/bioidiap/bob.io.base +bob.learn.activation = git https://github.com/bioidiap/bob.learn.activation +bob.math = git https://github.com/bioidiap/bob.math +bob.sp = git https://github.com/bioidiap/bob.sp +bob.learn.linear = git https://github.com/bioidiap/bob.learn.linear +bob.learn.em = git https://github.com/bioidiap/bob.learn.em +bob.measure = git https://github.com/bioidiap/bob.measure +bob.db.verification.utils = git https://github.com/bioidiap/bob.db.verification.utils +bob.db.atnt = git https://github.com/bioidiap/bob.db.atnt +bob.io.image = git https://github.com/bioidiap/bob.io.image + [scripts] recipe = bob.buildout:scripts dependent-scripts = true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..754f8e27 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,15 @@ +setuptools +bob.extension +bob.blitz +bob.core +bob.io.base +bob.learn.activation +bob.math +bob.learn.linear +bob.sp +bob.learn.em +bob.measure +bob.db.verification.utils +bob.db.atnt # for test purposes +bob.io.image # for test purposes +matplotlib # for plotting diff --git a/setup.py b/setup.py index fbe07a67..f0d76157 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,11 @@ # allows you to test your package with new python dependencies w/o requiring # administrative interventions. -from setuptools import setup, find_packages +from setuptools import setup, find_packages, dist +dist.Distribution(dict(setup_requires=['bob.extension'])) + +from bob.extension.utils import load_requirements +install_requires = load_requirements() # The only thing we do in this file is to call the setup() function with all # parameters that define our package. @@ -64,9 +68,7 @@ setup( # on the current system will be installed locally and only visible to the # scripts of this package. Don't worry - You won't need administrative # privileges when using buildout. - install_requires = [ - 'setuptools', - ], + install_requires = install_requires, # Your project should be called something like 'bob.<foo>' or # 'bob.<foo>.<bar>'. To implement this correctly and still get all your @@ -122,7 +124,9 @@ setup( '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', + 'pca+lda = bob.bio.base.config.algorithm.pca_lda:algorithm', + 'plda = bob.bio.base.config.algorithm.plda:algorithm', + 'pca+plda = bob.bio.base.config.algorithm.pca_plda:algorithm', 'bic = bob.bio.base.config.algorithm.bic:algorithm', ], -- GitLab