An Introduction to Tensorflow and Tensors
- Goals
- Imports
- Tensors
- Working With Tensors
- Shuffling Tensors
- Creating Tensors: Ones and Zeros
- Changing Tensor Datatypes
- Getting information from tensors
- Manipulating tensors (tensor operations)
- Add A Dimension with newaxis and expanddims
- Add
- Multiply
- SubtractTensorFlow: an open-source end-to-end machine learning library useful for things like data "wrangling", modelling, building machine-learning models, and more.
Rather than building machine learning and deep learning models from "scratch", tensorflow contains many of the most common machine learning functions you'll want to use.
Goals
- creating tensors- Getting information from tensors
- Manipulating tensors
- Comparing & Combining Tensorflow, Tensors, and NumPy
- Using
@tf.functionas a way to speed up Python functions - Using GPUs with TensorFlow
Imports
import datetime
import numpy as np
import tensorflow as tf
print(f'tensorlfow version: {tf.__version__}') # find the version number (should be 2.x+)Tensors
Like Numpy Arrays
NumPy arrays are similar to tensors. One major difference between tensors and NumPy arrays (also an n-dimensional array of numbers) is that tensorflow (and tensors) can be used & processed on GPUs (graphical processing units) and TPUs (tensor processing units). One benefit of being able to run on GPUs and TPUs is faster computation. GPUs and TPUs will process data faster than CPUs.Tensors are like multi-dimensional numerical representations (also referred to as n-dimensional, where n can be any number) of things:
- numbers themselves, using tensors to represent the price of houses)
- images, using tensors to represent the pixels of an image)
- text, using tensors to represent words
Working With Tensors
Creating Tensors with tf.constant()
Creating tensors, "from scratch", may not be common. TensorFlow has modules built-in (such as tf.io and tf.data) which are able to read input data sources and automatically convert them to tensors. Here, though, a look at creating tensors with tf.constant().
A scalar is known as a "rank 0" tensor. Scalars have no dimensions - just a single number.
By default, TensorFlow creates tensors with either an int32 or float32 datatype.
This is known as [32-bit precision](https://en.wikipedia.org/wiki/Precision_(computer_science) (the higher the number, the more precise the number, the more space it takes up on your computer).
There are different types of numbers and number datatypes:
- scalar: a single number.
- vector: a number with direction (e.g. wind speed with direction).
- matrix: a 2-dimensional array of numbers.
- tensor: an n-dimensional arrary of numbers (where n can be any number, a 0-dimension tensor is a scalar, a 1-dimension tensor is a vector).
"Matrix" and "Tensor" may be used interchangably.
For more on the mathematical difference between scalars, vectors and matrices see the visual algebra post by Math is Fun.
Scalar
# Create a scalar (rank 0 tensor)
scalar = tf.constant(7)
print(f'scalar tensor: {scalar}')
scalar# Check the number of dimensions of a tensor (ndim stands for number of dimensions)
scalar.ndim# Create a vector (more than 0 dimensions)
vector = tf.constant([10, 10])
vector# Check the number of dimensions of our vector tensor
vector.ndim# Create a matrix (more than 1 dimension)
matrix = tf.constant([[10, 7],
[7, 10]])
matrixmatrix.ndim# Create another matrix and define the datatype
another_matrix = tf.constant([[10., 7.],
[3., 2.],
[8., 9.]], dtype=tf.float16) # specify the datatype with 'dtype'
another_matrix# Even though another_matrix contains more numbers than matrix, its 'ndim' (dimension count) is the same:
another_matrix.ndim# How about a tensor with more than 2 dimensions
tensor = tf.constant([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]],
[[13, 14, 15],
[16, 17, 18]]])
tensortensor.ndimOn Tensor Dimensions
This tensor is known as a rank 3 tensor (3-dimensions):tensor = tf.constant([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]],
[[13, 14, 15],
[16, 17, 18]]])
Dimension Count from Source Data
Tensors may be created based on a series of images:- image-width of 224
- image-height of 224
- 3 color channels: (red, green blue)
- instruct tensorflow to process 32 images at-a-time, in a "batch" of 32 That tensor might have a shape (224, 224, 3, 32)
# Create a rank 5 (5 dimensions) tensor of 50 numbers between 0 and 100
G = tf.constant(np.random.randint(0, 100, 50), shape=(1, 1, 1, 1, 50))
print(f'G Shape: {G.shape}')
print(f'G Dimension count: {G.ndim}')
# Squeeze tensor G (remove all 1 dimensions)
G_squeezed = tf.squeeze(G)
print(f'SQUEEZED G Shape: {G_squeezed.shape}')
print(f'SQUEEZED G Dimension count: {G_squeezed.ndim}')Creating Tensors with tf.Variable()
Often, when working with data, tensors are created automatically. Here, creating tensors using tf.Variable().
Constants are Immutable, Variables are mutable
The difference betweentf.Variable() and tf.constant() is tensors created with tf.constant() are immutable (can't be changed, can only be used to create a new tensor), where as, tensors created with tf.Variable() are mutable (can be changed).
To change an element of a tf.Variable() tensor requires the assign() method.
# Create the same tensor with tf.Variable() and tf.constant()
changeable_tensor = tf.Variable([10, 7])
unchangeable_tensor = tf.constant([10, 7])
print(changeable_tensor)
print(unchangeable_tensor)#
# FAILING at changing a Variable without ".assign()"
#
# Will error (requires the .assign() method)
# changeable_tensor[0] = 7
# changeable_tensor
# will return
# TypeError: 'ResourceVariable' object does not support item assignment#
# Success changing a tensor
#
changeable_tensor[0].assign(7)
changeable_tensor#
# FAILURE attemtpting to change a constant tensor
#
# unchangeable_tensor[0].assign(7)
# unchangleable_tensor
# will return
# AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute 'assign'Creating random tensors
Random tensors are tensors of some abitrary size which contain random numbers.Why would you want to create random tensors?
Random tensors are what neural networks use to intialize their weights when recognizing (patterns) in data. The process of a neural network learning might often involve taking a random n-dimensional array of numbers and refining them until they represent some kind of pattern (a compressed way to represent the original data).
Here, creating random tensors by using the tf.random.Generator class.
# Create two random (but the same) tensors
random_1 = tf.random.Generator.from_seed(42) # set the seed for reproducibility
random_1 = random_1.normal(shape=(3, 2)) # create tensor from a normal distribution
random_2 = tf.random.Generator.from_seed(42)
random_2 = random_2.normal(shape=(3, 2))
# Are they equal?
print(random_1)
print(random_2)
print(f'random_1 == random_2: {random_1 == random_2}')The random tensors here are pseudorandom numbers (they appear as random, but really aren't). Once a seed is set,tensorflow will generate the same random numbers.
# Create two random (and different) tensors
random_3 = tf.random.Generator.from_seed(42)
random_3 = random_3.normal(shape=(3, 2))
random_4 = tf.random.Generator.from_seed(11)
random_4 = random_4.normal(shape=(3, 2))
# Check the tensors and see if they are equal
# Are they equal?
print(random_3)
print(random_4)
print(f'random_3 == random_4: {random_3 == random_4}')# Shuffle a tensor (valuable for when you want to shuffle your data)
not_shuffled = tf.constant([[10, 7],
[3, 4],
[2, 5]])
# Gets different results each time
tf.random.shuffle(not_shuffled)# Shuffle in the same order every time using the seed parameter (won't acutally be the same)
tf.random.shuffle(not_shuffled, seed=42)Rule #4 of the tf.random.set_seed() documentation says that
"4. If both the global and the operation seed are set: Both seeds are used in conjunction to determine the random sequence."
tf.random.set_seed(42) sets the global seed, and the seed parameter in tf.random.shuffle(seed=42) sets the operation seed.
Because, "Operations that rely on a random seed actually derive it from two seeds: the global and operation-level seeds. This sets the global seed."
# Shuffle in the same order every time
# Set the global random seed
tf.random.set_seed(42)
# Set the operation random seed
tf.random.shuffle(not_shuffled, seed=42)# Set the global random seed
tf.random.set_seed(42) # if you comment this out you'll get different results
# Set the operation random seed
tf.random.shuffle(not_shuffled)Creating Tensors: Ones and Zeros
tf.ones()will create a tensor of all onestf.zeros()will create a tensor of all zeros
# Make a tensor of all ones
print(tf.ones(shape=(3, 2)))
# Make a tensor of all zeros
print(tf.zeros(shape=(3, 2)))numpy_A = np.arange(1, 25, dtype=np.int32) # create a NumPy array between 1 and 25
A = tf.constant(numpy_A,
shape=[2, 4, 3]) # note: the shape total (2*4*3) has to match the number of elements in the array
numpy_A, A# Create a new tensor with default datatype (float32)
B = tf.constant([1.7, 7.4])
# Create a new tensor with default datatype (int32)
C = tf.constant([1, 7])
print(f'B.dtype: {B.dtype}')
print(f'C.dtype: {C.dtype}')# Change from float32 to float16 (reduced precision)
B = tf.cast(B, dtype=tf.float16)
print(f'B.dtype: {B.dtype}')# Change from int32 to float32
C = tf.cast(C, dtype=tf.float32)
print(f'C.dtype: {C.dtype}')Getting information from tensors
Attributes
Common bits to get about a tensor:- Shape: The length (number of elements) of each of the dimensions of a tensor
- Rank: The number of tensor dimensions:
- A scalar has rank 0
- a vector has rank 1
- a matrix is rank 2
- a tensor has rank
n
- Axis or Dimension: A particular dimension of a tensor
- Size: The total number of items in the tensor
These might be especially useful when trying to organize the shapes of input data to inform/match the shapes of a model. I.E, , making sure the shape of image tensors are the same shape as a models input layer.
# Create a rank 4 tensor (4 dimensions)
rootDim = 2
nestedOneDim = 3
nestedTwoDim = 4
nestedThreeDim = 5
rank_4_tensor = tf.zeros([rootDim, nestedOneDim, nestedTwoDim, nestedThreeDim])
rank_4_tensorrank_4_tensor.shape, rank_4_tensor.ndim, tf.size(rank_4_tensor)# Get various attributes of tensor
print("Datatype of every element:", rank_4_tensor.dtype)
print("Number of dimensions (rank):", rank_4_tensor.ndim)
print("Shape of tensor:", rank_4_tensor.shape)
print("Elements along axis 0 of tensor:", rank_4_tensor.shape[0])
print("Elements along last axis of tensor:", rank_4_tensor.shape[-1])
print("Total number of elements (2*3*4*5):", tf.size(rank_4_tensor).numpy()) # .numpy() converts to NumPy array# Get the first 2 items of each dimension
rank_4_tensor[:2, :2, :2, :2]# Get the dimension from each index except for the final one
rank_4_tensor[:1, :1, :1, :]# Create a rank 2 tensor (2 dimensions)
rank_2_tensor = tf.constant([[10, 7],
[3, 4]])
# Get the last item of each row
rank_2_tensor[:, -1]Manipulating tensors (tensor operations)
Finding patterns in tensors (numberical representation of data) requires manipulating them.Again, when building models in TensorFlow, much of this pattern discovery is done for you.
Add A Dimension with newaxis and expanddims
Add dimensions to a tensor whilst keeping the same information present usingtf.newaxis
# Add an extra dimension (to the end)
rank_3_tensor = rank_2_tensor[..., tf.newaxis] # in Python "..." means "all dimensions prior to"
print('---- rank_2_tensor ----')
print(rank_2_tensor)
print('---- rank_3_tensor ----')
print(rank_3_tensor)#
# expand_dims
#
tf.expand_dims(rank_2_tensor, axis=-1) # "-1" means last axisAdd
Here, creating a tensor withtf.constant(), adding 10, and seeing that the original tensor is unchanged (the addition gets done on a copy).# You can add values to a tensor using the addition operator
tensor = tf.constant([[10, 7], [3, 4]])
print(tensor + 10)
print(tensor)print(tensor * 10)
# Use the tensorflow function equivalent of the '*' (multiply) operator
print(tf.multiply(tensor, 10))tensor - 10