<jupyter_start><jupyter_text>Numpy BasicsNumpy is the main package for scientific computing in Python. It is maintained by a large community (www.numpy.org).__Run the next cell to get access to numpy__.<jupyter_code>import numpy as np  # this means you can access numpy functions by writing np.function() instead of numpy.function()<jupyter_output><empty_output><jupyter_text>1. Why numpyWhy do we need numpy? Let's compare computation time for python and numpy* Two vectors with $10^6$ elements each * first - from 0 to 1 000 000 * second - from 99 to 1 000 099 * Computing: * Sum of two vectors * Hadamard product – entrywise product of 2 vectors * Square root for each element of first array * Sum of all elements in the first array<jupyter_code>%%time
# ^-- this "magic" measures and prints cell computation time

print("Option I: pure python")
arr_1 = range(1000000)
arr_2 = range(99, 1000099)


a_sum = [0.0]*len(arr_1) 
a_prod = [0.0]*len(arr_1) 
sqrt_a1 = [0.0]*len(arr_1) 
for i in range(len(arr_1)):
    a_sum[i] = arr_1[i]+arr_2[i]
    a_prod[i] = arr_1[i]*arr_2[i]
    a_sum[i] = arr_1[i]**0.5

arr_1_sum = sum(arr_1)
%%time

print("Option II: start from python, convert to numpy")
arr_1 = range(1000000)
arr_2 = range(99, 1000099)

arr_1, arr_2 = np.array(arr_1), np.array(arr_2)


a_sum = arr_1 + arr_2
a_prod = arr_1 * arr_2
sqrt_a1 = arr_1 ** .5
arr_1_sum = arr_1.sum()
%%time

print("Option III: pure numpy")
arr_1 = np.arange(1000000)
arr_2 = np.arange(99, 1000099)

a_sum = arr_1 + arr_2
a_prod = arr_1 * arr_2
sqrt_a1 = arr_1 ** .5
arr_1_sum = arr_1.sum()<jupyter_output>Option III: pure numpy
CPU times: user 51.1 ms, sys: 0 ns, total: 51.1 ms
Wall time: 54.6 ms<jupyter_text>Can it be that numpy is almost as fast as C? Take a look at [this](https://notes-on-cython.readthedocs.io/en/latest/std_dev.html).  2. Reshaping arrays Two important numpy functions are [np.shape()](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.shape.html) and [np.reshape()](https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html). - X.shape is used to get the shape (dimension) of a matrix/vector X. - X.reshape(...) is used to reshape X into some other dimension. For example, an image can be represented by a 3D array of shape $(length, height, depth = 3)$. However, when you use an image as the input of an algorithm you often need to convert it to a vector of shape $(length*height*3, 1)$. In other words, you "unroll", or reshape, the 3D array into a 1D vector.     Task 1    Implement image2vector() that takes an input of shape (length, height, 3) and returns a vector of shape (length*height*3, 1). For example, if you would like to reshape an array v of shape (a, b, c) into a vector of shape (a*b,c) you would do:v = v.reshape((v.shape[0]*v.shape[1], v.shape[2]))  v.shape[0] = a  v.shape[1] = b  v.shape[2] = c    Note: Please don't hardcode the dimensions of image as a constant. Instead look up the quantities you need with image.shape[0], etc.<jupyter_code># GRADED FUNCTION: image2vector
def image2vector(image):
    """
    Argument:
    image -- a numpy array of shape (length, height, depth)
    
    Returns:
    v -- a vector of shape (length*height*depth, 1)
    """
    
    v = image.reshape((image.shape[0]*image.shape[1]* image.shape[2]), 1)
   


    return v
# Test yourself.
# This is a 3 by 3 by 2 array, typically images will be (num_px_x, num_px_y, 3) where 3 represents the RGB values
image = np.array([[[ 0.67826139,  0.29380381],
        [ 0.90714982,  0.52835647],
        [ 0.4215251 ,  0.45017551]],

       [[ 0.92814219,  0.96677647],
        [ 0.85304703,  0.52351845],
        [ 0.19981397,  0.27417313]],

       [[ 0.60659855,  0.00533165],
        [ 0.10820313,  0.49978937],
        [ 0.34144279,  0.94630077]]])

print ("image2vector(image) = " + str(image2vector(image)))<jupyter_output>image2vector(image) = [[0.67826139]
 [0.29380381]
 [0.90714982]
 [0.52835647]
 [0.4215251 ]
 [0.45017551]
 [0.92814219]
 [0.96677647]
 [0.85304703]
 [0.52351845]
 [0.19981397]
 [0.27417313]
 [0.60659855]
 [0.00533165]
 [0.10820313]
 [0.49978937]
 [0.34144279]
 [0.94630077]]<jupyter_text>**Expected Output**:               **image2vector(image)**          [[ 0.67826139] [ 0.29380381] [ 0.90714982] [ 0.52835647] [ 0.4215251 ] [ 0.45017551] [ 0.92814219] [ 0.96677647] [ 0.85304703] [ 0.52351845] [ 0.19981397] [ 0.27417313] [ 0.60659855] [ 0.00533165] [ 0.10820313] [ 0.49978937] [ 0.34144279] [ 0.94630077]]               3. Implementing basic functions with numpy  3.1. Sigmoid function$sigmoid(x) = \frac{1}{1+e^{-x}}$ is sometimes also known as the logistic function. It is a non-linear function used in Logistic Regression and in Simple Neural Networks.     Task 2    Implement a function that returns the sigmoid of a real number x. Use math.exp to implement the exponent.<jupyter_code># GRADED FUNCTION: basic_sigmoid

import math

def basic_sigmoid(x):
    """
    Compute sigmoid of x.

    Arguments:
    x -- A scalar

    Return:
    s -- sigmoid(x)
    """
    
    ### START CODE HERE ### (≈ 1 line of code)
    s = 1/(1 + math.exp(-x))
   
    ### END CODE HERE ###
    
    return s
basic_sigmoid(3)<jupyter_output><empty_output><jupyter_text>**Expected Output**:         ** basic_sigmoid(3) **         0.9525741268224334       We rarely use the "math" library in Machine Learning because the inputs of the functions are mostly matrices and vectors. This is why numpy is more useful.<jupyter_code>### One reason why we use "numpy" instead of "math" in Machine Learning ###
x = [1, 2, 3]
basic_sigmoid(x) # you will see this gives an error when you run it, because x is a vector.<jupyter_output><empty_output><jupyter_text>In fact, if $ x = (x_1, x_2, ..., x_n)$ is a row vector then $np.exp(x)$ will apply the exponential function to every element of x. The output will thus be: $np.exp(x) = (e^{x_1}, e^{x_2}, ..., e^{x_n})$<jupyter_code>import numpy as np

# example of np.exp
x = np.array([1, 2, 3])
print(np.exp(x)) # result is (exp(1), exp(2), exp(3))<jupyter_output>[ 2.71828183  7.3890561  20.08553692]<jupyter_text>__Hint:__ Any time you need more info on a numpy function, you can look at [the official documentation](https://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.exp.html). You can also create a new cell in the notebook and write `np.exp?` (for example) to get quick access to the documentation. We want to implement a new sigmoid function which can work on either a real number, a vector, or a matrix. The numpy array data structure can be used to represent all these shapes (vectors, matrices...).The sigmoid(x) is now defined as following:$$ \text{For } x \in \mathbb{R}^n \text{,     } sigmoid(x) = sigmoid\begin{pmatrix}    x_1  \\    x_2  \\    ...  \\    x_n  \\\end{pmatrix} = \begin{pmatrix}    \frac{1}{1+e^{-x_1}}  \\    \frac{1}{1+e^{-x_2}}  \\    ...  \\    \frac{1}{1+e^{-x_n}}  \\\end{pmatrix}\tag{1} $$     Task 3    Implement the sigmoid function using numpy.<jupyter_code># GRADED FUNCTION: sigmoid

import numpy as np 

def sigmoid(x):
    """
    Compute the sigmoid of x

    Arguments:
    x -- A scalar or numpy array of any shape

    Return:
    s -- sigmoid(x)
    """
    
    ### START CODE HERE ### (≈ 1 line of code)
    s = 1/(1+np.exp(-x))
    ### END CODE HERE ###
    
    return s
x = np.array([1, 2, 3])
sigmoid(x)<jupyter_output><empty_output><jupyter_text>**Expected Output**:               **sigmoid([1,2,3])**          array([ 0.73105858,  0.88079708,  0.95257413])         3.2. Sigmoid gradient (derivative)We often need to compute derivatives of a given function (gradient) to optimize loss functions in many ML algorithms. Let's code your first gradient function.The gradient of the sigmoid function with respect to its input x is given by the formula: $$sigmoid\_derivative(x) = \sigma'(x) = \sigma(x) (1 - \sigma(x))\tag{2}$$     Task 4    Implement the function sigmoid_derivative(x) to compute the gradient (also called the slope or derivative) of the sigmoid function with respect to its input x. Hint: You can code this function in two steps:1. Set s to be the sigmoid of x. You might find your `sigmoid(x)` function useful.2. Compute $\sigma'(x) = s(1-s)$<jupyter_code># GRADED FUNCTION: sigmoid_derivative

def sigmoid_derivative(x):
    """    
    Arguments:
    x -- A scalar or numpy array

    Return:
    ds -- Your computed gradient.
    """
    
    ### START CODE HERE ### (≈ 2 lines of code)
    y = sigmoid(x)
    ds = y*(1-y)
    
    ### END CODE HERE ###
    
    return ds
x = np.array([1, 2, 3])
print ("sigmoid_derivative(x) = " + str(sigmoid_derivative(x)))<jupyter_output>sigmoid_derivative(x) = [0.19661193 0.10499359 0.04517666]
{'fraction_non_alphanumeric': 0.13327216423901825, 'fraction_numerical': 0.09244179378369079, 'mean_word_length': 3.433146924250127, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '55899', 'license': 'MIT', 'n_tokens_mistral': 3611, 'n_tokens_neox': 3129, 'n_words': 1006, 'path': 'numpy_primer_lab.ipynb', 'repo_name': 'lyb770/labs_ml_basics', 'size': '8719'}