class: center, middle, title-slide .title[ # Introdução ao Aprendizado Profundo ] .subtitle[ ## Redes Neurais Artificiais ] .author[ ### Jaime Utria ] .institute[ ### Departamento de Estatística - UFF ] --- <!-- macro comandos matemáticos Latex --> <script type="text/x-mathjax-config"> MathJax.Hub.Config({ TeX: { Macros: { vet: ["{\\mathbf #1}",1], prodint: ["\\langle #1, #2 \\rangle",2], posto: ["{\\mathrm{posto} (#1)}",1], tr: ["{\\mathrm{tr} (#1)}",1] } } }); </script> ## Introdução - **Redes neurais artificiais** é um nome abrangente para designar um conjunto de algoritmos desenhados para identificar padrões/relações entre as variáveis de um conjunto de dados através de um processo de "imitação" de uma rede neural biológica. - A unidade básica de cálculo dentro de uma rede neural é chamada de **neurônio** e é simplesmente uma função à qual dados são alimentados e transformados em uma resposta. - A resposta obtida é repassada para um ou mais neurônios da rede que, ao final do processo, retorna uma saída. --- ## Arquitetura de uma RNA Redes neurais são caracterizadas por uma arquitetura que corresponde à forma como os neurônios estão dispostos em **camadas** (*layers*). Existem 3 tipos comumente usadas: 1. **Rede neural com uma única camada de entrada e uma de saída** (*perceptron*). 2. **Rede neural multicamadas** (*perceptron* multicamadas proalimentadas). Além das camadas de entrada e saída, existem uma ou mais camadas de neurônios chamadas de camadas ocultas. 3. **Rede neural recorrente**. Nesta rede pelo menos um neurônio conecta-se com um neurônio da camada precedente, criando uma retroalimentação. --- ## Perceptron (Rosenblatt, 1958): Classificação binária <img src="data:image/png;base64,#perceptron.png" width="1133" /> --- ## Perceptron: Detalhes - Seja `\(\mathbf x^\top = [x_1,\ldots, x_p]\)` um dado de entrada e `\(\mathbf w = [w_1, \ldots, w_p]\)` o vetor de pesos associados a cada elemento de `\(\mathbf x\)`, calculamos inicialmente: `$$\sum_{i=1}^p w_i x_i = \mathbf w^\top \mathbf x.$$` - O classificador é como segue: `$$\phi(\mathbf w^\top\mathbf x) = \begin{cases} +1, \text{ se }\mathbf w^\top \mathbf x \ge -b \\ -1, \text{ se } \mathbf w^\top \mathbf x < -b \end{cases}$$` - Para simplificar, podemos introduzir `\(x_0 = 1\)` e `\(w_0 = b\)`, então o classificador fica: `$$\phi(\mathbf w^\top\mathbf x) = \begin{cases} +1, \text{ se }\mathbf w^\top \mathbf x \ge 0 \\ -1, \text{ se } \mathbf w^\top \mathbf x < 0 \end{cases}$$` --- <img src="data:image/png;base64,#boundary_perceptron.png" width="3400" /> --- ## Regra de aprendizado (treinamento) - Os parâmetro do *perceptron* são os pesos `\(w_1, \ldots, w_p\)` e o viés (limiar) `\(b\)`, ou seja, o vetor `\(\mathbf w = (b,w_1, \ldots, w_p)\)`. - A regra de treinamento do *perceptron* pode ser resumida nos seguintes passos: 1. Iniciar os pesos como 0 ou números aleatórios pequenos. 2. Para cada amostra de treino `\(\mathbf x_i\)`: - Calcular o valor de saída `\(\hat y_i\)`. - Atualizar os pesos. --- - O valor de saída corresponde ao rótulo pedito por `\(\phi\)` e a atualização dos pesos é feita por: `$$w_{j + 1} = w_{j} + \Delta w_{j},$$` em que - `\(\Delta w_{j}=\eta(y_i - \hat y_i)x_{ij}\)`, - `\(\eta\)` é a taxa de aprendizado (valor entre 0 e 1), - `\(y_i\)` é o verdadeiro rótulo, - `\(x_{ij}\)` é o valor da j-ésima variável de entrada da i-ésima amostra. - É importante destacar que todos os pesos são atualizados simultaneamente, o que significa que não podemos calcular `\(\hat y_i\)` antes de que todos os pesos estejam atualizados. --- ## Duas dimensões - Em duas dimensões, por exemplo, os pesos são atualizados através de: `$$\Delta w_0 = \eta(y_i - \hat y_i)$$` `$$\Delta w_1 = \eta(y_i - \hat y_i)x_{i1}$$` `$$\Delta w_2 = \eta(y_i - \hat y_i)x_{i2}$$` - Note que `$$\Delta w_{j} = \begin{cases} 0; \; y_i = \hat y_i\\ 2\eta x_{ij}; \; y_i=1,\hat y_i = -1,\\ -2\eta x_{ij}; y_i = -1, \hat y_i = 1.\end{cases}$$` ou seja, se não erramos na previsão mantemos o peso, caso contrário os pesos são empurrados para a direção da classe de destino positiva ou negativa. --- ## Observações - A regra de aprendizado do perceptron é baseada no método do Gradiente descendente: `$$w_{j+1} = w_j + \eta \nabla_j L(\mathbf w),$$` com `\(L\)` sendo a função de perda quadrática. - A convergência do perceptron só está garantida se as classes são linearmente separáveis e `\(\eta\)` é suficientemente pequeno. - Se as duas classes não são linearmente separáveis, podemos ajustar um número máximo de iterações (épocas) ou um limiar para o número máximo tolerável de classificações erradas. --- ``` r set.seed(12345) x1 <- rnorm(20) x2 <- rnorm(20) y <- ifelse(x2 > 1.5*x1+0.2,1,-1) training <- data.frame(x1,x2,y) ``` ``` r plot(training$x1, training$x2, pch = 19, col=as.factor(y), xlab = "x1", ylab = "x2", cex = 2) grid() ``` --- .center[ <!-- --> ] --- ## Treinando um perceptron ``` r library(neuralnet) perceptron_nn <- neuralnet(y ~ x1 + x2, data = training, hidden = 0, linear.output = FALSE) # pesos w <- unlist(perceptron_nn$weights) w ``` ``` ## [1] -2.10347 -21.88171 12.98468 ``` --- <img src="data:image/png;base64,#perceptron_nn.png" width="1328" /> --- .center[ <!-- --> ] --- ## Redes neurais multicamadas (*multilayer perceptron*) .center[ <img src="data:image/png;base64,#perceptron_multicamadas.png" width="600" /> ] --- ## Treinamento da rede *MLP* - A saída de uma rede neural pode ser expressa na forma: `$$f(\mathbf x, \mathbf w) = \varphi \left( \sum_{j=0}^{M-1} w_j \phi_j(\mathbf x)\right),$$` em que - `\(M\)` é o número de neurônios na camada oculta; - As `\(\phi_j\)` são funções que dependem das funções de ativação; - `\(\varphi\)` é uma função não linear (para classificação) ou identidade (para regressão). - Os `\(w_j\)` são pesos a serem encontrados. --- **Forward Propagation** 1) Considere as ativações: `$$a_j = \sum_{i=0}^p w_{ji}^{(1)}x_{i}, \; j = 1, \ldots, M.$$` 2) Cada ativação `\(a_j\)` é transformada por meio de uma **função de ativação** `\(h\)`: `$$y_j = h(a_j)$$` 3) as ativações de saída `$$a_k = \sum_{j=0}^M w_{kj}^{(2)}y_j, k=1, \ldots, K$$` 4) As ativações são transformadas por outra função de ativação `\(b\)`: `$$z_k=b(a_k)$$` --- **Backpropagation** (Retropropagação) - No algoritmo, atualizamos o gradiente da função de perda de maneira que os pesos sejam tranformados na direção oposta indicada pelo sinal do gradiente até que um mínimo seja atingido. - A função objetivo a minimizar é `$$J_n(\mathbf w) = \frac{1}{2} \sum_{i=1}^n ||z_i - f(\mathbf x_i, \mathbf w)||^2.$$` - A otimização pode ser feita iterativamente (pelo método do GD), até convergência, usando: `$$\mathbf w^{(j+1)} = \mathbf w^{(j)} - \eta \nabla J_n(\mathbf w^{(j)})$$` - O ajuste de uma rede neural baseia-se em um algoritmo, mais eficiente do que o GD, chamado de **retropropagação**. --- ## Funções de ativação 1. Função logística: `\(f(x) = [1+\exp(-x)]^{-1}\)`. 2. Função tangente hiperbólica: `\(f(x) = [\exp(x) - \exp(-x)]/[\exp(x) + \exp(-x)]\)`. 3. Função ReLU: `\(f(x) = \max(0,x)\)`. --- ## Treinando uma rede neural aos dados *Sonar* ``` r library(mlbench) library(caret) library(neuralnet) data(Sonar) preditoras <- Sonar[,1:60] classes <- Sonar$Class preditoras_padr <- data.frame(scale(preditoras)) df_processado <- data.frame(preditoras_padr, class = classes) ``` --- ``` r set.seed(12345) index_train <- createDataPartition(y = df_processado$class, p = 0.8, list = F) training <- df_processado[index_train,] testing <- df_processado[-index_train,] training$class <- as.factor(ifelse(training$class == "M", 1,0)) testing$class <- as.factor(ifelse(testing$class == "M", 1,0)) # criando a formula da rede neural num_preditoras <- ncol(training) - 1 nomes_preditoras <- names(training)[1:num_preditoras] formula_nn <- as.formula(paste("class ~", paste(nomes_preditoras, collapse = " + "))) ``` --- ``` r # Ajustando uma rede neural com 2 camadas ocultas # primeira com 10 neuronios e segunda com 5 neuronios sonar_nn <- neuralnet(formula_nn, data = training, hidden = c(10, 5), #camadas ocultas linear.output = FALSE, threshold = 0.05, rep = 1) # fazendo predições predictions_prob <- compute(sonar_nn, testing[, 1:num_preditoras])$net.result[, 2] predictions_class <- as.factor(ifelse(predictions_prob >= 0.5, 1, 0)) ``` --- ``` r confusionMatrix(predictions_class,testing$class, positive = "1") ``` ``` ## Confusion Matrix and Statistics ## ## Reference ## Prediction 0 1 ## 0 19 1 ## 1 0 21 ## ## Accuracy : 0.9756 ## 95% CI : (0.8714, 0.9994) ## No Information Rate : 0.5366 ## P-Value [Acc > NIR] : 2.995e-10 ## ## Kappa : 0.9511 ## ## Mcnemar's Test P-Value : 1 ## ## Sensitivity : 0.9545 ## Specificity : 1.0000 ## Pos Pred Value : 1.0000 ## Neg Pred Value : 0.9500 ## Prevalence : 0.5366 ## Detection Rate : 0.5122 ## Detection Prevalence : 0.5122 ## Balanced Accuracy : 0.9773 ## ## 'Positive' Class : 1 ## ```