Abrir en Google Colab Descargar notebook

Tensores

Introducción

Podemos pensar en un tensor como un arreglo multidimensional, es decir, que son generalizaciones de los vectores y las matrices. En general, diremos que cualquier arreglo de numeros organizados en una estructura regular con una determinada cantidad de ejes es un tensor.

De esta forma un vector es un tensor unidimensional (o de orden 1) y una matriz es un tensor de 2 dimensiones (o de orden 2). Por lo tnato, muchas de las operaciones que se pueden realizar sobre vetores y matrices también pueden ser reformuladas para ejecutarse sobre tensores.

efa682d832a841beb4182503fcd24a85

Imagen de Cassie Kozyrkov (@quaesita)

Algunas consideraciones:

  • Todos los valores dentro de un tensor son del mismo tipo.

  • Las dimensiones de los datos son las dimensiones del tensor.

  • Las dimensiones del tensor son conocidas o al menos parcialmente conocidas.

Tensores en Python

Los tensores en Python se pueden representar utilizando arreglos n-dimensionales. Diferentes librerias permiten representar tensores.

En numpy:

[ ]:
import numpy as np

t = np.array([
      [[1,2,3],    [4,5,6],    [7,8,9]],
      [[11,12,13], [14,15,16], [17,18,19]],
      [[21,22,23], [24,25,26], [27,28,29]],
  ])

print(type(t))
<class 'numpy.ndarray'>

En TensorFlow:

[ ]:
import tensorflow as tf

t = tf.constant([
      [[1,2,3],    [4,5,6],    [7,8,9]],
      [[11,12,13], [14,15,16], [17,18,19]],
      [[21,22,23], [24,25,26], [27,28,29]],
  ])

print(type(t))
<class 'tensorflow.python.framework.ops.EagerTensor'>

En PyTorch:

[ ]:
import torch

t = torch.tensor([
      [[1,2,3],    [4,5,6],    [7,8,9]],
      [[11,12,13], [14,15,16], [17,18,19]],
      [[21,22,23], [24,25,26], [27,28,29]],
  ])

print(type(t))
<class 'torch.Tensor'>

Notemos la diferencia con un arreglo en Python:

[ ]:
arreglo = [
  [[1,2,3],    [4,5,6],    [7,8,9]],
  [[11,12,13], [14,15,16], [17,18,19]],
  [[21,22,23], [24,25,26], [27,28,29]],
  ]

print(type(arreglo))
<class 'list'>

Nota: Veremos que en la mayoria de los casos numpy.ndarray es un tipo compatible para tensorflow.python.framework.ops.EagerTensor y torch.Tensor, sin embargo el tipo list no lo es. En esos casos, podemos utilizar:

[ ]:
np.asarray(arreglo)

Dimensiones de un tensor

Podemos acceder a las dimensiones de un tensor en cualquier momento utilizando la instrucción shape:

[ ]:
t.shape
torch.Size([3, 3, 3])

En este caso vemos que este tensor tiene 3 dimensiones. Cada una de las cuales tiene 3 elementos. Estos numeros que aparecen aqui se llaman ejes o axis y reciben numeros ordinales para identificarlos. La primera dimension corresponde a axis=0, la segunda a axis=1 y la tercera a axis=2.

Dado que un tensor no es mas que un arreglo de numeros, podemos cambiar sus dimensiones para llevarlo a otras dimensiones. Por ejemplo, el siguiente codigo crea un tensor de 1 dimensión con 30 elementos:

[ ]:
t = np.arange(30)
print(t)
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29]

Las dimensiones de este tensor son:

[ ]:
t.shape
(30,)

Podemos convertir este tensor de 1 dimension a uno de 3 dimensiones:

0b57591af24d4c388a688ed65b9ff931

Fuente de la imagen: https://www.tensorflow.org/guide/tensor

[ ]:
t = t.reshape((3, 2, 5))
print(t)
print("Shape:", t.shape)
[[[ 0  1  2  3  4]
  [ 5  6  7  8  9]]

 [[10 11 12 13 14]
  [15 16 17 18 19]]

 [[20 21 22 23 24]
  [25 26 27 28 29]]]
Shape: (3, 2, 5)

Nota: Si trabaja con tensore en TensorFlow, la operación anterior se realiza como tf.reshape(t, (3 , 2, 5)). No te como especificar estas operaciones en este framework a diferencia de numpy o pytorch.

