# Aprendizagem e redes neuronais

Árvores de decisão com entropia, matrizes de confusão e um perceptrão treinado em duas épocas.

Página: https://resumos.rgo.pt/cadeiras/ia/aprendizagem-e-redes/

Nas páginas anteriores, o conhecimento vinha escrito por ti: regras, probabilidades, heurísticas. Na **aprendizagem computacional**, o programa extrai o conhecimento dos dados. Há três regimes: **supervisionada** (dados com resposta certa, para classificar ou prever), **não supervisionada** (dados sem resposta, para agrupar) e **por reforço** (aprende por tentativa com recompensas). Esta página fica na supervisionada, com um dataset de 12 animais resolvido de três maneiras.

## O dataset

Doze animais com dois atributos binários, penas e voa, e a classe ave ou mamífero. Aves: pardal (penas, voa), águia (penas, voa), pato (penas, voa), pinguim (penas, não voa), avestruz (penas, não voa), galinha (penas, não voa). Mamíferos: morcego (sem penas, voa), golfinho, cão, gato, cavalo e baleia (todos sem penas e sem voo).

## Árvore de decisão com entropia

Uma **árvore de decisão** pergunta atributos por ordem e classifica na folha. A pergunta de cada nó é a que mais reduz a incerteza, medida pela **entropia** $H = -\sum p_i \log_2 p_i$. Na raiz há 6 aves e 6 mamíferos: $H = -(0{,}5 \log_2 0{,}5 + 0{,}5 \log_2 0{,}5) = 1$ bit, incerteza máxima.

Testa o atributo penas. O ramo “tem penas” tem 6 aves e 0 mamíferos ($H = 0$); o ramo “sem penas” tem 0 aves e 6 mamíferos ($H = 0$). A entropia ponderada depois da pergunta é 0, por isso o **ganho de informação** é $1 - 0 = 1$ bit, o máximo possível. Compara com o atributo voa: o ramo “voa” tem 3 aves e 1 mamífero ($H \approx 0{,}81$), o ramo “não voa” tem 3 aves e 5 mamíferos ($H \approx 0{,}95$), e a média ponderada dá $(4/12) \times 0{,}81 + (8/12) \times 0{,}95 \approx 0{,}91$, ou seja, ganho de só $0{,}09$. A árvore pergunta penas primeiro e classifica tudo na perfeição com profundidade 1.

A lição não é que penas seja sempre a resposta, é o método: calcula a entropia antes e depois de cada atributo candidato e escolhe o maior ganho. Árvores demasiado fundas decoram o treino e falham no novo (**sobreajuste**); limita-se a profundidade ou exige-se um mínimo de exemplos por folha.

