Commit 386aa785 authored by Bharath Ramsundar's avatar Bharath Ramsundar Committed by Nathan Frey
Browse files

Cleaning up atomic conv some more

parent 1e810c94
Loading
Loading
Loading
Loading
+131 −62
Original line number Diff line number Diff line
import sys

import logging
import deepchem as dc
from deepchem.models import KerasModel
from deepchem.models.layers import AtomicConvolution
from deepchem.models.losses import L2Loss
from tensorflow.keras.layers import Input, Layer, Dense, Flatten, Concatenate 
from tensorflow.keras.layers import Input, Dense, Reshape, Softmax, Dropout, Activation, Lambda

import numpy as np
import tensorflow as tf
import itertools
try:
  from collections.abc import Sequence as SequenceCollection
except:
  from collections import Sequence as SequenceCollection
from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, Union
from deepchem.utils.typing import KerasActivationFn, LossFn, OneOrMany


def initializeWeightsBiases(prev_layer_size,
                            size,
                            weights=None,
                            biases=None,
                            name=None):
  """Initializes weights and biases to be used in a fully-connected layer.

  Parameters
  ----------
  prev_layer_size: int
    Number of features in previous layer.
  size: int
    Number of nodes in this layer.
  weights: tf.Tensor, optional (Default None)
    Weight tensor.
  biases: tf.Tensor, optional (Default None)
    Bias tensor.
  name: str
    Name for this op, optional (Defaults to 'fully_connected' if None)

  Returns
  -------
  weights: tf.Variable
    Initialized weights.
  biases: tf.Variable
    Initialized biases.

  """

  if weights is None:
    weights = tf.random.truncated_normal([prev_layer_size, size], stddev=0.01)
  if biases is None:
    biases = tf.zeros([size])

  w = tf.Variable(weights, name='w')
  b = tf.Variable(biases, name='b')
  return w, b

logger = logging.getLogger(__name__)

class AtomicConvScore(Layer):
  """The scoring function used by the atomic convolution models."""
@@ -97,16 +68,20 @@ class AtomicConvScore(Layer):
    def atomnet(current_input, atomtype):
      prev_layer = current_input
      for i in range(num_layers):
        layer = tf.nn.bias_add(
            tf.matmul(prev_layer, self.type_weights[atomtype][i]),
            self.type_biases[atomtype][i])
        layer = tf.nn.relu(layer)
        #layer = tf.nn.bias_add(
        #    tf.matmul(prev_layer, self.type_weights[atomtype][i]),
        #    self.type_biases[atomtype][i])
        #layer = tf.nn.relu(layer)
        layer = Dense(100)(prev_layer)
        prev_layer = layer

      output_layer = tf.squeeze(
          tf.nn.bias_add(
              tf.matmul(prev_layer, self.output_weights[atomtype][0]),
              self.output_biases[atomtype][0]))
      #output_layer = tf.squeeze(
      #    tf.nn.bias_add(
      #        tf.matmul(prev_layer, self.output_weights[atomtype][0]),
      #        self.output_biases[atomtype][0]))
      print("self.output_weights[atomtype][0].shape")
      print(self.output_weights[atomtype][0].shape)
      output_layer = Dense(self.output_weights[atomtype][0].shape[0])(prev_layer)
      return output_layer

    frag1_zeros = tf.zeros_like(frag1_z, dtype=tf.float32)
@@ -157,25 +132,36 @@ class AtomicConvModel(KerasModel):
  """

  def __init__(self,
               frag1_num_atoms=70,
               frag2_num_atoms=634,
               complex_num_atoms=701,
               max_num_neighbors=12,
               batch_size=24,
               atom_types=[
               n_tasks: int,
               frag1_num_atoms: int =70,
               frag2_num_atoms: int =634,
               complex_num_atoms: int=701,
               max_num_neighbors: int=12,
               batch_size: int=24,
               atom_types: Sequence[float]=[
                   6, 7., 8., 9., 11., 12., 15., 16., 17., 20., 25., 30., 35.,
                   53., -1.
               ],
               radial=[[
               radial: Sequence[Sequence[float]]=[[
                   1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0,
                   7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0
               ], [0.0, 4.0, 8.0], [0.4]],
               layer_sizes=[32, 32, 16],
               #layer_sizes=[32, 32, 16],
               layer_sizes=[100],
               weight_init_stddevs: OneOrMany[float] = 0.02,
               bias_init_consts: OneOrMany[float] = 1.0,
               weight_decay_penalty: float = 0.0,
               weight_decay_penalty_type: str = "l2",
               dropouts: OneOrMany[float] = 0.5,
               activation_fns: OneOrMany[KerasActivationFn] = tf.nn.relu,
               residual: bool = False,
               learning_rate=0.001,
               **kwargs):
               **kwargs) -> None:
    """
    Parameters
    ----------
    n_tasks: int
      number of tasks
    frag1_num_atoms: int
      Number of atoms in first fragment
    frag2_num_atoms: int
