Interactive House Price Prediction & Cost Analysis by Machine Learning


house price prediction

AI Generated image: House Price predictions





1. Introduction

House Price Prediction

This project showcases an interactive machine learning model that predicts house prices based on features such as size. Inspired by the DeepLearning.ai labs during my training under Stanford University's Machine Learning course on Coursera, this project highlights key Macchine Learning concepts like cost functions, gradient descent, and interactive visualizations. By redefining and recreating tools from the lab environment, I successfully reproduced the project on local systems, ensuring it works seamlessly outside the proprietary lab setup.

Project Overview

This project explores linear regression and its application to predicting house prices. The model learns the relationship between house size (input) and price (output) using a cost function that measures the error in predictions. Key highlights of the project include:

VIDEO: Example Visualization:


VIDEO Created during my exercises in ML Lab Lessons. Interactive visualization of linear regression.
Clicking on right Contour graph, house prediction line graph at the reflect the changes.

RUN VIDEO: click on play icon

Dynamic Visualizations

Interactive plots demonstrate the impact of different parameters (w, b) on the cost function. Users can:

  • Rotate 3D visualizations of the cost surface.
  • Click on contour plots to visualize cost changes dynamically.

PYTHON CODES FOR THE PROJECT:

Codes representing in 19 number of Jupyter Notebook cells

Cell 1: Importing Required Libraries and Defining Plot Colors:
This cell begins by importing the essential Python libraries used throughout the notebook. numpy is imported for numerical computations, and matplotlib.pyplot is included for data visualization and plotting.

Additionally, five custom color codes are defined (such as dlblue, dlorange, and dlpurple) which will be used consistently for styling graphs and plots. These colors are sourced from lab_utils_common.py, typically used in DeepLearning.AI courses for clean and informative visuals.

Defining them early helps maintain visual consistency in plots across the notebook.


      
          import numpy as np
          import matplotlib.pyplot as plt

          dlblue = '#0096ff'
          dlorange = '#FF9300'
          dldarkred = '#C00000'
          dlmagenta = '#FF40FF'
          dlpurple = '#7030A0'
          dlcolors = [dlblue, dlorange, dldarkred, dlmagenta, dlpurple]
      
    



Cell 2: Defining the Cost Function for Linear Regression

This cell defines the compute_cost_matrix function, which calculates the cost (or loss) of a linear regression model.
It accepts input features X, target values y, model weights w, and bias b. The cost is computed using the Mean Squared Error (MSE) formula, which measures how far off the predicted values are from the actual values.
The function predicts output values using vectorized operations (f_wb = X @ w + b) and computes the average squared error. An optional verbose flag can be set to True to print the predictions for debugging.

      
          #Function to calculate the cost
          def compute_cost_matrix(X, y, w, b, verbose=False):
              """
              Computes the gradient for linear regression
              Args:
                X (ndarray (m,n)): Data, m examples with n features
                y (ndarray (m,)) : target values
                w (ndarray (n,)) : model parameters  
                b (scalar)       : model parameter
                verbose : (Boolean) If true, print out intermediate value f_wb
              Returns
                cost: (scalar)
              """
              m = X.shape[0]
          
              # calculate f_wb for all examples.
              f_wb = X @ w + b
              # calculate cost
              total_cost = (1/(2*m)) * np.sum((f_wb-y)**2)
          
              if verbose: print("f_wb:")
              if verbose: print(f_wb)
          
              return total_cost
      
    



Cell 3: Computing Gradients for Linear Regression

This cell defines the compute_gradient_matrix function, used to calculate the gradients of the cost function with respect to the weights w and bias b.
The function takes the input data X, actual target values y, and current model parameters w and b. It computes the prediction error and then calculates:

  • dj_dw: The gradient of the cost with respect to weights.
  • dj_db: The gradient of the cost with respect to bias.

These gradients are used in gradient descent to update the parameters and minimize the prediction error.

      
        def compute_gradient_matrix(X, y, w, b):
          """
          Computes the gradient for linear regression
      
          Args:
            X (ndarray (m,n)): Data, m examples with n features
            y (ndarray (m,)) : target values
            w (ndarray (n,)) : model parameters  
            b (scalar)       : model parameter
          Returns
            dj_dw (ndarray (n,1)): The gradient of the cost w.r.t. the parameters w.
            dj_db (scalar):        The gradient of the cost w.r.t. the parameter b.
      
          """
          m,n = X.shape
          f_wb = X @ w + b
          e   = f_wb - y
          dj_dw  = (1/m) * (X.T @ e)
          dj_db  = (1/m) * np.sum(e)
    
          return dj_db,dj_dw    
      
    



Cell 4: Loop-Based Cost Function for Multivariable Linear Regression

This cell defines the compute_cost function, which calculates the cost using a loop-based approach instead of vectorized operations.
For each training example, the model computes a prediction f_wb_i using dot product and bias, then compares it with the actual target value y[i].
The squared differences are summed up and averaged to return the mean squared error as the cost. Though less efficient, this approach improves understanding and is useful for debugging.

      
          # Loop version of multi-variable compute_cost
          def compute_cost(X, y, w, b):
              """
              compute cost
              Args:
                X (ndarray (m,n)): Data, m examples with n features
                y (ndarray (m,)) : target values
                w (ndarray (n,)) : model parameters  
                b (scalar)       : model parameter
              Returns
                cost (scalar)    : cost
              """
              m = X.shape[0]
              cost = 0.0
              for i in range(m):
                  f_wb_i = np.dot(X[i],w) + b           #(n,)(n,)=scalar
                  cost = cost + (f_wb_i - y[i])**2
              cost = cost/(2*m)
              return cost
      
    



Cell 5: Loop-Based Gradient Computation for Linear Regression

This cell defines the compute_gradient function that calculates the gradients of the cost function with respect to weights w and bias b.
Using nested loops, it iterates over each training example to compute the prediction error and updates each weight's partial derivative dj_dw and the bias gradient dj_db.
After looping through all examples, the gradients are averaged. This implementation builds foundational understanding of gradient descent mechanics despite being less efficient than vectorized alternatives.

      
        def compute_gradient(X, y, w, b):
          """
          Computes the gradient for linear regression
          Args:
            X (ndarray (m,n)): Data, m examples with n features
            y (ndarray (m,)) : target values
            w (ndarray (n,)) : model parameters  
            b (scalar)       : model parameter
          Returns
            dj_dw (ndarray Shape (n,)): The gradient of the cost w.r.t. the parameters w.
            dj_db (scalar):             The gradient of the cost w.r.t. the parameter b.
          """
          m,n = X.shape           #(number of examples, number of features)
          dj_dw = np.zeros((n,))
          dj_db = 0.

          for i in range(m):
              err = (np.dot(X[i], w) + b) - y[i]
              for j in range(n):
                  dj_dw[j] = dj_dw[j] + err * X[i,j]
              dj_db = dj_db + err
          dj_dw = dj_dw/m
          dj_db = dj_db/m

          return dj_db,dj_dw
      
    



Cell 6: Setup for Univariate Visualization Utilities

This cell sets up the environment for visualizing univariate regression tasks. It imports key libraries like NumPy, Matplotlib, and ipywidgets for plotting and interactivity.
The default plot style is applied for consistency, and a custom color map dlcm is created using LinearSegmentedColormap with five color bins.
These utilities support interactive and colorful plots for illustrating model predictions, cost function surfaces, and gradient flows during learning, enhancing both clarity and user interaction.

      
        #lab_utils_uni.py routines used in Course 1, Week2, labs1-3 dealing with single variables (univariate)
        # Combined content of lab_utils_uni.py

        import numpy as np
        import matplotlib.pyplot as plt
        from matplotlib.ticker import MaxNLocator
        from matplotlib.gridspec import GridSpec
        from matplotlib.colors import LinearSegmentedColormap
        from ipywidgets import interact
        #from lab_utils_common import compute_cost
        #from lab_utils_common import dlblue, dlorange, dldarkred, dlmagenta, dlpurple, dlcolors

        #plt.style.use('./deeplearning.mplstyle')
        plt.style.use('default')  # Use a default style temporarily
        n_bin = 5
        dlcm = LinearSegmentedColormap.from_list('dl_map', dlcolors, N=n_bin)
      
    



Cell 7: Plotting Routines for House Price Visualization and Cost Illustration

This cell provides two custom plotting functions to enhance the interpretability of linear regression outputs in housing data:

  • plt_house_x(X, y, f_wb, ax): Plots a scatter of actual house sizes vs. prices with red 'x' markers. If model predictions f_wb are passed, it overlays a prediction line in blue, with axis labels and a legend.
  • mk_cost_lines(x, y, w, b, ax): Draws vertical dotted lines from each actual point to its corresponding predicted point, visually representing the squared error. It also annotates each error and constructs a textual cost formula at the bottom of the plot for clarity.

These visualizations are essential for understanding model behavior, particularly how predictions compare to actual data and how cost is computed across the dataset.

      
        # Plotting Routines

        def plt_house_x(X, y,f_wb=None, ax=None):
            ''' plot house with aXis '''
            if not ax:
                fig, ax = plt.subplots(1,1)
            ax.scatter(X, y, marker='x', c='r', label="Actual Value")

            ax.set_title("Housing Prices")
            ax.set_ylabel('Price (in 1000s of dollars)')
            ax.set_xlabel(f'Size (1000 sqft)')
            if f_wb is not None:
                ax.plot(X, f_wb,  c=dlblue, label="Our Prediction")
            ax.legend()


        def mk_cost_lines(x,y,w,b, ax):
            ''' makes vertical cost lines'''
            cstr = "cost = (1/m)*("
            ctot = 0
            label = 'cost for point'
            addedbreak = False
            for p in zip(x,y):
                f_wb_p = w*p[0]+b
                c_p = ((f_wb_p - p[1])**2)/2
                c_p_txt = c_p
                ax.vlines(p[0], p[1],f_wb_p, lw=3, color=dlpurple, ls='dotted', label=label)
                label='' #just one
                cxy = [p[0], p[1] + (f_wb_p-p[1])/2]
                ax.annotate(f'{c_p_txt:0.0f}', xy=cxy, xycoords='data',color=dlpurple,
                    xytext=(5, 0), textcoords='offset points')
                cstr += f"{c_p_txt:0.0f} +"
                if len(cstr) > 38 and addedbreak is False:
                    cstr += "\n"
                    addedbreak = True
                ctot += c_p
            ctot = ctot/(len(x))
            cstr = cstr[:-1] + f") = {ctot:0.0f}"
            ax.text(0.15,0.02,cstr, transform=ax.transAxes, color=dlpurple)
      
    



