Aula 13: Árvores de Regressão

1 Árvore de regressão

Quando usamos uma árvore de decisão para prever um valor contínuo, estamos trabalhando com uma árvore de regressão.

A construção da árvore de regressão segue a mesma lógica e procedimento que para árvores de classificação, exceto que a impureza dos nós é medida pelos desvios quadráticos da média (erros quadráticos) em cada divisão, e o desempenho preditivo é avaliado pelo RMSE.

Em uma árvore de classificação, a classe de cada nó folha é atribuida pela classe mais frequente de todas as observações naquele nó. Em uma árvore de regressão, a resposta de um nó \(a\), precisa ser um valor constante.

2 Atribuindo o valor do nó folha

Seja \(\mathcal{L} = \{(\mathbf{x}_i,y_i): \mathbf{x}_i \in \mathbb{R}^p, y_i \in \mathbb{R}, i= 1, \ldots, n\}\) o conjunto de dados de treino. Para uma árvore \(T\), denotamos por \(\widetilde{T} = \{a_1, \ldots, a_L\}\) o conjunto de seus nós folhas.

Definimos a impureza do nó \(a\) por

\[I(a) = \sum_{i: \mathbf{x}_i \in a}(y_i - \hat{y}_i(a))^2,\] em que \(y_i\) é a resposta da i-ésima unidade de investigação pertencente ao nó \(a\) e \(\hat{y}_i(a)\) é o valor que minimiza o erro de resubstituição: \[R(T) = \frac{1}{n}\sum_{a \in \widetilde{T}} \sum_{i: \mathbf{x}_i \in a} (y_i - \hat{y}_i(a))^2 = \sum_{l=1}^L R(a_l),\] em que \[R(a_l) = \frac{1}{n}\sum_{i: \mathbf{x}_i \in a} (y_i - \hat{y}_i(a))^2 = p(a_l)s^2(a_l),\] \(\displaystyle s^2(a_l) = \frac{1}{n(a_l)}\sum_{i:\mathbf{x}_i \in a_l} (y_i - \hat{y}_i(a_l))^2\) e \(p(a_l) = n(a_l)/n\).

Portanto, \[\hat{y}_i(a) = \frac{1}{n(a)}\sum_{i:\mathbf{x}_i \in a} y_i,\] ou seja, é a média das observações de \(y\) que pertencem ao nó \(a\).

3 Estratégia de divisão

Como antes, para cada variável de entrada \(X_j\) escolhemos aquela divisão \(d\) do nó pai \(a_p\) em seus dois nós filhos \(a_e\) e \(a_d\), que tenha o maior ganho de informação (ou seja, a maior redução no erro de resubstituição): \[\begin{align}IG(a_p,d) = \Delta(a_p,d) &= I(a_p) - \frac{n(e)}{n(p)}I(a_e)-\frac{n(d)}{n(p)}I(a_d)\\ &=R(a_p) - R(a_e)-R(a_d),\end{align}\] em que \(n(p), n(e)\) e \(n(d)\), são o número de observações no nó pai, no nó filho esquerdo e o nó filho direito, respectivamente.

4 Poda da árvore

Como antes, primeiro fazemos crescer uma árvore muito grande, \(T_\max\), dividindo os nós repetidamente até que cada nó contenha menos que um número mínimo de observações \(n_\min\), isto é, \(n(a) \leq n_\min\), para cada nó folha \(a \in \widetilde T\), tipicamente, fixamos \(n_\min = 5\).

Logo, calculamos a medida do custo de complexidade por \[R_\alpha(T) = R(T) + \alpha|\widetilde{T}|,\]

em que \(\alpha \geq 0\) é um parâmetro de complexidade. E usamos \(R_\alpha(T)\) na mesma maneira que foi usado para fazer a poda de uma árvore de classificação.

5 Prevendo o salário dos jogadores da Major League Baseball

O conjunto de dados consiste em informações sobre jogadores da Major League Baseball. A variável de resposta são seus salários de 1992 (medidos em milhares de dólares e obtidos do New York Times de 19 de novembro de 1992). Possíveis variáveis explicativas incluem diversas medidas do desempenho dos jogadores em 1991. Esses dados foram obtidos do Sacramento Bee de 15 de outubro de 1991. Mais detalhes podem ser encontrados em Watnik,1998.

Obs. Os alunos que não estão familiarizados com beisebol podem ser informados de que, com exceção de strike-outs e erros, todas essas variáveis seriam sensatamente correlacionadas positivamente com o salário.

# Carregando os dados
mlb <- read.csv("mlb.txt")
# Removendo os nomes dos jogadores
mlb <- mlb[,-18]
# Renomeando as vars indicadoras
names(mlb)[c(14,15,16,17)] <- c("FAE","FA","AE","A") 
library(rpart)
library(rpart.plot)
# Ajustando a árvore 
set.seed(12345)
mlb_rpart <- rpart::rpart(Salary ~ ., 
                               data = mlb,
                               method = "anova", 
                               xval = 30, 
                               cp = 0.00011)
rpart.plot(mlb_rpart)

