<jupyter_start><jupyter_text>Banknote DatasetO conjunto de dados de notas envolve prever se uma determinada nota é autêntica, dadas várias medidas tomadas em uma fotografia.  Importando as bibliotecas<jupyter_code># data analysis
import numpy as np
import pandas as pd 

# data visualization
import matplotlib.pyplot as plt
plt.style.use('seaborn-bright')
import matplotlib.colors
from matplotlib import rcParams
labelsize = 14
rcParams['xtick.labelsize'] = labelsize
rcParams['ytick.labelsize'] = labelsize 
import seaborn as sns
sns.set_style('white')

# machine learning...
from sklearn.model_selection import train_test_split, RepeatedStratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler, QuantileTransformer
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression, RidgeClassifier, PassiveAggressiveClassifier, SGDClassifier
from sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from sklearn.metrics import classification_report, cohen_kappa_score, confusion_matrix, f1_score, precision_score, recall_score
from sklearn.metrics import recall_score, roc_auc_score, roc_curve<jupyter_output><empty_output><jupyter_text>FunçõesVamos criar algumas funções que irão otimizar nosso trabalho  Avaliação dos modelos<jupyter_code>def avalia_modelos(X, y, model, metric='accuracy'):
    """
    Função para avaliar os modelos
    --------------------------------------------------------------
    Parametros:
    X = valores de entrada (input values)
    y = variável alvo (target value)
    model = modelo que sera avaliado
    metric = metrica de desempenho. Por padrão "accuracy".
    Obs: para classes desbalanceadas não é melhor metrica de avaliação.
    consultar link para ver outras opções : 
    https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter
    --------------------------------------------------------------
    Retorna:
    Pontuação (score) do modelo avaliado
    
    """
    # definindo KFold Cross-Validation.
    kf = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
    # avaliando com cross_val_score
    scores = cross_val_score(model, X, y, scoring=metric, cv=kf, n_jobs=-1)
    # retornando pontuação
    return scores<jupyter_output><empty_output><jupyter_text>Definição dos modelos<jupyter_code>def define_modelos(models=dict()):
    """
    Retorna um dicionário de nomes de modelos mapeados para o objeto de modelo scikit-learn
    """
    # MODELOS LINEARES
    models['log'] = LogisticRegression()
    alphas = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
    for a in alphas:
        models['ridge-'+str(a)] = RidgeClassifier(alpha=a)
    models['pac'] = PassiveAggressiveClassifier(max_iter=1000, tol=1e-3)
    models['sgd'] = SGDClassifier(max_iter=1000, tol=1e-3)
    models['lda'] = LinearDiscriminantAnalysis()
    
    # MODELOS NAO LINEARES
    for k in range(1, 21):
        models['knn-'+str(k)] = KNeighborsClassifier(n_neighbors=k)
    models['cart'] = DecisionTreeClassifier()
    models['extra'] = ExtraTreeClassifier()
    models['svml'] = SVC(kernel='linear')
    models['svmp'] = SVC(kernel='poly')
    c_values = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
    for c in c_values:
        models['svmr-'+str(c)] = SVC(C=c)
    models['nb'] = GaussianNB()
    
    # MODELOS ENSEMBLE
    models['ada'] = AdaBoostClassifier(n_estimators=100)
    models['rf'] = RandomForestClassifier(n_estimators=100)
    models['gb'] = GradientBoostingClassifier(n_estimators=100)
    models['xgb'] = XGBClassifier()
    models['lgb'] = LGBMClassifier(n_estimators=100)
    
    print(f"Foram definidos {len(models)} modelos:")
    return models<jupyter_output><empty_output><jupyter_text>Apresentação dos resultados<jupyter_code>def show_resultados(resultados, maximize=True, top_n=10):
    """
    Seleciona os melhores modelos e os apresenta em forma de ranking do melhor para o pior.
    resultados = dict contendo nome do modelo atrelado ao seu score
    maximize = True. Quanto mais proximo do 1 melhor.
    top_n = Indica quantos modelo apresentar. Por padrão 10
    """
    # gera a tupla com (nome, media(pontuação))
    meanScores = [(k, v.mean()) for k, v in resultados.items()]
    # ordenando do menor para o maior
    meanScores = sorted(meanScores, key=lambda x: x[1]) 
    if maximize:
        # faz a reversão do resultado (maior pra menor)
        meanScores = list(reversed(meanScores))
    # pegando os nomes dos melhores resultados    
    nomes = [x[0] for x in meanScores[:top_n]]
    # armazenando as pontuaçães
    scores = [resultados[x[0]] for x in meanScores[:top_n]] 
    # criando o loop para apresentar os resultados
    for i in range(top_n):
        nome = nomes[i]
        meanScore, stdScore = resultados[nome].mean(), resultados[nome].std()
        print(f"Pos:{i+1}, Model:{nome}, Score:{round(meanScore, 4)} ({round(stdScore, 4)})")
        
    # plotar o resultados
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.boxplot(scores, showmeans=True)
    ax.set_title("Melhores Resultados", fontsize=17)
    ax.set_xticklabels(labels=nomes, rotation=15)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Importando dados<jupyter_code>names = ['variance', 'skewness','kurtosis','entropy','class']

