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

Merge pull request #1156 from lilleswing/kb-atom-features

Enable Atom-Based Properties Stored on Molecules to be used in the ConvMol Featurizer
parents 030c82a5 e1ed1972
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -200,8 +200,8 @@ class Dataset(object):
    >>> dataset = NumpyDataset(np.ones((2,2)))
    >>> for x, y, w, id in dataset.itersamples():
    ...   print(x.tolist(), y.tolist(), w.tolist(), id)
    [1.0 1.0] [0.0] [0.0] 0
    [1.0 1.0] [0.0] [0.0] 1
    [1.0, 1.0] [0.0] [0.0] 0
    [1.0, 1.0] [0.0] [0.0] 1
    """
    raise NotImplementedError()

@@ -409,8 +409,8 @@ class NumpyDataset(Dataset):
    >>> dataset = NumpyDataset(np.ones((2,2)))
    >>> for x, y, w, id in dataset.itersamples():
    ...   print(x.tolist(), y.tolist(), w.tolist(), id)
    [1.0 1.0] [0.0] [0.0] 0
    [1.0 1.0] [0.0] [0.0] 1
    [1.0, 1.0] [0.0] [0.0] 0
    [1.0, 1.0] [0.0] [0.0] 1
    """
    n_samples = self._X.shape[0]
    return ((self._X[i], self._y[i], self._w[i], self._ids[i])
@@ -889,8 +889,8 @@ class DiskDataset(Dataset):
    >>> dataset = DiskDataset.from_numpy(np.ones((2,2)), np.ones((2,1)), verbose=False)
    >>> for x, y, w, id in dataset.itersamples():
    ...   print(x.tolist(), y.tolist(), w.tolist(), id)
    [1.0 1.0] [0.0] [0.0] 0
    [1.0 1.0] [0.0] [0.0] 1
    [1.0, 1.0] [1.0] [1.0] 0
    [1.0, 1.0] [1.0] [1.0] 1
    """

    def iterate(dataset):
+48 −5
Original line number Diff line number Diff line
@@ -3,7 +3,6 @@ from __future__ import unicode_literals

import numpy as np
from rdkit import Chem
import itertools, operator

from deepchem.feat import Featurizer
from deepchem.feat.mol_graphs import ConvMol, WeaveMol
@@ -264,7 +263,8 @@ def find_distance(a1, num_atoms, canon_adj_list, max_distance=7):
class ConvMolFeaturizer(Featurizer):
  name = ['conv_mol']

  def __init__(self, master_atom=False, use_chirality=False):
  def __init__(self, master_atom=False, use_chirality=False,
               atom_properties=[]):
    """
    Parameters
    ----------
@@ -274,7 +274,20 @@ class ConvMolFeaturizer(Featurizer):
      the molecule.  This technique is briefly discussed in
      Neural Message Passing for Quantum Chemistry
      https://arxiv.org/pdf/1704.01212.pdf

    use_chirality: Boolean
      if true then make the resulting atom features aware of the 
      chirality of the molecules in question
    atom_properties: list of string or None
      properties in the RDKit Mol object to use as additional
      atom-level features in the larger molecular feature.  If None,
      then no atom-level properties are used.  Properties should be in the 
      RDKit mol object should be in the form 
      atom XXXXXXXX NAME
      where XXXXXXXX is a zero-padded 8 digit number coresponding to the
      zero-indexed atom index of each atom and NAME is the name of the property
      provided in atom_properties.  So "atom 00000000 sasa" would be the
      name of the molecule level property in mol where the solvent 
      accessible surface area of atom 0 would be stored. 

    Since ConvMol is an object and not a numpy array, need to set dtype to
    object.
@@ -282,12 +295,39 @@ class ConvMolFeaturizer(Featurizer):
    self.dtype = object
    self.master_atom = master_atom
    self.use_chirality = use_chirality
    self.atom_properties = list(atom_properties)

  def _get_atom_properties(self, atom):
    """
    For a given input RDKit atom return the values of the properties
    requested when initializing the featurize.  See the __init__ of the
    class for a full description of the names of the properties
    
    Parameters
    ----------
    atom: RDKit.rdchem.Atom
      Atom to get the properties of
    returns a numpy lists of floats of the same size as self.atom_properties
    """
    values = []
    for prop in self.atom_properties:
      mol_prop_name = str("atom %08d %s" % (atom.GetIdx(), prop))
      try:
        values.append(float(atom.GetOwningMol().GetProp(mol_prop_name)))
      except KeyError:
        raise KeyError("No property %s found in %s in %s" %
                       (mol_prop_name, atom.GetOwningMol(), self))
    return np.array(values)

  def _featurize(self, mol):
    """Encodes mol as a ConvMol object."""
    # Get the node features
    idx_nodes = [(a.GetIdx(), atom_features(
        a, use_chirality=self.use_chirality)) for a in mol.GetAtoms()]
    idx_nodes = [(a.GetIdx(),
                  np.concatenate((atom_features(
                      a, use_chirality=self.use_chirality),
                                  self._get_atom_properties(a))))
                 for a in mol.GetAtoms()]

    idx_nodes.sort()  # Sort by ind to ensure same order as rd_kit
    idx, nodes = list(zip(*idx_nodes))

@@ -315,6 +355,9 @@ class ConvMolFeaturizer(Featurizer):

    return ConvMol(nodes, canon_adj_list)

  def feature_length(self):
    return 75 + len(self.atom_properties)


class WeaveFeaturizer(Featurizer):
  name = ['weave_mol']
+9 −3
Original line number Diff line number Diff line
@@ -672,6 +672,7 @@ class GraphConvTensorGraph(TensorGraph):
               dense_layer_size=128,
               dropout=0.0,
               mode="classification",
               number_atom_features=75,
               **kwargs):
    """
            Parameters
@@ -686,6 +687,10 @@ class GraphConvTensorGraph(TensorGraph):
              Droupout dropout probability.  Dropout is applied after the per Atom Level Dense Layer
            mode: str
              Either "classification" or "regression"
            number_atom_features: int
                75 is the default number of atom features created, but
                this can vary if various options are passed to the 
                function atom_features in graph_features
            """
    self.n_tasks = n_tasks
    self.mode = mode