A continuação, apresentamos os resultados da validação cruzada para a escolha do parâmetro de complexidade:

rsq.rpart(mlb_rpart)
## 
## Regression tree:
## rpart::rpart(formula = Salary ~ ., data = mlb, method = "anova", 
##     xval = 30, cp = 0.00011)
## 
## Variables actually used in tree construction:
##  [1] AE      Doubles FA      FAE     Hits    HR      OBP     RBI     Runs   
## [10] SB      SO     
## 
## Root node error: 516644690/337 = 1533070
## 
## n= 337 
## 
##            CP nsplit rel error  xerror     xstd
## 1  0.33946196      0   1.00000 1.00863 0.089271
## 2  0.14117583      1   0.66054 0.77075 0.075663
## 3  0.08888501      2   0.51936 0.65380 0.065270
## 4  0.05133621      3   0.43048 0.50514 0.057393
## 5  0.04766668      4   0.37914 0.50009 0.057105
## 6  0.03075163      5   0.33147 0.41532 0.046408
## 7  0.02166442      6   0.30072 0.40437 0.045434
## 8  0.02095516      7   0.27906 0.39333 0.044282
## 9  0.01992146      8   0.25810 0.38403 0.044419
## 10 0.01520262      9   0.23818 0.34130 0.045321
## 11 0.00858541     10   0.22298 0.32188 0.044154
## 12 0.00474248     11   0.21439 0.34416 0.046773
## 13 0.00440260     12   0.20965 0.36711 0.047633
## 14 0.00439663     13   0.20525 0.36782 0.047623
## 15 0.00349045     14   0.20085 0.36198 0.047531
## 16 0.00253731     15   0.19736 0.36898 0.048096
## 17 0.00156020     16   0.19482 0.37147 0.048106
## 18 0.00085416     17   0.19326 0.37060 0.048103
## 19 0.00063415     18   0.19241 0.37311 0.048066
## 20 0.00061661     20   0.19114 0.37265 0.047978
## 21 0.00038316     21   0.19052 0.37302 0.047972
## 22 0.00030215     22   0.19014 0.37368 0.047781
## 23 0.00021399     23   0.18984 0.37411 0.047774
## 24 0.00018508     24   0.18963 0.37412 0.047773
## 25 0.00011000     25   0.18944 0.37424 0.047771

Pelo observado nos gráficos acima a árvore de regressão podada para os dados da mlb, o menor valor do erro relativo, obtido por VC, ocorre para uma árvore com 10 divisões e 11 nós folhas.

cpmin <- mlb_rpart$cptable[which.min(mlb_rpart$cptable[,"xerror"])]
mlb_poda <- prune(mlb_rpart, cp = cpmin)
rpart.plot(mlb_poda, clip.right.labs = TRUE, under = FALSE, extra = 101, type = 4)

printcp(mlb_poda)
## 
## Regression tree:
## rpart::rpart(formula = Salary ~ ., data = mlb, method = "anova", 
##     xval = 30, cp = 0.00011)
## 
## Variables actually used in tree construction:
## [1] AE   FAE  RBI  Runs SB  
## 
## Root node error: 516644690/337 = 1533070
## 
## n= 337 
## 
##           CP nsplit rel error  xerror     xstd
## 1  0.3394620      0   1.00000 1.00863 0.089271
## 2  0.1411758      1   0.66054 0.77075 0.075663
## 3  0.0888850      2   0.51936 0.65380 0.065270
## 4  0.0513362      3   0.43048 0.50514 0.057393
## 5  0.0476667      4   0.37914 0.50009 0.057105
## 6  0.0307516      5   0.33147 0.41532 0.046408
## 7  0.0216644      6   0.30072 0.40437 0.045434
## 8  0.0209552      7   0.27906 0.39333 0.044282
## 9  0.0199215      8   0.25810 0.38403 0.044419
## 10 0.0152026      9   0.23818 0.34130 0.045321
## 11 0.0085854     10   0.22298 0.32188 0.044154

O erro de resubstituição da árvore de regressão, após a poda, é \(R(T) = 1533070 \times 0,22298 = \$341843,9\). Uma estimativa mais realista pode ser obtido pela VC e é igual \(R^{VC}(T) = 1533070 \times 0.32188 =\$493464,6\), com um desvio padrão de \(DP^{VC}(T)=1533070 \times 0.044154 = \$67691,17\).

6 Comentários finais

  • Em geral, árvores com muitos nós folhas apresentam bom desempenho no conjunto de treino, mas um desempenho pobre nos dados de teste, o que siginifica que podem estar associadas a sobreajuste.

  • Árvores com um número menor de subdivisões oferecem resultados com menor variância e mais interpretatibilidade.

  • A poda pode ser feita durante o treinamento (construção da árvore), isto é uma pré-poda, por exemplo, fixando um número mínimo de nós que deve conter cada nó folha.

  • A poda propriamente dita consiste em construir uma árvore muito grande e logo aplicamos uma regra de eliminação de alguns dos nós folhas. Neste caso, usamos o parâmetro de complexidade para controlar o tamanho da árvore.