Como Acertar no Mundo Real - Validação e Teste de Modelos e Overfitting

Como Acertar no Mundo Real - Validação e Teste de Modelos e Overfitting

Olá, leitores! Ainda não sei qual a melhor maneira de iniciar a Newsletter, essa primeira linha sempre é a mais difícil.

Na última edição, falamos sobre como prever o churn — aquele momento em que o cliente decide não usar mais um serviço. E 3 pessoas me procuraram para entender um pouco melhor sobre o modelo que mostrei no exemplo, sobre o processo de Data Mining para escolher que variáveis e KPI's podem prever o comportamento de seus clientes e como eles poderiam colocar isso em produção em suas empresas! Fiquei maravilhado com isso, pois plantei realmente uma sementinha de aplicação de Data Science na mente de alguns de vocês. Bom, vamos então falar de alguns pontos que falei para quem me procurou para entender melhor sobre como eles teriam certeza se possuem bons resultados e qual o melhor modelo poderiam utilizar.

Na edição passada vimos como preparar e transformar os dados, exploramos o conjunto de dados e criamos nosso modelo de machine learning utilizando uma Árvore de Decisão para prever se um cliente iria cancelar ou não. O que já é um excelente início!

Mas, criar o modelo é só uma parte do processo. Não adianta se ele apenas funcionar bem nos dados de treino — o verdadeiro teste é saber se ele funciona com dados que ele nunca viu antes. E é aí que entra a validação do modelo com dados de teste. Além disso, é importante garantir que nosso modelo seja capaz de generalizar bem. E aqui entra um tema muito comum em machine learning: o overfitting.

Nessa edição, vamos falar sobre como validar nosso modelo e algumas técnicas para prevenir o overfitting, como regularização L1, regularização L2, e o cross-validation. Além disso, vamos explorar alguns modelos mais robustos, que oferecem mais controle sobre essas técnicas e podem nos ajudar a melhorar as previsões.


Validação do Modelo com Dados de Teste

Então, já criamos o nosso modelo na edição passada, mas como saber se ele está bom de verdade? É aí que entra a validação com dados de teste. Quando estamos construindo um modelo de machine learning, não podemos treinar e testar o modelo usando os mesmos dados. Isso seria como estudar para uma prova olhando as respostas — claro que vamos acertar, mas isso não significa que aprendemos de verdade (tá, as vezes nós erramos mesmo estando com as resposta, mas o computador seria capaz de lembrá-las perfeitamente).

Para avaliar corretamente o desempenho do nosso modelo, precisamos dividi-lo em duas partes: dados de treino e dados de teste. Assim, usamos uma parte dos dados para ensinar o modelo (treino) e a outra parte, que ele nunca viu, para avaliar como ele se sai (teste).

Como separar os dados de treino e teste no Python?

No Python, temos uma função muito prática da biblioteca scikit-learn chamada train_test_split, que faz essa divisão automaticamente para a gente. Vamos ver um exemplo de como podemos separar 80% dos dados para treino e 20% para teste:

from sklearn.model_selection import train_test_split

# Supondo que X sejam as features e y os rótulos (labels) do nosso conjunto de dados
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(f"Tamanho dos dados de treino: {len(X_train)}")
print(f"Tamanho dos dados de teste: {len(X_test)}")        

Aqui, o test_size=0.2 significa que estamos separando 20% dos dados para teste e 80% para treino. O random_state=42 garante que essa separação seja sempre a mesma, ou seja, a aleatoriedade é controlada para garantir reprodutibilidade nos experimentos (Você quer esse controle quando está fazendo um tutorial ou algo que possa ser replicado por outra pessoa. Mas para aplicações reais, não é tão legal colocar).

Disclaimer: Essa técnica se sai bem em modelos de classificação, mas se você estiver trabalhando em uma aplicação de séries temporais, precisará tomar alguns cuidados ao subdividir seu dataset. Mas isso é papo pro futuro.

E por que é importante usar dados de teste?

Ao usar dados de teste, conseguimos medir a capacidade do nosso modelo de generalizar, ou seja, de fazer previsões em dados que ele não viu antes. Se o desempenho nos dados de treino for ótimo, mas o desempenho nos dados de teste for ruim, é um sinal de que o modelo provavelmente está superajustado (ou seja, sofre de overfitting).


O que é Overfitting?

