Commit a47422a8 authored by nd-02110114's avatar nd-02110114
Browse files

✨ refactor featurizer

parent b8504198
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ from deepchem.feat.molecule_featurizers import MolGraphConvFeaturizer
from deepchem.feat.molecule_featurizers import CircularFingerprint
from deepchem.feat.molecule_featurizers import CoulombMatrix
from deepchem.feat.molecule_featurizers import CoulombMatrixEig
from deepchem.feat.molecule_featurizers import MordredDescriptors
from deepchem.feat.molecule_featurizers import Mol2VecFingerprint
from deepchem.feat.molecule_featurizers import RawFeaturizer
from deepchem.feat.molecule_featurizers import RDKitDescriptors
from deepchem.feat.molecule_featurizers import SmilesToImage
+2 −0
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ from deepchem.feat.molecule_featurizers.bp_symmetry_function_input import BPSymm
from deepchem.feat.molecule_featurizers.circular_fingerprint import CircularFingerprint
from deepchem.feat.molecule_featurizers.coulomb_matrices import CoulombMatrix
from deepchem.feat.molecule_featurizers.coulomb_matrices import CoulombMatrixEig
from deepchem.feat.molecule_featurizers.mordred_descriptors import MordredDescriptors
from deepchem.feat.molecule_featurizers.mol2vec_fingerprint import Mol2VecFingerprint
from deepchem.feat.molecule_featurizers.raw_featurizer import RawFeaturizer
from deepchem.feat.molecule_featurizers.rdkit_descriptors import RDKitDescriptors
from deepchem.feat.molecule_featurizers.smiles_to_image import SmilesToImage
+22 −10
Original line number Diff line number Diff line
@@ -90,12 +90,16 @@ class CoulombMatrix(MolecularFeaturizer):
    -------
    np.ndarray
      The coulomb matrices of the given molecule.
      The shape is `(num_confs, max_atoms, max_atoms)`.
      The default shape is `(num_confs, max_atoms, max_atoms)`.
      If num_confs == 1, the shape is `(max_atoms, max_atoms)`.
    """
    features = self.coulomb_matrix(mol)
    if self.upper_tri:
      features = [f[np.triu_indices_from(f)] for f in features]
    features = np.asarray(features)
    if features.shape[0] == 1:
      # `(1, max_atoms, max_atoms)` -> `(max_atoms, max_atoms)`
      features = np.squeeze(features, axis=0)
    return features

  def coulomb_matrix(self, mol: RDKitMol) -> np.ndarray:
@@ -114,9 +118,16 @@ class CoulombMatrix(MolecularFeaturizer):
    """
    try:
      from rdkit import Chem
      from rdkit.Chem import AllChem
    except ModuleNotFoundError:
      raise ValueError("This class requires RDKit to be installed.")

    # Check whether num_confs >=1 or not
    num_confs = len(mol.GetConformers())
    if num_confs == 0:
      mol = Chem.AddHs(mol)
      AllChem.EmbedMolecule(mol, AllChem.ETKDG())

    if self.remove_hydrogens:
      mol = Chem.RemoveHs(mol)
    n_atoms = mol.GetNumAtoms()
@@ -203,8 +214,8 @@ class CoulombMatrixEig(CoulombMatrix):
  This featurizer computes the eigenvalues of the Coulomb matrices for provided
  molecules. Coulomb matrices are described in [1]_.

  Example
  -------
  Examples
  --------
  >>> featurizers = dc.feat.CoulombMatrixEig(max_atoms=23)
  >>> input_file = 'deepchem/feat/tests/data/water.sdf' # really backed by water.sdf.csv
  >>> tasks = ["atomization_energy"]
@@ -218,9 +229,6 @@ class CoulombMatrixEig(CoulombMatrix):
     processing systems. 2012.
  """

  conformers = True
  name = 'coulomb_matrix'

  def __init__(self,
               max_atoms: int,
               remove_hydrogens: bool = False,
@@ -266,10 +274,11 @@ class CoulombMatrixEig(CoulombMatrix):
    -------
    np.ndarray
      The eigenvalues of Coulomb matrix for molecules.
      The shape is `(num_confs, max_atoms)`.
      The default shape is `(num_confs, max_atoms)`.
      If num_confs == 1, the shape is `(max_atoms,)`.
    """
    cmat = self.coulomb_matrix(mol)
    features = []
    features_list = []
    for f in cmat:
      w, v = np.linalg.eig(f)
      w_abs = np.abs(w)
@@ -277,6 +286,9 @@ class CoulombMatrixEig(CoulombMatrix):
      sortidx = sortidx[::-1]
      w = w[sortidx]
      f = pad_array(w, self.max_atoms)
      features.append(f)
    features = np.asarray(features)
      features_list.append(f)
    features = np.asarray(features_list)
    if features.shape[0] == 1:
      # `(1, max_atoms)` -> `(max_atoms,)`
      features = np.squeeze(features, axis=0)
    return features
+110 −0
Original line number Diff line number Diff line
from os import path
from typing import Optional

import numpy as np

from deepchem.utils import download_url, get_data_dir, untargz_file
from deepchem.utils.typing import RDKitMol
from deepchem.feat.base_classes import MolecularFeaturizer

DEFAULT_PRETRAINED_MODEL_URL = 'https://deepchemdata.s3-us-west-1.amazonaws.com/trained_models/mol2vec_model_300dim.tar.gz'


