En este tutorial vamos a entender en qué consiste el escalamiento de los datos cuando construimos un Modelo de Machine Learning.
Además vamos a entender por qué resulta necesario este procedimiento, en qué ocasiones deberíamos escalar nuestros datos y cuándo no.
Al final del tutorial se encuentran los enlaces para descargar el set de datos y el código fuente.
Video
En el canal de YouTube se encuentra el video de este tutorial:
Introducción
Los datos son el insumo principal para la construcción de un Modelo de Machine Learning.
Pero en ocasiones esos datos que usamos tienen diferentes escalas o rangos de valores. Y, dependiendo del tipo de modelo que estemos entrenando, esas diferentes escalas pueden terminar afectando su desempeño.
En este tutorial veremos, a través de tres ejemplos diferentes, cómo el escalamiento de los datos puede llegar a afectar el desempeño de un modelo de Machine Learning.
Y con base en estos ejemplos podremos concluir en qué situaciones es recomendable realizar el escalamiento de los datos antes de entrenar el modelo y cuando no resulta necesario.
El problema a resolver
Para este tutorial usaremos un set de datos que contiene la información asociada al pago (“no default”) e impago (“default”) de un crédito bancario para un total de 5.383 personas.
Construiremos varios modelos de Machine Learning que tomarán como entrada datos como el puntaje crediticio, los ingresos mensuales y el monto del préstamo y con esta información aprenderán a predecir si el usuario pagará a tiempo (“no default”, 0) o se retrasará en sus pagos (“default”, 1).
Y cada modelo será entrenado con datos con y sin escalar, con lo cual podremos ver el efecto que este escalamiento tiene en cada tipo de modelo.
El set de datos
Comencemos importando la librería Pandas para realizar la lectura de los datos haciendo uso del módulo read_csv:
import pandas as pd
# Leer el dataset
RUTA = '/gdrive/MyDrive/02-CODIFICANDOBITS.COM/04-Academia/02-Tutoriales/2023-10-02-tutorial-aleatoriedad-machine-learning/code/'
df = pd.read_csv(RUTA + 'dataset_default.csv')
df
Esta lectura nos arroja el siguiente DataFrame de Pandas:
| | Puntaje crediticio | Ingresos mensuales (USD) | Monto del préstamo (USD) | ¿Default/No default? |
|------|--------------------|--------------------------|--------------------------|----------------------|
| 0 | 5.3 | 6074.6 | 34159.9 | 0 |
| 1 | 4.1 | 4955.0 | 35168.6 | 0 |
| 2 | 4.4 | 4573.6 | 26852.8 | 0 |
| 3 | 3.1 | 6019.0 | 31500.9 | 0 |
| 4 | 5.0 | 1481.1 | 38094.6 | 1 |
| ... | ... | ... | ... | ... |
| 5378 | 2.9 | 2558.5 | 47140.9 | 1 |
| 5379 | 0.7 | 3594.2 | 46654.5 | 1 |
| 5380 | 6.4 | 681.2 | 44423.2 | 1 |
| 5381 | 2.5 | 5172.0 | 24832.9 | 0 |
| 5382 | 7.1 | 3556.1 | 46061.5 | 1 |
5383 rows × 4 columns
Usando el método value_counts() podemos verificar si el set de datos está balanceado, es decir si contiene aproximadamente la misma proporción de datos de una y otra categoría. Esto resulta esencial para poder entrenar los modelos adecuadamente:
df['¿Default/No default?'].value_counts(normalize=True)
Lo cual arroja este resultado:
1 0.507152
0 0.492848
Name: ¿Default/No default?, dtype: float64
Y con esto verificamos que las categorías 1 (“default”) y 0 (“no default”) contienen prácticamente la misma proporción de datos (50% en cada caso).
Veamos además los rangos de valores que alcanzan los datos que usaremos como entrada de cada modelo. Es decir, veamos la escala en la que se encuentra cada una de estas variables.
Para ello iteraremos por las primeras 3 columnas del set de datos (Puntaje crediticio, Ingresos mensuales (USD) y Monto del préstamo (USD)) e imprimiremos en pantalla sus valores máximo y mínimo (obtenidos con los métodos min() y max() de Pandas):
for col in df.columns[0:3]:
# Calcular valores mínimo y máximo de la columna
minimo = df[col].min()
maximo = df[col].max()
print(f'Máximo/mínimo columna "{col}": {maximo:.1f}/{minimo:.1f}')
Lo cual nos arroja estos resultados:
Máximo/mínimo columna "Puntaje crediticio": 7.5/0.0
Máximo/mínimo columna "Ingresos mensuales (USD)": 6997.8/1.4
Máximo/mínimo columna "Monto del préstamo (USD)": 49991.1/20023.4
Y con esto podemos comprobar que cada columna tiene una escala diferente. Esto resultará relevante en un momento cuando construyamos y entrenemos los diferentes modelos de Machine Learning.
Pero antes de esto resulta esencial entender el concepto central de este tutorial: el escalamiento.
¿Qué es el escalamiento de los datos?
De forma sencilla podemos decir que el escalamiento consiste en hacer que cada variable (columna) de nuestro set de datos tenga aproximadamente el mismo rango de valores.
Por ejemplo, veamos la escala o rango de valores que tienen las tres variables con las que alimentaremos los modelos:
Puntaje crediticio: [0.0 – 7.5]Ingresos mensuales: [1.4 – 6997.8]Monto del préstamo: [20023.4 – 49991.1]
Vemos que el Puntaje crediticio tiene un rango de valores de unidades (de 0 a máximo 10), mientras que la columna Ingresos mensuales tiene una escala que va de unidades al orden de miles (6997.8) y el monto del préstamo unidades de decenas de miles. Es evidente que las variables tienen escalas muy diferentes.
Así que después del escalamiento esperaríamos tener por ejemplo:
- Todas las variables entre 0 y 1. En este caso estaríamos hablando de la “normalización” de los datos, un tipo de escalamiento muy usado en Machine Learning.
- O todas las variables con valores promedio de 0 y desviaciones estándar de 1, otra forma de escalar los datos, también muy usada, y que se conoce como “estandarización”.
Muy bien, teniendo claro este sencillo concepto del escalamiento, veamos cómo crear algunas funciones de Python que resultarán útiles para lo que resta del tutorial.
Funciones requeridas
Para introducir los datos a cada modelo necesitaremos dos funciones:
crear_in_out: obtener las entradas (X) y salida (Y) del modelo a partir del set de datos originalcrear_train_val_test: crear los sets de entrenamiento, validación y prueba a partir deXyY
Además, para entrenar y validar cada modelo con los datos escalados necesitaremos crear una función de escalamiento: escalar_datos.
Veamos cómo implementar cada una de estas funciones.
Función crear_in_out
Esta función simplemente toma el set de datos y:
- Extrae las columnas “puntaje crediticio, “Ingresos mensuales (USD)”, “Monto del préstamo (USD)” y las almacena en la variable
X(un arreglo de NumPy) - Extrae la última columna del dataset (que contiene la categoría a la que pertenece cada dato) y la almacena en la variable
Y(que es también un arreglo de NumPy):
def crear_in_out(datos):
'''Obtiene las entradas y salida del modelo a partir
de los datos originales'''
# X: columnas "puntaje crediticio, "Ingresos mensuales (USD)", "Monto del préstamo (USD)"
X = datos.iloc[:,0:3].to_numpy()
# Y: columna "¿Default/No default?"
Y = datos.iloc[:,3].to_numpy()
return X, Y
# Ejecutar la función
X, Y = crear_in_out(df)
X, Y
Tras ejecutar la función obtenemos este resultado:
(array([[5.30000e+00, 6.07460e+03, 3.41599e+04],
[4.10000e+00, 4.95500e+03, 3.51686e+04],
[4.40000e+00, 4.57360e+03, 2.68528e+04],
...,
[6.40000e+00, 6.81200e+02, 4.44232e+04],
[2.50000e+00, 5.17200e+03, 2.48329e+04],
[7.10000e+00, 3.55610e+03, 4.60615e+04]]),
array([0, 0, 0, ..., 1, 0, 1]))
El primer arreglo corresponde a la variable X y tiene un total de 5.383 filas y 3 columnas (correspondientes a las 3 variables que usaremos como entrada al modelo), mientras que el segundo arreglo (unidimensional) corresponde a la variable Y y contiene la categoría (0 o 1) a la que pertenece cada uno de los datos.
Función crear_train_val_test
Esta función nos permitirá crear los sets de entrenamiento, validación y prueba con los que podremos entrenar y validar cada uno de los modelos.
Para lograr esto usaremos el módulo train_test_split de la librería Scikit Learn, el cual nos permitirá realizar la partición en dos etapas:
- En el primer llamado crearemos una partición con una relación 70%/30%, donde el 70% será el set de entrenamiento
- En el segundo llamado tomaremos el 30% de los datos restantes (generados en el paso anterior) y lo dividiremos en dos mitades (15% y 15%) con lo cual obtendremos los sets de validación y prueba.
Al momento de llamar la función train_test_split usaremos el argumento random_state que nos permitirá garantizar la reproducibilidad de la partición de los datos (para que cada vez que ejecutemos el código obtengamos exactamente las mismas particiones).
from sklearn.model_selection import train_test_split
def crear_train_val_test(X,Y):
'''Crear los sets de entrenamiento (70%), validación (15%)
y prueba (15%)'''
xtr, x_resto, ytr, y_resto = train_test_split(X, Y, test_size=0.3,
random_state=22)
xvl, xts, yvl, yts = train_test_split(x_resto, y_resto, test_size=0.5,
random_state=22)
return xtr, ytr, xvl, yvl, xts, yts
# Ejecutar la función
x_train, y_train, x_val, y_val, x_test, y_test = crear_train_val_test(X,Y)
# Verificar
print(x_train.shape, y_train.shape)
print(x_val.shape, y_val.shape)
print(x_test.shape, y_test.shape)
Al crear y ejecutar la función anterior obtenemos este resultado impreso en pantalla, correspondiente a los tamaños de los sets de entrenamiento, validación y prueba obtenidos (entradas y salidas):
(3768, 3) (3768,)
(807, 3) (807,)
(808, 3) (808,)
Función escalar_datos
En este caso haremos simplemente la normalización de los datos. Es decir que tomaremos las variables de entrada al modelo (las columnas de los arreglos x_train, x_val y x_test obtenidas anteriormente) y las llevaremos al rango de 0 a 1.
Para esto usaremos el módulo MinMaxScaler de la librería Scikit-Learn. Este método permite, con muy pocas líneas de código, calcular los valores máximo y mínimo de cada columna del set de entrenamiento y realizar el escalamiento siguiendo esta fórmula:
$$x_{is} = \frac{x_i – x_{i_\text{min}}}{x_{i_\text{max}}-x_{i_\text{min}}}$$
donde:
- $x_i$ hace referencia a cada columna del set de datos
- $x_{i_\text{min}}$ y $x_{i_\text{max}}$ son los valores máximo y mínimo de cada columna
- $x_{is}$ es el dato escalado en el rango de 0 a 1
Para lograr este escalamiento seguiremos estos pasos:
- Crearemos una instancia de
MinMaxScaler, que almacenaremos en la variablescaler. - Calcularemos el máximo y el mínimo de cada columna a partir del set de entrenamiento y luego escalaremos dicho sub-set usando el método
fit_transform() - Usaremos el mínimo y máximo de cada columna del set de entrenamiento, calculados en el paso anterior, para escalar los sets de validación y prueba usando el método
transform()
Veamos entonces la implementación completa de esta función:
# Haremos escalamiento entre 0 y 1 (normalización)
# En otro tutorial veremos otras formas de escalamiento
from sklearn.preprocessing import MinMaxScaler
def escalar_datos(xtr, xvl, xts):
'''Escalar datos de entrada (entrenamiento, validación y prueba)
en el rango de 0 a 1'''
scaler = MinMaxScaler()
# Fit + transform sobre el set de entrenamiento
xtr_s = scaler.fit_transform(xtr)
# Transform sobre prueba y validación
xvl_s = scaler.transform(xvl)
xts_s = scaler.transform(xts)
return xtr_s, xvl_s, xts_s
# Ejecutar la función
x_train_s, x_val_s, x_test_s = escalar_datos(x_train, x_val, x_test)
# Verificar
print(x_train_s.min(axis=0),x_train_s.max(axis=0))
print(x_val_s.min(axis=0),x_val_s.max(axis=0))
print(x_test_s.min(axis=0),x_test_s.max(axis=0))
Tras implementar la función y realizar el escalamiento de los sets de entrenamiento, validación y prueba, podemos verificar que los valores mínimo y máximo de cada set de datos están en el rango de 0 a 1:
[0. 0. 0.] [1. 1. 1.]
[ 0. 0.00147219 -0.00060112] [1. 0.99745583 0.99843041]
[0. 0.00012864 0.00688616] [1. 1. 1.00018702]
Así que en este punto ya tenemos los sets de datos sin escalar (variables x_train, x_val y x_test) y escalados (variables x_train_s, x_val_s y x_test_s).
En este punto es importante aclarar en este tutorial no haremos uso de los sets de prueba (x_test y x_test_s) pues sólo queremos entrenar y validar cada modelo (para lo cual usaremos únicamente los sets x_train/x_val o x_train_s/x_val_s). Sin embargo, es importante tener en cuenta que en un proyecto real deberíamos usar el set de prueba para medir la capacidad de generalización del modelo (de lo cual hablo en detalle en el artículo acerca de los sets de entrenamiento, validación y prueba).
Así que en este punto ya estamos listos para ver cuál es el efecto que el escalamiento de los datos puede tener en diferentes modelos de Machine Learning.
Efecto del escalamiento en diferentes modelos de Machine Learning
En este experimento probaremos con tres tipos de modelos:
- Un árbol de clasificación que, como veremos, es “inmune” al escalamiento
- Un clasificador kNN (k-nearest neighbors) que es sensible al escalamiento
- Una Red Neuronal, que también es sensible al escalamiento
Veamos entonces el efecto que el escalamiento tiene sobre estos tres tipos de modelos.
Árboles de clasificación
En los árboles de clasificación (así como en los árboles de regresión y los bosques aleatorios) el desempeño es el mismo con o sin escalamiento de los datos.
Lo anterior se debe a que al momento de clasificar (o predecir un dato numérico en el caso de la regresión) internamente los árboles/bosques calculan umbrales que están dados en la escala en la que se encuentren los datos.
Así por ejemplo:
- Si los datos están en la escala de -1000 a 1000 tras el entrenamiento por ejemplo el umbral usado por el árbol al momento de clasificar los datos puede ser por ejemplo 200.
- Pero si ahora los datos están escalados en el rango de 0 a 1 entonces el umbral se desplaza en la misma proporción que lo hace el escalamiento. Así que en este caso el árbol usará un umbral igual a 0.6
Entonces, como el umbral se desplaza en la misma proporción que lo hace el escalamiento, todos estos modelos basados en árboles de decisión tendrán el mismo desempeño con o sin escalamiento de los datos.
Para verificar lo anterior entrenemos y validemos un sencillo árbol de clasificación para el caso particular de nuestros datos.
Para ello haremos uso del módulo DecisionTreeClassifier de Scikit-Learn:
from sklearn.tree import DecisionTreeClassifier
def arbol_entrenar_evaluar(x_tr, y_tr, x_vl, y_vl):
arbol = DecisionTreeClassifier(random_state=45)
arbol.fit(x_tr, y_tr)
train_acc = arbol.score(x_tr, y_tr)
val_acc = arbol.score(x_vl, y_vl)
print(f'Exactitud entrenamiento/validación: {100*train_acc:.2f}% / {100*val_acc:.2f}%')
return arbol
En la función anterior, hacemos uso del método fit para entrenar el árbol con el set de entrenamiento (x_tr, y_tr) y luego usamos el método score para obtener el desempeño del árbol entrenado con los sets de entrenamiento y validación.
Estos desempeños corresponden simplemente a la exactitud (es decir el porcentaje de datos clasificados correctamente con respecto a la totalidad de los datos) y son almacenados en las variables train_acc (exactitud para el set de entrenamiento) y val_acc (para el set de validación).
Ahora, haciendo uso de la función que acabamos de crear, podemos simplemente llamarla para obtener el desempeño de este clasificador con y sin el escalamiento de los datos:
# Sin escalamiento
print('Desempeño SIN escalamiento de los datos de entrada: ')
tree = arbol_entrenar_evaluar(x_train, y_train, x_val, y_val)
# Con escalamiento
print('-'*40)
print('Desempeño CON escalamiento de los datos de entrada: ')
tree_s = arbol_entrenar_evaluar(x_train_s, y_train, x_val_s, y_val)
Tras ejecutar el código anterior obtenemos estos resultados:
Desempeño SIN escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 100.00% / 97.15%
----------------------------------------
Desempeño CON escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 100.00% / 97.15%
Y con esto podemos ver que ¡¡¡el desempeño es idéntico en ambos casos!!! Así que podemos concluir que el escalamiento no tiene efecto alguno en el desempeño de los modelos basados en árboles.
Veamos ahora el segundo caso: un modelo basado en k-vecinos más cercanos.
Clasificador kNN (k-nearest neighbors)
En este tipo de clasificadores el desempeño NO necesariamente será el mismo con o sin escalamiento de los datos.
Para entender esto debemos comprender el principio de funcionamiento de este clasificador.
El algoritmo kNN toma el dato a clasificar y calcula su distancia con respecto a la totalidad de los datos y luego determina la categoría con base en las categorías de los k datos más cercanos (con la menor distancia).
Así que si por ejemplo tenemos 200 datos en nuestro set de entrenamiento y queremos determinar la categoría de un dato de prueba, el algoritmo:
- Calcula la distancia entre el dato de prueba y cada uno de los 200 datos con categorías conocidas.
- Organiza las 200 distancias calculadas de manera ascendente (de menor a mayor)
- Toma los datos con las “k” menores distancias (donde “k” es un número entero que elegimos al momento de programar el algoritmo)
- Y asigna la categoría del dato de prueba con base en esos “k” vecinos más cercanos.
Por ejemplo, si el parámetro “k” tiene un valor igual a 5 y los “k” vecinos más cercanos tienen categorías 0, 1, 1, 0 y 0 entonces 3 de los 5 vecinos (es decir la mayoría) tendrán una categoría igual a 0 y por tanto esta será la categoría asignada al dato de prueba por parte del algoritmo.
En el fondo la clave del algoritmo kNN está en la forma coomo se calculan las distancias entre el dato a clasificar y los demás datos del set de entrenamiento.
Por ejemplo, en nuestro caso particular, cada dato estará representado por un vector $x$ que contendrá 3 valores, correspondientes a las variables Puntaje crediticio, Ingresos mensuales (USD) y Monto del préstamo (USD) así que la distancia entre el dato de prueba y cada punto del set de entrenamiento estaría calculada usando esta ecuación:
$$d_{pi}=\sqrt{(x_{1p}-x_{1i})^2 + (x_{2p}-x_{2i})^2 + (x_{3p}-x_{3i})^2}$$
donde:
- $d_{pi}$ es la distancia desde el dato a clasificar ($p$) y cada uno de los datos del set de entrenamiento ($i$)
- $x_1$, $x_2$ y $x_3$ son cada una de las variables introducidas al modelo (puntaje crediticio, ingresos mensuales y monto del préstamo)
Y acá es evidente que si cada característica tiene una escala diferente entonces aquellas características con mayores escalas tendrán un mayor efecto en el valor de la distancia obtenida.
Por ejemplo, en la ecuación anterior, el término $(x_{3p}-x_{3i})^2$ hace referencia a la característica 3 (monto del préstamo), que tiene una escala en el orden de decenas de miles de dólares. Mientras que el término $(x_{1p}-x_{1i})^2$ hace referencia a la característica 1 (puntaje crediticio) que tiene una escala de unidades.
Así que es de esperar que sin ningún tipo de escalamiento el monto del préstamo tenga un mayor efecto en el cálculo de la distancia en comparación con el puntaje crediticio. Mientras que al escalar los datos todas las variables tendrán un peso equivalente al momento de calcular la distancia. Entonces, para este tipo de modelos deberíamos esperar resultados diferentes al momento de usar el escalamiento.
Para verificar este comportamiento creemos un clasificador kNN haciendo uso del módulo KNeighborsClassifier de Scikit-Learn.
La manera de programar el algoritmo es similar a como lo hicimos en el caso del árbol de clasificación:
- Creamos la instancia del clasificador usando
KLNeighborsClassifier. En este caso usaremos un valor de “k” igual a 3 (n_neighbors=3) y almacenaremos esta instancia en la variableknn. - Entrenamos el modelo usando el método
fity el set de entrenamiento - Validamos el modelo con los sets de entrenamiento y prueba usando el método
score. Los resultados de esta validación quedarán almacenados en las variablestrain_accyval_acc.
Esta es la implementación completa:
from sklearn.neighbors import KNeighborsClassifier
def clasificadorKNN_entrenar_evaluar(x_tr, y_tr, x_vl, y_vl):
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(x_tr, y_tr)
train_acc = knn.score(x_tr, y_tr)
val_acc = knn.score(x_vl, y_vl)
print(f'Exactitud entrenamiento/validación: {100*train_acc:.2f}% / {100*val_acc:.2f}%')
return knn
Y ahora llamemos la función anterior para imprimir en pantalla el el desempeño del clasificador sin y con escalamiento:
# Sin escalamiento
print('Desempeño SIN escalamiento de los datos de entrada: ')
knn = clasificadorKNN_entrenar_evaluar(x_train, y_train, x_val, y_val)
# Con escalamiento
print('-'*40)
print('Desempeño CON escalamiento de los datos de entrada: ')
knn_s = clasificadorKNN_entrenar_evaluar(x_train_s, y_train, x_val_s, y_val)
con lo que obtenemos este resultado:
Desempeño SIN escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 98.14% / 97.65%
----------------------------------------
Desempeño CON escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 98.57% / 97.77%
Y vemos que el desempeño cambia ligeramente con el escalamiento. Así que podemos concluir que, en general, el clasificador kNN tendrá un mejor desempeño con datos escalados.
En la práctica este nivel de mejora dependerá de las características que tengan nuestros datos y de los parámetros que escojamos al momento de programar el algoritmo de clasificación.
Y por último, veamos el efecto que tiene el escalamiento en el desempeño de una Red Neuronal.
Red Neuronal
En este caso podremos ver que ocurre algo similar al modelo anterior, es decir que en los modelos basados en Redes Neuronales el desempeño NO necesariamente será el mismo con o sin escalamiento de los datos.
Esto se debe a que las Redes Neuronales durante el entrenamiento aprenden una serie de parámetros (w, b) que están asociados a cada variable de entrada. Si cada variable tiene una escala diferente, cada parámetro tendrá escalas diferentes y habrá unos parámetros que tendrán mayor efecto que otros en las predicciones.
Nuevamente, veamos esto con un ejemplo. Creemos una función para construir una pequeña Red Neuronal para procesar nuestros datos.
Esta Red Neuronal tendrá una capa de entrada con 3 nodos (correspondientes a las 3 características de cada dato a clasificar), una capa oculta con 2 neuronas y una capa de salida con 1 neurona (encargada de predecir la categoría a la que pertenece el dato de entrada).
Para esto haremos uso de la librería Keras y en el tutorial de tensorflow y keras explico en detalle cómo implementar una Red Neuronal usando esta librería:
import tensorflow as tf
def neurona_entrenar_validar(x_tr, y_tr, x_vl, y_vl):
'''Crea y entrena una neurona artificial en Keras, con estas características:
- Tamaño de la entrada: 3x1
- Tamaño de la capa oculta: 2 neuronas
- Tamaño de la salida: 1 neurona (default/no default)'''
# Fijar semilla del generador aleatorio
tf.keras.utils.set_random_seed(45)
# Crear la neurona
modelo = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(3,1)), # Entrada
tf.keras.layers.Dense(2, activation='relu'), # Capa oculta
tf.keras.layers.Dense(1, activation='sigmoid') # Salida
])
# Compilar el modelo
modelo.compile(optimizer='sgd',
loss='BinaryCrossentropy',
metrics = ['accuracy'])
# Entrenarlo
modelo.fit(x_tr, y_tr, epochs=5, verbose=0)
# Validarlo
train_loss, train_acc = modelo.evaluate(x_tr, y_tr, verbose=0)
val_loss, val_acc = modelo.evaluate(x_vl, y_vl, verbose=0)
# Imprimir resultado en pantalla
print(f'Exactitud entrenamiento/validación: {100*train_acc:.2f}% / {100*val_acc:.2f}%')
return modelo
La última parte del código anterior nos permite calcular e imprimir en pantalla la exactitud del modelo entrenado usando los sets de entrenamiento (train_acc) y de validación (val_acc).
Así que lo único que nos resta es llamar la función anterior para evaluar el desempeño de esta Red Neuronal sin y con el escalamiento de los datos:
# Sin escalamiento
print('Desempeño SIN escalamiento de los datos de entrada: ')
red = neurona_entrenar_validar(x_train, y_train, x_val, y_val)
# Con escalamiento
print('-'*40)
print('Desempeño CON escalamiento de los datos de entrada: ')
red_s = neurona_entrenar_validar(x_train_s, y_train, x_val_s, y_val)
Como resultado obtenemos:
Desempeño SIN escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 51.09% / 49.07%
----------------------------------------
Desempeño CON escalamiento de los datos de entrada:
Exactitud entrenamiento/validación: 91.14% / 91.57%
Y en este caso podemos ver una diferencia significativa en los desempeños: obteniendo un desempeño mucho mejor con los datos escalados (cercano al 91%) en comparación con el desempeño obtenido sin el escalamiento (cercano al 50%).
De hecho podemos ver los valores que tienen los coeficientes de la capa oculta en cada Red Neuronal usando el método get_weights():
# Coeficientes Red Neuronal SIN escalamiento
red.layers[1].get_weights()
obteniendo este resultado:
[array([[ 0.4892696 , -0.76216847],
[ 0.5578587 , -31.500042 ],
[ -0.7221511 , -167.76122 ]], dtype=float32),
array([ 0. , -0.005197], dtype=float32)]
Vemos que algunos parámetros tienen valores muy bajos (0.48 o 4.55 por ejemplo) mientras que otros tienen valores extremadamente altos (por ejemplo -167.76).
Sin embargo, si imprimimos en pantalla los parámetros de la Red Neuronal entrenada con datos escalados:
# Coeficientes Red Neuronal CON escalamiento
red_s.layers[1].get_weights()
obtenemos estos valores:
[array([[ 0.41793063, -0.416981 ],
[ 1.0002643 , -0.41641808],
[-0.8074544 , 0.96720004]], dtype=float32),
array([0.27009135, 0.32250693], dtype=float32)]
Y podemos verificar que todos los parámetros tienen valores que se encuentran dentro del mismo rango (entre -1 y 1).
Así que podemos concluir que una Red Neuronal tendrá en general mejor desempeño con datos escalados. Y el nivel de mejoría dependerá de los datos mismos y de las características de la Red.
Enlaces de descarga set de datos y código fuente
Conclusiones
En este tutorial pudimos verificar el efecto que puede llegar a tener el escalamiento de los datos en diferentes tipos de modelos de Machine Learning.
En conclusión podemos decir que los modelos basados en árboles (árboles de clasificación, árboles de decisión, bosques aleatorios, etc.) son los únicos cuyo desempeño es el mismo con o sin el escalamiento de los datos.
Sin embargo, en general los modelos que no son basados en árboles (kNN, Redes Neuronales, Máquinas de Soporte Vectorial, Redes Transformer, Redes Convolucionales y Redes LSTM, entre otros) son sensibles al escalamiento de los datos.