# instanciando o DataFrame
bankDf = pd.read_csv('https://raw.githubusercontent.com/DanOliveira-DS/Datasets_for_Practicing_Applied_Machine_Learning/master/csv%20files/banknote_authentication.csv', header=None, names=names)<jupyter_output><empty_output><jupyter_text>Os dados foram extraídos de imagens referentes a notas de banco originais e falsas. Essas imagens então foram digitalizadas e analisadas utilizando a transformada continua de wavelet (CWT) para extrair os recursos (features) que compõem nosso conjunto de dados. Caso queira saber mais sobre essa transformada vou deixar um link de um artigo hospedado na [scielo](https://www.scielo.br/pdf/rbef/v38n3/1806-1117-rbef-38-03-e3314.pdf)<jupyter_code># verificando as primeiras entradas
print(bankDf.head())<jupyter_output>variance  skewness  kurtosis  entropy  class
0   3.62160    8.6661   -2.8073 -0.44699      0
1   4.54590    8.1674   -2.4586 -1.46210      0
2   3.86600   -2.6383    1.9242  0.10645      0
3   3.45660    9.5228   -4.0112 -3.59440      0
4   0.32924   -4.4552    4.5718 -0.98880      0<jupyter_text>Os nomes das nossas features são os seguintes:* Variação da imagem (CWT).* Inclinação da imagem (CWT).* Kurtosis da imagem (CWT).* Entropia da imagem.* Classe (0 para autêntico, 1 para inautêntico).  EDA  Começando pelo Básico  Qual o tamanho do conjunto de dados?A primeira coisa que faremos após importar o dataset será examinar as dimensões do DataFrame e as primeiras entradas. Isso possibilitará criar uma consciência situacional inicial a respeito do formato de entrada e da estrutura geral dos dados.Em relação à dimensionalidade dos dados, nosso conjunto de dados é composto por 1372 observações(entradas) sendo 4 variáveis de entrada e 1 variável de saída (variável alvo).<jupyter_code># size of dataset
print(f"dimensões do conjunto de dados".upper())
print(f"Observações: {bankDf.shape[0]}")
print(f"Features: {bankDf.shape[1]}")<jupyter_output>DIMENSÕES DO CONJUNTO DE DADOS
Observações: 1372
Features: 5<jupyter_text>Quais são os tipos de dados das minhas features?Vamos realizar o levantamento dos tipos de variáveis que compõem o DataFrame a fim de estabelecer a divisão entre as variáveis numéricas e as variáveis categóricas.<jupyter_code># types of my features
print(bankDf.dtypes)<jupyter_output>variance    float64
skewness    float64
kurtosis    float64
entropy     float64
class         int64
dtype: object<jupyter_text>Nosso conjunto de dados apresenta apenas variáveis numéricas. Um ponto positivo já que os algoritmos de machine learning gostam de trabalhar com dados numéricos. Nosso caso de variáveis categóricas precisamos primeiro tratá-las antes de alimentar nosso modelo.    Valores AusentesUm passo importante na análise de dados é verificação de dados ausentes, isso porque a qualidade de um conjunto de dados está diretamente relacionada à quantidade de valores ausentes. É importante entender logo no início se esses valores nulos são significativos comparados ao total de entradas. Caso eles sejam significativos será necessário um tratamento nos dados antes de realizarmos nossas análises<jupyter_code># Null Values
print("Quantidade de valores ausentes por feature:")
print(bankDf.isnull().sum())<jupyter_output>Quantidade de valores ausentes por feature:
variance    0
skewness    0
kurtosis    0
entropy     0
class       0
dtype: int64<jupyter_text>Como podemos ver temos um conjunto de dados bem limpo que não apresenta valores ausentes.  Resumo estatísticoO método `pd.describe()` nos ajuda a visualizar as propriedades estatísticas de variáveis numéricas. Exclui variáveis de caracteres.Imediatamente você obtém um resumo contendo algumas das principais informações estatísticas relevantes:* **count** - quantidade de entradas válidas* **mean** - média dos valores* **std** - desvio padrão* **min** - menor valor da coluna* **25%** - (Q1) primeiro quartil 25* **50%** - mediana* **75%** - (Q3) terceiro quartil 75* **max** - maior valor da colunaCom método `pd.describe()` é possível identificar colunas com possíveis outliers (por exemplo, olhando o valor da mediana e comparando com valores máximos e mínimos)<jupyter_code># describe data
print(bankDf.describe())<jupyter_output>variance     skewness     kurtosis      entropy        class
count  1372.000000  1372.000000  1372.000000  1372.000000  1372.000000
mean      0.433735     1.922353     1.397627    -1.191657     0.444606
std       2.842763     5.869047     4.310030     2.101013     0.497103
min      -7.042100   -13.773100    -5.286100    -8.548200     0.000000
25%      -1.773000    -1.708200    -1.574975    -2.413450     0.000000
50%       0.496180     2.319650     0.616630    -0.586650     0.000000
75%       2.821475     6.814625     3.179250     0.394810     1.000000
max       6.824800    12.951600    17.927400     2.449500     1.000000<jupyter_text>Conclusões:* A primeira vista pode parecer que nossas *features* apresentam *outliers* pelo que foi dito acima. Entretanto, vale lembrar que nossos dados passaram por uma transformação que foi a `transformada contínua de Wavelet (*CWT*)` que apresenta o comportamento que vimos no nosso resumo estatístico, mas isso não garante que nosso conjunto de dados não possua *outiliers*. Uma maneira de fazer essa verificação é utilizando gráficos como histogramas e box plots  Visualização dos Dados  HistogramaUma maneira rápida de ter uma ideia da distribuição de cada atributo é observar os histogramas. Os histogramas agrupam dados em posições e fornecem uma contagem do número de observações em cada posição. A partir da forma das caixas você pode ter uma ideia rápida de se um atributo é gaussiano, inclinado ou mesmo se tem uma distribuição exponencial. Isso é importante porque muitos algoritmos de aprendizagem de máquinas têm melhor desempenho quando a distribuição das variáveis é gaussiana. Portanto, saber de antemão como está nossa distribuição vai nos ajudar a escolher técnicas de escalonamento mais apropriada para nosso conjunto de dados.<jupyter_code># hist
bankDf.hist(column=['variance', 'skewness', 'kurtosis', 'entropy'], figsize=(12, 9), grid=False, color='royalblue')
plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Podemos ver que as variáveis têm escalas diferentes e que a maioria das variáveis tem uma distribuição inclinada. A variável final parece ter uma distribuição mais uniforme.  Box plot  Um gráfico de caixa é um método para representar graficamente grupos de dados numéricos através de seus quartis. A caixa se estende dos valores do quartil Q1 a Q3 dos dados, com uma linha na mediana (Q2). Os bigodes se estendem desde as bordas da caixa para mostrar o intervalo dos dados. A posição dos bigodes é definido por padrão como `[1.5 * IQR (IQR = Q3 – Q1)]` nas bordas da caixa. Pontos extremos (*outliers*) são aqueles que passaram do fim dos bigodes e identificá-los é importante porque eles podem distorcer uma distribuição de probabilidade e dificultar o dimensionamento de dados usando a padronização (ex. *StandartScaler()*, *MinMaxScaler()*), pois a média e o desvio padrão calculados serão distorcidos pela presença dos mesmos.     Vamos à visualização:<jupyter_code>fig, ax = plt.subplots(figsize=(10, 6))
bankDf.boxplot(column=['variance','skewness','kurtosis','entropy'], showmeans=True, vert=False, ax=ax, grid=False)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title('Distribuição dos recursos', fontsize=17)

plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Vamos apresentar a distribuição dos nossos dados divido pela nossa classe.<jupyter_code># criando DataFrame com os dados autenticos class=0
bankAut = bankDf[bankDf['class']==0]
# criando DataFrame com os dados falsos class=1
bankFal = bankDf[bankDf['class']==1]

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(12, 8), sharex=True, )
bankAut.boxplot(column=['variance','skewness','kurtosis','entropy'], vert=False, figsize=(8, 4), showmeans=True, ax=ax1)
ax1.set_title('Class 0', fontsize=16)
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
bankFal.boxplot(column=['variance','skewness','kurtosis','entropy'], vert=False, figsize=(8, 4), showmeans=True, ax=ax2)
ax2.set_title("Class 1", fontsize=16)
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)

plt.tight_layout(pad=4.0)<jupyter_output><empty_output><jupyter_text>Conclusões:* `Class = 1` apresenta mais valores discrepantes (*outliers*) que a `class = 0` principalmente em relação a feature `kurtosis`. Outro ponto interessante são os dados com valores negativos para as features `[skewness,variance]` que podem ser um indicativo de que as notas bancarias pertencem as `class = 1`  Gráfico de dispersão Gráfico de dispersão mostra o relacionamento entre duas variáveis como pontos em duas dimensões, um eixo para cada atributo. São úteis para identificar relacionamentos estruturados entre variáveis. O *sns.pairplot()* que usaremos para gerar nosso gráfico de dispersão ainda permite passar o parâmetro *hue=* que adiciona uma terceira dimensão onde podemos definir como sendo nossa classe e assim conseguimos ver como está sua distribuição de forma colorida.<jupyter_code>sns.set(font_scale=1.1), sns.set_style('white')
sns.pairplot(bankDf, hue='class', height=3.5,
             diag_kind='kde', palette='Set1',
             plot_kws = {'alpha': 0.6, 's': 50, 'edgecolor': 'k'}
            );<jupyter_output><empty_output><jupyter_text>Análise da variálvel alvoNosso objetivo é verificar como está o balanceamento da nossa variável alvo no conjunto de dados. Isso porque dados desequilibrados é algo comum e a maioria dos conjuntos de dados de classificação não possui um número exatamente igual de instâncias em cada classe, mas uma pequena diferença geralmente não importa. Já em problemas onde temos classes altamente desequilibradas algumas estratégias podem ser adotadas, como:* **Coletar mais dados**(Caso possível): Um conjunto de dados maior pode expor uma perspectiva diferente e talvez mais equilibra sobre as classes* **Mudar a métrica de avaliação**: Acurácia(*Accuracy*) não é a métrica a ser usada ao trabalhar com um conjunto de dados desequilibrados. Existem métricas que foram projetadas para contar uma história mais verdadeira ao trabalhar com classes desequilibradas:        >* Matriz de Confusão;    >* Precisão;    >* Recall;    >* pontuação f1;    >* Kappa de cohen;    >* Curva ROC.    * **Alterar o conjunto de dados**: Utilizar métodos como *Oversampling* e *Undersampling*<jupyter_code># mostrando distribuica da variavel alvo
print('Balanceamento da variável alvo:')
print(f"{bankDf['class'].value_counts()}\n")
print("Representação gráfica:")
# Plotando balanceamento
labels=['class 0', 'class 1']
fig, ax = plt.subplots(figsize=(10,6))
bankDf['class'].value_counts(normalize=True).plot(kind='bar', color='dimgrey', ax=ax)
ax.set_xlabel("Classe", fontsize=14, labelpad=20)
ax.set_ylabel("%", fontsize=14, labelpad=17)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_xticklabels(labels=labels, fontsize=15, rotation=0)
ax.patches[0].set_color('royalblue')
plt.tight_layout()<jupyter_output>Balanceamento da variável alvo:
0    762
1    610
Name: class, dtype: int64

Representação gráfica:<jupyter_text>Nosso conjunto de dados não apresenta um desbalanceamento tão significativo. Sendo `class = 0` representando ~55% e o restante ~44%  a `class = 1`. Portanto, uma simples mudança na métrica de avaliação deve gerar bons resultados.  Dividir os dados Treino/Teste<jupyter_code># train/test split
X = bankDf.loc[:,:'entropy']
y = bankDf['class']

X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.33, random_state=1)<jupyter_output><empty_output><jupyter_text>Modelo de Referência(*Baseline*)O DummyClassifier nos permite criar um modelo muito simples que podemos usar como referência para comparar com outros modelos<jupyter_code># criar modelo de referencia
naive = DummyClassifier(strategy='most_frequent')
scores = avalia_modelos(X_train, y_train, model=naive)
print(f"{scores}")
print(f"\nBaseline Accuracy: {round(scores.mean()*100, 4)}% ({round(scores.std(), 4)})")<jupyter_output>[0.55434783 0.55434783 0.55434783 0.54347826 0.54347826 0.54347826
 0.54347826 0.54347826 0.54347826 0.54945055 0.55434783 0.55434783
 0.55434783 0.54347826 0.54347826 0.54347826 0.54347826 0.54347826
 0.54347826 0.54945055 0.55434783 0.55434783 0.55434783 0.54347826
 0.54347826 0.54347826 0.54347826 0.54347826 0.54347826 0.54945055]

