4/5/2021

Python

\(\texttt{Python}\) es un lenguaje de programación en el que no es necesario compilar, se puede ejecutar línea por línea. La versión actual es \(\texttt{Python 3}\). Una forma de codificar en \(\texttt{Python}\) es a través de \(\texttt{Jupyter Notebook}\). Esta es probablemente la mejor manera de combinar programación, texto e imágenes. En una notebook, todo está dispuesto en celdas. Para ejecutar el contenido de una celda de código, se puede hacer clic en ella y presionar \(\texttt{Shift + Enter}\), o si hay una pequeña flecha a la izquierda, se puede hacer clic en ella. La importación de paquetes se realiza con la instrucción \(\texttt{import}\) y para ahorrar escritura, es común utilizar un alias abreviado para cada paquetería. Para sacar todas las funciones directamente de un paquete se cuenta con la llamada \(\texttt{from}\) paquetería \(\texttt{import *}\)

Introducción a DeepChem

\(\texttt{DeepChem}\), es una biblioteca de \(\texttt{Python}\) que está integrada y construida sobre la plataforma \(\texttt{TensorFlow}\) para facilitar el uso del aprendizaje profundo en las ciencias biomédicas. \(\texttt{DeepChem}\) proporciona soporte para el manejo de moléculas, conjuntos de datos genéticos o conjuntos de datos de microscopía. Usa la abstracción básica de objetos del tipo \(\texttt{Dataset}\) que contienen la información sobre un conjunto de muestras: los vectores de entrada \(\mathbf{x}\), los vectores de salida \(\textbf{y}\), y posiblemente otra información, como una descripción de lo que representa cada muestra. Hay subclases de \(\texttt{DataSet}\) correspondientes a diferentes formas de almacenar los datos. El objeto \(\texttt{NumpyDataset}\) en particular sirve como un contenedor conveniente para matrices del tipo \(\texttt{NumPy}\).

Se comienza con algunas importaciones simples:

import numpy as np
import deepchem as dc

Se construyen ahora algunas matrices en \(\texttt{NumPy}\):

x = np.random.random((4, 5))
y = np.random.random((4, 1))

La matriz \(\texttt{x}\) tiene cinco elementos (“características”) para cada muestra, \(\texttt{y}\) tiene un elemento para cada muestra. Se visualizan estas matrices

x 
y 

Ahora se envuelven estas matrices en un objeto \(\texttt{NumpyDataset}\):

dataset = dc.data.NumpyDataset(x, y)

Podemos desenvolver el objeto \(\texttt{dataset}\) para obtener las matrices originales que almacenamos dentro:

print(dataset.X)
print(dataset.y)

Y se puede notar que estas matrices son las mismas que las matrices originales \(\texttt{x}\), \(\texttt{y}\):

np.array_equal(x, dataset.X)
np.array_equal(y, dataset.y)

Entrenamiento

Se comienza con las importaciones necesarias para entrenar un modelo en \(\texttt{DeepChem}\):

import deepchem as dc
import numpy as np

\(\texttt{DeepChem}\) mantiene un módulo llamado \(\texttt{dc.molnet}\) (abreviatura de MoleculeNet) que contiene una serie de conjuntos de datos preprocesados para su uso en la experimentación. En particular, se usará la función \(\texttt{dc.molnet.load_tox21()}\), que cargará y procesará el conjunto de datos de toxicidad de \(\texttt{Tox21}\).

tox21_tasks, tox21_datasets, transformers = dc.molnet.load_tox21()

El proceso de featurización consiste en como el conjunto de datos que contiene información sobre moléculas se transforma en matrices y vectores. Exploremos cada una de las salidas de datos que hemos procesado.

tox21_tasks
len(tox21_tasks)

Cada una de estas tareas (tasks) corresponde con un experimento particular, en este caso, un ensayo enzimático que mide si las moléculas del conjunto de datos de \(\texttt{Tox21}\) se unen al objetivo biológico en cuestión. Los términos \(\texttt{NR-AR}\) y así sucesivamente se corresponden con estos objetivos. Cada objetivo es una enzima particular que se cree que está vinculada a respuestas tóxicas a posibles moléculas terapéuticas.

\(\texttt{tox21_datasets}\) es una tupla que contiene varios objetos del tipo \(\texttt{dc.data.Dataset}\) y que corresponden a los conjuntos de entrenamiento, validación y prueba

tox21_datsets

Estos son objetos \(\texttt{DiskDataset}\); el módulo \(\texttt{dc.molnet}\) almacena en disco estos conjuntos de datos para que no se necesite volver a featurizar el conjunto de datos \(\texttt{Tox21}\). La división de estos conjuntos de datos y sus formas son

train_dataset, valid_dataset, test_dataset = tox21_datsets
train_dataset.X.shape
valid_dataset.X.shape
test_dataset.X.shape

Ahora se visualizan a los vectores \(\texttt{y}\)

