Análisis de Consumidores de un Mall

Introducción

Acontinuación se realizará una regresión para predecir el puntaje de los clientes en un mall.

Para el análisis se considerará:

  • Un breve análisis exploratorio de los datos.

  • Dividir los datos en conjunto de entrenamiento y validación (80%-20%).

  • Con esto se realizarán diversos algoritmos de regresión mediante ternsorflow, donde el entrenamiento se realizará utilizando validación cruzada aleatoria (en cada algoritmo hay una descripción del procedimiento utilizado).

  • El conjunto de validación se utilizará para comparar el RMSE de cada método.

Análisis exploratorio y preparación de los datos

En el dataset se encuentra en el siguiente archivo 'Mall_Customers.csv' que contiene la siguiente información:

  • CustomerID: Identificador único para cada cliente.

  • Gender: Género de cada cliente (Másculino/Femenino).

  • Age: Edad del cliente.

  • Annual Income (k$): Ingresos anuales del cliente.

  • Spending Score (1-100): Puntaje asignado por el centro comercial en función del comportamiento y gasto del cliente.

In [1]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import requests
from sklearn.preprocessing import normalize
import os.path
import csv
In [2]:
data_file_name = "Mall-Customers.csv"
data = []
with open(data_file_name, newline='') as csvfile:
    csv_reader = csv.reader(csvfile)
    data_header = next(csv_reader)
    for row in csv_reader:
        data.append(row)
In [3]:
pd.DataFrame(data, columns=data_header).head(10)
Out[3]:
CustomerID Gender Age Annual Income (k$) Spending Score (1-100)
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40
5 6 Female 22 17 76
6 7 Female 35 18 6
7 8 Female 23 18 94
8 9 Male 64 19 3
9 10 Female 30 19 72
In [4]:
# Transformación de datos de string a float.
# Binarización del género (Male = 1, Female = 0).
x_vals = np.array([[float(x[0]), x[1]=='Male', float(x[2]), float(x[3])] for x in data]) # Male=1, Female=0
y_vals = np.array([float(x[4]) for x in data])
pd.DataFrame(x_vals, columns=data_header[0:4]).head(10)
Out[4]:
CustomerID Gender Age Annual Income (k$)
0 1.0 1.0 19.0 15.0
1 2.0 1.0 21.0 15.0
2 3.0 0.0 20.0 16.0
3 4.0 0.0 23.0 16.0
4 5.0 0.0 31.0 17.0
5 6.0 0.0 22.0 17.0
6 7.0 0.0 35.0 18.0
7 8.0 0.0 23.0 18.0
8 9.0 1.0 64.0 19.0
9 10.0 0.0 30.0 19.0
In [5]:
# Gráfico del Puntaje vs cada Predictor
fig, axs = plt.subplots(4, 1, sharex=False, figsize=[10,10])
fig.suptitle(str(data_header[4])+' vs Predictores')
fig.subplots_adjust(hspace=0.4, wspace=0.4, top=0.92)
color = ['blue', 'red', 'green', 'orange']

for n1 in range(4):
    axs[n1].scatter(x_vals[:,n1], y_vals, color=color[n1])
    axs[n1].set_title(data_header[n1])
        
for ax in fig.get_axes():
    ax.label_outer()

Para los análisis posteriores no se considerará la variable CustomerID, ya que no contiene información útil.

Acontinuación se separán los datos en conjuntos de prueba y entrenamiento. Posteriormente se normalizarán los datos en función de los valores del conjunto de entrenamiento.

In [6]:
f_train_test = 0.8
np.random.seed(123)
train_idx = np.random.choice(len(x_vals), round(len(x_vals)*f_train_test), replace=False)
test_idx = np.array(list(set(range(len(x_vals)))-set(train_idx)))
# Se elimina CustomerID del conjunto de datos.
x_vals = np.array([[x[1]=='Male', float(x[2]), float(x[3])] for x in data]) # Male=1, Female=0
In [7]:
x_vals_train = x_vals[train_idx,]
x_vals_test = x_vals[test_idx]
y_vals_train = y_vals[train_idx]
y_vals_test = y_vals[test_idx]
In [8]:
# función de normalización por columna.
def normalize_cols(m, col_min = np.array([None]), col_max = np.array([None])):
    if not col_min[0]:
        col_min = m.min(axis=0)
    if not col_max[0]:
        col_max = m.max(axis=0)
    return(m-col_min)/(col_max-col_min), col_min, col_max
