Нейронные сети в борьбе с раком
В прошлом году мы с Артуром Кадуриным решили присоединиться к новой волне обучения нейронных сетей — к глубокому обучению. Сразу стало ясно, что машинное обучение во многих сферах практически не используется, а мы в свою очередь понимаем как его можно применить. Оставалось найти интересную область и сильных экспертов в ней. Так мы и познакомились с командой из Insilico Medicine (резидент БМТ-кластера фонда «Сколково») и разработчиками из МФТИ и решили вместе поработать над задачей поиска лекарств против рака.
Ниже вы прочитаете обзор статьи The cornucopia of meaningful leads: Applying deep adversarial autoencoders for new molecule development in oncology, которую мы с коллегами из Insilico Medicine и МФТИ подготовили для американского журнала Oncotarget, с упором на реализацию предложенной модели во фреймворке tensorflow. Исходная задача была следующей. Есть данные вида: вещество, концентрация, показатель роста раковых клеток. Нужно сгенерировать новые вещества, которые останавливали бы рост опухоли при определенной концентрации. Датасет доступен на сайте NCI Wiki.
Давайте более подробно опишем данные.
Вещества представлены в стандартном для биоинформатики виде — SMILES, фактически это унифицированный способ записи соединений в ASCII. К сожалению, здесь нет полной унификации, разные пакеты генерируют разные SMILES. Мы пользовались теми, что созданы с помощью пакета CACTVS. Подробнее об этом написано, например, тут. Каждый SMILES можно перевести в бинарное представление размерности 166: Molecular ACCess System (MACCS) chemical fingerprint. Каждый бит в этом представлении — ответ на вопрос о соответствующей молекуле, MACCS keys — набор соответствующих вопросов. Примеры вопросов: в молекуле меньше трех атомов кислорода? Есть ли в молекуле кольцо размера три? Более подробное описание — здесь.
Индекс роста определяется следующим образом:
где — начальный размер опухоли, — размер опухоли через какой-то интервал времени в присутствии препарата, — размер опухоли в контрольной группе без добавления препарата. Фактически показывает, насколько медленнее растет опухоль или как быстро она уменьшается.
После описанной обработки получается 78 728 троек вида fingerprint, log(concentration), GI, описывающие 6252 молекулы. Для валидации модели мы использовали бинарные представления почти для 100 миллионов молекул из базы Pubchem (The PubChem Project).
Пример полученных исходных данных:
Fingerprint Log(concentration) Growth inhibition percentage 000011100010. –5 10 % 00000110101… –7 –15 % 10010011000… –4,8 75 %
Пути решения
Наивный подходПервый приходящий в голову подход: обучить регрессор (например, xgboost) определять по двойке вида fingerprint, log(concentration) показатель роста, а дальше семплировать такие двойки из большой базы и выделять лучшие.
Мы не смогли довести такой подход до чего-либо хорошего.
Генеративный подходДругой вариант: научиться генерировать пары (fingerprint, концентрация) и дальше искать похожие молекулы из большой базы соединений. Один из первых вопросов: как сравнивать разные молекулы? К счастью (?), опытным путем выяснили, что в данном случае можно использовать меру Жаккара, причем для многих приложений молекулы с коэффициентом Жаккара больше 0,8 уже считаются близкими.
Кроме решения задачи как таковой мы преследовали цель научиться использовать новые генеративные нейросетевые подходы. Известны две современных генеративных модели — VAE (variational autoencoder) и GAN (generative adversarial encoder). Так как обе модели уже несколько раз были описаны на Хабре, ограничимся небольшим рассказом о них.
В случае VAE решается задача приближения в виде
где — входные данные, — латентное представление, а — параметры модели.
Стандартный VAE предполагает, что распределения и нормальные, и приближает с помощью нейронной сети со структурой autoencoder.
Входные данные подаются на вход энкодеру, на выходе которого получается нормальное распределение . Далее из этого распределения семплируется вектор , который, в свою очередь, подается на вход декодеру. На выходе — вектор , реконструкция вектора .
При обучении нейронной сети оптимизируется среднее между двумя функциями потерь: KL divergence между распределением на латентном слое и и ошибка реконструкции — расстояние между и .
В случае GAN обучаются две нейронных сети: генератор и дискриминатор. Генератор переводит латентную переменную , семплированную из приорного распределения , в пространство входных данных, а дискриминатор учится отличать семплированные данные от настоящих. Задачу можно сформулировать в теоретико-игровой форме:
Наконец, в статье Adversarial Autoencoders вводится смесь моделей VAE и GAN. Так же, как и в обычном AE, вход подается в энкодер, выход которого , в свою очередь, попадает на вход декодеру. Сеть-дискриминатор учится различать вектор , семплированный из , и вектор . Как и в GAN, оптимизируются две функции потерь:
- Веса генератора оптимизируются «обманывать» дискриминатор: не давать отличить от .
- Веса дискриминатора оптимизируются отличать от .
При этом вводится дополнительная функция потерь — ошибка реконструкции. Оптимизация по функциям потерь происходит поочередно. Например, первый цикл обучения будет выглядеть следующим образом: обучить дискриминатор на мини-батче, обучить энкодер на следующем мини-батче и обучить автоэнкодер на третьем мини-батче.
Модели VAE и GAN в последнее время особенно популярны в задачах генерации изображений, причем модели, основанные на GAN, на практике показывают себя лучше (например, NIPS 2016 Tutorial: Generative Adversarial Networks).
По этой причине и из-за простоты добавления индекса роста в латентный слой автоэнкодера мы остановились на последнем подходе для решения конкретной задачи (ждем публикации нашей новой статьи с детальным сравнением разных моделей). К тому же в решении задачи мы так и не смогли найти гиперпараметры стандартной архитектуры GAN, с которыми она бы стабильно работала.
Модель, к которой мы пришли, выглядит следующим образом:
Для того чтобы концентрация как можно меньше влияла на выход энкодера, мы добавили еще одну функцию потерь — manifold loss:
где — энкодер, а — косинусная мера.
Наконец, в латентном слое мы решаем задачу регрессии: по латентному представлению фингерпринта и концентрации определить , добавив соответствующую функцию потерь. В итоге каждый шаг обучения состоит из пяти этапов:
- Обучение весов дискриминатора (как в GAN).
- Обучение весов генератора (как в GAN).
- Обучение автоэнкодера, т. е. оптимизация ошибки реконструкции.
- Обучение регрессора.
- Оптимизация функции потерь — manifold loss.
Код всего процесса обучения лежит здесь (в данный момент мы продолжаем эксперименты с кодом и собираемся в ближайшем будущем выложить улучшенную версию).
Разберем подробнее реализацию архитектуры сети и метрики. Веса каждой части сети мы выделяем в отдельный tf.name_scope, чтобы иметь возможность их по отдельности оптимизировать. Дополнительно мы выделяем концентрацию из входного слоя в отдельную переменную и отдельно считаем для нее ошибку реконструкции.
Как обычно, сеть является последовательностью линейных слоев и функций активации. Например, энкодер устроен следующим образом (aae_v3.py, строки 109—127):
Функция потерь дискриминатора:
Для борьбы с переполнением мы считаем сигмоиду не сразу на выходе дискриминатора, а прямо внутри функции потерь. В коде: