Commit 71bc15c9 authored by Bharath Ramsundar's avatar Bharath Ramsundar
Browse files

DAG changes

parent 38bac34d
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -337,7 +337,16 @@ class DAGModel(KerasModel):
               uncertainty=False,
               batch_size=100,
               **kwargs):
    """
    """Directed Acyclic Graph models for molecular property prediction.

    This model is based on the following paper: 

    Lusci, Alessandro, Gianluca Pollastri, and Pierre Baldi. "Deep architectures and deep learning in chemoinformatics: the prediction of aqueous solubility for drug-like molecules." Journal of chemical information and modeling 53.7 (2013): 1563-1575.

   The basic idea for this paper is that a molecule is usually viewed as an undirected graph. However, you can convert it to a series of directed graphs. The idea is that for each atom, you make a DAG using that atom as the vertex of the DAG and edges pointing "inwards" to it. This transformation is implemented in dc.trans.transformers.DAGTransformer.UG_to_DAG.

   This model accepts ConvMols as input, just as GraphConvModel does, but these ConvMol objects must be transformed by dc.trans.DAGTransformer. 

    Parameters
    ----------
    n_tasks: int
+36 −23
Original line number Diff line number Diff line
@@ -2363,7 +2363,15 @@ class DAGLayer(tf.keras.layers.Layer):
               dropout=None,
               batch_size=64,
               **kwargs):
    """
    """DAG computation layer.

    This layer generates a directed acyclic graph for each atom
    in a molecule. This layer is based on the algorithm from the
    following paper: 

    Lusci, Alessandro, Gianluca Pollastri, and Pierre Baldi. "Deep architectures and deep learning in chemoinformatics: the prediction of aqueous solubility for drug-like molecules." Journal of chemical information and modeling 53.7 (2013): 1563-1575.

  
    Parameters
    ----------
    n_graph_feat: int, optional
@@ -2427,6 +2435,10 @@ class DAGLayer(tf.keras.layers.Layer):
    self.b_list.append(backend.zeros(shape=[
        self.n_outputs,
    ]))
    with tf.init_scope():
      graph_features_initial = tf.zeros((self.max_atoms * self.batch_size,
                                         self.max_atoms + 1, self.n_graph_feat))
      self.graph_features = tf.Variable(graph_features_initial, trainable=False)
    self.built = True

  def call(self, inputs):
@@ -2436,20 +2448,21 @@ class DAGLayer(tf.keras.layers.Layer):
    atom_features = inputs[0]
    # each atom corresponds to a graph, which is represented by the `max_atoms*max_atoms` int32 matrix of index
    # each gragh include `max_atoms` of steps(corresponding to rows) of calculating graph features
    parents = inputs[1]
    parents = tf.cast(inputs[1], dtype=tf.int32)
    # target atoms for each step: (batch_size*max_atoms) * max_atoms
    calculation_orders = inputs[2]
    calculation_masks = inputs[3]

    n_atoms = tf.squeeze(inputs[4])
    dropout_switch = tf.squeeze(inputs[5])
    with tf.init_scope():
      # initialize graph features for each graph
      graph_features_initial = tf.zeros((self.max_atoms * self.batch_size,
                                         self.max_atoms + 1, self.n_graph_feat))
      # initialize graph features for each graph
      # another row of zeros is generated for padded dummy atoms
      graph_features = tf.Variable(graph_features_initial, trainable=False)
    #with tf.init_scope():
    #  # initialize graph features for each graph
    #  graph_features_initial = tf.zeros((self.max_atoms * self.batch_size,
    #                                     self.max_atoms + 1, self.n_graph_feat))
    #  # initialize graph features for each graph
    #  # another row of zeros is generated for padded dummy atoms
    #  #graph_features = tf.Variable(graph_features_initial, trainable=False)
    #  self.graph_features.assign(graph_features_initial)

    for count in range(self.max_atoms):
      # `count`-th step
@@ -2459,20 +2472,16 @@ class DAGLayer(tf.keras.layers.Layer):
      batch_atom_features = tf.gather(atom_features, current_round)

      # generating index for graph features used in the inputs
      index = tf.stack(
          [
              tf.reshape(
      stack1 = tf.reshape(
          tf.stack(
                      [tf.boolean_mask(tf.range(n_atoms), mask)] *
                      (self.max_atoms - 1),
                      axis=1), [-1]),
              tf.reshape(tf.boolean_mask(parents[:, count, 1:], mask), [-1])
          ],
          axis=1)
              [tf.boolean_mask(tf.range(n_atoms), mask)] * (self.max_atoms - 1),
              axis=1), [-1])
      stack2 = tf.reshape(tf.boolean_mask(parents[:, count, 1:], mask), [-1])
      index = tf.stack([stack1, stack2], axis=1)
      # extracting graph features for parents of the target atoms, then flatten
      # shape: (batch_size*max_atoms) * [(max_atoms-1)*n_graph_features]
      batch_graph_features = tf.reshape(
          tf.gather_nd(graph_features, index),
          tf.gather_nd(self.graph_features, index),
          [-1, (self.max_atoms - 1) * self.n_graph_feat])

      # concat into the input tensor: (batch_size*max_atoms) * n_inputs
