Red de inicio | Implementación de GoogleNet en Keras

Contenidos

Introducción

El aprendizaje profundo está ganando fuerza rápidamente a medida que surgen más y más artículos de investigación de todo el mundo. Sin duda, estos documentos contienen una gran cantidad de información, pero a menudo pueden ser difíciles de analizar. Y para comprenderlos, es posible que tenga que revisar ese documento varias veces (¡y tal vez incluso otros documentos dependientes!).

Esta es realmente una tarea desalentadora para los no académicos como nosotros.

after-finishing-this-300x225-6282215

Personalmente, encuentro la tarea de revisar un artículo de investigación, interpretar el quid detrás de él e implementar el código como una habilidad importante que todo entusiasta y practicante del aprendizaje profundo debería poseer. La implementación práctica de ideas de investigación saca a relucir el proceso de pensamiento del autor y también ayuda a transformar esas ideas en aplicaciones de la industria del mundo real.

Entonces, en este artículo (y la siguiente serie de artículos) mi motivo para escribir es doble:

  1. Permita que los lectores se mantengan al día con la investigación de vanguardia dividiendo los artículos de aprendizaje profundo en conceptos comprensibles.
  2. Aprender a convertir ideas de investigación en código para mí mismo y animar a las personas a que lo hagan simultáneamente.

Este artículo asume que tiene un buen conocimiento de los conceptos básicos del aprendizaje profundo. En caso de que no lo necesite, o simplemente necesite un repaso, consulte primero los artículos a continuación y luego regrese aquí pronto:

Tabla de contenido

  • Resumen del documento «Profundizar en las convoluciones»
    • Objetivo del trabajo
    • Detalles arquitectónicos propuestos
    • Metodología de formación
  • Implementación de GoogLeNet en Keras

Resumen del documento «Profundizar en las convoluciones»

Este artículo se centra en el papel «Profundizar con las convoluciones» de donde surgió la idea distintiva de la red de inicio. La red de inicio se consideró una vez una arquitectura (o modelo) de aprendizaje profundo de última generación para resolver problemas de detección y reconocimiento de imágenes.

Presentó un rendimiento revolucionario en el Desafío de reconocimiento visual ImageNet (en 2014), que es una plataforma de renombre para la evaluación comparativa de algoritmos de detección y reconocimiento de imágenes. Junto con esto, se inició una gran cantidad de investigaciones en la creación de nuevas arquitecturas de aprendizaje profundo con ideas innovadoras e impactantes.

Repasaremos las principales ideas y sugerencias propuestas en el documento mencionado anteriormente y trataremos de comprender las técnicas que contiene. En palabras del autor:

«En este artículo, nos centraremos en una arquitectura de red neuronal profunda eficiente para la visión por computadora, cuyo nombre en código es Inception, que deriva su nombre de (…) el famoso meme de Internet» necesitamos ir más profundo «.

a88-1164825

Eso suena intrigante, ¿no? Bueno, ¡sigue leyendo entonces!

Objetivo del trabajo

Existe una forma simple pero poderosa de crear mejores modelos de aprendizaje profundo. Puede simplemente hacer un modelo más grande, ya sea en términos de profundidad, es decir, número de capas, o el número de neuronas en cada capa. Pero como puede imaginar, esto a menudo puede crear complicaciones:

  • Cuanto más grande es el modelo, más propenso a sobreajustarse. Esto es particularmente notable cuando los datos de entrenamiento son pequeños.
  • Aumentar la cantidad de parámetros significa que necesita aumentar sus recursos computacionales existentes

Una solución para esto, como sugiere el documento, es pasar a arquitecturas de red escasamente conectadas que reemplazarán las arquitecturas de red completamente conectadas, especialmente dentro de las capas convolucionales. Esta idea se puede conceptualizar en las siguientes imágenes:

screenshot-from-2018-10-16-10-52-13-206x300-3658357

Arquitectura densamente conectada

screenshot-from-2018-10-16-10-52-30-300x165-1701673

Arquitectura escasamente conectada

