Commit ab7bc3a7 authored by peastman's avatar peastman
Browse files

Implemented Shared layer

parent 8b2312b8
Loading
Loading
Loading
Loading
+62 −10
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ class Layer(object):
    -------
    Layer
    """
    raise NotImplementedError("Each Layer must implement shared for itself")
    return Shared(self, in_layers=in_layers)

  def __call__(self, *in_layers):
    return self.create_tensor(in_layers=in_layers, set_tensors=False)
@@ -173,7 +173,7 @@ class Layer(object):
    elif self.summary_op == 'histogram':
      tf.summary.histogram(self.name, self.tb_input, self.collections)

  def copy(self, replacements={}, variables_graph=None):
  def copy(self, replacements={}, variables_graph=None, shared=False):
    """Duplicate this Layer and all its inputs.

    This creates and returns a clone of this layer.  It also recursively calls
@@ -210,12 +210,20 @@ class Layer(object):
      the current value of each variable in each layer is recorded, and the copy
      has that value specified as its initial value.  This allows a piece of a
      pre-trained model to be copied to another model.
    shared: bool
      if True, create Shared layers instead of directly cloning the input layers.
      This means the newly created layers will share variables with the original
      ones.
    """
    if self in replacements:
      return replacements[self]
    copied_inputs = [
        layer.copy(replacements, variables_graph) for layer in self.in_layers
        layer.copy(replacements, variables_graph, shared)
        for layer in self.in_layers
    ]
    if shared:
      copy = Shared(self, in_layers=copied_inputs)
    else:
      saved_inputs = self.in_layers
      self.in_layers = []
      saved_tensors = self.none_tensors()
@@ -224,6 +232,8 @@ class Layer(object):
      self.set_tensors(saved_tensors)
      copy.in_layers = copied_inputs
    if variables_graph is not None:
      if shared:
        raise ValueError('Cannot specify variables_graph when shared==True')
      variables = variables_graph.get_layer_variables(self)
      if len(variables) > 0:
        with variables_graph._get_tf("Graph").as_default():
@@ -314,6 +324,48 @@ def convert_to_layers(in_layers):
  return layers


class Shared(Layer):
  """A copy of another layer that shares variables with it.

  A Shared layer duplicates all the computations of another layer so those
  computations may be performed on a second set of inputs.  It does this while
  sharing variables with the original layer.
  """

  def __init__(self, original_layer, **kwargs):
    """Create a Shared layer.

    Parameters
    ----------
    original_layer: Layer
      the Layer whose computations this layer should duplicate, and with which
      it should share variables
    """
    super(Shared, self).__init__(**kwargs)
    self.original_layer = original_layer

  def create_tensor(self, in_layers=None, set_tensors=True, **kwargs):
    inputs = self._get_input_tensors(in_layers)
    if len(inputs) != len(self.original_layer.in_layers):
      raise ValueError(
          "Shared must have the same number of inputs as the original layer")
    replacements = {}
    for original, input in zip(self.original_layer.in_layers, inputs):
      replacements[original.out_tensor] = input
    if self.original_layer.variable_scope != '':
      for var in tf.get_collection(
          tf.GraphKeys.TRAINABLE_VARIABLES,
          scope=self.original_layer.variable_scope):
        var_tensor = tf.convert_to_tensor(var)
        replacements[var_tensor] = var_tensor
    out_tensor = tf.contrib.graph_editor.graph_replace(
        self.original_layer.out_tensor, replacements)
    if set_tensors:
      self._record_variable_scope(self.original_layer.variable_scope)
      self.out_tensor = out_tensor
    return out_tensor


class Conv1D(Layer):
  """A 1D convolution on the input.