Cell 8: Cost Intuition Visualization Using Interactive Widget

This cell introduces the plt_intuition() function, which uses an interactive widget to demonstrate how the cost changes as the model weight w is varied, while keeping the bias b fixed at 100.


  • Weight Range & Cost Calculation: A range of w values is defined around 200, and the corresponding cost is calculated using compute_cost().
  • Interactive Visualization: Using @interact, a slider is provided to dynamically select w values. For each selection:
    • A plot shows predicted vs. actual values and cost error lines.
    • A separate graph displays the cost vs. weight curve, marking the current cost with a red dot and guiding lines.

This visualization is a valuable tool for intuitively understanding the gradient descent process and the impact of the weight parameter on model performance.

      
        # Cost lab
        ##########

        def plt_intuition(x_train, y_train):

            w_range = np.array([200-200,200+200])
            tmp_b = 100

            w_array = np.arange(*w_range, 5)
            cost = np.zeros_like(w_array)
            for i in range(len(w_array)):
                tmp_w = w_array[i]
                cost[i] = compute_cost(x_train, y_train, tmp_w, tmp_b)

            @interact(w=(*w_range,10),continuous_update=False)
            def func( w=150):
                f_wb = np.dot(x_train, w) + tmp_b

                fig, ax = plt.subplots(1, 2, constrained_layout=True, figsize=(8,4))
                fig.canvas.toolbar_position = 'bottom'

                mk_cost_lines(x_train, y_train, w, tmp_b, ax[0])
                plt_house_x(x_train, y_train, f_wb=f_wb, ax=ax[0])

                ax[1].plot(w_array, cost)
                cur_cost = compute_cost(x_train, y_train, w, tmp_b)
                ax[1].scatter(w,cur_cost, s=100, color=dldarkred, zorder= 10, label= f"cost at w={w}")
                ax[1].hlines(cur_cost, ax[1].get_xlim()[0],w, lw=4, color=dlpurple, ls='dotted')
                ax[1].vlines(w, ax[1].get_ylim()[0],cur_cost, lw=4, color=dlpurple, ls='dotted')
                ax[1].set_title("Cost vs. w, (b fixed at 100)")
                ax[1].set_ylabel('Cost')
                ax[1].set_xlabel('w')
                ax[1].legend(loc='upper center')
                fig.suptitle(f"Minimize Cost: Current Cost = {cur_cost:0.0f}", fontsize=12)
                plt.show()
      
    


Cell 9: Stationary Visualization of Cost Function

The plt_stationary() function visualizes how the cost varies across different values of weight w and bias b in linear regression. It provides three coordinated subplots:

  1. Prediction Plot (Top-left): Shows actual housing prices versus predicted values using a fixed w=200 and b=-100. Visual error lines illustrate the difference between predictions and actual values.
  2. Contour Plot (Top-right): Displays a 2D contour map of log(J(w, b)) using a color gradient. A blue dot and dotted lines indicate the current parameter values.
  3. 3D Surface Plot (Bottom): Presents a 3D surface and wireframe of the cost function, giving a visual understanding of how cost varies with both w and b. The 3D view can be rotated.

