Redes neuronales¶


Clase9: Redes neuronales¶


Autor: Luis Fernando Apáez Álvarez

Contenido¶

  • Algunas consideraciones
  • Definiendo una red neuronal con keras
  • Entrenamiento y validación

Algunas consideraciones ¶

Sabemos que la inicialización de los pesos es de manera aleatoria, y de hecho hay algunos caminos específicos para hacerlo. Por ejemplo, inicializaremos los pesos de manera aleatoria que siguen una distribución normal:

In [1]:
# Enfoque de bajo nivel
import tensorflow as tf

# Definimos 500x500 variable aleatoria normal
pesos1 = tf.Variable(tf.random.normal([500,500]))

# Definimos 500x500 variable aletoria normal truncada
# la cual descarta sorteos muy grandes o muy pequenios
pesos2 = tf.Variable(tf.random.truncated_normal([500,500]))

También, podemos utilizar un enfoque de alto nivel inicializando un capa densa:

In [2]:
# capa densa con el inicializador por defecto
dense1 = tf.keras.layers.Dense(32, activation='relu')

# capa densa inicializada con ceros
dense2 = tf.keras.layers.Dense(32, activation='relu', kernel_initializer='zeros')

Viendo así algunas alternativas para la inicialización de los pesos desde un punto de vista de alto y bajo nivel.

Otro problema con el cual nos podemos encontrar a la hora de programar una red neuronal es el sobreajuste. Una manera de mitigarlo es utilizando droput, la cual es una operación que aleatoriamente "suelta" los pesos conectados con ciertos nodos en una capa durante el proceso del entrenamiento. Lo anterior hará que el rendimiento de nuestra red sea mejor. Por ejemplo


import numpy as np

# Definimos los datos de entrada
inputs = np.array(data, np.float32)

# capa de entrada
dense1 = tf.keras.layers.Dense(32, activation='relu')(inputs)

# capa 2
dense2 = tf.keras.layers.Dense(16, activation='relu')(dense1)

# aplicamos la operacion dropout
# donde descartaremos aleatoriamente los pesos conectados
# al 25% de los nodos de la segunga capa
dropout1 = tf.keras.layers.Dropout(0.25)(dense2)

# Capa de salida
output = tf.keras.layers.Dense(1, activation='sigmoid')(dropout1)

Definiendo una red neuronal con keras ¶

Para trabajar con redes neuronales utilizando keras, estaremos utilizando el conjunto de datos MNIST, con lo cual buscaremos clasificar cuatro letras del lenguaje de señas: a, b, c y d. Para ello comenzaremos:

In [3]:
# importacion necesaria
import pandas as pd
from tensorflow import keras

# Definimos el modelo secuencial
model = keras.Sequential()

# Cargamos los datos
data =  pd.read_csv('slmnist.csv', header=None)
data.head()
Out[3]:
0 1 2 3 4 5 6 7 8 9 ... 775 776 777 778 779 780 781 782 783 784
0 1 142 143 146 148 149 149 149 150 151 ... 0 15 55 63 37 61 77 65 38 23
1 0 141 142 144 145 147 149 150 151 152 ... 173 179 179 180 181 181 182 182 183 183
2 1 156 157 160 162 164 166 169 171 171 ... 181 197 195 193 193 191 192 198 193 182
3 3 63 26 65 86 97 106 117 123 128 ... 175 179 180 182 183 183 184 185 185 185
4 1 156 160 164 168 172 175 178 180 182 ... 108 107 106 110 111 108 108 102 84 70

5 rows × 785 columns

Veamos ahora cómo se ven los datos almacenado en el dataframe anterior, donde cada fila es la representación de una imagen. Para ver dicha imagen haremos lo siguiente (antes de ello, se recomienda ver primero el siguiente apunte para aprender como convertir arrays en imágenes mediante arrays numpy: Link)

