<jupyter_start><jupyter_text>Clasificación de imágenes pulmonares de rayos X mediante redes neuronales convolucionales Autores:*   <NAME>*   <NAME>*   <NAME>  IntroducciónLa neumonía es una infección que puede afectar a uno a ambos pulmones, causada tanto por virus como por hongos o bacterias. Según la OMS, en 2019 el número de fallecidos a causa de esta enfermedad fue de más de 2 millones de personas en todo el mundo, por lo que un diágnóstico temprano ayudaría a disminuir este número [[Ref.]](https://www.who.int/es/news-room/fact-sheets/detail/the-top-10-causes-of-death)En el trabajo final de la asignatura "Ingeniería de Grandes Volúmenes de Datos" pretendemos crear un modelo de "Deep Learning" basado en Redes Neuronales Convolucionales que sea capaz de distinguir entre imágenes pulmonares de rayos X procedentes de pacientes con y sin neumonía. De esta forma, podríamos desarrollar una herramienta informática que automatice con cierta calidad el diagnóstico temprano de estos casos.Dada la gran cantidad de datos que manejamos, hemos utilizado una solución basada en Apache Spark para llevar a cabo el entrenamiento de la red neuronal. De esta forma, el proceso se realiza de forma óptima.  Preparación del entorno de trabajo Instalar JAVA (jdk8)<jupyter_code># Instalamos jdk8
!apt-get install openjdk-8-jdk-headless -qq > /dev/null

import os
# Establecemos la ruta del entorno jdk para poder ejecutar Pyspark en Colab
os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-8-openjdk-amd64"
!update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java<jupyter_output>update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java to provide /usr/bin/java (java) in manual mode<jupyter_text>Instalar Analytics Zoo<jupyter_code># Instalamos la última versión de Analytics Zoo
# Mediante esta acción se instalan automáticamente pyspark, bigdl y sus dependencias.
!pip install --pre --upgrade analytics-zoo[ray]
exit()<jupyter_output>Collecting analytics-zoo[ray]
  Downloading analytics_zoo-0.12.0b2022021001-py2.py3-none-manylinux1_x86_64.whl (194.7 MB)
[K     |████████████████████████████████| 194.7 MB 64 kB/s 
[?25hRequirement already satisfied: filelock in /usr/local/lib/python3.7/dist-packages (from analytics-zoo[ray]) (3.4.2)
Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from analytics-zoo[ray]) (21.3)
Collecting bigdl==0.13.1.dev0
  Downloading BigDL-0.13.1.dev0-py2.py3-none-manylinux1_x86_64.whl (114.0 MB)
[K     |████████████████████████████████| 114.0 MB 29 kB/s 
[?25hCollecting conda-pack==0.3.1
  Downloading conda_pack-0.3.1-py2.py3-none-any.whl (27 kB)
Collecting pyspark==2.4.6
  Downloading pyspark-2.4.6.tar.gz (218.4 MB)
[K     |████████████████████████████████| 218.4 MB 59 kB/s 
[?25hRequirement already satisfied: psutil in /usr/local/lib/python3.7/dist-packages (from analytics-zoo[ray]) (5.4.8)
Collecting async-timeout==3.0.1
  Downloading async_timeout-3.[...]<jupyter_text>Importamos Orca<jupyter_code># Importamos las librerías y módulos necesarios
from zoo.orca import init_orca_context, stop_orca_context
from zoo.orca import OrcaContext

# Fijando este parámetro a 'True' se muestra en el terminal del Jupyter Notebook el stdout y el stderr
OrcaContext.log_output = True

# Iniciamos el contexto Orca
init_orca_context(cluster_mode="local", cores=1)

# Obtenemos la sesión de spark iniciada por Orca
spark = OrcaContext.get_spark_session()<jupyter_output>Initializing orca context
Current pyspark location is : /usr/local/lib/python3.7/dist-packages/pyspark/__init__.py
Start to getOrCreate SparkContext
pyspark_submit_args is:  --driver-class-path /usr/local/lib/python3.7/dist-packages/zoo/share/lib/analytics-zoo-bigdl_0.13.1-SNAPSHOT-spark_2.4.6-0.12.0-SNAPSHOT-jar-with-dependencies.jar:/usr/local/lib/python3.7/dist-packages/bigdl/share/lib/bigdl-0.13.1-SNAPSHOT-jar-with-dependencies.jar pyspark-shell 
2022-02-10 11:26:07 WARN  NativeCodeLoader:62 - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable<jupyter_text>Importamos otras librerías y módulos necesarios<jupyter_code>import os
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator<jupyter_output><empty_output><jupyter_text>Comprobamos la versión instalada de TensorFlow y Keras<jupyter_code>tf.__version__
keras.__version__<jupyter_output><empty_output><jupyter_text>Importación de los datos y creación del modelo Definimos tres funciones que serán llamadas posteriormente en el modelo, cuya finalidad es cargar los datos.Cada una de ellas se encarga de cargar uno de los subconjuntos utilizados en el desarrollo del modelo (entrenamiento, validación y evaluación).<jupyter_code># Ruta al directorio base que contiene todas las imágenes de rayos X a utilizar
basepath = "/content/drive/MyDrive/MBC/Big_Data_Engineering/Big_Data_Final_Project/chest_xray"

# Función para cargar los datos de entrenamiento
def train_data_creator(config, batch_size):
    # Creamos una instancia de ImageDataGenerator que reescala los colores 
    # (en formato RGB) de los píxeles de 0 a 1 en lugar de la codificación 
    # original de 0 a 255, y voltea algunas imágenes horizontalmente al azar
    # para aumentar la diversidad de los datos y que el modelo no pierda
    # precisión por la orientación de la persona a la hora de tomar la radiografía.
    # Esto último solo es necesario en los datos de entrenamiento, por lo que
    # no lo repetimos en los datos de validación y evaluación
    datagen_train = ImageDataGenerator(rescale=1./255, horizontal_flip=True)

    # Usamos la instancia de ImageDataGenerator para cargar los datos del directorio
    # especificado con el tamaño, modo de color y tamaño de 'batch' requerido
    training_set = datagen_train.flow_from_directory(os.path.join(basepath, "train"),
                                              target_size=(150, 150),
                                              color_mode="grayscale",
                                              batch_size=batch_size)

    return training_set

# Función para cargar los datos de validación
def val_data_creator(config, batch_size):
    # Creamos una instancia de ImageDataGenerator que reescala los colores 
    # (en formato RGB) de los píxeles de 0 a 1 en lugar de la codificación 
    # original de 0 a 255.
    datagen_val = ImageDataGenerator(rescale=1./255)

    # Usamos la instancia de ImageDataGenerator para cargar los datos del directorio
    # especificado con el tamaño, modo de color y tamaño de 'batch' requerido
    val_set = datagen_val.flow_from_directory(os.path.join(basepath, "val"),
                                          target_size=(150, 150),
                                          color_mode="grayscale",
                                          batch_size=batch_size)
    return val_set

# Función para cargar los datos de evaluación
def test_data_creator(config, batch_size):
    # Creamos una instancia de ImageDataGenerator que reescala los colores 
    # (en formato RGB) de los píxeles de 0 a 1 en lugar de la codificación 
    # original de 0 a 255.
    datagen_test = ImageDataGenerator(rescale=1./255)

    # Usamos la instancia de ImageDataGenerator para cargar los datos del directorio
    # especificado con el tamaño, modo de color y tamaño de 'batch' requerido
    test_set = datagen_test.flow_from_directory(os.path.join(basepath, "test"),
                                          target_size=(150, 150),
                                          color_mode="grayscale",
                                          batch_size=batch_size)
    return test_set<jupyter_output><empty_output><jupyter_text>Definimos el modelo que vamos a utilizar<jupyter_code># Función en la que se define el modelo de red neuronal
def model_creator(config):
    # Utilizamos el modo Secuencial de keras para la construcción de la red
    model = tf.keras.Sequential()

    # Creamos 5 capas de 32 neuronas convolucionales, definiendo la función de
    # activación como 'relu' y el tamaño de los datos de entrada en la primera de todas
    model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), input_shape=(150, 150, 1), activation="relu"))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation="relu"))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation="relu"))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation="relu"))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation="relu"))
    # Creamos una capa de MaxPooling para reducir la dimensionalidad de la imagen y
    # así reducir el coste computacional y minimizar la posibilidad de 'overfitting'
    model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
    # Creamos una capa Flatten para convertir la salida bidimensional de la capa
    # anterior en una entrada unidimensional de la siguiente
    model.add(tf.keras.layers.Flatten())
    # Creamos una capa de 10 neuronas completamente conectadas
    model.add(tf.keras.layers.Dense(10, activation="relu"))
    # Creamos una capa de 2 neuronas como salida, con función de activación 'softmax'
    # para realizar una clasificación binaria
    model.add(tf.keras.layers.Dense(2, activation="softmax"))

    # Compilamos el modelo con 'adam' como optimizador, 'categorical_crossentropy'
    # como función de pérdida y obteniendo métricas de 'accuracy', 'precision', 
    # 'recall' y 'area under the curve' (AUC)
    model.compile(optimizer="Adam", 
                  loss="categorical_crossentropy", 
                  metrics=["accuracy",
                            keras.metrics.Precision(name='precision'), 
                            keras.metrics.Recall(name='recall'), 
                            keras.metrics.AUC(name='prc', curve='PR') # precision-recall curve
                           ])
    
    return model<jupyter_output><empty_output><jupyter_text>Creamos una instancia del 'Estimator' proporcionado por Orca (Analytics Zoo) a partir del modelo previamente definido<jupyter_code>from zoo.orca.learn.tf2 import Estimator

