Tensores#

Introducción

Fuente: Alvaro Montenegro

Introducción#

En esta lección aprenderemos los conceptos básicos de tensores y como los usamos para manipular imágenes usando tensores.

Tensor#

Un tensor es un concepto matemático que generaliza los conceptos de escalares, vectores y matrices.

Fuente: Alvaro Montenegro

En términos muy simples, un tensor es un objeto dinámico (matemáticamente diríamos que es una función entre espacios vectoriales) que vive dentro de una estructura.

Pero no vamos a hacer un tratado matemático aquí.

Lo importante en esta clase es entender que en realidad, escalares, vectores, matrices pueden verse como tensores fijos y eso será suficiente para lo que sigue.

Rango#

Diremos que los escalares tienen rango (shape) 0, los vectores tiene rango 1, las matrices rango 2 y el tensor de la derecha rango 3.

El rango corresponde al número de índices que se requiere para identificar de manera única a cada elemento del tensor.

Observe que por ejemplo, en el último tensor, requiere (fila, columna, cajón).

También podría ser (cajón, fila, columna).

Redes Neuronales#

La siguiente imagen muestra el estado en un instante de una una parte oculta de una red neuronal profunda.

Fuente: Alvaro Montenegro

El proceso puede modelarse en forma simplificada usando matrices y vectores como se ve a continuación.

\[\begin{split} W_{12}L_1 = L2 \to \begin{pmatrix} -1 & 0.4 & 1.5\\ 0.8 & 0.5 & 0.75 \\ 0.2 & -0.3 & 1\\ \end{pmatrix}\begin{pmatrix} 2.5\\ 4 \\ 1.2 \end{pmatrix} = \begin{pmatrix} 0.9\\ 4.9 \\ 0.5 \end{pmatrix} \end{split}\]

Observe por ejemplo que:

\[-1\times 2.5 + 0.4\times 4 + 1.5\times 1.2 = 0.9\]

En la fase de entrenamiento de la red neuronal, los pesos de la matriz se van modificando hasta que se encuentra un óptimo local. Este proceso ocurre en toda la estructura de la red.

Por lo que no parece extraño que las GPU y las TPU pasen todo el tiempo haciendo operaciones de este tipo, que al final se reduce a sumas y multiplicaciones.

Por otro lado, lo que ocurre es que los objetos que se procesan no necesariamente son vectores como en el ejemplo, y esto lleva a la necesidad de generalizar los conceptos.

Producto tensorial#

La operación más ejecutada en aprendizaje profundo es el producto tensorial.

Vamos a suponer que cada elemento en los tensores de rango 3 se indexan mediante coordenadas (fila, columna, profundidad) y que los tensores de rango 2 se indexan como (fila, columna).

La siguiente imagen ilustra la forma de un producto tensorial.

  • A la izquierda (azul) se tiene un tensor de tamaño digamos \(n \times p \times s\).

  • El tensor que está operando en el centro (rosa) es de tamaño \(p \times r\). Este actúa operando en este caso sobre cada capa del tensor de la izquierda haciendo un producto usual de matrices.

  • Por lo que el tensor resultante (verde) a la derecha tiene tamaño \(n \times r \times s\)

Fuente: Alvaro Montenegro

Explicación del producto#

La explicación del proceso es la siguiente:

Cada capa frontal del tensor azul es multiplica por el tensor rosa y el resultado es colocando como una capa frontal en el tensor resultante (verde).

Cada multiplicación es entre dos matrices (azul * rosa) y el resultado es una matriz (verde).

Cada multiplicación de matrices se hace por la fórmula fila (matriz azul) * columna (matriz rosa)

Vamos por ejemplo a suponer que una capa roja es \( azul = \begin{pmatrix} 1 & 2 & 1\\ 3 & 4 & 1 \\ 4 & 5 & 0\\ \end{pmatrix}\), \(rosa = \begin{pmatrix} 5 & 10\\ 20 & 30 \\ 4 & 1\end{pmatrix}\)

Entonces se tiene que

\[\begin{split} azul \times rosa = \begin{pmatrix} 1 & 2 & 1\\ 3 & 4 & 1 \\ 4 & 5 & 0\\ \end{pmatrix} \times \begin{pmatrix} 5 & 10\\ 20 & 30\\ 4 & 1\end{pmatrix} = \begin{pmatrix} 1\times 5 + 2 \times 20 + 1 \times 4 & 1 \times 10 + 2\times 30 + 1\times 1 \\ 3\times 5 + 4 \times 20 + 1 \times 4 & 3 \times 10 + 4 \times 30 + 1 \times 1 \\ 4\times 5 + 5 \times 20 + 0 \times 4 & 4 \times 10 + 5 \times 30 + 0 \times 1\end{pmatrix} = turquesa \end{split}\]

Imágenes a color#

De manera clásica una imagen a color está compuesta de tres colores primarios: rojo (Red), verde (Green) y azul (Blue). Para generar una imagen a color un computador maneja tres planos de color, los cuales son controlados desde tensores tridimensionales. Considere el siguiente ejemplo.

Fuente: Alvaro Montenegro

Cada pixel (punto) de la imagen es representado por una valor numérico en el rango de 0 a 255, o en rango de valores reales entre cero y 1.

Construcción aleatoria de una imagen#

Considere el siguiente código Python.

import numpy as np
I=np.random.randint(0,255,size=(3,10,10))
print(I)
[[[ 71 153  58 134 254  44 191  74  44 241]
  [ 72 142 162 105 136  67 116  23 136  87]
  [153  20 142 197  10 163  54 117 104  34]
  [ 27 138  50  54 217 114 229  41  53 236]
  [ 30 139 231 232 115  70 105  74 119 236]
  [ 83 156  24  65 139 173 219  91 175 187]
  [212 232 138 101 202  22  73   3 222 170]
  [194  95 147 252 136 100 114  60 242 103]
  [120 113  75 243  12 111  91  53 197 100]
  [ 73  53  87 146 190  49 176  96 173  19]]

 [[157  41  53  91 160  84 239 222  98  19]
  [ 40 247 241 227   5 198 138 164 142  29]
  [  2  77 158  68 122 239  93 167 100  80]
  [118 167 247 237  44 243  37 187 225  30]
  [250 213   7 122 242  63  90  12 185 128]
  [  3 132 171  58 192  94  98 124 120  46]
  [ 91  46 154 220  85   2 154  89  17 199]
  [194  80  89 227   4   2  70  11 193 149]
  [223 129  76 167  89 232 113 142   5   6]
  [ 84 194  44 235   6 125 139 189 238 249]]

 [[ 27 128 148 165  81 191 160 233  13  78]
  [228  75 111  98 122  17 226 199 125  44]
  [ 15 149 143 206  98  97 199 188 171 185]
  [183  64  43 141 183  74 211 154 217 147]
  [120 207  48 121 168 164  87  39 223 118]
  [178 152 101 101 151 120   7 140 251 158]
  [248 225 248 233 114 107  25  93 188 159]
  [174  25 231   9 211  80 192  98  13  44]
  [  2  89 211 102 108  43 100  13   1  83]
  [ 27  41 153 122 253   5  45 108 231  32]]]

Este tensor representa una imagen de tamaño \(10 \times 10\). Son tres planos de color \(10 \times 10\).

Observe que la primera dimensión corresponde a cada plano de color y las restantes dos dimensiones a las intensidades de cada color para cada punto.

Renderizar (dibujar en este caso), nos lleva a la siguiente imagen.

# conda install -c conda-forge matplotlib
import matplotlib.pyplot as plt

plt.imshow(I.T)
plt.show()
../../_images/Intro_Tensores_II_31_0.png

Observe que

(I.T).shape
(10, 10, 3)

Porque Python maneja las imágenes en este formato: Fila, columna y plano de color.

Imagen real#

Vamos a trabajar ahora con una imagen real.

import numpy as np
import matplotlib.pyplot as plt

# conda install -c anaconda scikit-image
from skimage import data
from skimage.color import rgb2gray

original = data.astronaut()
grayscale = rgb2gray(original)

fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()

ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(grayscale, cmap=plt.cm.gray)
ax[1].set_title("Grayscale")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_37_0.png
Idata=np.array(grayscale)
print("\nLa imagen tiene forma: ",Idata.shape,"\n")
print(Idata)
La imagen tiene forma:  (512, 512) 

[[5.83434902e-01 4.14859216e-01 2.44058431e-01 ... 4.75007843e-01
  4.58213333e-01 4.69121961e-01]
 [6.75588235e-01 5.56006667e-01 4.49052941e-01 ... 4.68548627e-01
  4.56501176e-01 4.55958431e-01]
 [7.66334902e-01 7.00524314e-01 6.49276078e-01 ... 4.76406667e-01
  4.62104314e-01 4.53978431e-01]
 ...
 [6.81696471e-01 6.81979216e-01 6.71889020e-01 ... 0.00000000e+00
  2.82745098e-04 0.00000000e+00]
 [6.74694510e-01 6.68532941e-01 6.64030196e-01 ... 2.82745098e-04
  3.92156863e-03 0.00000000e+00]
 [6.70482353e-01 6.63189804e-01 6.52838824e-01 ... 0.00000000e+00
  3.92156863e-03 0.00000000e+00]]

Planos de color#

Idata = np.array(original)
print("\nLa imagen tiene forma: ",Idata.shape,"\n")
print("\nEscala de Rojos:\n\n",Idata[:511,:511,0],"\n")
print("\nEscala de Verdes:\n\n",Idata[:511,:511,1],"\n")
print("\nEscala de Azules:\n\n",Idata[:511,:511,2],"\n")
La imagen tiene forma:  (512, 512, 3) 


Escala de Rojos:

 [[154 109  63 ... 126 127 120]
 [177 144 113 ... 126 127 124]
 [201 182 168 ... 125 128 126]
 ...
 [186 188 184 ...   0   0   0]
 [186 186 183 ...   2   0   0]
 [183 182 185 ...  21   0   1]] 


Escala de Verdes:

 [[147 103  58 ... 120 120 117]
 [171 141 114 ... 118 118 115]
 [194 178 165 ... 119 120 116]
 ...
 [169 169 167 ...   0   0   0]
 [170 170 168 ...   2   0   0]
 [169 167 164 ...  21   0   1]] 


Escala de Azules:

 [[151 124 102 ... 114 115 106]
 [171 143 124 ... 111 112 108]
 [193 175 164 ... 113 117 112]
 ...
 [174 177 170 ...   0   0   1]
 [176 177 170 ...   3   0   1]
 [170 171 176 ...  16   1   1]] 
fig, (ax1, ax2,ax3) = plt.subplots(1, 3,figsize=(15,15))

ax1.imshow(Idata[:,:,0],cmap="Reds")
ax1.set_xlabel('Red')
ax2.imshow(Idata[:,:,1],cmap="Greens")
ax2.set_xlabel('Green')
ax3.imshow(Idata[:,:,2],cmap="Blues")
ax3.set_xlabel('Blue')
plt.show()
../../_images/Intro_Tensores_II_41_0.png

Manipulación de imágenes#

Intercambia dos planos de color#

import numpy as np
import matplotlib.pyplot as plt

from skimage import data
from skimage.color import rgb2gray

original = data.astronaut()

Idata_m = Idata
Idata_m[:,:,0], Idata_m[:,:,2] = Idata_m[:,:,2], Idata_m[:,:,0]

fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()

ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(Idata_m)
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_44_0.png

Suma una constante a la imagen#

fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()

k = 10
ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(original + k)
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_46_0.png
fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()

