【個人開発】化合物の物性予測サービスをリリースした話

# はじめに
化合物の物性を予測するサービス CHeMODEL(https://chemodel.herokuapp.com/) を個人開発によりリリースしてみた。

化合物データを対象とした機械学習については、様々な手法や実装が論文やgithubで公開されているものの、それらを用いてWEB上で手軽に予測できるサービスが少ないと感じていた。そもそもニーズがあまりないという可能性もあるが、化学分野の業務知識と開発ノウハウ**（＋それなりの時間）**を持つ人間が少ないことも関係しているかもしれない。そういうわけで今回、自分の持つ知識と開発スキルを総動員し、WEB上での予測サービスを開発するに至った。

# 作ったもの
今回2つの物性の予測サービスを開発した。単に予測するだけでなく、**予測根拠**や**予測の信頼性**が分かるような機能にこだわってみた。以下、各画面と機能について説明する。

## 化合物入力
トップ画面である化合物入力 (URL : https://chemodel.herokuapp.com/) は以下の通りである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/5699c2b9-5e35-041c-f60a-d0777c5642aa.png)

予測したい化合物は以下4つのいずれかの方法で指定することができる。

- SMILES文字列
- SMILESファイル (1行1SMILESのファイル)
- SDFファイル
- 化学構造式(直接描画)
 
予測したい物性のモデルは、以下いずれかまたは両方を選択することができる。

- Solubility
- Lipophilicity

なお、**Solubility**は、https://github.com/rdkit/rdkit/tree/master/Docs/Book/data/solubility.train.sdf 、 
**Lipophilicity**は、https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/Lipophilicity.csv のデータを用いた機械学習モデルである。

※ Lipophilicity単独指定の場合、負荷の高いLipophilicityの適用領域の表示処理が行われ実質タイムアウトしてしまうため、**必ずSolubilityを選択してほしい**(笑)。
※同様に、化合物を複数指定すると、**かなりの確率**でタイムアウトが発生するため、単独の指定をお奨めする(笑)。

## 予測結果一覧表示
化合物入力画面でsubmitボタンをクリックすると、以下の通り予測結果が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/ccdfc0c3-6fab-40ee-d512-4044fb22f4e5.png)

各情報の意味は次の通りである。

- **Predicted Value** 物性の予測値である。
- **AD distance** 予測モデルの学習データの重心からの距離である。どの程度、予測に信頼性を示しているかの指標として今回表示した。
なお、本来であればモデル構築時の説明変数で距離を求めるべきであるが、モデルの構築に用いたDeep Learningモデルから説明変数に相当するデータを取得するのが**面倒だった**ため、今回 RDKit の記述子計算結果を利用している。

- **AD Knn5**　学習データのうち最も近い距離にある5つの化合物との平均距離である。これも、予測の信頼性を示す指標である。
AD distance が学習データの分布の範囲内にあったとしても、AD Knn5が大きい場合(平均距離が大きい場合)、**学習データの中に近いサンプルがない**ことになり、**予測の信頼性が低い**と考えられる。
- **explanatory visualization** 化学構造中の**どの原子が予測に寄与したか**を示している。赤い程プラスに貢献しており、青い程マイナスに貢献している。画像をクリックすると2倍に拡大表示する(下図)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/03c0be80-e1d2-4764-d91d-251ba5c99580.png)


## 予測モデルの適用領域の可視化
予測結果一覧表示のタブで「Applicability Domain」を選択すると、以下の通り、学習データおよび予測対象化合物が2次元に可視化される。2次元座標の生成は主成分分析を用いた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/b875980c-8d55-3e70-f3e8-8064368ef866.png)

予測対象のデータは赤でプロットされる。マウスカーソルをあてると対象サンプルの情報をホバーで表示する。後述する色モードが **Normal**の場合、クリックするとサンプルが大きな青色表示に変わり、そのサンプルの近傍の10個のサンプルがグレー表示される（下図）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/c7e5df8b-4b59-91c4-282a-8f7c246246e7.png)