In [9]:
x_vals_train, train_min, train_max = np.nan_to_num(normalize_cols(x_vals_train))
x_vals_test,_,_ = np.nan_to_num(normalize_cols(x_vals_test, train_min, train_max))
pd.DataFrame(x_vals_train, columns=data_header[1:4]).head(10)
Out[9]:
Gender Age Annual Income (k$)
0 0.0 0.596154 0.221311
1 1.0 0.423077 0.459016
2 0.0 0.230769 0.155738
3 1.0 0.307692 0.516393
4 0.0 0.326923 0.065574
5 1.0 0.596154 0.385246
6 1.0 0.326923 0.639344
7 1.0 0.788462 0.229508
8 1.0 0.019231 0.540984
9 1.0 0.307692 0.516393
In [10]:
# Gráfico de Edad vs Predictores (queda fuera Spending Score)

predictor = 1
fig, axs = plt.subplots(3, 1, sharex=False, figsize=[10,10])
fig.suptitle(str(data_header[predictor+1])+' vs Predictores')
fig.subplots_adjust(hspace=0.4, wspace=0.4, top=0.92)
color = ['blue', 'red', 'green', 'orange']

for n1 in range(3):
    axs[n1].scatter(x_vals_train[:,n1], 
                    #y_vals_train, 
                    x_vals_train[:,predictor], 
                    color=color[n1])
    axs[n1].set_title(data_header[n1+1])
        
for ax in fig.get_axes():
    ax.label_outer()

Modelos de regresión

Se utilizarán los siguientes modelos de regresión para predecir Spending Score:

  • Regresión lineal.
  • SVM lineal.
  • KNN.
  • Red Neuronal.

Regresión lineal

In [11]:
# Declaración de Sesion y fijado de semillas aleatorias.
session = tf.Session()
tf.set_random_seed(123)
np.random.seed(123)

# Hiperparámetros
epochs = 8000
batch_size = int(np.round(x_vals_train.shape[0]*0.7)) # 70% del conjunto de entrenamiento
learning_rate = 0.01
elastic_param1 = tf.constant(1.0)
elastic_param2 = tf.constant(0.01)

# Declaración de placeholders
x_data = tf.placeholder(shape=[None, x_vals_train.shape[1]], dtype = tf.float32)
y_target = tf.placeholder(shape = [None, 1], dtype = tf.float32)

# Declaración de variables
A_ml = tf.Variable(tf.random_normal(shape = [x_vals_train.shape[1],1]))
b_ml = tf.Variable(tf.random_normal(shape = [1,1]))

# Declaración de predicción
y_pred = tf.add(tf.matmul(x_data, A_ml), b_ml)
WARNING:tensorflow:From C:\Users\HP\Anaconda3\lib\site-packages\tensorflow\python\framework\op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
In [12]:
# Función de pérdidas
l1_a_loss = tf.reduce_mean(tf.abs(A_ml))
l2_a_loss = tf.reduce_mean(tf.square(A_ml))
e1_term = tf.multiply(elastic_param1, l1_a_loss)
e2_term = tf.multiply(elastic_param2, l2_a_loss)
loss = tf.expand_dims(tf.sqrt(tf.add(tf.add(tf.reduce_mean(tf.square(y_target- y_pred)),e1_term), e2_term)), 0)
In [13]:
# Inicialización de varibles
init = tf.global_variables_initializer()
session.run(init)
In [14]:
# Optimizador
my_opt = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
train_step = my_opt.minimize(loss)

Entrenamiento de algoritmo.

El entrenamiento se lleva a cabo de la siguiente manera:

  • Toma una muestra aleatoria de tamaño batch_size para entrenar el algoritmo.
  • Con los datos restantes del conjunto de entrenamiento se crea un conjunto de test, donde se evalúan los resultados del entrenamiento.
  • Con el conjunto de validación (totalmente independiente) se evalúa el desempeño del algoritmo y el RMSE para poder comparar con otros métodos.
In [15]:
loss_vec_train = []
loss_vec_test = []
loss_vec_val = []

for i in range(epochs):
    # carga las observaciones aleatorias de entrenamiento y pruebas
    rand_idx = np.random.choice(len(x_vals_train), size=batch_size)
    rand_test_idx = np.array(list(set(range(len(x_vals_train)))-set(rand_idx)))
    
    # separa conjunto de prueba aleatorio
    rand_x = x_vals_train[rand_idx]
    rand_y = np.transpose([y_vals_train[rand_idx]])
    
    # separa conjunto de prueba aleatorio
    rand_test_x = x_vals_train[rand_test_idx]
    rand_test_y = np.transpose([y_vals_train[rand_test_idx]])
    
    # entrenamiento iteracion i
    session.run(train_step, feed_dict={x_data : rand_x, y_target: rand_y})
    
    # perdidas del conjunto de entrenamiento
    temp_loss_train = session.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})[0]
    loss_vec_train.append(temp_loss_train)

    # perdidas del conjunto de prueba
    temp_loss_test = session.run(loss, feed_dict={x_data: rand_test_x, y_target: rand_test_y})[0]
    loss_vec_test.append(temp_loss_test)
    
    # perdidas del conjunto de validación   
    temp_loss_val = session.run(loss, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})[0]
    loss_vec_val.append(temp_loss_val)
    
    if(i+1)%np.round(epochs/30)==0:
        acc_and_loss = [i+1, temp_loss_train, temp_loss_test, temp_loss_val]
        print("Paso #{}, Train Loss {:.3f}, Test Loss {:.3f}, Valid Loss {:.3f}".format(*acc_and_loss))
