Prevención de la ceguera ocular mediante la predicción de las etapas de la retinopatía diabética

Preventing blindness by predicting stages of diabetic retinopathy

Introducción

La retinopatía diabética es una afección ocular que provoca cambios en los vasos sanguíneos de la retina. Si no se trata, puede causar pérdida de visión. Por lo tanto, es crucial detectar las etapas de la retinopatía diabética para prevenir la ceguera ocular. Este estudio de caso trata sobre la detección de la ceguera ocular a partir de los síntomas de la gravedad de la retinopatía diabética para evitar que la persona sufra ceguera. Aquí, se han recopilado datos de áreas rurales por parte de diversos expertos clínicos capacitados que utilizaron cámaras de fondo de ojo (cámaras que fotografían la parte posterior del ojo). Estas fotos se tomaron en diversas condiciones de imagen. En 2019, Kaggle llevó a cabo una competencia (APTOS 2019 Blindness Detection) para detectar las etapas de la retinopatía diabética; nuestros datos se han obtenido de la misma competencia de Kaggle. La detección temprana de esta retinopatía diabética puede ayudar a acelerar el tratamiento y reducir significativamente el riesgo de pérdida de visión.

La intervención manual de expertos clínicos capacitados requiere tiempo y esfuerzo, especialmente en países subdesarrollados. Por lo tanto, el objetivo principal de este estudio de caso es utilizar tecnología eficiente para detectar la gravedad de la afección y prevenir la ceguera. Implementamos técnicas de aprendizaje profundo para obtener resultados efectivos en la clasificación de la gravedad de la afección.

Objetivos de aprendizaje

  • Comprensión de la retinopatía diabética: Aprender sobre la afección ocular y su impacto en la visión, enfatizando la importancia de la detección temprana.
  • Fundamentos del aprendizaje profundo: Explorar los conceptos básicos del aprendizaje profundo y su relevancia en el diagnóstico de la retinopatía diabética.
  • Preprocesamiento y aumento de datos: Comprender cómo preparar y mejorar de manera efectiva el conjunto de datos para entrenar modelos de aprendizaje profundo.
  • Selección y evaluación de modelos: Aprender a elegir y evaluar el rendimiento de los modelos de aprendizaje profundo para la clasificación de la gravedad.
  • Implementación práctica: Descubrir la implementación del mejor modelo utilizando Flask para predicciones en el mundo real.

Este artículo se publicó como parte del Data Science Blogathon.

Problema de negocio

En este caso, la gravedad de la afección de una persona se clasifica en cinco categorías, es decir, una clasificación de múltiples clases, ya que una persona puede ser reconocida con solo uno de los niveles de gravedad.

Restricciones comerciales

La precisión y la interpretabilidad son muy importantes en el campo de la medicina. Debido a que las predicciones incorrectas pueden llevar a la ignorancia y poner en peligro la vida de una persona, no tenemos restricciones estrictas de latencia, pero debemos ser precisos en los resultados.

Descripción del conjunto de datos

El conjunto de datos incluye 3,662 imágenes etiquetadas de la retina de pacientes clínicos, en las cuales expertos clínicos capacitados categorizan cada imagen en términos de la gravedad de la retinopatía diabética, como se muestra a continuación.

0 — Sin retinopatía diabética,

1 — Leve,

2 — Moderada,

3 — Grave,

4 — Retinopatía diabética proliferativa.

La tabla anterior indica que nuestras imágenes del conjunto de datos se identifican con una de las etapas de la retinopatía diabética.

Métrica de rendimiento

Utilizamos el Kappa ponderado cuadrático y la matriz de confusión como métricas de evaluación en nuestra clasificación de múltiples clases.

El Kappa mide la concordancia (similitud) entre las etiquetas reales y las etiquetas predichas. Un puntaje de Kappa de 1.0 indica que las predicciones y las etiquetas reales son iguales, un puntaje de Kappa de -1 indica que las predicciones están muy alejadas de las etiquetas reales. Nuestro objetivo con esta métrica es obtener un puntaje de Kappa superior a 0.6

La métrica de Kappa juega un papel crucial en el diagnóstico médico, ya que, en el caso de un puntaje de -1, indica qué tan similares son los dos evaluadores (evaluadores predichos y reales) y penaliza las discrepancias. Por ejemplo, en nuestro caso, si nuestra etiqueta predicha es 0 y la etiqueta real es 1, esto plantea un problema grave para el paciente, ya que este caso se ignorará y no se recomendará ningún diagnóstico adicional para el paciente.

Como sabemos, la matriz de confusión se utiliza para evaluar el rendimiento de un modelo de clasificación. La matriz compara los valores objetivo reales con los predichos por nuestro modelo. Esto nos brinda una visión holística de qué tan bien funciona nuestro modelo de clasificación y qué tipos de errores comete.

Análisis Exploratorio de Datos y Preprocesamiento

Vamos a verificar la distribución de nuestros datos mediante la creación de un gráfico de barras.

v=df['diagnosis'].value_counts().plot(kind='bar')

A partir del gráfico anterior, podemos deducir que nuestros datos están claramente desequilibrados. Necesitamos asegurarnos de equilibrar nuestros datos para evitar obtener resultados inexactos.

Podemos aplicar pesos de clase para mantener uniformidad en el conjunto de datos y obtener una distribución uniforme en los datos.

Nuestro conjunto de datos de entrenamiento contiene solo 3,662 imágenes, por lo que el conjunto de datos proporcionado por Kaggle es muy pequeño. Es deseable sobreajustar debido al entrenamiento en el pequeño conjunto de datos, por lo que aquí el preprocesamiento juega un papel crucial en un mejor rendimiento al aumentar nuestros conjuntos de datos. Por lo tanto, recurrimos a la ampliación de datos para mejorar nuestros conjuntos de datos. Antes de la ampliación de datos, debemos verificar las condiciones de las imágenes, ya que los datos se han recopilado de diversas fuentes, como si las imágenes son muy oscuras, tienen un fondo negro adicional y son imágenes de diferentes tamaños. Por lo tanto, debemos aplicar técnicas de suavizado a las imágenes para mantener la uniformidad en la calidad de la imagen mediante el recorte de fondos negros adicionales, el redimensionamiento de las imágenes a un tamaño de imagen típico, etc.

A partir de lo anterior, podemos observar que nuestro conjunto de datos contiene imágenes de diferentes tamaños con recorte horizontal, recorte vertical y regiones negras adicionales.

Aplicamos las siguientes técnicas de suavizado para obtener todas las imágenes con una calidad uniforme.

Función de Recorte

→ Función de recorte: se utiliza para eliminar partes oscuras adicionales alrededor de la imagen.

def crop(img,tol=7):
 # aquí tol es la tolerancia  
  '''
  esta función de recorte se utiliza para eliminar partes oscuras alrededor de la imagen
  '''

  if img.ndim==2:
# este bucle se utiliza para recortar imágenes GRIS
    mask=img>tol
    return img[np.ix_(mask.any(1),mask.any(0))]
  elif img.ndim==3:
# este bucle se utiliza para recortar imágenes en color    
    grayimg=cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    mask=grayimg>tol
    shap=img[:,:,0][np.ix_(mask.any(1),mask.any(0))].shape[0]
    if shap==0:
# la imagen es demasiado oscura, por lo que recortamos todo   
      return img
    else:
      img0=img[:,:,0][np.ix_(mask.any(1),mask.any(0))]
      img1=img[:,:,1][np.ix_(mask.any(1),mask.any(0))]
      img2=img[:,:,2][np.ix_(mask.any(1),mask.any(0))]
      img=np.stack([img0,img1,img2],axis=-1)
    return img #import csv

La siguiente figura indica que, después de aplicar la función, obtenemos imágenes recortando las partes oscuras alrededor de ellas.

→ La función de recorte circular recorta una imagen de forma circular tomando como referencia el centro.

def circlecrop(img):
  '''
  utilizado para recortar la imagen de forma circular desde el centro de la imagen
  '''

  h,w,d= img.shape
  x = int(w/2)
  y = int(h/2)
  r = np.amin((x,y))
  circle_img = np.zeros((h,w), np.uint8)
  cv2.circle(circle_img, (x,y), int(r), 1, thickness=-1)
  img = cv2.bitwise_and(img, img, mask=circle_img) 
  return img

La siguiente figura se obtiene después de aplicar la función de recorte circular

Función de Ben Graham

→ Función de Ben Graham: esta función se aplica para mejorar el brillo de la imagen

def ben(img,sigmaX=10):
  '''
  Método de Ben Graham para mejorar las condiciones de iluminación.

  '''
  image=cv2.addWeighted( img,4, cv2.GaussianBlur( img , (0,0) , sigmaX) ,-4 ,128)
  return image