Agora que já dividimos nossos dados entre treino e teste, precisamos falar sobre um dos maiores vilões no desenvolvimento de modelos de machine learning: o overfitting.

Para explicar isso de forma simples, pense na seguinte situação: você tem um aluno que, em vez de aprender os conceitos de matemática, decidiu decorar todas as respostas do livro de exercícios. Se a prova for composta pelas mesmas questões do livro, ele vai se sair bem. Mas se o professor fizer qualquer pequena alteração nas perguntas, ele não saberá o que fazer, porque não aprendeu o processo, apenas decorou as respostas.

É exatamente isso que acontece com um modelo que sofre de overfitting. Ele “aprende” a mapear tão bem os dados de treino que, na prática, está decorando os padrões desses dados, incluindo os ruídos ou pequenas variações que são específicas daquele conjunto. Quando o modelo encontra novos dados, os dados de teste ou dados reais, ele pode falhar, já que esses novos dados podem ter pequenas diferenças.

Para ilustrar, imagine que treinamos nosso modelo em um dataset que tem características muito particulares, como pequenos erros ou outliers. Se o modelo se ajustar muito a esses detalhes, ele estará capturando padrões que não se repetem no mundo real, o que leva a um desempenho ruim quando tentamos aplicá-lo a dados de teste.

Para ilustrar mais ainda, eu gosto bastante dessa imagem que encontrei da Jackeline Gregório em um post no Medium falando sobre regularização e overfitting:

Underfitted = Modelo não se ajustou apropriadamente aos dados (Não foi capaz de aprender)

Good Fit = Modelo se ajustou bem ao comportamento dos dados (Conseguiu aprender bem)

Overffited = Modelo se sobreajustou aos dados, não ajustanto ao comportamento, mas aos dados em si (Decorou os dados e onde estavam os pontos)


Técnicas para Prevenir Overfitting

Como vimos, o overfitting acontece quando o modelo se ajusta tão bem aos dados de treino que acaba capturando detalhes e ruídos irrelevantes. Felizmente, temos algumas técnicas que podem ajudar a regular o comportamento do modelo, garantindo que ele foque mais nos padrões gerais dos dados. Aqui, vou mostrar três técnicas que são amplamente utilizadas: Regularização L1, Regularização L2, e Cross-validation.

1. Regularização L1 (Lasso)

A regularização L1, também conhecida como Lasso (Least Absolute Shrinkage and Selection Operator), adiciona uma penalidade proporcional à soma dos valores absolutos dos coeficientes do modelo. O efeito dessa técnica é "forçar" alguns coeficientes a serem exatamente zero, o que significa que ela tende a criar modelos mais simples, eliminando variáveis irrelevantes.

Em termos práticos, isso ajuda a reduzir o overfitting, pois o modelo se torna mais enxuto e menos sensível a ruídos.

Aqui está um exemplo de como aplicar a regularização L1 em uma regressão logística no Python:

from sklearn.linear_model import LogisticRegression

# Criando o modelo com regularização L1 (penalty='l1')
modelo_lasso = LogisticRegression(penalty='l1', solver='saga', C=1.0)
modelo_lasso.fit(X_train, y_train)

# Avaliando o desempenho
print(f"Score nos dados de teste: {modelo_lasso.score(X_test, y_test)}")        

No exemplo, penalty='l1' indica que estamos usando regularização L1, e o parâmetro C controla a força da regularização (quanto menor o valor de C, mais forte a regularização).

2. Regularização L2 (Ridge)

A regularização L2, também conhecida como Ridge, funciona de maneira semelhante à L1, mas em vez de penalizar a soma dos valores absolutos dos coeficientes, ela penaliza a soma dos quadrados dos coeficientes. Isso faz com que os coeficientes sejam "encolhidos", mas dificilmente serão exatamente zero.

Diferentemente da L1, que tende a eliminar variáveis, a regularização L2 mantém todas as variáveis, mas diminui seus efeitos, o que pode ser útil quando acreditamos que todas as variáveis têm alguma importância.

Aqui está como aplicar a regularização L2 no Python:

from sklearn.linear_model import Ridge

# Criando o modelo com regularização L2
modelo_ridge = Ridge(alpha=1.0)
modelo_ridge.fit(X_train, y_train)

# Avaliando o desempenho
print(f"Score nos dados de teste: {modelo_ridge.score(X_test, y_test)}")        

