Descarga la aplicación para disfrutar aún más
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
Compartir