Este artículo propone una nueva idea de creación de arquitecturas profundas. Este enfoque le permite mantener el «presupuesto computacional», mientras aumenta la profundidad y el ancho de la red. ¡Suena demasiado bueno para ser verdad! Así es como se ve la idea conceptualizada:

googlenet-1196311

Veamos la arquitectura propuesta con un poco más de detalle.

Detalles arquitectónicos propuestos

El documento propone un nuevo tipo de arquitectura: GoogLeNet o Inception v1. Es básicamente una red neuronal convolucional (CNN) que tiene 27 capas de profundidad.. A continuación se muestra el resumen del modelo:

screenshot-from-2018-10-16-11-24-42-74x300-3793794

Observe en la imagen de arriba que hay una capa llamada capa de inicio. Esta es en realidad la idea principal detrás del enfoque del documento. La capa inicial es el concepto central de una arquitectura escasamente conectada.

screenshot-from-2018-10-16-10-52-30-300x1651-300x165-3297392

Idea de un módulo de inicio

Permítanme explicar con un poco más de detalle de qué se trata una capa de inicio. Tomando un extracto del artículo:

«(Capa de inicio) es una combinación de todas esas capas (es decir, capa convolucional 1 × 1, capa convolucional 3 × 3, capa convolucional 5 × 5) con sus bancos de filtros de salida concatenados en un único vector de salida que forma la entrada del siguiente escenario.»

Junto con las capas mencionadas anteriormente, hay dos complementos principales en la capa de inicio original:

  • Capa convolucional 1 × 1 antes de aplicar otra capa, que se utiliza principalmente para la reducción de dimensionalidad
  • Una capa de agrupación máxima paralela, que proporciona otra opción a la capa de inicio
screenshot-from-2018-10-17-11-14-10-3445962

Capa de inicio

Para comprender la importancia de la estructura de la capa inicial, el autor recurre al principio de Hebbiano del aprendizaje humano. Esto dice que «las neuronas que se disparan juntas, se conectan juntas». El autor sugiere que Al crear una capa posterior en un modelo de aprendizaje profundo, se debe prestar atención a los aprendizajes de la capa anterior.

Supongamos, por ejemplo, que una capa de nuestro modelo de aprendizaje profundo ha aprendido a centrarse en partes individuales de un rostro. La siguiente capa de la red probablemente se centraría en la cara general de la imagen para identificar los diferentes objetos presentes allí. Ahora, para hacer esto, la capa debe tener los tamaños de filtro apropiados para detectar diferentes objetos.

cat_vs_dog_explain4-300x231-5284051

Aquí es donde la capa inicial pasa a primer plano. Permite que las capas internas seleccionen y elijan qué tamaño de filtro será relevante para conocer la información requerida. Entonces, incluso si el tamaño de la cara en la imagen es diferente (como se ve en las imágenes a continuación), la capa funciona en consecuencia para reconocer la cara. Para la primera imagen, probablemente necesitaría un tamaño de filtro más alto, mientras que tomaría uno más bajo para la segunda imagen.

screenshot-from-2018-10-17-11-42-40-300x103-5993337

La arquitectura general, con todas las especificaciones, se ve así:

screenshot-from-2018-10-16-11-56-41-300x163-9994576

Metodología de formación

Tenga en cuenta que esta arquitectura surgió en gran parte debido a que los autores participaron en un desafío de detección y reconocimiento de imágenes. Por lo tanto, hay bastantes «campanas y silbidos» que han explicado en el documento. Éstos incluyen:

  • El hardware que usaron para entrenar a los modelos.
  • La técnica de aumento de datos para crear el conjunto de datos de entrenamiento.
  • Los hiperparámetros de la red neuronal, como la técnica de optimización y el programa de tasas de aprendizaje.
  • Formación auxiliar necesaria para entrenar el modelo.
  • Técnicas de ensamble que usaron para construir la presentación final.

Entre estos, la formación auxiliar realizada por los autores es bastante interesante y novedosa por naturaleza. Así que nos centraremos en eso por ahora. Los detalles del resto de técnicas se pueden tomar del propio artículo, o en la implementación que veremos a continuación.

