Commit 92835804 authored by peastman's avatar peastman
Browse files

Added TensorGraph.__call__()

parent db395e85
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -104,8 +104,29 @@ class Layer(object):
      return self.clone(in_layers)
    raise ValueError('%s does not implement shared()' % self.__class__.__name__)

  def __call__(self, *in_layers, **kwargs):
    return self.create_tensor(in_layers=in_layers, set_tensors=False, **kwargs)
  def __call__(self, *inputs, **kwargs):
    """Execute the layer in eager mode to compute its output as a function of inputs.

    If the layer defines any variables, they are created the first time it is invoked.

    Arbitrary keyword arguments may be specified after the list of inputs.  Most
    layers do not expect or use any additional arguments, but there are a few
    significant cases.

    - Recurrent layers usually accept an argument `initial_state` which can be
      used to specify the initial state for the recurrent cell.  When this
      argument is omitted, they use a default initial state, usually all zeros.
    - A few layers behave differently during training than during inference,
      such as Dropout and CombineMeanStd.  You can specify a boolean value with
      the `training` argument to tell it which mode it is being called in.

    Parameters
    ----------
    inputs: tensors
      the inputs to pass to the layer.  The values may be tensors, numpy arrays,
      or anything else that can be converted to tensors of the correct shape.
    """
    return self.create_tensor(in_layers=inputs, set_tensors=False, **kwargs)

  @property
  def shape(self):
+34 −0
Original line number Diff line number Diff line
@@ -325,6 +325,40 @@ class TensorGraph(Model):
          feed_dict[initial_state] = zero_state
        yield feed_dict

  def __call__(self, *inputs, outputs=None):
    """Execute the model in eager mode to compute outputs as a function of inputs.

    This is very similar to predict_on_batch(), except that it returns the outputs
    as tensors rather than numpy arrays.  That means you can compute the graph's
    outputs, then do additional calculations based on them, and gradients will
    be tracked correctly through the whole process.

    Parameters
    ----------
    inputs: tensors
      the values to use for the model's features.  The number of inputs must
      exactly match the length of the model's `features` property.  The values
      may be tensors, numpy arrays, or anything else that can be converted to
      tensors of the correct shape.
    outputs: list of Layers
      the output layers to compute.  If this is None, self.outputs is used
      (that is, all outputs that have been added by calling add_output()).

    Returns
    -------
    The output tensors, or a list of tensors if multiple outputs were requested.
    """
    if len(inputs) != len(self.features):
      raise ValueError('Expected %d inputs, received %d' % len(self.features),
                       len(inputs))
    if outputs is None:
      outputs = self.outputs
    feed_dict = dict(zip(self.features, inputs))
    results = self._run_graph(outputs, feed_dict, False)
    if len(results) == 1:
      return results[0]
    return results

  def predict_on_generator(self, generator, transformers=[], outputs=None):
    """
    Parameters
+49 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ from deepchem.data import NumpyDataset
from deepchem.data.datasets import Databag
from deepchem.models.tensorgraph.layers import Dense, SoftMaxCrossEntropy, ReduceMean, SoftMax, Constant, Variable
from deepchem.models.tensorgraph.layers import Feature, Label
from deepchem.models.tensorgraph.layers import ReduceSquareDifference, Add
from deepchem.models.tensorgraph.layers import ReduceSquareDifference, Add, GRU
from deepchem.models.tensorgraph.tensor_graph import TensorGraph
from deepchem.models.tensorgraph.optimizers import GradientDescent, ExponentialDecay
import tensorflow.contrib.eager as tfe
@@ -496,3 +496,51 @@ class TestTensorGraph(unittest.TestCase):
    with context.eager_mode():
      with tfe.IsolateTest():
        self.test_submodels()

  def test_recurrent_layer(self):
    """Test a model that includes a recurrent layer."""
    batch_size = 5
    tg = dc.models.TensorGraph(batch_size=batch_size, use_queue=False)
    features = Feature(shape=(None, 10, 1))
    gru = GRU(10, batch_size, in_layers=features)
    loss = ReduceMean(in_layers=gru)
    tg.add_output(gru)
    tg.set_loss(loss)
    input = np.random.rand(batch_size, 10, 1)

    # If we don't specify the initial state, it should default to zeros.

    predictions1 = tg.predict_on_batch(input)

    # Explicitly specifying the zero state should give the same result.

    initial_state = gru.rnn_initial_states[0]
    zero_state = gru.rnn_zero_states[0]
    generator = [{features: input, initial_state: zero_state}]
    predictions2 = tg.predict_on_generator(generator)

    # Specifying a different initial state should produce a different result.

    generator = [{features: input, initial_state: np.ones(zero_state.shape)}]
    predictions3 = tg.predict_on_generator(generator)
    assert np.allclose(predictions1, predictions2)
    assert not np.allclose(predictions1, predictions3)

  def test_invoke_model_eager(self):
    """Test invoking the model with __call__() in eager mode."""
    with context.eager_mode():
      with tfe.IsolateTest():
        batch_size = 5
        tg = dc.models.TensorGraph(batch_size=batch_size)
        features = Feature(shape=(None, 10))
        dense = Dense(10, in_layers=features)
        loss = ReduceMean(in_layers=dense)
        tg.add_output(dense)
        tg.set_loss(loss)
        input = np.random.rand(batch_size, 10).astype(np.float32)

        # We should get the same result with either predict_on_batch() or __call__().

        output1 = tg.predict_on_batch(input)
        output2 = tg(input)
        assert np.allclose(output1, output2.numpy())