Commit fd76aee7 authored by Bharath Ramsundar's avatar Bharath Ramsundar Committed by GitHub
Browse files

Merge pull request #817 from peastman/gather

Gather and Log layers
parents 4da7b256 d7b11cc7
Loading
Loading
Loading
Loading
+94 −3
Original line number Diff line number Diff line
@@ -301,7 +301,7 @@ class Dense(Layer):
    self.time_series = time_series
    try:
      parent_shape = self.in_layers[0].shape
      self._shape = (parent_shape[0], out_channels)
      self._shape = tuple(parent_shape[:-1]) + (out_channels,)
    except:
      pass
    self._reuse = False
@@ -517,6 +517,63 @@ class Repeat(Layer):
    return out_tensor


class Gather(Layer):
  """Gather elements or slices from the input."""

  def __init__(self, in_layers=None, indices=None, **kwargs):
    """Create a Gather layer.

    The indices can be viewed as a list of element identifiers, where each
    identifier is itself a length N array specifying the indices of the
    first N dimensions of the input tensor.  Those elements (or slices, depending
    on the shape of the input) are then stacked together.  For example,
    indices=[[0],[2],[4]] will produce [input[0], input[2], input[4]], while
    indices=[[1,2]] will produce [input[1,2]].

    The indices may be specified in two ways.  If they are constants, you can pass
    them to this constructor as a list or array.  Alternatively, the indices can
    be calculated by another layer.  In that case, pass None for indices, and
    instead provide them as the second input layer.

    Parameters
    ----------
    in_layers: list
      the input layers.  If indices is not None, this should be of length 1.  If indices
      is None, this should be of length 2, with the first entry calculating the tensor
      from which to take slices, and the second entry calculating the slice indices.
    indices: array
      the slice indices (if they are constants) or None (if the indices are provided by
      an input)
    """
    self.indices = indices
    super(Gather, self).__init__(in_layers, **kwargs)
    try:
      s = tuple(self.in_layers[0].shape)
      if indices is None:
        s2 = self.in_layers[1].shape
        self._shape = (s2[0],) + s[s2[-1]:]
      else:
        self._shape = (len(indices),) + s[np.array(indices).shape[-1]:]
    except:
      pass

  def create_tensor(self, in_layers=None, set_tensors=True, **kwargs):
    inputs = self._get_input_tensors(in_layers)
    if self.indices is None:
      if len(inputs) != 2:
        raise ValueError("Must have two parents")
      indices = inputs[1]
    if self.indices is not None:
      if len(inputs) != 1:
        raise ValueError("Must have one parent")
      indices = self.indices
    parent_tensor = inputs[0]
    out_tensor = tf.gather_nd(parent_tensor, indices)
    if set_tensors:
      self.out_tensor = out_tensor
    return out_tensor


