Machine Learning / Aprendizagem de Máquina

Prof. Dr. Pedro Rafael D. Marinho
Departamento de Estatística - UFPB

2023-09-20

Aprendizagem de Máquina

Bacharelado em Estatística

UFPB

Apresentação

Sobre mim



O Departamento

Meu segundo lar

Departamento de Estatística da UFPB.

Que linguagem de programação utilizar?


Nesse curso, será abordado a linguagem de programação R, mas lembre-se que você poderá utilizar qualquer linguagem de programação para fazer ciência de dados. Porém, R e Python são as minhas sugestões, haja vista que, atualmente, elas são as linguagens com maior quantidade de ferramentas e usuários trabalhando na área de ciência de dados.


Outros motivos que me leva a lecionar a disciplina utilizando a linguagem R são:

  1. Possui ferramentas muito bem pensadas para manipulação e tratamento de dados;
  2. Normalmente, os frameworks de machine learning de R são menos verbosos que os de Python;
  3. Matrizes e data frames são estruturas de dados que já encontra-se definidas dentro da linguagem, não precisando assim de importar bibliotecas.

Isso é meu gosto pessoal! É um gosto que, talvez, faz mais sentido, em se tratando de alguém que vem da estatística. No mercado de trabalho e em seus estudos, após cursar as disciplinas de R e Python, fornecidas pelo Bacharelado em Estatística da UFPB, você terá a capacidade de estudar os frameworks de machine learning, aos seus próprios passos e escolher o que melhor te agrada. A linguagem Julia também poderá ser uma ótima opção.

Aprendizagem de Máquina: O que é?

Aprendizagem de máquina



Aprendizagem de máquina



Alguns pontos:


  1. A Aprendizagem de Máquina (AM), também chamada de Machine Learning (ML), no inglês, nasceu na década de 60 como um campo da inteligênica artificial;

  2. Em sua origem, as aplicações de AM tinha como objetivo aprender padrões com base nos dados;

  3. Originalmente, as aplicações de AM eram de cunho estritamente computacional. Todavia, desde o início dos anos 90, a área de aprendizagem de máquina expandiu seus horizontes e começou a se estabelecer como um campo por sim mesma;

  4. Em particular, a área de aprendizagem de máquina começou a estabelecer muitas intersecções com a estatística. Muitos de seus algoritmos são construídos com base em metodologias que surgiram na estatística;

  5. Atualmente, a comunidade de AM é bastante interdisciplinar e utiliza-se de ideias desenvolvidas em diversas áreas, sendo a estatística uma delas.

Tipos de Aprendizado


Aprendizado supervisionado


Nesse curso, inicialmente estudaremos problemas de aprendizado supervisionado, que consiste em aprender a fazer predições a partir de conjunto de dados em que rótulos (valores da variável resposta Y) são observados. Trataremos tanto de problemas de regressão (estimar um valor númérico) quanto problemas de classificação (classificar um cliente como aprovado ou reprovado, em um problema de concessão de crédito). Por exemplo, os modelos de regressão são exemplos de aprendizado supervisionado.


Aprendizado não-supervisionado


Na segunda parte do curso, aprenderemos alguns métodos de aprendizado não-supervisionado, ou seja, algoritmos que não utilizam-se de rótulos, em que busca-se aprender mais sobre a estrutura dos dados. Por exemplo, os métodos de agrupamento (cluster), são exempĺos de métodos de aprendizado não-supervisionado.

Tipos de Aprendizado


Muito embora no nosso curso focaremos nas abordagens de aprendizagem supervisionada e não-supervisionada, os tipos de aprendizagem, em geral, podem ser mais amplos, em que temos:


  1. Aprendizagem supervisionada;
  2. Aprendizagem não-supervisionada;
  3. Aprendizagem semi-supervisionada;
  4. Aprendizagem por reforço.

O que é aprender?


Antes de detalharmos os tipos de aprendizagem de máquina, uma dúvida que poderá surgir é: “O que é aprender?”. “Como a máquina aprende?”.


O que é aprender?


De forma simples, aprender é ganhar conhecimento através de estudo, experiências, por meio de ensinamentos.


Tá, mais como é que a máquina aprende?


  1. Aprendizagem é o processo em que se adquire conhecimento, isto é, é o processo em que utilizamos de algoritmos e fornecemos dados a esses algoritmos para que possamos extrair conhecimento. Nesse processo de aprendizagem, os algoritmos fazem uso de dados para a extressão de conhecimento, através de procedimentos supervisionado, não-supervisionado, semi-supervisionado ou por reforço, a depender do algoritmo que você deseja utilizar.

  2. Aprendizado é o modelo ajustado, isto é, é o conhecimento adquirido após o treinamamento obtido no processo de aprendizagem. Você poderá entender como sendo o modelo ajustado e que utilizamores para a tomada de decisões.

O que é aprender?


Portanto, você poderá entender, basiciamente, existe quatro tipos de aprendizagem, sendo os dois primeiros o que mais focaremos nesse curso e que de longe são os mais utilizados:


  1. Aprendizagem supervisionada;
  2. Aprendizagem não-supervisionada;
  3. Aprendizagem semi-supervisionada;
  4. Aprendizagem por reforço.


Aprendizagem supervisionada


Nesse tipo de aprendizagem, o algoritmo irá receber um conjunto de dados em que conhecemos rótulos para a variável de interesse. É como se você soubesse onde um bom modelo deve chegar, para assim ser reconhecido como um bom modelo. Por exemplo,


  1. Classificação: precisamos determinar a classe de uma instância de dados, o seu atributo, i.e., \widehat{y} = \mathrm{argmax}_y\,P(Y = y\,|\, X = \bf{x}), em que y é um atributo que desejamos prever (cahorro, gato, sapo), e \bf{x} é um vetor de características (peso, altura, comprimento, se tem rabo, etc).

  2. Regressão: precisamos estimar uma quantidade numérica, i.e., o valor da variável alvo por meio de uma instância de dados, ou seja, precisamos estimar Y = \mathbb{E}(Y|X = \bf{x}), i.e., devemos encontrar meios de obter \widehat{Y}.


Aprendizagem supervisionada


Em se tratando de métodos de classificação, podemos ter os métodos:


  1. Generativos: são os métodos que dada as variáveis X e Y, o objetivo é encontrar a distribuição de probabilidade conjunta P(X, Y), para então poder determinar P(Y|X = \bf{x}). Alguns métodos são:

    • Naive Bayes;
    • Descriminante linear.


  1. Descriminativos: são os métodos que estimam diretamente a probabilidade condicional P(Y|X = \bf{x}) ou que mesmo nem assumem modelos probabilísticos. Os modelos dessa classe são projetados para aprender a fronteira de decisão que separa as classes diretamente com base nas características de entrada. Podemos citar:
    • Regressão logistica;
    • Perceptron;
    • Support Vector Machine - SVM.

Aprendizagem supervisionada



Poderíamos estar interessados em classificar o tamanho de morangos:


  1. S (Slow): pequeno;

  2. M (Medium): médio;

  3. L (Large): grande.

Aprendizagem supervisionada



Mais dois problemas de classificação (linear x não-linear).

Aprendizagem supervisionada



Um exemplo de de um problema de regressão. Aqui, a ideia é utilizar a equação da reta estimada, a reta que minimiza a soma dos quadrados entre a reta e os ponto seria a melhor, de modo a ter uma estimativa numérica através de novos atributos passado ao modelo, i.e., por meio da equação da reta e de um novo valor de x.

Aprendizagem supervisionada


Um outro exemplo seria a classificação de imagem/vídeo, utilizando um algoritmo de rede neural, por exemplo, usando uma Convolutional Neural Network - CNN. Foram utilizados diversas imagens de pessoas “com” e “sem” máscara. Em que “com” representa detecção da máscara na face da pessoa e “sem” a não detecção.


Aprendizagem não-supervisionada


Nesse tipo de aprendizagem, os algoritmos trabalham sobre dados não rotulados, por exemplo, em uma trarefa de agrupamento.


Os algoritmos verificam se as instâncias observadas poderão ser arranjadas de alguma maneira, por exemplo, usando alguma métrica de distância, formando grupos (clusters).


A ideia é maximizar a distância entre os clusters e minimizar a distância entre os elementos no interior do grupo. Em outras palavras, o que se quer é tornar os grupos mais diferentes possíveis e tornar os elementos dos grupos o mais parecido possível.


Aqui, por não haver rótulos, um problema comum é determinar a quantidade de grupos ideal que muitas vezes são obtidos de forma subjetiva ou por heurísticas. A quantidade de grupos é um dilema!

Aprendizagem não-supervisionada


Após a detecção dos grupos, é preciso analisar o resultado de modo a tentar extrair informações coerentes de modo a saber o que cada grupo representa no problema em questão.

Aprendizagem semi-supervisionada


A aprendizagem semi-supervisionada é uma abordagem na área de aprendizagem de máquina, em que um algoritmo utiliza tanto dados rotulados quanto não rotulados para treinamento. Por exemplo, algoritmos que propagam rótulos, como o Label Propagation, em que rótulos conhecidos são propagados para dados não rotulados com base em sua sua proximidade no espaço de características.


Uma outra abordagem seria misturar modelos (Model Blending), em que diferentes modelos são treinados em diferentes partes do conjunto de dados, por exemplo, um modelo para a parte roturada e um para a parte não rotulada.


Aprendizagem por reforço


Nesse tipo de aprendizagem, não há uma fonte externa de exemplos. O agente (modelo) aprende aprende com sua própria experiência, por tentativas e erros, em que você deverá definir uma medida de sucesso, e eventualmente recompensar os acertos. No vídeo abaixo, veja um joguinho que criei em R, em que o carrinho aprendeu a desviar de obstáculos aleatórios que aparecem em sua frente. Utilizou-se uma rede neural cuja a saída poderia ser (“parado”, “para cima” ou “para baixo”). Veja o código clicando aqui.


Dados: exploração e tratamento


Um dos passos mais importante no fluxo de trabalho (workflow) de um modelo de aprendizagem de máquina, consiste na preparação dos dados, em que realizamos transformações, inputações de valores ausentes, identificação de outliers, remoção de variáveis altamente correlacionadas, entre outros.


Fazer uma análise exploratória dos dados é um passo importante para que se possa entender e detecatar possíveis inconsistências na base de dados. Não adianta fazer uso de modelos muito sofisticados quando se tem uma base de dados cheia de problemas.


Normalmente trabalhamos com juntos de dados (tabelas) relacionais, em que cada linha é uma observação e cada coluna representa um atributo do objeto/observação. A linha de uma base de dados relacional, sem sua a variável de interesse, lembre-se que denominamos Y de rótulo ou label, fornece o vetor de características \bf{x} que descreve uma dada observação.

Dados: exploração e tratamento

No artigo Tidy Data, 2014, publicado no Journal of Statistical Sofware, o Hadley Wickham discute que o princípio de dados organizados estão intimamente relacionados com banco de dados relacional e mais próximo do reciocínio que empregamos na álgebra. Nesse artigo, ele define o que é Tidy Data, sendo essa uma maneira de mapear um conjunto de dados.


Segundo o artigo, um conjunto de dados é bagunçado ou arrumado/tidy, dependendo de como as linhas, colunas e tabelas são combinadas com as observações, variáveis e tipos. Em dados arrumados (dados tidy), temos que:


  1. Cada variável forma uma coluna;
  2. Cada observação forma uma linha;
  3. Cada valor deve ter sua própria célula.


Embora existam situações em que já podemos começar a analisar uma base de dados real, essa é a exceção e não a regra. Normalmente, nos deparamos com bases de dados que violam uma ou mais dessas regras. Sempre, que possível, procure utilizar dados no formato Tidy.

Dados: exploração e tratamento


Representação de uma base de dados no formato tidy.

Dados: exploração e tratamento


“As famílias felizes são todas iguais; toda família infeliz é infeliz à sua maneira.” – Leo Tolstoy

“Conjuntos de dados organizados são todos iguais, mas todo conjunto de dados confuso é confuso à sua maneira.” – Hadley Wickham


Trabalhar com a Tabela do lado esquerdo é melhor que a Tabela do lado direito. Prefira, sempre que possível, o formato tidy. Não permita-se ficar estressado tão facilmente. 😃

Dados: exploração e tratamento

A linguagem de programação R possue diversas ferramentas que permite manipular e explorar bases de dados. Enumero algumas:

  1. dplyr: biblioteca que implementa é uma gramática de manipulação de dados, fornecendo um conjunto consistente de verbos que ajudam a resolver os desafios mais comuns de manipulação de dados;
  2. tidyr: ferramentas para ajudar a criar dados organizados, em que cada coluna é uma variável, cada linha é uma observação e cada célula contém um único valor;
  3. ggplot2: um sistema para criar gráficos ‘declarativamente’, baseado no livro The Grammar of Graphics, de Leland Wilkinson;
  4. visdat: uma biblioteca útil para um visualização exploratória preliminar de dados;
  5. explore: biblioteca que apresenta algumas rotinas de análise para realizar uma análise exploratória nos dados.

Todas essas bibliotecas estão muito bem documentadas. É importante que vocês explorem as documentas dessas bibliotecas, pois eventualmente irei utizar alguma delas.

Dados: exploração e tratamento


No Capítulo 12, do livro R for Data Science, o autor aborda mais sobre o formato Tidy e como trabalhar com a biblioteca tidyr. Aqui o autor aborda de forma básica o pacote dplyr.


Durante o curso, na medida da necessidade de utilização dessas ferramentas, durante a exposição de exemplos, abordaremos alguns conceitos. Você terá a oportunidade de também explorar essas bibliotecas nos exercícios. Ok?!


Dados: exploração e tratamento


Muitas vezes, no processo de tratamento dos dados, também estamos preocupados em remover atributos que não são significativo para a modelagem, em que nesse momento a experiência dos especialistas são fundamentais.


É comum enriquercermos a base de dados com informações de outras bases de dados, em um sistema de gerenciamento de banco de dados relacional, em que as bases de dados estão relacionadas por uma chave. Nesse caso, buscamos por novos atributos para um mesmo objeto (para uma mesma linha da base), em que atributos cruzados devem ter um único valor, para cada objeto, respeitando a regra três de conjuntos de dados tidy.


As vezes transformamos variáveis. Por exemplo, é comum tomar o logaritmo de uma variável numérica que é assimétrica, se x \geq 1, em que x é um atributo numérico qualquer.


Em diveras situações, também é comum a base de dados apresentar informações faltantes. Nos data frames de R, a falta de informação na base, normlamente serão representadas por NA.

Dados: exploração e tratamento


Poderá ser que um dado atributo apresente informação faltante, e normalmente não optaremos em remover a observação e precisaremos imputar a informação, por exemplo:


  1. Tomando alguma medida de tendência central como média/moda/mediana dos valores que são conhecidos para aquele atributo;
  2. Criar um novo valor que é indicação de valor faltante;
  3. Usar algoritmos como k-nearest neighbors - kNN (k vizinhos mais próximos) para imputar valores coerentes;
  4. Interpolar os dados.

Esses são alguns exemplos de como podemos imputar observações faltantes. Muitas vezes não podemos nos dar o luxo de percer observações de nossa base de dados.

Dados: exploração e tratamento


É comum ser necessário transformar os dados:

  1. Pode ser necessário transformar os tipos ou os valores dos atributos para tentar obter um melhor ajuste do modelo;

  2. Pode-se discretizar valores contínuos ou transformá-los em intervalos;

  3. É comum transformar atributos categóricos com p categorias, em p novos atributos binários.

  4. Outra transformação muito comum é a normalização dos dados. Normalizar os dados é muito útil quando os atributos numéricos possuem escalas muito diferentes.

X_{novo} = \frac{X - X_{min}}{X_{max} - X_{min}}, em que X_{novo} \in [0, 1].

X_{novo} = Z = \frac{X - \mu}{\sigma^2}, em que \mathbb{E}(X) = \mu é a média dos dados e \mathrm{Var}(X) = \sigma^2. Na prática, em um contexto de v.a., iids, usamos \overline{x} como estimador de \mu e S^2 (variância amostral) como estimador de \sigma^2.

Dados: exploração e tratamento


Lembre-se, como citado anteriormente, tomar o logaritmo natural, ou mesmo na base 10 de variáveis numéricas muito assimétricas, poderá ajudar um pouco, desde que seja possivel tomar o \log(\cdot).


set.seed(0)
rgamma(1000, 2, 2) |> 
  hist()

set.seed(0)
rgamma(1000, 2, 2) |> 
  log() |> hist()

Dados: exploração e tratamento


Anteriormente eu citei algumas bibliotecas úteis de R para explorar os dados, na fase de tratamento das observações. Porém, não estranhe não ter, até o momento, citado bibliotecas do framework tidymodels, em especial o recipes que é muito utilizado no workflow de aprendizagem de máquina, na fase de pré-processamento dos dados. Muitas dessas transformações são aplicadas como receitas de pré-processamento com o pacote recipes.



O tidymodels será muito útil para nós, mas, aos poucos, seu uso e explicações mais detalhadas serão apresentadas, apesar que em algumas situações mais simples, poderei não utilizá-lo, para expor detalhes que eventualmente não será possível ou estariam camuflados (black box) na utilização do tidymodels.


Para não deixar de valar sobre o tidymodels, explicarei, agora, a sua filosofia e como ele está dividido em outras bibliotecas que são úteis em cada parte do processo de treinamento de um modelo de machine learning.

💻 Tidymodels


Diversas bibliotecas na linguagem R são preparadas para trabalharem na área de aprendizagem de máquina. Várias dessas bibliotecas vem sendo desenvolvidas há anos. Por exemplo,as bibliotecas glmnet, ranger, kknn, xgboost, keras, rpart, randomForest, entre diversos outros.


O número de pacotes abaixo é o mais recente. Obtido automaticamente por webscraping.
library(xml2)
library(httr)
library(stringr)

numero_pacotes_r <- httr::GET("https://cloud.r-project.org/web/packages/index.html") |> 
  xml2::read_html() |> 
  xml2::xml_find_all("//p[1]") |> 
  xml2::xml_text() |> 
  stringr::str_extract(pattern = "[0-9]+")


Atualmente, a linguagem R possui 19898, em que muitos deles são preparados para para trabalharem em tarefas de aprendizagem de máquina, porém, cada com sua sintaxe específica. Muitos implementam o mesmo modelo, uns com algumas variações, porém, o uso é totalmente diferente, nomes de parâmetros distintos, saídas distintas, etc.

💻 Tidymodels


Essas diferentes implementações torna confuso trabalhar e testar diferentes modelos ao mesmo tempo.


Uma das primeiras ideias mais conhecidas de unificação de sintaxe do workflow de machine learning, na linguagem R, foi idealizada pelo estatístico Max Khun.


Ele criou a biblioteca caret - Classification And REgression Training de R que é muito bem desenvolvida e abrangente. Você poderá estudar o caret clicando aqui.


Não foi um trabalho simples, veja uma tabela com a quantidade de modelos que o caret suporta, clicando aqui. Então, “por baixo dos panos”, a ideia era unificar a entrada e saída. A biblioteca caret continua sendo mantida, apesar da existência do tidymodels.

💻 Tidymodels


Max Kuhn, atualmente, no momento de escrita desse mateiral, é funcionário da Posit Ltda e foi contratado para estar a frente do desenvolvimento de uma versão “arrumada” (tidy) do caret, que é o tidymodels.


💻 Tidymodels


O workflow (pipeline) do treinamento de um modelo usando o framework tidymodels. Todos os pacotes (rsample, recipes, parsnip, tune, dails, yardstick) são gerenciados pelo pacote tidymodels. Cada um desses pacotes fornece um conjunto de funções úteis em tarefas específicas no workflow de machine learning.

💻 Tidymodels


  1. rsample: responsável pela reamostragem dos dados, parte importante para que possamos treinar um modelo de aprendizagem de máquina. É nele que encontra-se funções para realizar reamostragem como bootstrap, k-folds cross-validation, nested cross-validation, entre outras.

  1. recipes: apresenta diversas funções para transformações de variáveis como criação de variáveis dummy, normalização de variáveis, inputação de dados pela média, mediana, kNN, entre outras formas de imputação, transformações de variáveis categórias em numéricas, entre outras funcionalidades. Ele permite que possamos criar uma receita de transformações nos dados para que esses, após transformados, possam entrar no modelo.

  1. parsnip: é o pacote que unifica as entradas e saídas de diversos pacotes de aprendizagem de máquina de R. Ele possui os motores (engines) que são as comunicações com os algoritmos implementados em diversos pacotes de R que trabalham com tarefas de regressão e classificação, em aprendizagem de máquina.

💻 Tidymodels


Os pacotes tune, dails e yardstick tomará conta da parte de treino do modelo. Os pacote tune e dails são responsáveis pela “tunagem” dos eventuais hiperparâmetros, já o yardstick é responsável pelas métricas de avaliação do modelo.


A biblioteca dails está mais relacionada a criação dos grids para os eventuais hiperparâmetros do modelo. Já o pacote tune, utiliza-se da validação cruzada criada pelo pacote rsample para varrer as combinações de hiperparâmetros criadas pelo dails, i.e., o tune está mais relacionado com a otimização dos hiperparâmetros.

💻 Tidymodels


Todo o fluxo de trabalho é gerido pela biblioteca workflows de R. Em especial, as etapas de feature engineering e especificação do modelo.


💻 Tidymodels


Perceba o papel da biblioteca workflows de R. Basicamente gostaríamos de ter uma automação da faze do tratamento das features realizada com o recipes com a modelagem.

💻 Tidymodels


Não necessariamente iremos utilizar o tidymodels em todos os exemplos e exercícios. Porém, iremos explorar bastante, até o fim do curso, o treinamento de modelos usando o tidymodels. Por tanto, aos poucos, a medida em que exemplos são apresentados e exercícios forem passados, o aprendizado do uso do tidymodels se dará.



Sempre que possível, deveremos colocar as “mãos na massa” 🍝 para que possamos dominar e compreender uma ferramenta computacional. A prática é importante!

🎭 As duas culturas


Em Breiman, L. (2001a). Statistical modeling: The two cultures. Statistical Science, 16(3), 199–231, o Leo Breiman argumenta que existe duas culturas no uso de modelos estatísticos, em especialmente na área de modelos de regressão. Segundo eles, as culturas são:


  1. Data modeling culture: nela, em geral, se assume que o modelo de regressão utilizado r(x), por exemplo, r(x) = \beta_0 + \sum_{i = 1}^d \beta_ix_i é correto. O principal objetivo dessa abordagem é a interpretação dos parâmetros que indexam o modelo r(x). Nesse tipo de cultura, a ideia também é construir intervalos aleatórios e testar hipóteses para os \beta_i's. Sob essa ótica, muitas suposições sob o modelo são realizadas, em que formas para checar essas suposições são desenvolvidas, uma vez que elas são fundamentais para esse tipo de modelagem.


  1. Algorithmic modeling culture: essa é a cultura que domina a comunidade de aprendizagem de máquina. Nessa abordagem, o principal objetivo são as predições por meio de novas observações. Não se assume que o modelo utilizado é o modelo correto. Nesse tipo de modelagem, muitas vezes os algoritmos não envolve nenhuma estrutura probabilística. Muitas vezes, modelos não bem especificado conduzem a boas predições.

🎭 As duas culturas


Breiman, L. (2001a). Statistical modeling: The two cultures. Statistical Science, 16(3), 199–231.

Leo, na época em que era um jovem probabilista na Universidade da Califórina.

🎭 As duas culturas


Há diversos artigos interessantes que são respostas ao artigo do Leo Breiman, como por exemplo, o artigo Statistical Modeling: The Two Cultures: Comment do David Cox e com comentários do Brad Efron.

Sir David Cox.

🎭 As duas culturas


Muito embora exista essa divisão entre as culturas, Breiman foi um estatístico que desempenhou um grande trabalho para unir a área de estatística com aprendizado de máquina. Por conta dessa grande importância, um prêmio concedido em sua homenagem foi criado pela American Statistical Association.

Leo Breiman trabalhando em sua residência, em 1985.

🎭 As duas culturas


Leo Breiman, renomado estatístico, contribuiu significativamente para o campo de aprendizagem de máquina. Ele é conhecido por ter criado métodos populares e influentes para a área. Entre tais métodos famosos, cito dois:


  1. Random forest (florestas aleatórias): método que combina a previsão de vários modelos de árvores de decisão (decision tree), que veremos mais a frente, por isso o termo “floresta” para problemas de regressão e classificação;

  2. Bootstrap aggregating (bagging): técnica de aprendizagem ensemble, em que cria-se multiplos conjuntos de dados obtidos com reposição da amostra de treinamento. O modelo de aprendizagem de máquina é treinado em cada conjunto de dados e as previsões de cada um dos modelos são combinadas por meio da média (em problemas de regressão), ou por voto majoritário, em problemas de classificação. O bagging é utilizado para reduzir a variância e melhorar a estabilidade do modelo.

Regressão / Parte I

Regressão


Métodos de regressão surgiram há mais de dois séculos com Legendre (1805) e Gauss (1809), que exploraram o método dos mínimos quadrados com o objetivo de prever órbitas ao redor do Sol. Hoje em dia, o problema de estimação de uma função de regressão possui papel central em estatística.


Apesar de as primeiras técnicas para solucionar esse problema datarem de ao menos 200 anos, os avanços computacionais recentes permitiram que novas metodologias fossem exploradas. Em particular, com a capacidade cada vez maior de armazenamento de dados, métodos com menos suposições sobre o verdadeiro estado da natureza ganham cada vez mais espaço. Com isso, vários desafios surgiram: por exemplo, métodos tradicionais não são capazes de lidar de forma satisfatória com bancos de dados em que há mais covariáveis que observações, uma situação muito comum nos dias de hoje. Similarmente, são frequentes as aplicações em que cada observação consiste em uma imagem ou um documento de texto, objetos complexos que levam a análises que requerem metodologias mais elaboradas. – Izbick et al.

Regressão


De forma geral, temos que o objetivo de um modelo de regressão é determinar a relação entre uma variável aleatória (label) Y \in \mathbb{R} e um vetor de covariáveis (features) \mathbf{x} = (x_1, \cdots, x_d) \in \mathbb{R}^d. Mais especificamente, busaca-se estimar

r(\bf{x}) := \mathbb{E}(Y|\bf{X} = \bf{x}),

sendo esta chamada de função de regressão. Temos que:


  1. Se Y é uma variável quantitativa, então estamos sob um problema de regressão;
  2. Se Y é uma variável qualitativa, então teremos um problema de classificação.

Em aprendizagem de máquina, assumimos que não temos meios de calcular r({\bf{x}}), i.e., não conhecemos a distribuição condicional de {\bf{Y}\,|\,X}. Portanto, não temos meios de calcular

\mathbb{E}({\bf X}|Y = y) = \int x\,\mathrm{d}F_{\bf X}({\bf x} | Y = y).

Notações


A variável Y recebe frequentemente o nome de variável resposta, variável dependente, rótulo ou label. Já as observações contidas no vetor \bf{x} = (x_1, \cdots, x_d), são, em geral, denominadas de variáveis explicativas, variáveis independentes, características, atributos, preditores, covariáveis ou features.


A ideia, nessa primeira parte do curso, é descrever algumas técnicas para estimar (treinar, como é dito em aprendizagem de máquina) r(\bf{x}).


A menos quando dito o contrário, assumiremos que nossa amostra são i.i.d. (independentes e identicamente distribuídas), ou seja, (\bf{X}_1, Y_1), \cdots, (\bf{X}_n, Y_n) são i.i.d.


Denota-se por x_{i,j} o valor da j-ésima covariável na i-ésima amostra, com j = 1, \cdots, d e i = 1, \cdots, n.

Notações


Notação utilizada nesse material para as variáveis envolvidas em um problema de regressão.
Label Features
Y_1 X_{1,1},\cdots, X_{1,d}\,\,\, (= \bf{X}_1)
\vdots \,\,\,\vdots\,\,\,\,\, \ddots\,\,\ \vdots
Y_n X_{n,1},\cdots, X_{n,d}\,\,\, (= \bf{X}_n)

Regressão

Nossa ideia é construir uma boa estimativa g da função de regressão r(\bf{x}) := \mathbb{E}(Y\,|\,\bf{X} = \bf{x}), para novas observações, i.e., queremos obter uma função g, tal que:

g: \mathbb{R}^d \rightarrow \mathbb{R},

de tal forma que g possua um bom poder preditivo. Em aprendizagem de máquina, só estaremos interessados em obter uma função g que estime bem um número real (em problemas de regressão), ou que classifique bem (em um problema de classificação), utilizando as d covariáveis. Ou seja, para m novas observações, desejamos obter g, que

g({\bf{x}}_{n + 1}) \approx y_{n + 1}, \cdots, g({\bf{x}}_{n + m}) \approx y_{n + m}.

Função de risco


Para que possamos construir boas funções de predição, é preciso que tenhamos um critério para medir o desempenho de uma dada função g:\mathbb{R}^d \rightarrow \mathbb{R}. Em contexto de regressão, usaremos o risco quadrático, muito embora esta não é a única opção. Denotaremos a função de risco quadrático por:

R_{pred}(g) = \mathbb{E}\left[({\bf Y} - g({\bf X}))^2\right], em que (\bf X, Y) são observações novas que não foram utilizadas para treinar/estimar g. Lê-se R_{pred}(g) como “risco preditivo de g”. Note que, como \bf X são observações conhecidas e g(\cdot) é um modelo preditivo, portanto, g é conhecido, então, \widehat{\bf Y} = g(\bf X) é um estimador dos labels, i.e., de \bf Y.


Diremos que L(g({\bf X}); {\bf Y}) = ({\bf Y} - g({\bf X}))^2 é a função de perda quadrática, as vezes chamado de perda L_2. Outra funções como a função de perda absoluta denotada por L(g({\bf X}); {\bf Y}) = |{\bf Y} - g({\bf X})|, as vezes chamada de perda L_1 poderiam ser consideradas.

Função de risco


Em linhas gerais, seja L(\cdot) uma função qualquer, tal que \forall \, 0 < u < v, de modo que:


  1. 0 = L(0) \leq L(u) \leq L(v);
  2. 0 = L(0) \leq L(-u) \leq L(-v).


Qualquer função L(\cdot) que satisfaz as propriedades acima é chamada de função de perda. Em especial, temos que:


  • Função de perda quadrática: L(u) = u^2;
  • Função de perda absoluta: L(u) = |u|;
  • Função de perda degrau: L(0) = 0, se |u| < \delta e 1 caso contrário, para algum \delta > 0;

Função de risco


Normalmente considera-se a perda L_2, uma vez que em modelos de regressão, minimizar R_{pred}(g), em g, equivale a encontrar r({\bf x}) = \mathbb{E}({\bf X}|{\bf Y}), i.e., equivale a estimar a função de regressão.


Teorema: Suponha que definimos o risco de uma função de predição g: \mathbb{R}^d \rightarrow \mathbb{R} via função perda quadrática, i.e, R_{pred}(g) = \mathbb{E}\left[({\bf Y} - g({\bf X}))^2\right], em que \bf (X, Y) são novas observações que não foram utilizadas para estimar g. Suponha também que estimamos o risco de um estimador de regressão r({\bf X}) via função perda quadrática R_{reg}(g) = \mathbb{E}\left[(r({\bf X}) - g({\bf X}))^2\right]. Então,

R_{pred}(g) = R_{reg}(g) + \mathbb{E}\left[\mathbb{V}[{\bf Y} | {\bf X}]\right],

em que \mathbb{E}\left[\mathbb{V}[{\bf Y} | {\bf X}]\right] é a variância média do modelo que não depende de g. Portanto, estimar bem r({\bf x}) é de fundamental importância para criar uma boa função de predição. Em especial, sob a ótica do risco quadrático, a melhor função de predição para \bf Y é a função de regressão r({\bf x}), de tal modo que:

\argmin_g R_{pred}(g) = \argmin_g R_{reg}(g) = r({\bf x}).

Função de risco


Lembre-se: r({\bf x}) = \mathbb{E}(Y | \bf{X} = \bf{x}) é a nossa função de regressão.


A definição de risco preditivo R_{pred}, que também denotaremos simplesmente por R, tem um apelo frequentista. Dessa forma, para um novo conjunto com m novas observaçõs, ({\bf X}_{n+1}, Y_{n+1}), \cdots, ({\bf X}_{n+m}, Y_{n+m}), temos que essa nova amostra é i.i.d. à amostra observada (utilizada no treinamento do modelo/na estimação). Então, pela Lei dos Grandes Números, temos que um bom estimador para a função para o risco preditivo é dado por:

\frac{1}{m}\sum_{i = 1}^m (Y_{n + i} - g(X_{n + i}))^2 \approx R(g) := \mathbb{E}\left[(Y - g({\bf X}))^2\right]. \tag{1}

Chamaremos a quantidade acima de Erro Quadrático Médio - EQM. Em aprendizagem de máquina, normalmente estaremos no contexto em que temos muitas observações, e que portanto, poderemos fazer esse apelo frequentista.


Desejamos encontrar g (encontrar métodos) que minimize de forma satisfatória R, i.e., métodos que nos conduzam à um risco baixo.

Função de risco


Sendo assim, se R(g) possui um valor baixo, então, temos que

g({\bf x}_{n+1}) \approx y_{n+1}, \cdots, g({\bf x}_{n+m}) \approx y_{n+m}.

Regressão linear


Nesse momento, vamos pensar um pouco em regressão linear. No caso mais simples, queremos prever o comportamento de uma variável de interesse Y condicional a uma variável explicativa X (regressão linear simples, i.e., d = 1). O melhor preditor de Y condicional em X é aquele que minimiza a função de perda esperada, ou seja, é aquele que resolve:

\argmin_g \mathbb{E}(L(Y - g)|X).

Para o caso da função perda quadrática (função L_2), o melhor preditor de Y condicional à X é a média condicional de Y dado X, i.e., r(X) = \mathbb{E}(Y|X). Já, na situação em que considera-se a perda absoluta (função L_1), o melhor estimador é a mediana condicional.


Os modelos de regressão, em geral, fazem uso da função de perda quadrática.

Regressão linear simples


No caso da regressão linear simples (d = 1), temos que o modelo é dado por:

g(x) = \beta_0 + \beta_1 x_{i,1} + \varepsilon_i, \,\, i = 1, \cdots, n, em que \varepsilon_i é um erro aleatório. Na abordagem data modeling culture, várias suposições poderem ser feitas para \varepsilon_i.

Assumindo que a regressão linear simples é o modelo g que iremos utilizar, então, desejamos minimizar:

\argmin_{\beta} R(g_\beta) = \argmin_{\beta} \sum_{i = 1}^n(y_i - \beta_0 - \beta_1x_{i,1})^2. Derivando em relação à \beta e igualando a zero, após algumas manipulações algébricas, temos que:

\widehat{\beta} = \frac{\sum_{i = 1}^n (x_i - \overline{x})(y_i - \overline{y})}{\sum_{i=1}^n(x_i - \overline{x})^2} = r_{xy}\frac{s_y}{s_x}, em que s_x e s_y são os desvio-padrão de x e y, respectivamente, e r_{xy} é o coeficiente de correlação da amostra, em que -1 \leq r_{xy} \leq 1.

Regressão linear simples


r_{xy} = \frac{\overline{xy} - \overline{x}\,\overline{y}}{\sqrt{(\overline{x^2} - \overline{x}^2)(\overline{y^2} - \overline{y}^2)}}. O coeficiente de determinação R^2 do modelo é dado por r_{xy}^2, quando o modelo é linear e possue uma única variável independente (feature).


Portanto, temos que:

\widehat{\beta_0} = \overline{y} - \widehat{\beta}\overline{x},

Na data modeling culture (na estatística), normalmente assumimos que o \varepsilon_i tem distribuição normal e variância constante, \forall\, i = 1, \cdots, n. Assume-se também que \mathbb{E}(\varepsilon_i) = 0, \, \forall i.

Regressão linear simples


Aqui não iremos nos preocupar com essas suposições, uma vez que em algorithmic modeling culture, não estamos preocupados com suposições nem interpretações, ok!?


Regressão linear multipla


A função de perda quadrática (função L_2) tem algumas vantagens em relação a função de perda absoluta. Listo algumas:

  1. A função de perda quadrática penaliza mais os erros maiores, devido ao fato dos erros serem levado ao quadrado;

  2. A função de perda quadrática é mais sensível a presença de outlier, que em compensação são menos penalizados ao se considerar a função de perda absoluta (função L_1);

  3. Em situações em que o erro tem distribuição normal, a estimativa de mínimos quadrados é a solução de máxima verossimilhança e é a estimativa linear não viesada e com menor variância. Portanto, gozamos de um estimador com ótimas propriedades, muito embora ele também é um bom estimador mesmo quando a suposição de normalidade não é verificada;

  4. A função de perda quadrática é deferenciável, já a função de perda absoluta não é.

Para o caso de regressão linear múltipla, i.e., quando d > 1, poderemos utilizar uma notação matricial para representar o modelo linear múltiplo de regressão.

Regressão linear multipla


Considerando o modelo de regressão linear múltiplo, temos que:

Y = g({\bf X}) = \beta^{T}{\bf X} + \varepsilon,

em que Y é um vetor n \times 1, {\bf X} é uma matriz fixa e conhecida com os atributos de dimensão n \times d, em que a primeira coluna é preenchida de 1, \beta = (\beta_0, \cdots, \beta_d). Na cultura de machine learning, iremos desconsiderar \varepsilon, i.e., não feremos suposições sobre \varepsilon. Portanto, considere

g({\bf x}) = \beta^{T}{\bf X} = \beta_{0}x_0 + \beta_1x_{i,1} + \cdots + \beta_dx_{i,d}, em que x_0 \equiv 1.

Regressão linear multipla


O método dos mínimos quadrados, para o caso de regressão linear múltipla (d > 1) é dado por aquele que minimiza R(\beta^{T}{\bf X}), i.e., minimiza:

\argmin_\beta \sum_{i = 1}^n (Y_i - \beta_0 - \beta_1x_{i,1} - \cdots - \beta_dx_{i,d})^2. Temos que

\widehat{\beta} = ({\bf X}^{T}{\bf X})^{-1}{\bf X}^{T}Y.

Portanto, a função de regressão estimada é dada por:

g({\bf x}) = \widehat{\beta}^{T}{\bf x}.

Regressão linear multipla


Grande parte da literatura estatística é voltada para justificar que o método de mínimos quadrados sob um ponto de vista de um estimador de máxima verossimilhança, assim como também para construção de testes de aderência, métodos para construção de intervalos de confiança e teste de hipótese para \beta_i (parâmetros que indexam o modelo), análise de resíduos, entre outros.


Assumir que a verdadeira regressão r({\bf x}) = \mathbb{E}({\bf X}\,|\,Y) é uma suposição muito forte. Contudo, existe, na literatura, justificativas para o uso de métodos de mínimos quadrados para estimar os coeficientes, mesmo quando a regressão real r({\bf x}) não satisfaz a suposição de linearidade.


Regressão linear multipla


O estimador de mínimos quadrados \widehat{\beta} = ({\bf X}^{T}{\bf X})^{-1}{\bf X}^{T}Y é bom, por alguns motivos:


  1. É igual ao estimador de máxima verossimilhança sob normalidade, linearidade e homoscedasticidade, portanto, consistente sob essas condições

  2. É best linear unbiased prediction - BLUE sob linearidade e homoscedasticidade;

  3. O método de mínimos quadrados tem alguma garantia, mesmo sem assumir muitas suposições.


Mínimos quadrados sem suposição de linearidade


Quando a suposição de linearidade falha, ou seja, quando a regressão verdadeira que desconhecemos r({\bf x}) não é linear, frequentemente existe um vetor \beta_{*}, tal que g_{\beta_{*}}({\bf x}) = \beta_{*}^{T}{\bf x} tem um bom poder preditivo. Nesses casos, o métrodo dos mínimos quadrados \widehat{\beta} tende a produzir estimadores com baixo risco. Isso se deve ao fato que \widehat{\beta} converge para o melhor preditor linear (para o oráculo \beta_{*}) que é dado por:

\beta_{*} = \argmin_\beta R(g_\beta) = \argmin_\beta \mathbb{E}\left[(Y - \beta^{T}X)^2\right], mesmo que a verdadeira regressão r({\bf x}) não seja linear, em que ({\bf X}, Y) é uma nova observação.


Teorema: Seja \beta_{*} o melhor estimador linear e \widehat{\beta} o estimador de mínimos quadrados. Então,

\widehat{\beta}\overset{p}{\longrightarrow} \beta_{*}\,\, \mathrm{e}\,\, R(g_{\widehat{\beta}})\overset{p}{\longrightarrow} R(g_{\beta_{*}}), quando n \longrightarrow \infty. Para uma demonstração, veja http://www.rizbicki.ufscar.br/AME.pdf, página. 29.

Mínimos quadrados sem suposição de linearidade


Em palavras, o que o Teorema anterior diz é que mesmo quando a regressão verdadeira não é linear, o estimador de mínimos quadrados é consistente para nos conduzir a um bom estimador linear, ou seja, ao menos conseguiremos o melhor estimador linear como uma aproximação à r({\bf x}) que não é linear.


Isso não quer dizer que você terá boas estimativas em todas as situações, muito embora o oráculo \beta_{*}, em muitas situações, terá bom poder preditivo. Em outras palavras, em situações que um problema, em sua natureza, não linear, poderemos alcançar boas estimativas por uma aproximação linear pelo método dos mínimos quadrados.


Leia mais sobre regressão linear


Caso você deseje ler um pouco mais sobre regressão linear sob homocedasticidade e sob heteroscedasticidades, leia o segundo Capítulo de minha dissertação de mestrado intitulada Estimadores Intervalares sob Heteroscedasticidade de Forma Desconhecida via Bootstrap Duplo. Apesar do título, o segundo capítulo é uma revisão do conceito de regressão linear é apresentado de forma didática. Clique aqui para ler.


Predição versus Inferência


Inferência: assume que o modelo linear é correto. O principal objetivo consiste em interpretar os parâmetros:


  • Quais são os parâmetros significantes?
  • Qual o efeito do aumento da dose de um remédio no paciente?


Predição: queremos criar g({\bf x}) com bom poder preditivo, mesmo que a especificação do modelo não esteja correta. Não assume que a verdadeira regressão é de fato linear! A interpretação aqui não é o foco. Tudo bem?!


Ajustando uma regressão linear no R


Caso você não queira implementar o estimador de mínimos quadrados \widehat{\beta} = ({\bf X}^{T}{\bf X})^{-1}{\bf X}^{T}Y, você poderá utilizar a famosa função lm. Na verdade é melhor que não implemente o estimador \widehat{\beta}, uma vez que a função lm, assim como a função glmnet do pacote glmnet, utilizam-se de truques numéricos para um cálculo mais eficiente.


Falaremos do pacote glmnet, um pouco mais a frente, quando abordarmos regressão penalizada. Certo!?


Ajustando uma regressão linear no R


Considere o conjunto de dados de expectativa de vida versus PIB per Capita disponíveis aqui. O comportamente entre as variáveis LifeExpectancy e GDPercapita, se fizermos um gráfico, não é linear.


Todavia, isso não impede que possamos ajustar um modelo de regressão linear, muito embora o seu poder preditivo será baixo.


Porém, como já sabemos, ao menos conseguiremos o melhor oráculo, denotado por \beta_{*}, i.e., o melhor estimador dentre os possíveis estimadores lineares, como mostrado em teoremas anteriores.


E está tudo bem. Aqui não estou querendo defender que você use uma aproximação linear para esse caso. Em breve, com um pequeno truque, poderemos ajustar uma regressão polinomial à esses dados, e incorporaremos um pouco da tendência não linar presente nos dados.

Ajustando uma regressão linear no R


Veja o código do gráfico
library(ggplot2)

url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"

# Criando um arquivo temporário
arquivo_temp <- tempfile()

# Baixando um arquivo temporário
download.file(url = url, destfile = arquivo_temp)

# Carregando os dados
load(arquivo_temp)

dados_expectativa_renda |> 
  ggplot(aes(x = GDPercapita, y = LifeExpectancy)) +
  geom_point() +
  labs(
    title = "PIB per Capita versus Expectativa de Vida",
    x = "PIB per Capita",
    y = "Expectativa de Vida"
  ) +
  geom_smooth(method = "lm", se = FALSE) +
  theme(
    plot.title = element_text(face = "bold"),
    axis.title = element_text(face = "bold")
  )

Ajustando uma regressão linear no R


Claramente, a reta de regressão (linha azul) do gráfico anterior não tem um bom poder preditivo. O ajuste foi feito diretamente usando o pacote ggplot2, utilizando a função geom_smooth, em que foi escolhido o método "lm".


Poderíamos ter utilizado a função lm:


Veja o código do gráfico
library(ggplot2)

url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"

# Criando um arquivo temporário
arquivo_temp <- tempfile()

# Baixando um arquivo temporário
download.file(url = url, destfile = arquivo_temp)

# Carregando os dados
load(arquivo_temp)

# Ajustando o modelo usando a função lm
ajuste <- lm(LifeExpectancy ~ GDPercapita, data = dados_expectativa_renda)

modelo <- function(x){
  novos_dados <- tibble::tibble(GDPercapita = x)
  predict(ajuste, newdata = novos_dados)
}

dados_expectativa_renda |> 
  ggplot(aes(x = GDPercapita, y = LifeExpectancy)) +
  geom_point() +
  labs(
    title = "PIB per Capita versus Expectativa de Vida",
    x = "PIB per Capita",
    y = "Expectativa de Vida"
  ) +
  stat_function(fun = modelo, color = "red", size = 1.2) +
  theme(
    plot.title = element_text(face = "bold"),
    axis.title = element_text(face = "bold")
  )

Matriz esparsa


Para grandes bases de dados, em um problema real que você venha trabalhar, e se o custo computacional você considera elevado, poderá utilizar o pacote biglm.


Em situações em que há muitos zeros na sua matriz, poderá utilizar representação esparsa.


Matrizes esparsas são matrizes com muitas entradas iguais à 0. Elas ocorrem naturalmente em diversas aplicações, como por exemplo uma matriz de termos presentes em um documento, em que se o termo estiver no documento resebe 1, e zero, caso contrário. Abaixo, {\bf X} é um exemplo de matriz esparsa.


{\bf X} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 3 & 0 & 0 \\ 0 & 0 & 0 & 4 & 0 \\ \end{bmatrix}

Matriz esparsa


Considere os textos:

  1. Texto 1: “Eu amo essa disciplina.”
  2. Texto 2: “Eu adoro meu professor.”
  3. Texto 3: “Eu serei muito bom em aprendizagem de máquina.”
  4. Texto 4: “Adoro o departamento de estatística da UFPB.”


Textos disciplina amo aprendizagem máquina estatistica adoro UFPB
Texto 1 1 1 0 0 0 0 0
Texto 2 0 0 0 0 0 1 0
Texto 3 0 0 1 1 0 0 0
Texto 4 0 0 0 0 1 1 1

Matriz esparsa


A matriz com a ocorrência de determinados termos nos textos é dada por:

{\bf X} = \begin{bmatrix} 1 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 1 & 1 \\ \end{bmatrix}

A representação esparsa de {\bf X}, aqui denotada por {\bf X_*} é:

{\bf X_*} = \begin{bmatrix} 1 & 1 & 1 \\ 2 & 6 & 1 \\ 3 & 3 & 1 \\ 3 & 4 & 1 \\ 4 & 5 & 1 \\ 4 & 6 & 1 \\ 4 & 7 & 1 \\ \end{bmatrix}, em que as duas primeiras colunas, são as linhas e colunas de {\bf X} com valor diferente de 0. A última coluna representa o valor.

Regressão linear com matriz esparsa


Exemplo: Ajuste de um modelo de regerssão linear múltiplo, em que {\bf X} poderá ter uma representação esparsa. Aqui não estamos interessados em verificar qualidade de predições. Trata-se apenas de um exemplo de como utilizar uma representação esparsa para ajustar um modelo de regessão linear com algumas covariáveis, em R.


Estude o código
library(glmnet)
library(Matrix)

# Dados de exemplo
x1 <- c(1, 0, 2, 0, 0)
x2 <- c(0, 3, 0, 4, 0)
x3 <- c(5, 0, 6, 0, 7)
y <- c(1, 2, 3, 4, 5)

# Criar data frame com as variáveis explicativas
dados <- data.frame(x1, x2, x3)

# Converter o data frame para matriz esparsa
X <- sparse.model.matrix(~ ., data = dados)

# Ajustar a regressão linear utilizando glmnet
modelo <- glmnet(x = X, y = y, alpha = 0, lambda = 0)

# Realizar previsões
predicoes <- predict(modelo, newx = X)

Erro quadrático médio


Como exposto anteriormente, para avaliar o poder preditivo de uma modelo, i.e., a aprendizagem de um modelo, devemos avaliar a função de risco, i.e., devemos avaliar R(g) := \mathbb{E}\left[L(g({\bf X}); Y)\right]. Em particular, considere L = L_2 (função perda quadrática). Então, poderíamos ser levados a acreditar que o melhor estimador de R(g), utilizando a Lei dos Grandes Números seria:

\frac{1}{n}\sum_{i = 1}^n(Y_{i} - g({\bf X_{i}}))^2 \approx R(g) := \mathbb{E}\left[L_2(g({\bf X}); Y)\right].


Essa quantidade é chamada, de Erro Quadrático Médio - EQM. Desejamos escolher o melhor mode, entre os modelos testados, que minimiza o EQM.


O apelo frequentista em utilizar a Lei dos Grandes Números na forma acima não é correto, uma vez que usamos as n observações para treinar/ajustar o modelo g.

Erro quadrático médio


Por exemplo, no problema de PIB per Capita versus expectativa de vida, em que consideramos uma aproximação linear, não poderíamos utilizar o EQM da forma acima, com as n observações utilizadas para treinar o modelo. É um detalhe sutil, mas que muitas pessoas cometem esse erro.


Não podemos utilizar as n observações para estimar o risco R(g) através do EQM, uma vez que estamos utilizando o mesmo conjunto de dados para ajustar e avaliar g.


Qual o problema?


  1. Não vale a Lei dos Grandes Números;
  2. Usamos os mesmos valores de {\bf x} e y para treinar e avaliar o modelo.

Erro quadrático médio


O que diz a Lei dos Grandes Números, em particular, a Lei Forte de Kolmogorov?


Teorema (Lei Forte de Kolmogorov): Sejam X_1, \cdots, X_n uma sequência de veriáveis aleatórias - v.a. i.i.d. e integráveis, i.e., com valor esperado limitado, tal que \mathbb{E}(X) = \mu\,\, \forall i. Então,

\frac{X_1 + X_2 + \cdots + X_n}{n} \rightarrow \mu,

quase certamente, i.e., com probabilidade 1.


Note que se desejamos comparar diversos modelos, g_1({\bf x}), g_2({\bf x}), \cdots, e se utilizarmos as mesmas n obervações para calularmos R(g_1({\bf x})), R(g_2({\bf x})), \cdots, os termos de cada uma das somas não são independentes. Lembre-se que desejamos obter \argmin_g R_{pred}(g).

Erro quadrático médio


Portanto, nunca utilize as mesmas observações utilizadas para treinar o modelo, como aquelas que serão utilizadas para se estimar R(g). Nunca! Isso é um pecado mortal! Ok?!


Data Splitting


Corrigir o problema de dependência que há ao estimarmos o risco usando o EQM é fácil. Uma abordagem muito utilizada é utilizar data splitting, também chamado de método hold-out. Algo como a segunda linha da imagem abaixo:


Data Splitting


Essa divisão é feita de forma aleatória, algumas vezes estratificada de acordo com algumas variáváveis. A ideia de aleatorizar é se livrar de problemas de conjunto de dados ordenados. Queremos que tanto no conjunto de treinamento Training quanto no conjunto Testing, na imagem, contenham a mesma diversidade de observações.


Ainda no exemplo de PIB per Capita versus Expectaitiva de Vida, não quero correr o risco de ter no conjunto de treinamento apenas o países com maiores valores de PIB per Capita, caso o conjunto de dados tenha sido ordenado pela variável GDPercapita. Por isso, aleatorizar o conjunto de treinamento e teste é sempre uma ótima ideia. Certo!?


Data Splitting


O percentual de divisão dos dados normalmente é empírico. Usa-se normalmente a proporção de 70\% para treinamento e 30\% para teste (70\%, 30\%). Outros esquemas de divisões são bastante utilizados, por exemplo, (80\%, 20\%), (99\%, 1\%), a depender da quantidade de observações (tamanho do conjunto de dados).


Portanto, utilizar o EQM sob o conjunto de dados de teste para avaliar g_1({\bf x}), g_2({\bf x}), \cdots,, é uma boa estratégia, uma vez que agora não teremos mais uma dependência no numerador do cálculo do EQM. Em notação matemática, poderíamos escrever como já apresentado anteriormente, em Equação 1, i.e.,

\frac{1}{m}\sum_{i = 1}^m (Y_{n + i} - g(X_{n + i}))^2 \approx R(g) := \mathbb{E}\left[(Y - g({\bf X}))^2\right].


Esse resultado valeria para qualquer outra função de perda.

Data Splitting

Reescrevendo, suponha que o conjunto de dados total possua n observações e que separamos aleatoriamente s < n observações para o conjunto de treinamento. Assim, temos, algo como:


\overbrace{(X_1, Y_1), (X_2, Y_2), \cdots, (X_s, Y_s)}^{70\%}, \,\,\, \overbrace{(X_{s + 1}, Y_{s + 1}), (X_{s + 2}, Y_{s + 2}), \cdots, (X_n, Y_n)}^{30\%}.


Então, temos que uma boa estimativa de R(g) é dada pelo EQM calculado sobre o conjunto de dados de teste, que nesse caso considerei o conjunto com 30\%, mas esse percentual poderia ser outro. Então, temos que um bom estimador é:

\frac{1}{n - s}\sum_{i = s + 1}^n (Y_{i} - g(X_{i}))^2 \approx R(g) := \mathbb{E}\left[(Y - g({\bf X}))^2\right].

Data Splitting


Agora você entende por que dividimos os dados em treinamento e teste?



Dividimos para obermos um bom estimador do risco utilizando o EQM. 🎁

Data Splitting


Podemos argumentar que o procedimento de data splitting, em que dividimos o nosso conjunto de dados em treinamento e teste fará com que venhamos perder muitas observações que poderiam ter sido utilizadas para treinar o modelo. E de certa forma isso é verdade, principalmente quando termos um conjunto não muito grande de observações.


Portanto, uma melhor abordagem, sendo esta uma variação do método de data splitting é o procedimento de cross-validation - cv (validação cruzada). Uma versão mais geral de uma validação cruzada é o leave-one-out cross-validation.


Em palavras, o procedimento consiste em tirar de fora uma única observação das n observações da base de dados para ser o nosso conjunto de teste e treinar o modelo com as observações que permaneceram. Daí, calcula-se o risco observado (EQM, sob o conjunto de teste/validação). Na segunda iteração, a observação que antes era de teste volta para perterncer ao conjunto de treinamento e uma nova observação é removida para ser teste. Esse procedimento ocorre de forma iterativa até a retirada da última observação como teste.

Leave-one-out cross-validation


Observe a animação abaixo que ilustra o procedimento de leave-one-out cross-validation - LOOCV, em uma amostra de tamanho n = 8. Ao fim, teremos n modelos ajustados, em que calculamos as suas respectivas performances, i.e., com o risco observado, estimamos o risco de R(g).


Leave-one-out cross-validation


Vejo muitas pessoas que usam uma validação cruzada, por exemplo, leave-one-out cross-validation - LOOCV comparando com o método Jackknife e algumas inclusive dizendo ser a mesma coisa. Não, não são!


O algoritmo Jackknife é um procedimento de estimação, e que, por sua vez, deve estar dentro do conjunto de treinamento. Para haver algum Jackknife, a estimativa com n-1 observações deve estar dentro do conjunto de treinamento, em que dentro do treinamento teria a remoção de um observação por vez. Consegue perceber a diferença sutil?


Leave-one-out cross-validation


O método LOOCV foi proposto por Stones (1974), no artigo intitulado Cross-Validatory Choice and Assessment of Statistical Predictions, no Royal Statistical Society, Série B. Clique aqui se tiver curiosidade em ler o artigo.


Escrevendo o estimador do risco em um procedimento de LOOCV, temos que:

\widehat{R}(g) = \frac{1}{n}\sum_{i = 1}^n (Y_i - g_{-i}({\bf X}_i))^2, em que g_{-i}(\bf{X}_i), representa o ajuste do modelo no conjunto de dados sem a i-ésima observação.


Não é difícil perceber que a depender do valor de n, o método LOOCV é computacionalmente intensivo. O método requer que ajustemos n modelos. Em algumas situações isso não é um grande problema, porém, em diversas outras pode ser impeditivo utilizar o LOOCV. 🤯

Leave-one-out cross-validation


O método LOOCV converge assintotivamente para o AIC, porém, este, muitas vezes não poderemos calcular diretamente, uma vez que não conhecemos a distribuição conjunta dos dados, i.e., não conhecemos a estrutura probabilística.


Leave-one-out cross-validation


Essa relação entre o LOOCV e o Akaike Information Criterion - AIC foi provada no paper Stone (1977) intitulado An Asymptotic Equivalence of Choice of Model by Cross-Validation and Akaike’s Criterion e publicado no Journal of the Royal Statistical Society, Series B. Clique aqui, se quiser ler o artigo.




k-fold cross-validation


Uma alternativa ao LOOCV é utilizar o método k-fold cross-validation. Nessa abordagem, dividimos o conjunto de dados em k-folds (lotes) disjuntos e com aproximadamente o mesmo tamanho. Dessa forma, temos L_1, \cdots, L_k \subset \{1, \cdots, n\} são, cada um, um conjunto de índices aleatórios associados a cada um dos lotes. A ideia aqui é construir k estimadores da função de regressão, denotados por \widehat{g}_{-1}, \cdots, \widehat{g}_{-k}, em que \widehat{g}_{-j} é criado usando todas as observações do banco de dados, com exceção daquelas do lote L_j, utilizado para validação. O estimador do risco é dado por:

\widehat{R}(g) = \frac{1}{n}\sum_{j=1}^k \sum_{i \in L_j}(Y_i - g_{-j}({\bf X}_i))^2. Perceba que, que o LOOCV é um caso particular do k-fold cross-validation, quando fazemos k = n. Em outras palavras, L_1, \cdots, L_k \subset \{1, \cdots, n\} representam os índices aleatórios do conjunto de treinamento nos k lotes.

k-fold cross-validation

A animação abaixo, ilustra o procedimento de 3-fold cross-validation (k = 3), para uma amostra de tamanho n = 12 observações. Note que os valores que pertencem a cada um dos lotes são aleatórios. Portanto, o procedimento LOOCV é deterministico, já o procedimento de k-fold cross-validation é randomizado.


Perceba que teremos agora apenas 3 modelos. Para cada um desses lotes, calulamos o EQM com o conjunto de teste (parte azul) e treinamos o modelo com o conjunto de treinamento (parte vermelha).

k-fold cross-validation


Muitos modelos mais sofisticados apresentam hiperparâmetros (parâmetros de sintonização) que não dependem dos dados. É muito comum os algoritmos de aprendizagem de máquina se utilizarem do procedimento de validação cruzada, para além da estimação do risco R(g) no conjunto de validação.


Ao estimar k modelos, normalmente faz-se um grid de possíveis valores para esses hiperparâmetros em que ao final, escolhe-se como hiperparâmetro o modelo com menor EQM. Por fim, ajusta-se um modelo final, com todo o conjunto de treinamento usando o valor do hiperparâmetro que retornou o menor EQM no conjunto de validação.


Alias, utilizamos um procedimento de validação para selecionar a melhor combinação de hiperparâmetros e é será risco preditivo observado sob o conjunto de teste que realmente irá nos fornecer uma estimativa válida do risco preditivo R(g).


k-fold cross-validation


O termo validação refere-se à parcela do conjunto de treinamento incial que dividimos em validação e treinamento, dentro de uma validação cruzada.


O conjunto Testing na segunda hierarquia da árvore ao lado, só usamos no final para avaliar o desempenho do modelo nesse conjunto. Isto é, usamos o Testing para o cálculo do risco observado.


Perceba que o conjunto de treinamento (Not Testing) é particionado em treinamento e validação. Poderíamos fazer uma única partição, mas o procedimento comumente utilizado é particionar entre Training e Validation uzando algum procedimento de validação cruzada.

k-fold cross-validation


Em alguns livros o conjunto de treinamento é denominado de not-testing (não-teste). Isso, porquê eles querem enfatizar o fato de que o conjunto not-testing é utilizado para treinar/ensinar o modelo e jamais deverá ser utilizado para avaliar o risco preditivo R(g).


Ao usar essa terminologia, os autores dos livros tentam enfatizar que o conjunto de treinamento é usado exclusivamente para ensinar o modelo a aprender os padrões nos dados, enquanto o conjunto de teste é usado para medir quão bem o modelo generaliza esses padrões para dados não vistos anteriormente.


k-fold cross-validation

A imagem abaixo ilustra o procedimento k-fold cross-validation, em que uma 5-fold cross-validation é realizada dentro do conjunto de treinamento. Em cada split, o conjunto verde de observações (fold verde) são utilizados para treinar/ajustar o modelo e o conjunto azul, em cada um dos splits é utilizado para avaliar o risco preditivo R(g) (através, por exemplo do EQM).

Não confunda os folds azuis com o conjunto de teste (Test data), este último utilizado por fim, depois do modelo pronto, para avaliar o desempenho do modelo treinado.

Note também que a validação cruzada também é utilizada para o ajuste de hiperparâmetros, que são parâmetros de sintonização que não dependem dos dados para serem equalizados. Por exemplo, em uma regressão lasso, que veremos adiante, há o hiperparâmetro \lambda que precisamos obter, normalmente por meio de um grid search (sequência finita), por exemplo, \lambda \in [0.5, 1, 1.5, 2, 2.5] de possíveis valores. Cada split pode ser utilizado para avaliar um valor de \lambda, dos possíveis valores dispostos no grid. Aumentaríamos a quantidade de splits para mais valores de \lambda na sequência.

Resumindo: data splitting, validação cruzada e conjunto de validação

O simples procedimento de dividir o conjunto de dados em dois, uma parte para treinar o modelo e a outra parte (conjunto de teste) para estimar o risco R(g) é denominado de data splitting ou hold-out method. É um procedimento mais simples, porém, pode não ser útil em conjunto de dados não muito grandes.

A segunda linha da ilustração, demonstra o procedimento de cross-validation (validação cruzada), procedimento mais utilizado nos treinamentos de modelos de aprendizagem de máquina.

A terceira linha é uma abordagem também utilizada, porém não tão interessante quanto a validação cruzada. Nessa abordagem o banco de dados é dividido aleatoriamente em três partes. Treina-se o modelo com a parte verde, estima-se o risco com o conjunto de validação amarelo e testa-se o modelo com o conjunto de teste.

Resumindo: data splitting, validação cruzada e conjunto de validação


A abordagem do conjunto de validação envolve dividir o conjunto de treinamento em duas partes: uma parte é usada para treinar o modelo e a outra parte é usada para avaliar o desempenho do modelo para uma dada combinação. de hiperparâmetros. O conjunto de validação é utilizado para avaliar os hiperparâmetros do modelo, como a taxa de aprendizado, o número de camadas ocultas em uma rede neural, entre outros. Após o ajuste dos hiperparâmetros, o modelo final é treinado com o conjunto de treinamento completo e avaliado em um conjunto separado chamado conjunto de teste. Essa abordagem é conhecida como divisão simples de treinamento/validação/teste.


Por outro lado, a validação cruzada k-fold é uma abordagem que visa obter uma estimativa mais robusta do desempenho do modelo. Nessa abordagem, o conjunto de treinamento é dividido em k subconjuntos (folds) de tamanho aproximadamente igual. O modelo é treinado k vezes, cada vez usando k-1 folds como conjunto de treinamento e 1 fold como conjunto de validação. O desempenho do modelo é então calculado como a média dos resultados obtidos em cada iteração. Isso permite avaliar o modelo, em diferentes combinações dos hiperparâmetros, de forma mais precisa, pois utiliza todos os dados para treinamento e validação, evitando a dependência de uma única divisão do conjunto de treinamento.

Resumindo: data splitting, validação cruzada e conjunto de validação


A validação cruzada k-fold é particularmente útil quando o conjunto de dados é limitado, pois aproveita ao máximo os dados disponíveis. Além disso, ela permite verificar se o modelo é estável e se seu desempenho varia significativamente com diferentes divisões dos dados. É importante ressaltar que a validação cruzada k-fold pode ser computacionalmente mais cara do que a abordagem do conjunto de validação, uma vez que envolve treinar e avaliar o modelo várias vezes.


Resumindo: data splitting, validação cruzada e conjunto de validação


Um outro detalhe que muitas vezes não é falado é que apesar de temos duas tarefas de estimação, uma envolvendo o conjunto de treinamento, em que treinamos o modelo e outra envolvendo o conjunto de teste, em que queremos estimar o risco R(g), de modo a poder selecionar o melhor modelo, a segunda tarefa é bem mais fácil. É por isso que o conjunto de treinamento tende a ser menor que o conjunto de teste.


Resumindo: data splitting, validação cruzada e conjunto de validação


No tidymodels, utilizamos a biblioteca rsample para realizar procedimentos de validação cruzada:


  1. initial_split(): útil para uma divisão inicial dos dados, i.e., para aplicação do hold-out;

  2. loo_cv(): se desejar realizar um procedimento de leave-one-out cross-validation;

  3. vfold_cv(): para um procedimento de k-folds cross-validation. Podemos inclusive realizar várias repetições de validação cruzada o que poderá remelhorar ainda mais a seleção da melhor combinação de hiperparâmetros. O número de repetições de validação cruzada poderá ser especificado no argumento repeats que por padrão é 1L;

  4. bootstraps(): se for desejado utilizar um procedimento de bootstrap não-paramétrico ao invés de uma validação cruzada. O papel do bootstrap é o mesmo da validação cruzada, porém, a seleção das amostras de treinamento é de mesmo tamanho da amostra de treinamento original e o procedimento é feito com reposição, i.e., os mesmos dados podem aparecer nas amostras. O conjunto de avaliação é definido como as linhas dos dados originais que não foram incluídos na amostra bootstrap. Isso geralmente é chamado de amostra out-of-bag - OOB. Podemos alterar o número de amostras bootstrap modificando o argumento times que por padrão é 25L.


Lembre-se que qualquer procedimento de validação cruzada que você escolher deve ser realizado no conjunto de treinamento! 📌

⚖️ Balanço viés e variância


A ideia de precisão e exatidão estão ligadas ao viés e variância do modelo g, em que precisão está ligado a ideia de variância pequena e exatidão está ligada a ideia de baixo viés. A ideia é termos um estimador próximo o que ilustra o item d. Muitas vezes temos um estimador nas situações b e c. O ideal é o balanço de viés e variância, que seria o estimador ilustrado pelo item d.

⚖️ Balanço viés e variância


Um grande apelo para o uso do risco quadrático, i.e., risco que utiliza a função de perda L_2 é sua interpretabilidade. Temos que o risco quadrático R(g) condicional a um novo {\bf x} poderá ser decomposto por:

\mathbb{E}\left[(Y - \widehat{g}({\bf X}))^2| {\bf X} = {\bf x}\right] = \underbrace{\mathbb{V}[Y | {\bf X = x}]}_{\mathrm{i - Variância\,\, intrínseca}} + \overbrace{(r({\bf x}) - \mathbb{E}[\widehat{g}({\bf x})])^2}^{\mathrm{ii - Viés\, ao\, quadrado\, do\, modelo}} + \underbrace{\mathbb{V}[\widehat{g}({\bf x})]}_{\mathrm{iii - Variância\, do\, modelo}}. \tag{2} Temos que:


i - É a variância intrínseca da vairável resposta (label), que não depende da função \widehat{g} escolhida e, assim, não poderá ser reduzida. Na verdade, poderemos reduzir i, se incluirmos mais features (covariáveis/variáveis explicativas) ao nosso modelo;

ii - É o viés ao quadrado do estimador \widehat{g} (viés ao quadrado do modelo);

iii - É a variância do estimador \widehat{g}.

⚖️ Balanço viés e variância


Assim, lembre-se que uma escolha adequada de \widehat{g} nos garante que conseguiremos reduzir o risco preditivo R(g), pois a escolha apropriada implica em escolhermos um estimador de \widehat{g} com balanço entre víes e variância.


Modelos com muitos parâmetros possuem viés relativamente baixo, porém, tendem a ter variância muito alta, em geral, uma vez que precisamos estimar muitos parâmetros. Já modelos com poucos parâmetros, normalmente tendem a ter variância baixa, acompanhados normalmente de um alto viés.


Geralmente, modelos com muitos parâmetros nos levam a termos overffiting (super-ajuste), o que não é bom pois são acompanhados de alta variância. Já modelos muito simplistas nos conduzem à um ajuste muito ruim (underffiting ou sub-ajuste). Entendeu!?

⚖️ Balanço viés e variância


Estude o código
library(ggplot2)
library(tibble)
library(ggplot2)
library(patchwork)

# Função de regressão verdadeira. Na prática é desconhecida.
regressao_verdadeira <- function(x)
  45 * tanh(x/1.9 - 7) + 57

observacoes_regressao_real <- function(n, desvio_padrao = 0.2) {
  # Permitindo que o mesmo x possa ter dois pontos de y, como ocorre na 
  # pratica
  seq_x <- sample(seq(0, 17.5, length.out = n), size = n, replace = TRUE)
  
  step <- function(x)
    regressao_verdadeira(x) + rnorm(n = 1L, mean = 0, sd = desvio_padrao)
  
  tibble::tibble(y = purrr::map_vec(.x = seq_x, .f = step), x = seq_x)
}

# Usaremos uma regressão polinomial para tentar ajustar à regressão -------
regressao_polinomial <- function(n = 30L, desvio_padrao = 4, grau = 1L) {
  
  dados <- observacoes_regressao_real(n = n, desvio_padrao = desvio_padrao)
    
  iteracoes <- function(tibble_data, grau) {
      x <- tibble_data$x
      iteracoes <- lapply(X = 2L:grau, FUN = function(i) x^i)
      
      result <- cbind(tibble_data, do.call(cbind, iteracoes))
      colnames(result)[(ncol(tibble_data) + 1):ncol(result)] <- paste0("x", 2L:grau)
      
      as_tibble(result)
  }  
  
  if(grau >= 2L)
    dados <- iteracoes(dados, grau = grau)
  
  ajuste <- lm(formula = y ~ ., data = dados)
  dados$y_chapeu <- predict(ajuste, new.data = dados)
  
  dados |> 
    dplyr::relocate(y_chapeu, .before = x)
}

plotando <- function(dados){
  dados |>  
    ggplot(aes(x = x, y = y_chapeu)) +
    geom_point()
}

mc_ajustes <- function(mc = 100L, n = 50L, desvio_padrao = 5, grau = 1L){

  p <- 
    ggplot(data = NULL) +
      coord_cartesian(xlim = c(0, 17.5), ylim = c(0, 110)) +      
      ylab("Valores estimados")
  
  df <- NULL
  for(i in 1L:mc){
    df <- regressao_polinomial(n = n, desvio_padrao = desvio_padrao, grau = grau)
    p <- p + geom_line(data = df, aes(x = x, y = y_chapeu))
  }
  p + 
    stat_function(fun = regressao_verdadeira, col = "red", size= 1.4) +
    labs(
      title = "Regressão Polinomial",
      subtitle = paste("Grau: ", grau)
    ) +
    theme(
      plot.title = element_text(face = "bold"),
      axis.title = element_text(face = "bold")
    )
}

# Fixando uma semente
set.seed(0)

p1 <- mc_ajustes(grau = 1, n = 100, desvio_padrao = 10)
p2 <- mc_ajustes(grau = 7, n = 100, desvio_padrao = 10)
p3 <- mc_ajustes(grau = 70, n = 100, desvio_padrao = 10)
p4 <- mc_ajustes(grau = 200, n = 100, desvio_padrao = 10)

p <- ((p1 | p2) / (p3 | p4)) + plot_annotation(tag_levels = "A")

ggsave(p, file = "imgs/vies_variancia.png", device = "png", width = 40, height = 30, units = "cm")

⚖️ Balanço viés e variância


Experimente de forma interativa altera a complefixade do modelo.



Para ampliar a aplicação, clique aqui.

🎛 Tuning Parameters


No exemplo anterior, note que os parâmetros dos modelos são os coeficientes \beta’s que indexam a regressão polinomial. Porém, perceba que á um parâmetro de sintonização (tuning parameter) que é o valor de p, isto é, qual o grau do polinômio que iremos utilizar.


Normalmente a escolha é feita realizando um grid search por meio de um cross-validation.


No exemplo anterior, fizemos uma simulação e observamos que ao considerar graus nem muito grandes nem muito pequenos, aparentemente teremos escolhas razoáveis.


🎛 Tuning Parameters


Exemplo: Consedere os dados de expectativa de vida versus PIB per Capita, disponíveis aqui. Selecione o melhor estimador g da classe \mathbb{G}, em que

\mathbb{G} = \left\{g(x)\,\,:\,\, \beta_0 + \sum_{i = 1}^p \beta_i x^i\,\, \text{para } p \in \{1, 2, \cdots,11\} \right\}. Note que selecionar o melhor polinômio é uma busca em p. Devemos utilizar o erro quadrático médio - EQM sob o conjunto de validação, uma vez que sabemos que apenas em um conjunto de validação ou em novas observações o estimador do risco pelo EQM é consistente, pela Lei dos Grandes Números.


Vamos utilizar a biblioteca rsample para a tarefa de validação cruzada. Leia a documentação da biblioteca, em especial, a da função vfold_cv, responsável por construir a validação cruzada. Na verdade ela faz a divisão da base de dados em v splits de tamanho aproximadamente iguais. Por padrão, v = 10. Esse é o procedimento de k-fold cross-validation que apresentamos aqui, em que v = k.

🎛 Tuning Parameters


Algumas observações gerais a respetio da biblioteca rsample, que são úteis para resolver esse problema:

  1. Para realizar uma primeira divisão do conjunto de dados (data splitting/hold-out), utiliza-se a função initial_split;
  2. Para acessar o conjunto de treinamento dos dados, usamos a função training;
  3. Para acessar o conjunto de teste, usamos a função testing;
  4. Para constuir todas as divisões da validação cruzada, entre treinamento e validação, no conjunto de treinamento inicial, usamos a função vfold_cv já mencionada;
  5. Para acessar o conjunto de treinamento de um split da validação cruzada, usamos a função analysis;
  6. Para acessar o conjunto de validação, utilizamos a função assessment.

🎛 Tuning Parameters


Note que realizar uma validação cruzada é importante para podemos selecionar o melhor polinômio, i.e., o melhor valor de p. Caso venhamos negligenciar esse aspecto da análise, iremos cair na falácia de acreditarmos que quanto maior o grau do polinômio, maior será o poder preditivo do modelo. Isso não é verdade e você deverá selecionar o melhor modelo dentro de um esquema de validação cruzada.


No mundo de aprendizagem de máquina, muitos chamam o processo de encontrar o melhor hiperparâmetro de “tunagem”. Em vários modelos, podemos ter mais de um.

🎛 Tuning Parameters


As Figuras abaixo, mostram a avaliação dos polinômios da classe \mathbb{G}, usando o risco estimado \widehat{R}(g) pelo erro quadrático médio - EQM. Porém, a Figura A aprenseta a avaliação dos modelos, usando simplesmente o conjunto de treinamento e a Figura B aprensenta a avaliação do grau do polinômio considerando uma validação cruzada dentro do conjunto de treinamento.



A mensagem equivocada passada pela Figura A é que supostamente aumentar a complexidade do modelo seria uma uma boa alternativa e nos conduziríamos à bons modelos preditivos. Mas sempre se lembre do equilíbrio que temos que ter entre viés e variância. A Figura B mostra que um polinômio com grau próximo à p = 8 é a melhor alternativa.

🎛 Tuning Parameters


Observe o dashboard interativo! Sabemos que para que tenhamos uma boa estimativa do risco preditivo, devemos utilizar novas observações. No dashboard, é possível observar que a forma errada (usando o conjunto de treinamento para avaliar o risco), sugere que sempre será bom adicionar mais parâmetros ao modelo, levando a overfitting. Perceba que usando a forma correta (usando validação cruzada), o EQM (risco estimado) sugere que não podemos aumentar muito a quantidade de parâmetros.


🎛 Tuning Parameters


Abaixo você poderá acessar o código que soluciona o problema. O parâmetro errado = FALSE da função validação no código que segue, conduz a solução correta (usando a validação cruzada), que sempre você deverá considerar na prática.


Estude o código da solução de exemplo
library(rsample)
library(yardstick)
library(tibble)
library(purrr)
library(ggplot2)
library(patchwork)

# Lendo dados
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados <- 
  dados_expectativa_renda |> 
  dplyr::select(-CountryName) |> 
  dplyr::rename(y = LifeExpectancy, x = GDPercapita)
  
iteracoes <- function(tibble_data, grau) {
  x <- tibble_data$x
  iteracoes <- lapply(X = 2L:grau, FUN = function(i) x^i)
  
  result <- cbind(tibble_data, do.call(cbind, iteracoes))
  colnames(result)[(ncol(tibble_data) + 1):ncol(result)] <- paste0("x", 2L:grau)
  
  as_tibble(result)
}  

regressao_polinomial <- function(dados, grau = 1L) {
  if(grau >= 2L)
    dados <- iteracoes(dados, grau = grau)
  
  lm(formula = y ~ ., data = dados)
}

# Divisão dos dados
divisao_inicial <- rsample::initial_split(dados)
treinamento <- rsample::training(divisao_inicial)
teste <- rsample::testing(divisao_inicial) # Teste final

# v-folds cross-validation
validacao <- function(dados, grau = 1L, errado = FALSE, ...){
  
  # Todas as divisões da validacao cruzada
  cv <- rsample::vfold_cv(dados, ...)
  
  hiper <- function(i){
    treino <- rsample::analysis(cv$splits[[i]]) # Treinamento
    validacao <- rsample::assessment(cv$splits[[i]]) # Validacação
    ajuste <- regressao_polinomial(dados = treino, grau = grau)
    
    if(errado){
      df_treino <- iteracoes(treino, grau = grau)
      df_treino <- df_treino |> dplyr::mutate(y_chapeu = predict(ajuste, newdata = df_treino))
      yardstick::rmse(data = df_treino, truth = y, estimate = y_chapeu)$.estimate
    } else {
      df_validacao <- iteracoes(validacao, grau = grau)
      df_validacao <- df_validacao |> dplyr::mutate(y_chapeu = predict(ajuste, newdata = df_validacao))
      yardstick::rmse(data = df_validacao, truth = y, estimate = y_chapeu)$.estimate
    }
  }
  purrr::map_dbl(.x = seq_along(cv$splits), .f = hiper) |> 
    mean()
}

plot_avaliacao <- function(dados, errado = FALSE){
  # Testando iterativamente, vários valores de p:
  p <- seq(1L:11L)
  risco <- purrr::map_dbl(.x = p, .f = \(p) validacao(dados = dados, grau = p, errado = errado))
  df_risco <- tibble::tibble(p = p, risco = risco)
  
  # Plotando
  df_risco |> 
    ggplot(aes(x = p, y = risco, color = risco)) +
    geom_point(size = 5) +
    scale_x_continuous(breaks = p) +
    scale_y_continuous(breaks = p) +
    labs(
      title = "Valiando o risco estimado para diversos graus do polinômio",
      subtitle = "EQM no conjunto de validação"
    ) +
    theme(
      plot.title = element_text(size = 18, face = "bold"),
      plot.subtitle = element_text(size = 16),
      axis.text = element_text(size = 10), 
      axis.title = element_text(size = 14, face = "bold")
    )
}

# Avaliacão errada versus correta
set.seed(0)
grafico <- 
  plot_avaliacao(dados, errado = TRUE) + 
  plot_avaliacao(dados, errado = FALSE) +
  plot_annotation(tag_levels = c("A", "B"))

ggsave(grafico, file = "imgs/avaliacao_risco.png", device = "png", width = 50, height = 20, units = "cm", limitsize = F)

plot_bar <- function(grau){
  ruim <- validacao(dados, errado = TRUE, grau = grau)
  bom <- validacao(dados, errado = FALSE, grau = grau)
  df <- tibble::tibble(x = c("Errado", "Certo"), y = c(log(ruim), log(bom)))
  
  df |> 
    ggplot(aes(x = x, y = y)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label = y), vjust = 0)
}

📚 Exercícios


Exercício: Explique resumidamente o que é aprendizagem supervisionada e não-supervisionada. Cite um problema de aprendizagem supervisionada e um outro de aprendizagem não-supervisionada.


Exercício: Considere o conjunto de dados de Expectativa de vida versus PIB per Capita, disponível aqui. Considere a função g, da seguinte forma:

g(x) = \beta_0 + \sum_{i = 1}^p \beta_i x^i, com p \in \{1, 2, ..., 50\}. Utilizando o erro quadrático médio observado, sem fazer nenhuma estratégia de divisão dos dados, implemente um código em R para checar qual o melhor modelo.


Exercício: Explique qual o motivo que faz com que o Erro Quadrático Médio - EQM para avaliar o desempenho de um modelo é ruim quando não adotamos nenhuma estratégia de divisão do conjunto de dados em treinamento e teste.

📚 Exercícios


Exercício: Com suas palavras, explique o dilema de balanço entre víes e variância.


Exercício: Refaça o exercício do polinômio, utilizando a estratégia de data splitting, em que divide-se o conjunto de dados em treinamento e teste. Utilize o conjunto de teste para calcular a estimativa do risco, usando o EQM.


Exercício: Ainda considerando o exercício do polinômio, implemente uma estratégia de leave-one-out cross-validation e selecione o melhor modelo minimizando a função de risco.


Exercício: Por fim, considerando o exercício do polinômio, rafaça-o utilizando um procedimento de k-fold cross-validation. Considere k = 5. Dica: considere utiliza a biblioteca rsample.

Melhor subconjunto de covariáveis


O estimador de mínimos quadrados - EMQ, na presença de muitas features (covariáveis), i.e., quando temos d grande, possui um baixo poder preditivo devido a possibilidade de overfitting (super-ajuste). Isso, porquê haverá muitos parâmetros a serem estimados, e portanto, a função de regressão estimada \widehat{r}({\bf x}) terá baixo poder preditivo.


Isso se deve ao fato do balanço de viés e variância. Havendo muitos parâmetros, como já tinhamos visto, a variância do modelo poderá ser muito alta.


Portanto, deveremos buscar meios de encontrar o melhor (ao menos um bom) conjunto de covariáveis. Queremos diminuir a variância do Estimador de Mínimos Quadrados!


Melhor subconjunto de covariáveis


A ideia para resolver esse problema é retirar algumas covariáveis do modelo de regressão, com o objetivo de diminuir a variância de \widehat{g}.


Você poderá entender que estamos em busca de um estimador \widehat{g} de g um pouco mais viesado. Trata-se de uma troca em que desejamos reduzir substancialmente a variabilidade do estimador do modelo em troca de um pouco mais de viés.


Melhor subconjunto de covariáveis


Matematicamente, uma maneira de fazer isso, é buscar a estimativa para

\widehat{\beta}_{L_0} = \argmin_{\beta_0 \in \mathbb{R}, \beta \in \mathbb{R}^d}\sum_{k = 1}^n\overbrace{\left(y_k - \beta_0 - \sum_{i = 1}^d \beta_i x_{k,i}\right)^2}^{n \times EQM} + \lambda \,\,\underbrace{\sum_{i = 1}^d \mathbb{I}(\beta_i \neq 0)}_{\text{Penalização}}. \tag{3}

Note que a penalização \sum_{i = 1}^d \mathbb{I}(\beta_i \neq 0) nos conduz na direção de modelos com poucas covariáveis, quando \lambda é um valor alto. Em particualr, quando \lambda \to \infty, forçamos a retirada de todas as covariáveis \beta_i’s, i.e., a solução para o problema seria \widehat{\beta}_{L_0} \equiv (\overline{y}, {\bf 0}). Note que não há penalização para o intercepto \beta_0.


No outro extremo, para \lambda = 0, temos o estimador de mínimos quadrados, em que nenhuma penalização será considerada.


Não há uma forma fácil de minimizar \widehat{\beta}_{L_0}. 🤯

Melhor subconjunto de covariáveis


Poderíamos, ingenuamente, pensar em ajustar todas as combinações possíveis de parâmetros e utilizar algum critério, por exemplo, o EQM em novas observações para escolher o melhor modelo entre todas as combinações possíveis. Isto é, escolher o melhor modelo entre todas as 2^d combinações possíveis de modelos em \mathbb{G}.


Se \widehat{\lambda} = \frac{2}{n}\widehat{\sigma}^2, estimar \widehat{\beta}_{L_0} equivale uma busca entre 2^d modelos da classe \mathbb{G}:


\begin{align*} \mathbb{G} = \{ &g({\bf x}) = \widehat{\beta}_0, \\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1,\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_2x_2,\\ &\cdots\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_dx_d,\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_2x_2,\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_3x_3,\\ &\cdots\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_dx_d,\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_2x_2 + \widehat{\beta}_3x_3,\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_2x_2 + \widehat{\beta}_dx_d,\\ &\cdots\\ &g({\bf x}) = \widehat{\beta}_0 + \widehat{\beta}_1x_1 + \widehat{\beta}_2x_2 + \widehat{\beta}_3x_3 + \cdots + &\widehat{\beta}_dx_d \}. \end{align*}

Melhor subconjunto de covariáveis


Utilizar \lambda = \frac{2}{n}\widehat{\sigma}^2 é o mesmo que utilizar o critério AIC para determinar o melhor modelo, em que dado

\widehat{R}(g) = \frac{1}{m}\sum_{k = 1}^m \underbrace{(\widetilde{Y}_k - g({\bf \widetilde{X}}_k))^2}_{W_k}, em que (\widetilde{{\bf X}}_1, \widetilde{Y}_1), \cdots, (\widetilde{{\bf X}}_m, \widetilde{Y}_m), representa o conjunto de teste, i.e., calculado com base em m observações em um conjundo de dados não utilizados para treinar o modelo, independentemente da estratégia de divisão utilizada, em que

\widehat{\sigma}^2 = \frac{1}{m}\sum_{k = 1}^m (W_k - \overline{W})^2, com \overline{W} = \frac{1}{m}\sum_{k=1}^m W_k.

Melhor subconjunto de covariáveis


Temos que \widehat{R}(g), calculado em novas observações (observações não utilizadas no treinamento), pode se valer do Teorema Central do Limite, uma vez que \widehat{R}(g) é calculado em uma sequência de variáveis aleatórias i.i.d.’s. Então:

\widehat{R}(g) \sim \text{Normal}\left(R(g), \frac{1}{m}\mathbb{V}[W_1]\right).

Portanto, um intervalo aleatório de aproximadamente 95\% de confiança para o erro preditivo R(g) poderá ser calculado como:

\widehat{R}(g) \pm 1,645 \sqrt{\frac{1}{m}\widehat{\sigma}^2}.

O cálculo de um intervalo de confiança poderá ser útil para entendermos como está variando o risco preditivo do nosso modelo. Gostamos de ter modelos com intervalo de amplitude pequena. O intervalo poderá ser utilizado para fornecer insight de como escolher a divisão de treinamento e teste. Por exemplo, pode-se escolher o menor valor de m de modo que a amplitude seja a menor possível. 💭💡

Melhor subconjunto de covariáveis


Por que essa seria uma escolha ingênua? Pense na situação em que temos d = 30, i.e., trinta covariáveis. Teríamos portanto 2^{30} modelos para ajustar, ou seja, um bilhão e setenta e três milhões, setecentos e quarenta e um mil, oitocentos e vinte e quatro modelos para ajustar. É um a quantidade absurda de modelos para serem estimados!


Se d = 100, teríamos que estimar uma quantidade de modelos que a quantidade estimada de estrelas no universo. A depender do número de covariáveis, esqueça a ideia de estimar 2^d modelos! Não permita se frustrar facilmente. 😏


Regressão Stepwise


Devido a impossibilidade de experimentar uma grande quantidade de modelos (2^d), existe uma série de algoritmos (heurísticas), que visam reduzir a quantidade de modelos avaliados. Um dos mais conhecidos é o forward stepwise. Trata-se de um algoritmo sequencial, que em cada passo, apenas uma variável é adicionada:


1 - Para j = 1, \cdots, d, ajuste a regressão de Y na j-ésima variável X_j. Seja \widehat{R}(g_j) o risco estimado desta função. Então,

\widehat{j} = \argmin_j \widehat{R}(g_j)\,\,\,\,\,\, \text{e}\,\,\,\,\,\, S = \{\widehat{j}\}. 2 - Para cada j \in S^c, ajuste a regressão Y = \beta_jX_j + \sum_{s \in S}\beta_sX_S, em que \widehat{R}(g_j) é o risco estimado desta função. Defina

\widehat{j} = \argmin_{j \in S^c} \widehat{R}(g_j)\,\,\,\,\,\, \text{e atualize}\,\,\,\,\,\, S \leftarrow \{S \cup \widehat{j}\}.

3 - Repita os passos anteriores até que todas as variáveis estejam em S ou até quando não seja mais possível ajustar o modelo de regressão.

4 - Selecione o modelo com menor risco estimado.

Regressão Stepwise


Utilizar o algoritmo de seleção de variáveis foward stepwise, ao invés de buscarmos o melhor ajuste entre 2^d possíveis modelos, que muitas vezes é impossível, precisaremos investigar apenas 1 + d(d + 1)/2 modelos. Reduzimos a complexidade da seleção que antes era um problema exponencial. Melhor, não?!


Penalização


Quando temos modelos que envolve d parâmetros e que temos controle sobre eles (conhecemos muito bem cada um deles), acrescentar algum tipo de penalização à função objetivo poderá ser útil. A penalização é uma medida de complexidade, em que é útil para equilibrar o modelo, de modo a tentar buscar um equilibrio entre viés e variância, discutidos anteriormente. Assim, sob novas observações, desejamos estimar o risco R(g), por


R(g) \approx EQM(g) + \mathcal{P}(g).


Desejamos minimiar R(g), mas não a custa de muitos parâmetros, pois assim teríamos overfitting. Portanto, para muitos parâmetros temos que ter EQM baixo, porém, \mathcal{P}(g) deve ser alto. Já em modelos viesados, quando temos poucos parâmetros, o EQM normalmente é alto, mas a complexidade \mathcal{P}(g) deve ser baixo, pois temos um modelo mais simplista.

AIC e BIC


Existem diversas penalizações, em que o AIC (Akaike Information Criterion) e BIC (Bayesian Information Criterion) são as mais conhecidas. Com base nesses critérios, temos que


  1. AIC: EQM + \frac{2}{n\,d\, \widehat{\sigma}^2}.
  2. BIC: EQM + \frac{\log(n)}{n\, d\, \widehat{\sigma}^2}.

\widehat{\sigma}^2 = \frac{1}{m}\sum_{k = 1}^m (W_k - \overline{W})^2.


Aqui, d é a quantidade de parâmetros no modelo e \widehat{\sigma}^2 é uma estimativa da variância do erro, que para um conjunto de teste suficientemente grande, poderá ser considerado o estimador de \widehat{\sigma}^2 conforme descrito anteriormente. Segundo James, Gareth, et al. An introduction to statistical learning. Ed. 2, p. 233, assume-se o modelo com todos os preditores para o cálculo de \widehat{\sigma}^2.

Lasso


O lasso foi desenvolvido pelo Robert Tibshirani, em um artigo publicado no artigo Regression Shrinkage and Selection via the Lasso.

Lasso


O lasso tem como objetivo encontrar um estimador de uma regressão linear que possui risco menor que o de mínimos quadrados, possuindo duas grandes vantagens, em relação ao stepwise:


  1. Sua solução é mais rápida, ainda que stepwise seja consideravalmente mais rápido do que avaliar 2^d modelos;
  2. O lasso é capaz de selecionar automaticamente as variáveis mais relevantes para o modelo, reduzindo a dimensionalidade dos dados.


A segunda vantagem ocorre, uma vez que ele realiza uma penalização que leva à estimativas de alguns coeficientes \beta_i igual a zero, eliminando as variáveis menos importantes. Normalmente o vetor \beta é esparso!


Lasso


No lasso, ao invés de reduzir a variância do estimador de mínimos quadrados usando a complexidade (L_0 = \sum_{i = 1}^d\mathbb{I}(\beta_i) \neq 0) em Equação 3, usa-se a penalização L_1 = \sum_{i = 1}^d|\beta_i|. No lasso, buscamos:

\widehat{\beta}_{L_1,\lambda} = \argmin_{\beta_0 \in \mathbb{R}, \beta \in \mathbb{R}^d}\sum_{k = 1}^n\overbrace{\left(y_k - \beta_0 - \sum_{i = 1}^d \beta_i x_{x,i}\right)^2}^{n \times EQM} + \overbrace{\lambda \,\,\underbrace{\sum_{j = 1}^d|\beta_j|}_{\text{Penalização}}}^{\text{A magnitude do coeficiente importa!}}, em que \lambda é um tuning parameter 🎛. Perceba que quando \lambda = 0, caímos no caso do modelo de regressão por mínimos quadrados sem penalização. Já, quando \lambda \rightarrow \infty, temos um modelo em que todas as variáveis são removidas, uma vez que a primeira parte do modelo torna-se insignificante.

Lasso



Quando \lambda é grande, temos que

\sum_{k = 1}^n \left(y_k - \beta_0 - \sum_{j = 1}^d \beta_j x_{k,j}\right)^2 + \lambda \sum_{j = 1}^d |\beta_j| \approx \lambda \sum_{j = 1}^d |\beta_j|, e portanto, \widehat{\beta}_1 = 0, \cdots, \widehat{\beta}_d = 0.


A escolha de \lambda, em geral, é feita utilizado algum método de validação cruzada. 🔍


Lasso


O lasso é extremamente rápido, e nos últimos anos, diversos algoritmos foram construídos para fazer essa taréfa de forma eficiente. O LARS foi um dos primeiros algoritmos desenvolvidos em 2010. Para detalhes, ler Friedman, J. H. (2001). Greedy function approximation: a gradient boosting machine. Annals of statistics, 1189–1232.


No R, a regressão lasso poderá ser feita usando a biblioteca glmnet, assim:


library(glmnet)
ajuste <- cv.glmnet(x, y, alpha = 1)


Ridge


Uma alternativa que surgiu antes do lasso é a regressão ridge. Ela foi proposta no artigo Hoerl, A. E. & Kennard, R. W. (1970). Ridge regression: Biased estimation for nonorthogonal problems. Technometrics, 12(1), 55–67. Aqui, utiliza-se como medida de complexidade a norma L_2, em que, o estimador é dado por:

\widehat{\beta}_{L_2,\lambda} = \argmin_{\beta_0 \in \mathbb{R}, \beta \in \mathbb{R}^d}\sum_{k = 1}^n\overbrace{\left(y_k - \beta_0 - \sum_{i = 1}^d \beta_i x_{x,i}\right)^2}^{n \times EQM} + \lambda \,\,\underbrace{\sum_{j = 1}^d\beta_j^2}_{\text{Penalização}}. Diferentemente do lasso, a regressão ridge possui solução analítica, dada por:

\widehat{\beta}_{L_2,\lambda} = ({\bf X}^{T}{\bf X} + \lambda\mathbb{\bf I}_0)^{-1}{\bf X}^{T}Y, em que \mathbb{\bf I}_0 é a matriz identidade (d + 1) \times (d + 1) com \mathbb{\bf I}_0(1,1) = 0.


Ridge


A regressão ridge poderá ter uma variância menor que a regressão lasso, porém seu viés poderá ser maior. Outra característica da regressão ridge é que ela possue uma única solução, enquanto a regressão lasso poderá ter multiplas soluções. Os autores também demonstram que a regressão ridge lida melhor com multicolinearidade.


No R, também poderemos utilizar a biblioteca glmnet:


library(glmnet)
ajuste <- cv.glmnet(x, y, alpha = 0)


Elastic Net


Na documentação da função glmnet, note que a função realiza o lasso mais o ridge, i.e.,


(1 - \alpha)/2 ||\beta_j||^2_2 + \alpha||\beta_j||_2.


Note que para \alpha = 1, temos uma regressão do tipo lasso, já para \alpha = 0 recaímos para uma penalização do tipo ridge. Para \alpha diferente de zero e um, temos uma elastic net, ok?!


Elastic Net


Nesse tipo de modelo de regressão, combina-se as penalizações da regressão ridge com a utilizada na regressão lasso, herdando os benefícios do uso de cada um dos métodos isoladamente, melhorando a estabilidade das estimativas do lasso, em situações de multicolinearidade entre as variáveis e também permitindo a seleção automática de variáveis.

(1-\alpha)\widehat{\beta}_{L_2,\lambda} + \alpha\widehat{\beta}_{L_1,\lambda}, em que 0 \leq \alpha \leq1. Em R, basta especificar para a função glmnet um valor de \alpha diferente de 0 e 1. Esse valor normalmente é escolhido por um procedimento de validação cruzada.


Exemplo: EMQ, Ridge, Lasso e Elastic Net


Exemplo: Considere uma base de dados simulada, com n = 500 observações, de tal forma que

Y = 3X_{1} - 2X_2 + X_3 + -3X_4 + X_5 + \sum_{i = 6}^{20}0X_i + \varepsilon, em que \varepsilon \sim \text{Normal}(0, 0.5^2) e X_i \sim \text{Normal}(0, 1), independentes, com i = 1, \cdots, 20. Desejamos ajustar quatro modelos de regressão. Para o caso do Estimador de Mínimos Quadrados - EMQ e do modelo Ridge, que não tem seleção automática de variáveis, usaremos todas as variáveis. Desejamos avaliar o risco estimado de cada uma das regressões.

Exemplo: EMQ, Ridge, Lasso e Elastic Net


Solução utilizando o tidymodels
library(tidymodels)
library(tibble)
library(purrr)
library(ggplot2)
library(patchwork)

# Removendo possíveis conflitos de pacotes --------------------------------
tidymodels::tidymodels_prefer()

# Função para gerar os dados ----------------------------------------------
gerando_dados <- function(n = 300L){
  regressao <- function(i){
    x <- rnorm(n = 5L)
    y <- 3*x[1L] - 2*x[2L] + x[3L] - 3*x[4L] + x[5L] + rnorm(1L, 0, 0.5)
    tibble(
      y = y,
      x1 = x[1L],
      x2 = x[2L],
      x3 = x[3L],
      x4 = x[4L],
      x5 = x[5L]
    )
  }
  dados <- purrr::map(.x = 1L:n, .f = regressao) |> 
    purrr::list_rbind()
  
  parte_esparsa <- matrix(0, n, 15)
  
  dados <- cbind(dados, parte_esparsa)
  colnames(dados) <- c("y", paste0("x", 2L:ncol(dados)))
  as_tibble(dados)
}

dados <- gerando_dados(n = 500)

