paint-brush
Manipulación de tensores en PyTorch. ¡El primer paso para el deep learning!by@espejelomar
5,112 reads
5,112 reads

Manipulación de tensores en PyTorch. ¡El primer paso para el deep learning!

by Omar U. EspejelJanuary 15th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Pytorch is a herramientienta for the construcción of tensores capaces of correr en CUDA (GPU) The mejor forma de aprender deep learning is escribir and escribar código. The author uses this to expand the conocimiento of deep learning in español. The tensor is a form of tensor como the estructura de datos básica in the creación de deep learning.

People Mentioned

Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Manipulación de tensores en PyTorch. ¡El primer paso para el deep learning!
Omar U. Espejel HackerNoon profile picture

*Nota: Contactar a Omar Espejel ([email protected]) para cualquier observación. Cualquier error es responsabilidad del autor.

*Nota: El autor considera que, si bien el escrito está en español y el objetivo es expandir el conocimiento del deep learning en este idioma, existen palabras insustituibles del inglés. Por lo tanto, este y siguientes escritos utilizarán indiscriminadamente palabras en inglés.

Manipulación de tensores

Se introduce el tensor como la estructura de datos básica en la creación de deep learning. Usamos pytorch como herramienta para la construcción de tensores capaces de correr en CUDA (GPU). Finalmente, aplicamos manipulación de tensores para hacer broadcasting (transmisión, en español), agregar y quitar dimensiones, y hacer reshaping de nuestras estructuras. Terminamos con una función que utilizaremos más adelante para aplanar imágenes.

¿Dónde correr el código?

En mi opinión, la mejor forma de aprender deep learning es escribir y escribir código. Esto es irrealizable, en terminos de tiempo, si se utiliza el poder de una CPU. Estaremos trabajando en Google Colab donde se nos asigna de manera gratuita por máximo 12 horas un GPU modelo Tesla K80. Solo necesitamos una cuenta de Google.

Ingresar a 

https://colab.research.google.com
 y hacer click en 
NUEVO BLOCK DE NOTAS DE PYTHON 3
. Ya podemos comenzar a escribir código python en la nube completamente gratis. Para acceder a un GPU dar click en 
Entorno de ejecución
-
Cambiar tipo de entorno de ejecución
 y en acelerador de hardware seleccionar GPU. Eso es todo, estamos listos para hacer deep learning.

El tensor

En matemáticas estamos comunmente familiarizados con tres estructuras de datos: el escalar, el vector y la matriz. Los escalares corresponden a un único número, 2 por ejemplo; los vectores son una formación ordenada (arrays, en inglés) de datos en una única dimensión, por ejemplo (1,3,5,6,7,8); las matrices son una formación ordenada de dos dimensiones, por ejemplo ([1,2,3],[4,5,6]). Sin embargo, ¿cómo nombramos a las estructuras de datos, como las imágenes, de más de 2 dimensiones? La siguiente imagen obtenida de Leonardo Araujo Santos nos permite visualizar estas estructuras de datos y la limitación al tratar de nombrar estructuras de más de dos dimensiones.

Los matemáticos tienen la tendencia de nombrar cada particularidad de una forma diferente, en deep learning usaremos una manera general de nombrar las estructuras de datos. No hablaré de escalares, vectores o matrices, sino de tensores de rango cero, uno y dos, respectivamente. Para una explicación más detallada de lo que es un tensor ver este artículo. Sin embargo, para los fines de este artículo, pensemos en los tensores como una formación ordenada de N dimensiones.

Creemos tensores (si se tienen conocimientos previos de lenguajes como R y Matlab entonces la sintaxis les parecerá conocida). Primero necesitamos importar Pyorch: 

import torch
. Ahora creemos un tensor de rango cero:

t = torch.tensor(1, dtype = torch.int32, device = "cuda")

El argumento

dtype
selecciona el tipo de tensor. En este caso usamos enteros pero podemos incluir 
torch.double
 o 
torch.float64
 para crear variables que almacenen números con puntos decimales. En
device
seleccionamos el tipo de hardware a utilizar, en este caso utilizaremos CUDA para correr nuestros tensores en nuestra GPU. Si fueramos a correr nuestros modelos en un CPU entonces usamos 
device = "cpu"
. Vamos con tensores de rango 1 y 2:

Rango 1:

t = torch.tensor([1.1, 2.2, 3.3], dtype = torch.double64, device = "cuda")

Rango 2:

t = torch.tensor([
    [1,1,1,1],
    [2,2,2,2],
    [3,3,3,3]
], dtype=torch.float32, device = "cuda")

Para conocer la forma de un tensor utilizamos 

t.shape
, que en este caso nos regresa 
torch.Size([3, 4])
 pues nuestro tensor 
t
 tiene tres filas y cuatro columnas. Más adelante crearemos tensores de rango 3 que nos servirán como inputs para nuestros modelos.

Finalmente, podemos realizar operaciones entre tensores. Usemos nuestro tensor 

t
 de forma 
torch.Size([3,4])
.

  1. Suma: 
    t + t 
    o
    t.sum(t)
    .
  2. Multiplicación
    t*t
    .
  3. Multiplicación de matrices
    t.mm(s)
     donde 
    s
     se define como:
s = torch.tensor([
    [1,1,1],
    [2,2,2],
    [3,3,3],
    [4,4,4]
], dtype=torch.float32, device = "cuda")

Además, pytorch nos permite hacer indexing de la misma manera que se hace con numpy. No ahondamos en el tema dado que no es tan relevante para iniciar con el deep learning, sin embargo, en la documentación de pytorch se encuentra todo lo necesario.

Broadcasting (transmisión, en español)

El broadcasting es una herramienta muy poderosa que nos permite trabajar mejor con tensores sin usar fors. Esta técnica existe también en numpy.

Cuando pytorch opera en dos tensores, compara sus formas en cuanto a elementos. Comienza con las dimensiones finales y avanza.

Dos dimensiones son compatibles cuando: (1) son iguales o (2) una de ellas es 1, en cuyo caso esta dimensión se transmite para que tenga el mismo tamaño. Esto significa que una dimensión con valor de 1 es compatible con cualquier otra dimensión, ya que se transmitirá. Por ejemplo, se puede operar en tensores de forma (1,3) y (5,1) ya que 1 es compatible con 3 y 5.

Los tensores no necesitan tener el mismo número de dimensiones. Por ejemplo, si tienes un tensor 256 * 256 * 3 de valores RGB (red-green-blue), 

RGB_tensor
, y deseas escalar cada color de la imagen por un valor diferente, puedes multiplicar la imagen por un tensor de rango 1, 
t
, con 3 valores, es decir con forma (3). Los tamaños de los ejes finales de estos tensores, de acuerdo con las reglas de transmisión, los hace compatibles.

Para hacer esto necesitamos compatibilizar nuestros tensores 

RGB_tensor
 y 
t
. Para multiplicar cada dimensión del 
RGB_tensor
 de acuerdo con cada uno de los valores en 
t
, necesitamos expandir a este último para tener 1s en el primer eje de
t
t = t[None, None,:]
 creará un tensor en forma (1,1,3) donde el tercer eje incluye los valores que se aplicarán a cada uno de los colores en 
RGB_tensor
t.expand_as(RGB_tensor)
 te dará el tensor que puede multiplicar por la imagen de tamaño (256,256,3). Los ejes con valores 1 se transmitirán.

Estamos escribiendo código que se ejecuta tan rápido como C con esto. Además, el broadcasting nos da velocidad C sin necesidad de memoria adicional. Toma un poco de tiempo acostumbrarse a la forma de codificación de broadcasting, pero, según Jeremy Howard en su clase de practical deep learning, vale la pena ya que reduce muchos errores provenientes de los bucles, y también es más cómodo una vez que nos acostumbramos.

De acuerdo a Jeremy Howard, el broadcasting podría ser el truco más útil para escribir código que sea lo suficientemente rápido.

Agregar dimensiones o ejes

En el texto se utilizarán de forma indiscriminada las palabras "dimensión" y "eje".

Hay dos formas diferentes de agregar una nueva dimensión a un Tensor de pytorch

tensor.unsqueeze(dim = 0)
, por ejemplo, agregará un nuevo eje en la dimensión 0 del tensor de modo que si el tensor originalmente tenía forma (3,750,750) después de la transformación, obtendremos una forma (1,3,750,750). La segunda forma es la favorita de Jeremy Howard por su simplicidad. Lo mismo que antes se puede lograr escribiendo tensor[None,:,:, :). También funciona para numpy. Otro truco útil proviene de recordar que -1 siempre se refiere a la última dimensión del tensor, por ejemplo, t.unsqueeze (-1) agrega un nuevo eje en la última dimensión.

Esto es relevante porque nos permite elegir cómo transmitir nuestros tensores. Considere que la transmisión se producirá automáticamente para la dimensión del tensor con forma 1. Por ejemplo, si tenemos un tensor, 

t
, con forma (3,3) y un tensor, 
s
, con forma
torch.Size([3])
, entonces 
s = torch.unsqueeze(s, dim = 0)
 creará un tensor horizontal de forma (1,3) que podemos expandir al tamaño de
t
simplemente como 
t.expand_as(t)
 que producirá un tensor en forma (3,3) que transmitirá los valores del vector horizontal para cada fila.

Nota: Squeeze hace lo contrario que unsqueeze(); elimina dimensiones.

Reshaping (remodelación, en español) de tensores

Continuemos con nuestro tensor 

t
. Podemos conocer el rango y la forma del tensor con 
t.shape
 y 
len(t.shape)
t.numel()
 muestra el número de elementos en el tensor:

t.shape, len(t.shape), t.numel()

que da como resultado 

(torch.Size([3, 4]), 2, 12)
. En 
t
, por lo tanto, tenemos 12 elementos acomodados en dos dimensiones. Los 12 elementos los podemos acomodar de diferentes en diferentes dimensiones, por ejemplo:

t.reshape(1,12), t.reshape(6,2), t.reshape(12,1);

Nota: reshape() y view() hacen lo mismo.

“Veamos un caso de uso común para squeeze un tensor mediante la construcción de una función flatten (aplanar). […] Una operación de aplanar en un tensor da nueva forma al tensor para que tenga una forma que sea igual al número de elementos contenidos en el tensor. Esto es lo mismo que un tensor de rango 1°. […] Aplanar un tensor significa eliminar todas las dimensiones excepto una” - Deeplizard.

En este texto, recreé la función de aplanar en el blog para tomar en cuenta los batches (lotes, en español) de datos.

Indiquemos que nuestro tensor es un batch creando una dimensión adicional en la posición 0:

t = t[None, :]
t.shape

Da 

torch.Size([1, 3, 4])
. Ahora usemos la función 
cat
 para concatenar dos 
t
 de forma que nuestro batch ahora tenga dos tensores.

t = t[None, :]
t_batch = torch.cat((t, t), dim = 0)

t_batch.shape
retorna 
torch.Size([2, 3, 4])
. Definimos entonces nuestra función para aplanar que toma en cuenta los batches.

def flatten(t):
  f = torch.reshape(t, (t.shape[0], 1, -1))
  return f.squeeze()

Entonces 

t_batch.shape
,
flatten(t_batch).shape
 retorna 
(torch.Size([2, 3, 4])
,
torch.Size([2, 12]))
.

“Dado que el argumento t puede ser cualquier tensor, pasamos -1 como segundo (tercer) argumento a la función reshape(). En pytorch, el -1 le dice a la función reshape() que descubra cuál debe ser el valor en función del número de elementos contenidos dentro del tensor. […] Veremos que se requieren operaciones de aplanamiento cuando se pasa un tensor de salida de una capa convolucional a una capa lineal.” Deeplizard.

En el siguiente texto comenzaremos con los básicos del deep learning.