Paso #267, Train Loss 51.701, Test Loss 57.301, Valid Loss 49.157
Paso #534, Train Loss 53.707, Test Loss 50.030, Valid Loss 46.180
Paso #801, Train Loss 46.381, Test Loss 49.906, Valid Loss 43.356
Paso #1068, Train Loss 47.185, Test Loss 46.779, Valid Loss 40.672
Paso #1335, Train Loss 49.097, Test Loss 42.575, Valid Loss 38.161
Paso #1602, Train Loss 42.197, Test Loss 42.357, Valid Loss 35.831
Paso #1869, Train Loss 38.703, Test Loss 42.323, Valid Loss 33.696
Paso #2136, Train Loss 35.989, Test Loss 41.195, Valid Loss 31.779
Paso #2403, Train Loss 37.433, Test Loss 38.264, Valid Loss 30.081
Paso #2670, Train Loss 33.377, Test Loss 37.576, Valid Loss 28.601
Paso #2937, Train Loss 31.853, Test Loss 36.889, Valid Loss 27.325
Paso #3204, Train Loss 35.087, Test Loss 33.719, Valid Loss 26.229
Paso #3471, Train Loss 33.767, Test Loss 33.547, Valid Loss 25.337
Paso #3738, Train Loss 33.281, Test Loss 31.893, Valid Loss 24.606
Paso #4005, Train Loss 33.235, Test Loss 30.859, Valid Loss 24.019
Paso #4272, Train Loss 31.075, Test Loss 31.341, Valid Loss 23.558
Paso #4539, Train Loss 31.380, Test Loss 28.826, Valid Loss 23.199
Paso #4806, Train Loss 30.338, Test Loss 30.230, Valid Loss 22.907
Paso #5073, Train Loss 29.729, Test Loss 31.578, Valid Loss 22.674
Paso #5340, Train Loss 30.960, Test Loss 29.666, Valid Loss 22.499
Paso #5607, Train Loss 28.297, Test Loss 30.521, Valid Loss 22.362
Paso #5874, Train Loss 30.937, Test Loss 28.517, Valid Loss 22.250
Paso #6141, Train Loss 28.963, Test Loss 29.201, Valid Loss 22.164
Paso #6408, Train Loss 30.411, Test Loss 28.228, Valid Loss 22.091
Paso #6675, Train Loss 29.565, Test Loss 28.663, Valid Loss 22.031
Paso #6942, Train Loss 30.036, Test Loss 28.346, Valid Loss 21.982
Paso #7209, Train Loss 31.116, Test Loss 26.016, Valid Loss 21.938
Paso #7476, Train Loss 30.246, Test Loss 27.556, Valid Loss 21.900
Paso #7743, Train Loss 28.118, Test Loss 29.201, Valid Loss 21.864
In [16]:
# Ecuación regresión lineal
slope = session.run(A_ml)
[[intercept]] = session.run(b_ml)
print("Modelo de regresión: \n y="+str(slope[0][0])+"x1+"+str(slope[1][0])+"x2+"+str(slope[2][0])+"x3+"+str(intercept))
x_vals_norm,_,_= np.nan_to_num(normalize_cols(x_vals, train_min, train_max))
pred_test_rl = session.run(y_pred, feed_dict={x_data: x_vals_test})
Modelo de regresión: 
 y=9.429519x1+6.4527164x2+11.720885x3+36.09238
In [17]:
plt.figure(figsize=(14,6))
plt.plot(loss_vec_train, 'b-.', label="Pérdidas en Entrenamiento")
plt.plot(loss_vec_test, 'r--', label="Pérdidas en Pruebas")
plt.plot(loss_vec_val, 'k-', label="Pérdidas en Validación")
plt.title("Regresión Lineal (ElasticNet)")
plt.xlabel("Iteración")
plt.ylabel("Pérdida")
plt.legend(loc="upper right")
plt.show()
In [18]:
plt.figure(figsize=(14,6))
plt.plot(abs(y_vals_test - pred_test_rl[0]), 'r-.',label="Error de Validación")
plt.axhline(y=np.sqrt(np.mean(np.square(y_vals_test - pred_test_rl[0]))), color='b', linestyle='-', 
            label = "RMSE "+str(np.round(np.sqrt(np.mean(np.square(y_vals_test - pred_test_rl[0]))),3)))