Baseline Accuracy: 54.7336% (0.0049)<jupyter_text>Nosso modelo de referência atingiu uma acurácia de ~54%. Vamos agora avaliar os modelos definidos e ver se conseguimos resultados melhores.  Avaliando Modelos<jupyter_code># instanciando modelos
models = define_modelos()
# dicionario que vai conter o resulta atrelado ao nome do modelo avaliado
resultados = dict()

# iterar pelo nome e modelo do nosso dicionario de modelos
for name, model in models.items():
    # avaliando os modelos utilizando nossa função de avaliação
    scores = avalia_modelos(X_train, y_train, model, metric='recall') 
    # armezando o nome e a pontuação
    resultados[name] = scores 
    # imprime resultados
    print(f"Model:{name}, Score:{round(scores.mean()*100, 4)} ({round(scores.std(), 4)})")
# apresentar os resultados
show_resultados(resultados)<jupyter_output>Pos:1, Model:svmr-1.0, Score:1.0 (0.0)
Pos:2, Model:svmr-0.9, Score:1.0 (0.0)
Pos:3, Model:svmr-0.8, Score:1.0 (0.0)
Pos:4, Model:svmr-0.7, Score:1.0 (0.0)
Pos:5, Model:svmr-0.6, Score:1.0 (0.0)
Pos:6, Model:svmr-0.5, Score:1.0 (0.0)
Pos:7, Model:svmr-0.4, Score:1.0 (0.0)
Pos:8, Model:svmr-0.3, Score:1.0 (0.0)
Pos:9, Model:svmr-0.2, Score:1.0 (0.0)
Pos:10, Model:svmr-0.1, Score:1.0 (0.0)<jupyter_text>Transformação dos DadosMuitos algoritmos de *machine learning* têm um melhor desempenho quando as variáveis numéricas de entrada são escaladas para um intervalo padronizado. Isto inclui algoritmos que utilizam uma soma ponderada, como a regressão linear, e algoritmos que utilizam medidas de distância, como os vizinhos k-nearest. Entretanto, é difícil saber se o redimensionamento dos seus dados melhorará o desempenho dos seus algoritmos antes de os aplicar. Muitas vezes pode, mas nem sempre. Uma boa dica é testar vários transformadores e compará-los isto pode rapidamente realçar os benefícios (ou falta deles) de redimensionar os seus dados com determinados modelos, e qual o método de redimensionamento que pode ser digno de mais investigação.Vamos criar funções onde utilizaremos o pacote `sklearn.prepocessing` que vai conter os transformadores e o pacote `sklearn.pipeline` que vai aplicar os transformadores definidos aos nossos modelos. O objetivo do pipeline é encapsular todo o processo logo, fica muito mais fácil validá-lo de forma correta e confiável evitando possíveis erros e vazamento dos dados.<jupyter_code>def pipeNone(model):
    return model