@@ -187,9 +173,35 @@ class AtomicConvModel(KerasModel):
      List of atoms recognized by model. Atoms are indicated by their
      nuclear numbers.
    radial: list
      TODO: add description
      Radial parameters used in the atomic convolution transformation.
    layer_sizes: list
      TODO: add description
      the size of each dense layer in the network.  The length of
      this list determines the number of layers.
    weight_init_stddevs: list or float
      the standard deviation of the distribution to use for weight
      initialization of each layer.  The length of this list should
      equal len(layer_sizes).  Alternatively this may be a single
      value instead of a list, in which case the same value is used
      for every layer.
    bias_init_consts: list or float
      the value to initialize the biases in each layer to.  The
      length of this list should equal len(layer_sizes).
      Alternatively this may be a single value instead of a list, in
      which case the same value is used for every layer.
    weight_decay_penalty: float
      the magnitude of the weight decay penalty to use
    weight_decay_penalty_type: str
      the type of penalty to use for weight decay, either 'l1' or 'l2'
    dropouts: list or float
      the dropout probablity to use for each layer.  The length of this list should equal len(layer_sizes).
      Alternatively this may be a single value instead of a list, in which case the same value is used for every layer.
    activation_fns: list or object
      the Tensorflow activation function to apply to each layer.  The length of this list should equal
      len(layer_sizes).  Alternatively this may be a single value instead of a list, in which case the
      same value is used for every layer.
    residual: bool
      if True, the model will be composed of pre-activation residual blocks instead
      of a simple stack of dense layers.
    learning_rate: float
      Learning rate for the model.
    """
@@ -234,14 +246,71 @@ class AtomicConvModel(KerasModel):

    concat = Concatenate()([flattened1, flattened2, flattened3])

    n_layers = len(layer_sizes)
    if not isinstance(weight_init_stddevs, SequenceCollection):
      weight_init_stddevs = [weight_init_stddevs] * n_layers
    if not isinstance(bias_init_consts, SequenceCollection):
      bias_init_consts = [bias_init_consts] * n_layers
    if not isinstance(dropouts, SequenceCollection):
      dropouts = [dropouts] * n_layers
    if not isinstance(activation_fns, SequenceCollection):
      activation_fns = [activation_fns] * n_layers
    if weight_decay_penalty != 0.0:
      if weight_decay_penalty_type == 'l1':
        regularizer = tf.keras.regularizers.l1(weight_decay_penalty)
      else:
        regularizer = tf.keras.regularizers.l2(weight_decay_penalty)
    else:
      regularizer = None
    #score = AtomicConvScore(self.atom_types, layer_sizes)([
    #    self._frag1_conv, self._frag2_conv, self._complex_conv, frag1_z,
    #    frag2_z, complex_z
    #])
    layer = Dense(100)(concat)
    output = Dense(1)(layer)
    print("output")
    print(output)
    #print("score")
    #print(score)
    prev_layer = concat
    #dropout_switch = Input(shape=tuple())
    prev_size = concat.shape[0] 
    next_activation = None
    ## Add the dense layers

    for size, weight_stddev, bias_const, dropout, activation_fn in zip(
        layer_sizes, weight_init_stddevs, bias_init_consts, dropouts,
        activation_fns):
      layer = prev_layer
      if next_activation is not None:
        layer = Activation(next_activation)(layer)
      print("size")
      print(size)
      #layer = Dense(100)(layer)
      layer = Dense(
          size,
          kernel_initializer=tf.keras.initializers.TruncatedNormal(
              stddev=weight_stddev),
          bias_initializer=tf.constant_initializer(value=bias_const),
          kernel_regularizer=regularizer)(layer)
      if dropout > 0.0:
        layer = Dropout(rate=dropout)(layer)
      if residual and prev_size == size:
        prev_layer = Lambda(lambda x: x[0] + x[1])([prev_layer, layer])
      else:
        prev_layer = layer
      prev_size = size
      next_activation = activation_fn
      if next_activation is not None:
        prev_layer = Activation(activation_fn)(prev_layer)
    self.neural_fingerprint = prev_layer
    output = Reshape((n_tasks, 1))(Dense(
        n_tasks,
        kernel_initializer=tf.keras.initializers.TruncatedNormal(
            stddev=weight_init_stddevs[-1]),
        bias_initializer=tf.constant_initializer(
            value=bias_init_consts[-1]))(prev_layer))
    loss: Union[dc.models.losses.Loss, LossFn]
    #prev_layer = Dense(100)(prev_layer)
    #output = Dense(1)(prev_layer)
    #print("output")
    #print(output)
    #loss = dc.models.losses.L2Loss()

    model = tf.keras.Model(
+75 −71
Original line number Diff line number Diff line
@@ -24,10 +24,12 @@ def test_atomic_conv():
  N_atoms = 5
  batch_size = 1
  atomic_convnet = atomic_conv.AtomicConvModel(
      n_tasks=1,
      batch_size=batch_size,
      frag1_num_atoms=5,
      frag2_num_atoms=5,
      complex_num_atoms=10,
      dropouts=0.0,
      learning_rate=0.003)

  # Creates a set of dummy features that contain the coordinate and
@@ -62,84 +64,86 @@ def test_atomic_conv():
  labels = np.random.rand(batch_size)
  train = NumpyDataset(features, labels)
  #atomic_convnet.fit(train, nb_epoch=300)
  atomic_convnet.fit(train, nb_epoch=100)
  atomic_convnet.fit(train, nb_epoch=150)
  print("labels")
  print(labels)
  print("atomic_convnet.predict(train)")
  print(atomic_convnet.predict(train))
  assert np.allclose(labels, atomic_convnet.predict(train), atol=0.01)

@pytest.mark.slow
def test_atomic_conv_variable():
  """A simple test that initializes and fits an AtomicConvModel on variable input size."""
  # For simplicity, let's assume both molecules have same number of
  # atoms.
  frag1_num_atoms = 1000
  frag2_num_atoms = 1200
  complex_num_atoms = frag1_num_atoms + frag2_num_atoms
  batch_size = 1
  atomic_convnet = atomic_conv.AtomicConvModel(
      n_tasks=1,
      batch_size=batch_size,
      frag1_num_atoms=frag1_num_atoms,
      frag2_num_atoms=frag2_num_atoms,
      complex_num_atoms=complex_num_atoms)

  # Creates a set of dummy features that contain the coordinate and
  # neighbor-list features required by the AtomicConvModel.
  features = []
  frag1_coords = np.random.rand(frag1_num_atoms, 3)
  frag1_nbr_list = {i: [] for i in range(frag1_num_atoms)}
  frag1_z = np.random.randint(10, size=(frag1_num_atoms))
  frag2_coords = np.random.rand(frag2_num_atoms, 3)
  frag2_nbr_list = {i: [] for i in range(frag2_num_atoms)}
  frag2_z = np.random.randint(10, size=(frag2_num_atoms))
  system_coords = np.random.rand(complex_num_atoms, 3)
  system_nbr_list = {i: [] for i in range(complex_num_atoms)}
  system_z = np.random.randint(10, size=(complex_num_atoms))

  features.append(
      (frag1_coords, frag1_nbr_list, frag1_z, frag2_coords, frag2_nbr_list,
       frag2_z, system_coords, system_nbr_list, system_z))
  features = np.asarray(features)
  labels = np.zeros(batch_size)
  train = NumpyDataset(features, labels)
  atomic_convnet.fit(train, nb_epoch=1)

@pytest.mark.slow
def test_atomic_conv_with_feat():
  """A simple test for running an atomic convolution on featurized data."""
  dir_path = os.path.dirname(os.path.realpath(__file__))
  ligand_file = os.path.join(dir_path,
                             "../../feat/tests/data/3zso_ligand_hyd.pdb")
  protein_file = os.path.join(dir_path,
                              "../../feat/tests/data/3zso_protein.pdb")
  # Pulled from PDB files. For larger datasets with more PDBs, would use
  # max num atoms instead of exact.
  frag1_num_atoms = 44  # for ligand atoms
  frag2_num_atoms = 2336  # for protein atoms
  complex_num_atoms = 2380  # in total
  max_num_neighbors = 4
  # Cutoff in angstroms
  neighbor_cutoff = 4
  complex_featurizer = ComplexNeighborListFragmentAtomicCoordinates(
      frag1_num_atoms, frag2_num_atoms, complex_num_atoms, max_num_neighbors,
      neighbor_cutoff)
  # arbitrary label
  labels = np.array([0])
  features, _ = complex_featurizer.featurize([ligand_file], [protein_file])
  dataset = deepchem.data.DiskDataset.from_numpy(features, labels)

  batch_size = 1
  print("Constructing Atomic Conv model")
  atomic_convnet = atomic_conv.AtomicConvModel(
      n_tasks=1,
      batch_size=batch_size,
      frag1_num_atoms=frag1_num_atoms,
      frag2_num_atoms=frag2_num_atoms,
      complex_num_atoms=complex_num_atoms)

  print("About to call fit")
  # Run a fitting operation
  atomic_convnet.fit(dataset)
#class TestAtomicConv(unittest.TestCase):
#
#  @pytest.mark.slow
#  def test_atomic_conv_variable(self):
#    """A simple test that initializes and fits an AtomicConvModel on variable input size."""
#    # For simplicity, let's assume both molecules have same number of
#    # atoms.
#    frag1_num_atoms = 1000
#    frag2_num_atoms = 1200
#    complex_num_atoms = frag1_num_atoms + frag2_num_atoms
#    batch_size = 1
#    atomic_convnet = atomic_conv.AtomicConvModel(
#        batch_size=batch_size,
#        frag1_num_atoms=frag1_num_atoms,
#        frag2_num_atoms=frag2_num_atoms,
#        complex_num_atoms=complex_num_atoms)
#
#    # Creates a set of dummy features that contain the coordinate and
#    # neighbor-list features required by the AtomicConvModel.
#    features = []
#    frag1_coords = np.random.rand(frag1_num_atoms, 3)
#    frag1_nbr_list = {i: [] for i in range(frag1_num_atoms)}
#    frag1_z = np.random.randint(10, size=(frag1_num_atoms))
#    frag2_coords = np.random.rand(frag2_num_atoms, 3)
#    frag2_nbr_list = {i: [] for i in range(frag2_num_atoms)}
#    frag2_z = np.random.randint(10, size=(frag2_num_atoms))
#    system_coords = np.random.rand(complex_num_atoms, 3)
#    system_nbr_list = {i: [] for i in range(complex_num_atoms)}
#    system_z = np.random.randint(10, size=(complex_num_atoms))
#
#    features.append(
#        (frag1_coords, frag1_nbr_list, frag1_z, frag2_coords, frag2_nbr_list,
#         frag2_z, system_coords, system_nbr_list, system_z))
#    features = np.asarray(features)
#    labels = np.zeros(batch_size)
#    train = NumpyDataset(features, labels)
#    atomic_convnet.fit(train, nb_epoch=1)
#
#  @pytest.mark.slow
#  def test_atomic_conv_with_feat(self):
#    """A simple test for running an atomic convolution on featurized data."""
#    dir_path = os.path.dirname(os.path.realpath(__file__))
#    ligand_file = os.path.join(dir_path,
#                               "../../feat/tests/data/3zso_ligand_hyd.pdb")
#    protein_file = os.path.join(dir_path,
#                                "../../feat/tests/data/3zso_protein.pdb")
#    # Pulled from PDB files. For larger datasets with more PDBs, would use
#    # max num atoms instead of exact.
#    frag1_num_atoms = 44  # for ligand atoms
#    frag2_num_atoms = 2336  # for protein atoms
#    complex_num_atoms = 2380  # in total
#    max_num_neighbors = 4
#    # Cutoff in angstroms
#    neighbor_cutoff = 4
#    complex_featurizer = ComplexNeighborListFragmentAtomicCoordinates(
#        frag1_num_atoms, frag2_num_atoms, complex_num_atoms, max_num_neighbors,
#        neighbor_cutoff)
#    # arbitrary label
#    labels = np.array([0])
#    features, _ = complex_featurizer.featurize([ligand_file], [protein_file])
#    dataset = deepchem.data.DiskDataset.from_numpy(features, labels)
#
#    batch_size = 1
#    print("Constructing Atomic Conv model")
#    atomic_convnet = atomic_conv.AtomicConvModel(
#        batch_size=batch_size,
#        frag1_num_atoms=frag1_num_atoms,
#        frag2_num_atoms=frag2_num_atoms,
#        complex_num_atoms=complex_num_atoms)
#
#    print("About to call fit")
#    # Run a fitting operation
#    atomic_convnet.fit(dataset)