← Back to list

Criando um classificador de Spam usando ML

Este artigo descreve o passo a passo do desenvolvimento e comparação de algoritmos de Machine Learning para classificação de emails de…

Ana Reinert · 2026-01-14 12:44 · 0 claps · 9.5 min read
#classification-models #machine-learning #artificial-intelligence
Open on Medium ↗
Wiki topics: ML · Machine Learning AI · AI · General EDU · Education & Learning

Criando um classificador de Spam

Este artigo apresenta um passo a passo da criação de um classificador de spam utilizando algoritmos de Machine Learning para o case de Data Science. O objetivo é aplicar conceitos, análise de dados e modelagem para desenvolver um modelo que classifique corretamente emails como spam ou não-spam.

1. Carregamento e Avaliação do Dataset

O dataset utilizado nesse projeto foi fornecido pelo case, contendo 747 mensagens classificadas como spam e 4.827 mensagens classificadas como não spam. As ferramentas utilizadas foram: pandas, matplotlib e scikit-learn. Todo o desenvolvimento, incluindo a visualização de dados e o treinamento e avaliação dos modelos, foi feito utilizando o Google Colab. Após a importação do csv para o Google Colab, o dataset foi lido utilizando o read_csv do pandas, que por sua vez retorna o DataFrame(sms_df) utilizado nas análises a seguir.

1.1 Gráfico as palavras mais frequentes em toda a base de dados

Para gerar um gráfico com as palavras mais frequentes do dataset, utilizaremos as colunas referentes às palavras contidas no corpo do email. Utilizando a biblioteca pandas, essas informações foram extraídas do sms_df. Como nem todas as colunas são de frequência de palavras, estas foram filtradas utilizando o ***iloc***[], buscando todas as linhas da coluna de índice 1 até o índice 150, que engloba todas as palavras e suas respectivas frequências.

Após a filtragem, faz-se a soma utilizando o sum() e a ordenação com o sort_values usando o parâmetro ascending=False (do maior para o menor). Por fim, usando o ***pyplot(plt) **do matplotlib, foi criado um gráfico de barra (plot.bar()) contendo as informações das somas das frequências de todas as palavras do dataset.*

Script para criação e visualização de gráfico

Script para criação e visualização de gráfico

Gráfico de frequência de palavras

Gráfico de frequência de palavras

Como a visualização das informações do gráfico ficou apertada, outro gráfico foi gerado, desta vez com as 10 palavras mais frequentes. O top_words reutilizou a soma feita anteriormente pelo word_counts, utilizando apenas os 10 primeiros resultados, retornados do ***head(n)***. Um novo gráfico, com as mesmas características do anterior, foi criado e plotado.

Script para geração de gráfico das 10 palavras mais frequentes

Script para geração de gráfico das 10 palavras mais frequentes

Gráfico das 10 palavras mais frequentes

Gráfico das 10 palavras mais frequentes

Com esse gráfico gerado, percebe-se que as palavras mais comuns do dataset se misturam bem entre palavras comuns em emails não spam e palavras comuns em emails de spam. Spams geralmente são textos que focam na engenharia social e na inocência do usuário, pedindo informações com um teor imediatista na intenção de assustar o usuário ou oferecendo algo “gratuito”.

  1. 2 Gráfico com as quantidades de mensagens comuns e spams para cada mês

Para visualizar a quantidade de mensagens comuns e spams para cada mês, o primeiro passo foi converter a coluna Date para um datetime utilizando o to_datetime(). Com ele, é possível acessar as propriedades de uma data, como o dia, mês e ano. Após convertido, o próximo passo foi agrupar as datas em meses utilizando o *dt.to_period(), passando como parâmetro o que se deseja agrupar, nesse caso, o mês (‘M’). Os períodos retornados foram salvos em uma nova coluna, intitulada Mês, e posteriormente agrupados e seus grupos somados, utilizando o [groupby](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html)() e [size](https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.DataFrameGroupBy.size.html)(). Com os novos dados, um novo gráfico de barra foi criado *e apresentado.

