Introducción a tensores

Introducción

En este cuaderno se introducen los conceptos de vectores, matrices y tensores.

Los tensores son la estructura de datos más utilizada en el aprendizaje profundo. Desde el punto de vista matemático, un tensor generaliza los conceptos de escalares, vectores y matrices.

Solamente haremos la introducción al concepto de tensores desde el punto de vista de las estructuras de datos requeridas en el aprendizaje profundo.

Vectores (Tensores unidimensionales)

En esta sección revisamos el concepto de vector. Desde el punto de vista del aprendizaje profundo. Entendemos un vector como un contenedor de n datos, cada uno de los cuales se identifica genéricamente mediante un índice. Por ejemplo supongamos que w es un vector de tamaño tres. Este vector se representa genéricamente como

w=(w1,w2,w3).

En estadística es usual escribir los vectores el columna. En este caso w se escribe como

w=(w1w2w3).

El tipo de valores que puede contener un vector debe ser de la misma clase por convención. Por ejemplo, si w es un vector de números reales, entonces z=(3.2, 1.5, -7.2,0.0) es un vector real de tamaño cuatro. Matemáticamente se dice el vector z tiene dimensión cuatro. En otras palabras, la dimension matemática de un vector es su tamaño.

El contenido y tipo de datos de un vector depende del contexto en que se está utilizando. Supongamos que se trata de construir una máquina de aprendizaje profundo que identifique digitos escritos a mano. Lo que se acostumbra a hacer es digitalizar las imágenes correspondientes.

Ejemplo en Numpy

En NumPy el vector w=(1,2,3) se puede crear así:

!pip install numpy matplotlib
Requirement already satisfied: numpy in /opt/anaconda3/lib/python3.8/site-packages (1.20.3)
Requirement already satisfied: matplotlib in /opt/anaconda3/lib/python3.8/site-packages (3.4.2)
Requirement already satisfied: pillow>=6.2.0 in /opt/anaconda3/lib/python3.8/site-packages (from matplotlib) (8.3.1)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/anaconda3/lib/python3.8/site-packages (from matplotlib) (1.3.1)
Requirement already satisfied: cycler>=0.10 in /opt/anaconda3/lib/python3.8/site-packages (from matplotlib) (0.10.0)
Requirement already satisfied: python-dateutil>=2.7 in /opt/anaconda3/lib/python3.8/site-packages (from matplotlib) (2.8.2)
Requirement already satisfied: pyparsing>=2.2.1 in /opt/anaconda3/lib/python3.8/site-packages (from matplotlib) (2.4.7)
Requirement already satisfied: six in /opt/anaconda3/lib/python3.8/site-packages (from cycler>=0.10->matplotlib) (1.16.0)
import numpy as np
# crea el vector (array)
w = np.array([1,2,3]) 
# lo imprime
print(w)
# Muestra el tamaño (shape) del vector
print(w.shape)
[1 2 3]
(3,)

Discusión:¿Vector o Tensor?

En matemáticas la dimensión por lo general hace referencia al número de componentes con el que se representa un objeto en un espacio. Por lo general el espacio Euclideano, digamos R2 o R3.

El siguiente código dibuja algunos vectores en R2. Los matemáticos dicen que estos objetos geométricos tienen dimensión geométrica 2. Por favor revisa cada línea del código para estar seguro que lo entiende.

import numpy as np
import matplotlib.pyplot as plt

soa = np.array([[0, 0, 4, 1], [0, 0, 1, 5], [0, 0, 3, 2]])
X, Y, U, V = zip(*soa)
plt.figure()
plt.title('Vectores en el espacio Euclideano $R^2$ ')
ax = plt.gca()
ax.quiver(X, Y, U, V, angles='xy', scale_units='xy', scale=1)
ax.set_xlim([-1, 6])
ax.set_ylim([-1, 6])
plt.draw()
plt.show()
_images/py8_12_0.png

Ayuda

Por ejemplo busque en Google ax.quiver.

Los tensores son objetos de tipo algebraico. La dimensión de un tensor se define como el número de índices requerido para representar todos a los elementos del tensor.

Entonces:

  1. El vector w=(w1,w2,w3) tiene dimensión 3.

  2. El tensor w=(w1,w2,w3) tiene dimensión 1 y tamaño (shape = 3).

En aprendizaje profundo usaremos con más frecuencia el concepto de tensor. Asegúrese de comprender la diferencia.

Aritmética básica de tensores unidimensionales

Mientras no se diga lo contrario, asumiremos que los tensores que usaremos tienen el mismo tamaño. Por facilidad, en las definiciones usaremos tensores unidimensionales de tamaño n=3. En realidad el tamaño de los tensores unidimensionales puede ser cualquier número entero n y las definiciones se generalizan de forma obvia.

Supongamos que a=(a1,a2,a3) y b=(b1,b2,b3) son dos vectores. La suma entre a Y b es un vector c definido por

c=a+b=(a1+b1,a2+b2,a3+b3)

En Python escribimos

a = np.array([1,2,3])
b = np.array([7,8,9])
c = a + b
print(c)
[ 8 10 12]

Similarmente la diferencia de vectores ab es definida por

c=ab=(a1b1,a2b2,a3b3)
a = np.array([1,2,3])
b = np.array([7,8,9])
c = a - b
print(c)
[-6 -6 -6]

El producto de Hadamard, o producto elemento by elemento entre dos vectores se denota ab y se define como

c=ab=(a1b1,a2b2,a3b3).

En Python el producto de Hadamard se implementa simplemente usando el operador de multiplicación (*). Veamos

a = np.array([1,2,3])
b = np.array([7,8,9])
c = a * b
print(c)
[ 7 16 27]

La división entre vectores no es una operación formalmente definida. En ocasiones sin embargo se requiere dividir los elementos de un vector entre los elementos de otro, elemento a elemento. Esta operación se implementa en Python simplemente usando el operador división (/)

a = np.array([1,2,3])
b = np.array([7,8,9])
c = a / b
print(c)
[0.14285714 0.25       0.33333333]

Matrices (Tensores bidimensionales)

Una matriz es una organización (tensor) bidimensional. Por ejemplo la matriz M de tamaño 2×3 puede ser

(123456)

Las matrices son muy utilizadas en prácticamente todas las área de la ciencia y la tecnología.

En el caso del aprendizaje profundo, y más generalmente en Estadística las matrices se usan para representar conjuntos de datos. En los casos de regresión, las filas usualmente representan individuos y las columnas variables.

En adelante llamaremos a las matrices tensores bidimensionales (o de dos dimensiones). Entonces Una matriz que tiene mfilas y n columnas, es un tensor bidimensional de tamaño (shape) =(m,n).

El tensor M se representa en NumPy de la siguiente forma:

import numpy as np

# Crea el tensor
M = np.array([[1,2,3],
              [4,5,6]])
# Imprime el Tensor
print(M)
# Muestra la forma (shape)
M.shape
[[1 2 3]
 [4 5 6]]
(2, 3)

Creación de algunos tensores bidimensionales

Tensor vacío

La función empty() crea un arreglo de la forma especificada.

v = np.empty([2,3])
print(v)
print(v.shape)
[[0.98707373 0.16026681 0.29430822]
 [0.95571056 0.91767558 0.3973305 ]]
(2, 3)

Observe que v es un tensor que tiene forma (shape) 2x2. En NumPy un tensor está compuesto por uno más tensores. La dimensión del tensor v es 2. Se requiere un objeto de doble entrada para representar un tensor bidimensional. Los rangos de este tensor son (2,3). Observe que el arreglo es representado como una lista con dos elementos, cada uno de los cuales es un arreglo de rango 3.

Tensor de ceros

La función zeros() crea un arreglo de la forma especificada relleno de ceros.

w = np.zeros([3,2])
print(w)
[[0. 0.]
 [0. 0.]
 [0. 0.]]

Arreglo de unos

La función ones() crea un arreglo de la forma especificada relleno de unos.

w = np.ones([2,2])
print(w)
[[1. 1.]
 [1. 1.]]

Combinación de arreglos

Vertical. Con la función vstack()

v = np.ones([2,3])
w = np.array([2,2,2])
z = np.vstack((v,w))
print(z)
print(z.shape)
[[1. 1. 1.]
 [1. 1. 1.]
 [2. 2. 2.]]
(3, 3)

horizontal. Con la función hstack()

v = np.ones([2,3])
w = np.array([[5],[5]])
z = np.hstack((v,w))
print(z)
print(z.shape)
[[1. 1. 1. 5.]
 [1. 1. 1. 5.]]
(2, 4)

Creación de tensores multidimensionales

En Pyhton una lista de listas puede crearse como se muestra en el siguiente fragmento de código. En el código se observa como acceder a la primera lista y como acceder al segundo elemento de la segunda lista. Asegúrese de entender la lógica involucrada.

Ejemplo con arreglos tridimensionales

Para completar de entender la indexación y rebanado de arreglos, observe el siguiente ejemplo. Asegúrese de entender completamente la lógica.

a = np.array([[[1,2],[3,4],[5,6]],[[7,8],[9,10],[11,12]],[[13,14],[15,16],[17,18]],
              [[19,20],[21,22],[23,24]]])
print('a.shape=', a.shape)
print('a =',a)
a.shape= (4, 3, 2)
a = [[[ 1  2]
  [ 3  4]
  [ 5  6]]

 [[ 7  8]
  [ 9 10]
  [11 12]]

 [[13 14]
  [15 16]
  [17 18]]

 [[19 20]
  [21 22]
  [23 24]]]

Tarea

Investigue sobre los siguiebtes temas

  1. Indexación de tensores Numpy.

  2. Rabanado (slicing) de tensores

  3. Reorganización (Reshape) de tensores

Algebra Tensorial

Numpy esta preparado para trabajar las operaciones ordinarias del álgebra lineal y más extendidamente del álgebra tensorial directamente. Los siguientes ejemplos muestran como sumar,….

# Tensor-operations
# We use 3*2*3 arrays (tensors)
a = np.array([[[1,2,3],[4,5,6]],[[10,20,30],[40,50,60]],[[100,200,300],[400,500,600]]])
print(a.shape)
print(a)
# Observe that has 3 layers of shape 2*3
(3, 2, 3)
[[[  1   2   3]
  [  4   5   6]]

 [[ 10  20  30]
  [ 40  50  60]]

 [[100 200 300]
  [400 500 600]]]
# multiply a by -1
b = -a
print(b.shape)
print(b)
(3, 2, 3)
[[[  -1   -2   -3]
  [  -4   -5   -6]]

 [[ -10  -20  -30]
  [ -40  -50  -60]]

 [[-100 -200 -300]
  [-400 -500 -600]]]
# sum
c = a + b
print("c=",c)
print(c.shape)
c= [[[0 0 0]
  [0 0 0]]

 [[0 0 0]
  [0 0 0]]

 [[0 0 0]
  [0 0 0]]]
(3, 2, 3)
# difference
c = a - b
print("c=",c)
print(c.shape)
c= [[[   2    4    6]
  [   8   10   12]]

 [[  20   40   60]
  [  80  100  120]]

 [[ 200  400  600]
  [ 800 1000 1200]]]
(3, 2, 3)
# Hadamard product
c = a * b
print("c=",c)
print(c.shape)
c= [[[     -1      -4      -9]
  [    -16     -25     -36]]

 [[   -100    -400    -900]
  [  -1600   -2500   -3600]]

 [[ -10000  -40000  -90000]
  [-160000 -250000 -360000]]]
(3, 2, 3)
# component-wise division
c = a / b
print("c=",c)
print(c.shape)
c= [[[-1. -1. -1.]
  [-1. -1. -1.]]

 [[-1. -1. -1.]
  [-1. -1. -1.]]

 [[-1. -1. -1.]
  [-1. -1. -1.]]]
(3, 2, 3)

Producto escalar (dot product)

Si a=(a1,a2,a3) y b=(b1,b2,b3) el producto escalar entre a y b está definido por

<a,b>=a1b1+a2b2+a3b3

Este producto es base de mucho métodos estadísticos, como la comparación misma de vectores.

Con Numpy escribimos

# dot product (vectors): c = sum(a_i*b_i)
a = np.array([1,2,3])
b = np.array([4,5,6])
c = np.dot(a,b)
print("c=",c)
print(c.shape)
c= 32
()

Producto de matrices (2D-Tensores)

Supongamos que A=[aij]N×P y B=[bjk]P×Q son matrices de tamaños (shape) N×P y P×Q respectivamente. Entonces C=A×B es una matriz C=[cik]N×Q de tamaño P×Q, en donde

cik=j=1Paijbjk
# dot product (tensors ): c = sum(a_i*b_i)
A = np.array([[1,2,3],[4,5,6]])
B = np.array([[10,20,30,40],[50,60,70,80],[90,100,110,120]])          
print("A=",A)
print("A.shape =",A.shape)
print("B=",B)
print("B.shape =",B.shape)
# Matrix product ( equivalent results)
# prefer C or D computation
C = A @ B
D = np.matmul(A,B)
E = np.dot(A,B)
print("C=",C)
print("C.shape =",C.shape)
print("D=",D)
print("E=",E)
A= [[1 2 3]
 [4 5 6]]
A.shape = (2, 3)
B= [[ 10  20  30  40]
 [ 50  60  70  80]
 [ 90 100 110 120]]
B.shape = (3, 4)
C= [[ 380  440  500  560]
 [ 830  980 1130 1280]]
C.shape = (2, 4)
D= [[ 380  440  500  560]
 [ 830  980 1130 1280]]
E= [[ 380  440  500  560]
 [ 830  980 1130 1280]]
# a.dot puede ser encadenado con arreglos 2D
a = np.eye(2) #identity matrix 2x2
b = np.ones((2,2))*2# matrix 2x2, full of ith 2's 
a.dot(b).dot(b)
array([[8., 8.],
       [8., 8.]])

Trabajando con arreglos de diferente tamaño(broadcasting)

Si a es escalar (0-D array) y b es N-D array) se multiplican todos los elementos de b por a. Revise el ejemplo anterior.

Si a en un arreglo N-dimensional (N-D) y b es un arreglo 1D (uno-dimensional)

a = np.array([[1,2],[3,4],[5,6]])
b = np.array([[10],[100]])
c = a.dot(b)   
d = a@b
print('a=', a)
print('b=', b)
print('c=', c)
print('d=', d)
a= [[1 2]
 [3 4]
 [5 6]]
b= [[ 10]
 [100]]
c= [[210]
 [430]
 [650]]
d= [[210]
 [430]
 [650]]

Producto tensorial

Trabajando con un arreglo A 3-D y arreglo B 1D de tal forma que el tamaño de la última dimension de A coincide con el tamaño de B, el producto se puede ver como el resultado del producto de matrices entre la matriz en cada capa por el vector B.

En el siguiente ejemplo A tiene tamaño 4x3x2 y b tiene tamaño 2 (en realidad 2x1). Entonces el producto tensorial C=A@B da como resultado un arreglo de tamaño 4x3x1. Cada una de las 4 matrices de tamaño 3x2 del arreglo A se multiplican por el vector B, lo que da como resultado 4 matrices de tamaño 3x1. Veamos los cálculos con Numpy.

a = np.array([[[1,2],[3,4],[5,6]],[[7,8],[9,10],[11,12]],[[13,14],[15,16],[17,18]],
              [[19,20],[21,22],[23,24]]])
b = np.array([[10],[100]])
c = a.dot(b)   
d = a@b
# revisamos este subcálculo. a_0 es la primera matrix 3x2 del arreglo A.
a_0 = a[0,:,:]
# e es la primera matriz de tamaño 3*1 del producto. Compare los resultados.
e = a_0@b
print('a=', a)
print('a.shape=',a.shape)
print('b=', b)
print('b.shape=',b.shape)
print('c=', c)
print('d=', d)
print('d.shape=',d.shape)
print('e=', e)
print('a_1.shape',a_0.shape)
print('e.shape=',e.shape)
a= [[[ 1  2]
  [ 3  4]
  [ 5  6]]

 [[ 7  8]
  [ 9 10]
  [11 12]]

 [[13 14]
  [15 16]
  [17 18]]

 [[19 20]
  [21 22]
  [23 24]]]
a.shape= (4, 3, 2)
b= [[ 10]
 [100]]
b.shape= (2, 1)
c= [[[ 210]
  [ 430]
  [ 650]]

 [[ 870]
  [1090]
  [1310]]

 [[1530]
  [1750]
  [1970]]

 [[2190]
  [2410]
  [2630]]]
d= [[[ 210]
  [ 430]
  [ 650]]

 [[ 870]
  [1090]
  [1310]]

 [[1530]
  [1750]
  [1970]]

 [[2190]
  [2410]
  [2630]]]
d.shape= (4, 3, 1)
e= [[210]
 [430]
 [650]]
a_1.shape (3, 2)
e.shape= (3, 1)

Autores

  1. Alvaro Mauricio Montenegro Díaz, ammontenegrod@unal.edu.co

  2. Daniel Mauricio Montenegro Reyes, dextronomo@gmail.com

  3. Oleg Jarma, ojarmam@unal.edu.co

Comentarios