La figura de abajo indica que, después de aplicar la función de Ben Graham, mejoramos la condición de iluminación de la imagen.

Veamos las imágenes de retina preprocesadas después de aplicar las funciones anteriores.

Dado que nuestro conjunto de datos es muy pequeño, podemos tener problemas de sobreajuste. Para superar este escenario, necesitamos aumentar nuestros datos de entrenamiento. Aumente los datos utilizando técnicas de aumento como volteo horizontal, volteo vertical, rotación de imágenes, zoom y ajuste de brillo.

from keras_preprocessing.image import ImageDataGenerator

datagen=ImageDataGenerator(horizontal_flip=True,vertical_flip=True,rotation_range=360,
                           brightness_range=[0.5, 1],
                           zoom_range = 0.2,rescale=1./255.,validation_split=0.25)
validation_datagen = ImageDataGenerator(rescale = 1./255)

train_generator=datagen.flow_from_dataframe(
dataframe=df,
directory="prep",
x_col="add",
y_col="diagnosis",
subset="training",
batch_size=12,
seed=42,
shuffle=True,
class_mode="categorical",
target_size=(256, 256))
     

Dado que hemos utilizado una división de validación del 0,25, obtenemos 2.747 imágenes de entrenamiento y 915 imágenes de validación.

Aquí, cada imagen se replica cinco veces, ya que hemos utilizado cinco técnicas: volteo horizontal, volteo vertical, rango de rotación, rango de brillo y rango de zoom.

Modelo de Aprendizaje Profundo

En primer lugar, construimos un modelo de referencia con una arquitectura CNN simple.

inp=Input(shape=(256,256,3))
x=Conv2D(32,(3,3),activation='relu')(inp)
x=MaxPooling2D(pool_size=(2, 2))(x)
x=Dropout(0.5)(x)
x=Flatten()(x)
x=BatchNormalization()(x)
x=Dense(5, activation='softmax')(x)

Para el modelo anterior, obtenemos una puntuación kappa de 0,554, que no es aceptable para predecir la etapa de la condición.

Recurremos al aprendizaje por transferencia para utilizar el modelo preentrenado y obtener una alta puntuación kappa.

VGG-16

model_vgg16= VGG16(weights='imagenet', include_top=False,input_shape=(256,256, 3))
x=GlobalAveragePooling2D()(model_vgg16.layers[-1].output)
x=Dropout(0.5)(x)
x=Dense(5, activation='softmax')(x)

→ Puntuación Cohen Kappa de entrenamiento: 0,913

→ Puntuación de precisión de entrenamiento: 0,817

Con el modelo anterior, obtenemos una puntuación kappa de 0,913

DENSENET

modeldense=DenseNet121(weights='imagenet', include_top=False,input_shape=(256,256, 3))
x=GlobalAveragePooling2D()(modeldense.layers[-1].output)
x=Dropout(0.5)(x)
x=Dense(5, activation='softmax')(x)

→ Puntuación Cohen Kappa de entrenamiento: 0,933

→ Puntuación de precisión de entrenamiento: 0,884

Con el modelo anterior, obtenemos una puntuación kappa de 0,933

RESNET

modelres152=ResNet152(weights='imagenet', include_top=False,input_shape=(256,256, 3))
x=GlobalAveragePooling2D()(modelres152.layers[-1].output)
x=Dropout(0.5)(x)
x=Dense(5, activation='softmax')(x)

→ Puntuación Cohen Kappa de entrenamiento: 0,910

→ Puntuación de precisión de entrenamiento: 0,844

Con el modelo anterior, obtenemos una puntuación kappa de 0,91

EFFICIENTNET

Después de implementar varios modelos eficientes como EEfficientNetB0, B3, B4 y B7, podemos obtener mejores resultados utilizando EfficientNetB7.

modeleffB7=EfficientNetB7(weights='imagenet', include_top=False,input_shape=(256,256, 3))
x=GlobalAveragePooling2D()(modeleffB7.layers[-1].output)
x=Dropout(0.5)(x)
x=Flatten()(x)
x=Dense(5, activation='softmax')(x)

→Puntuación Kappa de entrenamiento: 0.877

→Puntuación de precisión de entrenamiento: 0.838

A partir del modelo anterior, obtenemos una puntuación kappa de 0.877

XCEPTION

