Commit 8e4452fe authored by Amir MOHAMMADI's avatar Amir MOHAMMADI
Browse files

Merge branch 'dask-pipelines' into 'master'

Remove deprecated code

See merge request !77
parents 49ee5285 ee6a0cad
Pipeline #45294 failed with stages
in 3 minutes and 20 seconds
......@@ -15,12 +15,13 @@
Scripts to run anti-spoofing experiments
========================================
This package is part of the signal-processing and machine learning toolbox
Bob_. This package is the base of ``bob.pad`` family of packages, which allow to run comparable and reproducible
presentation attack detection (PAD) experiments on publicly available databases.
This package is part of the signal-processing and machine learning toolbox Bob_.
This package is the base of ``bob.pad`` family of packages, which allow to run
comparable and reproducible presentation attack detection (PAD) experiments on
publicly available databases.
This package contains basic functionality to run PAD experiments.
It provides a generic ``./bin/spoof.py`` script that takes several parameters, including:
It provides a generic API for PAD including:
* A database and its evaluation protocol
* A data preprocessing algorithm
......@@ -28,9 +29,11 @@ It provides a generic ``./bin/spoof.py`` script that takes several parameters, i
* A PAD algorithm
All these steps of the PAD system are given as configuration files.
All the algorithms are standardized on top of scikit-learn estimators.
In this base class implementation, only a core functionality is implemented. The specialized algorithms should
be provided by other packages, which are usually in the ``bob.pad`` namespace, like a ``bob.pad.voice`` package.
In this base package, only a core functionality is implemented. The specialized
algorithms should be provided by other packages, which are usually in the
``bob.pad`` namespace, like ``bob.pad.face``.
Installation
------------
......@@ -50,5 +53,5 @@ development `mailing list`_.
.. Place your references here:
.. _bob: https://www.idiap.ch/software/bob
.. _installation: https://gitlab.idiap.ch/bob/bob/wikis/Installation
.. _installation: https://www.idiap.ch/software/bob/install
.. _mailing list: https://groups.google.com/forum/?fromgroups#!forum/bob-devel
from .utils import *
from . import database
from . import algorithm
from . import script
from . import test
......
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Pavel Korshunov <pavel.korshunov@idiap.ch>
# @date: Wed 19 Aug 13:43:21 2015
#
import numpy
import os
from bob.bio.base import utils
class Algorithm(object):
"""This is the base class for all anti-spoofing algorithms.
It defines the minimum requirements for all derived algorithm classes.
Call the constructor in derived class implementations.
If your derived algorithm performs feature projection, please register this here.
If it needs training for the projector, please set this here, too.
**Parameters:**
performs_projection : bool
Set to ``True`` if your derived algorithm performs a projection.
Also implement the :py:meth:`project` function, and the :py:meth:`load_projector` if necessary.
requires_projector_training : bool
Only valid, when ``performs_projection = True``.
Set this flag to ``False``, when the projection is applied, but the projector does not need to be trained.
kwargs : ``key=value`` pairs
A list of keyword arguments to be written in the `__str__` function.
"""
def __init__(
self,
performs_projection=False, # enable if your tool will project the features
requires_projector_training=True, # by default, the projector needs training, if projection is enabled
**kwargs # parameters from the derived class that should be reported in the __str__() function
):
self.performs_projection = performs_projection
self.requires_projector_training = performs_projection and requires_projector_training
self._kwargs = kwargs
def __str__(self):
"""__str__() -> info
This function returns all parameters of this class (and its derived class).
**Returns:**
info : str
A string containing the full information of all parameters of this (and the derived) class.
"""
return "%s(%s)" % (str(self.__class__), ", ".join(
["%s=%s" % (key, value) for key, value in self._kwargs.items() if value is not None]))
def project(self, feature):
"""project(feature) -> projected
This function will project the given feature.
It must be overwritten by derived classes, as soon as ``performs_projection = True`` was set in the constructor.
It is assured that the :py:meth:`load_projector` was called once before the ``project`` function is executed.
**Parameters:**
feature : object
The feature to be projected.
**Returns:**
projected : object
The projected features.
Must be writable with the :py:meth:`write_feature` function and readable with the :py:meth:`read_feature` function.
"""
raise NotImplementedError("Please overwrite this function in your derived class")
def score(self, toscore):
"""score(toscore) -> score
This function will compute the score for the given object ``toscore``.
It must be overwritten by derived classes.
**Parameters:**
toscore : object
The object to compute the score for. This will be the output of
extractor if performs_projection is False, otherwise this will be the
output of project method of the algorithm.
**Returns:**
score : float
A score value for the object ``toscore``.
"""
raise NotImplementedError("Please overwrite this function in your derived class")
def score_for_multiple_projections(self, toscore):
"""scorescore_for_multiple_projections(toscore) -> score
This function will compute the score for a list of objects in ``toscore``.
It must be overwritten by derived classes.
**Parameters:**
toscore : [object]
A list of objects to compute the score for.
**Returns:**
score : float
A score value for the object ``toscore``.
"""
raise NotImplementedError("Please overwrite this function in your derived class")
############################################################
### Special functions that might be overwritten on need
############################################################
def write_feature(self, feature, feature_file):
"""Saves the given *projected* feature to a file with the given name.
In this base class implementation:
- If the given feature has a ``save`` attribute, it calls ``feature.save(bob.io.base.HDF5File(feature_file), 'w')``.
In this case, the given feature_file might be either a file name or a bob.io.base.HDF5File.
- Otherwise, it uses :py:func:`bob.io.base.save` to do that.
If you have a different format, please overwrite this function.
Please register 'performs_projection = True' in the constructor to enable this function.
**Parameters:**
feature : object
A feature as returned by the :py:meth:`project` function, which should be written.
feature_file : str or :py:class:`bob.io.base.HDF5File`
The file open for writing, or the file name to write to.
"""
utils.save(feature, feature_file)
def read_feature(self, feature_file):
"""read_feature(feature_file) -> feature
Reads the *projected* feature from file.
In this base class implementation, it uses :py:func:`bob.io.base.load` to do that.
If you have different format, please overwrite this function.
Please register ``performs_projection = True`` in the constructor to enable this function.
**Parameters:**
feature_file : str or :py:class:`bob.io.base.HDF5File`
The file open for reading, or the file name to read from.
**Returns:**
feature : object
The feature that was read from file.
"""
return utils.load(feature_file)
def train_projector(self, training_features, projector_file):
"""This function can be overwritten to train the feature projector.
If you do this, please also register the function by calling this base class constructor
and enabling the training by ``requires_projector_training = True``.
**Parameters:**
training_features : [object] or [[object]]
A list of *extracted* features that can be used for training the projector.
Features will be provided in a single list
projector_file : str
The file to write.
This file should be readable with the :py:meth:`load_projector` function.
"""
raise NotImplementedError(
"Please overwrite this function in your derived class, or unset the 'requires_projector_training' option in the constructor.")
def load_projector(self, projector_file):
"""Loads the parameters required for feature projection from file.
This function usually is useful in combination with the :py:meth:`train_projector` function.
In this base class implementation, it does nothing.
Please register `performs_projection = True` in the constructor to enable this function.
**Parameters:**
projector_file : str
The file to read the projector from.
"""
pass
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# @author: Pavel Korshunov <pavel.korshunov@idiap.ch>
# @date: Wed 19 Oct 23:43:22 2016
import logging
import numpy
import bob.io.base
import bob.learn.linear
import bob.learn.em
from bob.bio.video import FrameContainer
from . import Algorithm
from ..utils import (
convert_and_prepare_features,
)
logger = logging.getLogger(__name__)
class GMM(Algorithm):
"""Trains two GMMs for two classes of PAD and calculates log likelihood ratio during
evaluation.
"""
def __init__(
self,
# parameters for the GMM
number_of_gaussians,
# parameters of UBM training
kmeans_training_iterations=25, # Maximum number of iterations for K-Means
gmm_training_iterations=10, # Maximum number of iterations for ML GMM Training
training_threshold=5e-4, # Threshold to end the ML training
variance_threshold=5e-4, # Minimum value that a variance can reach
update_weights=True,
update_means=True,
update_variances=True,
responsibility_threshold=0,
# If set, the weight of a particular Gaussian will at least be greater than this
# threshold. In the case the real weight is lower, the prior mean value will be
# used to estimate the current mean and variance.
INIT_SEED=5489,
performs_projection=True,
requires_projector_training=True,
**kwargs,
):
super().__init__(
performs_projection=performs_projection,
requires_projector_training=requires_projector_training,
**kwargs,
)
self._kwargs.update(
dict(
number_of_gaussians=number_of_gaussians,
kmeans_training_iterations=kmeans_training_iterations,
gmm_training_iterations=gmm_training_iterations,
training_threshold=training_threshold,
variance_threshold=variance_threshold,
update_weights=update_weights,
update_means=update_means,
update_variances=update_variances,
responsibility_threshold=responsibility_threshold,
INIT_SEED=INIT_SEED,
)
)
# copy parameters
self.gaussians = number_of_gaussians
self.kmeans_training_iterations = kmeans_training_iterations
self.gmm_training_iterations = gmm_training_iterations
self.training_threshold = training_threshold
self.variance_threshold = variance_threshold
self.update_weights = update_weights
self.update_means = update_means
self.update_variances = update_variances
self.responsibility_threshold = responsibility_threshold
self.init_seed = INIT_SEED
self.rng = bob.core.random.mt19937(self.init_seed)
self.gmm_machine_real = None
self.gmm_machine_attack = None
self.kmeans_trainer = bob.learn.em.KMeansTrainer()
self.gmm_trainer = bob.learn.em.ML_GMMTrainer(
self.update_means,
self.update_variances,
self.update_weights,
self.responsibility_threshold,
)
def _check_feature(self, feature, machine=None, projected=False):
"""Checks that the features are appropriate."""
if (
not isinstance(feature, numpy.ndarray)
or feature.ndim != 2
or feature.dtype != numpy.float64
):
raise ValueError("The given feature is not appropriate", feature)
if (
self.gmm_machine_real is not None
and feature.shape[1] != self.gmm_machine_real.shape[1]
):
raise ValueError(
"The given feature is expected to have %d elements, but it has %d"
% (self.gmm_machine_real.shape[1], feature.shape[1])
)
if (
self.gmm_machine_attack is not None
and feature.shape[1] != self.gmm_machine_attack.shape[1]
):
raise ValueError(
"The given feature is expected to have %d elements, but it has %d"
% (self.gmm_machine_attack.shape[1], feature.shape[1])
)
return True
#######################################################
# GMM training #
def train_gmm(self, array):
logger.debug(" .... Training with %d feature vectors", array.shape[0])
# Computes input size
input_size = array.shape[1]
# Creates the machines (KMeans and GMM)
logger.debug(" .... Creating machines")
kmeans_machine = bob.learn.em.KMeansMachine(self.gaussians, input_size)
gmm_machine = bob.learn.em.GMMMachine(self.gaussians, input_size)
# initialize the random generator with out one single cool seed that allows us to reproduce experiments
logger.info(" -> Init random generator with seed %d", self.init_seed)
self.rng = bob.core.random.mt19937(self.init_seed)
# Trains using the KMeansTrainer
logger.info(" -> Training K-Means")
bob.learn.em.train(
self.kmeans_trainer,
kmeans_machine,
array,
self.kmeans_training_iterations,
self.training_threshold,
self.rng,
)
variances, weights = kmeans_machine.get_variances_and_weights_for_each_cluster(
array
)
means = kmeans_machine.means
# Initializes the GMM
gmm_machine.means = means
gmm_machine.variances = variances
gmm_machine.weights = weights
gmm_machine.set_variance_thresholds(self.variance_threshold)
# Trains the GMM
logger.info(" -> Training GMM")
bob.learn.em.train(
self.gmm_trainer,
gmm_machine,
array,
self.gmm_training_iterations,
self.training_threshold,
self.rng,
)
return gmm_machine
def save_gmms(self, projector_file):
"""Save projector to file"""
# Saves the trained GMMs to file
logger.debug(" .... Saving GMM models to file '%s'", projector_file)
hdf5 = (
projector_file
if isinstance(projector_file, bob.io.base.HDF5File)
else bob.io.base.HDF5File(projector_file, "w")
)
hdf5.create_group("GMMReal")
hdf5.cd("GMMReal")
self.gmm_machine_real.save(hdf5)
hdf5.cd("/")
hdf5.create_group("GMMAttack")
hdf5.cd("GMMAttack")
self.gmm_machine_attack.save(hdf5)
def train_projector(self, training_features, projector_file):
if len(training_features) != 2:
raise ValueError(
"Training projector: features should contain two lists: real and attack!"
)
logger.info(
" - Training: number of real features %d", len(training_features[0])
)
logger.info(
" - Training: number of attack features %d", len(training_features[1])
)
attack_features = convert_and_prepare_features(
training_features[1], dtype="float64"
)
del training_features[1]
real_features = convert_and_prepare_features(
training_features[0], dtype="float64"
)
del training_features
[self._check_feature(feature) for feature in real_features]
[self._check_feature(feature) for feature in attack_features]
logger.debug(
"GMM:train_projector(), real_features shape: %s", real_features.shape
)
logger.debug(
"GMM:train_projector(), attack_features shape: %s", attack_features.shape
)
logger.debug("Min real %g", numpy.min(real_features))
logger.debug("Max real %g", numpy.max(real_features))
logger.debug("Min attack %g", numpy.min(attack_features))
logger.debug("Max attack %g", numpy.max(attack_features))
logger.info("Training the GMM for real samples")
self.gmm_machine_real = self.train_gmm(real_features)
logger.info("Training the GMM for attack samples")
self.gmm_machine_attack = self.train_gmm(attack_features)
logger.info("Saving the GMMs")
self.save_gmms(projector_file)
def load_projector(self, projector_file):
with bob.io.base.HDF5File(projector_file) as hdf5file:
# read GMM for real data
hdf5file.cd("/GMMReal")
self.gmm_machine_real = bob.learn.em.GMMMachine(hdf5file)
# read GMM for attack data
hdf5file.cd("/GMMAttack")
self.gmm_machine_attack = bob.learn.em.GMMMachine(hdf5file)
self.gmm_machine_real.set_variance_thresholds(self.variance_threshold)
self.gmm_machine_attack.set_variance_thresholds(self.variance_threshold)
def project(self, feature):
"""project(feature) -> projected
Projects the given feature into GMM space.
**Parameters:**
feature : 1D :py:class:`numpy.ndarray`
The 1D feature to be projected.
**Returns:**
projected : 1D :py:class:`numpy.ndarray`
The ``feature`` projected into GMM space.
"""
if isinstance(feature, FrameContainer):
feature = feature.as_array()
feature = numpy.asarray(feature, dtype=numpy.float64)
self._check_feature(feature)
logger.debug(" .... Projecting %d features vector" % feature.shape[0])
# return the resulting log likelihoods
return numpy.asarray(
[self.gmm_machine_real(feature), self.gmm_machine_attack(feature)],
dtype=numpy.float64,
)
def score(self, toscore):
"""Returns the difference between log likelihoods of being real or attack"""
return [toscore[0] - toscore[1]]
def score_for_multiple_projections(self, toscore):
"""Returns the difference between log likelihoods of being real or attack"""
return self.score(toscore)
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 25 09:29:02 2017
@author: Olegs Nikisins
"""
#==============================================================================
# Import what is needed here:
from bob.pad.base.algorithm import Algorithm
from bob.bio.video.utils import FrameContainer
import numpy as np
from sklearn import linear_model
import bob.io.base
from bob.pad.base.utils import convert_frame_cont_to_array, convert_list_of_frame_cont_to_array, mean_std_normalize, \
norm_train_data
#==============================================================================
# Main body :
class LogRegr(Algorithm):
"""
This class is designed to train Logistic Regression classifier given Frame Containers
with features of real and attack classes. The procedure is the following:
1. First, the input data is mean-std normalized using mean and std of the
real class only.
2. Second, the Logistic Regression classifier is trained on normalized
input features.
3. The input features are next classified using pre-trained LR machine.
**Parameters:**
``C`` : :py:class:`float`
Inverse of regularization strength in LR classifier; must be a positive.
Like in support vector machines, smaller values specify stronger
regularization. Default: 1.0 .
``frame_level_scores_flag`` : :py:class:`bool`
Return scores for each frame individually if True. Otherwise, return a
single score per video. Default: ``False``.
``subsample_train_data_flag`` : :py:class:`bool`
Uniformly subsample the training data if ``True``. Default: ``False``.
``subsampling_step`` : :py:class:`int`
Training data subsampling step, only valid is
``subsample_train_data_flag = True``. Default: 10 .
``subsample_videos_flag`` : :py:class:`bool`
Uniformly subsample the training videos if ``True``. Default: ``False``.
``video_subsampling_step`` : :py:class:`int`
Training videos subsampling step, only valid is
``subsample_videos_flag = True``. Default: 3 .
"""
def __init__(self,
C=1,
frame_level_scores_flag=False,
subsample_train_data_flag=False,
subsampling_step=10,
subsample_videos_flag=False,
video_subsampling_step=3):
Algorithm.__init__(
self,
C=C,
frame_level_scores_flag=frame_level_scores_flag,
subsample_train_data_flag=subsample_train_data_flag,
subsampling_step=subsampling_step,
subsample_videos_flag=subsample_videos_flag,
video_subsampling_step=video_subsampling_step,
performs_projection=True,
requires_projector_training=True)
self.C = C
self.frame_level_scores_flag = frame_level_scores_flag
self.subsample_train_data_flag = subsample_train_data_flag