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:
# 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:
# 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)
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:
# 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()
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)
# 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)
# Veamos algunos ejemplos
for i in range(5):
to_image(i)
Continuando, lo que haremos será definir la red neuronal
# 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,)))
# 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
# 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
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
# 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
# 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:
# 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')
Normalmente realizaremos los siguientes pasos:
Por ejemplo
# 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
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
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
# 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
:
import numpy as np
# Caracteristicas
X = np.array(data[[i for i in range(1,785)]])
X
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
# Variable objetivo
y = np.array(data[0]).reshape(-1,1)
y
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 3Dicha transformación la lograremos mediante OneHotEncoder
(codifica características categóricas como una matriz numérica única). Para ello escribimos
# Ajuste del one hot encoder sobre la variable y
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(handle_unknown='ignore')
enc.fit(y)
OneHotEncoder(handle_unknown='ignore')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
OneHotEncoder(handle_unknown='ignore')
# transformamos la variable y desplegando el array
# como se menciono antes de acuerdo a las categorias
y_enc = enc.transform(y).toarray()
y_enc
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.]])
# Dimension de las variables en cuestion
print(X.shape)
print(y_enc.shape)
(2000, 784) (2000, 4)
Procedemos a entrenar el modelo
# 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
<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
model.evaluate(X, y_enc)
63/63 [==============================] - 0s 1ms/step - loss: 0.1886 - accuracy: 0.9405
[0.18859298527240753, 0.940500020980835]
Podemos ver un ejemplo de predicción:
# Tomamos la primer imagen
X_1 = X[0].reshape(1, 784)
X_1.shape
(1, 784)
# Realizamos la predicion
model.predict(X_1).round()
1/1 [==============================] - 0s 74ms/step
array([[0., 1., 0., 0.]], dtype=float32)
De donde se ha predicho que la categoría es la 1. En efecto:
# La categoria asociada es la uno
data.iloc[0]
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:
Veamos un ejemplo de lo anterior:
# Definimos las caracteristicas numericas
# como una lista
features_list = [tf.feature_column.numeric_column('image', shape=(784, ))]
# 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
# 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.
<tensorflow_estimator.python.estimator.canned.dnn.DNNClassifierV2 at 0x23d4b0b99a0>
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