Commit 78b98390 authored by leswing's avatar leswing
Browse files

Maybe ready for CR

parent c10f34fc
Loading
Loading
Loading
Loading
+101 −43
Original line number Diff line number Diff line
@@ -372,49 +372,93 @@ class Conv1D(Layer):
  """

  def __init__(self,
               filters,
               kernel_size,
               filter_size,
               stride=1,
               padding='SAME',
               activation_fn=tf.nn.relu,
               biases_initializer=tf.random_normal_initializer,
               weights_initializer=tf.random_normal_initializer,
               strides=1,
               padding='valid',
               dilation_rate=1,
               activation=None,
               use_bias=True,
               kernel_initializer='glorot_uniform',
               bias_initializer='zeros',
               kernel_regularizer=None,
               bias_regularizer=None,
               activity_regularizer=None,
               kernel_constraint=None,
               bias_constraint=None,
               in_layers=None,
               **kwargs):
    """Create a Conv1D layer.
    TODO(LESWING) match function prototype to tf.keras.layers

    Parameters
    ----------
    width: int
      the width of the convolutional kernel
    out_channels: int
      the number of outputs produced by the convolutional kernel
    stride: int
      the stride between applications of the convolutional kernel
    padding: str
      the padding method to use, either 'SAME' or 'VALID'
    activation_fn: object
      the Tensorflow activation function to apply to the output
    biases_initializer: callable object
      the initializer for bias values.  This may be None, in which case the layer
      will not include biases.
    weights_initializer: callable object
      the initializer for weight values
    """1D convolution layer (e.g. temporal convolution).

      This layer creates a convolution kernel that is convolved
      with the layer input over a single spatial (or temporal) dimension
      to produce a tensor of outputs.
      If `use_bias` is True, a bias vector is created and added to the outputs.
      Finally, if `activation` is not `None`,
      it is applied to the outputs as well.

      When using this layer as the first layer in a model,
      provide an `input_shape` argument
      (tuple of integers or `None`, e.g.
      `(10, 128)` for sequences of 10 vectors of 128-dimensional vectors,
      or `(None, 128)` for variable-length sequences of 128-dimensional vectors.

      TODO(LESWING): Calculate output shape at construction time
      Arguments:
          filters: Integer, the dimensionality of the output space
              (i.e. the number output of filters in the convolution).
          kernel_size: An integer or tuple/list of a single integer,
              specifying the length of the 1D convolution window.
          strides: An integer or tuple/list of a single integer,
              specifying the stride length of the convolution.
              Specifying any stride value != 1 is incompatible with specifying
              any `dilation_rate` value != 1.
          padding: One of `"valid"`, `"causal"` or `"same"` (case-insensitive).
              `"causal"` results in causal (dilated) convolutions, e.g. output[t]
              does not depend on input[t+1:]. Useful when modeling temporal data
              where the model should not violate the temporal order.
              See [WaveNet: A Generative Model for Raw Audio, section
                2.1](https://arxiv.org/abs/1609.03499).
          dilation_rate: an integer or tuple/list of a single integer, specifying
              the dilation rate to use for dilated convolution.
              Currently, specifying any `dilation_rate` value != 1 is
              incompatible with specifying any `strides` value != 1.
          activation: Activation function to use.
              If you don't specify anything, no activation is applied
              (ie. "linear" activation: `a(x) = x`).
          use_bias: Boolean, whether the layer uses a bias vector.
          kernel_initializer: Initializer for the `kernel` weights matrix.
          bias_initializer: Initializer for the bias vector.
          kernel_regularizer: Regularizer function applied to
              the `kernel` weights matrix.
          bias_regularizer: Regularizer function applied to the bias vector.
          activity_regularizer: Regularizer function applied to
              the output of the layer (its "activation")..
          kernel_constraint: Constraint function applied to the kernel matrix.
          bias_constraint: Constraint function applied to the bias vector.

      Input shape:
          3D tensor with shape: `(batch_size, steps, input_dim)`

      Output shape:
          3D tensor with shape: `(batch_size, new_steps, filters)`
          `steps` value might have changed due to padding or strides.
    """
    self.filters = filters
    self.kernel_size = kernel_size
    self.filter_size = filter_size
    self.stride = stride
    self.strides = strides
    self.padding = padding
    self.activation_fn = activation_fn
    self.weights_initializer = weights_initializer
    self.biases_initializer = biases_initializer
    self.out_tensor = None
    super(Conv1D, self).__init__(**kwargs)
    try:
      parent_shape = self.in_layers[0].shape
      self._shape = (parent_shape[0], parent_shape[1] // stride, filter_size)
    except:
      pass
    self.dilation_rate = dilation_rate
    self.activation = activation
    self.use_bias = use_bias
    self.kernel_initializer = kernel_initializer
    self.bias_initializer = bias_initializer
    self.kernel_regularizer = kernel_regularizer
    self.bias_regularizer = bias_regularizer
    self.activity_regularizer = activity_regularizer
    self.kernel_constraint = kernel_constraint
    self.bias_constraint = bias_constraint
    super(Conv1D, self).__init__(in_layers, **kwargs)

  def create_tensor(self, in_layers=None, set_tensors=True, **kwargs):
    inputs = self._get_input_tensors(in_layers)
@@ -426,10 +470,20 @@ class Conv1D(Layer):
    elif len(parent.get_shape()) != 3:
      raise ValueError("Parent tensor must be (batch, width, channel)")
    out_tensor = tf.keras.layers.Conv1D(
      filters=self.filter_size,
        filters=self.filters,
        kernel_size=self.kernel_size,
      activation=self.activation_fn,
    )(parent)
        strides=self.strides,
        padding=self.padding,
        dilation_rate=self.dilation_rate,
        activation=self.activation,
        use_bias=self.use_bias,
        kernel_initializer=self.kernel_initializer,
        bias_initializer=self.bias_initializer,
        kernel_regularizer=self.kernel_regularizer,
        bias_regularizer=self.bias_regularizer,
        activity_regularizer=self.activity_regularizer,
        kernel_constraint=self.kernel_constraint,
        bias_constraint=self.bias_constraint)(parent)
    if set_tensors:
      self._record_variable_scope(self.name)
      self.out_tensor = out_tensor