This function is an excellent tool to develop geometric and intuitive understanding of how gradient descent would move over the cost surface to minimize J(w, b).

      
        def plt_stationary(x_train, y_train):
          # setup figure
          fig = plt.figure( figsize=(9,8))
          #fig = plt.figure(constrained_layout=True,  figsize=(12,10))
          fig.set_facecolor('#ffffff') #white
          fig.canvas.toolbar_position = 'top'
          #gs = GridSpec(2, 2, figure=fig, wspace = 0.01)
          gs = GridSpec(2, 2, figure=fig)
          ax0 = fig.add_subplot(gs[0, 0])
          ax1 = fig.add_subplot(gs[0, 1])
          ax2 = fig.add_subplot(gs[1, :],  projection='3d')
          ax = np.array([ax0,ax1,ax2])

          #setup useful ranges and common linspaces
          w_range = np.array([200-300.,200+300])
          b_range = np.array([50-300., 50+300])
          b_space  = np.linspace(*b_range, 100)
          w_space  = np.linspace(*w_range, 100)

          # get cost for w,b ranges for contour and 3D
          tmp_b,tmp_w = np.meshgrid(b_space,w_space)
          z=np.zeros_like(tmp_b)
          for i in range(tmp_w.shape[0]):
              for j in range(tmp_w.shape[1]):
                  z[i,j] = compute_cost(x_train, y_train, tmp_w[i][j], tmp_b[i][j] )
                  if z[i,j] == 0: z[i,j] = 1e-6

          w0=200;b=-100    #initial point
          ### plot model w cost ###
          f_wb = np.dot(x_train,w0) + b
          mk_cost_lines(x_train,y_train,w0,b,ax[0])
          plt_house_x(x_train, y_train, f_wb=f_wb, ax=ax[0])

          ### plot contour ###
          CS = ax[1].contour(tmp_w, tmp_b, np.log(z),levels=12, linewidths=2, alpha=0.7,colors=dlcolors)
          ax[1].set_title('Cost(w,b)')
          ax[1].set_xlabel('w', fontsize=10)
          ax[1].set_ylabel('b', fontsize=10)
          ax[1].set_xlim(w_range) ; ax[1].set_ylim(b_range)
          cscat  = ax[1].scatter(w0,b, s=100, color=dlblue, zorder= 10, label="cost with \ncurrent w,b")
          chline = ax[1].hlines(b, ax[1].get_xlim()[0],w0, lw=4, color=dlpurple, ls='dotted')
          cvline = ax[1].vlines(w0, ax[1].get_ylim()[0],b, lw=4, color=dlpurple, ls='dotted')
          ax[1].text(0.5,0.95,"Click to choose w,b",  bbox=dict(facecolor='white', ec = 'black'), fontsize = 10,
                      transform=ax[1].transAxes, verticalalignment = 'center', horizontalalignment= 'center')

          #Surface plot of the cost function J(w,b)
          ax[2].plot_surface(tmp_w, tmp_b, z,  cmap = dlcm, alpha=0.3, antialiased=True)
          ax[2].plot_wireframe(tmp_w, tmp_b, z, color='k', alpha=0.1)
          plt.xlabel("$w$")
          plt.ylabel("$b$")
          ax[2].zaxis.set_rotate_label(False)
          ax[2].xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax[2].yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax[2].zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax[2].set_zlabel("J(w, b)\n\n", rotation=90)
          plt.title("Cost(w,b) \n [You can rotate this figure]", size=12)
          ax[2].view_init(30, -120)

          return fig,ax, [cscat, chline, cvline]

      
    


Cell 10: Interactive Cost Visualization with plt_update_onclick

This class adds interactivity to the static plots from plt_stationary() by letting users click on the contour plot to choose different values for the weight w and bias b.


  • Event Binding: When a user clicks on the contour plot, the class captures the click coordinates.
  • Dynamic Updates: It recalculates cost, updates the prediction plot with new lines and points, and refreshes the contour and 3D plots with new markers and annotations.
  • Real-Time Cost Display: The cost at the clicked point is shown as text on the plot, and a marker is added on the 3D surface to indicate the point in the cost landscape.

This tool turns the cost function plots into an interactive dashboard for exploring how changes in w and b affect the model and its cost.

      
        #https://matplotlib.org/stable/users/event_handling.html
        class plt_update_onclick:
            def __init__(self, fig, ax, x_train,y_train, dyn_items):
                self.fig = fig
                self.ax = ax
                self.x_train = x_train
                self.y_train = y_train
                self.dyn_items = dyn_items
                self.cid = fig.canvas.mpl_connect('button_press_event', self)
        
            def __call__(self, event):
                if event.inaxes == self.ax[1]:
                    ws = event.xdata
                    bs = event.ydata
                    cst = compute_cost(self.x_train, self.y_train, ws, bs)
        
                    # clear and redraw line plot
                    self.ax[0].clear()
                    f_wb = np.dot(self.x_train,ws) + bs
                    mk_cost_lines(self.x_train,self.y_train,ws,bs,self.ax[0])
                    plt_house_x(self.x_train, self.y_train, f_wb=f_wb, ax=self.ax[0])
        
                    # remove lines and re-add on countour plot and 3d plot
                    for artist in self.dyn_items:
                        artist.remove()
        
                    a = self.ax[1].scatter(ws,bs, s=100, color=dlblue, zorder= 10, label="cost with \ncurrent w,b")
                    b = self.ax[1].hlines(bs, self.ax[1].get_xlim()[0],ws, lw=4, color=dlpurple, ls='dotted')
                    c = self.ax[1].vlines(ws, self.ax[1].get_ylim()[0],bs, lw=4, color=dlpurple, ls='dotted')
                    d = self.ax[1].annotate(f"Cost: {cst:.0f}", xy= (ws, bs), xytext = (4,4), textcoords = 'offset points',
                                       bbox=dict(facecolor='white'), size = 10)
        
                    #Add point in 3D surface plot
                    e = self.ax[2].scatter3D(ws, bs,cst , marker='X', s=100)
        
                    self.dyn_items = [a,b,c,d,e]
                    self.fig.canvas.draw()       
      
    


Cell 11: 3D Visualization of a Bowl-Shaped Cost Function

This function, soup_bowl(), creates a 3D surface plot that visualizes a simple bowl-shaped cost function:

\[ J(w, b) = w^2 + b^2 \]

The resulting plot is a convex surface, shaped like a bowl, which helps in visualizing the optimization landscape of a quadratic cost function. This is especially useful for understanding how gradient descent converges toward the global minimum.

  • Grid Setup: Creates ranges for w and b, then calculates the corresponding cost values using nested loops.
  • Plot Aesthetics: Sets up 3D viewing angles and makes plot panes transparent for better visibility.
  • Surface + Wireframe: The colored surface shows the cost landscape, while the wireframe outlines the curvature of the bowl.

