Inteligencia Artificial

Clasificación de imágenes con Deep Learning

En este artículo, utilizaremos PyTorch para probar diferentes modelos para lograr la clasificación de imágenes con Deep Learning del conjunto de datos COIL-100 y comparar su rendimiento.

COIL-100 Dataset

La Biblioteca de Imágenes de la Universidad de Columbia (COIL-100) es un conjunto de datos de imágenes en color de 100 objetos. Los objetos se colocaron en una plataforma giratoria motorizada sobre un fondo negro. La plataforma giratoria se hizo girar 360 grados para variar la postura del objeto con respecto a una cámara en color fija. Se tomaron imágenes de los objetos a intervalos de 5 grados. Esto corresponde a 72 poses por objeto (7.200 imágenes en total). Las imágenes se normalizaron por tamaño.

Preparación de los datos para la clasificación de imágenes con Deep Learning

En primer lugar, importamos las bibliotecas que vamos a utilizar para este proyecto.

In [1]:

import torch
from torch.utils.data import Dataset, DataLoader, random_split

from PIL import Image
import pandas as pd

from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt

import torch.nn as nn
import torch.nn.functional as F

from torchvision.utils import make_grid

Aquí creamos dos variables, una para la ruta de la carpeta de imágenes y otra para la ruta de un fichero .csv que he creado con información sobre el nombre de los ficheros y su clasificación entre las 100 clases existentes.

In [2]:

path = '../input/coil100/coil-100/coil-100/'
csv_path = '../input/namescsv/names.csv'

In [3]:

!head "{csv_path}"

Ahora, creamos un conjunto de datos personalizado extendiendo la clase Dataset de PyTorch. Esto nos permitirá asociar la etiqueta correcta a cada imagen utilizando los datos de nuestro archivo .csv.

In [4]:

class CoilDataset(Dataset):
    def __init__(self, csv_file, root_dir, transform=None):
        self.df = pd.read_csv(csv_file)
        self.transform = transform
        self.root_dir = root_dir

    def __len__(self):
        return len(self.df)    

    def __getitem__(self, idx):
        row = self.df.loc[idx]
        img_id, img_label = row['image'], row['label']
        img_fname = self.root_dir + "/" + str(img_id)
        img = Image.open(img_fname)
        if self.transform:
            img = self.transform(img)
        return img, img_label

In [5]:

dataset = CoilDataset(csv_path, path, transform=ToTensor())

Podemos ver que convertimos cada imagen en un tensor de 3 dimensiones (3, 128, 128). La primera dimensión corresponde al número de canales. Como estamos utilizando imágenes en color, habrá 3 canales (rojo, verde, azul). La segunda y tercera dimensión son para el tamaño de la imagen, en este caso, 128px por 128px.

In [6]:

img, label = dataset[0]
print(img.shape, label)
img
torch.Size([3, 128, 128]) 20

Out[6]:

tensor([[[0.2078, 0.2078, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         ...,
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980]],

        [[0.2000, 0.2000, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         ...,
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980]],

        [[0.1255, 0.0392, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         ...,
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980],
         [0.0980, 0.0980, 0.0980,  ..., 0.0980, 0.0980, 0.0980]]])

Ahora definiremos los hiperparámetros de nuestros modelos.

In [7]:

# Hiperparámetros
batch_size = 128
learning_rate = 0.001

# Otras constantes
in_channels = 3
input_size = in_channels * 128 * 128
num_classes = 101

Conjuntos de datos de entrenamiento y validación

Vamos a dividir el conjunto de datos en 3 partes:

  1. Conjunto de entrenamiento: se utiliza para entrenar el modelo (calcular la pérdida y ajustar los pesos del modelo mediante el descenso de gradiente).
  2. Conjunto de validación: se utiliza para evaluar el modelo durante el entrenamiento, ajustar los hiperparámetros (velocidad de aprendizaje, etc.) y elegir la mejor versión del modelo.
  3. Conjunto de prueba: se utiliza para comparar diferentes modelos o diferentes tipos de enfoques de modelado e informar de la precisión final del modelo.

In [8]:

random_seed = 11
torch.manual_seed(random_seed);

In [9]:

val_size = 720
test_size = 720
train_size = len(dataset) - val_size - test_size

train_ds, val_ds, test_ds = random_split(dataset, [train_size, val_size, test_size])
len(train_ds), len(val_ds), len(test_ds)

Out[9]:

(5760, 720, 720)

Ahora podemos crear cargadores de datos para el entrenamiento y la validación, para cargar los datos por lotes.

In [10]:

train_dl = DataLoader(train_ds, batch_size, shuffle=True, num_workers=4, pin_memory=True)
val_dl = DataLoader(val_ds, batch_size*2, num_workers=4, pin_memory=True)
test_dl = DataLoader(test_ds, batch_size*2, num_workers=4, pin_memory=True)

Podemos ver lotes de imágenes del conjunto de datos utilizando el método make_grid de torchvision. Cada vez que se ejecuta el siguiente código, obtenemos un bach diferente, ya que el muestreador baraja los índices antes de crear los lotes.

In [11]:

def show_batch(dl):
    for images, labels in dl:
        fig, ax = plt.subplots(figsize=(12, 6))
        ax.set_xticks([]); ax.set_yticks([])
        ax.imshow(make_grid(images, nrow=16).permute(1, 2, 0))
        break

In [12]:

show_batch(train_dl)
 PyTorch

Definición de los modelos

Vamos a crear tres modelos diferentes para este proyecto:

  1. Regresión Logística.
  2. Red neuronal profunda.
  3. Red neuronal convolucional.

1. Regresión Logística

La regresión logística es un algoritmo de clasificación de aprendizaje supervisado que se utiliza para predecir la probabilidad de una variable objetivo. La naturaleza de la variable objetivo o dependiente es dicotómica, lo que significa que sólo hay dos clases posibles. La variable dependiente es de naturaleza binaria, con datos codificados como 1 (significa éxito/sí) o 0 (significa fracaso/no).

Nuestras imágenes tienen la forma 3x128x128, pero necesitamos que sean vectores de tamaño 49.152. Utilizaremos el método .reshape de un tensor, que nos permitirá «ver» eficientemente cada imagen como un vector plano, sin modificar realmente los datos subyacentes.

Para incluir esta funcionalidad adicional en nuestro modelo, necesitamos definir un modelo personalizado, extendiendo la clase nn.Module de PyTorch.

In [13]:

class CoilLRModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(input_size, num_classes)

def forward(self, xb):
xb = xb.reshape(-1, in_channels*128*128)
out = self.linear(xb)
return out

def training_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdida
return loss

def validation_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdida
acc = accuracy(out, labels) # Calcular exactitud
return {'val_loss': loss.detach(), 'val_acc': acc.detach()}

def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs] epoch_loss = torch.stack(batch_losses).mean() # Combinar pérdidas
batch_accs = [x['val_acc'] for x in outputs] epoch_acc = torch.stack(batch_accs).mean() # Combinar exactitud
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}

def epoch_end(self, epoch, result):
print("Epoch [{}], val_loss: {:.4f}, val_acc: {:.4f}".format(epoch, result['val_loss'], result['val_acc']))

model = CoilLRModel()

Métrica de evaluación y función de pérdida para la clasificación de imágenes con Deep Learning

Necesitamos una forma de evaluar el rendimiento de nuestro modelo. Una forma natural de hacerlo sería encontrar el porcentaje de etiquetas que se predijeron correctamente (precisión de las predicciones). A continuación, definiremos una función de evaluación, que llevará a cabo la fase de validación, y una función de ajuste que realizará todo el proceso de formación.

In [14]:

def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    return torch.tensor(torch.sum(preds == labels).item() / len(preds))

def evaluate(model, val_loader):
    outputs = [model.validation_step(batch) for batch in val_loader]
    return model.validation_epoch_end(outputs)

Entrenamiento del modelo para la clasificación de imágenes con Deep Learning

In [15]:

def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
    history = []
    optimizer = opt_func(model.parameters(), lr)
    for epoch in range(epochs):
        # Training Phase 
        for batch in train_loader:
            loss = model.training_step(batch)
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        # Validation phase
        result = evaluate(model, val_loader)
        model.epoch_end(epoch, result)
        history.append(result)
    return history

La función de ajuste registra la pérdida de validación y la métrica de cada época y devuelve un historial del proceso de entrenamiento. Esto es útil para depurar y visualizar el proceso de entrenamiento. Antes de entrenar el modelo, veamos cómo se comporta en el conjunto de validación con el conjunto inicial de pesos y sesgos inicializados aleatoriamente.

Las configuraciones como el tamaño del lote, la tasa de aprendizaje, etc. deben elegirse de antemano al entrenar modelos de aprendizaje automático, y se denominan hiperparámetros. Elegir los hiperparámetros adecuados es fundamental para entrenar un modelo preciso en un tiempo razonable, y es un área activa de investigación y experimentación.

In [16]:

result0 = evaluate(model, val_dl)
result0

Out[16]:

{'val_loss': 4.655781269073486, 'val_acc': 0.018830128014087677}

La precisión inicial ronda el 1,8%, que es lo que cabría esperar de un modelo inicializado aleatoriamente (ya que tiene una probabilidad de 1 entre 100 de acertar una etiqueta adivinando al azar).

In [17]:

history = fit(10, learning_rate, model, train_dl, val_dl)
Epoch [0], val_loss: 4.2210, val_acc: 0.1641
Epoch [1], val_loss: 3.8676, val_acc: 0.2875
Epoch [2], val_loss: 3.5594, val_acc: 0.4057
Epoch [3], val_loss: 3.2867, val_acc: 0.5219
Epoch [4], val_loss: 3.0482, val_acc: 0.5643
Epoch [5], val_loss: 2.8349, val_acc: 0.6155
Epoch [6], val_loss: 2.6460, val_acc: 0.6747
Epoch [7], val_loss: 2.4802, val_acc: 0.7192
Epoch [8], val_loss: 2.3304, val_acc: 0.7447
Epoch [9], val_loss: 2.1958, val_acc: 0.7738

Con 10 iteraciones, pasamos de un 1,8% de precisión a un 79%, lo que supone una mejora muy buena.

In [18]:

accuracies = [r['val_acc'] for r in history]
plt.plot(accuracies, '-x')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.title('Accuracy vs. No. of epochs');

In [19]:

# Evaluar en el conjunto de datos de prueba
result = evaluate(model, test_dl)
result

Out[19]:

{'val_loss': 2.218763589859009, 'val_acc': 0.7651241421699524}

Predicción

In [20]:

def predict_image(img, model):
    xb = img.unsqueeze(0)
    yb = model(xb)
    _, preds  = torch.max(yb, dim=1)
    return preds[0].item()

In [21]:

img, label = test_ds[158]
plt.imshow(img.permute(1, 2, 0))
print('Label:', label, ', Predicted:', predict_image(img, model))
Label: 64 , Predicted: 64

Guardar el modelo

In [22]:

torch.save(model.state_dict(), '1-coil-logistic.pth')

2. Red neuronal profunda para la clasificación de imágenes con Deep Learning

Una red neuronal profunda (DNN) es una red neuronal artificial (ANN) con múltiples capas entre las capas de entrada y salida. La DNN encuentra la manipulación matemática correcta para convertir la entrada en la salida, ya sea una relación lineal o no lineal. La red se desplaza por las capas calculando la probabilidad de cada salida.

In [23]:

def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    return torch.tensor(torch.sum(preds == labels).item() / len(preds))

In [24]:

class CoilDNNModel(nn.Module):
"""Feedfoward neural network with 1 hidden layer"""
def __init__(self, in_size, out_size):
super().__init__()
# hidden layer
self.linear1 = nn.Linear(in_size, 2048)
# hidden layer 2
self.linear2 = nn.Linear(2048, 1024)
# output layer
self.linear3 = nn.Linear(1024, out_size)

def forward(self, xb):
# Flatten the image tensors
out = xb.view(xb.size(0), -1)
# Get intermediate outputs using hidden layer 1
out = self.linear1(out)
# Apply activation function
out = F.relu(out)
# Get intermediate outputs using hidden layer 2
out = self.linear2(out)
# Apply activation function
out = F.relu(out)
# Get predictions using output layer
out = self.linear3(out)
return out

def training_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdidas
return loss

def validation_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdidas
acc = accuracy(out, labels) # Calcular exactitud
return {'val_loss': loss, 'val_acc': acc}

def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs] epoch_loss = torch.stack(batch_losses).mean() # Combinar pérdidas
batch_accs = [x['val_acc'] for x in outputs] epoch_acc = torch.stack(batch_accs).mean() # Combinar precisiones
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}

def epoch_end(self, epoch, result):
print("Epoch [{}], val_loss: {:.4f}, val_acc: {:.4f}".format(epoch, result['val_loss'], result['val_acc']))

Uso de GPU para la clasificación de imágenes con Deep Learning

In [25]:

torch.cuda.is_available()

Out[25]:

True

In [26]:

def get_default_device():
    """Pick GPU if available, else CPU"""
    if torch.cuda.is_available():
        return torch.device('cuda')
    else:
        return torch.device('cpu')

In [27]:

device = get_default_device()
device

Out[27]:

device(type='cuda')

In [28]:

def to_device(data, device):
    """Move tensor(s) to chosen device"""
    if isinstance(data, (list,tuple)):
        return [to_device(x, device) for x in data]
    return data.to(device, non_blocking=True)

In [29]:

class DeviceDataLoader():
    """Wrap a dataloader to move data to a device"""
    def __init__(self, dl, device):
        self.dl = dl
        self.device = device

    def __iter__(self):
        """Yield a batch of data after moving it to device"""
        for b in self.dl: 
            yield to_device(b, self.device)

    def __len__(self):
        """Number of batches"""
        return len(self.dl)

In [30]:

train_dl = DeviceDataLoader(train_dl, device)
val_dl = DeviceDataLoader(val_dl, device)
test_dl = DeviceDataLoader(test_dl, device)

Entrenamiento del modelo para la clasificación de imágenes con Deep Learning

In [31]:

def evaluate(model, val_loader):
    outputs = [model.validation_step(batch) for batch in val_loader]
    return model.validation_epoch_end(outputs)

def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
    history = []
    optimizer = opt_func(model.parameters(), lr)
    for epoch in range(epochs):
        # Training Phase 
        for batch in train_loader:
            loss = model.training_step(batch)
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        # Validation phase
        result = evaluate(model, val_loader)
        model.epoch_end(epoch, result)
        history.append(result)
    return history

In [32]:

model = CoilDNNModel(input_size, out_size=num_classes)
to_device(model, device)

Out[32]:

CoilDNNModel(
  (linear1): Linear(in_features=49152, out_features=2048, bias=True)
  (linear2): Linear(in_features=2048, out_features=1024, bias=True)
  (linear3): Linear(in_features=1024, out_features=101, bias=True)
)

In [33]:

history = fit(10, learning_rate*10, model, train_dl, val_dl)
Epoch [0], val_loss: 4.4509, val_acc: 0.2058
Epoch [1], val_loss: 4.2162, val_acc: 0.2385
Epoch [2], val_loss: 3.8548, val_acc: 0.2724
Epoch [3], val_loss: 3.3516, val_acc: 0.3716
Epoch [4], val_loss: 2.8126, val_acc: 0.4919
Epoch [5], val_loss: 2.3059, val_acc: 0.5774
Epoch [6], val_loss: 1.8835, val_acc: 0.6671
Epoch [7], val_loss: 1.5763, val_acc: 0.6943
Epoch [8], val_loss: 1.3278, val_acc: 0.7539
Epoch [9], val_loss: 1.1209, val_acc: 0.8125

In [34]:

losses = [x['val_loss'] for x in history]
plt.plot(losses, '-x')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Loss vs. No. of epochs');