plt.title("Spending Score (Regresión Lineal)")
plt.xlabel("Iteración")
plt.ylabel("RMSE")
plt.legend(loc="upper right")
plt.show()
RMSE = []
RMSE.append(np.sqrt(np.mean(np.square(y_vals_test - pred_test_rl[0]))))
session.close()

SVM Lineal

In [19]:
# Declaración de Sesion y fijado de semillas aleatorias.
session = tf.Session()
tf.set_random_seed(123)
np.random.seed(123)

# Hiperparámetros
epochs = 8000
batch_size = int(np.round(x_vals_train.shape[0]*0.7))
learning_rate = 0.01
epsilon = tf.constant([0.3])

# Declaración de placeholders
x_data = tf.placeholder(shape=[None, x_vals_train.shape[1]], dtype = tf.float32)
y_target = tf.placeholder(shape = [None, 1], dtype = tf.float32)

# Declaración de variables
A_svm_l = tf.Variable(tf.random_normal(shape = [x_vals_train.shape[1],1]))
b_svm_l = tf.Variable(tf.random_normal(shape = [1,1]))

# Declaración de predicción
y_pred = tf.add(tf.matmul(x_data, A_svm_l), b_svm_l)
In [20]:
# Función de pérdida
loss_svm_l = tf.expand_dims(tf.reduce_mean(tf.maximum(0.0, tf.subtract(tf.abs(tf.subtract(y_pred, y_target)), epsilon))),0)
In [21]:
# Inicialización de variables
init = tf.global_variables_initializer()
session.run(init)
In [22]:
# Optimizador
my_opt = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
train_step = my_opt.minimize(loss_svm_l)

Entrenamiento de algoritmo.

El entrenamiento se lleva a cabo de la siguiente manera:

  • Toma una muestra aleatoria de tamaño batch_size para entrenar el algoritmo.
  • Con los datos restantes del conjunto de entrenamiento se crea un conjunto de test, donde se evalúan los resultados del entrenamiento.
  • Con el conjunto de validación (totalmente independiente) se evalúa el desempeño del algoritmo y el RMSE para poder comparar con otros métodos.
In [23]:
loss_vec_train = []
loss_vec_test = []
loss_vec_val = []

for i in range(epochs):
    # carga las observaciones aleatorias de entrenamiento y pruebas
    rand_idx = np.random.choice(len(x_vals_train), size=batch_size)
    rand_test_idx = np.array(list(set(range(len(x_vals_train)))-set(rand_idx)))
    
    # separa conjunto de prueba aleatorio
    rand_x = x_vals_train[rand_idx]
    rand_y = np.transpose([y_vals_train[rand_idx]])
    
    # separa conjunto de prueba aleatorio
    rand_test_x = x_vals_train[rand_test_idx]
    rand_test_y = np.transpose([y_vals_train[rand_test_idx]])
    
    # entrenamiento iteracion i
    session.run(train_step, feed_dict={x_data : rand_x, y_target: rand_y})
    
    # perdidas del conjunto de entrenamiento
    temp_loss_train = session.run(loss_svm_l, feed_dict={x_data: rand_x, y_target: rand_y})[0]
    loss_vec_train.append(temp_loss_train)

    # perdidas del conjunto de prueba
    temp_loss_test = session.run(loss_svm_l, feed_dict={x_data: rand_test_x, y_target: rand_test_y})[0]
    loss_vec_test.append(temp_loss_test)
    
    # perdidas del conjunto de validación   
    temp_loss_val = session.run(loss_svm_l, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})[0]
    loss_vec_val.append(temp_loss_val)
    
    if(i+1)%np.round(epochs/30)==0:
        acc_and_loss = [i+1, temp_loss_train, temp_loss_test, temp_loss_val]
        print("Paso #{}, Train Loss {:.3f}, Test Loss {:.3f}, Valid Loss {:.3f}".format(*acc_and_loss))
