Дескрипторы заряда (признаки) для предсказания молекулярных свойств.

В этом посте я пытаюсь исследовать отпечатки дескрипторов заряда, как это можно назвать. Дэвид Винклер в 2009 году опубликовал статью об этом Towards Novel Universal Descriptors: Charge Fingerprints. Это выглядит очень интересно и может дать представление об электростатических свойствах молекул, которые играют важную роль в молекулярных взаимодействиях и сродстве к связыванию. Кодируя частичные заряды атомов, отпечатки пальцев могут помочь в сравнении сходств и различий между молекулами на основе их распределения заряда, а также могут помочь в прогнозировании активности соединений в биологических системах или их физико-химических свойств.

Вычисление отпечатков заряда обычно включает два основных этапа. Во-первых, парциальные заряды атомов в молекуле вычисляются с использованием модели заряда, такой как Gasteiger или MMFF94 в открытом вавилоне. Существует несколько способов вычисления. Еще одним пакетом квантовой химии является psi4, который можно использовать для расчета зарядов Малликена для каждого атома. Эти модели основаны на различных приближениях и эмпирических правилах, полученных из расчетов квантовой химии и экспериментальных данных. Такие методы, как метод выравнивания заряда Гастайгера, а также более современный метод выравнивания электроотрицательности (EEM), основанный на уравнении Сандерсона
. Другие методы, такие как полуэмпирические методы молекулярных орбиталей, DFT или методы ab initio, также могут использоваться для расчета зарядов атомов, если границы ячеек установлены соответствующим образом. Однако я не пытался воспроизвести бумагу, но попытался использовать идею для создания отпечатков пальцев.

После получения частичных зарядов их можно закодировать в отпечаток пальца, который обычно представляет собой двоичный вектор фиксированной длины. Обычный подход к созданию отпечатка пальца заключается в дискретизации значений заряда по ячейкам и назначении каждого атома определенной ячейке. Это приводит к разреженному двоичному вектору, где каждый элемент соответствует определенной комбинации атома и ячейки заряда. Наличие «1» в определенном положении в векторе указывает на то, что соответствующий атом имеет частичный заряд в пределах диапазона соответствующего бина. Сравнивая зарядовые отпечатки разных молекул, можно оценить их сходство с точки зрения электростатических свойств, что важно для различных задач химико-информатики, таких как виртуальный скрининг, поиск сходства и предсказание свойств.

Код ниже показывает, как вы можете сгенерировать эти отпечатки пальцев с помощью принудительного поля mmff94.

import numpy as np
import openbabel as ob
from openbabel import openbabel

def tanimoto_similarity(fp1, fp2):
    common_bits = np.bitwise_and(fp1, fp2).sum()
    total_bits = np.bitwise_or(fp1, fp2).sum()
    return common_bits / total_bits

def generate_charge_fingerprint(smiles, n_bits=2048, bin_min=-1.0, bin_max=1.0, nbins=32):
    # Initialize Open Babel objects
    ob_conversion = ob.OBConversion()
    ob_conversion.SetInFormat("smi")
    ob_mol = ob.OBMol()

    # Convert SMILES to Open Babel molecule
    ob_conversion.ReadString(ob_mol, smiles)
    ob_mol.AddHydrogens()

    ob_charge_model = ob.OBChargeModel.FindType("mmff94")
    ob_charge_model.ComputeCharges(ob_mol)
    charges = [ob_mol.GetAtom(i+1).GetPartialCharge() for i in range(ob_mol.NumAtoms())]

    # Initialize the fingerprint vector
    fingerprint = np.zeros(n_bits, dtype=np.uint8)

    # Create bins for the partial charges
    bins = np.linspace(bin_min, bin_max, nbins + 1)

    # Set the corresponding bits for each atom's partial charge
    for idx, charge in enumerate(charges):
        bin_index = np.digitize(charge, bins) - 1
        bit_index = idx * nbins + bin_index
        if bit_index < n_bits:
            fingerprint[bit_index] = 1

    return fingerprint

Добавление атомов водорода в молекулярную структуру перед расчетом дескрипторов заряда важно, поскольку атомы водорода играют важную роль в распределении зарядов внутри молекулы. Большинство молекулярных представлений, таких как SMILES или SDF, явно не включают атомы водорода, поскольку они часто опускаются для краткости и простоты. Однако атомы водорода участвуют в различных химических взаимодействиях, таких как образование водородных связей и протонирование/депротонирование, которые могут существенно влиять на распределение заряда молекулы и ее физико-химические свойства. При расчете дескрипторов заряда базовые модели заряда, такие как Gasteiger или MMFF94, нуждаются в точной информации о молекулярной структуре, чтобы обеспечить надежные оценки частичного заряда. Явно добавляя атомы водорода в молекулу, вы гарантируете, что модели заряда учитывают правильную среду связывания каждого атома, что приводит к более точным дескрипторам заряда.

