Repository: Benardi/touvlo

Issue:
Generalize the function utils.gradient_descent 
The function `gradient_descent` should only be aware of the parameters  `X, y, grad, initial_theta,alpha, num_iters`. Any further parameter should be fed to the `grad` function with no impact in the signature of the function `gradient_descent`.

TL; DR: Edit the function `gradient_descent` in the module `utils` to allow a gradient function (grad) of any signature.

Discussion:

Patch:
diff --git a/touvlo/utils.py b/touvlo/utils.py
index fdf47db..ca8531c 100644
--- a/touvlo/utils.py
+++ b/touvlo/utils.py
@@ -35,7 +35,7 @@ def g_grad(x):
 
 
 def gradient_descent(X, y, grad, initial_theta,
-                     alpha, num_iters, _lambda=None):
+                     alpha, num_iters, **kwargs):
     """This function performs parameter optimization via gradient descent.
 
     :param X: Features' dataset plus bias column.
@@ -56,22 +56,12 @@ def gradient_descent(X, y, grad, initial_theta,
     :param num_iters: Number of times the optimization will be performed.
     :type num_iters: int
 
-    :param _lambda: Weight of the penalty term.
-    :type _lambda: float
-
     :returns: Optimized model parameters.
     :rtype: numpy.array
     """
-    if _lambda is not None:
-        theta = copy(initial_theta)
-
-        for _ in range(num_iters):
-            theta = theta - alpha * grad(theta, X, y, _lambda)
-
-    else:
-        theta = copy(initial_theta)
-        for _ in range(num_iters):
-            theta = theta - alpha * grad(theta, X, y)
+    theta = copy(initial_theta)
+    for _ in range(num_iters):
+        theta = theta - alpha * grad(X, y, theta, **kwargs)
 
     return theta
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index d695ead..5adeb9b 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -4,7 +4,7 @@ import pytest
 from numpy import array, cos, sin, exp
 from numpy.testing import assert_allclose
 
-from touvlo.utils import numerical_grad, g_grad
+from touvlo.utils import numerical_grad, g_grad, gradient_descent
 
 
 class TestLogisticRegression:
@@ -82,3 +82,45 @@ class TestLogisticRegression:
         assert_allclose(g_grad(z),
                         [0.196612, 0.235004, 0.25, 0.235004, 0.196612],
                         rtol=0, atol=0.001, equal_nan=False)
+
+    def test_gradient_descent1(self, err):
+        def grad(X, y, theta):
+            m = len(y)
+            grad = (1 / m) * (X.T).dot(X.dot(theta) - y)
+            return grad
+
+        X = array([[0, 1, 2], [-1, 5, 3], [2, 0, 1]])
+        initial_theta = array([[0], [0], [0]])
+        y = array([[0.3], [1.2], [0.5]])
+        num_iters = 3
+        alpha = 1
+
+        assert_allclose(array([[-46.415], [276.248], [192.204]]),
+                        gradient_descent(X, y, grad, initial_theta,
+                                         alpha, num_iters),
+                        rtol=0, atol=0.001, equal_nan=False)
+
+    def test_gradient_descent2(self, err):
+        def grad(X, y, theta, schleem, plumbus, wubba, lubba):
+            m = len(y)
+            grad = (schleem / (m * wubba))
+            grad = grad * (X.T).dot(X.dot(theta) - y)
+            grad = grad + plumbus / (2 * lubba)
+            return grad
+
+        X = array([[0, 1, 2], [-1, 5, 3], [2, 0, 1]])
+        initial_theta = array([[0], [0], [0]])
+        y = array([[0.3], [1.2], [0.5]])
+        num_iters = 5
+        plumbus = 0.8
+        schleem = 0.6
+        wubba = 3.4
+        lubba = 2.7
+        alpha = 0.01
+
+        assert_allclose(array([[-0.0078777], [0.0106179], [0.0060865]]),
+                        gradient_descent(X, y, grad, initial_theta,
+                                         alpha, num_iters, lubba=lubba,
+                                         schleem=schleem, wubba=wubba,
+                                         plumbus=plumbus),
+                        rtol=0, atol=0.001, equal_nan=False)