Paso #267, Train Loss 41.757, Test Loss 49.106, Valid Loss 42.216
Paso #534, Train Loss 43.512, Test Loss 41.176, Valid Loss 38.992
Paso #801, Train Loss 36.842, Test Loss 41.357, Valid Loss 36.239
Paso #1068, Train Loss 39.473, Test Loss 37.812, Valid Loss 33.934
Paso #1335, Train Loss 40.915, Test Loss 35.291, Valid Loss 31.972
Paso #1602, Train Loss 35.045, Test Loss 35.106, Valid Loss 30.195
Paso #1869, Train Loss 33.010, Test Loss 34.816, Valid Loss 28.534
Paso #2136, Train Loss 30.441, Test Loss 34.321, Valid Loss 27.010
Paso #2403, Train Loss 31.634, Test Loss 32.236, Valid Loss 25.615
Paso #2670, Train Loss 28.231, Test Loss 31.217, Valid Loss 24.302
Paso #2937, Train Loss 26.551, Test Loss 31.190, Valid Loss 23.089
Paso #3204, Train Loss 30.199, Test Loss 28.168, Valid Loss 21.961
Paso #3471, Train Loss 27.885, Test Loss 28.787, Valid Loss 20.972
Paso #3738, Train Loss 27.599, Test Loss 26.900, Valid Loss 20.092
Paso #4005, Train Loss 27.576, Test Loss 25.626, Valid Loss 19.434
Paso #4272, Train Loss 26.027, Test Loss 25.773, Valid Loss 18.975
Paso #4539, Train Loss 25.816, Test Loss 23.163, Valid Loss 18.601
Paso #4806, Train Loss 24.838, Test Loss 25.035, Valid Loss 18.270
Paso #5073, Train Loss 23.924, Test Loss 26.359, Valid Loss 17.963
Paso #5340, Train Loss 26.304, Test Loss 23.121, Valid Loss 17.733
Paso #5607, Train Loss 22.808, Test Loss 25.437, Valid Loss 17.521
Paso #5874, Train Loss 24.701, Test Loss 23.544, Valid Loss 17.324
Paso #6141, Train Loss 23.278, Test Loss 23.831, Valid Loss 17.179
Paso #6408, Train Loss 24.894, Test Loss 22.920, Valid Loss 17.057
Paso #6675, Train Loss 24.368, Test Loss 23.077, Valid Loss 16.952
Paso #6942, Train Loss 24.132, Test Loss 23.316, Valid Loss 16.858
Paso #7209, Train Loss 26.449, Test Loss 20.092, Valid Loss 16.769
Paso #7476, Train Loss 24.780, Test Loss 22.267, Valid Loss 16.687
Paso #7743, Train Loss 22.525, Test Loss 24.003, Valid Loss 16.608
In [24]:
pred_test_svm = session.run(y_pred, feed_dict={x_data: x_vals_test})
In [25]:
plt.figure(figsize=(14,6))
plt.plot(loss_vec_train, 'b-.', label="Pérdidas en Entrenamiento")
plt.plot(loss_vec_test, 'r--', label="Pérdidas en Pruebas")
plt.plot(loss_vec_val, 'k-', label="Pérdidas en Validación")
plt.title("SVM lineal")
plt.xlabel("Iteración")
plt.ylabel("Pérdida")
plt.legend(loc="upper right")
plt.show()
In [26]:
plt.figure(figsize=(14,6))
plt.plot(abs(y_vals_test - pred_test_svm[0]), 'r-.',label="Error de Validación")
plt.axhline(y=np.sqrt(np.mean(np.square(y_vals_test - pred_test_svm[0]))), color='b', linestyle='-', 
            label = "RMSE "+str(np.round(np.sqrt(np.mean(np.square(y_vals_test - pred_test_svm[0]))),3)))
plt.title("Spending Score (SVM lineal)")
plt.xlabel("Iteración")
plt.ylabel("RMSE")
plt.legend(loc="upper right")
plt.show()
RMSE.append(np.sqrt(np.mean(np.square(y_vals_test - pred_test_svm[0]))))
session.close()

KNN

In [27]:
# Declaración de Sesion y fijado de semillas aleatorias.
session = tf.Session()
tf.set_random_seed(123)
np.random.seed(123)

# Hiperparámetros
k = 7
learning_rate = 0.01
#batch_size = int(np.ceil(len(x_vals_train)/2))
batch_size = len(x_vals_test)

# Declaración de placeholders
x_data_train = tf.placeholder(shape=[batch_size, x_vals_train.shape[1]], dtype=tf.float32)
x_data_test = tf.placeholder(shape=[batch_size, x_vals_train.shape[1]], dtype=tf.float32)
y_target_train = tf.placeholder(shape = [None, 1], dtype=tf.float32)
y_target_test = tf.placeholder(shape=[None, 1], dtype=tf.float32)

# Cálculo de matriz de ponderación de distancias
weight_matrix_2 = x_vals_train.std(0)
weight_matrix = tf.cast(tf.diag(weight_matrix_2), dtype=tf.float32)

# Cálculo de distancia
subs_term = tf.subtract(x_data_train, tf.expand_dims(x_data_test, 1))
first_prod = tf.matmul(subs_term, tf.tile(tf.expand_dims(weight_matrix,0), [batch_size, 1, 1]))
second_prod = tf.matmul(first_prod, tf.transpose(subs_term, perm=[0,2,1]))

distance = tf.sqrt(tf.matrix_diag_part(second_prod))

# Toma los k datos con menor distancia al valor dado y cálcula su valor promedio
top_k_xvals, top_k_idx = tf.nn.top_k(tf.negative(distance), k = k)
x_sums = tf.expand_dims(tf.reduce_sum(top_k_xvals,1),1)
x_sums_rep = tf.matmul(x_sums, tf.ones([1,k], tf.float32))
x_vals_w = tf.expand_dims(tf.divide(top_k_xvals, x_sums_rep),1) # -> wi = di / \sum(dj)
top_k_yvals = tf.gather(y_target_train, top_k_idx)

# Declaración de predicción
prediction = tf.squeeze(tf.matmul(x_vals_w, top_k_yvals),axis=[1]) ## \sum w_i y_i

# función de pérdida
mse = tf.reduce_mean(tf.abs(tf.subtract(prediction, y_target_test)))

Entrenamiento de algoritmo.

Este algotrimo no se entrena, los resultados se calculan a partir de los promedios de los k vecinos más cercanos.

Para evaluar el desempeño de este algoritmo se tomarán 100 muestras aleatorias del conjunto de entrenamiento de tamaño batch_size. Con estos datos se calcularán los valores de Speding Score del resto del conjunto de entrenamiento fuera del batch_size.

En cada pasada se revisará el desempeño del conjunto de validación (pérdida en cada iteración y RMSE).

In [28]:
num_loops = 100
loss_vec_train = []
loss_vec_val = []

for i in range(num_loops):
    # carga las observaciones aleatorias de entrenamiento y pruebas
    rand_idx = np.random.choice(len(x_vals_train), size=batch_size)
    rand_test_idx = np.array(list(set(range(len(x_vals_train)))-set(rand_idx)))
    rand_test_idx = np.random.choice(rand_test_idx, size=batch_size)
    
    # separa conjunto de entrenamiento aleatorio
    rand_x = x_vals_train[rand_idx]
    rand_y = np.transpose([y_vals_train[rand_idx]])
    
    # separa conjunto de prueba aleatorio
    rand_test_x = x_vals_train[rand_test_idx]
    rand_test_y = np.transpose([y_vals_train[rand_test_idx]])
    
    # separa conjunto de validacion aleatorio
    rand_idx_val = np.random.choice(len(x_vals_test), size=batch_size)
    #rand_val_x = x_vals_test[rand_idx_val]
    rand_val_x = x_vals_test
    #rand_val_y = np.transpose([y_vals_test[rand_idx_val]])
    rand_val_y = np.transpose([y_vals_test])
        
    temp_loss_train = session.run(mse, feed_dict={x_data_train: rand_x,
                                                  x_data_test: rand_test_x,
                                                  y_target_train: rand_y,
                                                  y_target_test: rand_test_y})
    loss_vec_train.append(temp_loss_train)
    
    temp_loss_val = session.run(mse, feed_dict={x_data_train: rand_x,
                                                  x_data_test: rand_val_x,
                                                  y_target_train: rand_y,
                                                  y_target_test: rand_val_y})
    
    pred_test_knn = session.run(prediction, feed_dict={x_data_train: rand_x, x_data_test: rand_val_x,
                                         y_target_train: rand_y, y_target_test: rand_val_y})
    
    loss_vec_val.append(temp_loss_val)
    if(i+1)%np.round(num_loops/10)==0:
        acc_and_loss = [i+1, temp_loss_train]
        print("Paso #{}, Loss {:.3f}".format(*acc_and_loss))
Paso #10, Loss 22.737
Paso #20, Loss 22.084
Paso #30, Loss 22.666
Paso #40, Loss 21.611
Paso #50, Loss 26.576
Paso #60, Loss 23.994
Paso #70, Loss 24.906
Paso #80, Loss 29.167
Paso #90, Loss 26.571
Paso #100, Loss 24.776
In [29]:
plt.figure(figsize=(14,6))
plt.plot(loss_vec_train, 'b-.', label="Pérdidas en Entrenamiento")
#plt.plot(loss_vec_test, 'r--', label="Pérdidas en Pruebas")
plt.plot(loss_vec_val, 'k-', label="Pérdidas en Validación")
plt.title("KNN k=7 (100 iteraciones)")
plt.xlabel("Iteración")
plt.ylabel("Pérdida")
plt.legend(loc="upper right")
plt.show()
In [30]:
plt.figure(figsize=(14,6))
plt.plot(abs(rand_val_y - pred_test_knn[0]), 'r-.',label="Error de Validación")
plt.axhline(y=np.sqrt(np.mean(np.square(rand_val_y - pred_test_knn[0]))), color='b', linestyle='-', 
            label = "RMSE "+str(np.round(np.sqrt(np.mean(np.square(rand_val_y - pred_test_knn[0]))),3)))
plt.title("Spending Score (KNN)")
plt.xlabel("Iteración")
plt.ylabel("RMSE")
plt.legend(loc="upper right")
plt.show()
RMSE.append(np.sqrt(np.mean(np.square(rand_val_y - pred_test_knn[0]))))
session.close()

