Created
February 19, 2024 13:06
-
-
Save rafaelvareto/86ffedbd522b28b31a82800f047a0107 to your computer and use it in GitHub Desktop.
19_02 - Programacao Diferenciável (Gabarito)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/rafaelvareto/86ffedbd522b28b31a82800f047a0107/19_02-programacao-diferenci-vel-gabarito.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "2728a6f2", | |
| "metadata": { | |
| "id": "2728a6f2" | |
| }, | |
| "source": [ | |
| "# Programação Diferenciável (Aprendizado Profundo - UFMG)\n", | |
| "\n", | |
| "## Preâmbulo\n", | |
| "\n", | |
| "O código abaixo consiste dos imports comuns. Além do mais, configuramos as imagens para ficar de um tamanho aceitável e criamos algumas funções auxiliares. No geral, você pode ignorar a próxima célula." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "a2ef04d2", | |
| "metadata": { | |
| "id": "a2ef04d2" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# -*- coding: utf8\n", | |
| "\n", | |
| "import matplotlib.pyplot as plt" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "7ee22830", | |
| "metadata": { | |
| "id": "7ee22830" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "plt.rcParams['figure.figsize'] = (8, 5)\n", | |
| "\n", | |
| "plt.rcParams['axes.axisbelow'] = True\n", | |
| "plt.rcParams['axes.labelsize'] = 16\n", | |
| "plt.rcParams['axes.linewidth'] = 1\n", | |
| "plt.rcParams['axes.spines.bottom'] = True\n", | |
| "plt.rcParams['axes.spines.left'] = True\n", | |
| "plt.rcParams['axes.titlesize'] = 16\n", | |
| "plt.rcParams['axes.ymargin'] = 0.1\n", | |
| "\n", | |
| "plt.rcParams['font.family'] = 'serif'\n", | |
| "\n", | |
| "plt.rcParams['axes.grid'] = True\n", | |
| "plt.rcParams['grid.color'] = 'lightgrey'\n", | |
| "plt.rcParams['grid.linewidth'] = .1\n", | |
| "\n", | |
| "plt.rcParams['xtick.labelsize'] = 16\n", | |
| "plt.rcParams['xtick.bottom'] = True\n", | |
| "plt.rcParams['xtick.direction'] = 'out'\n", | |
| "plt.rcParams['xtick.major.size'] = 10\n", | |
| "plt.rcParams['xtick.major.width'] = 1\n", | |
| "plt.rcParams['xtick.minor.size'] = 3\n", | |
| "plt.rcParams['xtick.minor.width'] = .5\n", | |
| "plt.rcParams['xtick.minor.visible'] = True\n", | |
| "\n", | |
| "plt.rcParams['ytick.labelsize'] = 16\n", | |
| "plt.rcParams['ytick.left'] = True\n", | |
| "plt.rcParams['ytick.direction'] = 'out'\n", | |
| "plt.rcParams['ytick.major.size'] = 10\n", | |
| "plt.rcParams['ytick.major.width'] = 1\n", | |
| "plt.rcParams['ytick.minor.size'] = 3\n", | |
| "plt.rcParams['ytick.minor.width'] = .5\n", | |
| "plt.rcParams['ytick.minor.visible'] = True\n", | |
| "\n", | |
| "plt.rcParams['legend.fontsize'] = 16\n", | |
| "\n", | |
| "plt.rcParams['lines.linewidth'] = 4\n", | |
| "plt.rcParams['lines.markersize'] = 10" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "2df6a530", | |
| "metadata": { | |
| "id": "2df6a530", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "4b48098b-3bd7-41ce-9fa8-baf48f6ef7c4" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "<matplotlib.pyplot._IonContext at 0x7faa56ce9490>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 3 | |
| } | |
| ], | |
| "source": [ | |
| "plt.style.use('tableau-colorblind10') # use um estilo colorblind!\n", | |
| "plt.ion()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "0648be06", | |
| "metadata": { | |
| "id": "0648be06" | |
| }, | |
| "source": [ | |
| "## 1. Tensores em Numpy\n", | |
| "\n", | |
| "O primeiro passo para usar numpy é importar a biblioteca.\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "f7d132a0", | |
| "metadata": { | |
| "id": "f7d132a0" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "import numpy as np" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "d5dba789", | |
| "metadata": { | |
| "id": "d5dba789" | |
| }, | |
| "source": [ | |
| "Quando pensamos no lado prático do aprendizado profundo, um aspecto chave que ajuda na implementação de novos algoritmos é a chamada programação diferenciável. Na próxima aula vamos voltar na mesma. No momento, o importante é salientar que a programação diferenciável faz uso extensivo de Tensores." | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Um [Tensor](http://en.wikipedia.org/wiki/Tensor) é uma generalização de matrizes para mais dimensões. Quando falamos de tensores, temos três casos especiais e um genérico que engloba os outros três:\n", | |
| "\n", | |
| "\n", | |
| "1. **Escalar:** Um tensor de zero dimensões." | |
| ], | |
| "metadata": { | |
| "id": "40aqkx3bQLSl" | |
| }, | |
| "id": "40aqkx3bQLSl" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "8290c1a3", | |
| "metadata": { | |
| "id": "8290c1a3", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "db1649ec-ef83-40ae-a6ac-f131087f8d66" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "1" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 5 | |
| } | |
| ], | |
| "source": [ | |
| "1" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "d1c675fd", | |
| "metadata": { | |
| "id": "d1c675fd" | |
| }, | |
| "source": [ | |
| "2. **Vetor:** Um tensor de uma dimensão." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "fcc9d3d7", | |
| "metadata": { | |
| "id": "fcc9d3d7", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "d1425c37-11be-4158-89ed-eeaeac7a0791" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([1, 2])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 6 | |
| } | |
| ], | |
| "source": [ | |
| "np.array([1, 2])" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "59189d49", | |
| "metadata": { | |
| "id": "59189d49" | |
| }, | |
| "source": [ | |
| "3. **Matrizes:** Um tensor de duas dimensões." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "8c649337", | |
| "metadata": { | |
| "id": "8c649337", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "ebd7301d-92fd-41de-fc95-7133109e0a57" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 2],\n", | |
| " [3, 4]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 7 | |
| } | |
| ], | |
| "source": [ | |
| "np.array([[1, 2], [3, 4]])" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "be770aea", | |
| "metadata": { | |
| "id": "be770aea" | |
| }, | |
| "source": [ | |
| "4. **Tensores**. Caso geral, representam n-dimensões. Na figura temos um tensor 3x3x3.\n", | |
| "\n", | |
| "\n", | |
| "# " | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "No exemplo abaixo, temos um tensor de dimensão $3 \\times 2 \\times 2$." | |
| ], | |
| "metadata": { | |
| "id": "uPNvuQMrQ8Gx" | |
| }, | |
| "id": "uPNvuQMrQ8Gx" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "ce0624d4", | |
| "metadata": { | |
| "id": "ce0624d4", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "00f594ff-eca9-4350-b076-e7e69b8bde53" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[[-0.06660965, 0.31547158],\n", | |
| " [ 0.13452463, 0.55815581]],\n", | |
| "\n", | |
| " [[-1.48999998, -1.61214135],\n", | |
| " [-1.04124586, 0.67929137]],\n", | |
| "\n", | |
| " [[-0.35691515, -1.11419325],\n", | |
| " [-0.84432855, 2.15831105]]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 8 | |
| } | |
| ], | |
| "source": [ | |
| "X = np.random.randn(3, 2, 2) # Gera números aleatórios de uma normal N(0, 1)\n", | |
| "X" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Note que ao selecionar elementos da primeira dimensão ficamos com matrizes $2 \\times 2$." | |
| ], | |
| "metadata": { | |
| "id": "E1Ybc0pHRUNS" | |
| }, | |
| "id": "E1Ybc0pHRUNS" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "fd2a9a86", | |
| "metadata": { | |
| "id": "fd2a9a86", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "9a3350c1-595d-4db8-c079-0a26ffeab686" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[-0.06660965, 0.31547158],\n", | |
| " [ 0.13452463, 0.55815581]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 9 | |
| } | |
| ], | |
| "source": [ | |
| "X[0]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "a9b07a30", | |
| "metadata": { | |
| "id": "a9b07a30", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "552c09ce-1000-4bf6-96f2-e1a34e2e7bbb" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[-1.48999998, -1.61214135],\n", | |
| " [-1.04124586, 0.67929137]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 10 | |
| } | |
| ], | |
| "source": [ | |
| "X[1]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "26769cb9", | |
| "metadata": { | |
| "id": "26769cb9", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "f8940eca-bf0f-4d12-d14e-807443cfb561" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[-0.35691515, -1.11419325],\n", | |
| " [-0.84432855, 2.15831105]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 11 | |
| } | |
| ], | |
| "source": [ | |
| "X[2]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "5d62c20e", | |
| "metadata": { | |
| "id": "5d62c20e" | |
| }, | |
| "source": [ | |
| "### 1.1) Indexando\n", | |
| "\n", | |
| "Sendo X uma matriz:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "4b7360f7", | |
| "metadata": { | |
| "id": "4b7360f7", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "afc18d56-8906-405e-af76-942ed1df4caa" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 2],\n", | |
| " [3, 4]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 12 | |
| } | |
| ], | |
| "source": [ | |
| "X = np.array([[1, 2], [3, 4]])\n", | |
| "X" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "cc670b09", | |
| "metadata": { | |
| "id": "cc670b09" | |
| }, | |
| "source": [ | |
| "Podemos selecionar uma linha com a sintaxe `X[i]`, sendo `i` um inteiro." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "35bfe57f", | |
| "metadata": { | |
| "id": "35bfe57f", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "ca51acee-3c85-43bd-b1f7-4e6e2a61af92" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([1, 2])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 13 | |
| } | |
| ], | |
| "source": [ | |
| "X[0] # pegando a primeira linha de X" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "4a505a42", | |
| "metadata": { | |
| "id": "4a505a42" | |
| }, | |
| "source": [ | |
| "Podemos selecionar uma coluna com a sintaxe `X[:, j]`, sendo `j` um inteiro." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "fd6b53ef", | |
| "metadata": { | |
| "id": "fd6b53ef", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "643e661f-a3ca-4ead-d1f5-50f07cbd79b6" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([2, 4])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 14 | |
| } | |
| ], | |
| "source": [ | |
| "X[:, 1] # pegando a segunda coluna de X" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "84299746", | |
| "metadata": { | |
| "id": "84299746" | |
| }, | |
| "source": [ | |
| "Podemos selecionar mais de uma linha ou coluna utilizando a sintaxe `X[um_vetor]` ou `X[:, um_vetor]`, respectivamente." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "ca8b6131", | |
| "metadata": { | |
| "id": "ca8b6131", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "59cb1a6a-f340-4d0f-9153-1195fbe6bed6" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 2, 3],\n", | |
| " [4, 5, 6]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 15 | |
| } | |
| ], | |
| "source": [ | |
| "X = np.array([[1, 2, 3], [4, 5, 6]])\n", | |
| "X" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "cols = [0, 1]\n", | |
| "X[:, cols] # iremos pegar a primeira e segunda colunas de X" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "_CtY99AejnG1", | |
| "outputId": "236c90e7-68f6-4c0d-c8c7-8dfe7f014d23" | |
| }, | |
| "id": "_CtY99AejnG1", | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 2],\n", | |
| " [4, 5]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 16 | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "440b143a", | |
| "metadata": { | |
| "id": "440b143a" | |
| }, | |
| "source": [ | |
| "Podemos selecionar linhas e colunas também indexando o tensor através de um vetor booleano.\n", | |
| "\n", | |
| "A sintaxe `X[vetor_booleano]` retorna as linhas (ou colunas quando `X[:, vetor_booleano]`) onde o vetor é `True`." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "e666700b", | |
| "metadata": { | |
| "id": "e666700b", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "3bff35e1-60c5-44f1-c31e-6a2e574c239c" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 2, 3]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 17 | |
| } | |
| ], | |
| "source": [ | |
| "X[[True, False]] # selecionamos apenas a primeira linha" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "fd88174a", | |
| "metadata": { | |
| "id": "fd88174a", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "4ec8a791-04c3-4331-97c5-9d87395a2bf2" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "array([[1, 3],\n", | |
| " [4, 6]])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 18 | |
| } | |
| ], | |
| "source": [ | |
| "X[:, [True, False, True]] # selecionamos apenas a primeira e última coluna" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "f953c649", | |
| "metadata": { | |
| "id": "f953c649" | |
| }, | |
| "source": [ | |
| "### 1.2) Shape, Reshape e Ravel\n", | |
| "\n", | |
| "Todo vetor, matriz e tensor pode ser redimensionado.\n", | |
| "\n", | |
| "Observe como no tensor abaixo temos `3x2x2=12` elementos. Podemos redimensionar os mesmos para outros tensores com 12 elementos." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "9bb1cce1", | |
| "metadata": { | |
| "id": "9bb1cce1", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "70fc5215-172f-42d9-c168-9653fcd90716" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(3, 2, 2)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 19 | |
| } | |
| ], | |
| "source": [ | |
| "X = np.random.rand(3, 2, 2)\n", | |
| "X.shape" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "c80f667a", | |
| "metadata": { | |
| "id": "c80f667a" | |
| }, | |
| "source": [ | |
| "Podemos redimensionar os elementos como uma matriz $2 \\times 6$:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "e8f8bc0c", | |
| "metadata": { | |
| "id": "e8f8bc0c", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "cf3ee33b-5104-4767-9b28-0fcca52be68f" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(2, 6)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 20 | |
| } | |
| ], | |
| "source": [ | |
| "X_matrix = X.reshape((2, 6))\n", | |
| "X_matrix.shape" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Em Numpy (e PyTorch), podemos fazer com que a biblioteca infira uma dimensão utilizando `-1`:" | |
| ], | |
| "metadata": { | |
| "id": "ZWT0OKyqTQHG" | |
| }, | |
| "id": "ZWT0OKyqTQHG" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "d535c1f4", | |
| "metadata": { | |
| "id": "d535c1f4", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "aa0611fc-744c-4407-def8-841a5fec9d44" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(2, 6)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 21 | |
| } | |
| ], | |
| "source": [ | |
| "X_matrix = X.reshape((2, -1))\n", | |
| "X_matrix.shape # Note que também temos uma matriz de dimensão: 2x6" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "caf67b53", | |
| "metadata": { | |
| "id": "caf67b53" | |
| }, | |
| "source": [ | |
| "Podemos redimensionar os elementos para um outro tensor:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "f7904f56", | |
| "metadata": { | |
| "id": "f7904f56", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "3742a7a8-524c-4176-b6e9-0f7cbe71f3c1" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(6, 2, 1)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 22 | |
| } | |
| ], | |
| "source": [ | |
| "X_tensor = X.reshape((6, 2, 1))\n", | |
| "X_tensor.shape" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "dbf5211c", | |
| "metadata": { | |
| "id": "dbf5211c" | |
| }, | |
| "source": [ | |
| "E, por fim, podemos redimensionar os elementos como um vetor, realizando uma operação de `flattening`:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "0a9829bc", | |
| "metadata": { | |
| "id": "0a9829bc", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "29511aa2-282e-4e4c-a931-de830028f8d4" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(12,)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 23 | |
| } | |
| ], | |
| "source": [ | |
| "X_vetor = X.flatten()\n", | |
| "X_vetor.shape" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Observação:** Podemos redimensionar os elementos como um vetor de 3 formas principais:\n", | |
| "1. Através da função `flatten`, como visto acima;\n", | |
| "2. Através da função `ravel` (presente tanto em Numpy quanto em PyTorch);\n", | |
| "3. Através de um `.reshape`, passando como parâmetro o número dos elementos (ou -1).\n", | |
| "\n", | |
| "Os 3 métodos possuem algumas sutilezas que diferenciam um dos outros. Para mais informações consulte o seguinte [link](https://www.techentice.com/numpy-difference-between-reshape-flatten/#:~:text=Use%20flatten()%20when%20you,create%20only%201-D%20array.)." | |
| ], | |
| "metadata": { | |
| "id": "4iWl-PbBUN8D" | |
| }, | |
| "id": "4iWl-PbBUN8D" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "01da6073", | |
| "metadata": { | |
| "id": "01da6073" | |
| }, | |
| "source": [ | |
| "## 2. Tensores em PyTorch\n", | |
| "\n", | |
| "PyTorch é o arcabouço que vamos usar para as nossas tarefas. Assim como o NumPy, o Pytorch é uma biblioteca de processamento vetorial/matricial/tensorial. Operações sobre os tensores do Pytorch possuem sintaxe consideravelmente parecida com operações sobre tensores do NumPy.\n", | |
| "\n", | |
| "O mesmo faz uso de tensores bem similares ao NumPy. Porém, com PyTorch conseguimos fazer uso da GPU." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "bb86fa6c", | |
| "metadata": { | |
| "id": "bb86fa6c" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "import torch" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "eef55b0f", | |
| "metadata": { | |
| "id": "eef55b0f" | |
| }, | |
| "source": [ | |
| "### 2.1) Casting para o dispositivo correto\n", | |
| "\n", | |
| "Como usaremos processamento vetorial principalmente em GPUs para aprendizado profundo, primeiramente é possível verificar se há uma GPU disponível com o trecho de código abaixo, armazenando os tensores nos dispositivos apropriados." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "77e07616", | |
| "metadata": { | |
| "id": "77e07616", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "bf60343d-dfcf-4802-c775-988386ce5ddd" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "cpu\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "if torch.cuda.is_available():\n", | |
| " device = torch.device('cuda')\n", | |
| "else:\n", | |
| " device = torch.device('cpu')\n", | |
| "\n", | |
| "print(device)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Podemos também realizar essa verificação em uma linha de código (+ pytônico)" | |
| ], | |
| "metadata": { | |
| "id": "e-U9ClDLWA9O" | |
| }, | |
| "id": "e-U9ClDLWA9O" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", | |
| "print(device)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "upFrmCUhV3eu", | |
| "outputId": "1a0632d8-824b-4885-ccd2-caac0ab7ad93" | |
| }, | |
| "id": "upFrmCUhV3eu", | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "cpu\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "0fa1fb4b", | |
| "metadata": { | |
| "id": "0fa1fb4b" | |
| }, | |
| "source": [ | |
| "### 2.2.) Tensores no Pytorch" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "fe7618b5", | |
| "metadata": { | |
| "id": "fe7618b5" | |
| }, | |
| "source": [ | |
| "Para criar tensores novos, podemos utilizar a função `torch.tensor`, similar à função `numpy.array` da biblioteca Numpy:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "2835208e", | |
| "metadata": { | |
| "id": "2835208e", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "f0a1820a-eaa6-4f78-c4d0-20f28705ac37" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([1, 2, 3, 4, 5, 6])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "tns = torch.tensor([1, 2, 3, 4, 5, 6])\n", | |
| "print(tns)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "09edf473", | |
| "metadata": { | |
| "id": "09edf473" | |
| }, | |
| "source": [ | |
| "Podemos redimensionar os tensores de maneira similar ao que vimos em Numpy através da função `view`:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "657b44b4", | |
| "metadata": { | |
| "id": "657b44b4", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "136bff38-b745-43bd-8063-96d92238a215" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[1, 2, 3],\n", | |
| " [4, 5, 6]])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "print(tns.view(2, 3))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Assim como mencionado em Numpy, podemos utilizar `-1` para fazer com que a biblioteca faça uma inferência no formato necessário de acordo com os elementos restantes:" | |
| ], | |
| "metadata": { | |
| "id": "mxMi2Up9WwbL" | |
| }, | |
| "id": "mxMi2Up9WwbL" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Note que obtemos o mesmo tensor anterior\n", | |
| "print(tns.view(2, -1))" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "SubQAAGRna8P", | |
| "outputId": "d30fde26-99dd-4d2f-ffd2-f6e848f64860" | |
| }, | |
| "id": "SubQAAGRna8P", | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[1, 2, 3],\n", | |
| " [4, 5, 6]])\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Observação:** Em PyTorch, além de possuirmos a função `view`, também possuímos a função `reshape`. Ambas possuem algumas diferenças sutis. Caso queira obter mais informações sobre tais diferenças, acesse o seguinte [link](https://stackoverflow.com/questions/49643225/whats-the-difference-between-reshape-and-view-in-pytorch). Porém, no geral, iremos trabalhar mais com a função `view` ao desenvolver códigos utilizando PyTorch." | |
| ], | |
| "metadata": { | |
| "id": "VpQDTi7NW9vN" | |
| }, | |
| "id": "VpQDTi7NW9vN" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "aa018512", | |
| "metadata": { | |
| "id": "aa018512" | |
| }, | |
| "source": [ | |
| "Podemos criar tensores previamente preenchidos por elementos, como 0s através da função `torch.zeros` e 1s através da função `torch.ones`:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "25be82ea", | |
| "metadata": { | |
| "id": "25be82ea", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "f43b4add-68ea-44b8-b4fb-1a8d1671d760" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[0., 0., 0.],\n", | |
| " [0., 0., 0.]])\n", | |
| "tensor([[1., 1., 1.],\n", | |
| " [1., 1., 1.]])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "tns_0 = torch.zeros(2, 3) # iniciando um tensor com 0s\n", | |
| "tns_1 = torch.ones(2, 3) # iniciando um tensor com 1s\n", | |
| "\n", | |
| "print(tns_0)\n", | |
| "print(tns_1)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "cda70838", | |
| "metadata": { | |
| "id": "cda70838" | |
| }, | |
| "source": [ | |
| "Similar à funções do Numpy, podemos iniciar tensores com valores aleatórios:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "a907a653", | |
| "metadata": { | |
| "id": "a907a653", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "17847f5a-e855-4908-e894-aff974884597" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[0.5401, 0.9377, 0.0047],\n", | |
| " [0.6100, 0.8337, 0.6879]])\n", | |
| "tensor([[-1.4032e+00, -5.6653e-01, -1.2894e-03],\n", | |
| " [ 2.1798e-02, 1.0908e-01, 5.7875e-01]])\n", | |
| "tensor([5, 1, 2, 3, 4, 0])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "tns_u = torch.rand(2, 3) # valores que seguem uma distribuição uniforme\n", | |
| "print(tns_u)\n", | |
| "\n", | |
| "tns_n = torch.randn(2, 3) # valores que seguem uma distribuição normal\n", | |
| "print(tns_n)\n", | |
| "\n", | |
| "tns_perm = torch.randperm(6) # valores que são uma permutação aleatória no intervalo [0, 5]\n", | |
| "print(tns_perm)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "960deb73", | |
| "metadata": { | |
| "id": "960deb73" | |
| }, | |
| "source": [ | |
| "Similar à Numpy, podemos realizar operações de soma, multiplicação, entre outras, com tensores:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "20c406a0", | |
| "metadata": { | |
| "id": "20c406a0", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "3464126c-b739-4d2f-ab11-0be387ff3304" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[0.5401, 0.9377, 0.0047],\n", | |
| " [0.6100, 0.8337, 0.6879]])\n", | |
| "tensor([[-1.4032e+00, -5.6653e-01, -1.2894e-03],\n", | |
| " [ 2.1798e-02, 1.0908e-01, 5.7875e-01]])\n", | |
| "tensor([[-0.8631, 0.3712, 0.0034],\n", | |
| " [ 0.6318, 0.9428, 1.2667]])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "print(tns_u)\n", | |
| "print(tns_n)\n", | |
| "\n", | |
| "tns_sum = tns_u + tns_n\n", | |
| "print(tns_sum)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "3fda5520", | |
| "metadata": { | |
| "id": "3fda5520" | |
| }, | |
| "source": [ | |
| "Similar à Numpy, podemos indexar os tensores da mesma forma apresentada anteriormente:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "1875ca00", | |
| "metadata": { | |
| "id": "1875ca00", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "251660d2-379f-4259-de7c-7f69993b21b2" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor(0.9428)\n", | |
| "tensor([-0.8631, 0.3712, 0.0034])\n", | |
| "tensor([0.3712, 0.9428])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "print(tns_sum[1, 1]) # Indexando um elemento\n", | |
| "print(tns_sum[0, :]) # Indexando uma linha (pode também ser tns_sum[0])\n", | |
| "print(tns_sum[:, 1]) # Indexando uma coluna" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "b6911ae8", | |
| "metadata": { | |
| "id": "b6911ae8" | |
| }, | |
| "source": [ | |
| "Podemos utilizar a função `torch.from_numpy` para converter um tensor de Numpy para PyTorch; ou a função `.numpy()` para converter um tensor de PyTorch para Numpy:" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "6ca112e7", | |
| "metadata": { | |
| "id": "6ca112e7", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "b7cda352-4076-42d3-f5fc-fa7ac447f908" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "[[ 0.09579046 0.50616887 -0.34923084]\n", | |
| " [ 0.69487476 -1.18931518 0.04173277]] float64\n", | |
| "tensor([[ 0.0958, 0.5062, -0.3492],\n", | |
| " [ 0.6949, -1.1893, 0.0417]], dtype=torch.float64)\n", | |
| "[[ 0.09579046 0.50616887 -0.34923084]\n", | |
| " [ 0.69487476 -1.18931518 0.04173277]]\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "np_arr = np.random.randn(2, 3)\n", | |
| "print(np_arr, np_arr.dtype)\n", | |
| "\n", | |
| "torch_tns = torch.from_numpy(np_arr)\n", | |
| "print(torch_tns)\n", | |
| "\n", | |
| "arr = torch_tns.numpy()\n", | |
| "print(arr)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "bddafbb4", | |
| "metadata": { | |
| "id": "bddafbb4" | |
| }, | |
| "source": [ | |
| "Por fim, podemos concatenar dois, ou mais, tensores com a função `torch.cat`:\n", | |
| "- O parâmetro `dim` em PyTorch é análogo ao parâmetro `axis` do Numpy. Nele, iremos informar sobre qual dimensão queremos que uma certa operação seja feita. No caso abaixo, ao informarmos a primeira dimensão (0), estamos dizendo para o PyTorch efetuar uma concatenação dos dois tensores a partir da sua primeira dimensão, ou seja, \"colando\" os tensores verticalmente (no sentido das linhas)." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "01f1dc76", | |
| "metadata": { | |
| "id": "01f1dc76", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "8aca18cb-6468-4352-b313-c2e7db4b0e92" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([[0., 0., 0.],\n", | |
| " [0., 0., 0.],\n", | |
| " [1., 1., 1.],\n", | |
| " [1., 1., 1.]])\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "tns_cat = torch.cat((tns_0, tns_1), dim=0)\n", | |
| "print(tns_cat)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "af99b899", | |
| "metadata": { | |
| "id": "af99b899" | |
| }, | |
| "source": [ | |
| "Várias outras operações sobre tensores do Pytorch podem ser vistas nos seguintes tutoriais:\n", | |
| "1. https://jhui.github.io/2018/02/09/PyTorch-Basic-operations/\n", | |
| "2. https://pytorch.org/tutorials/beginner/pytorch_with_examples.html" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "82afff56", | |
| "metadata": { | |
| "id": "82afff56" | |
| }, | |
| "source": [ | |
| "## Conjunto de Problemas 1: Vetorização" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "23f98d71", | |
| "metadata": { | |
| "id": "23f98d71" | |
| }, | |
| "source": [ | |
| "Antes de continuar, vamos importar algumas funções que serão utilizadas para testar o resultado dos seus algoritmos.\n", | |
| "\n", | |
| "Estas, vão vir do módulo `testing` do numpy." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "cae67b57", | |
| "metadata": { | |
| "id": "cae67b57" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "from numpy.testing import assert_equal\n", | |
| "from numpy.testing import assert_almost_equal\n", | |
| "from numpy.testing import assert_array_almost_equal" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "74e733c8", | |
| "metadata": { | |
| "id": "74e733c8" | |
| }, | |
| "source": [ | |
| "Seu objetivo é medir a velocidade das operações de álgebra linear para diferentes níveis de vetorização.\n", | |
| "\n", | |
| "1. Construa duas matrizes $ A $ e $ B $ com entradas aleatórias Gaussianas de tamanho $ 128 \\times 256 $.\n", | |
| "**Dica:** Use o módulo time para mensurar o tempo da operação." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "aa6d230e", | |
| "metadata": { | |
| "id": "aa6d230e" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# Implemente a sua solução aqui\n", | |
| "A = torch.randn(128, 256)\n", | |
| "B = torch.randn(128, 256)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "b704bc02", | |
| "metadata": { | |
| "id": "b704bc02" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# testes, não apague as linhas!!\n", | |
| "assert_equal((128, 256), A.shape)\n", | |
| "assert_equal((128, 256), B.shape)\n", | |
| "\n", | |
| "# A chamada .numpy() converte os vetores em vetores numpy. Útil para testes!\n", | |
| "Anp = A.numpy()\n", | |
| "Bnp = A.numpy()\n", | |
| "\n", | |
| "# testando média e desvio padrão\n", | |
| "assert_almost_equal(Anp.mean(), 0, decimal=2)\n", | |
| "assert_almost_equal(Anp.std(ddof=1), 1, decimal=2)\n", | |
| "\n", | |
| "assert_almost_equal(Bnp.mean(), 0, decimal=2)\n", | |
| "assert_almost_equal(Bnp.std(ddof=1), 1, decimal=2)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "000d92ec", | |
| "metadata": { | |
| "id": "000d92ec" | |
| }, | |
| "source": [ | |
| "2. Calcule $C = AB^T$, tratando $ A $ como uma matriz, mas computando o resultado para cada coluna de $ B $. Em outras palavras, realize um produto matricial utilizando um laço `for`! Pare realizar este código, é importante entender o conceito de broadcasting.\n", | |
| "\n", | |
| "Em código numpy e torch, a operação de broadcasting replica linhas e colunas de tensores para realizar operações. Para entender melhor, leia o [documento](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html). A figura abaixo exemplifica broadcasting. No geral, as dimensões de arrays casam, as operações são realizadas (primeira linha da figura). Mesmo quando as dimensões não casem, se a última dimensão for compatível é feito a replicação (broadcasting), ver a segunda linha da figura. Por fim, mesmo quando as dimensões não casam mas uma delas é 1 (4x1 + 1x3 na linha 3), é feito broadcasting.\n", | |
| "\n", | |
| "#\n", | |
| "\n", | |
| "\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Dica:** Você deverá fazer o código em uma linha apenas. Para isso, você vai focar no caso da linha 2 da figura. Multiplique uma linha de A por B. Depois disso, use `.sum(axis=...)` para realizar a soma na dimensão correta." | |
| ], | |
| "metadata": { | |
| "id": "Af0Fc_LDcAnl" | |
| }, | |
| "id": "Af0Fc_LDcAnl" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "fc16b00a", | |
| "metadata": { | |
| "id": "fc16b00a", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "93de05c5-221e-4bd5-f73b-59963726edf8" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "CPU times: user 7.41 ms, sys: 895 µs, total: 8.31 ms\n", | |
| "Wall time: 9.92 ms\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "%%time\n", | |
| "\n", | |
| "C = np.zeros((128, 128))\n", | |
| "for linha in range(A.shape[0]):\n", | |
| " # implemente aqui a sua solução\n", | |
| " C[linha] = (A[linha] * B).sum(dim=1)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "ab8a4750", | |
| "metadata": { | |
| "id": "ab8a4750" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# testes, não apague as linhas!!\n", | |
| "Cteste = np.matmul(A, B.T) # faz a leitura, realiza operação\n", | |
| "assert_array_almost_equal(Cteste, C, decimal=3)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "66b51a8f", | |
| "metadata": { | |
| "id": "66b51a8f" | |
| }, | |
| "source": [ | |
| "3. Calcule $ C = AB^t $ usando operações matriciais. Ou seja, sem usar nenhum laço. Ao mensurar o tempo, ficou mais rápido?" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "4b5e6529", | |
| "metadata": { | |
| "id": "4b5e6529", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "f7daddfd-bc88-497a-f04c-8d5f170804f8" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "CPU times: user 773 µs, sys: 0 ns, total: 773 µs\n", | |
| "Wall time: 785 µs\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "%%time\n", | |
| "\n", | |
| "# Implemente aqui a sua solução\n", | |
| "C = torch.mm(A, B.T)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "95dc9237", | |
| "metadata": { | |
| "id": "95dc9237" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# testes, não apague as linhas!!\n", | |
| "Cteste = np.matmul(A, B.T) # faz a leitura, realiza operação\n", | |
| "assert_array_almost_equal(Cteste, C, decimal=3)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "- Observando o tempo gasto nas duas formas de calcular $C = AB^T$, podemos perceber que ao utilizarmos funções nativas da biblioteca (como `np.matmul` e `torch.mm`), temos uma redução significativa do tempo se compararmos à utilização de um laço `for`." | |
| ], | |
| "metadata": { | |
| "id": "6qrAitJhAVcq" | |
| }, | |
| "id": "6qrAitJhAVcq" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "f347bdc4", | |
| "metadata": { | |
| "id": "f347bdc4" | |
| }, | |
| "source": [ | |
| "## Conjunto de Problemas 2: Computação eficiente de memória\n", | |
| "\n", | |
| "Crie duas matrizes aleatórias de tamanho $4096 \\times 4096$. Chame as mesmas de $A$ e $B$ novamente.\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "7756d932", | |
| "metadata": { | |
| "id": "7756d932" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# Implemente aqui a sua solução\n", | |
| "A = torch.randn(4096, 4096)\n", | |
| "B = torch.randn(4096, 4096)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "5. Crie uma função que recebe as matrizes $A$, $B$ e $C$, e um número de iterações para atualizar $C$, de forma que $C = AB^T + C$. Essa função deve, primeiro, calcular a multiplicação de matrizes entre $A$ e $B$ e depois adicionar o valor à $C$, de acordo com o número de iterações. A mesma deve atualizar $C$ sem alocar memória nova para essa variável." | |
| ], | |
| "metadata": { | |
| "id": "ysUoOzLOBJ0e" | |
| }, | |
| "id": "ysUoOzLOBJ0e" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "34a9bc94", | |
| "metadata": { | |
| "id": "34a9bc94" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def update_c(C, A, B, n_iter=2):\n", | |
| " matrix_product = torch.mm(A, B.T)\n", | |
| " for _ in range(n_iter):\n", | |
| " C += matrix_product\n", | |
| "\n", | |
| " return C" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "37eb815e", | |
| "metadata": { | |
| "id": "37eb815e" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# testes não apague!\n", | |
| "Ct = torch.zeros(A.shape)\n", | |
| "Cteste = (Ct + np.matmul(A, B.T))\n", | |
| "Cteste = (Cteste + np.matmul(A, B.T))\n", | |
| "\n", | |
| "C = torch.zeros(A.shape)\n", | |
| "update_c(C, A, B, 2)\n", | |
| "assert_array_almost_equal(Cteste, C.numpy(), decimal=3)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "e87c26d4", | |
| "metadata": { | |
| "id": "e87c26d4" | |
| }, | |
| "source": [ | |
| "## Conjunto de Problemas 3: Programação Diferenciável\n", | |
| "\n", | |
| "Agora vamos aprender um dos pontos chaves de fazer uso de bibliotecas como pytorch/tensorflow/etc, a programação diferenciável. Diferente do exercício que vocês fizeram na mão, usando a biblioteca conseguimos derivar de forma automágica. Portanto, observe como o código abaixo deriva a função seno." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "5e88cfa6", | |
| "metadata": { | |
| "id": "5e88cfa6", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 350 | |
| }, | |
| "outputId": "5c2e6feb-1d71-4c99-dcfb-f32863996d98" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "[<matplotlib.lines.Line2D at 0x7fa9a8697490>]" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 46 | |
| }, | |
| { | |
| "output_type": "display_data", | |
| "data": { | |
| "text/plain": [ | |
| "<Figure size 576x360 with 1 Axes>" | |
| ], | |
| "image/png": "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\n" | |
| }, | |
| "metadata": { | |
| "needs_background": "light" | |
| } | |
| } | |
| ], | |
| "source": [ | |
| "x = np.linspace(-10, 10, 1000)\n", | |
| "x_torch = torch.tensor(x, requires_grad=True)\n", | |
| "\n", | |
| "y = torch.sin(x_torch) # seno original\n", | |
| "\n", | |
| "# Devido ao fato de que o y final não é um escalar precisamos passar o vetor v tal que v é a jacobiana\n", | |
| "# pela qual vamos multiplicar as jacobianas da variavel node (neste caso, x) conforme descrito em\n", | |
| "# https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html\n", | |
| "# veremos mais sobre o processo de backprogation, jacobiano, etc no futuro\n", | |
| "v = torch.ones(x_torch.shape, dtype=torch.double)\n", | |
| "\n", | |
| "# Como aqui x da origem a y diretamente, nosso v sera apenas um vetor de 1's com a mesma dimensao de x\n", | |
| "y.backward(v)\n", | |
| "\n", | |
| "plt.plot(x_torch.detach().numpy(), y.detach().numpy(), 'g', label='sin(x)')\n", | |
| "plt.plot(x_torch.detach().numpy(), x_torch.grad.numpy(), 'b', label='sin\\'(x)')" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "6f1894fc", | |
| "metadata": { | |
| "id": "6f1894fc" | |
| }, | |
| "source": [ | |
| "O resultado é a mesma curva da função cosseno! Para entender melhor o autograd, leia a seção respectiva do [pyTorch Blitz](https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html)." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "03ce34b9", | |
| "metadata": { | |
| "id": "03ce34b9", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 350 | |
| }, | |
| "outputId": "102c903d-38e2-4aac-8255-4d88c7184750" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "[<matplotlib.lines.Line2D at 0x7fa9a851f640>]" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 47 | |
| }, | |
| { | |
| "output_type": "display_data", | |
| "data": { | |
| "text/plain": [ | |
| "<Figure size 576x360 with 1 Axes>" | |
| ], | |
| "image/png": "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\n" | |
| }, | |
| "metadata": { | |
| "needs_background": "light" | |
| } | |
| } | |
| ], | |
| "source": [ | |
| "plt.plot(x_torch.detach().numpy(), x_torch.grad.numpy(), 'g', label='sin\\'(x)')\n", | |
| "plt.plot(x_torch.detach().numpy(), torch.cos(x_torch).detach().numpy(), 'b', label='cos(x)')" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "u92eQeXdeNOT" | |
| }, | |
| "source": [ | |
| "6. Derive a função logística usando pytorch.\n", | |
| "\n", | |
| "$$f(x) = \\frac{1}{1 + e^{-x}}$$" | |
| ], | |
| "id": "u92eQeXdeNOT" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "b4d299bd" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "x = np.linspace(-10, 10, 1000) # Não mude o valor de x!\n", | |
| "x_torch = torch.from_numpy(x)\n", | |
| "\n", | |
| "# implemente\n", | |
| "x_torch.requires_grad_(True)\n", | |
| "f = 1 / (1 + torch.exp(-x_torch))\n", | |
| "v = torch.ones(x_torch.shape, dtype=torch.double)\n", | |
| "f.backward(v)" | |
| ], | |
| "id": "b4d299bd" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "Vd2DxHxbeNOU" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# testes, não apagar\n", | |
| "y_test = 1.0/(1 + np.exp(-x))\n", | |
| "derivada_teste = y_test * (1 - y_test)\n", | |
| "assert_array_almost_equal(derivada_teste, x_torch.grad.numpy(), decimal=3)" | |
| ], | |
| "id": "Vd2DxHxbeNOU" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "nOkegnX_eNOU" | |
| }, | |
| "source": [ | |
| "A operação *detach* permite quebrar a computação em várias partes. Em particular, isto é útil para aplicar a regra da cadeia. Suponha que $u = f(x)$ e $z = g(u)$, pela regra da cadeia, temos $\\frac{dz}{dx}$ = $\\frac{dz}{du}\\frac{du}{dx}$. Para calcular $\\frac{dz}{du}$, podemos primeiro separar $u$ da computação e, em seguida, chamar `z.backward()` para calcular o primeiro termo.\n", | |
| "\n", | |
| "Observe no caso abaixo como derivamos $u = x^2$. A resposta deve ser $2x$ para cada termo `[0, 1, 2, 3]`." | |
| ], | |
| "id": "nOkegnX_eNOU" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "pOBoYtmseNOU", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "7bc182b5-19f4-4d29-f93b-447556dd0fde" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([0., 2., 4., 6.])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 50 | |
| } | |
| ], | |
| "source": [ | |
| "x = torch.arange(4, dtype =torch.float)\n", | |
| "x.requires_grad_(True)\n", | |
| "\n", | |
| "u = x * x\n", | |
| "jacobX = torch.ones(x.shape)\n", | |
| "u.backward(jacobX)\n", | |
| "x.grad" | |
| ], | |
| "id": "pOBoYtmseNOU" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "fqrzNdjpeNOV" | |
| }, | |
| "source": [ | |
| "Agora vamos fazer $z = u^3$ e computar as derivadas intermediarias." | |
| ], | |
| "id": "fqrzNdjpeNOV" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "e1QHvjsieNOV", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "bfc2c387-fac7-421f-d214-2f90d4b1dd46" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "tensor([ 0., 1., 64., 729.], grad_fn=<MulBackward0>)\n" | |
| ] | |
| }, | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([0., 2., 4., 6.])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 51 | |
| } | |
| ], | |
| "source": [ | |
| "x = torch.arange(4, dtype = torch.float)\n", | |
| "x.requires_grad_(True)\n", | |
| "\n", | |
| "u = x * x\n", | |
| "v = u.detach() # u still keeps the computation graph\n", | |
| "v.requires_grad_(True)\n", | |
| "z = v * v * v\n", | |
| "\n", | |
| "print(z)\n", | |
| "\n", | |
| "jacobX = torch.ones(x.shape)\n", | |
| "u.backward(jacobX)\n", | |
| "x.grad" | |
| ], | |
| "id": "e1QHvjsieNOV" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "hFkdndNZeNOV" | |
| }, | |
| "source": [ | |
| "Acima temos a derivada de $x^2$. Abaixo temos a derivada de $g(x^2)$." | |
| ], | |
| "id": "hFkdndNZeNOV" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "r8lr0dy7eNOW", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "af729048-a24c-4697-bbda-d584b485a5e3" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([ 0., 3., 48., 243.])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 52 | |
| } | |
| ], | |
| "source": [ | |
| "jacobV = torch.ones(v.shape)\n", | |
| "z.backward(jacobV)\n", | |
| "v.grad" | |
| ], | |
| "id": "r8lr0dy7eNOW" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "HN0-MnYSeNOW" | |
| }, | |
| "source": [ | |
| "7. Agora, sendo $f(x) = 1 + x^2$ e $g(x) = 1 + 7 f(x)^4$. Vamos aplicar a regra da cadeia em pytorch" | |
| ], | |
| "id": "HN0-MnYSeNOW" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "ysFCKFD_eNOX" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "x = torch.arange(4, dtype = torch.float)\n", | |
| "\n", | |
| "# implemente a derivada de f(x) em função de x\n", | |
| "x.requires_grad_(True)\n", | |
| "jacobX = torch.ones(x.shape)\n", | |
| "\n", | |
| "f = 1 + x * x\n", | |
| "f.backward(jacobX)" | |
| ], | |
| "id": "ysFCKFD_eNOX" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "PxBQ8YZ6eNOX" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# teste para x\n", | |
| "# Aqui pode ser meio confuso, mas o gradiente referente à derivada de\n", | |
| "# f(x) com relação a x, por exemplo, fica armazenada no tensor x, mais especificamente em x.grad\n", | |
| "assert_array_almost_equal([0, 2, 4, 6], x.grad.numpy())" | |
| ], | |
| "id": "PxBQ8YZ6eNOX" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "spFaaW1yeNOX", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "ed089458-3ef7-4fca-b06a-a1098c397e9e" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([ 28., 224., 3500., 28000.])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 55 | |
| } | |
| ], | |
| "source": [ | |
| "# implemente a derivada de g(x) em função de f(x)\n", | |
| "u = f.detach()\n", | |
| "u.requires_grad_(True)\n", | |
| "jacobU = torch.ones(u.shape)\n", | |
| "\n", | |
| "g = 1 + 7 * torch.pow(u, 4)\n", | |
| "g.backward(jacobU)\n", | |
| "\n", | |
| "u.grad" | |
| ], | |
| "id": "spFaaW1yeNOX" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Testando a sua solução (caso seja necessário, modifique o nome da sua variável auxiliar para 'u')\n", | |
| "# Note que agora o gradiente referente à derivada está presente na variável 'u', e não mais em 'x'\n", | |
| "assert_array_almost_equal([28, 224, 3500, 28000], u.grad.numpy())" | |
| ], | |
| "metadata": { | |
| "id": "-u7iI63SFKzU" | |
| }, | |
| "id": "-u7iI63SFKzU", | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "My7R1U38eNOX" | |
| }, | |
| "source": [ | |
| "### Conjunto de Problemas 4: Mais Derivadas" | |
| ], | |
| "id": "My7R1U38eNOX" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "oynEDx7zeNOY" | |
| }, | |
| "source": [ | |
| "Vamos brincar um pouco de derivadas dentro de funções. Dado dois números $x$ e $y$, implemente a função `log_exp`, que retorna:\n", | |
| "\n", | |
| "$$ f(x,y) = -\\log\\left(\\frac{e^x}{e^x+e^y}\\right)$$" | |
| ], | |
| "id": "oynEDx7zeNOY" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "DfXmkIc7eNOY" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def log_exp(x, y):\n", | |
| " # implemente aqui a sua solução\n", | |
| " return -1 * torch.log(torch.exp(x) / (torch.exp(x) + torch.exp(y)))" | |
| ], | |
| "id": "DfXmkIc7eNOY" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "5oDhz-apeNOY" | |
| }, | |
| "source": [ | |
| "1. Abaixo vamos testar o seu código com algumas entradas simples." | |
| ], | |
| "id": "5oDhz-apeNOY" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "1RNk8XUHeNOY", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "2c8c2c9c-d976-4196-b234-55125932a645" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([1.3133])" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 58 | |
| } | |
| ], | |
| "source": [ | |
| "x, y = torch.tensor([2.0]), torch.tensor([3.0])\n", | |
| "z = log_exp(x, y)\n", | |
| "z" | |
| ], | |
| "id": "1RNk8XUHeNOY" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "txUIVOQeeNOY" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# Teste. Não apague\n", | |
| "assert_almost_equal(1.31326175, z.numpy())" | |
| ], | |
| "id": "txUIVOQeeNOY" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "8s88vTx0eNOZ" | |
| }, | |
| "source": [ | |
| "2. A função a seguir computa $\\partial z/\\partial x$ e $\\partial z/\\partial y$ usando `autograd`." | |
| ], | |
| "id": "8s88vTx0eNOZ" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "yu3HcNAKeNOZ" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# O argumento funcao_forward é uma função python. Será a sua log_exp.\n", | |
| "# A ideia aqui é deixar claro a ideia de forward e backward propagation, depois\n", | |
| "# de avaliar a função chamamos backward e temos as derivadas.\n", | |
| "def grad(funcao_forward, x, y):\n", | |
| " x.requires_grad_(True)\n", | |
| " y.requires_grad_(True)\n", | |
| " z = funcao_forward(x, y)\n", | |
| " z.backward()\n", | |
| " return x.grad, y.grad" | |
| ], | |
| "id": "yu3HcNAKeNOZ" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "zVqaKoq6eNOZ" | |
| }, | |
| "source": [ | |
| "Testando" | |
| ], | |
| "id": "zVqaKoq6eNOZ" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "izUZIoBueNOZ" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "x, y = torch.tensor([2.0], dtype = torch.double) ,torch.tensor([3.0], dtype = torch.double)\n", | |
| "dx, dy = grad(log_exp, x, y)" | |
| ], | |
| "id": "izUZIoBueNOZ" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "2kiIdjZMeNOa" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "assert_almost_equal(-0.7310586, dx.numpy())\n", | |
| "assert_almost_equal(0.7310586, dy.numpy())" | |
| ], | |
| "id": "2kiIdjZMeNOa" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "9b-NWlkpeNOa" | |
| }, | |
| "source": [ | |
| "4. Agora teste com números maiores, algum problema?" | |
| ], | |
| "id": "9b-NWlkpeNOa" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "lBKMNLureNOa", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "7cc1f24d-a8b0-4583-be20-36d61def4beb" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "(tensor([nan], dtype=torch.float64), tensor([nan], dtype=torch.float64))" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 63 | |
| } | |
| ], | |
| "source": [ | |
| "x, y = torch.tensor([400.0]).double() ,torch.tensor([800.0]).double()\n", | |
| "grad(log_exp, x, y)" | |
| ], | |
| "id": "lBKMNLureNOa" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "qOG2yCPceNOa" | |
| }, | |
| "source": [ | |
| "5. Pense um pouco sobre o motivo do erro acima. Usando as propriedade de logaritmos, é possível fazer uma função mais estável. Abaixo segue a implementação da mesma. O problema aqui é que o exponencial \"explode\" quando $x$ ou $y$ são muito grandes. Este [link](http://www.wolframalpha.com/input/?i=log[e%5Ex+%2F+[e%5Ex+%2B+e%5Ey]]) pode ajudar." | |
| ], | |
| "id": "qOG2yCPceNOa" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "gnas2cVQeNOa" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "x, y = torch.tensor([400.0], dtype = torch.double), torch.tensor([800.0], dtype = torch.double)\n", | |
| "def stable_log_exp(x, y):\n", | |
| " return torch.log(1 + torch.exp(y-x))\n", | |
| "\n", | |
| "dx, dy = grad(stable_log_exp, x, y)" | |
| ], | |
| "id": "gnas2cVQeNOa" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "omq3r5Z5eNOa", | |
| "outputId": "b1a14c03-3d01-43e3-9a6e-9f5c2c4ab9f6" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "data": { | |
| "text/plain": [ | |
| "tensor([400.], dtype=torch.float64, grad_fn=<LogBackward0>)" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "execution_count": 65 | |
| } | |
| ], | |
| "source": [ | |
| "stable_log_exp(x, y)" | |
| ], | |
| "id": "omq3r5Z5eNOa" | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "pdzogMVCeNOa" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# Teste. Não apague\n", | |
| "assert_equal(-1, dx.numpy())\n", | |
| "assert_equal(1, dy.numpy())" | |
| ], | |
| "id": "pdzogMVCeNOa" | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "P2G3bY0deNOa" | |
| }, | |
| "source": [ | |
| "O exemplo acima mostra um pouco de problemas de estabilidade númerica. Às vezes é melhor usar versões alternativas de funções. Isto vai ocorrer quando você ver vários `nans` na sua frente :-) Claro, estamos assumindo que existe uma outra função equivalente que é mais estável para o computador." | |
| ], | |
| "id": "P2G3bY0deNOa" | |
| } | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "provenance": [], | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "display_name": "Python 3 (ipykernel)", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.10.2" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 5 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment