<jupyter_start><jupyter_text>Parte 1 : Regresión lineal con una variable _**Autores: <NAME> e <NAME>**_  Carga de datos En la práctica uno se va a trabajar la regresion lineal con una variable a partir de los datos almacenados en un archivo csv que se nos proporciona. Los datos extraidos estan separado en dos columnas y representan los beneficios de una compañia de distribución de comida en distintas ciudades en base a su población. A continuación se importaran todas las librerias necesarias para hacer esta practica, tanto para leer el csv como numpy para el soporte de vectores y matrices.<jupyter_code>from pandas.io.parsers import read_csv
import numpy as np<jupyter_output><empty_output><jupyter_text>Definimos un método que lee y carga los datos de un csv y retorna esta información recopilada. Se dicta que en el archivo no hay información de cabecera (header = None) y que los valores que son devueltos son de tipo flotantes.<jupyter_code>def load_csv(filename):
    values = read_csv(filename, header=None).values
    return values.astype(float)
dataset = load_csv('./datasets/ex1data1.csv')<jupyter_output><empty_output><jupyter_text>Utilizamos el conjunto de datos obtenido del csv con la función especificada previamente y separamos cada columna en vectores diferentes para poder gestionarlos de manera independiente, de este modo de puede usar cada columna para su estudio.<jupyter_code>independent_data = dataset[:,0]
dependent_data = dataset[:,1]<jupyter_output><empty_output><jupyter_text>Visualización simple de dataset  A continuación se generará un plotter con los datos que previamente tratados. Se creará un plotter de cruces enfrentando los valores de beneficios de la empresa estudiada y la población que se nos ha proporcionado en el dataset.<jupyter_code>import matplotlib.pyplot as plt
plt.figure()
plt.plot(independent_data, dependent_data, 'rx')
plt.ylabel('Ingresos en $10.000s')
plt.xlabel('Poblacion de la ciudad en 10.000s')<jupyter_output><empty_output><jupyter_text>Como se observa en el plotter generado, ya es posible hacer un estudio previo sobre los datos, llegando a la conclusión inicial en que en la mayoría de los datos, según aumente el número de ingresos también aumenta la población de la ciudad. Se puede hacer la hipótesis asimilando que el modelo válido sea la regresión lineal.   Método de descenso gradiente Para encontrar la regresión lineal que más se adapte a nuestro dataset, es necesario realizar operaciones básicas de modo que en cada una de las iteraciones se obtenga un resultado más cercano al óptimo. Se necesita definir la función de coste y la función de gradiente de descenso que nos permitira obtener este resultado.Siempre se tiene que tener en cuenta la función que se usará como hipótesis, en nuestro caso se trata de una unica variable y de una recta.   Implementación de la función de hipótesis de la regresión lineal<jupyter_code>def hyphotesis_function(th0, th1, x):
    return th0 + th1*x<jupyter_output><empty_output><jupyter_text>Implementacion de la función de coste de la regresion lineal J($\theta_0$,$\theta_1$)<jupyter_code>def cost_function(fun, th0, th1, m, x, y):
    sum_cost = 0
    for i in range(0, m):
        sum_cost += (fun(th0, th1, x[i]) - y[i])**2
    cost = sum_cost / (2*m)
    return cost<jupyter_output><empty_output><jupyter_text>Implementación de función _gradient descent_<jupyter_code>def gradient_descent(fun, th0, th1, m, x, y, lr=0.01, epochs=1500):
    cost = []
    vc_th0 = []
    vc_th1 = []
    curr_th0 = th0
    curr_th1 = th1
    for i in range(0, epochs):
        new_th0 = curr_th0 - (lr/m)*np.sum([fun(curr_th0, curr_th1, x[j]) - y[j]
                                            for j in range(0, m)])
        new_th1 = curr_th1 - (lr/m)*np.sum([((fun(curr_th0, curr_th1, x[j]) - y[j])*x[j])
                                            for j in range(0, m)])
        curr_th0 = new_th0
        curr_th1 = new_th1
        epoch_cost = cost_function(fun, curr_th0, curr_th1, m, x, y)
        cost += [epoch_cost]
        vc_th0 += [curr_th0]
        vc_th1 += [curr_th1]
    return curr_th0, curr_th1, cost, vc_th0, vc_th1<jupyter_output><empty_output><jupyter_text>A continución se muestran las variables que se han usado para la gestión de la práctica, de este modo se pueden cambiar paramentros de las pruebas de forma comoda.<jupyter_code>th0 = 0
th1 = 0
lr = 0.01
m = len(dataset)<jupyter_output><empty_output><jupyter_text>Resultados obtenidos del estudio Se aplican las funciones definidas previamente y se muestran los resultados para poder tomar unas conclusiones concretas. Se ejecuta la función de descenso del gradiente en nuestra función de hipótesis, además de toda la información necesaria gestionada y obtenida de nuestro conjunto de datos.<jupyter_code>gd_th0, gd_th1, gd_cost, vc_gd_th0, vc_gd_th1 = gradient_descent(hyphotesis_function,
                                                                 th0, th1, m, independent_data,
                                                                 dependent_data, lr)
print ('th0:{}, th1:{}, cost:{}'.format(gd_th0, gd_th1, gd_cost[-1]))<jupyter_output>th0:-3.6302914394043606, th1:1.166362350335582, cost:4.483388256587728<jupyter_text>Como se estudia en los datos mostrados previamente y en la gráfica que se genera posteriormente, se observa que nuestro valor de coste va disminuyendo según se aplica el descenso de gradiente. Es importante que este coste baje de manera constante, una cantidad mayor al principio y después se estabilice. Si esta acción no se esta realizando es síntoma de que alguna acción esta mal.<jupyter_code>plt.plot(gd_cost)
plt.title('Coste por iteración en el método gradient_descent()')
plt.ylabel('Cost')
plt.xlabel('num_epoch')<jupyter_output><empty_output><jupyter_text>Visualización en plotter normal Finalmente se muestra el resultado de manera coherente enfrentando los valores del dataset original y los datos obtenidos de ejecutar nuestra función gradiente sobre esos datos.<jupyter_code>plt.plot(independent_data, dependent_data, 'rx',
         independent_data, hyphotesis_function(gd_th0, gd_th1, independent_data))
plt.title('Recta resultado gradient_descent()')
plt.ylabel('Ingresos en $10.000s')
plt.xlabel('Poblacion de la ciudad en 10.000s')<jupyter_output><empty_output><jupyter_text>El resultado es una recta que se encuentra ajustada lo máximo posible para nuestro conjunto de datos, esta recta tiene la separación mínima entre los puntos y la resta, obtenido gracias a la función descenso de gradiente.  Visualización en plotters 3D A continuación se monstrará el resultado obtenido en gráficas con mas de dos dimensiones. Estas gráficas son importantes para el análisis del descenso del gradiente de forma visual y para el estudio de aprendizajes mas complejos.<jupyter_code>from mpl_toolkits.mplot3d import Axes3D
from matplotlib.pyplot import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
fig = plt.figure()
ax = Axes3D(fig)

X, Y = np.meshgrid(np.arange(-10, 10, 0.1), np.arange(-1, 4, 0.1))
Z = cost_function(hyphotesis_function, X, Y, m, independent_data, dependent_data)

ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, linewidth=0, antialiased=False)
ax.set_xlim(-10, 10)
ax.set_ylim(-1, 4)
plt.xlabel('Th0')
plt.ylabel('Th1')
plt.contour(X, Y, Z ,np.logspace(-2, 3, 20), colors='black')
plt.plot(vc_gd_th0[::150], vc_gd_th1[::150], 'rx')<jupyter_output><empty_output><jupyter_text>Parte 2 : Regresión lineal con dos variables  Carga y normalización datos En esta sección, realizaremos regresión lineal con múltiples variables utilizando un dataset diferente al utilizado en la sección anterior. Por ello, realizamos una nueva carga.<jupyter_code>dataset = load_csv('./datasets/ex1data2.csv')
nfeatures = 2
dataset[:5]
features = dataset[:,:nfeatures] # (47,2)
target = dataset[:,nfeatures] # (47,)
features.shape<jupyter_output><empty_output><jupyter_text>Normalizamos los datos los datos de las variables características ya que las unidades utilizadas en el dataset utilizado varían en función al atributo en cuestión. Esto podría generar problemas por lo que sustituimos cada valor de X por su división entre su diferencia con la media de su columna y la desviación estándar de su columna, teniendo en cuenta que cada columna se corresponde con una variable característica en cuestión. Esto queda expresado por: $$ x_i \leftarrow \frac{x_i - \mu}{\sigma_i} $$<jupyter_code>def normalice(x):
    mu = [np.mean(x[:,i]) for i in range(0,len(x[0]))]
    sigma = [np.std(x[:,i]) for i in range(0,len(x[0]))]
    xnorm = (x - mu)/sigma
    return xnorm, mu, sigma
features_norm, mu, sigma = normalice(features)
ones = np.ones([len(features),len(features[0])+1])
ones[:,1:] = features_norm
features_norm = ones
features_norm[:3]
th = [1,1,1]
lr = 0.01
m = len(dataset)<jupyter_output><empty_output><jupyter_text>Función hipótesis Debido a que tenemos más de una variable característica, en este caso utilizaremos una función definida de la siguiente forma:$$ h_\theta(x)= \theta^T x $$<jupyter_code>hyphotesis_function = lambda th,x : np.dot(x, th)
hyphotesis_function(th, features_norm[0,:])<jupyter_output><empty_output><jupyter_text>Función coste La función de coste (debajo) queda definida por la siguiente expresión:$$ J(\theta) = \frac{1}{2m}(X\theta - \vec{y})^{T}(X\theta-\vec{y}) $$<jupyter_code>def cost_function(fun, th, m, x, y):
    cost = (x.dot(th) - y).T.dot(x.dot(th) - y) / (2*m)
    return cost
cost_function(hyphotesis_function, th, m, features_norm, target)<jupyter_output><empty_output><jupyter_text>_Gradient descent_ La función _gradient descent_ (debajo) actualiza los valores de $\theta_j$ _epochs_ veces siguiendo la siguiente expresión:$$ \theta_j := \theta_j - \alpha\frac{1}{m}\sum_{i=1}^{m}(h_{\theta}(x^{(i)}) - y^{(i)}){x_{j}^{(i)}} $$<jupyter_code>def gradient_descent(fun, th, m, x, y, lr=0.01, epochs=1500):
    cost = []
    curr_th = th
    for e in range(0, epochs):        
        curr_th = curr_th - (1/m)*lr*(np.dot(x.T, fun(curr_th, x) - y))
        epoch_cost = cost_function(fun, curr_th, m, x, y)
        cost += [epoch_cost]
        #print('It: {}, Cost: {}'.format(e + 1, epoch_cost))
    return curr_th, cost   
gd_th, gd_cost = gradient_descent(hyphotesis_function, th,
                                  m, features_norm, target, lr=0.01, epochs=1500)
gd_th<jupyter_output><empty_output><jupyter_text>Mostramos la grafica donde se ve la disminucion de costes:<jupyter_code>plt.figure()
plt.plot(gd_cost)
plt.title('Coste por iteración gradient_descent()')
plt.ylabel('Cost')
plt.xlabel('num_epoch')<jupyter_output><empty_output><jupyter_text>_Normal Equation_ De forma alternativa a _gradient descent_, planteamos la expresión de la ecuación normal que resuelve los valores de $\theta$ siguiendo la siguiente expresión:$$ \theta = (X^{T}X)^{-1}X^{T}\vec{y} $$<jupyter_code>ones = np.ones([len(features),len(features[0])+1])
ones[:,1:] = features
features_wones = ones
features_t = np.transpose(features_wones)
th_norm_eq = np.dot(np.dot(np.linalg.pinv(features_t.dot(features_wones)), features_t), target)
print (th_norm_eq)<jupyter_output>[89597.90954361   139.21067402 -8738.01911255]<jupyter_text>Resultados Para comprobar que el modelo implementado utilizando _gradient descent_ es correcto, realizamos una predicción con los valores obtenidos de $\theta$ con nuestra implementación y los valores obtenidos con la ecuación normal<jupyter_code>x_test = [1, 1650, 3]
x_test[1:] = (np.array(x_test[1:]) - mu ) / sigma
print('Predicción gradient descent:{}, predicción ecuación normal:{}'.
      format(hyphotesis_function(gd_th, x_test),
             hyphotesis_function(th_norm_eq, [1, 1650, 3])))<jupyter_output>Predicción gradient descent:293098.4666760489, predicción ecuación normal:293081.4643349892
{'fraction_non_alphanumeric': 0.09657215404147271, 'fraction_numerical': 0.026914938637325435, 'mean_word_length': 4.817823732151649, 'pattern_counts': {'":': 0, '<': 80, '<?xml version=': 0, '>': 80, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '119447', 'license': 'Apache-2.0', 'n_tokens_mistral': 4273, 'n_tokens_neox': 4140, 'n_words': 1261, 'path': '1 - Regresion Lineal .ipynb', 'repo_name': 'ivanfermena/Machine-Learning-Practice-1', 'size': '11843'}