<jupyter_start><jupyter_text>Nanodegree Engenheiro de Machine Learning Aprendizado Supervisionado Projeto: Encontrando doadores para a *CharityML* Seja bem-vindo ao segundo projeto do Nanodegree Engenheiro de Machine Learning! Neste notebook, você receberá alguns códigos de exemplo e será seu trabalho implementar as funcionalidades adicionais necessárias para a conclusão do projeto. As seções cujo cabeçalho começa com **'Implementação'** indicam que o bloco de código posterior requer funcionalidades adicionais que você deve desenvolver. Para cada parte do projeto serão fornecidas instruções e as diretrizes da implementação estarão marcadas no bloco de código com uma expressão `'TODO'`. Por favor, leia cuidadosamente as instruções!Além de implementações de código, você terá de responder questões relacionadas ao projeto e à sua implementação. Cada seção onde você responderá uma questão terá um cabeçalho com o termo **'Questão X'**. Leia com atenção as questões e forneça respostas completas nas caixas de texto que começam com o termo **'Resposta:'**. A submissão do seu projeto será avaliada baseada nas suas resostas para cada uma das questões além das implementações que você disponibilizar.>**Nota:** Por favor, especifique QUAL A VERSÃO DO PYTHON utilizada por você para a submissão deste notebook. As células "Code" e "Markdown" podem ser executadas utilizando o atalho do teclado **Shift + Enter**. Além disso, as células "Markdown" podem ser editadas clicando-se duas vezes na célula.  IniciandoNeste projeto, você utilizará diversos algoritmos de aprendizado supervisionado para modelar com precisão a remuneração de indivíduos utilizando dados coletados no censo americano de 1994. Você escolherá o algoritmo mais adequado através dos resultados preliminares e irá otimizá-lo para modelagem dos dados. O seu objetivo com esta implementação é construir um modelo que pode predizer com precisão se um indivíduo possui uma remuneração superior a $50,000. Este tipo de tarefa pode surgir em organizações sem fins lucrativos que sobrevivem de doações. Entender a remuneração de um indivíduo pode ajudar a organização o montante mais adequado para uma solicitação de doação, ou ainda se eles realmente deveriam entrar em contato com a pessoa. Enquanto pode ser uma tarefa difícil determinar a faixa de renda de uma pesssoa de maneira direta, nós podemos inferir estes valores através de outros recursos disponíveis publicamente. O conjunto de dados para este projeto se origina do [Repositório de Machine Learning UCI](https://archive.ics.uci.edu/ml/datasets/Census+Income) e foi cedido por <NAME> e <NAME>, após a sua publicação no artigo _"Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid"_. Você pode encontrar o artigo de <NAME> [online](https://www.aaai.org/Papers/KDD/1996/KDD96-033.pdf). Os dados que investigaremos aqui possuem algumas pequenas modificações se comparados com os dados originais, como por exemplo a remoção da funcionalidade `'fnlwgt'` e a remoção de registros inconsistentes. ---- Explorando os dadosExecute a célula de código abaixo para carregas as bibliotecas Python necessárias e carregas os dados do censo. Perceba que a última coluna deste conjunto de dados, `'income'`, será o rótulo do nosso alvo (se um indivíduo possui remuneração igual ou maior do que $50,000 anualmente). Todas as outras colunas são dados de cada indívduo na base de dados do censo.<jupyter_code># Importe as bibliotecas necessárias para o projeto.
import numpy as np
import pandas as pd
from time import time
from IPython.display import display # Permite a utilização da função display() para DataFrames.
import warnings
warnings.filterwarnings('ignore')

# Importação da biblioteca de visualização visuals.py
import visuals as vs

# Exibição amigável para notebooks
%matplotlib inline

# Carregando os dados do Censo
data = pd.read_csv("census.csv")

# Sucesso - Exibindo o primeiro registro
display(data.head(n=1))<jupyter_output><empty_output><jupyter_text>Implementação: Explorando os DadosUma investigação superficial da massa de dados determinará quantos indivíduos se enquadram em cada grupo e nos dirá sobre o percentual destes indivúdos com remuneração anual superior à \$50,000. No código abaixo, você precisará calcular o seguinte:- O número total de registros, `'n_records'`- O número de indivíduos com remuneração anual superior à \$50,000, `'n_greater_50k'`.- O número de indivíduos com remuneração anual até \$50,000, `'n_at_most_50k'`.- O percentual de indivíduos com remuneração anual superior à \$50,000, `'greater_percent'`.** DICA: ** Você pode precisar olhar a tabela acima para entender como os registros da coluna `'income'` estão formatados.<jupyter_code># TODO: Número total de registros.
n_records = data.shape[0]

# TODO: Número de registros com remuneração anual superior à $50,000
n_greater_50k = data['income'].value_counts()['>50K']

# TODO: O número de registros com remuneração anual até $50,000
n_at_most_50k = data['income'].value_counts()['<=50K']

# TODO: O percentual de indivíduos com remuneração anual superior à $50,000
greater_percent = n_greater_50k / n_records * 100

# Exibindo os resultados
print("Total number of records: {}".format(n_records))
print("Individuals making more than $50,000: {}".format(n_greater_50k))
print("Individuals making at most $50,000: {}".format(n_at_most_50k))
print("Percentage of individuals making more than $50,000: {:.2f}%".format(greater_percent))<jupyter_output>Total number of records: 45222
Individuals making more than $50,000: 11208
Individuals making at most $50,000: 34014
Percentage of individuals making more than $50,000: 24.78%<jupyter_text>**Explorando as colunas*** **age**: contínuo. * **workclass**: Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked. * **education**: Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool. * **education-num**: contínuo. * **marital-status**: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse. * **occupation**: Tech-support, Craft-repair, Other-service, Sales, Exec-managerial, Prof-specialty, Handlers-cleaners, Machine-op-inspct, Adm-clerical, Farming-fishing, Transport-moving, Priv-house-serv, Protective-serv, Armed-Forces. * **relationship**: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried. * **race**: Black, White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other. * **sex**: Female, Male. * **capital-gain**: contínuo. * **capital-loss**: contínuo. * **hours-per-week**: contínuo. * **native-country**: United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, Greece, South, China, Cuba, Iran, Honduras, Philippines, Italy, Poland, Jamaica, Vietnam, Mexico, Portugal, Ireland, France, Dominican-Republic, Laos, Ecuador, Taiwan, Haiti, Columbia, Hungary, Guatemala, Nicaragua, Scotland, Thailand, Yugoslavia, El-Salvador, Trinadad&Tobago, Peru, Hong, Holand-Netherlands. ---- Preparando os dadosAntes de que os dados possam ser utilizados como input para algoritmos de machine learning, muitas vezes eles precisam ser tratados, formatados e reestruturados — este processo é conhecido como **pré-processamento**. Felizmente neste conjunto de dados não existem registros inconsistentes para tratamento, porém algumas colunas precisam ser ajustadas. Este pré-processamento pode ajudar muito com o resultado e poder de predição de quase todos os algoritmos de aprendizado.  Transformando os principais desvios das colunas contínuasUm conjunto de dados pode conter ao menos uma coluna onde os valores tendem a se próximar para um único número, mas também podem conter registros com o mesmo atributo contendo um valor muito maior ou muito menor do que esta tendência. Algoritmos podem ser sensíveis para estes casos de distribuição de valores e este fator pode prejudicar sua performance se a distribuição não estiver normalizada de maneira adequada. Com o conjunto de dados do censo, dois atributos se encaixam nesta descrição: '`capital-gain'` e `'capital-loss'`.Execute o código da célula abaixo para plotar um histograma destes dois atributos. Repare na distribuição destes valores.<jupyter_code># Dividindo os dados entre features e coluna alvo
income_raw = data['income']
features_raw = data.drop('income', axis = 1)

# Visualizando os principais desvios das colunas contínuas entre os dados
vs.distribution(data)<jupyter_output><empty_output><jupyter_text>Para atributos com distribuição muito distorcida, tais como `'capital-gain'` e `'capital-loss'`, é uma prática comum aplicar uma transformação logarítmica nos dados para que os valores muito grandes e muito pequenos não afetem a performance do algoritmo de aprendizado. Usar a transformação logarítmica reduz significativamente os limites dos valores afetados pelos outliers (valores muito grandes ou muito pequenos). Deve-se tomar cuidado ao aplicar esta transformação, pois o logaritmo de `0` é indefinido, portanto temos que incrementar os valores em uma pequena quantia acima de `0` para aplicar o logaritmo adequadamente.Execute o código da célula abaixo para realizar a transformação nos dados e visualizar os resultados. De novo, note os valores limite e como os valores estão distribuídos.<jupyter_code># Aplicando a transformação de log nos registros distorcidos.
skewed = ['capital-gain', 'capital-loss']
features_log_transformed = pd.DataFrame(data = features_raw)
features_log_transformed[skewed] = features_raw[skewed].apply(lambda x: np.log(x + 1))

# Visualizando as novas distribuições após a transformação.
vs.distribution(features_log_transformed, transformed = True)<jupyter_output><empty_output><jupyter_text>Normalizando atributos numéricosAlém das transformações em atributos distorcidos, é uma boa prática comum realizar algum tipo de adaptação de escala nos atributos numéricos. Ajustar a escala nos dados não modifica o formato da distribuição de cada coluna (tais como `'capital-gain'` ou `'capital-loss'` acima); no entanto, a normalização garante que cada atributo será tratado com o mesmo peso durante a aplicação de aprendizado supervisionado. Note que uma vez aplicada a escala, a observação dos dados não terá o significado original, como exemplificado abaixo.Execute o código da célula abaixo para normalizar cada atributo numérico, nós usaremos para isso a [`sklearn.preprocessing.MinMaxScaler`](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html).<jupyter_code># Importando sklearn.preprocessing.StandardScaler
from sklearn.preprocessing import MinMaxScaler

# Inicializando um aplicador de escala e aplicando em seguida aos atributos
scaler = MinMaxScaler() # default=(0, 1)
numerical = ['age', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']

features_log_minmax_transform = pd.DataFrame(data = features_log_transformed)
features_log_minmax_transform[numerical] = scaler.fit_transform(features_log_transformed[numerical])

# Exibindo um exemplo de registro com a escala aplicada
display(features_log_minmax_transform.head(n=5))<jupyter_output><empty_output><jupyter_text>Implementação: Pré-processamento dos dadosA partir da tabela em **Explorando os dados** acima, nós podemos observar que existem diversos atributos não-numéricos para cada registro. Usualmente, algoritmos de aprendizado esperam que os inputs sejam numéricos, o que requer que os atributos não numéricos (chamados de *variáveis de categoria*) sejam convertidos. Uma maneira popular de converter as variáveis de categoria é utilizar a estratégia **one-hot encoding**. Esta estratégia cria uma variável para cada categoria possível de cada atributo não numérico. Por exemplo, assuma que `algumAtributo` possuí três valores possíveis: `A`, `B`, ou `C`. Nós então transformamos este atributo em três novos atributos: `algumAtributo_A`, `algumAtributo_B` e `algumAtributo_C`.|   | algumAtributo |                    | algumAtributo_A | algumAtributo_B | algumAtributo_C || :-: | :-: |                            | :-: | :-: | :-: || 0 |  B  |  | 0 | 1 | 0 || 1 |  C  | ----> one-hot encode ----> | 0 | 0 | 1 || 2 |  A  |  | 1 | 0 | 0 |Além disso, assim como os atributos não-numéricos, precisaremos converter a coluna alvo não-numérica, `'income'`, para valores numéricos para que o algoritmo de aprendizado funcione. Uma vez que só existem duas categorias possíveis para esta coluna ("50K"), nós podemos evitar a utilização do one-hot encoding e simplesmente transformar estas duas categorias para `0` e `1`, respectivamente. No trecho de código abaixo, você precisará implementar o seguinte: - Utilizar [`pandas.get_dummies()`](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.get_dummies.html?highlight=get_dummiespandas.get_dummies) para realizar o one-hot encoding nos dados da `'features_log_minmax_transform'`. - Converter a coluna alvo `'income_raw'` para re.   - Transforme os registros com "50K" para `1`.<jupyter_code># TODO: Utilize o one-hot encoding nos dados em 'features_log_minmax_transform' utilizando pandas.get_dummies()
features_final = pd.get_dummies(features_log_minmax_transform)

# TODO: Faça o encode da coluna 'income_raw' para valores numéricos
income = income_raw.replace("<=50K", 0).replace(">50K", 1)
print("{} income registers '<=50K' converted to 0.".format(income.value_counts()[0]))
print("{} income registers '>50K' converted to 1.".format(income.value_counts()[1]))

# Exiba o número de colunas depois do one-hot encoding
encoded = list(features_final.columns)
print("{} total features after one-hot encoding.".format(len(encoded)))

# Descomente a linha abaixo para ver as colunas após o encode
# print(encoded)<jupyter_output>34014 income registers '<=50K' converted to 0.
11208 income registers '>50K' converted to 1.
103 total features after one-hot encoding.<jupyter_text>Embaralhar e dividir os dadosAgora todas as _variáveis de categoria_ foram convertidas em atributos numéricos e todos os atributos numéricos foram normalizados. Como sempre, nós agora dividiremos os dados entre conjuntos de treinamento e de teste. 80% dos dados serão utilizados para treinamento e 20% para teste.Execute o código da célula abaixo para realizar divisão.<jupyter_code># Importar train_test_split
from sklearn.model_selection import train_test_split

# Dividir os 'atributos' e 'income' entre conjuntos de treinamento e de testes.
X_train, X_test, y_train, y_test = train_test_split(features_final, 
                                                    income, 
                                                    test_size = 0.2, 
                                                    random_state = 0)

# Show the results of the split
print("Training set has {} samples.".format(X_train.shape[0]))
print("Testing set has {} samples.".format(X_test.shape[0]))<jupyter_output>Training set has 36177 samples.
Testing set has 9045 samples.<jupyter_text>---- Avaliando a performance do modeloNesta seção nós investigaremos quatro algoritmos diferentes e determinaremos qual deles é melhor para a modelagem dos dados. Três destes algoritmos serão algoritmos de aprendizado supervisionado de sua escolha e o quarto algoritmo é conhecido como *naive predictor*.  Métricas e o Naive predictor*CharityML*, equipada com sua pesquisa, sabe que os indivíduos que fazem mais do que \$50,000 possuem maior probabilidade de doar para a sua campanha de caridade. Por conta disto, a *CharityML* está particularmente interessada em predizer com acurácia quais indivíduos possuem remuneração acima de \$50,000. Parece que utilizar **acurácia (accuracy)** como uma métrica para avaliar a performance de um modelo é um parâmetro adequado. Além disso, identificar alguém que *não possui* remuneração acima de \$50,000 como alguém que recebe acima deste valor seria ruim para a *CharityML*, uma vez que eles estão procurando por indivíduos que desejam doar. Com isso, a habilidade do modelo em predizer com preisão aqueles que possuem a remuneração acima dos \$50,000 é *mais importante* do que a habilidade de realizar o **recall** destes indivíduos. Nós podemos utilizar a fórmula **F-beta score** como uma métrica que considera ambos: precision e recall.$$ F_{\beta} = (1 + \beta^2) \cdot \frac{precision \cdot recall}{\left( \beta^2 \cdot precision \right) + recall} $$Em particular, quando $\beta = 0.5$, maior ênfase é atribuída para a variável precision. Isso é chamado de **F$_{0.5}$ score** (ou F-score, simplificando).Analisando a distribuição de classes (aqueles que possuem remuneração até \$50,000 e aqueles que possuem remuneração superior), fica claro que a maioria dos indivíduos não possui remuneração acima de \$50,000. Isto pode ter grande impacto na **acurácia (accuracy)**, uma vez que nós poderíamos simplesmente dizer *"Esta pessoa não possui remuneração acima de \$50,000"* e estar certos em boa parte das vezes, sem ao menos olhar os dados! Fazer este tipo de afirmação seria chamado de **naive**, uma vez que não consideramos nenhuma informação para balisar este argumento. É sempre importante considerar a *naive prediction* para seu conjunto de dados, para ajudar a estabelecer um benchmark para análise da performance dos modelos. Com isso, sabemos que utilizar a naive prediction não traria resultado algum: Se a predição apontasse que todas as pessoas possuem remuneração inferior à \$50,000, a *CharityML* não identificaria ninguém como potencial doador.  Nota: Revisando: accuracy, precision e recall**Accuracy** mede com que frequência o classificador faz a predição correta. É a proporção entre o número de predições corretas e o número total de predições (o número de registros testados).**Precision** informa qual a proporção de mensagens classificamos como spam eram realmente spam. Ou seja, é a proporção de verdadeiros positivos (mensagens classificadas como spam que eram realmente spam) sobre todos os positivos (todas as palavras classificadas como spam, independente se a classificação estava correta), em outras palavras, é a proporção`[Verdadeiros positivos/(Verdadeiros positivos + Falso positivos)]`**Recall (sensibilidade)** nos informa qual a proporção das mensagens que eram spam que foram corretamente classificadas como spam. É a proporção entre os verdadeiros positivos (classificados como spam, que realmente eram spam) sobre todas as palavras que realmente eram spam. Em outras palavras, é a proporção entre`[Verdadeiros positivos/(Verdadeiros positivos + Falso negativos)]`Para problemas de classificação distorcidos em suas distribuições, como no nosso caso, por exemplo, se tivéssemos 100 mensagems de texto e apenas 2 fossem spam e todas as outras não fossem, a "accuracy" por si só não seria uma métrica tão boa. Nós poderiamos classificar 90 mensagems como "não-spam" (incluindo as 2 que eram spam mas que teriam sido classificadas como não-spam e, por tanto, seriam falso negativas.) e 10 mensagems como spam (todas as 10 falso positivas) e ainda assim teriamos uma boa pontuação de accuracy. Para estess casos, precision e recall são muito úteis. Estas duas métricas podem ser combinadas para resgatar o F1 score, que é calculado através da média(harmônica) dos valores de precision e de recall. Este score pode variar entre 0 e 1, sendo 1 o melhor resultado possível para o F1 score (consideramos a média harmônica pois estamos lidando com proporções).  Questão 1 - Performance do Naive PredictorSe escolhessemos um modelo que sempre prediz que um indivíduo possui remuneração acima de $50,000, qual seria a accuracy e o F-score considerando este conjunto de dados? Você deverá utilizar o código da célula abaixo e atribuir os seus resultados para as variáveis `'accuracy'` e `'fscore'` que serão usadas posteriormente.Por favor, note que o propósito ao gerar um naive predictor é simplesmente exibir como um modelo sem nenhuma inteligência se comportaria. No mundo real, idealmente o seu modelo de base será o resultado de um modelo anterior ou poderia ser baseado em um paper no qual você se basearia para melhorar. Quando não houver qualquer benchmark de modelo, utilizar um naive predictor será melhor do que uma escolha aleatória.**DICA:** * Quando temos um modelo que sempre prediz '1' (e.x o indivíduo possui remuneração superior à 50k) então nosso modelo não terá Verdadeiros Negativos ou Falso Negativos, pois nós não estaremos afirmando que qualquer dos valores é negativo (ou '0') durante a predição. Com isso, nossa accuracy neste caso se torna o mesmo valor da precision (Verdadeiros positivos/ (Verdadeiros positivos + Falso positivos)) pois cada predição que fizemos com o valor '1' que deveria ter o valor '0' se torna um falso positivo; nosso denominador neste caso é o número total de registros.* Nossa pontuação de Recall(Verdadeiros positivos/(Verdadeiros Positivos + Falsos negativos)) será 1 pois não teremos Falsos negativos.<jupyter_code>'''
TP = np.sum(income) # Contando pois este é o caso "naive". Note que 'income' são os dados 'income_raw' convertidos
para valores numéricos durante o passo de pré-processamento de dados.
FP = income.count() - TP # Específico para o caso naive

TN = 0 # Sem predições negativas para o caso naive
FN = 0 # Sem predições negativas para o caso naive
'''

TP = income.sum()
FP = income.count() - TP
TN = 0
FN = 0

# TODO: Calcular accuracy, precision e recall
accuracy = (TP + TN) / (TP + TN + FP + FN)
recall = TP / (TP + FN)
precision = TP / (TP + FP)

# TODO: Calcular o F-score utilizando a fórmula acima para o beta = 0.5 e os valores corretos de precision e recall.
beta = 0.5
fscore = (1 + beta**2) * (precision * recall) / ((beta**2) * precision + recall)

# Exibir os resultados 
print("Naive Predictor: [Accuracy score: {:.4f}, F-score: {:.4f}]".format(accuracy, fscore))<jupyter_output>Naive Predictor: [Accuracy score: 0.2478, F-score: 0.2917]<jupyter_text>Modelos de Aprendizado Supervisionado**Estes são alguns dos modelos de aprendizado supervisionado disponíveis em** [`scikit-learn`](http://scikit-learn.org/stable/supervised_learning.html)- Gaussian Naive Bayes (GaussianNB)- Decision Trees (Árvores de decisão)- Ensemble Methods (Bagging, AdaBoost, Random Forest, Gradient Boosting)- K-Nearest Neighbors (KNeighbors)- Stochastic Gradient Descent Classifier (SGDC)- Support Vector Machines (SVM)- Logistic Regression  Questão 2 - Aplicação do ModeloListe três dos modelos de aprendizado supervisionado acima que são apropriados para este problema que você irá testar nos dados do censo. Para cada modelo escolhido- Descreva uma situação do mundo real onde este modelo pode ser utilizado. - Quais são as vantagems da utilização deste modelo; quando ele performa bem?- Quais são as fraquezas do modelo; quando ele performa mal?- O que torna este modelo um bom candidato para o problema, considerando o que você sabe sobre o conjunto de dados?**DICA:**Estruture sua resposta no mesmo formato acima, com 4 partes para cada um dos modelos que você escolher. Por favor, inclua referências em cada uma das respostas. **Resposta:**A princípio, o nosso problema é um caso de classificação binária, em que a variável alvo (*income*) ou possui um valor superior a $50k ou um valor menor ou igual a este. Dentre as variáveis independentes, temos dados contínuos e categóricos. Para resolver este problema, vários algoritmos poderiam ser adotados, mas neste trabalho escolhemos os seguintes:- Regressão Logística- Árvore de Decisão- AdaBoostO índices entre colchetes indicam as fontes e referências bibliográficas utlizadas para obter ou complementar o conhecimento para construção do parágrafo que o acompanha. Todas as referências utilizadas estão no final deste documento.**Regressão Logística**É um algoritmo de classificação linear amplamente empregado em várias áreas, como na saúde predizendo o risco de desenvolver determinada doença a partir de várias características do paciente. [1]Dentre suas vantagens: além de predizer a qual classe pertence, ele pode estimar a probabilidade desse pertencimento; é um algoritmo baseado em funções matemáticas que cria uma linha, plano ou hiperplano de segmentação simples e flexível entre classes, podendo assumir qualquer direção ou orientação do espaço. [1][8]Por outro lado, entre as desvantagens: este algoritmo se aplica apenas a problemas de classificação binários ou dicotômicos; embora o plano de classificação seja simples, sua interpretação e entendimento pode ser mais complexa para aqueles que não tem uma boa base matemática; não se ajusta muito bem quando a relação entre as variáveis dependentes e independentes for complexa, com uma alta não linearidade. [1][8]É um método computacionalmente barato e muito poderoso, considerando que ele se restringe a problemas de classificação binárias, o que é o nosso caso, é um algoritmo que vale muito tentar antes de partir para outros mais complexos.**Árvore de Decisão**Este é outro algoritmo amplamente usado em várias áreas, mas tem um grande espaço conquistado nas áreas e processos de negócio que usam decisões analíticas por dados, principalmente por sua facilidade de compreensão. [2][8]Podemos citar como as vantagens: é um método poderoso e flexível, se enquadrando tanto em problemas de classificação quanto de regressão, atua com variáveis dependentes e independentes categóricas e contínuas, podendo ser utilizado em classificações binárias e não-binárias; como em regressão logística, além da classificação pode também estimar a probabilidade de pertencimento à classe; os limites de decisão criados para segmentar as classes são sempre perpendiculares aos eixos, podendo criar complexas regiões de classificação, o que torna um bom algoritmo para lidar com a não linearidade; como sua classificação não se dá por meio de funções matemáticas, mas por declarações lógicas, é um modelo muito mais simples e intuitivo de entender e explicar, principalmente para pessoas sem sólida fundamentação matemática. [2][4][8]Como desvantagens: por sua possibilidade de criar complexas regiões de classificação, é um método que pode facilmente sobreajustar o modelo, exigindo maior atenção na seleção dos hiperparâmetros; comparado com outros algoritmos, ele tende a produzir modelos com menor acurácia; é instável, significando que pequenas mudanças nos dados pode levar a grandes mudanças na estrutura de otimização do modelo. [2][4][8]É um modelo também computacionalmente barato, flexível e trabalha bem com problemas não lineares, fazendo um contraponto ao primeiro algoritmo escolhido, a regressão logística. Logo, vale muito a pena avaliar a performance deste algoritmo antes de partirmos para outros ainda mais complexos.**AdaBoost**Os chamados *ensemble methods* tem ganhado cada vez mais espaço, principalmente em competições de machine learning como o Kaggle. Dentre eles, o Adaboost é um dos mais famosos e tem sido utilizado em aplicações de visão computacional, de detecção e reconhecimento de objetos. [3][5]Seus pontos fortes: de modo geral, os ensemble methods utilizam vários estimadores fracos para alcançar um modelo forte e os métodos de boosting, o Adaboot é um deles, fazem isso de forma iterativa, penalizando com maior intensidade os dados que na etapa anterior foram classificados erroneamente. Com isso, a tendência é criar modelos com maior acurácia e redução significativa dos erros com o crescimento da quantidade de estimadores; realiza uma boa generalização do modelo, reduzindos os erros de viés e variância; tem a capacidade de realizar classificações não binárias. [3][6][7]E suas fraquezas: naturalmente, como precisa de vários estimadores fracos para compor um modelo forte, seu custo computacional é mais elevado; é um modelo menos intuitivo e intelegível para profissionais fora do universo de data science; é um método sensível a ruídos e outliers. [3][6][7]É um metodo mais complexo e menos intuitivo, contudo tende a apresentar excelente performance com o incremento de estimadores. Com os tratamentos e as transformações dos dados na etapa de preparação, mitigando o efeito dos outliers, avaliamos que esta é uma excelente opção e complementa bem o arcabouço de algoritmos escolhidos para este problema.  Implementação - Criando um Pipeline de Treinamento e PrediçãoPara avaliar adequadamente a performance de cada um dos modelos que você escolheu é importante que você crie um pipeline de treinamento e predição que te permite de maneira rápida e eficiente treinar os modelos utilizando vários tamanhos de conjuntos de dados para treinamento, além de performar predições nos dados de teste. Sua implementação aqui será utilizada na próxima seção. No bloco de código abaixo, você precisará implementar o seguinte: - Importar `fbeta_score` e `accuracy_score` de [`sklearn.metrics`](http://scikit-learn.org/stable/modules/classes.htmlsklearn-metrics-metrics). - Adapte o algoritmo para os dados de treinamento e registre o tempo de treinamento.  - Realize predições nos dados de teste `X_test`, e também nos 300 primeiros pontos de treinamento `X_train[:300]`.   - Registre o tempo total de predição.  - Calcule a acurácia tanto para o conjundo de dados de treino quanto para o conjunto de testes. - Calcule o F-score para os dois conjuntos de dados: treino e testes.    - Garanta que você configurou o parâmetro `beta`!<jupyter_code># TODO: Import two metrics from sklearn - fbeta_score and accuracy_score
from sklearn.metrics import accuracy_score, fbeta_score

def train_predict(learner, sample_size, X_train, y_train, X_test, y_test): 
    '''
    inputs:
       - learner: the learning algorithm to be trained and predicted on
       - sample_size: the size of samples (number) to be drawn from training set
       - X_train: features training set
       - y_train: income training set
       - X_test: features testing set
       - y_test: income testing set
    '''
    
    results = {}
    
    # TODO: Fit the learner to the training data using slicing with 'sample_size' using .fit(training_features[:], training_labels[:])
    start = time() # Get start time
    step = round(len(y_train) / sample_size) # Calcula o passo necessário para selecionar a quantidade de amostras definida por sample_size
    learner.fit(X_train[::step], y_train[::step]) # Aplica o passo na seleção das amostras para treinamento do modelo
    end = time() # Get end time
    
    # TODO: Calculate the training time
    results['train_time'] = end - start
        
    # TODO: Get the predictions on the test set(X_test),
    #       then get predictions on the first 300 training samples(X_train) using .predict()
    start = time() # Get start time
    predictions_test = learner.predict(X_test)
    predictions_train = learner.predict(X_train[:300])
    end = time() # Get end time
    
    # TODO: Calculate the total prediction time
    results['pred_time'] = end - start
            
    # TODO: Compute accuracy on the first 300 training samples which is y_train[:300]
    results['acc_train'] = accuracy_score(predictions_train, y_train[:300])
        
    # TODO: Compute accuracy on test set using accuracy_score()
    results['acc_test'] = accuracy_score(predictions_test, y_test)
    
    # TODO: Compute F-score on the the first 300 training samples using fbeta_score()
    results['f_train'] = fbeta_score(predictions_train, y_train[:300], beta=0.5)
        
    # TODO: Compute F-score on the test set which is y_test
    results['f_test'] = fbeta_score(predictions_test, y_test, beta=0.5)
       
    # Success
    print("{} trained on {} samples.".format(learner.__class__.__name__, sample_size))
        
    # Return the results
    return results<jupyter_output><empty_output><jupyter_text>Implementação: Validação inicial do modeloNo código da célula, você precisará implementar o seguinte:- Importar os três modelos de aprendizado supervisionado que você escolheu na seção anterior - Inicializar os três modelos e armazená-los em `'clf_A'`, `'clf_B'`, e `'clf_C'`.   - Utilize um `'random_state'` para cada modelo que você utilizar, caso seja fornecido.  - **Nota:** Utilize as configurações padrão para cada modelo - você otimizará um modelo específico em uma seção posterior- Calcule o número de registros equivalentes à 1%, 10%, e 100% dos dados de treinamento.  - Armazene estes valores em `'samples_1'`, `'samples_10'`, e `'samples_100'` respectivamente.**Nota:** Dependendo do algoritmo de sua escolha, a implementação abaixo pode demorar algum tempo para executar!<jupyter_code># TODO: Importe os três modelos de aprendizado supervisionado da sklearn
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import AdaBoostClassifier

# TODO: Inicialize os três modelos
clf_A = LogisticRegression(random_state=42)
clf_B = DecisionTreeClassifier(random_state=42)
clf_C = AdaBoostClassifier(random_state=42)

# TODO: Calcule o número de amostras para 1%, 10%, e 100% dos dados de treinamento
# HINT: samples_100 é todo o conjunto de treinamento e.x.: len(y_train)
# HINT: samples_10 é 10% de samples_100
# HINT: samples_1 é 1% de samples_100
samples_100 = len(y_train)
samples_10 = round(0.1 * samples_100)
samples_1 = round(0.01 * samples_100)

# Colete os resultados dos algoritmos de aprendizado
results = {}
for clf in [clf_A, clf_B, clf_C]:
    clf_name = clf.__class__.__name__
    results[clf_name] = {}
    for i, samples in enumerate([samples_1, samples_10, samples_100]):
        results[clf_name][i] = \
        train_predict(clf, samples, X_train, y_train, X_test, y_test)

# Run metrics visualization for the three supervised learning models chosen
vs.evaluate(results, accuracy, fscore)<jupyter_output>LogisticRegression trained on 362 samples.
LogisticRegression trained on 3618 samples.
LogisticRegression trained on 36177 samples.
DecisionTreeClassifier trained on 362 samples.
DecisionTreeClassifier trained on 3618 samples.
DecisionTreeClassifier trained on 36177 samples.
AdaBoostClassifier trained on 362 samples.
AdaBoostClassifier trained on 3618 samples.
AdaBoostClassifier trained on 36177 samples.<jupyter_text>---- Melhorando os resultadosNesta seção final, você irá escolher o melhor entre os três modelos de aprendizado supervisionado para utilizar nos dados dos estudantes. Você irá então realizar uma busca grid para otimização em todo o conjunto de dados de treino (`X_train` e `y_train`) fazendo o tuning de pelo menos um parâmetro para melhorar o F-score anterior do modelo.  Questão 3 - Escolhendo o melhor modeloBaseado na validação anterior, em um ou dois parágrafos explique para a *CharityML* qual dos três modelos você acredita ser o mais apropriado para a tarefa de identificar indivíduos com remuneração anual superior à \$50,000.  **DICA:** Analise o gráfico do canto inferior esquerdo da célula acima(a visualização criada através do comando `vs.evaluate(results, accuracy, fscore)`) e verifique o F score para o conjunto de testes quando 100% do conjunto de treino é utilizado. Qual modelo possui o maior score? Sua resposta deve abranger os seguintes pontos:* métricas - F score no conjunto de testes quando 100% dos dados de treino são utilizados, * tempo de predição/treinamento * a adequação do algoritmo para este conjunto de dados. **Resposta:**Acreditamos que o algoritmo mais adequado para solução do nosso problema é o **Adaboost**. Utilizando os classificadores com os parâmetros padrões, a Árvore de Decisão tem um bom desempenho com os dados de treinamento, mas não generaliza bem como pode ser observado nos resultados com os dados de teste, indicando um sobreajuste do modelo. Observando apenas os resultados gráficos com os dados de teste, notamos uma acurácia e um f-score aproximado entre os 3 modelos, com um desempenho superior para o Adaboost. Por outro lado, embora tenha um desempenho melhor, ele precisa de mais tempo para treinar o modelo e realizar predições.O nosso caso de uso não é uma aplicação em tempo real, então não é necessário criar um streaming de dados que serão processados e utlizados para tomada de decisão e ação no momento que chegam. Como parte de uma estratégia de campanha de marketing, o nosso modelo tem o papel de identificar potenciais indíviduos com maiores chances de serem impactados por nossa campanha e realizarem doações para nossa ONG. Desta forma, o critério *tempo* pode ser secundarizado ou preterido diante um melhor desempenho de classificação dos indivíduos.  Questão 4 - Descrevendo o modelo nos termos de Layman Em um ou dois parágrafos, explique para a *CharityML*, nos termos de layman, como o modelo final escolhido deveria funcionar. Garanta que você está descrevendo as principais vantagens do modelo, tais como o modo de treinar o modelo e como o modelo realiza a predição. Evite a utilização de jargões matemáticos avançados, como por exemplo a descrição de equações. **DICA:**Quando estiver explicando seu modelo, cite as fontes externas utilizadas, caso utilize alguma. **Resposta:** O Adaboost é o modelo que escolhemos para a seleção dos melhores indíviduos, ou seja, aqueles com o maior potencial de responderem às nossas campanhas e converterem em doações. Ele é um modelo muito simples de ser implementado e muito poderoso. Mas como funciona?Diferente de vários outros métodos que criam apenas um modelo de classificação, o Adaboost divide aleatoriamente o nosso conjunto de dados em vários conjuntos menores e a partir de cada conjunto reduzido é criado um modelo "fraco" de classificação. Por fraco, estamos dizendo que é um modelo com baixa precisão, que não acerta tanto quanto gostaríamos. Contudo, combinando de forma iterativa vários desses modelos fracos, é possível criar um modelo forte, com esteróides.Em uma analogia, seria como se pegássemos um grande projeto de um prédio e entregássemos para vários especialistas (arquiteto, paisagista, engenheiro civil, engenheiro eletricista, entre outros) para projetar nossa estrutura. Cada projeto individualmente seria fraco para construir o nosso prédio, mas pegando o melhor de cada é possível alcançarmos o melhor projeto. É isso que o Adaboost faz. Como é preciso treinar e combinar vários modelos em apenas um, normalmente o Adaboost leva mais tempo que outros algoritmos nas tarefas de treinamento e até mesmo de predição, mas ele apresenta bons resultados em termos de precisão da predição. Então, com certeza é um método que vale a pena investirmos.  Implementação: Tuning do modeloRefine o modelo escolhido. Utilize uma busca grid (`GridSearchCV`) com pleo menos um parâmetro importante refinado com pelo menos 3 valores diferentes. Você precisará utilizar todo o conjunto de treinamento para isso. Na célula de código abaixo, você precisará implementar o seguinte:- Importar [`sklearn.model_selection.GridSearchCV`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) e [`sklearn.metrics.make_scorer`](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html).- Inicializar o classificador escolhido por você e armazená-lo em `clf`. - Configurar um `random_state` se houver um disponível para o mesmo estado que você configurou anteriormente.- Criar um dicionário dos parâmetros que você quer otimizar para o modelo escolhido. - Exemplo: `parâmetro = {'parâmetro' : [lista de valores]}`. - **Nota:** Evite otimizar o parâmetro `max_features` se este parâmetro estiver disponível! - Utilize `make_scorer` para criar um objeto de pontuação `fbeta_score` (com $\beta = 0.5$).- Realize a busca gride no classificador `clf` utilizando o `'scorer'` e armazene-o na variável `grid_obj`.   - Adeque o objeto da busca grid aos dados de treino (`X_train`, `y_train`) e armazene em `grid_fit`.**Nota:** Dependendo do algoritmo escolhido e da lista de parâmetros, a implementação a seguir pode levar algum tempo para executar!<jupyter_code># TODO: Importar 'GridSearchCV', 'make_scorer', e qualquer biblioteca necessária
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer
from sklearn.metrics import fbeta_score

# TODO: Inicializar o classificador
clf = AdaBoostClassifier(random_state=42)

# TODO: Criar a lista de parâmetros que você quer otimizar, utilizando um dicionário, caso necessário.
# HINT: parameters = {'parameter_1': [value1, value2], 'parameter_2': [value1, value2]}
parameters = {'n_estimators': [50, 100, 250, 500],
              'learning_rate': [0.25, 0.5, 1, 1.5, 2]}

# TODO: Criar um objeto fbeta_score utilizando make_scorer()
scorer = make_scorer(fbeta_score, beta=0.5)

# TODO: Realizar uma busca grid no classificador utilizando o 'scorer' como o método de score no GridSearchCV() 
grid_obj = GridSearchCV(estimator=clf, param_grid=parameters, scoring=scorer)

# TODO: Adequar o objeto da busca grid como os dados para treinamento e encontrar os parâmetros ótimos utilizando fit() 
start = time()
grid_fit = grid_obj.fit(X_train, y_train)
end = time()
train_time = end - start

# Recuperar o estimador
best_clf = grid_fit.best_estimator_

# Realizar predições utilizando o modelo não otimizado e modelar
start = time()
predictions = (clf.fit(X_train, y_train)).predict(X_test)
best_predictions = best_clf.predict(X_test)
end = time()
pred_time = end - start

# Reportar os scores de antes e de depois
print("Train/Optimize and Predict Time\n------")
print("Time to train: ", train_time)
print("Time to predict: ", pred_time)

print("\nUnoptimized model\n------")
print("Accuracy score on testing data: {:.4f}".format(accuracy_score(y_test, predictions)))
print("F-score on testing data: {:.4f}".format(fbeta_score(y_test, predictions, beta = 0.5)))

print("\nOptimized Model\n------")
print("Final accuracy score on the testing data: {:.4f}".format(accuracy_score(y_test, best_predictions)))
print("Final F-score on the testing data: {:.4f}".format(fbeta_score(y_test, best_predictions, beta = 0.5)))<jupyter_output>Train/Optimize and Predict Time
------
Time to train:  658.7397973537445
Time to predict:  3.942863702774048

Unoptimized model
------
Accuracy score on testing data: 0.8576
F-score on testing data: 0.7246

Optimized Model
------
Final accuracy score on the testing data: 0.8677
Final F-score on the testing data: 0.7452<jupyter_text>Questão 5 - Validação final do modelo* Qual é a accuracy e o F-score do modelo otimizado utilizando os dados de testes?* Estes scores são melhores ou piores do que o modelo antes da otimização? * Como os resultados do modelo otimizado se comparam aos benchmarks do naive predictor que você encontrou na **Questão 1**?**Nota:** Preencha a tabela abaixo com seus resultados e então responda as questões no campo **Resposta**   Resultados:| Metric        | Naive Predictor | Unoptimized Model | Optimized Model ||---------------|-----------------|-------------------|-----------------| |Accuracy Score |0.2478 |0.8576 |0.8677|F-score        |0.2917 |0.7246 |0.7452 **Resposta:**Observamos que os nossos modelos não otimizados e otimizados apresentam uma performance bem superior ao nosso naive predictor. Observamos também que o Adaboost sem qualquer otimização, utilizando seus hiperparâmetros padrões, já apresenta uma performance considerável, sendo aproximadamente 85,8% de acurácia e 72,5% de F-score. Com a otimização, tivemos um ganho em torno de 1% na acurácia e 2% no F-score, o que pode ser muito significativo, já que a nossa base tem mais de 45 mil pessoas e qualquer percentual pode significar um impacto em 450 pessoas. ---- Importância dos atributosUma tarefa importante quando realizamos aprendizado supervisionado em um conjunto de dados como os dados do censo que estudamos aqui é determinar quais atributos fornecem maior poder de predição. Focando no relacionamento entre alguns poucos atributos mais importantes e na label alvo nós simplificamos muito o nosso entendimento do fenômeno, que é a coisa mais importante a se fazer. No caso deste projeto, isso significa que nós queremos identificar um pequeno número de atributos que possuem maior chance de predizer se um indivíduo possui renda anual superior à \$50,000.Escolha um classificador da scikit-learn (e.x.: adaboost, random forests) que possua o atributo `feature_importance_`, que é uma função que calcula o ranking de importância dos atributos de acordo com o classificador escolhido. Na próxima célula python ajuste este classificador para o conjunto de treinamento e utilize este atributo para determinar os 5 atributos mais importantes do conjunto de dados do censo.  Questão 6 - Observação da Relevância dos AtributosQuando **Exploramos os dados**, vimos que existem treze atributos disponíveis para cada registro nos dados do censo. Destes treze atributos, quais os 5 atributos que você acredita que são os mais importantes para predição e em que ordem você os ranquearia? Por quê? **Resposta:**Intuitivamente falando, acreditamos que os cinco atributos abaixo sejam os mais significativos para a determinação do nível de renda (income) dos indívíduos:- **capital-gain e capita-loss**: o montante de ganho e perda de capital, definitivamente, devem ser dois atribuitos importantes para determinação do nível de renda.- **education**: níveis maiores de educação normalmente implica em melhores empregos com melhores condições de salário e benefícios.- **workclass**: a estabilidade financeira também deve outro aspecto importante para indivíduos realizarem doações. Acreditamos que empregos/condições mais estáveis e com maiores direitos/benefícios, traz uma maior segurança e tranquilidade para indíviduos, podendo realizar gastos e investimentos para além da subsistência.- **age**: acreditamos que pessoas mais jovens e que estão iniciando suas carreiras e construções de patrimônios realizem menos doações do que pessoas mais velhas e com maior estabilidade.  Implementação - Extraindo a importância do atributoEscolha um algoritmo de aprendizado supervisionado da `sciki-learn` que possui o atributo `feature_importance_` disponível. Este atributo é uma função que ranqueia a importância de cada atributo dos registros do conjunto de dados quando realizamos predições baseadas no algoritmo escolhido.Na célula de código abaixo, você precisará implementar o seguinte: - Importar um modelo de aprendizado supervisionado da sklearn se este for diferente dos três usados anteriormente.  - Treinar o modelo supervisionado com todo o conjunto de treinamento. - Extrair a importância dos atributos utilizando `'.feature_importances_'`.<jupyter_code># TODO: Importar um modelo de aprendizado supervisionado que tenha 'feature_importances_'
from sklearn.ensemble import AdaBoostClassifier

# TODO: Treinar o modelo utilizando o conjunto de treinamento com .fit(X_train, y_train)
model = AdaBoostClassifier(n_estimators=500, learning_rate=1.5, random_state=42)
model.fit(X_train, y_train)

# TODO: Extrair a importância dos atributos utilizando .feature_importances_ 
importances = model.feature_importances_

# Plotar
vs.feature_plot(importances, X_train, y_train)<jupyter_output><empty_output><jupyter_text>Questão 7 - Extraindo importância dos atributosObserve a visualização criada acima que exibe os cinco atributos mais relevantes para predizer se um indivíduo possui remuneração igual ou superior à \$50,000 por ano.* Como estes cinco atributos se comparam com os 5 atributos que você discutiu na **Questão 6**? * Se você estivesse próximo da mesma resposta, como esta visualização confirma o seu raciocínio? * Se você não estava próximo, por que você acha que estes atributos são mais relevantes?  **Resposta:**A avaliação dos cinco atributos mais importantes mostra que nossa intuição estava parcialmente certa. De fato, as características *capital-gain* e *capital-loss* são determinantes para estimar a renda sendo, reespectivamente, o primeiro e segunto atributos mais importantes. A idade (*age*) também se mostrou um forte atributo, embora tenha um impacto superior ao esperado, sendo o terceiro atributo mais significativo. Nossa intuição apontava que o nível educacional também seria relevante no processo de classificação e isso se confirma em partes, embora seja importante, esta característica é melhor representada pelo atributo *education-num* ao invés de *education*, que foi a nossa suposição inicial. Por fim, um atributo inicialmente inesperado, sendo o quarto mais relevante, é o *hours-per-week* e faz muito sentido. Naturalmente, a tendência é de que quanto mais horas trabalhadas por dia, semana ou mês, maior será remuneração e consequentemente, mais fácil será atingir o patamar de $50k de renda, o que qualifica o nosso indíviduo com maior propensão para realizar doações.  Selecionando atributosComo um modelo performa se nós só utilizamos um subconjunto de todos os atributos disponíveis nos dados? Com menos atributos necessários para treinar, a expectativa é que o treinamento e a predição sejam executados em um tempo muito menor — com o custo da redução nas métricas de performance. A partir da visualização acima, nós vemos que os cinco atributos mais importantes contribuem para mais de 50% da importância de **todos** os atributos presentes nos dados. Isto indica que nós podemos tentar *reduzir os atributos* e simplificar a informação necessária para o modelo aprender. O código abaixo utilizará o mesmo modelo otimizado que você encontrou anteriormente e treinará o modelo com o mesmo conjunto de dados de treinamento, porém apenas com *os cinco atributos mais importantes*<jupyter_code># Importar a funcionalidade para clonar um modelo
from sklearn.base import clone

# Reduzir a quantidade de atributos
X_train_reduced = X_train[X_train.columns.values[(np.argsort(importances)[::-1])[:5]]]
X_test_reduced = X_test[X_test.columns.values[(np.argsort(importances)[::-1])[:5]]]

# Treinar o melhor modelo encontrado com a busca grid anterior
clf = (clone(best_clf)).fit(X_train_reduced, y_train)

# Fazer novas predições
reduced_predictions = clf.predict(X_test_reduced)

# Reportar os scores do modelo final utilizando as duas versões dos dados.
print("Final Model trained on full data\n------")
print("Accuracy on testing data: {:.4f}".format(accuracy_score(y_test, best_predictions)))
print("F-score on testing data: {:.4f}".format(fbeta_score(y_test, best_predictions, beta = 0.5)))
print("\nFinal Model trained on reduced data\n------")
print("Accuracy on testing data: {:.4f}".format(accuracy_score(y_test, reduced_predictions)))
print("F-score on testing data: {:.4f}".format(fbeta_score(y_test, reduced_predictions, beta = 0.5)))<jupyter_output>Final Model trained on full data
------
Accuracy on testing data: 0.8677
F-score on testing data: 0.7452

Final Model trained on reduced data
------
Accuracy on testing data: 0.8421
F-score on testing data: 0.7003