In [4]:
# importaciones necesarias
import numpy as np
from PIL import Image

# Definimos una funcion
def to_image(fila):
    # dada una fila del dataframe data, convertimos
    # todos los valores de dicha fila en un array numpy
    array = np.array(data.iloc[fila][1:])
    # Cambiamos la forma del array y especificamos el tipo de dato
    # de entero de 8 bits (para asi poder convertir el array a imagen)
    # Donde colocaremos la dimension de los arrays en 28x28, puesto que
    # las imagenes que usaremos son de 28 pixeles por 28 pixeles
    array = np.array(array.reshape((28, 28)), dtype='uint8')
    # Convertimos el array a imagen
    img = Image.fromarray(array)
    # Cambiamos la dimension de la imagen
    img = img.resize((128,128))
    # mostramos la imagen
    display(img)
In [5]:
# Veamos algunos ejemplos
for i in range(5):
    to_image(i)

Continuando, lo que haremos será definir la red neuronal

In [6]:
# Definimos la primer capa, la cual es una capa densa con 16 nodos. Ademas
# especificamos la forma (shape) de los datos de entrada
model.add(keras.layers.Dense(16, activation='relu', input_shape=(28*28,)))
In [7]:
# definimos una capa oculta con 8 nodos
model.add(keras.layers.Dense(8, activation='relu'))

# Especificamos la capa de salida con 4 nodos
model.add(keras.layers.Dense(4, activation='softmax'))

donde se ha especificado 4 nodos de salida dado que nos interesa clasificar las imágenes que representen la letra a, b, c y d (4 letras). Además, la función de activación para la capa de salida es softmax, la cual nos sirve para problemas de clasificación multiclase. Procedemos después a compilar el modelo

In [8]:
#           optimizador | funcion de perdida para problemas
#                       | de clasificacion con mas de dos 
#                       | clases
model.compile('adam',   loss='categorical_crossentropy')

# Resumen dle modelo
model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_2 (Dense)             (None, 16)                12560     
                                                                 
 dense_3 (Dense)             (None, 8)                 136       
                                                                 
 dense_4 (Dense)             (None, 4)                 36        
                                                                 
=================================================================
Total params: 12,732
Trainable params: 12,732
Non-trainable params: 0
_________________________________________________________________

Por otro lado, consideremos el caso en el que deseamos entrenar dos modelos de manera conjunta para predecir el mismo objetivo

rr1.PNG

Para ello utilizaremos la api funcional. Consideremos el ejemplo de las imágenes, y supongamos que tenemos un conjunto de datos con 10 características

In [9]:
# capa de entrada para el modelo 1
model1_inputs = tf.keras.Input(shape=(28*28,))

# capa de entrada para el modelo 2
model2_inputs = tf.keras.Input(shape=(10,))

y queremos utilizar ambos para predecir la clase de la imagen, pero restringir cómo interactúan en el modelo conjunto. Después

In [10]:
# Capa 1 para el modelo 1
model1_layer1 = tf.keras.layers.Dense(12, activation='relu')(model1_inputs)

# Capa 2 para el modelo 1
model1_layer2 = tf.keras.layers.Dense(4, activation='softmax')(model1_layer1)

# Capa 1 para el modelo 2
model2_layer1 = tf.keras.layers.Dense(8, activation='relu')(model2_inputs)

# Capa 2 para el modelo 2
model2_layer2 = tf.keras.layers.Dense(4, activation='softmax')(model2_layer1)

Luego, definimos la capa donde uniremos los modelos:

In [11]:
# union de los modelos 1 y 2
merged = tf.keras.layers.add([model1_layer2, model2_layer2])

# definimos el modelo funcional
#                                  entradas de cada red       salida conjunta
model = tf.keras.Model(inputs=[model1_inputs, model2_inputs], outputs=merged)

# compilamos el modelo
model.compile('adam', loss='categorical_crossentropy')

Entrenamiento y validación ¶

Normalmente realizaremos los siguientes pasos:

  1. Cargar y limpiar los datos.
  2. Definición del modelo a ocupar (especificando la arquitectura).
  3. Entrenamiento y validación del modelo.
  4. Evaluación.

Por ejemplo

In [12]:
# importacion necesaria
import pandas as pd
import tensorflow as tf

# Definimos el modelo secuencial
model = tf.keras.Sequential()

# Cargamos los datos
data =  pd.read_csv('slmnist.csv', header=None)

Luego, agregamos una capa densa al modelo con 16 nodos

In [13]:
model.add(tf.keras.layers.Dense(16, activation='relu', input_shape=(784, )))

Proseguimos definiendo la capa de salida con 4 nodos y una función de activación softmax

In [14]:
model.add(tf.keras.layers.Dense(4, activation='softmax'))

Después, compilamos el modelo utilizando una función de pérdida de entropía cruzada y el optimizador Adam

In [15]:
# agregamos el parametro de metrics para observar la precision
# a la hora del entrenamiento
model.compile('adam', loss='categorical_crossentropy', metrics=['accuracy'])

Definimos las variables X y y:

In [16]:
import numpy as np

# Caracteristicas
X = np.array(data[[i for i in range(1,785)]])
X
Out[16]:
array([[142, 143, 146, ...,  65,  38,  23],
       [141, 142, 144, ..., 182, 183, 183],
       [156, 157, 160, ..., 198, 193, 182],
       ...,
       [177, 179, 180, ..., 239, 233, 240],
       [121, 129, 138, ..., 197, 198, 211],
       [178, 178, 178, ..., 195, 194, 192]], dtype=int64)

Depues, si consideramos la variable y como sigue

In [17]:
# Variable objetivo
y = np.array(data[0]).reshape(-1,1)
y
Out[17]:
array([[1],
       [0],
       [1],
       ...,
       [2],
       [3],
       [2]], dtype=int64)

Tendremos un problema pues la clasificación será multiclase, para lo cual nuestra variable objetivo debe ser de la forma

  • [1, 0 , 0, 0]: si la imagen corresponde a la categoría 0
  • [0, 1 , 0, 0]: si la imagen corresponde a la categoría 1
  • [0, 0 , 1, 0]: si la imagen corresponde a la categoría 2
  • [0, 0 , 0, 1]: si la imagen corresponde a la categoría 3

Dicha transformación la lograremos mediante OneHotEncoder (codifica características categóricas como una matriz numérica única). Para ello escribimos

