使用Chainer尝试简单的QSAR模型[预测化合物的血脑屏障通透性]

2020年3月2日

QSAR(定量结构-活性关系)是化学物质的结构与其生理活性(毒性,与酶结合的能力,作为药物的活性等)之间的统计相关性。可以根据大量化学实验数据集从相关性预测化合物的性能。

这次,我们将使用日本制造的深度学习框架Chainer创建一个简单的QSAR模型,“预测化合物的血脑屏障通透性”,并针对测试集验证其性能。

预测目标和数据

对于数据,使用 Molecule Net 的 BBBP。请参阅下面的数据鸟瞰图。

关于该化合物的血脑屏障渗透性,“渗透率”为XNUMX,“非渗透率”为XNUMX。

建立模型

环境

from rdkit import rdBase
import chainer
print('rdkit version: ',rdBase.rdkitVersion)
chainer.print_runtime_info()
rdkit版本:2019.03.4平台:Linux-5.0.0-37-generic-x86_64-with-debian-buster-sid Chainer:6.2.0 NumPy:1.17.4 CuPy:CuPy版本:6.2.0 CUDA根目录:/ usr /本地/ cuda CUDA Build版本:10010 CUDA驱动程序版本:10010 CUDA Runtime版本:10010 cuDNN Build版本:7500 cuDNN版本:7605 NCCL Build版本:2402 NCCL Runtime版本:2402 iDeep:不可用

您可以检查chainer.print_runtime_info()中使用的Chainer,Numpy和Cupy的版本。

建筑模型

import numpy as np
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Draw, PandasTools, Descriptors
 
# データの読み込み
df = pd.read_csv('BBBP.csv',index_col=0)
 
# smilesからmolファイルを生成し、データフレーム中に加える
PandasTools.AddMoleculeColumnToFrame(df, smilesCol = 'smiles')
 
# molができなかった行を削除する
df = df.dropna()
 
# molファイルから化合物記述子を算出する
for i,j in Descriptors.descList:
    df[i] = df['ROMol'].map(j)
df['Ipc'] = [Descriptors.Ipc(mol, avg=True) for mol in df['ROMol']]  
 
# chainer用にデータ型を変換
x = df.iloc[:,4:].values.astype('float32')
y = df['p_np'].values.astype('int32')
indices = np.array(range(x.shape[0])) # train_test_split後も列番号を保持しておく
 
# train, test, valに分割
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test, indices_train, indices_test = train_test_split(x, y, indices, test_size=0.05, random_state=123)
 
# 説明変数の標準化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train)
x_train= scaler.transform(x_train)
x_test = scaler.transform(x_test)
 
print(type(x_train), x_train.shape, type(y_train), y_train.shape)
print(type(x_test), x_test.shape, type(y_test), y_test.shape)
(1937,200) (1937年) (102,200) (102,) 

有关从mol对象创建描述符的信息,请参见此处。
从SMILES计算分子描述符和指纹并将它们存储在数据框中[Python,RDKit]

有关如何使用Chainer的官方教程非常广泛。
深度学习Chainer教程入门

# 説明変数と目的変数のセットで使えるように変換する
from chainer.datasets import TupleDataset
train = TupleDataset(x_train, y_train)
test = TupleDataset(x_test, y_test)
 
# イテレータの準備
from chainer.iterators import SerialIterator
train_iter = SerialIterator(train, batch_size=64, repeat=True, shuffle=True)
test_iter = SerialIterator(test, batch_size=64, shuffle=False, repeat=False)
 
# ニューラルネットワークの作成
# 3層のmulti layer perceptron(MLP)
import chainer.links as L
import chainer.functions as F
from chainer import Chain
from chainer import optimizers, training
from chainer.training import extensions

class MLP(chainer.Chain):
 
    def __init__(self):
        super().__init__()
        with self.init_scope():
            self.fc1 = L.Linear(None, 100)
            self.fc2 = L.Linear(None, 20)
            self.fc3 = L.Linear(None, 2)
 
    def forward(self, x):
        h = F.relu(self.fc1(x))
        h = F.relu(self.fc2(h))
        h = self.fc3(h)
        return h
 
# ネットワークをClassifierでラップしする
# (目的関数(デフォルトはsoftmax交差エントロピー)の計算し、損失を返す)
predictor = MLP()
net = L.Classifier(predictor)
 
# 最適化手法を選択して、オプティマイザを作成する
optimizer = optimizers.MomentumSGD(lr=0.1).setup(net)
 
# アップデータにイテレータとオプティマイザを渡す
updater = training.StandardUpdater(train_iter, optimizer, device=-1)
trainer = training.Trainer(updater, (50, 'epoch'), out='/results/')
from chainer.training import extensions
 
trainer.extend(extensions.LogReport(trigger=(5, 'epoch'), log_name='log'))
trainer.extend(extensions.snapshot(filename='snapshot_epoch-{.updater.epoch}'))
trainer.extend(extensions.dump_graph('main/loss'))
trainer.extend(extensions.Evaluator(test_iter, net, device=-1), name='val')
trainer.extend(extensions.PrintReport(['epoch', 'iteration', 'main/loss', 'main/accuracy', 'val/main/loss', 'val/main/accuracy', 'fc1/W/data/mean', 'elapsed_time']))
trainer.extend(extensions.PlotReport(['fc1/W/grad/mean'], x_key='epoch', file_name='mean.png'))
trainer.extend(extensions.PlotReport(['main/loss', 'val/main/loss'], x_key='epoch', file_name='loss.png'))
trainer.extend(extensions.PlotReport(['main/accuracy', 'val/main/accuracy'], x_key='epoch', file_name='accuracy.png'))
trainer.extend(extensions.ParameterStatistics(net.predictor.fc1, {'mean': np.mean}, report_grads=True))
 
trainer.run()
from IPython.display import Image, display
display(Image(filename='results/accuracy.png'))

看来准确性是合理的,但是即使学习进展,测试集的准确性也没有太大提高。

推理

# 学習したモデルで推論してみる
with chainer.using_config('train', False), chainer.using_config('enable_backprop', False):
    y_pred = predictor(x_test)
 
# 推論結果の確認
print('accuracy', F.accuracy(y_pred, y_test)) # accuracy variable(0.88235295)
 
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred.data.argmax(axis=1))
 数组([[21,7],[5,69]]) 

从图中可以看出,准确性是一个值。当通过混淆矩阵评估分类的准确性时,存在假阳性和假阴性,但是似乎偏向一侧的分类不能获得准确性。

# 一部予測結果を見てみる
for i in range(int(len(y_pred)/10)):
    print('No.', indices_test[i])
    print('label:', y_test[i])
    print('pred :', np.argmax(y_pred[i].array))
    img = Draw.MolToImage(df.ROMol[indices_test[i]])
    display(img)

图像是输出的一部分,但是正确的答案是可以用人眼识别。仔细检查您所犯的错误似乎很有趣。