def pipeNorm(model):
    steps = []
    steps.append(('n', MinMaxScaler()))
    steps.append(('m', model))
    pipeline = Pipeline(steps=steps)
    return pipeline

def pipeStd(model):
    steps = []
    steps.append(('s', StandardScaler()))
    steps.append(('m', model))
    pipeline = Pipeline(steps=steps)
    return pipeline

def pipeRobu(model):
    steps = []
    steps.append(('r', RobustScaler()))
    steps.append(('m', model))
    pipeline = Pipeline(steps=steps)
    return pipeline

def pipeQuant(model):
    steps = []
    steps.append(('q', QuantileTransformer()))
    steps.append(('m', model))
    pipeline = Pipeline(steps=steps)
    return pipeline

pipelines = [pipeNone, pipeNorm, pipeStd, pipeRobu, pipeQuant]
models = define_modelos()

resultados = dict()

for name, model in models.items():
    for i in range(len(pipelines)):
        # avaliando os modelos utilizando nossa função de avaliação para cara pipeline
        scores = avalia_modelos(X_train, y_train, model=pipelines[i](model), metric='recall')
        resultados["["+str(i)+"]"+name] = scores
        print(f"Model:[{i}]{name}, Score:{round(scores.mean(), 4)} ({round(scores.std(), 4)})")
show_resultados(resultados)<jupyter_output>Pos:1, Model:[4]svmr-1.0, Score:1.0 (0.0)
Pos:2, Model:[3]svmr-1.0, Score:1.0 (0.0)
Pos:3, Model:[2]svmr-1.0, Score:1.0 (0.0)
Pos:4, Model:[1]svmr-1.0, Score:1.0 (0.0)
Pos:5, Model:[0]svmr-1.0, Score:1.0 (0.0)
Pos:6, Model:[4]svmr-0.9, Score:1.0 (0.0)
Pos:7, Model:[3]svmr-0.9, Score:1.0 (0.0)
Pos:8, Model:[2]svmr-0.9, Score:1.0 (0.0)
Pos:9, Model:[1]svmr-0.9, Score:1.0 (0.0)
Pos:10, Model:[0]svmr-0.9, Score:1.0 (0.0)<jupyter_text>Modelo FinalComo praticamente todos nossos modelos tiverem um bom desempenho vou utilizar o algoritmo de **regressão logística** para ser nosso modelo final. Isso porque diferente dos outros algoritmos que definimos a regressão logística não possui realmente nenhum parâmetro crítico para ajustar. E como nosso conjunto de dados apresentou *outliers* vamos utilizar como padronizador o *RobustScaler()* que apresentou uma boa performance com o algoritmo de regressão logística nos testes realizados.<jupyter_code># modelos mais simples Regressão Logística
model = LogisticRegression()
transformer = RobustScaler()

# criando pipeline
finalModel = Pipeline(steps=[('t', transformer), ('m', model)])
scores = avalia_modelos(X_train, y_train, model=finalModel, metric='f1')
scores
scores.mean()
# treinando modelo final
finalModel.fit(X_train, y_train)
# realizando as previsões
p = finalModel.predict(X_valid)<jupyter_output><empty_output><jupyter_text>Avaliando modelo finalPara avaliar nosso modelo vamos utilizar as seguintes métricas:* **Relatório de Classificação (Classification Report)** - Exibe a precisão (*precision*), recuperação (*recall*), pontuação F1 (*f1 score*) e suporte (*support*)    * Precisão : É a capacidade do classificador não rotular uma observação negativa verdadeira como positiva. Mede a **exatidão** dos classificadores    * Recuperação: É a capacidade do classificador de encontrar exemplos positivos. Se quiséssemos ter certeza de encontrar todos os exemplos positivos poderíamos maximizar o recall. Mede a **integridade** dos classificadores    * Pontuação f1: É a média harmônica de precisão e recall. Os valores variam de **0(ruim)** a **1(bom)**        * Suporte: É o numero de ocorrências de cada classe.* **Cohen Kappa**: precisão da classificação normalizada pelo desequilíbrio das classes nos dados* **ROC AUC Score**: Capacidade de um modelo de discriminar entre classes positivas e negativas. Uma área de 1 representa um modelo que fez todas as previsões perfeitamente. Uma área de 0.5 representa um modelo tão bom quanto aleatório  Resultados<jupyter_code># metricas
# classification report
print(f"Relatório de Classificação:\n\n{classification_report(y_valid,p)}")
print("-"*60)
print(f"Cohen Kappa Score: {round(cohen_kappa_score(y_valid, p), 4)}")
print("-"*60)
print(f"ROC AUC Score: {round(roc_auc_score(y_valid, p), 4)}")<jupyter_output>Relatório de Classificação:

              precision    recall  f1-score   support

           0       0.99      0.97      0.98       259
           1       0.96      0.99      0.98       194

    accuracy                           0.98       453
   macro avg       0.98      0.98      0.98       453
