From a1d08d1b92639c6aef66c6187b510ba9e505799b Mon Sep 17 00:00:00 2001 From: Tiago Freitas Pereira <tiagofrepereira@gmail.com> Date: Tue, 26 May 2020 09:30:52 +0200 Subject: [PATCH] Implemented S-Norm score normalization --- .../pipelines/vanilla_biometrics/zt_norm.py | 91 ++++++++++++++++++- .../test_vanilla_biometrics_score_norm.py | 32 ++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py b/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py index 0b58fb88..7181674b 100644 --- a/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py +++ b/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py @@ -110,6 +110,7 @@ class ZTNormPipeline(object): return z_normed_scores # T NORM + t_normed_scores, t_scores, t_biometric_references = self.compute_tnorm_scores( t_biometric_reference_samples, probe_features, @@ -128,8 +129,14 @@ class ZTNormPipeline(object): t_scores, allow_scoring_with_all_biometric_references, ) + + + # S-norm + s_normed_scores = self.compute_snorm_scores(z_normed_scores, t_normed_scores) + + - return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores + return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores, s_normed_scores def train_background_model(self, background_model_samples): return self.vanilla_biometrics_pipeline.train_background_model( @@ -227,6 +234,19 @@ class ZTNormPipeline(object): return zt_normed_scores + + def compute_snorm_scores( + self, + znormed_scores, + tnormed_scores + ): + + s_normed_scores = self.ztnorm_solver.compute_snorm_scores( + znormed_scores, tnormed_scores + ) + + return s_normed_scores + def write_scores(self, scores): return self.vanilla_biometrics_pipeline.write_scores(scores) @@ -395,6 +415,33 @@ class ZTNorm(object): return self._tnorm_samplesets(probe_scores, stats) + def _snorm(self, z_score, t_score): + return 0.5*(z_score + t_score) + + def _snorm_samplesets(self, znormed_scores, tnormed_scores): + + s_normed_samplesets = [] + for z, t in zip(znormed_scores, tnormed_scores): + s_normed_scores = SampleSet([], parent=z) + for b_z, b_t in zip(z, t): + score = self._snorm(b_z.data, b_t.data) + + new_sample = Sample(score, parent=b_z) + s_normed_scores.samples.append(new_sample) + s_normed_samplesets.append(s_normed_scores) + + return s_normed_samplesets + + + def compute_snorm_scores( + self, + znormed_scores, + tnormed_scores + ): + + return self._snorm_samplesets(znormed_scores, tnormed_scores) + + class ZTNormDaskWrapper(object): """ Wrap :any:`ZTNorm` to work with DASK @@ -435,6 +482,13 @@ class ZTNormDaskWrapper(object): return probe_scores.map_partitions(self.ztnorm._tnorm_samplesets, stats) + def compute_snorm_scores( + self, + znormed_scores, + tnormed_scores + ): + return znormed_scores.map_partitions(self.ztnorm._snorm_samplesets, tnormed_scores) + class ZTNormCheckpointWrapper(object): """ @@ -488,6 +542,29 @@ class ZTNormCheckpointWrapper(object): return z_normed_score + + def _apply_tnorm(self, probe_score, stats): + + path = os.path.join(self.tnorm_score_path, str(probe_score.key) + ".pkl") + + if self.force or not os.path.exists(path): + t_normed_score = self.ztnorm._apply_tnorm(probe_score, path) + + self.write_scores(t_normed_score.samples) + + t_normed_score = SampleSet( + [ + DelayedSample( + functools.partial(self._load, path), parent=probe_score + ) + ], + parent=probe_score, + ) + else: + t_normed_score = SampleSet(self._load(path), parent=probe_score) + + return t_normed_score + def compute_znorm_scores( self, probe_scores, sampleset_for_znorm, biometric_references ): @@ -499,11 +576,18 @@ class ZTNormCheckpointWrapper(object): def compute_tnorm_scores( self, probe_scores, sampleset_for_tnorm, t_biometric_references ): - return self.ztnorm.compute_tnorm_scores( probe_scores, sampleset_for_tnorm, t_biometric_references ) + def compute_snorm_scores( + self, + znormed_scores, + tnormed_scores + ): + return self.ztnorm.compute_snorm_scores(znormed_scores, tnormed_scores) + + def _compute_stats(self, sampleset_for_norm, biometric_references, axis=0): return self.ztnorm._compute_stats( sampleset_for_norm, biometric_references, axis=axis @@ -514,3 +598,6 @@ class ZTNormCheckpointWrapper(object): def _tnorm_samplesets(self, probe_scores, stats): return self.ztnorm._tnorm_samplesets(probe_scores, stats) + + def _snorm_samplesets(self, probe_scores, stats): + return self.ztnorm._snorm_samplesets(probe_scores, stats) diff --git a/bob/bio/base/test/test_vanilla_biometrics_score_norm.py b/bob/bio/base/test/test_vanilla_biometrics_score_norm.py index eb9f1593..4829f78c 100644 --- a/bob/bio/base/test/test_vanilla_biometrics_score_norm.py +++ b/bob/bio/base/test/test_vanilla_biometrics_score_norm.py @@ -127,7 +127,11 @@ def zt_norm_stubs(references, probes, t_references, z_probes): zt_normed_scores = _norm(z_normed_scores, z_t_scores, axis=0) assert zt_normed_scores.shape == (n_reference, n_probes) - return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores + s_normed_scores = (z_normed_scores+t_normed_scores)*0.5 + assert s_normed_scores.shape == (n_reference, n_probes) + + + return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores, s_normed_scores def test_norm_mechanics(): @@ -191,6 +195,7 @@ def test_norm_mechanics(): z_normed_scores_ref, t_normed_scores_ref, zt_normed_scores_ref, + s_normed_scores_ref, ) = zt_norm_stubs(references, probes, t_references, z_probes) ############ @@ -338,6 +343,7 @@ def test_norm_mechanics(): z_normed_score_samples, t_normed_score_samples, zt_normed_score_samples, + s_normed_score_samples, ) = zt_vanilla_pipeline( [], biometric_reference_sample_sets, @@ -360,6 +366,11 @@ def test_norm_mechanics(): scheduler="single-threaded" ) + s_normed_score_samples = s_normed_score_samples.compute( + scheduler="single-threaded" + ) + + raw_scores = _dump_scores_from_samples( raw_score_samples, shape=(n_probes, n_references) ) @@ -380,6 +391,14 @@ def test_norm_mechanics(): ) assert np.allclose(zt_normed_scores, zt_normed_scores_ref) + s_normed_scores = _dump_scores_from_samples( + s_normed_score_samples, shape=(n_probes, n_references) + ) + assert np.allclose(s_normed_scores, s_normed_scores_ref) + + + + # No dask run(False) # On memory @@ -418,7 +437,7 @@ def test_znorm_on_memory(): vanilla_biometrics_pipeline, npartitions=2 ) - raw_scores, z_scores, t_scores, zt_scores = vanilla_biometrics_pipeline( + raw_scores, z_scores, t_scores, zt_scores, s_scores = vanilla_biometrics_pipeline( database.background_model_samples(), database.references(), database.probes(), @@ -448,15 +467,22 @@ def test_znorm_on_memory(): t_scores = _concatenate( vanilla_biometrics_pipeline, t_scores, "scores-dev_tscores" ) + zt_scores = _concatenate( vanilla_biometrics_pipeline, zt_scores, "scores-dev_ztscores" ) + s_scores = _concatenate( + vanilla_biometrics_pipeline, s_scores, "scores-dev_sscores" + ) + if with_dask: raw_scores = raw_scores.compute(scheduler="single-threaded") z_scores = z_scores.compute(scheduler="single-threaded") t_scores = t_scores.compute(scheduler="single-threaded") zt_scores = zt_scores.compute(scheduler="single-threaded") + s_scores = s_scores.compute(scheduler="single-threaded") + if isinstance(score_writer, CSVScoreWriter): n_lines = 51 if with_dask else 101 @@ -465,12 +491,14 @@ def test_znorm_on_memory(): assert len(open(z_scores[0], "r").readlines()) == n_lines assert len(open(t_scores[0], "r").readlines()) == n_lines assert len(open(zt_scores[0], "r").readlines()) == n_lines + assert len(open(s_scores[0], "r").readlines()) == n_lines else: assert len(raw_scores) == 10 assert len(z_scores) == 10 assert len(t_scores) == 10 assert len(zt_scores) == 10 + assert len(s_scores) == 10 run_pipeline(False) run_pipeline(False) # Testing checkpoint -- GitLab