In [35]:

accuracies = [x['val_acc'] for x in history]
plt.plot(accuracies, '-x')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.title('Accuracy vs. No. of epochs');

In [36]:

# Evaluate on test dataset
result = evaluate(model, test_dl)
result

Out[36]:

{'val_loss': 1.1144559383392334, 'val_acc': 0.8215144276618958}

Predicciones

In [37]:

def predict_image(img, model):
    xb = to_device(img.unsqueeze(0), device)
    yb = model(xb)
    _, preds  = torch.max(yb, dim=1)
    return preds[0].item()

In [38]:

img, label = test_ds[200]
plt.imshow(img.permute(1, 2, 0))
print('Label:', label, ', Predicted:', predict_image(img, model))
Label: 2 , Predicted: 2
para la clasificación de imágenes con Deep Learning

Guardar el modelo

In [39]:

torch.save(model.state_dict(), '2-coil-dnn.pth')

3. Red neuronal convolucional para la clasificación de imágenes con Deep Learning

Las redes neuronales convolucionales (CNN o ConvNet) son un tipo de redes neuronales profundas que suelen aplicarse al análisis de imágenes visuales. También se conocen como redes neuronales artificiales invariantes de desplazamiento o espacio (SIANN), debido a su arquitectura de pesos compartidos y a sus características de invariancia de traslación. Tienen aplicaciones en el reconocimiento de imágenes y vídeos, sistemas de recomendación, clasificación de imágenes, análisis de imágenes médicas, procesamiento del lenguaje natural y series temporales financieras.

In [40]:

def accuracy(outputs, labels):
_, preds = torch.max(outputs, dim=1)
return torch.tensor(torch.sum(preds == labels).item() / len(preds))

class ImageClassificationBase(nn.Module):
def training_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdidas
return loss

def validation_step(self, batch):
images, labels = batch
out = self(images) # Generar predicciones
loss = F.cross_entropy(out, labels) # Calcular pérdidas
acc = accuracy(out, labels) # Calcular exactitud
return {'val_loss': loss.detach(), 'val_acc': acc}

def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs] epoch_loss = torch.stack(batch_losses).mean() # Combinar pérdidas
batch_accs = [x['val_acc'] for x in outputs] epoch_acc = torch.stack(batch_accs).mean() # Combinar precisiones
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}

def epoch_end(self, epoch, result):
print("Epoch [{}], train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}".format(
epoch, result['train_loss'], result['val_loss'], result['val_acc']))

In [41]:

class CoilCNNModel(ImageClassificationBase):
    def __init__(self, in_channels, num_classes):
        super().__init__()
        self.network = nn.Sequential(
            nn.Conv2d(in_channels, 128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),

            nn.Flatten(), 
            nn.Linear(256*1024, 1024),
            nn.ReLU(),
            nn.Linear(1024, 512),
            nn.ReLU(),
            nn.Linear(512, num_classes))

    def forward(self, xb):
        return self.network(xb)

In [42]:

model = CoilCNNModel(in_channels, num_classes)
model

Out[42]:

CoilCNNModel(
  (network): Sequential(
    (0): Conv2d(3, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU()
    (7): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU()
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Flatten()
    (11): Linear(in_features=262144, out_features=1024, bias=True)
    (12): ReLU()
    (13): Linear(in_features=1024, out_features=512, bias=True)
    (14): ReLU()
    (15): Linear(in_features=512, out_features=101, bias=True)
  )
)

Uso de GPU

In [43]:

def get_default_device():
    """Pick GPU if available, else CPU"""
    if torch.cuda.is_available():
        return torch.device('cuda')
    else:
        return torch.device('cpu')

def to_device(data, device):
    """Move tensor(s) to chosen device"""
    if isinstance(data, (list,tuple)):
        return [to_device(x, device) for x in data]
    return data.to(device, non_blocking=True)

class DeviceDataLoader():
    """Wrap a dataloader to move data to a device"""
    def __init__(self, dl, device):
        self.dl = dl
        self.device = device

    def __iter__(self):
        """Yield a batch of data after moving it to device"""
        for b in self.dl: 
            yield to_device(b, self.device)

    def __len__(self):
        """Number of batches"""
        return len(self.dl)

In [44]:

device = get_default_device()
device

Out[44]:

device(type='cuda')

In [45]:

train_dl = DeviceDataLoader(train_dl, device)
val_dl = DeviceDataLoader(val_dl, device)
test_dl = DeviceDataLoader(test_dl, device)
to_device(model, device);

Entrenamiento del modelo para la clasificación de imágenes con Deep Learning

In [46]:

@torch.no_grad()
def evaluate(model, val_loader):
    model.eval()
    outputs = [model.validation_step(batch) for batch in val_loader]
    return model.validation_epoch_end(outputs)

def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
    history = []
    optimizer = opt_func(model.parameters(), lr)
    for epoch in range(epochs):
        # Training Phase 
        model.train()
        train_losses = []
        for batch in train_loader:
            loss = model.training_step(batch)
            train_losses.append(loss)
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        # Validation phase
        result = evaluate(model, val_loader)
        result['train_loss'] = torch.stack(train_losses).mean().item()
        model.epoch_end(epoch, result)
        history.append(result)
    return history

In [47]:

model = to_device(CoilCNNModel(in_channels, num_classes), device)

In [48]:

evaluate(model, val_dl)

Out[48]:

{'val_loss': 4.615262031555176, 'val_acc': 0.011017628014087677}

In [49]:

num_epochs = 4
opt_func = torch.optim.Adam

In [50]:

history = fit(num_epochs, learning_rate, model, train_dl, val_dl, opt_func)
Epoch [0], train_loss: 2.8882, val_loss: 0.8734, val_acc: 0.7458
Epoch [1], train_loss: 0.4132, val_loss: 0.1532, val_acc: 0.9605
Epoch [2], train_loss: 0.1390, val_loss: 0.0667, val_acc: 0.9764
Epoch [3], train_loss: 0.0844, val_loss: 0.1092, val_acc: 0.9676

In [51]:

def plot_accuracies(history):
    accuracies = [x['val_acc'] for x in history]
    plt.plot(accuracies, '-x')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.title('Accuracy vs. No. of epochs');

In [52]:

plot_accuracies(history)

In [53]:

def plot_losses(history):
    train_losses = [x.get('train_loss') for x in history]
    val_losses = [x['val_loss'] for x in history]
    plt.plot(train_losses, '-bx')
    plt.plot(val_losses, '-rx')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.legend(['Training', 'Validation'])
    plt.title('Loss vs. No. of epochs');

In [54]:

plot_losses(history)

Pruebas con imágenes individuales

In [55]:

def predict_image(img, model):
    # Convert to a batch of 1
    xb = to_device(img.unsqueeze(0), device)
    # Get predictions from model
    yb = model(xb)
    # Pick index with highest probability
    _, preds  = torch.max(yb, dim=1)
    # Retrieve the class label
    return preds[0].item()

In [56]:

img, label = test_ds[0]
plt.imshow(img.permute(1, 2, 0))
print('Label:', label, ', Predicted:', predict_image(img, model))
Label: 94 , Predicted: 94

In [57]:

img, label = test_ds[100]
plt.imshow(img.permute(1, 2, 0))
print('Label:', label, ', Predicted:', predict_image(img, model))
Label: 35 , Predicted: 35

In [58]:

# Evaluate on test dataset
result = evaluate(model, test_dl)
result

Out[58]:

{'val_loss': 0.10439281165599823, 'val_acc': 0.9718549847602844}

Guardar el modelo

In [59]:

torch.save(model.state_dict(), '3-coil-cnn.pth')

Sergio Alves

Ingeniero de Sistemas. MSc. en Data Science. Cuento con una amplia trayectoria profesional en las áreas de Desarrollo Web FullStack, DBA, DevOps, Inteligencia Artificial y Ciencia de Datos. Soy un entusiasta de la música, la tecnología y el aprendizaje contínuo.

Artículos Relacionados

Back to top button