The plot clearly shows that the minimum cost occurs at (w, b) = (0, 0), helping visualize why gradient descent moves toward this point.

      
        def soup_bowl():
          """ Create figure and plot with a 3D projection"""
          fig = plt.figure(figsize=(8,8))

          #Plot configuration
          ax = fig.add_subplot(111, projection='3d')
          ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
          ax.zaxis.set_rotate_label(False)
          ax.view_init(45, -120)

          #Useful linearspaces to give values to the parameters w and b
          w = np.linspace(-20, 20, 100)
          b = np.linspace(-20, 20, 100)

          #Get the z value for a bowl-shaped cost function
          z=np.zeros((len(w), len(b)))
          j=0
          for x in w:
              i=0
              for y in b:
                  z[i,j] = x**2 + y**2
                  i+=1
              j+=1

          #Meshgrid used for plotting 3D functions
          W, B = np.meshgrid(w, b)

          #Create the 3D surface plot of the bowl-shaped cost function
          ax.plot_surface(W, B, z, cmap = "Spectral_r", alpha=0.7, antialiased=False)
          ax.plot_wireframe(W, B, z, color='k', alpha=0.1)
          ax.set_xlabel("$w$")
          ax.set_ylabel("$b$")
          ax.set_zlabel("$J(w,b)$", rotation=90)
          ax.set_title("$J(w,b)$\n [You can rotate this figure]", size=15)

          plt.show()
      
    


Cell 12: Contour Plot with Gradient Descent Path Visualization

This cell introduces two functions to help visualize the behavior of gradient descent on a 2D cost surface defined by J(w, b).

  • inbounds(): A helper function that ensures plotted arrows remain within the current axis limits to avoid clutter or errors.
  • plt_contour_wgrad(): Generates a contour plot of the cost landscape and overlays the path taken by gradient descent using arrows.

The function calculates the cost surface by looping over a mesh grid of weight and bias values. It then uses contour lines to represent areas of equal cost, with colors indicating different cost levels. The gradient descent path is drawn on top, showing how the algorithm iteratively approaches the optimal parameters.
This plot is essential to demonstrate the concept of convergence during optimization and visually trace the steps taken by the algorithm.

      
        def inbounds(a,b,xlim,ylim):
          xlow,xhigh = xlim
          ylow,yhigh = ylim
          ax, ay = a
          bx, by = b
          if (ax > xlow and ax < xhigh) and (bx > xlow and bx < xhigh) \
              and (ay > ylow and ay < yhigh) and (by > ylow and by < yhigh):
              return True
          return False

        def plt_contour_wgrad(x, y, hist, ax, w_range=[-100, 500, 5], b_range=[-500, 500, 5],
                        contours = [0.1,50,1000,5000,10000,25000,50000],
                              resolution=5, w_final=200, b_final=100,step=10 ):
            b0,w0 = np.meshgrid(np.arange(*b_range),np.arange(*w_range))
            z=np.zeros_like(b0)
            for i in range(w0.shape[0]):
                for j in range(w0.shape[1]):
                    z[i][j] = compute_cost(x, y, w0[i][j], b0[i][j] )

            CS = ax.contour(w0, b0, z, contours, linewidths=2,
                          colors=[dlblue, dlorange, dldarkred, dlmagenta, dlpurple])
            ax.clabel(CS, inline=1, fmt='%1.0f', fontsize=10)
            ax.set_xlabel("w");  ax.set_ylabel("b")
            ax.set_title('Contour plot of cost J(w,b), vs b,w with path of gradient descent')
            w = w_final; b=b_final
            ax.hlines(b, ax.get_xlim()[0],w, lw=2, color=dlpurple, ls='dotted')
            ax.vlines(w, ax.get_ylim()[0],b, lw=2, color=dlpurple, ls='dotted')

            base = hist[0]
            for point in hist[0::step]:
                edist = np.sqrt((base[0] - point[0])**2 + (base[1] - point[1])**2)
                if(edist > resolution or point==hist[-1]):
                    if inbounds(point,base, ax.get_xlim(),ax.get_ylim()):
                        plt.annotate('', xy=point, xytext=base,xycoords='data',
                                arrowprops={'arrowstyle': '->', 'color': 'r', 'lw': 3},
                                va='center', ha='center')
                    base=point
            return

      
    


Cell 13: Visualizing Divergence Due to High Learning Rate

This function visualizes what happens when the learning rate in gradient descent is too large, causing the algorithm to diverge rather than converge to a minimum.
Function: plt_divergence(p_hist, J_hist, x_train, y_train)

  • Left Plot: Shows how the cost changes with respect to weight w while keeping the bias fixed. It includes the path taken by gradient descent, helping identify erratic behavior.
  • Right Plot: A 3D surface plot of the cost function over (w, b) with the actual trajectory of parameter updates plotted over it. If the path spirals outward or bounces chaotically, it indicates divergence.

