<jupyter_start><jupyter_text>Aula 3.12 - Ensemble TheoryFala galera! Tudo bem? Continuamos nossas aulas de técnicas de *machine learning* para classificação. Até agora vimos como montar modelos isolados para tarefas de classificação. Cada modelo, por seu funcionamento e capacidade, acaba tendo seus prós e contras: alguns focam em certas métricas, outros encaram o trade-off de viés e variância de certo modo. Dessa forma, escolher um algoritmo acarreta diretamente no comportamento e trade-offs do seu modelo. Mas e se não usássemos apenas um, mas vários modelos que se completam. Enter Ensembles! Nas próximas 2 aulas, veremos como encarar problemas utilizando técnicas de Ensemble, que nada mais são do que juntar modelos diversos para termos uma melhor modelagem. Dica de hoje - Principais algoritmos simples de classificação para um DS KNN (K-Nearest Neighbors) Logistic Regression - __[Artigo do Datacamp](https://www.datacamp.com/community/tutorials/understanding-logistic-regression-python)__ - __[Tutorial do <NAME>](https://www.youtube.com/watch?v=-la3q9d7AKQ)__ - __[Tutorial StatsQuest](https://www.youtube.com/watch?v=yIYKR4sgzI8)__ Naive Bayes - __[Artigo do Datacamp](https://www.datacamp.com/community/tutorials/naive-bayes-scikit-learn)__ - __[Teoria de Probabilidade](https://www.youtube.com/watch?v=PrkiRVcrxOs)__ - __[Video Tutorial](https://www.youtube.com/watch?v=CPqOCI0ahss&t=236s)__ SVM (Support Vector Machine) - __[Artigo do Datacamp](https://www.datacamp.com/community/tutorials/svm-classification-scikit-learn-python)__ - __[Tutorial Codebasics](https://www.youtube.com/watch?v=FB5EdxAGxQg)__ Decision Tree - __[Artigo Datacamp](https://www.datacamp.com/community/tutorials/decision-tree-classification-python)__ - __[Tutorial StatQuest](https://datahackers.com.br/blog)__ - __[Google Devs from zero](https://www.youtube.com/watch?v=LDRbO9a6XPU)__  Sabedoria da multidão Indico que vocês vejam o documentário "The Code", que tem apenas 3 episódios. Um episódio em especial trata exatamente desse assunto e eu vou mostrar um trechinho para vocês. EP03 41:49 até 47. Que tal um pouquinho de história da matemática? =]<NAME>, em 1906, acompanhou participantes de uma feira que estavam tentando adivinhar o peso de um boi. Quem estava na feira podia escrever no ticket de entrada quanto achava que o boi pesava e quem chegasse mais perto ganharia um prêmio. O boi pesava 543,4 Kg. Individualmente os chutes das pessoas chegavam a ter erros de mais de 20Kg, mas tomando a média dos 787 tickets, Galton descobriu que o grupo tinha chegado ao valor de 547,4Kg. Eles acertaram com menos de 1% de erro!   Tipos de Ensemble  Não, eu não tive a maturidade pra deixar de fazer essa piada em época de EndGame.Ensembles são agrupamentos de algoritmos treinados para a realização da mesma tarefa (nem que seja derrotar o Thanos). Imagine uma orquestra funcionando. Dificilmente você imaginou uma pessoa sozinha tocando todos os instrumentos, certo? O mesmo princípio é aplicado em machine learning: ao invés de termos um só algoritmo como classificador ou regressor, vamos utilizar o conjunto deles para melhor representar nosso *mapping*. Ou seja, pensando na Sabedoria da Multidão, nós estamos tentando reproduzir o maior número de "chutes" possível para que consigamos chegar o mais perto que conseguirmos do resultado que desejamos.  Os esembles podem ser homogênios, utilizando vários modelos gerados a partir do mesmo método (por exemplo, contendo apenas regressões lineares) ou heterogênios (utilizando vários tipos de modelos diferentes para chegar em um resultado). Todos eles partem do princípio que iremos usar *weak learners* (lembra dos chutes que falamos?) para atingir o objetivo finalExistem essencialmente três tipos de ensembles:        - Bagging    - Boosting    - Stacking    Para o *Bagging* e para a *Random Forest*, podemos agregar o resultado de algumas maneiras diferentes: Fazendo uma média dos valores, quando estamos nos referindo resultados de valores contínuos, fazendo a moda dos valores (votação) quando estamos falando de classificações  ou ainda atribuindo um peso a cada modelo e fazendo uma média ponderada dos seus resultados. Todos esses modelos podem ser feitos de forma paralela (ao mesmo tempo).<jupyter_code>Um exemplo de obtenção do resultado por votação:<jupyter_output><empty_output><jupyter_text>O Boosting funciona de uma maneira um pouquinho diferente, já que seu resultado é construído sequencialmente a partir de um único weak learner inicial. Mas vamos explicar isso em detalhes mais adiante!  1 - Bagging ensemble A palavra *Bagging* é a abreviação de *Bootstrap Aggregation*, onde a primeira palavra indica a criação de um sample com reposição e a segunda palavra a agregação dos resultados obtidos por modelos treinados a partir dessas diferentes samples. Ou seja, primeiramente temos a etapa de *resampling* para cada modelo, para então fazermos seus treinos e por fim agregar suas respostas da maneira mais adequada.  Na criação das novas samples a partir da sample original, todos os exemplos da sample original, nesse caso, tem a mesma chance de aparecer na nova sample. Cada exemplo que é selecionado para entrar na nova sample sempre retorna ao "banco de exemplos" existentes na sample original, podendo assim ser escolhido novamente. As novas samples podem ser de tamahos menores ou do mesmo tamanho da sample original.   Com todas as novas samples geradas, podemos então treinar cada um dos seus respectivos modelos, como é ilustrado na imagem abaixo:  Dessa forma, formamos os várias *weak learners* (modelos que podem ser de quaisquer algoritmos como regressões logísticas, árvores de decisão, KNN; geralmente escolhe-se um deles, onde o mais comum é o de Árvore de Decisão pelo fato de ser mais facilmente interpretável), que irão gerar os seus "chutes" (previsões) para cada um dos exemplos de validação/teste, que serão agregados da forma mais adequada para cada tipo de problema.  Random Forests O algoritmo *Random Forests* pode ser considerado uma evolução do método de *bagging*. Suas árvores podem ser geradas com ou sem resample dos examplos da base de dados~, e caso seja optado por não fazer o *bagging* com os exemplos, todas as árvores serão testadas com o mesmo sample. A novidade aqui está na escolha dos preditores (features, características...) da base de dados. O número de preditores que cada árvore terá (hiperparâmetro) pode ser determinado dependendo do tipo de resultado que pretendemos obter, como mostrado a seguir, sendo esta a sugestão do criador, mas também de diversos outras maneiras como usando o log:    - Quando uma regressão: teremos que o número de parâmetros de cada árvore será o número total de preditores sobre 3 (p/3)    - Quando uma classificação: teremos que o número de parâmetros de cada árvore será a raiz quadrada do número total de preditores (sqrt(p))       A partir dessa determinação, cada árvore terá seus parâmetros selecionados randomicamente do total (por exemplo, se queremos resolver um problema de classificação e temos um total de 9 parâmetros, cada árvore terá 3 parâmetros, os quais serão selecionados aleatóriamente para cada uma).   2 - StackingA ideia por trás do stacking se divide em 2 partes. Queremos treinar diversos algoritmos nos nossos dados, e queremos que as visões deles sobre os algoritmos sejam complementares, ou seja, cada algoritmo aprenda algo a partir do anterior. Desse modo, o stacking se dá por 2 loops: - loop externo: treino de cada algoritmo de modo independente. - loop interno: quebre seu dataset de treino em 10 partes, você terá 10 combinações de 9:1. Treine cada algoritmo em cada uma dessas 10 combinações. Os outputs de cada algoritmo no dataset de teste servirão de features para o próximo algoritmo no loop externoDe modo visual, o stacking se parece com isso: 1) Após ter feito o `train_test_split`, quebre seu dataset de treino em 10 partes  2) Fazendo combinações de 9:1 partes, treine seu algoritmo (por ex, uma Decision Tree) nas 9 partes e use a décima parte com pré-teste. Repita isso para todas as combinações. Isso ajuda o algoritmo a entender pontos de vista diferentes sob seu dataset. O nome disso é **K-Fold Cross Validation** 3) Após o treino do primeiro algoritmo em seu loop interno, faça as predictions no dataset de teste. Esse output será utilizado como *feature* para os próximos algoritmos 4) Repita o passo desse loop externo com os próximos algoritmos, até alcançar a performance desejada O legal do Stacking é que você pode criar as features que desejar com esse método! Por exemplo, antes de fazer um regressor para prever a quantidade de vendas de um determinado produto na próxima semana, você pode antes fazer um classificador que preve se as vendas vão aumentar ou diminuir, para então passar esse output como nova feature :DAlgoritmos de Stacking:- quaisquer  Esse método é amplamente utilizado nas competições do Kaggle!  3- BoostingAh, o crème de la crème dos ensembles. Imagina que estamos construindo um classificador. Ele terá um erro, correto? E se ao invés de treinar nossos modelos e cada um tivesse seu erro, nós treinássemos modelos especializados em corrigir os erros anteriores? Diferente dos ensembles anteriores, que são ensembles em paralelo, boosting é um ensemble sequencial.1) Primeiramente, criamos um classificador base. Ele vai conter erros, e precisamos corrigí-los2) Criamos um segundo classificador, mas que opera em cima dos erros do primeiro (wrong predictions).3) Continuamos esse loop até chegar na performance desejada (um threshold específico, acurácia,...). No momento de definir este *trashold* precisamos tomar cuidado para não overfitarmos os nossos dados. O output final é dado pela Weighted Average dos sub-modelos.4) Nosso modelo final é uma combinação de todos os outrosO Gradient Boosting e o XGBoost são os modelos de boosting mais utilizados por diversas razões, entretando entre os dois citados, o XGBoost costuma ter uma desempenho melhor por ter uma regularização diferenciada. Algoritmos baseados em boosting: - Adaboost - GBM (Gradient Boosting Machine) - XGBoost (Extreme Gradient Boost) - LightGBM - CatBoost De um modo geral, os métodos de ensemble citados acima diminuem a variância do modelo único, já que eles combinam as estimativas dos diversos modelos. Isso faz com que o resultado seja mais estável (faça uma analogia com a Sabedoria da multidão). Se o problema do modelo único for baixa performance, o bagging será capaz de dar um bias melhor mas provavelmente a abordagem de boosting fará isso melhor. Entretando, se o modelo singular está overfitando, o bagging se torna a abordagem mais interessante.  Por fim, alguns códigos base para vocês guardarem:<jupyter_code>from sklearn.ensemble import VotingClassifier
# Voting Classifier
model1 = tree.DecisionTreeClassifier()
model2 = KNeighborsClassifier()
model3= LogisticRegression()

model1.fit(x_train,y_train)
model2.fit(x_train,y_train)
model3.fit(x_train,y_train)

pred1=model1.predict(x_test)
pred2=model2.predict(x_test)
pred3=model3.predict(x_test)

final_pred = np.array([])
for i in range(0,len(x_test)):
    final_pred = np.append(final_pred, mode([pred1[i], pred2[i], pred3[i]]))
    
model1 = LogisticRegression(random_state=1)
model2 = tree.DecisionTreeClassifier(random_state=1)
model = VotingClassifier(estimators=[('lr', model1), ('dt', model2)], voting='hard')
model.fit(x_train,y_train)
model.score(x_test,y_test)
# Stacking
def Stacking(model,train,y,test,n_fold):
    folds=StratifiedKFold(n_splits=n_fold,random_state=1)
    test_pred=np.empty((test.shape[0],1),float)
    train_pred=np.empty((0,1),float)
    for train_indices,val_indices in folds.split(train,y.values):
        x_train,x_val=train.iloc[train_indices],train.iloc[val_indices]
        y_train,y_val=y.iloc[train_indices],y.iloc[val_indices]

        model.fit(X=x_train,y=y_train)
        train_pred=np.append(train_pred,model.predict(x_val))
        test_pred=np.append(test_pred,model.predict(test))
    return test_pred.reshape(-1,1),train_pred

model1 = tree.DecisionTreeClassifier(random_state=1)
test_pred1 ,train_pred1=Stacking(model=model1,n_fold=10, train=x_train,test=x_test,y=y_train)
train_pred1=pd.DataFrame(train_pred1)
test_pred1=pd.DataFrame(test_pred1)

model2 = KNeighborsClassifier()
test_pred2 ,train_pred2=Stacking(model=model2,n_fold=10,train=x_train,test=x_test,y=y_train)
train_pred2=pd.DataFrame(train_pred2)
test_pred2=pd.DataFrame(test_pred2)

df = pd.concat([train_pred1, train_pred2], axis=1)
df_test = pd.concat([test_pred1, test_pred2], axis=1)
model = LogisticRegression(random_state=1)
model.fit(df,y_train)
model.score(df_test, y_test)
# Bagging
# Na real que a gente só usa RandomForest mesmo
# Boosting
# Usamos amplamente o XGBoost. Para isso, usamos a biblioteca xgb<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.058896852602056717, 'fraction_numerical': 0.010127765659083826, 'mean_word_length': 5.320531757754801, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 11, 'lorem ipsum': 0, 'www.': 10, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '600259', 'license': 'MIT', 'n_tokens_mistral': 4286, 'n_tokens_neox': 4136, 'n_words': 1648, 'path': '06-derivando-modelos/02_arvores_decisao/intro_ensemble.ipynb', 'repo_name': 'pavellelis/datascience_course', 'size': '12847'}