Tensorflow - part 1: Creating and manipulating tensors (continue)

In the previous part, we have shown how to create a tensor, convert a tensor to numpy array, some attributes of a tensor, math operations, concatenating and stacking, reshaping. Now, we will tell a litte more things about what we can do with tensors.

First, supposing that you have installed Tensorflow, let's import it.

import numpy as np # Numpy array is often used together with tensors,
                   # so you need to include it as well. 
import tensorflow as tf
print(tf.__version__) # You can also use this line to check its installed version

Calculating mean, standard deviation

In tf.math, there are functions that are used to compute the mean, sum, or standard deviation of values in a tensor. We can choose which axis to compute along. You should investigate the code below to see how axis affects the shape and values of the result tensor.

tensor_x = tf.random.uniform(shape=(5, 3), minval=-1.0, maxval=1.0) # create a tensor to work with
tensor_mean = tf.math.reduce_mean(tensor_x, axis=0) # mean
tensor_sum = tf.math.reduce_sum(tensor_x, axis=1) # sum
tensor_std = tf.math.reduce_std(tensor_x, axis=0) # standard deviation

Output

tf.Tensor(
[[ 0.47  -0.716 -0.873]
 [-0.622  0.596  0.408]
 [-0.182  0.029 -0.856]
 [-0.309  0.501 -0.693]
 [-0.473  0.25   0.309]], shape=(5, 3), dtype=float32)
tf.Tensor([-0.223  0.132 -0.341], shape=(3,), dtype=float32)
tf.Tensor([-1.119  0.383 -1.009 -0.502  0.087], shape=(5,), dtype=float32)
tf.Tensor([0.377 0.468 0.576], shape=(3,), dtype=float32)

When axis=0, the calculations happen along the vertical direction (column) of the tensor_x. There are 3 columns in the tensor_x, so the result tensor (tensor_mean and tensor_std) will have 3 values, each of which is the result of one column. When axis=1, in contrast, the calculations happen along the horizontal direction (row) of the tensor_x.

Slicing

If wanting to extract sub tensors from a tensor, we need to use slicing. Supposing we have a tensor

tensor = tf.range(0, 8)

Output

tf.Tensor([0 1 2 3 4 5 6 7], shape=(8,), dtype=int32)

Extract from the second element to the third element. It is also the same as in python slicing that the last element (at position 4) is excluded.

tensor[1:4]

Output

tf.Tensor([1 2 3], shape=(3,), dtype=int32)

Extract 3 last elements

tensor[-3:]

Output

tf.Tensor([5 6 7], shape=(3,), dtype=int32)

You can also use tf.slice for this with one difference. For the second argument size, it does not use the end index like above, but size instead. Get a tensor from previous post as example.

stack_axis_1_tensor # from previous post

Output

tf.Tensor(
[[[  0   1   1   2   3   5   8  13  21  34]
  [ 22  40  42  31  10 -13  17  19 -24  46]]

 [[  5   6   9   7  11  10   0   4   3   2]
  [ 17 -31  -3 -43  17   7  46  19  12  11]]

 [[ 20  30  40  50  60 100  90  80  70  10]
  [-27  -3 -20  34 -50   6  23 -12 -26 -50]]], shape=(3, 2, 10), dtype=int32)

Use tf.slice:

tf.slice(stack_axis_1_tensor, begin=[1, 0, 3], size=[2, 1, 4])

Output

tf.Tensor(
[[[  7  11  10   0]]

 [[ 50  60 100  90]]], shape=(2, 1, 4), dtype=int32)

We can also use step for strided slice. For example, if you want to extract one element in every two elements (step=2), you can use slicing as above. tf.gather is an another way to do this, but with this you need need to specify correctly which positions to get the value.

tensor[::2]
tf.gather(tensor, indices=[0, 2, 4, 6]) # tf.gather to get elements at even indices: 0, 2, 4, 6

Output

tf.Tensor([0 2 4 6], shape=(4,), dtype=int32)
tf.Tensor([0 2 4 6], shape=(4,), dtype=int32)

tf.gather can even get elements at indices that are not equally spaces and the new tensor can also be shuffled

string_tensor = tf.constant(list('Hello world'))
tf.gather(string_tensor, indices=[8, 1, 4]) # Get at positions 8, 1, 4 respectively

Output

tf.Tensor([b'r' b'e' b'o'], shape=(3,), dtype=string)

Here, the indices list does not contain positions in the increasing or decreasing order, but they are shuffled in an unordered style.

Expand dims

Sometimes you need to add a new axis to a tensor so it can be computed (or concatenated) together with another tensor. Some operations required the involved tensors have the same number of dimensions.

tensor_x = tf.zeros([50, 50, 4])
tf.expand_dims(tensor_x, axis=0).shape
tf.expand_dims(tensor_x, axis=1).shape

Output

(1, 50, 50, 4)
(50, 1, 50, 4)

The new axis has been added at the specified position (0 and 1).

Tiling

Repeat elements in tensor_a by a factor as specified for each dim in tensor_b

tensor_a = tf.constant([[1, 2], [3, 4]], tf.int32) 
tensor_b = tf.constant([1, 3], tf.int32) # repeat 1 time in dimension 0, repeat 3 times in dimension 1
print(tf.tile(tensor_a, tensor_b))
tensor_c = tf.constant([3, 1], tf.int32) # repeat 3 times in dimension 0, repeat 1 time in dimension 1
print(tf.tile(tensor_a, tensor_c))

Output

tf.Tensor(
[[1 2 1 2 1 2]
 [3 4 3 4 3 4]], shape=(2, 6), dtype=int32)

tf.Tensor(
[[1 2]
 [3 4]
 [1 2]
 [3 4]
 [1 2]
 [3 4]], shape=(6, 2), dtype=int32)