@@ -889,21 +943,25 @@ class GRU(Layer):
      self.rnn_initial_states.append(initial_state)
      self.rnn_final_states.append(final_state)
      self.rnn_zero_states.append(np.zeros(zero_state.get_shape(), np.float32))
      self.out_tensors = [
          self.out_tensor, initial_state, final_state, zero_state
      ]
    return out_tensor

  def none_tensors(self):
    saved_tensors = [
        self.out_tensor, self.rnn_initial_states, self.rnn_final_states,
      self.rnn_zero_states
        self.rnn_zero_states, self.out_tensors
    ]
    self.out_tensor = None
    self.rnn_initial_states = []
    self.rnn_final_states = []
    self.rnn_zero_states = []
    self.out_tensors = []
    return saved_tensors

  def set_tensors(self, tensor):
    self.out_tensor, self.rnn_initial_states, self.rnn_final_states, self.rnn_zero_states = tensor
    self.out_tensor, self.rnn_initial_states, self.rnn_final_states, self.rnn_zero_states, self.out_tensors = tensor


class LSTM(Layer):
+141 −36
Original line number Diff line number Diff line
@@ -128,7 +128,7 @@ class SeqToSeq(TensorGraph):
    self._embedding_dimension = embedding_dimension
    self._annealing_final_step = annealing_final_step
    self._annealing_start_step = annealing_start_step
    self._features = layers.Feature(shape=(None, None, len(input_tokens)))
    self._features = self._create_features()
    self._labels = layers.Label(shape=(None, None, len(output_tokens)))
    self._gather_indices = layers.Feature(
        shape=(self.batch_size, 2), dtype=tf.int32)
@@ -139,6 +139,9 @@ class SeqToSeq(TensorGraph):
    self.set_loss(self._create_loss())
    self.add_output(self.output)

  def _create_features(self):
    return layers.Feature(shape=(None, None, len(self._input_tokens)))

  def _create_encoder(self, n_layers, dropout):
    """Create the encoder layers."""
    prev_layer = self._features
@@ -394,59 +397,120 @@ class SeqToSeq(TensorGraph):
      feed_dict = {}
      feed_dict[self._features] = self._create_input_array(inputs)
      feed_dict[self._labels] = self._create_output_array(outputs)
      feed_dict[self._gather_indices] = [(i, len(x))
                                         for i, x in enumerate(inputs)]
      feed_dict[self._gather_indices] = [
          (i, len(x)) for i, x in enumerate(inputs)
      ]
      for initial, zero in zip(self.rnn_initial_states, self.rnn_zero_states):
        feed_dict[initial] = zero
      yield feed_dict