weighted avg       0.98      0.98      0.98       453

------------------------------------------------------------
Cohen Kappa Score: 0.9596
------------------------------------------------------------
ROC AUC Score: 0.9813<jupyter_text>Podemos ver que nosso modelo teve otimos resultado. Generalizou bem o que indica que o mesmo irá se sair bem com novos dados. Vamos agora ver esse resultado de forma gráfica apresentando a matrix de confusão e a curva roc.  Matriz de confusãoÉ uma apresentação útil da precisão de um modelo com duas ou mais classes. A tabela apresenta previsões no eixo x e os resultados de precisão no eixo y. Valores corretos que queremos que nosso modelo acerte estão na diagonal (esquerda para direita)<jupyter_code>matrix = confusion_matrix(y_valid, p) # criando matrix de confusão
confusionDf = pd.DataFrame(matrix, index=[0, 1], columns=['Autêntica','Falsa'])

fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(confusionDf, cmap='Blues', cbar=None, square=True, annot=True, fmt='.2f', linecolor='white', annot_kws={"size": 20}, ax=ax)
ax.set_title("Matrix de Confusão", fontsize=17)
ax.set_ylabel("Real", fontsize=14, rotation=0, labelpad=25)
ax.set_yticklabels(labels=['Autêntica','Falsa'], rotation=0)
ax.set_xlabel("Predito", fontsize=14, rotation=0, labelpad=25)

plt.tight_layout()<jupyter_output><empty_output><jupyter_text>O que a matriz de confusão nos mostrou:* Em um total de 259 entradas que eram verdadeiras, ou seja, representavam nossa `class = 0` nosso modelo acertou 252 e errou 7* Em um total de 194 entradas que eram falsas, ou seja, representavam nossa `class = 1` nosso modelo acertou 192 e errou 2  Curva ROCÉ um gráfico da taxa positiva verdadeira e da taxa positiva falsa para um determina conjunto de previsões de probabilidades em diferentes limites usados para mapear as probabilidades para os rótulos das classes. A área sob a curva é então a integral aproximada sob a curva ROC. É a representação gráfica do resultado apresentado anteriormente (ROC AUC Score: 0.9813)<jupyter_code>target_prob = finalModel.predict_proba(X_valid)[:,1] # instanciando as probalidades 
fpr, tpr, threshold = roc_curve(y_valid, target_prob) # instanciando as taxas de falso e verdadeiras

# Plotando Curva ROC
fig , ax = plt.subplots(figsize=(12, 6))
ax.plot(fpr, tpr, color='blue')
ax.plot([0, 1], ls='--', color='dimgrey'), ax.plot([0, 0], [1, 0], c=".8")
ax.plot([1, 1], c='.8')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title("ROC Curve", fontsize=20, c=".4")
ax.set_xlabel("False Positive Rate", fontsize=14, labelpad=25)
ax.set_ylabel("True Positive Rate", fontsize=14, rotation=0, labelpad=80)

plt.tight_layout()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10614545193481226, 'fraction_numerical': 0.04573192619714265, 'mean_word_length': 4.632632430246664, 'pattern_counts': {'":': 1, '<': 83, '<?xml version=': 0, '>': 89, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '380163', 'license': 'Apache-2.0', 'n_tokens_mistral': 10578, 'n_tokens_neox': 9725, 'n_words': 2903, 'path': 'Banknote.ipynb', 'repo_name': 'DanOliveira-DS/10-Standard-Datasets-for-Practicing-Applied-Machine-Learning', 'size': '27858'}