Commit 90ae7779 authored by nd-02110114's avatar nd-02110114
Browse files

add mode option of CGCNN

parent c6d2dd71
Loading
Loading
Loading
Loading
+9.19 KiB

File added.

No diff preview for this file type.

−12.1 KiB (4.38 KiB)

File changed.

No diff preview for this file type.

+45 −21
Original line number Diff line number Diff line
@@ -2,9 +2,9 @@ import unittest
from os import path, remove

from deepchem.feat import CGCNNFeaturizer
from deepchem.molnet import load_perovskite
from deepchem.metrics import Metric, mae_score
from deepchem.models import CGCNNModel, losses
from deepchem.molnet import load_perovskite, load_mp_metallicity
from deepchem.metrics import Metric, mae_score, roc_auc_score
from deepchem.models import CGCNNModel

try:
  import dgl  # noqa
@@ -16,6 +16,7 @@ except:

@unittest.skipIf(not has_pytorch_and_dgl, 'PyTorch and DGL are not installed')
def test_cgcnn():
  # regression test
  # load datasets
  current_dir = path.dirname(path.abspath(__file__))
  config = {
@@ -23,38 +24,61 @@ def test_cgcnn():
      "featurizer": CGCNNFeaturizer,
      # disable transformer
      "transformers": [],
      # load 'deepchem/models/test/perovskite.tar.gz'
      "data_dir": current_dir
  }
  tasks, datasets, transformers = load_perovskite(**config)
  train, valid, test = datasets

  # initialize models
  n_tasks = 1
  n_tasks = len(tasks)
  model = CGCNNModel(
      n_tasks=n_tasks, mode='regression', batch_size=4, learning_rate=0.001)

  # check train
  model.fit(train, nb_epoch=20)

  # check predict shape
  valid_preds = model.predict_on_batch(valid.X)
  assert valid_preds.shape == (2, n_tasks)
  test_preds = model.predict(test)
  assert test_preds.shape == (3, n_tasks)

  # check overfit
  regression_metric = Metric(mae_score, n_tasks=n_tasks)
  scores = model.evaluate(train, [regression_metric], transformers)
  assert scores[regression_metric.name] < 0.5

  # classification test
  tasks, datasets, transformers = load_mp_metallicity(**config)
  train, valid, test = datasets

  # load datasets
  n_tasks = len(tasks)
  n_classes = 2
  model = CGCNNModel(
      in_node_dim=92,
      hidden_node_dim=64,
      in_edge_dim=41,
      num_conv=3,
      predicator_hidden_feats=128,
      n_tasks=n_tasks,
      loss=losses.L2Loss(),
      batch_size=32,
      n_classes=n_classes,
      mode='classification',
      batch_size=4,
      learning_rate=0.001)

  # check train
  model.fit(train, nb_epoch=50)
  model.fit(train, nb_epoch=20)

  # check predict
  # check predict shape
  valid_preds = model.predict_on_batch(valid.X)
  assert valid_preds.shape == (10, n_tasks)
  assert valid_preds.shape == (2, n_classes)
  test_preds = model.predict(test)
  assert test_preds.shape == (10, n_tasks)
  assert test_preds.shape == (3, n_classes)

  # eval model on test
  regression_metric = Metric(mae_score, n_tasks=n_tasks)
  scores = model.evaluate(test, [regression_metric])
  assert scores[regression_metric.name] < 1.0
  # check overfit
  classification_metric = Metric(roc_auc_score, n_tasks=n_tasks)
  scores = model.evaluate(
      train, [classification_metric], transformers, n_classes=n_classes)
  assert scores[classification_metric.name] > 0.8

  # TODO: Multi task classification test

  if path.exists(path.join(current_dir, 'perovskite.json')):
    remove(path.join(current_dir, 'perovskite.json'))
  if path.exists(path.join(current_dir, 'mp_is_metal.json')):
    remove(path.join(current_dir, 'mp_is_metal.json'))
+47 −10
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ import torch
import torch.nn as nn
import torch.nn.functional as F

from deepchem.models.losses import L2Loss, SparseSoftmaxCrossEntropy
from deepchem.models.torch_models.torch_model import TorchModel


@@ -116,7 +117,7 @@ class CGCNN(nn.Module):
  >>> cgcnn_dgl_feat = cgcnn_feat.to_dgl_graph()
  >>> print(type(cgcnn_dgl_feat))
  <class 'dgl.heterograph.DGLHeteroGraph'>
  >>> model = dc.models.CGCNN(n_tasks=2)
  >>> model = dc.models.CGCNN(mode='regression', n_tasks=2)
  >>> out = model(cgcnn_dgl_feat)
  >>> print(type(out))
  <class 'torch.Tensor'>