By visualizing the cost surface and gradient path, this function provides clear insight into why selecting an appropriate learning rate is crucial for model training.

      
        def plt_divergence(p_hist, J_hist, x_train,y_train):

          x=np.zeros(len(p_hist))
          y=np.zeros(len(p_hist))
          v=np.zeros(len(p_hist))
          for i in range(len(p_hist)):
              x[i] = p_hist[i][0]
              y[i] = p_hist[i][1]
              v[i] = J_hist[i]

          fig = plt.figure(figsize=(12,5))
          plt.subplots_adjust( wspace=0 )
          gs = fig.add_gridspec(1, 5)
          fig.suptitle(f"Cost escalates when learning rate is too large")
          #===============
          #  First subplot
          #===============
          ax = fig.add_subplot(gs[:2], )

          # Print w vs cost to see minimum
          fix_b = 100
          w_array = np.arange(-70000, 70000, 1000, dtype="int64")
          cost = np.zeros_like(w_array,float)

          for i in range(len(w_array)):
              tmp_w = w_array[i]
              cost[i] = compute_cost(x_train, y_train, tmp_w, fix_b)

          ax.plot(w_array, cost)
          ax.plot(x,v, c=dlmagenta)
          ax.set_title("Cost vs w, b set to 100")
          ax.set_ylabel('Cost')
          ax.set_xlabel('w')
          ax.xaxis.set_major_locator(MaxNLocator(2))

          #===============
          # Second Subplot
          #===============

          tmp_b,tmp_w = np.meshgrid(np.arange(-35000, 35000, 500),np.arange(-70000, 70000, 500))
          tmp_b = tmp_b.astype('int64')
          tmp_w = tmp_w.astype('int64')
          z=np.zeros_like(tmp_b,float)
          for i in range(tmp_w.shape[0]):
              for j in range(tmp_w.shape[1]):
                  z[i][j] = compute_cost(x_train, y_train, tmp_w[i][j], tmp_b[i][j] )

          ax = fig.add_subplot(gs[2:], projection='3d')
          ax.plot_surface(tmp_w, tmp_b, z,  alpha=0.3, color=dlblue)
          ax.xaxis.set_major_locator(MaxNLocator(2))
          ax.yaxis.set_major_locator(MaxNLocator(2))

          ax.set_xlabel('w', fontsize=16)
          ax.set_ylabel('b', fontsize=16)
          ax.set_zlabel('\ncost', fontsize=16)
          plt.title('Cost vs (b, w)')
          # Customize the view angle
          ax.view_init(elev=20., azim=-65)
          ax.plot(x, y, v,c=dlmagenta)

          return
      
    


Cell 14: Visualizing a Partial Derivative Line

This function draws a tangent line at a specific point on a cost curve to represent the partial derivative of the cost function with respect to weight, \( \frac{\partial J}{\partial w} \).

Uses the point-slope form:

\[ y = \frac{\partial J}{\partial w} \cdot (x - x_1) + y_1 \]

This equation helps visualize the slope of the cost function at a point \( (x_1, y_1) \), aiding in understanding how gradient descent uses this slope to update weights.

Function: add_line(dj_dx, x1, y1, d, ax)


  • dj_dx: The slope (partial derivative) at the point.
  • x1, y1: The coordinates of the point of tangency.
  • d: The half-width of the tangent line (range around x1).
  • ax: The axis object where the line will be plotted.