class GRU(Layer):
  """A Gated Recurrent Unit.

@@ -807,6 +864,14 @@ class Variable(Layer):
    return out_tensor


def _max_dimension(x, y):
  if x is None:
    return y
  if y is None:
    return x
  return max(x, y)


class Add(Layer):
  """Compute the (optionally weighted) sum of the input layers."""

@@ -828,7 +893,7 @@ class Add(Layer):
        shape2, shape1 = shape1, shape2
      offset = len(shape1) - len(shape2)
      for i in range(len(shape2)):
        shape1[i + offset] = max(shape1[i + offset], shape2[i])
        shape1[i + offset] = _max_dimension(shape1[i + offset], shape2[i])
      self._shape = tuple(shape1)
    except:
      pass
@@ -863,7 +928,7 @@ class Multiply(Layer):
        shape2, shape1 = shape1, shape2
      offset = len(shape1) - len(shape2)
      for i in range(len(shape2)):
        shape1[i + offset] = max(shape1[i + offset], shape2[i])
        shape1[i + offset] = _max_dimension(shape1[i + offset], shape2[i])
      self._shape = tuple(shape1)
    except:
      pass
@@ -878,6 +943,26 @@ class Multiply(Layer):
    return out_tensor


class Log(Layer):
  """Compute the natural log of the input."""

  def __init__(self, in_layers=None, **kwargs):
    super(Log, self).__init__(in_layers, **kwargs)
    try:
      self._shape = self.in_layers[0].shape
    except:
      pass

  def create_tensor(self, in_layers=None, set_tensors=True, **kwargs):
    inputs = self._get_input_tensors(in_layers)
    if len(inputs) != 1:
      raise ValueError('Log must have a single parent')
    out_tensor = tf.log(inputs[0])
    if set_tensors:
      self.out_tensor = out_tensor
    return out_tensor


class InteratomicL2Distances(Layer):
  """Compute (squared) L2 Distances between atoms given neighbors."""

@@ -953,6 +1038,8 @@ class SoftMaxCrossEntropy(Layer):
class ReduceMean(Layer):

  def __init__(self, in_layers=None, axis=None, **kwargs):
    if axis is not None and not isinstance(axis, Sequence):
      axis = [axis]
    self.axis = axis
    super(ReduceMean, self).__init__(in_layers, **kwargs)
    if axis is None:
@@ -1001,6 +1088,8 @@ class ToFloat(Layer):
class ReduceSum(Layer):

  def __init__(self, in_layers=None, axis=None, **kwargs):
    if axis is not None and not isinstance(axis, Sequence):
      axis = [axis]
    self.axis = axis
    super(ReduceSum, self).__init__(in_layers, **kwargs)
    if axis is None:
@@ -1030,6 +1119,8 @@ class ReduceSum(Layer):
class ReduceSquareDifference(Layer):

  def __init__(self, in_layers=None, axis=None, **kwargs):
    if axis is not None and not isinstance(axis, Sequence):
      axis = [axis]
    self.axis = axis
    super(ReduceSquareDifference, self).__init__(in_layers, **kwargs)
    if axis is None:
+1 −1
Original line number Diff line number Diff line
@@ -167,7 +167,7 @@ class TensorGraph(Model):
        while True:
          yield {self._training_placeholder: 1.0}
      for d in feed_dict_generator:
        feed_dict = {k.out_tensor: v for k, v in six.iteritems(d)}
        feed_dict = dict(d)
        feed_dict[self._training_placeholder] = 1.0
        yield feed_dict

+18 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ from deepchem.models.tensorgraph.layers import Reshape
from deepchem.models.tensorgraph.layers import Transpose
from deepchem.models.tensorgraph.layers import CombineMeanStd
from deepchem.models.tensorgraph.layers import Repeat
from deepchem.models.tensorgraph.layers import Gather
from deepchem.models.tensorgraph.layers import GRU
from deepchem.models.tensorgraph.layers import TimeSeriesDense
from deepchem.models.tensorgraph.layers import Input
@@ -25,6 +26,7 @@ from deepchem.models.tensorgraph.layers import Constant
from deepchem.models.tensorgraph.layers import Variable
from deepchem.models.tensorgraph.layers import Add
from deepchem.models.tensorgraph.layers import Multiply
from deepchem.models.tensorgraph.layers import Log
from deepchem.models.tensorgraph.layers import InteratomicL2Distances
from deepchem.models.tensorgraph.layers import SoftMaxCrossEntropy
from deepchem.models.tensorgraph.layers import ReduceMean
@@ -146,6 +148,15 @@ class TestLayers(test_util.TensorFlowTestCase):
      out_tensor = out_tensor.eval()
      assert out_tensor.shape == (batch_size, n_repeat, in_dim)

  def test_gather(self):
    """Test that Gather can be invoked."""
    in_tensor = np.random.uniform(size=(5, 4)).astype(np.float32)
    with self.test_session() as sess:
      out_tensor = Gather(indices=[[2], [3]])(in_tensor).eval()
      assert np.array_equal([in_tensor[2], in_tensor[3]], out_tensor)
      out_tensor = Gather()(in_tensor, np.array([[1, 1], [0, 3]])).eval()
      assert np.array_equal([in_tensor[1, 1], in_tensor[0, 3]], out_tensor)

  def test_gru(self):
    """Test that GRU can be invoked."""
    batch_size = 10
@@ -253,6 +264,13 @@ class TestLayers(test_util.TensorFlowTestCase):
                              tf.constant(value3))
      assert np.array_equal(value1 * value2 * value3, out_tensor.eval())

  def test_log(self):
    """Test that Log can be invoked."""
    value = np.random.uniform(size=(2, 3)).astype(np.float32)
    with self.test_session() as sess:
      result = Log()(value).eval()
      assert np.array_equal(np.log(value), result)

  def test_interatomic_distances(self):
    """Test that the interatomic distance calculation works."""
    N_atoms = 5
+21 −1
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ import numpy as np
import tensorflow as tf
from deepchem.models import TensorGraph
from deepchem.models.tensorgraph.layers import Feature, Conv1D, Dense, Flatten, Reshape, Squeeze, Transpose, \
    CombineMeanStd, Repeat, GRU, L2Loss, Concat, SoftMax, Constant, Variable, Add, Multiply, InteratomicL2Distances, \
    CombineMeanStd, Repeat, Gather, GRU, L2Loss, Concat, SoftMax, Constant, Variable, Add, Multiply, Log, InteratomicL2Distances, \
    SoftMaxCrossEntropy, ReduceMean, ToFloat, ReduceSquareDifference, Conv2D, MaxPool, ReduceSum, GraphConv, GraphPool, \
    GraphGather, BatchNorm, WeightedError, \
    LSTMStep, AttnLSTMEmbedding, IterRefLSTMEmbedding
@@ -91,6 +91,16 @@ def test_Repeat_pickle():
  tg.save()


def test_Gather_pickle():
  tg = TensorGraph()
  feature = Feature(shape=(tg.batch_size, 1))
  layer = Gather(indices=[[0], [2], [3]], in_layers=feature)
  tg.add_output(layer)
  tg.set_loss(layer)
  tg.build()
  tg.save()


def test_GRU_pickle():
  tg = TensorGraph()
  feature = Feature(shape=(tg.batch_size, 10, 10))
@@ -153,6 +163,16 @@ def test_Variable_pickle():
  tg.save()


def test_Log_pickle():
  tg = TensorGraph()
  feature = Feature(shape=(tg.batch_size, 1))
  layer = Log(feature)
  tg.add_output(layer)
  tg.set_loss(layer)
  tg.build()
  tg.save()


def testInteratomicL2Distances():
  """
    TODO(LESWING) what is ndim here?