diff --git a/.travis.yml b/.travis.yml index 5694f5c2b79e1f71ba83cc3bcb35e6c8b35c4bd3..f38280c48718810a8eaa1d57a846942d8d53544c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,14 +14,11 @@ matrix: - NUMPYSPEC===1.7.1 before_install: - sudo add-apt-repository -y ppa:biometrics/bob -- sudo add-apt-repository -y ppa:biometrics/bob.preview - sudo apt-get update -qq -- sudo apt-get install -qq --force-yes bob-dev -- if [ -n "${NUMPYSPEC}" ]; then sudo apt-get install -qq libatlas-dev libatlas-base-dev - liblapack-dev gfortran; fi +- sudo apt-get install -qq --force-yes libboost-all-dev libblitz1-dev libhdf5-serial-dev libatlas-dev libatlas-base-dev liblapack-dev +- if [ -n "${NUMPYSPEC}" ]; then sudo apt-get install -qq gfortran; fi - if [ -n "${NUMPYSPEC}" ]; then pip install --upgrade pip setuptools; fi -- if [ -n "${NUMPYSPEC}" ]; then pip install --find-links http://wheels.astropy.org/ - --find-links http://wheels2.astropy.org/ --use-wheel numpy$NUMPYSPEC sphinx nose; +- if [ -n "${NUMPYSPEC}" ]; then pip install --find-links http://wheels.astropy.org/ --find-links http://wheels2.astropy.org/ --use-wheel numpy$NUMPYSPEC sphinx nose; fi - pip install cpp-coveralls install: diff --git a/bob/learn/misc/__init__.py b/bob/learn/misc/__init__.py index 15128195bae11c14871bf2ce0173478fe5f7453b..bae1acf977d226b33e12046bd6e1c67d10cab0ad 100644 --- a/bob/learn/misc/__init__.py +++ b/bob/learn/misc/__init__.py @@ -1,4 +1,14 @@ +# import Libraries of other lib packages +import bob.io.base +import bob.math +import bob.learn.linear + +# import our own Library +import bob.extension +bob.extension.load_bob_library('bob.learn.misc', __file__) + from ._old_library import * +#from ._library import * from . import version from .version import module as __version__ diff --git a/bob/learn/misc/cpp/BICMachine.cpp b/bob/learn/misc/cpp/BICMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e877b076c48f23afe0771d0296c1509c1fa61e1b --- /dev/null +++ b/bob/learn/misc/cpp/BICMachine.cpp @@ -0,0 +1,348 @@ +/** + * @date Tue Jun 5 16:54:27 CEST 2012 + * @author Manuel Guenther <Manuel.Guenther@idiap.ch> + * + * A machine that implements the liner projection of input to the output using + * weights, biases and sums: + * output = sum(inputs * weights) + bias + * It is possible to setup the machine to previously normalize the input taking + * into consideration some input bias and division factor. It is also possible + * to set it up to have an activation function. + * A linear classifier. See C. M. Bishop, "Pattern Recognition and Machine + * Learning", chapter 4 + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/BICMachine.h> +#include <bob.math/linear.h> +#include <bob.core/assert.h> +#include <bob.core/check.h> + +/** + * Initializes an empty BIC Machine + * + * @param use_DFFS Add the Distance From Feature Space during score computation? + */ +bob::machine::BICMachine::BICMachine(bool use_DFFS) +: + m_project_data(use_DFFS), + m_use_DFFS(use_DFFS) +{} + +/** + * Assigns the other BICMachine to this, i.e., makes a deep copy of the given machine. + * + * @param other The other BICMachine to get a shallow copy of + * @return a reference to *this + */ +bob::machine::BICMachine::BICMachine(const BICMachine& other) +: + m_project_data(other.m_project_data), + m_use_DFFS(other.m_use_DFFS) +{ + if (m_project_data){ + setBIC(false, other.m_mu_I, other.m_lambda_I, other.m_Phi_I, other.m_rho_I, true); + setBIC(true , other.m_mu_E, other.m_lambda_E, other.m_Phi_E, other.m_rho_E, true); + } else { + setIEC(false, other.m_mu_I, other.m_lambda_I, true); + setIEC(true , other.m_mu_E, other.m_lambda_E, true); + } +} + +/** + * Assigns the other BICMachine to this, i.e., makes a deep copy of the given BICMachine + * + * @param other The other BICMachine to get a deep copy of + * @return a reference to *this + */ +bob::machine::BICMachine& bob::machine::BICMachine::operator=(const BICMachine& other) +{ + if (this != &other) + { + if (other.m_project_data){ + m_use_DFFS = other.m_use_DFFS; + setBIC(false, other.m_mu_I, other.m_lambda_I, other.m_Phi_I, other.m_rho_I, true); + setBIC(true , other.m_mu_E, other.m_lambda_E, other.m_Phi_E, other.m_rho_E, true); + } else { + m_use_DFFS = false; + setIEC(false, other.m_mu_I, other.m_lambda_I, true); + setIEC(true , other.m_mu_E, other.m_lambda_E, true); + } + } + return *this; +} + +/** + * Compares if this machine and the given one are identical + * + * @param other The BICMachine to compare with + * @return true if both machines are identical, i.e., have exactly the same parameters, otherwise false + */ +bool bob::machine::BICMachine::operator==(const BICMachine& other) const +{ + return (m_project_data == other.m_project_data && + (!m_project_data || m_use_DFFS == other.m_use_DFFS) && + bob::core::array::isEqual(m_mu_I, other.m_mu_I) && + bob::core::array::isEqual(m_mu_E, other.m_mu_E) && + bob::core::array::isEqual(m_lambda_I, other.m_lambda_I) && + bob::core::array::isEqual(m_lambda_E, other.m_lambda_E) && + (!m_project_data || + (bob::core::array::isEqual(m_Phi_I, other.m_Phi_I) && + bob::core::array::isEqual(m_Phi_E, other.m_Phi_E) && + (!m_use_DFFS || (m_rho_I == other.m_rho_I && m_rho_E == other.m_rho_E))))); +} + +/** + * Checks if this machine and the given one are different + * + * @param other The BICMachine to compare with + * @return false if both machines are identical, i.e., have exactly the same parameters, otherwise true + */ +bool bob::machine::BICMachine::operator!=(const BICMachine& other) const +{ + return !(this->operator==(other)); +} + +/** + * Compares the given machine with this for similarity + * + * @param other The BICMachine to compare with + * @param r_epsilon The largest value any parameter might relatively differ between the two machines + * @param a_epsilon The largest value any parameter might absolutely differ between the two machines + + * @return true if both machines are approximately equal, otherwise false + */ +bool bob::machine::BICMachine::is_similar_to(const BICMachine& other, + const double r_epsilon, const double a_epsilon) const +{ + if (m_project_data){ + // compare data + if (not bob::core::array::hasSameShape(m_Phi_I, other.m_Phi_I)) return false; + if (not bob::core::array::hasSameShape(m_Phi_E, other.m_Phi_E)) return false; + // check that the projection matrices are close, + // but allow that eigen vectors might have opposite directions + // (i.e., they are either identical -> difference is 0, or opposite -> sum is zero) + for (int i = m_Phi_I.extent(1); i--;){ + const blitz::Array<double,1>& sub1 = m_Phi_I(blitz::Range::all(), i); + const blitz::Array<double,1>& sub2 = other.m_Phi_I(blitz::Range::all(), i); + blitz::Array<double,1> sub2_negative(-sub2); + if (!bob::core::array::isClose(sub1, sub2, r_epsilon, a_epsilon) && !bob::core::array::isClose(sub1, sub2_negative, r_epsilon, a_epsilon)) return false; + } + for (int i = m_Phi_E.shape()[1]; i--;){ + const blitz::Array<double,1>& sub1 = m_Phi_E(blitz::Range::all(), i); + const blitz::Array<double,1>& sub2 = other.m_Phi_E(blitz::Range::all(), i); + blitz::Array<double,1> sub2_negative(-sub2); + if (!bob::core::array::isClose(sub1, sub2, r_epsilon, a_epsilon) && !bob::core::array::isClose(sub1, sub2_negative, r_epsilon, a_epsilon)) return false; + } + } + + return (m_project_data == other.m_project_data && + (!m_project_data || m_use_DFFS == other.m_use_DFFS) && + bob::core::array::isClose(m_mu_I, other.m_mu_I, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_mu_E, other.m_mu_E, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_lambda_I, other.m_lambda_I, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_lambda_E, other.m_lambda_E, r_epsilon, a_epsilon) && + (!m_project_data || + (!m_use_DFFS || (bob::core::isClose(m_rho_I, other.m_rho_I, r_epsilon, a_epsilon) && + bob::core::isClose(m_rho_E, other.m_rho_E, r_epsilon, a_epsilon))))); +} + + + +void bob::machine::BICMachine::initialize(bool clazz, int input_length, int projected_length){ + blitz::Array<double,1>& diff = clazz ? m_diff_E : m_diff_I; + blitz::Array<double,1>& proj = clazz ? m_proj_E : m_proj_I; + diff.resize(input_length); + proj.resize(projected_length); +} + +/** + * Sets the parameters of the given class that are required for computing the IEC scores (Guenther, Wuertz) + * + * @param clazz false for the intrapersonal class, true for the extrapersonal one. + * @param mean The mean vector of the training data + * @param variances The variances of the training data + * @param copy_data If true, makes a deep copy of the matrices, otherwise it just references it (the default) + */ +void bob::machine::BICMachine::setIEC( + bool clazz, + const blitz::Array<double,1>& mean, + const blitz::Array<double,1>& variances, + bool copy_data +){ + m_project_data = false; + // select the right matrices to write + blitz::Array<double,1>& mu = clazz ? m_mu_E : m_mu_I; + blitz::Array<double,1>& lambda = clazz ? m_lambda_E : m_lambda_I; + + // copy mean and variances + if (copy_data){ + mu.resize(mean.shape()); + mu = mean; + lambda.resize(variances.shape()); + lambda = variances; + } else { + mu.reference(mean); + lambda.reference(variances); + } +} + +/** + * Sets the parameters of the given class that are required for computing the BIC scores (Teixeira) + * + * @param clazz false for the intrapersonal class, true for the extrapersonal one. + * @param mean The mean vector of the training data + * @param variances The eigenvalues of the training data + * @param projection The PCA projection matrix + * @param rho The residual eigenvalues, used for DFFS calculation + * @param copy_data If true, makes a deep copy of the matrices, otherwise it just references it (the default) + */ +void bob::machine::BICMachine::setBIC( + bool clazz, + const blitz::Array<double,1>& mean, + const blitz::Array<double,1>& variances, + const blitz::Array<double,2>& projection, + const double rho, + bool copy_data +){ + m_project_data = true; + // select the right matrices to write + blitz::Array<double,1>& mu = clazz ? m_mu_E : m_mu_I; + blitz::Array<double,1>& lambda = clazz ? m_lambda_E : m_lambda_I; + blitz::Array<double,2>& Phi = clazz ? m_Phi_E : m_Phi_I; + double& rho_ = clazz ? m_rho_E : m_rho_I; + + // copy information + if (copy_data){ + mu.resize(mean.shape()); + mu = mean; + lambda.resize(variances.shape()); + lambda = variances; + Phi.resize(projection.shape()); + Phi = projection; + } else { + mu.reference(mean); + lambda.reference(variances); + Phi.reference(projection); + } + rho_ = rho; + + // check that rho has a reasonable value (if it is used) + if (m_use_DFFS && rho_ < 1e-12) throw std::runtime_error("The given average eigenvalue (rho) is too close to zero"); + + // initialize temporaries + initialize(clazz, Phi.shape()[0], Phi.shape()[1]); +} + +/** + * Set or unset the usage of the Distance From Feature Space + * + * @param use_DFFS The new value of use_DFFS + */ +void bob::machine::BICMachine::use_DFFS(bool use_DFFS){ + m_use_DFFS = use_DFFS; + if (m_project_data && m_use_DFFS && (m_rho_E < 1e-12 || m_rho_I < 1e-12)) std::runtime_error("The average eigenvalue (rho) is too close to zero, so using DFFS will not work"); +} + +/** + * Loads the BICMachine from the given hdf5 file. + * + * @param config The hdf5 file containing the required information. + */ +void bob::machine::BICMachine::load(bob::io::base::HDF5File& config){ + //reads all data directly into the member variables + m_project_data = config.read<bool>("project_data"); + m_mu_I.reference(config.readArray<double,1>("intra_mean")); + m_lambda_I.reference(config.readArray<double,1>("intra_variance")); + if (m_project_data){ + m_use_DFFS = config.read<bool>("use_DFFS"); + m_Phi_I.reference(config.readArray<double,2>("intra_subspace")); + initialize(false, m_Phi_I.shape()[0], m_Phi_I.shape()[1]); + m_rho_I = config.read<double>("intra_rho"); + } + + m_mu_E.reference(config.readArray<double,1>("extra_mean")); + m_lambda_E.reference(config.readArray<double,1>("extra_variance")); + if (m_project_data){ + m_Phi_E.reference(config.readArray<double,2>("extra_subspace")); + initialize(true, m_Phi_E.shape()[0], m_Phi_E.shape()[1]); + m_rho_E = config.read<double>("extra_rho"); + } + // check that rho has reasonable values + if (m_project_data && m_use_DFFS && (m_rho_E < 1e-12 || m_rho_I < 1e-12)) throw std::runtime_error("The loaded average eigenvalue (rho) is too close to zero"); + +} + +/** + * Saves the parameters of the BICMachine to the given hdf5 file. + * + * @param config The hdf5 file to write the configuration into. + */ +void bob::machine::BICMachine::save(bob::io::base::HDF5File& config) const{ + config.set("project_data", m_project_data); + config.setArray("intra_mean", m_mu_I); + config.setArray("intra_variance", m_lambda_I); + if (m_project_data){ + config.set("use_DFFS", m_use_DFFS); + config.setArray("intra_subspace", m_Phi_I); + config.set("intra_rho", m_rho_I); + } + + config.setArray("extra_mean", m_mu_E); + config.setArray("extra_variance", m_lambda_E); + if (m_project_data){ + config.setArray("extra_subspace", m_Phi_E); + config.set("extra_rho", m_rho_E); + } +} + +/** + * Computes the BIC or IEC score for the given input vector. + * The score itself is the log-likelihood score of the given input vector belonging to the intrapersonal class. + * No sanity checks of input and output are performed. + * + * @param input A vector (of difference values) to compute the BIC or IEC score for. + * @param output The one-element array that will contain the score afterwards. + */ +void bob::machine::BICMachine::forward_(const blitz::Array<double,1>& input, double& output) const{ + if (m_project_data){ + // subtract mean + m_diff_I = input - m_mu_I; + m_diff_E = input - m_mu_E; + // project data to intrapersonal and extrapersonal subspace + bob::math::prod(m_diff_I, m_Phi_I, m_proj_I); + bob::math::prod(m_diff_E, m_Phi_E, m_proj_E); + + // compute Mahalanobis distance + output = blitz::sum(blitz::pow2(m_proj_E) / m_lambda_E) - blitz::sum(blitz::pow2(m_proj_I) / m_lambda_I); + + // add the DFFS? + if (m_use_DFFS){ + output += blitz::sum(blitz::pow2(m_diff_E) - blitz::pow2(m_proj_E)) / m_rho_E; + output -= blitz::sum(blitz::pow2(m_diff_I) - blitz::pow2(m_proj_I)) / m_rho_I; + } + output /= (m_proj_E.extent(0) + m_proj_I.extent(0)); + } else { + // forward without projection + output = blitz::mean( blitz::pow2(input - m_mu_E) / m_lambda_E + - blitz::pow2(input - m_mu_I) / m_lambda_I); + } +} + +/** + * Computes the BIC or IEC score for the given input vector. + * The score itself is the log-likelihood score of the given input vector belonging to the intrapersonal class. + * Sanity checks of input and output shape are performed. + * + * @param input A vector (of difference values) to compute the BIC or IEC score for. + * @param output The one-element array that will contain the score afterwards. + */ +void bob::machine::BICMachine::forward(const blitz::Array<double,1>& input, double& output) const{ + // perform some checks + bob::core::array::assertSameShape(input, m_mu_E); + + // call the actual method + forward_(input, output); +} + diff --git a/bob/learn/misc/cpp/BICTrainer.cpp b/bob/learn/misc/cpp/BICTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5c52a28e27a061a317cc0aac4b8791fe38d970f5 --- /dev/null +++ b/bob/learn/misc/cpp/BICTrainer.cpp @@ -0,0 +1,94 @@ +/** + * @date Wed Jun 6 10:29:09 CEST 2012 + * @author Manuel Guenther <Manuel.Guenther@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/BICTrainer.h> +#include <bob.learn.linear/pca.h> +#include <bob.learn.linear/machine.h> + +static double sqr(const double& x){ + return x*x; +} + +/** + * This function trains one of the classes of the given machine with the given data. + * It computes either BIC projection matrices, or IEC mean and variance. + * + * @param clazz false for the intrapersonal class, true for the extrapersonal one. + * @param machine The machine to be trained. + * @param differences A set of (intra/extra)-personal difference vectors that should be trained. + */ +void bob::trainer::BICTrainer::train_single(bool clazz, bob::machine::BICMachine& machine, const blitz::Array<double,2>& differences) const { + int subspace_dim = clazz ? m_M_E : m_M_I; + int input_dim = differences.extent(1); + int data_count = differences.extent(0); + blitz::Range a = blitz::Range::all(); + + if (subspace_dim){ + // train the class using BIC + + // Compute PCA on the given dataset + bob::learn::linear::PCATrainer trainer; + const int n_eigs = trainer.output_size(differences); + bob::learn::linear::Machine pca(input_dim, n_eigs); + blitz::Array<double,1> variances(n_eigs); + trainer.train(pca, variances, differences); + + // compute rho + double rho = 0.; + int non_zero_eigenvalues = std::min(input_dim, data_count-1); + // assert that the number of kept eigenvalues is not chosen to big + if (subspace_dim >= non_zero_eigenvalues) + throw std::runtime_error((boost::format("The chosen subspace dimension %d is larger than the theoretical number of nonzero eigenvalues %d")%subspace_dim%non_zero_eigenvalues).str()); + // compute the average of the reminding eigenvalues + for (int i = subspace_dim; i < non_zero_eigenvalues; ++i){ + rho += variances(i); + } + rho /= non_zero_eigenvalues - subspace_dim; + + // limit dimensionalities + pca.resize(input_dim, subspace_dim); + variances.resizeAndPreserve(subspace_dim); + + // check that all variances are meaningful + for (int i = 0; i < subspace_dim; ++i){ + if (variances(i) < 1e-12) + throw std::runtime_error((boost::format("The chosen subspace dimension is %d, but the %dth eigenvalue is already to small")%subspace_dim%i).str()); + } + + // initialize the machine + blitz::Array<double, 2> projection = pca.getWeights(); + blitz::Array<double, 1> mean = pca.getInputSubtraction(); + machine.setBIC(clazz, mean, variances, projection, rho); + } else { + // train the class using IEC + // => compute mean and variance only + blitz::Array<double,1> mean(input_dim), variance(input_dim); + + // compute mean and variance + mean = 0.; + variance = 0.; + for (int n = data_count; n--;){ + const blitz::Array<double,1>& diff = differences(n,a); + assert(diff.shape()[0] == input_dim); + for (int i = input_dim; i--;){ + mean(i) += diff(i); + variance(i) += sqr(diff(i)); + } + } + // normalize mean and variances + for (int i = 0; i < input_dim; ++i){ + // intrapersonal + variance(i) = (variance(i) - sqr(mean(i)) / data_count) / (data_count - 1.); + mean(i) /= data_count; + if (variance(i) < 1e-12) + throw std::runtime_error((boost::format("The variance of the %dth dimension is too small. Check your data!")%i).str()); + } + + // set the results to the machine + machine.setIEC(clazz, mean, variance); + } +} diff --git a/bob/learn/misc/cpp/EMPCATrainer.cpp b/bob/learn/misc/cpp/EMPCATrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e988145bf1baf8d6b0e3067ef81774e3d5b50229 --- /dev/null +++ b/bob/learn/misc/cpp/EMPCATrainer.cpp @@ -0,0 +1,423 @@ +/** + * @date Tue Oct 11 12:18:23 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <vector> +#include <algorithm> +#include <boost/random.hpp> +#include <cmath> + +#include <bob.learn.misc/EMPCATrainer.h> +#include <bob.core/array_copy.h> +#include <bob.core/check.h> +#include <bob.math/linear.h> +#include <bob.math/det.h> +#include <bob.math/inv.h> +#include <bob.math/stats.h> + +bob::trainer::EMPCATrainer::EMPCATrainer(double convergence_threshold, + size_t max_iterations, bool compute_likelihood): + EMTrainer<bob::learn::linear::Machine, blitz::Array<double,2> >(convergence_threshold, + max_iterations, compute_likelihood), + m_S(0,0), + m_z_first_order(0,0), m_z_second_order(0,0,0), + m_inW(0,0), m_invM(0,0), m_sigma2(0), m_f_log2pi(0), + m_tmp_dxf(0,0), m_tmp_d(0), m_tmp_f(0), + m_tmp_dxd_1(0,0), m_tmp_dxd_2(0,0), + m_tmp_fxd_1(0,0), m_tmp_fxd_2(0,0), + m_tmp_fxf_1(0,0), m_tmp_fxf_2(0,0) +{ +} + +bob::trainer::EMPCATrainer::EMPCATrainer(const bob::trainer::EMPCATrainer& other): + EMTrainer<bob::learn::linear::Machine, blitz::Array<double,2> >(other.m_convergence_threshold, + other.m_max_iterations, other.m_compute_likelihood), + m_S(bob::core::array::ccopy(other.m_S)), + m_z_first_order(bob::core::array::ccopy(other.m_z_first_order)), + m_z_second_order(bob::core::array::ccopy(other.m_z_second_order)), + m_inW(bob::core::array::ccopy(other.m_inW)), + m_invM(bob::core::array::ccopy(other.m_invM)), + m_sigma2(other.m_sigma2), m_f_log2pi(other.m_f_log2pi), + m_tmp_dxf(bob::core::array::ccopy(other.m_tmp_dxf)), + m_tmp_d(bob::core::array::ccopy(other.m_tmp_d)), + m_tmp_f(bob::core::array::ccopy(other.m_tmp_f)), + m_tmp_dxd_1(bob::core::array::ccopy(other.m_tmp_dxd_1)), + m_tmp_dxd_2(bob::core::array::ccopy(other.m_tmp_dxd_2)), + m_tmp_fxd_1(bob::core::array::ccopy(other.m_tmp_fxd_1)), + m_tmp_fxd_2(bob::core::array::ccopy(other.m_tmp_fxd_2)), + m_tmp_fxf_1(bob::core::array::ccopy(other.m_tmp_fxf_1)), + m_tmp_fxf_2(bob::core::array::ccopy(other.m_tmp_fxf_2)) +{ +} + +bob::trainer::EMPCATrainer::~EMPCATrainer() +{ +} + +bob::trainer::EMPCATrainer& bob::trainer::EMPCATrainer::operator= + (const bob::trainer::EMPCATrainer& other) +{ + if (this != &other) + { + bob::trainer::EMTrainer<bob::learn::linear::Machine, + blitz::Array<double,2> >::operator=(other); + m_S = bob::core::array::ccopy(other.m_S); + m_z_first_order = bob::core::array::ccopy(other.m_z_first_order); + m_z_second_order = bob::core::array::ccopy(other.m_z_second_order); + m_inW = bob::core::array::ccopy(other.m_inW); + m_invM = bob::core::array::ccopy(other.m_invM); + m_sigma2 = other.m_sigma2; + m_f_log2pi = other.m_f_log2pi; + m_tmp_dxf = bob::core::array::ccopy(other.m_tmp_dxf); + m_tmp_d = bob::core::array::ccopy(other.m_tmp_d); + m_tmp_f = bob::core::array::ccopy(other.m_tmp_f); + m_tmp_dxd_1 = bob::core::array::ccopy(other.m_tmp_dxd_1); + m_tmp_dxd_2 = bob::core::array::ccopy(other.m_tmp_dxd_2); + m_tmp_fxd_1 = bob::core::array::ccopy(other.m_tmp_fxd_1); + m_tmp_fxd_2 = bob::core::array::ccopy(other.m_tmp_fxd_2); + m_tmp_fxf_1 = bob::core::array::ccopy(other.m_tmp_fxf_1); + m_tmp_fxf_2 = bob::core::array::ccopy(other.m_tmp_fxf_2); + } + return *this; +} + +bool bob::trainer::EMPCATrainer::operator== + (const bob::trainer::EMPCATrainer &other) const +{ + return bob::trainer::EMTrainer<bob::learn::linear::Machine, + blitz::Array<double,2> >::operator==(other) && + bob::core::array::isEqual(m_S, other.m_S) && + bob::core::array::isEqual(m_z_first_order, other.m_z_first_order) && + bob::core::array::isEqual(m_z_second_order, other.m_z_second_order) && + bob::core::array::isEqual(m_inW, other.m_inW) && + bob::core::array::isEqual(m_invM, other.m_invM) && + m_sigma2 == other.m_sigma2 && + m_f_log2pi == other.m_f_log2pi; +} + +bool bob::trainer::EMPCATrainer::operator!= + (const bob::trainer::EMPCATrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::EMPCATrainer::is_similar_to + (const bob::trainer::EMPCATrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::EMTrainer<bob::learn::linear::Machine, + blitz::Array<double,2> >::is_similar_to(other, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_S, other.m_S, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_z_first_order, other.m_z_first_order, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_z_second_order, other.m_z_second_order, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_inW, other.m_inW, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_invM, other.m_invM, r_epsilon, a_epsilon) && + bob::core::isClose(m_sigma2, other.m_sigma2, r_epsilon, a_epsilon) && + bob::core::isClose(m_f_log2pi, other.m_f_log2pi, r_epsilon, a_epsilon); +} + +void bob::trainer::EMPCATrainer::initialize(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar) +{ + // reinitializes array members and checks dimensionality + initMembers(machine, ar); + + // computes the mean and the covariance if required + computeMeanVariance(machine, ar); + + // Random initialization of W and sigma2 + initRandomWSigma2(machine); + + // Computes the product m_inW = W^T.W + computeWtW(machine); + // Computes inverse(M), where M = Wt * W + sigma2 * Id + computeInvM(); +} + +void bob::trainer::EMPCATrainer::finalize(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar) +{ +} + +void bob::trainer::EMPCATrainer::initMembers( + const bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar) +{ + // Gets dimensions + const size_t n_samples = ar.extent(0); + const size_t n_features = ar.extent(1); + + // Checks that the dimensions are matching + const size_t n_inputs = machine.inputSize(); + const size_t n_outputs = machine.outputSize(); + + // Checks that the dimensions are matching + if (n_inputs != n_features) { + boost::format m("number of inputs (%u) does not match the number of features (%u)"); + m % n_inputs % n_features; + throw std::runtime_error(m.str()); + } + + // Covariance matrix S is only required to compute the log likelihood + if (m_compute_likelihood) + m_S.resize(n_features,n_features); + else + m_S.resize(0,0); + m_z_first_order.resize(n_samples, n_outputs); + m_z_second_order.resize(n_samples, n_outputs, n_outputs); + m_inW.resize(n_outputs, n_outputs); + m_invM.resize(n_outputs, n_outputs); + m_sigma2 = 0.; + m_f_log2pi = n_features * log(2*M_PI); + + // Cache + m_tmp_dxf.resize(n_outputs, n_features); + m_tmp_d.resize(n_outputs); + m_tmp_f.resize(n_features); + m_tmp_dxd_1.resize(n_outputs, n_outputs); + m_tmp_dxd_2.resize(n_outputs, n_outputs); + m_tmp_fxd_1.resize(n_features, n_outputs); + m_tmp_fxd_2.resize(n_features, n_outputs); + // The following large cache matrices are only required to compute the + // log likelihood. + if (m_compute_likelihood) + { + m_tmp_fxf_1.resize(n_features, n_features); + m_tmp_fxf_2.resize(n_features, n_features); + } + else + { + m_tmp_fxf_1.resize(0,0); + m_tmp_fxf_2.resize(0,0); + } +} + +void bob::trainer::EMPCATrainer::computeMeanVariance(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar) +{ + size_t n_samples = ar.extent(0); + blitz::Array<double,1> mu = machine.updateInputSubtraction(); + blitz::Range all = blitz::Range::all(); + if (m_compute_likelihood) + { + // Mean and scatter computation + bob::math::scatter(ar, m_S, mu); + // divides scatter by N-1 + m_S /= static_cast<double>(n_samples-1); + } + else + { + // computes the mean and updates mu + mu = 0.; + for (size_t i=0; i<n_samples; ++i) + mu += ar(i,all); + mu /= static_cast<double>(n_samples); + } +} + +void bob::trainer::EMPCATrainer::initRandomWSigma2(bob::learn::linear::Machine& machine) +{ + // Initializes the random number generator + boost::uniform_01<> range01; + boost::variate_generator<boost::mt19937&, boost::uniform_01<> > die(*m_rng, range01); + + // W initialization (TODO: add method in core) + blitz::Array<double,2> W = machine.updateWeights(); + double ratio = 2.; /// Follows matlab implementation using a ratio of 2 + for (int i=0; i<W.extent(0); ++i) + for (int j=0; j<W.extent(1); ++j) + W(i,j) = die() * ratio; + // sigma2 initialization + m_sigma2 = die() * ratio; +} + +void bob::trainer::EMPCATrainer::computeWtW(bob::learn::linear::Machine& machine) +{ + const blitz::Array<double,2> W = machine.getWeights(); + const blitz::Array<double,2> Wt = W.transpose(1,0); + bob::math::prod(Wt, W, m_inW); +} + +void bob::trainer::EMPCATrainer::computeInvM() +{ + // Compute inverse(M), where M = W^T * W + sigma2 * Id + bob::math::eye(m_tmp_dxd_1); // m_tmp_dxd_1 = Id + m_tmp_dxd_1 *= m_sigma2; // m_tmp_dxd_1 = sigma2 * Id + m_tmp_dxd_1 += m_inW; // m_tmp_dxd_1 = M = W^T * W + sigma2 * Id + bob::math::inv(m_tmp_dxd_1, m_invM); // m_invM = inv(M) +} + + + +void bob::trainer::EMPCATrainer::eStep(bob::learn::linear::Machine& machine, const blitz::Array<double,2>& ar) +{ + // Gets mu and W from the machine + const blitz::Array<double,1>& mu = machine.getInputSubtraction(); + const blitz::Array<double,2>& W = machine.getWeights(); + const blitz::Array<double,2> Wt = W.transpose(1,0); // W^T + + // Computes the statistics + blitz::Range a = blitz::Range::all(); + for(int i=0; i<ar.extent(0); ++i) + { + /// 1/ First order statistics: \f$z_first_order_i = inv(M) W^T (t - \mu)\f$ + // m_tmp_f = t (sample) - mu (normalized sample) + m_tmp_f = ar(i,a) - mu; + // m_tmp_dxf = inv(M) * W^T + bob::math::prod(m_invM, Wt, m_tmp_dxf); + blitz::Array<double,1> z_first_order_i = m_z_first_order(i,blitz::Range::all()); + // z_first_order_i = inv(M) * W^T * (t - mu) + bob::math::prod(m_tmp_dxf, m_tmp_f, z_first_order_i); + + /// 2/ Second order statistics: + /// z_second_order_i = sigma2 * inv(M) + z_first_order_i * z_first_order_i^T + blitz::Array<double,2> z_second_order_i = m_z_second_order(i,blitz::Range::all(),blitz::Range::all()); + // m_tmp_dxd = z_first_order_i * z_first_order_i^T + bob::math::prod(z_first_order_i, z_first_order_i, m_tmp_dxd_1); // outer product + // z_second_order_i = sigma2 * inv(M) + z_second_order_i = m_invM; + z_second_order_i *= m_sigma2; + // z_second_order_i = sigma2 * inv(M) + z_first_order_i * z_first_order_i^T + z_second_order_i += m_tmp_dxd_1; + } +} + +void bob::trainer::EMPCATrainer::mStep(bob::learn::linear::Machine& machine, const blitz::Array<double,2>& ar) +{ + // 1/ New estimate of W + updateW(machine, ar); + + // 2/ New estimate of sigma2 + updateSigma2(machine, ar); + + // Computes the new value of inverse(M), where M = Wt * W + sigma2 * Id + computeInvM(); +} + +void bob::trainer::EMPCATrainer::updateW(bob::learn::linear::Machine& machine, const blitz::Array<double,2>& ar) { + // Get the mean mu and the projection matrix W + const blitz::Array<double,1>& mu = machine.getInputSubtraction(); + blitz::Array<double,2>& W = machine.updateWeights(); + const blitz::Array<double,2> Wt = W.transpose(1,0); // W^T + + // Compute W = sum{ (t_{i} - mu) z_first_order_i^T} * inv( sum{z_second_order_i} ) + m_tmp_fxd_1 = 0.; + m_tmp_dxd_1 = 0.; + blitz::Range a = blitz::Range::all(); + for(int i=0; i<ar.extent(0); ++i) + { + // m_tmp_f = t (sample) - mu (normalized sample) + m_tmp_f = ar(i,a) - mu; + // first order statistics of sample i + blitz::Array<double,1> z_first_order_i = m_z_first_order(i,blitz::Range::all()); + // m_tmp_fxd_2 = (t - mu)*z_first_order_i + bob::math::prod(m_tmp_f, z_first_order_i, m_tmp_fxd_2); + m_tmp_fxd_1 += m_tmp_fxd_2; + + // second order statistics of sample i + blitz::Array<double,2> z_second_order_i = m_z_second_order(i,blitz::Range::all(),blitz::Range::all()); + m_tmp_dxd_1 += z_second_order_i; + } + + // m_tmp_dxd_2 = inv( sum(E(x_i.x_i^T)) ) + bob::math::inv(m_tmp_dxd_1, m_tmp_dxd_2); + // New estimates of W + bob::math::prod(m_tmp_fxd_1, m_tmp_dxd_2, W); + // Updates W'*W as well + bob::math::prod(Wt, W, m_inW); +} + +void bob::trainer::EMPCATrainer::updateSigma2(bob::learn::linear::Machine& machine, const blitz::Array<double,2>& ar) { + // Get the mean mu and the projection matrix W + const blitz::Array<double,1>& mu = machine.getInputSubtraction(); + const blitz::Array<double,2>& W = machine.getWeights(); + const blitz::Array<double,2> Wt = W.transpose(1,0); // W^T + + m_sigma2 = 0.; + blitz::Range a = blitz::Range::all(); + for(int i=0; i<ar.extent(0); ++i) + { + // a. sigma2 += || t - mu ||^2 + // m_tmp_f = t (sample) - mu (normalized sample) + m_tmp_f = ar(i,a) - mu; + // sigma2 += || t - mu ||^2 + m_sigma2 += blitz::sum(blitz::pow2(m_tmp_f)); + + // b. sigma2 -= 2*E(x_i)^T*W^T*(t - mu) + // m_tmp_d = W^T*(t - mu) + bob::math::prod(Wt, m_tmp_f, m_tmp_d); + // first order of i + blitz::Array<double,1> z_first_order_i = m_z_first_order(i,blitz::Range::all()); + // sigma2 -= 2*E(x_i)^T*W^T*(t - mu) + m_sigma2 -= 2*bob::math::dot(z_first_order_i, m_tmp_d); + + // c. sigma2 += trace( E(x_i.x_i^T)*W^T*W ) + // second order of i + blitz::Array<double,2> z_second_order_i = m_z_second_order(i,blitz::Range::all(),blitz::Range::all()); + // m_tmp_dxd_1 = E(x_i.x_i^T)*W^T*W + bob::math::prod(z_second_order_i, m_inW, m_tmp_dxd_1); + // sigma2 += trace( E(x_i.x_i^T)*W^T*W ) + m_sigma2 += bob::math::trace(m_tmp_dxd_1); + } + // Normalization factor + m_sigma2 /= (static_cast<double>(ar.extent(0)) * mu.extent(0)); +} + +double bob::trainer::EMPCATrainer::computeLikelihood(bob::learn::linear::Machine& machine) +{ + // Get W projection matrix + const blitz::Array<double,2>& W = machine.getWeights(); + const blitz::Array<double,2> Wt = W.transpose(1,0); // W^T + const size_t n_features = m_S.extent(0); + + // 1/ Compute det(C), where C = sigma2.I + W.W^T + // det(C) = det(sigma2 * C / sigma2) = det(sigma2 * Id) * det(C / sigma2) + // We are using Sylvester's determinant theorem to compute a dxd + // determinant rather than a fxf one: det(I + A.B) = det(I + B.A) + // det(C) = sigma2^n_features * det(I + W.W^T/sigma2) + // = sigma2^n_features * det(I + W^T.W/sigma2) (cf. Bishop Appendix C) + // detC = det( eye(n_features) * sigma2 ) + + // detC = sigma2^n_features + double detC = pow(m_sigma2, n_features); + // m_tmp_dxd_1 = Id + bob::math::eye(m_tmp_dxd_1); + // m_tmp_dxd_2 = W^T.W + bob::math::prod(Wt, W, m_tmp_dxd_2); + // m_tmp_dxd_2 = W^T.W / sigma2 + m_tmp_dxd_2 /= m_sigma2; + // m_tmp_dxd_1 = Id + W^T.W / sigma2 + m_tmp_dxd_1 += m_tmp_dxd_2; + // detC = sigma2^n_features * det(I + W^T.W/sigma2) + detC *= bob::math::det(m_tmp_dxd_1); + + // 2/ Compute inv(C), where C = sigma2.I + W.W^T + // We are using the following identity (Property C.7 of Bishop's book) + // (A + B.D^-1.C)^-1 = A^-1 - A^-1.B(D+C.A^-1.B)^-1.C.A^-1 + // Hence, inv(C) = sigma2^-1 .(I - W.M^-1.W^T) + + // Compute inverse(M), where M = Wt * W + sigma2 * Id + computeInvM(); + // m_tmp_fxf_1 = I = eye(n_features) + bob::math::eye(m_tmp_fxf_1); + // m_tmp_fxd_1 = W * inv(M) + bob::math::prod(W, m_invM, m_tmp_fxd_1); + // m_tmp_fxf_2 = (W * inv(M) * Wt) + bob::math::prod(m_tmp_fxd_1, Wt, m_tmp_fxf_2); + // m_tmp_fxd_1 = inv(C) = (I - W.M^-1.W^T) / sigma2 + m_tmp_fxf_1 -= m_tmp_fxf_2; + m_tmp_fxf_1 /= m_sigma2; + + // 3/ Compute inv(C).S + bob::math::prod(m_tmp_fxf_1, m_S, m_tmp_fxf_2); + + // 4/ Use previous values to compute the log likelihood: + // Log likelihood = - N/2*{ d*ln(2*PI) + ln |detC| + tr(C^-1.S) } + double llh = - static_cast<double>(m_z_first_order.extent(0)) / 2. * + ( m_f_log2pi + log(fabs(detC)) + bob::math::trace(m_tmp_fxf_2) ); + + return llh; +} diff --git a/bob/learn/misc/cpp/GMMMachine.cpp b/bob/learn/misc/cpp/GMMMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..727e690d62c76357d03f9bf663afbcd064aabbbf --- /dev/null +++ b/bob/learn/misc/cpp/GMMMachine.cpp @@ -0,0 +1,456 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/GMMMachine.h> +#include <bob.core/assert.h> +#include <bob.math/log.h> + +bob::machine::GMMMachine::GMMMachine(): m_gaussians(0) { + resize(0,0); +} + +bob::machine::GMMMachine::GMMMachine(const size_t n_gaussians, const size_t n_inputs): + m_gaussians(0) +{ + resize(n_gaussians,n_inputs); +} + +bob::machine::GMMMachine::GMMMachine(bob::io::base::HDF5File& config): + m_gaussians(0) +{ + load(config); +} + +bob::machine::GMMMachine::GMMMachine(const GMMMachine& other): + Machine<blitz::Array<double,1>, double>(other), m_gaussians(0) +{ + copy(other); +} + +bob::machine::GMMMachine& bob::machine::GMMMachine::operator=(const bob::machine::GMMMachine &other) { + // protect against invalid self-assignment + if (this != &other) + copy(other); + + // by convention, always return *this + return *this; +} + +bool bob::machine::GMMMachine::operator==(const bob::machine::GMMMachine& b) const +{ + if (m_n_gaussians != b.m_n_gaussians || m_n_inputs != b.m_n_inputs || + !bob::core::array::isEqual(m_weights, b.m_weights)) + return false; + + for(size_t i=0; i<m_n_gaussians; ++i) { + if(!(*(m_gaussians[i]) == *(b.m_gaussians[i]))) + return false; + } + + return true; +} + +bool bob::machine::GMMMachine::operator!=(const bob::machine::GMMMachine& b) const { + return !(this->operator==(b)); +} + +bool bob::machine::GMMMachine::is_similar_to(const bob::machine::GMMMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + if (m_n_gaussians != b.m_n_gaussians || m_n_inputs != b.m_n_inputs || + !bob::core::array::isClose(m_weights, b.m_weights, r_epsilon, a_epsilon)) + return false; + + for (size_t i = 0; i < m_n_gaussians; ++i) + if (!m_gaussians[i]->is_similar_to(*b.m_gaussians[i], r_epsilon, a_epsilon)) + return false; + + return true; +} + +void bob::machine::GMMMachine::copy(const GMMMachine& other) { + m_n_gaussians = other.m_n_gaussians; + m_n_inputs = other.m_n_inputs; + + // Initialise weights + m_weights.resize(m_n_gaussians); + m_weights = other.m_weights; + + // Initialise Gaussians + m_gaussians.clear(); + for(size_t i=0; i<m_n_gaussians; ++i) { + boost::shared_ptr<bob::machine::Gaussian> g(new bob::machine::Gaussian(*(other.m_gaussians[i]))); + m_gaussians.push_back(g); + } + + // Initialise cache + initCache(); +} + +bob::machine::GMMMachine::~GMMMachine() { } + +void bob::machine::GMMMachine::setNInputs(const size_t n_inputs) { + resize(m_n_gaussians,n_inputs); +} + +void bob::machine::GMMMachine::resize(const size_t n_gaussians, const size_t n_inputs) { + m_n_gaussians = n_gaussians; + m_n_inputs = n_inputs; + + // Initialise weights + m_weights.resize(m_n_gaussians); + m_weights = 1.0 / m_n_gaussians; + + // Initialise Gaussians + m_gaussians.clear(); + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians.push_back(boost::shared_ptr<bob::machine::Gaussian>(new bob::machine::Gaussian(n_inputs))); + + // Initialise cache arrays + initCache(); +} + + +void bob::machine::GMMMachine::setWeights(const blitz::Array<double,1> &weights) { + bob::core::array::assertSameShape(weights, m_weights); + m_weights = weights; + recomputeLogWeights(); +} + +void bob::machine::GMMMachine::recomputeLogWeights() const +{ + m_cache_log_weights = blitz::log(m_weights); +} + +void bob::machine::GMMMachine::setMeans(const blitz::Array<double,2> &means) { + bob::core::array::assertSameDimensionLength(means.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(means.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians[i]->updateMean() = means(i,blitz::Range::all()); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::getMeans(blitz::Array<double,2> &means) const { + bob::core::array::assertSameDimensionLength(means.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(means.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + means(i,blitz::Range::all()) = m_gaussians[i]->getMean(); +} + +void bob::machine::GMMMachine::setMeanSupervector(const blitz::Array<double,1> &mean_supervector) { + bob::core::array::assertSameDimensionLength(mean_supervector.extent(0), m_n_gaussians*m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians[i]->updateMean() = mean_supervector(blitz::Range(i*m_n_inputs, (i+1)*m_n_inputs-1)); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::getMeanSupervector(blitz::Array<double,1> &mean_supervector) const { + bob::core::array::assertSameDimensionLength(mean_supervector.extent(0), m_n_gaussians*m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + mean_supervector(blitz::Range(i*m_n_inputs, (i+1)*m_n_inputs-1)) = m_gaussians[i]->getMean(); +} + +void bob::machine::GMMMachine::setVariances(const blitz::Array<double, 2 >& variances) { + bob::core::array::assertSameDimensionLength(variances.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(variances.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) { + m_gaussians[i]->updateVariance() = variances(i,blitz::Range::all()); + m_gaussians[i]->applyVarianceThresholds(); + } + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::getVariances(blitz::Array<double, 2 >& variances) const { + bob::core::array::assertSameDimensionLength(variances.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(variances.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + variances(i,blitz::Range::all()) = m_gaussians[i]->getVariance(); +} + +void bob::machine::GMMMachine::setVarianceSupervector(const blitz::Array<double,1> &variance_supervector) { + bob::core::array::assertSameDimensionLength(variance_supervector.extent(0), m_n_gaussians*m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) { + m_gaussians[i]->updateVariance() = variance_supervector(blitz::Range(i*m_n_inputs, (i+1)*m_n_inputs-1)); + m_gaussians[i]->applyVarianceThresholds(); + } + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::getVarianceSupervector(blitz::Array<double,1> &variance_supervector) const { + bob::core::array::assertSameDimensionLength(variance_supervector.extent(0), m_n_gaussians*m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) { + variance_supervector(blitz::Range(i*m_n_inputs, (i+1)*m_n_inputs-1)) = m_gaussians[i]->getVariance(); + } +} + +void bob::machine::GMMMachine::setVarianceThresholds(const double value) { + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians[i]->setVarianceThresholds(value); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::setVarianceThresholds(blitz::Array<double, 1> variance_thresholds) { + bob::core::array::assertSameDimensionLength(variance_thresholds.extent(0), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians[i]->setVarianceThresholds(variance_thresholds); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::setVarianceThresholds(const blitz::Array<double, 2>& variance_thresholds) { + bob::core::array::assertSameDimensionLength(variance_thresholds.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(variance_thresholds.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + m_gaussians[i]->setVarianceThresholds(variance_thresholds(i,blitz::Range::all())); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::getVarianceThresholds(blitz::Array<double, 2>& variance_thresholds) const { + bob::core::array::assertSameDimensionLength(variance_thresholds.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(variance_thresholds.extent(1), m_n_inputs); + for(size_t i=0; i<m_n_gaussians; ++i) + variance_thresholds(i,blitz::Range::all()) = m_gaussians[i]->getVarianceThresholds(); +} + +double bob::machine::GMMMachine::logLikelihood(const blitz::Array<double, 1> &x, + blitz::Array<double,1> &log_weighted_gaussian_likelihoods) const +{ + // Check dimension + bob::core::array::assertSameDimensionLength(log_weighted_gaussian_likelihoods.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(x.extent(0), m_n_inputs); + return logLikelihood_(x,log_weighted_gaussian_likelihoods); +} + +double bob::machine::GMMMachine::logLikelihood_(const blitz::Array<double, 1> &x, + blitz::Array<double,1> &log_weighted_gaussian_likelihoods) const +{ + // Initialise variables + double log_likelihood = bob::math::Log::LogZero; + + // Accumulate the weighted log likelihoods from each Gaussian + for(size_t i=0; i<m_n_gaussians; ++i) { + double l = m_cache_log_weights(i) + m_gaussians[i]->logLikelihood_(x); + log_weighted_gaussian_likelihoods(i) = l; + log_likelihood = bob::math::Log::logAdd(log_likelihood, l); + } + + // Return log(p(x|GMMMachine)) + return log_likelihood; +} + +double bob::machine::GMMMachine::logLikelihood(const blitz::Array<double, 1> &x) const { + // Check dimension + bob::core::array::assertSameDimensionLength(x.extent(0), m_n_inputs); + // Call the other logLikelihood_ (overloaded) function + // (log_weighted_gaussian_likelihoods will be discarded) + return logLikelihood_(x,m_cache_log_weighted_gaussian_likelihoods); +} + +double bob::machine::GMMMachine::logLikelihood_(const blitz::Array<double, 1> &x) const { + // Call the other logLikelihood (overloaded) function + // (log_weighted_gaussian_likelihoods will be discarded) + return logLikelihood_(x,m_cache_log_weighted_gaussian_likelihoods); +} + +void bob::machine::GMMMachine::forward(const blitz::Array<double,1>& input, double& output) const { + if(static_cast<size_t>(input.extent(0)) != m_n_inputs) { + boost::format m("expected input size (%u) does not match the size of input array (%d)"); + m % m_n_inputs % input.extent(0); + throw std::runtime_error(m.str()); + } + + forward_(input,output); +} + +void bob::machine::GMMMachine::forward_(const blitz::Array<double,1>& input, + double& output) const { + output = logLikelihood(input); +} + +void bob::machine::GMMMachine::accStatistics(const blitz::Array<double,2>& input, + bob::machine::GMMStats& stats) const { + // iterate over data + blitz::Range a = blitz::Range::all(); + for(int i=0; i<input.extent(0); ++i) { + // Get example + blitz::Array<double,1> x(input(i,a)); + // Accumulate statistics + accStatistics(x,stats); + } +} + +void bob::machine::GMMMachine::accStatistics_(const blitz::Array<double,2>& input, bob::machine::GMMStats& stats) const { + // iterate over data + blitz::Range a = blitz::Range::all(); + for(int i=0; i<input.extent(0); ++i) { + // Get example + blitz::Array<double,1> x(input(i, a)); + // Accumulate statistics + accStatistics_(x,stats); + } +} + +void bob::machine::GMMMachine::accStatistics(const blitz::Array<double, 1>& x, bob::machine::GMMStats& stats) const { + // check GMMStats size + bob::core::array::assertSameDimensionLength(stats.sumPx.extent(0), m_n_gaussians); + bob::core::array::assertSameDimensionLength(stats.sumPx.extent(1), m_n_inputs); + + // Calculate Gaussian and GMM likelihoods + // - m_cache_log_weighted_gaussian_likelihoods(i) = log(weight_i*p(x|gaussian_i)) + // - log_likelihood = log(sum_i(weight_i*p(x|gaussian_i))) + double log_likelihood = logLikelihood(x, m_cache_log_weighted_gaussian_likelihoods); + + accStatisticsInternal(x, stats, log_likelihood); +} + +void bob::machine::GMMMachine::accStatistics_(const blitz::Array<double, 1>& x, bob::machine::GMMStats& stats) const { + // Calculate Gaussian and GMM likelihoods + // - m_cache_log_weighted_gaussian_likelihoods(i) = log(weight_i*p(x|gaussian_i)) + // - log_likelihood = log(sum_i(weight_i*p(x|gaussian_i))) + double log_likelihood = logLikelihood_(x, m_cache_log_weighted_gaussian_likelihoods); + + accStatisticsInternal(x, stats, log_likelihood); +} + +void bob::machine::GMMMachine::accStatisticsInternal(const blitz::Array<double, 1>& x, + bob::machine::GMMStats& stats, const double log_likelihood) const +{ + // Calculate responsibilities + m_cache_P = blitz::exp(m_cache_log_weighted_gaussian_likelihoods - log_likelihood); + + // Accumulate statistics + // - total likelihood + stats.log_likelihood += log_likelihood; + + // - number of samples + stats.T++; + + // - responsibilities + stats.n += m_cache_P; + + // - first order stats + blitz::firstIndex i; + blitz::secondIndex j; + + m_cache_Px = m_cache_P(i) * x(j); + + stats.sumPx += m_cache_Px; + + // - second order stats + stats.sumPxx += (m_cache_Px(i,j) * x(j)); +} + +boost::shared_ptr<const bob::machine::Gaussian> bob::machine::GMMMachine::getGaussian(const size_t i) const { + if (i>=m_n_gaussians) { + throw std::runtime_error("getGaussian(): index out of bounds"); + } + boost::shared_ptr<const bob::machine::Gaussian> res = m_gaussians[i]; + return res; +} + +boost::shared_ptr<bob::machine::Gaussian> bob::machine::GMMMachine::updateGaussian(const size_t i) { + if (i>=m_n_gaussians) { + throw std::runtime_error("updateGaussian(): index out of bounds"); + } + return m_gaussians[i]; +} + +void bob::machine::GMMMachine::save(bob::io::base::HDF5File& config) const { + int64_t v = static_cast<int64_t>(m_n_gaussians); + config.set("m_n_gaussians", v); + v = static_cast<int64_t>(m_n_inputs); + config.set("m_n_inputs", v); + + for(size_t i=0; i<m_n_gaussians; ++i) { + std::ostringstream oss; + oss << "m_gaussians" << i; + + if (!config.hasGroup(oss.str())) config.createGroup(oss.str()); + config.cd(oss.str()); + m_gaussians[i]->save(config); + config.cd(".."); + } + + config.setArray("m_weights", m_weights); +} + +void bob::machine::GMMMachine::load(bob::io::base::HDF5File& config) { + int64_t v; + v = config.read<int64_t>("m_n_gaussians"); + m_n_gaussians = static_cast<size_t>(v); + v = config.read<int64_t>("m_n_inputs"); + m_n_inputs = static_cast<size_t>(v); + + m_gaussians.clear(); + for(size_t i=0; i<m_n_gaussians; ++i) { + m_gaussians.push_back(boost::shared_ptr<bob::machine::Gaussian>(new bob::machine::Gaussian(m_n_inputs))); + std::ostringstream oss; + oss << "m_gaussians" << i; + config.cd(oss.str()); + m_gaussians[i]->load(config); + config.cd(".."); + } + + m_weights.resize(m_n_gaussians); + config.readArray("m_weights", m_weights); + + // Initialise cache + initCache(); +} + +void bob::machine::GMMMachine::updateCacheSupervectors() const +{ + m_cache_mean_supervector.resize(m_n_gaussians*m_n_inputs); + m_cache_variance_supervector.resize(m_n_gaussians*m_n_inputs); + + for(size_t i=0; i<m_n_gaussians; ++i) { + blitz::Range range(i*m_n_inputs, (i+1)*m_n_inputs-1); + m_cache_mean_supervector(range) = m_gaussians[i]->getMean(); + m_cache_variance_supervector(range) = m_gaussians[i]->getVariance(); + } + m_cache_supervector = true; +} + +void bob::machine::GMMMachine::initCache() const { + // Initialise cache arrays + m_cache_log_weights.resize(m_n_gaussians); + recomputeLogWeights(); + m_cache_log_weighted_gaussian_likelihoods.resize(m_n_gaussians); + m_cache_P.resize(m_n_gaussians); + m_cache_Px.resize(m_n_gaussians,m_n_inputs); + m_cache_supervector = false; +} + +void bob::machine::GMMMachine::reloadCacheSupervectors() const { + if(!m_cache_supervector) + updateCacheSupervectors(); +} + +const blitz::Array<double,1>& bob::machine::GMMMachine::getMeanSupervector() const { + if(!m_cache_supervector) + updateCacheSupervectors(); + return m_cache_mean_supervector; +} + +const blitz::Array<double,1>& bob::machine::GMMMachine::getVarianceSupervector() const { + if(!m_cache_supervector) + updateCacheSupervectors(); + return m_cache_variance_supervector; +} + +namespace bob { + namespace machine { + std::ostream& operator<<(std::ostream& os, const GMMMachine& machine) { + os << "Weights = " << machine.m_weights << std::endl; + for(size_t i=0; i < machine.m_n_gaussians; ++i) { + os << "Gaussian " << i << ": " << std::endl << *(machine.m_gaussians[i]); + } + + return os; + } + } +} diff --git a/bob/learn/misc/cpp/GMMStats.cpp b/bob/learn/misc/cpp/GMMStats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5695cea2fa0b907fe83c11255e0926f14eb02d66 --- /dev/null +++ b/bob/learn/misc/cpp/GMMStats.cpp @@ -0,0 +1,153 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/GMMStats.h> +#include <bob.core/logging.h> +#include <bob.core/check.h> + +bob::machine::GMMStats::GMMStats() { + resize(0,0); +} + +bob::machine::GMMStats::GMMStats(const size_t n_gaussians, const size_t n_inputs) { + resize(n_gaussians,n_inputs); +} + +bob::machine::GMMStats::GMMStats(bob::io::base::HDF5File& config) { + load(config); +} + +bob::machine::GMMStats::GMMStats(const bob::machine::GMMStats& other) { + copy(other); +} + +bob::machine::GMMStats::~GMMStats() { +} + +bob::machine::GMMStats& +bob::machine::GMMStats::operator=(const bob::machine::GMMStats& other) { + // protect against invalid self-assignment + if (this != &other) + copy(other); + + // by convention, always return *this + return *this; +} + +bool bob::machine::GMMStats::operator==(const bob::machine::GMMStats& b) const +{ + return (T == b.T && log_likelihood == b.log_likelihood && + bob::core::array::isEqual(n, b.n) && + bob::core::array::isEqual(sumPx, b.sumPx) && + bob::core::array::isEqual(sumPxx, b.sumPxx)); +} + +bool +bob::machine::GMMStats::operator!=(const bob::machine::GMMStats& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::GMMStats::is_similar_to(const bob::machine::GMMStats& b, + const double r_epsilon, const double a_epsilon) const +{ + return (T == b.T && + bob::core::isClose(log_likelihood, b.log_likelihood, r_epsilon, a_epsilon) && + bob::core::array::isClose(n, b.n, r_epsilon, a_epsilon) && + bob::core::array::isClose(sumPx, b.sumPx, r_epsilon, a_epsilon) && + bob::core::array::isClose(sumPxx, b.sumPxx, r_epsilon, a_epsilon)); +} + + +void bob::machine::GMMStats::operator+=(const bob::machine::GMMStats& b) { + // Check dimensions + if(n.extent(0) != b.n.extent(0) || + sumPx.extent(0) != b.sumPx.extent(0) || sumPx.extent(1) != b.sumPx.extent(1) || + sumPxx.extent(0) != b.sumPxx.extent(0) || sumPxx.extent(1) != b.sumPxx.extent(1)) + // TODO: add a specialized exception + throw std::runtime_error("if you see this exception, fill a bug report"); + + // Update GMMStats object with the content of the other one + T += b.T; + log_likelihood += b.log_likelihood; + n += b.n; + sumPx += b.sumPx; + sumPxx += b.sumPxx; +} + +void bob::machine::GMMStats::copy(const GMMStats& other) { + // Resize arrays + resize(other.sumPx.extent(0),other.sumPx.extent(1)); + // Copy content + T = other.T; + log_likelihood = other.log_likelihood; + n = other.n; + sumPx = other.sumPx; + sumPxx = other.sumPxx; +} + +void bob::machine::GMMStats::resize(const size_t n_gaussians, const size_t n_inputs) { + n.resize(n_gaussians); + sumPx.resize(n_gaussians, n_inputs); + sumPxx.resize(n_gaussians, n_inputs); + init(); +} + +void bob::machine::GMMStats::init() { + log_likelihood = 0; + T = 0; + n = 0.0; + sumPx = 0.0; + sumPxx = 0.0; +} + +void bob::machine::GMMStats::save(bob::io::base::HDF5File& config) const { + //please note we fix the output values to be of a precise type so they can be + //retrieved at any platform with the exact same precision. + // TODO: add versioning, replace int64_t by uint64_t and log_liklihood by log_likelihood + int64_t sumpx_shape_0 = sumPx.shape()[0]; + int64_t sumpx_shape_1 = sumPx.shape()[1]; + config.set("n_gaussians", sumpx_shape_0); + config.set("n_inputs", sumpx_shape_1); + config.set("log_liklihood", log_likelihood); //double + config.set("T", static_cast<int64_t>(T)); + config.setArray("n", n); //Array1d + config.setArray("sumPx", sumPx); //Array2d + config.setArray("sumPxx", sumPxx); //Array2d +} + +void bob::machine::GMMStats::load(bob::io::base::HDF5File& config) { + log_likelihood = config.read<double>("log_liklihood"); + int64_t n_gaussians = config.read<int64_t>("n_gaussians"); + int64_t n_inputs = config.read<int64_t>("n_inputs"); + T = static_cast<size_t>(config.read<int64_t>("T")); + + //resize arrays to prepare for HDF5 readout + n.resize(n_gaussians); + sumPx.resize(n_gaussians, n_inputs); + sumPxx.resize(n_gaussians, n_inputs); + + //load data + config.readArray("n", n); + config.readArray("sumPx", sumPx); + config.readArray("sumPxx", sumPxx); +} + +namespace bob { + namespace machine { + std::ostream& operator<<(std::ostream& os, const GMMStats& g) { + os << "log_likelihood = " << g.log_likelihood << std::endl; + os << "T = " << g.T << std::endl; + os << "n = " << g.n; + os << "sumPx = " << g.sumPx; + os << "sumPxx = " << g.sumPxx; + + return os; + } + } +} diff --git a/bob/learn/misc/cpp/GMMTrainer.cpp b/bob/learn/misc/cpp/GMMTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..818dc658ef19d2347a0561d2b6fa24c4159331a7 --- /dev/null +++ b/bob/learn/misc/cpp/GMMTrainer.cpp @@ -0,0 +1,111 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/GMMTrainer.h> +#include <bob.core/assert.h> +#include <bob.core/check.h> + +bob::trainer::GMMTrainer::GMMTrainer(const bool update_means, + const bool update_variances, const bool update_weights, + const double mean_var_update_responsibilities_threshold): + bob::trainer::EMTrainer<bob::machine::GMMMachine, blitz::Array<double,2> >(), + m_update_means(update_means), m_update_variances(update_variances), + m_update_weights(update_weights), + m_mean_var_update_responsibilities_threshold(mean_var_update_responsibilities_threshold) +{ +} + +bob::trainer::GMMTrainer::GMMTrainer(const bob::trainer::GMMTrainer& b): + bob::trainer::EMTrainer<bob::machine::GMMMachine, blitz::Array<double,2> >(b), + m_update_means(b.m_update_means), m_update_variances(b.m_update_variances), + m_mean_var_update_responsibilities_threshold(b.m_mean_var_update_responsibilities_threshold) +{ +} + +bob::trainer::GMMTrainer::~GMMTrainer() +{ +} + +void bob::trainer::GMMTrainer::initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + // Allocate memory for the sufficient statistics and initialise + m_ss.resize(gmm.getNGaussians(),gmm.getNInputs()); +} + +void bob::trainer::GMMTrainer::eStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + m_ss.init(); + // Calculate the sufficient statistics and save in m_ss + gmm.accStatistics(data, m_ss); +} + +double bob::trainer::GMMTrainer::computeLikelihood(bob::machine::GMMMachine& gmm) +{ + return m_ss.log_likelihood / m_ss.T; +} + +void bob::trainer::GMMTrainer::finalize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ +} + +bob::trainer::GMMTrainer& bob::trainer::GMMTrainer::operator= + (const bob::trainer::GMMTrainer &other) +{ + if (this != &other) + { + bob::trainer::EMTrainer<bob::machine::GMMMachine, + blitz::Array<double,2> >::operator=(other); + m_ss = other.m_ss; + m_update_means = other.m_update_means; + m_update_variances = other.m_update_variances; + m_update_weights = other.m_update_weights; + m_mean_var_update_responsibilities_threshold = other.m_mean_var_update_responsibilities_threshold; + } + return *this; +} + +bool bob::trainer::GMMTrainer::operator== + (const bob::trainer::GMMTrainer &other) const +{ + return bob::trainer::EMTrainer<bob::machine::GMMMachine, + blitz::Array<double,2> >::operator==(other) && + m_ss == other.m_ss && + m_update_means == other.m_update_means && + m_update_variances == other.m_update_variances && + m_update_weights == other.m_update_weights && + m_mean_var_update_responsibilities_threshold == other.m_mean_var_update_responsibilities_threshold; +} + +bool bob::trainer::GMMTrainer::operator!= + (const bob::trainer::GMMTrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::GMMTrainer::is_similar_to + (const bob::trainer::GMMTrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::EMTrainer<bob::machine::GMMMachine, + blitz::Array<double,2> >::operator==(other) && + // TODO: use is similar to method for the accumulator m_ss + m_ss == other.m_ss && + m_update_means == other.m_update_means && + m_update_variances == other.m_update_variances && + m_update_weights == other.m_update_weights && + bob::core::isClose(m_mean_var_update_responsibilities_threshold, + other.m_mean_var_update_responsibilities_threshold, r_epsilon, a_epsilon); +} + +void bob::trainer::GMMTrainer::setGMMStats(const bob::machine::GMMStats& stats) +{ + bob::core::array::assertSameShape(m_ss.sumPx, stats.sumPx); + m_ss = stats; +} diff --git a/bob/learn/misc/cpp/Gaussian.cpp b/bob/learn/misc/cpp/Gaussian.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7169c5f747596e54a4e76bb9e0f2d50730a2d3ab --- /dev/null +++ b/bob/learn/misc/cpp/Gaussian.cpp @@ -0,0 +1,186 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/Gaussian.h> + +#include <bob.core/assert.h> +#include <bob.math/log.h> + +bob::machine::Gaussian::Gaussian() { + resize(0); +} + +bob::machine::Gaussian::Gaussian(const size_t n_inputs) { + resize(n_inputs); +} + +bob::machine::Gaussian::Gaussian(const bob::machine::Gaussian& other) { + copy(other); +} + +bob::machine::Gaussian::Gaussian(bob::io::base::HDF5File& config) { + load(config); +} + +bob::machine::Gaussian::~Gaussian() { +} + +bob::machine::Gaussian& bob::machine::Gaussian::operator=(const bob::machine::Gaussian &other) { + if(this != &other) + copy(other); + + return *this; +} + +bool bob::machine::Gaussian::operator==(const bob::machine::Gaussian& b) const +{ + return (bob::core::array::isEqual(m_mean, b.m_mean) && + bob::core::array::isEqual(m_variance, b.m_variance) && + bob::core::array::isEqual(m_variance_thresholds, b.m_variance_thresholds)); +} + +bool bob::machine::Gaussian::operator!=(const bob::machine::Gaussian& b) const { + return !(this->operator==(b)); +} + +bool bob::machine::Gaussian::is_similar_to(const bob::machine::Gaussian& b, + const double r_epsilon, const double a_epsilon) const +{ + return (bob::core::array::isClose(m_mean, b.m_mean, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_variance, b.m_variance, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_variance_thresholds, b.m_variance_thresholds, r_epsilon, a_epsilon)); +} + +void bob::machine::Gaussian::copy(const bob::machine::Gaussian& other) { + m_n_inputs = other.m_n_inputs; + + m_mean.resize(m_n_inputs); + m_mean = other.m_mean; + + m_variance.resize(m_n_inputs); + m_variance = other.m_variance; + + m_variance_thresholds.resize(m_n_inputs); + m_variance_thresholds = other.m_variance_thresholds; + + m_n_log2pi = other.m_n_log2pi; + m_g_norm = other.m_g_norm; +} + + +void bob::machine::Gaussian::setNInputs(const size_t n_inputs) { + resize(n_inputs); +} + +void bob::machine::Gaussian::resize(const size_t n_inputs) { + m_n_inputs = n_inputs; + m_mean.resize(m_n_inputs); + m_mean = 0; + m_variance.resize(m_n_inputs); + m_variance = 1; + m_variance_thresholds.resize(m_n_inputs); + m_variance_thresholds = 0; + + // Re-compute g_norm, because m_n_inputs and m_variance + // have changed + preComputeNLog2Pi(); + preComputeConstants(); +} + +void bob::machine::Gaussian::setMean(const blitz::Array<double,1> &mean) { + // Check and set + bob::core::array::assertSameShape(m_mean, mean); + m_mean = mean; +} + +void bob::machine::Gaussian::setVariance(const blitz::Array<double,1> &variance) { + // Check and set + bob::core::array::assertSameShape(m_variance, variance); + m_variance = variance; + + // Variance flooring + applyVarianceThresholds(); +} + +void bob::machine::Gaussian::setVarianceThresholds(const blitz::Array<double,1> &variance_thresholds) { + // Check and set + bob::core::array::assertSameShape(m_variance_thresholds, variance_thresholds); + m_variance_thresholds = variance_thresholds; + + // Variance flooring + applyVarianceThresholds(); +} + +void bob::machine::Gaussian::setVarianceThresholds(const double value) { + blitz::Array<double,1> variance_thresholds(m_n_inputs); + variance_thresholds = value; + setVarianceThresholds(variance_thresholds); +} + +void bob::machine::Gaussian::applyVarianceThresholds() { + // Apply variance flooring threshold + m_variance = blitz::where( m_variance < m_variance_thresholds, m_variance_thresholds, m_variance); + + // Re-compute g_norm, because m_variance has changed + preComputeConstants(); +} + +double bob::machine::Gaussian::logLikelihood(const blitz::Array<double,1> &x) const { + // Check + bob::core::array::assertSameShape(x, m_mean); + return logLikelihood_(x); +} + +double bob::machine::Gaussian::logLikelihood_(const blitz::Array<double,1> &x) const { + double z = blitz::sum(blitz::pow2(x - m_mean) / m_variance); + // Log Likelihood + return (-0.5 * (m_g_norm + z)); +} + +void bob::machine::Gaussian::preComputeNLog2Pi() { + m_n_log2pi = m_n_inputs * bob::math::Log::Log2Pi; +} + +void bob::machine::Gaussian::preComputeConstants() { + m_g_norm = m_n_log2pi + blitz::sum(blitz::log(m_variance)); +} + +void bob::machine::Gaussian::save(bob::io::base::HDF5File& config) const { + config.setArray("m_mean", m_mean); + config.setArray("m_variance", m_variance); + config.setArray("m_variance_thresholds", m_variance_thresholds); + config.set("g_norm", m_g_norm); + int64_t v = static_cast<int64_t>(m_n_inputs); + config.set("m_n_inputs", v); +} + +void bob::machine::Gaussian::load(bob::io::base::HDF5File& config) { + int64_t v = config.read<int64_t>("m_n_inputs"); + m_n_inputs = static_cast<size_t>(v); + + m_mean.resize(m_n_inputs); + m_variance.resize(m_n_inputs); + m_variance_thresholds.resize(m_n_inputs); + + config.readArray("m_mean", m_mean); + config.readArray("m_variance", m_variance); + config.readArray("m_variance_thresholds", m_variance_thresholds); + + preComputeNLog2Pi(); + m_g_norm = config.read<double>("g_norm"); +} + +namespace bob{ + namespace machine{ + std::ostream& operator<<(std::ostream& os, const Gaussian& g) { + os << "Mean = " << g.m_mean << std::endl; + os << "Variance = " << g.m_variance << std::endl; + return os; + } + } +} diff --git a/bob/learn/misc/cpp/IVectorMachine.cpp b/bob/learn/misc/cpp/IVectorMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c022a80228400b439053835950e70462cdd6bfd6 --- /dev/null +++ b/bob/learn/misc/cpp/IVectorMachine.cpp @@ -0,0 +1,248 @@ +/** + * @date Sat Mar 30 21:00:00 2013 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/IVectorMachine.h> +#include <bob.core/array_copy.h> +#include <bob.core/check.h> +#include <bob.math/linear.h> +#include <bob.math/linsolve.h> + +bob::machine::IVectorMachine::IVectorMachine() +{ +} + +bob::machine::IVectorMachine::IVectorMachine(const boost::shared_ptr<bob::machine::GMMMachine> ubm, + const size_t rt, const double variance_threshold): + m_ubm(ubm), m_rt(rt), + m_T(getDimCD(),rt), m_sigma(getDimCD()), + m_variance_threshold(variance_threshold) +{ + resizePrecompute(); +} + +bob::machine::IVectorMachine::IVectorMachine(const bob::machine::IVectorMachine& other): + m_ubm(other.m_ubm), m_rt(other.m_rt), + m_T(bob::core::array::ccopy(other.m_T)), + m_sigma(bob::core::array::ccopy(other.m_sigma)), + m_variance_threshold(other.m_variance_threshold) +{ + resizePrecompute(); +} + +bob::machine::IVectorMachine::IVectorMachine(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::IVectorMachine::~IVectorMachine() { +} + +void bob::machine::IVectorMachine::save(bob::io::base::HDF5File& config) const +{ + config.setArray("m_T", m_T); + config.setArray("m_sigma", m_sigma); + config.set("m_variance_threshold", m_variance_threshold); +} + +void bob::machine::IVectorMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + m_T.reference(config.readArray<double,2>("m_T")); + m_rt = m_T.extent(1); + m_sigma.reference(config.readArray<double,1>("m_sigma")); + m_variance_threshold = config.read<double>("m_variance_threshold"); + resizePrecompute(); +} + +void bob::machine::IVectorMachine::resize(const size_t rt) +{ + m_rt = rt; + m_T.resizeAndPreserve(m_T.extent(0), rt); + resizePrecompute(); +} + +bob::machine::IVectorMachine& +bob::machine::IVectorMachine::operator=(const bob::machine::IVectorMachine& other) +{ + if (this != &other) + { + m_ubm = other.m_ubm; + m_rt = other.m_rt; + m_T.reference(bob::core::array::ccopy(other.m_T)); + m_sigma.reference(bob::core::array::ccopy(other.m_sigma)); + m_variance_threshold = other.m_variance_threshold; + resizePrecompute(); + } + return *this; +} + +bool bob::machine::IVectorMachine::operator==(const IVectorMachine& b) const +{ + return (((m_ubm && b.m_ubm) && *m_ubm == *(b.m_ubm)) || (!m_ubm && !b.m_ubm)) && + m_rt == b.m_rt && + bob::core::array::isEqual(m_T, b.m_T) && + bob::core::array::isEqual(m_sigma, b.m_sigma) && + m_variance_threshold == b.m_variance_threshold; +} + +bool bob::machine::IVectorMachine::operator!=(const bob::machine::IVectorMachine& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::IVectorMachine::is_similar_to(const IVectorMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + // TODO: update with new is_similar_to method + return (((m_ubm && b.m_ubm) && m_ubm->is_similar_to(*(b.m_ubm), r_epsilon)) || (!m_ubm && !b.m_ubm)) && + m_rt == b.m_rt && + bob::core::array::isClose(m_T, b.m_T, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_sigma, b.m_sigma, r_epsilon, a_epsilon) && + bob::core::isClose(m_variance_threshold, b.m_variance_threshold, r_epsilon, a_epsilon); +} + +void bob::machine::IVectorMachine::setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm) +{ + m_ubm = ubm; + resizePrecompute(); +} + +void bob::machine::IVectorMachine::setT(const blitz::Array<double,2>& T) +{ + bob::core::array::assertSameShape(m_T, T); + m_T = T; + // Update cache + precompute(); +} + +void bob::machine::IVectorMachine::setSigma(const blitz::Array<double,1>& sigma) +{ + bob::core::array::assertSameShape(m_sigma, sigma); + m_sigma = sigma; + // Update cache + precompute(); +} + + +void bob::machine::IVectorMachine::setVarianceThreshold(const double thd) +{ + m_variance_threshold = thd; + // Update cache + precompute(); +} + +void bob::machine::IVectorMachine::applyVarianceThreshold() +{ + // Apply variance flooring threshold + m_sigma = blitz::where(m_sigma < m_variance_threshold, m_variance_threshold, m_sigma); +} + +void bob::machine::IVectorMachine::precompute() +{ + if (m_ubm) + { + // Apply variance threshold + applyVarianceThreshold(); + + blitz::firstIndex i; + blitz::secondIndex j; + blitz::Range rall = blitz::Range::all(); + const int C = (int)m_ubm->getNGaussians(); + const int D = (int)m_ubm->getNInputs(); + // T_{c}^{T}.sigma_{c}^{-1} + for (int c=0; c<C; ++c) + { + blitz::Array<double,2> Tct_sigmacInv = m_cache_Tct_sigmacInv(c, rall, rall); + blitz::Array<double,2> Tc = m_T(blitz::Range(c*D,(c+1)*D-1), rall); + blitz::Array<double,2> Tct = Tc.transpose(1,0); + blitz::Array<double,1> sigma_c = m_sigma(blitz::Range(c*D,(c+1)*D-1)); + Tct_sigmacInv = Tct(i,j) / sigma_c(j); + } + + // T_{c}^{T}.sigma_{c}^{-1}.T_{c} + for (int c=0; c<C; ++c) + { + blitz::Array<double,2> Tc = m_T(blitz::Range(c*D,(c+1)*D-1), rall); + blitz::Array<double,2> Tct_sigmacInv = m_cache_Tct_sigmacInv(c, rall, rall); + blitz::Array<double,2> Tct_sigmacInv_Tc = m_cache_Tct_sigmacInv_Tc(c, rall, rall); + bob::math::prod(Tct_sigmacInv, Tc, Tct_sigmacInv_Tc); + } + } +} + +void bob::machine::IVectorMachine::resizePrecompute() +{ + resizeCache(); + resizeTmp(); + precompute(); +} + +void bob::machine::IVectorMachine::resizeCache() +{ + if (m_ubm) + { + const int C = (int)m_ubm->getNGaussians(); + const int D = (int)m_ubm->getNInputs(); + m_cache_Tct_sigmacInv.resize(C, (int)m_rt, D); + m_cache_Tct_sigmacInv_Tc.resize(C, (int)m_rt, (int)m_rt); + } +} + +void bob::machine::IVectorMachine::resizeTmp() +{ + if (m_ubm) + m_tmp_d.resize(m_ubm->getNInputs()); + m_tmp_t1.resize(m_rt); + m_tmp_t2.resize(m_rt); + m_tmp_tt.resize(m_rt, m_rt); +} + +void bob::machine::IVectorMachine::forward(const bob::machine::GMMStats& gs, + blitz::Array<double,1>& ivector) const +{ + bob::core::array::assertSameDimensionLength(ivector.extent(0), (int)m_rt); + forward_(gs, ivector); +} + +void bob::machine::IVectorMachine::computeIdTtSigmaInvT( + const bob::machine::GMMStats& gs, blitz::Array<double,2>& output) const +{ + // Computes \f$(Id + \sum_{c=1}^{C} N_{i,j,c} T^{T} \Sigma_{c}^{-1} T)\f$ + blitz::Range rall = blitz::Range::all(); + bob::math::eye(output); + for (int c=0; c<(int)getDimC(); ++c) + output += gs.n(c) * m_cache_Tct_sigmacInv_Tc(c, rall, rall); +} + +void bob::machine::IVectorMachine::computeTtSigmaInvFnorm( + const bob::machine::GMMStats& gs, blitz::Array<double,1>& output) const +{ + // Computes \f$T^{T} \Sigma^{-1} \sum_{c=1}^{C} (F_c - N_c ubmmean_{c})\f$ + blitz::Range rall = blitz::Range::all(); + output = 0; + for (int c=0; c<(int)getDimC(); ++c) + { + m_tmp_d = gs.sumPx(c,rall) - gs.n(c) * m_ubm->getGaussian(c)->getMean(); + blitz::Array<double,2> Tct_sigmacInv = m_cache_Tct_sigmacInv(c, rall, rall); + bob::math::prod(Tct_sigmacInv, m_tmp_d, m_tmp_t2); + output += m_tmp_t2; + } +} + +void bob::machine::IVectorMachine::forward_(const bob::machine::GMMStats& gs, + blitz::Array<double,1>& ivector) const +{ + // Computes \f$(Id + \sum_{c=1}^{C} N_{i,j,c} T^{T} \Sigma_{c}^{-1} T)\f$ + computeIdTtSigmaInvT(gs, m_tmp_tt); + + // Computes \f$T^{T} \Sigma^{-1} \sum_{c=1}^{C} (F_c - N_c ubmmean_{c})\f$ + computeTtSigmaInvFnorm(gs, m_tmp_t1); + + // Solves m_tmp_tt.ivector = m_tmp_t1 + bob::math::linsolve(m_tmp_tt, ivector, m_tmp_t1); +} + diff --git a/bob/learn/misc/cpp/IVectorTrainer.cpp b/bob/learn/misc/cpp/IVectorTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..538f58775c3adca5f96f8562e8d5a9c391f637f2 --- /dev/null +++ b/bob/learn/misc/cpp/IVectorTrainer.cpp @@ -0,0 +1,251 @@ +/** + * @date Sun Mar 31 20:15:00 2013 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + + +#include <bob.learn.misc/IVectorTrainer.h> +#include <bob.learn.misc/IVectorMachine.h> +#include <bob.core/array_copy.h> +#include <bob.core/array_random.h> +#include <bob.core/check.h> +#include <bob.math/inv.h> +#include <bob.math/linear.h> +#include <bob.math/linsolve.h> +#include <boost/shared_ptr.hpp> +#include <boost/random.hpp> + +bob::trainer::IVectorTrainer::IVectorTrainer(const bool update_sigma, + const double convergence_threshold, + const size_t max_iterations, bool compute_likelihood): + bob::trainer::EMTrainer<bob::machine::IVectorMachine, + std::vector<bob::machine::GMMStats> >(convergence_threshold, + max_iterations, compute_likelihood), + m_update_sigma(update_sigma) +{ +} + +bob::trainer::IVectorTrainer::IVectorTrainer(const bob::trainer::IVectorTrainer& other): + bob::trainer::EMTrainer<bob::machine::IVectorMachine, + std::vector<bob::machine::GMMStats> >(other), + m_update_sigma(other.m_update_sigma) +{ + m_acc_Nij_wij2.reference(bob::core::array::ccopy(other.m_acc_Nij_wij2)); + m_acc_Fnormij_wij.reference(bob::core::array::ccopy(other.m_acc_Fnormij_wij)); + m_acc_Nij.reference(bob::core::array::ccopy(other.m_acc_Nij)); + m_acc_Snormij.reference(bob::core::array::ccopy(other.m_acc_Snormij)); + + m_tmp_wij.reference(bob::core::array::ccopy(other.m_tmp_wij)); + m_tmp_wij2.reference(bob::core::array::ccopy(other.m_tmp_wij2)); + m_tmp_d1.reference(bob::core::array::ccopy(other.m_tmp_d1)); + m_tmp_t1.reference(bob::core::array::ccopy(other.m_tmp_t1)); + m_tmp_dd1.reference(bob::core::array::ccopy(other.m_tmp_dd1)); + m_tmp_dt1.reference(bob::core::array::ccopy(other.m_tmp_dt1)); + m_tmp_tt1.reference(bob::core::array::ccopy(other.m_tmp_tt1)); + m_tmp_tt2.reference(bob::core::array::ccopy(other.m_tmp_tt2)); +} + +bob::trainer::IVectorTrainer::~IVectorTrainer() +{ +} + +void bob::trainer::IVectorTrainer::initialize( + bob::machine::IVectorMachine& machine, + const std::vector<bob::machine::GMMStats>& data) +{ + const int C = machine.getDimC(); + const int D = machine.getDimD(); + const int Rt = machine.getDimRt(); + + // Cache + m_acc_Nij_wij2.resize(C,Rt,Rt); + m_acc_Fnormij_wij.resize(C,D,Rt); + if (m_update_sigma) + { + m_acc_Nij.resize(C); + m_acc_Snormij.resize(C,D); + } + + // Tmp + m_tmp_wij.resize(Rt); + m_tmp_wij2.resize(Rt,Rt); + m_tmp_d1.resize(D); + m_tmp_t1.resize(Rt); + m_tmp_dt1.resize(D,Rt); + m_tmp_tt1.resize(Rt,Rt); + m_tmp_tt2.resize(Rt,Rt); + if (m_update_sigma) + m_tmp_dd1.resize(D,D); + + // Initializes \f$T\f$ and \f$\Sigma\f$ of the machine + blitz::Array<double,2>& T = machine.updateT(); + bob::core::array::randn(*m_rng, T); + blitz::Array<double,1>& sigma = machine.updateSigma(); + sigma = machine.getUbm()->getVarianceSupervector(); + machine.precompute(); +} + +void bob::trainer::IVectorTrainer::eStep( + bob::machine::IVectorMachine& machine, + const std::vector<bob::machine::GMMStats>& data) +{ + blitz::Range rall = blitz::Range::all(); + const int C = machine.getDimC(); + + // Reinitializes accumulators to 0 + m_acc_Nij_wij2 = 0.; + m_acc_Fnormij_wij = 0.; + if (m_update_sigma) + { + m_acc_Nij = 0.; + m_acc_Snormij = 0.; + } + for (std::vector<bob::machine::GMMStats>::const_iterator it = data.begin(); + it != data.end(); ++it) + { + // Computes E{wij} and E{wij.wij^{T}} + // a. Computes \f$T^{T} \Sigma^{-1} F_{norm}\f$ + machine.computeTtSigmaInvFnorm(*it, m_tmp_t1); + // b. Computes \f$Id + T^{T} \Sigma^{-1} T\f$ + machine.computeIdTtSigmaInvT(*it, m_tmp_tt1); + // c. Computes \f$(Id + T^{T} \Sigma^{-1} T)^{-1}\f$ + bob::math::inv(m_tmp_tt1, m_tmp_tt2); + // d. Computes \f$E{wij} = (Id + T^{T} \Sigma^{-1} T)^{-1} T^{T} \Sigma^{-1} F_{norm}\f$ + bob::math::prod(m_tmp_tt2, m_tmp_t1, m_tmp_wij); // E{wij} + // e. Computes \f$E{wij}.E{wij^{T}}\f$ + bob::math::prod(m_tmp_wij, m_tmp_wij, m_tmp_wij2); + // f. Computes \f$E{wij.wij^{T}} = (Id + T^{T} \Sigma^{-1} T)^{-1} + E{wij}.E{wij^{T}}\f$ + m_tmp_wij2 += m_tmp_tt2; // E{wij.wij^{T}} + + if (m_update_sigma) + m_acc_Nij += (*it).n; + + for (int c=0; c<C; ++c) + { + blitz::Array<double,2> acc_Nij_wij2_c = m_acc_Nij_wij2(c,rall,rall); + blitz::Array<double,2> acc_Fnormij_wij = m_acc_Fnormij_wij(c,rall,rall); + // acc_Nij_wij2_c += Nijc . E{wij.wij^{T}} + acc_Nij_wij2_c += (*it).n(c) * m_tmp_wij2; + blitz::Array<double,1> mc = machine.getUbm()->getGaussian(c)->getMean(); + // m_tmp_d1 = Fijc - Nijc * ubmmean_{c} + m_tmp_d1 = (*it).sumPx(c,rall) - (*it).n(c)*mc; // Fnorm_c + // m_tmp_dt1 = (Fijc - Nijc * ubmmean_{c}).E{wij}^{T} + bob::math::prod(m_tmp_d1, m_tmp_wij, m_tmp_dt1); + // acc_Fnormij_wij += (Fijc - Nijc * ubmmean_{c}).E{wij}^{T} + acc_Fnormij_wij += m_tmp_dt1; + if (m_update_sigma) + { + blitz::Array<double,1> acc_Snormij_c = m_acc_Snormij(c,rall); + acc_Snormij_c += (*it).sumPxx(c,rall) - mc*((*it).sumPx(c,rall) + m_tmp_d1); + } + } + } +} + +void bob::trainer::IVectorTrainer::mStep( + bob::machine::IVectorMachine& machine, + const std::vector<bob::machine::GMMStats>& data) +{ + blitz::Range rall = blitz::Range::all(); + blitz::Array<double,2>& T = machine.updateT(); + blitz::Array<double,1>& sigma = machine.updateSigma(); + const int C = (int)machine.getDimC(); + const int D = (int)machine.getDimD(); + for (int c=0; c<C; ++c) + { + // Solves linear system A.T = B to update T, based on accumulators of + // the eStep() + blitz::Array<double,2> acc_Nij_wij2_c = m_acc_Nij_wij2(c,rall,rall); + blitz::Array<double,2> tacc_Nij_wij2_c = acc_Nij_wij2_c.transpose(1,0); + blitz::Array<double,2> acc_Fnormij_wij_c = m_acc_Fnormij_wij(c,rall,rall); + blitz::Array<double,2> tacc_Fnormij_wij_c = acc_Fnormij_wij_c.transpose(1,0); + blitz::Array<double,2> T_c = T(blitz::Range(c*D,(c+1)*D-1),rall); + blitz::Array<double,2> Tt_c = T_c.transpose(1,0); + if (blitz::all(acc_Nij_wij2_c == 0)) // TODO + Tt_c = 0; + else + bob::math::linsolve(tacc_Nij_wij2_c, Tt_c, tacc_Fnormij_wij_c); + if (m_update_sigma) + { + blitz::Array<double,1> sigma_c = sigma(blitz::Range(c*D,(c+1)*D-1)); + bob::math::prod(acc_Fnormij_wij_c, Tt_c, m_tmp_dd1); + bob::math::diag(m_tmp_dd1, m_tmp_d1); + sigma_c = (m_acc_Snormij(c,rall) - m_tmp_d1) / m_acc_Nij(c); + } + } + machine.precompute(); +} + + +double bob::trainer::IVectorTrainer::computeLikelihood( + bob::machine::IVectorMachine& machine) +{ + // TODO: implementation + return 0; +} + +void bob::trainer::IVectorTrainer::finalize( + bob::machine::IVectorMachine& machine, + const std::vector<bob::machine::GMMStats>& data) +{ +} + +bob::trainer::IVectorTrainer& bob::trainer::IVectorTrainer::operator= + (const bob::trainer::IVectorTrainer &other) +{ + if (this != &other) + { + bob::trainer::EMTrainer<bob::machine::IVectorMachine, + std::vector<bob::machine::GMMStats> >::operator=(other); + m_update_sigma = other.m_update_sigma; + + m_acc_Nij_wij2.reference(bob::core::array::ccopy(other.m_acc_Nij_wij2)); + m_acc_Fnormij_wij.reference(bob::core::array::ccopy(other.m_acc_Fnormij_wij)); + m_acc_Nij.reference(bob::core::array::ccopy(other.m_acc_Nij)); + m_acc_Snormij.reference(bob::core::array::ccopy(other.m_acc_Snormij)); + + m_tmp_wij.reference(bob::core::array::ccopy(other.m_tmp_wij)); + m_tmp_wij2.reference(bob::core::array::ccopy(other.m_tmp_wij2)); + m_tmp_d1.reference(bob::core::array::ccopy(other.m_tmp_d1)); + m_tmp_t1.reference(bob::core::array::ccopy(other.m_tmp_t1)); + m_tmp_dd1.reference(bob::core::array::ccopy(other.m_tmp_dd1)); + m_tmp_dt1.reference(bob::core::array::ccopy(other.m_tmp_dt1)); + m_tmp_tt1.reference(bob::core::array::ccopy(other.m_tmp_tt1)); + m_tmp_tt2.reference(bob::core::array::ccopy(other.m_tmp_tt2)); + } + return *this; +} + +bool bob::trainer::IVectorTrainer::operator== + (const bob::trainer::IVectorTrainer &other) const +{ + return bob::trainer::EMTrainer<bob::machine::IVectorMachine, + std::vector<bob::machine::GMMStats> >::operator==(other) && + m_update_sigma == other.m_update_sigma && + bob::core::array::isEqual(m_acc_Nij_wij2, other.m_acc_Nij_wij2) && + bob::core::array::isEqual(m_acc_Fnormij_wij, other.m_acc_Fnormij_wij) && + bob::core::array::isEqual(m_acc_Nij, other.m_acc_Nij) && + bob::core::array::isEqual(m_acc_Snormij, other.m_acc_Snormij); +} + +bool bob::trainer::IVectorTrainer::operator!= + (const bob::trainer::IVectorTrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::IVectorTrainer::is_similar_to + (const bob::trainer::IVectorTrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::EMTrainer<bob::machine::IVectorMachine, + std::vector<bob::machine::GMMStats> >::is_similar_to(other, r_epsilon, a_epsilon) && + m_update_sigma == other.m_update_sigma && + bob::core::array::isClose(m_acc_Nij_wij2, other.m_acc_Nij_wij2, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_acc_Fnormij_wij, other.m_acc_Fnormij_wij, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_acc_Nij, other.m_acc_Nij, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_acc_Snormij, other.m_acc_Snormij, r_epsilon, a_epsilon); +} + diff --git a/bob/learn/misc/cpp/JFAMachine.cpp b/bob/learn/misc/cpp/JFAMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7324cf196263c0ec85ea9cb6e54aa46ce4202b6e --- /dev/null +++ b/bob/learn/misc/cpp/JFAMachine.cpp @@ -0,0 +1,791 @@ +/** + * @date Sat Jul 23 21:41:15 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + + +#include <bob.learn.misc/JFAMachine.h> +#include <bob.core/array_copy.h> +#include <bob.math/linear.h> +#include <bob.math/inv.h> +#include <bob.learn.misc/LinearScoring.h> +#include <limits> + + +//////////////////// FABase //////////////////// +bob::machine::FABase::FABase(): + m_ubm(boost::shared_ptr<bob::machine::GMMMachine>()), m_ru(1), m_rv(1), + m_U(0,1), m_V(0,1), m_d(0) +{ +} + +bob::machine::FABase::FABase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, + const size_t ru, const size_t rv): + m_ubm(ubm), m_ru(ru), m_rv(rv), + m_U(getDimCD(),ru), m_V(getDimCD(),rv), m_d(getDimCD()) +{ + if (ru < 1) { + boost::format m("value for parameter `ru' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + if (rv < 1) { + boost::format m("value for parameter `rv' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + updateCache(); +} + +bob::machine::FABase::FABase(const bob::machine::FABase& other): + m_ubm(other.m_ubm), m_ru(other.m_ru), m_rv(other.m_rv), + m_U(bob::core::array::ccopy(other.m_U)), + m_V(bob::core::array::ccopy(other.m_V)), + m_d(bob::core::array::ccopy(other.m_d)) +{ + updateCache(); +} + +bob::machine::FABase::~FABase() { +} + +bob::machine::FABase& bob::machine::FABase::operator= +(const bob::machine::FABase& other) +{ + if (this != &other) + { + m_ubm = other.m_ubm; + m_ru = other.m_ru; + m_rv = other.m_rv; + m_U.reference(bob::core::array::ccopy(other.m_U)); + m_V.reference(bob::core::array::ccopy(other.m_V)); + m_d.reference(bob::core::array::ccopy(other.m_d)); + + updateCache(); + } + return *this; +} + +bool bob::machine::FABase::operator==(const bob::machine::FABase& b) const +{ + return ( (((m_ubm && b.m_ubm) && *m_ubm == *(b.m_ubm)) || (!m_ubm && !b.m_ubm)) && + m_ru == b.m_ru && m_rv == b.m_rv && + bob::core::array::isEqual(m_U, b.m_U) && + bob::core::array::isEqual(m_V, b.m_V) && + bob::core::array::isEqual(m_d, b.m_d)); +} + +bool bob::machine::FABase::operator!=(const bob::machine::FABase& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::FABase::is_similar_to(const bob::machine::FABase& b, + const double r_epsilon, const double a_epsilon) const +{ + // TODO: update is_similar_to of the GMMMachine with the 2 epsilon's + return (( ((m_ubm && b.m_ubm) && m_ubm->is_similar_to(*(b.m_ubm), a_epsilon)) || + (!m_ubm && !b.m_ubm) ) && + m_ru == b.m_ru && m_rv == b.m_rv && + bob::core::array::isClose(m_U, b.m_U, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_V, b.m_V, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_d, b.m_d, r_epsilon, a_epsilon)); +} + +void bob::machine::FABase::resize(const size_t ru, const size_t rv) +{ + if (ru < 1) { + boost::format m("value for parameter `ru' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + if (rv < 1) { + boost::format m("value for parameter `rv' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + + m_ru = ru; + m_rv = rv; + m_U.resizeAndPreserve(m_U.extent(0), ru); + m_V.resizeAndPreserve(m_V.extent(0), rv); + + updateCacheUbmUVD(); +} + +void bob::machine::FABase::resize(const size_t ru, const size_t rv, const size_t cd) +{ + if (ru < 1) { + boost::format m("value for parameter `ru' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + if (rv < 1) { + boost::format m("value for parameter `rv' (%lu) cannot be smaller than 1"); + m % ru; + throw std::runtime_error(m.str()); + } + + if (!m_ubm || (m_ubm && getDimCD() == cd)) + { + m_ru = ru; + m_rv = rv; + m_U.resizeAndPreserve(cd, ru); + m_V.resizeAndPreserve(cd, rv); + m_d.resizeAndPreserve(cd); + + updateCacheUbmUVD(); + } + else { + boost::format m("value for parameter `cd' (%lu) is not set to %lu"); + m % cd % getDimCD(); + throw std::runtime_error(m.str()); + } +} + +void bob::machine::FABase::setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm) +{ + m_ubm = ubm; + m_U.resizeAndPreserve(getDimCD(), m_ru); + m_V.resizeAndPreserve(getDimCD(), m_rv); + m_d.resizeAndPreserve(getDimCD()); + + updateCache(); +} + +void bob::machine::FABase::setU(const blitz::Array<double,2>& U) +{ + if(U.extent(0) != m_U.extent(0)) { //checks dimension + boost::format m("number of rows in parameter `U' (%d) does not match the expected size (%d)"); + m % U.extent(0) % m_U.extent(0); + throw std::runtime_error(m.str()); + } + if(U.extent(1) != m_U.extent(1)) { //checks dimension + boost::format m("number of columns in parameter `U' (%d) does not match the expected size (%d)"); + m % U.extent(1) % m_U.extent(1); + throw std::runtime_error(m.str()); + } + m_U.reference(bob::core::array::ccopy(U)); + + // update cache + updateCacheUbmUVD(); +} + +void bob::machine::FABase::setV(const blitz::Array<double,2>& V) +{ + if(V.extent(0) != m_V.extent(0)) { //checks dimension + boost::format m("number of rows in parameter `V' (%d) does not match the expected size (%d)"); + m % V.extent(0) % m_V.extent(0); + throw std::runtime_error(m.str()); + } + if(V.extent(1) != m_V.extent(1)) { //checks dimension + boost::format m("number of columns in parameter `V' (%d) does not match the expected size (%d)"); + m % V.extent(1) % m_V.extent(1); + throw std::runtime_error(m.str()); + } + m_V.reference(bob::core::array::ccopy(V)); +} + +void bob::machine::FABase::setD(const blitz::Array<double,1>& d) +{ + if(d.extent(0) != m_d.extent(0)) { //checks dimension + boost::format m("size of input vector `d' (%d) does not match the expected size (%d)"); + m % d.extent(0) % m_d.extent(0); + throw std::runtime_error(m.str()); + } + m_d.reference(bob::core::array::ccopy(d)); +} + + +void bob::machine::FABase::updateCache() +{ + updateCacheUbm(); + updateCacheUbmUVD(); + resizeTmp(); +} + +void bob::machine::FABase::resizeTmp() +{ + m_tmp_IdPlusUSProdInv.resize(getDimRu(),getDimRu()); + m_tmp_Fn_x.resize(getDimCD()); + m_tmp_ru.resize(getDimRu()); + m_tmp_ruD.resize(getDimRu(), getDimD()); + m_tmp_ruru.resize(getDimRu(), getDimRu()); +} + +void bob::machine::FABase::updateCacheUbm() +{ + // Put supervectors in cache + if (m_ubm) + { + m_cache_mean.resize(getDimCD()); + m_cache_sigma.resize(getDimCD()); + m_ubm->getMeanSupervector(m_cache_mean); + m_ubm->getVarianceSupervector(m_cache_sigma); + } +} + +void bob::machine::FABase::updateCacheUbmUVD() +{ + // Compute and put U^{T}.diag(sigma)^{-1} in cache + if (m_ubm) + { + blitz::firstIndex i; + blitz::secondIndex j; + m_cache_UtSigmaInv.resize(getDimRu(), getDimCD()); + m_cache_UtSigmaInv = m_U(j,i) / m_cache_sigma(j); // Ut * diag(sigma)^-1 + } +} + +void bob::machine::FABase::computeIdPlusUSProdInv(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,2>& output) const +{ + // Computes (Id + U^T.Sigma^-1.U.N_{i,h}.U)^-1 = + // (Id + sum_{c=1..C} N_{i,h}.U_{c}^T.Sigma_{c}^-1.U_{c})^-1 + + // Blitz compatibility: ugly fix (const_cast, as old blitz version does not + // provide a non-const version of transpose()) + blitz::Array<double,2> Ut = const_cast<blitz::Array<double,2>&>(m_U).transpose(1,0); + + blitz::firstIndex i; + blitz::secondIndex j; + blitz::Range rall = blitz::Range::all(); + + bob::math::eye(m_tmp_ruru); // m_tmp_ruru = Id + // Loop and add N_{i,h}.U_{c}^T.Sigma_{c}^-1.U_{c} to m_tmp_ruru at each iteration + const size_t dim_c = getDimC(); + const size_t dim_d = getDimD(); + for(size_t c=0; c<dim_c; ++c) { + blitz::Range rc(c*dim_d,(c+1)*dim_d-1); + blitz::Array<double,2> Ut_c = Ut(rall,rc); + blitz::Array<double,1> sigma_c = m_cache_sigma(rc); + m_tmp_ruD = Ut_c(i,j) / sigma_c(j); // U_{c}^T.Sigma_{c}^-1 + blitz::Array<double,2> U_c = m_U(rc,rall); + // Use m_cache_IdPlusUSProdInv as an intermediate array + bob::math::prod(m_tmp_ruD, U_c, output); // U_{c}^T.Sigma_{c}^-1.U_{c} + // Finally, add N_{i,h}.U_{c}^T.Sigma_{c}^-1.U_{c} to m_tmp_ruru + m_tmp_ruru += output * gmm_stats.n(c); + } + // Computes the inverse + bob::math::inv(m_tmp_ruru, output); +} + + +void bob::machine::FABase::computeFn_x(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,1>& output) const +{ + // Compute Fn_x = sum_{sessions h}(N*(o - m) (Normalised first order statistics) + blitz::Range rall = blitz::Range::all(); + const size_t dim_c = getDimC(); + const size_t dim_d = getDimD(); + for(size_t c=0; c<dim_c; ++c) { + blitz::Range rc(c*dim_d,(c+1)*dim_d-1); + blitz::Array<double,1> Fn_x_c = output(rc); + blitz::Array<double,1> mean_c = m_cache_mean(rc); + Fn_x_c = gmm_stats.sumPx(c,rall) - mean_c*gmm_stats.n(c); + } +} + +void bob::machine::FABase::estimateX(const blitz::Array<double,2>& IdPlusUSProdInv, + const blitz::Array<double,1>& Fn_x, blitz::Array<double,1>& x) const +{ + // m_tmp_ru = UtSigmaInv * Fn_x = Ut*diag(sigma)^-1 * N*(o - m) + bob::math::prod(m_cache_UtSigmaInv, Fn_x, m_tmp_ru); + // x = IdPlusUSProdInv * m_cache_UtSigmaInv * Fn_x + bob::math::prod(IdPlusUSProdInv, m_tmp_ru, x); +} + + +void bob::machine::FABase::estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const +{ + if (!m_ubm) throw std::runtime_error("No UBM was set in the JFA machine."); + computeIdPlusUSProdInv(gmm_stats, m_tmp_IdPlusUSProdInv); // Computes first term + computeFn_x(gmm_stats, m_tmp_Fn_x); // Computes last term + estimateX(m_tmp_IdPlusUSProdInv, m_tmp_Fn_x, x); // Estimates the value of x +} + + + +//////////////////// JFABase //////////////////// +bob::machine::JFABase::JFABase() +{ +} + +bob::machine::JFABase::JFABase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, + const size_t ru, const size_t rv): + m_base(ubm, ru, rv) +{ +} + +bob::machine::JFABase::JFABase(const bob::machine::JFABase& other): + m_base(other.m_base) +{ +} + + +bob::machine::JFABase::JFABase(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::JFABase::~JFABase() { +} + +void bob::machine::JFABase::save(bob::io::base::HDF5File& config) const +{ + config.setArray("U", m_base.getU()); + config.setArray("V", m_base.getV()); + config.setArray("d", m_base.getD()); +} + +void bob::machine::JFABase::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + blitz::Array<double,2> U = config.readArray<double,2>("U"); + blitz::Array<double,2> V = config.readArray<double,2>("V"); + blitz::Array<double,1> d = config.readArray<double,1>("d"); + const int ru = U.extent(1); + const int rv = V.extent(1); + if (!m_base.getUbm()) + m_base.resize(ru, rv, U.extent(0)); + else + m_base.resize(ru, rv); + m_base.setU(U); + m_base.setV(V); + m_base.setD(d); +} + +bob::machine::JFABase& +bob::machine::JFABase::operator=(const bob::machine::JFABase& other) +{ + if (this != &other) + { + m_base = other.m_base; + } + return *this; +} + + +//////////////////// ISVBase //////////////////// +bob::machine::ISVBase::ISVBase() +{ +} + +bob::machine::ISVBase::ISVBase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, + const size_t ru): + m_base(ubm, ru, 1) +{ + blitz::Array<double,2>& V = m_base.updateV(); + V = 0; +} + +bob::machine::ISVBase::ISVBase(const bob::machine::ISVBase& other): + m_base(other.m_base) +{ +} + + +bob::machine::ISVBase::ISVBase(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::ISVBase::~ISVBase() { +} + +void bob::machine::ISVBase::save(bob::io::base::HDF5File& config) const +{ + config.setArray("U", m_base.getU()); + config.setArray("d", m_base.getD()); +} + +void bob::machine::ISVBase::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + blitz::Array<double,2> U = config.readArray<double,2>("U"); + blitz::Array<double,1> d = config.readArray<double,1>("d"); + const int ru = U.extent(1); + if (!m_base.getUbm()) + m_base.resize(ru, 1, U.extent(0)); + else + m_base.resize(ru, 1); + m_base.setU(U); + m_base.setD(d); + blitz::Array<double,2>& V = m_base.updateV(); + V = 0; +} + +bob::machine::ISVBase& +bob::machine::ISVBase::operator=(const bob::machine::ISVBase& other) +{ + if (this != &other) + { + m_base = other.m_base; + } + return *this; +} + + + +//////////////////// JFAMachine //////////////////// +bob::machine::JFAMachine::JFAMachine(): + m_y(1), m_z(1) +{ + resizeTmp(); +} + +bob::machine::JFAMachine::JFAMachine(const boost::shared_ptr<bob::machine::JFABase> jfa_base): + m_jfa_base(jfa_base), + m_y(jfa_base->getDimRv()), m_z(jfa_base->getDimCD()) +{ + if (!m_jfa_base->getUbm()) throw std::runtime_error("No UBM was set in the JFA machine."); + updateCache(); + resizeTmp(); +} + + +bob::machine::JFAMachine::JFAMachine(const bob::machine::JFAMachine& other): + m_jfa_base(other.m_jfa_base), + m_y(bob::core::array::ccopy(other.m_y)), + m_z(bob::core::array::ccopy(other.m_z)) +{ + updateCache(); + resizeTmp(); +} + +bob::machine::JFAMachine::JFAMachine(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::JFAMachine::~JFAMachine() { +} + +bob::machine::JFAMachine& +bob::machine::JFAMachine::operator=(const bob::machine::JFAMachine& other) +{ + if (this != &other) + { + m_jfa_base = other.m_jfa_base; + m_y.reference(bob::core::array::ccopy(other.m_y)); + m_z.reference(bob::core::array::ccopy(other.m_z)); + } + return *this; +} + +bool bob::machine::JFAMachine::operator==(const bob::machine::JFAMachine& other) const +{ + return (*m_jfa_base == *(other.m_jfa_base) && + bob::core::array::isEqual(m_y, other.m_y) && + bob::core::array::isEqual(m_z, other.m_z)); +} + +bool bob::machine::JFAMachine::operator!=(const bob::machine::JFAMachine& b) const +{ + return !(this->operator==(b)); +} + + +bool bob::machine::JFAMachine::is_similar_to(const bob::machine::JFAMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + return (m_jfa_base->is_similar_to(*(b.m_jfa_base), r_epsilon, a_epsilon) && + bob::core::array::isClose(m_y, b.m_y, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_z, b.m_z, r_epsilon, a_epsilon)); +} + +void bob::machine::JFAMachine::save(bob::io::base::HDF5File& config) const +{ + config.setArray("y", m_y); + config.setArray("z", m_z); +} + +void bob::machine::JFAMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + blitz::Array<double,1> y = config.readArray<double,1>("y"); + blitz::Array<double,1> z = config.readArray<double,1>("z"); + if (!m_jfa_base) + { + m_y.resize(y.extent(0)); + m_z.resize(z.extent(0)); + } + setY(y); + setZ(z); + // update cache + updateCache(); + resizeTmp(); +} + + +void bob::machine::JFAMachine::setY(const blitz::Array<double,1>& y) +{ + if(y.extent(0) != m_y.extent(0)) { //checks dimension + boost::format m("size of input vector `y' (%d) does not match the expected size (%d)"); + m % y.extent(0) % m_y.extent(0); + throw std::runtime_error(m.str()); + } + m_y.reference(bob::core::array::ccopy(y)); + // update cache + updateCache(); +} + +void bob::machine::JFAMachine::setZ(const blitz::Array<double,1>& z) +{ + if(z.extent(0) != m_z.extent(0)) { //checks dimension + boost::format m("size of input vector `z' (%d) does not match the expected size (%d)"); + m % z.extent(0) % m_z.extent(0); + throw std::runtime_error(m.str()); + } + m_z.reference(bob::core::array::ccopy(z)); + // update cache + updateCache(); +} + +void bob::machine::JFAMachine::setJFABase(const boost::shared_ptr<bob::machine::JFABase> jfa_base) +{ + if (!jfa_base->getUbm()) + throw std::runtime_error("No UBM was set in the JFA machine."); + m_jfa_base = jfa_base; + // Resize variables + resize(); +} + +void bob::machine::JFAMachine::resize() +{ + m_y.resizeAndPreserve(getDimRv()); + m_z.resizeAndPreserve(getDimCD()); + updateCache(); + resizeTmp(); +} + +void bob::machine::JFAMachine::resizeTmp() +{ + if (m_jfa_base) + { + m_tmp_Ux.resize(getDimCD()); + } +} + +void bob::machine::JFAMachine::updateCache() +{ + if (m_jfa_base) + { + // m + Vy + Dz + m_cache_mVyDz.resize(getDimCD()); + bob::math::prod(m_jfa_base->getV(), m_y, m_cache_mVyDz); + m_cache_mVyDz += m_jfa_base->getD()*m_z + m_jfa_base->getUbm()->getMeanSupervector(); + m_cache_x.resize(getDimRu()); + } +} + +void bob::machine::JFAMachine::estimateUx(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,1>& Ux) +{ + estimateX(gmm_stats, m_cache_x); + bob::math::prod(m_jfa_base->getU(), m_cache_x, Ux); +} + +void bob::machine::JFAMachine::forward(const bob::machine::GMMStats& input, + double& score) const +{ + forward_(input, score); +} + +void bob::machine::JFAMachine::forward(const bob::machine::GMMStats& gmm_stats, + const blitz::Array<double,1>& Ux, double& score) const +{ + // Checks that a Base machine has been set + if (!m_jfa_base) throw std::runtime_error("No UBM was set in the JFA machine."); + + score = bob::machine::linearScoring(m_cache_mVyDz, + m_jfa_base->getUbm()->getMeanSupervector(), m_jfa_base->getUbm()->getVarianceSupervector(), + gmm_stats, Ux, true); +} + +void bob::machine::JFAMachine::forward_(const bob::machine::GMMStats& input, + double& score) const +{ + // Checks that a Base machine has been set + if (!m_jfa_base) throw std::runtime_error("No UBM was set in the JFA machine."); + + // Ux and GMMStats + estimateX(input, m_cache_x); + bob::math::prod(m_jfa_base->getU(), m_cache_x, m_tmp_Ux); + + score = bob::machine::linearScoring(m_cache_mVyDz, + m_jfa_base->getUbm()->getMeanSupervector(), m_jfa_base->getUbm()->getVarianceSupervector(), + input, m_tmp_Ux, true); +} + + + +//////////////////// ISVMachine //////////////////// +bob::machine::ISVMachine::ISVMachine(): + m_z(1) +{ + resizeTmp(); +} + +bob::machine::ISVMachine::ISVMachine(const boost::shared_ptr<bob::machine::ISVBase> isv_base): + m_isv_base(isv_base), + m_z(isv_base->getDimCD()) +{ + if (!m_isv_base->getUbm()) + throw std::runtime_error("No UBM was set in the JFA machine."); + updateCache(); + resizeTmp(); +} + + +bob::machine::ISVMachine::ISVMachine(const bob::machine::ISVMachine& other): + m_isv_base(other.m_isv_base), + m_z(bob::core::array::ccopy(other.m_z)) +{ + updateCache(); + resizeTmp(); +} + +bob::machine::ISVMachine::ISVMachine(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::ISVMachine::~ISVMachine() { +} + +bob::machine::ISVMachine& +bob::machine::ISVMachine::operator=(const bob::machine::ISVMachine& other) +{ + if (this != &other) + { + m_isv_base = other.m_isv_base; + m_z.reference(bob::core::array::ccopy(other.m_z)); + } + return *this; +} + +bool bob::machine::ISVMachine::operator==(const bob::machine::ISVMachine& other) const +{ + return (*m_isv_base == *(other.m_isv_base) && + bob::core::array::isEqual(m_z, other.m_z)); +} + +bool bob::machine::ISVMachine::operator!=(const bob::machine::ISVMachine& b) const +{ + return !(this->operator==(b)); +} + + +bool bob::machine::ISVMachine::is_similar_to(const bob::machine::ISVMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + return (m_isv_base->is_similar_to(*(b.m_isv_base), r_epsilon, a_epsilon) && + bob::core::array::isClose(m_z, b.m_z, r_epsilon, a_epsilon)); +} + +void bob::machine::ISVMachine::save(bob::io::base::HDF5File& config) const +{ + config.setArray("z", m_z); +} + +void bob::machine::ISVMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + blitz::Array<double,1> z = config.readArray<double,1>("z"); + if (!m_isv_base) + m_z.resize(z.extent(0)); + setZ(z); + // update cache + updateCache(); + resizeTmp(); +} + +void bob::machine::ISVMachine::setZ(const blitz::Array<double,1>& z) +{ + if(z.extent(0) != m_z.extent(0)) { //checks dimension + boost::format m("size of input vector `z' (%d) does not match the expected size (%d)"); + m % z.extent(0) % m_z.extent(0); + throw std::runtime_error(m.str()); + } + m_z.reference(bob::core::array::ccopy(z)); + // update cache + updateCache(); +} + +void bob::machine::ISVMachine::setISVBase(const boost::shared_ptr<bob::machine::ISVBase> isv_base) +{ + if (!isv_base->getUbm()) + throw std::runtime_error("No UBM was set in the JFA machine."); + m_isv_base = isv_base; + // Resize variables + resize(); +} + +void bob::machine::ISVMachine::resize() +{ + m_z.resizeAndPreserve(getDimCD()); + updateCache(); + resizeTmp(); +} + +void bob::machine::ISVMachine::resizeTmp() +{ + if (m_isv_base) + { + m_tmp_Ux.resize(getDimCD()); + } +} + +void bob::machine::ISVMachine::updateCache() +{ + if (m_isv_base) + { + // m + Dz + m_cache_mDz.resize(getDimCD()); + m_cache_mDz = m_isv_base->getD()*m_z + m_isv_base->getUbm()->getMeanSupervector(); + m_cache_x.resize(getDimRu()); + } +} + +void bob::machine::ISVMachine::estimateUx(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,1>& Ux) +{ + estimateX(gmm_stats, m_cache_x); + bob::math::prod(m_isv_base->getU(), m_cache_x, Ux); +} + +void bob::machine::ISVMachine::forward(const bob::machine::GMMStats& input, + double& score) const +{ + forward_(input, score); +} + +void bob::machine::ISVMachine::forward(const bob::machine::GMMStats& gmm_stats, + const blitz::Array<double,1>& Ux, double& score) const +{ + // Checks that a Base machine has been set + if (!m_isv_base) throw std::runtime_error("No UBM was set in the JFA machine."); + + score = bob::machine::linearScoring(m_cache_mDz, + m_isv_base->getUbm()->getMeanSupervector(), m_isv_base->getUbm()->getVarianceSupervector(), + gmm_stats, Ux, true); +} + +void bob::machine::ISVMachine::forward_(const bob::machine::GMMStats& input, + double& score) const +{ + // Checks that a Base machine has been set + if(!m_isv_base) throw std::runtime_error("No UBM was set in the JFA machine."); + + // Ux and GMMStats + estimateX(input, m_cache_x); + bob::math::prod(m_isv_base->getU(), m_cache_x, m_tmp_Ux); + + score = bob::machine::linearScoring(m_cache_mDz, + m_isv_base->getUbm()->getMeanSupervector(), m_isv_base->getUbm()->getVarianceSupervector(), + input, m_tmp_Ux, true); +} + diff --git a/bob/learn/misc/cpp/JFATrainer.cpp b/bob/learn/misc/cpp/JFATrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28e18c1a2a2e12a2b5e17ac3fb03f191c998c46d --- /dev/null +++ b/bob/learn/misc/cpp/JFATrainer.cpp @@ -0,0 +1,849 @@ +/** + * @date Tue Jul 19 12:16:17 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Joint Factor Analysis Trainer + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/JFATrainer.h> +#include <bob.core/check.h> +#include <bob.core/array_copy.h> +#include <bob.core/array_random.h> +#include <bob.math/inv.h> +#include <bob.math/linear.h> +#include <bob.core/check.h> +#include <bob.core/array_repmat.h> +#include <algorithm> + + +bob::trainer::FABaseTrainer::FABaseTrainer(): + m_Nid(0), m_dim_C(0), m_dim_D(0), m_dim_ru(0), m_dim_rv(0), + m_x(0), m_y(0), m_z(0), m_Nacc(0), m_Facc(0) +{ +} + +bob::trainer::FABaseTrainer::FABaseTrainer(const bob::trainer::FABaseTrainer& other) +{ +} + +bob::trainer::FABaseTrainer::~FABaseTrainer() +{ +} + +void bob::trainer::FABaseTrainer::checkStatistics( + const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + for (size_t id=0; id<stats.size(); ++id) { + for (size_t s=0; s<stats[id].size(); ++s) { + if (stats[id][s]->sumPx.extent(0) != (int)m_dim_C) { + boost::format m("GMMStats C dimension parameter = %d is different than the expected value of %d"); + m % stats[id][s]->sumPx.extent(0) % (int)m_dim_C; + throw std::runtime_error(m.str()); + } + if (stats[id][s]->sumPx.extent(1) != (int)m_dim_D) { + boost::format m("GMMStats D dimension parameter = %d is different than the expected value of %d"); + m % stats[id][s]->sumPx.extent(1) % (int)m_dim_D; + throw std::runtime_error(m.str()); + } + } + } +} + + +void bob::trainer::FABaseTrainer::initUbmNidSumStatistics( + const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + m_Nid = stats.size(); + boost::shared_ptr<bob::machine::GMMMachine> ubm = m.getUbm(); + // Put UBM in cache + m_dim_C = ubm->getNGaussians(); + m_dim_D = ubm->getNInputs(); + m_dim_ru = m.getDimRu(); + m_dim_rv = m.getDimRv(); + // Check statistics dimensionality + checkStatistics(m, stats); + // Precomputes the sum of the statistics for each client/identity + precomputeSumStatisticsN(stats); + precomputeSumStatisticsF(stats); + // Cache and working arrays + initCache(); +} + +void bob::trainer::FABaseTrainer::precomputeSumStatisticsN( + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + m_Nacc.clear(); + blitz::Array<double,1> Nsum(m_dim_C); + for (size_t id=0; id<stats.size(); ++id) { + Nsum = 0.; + for (size_t s=0; s<stats[id].size(); ++s) { + Nsum += stats[id][s]->n; + } + m_Nacc.push_back(bob::core::array::ccopy(Nsum)); + } +} + +void bob::trainer::FABaseTrainer::precomputeSumStatisticsF( + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + m_Facc.clear(); + blitz::Array<double,1> Fsum(m_dim_C*m_dim_D); + for (size_t id=0; id<stats.size(); ++id) { + Fsum = 0.; + for (size_t s=0; s<stats[id].size(); ++s) { + for (size_t c=0; c<m_dim_C; ++c) { + blitz::Array<double,1> Fsum_c = Fsum(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1)); + Fsum_c += stats[id][s]->sumPx(c,blitz::Range::all()); + } + } + m_Facc.push_back(bob::core::array::ccopy(Fsum)); + } +} + +void bob::trainer::FABaseTrainer::initializeXYZ(const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& vec) +{ + m_x.clear(); + m_y.clear(); + m_z.clear(); + + blitz::Array<double,1> z0(m_dim_C*m_dim_D); + z0 = 0; + blitz::Array<double,1> y0(m_dim_rv); + y0 = 0; + blitz::Array<double,2> x0(m_dim_ru,0); + x0 = 0; + for (size_t i=0; i<vec.size(); ++i) + { + m_z.push_back(bob::core::array::ccopy(z0)); + m_y.push_back(bob::core::array::ccopy(y0)); + x0.resize(m_dim_ru, vec[i].size()); + x0 = 0; + m_x.push_back(bob::core::array::ccopy(x0)); + } +} + +void bob::trainer::FABaseTrainer::resetXYZ() +{ + for (size_t i=0; i<m_x.size(); ++i) + { + m_x[i] = 0.; + m_y[i] = 0.; + m_z[i] = 0.; + } +} + +void bob::trainer::FABaseTrainer::initCache() +{ + const size_t dim_CD = m_dim_C*m_dim_D; + // U + m_cache_UtSigmaInv.resize(m_dim_ru, dim_CD); + m_cache_UProd.resize(m_dim_C, m_dim_ru, m_dim_ru); + m_cache_IdPlusUProd_ih.resize(m_dim_ru, m_dim_ru); + m_cache_Fn_x_ih.resize(dim_CD); + m_acc_U_A1.resize(m_dim_C, m_dim_ru, m_dim_ru); + m_acc_U_A2.resize(dim_CD, m_dim_ru); + // V + m_cache_VtSigmaInv.resize(m_dim_rv, dim_CD); + m_cache_VProd.resize(m_dim_C, m_dim_rv, m_dim_rv); + m_cache_IdPlusVProd_i.resize(m_dim_rv, m_dim_rv); + m_cache_Fn_y_i.resize(dim_CD); + m_acc_V_A1.resize(m_dim_C, m_dim_rv, m_dim_rv); + m_acc_V_A2.resize(dim_CD, m_dim_rv); + // D + m_cache_DtSigmaInv.resize(dim_CD); + m_cache_DProd.resize(dim_CD); + m_cache_IdPlusDProd_i.resize(dim_CD); + m_cache_Fn_z_i.resize(dim_CD); + m_acc_D_A1.resize(dim_CD); + m_acc_D_A2.resize(dim_CD); + + // tmp + m_tmp_CD.resize(dim_CD); + m_tmp_CD_b.resize(dim_CD); + + m_tmp_ru.resize(m_dim_ru); + m_tmp_ruD.resize(m_dim_ru, m_dim_D); + m_tmp_ruru.resize(m_dim_ru, m_dim_ru); + + m_tmp_rv.resize(m_dim_rv); + m_tmp_rvD.resize(m_dim_rv, m_dim_D); + m_tmp_rvrv.resize(m_dim_rv, m_dim_rv); +} + + + +//////////////////////////// V /////////////////////////// +void bob::trainer::FABaseTrainer::computeVtSigmaInv(const bob::machine::FABase& m) +{ + const blitz::Array<double,2>& V = m.getV(); + // Blitz compatibility: ugly fix (const_cast, as old blitz version does not + // provide a non-const version of transpose()) + const blitz::Array<double,2> Vt = const_cast<blitz::Array<double,2>&>(V).transpose(1,0); + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + blitz::firstIndex i; + blitz::secondIndex j; + m_cache_VtSigmaInv = Vt(i,j) / sigma(j); // Vt * diag(sigma)^-1 +} + +void bob::trainer::FABaseTrainer::computeVProd(const bob::machine::FABase& m) +{ + const blitz::Array<double,2>& V = m.getV(); + blitz::firstIndex i; + blitz::secondIndex j; + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + blitz::Range rall = blitz::Range::all(); + for (size_t c=0; c<m_dim_C; ++c) + { + blitz::Array<double,2> VProd_c = m_cache_VProd(c, rall, rall); + blitz::Array<double,2> Vv_c = V(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1), rall); + blitz::Array<double,2> Vt_c = Vv_c.transpose(1,0); + blitz::Array<double,1> sigma_c = sigma(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1)); + m_tmp_rvD = Vt_c(i,j) / sigma_c(j); // Vt_c * diag(sigma)^-1 + bob::math::prod(m_tmp_rvD, Vv_c, VProd_c); + } +} + +void bob::trainer::FABaseTrainer::computeIdPlusVProd_i(const size_t id) +{ + const blitz::Array<double,1>& Ni = m_Nacc[id]; + bob::math::eye(m_tmp_rvrv); // m_tmp_rvrv = I + blitz::Range rall = blitz::Range::all(); + for (size_t c=0; c<m_dim_C; ++c) { + blitz::Array<double,2> VProd_c = m_cache_VProd(c, rall, rall); + m_tmp_rvrv += VProd_c * Ni(c); + } + bob::math::inv(m_tmp_rvrv, m_cache_IdPlusVProd_i); // m_cache_IdPlusVProd_i = ( I+Vt*diag(sigma)^-1*Ni*V)^-1 +} + +void bob::trainer::FABaseTrainer::computeFn_y_i(const bob::machine::FABase& mb, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& stats, const size_t id) +{ + const blitz::Array<double,2>& U = mb.getU(); + const blitz::Array<double,1>& d = mb.getD(); + // Compute Fn_yi = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - U*x_{i,h}) (Normalised first order statistics) + const blitz::Array<double,1>& Fi = m_Facc[id]; + const blitz::Array<double,1>& m = mb.getUbmMean(); + const blitz::Array<double,1>& z = m_z[id]; + bob::core::array::repelem(m_Nacc[id], m_tmp_CD); + m_cache_Fn_y_i = Fi - m_tmp_CD * (m + d * z); // Fn_yi = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i}) + const blitz::Array<double,2>& X = m_x[id]; + blitz::Range rall = blitz::Range::all(); + for (int h=0; h<X.extent(1); ++h) // Loops over the sessions + { + blitz::Array<double,1> Xh = X(rall, h); // Xh = x_{i,h} (length: ru) + bob::math::prod(U, Xh, m_tmp_CD_b); // m_tmp_CD_b = U*x_{i,h} + const blitz::Array<double,1>& Nih = stats[h]->n; + bob::core::array::repelem(Nih, m_tmp_CD); + m_cache_Fn_y_i -= m_tmp_CD * m_tmp_CD_b; // N_{i,h} * U * x_{i,h} + } + // Fn_yi = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - U*x_{i,h}) +} + +void bob::trainer::FABaseTrainer::updateY_i(const size_t id) +{ + // Computes yi = Ayi * Cvs * Fn_yi + blitz::Array<double,1>& y = m_y[id]; + // m_tmp_rv = m_cache_VtSigmaInv * m_cache_Fn_y_i = Vt*diag(sigma)^-1 * sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - U*x_{i,h}) + bob::math::prod(m_cache_VtSigmaInv, m_cache_Fn_y_i, m_tmp_rv); + bob::math::prod(m_cache_IdPlusVProd_i, m_tmp_rv, y); +} + +void bob::trainer::FABaseTrainer::updateY(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Precomputation + computeVtSigmaInv(m); + computeVProd(m); + // Loops over all people + for (size_t id=0; id<stats.size(); ++id) { + computeIdPlusVProd_i(id); + computeFn_y_i(m, stats[id], id); + updateY_i(id); + } +} + +void bob::trainer::FABaseTrainer::computeAccumulatorsV( + const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Initializes the cache accumulator + m_acc_V_A1 = 0.; + m_acc_V_A2 = 0.; + // Loops over all people + blitz::firstIndex i; + blitz::secondIndex j; + blitz::Range rall = blitz::Range::all(); + for (size_t id=0; id<stats.size(); ++id) { + computeIdPlusVProd_i(id); + computeFn_y_i(m, stats[id], id); + + // Needs to return values to be accumulated for estimating V + const blitz::Array<double,1>& y = m_y[id]; + m_tmp_rvrv = m_cache_IdPlusVProd_i; + m_tmp_rvrv += y(i) * y(j); + for (size_t c=0; c<m_dim_C; ++c) + { + blitz::Array<double,2> A1_y_c = m_acc_V_A1(c, rall, rall); + A1_y_c += m_tmp_rvrv * m_Nacc[id](c); + } + m_acc_V_A2 += m_cache_Fn_y_i(i) * y(j); + } +} + +void bob::trainer::FABaseTrainer::updateV(blitz::Array<double,2>& V) +{ + blitz::Range rall = blitz::Range::all(); + for (size_t c=0; c<m_dim_C; ++c) + { + const blitz::Array<double,2> A1 = m_acc_V_A1(c, rall, rall); + bob::math::inv(A1, m_tmp_rvrv); + const blitz::Array<double,2> A2 = m_acc_V_A2(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1), rall); + blitz::Array<double,2> V_c = V(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1), rall); + bob::math::prod(A2, m_tmp_rvrv, V_c); + } +} + + +//////////////////////////// U /////////////////////////// +void bob::trainer::FABaseTrainer::computeUtSigmaInv(const bob::machine::FABase& m) +{ + const blitz::Array<double,2>& U = m.getU(); + // Blitz compatibility: ugly fix (const_cast, as old blitz version does not + // provide a non-const version of transpose()) + const blitz::Array<double,2> Ut = const_cast<blitz::Array<double,2>&>(U).transpose(1,0); + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + blitz::firstIndex i; + blitz::secondIndex j; + m_cache_UtSigmaInv = Ut(i,j) / sigma(j); // Ut * diag(sigma)^-1 +} + +void bob::trainer::FABaseTrainer::computeUProd(const bob::machine::FABase& m) +{ + const blitz::Array<double,2>& U = m.getU(); + blitz::firstIndex i; + blitz::secondIndex j; + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + for (size_t c=0; c<m_dim_C; ++c) + { + blitz::Array<double,2> UProd_c = m_cache_UProd(c, blitz::Range::all(), blitz::Range::all()); + blitz::Array<double,2> Uu_c = U(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1), blitz::Range::all()); + blitz::Array<double,2> Ut_c = Uu_c.transpose(1,0); + blitz::Array<double,1> sigma_c = sigma(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1)); + m_tmp_ruD = Ut_c(i,j) / sigma_c(j); // Ut_c * diag(sigma)^-1 + bob::math::prod(m_tmp_ruD, Uu_c, UProd_c); + } +} + +void bob::trainer::FABaseTrainer::computeIdPlusUProd_ih( + const boost::shared_ptr<bob::machine::GMMStats>& stats) +{ + const blitz::Array<double,1>& Nih = stats->n; + bob::math::eye(m_tmp_ruru); // m_tmp_ruru = I + for (size_t c=0; c<m_dim_C; ++c) { + blitz::Array<double,2> UProd_c = m_cache_UProd(c,blitz::Range::all(),blitz::Range::all()); + m_tmp_ruru += UProd_c * Nih(c); + } + bob::math::inv(m_tmp_ruru, m_cache_IdPlusUProd_ih); // m_cache_IdPlusUProd_ih = ( I+Ut*diag(sigma)^-1*Ni*U)^-1 +} + +void bob::trainer::FABaseTrainer::computeFn_x_ih(const bob::machine::FABase& mb, + const boost::shared_ptr<bob::machine::GMMStats>& stats, const size_t id) +{ + const blitz::Array<double,2>& V = mb.getV(); + const blitz::Array<double,1>& d = mb.getD(); + // Compute Fn_x_ih = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - V*y_{i}) (Normalised first order statistics) + const blitz::Array<double,2>& Fih = stats->sumPx; + const blitz::Array<double,1>& m = mb.getUbmMean(); + const blitz::Array<double,1>& z = m_z[id]; + const blitz::Array<double,1>& Nih = stats->n; + bob::core::array::repelem(Nih, m_tmp_CD); + for (size_t c=0; c<m_dim_C; ++c) { + blitz::Array<double,1> Fn_x_ih_c = m_cache_Fn_x_ih(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1)); + Fn_x_ih_c = Fih(c,blitz::Range::all()); + } + m_cache_Fn_x_ih -= m_tmp_CD * (m + d * z); // Fn_x_ih = N_{i,h}*(o_{i,h} - m - D*z_{i}) + + const blitz::Array<double,1>& y = m_y[id]; + bob::math::prod(V, y, m_tmp_CD_b); + m_cache_Fn_x_ih -= m_tmp_CD * m_tmp_CD_b; + // Fn_x_ih = N_{i,h}*(o_{i,h} - m - D*z_{i} - V*y_{i}) +} + +void bob::trainer::FABaseTrainer::updateX_ih(const size_t id, const size_t h) +{ + // Computes xih = Axih * Cus * Fn_x_ih + blitz::Array<double,1> x = m_x[id](blitz::Range::all(), h); + // m_tmp_ru = m_cache_UtSigmaInv * m_cache_Fn_x_ih = Ut*diag(sigma)^-1 * N_{i,h}*(o_{i,h} - m - D*z_{i} - V*y_{i}) + bob::math::prod(m_cache_UtSigmaInv, m_cache_Fn_x_ih, m_tmp_ru); + bob::math::prod(m_cache_IdPlusUProd_ih, m_tmp_ru, x); +} + +void bob::trainer::FABaseTrainer::updateX(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Precomputation + computeUtSigmaInv(m); + computeUProd(m); + // Loops over all people + for (size_t id=0; id<stats.size(); ++id) { + int n_session_i = stats[id].size(); + for (int s=0; s<n_session_i; ++s) { + computeIdPlusUProd_ih(stats[id][s]); + computeFn_x_ih(m, stats[id][s], id); + updateX_ih(id, s); + } + } +} + +void bob::trainer::FABaseTrainer::computeAccumulatorsU( + const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Initializes the cache accumulator + m_acc_U_A1 = 0.; + m_acc_U_A2 = 0.; + // Loops over all people + blitz::firstIndex i; + blitz::secondIndex j; + blitz::Range rall = blitz::Range::all(); + for (size_t id=0; id<stats.size(); ++id) { + int n_session_i = stats[id].size(); + for (int h=0; h<n_session_i; ++h) { + computeIdPlusUProd_ih(stats[id][h]); + computeFn_x_ih(m, stats[id][h], id); + + // Needs to return values to be accumulated for estimating U + blitz::Array<double,1> x = m_x[id](rall, h); + m_tmp_ruru = m_cache_IdPlusUProd_ih; + m_tmp_ruru += x(i) * x(j); + for (int c=0; c<(int)m_dim_C; ++c) + { + blitz::Array<double,2> A1_x_c = m_acc_U_A1(c,rall,rall); + A1_x_c += m_tmp_ruru * stats[id][h]->n(c); + } + m_acc_U_A2 += m_cache_Fn_x_ih(i) * x(j); + } + } +} + +void bob::trainer::FABaseTrainer::updateU(blitz::Array<double,2>& U) +{ + for (size_t c=0; c<m_dim_C; ++c) + { + const blitz::Array<double,2> A1 = m_acc_U_A1(c,blitz::Range::all(),blitz::Range::all()); + bob::math::inv(A1, m_tmp_ruru); + const blitz::Array<double,2> A2 = m_acc_U_A2(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1),blitz::Range::all()); + blitz::Array<double,2> U_c = U(blitz::Range(c*m_dim_D,(c+1)*m_dim_D-1),blitz::Range::all()); + bob::math::prod(A2, m_tmp_ruru, U_c); + } +} + + +//////////////////////////// D /////////////////////////// +void bob::trainer::FABaseTrainer::computeDtSigmaInv(const bob::machine::FABase& m) +{ + const blitz::Array<double,1>& d = m.getD(); + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + m_cache_DtSigmaInv = d / sigma; // Dt * diag(sigma)^-1 +} + +void bob::trainer::FABaseTrainer::computeDProd(const bob::machine::FABase& m) +{ + const blitz::Array<double,1>& d = m.getD(); + const blitz::Array<double,1>& sigma = m.getUbmVariance(); + m_cache_DProd = d / sigma * d; // Dt * diag(sigma)^-1 * D +} + +void bob::trainer::FABaseTrainer::computeIdPlusDProd_i(const size_t id) +{ + const blitz::Array<double,1>& Ni = m_Nacc[id]; + bob::core::array::repelem(Ni, m_tmp_CD); // m_tmp_CD = Ni 'repmat' + m_cache_IdPlusDProd_i = 1.; // m_cache_IdPlusDProd_i = Id + m_cache_IdPlusDProd_i += m_cache_DProd * m_tmp_CD; // m_cache_IdPlusDProd_i = I+Dt*diag(sigma)^-1*Ni*D + m_cache_IdPlusDProd_i = 1 / m_cache_IdPlusDProd_i; // m_cache_IdPlusVProd_i = (I+Dt*diag(sigma)^-1*Ni*D)^-1 +} + +void bob::trainer::FABaseTrainer::computeFn_z_i( + const bob::machine::FABase& mb, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& stats, const size_t id) +{ + const blitz::Array<double,2>& U = mb.getU(); + const blitz::Array<double,2>& V = mb.getV(); + // Compute Fn_z_i = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - V*y_{i} - U*x_{i,h}) (Normalised first order statistics) + const blitz::Array<double,1>& Fi = m_Facc[id]; + const blitz::Array<double,1>& m = mb.getUbmMean(); + const blitz::Array<double,1>& y = m_y[id]; + bob::core::array::repelem(m_Nacc[id], m_tmp_CD); + bob::math::prod(V, y, m_tmp_CD_b); // m_tmp_CD_b = V * y + m_cache_Fn_z_i = Fi - m_tmp_CD * (m + m_tmp_CD_b); // Fn_yi = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - V*y_{i}) + + const blitz::Array<double,2>& X = m_x[id]; + blitz::Range rall = blitz::Range::all(); + for (int h=0; h<X.extent(1); ++h) // Loops over the sessions + { + const blitz::Array<double,1>& Nh = stats[h]->n; // Nh = N_{i,h} (length: C) + bob::core::array::repelem(Nh, m_tmp_CD); + blitz::Array<double,1> Xh = X(rall, h); // Xh = x_{i,h} (length: ru) + bob::math::prod(U, Xh, m_tmp_CD_b); + m_cache_Fn_z_i -= m_tmp_CD * m_tmp_CD_b; + } + // Fn_z_i = sum_{sessions h}(N_{i,h}*(o_{i,h} - m - V*y_{i} - U*x_{i,h}) +} + +void bob::trainer::FABaseTrainer::updateZ_i(const size_t id) +{ + // Computes zi = Azi * D^T.Sigma^-1 * Fn_zi + blitz::Array<double,1>& z = m_z[id]; + // m_tmp_CD = m_cache_DtSigmaInv * m_cache_Fn_z_i = Dt*diag(sigma)^-1 * sum_{sessions h}(N_{i,h}*(o_{i,h} - m - V*y_{i} - U*x_{i,h}) + z = m_cache_IdPlusDProd_i * m_cache_DtSigmaInv * m_cache_Fn_z_i; +} + +void bob::trainer::FABaseTrainer::updateZ(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Precomputation + computeDtSigmaInv(m); + computeDProd(m); + // Loops over all people + for (size_t id=0; id<m_Nid; ++id) { + computeIdPlusDProd_i(id); + computeFn_z_i(m, stats[id], id); + updateZ_i(id); + } +} + +void bob::trainer::FABaseTrainer::computeAccumulatorsD( + const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats) +{ + // Initializes the cache accumulator + m_acc_D_A1 = 0.; + m_acc_D_A2 = 0.; + // Loops over all people + blitz::firstIndex i; + blitz::secondIndex j; + for (size_t id=0; id<stats.size(); ++id) { + computeIdPlusDProd_i(id); + computeFn_z_i(m, stats[id], id); + + // Needs to return values to be accumulated for estimating D + blitz::Array<double,1> z = m_z[id]; + bob::core::array::repelem(m_Nacc[id], m_tmp_CD); + m_acc_D_A1 += (m_cache_IdPlusDProd_i + z * z) * m_tmp_CD; + m_acc_D_A2 += m_cache_Fn_z_i * z; + } +} + +void bob::trainer::FABaseTrainer::updateD(blitz::Array<double,1>& d) +{ + d = m_acc_D_A2 / m_acc_D_A1; +} + + + +//////////////////////////// ISVTrainer /////////////////////////// +bob::trainer::ISVTrainer::ISVTrainer(const size_t max_iterations, const double relevance_factor): + EMTrainer<bob::machine::ISVBase, std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > > + (0.001, max_iterations, false), + m_relevance_factor(relevance_factor) +{ +} + +bob::trainer::ISVTrainer::ISVTrainer(const bob::trainer::ISVTrainer& other): + EMTrainer<bob::machine::ISVBase, std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > > + (other.m_convergence_threshold, other.m_max_iterations, + other.m_compute_likelihood), + m_relevance_factor(other.m_relevance_factor) +{ +} + +bob::trainer::ISVTrainer::~ISVTrainer() +{ +} + +bob::trainer::ISVTrainer& bob::trainer::ISVTrainer::operator= +(const bob::trainer::ISVTrainer& other) +{ + if (this != &other) + { + bob::trainer::EMTrainer<bob::machine::ISVBase, + std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > >::operator=(other); + m_relevance_factor = other.m_relevance_factor; + } + return *this; +} + +bool bob::trainer::ISVTrainer::operator==(const bob::trainer::ISVTrainer& b) const +{ + return bob::trainer::EMTrainer<bob::machine::ISVBase, + std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > >::operator==(b) && + m_relevance_factor == b.m_relevance_factor; +} + +bool bob::trainer::ISVTrainer::operator!=(const bob::trainer::ISVTrainer& b) const +{ + return !(this->operator==(b)); +} + +bool bob::trainer::ISVTrainer::is_similar_to(const bob::trainer::ISVTrainer& b, + const double r_epsilon, const double a_epsilon) const +{ + return bob::trainer::EMTrainer<bob::machine::ISVBase, + std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > >::is_similar_to(b, r_epsilon, a_epsilon) && + m_relevance_factor == b.m_relevance_factor; +} + +void bob::trainer::ISVTrainer::initialize(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + m_base_trainer.initUbmNidSumStatistics(machine.getBase(), ar); + m_base_trainer.initializeXYZ(ar); + + blitz::Array<double,2>& U = machine.updateU(); + bob::core::array::randn(*m_rng, U); + initializeD(machine); + machine.precompute(); +} + +void bob::trainer::ISVTrainer::initializeD(bob::machine::ISVBase& machine) const +{ + // D = sqrt(variance(UBM) / relevance_factor) + blitz::Array<double,1> d = machine.updateD(); + d = sqrt(machine.getBase().getUbmVariance() / m_relevance_factor); +} + +void bob::trainer::ISVTrainer::finalize(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ +} + +void bob::trainer::ISVTrainer::eStep(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + m_base_trainer.resetXYZ(); + + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateX(base, ar); + m_base_trainer.updateZ(base, ar); + m_base_trainer.computeAccumulatorsU(base, ar); +} + +void bob::trainer::ISVTrainer::mStep(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + blitz::Array<double,2>& U = machine.updateU(); + m_base_trainer.updateU(U); + machine.precompute(); +} + +double bob::trainer::ISVTrainer::computeLikelihood(bob::machine::ISVBase& machine) +{ + // TODO + return 0; +} + +void bob::trainer::ISVTrainer::enrol(bob::machine::ISVMachine& machine, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& ar, + const size_t n_iter) +{ + std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > vvec; + vvec.push_back(ar); + + const bob::machine::FABase& fb = machine.getISVBase()->getBase(); + + m_base_trainer.initUbmNidSumStatistics(fb, vvec); + m_base_trainer.initializeXYZ(vvec); + + for (size_t i=0; i<n_iter; ++i) { + m_base_trainer.updateX(fb, vvec); + m_base_trainer.updateZ(fb, vvec); + } + + const blitz::Array<double,1> z(m_base_trainer.getZ()[0]); + machine.setZ(z); +} + + + +//////////////////////////// JFATrainer /////////////////////////// +bob::trainer::JFATrainer::JFATrainer(const size_t max_iterations): + m_max_iterations(max_iterations), m_rng(new boost::mt19937()) +{ +} + +bob::trainer::JFATrainer::JFATrainer(const bob::trainer::JFATrainer& other): + m_max_iterations(other.m_max_iterations), m_rng(other.m_rng) +{ +} + +bob::trainer::JFATrainer::~JFATrainer() +{ +} + +bob::trainer::JFATrainer& bob::trainer::JFATrainer::operator= +(const bob::trainer::JFATrainer& other) +{ + if (this != &other) + { + m_max_iterations = other.m_max_iterations; + m_rng = other.m_rng; + } + return *this; +} + +bool bob::trainer::JFATrainer::operator==(const bob::trainer::JFATrainer& b) const +{ + return m_max_iterations == b.m_max_iterations && *m_rng == *(b.m_rng); +} + +bool bob::trainer::JFATrainer::operator!=(const bob::trainer::JFATrainer& b) const +{ + return !(this->operator==(b)); +} + +bool bob::trainer::JFATrainer::is_similar_to(const bob::trainer::JFATrainer& b, + const double r_epsilon, const double a_epsilon) const +{ + return m_max_iterations == b.m_max_iterations && *m_rng == *(b.m_rng); +} + +void bob::trainer::JFATrainer::initialize(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + m_base_trainer.initUbmNidSumStatistics(machine.getBase(), ar); + m_base_trainer.initializeXYZ(ar); + + blitz::Array<double,2>& U = machine.updateU(); + bob::core::array::randn(*m_rng, U); + blitz::Array<double,2>& V = machine.updateV(); + bob::core::array::randn(*m_rng, V); + blitz::Array<double,1>& D = machine.updateD(); + bob::core::array::randn(*m_rng, D); + machine.precompute(); +} + +void bob::trainer::JFATrainer::eStep1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateY(base, ar); + m_base_trainer.computeAccumulatorsV(base, ar); +} + +void bob::trainer::JFATrainer::mStep1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + blitz::Array<double,2>& V = machine.updateV(); + m_base_trainer.updateV(V); +} + +void bob::trainer::JFATrainer::finalize1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateY(base, ar); +} + + +void bob::trainer::JFATrainer::eStep2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateX(base, ar); + m_base_trainer.computeAccumulatorsU(base, ar); +} + +void bob::trainer::JFATrainer::mStep2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + blitz::Array<double,2>& U = machine.updateU(); + m_base_trainer.updateU(U); + machine.precompute(); +} + +void bob::trainer::JFATrainer::finalize2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateX(base, ar); +} + + +void bob::trainer::JFATrainer::eStep3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + const bob::machine::FABase& base = machine.getBase(); + m_base_trainer.updateZ(base, ar); + m_base_trainer.computeAccumulatorsD(base, ar); +} + +void bob::trainer::JFATrainer::mStep3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + blitz::Array<double,1>& d = machine.updateD(); + m_base_trainer.updateD(d); +} + +void bob::trainer::JFATrainer::finalize3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ +} + +void bob::trainer::JFATrainer::train_loop(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + // V subspace + for (size_t i=0; i<m_max_iterations; ++i) { + eStep1(machine, ar); + mStep1(machine, ar); + } + finalize1(machine, ar); + // U subspace + for (size_t i=0; i<m_max_iterations; ++i) { + eStep2(machine, ar); + mStep2(machine, ar); + } + finalize2(machine, ar); + // d subspace + for (size_t i=0; i<m_max_iterations; ++i) { + eStep3(machine, ar); + mStep3(machine, ar); + } + finalize3(machine, ar); +} + +void bob::trainer::JFATrainer::train(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar) +{ + initialize(machine, ar); + train_loop(machine, ar); +} + +void bob::trainer::JFATrainer::enrol(bob::machine::JFAMachine& machine, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& ar, + const size_t n_iter) +{ + std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > vvec; + vvec.push_back(ar); + + const bob::machine::FABase& fb = machine.getJFABase()->getBase(); + + m_base_trainer.initUbmNidSumStatistics(fb, vvec); + m_base_trainer.initializeXYZ(vvec); + + for (size_t i=0; i<n_iter; ++i) { + m_base_trainer.updateY(fb, vvec); + m_base_trainer.updateX(fb, vvec); + m_base_trainer.updateZ(fb, vvec); + } + + const blitz::Array<double,1> y(m_base_trainer.getY()[0]); + const blitz::Array<double,1> z(m_base_trainer.getZ()[0]); + machine.setY(y); + machine.setZ(z); +} + diff --git a/bob/learn/misc/cpp/KMeansMachine.cpp b/bob/learn/misc/cpp/KMeansMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75a87d07fea2a78f2910efb0d36147919bc3a6a5 --- /dev/null +++ b/bob/learn/misc/cpp/KMeansMachine.cpp @@ -0,0 +1,259 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/KMeansMachine.h> + +#include <bob.core/assert.h> +#include <bob.core/check.h> +#include <bob.core/array_copy.h> +#include <limits> + +bob::machine::KMeansMachine::KMeansMachine(): + m_n_means(0), m_n_inputs(0), m_means(0,0), + m_cache_means(0,0) +{ + m_means = 0; +} + +bob::machine::KMeansMachine::KMeansMachine(const size_t n_means, const size_t n_inputs): + m_n_means(n_means), m_n_inputs(n_inputs), m_means(n_means, n_inputs), + m_cache_means(n_means, n_inputs) +{ + m_means = 0; +} + +bob::machine::KMeansMachine::KMeansMachine(const blitz::Array<double,2>& means): + m_n_means(means.extent(0)), m_n_inputs(means.extent(1)), + m_means(bob::core::array::ccopy(means)), + m_cache_means(means.shape()) +{ +} + +bob::machine::KMeansMachine::KMeansMachine(const bob::machine::KMeansMachine& other): + m_n_means(other.m_n_means), m_n_inputs(other.m_n_inputs), + m_means(bob::core::array::ccopy(other.m_means)), + m_cache_means(other.m_cache_means.shape()) +{ +} + +bob::machine::KMeansMachine::KMeansMachine(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::KMeansMachine::~KMeansMachine() { } + +bob::machine::KMeansMachine& bob::machine::KMeansMachine::operator= +(const bob::machine::KMeansMachine& other) +{ + if(this != &other) + { + m_n_means = other.m_n_means; + m_n_inputs = other.m_n_inputs; + m_means.reference(bob::core::array::ccopy(other.m_means)); + m_cache_means.resize(other.m_means.shape()); + } + return *this; +} + +bool bob::machine::KMeansMachine::operator==(const bob::machine::KMeansMachine& b) const +{ + return m_n_inputs == b.m_n_inputs && m_n_means == b.m_n_means && + bob::core::array::isEqual(m_means, b.m_means); +} + +bool bob::machine::KMeansMachine::operator!=(const bob::machine::KMeansMachine& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::KMeansMachine::is_similar_to(const bob::machine::KMeansMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + return m_n_inputs == b.m_n_inputs && m_n_means == b.m_n_means && + bob::core::array::isClose(m_means, b.m_means, r_epsilon, a_epsilon); +} + +void bob::machine::KMeansMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + m_means.reference(config.readArray<double,2>("means")); + m_n_means = m_means.extent(0); + m_n_inputs = m_means.extent(1); + m_cache_means.resize(m_n_means, m_n_inputs); +} + +void bob::machine::KMeansMachine::save(bob::io::base::HDF5File& config) const +{ + config.setArray("means", m_means); +} + +void bob::machine::KMeansMachine::setMeans(const blitz::Array<double,2> &means) +{ + bob::core::array::assertSameShape(means, m_means); + m_means = means; +} + +void bob::machine::KMeansMachine::setMean(const size_t i, const blitz::Array<double,1> &mean) +{ + if(i>=m_n_means) { + boost::format m("cannot set mean with index %lu: out of bounds [0,%lu["); + m % i % m_n_means; + throw std::runtime_error(m.str()); + } + bob::core::array::assertSameDimensionLength(mean.extent(0), m_means.extent(1)); + m_means(i,blitz::Range::all()) = mean; +} + +void bob::machine::KMeansMachine::getMean(const size_t i, blitz::Array<double,1> &mean) const +{ + if(i>=m_n_means) { + boost::format m("cannot get mean with index %lu: out of bounds [0,%lu["); + m % i % m_n_means; + throw std::runtime_error(m.str()); + } + bob::core::array::assertSameDimensionLength(mean.extent(0), m_means.extent(1)); + mean = m_means(i,blitz::Range::all()); +} + +double bob::machine::KMeansMachine::getDistanceFromMean(const blitz::Array<double,1> &x, + const size_t i) const +{ + return blitz::sum(blitz::pow2(m_means(i,blitz::Range::all()) - x)); +} + +void bob::machine::KMeansMachine::getClosestMean(const blitz::Array<double,1> &x, + size_t &closest_mean, double &min_distance) const +{ + min_distance = std::numeric_limits<double>::max(); + + for(size_t i=0; i<m_n_means; ++i) { + double this_distance = getDistanceFromMean(x,i); + if(this_distance < min_distance) { + min_distance = this_distance; + closest_mean = i; + } + } +} + +double bob::machine::KMeansMachine::getMinDistance(const blitz::Array<double,1>& input) const +{ + size_t closest_mean = 0; + double min_distance = 0; + getClosestMean(input,closest_mean,min_distance); + return min_distance; +} + +void bob::machine::KMeansMachine::getVariancesAndWeightsForEachClusterInit(blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const +{ + // check arguments + bob::core::array::assertSameShape(variances, m_means); + bob::core::array::assertSameDimensionLength(weights.extent(0), m_n_means); + + // initialise output arrays + bob::core::array::assertSameShape(variances, m_means); + bob::core::array::assertSameDimensionLength(weights.extent(0), m_n_means); + variances = 0; + weights = 0; + + // initialise (temporary) mean array + m_cache_means = 0; +} + +void bob::machine::KMeansMachine::getVariancesAndWeightsForEachClusterAcc(const blitz::Array<double,2>& data, blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const +{ + // check arguments + bob::core::array::assertSameShape(variances, m_means); + bob::core::array::assertSameDimensionLength(weights.extent(0), m_n_means); + + // iterate over data + blitz::Range a = blitz::Range::all(); + for(int i=0; i<data.extent(0); ++i) { + // - get example + blitz::Array<double,1> x(data(i,a)); + + // - find closest mean + size_t closest_mean = 0; + double min_distance = 0; + getClosestMean(x,closest_mean,min_distance); + + // - accumulate stats + m_cache_means(closest_mean, blitz::Range::all()) += x; + variances(closest_mean, blitz::Range::all()) += blitz::pow2(x); + ++weights(closest_mean); + } +} + +void bob::machine::KMeansMachine::getVariancesAndWeightsForEachClusterFin(blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const +{ + // check arguments + bob::core::array::assertSameShape(variances, m_means); + bob::core::array::assertSameDimensionLength(weights.extent(0), m_n_means); + + // calculate final variances and weights + blitz::firstIndex idx1; + blitz::secondIndex idx2; + + // find means + m_cache_means = m_cache_means(idx1,idx2) / weights(idx1); + + // find variances + variances = variances(idx1,idx2) / weights(idx1); + variances -= blitz::pow2(m_cache_means); + + // find weights + weights = weights / blitz::sum(weights); +} + +void bob::machine::KMeansMachine::setCacheMeans(const blitz::Array<double,2> &cache_means) +{ + bob::core::array::assertSameShape(cache_means, m_cache_means); + m_cache_means = cache_means; +} + +void bob::machine::KMeansMachine::getVariancesAndWeightsForEachCluster(const blitz::Array<double,2>& data, blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const +{ + // initialise + getVariancesAndWeightsForEachClusterInit(variances, weights); + // accumulate + getVariancesAndWeightsForEachClusterAcc(data, variances, weights); + // merge/finalize + getVariancesAndWeightsForEachClusterFin(variances, weights); +} + +void bob::machine::KMeansMachine::forward(const blitz::Array<double,1>& input, double& output) const +{ + if(static_cast<size_t>(input.extent(0)) != m_n_inputs) { + boost::format m("machine input size (%u) does not match the size of input array (%d)"); + m % m_n_inputs % input.extent(0); + throw std::runtime_error(m.str()); + } + forward_(input,output); +} + +void bob::machine::KMeansMachine::forward_(const blitz::Array<double,1>& input, double& output) const +{ + output = getMinDistance(input); +} + +void bob::machine::KMeansMachine::resize(const size_t n_means, const size_t n_inputs) +{ + m_n_means = n_means; + m_n_inputs = n_inputs; + m_means.resizeAndPreserve(n_means, n_inputs); + m_cache_means.resizeAndPreserve(n_means, n_inputs); +} + +namespace bob{ + namespace machine{ + std::ostream& operator<<(std::ostream& os, const KMeansMachine& km) { + os << "Means = " << km.m_means << std::endl; + return os; + } + } +} diff --git a/bob/learn/misc/cpp/KMeansTrainer.cpp b/bob/learn/misc/cpp/KMeansTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d902b238bdcfa698179346afb5c8b5f228099439 --- /dev/null +++ b/bob/learn/misc/cpp/KMeansTrainer.cpp @@ -0,0 +1,239 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/KMeansTrainer.h> +#include <bob.core/array_copy.h> +#include <boost/random.hpp> + +#if BOOST_VERSION >= 104700 +#include <boost/random/discrete_distribution.hpp> +#endif + +bob::trainer::KMeansTrainer::KMeansTrainer(double convergence_threshold, + size_t max_iterations, bool compute_likelihood, InitializationMethod i_m): + bob::trainer::EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> >( + convergence_threshold, max_iterations, compute_likelihood), + m_initialization_method(i_m), + m_rng(new boost::mt19937()), m_average_min_distance(0), + m_zeroethOrderStats(0), m_firstOrderStats(0,0) +{ +} + +bob::trainer::KMeansTrainer::KMeansTrainer(const bob::trainer::KMeansTrainer& other): + bob::trainer::EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> >( + other.m_convergence_threshold, other.m_max_iterations, other.m_compute_likelihood), + m_initialization_method(other.m_initialization_method), + m_rng(other.m_rng), m_average_min_distance(other.m_average_min_distance), + m_zeroethOrderStats(bob::core::array::ccopy(other.m_zeroethOrderStats)), + m_firstOrderStats(bob::core::array::ccopy(other.m_firstOrderStats)) +{ +} + +bob::trainer::KMeansTrainer& bob::trainer::KMeansTrainer::operator= +(const bob::trainer::KMeansTrainer& other) +{ + if(this != &other) + { + EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> >::operator=(other); + m_initialization_method = other.m_initialization_method; + m_rng = other.m_rng; + m_average_min_distance = other.m_average_min_distance; + m_zeroethOrderStats.reference(bob::core::array::ccopy(other.m_zeroethOrderStats)); + m_firstOrderStats.reference(bob::core::array::ccopy(other.m_firstOrderStats)); + } + return *this; +} + +bool bob::trainer::KMeansTrainer::operator==(const bob::trainer::KMeansTrainer& b) const { + return EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> >::operator==(b) && + m_initialization_method == b.m_initialization_method && + *m_rng == *(b.m_rng) && m_average_min_distance == b.m_average_min_distance && + bob::core::array::hasSameShape(m_zeroethOrderStats, b.m_zeroethOrderStats) && + bob::core::array::hasSameShape(m_firstOrderStats, b.m_firstOrderStats) && + blitz::all(m_zeroethOrderStats == b.m_zeroethOrderStats) && + blitz::all(m_firstOrderStats == b.m_firstOrderStats); +} + +bool bob::trainer::KMeansTrainer::operator!=(const bob::trainer::KMeansTrainer& b) const { + return !(this->operator==(b)); +} + +void bob::trainer::KMeansTrainer::initialize(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>& ar) +{ + // split data into as many chunks as there are means + size_t n_data = ar.extent(0); + + // assign the i'th mean to a random example within the i'th chunk + blitz::Range a = blitz::Range::all(); +#if BOOST_VERSION >= 104700 + if(m_initialization_method == RANDOM || m_initialization_method == RANDOM_NO_DUPLICATE) // Random initialization +#endif + { + unsigned int n_chunk = n_data / kmeans.getNMeans(); + size_t n_max_trials = (size_t)n_chunk * 5; + blitz::Array<double,1> cur_mean; + if(m_initialization_method == RANDOM_NO_DUPLICATE) + cur_mean.resize(kmeans.getNInputs()); + + for(size_t i=0; i<kmeans.getNMeans(); ++i) + { + boost::uniform_int<> range(i*n_chunk, (i+1)*n_chunk-1); + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(*m_rng, range); + + // get random index within chunk + unsigned int index = die(); + + // get the example at that index + blitz::Array<double, 1> mean = ar(index,a); + + if(m_initialization_method == RANDOM_NO_DUPLICATE) + { + size_t count = 0; + while(count < n_max_trials) + { + // check that the selected sampled is different than all the previously + // selected ones + bool valid = true; + for(size_t j=0; j<i && valid; ++j) + { + kmeans.getMean(j, cur_mean); + valid = blitz::any(mean != cur_mean); + } + // if different, stop otherwise, try with another one + if(valid) + break; + else + { + index = die(); + mean = ar(index,a); + ++count; + } + } + // Initialization fails + if(count >= n_max_trials) { + boost::format m("initialization failure: surpassed the maximum number of trials (%u)"); + m % n_max_trials; + throw std::runtime_error(m.str()); + } + } + + // set the mean + kmeans.setMean(i, mean); + } + } +#if BOOST_VERSION >= 104700 + else // K-Means++ + { + // 1.a. Selects one sample randomly + boost::uniform_int<> range(0, n_data-1); + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(*m_rng, range); + // Gets the example at a random index + blitz::Array<double,1> mean = ar(die(),a); + kmeans.setMean(0, mean); + + // 1.b. Loops, computes probability distribution and select samples accordingly + blitz::Array<double,1> weights(n_data); + for(size_t m=1; m<kmeans.getNMeans(); ++m) + { + // For each sample, puts the distance to the closest mean in the weight vector + for(size_t s=0; s<n_data; ++s) + { + blitz::Array<double,1> s_cur = ar(s,a); + double& w_cur = weights(s); + // Initializes with the distance to first mean + w_cur = kmeans.getDistanceFromMean(s_cur, 0); + // Loops over the remaining mean and update the mean distance if required + for(size_t i=1; i<m; ++i) + w_cur = std::min(w_cur, kmeans.getDistanceFromMean(s_cur, i)); + } + // Square and normalize the weights vectors such that + // \f$weights[x] = D(x)^{2} \sum_{y} D(y)^{2}\f$ + weights = blitz::pow2(weights); + weights /= blitz::sum(weights); + + // Takes a sample according to the weights distribution + // Blitz iterators is fine as the weights array should be C-style contiguous + bob::core::array::assertCContiguous(weights); + boost::random::discrete_distribution<> die2(weights.begin(), weights.end()); + blitz::Array<double,1> new_mean = ar(die2(*m_rng),a); + kmeans.setMean(m, new_mean); + } + } +#endif + // Resize the accumulator + m_zeroethOrderStats.resize(kmeans.getNMeans()); + m_firstOrderStats.resize(kmeans.getNMeans(), kmeans.getNInputs()); +} + +void bob::trainer::KMeansTrainer::eStep(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>& ar) +{ + // initialise the accumulators + resetAccumulators(kmeans); + + // iterate over data samples + blitz::Range a = blitz::Range::all(); + for(int i=0; i<ar.extent(0); ++i) { + // get example + blitz::Array<double, 1> x(ar(i,a)); + + // find closest mean, and distance from that mean + size_t closest_mean = 0; + double min_distance = 0; + kmeans.getClosestMean(x,closest_mean,min_distance); + + // accumulate the stats + m_average_min_distance += min_distance; + ++m_zeroethOrderStats(closest_mean); + m_firstOrderStats(closest_mean,blitz::Range::all()) += x; + } + m_average_min_distance /= static_cast<double>(ar.extent(0)); +} + +void bob::trainer::KMeansTrainer::mStep(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>&) +{ + blitz::Array<double,2>& means = kmeans.updateMeans(); + for(size_t i=0; i<kmeans.getNMeans(); ++i) + { + means(i,blitz::Range::all()) = + m_firstOrderStats(i,blitz::Range::all()) / m_zeroethOrderStats(i); + } +} + +double bob::trainer::KMeansTrainer::computeLikelihood(bob::machine::KMeansMachine& kmeans) +{ + return m_average_min_distance; +} + +void bob::trainer::KMeansTrainer::finalize(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>& ar) +{ +} + +bool bob::trainer::KMeansTrainer::resetAccumulators(bob::machine::KMeansMachine& kmeans) +{ + m_average_min_distance = 0; + m_zeroethOrderStats = 0; + m_firstOrderStats = 0; + return true; +} + +void bob::trainer::KMeansTrainer::setZeroethOrderStats(const blitz::Array<double,1>& zeroethOrderStats) +{ + bob::core::array::assertSameShape(m_zeroethOrderStats, zeroethOrderStats); + m_zeroethOrderStats = zeroethOrderStats; +} + +void bob::trainer::KMeansTrainer::setFirstOrderStats(const blitz::Array<double,2>& firstOrderStats) +{ + bob::core::array::assertSameShape(m_firstOrderStats, firstOrderStats); + m_firstOrderStats = firstOrderStats; +} + diff --git a/bob/learn/misc/cpp/LinearScoring.cpp b/bob/learn/misc/cpp/LinearScoring.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6914488e60d396907bb87915a2934bf4f5f06fe1 --- /dev/null +++ b/bob/learn/misc/cpp/LinearScoring.cpp @@ -0,0 +1,173 @@ +/** + * @date Wed Jul 13 16:00:04 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ +#include <bob.learn.misc/LinearScoring.h> +#include <bob.math/linear.h> + +namespace bob { namespace machine { + +namespace detail { + + void linearScoring(const std::vector<blitz::Array<double,1> >& models, + const blitz::Array<double,1>& ubm_mean, + const blitz::Array<double,1>& ubm_variance, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const std::vector<blitz::Array<double,1> >* test_channelOffset, + const bool frame_length_normalisation, + blitz::Array<double,2>& scores) + { + int C = test_stats[0]->sumPx.extent(0); + int D = test_stats[0]->sumPx.extent(1); + int CD = C*D; + int Tt = test_stats.size(); + int Tm = models.size(); + + // Check output size + bob::core::array::assertSameDimensionLength(scores.extent(0), models.size()); + bob::core::array::assertSameDimensionLength(scores.extent(1), test_stats.size()); + + blitz::Array<double,2> A(Tm, CD); + blitz::Array<double,2> B(CD, Tt); + + // 1) Compute A + for(int t=0; t<Tm; ++t) { + blitz::Array<double, 1> tmp = A(t, blitz::Range::all()); + tmp = (models[t] - ubm_mean) / ubm_variance; + } + + // 2) Compute B + if(test_channelOffset == 0) { + for(int t=0; t<Tt; ++t) + for(int s=0; s<CD; ++s) + B(s, t) = test_stats[t]->sumPx(s/D, s%D) - (ubm_mean(s) * test_stats[t]->n(s/D)); + } + else { + bob::core::array::assertSameDimensionLength((*test_channelOffset).size(), Tt); + + for(int t=0; t<Tt; ++t) { + bob::core::array::assertSameDimensionLength((*test_channelOffset)[t].extent(0), CD); + for(int s=0; s<CD; ++s) + B(s, t) = test_stats[t]->sumPx(s/D, s%D) - (test_stats[t]->n(s/D) * (ubm_mean(s) + (*test_channelOffset)[t](s))); + } + } + + // Apply the normalisation if needed + if(frame_length_normalisation) { + for(int t=0; t<Tt; ++t) { + double sum_N = test_stats[t]->T; + blitz::Array<double, 1> v_t = B(blitz::Range::all(),t); + + if (sum_N <= std::numeric_limits<double>::epsilon() && sum_N >= -std::numeric_limits<double>::epsilon()) + v_t = 0; + else + v_t /= sum_N; + } + } + + // 3) Compute LLR + bob::math::prod(A, B, scores); + } +} + + +void linearScoring(const std::vector<blitz::Array<double,1> >& models, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const std::vector<blitz::Array<double,1> >& test_channelOffset, + const bool frame_length_normalisation, + blitz::Array<double, 2>& scores) +{ + detail::linearScoring(models, ubm_mean, ubm_variance, test_stats, &test_channelOffset, frame_length_normalisation, scores); +} + +void linearScoring(const std::vector<blitz::Array<double,1> >& models, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const bool frame_length_normalisation, + blitz::Array<double, 2>& scores) +{ + detail::linearScoring(models, ubm_mean, ubm_variance, test_stats, 0, frame_length_normalisation, scores); +} + +void linearScoring(const std::vector<boost::shared_ptr<const bob::machine::GMMMachine> >& models, + const bob::machine::GMMMachine& ubm, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const bool frame_length_normalisation, + blitz::Array<double, 2>& scores) +{ + int C = test_stats[0]->sumPx.extent(0); + int D = test_stats[0]->sumPx.extent(1); + int CD = C*D; + std::vector<blitz::Array<double,1> > models_b; + // Allocate and get the mean supervector + for(size_t i=0; i<models.size(); ++i) { + blitz::Array<double,1> mod(CD); + models[i]->getMeanSupervector(mod); + models_b.push_back(mod); + } + const blitz::Array<double,1>& ubm_mean = ubm.getMeanSupervector(); + const blitz::Array<double,1>& ubm_variance = ubm.getVarianceSupervector(); + detail::linearScoring(models_b, ubm_mean, ubm_variance, test_stats, 0, frame_length_normalisation, scores); +} + +void linearScoring(const std::vector<boost::shared_ptr<const bob::machine::GMMMachine> >& models, + const bob::machine::GMMMachine& ubm, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const std::vector<blitz::Array<double,1> >& test_channelOffset, + const bool frame_length_normalisation, + blitz::Array<double, 2>& scores) +{ + int C = test_stats[0]->sumPx.extent(0); + int D = test_stats[0]->sumPx.extent(1); + int CD = C*D; + std::vector<blitz::Array<double,1> > models_b; + // Allocate and get the mean supervector + for(size_t i=0; i<models.size(); ++i) { + blitz::Array<double,1> mod(CD); + models[i]->getMeanSupervector(mod); + models_b.push_back(mod); + } + const blitz::Array<double,1>& ubm_mean = ubm.getMeanSupervector(); + const blitz::Array<double,1>& ubm_variance = ubm.getVarianceSupervector(); + detail::linearScoring(models_b, ubm_mean, ubm_variance, test_stats, &test_channelOffset, frame_length_normalisation, scores); +} + + + +double linearScoring(const blitz::Array<double,1>& models, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const bob::machine::GMMStats& test_stats, + const blitz::Array<double,1>& test_channelOffset, + const bool frame_length_normalisation) +{ + int C = test_stats.sumPx.extent(0); + int D = test_stats.sumPx.extent(1); + int CD = C*D; + + + blitz::Array<double,1> A(CD); + blitz::Array<double,1> B(CD); + + // 1) Compute A + A = (models - ubm_mean) / ubm_variance; + + // 2) Compute B + for (int s=0; s<CD; ++s) + B(s) = test_stats.sumPx(s/D, s%D) - (test_stats.n(s/D) * (ubm_mean(s) + test_channelOffset(s))); + + // Apply the normalisation if needed + if (frame_length_normalisation) { + double sum_N = test_stats.T; + if (sum_N == 0) + B = 0; + else + B /= sum_N; + } + + return blitz::sum(A * B); +} + +}} diff --git a/bob/learn/misc/cpp/MAP_GMMTrainer.cpp b/bob/learn/misc/cpp/MAP_GMMTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f48dd4dcae56fab39e10e43eb7305442940b0c88 --- /dev/null +++ b/bob/learn/misc/cpp/MAP_GMMTrainer.cpp @@ -0,0 +1,185 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/MAP_GMMTrainer.h> +#include <bob.core/check.h> + +bob::trainer::MAP_GMMTrainer::MAP_GMMTrainer(const double relevance_factor, + const bool update_means, const bool update_variances, + const bool update_weights, const double mean_var_update_responsibilities_threshold): + GMMTrainer(update_means, update_variances, update_weights, mean_var_update_responsibilities_threshold), + m_relevance_factor(relevance_factor), + m_prior_gmm(boost::shared_ptr<bob::machine::GMMMachine>()), + m_T3_alpha(0.), m_T3_adaptation(false) +{ +} + +bob::trainer::MAP_GMMTrainer::MAP_GMMTrainer(const bob::trainer::MAP_GMMTrainer& b): + bob::trainer::GMMTrainer(b), + m_relevance_factor(b.m_relevance_factor), + m_prior_gmm(b.m_prior_gmm), + m_T3_alpha(b.m_T3_alpha), m_T3_adaptation(b.m_T3_adaptation) +{ +} + +bob::trainer::MAP_GMMTrainer::~MAP_GMMTrainer() +{ +} + +void bob::trainer::MAP_GMMTrainer::initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + // Check that the prior GMM has been specified + if (!m_prior_gmm) + throw std::runtime_error("MAP_GMMTrainer: Prior GMM distribution has not been set"); + + // Allocate memory for the sufficient statistics and initialise + bob::trainer::GMMTrainer::initialize(gmm, data); + + const size_t n_gaussians = gmm.getNGaussians(); + // TODO: check size? + gmm.setWeights(m_prior_gmm->getWeights()); + for(size_t i=0; i<n_gaussians; ++i) + { + gmm.updateGaussian(i)->updateMean() = m_prior_gmm->getGaussian(i)->getMean(); + gmm.updateGaussian(i)->updateVariance() = m_prior_gmm->getGaussian(i)->getVariance(); + gmm.updateGaussian(i)->applyVarianceThresholds(); + } + // Initializes cache + m_cache_alpha.resize(n_gaussians); + m_cache_ml_weights.resize(n_gaussians); +} + +bool bob::trainer::MAP_GMMTrainer::setPriorGMM(boost::shared_ptr<bob::machine::GMMMachine> prior_gmm) +{ + if (!prior_gmm) return false; + m_prior_gmm = prior_gmm; + return true; +} + +void bob::trainer::MAP_GMMTrainer::mStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + // Read options and variables + double n_gaussians = gmm.getNGaussians(); + + // Check that the prior GMM has been specified + if (!m_prior_gmm) + throw std::runtime_error("MAP_GMMTrainer: Prior GMM distribution has not been set"); + + blitz::firstIndex i; + blitz::secondIndex j; + + // Calculate the "data-dependent adaptation coefficient", alpha_i + // TODO: check if required // m_cache_alpha.resize(n_gaussians); + if (m_T3_adaptation) + m_cache_alpha = m_T3_alpha; + else + m_cache_alpha = m_ss.n(i) / (m_ss.n(i) + m_relevance_factor); + + // - Update weights if requested + // Equation 11 of Reynolds et al., "Speaker Verification Using Adapted Gaussian Mixture Models", Digital Signal Processing, 2000 + if (m_update_weights) { + // Calculate the maximum likelihood weights + m_cache_ml_weights = m_ss.n / static_cast<double>(m_ss.T); //cast req. for linux/32-bits & osx + + // Get the prior weights + const blitz::Array<double,1>& prior_weights = m_prior_gmm->getWeights(); + blitz::Array<double,1>& new_weights = gmm.updateWeights(); + + // Calculate the new weights + new_weights = m_cache_alpha * m_cache_ml_weights + (1-m_cache_alpha) * prior_weights; + + // Apply the scale factor, gamma, to ensure the new weights sum to unity + double gamma = blitz::sum(new_weights); + new_weights /= gamma; + + // Recompute the log weights in the cache of the GMMMachine + gmm.recomputeLogWeights(); + } + + // Update GMM parameters + // - Update means if requested + // Equation 12 of Reynolds et al., "Speaker Verification Using Adapted Gaussian Mixture Models", Digital Signal Processing, 2000 + if (m_update_means) { + // Calculate new means + for (size_t i=0; i<n_gaussians; ++i) { + const blitz::Array<double,1>& prior_means = m_prior_gmm->getGaussian(i)->getMean(); + blitz::Array<double,1>& means = gmm.updateGaussian(i)->updateMean(); + if (m_ss.n(i) < m_mean_var_update_responsibilities_threshold) { + means = prior_means; + } + else { + // Use the maximum likelihood means + means = m_cache_alpha(i) * (m_ss.sumPx(i,blitz::Range::all()) / m_ss.n(i)) + (1-m_cache_alpha(i)) * prior_means; + } + } + } + + // - Update variance if requested + // Equation 13 of Reynolds et al., "Speaker Verification Using Adapted Gaussian Mixture Models", Digital Signal Processing, 2000 + if (m_update_variances) { + // Calculate new variances (equation 13) + for (size_t i=0; i<n_gaussians; ++i) { + const blitz::Array<double,1>& prior_means = m_prior_gmm->getGaussian(i)->getMean(); + blitz::Array<double,1>& means = gmm.updateGaussian(i)->updateMean(); + const blitz::Array<double,1>& prior_variances = m_prior_gmm->getGaussian(i)->getVariance(); + blitz::Array<double,1>& variances = gmm.updateGaussian(i)->updateVariance(); + if (m_ss.n(i) < m_mean_var_update_responsibilities_threshold) { + variances = (prior_variances + prior_means) - blitz::pow2(means); + } + else { + variances = m_cache_alpha(i) * m_ss.sumPxx(i,blitz::Range::all()) / m_ss.n(i) + (1-m_cache_alpha(i)) * (prior_variances + prior_means) - blitz::pow2(means); + } + gmm.updateGaussian(i)->applyVarianceThresholds(); + } + } +} + +bob::trainer::MAP_GMMTrainer& bob::trainer::MAP_GMMTrainer::operator= + (const bob::trainer::MAP_GMMTrainer &other) +{ + if (this != &other) + { + bob::trainer::GMMTrainer::operator=(other); + m_relevance_factor = other.m_relevance_factor; + m_prior_gmm = other.m_prior_gmm; + m_T3_alpha = other.m_T3_alpha; + m_T3_adaptation = other.m_T3_adaptation; + m_cache_alpha.resize(other.m_cache_alpha.extent(0)); + m_cache_ml_weights.resize(other.m_cache_ml_weights.extent(0)); + } + return *this; +} + +bool bob::trainer::MAP_GMMTrainer::operator== + (const bob::trainer::MAP_GMMTrainer &other) const +{ + return bob::trainer::GMMTrainer::operator==(other) && + m_relevance_factor == other.m_relevance_factor && + m_prior_gmm == other.m_prior_gmm && + m_T3_alpha == other.m_T3_alpha && + m_T3_adaptation == other.m_T3_adaptation; +} + +bool bob::trainer::MAP_GMMTrainer::operator!= + (const bob::trainer::MAP_GMMTrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::MAP_GMMTrainer::is_similar_to + (const bob::trainer::MAP_GMMTrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::GMMTrainer::is_similar_to(other, r_epsilon, a_epsilon) && + bob::core::isClose(m_relevance_factor, other.m_relevance_factor, r_epsilon, a_epsilon) && + m_prior_gmm == other.m_prior_gmm && + bob::core::isClose(m_T3_alpha, other.m_T3_alpha, r_epsilon, a_epsilon) && + m_T3_adaptation == other.m_T3_adaptation; +} + diff --git a/bob/learn/misc/cpp/ML_GMMTrainer.cpp b/bob/learn/misc/cpp/ML_GMMTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d595d1a4f14fcd43998806e8d4745b9e1df5a125 --- /dev/null +++ b/bob/learn/misc/cpp/ML_GMMTrainer.cpp @@ -0,0 +1,111 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/ML_GMMTrainer.h> +#include <algorithm> + +bob::trainer::ML_GMMTrainer::ML_GMMTrainer(const bool update_means, + const bool update_variances, const bool update_weights, + const double mean_var_update_responsibilities_threshold): + bob::trainer::GMMTrainer(update_means, update_variances, update_weights, + mean_var_update_responsibilities_threshold) +{ +} + +bob::trainer::ML_GMMTrainer::ML_GMMTrainer(const bob::trainer::ML_GMMTrainer& b): + bob::trainer::GMMTrainer(b) +{ +} + +bob::trainer::ML_GMMTrainer::~ML_GMMTrainer() +{ +} + +void bob::trainer::ML_GMMTrainer::initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + bob::trainer::GMMTrainer::initialize(gmm, data); + // Allocate cache + size_t n_gaussians = gmm.getNGaussians(); + m_cache_ss_n_thresholded.resize(n_gaussians); +} + + +void bob::trainer::ML_GMMTrainer::mStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data) +{ + // Read options and variables + const size_t n_gaussians = gmm.getNGaussians(); + + // - Update weights if requested + // Equation 9.26 of Bishop, "Pattern recognition and machine learning", 2006 + if (m_update_weights) { + blitz::Array<double,1>& weights = gmm.updateWeights(); + weights = m_ss.n / static_cast<double>(m_ss.T); //cast req. for linux/32-bits & osx + // Recompute the log weights in the cache of the GMMMachine + gmm.recomputeLogWeights(); + } + + // Generate a thresholded version of m_ss.n + for(size_t i=0; i<n_gaussians; ++i) + m_cache_ss_n_thresholded(i) = std::max(m_ss.n(i), m_mean_var_update_responsibilities_threshold); + + // Update GMM parameters using the sufficient statistics (m_ss) + // - Update means if requested + // Equation 9.24 of Bishop, "Pattern recognition and machine learning", 2006 + if (m_update_means) { + for(size_t i=0; i<n_gaussians; ++i) { + blitz::Array<double,1>& means = gmm.updateGaussian(i)->updateMean(); + means = m_ss.sumPx(i, blitz::Range::all()) / m_cache_ss_n_thresholded(i); + } + } + + // - Update variance if requested + // See Equation 9.25 of Bishop, "Pattern recognition and machine learning", 2006 + // ...but we use the "computational formula for the variance", i.e. + // var = 1/n * sum (P(x-mean)(x-mean)) + // = 1/n * sum (Pxx) - mean^2 + if (m_update_variances) { + for(size_t i=0; i<n_gaussians; ++i) { + const blitz::Array<double,1>& means = gmm.getGaussian(i)->getMean(); + blitz::Array<double,1>& variances = gmm.updateGaussian(i)->updateVariance(); + variances = m_ss.sumPxx(i, blitz::Range::all()) / m_cache_ss_n_thresholded(i) - blitz::pow2(means); + gmm.updateGaussian(i)->applyVarianceThresholds(); + } + } +} + +bob::trainer::ML_GMMTrainer& bob::trainer::ML_GMMTrainer::operator= + (const bob::trainer::ML_GMMTrainer &other) +{ + if (this != &other) + { + bob::trainer::GMMTrainer::operator=(other); + m_cache_ss_n_thresholded.resize(other.m_cache_ss_n_thresholded.extent(0)); + } + return *this; +} + +bool bob::trainer::ML_GMMTrainer::operator== + (const bob::trainer::ML_GMMTrainer &other) const +{ + return bob::trainer::GMMTrainer::operator==(other); +} + +bool bob::trainer::ML_GMMTrainer::operator!= + (const bob::trainer::ML_GMMTrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::ML_GMMTrainer::is_similar_to + (const bob::trainer::ML_GMMTrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::GMMTrainer::is_similar_to(other, r_epsilon, a_epsilon); +} + diff --git a/bob/learn/misc/cpp/PLDAMachine.cpp b/bob/learn/misc/cpp/PLDAMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e3adafb48a1214edd7ab10ddbdc42a7fb5431b8f --- /dev/null +++ b/bob/learn/misc/cpp/PLDAMachine.cpp @@ -0,0 +1,962 @@ +/** + * @date Fri Oct 14 18:07:56 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Machines that implements the PLDA model + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.core/assert.h> +#include <bob.core/check.h> +#include <bob.core/array_copy.h> +#include <bob.learn.misc/PLDAMachine.h> +#include <bob.math/linear.h> +#include <bob.math/det.h> +#include <bob.math/inv.h> + +#include <cmath> +#include <boost/lexical_cast.hpp> +#include <string> + +bob::machine::PLDABase::PLDABase(): + m_variance_threshold(0.) +{ + resizeNoInit(0, 0, 0); +} + +bob::machine::PLDABase::PLDABase(const size_t dim_d, const size_t dim_f, + const size_t dim_g, const double variance_threshold): + m_variance_threshold(variance_threshold) +{ + resize(dim_d, dim_f, dim_g); +} + + +bob::machine::PLDABase::PLDABase(const bob::machine::PLDABase& other): + m_dim_d(other.m_dim_d), + m_dim_f(other.m_dim_f), + m_dim_g(other.m_dim_g), + m_F(bob::core::array::ccopy(other.m_F)), + m_G(bob::core::array::ccopy(other.m_G)), + m_sigma(bob::core::array::ccopy(other.m_sigma)), + m_mu(bob::core::array::ccopy(other.m_mu)), + m_variance_threshold(other.m_variance_threshold), + m_cache_isigma(bob::core::array::ccopy(other.m_cache_isigma)), + m_cache_alpha(bob::core::array::ccopy(other.m_cache_alpha)), + m_cache_beta(bob::core::array::ccopy(other.m_cache_beta)), + m_cache_gamma(), + m_cache_Ft_beta(bob::core::array::ccopy(other.m_cache_Ft_beta)), + m_cache_Gt_isigma(bob::core::array::ccopy(other.m_cache_Gt_isigma)), + m_cache_logdet_alpha(other.m_cache_logdet_alpha), + m_cache_logdet_sigma(other.m_cache_logdet_sigma), + m_cache_loglike_constterm(other.m_cache_loglike_constterm) +{ + bob::core::array::ccopy(other.m_cache_gamma, m_cache_gamma); + resizeTmp(); +} + +bob::machine::PLDABase::PLDABase(bob::io::base::HDF5File& config) { + load(config); +} + +bob::machine::PLDABase::~PLDABase() { +} + +bob::machine::PLDABase& bob::machine::PLDABase::operator= + (const bob::machine::PLDABase& other) +{ + if (this != &other) + { + m_dim_d = other.m_dim_d; + m_dim_f = other.m_dim_f; + m_dim_g = other.m_dim_g; + m_F.reference(bob::core::array::ccopy(other.m_F)); + m_G.reference(bob::core::array::ccopy(other.m_G)); + m_sigma.reference(bob::core::array::ccopy(other.m_sigma)); + m_mu.reference(bob::core::array::ccopy(other.m_mu)); + m_variance_threshold = other.m_variance_threshold; + m_cache_isigma.reference(bob::core::array::ccopy(other.m_cache_isigma)); + m_cache_alpha.reference(bob::core::array::ccopy(other.m_cache_alpha)); + m_cache_beta.reference(bob::core::array::ccopy(other.m_cache_beta)); + bob::core::array::ccopy(other.m_cache_gamma, m_cache_gamma); + m_cache_Ft_beta.reference(bob::core::array::ccopy(other.m_cache_Ft_beta)); + m_cache_Gt_isigma.reference(bob::core::array::ccopy(other.m_cache_Gt_isigma)); + m_cache_logdet_alpha = other.m_cache_logdet_alpha; + m_cache_logdet_sigma = other.m_cache_logdet_sigma; + m_cache_loglike_constterm = other.m_cache_loglike_constterm; + resizeTmp(); + } + return *this; +} + +bool bob::machine::PLDABase::operator== + (const bob::machine::PLDABase& b) const +{ + if (!(m_dim_d == b.m_dim_d && m_dim_f == b.m_dim_f && + m_dim_g == b.m_dim_g && + bob::core::array::isEqual(m_F, b.m_F) && + bob::core::array::isEqual(m_G, b.m_G) && + bob::core::array::isEqual(m_sigma, b.m_sigma) && + bob::core::array::isEqual(m_mu, b.m_mu) && + m_variance_threshold == b.m_variance_threshold && + bob::core::array::isEqual(m_cache_isigma, b.m_cache_isigma) && + bob::core::array::isEqual(m_cache_alpha, b.m_cache_alpha) && + bob::core::array::isEqual(m_cache_beta, b.m_cache_beta) && + bob::core::array::isEqual(m_cache_gamma, b.m_cache_gamma) && + bob::core::array::isEqual(m_cache_Ft_beta, b.m_cache_Ft_beta) && + bob::core::array::isEqual(m_cache_Gt_isigma, b.m_cache_Gt_isigma) && + m_cache_logdet_alpha == b.m_cache_logdet_alpha && + m_cache_logdet_sigma == b.m_cache_logdet_sigma)) + return false; + + // m_cache_loglike_constterm + if (this->m_cache_loglike_constterm.size() != b.m_cache_loglike_constterm.size()) + return false; // differing sizes, they are not the same + std::map<size_t, double>::const_iterator i, j; + for (i = this->m_cache_loglike_constterm.begin(), j = b.m_cache_loglike_constterm.begin(); + i != this->m_cache_loglike_constterm.end(); ++i, ++j) + { + if (i->first != j->first || i->second != j->second) + return false; + } + + return true; +} + +bool bob::machine::PLDABase::operator!= + (const bob::machine::PLDABase& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::PLDABase::is_similar_to(const bob::machine::PLDABase& b, + const double r_epsilon, const double a_epsilon) const +{ + return (m_dim_d == b.m_dim_d && m_dim_f == b.m_dim_f && + m_dim_g == b.m_dim_g && + bob::core::array::isClose(m_F, b.m_F, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_G, b.m_G, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_sigma, b.m_sigma, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_mu, b.m_mu, r_epsilon, a_epsilon) && + bob::core::isClose(m_variance_threshold, b.m_variance_threshold, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_isigma, b.m_cache_isigma, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_alpha, b.m_cache_alpha, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_beta, b.m_cache_beta, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_gamma, b.m_cache_gamma, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_Ft_beta, b.m_cache_Ft_beta, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_Gt_isigma, b.m_cache_Gt_isigma, r_epsilon, a_epsilon) && + bob::core::isClose(m_cache_logdet_alpha, b.m_cache_logdet_alpha, r_epsilon, a_epsilon) && + bob::core::isClose(m_cache_logdet_sigma, b.m_cache_logdet_sigma, r_epsilon, a_epsilon) && + bob::core::isClose(m_cache_loglike_constterm, b.m_cache_loglike_constterm)); +} + +void bob::machine::PLDABase::load(bob::io::base::HDF5File& config) +{ + if (!config.contains("dim_d")) + { + // Then the model was saved using bob < 1.2.0 + //reads all data directly into the member variables + m_F.reference(config.readArray<double,2>("F")); + m_G.reference(config.readArray<double,2>("G")); + m_dim_d = m_F.extent(0); + m_dim_f = m_F.extent(1); + m_dim_g = m_G.extent(1); + m_sigma.reference(config.readArray<double,1>("sigma")); + m_mu.reference(config.readArray<double,1>("mu")); + m_cache_isigma.resize(m_dim_d); + precomputeISigma(); + m_variance_threshold = 0.; + m_cache_alpha.reference(config.readArray<double,2>("alpha")); + m_cache_beta.reference(config.readArray<double,2>("beta")); + // gamma and log like constant term (a-dependent terms) + if (config.contains("a_indices")) + { + blitz::Array<uint32_t, 1> a_indices; + a_indices.reference(config.readArray<uint32_t,1>("a_indices")); + for (int i=0; i<a_indices.extent(0); ++i) + { + std::string str1 = "gamma_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_gamma[a_indices(i)].reference(config.readArray<double,2>(str1)); + std::string str2 = "loglikeconstterm_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_loglike_constterm[a_indices(i)] = config.read<double>(str2); + } + } + m_cache_Ft_beta.reference(config.readArray<double,2>("Ft_beta")); + m_cache_Gt_isigma.reference(config.readArray<double,2>("Gt_isigma")); + m_cache_logdet_alpha = config.read<double>("logdet_alpha"); + m_cache_logdet_sigma = config.read<double>("logdet_sigma"); + } + else + { + // Then the model was saved using bob >= 1.2.0 + //reads all data directly into the member variables + m_F.reference(config.readArray<double,2>("F")); + m_G.reference(config.readArray<double,2>("G")); + // Conditional because previous versions had not these variables + m_dim_d = config.read<uint64_t>("dim_d"); + m_dim_f = config.read<uint64_t>("dim_f"); + m_dim_g = config.read<uint64_t>("dim_g"); + m_sigma.reference(config.readArray<double,1>("sigma")); + m_mu.reference(config.readArray<double,1>("mu")); + m_cache_isigma.resize(m_dim_d); + precomputeISigma(); + if (config.contains("variance_threshold")) + m_variance_threshold = config.read<double>("variance_threshold"); + else if (config.contains("variance_thresholds")) // In case 1.2.0 alpha/beta version has been used + { + blitz::Array<double,1> tmp; + tmp.reference(config.readArray<double,1>("variance_thresholds")); + m_variance_threshold = tmp(0); + } + m_cache_alpha.reference(config.readArray<double,2>("alpha")); + m_cache_beta.reference(config.readArray<double,2>("beta")); + // gamma's (a-dependent terms) + if(config.contains("a_indices_gamma")) + { + blitz::Array<uint32_t, 1> a_indices; + a_indices.reference(config.readArray<uint32_t,1>("a_indices_gamma")); + for(int i=0; i<a_indices.extent(0); ++i) + { + std::string str = "gamma_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_gamma[a_indices(i)].reference(config.readArray<double,2>(str)); + } + } + // log likelihood constant term's (a-dependent terms) + if(config.contains("a_indices_loglikeconstterm")) + { + blitz::Array<uint32_t, 1> a_indices; + a_indices.reference(config.readArray<uint32_t,1>("a_indices_loglikeconstterm")); + for(int i=0; i<a_indices.extent(0); ++i) + { + std::string str = "loglikeconstterm_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_loglike_constterm[a_indices(i)] = config.read<double>(str); + } + } + m_cache_Ft_beta.reference(config.readArray<double,2>("Ft_beta")); + m_cache_Gt_isigma.reference(config.readArray<double,2>("Gt_isigma")); + m_cache_logdet_alpha = config.read<double>("logdet_alpha"); + m_cache_logdet_sigma = config.read<double>("logdet_sigma"); + } + resizeTmp(); +} + +void bob::machine::PLDABase::save(bob::io::base::HDF5File& config) const +{ + config.set("dim_d", (uint64_t)m_dim_d); + config.set("dim_f", (uint64_t)m_dim_f); + config.set("dim_g", (uint64_t)m_dim_g); + config.setArray("F", m_F); + config.setArray("G", m_G); + config.setArray("sigma", m_sigma); + config.setArray("mu", m_mu); + config.set("variance_threshold", m_variance_threshold); + config.setArray("alpha", m_cache_alpha); + config.setArray("beta", m_cache_beta); + // gamma's + if(m_cache_gamma.size() > 0) + { + blitz::Array<uint32_t, 1> a_indices(m_cache_gamma.size()); + int i = 0; + for(std::map<size_t,blitz::Array<double,2> >::const_iterator + it=m_cache_gamma.begin(); it!=m_cache_gamma.end(); ++it) + { + a_indices(i) = it->first; + std::string str = "gamma_" + boost::lexical_cast<std::string>(it->first); + config.setArray(str, it->second); + ++i; + } + config.setArray("a_indices_gamma", a_indices); + } + // log likelihood constant terms + if(m_cache_loglike_constterm.size() > 0) + { + blitz::Array<uint32_t, 1> a_indices(m_cache_loglike_constterm.size()); + int i = 0; + for(std::map<size_t,double>::const_iterator + it=m_cache_loglike_constterm.begin(); it!=m_cache_loglike_constterm.end(); ++it) + { + a_indices(i) = it->first; + std::string str = "loglikeconstterm_" + boost::lexical_cast<std::string>(it->first); + config.set(str, it->second); + ++i; + } + config.setArray("a_indices_loglikeconstterm", a_indices); + } + + config.setArray("Ft_beta", m_cache_Ft_beta); + config.setArray("Gt_isigma", m_cache_Gt_isigma); + config.set("logdet_alpha", m_cache_logdet_alpha); + config.set("logdet_sigma", m_cache_logdet_sigma); +} + +void bob::machine::PLDABase::resizeNoInit(const size_t dim_d, const size_t dim_f, + const size_t dim_g) +{ + m_dim_d = dim_d; + m_dim_f = dim_f; + m_dim_g = dim_g; + m_F.resize(dim_d, dim_f); + m_G.resize(dim_d, dim_g); + m_sigma.resize(dim_d); + m_mu.resize(dim_d); + m_cache_alpha.resize(dim_g, dim_g); + m_cache_beta.resize(dim_d, dim_d); + m_cache_Ft_beta.resize(dim_f, dim_d); + m_cache_Gt_isigma.resize(dim_g, dim_d); + m_cache_gamma.clear(); + m_cache_isigma.resize(dim_d); + m_cache_loglike_constterm.clear(); + resizeTmp(); +} + +void bob::machine::PLDABase::resizeTmp() +{ + m_tmp_d_1.resize(m_dim_d); + m_tmp_d_2.resize(m_dim_d); + m_tmp_d_ng_1.resize(m_dim_d, m_dim_g); + m_tmp_nf_nf_1.resize(m_dim_f, m_dim_f); + m_tmp_ng_ng_1.resize(m_dim_g, m_dim_g); +} + +void bob::machine::PLDABase::resize(const size_t dim_d, const size_t dim_f, + const size_t dim_g) +{ + resizeNoInit(dim_d, dim_f, dim_g); + initMuFGSigma(); +} + +void bob::machine::PLDABase::setF(const blitz::Array<double,2>& F) +{ + bob::core::array::assertSameShape(F, m_F); + m_F.reference(bob::core::array::ccopy(F)); + // Precomputes useful matrices + precompute(); +} + +void bob::machine::PLDABase::setG(const blitz::Array<double,2>& G) +{ + bob::core::array::assertSameShape(G, m_G); + m_G.reference(bob::core::array::ccopy(G)); + // Precomputes useful matrices and values + precompute(); + precomputeLogDetAlpha(); +} + +void bob::machine::PLDABase::setSigma(const blitz::Array<double,1>& sigma) +{ + bob::core::array::assertSameShape(sigma, m_sigma); + m_sigma.reference(bob::core::array::ccopy(sigma)); + // Apply variance flooring threshold: This will also + // call the precompute() and precomputeLogLike() methods! + applyVarianceThreshold(); +} + +void bob::machine::PLDABase::setMu(const blitz::Array<double,1>& mu) +{ + bob::core::array::assertSameShape(mu, m_mu); + m_mu.reference(bob::core::array::ccopy(mu)); +} + +void bob::machine::PLDABase::setVarianceThreshold(const double value) +{ + // Variance flooring + m_variance_threshold = value; + // Apply variance flooring thresholds: This will also + // call the precompute() and precomputeLogLike() methods! + applyVarianceThreshold(); +} + +void bob::machine::PLDABase::applyVarianceThreshold() +{ + // Apply variance flooring threshold + m_sigma = blitz::where( m_sigma < m_variance_threshold, m_variance_threshold, m_sigma); + // Re-compute constants, because m_sigma has changed + precompute(); + precomputeLogLike(); +} + +const blitz::Array<double,2>& bob::machine::PLDABase::getGamma(const size_t a) const +{ + if(!hasGamma(a)) + throw std::runtime_error("Gamma for this number of samples is not currently in cache. You could use the getAddGamma() method instead"); + return (m_cache_gamma.find(a))->second; +} + +const blitz::Array<double,2>& bob::machine::PLDABase::getAddGamma(const size_t a) +{ + if(!hasGamma(a)) precomputeGamma(a); + return m_cache_gamma[a]; +} + +void bob::machine::PLDABase::initMuFGSigma() +{ + // To avoid problems related to precomputation + m_mu = 0.; + bob::math::eye(m_F); + bob::math::eye(m_G); + m_sigma = 1.; + // Precompute variables + precompute(); + precomputeLogLike(); +} + +void bob::machine::PLDABase::precompute() +{ + precomputeISigma(); + precomputeGtISigma(); + precomputeAlpha(); + precomputeBeta(); + m_cache_gamma.clear(); + precomputeFtBeta(); + m_cache_loglike_constterm.clear(); +} + +void bob::machine::PLDABase::precomputeLogLike() +{ + precomputeLogDetAlpha(); + precomputeLogDetSigma(); +} + +void bob::machine::PLDABase::precomputeISigma() +{ + // Updates inverse of sigma + m_cache_isigma = 1. / m_sigma; +} + +void bob::machine::PLDABase::precomputeGtISigma() +{ + // m_cache_Gt_isigma = G^T \Sigma^{-1} + blitz::firstIndex i; + blitz::secondIndex j; + blitz::Array<double,2> Gt = m_G.transpose(1,0); + m_cache_Gt_isigma = Gt(i,j) * m_cache_isigma(j); +} + +void bob::machine::PLDABase::precomputeAlpha() +{ + // alpha = (Id + G^T.sigma^-1.G)^-1 + + // m_tmp_ng_ng_1 = G^T.sigma^-1.G + bob::math::prod(m_cache_Gt_isigma, m_G, m_tmp_ng_ng_1); + // m_tmp_ng_ng_1 = Id + G^T.sigma^-1.G + for(int i=0; i<m_tmp_ng_ng_1.extent(0); ++i) m_tmp_ng_ng_1(i,i) += 1; + // m_cache_alpha = (Id + G^T.sigma^-1.G)^-1 + bob::math::inv(m_tmp_ng_ng_1, m_cache_alpha); +} + +void bob::machine::PLDABase::precomputeBeta() +{ + // beta = (sigma + G.G^T)^-1 + // BUT, there is a more efficient computation (Woodbury identity): + // beta = sigma^-1 - sigma^-1.G.(Id + G^T.sigma^-1.G)^-1.G^T.sigma^-1 + // beta = sigma^-1 - sigma^-1.G.alpha.G^T.sigma^-1 + + blitz::Array<double,2> GtISigmaT = m_cache_Gt_isigma.transpose(1,0); + // m_tmp_d_ng_1 = sigma^-1.G.alpha + bob::math::prod(GtISigmaT, m_cache_alpha, m_tmp_d_ng_1); + // m_cache_beta = -sigma^-1.G.alpha.G^T.sigma^-1 + bob::math::prod(m_tmp_d_ng_1, m_cache_Gt_isigma, m_cache_beta); + m_cache_beta = -m_cache_beta; + // m_cache_beta = sigma^-1 - sigma^-1.G.alpha.G^T.sigma^-1 + for(int i=0; i<m_cache_beta.extent(0); ++i) m_cache_beta(i,i) += m_cache_isigma(i); +} + +void bob::machine::PLDABase::precomputeGamma(const size_t a) +{ + + blitz::Array<double,2> gamma_a(getDimF(),getDimF()); + m_cache_gamma[a].reference(gamma_a); + computeGamma(a, gamma_a); +} + +void bob::machine::PLDABase::precomputeFtBeta() +{ + // m_cache_Ft_beta = F^T.beta = F^T.(sigma + G.G^T)^-1 + blitz::Array<double,2> Ft = m_F.transpose(1,0); + bob::math::prod(Ft, m_cache_beta, m_cache_Ft_beta); +} + +void bob::machine::PLDABase::computeGamma(const size_t a, + blitz::Array<double,2> res) const +{ + // gamma = (Id + a.F^T.beta.F)^-1 + + // Checks destination size + bob::core::array::assertSameShape(res, m_tmp_nf_nf_1); + // m_tmp_nf_nf_1 = F^T.beta.F + bob::math::prod(m_cache_Ft_beta, m_F, m_tmp_nf_nf_1); + // m_tmp_nf_nf_1 = a.F^T.beta.F + m_tmp_nf_nf_1 *= static_cast<double>(a); + // m_tmp_nf_nf_1 = Id + a.F^T.beta.F + for(int i=0; i<m_tmp_nf_nf_1.extent(0); ++i) m_tmp_nf_nf_1(i,i) += 1; + + // res = (Id + a.F^T.beta.F)^-1 + bob::math::inv(m_tmp_nf_nf_1, res); +} + +void bob::machine::PLDABase::precomputeLogDetAlpha() +{ + int sign; + m_cache_logdet_alpha = bob::math::slogdet(m_cache_alpha, sign); +} + +void bob::machine::PLDABase::precomputeLogDetSigma() +{ + m_cache_logdet_sigma = blitz::sum(blitz::log(m_sigma)); +} + +double bob::machine::PLDABase::computeLogLikeConstTerm(const size_t a, + const blitz::Array<double,2>& gamma_a) const +{ + // loglike_constterm[a] = a/2 * + // ( -D*log(2*pi) -log|sigma| +log|alpha| +log|gamma_a|) + int sign; + double logdet_gamma_a = bob::math::slogdet(gamma_a, sign); + double ah = static_cast<double>(a)/2.; + double res = ( -ah*((double)m_dim_d)*log(2*M_PI) - + ah*m_cache_logdet_sigma + ah*m_cache_logdet_alpha + logdet_gamma_a/2.); + return res; +} + +double bob::machine::PLDABase::computeLogLikeConstTerm(const size_t a) +{ + const blitz::Array<double,2>& gamma_a = getAddGamma(a); + return computeLogLikeConstTerm(a, gamma_a); +} + +void bob::machine::PLDABase::precomputeLogLikeConstTerm(const size_t a) +{ + double val = computeLogLikeConstTerm(a); + m_cache_loglike_constterm[a] = val; +} + +double bob::machine::PLDABase::getLogLikeConstTerm(const size_t a) const +{ + if(!hasLogLikeConstTerm(a)) + throw std::runtime_error("The LogLikelihood constant term for this number of samples is not currently in cache. You could use the getAddLogLikeConstTerm() method instead"); + return (m_cache_loglike_constterm.find(a))->second; +} + +double bob::machine::PLDABase::getAddLogLikeConstTerm(const size_t a) +{ + if(!hasLogLikeConstTerm(a)) precomputeLogLikeConstTerm(a); + return m_cache_loglike_constterm[a]; +} + +void bob::machine::PLDABase::clearMaps() +{ + m_cache_gamma.clear(); + m_cache_loglike_constterm.clear(); +} + +double bob::machine::PLDABase::computeLogLikelihoodPointEstimate( + const blitz::Array<double,1>& xij, const blitz::Array<double,1>& hi, + const blitz::Array<double,1>& wij) const +{ + // Check inputs + bob::core::array::assertSameDimensionLength(xij.extent(0), getDimD()); + bob::core::array::assertSameDimensionLength(hi.extent(0), getDimF()); + bob::core::array::assertSameDimensionLength(wij.extent(0), getDimG()); + // Computes: -D/2 log(2pi) -1/2 log(det(\Sigma)) + // -1/2 {(x_{ij}-(\mu+Fh_{i}+Gw_{ij}))^{T}\Sigma^{-1}(x_{ij}-(\mu+Fh_{i}+Gw_{ij}))} + double res = -0.5*((double)m_dim_d)*log(2*M_PI) - 0.5*m_cache_logdet_sigma; + // m_tmp_d_1 = (x_{ij} - (\mu+Fh_{i}+Gw_{ij})) + m_tmp_d_1 = xij - m_mu; + bob::math::prod(m_F, hi, m_tmp_d_2); + m_tmp_d_1 -= m_tmp_d_2; + bob::math::prod(m_G, wij, m_tmp_d_2); + m_tmp_d_1 -= m_tmp_d_2; + // add third term to res + res += -0.5*blitz::sum(blitz::pow2(m_tmp_d_1) * m_cache_isigma); + return res; +} + +namespace bob{ + namespace machine{ + /** + * @brief Prints a PLDABase in the output stream. This will print + * the values of the parameters \f$\mu\f$, \f$F\f$, \f$G\f$ and + * \f$\Sigma\f$ of the PLDA model. + */ + std::ostream& operator<<(std::ostream& os, const PLDABase& m) { + os << "mu = " << m.m_mu << std::endl; + os << "sigma = " << m.m_sigma << std::endl; + os << "F = " << m.m_F << std::endl; + os << "G = " << m.m_G << std::endl; + return os; + } + } +} + + +bob::machine::PLDAMachine::PLDAMachine(): + m_plda_base(), + m_n_samples(0), m_nh_sum_xit_beta_xi(0), m_weighted_sum(0), + m_loglikelihood(0), m_cache_gamma(), m_cache_loglike_constterm(), + m_tmp_d_1(0), m_tmp_d_2(0), m_tmp_nf_1(0), m_tmp_nf_2(0), m_tmp_nf_nf_1(0,0) +{ +} + +bob::machine::PLDAMachine::PLDAMachine(const boost::shared_ptr<bob::machine::PLDABase> plda_base): + m_plda_base(plda_base), + m_n_samples(0), m_nh_sum_xit_beta_xi(0), m_weighted_sum(plda_base->getDimF()), + m_loglikelihood(0), m_cache_gamma(), m_cache_loglike_constterm() +{ + resizeTmp(); +} + + +bob::machine::PLDAMachine::PLDAMachine(const bob::machine::PLDAMachine& other): + m_plda_base(other.m_plda_base), + m_n_samples(other.m_n_samples), + m_nh_sum_xit_beta_xi(other.m_nh_sum_xit_beta_xi), + m_weighted_sum(bob::core::array::ccopy(other.m_weighted_sum)), + m_loglikelihood(other.m_loglikelihood), m_cache_gamma(), + m_cache_loglike_constterm(other.m_cache_loglike_constterm) +{ + bob::core::array::ccopy(other.m_cache_gamma, m_cache_gamma); + resizeTmp(); +} + +bob::machine::PLDAMachine::PLDAMachine(bob::io::base::HDF5File& config, + const boost::shared_ptr<bob::machine::PLDABase> plda_base): + m_plda_base(plda_base) +{ + load(config); +} + +bob::machine::PLDAMachine::~PLDAMachine() { +} + +bob::machine::PLDAMachine& bob::machine::PLDAMachine::operator= +(const bob::machine::PLDAMachine& other) +{ + if(this!=&other) + { + m_plda_base = other.m_plda_base; + m_n_samples = other.m_n_samples; + m_nh_sum_xit_beta_xi = other.m_nh_sum_xit_beta_xi; + m_weighted_sum.reference(bob::core::array::ccopy(other.m_weighted_sum)); + m_loglikelihood = other.m_loglikelihood; + bob::core::array::ccopy(other.m_cache_gamma, m_cache_gamma); + m_cache_loglike_constterm = other.m_cache_loglike_constterm; + resizeTmp(); + } + return *this; +} + +bool bob::machine::PLDAMachine::operator== + (const bob::machine::PLDAMachine& b) const +{ + if (!(( (!m_plda_base && !b.m_plda_base) || + ((m_plda_base && b.m_plda_base) && *(m_plda_base) == *(b.m_plda_base))) && + m_n_samples == b.m_n_samples && + m_nh_sum_xit_beta_xi ==b.m_nh_sum_xit_beta_xi && + bob::core::array::isEqual(m_weighted_sum, b.m_weighted_sum) && + m_loglikelihood == b.m_loglikelihood && + bob::core::array::isEqual(m_cache_gamma, b.m_cache_gamma))) + return false; + + // m_cache_loglike_constterm + if (this->m_cache_loglike_constterm.size() != b.m_cache_loglike_constterm.size()) + return false; // differing sizes, they are not the same + std::map<size_t, double>::const_iterator i, j; + for (i = this->m_cache_loglike_constterm.begin(), j = b.m_cache_loglike_constterm.begin(); + i != this->m_cache_loglike_constterm.end(); ++i, ++j) + { + if (i->first != j->first || i->second != j->second) + return false; + } + + return true; +} + +bool bob::machine::PLDAMachine::operator!= + (const bob::machine::PLDAMachine& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::PLDAMachine::is_similar_to( + const bob::machine::PLDAMachine& b, const double r_epsilon, + const double a_epsilon) const +{ + return (( (!m_plda_base && !b.m_plda_base) || + ((m_plda_base && b.m_plda_base) && + m_plda_base->is_similar_to(*(b.m_plda_base), r_epsilon, a_epsilon))) && + m_n_samples == b.m_n_samples && + bob::core::isClose(m_nh_sum_xit_beta_xi, b.m_nh_sum_xit_beta_xi, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_weighted_sum, b.m_weighted_sum, r_epsilon, a_epsilon) && + bob::core::isClose(m_loglikelihood, b.m_loglikelihood, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_gamma, b.m_cache_gamma, r_epsilon, a_epsilon) && + bob::core::isClose(m_cache_loglike_constterm, b.m_cache_loglike_constterm, r_epsilon, a_epsilon)); +} + +void bob::machine::PLDAMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + m_n_samples = config.read<uint64_t>("n_samples"); + m_nh_sum_xit_beta_xi = config.read<double>("nh_sum_xit_beta_xi"); + m_weighted_sum.reference(config.readArray<double,1>("weighted_sum")); + m_loglikelihood = config.read<double>("loglikelihood"); + // gamma and log like constant term (a-dependent terms) + clearMaps(); + if(config.contains("a_indices")) + { + blitz::Array<uint32_t, 1> a_indices; + a_indices.reference(config.readArray<uint32_t,1>("a_indices")); + for(int i=0; i<a_indices.extent(0); ++i) + { + std::string str1 = "gamma_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_gamma[a_indices(i)].reference(config.readArray<double,2>(str1)); + std::string str2 = "loglikeconstterm_" + boost::lexical_cast<std::string>(a_indices(i)); + m_cache_loglike_constterm[a_indices(i)] = config.read<double>(str2); + } + } + resizeTmp(); +} + +void bob::machine::PLDAMachine::save(bob::io::base::HDF5File& config) const +{ + config.set("n_samples", m_n_samples); + config.set("nh_sum_xit_beta_xi", m_nh_sum_xit_beta_xi); + config.setArray("weighted_sum", m_weighted_sum); + config.set("loglikelihood", m_loglikelihood); + // Gamma + if(m_cache_gamma.size() > 0) + { + blitz::Array<uint32_t, 1> a_indices(m_cache_gamma.size()); + int i = 0; + for(std::map<size_t,blitz::Array<double,2> >::const_iterator + it=m_cache_gamma.begin(); it!=m_cache_gamma.end(); ++it) + { + a_indices(i) = it->first; + std::string str1 = "gamma_" + boost::lexical_cast<std::string>(it->first); + config.setArray(str1, it->second); + std::string str2 = "loglikeconstterm_" + boost::lexical_cast<std::string>(it->first); + double v = m_cache_loglike_constterm.find(it->first)->second; + config.set(str2, v); + ++i; + } + config.setArray("a_indices", a_indices); + } +} + +void bob::machine::PLDAMachine::setPLDABase(const boost::shared_ptr<bob::machine::PLDABase> plda_base) +{ + m_plda_base = plda_base; + m_weighted_sum.resizeAndPreserve(getDimF()); + clearMaps(); + resizeTmp(); +} + + +void bob::machine::PLDAMachine::setWeightedSum(const blitz::Array<double,1>& ws) +{ + if(ws.extent(0) != m_weighted_sum.extent(0)) { + boost::format m("size of parameter `ws' (%d) does not match the expected size (%d)"); + m % ws.extent(0) % m_weighted_sum.extent(0); + throw std::runtime_error(m.str()); + } + m_weighted_sum.reference(bob::core::array::ccopy(ws)); +} + +const blitz::Array<double,2>& bob::machine::PLDAMachine::getGamma(const size_t a) const +{ + // Checks in both base machine and this machine + if (m_plda_base->hasGamma(a)) return m_plda_base->getGamma(a); + else if (!hasGamma(a)) + throw std::runtime_error("Gamma for this number of samples is not currently in cache. You could use the getAddGamma() method instead"); + return (m_cache_gamma.find(a))->second; +} + +const blitz::Array<double,2>& bob::machine::PLDAMachine::getAddGamma(const size_t a) +{ + if (m_plda_base->hasGamma(a)) return m_plda_base->getGamma(a); + else if (hasGamma(a)) return m_cache_gamma[a]; + // else computes it and adds it to this machine + blitz::Array<double,2> gamma_a(getDimF(),getDimF()); + m_cache_gamma[a].reference(gamma_a); + m_plda_base->computeGamma(a, gamma_a); + return m_cache_gamma[a]; +} + +double bob::machine::PLDAMachine::getLogLikeConstTerm(const size_t a) const +{ + // Checks in both base machine and this machine + if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + if (m_plda_base->hasLogLikeConstTerm(a)) return m_plda_base->getLogLikeConstTerm(a); + else if (!hasLogLikeConstTerm(a)) + throw std::runtime_error("The LogLikelihood constant term for this number of samples is not currently in cache. You could use the getAddLogLikeConstTerm() method instead"); + return (m_cache_loglike_constterm.find(a))->second; +} + +double bob::machine::PLDAMachine::getAddLogLikeConstTerm(const size_t a) +{ + if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + if (m_plda_base->hasLogLikeConstTerm(a)) return m_plda_base->getLogLikeConstTerm(a); + else if (hasLogLikeConstTerm(a)) return m_cache_loglike_constterm[a]; + // else computes it and adds it to this machine + m_cache_loglike_constterm[a] = + m_plda_base->computeLogLikeConstTerm(a, getAddGamma(a)); + return m_cache_loglike_constterm[a]; +} + +void bob::machine::PLDAMachine::clearMaps() +{ + m_cache_gamma.clear(); + m_cache_loglike_constterm.clear(); +} + +void bob::machine::PLDAMachine::forward(const blitz::Array<double,1>& sample, double& score) const +{ + forward_(sample,score); +} + +void bob::machine::PLDAMachine::forward_(const blitz::Array<double,1>& sample, double& score) const +{ + // Computes the log likelihood ratio + score = computeLogLikelihood(sample, true) - // match + (computeLogLikelihood(sample, false) + m_loglikelihood); // no match +} + +void bob::machine::PLDAMachine::forward(const blitz::Array<double,2>& samples, double& score) const +{ + // Computes the log likelihood ratio + score = computeLogLikelihood(samples, true) - // match + (computeLogLikelihood(samples, false) + m_loglikelihood); // no match +} + +double bob::machine::PLDAMachine::computeLogLikelihood(const blitz::Array<double,1>& sample, + bool enrol) const +{ + if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + // Check dimensionality + bob::core::array::assertSameDimensionLength(sample.extent(0), getDimD()); + + int n_samples = 1 + (enrol?m_n_samples:0); + + // 3/ Third term of the likelihood: -1/2*X^T*(SIGMA+A.A^T)^-1*X + // Efficient way: -1/2*sum_i(xi^T.sigma^-1.xi - xi^T.sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1.xi + // -1/2*sumWeighted^T*(I+aF^T.(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1).F)^-1*sumWeighted + // where sumWeighted = sum_i(F^T*(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1)*xi) + const blitz::Array<double,2>& beta = getPLDABase()->getBeta(); + const blitz::Array<double,2>& Ft_beta = getPLDABase()->getFtBeta(); + const blitz::Array<double,1>& mu = getPLDABase()->getMu(); + double terma = (enrol?m_nh_sum_xit_beta_xi:0.); + // sumWeighted + if (enrol && m_n_samples > 0) m_tmp_nf_1 = m_weighted_sum; + else m_tmp_nf_1 = 0; + + // terma += -1 / 2. * (xi^t*beta*xi) + m_tmp_d_1 = sample - mu; + bob::math::prod(beta, m_tmp_d_1, m_tmp_d_2); + terma += -1 / 2. * (blitz::sum(m_tmp_d_1*m_tmp_d_2)); + + // sumWeighted + bob::math::prod(Ft_beta, m_tmp_d_1, m_tmp_nf_2); + m_tmp_nf_1 += m_tmp_nf_2; + blitz::Array<double,2> gamma_a; + if (hasGamma(n_samples) || m_plda_base->hasGamma(n_samples)) + gamma_a.reference(getGamma(n_samples)); + else + { + gamma_a.reference(m_tmp_nf_nf_1); + m_plda_base->computeGamma(n_samples, gamma_a); + } + bob::math::prod(gamma_a, m_tmp_nf_1, m_tmp_nf_2); + double termb = 1 / 2. * (blitz::sum(m_tmp_nf_1*m_tmp_nf_2)); + + // 1/2/ Constant term of the log likelihood: + // 1/ First term of the likelihood: -Nsamples*D/2*log(2*PI) + // 2/ Second term of the likelihood: -1/2*log(det(SIGMA+A.A^T)) + // Efficient way: -Nsamples/2*log(det(sigma))-Nsamples/2*log(det(I+G^T.sigma^-1.G)) + // -1/2*log(det(I+aF^T.(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)*G^T*sigma^-1).F)) + double log_likelihood; // = getAddLogLikeConstTerm(static_cast<size_t>(n_samples)); + if (hasLogLikeConstTerm(n_samples) || m_plda_base->hasLogLikeConstTerm(n_samples)) + log_likelihood = getLogLikeConstTerm(n_samples); + else + log_likelihood = m_plda_base->computeLogLikeConstTerm(n_samples, gamma_a); + + log_likelihood += terma + termb; + return log_likelihood; +} + +double bob::machine::PLDAMachine::computeLogLikelihood(const blitz::Array<double,2>& samples, + bool enrol) const +{ + if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + // Check dimensionality + bob::core::array::assertSameDimensionLength(samples.extent(1), getDimD()); + + int n_samples = samples.extent(0) + (enrol?m_n_samples:0); + // 3/ Third term of the likelihood: -1/2*X^T*(SIGMA+A.A^T)^-1*X + // Efficient way: -1/2*sum_i(xi^T.sigma^-1.xi - xi^T.sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1.xi + // -1/2*sumWeighted^T*(I+aF^T.(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1).F)^-1*sumWeighted + // where sumWeighted = sum_i(F^T*(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)^-1*G^T*sigma^-1)*xi) + const blitz::Array<double,2>& beta = getPLDABase()->getBeta(); + const blitz::Array<double,2>& Ft_beta = getPLDABase()->getFtBeta(); + const blitz::Array<double,1>& mu = getPLDABase()->getMu(); + double terma = (enrol?m_nh_sum_xit_beta_xi:0.); + // sumWeighted + if (enrol && m_n_samples > 0) m_tmp_nf_1 = m_weighted_sum; + else m_tmp_nf_1 = 0; + for (int k=0; k<samples.extent(0); ++k) + { + blitz::Array<double,1> samp = samples(k,blitz::Range::all()); + m_tmp_d_1 = samp - mu; + // terma += -1 / 2. * (xi^t*beta*xi) + bob::math::prod(beta, m_tmp_d_1, m_tmp_d_2); + terma += -1 / 2. * (blitz::sum(m_tmp_d_1*m_tmp_d_2)); + + // sumWeighted + bob::math::prod(Ft_beta, m_tmp_d_1, m_tmp_nf_2); + m_tmp_nf_1 += m_tmp_nf_2; + } + + blitz::Array<double,2> gamma_a; + if (hasGamma(n_samples) || m_plda_base->hasGamma(n_samples)) + gamma_a.reference(getGamma(n_samples)); + else + { + gamma_a.reference(m_tmp_nf_nf_1); + m_plda_base->computeGamma(n_samples, gamma_a); + } + bob::math::prod(gamma_a, m_tmp_nf_1, m_tmp_nf_2); + double termb = 1 / 2. * (blitz::sum(m_tmp_nf_1*m_tmp_nf_2)); + + // 1/2/ Constant term of the log likelihood: + // 1/ First term of the likelihood: -Nsamples*D/2*log(2*PI) + // 2/ Second term of the likelihood: -1/2*log(det(SIGMA+A.A^T)) + // Efficient way: -Nsamples/2*log(det(sigma))-Nsamples/2*log(det(I+G^T.sigma^-1.G)) + // -1/2*log(det(I+aF^T.(sigma^-1-sigma^-1*G*(I+G^T.sigma^-1.G)*G^T*sigma^-1).F)) + double log_likelihood; // = getAddLogLikeConstTerm(static_cast<size_t>(n_samples)); + if (hasLogLikeConstTerm(n_samples) || m_plda_base->hasLogLikeConstTerm(n_samples)) + log_likelihood = getLogLikeConstTerm(n_samples); + else + log_likelihood = m_plda_base->computeLogLikeConstTerm(n_samples, gamma_a); + + log_likelihood += terma + termb; + return log_likelihood; +} + +void bob::machine::PLDAMachine::resize(const size_t dim_d, const size_t dim_f, + const size_t dim_g) +{ + m_weighted_sum.resizeAndPreserve(dim_f); + clearMaps(); + resizeTmp(); +} + +void bob::machine::PLDAMachine::resizeTmp() +{ + if (m_plda_base) + { + m_tmp_d_1.resize(getDimD()); + m_tmp_d_2.resize(getDimD()); + m_tmp_nf_1.resize(getDimF()); + m_tmp_nf_2.resize(getDimF()); + m_tmp_nf_nf_1.resize(getDimF(), getDimF()); + } +} diff --git a/bob/learn/misc/cpp/PLDATrainer.cpp b/bob/learn/misc/cpp/PLDATrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..98fcfbbe5b05f0c15bc031bdf99e6408aa3f5722 --- /dev/null +++ b/bob/learn/misc/cpp/PLDATrainer.cpp @@ -0,0 +1,806 @@ +/** + * @date Fri Oct 14 18:07:56 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Probabilistic Linear Discriminant Analysis + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/PLDATrainer.h> +#include <bob.core/array_copy.h> +#include <bob.core/array_random.h> +#include <bob.math/linear.h> +#include <bob.math/inv.h> +#include <bob.math/svd.h> +#include <algorithm> +#include <boost/random.hpp> +#include <vector> +#include <limits> + +bob::trainer::PLDATrainer::PLDATrainer(const size_t max_iterations, + const bool use_sum_second_order): + EMTrainer<bob::machine::PLDABase, std::vector<blitz::Array<double,2> > > + (0.001, max_iterations, false), + m_dim_d(0), m_dim_f(0), m_dim_g(0), + m_use_sum_second_order(use_sum_second_order), + m_initF_method(bob::trainer::PLDATrainer::RANDOM_F), m_initF_ratio(1.), + m_initG_method(bob::trainer::PLDATrainer::RANDOM_G), m_initG_ratio(1.), + m_initSigma_method(bob::trainer::PLDATrainer::RANDOM_SIGMA), + m_initSigma_ratio(1.), + m_cache_S(0,0), + m_cache_z_first_order(0), m_cache_sum_z_second_order(0,0), m_cache_z_second_order(0), + m_cache_n_samples_per_id(0), m_cache_n_samples_in_training(), m_cache_B(0,0), + m_cache_Ft_isigma_G(0,0), m_cache_eta(0,0), m_cache_zeta(), m_cache_iota(), + m_tmp_nf_1(0), m_tmp_nf_2(0), m_tmp_ng_1(0), + m_tmp_D_1(0), m_tmp_D_2(0), + m_tmp_nfng_nfng(0,0), m_tmp_D_nfng_1(0,0), m_tmp_D_nfng_2(0,0) +{ +} + +bob::trainer::PLDATrainer::PLDATrainer(const bob::trainer::PLDATrainer& other): + EMTrainer<bob::machine::PLDABase, std::vector<blitz::Array<double,2> > > + (other.m_convergence_threshold, other.m_max_iterations, + other.m_compute_likelihood), + m_dim_d(other.m_dim_d), m_dim_f(other.m_dim_f), m_dim_g(other.m_dim_g), + m_use_sum_second_order(other.m_use_sum_second_order), + m_initF_method(other.m_initF_method), m_initF_ratio(other.m_initF_ratio), + m_initG_method(other.m_initG_method), m_initG_ratio(other.m_initG_ratio), + m_initSigma_method(other.m_initSigma_method), m_initSigma_ratio(other.m_initSigma_ratio), + m_cache_S(bob::core::array::ccopy(other.m_cache_S)), + m_cache_z_first_order(), + m_cache_sum_z_second_order(bob::core::array::ccopy(other.m_cache_sum_z_second_order)), + m_cache_z_second_order(), + m_cache_n_samples_per_id(other.m_cache_n_samples_per_id), + m_cache_n_samples_in_training(other.m_cache_n_samples_in_training), + m_cache_B(bob::core::array::ccopy(other.m_cache_B)), + m_cache_Ft_isigma_G(bob::core::array::ccopy(other.m_cache_Ft_isigma_G)), + m_cache_eta(bob::core::array::ccopy(other.m_cache_eta)) +{ + bob::core::array::ccopy(other.m_cache_z_first_order, m_cache_z_first_order); + bob::core::array::ccopy(other.m_cache_z_second_order, m_cache_z_second_order); + bob::core::array::ccopy(other.m_cache_zeta, m_cache_zeta); + bob::core::array::ccopy(other.m_cache_iota, m_cache_iota); + // Resize working arrays + resizeTmp(); +} + +bob::trainer::PLDATrainer::~PLDATrainer() {} + +bob::trainer::PLDATrainer& bob::trainer::PLDATrainer::operator= +(const bob::trainer::PLDATrainer& other) +{ + if(this != &other) + { + bob::trainer::EMTrainer<bob::machine::PLDABase, + std::vector<blitz::Array<double,2> > >::operator=(other); + m_dim_d = other.m_dim_d; + m_dim_f = other.m_dim_f; + m_dim_g = other.m_dim_g; + m_use_sum_second_order = other.m_use_sum_second_order; + m_initF_method = other.m_initF_method; + m_initF_ratio = other.m_initF_ratio; + m_initG_method = other.m_initG_method; + m_initG_ratio = other.m_initG_ratio; + m_initSigma_method = other.m_initSigma_method; + m_initSigma_ratio = other.m_initSigma_ratio; + m_cache_S = bob::core::array::ccopy(other.m_cache_S); + bob::core::array::ccopy(other.m_cache_z_first_order, m_cache_z_first_order); + m_cache_sum_z_second_order = bob::core::array::ccopy(other.m_cache_sum_z_second_order); + bob::core::array::ccopy(other.m_cache_z_second_order, m_cache_z_second_order); + m_cache_n_samples_per_id = other.m_cache_n_samples_per_id; + m_cache_n_samples_in_training = other.m_cache_n_samples_in_training; + m_cache_B = bob::core::array::ccopy(other.m_cache_B); + m_cache_Ft_isigma_G = bob::core::array::ccopy(other.m_cache_Ft_isigma_G); + m_cache_eta = bob::core::array::ccopy(other.m_cache_eta); + bob::core::array::ccopy(other.m_cache_iota, m_cache_iota); + // Resize working arrays + resizeTmp(); + } + return *this; +} + +bool bob::trainer::PLDATrainer::operator== + (const bob::trainer::PLDATrainer& other) const +{ + return bob::trainer::EMTrainer<bob::machine::PLDABase, + std::vector<blitz::Array<double,2> > >::operator==(other) && + m_dim_d == other.m_dim_d && + m_dim_f == other.m_dim_f && + m_dim_g == other.m_dim_g && + m_initF_method == other.m_initF_method && + m_initF_ratio == other.m_initF_ratio && + m_initG_method == other.m_initG_method && + m_initG_ratio == other.m_initG_ratio && + m_initSigma_method == other.m_initSigma_method && + m_initSigma_ratio == other.m_initSigma_ratio && + bob::core::array::isEqual(m_cache_S, m_cache_S) && + bob::core::array::isEqual(m_cache_z_first_order, other.m_cache_z_first_order) && + bob::core::array::isEqual(m_cache_sum_z_second_order, other.m_cache_sum_z_second_order) && + bob::core::array::isEqual(m_cache_z_second_order, other.m_cache_z_second_order) && + m_cache_n_samples_per_id.size() == m_cache_n_samples_per_id.size() && + std::equal(m_cache_n_samples_per_id.begin(), m_cache_n_samples_per_id.end(), other.m_cache_n_samples_per_id.begin()) && + m_cache_n_samples_in_training.size() == m_cache_n_samples_in_training.size() && + std::equal(m_cache_n_samples_in_training.begin(), m_cache_n_samples_in_training.end(), other.m_cache_n_samples_in_training.begin()) && + bob::core::array::isEqual(m_cache_B, other.m_cache_B) && + bob::core::array::isEqual(m_cache_Ft_isigma_G, other.m_cache_Ft_isigma_G) && + bob::core::array::isEqual(m_cache_eta, other.m_cache_eta) && + bob::core::array::isEqual(m_cache_zeta, other.m_cache_zeta) && + bob::core::array::isEqual(m_cache_iota, other.m_cache_iota); +} + +bool bob::trainer::PLDATrainer::operator!= + (const bob::trainer::PLDATrainer &other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::PLDATrainer::is_similar_to + (const bob::trainer::PLDATrainer &other, const double r_epsilon, + const double a_epsilon) const +{ + return bob::trainer::EMTrainer<bob::machine::PLDABase, + std::vector<blitz::Array<double,2> > >::is_similar_to(other, r_epsilon, a_epsilon) && + m_dim_d == other.m_dim_d && + m_dim_f == other.m_dim_f && + m_dim_g == other.m_dim_g && + m_use_sum_second_order == other.m_use_sum_second_order && + m_initF_method == other.m_initF_method && + bob::core::isClose(m_initF_ratio, other.m_initF_ratio, r_epsilon, a_epsilon) && + m_initG_method == other.m_initG_method && + bob::core::isClose(m_initG_ratio, other.m_initG_ratio, r_epsilon, a_epsilon) && + m_initSigma_method == other.m_initSigma_method && + bob::core::isClose(m_initSigma_ratio, other.m_initSigma_ratio, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_S, m_cache_S, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_z_first_order, other.m_cache_z_first_order, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_sum_z_second_order, other.m_cache_sum_z_second_order, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_z_second_order, other.m_cache_z_second_order, r_epsilon, a_epsilon) && + m_cache_n_samples_per_id.size() == m_cache_n_samples_per_id.size() && + std::equal(m_cache_n_samples_per_id.begin(), m_cache_n_samples_per_id.end(), other.m_cache_n_samples_per_id.begin()) && + m_cache_n_samples_in_training.size() == m_cache_n_samples_in_training.size() && + std::equal(m_cache_n_samples_in_training.begin(), m_cache_n_samples_in_training.end(), other.m_cache_n_samples_in_training.begin()) && + bob::core::array::isClose(m_cache_B, other.m_cache_B, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_Ft_isigma_G, other.m_cache_Ft_isigma_G, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_eta, other.m_cache_eta, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_zeta, other.m_cache_zeta, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_cache_iota, other.m_cache_iota, r_epsilon, a_epsilon); +} + +void bob::trainer::PLDATrainer::initialize(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Checks training data + checkTrainingData(v_ar); + + // Gets dimension (first Arrayset) + size_t n_features = v_ar[0].extent(1); + m_dim_d = machine.getDimD(); + // Get dimensionalities from the PLDABase + bob::core::array::assertSameDimensionLength(n_features, m_dim_d); + m_dim_f = machine.getDimF(); + m_dim_g = machine.getDimG(); + + // Reinitializes array members + initMembers(v_ar); + + // Computes the mean and the covariance if required + computeMeanVariance(machine, v_ar); + + // Initialization (e.g. using scatter) + initFGSigma(machine, v_ar); +} + +void bob::trainer::PLDATrainer::finalize(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Precomputes constant parts of the log likelihood and (gamma_a) + precomputeLogLike(machine, v_ar); + // Adds the case 1 sample if not already done (always used for scoring) + machine.getAddGamma(1); + machine.getAddLogLikeConstTerm(1); +} + +void bob::trainer::PLDATrainer::checkTrainingData(const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Checks that the vector of Arraysets is not empty + if (v_ar.size() == 0) { + throw std::runtime_error("input training set is empty"); + } + + // Gets dimension (first Arrayset) + int n_features = v_ar[0].extent(1); + // Checks dimension consistency + for (size_t i=0; i<v_ar.size(); ++i) { + if (v_ar[i].extent(1) != n_features) { + boost::format m("number of features (columns) of array for class %u (%d) does not match that of array for class 0 (%d)"); + m % i % v_ar[0].extent(1) % n_features; + throw std::runtime_error(m.str()); + } + } +} + +void bob::trainer::PLDATrainer::initMembers(const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Gets dimension (first Arrayset) + const size_t n_features = v_ar[0].extent(1); // dimensionality of the data + const size_t n_identities = v_ar.size(); + + m_cache_S.resize(n_features, n_features); + m_cache_sum_z_second_order.resize(m_dim_f+m_dim_g, m_dim_f+m_dim_g); + + // Loops over the identities + for (size_t i=0; i<n_identities; ++i) + { + // Number of training samples for this identity + const size_t n_i = v_ar[i].extent(0); + // m_cache_z_first_order + blitz::Array<double,2> z_i(n_i, m_dim_f+m_dim_g); + m_cache_z_first_order.push_back(z_i); + // m_z_second_order + if (!m_use_sum_second_order) + { + blitz::Array<double,3> z2_i(n_i, m_dim_f+m_dim_g, m_dim_f+m_dim_g); + m_cache_z_second_order.push_back(z2_i); + } + + // m_cache_n_samples_per_id + m_cache_n_samples_per_id.push_back(n_i); + + // Maps dependent on the number of samples per identity + std::map<size_t,bool>::iterator it; + it = m_cache_n_samples_in_training.find(n_i); + if (it == m_cache_n_samples_in_training.end()) + { + // Indicates if there are identities with n_i training samples and if + // corresponding matrices are up to date. + m_cache_n_samples_in_training[n_i] = false; + // Allocates arrays for identities with n_i training samples + m_cache_zeta[n_i].reference(blitz::Array<double,2>(m_dim_g, m_dim_g)); + m_cache_iota[n_i].reference(blitz::Array<double,2>(m_dim_f, m_dim_g)); + } + } + + m_cache_B.resize(n_features, m_dim_f+m_dim_g); + m_cache_Ft_isigma_G.resize(m_dim_f, m_dim_g); + m_cache_eta.resize(m_dim_f, m_dim_g); + + // Working arrays + resizeTmp(); +} + +void bob::trainer::PLDATrainer::resizeTmp() +{ + m_tmp_nf_1.resize(m_dim_f); + m_tmp_nf_2.resize(m_dim_f); + m_tmp_ng_1.resize(m_dim_g); + m_tmp_D_1.resize(m_dim_d); + m_tmp_D_2.resize(m_dim_d); + m_tmp_nfng_nfng.resize(m_dim_f+m_dim_g, m_dim_f+m_dim_g); + m_tmp_D_nfng_1.resize(m_dim_d, m_dim_f+m_dim_g); + m_tmp_D_nfng_2.resize(m_dim_d, m_dim_f+m_dim_g); +} + +void bob::trainer::PLDATrainer::computeMeanVariance(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + blitz::Array<double,1>& mu = machine.updateMu(); + blitz::Range all = blitz::Range::all(); + // TODO: Uncomment variance computation if required + /* if(m_compute_likelihood) + { + // loads all the data in a single shot - required for scatter + blitz::Array<double,2> data(n_features, n_samples); + for (size_t i=0; i<n_samples; ++i) + data(all,i) = ar(i,all); + // Mean and scatter computation + bob::math::scatter(data, m_cache_S, mu); + // divides scatter by N-1 + m_cache_S /= static_cast<double>(n_samples-1); + } + else */ + { + // Computes the mean and updates mu + mu = 0.; + size_t n_samples = 0; + for (size_t j=0; j<v_ar.size(); ++j) { + n_samples += v_ar[j].extent(0); + for (int i=0; i<v_ar[j].extent(0); ++i) + mu += v_ar[j](i,all); + } + mu /= static_cast<double>(n_samples); + m_cache_S = 0.; + } +} + +void bob::trainer::PLDATrainer::initFGSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Initializes F, G and sigma + initF(machine, v_ar); + initG(machine, v_ar); + initSigma(machine, v_ar); + + // Precomputes values using new F, G and sigma + machine.precompute(); +} + +void bob::trainer::PLDATrainer::initF(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + blitz::Array<double,2>& F = machine.updateF(); + blitz::Range a = blitz::Range::all(); + + // 1: between-class scatter + if (m_initF_method == bob::trainer::PLDATrainer::BETWEEN_SCATTER) + { + if (machine.getDimF() > v_ar.size()) { + boost::format m("The rank of the matrix F ('%ld') can't be larger than the number of classes in the training set ('%ld')"); + m % machine.getDimF() % v_ar.size(); + throw std::runtime_error(m.str()); + } + + // a/ Computes between-class scatter matrix + blitz::firstIndex bi; + blitz::secondIndex bj; + blitz::Array<double,2> S(machine.getDimD(), v_ar.size()); + S = 0.; + m_tmp_D_1 = 0.; + for (size_t i=0; i<v_ar.size(); ++i) + { + blitz::Array<double,1> Si = S(blitz::Range::all(),i); + Si = 0.; + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // Si += x_ij + Si += v_ar[i](j,a); + } + // Si = mean of the samples class i + Si /= static_cast<double>(v_ar[i].extent(0)); + m_tmp_D_1 += Si; + } + m_tmp_D_1 /= static_cast<double>(v_ar.size()); + + // b/ Removes the mean + S = S(bi,bj) - m_tmp_D_1(bi); + + // c/ SVD of the between-class scatter matrix + const size_t n_singular = std::min(machine.getDimD(),v_ar.size()); + blitz::Array<double,2> U(machine.getDimD(), n_singular); + blitz::Array<double,1> sigma(n_singular); + bob::math::svd(S, U, sigma); + + // d/ Updates F + blitz::Array<double,2> Uslice = U(a, blitz::Range(0,m_dim_f-1)); + blitz::Array<double,1> sigma_slice = sigma(blitz::Range(0,m_dim_f-1)); + sigma_slice = blitz::sqrt(sigma_slice); + F = Uslice(bi,bj) / sigma_slice(bj); + } + // otherwise: random initialization + else { + // F initialization + bob::core::array::randn(*m_rng, F); + F *= m_initF_ratio; + } +} + +void bob::trainer::PLDATrainer::initG(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + blitz::Array<double,2>& G = machine.updateG(); + blitz::Range a = blitz::Range::all(); + + // 1: within-class scatter + if (m_initG_method == bob::trainer::PLDATrainer::WITHIN_SCATTER) + { + // a/ Computes within-class scatter matrix + blitz::firstIndex bi; + blitz::secondIndex bj; + size_t Nsamples=0; + for (size_t i=0; i<v_ar.size(); ++i) + Nsamples += v_ar[i].extent(0); + + blitz::Array<double,2> S(machine.getDimD(), Nsamples); + S = 0.; + m_tmp_D_1 = 0.; + int counter = 0; + for (size_t i=0; i<v_ar.size(); ++i) + { + // Computes the mean of the samples class i + m_tmp_D_2 = 0.; + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // m_tmp_D_2 += x_ij + m_tmp_D_2 += v_ar[i](j,a); + } + // m_tmp_D_2 = mean of the samples class i + m_tmp_D_2 /= static_cast<double>(v_ar[i].extent(0)); + + // Generates the scatter + for (int j=0; j<v_ar[i].extent(0); ++j) + { + blitz::Array<double,1> Si = S(a, counter); + // Si = x_ij - mean_i + Si = v_ar[i](j,a) - m_tmp_D_2; + // mean of the within class + m_tmp_D_1 += Si; + ++counter; + } + } + m_tmp_D_1 /= static_cast<double>(Nsamples); + + // b/ Removes the mean + S = S(bi,bj) - m_tmp_D_1(bi); + + // c/ SVD of the between-class scatter matrix + blitz::Array<double,2> U(m_dim_d, std::min(m_dim_d, Nsamples)); + blitz::Array<double,1> sigma(std::min(m_dim_d, Nsamples)); + bob::math::svd(S, U, sigma); + + // d/ Updates G + blitz::Array<double,2> Uslice = U(blitz::Range::all(), blitz::Range(0,m_dim_g-1)); + blitz::Array<double,1> sigma_slice = sigma(blitz::Range(0,m_dim_g-1)); + sigma_slice = blitz::sqrt(sigma_slice); + G = Uslice(bi,bj) / sigma_slice(bj); + } + // otherwise: random initialization + else { + // G initialization + bob::core::array::randn(*m_rng, G); + G *= m_initG_ratio; + } +} + +void bob::trainer::PLDATrainer::initSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + blitz::Array<double,1>& sigma = machine.updateSigma(); + blitz::Range a = blitz::Range::all(); + + // 1: percentage of the variance of G + if (m_initSigma_method == bob::trainer::PLDATrainer::VARIANCE_G) { + const blitz::Array<double,2>& G = machine.getG(); + blitz::secondIndex bj; + m_tmp_D_1 = blitz::mean(G, bj); + // Updates sigma + sigma = blitz::fabs(m_tmp_D_1) * m_initSigma_ratio; + } + // 2: constant value + else if (m_initSigma_method == bob::trainer::PLDATrainer::CONSTANT) { + sigma = m_initSigma_ratio; + } + // 3: percentage of the variance of the data + else if (m_initSigma_method == bob::trainer::PLDATrainer::VARIANCE_DATA) { + // a/ Computes the global mean + // m_tmp_D_1 = 1/N sum_i x_i + m_tmp_D_1 = 0.; + size_t Ns = 0; + for (size_t i=0; i<v_ar.size(); ++i) + { + for (int j=0; j<v_ar[i].extent(0); ++j) + m_tmp_D_1 += v_ar[i](j,a); + Ns += v_ar[i].extent(0); + } + m_tmp_D_1 /= static_cast<double>(Ns); + + // b/ Computes the variance: + m_tmp_D_2 = 0.; + for (size_t i=0; i<v_ar.size(); ++i) + for (int j=0; j<v_ar[i].extent(0); ++j) + m_tmp_D_2 += blitz::pow2(v_ar[i](j,a) - m_tmp_D_1); + sigma = m_initSigma_ratio * m_tmp_D_2 / static_cast<double>(Ns-1); + } + // otherwise: random initialization + else { + // sigma initialization + bob::core::array::randn(*m_rng, sigma); + sigma = blitz::fabs(sigma) * m_initSigma_ratio; + } + // Apply variance threshold + machine.applyVarianceThreshold(); +} + +void bob::trainer::PLDATrainer::eStep(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Precomputes useful variables using current estimates of F,G, and sigma + precomputeFromFGSigma(machine); + // Gets the mean mu from the machine + const blitz::Array<double,1>& mu = machine.getMu(); + const blitz::Array<double,2>& alpha = machine.getAlpha(); + const blitz::Array<double,2>& F = machine.getF(); + const blitz::Array<double,2>& FtBeta = machine.getFtBeta(); + const blitz::Array<double,2>& GtISigma = machine.getGtISigma(); + blitz::Range a = blitz::Range::all(); + + // blitz indices + blitz::firstIndex bi; + blitz::secondIndex bj; + // Initializes sum of z second order statistics to 0 + m_cache_sum_z_second_order = 0.; + for (size_t i=0; i<v_ar.size(); ++i) + { + // Computes expectation of z_ij = [h_i w_ij] + // 1/a/ Computes expectation of h_i + // Loop over the samples + m_tmp_nf_1 = 0.; + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // m_tmp_D_1 = x_sj-mu + m_tmp_D_1 = v_ar[i](j,a) - mu; + + // m_tmp_nf_2 = F^T.beta.(x_sj-mu) + bob::math::prod(FtBeta, m_tmp_D_1, m_tmp_nf_2); + // m_tmp_nf_1 = sum_j F^T.beta.(x_sj-mu) + m_tmp_nf_1 += m_tmp_nf_2; + } + const blitz::Array<double,2>& gamma_a = machine.getAddGamma(v_ar[i].extent(0)); + blitz::Range r_hi(0, m_dim_f-1); + // m_tmp_nf_2 = E(h_i) = gamma_A sum_j F^T.beta.(x_sj-mu) + bob::math::prod(gamma_a, m_tmp_nf_1, m_tmp_nf_2); + + // 1/b/ Precomputes: m_tmp_D_2 = F.E{h_i} + bob::math::prod(F, m_tmp_nf_2, m_tmp_D_2); + + // 2/ First and second order statistics of z + // Precomputed values + blitz::Array<double,2>& zeta_a = m_cache_zeta[v_ar[i].extent(0)]; + blitz::Array<double,2>& iota_a = m_cache_iota[v_ar[i].extent(0)]; + blitz::Array<double,2> iotat_a = iota_a.transpose(1,0); + + // Extracts statistics of z_ij = [h_i w_ij] from y_i = [h_i w_i1 ... w_iJ] + blitz::Range r1(0, m_dim_f-1); + blitz::Range r2(m_dim_f, m_dim_f+m_dim_g-1); + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // 1/ First order statistics of z + blitz::Array<double,1> z_first_order_ij_1 = m_cache_z_first_order[i](j,r1); + z_first_order_ij_1 = m_tmp_nf_2; // E{h_i} + // m_tmp_D_1 = x_sj - mu - F.E{h_i} + m_tmp_D_1 = v_ar[i](j,a) - mu - m_tmp_D_2; + // m_tmp_ng_1 = G^T.sigma^-1.(x_sj-mu-fhi) + bob::math::prod(GtISigma, m_tmp_D_1, m_tmp_ng_1); + // z_first_order_ij_2 = (Id+G^T.sigma^-1.G)^-1.G^T.sigma^-1.(x_sj-mu) = E{w_ij} + blitz::Array<double,1> z_first_order_ij_2 = m_cache_z_first_order[i](j,r2); + bob::math::prod(alpha, m_tmp_ng_1, z_first_order_ij_2); + + // 2/ Second order statistics of z + blitz::Array<double,2> z_sum_so_11 = m_cache_sum_z_second_order(r1,r1); + blitz::Array<double,2> z_sum_so_12 = m_cache_sum_z_second_order(r1,r2); + blitz::Array<double,2> z_sum_so_21 = m_cache_sum_z_second_order(r2,r1); + blitz::Array<double,2> z_sum_so_22 = m_cache_sum_z_second_order(r2,r2); + if (m_use_sum_second_order) + { + z_sum_so_11 += gamma_a + z_first_order_ij_1(bi) * z_first_order_ij_1(bj); + z_sum_so_12 += iota_a + z_first_order_ij_1(bi) * z_first_order_ij_2(bj); + z_sum_so_21 += iotat_a + z_first_order_ij_2(bi) * z_first_order_ij_1(bj); + z_sum_so_22 += zeta_a + z_first_order_ij_2(bi) * z_first_order_ij_2(bj); + } + else + { + blitz::Array<double,2> z_so_11 = m_cache_z_second_order[i](j,r1,r1); + z_so_11 = gamma_a + z_first_order_ij_1(bi) * z_first_order_ij_1(bj); + z_sum_so_11 += z_so_11; + blitz::Array<double,2> z_so_12 = m_cache_z_second_order[i](j,r1,r2); + z_so_12 = iota_a + z_first_order_ij_1(bi) * z_first_order_ij_2(bj); + z_sum_so_12 += z_so_12; + blitz::Array<double,2> z_so_21 = m_cache_z_second_order[i](j,r2,r1); + z_so_21 = iotat_a + z_first_order_ij_2(bi) * z_first_order_ij_1(bj); + z_sum_so_21 += z_so_21; + blitz::Array<double,2> z_so_22 = m_cache_z_second_order[i](j,r2,r2); + z_so_22 = zeta_a + z_first_order_ij_2(bi) * z_first_order_ij_2(bj); + z_sum_so_22 += z_so_22; + } + } + } +} + +void bob::trainer::PLDATrainer::precomputeFromFGSigma(bob::machine::PLDABase& machine) +{ + // Blitz compatibility: ugly fix (const_cast, as old blitz version does not + // provide a non-const version of transpose()) + const blitz::Array<double,2>& F = machine.getF(); + const blitz::Array<double,2> Ft = const_cast<blitz::Array<double,2>&>(F).transpose(1,0); + const blitz::Array<double,2>& Gt_isigma = machine.getGtISigma(); + const blitz::Array<double,2> Gt_isigma_t = const_cast<blitz::Array<double,2>&>(Gt_isigma).transpose(1,0); + const blitz::Array<double,2>& alpha = machine.getAlpha(); + + // Precomputes F, G and sigma-based expressions + bob::math::prod(Ft, Gt_isigma_t, m_cache_Ft_isigma_G); + bob::math::prod(m_cache_Ft_isigma_G, alpha, m_cache_eta); + blitz::Array<double,2> etat = m_cache_eta.transpose(1,0); + + // Reinitializes all the zeta_a and iota_a + std::map<size_t,bool>::iterator it; + for (it=m_cache_n_samples_in_training.begin(); it!=m_cache_n_samples_in_training.end(); + ++it) + it->second = false; + + for (it=m_cache_n_samples_in_training.begin(); it!=m_cache_n_samples_in_training.end(); + ++it) + { + size_t n_i = it->first; + // Precomputes zeta and iota for identities with q_i training samples, + // if not already done + if (!it->second) + { + const blitz::Array<double,2>& gamma_a = machine.getAddGamma(n_i); + blitz::Array<double,2>& zeta_a = m_cache_zeta[n_i]; + blitz::Array<double,2>& iota_a = m_cache_iota[n_i]; + bob::math::prod(gamma_a, m_cache_eta, iota_a); + bob::math::prod(etat, iota_a, zeta_a); + zeta_a += alpha; + iota_a = - iota_a; + // Now up to date + it->second = true; + } + } +} + +void bob::trainer::PLDATrainer::precomputeLogLike(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // Precomputes the log determinant of alpha and sigma + machine.precomputeLogLike(); + + // Precomputes the log likelihood constant term + std::map<size_t,bool>::iterator it; + for (it=m_cache_n_samples_in_training.begin(); + it!=m_cache_n_samples_in_training.end(); ++it) + { + // Precomputes the log likelihood constant term for identities with q_i + // training samples, if not already done + machine.getAddLogLikeConstTerm(it->first); + } +} + + +void bob::trainer::PLDATrainer::mStep(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + // 1/ New estimate of B = {F G} + updateFG(machine, v_ar); + + // 2/ New estimate of Sigma + updateSigma(machine, v_ar); + + // 3/ Precomputes new values after updating F, G and sigma + machine.precompute(); + // Precomputes useful variables using current estimates of F,G, and sigma + precomputeFromFGSigma(machine); +} + +void bob::trainer::PLDATrainer::updateFG(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + /// Computes the B matrix (B = [F G]) + /// B = (sum_ij (x_ij-mu).E{z_i}^T).(sum_ij E{z_i.z_i^T})^-1 + + // 1/ Computes the numerator (sum_ij (x_ij-mu).E{z_i}^T) + // Gets the mean mu from the machine + const blitz::Array<double,1>& mu = machine.getMu(); + blitz::Range a = blitz::Range::all(); + m_tmp_D_nfng_2 = 0.; + for (size_t i=0; i<v_ar.size(); ++i) + { + // Loop over the samples + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // m_tmp_D_1 = x_sj-mu + m_tmp_D_1 = v_ar[i](j,a) - mu; + // z_first_order_ij = E{z_ij} + blitz::Array<double,1> z_first_order_ij = m_cache_z_first_order[i](j, a); + // m_tmp_D_nfng_1 = (x_sj-mu).E{z_ij}^T + bob::math::prod(m_tmp_D_1, z_first_order_ij, m_tmp_D_nfng_1); + m_tmp_D_nfng_2 += m_tmp_D_nfng_1; + } + } + + // 2/ Computes the denominator inv(sum_ij E{z_i.z_i^T}) + bob::math::inv(m_cache_sum_z_second_order, m_tmp_nfng_nfng); + + // 3/ Computes numerator / denominator + bob::math::prod(m_tmp_D_nfng_2, m_tmp_nfng_nfng, m_cache_B); + + // 4/ Updates the machine + blitz::Array<double, 2>& F = machine.updateF(); + blitz::Array<double, 2>& G = machine.updateG(); + F = m_cache_B(a, blitz::Range(0, m_dim_f-1)); + G = m_cache_B(a, blitz::Range(m_dim_f, m_dim_f+m_dim_g-1)); +} + +void bob::trainer::PLDATrainer::updateSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar) +{ + /// Computes the Sigma matrix + /// Sigma = 1/IJ sum_ij Diag{(x_ij-mu).(x_ij-mu)^T - B.E{z_i}.(x_ij-mu)^T} + + // Gets the mean mu and the matrix sigma from the machine + blitz::Array<double,1>& sigma = machine.updateSigma(); + const blitz::Array<double,1>& mu = machine.getMu(); + blitz::Range a = blitz::Range::all(); + + sigma = 0.; + size_t n_IJ=0; /// counts the number of samples + for (size_t i=0; i<v_ar.size(); ++i) + { + // Loop over the samples + for (int j=0; j<v_ar[i].extent(0); ++j) + { + // m_tmp_D_1 = x_ij-mu + m_tmp_D_1 = v_ar[i](j,a) - mu; + // sigma += Diag{(x_ij-mu).(x_ij-mu)^T} + sigma += blitz::pow2(m_tmp_D_1); + + // z_first_order_ij = E{z_ij} + blitz::Array<double,1> z_first_order_ij = m_cache_z_first_order[i](j,a); + // m_tmp_D_2 = B.E{z_ij} + bob::math::prod(m_cache_B, z_first_order_ij, m_tmp_D_2); + // sigma -= Diag{B.E{z_ij}.(x_ij-mu) + sigma -= (m_tmp_D_1 * m_tmp_D_2); + ++n_IJ; + } + } + // Normalizes by the number of samples + sigma /= static_cast<double>(n_IJ); + // Apply variance threshold + machine.applyVarianceThreshold(); +} + +double bob::trainer::PLDATrainer::computeLikelihood(bob::machine::PLDABase& machine) +{ + double llh = 0.; + // TODO: implement log likelihood computation + return llh; +} + +void bob::trainer::PLDATrainer::enrol(bob::machine::PLDAMachine& plda_machine, + const blitz::Array<double,2>& ar) const +{ + // Gets dimension + const size_t dim_d = ar.extent(1); + const int n_samples = ar.extent(0); + // Compare the dimensionality from the base trainer/machine with the one + // of the enrollment samples + if (plda_machine.getDimD() != dim_d) { + boost::format m("the extent of the D dimension of the input machine (%u) does not match the input sample (%u)"); + m % plda_machine.getDimD() % dim_d; + throw std::runtime_error(m.str()); + } + const size_t dim_f = plda_machine.getDimF(); + + // Resize working arrays + m_tmp_D_1.resize(dim_d); + m_tmp_D_2.resize(dim_d); + m_tmp_nf_1.resize(dim_f); + + // Useful values from the base machine + blitz::Array<double,1>& weighted_sum = plda_machine.updateWeightedSum(); + const blitz::Array<double,1>& mu = plda_machine.getPLDABase()->getMu(); + const blitz::Array<double,2>& beta = plda_machine.getPLDABase()->getBeta(); + const blitz::Array<double,2>& FtBeta = plda_machine.getPLDABase()->getFtBeta(); + + // Updates the PLDA machine + plda_machine.setNSamples(n_samples); + double terma = 0.; + weighted_sum = 0.; + blitz::Range a = blitz::Range::all(); + for (int i=0; i<n_samples; ++i) { + m_tmp_D_1 = ar(i,a) - mu; + // a/ weighted sum + bob::math::prod(FtBeta, m_tmp_D_1, m_tmp_nf_1); + weighted_sum += m_tmp_nf_1; + // b/ first xi dependent term of the log likelihood + bob::math::prod(beta, m_tmp_D_1, m_tmp_D_2); + terma += -1 / 2. * blitz::sum(m_tmp_D_1 * m_tmp_D_2); + } + plda_machine.setWSumXitBetaXi(terma); + + // Adds the precomputed values for the cases N and N+1 if not already + // in the base machine (used by the forward function, 1 already added) + plda_machine.getAddGamma(n_samples); + plda_machine.getAddLogLikeConstTerm(n_samples); + plda_machine.getAddGamma(n_samples+1); + plda_machine.getAddLogLikeConstTerm(n_samples+1); + plda_machine.setLogLikelihood(plda_machine.computeLogLikelihood( + blitz::Array<double,2>(0,dim_d),true)); +} diff --git a/bob/learn/misc/cpp/WienerMachine.cpp b/bob/learn/misc/cpp/WienerMachine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..921db057537c7bb022e7e3668bf8b5db0e1c29e6 --- /dev/null +++ b/bob/learn/misc/cpp/WienerMachine.cpp @@ -0,0 +1,214 @@ +/** + * @date Fri Sep 30 16:56:06 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Implements a WienerMachine + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.core/array_copy.h> +#include <bob.core/cast.h> +#include <bob.learn.misc/WienerMachine.h> +#include <bob.sp/FFT2D.h> +#include <complex> + +bob::machine::WienerMachine::WienerMachine(): + m_Ps(0,0), + m_variance_threshold(1e-8), + m_Pn(0), + m_W(0,0), + m_fft(), + m_ifft(), + m_buffer1(0,0), m_buffer2(0,0) +{ +} + +bob::machine::WienerMachine::WienerMachine(const blitz::Array<double,2>& Ps, + const double Pn, const double variance_threshold): + m_Ps(bob::core::array::ccopy(Ps)), + m_variance_threshold(variance_threshold), + m_Pn(Pn), + m_W(m_Ps.extent(0),m_Ps.extent(1)), + m_fft(m_Ps.extent(0),m_Ps.extent(1)), + m_ifft(m_Ps.extent(0),m_Ps.extent(1)), + m_buffer1(m_Ps.extent(0),m_Ps.extent(1)), + m_buffer2(m_Ps.extent(0),m_Ps.extent(1)) +{ + computeW(); +} + +bob::machine::WienerMachine::WienerMachine(const size_t height, + const size_t width, const double Pn, const double variance_threshold): + m_Ps(height,width), + m_variance_threshold(variance_threshold), + m_Pn(Pn), + m_W(height,width), + m_fft(height,width), + m_ifft(height,width), + m_buffer1(0,0), m_buffer2(0,0) +{ + m_Ps = 1.; + computeW(); +} + +bob::machine::WienerMachine::WienerMachine(const bob::machine::WienerMachine& other): + m_Ps(bob::core::array::ccopy(other.m_Ps)), + m_variance_threshold(other.m_variance_threshold), + m_Pn(other.m_Pn), + m_W(bob::core::array::ccopy(other.m_W)), + m_fft(other.m_fft), + m_ifft(other.m_ifft), + m_buffer1(m_Ps.extent(0),m_Ps.extent(1)), + m_buffer2(m_Ps.extent(0),m_Ps.extent(1)) +{ +} + +bob::machine::WienerMachine::WienerMachine(bob::io::base::HDF5File& config) +{ + load(config); +} + +bob::machine::WienerMachine::~WienerMachine() {} + +bob::machine::WienerMachine& bob::machine::WienerMachine::operator= +(const bob::machine::WienerMachine& other) +{ + if (this != &other) + { + m_Ps.reference(bob::core::array::ccopy(other.m_Ps)); + m_Pn = other.m_Pn; + m_variance_threshold = other.m_variance_threshold; + m_W.reference(bob::core::array::ccopy(other.m_W)); + m_fft.setShape(m_Ps.extent(0),m_Ps.extent(1)); + m_ifft.setShape(m_Ps.extent(0),m_Ps.extent(1)); + m_buffer1.resize(m_Ps.extent(0),m_Ps.extent(1)); + m_buffer2.resize(m_Ps.extent(0),m_Ps.extent(1)); + } + return *this; +} + +bool bob::machine::WienerMachine::operator==(const bob::machine::WienerMachine& b) const +{ + return bob::core::array::isEqual(m_Ps, b.m_Ps) && + m_variance_threshold == b.m_variance_threshold && + m_Pn == b.m_Pn && + bob::core::array::isEqual(m_W, b.m_W); +} + +bool bob::machine::WienerMachine::operator!=(const bob::machine::WienerMachine& b) const +{ + return !(this->operator==(b)); +} + +bool bob::machine::WienerMachine::is_similar_to(const bob::machine::WienerMachine& b, + const double r_epsilon, const double a_epsilon) const +{ + return bob::core::array::isClose(m_Ps, b.m_Ps, r_epsilon, a_epsilon) && + bob::core::isClose(m_variance_threshold, b.m_variance_threshold, r_epsilon, a_epsilon) && + bob::core::isClose(m_Pn, b.m_Pn, r_epsilon, a_epsilon) && + bob::core::array::isClose(m_W, b.m_W, r_epsilon, a_epsilon); +} + +void bob::machine::WienerMachine::load(bob::io::base::HDF5File& config) +{ + //reads all data directly into the member variables + m_Ps.reference(config.readArray<double,2>("Ps")); + m_Pn = config.read<double>("Pn"); + m_variance_threshold = config.read<double>("variance_threshold"); + m_W.reference(config.readArray<double,2>("W")); + m_fft.setShape(m_Ps.extent(0),m_Ps.extent(1)); + m_ifft.setShape(m_Ps.extent(0),m_Ps.extent(1)); + m_buffer1.resize(m_Ps.extent(0),m_Ps.extent(1)); + m_buffer2.resize(m_Ps.extent(0),m_Ps.extent(1)); +} + +void bob::machine::WienerMachine::resize(const size_t height, + const size_t width) +{ + m_Ps.resizeAndPreserve(height,width); + m_W.resizeAndPreserve(height,width); + m_fft.setShape(height,width); + m_ifft.setShape(height,width); + m_buffer1.resizeAndPreserve(height,width); + m_buffer2.resizeAndPreserve(height,width); +} + +void bob::machine::WienerMachine::save(bob::io::base::HDF5File& config) const +{ + config.setArray("Ps", m_Ps); + config.set("Pn", m_Pn); + config.set("variance_threshold", m_variance_threshold); + config.setArray("W", m_W); +} + +void bob::machine::WienerMachine::computeW() +{ + // W = 1 / (1 + Pn / Ps_thresholded) + m_W = 1. / (1. + m_Pn / m_Ps); +} + + +void bob::machine::WienerMachine::forward_(const blitz::Array<double,2>& input, + blitz::Array<double,2>& output) const +{ + m_fft(bob::core::array::cast<std::complex<double> >(input), m_buffer1); + m_buffer1 *= m_W; + m_ifft(m_buffer1, m_buffer2); + output = blitz::abs(m_buffer2); +} + +void bob::machine::WienerMachine::forward(const blitz::Array<double,2>& input, + blitz::Array<double,2>& output) const +{ + if (m_W.extent(0) != input.extent(0)) { //checks input + boost::format m("number of input rows (%d) is not compatible with internal weight matrix (%d)"); + m % input.extent(0) % m_W.extent(0); + throw std::runtime_error(m.str()); + } + if (m_W.extent(1) != input.extent(1)) { //checks input + boost::format m("number of input columns (%d) is not compatible with internal weight matrix (%d)"); + m % input.extent(1) % m_W.extent(1); + throw std::runtime_error(m.str()); + } + if (m_W.extent(0) != output.extent(0)) { //checks output + boost::format m("number of output rows (%d) is not compatible with internal weight matrix (%d)"); + m % output.extent(0) % m_W.extent(0); + throw std::runtime_error(m.str()); + } + if (m_W.extent(1) != output.extent(1)) { //checks output + boost::format m("number of output columns (%d) is not compatible with internal weight matrix (%d)"); + m % output.extent(1) % m_W.extent(1); + throw std::runtime_error(m.str()); + } + forward_(input, output); +} + +void bob::machine::WienerMachine::setVarianceThreshold( + const double variance_threshold) +{ + m_variance_threshold = variance_threshold; + applyVarianceThreshold(); + computeW(); +} + +void bob::machine::WienerMachine::setPs(const blitz::Array<double,2>& Ps) +{ + if (m_Ps.extent(0) != Ps.extent(0)) { + boost::format m("number of rows (%d) for input `Ps' does not match the expected (internal) size (%d)"); + m % Ps.extent(0) % m_Ps.extent(0); + throw std::runtime_error(m.str()); + } + if (m_Ps.extent(1) != Ps.extent(1)) { + boost::format m("number of columns (%d) for input `Ps' does not match the expected (internal) size (%d)"); + m % Ps.extent(1) % m_Ps.extent(1); + throw std::runtime_error(m.str()); + } + m_Ps = bob::core::array::ccopy(Ps); + computeW(); +} + +void bob::machine::WienerMachine::applyVarianceThreshold() +{ + m_Ps = blitz::where(m_Ps < m_variance_threshold, m_variance_threshold, m_Ps); +} diff --git a/bob/learn/misc/cpp/WienerTrainer.cpp b/bob/learn/misc/cpp/WienerTrainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..697082c359da217bcf45dc0ef4886d9e331ade9a --- /dev/null +++ b/bob/learn/misc/cpp/WienerTrainer.cpp @@ -0,0 +1,101 @@ +/** + * @date Fri Sep 30 16:58:42 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/WienerTrainer.h> +#include <bob.core/cast.h> +#include <bob.sp/FFT2D.h> +#include <complex> + +bob::trainer::WienerTrainer::WienerTrainer() +{ +} + +bob::trainer::WienerTrainer::WienerTrainer(const bob::trainer::WienerTrainer& other) +{ +} + +bob::trainer::WienerTrainer::~WienerTrainer() +{ +} + +bob::trainer::WienerTrainer& bob::trainer::WienerTrainer::operator= +(const bob::trainer::WienerTrainer& other) +{ + return *this; +} + +bool bob::trainer::WienerTrainer::operator== + (const bob::trainer::WienerTrainer& other) const +{ + return true; +} + +bool bob::trainer::WienerTrainer::operator!= + (const bob::trainer::WienerTrainer& other) const +{ + return !(this->operator==(other)); +} + +bool bob::trainer::WienerTrainer::is_similar_to + (const bob::trainer::WienerTrainer& other, const double r_epsilon, + const double a_epsilon) const +{ + return true; +} + +void bob::trainer::WienerTrainer::train(bob::machine::WienerMachine& machine, + const blitz::Array<double,3>& ar) +{ + // Data is checked now and conforms, just proceed w/o any further checks. + const size_t n_samples = ar.extent(0); + const size_t height = ar.extent(1); + const size_t width = ar.extent(2); + // machine dimensions + const size_t height_m = machine.getHeight(); + const size_t width_m = machine.getWidth(); + + // Checks that the dimensions are matching + if (height != height_m) { + boost::format m("number of inputs (height) for machine (%u) does not match number of columns at input parameter (%u)"); + m % height_m % height; + throw std::runtime_error(m.str()); + } + if (width != width_m) { + boost::format m("number of inputs (width) for machine (%u) does not match number of depths at input parameter (%u)"); + m % width_m % width; + throw std::runtime_error(m.str()); + } + + // FFT2D + bob::sp::FFT2D fft2d(height, width); + + // Loads the data + blitz::Array<double,3> data(height, width, n_samples); + blitz::Array<std::complex<double>,2> sample_fft(height, width); + blitz::Range all = blitz::Range::all(); + for (size_t i=0; i<n_samples; ++i) { + blitz::Array<double,2> sample = ar(i,all,all); + blitz::Array<std::complex<double>,2> sample_c = bob::core::array::cast<std::complex<double> >(sample); + fft2d(sample_c, sample_fft); + data(all,all,i) = blitz::abs(sample_fft); + } + // Computes the mean of the training data + blitz::Array<double,2> tmp(height,width); + blitz::thirdIndex k; + tmp = blitz::mean(data,k); + // Removes the mean from the data + for (size_t i=0; i<n_samples; ++i) { + data(all,all,i) -= tmp; + } + // Computes power of 2 values + data *= data; + // Sums to get the variance + tmp = blitz::sum(data,k) / n_samples; + + // sets the Wiener machine with the results: + machine.setPs(tmp); +} diff --git a/bob/learn/misc/cpp/ZTNorm.cpp b/bob/learn/misc/cpp/ZTNorm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2a99ecaaa0bbb35aa65053c15f5c208d9cec1de6 --- /dev/null +++ b/bob/learn/misc/cpp/ZTNorm.cpp @@ -0,0 +1,187 @@ +/** + * @date Tue Jul 19 15:33:20 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#include <bob.learn.misc/ZTNorm.h> +#include <bob.core/assert.h> +#include <limits> + +namespace bob { +namespace machine { + +namespace detail { + void ztNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>* rawscores_zprobes_vs_models, + const blitz::Array<double,2>* rawscores_probes_vs_tmodels, + const blitz::Array<double,2>* rawscores_zprobes_vs_tmodels, + const blitz::Array<bool,2>* mask_zprobes_vs_tmodels_istruetrial, + blitz::Array<double,2>& scores) + { + // Rename variables + const blitz::Array<double,2>& A = rawscores_probes_vs_models; + const blitz::Array<double,2>* B = rawscores_zprobes_vs_models; + const blitz::Array<double,2>* C = rawscores_probes_vs_tmodels; + const blitz::Array<double,2>* D = rawscores_zprobes_vs_tmodels; + + // Compute the sizes + int size_eval = A.extent(0); + int size_enrol = A.extent(1); + int size_tnorm = (C ? C->extent(0) : 0); + int size_znorm = (B ? B->extent(1) : 0); + + // Check the inputs + bob::core::array::assertSameDimensionLength(A.extent(0), size_eval); + bob::core::array::assertSameDimensionLength(A.extent(1), size_enrol); + + if (B) { + bob::core::array::assertSameDimensionLength(B->extent(1), size_znorm); + if (size_znorm > 0) + bob::core::array::assertSameDimensionLength(B->extent(0), size_eval); + } + + if (C) { + bob::core::array::assertSameDimensionLength(C->extent(0), size_tnorm); + if (size_tnorm > 0) + bob::core::array::assertSameDimensionLength(C->extent(1), size_enrol); + } + + if (D && size_znorm > 0 && size_tnorm > 0) { + bob::core::array::assertSameDimensionLength(D->extent(0), size_tnorm); + bob::core::array::assertSameDimensionLength(D->extent(1), size_znorm); + } + + if (mask_zprobes_vs_tmodels_istruetrial) { + bob::core::array::assertSameDimensionLength(mask_zprobes_vs_tmodels_istruetrial->extent(0), size_tnorm); + bob::core::array::assertSameDimensionLength(mask_zprobes_vs_tmodels_istruetrial->extent(1), size_znorm); + } + + bob::core::array::assertSameDimensionLength(scores.extent(0), size_eval); + bob::core::array::assertSameDimensionLength(scores.extent(1), size_enrol); + + // Declare needed IndexPlaceholder + blitz::firstIndex ii; + blitz::secondIndex jj; + + // Constant to check if the std is close to 0. + const double eps = std::numeric_limits<double>::min(); + + // zA + blitz::Array<double,2> zA(A.shape()); + if (B && size_znorm > 0) { + // Znorm --> zA = (A - mean(B) ) / std(B) [znorm on oringinal scores] + // mean(B) + blitz::Array<double,1> mean_B(blitz::mean(*B, jj)); + // std(B) + blitz::Array<double,2> B2n(B->shape()); + B2n = blitz::pow2((*B)(ii, jj) - mean_B(ii)); + blitz::Array<double,1> std_B(B->extent(0)); + if(size_znorm>1) + std_B = blitz::sqrt(blitz::sum(B2n, jj) / (size_znorm - 1)); + else // 1 single value -> std = 0 + std_B = 0; + std_B = blitz::where( std_B <= eps, 1., std_B); + + zA = (A(ii, jj) - mean_B(ii)) / std_B(ii); + } + else + zA = A; + + blitz::Array<double,2> zC(size_tnorm, size_enrol); + if (D && size_tnorm > 0 && size_znorm > 0) { + blitz::Array<double,1> mean_Dimp(size_tnorm); + blitz::Array<double,1> std_Dimp(size_tnorm); + + // Compute mean_Dimp and std_Dimp = D only with impostors + for (int i = 0; i < size_tnorm; ++i) { + double sum = 0; + double sumsq = 0; + double count = 0; + for (int j = 0; j < size_znorm; ++j) { + bool keep; + // The second part is never executed if mask_zprobes_vs_tmodels_istruetrial==NULL + keep = (mask_zprobes_vs_tmodels_istruetrial == NULL) || !(*mask_zprobes_vs_tmodels_istruetrial)(i, j); //tnorm_models_spk_ids(i) != znorm_tests_spk_ids(j); + + double value = keep * (*D)(i, j); + sum += value; + sumsq += value*value; + count += keep; + } + + double mean = sum / count; + mean_Dimp(i) = mean; + if (count > 1) + std_Dimp(i) = sqrt((sumsq - count * mean * mean) / (count -1)); + else // 1 single value -> std = 0 + std_Dimp(i) = 0; + } + + // zC = (C - mean(D)) / std(D) [znorm the tnorm scores] + std_Dimp = blitz::where( std_Dimp <= eps, 1., std_Dimp); + zC = ((*C)(ii, jj) - mean_Dimp(ii)) / std_Dimp(ii); + } + else if (C && size_tnorm > 0) + zC = *C; + + if (C && size_tnorm > 0) + { + blitz::Array<double,1> mean_zC(size_enrol); + blitz::Array<double,1> std_zC(size_enrol); + + // ztA = (zA - mean(zC)) / std(zC) [ztnorm on eval scores] + mean_zC = blitz::mean(zC(jj, ii), jj); + if (size_tnorm > 1) + std_zC = sqrt(blitz::sum(pow(zC(jj, ii) - mean_zC(ii), 2) , jj) / (size_tnorm - 1)); + else // 1 single value -> std = 0 + std_zC = 0; + std_zC = blitz::where( std_zC <= eps, 1., std_zC); + + // Normalised scores + scores = (zA(ii, jj) - mean_zC(jj)) / std_zC(jj); + } + else + scores = zA; + } +} + +void ztNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_zprobes_vs_models, + const blitz::Array<double,2>& rawscores_probes_vs_tmodels, + const blitz::Array<double,2>& rawscores_zprobes_vs_tmodels, + const blitz::Array<bool,2>& mask_zprobes_vs_tmodels_istruetrial, + blitz::Array<double,2>& scores) +{ + detail::ztNorm(rawscores_probes_vs_models, &rawscores_zprobes_vs_models, &rawscores_probes_vs_tmodels, + &rawscores_zprobes_vs_tmodels, &mask_zprobes_vs_tmodels_istruetrial, scores); +} + +void ztNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_zprobes_vs_models, + const blitz::Array<double,2>& rawscores_probes_vs_tmodels, + const blitz::Array<double,2>& rawscores_zprobes_vs_tmodels, + blitz::Array<double,2>& scores) +{ + detail::ztNorm(rawscores_probes_vs_models, &rawscores_zprobes_vs_models, &rawscores_probes_vs_tmodels, + &rawscores_zprobes_vs_tmodels, NULL, scores); +} + +void tNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_probes_vs_tmodels, + blitz::Array<double,2>& scores) +{ + detail::ztNorm(rawscores_probes_vs_models, NULL, &rawscores_probes_vs_tmodels, + NULL, NULL, scores); +} + +void zNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_zprobes_vs_models, + blitz::Array<double,2>& scores) +{ + detail::ztNorm(rawscores_probes_vs_models, &rawscores_zprobes_vs_models, NULL, + NULL, NULL, scores); +} + +}} diff --git a/bob/learn/misc/include/bob.learn.misc/BICMachine.h b/bob/learn/misc/include/bob.learn.misc/BICMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..21c6e88f8bbb9f6388099c1ae31bebdbf6a0d8a2 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/BICMachine.h @@ -0,0 +1,117 @@ +/** + * @date Tue Jun 5 16:54:27 CEST 2012 + * @author Manuel Guenther <Manuel.Guenther@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + + +#ifndef BOB_MACHINE_BICMACHINE_H +#define BOB_MACHINE_BICMACHINE_H + +#include <blitz/array.h> +#include <bob.io.base/HDF5File.h> +#include <bob.learn.misc/Machine.h> + +namespace bob { namespace machine { + /** + * @ingroup MACHINE + * @{ + */ + + /** + * This class computes the Bayesian Intrapersonal/Extrapersonal Classifier (BIC), + * (see "Beyond Eigenfaces: Probabilistic Matching for Face Recognition" from Moghaddam, Wahid and Pentland) + * which estimates the posterior probability that the given <b>image difference vector</b> + * is of the intrapersonal class, i.e., both images stem from the same person. + * + * There are two possible implementations of the BIC: + * <ul> + * <li> "The Bayesian Intrapersonal/Extrapersonal Classifier" from Teixeira.<br> + * A full projection of the data is performed. No prior for the classes has to be selected.</li> + * + * <li> "Face Detection and Recognition using Maximum Likelihood Classifiers on Gabor Graphs" from Günther and Würtz.<br> + * Only mean and variance of the difference vectors are calculated. There is no subspace truncation and no priors.</li> + * </ul> + * In any of the two implementations, the resulting score (using the forward() method) is a log-likelihood estimate, + * using Mahalanobis(-like) distance measures. + * + */ + class BICMachine: public Machine<blitz::Array<double,1>, double> + { + public: + //! generates an empty BIC machine + BICMachine(bool use_DFFS = false); + + //! Copy constructor + BICMachine(const BICMachine& other); + + //! Assignment Operator + BICMachine& operator =(const BICMachine &other); + + //! Equality operator + bool operator ==(const BICMachine& other) const; + + //! Inequality operator + bool operator !=(const BICMachine& other) const; + + //! Similarity operator + bool is_similar_to(const BICMachine& other, const double r_epsilon=1e-5, const double a_epsilon=1e-8) const; + + //! computes the BIC probability score for the given input difference vector + void forward_(const blitz::Array<double,1>& input, double& output) const; + + //! performs some checks before calling the forward_ method + void forward (const blitz::Array<double,1>& input, double& output) const; + + //! sets the IEC vectors of the given class + void setIEC(bool clazz, const blitz::Array<double,1>& mean, const blitz::Array<double,1>& variances, bool copy_data = false); + + //! sets the BIC projection details of the given class + void setBIC(bool clazz, const blitz::Array<double,1>& mean, const blitz::Array<double,1>& variances, const blitz::Array<double,2>& projection, const double rho, bool copy_data = false); + + //! loads this machine from the given hdf5 file. + void load (bob::io::base::HDF5File& config); + + //! saves this machine to the given hdf5 file. + void save (bob::io::base::HDF5File& config) const; + + //! Use the Distance From Feature Space + void use_DFFS(bool use_DFFS = true); + + //! Use the Distance From Feature Space + bool use_DFFS() const {return m_use_DFFS;} + + private: + + //! initializes internal data storages for the given class + void initialize(bool clazz, int input_length, int projected_length); + + //! project data? + bool m_project_data; + + //! mean vectors + blitz::Array<double, 1> m_mu_I, m_mu_E; + //! variances (eigenvalues) + blitz::Array<double, 1> m_lambda_I, m_lambda_E; + + /// + // only required when projection is enabled + //! add the distance from feature space? + bool m_use_DFFS; + //! projection matrices (PCA) + blitz::Array<double, 2> m_Phi_I, m_Phi_E; + //! averaged eigenvalues to calculate DFFS + double m_rho_I, m_rho_E; + //! temporary storage + mutable blitz::Array<double, 1> m_diff_I, m_diff_E; + mutable blitz::Array<double, 1> m_proj_I, m_proj_E; + + }; + + /** + * @} + */ +}} + +#endif // BOB_MACHINE_BICMACHINE_H diff --git a/bob/learn/misc/include/bob.learn.misc/BICTrainer.h b/bob/learn/misc/include/bob.learn.misc/BICTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..81e13531f9a9c26a195a5654ea9e19f87d988ca5 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/BICTrainer.h @@ -0,0 +1,48 @@ +/** + * @date Wed Jun 6 10:29:09 CEST 2012 + * @author Manuel Guenther <Manuel.Guenther@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_BICTRAINER_H +#define BOB_TRAINER_BICTRAINER_H + +#include <bob.learn.misc/BICMachine.h> + +namespace bob { namespace trainer { + /** + * @ingroup TRAINER + * @{ + */ + + class BICTrainer { + public: + //! initializes a BICTrainer to train IEC (without subspace estimation) + BICTrainer() : m_M_I(0), m_M_E(0) {} + //! initializes a BICTrainer to train BIC (including subspace truncation) + BICTrainer(int intra_dim, int extra_dim) : m_M_I(intra_dim), m_M_E(extra_dim) {} + + //! trains the intrapersonal and extrapersonal classes of the given BICMachine + void train(bob::machine::BICMachine& machine, const blitz::Array<double,2>& intra_differences, const blitz::Array<double,2>& extra_differences) const { + train_single(false, machine, intra_differences); + train_single(true, machine, extra_differences); + } + + //! trains the intrapersonal or the extrapersonal class of the given BICMachine + void train_single(bool clazz, bob::machine::BICMachine& machine, const blitz::Array<double,2>& differences) const; + + private: + + //! dimensions of the intrapersonal and extrapersonal subspace; + //! zero if training IEC. + int m_M_I, m_M_E; + }; + + /** + * @} + */ +}} + + +#endif // BOB_TRAINER_BICTRAINER_H diff --git a/bob/learn/misc/include/bob.learn.misc/EMPCATrainer.h b/bob/learn/misc/include/bob.learn.misc/EMPCATrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..81c5ae150dd09fb89b76233d4a6ddaaa25e8b75f --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/EMPCATrainer.h @@ -0,0 +1,197 @@ +/** + * @date Tue Oct 11 12:18:23 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Expectation Maximization Algorithm for Principal Component + * Analysis + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_EMPCA_TRAINER_H +#define BOB_TRAINER_EMPCA_TRAINER_H + +#include <bob.learn.misc/EMTrainer.h> +#include <bob.learn.linear/machine.h> +#include <blitz/array.h> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief Trains a linear machine using an Expectation-Maximization algorithm + * on the given dataset.\n + * References:\n + * 1. "Probabilistic Principal Component Analysis", + * Michael Tipping and Christopher Bishop, + * Journal of the Royal Statistical Society, + * Series B, 61, Part 3, pp. 611–622\n + * 2. "EM Algorithms for PCA and SPCA", + * Sam Roweis, Neural Information Processing Systems 10 (NIPS'97), + * pp.626-632 (Sensible Principal Component Analysis part)\n + * + * Notations used are the ones from reference 1.\n + * The probabilistic model is given by: \f$t = W x + \mu + \epsilon\f$\n + * - \f$t\f$ is the observed data (dimension \f$f\f$)\n + * - \f$W\f$ is a projection matrix (dimension \f$f \times d\f$)\n + * - \f$x\f$ is the projected data (dimension \f$d < f\f$)\n + * - \f$\mu\f$ is the mean of the data (dimension \f$f\f$)\n + * - \f$\epsilon\f$ is the noise of the data (dimension \f$f\f$) + * Gaussian with zero-mean and covariance matrix \f$\sigma^2 Id\f$ + */ +class EMPCATrainer: public EMTrainer<bob::learn::linear::Machine, blitz::Array<double,2> > +{ + public: //api + /** + * @brief Initializes a new EM PCA trainer. The training stage will place the + * resulting components in the linear machine and set it up to + * extract the variable means automatically. + */ + EMPCATrainer(double convergence_threshold=0.001, + size_t max_iterations=10, bool compute_likelihood=true); + + /** + * @brief Copy constructor + */ + EMPCATrainer(const EMPCATrainer& other); + + /** + * @brief (virtual) Destructor + */ + virtual ~EMPCATrainer(); + + /** + * @brief Assignment operator + */ + EMPCATrainer& operator=(const EMPCATrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const EMPCATrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const EMPCATrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const EMPCATrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief This methods performs some initialization before the EM loop. + */ + virtual void initialize(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + /** + * @brief This methods performs some actions after the EM loop. + */ + virtual void finalize(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + + /** + * @brief Calculates and saves statistics across the dataset, and saves + * these as m_z_{first,second}_order. + * + * The statistics will be used in the mStep() that follows. + */ + virtual void eStep(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + + /** + * @brief Performs a maximization step to update the parameters of the + * factor analysis model. + */ + virtual void mStep(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + + /** + * @brief Computes the average log likelihood using the current estimates + * of the latent variables. + */ + virtual double computeLikelihood(bob::learn::linear::Machine& machine); + + /** + * @brief Sets \f$\sigma^2\f$ (Mostly for test purpose) + */ + void setSigma2(double sigma2) { m_sigma2 = sigma2; } + + /** + * @brief Gets \f$\sigma^2\f$ (Mostly for test purpose) + */ + double getSigma2() const { return m_sigma2; } + + private: //representation + blitz::Array<double,2> m_S; /// Covariance of the training data (required only if we need to compute the log likelihood) + blitz::Array<double,2> m_z_first_order; /// Current mean of the \f$z_{n}\f$ latent variable + blitz::Array<double,3> m_z_second_order; /// Current covariance of the \f$z_{n}\f$ latent variable + blitz::Array<double,2> m_inW; /// The matrix product \f$W^T W\f$ + blitz::Array<double,2> m_invM; /// The matrix \f$inv(M)\f$, where \f$M = W^T W + \sigma^2 Id\f$ + double m_sigma2; /// The variance \f$sigma^2\f$ of the noise epsilon of the probabilistic model + double m_f_log2pi; /// The constant \f$n_{features} log(2*\pi)\f$ used during the likelihood computation + + // Working arrays + mutable blitz::Array<double,2> m_tmp_dxf; /// size dimensionality x n_features + mutable blitz::Array<double,1> m_tmp_d; /// size dimensionality + mutable blitz::Array<double,1> m_tmp_f; /// size n_features + mutable blitz::Array<double,2> m_tmp_dxd_1; /// size dimensionality x dimensionality + mutable blitz::Array<double,2> m_tmp_dxd_2; /// size dimensionality x dimensionality + mutable blitz::Array<double,2> m_tmp_fxd_1; /// size n_features x dimensionality + mutable blitz::Array<double,2> m_tmp_fxd_2; /// size n_features x dimensionality + mutable blitz::Array<double,2> m_tmp_fxf_1; /// size n_features x n_features + mutable blitz::Array<double,2> m_tmp_fxf_2; /// size n_features x n_features + + + /** + * @brief Initializes/resizes the (array) members + */ + void initMembers(const bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + /** + * @brief Computes the mean and the variance (if required) of the training + * data + */ + void computeMeanVariance(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + /** + * @brief Random initialization of \f$W\f$ and \f$sigma^2\f$. + * W is the projection matrix (from the LinearMachine) + */ + void initRandomWSigma2(bob::learn::linear::Machine& machine); + /** + * @brief Computes the product \f$W^T W\f$. + * \f$W\f$ is the projection matrix (from the LinearMachine) + */ + void computeWtW(bob::learn::linear::Machine& machine); + /** + * @brief Computes the inverse of \f$M\f$ matrix, where + * \f$M = W^T W + \sigma^2 Id\f$. + * \f$W\f$ is the projection matrix (from the LinearMachine) + */ + void computeInvM(); + /** + * @brief M-Step (part 1): Computes the new estimate of \f$W\f$ using the + * new estimated statistics. + */ + void updateW(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); + /** + * @brief M-Step (part 2): Computes the new estimate of \f$\sigma^2\f$ using + * the new estimated statistics. + */ + void updateSigma2(bob::learn::linear::Machine& machine, + const blitz::Array<double,2>& ar); +}; + +/** + * @} + */ +}} + +#endif /* BOB_TRAINER_EMPCA_TRAINER_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/EMTrainer.h b/bob/learn/misc/include/bob.learn.misc/EMTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..9cf5424de4c15b815df0e523f7216dbbd2c49af1 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/EMTrainer.h @@ -0,0 +1,263 @@ +/** + * @date Tue Jan 18 17:07:26 2011 +0100 + * @author André Anjos <andre.anjos@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Base class for Expectation-Maximization-like algorithms + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + + +#ifndef BOB_TRAINER_EMTRAINER_H +#define BOB_TRAINER_EMTRAINER_H + +#include <bob.learn.misc/Trainer.h> + +#include <limits> +#include <bob.core/check.h> +#include <bob.core/logging.h> +#include <boost/shared_ptr.hpp> +#include <boost/random.hpp> + + +namespace bob { namespace trainer { + /** + * @ingroup TRAINER + * @{ + */ + + /** + * @brief This class implements the general Expectation-maximization algorithm. + * @details See Section 9.3 of Bishop, "Pattern recognition and machine learning", 2006 + * Derived classes must implement the initialize(), eStep(), mStep() and finalize() methods. + */ + template<class T_machine, class T_sampler> + class EMTrainer: virtual public Trainer<T_machine, T_sampler> + { + public: + /** + * @brief Destructor + */ + virtual ~EMTrainer() {} + + /** + * @brief Assignment operator + */ + EMTrainer& operator=(const EMTrainer& other) + { + if(this != &other) + { + m_compute_likelihood = other.m_compute_likelihood; + m_convergence_threshold = other.m_convergence_threshold; + m_max_iterations = other.m_max_iterations; + m_rng = other.m_rng; + } + return *this; + } + + /** + * @brief Equal to + */ + bool operator==(const EMTrainer& b) const { + return m_compute_likelihood == b.m_compute_likelihood && + m_convergence_threshold == b.m_convergence_threshold && + m_max_iterations == b.m_max_iterations && + *m_rng == *(b.m_rng); + } + + /** + * @brief Not equal to + */ + bool operator!=(const EMTrainer& b) const + { return !(this->operator==(b)); } + + /** + * @brief Similarity operator + */ + bool is_similar_to(const EMTrainer& b, + const double r_epsilon=1e-5, const double a_epsilon=1e-8) const + { + return m_compute_likelihood == b.m_compute_likelihood && + bob::core::isClose(m_convergence_threshold, b.m_convergence_threshold, r_epsilon, a_epsilon) && + m_max_iterations == b.m_max_iterations && + *m_rng == *(b.m_rng); + } + + /** + * @brief The name for this trainer + */ + virtual std::string name() const { return "EMTrainer"; } + + /** + * @brief The main method to train a machine using an EM-based algorithm + */ + virtual void train(T_machine& machine, const T_sampler& sampler) + { + bob::core::info << "# " << name() << ":" << std::endl; + + /* + // Check that the machine and dataset have the same feature dimensionality + if (!checkForDimensionalityMatch()) + { + bob::core::error << "mismatch in dimensionality of dataset and machine" << endl; + return false; + } + */ + + // Initialization + initialize(machine, sampler); + // Do the Expectation-Maximization algorithm + double average_output_previous; + double average_output = - std::numeric_limits<double>::max(); + + // - eStep + eStep(machine, sampler); + + if(m_compute_likelihood) + average_output = computeLikelihood(machine); + + // - iterates... + for(size_t iter=0; ; ++iter) { + + // - saves average output from last iteration + average_output_previous = average_output; + + // - mStep + mStep(machine, sampler); + + // - eStep + eStep(machine, sampler); + + // - Computes log likelihood if required + if(m_compute_likelihood) { + average_output = computeLikelihood(machine); + + bob::core::info << "# Iteration " << iter+1 << ": " + << average_output_previous << " -> " + << average_output << std::endl; + + // - Terminates if converged (and likelihood computation is set) + if(fabs((average_output_previous - average_output)/average_output_previous) <= m_convergence_threshold) { + bob::core::info << "# EM terminated: likelihood converged" << std::endl; + break; + } + } + else + bob::core::info << "# Iteration " << iter+1 << std::endl; + + // - Terminates if maximum number of iterations has been reached + if(m_max_iterations > 0 && iter+1 >= m_max_iterations) { + bob::core::info << "# EM terminated: maximum number of iterations reached." << std::endl; + break; + } + } + + // Finalization + finalize(machine, sampler); + } + + /** + * @brief This method is called before the EM algorithm to initialize + * variables. + */ + virtual void initialize(T_machine& machine, const T_sampler& sampler) = 0; + + /** + * @brief Computes the hidden variable distribution (or the sufficient + * statistics) given the Machine parameters. + */ + virtual void eStep(T_machine& machine, const T_sampler& sampler) = 0; + + /** + * @brief Updates the Machine parameters given the hidden variable + * distribution (or the sufficient statistics). + */ + virtual void mStep(T_machine& machine, const T_sampler& sampler) = 0; + + /** + * @return The average output of the Machine across the dataset. + * The EM algorithm will terminate once the change in average_output + * is less than the convergence_threshold. + */ + virtual double computeLikelihood(T_machine& machine) = 0; + + /** + * @brief This method is called after the EM-loop + */ + virtual void finalize(T_machine& machine, const T_sampler& sampler) = 0; + + /** + * @brief Sets whether the likelihood is computed or not at each iteration + */ + void setComputeLikelihood(bool compute) + { m_compute_likelihood = compute; } + + /** + * @brief Tells whether the likelihood is computed or not at each iteration + */ + bool getComputeLikelihood() const + { return m_compute_likelihood; } + + /** + * @brief Sets the convergence threshold + */ + void setConvergenceThreshold(double threshold) + { m_convergence_threshold = threshold; } + + /** + * @brief Gets the convergence threshold + */ + double getConvergenceThreshold() const + { return m_convergence_threshold; } + + /** + * @brief Sets the maximum number of EM iterations + */ + void setMaxIterations(size_t max_iterations) + { m_max_iterations = max_iterations; } + + /** + * @brief Gets the maximum number of EM iterations + */ + size_t getMaxIterations() const + { return m_max_iterations; } + + /** + * @brief Sets the Random Number Generator + */ + void setRng(const boost::shared_ptr<boost::mt19937> rng) + { m_rng = rng; } + + /** + * @brief Gets the Random Number Generator + */ + const boost::shared_ptr<boost::mt19937> getRng() const + { return m_rng; } + + protected: + bool m_compute_likelihood; ///< whether lilelihood is computed during the EM loop or not + double m_convergence_threshold; ///< convergence threshold + size_t m_max_iterations; ///< maximum number of EM iterations + boost::shared_ptr<boost::mt19937> m_rng; ///< The random number generator for the inialization + + /** + * @brief Protected constructor to be called in the constructor of derived + * classes + */ + EMTrainer(const double convergence_threshold = 0.001, + const size_t max_iterations = 10, const bool compute_likelihood = true): + m_compute_likelihood(compute_likelihood), + m_convergence_threshold(convergence_threshold), + m_max_iterations(max_iterations), + m_rng(new boost::mt19937()) + { + } + }; + + /** + * @} + */ +}} + +#endif // BOB_TRAINER_EMTRAINER_H diff --git a/bob/learn/misc/include/bob.learn.misc/GMMMachine.h b/bob/learn/misc/include/bob.learn.misc/GMMMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..1b9acafc3cca8514fd0dbe60815ddfb7186df9f1 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/GMMMachine.h @@ -0,0 +1,381 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief This class implements a multivariate diagonal Gaussian distribution. + * @details See Section 2.3.9 of Bishop, "Pattern recognition and machine learning", 2006 + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_GMMMACHINE_H +#define BOB_MACHINE_GMMMACHINE_H + +#include <bob.learn.misc/Machine.h> +#include <bob.learn.misc/Gaussian.h> +#include <bob.learn.misc/GMMStats.h> +#include <bob.io.base/HDF5File.h> +#include <iostream> +#include <boost/shared_ptr.hpp> +#include <vector> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief This class implements a multivariate diagonal Gaussian distribution. + * @details See Section 2.3.9 of Bishop, "Pattern recognition and machine learning", 2006 + */ +class GMMMachine: public Machine<blitz::Array<double,1>, double> +{ + public: + /** + * Default constructor + */ + GMMMachine(); + + /** + * Constructor + * @param[in] n_gaussians The number of Gaussian components + * @param[in] n_inputs The feature dimensionality + */ + GMMMachine(const size_t n_gaussians, const size_t n_inputs); + + /** + * Copy constructor + * (Needed because the GMM points to its constituent Gaussian members) + */ + GMMMachine(const GMMMachine& other); + + /** + * Constructor from a Configuration + */ + GMMMachine(bob::io::base::HDF5File& config); + + /** + * Assignment + */ + GMMMachine& operator=(const GMMMachine &other); + + /** + * Equal to + */ + bool operator==(const GMMMachine& b) const; + + /** + * Not equal to + */ + bool operator!=(const GMMMachine& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const GMMMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * Destructor + */ + virtual ~GMMMachine(); + + /** + * Set the feature dimensionality + */ + void setNInputs(const size_t n_inputs); + + /** + * Get number of inputs + */ + inline size_t getNInputs() const + { return m_n_inputs; } + + /** + * Reset the input dimensionality, and the number of Gaussian components. + * Initialises the weights to uniform distribution. + * @param n_gaussians The number of Gaussian components + * @param n_inputs The feature dimensionality + */ + void resize(const size_t n_gaussians, const size_t n_inputs); + + /** + * Set the weights + */ + void setWeights(const blitz::Array<double,1> &weights); + + /** + * Get the weights ("mixing coefficients") of the Gaussian components + */ + inline const blitz::Array<double,1>& getWeights() const + { return m_weights; } + + /** + * Get the weights in order to be updated + * ("mixing coefficients") of the Gaussian components + * @warning Only trainers should use this function for efficiency reason + */ + inline blitz::Array<double,1>& updateWeights() + { return m_weights; } + + /** + * Get the logarithm of the weights of the Gaussian components + */ + inline const blitz::Array<double,1>& getLogWeights() const + { return m_cache_log_weights; } + + /** + * Update the log of the weights in cache + * @warning Should be used by trainer only when using updateWeights() + */ + void recomputeLogWeights() const; + + /** + * Set the means + */ + void setMeans(const blitz::Array<double,2> &means); + /** + * Set the means from a supervector + */ + void setMeanSupervector(const blitz::Array<double,1> &mean_supervector); + /** + * Get the means + */ + void getMeans(blitz::Array<double,2> &means) const; + /** + * Get the mean supervector + */ + void getMeanSupervector(blitz::Array<double,1> &mean_supervector) const; + /** + * Returns a const reference to the supervector (Put in cache) + */ + const blitz::Array<double,1>& getMeanSupervector() const; + + /** + * Set the variances + */ + void setVariances(const blitz::Array<double,2> &variances); + /** + * Set the variances from a supervector + */ + void setVarianceSupervector(const blitz::Array<double,1> &variance_supervector); + /** + * Get the variances + */ + void getVariances(blitz::Array<double,2> &variances) const; + /** + * Get the variance supervector + */ + void getVarianceSupervector(blitz::Array<double,1> &variance_supervector) const; + /** + * Returns a const reference to the supervector (Put in cache) + */ + const blitz::Array<double,1>& getVarianceSupervector() const; + + /** + * Set the variance flooring thresholds in each dimension + */ + void setVarianceThresholds(const double value); + /** + * Set the variance flooring thresholds in each dimension + * (equal for all Gaussian components) + */ + void setVarianceThresholds(blitz::Array<double,1> variance_thresholds); + /** + * Set the variance flooring thresholds for each Gaussian in each dimension + */ + void setVarianceThresholds(const blitz::Array<double,2> &variance_thresholds); + /** + * Get the variance flooring thresholds for each Gaussian in each dimension + */ + void getVarianceThresholds(blitz::Array<double,2> &variance_thresholds) const; + + /** + * Output the log likelihood of the sample, x, i.e. log(p(x|GMMMachine)) + * @param[in] x The sample + * @param[out] log_weighted_gaussian_likelihoods For each Gaussian, i: log(weight_i*p(x|Gaussian_i)) + * @return The GMMMachine log likelihood, i.e. log(p(x|GMMMachine)) + * Dimensions of the parameters are checked + */ + double logLikelihood(const blitz::Array<double, 1> &x, blitz::Array<double,1> &log_weighted_gaussian_likelihoods) const; + + /** + * Output the log likelihood of the sample, x, i.e. log(p(x|GMMMachine)) + * @param[in] x The sample + * @param[out] log_weighted_gaussian_likelihoods For each Gaussian, i: log(weight_i*p(x|Gaussian_i)) + * @return The GMMMachine log likelihood, i.e. log(p(x|GMMMachine)) + * @warning Dimensions of the parameters are not checked + */ + double logLikelihood_(const blitz::Array<double, 1> &x, blitz::Array<double,1> &log_weighted_gaussian_likelihoods) const; + + /** + * Output the log likelihood of the sample, x, i.e. log(p(x|GMM)) + * @param[in] x The sample + * Dimension of the input is checked + */ + double logLikelihood(const blitz::Array<double, 1> &x) const; + + /** + * Output the log likelihood of the sample, x, i.e. log(p(x|GMM)) + * @param[in] x The sample + * @warning Dimension of the input is not checked + */ + double logLikelihood_(const blitz::Array<double, 1> &x) const; + + /** + * Output the log likelihood of the sample, x + * (overrides Machine::forward) + * Dimension of the input is checked + */ + void forward(const blitz::Array<double,1>& input, double& output) const; + + /** + * Output the log likelihood of the sample, x + * (overrides Machine::forward_) + * @warning Dimension of the input is not checked + */ + void forward_(const blitz::Array<double,1>& input, double& output) const; + + /** + * Accumulates the GMM statistics over a set of samples. + * @see bool accStatistics(const blitz::Array<double,1> &x, GMMStats stats) + * Dimensions of the parameters are checked + */ + void accStatistics(const blitz::Array<double,2>& input, GMMStats &stats) const; + + /** + * Accumulates the GMM statistics over a set of samples. + * @see bool accStatistics(const blitz::Array<double,1> &x, GMMStats stats) + * @warning Dimensions of the parameters are not checked + */ + void accStatistics_(const blitz::Array<double,2>& input, GMMStats &stats) const; + + /** + * Accumulate the GMM statistics for this sample. + * + * @param[in] x The current sample + * @param[out] stats The accumulated statistics + * Dimensions of the parameters are checked + */ + void accStatistics(const blitz::Array<double,1> &x, GMMStats &stats) const; + + /** + * Accumulate the GMM statistics for this sample. + * + * @param[in] x The current sample + * @param[out] stats The accumulated statistics + * @warning Dimensions of the parameters are not checked + */ + void accStatistics_(const blitz::Array<double,1> &x, GMMStats &stats) const; + + /** + * Get a pointer to a particular Gaussian component + * @param[in] i The index of the Gaussian component + * @return A smart pointer to the i'th Gaussian component + * if it exists, otherwise throws an exception + */ + boost::shared_ptr<const bob::machine::Gaussian> getGaussian(const size_t i) const; + + /** + * Get a pointer to a particular Gaussian component + * @param[in] i The index of the Gaussian component + * @return A smart pointer to the i'th Gaussian component + * if it exists, otherwise throws an exception + */ + boost::shared_ptr<bob::machine::Gaussian> updateGaussian(const size_t i); + + + /** + * Return the number of Gaussian components + */ + inline size_t getNGaussians() const + { return m_n_gaussians; } + + /** + * Save to a Configuration + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * Load from a Configuration + */ + void load(bob::io::base::HDF5File& config); + + /** + * Load/Reload mean/variance supervector in cache + */ + void reloadCacheSupervectors() const; + + friend std::ostream& operator<<(std::ostream& os, const GMMMachine& machine); + + + private: + /** + * Copy another GMMMachine + */ + void copy(const GMMMachine&); + + /** + * The number of Gaussian components + */ + size_t m_n_gaussians; + + /** + * The feature dimensionality + */ + size_t m_n_inputs; + + /** + * The Gaussian components + */ + std::vector<boost::shared_ptr<Gaussian> > m_gaussians; + + /** + * The weights (also known as "mixing coefficients") + */ + blitz::Array<double,1> m_weights; + + /** + * Update the mean and variance supervectors + * in cache (into a 1D blitz array) + */ + void updateCacheSupervectors() const; + + /** + * Initialise the cache members (allocate arrays) + */ + void initCache() const; + + /** + * Accumulate the GMM statistics for this sample. + * Called by accStatistics() and accStatistics_() + * + * @param[in] x The current sample + * @param[out] stats The accumulated statistics + * @param[in] log_likelihood The current log_likelihood + * @warning Dimensions of the parameters are not checked + */ + void accStatisticsInternal(const blitz::Array<double,1> &x, + GMMStats &stats, const double log_likelihood) const; + + + /// Some cache arrays to avoid re-allocation when computing log-likelihoods + mutable blitz::Array<double,1> m_cache_log_weights; + mutable blitz::Array<double,1> m_cache_log_weighted_gaussian_likelihoods; + mutable blitz::Array<double,1> m_cache_P; + mutable blitz::Array<double,2> m_cache_Px; + + mutable blitz::Array<double,1> m_cache_mean_supervector; + mutable blitz::Array<double,1> m_cache_variance_supervector; + mutable bool m_cache_supervector; + +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/GMMStats.h b/bob/learn/misc/include/bob.learn.misc/GMMStats.h new file mode 100644 index 0000000000000000000000000000000000000000..7b3eae02198e30a077135d0904791c7a46e4649f --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/GMMStats.h @@ -0,0 +1,148 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_GMMSTATS_H +#define BOB_MACHINE_GMMSTATS_H + +#include <blitz/array.h> +#include <bob.io.base/HDF5File.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief A container for GMM statistics. + * @see GMMMachine + * + * With respect to Reynolds, "Speaker Verification Using Adapted + * Gaussian Mixture Models", DSP, 2000: + * Eq (8) is n(i) + * Eq (9) is sumPx(i) / n(i) + * Eq (10) is sumPxx(i) / n(i) + */ +class GMMStats { + public: + + /** + * Default constructor. + */ + GMMStats(); + + /** + * Constructor. + * @param n_gaussians Number of Gaussians in the mixture model. + * @param n_inputs Feature dimensionality. + */ + GMMStats(const size_t n_gaussians, const size_t n_inputs); + + /** + * Copy constructor + */ + GMMStats(const GMMStats& other); + + /** + * Constructor (from a Configuration) + */ + GMMStats(bob::io::base::HDF5File& config); + + /** + * Assigment + */ + GMMStats& operator=(const GMMStats& other); + + /** + * Equal to + */ + bool operator==(const GMMStats& b) const; + + /** + * Not Equal to + */ + bool operator!=(const GMMStats& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const GMMStats& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * Updates a GMMStats with another GMMStats + */ + void operator+=(const GMMStats& b); + + /** + * Destructor + */ + ~GMMStats(); + + /** + * Allocates space for the statistics and resets to zero. + * @param n_gaussians Number of Gaussians in the mixture model. + * @param n_inputs Feature dimensionality. + */ + void resize(const size_t n_gaussians, const size_t n_inputs); + + /** + * Resets statistics to zero. + */ + void init(); + + /** + * The accumulated log likelihood of all samples + */ + double log_likelihood; + + /** + * The accumulated number of samples + */ + size_t T; + + /** + * For each Gaussian, the accumulated sum of responsibilities, i.e. the sum of P(gaussian_i|x) + */ + blitz::Array<double,1> n; + + /** + * For each Gaussian, the accumulated sum of responsibility times the sample + */ + blitz::Array<double,2> sumPx; + + /** + * For each Gaussian, the accumulated sum of responsibility times the sample squared + */ + blitz::Array<double,2> sumPxx; + + /** + * Save to a Configuration + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * Load from a Configuration + */ + void load(bob::io::base::HDF5File& config); + + friend std::ostream& operator<<(std::ostream& os, const GMMStats& g); + + private: + /** + * Copy another GMMStats + */ + void copy(const GMMStats&); +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/GMMTrainer.h b/bob/learn/misc/include/bob.learn.misc/GMMTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..1d164b51ca1e8dee9a074929c063cc00ea70c127 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/GMMTrainer.h @@ -0,0 +1,152 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * @brief This class implements the E-step of the expectation-maximisation algorithm for a GMM Machine. + * @details See Section 9.2.2 of Bishop, "Pattern recognition and machine learning", 2006 + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_GMMTRAINER_H +#define BOB_TRAINER_GMMTRAINER_H + +#include <bob.learn.misc/EMTrainer.h> +#include <bob.learn.misc/GMMMachine.h> +#include <bob.learn.misc/GMMStats.h> +#include <limits> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief This class implements the E-step of the expectation-maximisation + * algorithm for a GMM Machine. + * @details See Section 9.2.2 of Bishop, + * "Pattern recognition and machine learning", 2006 + */ +class GMMTrainer: public EMTrainer<bob::machine::GMMMachine, blitz::Array<double,2> > +{ + public: + /** + * @brief Default constructor + */ + GMMTrainer(const bool update_means=true, + const bool update_variances=false, const bool update_weights=false, + const double mean_var_update_responsibilities_threshold = + std::numeric_limits<double>::epsilon()); + + /** + * @brief Copy constructor + */ + GMMTrainer(const GMMTrainer& other); + + /** + * @brief Destructor + */ + virtual ~GMMTrainer(); + + /** + * @brief Initialization before the EM steps + */ + virtual void initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Calculates and saves statistics across the dataset, + * and saves these as m_ss. Calculates the average + * log likelihood of the observations given the GMM, + * and returns this in average_log_likelihood. + * + * The statistics, m_ss, will be used in the mStep() that follows. + * Implements EMTrainer::eStep(double &) + */ + virtual void eStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Computes the likelihood using current estimates of the latent + * variables + */ + virtual double computeLikelihood(bob::machine::GMMMachine& gmm); + + /** + * @brief Finalization after the EM steps + */ + virtual void finalize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Assigns from a different GMMTrainer + */ + GMMTrainer& operator=(const GMMTrainer &other); + + /** + * @brief Equal to + */ + bool operator==(const GMMTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const GMMTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const GMMTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Returns the internal GMM statistics. Useful to parallelize the + * E-step + */ + const bob::machine::GMMStats& getGMMStats() const + { return m_ss; } + + /** + * @brief Sets the internal GMM statistics. Useful to parallelize the + * E-step + */ + void setGMMStats(const bob::machine::GMMStats& stats); + + protected: + /** + * These are the sufficient statistics, calculated during the + * E-step and used during the M-step + */ + bob::machine::GMMStats m_ss; + + /** + * update means on each iteration + */ + bool m_update_means; + + /** + * update variances on each iteration + */ + bool m_update_variances; + + /** + * update weights on each iteration + */ + bool m_update_weights; + + /** + * threshold over the responsibilities of the Gaussians + * Equations 9.24, 9.25 of Bishop, "Pattern recognition and machine learning", 2006 + * require a division by the responsibilities, which might be equal to zero + * because of numerical issue. This threshold is used to avoid such divisions. + */ + double m_mean_var_update_responsibilities_threshold; +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/Gaussian.h b/bob/learn/misc/include/bob.learn.misc/Gaussian.h new file mode 100644 index 0000000000000000000000000000000000000000..fcfd57db11f3a6b954a54dd71382e8367c826087 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/Gaussian.h @@ -0,0 +1,271 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_GAUSSIAN_H +#define BOB_MACHINE_GAUSSIAN_H + +#include <bob.learn.misc/Machine.h> +#include <bob.io.base/HDF5File.h> +#include <blitz/array.h> +#include <limits> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief This class implements a multivariate diagonal Gaussian distribution. + */ +class Gaussian: public Machine<blitz::Array<double,1>, double> +{ + public: + /** + * Default constructor + */ + Gaussian(); + + /** + * Constructor + * @param[in] n_inputs The feature dimensionality + */ + Gaussian(const size_t n_inputs); + + /** + * Destructor + */ + virtual ~Gaussian(); + + /** + * Copy constructor + */ + Gaussian(const Gaussian& other); + + /** + * Constructs from a configuration file + */ + Gaussian(bob::io::base::HDF5File& config); + + /** + * Assignment + */ + Gaussian& operator=(const Gaussian &other); + + /** + * Equal to + */ + bool operator==(const Gaussian& b) const; + /** + * Not equal to + */ + bool operator!=(const Gaussian& b) const; + /** + * @brief Similar to + */ + bool is_similar_to(const Gaussian& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * Set the input dimensionality, reset the mean to zero + * and the variance to one. + * @see resize() + * @param n_inputs The feature dimensionality + * @warning The mean and variance are not initialized + */ + void setNInputs(const size_t n_inputs); + + /** + * Get the input dimensionality + */ + size_t getNInputs() const + { return m_n_inputs; } + + /** + * Set the input dimensionality, reset the mean to zero + * and the variance to one. + * @see setNInputs() + * @param n_inputs The feature dimensionality + */ + void resize(const size_t n_inputs); + + /** + * Get the mean + */ + inline const blitz::Array<double,1>& getMean() const + { return m_mean; } + + /** + * Get the mean in order to be updated + * @warning Only trainers should use this function for efficiency reason + */ + inline blitz::Array<double,1>& updateMean() + { return m_mean; } + + /** + * Set the mean + */ + void setMean(const blitz::Array<double,1> &mean); + + /** + * Get the variance (the diagonal of the covariance matrix) + */ + inline const blitz::Array<double,1>& getVariance() const + { return m_variance; } + + /** + * Get the variance in order to be updated + * @warning Only trainers should use this function for efficiency reason + */ + inline blitz::Array<double,1>& updateVariance() + { return m_variance; } + + /** + * Set the variance + */ + void setVariance(const blitz::Array<double,1> &variance); + + /** + * Get the variance flooring thresholds + */ + const blitz::Array<double,1>& getVarianceThresholds() const + { return m_variance_thresholds; } + + /** + * Get the variance thresholds in order to be updated + * @warning Only trainers should use this function for efficiency reason + */ + inline blitz::Array<double,1>& updateVarianceThreshods() + { return m_variance_thresholds; } + + /** + * Set the variance flooring thresholds + */ + void setVarianceThresholds(const blitz::Array<double,1> &variance_thresholds); + + /** + * Set the variance flooring thresholds + */ + void setVarianceThresholds(const double value); + + /** + * Apply the variance flooring thresholds + * This method is called when using setVarianceThresholds() + * @warning It is only useful when using updateVarianceThreshods(), + * and should mostly be done by trainers + */ + void applyVarianceThresholds(); + + /** + * Output the log likelihood of the sample, x + * @param x The data sample (feature vector) + */ + double logLikelihood(const blitz::Array<double,1>& x) const; + + /** + * Output the log likelihood of the sample, x + * @param x The data sample (feature vector) + * @warning The input is NOT checked + */ + double logLikelihood_(const blitz::Array<double,1>& x) const; + + /** + * Computes the log likelihood of the sample, x + * @param x The data sample (feature vector) + * @param output The computed log likelihood + */ + void forward(const blitz::Array<double,1>& x, double& output) const + { output = logLikelihood(x); } + + /** + * Computes the log likelihood of the sample, x + * @param x The data sample (feature vector) + * @param output The computed log likelihood + * @warning The input is NOT checked + */ + void forward_(const blitz::Array<double,1>& x, double& output) const + { output = logLikelihood_(x); } + + /** + * Saves to a Configuration + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * Loads from a Configuration + */ + void load(bob::io::base::HDF5File& config); + + /** + * Prints a Gaussian in the output stream + */ + friend std::ostream& operator<<(std::ostream& os, const Gaussian& g); + + + private: + /** + * Copies another Gaussian + */ + void copy(const Gaussian& other); + + /** + * Computes n_inputs * log(2*pi) + */ + void preComputeNLog2Pi(); + + /** + * Computes and stores the value of g_norm, + * to later speed up evaluation of logLikelihood() + * Note: g_norm is defined as follows: + * log(Gaussian pdf) = log(1/((2pi)^(k/2)(det)^(1/2)) * exp(...)) + * = -1/2 * g_norm * (...) + */ + void preComputeConstants(); + + /** + * The mean vector of the Gaussian + */ + blitz::Array<double,1> m_mean; + + /** + * The diagonal of the covariance matrix (assumed to be diagonal) + */ + blitz::Array<double,1> m_variance; + + /** + * The variance flooring thresholds, i.e. the minimum allowed + * value of variance in each dimension. + * The variance will be set to this value if an attempt is made + * to set it to a smaller value. + */ + blitz::Array<double,1> m_variance_thresholds; + + /** + * A constant that depends only on the feature dimensionality + * m_n_log2pi = n_inputs * log(2*pi) (used to compute m_gnorm) + */ + double m_n_log2pi; + + /** + * A constant that depends only on the feature dimensionality + * (m_n_inputs) and the variance + * @see bool preComputeConstants() + */ + double m_g_norm; + + /** + * The number of inputs (feature dimensionality) + */ + size_t m_n_inputs; +}; + +/** + * @} + */ +}} +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h b/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..93bf66497212087c276785fd18648b917591713d --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h @@ -0,0 +1,282 @@ +/** + * @date Sat Mar 30 20:55:00 2013 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_IVECTOR_H +#define BOB_MACHINE_IVECTOR_H + +#include <blitz/array.h> +#include <bob.learn.misc/Machine.h> +#include <bob.learn.misc/GMMMachine.h> +#include <bob.learn.misc/GMMStats.h> +#include <bob.io.base/HDF5File.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief An IVectorMachine consists of a Total Variability subspace \f$T\f$ + * and allows the extraction of IVector\n + * Reference:\n + * "Front-End Factor Analysis For Speaker Verification", + * N. Dehak, P. Kenny, R. Dehak, P. Dumouchel, P. Ouellet, + * IEEE Trans. on Audio, Speech and Language Processing + */ +class IVectorMachine: public bob::machine::Machine<bob::machine::GMMStats, blitz::Array<double,1> > +{ + public: + /** + * @brief Default constructor. Builds an IVectorMachine. + * The Universal Background Model and the matrices \f$T\f$ and + * \f$diag(\Sigma)\f$ are not initialized. + */ + IVectorMachine(); + + /** + * @brief Constructor. Builds a new IVectorMachine. + * The Universal Background Model and the matrices \f$T\f$ and + * \f$diag(\Sigma)\f$ are not initialized. + * + * @param ubm The Universal Background Model + * @param rt size of \f$T\f$ (CD x rt) + * @param variance_threshold variance flooring threshold for the + * \f$\Sigma\f$ (diagonal) matrix + * @warning rt SHOULD BE >= 1. + */ + IVectorMachine(const boost::shared_ptr<bob::machine::GMMMachine> ubm, + const size_t rt=1, const double variance_threshold=1e-10); + + /** + * @brief Copy constructor + */ + IVectorMachine(const IVectorMachine& other); + + /** + * @brief Starts a new IVectorMachine from an existing Configuration object. + */ + IVectorMachine(bob::io::base::HDF5File& config); + + /** + * @brief Destructor + */ + virtual ~IVectorMachine(); + + /** + * @brief Assigns from a different IVectorMachine + */ + IVectorMachine& operator=(const IVectorMachine &other); + + /** + * @brief Equal to + */ + bool operator==(const IVectorMachine& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const IVectorMachine& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const IVectorMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Saves model to an HDF5 file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Loads data from an existing configuration object. Resets + * the current state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * @brief Returns the UBM + */ + const boost::shared_ptr<bob::machine::GMMMachine> getUbm() const + { return m_ubm; } + + /** + * @brief Returns the \f$T\f$ matrix + */ + const blitz::Array<double,2>& getT() const + { return m_T; } + + /** + * @brief Returns the \f$\Sigma\f$ (diagonal) matrix as a 1D array + */ + const blitz::Array<double,1>& getSigma() const + { return m_sigma; } + + /** + * @brief Gets the variance flooring threshold + */ + const double getVarianceThreshold() const + { return m_variance_threshold; } + + /** + * @brief Returns the number of Gaussian components C. + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { return m_ubm->getNGaussians(); } + + /** + * @brief Returns the feature dimensionality D. + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { return m_ubm->getNInputs(); } + + /** + * @brief Returns the supervector length CD. + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { return m_ubm->getNGaussians()*m_ubm->getNInputs(); } + + /** + * @brief Returns the size/rank rt of the \f$T\f$ matrix + */ + const size_t getDimRt() const + { return m_rt; } + + /** + * @brief Resets the dimensionality of the subspace \f$T\f$. + * \f$T\f$ is hence uninitialized. + */ + void resize(const size_t rt); + + /** + * @brief Returns the \f$T\f$ matrix in order to update it. + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateT() + { return m_T; } + + /** + * @brief Returns the \f$\Sigma\f$ (diagonal) matrix in order to update it. + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,1>& updateSigma() + { return m_sigma; } + + /** + * @brief Sets (the mean supervector of) the Universal Background Model. + * \f$T\f$ and \f$\Sigma\f$ are uninitialized in case of dimensions update (C or D) + */ + void setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm); + + /** + * @brief Sets the \f$T\f$ matrix + */ + void setT(const blitz::Array<double,2>& T); + + /** + * @brief Sets the \f$\Sigma\f$ (diagonal) matrix + */ + void setSigma(const blitz::Array<double,1>& sigma); + + /** + * @brief Set the variance flooring threshold + */ + void setVarianceThreshold(const double value); + + /** + * @brief Update arrays in cache + * @warning It is only useful when using updateT() or updateSigma() + * and should mostly be done by trainers + */ + void precompute(); + + /** + * @brief Computes \f$(Id + \sum_{c=1}^{C} N_{i,j,c} T^{T} \Sigma_{c}^{-1} T)\f$ + * @warning No check is perform + */ + void computeIdTtSigmaInvT(const bob::machine::GMMStats& input, blitz::Array<double,2>& output) const; + + /** + * @brief Computes \f$T^{T} \Sigma^{-1} \sum_{c=1}^{C} (F_c - N_c ubmmean_{c})\f$ + * @warning No check is perform + */ + void computeTtSigmaInvFnorm(const bob::machine::GMMStats& input, blitz::Array<double,1>& output) const; + + /** + * @brief Extracts an ivector from the input GMM statistics + * + * @param input GMM statistics to be used by the machine + * @param output I-vector computed by the machine + */ + void forward(const bob::machine::GMMStats& input, blitz::Array<double,1>& output) const; + + /** + * @brief Extracts an ivector from the input GMM statistics + * + * @param input GMM statistics to be used by the machine + * @param output I-vector computed by the machine + * @warning Inputs are NOT checked + */ + void forward_(const bob::machine::GMMStats& input, blitz::Array<double,1>& output) const; + + protected: + /** + * @brief Apply the variance flooring thresholds. + * This method is called when using setVarianceThresholds() + */ + void applyVarianceThreshold(); + + /** + * @brief Resize cache + */ + void resizeCache(); + /** + * @brief Resize working arrays + */ + void resizeTmp(); + /** + * @brief Resize cache and working arrays before updating cache + */ + void resizePrecompute(); + + // UBM + boost::shared_ptr<bob::machine::GMMMachine> m_ubm; + + // dimensionality + size_t m_rt; ///< size of \f$T\f$ (CD x rt) + + ///< \f$T\f$ and \f$Sigma\f$ matrices. + ///< \f$Sigma\f$ is assumed to be diagonal, and only the diagonal is stored + blitz::Array<double,2> m_T; ///< The total variability matrix \f$T\f$ + blitz::Array<double,1> m_sigma; ///< The diagonal covariance matrix \f$\Sigma\f$ + double m_variance_threshold; ///< The variance flooring threshold + + blitz::Array<double,3> m_cache_Tct_sigmacInv; + blitz::Array<double,3> m_cache_Tct_sigmacInv_Tc; + + mutable blitz::Array<double,1> m_tmp_d; + mutable blitz::Array<double,1> m_tmp_t1; + mutable blitz::Array<double,1> m_tmp_t2; + mutable blitz::Array<double,2> m_tmp_tt; +}; + +/** + * @} + */ +}} + +#endif // BOB_MACHINE_IVECTORMACHINE_H diff --git a/bob/learn/misc/include/bob.learn.misc/IVectorTrainer.h b/bob/learn/misc/include/bob.learn.misc/IVectorTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..55bc6879fd88b83c3be3b952634686c137678da1 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/IVectorTrainer.h @@ -0,0 +1,167 @@ +/** + * @date Sat Mar 30 20:55:00 2013 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_IVECTOR_H +#define BOB_TRAINER_IVECTOR_H + +#include <blitz/array.h> +#include <bob.learn.misc/EMTrainer.h> +#include <bob.learn.misc/IVectorMachine.h> +#include <bob.learn.misc/GMMStats.h> +#include <boost/shared_ptr.hpp> +#include <boost/random.hpp> +#include <vector> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief An IVectorTrainer to learn a Total Variability subspace \f$T\f$ + * (and eventually a covariance matrix \f$\Sigma\f$).\n + * Reference:\n + * "Front-End Factor Analysis For Speaker Verification", + * N. Dehak, P. Kenny, R. Dehak, P. Dumouchel, P. Ouellet, + * IEEE Trans. on Audio, Speech and Language Processing + */ +class IVectorTrainer: public bob::trainer::EMTrainer<bob::machine::IVectorMachine, std::vector<bob::machine::GMMStats> > +{ + public: + /** + * @brief Default constructor. Builds an IVectorTrainer + */ + IVectorTrainer(const bool update_sigma=false, + const double convergence_threshold=0.001, + const size_t max_iterations=10, const bool compute_likelihood=false); + + /** + * @brief Copy constructor + */ + IVectorTrainer(const IVectorTrainer& other); + + /** + * @brief Destructor + */ + virtual ~IVectorTrainer(); + + /** + * @brief Initialization before the EM loop + */ + virtual void initialize(bob::machine::IVectorMachine& ivector, + const std::vector<bob::machine::GMMStats>& data); + + /** + * @brief Calculates statistics across the dataset, + * and saves these as: + * - m_acc_Nij_wij2 + * - m_acc_Fnormij_wij + * - m_acc_Nij (only if update_sigma is enabled) + * - m_acc_Snormij (only if update_sigma is enabled) + * + * These statistics will be used in the mStep() that follows. + */ + virtual void eStep(bob::machine::IVectorMachine& ivector, + const std::vector<bob::machine::GMMStats>& data); + + /** + * @brief Maximisation step: Update the Total Variability matrix \f$T\f$ + * and \f$\Sigma\f$ if update_sigma is enabled. + */ + virtual void mStep(bob::machine::IVectorMachine& ivector, + const std::vector<bob::machine::GMMStats>& data); + + /** + * @brief Computes the likelihood using current estimates + * @warning (currently unsupported) + */ + virtual double computeLikelihood(bob::machine::IVectorMachine& ivector); + + /** + * @brief Finalization after the EM loop + */ + virtual void finalize(bob::machine::IVectorMachine& ivector, + const std::vector<bob::machine::GMMStats>& data); + + /** + * @brief Assigns from a different IVectorTrainer + */ + IVectorTrainer& operator=(const IVectorTrainer &other); + + /** + * @brief Equal to + */ + bool operator==(const IVectorTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const IVectorTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const IVectorTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Getters for the accumulators + */ + const blitz::Array<double,3>& getAccNijWij2() const + { return m_acc_Nij_wij2; } + const blitz::Array<double,3>& getAccFnormijWij() const + { return m_acc_Fnormij_wij; } + const blitz::Array<double,1>& getAccNij() const + { return m_acc_Nij; } + const blitz::Array<double,2>& getAccSnormij() const + { return m_acc_Snormij; } + + /** + * @brief Setters for the accumulators, Very useful if the e-Step needs + * to be parallelized. + */ + void setAccNijWij2(const blitz::Array<double,3>& acc) + { bob::core::array::assertSameShape(acc, m_acc_Nij_wij2); + m_acc_Nij_wij2 = acc; } + void setAccFnormijWij(const blitz::Array<double,3>& acc) + { bob::core::array::assertSameShape(acc, m_acc_Fnormij_wij); + m_acc_Fnormij_wij = acc; } + void setAccNij(const blitz::Array<double,1>& acc) + { bob::core::array::assertSameShape(acc, m_acc_Nij); + m_acc_Nij = acc; } + void setAccSnormij(const blitz::Array<double,2>& acc) + { bob::core::array::assertSameShape(acc, m_acc_Snormij); + m_acc_Snormij = acc; } + + protected: + // Attributes + bool m_update_sigma; + + // Acccumulators + blitz::Array<double,3> m_acc_Nij_wij2; + blitz::Array<double,3> m_acc_Fnormij_wij; + blitz::Array<double,1> m_acc_Nij; + blitz::Array<double,2> m_acc_Snormij; + + // Working arrays + mutable blitz::Array<double,1> m_tmp_wij; + mutable blitz::Array<double,2> m_tmp_wij2; + mutable blitz::Array<double,1> m_tmp_d1; + mutable blitz::Array<double,1> m_tmp_t1; + mutable blitz::Array<double,2> m_tmp_dd1; + mutable blitz::Array<double,2> m_tmp_dt1; + mutable blitz::Array<double,2> m_tmp_tt1; + mutable blitz::Array<double,2> m_tmp_tt2; +}; + +/** + * @} + */ +}} + +#endif // BOB_TRAINER_IVECTORTRAINER_H diff --git a/bob/learn/misc/include/bob.learn.misc/JFAMachine.h b/bob/learn/misc/include/bob.learn.misc/JFAMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..4644364f6beaa547c907343fdf9e663fe074a6e6 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/JFAMachine.h @@ -0,0 +1,1158 @@ +/** + * @date Sat Jul 23 21:41:15 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief A base class for Joint Factor Analysis-like machines + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_FABASE_H +#define BOB_MACHINE_FABASE_H + +#include <stdexcept> + +#include <bob.learn.misc/Machine.h> +#include <bob.learn.misc/GMMMachine.h> +#include <bob.learn.misc/LinearScoring.h> + +#include <bob.io.base/HDF5File.h> +#include <boost/shared_ptr.hpp> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief A FA Base class which contains U, V and D matrices + * TODO: add a reference to the journal articles + */ +class FABase +{ + public: + /** + * @brief Default constructor. Builds an otherwise invalid 0 x 0 FABase + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + */ + FABase(); + + /** + * @brief Constructor. Builds a new FABase. + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + * + * @param ubm The Universal Background Model + * @param ru size of U (CD x ru) + * @param rv size of U (CD x rv) + * @warning ru and rv SHOULD BE >= 1. Just set U/V/D to zero if you want + * to ignore one subspace. This is the case for ISV. + */ + FABase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, const size_t ru=1, const size_t rv=1); + + /** + * @brief Copy constructor + */ + FABase(const FABase& other); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~FABase(); + + /** + * @brief Assigns from a different JFA machine + */ + FABase& operator=(const FABase &other); + + /** + * @brief Equal to + */ + bool operator==(const FABase& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const FABase& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const FABase& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Returns the UBM + */ + const boost::shared_ptr<bob::machine::GMMMachine> getUbm() const + { return m_ubm; } + + /** + * @brief Returns the U matrix + */ + const blitz::Array<double,2>& getU() const + { return m_U; } + + /** + * @brief Returns the V matrix + */ + const blitz::Array<double,2>& getV() const + { return m_V; } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) + */ + const blitz::Array<double,1>& getD() const + { return m_d; } + + /** + * @brief Returns the UBM mean supervector (as a 1D vector) + */ + const blitz::Array<double,1>& getUbmMean() const + { return m_cache_mean; } + + /** + * @brief Returns the UBM variance supervector (as a 1D vector) + */ + const blitz::Array<double,1>& getUbmVariance() const + { return m_cache_sigma; } + + /** + * @brief Returns the number of Gaussian components C + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { if(!m_ubm) throw std::runtime_error("No UBM was set in the JFA machine."); + return m_ubm->getNGaussians(); } + + /** + * @brief Returns the feature dimensionality D + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { if(!m_ubm) throw std::runtime_error("No UBM was set in the JFA machine."); + return m_ubm->getNInputs(); } + + /** + * @brief Returns the supervector length CD + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { if(!m_ubm) throw std::runtime_error("No UBM was set in the JFA machine."); + return m_ubm->getNInputs()*m_ubm->getNGaussians(); } + + /** + * @brief Returns the size/rank ru of the U matrix + */ + const size_t getDimRu() const + { return m_ru; } + + /** + * @brief Returns the size/rank rv of the V matrix + */ + const size_t getDimRv() const + { return m_rv; } + + /** + * @brief Resets the dimensionality of the subspace U and V + * U and V are hence uninitialized. + */ + void resize(const size_t ru, const size_t rv); + + /** + * @brief Resets the dimensionality of the subspace U and V, + * assuming that no UBM has yet been set + * U and V are hence uninitialized. + */ + void resize(const size_t ru, const size_t rv, const size_t cd); + + /** + * @brief Returns the U matrix in order to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateU() + { return m_U; } + + /** + * @brief Returns the V matrix in order to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateV() + { return m_V; } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) in order + * to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,1>& updateD() + { return m_d; } + + + /** + * @brief Sets (the mean supervector of) the Universal Background Model + * U, V and d are uninitialized in case of dimensions update (C or D) + */ + void setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm); + + /** + * @brief Sets the U matrix + */ + void setU(const blitz::Array<double,2>& U); + + /** + * @brief Sets the V matrix + */ + void setV(const blitz::Array<double,2>& V); + + /** + * @brief Sets the diagonal matrix diag(d) + * (a 1D vector is expected as an argument) + */ + void setD(const blitz::Array<double,1>& d); + + + /** + * @brief Estimates x from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const; + + /** + * @brief Compute and put U^{T}.Sigma^{-1} matrix in cache + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + void updateCacheUbmUVD(); + + + private: + /** + * @brief Update cache arrays/variables + */ + void updateCache(); + /** + * @brief Put GMM mean/variance supervector in cache + */ + void updateCacheUbm(); + /** + * @brief Resize working arrays + */ + void resizeTmp(); + /** + * @brief Computes (Id + U^T.Sigma^-1.U.N_{i,h}.U)^-1 = + * (Id + sum_{c=1..C} N_{i,h}.U_{c}^T.Sigma_{c}^-1.U_{c})^-1 + */ + void computeIdPlusUSProdInv(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,2>& out) const; + /** + * @brief Computes Fn_x = sum_{sessions h}(N*(o - m)) + * (Normalised first order statistics) + */ + void computeFn_x(const bob::machine::GMMStats& gmm_stats, + blitz::Array<double,1>& out) const; + /** + * @brief Estimates the value of x from the passed arguments + * (IdPlusUSProdInv and Fn_x), considering the LPT assumption + */ + void estimateX(const blitz::Array<double,2>& IdPlusUSProdInv, + const blitz::Array<double,1>& Fn_x, blitz::Array<double,1>& x) const; + + + // UBM + boost::shared_ptr<bob::machine::GMMMachine> m_ubm; + + // dimensionality + size_t m_ru; // size of U (CD x ru) + size_t m_rv; // size of V (CD x rv) + + // U, V, D matrices + // D is assumed to be diagonal, and only the diagonal is stored + blitz::Array<double,2> m_U; + blitz::Array<double,2> m_V; + blitz::Array<double,1> m_d; + + // Vectors/Matrices precomputed in cache + blitz::Array<double,1> m_cache_mean; + blitz::Array<double,1> m_cache_sigma; + blitz::Array<double,2> m_cache_UtSigmaInv; + + mutable blitz::Array<double,2> m_tmp_IdPlusUSProdInv; + mutable blitz::Array<double,1> m_tmp_Fn_x; + mutable blitz::Array<double,1> m_tmp_ru; + mutable blitz::Array<double,2> m_tmp_ruD; + mutable blitz::Array<double,2> m_tmp_ruru; +}; + + +/** + * @brief A JFA Base class which contains U, V and D matrices + * TODO: add a reference to the journal articles + */ +class JFABase +{ + public: + /** + * @brief Default constructor. Builds a 1 x 1 JFABase + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + */ + JFABase(); + + /** + * @brief Constructor. Builds a new JFABase. + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + * + * @param ubm The Universal Background Model + * @param ru size of U (CD x ru) + * @param rv size of U (CD x rv) + * @warning ru and rv SHOULD BE >= 1. + */ + JFABase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, const size_t ru=1, const size_t rv=1); + + /** + * @brief Copy constructor + */ + JFABase(const JFABase& other); + + /** + * @deprecated Starts a new JFAMachine from an existing Configuration object. + */ + JFABase(bob::io::base::HDF5File& config); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~JFABase(); + + /** + * @brief Assigns from a different JFA machine + */ + JFABase& operator=(const JFABase &other); + + /** + * @brief Equal to + */ + bool operator==(const JFABase& b) const + { return m_base.operator==(b.m_base); } + + /** + * @brief Not equal to + */ + bool operator!=(const JFABase& b) const + { return m_base.operator!=(b.m_base); } + + /** + * @brief Similar to + */ + bool is_similar_to(const JFABase& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const + { return m_base.is_similar_to(b.m_base, r_epsilon, a_epsilon); } + + /** + * @brief Saves model to an HDF5 file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Loads data from an existing configuration object. Resets + * the current state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * @brief Returns the UBM + */ + const boost::shared_ptr<bob::machine::GMMMachine> getUbm() const + { return m_base.getUbm(); } + + /** + * @brief Returns the U matrix + */ + const blitz::Array<double,2>& getU() const + { return m_base.getU(); } + + /** + * @brief Returns the V matrix + */ + const blitz::Array<double,2>& getV() const + { return m_base.getV(); } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) + */ + const blitz::Array<double,1>& getD() const + { return m_base.getD(); } + + /** + * @brief Returns the number of Gaussian components C + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { return m_base.getDimC(); } + + /** + * @brief Returns the feature dimensionality D + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { return m_base.getDimD(); } + + /** + * @brief Returns the supervector length CD + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { return m_base.getDimCD(); } + + /** + * @brief Returns the size/rank ru of the U matrix + */ + const size_t getDimRu() const + { return m_base.getDimRu(); } + + /** + * @brief Returns the size/rank rv of the V matrix + */ + const size_t getDimRv() const + { return m_base.getDimRv(); } + + /** + * @brief Resets the dimensionality of the subspace U and V + * U and V are hence uninitialized. + */ + void resize(const size_t ru, const size_t rv) + { m_base.resize(ru, rv); } + + /** + * @brief Returns the U matrix in order to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateU() + { return m_base.updateU(); } + + /** + * @brief Returns the V matrix in order to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateV() + { return m_base.updateV(); } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) in order + * to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,1>& updateD() + { return m_base.updateD(); } + + + /** + * @brief Sets (the mean supervector of) the Universal Background Model + * U, V and d are uninitialized in case of dimensions update (C or D) + */ + void setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm) + { m_base.setUbm(ubm); } + + /** + * @brief Sets the U matrix + */ + void setU(const blitz::Array<double,2>& U) + { m_base.setU(U); } + + /** + * @brief Sets the V matrix + */ + void setV(const blitz::Array<double,2>& V) + { m_base.setV(V); } + + /** + * @brief Sets the diagonal matrix diag(d) + * (a 1D vector is expected as an argument) + */ + void setD(const blitz::Array<double,1>& d) + { m_base.setD(d); } + + /** + * @brief Estimates x from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const + { m_base.estimateX(gmm_stats, x); } + + /** + * @brief Precompute (put U^{T}.Sigma^{-1} matrix in cache) + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + void precompute() + { m_base.updateCacheUbmUVD(); } + + /** + * @brief Returns the FABase member + */ + const bob::machine::FABase& getBase() const + { return m_base; } + + + private: + // FABase + bob::machine::FABase m_base; +}; + + +/** + * @brief An ISV Base class which contains U and D matrices + * TODO: add a reference to the journal articles + */ +class ISVBase +{ + public: + /** + * @brief Default constructor. Builds an otherwise invalid 0 x 0 ISVBase + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + */ + ISVBase(); + + /** + * @brief Constructor. Builds a new ISVBase. + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + * + * @param ubm The Universal Background Model + * @param ru size of U (CD x ru) + * @warning ru SHOULD BE >= 1. + */ + ISVBase(const boost::shared_ptr<bob::machine::GMMMachine> ubm, const size_t ru=1); + + /** + * @brief Copy constructor + */ + ISVBase(const ISVBase& other); + + /** + * @deprecated Starts a new JFAMachine from an existing Configuration object. + */ + ISVBase(bob::io::base::HDF5File& config); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~ISVBase(); + + /** + * @brief Assigns from a different JFA machine + */ + ISVBase& operator=(const ISVBase &other); + + /** + * @brief Equal to + */ + bool operator==(const ISVBase& b) const + { return m_base.operator==(b.m_base); } + + /** + * @brief Not equal to + */ + bool operator!=(const ISVBase& b) const + { return m_base.operator!=(b.m_base); } + + /** + * @brief Similar to + */ + bool is_similar_to(const ISVBase& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const + { return m_base.is_similar_to(b.m_base, r_epsilon, a_epsilon); } + + /** + * @brief Saves machine to an HDF5 file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Loads data from an existing configuration object. Resets + * the current state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * @brief Returns the UBM + */ + const boost::shared_ptr<bob::machine::GMMMachine> getUbm() const + { return m_base.getUbm(); } + + /** + * @brief Returns the U matrix + */ + const blitz::Array<double,2>& getU() const + { return m_base.getU(); } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) + */ + const blitz::Array<double,1>& getD() const + { return m_base.getD(); } + + /** + * @brief Returns the number of Gaussian components C + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { return m_base.getDimC(); } + + /** + * @brief Returns the feature dimensionality D + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { return m_base.getDimD(); } + + /** + * @brief Returns the supervector length CD + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { return m_base.getDimCD(); } + + /** + * @brief Returns the size/rank ru of the U matrix + */ + const size_t getDimRu() const + { return m_base.getDimRu(); } + + /** + * @brief Resets the dimensionality of the subspace U and V + * U and V are hence uninitialized. + */ + void resize(const size_t ru) + { m_base.resize(ru, 1); + blitz::Array<double,2>& V = m_base.updateV(); + V = 0; + } + + /** + * @brief Returns the U matrix in order to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,2>& updateU() + { return m_base.updateU(); } + + /** + * @brief Returns the diagonal matrix diag(d) (as a 1D vector) in order + * to update it + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + blitz::Array<double,1>& updateD() + { return m_base.updateD(); } + + + /** + * @brief Sets (the mean supervector of) the Universal Background Model + * U, V and d are uninitialized in case of dimensions update (C or D) + */ + void setUbm(const boost::shared_ptr<bob::machine::GMMMachine> ubm) + { m_base.setUbm(ubm); } + + /** + * @brief Sets the U matrix + */ + void setU(const blitz::Array<double,2>& U) + { m_base.setU(U); } + + /** + * @brief Sets the diagonal matrix diag(d) + * (a 1D vector is expected as an argument) + */ + void setD(const blitz::Array<double,1>& d) + { m_base.setD(d); } + + /** + * @brief Estimates x from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const + { m_base.estimateX(gmm_stats, x); } + + /** + * @brief Precompute (put U^{T}.Sigma^{-1} matrix in cache) + * @warning Should only be used by the trainer for efficiency reason, + * or for testing purpose. + */ + void precompute() + { m_base.updateCacheUbmUVD(); } + + /** + * @brief Returns the FABase member + */ + const bob::machine::FABase& getBase() const + { return m_base; } + + + private: + // FABase + bob::machine::FABase m_base; +}; + + +/** + * @brief A JFAMachine which is associated to a JFABase that contains + * U, V and D matrices. The JFAMachine describes the identity part + * (latent variables y and z) + * TODO: add a reference to the journal articles + */ +class JFAMachine: public Machine<bob::machine::GMMStats, double> +{ + public: + /** + * @brief Default constructor. Builds an otherwise invalid 0 x 0 JFAMachine + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + */ + JFAMachine(); + + /** + * @brief Constructor. Builds a new JFAMachine. + * + * @param jfa_base The JFABase associated with this machine + */ + JFAMachine(const boost::shared_ptr<bob::machine::JFABase> jfa_base); + + /** + * @brief Copy constructor + */ + JFAMachine(const JFAMachine& other); + + /** + * @deprecated Starts a new JFAMachine from an existing Configuration object. + */ + JFAMachine(bob::io::base::HDF5File& config); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~JFAMachine(); + + /** + * @brief Assigns from a different JFA machine + */ + JFAMachine& operator=(const JFAMachine &other); + + /** + * @brief Equal to + */ + bool operator==(const JFAMachine& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const JFAMachine& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const JFAMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Saves machine to an HDF5 file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Loads data from an existing configuration object. Resets + * the current state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * @brief Returns the number of Gaussian components C + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { return m_jfa_base->getDimC(); } + + /** + * @brief Returns the feature dimensionality D + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { return m_jfa_base->getDimD(); } + + /** + * @brief Returns the supervector length CD + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { return m_jfa_base->getDimCD(); } + + /** + * @brief Returns the size/rank ru of the U matrix + */ + const size_t getDimRu() const + { return m_jfa_base->getDimRu(); } + + /** + * @brief Returns the size/rank rv of the V matrix + */ + const size_t getDimRv() const + { return m_jfa_base->getDimRv(); } + + /** + * @brief Returns the x session factor + */ + const blitz::Array<double,1>& getX() const + { return m_cache_x; } + + /** + * @brief Returns the y speaker factor + */ + const blitz::Array<double,1>& getY() const + { return m_y; } + + /** + * @brief Returns the z speaker factor + */ + const blitz::Array<double,1>& getZ() const + { return m_z; } + + /** + * @brief Returns the y speaker factors in order to update it + */ + blitz::Array<double,1>& updateY() + { return m_y; } + + /** + * @brief Returns the z speaker factors in order to update it + */ + blitz::Array<double,1>& updateZ() + { return m_z; } + + /** + * @brief Returns the y speaker factors + */ + void setY(const blitz::Array<double,1>& y); + + /** + * @brief Returns the V matrix + */ + void setZ(const blitz::Array<double,1>& z); + + /** + * @brief Returns the JFABase + */ + const boost::shared_ptr<bob::machine::JFABase> getJFABase() const + { return m_jfa_base; } + + /** + * @brief Sets the JFABase + */ + void setJFABase(const boost::shared_ptr<bob::machine::JFABase> jfa_base); + + + /** + * @brief Estimates x from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const + { m_jfa_base->estimateX(gmm_stats, x); } + /** + * @brief Estimates Ux from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateUx(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& Ux); + + /** + * @brief Execute the machine + * + * @param input input data used by the machine + * @param score value computed by the machine + * @warning Inputs are checked + */ + void forward(const bob::machine::GMMStats& input, double& score) const; + /** + * @brief Computes a score for the given UBM statistics and given the + * Ux vector + */ + void forward(const bob::machine::GMMStats& gmm_stats, + const blitz::Array<double,1>& Ux, double& score) const; + + /** + * @brief Execute the machine + * + * @param input input data used by the machine + * @param score value computed by the machine + * @warning Inputs are NOT checked + */ + void forward_(const bob::machine::GMMStats& input, double& score) const; + + protected: + /** + * @brief Resize latent variable according to the JFABase + */ + void resize(); + /** + * @brief Resize working arrays + */ + void resizeTmp(); + /** + * @brief Update the cache + */ + void updateCache(); + + // UBM + boost::shared_ptr<bob::machine::JFABase> m_jfa_base; + + // y and z vectors/factors learned during the enrolment procedure + blitz::Array<double,1> m_y; + blitz::Array<double,1> m_z; + + // cache + blitz::Array<double,1> m_cache_mVyDz; + mutable blitz::Array<double,1> m_cache_x; + + // x vector/factor in cache when computing scores + mutable blitz::Array<double,1> m_tmp_Ux; +}; + +/** + * @brief A ISVMachine which is associated to a ISVBase that contains + * U D matrices. + * TODO: add a reference to the journal articles + */ +class ISVMachine: public Machine<bob::machine::GMMStats, double> +{ + public: + /** + * @brief Default constructor. Builds an otherwise invalid 0 x 0 ISVMachine + * The Universal Background Model and the matrices U, V and diag(d) are + * not initialized. + */ + ISVMachine(); + + /** + * @brief Constructor. Builds a new ISVMachine. + * + * @param isv_base The ISVBase associated with this machine + */ + ISVMachine(const boost::shared_ptr<bob::machine::ISVBase> isv_base); + + /** + * @brief Copy constructor + */ + ISVMachine(const ISVMachine& other); + + /** + * @brief Starts a new ISVMachine from an existing Configuration object. + */ + ISVMachine(bob::io::base::HDF5File& config); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~ISVMachine(); + + /** + * @brief Assigns from a different ISV machine + */ + ISVMachine& operator=(const ISVMachine &other); + + /** + * @brief Equal to + */ + bool operator==(const ISVMachine& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const ISVMachine& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const ISVMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Saves machine to an HDF5 file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Loads data from an existing configuration object. Resets + * the current state. + */ + void load(bob::io::base::HDF5File& config); + + + /** + * @brief Returns the number of Gaussian components C + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimC() const + { return m_isv_base->getDimC(); } + + /** + * @brief Returns the feature dimensionality D + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimD() const + { return m_isv_base->getDimD(); } + + /** + * @brief Returns the supervector length CD + * (CxD: Number of Gaussian components by the feature dimensionality) + * @warning An exception is thrown if no Universal Background Model has + * been set yet. + */ + const size_t getDimCD() const + { return m_isv_base->getDimCD(); } + + /** + * @brief Returns the size/rank ru of the U matrix + */ + const size_t getDimRu() const + { return m_isv_base->getDimRu(); } + + /** + * @brief Returns the x session factor + */ + const blitz::Array<double,1>& getX() const + { return m_cache_x; } + + /** + * @brief Returns the z speaker factor + */ + const blitz::Array<double,1>& getZ() const + { return m_z; } + + /** + * @brief Returns the z speaker factors in order to update it + */ + blitz::Array<double,1>& updateZ() + { return m_z; } + + /** + * @brief Returns the V matrix + */ + void setZ(const blitz::Array<double,1>& z); + + /** + * @brief Returns the ISVBase + */ + const boost::shared_ptr<bob::machine::ISVBase> getISVBase() const + { return m_isv_base; } + + /** + * @brief Sets the ISVBase + */ + void setISVBase(const boost::shared_ptr<bob::machine::ISVBase> isv_base); + + + /** + * @brief Estimates x from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateX(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& x) const + { m_isv_base->estimateX(gmm_stats, x); } + /** + * @brief Estimates Ux from the GMM statistics considering the LPT + * assumption, that is the latent session variable x is approximated + * using the UBM + */ + void estimateUx(const bob::machine::GMMStats& gmm_stats, blitz::Array<double,1>& Ux); + + /** + * @brief Execute the machine + * + * @param input input data used by the machine + * @param score value computed by the machine + * @warning Inputs are checked + */ + void forward(const bob::machine::GMMStats& input, double& score) const; + /** + * @brief Computes a score for the given UBM statistics and given the + * Ux vector + */ + void forward(const bob::machine::GMMStats& gmm_stats, + const blitz::Array<double,1>& Ux, double& score) const; + + /** + * @brief Execute the machine + * + * @param input input data used by the machine + * @param score value computed by the machine + * @warning Inputs are NOT checked + */ + void forward_(const bob::machine::GMMStats& input, double& score) const; + + protected: + /** + * @brief Resize latent variable according to the ISVBase + */ + void resize(); + /** + * @ Update cache + */ + void updateCache(); + /** + * @brief Resize working arrays + */ + void resizeTmp(); + + // UBM + boost::shared_ptr<bob::machine::ISVBase> m_isv_base; + + // y and z vectors/factors learned during the enrolment procedure + blitz::Array<double,1> m_z; + + // cache + blitz::Array<double,1> m_cache_mDz; + mutable blitz::Array<double,1> m_cache_x; + + // x vector/factor in cache when computing scores + mutable blitz::Array<double,1> m_tmp_Ux; +}; + + +/** + * @} + */ +}} + +#endif // BOB_MACHINE_FABASE_H diff --git a/bob/learn/misc/include/bob.learn.misc/JFATrainer.h b/bob/learn/misc/include/bob.learn.misc/JFATrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..7519b05f6013dfd933970be082da7e953b429bc5 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/JFATrainer.h @@ -0,0 +1,687 @@ +/** + * @date Tue Jul 19 12:16:17 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief JFA functions + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_JFATRAINER_H +#define BOB_TRAINER_JFATRAINER_H + +#include <blitz/array.h> +#include <bob.learn.misc/EMTrainer.h> +#include <bob.learn.misc/GMMStats.h> +#include <bob.learn.misc/JFAMachine.h> +#include <vector> + +#include <map> +#include <string> +#include <bob.core/array_copy.h> +#include <boost/shared_ptr.hpp> +#include <boost/random.hpp> +#include <bob.core/logging.h> + +namespace bob { namespace trainer { + +class FABaseTrainer +{ + public: + /** + * @brief Constructor + */ + FABaseTrainer(); + + /** + * @brief Copy constructor + */ + FABaseTrainer(const FABaseTrainer& other); + + /** + * @brief Destructor + */ + ~FABaseTrainer(); + + /** + * @brief Check that the dimensionality of the statistics match. + */ + void checkStatistics(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + + /** + * @brief Initialize the dimensionality, the UBM, the sums of the + * statistics and the number of identities. + */ + void initUbmNidSumStatistics(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + + /** + * @brief Precomputes the sums of the zeroth order statistics over the + * sessions for each client + */ + void precomputeSumStatisticsN(const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Precomputes the sums of the first order statistics over the + * sessions for each client + */ + void precomputeSumStatisticsF(const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + + /** + * @brief Initializes (allocates and sets to zero) the x, y, z speaker + * factors + */ + void initializeXYZ(const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + + /** + * @brief Resets the x, y, z speaker factors to zero values + */ + void resetXYZ(); + + + /**** Y and V functions ****/ + /** + * @brief Computes Vt * diag(sigma)^-1 + */ + void computeVtSigmaInv(const bob::machine::FABase& m); + /** + * @brief Computes Vt_{c} * diag(sigma)^-1 * V_{c} for each Gaussian c + */ + void computeVProd(const bob::machine::FABase& m); + /** + * @brief Computes (I+Vt*diag(sigma)^-1*Ni*V)^-1 which occurs in the y + * estimation for the given person + */ + void computeIdPlusVProd_i(const size_t id); + /** + * @brief Computes sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - U*x_{i,h}) + * which occurs in the y estimation of the given person + */ + void computeFn_y_i(const bob::machine::FABase& m, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& stats, + const size_t id); + /** + * @brief Updates y_i (of the current person) and the accumulators to + * compute V with the cache values m_cache_IdPlusVprod_i, m_VtSigmaInv and + * m_cache_Fn_y_i + */ + void updateY_i(const size_t id); + /** + * @brief Updates y and the accumulators to compute V + */ + void updateY(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Computes the accumulators m_acc_V_A1 and m_acc_V_A2 for V + * V = A2 * A1^-1 + */ + void computeAccumulatorsV(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Updates V from the accumulators m_acc_V_A1 and m_acc_V_A2 + */ + void updateV(blitz::Array<double,2>& V); + + + /**** X and U functions ****/ + /** + * @brief Computes Ut * diag(sigma)^-1 + */ + void computeUtSigmaInv(const bob::machine::FABase& m); + /** + * @brief Computes Ut_{c} * diag(sigma)^-1 * U_{c} for each Gaussian c + */ + void computeUProd(const bob::machine::FABase& m); + /** + * @brief Computes (I+Ut*diag(sigma)^-1*Ni*U)^-1 which occurs in the x + * estimation + */ + void computeIdPlusUProd_ih(const boost::shared_ptr<bob::machine::GMMStats>& stats); + /** + * @brief Computes sum_{sessions h}(N_{i,h}*(o_{i,h} - m - D*z_{i} - U*x_{i,h}) + * which occurs in the y estimation of the given person + */ + void computeFn_x_ih(const bob::machine::FABase& m, + const boost::shared_ptr<bob::machine::GMMStats>& stats, const size_t id); + /** + * @brief Updates x_ih (of the current person/session) and the + * accumulators to compute U with the cache values m_cache_IdPlusVprod_i, + * m_VtSigmaInv and m_cache_Fn_y_i + */ + void updateX_ih(const size_t id, const size_t h); + /** + * @brief Updates x + */ + void updateX(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Computes the accumulators m_acc_U_A1 and m_acc_U_A2 for U + * U = A2 * A1^-1 + */ + void computeAccumulatorsU(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Updates U from the accumulators m_acc_U_A1 and m_acc_U_A2 + */ + void updateU(blitz::Array<double,2>& U); + + + /**** z and D functions ****/ + /** + * @brief Computes diag(D) * diag(sigma)^-1 + */ + void computeDtSigmaInv(const bob::machine::FABase& m); + /** + * @brief Computes Dt_{c} * diag(sigma)^-1 * D_{c} for each Gaussian c + */ + void computeDProd(const bob::machine::FABase& m); + /** + * @brief Computes (I+diag(d)t*diag(sigma)^-1*Ni*diag(d))^-1 which occurs + * in the z estimation for the given person + */ + void computeIdPlusDProd_i(const size_t id); + /** + * @brief Computes sum_{sessions h}(N_{i,h}*(o_{i,h} - m - V*y_{i} - U*x_{i,h}) + * which occurs in the y estimation of the given person + */ + void computeFn_z_i(const bob::machine::FABase& m, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& stats, const size_t id); + /** + * @brief Updates z_i (of the current person) and the accumulators to + * compute D with the cache values m_cache_IdPlusDProd_i, m_VtSigmaInv + * and m_cache_Fn_z_i + */ + void updateZ_i(const size_t id); + /** + * @brief Updates z and the accumulators to compute D + */ + void updateZ(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Computes the accumulators m_acc_D_A1 and m_acc_D_A2 for d + * d = A2 * A1^-1 + */ + void computeAccumulatorsD(const bob::machine::FABase& m, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& stats); + /** + * @brief Updates d from the accumulators m_acc_D_A1 and m_acc_D_A2 + */ + void updateD(blitz::Array<double,1>& d); + + + /** + * @brief Get the zeroth order statistics + */ + const std::vector<blitz::Array<double,1> >& getNacc() const + { return m_Nacc; } + /** + * @brief Get the first order statistics + */ + const std::vector<blitz::Array<double,1> >& getFacc() const + { return m_Facc; } + /** + * @brief Get the x speaker factors + */ + const std::vector<blitz::Array<double,2> >& getX() const + { return m_x; } + /** + * @brief Get the y speaker factors + */ + const std::vector<blitz::Array<double,1> >& getY() const + { return m_y; } + /** + * @brief Get the z speaker factors + */ + const std::vector<blitz::Array<double,1> >& getZ() const + { return m_z; } + /** + * @brief Set the x speaker factors + */ + void setX(const std::vector<blitz::Array<double,2> >& X) + { m_x = X; } + /** + * @brief Set the y speaker factors + */ + void setY(const std::vector<blitz::Array<double,1> >& y) + { m_y = y; } + /** + * @brief Set the z speaker factors + */ + void setZ(const std::vector<blitz::Array<double,1> >& z) + { m_z = z; } + + /** + * @brief Initializes the cache to process the given statistics + */ + void initCache(); + + /** + * @brief Getters for the accumulators + */ + const blitz::Array<double,3>& getAccVA1() const + { return m_acc_V_A1; } + const blitz::Array<double,2>& getAccVA2() const + { return m_acc_V_A2; } + const blitz::Array<double,3>& getAccUA1() const + { return m_acc_U_A1; } + const blitz::Array<double,2>& getAccUA2() const + { return m_acc_U_A2; } + const blitz::Array<double,1>& getAccDA1() const + { return m_acc_D_A1; } + const blitz::Array<double,1>& getAccDA2() const + { return m_acc_D_A2; } + + /** + * @brief Setters for the accumulators, Very useful if the e-Step needs + * to be parallelized. + */ + void setAccVA1(const blitz::Array<double,3>& acc) + { bob::core::array::assertSameShape(acc, m_acc_V_A1); + m_acc_V_A1 = acc; } + void setAccVA2(const blitz::Array<double,2>& acc) + { bob::core::array::assertSameShape(acc, m_acc_V_A2); + m_acc_V_A2 = acc; } + void setAccUA1(const blitz::Array<double,3>& acc) + { bob::core::array::assertSameShape(acc, m_acc_U_A1); + m_acc_U_A1 = acc; } + void setAccUA2(const blitz::Array<double,2>& acc) + { bob::core::array::assertSameShape(acc, m_acc_U_A2); + m_acc_U_A2 = acc; } + void setAccDA1(const blitz::Array<double,1>& acc) + { bob::core::array::assertSameShape(acc, m_acc_D_A1); + m_acc_D_A1 = acc; } + void setAccDA2(const blitz::Array<double,1>& acc) + { bob::core::array::assertSameShape(acc, m_acc_D_A2); + m_acc_D_A2 = acc; } + + + private: + size_t m_Nid; // Number of identities + size_t m_dim_C; // Number of Gaussian components of the UBM GMM + size_t m_dim_D; // Dimensionality of the feature space + size_t m_dim_ru; // Rank of the U subspace + size_t m_dim_rv; // Rank of the V subspace + + std::vector<blitz::Array<double,2> > m_x; // matrix x of speaker factors for eigenchannels U, for each client + std::vector<blitz::Array<double,1> > m_y; // vector y of spealer factors for eigenvoices V, for each client + std::vector<blitz::Array<double,1> > m_z; // vector z of spealer factors for eigenvoices Z, for each client + + std::vector<blitz::Array<double,1> > m_Nacc; // Sum of the zeroth order statistics over the sessions for each client, dimension C + std::vector<blitz::Array<double,1> > m_Facc; // Sum of the first order statistics over the sessions for each client, dimension CD + + // Accumulators for the M-step + blitz::Array<double,3> m_acc_V_A1; + blitz::Array<double,2> m_acc_V_A2; + blitz::Array<double,3> m_acc_U_A1; + blitz::Array<double,2> m_acc_U_A2; + blitz::Array<double,1> m_acc_D_A1; + blitz::Array<double,1> m_acc_D_A2; + + // Cache/Precomputation + blitz::Array<double,2> m_cache_VtSigmaInv; // Vt * diag(sigma)^-1 + blitz::Array<double,3> m_cache_VProd; // first dimension is the Gaussian id + blitz::Array<double,2> m_cache_IdPlusVProd_i; + blitz::Array<double,1> m_cache_Fn_y_i; + + blitz::Array<double,2> m_cache_UtSigmaInv; // Ut * diag(sigma)^-1 + blitz::Array<double,3> m_cache_UProd; // first dimension is the Gaussian id + blitz::Array<double,2> m_cache_IdPlusUProd_ih; + blitz::Array<double,1> m_cache_Fn_x_ih; + + blitz::Array<double,1> m_cache_DtSigmaInv; // Dt * diag(sigma)^-1 + blitz::Array<double,1> m_cache_DProd; // supervector length dimension + blitz::Array<double,1> m_cache_IdPlusDProd_i; + blitz::Array<double,1> m_cache_Fn_z_i; + + // Working arrays + mutable blitz::Array<double,2> m_tmp_ruru; + mutable blitz::Array<double,2> m_tmp_ruD; + mutable blitz::Array<double,2> m_tmp_rvrv; + mutable blitz::Array<double,2> m_tmp_rvD; + mutable blitz::Array<double,1> m_tmp_rv; + mutable blitz::Array<double,1> m_tmp_ru; + mutable blitz::Array<double,1> m_tmp_CD; + mutable blitz::Array<double,1> m_tmp_CD_b; +}; + + +class JFATrainer +{ + public: + /** + * @brief Constructor + */ + JFATrainer(const size_t max_iterations=10); + + /** + * @brief Copy onstructor + */ + JFATrainer(const JFATrainer& other); + + /** + * @brief Destructor + */ + virtual ~JFATrainer(); + + /** + * @brief Assignment operator + */ + JFATrainer& operator=(const JFATrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const JFATrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const JFATrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const JFATrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Sets the maximum number of EM-like iterations (for each subspace) + */ + void setMaxIterations(const size_t max_iterations) + { m_max_iterations = max_iterations; } + + /** + * @brief Gets the maximum number of EM-like iterations (for each subspace) + */ + size_t getMaxIterations() const + { return m_max_iterations; } + + /** + * @brief This methods performs some initialization before the EM loop. + */ + virtual void initialize(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief This methods performs the e-Step to train the first subspace V + */ + virtual void eStep1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the m-Step to train the first subspace V + */ + virtual void mStep1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the finalization after training the first + * subspace V + */ + virtual void finalize1(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the e-Step to train the second subspace U + */ + virtual void eStep2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the m-Step to train the second subspace U + */ + virtual void mStep2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the finalization after training the second + * subspace U + */ + virtual void finalize2(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the e-Step to train the third subspace d + */ + virtual void eStep3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the m-Step to train the third subspace d + */ + virtual void mStep3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs the finalization after training the third + * subspace d + */ + virtual void finalize3(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief This methods performs the main loops to train the subspaces U, V and d + */ + virtual void train_loop(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods trains the subspaces U, V and d + */ + virtual void train(bob::machine::JFABase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief Enrol a client + */ + void enrol(bob::machine::JFAMachine& machine, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& features, + const size_t n_iter); + + /** + * @brief Sets the Random Number Generator + */ + void setRng(const boost::shared_ptr<boost::mt19937> rng) + { m_rng = rng; } + + /** + * @brief Gets the Random Number Generator + */ + const boost::shared_ptr<boost::mt19937> getRng() const + { return m_rng; } + + /** + * @brief Get the x speaker factors + */ + const std::vector<blitz::Array<double,2> >& getX() const + { return m_base_trainer.getX(); } + /** + * @brief Get the y speaker factors + */ + const std::vector<blitz::Array<double,1> >& getY() const + { return m_base_trainer.getY(); } + /** + * @brief Get the z speaker factors + */ + const std::vector<blitz::Array<double,1> >& getZ() const + { return m_base_trainer.getZ(); } + /** + * @brief Set the x speaker factors + */ + void setX(const std::vector<blitz::Array<double,2> >& X) + { m_base_trainer.setX(X); } + /** + * @brief Set the y speaker factors + */ + void setY(const std::vector<blitz::Array<double,1> >& y) + { m_base_trainer.setY(y); } + /** + * @brief Set the z speaker factors + */ + void setZ(const std::vector<blitz::Array<double,1> >& z) + { m_base_trainer.setZ(z); } + + /** + * @brief Getters for the accumulators + */ + const blitz::Array<double,3>& getAccVA1() const + { return m_base_trainer.getAccVA1(); } + const blitz::Array<double,2>& getAccVA2() const + { return m_base_trainer.getAccVA2(); } + const blitz::Array<double,3>& getAccUA1() const + { return m_base_trainer.getAccUA1(); } + const blitz::Array<double,2>& getAccUA2() const + { return m_base_trainer.getAccUA2(); } + const blitz::Array<double,1>& getAccDA1() const + { return m_base_trainer.getAccDA1(); } + const blitz::Array<double,1>& getAccDA2() const + { return m_base_trainer.getAccDA2(); } + + /** + * @brief Setters for the accumulators, Very useful if the e-Step needs + * to be parallelized. + */ + void setAccVA1(const blitz::Array<double,3>& acc) + { m_base_trainer.setAccVA1(acc); } + void setAccVA2(const blitz::Array<double,2>& acc) + { m_base_trainer.setAccVA2(acc); } + void setAccUA1(const blitz::Array<double,3>& acc) + { m_base_trainer.setAccUA1(acc); } + void setAccUA2(const blitz::Array<double,2>& acc) + { m_base_trainer.setAccUA2(acc); } + void setAccDA1(const blitz::Array<double,1>& acc) + { m_base_trainer.setAccDA1(acc); } + void setAccDA2(const blitz::Array<double,1>& acc) + { m_base_trainer.setAccDA2(acc); } + + + private: + // Attributes + size_t m_max_iterations; + boost::shared_ptr<boost::mt19937> m_rng; ///< The random number generator for the inialization + bob::trainer::FABaseTrainer m_base_trainer; +}; + + +class ISVTrainer: public EMTrainer<bob::machine::ISVBase, std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > > > +{ + public: + /** + * @brief Constructor + */ + ISVTrainer(const size_t max_iterations=10, const double relevance_factor=4.); + + /** + * @brief Copy onstructor + */ + ISVTrainer(const ISVTrainer& other); + + /** + * @brief Destructor + */ + virtual ~ISVTrainer(); + + /** + * @brief Assignment operator + */ + ISVTrainer& operator=(const ISVTrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const ISVTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const ISVTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const ISVTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief This methods performs some initialization before the EM loop. + */ + virtual void initialize(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + /** + * @brief This methods performs some actions after the EM loop. + */ + virtual void finalize(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief Calculates and saves statistics across the dataset + * The statistics will be used in the mStep() that follows. + */ + virtual void eStep(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief Performs a maximization step to update the parameters of the + * factor analysis model. + */ + virtual void mStep(bob::machine::ISVBase& machine, + const std::vector<std::vector<boost::shared_ptr<bob::machine::GMMStats> > >& ar); + + /** + * @brief Computes the average log likelihood using the current estimates + * of the latent variables. + */ + virtual double computeLikelihood(bob::machine::ISVBase& machine); + + /** + * @brief Enrol a client + */ + void enrol(bob::machine::ISVMachine& machine, + const std::vector<boost::shared_ptr<bob::machine::GMMStats> >& features, + const size_t n_iter); + + /** + * @brief Get the x speaker factors + */ + const std::vector<blitz::Array<double,2> >& getX() const + { return m_base_trainer.getX(); } + /** + * @brief Get the z speaker factors + */ + const std::vector<blitz::Array<double,1> >& getZ() const + { return m_base_trainer.getZ(); } + /** + * @brief Set the x speaker factors + */ + void setX(const std::vector<blitz::Array<double,2> >& X) + { m_base_trainer.setX(X); } + /** + * @brief Set the z speaker factors + */ + void setZ(const std::vector<blitz::Array<double,1> >& z) + { m_base_trainer.setZ(z); } + + /** + * @brief Getters for the accumulators + */ + const blitz::Array<double,3>& getAccUA1() const + { return m_base_trainer.getAccUA1(); } + const blitz::Array<double,2>& getAccUA2() const + { return m_base_trainer.getAccUA2(); } + + /** + * @brief Setters for the accumulators, Very useful if the e-Step needs + * to be parallelized. + */ + void setAccUA1(const blitz::Array<double,3>& acc) + { m_base_trainer.setAccUA1(acc); } + void setAccUA2(const blitz::Array<double,2>& acc) + { m_base_trainer.setAccUA2(acc); } + + + private: + /** + * @brief Initialize D to sqrt(ubm_var/relevance_factor) + */ + void initializeD(bob::machine::ISVBase& machine) const; + + // Attributes + bob::trainer::FABaseTrainer m_base_trainer; + double m_relevance_factor; +}; + + +}} + +#endif /* BOB_TRAINER_FATRAINER_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/KMeansMachine.h b/bob/learn/misc/include/bob.learn.misc/KMeansMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..38413c1ef6bff0347fd951df7c777dc287ee4d58 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/KMeansMachine.h @@ -0,0 +1,251 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ +#ifndef BOB_MACHINE_KMEANSMACHINE_H +#define BOB_MACHINE_KMEANSMACHINE_H + +#include <blitz/array.h> +#include <cfloat> + +#include <bob.io.base/HDF5File.h> +#include <bob.learn.misc/Machine.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief This class implements a k-means classifier. + * @details See Section 9.1 of Bishop, "Pattern recognition and machine learning", 2006 + */ +class KMeansMachine: public Machine<blitz::Array<double,1>, double> { + public: + /** + * Default constructor. Builds an otherwise invalid 0 x 0 k-means + * machine. This is equivalent to construct a LinearMachine with two + * size_t parameters set to 0, as in LinearMachine(0, 0). + */ + KMeansMachine(); + + /** + * Constructor + * @param[in] n_means The number of means + * @param[in] n_inputs The feature dimensionality + */ + KMeansMachine(const size_t n_means, const size_t n_inputs); + + /** + * Builds a new machine with the given means. Each row of the means + * matrix should represent a mean. + */ + KMeansMachine(const blitz::Array<double,2>& means); + + /** + * Copies another machine (copy constructor) + */ + KMeansMachine(const KMeansMachine& other); + + /** + * Starts a new KMeansMachine from an existing Configuration object. + */ + KMeansMachine(bob::io::base::HDF5File& config); + + /** + * Destructor + */ + virtual ~KMeansMachine(); + + /** + * Assigns from a different machine + */ + KMeansMachine& operator=(const KMeansMachine& other); + + /** + * Equal to + */ + bool operator==(const KMeansMachine& b) const; + + /** + * Not equal to + */ + bool operator!=(const KMeansMachine& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const KMeansMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * Loads data from an existing configuration object. Resets the current + * state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * Saves an existing machine to a Configuration object. + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * Output the minimum (Square Euclidean) distance between the input and + * one of the means (overrides Machine::forward) + */ + void forward(const blitz::Array<double,1>& input, double& output) const; + + /** + * Output the minimum (Square Euclidean) distance between the input and + * one of the means (overrides Machine::forward_) + * @warning Inputs are NOT checked + */ + void forward_(const blitz::Array<double,1>& input, double& output) const; + + + /** + * Set the means + */ + void setMeans(const blitz::Array<double,2>& means); + + /** + * Set the i'th mean + */ + void setMean(const size_t i, const blitz::Array<double,1>& mean); + + /** + * Get a mean + * @param[in] i The index of the mean + * @param[out] mean The mean, a 1D array, with a length equal to the number of feature dimensions. + */ + void getMean(const size_t i, blitz::Array<double,1>& mean) const; + + /** + * Get the means (i.e. a 2D array, with as many rows as means, and as + * many columns as feature dimensions.) + */ + const blitz::Array<double,2>& getMeans() const + { return m_means; } + + /** + * Get the means in order to be updated (i.e. a 2D array, with as many + * rows as means, and as many columns as feature dimensions.) + * @warning Only trainers should use this function for efficiency reasons + */ + blitz::Array<double,2>& updateMeans() + { return m_means; } + + /** + * Return the power of two of the (Square Euclidean) distance of the + * sample, x, to the i'th mean + * @param x The data sample (feature vector) + * @param i The index of the mean + */ + double getDistanceFromMean(const blitz::Array<double,1>& x, + const size_t i) const; + + /** + * Calculate the index of the mean that is closest + * (in terms of Square Euclidean distance) to the data sample, x + * @param x The data sample (feature vector) + * @param closest_mean (output) The index of the mean closest to the sample + * @param min_distance (output) The distance of the sample from the closest mean + */ + void getClosestMean(const blitz::Array<double,1>& x, + size_t &closest_mean, double &min_distance) const; + + /** + * Output the minimum (Square Euclidean) distance between the input and + * one of the means + */ + double getMinDistance(const blitz::Array<double,1>& input) const; + + /** + * For each mean, find the subset of the samples + * that is closest to that mean, and calculate + * 1) the variance of that subset (the cluster variance) + * 2) the proportion of the samples represented by that subset (the cluster weight) + * @param[in] data The data + * @param[out] variances The cluster variances (one row per cluster), + * with as many columns as feature dimensions. + * @param[out] weights A vector of weights, one per cluster + */ + void getVariancesAndWeightsForEachCluster(const blitz::Array<double,2> &data, blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const; + /** + * Methods consecutively called by getVariancesAndWeightsForEachCluster() + * This should help for the parallelization on several nodes by splitting the data and calling + * getVariancesAndWeightsForEachClusterAcc() for each split. In this case, there is a need to sum + * with the m_cache_means, variances, and weights variables before performing the merge on one + * node using getVariancesAndWeightsForEachClusterFin(). + */ + void getVariancesAndWeightsForEachClusterInit(blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const; + void getVariancesAndWeightsForEachClusterAcc(const blitz::Array<double,2> &data, blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const; + void getVariancesAndWeightsForEachClusterFin(blitz::Array<double,2>& variances, blitz::Array<double,1>& weights) const; + + /** + * Get the m_cache_means array. + * @warning This variable should only be used in the case you want to parallelize the + * getVariancesAndWeightsForEachCluster() method! + */ + const blitz::Array<double,2>& getCacheMeans() const + { return m_cache_means; } + + /** + * Set the m_cache_means array. + * @warning This variable should only be used in the case you want to parallelize the + * getVariancesAndWeightsForEachCluster() method! + */ + void setCacheMeans(const blitz::Array<double,2>& cache_means); + + /** + * Resize the means + */ + void resize(const size_t n_means, const size_t n_inputs); + + /** + * Return the number of means + */ + size_t getNMeans() const { return m_n_means; } + + /** + * Return the number of inputs + */ + size_t getNInputs() const { return m_n_inputs; } + + /** + * Prints a KMeansMachine in the output stream + */ + friend std::ostream& operator<<(std::ostream& os, const KMeansMachine& km); + + + private: + /** + * The number of means + */ + size_t m_n_means; + + /** + * The number of inputs + */ + size_t m_n_inputs; + + /** + * The means (each row is a mean) + */ + blitz::Array<double,2> m_means; + + /** + * cache to avoid re-allocation + */ + mutable blitz::Array<double,2> m_cache_means; +}; + +/** + * @} + */ +}} +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/KMeansTrainer.h b/bob/learn/misc/include/bob.learn.misc/KMeansTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..52d5a14b9ed728f07ece77b449dfa47298072cb5 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/KMeansTrainer.h @@ -0,0 +1,200 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ +#ifndef BOB_TRAINER_KMEANSTRAINER_H +#define BOB_TRAINER_KMEANSTRAINER_H + +#include <bob.learn.misc/KMeansMachine.h> +#include <bob.learn.misc/EMTrainer.h> +#include <boost/version.hpp> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * Trains a KMeans machine. + * @brief This class implements the expectation-maximisation algorithm for a k-means machine. + * @details See Section 9.1 of Bishop, "Pattern recognition and machine learning", 2006 + * It uses a random initialisation of the means followed by the expectation-maximization algorithm + */ +class KMeansTrainer: public EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> > +{ + public: + /** + * @brief This enumeration defines different initialization methods for + * K-means + */ + typedef enum { + RANDOM=0, + RANDOM_NO_DUPLICATE +#if BOOST_VERSION >= 104700 + , + KMEANS_PLUS_PLUS +#endif + } + InitializationMethod; + + /** + * @brief Constructor + */ + KMeansTrainer(double convergence_threshold=0.001, + size_t max_iterations=10, bool compute_likelihood=true, + InitializationMethod=RANDOM); + + /** + * @brief Virtualize destructor + */ + virtual ~KMeansTrainer() {} + + /** + * @brief Copy constructor + */ + KMeansTrainer(const KMeansTrainer& other); + + /** + * @brief Assigns from a different machine + */ + KMeansTrainer& operator=(const KMeansTrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const KMeansTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const KMeansTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const KMeansTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief The name for this trainer + */ + virtual std::string name() const { return "KMeansTrainer"; } + + /** + * @brief Initialise the means randomly. + * Data is split into as many chunks as there are means, + * then each mean is set to a random example within each chunk. + */ + virtual void initialize(bob::machine::KMeansMachine& kMeansMachine, + const blitz::Array<double,2>& sampler); + + /** + * @brief Accumulate across the dataset: + * - zeroeth and first order statistics + * - average (Square Euclidean) distance from the closest mean + * Implements EMTrainer::eStep(double &) + */ + virtual void eStep(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>& data); + + /** + * @brief Updates the mean based on the statistics from the E-step. + */ + virtual void mStep(bob::machine::KMeansMachine& kmeans, + const blitz::Array<double,2>&); + + /** + * @brief This functions returns the average min (Square Euclidean) + * distance (average distance to the closest mean) + */ + virtual double computeLikelihood(bob::machine::KMeansMachine& kmeans); + + /** + * @brief Function called at the end of the training + */ + virtual void finalize(bob::machine::KMeansMachine& kMeansMachine, const blitz::Array<double,2>& sampler); + + /** + * @brief Reset the statistics accumulators + * to the correct size and a value of zero. + */ + bool resetAccumulators(bob::machine::KMeansMachine& kMeansMachine); + + /** + * @brief Sets the Random Number Generator + */ + void setRng(const boost::shared_ptr<boost::mt19937> rng) + { m_rng = rng; } + + /** + * @brief Gets the Random Number Generator + */ + const boost::shared_ptr<boost::mt19937> getRng() const + { return m_rng; } + + /** + * @brief Sets the initialization method used to generate the initial means + */ + void setInitializationMethod(InitializationMethod v) { m_initialization_method = v; } + + /** + * @brief Gets the initialization method used to generate the initial means + */ + InitializationMethod getInitializationMethod() const { return m_initialization_method; } + + /** + * @brief Returns the internal statistics. Useful to parallelize the E-step + */ + const blitz::Array<double,1>& getZeroethOrderStats() const { return m_zeroethOrderStats; } + const blitz::Array<double,2>& getFirstOrderStats() const { return m_firstOrderStats; } + double getAverageMinDistance() const { return m_average_min_distance; } + /** + * @brief Sets the internal statistics. Useful to parallelize the E-step + */ + void setZeroethOrderStats(const blitz::Array<double,1>& zeroethOrderStats); + void setFirstOrderStats(const blitz::Array<double,2>& firstOrderStats); + void setAverageMinDistance(const double value) { m_average_min_distance = value; } + + + protected: + /** + * @brief The initialization method + * Check that there is no duplicated means during the random initialization + */ + InitializationMethod m_initialization_method; + + /** + * @brief The random number generator for the inialization + */ + boost::shared_ptr<boost::mt19937> m_rng; + + /** + * @brief Average min (Square Euclidean) distance + */ + double m_average_min_distance; + + /** + * @brief Zeroeth order statistics accumulator. + * The k'th value in m_zeroethOrderStats is the denominator of + * equation 9.4, Bishop, "Pattern recognition and machine learning", 2006 + */ + blitz::Array<double,1> m_zeroethOrderStats; + + /** + * @brief First order statistics accumulator. + * The k'th row of m_firstOrderStats is the numerator of + * equation 9.4, Bishop, "Pattern recognition and machine learning", 2006 + */ + blitz::Array<double,2> m_firstOrderStats; +}; + +/** + * @} + */ +}} + +#endif // BOB_TRAINER_KMEANSTRAINER_H diff --git a/bob/learn/misc/include/bob.learn.misc/LinearScoring.h b/bob/learn/misc/include/bob.learn.misc/LinearScoring.h new file mode 100644 index 0000000000000000000000000000000000000000..353dbf5907311bfc1862d7944203bea8b27e5745 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/LinearScoring.h @@ -0,0 +1,105 @@ +/** + * @date Wed Jul 13 16:00:04 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ +#ifndef BOB_MACHINE_LINEARSCORING_H +#define BOB_MACHINE_LINEARSCORING_H + +#include <blitz/array.h> +#include <boost/shared_ptr.hpp> +#include <vector> +#include <bob.learn.misc/GMMMachine.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * Compute a matrix of scores using linear scoring. + * + * @warning Each GMM must have the same size. + * + * @param models list of mean supervector for the client models + * @param ubm_mean mean supervector of the world model + * @param ubm_variance variance supervector of the world model + * @param test_stats list of accumulate statistics for each test trial + * @param test_channelOffset list of channel offset if any (for JFA/ISA for instance) + * @param frame_length_normalisation perform a normalisation by the number of feature vectors + * @param[out] scores 2D matrix of scores, <tt>scores[m, s]</tt> is the score for model @c m against statistics @c s + * @warning the output scores matrix should have the correct size (number of models x number of test_stats) + */ +void linearScoring(const std::vector<blitz::Array<double,1> >& models, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const std::vector<blitz::Array<double, 1> >& test_channelOffset, + const bool frame_length_normalisation, + blitz::Array<double,2>& scores); +void linearScoring(const std::vector<blitz::Array<double,1> >& models, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const bool frame_length_normalisation, + blitz::Array<double,2>& scores); + +/** + * Compute a matrix of scores using linear scoring. + * + * @warning Each GMM must have the same size. + * + * @param models list of client models as GMMMachines + * @param ubm world model as a GMMMachine + * @param test_stats list of accumulate statistics for each test trial + * @param frame_length_normalisation perform a normalisation by the number of feature vectors + * @param[out] scores 2D matrix of scores, <tt>scores[m, s]</tt> is the score for model @c m against statistics @c s + * @warning the output scores matrix should have the correct size (number of models x number of test_stats) + */ +void linearScoring(const std::vector<boost::shared_ptr<const bob::machine::GMMMachine> >& models, + const bob::machine::GMMMachine& ubm, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const bool frame_length_normalisation, + blitz::Array<double,2>& scores); +/** + * Compute a matrix of scores using linear scoring. + * + * @warning Each GMM must have the same size. + * + * @param models list of client models as GMMMachines + * @param ubm world model as a GMMMachine + * @param test_stats list of accumulate statistics for each test trial + * @param test_channelOffset list of channel offset if any (for JFA/ISA for instance) + * @param frame_length_normalisation perform a normalisation by the number of feature vectors + * @param[out] scores 2D matrix of scores, <tt>scores[m, s]</tt> is the score for model @c m against statistics @c s + * @warning the output scores matrix should have the correct size (number of models x number of test_stats) + */ +void linearScoring(const std::vector<boost::shared_ptr<const bob::machine::GMMMachine> >& models, + const bob::machine::GMMMachine& ubm, + const std::vector<boost::shared_ptr<const bob::machine::GMMStats> >& test_stats, + const std::vector<blitz::Array<double, 1> >& test_channelOffset, + const bool frame_length_normalisation, + blitz::Array<double,2>& scores); + +/** + * Compute a score using linear scoring. + * + * @param model mean supervector for the client model + * @param ubm_mean mean supervector of the world model + * @param ubm_variance variance supervector of the world model + * @param test_stats accumulate statistics of the test trial + * @param test_channelOffset channel offset + * @param frame_length_normalisation perform a normalisation by the number of feature vectors + */ +double linearScoring(const blitz::Array<double,1>& model, + const blitz::Array<double,1>& ubm_mean, const blitz::Array<double,1>& ubm_variance, + const bob::machine::GMMStats& test_stats, + const blitz::Array<double,1>& test_channelOffset, + const bool frame_length_normalisation); + +/** + * @} + */ +}} + +#endif // BOB_MACHINE_LINEARSCORING_H diff --git a/bob/learn/misc/include/bob.learn.misc/MAP_GMMTrainer.h b/bob/learn/misc/include/bob.learn.misc/MAP_GMMTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..03790bcd682b07128b8db29a889d6068e266bc7c --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/MAP_GMMTrainer.h @@ -0,0 +1,133 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * @brief This class implements the maximum a posteriori M-step of the expectation-maximisation algorithm for a GMM Machine. The prior parameters are encoded in the form of a GMM (e.g. a universal background model). The EM algorithm thus performs GMM adaptation. + * @details See Section 3.4 of Reynolds et al., "Speaker Verification Using Adapted Gaussian Mixture Models", Digital Signal Processing, 2000. We use a "single adaptation coefficient", alpha_i, and thus a single relevance factor, r. + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_MAP_GMMTRAINER_H +#define BOB_TRAINER_MAP_GMMTRAINER_H + +#include <bob.learn.misc/GMMTrainer.h> +#include <limits> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief This class implements the maximum a posteriori M-step of the expectation-maximisation algorithm for a GMM Machine. The prior parameters are encoded in the form of a GMM (e.g. a universal background model). The EM algorithm thus performs GMM adaptation. + * @details See Section 3.4 of Reynolds et al., "Speaker Verification Using Adapted Gaussian Mixture Models", Digital Signal Processing, 2000. We use a "single adaptation coefficient", alpha_i, and thus a single relevance factor, r. + */ +class MAP_GMMTrainer: public GMMTrainer +{ + public: + /** + * @brief Default constructor + */ + MAP_GMMTrainer(const double relevance_factor=0, const bool update_means=true, + const bool update_variances=false, const bool update_weights=false, + const double mean_var_update_responsibilities_threshold = + std::numeric_limits<double>::epsilon()); + + /** + * @brief Copy constructor + */ + MAP_GMMTrainer(const MAP_GMMTrainer& other); + + /** + * @brief Destructor + */ + virtual ~MAP_GMMTrainer(); + + /** + * @brief Initialization + */ + virtual void initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Assigns from a different MAP_GMMTrainer + */ + MAP_GMMTrainer& operator=(const MAP_GMMTrainer &other); + + /** + * @brief Equal to + */ + bool operator==(const MAP_GMMTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const MAP_GMMTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const MAP_GMMTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Set the GMM to use as a prior for MAP adaptation. + * Generally, this is a "universal background model" (UBM), + * also referred to as a "world model". + */ + bool setPriorGMM(boost::shared_ptr<bob::machine::GMMMachine> prior_gmm); + + /** + * @brief Performs a maximum a posteriori (MAP) update of the GMM + * parameters using the accumulated statistics in m_ss and the + * parameters of the prior model + * Implements EMTrainer::mStep() + */ + void mStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Use a Torch3-like adaptation rule rather than Reynolds'one + * In this case, alpha is a configuration variable rather than a function of the zeroth + * order statistics and a relevance factor (should be in range [0,1]) + */ + void setT3MAP(const double alpha) { m_T3_adaptation = true; m_T3_alpha = alpha; } + void unsetT3MAP() { m_T3_adaptation = false; } + + protected: + + /** + * The relevance factor for MAP adaptation, r (see Reynolds et al., \"Speaker Verification Using Adapted Gaussian Mixture Models\", Digital Signal Processing, 2000). + */ + double m_relevance_factor; + + /** + * The GMM to use as a prior for MAP adaptation. + * Generally, this is a "universal background model" (UBM), + * also referred to as a "world model" + */ + boost::shared_ptr<bob::machine::GMMMachine> m_prior_gmm; + + /** + * The alpha for the Torch3-like adaptation + */ + double m_T3_alpha; + /** + * Whether Torch3-like adaptation should be used or not + */ + bool m_T3_adaptation; + + private: + /// cache to avoid re-allocation + mutable blitz::Array<double,1> m_cache_alpha; + mutable blitz::Array<double,1> m_cache_ml_weights; +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/ML_GMMTrainer.h b/bob/learn/misc/include/bob.learn.misc/ML_GMMTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..d4220f49ee99499a6cc195ebddde77a3ee1f90d1 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/ML_GMMTrainer.h @@ -0,0 +1,97 @@ +/** + * @date Tue May 10 11:35:58 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * + * @brief This class implements the maximum likelihood M-step of the expectation-maximisation algorithm for a GMM Machine. + * @details See Section 9.2.2 of Bishop, "Pattern recognition and machine learning", 2006 + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_ML_GMMTRAINER_H +#define BOB_TRAINER_ML_GMMTRAINER_H + +#include <bob.learn.misc/GMMTrainer.h> +#include <limits> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief This class implements the maximum likelihood M-step of the + * expectation-maximisation algorithm for a GMM Machine. + * @details See Section 9.2.2 of Bishop, + * "Pattern recognition and machine learning", 2006 + */ +class ML_GMMTrainer: public GMMTrainer { + public: + /** + * @brief Default constructor + */ + ML_GMMTrainer(const bool update_means=true, + const bool update_variances=false, const bool update_weights=false, + const double mean_var_update_responsibilities_threshold = + std::numeric_limits<double>::epsilon()); + + /** + * @brief Copy constructor + */ + ML_GMMTrainer(const ML_GMMTrainer& other); + + /** + * @brief Destructor + */ + virtual ~ML_GMMTrainer(); + + /** + * @brief Initialisation before the EM steps + */ + virtual void initialize(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Performs a maximum likelihood (ML) update of the GMM parameters + * using the accumulated statistics in m_ss + * Implements EMTrainer::mStep() + */ + virtual void mStep(bob::machine::GMMMachine& gmm, + const blitz::Array<double,2>& data); + + /** + * @brief Assigns from a different ML_GMMTrainer + */ + ML_GMMTrainer& operator=(const ML_GMMTrainer &other); + + /** + * @brief Equal to + */ + bool operator==(const ML_GMMTrainer& b) const; + + /** + * @brief Not equal to + */ + bool operator!=(const ML_GMMTrainer& b) const; + + /** + * @brief Similar to + */ + bool is_similar_to(const ML_GMMTrainer& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + + private: + /** + * @brief Add cache to avoid re-allocation at each iteration + */ + mutable blitz::Array<double,1> m_cache_ss_n_thresholded; +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/Machine.h b/bob/learn/misc/include/bob.learn.misc/Machine.h new file mode 100644 index 0000000000000000000000000000000000000000..55599944eb34972ba10f839a977ebca8051df929 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/Machine.h @@ -0,0 +1,59 @@ +/** + * @date Tue Jan 18 17:07:26 2011 +0100 + * @author André Anjos <andre.anjos@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_MACHINE_H +#define BOB_MACHINE_MACHINE_H + +#include <cstring> + +/** + * @addtogroup MACHINE machine + * @brief Machine module API + */ +namespace bob { +/** + * @ingroup MACHINE + */ +namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * Root class for all machines + */ +template<class T_input, class T_output> +class Machine +{ + public: + virtual ~Machine() {} + + /** + * Execute the machine + * + * @param input input data used by the machine + * @param output value computed by the machine + * @warning Inputs are checked + */ + virtual void forward(const T_input& input, T_output& output) const = 0; + + /** + * Execute the machine + * + * @param input input data used by the machine + * @param output value computed by the machine + * @warning Inputs are NOT checked + */ + virtual void forward_(const T_input& input, T_output& output) const = 0; +}; + +/** + * @} + */ +}} +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/PLDAMachine.h b/bob/learn/misc/include/bob.learn.misc/PLDAMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..d66643c40042d45623656d1361999a872d5d7835 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/PLDAMachine.h @@ -0,0 +1,710 @@ +/** + * @date Fri Oct 14 18:07:56 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Machines that implements the Probabilistic Linear Discriminant + * Analysis Model of Prince and Helder, + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_PLDAMACHINE_H +#define BOB_MACHINE_PLDAMACHINE_H + +#include <bob.learn.misc/Machine.h> +#include <blitz/array.h> +#include <bob.io.base/HDF5File.h> +#include <map> +#include <iostream> +#include <stdexcept> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief This class is a container for the \f$F\f$, \f$G\f$ and \f$\Sigma\f$ + * matrices and the mean vector \f$\mu\f$ of a PLDA model. This also + * precomputes useful matrices to make the model scalable.\n + * References:\n + * 1. 'A Scalable Formulation of Probabilistic Linear Discriminant Analysis: + * Applied to Face Recognition', Laurent El Shafey, Chris McCool, + * Roy Wallace, Sebastien Marcel, TPAMI'2013 + * 2. 'Probabilistic Linear Discriminant Analysis for Inference About + * Identity', Prince and Elder, ICCV'2007\n + * 3. 'Probabilistic Models for Inference about Identity', Li, Fu, Mohammed, + * Elder and Prince, TPAMI'2012 + */ +class PLDABase +{ + public: + /** + * @brief Default constructor.\n Builds an otherwise invalid 0x0x0 + * PLDABase. + */ + PLDABase(); + /** + * @brief Constructor, builds a new PLDABase.\n \f$F\f$, \f$G\f$ + * and \f$\Sigma\f$ are initialized to the 'eye' matrix (matrix with 1's + * on the diagonal and 0 outside), and \f$\mu\f$ is initialized to 0. + * + * @param dim_d Dimensionality of the feature vector + * @param dim_f size of \f$F\f$ (dim_d x dim_f) + * @param dim_g size of \f$G\f$ (dim_d x dim_g) + * @param variance_threshold The smallest possible value of the variance + * (Ignored if set to 0.) + */ + PLDABase(const size_t dim_d, const size_t dim_f, + const size_t dim_g, const double variance_threshold=0.); + /** + * @brief Copies another PLDABase + */ + PLDABase(const PLDABase& other); + /** + * @brief Starts a new PLDABase from an existing configuration + * object. + * @param config HDF5 configuration file + */ + PLDABase(bob::io::base::HDF5File& config); + + /** + * @brief Just to virtualize the destructor + */ + virtual ~PLDABase(); + + /** + * @brief Assigns from a different PLDABase + */ + PLDABase& operator=(const PLDABase &other); + + /** + * @brief Equal to.\n Even precomputed members such as \f$\alpha\f$, + * \f$\beta\f$ and \f$\gamma_a\f$'s are compared! + */ + bool operator==(const PLDABase& b) const; + /** + * @brief Not equal to.\n Defined as the negation of operator== + */ + bool operator!=(const PLDABase& b) const; + /** + * @brief Similar to.\n Even precomputed members such as \f$\alpha\f$, + * \f$\beta\f$ and \f$\gamma_a\f$'s are compared! + */ + bool is_similar_to(const PLDABase& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Loads data from an existing configuration object. Resets the + * current state. + * @param config HDF5 configuration file + */ + void load(bob::io::base::HDF5File& config); + /** + * @brief Saves an existing machine to a configuration object. + * @param config HDF5 configuration file + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Resizes the PLDABase. + * @warning \f$F\f$, \f$G\f$, \f$\Sigma\f$, \f$\mu\f$ and the variance + * flooring thresholds will be reinitialized! + * @param dim_d Dimensionality of the feature vector + * @param dim_f Rank of \f$F\f$ (dim_d x dim_f) + * @param dim_g Rank of \f$G\f$ (dim_d x dim_g) + */ + void resize(const size_t dim_d, const size_t dim_f, const size_t dim_g); + + /** + * @brief Gets the \f$F\f$ subspace/matrix of the PLDA model + */ + const blitz::Array<double,2>& getF() const + { return m_F; } + /** + * @brief Sets the \f$F\f$ subspace/matrix of the PLDA model + */ + void setF(const blitz::Array<double,2>& F); + /** + * @brief Returns the current \f$F\f$ matrix/subspace of the PLDA model + * in order to be updated. + * @warning Use with care. Only trainers should use this function for + * efficiency reasons. + */ + blitz::Array<double,2>& updateF() + { return m_F; } + + /** + * @brief Gets the \f$G\f$ subspace/matrix of the PLDA model + */ + const blitz::Array<double,2>& getG() const + { return m_G; } + /** + * @brief Sets the \f$G\f$ subspace/matrix of the PLDA model + */ + void setG(const blitz::Array<double,2>& G); + /** + * @brief Returns the current \f$G\f$ subspace/matrix of the PLDA model + * in order to be updated. + * @warning Use with care. Only trainers should use this function for + * efficiency reasons. + */ + blitz::Array<double,2>& updateG() + { return m_G; } + + /** + * @brief Gets the \f$\Sigma\f$ (diagonal) covariance matrix of the PLDA + * model + */ + const blitz::Array<double,1>& getSigma() const + { return m_sigma; } + /** + * @brief Sets the \f$\Sigma\f$ (diagonal) covariance matrix of the PLDA + * model + */ + void setSigma(const blitz::Array<double,1>& s); + /** + * @brief Returns the current \f$\Sigma\f$ (diagonal) covariance matrix of + * the PLDA model in order to be updated. + * @warning Use with care. Only trainers should use this function for + * efficiency reasons. Variance threshold should be applied after + * updating \f$\Sigma\f$! + */ + blitz::Array<double,1>& updateSigma() + { return m_sigma; } + + /** + * @brief Gets the \f$\mu\f$ mean vector of the PLDA model + */ + const blitz::Array<double,1>& getMu() const + { return m_mu; } + /** + * @brief Sets the \f$\mu\f$ mean vector of the PLDA model + */ + void setMu(const blitz::Array<double,1>& mu); + /** + * @brief Returns the current \f$\mu\f$ mean vector of the PLDA model + * in order to be updated. + * @warning Use with care. Only trainers should use this function for + * efficiency reasons. + */ + blitz::Array<double,1>& updateMu() + { return m_mu; } + + /** + * @brief Gets the variance flooring threshold + */ + double getVarianceThreshold() const + { return m_variance_threshold; } + /** + * @brief Sets the variance flooring threshold + */ + void setVarianceThreshold(const double value); + /** + * @brief Apply the variance flooring thresholds. + * This method is automatically called when using setVarianceThresholds(). + * @warning It is only useful when using updateVarianceThreshods(), + * and should mostly be done by trainers + */ + void applyVarianceThreshold(); + + /** + * @brief Gets the feature dimensionality + */ + size_t getDimD() const + { return m_dim_d; } + /** + * @brief Gets the size/rank the \f$F\f$ subspace/matrix of the PLDA model + */ + size_t getDimF() const + { return m_dim_f; } + /** + * @brief Gets the size/rank the \f$G\f$ subspace/matrix of the PLDA model + */ + size_t getDimG() const + { return m_dim_g; } + + /** + * @brief Precomputes useful values such as \f$\Sigma^{-1}\f$, + * \f$G^{T}\Sigma^{-1}\f$, \f$\alpha\f$, \f$\beta\f$, and + * \f$F^{T}\beta\f$. + * @warning Previous \f$\gamma_a\f$ values and log likelihood constant + * terms are cleared. + */ + void precompute(); + /** + * @brief Precomputes useful values for the log likelihood + * \f$\log(\det(\alpha))\f$ and \f$\log(\det(\Sigma))\f$. + */ + void precomputeLogLike(); + /** + * @brief Gets the inverse vector/diagonal matrix of \f$\Sigma^{-1}\f$ + */ + const blitz::Array<double,1>& getISigma() const + { return m_cache_isigma; } + /** + * @brief Gets the \f$\alpha\f$ matrix. + * \f$\alpha = (Id + G^T \Sigma^{-1} G)^{-1} = \mathcal{G}\f$ + */ + const blitz::Array<double,2>& getAlpha() const + { return m_cache_alpha; } + /** + * @brief Gets the \f$\beta\f$ matrix + * \f$\beta = (\Sigma + G G^T)^{-1} = \mathcal{S} = + * \Sigma^{-1} - \Sigma^{-1} G \mathcal{G} G^{T} \Sigma^{-1}\f$ + */ + const blitz::Array<double,2>& getBeta() const + { return m_cache_beta; } + /** + * @brief Gets the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number of + * samples). + * \f$\gamma_{a} = (Id + a F^T \beta F)^{-1} = \mathcal{F}_{a}\f$ + * @warning an exception is thrown if \f$\gamma_a\f$ does not exists + */ + const blitz::Array<double,2>& getGamma(const size_t a) const; + /** + * @brief Gets the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number of + * samples). + * \f$\gamma_a = (Id + a F^T \beta F)^{-1} = \mathcal{F}_{a}\f$ + * @warning The matrix is computed if it does not already exists + */ + const blitz::Array<double,2>& getAddGamma(const size_t a); + /** + * @brief Gets the \f$F^T \beta\f$ matrix + */ + const blitz::Array<double,2>& getFtBeta() const + { return m_cache_Ft_beta; } + /** + * @brief Gets the \f$G^T \Sigma^{-1}\f$ matrix + */ + const blitz::Array<double,2>& getGtISigma() const + { return m_cache_Gt_isigma; } + /** + * @brief Gets \f$\log(\det(\alpha))\f$ + */ + double getLogDetAlpha() const + { return m_cache_logdet_alpha; } + /** + * @brief Gets \f$\log(\det(\Sigma))\f$ + */ + double getLogDetSigma() const + { return m_cache_logdet_sigma; } + /** + * @brief Computes the log likelihood constant term for a given \f$a\f$ + * (number of samples), given the provided \f$\gamma_a\f$ matrix + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + */ + double computeLogLikeConstTerm(const size_t a, + const blitz::Array<double,2>& gamma_a) const; + /** + * @brief Computes the log likelihood constant term for a given \f$a\f$ + * (number of samples) + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + * @warning: gamma_a will be computed and added if it does + * not already exists + */ + double computeLogLikeConstTerm(const size_t a); + /** + * @brief Tells if the log likelihood constant term for a given \f$a\f$ + * (number of samples) exists + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + */ + bool hasLogLikeConstTerm(const size_t a) const + { return (m_cache_loglike_constterm.find(a) != m_cache_loglike_constterm.end()); } + /** + * @brief Gets the log likelihood constant term for a given \f$a\f$ + * (number of samples) + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + * @warning an exception is thrown if the value does not exists + */ + double getLogLikeConstTerm(const size_t a) const; + /** + * @brief Gets the log likelihood constant term for a given \f$a\f$ + * (number of samples) + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + * @warning The value is computed if it does not already exists + */ + double getAddLogLikeConstTerm(const size_t a); + + /** + * @brief Computes the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number + * of samples) and put the result in the provided array. + * \f$\gamma_a = (Id + a F^T \beta F)^{-1}\f$ + */ + void computeGamma(const size_t a, blitz::Array<double,2> res) const; + /** + * @brief Tells if the \f$\gamma_a\f$ matrix for a given a (number of + * samples) exists. + * \f$\gamma_a = (Id + a F^T \beta F)^{-1}\f$ + */ + bool hasGamma(const size_t a) const + { return (m_cache_gamma.find(a) != m_cache_gamma.end()); } + + /** + * @brief Clears the maps (\f$\gamma_a\f$ and loglike_constterm_a). + */ + void clearMaps(); + + /** + * @brief Gets the log-likelihood of an observation, given the current model + * and the latent variables (point estimate).\n + * This will basically compute \f$p(x_{ij} | h_{i}, w_{ij}, \Theta)\f$\n + * , given by \n + * \f$\mathcal{N}(x_{ij}|[\mu + F h_{i} + G w_{ij} + \epsilon_{ij}, \Sigma])\f$\n + * , which is in logarithm, \n + * \f$-\frac{D}{2} log(2\pi) -\frac{1}{2} log(det(\Sigma)) -\frac{1}{2} {(x_{ij}-(\mu+F h_{i}+G w_{ij}))^{T}\Sigma^{-1}(x_{ij}-(\mu+F h_{i}+G w_{ij}))}\f$. + */ + double computeLogLikelihoodPointEstimate(const blitz::Array<double,1>& xij, + const blitz::Array<double,1>& hi, const blitz::Array<double,1>& wij) const; + + // Friend method declaration + friend std::ostream& operator<<(std::ostream& os, const PLDABase& m); + + + private: + // Attributes + size_t m_dim_d; ///< Dimensionality of the input feature vector + size_t m_dim_f; ///< Size/rank of the \f$F\f$ subspace + size_t m_dim_g; ///< Size/rank of the \f$G\f$ subspace + blitz::Array<double,2> m_F; ///< \f$F\f$ subspace of the PLDA model + blitz::Array<double,2> m_G; ///< \f$G\f$ subspace of the PLDA model + /** + * @brief \f$\Sigma\f$ diagonal (by assumption) covariance matrix of the + * PLDA model + */ + blitz::Array<double,1> m_sigma; + blitz::Array<double,1> m_mu; ///< \f$\mu\f$ mean vector of the PLDA model + /** + * @brief The variance flooring thresholds, i.e. the minimum allowed + * value of variance m_sigma in each dimension. + * The variance will be set to this value if an attempt is made + * to set it to a smaller value. + */ + double m_variance_threshold; + + // Internal values very useful used to optimize the code + blitz::Array<double,1> m_cache_isigma; ///< \f$\Sigma^{-1}\f$ + blitz::Array<double,2> m_cache_alpha; ///< \f$\alpha = (Id + G^T \Sigma^{-1} G)^{-1}\f$ + /** + * @brief \f$\beta = (\Sigma+G G^T)^{-1} = (\Sigma^{-1} - \Sigma^{-1} G \alpha G^T \Sigma^{-1})^{-1}\f$ + */ + blitz::Array<double,2> m_cache_beta; + std::map<size_t, blitz::Array<double,2> > m_cache_gamma; ///< \f$\gamma_{a} = (Id + a F^T \beta F)^{-1}\f$ + blitz::Array<double,2> m_cache_Ft_beta; ///< \f$F^{T} \beta \f$ + blitz::Array<double,2> m_cache_Gt_isigma; ///< \f$G^{T} \Sigma^{-1} \f$ + double m_cache_logdet_alpha; ///< \f$\log(\det(\alpha))\f$ + double m_cache_logdet_sigma; ///< \f$\log(\det(\Sigma))\f$ + /** + * @brief \f$l_{a} = \frac{a}{2} ( -D log(2*\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + */ + std::map<size_t, double> m_cache_loglike_constterm; + + // working arrays + mutable blitz::Array<double,1> m_tmp_d_1; ///< Cache vector of size dim_d + mutable blitz::Array<double,1> m_tmp_d_2; ///< Cache vector of size dim_d + mutable blitz::Array<double,2> m_tmp_d_ng_1; ///< Cache matrix of size dim_d x dim_g + mutable blitz::Array<double,2> m_tmp_nf_nf_1; ///< Cache matrix of size dim_f x dim_f + mutable blitz::Array<double,2> m_tmp_ng_ng_1; ///< Cache matrix of size dim_g x dim_g + + // private methods + void resizeNoInit(const size_t dim_d, const size_t dim_f, const size_t dim_g); + void resizeTmp(); + void initMuFGSigma(); + void precomputeISigma(); + void precomputeAlpha(); + void precomputeBeta(); + void precomputeGamma(const size_t a); + void precomputeFtBeta(); + void precomputeGtISigma(); + void precomputeLogDetAlpha(); + void precomputeLogDetSigma(); + void precomputeLogLikeConstTerm(const size_t a); +}; + + +/** + * @brief This class is a container for an enrolled identity/class. It + * contains information extracted from the enrollment samples. It should + * be used in combination with a PLDABase instance.\n + * References:\n + * 1. 'A Scalable Formulation of Probabilistic Linear Discriminant Analysis: + * Applied to Face Recognition', Laurent El Shafey, Chris McCool, + * Roy Wallace, Sebastien Marcel, TPAMI'2013 + * 2. 'Probabilistic Linear Discriminant Analysis for Inference About + * Identity', Prince and Elder, ICCV'2007\n + * 3. 'Probabilistic Models for Inference about Identity', Li, Fu, Mohammed, + * Elder and Prince, TPAMI'2012 + */ +class PLDAMachine: public Machine<blitz::Array<double,1>, double> +{ + public: + /** + * @brief Default constructor.\n + * Builds an otherwise invalid (No attached PLDABase) PLDAMachine. + */ + PLDAMachine(); + /** + * @brief Constructor, builds a new PLDAMachine, setting a + * PLDABase. + */ + PLDAMachine(const boost::shared_ptr<bob::machine::PLDABase> pldabase); + /** + * @brief Copies another PLDAMachine.\n Both PLDAMachine's will point + * to the same PLDABase. + */ + PLDAMachine(const PLDAMachine& other); + /** + * @brief Starts a new PLDAMachine from an existing configuration object, + * and a PLDABase. + */ + PLDAMachine(bob::io::base::HDF5File& config, + const boost::shared_ptr<bob::machine::PLDABase> pldabase); + + /** + * @brief Just to virtualise the destructor + */ + virtual ~PLDAMachine(); + + /** + * @brief Assigns from a different machine + */ + PLDAMachine& operator=(const PLDAMachine &other); + + /** + * @brief Equal to.\n The two PLDAMachine's should have the same + * PLDABase. Precomputed members such as \f$\gamma_a\f$'s + * are compared! + */ + bool operator==(const PLDAMachine& b) const; + /** + * @brief Not equal to.\n Defined as the negation of operator== + */ + bool operator!=(const PLDAMachine& b) const; + /** + * @brief Equal to.\n The two PLDAMachine's should have the same + * PLDABase. Precomputed members such as \f$\gamma_a\f$'s + * are compared! + */ + bool is_similar_to(const PLDAMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Loads data from an existing configuration object. Resets the + * current state. + */ + void load(bob::io::base::HDF5File& config); + /** + * @brief Saves an existing machine to a configuration object. + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Gets the attached PLDABase + */ + const boost::shared_ptr<PLDABase> getPLDABase() const + { return m_plda_base; } + /** + * @brief Sets the attached PLDABase + */ + void setPLDABase(const boost::shared_ptr<bob::machine::PLDABase> plda_base); + + /** + * @brief Gets the feature dimensionality + */ + size_t getDimD() const + { if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + return m_plda_base->getDimD(); } + /** + * @brief Gets the size/rank the \f$F\f$ subspace/matrix of the PLDA model + */ + size_t getDimF() const + { if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + return m_plda_base->getDimF(); } + /** + * @brief Gets the size/rank the \f$G\f$ subspace/matrix of the PLDA model + */ + size_t getDimG() const + { if (!m_plda_base) throw std::runtime_error("No PLDABase set to this machine"); + return m_plda_base->getDimG(); } + + /** + * @brief Gets the number of enrolled samples + */ + uint64_t getNSamples() const + { return m_n_samples; } + /** + * @brief Sets the number of enrolled samples + */ + void setNSamples(const uint64_t n_samples) + { m_n_samples = n_samples; } + /** + * @brief Gets the \f$A = -0.5 \sum_{i} x_{i}^T \beta x_{i}\f$ value + */ + double getWSumXitBetaXi() const + { return m_nh_sum_xit_beta_xi; } + /** + * @brief Sets the \f$A = -0.5 \sum_{i} x_{i}^T \beta x_{i}\f$ value + */ + void setWSumXitBetaXi(const double val) + { m_nh_sum_xit_beta_xi = val; } + /** + * @brief Gets the current \f$\sum_{i} F^T \beta x_{i}\f$ value + */ + const blitz::Array<double,1>& getWeightedSum() const + { return m_weighted_sum; } + /** + * @brief Sets the \f$\sum_{i} F^T \beta x_{i}\f$ value + */ + void setWeightedSum(const blitz::Array<double,1>& weighted_sum); + /** + * @brief Returns the current \f$\sum_{i} F^T \beta x_{i}\f$ value + * in order to be updated. + * @warning Use with care. Only trainers should use this function for + * efficiency reasons. + */ + blitz::Array<double,1>& updateWeightedSum() + { return m_weighted_sum; } + /** + * @brief Gets the log likelihood of the enrollment samples + */ + double getLogLikelihood() const + { return m_loglikelihood; } + /** + * @brief Sets the log likelihood of the enrollment samples + */ + void setLogLikelihood(const double val) + { m_loglikelihood = val; } + + /** + * @brief Tells if the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number + * of samples) exists in this machine (does not check the base machine) + * \f$\gamma_a = (Id + a F^T \beta F)^{-1} = \mathcal{F}_{a}\f$ + */ + bool hasGamma(const size_t a) const + { return (m_cache_gamma.find(a) != m_cache_gamma.end()); } + /** + * @brief Gets the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number of + * samples) \f$\gamma_a = (Id + a F^T \beta F)^{-1} = \mathcal{F}_{a}\f$ + * Tries to find it from the base machine and then from this machine + * @warning an exception is thrown if gamma does not exists + */ + const blitz::Array<double,2>& getGamma(const size_t a) const; + /** + * @brief Gets the \f$\gamma_a\f$ matrix for a given \f$a\f$ (number of + * samples) \f$\gamma_a = (Id + a F^T \beta F)^{-1} = \mathcal{F}_{a}\f$ + * Tries to find it from the base machine and then from this machine + * @warning The matrix is computed if it does not already exists, + * and stored in this machine + */ + const blitz::Array<double,2>& getAddGamma(const size_t a); + + /** + * @brief Tells if the log likelihood constant term for a given \f$a\f$ + * (number of samples) exists in this machine + * (does not check the base machine) + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + */ + bool hasLogLikeConstTerm(const size_t a) const + { return (m_cache_loglike_constterm.find(a) != m_cache_loglike_constterm.end()); } + /** + * @brief Gets the log likelihood constant term for a given \f$a\f$ + * (number of samples) + * Tries to find it from the base machine and then from this machine + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + * @warning an exception is thrown if the value does not exists + */ + double getLogLikeConstTerm(const size_t a) const; + /** + * @brief Gets the log likelihood constant term for a given \f$a\f$ + * (number of samples) + * Tries to find it from the base machine and then from this machine + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + * @warning The value is computed if it does not already exists + */ + double getAddLogLikeConstTerm(const size_t a); + + /** + * @brief Clears the maps (\f$\gamma_a\f$ and loglike_constterm[a]). + */ + void clearMaps(); + + + /** + * @brief Compute the log-likelihood of the given sample and (optionally) + * the enrolled samples + */ + double computeLogLikelihood(const blitz::Array<double,1>& sample, + bool with_enrolled_samples=true) const; + /** + * @brief Compute the log-likelihood of the given samples and (optionally) + * the enrolled samples + */ + double computeLogLikelihood(const blitz::Array<double,2>& samples, + bool with_enrolled_samples=true) const; + + /** + * @brief Computes a log likelihood ratio from a 1D or 2D blitz::Array + */ + void forward(const blitz::Array<double,1>& sample, double& score) const; + void forward_(const blitz::Array<double,1>& sample, double& score) const; + void forward(const blitz::Array<double,2>& samples, double& score) const; + + + private: + /** + * @brief Associated PLDABase containing the model (\f$\mu\f$, + * \f$F\f$, \f$G\f$ and \f$\Sigma\f$) + */ + boost::shared_ptr<PLDABase> m_plda_base; + uint64_t m_n_samples; ///< Number of enrollment samples + /** + * @brief Contains the value:\n + * \f$A = -0.5 (\sum_{i} x_{i}^{T} \Sigma^{-1} x_{i} - x_{i}^T \Sigma^{-1} G \alpha G^{T} \Sigma^{-1} x_{i})\f$\n + * \f$A = -0.5 \sum_{i} x_{i}^T \beta x_{i}\f$\n + * used in the likelihood computation (first \f$x_{i}\f$ dependent term) + */ + double m_nh_sum_xit_beta_xi; + /** + * @brief Contains the value \f$\sum_{i} F^T \beta x_{i}\f$ used in the + * likelihood computation (for the second \f$x_{i}\f$ dependent term) + */ + blitz::Array<double,1> m_weighted_sum; + double m_loglikelihood; ///< Log likelihood of the enrollment samples + /** + * @brief \f$\gamma_a\f$ balues which are not already in the + * PLDABase \f$\gamma_a = (Id + a F^T \beta F)^{-1}\f$ + * (depend on the number of samples \f$a\f$) + */ + std::map<size_t, blitz::Array<double,2> > m_cache_gamma; + /** + * @brief Log likelihood constant terms which depend on the number of + * samples \f$a\f$ + * \f$l_{a} = \frac{a}{2} ( -D log(2\pi) -log|\Sigma| +log|\alpha| +log|\gamma_a|)\f$ + */ + std::map<size_t, double> m_cache_loglike_constterm; + + + // working arrays + mutable blitz::Array<double,1> m_tmp_d_1; ///< Cache vector of size dim_d + mutable blitz::Array<double,1> m_tmp_d_2; ///< Cache vector of size dim_d + mutable blitz::Array<double,1> m_tmp_nf_1; ///< Cache vector of size dim_f + mutable blitz::Array<double,1> m_tmp_nf_2; ///< Cache vector of size dim_f + mutable blitz::Array<double,2> m_tmp_nf_nf_1; ///< Cache vector of size dim_f dim_f + + /** + * @brief Resizes the PLDAMachine + */ + void resize(const size_t dim_d, const size_t dim_f, const size_t dim_g); + /** + * @brief Resize working arrays + */ + void resizeTmp(); +}; + +/** + * @} + */ +}} + +#endif diff --git a/bob/learn/misc/include/bob.learn.misc/PLDATrainer.h b/bob/learn/misc/include/bob.learn.misc/PLDATrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..5a7bc64fbbaf39a4fa2cb8b3812d0237e29e94f9 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/PLDATrainer.h @@ -0,0 +1,305 @@ +/** + * @date Fri Oct 14 18:07:56 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Probabilistic PLDA Discriminant Analysis implemented using + * Expectation Maximization. + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_PLDA_TRAINER_H +#define BOB_TRAINER_PLDA_TRAINER_H + +#include <bob.learn.misc/EMTrainer.h> +#include <bob.learn.misc/PLDAMachine.h> +#include <blitz/array.h> +#include <map> +#include <vector> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief This class can be used to train the \f$F\f$, \f$G\f$ and + * \f$\Sigma\f$ matrices and the mean vector \f$\mu\f$ of a PLDA model.\n + * References:\n + * 1. 'A Scalable Formulation of Probabilistic Linear Discriminant Analysis: + * Applied to Face Recognition', Laurent El Shafey, Chris McCool, + * Roy Wallace, Sebastien Marcel, TPAMI'2013 + * 2. 'Probabilistic Linear Discriminant Analysis for Inference About + * Identity', Prince and Elder, ICCV'2007\n + * 3. 'Probabilistic Models for Inference about Identity', Li, Fu, Mohammed, + * Elder and Prince, TPAMI'2012 + */ +class PLDATrainer: public EMTrainer<bob::machine::PLDABase, + std::vector<blitz::Array<double,2> > > +{ + public: //api + /** + * @brief Default constructor.\n Initializes a new PLDA trainer. The + * training stage will place the resulting components in the + * PLDABase. + */ + PLDATrainer(const size_t max_iterations=100, const bool use_sum_second_order=true); + + /** + * @brief Copy constructor + */ + PLDATrainer(const PLDATrainer& other); + + /** + * @brief (virtual) Destructor + */ + virtual ~PLDATrainer(); + + /** + * @brief Assignment operator + */ + PLDATrainer& operator=(const PLDATrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const PLDATrainer& other) const; + + /** + * @brief Not equal to + */ + bool operator!=(const PLDATrainer& other) const; + + /** + * @brief Similarity operator + */ + virtual bool is_similar_to(const PLDATrainer& b, + const double r_epsilon=1e-5, const double a_epsilon=1e-8) const; + + /** + * @brief Performs some initialization before the E- and M-steps. + */ + virtual void initialize(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + /** + * @brief Performs some actions after the end of the E- and M-steps. + */ + virtual void finalize(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + /** + * @brief Calculates and saves statistics across the dataset, and saves + * these as m_z_{first,second}_order. + * The statistics will be used in the mStep() that follows. + */ + virtual void eStep(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + /** + * @brief Performs a maximization step to update the parameters of the + * PLDABase + */ + virtual void mStep(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + /** + * @brief Computes the average log likelihood using the current estimates + * of the latent variables. + */ + virtual double computeLikelihood(bob::machine::PLDABase& machine); + + /** + * @brief Sets whether the second order statistics are stored during the + * training procedure, or only their sum. + */ + void setUseSumSecondOrder(bool v) { m_use_sum_second_order = v; } + /** + * @brief Tells whether the second order statistics are stored during the + * training procedure, or only their sum. + */ + bool getUseSumSecondOrder() const + { return m_use_sum_second_order; } + + /** + * @brief This enum defines different methods for initializing the \f$F\f$ + * subspace + */ + typedef enum { + RANDOM_F=0, + BETWEEN_SCATTER=1 + } + InitFMethod; + /** + * @brief This enum defines different methods for initializing the \f$G\f$ + * subspace + */ + typedef enum { + RANDOM_G=0, + WITHIN_SCATTER=1 + } + InitGMethod; + /** + * @brief This enum defines different methods for initializing the + * \f$\Sigma\f$ covariance matrix + */ + typedef enum { + RANDOM_SIGMA=0, + VARIANCE_G=1, + CONSTANT=2, + VARIANCE_DATA=3 + } + InitSigmaMethod; + /** + * @brief Sets the method used to initialize \f$F\f$ + */ + void setInitFMethod(const InitFMethod m) { m_initF_method = m; } + /** + * @brief Gets the method used to initialize \f$F\f$ + */ + InitFMethod getInitFMethod() const { return m_initF_method; } + /** + * @brief Sets the ratio value used to initialize \f$F\f$ + */ + void setInitFRatio(double d) { m_initF_ratio = d; } + /** + * @brief Gets the ratio value used to initialize \f$F\f$ + */ + double getInitFRatio() const { return m_initF_ratio; } + /** + * @brief Sets the method used to initialize \f$G\f$ + */ + void setInitGMethod(const InitGMethod m) { m_initG_method = m; } + /** + * @brief Gets the method used to initialize \f$G\f$ + */ + InitGMethod getInitGMethod() const { return m_initG_method; } + /** + * @brief Sets the ratio value used to initialize \f$G\f$ + */ + void setInitGRatio(double d) { m_initG_ratio = d; } + /** + * @brief Gets the ratio value used to initialize \f$G\f$ + */ + double getInitGRatio() const { return m_initG_ratio; } + /** + * @brief Sets the method used to initialize \f$\Sigma\f$ + */ + void setInitSigmaMethod(const InitSigmaMethod m) + { m_initSigma_method = m; } + /** + * @brief Gets the method used to initialize \f$\Sigma\f$ + */ + InitSigmaMethod getInitSigmaMethod() const + { return m_initSigma_method; } + /** + * @brief Sets the ratio value used to initialize \f$\Sigma\f$ + */ + void setInitSigmaRatio(double d) { m_initSigma_ratio = d; } + /** + * @brief Gets the ratio value used to initialize \f$\Sigma\f$ + */ + double getInitSigmaRatio() const { return m_initSigma_ratio; } + + /** + * @brief Gets the z first order statistics (mostly for test purposes) + */ + const std::vector<blitz::Array<double,2> >& getZFirstOrder() const + { return m_cache_z_first_order;} + /** + * @brief Gets the z second order statistics (mostly for test purposes) + */ + const blitz::Array<double,2>& getZSecondOrderSum() const + { return m_cache_sum_z_second_order;} + /** + * @brief Gets the z second order statistics (mostly for test purposes) + */ + const std::vector<blitz::Array<double,3> >& getZSecondOrder() const + { if(m_use_sum_second_order) + throw std::runtime_error("You should disable the use_sum_second_order flag to use this feature"); + return m_cache_z_second_order; + } + + /** + * @brief Main procedure for enrolling a PLDAMachine + */ + void enrol(bob::machine::PLDAMachine& plda_machine, + const blitz::Array<double,2>& ar) const; + + private: + //representation + size_t m_dim_d; ///< Dimensionality of the input features + size_t m_dim_f; ///< Size/rank of the \f$F\f$ subspace + size_t m_dim_g; ///< Size/rank of the \f$G\f$ subspace + bool m_use_sum_second_order; ///< If set, only the sum of the second order statistics is stored/allocated + InitFMethod m_initF_method; ///< Initialization method for \f$F\f$ + double m_initF_ratio; ///< Ratio/factor used for the initialization of \f$F\f$ + InitGMethod m_initG_method; ///< Initialization method for \f$G\f$ + double m_initG_ratio; ///< Ratio/factor used for the initialization of \f$G\f$ + InitSigmaMethod m_initSigma_method; ///< Initialization method for \f$\Sigma\f$ + double m_initSigma_ratio; ///< Ratio/factor used for the initialization of \f$\Sigma\f$ + + // Statistics and covariance computed during the training process + blitz::Array<double,2> m_cache_S; ///< Covariance of the training data + std::vector<blitz::Array<double,2> > m_cache_z_first_order; ///< Current mean of the z_{n} latent variable (1 for each sample) + blitz::Array<double,2> m_cache_sum_z_second_order; ///< Current sum of the covariance of the z_{n} latent variable + std::vector<blitz::Array<double,3> > m_cache_z_second_order; ///< Current covariance of the z_{n} latent variable + // Precomputed + /** + * @brief Number of training samples for each individual in the training set + */ + std::vector<size_t> m_cache_n_samples_per_id; + /** + * @brief Tells if there is an identity with a 'key'/particular number of + * training samples, and if corresponding matrices are up to date. + */ + std::map<size_t,bool> m_cache_n_samples_in_training; + blitz::Array<double,2> m_cache_B; ///< \f$B = [F, G]\f$ (size nfeatures x (m_dim_f+m_dim_g) ) + blitz::Array<double,2> m_cache_Ft_isigma_G; ///< \f$F^T \Sigma^-1 G\f$ + blitz::Array<double,2> m_cache_eta; ///< \f$F^T \Sigma^-1 G \alpha\f$ + // Blocks (with \f$\gamma_{a}\f$) of \f$(Id + A^T \Sigma'^-1 A)^-1\f$ (efficient inversion) + std::map<size_t,blitz::Array<double,2> > m_cache_zeta; ///< \f$\zeta_{a} = \alpha + \eta^T \gamma_{a} \eta\f$ + std::map<size_t,blitz::Array<double,2> > m_cache_iota; ///< \f$\iota_{a} = -\gamma_{a} \eta\f$ + + // Working arrays + mutable blitz::Array<double,1> m_tmp_nf_1; ///< vector of dimension dim_f + mutable blitz::Array<double,1> m_tmp_nf_2; ///< vector of dimension dim_f + mutable blitz::Array<double,1> m_tmp_ng_1; ///< vector of dimension dim_f + mutable blitz::Array<double,1> m_tmp_D_1; ///< vector of dimension dim_d + mutable blitz::Array<double,1> m_tmp_D_2; ///< vector of dimension dim_d + mutable blitz::Array<double,2> m_tmp_nfng_nfng; ///< matrix of dimension (dim_f+dim_g)x(dim_f+dim_g) + mutable blitz::Array<double,2> m_tmp_D_nfng_1; ///< matrix of dimension (dim_d)x(dim_f+dim_g) + mutable blitz::Array<double,2> m_tmp_D_nfng_2; ///< matrix of dimension (dim_d)x(dim_f+dim_g) + + // internal methods + void computeMeanVariance(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + void initMembers(const std::vector<blitz::Array<double,2> >& v_ar); + void initFGSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + void initF(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + void initG(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + void initSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + void checkTrainingData(const std::vector<blitz::Array<double,2> >& v_ar); + void precomputeFromFGSigma(bob::machine::PLDABase& machine); + void precomputeLogLike(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + void updateFG(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + void updateSigma(bob::machine::PLDABase& machine, + const std::vector<blitz::Array<double,2> >& v_ar); + + void resizeTmp(); +}; + +/** + * @} + */ +}} + +#endif /* BOB_TRAINER_PLDA_TRAINER_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/Trainer.h b/bob/learn/misc/include/bob.learn.misc/Trainer.h new file mode 100644 index 0000000000000000000000000000000000000000..8c70f24ab572952f0730766a4d9614851629e081 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/Trainer.h @@ -0,0 +1,47 @@ +/** + * @date Tue Jan 18 17:07:26 2011 +0100 + * @author André Anjos <andre.anjos@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ +#ifndef BOB_TRAINER_TRAINER_H +#define BOB_TRAINER_TRAINER_H + +/** + * @addtogroup TRAINER trainer + * @brief Trainer module API + */ +namespace bob { +/** + * @ingroup TRAINER + */ +namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief Root class for all trainers + */ +template<class T_machine, class T_sampler> +class Trainer +{ +public: + virtual ~Trainer() {}; + + /** + * @brief Train a \c machine using a sampler + * + * @param machine machine to train + * @param sampler sampler that provides training data + */ + virtual void train(T_machine& machine, const T_sampler& sampler) = 0; +}; + +/** + * @} + */ +}} + +#endif // BOB_TRAINER_TRAINER_H diff --git a/bob/learn/misc/include/bob.learn.misc/WienerMachine.h b/bob/learn/misc/include/bob.learn.misc/WienerMachine.h new file mode 100644 index 0000000000000000000000000000000000000000..58b37f2815042931c9271f0f634b4141f4fbd8ed --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/WienerMachine.h @@ -0,0 +1,219 @@ +/** + * @date Fri Sep 30 16:56:06 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_WIENERMACHINE_H +#define BOB_MACHINE_WIENERMACHINE_H + +#include <bob.learn.misc/Machine.h> +#include <blitz/array.h> +#include <complex> +#include <bob.io.base/HDF5File.h> +#include <bob.sp/FFT2D.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * @brief A Wiener machine, which can be used to denoise a signal, + * by comparing with a statistical model of the noiseless signal.\n + * + * Reference:\n + * Computer Vision: Algorithms and Applications, Richard Szeliski + * (Part 3.4.3) + */ +class WienerMachine: Machine<blitz::Array<double,2>, blitz::Array<double,2> > +{ + public: //api + /** + * @brief Default constructor. Builds an otherwise invalid 0 x 0 Wiener + * machine. This is equivalent to construct a WienerMachine with two + * size_t parameters set to 0, as in WienerMachine(0, 0, 0). + */ + WienerMachine(); + + /** + * @brief Constructor, builds a new Wiener machine. Wiener filter is + * initialized with the given size, Ps being sets to the variance + * threshold. + */ + WienerMachine(const size_t height, const size_t width, const double Pn, + const double variance_threshold=1e-8); + + /** + * @brief Builds a new machine with the given variance estimate Ps and + * noise level Pn. + */ + WienerMachine(const blitz::Array<double,2>& Ps, const double Pn, + const double variance_threshold=1e-8); + + /** + * @brief Builds a new machine with the given Wiener filter. + */ + WienerMachine(const blitz::Array<double,2>& W); + + /** + * @brief Copy constructor + */ + WienerMachine(const WienerMachine& other); + + /** + * @brief Starts a new WienerMachine from an existing Configuration + * object. + */ + WienerMachine(bob::io::base::HDF5File& config); + + /** + * @brief Destructor + */ + virtual ~WienerMachine(); + + /** + * @brief Assignment operator + */ + WienerMachine& operator=(const WienerMachine& other); + + /** + * @brief Equal to + */ + bool operator==(const WienerMachine& other) const; + + /** + * @brief Not equal to + */ + bool operator!=(const WienerMachine& other) const; + + /** + * @brief Is similar to + */ + bool is_similar_to(const WienerMachine& b, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Loads data from an existing configuration object. Resets the + * current state. + */ + void load(bob::io::base::HDF5File& config); + + /** + * @brief Saves an existing machine to a Configuration object. + */ + void save(bob::io::base::HDF5File& config) const; + + /** + * @brief Forwards data through the machine. + * + * The input and output are NOT checked for compatibility each time. It + * is your responsibility to do it. + */ + void forward_(const blitz::Array<double,2>& input, + blitz::Array<double,2>& output) const; + + /** + * @brief Forwards data through the machine. + * + * The input and output are checked for compatibility each time the + * forward method is applied. + */ + void forward(const blitz::Array<double,2>& input, + blitz::Array<double,2>& output) const; + + /** + * @brief Resizes the machine. + */ + void resize(const size_t height, const size_t width); + + /** + * @brief Returns the height of the filter/input + */ + size_t getHeight() const { return m_W.extent(0); } + + /** + * @brief Returns the width of the filter/input + */ + size_t getWidth() const { return m_W.extent(1); } + + /** + * @brief Returns the current variance Ps estimated at each frequency + */ + const blitz::Array<double, 2>& getPs() const + { return m_Ps; } + + /** + * @brief Returns the current variance threshold applied to Ps + */ + double getVarianceThreshold() const + { return m_variance_threshold; } + + /** + * @brief Returns the current noise level Pn + */ + double getPn() const + { return m_Pn; } + + /** + * @brief Returns the current Wiener filter (in the frequency domain). + */ + const blitz::Array<double, 2>& getW() const + { return m_W; } + + /** + * @brief Sets the height of the filter/input + */ + void setHeight(const size_t height) + { resize(height, m_W.extent(1)); } + + /** + * @brief Returns the width of the filter/input + */ + void setWidth(const size_t width) + { resize(m_W.extent(0), width); } + + /** + * @brief Sets the current variance Ps estimated at each frequency. + * This will also update the Wiener filter, using thresholded values. + */ + void setPs(const blitz::Array<double,2>& Ps); + + /** + * @brief Sets the current variance threshold to be used. + * This will also update the Wiener filter + */ + void setVarianceThreshold(const double variance_threshold); + + /** + * @brief Sets the current noise level Pn to be considered. + * This will update the Wiener filter + */ + void setPn(const double Pn) + { m_Pn = Pn; computeW(); } + + + private: //representation + void computeW(); /// Compute the Wiener filter using Pn, Ps, etc. + void applyVarianceThreshold(); /// Apply variance flooring threshold + + blitz::Array<double, 2> m_Ps; ///< variance at each frequency estimated empirically + double m_variance_threshold; ///< Threshold on Ps values when computing the Wiener filter + /// (to avoid division by zero) + double m_Pn; ///< variance of the noise + blitz::Array<double, 2> m_W; ///< Wiener filter in the frequency domain (W=1/(1+Pn/Ps)) + bob::sp::FFT2D m_fft; + bob::sp::IFFT2D m_ifft; + + mutable blitz::Array<std::complex<double>, 2> m_buffer1; ///< a buffer for speed + mutable blitz::Array<std::complex<double>, 2> m_buffer2; ///< a buffer for speed +}; + +/** + * @} + */ +}} + +#endif /* BOB_MACHINE_WIENERMACHINE_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/WienerTrainer.h b/bob/learn/misc/include/bob.learn.misc/WienerTrainer.h new file mode 100644 index 0000000000000000000000000000000000000000..2b9ebabdbbc62e3bc0764041ae501344142d3791 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/WienerTrainer.h @@ -0,0 +1,83 @@ +/** + * @date Fri Sep 30 16:58:42 2011 +0200 + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * @brief Trainer for the Wiener Machine + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_TRAINER_WIENER_TRAINER_H +#define BOB_TRAINER_WIENER_TRAINER_H + +#include <bob.learn.misc/Trainer.h> +#include <bob.learn.misc/WienerMachine.h> +#include <blitz/array.h> + +namespace bob { namespace trainer { +/** + * @ingroup TRAINER + * @{ + */ + +/** + * @brief Sets a Wiener machine to perform a Wiener filtering, using the + * Fourier statistics of a given dataset.\n + * + * Reference:\n + * "Computer Vision: Algorithms and Applications", Richard Szeliski + * (Part 3.4.3) + */ +class WienerTrainer: Trainer<bob::machine::WienerMachine, blitz::Array<double,3> > +{ + public: //api + /** + * @brief Default constructor. + * Initializes a new Wiener trainer. + */ + WienerTrainer(); + + /** + * @brief Copy constructor + */ + WienerTrainer(const WienerTrainer& other); + + /** + * @brief Destructor + */ + virtual ~WienerTrainer(); + + /** + * @brief Assignment operator + */ + WienerTrainer& operator=(const WienerTrainer& other); + + /** + * @brief Equal to + */ + bool operator==(const WienerTrainer& other) const; + /** + * @brief Not equal to + */ + bool operator!=(const WienerTrainer& other) const; + /** + * @brief Similar to + */ + bool is_similar_to(const WienerTrainer& other, const double r_epsilon=1e-5, + const double a_epsilon=1e-8) const; + + /** + * @brief Trains the WienerMachine to perform the filtering. + */ + virtual void train(bob::machine::WienerMachine& machine, + const blitz::Array<double,3>& data); + + private: //representation +}; + +/** + * @} + */ +}} + +#endif /* BOB_TRAINER_WIENER_TRAINER_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/ZTNorm.h b/bob/learn/misc/include/bob.learn.misc/ZTNorm.h new file mode 100644 index 0000000000000000000000000000000000000000..9ce747eb233b0dc569d97d9bfbee3f382773e642 --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/ZTNorm.h @@ -0,0 +1,96 @@ +/** + * @date Tue Jul 19 15:33:20 2011 +0200 + * @author Francois Moulin <Francois.Moulin@idiap.ch> + * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch> + * + * Copyright (C) Idiap Research Institute, Martigny, Switzerland + */ + +#ifndef BOB_MACHINE_ZTNORM_H +#define BOB_MACHINE_ZTNORM_H + +#include <blitz/array.h> + +namespace bob { namespace machine { +/** + * @ingroup MACHINE + * @{ + */ + +/** + * Normalise raw scores with ZT-Norm + * + * @exception std::runtime_error matrix sizes are not consistent + * + * @param rawscores_probes_vs_models + * @param rawscores_zprobes_vs_models + * @param rawscores_probes_vs_tmodels + * @param rawscores_zprobes_vs_tmodels + * @param mask_zprobes_vs_tmodels_istruetrial + * @param[out] normalizedscores normalized scores + * @warning The destination score array should have the correct size + * (Same size as rawscores_probes_vs_models) + */ +void ztNorm(const blitz::Array<double, 2>& rawscores_probes_vs_models, + const blitz::Array<double, 2>& rawscores_zprobes_vs_models, + const blitz::Array<double, 2>& rawscores_probes_vs_tmodels, + const blitz::Array<double, 2>& rawscores_zprobes_vs_tmodels, + const blitz::Array<bool, 2>& mask_zprobes_vs_tmodels_istruetrial, + blitz::Array<double, 2>& normalizedscores); + +/** + * Normalise raw scores with ZT-Norm. + * Assume that znorm and tnorm have no common subject id. + * + * @exception std::runtime_error matrix sizes are not consistent + * + * @param rawscores_probes_vs_models + * @param rawscores_zprobes_vs_models + * @param rawscores_probes_vs_tmodels + * @param rawscores_zprobes_vs_tmodels + * @param[out] normalizedscores normalized scores + * @warning The destination score array should have the correct size + * (Same size as rawscores_probes_vs_models) + */ +void ztNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_zprobes_vs_models, + const blitz::Array<double,2>& rawscores_probes_vs_tmodels, + const blitz::Array<double,2>& rawscores_zprobes_vs_tmodels, + blitz::Array<double,2>& normalizedscores); + +/** + * Normalise raw scores with T-Norm. + * + * @exception std::runtime_error matrix sizes are not consistent + * + * @param rawscores_probes_vs_models + * @param rawscores_probes_vs_tmodels + * @param[out] normalizedscores normalized scores + * @warning The destination score array should have the correct size + * (Same size as rawscores_probes_vs_models) + */ +void tNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_probes_vs_tmodels, + blitz::Array<double,2>& normalizedscores); + +/** + * Normalise raw scores with Z-Norm. + * + * @exception std::runtime_error matrix sizes are not consistent + * + * @param rawscores_probes_vs_models + * @param rawscores_zprobes_vs_models + * @param[out] normalizedscores normalized scores + * @warning The destination score array should have the correct size + * (Same size as rawscores_probes_vs_models) + */ +void zNorm(const blitz::Array<double,2>& rawscores_probes_vs_models, + const blitz::Array<double,2>& rawscores_zprobes_vs_models, + blitz::Array<double,2>& normalizedscores); + +/** + * @} + */ +}} + +#endif /* BOB_MACHINE_ZTNORM_H */ diff --git a/bob/learn/misc/include/bob.learn.misc/config.h b/bob/learn/misc/include/bob.learn.misc/config.h new file mode 100644 index 0000000000000000000000000000000000000000..c35fa5c31e9d60008edad46a7e56324d3d77919f --- /dev/null +++ b/bob/learn/misc/include/bob.learn.misc/config.h @@ -0,0 +1,14 @@ +/** + * @author Manuel Guenther <manuel.guenther@idiap.ch> + * @date Thu Aug 21 20:49:42 CEST 2014 + * + * @brief General directives for all modules in bob.learn.misc + */ + +#ifndef BOB_LEARN_MISC_CONFIG_H +#define BOB_LEARN_MISC_CONFIG_H + +/* Macros that define versions and important names */ +#define BOB_LEARN_MISC_API_VERSION 0x0200 + +#endif /* BOB_LEARN_MISC_CONFIG_H */ diff --git a/bob/learn/misc/old/bic.cc b/bob/learn/misc/old/bic.cc index b175842abb4b85826f22f167d2b9f54205f62e76..7174d24ec17c42f00e136411bc288807049f313b 100644 --- a/bob/learn/misc/old/bic.cc +++ b/bob/learn/misc/old/bic.cc @@ -5,8 +5,12 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/BICMachine.h> +#include <bob.learn.misc/BICMachine.h> static double bic_call_(const bob::machine::BICMachine& machine, bob::python::const_ndarray input){ double o; @@ -20,6 +24,19 @@ static double bic_call(const bob::machine::BICMachine& machine, bob::python::con return o; } +static void bic_load(bob::machine::BICMachine& machine, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + machine.load(*hdf5->f); +} + +static void bic_save(const bob::machine::BICMachine& machine, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + machine.save(*hdf5->f); +} + + void bind_machine_bic(){ // bind BICMachine @@ -63,14 +80,14 @@ void bind_machine_bic(){ .def( "load", - &bob::machine::BICMachine::load, + &bic_load, (boost::python::arg("self"), boost::python::arg("file")), "Loads the configuration parameters from an hdf5 file." ) .def( "save", - &bob::machine::BICMachine::save, + &bic_save, (boost::python::arg("self"), boost::python::arg("file")), "Saves the configuration parameters to an hdf5 file." ) diff --git a/bob/learn/misc/old/bic_trainer.cc b/bob/learn/misc/old/bic_trainer.cc index 4e4ec19b8a66fbe6057b9252e657d01376b5a4a6..bdf3ab7e6cd17b6bc39f2cd0bc69268110fb3157 100644 --- a/bob/learn/misc/old/bic_trainer.cc +++ b/bob/learn/misc/old/bic_trainer.cc @@ -7,7 +7,7 @@ */ #include "ndarray.h" -#include <bob/trainer/BICTrainer.h> +#include <bob.learn.misc/BICTrainer.h> void py_train(const bob::trainer::BICTrainer& t, bob::machine::BICMachine& m, bob::python::const_ndarray intra_differences, diff --git a/bob/learn/misc/old/blitz_numpy.cc b/bob/learn/misc/old/blitz_numpy.cc index 1a391d68b0d874d05022ea642d20cb5390a92c66..635495294a76380bc78f1e9e6c1b3fcba00c91de 100644 --- a/bob/learn/misc/old/blitz_numpy.cc +++ b/bob/learn/misc/old/blitz_numpy.cc @@ -78,7 +78,7 @@ template <typename T, int N> struct bz_from_npy { boost::python::handle<> hdl(boost::python::borrowed(boost::python::allow_null(obj_ptr))); boost::python::object obj(hdl); - bob::core::array::typeinfo tinfo(bob::core::array::getElementType<T>(), N); + bob::io::base::array::typeinfo tinfo(bob::io::base::array::getElementType<T>(), N); bob::python::convert_t result = bob::python::convertible_to(obj, tinfo, false, true); @@ -90,7 +90,7 @@ template <typename T, int N> struct bz_from_npy { PyArrayObject* arr = reinterpret_cast<PyArrayObject*>(obj_ptr); if (result == bob::python::WITHARRAYCOPY && bob::python::ctype_to_num<T>() == PyArray_DESCR(arr)->type_num) { - PYTHON_ERROR(RuntimeError, "The bindings you are trying to use to this C++ method require a numpy.ndarray -> blitz::Array<%s,%d> conversion, but the array you passed, despite the correct type, is not C-style contiguous and/or properly aligned, so I cannot automatically wrap it. You can check this by yourself by printing the flags on such a variable with the command 'print(<varname>.flags)'. The only way to circumvent this problem, from python, is to create a copy the variable by issuing '<varname>.copy()' before calling the bound method. Otherwise, if you wish the copy to be executed automatically, you have to re-bind the method to use our custom 'const_ndarray' type.", bob::core::array::stringize<T>(), N); + PYTHON_ERROR(RuntimeError, "The bindings you are trying to use to this C++ method require a numpy.ndarray -> blitz::Array<%s,%d> conversion, but the array you passed, despite the correct type, is not C-style contiguous and/or properly aligned, so I cannot automatically wrap it. You can check this by yourself by printing the flags on such a variable with the command 'print(<varname>.flags)'. The only way to circumvent this problem, from python, is to create a copy the variable by issuing '<varname>.copy()' before calling the bound method. Otherwise, if you wish the copy to be executed automatically, you have to re-bind the method to use our custom 'const_ndarray' type.", bob::io::base::array::stringize<T>(), N); } return 0; diff --git a/bob/learn/misc/old/empca_trainer.cc b/bob/learn/misc/old/empca_trainer.cc index dd4d70fedd45d47fda5d92c55281d00111e24332..61f210395a544c669b9010727d4366230cd3ae78 100644 --- a/bob/learn/misc/old/empca_trainer.cc +++ b/bob/learn/misc/old/empca_trainer.cc @@ -6,38 +6,39 @@ */ #include "ndarray.h" -#include <bob/trainer/EMPCATrainer.h> +#include <bob.learn.linear/pca.h> +#include <bob.learn.misc/EMPCATrainer.h> using namespace boost::python; -typedef bob::trainer::EMTrainer<bob::machine::LinearMachine, blitz::Array<double,2> > EMTrainerLinearBase; +typedef bob::trainer::EMTrainer<bob::learn::linear::Machine, blitz::Array<double,2> > EMTrainerLinearBase; static void py_train(EMTrainerLinearBase& trainer, - bob::machine::LinearMachine& machine, bob::python::const_ndarray data) + bob::learn::linear::Machine& machine, bob::python::const_ndarray data) { trainer.train(machine, data.bz<double,2>()); } static void py_initialize(EMTrainerLinearBase& trainer, - bob::machine::LinearMachine& machine, bob::python::const_ndarray data) + bob::learn::linear::Machine& machine, bob::python::const_ndarray data) { trainer.initialize(machine, data.bz<double,2>()); } static void py_finalize(EMTrainerLinearBase& trainer, - bob::machine::LinearMachine& machine, bob::python::const_ndarray data) + bob::learn::linear::Machine& machine, bob::python::const_ndarray data) { trainer.finalize(machine, data.bz<double,2>()); } static void py_eStep(EMTrainerLinearBase& trainer, - bob::machine::LinearMachine& machine, bob::python::const_ndarray data) + bob::learn::linear::Machine& machine, bob::python::const_ndarray data) { trainer.eStep(machine, data.bz<double,2>()); } static void py_mStep(EMTrainerLinearBase& trainer, - bob::machine::LinearMachine& machine, bob::python::const_ndarray data) + bob::learn::linear::Machine& machine, bob::python::const_ndarray data) { trainer.mStep(machine, data.bz<double,2>()); } diff --git a/bob/learn/misc/old/gaussian.cc b/bob/learn/misc/old/gaussian.cc index de31acd66ad1248c0714f63163d839c0ff88bd64..3543f726c192e47313d9612b00cc99db7b2b522e 100644 --- a/bob/learn/misc/old/gaussian.cc +++ b/bob/learn/misc/old/gaussian.cc @@ -5,8 +5,12 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/Gaussian.h> +#include <bob.learn.misc/Gaussian.h> using namespace boost::python; @@ -56,13 +60,34 @@ static double py_logLikelihood_(const bob::machine::Gaussian& machine, return output; } + +static boost::shared_ptr<bob::machine::Gaussian> _init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::Gaussian>(new bob::machine::Gaussian(*hdf5->f)); +} + +static void _load(bob::machine::Gaussian& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void _save(const bob::machine::Gaussian& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + void bind_machine_gaussian() { class_<bob::machine::Gaussian, boost::shared_ptr<bob::machine::Gaussian>, bases<bob::machine::Machine<blitz::Array<double,1>, double> > >("Gaussian", - "This class implements a multivariate diagonal Gaussian distribution.", init<>(arg("self"))) + "This class implements a multivariate diagonal Gaussian distribution.", no_init) + .def("__init__", boost::python::make_constructor(&_init)) + .def(init<>(arg("self"))) .def(init<const size_t>((arg("self"), arg("n_inputs")))) .def(init<bob::machine::Gaussian&>((arg("self"), arg("other")))) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")))) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::Gaussian::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this Gaussian with the 'other' one to be approximately the same.") @@ -79,8 +104,8 @@ void bind_machine_gaussian() .def("resize", &bob::machine::Gaussian::resize, (arg("self"), arg("dim_d")), "Set the input dimensionality, reset the mean to zero and the variance to one.") .def("log_likelihood", &py_logLikelihood, (arg("self"), arg("sample")), "Output the log likelihood of the sample, x. The input size is checked.") .def("log_likelihood_", &py_logLikelihood_, (arg("self"), arg("sample")), "Output the log likelihood of the sample, x. The input size is NOT checked.") - .def("save", &bob::machine::Gaussian::save, (arg("self"), arg("config")), "Save to a Configuration") - .def("load", &bob::machine::Gaussian::load, (arg("self"), arg("config")),"Load from a Configuration") + .def("save", &_save, (arg("self"), arg("config")), "Save to a Configuration") + .def("load", &_load, (arg("self"), arg("config")),"Load from a Configuration") .def(self_ns::str(self_ns::self)) ; } diff --git a/bob/learn/misc/old/gmm.cc b/bob/learn/misc/old/gmm.cc index 3f6d02e097bdfd3dce19e7360f93b0aa4bd508b5..73919b6ee8430f0692df1048ea8cbc5fce81b1bd 100644 --- a/bob/learn/misc/old/gmm.cc +++ b/bob/learn/misc/old/gmm.cc @@ -4,16 +4,21 @@ * * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ + +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/GMMStats.h> -#include <bob/machine/GMMMachine.h> +#include <bob.learn.misc/GMMStats.h> +#include <bob.learn.misc/GMMMachine.h> using namespace boost::python; static object py_gmmstats_getN(bob::machine::GMMStats& s) { - bob::python::ndarray n(bob::core::array::t_float64, s.n.extent(0)); + bob::python::ndarray n(bob::io::base::array::t_float64, s.n.extent(0)); blitz::Array<double,1> n_ = n.bz<double,1>(); n_ = s.n; return n.self(); @@ -27,7 +32,7 @@ static void py_gmmstats_setN(bob::machine::GMMStats& s, static object py_gmmstats_getSumpx(bob::machine::GMMStats& s) { - bob::python::ndarray sumpx(bob::core::array::t_float64, s.sumPx.extent(0), + bob::python::ndarray sumpx(bob::io::base::array::t_float64, s.sumPx.extent(0), s.sumPx.extent(1)); blitz::Array<double,2> sumpx_ = sumpx.bz<double,2>(); sumpx_ = s.sumPx; @@ -42,7 +47,7 @@ static void py_gmmstats_setSumpx(bob::machine::GMMStats& s, static object py_gmmstats_getSumpxx(bob::machine::GMMStats& s) { - bob::python::ndarray sumpxx(bob::core::array::t_float64, s.sumPxx.extent(0), + bob::python::ndarray sumpxx(bob::io::base::array::t_float64, s.sumPxx.extent(0), s.sumPxx.extent(1)); blitz::Array<double,2> sumpxx_ = sumpxx.bz<double,2>(); sumpxx_ = s.sumPxx; @@ -64,7 +69,7 @@ static void py_gmmmachine_setWeights(bob::machine::GMMMachine& machine, static object py_gmmmachine_getMeans(const bob::machine::GMMMachine& machine) { - bob::python::ndarray means(bob::core::array::t_float64, + bob::python::ndarray means(bob::io::base::array::t_float64, machine.getNGaussians(), machine.getNInputs()); blitz::Array<double,2> means_ = means.bz<double,2>(); machine.getMeans(means_); @@ -85,7 +90,7 @@ static void py_gmmmachine_setMeanSupervector(bob::machine::GMMMachine& machine, static object py_gmmmachine_getVariances(const bob::machine::GMMMachine& machine) { - bob::python::ndarray variances(bob::core::array::t_float64, + bob::python::ndarray variances(bob::io::base::array::t_float64, machine.getNGaussians(), machine.getNInputs()); blitz::Array<double,2> variances_ = variances.bz<double,2>(); machine.getVariances(variances_); @@ -106,7 +111,7 @@ static void py_gmmmachine_setVarianceSupervector(bob::machine::GMMMachine& machi static object py_gmmmachine_getVarianceThresholds(const bob::machine::GMMMachine& machine) { - bob::python::ndarray varianceThresholds(bob::core::array::t_float64, + bob::python::ndarray varianceThresholds(bob::io::base::array::t_float64, machine.getNGaussians(), machine.getNInputs()); blitz::Array<double,2> varianceThresholds_ = varianceThresholds.bz<double,2>(); machine.getVarianceThresholds(varianceThresholds_); @@ -180,7 +185,7 @@ static double py_gmmmachine_loglikelihoodB_(const bob::machine::GMMMachine& mach static void py_gmmmachine_accStatistics(const bob::machine::GMMMachine& machine, bob::python::const_ndarray x, bob::machine::GMMStats& gs) { - const bob::core::array::typeinfo& info = x.type(); + const bob::io::base::array::typeinfo& info = x.type(); switch(info.nd) { case 1: machine.accStatistics(x.bz<double,1>(), gs); @@ -196,7 +201,7 @@ static void py_gmmmachine_accStatistics(const bob::machine::GMMMachine& machine, static void py_gmmmachine_accStatistics_(const bob::machine::GMMMachine& machine, bob::python::const_ndarray x, bob::machine::GMMStats& gs) { - const bob::core::array::typeinfo& info = x.type(); + const bob::io::base::array::typeinfo& info = x.type(); switch(info.nd) { case 1: machine.accStatistics_(x.bz<double,1>(), gs); @@ -209,6 +214,40 @@ static void py_gmmmachine_accStatistics_(const bob::machine::GMMMachine& machine } } +static boost::shared_ptr<bob::machine::GMMStats> s_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::GMMStats>(new bob::machine::GMMStats(*hdf5->f)); +} + +static void s_load(bob::machine::GMMStats& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void s_save(const bob::machine::GMMStats& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + +static boost::shared_ptr<bob::machine::GMMMachine> m_init(boost::python::object file){ + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::GMMMachine>(new bob::machine::GMMMachine(*hdf5->f)); +} + +static void m_load(bob::machine::GMMMachine& self, boost::python::object file){ + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void m_save(const bob::machine::GMMMachine& self, boost::python::object file){ + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + void bind_machine_gmm() { class_<bob::machine::GMMStats, boost::shared_ptr<bob::machine::GMMStats> >("GMMStats", @@ -219,8 +258,8 @@ void bind_machine_gmm() "Eq (9) is sumPx(i) / n(i)\n" "Eq (10) is sumPxx(i) / n(i)\n", init<>(arg("self"))) + .def("__init__", boost::python::make_constructor(&s_init)) .def(init<const size_t, const size_t>((arg("self"), arg("n_gaussians"), arg("n_inputs")))) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")))) .def(init<bob::machine::GMMStats&>((arg("self"), arg("other")), "Creates a GMMStats from another GMMStats, using the copy constructor.")) .def(self == self) .def(self != self) @@ -233,8 +272,8 @@ void bind_machine_gmm() .def("resize", &bob::machine::GMMStats::resize, (arg("self"), arg("n_gaussians"), arg("n_inputs")), " Allocates space for the statistics and resets to zero.") .def("init", &bob::machine::GMMStats::init, (arg("self")), "Resets statistics to zero.") - .def("save", &bob::machine::GMMStats::save, (arg("self"), arg("config")), "Save to a Configuration") - .def("load", &bob::machine::GMMStats::load, (arg("self"), arg("config")), "Load from a Configuration") + .def("save", &s_save, (arg("self"), arg("config")), "Save to a Configuration") + .def("load", &s_load, (arg("self"), arg("config")), "Load from a Configuration") .def(self_ns::str(self_ns::self)) .def(self_ns::self += self_ns::self) ; @@ -243,9 +282,9 @@ void bind_machine_gmm() "This class implements a multivariate diagonal Gaussian distribution.\n" "See Section 2.3.9 of Bishop, \"Pattern recognition and machine learning\", 2006", init<>(arg("self"))) - .def(init<const size_t, const size_t>((arg("self"), arg("n_gaussians"), arg("n_inputs")))) + .def("__init__", boost::python::make_constructor(&m_init)) .def(init<bob::machine::GMMMachine&>((arg("self"), arg("other")), "Creates a GMMMachine from another GMMMachine, using the copy constructor.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")))) + .def(init<const size_t, const size_t>((arg("self"), arg("n_gaussians"), arg("n_inputs")))) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::GMMMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this GMMMachine with the 'other' one to be approximately the same.") @@ -283,8 +322,8 @@ void bind_machine_gmm() "Accumulate the GMM statistics for this sample(s). Inputs are checked.") .def("acc_statistics_", &py_gmmmachine_accStatistics_, args("self", "x", "stats"), "Accumulate the GMM statistics for this sample(s). Inputs are NOT checked.") - .def("load", &bob::machine::GMMMachine::load, (arg("self"), arg("config")), "Load from a Configuration") - .def("save", &bob::machine::GMMMachine::save, (arg("self"), arg("config")), "Save to a Configuration") + .def("load", &m_load, (arg("self"), arg("config")), "Load from a Configuration") + .def("save", &m_save, (arg("self"), arg("config")), "Save to a Configuration") .def(self_ns::str(self_ns::self)) ; diff --git a/bob/learn/misc/old/gmm_trainer.cc b/bob/learn/misc/old/gmm_trainer.cc index 0cdea0ea9f575a17fdfe7abd743e4a16e03a7675..49063b70b59fb4db5c40a0d328e47a640e761ead 100644 --- a/bob/learn/misc/old/gmm_trainer.cc +++ b/bob/learn/misc/old/gmm_trainer.cc @@ -8,9 +8,9 @@ #include "ndarray.h" #include <limits> -#include <bob/trainer/GMMTrainer.h> -#include <bob/trainer/MAP_GMMTrainer.h> -#include <bob/trainer/ML_GMMTrainer.h> +#include <bob.learn.misc/GMMTrainer.h> +#include <bob.learn.misc/MAP_GMMTrainer.h> +#include <bob.learn.misc/ML_GMMTrainer.h> using namespace boost::python; diff --git a/bob/learn/misc/old/hdf5.cc b/bob/learn/misc/old/hdf5.cc deleted file mode 100644 index 10d06446e5316f39ad608602e71820d1a3a81f08..0000000000000000000000000000000000000000 --- a/bob/learn/misc/old/hdf5.cc +++ /dev/null @@ -1,709 +0,0 @@ -/** - * @author Andre Anjos <andre.anjos@idiap.ch> - * @date Wed Jun 22 17:50:08 2011 +0200 - * - * @brief Binds our C++ HDF5 interface to python - * - * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland - */ - -#include "ndarray.h" -#include <boost/format.hpp> -#include <bob/io/HDF5File.h> - -using namespace boost::python; - -/** - * Returns a list of all paths inside a HDF5File - */ -static list hdf5file_paths(const bob::io::HDF5File& f, const bool relative) { - list retval; - std::vector<std::string> values; - f.paths(values, relative); - for (size_t i=0; i<values.size(); ++i) retval.append(str(values[i])); - return retval; -} - -/** - * Returns a list of all sub-directories inside a HDF5File - */ -static list hdf5file_sub_groups(const bob::io::HDF5File& f, const bool relative, bool recursive) { - list retval; - std::vector<std::string> values; - f.sub_groups(values, relative, recursive); - for (size_t i=0; i<values.size(); ++i) retval.append(str(values[i])); - return retval; -} - -/** - * Returns tuples for the description of all possible ways to read a certain - * path. - */ -static tuple hdf5file_describe(const bob::io::HDF5File& f, const std::string& p) { - const std::vector<bob::io::HDF5Descriptor>& dv = f.describe(p); - list retval; - for (size_t k=0; k<dv.size(); ++k) retval.append(dv[k]); - return tuple(retval); -} - -/** - * Functionality to read from HDF5File's - */ -static object hdf5file_xread(bob::io::HDF5File& f, const std::string& p, - int descriptor, int pos) { - - const std::vector<bob::io::HDF5Descriptor>& D = f.describe(p); - - //last descriptor always contains the full readout. - const bob::io::HDF5Type& type = D[descriptor].type; - const bob::io::HDF5Shape& shape = type.shape(); - - if (shape.n() == 1 && shape[0] == 1) { //read as scalar - switch(type.type()) { - case bob::io::s: - return object(f.read<std::string>(p, pos)); - case bob::io::b: - return object(f.read<bool>(p, pos)); - case bob::io::i8: - return object(f.read<int8_t>(p, pos)); - case bob::io::i16: - return object(f.read<int16_t>(p, pos)); - case bob::io::i32: - return object(f.read<int32_t>(p, pos)); - case bob::io::i64: - return object(f.read<int64_t>(p, pos)); - case bob::io::u8: - return object(f.read<uint8_t>(p, pos)); - case bob::io::u16: - return object(f.read<uint16_t>(p, pos)); - case bob::io::u32: - return object(f.read<uint32_t>(p, pos)); - case bob::io::u64: - return object(f.read<uint64_t>(p, pos)); - case bob::io::f32: - return object(f.read<float>(p, pos)); - case bob::io::f64: - return object(f.read<double>(p, pos)); - case bob::io::f128: - return object(f.read<long double>(p, pos)); - case bob::io::c64: - return object(f.read<std::complex<float> >(p, pos)); - case bob::io::c128: - return object(f.read<std::complex<double> >(p, pos)); - case bob::io::c256: - return object(f.read<std::complex<long double> >(p, pos)); - default: - PYTHON_ERROR(TypeError, "unsupported HDF5 type: %s", type.str().c_str()); - } - } - - //read as an numpy array - bob::core::array::typeinfo atype; - type.copy_to(atype); - bob::python::py_array retval(atype); - f.read_buffer(p, pos, atype, retval.ptr()); - return retval.pyobject(); -} - -static object hdf5file_lread(bob::io::HDF5File& f, const std::string& p, - int64_t pos=-1) { - if (pos >= 0) return hdf5file_xread(f, p, 0, pos); - - //otherwise returns as a list - const std::vector<bob::io::HDF5Descriptor>& D = f.describe(p); - list retval; - for (uint64_t k=0; k<D[0].size; ++k) - retval.append(hdf5file_xread(f, p, 0, k)); - return retval; -} -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_lread_overloads, hdf5file_lread, 2, 3) - -static inline object hdf5file_read(bob::io::HDF5File& f, const std::string& p) { - return hdf5file_xread(f, p, 1, 0); -} - -void set_string_type(bob::io::HDF5Type& t, object o) { - t = bob::io::HDF5Type(extract<std::string>(o)); -} - -template <typename T> void set_type(bob::io::HDF5Type& t) { - T v; - t = bob::io::HDF5Type(v); -} - -/** - * A function to check for python scalars that works with numpy-1.6.x - */ -static bool is_python_scalar(PyObject* obj) { - return ( - PyBool_Check(obj) || -#if PY_VERSION_HEX < 0x03000000 - PyString_Check(obj) || -#else - PyBytes_Check(obj) || -#endif - PyUnicode_Check(obj) || -#if PY_VERSION_HEX < 0x03000000 - PyInt_Check(obj) || -#endif - PyLong_Check(obj) || - PyFloat_Check(obj) || - PyComplex_Check(obj) - ); -} - -/** - * Sets at 't', the type of the object 'o' according to our support types. - * Raise in case of problems. Furthermore, returns 'true' if the object is as - * simple scalar. - */ -static bool get_object_type(object o, bob::io::HDF5Type& t) { - PyObject* op = o.ptr(); - - if (PyArray_IsScalar(op, Generic) || is_python_scalar(op)) { - if (PyArray_IsScalar(op, String)) set_string_type(t, o); - else if (PyBool_Check(op)) set_type<bool>(t); -#if PY_VERSION_HEX < 0x03000000 - else if (PyString_Check(op)) set_string_type(t, o); -#else - else if (PyBytes_Check(op)) set_string_type(t, o); -#endif - else if (PyUnicode_Check(op)) set_string_type(t, o); -#if PY_VERSION_HEX < 0x03000000 - else if (PyInt_Check(op)) set_type<int32_t>(t); -#endif - else if (PyLong_Check(op)) set_type<int64_t>(t); - else if (PyFloat_Check(op)) set_type<double>(t); - else if (PyComplex_Check(op)) set_type<std::complex<double> >(t); - else if (PyArray_IsScalar(op, Bool)) set_type<bool>(t); - else if (PyArray_IsScalar(op, Int8)) set_type<int8_t>(t); - else if (PyArray_IsScalar(op, UInt8)) set_type<uint8_t>(t); - else if (PyArray_IsScalar(op, Int16)) set_type<int16_t>(t); - else if (PyArray_IsScalar(op, UInt16)) set_type<uint16_t>(t); - else if (PyArray_IsScalar(op, Int32)) set_type<int32_t>(t); - else if (PyArray_IsScalar(op, UInt32)) set_type<uint32_t>(t); - else if (PyArray_IsScalar(op, Int64)) set_type<int64_t>(t); - else if (PyArray_IsScalar(op, UInt64)) set_type<uint64_t>(t); - else if (PyArray_IsScalar(op, Float)) set_type<float>(t); - else if (PyArray_IsScalar(op, Double)) set_type<double>(t); - else if (PyArray_IsScalar(op, LongDouble)) set_type<long double>(t); - else if (PyArray_IsScalar(op, CFloat)) set_type<std::complex<float> >(t); - else if (PyArray_IsScalar(op, CDouble)) set_type<std::complex<double> >(t); - else if (PyArray_IsScalar(op, CLongDouble)) set_type<std::complex<long double> >(t); - else { - str so(o); - std::string s = extract<std::string>(so); - PYTHON_ERROR(TypeError, "No support for HDF5 type conversion for scalar object '%s'", s.c_str()); - } - return true; - } - - else if (PyArray_Check(op)) { - bob::core::array::typeinfo ti; - bob::python::typeinfo_ndarray_(o, ti); - t = bob::io::HDF5Type(ti); - return false; - } - - else { - //checks for convertibility to numpy.ndarray (not necessarily writeable, - //but has to be "behaved" = C-style contiguous). - bob::core::array::typeinfo ti; - if (bob::python::convertible(o, ti, false, true) != bob::python::IMPOSSIBLE) { - t = bob::io::HDF5Type(ti); - return false; - } - } - - //if you get to this point, then this object is not supported - str so(o); - std::string printout = extract<std::string>(so); - PYTHON_ERROR(TypeError, "No support for HDF5 type conversion for object of unknown type %s", printout.c_str()); -} - -template <typename T> -static void inner_replace_scalar(bob::io::HDF5File& f, - const std::string& path, object obj, size_t pos) { - T value = extract<T>(obj); - f.replace(path, pos, value); -} - -static void inner_replace(bob::io::HDF5File& f, const std::string& path, - const bob::io::HDF5Type& type, object obj, size_t pos, bool scalar) { - - //no error detection: this should be done before reaching this method - - if (scalar) { //write as a scalar - switch(type.type()) { - case bob::io::s: - return inner_replace_scalar<std::string>(f, path, obj, pos); - case bob::io::b: - return inner_replace_scalar<bool>(f, path, obj, pos); - case bob::io::i8: - return inner_replace_scalar<int8_t>(f, path, obj, pos); - case bob::io::i16: - return inner_replace_scalar<int16_t>(f, path, obj, pos); - case bob::io::i32: - return inner_replace_scalar<int32_t>(f, path, obj, pos); - case bob::io::i64: - return inner_replace_scalar<int64_t>(f, path, obj, pos); - case bob::io::u8: - return inner_replace_scalar<uint8_t>(f, path, obj, pos); - case bob::io::u16: - return inner_replace_scalar<uint16_t>(f, path, obj, pos); - case bob::io::u32: - return inner_replace_scalar<uint32_t>(f, path, obj, pos); - case bob::io::u64: - return inner_replace_scalar<uint64_t>(f, path, obj, pos); - case bob::io::f32: - return inner_replace_scalar<float>(f, path, obj, pos); - case bob::io::f64: - return inner_replace_scalar<double>(f, path, obj, pos); - case bob::io::f128: - return inner_replace_scalar<long double>(f, path, obj, pos); - case bob::io::c64: - return inner_replace_scalar<std::complex<float> >(f, path, obj, pos); - case bob::io::c128: - return inner_replace_scalar<std::complex<double> >(f, path, obj, pos); - case bob::io::c256: - return inner_replace_scalar<std::complex<long double> >(f, path, obj, pos); - default: - break; - } - } - - else { //write as an numpy array - bob::python::py_array tmp(obj, object()); - f.write_buffer(path, pos, tmp.type(), tmp.ptr()); - } -} - -static void hdf5file_replace(bob::io::HDF5File& f, const std::string& path, - size_t pos, object obj) { - bob::io::HDF5Type type; - bool scalar = get_object_type(obj, type); - inner_replace(f, path, type, obj, pos, scalar); -} - -template <typename T> -static void inner_append_scalar(bob::io::HDF5File& f, const std::string& path, - object obj) { - T value = extract<T>(obj); - f.append(path, value); -} - -static void inner_append(bob::io::HDF5File& f, const std::string& path, - const bob::io::HDF5Type& type, object obj, size_t compression, bool scalar) { - - //no error detection: this should be done before reaching this method - - if (scalar) { //write as a scalar - switch(type.type()) { - case bob::io::s: - return inner_append_scalar<std::string>(f, path, obj); - case bob::io::b: - return inner_append_scalar<bool>(f, path, obj); - case bob::io::i8: - return inner_append_scalar<int8_t>(f, path, obj); - case bob::io::i16: - return inner_append_scalar<int16_t>(f, path, obj); - case bob::io::i32: - return inner_append_scalar<int32_t>(f, path, obj); - case bob::io::i64: - return inner_append_scalar<int64_t>(f, path, obj); - case bob::io::u8: - return inner_append_scalar<uint8_t>(f, path, obj); - case bob::io::u16: - return inner_append_scalar<uint16_t>(f, path, obj); - case bob::io::u32: - return inner_append_scalar<uint32_t>(f, path, obj); - case bob::io::u64: - return inner_append_scalar<uint64_t>(f, path, obj); - case bob::io::f32: - return inner_append_scalar<float>(f, path, obj); - case bob::io::f64: - return inner_append_scalar<double>(f, path, obj); - case bob::io::f128: - return inner_append_scalar<long double>(f, path, obj); - case bob::io::c64: - return inner_append_scalar<std::complex<float> >(f, path, obj); - case bob::io::c128: - return inner_append_scalar<std::complex<double> >(f, path, obj); - case bob::io::c256: - return inner_append_scalar<std::complex<long double> >(f, path, obj); - default: - break; - } - } - - else { //write as an numpy array - bob::python::py_array tmp(obj, object()); - if (!f.contains(path)) f.create(path, tmp.type(), true, compression); - f.extend_buffer(path, tmp.type(), tmp.ptr()); - } -} - -static void hdf5file_append_iterable(bob::io::HDF5File& f, const std::string& path, - object iterable, size_t compression) { - for (int k=0; k<len(iterable); ++k) { - object obj = iterable[k]; - bob::io::HDF5Type type; - bool scalar = get_object_type(obj, type); - inner_append(f, path, type, obj, compression, scalar); - } -} - -static void hdf5file_append(bob::io::HDF5File& f, const std::string& path, - object obj, size_t compression=0) { - PyObject* op = obj.ptr(); - if (PyList_Check(op) || PyTuple_Check(op)) { - hdf5file_append_iterable(f, path, obj, compression); - } - else { - bob::io::HDF5Type type; - bool scalar = get_object_type(obj, type); - inner_append(f, path, type, obj, compression, scalar); - } -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_append_overloads, hdf5file_append, 3, 4) - -template <typename T> -static void inner_set_scalar(bob::io::HDF5File& f, const std::string& path, - object obj) { - T value = extract<T>(obj); - f.set(path, value); -} - -static void inner_set(bob::io::HDF5File& f, const std::string& path, - const bob::io::HDF5Type& type, object obj, size_t compression, bool scalar) { - - //no error detection: this should be done before reaching this method - - if (scalar) { //write as a scalar - switch(type.type()) { - case bob::io::s: - return inner_set_scalar<std::string>(f, path, obj); - case bob::io::b: - return inner_set_scalar<bool>(f, path, obj); - case bob::io::i8: - return inner_set_scalar<int8_t>(f, path, obj); - case bob::io::i16: - return inner_set_scalar<int16_t>(f, path, obj); - case bob::io::i32: - return inner_set_scalar<int32_t>(f, path, obj); - case bob::io::i64: - return inner_set_scalar<int64_t>(f, path, obj); - case bob::io::u8: - return inner_set_scalar<uint8_t>(f, path, obj); - case bob::io::u16: - return inner_set_scalar<uint16_t>(f, path, obj); - case bob::io::u32: - return inner_set_scalar<uint32_t>(f, path, obj); - case bob::io::u64: - return inner_set_scalar<uint64_t>(f, path, obj); - case bob::io::f32: - return inner_set_scalar<float>(f, path, obj); - case bob::io::f64: - return inner_set_scalar<double>(f, path, obj); - case bob::io::f128: - return inner_set_scalar<long double>(f, path, obj); - case bob::io::c64: - return inner_set_scalar<std::complex<float> >(f, path, obj); - case bob::io::c128: - return inner_set_scalar<std::complex<double> >(f, path, obj); - case bob::io::c256: - return inner_set_scalar<std::complex<long double> >(f, path, obj); - default: - break; - } - } - - else { //write as an numpy array - bob::python::py_array tmp(obj, object()); - if (!f.contains(path)) f.create(path, tmp.type(), false, compression); - f.write_buffer(path, 0, tmp.type(), tmp.ptr()); - } -} - -static void hdf5file_set(bob::io::HDF5File& f, const std::string& path, - object obj, size_t compression=0) { - bob::io::HDF5Type type; - bool scalar = get_object_type(obj, type); - inner_set(f, path, type, obj, compression, scalar); -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_set_overloads, hdf5file_set, 3, 4) - -template <typename T> -static object inner_get_scalar_attr(const bob::io::HDF5File& f, - const std::string& path, const std::string& name, const bob::io::HDF5Type& type) { - T value; - f.read_attribute(path, name, type, static_cast<void*>(&value)); - return object(value); -} - -template <> -object inner_get_scalar_attr<std::string>(const bob::io::HDF5File& f, - const std::string& path, const std::string& name, const bob::io::HDF5Type&) { - std::string retval; - f.getAttribute(path, name, retval); - return object(retval); -} - -static object inner_get_attr(const bob::io::HDF5File& f, const std::string& path, - const std::string& name, const bob::io::HDF5Type& type) { - - //no error detection: this should be done before reaching this method - - const bob::io::HDF5Shape& shape = type.shape(); - - if (type.type() == bob::io::s || (shape.n() == 1 && shape[0] == 1)) { - //read as scalar - switch(type.type()) { - case bob::io::s: - return inner_get_scalar_attr<std::string>(f, path, name, type); - case bob::io::b: - return inner_get_scalar_attr<bool>(f, path, name, type); - case bob::io::i8: - return inner_get_scalar_attr<int8_t>(f, path, name, type); - case bob::io::i16: - return inner_get_scalar_attr<int16_t>(f, path, name, type); - case bob::io::i32: - return inner_get_scalar_attr<int32_t>(f, path, name, type); - case bob::io::i64: - return inner_get_scalar_attr<int64_t>(f, path, name, type); - case bob::io::u8: - return inner_get_scalar_attr<uint8_t>(f, path, name, type); - case bob::io::u16: - return inner_get_scalar_attr<uint16_t>(f, path, name, type); - case bob::io::u32: - return inner_get_scalar_attr<uint32_t>(f, path, name, type); - case bob::io::u64: - return inner_get_scalar_attr<uint64_t>(f, path, name, type); - case bob::io::f32: - return inner_get_scalar_attr<float>(f, path, name, type); - case bob::io::f64: - return inner_get_scalar_attr<double>(f, path, name, type); - case bob::io::f128: - return inner_get_scalar_attr<long double>(f, path, name, type); - case bob::io::c64: - return inner_get_scalar_attr<std::complex<float> >(f, path, name, type); - case bob::io::c128: - return inner_get_scalar_attr<std::complex<double> >(f, path, name, type); - case bob::io::c256: - return inner_get_scalar_attr<std::complex<long double> >(f, path, name, type); - default: - break; - } - } - - //read as an numpy array - bob::core::array::typeinfo atype; - type.copy_to(atype); - bob::python::py_array retval(atype); - f.read_attribute(path, name, type, retval.ptr()); - return retval.pyobject(); -} - -static dict hdf5file_get_attributes(const bob::io::HDF5File& f, const std::string& path=".") { - std::map<std::string, bob::io::HDF5Type> attributes; - f.listAttributes(path, attributes); - dict retval; - for (std::map<std::string, bob::io::HDF5Type>::iterator k=attributes.begin(); k!=attributes.end(); ++k) { - if (k->second.type() == bob::io::unsupported) { - boost::format m("unsupported HDF5 data type detected for attribute '%s' - setting None"); - m % k->first; - PYTHON_WARNING(UserWarning, m.str().c_str()); - retval[k->first] = object(); //None - } - else { - retval[k->first] = inner_get_attr(f, path, k->first, k->second); - } - } - return retval; -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_get_attributes_overloads, hdf5file_get_attributes, 1, 2) - -static object hdf5file_get_attribute(const bob::io::HDF5File& f, const std::string& name, const std::string& path=".") { - bob::io::HDF5Type type; - f.getAttributeType(path, name, type); - if (type.type() == bob::io::unsupported) { - boost::format m("unsupported HDF5 data type detected for attribute '%s' - returning None"); - m % name; - PYTHON_WARNING(UserWarning, m.str().c_str()); - return object(); - } - else { - return inner_get_attr(f, path, name, type); - } -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_get_attribute_overloads, hdf5file_get_attribute, 2, 3) - -template <typename T> -static void inner_set_scalar_attr(bob::io::HDF5File& f, - const std::string& path, const std::string& name, const bob::io::HDF5Type& type, - object obj) { - T value = extract<T>(obj); - f.write_attribute(path, name, type, static_cast<void*>(&value)); -} - -template <> -void inner_set_scalar_attr<std::string>(bob::io::HDF5File& f, - const std::string& path, const std::string& name, const bob::io::HDF5Type& type, - object obj) { - std::string value = extract<std::string>(obj); - f.write_attribute(path, name, type, static_cast<const void*>(value.c_str())); -} - -static void inner_set_attr(bob::io::HDF5File& f, const std::string& path, - const std::string& name, const bob::io::HDF5Type& type, object obj, - bool scalar) { - - //no error detection: this should be done before reaching this method - - if (scalar) { //write as a scalar - switch(type.type()) { - case bob::io::s: - return inner_set_scalar_attr<std::string>(f, path, name, type, obj); - case bob::io::b: - return inner_set_scalar_attr<bool>(f, path, name, type, obj); - case bob::io::i8: - return inner_set_scalar_attr<int8_t>(f, path, name, type, obj); - case bob::io::i16: - return inner_set_scalar_attr<int16_t>(f, path, name, type, obj); - case bob::io::i32: - return inner_set_scalar_attr<int32_t>(f, path, name, type, obj); - case bob::io::i64: - return inner_set_scalar_attr<int64_t>(f, path, name, type, obj); - case bob::io::u8: - return inner_set_scalar_attr<uint8_t>(f, path, name, type, obj); - case bob::io::u16: - return inner_set_scalar_attr<uint16_t>(f, path, name, type, obj); - case bob::io::u32: - return inner_set_scalar_attr<uint32_t>(f, path, name, type, obj); - case bob::io::u64: - return inner_set_scalar_attr<uint64_t>(f, path, name, type, obj); - case bob::io::f32: - return inner_set_scalar_attr<float>(f, path, name, type, obj); - case bob::io::f64: - return inner_set_scalar_attr<double>(f, path, name, type, obj); - case bob::io::f128: - return inner_set_scalar_attr<long double>(f, path, name, type, obj); - case bob::io::c64: - return inner_set_scalar_attr<std::complex<float> >(f, path, name, type, obj); - case bob::io::c128: - return inner_set_scalar_attr<std::complex<double> >(f, path, name, type, obj); - case bob::io::c256: - return inner_set_scalar_attr<std::complex<long double> >(f, path, name, type, obj); - default: - break; - } - } - - else { //write as an numpy array - bob::python::py_array retval(obj, object()); - f.write_attribute(path, name, type, retval.ptr()); - } -} - -static void hdf5file_set_attributes(bob::io::HDF5File& f, dict attributes, const std::string& path=".") { - object keys = attributes.iterkeys(); - for (int k=0; k<len(keys); ++k) { - std::string key = extract<std::string>(keys[k]); - bob::io::HDF5Type type; - object obj = attributes[keys[k]]; - bool scalar = get_object_type(obj, type); - inner_set_attr(f, path, key, type, obj, scalar); - } -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_set_attributes_overloads, hdf5file_set_attributes, 2, 3) - -static void hdf5file_set_attribute(bob::io::HDF5File& f, const std::string& key, object obj, const std::string& path=".") { - bob::io::HDF5Type type; - bool scalar = get_object_type(obj, type); - inner_set_attr(f, path, key, type, obj, scalar); -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_set_attribute_overloads, hdf5file_set_attribute, 3, 4) - -static bool hdf5file_has_attribute(const bob::io::HDF5File& f, const std::string& name, const std::string& path=".") { - return f.hasAttribute(path, name); -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_has_attribute_overloads, hdf5file_has_attribute, 2, 3) - -static void hdf5file_del_attribute(bob::io::HDF5File& f, const std::string& name, const std::string& path=".") { - f.deleteAttribute(path, name); -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_del_attribute_overloads, hdf5file_del_attribute, 2, 3) - -static void hdf5file_del_attributes(bob::io::HDF5File& f, const std::string& path=".") { - std::map<std::string, bob::io::HDF5Type> attributes; - f.listAttributes(path, attributes); - for (std::map<std::string, bob::io::HDF5Type>::iterator k=attributes.begin(); k!=attributes.end(); ++k) { - f.deleteAttribute(path, k->first); - } -} - -BOOST_PYTHON_FUNCTION_OVERLOADS(hdf5file_del_attributes_overloads, hdf5file_del_attributes, 1, 2) - -void bind_io_hdf5() { - class_<bob::io::HDF5File, boost::shared_ptr<bob::io::HDF5File> >("HDF5File", "A HDF5File allows users to read and write data from and to files containing standard bob binary coded data in HDF5 format. For an introduction to HDF5, please visit http://www.hdfgroup.org/HDF5.", no_init) - .def(init<const bob::io::HDF5File&> ((arg("self"), arg("other")), "Generates a shallow copy of the already opened file.")) - .def(init<const std::string&, const char> ((arg("self"), arg("filename"), arg("openmode_string")='r'), "Opens a new file in one of these supported modes: 'r' (read-only), 'a' (read/write/append), 'w' (read/write/truncate) or 'x' (read/write/exclusive)")) - .def("cd", &bob::io::HDF5File::cd, (arg("self"), arg("path")), "Changes the current prefix path. When this object is started, the prefix path is empty, which means all following paths to data objects should be given using the full path. If you set this to a different value, it will be used as a prefix to any subsequent operation until you reset it. If path starts with '/', it is treated as an absolute path. '..' and '.' are supported. This object should be a std::string. If the value is relative, it is added to the current path. If it is absolute, it causes the prefix to be reset. Note all operations taking a relative path, following a cd(), will be considered relative to the value defined by the 'cwd' property of this object.") - .def("has_group", &bob::io::HDF5File::hasGroup, (arg("self"), arg("path")), "Checks if a path exists inside a file - does not work for datasets, only for directories. If the given path is relative, it is take w.r.t. to the current working directory") - .def("create_group", &bob::io::HDF5File::createGroup, (arg("self"), arg("path")), "Creates a new directory inside the file. A relative path is taken w.r.t. to the current directory. If the directory already exists (check it with hasGroup()), an exception will be raised.") - .add_property("cwd", &bob::io::HDF5File::cwd) - .def("__contains__", &bob::io::HDF5File::contains, (arg("self"), arg("key")), "Returns True if the file contains an HDF5 dataset with a given path") - .def("has_key", &bob::io::HDF5File::contains, (arg("self"), arg("key")), "Returns True if the file contains an HDF5 dataset with a given path") - .def("describe", &hdf5file_describe, (arg("self"), arg("key")), "If a given path to an HDF5 dataset exists inside the file, return a type description of objects recorded in such a dataset, otherwise, raises an exception. The returned value type is a tuple of tuples (HDF5Type, number-of-objects, expandible) describing the capabilities if the file is read using theses formats.") - .def("unlink", &bob::io::HDF5File::unlink, (arg("self"), arg("key")), "If a given path to an HDF5 dataset exists inside the file, unlinks it. Please note this will note remove the data from the file, just make it inaccessible. If you wish to cleanup, save the reacheable objects from this file to another HDF5File object using copy(), for example.") - .def("rename", &bob::io::HDF5File::rename, (arg("self"), arg("from"), arg("to")), "If a given path to an HDF5 dataset exists in the file, rename it") - .def("keys", &hdf5file_paths, (arg("self"), arg("relative") = false), "Synonym for 'paths'") - .def("paths", &hdf5file_paths, (arg("self"), arg("relative") = false), "Returns all paths to datasets available inside this file, stored under the current working directory. If relative is set to True, the returned paths are relative to the current working directory, otherwise they are asbolute.") - .def("sub_groups", &hdf5file_sub_groups, (arg("self"), arg("relative") = false, arg("recursive") = true), "Returns all the subgroups (sub-directories) in the current file.") - .def("copy", &bob::io::HDF5File::copy, (arg("self"), arg("file")), "Copies all accessible content to another HDF5 file") - .def("read", &hdf5file_read, (arg("self"), arg("key")), "Reads the whole dataset in a single shot. Returns a single object with all contents.") - .def("lread", (object(*)(bob::io::HDF5File&, const std::string&, int64_t))0, hdf5file_lread_overloads((arg("self"), arg("key"), arg("pos")=-1), "Reads a given position from the dataset. Returns a single object if 'pos' >= 0, otherwise a list by reading all objects in sequence.")) - .def("replace", &hdf5file_replace, (arg("self"), arg("path"), arg("pos"), arg("data")), "Modifies the value of a scalar/array inside a dataset in the file.\n\n" \ - "Keyword Parameters:\n\n" \ - "path\n" \ - " This is the path to the HDF5 dataset to replace data at\n\n" \ - "pos\n" \ - " This is the position we should replace\n\n" \ - "data\n" \ - " This is the data that will be set on the position indicated") - .def("append", &hdf5file_append, hdf5file_append_overloads((arg("self"), arg("path"), arg("data"), arg("compression")=0), "Appends a scalar or an array to a dataset. If the dataset does not yet exist, one is created with the type characteristics.\n\n" \ - "Keyword Parameters:\n\n" \ - "path\n" \ - " This is the path to the HDF5 dataset to replace data at\n\n" \ - "data\n" \ - " This is the data that will be set on the position indicated. It may be a simple python or numpy scalar (such as :py:class:`numpy.uint8`) or a :py:class:`numpy.ndarray` of any of the supported data types. You can also, optionally, set this to a list or tuple of scalars or arrays. This will cause this method to iterate over the elements and add each individually.\n\n" \ - "compression\n" \ - " This parameter is effective when appending arrays. Set this to a number betwen 0 (default) and 9 (maximum) to compress the contents of this dataset. This setting is only effective if the dataset does not yet exist, otherwise, the previous setting is respected.")) - .def("set", &hdf5file_set, hdf5file_set_overloads((arg("self"), arg("path"), arg("data"), arg("compression")=0), "Sets the scalar or array at position 0 to the given value. This method is equivalent to checking if the scalar or array at position 0 exists and then replacing it. If the path does not exist, we append the new scalar or array.\n\n" \ - "Keyword Parameters:\n\n" \ - "path\n" \ - " This is the path to the HDF5 dataset to replace data at\n\n" \ - "data\n" \ - " This is the data that will be set on the position indicated. It may be a simple python or numpy scalar (such as :py:class:`numpy.uint8`) or a :py:class:`numpy.ndarray` of any of the supported data types. You can also, optionally, set this to an iterable of scalars or arrays. This will cause this method to collapse the whole iterable into a :py:class:`numpy.ndarray` and set that into the file.\n\n" \ - "compression\n" \ - " This parameter is effective when setting arrays. Set this to a number betwen 0 (default) and 9 (maximum) to compress the contents of this dataset. This setting is only effective if the dataset does not yet exist, otherwise, the previous setting is respected.")) - // attribute manipulation - .def("get_attributes", &hdf5file_get_attributes, hdf5file_get_attributes_overloads((arg("self"), arg("path")="."), "Returns a dictionary containing all attributes related to a particular (existing) path in this file. The path may point to a subdirectory or to a particular dataset. If the path does not exist, a RuntimeError is raised.")) - .def("get_attribute", &hdf5file_get_attribute, hdf5file_get_attribute_overloads((arg("self"), arg("name"), arg("path")="."), "Returns an object representing an attribute attached to a particular (existing) path in this file. The path may point to a subdirectory or to a particular dataset. If the path does not exist, a RuntimeError is raised.")) - .def("set_attributes", &hdf5file_set_attributes, hdf5file_set_attributes_overloads((arg("self"), arg("attrs"), arg("path")="."), "Sets attributes in a given (existing) path using a dictionary containing the names (keys) and values of those attributes. The path may point to a subdirectory or to a particular dataset. Only simple scalars (booleans, integers, floats and complex numbers) and arrays of those are supported at the time being. You can use :py:mod:`numpy` scalars to set values with arbitrary precision (e.g. :py:class:`numpy.uint8`). If the path does not exist, a RuntimeError is raised.")) - .def("set_attribute", &hdf5file_set_attribute, hdf5file_set_attribute_overloads((arg("self"), arg("name"), arg("value"), arg("path")="."), "Sets the attribute in a given (existing) path using the value provided. The path may point to a subdirectory or to a particular dataset. Only simple scalars (booleans, integers, floats and complex numbers) and arrays of those are supported at the time being. You can use :py:mod:`numpy` scalars to set values with arbitrary precision (e.g. :py:class:`numpy.uint8`). If the path does not exist, a RuntimeError is raised.")) - .def("has_attribute", &hdf5file_has_attribute, hdf5file_has_attribute_overloads((arg("self"), arg("name"), arg("path")="."), "Checks if given attribute exists in a given (existing) path. The path may point to a subdirectory or to a particular dataset. If the path does not exist, a RuntimeError is raised.")) - .def("delete_attribute", &hdf5file_del_attribute, hdf5file_del_attribute_overloads((arg("self"), arg("name"), arg("path")="."), "Deletes a given attribute associated to a (existing) path in the file. The path may point to a subdirectory or to a particular dataset. If the path does not exist, a RuntimeError is raised.")) - .def("delete_attributes", &hdf5file_del_attributes, hdf5file_del_attributes_overloads((arg("self"), arg("path")="."), "Deletes **all** attributes associated to a (existing) path in the file. The path may point to a subdirectory or to a particular dataset. If the path does not exist, a RuntimeError is raised.")) - - .add_property("filename", &bob::io::HDF5File::filename, "The name of the underlying file.") - ; -} diff --git a/bob/learn/misc/old/ivector.cc b/bob/learn/misc/old/ivector.cc index c3d0086c5b4fde5f926dcd58d46474e50f997146..1e72d584ab396603625fcae84497a65646418148 100644 --- a/bob/learn/misc/old/ivector.cc +++ b/bob/learn/misc/old/ivector.cc @@ -5,9 +5,13 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/IVectorMachine.h> +#include <bob.learn.misc/IVectorMachine.h> using namespace boost::python; @@ -33,7 +37,7 @@ static void py_computeIdTtSigmaInvT1(const bob::machine::IVectorMachine& machine static object py_computeIdTtSigmaInvT2(const bob::machine::IVectorMachine& machine, const bob::machine::GMMStats& gs) { - bob::python::ndarray output(bob::core::array::t_float64, machine.getDimRt(), machine.getDimRt()); + bob::python::ndarray output(bob::io::base::array::t_float64, machine.getDimRt(), machine.getDimRt()); blitz::Array<double,2> output_ = output.bz<double,2>(); machine.computeIdTtSigmaInvT(gs, output_); return output.self(); @@ -49,7 +53,7 @@ static void py_computeTtSigmaInvFnorm1(const bob::machine::IVectorMachine& machi static object py_computeTtSigmaInvFnorm2(const bob::machine::IVectorMachine& machine, const bob::machine::GMMStats& gs) { - bob::python::ndarray output(bob::core::array::t_float64, machine.getDimRt()); + bob::python::ndarray output(bob::io::base::array::t_float64, machine.getDimRt()); blitz::Array<double,1> output_ = output.bz<double,1>(); machine.computeTtSigmaInvFnorm(gs, output_); return output.self(); @@ -72,25 +76,44 @@ static void py_iv_forward1_(const bob::machine::IVectorMachine& machine, static object py_iv_forward2(const bob::machine::IVectorMachine& machine, const bob::machine::GMMStats& gs) { - bob::python::ndarray ivector(bob::core::array::t_float64, machine.getDimRt()); + bob::python::ndarray ivector(bob::io::base::array::t_float64, machine.getDimRt()); blitz::Array<double,1> ivector_ = ivector.bz<double,1>(); machine.forward(gs, ivector_); return ivector.self(); } + +static boost::shared_ptr<bob::machine::IVectorMachine> _init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::IVectorMachine>(new bob::machine::IVectorMachine(*hdf5->f)); +} + +static void _load(bob::machine::IVectorMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void _save(const bob::machine::IVectorMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + void bind_machine_ivector() { // TODO: reuse binding from generic machine class_<bob::machine::IVectorMachine, boost::shared_ptr<bob::machine::IVectorMachine> >("IVectorMachine", "An IVectorMachine to extract i-vector.\n\nReferences:\n[1] 'Front End Factor Analysis for Speaker Verification', N. Dehak, P. Kenny, R. Dehak, P. Dumouchel, P. Ouellet, IEEE Transactions on Audio, Speech and Language Processing, 2010, vol. 19, issue 4, pp. 788-798", init<boost::shared_ptr<bob::machine::GMMMachine>, optional<const size_t, const size_t> >((arg("self"), arg("ubm"), arg("rt")=1, arg("variance_threshold")=1e-10), "Builds a new IVectorMachine.")) .def(init<>((arg("self")), "Constructs a new empty IVectorMachine.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new IVectorMachine from a configuration file.")) + .def("__init__", boost::python::make_constructor(&_init), "Constructs a new IVectorMachine from a configuration file.") .def(init<const bob::machine::IVectorMachine&>((arg("self"), arg("machine")), "Copy constructs an IVectorMachine")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::IVectorMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this IVectorMachine with the 'other' one to be approximately the same.") - .def("load", &bob::machine::IVectorMachine::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::IVectorMachine::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .def("resize", &bob::machine::IVectorMachine::resize, (arg("self"), arg("rt")), "Reset the dimensionality of the Total Variability subspace T.") .add_property("ubm", &bob::machine::IVectorMachine::getUbm, &bob::machine::IVectorMachine::setUbm, "The UBM GMM attached to this Joint Factor Analysis model") .add_property("t", make_function(&bob::machine::IVectorMachine::getT, return_value_policy<copy_const_reference>()), &py_iv_setT, "The subspace T (Total Variability matrix)") diff --git a/bob/learn/misc/old/ivector_trainer.cc b/bob/learn/misc/old/ivector_trainer.cc index d86e2fc8b2fd7adb39acf2eac78a3616200692f5..9e09d0b5fee0f924db017fafe65ffbf49835416b 100644 --- a/bob/learn/misc/old/ivector_trainer.cc +++ b/bob/learn/misc/old/ivector_trainer.cc @@ -7,9 +7,9 @@ #include "ndarray.h" -#include <bob/trainer/IVectorTrainer.h> -#include <bob/machine/IVectorMachine.h> -#include <bob/trainer/EMTrainer.h> +#include <bob.learn.misc/IVectorTrainer.h> +#include <bob.learn.misc/IVectorMachine.h> +#include <bob.learn.misc/EMTrainer.h> #include <boost/python/stl_iterator.hpp> using namespace boost::python; diff --git a/bob/learn/misc/old/jfa.cc b/bob/learn/misc/old/jfa.cc index c8288d37dd54688af05e7e80462d8d2df4179d43..8f58b3b23ade1a75da7ce348e902fe92e19b72dd 100644 --- a/bob/learn/misc/old/jfa.cc +++ b/bob/learn/misc/old/jfa.cc @@ -7,10 +7,14 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/JFAMachine.h> -#include <bob/machine/GMMMachine.h> +#include <bob.learn.misc/JFAMachine.h> +#include <bob.learn.misc/GMMMachine.h> using namespace boost::python; @@ -135,6 +139,81 @@ static void py_gen2b_forward_(const bob::machine::Machine<bob::machine::GMMStats } +static boost::shared_ptr<bob::machine::JFABase> jb_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::JFABase>(new bob::machine::JFABase(*hdf5->f)); +} + +static void jb_load(bob::machine::JFABase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void jb_save(const bob::machine::JFABase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + +static boost::shared_ptr<bob::machine::JFAMachine> jm_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::JFAMachine>(new bob::machine::JFAMachine(*hdf5->f)); +} + +static void jm_load(bob::machine::JFAMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void jm_save(const bob::machine::JFAMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + +static boost::shared_ptr<bob::machine::ISVBase> ib_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::ISVBase>(new bob::machine::ISVBase(*hdf5->f)); +} + +static void ib_load(bob::machine::ISVBase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void ib_save(const bob::machine::ISVBase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + +static boost::shared_ptr<bob::machine::ISVMachine> im_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::ISVMachine>(new bob::machine::ISVMachine(*hdf5->f)); +} + +static void im_load(bob::machine::ISVMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void im_save(const bob::machine::ISVMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + void bind_machine_jfa() { class_<bob::machine::Machine<bob::machine::GMMStats, double>, boost::noncopyable>("MachineGMMStatsScalarBase", @@ -152,15 +231,16 @@ void bind_machine_jfa() ; - class_<bob::machine::JFABase, boost::shared_ptr<bob::machine::JFABase>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("JFABase", "A JFABase instance can be seen as a container for U, V and D when performing Joint Factor Analysis (JFA).\n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", init<const boost::shared_ptr<bob::machine::GMMMachine>, optional<const size_t, const size_t> >((arg("self"), arg("ubm"), arg("ru")=1, arg("rv")=1), "Builds a new JFABase.")) + class_<bob::machine::JFABase, boost::shared_ptr<bob::machine::JFABase>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("JFABase", "A JFABase instance can be seen as a container for U, V and D when performing Joint Factor Analysis (JFA).\n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", no_init) + .def("__init__", boost::python::make_constructor(&jb_init), "Constructs a new JFABaseMachine from a configuration file.") + .def(init<const boost::shared_ptr<bob::machine::GMMMachine>, optional<const size_t, const size_t> >((arg("self"), arg("ubm"), arg("ru")=1, arg("rv")=1), "Builds a new JFABase.")) .def(init<>((arg("self")), "Constructs a 1x1 JFABase instance. You have to set a UBM GMM and resize the U, V and D subspaces afterwards.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new JFABaseMachine from a configuration file.")) .def(init<const bob::machine::JFABase&>((arg("self"), arg("machine")), "Copy constructs a JFABase")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::JFABase::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this JFABase with the 'other' one to be approximately the same.") - .def("load", &bob::machine::JFABase::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::JFABase::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &jb_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &jb_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .def("resize", &bob::machine::JFABase::resize, (arg("self"), arg("ru"), arg("rv")), "Reset the dimensionality of the subspaces U and V.") .add_property("ubm", &bob::machine::JFABase::getUbm, &bob::machine::JFABase::setUbm, "The UBM GMM attached to this Joint Factor Analysis model") .add_property("u", make_function(&bob::machine::JFABase::getU, return_value_policy<copy_const_reference>()), &py_jfa_setU, "The subspace U for within-class variations") @@ -173,15 +253,16 @@ void bind_machine_jfa() .add_property("dim_rv", &bob::machine::JFABase::getDimRv, "The dimensionality of the between-class variations subspace (rank of V)") ; - class_<bob::machine::JFAMachine, boost::shared_ptr<bob::machine::JFAMachine>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("JFAMachine", "A JFAMachine. An attached JFABase should be provided for Joint Factor Analysis. The JFAMachine carries information about the speaker factors y and z, whereas a JFABase carries information about the matrices U, V and D.\n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", init<const boost::shared_ptr<bob::machine::JFABase> >((arg("self"), arg("jfa_base")), "Builds a new JFAMachine.")) + class_<bob::machine::JFAMachine, boost::shared_ptr<bob::machine::JFAMachine>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("JFAMachine", "A JFAMachine. An attached JFABase should be provided for Joint Factor Analysis. The JFAMachine carries information about the speaker factors y and z, whereas a JFABase carries information about the matrices U, V and D.\n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", no_init) + .def("__init__", boost::python::make_constructor(&jm_init), "Constructs a new JFAMachine from a configuration file.") .def(init<>((arg("self")), "Constructs a 1x1 JFAMachine instance. You have to set a JFABase afterwards.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new JFAMachine from a configuration file.")) + .def(init<const boost::shared_ptr<bob::machine::JFABase> >((arg("self"), arg("jfa_base")), "Builds a new JFAMachine.")) .def(init<const bob::machine::JFAMachine&>((arg("self"), arg("machine")), "Copy constructs a JFAMachine")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::JFAMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this JFABase with the 'other' one to be approximately the same.") - .def("load", &bob::machine::JFAMachine::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::JFAMachine::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &jm_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &jm_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .def("estimate_x", &py_jfa_estimateX, (arg("self"), arg("stats"), arg("x")), "Estimates the session offset x (LPT assumption) given GMM statistics.") .def("estimate_ux", &py_jfa_estimateUx, (arg("self"), arg("stats"), arg("ux")), "Estimates Ux (LPT assumption) given GMM statistics.") .def("forward_ux", &py_jfa_forwardUx, (arg("self"), arg("stats"), arg("ux")), "Processes the GMM statistics and Ux to return a score.") @@ -196,15 +277,16 @@ void bind_machine_jfa() .add_property("dim_rv", &bob::machine::JFAMachine::getDimRv, "The dimensionality of the between-class variations subspace (rank of V)") ; - class_<bob::machine::ISVBase, boost::shared_ptr<bob::machine::ISVBase>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("ISVBase", "An ISVBase instance can be seen as a container for U and D when performing Joint Factor Analysis (ISV). \n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", init<const boost::shared_ptr<bob::machine::GMMMachine>, optional<const size_t> >((arg("self"), arg("ubm"), arg("ru")=1), "Builds a new ISVBase.")) + class_<bob::machine::ISVBase, boost::shared_ptr<bob::machine::ISVBase>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("ISVBase", "An ISVBase instance can be seen as a container for U and D when performing Joint Factor Analysis (ISV). \n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", no_init) + .def("__init__", boost::python::make_constructor(&ib_init), "Constructs a new ISVBaseMachine from a configuration file.") + .def(init<const boost::shared_ptr<bob::machine::GMMMachine>, optional<const size_t> >((arg("self"), arg("ubm"), arg("ru")=1), "Builds a new ISVBase.")) .def(init<>((arg("self")), "Constructs a 1 ISVBase instance. You have to set a UBM GMM and resize the U and D subspaces afterwards.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new ISVBaseMachine from a configuration file.")) .def(init<const bob::machine::ISVBase&>((arg("self"), arg("machine")), "Copy constructs an ISVBase")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::ISVBase::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this ISVBase with the 'other' one to be approximately the same.") - .def("load", &bob::machine::ISVBase::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::ISVBase::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &ib_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &ib_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .def("resize", &bob::machine::ISVBase::resize, (arg("self"), arg("ru")), "Reset the dimensionality of the subspaces U.") .add_property("ubm", &bob::machine::ISVBase::getUbm, &bob::machine::ISVBase::setUbm, "The UBM GMM attached to this Joint Factor Analysis model") .add_property("u", make_function(&bob::machine::ISVBase::getU, return_value_policy<copy_const_reference>()), &py_isv_setU, "The subspace U for within-class variations") @@ -215,15 +297,16 @@ void bind_machine_jfa() .add_property("dim_ru", &bob::machine::ISVBase::getDimRu, "The dimensionality of the within-class variations subspace (rank of U)") ; - class_<bob::machine::ISVMachine, boost::shared_ptr<bob::machine::ISVMachine>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("ISVMachine", "An ISVMachine. An attached ISVBase should be provided for Inter-session Variability Modelling. The ISVMachine carries information about the speaker factors z, whereas a ISVBase carries information about the matrices U and D. \n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", init<const boost::shared_ptr<bob::machine::ISVBase> >((arg("self"), arg("isv_base")), "Builds a new ISVMachine.")) + class_<bob::machine::ISVMachine, boost::shared_ptr<bob::machine::ISVMachine>, bases<bob::machine::Machine<bob::machine::GMMStats, double> > >("ISVMachine", "An ISVMachine. An attached ISVBase should be provided for Inter-session Variability Modelling. The ISVMachine carries information about the speaker factors z, whereas a ISVBase carries information about the matrices U and D. \n\nReferences:\n[1] 'Explicit Modelling of Session Variability for Speaker Verification', R. Vogt, S. Sridharan, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38\n[2] 'Session Variability Modelling for Face Authentication', C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel, IET Biometrics, 2013", no_init) + .def("__init__", boost::python::make_constructor(&im_init), "Constructs a new ISVMachine from a configuration file.") .def(init<>((arg("self")), "Constructs a 1 ISVMachine instance. You have to set a ISVBase afterwards.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new ISVMachine from a configuration file.")) + .def(init<const boost::shared_ptr<bob::machine::ISVBase> >((arg("self"), arg("isv_base")), "Builds a new ISVMachine.")) .def(init<const bob::machine::ISVMachine&>((arg("self"), arg("machine")), "Copy constructs an ISVMachine")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::ISVMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this ISVBase with the 'other' one to be approximately the same.") - .def("load", &bob::machine::ISVMachine::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::ISVMachine::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &im_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &im_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .def("estimate_x", &py_isv_estimateX, (arg("self"), arg("stats"), arg("x")), "Estimates the session offset x (LPT assumption) given GMM statistics.") .def("estimate_ux", &py_isv_estimateUx, (arg("self"), arg("stats"), arg("ux")), "Estimates Ux (LPT assumption) given GMM statistics.") .def("forward_ux", &py_isv_forwardUx, (arg("self"), arg("stats"), arg("ux")), "Processes the GMM statistics and Ux to return a score.") diff --git a/bob/learn/misc/old/jfa_trainer.cc b/bob/learn/misc/old/jfa_trainer.cc index 004c991767a5ae780a5d964400a650e26fc6c1f9..771f16e018e6fd75f6e8784837638baeb28b87d4 100644 --- a/bob/learn/misc/old/jfa_trainer.cc +++ b/bob/learn/misc/old/jfa_trainer.cc @@ -9,7 +9,7 @@ #include "ndarray.h" #include <boost/python/stl_iterator.hpp> -#include <bob/trainer/JFATrainer.h> +#include <bob.learn.misc/JFATrainer.h> using namespace boost::python; diff --git a/bob/learn/misc/old/kmeans.cc b/bob/learn/misc/old/kmeans.cc index 6ba49f79103f9e5ed85ca18c6bb46fd8fb5fc480..bbac7e560ceed10264a8c61ffa169f2a8a565ccc 100644 --- a/bob/learn/misc/old/kmeans.cc +++ b/bob/learn/misc/old/kmeans.cc @@ -5,17 +5,21 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/KMeansMachine.h> +#include <bob.learn.misc/KMeansMachine.h> using namespace boost::python; static tuple py_getVariancesAndWeightsForEachCluster(const bob::machine::KMeansMachine& machine, bob::python::const_ndarray ar) { size_t n_means = machine.getNMeans(); size_t n_inputs = machine.getNInputs(); - bob::python::ndarray variances(bob::core::array::t_float64, n_means, n_inputs); - bob::python::ndarray weights(bob::core::array::t_float64, n_means); + bob::python::ndarray variances(bob::io::base::array::t_float64, n_means, n_inputs); + bob::python::ndarray weights(bob::io::base::array::t_float64, n_means); blitz::Array<double,2> variances_ = variances.bz<double,2>(); blitz::Array<double,1> weights_ = weights.bz<double,1>(); machine.getVariancesAndWeightsForEachCluster(ar.bz<double,2>(), variances_, weights_); @@ -41,7 +45,7 @@ static void py_getVariancesAndWeightsForEachClusterFin(const bob::machine::KMean } static object py_getMean(const bob::machine::KMeansMachine& kMeansMachine, const size_t i) { - bob::python::ndarray mean(bob::core::array::t_float64, kMeansMachine.getNInputs()); + bob::python::ndarray mean(bob::io::base::array::t_float64, kMeansMachine.getNInputs()); blitz::Array<double,1> mean_ = mean.bz<double,1>(); kMeansMachine.getMean(i, mean_); return mean.self(); @@ -77,6 +81,25 @@ static void py_setCacheMeans(bob::machine::KMeansMachine& machine, bob::python:: machine.setCacheMeans(cache_means.bz<double,2>()); } + +static boost::shared_ptr<bob::machine::KMeansMachine> _init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::KMeansMachine>(new bob::machine::KMeansMachine(*hdf5->f)); +} + +static void _load(bob::machine::KMeansMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void _save(const bob::machine::KMeansMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + void bind_machine_kmeans() { class_<bob::machine::KMeansMachine, boost::shared_ptr<bob::machine::KMeansMachine>, @@ -84,9 +107,9 @@ void bind_machine_kmeans() "This class implements a k-means classifier.\n" "See Section 9.1 of Bishop, \"Pattern recognition and machine learning\", 2006", init<>((arg("self")))) + .def("__init__", boost::python::make_constructor(&_init)) .def(init<const size_t, const size_t>((arg("self"), arg("n_means"), arg("n_inputs")))) .def(init<bob::machine::KMeansMachine&>((arg("self"), arg("other")))) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")))) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::KMeansMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this KMeansMachine with the 'other' one to be approximately the same.") @@ -116,8 +139,8 @@ void bind_machine_kmeans() .def("__get_variances_and_weights_for_each_cluster_fin__", &py_getVariancesAndWeightsForEachClusterFin, (arg("self"), arg("variances"), arg("weights")), "For the parallel version of get_variances_and_weights_for_each_cluster()\n" "Finalization step") - .def("load", &bob::machine::KMeansMachine::load, (arg("self"), arg("config")), "Load from a Configuration") - .def("save", &bob::machine::KMeansMachine::save, (arg("self"), arg("config")), "Save to a Configuration") + .def("load", &_load, (arg("self"), arg("config")), "Load from a Configuration") + .def("save", &_save, (arg("self"), arg("config")), "Save to a Configuration") .def(self_ns::str(self_ns::self)) ; } diff --git a/bob/learn/misc/old/kmeans_trainer.cc b/bob/learn/misc/old/kmeans_trainer.cc index 99787fa167643762ff2f73129bbd831f2b69fa0e..2e2cb21cea7e948dae07bc09d4b72559542d0589 100644 --- a/bob/learn/misc/old/kmeans_trainer.cc +++ b/bob/learn/misc/old/kmeans_trainer.cc @@ -6,22 +6,22 @@ */ #include "ndarray.h" -#include <bob/trainer/KMeansTrainer.h> +#include <bob.learn.misc/KMeansTrainer.h> using namespace boost::python; typedef bob::trainer::EMTrainer<bob::machine::KMeansMachine, blitz::Array<double,2> > EMTrainerKMeansBase; static void py_setZeroethOrderStats(bob::trainer::KMeansTrainer& op, bob::python::const_ndarray stats) { - const bob::core::array::typeinfo& info = stats.type(); - if(info.dtype != bob::core::array::t_float64 || info.nd != 1) + const bob::io::base::array::typeinfo& info = stats.type(); + if(info.dtype != bob::io::base::array::t_float64 || info.nd != 1) PYTHON_ERROR(TypeError, "cannot set array of type '%s'", info.str().c_str()); op.setZeroethOrderStats(stats.bz<double,1>()); } static void py_setFirstOrderStats(bob::trainer::KMeansTrainer& op, bob::python::const_ndarray stats) { - const bob::core::array::typeinfo& info = stats.type(); - if(info.dtype != bob::core::array::t_float64 || info.nd != 2) + const bob::io::base::array::typeinfo& info = stats.type(); + if(info.dtype != bob::io::base::array::t_float64 || info.nd != 2) PYTHON_ERROR(TypeError, "cannot set array of type '%s'", info.str().c_str()); op.setFirstOrderStats(stats.bz<double,2>()); } diff --git a/bob/learn/misc/old/linearscoring.cc b/bob/learn/misc/old/linearscoring.cc index ac80bf395ba2556cf54d6cab02730335f37b8314..01ecc32da5a44accb2520902e09ac94b9a3a7121 100644 --- a/bob/learn/misc/old/linearscoring.cc +++ b/bob/learn/misc/old/linearscoring.cc @@ -8,7 +8,7 @@ #include "ndarray.h" #include <vector> -#include <bob/machine/LinearScoring.h> +#include <bob.learn.misc/LinearScoring.h> #include <boost/python/stl_iterator.hpp> using namespace boost::python; @@ -55,7 +55,7 @@ static object linearScoring1(object models, std::vector<boost::shared_ptr<const bob::machine::GMMStats> > test_stats_c; convertGMMStatsList(test_stats, test_stats_c); - bob::python::ndarray ret(bob::core::array::t_float64, models_c.size(), test_stats_c.size()); + bob::python::ndarray ret(bob::io::base::array::t_float64, models_c.size(), test_stats_c.size()); blitz::Array<double,2> ret_ = ret.bz<double,2>(); if (test_channelOffset.ptr() == Py_None || len(test_channelOffset) == 0) { //list is empty bob::machine::linearScoring(models_c, ubm_mean_, ubm_variance_, test_stats_c, frame_length_normalisation, ret_); @@ -80,7 +80,7 @@ static object linearScoring2(object models, std::vector<boost::shared_ptr<const bob::machine::GMMStats> > test_stats_c; convertGMMStatsList(test_stats, test_stats_c); - bob::python::ndarray ret(bob::core::array::t_float64, models_c.size(), test_stats_c.size()); + bob::python::ndarray ret(bob::io::base::array::t_float64, models_c.size(), test_stats_c.size()); blitz::Array<double,2> ret_ = ret.bz<double,2>(); if (test_channelOffset.ptr() == Py_None || len(test_channelOffset) == 0) { //list is empty bob::machine::linearScoring(models_c, ubm, test_stats_c, frame_length_normalisation, ret_); diff --git a/bob/learn/misc/old/machine.cc b/bob/learn/misc/old/machine.cc index 2f6f212abdc2f17a2a843f00418eeda086198810..5a10dce37dfd558b96211bc8f88cb0179d2afa86 100644 --- a/bob/learn/misc/old/machine.cc +++ b/bob/learn/misc/old/machine.cc @@ -6,7 +6,7 @@ */ #include "ndarray.h" -#include <bob/machine/Machine.h> +#include <bob.learn.misc/Machine.h> using namespace boost::python; diff --git a/bob/learn/misc/old/main.cc b/bob/learn/misc/old/main.cc index e31f6d89bcc8b87b03d02d9ba6958935c3e26432..119d403f14cc274140857c91030511e416338da6 100644 --- a/bob/learn/misc/old/main.cc +++ b/bob/learn/misc/old/main.cc @@ -7,6 +7,14 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#ifdef NO_IMPORT_ARRAY +#undef NO_IMPORT_ARRAY +#endif + +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" /** extra bindings required for compatibility **/ @@ -14,7 +22,6 @@ void bind_core_tinyvector(); void bind_core_ndarray_numpy(); void bind_core_bz_numpy(); void bind_core_random(); -void bind_io_hdf5(); /** machine bindings **/ void bind_machine_base(); @@ -41,6 +48,18 @@ void bind_trainer_bic(); BOOST_PYTHON_MODULE(_old_library) { + if (import_bob_blitz() < 0) { + PyErr_Print(); + PyErr_Format(PyExc_ImportError, "cannot import `bob.blitz'"); + return; + } + + if (import_bob_io_base() < 0) { + PyErr_Print(); + PyErr_Format(PyExc_ImportError, "cannot import `bob.io.base'"); + return; + } + boost::python::docstring_options docopt(true, true, false); bob::python::setup_python("miscelaneous machines and trainers not yet ported into the new framework"); @@ -49,7 +68,6 @@ BOOST_PYTHON_MODULE(_old_library) { bind_core_tinyvector(); bind_core_ndarray_numpy(); bind_core_bz_numpy(); - bind_io_hdf5(); bind_core_random(); /** machine bindings **/ diff --git a/bob/learn/misc/old/ndarray.cc b/bob/learn/misc/old/ndarray.cc index 9fc03f926baae4aeaf62f9ea9c67f5f0123fd3db..d5f728f523343ed93292c8c6fbcd6ffde127f677 100644 --- a/bob/learn/misc/old/ndarray.cc +++ b/bob/learn/misc/old/ndarray.cc @@ -12,9 +12,9 @@ #include <stdexcept> #include <dlfcn.h> -#ifdef NO_IMPORT_ARRAY -#undef NO_IMPORT_ARRAY -#endif +//#ifdef NO_IMPORT_ARRAY +//#undef NO_IMPORT_ARRAY +//#endif #include "ndarray.h" #define TP_ARRAY(x) ((PyArrayObject*)x.ptr()) @@ -24,16 +24,16 @@ #define NUMPY16_API 0x00000006 #define NUMPY14_API 0x00000004 -#include <bob/core/logging.h> +#include <bob.core/logging.h> #if PY_VERSION_HEX >= 0x03000000 static void* wrap_import_array() { - import_array(); +// import_array(); return 0; } #else static void wrap_import_array() { - import_array(); +// import_array(); return; } #endif @@ -80,83 +80,83 @@ void bob::python::setup_python(const char* module_docstring) { * Dtype (PyArray_Descr) manipulations * ***************************************************************************/ -int bob::python::type_to_num(bob::core::array::ElementType type) { +int bob::python::type_to_num(bob::io::base::array::ElementType type) { switch(type) { - case bob::core::array::t_bool: + case bob::io::base::array::t_bool: return NPY_BOOL; - case bob::core::array::t_int8: + case bob::io::base::array::t_int8: return NPY_INT8; - case bob::core::array::t_int16: + case bob::io::base::array::t_int16: return NPY_INT16; - case bob::core::array::t_int32: + case bob::io::base::array::t_int32: return NPY_INT32; - case bob::core::array::t_int64: + case bob::io::base::array::t_int64: return NPY_INT64; - case bob::core::array::t_uint8: + case bob::io::base::array::t_uint8: return NPY_UINT8; - case bob::core::array::t_uint16: + case bob::io::base::array::t_uint16: return NPY_UINT16; - case bob::core::array::t_uint32: + case bob::io::base::array::t_uint32: return NPY_UINT32; - case bob::core::array::t_uint64: + case bob::io::base::array::t_uint64: return NPY_UINT64; - case bob::core::array::t_float32: + case bob::io::base::array::t_float32: return NPY_FLOAT32; - case bob::core::array::t_float64: + case bob::io::base::array::t_float64: return NPY_FLOAT64; #ifdef NPY_FLOAT128 - case bob::core::array::t_float128: + case bob::io::base::array::t_float128: return NPY_FLOAT128; #endif - case bob::core::array::t_complex64: + case bob::io::base::array::t_complex64: return NPY_COMPLEX64; - case bob::core::array::t_complex128: + case bob::io::base::array::t_complex128: return NPY_COMPLEX128; #ifdef NPY_COMPLEX256 - case bob::core::array::t_complex256: + case bob::io::base::array::t_complex256: return NPY_COMPLEX256; #endif default: - PYTHON_ERROR(TypeError, "unsupported C++ element type (%s)", bob::core::array::stringize(type)); + PYTHON_ERROR(TypeError, "unsupported C++ element type (%s)", bob::io::base::array::stringize(type)); } } -static bob::core::array::ElementType signed_integer_type(int bits) { +static bob::io::base::array::ElementType signed_integer_type(int bits) { switch(bits) { case 8: - return bob::core::array::t_int8; + return bob::io::base::array::t_int8; case 16: - return bob::core::array::t_int16; + return bob::io::base::array::t_int16; case 32: - return bob::core::array::t_int32; + return bob::io::base::array::t_int32; case 64: - return bob::core::array::t_int64; + return bob::io::base::array::t_int64; default: PYTHON_ERROR(TypeError, "unsupported signed integer element type with %d bits", bits); } } -static bob::core::array::ElementType unsigned_integer_type(int bits) { +static bob::io::base::array::ElementType unsigned_integer_type(int bits) { switch(bits) { case 8: - return bob::core::array::t_uint8; + return bob::io::base::array::t_uint8; case 16: - return bob::core::array::t_uint16; + return bob::io::base::array::t_uint16; case 32: - return bob::core::array::t_uint32; + return bob::io::base::array::t_uint32; case 64: - return bob::core::array::t_uint64; + return bob::io::base::array::t_uint64; default: PYTHON_ERROR(TypeError, "unsupported unsigned integer element type with %d bits", bits); } } -bob::core::array::ElementType bob::python::num_to_type(int num) { +bob::io::base::array::ElementType bob::python::num_to_type(int num) { switch(num) { case NPY_BOOL: - return bob::core::array::t_bool; + return bob::io::base::array::t_bool; //signed integers case NPY_BYTE: @@ -184,22 +184,22 @@ bob::core::array::ElementType bob::python::num_to_type(int num) { //floats case NPY_FLOAT32: - return bob::core::array::t_float32; + return bob::io::base::array::t_float32; case NPY_FLOAT64: - return bob::core::array::t_float64; + return bob::io::base::array::t_float64; #ifdef NPY_FLOAT128 case NPY_FLOAT128: - return bob::core::array::t_float128; + return bob::io::base::array::t_float128; #endif //complex case NPY_COMPLEX64: - return bob::core::array::t_complex64; + return bob::io::base::array::t_complex64; case NPY_COMPLEX128: - return bob::core::array::t_complex128; + return bob::io::base::array::t_complex128; #ifdef NPY_COMPLEX256 case NPY_COMPLEX256: - return bob::core::array::t_complex256; + return bob::io::base::array::t_complex256; #endif default: @@ -246,7 +246,7 @@ template <> int bob::python::ctype_to_num<std::complex<long double> >(void) #endif //! @endcond SKIP_DOXYGEN_WARNINGS -bob::core::array::ElementType bob::python::array_to_type(const boost::python::numeric::array& a) { +bob::io::base::array::ElementType bob::python::array_to_type(const boost::python::numeric::array& a) { return bob::python::num_to_type(PyArray_DESCR(TP_ARRAY(a))->type_num); } @@ -279,8 +279,8 @@ bob::python::dtype::dtype(int typenum) { m_self = boost::python::object(hdl); } -bob::python::dtype::dtype(bob::core::array::ElementType eltype) { - if (eltype != bob::core::array::t_unknown) { +bob::python::dtype::dtype(bob::io::base::array::ElementType eltype) { + if (eltype != bob::io::base::array::t_unknown) { PyArray_Descr* tmp = PyArray_DescrFromType(bob::python::type_to_num(eltype)); boost::python::handle<> hdl(boost::python::borrowed((PyObject*)tmp)); m_self = boost::python::object(hdl); @@ -305,12 +305,12 @@ bool bob::python::dtype::has_native_byteorder() const { return TPY_ISNONE(m_self)? false : (PyArray_EquivByteorders(TP_DESCR(m_self)->byteorder, NPY_NATIVE) || TP_DESCR(m_self)->elsize == 1); } -bool bob::python::dtype::has_type(bob::core::array::ElementType _eltype) const { +bool bob::python::dtype::has_type(bob::io::base::array::ElementType _eltype) const { return eltype() == _eltype; } -bob::core::array::ElementType bob::python::dtype::eltype() const { - return TPY_ISNONE(m_self)? bob::core::array::t_unknown : +bob::io::base::array::ElementType bob::python::dtype::eltype() const { + return TPY_ISNONE(m_self)? bob::io::base::array::t_unknown : bob::python::num_to_type(TP_DESCR(m_self)->type_num); } @@ -334,14 +334,14 @@ PyArray_Descr* bob::python::dtype::descr() { * Free methods * ****************************************************************************/ -void bob::python::typeinfo_ndarray_ (const boost::python::object& o, bob::core::array::typeinfo& i) { +void bob::python::typeinfo_ndarray_ (const boost::python::object& o, bob::io::base::array::typeinfo& i) { PyArrayObject* npy = TP_ARRAY(o); npy_intp strides[NPY_MAXDIMS]; for (int k=0; k<PyArray_NDIM(npy); ++k) strides[k] = PyArray_STRIDES(npy)[k]/PyArray_DESCR(npy)->elsize; i.set<npy_intp>(bob::python::num_to_type(PyArray_DESCR(npy)->type_num), PyArray_NDIM(npy), PyArray_DIMS(npy), strides); } -void bob::python::typeinfo_ndarray (const boost::python::object& o, bob::core::array::typeinfo& i) { +void bob::python::typeinfo_ndarray (const boost::python::object& o, bob::io::base::array::typeinfo& i) { if (!PyArray_Check(o.ptr())) { throw std::runtime_error("invalid input: cannot extract typeinfo object from anything else than ndarray"); } @@ -423,7 +423,7 @@ static int _GetArrayParamsFromObject(PyObject* op, } #endif -bob::python::convert_t bob::python::convertible(boost::python::object array_like, bob::core::array::typeinfo& info, +bob::python::convert_t bob::python::convertible(boost::python::object array_like, bob::io::base::array::typeinfo& info, bool writeable, bool behaved) { int ndim = 0; @@ -471,7 +471,7 @@ bob::python::convert_t bob::python::convertible(boost::python::object array_like } bob::python::convert_t bob::python::convertible_to (boost::python::object array_like, - const bob::core::array::typeinfo& info, bool writeable, bool behaved) { + const bob::io::base::array::typeinfo& info, bool writeable, bool behaved) { bob::python::dtype req_dtype(info.dtype); @@ -783,15 +783,15 @@ bob::python::py_array::py_array(boost::python::object o, boost::python::object _ m_ptr = static_cast<void*>(PyArray_DATA(TP_ARRAY(mine))); } -bob::python::py_array::py_array(const bob::core::array::interface& other) { +bob::python::py_array::py_array(const bob::io::base::array::interface& other) { set(other); } -bob::python::py_array::py_array(boost::shared_ptr<bob::core::array::interface> other) { +bob::python::py_array::py_array(boost::shared_ptr<bob::io::base::array::interface> other) { set(other); } -bob::python::py_array::py_array(const bob::core::array::typeinfo& info) { +bob::python::py_array::py_array(const bob::io::base::array::typeinfo& info) { set(info); } @@ -801,7 +801,7 @@ bob::python::py_array::~py_array() { /** * Wrap a C-style pointer with a PyArrayObject */ -static boost::python::object wrap_data (void* data, const bob::core::array::typeinfo& ti, +static boost::python::object wrap_data (void* data, const bob::io::base::array::typeinfo& ti, bool writeable=true) { npy_intp shape[NPY_MAXDIMS]; @@ -847,13 +847,13 @@ static boost::python::object copy_array (const boost::python::object& array) { /** * Copies a data pointer and type into a new numpy array. */ -static boost::python::object copy_data (const void* data, const bob::core::array::typeinfo& ti) { +static boost::python::object copy_data (const void* data, const bob::io::base::array::typeinfo& ti) { boost::python::object wrapped = wrap_data(const_cast<void*>(data), ti); boost::python::object retval = copy_array (wrapped); return retval; } -void bob::python::py_array::set(const bob::core::array::interface& other) { +void bob::python::py_array::set(const bob::io::base::array::interface& other) { TDEBUG1("[non-optimal] buffer copying operation being performed for " << other.type().str()); @@ -872,7 +872,7 @@ void bob::python::py_array::set(const bob::core::array::interface& other) { m_is_numpy = true; } -void bob::python::py_array::set(boost::shared_ptr<bob::core::array::interface> other) { +void bob::python::py_array::set(boost::shared_ptr<bob::io::base::array::interface> other) { m_type = other->type(); m_is_numpy = false; m_ptr = other->ptr(); @@ -882,7 +882,7 @@ void bob::python::py_array::set(boost::shared_ptr<bob::core::array::interface> o /** * Creates a new numpy array from a bob::io::typeinfo object. */ -static boost::python::object new_from_type (const bob::core::array::typeinfo& ti) { +static boost::python::object new_from_type (const bob::io::base::array::typeinfo& ti) { npy_intp shape[NPY_MAXDIMS]; npy_intp stride[NPY_MAXDIMS]; for (size_t k=0; k<ti.nd; ++k) { @@ -896,7 +896,7 @@ static boost::python::object new_from_type (const bob::core::array::typeinfo& ti return retval; } -void bob::python::py_array::set (const bob::core::array::typeinfo& req) { +void bob::python::py_array::set (const bob::io::base::array::typeinfo& req) { if (m_type.is_compatible(req)) return; ///< nothing to do! TDEBUG1("[non-optimal?] buffer re-size being performed from " << m_type.str() @@ -943,7 +943,7 @@ static void DeleteSharedPointer (PyObject* ptr) { } #endif -static boost::python::object make_readonly (const void* data, const bob::core::array::typeinfo& ti, +static boost::python::object make_readonly (const void* data, const bob::io::base::array::typeinfo& ti, boost::shared_ptr<const void> owner) { boost::python::object retval = wrap_data(const_cast<void*>(data), ti, false); @@ -995,13 +995,13 @@ bob::python::ndarray::ndarray(boost::python::object array_like) : px(new bob::python::py_array(array_like, boost::python::object())) { } -bob::python::ndarray::ndarray(const bob::core::array::typeinfo& info) +bob::python::ndarray::ndarray(const bob::io::base::array::typeinfo& info) : px(new bob::python::py_array(info)) { } bob::python::ndarray::~ndarray() { } -const bob::core::array::typeinfo& bob::python::ndarray::type() const { +const bob::io::base::array::typeinfo& bob::python::ndarray::type() const { return px->type(); } diff --git a/bob/learn/misc/old/ndarray.h b/bob/learn/misc/old/ndarray.h index 5dbb899dacb3de6e654a4b9cff5f9fc86d0b56d5..4090a0f5807083dcd5326b6a5085d5d52536b9ea 100644 --- a/bob/learn/misc/old/ndarray.h +++ b/bob/learn/misc/old/ndarray.h @@ -4,7 +4,7 @@ * @author André Anjos <andre.anjos@idiap.ch> * * @brief A boost::python extension object that plays the role of a NumPy - * ndarray (PyArrayObject*) and bob::core::array::interface at the same time. + * ndarray (PyArrayObject*) and bob::io::base::array::interface at the same time. * * Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland */ @@ -20,8 +20,8 @@ #include <numpy/arrayobject.h> #include "exception.h" -#include <bob/core/array.h> -#include <bob/core/cast.h> +#include <bob.io.base/array.h> +#include <bob.core/cast.h> #include <blitz/array.h> #include <stdint.h> @@ -63,12 +63,12 @@ namespace bob { namespace python { * @brief A generic method to convert from ndarray type_num to bob's * ElementType */ - bob::core::array::ElementType num_to_type(int num); + bob::io::base::array::ElementType num_to_type(int num); /** * @brief A method to retrieve the type of element of an array */ - bob::core::array::ElementType array_to_type(const boost::python::numeric::array& a); + bob::io::base::array::ElementType array_to_type(const boost::python::numeric::array& a); /** * @brief Retrieves the number of dimensions in an array @@ -79,7 +79,7 @@ namespace bob { namespace python { * @brief Converts from C/C++ type to ndarray type_num. */ template <typename T> int ctype_to_num(void) { - PYTHON_ERROR(TypeError, "unsupported C/C++ type (%s)", bob::core::array::stringize<T>()); + PYTHON_ERROR(TypeError, "unsupported C/C++ type (%s)", bob::io::base::array::stringize<T>()); } // The C/C++ types we support should be declared here. @@ -108,7 +108,7 @@ namespace bob { namespace python { /** * @brief Converts from bob's Element type to ndarray type_num */ - int type_to_num(bob::core::array::ElementType type); + int type_to_num(bob::io::base::array::ElementType type); /** * @brief Handles conversion checking possibilities @@ -126,14 +126,14 @@ namespace bob { namespace python { * ndarray. An exception may be thrown otherwise. */ void typeinfo_ndarray (const boost::python::object& o, - bob::core::array::typeinfo& i); + bob::io::base::array::typeinfo& i); /** * @brief This is the same as above, but does not run any check on the input * object "o". */ void typeinfo_ndarray_ (const boost::python::object& o, - bob::core::array::typeinfo& i); + bob::io::base::array::typeinfo& i); /** * @brief Checks if an array-like object is convertible to become a NumPy @@ -172,7 +172,7 @@ namespace bob { namespace python { * be able to implement write-back. */ convert_t convertible(boost::python::object array_like, - bob::core::array::typeinfo& info, bool writeable=true, + bob::io::base::array::typeinfo& info, bool writeable=true, bool behaved=true); /** @@ -187,7 +187,7 @@ namespace bob { namespace python { * 3. If 2. holds, shape values are checked if has_valid_shape() is 'true' */ convert_t convertible_to (boost::python::object array_like, - const bob::core::array::typeinfo& info, bool writeable=true, + const bob::io::base::array::typeinfo& info, bool writeable=true, bool behaved=true); /** @@ -228,7 +228,7 @@ namespace bob { namespace python { /** * @brief Builds a new dtype object from a bob element type */ - dtype(bob::core::array::ElementType eltype); + dtype(bob::io::base::array::ElementType eltype); /** * @brief Copy constructor @@ -254,12 +254,12 @@ namespace bob { namespace python { * @brief Some checks */ bool has_native_byteorder() const; ///< byte order is native - bool has_type(bob::core::array::ElementType eltype) const; ///< matches + bool has_type(bob::io::base::array::ElementType eltype) const; ///< matches /** * @brief Returns the current element type */ - bob::core::array::ElementType eltype() const; + bob::io::base::array::ElementType eltype() const; /** * @brief Returns the current type num or -1, if I'm None @@ -293,7 +293,7 @@ namespace bob { namespace python { }; - class py_array: public bob::core::array::interface { + class py_array: public bob::io::base::array::interface { public: //api @@ -313,44 +313,44 @@ namespace bob { namespace python { /** * @brief Builds a new array copying the data of an existing buffer. */ - py_array(const bob::core::array::interface& buffer); + py_array(const bob::io::base::array::interface& buffer); /** * @brief Builds a new array by referring to the data of an existing * buffer. */ - py_array(boost::shared_ptr<bob::core::array::interface> buffer); + py_array(boost::shared_ptr<bob::io::base::array::interface> buffer); /** * @brief Builds a new array from scratch using the typeinfo. This array * will be a NumPy ndarray internally. */ - py_array(const bob::core::array::typeinfo& info); + py_array(const bob::io::base::array::typeinfo& info); template <typename T> - py_array(bob::core::array::ElementType t, T d0) { - set(bob::core::array::typeinfo(t, (T)1, &d0)); + py_array(bob::io::base::array::ElementType t, T d0) { + set(bob::io::base::array::typeinfo(t, (T)1, &d0)); } template <typename T> - py_array(bob::core::array::ElementType t, T d0, T d1) { + py_array(bob::io::base::array::ElementType t, T d0, T d1) { T shape[2] = {d0, d1}; - set(bob::core::array::typeinfo(t, (T)2, &shape[0])); + set(bob::io::base::array::typeinfo(t, (T)2, &shape[0])); } template <typename T> - py_array(bob::core::array::ElementType t, T d0, T d1, T d2) { + py_array(bob::io::base::array::ElementType t, T d0, T d1, T d2) { T shape[3] = {d0, d1, d2}; - set(bob::core::array::typeinfo(t, (T)3, &shape[0])); + set(bob::io::base::array::typeinfo(t, (T)3, &shape[0])); } template <typename T> - py_array(bob::core::array::ElementType t, T d0, T d1, T d2, T d3) { + py_array(bob::io::base::array::ElementType t, T d0, T d1, T d2, T d3) { T shape[4] = {d0, d1, d2, d3}; - set(bob::core::array::typeinfo(t, (T)4, &shape[0])); + set(bob::io::base::array::typeinfo(t, (T)4, &shape[0])); } template <typename T> - py_array(bob::core::array::ElementType t, T d0, T d1, T d2, T d3, T d4) + py_array(bob::io::base::array::ElementType t, T d0, T d1, T d2, T d3, T d4) { T shape[5] = {d0, d1, d2, d3, d4}; - set(bob::core::array::typeinfo(t, (T)5, &shape[0])); + set(bob::io::base::array::typeinfo(t, (T)5, &shape[0])); } /** @@ -361,23 +361,23 @@ namespace bob { namespace python { /** * @brief Copies the data from another buffer. */ - virtual void set(const bob::core::array::interface& buffer); + virtual void set(const bob::io::base::array::interface& buffer); /** * @brief Refers to the data of another buffer. */ - virtual void set(boost::shared_ptr<bob::core::array::interface> buffer); + virtual void set(boost::shared_ptr<bob::io::base::array::interface> buffer); /** * @brief Re-allocates this buffer taking into consideration new * requirements. The internal memory should be considered uninitialized. */ - virtual void set (const bob::core::array::typeinfo& req); + virtual void set (const bob::io::base::array::typeinfo& req); /** * @brief Type information for this buffer. */ - virtual const bob::core::array::typeinfo& type() const { return m_type; } + virtual const bob::io::base::array::typeinfo& type() const { return m_type; } /** * @brief Borrows a reference from the underlying memory. This means @@ -425,7 +425,7 @@ namespace bob { namespace python { private: //representation - bob::core::array::typeinfo m_type; ///< type information + bob::io::base::array::typeinfo m_type; ///< type information void* m_ptr; ///< pointer to the data bool m_is_numpy; ///< true if initiated with a NumPy array boost::shared_ptr<void> m_data; ///< Pointer to the data owner @@ -466,22 +466,22 @@ namespace bob { namespace python { /** * @brief Builds a new array from scratch using a type and shape */ - ndarray(const bob::core::array::typeinfo& info); + ndarray(const bob::io::base::array::typeinfo& info); template <typename T> - ndarray(bob::core::array::ElementType t, T d0) + ndarray(bob::io::base::array::ElementType t, T d0) : px(new py_array(t, d0)) { } template <typename T> - ndarray(bob::core::array::ElementType t, T d0, T d1) + ndarray(bob::io::base::array::ElementType t, T d0, T d1) : px(new py_array(t, d0, d1)) { } template <typename T> - ndarray(bob::core::array::ElementType t, T d0, T d1, T d2) + ndarray(bob::io::base::array::ElementType t, T d0, T d1, T d2) : px(new py_array(t, d0, d1, d2)) { } template <typename T> - ndarray(bob::core::array::ElementType t, T d0, T d1, T d2, T d3) + ndarray(bob::io::base::array::ElementType t, T d0, T d1, T d2, T d3) : px(new py_array(t, d0, d1, d2, d3)) { } template <typename T> - ndarray(bob::core::array::ElementType t, T d0, T d1, T d2, T d3, T d4) + ndarray(bob::io::base::array::ElementType t, T d0, T d1, T d2, T d3, T d4) : px(new py_array(t, d0, d1, d2, d3, d4)) { } /** @@ -492,7 +492,7 @@ namespace bob { namespace python { /** * @brief Returns the type information */ - virtual const bob::core::array::typeinfo& type() const; + virtual const bob::io::base::array::typeinfo& type() const; /** * @brief Returns the underlying python representation. @@ -517,19 +517,19 @@ namespace bob { namespace python { typedef blitz::Array<T,N> array_type; typedef blitz::TinyVector<int,N> shape_type; - const bob::core::array::typeinfo& info = px->type(); + const bob::io::base::array::typeinfo& info = px->type(); if (info.nd != N) { boost::format mesg("cannot wrap numpy.ndarray(%s,%d) as blitz::Array<%s,%s> - dimensions do not match"); - mesg % bob::core::array::stringize(info.dtype) % info.nd; - mesg % bob::core::array::stringize<T>() % N; + mesg % bob::io::base::array::stringize(info.dtype) % info.nd; + mesg % bob::io::base::array::stringize<T>() % N; throw std::runtime_error(mesg.str().c_str()); } - if (info.dtype != bob::core::array::getElementType<T>()) { + if (info.dtype != bob::io::base::array::getElementType<T>()) { boost::format mesg("cannot wrap numpy.ndarray(%s,%d) as blitz::Array<%s,%s> - data type does not match"); - mesg % bob::core::array::stringize(info.dtype) % info.nd; - mesg % bob::core::array::stringize<T>() % N; + mesg % bob::io::base::array::stringize(info.dtype) % info.nd; + mesg % bob::io::base::array::stringize<T>() % N; throw std::runtime_error(mesg.str().c_str()); } @@ -587,16 +587,16 @@ namespace bob { namespace python { * ndarray outlives the blitz::Array<>, in case the data is not copied. */ template <typename T, int N> const blitz::Array<T,N> cast() { - const bob::core::array::typeinfo& info = px->type(); + const bob::io::base::array::typeinfo& info = px->type(); if (info.nd != N) { boost::format mesg("cannot wrap numpy.ndarray(%s,%d) as blitz::Array<%s,%s> - dimensions do not match"); - mesg % bob::core::array::stringize(info.dtype) % info.nd; - mesg % bob::core::array::stringize<T>() % N; + mesg % bob::io::base::array::stringize(info.dtype) % info.nd; + mesg % bob::io::base::array::stringize<T>() % N; throw std::runtime_error(mesg.str().c_str()); } - if (info.dtype == bob::core::array::getElementType<T>()) { + if (info.dtype == bob::io::base::array::getElementType<T>()) { // Type and shape matches, return the shallow copy of the array. return bz<T,N>(); } @@ -605,29 +605,29 @@ namespace bob { namespace python { // call the correct version of the cast function switch(info.dtype){ // boolean types - case bob::core::array::t_bool: return bob::core::array::cast<T>(bz<bool,N>()); + case bob::io::base::array::t_bool: return bob::core::array::cast<T>(bz<bool,N>()); // integral types - case bob::core::array::t_int8: return bob::core::array::cast<T>(bz<int8_t,N>()); - case bob::core::array::t_int16: return bob::core::array::cast<T>(bz<int16_t,N>()); - case bob::core::array::t_int32: return bob::core::array::cast<T>(bz<int32_t,N>()); - case bob::core::array::t_int64: return bob::core::array::cast<T>(bz<int64_t,N>()); + case bob::io::base::array::t_int8: return bob::core::array::cast<T>(bz<int8_t,N>()); + case bob::io::base::array::t_int16: return bob::core::array::cast<T>(bz<int16_t,N>()); + case bob::io::base::array::t_int32: return bob::core::array::cast<T>(bz<int32_t,N>()); + case bob::io::base::array::t_int64: return bob::core::array::cast<T>(bz<int64_t,N>()); // unsigned integral types - case bob::core::array::t_uint8: return bob::core::array::cast<T>(bz<uint8_t,N>()); - case bob::core::array::t_uint16: return bob::core::array::cast<T>(bz<uint16_t,N>()); - case bob::core::array::t_uint32: return bob::core::array::cast<T>(bz<uint32_t,N>()); - case bob::core::array::t_uint64: return bob::core::array::cast<T>(bz<uint64_t,N>()); + case bob::io::base::array::t_uint8: return bob::core::array::cast<T>(bz<uint8_t,N>()); + case bob::io::base::array::t_uint16: return bob::core::array::cast<T>(bz<uint16_t,N>()); + case bob::io::base::array::t_uint32: return bob::core::array::cast<T>(bz<uint32_t,N>()); + case bob::io::base::array::t_uint64: return bob::core::array::cast<T>(bz<uint64_t,N>()); // floating point types - case bob::core::array::t_float32: return bob::core::array::cast<T>(bz<float,N>()); - case bob::core::array::t_float64: return bob::core::array::cast<T>(bz<double,N>()); - case bob::core::array::t_float128: return bob::core::array::cast<T>(bz<long double,N>()); + case bob::io::base::array::t_float32: return bob::core::array::cast<T>(bz<float,N>()); + case bob::io::base::array::t_float64: return bob::core::array::cast<T>(bz<double,N>()); + case bob::io::base::array::t_float128: return bob::core::array::cast<T>(bz<long double,N>()); // complex types - case bob::core::array::t_complex64: return bob::core::array::cast<T>(bz<std::complex<float>,N>()); - case bob::core::array::t_complex128: return bob::core::array::cast<T>(bz<std::complex<double>,N>()); - case bob::core::array::t_complex256: return bob::core::array::cast<T>(bz<std::complex<long double>,N>()); + case bob::io::base::array::t_complex64: return bob::core::array::cast<T>(bz<std::complex<float>,N>()); + case bob::io::base::array::t_complex128: return bob::core::array::cast<T>(bz<std::complex<double>,N>()); + case bob::io::base::array::t_complex256: return bob::core::array::cast<T>(bz<std::complex<long double>,N>()); default: throw std::runtime_error("cast to the given (unknown) data type is not possible yet"); } diff --git a/bob/learn/misc/old/plda.cc b/bob/learn/misc/old/plda.cc index 902954d267627676723d7811fd612bb53d5336fd..0b2536fc9fd23aa4b4e1e6b6ebce1a39327ec0d2 100644 --- a/bob/learn/misc/old/plda.cc +++ b/bob/learn/misc/old/plda.cc @@ -7,8 +7,12 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/PLDAMachine.h> +#include <bob.learn.misc/PLDAMachine.h> using namespace boost::python; @@ -54,7 +58,7 @@ static void py_set_sigma(bob::machine::PLDABase& machine, static double computeLogLikelihood(bob::machine::PLDAMachine& plda, bob::python::const_ndarray samples, bool with_enrolled_samples=true) { - const bob::core::array::typeinfo& info = samples.type(); + const bob::io::base::array::typeinfo& info = samples.type(); switch (info.nd) { case 1: return plda.computeLogLikelihood(samples.bz<double,1>(), with_enrolled_samples); @@ -68,7 +72,7 @@ static double computeLogLikelihood(bob::machine::PLDAMachine& plda, static double plda_forward_sample(bob::machine::PLDAMachine& m, bob::python::const_ndarray samples) { - const bob::core::array::typeinfo& info = samples.type(); + const bob::io::base::array::typeinfo& info = samples.type(); switch (info.nd) { case 1: { @@ -99,17 +103,55 @@ static double py_log_likelihood_point_estimate(bob::machine::PLDABase& plda, BOOST_PYTHON_FUNCTION_OVERLOADS(computeLogLikelihood_overloads, computeLogLikelihood, 2, 3) + +static boost::shared_ptr<bob::machine::PLDABase> b_init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::PLDABase>(new bob::machine::PLDABase(*hdf5->f)); +} + +static void b_load(bob::machine::PLDABase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void b_save(const bob::machine::PLDABase& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + +static boost::shared_ptr<bob::machine::PLDAMachine> m_init(boost::python::object file, boost::shared_ptr<bob::machine::PLDABase> b){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::PLDAMachine>(new bob::machine::PLDAMachine(*hdf5->f, b)); +} + +static void m_load(bob::machine::PLDAMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void m_save(const bob::machine::PLDAMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + void bind_machine_plda() { class_<bob::machine::PLDABase, boost::shared_ptr<bob::machine::PLDABase> >("PLDABase", "A PLDABase can be seen as a container for the subspaces F, G, the diagonal covariance matrix sigma (stored as a 1D array) and the mean vector mu when performing Probabilistic Linear Discriminant Analysis (PLDA). PLDA is a probabilistic model that incorporates components describing both between-class and within-class variations. A PLDABase can be shared between several PLDAMachine that contains class-specific information (information about the enrolment samples).\n\nReferences:\n1. 'A Scalable Formulation of Probabilistic Linear Discriminant Analysis: Applied to Face Recognition', Laurent El Shafey, Chris McCool, Roy Wallace, Sebastien Marcel, TPAMI'2013\n2. 'Probabilistic Linear Discriminant Analysis for Inference About Identity', Prince and Elder, ICCV'2007.\n3. 'Probabilistic Models for Inference about Identity', Li, Fu, Mohammed, Elder and Prince, TPAMI'2012.", init<const size_t, const size_t, const size_t, optional<const double> >((arg("self"), arg("dim_d"), arg("dim_f"), arg("dim_g"), arg("variance_flooring")=0.), "Builds a new PLDABase. dim_d is the dimensionality of the input features, dim_f is the dimensionality of the F subspace and dim_g the dimensionality of the G subspace. The variance flooring threshold is the minimum value that the variance sigma can reach, as this diagonal matrix is inverted.")) .def(init<>((arg("self")), "Constructs a new empty PLDABase.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new PLDABase from a configuration file.")) + .def("__init__", boost::python::make_constructor(&b_init), "Constructs a new PLDABase from a configuration file.") .def(init<const bob::machine::PLDABase&>((arg("self"), arg("machine")), "Copy constructs a PLDABase")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::PLDABase::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this PLDABase with the 'other' one to be approximately the same.") - .def("load", &bob::machine::PLDABase::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") - .def("save", &bob::machine::PLDABase::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") + .def("load", &b_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file.") + .def("save", &b_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file.") .add_property("dim_d", &bob::machine::PLDABase::getDimD, &py_set_dim_d, "Dimensionality of the input feature vectors") .add_property("dim_f", &bob::machine::PLDABase::getDimF, &py_set_dim_f, "Dimensionality of the F subspace/matrix of the PLDA model") .add_property("dim_g", &bob::machine::PLDABase::getDimG, &py_set_dim_g, "Dimensionality of the G subspace/matrix of the PLDA model") @@ -145,13 +187,13 @@ void bind_machine_plda() class_<bob::machine::PLDAMachine, boost::shared_ptr<bob::machine::PLDAMachine> >("PLDAMachine", "A PLDAMachine contains class-specific information (from the enrolment samples) when performing Probabilistic Linear Discriminant Analysis (PLDA). It should be attached to a PLDABase that contains information such as the subspaces F and G.\n\nReferences:\n1. 'A Scalable Formulation of Probabilistic Linear Discriminant Analysis: Applied to Face Recognition', Laurent El Shafey, Chris McCool, Roy Wallace, Sebastien Marcel, TPAMI'2013\n2. 'Probabilistic Linear Discriminant Analysis for Inference About Identity', Prince and Elder, ICCV'2007.\n3. 'Probabilistic Models for Inference about Identity', Li, Fu, Mohammed, Elder and Prince, TPAMI'2012.", init<boost::shared_ptr<bob::machine::PLDABase> >((arg("self"), arg("plda_base")), "Builds a new PLDAMachine. An attached PLDABase should be provided, that can be shared by several PLDAMachine.")) .def(init<>((arg("self")), "Constructs a new empty (invalid) PLDAMachine. A PLDABase should then be set using the 'plda_base' attribute of this object.")) - .def(init<bob::io::HDF5File&, boost::shared_ptr<bob::machine::PLDABase> >((arg("self"), arg("config"), arg("plda_base")), "Constructs a new PLDAMachine from a configuration file (and a PLDABase object).")) + .def("__init__", make_constructor(&m_init), "Constructs a new PLDAMachine from a configuration file (and a PLDABase object).") .def(init<const bob::machine::PLDAMachine&>((arg("self"), arg("machine")), "Copy constructs a PLDAMachine")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::PLDAMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this PLDAMachine with the 'other' one to be approximately the same.") - .def("load", &bob::machine::PLDAMachine::load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file. The PLDABase will not be loaded, and has to be set manually using the 'plda_base' attribute.") - .def("save", &bob::machine::PLDAMachine::save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file. The PLDABase will not be saved, and has to be saved separately, as it can be shared by several PLDAMachines.") + .def("load", &m_load, (arg("self"), arg("config")), "Loads the configuration parameters from a configuration file. The PLDABase will not be loaded, and has to be set manually using the 'plda_base' attribute.") + .def("save", &m_save, (arg("self"), arg("config")), "Saves the configuration parameters to a configuration file. The PLDABase will not be saved, and has to be saved separately, as it can be shared by several PLDAMachines.") .add_property("plda_base", &bob::machine::PLDAMachine::getPLDABase, &bob::machine::PLDAMachine::setPLDABase) .add_property("dim_d", &bob::machine::PLDAMachine::getDimD, "Dimensionality of the input feature vectors") .add_property("dim_f", &bob::machine::PLDAMachine::getDimF, "Dimensionality of the F subspace/matrix of the PLDA model") diff --git a/bob/learn/misc/old/plda_trainer.cc b/bob/learn/misc/old/plda_trainer.cc index bcbc312319285ee6235def6b2fff73f54b7e6270..a5b2cf46c6e506a2abb72d0905d1ebd2c120006a 100644 --- a/bob/learn/misc/old/plda_trainer.cc +++ b/bob/learn/misc/old/plda_trainer.cc @@ -10,8 +10,8 @@ #include "ndarray.h" #include <boost/python/stl_iterator.hpp> -#include <bob/machine/PLDAMachine.h> -#include <bob/trainer/PLDATrainer.h> +#include <bob.learn.misc/PLDAMachine.h> +#include <bob.learn.misc/PLDATrainer.h> using namespace boost::python; diff --git a/bob/learn/misc/old/wiener.cc b/bob/learn/misc/old/wiener.cc index a5cac3580acabb63f8a568c0929a0ee6b7aaf263..4ff72dde4bf9c41cc93673637ba7687719d6994c 100644 --- a/bob/learn/misc/old/wiener.cc +++ b/bob/learn/misc/old/wiener.cc @@ -7,8 +7,12 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> +#include <bob.io.base/api.h> + #include "ndarray.h" -#include <bob/machine/WienerMachine.h> +#include <bob.learn.misc/WienerMachine.h> using namespace boost::python; @@ -29,8 +33,8 @@ static void py_forward1(const bob::machine::WienerMachine& m, static object py_forward2(const bob::machine::WienerMachine& m, bob::python::const_ndarray input) { - const bob::core::array::typeinfo& info = input.type(); - bob::python::ndarray output(bob::core::array::t_float64, info.shape[0], info.shape[1]); + const bob::io::base::array::typeinfo& info = input.type(); + bob::python::ndarray output(bob::io::base::array::t_float64, info.shape[0], info.shape[1]); blitz::Array<double,2> output_ = output.bz<double,2>(); m.forward(input.bz<double,2>(), output_); return output.self(); @@ -53,18 +57,38 @@ static void py_set_ps(bob::machine::WienerMachine& m, m.setPs(ps.bz<double,2>()); } + +static boost::shared_ptr<bob::machine::WienerMachine> _init(boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + return boost::shared_ptr<bob::machine::WienerMachine>(new bob::machine::WienerMachine(*hdf5->f)); +} + +static void _load(bob::machine::WienerMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.load(*hdf5->f); +} + +static void _save(const bob::machine::WienerMachine& self, boost::python::object file){ + if (!PyBobIoHDF5File_Check(file.ptr())) PYTHON_ERROR(TypeError, "Would have expected a bob.io.base.HDF5File"); + PyBobIoHDF5FileObject* hdf5 = (PyBobIoHDF5FileObject*) file.ptr(); + self.save(*hdf5->f); +} + + void bind_machine_wiener() { class_<bob::machine::WienerMachine, boost::shared_ptr<bob::machine::WienerMachine> >("WienerMachine", "A Wiener filter.\nReference:\n'Computer Vision: Algorithms and Applications', Richard Szeliski, (Part 3.4.3)", init<const size_t, const size_t, const double, optional<const double> >((arg("self"), arg("height"), arg("width"), arg("pn"), arg("variance_threshold")=1e-8), "Constructs a new Wiener filter dedicated to images of the given dimensions. The filter is initialized with zero values.")) .def(init<const blitz::Array<double,2>&, const double> ((arg("self"), arg("ps"), arg("pn")), "Constructs a new WienerMachine from a set of variance estimates ps, a noise level pn.")) .def(init<>((arg("self")), "Default constructor, builds a machine as with 'WienerMachine(0,0,0)'.")) - .def(init<bob::io::HDF5File&>((arg("self"), arg("config")), "Constructs a new WienerMachine from a configuration file.")) + .def("__init__", boost::python::make_constructor(&_init), "Constructs a new WienerMachine from a configuration file.") .def(init<const bob::machine::WienerMachine&>((arg("self"), arg("machine")), "Copy constructs an WienerMachine")) .def(self == self) .def(self != self) .def("is_similar_to", &bob::machine::WienerMachine::is_similar_to, (arg("self"), arg("other"), arg("r_epsilon")=1e-5, arg("a_epsilon")=1e-8), "Compares this WienerMachine with the 'other' one to be approximately the same.") - .def("load", &bob::machine::WienerMachine::load, (arg("self"), arg("config")), "Loads the filter from a configuration file.") - .def("save", &bob::machine::WienerMachine::save, (arg("self"), arg("config")), "Saves the filter to a configuration file.") + .def("load", &_load, (arg("self"), arg("config")), "Loads the filter from a configuration file.") + .def("save", &_save, (arg("self"), arg("config")), "Saves the filter to a configuration file.") .add_property("pn", &bob::machine::WienerMachine::getPn, &bob::machine::WienerMachine::setPn, "Noise level Pn") .add_property("variance_threshold", &bob::machine::WienerMachine::getVarianceThreshold, &bob::machine::WienerMachine::setVarianceThreshold, "Variance flooring threshold (min variance value)") .add_property("ps",make_function(&bob::machine::WienerMachine::getPs, return_value_policy<copy_const_reference>()), &py_set_ps, "Variance Ps estimated at each frequency") diff --git a/bob/learn/misc/old/wiener_trainer.cc b/bob/learn/misc/old/wiener_trainer.cc index 74a09c16a5aff0d2598bad8c54fdb4ee87b305f2..4a795ce6ac4f89c7ac22b2173f15a63086cd714a 100644 --- a/bob/learn/misc/old/wiener_trainer.cc +++ b/bob/learn/misc/old/wiener_trainer.cc @@ -8,8 +8,8 @@ */ #include "ndarray.h" -#include <bob/trainer/WienerTrainer.h> -#include <bob/machine/WienerMachine.h> +#include <bob.learn.misc/WienerTrainer.h> +#include <bob.learn.misc/WienerMachine.h> using namespace boost::python; diff --git a/bob/learn/misc/old/ztnorm.cc b/bob/learn/misc/old/ztnorm.cc index c72849c9044e03f22ccc11ffd69bdf270f104afd..898f512ca0bbb0286b7500a0ad260d4233151d1b 100644 --- a/bob/learn/misc/old/ztnorm.cc +++ b/bob/learn/misc/old/ztnorm.cc @@ -10,7 +10,7 @@ #include "ndarray.h" -#include <bob/machine/ZTNorm.h> +#include <bob.learn.misc/ZTNorm.h> using namespace boost::python; @@ -33,7 +33,7 @@ static object ztnorm1( mask_zprobes_vs_tmodels_istruetrial.bz<bool,2>(); // allocate output - bob::python::ndarray ret(bob::core::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); + bob::python::ndarray ret(bob::io::base::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); blitz::Array<double, 2> ret_ = ret.bz<double,2>(); bob::machine::ztNorm(rawscores_probes_vs_models_, @@ -62,7 +62,7 @@ static object ztnorm2( rawscores_zprobes_vs_tmodels.bz<double,2>(); // allocate output - bob::python::ndarray ret(bob::core::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); + bob::python::ndarray ret(bob::io::base::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); blitz::Array<double, 2> ret_ = ret.bz<double,2>(); bob::machine::ztNorm(rawscores_probes_vs_models_, @@ -84,7 +84,7 @@ static object tnorm( rawscores_probes_vs_tmodels.bz<double,2>(); // allocate output - bob::python::ndarray ret(bob::core::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); + bob::python::ndarray ret(bob::io::base::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); blitz::Array<double, 2> ret_ = ret.bz<double,2>(); bob::machine::tNorm(rawscores_probes_vs_models_, @@ -104,7 +104,7 @@ static object znorm( rawscores_zprobes_vs_models.bz<double,2>(); // allocate output - bob::python::ndarray ret(bob::core::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); + bob::python::ndarray ret(bob::io::base::array::t_float64, rawscores_probes_vs_models_.extent(0), rawscores_probes_vs_models_.extent(1)); blitz::Array<double, 2> ret_ = ret.bz<double,2>(); bob::machine::zNorm(rawscores_probes_vs_models_, diff --git a/bob/learn/misc/test_em.py b/bob/learn/misc/test_em.py index 88a5cdebd02a8cacdba1ef943b076f7e8a865ef1..c7d813114c1377b3d81c4945c2ff4407e5b79650 100644 --- a/bob/learn/misc/test_em.py +++ b/bob/learn/misc/test_em.py @@ -16,8 +16,6 @@ from bob.io.base.test_utils import datafile from . import KMeansMachine, GMMMachine, KMeansTrainer, \ ML_GMMTrainer, MAP_GMMTrainer -from . import HDF5File as OldHDF5File - def loadGMM(): gmm = GMMMachine(2, 2) @@ -54,12 +52,12 @@ def test_gmm_ML_1(): ml_gmmtrainer = ML_GMMTrainer(True, True, True) ml_gmmtrainer.train(gmm, ar) - #config = OldHDF5File(datafile('gmm_ML.hdf5", __name__), 'w') + #config = bob.io.base.HDF5File(datafile('gmm_ML.hdf5", __name__), 'w') #gmm.save(config) - gmm_ref = GMMMachine(OldHDF5File(datafile('gmm_ML.hdf5', __name__))) - gmm_ref_32bit_debug = GMMMachine(OldHDF5File(datafile('gmm_ML_32bit_debug.hdf5', __name__))) - gmm_ref_32bit_release = GMMMachine(OldHDF5File(datafile('gmm_ML_32bit_release.hdf5', __name__))) + gmm_ref = GMMMachine(bob.io.base.HDF5File(datafile('gmm_ML.hdf5', __name__))) + gmm_ref_32bit_debug = GMMMachine(bob.io.base.HDF5File(datafile('gmm_ML_32bit_debug.hdf5', __name__))) + gmm_ref_32bit_release = GMMMachine(bob.io.base.HDF5File(datafile('gmm_ML_32bit_release.hdf5', __name__))) assert (gmm == gmm_ref) or (gmm == gmm_ref_32bit_release) or (gmm == gmm_ref_32bit_debug) @@ -106,18 +104,18 @@ def test_gmm_MAP_1(): ar = bob.io.base.load(datafile('faithful.torch3_f64.hdf5', __name__)) - gmm = GMMMachine(OldHDF5File(datafile("gmm_ML.hdf5", __name__))) - gmmprior = GMMMachine(OldHDF5File(datafile("gmm_ML.hdf5", __name__))) + gmm = GMMMachine(bob.io.base.HDF5File(datafile("gmm_ML.hdf5", __name__))) + gmmprior = GMMMachine(bob.io.base.HDF5File(datafile("gmm_ML.hdf5", __name__))) map_gmmtrainer = MAP_GMMTrainer(16) map_gmmtrainer.set_prior_gmm(gmmprior) map_gmmtrainer.train(gmm, ar) - #config = OldHDF5File(datafile('gmm_MAP.hdf5", 'w', __name__)) + #config = bob.io.base.HDF5File(datafile('gmm_MAP.hdf5", 'w', __name__)) #gmm.save(config) - gmm_ref = GMMMachine(OldHDF5File(datafile('gmm_MAP.hdf5', __name__))) - #gmm_ref_32bit_release = GMMMachine(OldHDF5File(datafile('gmm_MAP_32bit_release.hdf5', __name__))) + gmm_ref = GMMMachine(bob.io.base.HDF5File(datafile('gmm_MAP.hdf5', __name__))) + #gmm_ref_32bit_release = GMMMachine(bob.io.base.HDF5File(datafile('gmm_MAP_32bit_release.hdf5', __name__))) assert (equals(gmm.means,gmm_ref.means,1e-3) and equals(gmm.variances,gmm_ref.variances,1e-3) and equals(gmm.weights,gmm_ref.weights,1e-3)) diff --git a/bob/learn/misc/test_gaussian.py b/bob/learn/misc/test_gaussian.py index 3c7515972b76acc56b087542879b92ed63523940..c6cc220f9cf06df50ab7ec8e816ab57e0cc9579c 100644 --- a/bob/learn/misc/test_gaussian.py +++ b/bob/learn/misc/test_gaussian.py @@ -16,8 +16,6 @@ import bob.io.base from . import Gaussian -from . import HDF5File as OldHDF5File - def equals(x, y, epsilon): return (abs(x - y) < epsilon) @@ -49,8 +47,8 @@ def test_GaussianMachine(): # Save and read from file filename = str(tempfile.mkstemp(".hdf5")[1]) - g.save(OldHDF5File(filename, 'w')) - g_loaded = Gaussian(OldHDF5File(filename)) + g.save(bob.io.base.HDF5File(filename, 'w')) + g_loaded = Gaussian(bob.io.base.HDF5File(filename)) assert g == g_loaded assert (g != g_loaded ) is False assert g.is_similar_to(g_loaded) diff --git a/bob/learn/misc/test_gmm.py b/bob/learn/misc/test_gmm.py index e3276468a94c4a314b5057f7c8270fb74bde8961..cd981742dc6ba95a6e4057961438de1dc3389bc9 100644 --- a/bob/learn/misc/test_gmm.py +++ b/bob/learn/misc/test_gmm.py @@ -17,8 +17,6 @@ from bob.io.base.test_utils import datafile from . import GMMStats, GMMMachine -from . import HDF5File as OldHDF5File - def test_GMMStats(): # Test a GMMStats @@ -42,8 +40,8 @@ def test_GMMStats(): # Saves and reads from file filename = str(tempfile.mkstemp(".hdf5")[1]) - gs.save(OldHDF5File(filename, 'w')) - gs_loaded = GMMStats(OldHDF5File(filename)) + gs.save(bob.io.base.HDF5File(filename, 'w')) + gs_loaded = GMMStats(bob.io.base.HDF5File(filename)) assert gs == gs_loaded assert (gs != gs_loaded ) is False assert gs.is_similar_to(gs_loaded) @@ -192,7 +190,7 @@ def test_GMMMachine_2(): stats = GMMStats(2, 2) gmm.acc_statistics(arrayset, stats) - stats_ref = GMMStats(OldHDF5File(datafile("stats.hdf5", __name__))) + stats_ref = GMMStats(bob.io.base.HDF5File(datafile("stats.hdf5", __name__))) assert stats.t == stats_ref.t assert numpy.allclose(stats.n, stats_ref.n, atol=1e-10) diff --git a/bob/learn/misc/test_jfa.py b/bob/learn/misc/test_jfa.py index 31428097bf966bdf7a7c913d637bb3dd44568309..1434a3e443a33810cae6062261a1b8a6f59eff7f 100644 --- a/bob/learn/misc/test_jfa.py +++ b/bob/learn/misc/test_jfa.py @@ -17,8 +17,6 @@ import bob.io.base from . import GMMMachine, GMMStats, JFABase, ISVBase, ISVMachine, JFAMachine -from . import HDF5File as OldHDF5File - def estimate_x(dim_c, dim_d, mean, sigma, U, N, F): # Compute helper values UtSigmaInv = {} @@ -85,8 +83,8 @@ def test_JFABase(): # Saves and loads filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = JFABase(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = JFABase(bob.io.base.HDF5File(filename)) m_loaded.ubm = ubm assert m == m_loaded assert (m != m_loaded) is False @@ -146,8 +144,8 @@ def test_ISVBase(): # Saves and loads filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = ISVBase(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = ISVBase(bob.io.base.HDF5File(filename)) m_loaded.ubm = ubm assert m == m_loaded assert (m != m_loaded) is False @@ -210,8 +208,8 @@ def test_JFAMachine(): # Saves and loads filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = JFAMachine(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = JFAMachine(bob.io.base.HDF5File(filename)) m_loaded.jfa_base = base assert m == m_loaded assert (m != m_loaded) is False @@ -306,8 +304,8 @@ def test_ISVMachine(): # Saves and loads filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = ISVMachine(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = ISVMachine(bob.io.base.HDF5File(filename)) m_loaded.isv_base = base assert m == m_loaded assert (m != m_loaded) is False diff --git a/bob/learn/misc/test_kmeans.py b/bob/learn/misc/test_kmeans.py index 8915b6bdb30e11262ed95b46a46183e223c4ad0a..cec6cf1a2f2713d579252c495b575e7efa343f08 100644 --- a/bob/learn/misc/test_kmeans.py +++ b/bob/learn/misc/test_kmeans.py @@ -15,8 +15,6 @@ import tempfile import bob.io.base from . import KMeansMachine -from . import HDF5File as OldHDF5File - def equals(x, y, epsilon): return (abs(x - y) < epsilon) @@ -50,8 +48,8 @@ def test_KMeansMachine(): # Loads and saves filename = str(tempfile.mkstemp(".hdf5")[1]) - km.save(OldHDF5File(filename, 'w')) - km_loaded = KMeansMachine(OldHDF5File(filename)) + km.save(bob.io.base.HDF5File(filename, 'w')) + km_loaded = KMeansMachine(bob.io.base.HDF5File(filename)) assert km == km_loaded # Resize diff --git a/bob/learn/misc/test_plda.py b/bob/learn/misc/test_plda.py index 4eeb22d44d629d0aaf6ac3bd8a5abba947afda76..1b90c1d44ab2879238e26b0231e90453fa8cf482 100644 --- a/bob/learn/misc/test_plda.py +++ b/bob/learn/misc/test_plda.py @@ -19,8 +19,6 @@ import bob.io.base from . import PLDABase, PLDAMachine -from . import HDF5File as OldHDF5File - # Defines common variables globally # Dimensionalities C_dim_d = 7 @@ -293,8 +291,8 @@ def test_plda_basemachine(): # Saves to file, loads and compares to original filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = PLDABase(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = PLDABase(bob.io.base.HDF5File(filename)) # Compares the values loaded with the former ones assert m_loaded == m @@ -418,8 +416,8 @@ def test_plda_machine(): # Saves to file, loads and compares to original filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = PLDAMachine(OldHDF5File(filename), mb) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = PLDAMachine(bob.io.base.HDF5File(filename), mb) # Compares the values loaded with the former ones assert m_loaded == m @@ -444,7 +442,7 @@ def test_plda_machine(): # Check exceptions m_loaded2 = PLDAMachine() - m_loaded2.load(OldHDF5File(filename)) + m_loaded2.load(bob.io.base.HDF5File(filename)) nose.tools.assert_raises(RuntimeError, getattr, m_loaded2, 'dim_d') nose.tools.assert_raises(RuntimeError, getattr, m_loaded2, 'dim_f') nose.tools.assert_raises(RuntimeError, getattr, m_loaded2, 'dim_g') diff --git a/bob/learn/misc/test_wiener.py b/bob/learn/misc/test_wiener.py index 6239d382b7c289cd58c55a01286b59f28ba70522..39cd0d64979a3278f7d9f113c48e0bd4f923e5d3 100644 --- a/bob/learn/misc/test_wiener.py +++ b/bob/learn/misc/test_wiener.py @@ -17,8 +17,6 @@ import bob.io.base from . import WienerMachine -from . import HDF5File as OldHDF5File - def test_initialization(): # Getters/Setters @@ -67,8 +65,8 @@ def test_load_save(): # Save and read from file filename = str(tempfile.mkstemp(".hdf5")[1]) - m.save(OldHDF5File(filename, 'w')) - m_loaded = WienerMachine(OldHDF5File(filename)) + m.save(bob.io.base.HDF5File(filename, 'w')) + m_loaded = WienerMachine(bob.io.base.HDF5File(filename)) assert m == m_loaded assert (m != m_loaded ) is False assert m.is_similar_to(m_loaded) diff --git a/bob/learn/misc/version.cpp b/bob/learn/misc/version.cpp index 03615dce05a8076359c1140c5df2ab39d7dae8b8..d3c36a9a80865f123deb2a1b21b7fb70116749bb 100644 --- a/bob/learn/misc/version.cpp +++ b/bob/learn/misc/version.cpp @@ -5,9 +5,19 @@ * @brief Binds configuration information available from bob */ -#include <Python.h> +#ifdef NO_IMPORT_ARRAY +#undef NO_IMPORT_ARRAY +#endif +#include <bob.blitz/capi.h> +#include <bob.blitz/cleanup.h> -#include <bob/config.h> +#include <bob.core/config.h> +#include <bob.io.base/config.h> +#include <bob.sp/config.h> +#include <bob.math/config.h> +#include <bob.learn.activation/config.h> +#include <bob.learn.linear/config.h> +// TODO: add other dependencies #include <string> #include <cstdlib> @@ -16,11 +26,6 @@ #include <boost/version.hpp> #include <boost/format.hpp> -#ifdef NO_IMPORT_ARRAY -#undef NO_IMPORT_ARRAY -#endif -#include <bob.blitz/capi.h> -#include <bob.blitz/cleanup.h> static int dict_set(PyObject* d, const char* key, const char* value) { PyObject* v = Py_BuildValue("s", value); @@ -80,13 +85,6 @@ static PyObject* python_version() { return Py_BuildValue("s", f.str().c_str()); } -/** - * Bob version, API version and platform - */ -static PyObject* bob_version() { - return Py_BuildValue("sis", BOB_VERSION, BOB_API_VERSION, BOB_PLATFORM); -} - /** * Numpy version */ @@ -102,6 +100,49 @@ static PyObject* bob_blitz_version() { return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_BLITZ_API_VERSION)); } +/** + * bob.core c/c++ api version + */ +static PyObject* bob_core_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_CORE_API_VERSION)); +} + +/** + * bob.io.base c/c++ api version + */ +static PyObject* bob_io_base_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_IO_BASE_API_VERSION)); +} + +/** + * bob.sp c/c++ api version + */ +static PyObject* bob_sp_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_SP_API_VERSION)); +} + +/** + * bob.math c/c++ api version + */ +static PyObject* bob_math_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_MATH_API_VERSION)); +} + +/** + * bob.learn.activation c/c++ api version + */ +static PyObject* bob_learn_activation_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_LEARN_ACTIVATION_API_VERSION)); +} + +/** + * bob.learn.linear c/c++ api version + */ +static PyObject* bob_learn_linear_version() { + return Py_BuildValue("{ss}", "api", BOOST_PP_STRINGIZE(BOB_LEARN_LINEAR_API_VERSION)); +} + + static PyObject* build_version_dictionary() { PyObject* retval = PyDict_New(); @@ -114,7 +155,13 @@ static PyObject* build_version_dictionary() { if (!dict_steal(retval, "Python", python_version())) return 0; if (!dict_steal(retval, "NumPy", numpy_version())) return 0; if (!dict_steal(retval, "bob.blitz", bob_blitz_version())) return 0; - if (!dict_steal(retval, "Bob", bob_version())) return 0; + if (!dict_steal(retval, "bob.core", bob_core_version())) return 0; + if (!dict_steal(retval, "bob.io.base", bob_io_base_version())) return 0; + if (!dict_steal(retval, "bob.sp", bob_sp_version())) return 0; + if (!dict_steal(retval, "bob.math", bob_math_version())) return 0; + if (!dict_steal(retval, "bob.learn.activation", bob_learn_activation_version())) return 0; + if (!dict_steal(retval, "bob.learn.linear", bob_learn_linear_version())) return 0; + if (!dict_steal(retval, "Bob", bob_core_version())) return 0; Py_INCREF(retval); return retval; diff --git a/buildout.cfg b/buildout.cfg index 563bcb0cda2ffaeac74f075275114d8cbe6c965c..a00b4ac1ecebcde82b58a064db033ebf5beec6c6 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -13,13 +13,15 @@ develop = src/bob.extension src/bob.core src/bob.io.base src/bob.sp + src/bob.math + src/bob.learn.activation + src/bob.learn.linear . ; options for bob.buildout extension debug = true verbose = true -prefixes = /idiap/group/torch5spro/releases/preview/install/linux-x86_64-release - /Users/andre/work/bob/b/dbg/ +prefixes = /idiap/group/torch5spro/externals/py278/usr [sources] bob.extension = git https://github.com/bioidiap/bob.extension @@ -27,6 +29,9 @@ 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.sp = git https://github.com/bioidiap/bob.sp +bob.math = git https://github.com/bioidiap/bob.math +bob.learn.activation = git https://github.com/bioidiap/bob.learn.activation +bob.learn.linear = git https://github.com/bioidiap/bob.learn.linear [scripts] recipe = bob.buildout:scripts diff --git a/setup.py b/setup.py index 86769f01ce7cc3b871f5b6045e1cea6088ce9321..dec74d21a9e49fc45cb03dbc7e8678f7ea2349d2 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,15 @@ # Andre Anjos <andre.anjos@idiap.ch> # Mon 16 Apr 08:18:08 2012 CEST +bob_packages = ['bob.core', 'bob.io.base', 'bob.sp', 'bob.math', 'bob.learn.activation', 'bob.learn.linear'] + from setuptools import setup, find_packages, dist -dist.Distribution(dict(setup_requires=['bob.blitz', 'bob.io.base'])) -from bob.blitz.extension import Extension -import bob.io.base +dist.Distribution(dict(setup_requires=['bob.blitz'] + bob_packages)) +from bob.blitz.extension import Extension, Library, build_ext -import os -include_dirs = [bob.io.base.get_include()] +packages = ['boost'] +boost_modules = ['system', 'python'] -packages = ['bob-machine >= 2.0.0a2', 'bob-trainer >= 2.0.0a2', 'boost'] version = '2.0.0a0' setup( @@ -35,22 +35,77 @@ setup( 'bob.core', 'bob.io.base', 'bob.sp', + 'bob.math', + 'bob.learn.activation', + 'bob.learn.linear', ], namespace_packages=[ "bob", "bob.learn", - ], + ], ext_modules = [ Extension("bob.learn.misc.version", [ "bob/learn/misc/version.cpp", - ], + ], + bob_packages = bob_packages, + packages = packages, + boost_modules = boost_modules, + version = version, + ), + + Library("bob.learn.misc.bob_learn_misc", + [ + "bob/learn/misc/cpp/BICMachine.cpp", + "bob/learn/misc/cpp/Gaussian.cpp", + "bob/learn/misc/cpp/GMMMachine.cpp", + "bob/learn/misc/cpp/GMMStats.cpp", + "bob/learn/misc/cpp/IVectorMachine.cpp", + "bob/learn/misc/cpp/JFAMachine.cpp", + "bob/learn/misc/cpp/KMeansMachine.cpp", + "bob/learn/misc/cpp/LinearScoring.cpp", + "bob/learn/misc/cpp/PLDAMachine.cpp", + "bob/learn/misc/cpp/WienerMachine.cpp", + "bob/learn/misc/cpp/ZTNorm.cpp", + + "bob/learn/misc/cpp/BICTrainer.cpp", + "bob/learn/misc/cpp/EMPCATrainer.cpp", + "bob/learn/misc/cpp/GMMTrainer.cpp", + "bob/learn/misc/cpp/IVectorTrainer.cpp", + "bob/learn/misc/cpp/JFATrainer.cpp", + "bob/learn/misc/cpp/KMeansTrainer.cpp", + "bob/learn/misc/cpp/MAP_GMMTrainer.cpp", + "bob/learn/misc/cpp/ML_GMMTrainer.cpp", + "bob/learn/misc/cpp/PLDATrainer.cpp", + "bob/learn/misc/cpp/WienerTrainer.cpp", + ], + bob_packages = bob_packages, packages = packages, - include_dirs = include_dirs, + boost_modules = boost_modules, version = version, - ), + ), + +# Extension("bob.learn.misc._library", +# [ +# "bob/learn/misc/old/bic.cc", +# +# # external requirements as boost::python bindings +# "bob/learn/misc/old/blitz_numpy.cc", +# "bob/learn/misc/old/ndarray.cc", +# "bob/learn/misc/old/ndarray_numpy.cc", +# "bob/learn/misc/old/tinyvector.cc", +# "bob/learn/misc/old/random.cc", +# +# "bob/learn/misc/old/main.cc", +# ], +# bob_packages = bob_packages, +# packages = packages, +# boost_modules = boost_modules, +# version = version, +# ), + Extension("bob.learn.misc._old_library", [ "bob/learn/misc/old/bic.cc", @@ -78,17 +133,20 @@ setup( "bob/learn/misc/old/ndarray.cc", "bob/learn/misc/old/ndarray_numpy.cc", "bob/learn/misc/old/tinyvector.cc", - "bob/learn/misc/old/hdf5.cc", "bob/learn/misc/old/random.cc", "bob/learn/misc/old/main.cc", ], + bob_packages = bob_packages, packages = packages, - boost_modules = ['python'], - include_dirs = include_dirs, + boost_modules = boost_modules, version = version, - ), - ], + ), + ], + + cmdclass = { + 'build_ext': build_ext + }, classifiers = [ 'Development Status :: 3 - Alpha', @@ -98,6 +156,6 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries :: Python Modules', - ], + ], - ) + )