More exercises#

Congratulations, you finished the tutorial! You will find below additional NumPy exercises of increasing difficulty.

import numpy as np

Quiz 11#

Create a vector of size 20 with an alternating pattern of 0 and 1.

Expected output: [0, 1, 0, 1, 0, 1, ..., 0, 1]

# ADD CODE HERE
zero_one = None

print(zero_one)

Quiz 12#

Swap the first half of a vector with its last half.

Example: [1,2,3, 4,5,6]  -->  [4,5,6, 1,2,3]

Hints:

  • The size of a vector is given by the attribute .size or .shape[0].

  • Use the integer division n//2 to compute the length of half a vector.

  • The function np.concatenate() merges a copy of the input vectors into a new array.

# This is the vector to work with
vector = np.array([4,2,8,  7,1,3])

# ADD CODE HERE
swapped = None

print("vector:", vector)
print("swapped:", swapped)

Quiz 13#

Reshape an array of n elements into a matrix with n/2 rows and 2 columns.

Hint: .reshape()

# This is the array to work with
a = np.array([[5,6,3], [3,2,7], [4,2,8], [7,1,3]])

reshaped = None # YOUR CODE HERE

print("--- before ---")
print(a)
print("--- after ---")
print(reshaped)

Quiz 14#

Given a vector with an even number of elements, compute the sum of two consecutive elements.

Example: [1, 2, 3, 4, 5, 6] –> [3, 7, 11]

Hint: Reshape the vector into a matrix with n/2 rows and 2 columns.

np.random.seed(6)

# This is the vector to work with
a = np.random.randint(10,size=(6,))

sum_pairs = None # YOUR CODE HERE

print("vector:", a)
print("sum by pairs:", sum_pairs)

Quiz 15#

Given a matrix with an even number of rows, compute the sum of two consecutive rows.

Expected result: Vector holding the sum of elements on 1st and 2nd rows, the sum of elements on 3rd and 4th rows, and so on.

Hint: Reshape the matrix so as to align the rows that must be summed together.

np.random.seed(1)

# This is the matrix to work with
A = np.random.randint(10,size=(6,4))

sum_rows = None # YOUR CODE HERE

print("---- A ----")
print(A)
print()
print("Sum:", sum_rows)

Quiz 16#

Replace the minimum value of a matrix by -10.

Hint: You can reshape the matrix into a vector, get the position of the minimum element with .argmin(), and modify the minimum value.

np.random.seed(1)

# This is the matrix to work with
matrix = np.random.randint(50, size=(4,6))

print("--- before ---")
print(matrix)

# ADD CODE HERE
None

print()
print("----- after -----")
print(matrix)

Quiz 17#

Divide each row of a matrix by its maximum value, and put the result in a new matrix. Exploit broadcasting to avoid loops!

Hint: Remember what the argument keepdims does in the function np.max().

np.random.seed(1)

# This is the matrix to work with
matrix = np.random.randint(10, size=(3,6))

# STEP 1. Compute the max along the rows
max_values = None # YOUR CODE HERE

# STEP 2. Divide the matrix by the max values
new_matrix = None # YOUR CODE HERE

Quiz 18#

Compute the following expression:

\[ {\sf sigmoid}({\bf w}^\top {\bf x}) = \frac{1}{1+e^{-(w_0 + w_1 x_1 + \dots + w_Q x_Q)}} \]

where

\[\begin{split} {\bf w} = \begin{bmatrix}w_0\\w_1\\\vdots\\w_Q\end{bmatrix} \in \mathbb{R}^{Q+1} \qquad\qquad {\bf x} = \begin{bmatrix}\vphantom{1}\\x_1\\\vdots\\x_Q\end{bmatrix} \in \mathbb{R}^{Q} \end{split}\]

with the convention \(x_0=1\).

Hint: Note that the vectors don’t have the same size:

  • w - vector of shape (Q+1,)

  • x - vector of shape (Q,).

To get around this, you can proceed as follows:

  • Slice w so as to extract the elements w[1], ..., w[Q].

  • Compute the scalar product between x and the above elements.

  • Add w[0] to the final result.

# These are the vectors to work with
x = np.array([3, 2, 7])
w = np.array([-3, 2, 0.5, -1])

# STEP 1. Compute 'w0 + w1*x1 + ... + wQ*xQ'.
w_dot_x = None # YOUR CODE HERE
    
# STEP 2. Compute the sigmoid of 'x_dot_w', as defined above.
logistic = None # YOUR CODE HERE

Quiz 19#

Create a 8x8 matrix and fill it with a checkerboard pattern.

Expected output:

[[0 1 0 1 0 1 0 1]
  [1 0 1 0 1 0 1 0]
  [0 1 0 1 0 1 0 1]
  [1 0 1 0 1 0 1 0]
  [0 1 0 1 0 1 0 1]
  [1 0 1 0 1 0 1 0]
  [0 1 0 1 0 1 0 1]
  [1 0 1 0 1 0 1 0]]
# ADD CODE HERE
checkboard = None

print(checkboard)

Quiz 20#

Swap (in-place) the 2nd and 4th column of a matrix.

Hint: Recall when data is shared or copied:

  • No copy - A simple assignment produces an alias of the original array.

  • Shared data - Slicing creates a view of the original array.

  • Copied data - The method .copy() makes a copy of the original array.

# This is the matrix to work with
matrix = np.arange(15).reshape(3,5)

print("--- matrix (before swap) ---")
print(matrix)

# ADD CODE HERE
None

print("\n--- matrix (after swap) ---")
print(matrix)

Quiz 21#

Find the element in a vector nearest to the value 1.

Hint: Given the vector x, compute the absolute values of x-1, and then find the index i of the minimum element.

Example: step-by-step

  • Consider the vector x = [5, -1, 3, 1.2, 8, 0]. What is the element in x nearest to the value 1?

  • The operation x-1 results in the vector [4, -2, 2, 0.2, 7, -1].

  • The absolute value of x-1 is thus [4, 2, 2, 0.2, 7, 1].

  • What is the minimum element of this vector? Take notice of its position. It is the same as the element you are looking for!

# This is the vector to work with
vector = np.array([5, -1, 3, 1.2, 8, 0])

# ADD CODE HERE
nearest_value = None

print('nearest value:', nearest_value)

Quiz 22#

Given a matrix with an even number of columns, compute the sum of two consecutive columns.

Expected result: Vector holding the sum of elements on 1st and 2nd columns, the sum of elements on 3rd and 4th columns, and so on.

Hint: Recall that a matrix is stored in row-major order, so you need to come up with a creative way of reshaping the matrix.

np.random.seed(1)

# This is the matrix to work with
A = np.random.randint(10,size=(6,4))

sum_cols = None # YOUR CODE HERE

print("--- A ---")
print(A)
print()
print("Sum:", sum_cols)

Quiz 23#

Implement a function that computes the following expression:

\[\begin{split} {\sf softmax}({\bf x}) = \begin{bmatrix} \dfrac{e^{x_0}}{\sum_{j=0}^{N-1} e^{x_j}}\\ \vdots \\ \dfrac{e^{x_{N-1}}}{\sum_{j=0}^{N-1} e^{x_j}} \end{bmatrix}. \end{split}\]

When the input is a matrix, the function must operate according to an input parameter axis:

  • axis=None - sum over all the elements

  • axis=0 - sum over the columns

  • axis=1 - sum over the rows.

Hint: In the skeleton function given below, print the shapes of intermediate variables x_exp, x_sum and s to help you with broadcasting.

  • The shape of x_sum must be (1,1), (1,5) or (2,1) depending on the input parameter axis.

  • The shape of x_exp and s must always be (2,5).

  • Then, the division x_exp/x_sum will work due to broadcasting.

def softmax(x, axis=None):
    """Compute the softmax of x"""
        
    # Compute the exponential.
    x_exp = None # YOUR CODE HERE

    # Sum the elements along the specified axis. Don't forget to keep the dimensions!
    x_sum = None # YOUR CODE HERE
    
    # Compute the division. It should automatically use broadcasting!
    s = None # YOUR CODE HERE

    return s
x = np.array([[9, 2, 5],
              [7, 5, 0]])

s_all  = softmax(x)
s_cols = softmax(x, axis=0)
s_rows = softmax(x, axis=1)

Quiz 24#

Implement a function that computes the following expression:

\[ J({\bf w}) = \sum_{n=1}^N \big(y^{(n)} - {\bf w}^\top {\bf x}^{(n)}\big)^2, \]

where

\[\begin{split} {\bf w} = \begin{bmatrix}w_0\\w_1\\\vdots\\w_Q\end{bmatrix} \in \mathbb{R}^{Q+1} \qquad\qquad {\bf x}^{(n)} = \begin{bmatrix}\vphantom{1}\\x_1\\\vdots\\x_Q\end{bmatrix} \in \mathbb{R}^{Q} \qquad\qquad y^{(n)}\in\mathbb{R} \end{split}\]

with the convention \(x_0^{(n)}=1\).

Hint: This computation can be vectorized as follows.

  • The vectors \({\bf x}^{(n)}\) are stacked in a matrix \(X\in\mathbb{R}^{N\times Q}\), whereas the scalars \(y^{(n)}\) are stacked in a vector \({\bf y}\in\mathbb{R}^N\):

