Source code for perceptron.models.classification.keras

# Copyright 2019 Baidu Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.

"""Classification model wrapper for Keras."""

from __future__ import absolute_import
import numpy as np
import logging
from perceptron.models.base import DifferentiableModel

[docs]class KerasModel(DifferentiableModel): """Create a :class:`Model` instance from a `Keras` model. Parameters ---------- model : `keras.model.Model` The `Keras` model that are loaded. bounds : tuple Tuple of lower and upper bound for the pixel values, usually (0, 1) or (0, 255). channel_axis : int The index of the axis that represents color channels. preprocessing: 2-element tuple with floats or numpy arrays Elementwises preprocessing of input; we first substract the first element of preprocessing from the input and then divide the input by the second element. predicts : str Specifies whether the `Keras` model predicts logits or probabilities. Logits are preferred, but probabilities are the default. """ def __init__( self, model, bounds, channel_axis=3, preprocessing=(0, 1), predicts='probabilities'): super(KerasModel, self).__init__(bounds=bounds, channel_axis=channel_axis, preprocessing=preprocessing) from keras import backend as K import keras self._task = 'cls' if predicts == 'probs': predicts = 'probabilities' assert predicts in ['probabilities', 'logits'] images_input = model.input label_input = K.placeholder(shape=(1,)) predictions = model.output shape = K.int_shape(predictions) _, num_classes = shape assert num_classes is not None self._num_classes = num_classes if predicts == 'probabilities': if K.backend() == 'tensorflow': predictions, = predictions.op.inputs loss = K.sparse_categorical_crossentropy( label_input, predictions, from_logits=True) elif predicts == 'logits': loss = K.sparse_categorical_crossentropy( label_input, predictions, from_logits=True) # sparse_categorical_crossentropy returns 1-dim tensor, # gradients wants 0-dim tensor loss = K.squeeze(loss, axis=0) grads = K.gradients(loss, images_input) grad_loss_output = K.placeholder(shape=(num_classes, 1)) external_loss =, grad_loss_output) # remove batch dimension of predictions external_loss = K.squeeze(external_loss, axis=0) # remove singleton dimension of grad_loss_output external_loss = K.squeeze(external_loss, axis=0) grads_loss_input = K.gradients(external_loss, images_input) if K.backend() == 'tensorflow': # tensorflow backend returns a list with the gradient # as the only element, even if loss is a single scalar tensor assert isinstance(grads, list) assert len(grads) == 1 grad = grads[0] assert isinstance(grads_loss_input, list) assert len(grads_loss_input) == 1 grad_loss_input = grads_loss_input[0] self._loss_fn = K.function( [images_input, label_input], [loss]) self._batch_pred_fn = K.function( [images_input], [predictions]) self._pred_grad_fn = K.function( [images_input, label_input], [predictions, grad]) self._bw_grad_fn = K.function( [grad_loss_output, images_input], [grad_loss_input]) def _to_logits(self, predictions): from keras import backend as K eps = 10e-8 predictions = K.clip(predictions, eps, 1 - eps) predictions = K.log(predictions) return predictions
[docs] def num_classes(self): """Return number of classes.""" return self._num_classes
[docs] def model_task(self): """Get the task that the model is used for.""" return self._task
[docs] def batch_predictions(self, images): """Batch prediction of images.""" px, _ = self._process_input(images) predictions = self._batch_pred_fn([px]) assert len(predictions) == 1 predictions = predictions[0] assert predictions.shape == (images.shape[0], self.num_classes()) return predictions
[docs] def predictions_and_gradient(self, image, label): """Returns both predictions and gradients.""" input_shape = image.shape px, dpdx = self._process_input(image) predictions, gradient = self._pred_grad_fn([ px[np.newaxis], np.array([label])]) predictions = np.squeeze(predictions, axis=0) gradient = np.squeeze(gradient, axis=0) gradient = self._process_gradient(dpdx, gradient) assert predictions.shape == (self.num_classes(),) assert gradient.shape == input_shape return predictions, gradient
[docs] def backward(self, gradient, image): """Get gradients w.r.t. the original image.""" assert gradient.ndim == 1 gradient = np.reshape(gradient, (-1, 1)) px, dpdx = self._process_input(image) gradient = self._bw_grad_fn([ gradient, px[np.newaxis], ]) gradient = gradient[0] # output of bw_grad_fn is a list gradient = np.squeeze(gradient, axis=0) gradient = self._process_gradient(dpdx, gradient) assert gradient.shape == image.shape return gradient