Help us understand the problem. What is going on with this article?

DeepChemで欠損値を含む列を除去するTransformerを書いてみる

はじめに

前回mordredをDeepChemのFeaturizerとして使う の記事でmordredで計算エラーが発生した場合0に置き換えていた。しかし前回もお伝えしたように通常はnanを含む記述子そのものを取り除くか、nanをそのまま欠損値を処理できるアルゴリズムに投入することが一般的に行われる。今回は、DeepChemのTransformerを継承し、欠損値を除去するTransformerを作成し、このTransformerによりnp.nanが含まれる記述子を取り除いて学習できるようにしたのでここにメモっておく。

環境

  • python 3.6
  • deepchem 2.2.1.dev54
  • rdkit 2019.03.3.0
  • morded 1.1.2

前回のソースの修正

まずは、前回のソースの修正として、MordredDescriptor.pyをエラーが発生した場合に0ではなくnumpyのnanを格納するよう修正する。

MordredDescriptor.py
from deepchem.feat import Featurizer
from mordred import Calculator, descriptors
import numpy as np

class MordredDescriptors(Featurizer):
    """
    Mordred descriptors.
    """
    name = 'descriptors'

    def __init__(self, ignore_3D=True):

        self.mordred_calculator = Calculator(descriptors, ignore_3D=ignore_3D)

        # 記述子名-計算関数の対応を保持
        self.descs = {}
        self.counter = 0

        for i, desc in enumerate(self.mordred_calculator.descriptors):
          self.descs[desc.__str__()] = desc

    def _featurize(self, mol):
      ret = []
      for desc in self.descs:
        try:
          ret.append(self.descs[desc](mol))
        except Exception as e:
          ret.append(np.nan)

      self.counter = self.counter + 1
      print(self.counter)

      return ret

Transformerの作成

準備は整ったので、欠損値を含む列を除去するTransformerを書いてみよう。
DeepChemのClippingTransformerや、NomalizationTransformerを参考にし、以下の通り作成した。
まず、コンストラクタでdatasetを受け取り、記述子データである"X"を取り出し、np.nanを含まない列かどうかのBoolean配列を取得し、private変数として保持する。
そして、tansoform_arrayメソッドで、そのBoolean配列の中でTrueをもつ列のみを記述子データを返却するようにする。

MissingValueElminatorTransformer.py
import numpy as np
from deepchem.trans.transformers import Transformer

#----------------------------------------------------
# #参考文献
# -[numpy配列からnanの列を除去する参考URL](https://note.nkmk.me/python-numpy-nan-remove/)
# -[Source code for deepchem.trans.transformers](https://deepchem.io/docs/_modules/deepchem/trans/transformers.html)
#----------------------------------------------------

class MissingValueElminateTransformer(Transformer):

    def __init__(self,
                 transform_X=False,
                 transform_y=False,
                 transform_w=False,
                 dataset=None,
                 transform_gradients=False):

        """Initialize normalization transformation."""

        if transform_X:
            X = dataset.X
            self.X_support = ~np.isnan(X).any(axis=0)

        super().__init__(
        transform_X=transform_X,
        transform_y=transform_y,
        transform_w=transform_w,
        dataset=dataset)


    def transform(self, dataset, parallel=False):
        return super().transform(dataset, parallel=parallel)


    def transform_array(self, X, y, w):

        X = X[:, self.X_support]

        return (X, y, w)


    def untransform(self, z):
        raise NotImplementedError(
            "Cannot untransform datasets with MissingValueElminateTransformer.")

使い方

MissingValueElminateTransformer(transform_X=True, dataset=train_dataset)のような感じで初期化してあげれれば、その後の使い方は他のTransformerと全く同様だ。例によってmetris以下は適当に書いているので各自修正してほしい。

import rdkit.Chem
from rdkit import Chem

import deepchem as dc
from deepchem.models.tensorgraph.models.graph_models import GraphConvModel
from deepchem.feat import RDKitDescriptors, ConvMolFeaturizer, CoulombMatrix, RdkitGridFeaturizer

from MordredDescriptors import MordredDescriptors
from MissingValueElminateTransformer import MissingValueElminateTransformer

# mordredのfeaturizerを定義
featurizer = MordredDescriptors()

# 学習データの読み込み
loader = dc.data.CSVLoader(tasks=["LogS"],
                               id_field="CompoundID",
                               smiles_field="smiles",
                               featurizer=featurizer)

dataset = loader.featurize("../ESOL/data/delaney-processed.csv")
splitter = dc.splits.RandomSplitter(dataset)

train_dataset, valid_dataset, test_dataset = splitter.train_valid_test_split(dataset, frac_train=0.8,
                                                                               frac_valid=0.1, frac_test=0.1)

# 欠損値除去するTrnasformerを追加
transformers = [
        MissingValueElminateTransformer(transform_X=True, dataset=train_dataset),
        dc.trans.NormalizationTransformer(transform_y=True, dataset=train_dataset)
    ]

for transformer in transformers:
    train_dataset = transformer.transform(train_dataset)
    valid_dataset = transformer.transform(valid_dataset)
    test_dataset = transformer.transform(test_dataset)

metric = dc.metrics.Metric(dc.metrics.r2_score)
optimizer = dc.hyper.HyperparamOpt(get_builder(), verbose=True)
params_dict = {"xxx:yyy"}
best_model, best_model_hyperparams, all_model_results = optimizer.hyperparam_search(params_dict, train_dataset, valid_dataset, transformers, metric=metric)

おわりに

DeepChemでここまで既存記述子の使用にこだわる必要があるのか?という気もするが、既存手法とGCN(Graph Convolution Network)をフェアに比較しようとした場合、DeepChemの上で既存手法を使えるにするのがよいと判断する。(と前向きに自分を奮い立たせてみる)

参考文献

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away