Data representations for neural networks — Tensor, Vector and Scaler Basics
By: Rohan Paul
Tags: Machine Learning, Python, TensorFlow, Python3, Data Science

Key attributes of Tensors

A tensor is defined by three key attributes:

Number of axes (rank) — For instance, a 3D tensor has three axes, and a matrix has two axes. This is also called the tensor’s ndim in Python libraries such as Numpy.

Shape — This is a tuple of integers that describes how many dimensions the tensor has along each axis. For instance, the previous matrix example has shape (3, 5), and the 3D tensor example has shape (3, 3, 5). A vector has a shape with a single element, such as (5,), whereas a scalar has an empty shape, ().

Data type (usually called dtype in Python libraries) — This is the type of the data contained in the tensor; for instance, a tensor’s type could be float32, uint8, float64, and so on. On rare occasions, you may see a char tensor. Note that string tensors don’t exist in Numpy (or in most other libraries), because tensors live in preallocated, contiguous memory segments: and strings, being variable length, would preclude the use of this implementation. You can see all supported dtypes at tf.dtypes.DType.

To make this more concrete, let’s look back at the data we processed in the MNIST example. First, we load the MNIST dataset: This is a dataset of 60,000 28x28 grayscale images of the 10 digits, along with a test set of 10,000 images.

from keras.datasets import mnist

(X_train_images, Y_train), (X_test_images, Y_test) = mnist.load_data()



# To display the number of axes of the tensor X_train_images , the ndim attribute:

print(X_train_images.ndim)



# Here’s its shape:

print('shape of X_train_images', X_train_images.shape)



# And this is its data type, the dtype attribute:

print('dtypes ', X_train_images.dtype) 3

The MNIST dataset will be loaded as a set of training and test inputs (X) and outputs (Y). The imputs are samples of digit images while the outputs contain the numerical value each input represents.

So what we have here is a 3D tensor of 8-bit integers. More precisely, it’s an array of 60,000 matrices of 28 × 8 integers. Each such matrix is a grayscale image, with coefficients between 0 and 255.

Let’s display the fourth digit in this 3D tensor, using Matplotlib’s imshow function. Below are some details on this function from its official docs.

The input may either be actual RGB(A) data, or 2D scalar data, which will be rendered as a pseudocolor image. For displaying a grayscale image set up the color mapping using the parameters cmap=’gray’, vmin=0, vmax=255.

The number of pixels used to render an image is set by the axes size and the dpi of the figure.

A note on image size, pixel and numpy array

Images are made up of pixels and each pixel is a dot of color. Lets say I have a cat image of 1200×800 pixels. When an image is loaded into a computer, it is saved as an array of numbers. Each pixel in a color image is made up of a Red, Green and Blue (RGB) part. It can take any value between 0 and 255 with 0 being the darkest and 255 being the brightest. In a grayscale image, each pixel is represented by just a single number between 0 and 1. If a pixel is 0, it is completely black, if it is 1 it is completely white. Everything in between is a shade of gray.

So, if the cat image was black and white, it would be a 2D numpy array with shape (800, 1200). As it is a color image, it is in fact a 3D numpy array (to represent the three different color channels) with shape (800, 1200, 3).

Now to display our mnist image data as an image, i.e., on a 2D regular raster.

digit = X_train_images[4]

plt.imshow(digit, cmap=plt.cm.binary)

# plt.imshow just finishes drawing a picture instead of printing it. If you want to print the picture, you just need to add plt.show.

plt.show()

Let’s inspect a few examples of this MNIST dataset, containing only grayscale images.

I will use the subplot() funtion — The Matplotlib subplot() function can be called to plot two or more plots in one figure. Matplotlib supports all kind of subplots including 2x1 vertical, 2x1 horizontal or a 2x2 grid.

Here is the arguments for matplotlib.pyplot.subplots:

*args, (int, int, index)

Three integers (nrows, ncols, index). The subplot will take the index position on a grid with nrows rows and ncols columns. index starts at 1 in the upper left corner and increases to the right.

There is also an important difference between plt.subplots() and plt.subplot() , notice the missing 's' at the end.

We can use plt.subplots() to make all their subplots at once and it returns the figure and axes (plural of axis) of the subplots as a tuple. A figure can be understood as a canvas where you paint your sketch.

# create a subplot with 2 rows and 1 columns

fig, ax = plt.subplots(2,1)

Whereas, you can use plt.subplot() if you want to add the subplots separately. It returns only the axis of one subplot.

fig = plt.figure() # create the canvas for plotting

ax1 = plt.subplot(2,1,1)

# (2,1,1) indicates total number of rows, columns, and figure number respectively

ax2 = plt.subplot(2,1,2)

In many cases, plt.subplots() is preferred because it gives you easier options to directly customize your whole figure

# for example, sharing x-axis, y-axis for all subplots can be specified at once

fig, ax = plt.subplots(2,2, sharex=True, sharey=True)

whereas, with plt.subplot() , one will have to specify individually for each axis which can become cumbersome.

fig = plt.figure()

for i in range(9):

plt.subplot(3, 3, i+1)

plt.tight_layout()

plt.imshow(X_test_images[i], cmap='gray', interpolation='none')

plt.title("Digits: {}".format(Y_train[i]))

plt.xticks([])

plt.yticks([])

The parameter tight-layout to fit plots within your figure cleanly.

tight_layout automatically adjusts subplot params so that the subplot(s) fits in to the figure area. This is an experimental feature and may not work for some cases. It only checks the extents of ticklabels, axis labels, and titles.

An alternative to tight_layout is constrained_layout.

Manipulating tensors in Numpy

In the previous example, we selected a specific digit alongside the first axis using the syntax X_train_images[i] . Selecting specific elements in a tensor is called tensor slicing. Let’s look at the tensor-slicing operations you can do on Numpy arrays. The following example selects digits #10 to #100 (#100 isn’t included) and puts them in an array of shape (90, 28, 28):

my_slice = X_train_images[10:100]

print(my_slice.shape)

It’s equivalent to this more detailed notation, which specifies a start index and stop index for the slice along each tensor axis. Note that : is equivalent to selecting the entire axis:

# The below slice notation with colon (:) is equivalent to the previous implementation

my_slice = X_train_images[10:100, :, :]

my_slice.shape # Even the below slice notation with colon (:) is also equivalent to the previous implementation

my_slice = X_train_images[10:100, 0:28, 0:28]

my_slice.shape

A note on Python slice notation (:)

So what does the 3 mean in somesequence[::3] ?

: is the delimiter of the slice syntax to 'slice out' sub-parts in sequences , [start:end]

[1:5] is equivalent to "from 1 to 5" (5 not included)

[1:] is equivalent to "1 to end"

[len(a):] is equivalent to "from length of a to end"



Remember that [1:5] starts with the object at index 1, and the object at index 5 is not included.

The third parameter is the step. So, it means ‘nothing for the first argument, nothing for the second, and jump by three’. It gets every third item of the sequence sliced.

[::3] just means that you have not specified any start or end indices for your slice. Since you have specified a step, 3, this will take every third entry of something starting at the first index. For example:

'123123123'[::3]

# '111'

source

a[start:stop] # items start through stop-1

a[start:] # items start through the rest of the array

a[:stop] # items from the beginning through stop-1

a[:] # a copy of the whole array

There is also the step value, which can be used with any of the above:

a[start:stop:step] # start through not past stop, by step

The key point to remember is that the :stop value represents the first value that is not in the selected slice. So, the difference between stop and start is the number of elements selected (if step is 1, the default).

The other feature is that start or stop may be a negative number, which means it counts from the end of the array instead of the beginning. So:

a[-1] # last item in the array

a[-2:] # last two items in the array

a[:-2] # everything except the last two items

Similarly, step may be a negative number:

a[::-1] # all items in the array, reversed

a[1::-1] # the first two items, reversed

a[:-3:-1] # the last two items, reversed

a[-3::-1] # everything except the last two items, reversed

Python is kind to the programmer if there are fewer items than you ask for. For example, if you ask for a[:-2] and a only contains one element, you get an empty list instead of an error. Sometimes you would prefer the error, so you have to be aware that this may happen.

Relation to slice() object

The slicing operator [] is actually being used in the above code with a slice() object using the : notation (which is only valid within [] ), i.e.:

a[start:stop:step]

is equivalent to:

a[slice(start, stop, step)]

Slice objects also behave slightly differently depending on the number of arguments, similarly to range() , i.e. both slice(stop) and slice(start, stop[, step]) are supported. To skip specifying a given argument, one might use None , so that e.g. a[start:] is equivalent to a[slice(start, None)] or a[::-1] is equivalent to a[slice(None, None, -1)] .