@@ -2489,8 +2498,11 @@ class DAGLayer(tf.keras.layers.Layer):
      target_index = tf.stack([tf.range(n_atoms), parents[:, count, 0]], axis=1)
      target_index = tf.boolean_mask(target_index, mask)
      # update the graph features for target atoms
      graph_features = tf.compat.v1.scatter_nd_update(
          graph_features, target_index, batch_outputs)
      #self.graph_features = tf.compat.v1.scatter_nd_update(
      #    self.graph_features, target_index, batch_outputs)
      self.graph_features.assign_add(
          tf.compat.v1.scatter_nd_update(self.graph_features, target_index,
                                         batch_outputs))
    return batch_outputs


@@ -2505,7 +2517,8 @@ class DAGGather(tf.keras.layers.Layer):
               activation='relu',
               dropout=None,
               **kwargs):
    """
    """DAG vector gathering layer

    Parameters
    ----------
    n_graph_feat: int, optional
+28 −0
Original line number Diff line number Diff line
@@ -305,3 +305,31 @@ class TestGraphModels(unittest.TestCase):
    pred = model.predict(dataset)
    mean_rel_error = np.mean(np.abs(1 - pred / y))
    assert mean_rel_error < 0.1

  def test_dag_model(self):
    tasks, dataset, transformers, metric = self.get_dataset(
        'regression', 'GraphConv')
    dag_transformer = dc.trans.DAGTransformer(max_atoms=50)
    dataset = dag_transformer.transform(dataset)

    n_tasks = len(tasks)
    n_feat = 75
    batch_size = 10
    model = dc.models.DAGModel(
        n_tasks,
        max_atoms=50,
        n_atom_feat=n_feat,
        batch_size=batch_size,
        learning_rate=0.001,
        use_queue=False,
        mode="regression")

    # Fit trained model
    model.fit(dataset, nb_epoch=1)
    #batch_size = 50
    #model = GraphConvModel(
    #    len(tasks), batch_size=batch_size, mode='classification')

    #model.fit(dataset, nb_epoch=10)
    #scores = model.evaluate(dataset, [metric], transformers)
    #assert scores['mean-roc_auc_score'] >= 0.9
+53 −0
Original line number Diff line number Diff line
@@ -354,3 +354,56 @@ class TestLayers(test_util.TensorFlowTestCase):

    result3 = layer([V, adjs])
    assert np.allclose(result, result3)

  def test_DAG_layer(self):
    """Test invoking DAGLayer."""
    batch_size = 10
    n_graph_feat = 30
    n_atom_feat = 75
    max_atoms = 50
    layer_sizes = [100]
    atom_features = np.random.rand(batch_size, n_atom_feat)
    parents = np.random.randint(
        0, max_atoms, size=(batch_size, max_atoms, max_atoms))
    calculation_orders = np.random.randint(
        0, batch_size, size=(batch_size, max_atoms))
    calculation_masks = np.random.randint(0, 2, size=(batch_size, max_atoms))
    # Recall that the DAG layer expects a MultiConvMol as input,
    # so the "batch" is a pooled set of atoms from all the
    # molecules in the batch, just as it is for the graph conv.
    # This means that n_atoms is the batch-size
    n_atoms = batch_size
    dropout_switch = False
    layer = layers.DAGLayer(
        n_graph_feat=n_graph_feat,
        n_atom_feat=n_atom_feat,
        max_atoms=max_atoms,
        layer_sizes=layer_sizes)
    outputs = layer([
        atom_features, parents, calculation_orders, calculation_masks, n_atoms,
        dropout_switch
    ])
    # TODO(rbharath): What is the shape of outputs supposed to be?
    # I'm getting (7, 30) here. Where does 7 come from??
    print("outputs.shape")
    print(outputs.shape)

  def test_DAG_gather(self):
    """Test invoking DAGGather."""
    # TODO(rbharath): We need more documentation about why
    # these numbers work.
    batch_size = 10
    n_graph_feat = 30
    n_atom_feat = 30
    n_outputs = 75
    max_atoms = 50
    layer_sizes = [100]
    layer = layers.DAGGather(
        n_graph_feat=n_graph_feat,
        n_outputs=n_outputs,
        max_atoms=max_atoms,
        layer_sizes=layer_sizes)
    atom_features = np.random.rand(batch_size, n_atom_feat)
    membership = np.sort(np.random.randint(0, batch_size, size=(batch_size)))
    dropout_switch = False
    outputs = layer([atom_features, membership, dropout_switch])
+3 −1
Original line number Diff line number Diff line
@@ -645,9 +645,11 @@ class TestOverfit(test_util.TensorFlowTestCase):
        mode="regression")

    # Fit trained model
    model.fit(dataset, nb_epoch=50)
    model.fit(dataset, nb_epoch=100)
    # Eval model on train
    scores = model.evaluate(dataset, [regression_metric])
    print("scores")
    print(scores)

    assert scores[regression_metric.name] > .8

Loading