Una guía completa para el análisis de resonancia magnética a través de modelos de aprendizaje profundo en PyTorch
Antes que nada, me gustaría presentarme. Mi nombre es Carla Pitarch y soy candidata a doctorado en IA. Mi investigación se centra en el desarrollo de un sistema automatizado de clasificación de grados de tumores cerebrales mediante la extracción de información de imágenes de resonancia magnética (MRI) utilizando modelos de aprendizaje profundo (DL), particularmente redes neuronales convolucionales (CNN).
Al comienzo de mi viaje de doctorado, sumergirme en los datos de resonancia magnética y la DL era un mundo completamente nuevo. Los pasos iniciales para ejecutar modelos en este ámbito no fueron tan sencillos como se esperaba. A pesar de pasar algún tiempo investigando en este dominio, encontré una falta de repositorios completos que guíen el inicio tanto en MRI como en DL. Por lo tanto, decidí compartir algunos de los conocimientos que he adquirido durante este período, con la esperanza de que su viaje sea un poco más sencillo.
Embarcarse en tareas de visión por computadora (CV) a través de DL a menudo implica el uso de conjuntos de datos de imágenes públicas estándar, como ImageNet
, caracterizado por imágenes naturales RGB de 3 canales. Los modelos de PyTorch están preparados para estas especificaciones y se espera que las imágenes de entrada estén en este formato. Sin embargo, cuando nuestros datos de imágenes provienen de un dominio distinto, como el campo médico, y difieren tanto en formato como en características de estos conjuntos de datos de imágenes naturales, presenta desafíos. Esta publicación profundiza en este tema, enfatizando dos pasos preparatorios cruciales antes de la implementación del modelo: alinear los datos con los requisitos del modelo y preparar el modelo para procesar nuestros datos de manera efectiva.
Comencemos con una breve descripción de los aspectos fundamentales de las CNN y las resonancias magnéticas.
Redes neuronales convolucionales
En esta sección, profundizamos en el ámbito de las CNN, asumiendo que los lectores tienen una comprensión fundamental de DL. Las CNN son las arquitecturas estándar de oro en CV, y se especializan en el procesamiento de datos de imágenes de entrada 2D y 3D. Nuestro enfoque en esta publicación se centrará en el procesamiento de datos de imágenes 2D.
La clasificación de imágenes, asociando clases o etiquetas de salida con imágenes de entrada, es una tarea central en las CNN. La arquitectura pionera LeNet5 introducida por LeCun et al.¹ en 1989 sentó las bases para las CNN. Esta arquitectura se puede resumir de la siguiente manera:
Las arquitecturas CNN 2D funcionan recibiendo píxeles de la imagen como entrada, esperando que la imagen sea un tensor con forma. Height x Width x Channels
. Las imágenes en color normalmente constan de 3 canales: rojo, verde y azul (RGB), mientras que las imágenes en escala de grises constan de un solo canal.
Una operación fundamental en las CNN es circunvoluciónejecutado aplicando un conjunto de filtros o granos en todas las áreas de los datos de entrada. La siguiente figura muestra un ejemplo de cómo funciona la convolución en un contexto 2D.
El proceso implica deslizar el filtro por la imagen hacia la derecha y calcular la suma ponderada para obtener un mapa de características convolucional. La salida representará si se reconoce un patrón visual específico, por ejemplo un borde, en esa ubicación en la imagen de entrada. Después de cada capa convolucional, una función de activación introduce no linealidad. Las opciones populares incluyen: ReLU (Unidad lineal rectificada), Leaky ReLu, Sigmoid, Tanh y Softmax. Para obtener más detalles sobre cada función de activación, esta publicación proporciona explicaciones claras Funciones de activación en redes neuronales | de SAGAR SHARMA | Hacia la ciencia de datos.
Diferentes tipos de capas contribuyen a la construcción de las CNN, y cada una desempeña un papel distinto en la definición de la funcionalidad de la red. Además de las capas convolucionales, otras capas destacadas utilizadas en las CNN incluyen:
- Capas de agrupacióncomo la agrupación máxima o la agrupación promedio, reducen de manera efectiva las dimensiones del mapa de características y al mismo tiempo preservan la información esencial.
- Capas de abandono se utilizan para evitar el sobreajuste desactivando aleatoriamente una fracción de neuronas durante el entrenamiento, mejorando así la capacidad de generalización de la red.
- Capas de normalización por lotes centrarse en estandarizar las entradas para cada capa, lo que acelera el entrenamiento de la red.
- Capas completamente conectadas (FC) establecer conexiones entre todas las neuronas de una capa y todas las activaciones de la capa anterior, integrando características aprendidas para facilitar las clasificaciones finales.
Las CNN aprenden a identificar patrones jerárquicamente. Las capas iniciales se centran en características de bajo nivel, pasando progresivamente a características altamente abstractas en capas más profundas. Al llegar a la capa FC, el softmax La función de activación estima las probabilidades de clase para la entrada de la imagen.
Más allá del inicio de LeNet, arquitecturas CNN notables como AlexNet², GoogLeNet³, VGGNet⁴, ResNet⁵ y la más reciente Transformer⁶ han contribuido significativamente al ámbito de DL.
Descripción general de imágenes naturales
La exploración de imágenes 2D naturales proporciona una comprensión fundamental de los datos de las imágenes. Para empezar, profundizaremos en algunos ejemplos.
Para nuestro primer ejemplo seleccionaremos una imagen del conocido MNIST
conjunto de datos.
Esta forma de imagen es (28,28)
, que representa una imagen en escala de grises con un solo canal. Entonces, la imagen de entrada para una red neuronal sería (28*28*1)
.
Ahora exploremos una imagen del ImageNet
conjunto de datos. Puede acceder a este conjunto de datos directamente desde el sitio web de ImageNet. ImageNet (image-net.org) o explorar un subconjunto disponible en Kaggle Desafío de localización de objetos ImageNet | Kaggle.
Podemos descomponer esta imagen en sus canales RGB:
Dado que la forma de esta imagen es (500, 402, 3)
la imagen de entrada de una red neuronal se representaría como (500*402*3)
.
Imagen de resonancia magnética
La resonancia magnética es la prueba más utilizada en neurología y neurocirugía y ofrece un método no invasivo que proporciona un buen contraste de los tejidos blandos⁷. Más allá de visualizar detalles estructurales, las imágenes por resonancia magnética profundizan más y brindan información valiosa sobre los aspectos estructurales y funcionales del cerebro.
Las resonancias magnéticas constituyen volúmenes 3D que permiten la visualización en los tres planos anatómicos: axial, coronal y sagital. Estos volúmenes están compuestos por cubos 3D conocidos como vóxeles, a diferencia de las imágenes 2D estándar, que se componen de cuadrados 2D llamados píxeles. Si bien los volúmenes 3D ofrecen datos completos, también se pueden descomponer en cortes 2D.
Para el diagnóstico normalmente se utilizan diversas modalidades o secuencias de resonancia magnética, como T1, T1 con realce de contraste con gadolinio (T1ce), T2 y FLAIR (Fluid Attenuated Inversion Recovery). Estas secuencias permiten la diferenciación de compartimentos tumorales al ofrecer distintas intensidades de señal que corresponden a regiones o tejidos específicos. A continuación se muestra una ilustración que presenta estas cuatro secuencias de un solo paciente diagnosticado con glioblastoma, conocida como la forma más agresiva entre los gliomas, los tumores cerebrales primarios más prevalentes.
Desafío de segmentación de tumores cerebrales (BraTS) puso a disposición uno de los conjuntos de datos de resonancias magnéticas cerebrales multimodales más extensos de pacientes con glioma que abarcan desde 2012 hasta 2023. El objetivo principal de la competencia BraTS es evaluar las metodologías de última generación para segmentar tumores cerebrales en técnicas multimodales. Exploraciones por resonancia magnética, aunque con el tiempo se han agregado tareas adicionales.
El conjunto de datos BraTS proporciona información clínica sobre los tumores, incluida una etiqueta binaria que indica el grado del tumor (grado bajo o alto). Los escaneos BraTS están disponibles como archivos NIfTI y describen las modalidades T1, T1ce, T2 y Flair. Las exploraciones se proporcionan después de algunos pasos de preprocesamiento, incluido el registro conjunto en la misma plantilla anatómica, la interpolación a una resolución isotrópica uniforme (1 mm³) y la extracción del cráneo.
En esta publicación usaremos el conjunto de datos de 2020 de Kaggle. Conjunto de datos BraTS2020 (Capacitación + Validación) clasificar las resonancias magnéticas de glioma en grado bajo o alto.
Las siguientes imágenes muestran ejemplos de gliomas de bajo y alto grado:
El repositorio de Kaggle consta de 369 directorios, cada uno de los cuales representa un paciente y contiene los datos de imagen correspondientes. Además, contiene dos .csv
archivos: nombre_mapping.csv y supervivencia_info.csv. Para nuestro propósito utilizaremos nombre_mapping.csvque vincula los nombres de los pacientes BraTS con el TCGA-LGG y TCGA-GBM conjuntos de datos públicos disponibles en Archivo de imágenes del cáncer. Este archivo no solo facilita el mapeo de nombres sino que también proporciona etiquetas de grado tumoral (LGG-HGG).
Exploremos el contenido de cada carpeta de paciente, tomando Paciente 006 como ejemplo. Dentro de SujetadorTS20_Training_006 En la carpeta encontramos 5 archivos, cada uno correspondiente a una modalidad de resonancia magnética y la máscara de segmentación:
- SujetadorTS20_Training_006_flair.nii
- SujetadorTS20_Training_006_t1.nii
- SujetadorTS20_Training_006_t1ce.nii
- SujetadorTS20_Training_006_t2.nii
- SujetadorTS20_Training_006_seg.nii
Estos archivos están en el .nii
formato, que representa el formato NIfTI, uno de los más frecuentes en neuroimagen.
Para manejar imágenes NIfTI en Python, podemos usar el es babel paquete. A continuación se muestra un ejemplo de la carga de datos. Al utilizar el get_fdata()
método podemos interpretar la imagen como una matriz numerosa.
La forma de la matriz, (240, 240, 155)
, indica un volumen 3D que comprende 240 cortes 2D dentro de las dimensiones xey, y 155 cortes en la dimensión z. Estas dimensiones corresponden a distintas perspectivas anatómicas: la vista axial (plano xy), la vista coronal (plano xz) y la vista sagital (plano yz).
Para hacerlo más simple, solo emplearemos cortes 2D en el plano axial, luego las imágenes resultantes tendrán forma (240, 240)
.
Para cumplir con las especificaciones de los modelos PyTorch, los tensores de entrada deben tener forma (batch_size, num_channels, height, width)
. En nuestros datos de resonancia magnética, cada una de las cuatro modalidades (FLAIR, T1ce, T1, T2) enfatiza características distintas dentro de la imagen, similares a los canales de una imagen. Para alinear nuestro formato de datos con los requisitos de PyTorch, apilaremos estas secuencias como canales logrando un (4, 240, 240)
tensor.
PyTorch proporciona dos utilidades de datos, torch.utils.data.Dataset
y torch.utils.data.DataLoader
, diseñado para iterar sobre conjuntos de datos y cargar datos en lotes. El Dataset
incluye subclases para varios conjuntos de datos estándar como MNIST
, CIFAR
o ImageNet
. Importar estos conjuntos de datos implica cargar la clase respectiva e inicializar el DataLoader
.
Consideremos un ejemplo con el MNIST
conjunto de datos:
Esto nos permite obtener el tensor final con dimensiones. (batch_size=32, num_channels=1, H=28, W=28)
tensor.
Dado que tenemos un conjunto de datos no trivial, la creación de un personalizado Dataset class
es necesario antes de utilizar el DataLoader
. Si bien los pasos detallados para crear este conjunto de datos personalizado no se tratan en esta publicación, se remite a los lectores al tutorial de PyTorch en Conjuntos de datos y cargadores de datos para una orientación integral.
PyTorch es un marco de DL desarrollado por investigadores de inteligencia artificial de Facebook en 2017. torchvision
El paquete contiene conjuntos de datos populares, transformaciones de imágenes y arquitecturas de modelos. En torchvision.models
Podemos encontrar la implementación de algunas arquitecturas DL para diferentes tareas, como clasificación, segmentación o detección de objetos.
Para nuestra aplicación cargaremos el ResNet18
arquitectura.
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=1000, bias=True)
)
En una red neuronal, las capas de entrada y salida están inherentemente vinculadas al problema específico que se aborda. Los modelos PyTorch DL normalmente esperan imágenes RGB de 3 canales como entrada, como podemos observar en la configuración de la capa convolucional inicial. Conv2d(in_channels = 3, out_channels = 64, kernel_size=(7,7), stride=(2,2), padding=(3,3), bias=False)
. Además, la capa final Linear(in_features = 512, out_features = 1000, bias = True)
El tamaño de salida predeterminado es 1000, lo que representa el número de clases en el conjunto de datos de ImageNet.
Antes de entrenar un modelo de clasificación, es esencial ajustar in_channels
y out_features
para alinearse con nuestro conjunto de datos específico. Podemos acceder a la primera capa convolucional a través de resnet.conv1
y actualizar el in_channels
. De manera similar, podemos acceder a la última capa completamente conectada a través deresnet.fc
y modificar el out_features
.
ResNet(
(conv1): Conv2d(4, 64, kernel_size=(7, 7), stride=(1, 1))
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=2, bias=True)
)
Con nuestro modelo y datos preparados para el entrenamiento, podemos proceder a su aplicación práctica.
El siguiente ejemplo ilustra cómo podemos utilizar eficazmente nuestro ResNet18 para clasificar una imagen en calidad baja o alta. Para administrar la dimensión del tamaño del lote en nuestro tensor, simplemente agregaremos una dimensión unitaria (tenga en cuenta que esto normalmente lo maneja el cargador de datos).
tensor(0.5465, device='cuda:0', grad_fn=<NllLossBackward0>)
Y eso nos lleva al final del post. Espero que esto haya sido útil para quienes se aventuran en la intersección de la resonancia magnética y el aprendizaje profundo. Gracias por tomarse el tiempo de leer. Para obtener información más profunda sobre mi investigación, ¡no dude en explorar mi artículo reciente! 🙂
Pitarch, C.; Ribas, V.; Vellido, A. Clasificación de gliomas basada en IA para un diagnóstico confiable: un proceso analítico para mejorar la confiabilidad. Cánceres 2023, 153369. https://doi.org/10.3390/cancers15133369.
A menos que se indique lo contrario, todas las imágenes son del autor.