<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Given a 2D set of points spanned by axes $x$ and $y$ axes, we will try to fit a line that best approximates the data. The equation of the line, in slope-intercept form, is defined by
Step1:  If $N$ = num_points, then the error in fitting a line to the points (also defined as Cost, $C$) can be defined as
<ASSISTANT_TASK:>
Python Code:
def generate_random_points_along_a_line (slope, intercept, num_points, abs_value, abs_noise):
    # randomly select x
    x = np.random.uniform(-abs_value, abs_value, num_points)
    # y = mx + b + noise
    y = slope*x + intercept + np.random.uniform(-abs_noise, abs_noise, num_points)
    return x, y
def plot_points(x,y):
    plt.scatter(x, y)
    plt.title('Scatter plot of x and y')
    plt.xlabel('x')
    plt.ylabel('y')
slope = 4
intercept = -3
num_points = 20
abs_value = 4
abs_noise = 2
x, y = generate_random_points_along_a_line (slope, intercept, num_points, abs_value, abs_noise)
plot_points(x, y)
Explanation: Given a 2D set of points spanned by axes $x$ and $y$ axes, we will try to fit a line that best approximates the data. The equation of the line, in slope-intercept form, is defined by: $y = mx + b$.
End of explanation
# this function computes gradient with respect to slope m
def grad_m (x, y, m, b):
    return np.sum(np.multiply(-2*(y - (m*x + b)), x))
# this function computes gradient with respect to intercept b
def grad_b (x, y, m, b):
    return np.sum(-2*(y - (m*x + b)))
# Performs gradient descent
def gradient_descent (x, y, num_iterations, learning_rate):
    # Initialize m and b
    m = np.random.uniform(-1, 1, 1)
    b = np.random.uniform(-1, 1, 1)
    # Update m and b in direction opposite to that of the gradient to minimize loss 
    for i in range(num_iterations):
        m = m - learning_rate * grad_m (x, y, m, b)
        b = b - learning_rate * grad_b (x, y, m, b)
    # Return final slope and intercept
    return m, b
# Plot point along with the best fit line
def plot_line (m, b, x, y):
    plot_points(x,y)
    plt.plot(x, x*m + b, 'r')
    plt.show()
# In general, keep num_iterations high and learning_rate low.
num_iterations =  1000
learning_rate = 0.0001
m, b = gradient_descent (x, y, num_iterations, learning_rate)
plot_line (m, b, x, y)
plt.show()
Explanation: If $N$ = num_points, then the error in fitting a line to the points (also defined as Cost, $C$) can be defined as:
$C = \sum_{i=0}^{N} (y-(mx+b))^2$
To perform gradient descent, we need the partial derivatives of Cost $C$ with respect to slope $m$ and intercept $b$.
$\frac{\partial C}{\partial m} = \sum_{i=0}^{N} -2(y-(mx+b)).x$
$\frac{\partial C}{\partial b} = \sum_{i=0}^{N} -2(y-(mx+b))$
End of explanation
<END_TASK>