{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Capítulo 3: Machine Learning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![cover](cover.jpeg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## O que é Inteligência Artificial? \n", "\n", "A Inteligência Artificial (IA) é uma área multidisciplinar da ciência da computação voltada para o desenvolvimento de sistemas capazes de realizar tarefas que, até então, exigiam inteligência humana. Essas tarefas incluem aprendizado, raciocínio, resolução de problemas, percepção sensorial (como visão e audição) e compreensão da linguagem natural. \n", "\n", "### Contexto Histórico \n", "\n", "O desenvolvimento moderno da IA teve início no século XX, impulsionado por avanços nas áreas de matemática, ciência da computação, neurociência e, posteriormente, estatística e engenharia. \n", "\n", "**1950:** Alan Turing, matemático britânico conhecido por seu papel fundamental na quebra do código Enigma durante a Segunda Guerra Mundial, propôs o [**Teste de Turing**](https://doi.org/10.1093/mind/LIX.236.433) como um critério para avaliar se uma máquina pode exibir comportamento inteligente indistinguível do humano. \n", "\n", "
\n", "\n", " \"AI\"\n", "\n", "
\n", "\n", "\n", "\n", "**1956:** A [**Conferência de Dartmouth**](https://doi.org/10.1609/aimag.v27i4.1904), organizada por John McCarthy, Marvin Minsky, Claude Shannon e Nathaniel Rochester, é considerada o marco inicial da pesquisa formal em IA. Nela, cunhou-se o termo \"Inteligência Artificial\". \n", "\n", "**Décadas de 1960 e 1970:** Houve avanços significativos em áreas como resolução de problemas e jogos. Programas notáveis incluem o [**ELIZA**](https://doi.org/10.21236/AD0611533), que simulava um psicoterapeuta por meio de regras simples de linguagem, e o [**SHRDLU**](https://doi.org/10.21236/AD0700856), que interpretava comandos em linguagem natural para manipular objetos em um mundo virtual. \n", "\n", "**Décadas de 1980 e 1990:** O interesse por redes neurais ressurgiu, impulsionado por novos algoritmos de aprendizado e pelo aumento da capacidade de processamento computacional. Nesse período, os **sistemas especialistas** ganharam destaque, como o [**MYCIN**](https://doi.org/10.1016/S0065-2458(08)60529-9), desenvolvido para auxiliar no diagnóstico de doenças infecciosas. \n", "\n", "**2012:** O artigo [**\"ImageNet Classification with Deep Convolutional Neural Networks\"**](https://papers.nips.cc/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html), publicado por Alex Krizhevsky, Ilya Sutskever e Geoffrey Hinton, introduziu a arquitetura conhecida como **AlexNet**. Essa rede neural convolucional profunda venceu a competição ImageNet Large Scale Visual Recognition Challenge (ILSVRC) de 2012, reduzindo significativamente a taxa de erro em comparação com os métodos anteriores. O sucesso da AlexNet demonstrou o potencial das redes neurais profundas treinadas com GPUs e técnicas como ReLU, dropout e data augmentation, marcando o início da era moderna do *deep learning*. \n", "\n", "**2000 até o presente:** A IA passou por uma nova revolução, impulsionada por três fatores principais: maior disponibilidade de grandes volumes de dados (*big data*), avanços nos algoritmos de [**aprendizado de máquina**](https://doi.org/10.1038/nature14539), especialmente [**aprendizado profundo (*deep learning*)**](https://doi.org/10.1145/3065386), e o acesso a maior poder computacional (como GPUs e, mais recentemente, TPUs). Essa fase resultou em avanços expressivos em reconhecimento de voz, visão computacional, tradução automática e sistemas de recomendação, além da popularização de assistentes virtuais e modelos de linguagem como o [**ChatGPT**](https://doi.org/10.48550/arXiv.2303.08774). \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Redes Generativas e Agentes Autônomos: A Nova Fronteira da IA \n", "\n", "As redes generativas representam um dos desenvolvimentos mais impactantes no campo recente. [Goodfellow et al. (2014)](https://arxiv.org/abs/1406.2661) introduziram as **Generative Adversarial Networks (GANs)**, cujos fundamentos são detalhados no [Dive into Deep Learning](https://d2l.ai/). Paralelamente, **Variational Autoencoders (VAEs)** [Kingma & Welling, 2013](https://arxiv.org/abs/1312.6114) e os mais recentes **Diffusion Models** [Ho et al., 2020](https://arxiv.org/abs/2006.11239) revolucionaram a geração de conteúdo multimídia.\n", "\n", "Um avanço significativo recente vem do trabalho da DeepSeek com seu paper sobre **Large Language Models** [DeepSeek, 2023](https://arxiv.org/abs/2305.14314), que demonstra técnicas inovadoras de treinamento em escala e alinhamento de modelos generativos. Essa abordagem permite a criação de agentes de IA mais robustos e capazes de raciocínio complexo.\n", "\n", "Os **Agentes Autônomos** emergiram como paradigma promissor, combinando modelos de linguagem com capacidades de planejamento e execução de tarefas. Trabalhos como [AutoGPT](https://arxiv.org/abs/2305.12403) e [AgentBench](https://arxiv.org/abs/2308.03688) demonstram como esses sistemas podem operar de forma autônoma, aprendendo com o ambiente e tomando decisões sequenciais.\n", "\n", "A convergência entre modelos generativos e agentes autônomos está criando sistemas capazes não apenas de gerar conteúdo, mas de planejar e executar fluxos complexos de trabalho. Essa sinergia, exemplificada em projetos como [Microsoft's AutoGen](https://arxiv.org/abs/2310.06710), aponta para um futuro onde assistentes de IA poderão gerenciar projetos completos de forma autônoma.\n", "\n", "A evolução contínua dessas tecnologias, impulsionada por avanços em arquiteturas neurais e técnicas de treinamento, está redefinindo radicalmente nosso conceito de automação inteligente e criatividade computacional.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fundamentos Matemáticos\n", "\n", "Antes de mergulharmos no universo das redes neurais artificiais, é fundamental construir uma base sólida em dois pilares da matemática: **álgebra linear** e **cálculo**. Essas áreas nos ajudam a entender como os modelos processam, transformam e aprendem a partir dos dados.\n", "\n", "Vamos explorar os principais conceitos de álgebra linear com exemplos práticos em Python, utilizando a biblioteca **NumPy** — uma ferramenta poderosa e amplamente utilizada em ciência de dados e inteligência artificial.\n", "\n", "### Álgebra Linear\n", "\n", "A álgebra linear estuda estruturas como **escalars**, **vetores**, **matrizes** e **tensores** — elementos essenciais para o funcionamento interno das redes neurais. A seguir, veremos cada um desses conceitos acompanhados de suas expressões matemáticas e exemplos computacionais.\n", "\n", "\n", "#### Escalares\n", "\n", "Um **escalar** é simplesmente um número real, denotado por:\n", "\n", "$$\n", "a \\in \\mathbb{R}\n", "$$\n", "\n", "Onde $a$ representa qualquer número real, como `3.5`. Esse valor pode, por exemplo, representar um parâmetro ajustável de uma rede neural.\n", "\n", "```python\n", "import numpy as np\n", "\n", "# Exemplo de escalar\n", "escalar = 3.5\n", "print(escalar)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "3.5\n", "```\n", "\n", "#### Vetores\n", "\n", "Um **vetor** é uma sequência ordenada de números reais. Ele é representado como:\n", "\n", "$$\n", "\\mathbf{v} = \\begin{bmatrix} v_1 \\\\ v_2 \\\\ \\vdots \\\\ v_n \\end{bmatrix}\n", "$$\n", "\n", "Onde cada $v_i \\in \\mathbb{R}$ é um componente do vetor $\\mathbf{v}$, que pode conter, por exemplo, atributos como altura, peso e idade.\n", "\n", "```python\n", "# Exemplo de vetor\n", "vetor = np.array([1.0, 2.0, 3.0])\n", "print(vetor)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "[1. 2. 3.]\n", "```\n", "\n", "\n", "#### Matrizes\n", "\n", "Uma **matriz** é uma grade de números organizada em linhas e colunas, representada por:\n", "\n", "$$\n", "\\mathbf{M} = \\begin{bmatrix}\n", "m_{11} & m_{12} & \\cdots & m_{1n} \\\\\n", "m_{21} & m_{22} & \\cdots & m_{2n} \\\\\n", "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n", "m_{m1} & m_{m2} & \\cdots & m_{mn}\n", "\\end{bmatrix}\n", "$$\n", "\n", "Onde $m_{ij}$ representa o elemento da linha $i$ e coluna $j$. Matrizes são muito úteis para representar conjuntos de dados ou os pesos entre camadas de uma rede.\n", "\n", "```python\n", "# Exemplo de matriz\n", "matriz = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", "print(matriz)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "[[1 2 3]\n", " [4 5 6]\n", " [7 8 9]]\n", "```\n", "\n", "\n", "#### Tensores\n", "\n", "Um **tensor** é uma generalização de vetores e matrizes para mais de duas dimensões. Um tensor 3D, por exemplo, pode ser representado como:\n", "\n", "$$\n", "\\mathcal{T} \\in \\mathbb{R}^{d_1 \\times d_2 \\times d_3}\n", "$$\n", "\n", "Onde $d_1$, $d_2$, $d_3$ representam as dimensões do tensor (como altura, largura e canais de cor no caso de uma imagem RGB).\n", "\n", "```python\n", "# Exemplo de tensor 3D\n", "tensor = np.random.rand(3, 3, 3)\n", "print(tensor)\n", "```\n", "\n", "**Saída (valores aleatórios):**\n", "```\n", "[[[0.86 0.68 0.33]\n", " [0.77 0.42 0.90]\n", " [0.22 0.35 0.75]]\n", "\n", " [[0.96 0.17 0.39]\n", " [0.34 0.67 0.72]\n", " [0.91 0.59 0.74]]\n", "\n", " [[0.05 0.37 0.93]\n", " [0.32 0.25 0.69]\n", " [0.02 0.27 0.30]]]\n", "```\n", "\n", "\n", "### Resumo Visual\n", "\n", "| Conceito | Representação Matemática | Exemplo em Python | Dimensão |\n", "|---------|---------------------------|-------------------|----------|\n", "| Escalar | $a \\in \\mathbb{R}$ | `3.5` | 0D |\n", "| Vetor | $\\mathbf{v} \\in \\mathbb{R}^n$ | `[1.0, 2.0, 3.0]` | 1D |\n", "| Matriz | $\\mathbf{M} \\in \\mathbb{R}^{m \\times n}$ | `[[1,2,3],[4,5,6]]` | 2D |\n", "| Tensor | $\\mathcal{T} \\in \\mathbb{R}^{d_1 \\times d_2 \\times \\cdots}$ | `np.random.rand(3,3,3)` | 3D+ |\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Operações entre Escalares, Vetores e Matrizes\n", "\n", "#### **Produto Interno (Produto Escalar)**\n", "\n", "O produto interno é amplamente usado no cálculo da ativação de neurônios em redes neurais.\n", "\n", "**Aplicação prática:**\n", "\n", "* Cálculo da saída de um neurônio na regressão linear.\n", "* Similaridade entre vetores de características (por exemplo, em embeddings de imagens ou textos).\n", "\n", "Matematicamente, para dois vetores $ \\mathbf{a} = [a_1, a_2, \\ldots, a_n] $ e $ \\mathbf{b} = [b_1, b_2, \\ldots, b_n] $, ambos com $n$ componentes reais:\n", "\n", "$$\n", "\\mathbf{a} \\cdot \\mathbf{b} = \\sum_{i=1}^{n} a_i b_i \n", "$$\n", "\n", "Ou seja, somamos o produto dos elementos correspondentes de cada vetor.\n", "\n", "```python\n", "# Produto Interno (equivalente a np.dot para vetores 1D)\n", "vetor1 = np.array([1, 2, 3])\n", "vetor2 = np.array([4, 5, 6])\n", "produto_interno = vetor1 @ vetor2 # operador @ equivale a np.dot aqui\n", "print(produto_interno)\n", "\n", "# Produto elemento a elemento (Hadamard)\n", "produto_elementwise = vetor1 * vetor2\n", "print(produto_elementwise)\n", "```\n", "\n", "**Saída do Código**\n", "\n", "```\n", "32\n", "[ 4 10 18]\n", "```\n", "\n", "\n", "#### **Multiplicação Matriz-Vetor**\n", "\n", "Usada para calcular a saída de uma camada densa (fully connected) em redes neurais.\n", "\n", "**Aplicação prática:**\n", "\n", "* Propagação de sinais em redes feedforward.\n", "* Transformações lineares em imagens (ex: escala de brilho ou contraste).\n", "\n", "Seja $\\mathbf{A} \\in \\mathbb{R}^{m \\times n}$ uma matriz com $m$ linhas e $n$ colunas, e $\\mathbf{x} \\in \\mathbb{R}^n$ um vetor com $n$ elementos. O produto resulta em um vetor $\\mathbf{y} \\in \\mathbb{R}^m$:\n", "\n", "$$\n", "\\mathbf{y} = \\mathbf{A} \\mathbf{x} \n", "$$\n", "\n", "Onde cada elemento de $\\mathbf{y}$ é obtido por um produto interno entre as linhas de $\\mathbf{A}$ e o vetor $\\mathbf{x}$.\n", "\n", "```python\n", "# Multiplicação Matriz-Vetor (np.dot e @ são equivalentes aqui)\n", "matriz = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", "vetor = np.array([1, 2, 3])\n", "resultado = matriz @ vetor # mais legível que np.dot para produto linear\n", "print(resultado)\n", "\n", "# Produto elemento a elemento não é válido aqui: erro se tentar matriz * vetor\n", "```\n", "\n", "**Saída do Código**\n", "\n", "```\n", "[14 32 50]\n", "```\n", "\n", "#### **Multiplicação Matriz-Matriz**\n", "\n", "A multiplicação de duas matrizes resulta em uma nova matriz. Essa operação é essencial em modelos de machine learning para processar lotes de dados e compor camadas de redes neurais.\n", "\n", "**Aplicação prática:**\n", "\n", "* Propagação de múltiplas amostras ao mesmo tempo (minibatches).\n", "* Implementação eficiente de camadas densas e convolucionais.\n", "\n", "Seja $\\mathbf{A} \\in \\mathbb{R}^{m \\times n}$ e $\\mathbf{B} \\in \\mathbb{R}^{n \\times p}$. O produto $\\mathbf{C} = \\mathbf{A} \\mathbf{B}$ resulta em uma nova matriz $\\mathbf{C} \\in \\mathbb{R}^{m \\times p}$, onde:\n", "\n", "* $m$ = número de linhas de $\\mathbf{A}$\n", "* $n$ = número de colunas de $\\mathbf{A}$ = número de linhas de $\\mathbf{B}$\n", "* $p$ = número de colunas de $\\mathbf{B}$\n", "\n", "$$\n", "\\mathbf{C} = \\mathbf{A} \\mathbf{B} \n", "$$\n", "\n", "> ⚠️ **Importante:** Para que a multiplicação seja válida, o número de colunas de $\\mathbf{A}$ deve ser igual ao número de linhas de $\\mathbf{B}$.\n", "\n", "```python\n", "# Multiplicação Matriz-Matriz (np.dot e @ são equivalentes para 2D)\n", "matriz1 = np.array([[1, 2], [3, 4], [5, 6]]) # 3x2\n", "matriz2 = np.array([[7, 8], [9, 10]]) # 2x2\n", "resultado = matriz1 @ matriz2 # mais claro que np.dot para 2D\n", "print(resultado)\n", "\n", "# Produto elemento a elemento (Hadamard) só pode ser feito entre matrizes com mesma forma\n", "produto_elementwise = matriz1 * matriz1 # Exemplo: 3x2 * 3x2\n", "print(produto_elementwise)\n", "```\n", "\n", "**Saída do Código**\n", "\n", "```\n", "[[ 25 28]\n", " [ 57 64]\n", " [ 89 100]]\n", "[[ 1 4]\n", " [ 9 16]\n", " [25 36]]\n", "```\n", "\n", "#### **Transposição de Matrizes**\n", "\n", "A transposição inverte as dimensões de uma matriz, trocando linhas por colunas.\n", "\n", "**Aplicação prática:** \n", "- Alinhamento de pesos e entradas. \n", "- Inversão de filtros em convoluções transpostas (usadas em redes geradoras, como GANs).\n", "\n", "Seja $\\mathbf{A} \\in \\mathbb{R}^{m \\times n}$, a transposta $\\mathbf{A}^\\top$ é:\n", "\n", "$$ \n", "\\mathbf{A}^\\top \\in \\mathbb{R}^{n \\times m} \n", "$$\n", "\n", "```python\n", "# Transposição de Matriz\n", "matriz = np.array([[1, 2, 3], [4, 5, 6]])\n", "matriz_transposta = matriz.T\n", "print(matriz_transposta)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "[[1 4]\n", " [2 5]\n", " [3 6]]\n", "```\n", "\n", "\n", "#### **Calculando Normas de Vetores: L1 e L2**\n", "\n", "As normas de vetores medem o \"tamanho\" ou a \"magnitude\" de um vetor no espaço. Duas das normas mais utilizadas são a **norma L1** e a **norma L2 (euclidiana)**.\n", "\n", "A **Norma L1** é a soma dos valores absolutos dos componentes de um vetor:\n", "\n", "$$\n", "\\| \\mathbf{v} \\|_1 = \\sum_{i=1}^{n} |v_i|\n", "$$\n", "\n", "**Aplicação prática:** \n", "- Regularização L1, que incentiva esparsidade nos coeficientes de modelos. \n", "- Métricas de distância em espaços de alta dimensão, como a \"manhattan distance\".\n", "\n", "```python\n", "# Norma L1\n", "vetor = np.array([1, 2, 3])\n", "norma_l1 = np.linalg.norm(vetor, ord=1)\n", "print(norma_l1)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "6.0\n", "```\n", "\n", "A **norma L2** mede a distância do vetor até a origem, levando em conta o \"comprimento reto\" no espaço vetorial:\n", "\n", "$$ \n", "\\| \\mathbf{v} \\|_2 = \\sqrt{\\sum_{i=1}^{n} v_i^2} \n", "$$\n", "\n", "**Aplicação prática:** \n", "- Regularização L2 (weight decay) para evitar overfitting. \n", "- Cálculo de distância entre embeddings em tarefas como reconhecimento facial.\n", "\n", "```python\n", "# Norma L2 (Euclidiana)\n", "norma_l2 = np.linalg.norm(vetor) # padrão é a norma L2\n", "print(norma_l2)\n", "```\n", "\n", "**Saída do Código**\n", "```\n", "3.7416573867739413\n", "```\n", "\n", "> **Nota:** A função `np.linalg.norm` calcula, por padrão, a norma L2. Para outras normas, como L1, é necessário especificar o parâmetro `ord`.\n", "\n", "\n", "**Visualizando as Normas**\n", "\n", "A seguir, temos uma representação gráfica das normas L1 e L2 para o vetor $\\vec{v} = [2, 1]$:\n", "\n", "- **Linha sólida:** representa o vetor original. \n", "- **Linha tracejada para L1:** caminho em forma de \"escada\", que soma os deslocamentos absolutos nas direções x e y. \n", "- **Linha tracejada para L2:** caminho retilíneo direto da origem até o ponto, correspondente à distância euclidiana.\n", "\n", "Essa visualização ajuda a entender a diferença conceitual entre as normas: \n", "- L1 mede deslocamentos em eixos ortogonais; \n", "- L2 mede a distância \"em linha reta\" até o destino.\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Vetor de exemplo\n", "v = np.array([2, 1])\n", "\n", "# Cálculo das normas\n", "norma_l1 = np.linalg.norm(v, ord=1) # 3.0\n", "norma_l2 = np.linalg.norm(v) # 2.23606797749979\n", "\n", "# Plotagem\n", "fig, ax = plt.subplots(figsize=(6, 6))\n", "\n", "# Vetor\n", "ax.quiver(0, 0, v[0], v[1], angles='xy', scale_units='xy', scale=1,\n", " linewidth=2, label=r'$\\vec{v} = [2, 1]$')\n", "\n", "# Caminhos\n", "ax.plot([0, v[0], v[0]], [0, 0, v[1]], '--', linewidth=2, label='Caminho L1 (Manhattan)')\n", "ax.plot([0, v[0]], [0, v[1]], '--', linewidth=2, label='Caminho L2 (Euclidiana)')\n", "\n", "# Anotações\n", "ax.text(2.1, 0.1, f'L1 = {norma_l1:.2f}', fontsize=10)\n", "ax.text(1.1, 0.7, f'L2 = {norma_l2:.2f}', fontsize=10)\n", "\n", "# Ajustes do gráfico\n", "ax.set_xlim(-1, 3)\n", "ax.set_ylim(-1, 3)\n", "ax.set_aspect('equal')\n", "ax.grid(True, linestyle=':', alpha=0.7)\n", "ax.axhline(0, color='gray', lw=1)\n", "ax.axvline(0, color='gray', lw=1)\n", "ax.legend()\n", "ax.set_title('Visualização das Normas L1 e L2', fontsize=12)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **Cálculo em Machine Learning**\n", "\n", "O **cálculo** é uma das ferramentas matemáticas fundamentais para o funcionamento de algoritmos de machine learning. Ele está diretamente relacionado com o **processo de otimização**, no qual ajustamos os parâmetros de um modelo para **minimizar uma função de custo** e, assim, melhorar o desempenho do modelo.\n", "\n", "\n", "#### **Derivadas Parciais (Gradientes)**\n", "\n", "As derivadas parciais indicam como pequenas variações em um parâmetro específico afetam o valor de uma função. No contexto de ML, usamos derivadas parciais para calcular **como cada parâmetro influencia o erro do modelo** — isso é o que chamamos de **gradiente**.\n", "\n", "**Aplicação Prática:** \n", "Durante o treinamento de um modelo (como uma rede neural), utilizamos o **gradiente da função de custo em relação aos pesos** para saber como atualizá-los. Isso é feito com algoritmos como **gradiente descendente**.\n", "\n", "**Definição Matemática:**\n", "\n", "Se temos uma função $f(x_1, x_2, \\ldots, x_n)$, a derivada parcial em relação a $x_i$ é:\n", "\n", "$$ \n", "\\frac{\\partial f}{\\partial x_i} = \\lim_{\\Delta x_i \\to 0} \\frac{f(x_1, \\ldots, x_i + \\Delta x_i, \\ldots, x_n) - f(x_1, \\ldots, x_i, \\ldots, x_n)}{\\Delta x_i} \n", "$$\n", "\n", "**Exemplo Analítico:** \n", "Considere $f(x, y) = x^2 + 3y$. \n", "Então:\n", "\n", "- $\\frac{\\partial f}{\\partial x} = 2x \\Rightarrow \\frac{\\partial f}{\\partial x}(2, 1) = 4$\n", "- $\\frac{\\partial f}{\\partial y} = 3 \\Rightarrow \\frac{\\partial f}{\\partial y}(2, 1) = 3$\n", "\n", "**Exemplo em Python:**\n", "\n", "```python\n", "def f(x, y):\n", " return x**2 + 3*y\n", "\n", "def derivada_parcial_x(f, x, y, delta=1e-5):\n", " return (f(x + delta, y) - f(x, y)) / delta\n", "\n", "def derivada_parcial_y(f, x, y, delta=1e-5):\n", " return (f(x, y + delta) - f(x, y)) / delta\n", "\n", "print(\"∂f/∂x:\", derivada_parcial_x(f, 2, 1)) \n", "print(\"∂f/∂y:\", derivada_parcial_y(f, 2, 1)) \n", "```\n", "\n", "**Saída:**\n", "```\n", "∂f/∂x: 4.000009999951316\n", "∂f/∂y: 3.000000248221113\n", "```\n", "\n", "#### **Gradiente**\n", "\n", "O **gradiente** de uma função multivariada é um vetor composto pelas derivadas parciais em relação a todos os seus parâmetros. Ele indica a direção de **maior crescimento da função** — ou seja, é usado para decidir para onde mover os pesos na minimização da função de custo.\n", "\n", "**Definição Matemática:**\n", "\n", "Se $J(\\theta)$ é a função de custo e $\\theta = [\\theta_1, \\theta_2, \\ldots, \\theta_n]$ os parâmetros do modelo:\n", "\n", "$$ \n", "\\nabla J(\\theta) = \\left[ \\frac{\\partial J}{\\partial \\theta_1}, \\frac{\\partial J}{\\partial \\theta_2}, \\ldots, \\frac{\\partial J}{\\partial \\theta_n} \\right] \n", "$$\n", "\n", "**Exemplo Analítico:** \n", "Considere $J(\\theta_1, \\theta_2) = \\theta_1^2 + \\theta_2^2$. \n", "Então:\n", "\n", "- $\\frac{\\partial J}{\\partial \\theta_1} = 2\\theta_1 \\Rightarrow 2$\n", "- $\\frac{\\partial J}{\\partial \\theta_2} = 2\\theta_2 \\Rightarrow 4$\n", "- Gradiente: $\\nabla J(\\theta) = [2, 4]$ para $\\theta = [1, 2]$\n", "\n", "**Exemplo em Python:**\n", "\n", "```python\n", "import numpy as np\n", "\n", "def J(theta):\n", " return theta[0]**2 + theta[1]**2\n", "\n", "def gradiente(J, theta, delta=1e-5):\n", " grad = np.zeros_like(theta)\n", " for i in range(len(theta)):\n", " theta_up = np.copy(theta)\n", " theta_up[i] += delta\n", " grad[i] = (J(theta_up) - J(theta)) / delta\n", " return grad\n", "\n", "theta = np.array([1.0, 2.0])\n", "print(\"Gradiente:\", gradiente(J, theta)) \n", "```\n", "\n", "**Saída:**\n", "```\n", "Gradiente: [2.00000001 4.00000033]\n", "```\n", "Aqui está a versão revisada com a explicação didática sobre a regra da cadeia — com o foco na ideia intuitiva de \"derivar por fora e depois por dentro\", mantendo toda a estrutura e formatação que você pediu:\n", "\n", "\n", "#### **Regra da Cadeia**\n", "\n", "A **regra da cadeia** é uma técnica fundamental do cálculo usada para derivar **funções compostas** — isto é, quando uma função está “dentro” de outra. Em machine learning, essa regra é essencial durante o **backpropagation** (retropropagação do erro), pois as redes neurais profundas consistem em múltiplas camadas de funções encadeadas.\n", "\n", "> 💡 **Intuição didática:** \n", "> Para derivar uma função composta, **primeiro derivamos a função de fora** (a mais externa), **mantendo a de dentro intacta**, e depois **multiplicamos pela derivada da função de dentro**. \n", "> \n", "> É como \"descascar uma cebola\": você começa de fora para dentro.\n", "\n", "**Definição Matemática**\n", "\n", "Se temos duas funções: \n", "- $u = g(x)$ \n", "- $y = f(u)$\n", "\n", "Então a derivada de $y$ com relação a $x$ é:\n", "\n", "$$\n", "\\frac{dy}{dx} = \\frac{dy}{du} \\cdot \\frac{du}{dx}\n", "$$\n", "\n", "**Exemplo Analítico**\n", "\n", "Seja: \n", "- $g(x) = 3x + 1$ \n", "- $f(u) = u^2 \\Rightarrow f(g(x)) = (3x + 1)^2$\n", "\n", "A derivada de $f(g(x))$ usando a regra da cadeia será:\n", "\n", "$$\n", "\\frac{dy}{dx} = f'(g(x)) \\cdot g'(x)\n", "$$\n", "\n", "Calculando cada parte: \n", "- $f'(u) = 2u \\Rightarrow f'(g(x)) = 2(3x + 1)$ \n", "- $g'(x) = 3$\n", "\n", "Logo:\n", "\n", "$$\n", "\\frac{dy}{dx} = 2(3x + 1) \\cdot 3 = 6(3x + 1)\n", "$$\n", "\n", "Substituindo $x = 2$:\n", "\n", "$$\n", "\\frac{dy}{dx} = 6(3 \\cdot 2 + 1) = 6(7) = \\boxed{42}\n", "$$\n", "\n", "**Exemplo em Python**\n", "\n", "```python\n", "def f(u):\n", " return u**2 # f(u) = u²\n", "\n", "def g(x):\n", " return 3*x + 1 # g(x) = 3x + 1\n", "\n", "def composicao(x):\n", " return f(g(x)) # f(g(x)) = (3x + 1)²\n", "\n", "def derivada(x):\n", " df_du = 2 * g(x) # f'(g(x)) = 2 * (3x + 1)\n", " du_dx = 3 # g'(x) = 3\n", " return df_du * du_dx # Regra da cadeia\n", "\n", "# Teste no ponto x = 2\n", "x = 2\n", "print(\"Valor da função composta:\", composicao(x)) # Deve ser 49\n", "print(\"Derivada via regra da cadeia:\", derivada(x)) # Deve ser 42\n", "```\n", "\n", "**Saída esperada:**\n", "```\n", "Valor da função composta: 49\n", "Derivada via regra da cadeia: 42\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Redes Neurais Artificiais (ANNs)\n", "\n", "As **Redes Neurais Artificiais (ANNs)** são modelos computacionais inspirados no funcionamento do cérebro humano. Elas conseguem **aprender padrões complexos** a partir de dados, sendo amplamente utilizadas em tarefas como reconhecimento de imagens, processamento de linguagem natural e previsão de séries temporais.\n", "\n", "Neste material, exploraremos os principais conceitos de forma **passo a passo**: começando pelo neurônio artificial, passando pelo processo de aprendizado (ajuste de pesos, propagação e retropropagação), e finalizando com a otimização da rede.\n", "\n", "\n", "### O Neurônio Artificial: A Unidade Básica\n", "\n", "O **neurônio artificial** é o bloco fundamental de uma rede neural. Ele simula o comportamento de um neurônio biológico, processando informações da seguinte maneira:\n", "\n", "1. **Recebe entradas** ($x_1, x_2, \\ldots, x_n$).\n", "2. **Multiplica cada entrada por um peso** ($w_1, w_2, \\ldots, w_n$), que indica sua importância.\n", "3. **Soma todas as entradas ponderadas** e adiciona um **bias** ($b$), um termo de ajuste que auxilia no aprendizado.\n", "4. **Passa o resultado por uma função de ativação** ($f$), que decide se o neurônio deve ser ativado.\n", "\n", "**Expressão matemática de um neurônio:**\n", "\n", "$$\n", "y = f\\left(\\sum_{i=1}^{n} w_i x_i + b\\right)\n", "$$\n", "\n", "**Onde:**\n", "\n", "* **$x_i$**: Valores de entrada.\n", "* **$w_i$**: Pesos (ajustáveis durante o treinamento).\n", "* **$b$**: Bias (ajusta o limiar de ativação).\n", "* **$f$**: Função de ativação (introduz não-linearidade).\n", "* **$y$**: Saída do neurônio.\n", "\n", "#### Visualização de um Neurônio Artificial\n", "\n", "![](neuron.png)\n", "\n", "\n", "### Pesos e Bias: Como a Rede Aprende?\n", "\n", "Os **pesos ($w_i$)** e o **bias ($b$)** são os parâmetros ajustáveis que determinam o comportamento da rede neural durante o aprendizado.\n", "\n", "* **Pesos ($w_i$)** determinam a **influência de cada entrada** na saída:\n", "\n", " * Se $w_i$ é grande, a entrada tem **maior impacto**.\n", " * Se $w_i$ é pequeno ou próximo de zero, a entrada é **quase ignorada**.\n", "\n", "* **Bias ($b$)** define o **limiar de ativação**, permitindo que o neurônio ative mesmo com uma soma ponderada baixa.\n", "\n", "**Como os pesos são ajustados?**\n", "\n", "* Inicialização aleatória.\n", "* A rede **compara a saída gerada com a resposta esperada** (cálculo do erro).\n", "* Utilizando **gradiente descendente**, os pesos e o bias são **ajustados para minimizar o erro**.\n", "\n", "\n", "### Propagação (Forward Propagation)\n", "\n", "A **propagação direta (forward propagation)** é o processo em que os dados de entrada passam pela rede, camada por camada, até gerar uma saída.\n", "\n", "**Etapas principais:**\n", "\n", "* **Entrada → Camadas Ocultas:**\n", "\n", " * Cada neurônio calcula a ativação com base na soma ponderada + bias.\n", " * A função de ativação é aplicada.\n", "\n", "* **Camadas Ocultas → Saída:**\n", "\n", " * O processo se repete até a última camada.\n", " * A saída final é usada para calcular o **erro** comparando-a com a saída esperada.\n", "\n", "\n", "### Cálculo do Erro: Medindo o Desempenho\n", "\n", "O **erro**, também chamado de **função de perda**, mede **quão longe** a saída da rede está do valor real.\n", "\n", "**Funções de perda comuns:**\n", "\n", "* **Erro Quadrático Médio (MSE):**\n", "\n", " $$\n", " L = \\frac{1}{N} \\sum_{i=1}^{N} (y_{\\text{predito}} - y_{\\text{real}})^2\n", " $$\n", "\n", " Utilizada em **problemas de regressão**.\n", "\n", "* **Entropia Cruzada (Cross-Entropy):**\n", "\n", " $$\n", " L = -\\sum_{i=1}^{N} y_{\\text{real}} \\log(y_{\\text{predito}})\n", " $$\n", "\n", " Utilizada em **problemas de classificação**.\n", "\n", "**Objetivo:**\n", "**Minimizar o erro** ajustando os pesos e o bias da rede.\n", "\n", "\n", "### Funções de Ativação: Introduzindo Não-Linearidade\n", "\n", "Sem funções de ativação, uma rede neural seria apenas uma **combinação linear** de entradas, incapaz de aprender padrões complexos. As funções de ativação **introduzem não-linearidade**, permitindo que a rede modele relações mais sofisticadas.\n", "\n", "Vamos começar, analisando um exemplo de Rede Sem Funções de Ativação.\n", "\n", "Considere uma **rede neural totalmente conectada com duas camadas lineares e sem função de ativação**. Sua estrutura é a seguinte:\n", "\n", "
\n", " \n", "
\n", "\n", "**Arquitetura da Rede:**\n", "\n", "* **Entrada**: vetor $\\mathbf{x} \\in \\mathbb{R}^2$, com duas características:\n", "\n", "$$\n", "\\mathbf{x} =\n", "\\begin{bmatrix}\n", "x_1 \\\\\n", "x_2\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "1 \\\\\n", "2\n", "\\end{bmatrix}\n", "$$\n", "\n", "* **Primeira camada (oculta)**:\n", "\n", " * **Pesos** $\\mathbf{W}^{(1)} \\in \\mathbb{R}^{2 \\times 2}$\n", " * **Bias** $\\mathbf{b}^{(1)} \\in \\mathbb{R}^2$\n", "\n", "$$\n", "\\mathbf{W}^{(1)} =\n", "\\begin{bmatrix}\n", "w_{11}^{(1)} & w_{12}^{(1)} \\\\\n", "w_{21}^{(1)} & w_{22}^{(1)}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "2 & -1 \\\\\n", "0 & 3\n", "\\end{bmatrix}\n", ",\\quad\n", "\\mathbf{b}^{(1)} =\n", "\\begin{bmatrix}\n", "b_1^{(1)} \\\\\n", "b_2^{(1)}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "1 \\\\\n", "-2\n", "\\end{bmatrix}\n", "$$\n", "\n", "* **Segunda camada (saída)**:\n", "\n", " * **Pesos** $\\mathbf{W}^{(2)} \\in \\mathbb{R}^{1 \\times 2}$\n", " * **Bias** $b^{(2)} \\in \\mathbb{R}$\n", "\n", "$$\n", "\\mathbf{W}^{(2)} =\n", "\\begin{bmatrix}\n", "w_1^{(2)} & w_2^{(2)}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "3 & 5\n", "\\end{bmatrix}\n", ",\\quad\n", "b^{(2)} = 5\n", "$$\n", "\n", " **Etapas do Cálculo**\n", "\n", "**Camada Oculta (linear, sem ativação)**:\n", "\n", "$$\n", "\\mathbf{h} = \\mathbf{W}^{(1)} \\mathbf{x} + \\mathbf{b}^{(1)} =\n", "\\begin{bmatrix}\n", "2 & -1 \\\\\n", "0 & 3\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 \\\\\n", "2\n", "\\end{bmatrix}\n", "+\n", "\\begin{bmatrix}\n", "1 \\\\\n", "-2\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "(2)(1) + (-1)(2) + 1 \\\\\n", "(0)(1) + (3)(2) - 2\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "1 \\\\\n", "4\n", "\\end{bmatrix}\n", "$$\n", "\n", "2. **Camada de Saída (também linear)**:\n", "\n", "$$\n", "y = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + b^{(2)} =\n", "[3\\quad 5]\n", "\\begin{bmatrix}\n", "1 \\\\\n", "4\n", "\\end{bmatrix}\n", "+ 5 = 3 \\cdot 1 + 5 \\cdot 4 + 5 = 3 + 20 + 5 = 28\n", "$$\n", "\n", " **Resultado Final:**\n", "\n", "A saída da rede é $y = 28$, que é **linearmente dependente da entrada $\\mathbf{x}$**, reforçando que **sem funções de ativação, a composição de múltiplas camadas lineares ainda resulta em uma função linear.**\n", "\n", " **Código Python**\n", "\n", "```python\n", "import numpy as np\n", "\n", "# Entrada\n", "x = np.array([[1], [2]]) # vetor coluna\n", "\n", "# Parâmetros da primeira camada\n", "W1 = np.array([[2, -1],\n", " [0, 3]])\n", "b1 = np.array([[1],\n", " [-2]])\n", "\n", "# Parâmetros da segunda camada (atualizados)\n", "W2 = np.array([[3, 5]])\n", "b2 = 5\n", "\n", "# Forward pass sem ativação\n", "h = W1 @ x + b1 # Saída da primeira camada\n", "y = W2 @ h + b2 # Saída final\n", "\n", "print(f\"Saída da camada oculta h:\\n{h}\")\n", "print(f\"Saída final y: {y.item()}\")\n", "```\n", "\n", " **Saída esperada:**\n", "\n", "```\n", "Saída da camada oculta h:\n", "[[1]\n", " [4]]\n", "Saída final y: 28\n", "```\n", "Agora, vamos introduzir a Não-Linearidade de um reunônio ou rede neural artificial. Primeiramente, vamos conhecer algumas das principais funções de ativação. Veja a tabela a seguir:\n", "\n", "#### Principais Funções de Ativação\n", "\n", "| Função | Fórmula | Comportamento | Aplicação |\n", "| -------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------- |\n", "| **Sigmoid** | $\\sigma(x) = \\frac{1}{1 + e^{-x}}$ | Mapeia qualquer valor para $(0, 1)$. Pode sofrer com **vanishing gradient**. | Saída de classificadores binários. |\n", "| **ReLU** | $\\text{ReLU}(x) = \\max(0, x)$ | Retorna $x$ se positivo, senão $0$. Simples e eficiente, mas **pode zerar neurônios**. | Camadas ocultas em redes profundas. |\n", "| **Tanh** | $\\tanh(x) = \\frac{e^x - e^{-x}}{e^x + e^{-x}}$ | Mapeia para $(-1, 1)$. Saída centrada, mas ainda sofre com **vanishing gradient**. | Camadas ocultas com dados normalizados. |\n", "| **Leaky ReLU** | $\\text{LeakyReLU}(x) = \\max(\\alpha x, x),\\ \\alpha \\approx 0.01$ | Variante do ReLU que permite pequeno gradiente quando $x < 0$. | Evita o “neurônio morto” do ReLU. |\n", "| **Softmax** | $\\text{Softmax}(x_i) = \\frac{e^{x_i}}{\\sum_j e^{x_j}}$ | Transforma vetor em uma **distribuição de probabilidade**. | Saída de classificadores multiclasse. |\n", "\n", "\n", "**Visualização das Funções de Ativação e suas Derivadas**\n", "\n", "\"Funções\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Incorporando a Função de Ativação\n", "\n", "A figura abaixo mostra visualmente, funções de ativação sendo incorporadas em nossa pequena rede artificial:\n", "\n", "
\n", " \n", "
\n", "\n", "Ao aplicar, por exemplo, a função de ativação **sigmoide** após as multiplicações lineares, o modelo agora é capaz de capturar relações **não-lineares** entre as variáveis de entrada e a saída. A nova estrutura da rede se mantém idêntica, mas com **ativação sigmoide aplicada tanto à saída da camada oculta quanto à saída final da rede**:\n", "\n", "A função sigmoide é definida por:\n", "\n", "$$\n", "\\sigma(z) = \\frac{1}{1 + e^{-z}}\n", "$$\n", "\n", "**Etapas do Cálculo (com ativação sigmoide)**\n", "\n", "* **Camada Oculta (linear + ativação sigmoide)**\n", "\n", "$$\n", "\\mathbf{h} = \\sigma(\\mathbf{W}^{(1)} \\mathbf{x} + \\mathbf{b}^{(1)}) =\n", "\\sigma\\left(\n", "\\begin{bmatrix}\n", "2 & -1 \\\\\n", "0 & 3\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 \\\\\n", "2\n", "\\end{bmatrix}\n", "+\n", "\\begin{bmatrix}\n", "1 \\\\\n", "-2\n", "\\end{bmatrix}\n", "\\right)\n", "=\n", "\\sigma\\left(\n", "\\begin{bmatrix}\n", "1 \\\\\n", "4\n", "\\end{bmatrix}\n", "\\right)\n", "=\n", "\\begin{bmatrix}\n", "\\sigma(1) \\\\\n", "\\sigma(4)\n", "\\end{bmatrix}\n", "\\approx\n", "\\begin{bmatrix}\n", "0{,}731 \\\\\n", "0{,}982\n", "\\end{bmatrix}\n", "$$\n", "\n", "* **Camada de Saída (linear + ativação sigmoide)**\n", "\n", "$$\n", "z = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + b^{(2)} =\n", "[3\\quad 5]\n", "\\begin{bmatrix}\n", "0{,}731 \\\\\n", "0{,}982\n", "\\end{bmatrix}\n", "+ 5\n", "\\approx 2{,}193 + 4{,}91 + 5 = 12{,}103\n", "$$\n", "\n", "$$\n", "y = \\sigma(z) = \\sigma(12{,}103) \\approx 0{,}9999945\n", "$$\n", "\n", "**Resultado Final**\n", "\n", "Com a **não-linearidade introduzida pela sigmoide em todas as camadas**, a rede retorna:\n", "\n", "$$\n", "y \\approx 0{,}9999945\n", "$$\n", "\n", "O resultado agora **não é mais uma combinação linear direta da entrada**, o que mostra o poder da não-linearidade em redes neurais.\n", "\n", "**Código Python (com sigmoide na saída também)**\n", "\n", "```python\n", "import numpy as np\n", "\n", "# Função sigmoide\n", "def sigmoid(z):\n", " return 1 / (1 + np.exp(-z))\n", "\n", "# Entrada\n", "x = np.array([[1], [2]])\n", "\n", "# Parâmetros da primeira camada\n", "W1 = np.array([[2, -1],\n", " [0, 3]])\n", "b1 = np.array([[1],\n", " [-2]])\n", "\n", "# Parâmetros da segunda camada\n", "W2 = np.array([[3, 5]])\n", "b2 = 5\n", "\n", "# Forward pass com ativação sigmoide em ambas as camadas\n", "h_linear = W1 @ x + b1\n", "h = sigmoid(h_linear)\n", "z = W2 @ h + b2\n", "y = sigmoid(z)\n", "\n", "print(f\"Saída linear da camada oculta (antes da ativação):\\n{h_linear}\")\n", "print(f\"Saída da camada oculta (após sigmoide):\\n{h}\")\n", "print(f\"Valor z (entrada da saída): {z.item():.3f}\")\n", "print(f\"Saída final y (após sigmoide): {y.item():.6f}\")\n", "```\n", "\n", "**Saída esperada:**\n", "\n", "```\n", "Saída linear da camada oculta (antes da ativação):\n", "[[1]\n", " [4]]\n", "Saída da camada oculta (após sigmoide):\n", "[[0.73105858]\n", " [0.98201379]]\n", "Valor z (entrada da saída): 12.103\n", "Saída final y (após sigmoide): 0.999995\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Calculando o Erro\n", "\n", "Após obter a saída da rede, podemos avaliar seu desempenho comparando-a com o **valor real** esperado. Essa comparação é feita por meio de uma **função de perda**, que mede a diferença entre a saída predita e a saída desejada. Uma das funções mais utilizadas para problemas de regressão é o **Erro Quadrático Médio (MSE)**, definido como:\n", "\n", "$$\n", "L = \\frac{1}{N} \\sum_{i=1}^{N} (y_{\\text{predito}}^{(i)} - y_{\\text{real}}^{(i)})^2\n", "$$\n", "\n", "Onde:\n", "\n", "* $L$ é o valor da perda (erro médio);\n", "* $N$ é o número de amostras;\n", "* $y_{\\text{predito}}^{(i)}$ é a saída da rede para a $i$-ésima amostra;\n", "* $y_{\\text{real}}^{(i)}$ é o valor real esperado para a mesma amostra.\n", "\n", "\n", "**Exemplo prático (com N = 1):**\n", "\n", "Suponha que o valor real esperado seja:\n", "\n", "$$\n", "y_{\\text{real}} = 0{,}5\n", "$$\n", "\n", "E a saída da rede (com ativação sigmoide) seja:\n", "\n", "$$\n", "y_{\\text{predito}} \\approx 0{,}999995\n", "$$\n", "\n", "Aplicando na fórmula do MSE:\n", "\n", "$$\n", "L = (0{,}999995 - 0{,}5)^2 = (0{,}499995)^2 \\approx 0{,}249995\n", "$$\n", "\n", "Portanto, o erro quadrático médio é aproximadamente **0,249995**, o que indica uma diferença relevante entre a predição da rede e o valor esperado.\n", "\n", "\n", "**Código Python (cálculo do erro MSE)**\n", "\n", "```python\n", "# Valor real esperado\n", "y_real = 0.5\n", "\n", "# Saída predita pela rede\n", "y_predito = y.item()\n", "\n", "# Cálculo do erro quadrático médio\n", "mse = (y_predito - y_real) ** 2\n", "\n", "print(f\"Erro Quadrático Médio (MSE): {mse:.6f}\")\n", "```\n", "\n", "**Saída esperada:**\n", "\n", "```\n", "Erro Quadrático Médio (MSE): 0.249995\n", "```\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 🧠 Exercício: Simulação Interativa de uma Rede Neural Simples\n", "\n", "Neste exercício, você irá **simular uma pequena rede neural** composta por:\n", "\n", "* **2 entradas**\n", "* **1 camada oculta com 2 neurônios**\n", "* **1 neurônio na saída**\n", "\n", "A saída será calculada em duas etapas:\n", "\n", "* **Camada oculta (com ativação)**:\n", "\n", " $$\n", " \\mathbf{h} = f(\\mathbf{W}^{(1)} \\cdot \\mathbf{x} + \\mathbf{b}^{(1)})\n", " $$\n", "\n", "* **Camada de saída (com ativação)**:\n", "\n", " $$\n", " y = f(\\mathbf{W}^{(2)} \\cdot \\mathbf{h} + b^{(2)})\n", " $$\n", "\n", "**O que você deve implementar (com Gradio):**\n", "\n", "Crie uma interface interativa que permita:\n", "\n", "* **Inserir manualmente** ou **gerar aleatoriamente** os seguintes valores:\n", "\n", " * Vetor de entrada $\\mathbf{x} = [x_1, x_2]$\n", " * Matriz de pesos da primeira camada $\\mathbf{W}^{(1)}$ (2×2)\n", " * Bias da primeira camada $\\mathbf{b}^{(1)}$ (2×1)\n", " * Vetor de pesos da segunda camada $\\mathbf{W}^{(2)}$ (1×2)\n", " * Bias da segunda camada $b^{(2)}$\n", "\n", "* **Escolher a função de ativação**:\n", "\n", " * Sigmoid\n", " * ReLU\n", " * Tanh\n", " * Leaky ReLU\n", "\n", "* **Inserir o valor desejado (target):**\n", "\n", " * Valor escalar $y_{\\text{true}}$ que representa a saída esperada do modelo.\n", "\n", "**Exibir os resultados do forward pass:**\n", "\n", "* Saída da camada oculta $\\mathbf{h}$\n", "* Valor intermediário $z$ da saída\n", "* Saída final $y = f(z)$\n", "* **Erro quadrático:**\n", "\n", " $$\n", " \\text{Erro} = (y - y_{\\text{true}})^2\n", " $$\n", "\n", "**Objetivo:**\n", "\n", "Explorar o comportamento de uma rede neural simples, observando como diferentes pesos, vieses e funções de ativação afetam o resultado da predição — e comparando com o valor esperado (target) para analisar o erro da rede.\n", "\n", "\n", "> **Sugestão Visual:**\n", "> Após clicar em **\"Calcular\"**, é desejado que seja **gerada automaticamente uma figura** representando:\n", ">\n", "> * Os **neurônios** (entradas, camada oculta e saída),\n", "> * As **conexões** entre eles,\n", "> * Os **pesos** associados a cada conexão,\n", "> * As **ativações** intermediárias e finais,\n", "> * E os **valores numéricos** em cada etapa.\n", ">\n", "> Isso facilita a compreensão do **fluxo de dados na rede** e torna o comportamento da rede mais intuitivo para o usuário.\n", ">\n", "> 💡 **Dica técnica:** bibliotecas como `graphviz`, `networkx` ou `matplotlib` podem ser utilizadas para gerar essa visualização de forma programática.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Backpropagation (Começando pela Camada de Saída)\n", "\n", "Para que uma rede neural aprenda, ela precisa ajustar seus **pesos e biases** de forma a reduzir o erro entre a **saída predita** e o **valor real**. Esse ajuste é feito por meio do algoritmo de **retropropagação do erro (backpropagation)**, que calcula o **gradiente da função de perda em relação aos parâmetros da rede**.\n", "\n", "**Visão Geral da Retropropagação**\n", "\n", "A lógica da retropropagação segue a **regra da cadeia** da derivada: partimos do erro final da rede e voltamos etapa por etapa até os parâmetros, aplicando:\n", "\n", "```\n", "∂L/∂W = ∂L/∂y · ∂y/∂z · ∂z/∂W\n", "```\n", "\n", "Neste caso:\n", "\n", "* **L** é a função de perda;\n", "* **y** é a saída da rede;\n", "* **z** é a entrada da função de ativação na última camada;\n", "* **W** representa os pesos da camada de saída.\n", "\n", "**Funções utilizadas**\n", "\n", "* **Função de perda (Erro Quadrático Médio - MSE):**\n", "\n", " $$\n", " L = \\frac{1}{2}(y - y_{\\text{real}})^2\n", " $$\n", "\n", " O fator \\$\\frac{1}{2}\\$ é utilizado para simplificar a derivada.\n", "\n", "* **Função de ativação (Sigmoide):**\n", "\n", " $$\n", " \\sigma(z) = \\frac{1}{1 + e^{-z}}, \\quad \\sigma'(z) = \\sigma(z)(1 - \\sigma(z))\n", " $$\n", "\n", "#### Etapas da Retropropagação\n", "\n", "**1. Derivada da perda em relação à saída da rede**\n", "\n", "A saída da rede é $y$ e o valor esperado é $y_{\\text{real}}$:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial y} = y - y_{\\text{real}} = 0{,}999995 - 0{,}5 = 0{,}499995\n", "$$\n", "\n", "**2. Derivada da saída em relação à entrada da função de ativação**\n", "\n", "Sabemos que:\n", "\n", "$$\n", "y = \\sigma(z), \\quad \\text{com} \\quad z = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + \\mathbf{b}^{(2)}\n", "$$\n", "\n", "Logo, aplicando a derivada da função sigmoide:\n", "\n", "$$\n", "\\frac{\\partial y}{\\partial z} = \\sigma(z)(1 - \\sigma(z)) = 0{,}999995 \\cdot (1 - 0{,}999995) \\approx 0{,}000005\n", "$$\n", "\n", "**3. Derivada da perda em relação a $z$ (entrada da camada de saída)**\n", "\n", "Aplicando a **regra da cadeia**:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial z} = \\frac{\\partial L}{\\partial y} \\cdot \\frac{\\partial y}{\\partial z} = 0{,}499995 \\cdot 0{,}000005 \\approx 2{,}5 \\times 10^{-6}\n", "$$\n", "\n", "Esse valor representa o **erro propagado** na saída e é comumente chamado de:\n", "\n", "$$\n", "\\delta = \\frac{\\partial L}{\\partial z}\n", "$$\n", "\n", "\n", "\n", "#### Gradientes para Ajuste de Parâmetros\n", "\n", "**Por que usamos $\\delta \\cdot \\mathbf{h}^T$ para calcular $\\partial L / \\partial \\mathbf{W}^{(2)}$?**\n", "\n", "A entrada $z$ depende dos pesos da forma:\n", "\n", "$$\n", "z = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + \\mathbf{b}^{(2)}\n", "$$\n", "\n", "Portanto:\n", "\n", "$$\n", "\\frac{\\partial z}{\\partial \\mathbf{W}^{(2)}} = \\mathbf{h}^T \\quad \\Rightarrow \\quad\n", "\\frac{\\partial L}{\\partial \\mathbf{W}^{(2)}} = \\delta \\cdot \\mathbf{h}^T\n", "$$\n", "\n", "**Para o bias:**\n", "\n", "Como $z$ depende linearmente do bias com derivada igual a 1:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{b}^{(2)}} = \\delta\n", "$$\n", "\n", "**Cálculo Numérico**\n", "\n", "Com:\n", "\n", "$$\n", "\\delta \\approx 2{,}5 \\times 10^{-6}, \\quad\n", "\\mathbf{h} = \\begin{bmatrix} 0{,}731 \\\\ 0{,}982 \\end{bmatrix}\n", "$$\n", "\n", "Temos:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{W}^{(2)}} \\approx\n", "\\delta \\cdot \\begin{bmatrix} 0{,}731 & 0{,}982 \\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "1{,}8275 \\times 10^{-6} & 2{,}4550 \\times 10^{-6}\n", "\\end{bmatrix}\n", "$$\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{b}^{(2)}} = \\delta \\approx 2{,}5 \\times 10^{-6}\n", "$$\n", "\n", "**Código: Gradientes da Saída**\n", "\n", "```python\n", "# Derivada da sigmoide\n", "def sigmoid_deriv(z):\n", " s = sigmoid(z)\n", " return s * (1 - s)\n", "\n", "# y_real já definido\n", "y_real = 0.5\n", "\n", "# Derivadas\n", "dL_dy = y - y_real # ∂L/∂y\n", "dy_dz = sigmoid_deriv(z) # ∂y/∂z\n", "dL_dz = dL_dy * dy_dz # ∂L/∂z\n", "\n", "# Gradientes para W2 e b2\n", "dL_dW2 = dL_dz * h.T\n", "dL_db2 = dL_dz\n", "\n", "print(f\"∂L/∂z (delta): {dL_dz.item():.8f}\")\n", "print(f\"Gradiente de W2: {dL_dW2}\")\n", "print(f\"Gradiente de b2: {dL_db2.item():.8f}\")\n", "```\n", "\n", "**Saída esperada:**\n", "\n", "```\n", "∂L/∂z (delta): 0.00000250\n", "Gradiente de W2: [[1.8275e-06 2.4550e-06]]\n", "Gradiente de b2: 0.00000250\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **Backpropagation na Camada Oculta**\n", "\n", "Agora que já calculamos o erro da **camada de saída**, vamos entender **como esse erro se propaga para a primeira camada**, afetando os pesos \\$\\mathbf{W}^{(1)}$e os bias \\$\\mathbf{b}^{(1)}\\$.\n", "\n", "Usamos os seguintes valores:\n", "\n", "* Entrada: $\\mathbf{x} = \\begin{bmatrix} 1 \\ 2 \\end{bmatrix} $\n", "* Pesos da 1ª camada: $\\mathbf{W}^{(1)} = \\begin{bmatrix} 2 & -1 \\ 0 & 3 \\end{bmatrix} $\n", "* Bias da 1ª camada: $\\mathbf{b}^{(1)} = \\begin{bmatrix} 1 \\ -2 \\end{bmatrix} $\n", "* Saída linear da camada oculta: $\\mathbf{h}\\_{\\text{linear}} = \\begin{bmatrix} 1 \\ 4 \\end{bmatrix} $\n", "* Saída com ativação (sigmoide): $\\mathbf{h} \\approx \\begin{bmatrix} 0{,}731 \\ 0{,}982 \\end{bmatrix} $\n", "\n", "**1. Derivada da perda em relação à saída da camada oculta $\\mathbf{h} $**\n", "\n", "A saída da rede é:\n", "\n", "$$\n", "z = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + b^{(2)}\n", "$$\n", "\n", "Aplicando a regra da cadeia:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{h}} = \\frac{\\partial L}{\\partial z} \\cdot \\frac{\\partial z}{\\partial \\mathbf{h}} = \\delta \\cdot \\mathbf{W}^{(2)}\n", "$$\n", "\n", "Com:\n", "\n", "* $\\delta = \\frac{\\partial L}{\\partial z} \\approx 2{,}5 \\times 10^{-6} $\n", "* $\\mathbf{W}^{(2)} = [3 \\quad 5] $\n", "\n", "Obtemos:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{h}} \\approx\n", "2{,}5 \\times 10^{-6} \\cdot\n", "\\begin{bmatrix}\n", "3 \\\\\n", "5\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "7{,}5 \\times 10^{-6} \\\\\n", "1{,}25 \\times 10^{-5}\n", "\\end{bmatrix}\n", "$$\n", "\n", "**2. Derivada da função sigmoide na camada oculta**\n", "\n", "$$\n", "\\sigma'(z) = \\sigma(z) \\cdot (1 - \\sigma(z))\n", "$$\n", "\n", "Calculando:\n", "\n", "* Para $z = 1 $: $\\sigma(1) \\approx 0{,}731 \\Rightarrow \\sigma'(1) \\approx 0{,}197 $\n", "* Para $z = 4 $: $\\sigma(4) \\approx 0{,}982 \\Rightarrow \\sigma'(4) \\approx 0{,}018 $\n", "\n", "Então:\n", "\n", "$$\n", "\\sigma'(\\mathbf{h}_{\\text{linear}}) \\approx\n", "\\begin{bmatrix}\n", "0{,}197 \\\\\n", "0{,}018\n", "\\end{bmatrix}\n", "$$\n", "\n", "**3. Derivada da perda em relação à entrada da sigmoide**\n", "\n", "Aplicamos o produto elemento a elemento:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{h}_{\\text{linear}}} =\n", "\\frac{\\partial L}{\\partial \\mathbf{h}} \\odot \\sigma'(\\mathbf{h}_{\\text{linear}})\n", "=\n", "\\begin{bmatrix}\n", "7{,}5 \\times 10^{-6} \\\\\n", "1{,}25 \\times 10^{-5}\n", "\\end{bmatrix}\n", "\\odot\n", "\\begin{bmatrix}\n", "0{,}197 \\\\\n", "0{,}018\n", "\\end{bmatrix}\n", "\\approx\n", "\\begin{bmatrix}\n", "1{,}48 \\times 10^{-6} \\\\\n", "2{,}25 \\times 10^{-7}\n", "\\end{bmatrix}\n", "$$\n", "\n", "**4. Gradiente da perda em relação aos pesos da 1ª camada $\\mathbf{W}^{(1)} $**\n", "\n", "A derivada é dada por:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{W}^{(1)}} =\n", "\\delta_{\\text{oculta}} \\cdot \\mathbf{x}^T\n", "$$\n", "\n", "Com:\n", "\n", "* $\\delta\\_{\\text{oculta}} = \\frac{\\partial L}{\\partial \\mathbf{h}\\_{\\text{linear}}} $\n", "* $\\mathbf{x}^T = \\begin{bmatrix} 1 & 2 \\end{bmatrix} $\n", "\n", "Este é um produto **vetor coluna × vetor linha**, resultando numa matriz:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{W}^{(1)}} =\n", "\\begin{bmatrix}\n", "1{,}48 \\times 10^{-6} \\\\\n", "2{,}25 \\times 10^{-7}\n", "\\end{bmatrix}\n", "\\cdot\n", "\\begin{bmatrix}\n", "1 & 2\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "1{,}48 \\times 10^{-6} & 2{,}96 \\times 10^{-6} \\\\\n", "2{,}25 \\times 10^{-7} & 4{,}50 \\times 10^{-7}\n", "\\end{bmatrix}\n", "$$\n", "\n", "**5. Gradiente da perda em relação ao bias da 1ª camada**\n", "\n", "O bias é somado diretamente, então:\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{b}^{(1)}} = \\delta_{\\text{oculta}} \\approx\n", "\\begin{bmatrix}\n", "1{,}48 \\times 10^{-6} \\\\\n", "2{,}25 \\times 10^{-7}\n", "\\end{bmatrix}\n", "$$\n", "\n", "**Resumo da cadeia de derivadas para $\\mathbf{W}^{(1)} $:**\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial \\mathbf{W}^{(1)}} =\n", "\\frac{\\partial L}{\\partial \\mathbf{h}} \\cdot\n", "\\frac{\\partial \\mathbf{h}}{\\partial \\mathbf{h}_{\\text{linear}}} \\cdot\n", "\\frac{\\partial \\mathbf{h}_{\\text{linear}}}{\\partial \\mathbf{W}^{(1)}}\n", "$$\n", "\n", "**Código: Gradientes da Camada Oculta**\n", "\n", "```python\n", "# Derivadas da sigmoide\n", "h_deriv = sigmoid_deriv(h_linear)\n", "\n", "# Gradiente da perda em relação à saída da camada oculta\n", "dL_dh = delta @ W2 # delta é (1,1), W2 é (1,2) → resultado (1,2)\n", "\n", "# Derivada em relação à entrada da sigmoide\n", "dL_dh_linear = dL_dh.T * h_deriv # Transpõe dL_dh e faz produto elemento a elemento\n", "\n", "# Gradiente de W1 e b1\n", "dL_dW1 = dL_dh_linear @ x.T\n", "dL_db1 = dL_dh_linear\n", "\n", "print(f\"Gradiente de W1:\\n{dL_dW1}\")\n", "print(f\"Gradiente de b1:\\n{dL_db1}\")\n", "```\n", "\n", "**Saída Esperada**\n", "\n", "```\n", "Gradiente de W1:\n", "[[1.4801e-06 2.9602e-06]\n", " [2.2499e-07 4.4999e-07]]\n", "Gradiente de b1:\n", "[[1.4801e-06]\n", " [2.2499e-07]]\n", "```\n", "\n", "Esses valores agora podem ser utilizados na **atualização dos pesos** com gradiente descendente:\n", "\n", "```python\n", "# Atualização dos parâmetros da primeira camada\n", "W1 -= eta * dL_dW1\n", "b1 -= eta * dL_db1\n", "```\n", "\n", "> Esses gradientes nos dizem como **cada peso e bias da camada oculta contribuem para o erro total**. Ao subtrairmos uma fração proporcional a eles (usando a taxa de aprendizado $\\eta \\$), **ajustamos os parâmetros para reduzir o erro da rede**.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **Resumo Completo: Ciclo de Treinamento**\n", "\n", "Usamos:\n", "\n", "* Entrada $\\mathbf{x} = \\begin{bmatrix} 1 \\ 2 \\end{bmatrix}$\n", "* Função de ativação: **sigmoide**\n", "* Função de perda: **Erro Quadrático Médio (MSE)**\n", "* Valor real esperado: $y\\_{\\text{real}} = 0{,}5$\n", "\n", "- **Forward Pass**\n", "\n", "* Camada oculta (linear + sigmoide):\n", "\n", " $$\n", " \\mathbf{h}_{\\text{linear}} = \\mathbf{W}^{(1)} \\mathbf{x} + \\mathbf{b}^{(1)} =\n", " \\begin{bmatrix}\n", " 0{,}1 \\\\\n", " 0{,}4\n", " \\end{bmatrix}\n", " \\Rightarrow\n", " \\mathbf{h} = \\sigma(\\mathbf{h}_{\\text{linear}}) \\approx\n", " \\begin{bmatrix}\n", " 0{,}525 \\\\\n", " 0{,}598\n", " \\end{bmatrix}\n", " $$\n", "\n", "* Camada de saída (linear + sigmoide):\n", "\n", " $$\n", " z = \\mathbf{W}^{(2)} \\cdot \\mathbf{h} + b^{(2)} = 0{,}990 \\Rightarrow y = \\sigma(z) \\approx 0{,}7291\n", " $$\n", "\n", "- **Cálculo da perda**\n", "\n", "Erro real esperado: $y\\_{\\text{real}} = 0{,}5$\n", "\n", "$$\n", "L = (0{,}7291 - 0{,}5)^2 \\approx 0{,}052469\n", "$$\n", "\n", "* **Backpropagation**\n", "\n", "- Gradiente da saída:\n", "\n", " $$\n", " \\delta_{\\text{output}} = (y - y_{\\text{real}}) \\cdot \\sigma'(z) \\approx 0{,}2291 \\cdot (1 - 0{,}7291) \\cdot 0{,}7291 \\approx 0{,}0450\n", " $$\n", "\n", "- Gradientes da segunda camada:\n", "\n", " $$\n", " \\frac{\\partial L}{\\partial \\mathbf{W}^{(2)}} = \\delta \\cdot \\mathbf{h}^T \\approx\n", " \\begin{bmatrix}\n", " 0{,}0237 & 0{,}0270\n", " \\end{bmatrix}\n", " \\quad,\\quad\n", " \\frac{\\partial L}{\\partial b^{(2)}} = \\delta = 0{,}0450\n", " $$\n", "\n", "- Gradientes da primeira camada:\n", "\n", " $$\n", " \\frac{\\partial L}{\\partial \\mathbf{W}^{(1)}} \\approx\n", " \\begin{bmatrix}\n", " 0{,}000685 & 0{,}001371 \\\\\n", " 0{,}000909 & 0{,}001818\n", " \\end{bmatrix}\n", " \\quad,\\quad\n", " \\frac{\\partial L}{\\partial \\mathbf{b}^{(1)}} \\approx\n", " \\begin{bmatrix}\n", " 0{,}000685 \\\\\n", " 0{,}000909\n", " \\end{bmatrix}\n", " $$\n", "\n", "**Atualização dos Pesos**\n", "\n", "Usando taxa de aprendizado $\\eta = 0{,}005$:\n", "\n", "$$\n", "\\theta \\leftarrow \\theta - \\eta \\cdot \\frac{\\partial L}{\\partial \\theta}\n", "$$\n", "\n", "**Código Final Completo**\n", "\n", "```python\n", "import numpy as np\n", "\n", "# Funções\n", "def sigmoid(z):\n", " return 1 / (1 + np.exp(-z))\n", "\n", "def sigmoid_deriv(z):\n", " s = sigmoid(z)\n", " return s * (1 - s)\n", "\n", "# Dados\n", "x = np.array([[1], [2]])\n", "y_real = 0.5\n", "eta = 0.005\n", "\n", "# Inicialização\n", "W1 = np.array([[0.2, -0.1],\n", " [0.0, 0.3]])\n", "b1 = np.array([[0.1],\n", " [-0.2]])\n", "W2 = np.array([[0.3, 0.5]])\n", "b2 = np.array([[0.5]])\n", "\n", "# Forward Pass\n", "h_linear = W1 @ x + b1\n", "h = sigmoid(h_linear)\n", "z = W2 @ h + b2\n", "y_pred = sigmoid(z)\n", "\n", "# Loss\n", "mse = (y_pred.item() - y_real)**2\n", "\n", "# Gradiente da saída\n", "dy_dz = sigmoid_deriv(z)\n", "delta = (y_pred - y_real) * dy_dz\n", "\n", "# Gradientes da segunda camada\n", "dL_dW2 = delta @ h.T\n", "dL_db2 = delta\n", "\n", "# Gradientes da primeira camada\n", "dL_dh = W2.T @ delta\n", "dh_dh_linear = sigmoid_deriv(h_linear)\n", "dL_dh_linear = dL_dh * dh_dh_linear\n", "dL_dW1 = dL_dh_linear @ x.T\n", "dL_db1 = dL_dh_linear\n", "\n", "# Atualização dos parâmetros\n", "W2 -= eta * dL_dW2\n", "b2 -= eta * dL_db2\n", "W1 -= eta * dL_dW1\n", "b1 -= eta * dL_db1\n", "\n", "# Novo Forward Pass\n", "h_linear_new = W1 @ x + b1\n", "h_new = sigmoid(h_linear_new)\n", "z_new = W2 @ h_new + b2\n", "y_new = sigmoid(z_new)\n", "\n", "print(f\"Saída original da rede: {y_pred.item():.6f}\")\n", "print(f\"Erro MSE: {mse:.6f}\")\n", "print(f\"Saída após atualização: {y_new.item():.6f}\")\n", "```\n", "\n", "**Resultado Final**\n", "\n", "A saída esperada será algo como:\n", "\n", "```\n", "Saída original da rede: 0.729103\n", "Erro MSE: 0.052469\n", "Saída após atualização: 0.728558\n", "```\n", "\n", "Ou seja, houve uma **pequena correção na direção do valor real desejado (0.5)**. Esse processo pode ser repetido em ciclos sucessivos até que a rede aprenda a produzir saídas cada vez mais próximas do valor alvo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **Treinamento Funcional**\n", "\n", "```python\n", "import numpy as np\n", "\n", "def sigmoid(z):\n", " return 1 / (1 + np.exp(-z))\n", "\n", "def sigmoid_deriv(z):\n", " s = sigmoid(z)\n", " return s * (1 - s)\n", "\n", "# Dados de entrada\n", "x = np.array([[1], [2]])\n", "y_real = 0.5\n", "\n", "# Hiperparâmetros\n", "eta = 0.005 # Taxa de aprendizado ajustada\n", "epochs = 10000\n", "\n", "# Inicialização dos pesos com valores menores para evitar saturação\n", "W1 = np.array([[0.2, -0.1],\n", " [0.0, 0.3]])\n", "b1 = np.array([[0.1],\n", " [-0.2]])\n", "W2 = np.array([[0.3, 0.5]])\n", "b2 = np.array([[0.5]])\n", "\n", "# Loop de treinamento\n", "for epoch in range(1, epochs+1):\n", " # FORWARD PASS\n", " h_linear = W1 @ x + b1\n", " h = sigmoid(h_linear)\n", " z = W2 @ h + b2\n", " y_pred = sigmoid(z)\n", "\n", " # Cálculo da perda\n", " mse = (y_pred.item() - y_real)**2\n", "\n", " # BACKPROPAGATION\n", " dy_dz = sigmoid_deriv(z)\n", " delta = (y_pred - y_real) * dy_dz\n", "\n", " # Gradientes da camada de saída\n", " dL_dW2 = delta @ h.T\n", " dL_db2 = delta\n", "\n", " # Gradientes da camada oculta\n", " dL_dh = W2.T @ delta\n", " dh_dh_linear = sigmoid_deriv(h_linear)\n", " dL_dh_linear = dL_dh * dh_dh_linear\n", " dL_dW1 = dL_dh_linear @ x.T\n", " dL_db1 = dL_dh_linear\n", "\n", " # Atualização dos pesos\n", " W2 -= eta * dL_dW2\n", " b2 -= eta * dL_db2\n", " W1 -= eta * dL_dW1\n", " b1 -= eta * dL_db1\n", "\n", " if epoch % 1000 == 0 or epoch == 1:\n", " print(f\"Época {epoch:5d} | y_pred = {y_pred.item():.6f} | MSE = {mse:.6f}\")\n", "\n", "print(\"\\nPesos finais:\")\n", "print(\"W1 =\\n\", W1)\n", "print(\"b1 =\\n\", b1)\n", "print(\"W2 =\\n\", W2)\n", "print(\"b2 =\\n\", b2)\n", "```\n", "\n", "**Saída esperada (resumida)**\n", "\n", "```\n", "Época 1 | y_pred = 0.710949 | MSE = 0.044290\n", "Época 1000 | y_pred = 0.536874 | MSE = 0.001358\n", "Época 5000 | y_pred = 0.502849 | MSE = 0.000008\n", "Época 10000 | y_pred = 0.500973 | MSE = 0.000001\n", "```\n", "\n", "A saída da rede, `y_pred`, converge suavemente para o valor esperado `0.5` e o erro quadrático médio (`MSE`) reduz consistentemente — isso confirma que o treinamento está correto e efetivo.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1 Neurônio\n", "\n", "### Forward e Backpropagation\n", "\n", "Vamos para um caso simples para facilitar o entendimento. Depois, você pode voltar e rever o item para uma pequena rede.\n", "\n", "Este exemplo demonstra o treinamento de um único neurônio com dois inputs. A ideia é mostrar, passo a passo, como o algoritmo de backpropagation atualiza os pesos para minimizar o erro entre a saída prevista e a saída real.\n", "\n", "\n", "**Dados de entrada e saída**\n", "\n", "* **Entradas:** `x = [0.6, 0.3]`\n", "* **Saída real (esperada):** `y_real = 0.8`\n", "* **Pesos iniciais:** `w = [0.4, -0.1]`\n", "* **Bias inicial:** `b = 0.2`\n", "* **Taxa de aprendizado:** `α = 0.1`\n", "\n", "\n", "\n", "**Forward Pass**\n", "\n", "1. **Cálculo da soma ponderada $z$:**\n", "\n", "$$\n", "z = x_1 w_1 + x_2 w_2 + b = 0.24 - 0.03 + 0.2 = 0.41\n", "$$\n", "\n", "2. **Ativação (função sigmoide):**\n", "\n", "$$\n", "\\sigma(z) = \\frac{1}{1 + e^{-z}} = \\frac{1}{1 + e^{-0.41}} \\approx 0.601\n", "$$\n", "\n", "3. **Função de perda (erro quadrático médio):**\n", "\n", "$$\n", "\\text{Loss} = (y_{\\text{pred}} - y_{\\text{real}})^2 = (0.601 - 0.8)^2 = 0.0396\n", "$$\n", "\n", "**Backpropagation – Passo a Passo com Expressões e Cálculos**\n", "\n", "**1. Derivada da função de perda:**\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial y_{\\text{pred}}} = 2(y_{\\text{pred}} - y_{\\text{real}}) = 2(0.601 - 0.8) = -0.398\n", "$$\n", "\n", "**2. Derivada da sigmoide:**\n", "\n", "$$\n", "\\frac{\\partial y_{\\text{pred}}}{\\partial z} = y_{\\text{pred}}(1 - y_{\\text{pred}}) = 0.601 \\cdot (1 - 0.601) = 0.2396\n", "$$\n", "\n", "**3. Derivada da perda em relação a $z$:**\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial z} = \\frac{\\partial L}{\\partial y_{\\text{pred}}} \\cdot \\frac{\\partial y_{\\text{pred}}}{\\partial z} = -0.398 \\cdot 0.2396 = -0.0954\n", "$$\n", "\n", "**4. Derivadas da perda em relação aos pesos e bias:**\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial w_1} = \\frac{\\partial L}{\\partial z} \\cdot x_1 = -0.0954 \\cdot 0.6 = -0.0572\n", "$$\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial w_2} = \\frac{\\partial L}{\\partial z} \\cdot x_2 = -0.0954 \\cdot 0.3 = -0.0286\n", "$$\n", "\n", "$$\n", "\\frac{\\partial L}{\\partial b} = \\frac{\\partial L}{\\partial z} = -0.0954\n", "$$\n", "\n", "**5. Atualização dos parâmetros:**\n", "\n", "$$\n", "w_1 = w_1 - \\alpha \\cdot \\frac{\\partial L}{\\partial w_1} = 0.4 - 0.1 \\cdot (-0.0572) = 0.4057\n", "$$\n", "\n", "$$\n", "w_2 = w_2 - \\alpha \\cdot \\frac{\\partial L}{\\partial w_2} = -0.1 - 0.1 \\cdot (-0.0286) = -0.0971\n", "$$\n", "\n", "$$\n", "b = b - \\alpha \\cdot \\frac{\\partial L}{\\partial b} = 0.2 - 0.1 \\cdot (-0.0954) = 0.2095\n", "$$\n", "\n", "**Quadro Resumo das Expressões Matemáticas**\n", "\n", "| **Passo** | **Expressão** |\n", "|-------------------------|------------------------------------------------------------------------------|\n", "| **Forward Pass** | |\n", "| Soma Ponderada ($z$) | $z = x_1 w_1 + x_2 w_2 + b$ |\n", "| Ativação ($\\sigma(z)$) | $\\sigma(z) = \\frac{1}{1 + e^{-z}}$ |\n", "| Função de Perda | $\\text{Loss} = (y_{\\text{pred}} - y_{\\text{real}})^2$ |\n", "| **Backpropagation** | |\n", "| Derivada da Perda | $\\frac{\\partial L}{\\partial y_{\\text{pred}}} = 2(y_{\\text{pred}} - y_{\\text{real}})$ |\n", "| Derivada da Sigmoide | $\\frac{\\partial y_{\\text{pred}}}{\\partial z} = y_{\\text{pred}}(1 - y_{\\text{pred}})$ |\n", "| Derivada da Perda em relação a $z$ | $\\frac{\\partial L}{\\partial z} = \\frac{\\partial L}{\\partial y_{\\text{pred}}} \\cdot \\frac{\\partial y_{\\text{pred}}}{\\partial z}$ |\n", "| Derivada da Perda em relação a $w_1$ | $\\frac{\\partial L}{\\partial w_1} = \\frac{\\partial L}{\\partial z} \\cdot x_1$ |\n", "| Derivada da Perda em relação a $w_2$ | $\\frac{\\partial L}{\\partial w_2} = \\frac{\\partial L}{\\partial z} \\cdot x_2$ |\n", "| Derivada da Perda em relação a $b$ | $\\frac{\\partial L}{\\partial b} = \\frac{\\partial L}{\\partial z}$ |\n", "| Atualização de $w_1$ | $w_1 = w_1 - \\alpha \\cdot \\frac{\\partial L}{\\partial w_1}$ |\n", "| Atualização de $w_2$ | $w_2 = w_2 - \\alpha \\cdot \\frac{\\partial L}{\\partial w_2}$ |\n", "| Atualização de $b$ | $b = b - \\alpha \\cdot \\frac{\\partial L}{\\partial b}$ |\n", "\n", "**Código Final – Uma Época**\n", "\n", "```python\n", "import numpy as np\n", "\n", "# Entrada e saída desejada\n", "x = np.array([0.6, 0.3])\n", "y_real = 0.8\n", "\n", "# Pesos e bias iniciais\n", "w = np.array([0.4, -0.1])\n", "b = 0.2\n", "alpha = 0.1 # taxa de aprendizado\n", "\n", "# Função sigmoide\n", "def sigmoid(z):\n", " return 1 / (1 + np.exp(-z))\n", "\n", "# Forward\n", "z = np.dot(x, w) + b\n", "y_pred = sigmoid(z)\n", "loss = (y_pred - y_real)**2\n", "\n", "print(f\"Forward:\")\n", "print(f\" z = {z:.4f}\")\n", "print(f\" y_pred = {y_pred:.4f}\")\n", "print(f\" loss = {loss:.4f}\")\n", "\n", "# Backpropagation\n", "dL_dypred = 2 * (y_pred - y_real)\n", "dypred_dz = y_pred * (1 - y_pred)\n", "dL_dz = dL_dypred * dypred_dz\n", "\n", "dL_dw = dL_dz * x\n", "dL_db = dL_dz\n", "\n", "# Atualização dos pesos e bias\n", "w = w - alpha * dL_dw\n", "b = b - alpha * dL_db\n", "\n", "print(\"\\nBackpropagation:\")\n", "print(f\" dL_dypred = {dL_dypred:.4f}\")\n", "print(f\" dypred_dz = {dypred_dz:.4f}\")\n", "print(f\" dL_dz = {dL_dz:.4f}\")\n", "print(f\" dL_dw = {dL_dw}\")\n", "print(f\" dL_db = {dL_db:.4f}\")\n", "\n", "print(\"\\nPesos e bias atualizados:\")\n", "print(f\" w = {w}\")\n", "print(f\" b = {b:.4f}\")\n", "\n", "# Nova predição após uma época\n", "z = np.dot(x, w) + b\n", "y_pred = sigmoid(z)\n", "print(f\"\\nNova predição: {y_pred:.4f}\")\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **Exercício: Treinamento de um Neurônio com Múltiplas Épocas** \n", "\n", "**Objetivo:** \n", "Modificar o código existente para treinar um neurônio com **1000 épocas**, armazenando e plotando o erro e a acurácia ao longo do treinamento. \n", "\n", "**Tarefas:** \n", "1. **Implemente o loop de treinamento:** \n", " - Calcule `y_pred`, o erro (MSE) e atualize os pesos (`w`, `b`) via backpropagation em cada época. \n", " - Salve o erro e a acurácia em listas. \n", "\n", "2. **Plote os resultados:** \n", " - Gráfico do **erro** (azul) e **acurácia** (laranja) por época. \n", " - Inclua legendas, título e labels (`Época`, `Erro/Acurácia`). \n", "\n", "**Dica:** Use `matplotlib` ou outra biblioteca para visualização e modelagem do neurônio graficamente.\n", "\n", "**Exemplo de saída:** \n", "```\n", "Época 0: Erro = 0.0236 | Acurácia = 0.9764 \n", "Época 10: Erro = 0.0021 | Acurácia = 0.9979 \n", "... \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Regressão Linear com um Único Neurônio usando o Keras\n", "\n", "A **regressão linear** é uma técnica estatística fundamental usada para modelar a relação entre uma variável de entrada \\$x\\$ (independente) e uma variável de saída \\$y\\$ (dependente), assumindo que essa relação é linear. O modelo busca ajustar a melhor reta que aproxima os dados, representada pela equação:\n", "\n", "$$\n", "y = wx + b\n", "$$\n", "\n", "onde:\n", "\n", "* $y$ é o valor previsto,\n", "* $x$ é a variável de entrada,\n", "* $w$ é o **peso**, também chamado de **coeficiente angular** ou **inclinação da reta**,\n", "* $b$ é o **viés (bias)**, ou intercepto da reta com o eixo $y$.\n", "\n", "Essa equação é exatamente o que um neurônio realiza quando usamos uma **camada densa com uma unidade e sem função de ativação**. Isso torna possível implementar a regressão linear com apenas um neurônio!\n", "\n", "\n", "**Geração dos Dados**\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Gerar dados com ruído (reta com leve aleatoriedade)\n", "np.random.seed(42)\n", "x = np.linspace(0, 10, 100)\n", "noise = np.random.normal(0, 1, size=x.shape)\n", "y = 2 * x + 1 + noise * 2 # Reta base: y = 2x + 1 com ruído\n", "```\n", "\n", "\n", "**Modelo com Keras**\n", "\n", "```python\n", "from tensorflow.keras.models import Sequential\n", "from tensorflow.keras.layers import Dense\n", "from tensorflow.keras.optimizers import SGD\n", "from tensorflow.keras import Input\n", "from sklearn.metrics import mean_squared_error\n", "\n", "# Criar modelo com um único neurônio (sem função de ativação)\n", "model = Sequential([\n", " Input(shape=(1,)),\n", " Dense(units=1)\n", "])\n", "\n", "model.compile(optimizer=SGD(learning_rate=0.001), loss='mse')\n", "\n", "# Predições antes do treino\n", "y_pred_initial = model.predict(x).flatten()\n", "mse_before = mean_squared_error(y, y_pred_initial)\n", "\n", "# Treinamento\n", "history = model.fit(x, y, epochs=20, verbose=1)\n", "\n", "# Predições depois do treino\n", "y_pred = model.predict(x).flatten()\n", "mse_after = mean_squared_error(y, y_pred)\n", "```\n", "\n", "\n", "**Resultados e Visualizações**\n", "\n", "**Comparação Antes e Depois do Treinamento:**\n", "\n", "```python\n", "# Subplots: antes e depois do treino\n", "fig, axs = plt.subplots(1, 2, figsize=(14, 5))\n", "\n", "# Gráfico 1: Antes do Treinamento — Dados, Reta Verdadeira e Reta Inicial\n", "axs[0].scatter(x, y, color='blue', label='Dados com ruído')\n", "axs[0].plot(x, 2 * x + 1, 'r--', label='Reta verdadeira: y=2x+1')\n", "axs[0].plot(x, y_pred_initial, 'gray', label='Reta inicial')\n", "axs[0].set_title(f'Antes do Treinamento\\nMSE = {mse_before:.2f}')\n", "axs[0].set_xlabel('x')\n", "axs[0].set_ylabel('y')\n", "axs[0].legend()\n", "axs[0].grid(True)\n", "\n", "# Gráfico 2: Depois do Treinamento — Dados, Reta Verdadeira e Reta Aprendida\n", "axs[1].scatter(x, y, color='blue', label='Dados com ruído')\n", "axs[1].plot(x, 2 * x + 1, 'r--', label='Reta verdadeira')\n", "axs[1].plot(x, y_pred, 'g-', label='Reta aprendida')\n", "axs[1].set_title(f'Depois do Treinamento\\nMSE = {mse_after:.2f}')\n", "axs[1].set_xlabel('x')\n", "axs[1].set_ylabel('y')\n", "axs[1].legend()\n", "axs[1].grid(True)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Evolução do Erro e Acurácia Adaptada:**\n", "\n", "```python\n", "# Calcular acurácia adaptada como 1 - erro normalizado\n", "loss = np.array(history.history['loss'])\n", "acc = 1 - loss / np.max(loss)\n", "\n", "# Plotar erro e acurácia\n", "fig, axs = plt.subplots(1, 2, figsize=(14, 4))\n", "\n", "axs[0].plot(loss, label='Erro (MSE)', color='red')\n", "axs[0].set_title('Erro durante o Treinamento')\n", "axs[0].set_xlabel('Épocas')\n", "axs[0].set_ylabel('MSE')\n", "axs[0].grid(True)\n", "axs[0].legend()\n", "\n", "axs[1].plot(acc, label='Acurácia adaptada', color='green')\n", "axs[1].set_title('Acurácia (1 - erro normalizado)')\n", "axs[1].set_xlabel('Épocas')\n", "axs[1].set_ylabel('Acurácia')\n", "axs[1].grid(True)\n", "axs[1].legend()\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "\n", "**Parâmetros Aprendidos**\n", "\n", "```python\n", "# Extrair os pesos aprendidos (w e b)\n", "w, b = model.layers[0].get_weights()\n", "print(f'Parâmetros aprendidos:')\n", "print(f' Inclinação (w): {w[0][0]:.4f}')\n", "print(f' Intercepto (b): {b[0]:.4f}')\n", "```\n", "\n", "```text\n", "Parâmetros aprendidos:\n", " Inclinação (w): 1.9876\n", " Intercepto (b): 1.0342\n", "```\n", "\n", "Esses valores mostram que o neurônio conseguiu **aprender uma reta muito próxima da verdadeira** $y = 2x + 1$, mesmo com ruído nos dados — o que é uma ótima ilustração de como redes neurais podem resolver problemas simples de regressão linear.\n", "\n", "\n", "\n", "Plote com os valores extraídos:\n", "\n", "```python\n", "# Usar os pesos aprendidos para calcular a reta\n", "w_val = w[0][0]\n", "b_val = b[0]\n", "reta_aprendida = w_val * x + b_val\n", "\n", "# Plotar os dados e a reta aprendida manualmente\n", "plt.figure(figsize=(8, 5))\n", "plt.scatter(x, y, color='blue', label='Dados com ruído')\n", "plt.plot(x, 2 * x + 1, 'r--', label='Reta verdadeira: y=2x+1')\n", "plt.plot(x, reta_aprendida, 'g-', label=f'Reta aprendida: y={w_val:.2f}x+{b_val:.2f}')\n", "plt.xlabel('x')\n", "plt.ylabel('y')\n", "plt.title('Visualização da Reta Aprendida')\n", "plt.legend()\n", "plt.grid(True)\n", "plt.show()\n", "```\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Experimente uma RNA no seu navegador\n", "\n", "\"cnn\"\n", "\n", "- [TensorFlow Playground](https://playground.tensorflow.org/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Redes Neurais com Múltiplas Camadas\n", "\n", "As **redes neurais artificiais** ([RNAs](https://mriquestions.com/what-is-a-neural-network.html)), inspiradas no funcionamento do cérebro humano, são ferramentas poderosas no campo do aprendizado de máquina. Vamos explorar sua estrutura e funcionamento:\n", "\n", "- **Camada de Entrada (Input Layer):** Onde os dados brutos entram na rede, como os pixels de uma imagem. \n", "- **Camadas Ocultas (Hidden Layers):** Responsáveis por realizar transformações complexas nos dados, extraindo padrões relevantes por meio de cálculos e funções de ativação. \n", "- **Camada de Saída (Output Layer):** Produz a resposta final da rede, como a classe prevista para uma imagem.\n", "\n", "A seguir, veja a estrutura de uma rede neural simples:\n", "\n", "\n", "
\n", " \n", "
\n", "\n", "### Múltiplos Neurônios e Camadas em Redes Neurais\n", "\n", "Depois de entender como um único neurônio funciona — somando entradas ponderadas e aplicando uma função de ativação — podemos evoluir para redes com **múltiplos neurônios organizados em camadas densamente conectadas** (*fully connected layers*). Ao empilhar essas camadas, a rede é capaz de aprender **padrões cada vez mais complexos**, fundamentais para tarefas como o **reconhecimento de dígitos manuscritos**.\n", "\n", "Vamos utilizar o famoso conjunto de dados **[MNIST Digits](https://www.tensorflow.org/datasets/catalog/mnist)**, que contém imagens de **28x28 pixels** representando dígitos de 0 a 9 escritos à mão.\n", "\n", "**Como a rede processa essas imagens?**\n", "\n", "- **Camada de Entrada:** Cada imagem 28×28 contém 784 pixels. Esses valores (em tons de cinza entre 0 e 255) são geralmente **normalizados para o intervalo [0, 1]**, o que facilita o treinamento da rede.\n", "\n", "- **Primeira Camada Oculta:** Supondo 128 neurônios, cada um realiza uma combinação linear dos 784 valores de entrada, seguida de uma função de ativação (como ReLU). Isso gera 128 ativações que representam padrões iniciais.\n", "\n", "- **Segunda Camada Oculta:** Com 64 neurônios, ela processa as ativações anteriores para extrair **padrões mais abstratos**, como contornos, formas e estruturas típicas de cada dígito.\n", "\n", "- **Camada de Saída:** Possui 10 neurônios, um para cada classe (dígitos de 0 a 9). Utiliza a função **softmax** para produzir **probabilidades** associadas a cada classe. A classe com maior probabilidade é a predição da rede.\n", "\n", "Durante o treinamento, os **pesos da rede são ajustados** para minimizar o erro entre a saída prevista e a resposta correta. Isso é feito através do algoritmo de **retropropagação do erro** (*backpropagation*) e um **otimizador** (como o gradiente descendente).\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementando Redes Neurais com Keras e TensorFlow\n", "\n", "Com a API **Keras**, integrada ao **[TensorFlow](https://www.tensorflow.org/?hl=pt-br)**, é possível montar e treinar redes neurais de forma simples e eficiente. Veja abaixo um exemplo usando o clássico conjunto de dados **MNIST**, que contém imagens de dígitos manuscritos (0 a 9).\n", "\n", "**Importações, Dados e Normalização**\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from tensorflow.keras.datasets import mnist\n", "from tensorflow.keras.models import Sequential\n", "from tensorflow.keras.layers import Input, Flatten, Dense\n", "from tensorflow.keras.optimizers import Adam\n", "\n", "# Carrega o dataset MNIST\n", "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n", "\n", "# Normaliza os dados para o intervalo [0, 1]\n", "x_train = x_train.astype('float32') / 255.0\n", "x_test = x_test.astype('float32') / 255.0\n", "```\n", "\n", "**Arquitetura da Rede Neural**\n", "\n", "```python\n", "model = Sequential([\n", " Input(shape=(28, 28)),\n", " Flatten(),\n", " Dense(128, activation='relu'),\n", " Dense(64, activation='relu'),\n", " Dense(10, activation='softmax')\n", "])\n", "```\n", "\n", "Explicando cada camada:\n", "\n", "* **`Input(shape=(28, 28))`**: define o formato das imagens de entrada (28x28 pixels).\n", "* **`Flatten()`**: transforma a imagem 2D em um vetor 1D com 784 valores (28 × 28), necessário para conectar à camada densa.\n", "* **`Dense(128, activation='relu')`**: primeira camada oculta com 128 neurônios. A função **ReLU** (*Rectified Linear Unit*) permite que apenas valores positivos sejam ativados, acelerando o aprendizado.\n", "* **`Dense(64, activation='relu')`**: segunda camada oculta com 64 neurônios, permitindo a aprendizagem de representações mais complexas.\n", "* **`Dense(10, activation='softmax')`**: camada de saída com 10 neurônios, um para cada classe. A função **softmax** transforma os valores em probabilidades, que somam 1.\n", "\n", "**Compilando o Modelo**\n", "\n", "```python\n", "model.compile(optimizer=Adam(),\n", " loss='sparse_categorical_crossentropy',\n", " metrics=['accuracy'])\n", "```\n", "\n", "* **`optimizer='adam'`**: define o algoritmo usado para atualizar os pesos.\n", "\n", " * O **Adam** combina gradiente descendente com momentum e adaptação da taxa de aprendizado.\n", "* **`loss='sparse_categorical_crossentropy'`**: mede o erro entre a saída da rede e a resposta correta.\n", "\n", " * Usamos essa função porque os rótulos são inteiros (0 a 9), e não vetores *one-hot*.\n", "* **`metrics=['accuracy']`**: define **acurácia** como métrica de desempenho, isto é, a proporção de acertos da rede.\n", "\n", "**Treinamento do Modelo**\n", "\n", "```python\n", "model.fit(x_train, y_train, epochs=5)\n", "```\n", "\n", "* O método **`fit()`** realiza o **treinamento da rede**:\n", "\n", " * A rede passa por **5 épocas** (iterações completas sobre o conjunto de treino).\n", " * Os pesos são ajustados com base no erro observado.\n", " * O **backpropagation** é aplicado a cada lote de dados, ajustando os pesos para melhorar as previsões.\n", "\n", "**Avaliação do Modelo**\n", "\n", "```python\n", "test_loss, test_acc = model.evaluate(x_test, y_test)\n", "print(f\"Precisão no teste: {test_acc:.2%}\")\n", "```\n", "\n", "* **`evaluate()`** calcula a **perda** e a **acurácia** nos dados de teste (não vistos durante o treino).\n", "* Serve como uma estimativa de **generalização** do modelo para dados reais.\n", "\n", "**Visualização de Amostras e Inferência**\n", "\n", "**Visualizando dados de treino:**\n", "\n", "```python\n", "plt.figure(figsize=(10, 2))\n", "for i in range(10):\n", " plt.subplot(1, 10, i + 1)\n", " plt.imshow(x_train[i], cmap='gray')\n", " plt.title(f\"Label: {y_train[i]}\")\n", " plt.axis('off')\n", "plt.suptitle(\"Amostras do Conjunto de Treino\")\n", "plt.show()\n", "```\n", "\n", "**Inferência com imagens de teste:**\n", "\n", "```python\n", "# Faz inferência com as 10 primeiras imagens de teste\n", "predictions = model.predict(x_test[:10])\n", "\n", "plt.figure(figsize=(10, 2))\n", "for i in range(10):\n", " plt.subplot(1, 10, i + 1)\n", " plt.imshow(x_test[i], cmap='gray')\n", " pred_label = np.argmax(predictions[i])\n", " true_label = y_test[i]\n", " plt.title(f\"P:{pred_label}\\nT:{true_label}\", fontsize=8)\n", " plt.axis('off')\n", "plt.suptitle(\"Inferência: P = Previsto, T = Verdadeiro\")\n", "plt.show()\n", "```\n", "\n", "**Matriz de Confusão**\n", "\n", "A matriz de confusão mostra, para cada classe verdadeira, como o modelo classificou as amostras. É uma excelente ferramenta para **avaliar o desempenho detalhado do classificador**.\n", "\n", "```python\n", "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", "\n", "# Gera previsões para o conjunto de teste\n", "y_pred = model.predict(x_test)\n", "y_pred_labels = np.argmax(y_pred, axis=1)\n", "\n", "# Gera a matriz de confusão\n", "cm = confusion_matrix(y_test, y_pred_labels)\n", "\n", "# Exibe a matriz graficamente\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=range(10))\n", "disp.plot(cmap=plt.cm.Blues, xticks_rotation=45)\n", "plt.title(\"Matriz de Confusão - MNIST\")\n", "plt.show()\n", "```\n", "\n", "**Explicação**\n", "\n", "* **`confusion_matrix(y_true, y_pred)`**: compara os rótulos verdadeiros com os previstos.\n", "* **Diagonal principal**: acertos (previsão == verdadeiro).\n", "* **Fora da diagonal**: erros (confusões entre dígitos).\n", "* Com o `ConfusionMatrixDisplay`, o gráfico mostra claramente **quais dígitos estão sendo confundidos**, por exemplo, se o modelo costuma confundir o 4 com o 9.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercício Prático: Classificação de Imagens com o Dataset [Fashion MNIST](https://github.com/zalandoresearch/fashion-mnist)\n", "\n", "Neste exercício, você vai implementar e treinar **sua própria rede neural** para classificar imagens de roupas utilizando o conjunto de dados **Fashion MNIST**, com a API `Sequential` do Keras (TensorFlow).\n", "\n", "O Fashion MNIST contém imagens em tons de cinza com resolução 28x28 pixels, organizadas em 10 categorias de roupas como camisetas, vestidos, tênis, bolsas, entre outros. É um dos datasets mais utilizados para aprendizado e experimentação com redes neurais simples.\n", "\n", "Você deve começar carregando o dataset com `tf.keras.datasets.fashion_mnist.load_data()` e separando os dados de treino e teste (`x_train`, `y_train`, `x_test`, `y_test`). Em seguida, normalize as imagens dividindo os valores dos pixels por 255, para que os dados fiquem no intervalo entre 0 e 1.\n", "\n", "Visualize algumas imagens de treino para entender melhor o conteúdo do conjunto de dados. Você pode usar o seguinte código para isso:\n", "\n", "```python\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from tensorflow.keras.datasets import fashion_mnist\n", "\n", "# Carrega o conjunto de dados Fashion MNIST\n", "(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()\n", "\n", "# Rótulos das classes\n", "class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',\n", " 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']\n", "\n", "# Normaliza os pixels para o intervalo [0, 1]\n", "x_train, x_test = x_train / 255.0, x_test / 255.0\n", "\n", "# Exibe 10 imagens de treino com seus respectivos rótulos\n", "plt.figure(figsize=(10, 2))\n", "for i in range(10):\n", " plt.subplot(1, 10, i + 1)\n", " plt.imshow(x_train[i], cmap='gray')\n", " plt.title(class_names[y_train[i]], fontsize=8)\n", " plt.axis('off')\n", "plt.suptitle(\"Amostras do Fashion MNIST\")\n", "plt.show()\n", "```\n", "\n", "Depois disso, implemente uma rede neural com a API `Sequential`. Você poderá escolher:\n", "\n", "* Quantas camadas ocultas usar\n", "* Quantos neurônios em cada camada\n", "* Quais funções de ativação utilizar (como `relu`, `sigmoid`, `tanh`)\n", "\n", "A camada de saída deve conter **10 neurônios com ativação `softmax`**, correspondentes às 10 categorias de roupas.\n", "\n", "Compile o modelo com a função de perda `sparse_categorical_crossentropy` e a métrica `accuracy`. Experimente **pelo menos dois otimizadores** entre os seguintes e compare seus desempenhos:\n", "\n", "* `adam`\n", "* `sgd`\n", "* `rmsprop`\n", "* `adagrad`\n", "\n", "Treine seu modelo com `model.fit()` por no mínimo 5 épocas. Observe e anote os valores de perda e acurácia durante o treinamento.\n", "\n", "Depois do treinamento, avalie o desempenho do modelo no conjunto de teste usando `model.evaluate()` e imprima a acurácia final. Compare os resultados obtidos com cada otimizador testado.\n", "\n", "Para entender melhor onde o modelo acerta ou erra, gere uma **matriz de confusão**. Você pode usar o código abaixo:\n", "\n", "```python\n", "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", "\n", "# Previsões no conjunto de teste\n", "y_pred = model.predict(x_test)\n", "y_pred_labels = np.argmax(y_pred, axis=1)\n", "\n", "# Matriz de confusão\n", "cm = confusion_matrix(y_test, y_pred_labels)\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)\n", "disp.plot(cmap=plt.cm.Blues, xticks_rotation=45)\n", "plt.title(\"Matriz de Confusão - Fashion MNIST\")\n", "plt.show()\n", "```\n", "\n", "Como tarefa adicional, monte um gráfico comparando a **acurácia** e a **função de perda** ao longo das épocas para cada otimizador. Isso ajudará a visualizar e justificar qual otimizador teve melhor desempenho em termos de velocidade de aprendizado e precisão final.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Referências e Conteúdo Extra\n", "\n", "* **Frameworks**\n", "\n", " * [TensorFlow (com Keras)](https://www.tensorflow.org/)\n", " * [PyTorch (by Meta)](https://pytorch.org/)\n", "\n", "* **Livro(s)**\n", "\n", " * [Dive into Deep Learning (D2L.ai)](https://d2l.ai/)\n", "\n", "* **Vídeo(s)**\n", "\n", " * [The Stilwell Brain](https://youtu.be/rA5qnZUXcqo?si=B7kWj9g7vE_RzC6R)\n", " * [The First Neural Networks](https://youtu.be/e5dVSygXbAE?si=BeWj4ntGVg3ylTCu)\n", " * [Neural Network In 5 Minutes](https://youtu.be/bfmFfD2RIcg?si=DC39QaO-iLVltMAz)\n", " * [Mas o que é uma rede neural?](https://youtu.be/aircAruvnKk?si=_A25G40fOBenAaEE)\n", " * [Descida do gradiente: como as redes neurais aprendem](https://youtu.be/IHZwWFHWa-w?si=bTy-h1I7gI2lRSoZ)\n" ] } ], "metadata": { "colab": { "provenance": [], "toc_visible": 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.9.20" } }, "nbformat": 4, "nbformat_minor": 4 }