Lab03 - Aprendizado Supervisionado - Regressão
Objetivos¶
- Apresentar o conceito de Regressão
- Apresentar e utilizar algoritmo de Regressão linear
- Apresentar e utilizar Regressão Polinomial
- Apresentar e discutir a matriz de correlação
- Apresentar uma intuição sobre métricas de avaliação (MSE, RMSE e $ R² $ )
Começando¶
Sabemos que dentro de aprendizado supervisionado vamos trabalhar com dois tipos de problemas:
- Classificação - (Já conhecemos o KNN)
- Regressão - (Objetivo de hoje)
Uma intuição sobre problemas que envolvem cada um deles:¶
Classificação --> Resultados discretos (categóricos).
Regressão --> Resultados numéricos e contínuos.
Regressão linear¶
É uma técnica que consiste em representar um conjunto de dados por meio de uma reta.
Na matemática aprendemos que a equação de uma reta é:
$$ Y = A + BX \\ $$ A e B são constantes que determinam a posição e inclinação da reta. Para cada valor de X temos um Y associado.
Em machine learning aprendemos que uma Regressão linear é:
$$ Y_{predito} = \beta_o + \beta_1X \\ $$
$ \beta_o $ e $ \beta_1 $ são parâmetros que determinam o peso e bias da rede. Para cada entrada $ X $ temos um $ Y_{predito} $ aproximado predito.
Essa ideia se estende para mais de um parâmetro independente, mas nesse caso não estamos associando a uma reta e sim a um plano ou hiperplano:
$$ Y_{predito} = \beta_o + \beta_1X_1 + \beta_2X_2 + ... + \beta_nX_n\\ $$
Em outras palavras, modelos de regressão linear são intuitivos, fáceis de interpretar e se ajustam aos dados razoavelmente bem em muitos problemas.
Bora lá!!¶
Vamos juntos realizar um projeto, do começo ao fim, usando regressão.
Definição do problema¶
Vamos trabalhar com um dataset com informações coletadas U.S Census Service (tipo IBGE americano) sobre habitação na área de Boston Mass.
ref: https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html
informação importante sobre o significado de cada um dos atributos
Attribute Information:
- CRIM per capita crime rate by town
- ZN proportion of residential land zoned for lots over 25,000 sq.ft.
- INDUS proportion of non-retail business acres per town
- CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
- NOX nitric oxides concentration (parts per 10 million)
- RM average number of rooms per dwelling
- AGE proportion of owner-occupied units built prior to 1940
- DIS weighted distances to five Boston employment centres
- RAD index of accessibility to radial highways
- TAX full-value property-tax rate per $10,000
- PTRATIO pupil-teacher ratio by town
- B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
- LSTAT % lower status of the population
- MEDV Median value of owner-occupied homes in $1000's
Queremos desenvolver um modelo capaz de predizer o valor de um imovel em Boston.
Desafio 1¶
Do ponto de vista de machine learning, que problema é esse:
Aprendizado supervisionado, não-supervisionado ou aprendizado por reforço?
R:
Classificação, regressão ou clusterização?
R:
# Inicializção das bibliotecas
%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
O scikit-learn possui diversos dataset em seu banco de dados, um deles é o dataset que vamos utilizar hoje.
faça o import direto usando sklearn.datasets
caso queira, você pode fazer o downlod do dataset direto do site e importar em seu projeto.
from sklearn.datasets import load_boston
boston_dataset = load_boston()
#para conhecer o que foi importado do dataset
boston_dataset.keys()
/usr/local/lib/python3.8/dist-packages/sklearn/utils/deprecation.py:87: FutureWarning: Function load_boston is deprecated; `load_boston` is deprecated in 1.0 and will be removed in 1.2. The Boston housing prices dataset has an ethical problem. You can refer to the documentation of this function for further details. The scikit-learn maintainers therefore strongly discourage the use of this dataset unless the purpose of the code is to study and educate about ethical issues in data science and machine learning. In this special case, you can fetch the dataset from the original source:: import pandas as pd import numpy as np data_url = "http://lib.stat.cmu.edu/datasets/boston" raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None) data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]]) target = raw_df.values[1::2, 2] Alternative datasets include the California housing dataset (i.e. :func:`~sklearn.datasets.fetch_california_housing`) and the Ames housing dataset. You can load the datasets as follows:: from sklearn.datasets import fetch_california_housing housing = fetch_california_housing() for the California housing dataset and:: from sklearn.datasets import fetch_openml housing = fetch_openml(name="house_prices", as_frame=True) for the Ames housing dataset. warnings.warn(msg, category=FutureWarning)
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename', 'data_module'])
# vamos carregar no pandas apenas data com os dados e "feature_names" com os nomes dos atributos
df = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
df.head()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0.0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1.0 | 296.0 | 15.3 | 396.90 | 4.98 |
1 | 0.02731 | 0.0 | 7.07 | 0.0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2.0 | 242.0 | 17.8 | 396.90 | 9.14 |
2 | 0.02729 | 0.0 | 7.07 | 0.0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2.0 | 242.0 | 17.8 | 392.83 | 4.03 |
3 | 0.03237 | 0.0 | 2.18 | 0.0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3.0 | 222.0 | 18.7 | 394.63 | 2.94 |
4 | 0.06905 | 0.0 | 2.18 | 0.0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3.0 | 222.0 | 18.7 | 396.90 | 5.33 |
#vamos adicionar mais uma coluna ao nosso dataframe com o target (alvo que vamos fazer a predição)
df['MEDV'] = boston_dataset.target
df.head()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | MEDV | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0.0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1.0 | 296.0 | 15.3 | 396.90 | 4.98 | 24.0 |
1 | 0.02731 | 0.0 | 7.07 | 0.0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2.0 | 242.0 | 17.8 | 396.90 | 9.14 | 21.6 |
2 | 0.02729 | 0.0 | 7.07 | 0.0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2.0 | 242.0 | 17.8 | 392.83 | 4.03 | 34.7 |
3 | 0.03237 | 0.0 | 2.18 | 0.0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3.0 | 222.0 | 18.7 | 394.63 | 2.94 | 33.4 |
4 | 0.06905 | 0.0 | 2.18 | 0.0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3.0 | 222.0 | 18.7 | 396.90 | 5.33 | 36.2 |
Desafio 2¶
Use os metodos info() e describe() para exibir as informações do dataframe e responda:
Existe dados faltantes?
Qual o tamanho do dataset, quantas linhas e quantas colunas?
# Mostra informações sobre o dataframe em si
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 506 entries, 0 to 505 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 CRIM 506 non-null float64 1 ZN 506 non-null float64 2 INDUS 506 non-null float64 3 CHAS 506 non-null float64 4 NOX 506 non-null float64 5 RM 506 non-null float64 6 AGE 506 non-null float64 7 DIS 506 non-null float64 8 RAD 506 non-null float64 9 TAX 506 non-null float64 10 PTRATIO 506 non-null float64 11 B 506 non-null float64 12 LSTAT 506 non-null float64 13 MEDV 506 non-null float64 dtypes: float64(14) memory usage: 55.5 KB
df.describe()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | MEDV | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 |
mean | 3.613524 | 11.363636 | 11.136779 | 0.069170 | 0.554695 | 6.284634 | 68.574901 | 3.795043 | 9.549407 | 408.237154 | 18.455534 | 356.674032 | 12.653063 | 22.532806 |
std | 8.601545 | 23.322453 | 6.860353 | 0.253994 | 0.115878 | 0.702617 | 28.148861 | 2.105710 | 8.707259 | 168.537116 | 2.164946 | 91.294864 | 7.141062 | 9.197104 |
min | 0.006320 | 0.000000 | 0.460000 | 0.000000 | 0.385000 | 3.561000 | 2.900000 | 1.129600 | 1.000000 | 187.000000 | 12.600000 | 0.320000 | 1.730000 | 5.000000 |
25% | 0.082045 | 0.000000 | 5.190000 | 0.000000 | 0.449000 | 5.885500 | 45.025000 | 2.100175 | 4.000000 | 279.000000 | 17.400000 | 375.377500 | 6.950000 | 17.025000 |
50% | 0.256510 | 0.000000 | 9.690000 | 0.000000 | 0.538000 | 6.208500 | 77.500000 | 3.207450 | 5.000000 | 330.000000 | 19.050000 | 391.440000 | 11.360000 | 21.200000 |
75% | 3.677083 | 12.500000 | 18.100000 | 0.000000 | 0.624000 | 6.623500 | 94.075000 | 5.188425 | 24.000000 | 666.000000 | 20.200000 | 396.225000 | 16.955000 | 25.000000 |
max | 88.976200 | 100.000000 | 27.740000 | 1.000000 | 0.871000 | 8.780000 | 100.000000 | 12.126500 | 24.000000 | 711.000000 | 22.000000 | 396.900000 | 37.970000 | 50.000000 |
Desafio 3¶
Aplique os métodos que achar conveniente (vimos algumas opções na última aula) para visualizar os dados de forma gráfica.
## Sua resposta e seus gráficos para análisar..
#Vamos explorar um pouco uma matrix de correlação
import seaborn as sns
correlation_matrix = df.corr().round(2)
fig, ax = plt.subplots(figsize=(10,10))
sns.heatmap(data=correlation_matrix, annot=True, linewidths=.5, ax=ax)
<AxesSubplot:>
Desafio 4¶
Analisando a matriz de correlação acima responda:
Qual feature possue a maior correlação positiva com o target?
Qual feature possue a maior correlação negativa com o target?
df.plot.scatter('RM', 'MEDV')
<AxesSubplot:xlabel='RM', ylabel='MEDV'>
df.plot.scatter('LSTAT', 'MEDV')
<AxesSubplot:xlabel='LSTAT', ylabel='MEDV'>
PARE!!!¶
A análise feita no desafio 2 e 3 é uma das etapas mais importantes. Caso você tenha pulado essa etapa, volte e faça suas análises.
Com essa etapa concluída, vamos criar um sub-dataset com os atributos que serão utilizados.
# Vamos treinar nosso modelo com 2 dois atributos independentes
# para predizer o valor de saida
X = df[['LSTAT', 'RM']] ### teste com duas entradas
#X = df[['RM']] ### teste com uma entrada
#X = df.drop(['MEDV'], axis=1) ### teste com todas as entradas
Y = df['MEDV']
print(f"Formato das tabelas de dados {X.shape} e saidas {Y.shape}")
Formato das tabelas de dados (506, 2) e saidas (506,)
Dividindo os dados em conjunto de treinamento e de testes¶
Dividir nosso dataset em dois conjuntos de dados.
Treinamento - Representa 80% das amostras do conjunto de dados original,
Teste - com 20% das amostras
Vamos escolher aleatoriamente algumas amostras do conjunto original. Isto pode ser feito com Scikit-Learn usando a função train_test_split()
scikit-learn Caso ainda não tenha instalado, no terminal digite:
- pip install scikit-learn
# Separamos 20% para o teste
from sklearn.model_selection import train_test_split
X_treino, X_teste, Y_treino, Y_teste = train_test_split(X, Y, test_size=0.2)
print(X_treino.shape)
print(X_teste.shape)
print(Y_treino.shape)
print(Y_teste.shape)
(404, 2) (102, 2) (404,) (102,)
#Primeiras linhas do dataframe
X_treino.head()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
209 | 0.43571 | 0.0 | 10.59 | 1.0 | 0.489 | 5.344 | 100.0 | 3.8750 | 4.0 | 277.0 | 18.6 | 396.90 | 23.09 |
310 | 2.63548 | 0.0 | 9.90 | 0.0 | 0.544 | 4.973 | 37.8 | 2.5194 | 4.0 | 304.0 | 18.4 | 350.45 | 12.64 |
360 | 4.54192 | 0.0 | 18.10 | 0.0 | 0.770 | 6.398 | 88.0 | 2.5182 | 24.0 | 666.0 | 20.2 | 374.56 | 7.79 |
79 | 0.08387 | 0.0 | 12.83 | 0.0 | 0.437 | 5.874 | 36.6 | 4.5026 | 5.0 | 398.0 | 18.7 | 396.06 | 9.10 |
291 | 0.07886 | 80.0 | 4.95 | 0.0 | 0.411 | 7.148 | 27.7 | 5.1167 | 4.0 | 245.0 | 19.2 | 396.90 | 3.56 |
Y_treino.head()
209 20.0 310 16.1 360 25.0 79 20.3 291 37.3 Name: MEDV, dtype: float64
Chegou a hora de aplicar o modelo preditivo¶
Treinar um modelo no python é simples se usar o Scikit-Learn. Treinar um modelo no Scikit-Learn é simples: basta criar o regressor, e chamar o método fit().
Uma observação sobre a sintaxe dos classificadores do scikit-learn
- O método
fit(X,Y)
recebe uma matriz ou dataframe X onde cada linha é uma amostra de aprendizado, e um array Y contendo as saídas esperadas do classificador, seja na forma de texto ou de inteiros - O método
predict(X)
recebe uma matriz ou dataframe X onde cada linha é uma amostra de teste, retornando um array de classes
# Importa a biblioteca
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# Cria o modelo de regressão
lin_model = LinearRegression()
# Cria o modelo de machine learning
lin_model.fit(X_treino, Y_treino)
LinearRegression()
Pronto!! bora testar se esta funcionando....
# Para obter as previsões, basta chamar o método predict()
y_teste_predito = lin_model.predict(X_teste)
print("Predição usando regressão, retorna valores continuos: {}".format(y_teste_predito))
Predição usando regressão, retorna valores continuos: [27.78116886 16.26183507 23.44015871 18.3282106 24.078629 23.05830511 17.20596108 26.55333901 27.29465033 12.08763269 21.03585378 21.83829692 28.32018637 24.77547133 30.89952717 29.1843249 20.65194401 26.20774337 20.89373715 30.24761832 37.55250921 19.26971857 28.14794273 24.12901374 29.39194699 21.30136803 19.05738983 20.33195147 31.28902138 18.7942289 21.75066712 19.46186472 17.07638469 22.94060637 16.72256778 29.9768014 27.27470683 20.46317244 21.65543256 20.35973369 21.46822532 12.96054581 11.31023591 17.62605536 21.66027072 29.63635815 17.04413491 22.30658657 21.44084101 33.38456927 28.12945767 36.51060065 28.13081707 20.55514342 16.33219577 27.24120865 9.26654829 31.49250893 18.067707 0.40299741 17.11051987 14.37542579 18.11218451 21.49691096 17.48697175 12.74074381 19.45037134 22.93711054 28.29503071 19.49300104 18.94201196 26.85663797 27.535026 38.85542517 29.17836814 19.5983952 36.91921008 30.82287671 25.19344284 29.18924673 17.47726129 20.84599435 24.35857743 28.79479823 23.35919952 35.72446343 20.11455061 6.34266384 19.20491842 36.88603298 15.62950427 31.31179809 5.61533587 24.7371333 11.0359966 19.61117113 25.80495918 21.48348778 16.89840631 31.14103851 19.61183545 28.99411878]
# vamos avaliar os parametros do nosso modelo
print('(A) Intercepto: ', lin_model.intercept_)
print('(B) Inclinação: ', lin_model.coef_)
if len(lin_model.coef_)>1:
print('Nossa equação é: Y_pred = {} + {} * X_LSTAT + {} * X_RM'.format(lin_model.intercept_.round(2),lin_model.coef_[0].round(2),lin_model.coef_[1].round(2)) )
else:
print('Nossa equação é: Y_pred = {} + {} * X_LSTAT'.format(lin_model.intercept_.round(2),lin_model.coef_[0].round(2)))
(A) Intercepto: 2.4355315463718874 (B) Inclinação: [-0.67611155 4.58198354] Nossa equação é: Y_pred = 2.44 + -0.68 * X_LSTAT + 4.58 * X_RM
plt.scatter(Y_teste,y_teste_predito)
plt.xlabel('Valor Real')
plt.ylabel('Valor Predito')
Text(0, 0.5, 'Valor Predito')
Avaliando o modelo treinado¶
Vamos colocar alguns valores e ver a predição do classificador.
from sklearn.metrics import r2_score, mean_squared_error,mean_absolute_error
import numpy as np
print("Soma dos Erros ao Quadrado (SSE): %2.f " % np.sum((y_teste_predito - Y_teste)**2))
print("Erro Quadrático Médio (MSE): %.2f" % mean_squared_error(Y_teste, y_teste_predito))
print("Erro Médio Absoluto (MAE): %.2f" % mean_absolute_error(Y_teste, y_teste_predito))
print ("Raiz do Erro Quadrático Médio (RMSE): %.2f " % np.sqrt(mean_squared_error(Y_teste, y_teste_predito)))
print("R2-score: %.2f" % r2_score(y_teste_predito , Y_teste) )
Soma dos Erros ao Quadrado (SSE): 2948 Erro Quadrático Médio (MSE): 28.90 Erro Médio Absoluto (MAE): 4.05 Raiz do Erro Quadrático Médio (RMSE): 5.38 R2-score: 0.42
Desafio 5¶
Refaça o notebook substituindo o algoritmo de regressão linear por outro algoritmo de regressão e compare os resultados obtidos.
Sugestão de alguns algoritmos de ML para problemas de regressão:
Nome | Vantagem | Desvantagem | Exemplo sklearn |
---|---|---|---|
Regressão Linear | Fácil de entender e implementar | Pode não ser adequado para problemas mais complexos | from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(X, y) prediction = model.predict([X_teste]) |
Árvores de decisão | Fácil de entender e visualizar | Pode levar a overfitting se a árvore for muito grande | from sklearn.tree import DecisionTreeRegressor model = DecisionTreeRegressor() model.fit(X, y) prediction = model.predict([X_teste]) |
Random Forest | Mais robusto e geralmente mais preciso do que uma única árvore de decisão | Pode ser mais lento e mais difícil de ajustar | from sklearn.ensemble import RandomForestRegressor model = RandomForestRegressor(n_estimators=100) model.fit(X, y) prediction = model.predict([X_teste]) |
Support Vector Regression (SVR) | Lida bem com dados multidimensionais e não lineares | Pode ser difícil de escolher o kernel correto e ajustar os hiperparâmetros | from sklearn.svm import SVR model = SVR(kernel='rbf') model.fit(X, y) prediction = model.predict([X_teste]) |
Gradient Boosting | Preciso e lida bem com dados multidimensionais e não lineares | Pode ser mais lento e mais difícil de ajustar | from sklearn.ensemble import GradientBoostingRegressor model = GradientBoostingRegressor(n_estimators=100) model.fit(X, y) prediction = model.predict([X_teste]) |
## implemente sua sua solução....
Regressão Polinomial¶
$$ Y = A + BX + C X² \\ $$ A, B e C são constantes que determinam a posição e inclinação da curva, o 2 indica o grau do polinômio. Para cada valor de X temos um Y associado.
Em machine learning aprendemos que uma Regressão Polinomial é:
$$ Y_{predito} = \beta_o + \beta_1X + \beta_2X² \\ $$
$ \beta_o $ , $ \beta_1 $ e $ \beta_2 $ são parâmetros que determinam o peso da rede. Para cada entrada $ X $ temos um $ Y_{predito} $ aproximado predito.
Essa ideia se estende para polinômio de graus maiores:
$$ Y_{predito} = \beta_o + \beta_1X + \beta_2X² + ... + \beta_nX^n\\ $$
import operator
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# importa feature polinomial
from sklearn.preprocessing import PolynomialFeatures
#####----------- vou gerar alguns numeros aleatórios ------------------
#gerando numeros aleatorios, apenas para este exemplo
np.random.seed(42)
x = 2 - 3 * np.random.normal(0, 1, 30)
y = x - 3 * (x ** 2) + 0.8 * (x ** 3)+ 0.2 * (x ** 4) + np.random.normal(-20, 20, 30)
# ajuste nos dados, pois estamos trabalhando com a numpy
x = x[:, np.newaxis]
y = y[:, np.newaxis]
####---------------pronto já temos os dados para treinar -------------
#----É aqui que o seu código muda ------------------------------------
# Chama a função definindo o grau do polinomio e aplica o modelo
grau_poly = 1
polynomial_features= PolynomialFeatures(degree = grau_poly)
x_poly = polynomial_features.fit_transform(x)
#----Pronto agora é tudo como era antes, com regressão linear
model = LinearRegression()
model.fit(x_poly, y)
y_poly_pred = model.predict(x_poly)
# Métrica de avaliação do modelo
print("Soma dos Erros ao Quadrado (SSE): %2.f " % np.sum((y_poly_pred - y)**2))
print("Erro Quadrático Médio (MSE): %.2f" % mean_squared_error(y,y_poly_pred))
print("Erro Médio Absoluto (MAE): %.2f" % mean_absolute_error(y, y_poly_pred))
print ("Raiz do Erro Quadrático Médio (RMSE): %.2f " % np.sqrt(mean_squared_error(y, y_poly_pred)))
print("R2-score: %.2f" % r2_score(y,y_poly_pred) )
plt.scatter(x, y, s=10)
# ordena os valores de x antes de plotar
sort_axis = operator.itemgetter(0)
sorted_zip = sorted(zip(x,y_poly_pred), key=sort_axis)
x, y_poly_pred = zip(*sorted_zip)
plt.plot(x, y_poly_pred, color='r')
plt.show()
Soma dos Erros ao Quadrado (SSE): 602124 Erro Quadrático Médio (MSE): 20070.81 Erro Médio Absoluto (MAE): 104.66 Raiz do Erro Quadrático Médio (RMSE): 141.67 R2-score: 0.55
Desafio 6¶
Faça uma função que calcula a regressão polinomial (basicamente colocar o codigo acima em uma função), agora faça um código que chama essa função alterando o grau do polinomio de 2 até 10, basicamente um loop for que chama a função criada.
Análise os resultados obtidos e determine qual o melhor grau polinomio do seu modelo.
## Implemente sua solução