DETECCIÓN DE NEUMONÍA EN RADIOGRAFÍAS: 4 – MODELOS DE APRENDIZAJE PROFUNDO (I)

Publicado por @JoseMa_deCuenca el March 8, 2020, 9:24 p.m.
En primer lugar decidiremos el diseño de nuestor modelo clasificador, basado en una red neuronal convolucional profunda, considerando que: - Una red más ancha (con mayor número de neuronas en sus capas) tiende a tener demasiados parámetros y a generar sobreajuste. - Una red más profunda es capaz de detectar no solo características, sino también sus combinaciones y agrupaciones (super características). - En redes neuronales, más profunda es mejor que más ancha. Se implementa intentando que cada capa sea más liviana. - Si utilizamos redes convolucionales, el número de parámetros crece de forma cuadrática con el tamaño del kernel. Por ello los filtros de convolución grandes no son tan rentables, salvo que vayan aparejados de una zancada (salto en la aplicación de la ventana o filtro) acorde. - El principio de más profundidad es mejor que más anchura en las redes neuronales se puede aplicar con el tamaño de los filtros convolucionales, aplicando dos capas convolucionales con un núcleo menor se logra una red con menor número total de parámetros, pero con más capacidad de aprender características complejas. La capa convolucional aplicará a todos los datos uno o varios filtros autoconfigurados (se usa un kernel 3x3 para imágenes en escala de grises en vez del kernel 5x5 apropiado para canales RGB). Los pesos y bias (sesgos) asignados durante el entrenamiento detectan las características comunes a toda la imagen. El filtro de convolución reduce la dimensión de datos que entran en la siguiente capa, y sus patrones aprendidos son invariantes a traslaciones (se pueden usar en toda la imagen, a diferencia de una RN convencional completamente conectada debe aprender un filtro para cada región. Además sus patrones se pueden jerarquizar: las primeras filtran patrones sencillos (bordes) y las últimas combinan la información para obtener patrones complejos (formas). Cada filtro se aplica a todas las dimensiones de los datos (p.e. alto, ancho, canales) y su salida es un escalar, por lo que la composición de las salidas de todos los filtros es un vector de tamaño los canales de salida. Con todo, las capas convolucionales no son mejores para detectar características espaciales que las capas totalmente conectadas. Por tanto si una capa convolucional puede aprender cierta característica, una capa completamente conectada también podría aprenderla. Pero como una capa totalmente conectada vincula cada posible entrada con cada posible salida, tiene un número de parámetros mucho mayor que una capa convolucional, que solo conecta una ventana de entrada (definida por el tamaño de su filtro o núcleo) con una ventana de salida única. Por ello las capas convolucionales reducen los costes de proceso en términos de memoria y tiempo. Adicionalmente, al trabajar con menos parámetros entrenables también mejoran la generalización y reducen el riesgo de sobreajuste. Adicionalmente al tamaño de los filtros, para reducir la carga de la red convolucional podemos recurrir a otras estrategias como: - Capas de Dropout (abandono o deserción): que incorporan esta técnica de regularización para reducir el sobreajuste u overfitting de las RN, previniendo coadaptaciones de los datos de entrenamiento. Abandonan (eliminan) aleatoriamente algunas neuronas de la red para restar sensibilidad de la red a los pesos y hacen que generalice mejor. Pueden utilizarse en capas ocultas o visibles, una sola o múltiples veces. Al hacerlo siempre es útil restringir los pesos máximos a 3 ó 5 en el resto de capas (con el argumento kernel_constraint en su definición) e incrementar la velocidad de aprendizaje 1 o 2 órdenes de magnitud. También elevar el momentum hasta 0,9 o 0,99. Aunque no siempre se mejora el rendimiento, y puede requerir más iteraciones. Suele mejorar con redes más grandes. Un valor permisible del 20% es aconsejable. Si es escaso (menos del 10%) no tiene efecto y si es excesivo (más del 50%) la red perder capacidad de aprendizaje. Tabmién debe considerarse que el uso de dropout reduce la precisión en entrenamiento pero la mejora en validación. - Capas de MaxPooling o agrupación máxima, que permiten hacer un submuestreo de los parámetros resultado de las capas convolucionales, tomando únciamente el valor máximo para reducir la dimensión espacial de los datos. También amplían el área de entrada para la siguiente capa, haciendo que se active con patrones de mayor tamaño, aunque con un mapa de características de menor tamaño. La implementación de esta red se realiza con una arquitectura convolucional clásica, repitiendo bloques compuestos de capas convolucionales y una capa muestreo maxpooling varias veces, agregando patrones de cada vez más complejidad, que se recoge al final con un clasificador compuesto por una capa de aplanado o Flatten otra totalmente conectada y la salida. Pero antes de construir cualquier modelo de aprendizaje profundo, en primer lugar debemos adaptar el formato del conjunto de datos para facilitar su tratamiento. Para ello es necesario un redimensionamiento (reshape), y adoptar una clasificación categórica de sus etiquetas, en la que cada clase etiquetada utilizará una única característica o columna. ## PREPARACION FORMATO DEL CONJUNTO DE DATOS PARA RN CONVOLUCIONALES #Formato de imagenes en los conjuntos de carga print("Formato imagenes en conjuntos de carga") print(X_train.shape) print(X_test.shape) print(X_val.shape) # Aseguro las dimensiones del array como imagenes de entrada para el modelo X_train=X_train.reshape(5216,3,150,150) X_test=X_test.reshape(624,3,150,150) X_val=X_val.reshape(16,3,150,150) # Compruebo print("Formato imagenes para entrada del modelo") print(X_train.shape) print(X_test.shape) print(X_val.shape) # Convierto los conjuntos de datos en los array de etiquetas a categoricos # Uso 2 clases (clasificacion binaria) porque el campo diagnostico tiene 2 valores. Si patogeno, 3. from keras.utils.np_utils import to_categorical y_train = to_categorical(y_train, num_classes=2, dtype='int') # Solo tomara enteros y_test = to_categorical(y_test, num_classes=2, dtype='int') y_val = to_categorical(y_val, num_classes=2, dtype='int') print(y_train.shape) print(y_test.shape) print(y_val.shape) Una vez redimensionados los datos, para implementar este diseño vamos a crear un modelo de red convolucional simple que aprende los detalles en bloques, compuestos de 2 capas convolucionales 2D y un MaxPooling para reducir datos. Se repite la estrategia 3 veces incrementando el número de filtros cada vez, en orden binario, con el fin de ir concentrando los detalles de las imágenes. Tras el aprendizaje de los detalles, incluyo una capa de formateador con Flatten, un clasificador completamente conectado Dense, un Dropout y una capa de clasificador sigmoide. En las convoluciones se utiliza la opción de relleno o Padding, incorporando la información adicional necesaria en los bordes del conjunto de datos para que el centro del filtro pueda llegar hasta ellos, de manera que las dimensiones de la salida son iguales a las de la entrada. Salvo en la última convolución donde se usa la opción “valid” para mantener el filtro en valores válidos aún a costa de eliminar algunas columnas a la derecha o las filas inferiores, con el fin de evitar fallos por dimensiones. En la compilación utilizo binary_crossentropy porque es una clasificación de 2 clases excluyentes. Para adaptar este código al modelo con 3 clases se puede usar categorical_crossentropy con clasificación one_hot_encoding. En ese caso, usaremos el campo que discrimina entre no infectados, neumonía bacteriana o neumonía vírica. Resulta un modelo con un elevado número de parámetros (1,25 millones), mucho más reducido que el perceptrón multicapa. A pesar de ello su tiempo de entrenamiento en cada iteración es mucho mayor, ya que es capaz de detectar mayor número de detalles. Para acelerar las pruebas, limito el número de épocas o iteraciones de entrenamiento al máximo. Posteriormente, tras seleccionar los mejores modelos, se usará un algortimo de autotuning con GridSearchCV para su optimización. Esto nos permite reducir el tiempo de las pruebas como mínimo a la mitad. # Carga de librerias y funciones de Keras para modelos RN from keras.models import Sequential from keras.layers import Dense , Activation from keras.layers import Dropout from keras.layers import Flatten from keras.constraints import maxnorm from keras.optimizers import SGD , RMSprop, Adam from keras.layers import Conv2D , BatchNormalization from keras.layers import Input, SeparableConv2D from keras.layers import MaxPooling2D from keras.utils import np_utils from keras import backend as K K.set_image_dim_ordering('th') from sklearn.model_selection import GridSearchCV from keras.wrappers.scikit_learn import KerasClassifier # Habilitamos funciones de llamada interna de keras para monitorizar el proceso durante el entrenamiento # Y para reducir la tasa de aprendizaje ReduceLROnPlateau cuando cerca de minimo from keras.callbacks import ReduceLROnPlateau , ModelCheckpoint lr_reduce = ReduceLROnPlateau(monitor='val_acc', factor=0.1, epsilon=0.0001, patience=1, verbose=1) # Definicion de un modelo base de referencia model_referencia = Sequential() # Fuerzo de nuevo el formato de entrada original pese al procesado anterior model_referencia.add(Conv2D(16, (3, 3), activation='relu', padding="same", input_shape=(3,150,150))) model_referencia.add(Conv2D(16, (3, 3), padding="same", activation='relu')) model_referencia.add(MaxPooling2D(pool_size=(2, 2))) model_referencia.add(Conv2D(32, (3, 3), activation='relu', padding="same")) model_referencia.add(Conv2D(32, (3, 3), padding="same", activation='relu')) model_referencia.add(MaxPooling2D(pool_size=(2, 2))) model_referencia.add(Conv2D(64, (3, 3), dilation_rate=(2, 2), activation='relu', padding="same")) model_referencia.add(Conv2D(64, (3, 3), padding="valid", activation='relu')) model_referencia.add(MaxPooling2D(pool_size=(2, 2))) # Formateo de datos con Flatten model_referencia.add(Flatten()) # Clasificador con capas totalmente conectadas model_referencia.add(Dense(64, activation='relu')) model_referencia.add(Dropout(0.4)) model_referencia.add(Dense(2 , activation='sigmoid')) # Compilacion # model_referencia.compile(loss='binary_crossentropy', optimizer=RMSprop(lr=0.00005), metrics=['accuracy']) model_referencia.compile(loss='binary_crossentropy', optimizer=RMSprop(lr=0.0005), metrics=['accuracy']) # Resumen del modelo print(model_referencia.summary()) ## ENTRENAMIENTO MODELO RN CONVOLUCIONAL DE REFERENCIA # Parametros de entrenamiento batch_size = 256 epochs = 4 # Habilito una ruta y un archivo para guardar los pesos de entrenamiento de la red ## ESPECIFICO PARA ESTA RED filepath="./checkpoints/chest_xray_weights_ref.hdf5" checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max') # Entrenamiento del modelo # Inicia cronometro start_time = time.time() history_referencia = model_referencia.fit(X_train, y_train, validation_data = (X_test , y_test), callbacks=[lr_reduce,checkpoint], epochs=epochs) # Detiene el cronometro end_time = time.time() # Informa cronometro dt = round(end_time - start_time, 2) print("El entrenamiento de este modelo tardó: ", dt, "segundos (", round(dt/60,1) , "minutos)") # Definimos variables para guardar los resultados del modelo de referencia entrenado acc_ref = history_referencia.history['acc'] val_acc_ref = history_referencia.history['val_acc'] loss_ref = history_referencia.history['loss'] val_loss_ref = history_referencia.history['val_loss'] # Representacion de metricas para el modelo de referenia durante el entrenamiento # Precision plt.plot(acc_ref, label='Precis. entrenamiento', color='Red') plt.plot(val_acc_ref, label='Precis. Prueba', color='Green') plt.title('Precision durante entrenamiento y prueba') plt.ylabel('accuracy') plt.xlabel('epoch-1') plt.legend(fontsize=10, loc='upper left') plt.figure() # Perdida plt.plot(loss_ref, label='Pérd. entrenamiento', color='Red') plt.plot(val_loss_ref, label='Pérd. prueba', color='Green') plt.title('Pérdida durante entrenamiento y prueba') plt.ylabel('loss') plt.xlabel('epoch-1') plt.legend(fontsize=10, loc='upper right') plt.figure() # Represento plt.show() from sklearn.metrics import confusion_matrix pred = model_referencia.predict(X_test) pred = np.argmax(pred,axis = 1) y_true = np.argmax(y_test,axis = 1) # Libreria mlxtend para dibujo rapido de la matriz CM = confusion_matrix(y_true, pred) from mlxtend.plotting import plot_confusion_matrix fig, ax = plot_confusion_matrix(conf_mat=CM , figsize=(5, 5)) plt.show() Los códigos completos pueden descargarse el repositorio https://github.com/watershed-lab/pneumo-detector En la siguiente entrega veremos el tipo de redes convolucionales separables, aún más eficiente en según qué casos.