class AspuruGuzikAutoEncoder(SeqToSeq):
  """
  This is an implementation of Automatic Chemical Design Using a Continuous Representation of Molecules
  http://pubs.acs.org/doi/full/10.1021/acscentsci.7b00572

  Abstract
  --------
  We report a method to convert discrete representations of molecules to and
  from a multidimensional continuous representation. This model allows us to
  generate new molecules for efficient exploration and optimization through
  open-ended spaces of chemical compounds. A deep neural network was trained on
  hundreds of thousands of existing chemical structures to construct three
  coupled functions: an encoder, a decoder, and a predictor. The encoder
  converts the discrete representation of a molecule into a real-valued
  continuous vector, and the decoder converts these continuous vectors back to
  discrete molecular representations. The predictor estimates chemical
  properties from the latent continuous vector representation of the molecule.
  Continuous representations of molecules allow us to automatically generate
  novel chemical structures by performing simple operations in the latent space,
  such as decoding random vectors, perturbing known chemical structures, or
  interpolating between molecules. Continuous representations also allow the use
  of powerful gradient-based optimization to efficiently guide the search for
  optimized functional compounds. We demonstrate our method in the domain of
  drug-like molecules and also in a set of molecules with fewer that nine heavy
  atoms.
  We report a method to convert discrete representations of molecules to and from a multidimensional continuous representation. This model allows us to generate new molecules for efficient exploration and optimization through open-ended spaces of chemical compounds. A deep neural network was trained on hundreds of thousands of existing chemical structures to construct three coupled functions: an encoder, a decoder, and a predictor. The encoder converts the discrete representation of a molecule into a real-valued continuous vector, and the decoder converts these continuous vectors back to discrete molecular representations. The predictor estimates chemical properties from the latent continuous vector representation of the molecule. Continuous representations of molecules allow us to automatically generate novel chemical structures by performing simple operations in the latent space, such as decoding random vectors, perturbing known chemical structures, or interpolating between molecules. Continuous representations also allow the use of powerful gradient-based optimization to efficiently guide the search for optimized functional compounds. We demonstrate our method in the domain of drug-like molecules and also in a set of molecules with fewer that nine heavy atoms.

  Notes
  -------
  This is currently an imperfect reproduction of the paper.  One difference is
  that teacher forcing in the decoder is not implemented.  The paper also
  discusses co-learning molecular properties at the same time as training the
  encoder/decoder.  This is not done here.  The hyperparameters chosen are from
  ZINC dataset.

  This network also currently suffers from exploding gradients.  Care has to be taken when training.

  TODO(LESWING): Teacher Forcing
  """

  def __init__(self,
               input_tokens,
               output_tokens,
               max_output_length,
               encoder_layers=4,
               decoder_layers=4,
               embedding_dimension=512,
               dropout=0.0,
               reverse_input=True,
               embedding_dimension=196,
               variational=True,
               annealing_start_step=5000,
               annealing_final_step=10000,
               filter_sizes=[9, 9, 10],
               kernel_sizes=[9, 9, 11],
               decoder_dimension=488,
               **kwargs):
    """
    TODO(LESWING) have qm9/zinc hyper params and construct via static method
    Parameters
    ----------
    filter_sizes: list of int
      Number of filters for each 1D convolution in the encoder
    kernel_sizes: list of int
      Kernel size for each 1D convolution in the encoder
    decoder_dimension: int
      Number of channels for the GRU Decoder
    """
    self.filter_sizes = [9, 9, 10]
    self.kernel_sizes = [9, 9, 11]
    if len(filter_sizes) != len(kernel_sizes):
      raise ValueError("Must have same number of layers and kernels")
    self._filter_sizes = filter_sizes
    self._kernel_sizes = kernel_sizes
    self._decoder_dimension = decoder_dimension
    super(AspuruGuzikAutoEncoder, self).__init__(
      input_tokens,
      output_tokens,
      max_output_length,
      encoder_layers,
      decoder_layers,
      embedding_dimension,
      dropout,
      reverse_input,
      variational,
      annealing_start_step,
      annealing_final_step,
      **kwargs
    )
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        max_output_length=max_output_length,
        embedding_dimension=embedding_dimension,
        variational=variational,
        **kwargs)

  def _create_features(self):
    return layers.Feature(
        shape=(self.batch_size, self._max_output_length + 1,
               len(self._input_tokens)))

  def _create_input_array(self, sequences):
    lengths = [len(x) for x in sequences]
    if self._reverse_input:
      sequences = [reversed(s) for s in sequences]
    features = np.zeros(
        (self.batch_size, self._max_output_length + 1, len(self._input_tokens)),
        dtype=np.float32)
    for i, sequence in enumerate(sequences):
      for j, token in enumerate(sequence):
        features[i, j, self._input_dict[token]] = 1
    features[np.arange(len(sequences)), lengths, self._input_dict[
        SeqToSeq.sequence_end]] = 1
    return features

  def _create_encoder(self, n_layers, dropout):
    """Create the encoder layers."""
    prev_layer = self._features
    for i in range(len(self.filter_sizes)):
      filter_size = self.filter_sizes[i]
      kernel_size = self.kernel_sizes[i]
    for i in range(len(self._filter_sizes)):
      filter_size = self._filter_sizes[i]
      kernel_size = self._kernel_sizes[i]
      if dropout > 0.0:
        prev_layer = layers.Dropout(dropout, in_layers=prev_layer)
      prev_layer = layers.Conv1D(
        kernel_size, filter_size, in_layers=prev_layer, activation_fn=tf.nn.relu)
    prev_layer = layers.Gather(in_layers=[prev_layer, self._gather_indices])
          kernel_size,
          filter_size,
          in_layers=prev_layer,
          activation_fn=tf.nn.relu)
    prev_layer = layers.Flatten(prev_layer)
    if self._variational:
      self._embedding_mean = layers.Dense(
          self._embedding_dimension, in_layers=prev_layer)