No caso da Ridge, usamos o parâmetro alpha para controlar a força da regularização (valores maiores de alpha significam maior regularização).

3. Cross-validation

A validação cruzada (cross-validation) é uma técnica poderosa para avaliar o desempenho do modelo de forma mais robusta. Em vez de simplesmente dividir os dados em treino e teste uma única vez, a validação cruzada divide os dados em vários subconjuntos e faz a avaliação múltiplas vezes, garantindo que o modelo seja testado em diferentes "fatias" do dataset.

A técnica mais comum é o k-fold cross-validation, onde o conjunto de dados é dividido em k partes (ou folds). O modelo é treinado em k-1 partes e testado na parte restante. Esse processo é repetido k vezes, e a média das pontuações obtidas é utilizada para avaliar o desempenho do modelo.

Imagem direto da biblioteca do SciKit-Learn Cross-Validation

Veja que na imagem, nós dividimos nossos dados entre Treino e Teste. Após isso, os dados de Treino são então divididos em 5 Partes, "Fold's".

A cada etapa do treinamento, o modelo irá treinar uilizando os dados dos fold's em verde para aprender seus parâmetros e irá testar o que aprendeu contra os dados presentes no fold azul, trocando a cada iteração de etapa qual fold será utilizado para validar o aprendizado da etapa.

Em uma analogia como a da escola, seria como você ter 10 perguntas, e, ao invés de tentar decorar/aprender estudando todas as 10 perguntas a partir do solucionário, você optasse por ver a solução de 8 perguntas, para entender como elas funcionam e tentasse resolver 2 perguntas sozinho. No dia seguinte, você separaria outras 2 perguntas para testar o que aprenderia com o solucionário das demais 8 questões (incluindo agora as que você havia separado para se testar no dia anterior). E assim por diante, até finalizar resolvendo todas as perguntas pelo menos uma vez.

E então, após todo esse processo, o modelo é submetido a uma validação com os dados de teste, tendo que resolver todas as questões da prova.

Aqui está como aplicar o cross-validation no Python:

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

# Criando o modelo sem regularização, apenas para fins de exemplo
modelo = LogisticRegression()

# Aplicando 5-fold cross-validation
scores = cross_val_score(modelo, X, y, cv=5)

print(f"Scores em cada fold: {scores}")
print(f"Média do score: {scores.mean()}")        

A função cross_val_score divide os dados e avalia o modelo em cada uma das partes, retornando as pontuações de cada execução. Isso nos dá uma ideia mais precisa de como o modelo se comporta em diferentes divisões do dataset.

Com essas três técnicas — regularização L1, regularização L2, e cross-validation — conseguimos melhorar a generalização do nosso modelo, prevenindo overfitting e garantindo que ele funcione bem em dados que ele nunca viu antes.


Modelos Mais Robustos

Na edição passada, utilizamos o DecisionTreeClassifier para o problema de churn, mas, como você já deve ter percebido, as árvores de decisão têm uma tendência a se ajustarem muito bem aos dados de treino, o que pode levar ao overfitting. Apesar de serem bastante intuitivas e fáceis de interpretar, quando falamos de dados mais complexos ou de alta dimensionalidade, precisamos de modelos que sejam mais robustos e flexíveis.

Que modelos então são mais robustos para pormos em produção ?

1. Regressão Logística

Embora o nome sugira que ela seja usada para problemas de regressão, a Regressão Logística é um modelo que é muito utilizado para problemas binários. É um modelo linear que prevê a probabilidade de um dado ponto pertencer a uma classe ou outra. E o melhor: podemos aplicar tanto a regularização L1 quanto L2!

Aqui está um exemplo de como aplicar a Regressão Logística com regularização L2 (que é a regularização padrão no scikit-learn):

from sklearn.linear_model import LogisticRegression

# Criando o modelo de regressão logística com regularização L2
modelo_log_reg = LogisticRegression(penalty='l2', solver='liblinear')
modelo_log_reg.fit(X_train, y_train)

# Avaliando o desempenho do modelo
score = modelo_log_reg.score(X_test, y_test)
print(f"Desempenho do modelo nos dados de teste: {score}")        

A regressão logística pode ser uma escolha interessante para problemas de classificação binária, como o churn, ela lida bem com grandes volumes de dados e pode ser regularizada para prevenir o overfitting.

2. Random Forest