Script para geração de gráfico com a quantidade de mensagens comuns e spam para cada mês

Script para geração de gráfico com a quantidade de mensagens comuns e spam para cada mês

Algumas alterações na forma com que o gráfico é criado foram adicionadas. O *unstack() foi utilizado no gráfico para melhorar a visualização dos dados, já que este pega os valores de um nível do índice hierárquico (neste caso, IsSpam) e transforma nos cabeçalhos de novas colunas, ou seja, transfoma as linhas do IsSpam em uma nova coluna. Já a plt.legend *serve para definir uma legenda para as barras apresentadas, diferente dos valores da coluna utilizada (IsSpam), sendo Comum as mensagens comuns e Spam as mensagens de Spam.

Gráfico de quantidade mensagens comuns e spam para cada mês

Gráfico de quantidade mensagens comuns e spam para cada mês

Com o gráfico gerado, consegue-se observar uma discrepância na quantidade de emails de spam e emails comuns. Isso já era previsto visualizando o dataset, já que a quantidade de amostras de emails de spam é 546% menor que os emails comuns. Isso pode vir a afetar a acurácia dos modelos de classificação.

  1. 3 Calculo do máximo, mínimo, a média, a mediana, o desvio padrão e a variância da quantidade total de palavras (Word_Count) para cada mês

Para entender o comportamento dos dados, a distribuição, a estabilidade e a confiabilidade do volume textual ao longo do tempo, será analisado algumas métricas. Para o calculo do máximo, mínimo, média, mediana, desvio padrão e variância, o pandas oferece um método para lidar com as funções de agregação chamado *describe(). Usando esse método após o agrupamento dos valores do Word_Count para cada mês com o groupby*, o retorno é uma tabela contendo os resultados das funções para cada mês, arredondados para 2 casas decimais.

Tabela das funções do Word_Count para cada mês

Tabela das funções do Word_Count para cada mês

Script para geração dos valores das funções de agregação

Script para geração dos valores das funções de agregação

Considerando que usando esse método, outros campos não solicitados são exibidos — como os 3 quartis — , há uma outra opção, utilizando o *arg(). A forma de utilização é muito similar à primeira forma com describe, basta chamar o método e informar explicitamente os campos que deseja. O resultado é também bastante similar ao do describe*, limitando apenas aos valores definidos no método.

Tabela das funções do Word_Count para cada mês utilizando arg

Tabela das funções do Word_Count para cada mês utilizando arg

Script para geração dos valores das funções de agregação com arg

Script para geração dos valores das funções de agregação com arg

Com essas informações, consegue-se notar que a distribuição é assimétrica, já que a média é maior que a mediana, e com presença de outliers, ou seja, a maior parte dos dados tem quantidades baixas de palavras, mas existem registros com uma quantidade muito alta, o que puxa a média pra cima. Mesmo assim, existe uma certa estabilidade no comportamento geral, já que todos os meses tem métricas bem parecidas.

1.4 Exibição do dia de cada mês que possui a maior sequência de mensagens comuns (não spam).

Para exibir o dia de cada mês com a maior sequência de mensagens comuns, um novo dataframe foi criado filtrando e removendo todas as mensangens isSpam iguais a “yes”. Com o novo dataframe, uma nova coluna chamada Dia foi criada e, com base nela e no Mês, os valores foram agrupados e as linhas com o mesmo mês e dias foram contadas com o size(). O reset_index(name=’Quantidade’) serviu para reindexar o dataframe e o name=’Quantidade’ criou uma nova coluna, resultante da soma do size().

Depois de criar o daily_counts, que tem um dataframe com mês, dia e quantidade de mensagens comuns, este foi filtrado com base no mês e na quantidade e aplicado o idxmax(). O pandas tem uma ferramenta chamada *idxmax(), que retorna o índice da primeira ocorrência do máximo sobre o eixo solicitado, ou seja, para cada agrupamento, vai retornar o índice com o maior valor. Nesse caso, o que ele fez foi procurar a maior quantidade do agrupamento por mês, retornando o índice do dia com maior quantidade para cada mês. Com esses índices, o ultimo passo é usar o loc para localiza-los na lista de daily_counts* e exibir.

Script para exibir o dia de cada mês com maior sequência de mensagens comuns

Script para exibir o dia de cada mês com maior sequência de mensagens comuns

Resultado da tabela do most_active_days

Resultado da tabela do most_active_days

2. Preparação e Avaliação do Modelo

A segunda etapa do processo consiste em aplicar métodos de classificação que sejam capazes de distinguir automaticamente entre mensagens “comuns” e “spam”. Para isso, foi utilizado dois modelos de classificação da biblioteca scikit-learn: o Logistic Regression e o Random Forest. Ambos são amplamente utilizados em problemas de classificação binária e possuem características complementares, o que justifica sua escolha para essa tarefa.

O Logistic Regression é um modelo linear simples, mas altamente eficaz em problemas de classificação binária, especialmente em casos de texto. Sua principal vantagem está na interpretação dos resultados e na eficiência computacional, tornando-o uma escolha robusta para tarefas de classificação em que a relação entre as variáveis de entrada (no caso, as palavras) e a classe (spam ou não) é linear. Além disso, o modelo pode ser facilmente ajustado e interpretado para análises de coeficientes e probabilidades de previsão.

O Random Forest é um modelo baseado em árvores de decisão e utiliza o conceito de ensemble, ou seja, combina múltiplos classificadores (árvores) para melhorar a robustez e precisão do modelo. Ele é particularmente eficaz em lidar com dados de alta dimensionalidade e em capturar relações não-lineares entre as variáveis, o que pode ser crucial em problemas de texto, onde as interações entre palavras e suas combinações podem ser complexas e não-lineares.

Para treinar os modelos, foram testadas duas abordagens:

  1. Abordagem 1: Utiliza apenas a coluna Full_Text do dataset, que contém os textos completos dos emails, como variável de entrada (X). Este é um modelo mais simples e serve como base para avaliar o desempenho dos modelos sem considerar características adicionais.
  2. Abordagem 2: Considera tanto a coluna Full_Text quanto a variável Word_Count, que representa a contagem de palavras em cada mensagem. A inclusão dessa variável adicional permite que o modelo capture informações complementares que podem ser úteis para a classificação, como a quantidade de palavras em uma mensagem, que pode ser um indicador de características de spam (por exemplo, mensagens muito longas ou curtas).

Pré-processamento e Divisão dos Dados

Antes de treinar os modelos, os dados foram divididos em conjuntos de treinamento e teste, utilizando a função train_test_split da biblioteca scikit-learn. A divisão foi feita com os seguintes parâmetros:

  • X_text = A coluna do Full_Text contendo os textos dos emails
  • y = A coluna target para classificação
  • test_size = O tamanho do conjunto de testes(nesse caso, 20% dos dados)
  • random_state= o nível de embaralhamento dos dados. O 42 utilizado é um valor popularmente utilizado pela comunidade, segundo o scikit-learn.
  • stratify = mantém a estratificação dos dados ao separar os conjuntos.

Após a divisão dos dados, o próximo passo foi a vetorização do texto. Para isso, foi utilizado o TfidfVectorizer, uma técnica de transformação de texto que converte os dados textuais em uma representação numérica. O TF-IDF (Term Frequency-Inverse Document Frequency) é uma medida que avalia a importância de uma palavra em um documento, levando em consideração tanto sua frequência no texto quanto sua raridade no corpus como um todo. Esse processo transforma o texto em um formato adequado para ser processado pelos modelos de aprendizado de máquina.

Divisão de dados para treinamento e testes com Full_Text