In [18]:
# Ajuste del one hot encoder sobre la variable y
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(handle_unknown='ignore')
enc.fit(y)
Out[18]:
OneHotEncoder(handle_unknown='ignore')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
OneHotEncoder(handle_unknown='ignore')
In [19]:
# transformamos la variable y desplegando el array 
# como se menciono antes de acuerdo a las categorias
y_enc = enc.transform(y).toarray()
y_enc
Out[19]:
array([[0., 1., 0., 0.],
       [1., 0., 0., 0.],
       [0., 1., 0., 0.],
       ...,
       [0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [0., 0., 1., 0.]])
In [20]:
# Dimension de las variables en cuestion
print(X.shape)
print(y_enc.shape)
(2000, 784)
(2000, 4)

Procedemos a entrenar el modelo

In [21]:
# el entrenamiento se realizara a 10 epocas dividiendo los datos
# en 20% para validacion y 80% para entrenamiento
model.fit(X, y_enc, epochs=10, validation_split = 0.2)
Epoch 1/10
50/50 [==============================] - 1s 5ms/step - loss: 17.7123 - accuracy: 0.4831 - val_loss: 9.8926 - val_accuracy: 0.5500
Epoch 2/10
50/50 [==============================] - 0s 2ms/step - loss: 5.2275 - accuracy: 0.6612 - val_loss: 3.3443 - val_accuracy: 0.7750
Epoch 3/10
50/50 [==============================] - 0s 2ms/step - loss: 2.2208 - accuracy: 0.7688 - val_loss: 1.6247 - val_accuracy: 0.7875
Epoch 4/10
50/50 [==============================] - 0s 2ms/step - loss: 2.0342 - accuracy: 0.7650 - val_loss: 1.2827 - val_accuracy: 0.7300
Epoch 5/10
50/50 [==============================] - 0s 2ms/step - loss: 1.4851 - accuracy: 0.7844 - val_loss: 0.6928 - val_accuracy: 0.8850
Epoch 6/10
50/50 [==============================] - 0s 2ms/step - loss: 0.9016 - accuracy: 0.8081 - val_loss: 1.2589 - val_accuracy: 0.7275
Epoch 7/10
50/50 [==============================] - 0s 2ms/step - loss: 0.5466 - accuracy: 0.8594 - val_loss: 0.6014 - val_accuracy: 0.8300
Epoch 8/10
50/50 [==============================] - 0s 2ms/step - loss: 0.3467 - accuracy: 0.9075 - val_loss: 0.4901 - val_accuracy: 0.8250
Epoch 9/10
50/50 [==============================] - 0s 2ms/step - loss: 0.8045 - accuracy: 0.8438 - val_loss: 0.2965 - val_accuracy: 0.8925
Epoch 10/10
50/50 [==============================] - 0s 4ms/step - loss: 0.3681 - accuracy: 0.8900 - val_loss: 0.2251 - val_accuracy: 0.9250
Out[21]:
<keras.callbacks.History at 0x23d4b132850>

Vemos que la precisión del modelo ha aumentado rápidamente llegando en la época 10 a una precisión del 99.5%. Tenemos también que el riesgo del sobreajuste es poco debido a que realizamos el entrenamiento agregando un porcentaje de los datos para la validación. Finalmente evaluamos

In [22]:
model.evaluate(X, y_enc)
63/63 [==============================] - 0s 1ms/step - loss: 0.1886 - accuracy: 0.9405
Out[22]:
[0.18859298527240753, 0.940500020980835]

Podemos ver un ejemplo de predicción:

In [23]:
# Tomamos la primer imagen
X_1 = X[0].reshape(1, 784)
X_1.shape
Out[23]:
(1, 784)
In [24]:
# Realizamos la predicion

model.predict(X_1).round()
1/1 [==============================] - 0s 74ms/step
Out[24]:
array([[0., 1., 0., 0.]], dtype=float32)

De donde se ha predicho que la categoría es la 1. En efecto:

In [25]:
# La categoria asociada es la uno
data.iloc[0]
Out[25]:
0        1
1      142
2      143
3      146
4      148
      ... 
780     61
781     77
782     65
783     38
784     23
Name: 0, Length: 785, dtype: int64

El ejemplo anterior es muy sencillo, pues recordemos que debemos dividir los datos para el entrenmaiento y la prueba, lo cual no hicimos.

Por otro lado, trabajaremos con la API de estimadores, la cual es un submódulo de Tensorflow de alto nivel. Dicho submódulo es menos flexible, no obstante, aplica un conjunto de mejores prácticas al imponer restricciones en la arquitectura y el entrenamiento del modelo.

Además, la ventaja de utilizar dicho submódulo es que se pueden especificar, entrenar, evaluar e implementar modelos con menos líneas de código. Adicionalmente, hay modelos prefabricados que ya vienen incluidos, donde solo bastará con modificar algunos valores de parámetros.

Para las especificaciones del modelo haremos:

  • Definimos las características.
  • Cargamos y transformamos los datos dentro de una función. La salida de dicha función será un objeto diccionario de características y etiquetas.
  • Se define un estimador. Donde hay ya estimadores prefabricados, o también podemos definir estimadores personalizados.
  • Entrenamiento del modelo.

Veamos un ejemplo de lo anterior:

In [26]:
# Definimos las caracteristicas numericas
# como una lista
features_list = [tf.feature_column.numeric_column('image', shape=(784, ))]
In [27]:
# Definimos una funcion para cargar y transformar los datos

def input_fn():
    # Definimos un diccionario de las caracteristicas
    features = {'image': X}
    # Definimos las etiquetas
    labels = y
    # Retornamos
    return features, labels
In [28]:
# Definimos el estimador que queremos entrenar
# hidden_units: cantidad de nodos por capa
# DNNClassifier: red neuronal profunda para la clasificacion
# DNNRegressor: red neuronal profunda para prediccion de valores continuos
model0 = tf.estimator.DNNClassifier(feature_columns=features_list,
                                    hidden_units=[32, 16, 8],
                                    n_classes=4)

# Entrenamiento del modelo
model0.train(input_fn, steps=20)
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_
INFO:tensorflow:Using config: {'_model_dir': 'C:\\Users\\usuario\\AppData\\Local\\Temp\\tmpi7fn7f9_', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_experimental_max_worker_delay_secs': None, '_session_creation_timeout_secs': 7200, '_checkpoint_save_graph_def': True, '_service': None, '_cluster_spec': ClusterSpec({}), '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
WARNING:tensorflow:From C:\ProgramData\Miniconda3\lib\site-packages\tensorflow\python\training\training_util.py:396: Variable.initialized_value (from tensorflow.python.ops.variables) is deprecated and will be removed in a future version.
Instructions for updating:
Use Variable.read_value. Variables in 2.X are initialized automatically both in eager and graph (inside tf.defun) contexts.
INFO:tensorflow:Calling model_fn.
WARNING:tensorflow:From C:\ProgramData\Miniconda3\lib\site-packages\keras\optimizers\optimizer_v2\adagrad.py:86: calling Constant.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 0...
INFO:tensorflow:Saving checkpoints for 0 into C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt.
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-0.data-00000-of-00001
INFO:tensorflow:200
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-0.index
INFO:tensorflow:200
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-0.meta
INFO:tensorflow:12900
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 0...
INFO:tensorflow:loss = 133.41812, step = 0
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 20...
INFO:tensorflow:Saving checkpoints for 20 into C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt.
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-20.data-00000-of-00001
INFO:tensorflow:200
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-20.index
INFO:tensorflow:200
INFO:tensorflow:C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-20.meta
INFO:tensorflow:12900
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 20...
INFO:tensorflow:Loss for final step: 2.0765338.
Out[28]:
<tensorflow_estimator.python.estimator.canned.dnn.DNNClassifierV2 at 0x23d4b0b99a0>
In [30]:
result = model0.evaluate(input_fn, steps=20)

for key, value in result.items():
    print(key, ":", value)
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2022-11-14T10:40:26
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-20
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Evaluation [2/20]
INFO:tensorflow:Evaluation [4/20]
INFO:tensorflow:Evaluation [6/20]
INFO:tensorflow:Evaluation [8/20]
INFO:tensorflow:Evaluation [10/20]
INFO:tensorflow:Evaluation [12/20]
INFO:tensorflow:Evaluation [14/20]
INFO:tensorflow:Evaluation [16/20]
INFO:tensorflow:Evaluation [18/20]
INFO:tensorflow:Evaluation [20/20]
INFO:tensorflow:Inference Time : 0.48490s
INFO:tensorflow:Finished evaluation at 2022-11-14-10:40:26
INFO:tensorflow:Saving dict for global step 20: accuracy = 0.3705, average_loss = 2.0157855, global_step = 20, loss = 2.0157855
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 20: C:\Users\usuario\AppData\Local\Temp\tmpi7fn7f9_\model.ckpt-20
accuracy : 0.3705
average_loss : 2.0157855
loss : 2.0157855
global_step : 20