O Random Forest é uma evolução das árvores de decisão. Em vez de construir uma única árvore, o Random Forest constrói várias árvores em subconjuntos aleatórios dos dados e depois faz uma média das previsões de todas essas árvores. Isso ajuda a reduzir o overfitting que vemos em uma única árvore de decisão.

Um ponto importante é que, como estamos trabalhando com várias árvores, o modelo se torna menos sensível aos ruídos e variações presentes nos dados de treino.

Aqui está como você pode aplicar o Random Forest no Python:

from sklearn.ensemble import RandomForestClassifier

# Criando o modelo Random Forest
modelo_rf = RandomForestClassifier(n_estimators=100, random_state=42)
modelo_rf.fit(X_train, y_train)

# Avaliando o desempenho
score_rf = modelo_rf.score(X_test, y_test)
print(f"Desempenho do Random Forest nos dados de teste: {score_rf}")        

Esse modelo é uma ótima opção quando temos um dataset maior e mais variado, já que ele melhora a capacidade de generalização.

3. Gradient Boosting

Outro modelo mais avançado é o Gradient Boosting, que também utiliza múltiplas árvores de decisão. No entanto, ao contrário do Random Forest, ele constrói as árvores de maneira sequencial, onde cada nova árvore tenta corrigir os erros cometidos pelas árvores anteriores. Isso faz com que o modelo se torne altamente preciso, mas também propenso a overfitting se não for controlado adequadamente.

Para prevenir o overfitting, podemos aplicar regularização ao Gradient Boosting, como mostramos anteriormente.

Aqui está um exemplo de aplicação do Gradient Boosting Classifier:

from sklearn.ensemble import GradientBoostingClassifier

# Criando o modelo Gradient Boosting
modelo_gb = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, random_state=42)
modelo_gb.fit(X_train, y_train)

# Avaliando o desempenho
score_gb = modelo_gb.score(X_test, y_test)
print(f"Desempenho do Gradient Boosting nos dados de teste: {score_gb}")        

O Gradient Boosting é uma boa escolha quando buscamos uma precisão maior e estamos dispostos a investir mais tempo em ajustes finos, como a escolha do número de estimadores e a taxa de aprendizado.

Esses três modelos — Regressão Logística, Random Forest e Gradient Boosting — são mais robustos que as árvores de decisão isoladas e, ao utilizarmos técnicas de regularização e validação cruzada, garantimos que eles mantenham um bom equilíbrio entre desempenho e generalização.


Conclusão

Nessa edição, exploramos um problema comum ao treinar modelos de machine learning: o overfitting. Vimos que, assim como um aluno que decora as respostas, mas falha quando o contexto muda, um modelo que memoriza os dados de treino tende a errar quando exposto a novos dados. Para evitar isso, discutimos três técnicas essenciais: regularização L1, regularização L2 e cross-validation.

A regularização L1 ajuda a eliminar pesos irrelevantes, tornando o modelo mais simples e eficiente, enquanto a regularização L2 penaliza pesos grandes, forçando o modelo a ser mais conservador e evitar ajustes excessivos. Já o cross-validation garante que o modelo seja testado em diferentes partes do conjunto de dados, ajudando a avaliar sua capacidade de generalização.

Essas técnicas foram aplicadas em modelos mais robustos do que a árvore de decisão que usamos anteriormente, como o Random Forest e o Gradient Boosting, que tendem a funcionar melhor em produção, especialmente quando lidamos com dados mais complexos e precisamos garantir estabilidade e precisão.

Prevenir o overfitting não é apenas uma questão de melhorar o desempenho no teste, mas de garantir que o modelo funcione bem no mundo real, onde os dados são dinâmicos e imprevisíveis. Além disso, esses modelos robustos, combinados com técnicas de validação adequadas, proporcionam soluções mais confiáveis e escaláveis para os desafios de machine learning.

Espero que essa edição tenha ajudado a entender melhor como prevenir overfitting e a importância da regularização e da validação contínua em um pipeline de machine learning!

Gostou do conteúdo? Se você quer continuar aprendendo mais sobre machine learning e seus desafios no mundo real, assine a newsletter e compartilhe com quem também está nessa jornada de dados. Tem alguma dúvida ou sugestão? Deixe um comentário, vou sempre acompanhar, irar dúvidas e tentar atender às sugestões!

Entre para ver ou adicionar um comentário

Outras pessoas também visualizaram

Conferir tópicos