\[\begin{split} X = \begin{bmatrix} \_\!\_\; { {\bf x}^{(1)}}^\top \_\!\_ \\ \vdots\\ \_\!\_\; { {\bf x}^{(N)}}^\top \_\!\_ \\ \end{bmatrix} \qquad\qquad {\bf y} = \begin{bmatrix} {y^{(1)}}^\top \\ \vdots\\ {y^{(N)}}^\top \end{bmatrix}. \end{split}\]
  • The products \(z^{(n)} = {\bf w}^\top{\bf x}^{(n)}\), for every index \(n\), are computed by multiplying \(X\) by \({\bf w}\):

\[\begin{split} {\bf z} = X{\bf w} = \begin{bmatrix} {\bf w}^\top{\bf x}^{(1)}\\ \vdots\\ {\bf w}^\top{\bf x}^{(N)} \end{bmatrix}.\end{split}\]
  • The distance \(J\) is now equal to the squared norm of the difference between \({\bf z}\) and \({\bf y}\):

\[ J = \|{\bf z} - {\bf y}\|^2 = \sum_{n=1}^{N} \big(z^{(n)}-y^{(n)}\big)^2. \]

Note hovewer that the shapes of X and w don’t align for a matrix-vector product. To get around this, you can proceed as follows:

  • Slice w so as to extract the elements w[1], ..., w[Q].

  • Compute the product between X and the above elements.

  • Add w[0] to the final result.

def ssd_cost(w, X, y):
    """Calculates the sum of squared differences.
     
    Arguments:
    w -- flat vector of shape (Q+1,)
    X -- matrix of shape (N, Q)
    y -- flat vector of shape (N,)

    Returns:
    s -- Scalar
    """
    
    # Compute w0 + w1*X1 + ... + wQ*XQ
    w_dot_x = None # YOUR CODE HERE

    # Compute the sum of squared distances between x_dot_w and y
    J = None # YOUR CODE HERE
    
    return J

Quiz 25#

The Game of Life is a grid of square cells that can be in one of two possible states: live or dead.

life

The player creates an initial configuration of live cells and observes how they evolve. Every cell interacts with its 8 neighbours (the cells horizontally, vertically, or diagonally adjacent). At each step in time, the following transitions occur.

  • Underpopulation: Any live cell with fewer than two live neighbours dies.

  • Overcrowding: Any live cell with more than three live neighbours dies.

  • Stability: Any live cell with two or three live neighbours lives unchanged to the next generation.

  • Rebirth: Any dead cell with exactly three live neighbours becomes a live cell.

The initial pattern constitutes the ‘seed’ of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed. Births and deaths happen simultaneously. The rules continue to be applied repeatedly to create further generations.

The Game of Life can be implemented by a matrix whose elements represent the single cells:

  • 0 means the cell is dead,

  • 1 means the cell is alive.

board = [[0,0,0,0,0,0],
         [0,0,0,1,0,0],
         [0,1,0,1,0,0],
         [0,0,1,1,0,0],
         [0,0,0,0,0,0],
         [0,0,0,0,0,0]]

The cells evolve through a for-loop which involves two operations at each iteration:

  • counting the neighbors,

  • applying the evolution rules.

def game_of_life(board, steps):
    for i in range(steps):
        count = neighbor_counting(board)
        board = evolution_rules(board, count)    
    return board
def neighbor_counting(board):
    board = np.array(board)
    padded = np.pad(board, (1,1), 'constant')
    count  = padded[ :-2, :-2] + padded[:-2, 1:-1] + padded[ :-2, 2:] \
           + padded[1:-1, :-2] +                   + padded[1:-1, 2:] \
           + padded[2:  , :-2] + padded[2: , 1:-1] + padded[2:  , 2:]
    return count

Implement a function that applies the evolution rules of the Game of Life.

Hint: Use advanced indexing to avoid loops!

  • Translate the evolution rules to boolean conditions,

  • Use the boolean masks to set the new values.

def evolution_rules(board, count):
    """
    Arguments:
     board -- matrix whose elements indicate whether a cell is dead (0) or alive (1)
     count -- matrix whose elements indicate the number of alive neighbors of a cell

    Returns:
     new_board -- matrix with the same shape as 'board'
    """
    board = np.array(board)          # Read the current state from this array
    new_board = np.zeros_like(board) # Write the new state to this array
            
    # Underpopulation
    R1 = None # YOUR CODE HERE
    ... # YOUR CODE HERE
    
    # overcrowding
    R2 = None # YOUR CODE HERE
    ... # YOUR CODE HERE
    
    # stability 
    R3 = None # YOUR CODE HERE
    ... # YOUR CODE HERE
    
    # rebirth
    R4 = None # YOUR CODE HERE
    ... # YOUR CODE HERE

    return new_board