est = Estimator.from_keras(model_creator=model_creator, workers_per_node=1)<jupyter_output>[2m[36m(pid=5224)[0m WARNING:tensorflow:From /usr/local/lib/python3.7/dist-packages/zoo/orca/learn/tf2/tf_runner.py:317: _CollectiveAllReduceStrategyExperimental.__init__ (from tensorflow.python.distribute.collective_all_reduce_strategy) is deprecated and will be removed in a future version.
[2m[36m(pid=5224)[0m Instructions for updating:
[2m[36m(pid=5224)[0m use distribute.MultiWorkerMirroredStrategy instead
[2m[36m(pid=5224)[0m 2022-02-10 12:30:48.294105: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected<jupyter_text>Entrenamiento del modelo Definimos 3 épocas y 32 como tamaño de 'batch' para entrenar el Estimador, llamando a las funciones que cargan los datos<jupyter_code>max_epoch = 3
batch_size = 32

stats = est.fit(train_data_creator,
                epochs=max_epoch,
                batch_size=batch_size,
                validation_data=val_data_creator)<jupyter_output>[2m[36m(pid=5224)[0m Found 5043 images belonging to 2 classes.
[2m[36m(pid=5224)[0m Found 10 images belonging to 2 classes.<jupyter_text>Evaluación del modelo Una vez el Estimador ha entrenado correctamente, lo evaluamos cargando los datos de evaluación llamando a su función correspondiente<jupyter_code>stats = est.evaluate(test_data_creator, 
                     batch_size = batch_size)

print(stats)<jupyter_output>[2m[36m(pid=5224)[0m Found 586 images belonging to 2 classes.<jupyter_text>Mejoramos la visualización de las métricas obtenidas tras la evaluación<jupyter_code>for stat, value in stats.items():
  print (stat, "\t", value, sep = "")<jupyter_output>validation_loss	0.7188912630081177
validation_accuracy	0.7798634767532349
validation_precision	0.7798634767532349
validation_recall	0.7798634767532349
validation_prc	0.8432269096374512<jupyter_text>Conseguimos finalmente un 'accuracy' del 78%, lo que nos da una visión global de la calidad de la clasificación.De forma más específica, los resultados de 'precision' y 'recall' nos muestran que se producen el mismo número de errores en la clasificación de falsos negativos como de falsos positivos.Por último, el resultado de 'Precision-Recall curve' es de 0'84, un valor superior al umbral de 0'5 para considerar válido el modelo.Consideramos que este resultado es suficientemente alto para utilizar el modelo como una primera aproximación al diagnóstico de neumonía, pero siempre con la supervisión de un profesional.  Guardado del modelo Guardamos el modelo entrenado para poder usarlo en el futuro<jupyter_code>model_path = "/content/drive/MyDrive/MBC/Big_Data_Engineering/Big_Data_Final_Project/"
est.save(os.path.join(model_path, "big_data_final_model_1"))
# est.shutdown()<jupyter_output><empty_output><jupyter_text>Comentarios finales Como hemos visto a lo largo del código, hemos utilizado datos en grandes cantidades para generar un modelo que pueda predecir con relativamente alta precisión (78%) si una radiografía corresponde a un paciente con neumonía o no.Con la idea de explorar más opciones, nos planteamos realizar más transformaciones a las imágenes de entrenamiento, como se muestra en el código a continuación, con el fin de introducir más variabilidad en el modelo. Sin embargo, las métricas de los resultados (precisión del 72%) no mejoraban las del modelo final expuesto. Además, consideramos que tampoco se genera tanta variabilidad en el entorno en el que se consiguen las imágenes, ya que la toma de radiografías de pecho es un proceso bastante estandarizado y no suele tener variaciones de brillo o ángulo.<jupyter_code>#datagen_train = ImageDataGenerator(rescale=1./255, 
#                                   horizontal_flip=True,
#                                   rotation_range=15,
#                                   brightness_range=[0.5,1.0],
#                                   zoom_range=[0.9,1.0])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0847206546275395, 'fraction_numerical': 0.03343679458239278, 'mean_word_length': 3.9936597393448396, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 50, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 27, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '420975', 'license': 'MIT', 'n_tokens_mistral': 5020, 'n_tokens_neox': 4627, 'n_words': 1364, 'path': 'BigData_FinalProject.ipynb', 'repo_name': 'Alvaro-UPM/Big-Data-Final-Project', 'size': '14219'}