@@ -457,9 +521,6 @@ class AspuruGuzikAutoEncoder(SeqToSeq):
    return prev_layer

  def _create_decoder(self, n_layers, dropout):
    """
    TODO(LESWING): Teacher forcing
    """
    """Create the decoder layers."""
    prev_layer = layers.Repeat(
        self._max_output_length, in_layers=self.embedding)
@@ -467,8 +528,52 @@ class AspuruGuzikAutoEncoder(SeqToSeq):
      if dropout > 0.0:
        prev_layer = layers.Dropout(dropout, in_layers=prev_layer)
      prev_layer = layers.GRU(
        488, self.batch_size, in_layers=prev_layer)
    return layers.Dense(
          self._decoder_dimension, self.batch_size, in_layers=prev_layer)
    retval = layers.Dense(
        len(self._output_tokens),
        in_layers=prev_layer,
        activation_fn=tf.nn.softmax)
    return retval

  def _generate_batches(self, sequences):
    """Create feed_dicts for fitting."""
    for batch in self._batch_elements(sequences):
      inputs = []
      outputs = []
      for input, output in batch:
        inputs.append(input)
        outputs.append(output)
      for i in range(len(inputs), self.batch_size):
        inputs.append([])
        outputs.append([])
      feed_dict = {}
      feed_dict[self._features] = self._create_input_array(inputs)
      feed_dict[self._labels] = self._create_output_array(outputs)
      for initial, zero in zip(self.rnn_initial_states, self.rnn_zero_states):
        feed_dict[initial] = zero
      yield feed_dict

  def predict_from_sequences(self, sequences, beam_width=5):
    """Given a set of input sequences, predict the output sequences.

    The prediction is done using a beam search with length normalization.

    Parameters
    ----------
    sequences: iterable
      the input sequences to generate a prediction for
    beam_width: int
      the beam width to use for searching.  Set to 1 to use a simple greedy search.
    """
    result = []
    with self._get_tf("Graph").as_default():
      for batch in self._batch_elements(sequences):
        feed_dict = {}
        feed_dict[self._features] = self._create_input_array(batch)
        feed_dict[self._training_placeholder] = 0.0
        for initial, zero in zip(self.rnn_initial_states, self.rnn_zero_states):
          feed_dict[initial] = zero
        probs = self.session.run(self.output, feed_dict=feed_dict)
        for i in range(len(batch)):
          result.append(self._beam_search(probs[i], beam_width))
    return result