Para evitar que la parte media de la red “desaparezca”, los autores introdujeron dos clasificadores auxiliares (los recuadros de color púrpura en la imagen). Básicamente, aplicaron softmax a las salidas de dos de los módulos de inicio y calcularon una pérdida auxiliar sobre las mismas etiquetas. La función de pérdida total es una suma ponderada de la pérdida auxiliar y la pérdida real. El valor de peso utilizado en el papel fue 0,3 por cada pérdida auxiliar.

Implementación de GoogLeNet en Keras

Ahora que ha entendido la arquitectura de GoogLeNet y la intuición detrás de ella, ¡es hora de encender Python e implementar nuestros aprendizajes usando Keras! Usaremos el conjunto de datos CIFAR-10 para este propósito.

cifar-9579521

CIFAR-10 es un conjunto de datos de clasificación de imágenes popular. Consta de 60.000 imágenes de 10 clases (cada clase se representa como una fila en la imagen de arriba). El conjunto de datos se divide en 50.000 imágenes de entrenamiento y 10.000 imágenes de prueba.

Tenga en cuenta que debe tener instaladas las bibliotecas necesarias para implementar el código que veremos en esta sección. Esto incluye Keras y TensorFlow (como back-end para Keras). Puede consultar el guía de instalación oficial en caso de que no tenga Keras ya instalado en su máquina.

Ahora que nos hemos ocupado de los requisitos previos, finalmente podemos comenzar a codificar la teoría que cubrimos en las secciones anteriores. Lo primero que debemos hacer es importar todas las bibliotecas y módulos necesarios que usaremos a lo largo del código.

import keras
from keras.layers.core import Layer
import keras.backend as K
import tensorflow as tf
from keras.datasets import cifar10
from keras.models import Model
from keras.layers import Conv2D, MaxPool2D,  
    Dropout, Dense, Input, concatenate,      
    GlobalAveragePooling2D, AveragePooling2D,
    Flatten

import cv2 
import numpy as np 
from keras.datasets import cifar10 
from keras import backend as K 
from keras.utils import np_utils

import math 
from keras.optimizers import SGD 
from keras.callbacks import LearningRateScheduler

Luego cargaremos el conjunto de datos y realizaremos algunos pasos de preprocesamiento. Esta es una tarea crítica antes de que se entrene el modelo de aprendizaje profundo.

num_classes = 10

def load_cifar10_data(img_rows, img_cols):

    # Load cifar10 training and validation sets
    (X_train, Y_train), (X_valid, Y_valid) = cifar10.load_data()

    # Resize training images
    X_train = np.array([cv2.resize(img, (img_rows,img_cols)) for img in X_train[:,:,:,:]])
    X_valid = np.array([cv2.resize(img, (img_rows,img_cols)) for img in X_valid[:,:,:,:]])

    # Transform targets to keras compatible format
    Y_train = np_utils.to_categorical(Y_train, num_classes)
    Y_valid = np_utils.to_categorical(Y_valid, num_classes)
    
    X_train = X_train.astype('float32')
    X_valid = X_valid.astype('float32')

    # preprocess data
    X_train = X_train / 255.0
    X_valid = X_valid / 255.0

    return X_train, Y_train, X_valid, Y_valid
X_train, y_train, X_test, y_test = load_cifar10_data(224, 224)

Ahora, definiremos nuestra arquitectura de aprendizaje profundo. Definiremos rápidamente una función para hacer esto, que, cuando se le da la información necesaria, nos devuelve toda la capa de inicio.