En muchas ocaciones necesitamos eliminar alguna de las dimensiones en particular.

1c5deca413534ba1954662d4fa228529

Fuente de la imagen: https://www.tensorflow.org/guide/tensor

[ ]:
t = t.reshape((3, -1))
print(t)
print("Shape:", t.shape)
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]]
Shape: (3, 10)

Noten como eliminamos la ultima posición del tensor. El valor -1 indica «la cantidad de elementos que entren». Esto significa que queremos que la primera componente del tensor sea de dimension 3 y la segunda de «la cantidad restante de elementos». Es importante que las dimensiones tengan sentido con la cantidad de elementos para evitar un error:

[ ]:
t.reshape((4, -1))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_13468/1980147985.py in <module>
----> 1 t.reshape((4, -1))

ValueError: cannot reshape array of size 30 into shape (4,newaxis)

Agregando dimensiones

Podemos agregar dimensiones extras pero sin distorcionar las otras dimensiones. Esto es util cuando por ejemplo la entrada de una red es una «lote» de valores pero nosotros queremos introducir solo un valor. Asi podemos convertir un tensor de 2 dimensiones en uno de 3.

[ ]:
print("Dimensiones antes:", t.shape)
Dimensiones antes: (3, 10)
[ ]:
t = t.reshape((1,3,10))

print("Dimensiones despues:", t.shape)
Dimensiones despues: (1, 3, 10)

Permutando dimensiones

Podemos cambiar el orden de las dimensiones. Esto es util cuando queremos ver la información de otra forma: La instrucción permute permite hacerlo, pero no está disponible en todos los tipos de tensores, solo en TensorFlow y PyTorch

[ ]:
t_torch = torch.tensor(t)
[ ]:
print("Dimensiones antes:", t_torch.shape)
Dimensiones antes: torch.Size([1, 3, 10])
[ ]:
t_torch = t_torch.permute(1,0,2)
print("Dimensiones despues:", t_torch.shape)
Dimensiones despues: torch.Size([3, 1, 10])

Quitando dimensiones

De igual forma podemos eliminarla si no la necesitaramos. La instrucción squeeze nos permite eliminar alguna dimensión cuya cantidad de elementos es 1. El argumento axis=0 indica cual es la dimensión a quitar, en este caso la primera:

[ ]:
print("Dimensiones antes:", t.shape)
Dimensiones antes: (1, 3, 10)
[ ]:
t = t.squeeze(axis=0)

print("Dimensiones despues:", t.shape)
Dimensiones despues: (3, 10)

Tipos de dato de un tensor

Todos los elementos de un tensor deben tener el mismo tipo de datos. Podemos ver el tipo de datos de cualquier tensor con la propiedad dtype

[ ]:
t.dtype
dtype('int64')

En muchos casos necesitamos cambiar el tipo de dato a otro, por ejemplo float32 o int32:

[ ]:
t_float = t.astype('float32')
print(t_float.dtype)
float32

Esto es importante sobre todo cuando estamos integrando datos de diferentes origines y necesitamos que todos los tensores tengan tipos que sean compatibles y con la misma precisión.

Operaciones sobre tensores

De igual forma que podemos sumar, multiplicar y dividir un vector, estas operaciones estan definidas para los tensores. Dependiendo del Framework que utilizamos, serán las operaciones disponibles. En general, todos implementan las mismas APIs:

En tensorflow por ejemplo:

tf.add(a, b)
tf.substract(a, b)
tf.multiply(a, b)
tf.div(a, b)
tf.pow(a, b)
tf.exp(a)
tf.sqrt(a)

Otras estructuras de datos

No dedemos confundir los tensores con otras estructuras comunes como ser los DataFrame en Pandas. Un DataFrame tiene 2 grandes diferencias con un Tensor:

  1. Los DataFrame tienen 2 dimensiones. Las Series tienen dimensiones 1.

  2. Los DataFrame y las Series tienen un nombre de columna.

[ ]:
datos = [
      [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9],
  ]
[ ]:
import pandas as pd

df = pd.DataFrame(datos, columns=["col1", "col2", "col3"])
[ ]:
df
col1 col2 col3
0 1 2 3
1 4 5 6
2 7 8 9

Podemos de todas formas convertir desde DataFrame a numpy.ndarray:

[ ]:
df.to_numpy()
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])