# Divisão inicial da base -------------------------------------------------
hod_out <- initial_split(dados, prop = 0.7)
treinamento <- training(hod_out)
teste <- testing(hod_out)

# Setando o modelo (set engine) -------------------------------------------
modelo_eqm <- 
  linear_reg(penalty = 0, mixture = 0) |> 
  set_mode("regression") |> 
  set_engine("glmnet")
  
modelo_ridge <- 
  linear_reg(penalty = tune::tune(), mixture = 0) |> 
  set_mode("regression") |> 
  set_engine("glmnet")

modelo_lasso <- 
  parsnip::linear_reg(penalty = tune::tune(), mixture = 1) |> 
  set_mode("regression") |> 
  parsnip::set_engine("glmnet")
  
modelo_elastic <- 
  parsnip::linear_reg(penalty = tune::tune(), mixture = tune::tune()) |> 
  set_mode("regression") |> 
  parsnip::set_engine("glmnet")

# Criando workflows -------------------------------------------------------
all_wf <- 
  workflow_set(
    preproc = list(y ~ . ),
    models = list(eqm = modelo_eqm, ridge = modelo_ridge, lasso = modelo_lasso, elastic = modelo_elastic), 
    cross = TRUE
  )

# Validação cruzada -------------------------------------------------------
set.seed(0)
cv <- rsample::vfold_cv(treinamento, v = 5L)

# Setando a métrica -------------------------------------------------------
metrica <- yardstick::metric_set(rmse)

# Tunagem dos hiperparâmetros ---------------------------------------------
# A semente (seed = 0) faz com que dentro da validação cruzada para cada modelo
# a semente seja sempre a mesma.
tunagem <- 
  all_wf |> 
  workflow_map(
    seed = 0, 
    verbose = TRUE,
    resamples = cv,
    grid = 50,
    metrics = metrica
  )

# Rank dos melhores modelos -----------------------------------------------
modelos_rank <- tunagem |> rank_results()

melhor_eqm <- 
  tunagem |> 
  extract_workflow_set_result("formula_eqm") |> 
  select_best("rmse")

melhor_ridge <- 
  tunagem |> 
  extract_workflow_set_result("formula_ridge") |> 
  select_best("rmse")

melhor_lasso <- 
  tunagem |> 
  extract_workflow_set_result("formula_lasso") |> 
  select_best("rmse")

melhor_elastic <- 
  tunagem |> 
  extract_workflow_set_result("formula_elastic") |> 
  select_best("rmse")

finalizando_eqm <- 
  tunagem |> 
  extract_workflow("formula_eqm") |> 
  finalize_workflow(melhor_eqm) |> 
  last_fit(split = hod_out)

finalizando_ridge <- 
  tunagem |> 
  extract_workflow("formula_ridge") |> 
  finalize_workflow(melhor_ridge) |> 
  last_fit(split = hod_out)

finalizando_lasso <- 
  tunagem |> 
  extract_workflow("formula_lasso") |> 
  finalize_workflow(melhor_lasso) |> 
  last_fit(split = hod_out)

finalizando_elastic <- 
  tunagem |> 
  extract_workflow("formula_elastic") |> 
  finalize_workflow(melhor_elastic) |> 
  last_fit(split = hod_out)

# Visualizando as métricas
finalizando_eqm |> collect_metrics()
finalizando_ridge |> collect_metrics()
finalizando_lasso |> collect_metrics()
finalizando_elastic |> collect_metrics()

# Visualizando predições:
finalizando_eqm |> collect_predictions()
finalizando_ridge |> collect_predictions()
finalizando_lasso |> collect_predictions()
finalizando_elastic |> collect_predictions()

📚 Exercícios


Exercício: Utilizando os dados de vinho vermelho🍷, disponíveis aqui, faça uma pequena análise exploratória dos dados. No link do Kaggle você consegue uma explicação sobre o que significa cada uma das variáveis.

📚 Exercícios


Exercício: Utilizando os dados de vinho vermelho🍷, disponíveis aqui, obtenha o melhor modelo de regressão linear para modelar a qualidade do vinho, considerando:


  1. g_1 - Método dos mínimos quadrados;
  2. g_2 - Regressão ridge;
  3. g_3 - Regressao lasso;
  4. g_4 - Elastic Net.


Você deverá selecionar o melhor modelo de cada uma das classes de modelos de regressão e construir uma tabela com o risco estimado \widehat{R}(g_i),\,\, i = 1, \cdots, 4, em que aqui, g_i representa o modelo geral não estimado. Ao fim, construa quatro gráficos mostrando o ajuste de cada um dos modelos.


Exercício: Se você utilizou o tidymodels para resolver o exercício anterior, rafaça usando a biblioteca glmnet. Caso contrário, resolva-o utilizando o tidymodels.

📚 Exercícios


Exercício: Considere agora o conjunto de dados de despesas médicas, disponível aqui. Refaça o mesmo exercício dos dados de vinho vermelho, em que aqui, o objetivo é prever a variável charges. Perceba que algumas variáveis são qualitativas, e portanto, você deverá transformá-las em dummy. Indique os melhores cenários dos quatro modelos e informe qual modelo você utilizaria. Explique! Dica: usando a função step_dummy() da biblioteca recipes, você poderá facilmente transformar variáveis qualitativas em numéricas.


Métodos não-paramétricos


Métodos paramétricos podem impor muitas limitações na solução de um problema de regressão, i.e., em problemas que desejamos estimar a função de regressão r({\bf x}). Por exemplo, nem sempre o melhor estimador linear é um bom estimador para r({\bf x}).


Métodos paramétricos são muitas vezes simplistas e restritivos, em que normalmente abrimos mãos para se ter um estimador um pouco mais viesado, em detrimento da diminuição da variância do modelo. Por exemplo, nas regressões penalizadas que vimos anteriormente (ridge, lasso e elastic-net), penalizamos modelos com muitas covariáveis o que naturalmente aumentará o viés, na maioria das vezes.


Métodos não-paramétricos


Em situações em que temos muitos dados (n grande), os modelos não-paramétricos possuem, em geral, boa peformance, uma vez que apesar de que nessa classe de modelos existir um aumento da variância, seguida de uma redução de viés, a variância do modelo não aumenta muito.


De um lado, nos modelos de regressão que vimos até o momento, introduzimos uma penalização para diminuir a variância do modelo frente ao método dos mínimos quadrados (quando não usamos penalização) em troca de um aumento no víes. Aqui, em modelos não paramétricos, desejamos fazer a troca oposta, i.e., diminuir o viés, em troca de um ganho na variância no modelo.


Qualquer abordagem paramétrica trás consigo a possibilidade de que a forma funcional g para estimar f seja muito diferente da verdadeira, claro, se o modelo resultante não se ajustar bem aos dados, isto é \widehat{g} não tem boa capacidade preditiva, muito embora, também é possível que tenhamos \widehat{g} com boa capacidade preditiva, mesmo que g não represente a estrutura real de f (sempre desconhecida).

Métodos não-paramétricos



Isso não é regra, porém ajuda a entender um pouco o dilema entre essas classes de modelos (paramétricos e não-paramétricos).


k-vizinhos mais próximo próximos


Também chamado de k-nearest neighbours - kNN, o kNN é um dos métodos mais populares na comunidade de aprendizagem de máquina. O método foi formulado em uma sequência de dois artigos:


1 - Benedetti, J. K. (1977). On the nonparametric estimation of regression functions. Journal of the Royal Statistical Society. Series B (Methodological), 248–253;


2 - Stone, C. J. (1977). Consistent nonparametric regression. The Annals of Statistics, 595– 620.


A ideia do método é estimar a função de regressão r({\bf x}), para um dado {\bf x} com base nas respostas Y dos k-vizinhos mais próximos de {\bf x}.

k-vizinhos mais próximo próximos


Formalmente, temos que

g({\bf x^*}) = \frac{1}{k}\sum_{i \in \mathcal{N}_{\bf x^*}}y_i, em que \mathcal{N}_{\bf x^*} é o conjunto índices das k observações mais próximas de {\bf x}, i.e,

\mathcal{N}_{\bf x^*} = \{i \in \{1, \cdots, n\}\, : \, d({\bf x}_i, {\bf x^*}) \leq d_{\bf x^*}^k\}, em que d_{\bf x^*}^k é a distância do k-ésimo vizinho mais próximo de \bf{x^*} em \bf{x}. Portanto, o valor da regressão no ponto {\bf x^*}, i.e., o valor de r({\bf x^*}) = \mathbb{E}(Y|{\bf X} = {\bf x^*}) é estimado pela média de Y_{N_{\bf x^*}}. Ou seja, estimamos por:

\overline{Y}_{N_{\bf x^*}} = \frac{1}{k} \sum_{i \in \mathcal{N}_{\bf x^*}}y_i.

k-vizinhos mais próximo próximos


Para determinar d_{\bf x^*}^k, poderemos utilizar alguma métrica de distância e assim mensurarmos a proximidade. Entre elas:


  1. Distância Euclidiana ou distância L_2: d(x^a, x^b) = \sqrt{(x_1^a - x_1^b)^2 + \cdots + (x_d^a - x_d^b)^2};


  1. Distância de Manhattan, City Block ou distância L_1: d(x^a, x^b) = \sqrt{|x_1^a - x_1^b| + \cdots + |x_d^a - x_d^b|};


  1. Distância de Mahalanobis: d(x^a, x^b) = \sqrt{(x^a - x^b)^{T}S^{-1}(x^a - x^b)}, em que S é a matriz de covariância, em que na diagonal princial temos as variâncias e fora dela as covariâncias entre os pontos. Lembre-se que se x e y são vetores de dados quaisquer, a interdependência linear entre eles poderá ser estimada como:


\mathrm{cov}(x, y) = \frac{1}{n-1}\sum_{i = 1}^n (x_i - \overline{x})(y_i - \overline{y}).

k-vizinhos mais próximo próximos


Você poderá utilizar qualquer outra medida de distância além das que foram citadas acima.


É importante perceber que o método kNN não faz uma “compressão” dos dados como a regressão linear que estudamos. Lá, temos uma equação que utilizamos para estimar o valor de Y, após as estimação dos coeficientes do modelo de regressão, ou seja, não precisamos mais dos dados para estimar novas observações. Já no kNN, precisamos sempre nos recorrer aos dados para fazer novas predições, ou seja, sempre que desejarmos calcular r({\bf x}) = \mathbb{E}(Y|{\bf X} = {\bf x}) deveremos sempre fazer uma nova consulta aos dados para calcular a média dos vizinhos mais próximos. O kNN não possui coeficientes para interpretar. Diferentemente da regressão linear, o kNN é um pouco mais “black box”.


k-vizinhos mais próximo próximos


O valor da constante k é um hiperparâmetro do kNN e deverá ser obtido por validação cruzada. Perceba que se k = n temos um modelo muito viesado, porém com variância pequena. Isso, porquê para k = n basicamente iremos tirar uma média dos dados. Para k = 1, teremos um overfitting, uma vez que o estimador irá interpolar os dados.


Exemplo: Considere novamente o conjunto de dados de expectativa de vida versus PIB per Capita disponíveis aqui. Utilizando o tidymodels, vamos construir uma fução que retorne um gráfico com as estimativas do kNN. A função receberá como argumentos o conjunto de dados e o valor de k. Você também poderia utilizar outras bibliotecas, como por exemplo a FNN, ou a KKNN, esta útlima é a que é utilizada internamente na biblioteca parsnip do tidymodels.


k-vizinhos mais próximo próximos


Abrindo apenas um parêntese, o tidymodels refere-se a um conjunto de pacotes que são úteis para o tratamento, treinamento, tunagem e avaliação de modelos de aprendizagem de máquina, em que o parsnip implementa as engines (algoritmos/motores) que iremos utilizar para treinar um modelo. Na verdade, os algoritmos estão implementados em pacotes de terceiros e não precisamente no parsnip. Porém, o parsnip unifica a sintaxe de diversos algoritmos implementados em pacotes separados.


k-vizinhos mais próximo próximos


Observe que na imagem abaixo, a Figura A, quando k=1, percebemos initidamente que houve overfitting, i.e., há uma interpolação dos dados. Já na Figura D temos um modelo com variância menor, porém, este é muito simplista, o que sugere um alto viés.


k-vizinhos mais próximo próximos


Estude o código! Ele fornece a solução para o exemplo.


Solução pelo método knn do exemplo anterior
library(tidymodels)
library(glue)
library(dplyr)
library(ggplot2)
library(patchwork)

tidymodels::tidymodels_prefer()

# Lendo dados
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados <- 
  dados_expectativa_renda |> 
  dplyr::select(-CountryName) |> 
  dplyr::rename(y = LifeExpectancy, x = GDPercapita)

knn_exp_pip <- function(dados, k = 1L){
  # Criando receita
  receita <- recipe(y ~ x, data = dados)
  
  # Definindo o modelo
  modelo_knn <- nearest_neighbor(neighbors = k) |> 
    set_mode("regression") |> 
    set_engine("kknn")
  
  # Workflow
  ajuste_final <- 
    workflow() |> 
    add_model(modelo_knn) |> 
    add_recipe(receita) |> 
    fit(data = dados)
  
  # Retornando previsoes
  y_chapeu <- predict(ajuste_final, new_data = dados)
  
  dados <- 
    dados |> 
    mutate(y_chapeu = y_chapeu$.pred)
  
  dados |> 
    ggplot() +
    geom_point(aes(x = x, y = y), size = 3) +
    geom_line(aes(x = x, y = y_chapeu), col = "red", alpha = 0.6, size = 2) +
    labs(title = "k-nearest neighbours", subtitle = glue("k = {k}")) +
    theme(
      title = element_text(face = "bold")
    )
}

p1 <- knn_exp_pip(dados, k = 1L)
p2 <- knn_exp_pip(dados, k = 7L)
p3 <- knn_exp_pip(dados, k = 10L)
p4 <- knn_exp_pip(dados, k = 200L)

p <- p1 + p2 + p3 + p4 + plot_annotation(tag_levels = "A")

ggsave(p, file = "imgs/knn_plot.png", width = 50, height = 30, units = "cm")


k-vizinhos mais próximo próximos


Algumas limitações do kNN são:


  1. É totalmente dependente do conjunto de dados para fazer novas predições;

  2. Para novas observações que são fora dos limites dos dados de treinamento, as predições do kNN tendem a serem imprecisas;

  3. Pode ser custoso para uma grande base de dados;

  4. O cálculo de distâncias pode sofrer com a chamada “maldição da dimensionalidade”, em que a depender da métrica de distância utilizada, tudo fica muito distante.


Algumas vantagens são:


  1. É um método simples de ser implementado;

  2. É muito utilizado para imputação de observações faltantes;

  3. É comumente utilizado, por conta de sua simplicidade, em explorações iniciais.

📚 Exercícios


Exercício: Considere o problema em que o objetivo é prever a pontuação (score) / (item 2) do aluno com base em algumas variáveis. São elas:


  1. Horas estudadas: o número total de horas gastas estudando por cada aluno;
  2. Pontuação: As notas obtidas pelos alunos em testes anteriores;
  3. Atividades extracurriculares: Se o aluno participa de atividades extracurriculares (Sim ou Não);
  4. Horas de sono: o número médio de horas de sono que o aluno teve por dia;
  5. Amostras de perguntas praticadas: O número de amostras de perguntas que o aluno praticou.


Você poderá baixar e ter uma descrição maior da base de dados clicando aqui. Avalie o poder preditivo do modelo lasso com o do kNN. Sua análise deve conter uma análise exploratória dos dados, deverá utilizar um esquema de k-folds cross-validation, com k = 20 e considerar um esquema de data splitting na proporção 90\% para treino e 10\% para teste. Sua análise deverá estar em um notebook de quarto, em que você deverá comentar cada passo da análise.

Nadaraya-Watson


Uma variação do método kNN que é bastante difundida na comunidade estatística é o método de Nadaraya-Watson, propostos nos artigos:


  1. Nadaraya, E. A. (1964). On estimating regression. Theory of Probability & Its Applications, 9(1), 141–142;

  2. Watson, G. S. (1964). Smooth regression analysis. Sankhya: The Indian Journal of Statistics, Series A, 359–372.


Esse método também é chamado de estimador k-vizinhos ponderados (kNN ponderado), uma vez que a estimação da função de regressão em um dado ponto {\bf x} utiliza de médias ponderadas das observações do conjunto de treinamento:

g({\bf x}) = \sum_{i = 1}^n w_i({\bf x})y_i, em que w_i({\bf x}) é o peso atribuído à i-ésima observação, medindo a similaridade de {\bf x}_i à {\bf x}.

Nadaraya-Watson


Temos que w_i({\bf x}) é definido por:

w_i({\bf x}) = \frac{K({\bf x}, {\bf x}_i)}{\sum_{j = 1}^n K({\bf x}, {\bf x}_j)}, em que K({\bf x}, {\bf x}_j) é um kernel de suavização usado para medir a similaridade entre as observações. Escolhas que são populares para K({\bf x}, {\bf x}_j), são:


  1. Kernel uniforme: K({\bf x}, {\bf x}_i) = \mathbb{I}(d({\bf x}, {\bf x}_i) \leq h);
  2. Kernel gaussiano: K({\bf x}, {\bf x}_i) = (\sqrt{2\pi h^2})^{-1}\exp\left\{ -\frac{d^2({\bf x}, {\bf x}_i)}{2h^2}\right\};
  3. Kernel triangular: K({\bf x}, {\bf x}_i) = (1 - d({\bf x}, {\bf x}_i)/h)\mathbb{I}(d({\bf x}, {\bf x}_i) \leq h);
  4. Kernel de Epanechnikov: K({\bf x}, {\bf x}_i) = (1 - d^2({\bf x}, {\bf x}_i)/h^2)\mathbb{I}(d({\bf x}, {\bf x}_i) \leq h).

Nadaraya-Watson


Enquanto no kernel uniforme, os pesos são iguais para as observações a uma distância menor que h de {\bf x} e atribui peso zero para observações maiores que h, os kernels triangular e de Epanechnikov atribui pesos maiores para observações mais próximas de {\bf x}. A quantidade h é um tuning parameter, e na prática, deve ser estimada por um procedimento de validação cruzada.


Algumas propriedades de uma função kernel são:


  1. Simetria: K(x,y) = K(y,x), permitindo que a função de similaridade seja invariante em relação a ordem dos argumentos;
  2. Positiva definida: Para qualquer vetor c, em que seja possível fazer c^{T}K(x,y), temos que c^{T}K(x,y)c > 0.


Você poderá encontrar outras funções kernel aqui.

Nadaraya-Watson


Exemplo: Novamente, considerando o conjunto de dados de expectativa de vida versus PIB per Capita disponíveis aqui. Utilizando o tidymodels, vamos construir uma fução que retorne um gráfico com as estimativas. A função receberá como argumentos o conjunto de dados e o valor de h. Observe a documentação da função nearest_neighbor do pacote parsnip. Perceba que o argumento weight_func permite que possamos escolher entre algumas funções kernel. Porém, como métrica de distâncias, apenas poderemos utilizar a de Minkowski. Seja x = (x_1, \cdots, x_n) e y = (y_1, \cdots, y_n), ambos vetores do \mathbb{R}^n. Então, a distância de Minkowski é dada por:

d(x,y) = \left(\sum_{i = 1}^n |x_i - y_i|^p\right)^{\frac{1}{p}}, com p \geq 1. Note que se p = 1 temos a distância euclidiana (distância L_1) e se p = 2 teremos a distancia de Manhattan (distância L_2).

Nadaraya-Watson


Portanto, com o argumento dist_power da função nearest_neighbor, do pacote parsnip, você poderá especificar o valor de p, que inclusive poderá ser um hiperparâmetro, podendo ser obtido por meio de uma validação cruzada.



No exemplo não iremos fazer a validação cruzada, pois apenas queremos implementar uma função em que seja possível experimentar o método para diferentes valores de h e diferentes funções de kernel.

Nadaraya-Watson


A imagem abaixo utiliza o kernel gaussiano:


Nadaraya-Watson


Solução do exempĺo de Nadaraya-Watson
library(tidymodels)
library(glue)
library(dplyr)
library(ggplot2)
library(patchwork)

tidymodels::tidymodels_prefer()


# Lendo dados
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados <- 
  dados_expectativa_renda |> 
  dplyr::select(-CountryName) |> 
  dplyr::rename(y = LifeExpectancy, x = GDPercapita)

nadaraya_watson_exp_pip <- function(dados, h = 1, ...){
  # Criando receita
  receita <- 
    recipe(y ~ x, data = dados) |> 
    step_normalize()
  
  # Definindo o modelo
  modelo_knn <- nearest_neighbor(dist_power = h, ...) |> 
    set_mode("regression") |> 
    set_engine("kknn")
  
  # Workflow
  ajuste_final <- 
    workflow() |> 
    add_model(modelo_knn) |> 
    add_recipe(receita) |> 
    fit(data = dados)
  
  # Retornando previsoes
  y_chapeu <- predict(ajuste_final, new_data = dados)
  
  dados <- 
    dados |> 
    mutate(y_chapeu = y_chapeu$.pred)
  
  dados |> 
    ggplot() +
    geom_point(aes(x = x, y = y), size = 3) +
    geom_line(aes(x = x, y = y_chapeu), col = "red", alpha = 0.6, size = 2) +
    labs(title = "Nadaraya-Watson", subtitle = glue("h = {h}")) +
    theme(
      title = element_text(face = "bold")
    )
}

p1 <- nadaraya_watson_exp_pip(dados, h = 1, weight_func = "gaussian")
p2 <- nadaraya_watson_exp_pip(dados, h = 1000, weight_func = "gaussian")

p <- p1 + p2 + plot_annotation(tag_levels = "A")

ggsave(p, file = "imgs/nadaraya_watson.png", width = 30, height = 15, units = "cm")


Tidymodels kNN e Nadaraya-Watson


Para que possamos experimentar o tidymodels seguindo todo o fluxo de “padrão” de aprendizagem de máquina, com divisão do conjunto de dados, validação cruzada e busca pelos melhores hiperparâmetros (“tunagem”). Iremos reproduir dois exemplos com os dados de expectativa de vida e PIB per Capita.


Exemplo: Nesse exemplo estamos realizando o kNN, em que estamos obtendo um candidato para o valor de k, por meio de um procedimento de cross-validation (10-folds cross-validation). Aqui, a busca do hiperparâmetro k fará uso de um grid search. Perceba, no código que segue o exemplo, que no processo de “tunagem”, alterei o grid de valores usando a função grid_max_entropy do pacote dails. Uma observação é que você poderia criar uma tibble com valores do seu interesse. O argumento grid da função tune_grid do pacote tune deve ser um data frame/tibble ou um número inteiro. Esse objeto conterá todas as possíveis combinações de hiperparâmetros que serão testadas na validação cruzada, no nosso caso, temos apenas um hiperparâmetro. Foi utilizado um grid de tamanho 60. Foi utilizado uma divisão de 80\% para treinamento e 20\% para teste.

Tidymodels kNN e Nadaraya-Watson


Workflow completo do treimanento de um modelo KNN.
library(tidymodels)
library(dplyr)
library(ggplot2)
library(patchwork)

# Resolvendo eventuais conflitos entre tidymodels e outros pacotes eventualmente
# carregados:
tidymodels::tidymodels_prefer()

# Lendo a base de dados:
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados_expectativa_renda <-
  dados_expectativa_renda |>
  dplyr::select(-CountryName)

# Setando uma semente
set.seed(0)

# Divisão da base de dados ------------------------------------------------
# Divisão inicial (treino e teste)
splits <- 
  rsample::initial_split(
    dados_expectativa_renda,
    strata = "LifeExpectancy", 
    prop = 0.8 
  )

# O conjunto de dados de treinamento será utilizado para ajustar/treinar o
# modelo:
treinamento <- rsample::training(splits)

# O conjunto de teste será utilizado apenas no fim, para avaliar o desempenho
# preditivo final do modelo:
teste <- rsample::testing(splits) 

# Criando uma receita para os dados ---------------------------------------

# Poderia ter passado o conjunto treinamento ou posso passar o conjunto "splits".
# O comando recipes já entende que deverá utilizar o conjunto de treinamento.
receita <- 
  recipes::recipe(formula = LifeExpectancy ~ ., data = treinamento) |> 
  step_YeoJohnson(all_predictors()) |> # Ajuda na normalização dos dados. Pode ser bom!
  step_normalize(all_predictors()) # Normalizando variáveis numéricas.

# Construindo modelo kNN --------------------------------------------------
modelo_knn <- 
  parsnip::nearest_neighbor(neighbors = tune()) |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kknn")

# Construindo um workflow (pipeline) --------------------------------------
wf_knn <-
  workflows::workflow() |>
  workflows::add_recipe(receita) |>
  workflows::add_model(modelo_knn)

# Cross-validation --------------------------------------------------------
cv <- 
  treinamento |> 
  rsample::vfold_cv(v = 10L, strata = "LifeExpectancy")

# Busca do hiperparâmetro k -----------------------------------------------
metrica <- metric_set(rmse)

