mordredをDeepChemのFeaturizerとして使う

#はじめに
DeepChemでは、Featurizerの1つとしてRDkitDescriptorsが用意されており、さらにscikit-learnのアルゴリズムも用意されているため、従来の機械学習手法とグラフコンボリューション手法による精度の比較が行えて便利である。そこで他の記述子計算ソフトウェアであるmordredも組み込めるようにすれば精度比較の幅が広がりさらに便利になるだろうと考えた。今回mordredのDescriptorをDeepChemのFeaturizerとしてシームレスに使えるようにしてみたのでメモっておく

#環境
- python 3.6
- deepchem  2.2.1.dev54
- rdkit 2019.03.3.0 
- morded 1.1.2 

#ソース
以下、作成したfeaturizerのソース。DeepChemのFeaturizerクラスを継承し、_featurizeメソッドをオーバライドしてmoredredの記述子計算処理を書いた。

```MordredDescriptor.py
from deepchem.feat import Featurizer
from mordred import Calculator, descriptors

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:
          #　面倒なためエラーは0で補完
          #  本来ここはnanにして、特徴選択で排除するなどの処理が必要
          ret.append(0)

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

      return ret

```

#使い方
こんな感じで、普通にfeatureizerとして定義して使える。
最後のmetric以下は、かなり適当に端折っているので、DeepChemのExampleを参考に各自自分の好きな予測アルゴリズムを書いてほしい。

```python
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

# 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)
 transformers = [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)

```

#課題
- mordredの場合は、計算エラーが比較的多いため、機械学習アルゴリズムを実行する前に、エラーとなった記述子を除去するなどの処理が必要となる場合がある(欠損値を処理できないアルゴリズムの場合)。今回、面倒なためエラーは一旦0で置き換えたが、ここはnanで返して、TransFormer等で何等かの変換を行うのがDeepChem的な考えか？

- DeepChemのソースをざっと見たところ、1分子ずつFeaturizerのfeaturizeメソッドを呼び出して処理をしている。このため今回は1分子ずつ処理される前提の実装にせざるを得なかったのだが、これだと並列処理が行われないためmordredのpandasメソッドで複数分子をまとめて処理するよりも遅い。これはDeepChemのFeaturizerの制約のため、今回やり方では回避が困難である。