MLP.py 4.49 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

import numpy

from bob.pad.base.algorithm import Algorithm
import bob.learn.mlp
import bob.io.base

from bob.bio.video.utils import FrameContainer
from bob.pad.base.utils import convert_frame_cont_to_array

from bob.core.log import setup
logger = setup("bob.pad.base")


class MLP(Algorithm):
    """Interfaces an MLP classifier used for PAD

    Attributes
    ----------
22
    hidden_units : :py:obj:`tuple` of :any:`int`
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
      The number of hidden units in each hidden layer
    max_iter : int
      The maximum number of training iterations
    """

    def __init__(self, hidden_units=(10, 10), max_iter=1000, precision=0.001, **kwargs):
        """Init function

        Parameters
        ----------
        hidden_units : :py:obj:`tuple` of int
          The number of hidden units in each hidden layer
        max_iter : int
          The maximum number of training iterations
        precision : float
          criterion to stop the training: if the difference
          between current and last loss is smaller than
          this number, then stop training.
        
        """
        Algorithm.__init__(self,  
                           performs_projection=True,
                           requires_projector_training=True, 
                           **kwargs)

        self.hidden_units = hidden_units
        self.max_iter = max_iter
        self.precision = precision
        self.mlp = None


    def train_projector(self, training_features, projector_file):
        """Trains the MLP

57 58
        Parameters
        ----------
59
        training_features : :any:`list` of :py:class:`numpy.ndarray` 
60
          Data used to train the MLP. The real attempts are in training_features[0] and the attacks are in training_features[1]
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        projector_file : str
          Filename where to save the trained model.
        
        """
        # training is done in batch (i.e. using all training data)
        batch_size = len(training_features[0]) + len(training_features[1])
        
        # The labels
        label_real = numpy.zeros((len(training_features[0]), 2), dtype='float64')
        label_real[:, 0] = 1
        label_attack = numpy.zeros((len(training_features[1]), 2), dtype='float64')
        label_attack[:, 1] = 0
        
        real = numpy.array(training_features[0])
        attack = numpy.array(training_features[1])
        X = numpy.vstack([real, attack])
        Y = numpy.vstack([label_real, label_attack])
      
        # Building MLP architecture
        input_dim = real.shape[1]
        shape = []
        shape.append(input_dim)
        for i in range(len(self.hidden_units)):
            shape.append(self.hidden_units[i])
        # last layer contains two units: one for each class (i.e. real and attack)
        shape.append(2)
        shape = tuple(shape)
    
        self.mlp = bob.learn.mlp.Machine(shape)
        self.mlp.output_activation = bob.learn.activation.Logistic()
        self.mlp.randomize()
        trainer = bob.learn.mlp.BackProp(batch_size, bob.learn.mlp.CrossEntropyLoss(self.mlp.output_activation), self.mlp, train_biases=True)
    
        n_iter = 0
        previous_cost = 0
        current_cost = 1
        while (n_iter < self.max_iter) or (abs(previous_cost - current_cost) > self.precision): 
            previous_cost = current_cost
            trainer.train(self.mlp, X, Y)
            current_cost = trainer.cost(self.mlp, X, Y)
            n_iter += 1
            logger.debug("Iteration {} -> cost = {} (previous = {})".format(n_iter, trainer.cost(self.mlp, X, Y), previous_cost))

        f = bob.io.base.HDF5File(projector_file, 'w')
        self.mlp.save(f)
    

    def project(self, feature):
        """Project the given feature
        
        Parameters
        ----------
113 114
        feature : :py:class:`numpy.ndarray` 
          The feature to classify
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

        Returns
        -------
        numpy.ndarray
          The value of the two units in the last layer of the MLP.
        """
       # if isinstance(feature, FrameContainer):
       #     feature = convert_frame_cont_to_array(feature)
        return self.mlp(feature)


    def score(self, toscore):
        """Returns the probability of the real class.

        Parameters
        ----------
        toscore : :py:class:`numpy.ndarray`
       
        Returns
        -------
        float
         probability of the authentication attempt to be real. 
        """
        if toscore.ndim == 1:
            return [toscore[0]]
        else:
            return numpy.mean([toscore[:, 0]])