![Árvore de decisão de profundidade 1: a raiz pergunta se tem penas; o ramo sim classifica 6 aves e o ramo não classifica 6 mamíferos.](https://resumos.rgo.pt/cadeiras/ia/aprendizagem-e-redes/figura-1.svg)

As entropias da conta acima, executáveis abaixo:

```python
import math

def h(a, b):
    e = 0.0
    for n in (a, b):
        if n:
            p = n / (a + b)
            e -= p * math.log2(p)
    return e

print("raiz:", h(6, 6))
print("penas:", h(6, 0), h(0, 6))
print("voa:", round(h(3, 1), 2), round(h(3, 5), 2))
print("ganho voa:", round(1 - (4 / 12 * h(3, 1) + 8 / 12 * h(3, 5)), 2))
```

Isto escreve `raiz: 1.0`, `penas: 0.0 0.0`, `voa: 0.81 0.95` e `ganho voa: 0.09`: o atributo penas separa tudo com ganho máximo, voa quase não ajuda.

## Matriz de confusão

Para avaliar um classificador, conta acertos e erros por classe na **matriz de confusão**. Testa a regra ingénua “voa, logo é ave” em 6 animais (pardal, águia e pinguim aves; morcego, cão e golfinho mamíferos):

| Real \\ Previsto | Ave | Mamífero |
| --- | --- | --- |
| Ave | 2 (pardal, águia) | 1 (pinguim) |
| Mamífero | 1 (morcego) | 2 (cão, golfinho) |

Exatidão: 4 em 6. O pinguim é um **falso negativo** (ave prevista mamífero) e o morcego um **falso positivo** (mamífero previsto ave). Repara como a exatidão sozinha engana: se 95 por cento dos animais fossem mamíferos, prever sempre mamífero dava 95 por cento de exatidão sem aprender nada. É por isso que se olha para a matriz e não só para o número global.

## Um perceptrão em duas épocas

O **perceptrão** é o neurónio original: soma pesos vezes entradas mais viés, e devolve uma classe conforme o sinal. Treina-se corrigindo erros: quando erra um exemplo, soma (ou subtrai) as entradas aos pesos. Segue o treino com taxa 1, pesos a zeros e viés a zero, nos pontos $x_1 = (1, 0)$ da classe +1, $x_2 = (0, 1)$ da classe -1 e $x_3 = (2, 1)$ da classe +1:

*   Época 1, $x_1$: saída $0 \ge 0$, prevê +1, correto. Sem alteração.
*   Época 1, $x_2$: saída $0$, prevê +1, mas é -1. Atualiza: pesos $(0, 0) - (0, 1) = (0, -1)$, viés $-1$.
*   Época 1, $x_3$: saída $(0)(2) + (-1)(1) - 1 = -2$, prevê -1, mas é +1. Atualiza: pesos $(0, -1) + (2, 1) = (2, 0)$, viés $0$.
*   Época 2, $x_1$: saída $2 \ge 0$, prevê +1, correto.
*   Época 2, $x_2$: saída $0$, prevê +1, mas é -1. Atualiza: pesos $(2, 0) - (0, 1) = (2, -1)$, viés $-1$.
*   Época 2, $x_3$: saída $(2)(2) + (-1)(1) - 1 = 2 \ge 0$, prevê +1, correto.

Pesos finais $(2, -1)$ com viés $-1$: confirma os três pontos, $x_1$ dá 2, $x_2$ dá $-2$, $x_3$ dá 2, todos do lado certo.

![Diagrama do perceptrão treinado: as entradas x1 e x2 chegam ao neurónio com pesos 2 e menos 1, o viés menos 1 entra por baixo, e o neurónio devolve a classe.](https://resumos.rgo.pt/cadeiras/ia/aprendizagem-e-redes/figura-2.svg)

O treino acima, executável: corre e confirma que escreve `[2, -1] -1`.

```python
w, b = [0, 0], 0
dados = [([1, 0], 1), ([0, 1], -1), ([2, 1], 1)]
for epoca in range(2):
    for x, y in dados:
        saida = w[0] * x[0] + w[1] * x[1] + b
        previsto = 1 if saida >= 0 else -1
        if previsto != y:
            w = [w[0] + y * x[0], w[1] + y * x[1]]
            b = b + y
print(w, b)
```

Se o teu código der outro resultado, revê a convenção do sinal na saída exatamente zero, que é onde quase toda a gente diverge. Os exemplos de Python seguem o estilo de [Fundamentos da Programação](https://resumos.rgo.pt/cadeiras/fp/).

## De um neurónio a uma rede

Uma rede neuronal empilha muitos perceptrões em camadas e treina-os com gradiente em vez de correções discretas, mas a ideia é a mesma: ajustar parâmetros até os exemplos ficarem do lado certo da fronteira. A **generalização**, acertar em dados novos e não só no treino, avalia-se com a matriz da secção anterior em dados que o modelo nunca viu.

O percetrão usa degrau e correções discretas. Uma rede **multicamada** (MLP) usa ativações suaves como a sigmoide $\sigma(z) = 1 / (1 + e^{-z})$ e minimiza o erro quadrático $E = \frac{1}{2}(t - y)^2$ por **retropropagação**: calcula como cada peso contribui para o erro e corrige-o na direção oposta. Segue um passo completo numa rede 2-2-1, isto é, 2 entradas, 1 neurónio escondido e 1 saída, com entrada $x = (1, 0)$, alvo $t = 1$ e taxa 1.

Pesos iniciais: escondido $w_h = (0{,}5, -0{,}5)$ com viés $0$; saída $w_o = 0{,}5$ com viés $0$. Propagação para a frente: $z_h = 0{,}5$, $a_h = \sigma(0{,}5) \approx 0{,}622$; $z_o = 0{,}5 \times 0{,}622 \approx 0{,}311$, $y = \sigma(0{,}311) \approx 0{,}577$. Erro $E = \frac{1}{2}(1 - 0{,}577)^2 \approx 0{,}089$.

Retropropagação, da saída para a entrada. Na saída, $\delta_o = (y - t)\,y\,(1 - y) \approx (0{,}577 - 1) \times 0{,}577 \times 0{,}423 \approx -0{,}103$. Gradientes: $\partial E / \partial w_o = \delta_o \times a_h \approx -0{,}064$ e $\partial E / \partial b_o = \delta_o \approx -0{,}103$. Atualiza: $w_o = 0{,}5 + 0{,}064 = 0{,}564$ e $b_o = 0{,}103$. No escondido, $\delta_h = \delta_o \times w_o \times a_h \times (1 - a_h) \approx -0{,}0121$, por isso $w_{h1} = 0{,}5 + 0{,}0121 = 0{,}5121$ (o $w_{h2}$ não mexe porque $x_2 = 0$) e $b_h = 0{,}0121$.

Confirma que o passo ajudou: com os pesos novos, $y \approx 0{,}612$ e $E \approx 0{,}075$, abaixo dos 0,089 iniciais. Um passo de gradiente é isto: medir a culpa de cada peso no erro e empurrá-lo um pouco na direção que baixa o erro. Repetir milhares de vezes sobre muitos exemplos é o treino de uma rede neuronal.

[Vídeo: But what is a neural network? (3Blue1Brown)](https://www.youtube.com/watch?v=aircAruvnKk)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=aircAruvnKk)

## Aprender por tentativa: reforço

Na aprendizagem por reforço não há respostas certas nem classes: há estados, ações e **recompensas**, e o agente aprende uma **política** (o que fazer em cada estado) por tentativa. O **Q-learning** guarda $Q(estado, ação)$, a recompensa futura esperada, e atualiza-a com $Q \leftarrow Q + \alpha\,[r + \gamma \max Q' - Q]$, onde $\alpha$ é a taxa e $\gamma$ desconta o futuro.

Grelha 2 por 2 com objetivo em (1, 1): entrar no objetivo dá $+10$, o resto dá 0. Taxa $\alpha = 0{,}5$, desconto $\gamma = 0{,}9$, tudo a zeros. Primeiro episódio, de (0, 1) para baixo até ao objetivo: $Q((0,1), Baixo) \leftarrow 0 + 0{,}5 \times [10 + 0] = 5$. Segundo episódio, de (0, 0) para a direita até (0, 1): $Q((0,0), Direita) \leftarrow 0 + 0{,}5 \times [0 + 0{,}9 \times 5] = 2{,}25$. A recompensa propaga-se para trás, uma casa por episódio: primeiro a casa vizinha do objetivo aprende 5, depois a seguinte aprende 2,25 a partir dela. É assim que o agente aprende o caminho sem nunca lhe mostrarem um exemplo correto.

## Para saber mais

*   [Simulador de redes neuronais no navegador](http://playground.tensorflow.org/): escolhe camadas e ativações e vê a fronteira de decisão a formar-se.
*   [Documentação de árvores de decisão do scikit-learn](https://scikit-learn.org/stable/modules/tree.html): critérios de divisão e poda contra o sobreajuste.
*   Entropia contada devagar, com exemplos:
    
    [Vídeo: Entropy Clearly Explained (StatQuest)](https://www.youtube.com/watch?v=YtebGVx-Fxw)
    
    A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=YtebGVx-Fxw)
