---
# System prepended metadata

title: Apontamentos IC 1ª Freq
tags: [IC, Teórica]

---

# Apontamentos IC 1ª Freq
###### tags: `IC` `Teórica`
# Aula 2
> [name=pdpm19] Livro sec. 2
## Neurónio Artificial
### Modelo do Neurónio Artificial
![](https://i.imgur.com/TCryRkg.png)

- Função $g: \mathbb{R}^n -> \mathbb{R}$, que a cada vetor $x$, de dimensão $n$, de entrada faz corresponder um real $y$;
- Conta ainda com um **vetor de pesos** $[w0w1...wn]$ e da **função de ativação** $f(.)$.

### Cálculo da Saída do Neurónio
- Os valores de entrada $x$, são alvo de uma soma pesada
    - $s = \sum_{i=0}^{n} x_{i}w_{i}$, tendo em conta que $x_{0} = 1$
- De seguida esse valor passa por uma função de ativação
    - $y = f(s)$

### Funções de Ativação
Existem várias, que podem assumir várias formas, entre elas:
- linear $f(s) = \beta s$
- degrau $f(s) = (\beta2, s >= 0)$ || $(\beta1, s < 0)$
- rampa $f(s) = (\beta1, s >= \beta)$ || $(s, |s| < \beta)$ ||$(-\beta1, s <= -\beta)$
- sigmoid $f(s) = \frac{1}{1+exp(- \lambda s)}$
- tangente hiperbólica $f(s) = \frac{exp(\lambda s)-exp(-\lambda s)}{exp(\lambda s)+\exp(-\lambda s)}$
- ReLu $f(s) = max(0, s)$
- Por norma $\beta1 = 0$ e $\beta2 = 1$
- Por norma as funções de ativação são **monótonas crescentes**, e à exceção da função de ativação linear: $f(-\infty)=-1 ||f(-\infty)=0$ e $f(\infty)=1$ 
![](https://i.imgur.com/D0rQOCG.png)

### Capacidade de Discriminância de um Neurónio
- Pontos do espaço de entrada que sejam **linearmente separáveis**;
- Um neurónio consegue implementar um **hiperplano** (**fronteira de decisão**) que separa os pontos em que a saída é $>=0$ daqueles em que a saída é $<0$ 
![](https://i.imgur.com/QSA8Ztc.png)

## Regras de Aprendizagem de um Neurónio
### Aprendizagem
- Para se encontrar os pesos são necessários **dados** relativos ao problema
- Os dois tipos de aprendizagem são:
    - **Supervisionada**: os pontos contêm informação sobre a classe a que pertencem;
    - **Não Supervisionada**: os pontos não contêm informação relativa à sua classe.
- Os pontos são: $P_{1}=(peso_{1}, altura_{1}, classe_{P_{1}})$, para o exemplo acima representado pela figura
- De uma forma mais genérica: $P_{i}=(x_{i,1},...., x_{i,n}, d_{i})$, onde os valores $x_{i,1},...., x_{i,n}$ são as **características** e $d_{i}$ a verdadeira **classe** do ponto $i$

### Descida do Gradiente
- Forma mais comum de aprendizagem utilizada em redes neuronais;
- Necessita de uma **função de custo** que permite saber quão próximo da verdadeira classe ficou a saída do neurónio;
- A mais comum é o quadrado do erro, _squared error_: $e_{j} = (d_{j} - y_{j})^2$, onde $d_{j}$ é a classe verdadeira e $y_{j}$ a saída do neurónio para o ponto $j$;
- A ideia consiste em encontrar os pesos que **minimizam o erro**, procurando no espaço dos pesos ao longo do sentido contrário ao do gradiente.

![](https://i.imgur.com/wtrVu7L.png)


- Dado um ponto $j$:
    - cada peso $w_{i}$ na iteração $t$ é ajudaste de acordo com \begin{equation} w_{i}(t) = w_{i}(t-1) + \Delta w_{i}(t) \end{equation} onde \begin{equation} \Delta w_{i}(t) = \eta (-\frac{\sigma e_{j}}{\sigma w_{i}}) \end{equation} e \begin{equation} \frac{\sigma e_{j}}{\sigma w_{i}} = -2(d_{j} - y_{j})\frac{\sigma f}{\sigma s_{j}}X_{j,i}\end{equation}
    - $\eta$ é **taxa de aprendizagem**, $X_{j,i}$ é a característica $i$ do ponto $j$ e $f$ é a função de ativação
    - Para que $\frac{\sigma f}{\sigma s_{j}}$ não anule esta derivada e percamos a informação relativa ao gradiente, **$f$ não pode ser o degrau**

- Se utilizarmos a funão de ativação sigmoid, temos:
    - $\Delta w_{i}(t) = 2\eta(d_{j} - y_{j})\lambda f(s_{j})(1- f(s_{j}))X_{j,i} = 2\eta(d_{j} - y_{j})\lambda y_{j}(1-y_{j})X_{j,i}$

### Widrow-Hoff
- Esta regra, que considera a descida do gradiente a função identidade, $f(s_{j}) =  s_{j}$, onde temos:
    - $\frac{\sigma f}{\sigma s_{j}} = 1$
    - $\Delta w_{i}(t) = 2\eta(d_{j} - y_{j})X_{j,i}$
- Também é conhecido por _Least Mean Squares_ ou _Delta Rule_. 

---
# Aula 3
> [name=pdpm19] Livro sec. 3
## Perceptrão (_Perceptron_)
- Chama-se perceptrão a um tipo de rede neuronal em que os neurónios estão organizados numa única camanda.
![](https://i.imgur.com/WorBfNy.png)


## _Multi Layer Perceptron_ (MLP)
- MLP ou Percetrão multicamada, é a junção de vários perceptrões, ou seja, uma junção de vários neurónios em várias camadas;
- Sendo assim a 1ª camada trabalha com a informação bruta, mas as restantes camadas, trabalham com a saída das camandas anteriores, um _feedforward neural network_ (FFNN);
- Permite a aprendizagem de problemas não lineares;
- O treino é feito através da retropropagação do erro (_error backpropagation_).

![](https://i.imgur.com/uK0p7NR.png)


### Arquitetura _MLP_
- Todas as camadas, menos a última são chamadas de camadas escondidas;
- A última camada, tem, ironicamente o nome de última camada, ou camada de saída;
- A **capacidade de aprendizagem** de uma rede, são o **nº de camadas escondidas**, devido à aprendizagem residir nos pesos;
- O nº de entradas e saídas são intrínsecos ao problema a resolver.
- Na imagem abaixo, temos um MLP com 2 camadas escondidas, 1 camada de entrada e 1 de saída.
![](https://i.imgur.com/XBhp0Aj.png)


### Propriedades _MLP_
- Uma rede com **uma camada escondida, consegue resolver funções contínuas diferenciáveis**;
- Uma rede com **2 camadas escondidas, consegue resolver qualquer função**, mas pode não ser a mais eficiente;
- Nos dias de hoje, estudam-se redes _FFNN_ com mais de 2 camadas escondidas, devido a poderem tornar a aprendizagem mais eficiente.

### Aprendizagem _MLP_
#### Retropropagação do erro
- Tem 2 fases:
    1. **Passa os dados para a frente**: calcula os valores nas saídas da rede;
    2. **Passa o erro para trás (retropropagação)**, propaga o erro vindo da camada de saída até à camada de entrada e no processo atualiza os pesos da rede em função deste erro.

#### Tipos de aprendizagem
- Permite os seguintes tipos de aprendizagem supervisionada:
    - Aprendizagem **estocástica ou online**, onde a **atualização dos pesos é feita para cada ponto**;
    - Aprendizagem por **lotes (_batch_) ou offline**, sendo que a **atualização dos pesos só é feita no fim de cada época** (Todo o conjunto de treino é uma lote);
    - Aprendizagem **mini-lotes (_mini-batch_)**, sendo um misto das anteriores, pois a **atualização dos pesos é feita quando todos os pontos do mini-lote passam pela rede**. (Por norma divide-se o conjunto de treino em vários lotes);
- Obs:
    - Do ponto de vista de computacional, é mais pesado de fazer o ajuste à rede na aprendizagem estocástica pois quando temos um conjunto de treino muito grande (fazemos muitos ajustes), ao invés dos lotes que só fazem 1 ajuste por iteração;
    - A aprendizagem estocástica permite escapar mais facilmente a minimos locais;
    - Por norma recorre-se mais vezes à mini-lotes por aproximar-se dos bons resultados da estocástica, mas evitando demorar-se demasiado tempo.

### Algoritmo da Retropropagação do Erro
Este algoritmo é para treino em lotes, visto que o erro só é atualizado no fim de cada iteração (época). 
1. **Inicializar os pesos**, com valores entre -0.1 e 0.1;
2. **Calcular as saídas e os erros**
    - na primeira camada 
    \begin{equation} s_{j} = w_{0j} + \sum_{i}{w_{ij}X{i}} \end{equation} 
    - restantes camadas
    \begin{equation} s_{j} = w_{0j} + \sum_{i \in camanda anterior}{w_{ij}y{i}} \end{equation}
    
    \begin{equation} y_{j} = f(s_{j}) \end{equation}
    - camada de saída
    \begin{equation} e_{j} = f'(s_{j}) = \frac{\sigma c}{\sigma y_{j}} \end{equation}
    - restantes camadas
    \begin{equation} e_{j} = f'(s_{j}) = \sum_{p \in camanda seguinte}{w_{jp}e_{p}} \end{equation}
3. **Atualizar os pesos**
    \begin{equation}w_{ij} <- w_{ij} + \Delta w_{ij} \end{equation}
    - primeira camada
    \begin{equation} \Delta w_{ij} = - \frac{\eta}{N} \sum_{x_{i} \in X}{x_{i}e_{j}}\end{equation}
    - restantes camadas
    \begin{equation} \Delta w_{ij} = - \frac{\eta}{N} \sum_{y_{i} \in camada anterior}{y_{i}e_{j}}\end{equation}
4. **Verificar as condições de paragem**

#### Condições de paragem
- Número fixo de épocas;
- Atribuição de um valor pré-definido de erro (_loss_);

#### Algoritmo cont.
Necessitamos ainda de escolher as funções de ativação e custo $f$ e $c$
- Escolhendo a função de ativação sigmoid $f'(x) = f(x).(1- f(x))$;
- A função de custo sendo o erro quadrático médio (MSE _Mean Squared Error_), onde $m$ é o número de saídas da rede (usada para regressão):
\begin{equation}c(d(x), y(x)) = \frac{1}{m} \sum_{i=1}^{m}(d_{i} - y{i})^2 \end{equation}
- Logo,
\begin{equation} \frac{\sigma c}{\sigma y_{j}} = - \frac{2}{m}(d_{j} - y_{j}) \end{equation}

## Classificação
- É um tipo de problema em que pretendemos fazer corresponder a cada **ponto de entrada uma etiqueta chamada de classe**;
- Consiste em que a rede consiga **aprender, a partir do conjunto de treino, a distinguir os pontos de cada classe**, para que assim possa **classificar corretamente pontos não pertencentes ao conjunto de treino (generalizar)**;

### MLP na Classificação
- A ideia é que o MLP crie **fronteiras de decisão entre as diferentes classes do problema**;
- **Cada linha** nas fronteiras corresponde a **um neurónio da camada escondida**;
- No exmplo abaixo temos 10 fronteiras de decisão, logo são necessários 10 neurónios na camada escondida;
![](https://i.imgur.com/lWjlQg3.png)
- Notando que ainda existem pontos mal classificados, iriamos necessitar de pelo menos mais 2 neurónios.
![](https://i.imgur.com/FcxM1au.png)

## Regressão
- É um tipo de problema em que se **efetua a aproximação de funções**, pois a cada **ponto na entrada** da rede pretendemos que a **saída seja a mais próxima possível de um valor dado por uma função**;
- A **função a apromixar** é muitas vezes **desconhecida** em termos analíticos, pois só se possui valores de amostras em determinados pontos;
- Os neurónios da camada escondida neste caso **servem para lidar com os pontos de inflexão da função a aproximar**, pelo que são **precisos tantos quantos os pontos de inflexão mais um**.

### MLP na Regressão
- Neste exemplo, deveriam de ser utilizados 5 neurónios na camda escondida, visto existirem 4 pontos de inflexão (são pontos na função, onde o sinal é trocado)
![](https://i.imgur.com/mcjSnrg.png)

## Regularização
- É uma alteração feita ao algortimo de treino ou à arquitetura da rede para **reduzir o erro de generalização**;
- Pode ser feita de várias formas, algumas das quais consistem em alterações à função de custo com o objetivo de limitar a gama de soluções que se podem obter com o modelo;
- Esta limitação ajuda a simplificar o espaço de pesquisa, para que assim se possa obter uma solução rapidamente e mais adequada ao problema que se está a resolver;
- Por vezes estas alterações são feitas de forma a que seja introduzido conhecimento _à priori_.

### Decaimento dos Pesos
- O decaimento dos pesos (_Weight Decay_) é um tipo de regularização simples, que consiste em modificar a função de custo de forma a **penalizar valores de pesos elevados**: \begin{equation} E = MSE + \lambda w^{T} w = MSE + \lambda \sum_{i}w_{i}^2 \end{equation} onde $\lambda$ é uma constante positiva que controla a importância dada ao termo de regularização e no vetor $w$ colocamos todos os pesos do modelo;
- Ao treinar a rede com esta função de custo, estamos a exigir que a rede aproxime os dados (através do _MSE_) e ao mesmo tempo que os pesos tenham valores pequenos, através do decaimento;
- Este regularizador representa na realidade a norma $L_{2}$ do vetor de pesos, ou regularizador de _Tikhonov_;
- Utilizando a descida do gradiente para atualizar os pesos: \begin{equation} w <- w - \eta \nabla_{w}E \end{equation} onde $\eta$ é a taxa de aprendizagem (_learning rate_) e $E$ representa a função de custo;
- Introduzindo agora o decaimento dos pesos, a expressão passa a ser: \begin{equation}w <- w - \eta(\nabla_{w}E + \lambda w) = \end{equation} \begin{equation} w <- (1- \eta\lambda)w - \eta\nabla_{w}E \end{equation}

### Norma $L_{1}$
\begin{equation} E = MSE + \lambda \sum_{i} |w_{i}| \end{equation}
- O uso deste regularizador faz com que os pesos contenham muitos valores zero e poucos sejam diferentes de zero;
- Pode ser visto como um _feature selection_, pois entradas associadas a um peso zero são ignoradas.

## Conjuntos de Redes
![](https://i.imgur.com/oKJdkv2.png =400x)

- Conjuntos ou _ensembles_ de redes  podem melhorar resultados;
- Um conjunto é normalmente composto por **várias redes idênticas**, com excessão de **serem inicializadas com pesos diferentes e treinadas independentemente umas das outras**;
- Estas redes, embora semelhantes, **irão produzir resultados diferentes**, mesmo tendo sido treinadas e testadas com o mesmo _dataset_ (devido aos diferentes pesos);
- Podemos então beneficiar do desempenho de cada uma destas redes.

### Combinar Saídas
- Tendo $n$ redes treinadas para resolver o mesmo problema;
- Num problema de classificação, com $m$ classes, podemos considerar que a saída do _ensemble_ corresponde à classe mais votada pelas $n$ redes;
- Num problema de regressão, podemos considerar que a saída do _ensemble_ corresponde:
    - à média das saídas: $y_{m} = \frac{1}{m} \sum_{i=1}^{n}y_{i}$;
    - à média pesada das saídas de cada rede: $y_{mp} = \sum_{i=1}^{n} w_{i}y{i}$, onde $w_{i}$ é o peso.
- Podemos sempre utilizar apenas a rede que apresentou melhores resultados, ignorando as restantes.

### Conjuntos de Redes
- Só faz sentido combinar a **informação de várias redes, se estas discordarem umas das outras**;
- Podemos também treinar **diversas redes, onde cada rede terá diferentes versões dos dados**;
- Pode-se obter **diferentes versões dos dados**, partindo o conjunto em sub-conjuntos diferentes, ou através de **_Bagging_**;
- O resultado destas redes é por fim agrupado **usando votação (classificação)** ou faz-se a **média das saídas (regressão)**;
- Outra forma de obter melhores resultados passa por fazer as **redes trocarem informação durante o treino**, _Boosting_, ao invés de serem **treinadas independentemente**;
- Outra forma de obter redes diferentes passam por alterar qualquer elemento constituinte da rede:
    - Mudar a topologia;
    - Mudar o algoritmo de treino;
    - Mudar a taxa de aprendizagem;
    - Mudar as funções de ativação,...

![](https://i.imgur.com/S9Q77tj.png =400x)

#### _Bagging_
- Consiste treinar cada rede com um sub-conjunto gerado, **através de repetições do conjunto de treino**, mantendo o **mesmo número de pontos do conjunto de treino original**;


#### _Boosting_
- Este método troca informação entre as redes durante o treino:
    - As redes são treinadas sequencialmente;
    - As primeiras tratão dos padrões mais fáceis e deixam os mais difíceis para as seguintes;
    - Estes padrões mais difíceis estão associados a um custo de erro superior, fazendo as redes ficarem mais aptas a lidarem com os padrões mais difíceis.

## Redes Profundas
- Estas redes, que levam ao _deep learning_ são redes que contêm muitas camadas, na casa das dezenas;
- Mais camadas porquê?:
    - Certas redes com várias camadas, tiram resultados melhores a processar imagens;
    - Existem vantagens computacionais em usar mais camadas.

### Redes de Convolução: _CNNs_
- São as redes profundas mais comuns (_Convolutional Neural Networks_);
- Tem por base a ideia de **simular o funcionamento do sistema visual humano**;
- A **primeira parte** consiste em **extrair as características (_features_)** e a **segunda** em usar essas **características para classificar a imagem de entrada**;
- A parte que faz a extração tem dois tipos de camandas:
    - **convolução**: aplicam um **filtro aos dados** que recebem e geram os **mapas de características (_feature maps_)**;
    - **sub-amostragem (_pooling_)**: faz uma **redução no tamanho dos mapas** criados acima.
-  Os **filtros aplicados**, na camada de convolução, servem só para **realçar determinadas características das imagens**;
-  Na camanda de sub-amostragem, torna irrelevante a zona da imagem onde dada característica foi detetada. Gera assim uma **invariância** na deteção de características (não interessa o local exato da imagem onde tal característica foi detetada, apenas interessa que estão na imagem).
![](https://i.imgur.com/cYHG3SA.png)

---
# Aula 4
> [name=pdpm19] Livro sec. 4
## Aprendizagem não supervisionada
- Consiste em **associar** elementos que são semelhantes no espaço de entrada (características), por norma estes agrupamentos tem o nome de _clusters_;
- Os $n$ pontos de entrada serão agrupados em $m$ _clusters_, tal que $m < n$. Estes pontos serão agrupados com os elementos mais parecidos entre si.

### Aprendizagem de _Hebb_
- Sempre que o **neurónio A ativa o neurónio B**, os pesos entre eles **são reforçados (aumentado)**;
- Consiste em **percetrões** (uma só camanda com neurónios);
- Padrões desconhecidos, valor baixos, são **coisas novas**. Pelo contrário, padrões conhecidos, apresentam um valor alto. Assim conseguimos criar um **detetor de novidades**.
![](https://i.imgur.com/bDMQ9Ev.png)
- Segundo a imagem acima, a mudança nos pesos na iteração $t$, é dada por: \begin{equation}\Delta w_{ik}(t) = \eta x_{i}(t)y_{k}(t) \end{equation} onde $\eta$ é a taxa de aprendizagem
- A atualização dos pesos segue a fórmula: \begin{equation}w_{ik}(t) = w_{ik}(t-1) + \Delta w_{ik}(t) \end{equation}


#### Algortimo:
    1. Inicializar os pesos a 0;
    2. Para cada valor de x, achar o valor de y correspondente;
    3. Ajustar os pesos;
    4. Parar quando delta (mudanças nos pesos) é pequeno ou atinja o máximo de épocas. 

#### Fator esquecimento:
- Por vezes os pesos atingem valores arbitrariamente elevados, $NaN$, então aplica-se um **fator de esquecimento**, $\alpha$, que limita o crescimento dos pesos: \begin{equation}\Delta w_{ik}(t) = \eta x_{i}(t)y_{k}(t) - \alpha y_{k}(t)w_{ik}(t-1) \end{equation} sendo $\alpha$ uma constante positiva.

#### Abordagem de Sejnowski
- Esta abordagem consiste em fazer o **crescimento dos pesos proporcional aos desvios** relativamente à média das quantidades que aparecem na expressão original de _Hebb_ : \begin{equation} \Delta w_{ik}(t) = \eta (x_{i}(t) - \bar{x_{i}})(y_{k}(t) - \bar{y_{k}})\end{equation} onde \begin{equation} \bar{x_{i}} = \frac{1}{m}\sum_{p=1}^{m} x_{i,p} \end{equation} e  \begin{equation} \bar{y_{k}} = \frac{1}{m}\sum_{p=1}^{m} y_{k,p} \end{equation} sendo $m$ o número de pontos do conjunto de treino.

#### Exemplo
- Padrão conhecido (ligações vistas anteriormente) $\rightarrow$ saída com valores elevados;
- Padrão desconhecido $\rightarrow$ saída com valores pequenos;
- Pode-se então construir um **detetor de novidade**.

![](https://i.imgur.com/ExdakWd.png)

- Padrões de 150 a 200 são normais e de 201 a 300 anormais, visto que os valores normais retornam valores elevados e valores desconhecidos valores pequenos, recorreu-se à aprendizagem **anti-hebbiana**, que produz valores altos quando aparecem padrões desconhecidos ($\eta$ passa a ser negativo).

### Aprendizagem por Quantização Vetorial (LVQ)
- Cada neurónio na camada de saída vai representar um grupo, _cluster_;
- Baseada na competição entre grupos;
- Ganha o **vetor de peso** que seja mais **próximo do ponto de entrada**;
- Por norma, a medida de proximidade é a distância Euclidiana: \begin{equation} d_{p,k} = \sqrt{\sum{i=1}^{n} (x_{i,p}(t) - w_{ik}(t-1))^2} \end{equation}
- A atualização dos pesos é feita de acordo com: \begin{equation} \Delta w_{ik}(t) = \begin{cases}\eta (t)(x_{ip}(t)-w_{ik}(t-1)), & k \in K_{p}(t) \\ 0\end{cases}  \end{equation} onde $\eta (t)$ é a taxa de aprendizagem e $K_{p}(t)$ o conjunto de vizinhos do neurónio vencedor.
- **Número de neurónios é igual ao número de grupos que quero apresentar**;
- As **condições de paragem** para este algoritmo podem ser:
    - Atingir o número máximo de épocas;
    - Ajustes dos pesos são muito pequenos;
    - Erro da quantização seja suficientemente pequeno: \begin{equation}EQ = \sum_{p=1}^{P} d_{p,k}^2\end{equation}

#### Algoritmo
        1. Incializar a rede:
            1.1 Escolher número de grupos a obter ( -> cada grupo é 1 neurónio);
            1.2 Pesos inicializados com valores pequenos e aleatórios || Utilizar os primeiros pontos de entrada para o efeito;
            1.3 Inicializar a taxa de aprendizagem e o raio da vizinhança.
        2. Aprendizagem:
            2.1 Cada ponto p fazer:
               2.1.1 Achar a distância Euclidiana entre o ponto de entrada e os pesos de cada neurónio da rede;
               2.1.2 Achar a saída para a qual a distância Euclidiana é a menor;
               2.1.3 Atualizar todos os pesos da vizinhança.
            2.2 Atualizar a taxa de aprendizagem;
            2.3 Reduzir o raio da vizinhança;
            2.4 Parar se as condições de paragem forem atingidas, senão voltar ao ponto 2.1.



#### Fator de consciência
- Por vezes um grupo, ao ganhar muito, faz com que de 2 ou mais neurónios, só 1 é que fica preenchido com os dados todos, e os restantes vazios;
-  Ao se introduzir um **fator de consciência, evitamos que um neurónio domine na decisão do vencedor**;
-  As saídas são então determinadas: \begin{equation}y_{k,p}= \begin{cases}1 & min_{\forall k}(d_{p,k}-b_{k}(t)) \\ 0 \end{cases} \end{equation} onde \begin{equation}b_{k}(t)= \gamma (\frac{1}{n} - g_{k}(t)) \end{equation} e \begin{equation}g_{k}(t)=g_{k}(t-1) + \beta (y_{k,p}- g_{k}(t-1)) \end{equation}
-  $n$ é o nº de características e $g_{k}(0)=0$;
- Inicialmente temos $b_{k}(0) = \gamma/n$, isto dá a todas as saídas a mesma probabilidade;
- $b_{k}(t)$ é o fator de consciência para cada saída $k$;
- Quantas mais vezes $k$ sair, maior será $g_{k}(t)$, tornando $b_{k}(t)$ mais negativo;
- $\beta=10^{-4}$ e $\gamma = 10$.

#### Exemplos
:::info
Ver melhor exemplos deste método
:::

### _Self Organizing Maps_ (SOM)
- É uma rede em que **todos os neurónios estão ligados a todas as saídas**, estando organizados numa **malha bidimensional** que é ajustada ao longo da aprendizagem;

![](https://i.imgur.com/G6wDDGc.png =400x)


#### Aprendizagem
- É baseada numa **estratégica competitiva**;
- Consideramos que $x_{,p}$ são os vetores de dados;
- A dimensão do espaço de entrada é $m$ e o número de neurónios $n$, e $n <m$;
- Duas formas de inicializar os vetores de pesos são:
    - Forma aleatória;
    - Valores de pontos de dados escolhidos de forma aleatória.
- A forma de treino é **estocástica** (os pesos são ajustados após a exibição de cada ponto do conjunto de dados);
- O ajuste de pesos é dado: \begin{equation} w_{kj}(t+1) = w_{kj}(t) + \eta (t)h_{qn,kj}(t)(x_{,p}(t)-w_{kj}(t)) \end{equation} onde $qn$ represam a linha e coluna do neurónio vencedor;
- O **neurónio vencedor** é aquele cujo **vetor de pesos possui a menor distância ao vetor de entrada** $x_{,p}$; 
- A **função de vizinhança**, $h_{qn,kj}$, onde determina quais os neurónios de coordenadas $kj$ terão os seus pesos ajustados \begin{equation}h_{qn,kj}(t) = exp(- \frac{||c_{qn}-c_{jk}||}{2 \sigma (t)^2}) \end{equation}
- O **tamanho da vizinhança**, $\sigma (t)$, é menor quanto menor for o tamanho da vizinhança a considerar\begin{equation}\sigma (t) = \sigma (0)exp(-t/T1) \end{equation}
- A **taxa de aprendizagem** \begin{equation} \eta (t)= \eta (0)exp(-t/T2)\end{equation}
- As condições de paragem para este algoritmo podem ser:
    - Atingir o número máximo de épocas;
    - Ajustes dos pesos são muito pequenos;

##### Algoritmo
            1. Inicializar a rede:
                1.1 Definir a grelha de neurónios (mapa) -> por norma é bidimensional e de forma retangular ou quadrada;
                1.2 Vetor de pesos, m-dimensional de cada neurónio é associado ao centro dum cluster;
                1.3 Inicializar os pesos;
            2. Aprendizagem:
                2.1 Para cada ponto:
                    2.1.1 Exibição de cada ponto ao conjunto de treino;
                    2.1.2 Verificar qual é o neurónio vencedor;
                2.2 Ajustar os pesos;
                2.3 Atualizar a taxa de aprendizagem;
                2.4 Reduzir o raio da vizinhança;
                2.5 Parar se as condições de paragem forem atingidas, senão voltar ao ponto 2.1.  

#### SOM Vs. LVQ
- **SOM** apresente or neurónios organizados como uma **malha**;
- **SOM** tem uma **taxa de aprendizagem variável** e não **estática como o LVQ**;
- **LVQ necessita do número de _clusters_**, face ao **SOM** que através do seu algoritmo faz o **agrupamento por si** (mapeia).

### _Autoencoders_ (AE)
- É uma **rede não supervisionada** que através da **retropropagação aprende**, assemelhando-se a uma MLP;
- Usamos como **valores desejados os mesmo valores de entrada**;
- Os valores de entrada, $d{_i}$ são utilizados como etiquetas desejadas à saída, $x{_i}$ , ou seja, $x{_i}=d{_i}$;
- Os valores $\hat{x_{i}}$ são as estimativas produzidas pela rede;
- Quanto mais **bem treinada estiver a rede**, **mais próximos os** valores de $\hat{x_{i}}$ estão de $x_{i}$;
![](https://i.imgur.com/nHEdEUn.png)
- Ao aprender os **dados de entrada** e simultaneamente estar a ser **alvo de uma redução no número de neurónios** na camada escondida, a rede é forçada a **fazer compressão da informação**;
- Esta compressão poderá **remover ruído presente nos dados**;
- Um AE pode ser utilizado para criar **novas representações dos dados** (mais compactas), como se pode ver na figura abaixo.

![](https://i.imgur.com/fSzdhy7.png =550x150)

---
# Aula 5
> [name=pdpm19] Livro sec. 7
## Erro de Generalização
### Objetivo da Aprendizagem
- Queremos que a nossa RN tenha o **menor erro possível em novos dados**, que provenham duma distribuição idêntica à utlizada pelos dados de treino;
- Um classificador com erro no conjunto de treino teria que decorar as classes (usar uma tabela), mas obviamente **não iria generalizar** bem. 

### Conjuntos de Treino e de Teste
- Para se saber o erro de generalização, divide-se o conjunto de dados em 2 subconjuntos: conjunto de treino e teste;
- **Conjunto de treino** é utilizado para **treinar o classficador**;
- **Conjunto de teste** é utilizado para estimar o **erro de generalização do classficador**.

### Erro de Treino e Teste
- O erro medido em cada um dos conjuntos anteriores é **erro de treino** e **teste**;
- O **erro de treino** é utilizado para **guiar o processo de aprendizagem**;
- O **erro de teste** é a **estimativa do erro de generalização**;
- Quando se inicia o treino, ambos erros decrescem, mas após algum tempo, o erro de teste poderá começar a aumentar, mesmo que o erro de treino vá diminuíndo. Quando isso acontece, estamos perante o **_overfitting_**.

### _Overfitting_
- Acontece quando a RN **memoriza os padrões de treino e perde assim a capacidade de generalizar**;
- Os pesos da RN estão em excesso relativo aos que seriam necessários para aprender o problema;
- A **RN memoriza** portanto a **informação do conjunto de treino (inclusive ruído)**.

#### Como Evitar o _Overfitting_
- Existem várias formas de evitar _overfitting_ através de **regularização**:
    - Utilizar uma RN de dimensões apropriadas face ao problema -> utilizar a arquitetura da rede mais apta;
    - Parar o treino antes de o _overfitting_ ocorrer (_early-stopping_) -> evita que o treino se prolongue demasiado;
    - Fazer alterações à função de custo.
- O uso de um **conjunto de validação** também ajuda.

#### Conjunto de Validação
- Estimando o **erro do conjunto de validação** conseguimos obter que quando **este aumenta**, o **treino deve de parar**;
- Este é utilizado, pois durante o treino não se pode utilizar informação do conjunto de teste.
![](https://i.imgur.com/70uEfAx.png)

#### Validação Cruzada (CV)
![](https://i.imgur.com/aG5fvAa.png)
- A _Cross Validation_ é uma forma de obter estimativas do erro de generalização;
- Consiste em dividir o total dos dados em $k$ dobras (_folds_) disjuntas obtidas de forma aleatória (faz _k-fold CV_);
- Na imagem acima temos $k = 3$, onde as regiões a vermelho são o conj. de treino e as a amarelo o conj. de teste;
- Para cada classficador, neste caso temos 3, obtemos a estimativa do erro de generalização, testando a respetiva área amarelo;
- Por fiz, faz-se a média das $k$ estimativas do erro para assim podermos obter a estimativa final do erro de generalização; 
- Consegue lidar bem com problemas com poucos dados;
- Pode ser apresentada indicando quantos pontos se deixa nos conjuntos de teste (_leave-p-out CV_); 

#### Amostras Estratificadas
- A **amostragem estratificada** garante que a proporção das **classes nos conjuntos se mantém igual**, em cada experiência ($k$), à **proporção original presente nos dados**;
- Dá portanto uma estimativa mais realista do desempenho do classificador;
- Imaginando que temos um problema de classificação binário, numa experiência podemos ter uma distribuição $50/50$, mas noutra $10/90$. Isto numa seleção aleatória dos padrões.

## Construção da Rede
### Arquitetura da Rede
- Sabemos que devemos de colocar **um neurónio para cada classe do problema na camada de saída**;
- Também uma rede com 2 camadas escondidas é suficiente para qualquer problema (devemos sempre tentar com 1 camada e se não for suciente migrar para 2 camadas);

#### Nº de Neurónios na Camada Escondida
- Não existe fórmula mágina para calcular o número de neurónios na camanda escondida, sendo que existem 2 abordagens que nos podem ajudar:
    - **Abordagem Construtiva**:
        - Começar com um nº pequeno de neurónios na camada escondida e vai-se progressivamente aumentando o seu nº até que o erro de validação não decresce;
        - É mais atrativa computacionalmente pois começamos com rede menores;
        - É mais fácil, à partida, especificar um valor inicial de neurónios.
    - **Abordagem Demolidora (_pruning_):**
        - Começar com um nº de neurónios elevado e vai-se reduzindo o seu nº até que o erro de validação pare de decrescer;
        - Computacionalmente mais exigente, pois começamos com redes enormes;
        - À partida é dificil especificar um valor inicial de neurónios.

### Neuroevolução
![](https://i.imgur.com/j55x8uA.png =400x175)

- Consiste em **cruzar** a **computação evolucionária com as redes neuronais**, para que assim se possa fazer **evoluir potenciais redes para resolver um dado problema**;
- Continua a ser computacionalmente exigente.

### Inicializar Pesos
- A descida do gradiente é sensível aos valores iniciais dos pesos;
- Uma posição inicial próxima de um mínimo apresenta uma convergência rápida;
- Uma posição inicial numa região plana ou com pouco declive apresenta uma convergência muito lenta;
- É sugerido, utilizar pesos no intervalo $[-1/a, 1/a]$

### Codificação das Saídas da Rede
- Tendo em conta que um problema com $L$ classes deve ser resolvido por uma rede com $L$ neurónios na camada de saída, implicando assim que um vetor $y$ de saída contenha $L$ componentes;
- Este vetor vai depois ser mapeado para as $L$ classes através de um codificador 1-em-$L$, onde das $L$ possíveis saídas apenas uma deve conter o valor $1$ e as restantes o valor $0$;
- Na figura abaixo podemos observar que exisitam $L=3$, pelo que após a codificação das saídas, as classes passaram a ser representadas por vetores e não por números inteiros.
![](https://i.imgur.com/ZH6gX1Z.png)


## Dados: questões práticas
### Aumento do Número de Dados
- Por norma quantos mais dados (conjunto de treino maior), mias informação terá a rede para aprender, obtendo melhores resultados;
- Por vezes é humanamente impossível etiquetar um conjunto de dados de milhares ou milhões de exemplos;
- Criamos então **novos dados a partir dos existentes**, para que assim o conjunto de treino aumente também;
- Esta criação de novos dados deve-se à aplicação de transformações aos dados;
- No exemplo de dados em imagens, podemos aplicar:
    - rotações à imagem;
    - alterações aleatórias às cores;
    - fazer _flip_ horizontal e vertical à imagem;
    - aplicar ruído aleatório à imagem.
- No exemplo abaixo, a partir de 1 imagem, gerou-se 35 novas imagens.
![](https://i.imgur.com/CDzQZvl.png)

### Normalização dos Dados
- As características do nosso problema podem provir de sensores diferentes, com diferentes grandezas;
- Estas características com valores em escalas diferentes levam a que a sua influência na rede seja diferente (valores maiores influênciam);
- Deve-se portanto **normalizar as características para que todas estejam compreendidas na mesma gama de valores**;
- Pode-se fazer utilizando uma das seguintes possibilidades:
    - Mudar de uma escala linear para valores que se encontrem no intervalo [-1,1];
    - Obrigar as características a terem valor médio e variância unitária.
:::info
A gama [-1,1] permite-nos utilizar pesos relativamente pequenos
:::

### Valores em Falta
- Por vezes os dados fornecidos podem conter alguns valores em falta de características, $NaN$;
- Pode acontecer por vários motivos;
- As possíveis soluções são:
    - remover o padrão em que existem valores em falta;
    - substituir o valor em falta pela média dos valores (caso estes sejam contínuos) ou pelo valor mais frequente (caso sejam discretos).

### Codificação dos Valores
- Características não numéricas, p.ex. $N$ profissões, têm de ser representadas através de características binárias.
- No exemplo abaixo temos $N = 3$ características, onde foram codificadas para que apenas uma delas valha $1$ e as restantes $0$.
![](https://i.imgur.com/iBPRdpu.png)
 
### _Outliers_
- Um **_outlier_ é um padrão cujas características se desviam grandemente das apresentadas pelos restantes padrões**;
- Podem levar a rede a fazer ajustes nos **pesos de grande amplitude**, podendo alterar significativamente o **resultado da aprendizagem**.

![](https://i.imgur.com/swFcovJ.png =350x)
- Estes podem ser removidos através:
    - Remover os _outliers_ antes do início do treino;
        - Com a desvantagem de poder remover alguns pontos que aparentemente são _outliers_ (falso-positivo).
    - Modificar a função de erro, que limite assim o efeito de erros muito elevados \begin{equation}e_{p} = \begin{cases}(d_{p}-y_{p})^2 & (d_{p}-y_{p})^2 < \alpha \\ \alpha \end{cases} \end{equation} onde $\alpha$ é um valor máximo a aceitar para o erro de um ponto.
---

# Aula 6
> [name=pdpm19] Livro sec. 8
## Computação Evolucionária (CE)
- Esta simula a abordagem no âmbito dos problemas de pesquisa (**só os indivíduos mais aptos é que sobrevivem e estes combinam os seus cromossomas através da reprodução**);
- Por vezes existem mutações que de forma aleatória alteram os cromossomas;
- Estas mutações podem ser negativas ou positivas;
:::info
Aprender a jogar o _Hill Climb_ através de várias gerações/populações $\rightarrow$ lembra-te do _Code Bullet_.
:::
- Qualquer algoritmo desenvolvido dentro da CE é um Algoritmo Evolucionário (AE);
- Consiste num algoritmo que de certa forma faz uma **pesquisa estocástica por uma solução ótimo de um problema**;

## Componentes dum AE
- Elementos que influenciam o AE são:
    - Codificação das soluções do problema como **cromossomas**;
    - Função que avalia a **aptidão** dos indivíduos;
    - **Inicialização** da população;
    - Operadores de **seleção**, do qual permite seleccionar certos indivíduos;
    - Operadores de **reprodução**, do qual permite juntar $2$ indivíduos para dar $1$.

### Cromossomas
- Cada **indivíduo**, da população, **representa uma possível solução do problema**;
- As **características de cada indivíduo** são representadas por **um cromossoma**;
- As características podem ser divididas em:
    - **Genótipos**: descreve a composição genética de um indivíduo como foi **herdada dos seus progenitores**;
    - **Fenótipos**: guarda os traços comportamentais de um indivíduo **aprendidos no seu ambiente**.

#### Representação do Cromossoma
- Cada **cromossoma** é visto como **um ponto no espaço de pesquisa**, sendo este constituído por um conjunto de **genes**;
- Cada **gene** representa **uma característica** do indivíduo, sendo o seu valor chamada de **alelo**;
- Cada gene representa um parâmetro a otimizar;
- No exemplo dos retângulos, em 2D, e caso queiramos colocar 8 retângulos no retângulo maior:
    - Temos 2 coordenadas (x,y do centro) para otimiar (características);
    - A sua orientação (vertical ou horizontal);
    - Temos então 8 * (2 + 1) = 24 parâmetros a otimizar (genes).
- A eficiência da pesquisa depende da forma de representar o cromossoma;
- Formas de representar o cromossoma:
    - **Algoritmos Genéricos (AGs)** utilizam **_strings_ binárias**, suportanto no entanto variáveis reais;
    - **Programação Genética (PG)** utiliza **árvores**;
    - **Estratégias Evolucionárias** utiliza **variáveis reais**.
- No exemplo dos retângulos, cada cromossoma pode ser representado como vetor com $24$ números reais, ou $16$ reais e $8$ _bits_ ($1$ _bit_ para indicar a orientação). 

### Função de Aptidão
- Serve para **mapear um cromossoma num número real**: \begin{equation} F_{ap}: C \rightarrow R \end{equation} onde $R$ representa o conjunto dos números reais e $C$ o espaço dos cromossomas.
- Esta função diz-nos qual é a **qualidade de um determinado cromossoma** (quão perto está ele da solução ótima);
- É de extrema importância, devendo **conter todos os critérios a serem otimizados**;
- Deve também conter as **restrições**;
- No exemplo dos retângulos:
    - Critério principal a otimizar: nº de retângulos B dentro de A;
    - Restrições:
        - Não podem estar parcialmente nem totalmente sobrepostos;
        - Têm que ficar contidos em A.

### População Inicial
- A população inicial tem os seus genes **inicalizados por valores aleatórios**;
- Conhecimento **_à priori_** é benéfico para criar **mais genes em regiões que se crê existir boas soluções para o problema** (mas **pode levar ao aparecimento de ótimos locais**);
- O seu tamanho inicial implica:
    - **População Pequena:**  custo computacional baixo em cada geração, mas vai **necessitar de muitas gerações para convergir**;
    - **População Grande:** custo computacional elevado em cada geração, mas em princípio **necessita de menos gerações para convergir**;
- De forma a ficar no meio das duas abordagens atrás, se aplicarmos uma taxa de mutação elevada....

### Operadores Evolucionários
- Existem 2 tipos de operadores:
    - Seleção;
    - Reprodução.

#### Operadores de Seleção
- **Cada geração produz novos indivíduos**, necessitamos de uma forma de **selecionar quais os melhores a serem mantidos para gerações futuras**;
- Ao passarem só os melhores, damos **mais relevância** aos que se encontram mais perto da **solução ótima**;
- Devemos também escolher quais se vão reproduzir, através de _cross-over_ ou de mutação.

##### Seleção aleatória
- Escolhemos um sub-conjunto da geração atual ao acaso (ex.: Temos 100 indivíduos e escolhemos 10 ao acaso);
- **Não tira partido da função de aptidão**.

##### Seleção proporcional
- Fazemos agora seleção, mas de **forma proporcional ao valor da função de aptidão** (ex.: Escolhemos os 10 indivíduos, mas estes serão os mais aptos deste conjunto de 10);
- A **probabilidade** de um invidíduo ser selecionado será tanto **maior quanto maior for o valor de função de aptidão para o seu cromossoma**;
\begin{equation}P(C_{i}) = \frac{F_{ap}(C_{i})}{\sum_{k=1}^{N}F_{ap}(C_{k})} \end{equation} onde $N$ representa o número de indivíduos nesta geração.
###### Algortimo:
    1. Sortear um número, Csi U(0,1);
    2. i = 1;
    3. s = P(Ci); (= P(1))
    4. Enquanto s < Csi
        1. i = i + 1;
        2. s = s + P(Ci); (= s + P(1))
    5. O cromossoma escolhido é o i

###### Exemplo: 
|$i$    | 1   | 2     | 3    |
| ---   | --- | ---   |  --- |
|$P(i)$ |0.5  |0.2    |0.3   |
1. $\xi = 0.62$
2. $i = 1$
3. $s = P(C_{i}) = P(i) = P(1) = 0.5$
4. $0.5 < 0.62$, sim
    1. $i = i + 1 == 2$
    2. $s = s + P(2) = 0.5 + 0.2 = 0.7$
5. $0.7 < 0.62$, não
6. Número escolhido será o $i = 2$ 

##### Seleção por torneio
- São selecionados $k$ indivíduos de forma aleatória;
- Dentro desse grupo, será **escolhido o melhor indivíduo** ($> F_{ap}$);
- Vantagens:
    - Os piores indivíduos não são escolhidos;
    - O melhor indivíduo não vai dominar (nota que não é escolhido o melhor da geração, mas sim do subconjunto);

##### Seleção baseada em ordenação (_rank_)
- **Ordenamos os cromossomas** pelo seu **valor de aptidão** (do menor para o maior);
- A probablidade de seleção fica **menos dependente** do valor da **função de aptidão**, importando só a ordem dos cromossomas;
- **Evita que um cromossoma** com valor de aptidão muito elevado **domine o processo de seleção**;
\begin{equation} P(C_{i})=\frac{R(C_{i})}{\sum_{k=1}^{N}R(C_{k})} \end{equation}

###### Exemplo:
- No exmplo abaixo é possível notar-se no domínio que o $C_{1}$ tem na sua probabilidade de ser selecionado através de uma seleção proporcional. O mesmo não ocorre na seleção ordenada.
![](https://i.imgur.com/PAoljmJ.png)

F: 5, 2, 3

|F   |   R   |   P   |
|---|---|---|
|2   |   1   | 0.5   |
|3   |   2   | 0.3333|
|5   |   3   | 0.1666|

##### Seleção por elitismo
- Escolher **os melhores indivíduos de uma geração** para passarem para a seguinte, **sem sofrerem alteração por mutação**;
- Quantos **mais indivíduos forem escolhidos, menor será a variabilidade**.
- Se tivermos 100 indivíduos e 50 foram gerados, mas só 100 passam para a seguinte geração, pegamos nos 150 e desses escolhemos os melhores 100;

##### Seleção da população da nova geração
- Escolher os $k$ melhores: garante que a **aptidão da próxima geração não será menor que a geração seguinte**;
- Escolher os $k$ indivíduos através dos métodos vistos atrás.

#### Operadores de Reprodução
- Tem como objetivo a **obtenção de novos indivíduos a partir de indivíduos préviamente selecionados**;
- **_Cross-over_**: criação de um novo indivíduo a partir dos genes dos progenitores;
- **Mutação:** criação de novos indivíduos a partir de indivíduos existentes;
- As mutações devem de ocorrer com baixa probabilidade (não é benéfico alterar grande parte da informação genética).

## Algortimo Evolucionário Genérico
- Critérios para convergência são variados;
- Como se atinge convergência:
    - Parar quando for atingido o número máximo de gerações definido _à priori_;
    - Parar quando se encontrar uma solução aceitável;
    - Para quando a aptidão (máximo ou média) não variar, entre 2 ou mais gerações consecutivas;
    - Parar se a mudança nos cromossomas for reduzida (em várias gerações consecutivas).
### Algoritmo
        1. Inicializar o contador de gerações g=1;
        2. Inicializar a população Cg com N indivíduos;
        3. Enquanto não houver convergência fazer:
            3.1 Avaliar a função de aptidão para cada indivíduo da população;
            3.2 Efetuar Cross-over;
            3.3 Efetuar Mutações;
            3.4 Selecionar a nova geração;
            3.5 Fazer g = g + 1 e voltar a 3.

### CE vs Otimização Clássica
![](https://i.imgur.com/nycoKGd.png =600x)


---
# Aula 7
> [name=pdpm19] Livro sec. 9 & 10
## Algoritmos Genéticos (AGs)
- Permitem resolver **problemas de otimização codificando soluções em cromossomas**.


## Representação dos Cromossomas
### Variáveis com Valores Binários
- Representação original para os cromossomas.

### Variáveis com Valores Nomiais
- Informação to tipo nomial, p. ex. {advogado, professor, outra}, tem de ser passado para uma _string_ binária (advogado = 00, professor = 01, outra = 10);
- Tendo $N$ valores, temos que os codificar em $2^N$.

### Variáveis com Valores Reais
- Variáveis reais tem de ser mapeadas em _strings_ binárias;
- Uma variável $z$ que varia entre $[z_{min}, z_{max}]$ e representada por 16 _bits_ temos que seguir a fórmula: \begin{equation}z_{bin}=(2^{16}-1)\frac{z-z_{min}}{z_{max}-z_{min}}\end{equation}
- Exemplo:
    - $[40,250]$;
    - $z=160$;
    - $z_{bin}=(2^{16}-1)\frac{160-40}{250-40} = 37449 \rightarrow 1001001001001001$
### Problemas com a Codificação Binária
- Dois números consecutivos variam em muitos _bits_;
- Por norma utiliza-se a Distância de _Hamming_, que faz uma contagem do número de _bits_ que diferem entre dois números;
- Exemplo:
    - Distância entre $3$ e $4$;
    - $d_{Hamming}(011,100)=3$

#### Código _Gray_
- Números consecutívos diferem apenas de $1$ (relativa à distância de _Hamming_);
- Código _Gray_, vê os _bits_ diferentes, pelo que a representação de números consecutivos difere sempre em 1:

|Decimal | 0    | 1    | 2    | 3    | 4    | 5    |
|---|---|---|---|---|---|---|---|---|
|Binário | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 |
|_Gray_  | 0000 | 0001 | 0011 | 0010 | 0110 | 0111 |

\begin{equation}
g_{i} = 
\begin{cases}
b_{1} & i=1 \\
b_{i-1} \overline{b_{i}} + \overline{b_{i-1}}b_{i}
\end{cases}
\end{equation}

:::info
Binário para Gray Code
https://www.youtube.com/watch?v=cbmh1DPPQyI&ab_channel=TheOrganicChemistryTutor
:::

## _Cross-Over_
- Produz descendentes a paritr de progenitores selecionados;

### Algoritmo
        1. Criar 2 novos cromossomas inicializados com o material genético dos pais, alpha = Ci1 e beta = Ci2 (Ci1 e Ci2 são os progenitores);
        2. Achar a máscara m;
        3. Para cada gene j do cromossoma, se mj = 1, trocar o material genético:
            3.1 alphaj = Ci2,j;
            3.2 beta = Ci1,j.
        4. Devolver os descendentes alpha e beta.
### Máscara
- Especifica quais os genes alvo de troca durante o _cross-over_;
- Pode ser calculada:
    - uniforme;
    - um ponto;
    - dois pontos;
    - aritmético. 

### _Cross-Over_ Uniforme
- A máscara é criada de forma aleatória;
- _bit_ a $1 \rightarrow$ alelo deve ser trocado;
- _bit_ a $0 \rightarrow$ alelo não é trocado;

#### Algoritmo
    1. mi = 0, i = 1,....,N (N = nº de genes);
    2. Para cada gene i
        2.1 Obter Csi U(0,1);
        2.2 Se Csi < p então mi = 1 (p é a probabildade de cross-over)
![](https://i.imgur.com/lM7FGo2.png)

### _Cross-Over_ num Ponto
- A máscara é criada escolhendo $1$ ponto no cromossoma de forma aleatória, onde todos os pontos a partir desse serão trocados (inclusive).

#### Algortimo
    1. mi = 0, i = 1,....,N (N = nº de genes);
    2. Obter Csi UI(1, N)
    3. Para cada i >= Csi,..., N fazer mi = 1

![](https://i.imgur.com/UBdLAZO.png)

### _Cross-Over_ em dois Pontos
- A máscara é criada escolhendo $2$ pontos no cromossoma de forma aleatória, onde todos os pontos nesse intrevalo serão trocados (inclusive).

#### Algortimo
    1. mi = 0, i = 1,....,N (N = nº de genes);
    2. Obter Csi1 e Csi2 UI(1, N), Csi1 < Csi2
    3. Para cada i no [Csi1 e Csi2] fazer mi = 1
![](https://i.imgur.com/XoyeXoy.png)

### _Cross-Over_ Aritmético
- Utilizado quando os genes tem **valores reais**;
- Geramos os descendentes $\alpha_{i}$ e $\beta_{i}$ dos progenitores $C_{i1}$ e $C_{i2}$
\begin{equation} \alpha_{i,j}=\xi_{1}C_{i1,j} + (1-\xi_{1})C_{i2,j}\end{equation} 
\begin{equation} \beta_{i,j}=(1-\xi_{2})C_{i1,j}+\xi_{2}C_{i2,j}\end{equation} 
onde $\xi_{1}, \xi_{2} \sim U(0,1)$
- Exemplo:
    - $C_{i1,1} = 74.20$;
    - $C_{i2,1} = 71.30$;
    - $\xi_{1} = 0.22$;
    - $\xi_{2} = 0.51$;
    - $\alpha_{i,1} = 71.94$;
    - $\beta_{i,1} = 72.72$.

## Mutação
- Introduz novo material genético num indivíduo de forma aleatória;
- Taxa de mutação, $p_{m}$, deve de ser um valor baixo para não distorcer as boas soloções encontradas;
- Pode-se também começar com um valor elevado e faz-se o decrescer de forma exponencial com as gerações.

### Mutação Aleatória
- As variáveis a codificar são binárias.
#### Algoritmo
        1. Para cada i = 1,..., N de Cj:
            1.1 Obter Csi U(0,1);
            1.2 Se Csi <= pm fazer Cj,i = negação(Cj,i)
![](https://i.imgur.com/OlKdHrc.png)

### Mutação _inorder_
- Escolher 2 posições aleatoriamente, e apenas os _bits_ entre elas é que poderão sofrer mutação.

#### Algoritmo
    1. Obter Csi1 e Csi2 U(1,N) e Csi1 <= Csi2;
    2. Para cada i = Csi1,...., Csi2 fazer:
        2.1 Obter Csi U(0,1);
        2.2 Se Csi <= pm fazer Cj,i = negação(Cj,i).
![](https://i.imgur.com/vi4dE7B.png)

### Mutação para Variáveis não Binárias
- **Valores nomiais** obrigam a que os operadores de mutação sejam modificados de forma a que os $D$ _bits_ representados sejam aleatoriamente substituídos por outros $D$ _bits_ que representem um valor válido diferente;
- Variáveis com **valores reais**, a mutação ocorre através da **adição de um valor aleatório** (utilizamos a distribuição Gaussiana).

#### Algoritmo para alelos reais
    1. Para cada gene real j fazer:
        1.1 Obter Csi U(0,1)
        1.2 Obter um valor eta N(0, sigma^2) (signa^2 é inversamente proporcional à aptidão do indivíduo)
        1.3 Se Csi <= pm fazer Cij += eta
## Programação Genética (PG)
- É uma especialização dos AGs;
- Esta representa os indivíduos através de árvores (face aos AGs que utilizam uma _string_);
- Cada indivíduo é um **programa executável**.

### Representação dos Cromossomas
- Cada cromossoma é representado na forma de uma árvore.

#### Gramática
- Necessitamos de definir dois conjuntos:
    - **terminal**:contém todas as variáveis e constantes;
    - **funções**: contém todas as funções aplicáveis ao conjunto terminal.
- As folhas da árvore $\rightarrow$ elementos do conjunto terminal;
- Os nós da árvore $\rightarrow$ elementos do conjunto das funções.

##### Exemplo
- Programa $y = x*ln(a)+sin(z)/exp(-x)-3.4$
- Conj. terminal = $\{x,a,z,3.4\}$;
- Conj. funções = $\{+,-,*,/,ln(),sin(),exp()\}$
![](https://i.imgur.com/bCFEY8o.png)

#### Representação
- **Espaço de pesquisa**: são todos os programas que podem ser gerados a partir da gramática definida para o problema;
- **Objetivo**: encontrar qual cromossoma se aproxima mais da função desejada;
- As árvores tem tamanhos:
    - fixo: profundidade igual para todas;
    - variável: é definida apenas uma profundiade máxima.

#### População Inicial
- É gerada aleatoriamente, mas obdece às restrições impostas pela gramática e pela profundidade máxima;
- O nº de filhos da raiz e restantes nodos não terminais são dados pelo nº de parâmetros que a função requer;
- 
##### Exemplo
- Dados: $\{x,a,z,3.4\}, \{+,-,*,/,ln(),sin(),exp()\}$
- Com a sequência 7,8,1,8,2,1,4;
- Obtemos: $y = \frac{x}{x/a}*3.4$;

![](https://i.imgur.com/sv7x4Uk.png)

### Funções de Aptidão
#### Avaliação da Aptidão
- **Avalia o programa**, requerendo assim que o **programa corra com diferentes parâmetros**;
- A **média dos resultados da aptidão** do indivíduo em cada execução é utilizada para **medir a aptidão**;
- A aptidão de um indivíduo pode ser alvaliada executando o programa em todos os padrões do conjutno de dados e **medindo o erro quadrático médio nesse conjunto**.

#### Penalização
- Pode incorporar **termos que permitam penalizar determinadas propriedades não desejadas**;
- Por exemplo, podemos penalizar árvores que tenham elevado grau (muitos filhos).

### _Cross-over_
#### 1 filho
- Seleciona-se aleatoriamente um nodo em cada progenitor e a sub-árvore de um dos progenitores substitui a do outro a partir dos nodos selecionados.
![](https://i.imgur.com/ymKrxMG.png)

#### 2 filhos
- Seleciona-se aleatoriamente um nodo em cada progenitor e as respetivas sub-árvores são trocadas.

![](https://i.imgur.com/sc57UZQ.png)

### Mutação
- Temos o seguinte programa: $y = exp(3.4*a)+ \frac{a}{z}$
![](https://i.imgur.com/O0bdfVr.png)

#### Mutação de Nodos Funcionais
- É selecionado um nodo funcional e este é substituído por um da mesma aridade.

![](https://i.imgur.com/uUoDbcY.png)

#### Mutação de Nodos Terminais
- Idêntico ao anterior, mas substitui as folhas por um elemento do conjunto terminal. 
![](https://i.imgur.com/cWbjAlF.png)

#### Mutação por Troca
- São trocados os elementos de um nodo funcional.

![](https://i.imgur.com/BGw6JHo.png)

#### Mutação por Crescimento
- É selecionado um nodo e substituído por uma sub-árvore gerada aleatorimente;
- A nova sub-árvore tem algura máxima limitada.
![](https://i.imgur.com/tJZqkwF.png)

#### Mutação Gaussiana
- Um nodo terminal, que representa uma constante é escolhido ao acaso e o seu valor é substituído pela sua soma mais outro valor proveniente de uma distribuição Gaussiana.

![](https://i.imgur.com/YP1jzJs.png)

#### Mutação por Truncatura
- Um nodo funcional é substituído por um nodo aleatorio do conjunto terminal;
- Permite encurtar a árvore.

![](https://i.imgur.com/fi1P4Nl.png)