Divisão de dados para treinamento e testes com Full_Text

Concatenando dados para treinamento e testes com Word_Count

Concatenando dados para treinamento e testes com Word_Count

Com os conjuntos separados e vetorizados, os modelos foram instanciados. Para LogisticRegression, foram adicionados os parametros: max_iter, que define o número máximo de iterações necessárias para convergir e class_weight=’balanced’, que ajusta automaticamente os pesos das classes inversamente proporcionais às suas frequências no dataset. Isso é útil em problemas de classes desbalanceadas, como é o caso deste dataset. Para RandomForest, o class_weight também foi adicionado, somado à n_estimators, que define quantos estimadores serão treinados, e random_state, que segue o mesmo principio que o *train_test_split.*

Após instanciar os modelos, ambos foram treinados utilizando o método fit() da biblioteca scikit-learn, aplicando o conjunto de treinamento. As métricas de desempenho foram analisadas com base na precisão da classificação dos rótulos "não-spam" e "spam".

Métricas utilizando Full_Text

Métricas utilizando Full_Text

Métricas utilizando Full_Text e Word_Count

Métricas utilizando Full_Text e Word_Count

Ambos os modelos, LogisticRegression e RandomForest, tiveram um bom desempenho para identificação de não-spam, atingindo métricas acima de 98%. Todavia, quando observamos a identificação de spams, percebe-se que o RandomForest teve dificuldades, principalmente na identificação de spams, apresentando uma porcentagem significativa de falsos negativos, 80%, o que explicaria o baixo f1-score na classificação de spams em relação aos não-spams. Esse comportamento pôde ser observado nos dois modelos com features diferentes, sendo o primeiro considerando apenas o texto completo recebido (Full_Text) e o segundo considerando o Full_Text e a quantidade total de palavras (Word_Count).

F1-score e Impacto dos Falsos Negativos

A métrica F1-score é uma média harmônica entre precisão (quanto o modelo acertou entre as previsões positivas) e recall (quantos casos positivos o modelo conseguiu identificar). Para o problema de spam, recall tem uma importância crucial, pois indica a capacidade do modelo de identificar todas as mensagens de spam, evitando que elas passem despercebidas. A alta taxa de falsos negativos no modelo RandomForest resultou diretamente em um F1-score baixo para a classe de spam, pois isso indica que o modelo está errando ao não identificar corretamente as mensagens de spam.

Esse comportamento comprometeu a performance do modelo, pois, apesar de ser muito bom em identificar as mensagens comuns (não-spam), o modelo não conseguiu identificar adequadamente as mensagens de spam. Isso é uma grande preocupação em problemas de classificação de spam, pois uma taxa alta de falsos negativos significa que o filtro de spam está permitindo que muitos emails indesejados cheguem à caixa de entrada.

O desempenho observado no modelo LogisticRegression, por outro lado, foi mais equilibrado, mostrando uma performance razoavelmente alta tanto para a classe não-spam quanto para spam. A escolha do Logistic Regression é frequentemente vantajosa em tarefas de classificação binária devido à sua simplicidade, facilidade de interpretação e eficiência computacional. No entanto, RandomForest demonstrou uma sensibilidade maior a padrões não-lineares no texto, o que poderia ser um ponto positivo em modelos mais complexos, mas a alta taxa de falsos negativos sugere que o modelo não estava lidando bem com os dados desbalanceados, que é uma característica do dataset fornecido.


메타데이터
post_id
a4fd686e85ff
slug
criando-um-classificador-de-spam-usando-ml-a4fd686e85ff
url
https://medium.com/@anacv.reinert/criando-um-classificador-de-spam-usando-ml-a4fd686e85ff
canonical_url
https://medium.com/@anacv.reinert/criando-um-classificador-de-spam-usando-ml-a4fd686e85ff
author_url
https://medium.com/@anacv.reinert
status
ok
fetched_at
2026-07-27 15:36:22