Red Neuronal

In [31]:
# Declaración de Sesion y fijado de semillas aleatorias.
session = tf.Session()
tf.set_random_seed(123)
np.random.seed(123)

# Hiperparámetros
epochs = 2500
batch_size = int(np.round(x_vals_train.shape[0]*0.7))
learning_rate = 0.001
layers = [x_vals_train.shape[1], 45, 25, 11]
sd = 0.1

# Declaración de funciones weight y bias (variables)
def init_weight(shape, st_dev):
    weight = tf.Variable(tf.random_normal(shape = shape, stddev=st_dev))
    return weight

def init_bias(shape, st_dev):
    bias = tf.Variable(tf.random_normal(shape = shape, stddev=st_dev))
    return bias

# Definición de función de conexión
def full_connected(input_layer, weights, biases):
    layer = tf.add(tf.matmul(input_layer, weights), biases)
    layer = tf.nn.relu(layer)
    return(layer)
In [32]:
# Arquitectura de la red
## Capa 1
w1 = init_weight(shape=[layers[0], layers[1]], st_dev=sd)
b1 = init_bias(shape=[layers[1]], st_dev=sd)
layer1 = full_connected(x_data, w1, b1) 
## Capa 2
w2 = init_weight(shape=[layers[1], layers[2]], st_dev=sd)
b2 = init_bias(shape=[layers[2]], st_dev=sd)
layer2 = full_connected(layer1, w2, b2) 
## Capa 3
w3 = init_weight(shape=[layers[2], layers[3]], st_dev=sd)
b3 = init_bias(shape=[layers[3]], st_dev=sd)
layer3 = full_connected(layer2, w3, b3) 
## Capa 4
w4 = init_weight(shape=[layers[3],1], st_dev=sd)
b4 = init_bias(shape=[1], st_dev=sd)
layer4 = full_connected(layer3, w4, b4) 

# función de pérdida y entrenamiento
loss_nn = tf.reduce_mean(tf.abs(y_target-layer4))
my_optim = tf.train.GradientDescentOptimizer(learning_rate)
train_step = my_optim.minimize(loss_nn)

# Inicialización de variables
init = tf.global_variables_initializer()
session.run(init)

Entrenamiento de algoritmo.

El entrenamiento se lleva a cabo de la siguiente manera:

  • Toma una muestra aleatoria de tamaño batch_size para entrenar el algoritmo.
  • Con los datos restantes del conjunto de entrenamiento se crea un conjunto de test, donde se evalúan los resultados del entrenamiento.
  • Con el conjunto de validación (totalmente independiente) se evalúa el desempeño del algoritmo y el RMSE para poder comparar con otros métodos.
In [33]:
loss_vec_train = []
loss_vec_test = []
loss_vec_val = []

for i in range(epochs):
    # carga las observaciones aleatorias de entrenamiento y pruebas
    rand_idx = np.random.choice(len(x_vals_train), size=batch_size)
    rand_test_idx = np.array(list(set(range(len(x_vals_train)))-set(rand_idx)))
    
    # separa conjunto de prueba aleatorio
    rand_x = x_vals_train[rand_idx]
    rand_y = np.transpose([y_vals_train[rand_idx]])
    
    # separa conjunto de prueba aleatorio
    rand_test_x = x_vals_train[rand_test_idx]
    rand_test_y = np.transpose([y_vals_train[rand_test_idx]])
    
    # entrenamiento iteracion i
    session.run(train_step, feed_dict={x_data : rand_x, y_target: rand_y})
    
    # perdidas del conjunto de entrenamiento
    temp_loss_train = session.run(loss_nn, feed_dict={x_data: rand_x, y_target: rand_y})
    loss_vec_train.append(temp_loss_train)

    # perdidas del conjunto de prueba
    temp_loss_test = session.run(loss_nn, feed_dict={x_data: rand_test_x, y_target: rand_test_y})
    loss_vec_test.append(temp_loss_test)
    
    # perdidas del conjunto de validación   
    temp_loss_val = session.run(loss_nn, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})
    loss_vec_val.append(temp_loss_val)
    
    if(i+1)%np.round(epochs/30)==0:
        acc_and_loss = [i+1, temp_loss_train, temp_loss_test, temp_loss_val]
        print("Paso #{}, Train Loss {:.3f}, Test Loss {:.3f}, Valid Loss {:.3f}".format(*acc_and_loss))

# Predicción conjunto validación        
pred_test_nn = [x for x in session.run(layer4, feed_dict={x_data:x_vals_test})]
Paso #83, Train Loss 54.408, Test Loss 49.641, Valid Loss 47.756
Paso #166, Train Loss 49.955, Test Loss 52.100, Valid Loss 47.660
Paso #249, Train Loss 50.437, Test Loss 50.102, Valid Loss 47.562
Paso #332, Train Loss 51.060, Test Loss 49.410, Valid Loss 47.462
Paso #415, Train Loss 48.715, Test Loss 50.714, Valid Loss 47.358
Paso #498, Train Loss 53.759, Test Loss 49.752, Valid Loss 47.250
Paso #581, Train Loss 49.030, Test Loss 49.329, Valid Loss 47.137
Paso #664, Train Loss 50.267, Test Loss 50.661, Valid Loss 47.017
Paso #747, Train Loss 51.782, Test Loss 47.305, Valid Loss 46.889
Paso #830, Train Loss 46.830, Test Loss 55.067, Valid Loss 46.755
Paso #913, Train Loss 48.015, Test Loss 49.715, Valid Loss 46.608
Paso #996, Train Loss 53.230, Test Loss 46.420, Valid Loss 46.447
Paso #1079, Train Loss 52.645, Test Loss 49.243, Valid Loss 46.266
Paso #1162, Train Loss 48.314, Test Loss 49.550, Valid Loss 46.058
Paso #1245, Train Loss 46.641, Test Loss 50.210, Valid Loss 45.818
Paso #1328, Train Loss 49.409, Test Loss 47.767, Valid Loss 45.531
Paso #1411, Train Loss 42.223, Test Loss 50.219, Valid Loss 45.177
Paso #1494, Train Loss 45.380, Test Loss 52.969, Valid Loss 44.730
Paso #1577, Train Loss 43.336, Test Loss 51.420, Valid Loss 44.146
Paso #1660, Train Loss 47.769, Test Loss 45.007, Valid Loss 43.364
Paso #1743, Train Loss 43.455, Test Loss 44.635, Valid Loss 42.242
Paso #1826, Train Loss 44.585, Test Loss 43.080, Valid Loss 40.505
Paso #1909, Train Loss 39.561, Test Loss 42.570, Valid Loss 37.433
Paso #1992, Train Loss 35.116, Test Loss 36.430, Valid Loss 31.603
Paso #2075, Train Loss 31.176, Test Loss 24.859, Valid Loss 21.767
Paso #2158, Train Loss 26.107, Test Loss 21.127, Valid Loss 15.957
Paso #2241, Train Loss 24.440, Test Loss 19.408, Valid Loss 16.148
Paso #2324, Train Loss 25.683, Test Loss 20.659, Valid Loss 15.949
Paso #2407, Train Loss 22.824, Test Loss 22.932, Valid Loss 15.957
Paso #2490, Train Loss 19.757, Test Loss 24.287, Valid Loss 15.876
In [34]:
plt.figure(figsize=(14,6))
plt.plot(loss_vec_train, 'b-.', label="Pérdidas en Entrenamiento")
plt.plot(loss_vec_test, 'r--', label="Pérdidas en Pruebas")
plt.plot(loss_vec_val, 'k-', label="Pérdidas en Validación")
plt.title("Red Neuronal")
plt.xlabel("Iteración")
plt.ylabel("Pérdida")
plt.legend(loc="upper right")
plt.show()
In [35]:
plt.figure(figsize=(14,6))
plt.plot(abs(y_vals_test - pred_test_nn[0]), 'r-.',label="Error de Validación")
plt.axhline(y=np.sqrt(np.mean(np.square(y_vals_test - pred_test_nn[0]))), color='b', linestyle='-', 
            label = "RMSE "+str(np.round(np.sqrt(np.mean(np.square(y_vals_test - pred_test_nn[0]))),3)))
plt.title("Spending Score (Red Neuronal)")
plt.xlabel("Iteración")
plt.ylabel("Error")
plt.legend(loc="upper right")
plt.show()
RMSE.append(np.sqrt(np.mean(np.square(y_vals_test - pred_test_nn[0]))))
session.close()

Resultados

In [36]:
pd.DataFrame(np.round(RMSE, 3), 
             columns=["RMSE"], 
             index=["Regresión Lineal", " SVM-Lineal", "KNN (k=7)", "Red Neuronal"])
Out[36]:
RMSE
Regresión Lineal 23.936
SVM-Lineal 24.772
KNN (k=7) 20.907
Red Neuronal 20.911

Los resultados indican que los algortimos con mejor desempeño son KNN y Red Neuronal. Esto se debe a que la relación entre los datos es no-lineal.

El mejor resultado lo entrega KNN, pero tiene la desventaja que depende de los datos selecionados y no es mejorable, ya que toma el promedio de los datos con menores distancias.

Por otro lado la red neuronal, aunque presenta un resultado levemente peor, se puede mejorar optimizando los hiperparámetros (learning_rate y configuración de la red).

link: https://github.com/desareca/Proyectos_tensorflow/tree/master/Analisis-Consmidores