k = 2
ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(original //k)
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_47_0.png
Idata_m = Idata

Idata_m[:,:,0 ]=0

fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()

ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(Idata_m)
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_48_0.png
Idata
array([[[  0, 147, 151],
        [  0, 103, 124],
        [  0,  58, 102],
        ...,
        [  0, 120, 115],
        [  0, 117, 106],
        [  0, 119, 110]],

       [[  0, 171, 171],
        [  0, 141, 143],
        [  0, 114, 124],
        ...,
        [  0, 118, 112],
        [  0, 115, 108],
        [  0, 116, 105]],

       [[  0, 194, 193],
        [  0, 178, 175],
        [  0, 165, 164],
        ...,
        [  0, 120, 117],
        [  0, 116, 112],
        [  0, 114, 109]],

       ...,

       [[  0, 170, 176],
        [  0, 170, 177],
        [  0, 168, 170],
        ...,
        [  0,   0,   0],
        [  0,   0,   1],
        [  0,   0,   0]],

       [[  0, 169, 170],
        [  0, 167, 171],
        [  0, 164, 176],
        ...,
        [  0,   0,   1],
        [  0,   1,   1],
        [  0,   0,   0]],

       [[  0, 167, 172],
        [  0, 165, 169],
        [  0, 162, 171],
        ...,
        [  0,   0,   0],
        [  0,   1,   1],
        [  0,   0,   0]]], dtype=uint8)
fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()
ax[0].imshow(original)
ax[0].set_title("Original")
ax[1].imshow(255 - Idata)
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_50_0.png

Colocar dos imagenes en un tensor#

Esta es una forma para organizar conjuntos de imágenes en un único tensor

original= np.expand_dims(original,axis=0)
original.shape
(1, 512, 512, 3)
Idata_m= np.expand_dims(Idata_m,axis=0)
images = np.concatenate((original, Idata_m),axis=0)
images.shape
(2, 512, 512, 3)
fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()
ax[0].imshow(images[0])
ax[0].set_title("Original")
ax[1].imshow(images[1])
ax[1].set_title("Modificada")

fig.tight_layout()
plt.show()
../../_images/Intro_Tensores_II_58_0.png

Trasformaciones afines#

En este ejemplo usaremos la librería OpenCV.

Esta es la imagen original tomada de omes-va.com. Código tomado del mismo sitio.

import numpy as np
import cv2
image = cv2.imread('../Imagenes/ave.jpeg')
#cv2.imshow('Imagen de entrada',image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Translación#

\[\begin{split} M =\begin{pmatrix} 1 & 0 & Tx\\ 0 & 1 & Ty \end{pmatrix} \end{split}\]
  • Tx, representa el desplazamiento en x.

  • Ty, representa el desplazamiento en y.

# Translación
ancho = image.shape[1] #columnas
alto = image.shape[0] # filas
# Traslación
M = np.float32([[1,0,100],[0,1,150]])
imageOut = cv2.warpAffine(image,M,(ancho,alto))
cv2.imshow('Imagen de entrada',image)
cv2.imshow('Imagen de salida',imageOut)
cv2.waitKey(0)
cv2.destroyAllWindows()

Rotación#

\[\begin{split} M =\begin{pmatrix} \cos \theta & -\sin \theta & 0\\ \cos \theta & \sin \theta & 1\\ 0 & 0 & 1 \end{pmatrix} \end{split}\]
  • \(\theta\) representa el ángulo de rotación. En este ejemplo \(\theta = \pi/4\) 0 lo que es lo mismo \(45^o\).

# rotación
ancho = image.shape[1] #columnas
alto = image.shape[0] # filas

M = cv2.getRotationMatrix2D((ancho//2,alto//2),15,1)
imageOut = cv2.warpAffine(image,M,(ancho,alto))
cv2.imshow('Imagen de entrada',image)
cv2.imshow('Imagen de salida',imageOut)
cv2.waitKey(0)
cv2.destroyAllWindows()
image.shape
(426, 640, 3)