<jupyter_start><jupyter_text>Descenso del gradiente En primer lugar vamos a ver el algoritmo de descenso de gradiente "básico".Supongamos la función $$f(x)=x^2 - 2x + 4$$ de la que queremos encontrar su mínimo.<jupyter_code>f = lambda x: x**2 - 2*x + 4<jupyter_output><empty_output><jupyter_text>En primer lugar calculamos su derivada:<jupyter_code>f_prima = lambda x: 2*x - 2<jupyter_output><empty_output><jupyter_text>Definimos el tamaño de paso (también llamado ratio de aprendizaje) para modificar el mínimo en cada iteración.<jupyter_code>STEP_SIZE = 0.02<jupyter_output><empty_output><jupyter_text>Definimos el algoritmo del descenso de gradiente.$$\theta_{i} = \theta_{i-1} - \alpha*\nabla(J(\theta_{i-1}))$$<jupyter_code>def descenso_gradiente(x):
    return x - STEP_SIZE*(f_prima(x))<jupyter_output><empty_output><jupyter_text>Ahora para un mínimo local elegido al azar podemos *"mejorarlo"* aplicando el descenso de gradiente<jupyter_code>x0 = 3

descenso_gradiente(x0)<jupyter_output><empty_output><jupyter_text>Ahora podemos aplicar el algoritmo de forma iterativa<jupyter_code>minimo_iteraciones = []
N_ITERACIONES = 100

x = 3
for i in range(N_ITERACIONES):
    minimo_iteraciones.append(x)
    x = descenso_gradiente(x)
plt.rcParams["figure.figsize"] = (6, 6)
plt.plot(minimo_iteraciones);<jupyter_output><empty_output><jupyter_text>Vemos que converge al mínimo de la función.  Batch Gradient Descent Cargamos los datos, en esta ocasión vamos a usar una función de `sklearn` llamada [make_regresion](http://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_regression.htmlsklearn.datasets.make_regression) que genera un dataset sintético creado mediante una regresión lineal.<jupyter_code>from sklearn.datasets import make_regression
n_muestras = 1000
n_variables = 2
X, y, coeficientes_objetivo = make_regression(n_samples=n_muestras,
                                              n_features=n_variables,
                                              coef=True)<jupyter_output><empty_output><jupyter_text>Lo bueno de usar este dataset es que sabemos exactamente cuales son los coeficientes $\beta_i$ que definen el modelo lineal que genera la variable objetivo<jupyter_code>coeficientes_objetivo<jupyter_output><empty_output><jupyter_text>Podemos obtener la variable objetivo mediante un producto escalar de los pesos con las variables independientes<jupyter_code>def predecir_batch(coeficientes, X):
    return coeficientes @ X.T
y_predicciones = predecir_batch(coeficientes_objetivo, X)
y_predicciones[:10]<jupyter_output><empty_output><jupyter_text>Comprobamos que dichas predicciones son exactamente iguales que la variable objetivo<jupyter_code>y_predicciones[:10]-y[:10]<jupyter_output><empty_output><jupyter_text>Necesitamos una función de error, en este caso usaremos el **Error Cuadrático Medio** dividido entre 2, para que su derivada no tenga el 2<jupyter_code>def error_batch(y_pred, y_true):
    m = y_pred.shape[0]
    return (np.sum(y_pred - y_true)**2)/2*m<jupyter_output><empty_output><jupyter_text>También necesitamos la derivada de la función de error.<jupyter_code>def derivada_error_batch(y_pred, y_true, x):
    m = y_pred.shape[0]
    return np.sum((y_pred - y_true)*x/m)<jupyter_output><empty_output><jupyter_text>Para empezar el proceso generamos los coeficientes iniciales al azar<jupyter_code>coeficientes = np.random.random((X.shape[1],))
coeficientes
coeficientes_objetivo<jupyter_output><empty_output><jupyter_text>Ahora podemos predecir y calcular el error y la derivada del error<jupyter_code>y_pred = predecir_batch(coeficientes, X)
error_batch(y_pred, y)
derivada_error_batch(y_pred, y, X[:,0])<jupyter_output><empty_output><jupyter_text>ya tenemos todo para implementar el descenso de gradiente batch<jupyter_code>def descenso_gradiente_batch(coeficientes, X, y):
    y_predicciones = predecir_batch(coeficientes, X)
    for i in range(coeficientes.shape[0]):
        coeficientes[i] = coeficientes[i]- STEP_SIZE * derivada_error_batch(y_predicciones, y, X[:,i])
    error = error_batch(y_predicciones, y)
    return coeficientes, error<jupyter_output><empty_output><jupyter_text>Simplemente definimos un número de iteraciones y un tamaño de paso (tambien llamado **ratio de aprendizaje o learning rate**), iteraremos y en cada iteración modificaremos los parámetros del modelo en función del tamaño de paso.<jupyter_code>coeficientes_iteraciones = []
error_iteraciones = []

N_ITERACIONES = 200
STEP_SIZE = 0.02
coeficientes = np.random.random((X.shape[1],))
error = error_batch(coeficientes, X)
for i in range(N_ITERACIONES):
    coeficientes_iteraciones.append(coeficientes.copy())
    error_iteraciones.append(error)
    coeficientes, error = descenso_gradiente_batch(coeficientes, X, y)

coeficientes_iteraciones = np.array(coeficientes_iteraciones)
coeficientes<jupyter_output><empty_output><jupyter_text>Vemos que los coeficientes obtenidos se parecen mucho a los coeficientes objetivo.<jupyter_code>coeficientes_objetivo
plt.plot(error_iteraciones)
plt.title("Evolución del error con el número de iteraciones");
plt.plot(coeficientes_iteraciones[:,0], color="red")
plt.axhline(coeficientes_objetivo[0], color="red", linestyle="dashed")

plt.plot(coeficientes_iteraciones[:,1], color="blue")
plt.axhline(coeficientes_objetivo[1], color="blue", linestyle="dashed")

plt.xlabel("Numero de iteraciones")
plt.ylabel("Valor del coeficiente")

plt.title("Evolución de coeficientes con el número de iteraciones");<jupyter_output><empty_output><jupyter_text>Descenso de gradiente estocástico (SGD)<jupyter_code>def predecir_observacion(coeficientes, x):
    return coeficientes @ x.T
X[0]
predecir_observacion(coeficientes, X[0])
def derivada_error_observacion(y_pred, y_true, x):
    return (y_pred - y_true) * x
derivada_error_observacion(
    predecir_observacion(coeficientes, X[0]), y[0], X[0])<jupyter_output><empty_output><jupyter_text>la definición de la iteracion de sgd es similar a la de batch, simplemente usando el error de observación en vez de el error total<jupyter_code>def descenso_gradiente_estocastico(coeficientes, x, y):
    y_predicciones = predecir_observacion(coeficientes, x)
    for i in range(coeficientes.shape[0]):
        coeficientes[i] = coeficientes[i]- STEP_SIZE * derivada_error_observacion(y_predicciones, y, x[i])
    return coeficientes<jupyter_output><empty_output><jupyter_text>Ahora hacemos solo una iteración (podriamos hacer más), pero la iteramos para cada observación individual.<jupyter_code>coeficientes_iteraciones = []
error_iteraciones = []

STEP_SIZE = 0.02
coeficientes = np.random.random((X.shape[1],))
error = error_batch(coeficientes, X)

indice_aleatorio = np.random.permutation(X.shape[0])
for i in indice_aleatorio:
    error_iteraciones.append(error)
    coeficientes_iteraciones.append(coeficientes.copy())
    
    x_iteracion = X[i]
    y_iteracion = y[i]
    coeficientes = descenso_gradiente_estocastico(coeficientes,
                                                  x_iteracion,
                                                  y_iteracion)
    y_predicciones = predecir_batch(coeficientes, X)
    error = error_batch(y_predicciones, y)
    
coeficientes_iteraciones = np.array(coeficientes_iteraciones)
coeficientes
coeficientes_objetivo
plt.plot(error_iteraciones)
plt.title("Evolución del error con el número de iteraciones");
plt.plot(coeficientes_iteraciones[:,0], color="red")
plt.axhline(coeficientes_objetivo[0], color="red", linestyle="dashed")

plt.plot(coeficientes_iteraciones[:,1], color="blue")
plt.axhline(coeficientes_objetivo[1], color="blue", linestyle="dashed")

plt.xlabel("Numero de observaciones")
plt.ylabel("Valor del coeficiente")

plt.title("Evolución de coeficientes en SGD con el número de observaciones iteradas");<jupyter_output><empty_output><jupyter_text>SGD en scikit-learnScikit-learn tiene estimadores para regresión y clasificación basados en SGD, [SGDRegressor](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDRegressor.htmlsklearn.linear_model.SGDRegressor) y [SGDClassifier](from sklearn.linear_model import SGDClassifier)Los parámetros más importantes para estos estimadores son:- **loss**: La función de pérdidas a utilizar- **learning_rate**: El tamaño de paso, también llamado learning rate (ratio de aprendizaje)- **max_iter**: Número de iteraciones (también llamadas épocas)<jupyter_code>from sklearn.linear_model import SGDClassifier, SGDRegressor
estimador_sgd = SGDRegressor(max_iter=10)
estimador_sgd.fit(X, y)
estimador_sgd.predict(X)[:10]<jupyter_output><empty_output><jupyter_text>Podemos ver los coeficientes que produce el estimador<jupyter_code>estimador_sgd.coef_<jupyter_output><empty_output><jupyter_text>Y vemos que son muy similares a los coeficientes objetivo<jupyter_code>coeficientes_objetivo<jupyter_output><empty_output><jupyter_text>Y por supuesto, podemos hacer validación cruzada como con cualquier estimador de `sklearn`<jupyter_code>from sklearn.model_selection import cross_val_score

cross_val_score(SGDRegressor(max_iter=10), X, y, 
                scoring="neg_mean_squared_error")<jupyter_output>C:\Users\mario\anaconda3\lib\site-packages\sklearn\linear_model\_stochastic_gradient.py:1208: ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit.
  warnings.warn("Maximum number of iteration reached before "
C:\Users\mario\anaconda3\lib\site-packages\sklearn\linear_model\_stochastic_gradient.py:1208: ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit.
  warnings.warn("Maximum number of iteration reached before "
C:\Users\mario\anaconda3\lib\site-packages\sklearn\linear_model\_stochastic_gradient.py:1208: ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit.
  warnings.warn("Maximum number of iteration reached before "
C:\Users\mario\anaconda3\lib\site-packages\sklearn\linear_model\_stochastic_gradient.py:1208: ConvergenceWarning: Maximum number of iteration reached before [...]
{'fraction_non_alphanumeric': 0.0937438326425893, 'fraction_numerical': 0.009275705545687784, 'mean_word_length': 6.501850481125093, 'pattern_counts': {'":': 0, '<': 96, '<?xml version=': 0, '>': 96, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '529852', 'license': 'MIT', 'n_tokens_mistral': 3629, 'n_tokens_neox': 3540, 'n_words': 742, 'path': 'Deep_Learning/1_Descenso_Gradiente.ipynb', 'repo_name': 'mariousm/Data-Science-Course', 'size': '10134'}