Skip to content
Snippets Groups Projects
Commit c2290e77 authored by Guillaume HEUSCH's avatar Guillaume HEUSCH
Browse files

Merge branch 'mlp_algorithm' into 'master'

MLPAlgorithm PAD algorithm V1 version

See merge request !4
parents 9ad938ef c94e1d3a
No related branches found
No related tags found
1 merge request!4MLPAlgorithm PAD algorithm V1 version
Pipeline #26699 passed
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@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 bob.ip.pytorch_extractor.utils import transform_and_net_forward
from bob.ip.pytorch_extractor.utils import load_pretrained_model
from bob.pad.base.utils import mean_std_normalize
from bob.pad.base.utils import convert_list_of_frame_cont_to_array
import bob.io.base
# =============================================================================
# Main body :
class MLPAlgorithm(Algorithm):
"""
This class is an MLP-based PAD algorithm, allowing to pass input feature
vectors (1D numpy arrays) via a pretrained MLP.
Attributes
-----------
config_file: str
Relative name of the config file.
The path should be relative to ``config_group``,
for example: "test_data/mlp_algo_test_config.py".
This file **must** contain at least the following definitions:
Function namely ``transform``, which takes numpy.ndarray as input,
and returns a transformed Tensor. The dimensionality of the output
tensor must match the format expected by the MLP.
A ``Network`` class, defining your network architecture. Note, if your
class is named differently, import it as ``Network``, for example:
``from bob.learn.pytorch.architectures import MyNetwork as Network``
Optional: ``network_kwargs`` to be used for ``Network`` initialization.
For example, if you don't want to use a sigmoid in the output of the
MLP, set the kwargs accodingly. Note: in current algorithm the
``forward()`` method of the ``Network`` is used for feature extraction.
config_group: str
Group/package name containing the configuration file. Usually all
configs should be stored in this folder/place.
For example: "bob.ip.pytorch_extractor".
Both ``config_file`` and ``config_group`` are used to access the
configuration module.
model_file : str
A paths to the model file to be used for network initialization.
The network structure is defined in the config file.
url : str
URL to download the pretrained models from.
If models are not available in the locations specified in the
``model_file`` sting, the system will try to download them from
``url``. The downloaded models **will be placed to the locations**
specified in ``model_file`` argument.
Default: None
archive_extension : str
Extension of the archived file to download from above ``url``.
Default: '.tar.gz'
frame_level_scores_flag : bool
Return scores for each frame individually if True. Otherwise, return a
single score per video. Default: ``True``.
mean_std_norm_flag : bool
Perform mean-std normalization of data if set to ``True``.
Note: make sure an MLP was trained on mean-std normalizaed
features, if this flag is set to ``True``. The tutorial on MLP
training in ``bob.learn.pytorch`` provides an example on how to train
network on normalized features.
Default: ``True``.
"""
def __init__(self,
config_file,
config_group,
model_file = None,
url = None,
archive_extension = '.tar.gz',
frame_level_scores_flag = True,
mean_std_norm_flag = True):
super(MLPAlgorithm, self).__init__(config_file = config_file,
config_group = config_group,
model_file = model_file,
url = url,
archive_extension = archive_extension,
frame_level_scores_flag = frame_level_scores_flag,
mean_std_norm_flag = mean_std_norm_flag,
performs_projection=True,
requires_projector_training=True)
self.config_file = config_file
self.config_group = config_group
self.model_file = model_file
self.url = url
self.archive_extension = archive_extension
self.frame_level_scores_flag = frame_level_scores_flag
self.mean_std_norm_flag = mean_std_norm_flag
self.features_mean = None # this argument will be updated with features mean
self.features_std = None # this argument will be updated with features std
# =========================================================================
def _save_mean_std(self,
projector_file,
features_mean,
features_std):
"""
Saves mean and std normalization to the hdf5 file.
The absolute name of the file is specified in ``projector_file`` string.
Parameters
----------
projector_file : str
Absolute name of the file to save the data to, as returned by
``bob.pad.base`` framework.
features_mean : 1D :py:class:`numpy.ndarray`
Mean of the features.
features_std : 1D :py:class:`numpy.ndarray`
Standart deviation of the features.
"""
f = bob.io.base.HDF5File(projector_file,'w') # open hdf5 file to save
f.set("features_mean", features_mean)
f.set("features_std", features_std)
del f
# =========================================================================
def train_projector(self, training_features, projector_file):
"""
Compute mean-std normalizers using samples of the real class only.
Parameters
----------
training_features : [[object], [object]]
A list containing two elements: [0] - a list of Frame Containers
with feature vectors for the real class;
[1] - a list of Frame Containers with feature vectors for the
attack class.
projector_file : str
The file to save the trained projector to, as returned by the
``bob.pad.base`` framework.
"""
# training_features[0] - training features for the REAL class.
# training_features[1] - training features for the ATTACK class.
real = convert_list_of_frame_cont_to_array(training_features[0])
# compute normalization params:
_, features_mean, features_std = mean_std_normalize(real)
# Save the normalizers:
self._save_mean_std(projector_file, features_mean, features_std)
# =========================================================================
def load_projector(self, projector_file):
"""
Loads features mean and std from the hdf5 file.
The absolute name of the file is specified in ``projector_file`` string.
Parameters
----------
projector_file : str
Absolute name of the file to load the trained projector from, as
returned by ``bob.pad.base`` framework.
"""
f = bob.io.base.HDF5File(projector_file, 'r') # file to read
features_mean = f.read("features_mean")
features_std = f.read("features_std")
del f
self.features_mean = features_mean
self.features_std = features_std
# =========================================================================
def project(self, feature):
"""
This function computes a vector of scores, one score for each sample
in the input array of features.
Set ``performs_projection = True`` in the constructor to enable this
function.
Parameters
----------
feature : FrameContainer or :py:class:`numpy.ndarray`
Two types of inputs are accepted.
A Frame Container conteining the features of an individual frmaes,
see ``bob.bio.video.utils.FrameContainer``.
Or a ND feature array of the size (n_samples x n_features).
Returns
-------
scores : 1D :py:class:`numpy.ndarray`
Vector of scores. Scores for the bona-fide class are expected to be
higher, than the scores of the negative / attack class.
"""
# try to load the model if not available, do nothing if available:
load_pretrained_model(model_path = self.model_file,
url = self.url,
archive_extension = self.archive_extension)
# 1. Convert input array to numpy array if necessary.
if isinstance(
feature,
FrameContainer): # if FrameContainer convert to 3D numpy array
feature = feature.as_array()
if self.mean_std_norm_flag:
feature, _, _ = mean_std_normalize(feature,
self.features_mean,
self.features_std)
# kwargs for the transform_and_net_forward function:
function_kwargs = {}
function_kwargs["config_file"] = self.config_file
function_kwargs["config_group"] = self.config_group
function_kwargs["model_file"] = self.model_file
function_kwargs["color_input_flag"] = False
scores = transform_and_net_forward(feature = feature,
**function_kwargs)
return scores
# =========================================================================
def score(self, toscore):
"""
Returns a probability of a sample being a real class.
Parameters
----------
toscore : 1D :py:class:`numpy.ndarray`
Vector with scores for each frame/sample defining the probability
of the frame being a sample of the real class.
Returns
-------
score : [:py:class:`float`]
If ``frame_level_scores_flag = False`` a single score is returned.
One score per video. This score is placed into a list, because
the ``score`` must be an iterable.
Score is a probability of a sample being a real class.
If ``frame_level_scores_flag = True`` a list of scores is returned.
One score per frame/sample.
"""
if self.frame_level_scores_flag:
score = list(toscore)
else:
score = [np.mean(toscore)] # compute a single score per video
return score
# =========================================================================
def score_for_multiple_projections(self, toscore):
"""
Returns a list of scores computed by the score method of this class.
Parameters
----------
toscore : 1D :py:class:`numpy.ndarray`
Vector with scores for each frame/sample defining the probability
of the frame being a sample of the real class.
Returns
-------
list_of_scores : [:py:class:`float`]
A list containing the scores.
"""
scores = self.score(
toscore) # returns float score or 1D array of scores
if isinstance(scores, np.float): # if a single score
list_of_scores = [scores]
else:
list_of_scores = list(scores)
return list_of_scores
...@@ -40,7 +40,7 @@ class MultiNetPatchExtractor(Extractor, object): ...@@ -40,7 +40,7 @@ class MultiNetPatchExtractor(Extractor, object):
config_file: str config_file: str
Relative name of the config file. Relative name of the config file.
The path should be relative to ``config_group``, The path should be relative to ``config_group``,
for example: "autoencoder/net1_batl_3_layers_partial.py". for example: "test_data/multi_net_patch_extractor_test_config.py".
This file **must** contain at least the following definitions: This file **must** contain at least the following definitions:
...@@ -59,7 +59,7 @@ class MultiNetPatchExtractor(Extractor, object): ...@@ -59,7 +59,7 @@ class MultiNetPatchExtractor(Extractor, object):
config_group: str config_group: str
Group/package name containing the configuration file. Usually all Group/package name containing the configuration file. Usually all
configs should be stored in this folder/place. configs should be stored in this folder/place.
For example: "bob.learn.pytorch.config". For example: "bob.ip.pytorch_extractor".
Both ``config_file`` and ``config_group`` are used to access the Both ``config_file`` and ``config_group`` are used to access the
configuration module. configuration module.
......
...@@ -2,6 +2,7 @@ from .CNN8 import CNN8Extractor ...@@ -2,6 +2,7 @@ from .CNN8 import CNN8Extractor
from .CasiaNet import CasiaNetExtractor from .CasiaNet import CasiaNetExtractor
from .LightCNN9 import LightCNN9Extractor from .LightCNN9 import LightCNN9Extractor
from .MultiNetPatchExtractor import MultiNetPatchExtractor from .MultiNetPatchExtractor import MultiNetPatchExtractor
from .MLPAlgorithm import MLPAlgorithm
# gets sphinx autodoc done right - don't remove it # gets sphinx autodoc done right - don't remove it
def __appropriate__(*args): def __appropriate__(*args):
...@@ -23,6 +24,7 @@ __appropriate__( ...@@ -23,6 +24,7 @@ __appropriate__(
CasiaNetExtractor, CasiaNetExtractor,
LightCNN9Extractor, LightCNN9Extractor,
MultiNetPatchExtractor, MultiNetPatchExtractor,
MLPAlgorithm,
) )
# gets sphinx autodoc done right - don't remove it # gets sphinx autodoc done right - don't remove it
......
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
import pkg_resources
import numpy import numpy
numpy.random.seed(10) numpy.random.seed(10)
import os
def test_cnn8(): def test_cnn8():
...@@ -65,7 +62,7 @@ def test_multi_net_patch_extractor(): ...@@ -65,7 +62,7 @@ def test_multi_net_patch_extractor():
# ========================================================================= # =========================================================================
# test the extractor: # test the extractor:
CONFIG_FILE = "test_data/net1_test_config.py" # config containing an instance of Composed Transform and a Network class to be used in feature extractor CONFIG_FILE = "test_data/multi_net_patch_extractor_test_config.py" # config containing an instance of Composed Transform and a Network class to be used in feature extractor
CONFIG_GROUP = "bob.ip.pytorch_extractor" CONFIG_GROUP = "bob.ip.pytorch_extractor"
# use specific/unique model for each patch. Models pre-trained on CelebA and fine-tuned (3 layers) on BATL: # use specific/unique model for each patch. Models pre-trained on CelebA and fine-tuned (3 layers) on BATL:
...@@ -98,4 +95,35 @@ def test_multi_net_patch_extractor(): ...@@ -98,4 +95,35 @@ def test_multi_net_patch_extractor():
# plt.show() # plt.show()
def test_mlp_algorithm():
"""
Test the MLPAlgorithm PAD algorithm class.
"""
from bob.ip.pytorch_extractor import MLPAlgorithm
# =========================================================================
# prepare the test data / feature vector:
features = numpy.random.randn(2, 1296)
# =========================================================================
# test the extractor:
CONFIG_FILE = "test_data/mlp_algo_test_config.py" # config containing an instance of Composed Transform and a Network class to be used in feature extractor
CONFIG_GROUP = "bob.ip.pytorch_extractor"
MODEL_FILE = None
algorithm = MLPAlgorithm(config_file = CONFIG_FILE,
config_group = CONFIG_GROUP,
model_file = MODEL_FILE,
url = None,
archive_extension = '.tar.gz',
frame_level_scores_flag = True)
# pass through encoder only, compute latent vector:
score = algorithm.project(features)
# test:
assert score.shape == (2,)
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
@author: Olegs Nikisins
"""
#==============================================================================
# Import here:
import torch
#==============================================================================
# Define parameters here:
"""
Transformations to be applied to the input 1D numpy arrays (feature vectors).
Only conversion to Tensor and unsqueezing is needed to match the input of
TwoLayerMLP network
"""
def transform(x):
"""
Convert input to Tensor and unsqueeze to match the input of
TwoLayerMLP network.
Arguments
---------
x : numpy array
1D numpy array / feature vector.
Return
------
x_transform : Tensor
Torch tensor, transformed ``x`` to be used as MLP input.
"""
return torch.Tensor(x).unsqueeze(0)
"""
Define the network to be trained as a class, named ``Network``.
Note: Do not change the name of the below class, always import as ``Network``.
"""
from bob.learn.pytorch.architectures import TwoLayerMLP as Network
"""
kwargs to be used for ``Network`` initialization. The name must be ``network_kwargs``.
"""
network_kwargs = {}
network_kwargs['in_features'] = 1296
network_kwargs['n_hidden_relu'] = 10
network_kwargs['apply_sigmoid'] = False # don't use sigmoid to make the scores more even
...@@ -8,10 +8,6 @@ ...@@ -8,10 +8,6 @@
from torchvision import transforms from torchvision import transforms
#from bob.pad.face.database import CELEBAPadDatabase
from torch import nn
#============================================================================== #==============================================================================
# Define parameters here: # Define parameters here:
......
...@@ -26,6 +26,7 @@ requirements: ...@@ -26,6 +26,7 @@ requirements:
- bob.core - bob.core
- bob.bio.base - bob.bio.base
- bob.learn.pytorch - bob.learn.pytorch
- bob.pad.base
run: run:
- python - python
- setuptools - setuptools
......
...@@ -6,3 +6,5 @@ bob.bio.base ...@@ -6,3 +6,5 @@ bob.bio.base
bob.learn.pytorch bob.learn.pytorch
torch >= 0.4.0 torch >= 0.4.0
torchvision >= 0.2.0 torchvision >= 0.2.0
bob.pad.base
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment