MLS.1.b Gradient Descent in Linear Regression
Tags: gradientdescent, python, linearregression, machinelearning
Published: 2019-11-12 06:13:28

# Gradient Descent in Linear Regression

Gradient Descent is a first order optimization algorithm to find the minimum of a function.It finds the minimum (local) of a function by moving along the direction of steep descent (downwards). This helps us to update the parameters of the model (weights and bias) more accurately. 

To get to the local minima we can't just go directly to the point (on a graph plot). We need to descend in smaller steps and check for minima and take another step to the direction of descent until we get our desired local minimum.

The small steps mentioned above is called as learning rate. If the learning rate is very small the precision is more but is very time consuming. And large learning rate may lead us to miss the minimum (overshooting). The theory is to use the learning rate at higher rate until the slope of curve starts to decrease ad once it  starts decreasing, we start using smaller learning rates(Less time and More Precision).

![Gradient descending over a slope](https://thepracticaldev.s3.amazonaws.com/i/q2iywvf7yjidq4gtht97.png)
<center><small>Gradient descending over a slope</small></center>

The cost function helps us to evaluate how good our model is functioning or predicting. Its a _**loss function**_. It has its own curve and parameters (weights and bias). The slope of the curve helps us to update our parameters accordingly. The less the cost more the predicted probability.

In the training phase, we are finding the y_train value to find how much is the value is deviating from the given output. Then we calculate the cost error in the given second phase by using the cost error formula.

<center><strong>
<code>y_train = w * x<sub>i</sub> + b</code>

<code>cost = (1/N) * ∑(y<sub>i</sub> − y_train)<sup>2</sup>  {i from 1 to n}</code>
</strong></center>


``` python
for i in range(n_iters):
    #Training phase 
    y_train = np.dot(X, self.weights) + self.bias
    
    #Cost error calculating Phase
    cost = (1 / n_samples) * np.sum((y_train - y)**2)
    costs.append(cost) 
```

Now we update the weights and bias to decrease our error by doing 

``` python
    #Updating the weight and bias derivatives
    Delta_w = (2 / n_samples) * np.dot(X.T, (y_hat - y))
    Delta_b = (2 / n_samples) * np.sum((y_hat - y)) 
    
    #Updating weights
    self.weights = self.weights - learn_rate * Delta_w
    self.bias = self.bias - learn_rate * Delta_b
    
    # end of loop
```

And ploting cost function against iterations

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/1h7gmh057q6gqx3jq7v1.png)
<small><center>Cost against iterations</center></small>

Above given is a cost function curve against number of iterations. As number of iterations increases (steps) cost decreased drastically meaning minimum is nearby and almost became zero. We do the above updations until the error becomes *negligible* or minimum is reached.
## Source code from Scratch

``` python
class LinearModel:
    """
    Linear Regression Model Class
    """
    def __init__(self):
        pass

    def gradient_descent(self, X, y, learn_rate=0.01, n_iters=100):
        """
        Trains a linear regression model using gradient descent
        """
        n_samples, n_features = X.shape
        self.weights = np.zeros(shape=(n_features,1))
        self.bias = 0
        self.prev_weights = []
        self.prev_bias = []
        self.X = X
        self.y = y
        costs = []

        for i in range(n_iters):
            """"
            Training Phase
            """
            y_hat = np.dot(X, self.weights) + self.bias
            """
            Cost error Phase
            """
            cost = (1 / n_samples) * np.sum((y_hat - y)**2)
            costs.append(cost)
            """
            Verbose: Description of cost at each iteration
            """
            if i % 200 == 0:
                print("Cost at iteration {0}: {1}".format(i,cost))
            """
            Updating the derivative
            """
            Delta_w = (2 / n_samples) * np.dot(X.T, (y_hat - y))
            Delta_b = (2 / n_samples) * np.sum((y_hat - y)) 
            
            """"
            Updating weights and bias
            """
            self.weights = self.weights - learn_rate * Delta_w
            self.bias = self.bias - learn_rate * Delta_b
            
            """
            Save the weights for visualisation
            """
            self.prev_weights.append(self.weights)
            self.prev_bias.append(self.bias)

        return self.weights, self.bias, costs

    def predict(self, X):
        """
        Predicting the values by using Linear Model
        """
        return np.dot(X, self.weights) + self.bias
``` 

``` python
# We have created our Linear Model class. Now we need to create and load our model.
model = LinearModel()

w_trained, b_trained, costs = model.gradient_descent(X_train, y_train, learn_rate=0.005, n_iters=1000)

```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/ya2x6ov129op3xc9v55g.png)

``` python
    def visualize_training(self):
        """
        Visualizing the line against the dataset        
        """
        
        self.prev_weights = np.array(self.prev_weights)

        x = self.X[:, 0]
        line, = ax.plot(x, x, color='red')
        ax.scatter(x, self.y)

        
        def animate(line_data):
            m, c = line_data
            line.set_ydata(m*x + c)  # update the data
            return line,
        
        def init():
            return line,
        
        def get_next_weight_and_bias():
            for i in range(len(self.prev_weights)):
                yield self.prev_weights[i][0], self.prev_bias[i]
        
        return animation.FuncAnimation(fig, animate, get_next_weight_and_bias, init_func=init,interval=35, blit=True)

```

``` python
# Visualization of training phase to get the best fit line
fig, ax = plt.subplots()
ani = model.visualize_training()
plt.show()

```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/6km0j9dyj36yhhbz85w0.gif)

``` python
# Prediction Phase to test our model  
n_samples, _= X_train.shape
n_samples_test, _ = X_test.shape

y_p_train = model.predict(X_train)
y_p_test = model.predict(X_test)

error_train =  (1 / n_samples) * np.sum((y_p_train - y_train) ** 2)
error_test  =  (1 / n_samples_test) * np.sum((y_p_test - y_test) ** 2)

print("Error on training set: {}".format(np.round(error_train, 6)))
print("Error on test set: {}".format(np.round(error_test, 6)))

```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/vjgmluaf12ffqou8x48c.png)

``` python
# Plotting predicted best fit line
fig = plt.figure(figsize=(8,6))
plt.scatter(X_train, y_train)
plt.scatter(X_test, y_p_test)
plt.xlabel("x")
plt.ylabel("y")
plt.show()

```
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/wiybp7ij6p976921wm91.png)

<center><small>Predicted Output</small></center>

> Check out the full source code for [Gradient Descent on GitHub](https://github.com/ML-Scratch/ML_Code_From_Scratch/blob/master/MLS.1.Linear%20Regression/LinearModel_Gradient_Descent.ipynb)
> and also check out the other approaches in [Linear Regression by ML-Scratch](https://github.com/ML-Scratch/ML_Code_From_Scratch/tree/master/MLS.1.Linear%20Regression)

## Contributors 

This series is made possible by help from:
* Pranav (@devarakondapranav)
* Ram (@r0mflip)
* Devika (@devikamadupu1)
* Pratyusha(@prathyushakallepu)
* Pranay (@pranay9866)
* Subhasri (@subhasrir)
* Laxman (@lmn)
* Vaishnavi(@vaishnavipulluri)
* Suraj (@suraj47)