From 2f94648c49415dc3b9ed610a77545ed9f4f99f26 Mon Sep 17 00:00:00 2001
From: Manuel Guenther <manuel.guenther@idiap.ch>
Date: Mon, 11 May 2015 17:45:02 +0200
Subject: [PATCH] Added IVector algorithm

---
 bob/bio/gmm/algorithm/GMM.py                 |   3 +-
 bob/bio/gmm/algorithm/IVector.py             | 210 +++++++++++++++++++
 bob/bio/gmm/algorithm/__init__.py            |   1 +
 bob/bio/gmm/config/algorithm/isv.py          |   1 -
 bob/bio/gmm/config/algorithm/ivector.py      |  10 +
 bob/bio/gmm/test/data/ivector_model.hdf5     | Bin 0 -> 2160 bytes
 bob/bio/gmm/test/data/ivector_projected.hdf5 | Bin 0 -> 2160 bytes
 bob/bio/gmm/test/data/ivector_projector.hdf5 | Bin 0 -> 19464 bytes
 bob/bio/gmm/test/test_algorithms.py          | 133 +++++-------
 setup.py                                     |   1 +
 10 files changed, 282 insertions(+), 77 deletions(-)
 create mode 100644 bob/bio/gmm/algorithm/IVector.py
 create mode 100644 bob/bio/gmm/config/algorithm/ivector.py
 create mode 100644 bob/bio/gmm/test/data/ivector_model.hdf5
 create mode 100644 bob/bio/gmm/test/data/ivector_projected.hdf5
 create mode 100644 bob/bio/gmm/test/data/ivector_projector.hdf5

diff --git a/bob/bio/gmm/algorithm/GMM.py b/bob/bio/gmm/algorithm/GMM.py
index d412abb..1f664ba 100644
--- a/bob/bio/gmm/algorithm/GMM.py
+++ b/bob/bio/gmm/algorithm/GMM.py
@@ -132,7 +132,8 @@ class GMM (Algorithm):
     """Save projector to file"""
     # Saves the UBM to file
     logger.debug(" .... Saving model to file '%s'", projector_file)
-    self.ubm.save(bob.io.base.HDF5File(projector_file, "w"))
+    hdf5 = projector_file if isinstance(projector_file, bob.io.base.HDF5File) else bob.io.base.HDF5File(projector_file, 'w')
+    self.ubm.save(hdf5)
 
 
   def train_projector(self, train_features, projector_file):
