Está en la página 1de 14

Clasificando 10

especies de monos
utilizando redes
neuronales artificiales

Realizado por: Rafael Ramírez & Julián Jiménez


Profesor: Ingeniero. Fredys Alberto Simanca Herrera

Universidad Libre Sede Bosque


Facultad de Ingeniería
Ingeniería de Sistemas
Bogotá, 28 de sept. de 20
Introducción
Los modelos de aprendizaje automático no son ‘pura caja negra’ como se suele decir. Por medio de
este trabajo se logra el entrenamiento de una red neuronal que logra clasificar e identificar 10 especies
de monos y además, intenta averiguar cómo hacer que las predicciones sean compartidas.
De esta manera, presentamos una red neuronal convolucional ¿qué es esto?, (también conocidas
como sistemas conexionistas) son un modelo computacional vagamente inspirado en el
comportamiento observado en su homólogo biológico. Consiste en un conjunto de unidades, llamadas
neuronas artificiales, conectadas entre sí para transmitirse señales. La información de entrada
atraviesa la red neuronal (donde se somete a diversas operaciones) produciendo unos valores de
salida.

Con todo y lo anterior, resolvemos el entrenamiento y la clasificación de la red neuronal por medio
del lenguaje de programación Python, unas robustas librería para el ‘Deep Learning’ que son
Tensorflow y Keras, además utilizamos como modelo base la técnica del transfer learning para
aprovechar la información de un modelo base ‘VVG16’.
Metodología
Una manera cómoda de explicar la metodología es viajando atreves del código, sin embargo
resaltamos una síntesis así:
1. Importación de las librerías requeridas
2. Cargar el set de datos y procesarlo
3. Aumentar los datos
4. Modelar la red neuronal
5. Obtener el modelo y los pesos

El Dataset
El conjunto de datos lo encontramos en como: ’10 Monkey Species’ consta de dos archivos,
entrenamiento y validación. Cada carpeta contiene 10 subfolders etiquetados como n0 hasta n9, cada
uno correspondiente a una especie de la cladograma de monos de Wikipedia . Las imágenes son de
400x300px o más grandes y en formato JPEG (casi 1400 imágenes). Las imágenes se descargaron
con la ayuda del código fuente abierto de googliser .
Fuente https://www.kaggle.com/aakashnain/what-does-a-cnn-see

Importación de las librerías necesarias


Lectura de los datos
# Definimos las rutas para simplificar el trabajo de lectura
training_data = Path('monkeys/training/training/')
validation_data = Path('monkeys/validation/validation/')
labels_path = Path('monkeys/monkey_labels.txt')

Leeremos el archivo monkey_labels.txt para extraer la información sobre las etiquetas. Podemos
almacenar esta información en una lista que luego se puede convertir en un marco de datos de
pandas.
labels_info = []

# Read the file


lines = labels_path.read_text().strip().splitlines()[1:]
for line in lines:
line = line.split(',')
line = [x.strip(' \n\t\r') for x in line]
line[3], line[4] = int(line[3]), int(line[4])
line = tuple(line)
labels_info.append(line)

# Convert the data into a pandas dataframe