この状態で他のサンプルにマウスオーバすると、クリック中のサンプルとの距離が追加で表示される。これらの機能を用意した理由は、2次元で距離が近いからといって、必ずしも元の多次元における距離が近いとは限らないため、近傍との距離を正しく確認するためである。

また、グラフの色付け方法を指定する**色モード**は以下4種類を用意した。

- **Normal** 色は全て同一色。この場合、先に説明したようにクリックしたサンプルと他のサンプルとの距離を確認できる。
- **AD Distance** AD Distance の大きさに応じて色を表示する。学習データの重心からの距離の分布を視覚的に確認できる。
- **AD Knn5** AD Knn5 の大きさに応じて色を表示する。学習データの密度の分布を視覚的に確認できる。
- **Target value** 実測値(予測対象の場合は予測値)に応じて色を表示する。実測値による分布がどうなっているかを視覚的に確認できる。

## その他
予測結果や信頼性指標をCSVでダウンロードできるようにした。

# 実装方法の詳細

### **デプロイ環境**
Herokuの無料プランを使った。またconda環境をデプロイしたかったため、Dockerによるデプロイを実施した( [Docker によるデプロイ](https://devcenter.heroku.com/ja/categories/deploying-with-docker) 参照)。

ちなみにDockerでデプロイする手順は以下の通りで、2回目以降は**実質3分程度**でリリースできる。"chemodel" は Heroku のアプリケーション名になるので適宜読み替えてほしい。

```sh
heroku login
heroku container login
heroku container:push web -a chemodel
heroku container:release web -a chemodel
```

ログは以下で参照可能である。

```sh
heroku logs --tail -a chemodel
```

### 言語/WEBアプリフレームワーク
言語は**Python**、WEBアプリフレームワークは**Django3**を採用し、Visual Studio Codeで開発した。非常に快適であり、これを機にPyCharmからVisual Studio Codeに乗り換えるほどであった。

### データベース
データベースは今回使用しなかった。Herokuの無料プランではレコード数の制限があるため、ユーザの入力データや、予測結果は、1時ファイルやメモリ上で処理することとし、データベースに**一切保存しない**ようにした。

### フロントエンドフレームワーク
**BootStrap4**を用いた。デザインはこれまで苦手な領域であったが、ファイルアップロードのフォームや、タブ切り替え等、このおかげで**苦労なく違和感のないUIを**実現できた。

### 予測モデル構築
PyTorchを使った自前のGCNライブラリ [simple-GCN](https://github.com/kimisyo/simple-GCN) を用いて予測モデルの構築を行った。

### 化合物データ処理
化合物画像の生成や、主成分分析用の記述子計算などに**RDKit**に用いた。

### 化合物構造入力エディタ
[JSME](https://jsme-editor.github.io/) を用いた。フリーのものは現状これ1択になる。

### 予測根拠表示
予測に寄与した原子への色付けはCaptum (https://captum.ai/) により実現した。モデルを解釈するアルゴリズムをまとめたライブラリであり、PyTorchに対応している。わがライブラリ[simple-GCN](https://github.com/kimisyo/simple-GCN)で作成した予測モデルに適用し、原子に色付けを行った断片コードを以下に示す。今回は**IntegratedGradients**という入力層の寄与度が分かる手法を利用した。


``` python 
  from captum.attr import Saliency, IntegratedGradients

  for i, (mol, batch) in enumerate(zip(mols, iters)):
        data = []
        atom_features, bond_features, array_rep, labels = batch

        y = model(atom_features, bond_features, array_rep)
        #予測値
        data.append(float(y.detach().numpy()))
        #予測確率
        data.append(float(y.detach().numpy()))

        #根拠画像の生成
        atom_features.requires_grad = True
        ig = IntegratedGradients(model)
        result = ig.attribute(atom_features, target=0,
                              additional_forward_args=(bond_features, array_rep),
                              internal_batch_size=atom_features.shape[0])

        # 原子毎に寄与度を集約
        total = torch.sum(result, dim=1)
        atom_hilights = {}
        bond_hilights = {}
        radii = {}
        max = 3
        for value, ix in zip(total, array_rep["rdkit_ix"]):
            # 値に応じた色の選定
            value = value.item()
            r = 0
            b = 0
            if value > 0:
                rate = value / max
                if rate > 1:
                    rate = 1
                color = get_color('AA', '55', 'AA', rate)
            else:
                rate = -value / max
                if rate > 1:
                    rate = 1
                color = get_color('55', '55', 'AA', rate)

            atom_hilights[int(ix)] = color
            radii[int(ix)] = 0.3
        exp_image = generate_exp_image(mol,
                       list(atom_hilights.keys()),
                       list(bond_hilights.keys()),
                       atom_hilights,
                       bond_hilights,
                       radii,
                       (240, 240),
                       type="image")
        data.append(exp_image)
        ret.append(data)

```

### 可視化処理
主成分分析は**scikit-learn**を用いた。

### グラフ描画
Jupyterではmatplotlibを使うところであるが、WEB上であるため一時期流行った**D3.js**で実装した。D3.jsの取り扱いを含むJavaScriptでのグラフの描画が、今回苦戦したところであったが、**非同期処理への対応** などでかなりの経験を積むことができた。

# 苦労したこと
### ローカルとDockerでの動作差異
ローカル(Windows10)で構築したcondaと同じ環境をDocker上に再現できなくて困った。これは今回採用したDockerのベースがLinuxベースであり、Windowsのローカルでインストールできたバージョンが、Docker経由だと見つからなかったためである。
また、グラフ可視化のために検討していたUMAPについて、ローカルで作成したモデルが、Dockerに登載すると動作しないことが判明した。これもOSの差が影響したとみている。condaを使う場合の注意点といえる。

### Herokuアプリの起動に時間がかかり、タイムアウトが発生する。　
Herokuの無料プランでは、30分アクセスがないとスリープする仕様である。スリープ後に起動する際にタイムアウトエラーがかなりの頻度で発生する。トップ画面を表示しているだけなのであるが、Dockerコンテナの起動に時間がかかっているのかもしれない。その場合以下の画面が表示される。この後再度アクセスすると**運がよければ表示される(笑)**。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f42fbc71-9539-f160-b15a-f3b1e8ee1d87.png)

ちなみにログはこんなエラーが表示されることが多い。

```
4-9030-4b31-b5f1-6610412427a7 fwd="XXX.XXX.XXX.XXX" dyno= connect= service= status=503 bytes= protocol=https
2021-05-08T04:57:26.885433+00:00 heroku[router]: at=error code=H20 desc="App boot timeout" method=GET path="/" host=chemodel.herokuapp.com request_id=772a517
```

# まとめと今後の課題
- 今回は、一通りサービスの実現の技術的な目途を立てることができたので、次回は**完成度の高い**サービスを目指したい。
- 性能面でいえば、Lipophilicityのグラフ表示が異常に遅い。グラフ表示の際に、サンプル同士の総当たりの距離計算したファイルを読み込んでいるのだが、4200 x 4200の組み合わせ数となるため時間がかかっている。実用上学習データ同士の距離表示は除外してもよいだろう。
- 大量のデータをさばけるよう、Heroku以外のPaaSも検討したい。
- 対応モデルや予測手法を増やしてみたい。

# 参考文献
- [D3.js](https://d3js.org/)
- [simple-GCN](https://github.com/kimisyo/simple-GCN)
- [HerokuにConda環境で開発したアプリをデプロイする方法](https://qiita.com/kimisyo/items/b7fbdd9afe82b872a18b)
- [Captum](https://captum.ai/)
- [Captumを試す](https://qiita.com/gorogoroyasu/items/819ce2613e72ac96d588)