<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Start with our simple example
Let's start with $f(x) = x^2$
Step1:  Let's assume we start at the top of the curve, at x = -4, and want to get down to x=0.
Step2:  In this algorithm, alpha is known as the "learning rate", and all it does is keep us from taking steps that are too aggressive, where we could shoot past the minimum.
Step3:  Here we can see that we took a pretty big step towards the minimum, just as we'd like. 
Let's take another step
Step4:  At this point, we've taken two steps in our Gradient Descent, and we've gone from x=-4 all the way to x=-1.44. Every additional step we take is going to give us smaller and smaller returns, so instead of writing out each additional step, let's do this programatically.
<ASSISTANT_TASK:>
Python Code:
# make our x array
x = np.linspace(-4, 4, 801)
# f(x) = x^2
def f(x):
    return x**2
# derivative of x^2 is 2x
def f_prime(x):
    return 2*x
# take a look at the curve
plt.plot(x, f(x), c='black')
sns.despine();
Explanation: Start with our simple example
Let's start with $f(x) = x^2$:
End of explanation
# starting position on the curve
x_start = -4.0
# looking at the values of the derivative, for each value of x. 
# we see the greatest change at the tops of the curve, namely 4 and -4
plt.plot(x, f_prime(x), c='black');
# learning rate
alpha = 0.2
Explanation: Let's assume we start at the top of the curve, at x = -4, and want to get down to x=0.
End of explanation
# let's take our first step!
step1 = alpha*f_prime(x_start)
# our new value of x is just the previous value, minus the step
next_x = x_start - step1
# take a look at the step we took, with respect to the curve
plt.plot(x, f(x), c='black')
plt.scatter([x_start, next_x], [f(x_start), f(next_x)], c='red')
plt.plot([x_start, next_x], [f(x_start), f(next_x)], c='red')
plt.xlim((-4, 4))
plt.ylim((0, 16))
sns.despine();
Explanation: In this algorithm, alpha is known as the "learning rate", and all it does is keep us from taking steps that are too aggressive, where we could shoot past the minimum.
End of explanation
another_x = next_x - alpha*f_prime(next_x)
# take a look at the combination of the two steps we've taken
plt.plot(x, f(x), c='black')
plt.plot([x_start, next_x], [f(x_start), f(next_x)], c='red')
plt.scatter([x_start, next_x], [f(x_start), f(next_x)], c='red')
plt.plot([x_start, next_x, another_x], [f(x_start), f(next_x), f(another_x)], c='red')
plt.scatter([x_start, next_x, another_x], [f(x_start), f(next_x), f(another_x)], c='red')
plt.xlim((-4, 4))
plt.ylim((0, 16))
sns.despine();
Explanation: Here we can see that we took a pretty big step towards the minimum, just as we'd like. 
Let's take another step:
End of explanation
# how many steps we're going to take in our Descent
num_steps = 101
# hold our steps, including our initial starting position
x_steps = [x_start]
# do num_steps iterations
for i in xrange(num_steps):
    prev_x = x_steps[i]
    new_x = prev_x - alpha*f_prime(prev_x)
    x_steps.append(new_x)
# plot the gradient descent as we go down the curve
plt.plot(x, f(x), c='black')
plt.plot(x_steps, [f(xi) for xi in x_steps], c='red')
plt.scatter(x_steps, [f(xi) for xi in x_steps], c='red')
plt.xlim((-4, 4))
plt.ylim((0, 16))
sns.despine();
# check the size of the derivative when we finished the iteration
print 'gradient at the end of our interations:', x_steps[-1]
# it's zero, for all intensive purposes
print 'Is the derivative effectively equal to zero at the bottom?', np.isclose(x_steps[-1], 0.0)
Explanation: At this point, we've taken two steps in our Gradient Descent, and we've gone from x=-4 all the way to x=-1.44. Every additional step we take is going to give us smaller and smaller returns, so instead of writing out each additional step, let's do this programatically.
End of explanation
<END_TASK>