@@ -142,6 +143,8 @@ class CGCNN(nn.Module):
      num_conv: int = 3,
      predicator_hidden_feats: int = 128,
      n_tasks: int = 1,
      mode: str = 'regression',
      n_classes: int = 2,
  ):
    """
    Parameters
@@ -157,11 +160,21 @@ class CGCNN(nn.Module):
    num_conv: int, default 3
      The number of convolutional layers.
    predicator_hidden_feats: int, default 128
      Size for hidden representations in the output MLP predictor, default to 128.
      The size for hidden representations in the output MLP predictor.
    n_tasks: int, default 1
      Number of the output size, default to 1.
      The number of the output size.
    mode: str, default 'regression'
      Whether the model type is 'classification' or 'regression'.
    n_classes: int, default 2
      The number of classes to predict (only used in classification mode).
    """
    super(CGCNN, self).__init__()
    if mode not in ['classification', 'regression']:
      raise ValueError("mode must be either 'classification' or 'regression'")

    self.n_tasks = n_tasks
    self.mode = mode
    self.n_classes = n_classes
    self.embedding = nn.Linear(in_node_dim, hidden_node_dim)
    self.conv_layers = nn.ModuleList([
        CGCNNLayer(
@@ -170,7 +183,10 @@ class CGCNN(nn.Module):
            batch_norm=True) for _ in range(num_conv)
    ])
    self.fc = nn.Linear(hidden_node_dim, predicator_hidden_feats)
    if self.mode == 'regression':
      self.out = nn.Linear(predicator_hidden_feats, n_tasks)
    else:
      self.out = nn.Linear(predicator_hidden_feats, n_tasks * n_classes)

  def forward(self, dgl_graph):
    """Predict labels
@@ -203,7 +219,15 @@ class CGCNN(nn.Module):
    graph_feat = dgl.mean_nodes(graph, 'x')
    graph_feat = self.fc(graph_feat)
    out = self.out(graph_feat)

    if self.mode == 'regression':
      return out
    else:
      logits = out.view(-1, self.n_tasks, self.n_classes)
      # for n_tasks == 1 case
      logits = torch.squeeze(logits)
      proba = F.softmax(logits)
      return proba, logits


class CGCNNModel(TorchModel):
@@ -216,7 +240,7 @@ class CGCNNModel(TorchModel):
  >> dataset_config = {"reload": False, "featurizer": dc.feat.CGCNNFeaturizer, "transformers": []}
  >> tasks, datasets, transformers = dc.molnet.load_perovskite(**dataset_config)
  >> train, valid, test = datasets
  >> model = dc.models.CGCNNModel(loss=dc.models.losses.L2Loss(), batch_size=32, learning_rate=0.001)
  >> model = dc.models.CGCNNModel(mode='regression', batch_size=32, learning_rate=0.001)
  >> model.fit(train, nb_epoch=50)

  This model takes arbitary crystal structures as an input, and predict material properties
@@ -248,6 +272,8 @@ class CGCNNModel(TorchModel):
               num_conv: int = 3,
               predicator_hidden_feats: int = 128,
               n_tasks: int = 1,
               mode: str = 'regression',
               n_classes: int = 2,
               **kwargs):
    """
    This class accepts all the keyword arguments from TorchModel.
@@ -265,15 +291,26 @@ class CGCNNModel(TorchModel):
    num_conv: int, default 3
      The number of convolutional layers.
    predicator_hidden_feats: int, default 128
      Size for hidden representations in the output MLP predictor, default to 128.
      The size for hidden representations in the output MLP predictor.
    n_tasks: int, default 1
      Number of the output size, default to 1.
      The number of the output size.
    mode: str, default 'regression'
      Whether the model type is 'classification' or 'regression'.
    n_classes: int, default 2
      The number of classes to predict (only used in classification mode).
    kwargs: Dict
      This class accepts all the keyword arguments from TorchModel.
    """
    model = CGCNN(in_node_dim, hidden_node_dim, in_edge_dim, num_conv,
                  predicator_hidden_feats, n_tasks)
    super(CGCNNModel, self).__init__(model, **kwargs)
                  predicator_hidden_feats, n_tasks, mode, n_classes)
    if mode == "regression":
      loss = L2Loss()
      output_types = ['prediction']
    else:
      loss = SparseSoftmaxCrossEntropy()
      output_types = ['prediction', 'loss']
    super(CGCNNModel, self).__init__(
        model, loss=loss, output_types=output_types, **kwargs)

  def _prepare_batch(self, batch):
    """Create batch data for CGCNN.