A tale of Tensorflow.js - Chapter 1: Tensors
Tags: javascript, machinelearning, tensorflow, deeplearning
Published: 2021-01-18 15:26:12

So it's been a while since I posted here, but here I am again!
Hey everyone, it's Álvaro, and today we are going to start a series about deep learning with **tensorflow.js**.

What is Tensorflow?
Well, Tensorflow is an open-source machine learning framework. It has a lot of tools and resources to make our development so so easy.

Why it's named Tensorflow?
The basic operation unit in this framework it's called **Tensor**, it can be either a variable or a constant, and depending on it, it can be trained or not.
A tensor usually is a multi-dimensial vector or matrix.

What is training a tensor?
We usually train our models, our neuronal networks, and training is the process that our models go through meanwhile they learn their input parameters to produce optimal outputs parameters.

For the sake of simplicity, let's keep it simple today.

## Let's start with some code

First, let's start making a blank HTML template and add the tensorflow.js library.
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>A Tale of Tensorflow.js</title>
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
</head>
<body>

</body>
</html>
```

Now, we can start playing with **tensors**:
```html
  <script>
    async function main() {
      const tensorOne = tf.tensor(1)
      const tensorTwo = tf.tensor([1.0, 2.0, 3.0, 4.0])
      const tensorThree = tf.tensor([[1.0, 2.0], [3.0, 4.0]])

      console.log(tensorOne)
      console.log(tensorTwo)
      console.log(tensorThree)
    }
    main();
  </script>
```
If we watch our console, we can see a lot of properties on the tensor:
**dtype**: also inferred, we can specify it when we create the tensor.
**id**: Each tensor has an unique ID, and now it's autogenerated as you can see.
**rankType**: it's the number of dimensions that our tensor has.
**shape**: shape it's inferred of the values we passed to the tensor. We can play with this next. We can see that for our first tensor the shape is 0, it has no dimensions, for the second tensor it's 4, it has one dimension with 4 values (let's say values for now). On the last tensor we can see `[2, 2]`, we have two dimesions with 2 values.

For now let's keep these in mind.

The API it's not limited only to `tf.tensor`, we can define beforehand the dimesions of our tensor with `tf.tensor2d`, `tf.tensor3d`, even `tf.tensor6d`, and, as you can intuit, we can pass arrays previously created to this function calls.

Now... how we access the tensors data?
The tensor API has a variety of methods we can use, synchronously and asynchronously, we'll go with the async methods:
```html
<script>
    async function main() {
      const tensorOne = tf.tensor(1)
      const tensorTwo = tf.tensor([1.0, 2.0, 3.0, 4.0])
      const tensorThree = tf.tensor([[1.0, 2.0], [3.0, 4.0]])

      // Print the values
      tensorOne.array().then(values => console.log(values))
      tensorTwo.array().then(values => console.log(values))
      tensorThree.array().then(values => console.log(values))

      // Download the values
      async function downloadTensorsValues() {
        const tensorOneValues = await tensorOne.data()
        const tensorTwoValues = await tensorTwo.data()
        const tensorThreeValues = await tensorThree.data()
        return {
          tensorOneValues,
          tensorTwoValues,
          tensorThreeValues
        }
      }

      console.log(await downloadTensorsValues())
    }
    main();
  </script>
```
**Note**: tensor.data() will flatten our arrays.

What if we create a tensor with different array shapes?
```html
  <script>
    async function main() {
      const tensor = tf.tensor([[1, 2], [3]])
      console.log(tensor)
      tensor.array().then(console.log)
    }
    main();
  </script>
```
We can see that the console prints our tensor, but throws an error when trying to see the values:
`Error: [2,2] does not match the input size.`
That's because we defined our first array with 2 values (size of 2), it expects that all our arrays are the same size:
```html
  <script>
    async function main() {
      const tensor = tf.tensor([[1, 2, 3], [3]])
      console.log(tensor)
      tensor.array().then(console.log)
    }
    main();
  </script>
```
Now it's expecting 2 arrays, with a length of 3.

What if we pass an incorrect shape to the tensor as a second value?
```html
  <script>
    async function main() {
      const tensor = tf.tensor([1, 2, 3], [3, 3])
      console.log(tensor)
    }
    main();
  </script>
```
`Error: Based on the provided shape, [3,3], the tensor should have 9 values but has 3`

It's expecting a matrix with 3 columns and 3 rows:
```html
  <script>
    async function main() {
      const tensor = tf.tensor([[1, 2, 3], [1, 2, 3], [1, 2, 3]], [3, 3])
      console.log(tensor)
      tensor.array().then(console.log)
    }
    main();
  </script>
```

We can make operations with our tensors:
```html
<script>
    async function main() {
      const tensor = tf.tensor([1, 2, 3])
      const tensor2 = tf.tensor([4, 5, 6])

      // returns a new tensor with bit wise sum included
      const sum = tf.add(tensor, tensor2)
      sum.array().then(console.log)

      // what if we pass diferent shapes?
      const tensor3 = tf.tensor([[1, 1], [2, 2]])
      // const sum2 = tf.add(tensor, tensor3)
      // Error: Operands could not be broadcast together with shapes 3 and 2,2.

      // Clone a tensor
      const clonedTensor = tensor3.clone()
      clonedTensor.array().then(console.log)

      // reshape a tensor
      // we have a tensor with shape [4]
      const tensor4 = tf.tensor([1, 2, 3, 4])
      console.log(tensor4)
      // we reshape it to [2, 2]
      const tensor4reshaped = tf.reshape(tensor4, [2, 2])
      console.log(tensor4reshaped)
      // due to async operations maybe you see this logs at the top of your console

      // we can also add values at the start and end of ours tensors with tf.pad
      const paddedTensor = tf.tensor([1, 2, 3, 4]);
      paddedTensor.pad([[1, 2]], 133).array().then(console.log) // 133 is a random number I picked
    }
    main();
  </script>
```

And all of this are the very few basic operations we should now to start playing with tensors and knowing what they can do and the potential and job they will have in the future.

This chapter, Chapter 1, it's a very basic introduction to tensorflow, but don't worry, at some point we'll end making instagram filters with a few lines of code in our own browser.

As always, it's been a pleasure, and if you have any doubt contact me!
Álvaro