#!/usr/bin/env python # coding: utf-8 # # # # # # Tensor Management with NumPy # # **NumPy** is a Python library providing support for large, multi-dimensional arrays and matrices, along with a large collection of mathematical functions to operate on these arrays. # # It is the fundamental package for scientific computing with Python. # In[2]: # Import the NumPy package under the alias "np" import numpy as np # ## Tensors # # In the context of data science, a **tensor** is a set of primitive values (almost always numbers) shaped into an array of any number of dimensions. # # Tensors are the core data structures for machine learning. # ### Tensor properties # # - A tensor's **rank** is its number of dimensions. # - A dimension is often called an **axis**. # - The tensor's **shape** describes the number of entries along each axis. # ### Scalars (0D tensors) # In[3]: x = np.array(12) print(x) print('Dimensions: ' + str(x.ndim)) print('Shape: ' + str(x.shape)) # ### Vectors (1D tensors) # In[4]: x = np.array([12, 3, 6, 14]) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Matrices (2D tensors) # In[5]: x = np.array([[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]]) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### 3D tensors # In[6]: x = np.array([[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]]]) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ## Tensor shape management # # The number of entries along a specific axis is also called **dimension**, which can be somewhat confusing. # # A 3 dimensions *vector* is not the same as a 3 dimensions *tensor*. # In[7]: x = np.array([12, 3, 6]) # x is a 3 dimensions vector (1D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Tensors with single-dimensional entries # # In[8]: x = np.array([[12, 3, 6, 14]]) # x is a one row matrix (2D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[9]: x = np.array([[12], [3], [6], [14]]) # x is a one column matrix (2D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Removing single-dimensional entries from a tensor # In[10]: x = np.array([[12, 3, 6, 14]]) x = np.squeeze(x) # x is now a vector (1D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[11]: x = np.array([[12], [3], [6], [14]]) x = np.squeeze(x) # x is now a vector (1D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Reshaping a tensor # In[12]: # Reshape a (3, 2) matrix into a (2, 3) matrix x = np.array([[1, 2], [3, 4], [5, 6]]) x = x.reshape(2, 3) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[13]: # Reshape a matrix into a vector x = np.array([[1, 2], [3, 4], [5, 6]]) x = x.reshape(6, ) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[14]: # Reshape a 3D tensor into a matrix x = np.array([[[5, 6], [7, 8]], [[9, 10], [11, 12]], [[13, 14], [15, 16]]]) print ('Original dimensions: ' + str(x.ndim)) print ('Original shape: ' + str(x.shape)) x = x.reshape(3, 2*2) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Adding a dimension to a tensor # In[15]: # Add a dimension to a vector, turning it into a row matrix x = np.array([1, 2, 3]) print ('Original dimensions: ' + str(x.ndim)) print ('Original shape: ' + str(x.shape)) x = x[np.newaxis, :] print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[16]: # Add a dimension to a vector, turning it into a column matrix x = np.array([1, 2, 3]) print ('Original dimensions: ' + str(x.ndim)) print ('Original shape: ' + str(x.shape)) x = x[:, np.newaxis] print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Transposing a tensor # In[17]: # Transpose a vector (no effect) x = np.array([12, 3, 6, 14]) x = x.T # alternative syntax: x = np.transpose(x) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[18]: # Transpose a matrix x = np.array([[5, 78, 2, 34], [6, 79, 3, 35], [7, 80, 4, 36]]) x = x.T print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ## Tensor slicing # In[19]: # Slice a vector x = np.array([1, 2, 3, 4, 5, 6, 7]) print(x[:3]) print(x[3:]) # In[20]: # Slice a matrix x = np.array([[5, 78, 2, 34], [6, 79, 3, 35], [7, 80, 4, 36]]) print(x[:2, :]) print(x[2:, :]) print(x[:, :2]) print(x[:, 2:]) # ## Creating tensors # # NumPy provides several useful functions for initializing tensors with particular values. # ### Filling a tensor with zeros # In[21]: x = np.zeros(3) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[22]: x = np.zeros((3,4)) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Filling a tensor with random numbers # # Values are sampled from a "normal" (Gaussian) distribution # In[23]: x = np.random.randn(5,2) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ## Operations between tensors # # **Element-wise** operations are applied independently to each entry in the tensors being considered. # # Other operations, like dot product, combine entries in the input tensors to produce a differently shaped result. # # ### Element-wise addition # In[24]: # Element-wise addition between two vectors x = np.array([2, 5, 4]) y = np.array([1, -1, 4]) z = x + y print(z) print ('Dimensions: ' + str(z.ndim)) print ('Shape: ' + str(z.shape)) # ### Element-wise product # In[25]: # Element-wise product between two matrices (shapes must be identical) x = np.array([[1, 2, 3], [3, 2, 1]]) y = np.array([[3, 0, 2], [1, 4, -2]]) z = x * y print(z) print ('Dimensions: ' + str(z.ndim)) print ('Shape: ' + str(z.shape)) # ### Dot product # ![Dot product](https://raw.githubusercontent.com/bpesquet/deep-learning-workshop/master/notebooks/images/02fig05.jpg) # In[26]: # Dot product between two matrices (shapes must be compatible) x = np.array([[1, 2, 3], [3, 2, 1]]) # x has shape (2, 3) y = np.array([[3, 0], [2, 1], [4, -2]]) # y has shape (3, 2) z = np.dot(x, y) # alternative syntax: z = x.dot(y) print(z) print ('Dimensions: ' + str(z.ndim)) print ('Shape: ' + str(z.shape)) # ## Broadcasting # # Broadcasting is a powerful NumPy functionality. # # If there is no ambiguity, the smaller tensor can be "broadcasted" implicitly to match the larger tensor's shape before an operation is applied to them. # ### Broadcasting between a vector and a scalar # In[27]: x = np.array([12, 3, 6, 14]) x = x + 3 print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Broadcasting between a matrix and a scalar # In[28]: x = np.array([[0, 1, 2], [-2, 5, 3]]) x = x - 1 print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Broadcasting between a matrix and a vector # In[29]: x = np.array([[0, 1, 2], [-2, 5, 3]]) y = np.array([1, 2, 3]) z = x + y print(z) print ('Dimensions: ' + str(z.ndim)) print ('Shape: ' + str(z.shape)) # ## Summing tensors # ### Summing on all axes # In[30]: x = np.array([[0, 1, 2], [-2, 5, 3]]) x = np.sum(x) # x is now a scalar (0D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Summing on a specific axis # In[31]: # Sums a matrix on its first axis (rows) x = np.array([[0, 1, 2], [-2, 5, 3]]) x = np.sum(x, axis=0) # x is now a vector (1D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # In[32]: # Sums a matrix on its second axis (columns) x = np.array([[0, 1, 2], [-2, 5, 3]]) x = np.sum(x, axis=1) # x is now a vector (1D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ### Keeping tensor dimensions while summing # In[33]: # Sums a matrix on its second axis (columns), keeping the same dimensions x = np.array([[0, 1, 2], [-2, 5, 3]]) x = np.sum(x, axis=0, keepdims=True) # x is still a matrix (2D tensor) print(x) print ('Dimensions: ' + str(x.ndim)) print ('Shape: ' + str(x.shape)) # ## Normalizing tensor entries # In[34]: x = np.random.randn(3,4) print(x) print("Mean: " + str(x.mean(axis=0))) print("Standard deviation: " + str(x.std(axis=0))) x -= x.mean(axis=0) x /= x.std(axis=0) print(x) print("Final mean: " + str(x.mean(axis=0))) print("Final standard deviation: " + str(x.std(axis=0))) # In[ ]: