Logo Studenta

Introducción a PyTorch para Aprendizaje Profundo

¡Este material tiene más páginas!

Vista previa del material en texto

PyTorch
September 21, 2022
1 Introduccion a PyTorch
En este primer capítulo, presentamos conceptos básicos de redes neuronales y aprendizaje pro-
fundo utilizando la biblioteca PyTorch.
Mi nombre es Juan Isaula y les presentaré Deep Learning con PyTorch. En este curso, vamos a
aprender conceptos básicos del aprendizaje profundo, una subfamilia de algoritmos de apren-
dizaje automático que ha estado a la vanguardia de los desarrollos recientes en Inteligencia Arti-
ficial.
1.1 Deep Learning (Aprendizaje Profundo)
Es posible que haya oído hablar de los éxitos de la clasificación de imagenes, la traducción au-
tomática, la conducción autónoma, Alpha Go o los bots informáticos que derrotaron a jugadores
profesionales en Starcraft (AlphaStar). Todas estas tecnologías han sido potenciadas por redes
neuronales, otro nombre para el aprendizaje profundo.
1
1.2 Redes Neuronales
La magia de las redes neuronales en comparacion con los modelos tradicionales está en el hecho de
que los modelos clásicos utilizan un algoritmo para la extracción de características seguido de un
clasificador de aprendizaje automatico, mientras que las redes neuronales hacen la optimización
por completo. Las primeras capas transforman la entrada en características que son fáciles de
clasificar, mientras que la capa final separa los datos en función de las características que han
generado las capas anteriores. Durante el curso, implementaremos muchos de los algoritmos
centrales de aprendizaje profundo utilizando la biblioteca PyTorch. Si bien los conceptos que
vamos a estudiar son generales, los ejemplos que vamos a utilizar están mayormente orientados a
la visión artificial, al igual que los conjuntos de datos.
1.2.1 ¿Por qué PyTorch?
Hay toneladas de excelente bibliotecas de aprendizaje profundo allí mismo. Elegimos pyTorch
por su:
• Sencilles
• Tiene un fuerte soporte de GPU
• Y ya ha implementado muchos algoritmos de aprendizaje profundo.
Tener un fuerte soporte OOP hace una elección natural para muchas empresas como Facebook
y Salesforce, además de ser una de las bibliotecas de aprendizaje profundo más utilizadas en la
investigación académica.
Calcular derivadas y gradientes es un aspecto muy importante de los algoritmos de aprendizaje
profundo. Afortunadamente PyTorch es muy bueno haciéndolo por nosotros. Finalmente, la
2
biblioteca es muy similar a NumPy,lo que hace que el cambio de NumPy a PyTorch sea lo menos
doloroso posible.
1.2.2 Multiplicación de Matrices
Las matrices son muy importantes en las redes neuronales. Los pesos y los valores de las redes se
almacenan en matrices y muchas de las operaciones se realizan en términos de multiplicaciones
de matrices. Así que es importante que el lector tenga una buena base sobre algebra matricial.
1.2.3 PyTorch en comparación con NumPy
El equivalente de PyTorch de NumPy ndarrays se llama tensor() Puede imaginar que un tensor
es una matriz con un número arbitrario de dimensiones. Se puede crear un tensor llamando a
torch.tensor(), como puede ver en el primer bloque de código de abajo. Al igual que en NumPy,
puede crear matrices aleatorias utilizando torch.rand(dim1, dim2). Vamos a crear una matriz
aleatoria con tamaños de 2× 2. De manera similar, puede establecer variables en matrices y puede
verificar su forma usando la función .shape.
[2]: # Creación de Matriz con PyTorch
import torch
torch.tensor([[2, 3, 5], [1, 2, 9]])
[2]: tensor([[2, 3, 5],
[1, 2, 9]])
[3]: # Creación de Matriz con Numpy
import numpy as np
np. array([[2, 3, 5], [1, 2, 9]])
[3]: array([[2, 3, 5],
[1, 2, 9]])
[4]: # Matriz aleatoria con PyTorch de 2x2
torch.rand(2, 2)
[4]: tensor([[0.5120, 0.3218],
[0.8740, 0.1745]])
[5]: # Matriz aleatoria con NumPy de 2x2
np.random.rand(2,2)
[5]: array([[0.75093478, 0.08774483],
[0.84899585, 0.12931466]])
[6]: # Verificacion de su forma usando PyTorch
a = torch.rand((3,5))
3
a.shape
[6]: torch.Size([3, 5])
[8]: # Verificacion de su forma usando NumPy
a = np.random.randn(3,5)
a.shape
[8]: (3, 5)
1.2.4 Operaciones Matriciales
Multiplicar matrices es una de las cosas más comunes que se hacen en el aprendizaje profundo.
En cada red neuronal que vas a entrenar, habrá millones de multiplicaciones de matrices. PyTorch
admite la multiplicación de matrices a través de la funcion torch.matmul(), como se puede ver en
el ejemplo aquí.
[19]: a = torch.rand((2, 2))
b = torch.rand((2, 2))
torch.matmul(a, b)
[19]: tensor([[0.2220, 0.7666],
[0.2566, 0.8878]])
[20]: a = np.random.rand(2, 2)
b = np.random.rand(2, 2)
np.dot(a, b)
[20]: array([[0.74001286, 1.27617829],
[0.40636382, 0.74089182]])
Otro operador importante es la multiplicación por elementos (donde cada elemento de la primera
matriz se multiplica por el elemento correspondiente de la segunda matriz), que se puede realizar
en PyTorch a través del operador de asterisco (*).
[21]: a*b
[21]: array([[0.34243091, 0.38442718],
[0.18433788, 0.56265311]])
[22]: np.multiply(a, b)
[22]: array([[0.34243091, 0.38442718],
[0.18433788, 0.56265311]])
4
1.2.5 Ceros y Unos
Algunos tipos especiales de matrices son matrices de ceros, matrices de unos y matrices de iden-
tidad. Estas matrices en PyTorch se pueden crear usando las funciones torch.zeros(), torch.ones()
y torch.eye(); muy similar a numpy.zeros(), numpy.ones() y numpy.identity().
[24]: a_torch = torch.zeros(2, 2)
a_torch
[24]: tensor([[0., 0.],
[0., 0.]])
[25]: a_numpy = np.zeros((2,2))
a_numpy
[25]: array([[0., 0.],
[0., 0.]])
[26]: b_torch = torch.ones(2, 2)
b_torch
[26]: tensor([[1., 1.],
[1., 1.]])
[27]: b_numpy = np.ones((2, 2))
b_numpy
[27]: array([[1., 1.],
[1., 1.]])
[28]: c_torch = torch.eye(2)
c_torch
[28]: tensor([[1., 0.],
[0., 1.]])
[29]: c_numpy = np.identity(2)
c_numpy
[29]: array([[1., 0.],
[0., 1.]])
Es fácil convertir matrices NumPy en tensores, se puede hacer a través de la función
from_numpy(). Del mismo modo, puede convertir tensores de antorcha en matrices NumPy a
través de la función numpy().
[30]: d_torch = torch.from_numpy(c_numpy)
d_torch
5
[30]: tensor([[1., 0.],
[0., 1.]], dtype=torch.float64)
[31]: d = c_torch.numpy()
d
[31]: array([[1., 0.],
[0., 1.]], dtype=float32)
Hemos preparado un resumen de las operaciones con matrices, así que no dudes en consultarlo si
olvidas los nombres de las funciones.
1.3 Practica
1.3.1 Creando Tensores en PyTorch
Los tensores aleatorios son muy importantes en las redes neuronales. Los parámetros de las redes
neuronales normalmente se inicializan con pesos aleatorios (tensores aleatorios).
Comencemos a practicar la construcción de tensores en la biblioteca de PyTorch. Como sabes,
los tensores son arreglos con un número arbitrario de dimensiones, correspondientes a los ndar-
rays de NumPy. Vamos a crear un tensor aleatorio de tamaños 3 por 3 y establecerlo en variable
your_first_tensor. Luego, tendrás que imprimirlo. Finalmente, calcule su tamaño en variable ten-
sor_size e imprima su valor.
[32]: # Importar la biblioteca principal de PyTorch.
import torch
# Creamos tensor aleatorio de 3x3
your_first_tensor = torch.rand(3, 3)
# Calculamos la forma del tensor
tensor_size = your_first_tensor.shape
6
# Imprime los valores del tensor y su forma
print(your_first_tensor)
print(tensor_size)
tensor([[0.6587, 0.0100, 0.9180],
[0.2484, 0.5444, 0.2647],
[0.0361, 0.8908, 0.9507]])
torch.Size([3, 3])
Hay muchos tipos importantes de matrices que tienen sus usos en las redes neuronales. Algunas
matrices importantes son las matrices de unos (donde cada entrada se establece en 1) y la matriz
de identidad (donde la diagonal se establece en 1 mientras que todos los demás valores son 0). La
matriz identidad es muy importante en álgebra lineal: cualquier matriz multiplicada por matriz
identidad es simplemente la matriz original.
Experimentemos con estos dos tipos de matrices. Vas a construir una matriz de unos con forma
de 3 por 3 llamada tensor_of_onesy una matriz identidad de la misma forma, llamada iden-
tity_tensor. Vamos a ver qué pasa cuando multiplicamosestas dos matrices, y qué pasa si hacemos
una multiplicación elemental de ellas.
[34]: # Crear una matriz de unos de 3x3
tensor_of_ones = torch.ones(3, 3)
# Crear una matriz identidad de 3x3
identity_tensor = torch.eye(3)
# Hacemos una matriz de multiplicacion de unos con identidad
matrices_multiplied = torch.matmul(tensor_of_ones, identity_tensor)
print(matrices_multiplied)
element_multiplication = tensor_of_ones * identity_tensor
print(element_multiplication)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
2 Forward propagation (Propagación hacia adelante)
Aquí vamos a explicar la propagación hacia adelante (también conocida como paso hacia ade-
lante), un paso importante en el entrenamiento y la evaluación de todos los clasificadores.
7
2.1 ¿Por qué propagación directa?
Tomemos un clasificador, tal vez una red neuronal. No se preocupe si no sabe lo que es, le prometo
que pronto lo aprenderá, y el principio es general para la mayoría de los clasificadores. El primer
paso es evaluar qué clasificador está haciendo con sus datos. Dados algunos datos (la capa amar-
illa), el modelo realiza toda la operación en las siguientes capas (azules) hasta que da algún resul-
tado en la capa de salida (marrón). Este paso se llama el paso hacia adelante.
2.1.1 Paso hacia adelante de un gráfico computacional simple
Vamos a empezar con un ejemplo sencillo. Supongamos que tenemos un modelo, podría ser una
red neuronal, algún otro tipo de clasificador o, en términos más abstractos, un gráfico computa-
cional. Intuitivamente, un gráfico computacional es una red de nodos que representan números,
escalares o tensores y están conectados mediante aristas que representan funciones u operaciones.
Por simplicidad y visualización, en lugar de usar grandes tensores multidimensionales, vamos a
usar escalares. Nuestro gráfico tiene nodos a, b, c y d y algunas operaciones entre los nodos, como
sumar el nodo a con el nodo b, o multiplicar el nodo c con el nodo d. Nuestro trabajo es hacer
todas las operaciones hasta obtener el resultado g.
8
Primero sumamos a y b, obteniendo 2 + (−4) = −2, poniendo el resultado en el nodo e. Del
mismo modo, multiplicamos c con d, obteniendo (−2) ∗ 2 = −4, y ponemos el resultado en el
nodo f. Ahora nuestros nodos en el gráfico son los nodos e y f, que contienen valores (-2) y (-4).
El último paso es multiplicar estos dos valores y poner el resultado en g, como se muestra en el
grafico siguiente.
2.2 Implementación de PyTorch
Implementémoslo en PyTorch. Primero inicializamos los tensores a, b, c y d, a sus valores corre-
spondientes 2, (-4), (-2) y 2. Luego sumamos a y b al tensor e, y multiplicamos c con d, poniendo
el resultado en tensor f. Finalmente, multiplicamos e con f, poniendo el resultado en el tensor g,
y luego imprimimos los valores de estos tres tensores, obteniendo el mismo resultado que en la
figura previa.
[35]: import torch
a = torch.Tensor([2])
b = torch.Tensor([-4])
c = torch.Tensor([-2])
d = torch.Tensor([2])
e = a + b
f = c*d
g = e*f
print(e, f, g)
tensor([-2.]) tensor([-4.]) tensor([8.])
Ahora, casi no hay nada sofisticado en este ejercicio, sin embargo, comprenderlo es de suma im-
portancia. Las redes neuronales (y la mayoría de los otros clasificadores) pueden entenderse como
gráficos computacionales (de hecho, su código se convierte en un gráfico computacional), solo que
los gráficos serán mucho más grandes y los tensores dentro de ellos contendrán millones de en-
tradas. Un beneficio adicional de los gráficos computacionales, es que hacen que el cálculo de la
derivada (o gradientes) mucho más fácil, como veremos en la siguiente sección.
9
2.3 Practica
Tengamos algo que se asemeje más a una red neuronal. El gráfico computacional se ha dado
a continuación. Vas a inicializar 3 tensores aleatorios grandes y luego hacer las operaciones
como se indica en el gráfico computacional. La operación final es la media del tensor, dada por
torch.mean(your_tensor).
[36]: # Inicializamos los tensores x,y,z.
x = torch.rand(1000, 1000)
y = torch.rand(1000, 1000)
z = torch.rand(1000, 1000)
# Multiplicamos x e y
q = x*y
# Multiplicamos z y q
f = z*q
# Computo de la media del Tensor
mean_f = torch.mean(f)
print(mean_f)
tensor(0.1251)
3 Backpropagation by auto-differentiation (Retropropagación por
diferenciación automática)
En esta sección, vamos a presentar el algoritmo principal de las redes neuronales, el llamado
algoritmo de retropropagación, y veremos cómo podemos usarlo en PyTorch. Esta lección es un
poco más teórica que la mayoría de las lecciones del curso, pero no hay necesidad de asustarse
por eso.
10
3.1 Derivadas
Las derivadas son uno de los conceptos centrales del cálculo. En términos simples, las derivadas
representan la tasa de cambio en una función, por lo que cuando la función cambia rápidamente,
el valor absoluto de las derivadas es alto, mientras que cuando la función no cambia, las derivadas
están cerca de 0. También podrían ser interpretada como la descripción de la inclinación de una
función.
Por ejemplo, en la función aquí, los puntos A y C tienen derivadas grandes, la línea es empinada
en estas posiciones, mientras que el punto B tiene una derivada muy pequeña. Si nunca ha oído
hablar de los derivados, le recomiendo echarles un vistazo, en Khan Academy, por ejemplo.
Reglas de Derivadas
Algunas reglas importantes de las derivadas son la regla de la suma y la multiplicación. La regla
de la suma (o suma) dice que para dos funciones f y g, la derivada de su suma es la suma de sus
derivadas individuales. Por otro lado, la regla de la multiplicación dice que la derivada de su
producto es f por la derivada de g más g por la derivada de f. La derivada de un número por una
función, es el número, por ejemplo, la derivada de 3x es 3. La derivada de un número en sí mismo
11
siempre es 0. La derivada de algo con respecto a sí mismo siempre es 1. Otra regla importante
es la regla de la cadena que se ocupa de la composición de funciones. Un término estrechamente
relacionado con las derivadas es el gradiente. El gradiente es una generalización multivariable
de la derivada, y considerando que las redes neuronales tienen muchas variables, normalmente
usaremos el término gradiente en lugar de derivada cuando trabajemos con redes neuronales.
3.1.1 Ejemplo de Derivada: Paso Adelante
Concretemos las cosas con un ejemplo. Tenemos 3 variables, x, y y z con valores −3, 5 y −2.
Primero sumamos x e y en la variable q, que es −3 + 5 = 2. Luego multiplicamos z por q, lo que
nos da −4.
Ahora, calculemos las derivadas. Los ponemos en los cuadros azules debajo de los valores de los
nodos. La derivada de f con respecto a sí misma es 1.
De manera similar, la derivada de z es q por la derivada de f, igual a 2.
12
Finalmente, calculamos las derivadas de x e y. Debido a la regla de la suma, su derivada es 1 veces
la derivada de q (de la regla de la cadena), lo que nos da -2. La lógica de los cálculos es la misma
independientemente de si tenemos 5 variables o mil millones de variables.
3.2 Backpropagation hacia atras en PyTorch
Las derivadas se calculan en PyTorch utilizando el modo inverso de diferenciación automática, por
lo que rara vez necesitará escribir código para calcular las derivadas. Calculemos las derivadas
del ejemplo de la sección anterior. Primero, inicializamos los tensores x, y y z con valores -3, 5 y -2.
Sin embargo, debemos establecer el indicador require_grad en True, para decirle a PyTorch que
necesitamos sus derivados. Luego sumamos x e y en q, y multiplicamos q con z en f. Finalmente,
escribimos f.backward() para decirle a PyTorch que calcule las derivadas. Los resultados son los
mismos que cuando los calculamos a mano. tensor.grad simplemente obtiene el gradiente de ese
tensor.
[40]: import torch
x = torch.tensor(-3., requires_grad=True)
y = torch.tensor(5., requires_grad=True)
z = torch.tensor(-2., requires_grad=True)
q = x + y
f = q*z
f.backward()
13
print("Gradientof z is: " + str(z.grad))
print("Gradient of y is: " + str(y.grad))
print("Gradient of x is: " + str(x.grad))
Gradient of z is: tensor(2.)
Gradient of y is: tensor(-2.)
Gradient of x is: tensor(-2.)
4 Introducción a Redes Neuronales
Vamos a presentar las redes neuronales. Más precisamente, vamos a estudiar redes neuronales
completamente conectadas, la forma más simple de redes neuronales modernas.
En aprendizaje supervisado con scikit-learn, ha visto algunos clasificadores como k-nn. Hay mu-
chos otros clasificadores, algunos muy buenos, como Random Forests, Adaboost o Support Vec-
tor Machines. Estos clasificadores funcionan bien cuando los datos se dan en formato vectorial,
como características (Ver siguiente figura).
14
4.1 ANN vs otros clasificadores
Sin embargo, la mayoría de los datos no se dan como características. En cambio, los datos están en
algún enriquecido, como imagenes, voz, texto o video. En esos casos, lo que la gente hacía antes
era usar otro algoritmo para extraer características. En visión por computadora, la mayor parte
de la investigación de la última decada se centró en encontrar algoritmos que obtengan buenas
características de las imagenes. Quizás el más famaoso de esos algoritmos fue el algoritmo SIFT,
que, dada una imagen, devuelve las características de esa imagen. Luego, esas características
se clasifican utilizando un clasificador como SVM. Tal vez puedas ver el problema aquí. Para
solucionar el problema, estamos optimizando dos algoritmos diferentes (SIFT y SVM) que no
tienen nada que ver entre si.
Las redes neuronales funcionan un poco diferente. Tienen una capa de entrada (en la figura indi-
cada con 1), una o más capas ocultas (indicadas con 2), y una capa de salida (indicada con 3).
15
El trabajo de las capas ocultas es obtener buenas características, mientras que el trabajo de la capa
de salida es clasificar esas características. Con la red entrenada de extremo a extremo, tenemos
un solo algoritmo que al mismo tiempo encuentra buenas características y las clasifica. Esto ha
demostrado funcionar muy bien, por lo que las redes neuronales han revolucionado muchos cam-
pos, hasta el punto de dejar obsoletos los viejos algoritmos.
4.2 Redes neuronales totalmente conectadas
Hechos un vistazo mas de cerca a la red dada en la ultima imagen.
Cada neurona (o mejor llamada unidad para evitar la analogía con las neuronas biológicas) en
una capa está conectada con cada unidad tanto en la capa anterior como en la siguiente. Estas
conexiones se denominan pesos y se representan mediante una matriz (tensor). Como entrada,
tenemos input_layer que contiene 10 unidades. Para obtener los valores de la primera capa oculta
h1, multiplicamos el vector de características con la primera matriz de pesos, llamada w1. Fíjese en
la matriz de pesos, la primera dimensión siempre debe corresponder a la capa anterior, mientras
que la segunda dimensión a la capa siguiente. Como puedes imaginar, h1 contiene 20 unidades.
Del mismo modo, continuamos con la segunda capa oculta h2, que es el producto de la primera
capa oculta h1 y la segunda matriz de pesos w2. Finalmente, obtenemos los resultados de out-
put_layer, que tiene 4 clases, multiplicando la segunda capa oculta h2 con la tercera matriz de
pesos.
[41]: import torch
input_layer = torch.rand(10)
w1 = torch.rand(10, 20)
w2 = torch.rand(20, 20)
w3 = torch.rand(20, 4)
h1 = torch.matmul(input_layer, w1)
h2 = torch.matmul(h1, w2)
output_layer = torch.matmul(h2, w3)
print(output_layer)
tensor([257.2097, 352.2205, 237.0192, 256.4155])
4.3 Construyendo una Red Neuronal - Estilo PyTorch
Escribir redes neuronales de esta manera es un poco complicado. Afortunadamente, PyTorch tiene
una forma mejor de hacerlo, que está orientada a objetos. Definimos una clase, llamémosla Net,
que hereda de nn.Module (mira que estamos importando torch.nn). En el método init, defini-
mos nuestros parámetros, los tensores de pesos. Para capas totalmente conectadas, se denominan
nn.Linear. El primer parámetro es el número de unidades de la capa actual, mientras que el se-
gundo parámetro es el número de unidades en la siguiente capa. En el método directo, aplicamos
todos esos pesos a nuestra entrada. Finalmente, creamos una instancia de nuestro modelo lla-
mando a la clase Net y obtenemos el resultado aplicando object net sobre nuestra input_layer.
16
[43]: import torch
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(10, 20)
self.fc2 = nn.Linear(20, 20)
self.output = nn.Linear(20, 4)
def forward(self, x):
x = self.fc1(x)
x = self.fc2(x)
x = self.output(x)
return x
input_layer = torch.rand(10)
net = Net()
result = net(input_layer)
17
	Introduccion a PyTorch
	Deep Learning (Aprendizaje Profundo)
	Redes Neuronales
	¿Por qué PyTorch?
	Multiplicación de Matrices
	PyTorch en comparación con NumPy
	Operaciones Matriciales
	Ceros y Unos
	Practica
	Creando Tensores en PyTorch
	Forward propagation (Propagación hacia adelante)
	¿Por qué propagación directa?
	Paso hacia adelante de un gráfico computacional simple
	Implementación de PyTorch
	Practica
	Backpropagation by auto-differentiation (Retropropagación por diferenciación automática)
	Derivadas
	Ejemplo de Derivada: Paso Adelante
	Backpropagation hacia atras en PyTorch
	Introducción a Redes Neuronales
	ANN vs otros clasificadores
	Redes neuronales totalmente conectadas
	Construyendo una Red Neuronal - Estilo PyTorch

Continuar navegando