np.shape(train_dataset.y)
np.shape(valid_dataset.y)
np.shape(test_dataset.y)

Las muestras corresponden a moléculas, las tareas corresponden a ensayos bioquímicos y cada etiqueta es el resultado de un ensayo y una molécula en particular. Sin embargo, algunas de estas etiquetas son marcadores de posición donde no se tienen datos para propiedades de algunas moléculas, por lo que se ignoran esos elementos al entrenar y probar el modelo verificando el campo \(\texttt{w}\) que registra sus pesos. La función de pérdida para un modelo, se multiplica por \(\texttt{w}\) antes de sumar tareas y muestras. Si una etiqueta tiene un peso de \(0\) no afecta la pérdida y se ignora durante el entrenamiento.

Así, se pueden encontrar cuántas etiquetas se han medido realmente en nuestros conjuntos de datos:

train_dataset.w.shape
np.count_nonzero(train_dataset.w)
np.count_nonzero(train_dataset.w == 0)

\(\texttt{transformers}\) es un objeto que modifica un conjunto de datos de alguna manera, o lo transforma. Las rutinas de carga de datos que se encuentran en MoleculeNet siempre devuelven una lista de transformadores que se han aplicado a los datos, ya que es posible que los necesite más adelante para deshacer las transformacies.

transformers

Aquí, los datos se han transformado para corregir datos desequilibrados.

Ahora, se comienza con la exploración de cómo podemos entrenar modelos en estos conjuntos de datos. El submódulo \(\texttt{dc.models}\) de \(\texttt{DeepChem}\) contiene una variedad de diferentes modelos que se heredan de la clase padre \(\texttt{dc.models.Model}\), y se proporcionará una instancia de un modelo \(\texttt{dc.models.MultitaskClassifier}\). Este modelo construye una red completamente conectada (un MLP) que asigna características de entrada a múltiples predicciones de salida. Esto lo hace útil para el problemas multitarea, donde hay de 12 ensayos diferentes que se desean predecir simultáneamente.

model = dc.models.MultitaskClassifier(n_tasks=12,
n_features=1024,
layer_sizes=[1000])

\(\texttt{n_tasks}\) es el número de tareas y \(\texttt{n_features}\) es el número de características de entrada para cada muestra. \(\texttt{layer_sizes}\) es una lista que establece el número de capas ocultas completamente conectadas en la red y el ancho de cada una. Con esto, cada objeto tipo \(\texttt{Model}\) tiene un método \(\texttt{fit()}\) que ajusta el modelo a los datos contenidos en un objeto \(\texttt{Dataset}\).

model.fit(train_dataset, nb_epoch=10)

\(\texttt{nb_epoch = 10}\) dice que se llevarán a cabo \(10\) épocas de entrenamiento de descenso en´ gradiente. Una época se refiere a una pasada completa a través de todas las muestras en un conjunto de datos. Para entrenar el modelo, se divide el conjunto de entrenamiento en lotes y se da un paso de descenso en gradiente para cada lote.

En la práctica, generalmente no hay suficientes datos de entrenamiento por lo que se queda sin datos antes de que el modelo esté completamente entrenado. Luego, se comienzan a reutilizar los datos realizando pases adicionales a través del conjunto de datos. Esto permite entrenar modelos con cantidades más pequeñas de datos, pero cuantas más épocas se usen, más probabilidades hay de terminar con un modelo sobreajustado.

La clase \(\texttt{dc.metrics.Metric}\) proporciona varias métricas. Una buena táctica es calcular la puntuación media de \(\texttt{ROC AUC}\) en todas las tareas, especificando \(\texttt{np.mean}\) para reportar la media en las tareas.

metric = dc.metrics.Metric(dc.metrics.roc_auc_score, np.mean)

Se eligen valores de umbral que predicen que una molécula es tóxica o no tóxica siempre que la salida sea mayor que el umbral. Un umbral bajo producirá muchos falsos positivos y un umbral más alto dará menos falsos positivos pero más falsos negativos. La curva ROC es una forma conveniente de visualizar esta compensación. Prueba muchos valores de umbral diferentes, luego traza una curva de la tasa de verdaderos positivos frente a la tasa de falsos positivos a medida que se varía el umbral. El área bajo la curva (AUC) proporciona una indicación de la capacidad del modelo para distinguir diferentes clases. Los modelos en \(\texttt{DeepChem}\) admiten la función \(\texttt{model.evaluate()}\), que evalúa el rendimiento del modelo en un conjunto de datos y una métrica determinados

train_scores = model.evaluate(train_dataset, [metric], transformers)
test_scores = model.evaluate(test_dataset, [metric], transformers)

Por último, se observan los resultados de las puntuaciones

print(train_scores)
print(test_scores)

Para evitar interpretar un sobreajuste, la puntuación del conjunto de pruebas es la que realmente se considera.