class Mol2VecFingerprint(MolecularFeaturizer):
  """Mol2Vec fingerprints.

  This class convert molecules to vector representations by using Mol2Vec.
  Mol2Vec is an unsupervised machine learning approach to learn vector representations
  of molecular substructures and the algorithm is based on Word2Vec, which is
  one of the most popular technique to learn word embeddings using neural network in NLP.
  Please see the details from [1]_.

  The Mol2Vec requires the pretrained model, so we use the model which is put on the mol2vec
  github repository [2]_. The default model was trained on 20 million compounds downloaded
  from ZINC using the following paramters.

  - radius 1
  - UNK to replace all identifiers that appear less than 4 times
  - skip-gram and window size of 10
  - embeddings size 300

  References
  ----------
  .. [1] Jaeger, Sabrina, Simone Fulle, and Samo Turk. "Mol2vec: unsupervised machine learning
     approach with chemical intuition." Journal of chemical information and modeling 58.1 (2018): 27-35.
  .. [2] https://github.com/samoturk/mol2vec/

  Notes
  -----
  This class requires mol2vec to be installed.
  """

  def __init__(self,
               pretrain_model_path: Optional[str] = None,
               radius: int = 1,
               unseen: str = 'UNK',
               gather_method: str = 'sum'):
    """
    Paremeters
    ----------
    pretrain_file: str, optional
      The path for pretrained model. If this value is None, we use the model which is put on
      github repository (https://github.com/samoturk/mol2vec/tree/master/examples/models).
      The model is trained on 20 million compounds downloaded from ZINC.
    radius: int, optional (default 1)
      The fingerprint radius. The default value was used to train the model which is put on
      github repository.
    unseen: str, optional (default 'UNK')
      The string to used to replace uncommon words/identifiers while training.
    gather_method: str, optional (default 'sum')
      How to aggregate vectors of identifiers are extracted from Mol2vec.
      'sum' or 'mean' is supported.
    """
    try:
      from gensim.models import word2vec
      from mol2vec.features import mol2alt_sentence, sentences2vec
    except ModuleNotFoundError:
      raise ValueError("This class requires mol2vec to be installed.")

    self.radius = radius
    self.unseen = unseen
    self.gather_method = gather_method
    self.sentences2vec = sentences2vec
    self.mol2alt_sentence = mol2alt_sentence
    if pretrain_model_path is None:
      data_dir = get_data_dir()
      pretrain_model_path = path.join(data_dir, 'mol2vec_model_300dim.pkl')
      if not path.exists(pretrain_model_path):
        targz_file = path.join(data_dir, 'mol2vec_model_300dim.tar.gz')
        if not path.exists(targz_file):
          download_url(DEFAULT_PRETRAINED_MODEL_URL, data_dir)
        untargz_file(
            path.join(data_dir, 'mol2vec_model_300dim.tar.gz'), data_dir)
    # load pretrained models
    self.model = word2vec.Word2Vec.load(pretrain_model_path)

  def _featurize(self, mol: RDKitMol) -> np.ndarray:
    """
    Calculate Mordred descriptors.

    Parameters
    ----------
    mol: rdkit.Chem.rdchem.Mol
      RDKit Mol object

    Returns
    -------
    np.ndarray
      1D array of mol2vec fingerprint. The default length is 300.
    """
    sentence = self.mol2alt_sentence(mol, self.radius)
    vec_identifiers = self.sentences2vec(
        sentence, self.model, unseen=self.unseen)
    if self.gather_method == 'sum':
      feature = np.sum(vec_identifiers, axis=0)
    elif self.gather_method == 'mean':
      feature = np.mean(vec_identifiers, axis=0)
    else:
      raise ValueError(
          'Not supported gather_method type. Please set "sum" or "mean"')
    return feature
+67 −0
Original line number Diff line number Diff line
import numpy as np

from deepchem.utils.typing import RDKitMol
from deepchem.feat.base_classes import MolecularFeaturizer


class MordredDescriptors(MolecularFeaturizer):
  """Mordred descriptors.

  This class comptues a list of chemical descriptors using Mordred.
  Please see the details about all descripors from [1]_, [2]_.

  Attributes
  ----------
  descriptors: List[str]
    List of RDKit descriptor names used in this class.

  References
  ----------
  .. [1] Moriwaki, Hirotomo, et al. "Mordred: a molecular descriptor calculator."
     Journal of cheminformatics 10.1 (2018): 4.
  .. [2] http://mordred-descriptor.github.io/documentation/master/descriptors.html

  Notes
  -----
  This class requires Mordred to be installed.
  """

  def __init__(self, ignore_3D: bool = True):
    """
    Paremeters
    ----------
    ignore_3D: bool, optional (default True)
      Whether to use 3D information or not.
    """
    try:
      from mordred import Calculator, descriptors, is_missing
    except ModuleNotFoundError:
      raise ValueError("This class requires RDKit to be installed.")

    self.calc = Calculator(descriptors, ignore_3D=ignore_3D)
    self.is_missing = is_missing
    self.descriptors = list(descriptors.__all__)

  def _featurize(self, mol: RDKitMol) -> np.ndarray:
    """
    Calculate Mordred descriptors.

    Parameters
    ----------
    mol: rdkit.Chem.rdchem.Mol
      RDKit Mol object

    Returns
    -------
    np.ndarray
      1D array of Mordred descriptors for `mol`.
      If ignore_3D is True, the length is 1613.
      If ignore_3D is False, the length is 1826.
    """
    feature = self.calc(mol)
    # convert errors to zero
    feature = [
        0.0 if self.is_missing(val) or isinstance(val, str) else val
        for val in feature
    ]
    return np.asarray(feature)
Loading