@@ -694,6 +699,7 @@ class GraphConvTensorGraph(TensorGraph):
    self.dropout = dropout
    self.graph_conv_layers = graph_conv_layers
    kwargs['use_queue'] = False
    self.number_atom_features = number_atom_features
    super(GraphConvTensorGraph, self).__init__(**kwargs)
    self.build_graph()

@@ -701,7 +707,7 @@ class GraphConvTensorGraph(TensorGraph):
    """
    Building graph structures:
    """
    self.atom_features = Feature(shape=(None, 75))
    self.atom_features = Feature(shape=(None, self.number_atom_features))
    self.degree_slice = Feature(shape=(None, 2), dtype=tf.int32)
    self.membership = Feature(shape=(None,), dtype=tf.int32)

+34 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ from deepchem.models import TensorGraph
from deepchem.molnet.load_function.delaney_datasets import load_delaney
from deepchem.models.tensorgraph.layers import ReduceSum, L2Loss
from deepchem.models import WeaveTensorGraph
from deepchem.feat import ConvMolFeaturizer


class TestGraphModels(unittest.TestCase):
@@ -82,6 +83,39 @@ class TestGraphModels(unittest.TestCase):
    assert mu.shape == (len(dataset), len(tasks))
    assert sigma.shape == (len(dataset), len(tasks))

  def test_graph_conv_atom_features(self):
    tasks, dataset, transformers, metric = self.get_dataset(
        'regression', 'Raw', num_tasks=1)

    atom_feature_name = 'feature'
    y = []
    for mol in dataset.X:
      atom_features = []
      for atom in mol.GetAtoms():
        val = np.random.normal()
        mol.SetProp("atom %08d %s" % (atom.GetIdx(), atom_feature_name),
                    str(val))
        atom_features.append(np.random.normal())
      y.append(np.sum(atom_features))

    featurizer = ConvMolFeaturizer(atom_properties=[atom_feature_name])
    X = featurizer.featurize(dataset.X)
    dataset = deepchem.data.NumpyDataset(X, np.array(y))
    batch_size = 50
    model = GraphConvTensorGraph(
        len(tasks),
        number_atom_features=featurizer.feature_length(),
        batch_size=batch_size,
        mode='regression')

    model.fit(dataset, nb_epoch=1)
    y_pred1 = model.predict(dataset)
    model.save()

    model2 = TensorGraph.load_from_dir(model.model_dir)
    y_pred2 = model2.predict(dataset)
    self.assertTrue(np.all(y_pred1 == y_pred2))

  def test_change_loss_function(self):
    tasks, dataset, transformers, metric = self.get_dataset(
        'regression', 'GraphConv', num_tasks=1)
+1 −1

File changed.

Contains only whitespace changes.