# Extraindo e atualizando range do parâmetro ------------------------------
update_parametros <-
  wf_knn |> 
  extract_parameter_set_dials() |>
  update("neighbors" = neighbors(c(1, 50)))

# Tunagem -----------------------------------------------------------------
meu_grid <- dials::grid_max_entropy(update_parametros, size = 60)

tunagem <-
  tune::tune_grid(
    wf_knn,
    resamples = cv,
    grid = meu_grid,
    metrics = metrica,
    control = control_grid(save_pred = TRUE, verbose = TRUE)
  )

p_hiper <- autoplot(tunagem) +
  labs(title = "KNN - Seleção do número k (vizinhos)", subtitle = "Sintonização do hiperparâmetro (valor de k)") +
  theme(
    title = element_text(face = "bold")
  )

# Atualizando workflow ----------------------------------------------------
wf_knn <- 
  wf_knn |> 
  finalize_workflow(select_best(tunagem))

# Ajustar o modelo ao conjunto de treinamento e avaliar no teste --------
ajuste_final <- last_fit(wf_knn, splits)

# Ajuste final com toda a base de dados -----------------------------------
modelo_final <- fit(wf_knn, data = dados_expectativa_renda)

# Visualizando as predições na base de treino
p_ajuste <- ajuste_final$.predictions[[1L]] |> 
  ggplot(aes(x = LifeExpectancy, y = .pred)) + 
  geom_point(size = 3, alpha = 0.7, col = "red") +
  labs(
    title = "Predições versus Real", 
    subtitle = "Usando apenas os dados de teste"
  ) +
  xlab("LifeExpectancy") + 
  xlab("LifeExpectancy predito") + 
  theme(
    title = element_text(face = "bold")
  )

# Unindo os dois plots
p <- p_hiper + p_ajuste + plot_annotation(tag_levels = "A")

# Salvando gráficos
ggsave(p, file = "imgs/plot_hiper_ajuste_tidymodels_knn_whatson.png", width = 30,
       height = 15, units = "cm")

O pacote dials fornece três funções o grid de parâmetros. São apenas funções que criam grids para os hiperparâmetros, segundo algumas metodologias, mas que na prática não há garantias de qual irá funcionar melhor. A ideia é experimentar e, por meio de uma validação cruzada, decidir por qual utilizar. Normalmente, a grid_max_entropy, utilizada no código acima, funciona bem.

Tidymodels kNN e Nadaraya-Watson


Para demonstar que podemos realizar transformações (receitas) na base de dados, entre do processo de treinamento, foi realizado duas transformações na variável GDPercapita. A primeira foi a Yeo–Johnson transformation e a segunda foi uma simples normalização dos dados (subitrair da média e dividir pelo desvio padrão). A transformação de Yeo–Johnson é uma transformação semelhante a de Box-Cox. A transformação de Yeo–Johnson trata de situações que a transformação de Box-Cox não trata. Por exemplo, ela trata de valores negativos e zero.


A ideia do steps utilizados na faze de pré-processamento dos dados, em que utilizamos o pacote recipes do R, é que para novas observações, depois do modelo ajustado, essas transformações serão automaticamente aplicadas aos novos dados.


Tidymodels kNN e Nadaraya-Watson


Tidymodels kNN e Nadaraya-Watson


Exemplo: Vamos reproduzir todo o fluxo de treinamento que fizemos com o método kNN no exemplo anterior, agora, utilizando o modelo Nadaraya-Watson. Note que uma vez que entendemos o tidymodels, fica fácil adaptar um código já existente para o treinamento de um outro modelo. Na verdade, o método de Nadaraya-Watson implementado no tidymodels é um pouco diferente. Ainda utilizamos a informação de k, em que o valor de h é deverminado pela média dos vizinhos mais próximos de {\bf x}_i à {\bf x}. Além de k, temos o valor de p para a distância de Minkowski como hiperparâmetro. Portanto, aqui, teremos dois hiperparâmetros (parâmetros de sintonização).


Workflow completo do treimanento de um modelo Nadaraya-Watson.
library(tidymodels)
library(dplyr)
library(ggplot2)
library(patchwork)
library(doMC)

# Paralelizando o código em sistemas Unix
registerDoMC(cores = parallel::detectCores())

# Resolvendo eventuais conflitos entre tidymodels e outros pacotes eventualmente
# carregados:
tidymodels::tidymodels_prefer()

# Lendo a base de dados:
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados_expectativa_renda <-
  dados_expectativa_renda |>
  dplyr::select(-CountryName)

# Setando uma semente
set.seed(0)

# Divisão da base de dados ------------------------------------------------
# Divisão inicial (treino e teste)
splits <- 
  rsample::initial_split(
    dados_expectativa_renda,
    strata = "LifeExpectancy", 
    prop = 0.8 
  )

# O conjunto de dados de treinamento será utilizado para ajustar/treinar o
# modelo:
treinamento <- rsample::training(splits)

# O conjunto de teste será utilizado apenas no fim, para avaliar o desempenho
# preditivo final do modelo:
teste <- rsample::testing(splits) 

# Criando uma receita para os dados ---------------------------------------

# Poderia ter passado o conjunto treinamento ou posso passar o conjunto "splits".
# O comando recipes já entende que deverá utilizar o conjunto de treinamento.
receita <- 
  recipes::recipe(formula = LifeExpectancy ~ ., data = treinamento) |> 
  step_YeoJohnson(all_predictors()) |> # Ajuda na normalização dos dados. Pode ser bom!
  step_normalize(all_predictors()) # Normalizando variáveis numéricas.

# Construindo modelo Nadaraya ---------------------------------------------
modelo_nadaraya <- 
  parsnip::nearest_neighbor(dist_power = tune(), weight_func = "gaussian") |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kknn")

# Construindo um workflow (pipeline) --------------------------------------
wf_nadaraya <-
  workflows::workflow() |>
  workflows::add_recipe(receita) |>
  workflows::add_model(modelo_nadaraya)

# Cross-validation --------------------------------------------------------
cv <- 
  treinamento |> 
  rsample::vfold_cv(v = 10L, strata = "LifeExpectancy")

# Busca do hiperparâmetro k -----------------------------------------------
metrica <- metric_set(rmse)

# Extraindo e atualizando range do parâmetro ------------------------------
update_parametros <-
  wf_nadaraya |> 
  extract_parameter_set_dials() |>
  update("dist_power" = dist_power(c(2, 50)))

# Tunagem -----------------------------------------------------------------
meu_grid <- dials::grid_max_entropy(update_parametros, size = 100L)

tunagem <-
  tune::tune_grid(
    wf_knn,
    resamples = cv,
    grid = meu_grid,
    metrics = metrica,
    control = control_grid(save_pred = TRUE, verbose = TRUE)
  )

p_hiper <- autoplot(tunagem) +
  labs(title = "Nadaraya-Watson - Seleção do parâmetro p", subtitle = "Sintonização do hiperparâmetro (distância p)") +
  theme(
    title = element_text(face = "bold")
  )

# Atualizando workflow ----------------------------------------------------
wf_nadaraya <- 
  wf_nadaraya |> 
  finalize_workflow(select_best(tunagem))

# Ajustar o modelo ao conjunto de treinamento e avaliar no teste --------
ajuste_final <- last_fit(wf_nadaraya, splits)

# Ajuste final com toda a base de dados -----------------------------------
modelo_final <- fit(wf_nadaraya, data = dados_expectativa_renda)

# Visualizando as predições na base de treino
p_ajuste <- ajuste_final$.predictions[[1L]] |> 
  ggplot(aes(x = LifeExpectancy, y = .pred)) + 
  geom_point(size = 3, alpha = 0.7, col = "red") +
  labs(
    title = "Predições versus Real", 
    subtitle = "Usando apenas os dados de teste"
  ) +
  xlab("LifeExpectancy") + 
  xlab("LifeExpectancy predito") + 
  theme(
    title = element_text(face = "bold")
  )

# Unindo os dois plots
p <- p_hiper + p_ajuste + plot_annotation(tag_levels = "A")

# Salvando gráficos
ggsave(p, file = "imgs/plot_hiper_ajuste_tidymodels_nadaraya.png", width = 30,
       height = 15, units = "cm")

Tidymodels kNN e Nadaraya-Watson


## Tidymodels kNN e Nadaraya-Watson


Podemos visualizar a melhor combinação de hiperparâmetros, segundo \widehat{R}(g) usando fazendo:

# As cinco melhores combinações
tunagem |> 
  show_best()

Comparando dois ou mais modelos


Você poderia perguntar: “Certo, mais tenho como comparar dois ou mais modelos de uma única vez?”



A resposta é Sim!


Comparando dois ou mais modelos


Na verdade, é bem mais interessante comparar conjuntamente, visto que para poder compararmos devemos garantir que as mesmas amostras de treinamento e teste estão sendo utilizadas, i.e., para termos uma comparação mais justa. Claro que dá para fazer de forma separada, fixando a semente dos geradores de números pseudo-aleatórios, para que a biblioteca rsample possa reproduzir a mesma divisão para ambos os modelos. Porém, a estratégia do exemplo abaixo é mais consistente.


Exemplo: Estude o código que segue! Ele compara os modelos de kNN com o método de Nadaraya-Watson.


Workflow completo do treimanento e comparação de dois modelos (KNN e Nadaraya-Whatson).
library(tidymodels)
library(dplyr)
library(ggplot2)
library(patchwork)

# Resolvendo eventuais conflitos entre tidymodels e outros pacotes eventualmente
# carregados:
tidymodels::tidymodels_prefer()

# Lendo a base de dados:
url <- "https://github.com/prdm0/dados/raw/main/dados_expectativa_renda.RData"
arquivo_temp <- tempfile()
download.file(url = url, destfile = arquivo_temp)
load(arquivo_temp)

dados_expectativa_renda <-
  dados_expectativa_renda |>
  dplyr::select(-CountryName)

# Setando uma semente
set.seed(0)

# Divisão da base de dados ------------------------------------------------
# Divisão inicial (treino e teste)
splits <- 
  rsample::initial_split(
    dados_expectativa_renda,
    strata = "LifeExpectancy", 
    prop = 0.8 
  )

# O conjunto de dados de treinamento será utilizado para ajustar/treinar o
# modelo:
treinamento <- rsample::training(splits)

# O conjunto de teste será utilizado apenas no fim, para avaliar o desempenho
# preditivo final do modelo:
teste <- rsample::testing(splits) 

# Criando uma receita para os dados ---------------------------------------

# Poderia ter passado o conjunto treinamento ou posso passar o conjunto "splits".
# O comando recipes já entende que deverá utilizar o conjunto de treinamento.
receita_knn <- 
  recipes::recipe(formula = LifeExpectancy ~ ., data = treinamento) |> 
  step_YeoJohnson(all_predictors()) |> # Ajuda na normalização dos dados. Pode ser bom!
  step_normalize(all_predictors()) # Normalizando variáveis numéricas.

receita_nadaraya <- receita_knn

# Construindo modelo kNN --------------------------------------------------
modelo_knn <- 
  parsnip::nearest_neighbor(neighbors = tune()) |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kknn")

# Construindo modelo Nadaraya ---------------------------------------------
modelo_nadaraya <- 
  parsnip::nearest_neighbor(dist_power = tune(), neighbors = tune(),
                            weight_func = "gaussian") |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kknn")

# Validação cruzada -------------------------------------------------------
set.seed(0)
cv <- rsample::vfold_cv(treinamento, v = 5L)

# Criando workflow conjunto -----------------------------------------------
all_wf <- 
  workflow_set(
    preproc = list(receita_knn, receita_nadaraya),
    models = list(modelo_knn = modelo_knn, modelo_nadaraya = modelo_nadaraya)
  )

# Tunando ambos os modelos ------------------------------------------------
tunagem <- 
  all_wf |> 
  workflow_map(
    seed = 0, 
    verbose = TRUE,
    resamples = cv,
    grid = 50,
    metrics = metric_set(rmse)
  )

# Selecionando o melhor de cada um dos modelos ----------------------------
melhor_knn <- 
  tunagem |> 
  extract_workflow_set_result("recipe_1_modelo_knn") |> 
  select_best("rmse")

melhor_nadaraya <- 
  tunagem |> 
  extract_workflow_set_result("recipe_1_modelo_nadaraya") |> 
  select_best("rmse")

# Avaliando o desempenho no conjunto de teste
teste_knn <- 
  tunagem |> 
  extract_workflow("recipe_1_modelo_knn") |> 
  finalize_workflow(melhor_knn) |> 
  last_fit(split = splits)

teste_nadaraya <- 
  tunagem |> 
  extract_workflow("recipe_1_modelo_nadaraya") |> 
  finalize_workflow(melhor_nadaraya) |> 
  last_fit(split = splits)

# Visualizando as métricas de cada um
collect_metrics(teste_knn)
collect_metrics(teste_nadaraya)

# Ajustando o modelo com todos os dados. Aqui escolhemos o Nadaraya-Watson
modelo_final <- 
  teste_nadaraya |> 
  extract_workflow("recipe_1_modelo_nadaraya") |> 
  fit(data = dados_expectativa_renda)

# Fazendo previsões com novos dados. Aqui usarei os mesmos dados
predict(modelo_final, new_data = dados_expectativa_renda)

# Salvando o modelo em um arquivo. Aqui estou supondo que salvei em
# "~/Downloads/modelo_final.rds":
# saveRDS(modelo_final, file = "~/Downloads/modelo_final.rds")

# Lendo um modelo salvo para depois fazer predições. Aqui estou supondo que 
# o modelo encontra-se salvo em "~/Downloads/modelo_final.rds":
# readRDS("~/Downloads/modelo_final.rds")
![](gifs/bom.gif)

Suport Vector Regression Machine


Métodos de estimação da função de regressão r({\bf x}) com base em Reproducing Kernel Hilbert Spaces - RKHs são famílias de metodologias bastante gerais. A ideia desses métodos envolvem definir uma função objetivo para a quantificação da qualidade das predições e, porteriormente, busca-se uma função que melhor se ajuste ao espaço de funções \mathcal{H}. Busca-se uma solução para

\argmin_{g \in \mathcal{H}} \sum_{k = 1}^n L(g({\bf x}_k, y_k)) + \mathcal{P}(g), \tag{4} em que L é uma função de perda arbitrária, \mathcal{P} uma medida de complexidade de g e \mathcal{H} um subespaço de funções.


Ocorre que para um espaço de funções arbitrário, a solução para o problema seria bastante difícil. A ideia é ter uma grande família de espaços \mathcal{H} (RKHs) de modo que a solução do problema seja relativamente simples de ser implementada.

Suport Vector Regression Machine


Métodos de regressão que se baseiam em panalização em RKHS são uma generalização da Equação 4, em que a função objetivo nesse caso é dada por:

\argmin_{g \in \mathcal{H}_k} \sum_{k = 1}^n L(g({\bf x}_k, y_k)) + \lambda||g||_{\mathcal{H}_k}^2, \tag{5} em que \mathcal{H}_{k} é um RKHS e L é uma função adequada para o problema em questão. Calma que vai ser relativamente simples definir \mathcal{H}_{k}, uma vez que isso é feito utilizando funções kernel, em particular, o kernel de Mercer.

Suport Vector Regression Machine


O termo ||g||_{\mathcal{H}_k}^2 na Equação 5 reflete a suavidade das funções em \mathcal{H}_k e cada espaço poderá conter uma noção de suavidade diferente, a depender da função de kernel escolhida. Sim, a função de kernel e função de perda L são escolhidas pelo usuário da metodologia.


É claro que para diferentes kernel (kernel de Mercer), poderemos ter diferentes resultados que pode ser avaliado em um procedimento de validação-cruzada (cross-validation). O parâmetro \lambda é um hiperparâmetro que podemos obter dentro de uma validação-cruzada, testando, por exemplo, um grid de parâmetros, para selecionarmos um \lambda que forneça o melhor risco estimado \widehat{R}(g).


A parcela ||g||_{\mathcal{H}_k}^2 mensura a suavidade das funções em \mathcal{H}_k. Assim como g, ||g||_{\mathcal{H}_k}^2 será definidas em termos da função de kernel, e essa é a grande sacada!


Suport Vector Regression Machine


Definição (Kernel de Mercer): Seja K({\bf x}_a, {\bf x}_b) uma função com domínio em \mathcal{X} \times \mathcal{X} (domínio das features/covariáveis/espaço de características) que poderá ser mais geral que \mathbb{R}^d. Diremos que uma função K, tal que K:{\mathcal{X}\times\mathcal{X}}\longrightarrow \mathbb{R} é um Kernel de Mercer se ele satisfaz às condições que seguem:


  1. Simetria: K({\bf x}_a, {\bf x}_b) = K({\bf x}_b, {\bf x}_a), para todo {\bf x}_a,{\bf x}_b \in \mathcal{X};

  2. Positivo semi-definido: a matriz \big[K({\bf x}_a,{\bf x}_b)\big]_{i,j = 1}^n é positiva semi-definida para todo n \in \mathbb{N} e para todo {\bf x}_1,\cdots, {\bf x}_n \in \mathcal{X}.


Ser positiva semi-definida, significa que para qualquer sequência c_r \in \mathbb{R}\,, \forall r = 1, \cdots, n, temos que

\sum_{i = 1}^n\sum_{k = 1}^n c_i c_k K({\bf x}_i,{\bf x}_k)\geq 0,\, \forall n \geq 2.

Suport Vector Regression Machine


Alguns kernels de Mercer comuns são:


  1. Kernel Linear: K({\bf x}_i, {\bf x}_l) = {\bf x}_i^T{\bf x}_l;
  2. Kernel Polinomial de grau p: K({\bf x}_i, {\bf x}_l) = (1 + \langle{\bf x}_i, {\bf x}_l\rangle)^p, \gamma > 0, \theta \geq 0, p \in \mathbb{N};
  3. Kernel Gaussiano: K({\bf x}_i, {\bf x}_l) = \exp\left\{-\frac{d^2({\bf x}_i, {\bf x}_l)}{2h^2}\right\},\, h > 0;
  4. Kernel Laplaciano: K({\bf x}_i, {\bf x}_l) = \mathrm{e}^{-\gamma d({\bf x}_i, {\bf x}_l)}\,, \gamma > 0;
  5. Kernel Sigmóide: K({\bf x}_i, {\bf x}_l) = \tanh(\gamma {\bf x}_i^T{\bf x}_l + \theta), \, \gamma>0, \theta>0.


em que \gamma, h, \theta e p, são parâmetros do kernel.

Suport Vector Regression Machine


A ideia principal por trás dos métodos que fazem uso de kernel é fazer o uso de um mapeamento não-linear arbitrário \phi do espaço original dos padrões de entrada para um espaço de mais alta dimensão, \mathcal{X} \times \mathcal{X} chamado de espaço de características.


Um conjunto de padrões que entrada, em um problema de classificação, por exemplo, que não é linearmente separável poderá se tornar linearmente separável através desse mapeamento não linear.


Em um espaço vetorial, o produto interno, \langle{\bf x}_i,{\bf x}_j\rangle = {\bf x}_i^{T}{\bf x}_j, normalmente é utilizado como medida de similaridade entre vetores. Porém, como não conhecemos \phi(\cdot), não é possível realizar \phi({\bf x}_i)^T\phi({\bf x}_j) em \mathcal{X}\times\mathcal{X}. Aparentemente é bem complicado calcular \langle\phi({\bf x}_i),\phi({\bf x}_j)\rangle, sem conhecer \phi(\cdot), não é verdade?!

Suport Vector Regression Machine


A essência dos métodos métodos baseado em kernel, é que não precisamos conhecer \phi(\cdot). Os produtos internos no espaço de características poderá ser calculado utilizando um kernel de Mercer.


Teorema de Mercer: Todo kernel de Mercer K pode ser decomposto como

K({\bf x}_a,{\bf x}_b) = \sum_{i \geq 0} \gamma_i \phi_i({\bf x}_a)\phi_i({\bf x}_b), \tag{6} em que \sum_{i \geq 0} \gamma_i^2 < \infty e \phi_0, \phi_1, \cdots é uma sequência de funções. Essa propriedade em que o kernel de Mercer poderá ser decomposto na forma acima e que não precisamos conhecer \phi(\cdot) para o cálculo de produtos internos no espaço de características é comumente denominada de kernel trick/truque kernel. Ver detalhes em aqui.

Suport Vector Regression Machine


Definição: Seja K um kernel de Mercer, e sejam \phi_i e \gamma_i,\, i \geq 0, da forma do teorema acima. Então,


\mathcal{H}_K = \left\{g \in L^2(\mathcal{X}):\,\, existem\,\, (c_i)_{i\geq0}\,\, com\,\,\sum_{i\geq1}\frac{c_i^2}{\gamma_i} < \infty\,\,,\, tais\,\, que\,\, g({\bf x}) = \sum_{i\geq1} c_i\phi_i({\bf x}) \right\}.


Diremos que \mathcal{H}_k é o Reproducing Kernel Hilbert Space - RKHS associado ao kernel K, em que a norma de uma função g({\bf x}) = \sum_{i\geq0}c_i\phi({\bf x}) é definda por

||g||_{\mathcal{H}_k}^2 := \sum_{i \geq 0} c_i^2/\gamma_i.


Além disso, tem-se que \mathcal{H}_K é única para um dado K, muito embora a decomposição dada pela Equação 6 não seja única.

Suport Vector Regression Machine


O Teorema que segue, frequentemente atribuído a ao artigo Kimeldorf, G. S. & Wahba, G. (1970). A correspondence between Bayesian estimation on stochastic processes and smoothing by splines. The Annals of Mathematical Statistics, 495–502, simplifica o problema de otimizar a função objetivo dada na Equação 5.


Teorema da Representação: Seja K um kernel de Mercer correspondente ap RKHS \mathcal{H}_K. Considere o conjunto de treinamento ({\bf x}_1, y_1), \cdots, ({\bf x}_n, y_n) e uma função de perda arbitrária L. Então, a solução de


\argmin_{g \in \mathcal{H}_K} \sum_{k = 1}^n L(g({\bf x}_k), y_k) + \lambda||g||_{\mathcal{H}_K^2}, \tag{7} existe, e é única, em que


g({\bf x}) = \sum_{k=1}^n \alpha_k K({\bf x}_k, {\bf x}), em que \alpha_1, \cdots, \alpha_n é uma sequência de valores reais.

Suport Vector Regression Machine


É possível desmontrar que a otimização da Equação 7 poderá se dar pela otimização de


\argmin_{\alpha_1, \cdots, \alpha_n}\sum_{k = 1}^n L\left(\overbrace{\sum_{i=1}^n\alpha_iK({\bf x}_i, {\bf x})}^{g({\bf x})}, y_k \right) + \lambda\underbrace{\sum_{1 \leq j,k \leq n}\alpha_i\alpha_k K({\bf x}_j, {\bf x}_k)}_{||g||_{\mathcal{H}_K}^2}.


Portanto, o problema de otimizar a função objetivo dada na Equação 5 se reduz a encontrar os valores de \alpha_1, \cdots, \alpha_n que minimiza a Equação 7. Portanto, o problema reduz a especificar o kernel K e a função de perda L, em que \lambda é um parâmetro de sintonização (hiperparâmetro) que poderá ser obtido por uma validação cruzada.

Suport Vector Regression Machine


Em se tratando de estimadores de regressão, Support Vector Regression Machines, utiliza-se uma função de perda diferente da quadrática, como definido em Drucker, H., Burges, C. J., Kaufman, L., Smola, A. J. & Vapnik, V. (1997). Support vector regression machines em Advances in neural information processing systems.


Eles definem a seguinte função de perda L(g({\bf x}_k, y_k)) = (|y_k - g({\bf x}_k)| - \varepsilon)_{+}, que assume o valor 0 se |y_k - g({\bf x}_k)| < \varepsilon e assumirá |y_k - g({\bf x}_k)| - \varepsilon, caso contrário.


Suport Vector Regression Machine


Exemplo: Vamos consirar a base de dados de vinho vermelho🍷, disponíveis aqui, faça uma pequena análise exploratória dos dados. No link do Kaggle você consegue uma explicação sobre o que significa cada uma das variáveis.


📚 Exercícios


Exercício: Considere o problema em que o objetivo é prever a pontuação (score) / (item 2) do aluno com base em algumas variáveis. São elas:


  1. Horas estudadas: o número total de horas gastas estudando por cada aluno;
  2. Pontuação: As notas obtidas pelos alunos em testes anteriores;
  3. Atividades Extracurriculares: Se o aluno participa de atividades extracurriculares (Sim ou Não);
  4. Horas de sono: o número médio de horas de sono que o aluno teve por dia;
  5. Amostras de perguntas praticadas: O número de amostras de perguntas que o aluno praticou.


Você poderá baixar e ter uma descrição maior da base de dados clicando aqui. Avalie o poder preditivo do modelo lasso com o do SVRM. Sua análise deve conter uma análise exploratória dos dados, deverá utilizar um esquema de k-folds cross-validation, com k = 20 e considerar um esquema de data splitting na proporção 90\% para treino e 10\% para teste. Sua análise deverá estar em um notebook de quarto, em que você deverá comentar cada passo da análise.

📚 Exercícios


Exercício: Refaça o exercício anterior considerando a base de dados de dados pessoais de custos médicos disponível aqui. As informações contidas na base são:


  1. Idade (age): idade do beneficiário principal;
  2. Sexo (sex): sexo do beneficiário;
  3. Índice de massa corporal - IMC (bmi): IMC = \frac{Peso}{altura^2}, com peso em Kg e altura em m (metro);
  4. Número de filhos (children): número de filhos cobertos pelo plano de saúde;
  5. Fumante (smoker): variável dummy que informa se o beneficiário é ou não fumante;
  6. Região (region): região dos EUA em que o beneficiário reside (northeast, southeast, southwest ou northwest);
  7. Custo (charges): custos médicos cobrados, em dólares.


O objetivo é prever o custo (charges) com base nas demais informações. Dica: utilizando a biblioteca recipes, utilize a função step_dummy especificando o argumento one_hot = TRUE para realizar um one hot encoding com a variável region. Dessa forma, region deixará ser ser uma variável nominal e se tornará uma variável numérica.

🌳 Árvores de regressão


Em aprendizagem de máquina, uma arvore de regressão consiste em uma metodologia não-paramétrica que nos leva a resultados que são facilmente interpretáveis. A árvore é construirda por meio de particionamentos recursivos no espaço das covariáveis. Cada partição recebe o nome de e o resultado final (valor da regressão) é denominado de folha 🍃.


Exemplo: Construindo uma árvore de regressão, usando a biblioteca rpart para construção da árvore e a biblioteca rpart.plot para a plotagem da árvore estimada.

library(rpart)
library(rpart.plot)

dados <- readr::read_csv(file = "dados/winequality-red.csv", show_col_types = FALSE)
arvore <-  rpart::rpart(formula = quality ~ ., data = dados)
rpart.plot(arvore, type = 4, extra = 1)

Perceba que há um particionamento binário em cada nó da árvore 🌳 e que poderemos sequir para a aresta da esquerda quando a condição no nó for verdadeira e para direita caso contrário. Dada uma nova observação {\bf x}_i, poderemos seguir as condições da árvore até chegar a uma folha 🍃 dessa árvore. Nesse caso, a folha 🍃 contém uma estimativa da qualidade do vinho.


Por exemplo, na árvore acima podemos perceber que vinhos com teor alcoólico inferior à 11 nos conduzem a vinhos 🍷 de qualidade inferior. Temos que vinhos com teor alcoólico maior que 11 e com sulfato maior que 0.65 nos levam à ótimos vinhos, segundo o conjunto de dados utilizado.


Perceba que dado {\bf x}_i, poderemos percorrer a árvore a mão.

🌳 Árvores de regressão


Formalmente, temos que a metodologia de estimação de uma árvore de regressão cria uma partição no espaço das covariáveis em regiões distintas e disjuntas, que denotaremos de R_1, R_2, \cdots, R_j, com R_a \cap R_b, \forall a,b \in 1, \cdots, j. A predição é dada por:

g({\bf x}) = \frac{1}{|\{i:{\bf x}_i \in R_k\}|}\sum_{i:{\bf x}_i \in R_k} y_i.


A construção de uma árvore de regressão envolve dois passos principais:


  1. A criação de uma árvore complexa que nos leve a partições “puras”, i.e., a partições nas observações do conjunto de covariáveis que nos leve a valores de Y, no conjunto de treinamento em cada uma das folhas sejam homogêneas;

  2. Podar a árvore, com a finalidade de evitarmos super-ajuste (overffiting), e portanto, termos uma alta variância do modelo.

🌳 Árvores de regressão


A ideia de uma árvore 🌳 pura é de que “todo mundo” que cai em uma dada folha é muito homogêneo em relação a variável resposta (ao rótulo/label).


Em uma árvore de regressão, a maneira mais simples de definir pureza é utilizar o EQM.


🌳 Árvores de regressão


No passo 1, para avaliarmos o quão razoável é uma árvore T, utilizamos o Erro Quadrático Médio - EQM, em que

\mathcal{P}(T) = \sum_{R}\sum_{i:{\bf x}_i \in R} \frac{(y_i - \widehat{y}_R)^2}{n}, em que \widehat{y}_R é o valor predito de y para a resposta de uma observação pertencente à região R. No gráfico de uma árvore de regressão, o R é dado por todos os indivíduos na base de dados que estão em uma dada folha 🍃.


Encontrar uma árvore T que minimize \mathcal{P}(T) é uma tarefa computacionalmente cara. Por isso, que os algoritmos de estimação de T normalmente utilizam partições binárias, como no exemplo anterior.


Existem diversos algoritmos utilizados para estimação de T, em que o Classification And Regression Tree - CART é o mais conhecido. O algoritmo foi estabelecido no livro Breiman L., Friedman J. H., Olshen R. A., and Stone, C. J. (1984) Classification and Regression Trees. Wadsworth.

🌳 Árvores de regressão


O algoritmo particiona o espaço de covariáveis em duas regiões disjuntas. Para a escolha dessa partição, busca-se, dentre todas as covariáveis x_i e cortes t_1, a combinação que conduz a uma partição (R_1, R_2) com menor predições de erro quadrático, i.e., dado um nó, a partição é construída de modo a minimizar:

\overbrace{SSE}^{\text{sum of squares error}} = \sum_{i:{\bf x}_i \in R_1}^n (y_i - \widehat{y}_{R_1})^2 + \sum_{i:{\bf x}_i \in R_2}^n (y_i - \widehat{y}_{R_2})^2, \tag{8} em que \widehat{y}_{R_k} é a predição de y fornecida pela região R_k e SSE é denominado Sum of Squares of Errors - SSE.


Assim, tem-se que o algoritmo irá fornecer:


R_1 = \{{\bf x} : {\bf x}_i < t_1\}\, \mathrm{e}\, R_2 = \{{\bf x} : {\bf x}_i \geq t_1\}, em que x_i é a variável escolhida e t_1 é o corte definido.

🌳 Árvores de regressão


Uma vez que estabelecemos um nó raiz, este é fixado. No passo seguinte, o algoritmo irá particionar as regiões R_1 e R_2 em regiões menores, seguindo o mesmo critério,tanto para R_1 quanto para R_2. O algoritmo continua de forma recursiva até que tenhamos uma árvore com poucas observações em uma das folhas 🍃.


Por exemplo, podemos decidir em parar de tornar a árvore profunda quando em cada folha tivermos menos de 5 observações. Porém, essa árvore criada irá produzir boas predições para o conjunto de treinamento, porém, não irá performar bem em novas observações. Isso, por conta, do trade off entre viés e variância. Em outras palavas, haverá overfitting.


Na etapa do processo de poda, cada nó é retirado, um por vez, e observa-se como o erro de predição varia no conjunto de validação. Com base nisso, decide-se quais nós permanecem na árvore. O processo de poda reduz o overffiting do modelo.

🌳 Árvores de regressão


Uma observação importante é que sempre é bom crescer a árvore ao máximo possível e depois proceder com a etapa de “poda”, em que removemos os ramos mais profundos e reavaliamos o poder preditivo da árvore 🌳 T. Isso porquê a melhoria do poder preditivo da árvore não é linear, i.e., as vezes podemos ter uma divisão que piore um pouco a capacidade preditiva da árvore, porém, a divisão seguinte poderá dar um grande salto de melhoria. Dessa forma, é mais conveniente deixar a árvore “profunda” para depois sairmos “podando” ✂️ a árvore 🌳.


🌳 Árvores de regressão


Para limitar a profundidade da arvore T a ser estimada, um parâmetro de penalização/complexidade poderá ser introduzido na Equação 8. Assim, o problema consistem em obter regiões e pontos de cortes que minimize:

SSE + \alpha|T|, em que \alpha > 0 é o hiperparâmetro de complexidade do modelo e |T| é um valor inteiro que define a profundidade máxima da árvore. Por exemplo, |T| na biblioteca rpart é definido pelo parâmetro tree_depth em 30. Normalmente nos concentramos em em tunar o parâmetro cost_complexity que é o \alpha.

🌳 Árvores de regressão


Exemplo: Poderemos podar a árvore usando a função prune do pacote rpart. Considerando o exemplo anterior, tornando a árvore menos compleça, poderíamos decidir em podar alterando o seu parâmetro de complexidade.

library(rpart)
library(rpart.plot)

dados <- readr::read_csv(file = "dados/winequality-red.csv")

arvore <- dados |> 
  rpart::rpart(formula = quality ~ ., data = _) 

# O melhor parâmetro de custo
melhor_grau <- arvore$cptable[nrow(arvore$cptable),][1L]

# Aumentando o parâmetro de custo de 0.01 para 0.04
arvore_podada <- rpart::prune(tree = arvore, cp = 0.04)

# Plotando a árvore podada
rpart.plot::rpart.plot(arvore_podada)

🌳 Árvores de regressão


O parâmetro de complexidade/custo é um hiperparâmetro, e deverá ser estimado dentro de um procedimento de validação cruzada.


🌳 Árvores de regressão


Algumas observações sobre a árvore 🌳 de regressão:


  1. É um método não-paramétrico;
  2. Pode ser representado graficamente;
  3. É útil na análise exploratória dos dados do problema em questão;
  4. Pode ser utilizada para selecionar variáveis. Aparentemente, as variáveis que pertence à árvore tem uma maior importância para o problema em questão;
  5. Poderá trabalhar com variáveis numéricas, mas também poderá trabalhar com variáveis categóricas;
  6. A árvore é robusta na presença de variáveis irrelevantes.


🌳 Árvores de regressão


O nível de complexidade é obtido via cross-validation de modo a encontrar a menor subárvore que generaliza melhor o problema para dados não visto.


Assim como nos modelos de regressão com penalidade que vimos anteriormente, aqui, para valores menores de \alpha tende a produzir modelos mais complexos. Consequentemente, à medida que uma árvore cresce, a redução no SSE deve ser maior do que a penalidade de complexidade de custo.


🌳 Árvores de regressão


Experimente alterar o parâmetro de complexidade e perceba como ele influencia nas regições da árvore de regressão. Quando maior o valor, maior a penalidade, e portanto, mais simples será a árvore de regressão que irá estimar os valores de Y_i com base em X_i.


🌳 Árvores de regressão


Algumas outras características das 🌳 árvores de regressão são:


  1. A função de regressão estimada \widehat{r}({\bf x}) é constante por partes, i.e., em uma folha 🍃 tendemos predizer que vários indivíduos distintos tem o mesmo valor de \widehat{r}({\bf x});
  2. Em uma árvore 🌳 de regressão as interações entre variáveis são consideradas de forma automática, enquanto em uma regressão as interações são introduzidas como os produtos entre covariáveis;
  3. Elas são uma espécie de “samambaias”, em que crescem para baixo;
  4. É fácil introduzir variáveis categóricas 🎉;
  5. Uma pessoa sem muito conhecimento poderá estimar \widehat{r}({\bf x}), dada uma árvore, para cada nova observação {\bf x}.

📚 Exercícios


Exercício: Considere a variável aleatória Y_i \sim \mathcal{N}(\sin(X_i), \sigma^2), com X_i \in \mathcal{U}(0, 10)\,, \forall i. Utilizando a biblioteca rpart, implemente a função arvore(n = 250L, complexidade, sigma = 0.1) que devolve o gráfico scatterplot com os pontos X_i e Y_i e por cima deles o gráfico de linha com os valores preditos. A função deverá ter três argumentos, n, complexidade e sigma, que são o tamanho da amostra, o grau de complexidade do modelo, e o desvio padrão, respectivamente. Aqui não se preocupe com divisão entre treino e teste nem validação cruzada. A solução desse exercício não tem como objetivo encontrar o melhor hiperparâmetro, i.e., não é necessário “tunar” o parâmetro complexidade. A função deverá retornar algo como:

📚 Exercícios


Exercício: Ainda com base no exercício anterior, construa uma função que retorne várias estimativas obtidas por árvores de regressão, com base em várias amostra de X_i e Y_i. O gráfico deverá mostrar as estimativas das diversas árvores de regressão, sem mostrar os pontos. Perceba a flutuação das estimativas em diferentes valores de complexidade, dado n e sigma fixos.


Exercício: Sem utilizar a biblioteca tidymodels, apenas as bibliotecas rsample e rpart, treine um modelo com 10 mil observações geradas. No procedimento k-folds cross-validation, para k = 20, encontre um bom valor para o grau de complexidade considerando um grid de possíveis valores. Dica: experimente testar um parâmetro dentro do conjunto de treino no procedimento de cross-validation. Por exemplo, experimente criar um grid com valores entre 0.001 e 0.4. Aprensente o gráfico com a estimativa do melhor modelo. Não esqueça de fixar um valor de semente, para que os resultados possam ser reproduzidos.


Exercício: Refaça o exercício anterior utilizando a biblioteca tidymodels. Fique livre para tunar o hiperparâmetro que achar necessário, da engine que utilizar com a biblioteca parsnip. Compare o risco preditivo do exercício anterior com o que você obteve utilizando o tidymodels.

Combinando predições - bagging


As árvores 🌳 de regressão tem a característica de ser facilmente interpretável, porém, costumam ser uma capacidade preditiva baixa.


Uma das ideias de melhorar a capacidade preditiva é combinar árvores usando a metodologia de reamostragem bootstrap. As técnicas de reamostragem via bootstrap não-paramétrico são bastante difundidas na estatística e consiste reamostrar da amostra original com reposição.


Na estatística, a ideia de bootstrap é muito comum para correção de víes de um estimador, cálculo do erro-padrão de um estimador, construção de intervalos de confianças e teste de hipóteses.


Em aprendizagem de máquina, o conceito de bagging consiste em reaplicar uma metodologia, nesse caso a de árvore 🌳 de regressão em diferentes amostras obtidas da amostra original com reposição.


Combinando predições - bagging


Caso você tenha interesse e entender o método de bootstrap, no contexto mais difundido na estatística como mencionado no slide anterior, assista a vídeo aula sobre bootstrap do Prof. Pedro Rafael do Departamento de Estatística da Universidade Federal da Paraíba - UFPB.


Combinando predições - bagging


Portanto, é importante ficar claro que o bagging é uma metodologia genérica que poderá ser aplicada em situações ao qual desejamos combinar modelos. Por exemplo, o método ramdom forest que veremos mais a frente é uma pequena modificação de um bagging de árvores de regressão que vimos anteriormente. Na verdade o ramdom forest, assim como as árvores de decisões podem ser utilizadas também para problemas de classificação, como veremos mais adiante no curso.


Combinando predições - bagging


A ideia de combinar árvores de regressão é interessante, uma vez que o risco preditivo da média das previsões das árvores é menor que o risco individual de cada uma das árvores.


Lembre-se que quando falávamos em momentos anteriores do curso sobre o balanço entre viés e variância, apresentamos uma decomposição do risco quadrático R(g) condicional a um novo {\bf x} dada na Equação 2. Para que não seja necessário necessário voltar um grande número de slides, recoloco a decomposição abaixo:


\mathbb{E}\left[(Y - \widehat{g}({\bf X}))^2| {\bf X} = {\bf x}\right] = \underbrace{\mathbb{V}[Y | {\bf X = x}]}_{\mathrm{i - Variância\,\, intrínseca}} + \overbrace{(r({\bf x}) - \mathbb{E}[\widehat{g}({\bf x})])^2}^{\mathrm{ii - Viés\, ao\, quadrado\, do\, modelo}} + \underbrace{\mathbb{V}[\widehat{g}({\bf x})]}_{\mathrm{iii - Variância\, do\, modelo}}.

Combinando predições - bagging


Ainda no caso de árvores 🌳 de regressão, suponha um caso mais simples, em que temos dois modelos de árvores de regressão, sejam eles g_1 e g_2, respectivamente, em que a previsão combinada é dada por:

\widehat{g}({\bf x}) = \frac{\widehat{g_1}({\bf x}) + \widehat{g_2}({\bf x})}{2}, em que \widehat{g_1}({\bf x}) e \widehat{g_2}({\bf x}) são as estimativas da função de regressão r({\bf x}) fornecidades pelas árvores g_1 e g_2, respectivamente.


Supondo que \widehat{g_1}({\bf x}) e \widehat{g_2}({\bf x}) são não-viesados e possuem a mesma variância, e além disso são não correlacionados então:

\begin{align*} \mathbb{E}[(Y - g({\bf x}))^2|{\bf x}] &= \mathbb{V}[Y|{\bf x}] + \frac{1}{4}(\mathbb{V}[\widehat{g}_1({\bf x}) + \widehat{g}_2({\bf x})|{\bf x}]) \\ &\quad + \left(\mathbb{E}[Y|{\bf x}] - \frac{\mathbb{E}[\widehat{g}_1({\bf x})|{\bf x}]+\mathbb{E}[\widehat{g}_2({\bf x})|{\bf x}]}{2} \right)^2 \end{align*}

Combinando predições - bagging


Note que se \widehat{g_1}({\bf x}) e \widehat{g_2}({\bf x}) então \mathbb{E}(\widehat{g}_1({\bf x})|{\bf x}) = \mathbb{E}(\widehat{g}_2({\bf x})|{\bf x}) = r({\bf x}). Isso faz com que a última parcela da equação anterior seja zero. Se além disso, se os estimadores possuem a mesma variância, então a decomposição do risco preditivo combinado é simplificada e dada por:

\overbrace{\mathbb{E}[(Y - \widehat{g}({\bf x}))^2|{\bf x}]}^{\text{Risco preditivo combinado}} = \mathbb{V}[Y|{\bf x}] + \frac{1}{2}\mathbb{V}[\widehat{g}_i({\bf x})| {\bf x}] \leq \underbrace{\mathbb{E}[(Y - \widehat{g}_i({\bf x}))^2 | {\bf x}]}_{\text{Risco preditivo individual}}, \tag{9} para um dado i, com i = 1, 2. Dado as suposições de estimadores não-viesados, variância iguais e que os estimadores são não-correlacionados, a Equação 9 poderia ser generalizada para o caso de mais de dois estimadores, no nosso caso, para mais de duas árvores de regressão 🌳. Basta utilizar indução matemática!


Combinando predições - bagging


Devemos notar, contudo que, para que tenhamos árvores 🌳 aproximadamente não-viesadas, não devemos “podar” as árvores️ 🌳. Muito embora elas possam aprensenta overfitting quando consideradas individualmente o risco preditivo combinado irá diminuir.


Portanto, seja B o número de pseudo-amostras bootstrap, i.e., amostras obtidas da amostra original com reposição. Então, o estimador combinado em um procedimento de bagging é dado por:

\widehat{g}({\bf x}) = \frac{1}{B}\sum_{b = 1}^B \widehat{g}_b({\bf x}).

Combinando predições - bagging


Uma observação importante é que o bagging pode ser ruim em modelos quando utilizado em modelos intrinsecamente estáveis, como é o caso de modelos lineares de regressão, kNN, regressão logística, entre outros. O procedimento de bagging costuma ser eficaz quando são utilizados em modelos que possuem uma alta variância e que tendem a ter overfitting, como o caso da árvore de decisão profunda (árvores de regressão e de classificação).


Combinando predições - bagging


Uma forma de perceber a estabilidade de um modelo de aprendizagem de máquina é avaliar as predições do modelo em diferentes partições do conjunto de dados, por exemplo, em um procedimento de validação cruzada repetida. A função rsample::vfold_cv(), permite a possibilidade de retepir uma validação cruzada por meio do argumento repeats, que por default é igual à 1.


Uma outra forma seria utilizar um procedimento de bootstrap não-paramétrico (reamostrar da amostra com reposição) e treinar o modelo em cada pseudo-amostra bootstrap e avaliar a variabilidade das estimativas no conjunto de validação.


Uma outra forma seria avaliar a curva de aprendizado do modelo. Essa curva poderá ser obtida treinando o modelo em diferentes tamanhos de conjunto de treinamento, em que observa-se como varia a peformance do modelo nos diferentes tamanhos do conjunto de treinamento.


Combinando predições - bagging


Muito embora a combinação preditiva usando bagging de um conjunto de árvores de regressão não são tão fáceis de interpretar quando comparada com uma única árvore de regressão, ele permite que possamos criar uma medida de importância para cada variável. Essa medida baseia-se na redução da Residual Sum of squares - RSS de cada divisão.


Uma simples divisão binária em qualquer ponto de uma dada árvore 🌳 de regressão. Desejamos encontrar a importância da variável “pai” que poderá aparecer em diversas divisões em uma mesma árvore.


Devemos computar a redução da soma dos quadrados dos resíduos em cada nó em que a variável do nó “pai” aparece em todas as árvores obtidas pelo procedimento de bagging, em que calculamos a soma dos quadrados no nó “pai” e subtraímos da soma dos quadrados do nó esquerdo e do nó direito.

Combinando predições - bagging


Assim, a importância da variável no nó “pai”, em uma dada divisão binária em uma dada árvore 🌳 do procedimento de bagging é dada por:


\begin{align*} \text{Importância local} &= RSS_{pai} - RSS_{esq} - RSS_{dir} = \\ & \sum_{i \in pai} (y_i - \overline{y}_{pai})^2 - \sum_{i \in esq} (y_i - \overline{y}_{esq})^2 - \sum_{i \in dir} (y_i - \overline{y}_{dir})^2. \end{align*}


Foi denominado de “Importância local”, uma vez que o nó “pai” (variável de interesse) poderá aparecer nas diversas B árvores de regressão do procedimento bagging, e mais, poderá aparecer várias vezes em uma mesma árvore. Portanto, em todas as B e em todas as ocorrências da variável “pai” em qualquer ponto de uma árvore a “Importância local” deverá ser calculada. Ao fim, deverá somar todas as importâncias locais para se ter a importância global da variável no nó “pai”.

📚 Exercícios


Considere a variável aleatória Y_i \sim \mathcal{N}(\sin(X_i), \sigma^2), com X_i \in \mathcal{U}(0, 10)\,, \forall i.


Exercício: Avalie a estabilidade de uma árvore de regressão usando um validação cruzada repetida. Construa um gráfico com os riscos observados na validação para um \sigma^2 fixo.


Exercício: Avalie a estibilidade usando um procedimento de bootstrap. Construa um gráfico com os riscos observados na validação para um \sigma^2 fixo.


Exercício: Por fim, avalie a estabilidade utilizando avaliando a curva de aprendizado do modelo.


🌲🐞🌳🐝🌲🦋🌳 Random Forest


O random forest (floresta aleatória) é um procedimento de bagging em que introduz um níveo de aleatoriedade maior no processo de selação das variáveis, visando reduzir ainda mais a correção entre as árvores de regressão. Isso é feito sorteando m < d covariáveis em cada particionamento, i.e., essa randomização é feita em toda divisão de todas as B árvores do procedimento de bagging, em que d é o total de covariáveis consideradas.


O valor de m poderá ser obtido via algum procedimento de cross-validation, i.e., é um hiperparâmetro que poderá ser “tunado”.

🌲🐞🌳🐝🌲🦋🌳 Random Forest


A ideia de construir árvores que sejam menos correlacionadas uma com as outras nos aproxima melhor do critério de não correlação que utilizamos para mostrar que o risco preditivo combinado é menor que o risco preditivo ao considerar uma única árvore, como mostra a Equação 9.


O algoritmo random forest consegue produzir um estimador com menor variância que o bagging. Além disso, assim como no bagging, podemos calcular a importância de cada variável usando o mesmo procedimento apresentado anteriormente.


Muito embora o bagging é um procedimento útil para diminuir a variância de um modelo utilizando predições combinadas, o procedimento de random forest para o caso de árvores de regressão ou de classificação nos conduz a um estimador com menor variância.


Um valor de m frequentemente considerado é o valor inteiro que aproxima \sqrt{d}.

🌲🐞🌳🐝🌲🦋🌳 Random Forest


A aplicação abaixo permite que você possa comparar as estratégias de bagging com o random forest, sendo este um bagging de árvores de regressão com o pequeno ajuste mencionado anteriormente, permitindo termos árvores menos correlacionadas. Perceba que o random forest consegue diminuir ainda mais a variância das previsões de Y. A linha vermelha é a distribuição verdadeira.



Se desejar ver de forma ampliada, acesse a aplicação clicando aqui.

🚀 Boosting


Da mesma forma que métodos como bagging e random forest, os métodos de boosting também tem como objetivo agregar diferentes estimadores da função de regreção r({\bf x}). A ideia desses métodos que combinam diferentes estimadores da função de regressão é melhora a precisão e a performance preditiva dos modelos de máquina, convertendo vários aprendizes fracos em um único modelo de aprendizado forte.


O boosting 🚀 funciona construindo os modelos de forma sequencial, dando mais peso às instâncias que foram classificadas incorretamente nos modelos anteriores. O funcionamento do boosting é semelhante ao bagging e random forest, exceto pelo fato de que a árvore irá crescendo sequencialmente: cada árvore é “cultivada” 🌱🚿🌳 usando informações de árvores crescidas. Boosting não envolve amostragem bootstrap; em vez de cada árvore é ajustada em uma versão modificada do conjunto de dados original.