+3 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ from tensorflow.python.framework.errors_impl import OutOfRangeError
from deepchem.data import NumpyDataset
from deepchem.metrics import to_one_hot, from_one_hot
from deepchem.models.models import Model
from deepchem.models.tensorgraph.layers import InputFifoQueue, Label, Feature, Weights, Constant
from deepchem.models.tensorgraph.layers import InputFifoQueue, Label, Feature, Weights, Constant, Shared
from deepchem.models.tensorgraph.optimizers import Adam
from deepchem.trans import undo_transforms
from deepchem.utils.evaluate import GeneratorEvaluator
@@ -442,6 +442,8 @@ class TensorGraph(Model):
    def add_layers_to_list(layer, sorted_layers):
      if layer in sorted_layers:
        return
      if isinstance(layer, Shared):
        add_layers_to_list(layer.original_layer, sorted_layers)
      for in_layer in layer.in_layers:
        add_layers_to_list(in_layer, sorted_layers)
      sorted_layers.append(layer)
+12 −1
Original line number Diff line number Diff line
@@ -10,10 +10,21 @@ from deepchem.models.tensorgraph.layers import Feature, Conv1D, Dense, Flatten,
  SoftMaxCrossEntropy, ReduceMean, ToFloat, ReduceSquareDifference, Conv2D, MaxPool2D, ReduceSum, GraphConv, GraphPool, \
  GraphGather, BatchNorm, WeightedError, \
  Conv3D, MaxPool3D, Conv2DTranspose, Conv3DTranspose, \
  LSTMStep, AttnLSTMEmbedding, IterRefLSTMEmbedding, GraphEmbedPoolLayer, GraphCNN
  LSTMStep, AttnLSTMEmbedding, IterRefLSTMEmbedding, GraphEmbedPoolLayer, GraphCNN, Shared
from deepchem.models.tensorgraph.symmetry_functions import AtomicDifferentiatedDense


def test_Shared_pickle():
  tg = TensorGraph()
  feature = Feature(shape=(tg.batch_size, 1, 1))
  dense = Dense(out_channels=1, in_layers=feature)
  shared = Shared(dense, in_layers=feature)
  tg.add_output(shared)
  tg.set_loss(shared)
  tg.build()
  tg.save()


def test_Conv1D_pickle():
  tg = TensorGraph()
  feature = Feature(shape=(tg.batch_size, 1, 1))
+26 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ import deepchem as dc
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 Feature, Label, Shared
from deepchem.models.tensorgraph.layers import ReduceSquareDifference, Add
from deepchem.models.tensorgraph.tensor_graph import TensorGraph
from deepchem.models.tensorgraph.optimizers import GradientDescent, ExponentialDecay
@@ -361,6 +361,31 @@ class TestTensorGraph(unittest.TestCase):
    for v1, v2 in zip(values, copy.in_layers[0].variable_values):
      assert np.array_equal(v1, v2)

  def test_copy_layers_shared(self):
    """Test copying layers with shared variables."""
    tg = dc.models.TensorGraph()
    features = Feature(shape=(None, 10))
    dense = Dense(
        10, in_layers=features, biases_initializer=tf.random_normal_initializer)
    constant = Constant(10.0)
    output = dense + constant
    tg.add_output(output)
    tg.set_loss(output)
    replacements = {features: features, constant: Constant(20.0)}
    copy = output.copy(replacements, shared=True)
    tg.add_output(copy)
    assert isinstance(copy, Shared)
    assert isinstance(copy.in_layers[0], Shared)
    assert isinstance(copy.in_layers[0].in_layers[0], Feature)
    assert copy.in_layers[1] == replacements[constant]
    variables1 = tg.get_layer_variables(dense)
    variables2 = tg.get_layer_variables(copy.in_layers[0])
    for v1, v2, in zip(variables1, variables2):
      assert v1 == v2
    feed_dict = {features: np.random.random((5, 10))}
    v1, v2 = tg.predict_on_generator([feed_dict], outputs=[output, copy])
    assert_true(np.all(np.isclose(v1 + 10, v2)))

  def test_submodels(self):
    """Test optimizing submodels."""
    tg = dc.models.TensorGraph(learning_rate=0.1, batch_size=1)