Introducción a tensores
Contents
Introducción a tensores#
Primera parte (empezando)
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:
En estadística es usual escribir los vectores en columna. En este caso \(w\) se escribe como:
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 que el vector \(z\) tiene dimensión cuatro. En otras palabras, la dimensión 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 idenfique dígitos 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í:
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 \(\mathbb{R}^2\) o \(\mathbb{R}^3\).
El siguiente código dibuja algunos vectores en \(\mathbb{R}^2\). 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()
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:
El vector \(w = (w_1,w_2, w_3)\) tiene dimensión 3.
El tensor \(w = (w_1,w_2, w_3)\) 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 definciones usaremos tensores unidimensionales de tamaño \(n=3\). En realidad el tamaño de los tensores unidimensionales pueden ser cualquier número entero \(n\) y las definciones se generalizan de forma obvia.
Supongamos que \(a= (a_1,a_2,a_3)\) y \(b=(b_1,b_2,b_3)\) son dos vectores. La suma entre \(a\) y \(b\) es un vector \(c\) definido por:
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 vectors \(a-b\) es definida por:
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 por elemento entre dos vectores se donota \(a \odot b\) y se define como:
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\times 3\) puede ser:
Las matrices son muy utilizadas en prácticamente todas las áreas 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 \(m\) filas 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.40208333 0.33611111 0.40208333]
[3.35611111 5.05208333 0.33611111]]
(2, 3)
Observe que v es un tensor que tiene forma (shape) \(2\times2\). En NumPy un tensor está compuesto por uno o 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 Python 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.
a = np.array([[1,2,3],[4,5,6]])
print('a=',a)
print('a[0]=',a[0])
print('a[1][1]=',a[1][1])
a= [[1 2 3]
[4 5 6]]
a[0]= [1 2 3]
a[1][1]= 5
Un 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]]]
Por hacer (Tarea)#
Investigue sobre los siguientes temas:
Indexación de tensores NumPy.
Rebanado (slicing) tensores.
Reorganización (Reshape) de tensores.
Algebra Tensorial#
NumPy está preparada para trabajar las operaciones ordinarias del álgebra lineal y más extendidamente del álgebra tensorial directamente. Los siguientes ejemplos muestran como sumar.
# Operaciones con Tensores
# Usamos 3*2*3 arrays (tensores)
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 que se tiene 3 capas con shape 2*3
(3, 2, 3)
[[[ 1 2 3]
[ 4 5 6]]
[[ 10 20 30]
[ 40 50 60]]
[[100 200 300]
[400 500 600]]]
# multiplicar por -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]]]
# suma
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)
# diferencia
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)
# producto de Hadamard
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)
# división componente a componente
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=(a_1,a_2,a_3)\) y \(b=(b_1,b_2, b_3)\) el producto escalar entre \(a\) y \(b\) está definido por:
Este producto es base de mucho métodos estadísticos, como la comparación misma de vectores.
Con NumPy escribimos:
# dot product (vectores): 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=[a_{ij}]_{N\times P}\) y \(B=[b_{jk}]_{P\times Q}\) son matrices de tamaños (shape) \(N\times P\) y \(P\times Q\) respectivamente. Entonces \(C = A\times B\) es una matriz \(C=[c_{ik}]_{N\times Q}\) de tamaño \(P\times Q\), en donde:
# producto de matrices (tensores ): 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)
# producto de matrices ( resultado equivalente)
# se prefiere el cálculo de C o D
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) #matriz identidad 2x2
b = np.ones((2,2))*2 # matriz 2x2, lleno con números 2 .
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 es 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. Veámos 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 matriz 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)