🚀 Boosting


Existem diversas variações e implementações de boosting com diversas implementações distintas em diferentes frameworks de aprendizagem de máquina. Aqui, será descrito o conceito geral, sendo este a forma mais usual do boosting.


No boosting, como mencionado anteriormente, o estimador \widehat{g}({\bf x}) é consturído incrementalmente, i.e., de forma sequencial, melhorando a cada passo. Inicialmente considera-se \widehat{g}({\bf x}) \equiv 0. Fazer \widehat{g}({\bf x}) \equiv 0 estamos iniciando um estimador com alto viés, porém, com variância muito baixa, a saber, variância zero.


A cada passo do algoritmo, procuramos obter uma árvore menos viesada, porém, como mencionado em anteriormente, quadando falamos sobre o trade off entre viés e variância, obtemos uma árvore atualizada com uma variância um pouco maior. Por isso é importante partir de uma árvore inicial com variância muito baixa.


🚀 Boosting


A ideia é construir um estimador para r_i, em que na primeira iteração, considera-se r_i = y_i. A cada passo subsequente, atualizamos r_i para r_i = y_i - \widehat{g}({\bf x}_i), em que r_i denominados de resíduo. Portanto, a ideia do boosting é prever o resíduo que inicia-se no rótulo/label y_i.


Uma observação importante é que as árvores tenham profundida pequena de modo a evitar overfitting.


Além disso, considera-se uma taxa de aprendizagem (learning rate) que denotaremos por \lambda \in [0, 1] que tem como objetivo controlar o super-ajuste. Trata-se de um hiperparâmetro que deverá ser obtido via algum procedimento de cross-validation. Portanto, deveremos “tunar” 🎛 o valor de \lambda de modo a encontrar um valor adequado que maximize nosso risco observado, i.e., que maximize a previsão de R(g).

🚀 Boosting


Os passos para conseguir boas estimativas de usando o boosting são:


  1. Definimos \widehat{g}({\bf x}) \equiv 0 e r_i = y_i, \, \forall i = 1, \cdots, n;
  2. Para cada b = 1, \cdots, B, fazemos:
  • Ajustamos uma árvore com p folhas para ({\bf x}_1, r_1), \cdots, ({\bf x}_n, r_n), em que denotamos essa função de predição por \widehat{g}^b({\bf x}). Lembre-se que estamos estimando r_i com base em {\bf x}_i;
  • Atualizamos g e os resíduos: \widehat{g}({\bf x}) \leftarrow \widehat{g}({\bf x}) + \lambda \widehat{g}^b({\bf x}) e r_i \leftarrow Y_i - \widehat{g}({\bf x}).
  1. Retorna-se o modelo final \widehat{g}({\bf x}).

No boosting, os valores de \lambda, p e B são hiperparâmetros e devem ser obtidos por meio de algum procedimento de validação cruzada, i.e., você deverá “tunar” 🎛. É comum que \lambda seja pequeno, por exemplo, 0,01 ou 0,001, B \approx 1000 e p de ordem próxima à 2 ou 4.

🚀 Boosting


Considere a variável aleatória Y_i \sim \mathcal{N}(\sin(X_i), \sigma^2), com X_i \in \mathcal{U}(0, 10)\,, \forall i. Experimente na aplicação web que segue o método boosting. Observe o comportamento do estimador variando os parâmetros do algoritmo boosting. Perceba que para \lambda = 0 não há aprendizado algum! Se desejar visualizar a aplicação abaixo de forma ampliada, clique aqui.


🚀 Boosting


Em geral, as abordagens de aprendizagem estatística que aprendem lentamente tendem a executar bem. Observe que no boosting, ao contrário do ensacamento, a construção de cada árvore depende fortemente das árvores que já foram “cultivadas” 🌱🚿🌳. Portanto, diferentemente do bagging que é um algoritmo em que as árvores em cada iteração são mutuamente independentes e, portanto, facilmente paralelizável, no boosting as árvores são dependentes umas das outras.


Na literatura de machine learning há diversos algoritmos que implementam o boosting. Uma implementação bastante popular, por conta de seu desempenho, é denominada XGBoost, proposto em CHEN, Tianqi; GUESTRIN, Carlos. Xgboost: A scalable tree boosting system. In: Proceedings of the 22nd acm sigkdd international conference on knowledge discovery and data mining. 2016. p. 785-794.


No R, o algoritmo está implementado na biblioteca xgboost. Caso deseje utilizar a biblioteca tidymodels em sua modelagem, é possível utilizar o XGBoost usando a função boost_tree da biblioteca parsnip que faz parte do tidymodels. Por padrão, essa função já utiliza o algoritmo XGBoost.

🚀 Boosting


Uma outra forma de obter um valor adequado de B é parar de adicionar iterações quando o risco observado começa a aumentar. Lembre-se que há um trade off entre viés e variância, em que as iterações começam por um estimador \widehat{g} com variância nula e na medida que as iterações progridem, \widehat{g} aumenta sua variância em troca da diminuição do viés. Para algum valor de B o estimador poderá perder um pouco de performance. A ideia é escolher um valor de B antes de atingir uma piora de \widehat{R}(g) (risco observado). Essa estratégia é denominada de early stopping ⌛.


Muito embora aqui apresentamos o algoritmo boostring em um contexto de árvores de regressão, esse algoritmo é genérico e poderá ser utilizado como estatégia para combinar modelos que são individualmente fracos.


📚 Exercícios


Exercício: Considere a variável aleatória Y_i \sim \mathcal{N}(\sin(X_i), \sigma^2), com X_i \in \mathcal{U}(0, 10)\,, \forall i. Implemente uma função em R que construi o gráfico da aplicação anterior. Essa função deverá ter os argumentos do algoritmo boosting, além de outros argumentos para controle do tamanho da amostra e da variância \sigma^2.


Exercício: Construa um gráfico com a avaliação do risco observado do método de boosting para diferentes valores de B. Considere B = 1, \cdots, 10000. Comente o resultado.


Exercício: Utilizando um procedimento de validação cruzada, e o tidymodels, obtenha uma estimativa para os hiperparâmetros da função boost_tree(), a saber, os argumentos trees (número de árvores), tree_depth (profundidade da árvore) e learn_rate (taxa de aprendizado). Avalie o risco preditivo do modelo, i.e., estime \widehat{R}(g).


Exercício: Compare o bagging de árvores de regressão, com o método random forest e o boosting de árvores de regressão. Qual o risco preditivo de cada um deles para prever Y_i? Construa um gráfico com as previsões, no conjunto de teste, de cada um dos modelos.

📚 Exercícios


Exercício: Considere o problema em que o objetivo é prever a pontuação (score) / (item 2) do aluno com base em algumas variáveis. São elas:


  1. Horas estudadas: o número total de horas gastas estudando por cada aluno;
  2. Pontuação: As notas obtidas pelos alunos em testes anteriores;
  3. Atividades extracurriculares: Se o aluno participa de atividades extracurriculares (Sim ou Não);
  4. Horas de sono: o número médio de horas de sono que o aluno teve por dia;
  5. Amostras de perguntas praticadas: O número de amostras de perguntas que o aluno praticou.


Você poderá baixar e ter uma descrição maior da base de dados clicando aqui. Avalie o poder preditivo do random forest comparando com o boosting e kNN. Construa uma análise usando um notebook de quarto, comentando os passos.


Exercício: Refaça o exercício anterior usando os dados de vermelho 🍇🍷, disponíveis aqui.

📚 Exercícios


Exercício: Considere os dados reais de imóveis anunciados para venda na cidade de João Pessoa - PB. São mais de 27 mil observações obtidas via web scraping de sites de anúncios imobiliários e referem-se à anúncios do primeiro semestre de 2023. O interesse consiste em prever o valor de um novo imóvel com base nas features. Clique aqui 🗃️ para acessar a base. Nessa base encontra-se diversas o label (valor dos imóveis) e features numéricas como área, número de quartos, número de banheiros, coordenadas geográficas (latitude e longitude), quantidade de vagas de estacionamento, além de diversas outras features em forma de variáveis dummy com valores TRUEe FALSE, como por exemplo, elevador (se tem TRUE ou não FALSE), piscina (se tem TRUE ou não FALSE), salao_de_festa (se tem TRUE ou não FALSE), entre outras. Crie um notebook em quarto comparando todos os modelos de regressão apresentados até aqui. Realize uma análise exploratória dos dados e comente todos os resultados intermediários de sua análise. Conclua sua análise realizando um rank da performance preditiva de cada modelo apresentando o risco preditivo R(g) estimado. Algumas sugestões:

  1. Note que a feature tipo apresenta os tipos de imóveis. Foque em casas, apartamentos e terrenos. Por exemplo, o tipo terrenos_lotes_condominio considere apenas como terrenos e o tipo casas_de_condominio considere como sendo casas. Você poderá utilizar um one hot encoding para tornar essa variável numérica;

  2. Você não irá precisar das features endereco e bairro, já que essas informações está contida nas coordenadas geográficas;

  3. Desconsidere as colunas iptu e condomínio, já que são bastante incipientes;

  4. Impute observações faltantes (as que contém NA) pelo método kNN. Veja como fazer isso usando a função step_impute_knn da biblioteca recipes.

Você também estará livre para realizar outras receitas na base de dados antes dos treinamentos dos modelos.

📚 Exercícios


Exercício: Refaça o exercício anterior para outras cidades. Acesse os dados clicando nos nomes das cidades abaixo:


  1. Recife 🗃️
  2. Natal 🗃️
  3. Maceió 🗃️
  4. Brasília 🗃️


🧠 Redes neurais artificiais


Redes neurais artificiais - RNAs geralmente são apresentadas como sistemas de “neurônios interconectados, que podem computar/receber valores de entradas”, simulando o comportamento de redes neurais biológicas 🧠.


Mcculloch e Pitts.

O primeiro modelo artificial de um neurônio biológico foi fruto do trabalho de Warren McCulloch e Walter Pitts, em 1943. MacCulloch, psicólogo e neurofisiologista, dedicou sua carreira à tentativa de representar e modelar eventos do sistema nervoso. Pitts, um matemático recém-graduado, juntou-se a ele em 1942. No trabalho que publicaram em 1943 intitulado A Logical Calculus of the Ideas Immanent in Nervous Activity, eles apresentam uma discussão sofisticada de redes lógicas de neurônios artificiais que denominaram de neurônio MCP, devido a McCulloch e Pitts.

🧠 Redes neurais artificiais



O aprendizado de redes artificiais veio a ser objeto de estudo somente alguns anos depois do trabalho de McCulloch e Pitts. O primeiro trabalho foi proposto por Donald Hebb em 1949, em que ele propôs uma teoria para explicar o aprendizado em neurônios biológicos baseada no reforço das ligações sinápticas entre neurônios excitados. A regra de Hebb, como é conhecida a sua teoria na comunidade de RNAs, foi interpretada do ponto de vista matemático, e é hoje utilizada em vários algoritmos de aprendizado.


Em 1958, Rank Rosenblatt demonstrou, com seu novo modelo, o perceptron, que se fossem acescidas de sinapes ajustáveis as RNAs com neurônios MCP poderiam ser treinadas para classificar certos tipos de padrões. Rosenblatt descreveu uma topologia de RNA, estruturas de ligação entre os neurônios e, o mais importante, porpôs uma algoritmo para treinar a rede.

🧠 Redes neurais artificiais


Do ponto de vista matemático, no contexto de regressão, uma RNA trata-se de um estimador não linear de r({\bf x}) = \mathbb{E}(Y|{\bf X}) que pode ser representado por uma estrutura, que denominaremos de arquitetura da rede, como a da Figura que segue:


Figura 1: Exemplo de uma simples rede neural feedforward, com apenas uma camada oculta, aplicável à umm problema de regressão.

A Figura 1 ao lado é um exemplo representativo de uma RNA com apenas uma camada oculta. As “bolinhas” representam os neurônios, em que os verdes são denominados de “camada de entrada”. No exemplo, {\bf x} = (x_1, x_2, x_3) representam covariáveis de nossa base de dados 💾. Cada flecha (arresta) representa uma conexão e possui um peso que denotaremos por \beta, sendo estes os parâmetros de nossa rede neural que desejamos otimizar. A camada de saída representa a nossa estimativa de r({\bf x}), i.e., \widehat{r}({\bf x}).

🧠 Redes neurais artificiais


Cada nó (neurônio) de uma camada posterior, em uma rede feedforward, i.e., redes com topologias cujo os sinais são propragados para frente, são transformações dos sinais dos neurônios das camadas anteriores.


É importante deixar claro que existem diversas topologias de redes neurais, em que o número de camadas ocultas e o número de neurônios que irá compor cada uma das camadas é decidido por quem faz a análise. São hiperparâmetros que poderão ser investigados de modo a reduzir o risco preditivo R(g).


Você poderá visualizar a representação de diversas topologias/arquiteturas de RNAs clicando aqui. Esses são apenas exemplos de topologias de redes e você poderá em uma pesquisa rápida na internet encontrar diversas outras representações. Muitas delas já encontram-se implementadas em diversos frameworks de aprendizagem de máquina.


🧠 Redes neurais artificiais


Diversas arquiteturas de redes neurais já foram experimentadas. Por exemplo, algumas são úteis para problemas de classificação de imagens, outras possuem uma performance melhor para classificação de áudios, outras com boa performance em problemas de regressão, e assim por diante. Por exemplo, as RNAs feedforwards (redes de propagação direta), em suas diferentes topologias/arquiteturas, possuem boa performance em problemas de regressão, classificação, reconhecimento de padrões, entre outros. Sendo assim, é possível tratar uma gama muito ampla de problemas com redes neurais feedforwards. 🎉


Uma rede feedforward normalmente terá limitações quando se trata de capturar dependências temporais. Porém, em muitos problemas em que não há uma dependência temporal nos dados, as redes feedforwards poderá ser uma escolha conveniente.


🧠 Redes neurais artificiais


Abaixo, uma animação de uma rede neural com propagação direta - feedforward, em que os sinais percorrem em um único sentido. O fluxo do sinal, destacado em branco representa os neurônios excitados nas respectivas camadas, de acordo dado um vetor de entrada {\bf x}. Aqui, a camada de saída possui vários neurônios, sendo esta a representação de um problema de classificação. Se a camada de saída houversse apenas um neurônio, como na rede da Figura 1, este seria um problema de regressão.


🧠 Redes neurais artificiais


Considerando a rede apresentada na Figura 1, para uma dado vetor de entrada {\bf x} = (x_1, x_2, x_3), o sinal em um dado neurônio j da camada oculta é calculado como

\overbrace{x_j^1}^{\text{Neurônio j da camada 1}} := f\left(\beta_{0,j}^0 + \sum_{i = 1}^3 \beta_{i,j}^0 x_{i}^0\right), em que x_i^0 = x_i, para i = 1, 2, 3 e f é uma função definida pelo usuário, que denominaremos função de ativação. A função de ativação desempenha um papel crucial em um neurônio de uma RNA. Ela é responsável por introduzir não-linearidade nos cálculos realizados pelo neurônio, permitindo que a RNA seja capaz de aprender e representar relações complexas nos dados.


🧠 Redes neurais artificiais


Devido a possibilidade de adicionar diversas camadas ocultas, variar o número de neurônios em cada uma das camadas, e o mais importante, devido a introdução de funções de ativações diferentes em cada camada, que normalmente são funções não lineares, uma RNA consegue representar uma quantidade muito grande de funções não lineares. Além disso, a função de ativação desempenham um papel de controle no sinal de um neurônio. Algumas dessas funções podem ajudar na regularização da RNA, i.e., ajudam a reduzir a probabilidade de overfitting.


Ainda considerando a rede representada pela Figura 1, calculado os sinais dos neurônios x_j^1, para todo j = 1, \cdots, 5, pode-se calcular o sinal do neurônio da camada de saída. Temos então que


\overbrace{x_1^2}^{\text{Neurônio j da camada 2}} := f\left(\beta_{0,1}^1 + \sum_{i = 1}^5 \beta_{i,1}^1 x_{i}^1\right) =: g_\beta({\bf x}).

🧠 Redes neurais artificiais


A Figura abaixo representa o procedimento de atualização e passagem de sinal de um neurônio.


Multiplica-se as entradas x_i da camada l com os respectivos pesos \beta_i^l, \, i = 1, 2, 3, realiza-se a soma que é passada para a função de ativação que retornará a saída. O sinal x_1^{l +1} será transmitido para o neurônio 1 da camada l + 1 (camada seguinte). A saída x_1^{l + 1} é dada por f(\beta_{0,1}^l + \sum_{i=1}^3 \beta_{i,1}^lx_{i}^l).

🧠 Redes neurais artificiais


As escolhas comuns para função de ativação são:


  1. Identidade: f(x) = x;
  2. Lógica: f(x) = \frac{1}{1 - \mathrm{e}^{-x}};
  3. Tangente hiperbólica: f(x) = \frac{\mathrm{e}^x - \mathrm{e}^{-x}}{\mathrm{e}^x + \mathrm{e}^{-x}};
  4. ReLu (rectified linear): f(x) = \max\{0, x\};
  5. Leaky ReLu: f(x) = \begin{cases} x, & \text{se}\ x > 0 \\ \alpha x, & \text{caso contrário} \end{cases}


Na função Leaky ReLu, o valor de \alpha > 0, normalmente pequeno que costuma ser 0,01. Todas as funções acima possue domínio em (-\infty, +\infty).

🧠 Redes neurais artificiais


De um modo geral, uma RNA poderá possuir mais de uma camada oculta, conforme representado Figura que segue:


Arquitetura de uma RNA feedforward para um problema de regressão, i.e., com um único neurônio na camada de saída.

Com o avanço da capacidade computacional dos computadores atuais, é possível trabalhar com RNAs com diversas camadas ocultas, uma vez que os algoritmos de aprendizagem como é o caso do backpropagation que será discutido adiante pode ser executado com mais eficiência.

🧠 Redes neurais artificiais


Os parâmetros \beta_{0,j}^l, nas equações do sinal que será transmitido, que está em todos os neurônios de uma camada oculta é denominado de viés, ou, bias no inglês. O viés nos neurônios de uma RNA é crucial para permitir que a rede aprenda relações complexas e não-lineares nos dados, além de se adaptar a diferentes deslocamentos e tendências presentes nos dados do mundo real. Ele faz parte dos parâmetros treináveis da rede, que são ajustados durante o processo de treinamento para otimizar a capacidade da rede de fazer previsões ou classificações precisas. Sua função é aumentar ou diminuir a entrada líquida, de forma a transladar a função de ativação no eixo.


A propagação das observações das covariáveis por uma RNA é feita sequencialmente nessas camadas.


🧠 Redes neurais artificiais


Considere uma RNA feedforward com H camadas ocultas, com h = 1, \cdots, H, em que cada camada possui d_h neurônios. Seja ainda \beta_{i,j}^l o peso atribuído à conexão entre a i-ésima entrada na camada l e a j-ésima saída na camada l + 1, com l = 0, \cdots, H. Perceba que a camada de entrada é denotada por h = 0, já a camada de saída, denota-se por H + 1. O estimador de uma RNA para a função de regressão r({\bf x}) é dado por:


g_\beta({\bf x}) = \overbrace{x_1^{H + 1}}^{\text{So há um neurônio como saída}} = f\left(\beta_{0,1}^H + \sum_{i = 1}^{d_H}\beta_{i,1}^Hx_i^H\right), \tag{10} em que, para todo l = 1, \cdots, H e j = 1, \cdots, d_{l + 1},

x_{j}^{l + 1} = f\left(\beta_{0,j}^{l} + \sum_{i=1}^{d_l}\beta_{i,j}^{l}x_{i}^l\right).

🧠 Redes neurais artificiais


Se a RNA possui como função de ativação a função identidade f(x) = x e nenhuma camada oculta, a Equação 10 se reduz à uma regressão linear que estudamos anteriormente. 🎉



Nas RNAs, como já mencionado anteriormente, topologias mais complexas são admitidas, podendo retroalimentação, em que o sinal de uma camada posterior poderá para retroalimentar camadas anteriores. Mais detalhes aprofundados da teoria de RNAs podem ser obtidos Goodfellow, I., Bengio, Y. and Courville, A. Deep Learning. The MIT Press Cambridge, Massachusetts London, England. (2017). Clique aqui.

🧠 Redes neurais artificiais


Para estimar os coeficientes \beta_{i,j}^l da Equação 10, i.e., para estimarmos o peso da saída i da camada l que entra na entrada j da camada l + 1, é necessário que haja uma função objetivo para ser otimizada. Como aprendemos, em um problema de regressão, essa função objetivo é o EQM. Portanto, desejamos minimizar


\underbrace{EQM(g_\beta) = \frac{1}{n}\sum_{k = 1}^n (g_\beta(x_k) - y_k)^2}_{\text{Precisamos de uma combinação de pesos que minimize essa Equação.}}. \tag{11}


Como também já sabemos, para termos uma estimativa consistente de R(g_\beta), devemos avaliar a Equação 11 no conjunto de teste. Encontrar os pesos \beta’s que minimize a Equação 11 são tem solução analítica.

🧠 Redes neurais artificiais


Para minimizar a Equação 11, podemos fazer uso de métodos numéricos. O método que mais conhecido para essa tarefa é o backpropagation, que nada mais é do que um método de gradiente descendente executado em uma determinada ordem específica. Nesse método, o gradiente descendente é calculado de trás para frente, i.e., primeiro atualizam-se os coeficientes da última camada, depois se atualizam os coeficientes da camada anterior, e assim por diante. Por isso o o termo back é utilizado no algoritmo.


A busca por uma solução global ou mínima global em uma função de perda não é garantida pelo backpropagation. O algoritmo pode convergir para mínimos locais, em que a função de perda é relativamente baixa dentro de uma região, mas esses mínimos locais podem não ser os melhores em termos de desempenho geral da rede neural. Portanto, o algoritmo de backpropagation em si não é uma técnica de otimização global.


🧠 Redes neurais artificiais


De forma mais específica, em cada passo do algoritmo backpropagation o peso \beta_{i,j}^l é atualizado por


\beta_{i,j}^l \leftarrow \beta_{i,j}^l - \eta \frac{\partial EQM(g_\beta)}{\partial\beta_{i,j}^l}, \tag{12} com j = 1, \cdots, d_{l + 1} e i = 0, \cdots, d_l, em que l = H, \cdots, 0. Lembre-se que o gradiente de uma função aponta para a direção de maior crescimento de uma função. Portanto, o gradiente com sinal trocado irá apontar para a região de descida mais rápida, e é por isso que a atualização do peso é feita subtraindo do gradiente, conforme a Equação 12.


O valor \eta > 0 é denominado de taxa de aprendizado, e controla o tamanho do passo que o algoritmo de otimização dá em direção ao mínimo da função de perda. Valores altos para \eta pode levar a oscilações e a dificuldades para convergir, enquanto uma taxa de aprendizado muito baixa pode resultar em convergência lenta.

🧠 Redes neurais artificiais


Muitas variações do algoritmo backpropagation surgiram, como é o caso do gradiente descendente estocástico, que nada mais é que o algoritmo backpropagation em apenas uma parte do conjunto de treinamento, denominados de lotes ou mini-batches 📦. Esses lotes são sorteados aleatoriamente e de tamanhos aproximadamente iguais. É utilizado apenas um desses lotes por vez, durante o processo de atualização dos pesos da RNA. Esse de atualização aumenta a eficiência computacional, reduz o uso de memória, introduz estocasticidade que pode evitar mínimos locais indesejados permitindo, assim, uma exploração mais ampla do espaço de parâmetros.


Com o gradiente descendente estocástico, o problema de overffiting é menor. Essa metodologia realiza uma espécie de regularização da rede e possui boas propriedades que ainda não são muito bem entendidas.


🧠 Redes neurais artificiais


No conceito de gradiente descendente estocástico, a atualização dos pesos é realizada em cada um dos lotes 📦. Dizemos, portanto, que o treinamento ocorreu em uma época. O conceito de época (epoch) 🕒 é comum em RNA e consiste em realizar o treinamento com todos os batches. Normalmente, escolhemos treinar nossa rede usando várias épocas, i.e., em cada época novos lotes são sorteados, permitindo que a rede não aprenda padrões dependentes da ordem dos dados de treinamento, ajudando expor a rede a uma diversidade de exemplos, tornando o aprendizado mais robusto.


Uma forma muito comum de tentar regularizar uma RNA e diminuir o overfitting é realizar dropout, que consiste em sortear neurônios que não irão ser atualizados. Em palavras, consistem em “fingir” que não existem alguns neurônios no processo de atualização dos pesos. Também podemos introduzir uma penalização, assim como na regressão lasso.


🧠 Redes neurais artificiais


Experimente treinar uma rede neural em um problema de regressão, utilizando a aplicação abaixo. Clique aqui para visualizar a aplicação ampliada. 🎁