modelxcep=Xception(weights='imagenet', include_top=False,input_shape=(256,256, 3))
x=GlobalAveragePooling2D()(modelxcep.layers[-1].output)
x=Dropout(0.5)(x)
x=Flatten()(x)
x=Dense(5, activation='softmax')(x)

→Puntuación Kappa de entrenamiento: 0.925

→Puntuación de precisión de entrenamiento: 0.854

A partir del modelo anterior, obtenemos una puntuación kappa de 0.925

A partir del modelo anterior, observamos que el modelo Denset obtiene una mejor puntuación Kappa. Por lo tanto, elegimos nuestro modelo Denset como el mejor y nos dirigimos a la predicción de etapas.

Predicción utilizando nuestro mejor modelo

Prediciendo las etapas usando nuestro mejor modelo:

X='/content/00a8624548a9.png'
img = cv2.imread(X)
img=crop(img)
img = cv2.resize(img,(256,256),interpolation=cv2.INTER_AREA)
img=circlecrop(img)
img=ben(img)
img = np.reshape(img,[1,256,256,3])
cd = ImageDataGenerator(horizontal_flip=True,vertical_flip=True,
                          rotation_range=360,brightness_range=[0.5, 1],
                           zoom_range = 0.2,rescale=1./255)
cg = cd.flow(img,batch_size=1)
tp = model.predict(cg)
op=np.argmax(tp)
if op==0:
  matter="Etapa 0 - No Retinopatía Diabética"
elif op==1:
  matter="Etapa 1 - Leve"
elif op==2:
  matter="Etapa 2 - Moderada"
elif op==3:
  matter="Etapa 3 - Severa"
elif op==4:
  matter="Etapa 4 - Retinopatía Diabética Proliferativa"
print(matter)

A partir de la imagen anterior, podemos observar que nuestro mejor modelo predice las etapas de la retinopatía diabética.

Implementación de nuestro modelo utilizando Flask

He utilizado Flask para implementar mi modelo para poder predecir la etapa de la retinopatía diabética de nuestra imagen de retina cargada. A continuación se muestra el video de las instancias en ejecución de mi modelo implementado.

Conclusión

En conclusión, este blog ha mostrado el poder transformador del aprendizaje profundo en la detección de la retinopatía diabética y la prevención de la pérdida de visión. Con la detección temprana y la clasificación precisa de la gravedad, la inteligencia artificial puede mejorar significativamente los resultados de los pacientes. La aplicabilidad del mundo real de estas técnicas a través de la implementación del modelo utilizando Flask destaca su practicidad en entornos de atención médica.

La investigación continua en técnicas de aumento y refinamiento del modelo mejorará aún más las capacidades de diagnóstico. Al aprovechar el potencial de la inteligencia artificial, podemos revolucionar el diagnóstico médico y allanar el camino hacia un futuro más saludable.

  • Los modelos de aprendizaje profundo, como VGG-16, DENSENET, RESNET, EFFICIENTNET y XCEPTION, han clasificado eficazmente la gravedad de la retinopatía diabética.
  • El modelo con mejor rendimiento, DENSENET, logró altas puntuaciones Kappa, demostrando su capacidad para predicciones precisas.
  • El preprocesamiento y el aumento de datos son vitales para mejorar el rendimiento y la generalización del modelo.
  • La implementación de Flask muestra la aplicabilidad práctica del aprendizaje profundo en escenarios del mundo real, facilitando un diagnóstico y tratamiento eficientes.
  • La investigación continua en técnicas de aumento y refinamiento del modelo tiene el potencial de mejorar aún más la precisión del diagnóstico y avanzar en el diagnóstico médico utilizando inteligencia artificial.

Trabajo futuro

  • Podemos implementar más técnicas de aumento.
  • Podemos probar varias capas convolucionales en nuestros modelos.
  • Necesitamos obtener más imágenes de retina para el entrenamiento.

Preguntas frecuentes

Referencias

  • https://github.com/btgraham/SparseConvNet/blob/kaggle_Diabetic_Retinopathy_competition/competitionreport.pdf
  • https://arxiv.org/abs/1905.11946
  • https://arxiv.org/abs/0704.1028
  • https://www.kaggle.com/xhlulu/aptos-2019-densenet-keras-starter
  • https://www.kaggle.com/c/aptos2019-blindness-detection/discussion/108065

Los medios mostrados en este artículo no son propiedad de Analytics Vidhya y se utilizan a discreción del autor.