labels_info = pd.DataFrame(labels_info, columns=['Label', 'Latin Name', 'Common
Name',
'Train Images', 'Validation
Images'], index=None)
# Sneak peek
labels_info.head(10)

Las etiquetas son n0, n1, n2, .... Crearemos un mapeo de estas etiquetas donde cada clase estará
representada por un número entero comenzando desde 0 hasta el número de clases. También
crearemos un mapeo para los nombres correspondientes a una clase. Usaremos la columna Common
Name para la última parte.
# Directorio para mapear las etiquetas a enteros
labels_dict= {'n0':0, 'n1':1, 'n2':2, 'n3':3, 'n4':4, 'n5':5, 'n6':6, 'n7':7,
'n8':8, 'n9':9}

# mapear etiquetas a nombres comunes


names_dict = dict(zip(labels_dict.values(), labels_info["Common Name"]))
print(names_dict)

Asi quedaría nuestro diccionario de clases:


{0: 'mantled_howler', 1: 'patas_monkey', 2: 'bald_uakari', 3:
'japanese_macaque', 4: 'pygmy_marmoset', 5: 'white_headed_capuchin', 6:
'silvery_marmoset', 7: 'common_squirrel_monkey', 8:
'black_headed_night_monkey', 9: 'nilgiri_langur'}

Este es un conjunto de datos muy pequeño. Puede cargar los datos en matrices numerosas que luego
se pueden usar directamente para el entrenamiento. Pero este no es siempre el escenario. La mayoría
de las veces no se podrá cargar el conjunto de datos completo en la memoria. Es por eso que siempre
es buena práctica almacenar sobre el conjunto de datos en marcos de datos y luego usar un
generador para cargar los datos sobre la marcha. Haremos lo mismo aquí
# Creando un dataframe para las imagenes de entrenamiento
train_df = []
for folder in os.listdir(training_data):
# Definirir la ruta de las imagenes
imgs_path = training_data / folder

# Conseguir la lista de todas las imagenes guardadas en dicho directorio


imgs = sorted(imgs_path.glob('*.jpg'))

# Almacenar cada imagen y su correspondiente etiqueta


for img_name in imgs:
train_df.append((str(img_name), labels_dict[folder]))

# Set de datos de entrenamiento


train_df = pd.DataFrame(train_df, columns=['image', 'label'], index=None)
# Desordenar las imagenes dentro del set de datos
train_df = train_df.sample(frac=1.).reset_index(drop=True)

#############################################################################
#######################

# Creaando un dataframe para el set de validación con el mismo procedimiento


valid_df = []
for folder in os.listdir(validation_data):
imgs_path = validation_data / folder
imgs = sorted(imgs_path.glob('*.jpg'))
for img_name in imgs:
valid_df.append((str(img_name), labels_dict[folder]))

valid_df = pd.DataFrame(valid_df, columns=['image', 'label'], index=None)


valid_df = valid_df.sample(frac=1.).reset_index(drop=True)

#############################################################################
#######################

print("Numero de muestras de entrenamiento: ", len(train_df))


print("Numero de muestras de validación: ", len(valid_df))

# Miramos la cabezera de cada set de datos


print("\n",train_df.head(), "\n")
print("=================================================================\n")
print("\n", valid_df.head())

Recién se crearon 2 Dataframes una para los datos de validación y otro para el entrenamiento.

Metadatos de la red neuronal


# Definición de algunas constantes importantes

# Las imagenes serán de 224X224 en 3 canales de color RGB


img_rows, img_cols, img_channels = 224,224,3

# El tamaño de lote
batch_size=8

# El número de clases que tendrá la red neuronal (posibles predicciones)


nb_classes=10
El tamaño del lote define la cantidad de muestras que se propagarán a través de la red. Por ejemplo,
supongamos que tiene 1050 muestras de entrenamiento y desea configurar un valor batch_sizeigual
a 100. El algoritmo toma las primeras 100 muestras (del 1 al 100) del conjunto de datos de
entrenamiento y entrena la red. A continuación, toma las segundas 100 muestras (de la 101ª a la
200ª) y vuelve a entrenar la red. Al final, el tamaño del batch_size será realmente el total de datos /
entre esta variable.

Aumento de Datos
El detalle de este set de datos es que realmente no es muy grande entonces, los modelos profundos
no funcionan muy bien asi. Los modelos de aprendizaje profundo están hambrientos de datos.
Cuantos más datos proporcione a un modelo de aprendizaje profundo, más mejorará el rendimiento
(hasta que su algoritmo haya alcanzado un límite). Aquí es donde el aumento de datos es realmente
útil. Usaremos imgaug, una biblioteca muy poderosa para aumentar nuestras imágenes. Definiremos
una secuencia de aumentos y para cada imagen, uno de estos aumentos se aplicará a la imagen
durante el entrenamiento.
# Secuencia de aumento
seq = iaa.OneOf([
iaa.Fliplr(), # horizontal flips
iaa.Affine(rotate=20), # roatación
iaa.Multiply((1.2, 1.5))]) # Brillo aleatorio

def data_generator(data, batch_size, is_validation_data=False):


# Conseguir el numero total de muestras de los datos
n = len(data)
nb_batches = int(np.ceil(n/batch_size))

# Obtencion de una matriz numpy de todos los índices de los datos de entrada
indices = np.arange(n)

# Definimos dos matrices numpy para contener etiquetas y datos por lotes
batch_data = np.zeros((batch_size, img_rows, img_cols, img_channels),
dtype=np.float32)
batch_labels = np.zeros((batch_size, nb_classes), dtype=np.float32)

while True:
if not is_validation_data:
# Desordenar indices de los datos de validación
np.random.shuffle(indices)

for i in range(nb_batches):
# Pasar al siguiente lote
next_batch_indices = indices[i*batch_size:(i+1)*batch_size]

# procesar siguiente lote


for j, idx in enumerate(next_batch_indices):
img = cv2.imread(data.iloc[idx]["image"])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
label = data.iloc[idx]["label"]
if not is_validation_data:
img = seq.augment_image(img)

img = cv2.resize(img, (img_rows, img_cols)).astype(np.float32)


batch_data[j] = img
batch_labels[j] = to_categorical(label,num_classes=nb_classes)

batch_data = preprocess_input(batch_data)
yield batch_data, batch_labels

# Generador de los datos de entrenamiento y validadción aumetados


train_data_gen = data_generator(train_df, batch_size)
valid_data_gen = data_generator(valid_df, batch_size, is_validation_data=True)

Creación del Modelo


Haremos aprendizaje de transferencia aquí, elegimos vgg16 como red base. Además, como el
conjunto de datos es muy pequeño y similar a Imagenet, haríamos cambios mínimos en la red para
mantener los parámetros entrenables lo menos posible. El aprendizaje por transferencia (TL) es un
problema de investigación en aprendizaje automático (ML) que se centra en almacenar el
conocimiento adquirido mientras se resuelve un problema y se aplica a un problema diferente pero
relacionado. Por ejemplo, el conocimiento adquirido al aprender a reconocer automóviles podría
aplicarse al tratar de reconocer camiones.
# Función que nos retorna el modelo base
def get_base_model():
base_model = VGG16(input_shape=(img_rows, img_cols, img_channels),
weights='imagenet', include_top=True)
return base_model

base_model = get_base_model()

# Capturamos el output del la ultima capa densa


base_model_output = base_model.layers[-2].output

# Adicionamos una capa de dropout


x = Dropout(0.7,name='drop2')(base_model_output)
output = Dense(10, activation='softmax', name='fc3')(x)

# Definimos un nuevo modelo


model = Model(base_model.input, output)

# Congelamos las capas del modelo anterior ya entrenadas


for layer in base_model.layers[:-1]:
layer.trainable=False

# Compilamos y chequeamos
optimizer = RMSprop(0.001)
model.compile(loss='categorical_crossentropy', optimizer=optimizer,
metrics=['accuracy'])
model.summary()

Modelo de la Red Neuronal


Model: "functional_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
_________________________________________________________________
flatten (Flatten) (None, 25088) 0
_________________________________________________________________
fc1 (Dense) (None, 4096) 102764544
_________________________________________________________________
fc2 (Dense) (None, 4096) 16781312
_________________________________________________________________
drop2 (Dropout) (None, 4096) 0
_________________________________________________________________
fc3 (Dense) (None, 10) 40970
=================================================================
Total params: 134,301,514
Trainable params: 40,970
Non-trainable params: 134,260,544

# Siempre debemos usar el EarlyStopping


# el parámetro restore_best_weights carga los pesos de la mejor iteración una
vez que finaliza el entrenamiento
es = EarlyStopping(patience=10, restore_best_weights=True)

# checkpoint para guardar el modelo


chkpt = ModelCheckpoint(filepath="model1", save_best_only=True)

# Numero de saltos para la validación y el set de entrenemiento


nb_train_steps = int(np.ceil(len(train_df)/batch_size))
nb_valid_steps = int(np.ceil(len(valid_df)/batch_size))

# Numero de epocas de entrenamiento


nb_epochs = 5

Entrenamiento de la RN
# Entrenamiento del modelo
history1 = model.fit_generator(train_data_gen,
epochs=nb_epochs,
steps_per_epoch=nb_train_steps,
validation_data=valid_data_gen,
validation_steps=nb_valid_steps,
callbacks=[es,chkpt])

Epoch 1/5
137/137 [==============================] - ETA: 0s - loss: 1.3320 - accura
cy: 0.7108
137/137 [==============================] - 303s 2s/step - loss: 1.3320 - accu
racy: 0.7108 - val_loss: 0.0430 - val_accuracy: 0.9890
Epoch 2/5
137/137 [==============================] - 299s 2s/step - loss: 0.3195 - accu
racy: 0.9188 - val_loss: 0.0501 - val_accuracy: 0.9743
Epoch 3/5
137/137 [==============================] - ETA: 0s - loss: 0.2494 - accuracy:
0.9443INFO:tensorflow:Assets written to: model1\assets
137/137 [==============================] - 317s 2s/step - loss: 0.2494 - accu
racy: 0.9443 - val_loss: 0.0408 - val_accuracy: 0.9853
Epoch 4/5
137/137 [==============================] - ETA: 0s - loss: 0.1439 - accuracy:
0.9672INFO:tensorflow:Assets written to: model1\assets
137/137 [==============================] - 318s 2s/step - loss: 0.1439 - accu
racy: 0.9672 - val_loss: 0.0325 - val_accuracy: 0.9890
Epoch 5/5
137/137 [==============================] - ETA: 0s - loss: 0.1644 - accuracy:
0.9653INFO:tensorflow:Assets written to: model1\assets
137/137 [==============================] - 318s 2s/step - loss: 0.1644 - accu
racy: 0.9653 - val_loss: 0.0121 - val_accuracy: 0.9963

Resultados
# Graficamos la perdida y la precisión

# obtenemos la precisión de entrenamiento y validación del objeto de historial


train_acc = history1.history['accuracy']
valid_acc = history1.history['val_accuracy']

# Obtenemos la perdida
train_loss = history1.history['loss']
valid_loss = history1.history['val_loss']

# Luego el numero de entradas


xvalues = np.arange(len(train_acc))

# Visualizamos
f,ax = plt.subplots(1,2, figsize=(10,5))
ax[0].plot(xvalues, train_loss)
ax[0].plot(xvalues, valid_loss)
ax[0].set_title("Loss curve")
ax[0].set_xlabel("Epoch")
ax[0].set_ylabel("loss")
ax[0].legend(['train', 'validation'])

ax[1].plot(xvalues, train_acc)
ax[1].plot(xvalues, valid_acc)
ax[1].set_title("Accuracy")
ax[1].set_xlabel("Epoch")
ax[1].set_ylabel("accuracy")
ax[1].legend(['train', 'validation'])

plt.show()
Guardamos la topología del modelo y los
pesos de la RN
# ¿Cuál es la pérdida final y la precisión de nuestros datos de validación?
valid_loss, valid_acc = model.evaluate_generator(valid_data_gen,
steps=nb_valid_steps)
print(f"Final validation accuracy: {valid_acc*100:.2f}%")

# Guardamos la topología del modelo


model_json = model.to_json()
with open("monkeys.json", "w") as json_file:
json_file.write(model_json)

# Guardar los pesos del modelo


model.save_weights("monkeys.h5")
Bibliografía
 NAIN 2018 ¿What a CNN sees? https://www.kaggle.com/aakashnain/what-does-a-cnn-see
 Set de datos https://www.kaggle.com/slothkong/10-monkey-species

También podría gustarte