The tangent line is drawn using the point-slope equation, and the slope is labeled with an annotation arrow. This is especially useful for visualizing the direction and steepness of gradient descent steps.

      
        # draw derivative line
        # y = m*(x - x1) + y1
        def add_line(dj_dx, x1, y1, d, ax):
            x = np.linspace(x1-d, x1+d,50)
            y = dj_dx*(x - x1) + y1
            ax.scatter(x1, y1, color=dlblue, s=50)
            ax.plot(x, y, '--', c=dldarkred,zorder=10, linewidth = 1)
            xoff = 30 if x1 == 200 else 10
            ax.annotate(r"$\frac{\partial J}{\partial w}$ =%d" % dj_dx, fontsize=14,
                        xy=(x1, y1), xycoords='data',
                    xytext=(xoff, 10), textcoords='offset points',
                    arrowprops=dict(arrowstyle="->"),
                    horizontalalignment='left', verticalalignment='top'
      
    


Cell 15: Visualizing Gradients

The plt_gradients() function displays how gradients behave in two ways:

  1. On the left subplot, it plots the cost function versus the weight \( w \), with bias \( b \) fixed at 100. For selected values of \( w \), it also draws tangent lines using the gradient: \[ y = \frac{\partial J}{\partial w}(x - x_1) + y_1 \] showing the local slope of the cost function.
  2. On the right subplot, it uses a quiver plot to visualize the gradient vectors \( \left( \frac{\partial J}{\partial w}, \frac{\partial J}{\partial b} \right) \) at different combinations of weights \( w \) and biases \( b \). The direction and magnitude of arrows show how the parameters change during gradient descent.

      
        def plt_gradients(x_train,y_train, f_compute_cost, f_compute_gradient):
          #===============
          #  First subplot
          #===============
          fig,ax = plt.subplots(1,2,figsize=(12,4))

          # Print w vs cost to see minimum
          fix_b = 100
          w_array = np.linspace(-100, 500, 50)
          w_array = np.linspace(0, 400, 50)
          cost = np.zeros_like(w_array)

          for i in range(len(w_array)):
              tmp_w = w_array[i]
              cost[i] = f_compute_cost(x_train, y_train, tmp_w, fix_b)
          ax[0].plot(w_array, cost,linewidth=1)
          ax[0].set_title("Cost vs w, with gradient; b set to 100")
          ax[0].set_ylabel('Cost')
          ax[0].set_xlabel('w')

          # plot lines for fixed b=100
          for tmp_w in [100,200,300]:
              fix_b = 100
              dj_dw,dj_db = f_compute_gradient(x_train, y_train, tmp_w, fix_b )
              j = f_compute_cost(x_train, y_train, tmp_w, fix_b)
              add_line(dj_dw, tmp_w, j, 30, ax[0])

          #===============
          # Second Subplot
          #===============

          tmp_b,tmp_w = np.meshgrid(np.linspace(-200, 200, 10), np.linspace(-100, 600, 10))
          U = np.zeros_like(tmp_w)
          V = np.zeros_like(tmp_b)
          for i in range(tmp_w.shape[0]):
              for j in range(tmp_w.shape[1]):
                  U[i][j], V[i][j] = f_compute_gradient(x_train, y_train, tmp_w[i][j], tmp_b[i][j] )
          X = tmp_w
          Y = tmp_b
          n=-2
          color_array = np.sqrt(((V-n)/2)**2 + ((U-n)/2)**2)

          ax[1].set_title('Gradient shown in quiver plot')
          Q = ax[1].quiver(X, Y, U, V, color_array, units='width', )
          ax[1].quiverkey(Q, 0.9, 0.9, 2, r'$2 \frac{m}{s}$', labelpos='E',coordinates='figure')
          ax[1].set_xlabel("w"); ax[1].set_ylabel("b")
      
    


Cell 16: Plotting a Sine Wave

The following code uses matplotlib to generate and display a simple sine wave graph using: \[ y = \sin(x), \quad \text{for } x \in [0, 5] \]

Sine wave plot from Cell 16

Generated Sine Wave Graph.

๐Ÿ“ˆ Graph Explanation:
The graph shows a single cycle of a sine wave over the interval from 0 to 5. The curve oscillates smoothly, illustrating the periodic nature of the sine function. This type of plot is fundamental in trigonometry, signal processing, and waveform visualization.

      
        #PROJECT STARTS
        %matplotlib widget
        import matplotlib.pyplot as plt
        import numpy as np

        x = np.linspace(0, 5, 100)
        y = np.sin(x)

        plt.plot(x, y)
        plt.show()
        
      
    



Cell 17: Linear Regression Intuition: Model Fit and Cost Function Visualization

๐Ÿ“˜ Explanation of Codes in this cell:

  • Data Initialization: Sets up training data x_train and y_train.
  • Mean Squared Error Cost Function::

    $$ J(w, b) = \frac{1}{2m} \sum_{i=1}^{m} \left( w \cdot x^{(i)} + b - y^{(i)} \right)^2 $$

  • Plotting: Calls plt_intuition() to generate the two graphs mentioned above.

Note: The function plt_intuition() used for plotting is defined earlier.

Linear Regression Intuition: Model Fit and Cost Function Visualization


Linear Regression Graphs

Graphs for Linear Regression Intuition: Model Fit and Cost Function Visualization

Explanation of Graphs:

  • Left Graph: Shows a straight line fitting the data points representing housing prices. The x-axis represents house sizes (in 1000's of sq. ft), and the y-axis shows prices (in 1000's of dollars).
  • Right Graph: Displays the cost versus weight w while keeping bias b fixed at 100. It shows a U-shaped curve with a red circular mark at the cost when w = 150, along with dotted lines indicating corresponding x and y positions.
      
        import numpy as np
        %matplotlib widget
        import matplotlib.pyplot as plt
        #from lab_utils_uni import plt_intuition, plt_stationary, plt_update_onclick, soup_bowl
        #plt.style.use('./deeplearning.mplstyle')

        x_train = np.array([1.0, 2.0])           #(size in 1000 square feet)
        y_train = np.array([300.0, 500.0])           #(price in 1000s of dollars)

        def compute_cost(x, y, w, b): 
            """
            Computes the cost function for linear regression.
            
            Args:
              x (ndarray (m,)): Data, m examples 
              y (ndarray (m,)): target values
              w,b (scalar)    : model parameters  
            
            Returns
                total_cost (float): The cost of using w,b as the parameters for linear regression
                      to fit the data points in x and y
            """
            # number of training examples
            m = x.shape[0] 
            
            cost_sum = 0 
            for i in range(m): 
                f_wb = w * x[i] + b            # f(x) = w * x[i] + b (THIS IS FOR 1 input in LOOP)
                cost = (f_wb - y[i]) ** 2      # COST = (f(x) - y[i]) ^ 2 (HERE ^ mean SQUARE)
                cost_sum = cost_sum + cost     # Costs are added at end of EACH LOOP
            total_cost = (1 / (2 * m)) * cost_sum  # After comleting all loops, given total costs

            return total_cost
        #   print(total_cost)     # I HAVE ADDED THIS LINE

        plt_intuition(x_train,y_train)
      
    



Cell 18: Counting Training Examples

This cell determines the number of training examples in the dataset x_train and displays the result.

    
      # m is the number of training examples
      m = len(x_train)
      print(f"Number of training examples is: {m}")
    
  

๐Ÿ” What it does:

  • x_train is a NumPy array holding input data (e.g., [1.0, 2.0]).
  • len(x_train) returns the count of examples in the array โ€” in this case, 2.
  • This value is stored in variable m.
  • The print() function outputs the total number of training examples.

โœ… Output:

    Number of training examples is: 2

๐ŸŽฏ Purpose:
Determining the number of training examples is a basic but essential task in machine learning. This count is used to:

  • Calculate averages (e.g., in cost/loss functions)
  • Control iteration over data during training
      
        # m is the number of training examples
        m = len(x_train)
        print(f"Number of training examples is: {m}")
      
    


Cell 19: Interactive Visualization of Linear Regression Cost Function

This visualization helps understand how the cost function of a linear regression model changes with model parameters w and b. The graphs were created using the training data and helper functions that generate both 2D and 3D plots of the cost function.

๐Ÿง  Code Summary

  
          x_train = np.array([1.0, 1.7, 2.0, 2.5, 3.0, 3.2])
          y_train = np.array([250, 300, 480, 430, 630, 730])

          plt.close('all') 
          fig, ax, dyn_items = plt_stationary(x_train, y_train)
          updater = plt_update_onclick(fig, ax, x_train, y_train, dyn_items)

          soup_bowl()
  

  • plt_stationary: Creates the housing price line fit and cost contour plot.
  • plt_update_onclick: Enables interactivityโ€”clicking the contour plot updates the line (in supported environments).
  • soup_bowl: Generates a 3D surface plot of the cost function J(w, b).

๐Ÿ“Š Image 1: 3D Surface Plot of Cost Function:

3D Bowl of J(w, b)

Figure 1: 3D visualization of the cost function \( J(w,b) = w^2 + b^2 \)


This 3D "bowl" shaped plot shows the cost surface of J(w, b). The lowest point represents the optimal values for w and b that minimize the cost. In a fully interactive environment (like Coursera Labs), this plot is rotatable.


๐Ÿ“‰ Image 2: Combined View โ€“ Line Fit, Contour Plot & Bowl:

Line Fit, Contour Plot, and Bowl Shape

Figure 2: Interactive visualization of linear regression โ€” Top-left: Housing Prices fitted line; Right: Contour plot of Cost \( J(w, b) \);
Bottom: 3D Bowl plot. (Note: Interactivity such as rotation and click updates is not available in this static view.)

This figure combines three visuals:

  1. Housing Prices (Top Left): Line fit of house size vs. price.
  2. Contour Plot (Top Right): Cost J(w, b) for different values of w and b as elliptical levels. In an interactive setting, clicking updates the line and bowl.
  3. 3D Bowl (Bottom): Duplicate of the above bowl with the same cost surface representation.

Note: In a full interactive environment (e.g., Coursera Labs or Jupyter Notebook with widget backend), the 3D bowl is rotatable and the contour plot is clickable. However, these features are inactive in this static rendering due to local environment limitations.

๐Ÿ“ Cost Function Formula:
The cost function used in this visualization is the Mean Square Error (MSE) defined as:

\[ J(w, b) = \frac{1}{2m} \sum_{i=1}^{m} (w \cdot x_i + b - y_i)^2 \]

This equation calculates the average squared error between predicted and actual housing prices.

      
        x_train = np.array([1.0, 1.7, 2.0, 2.5, 3.0, 3.2])
        y_train = np.array([250, 300, 480,  430,   630, 730,])

        plt.close('all') 
        fig, ax, dyn_items = plt_stationary(x_train, y_train)
        updater = plt_update_onclick(fig, ax, x_train, y_train, dyn_items)

        soup_bowl()
      
    


Recreated Utilities:

As the proprietary tools and functions from DeepLearning.ai were unavailable outside their lab environment, I redefined and recreated necessary utilities like cost computation, plotting functions, and interactive updates using Matplotlib and Numpy.


Uses and Applications

  • Educational Tool: Provides a practical understanding of linear regression, cost functions, and optimization techniques like gradient descent.
  • Visualization of ML Concepts: Interactive 2D and 3D plots help learners and professionals visualize and experiment with machine learning fundamentals.
  • Local Simulation: Demonstrates how to replicate lab-level projects on local systems, making ML education more accessible outside the controlled lab environments of DeepLearning.ai, Stanford University, and Coursera.

Cell 20: Conclusion

Closing Note

This project demonstrates not only technical skills in machine learning and data visualization but also the ability to adapt and innovate when tools and resources are limited. It reflects the resilience and creativity needed to succeed in the ever-evolving field of AI and machine learning.



Acknowledgements

I sincerely thank Prof. Andrew NG (DeepLearning.AI, Stanford University) for his inspiring courses that laid the foundation for this project.