Затем следующая часть довольно проста, когда вы получаете отпечатки пальцев и видите, имеют ли эти отпечатки смысл или нет, обучая модель. Я использовал здесь xgboost с 5-кратным CV. Набор данных, который меня интересовал, был herg, который я рассмотрел в сравнительном исследовании tdc. Однако я мало изучал другие наборы данных, но результаты с этим набором данных выглядят так, будто в этом дескрипторе что-то есть. Среднее значение AUC составило около ROC-AUC: 0,7929.

import numpy as np
import xgboost as xgb
from sklearn.model_selection import KFold
from sklearn import metrics
from sklearn.metrics import roc_auc_score, confusion_matrix

from tdc.single_pred import Tox
data = Tox(name = 'hERG')
split = data.get_split()

train,test = split['train'],split['test']

smiles_list = train['Drug'].tolist()
y = train['Y'].values

# Generate charge fingerprints for each molecule in the dataset
fingerprints = np.array([generate_charge_fingerprint(smiles,n_bits=2048, nbins=32) for smiles in smiles_list])

# Generate charge fingerprints for each molecule in the dataset
#fingerprints = np.array([generate_charge_fingerprint(smiles) for smiles in smiles_list])

# Cross-validation parameters
n_splits = 5
kf = KFold(n_splits=n_splits, shuffle=True, random_state=123)


params = {
  'colsample_bynode': 0.8,
  'learning_rate': 0.01,
  'max_depth': 12,
  'alpha':0.5,
  'lambda':0.5,
  'min_child_weight':1,
  'num_parallel_tree': 100,
  'objective': 'binary:logistic',
  'subsample': 0.8,
  'scale_pos_weight':1,
  'tree_method':'hist',
  'eval_metric':['auc','error'],
  'n_jobs': -1,
 'random_state': 123}

# Perform cross-validation
roc_auc_scores = []
for train_index, test_index in kf.split(fingerprints):
    X_train, X_test = fingerprints[train_index], fingerprints[test_index]
    y_train, y_test = y[train_index], y[test_index]

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    bst = xgb.train(params, dtrain, num_boost_round=100, early_stopping_rounds=10, evals=[(dtest, 'test')])

    y_pred_proba = bst.predict(dtest)
    y_pred = y_pred_proba > 0.5
    
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    roc_auc_scores.append(roc_auc)
  
# Calculate the average ROC-AUC score
average_roc_auc = np.mean(roc_auc_scores)
print(f"Average ROC-AUC: {average_roc_auc:.4f}")

# Train the model on the full dataset
dtrain_full = xgb.DMatrix(fingerprints, label=y)
bst_full = xgb.train(params, dtrain_full, num_boost_round=100)

Результаты тестов, которые я тестировал, приведены ниже, похоже, что эти функции могут быть ценным способом их использования в моделях.

from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score

test = split['test']
test_list = test['Drug'].tolist()
y_test = test['Y'].values
# # Generate charge fingerprints for each molecule in the dataset
test_fp = np.array([generate_charge_fingerprint(smiles,n_bits=2048, nbins=32) for smiles in test_list])
test = xgb.DMatrix(test_fp, label=y_test)
y_pred_proba = bst_full.predict(test)
y_pred = y_pred_proba > 0.5
    
roc_auc = roc_auc_score(y_test, y_pred_proba)
confusion_matrix(y_test, y_pred)


print('Precision: %.3f' % precision_score(y_test, y_pred))
 
print('Recall: %.3f' % recall_score(y_test, y_pred))
 
print('Accuracy: %.3f' % accuracy_score(y_test, y_pred))
print('F1 Score: %.3f' % f1_score(y_test, y_pred))

Точность: 0,839 Отзыв: 0,959 Точность: 0,832 Оценка F1: 0,895

Зарядовые отпечатки могут быть очень ценными при моделировании различных молекулярных свойств и активности, поскольку они дают представление об электростатическом поведении соединений, которое является ключевым фактором во многих химических и биологических взаимодействиях. Включение информации о заряде в молекулярные модели позволяет лучше уловить нюансы молекулярного распознавания, связывания и реактивности, что приводит к более точным прогнозам и лучшему пониманию лежащих в основе молекулярных механизмов.

Пожалуйста, оставляйте комментарии, если вы считаете эту идею полезной и хотели бы узнать больше по этой теме.