In the example above, the tensor_b is [1, 3]. This means the tensor_a is replicated one time along the 0-th dimension (row) and 3 times along the 1-th dimension (column).

Squeeze

Removing the unnecessary dimensions (dimensions that have size 1)

tensor_x = tf.zeros((1, 2, 1, 4, 1))
tensor_sqz = tf.squeeze(tensor_x)
print('old shape:', tensor_x.shape, '-->', 'new shape:', tensor_sqz.shape)
tensor_sqz = tf.squeeze(tensor_x, axis=(2, 4)) # We can also choose what axis to remove
print('old shape:', tensor_x.shape, '-->', 'new shape:', tensor_sqz.shape)

Output

old shape: (1, 2, 1, 4, 1) --> new shape: (2, 4)
old shape: (1, 2, 1, 4, 1) --> new shape: (1, 2, 4)

Split

There are 2 types of splits:

  • Split a tensor into multiple tensors of equal size
tf.random.set_seed(1)
tensor = tf.random.uniform((9,))
print(tensor.numpy())

tensor_splits = tf.split(tensor, num_or_size_splits=3)
[item.numpy() for item in tensor_splits]

Output

[0.165 0.901 0.631 0.435 0.292 0.643 0.976 0.435 0.66 ]

[array([0.165, 0.901, 0.631], dtype=float32),
 array([0.435, 0.292, 0.643], dtype=float32),
 array([0.976, 0.435, 0.66 ], dtype=float32)]
  • Split a tensor into multiple tensors of specified size for each new tensor
tensor = tf.random.uniform((7,))
print(tensor.numpy())

tensor_splits = tf.split(tensor, num_or_size_splits=[3, 4]) # size
[item.numpy() for item in tensor_splits]

Output

[0.829 0.634 0.515 0.391 0.581 0.048 0.178]

[array([0.829, 0.634, 0.515], dtype=float32),
 array([0.391, 0.581, 0.048, 0.178], dtype=float32)]

First tensor has size 3 and second tensor has size 4. That is because the numbers that we give to the paramerter num_or_size_splits.

Unique

Let's have a small exercise here. We have 2 flat tensors and want to check if all of their elements are equal.

Supposing we have 2 tensors: reshape_0 and reshape_1:

print(reshape_0)
print(reshape_1)

Output

tf.Tensor(
[  0   1   1   2   3   5   8  13  21  34   5   6   9   7  11  10   0   4
   3   2  20  30  40  50  60 100  90  80  70  10  22  40  42  31  10 -13
  17  19 -24  46  17 -31  -3 -43  17   7  46  19  12  11 -27  -3 -20  34
 -50   6  23 -12 -26 -50], shape=(60,), dtype=int32)

tf.Tensor(
[  0   1   1   2   3   5   8  13  21  34  22  40  42  31  10 -13  17  19
 -24  46   5   6   9   7  11  10   0   4   3   2  17 -31  -3 -43  17   7
  46  19  12  11  20  30  40  50  60 100  90  80  70  10 -27  -3 -20  34
 -50   6  23 -12 -26 -50], shape=(60,), dtype=int32)

To check if they are equal or not, use tf.math.equal: this method checks in an element-wise way and return a list of True, False values. If the list are all True values, it means the 2 tensors are equal.

print('Use tf.math.equal: ', tf.math.equal(reshape_0, reshape_1))

Output

Use tf.math.equal:  tf.Tensor(
[ True  True  True  True  True  True  True  True  True  True False False
 False False False False False False False False False False False False
 False False False False False False False False False False False False
 False  True False False False False False False False False False False
 False False  True  True  True  True  True  True  True  True  True  True], shape=(60,), dtype=bool)

In the case when there are too many elements, it would be better if we only show the unique elements.

print('Use tf.unique: ', tf.unique(tf.math.equal(reshape_0, reshape_1)))

Output

Use tf.unique:  Unique(y=<tf.Tensor: shape=(2,), dtype=bool, numpy=array([ True, False])>, idx=<tf.Tensor: shape=(60,), dtype=int32, numpy=
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int32)>)

Moreover, you can count the number of repetitions of each unique element.

print('Use tf.unique_with_count', tf.unique_with_counts(tf.math.equal(reshape_0, reshape_1)))

Output

Use tf.unique_with_count: UniqueWithCounts(y=<tf.Tensor: shape=(2,), dtype=bool, numpy=array([ True, False])>, idx=<tf.Tensor: shape=(60,), dtype=int32, numpy=
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int32)>, count=<tf.Tensor: shape=(2,), dtype=int32, numpy=array([21, 39], dtype=int32)>)

You can see in the end of the output there is [21, 39] which means there are 21 values 0 (True) and 39 values 1 (False). Therefore, not all elements are equal.

Broadcasting

When running computation on 2 tensors having different sizes, the smaller tensor will be automatically expanded to be matched to the larger one.

smaller = tf.constant(2) # is automatically expanded into [2, 2, 2]
larger = tf.constant([1, 2, 3])
print(smaller * larger)
print(tf.multiply(smaller, larger))

Output

tf.Tensor([2 4 6], shape=(3,), dtype=int32)
tf.Tensor([2 4 6], shape=(3,), dtype=int32)

Broadcasting is also useful when we consider the case of outer product of 2 tensors

x = tf.reshape(larger, [3, 1])
print(x)
y = tf.range(1, 6)
print(y)
print(tf.multiply(x, y)) # like outer product

Output

tf.Tensor(
[[1]
 [2]
 [3]], shape=(3, 1), dtype=int32)

tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)

tf.Tensor(
[[ 1  2  3  4  5]
 [ 2  4  6  8 10]
 [ 3  6  9 12 15]], shape=(3, 5), dtype=int32)

The end