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…
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

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

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”.
- 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
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
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.
- 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

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

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

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:
- 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.
- 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

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 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