def inception_module(x,
                     filters_1x1,
                     filters_3x3_reduce,
                     filters_3x3,
                     filters_5x5_reduce,
                     filters_5x5,
                     filters_pool_proj,
                     name=None):
    
    conv_1x1 = Conv2D(filters_1x1, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)
    
    conv_3x3 = Conv2D(filters_3x3_reduce, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)
    conv_3x3 = Conv2D(filters_3x3, (3, 3), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(conv_3x3)

    conv_5x5 = Conv2D(filters_5x5_reduce, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)
    conv_5x5 = Conv2D(filters_5x5, (5, 5), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(conv_5x5)

    pool_proj = MaxPool2D((3, 3), strides=(1, 1), padding='same')(x)
    pool_proj = Conv2D(filters_pool_proj, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(pool_proj)

    output = concatenate([conv_1x1, conv_3x3, conv_5x5, pool_proj], axis=3, name=name)
    
    return output

Luego crearemos la arquitectura GoogLeNet, como se menciona en el documento.

kernel_init = keras.initializers.glorot_uniform()
bias_init = keras.initializers.Constant(value=0.2)
screenshot-from-2018-10-16-11-56-41-300x163-9994576
input_layer = Input(shape=(224, 224, 3))

x = Conv2D(64, (7, 7), padding='same', strides=(2, 2), activation='relu', name='conv_1_7x7/2', kernel_initializer=kernel_init, bias_initializer=bias_init)(input_layer)
x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_1_3x3/2')(x)
x = Conv2D(64, (1, 1), padding='same', strides=(1, 1), activation='relu', name='conv_2a_3x3/1')(x)
x = Conv2D(192, (3, 3), padding='same', strides=(1, 1), activation='relu', name='conv_2b_3x3/1')(x)
x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_2_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=64,
                     filters_3x3_reduce=96,
                     filters_3x3=128,
                     filters_5x5_reduce=16,
                     filters_5x5=32,
                     filters_pool_proj=32,
                     name='inception_3a')

x = inception_module(x,
                     filters_1x1=128,
                     filters_3x3_reduce=128,
                     filters_3x3=192,
                     filters_5x5_reduce=32,
                     filters_5x5=96,
                     filters_pool_proj=64,
                     name='inception_3b')

x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_3_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=192,
                     filters_3x3_reduce=96,
                     filters_3x3=208,
                     filters_5x5_reduce=16,
                     filters_5x5=48,
                     filters_pool_proj=64,
                     name='inception_4a')


x1 = AveragePooling2D((5, 5), strides=3)(x)
x1 = Conv2D(128, (1, 1), padding='same', activation='relu')(x1)
x1 = Flatten()(x1)
x1 = Dense(1024, activation='relu')(x1)
x1 = Dropout(0.7)(x1)
x1 = Dense(10, activation='softmax', name='auxilliary_output_1')(x1)

x = inception_module(x,
                     filters_1x1=160,
                     filters_3x3_reduce=112,
                     filters_3x3=224,
                     filters_5x5_reduce=24,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4b')

x = inception_module(x,
                     filters_1x1=128,
                     filters_3x3_reduce=128,
                     filters_3x3=256,
                     filters_5x5_reduce=24,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4c')

x = inception_module(x,
                     filters_1x1=112,
                     filters_3x3_reduce=144,
                     filters_3x3=288,
                     filters_5x5_reduce=32,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4d')


x2 = AveragePooling2D((5, 5), strides=3)(x)
x2 = Conv2D(128, (1, 1), padding='same', activation='relu')(x2)
x2 = Flatten()(x2)
x2 = Dense(1024, activation='relu')(x2)
x2 = Dropout(0.7)(x2)
x2 = Dense(10, activation='softmax', name='auxilliary_output_2')(x2)

x = inception_module(x,
                     filters_1x1=256,
                     filters_3x3_reduce=160,
                     filters_3x3=320,
                     filters_5x5_reduce=32,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_4e')

x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_4_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=256,
                     filters_3x3_reduce=160,
                     filters_3x3=320,
                     filters_5x5_reduce=32,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_5a')

x = inception_module(x,
                     filters_1x1=384,
                     filters_3x3_reduce=192,
                     filters_3x3=384,
                     filters_5x5_reduce=48,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_5b')

x = GlobalAveragePooling2D(name='avg_pool_5_3x3/1')(x)

x = Dropout(0.4)(x)

x = Dense(10, activation='softmax', name='output')(x)
model = Model(input_layer, [x, x1, x2], name='inception_v1')

Resumamos nuestro modelo para comprobar si nuestro trabajo hasta ahora ha ido bien.

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 224, 224, 3)  0                                            
__________________________________________________________________________________________________
conv_1_7x7/2 (Conv2D)           (None, 112, 112, 64) 9472        input_1[0][0]                    
__________________________________________________________________________________________________
max_pool_1_3x3/2 (MaxPooling2D) (None, 56, 56, 64)   0           conv_1_7x7/2[0][0]               
__________________________________________________________________________________________________
norm1 (LRN2D)                   (None, 56, 56, 64)   0           max_pool_1_3x3/2[0][0]           
__________________________________________________________________________________________________
...
...
...
dropout_3 (Dropout)             (None, 1024)         0           avg_pool_5_3x3/1[0][0]           
__________________________________________________________________________________________________
dropout_1 (Dropout)             (None, 1024)         0           dense_1[0][0]                    
__________________________________________________________________________________________________
dropout_2 (Dropout)             (None, 1024)         0           dense_2[0][0]                    
__________________________________________________________________________________________________
output (Dense)                  (None, 10)           10250       dropout_3[0][0]                  
__________________________________________________________________________________________________
auxilliary_output_1 (Dense)     (None, 10)           10250       dropout_1[0][0]                  
__________________________________________________________________________________________________
auxilliary_output_2 (Dense)     (None, 10)           10250       dropout_2[0][0]                  
==================================================================================================
Total params: 10,334,030
Trainable params: 10,334,030
Non-trainable params: 0
__________________________________________________________________________________________________

El modelo se ve bien, como puede medir a partir de la salida anterior. Podemos agregar algunos toques finales antes de entrenar nuestro modelo. Definiremos lo siguiente:

  • Función de pérdida para cada capa de salida
  • Peso asignado a esa capa de salida
  • Función de optimización, que se modifica para incluir una disminución de peso después de cada 8 épocas.
  • Métrica de evaluación
epochs = 25
initial_lrate = 0.01

def decay(epoch, steps=100):
    initial_lrate = 0.01
    drop = 0.96
    epochs_drop = 8
    lrate = initial_lrate * math.pow(drop, math.floor((1+epoch)/epochs_drop))
    return lrate

sgd = SGD(lr=initial_lrate, momentum=0.9, nesterov=False)

lr_sc = LearningRateScheduler(decay, verbose=1)

model.compile(loss=['categorical_crossentropy', 'categorical_crossentropy', 'categorical_crossentropy'], loss_weights=[1, 0.3, 0.3], optimizer=sgd, metrics=['accuracy'])

¡Nuestro modelo ya está listo! Pruébelo para comprobar cómo funciona.

history = model.fit(X_train, [y_train, y_train, y_train], validation_data=(X_test, [y_test, y_test, y_test]), epochs=epochs, batch_size=256, callbacks=[lr_sc])

A continuación se muestra el resultado que obtuve al entrenar el modelo:

Train on 50000 samples, validate on 10000 samples
Epoch 1/25

Epoch 00001: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 188s 4ms/step - loss: 3.7140 - output_loss: 2.3280 - auxilliary_output_1_loss: 2.3101 - auxilliary_output_2_loss: 2.3099 - output_acc: 0.1030 - auxilliary_output_1_acc: 0.1029 - auxilliary_output_2_acc: 0.0992 - val_loss: 3.6898 - val_output_loss: 2.3085 - val_auxilliary_output_1_loss: 2.3018 - val_auxilliary_output_2_loss: 2.3025 - val_output_acc: 0.1000 - val_auxilliary_output_1_acc: 0.1017 - val_auxilliary_output_2_acc: 0.0984
Epoch 2/25

Epoch 00002: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 3.6635 - output_loss: 2.2894 - auxilliary_output_1_loss: 2.2817 - auxilliary_output_2_loss: 2.2987 - output_acc: 0.1161 - auxilliary_output_1_acc: 0.1321 - auxilliary_output_2_acc: 0.1151 - val_loss: 3.6559 - val_output_loss: 2.3095 - val_auxilliary_output_1_loss: 2.2315 - val_auxilliary_output_2_loss: 2.2565 - val_output_acc: 0.1466 - val_auxilliary_output_1_acc: 0.1478 - val_auxilliary_output_2_acc: 0.1417
Epoch 3/25

Epoch 00003: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 180s 4ms/step - loss: 3.2981 - output_loss: 2.0660 - auxilliary_output_1_loss: 2.0414 - auxilliary_output_2_loss: 2.0653 - output_acc: 0.2212 - auxilliary_output_1_acc: 0.2363 - auxilliary_output_2_acc: 0.2256 - val_loss: 3.1812 - val_output_loss: 2.0064 - val_auxilliary_output_1_loss: 1.9372 - val_auxilliary_output_2_loss: 1.9787 - val_output_acc: 0.2578 - val_auxilliary_output_1_acc: 0.2909 - val_auxilliary_output_2_acc: 0.2767
Epoch 4/25

Epoch 00004: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 3.0797 - output_loss: 1.9258 - auxilliary_output_1_loss: 1.9214 - auxilliary_output_2_loss: 1.9248 - output_acc: 0.2803 - auxilliary_output_1_acc: 0.2914 - auxilliary_output_2_acc: 0.2872 - val_loss: 3.0099 - val_output_loss: 1.8852 - val_auxilliary_output_1_loss: 1.8900 - val_auxilliary_output_2_loss: 1.8589 - val_output_acc: 0.3080 - val_auxilliary_output_1_acc: 0.3122 - val_auxilliary_output_2_acc: 0.3296
Epoch 5/25

Epoch 00005: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 2.8427 - output_loss: 1.7733 - auxilliary_output_1_loss: 1.7933 - auxilliary_output_2_loss: 1.7711 - output_acc: 0.3454 - auxilliary_output_1_acc: 0.3485 - auxilliary_output_2_acc: 0.3509 - val_loss: 2.6623 - val_output_loss: 1.6788 - val_auxilliary_output_1_loss: 1.6531 - val_auxilliary_output_2_loss: 1.6250 - val_output_acc: 0.3922 - val_auxilliary_output_1_acc: 0.4094 - val_auxilliary_output_2_acc: 0.4103
Epoch 6/25
...
...
...
Epoch 00024: LearningRateScheduler reducing learning rate to 0.008847359999999999.
50000/50000 [==============================] - 181s 4ms/step - loss: 0.7803 - output_loss: 0.3791 - auxilliary_output_1_loss: 0.7608 - auxilliary_output_2_loss: 0.5767 - output_acc: 0.8665 - auxilliary_output_1_acc: 0.7332 - auxilliary_output_2_acc: 0.7962 - val_loss: 1.0228 - val_output_loss: 0.6043 - val_auxilliary_output_1_loss: 0.7442 - val_auxilliary_output_2_loss: 0.6508 - val_output_acc: 0.7970 - val_auxilliary_output_1_acc: 0.7408 - val_auxilliary_output_2_acc: 0.7724
Epoch 25/25

Epoch 00025: LearningRateScheduler reducing learning rate to 0.008847359999999999.
50000/50000 [==============================] - 181s 4ms/step - loss: 0.7411 - output_loss: 0.3543 - auxilliary_output_1_loss: 0.7349 - auxilliary_output_2_loss: 0.5545 - output_acc: 0.8755 - auxilliary_output_1_acc: 0.7408 - auxilliary_output_2_acc: 0.8060 - val_loss: 0.9524 - val_output_loss: 0.5383 - val_auxilliary_output_1_loss: 0.7346 - val_auxilliary_output_2_loss: 0.6458 - val_output_acc: 0.8191 - val_auxilliary_output_1_acc: 0.7435 - val_auxilliary_output_2_acc: 0.7791

Nuestro modelo dio una impresionante precisión del 80% + en el conjunto de validación, lo que demuestra que realmente vale la pena comprobar esta arquitectura de modelo.

Notas finales

Este fue un artículo realmente agradable de escribir y espero que lo haya encontrado igualmente útil. Inception v1 fue el punto focal de este artículo, en el que expliqué el meollo de la cuestión de este marco y demostré cómo implementarlo desde cero en Keras.

En los próximos artículos, me centraré en los avances en las arquitecturas Inception. Estos avances se detallaron en artículos posteriores, a saber, Inception v2, Inception v3, etc. Y sí, son tan intrigantes como sugiere el nombre, ¡así que estad atentos!

Si tiene alguna sugerencia / comentario relacionado con el artículo, publíquelo en la sección de comentarios a continuación.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.