diff --git a/bob/bio/gmm/algorithm/IVector.py b/bob/bio/gmm/algorithm/IVector.py
new file mode 100644
index 0000000..90a36fb
--- /dev/null
+++ b/bob/bio/gmm/algorithm/IVector.py
@@ -0,0 +1,210 @@
+#!/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 .GMM import GMM
+from bob.bio.base.algorithm import Algorithm
+
+import logging
+logger = logging.getLogger("bob.bio.gmm")
+
+class IVector (GMM):
+  """Tool for extracting I-Vectors"""
+
+  def __init__(
+      self,
+      # IVector training
+      subspace_dimension_of_t,       # T subspace dimension
+      tv_training_iterations = 25,   # Number of EM iterations for the JFA training
+      update_sigma = True,
+      # parameters of the GMM
+      **kwargs
+  ):
+    """Initializes the local GMM tool with the given file selector object"""
+    # call base class constructor with its set of parameters
+    GMM.__init__(self, **kwargs)
+
+    # call tool constructor to overwrite what was set before
+    Algorithm.__init__(
+        self,
+        performs_projection = True,
+        use_projected_features_for_enrollment = True,
+        requires_enroller_training = False, # not needed anymore because it's done while training the projector
+        split_training_features_by_client = False,
+
+        subspace_dimension_of_t = subspace_dimension_of_t,
+        tv_training_iterations = tv_training_iterations,
+        update_sigma = update_sigma,
+
+        multiple_model_scoring = None,
+        multiple_probe_scoring = None,
+        **kwargs
+    )
+
+    self.update_sigma = update_sigma
+    self.subspace_dimension_of_t = subspace_dimension_of_t
+    self.tv_training_iterations = tv_training_iterations
+    self.ivector_trainer = bob.learn.em.IVectorTrainer(update_sigma=update_sigma)
+    self.whitening_trainer = bob.learn.linear.WhiteningTrainer()
+
+
+  def _check_projected(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")
+    if self.whitener is not None and feature.shape[0] != self.whitener.shape[1]:
+      raise ValueError("The given feature is expected to have %d elements, but it has %d" % (self.whitener.shape[1], feature.shape[0]))
+
+
+  def train_ivector(self, training_stats):
+    logger.info("  -> Training IVector enroller")
+    self.tv = bob.learn.em.IVectorMachine(self.ubm, self.subspace_dimension_of_t)
+    self.tv.variance_threshold = self.variance_threshold
+
+    # train IVector model
+    bob.learn.em.train(self.ivector_trainer, self.tv, training_stats, self.tv_training_iterations, rng=self.rng)
+
+  def train_whitening(self, training_features):
+    ivectors_matrix = numpy.vstack(training_features)
+    # create a Linear Machine
+    self.whitener = bob.learn.linear.Machine(ivectors_matrix.shape[1],ivectors_matrix.shape[1])
+    # create the whitening trainer
+    self.whitening_trainer.train(ivectors_matrix, self.whitener)
+
+  def train_projector(self, train_features, projector_file):
+    """Train Projector and Enroller at the same time"""
+    [self._check_feature(feature) for feature in train_features]
+
+    # train UBM
+    data = numpy.vstack(train_features)
+    self.train_ubm(data)
+    del data
+
+    # train IVector
+    logger.info("  -> Projecting training data")
+    training_stats = [self.project_ubm(feature) for feature in train_features]
+    # train IVector
+    self.train_ivector(training_stats)
+
+    # project training i-vectors
+    whitening_train_data = [self.project_ivec(stats) for stats in training_stats]
+    self.train_whitening(whitening_train_data)
+
+    # save
+    self.save_projector(projector_file)
+
+  def save_projector(self, projector_file):
+    # Save the IVector base AND the UBM AND the whitening into the same file
+    hdf5file = bob.io.base.HDF5File(projector_file, "w")
+    hdf5file.create_group('Projector')
+    hdf5file.cd('Projector')
+    self.save_ubm(hdf5file)
+
+    hdf5file.cd('/')
+    hdf5file.create_group('Enroller')
+    hdf5file.cd('Enroller')
+    self.tv.save(hdf5file)
+
+    hdf5file.cd('/')
+    hdf5file.create_group('Whitener')
+    hdf5file.cd('Whitener')
+    self.whitener.save(hdf5file)
+
+
+  def load_tv(self, tv_file):
+    hdf5file = bob.io.base.HDF5File(tv_file)
+    self.tv = bob.learn.em.IVectorMachine(hdf5file)
+    # add UBM model from base class
+    self.tv.ubm = self.ubm
+
+  def load_whitening(self, whitening_file):
+    hdf5file = bob.io.base.HDF5File(whitening_file)
+    self.whitener = bob.learn.linear.Machine(hdf5file)
+
+
+  def load_projector(self, projector_file):
+    """Load the GMM and the ISV model from the same HDF5 file"""
+    hdf5file = bob.io.base.HDF5File(projector_file)
+
+    # Load Projector
+    hdf5file.cd('/Projector')
+    self.load_ubm(hdf5file)
+
+    # Load Enroller
+    hdf5file.cd('/Enroller')
+    self.load_tv(hdf5file)
+
+    # Load Whitening
+    hdf5file.cd('/Whitener')
+    self.load_whitening(hdf5file)
+
+
+  def project_ivec(self, gmm_stats):
+    return self.tv.project(gmm_stats)
+
+  def project_whitening(self, ivector):
+    whitened = self.whitener.forward(ivector)
+    return whitened / numpy.linalg.norm(whitened)
+
+  #######################################################
+  ############## IVector projection #####################
+  def project(self, feature_array):
+    """Computes GMM statistics against a UBM, then corresponding Ux vector"""
+    self._check_feature(feature_array)
+    # project UBM
+    projected_ubm = self.project_ubm(feature_array)
+    # project I-Vector
+    ivector = self.project_ivec(projected_ubm)
+    # whiten I-Vector
+    return self.project_whitening(ivector)
+
+  #######################################################
+  ################## ISV model enroll ####################
+  def write_feature(self, data, feature_file):
+    """Saves the feature, which is the (whitened) I-Vector."""
+    bob.bio.base.save(data, feature_file)
+
+  def read_feature(self, feature_file):
+    """Read the type of features that we require, namely i-vectors (stored as simple numpy arrays)"""
+    return bob.bio.base.load(feature_file)
+
+
+
+  #######################################################
+  ################## Model  Enrollment ###################
+  def enroll(self, enroll_features):
+    """Performs IVector enrollment"""
+    [self._check_projected(feature) for feature in enroll_features]
+    model = numpy.mean(numpy.vstack(enroll_features), axis=0)
+    return model
+
+
+  ######################################################
+  ################ Feature comparison ##################
+  def read_model(self, model_file):
+    """Reads the whitened i-vector that holds the model"""
+    return bob.bio.base.load(model_file)
+
+  def read_probe(self, probe_file):
+    """read probe file which is an i-vector"""
+    return bob.bio.base.load(probe_file)
+
+  def score(self, model, probe):
+    """Computes the score for the given model and the given probe."""
+    self._check_projected(model)
+    self._check_projected(probe)
+    return numpy.dot(model/numpy.linalg.norm(model), probe/numpy.linalg.norm(probe))
+
+
+  def score_for_multiple_probes(self, model, probes):
+    """This function computes the score between the given model and several given probe files."""
+    [self._check_projected(probe) for probe in probes]
+    probe = numpy.mean(numpy.vstack(probes), axis=0)
+    return self.score(model, probe)
diff --git a/bob/bio/gmm/algorithm/__init__.py b/bob/bio/gmm/algorithm/__init__.py
index dff2ced..e302963 100644
--- a/bob/bio/gmm/algorithm/__init__.py
+++ b/bob/bio/gmm/algorithm/__init__.py
@@ -1,3 +1,4 @@
 from .GMM import GMM, GMMRegular
 from .JFA import JFA
 from .ISV import ISV
+from .IVector import IVector
diff --git a/bob/bio/gmm/config/algorithm/isv.py b/bob/bio/gmm/config/algorithm/isv.py
index 24a8be4..3ae069d 100644
--- a/bob/bio/gmm/config/algorithm/isv.py
+++ b/bob/bio/gmm/config/algorithm/isv.py
@@ -1,7 +1,6 @@
 #!/usr/bin/env python
 
 import bob.bio.gmm
-import numpy
 
 algorithm = bob.bio.gmm.algorithm.ISV(
     # ISV parameters
diff --git a/bob/bio/gmm/config/algorithm/ivector.py b/bob/bio/gmm/config/algorithm/ivector.py
new file mode 100644
index 0000000..ec07b80
--- /dev/null
+++ b/bob/bio/gmm/config/algorithm/ivector.py
@@ -0,0 +1,10 @@
+import bob.bio.gmm
+
+algorithm = bob.bio.gmm.algorithm.IVector(
+    # IVector parameters
+    subspace_dimension_of_t = 400,
+    update_sigma = True,
+    tv_training_iterations = 3,  # Number of EM iterations for the TV training
+    # GMM parameters
+    number_of_gaussians = 512,
+)
diff --git a/bob/bio/gmm/test/data/ivector_model.hdf5 b/bob/bio/gmm/test/data/ivector_model.hdf5
new file mode 100644
index 0000000000000000000000000000000000000000..1c2349f4f18926d1be88f569c215ed8665d480ef
GIT binary patch
literal 2160
zcmeD5aB<`1lHy_j0S*oZ76t(j3y%LoK>-Iu2+I8r;W02IKpBisx&unDV1h6h89<PM
zK?1^M5QLhKt}Z0V)s=yPkpX5tjD~7sFkpeOpw57BM#&Kq0v@i80U*ytfCvT#Xewf0
zH~~#(P+^9|qN2n~22e_Zh<HFKvPnh;HU@Co1`{B2At{-W5h4Jy9!4`WNPyJ~D%dkX
z(y9Z42{(hue`bgiSQ$9L@=Q!jU;{Xy>X{i6Aj;uBgQypRngUWRd^#YMfuRB-F+gOn
upT7$zM?)RUpaAvP06AyW+R+dg4S_)%0`>e%ik@PR?5{cm>Q?-Dz8?TbqBybu

literal 0
HcmV?d00001

diff --git a/bob/bio/gmm/test/data/ivector_projected.hdf5 b/bob/bio/gmm/test/data/ivector_projected.hdf5
new file mode 100644
index 0000000000000000000000000000000000000000..013da29abc64fdda1a5b85eb737886147b0f18ed
GIT binary patch
literal 2160
zcmeD5aB<`1lHy_j0S*oZ76t(j3y%LoK>-Iu2+I8r;W02IKpBisx&unDV1h6h89<PM
zK?1^M5QLhKt}Z0V)s=yPkpX5tjD~7sFkpeOpw57BM#&Kq0v@i80U*ytfCvT#Xewf0
zH~~#(P+^9|qN2n~22e_Zh<HFKvPnh;HU@Co1`{B2At{-W5h4Jy9!4`WNPyJ~D%dkX
z(y9Z42{(hue`bgiSQ$9L@=Q!jU;{Xy>X{i6Aj;uBgQypRngUWRd^#YMfuRB-F+gOn
upT7$zM?)RUpaAvP06AyW+R+dg4S_)%0uQwuD|n{8+<%BUtn8in<NW|#XF56n

literal 0
HcmV?d00001

diff --git a/bob/bio/gmm/test/data/ivector_projector.hdf5 b/bob/bio/gmm/test/data/ivector_projector.hdf5
new file mode 100644
index 0000000000000000000000000000000000000000..726988762bbdfdb51d6f62493a41e3be3c4fa3dd
GIT binary patch
literal 19464
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

literal 0
HcmV?d00001

diff --git a/bob/bio/gmm/test/test_algorithms.py b/bob/bio/gmm/test/test_algorithms.py
index b358496..ae933d7 100644
--- a/bob/bio/gmm/test/test_algorithms.py
+++ b/bob/bio/gmm/test/test_algorithms.py
@@ -326,80 +326,63 @@ def test_jfa():
   # assert abs(jfa1.score_for_multiple_probes(model, [probe, probe]) - reference_score) < 1e-5, jfa1.score_for_multiple_probes(model, [probe, probe])
 
 
-"""
-  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)
 
+def test_ivector():
+  temp_file = bob.io.base.test_utils.temporary_filename()
+  ivec1 = bob.bio.base.load_resource("ivector", "algorithm")
+  assert isinstance(ivec1, bob.bio.gmm.algorithm.IVector)
+  assert isinstance(ivec1, bob.bio.gmm.algorithm.GMM)
+  assert isinstance(ivec1, bob.bio.base.algorithm.Algorithm)
+  assert ivec1.performs_projection
+  assert ivec1.requires_projector_training
+  assert ivec1.use_projected_features_for_enrollment
+  assert not ivec1.split_training_features_by_client
+  assert not ivec1.requires_enroller_training
+
+  # create smaller IVector object
+  ivec2 = bob.bio.gmm.algorithm.IVector(
+      number_of_gaussians = 2,
+      subspace_dimension_of_t = 2,
+      kmeans_training_iterations = 1,
+      tv_training_iterations = 1,
+      INIT_SEED = seed_value
+  )
+
+  train_data = utils.random_training_set((100,45), count=5, minimum=-5., maximum=5.)
+  # reference is the same as for GMM projection
+  reference_file = pkg_resources.resource_filename('bob.bio.gmm.test', 'data/ivector_projector.hdf5')
+  try:
     # 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
-"""
+    ivec2.train_projector(train_data, temp_file)
+
+    assert os.path.exists(temp_file)
+
+    if regenerate_refs: shutil.copy(temp_file, reference_file)
+
+    # check projection matrix
+    ivec1.load_projector(reference_file)
+    ivec2.load_projector(temp_file)
+
+    assert ivec1.ubm.is_similar_to(ivec2.ubm)
+    assert ivec1.tv.is_similar_to(ivec2.tv)
+    assert ivec1.whitener.is_similar_to(ivec2.whitener)
+  finally:
+    if os.path.exists(temp_file): os.remove(temp_file)
+
+  # generate and project random feature
+  feature = utils.random_array((20,45), -5., 5., seed=84)
+  projected = ivec1.project(feature)
+  _compare(projected, pkg_resources.resource_filename('bob.bio.gmm.test', 'data/ivector_projected.hdf5'), ivec1.write_feature, ivec1.read_feature)
+
+  # enroll model from random features
+  random_features = utils.random_training_set((20,45), count=5, minimum=-5., maximum=5.)
+  enroll_features = [ivec1.project(feature) for feature in random_features]
+  model = ivec1.enroll(enroll_features)
+  _compare(model, pkg_resources.resource_filename('bob.bio.gmm.test', 'data/ivector_model.hdf5'), ivec1.write_model, ivec1.read_model)
+
+  # compare model with probe
+  probe = ivec1.read_probe(pkg_resources.resource_filename('bob.bio.gmm.test', 'data/ivector_projected.hdf5'))
+  reference_score = -0.00187151
+  assert abs(ivec1.score(model, probe) - reference_score) < 1e-5, "The scores differ: %3.8f, %3.8f" % (ivec1.score(model, probe), reference_score)
+  # TODO: implement that
+  assert abs(ivec1.score_for_multiple_probes(model, [probe, probe]) - reference_score) < 1e-5
diff --git a/setup.py b/setup.py
index 6b18015..801609b 100644
--- a/setup.py
+++ b/setup.py
@@ -121,6 +121,7 @@ setup(
         'gmm-regular       = bob.bio.gmm.config.algorithm.gmm_regular:algorithm',
         'jfa               = bob.bio.gmm.config.algorithm.jfa:algorithm',
         'isv               = bob.bio.gmm.config.algorithm.isv:algorithm',
+        'ivector           = bob.bio.gmm.config.algorithm.ivector:algorithm',
       ],
    },
 
-- 
GitLab