Pipeline de Classificação de Propostas
A plataforma do Brasil Participativo é uma nova aplicação digital do governo federal, software livre Decidim e um espaço para que a população possa contribuir na elaboração e votação de propostas para melhorar o serviço e definição de políticas públicas.
Por ser uma plataforma recente, ela não possui indexação automática das propostas. Isto é, não possui uma forma de categorização automatizada das propostas e o volume de propostas cadastradas até então já despende um esforço humano manual considerável. Então, o foco deste projeto é fazer uma análise das propostas por meio de processamento de linguagem natural (PLN) e automatizar esse processo de categorização.
A partir disso, o modelo MVP (produto com mínima viabilidade) definido junto com o cliente é um sistema classificador/categorizador das propostas publicadas na plataforma, sendo as categorias baseadas nos Ministérios do Brasil (cerca de 40 categorias). Uma outra forma de categorização, baseada no tópico principal da proposta, também foi considerado e ainda é um trabalho em progresso. Além disso, foi cogitado também a adição complementar de um classificador de discurso de ódio ao pipeline do sistema, que também está em discussão.
Mais detalhes sobre o MVP do sistema abaixo.
Até então, se trata de um projeto MVP, ou seja, primeira versão de modelo. Conforme a finalização dos procedimentos no repositório do GitHub, as novas informações estão sendo incrementadas ao documento. Ao final, o Notebook completo entrará como documento oficial do pipeline.
No decorrer do desenvolvimento do projeto, foi percebido que o processo de desenvolvimento segue um pipeline especifico, que passa em todas essas etapas. Também foi definido uma arquitetura, ambos podem ser vistos logo abaixo:
from IPython.display import Image
Image(filename= 'assets/img/pipeline.jpg')
Image(filename= 'assets/img/arquitetura.jpg')
Image(filename= 'assets/img/arquitetura2.jpg')
A primeira etapa para o desenvolvimento do projeto é a extração dos dados. Nesta etapa é onde os dados das propostas são baixados da plataforma e o foco é garantir que os dados vazios ou repetidos não sejam repassados para próxima etapa.
A elaboração de novas propostas foram encerradas temporariamente no dia 16 de julho de 2023, junto com o Plano Plurianual (PPA) para o planejamento orçamentário do governo, delimitando uma base de dados de 8438 propostas. Apesar da quantia ser considerável, ainda é pequena para o treinamento de um algoritmo classificador de propostas, dada a quantidade de categorias existentes e tendo em vista a desproporção de propostas por categoria.
Os dados estão disponibilizados no rodapé do site, conforme a imagem abaixo, no link "Baixar arquivos de Dados Abertos". Os arquivos com os dados das propostas (em formato csv
) estão compactados e necessitam ser extraídos.
Image(filename= 'assets/img/brasilParticipativo.png')
Importação das bibliotecas
import pandas as pd
import os, re, io
import requests
import zipfile
Essas serão algumas das bibliotecas utilizadas no algoritmo. Dentre delas, o uso da biblioteca Pandas responsável por manipular os dados em um DataFrame e remover as colunas e linhas vazias.
As outra bibliotecas são usadas como recursos que serão esclarecidos no decorrer do pipeline.
Baixando dataset
# Requisição ao site através do link para receber o dataset
url = "https://brasilparticipativo.presidencia.gov.br/open-data/download"
res = requests.get(url)
if res.status_code == 200:
print("Acesso realizado com sucesso!")
else:
print("Falha no acesso ao arquivo zip!")
Acesso realizado com sucesso!
A biblioteca Requests é utilizada para realizar uma requisição GET no link e receber o arquivo zip
.
Extração do arquivo
# Extrai o arquivo zip do brasil participativo
with zipfile.ZipFile(io.BytesIO(res.content), 'r') as zip_ref:
for arquivo in zip_ref.namelist():
if arquivo.endswith('brasilparticipativo.presidencia.gov.br-open-data-proposals.csv'):
zip_ref.extract(arquivo, 'data_extraction')
break
O zip
contém vários arquivos, porém busca-se extrair o csv
que contém os dados das propostas e categorias com a biblioteca ZipFile.
Leitura das propostas
# Lê as propostas com o Pandas
propostas = pd.read_csv("data_extraction/brasilparticipativo.presidencia.gov.br-open-data-proposals.csv", delimiter=";")
propostas # Impressão do DataFrame de propostas
id | category/id | category/name/pt-BR | scope/id | scope/name/en | scope/name/pt-BR | participatory_space/id | participatory_space/url | component/id | title/pt-BR | ... | attachments | followers | published_at | url | meeting_urls | related_proposals | is_amend | original_proposal/title | original_proposal/url | category/name/en | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 30.0 | Turismo | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 1 | Turismo: esse é o Destino | ... | 1 | 1 | 2023-05-10 10:03:41 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
1 | 8 | 31.0 | Desenvolvimento Agrário e Agricultura Familiar | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 1 | Agricultura Familiar e Agroecologia | ... | 1 | 0 | 2023-05-10 16:22:51 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
2 | 9 | 1.0 | Agricultura e Pecuária | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 1 | Agropecuária Sustentável | ... | 1 | 0 | 2023-05-10 16:35:47 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
3 | 10 | 27.0 | Saúde | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 1 | Atenção Primária à Saúde | ... | 1 | 513 | 2023-05-10 16:42:43 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
4 | 11 | 27.0 | Saúde | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 1 | Atenção Especializada à Saúde | ... | 1 | 383 | 2023-05-10 16:41:01 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
8433 | 11609 | 27.0 | Saúde | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 2 | Microchipagem de animais de companhia | ... | 0 | 1 | 2023-07-16 21:54:47 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
8434 | 11611 | 43.0 | Secretaria Geral da Presidência da República | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 2 | Direito a atendimento eficiente, é um direito... | ... | 0 | 3 | 2023-07-16 21:58:19 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
8435 | 11612 | 25.0 | Previdência Social | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 2 | Rever Reforma da Previdência | ... | 0 | 2 | 2023-07-16 22:02:00 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
8436 | 11613 | 16.0 | Meio Ambiente e Mudança do Clima | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 2 | Preservação da Serra do Espinhaço | ... | 0 | 1 | 2023-07-16 22:04:10 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
8437 | 11619 | 27.0 | Saúde | NaN | NaN | NaN | 1 | http://brasilparticipativo.presidencia.gov.br/... | 2 | Piso salarial pra todos. | ... | 0 | 3 | 2023-07-16 22:06:49 -0300 | http://brasilparticipativo.presidencia.gov.br/... | NaN | NaN | False | NaN | NaN | NaN |
8438 rows × 32 columns
O arquivo csv
precisa ser lido e, para isso, utiliza-se o pandas com a função read_csv
.
Perceba que existem muitos dados nulos (NaN
) e muitas colunas desnecessárias para o treinamento do modelo.
Limpeza e seleção das colunas
# Seleciona apenas as colunas importantes
propostas = pd.DataFrame(propostas, columns=['category/name/pt-BR','title/pt-BR','body/pt-BR'])
propostas.rename(columns={'category/name/pt-BR': 'Categoria', 'title/pt-BR': 'Título', 'body/pt-BR': 'Corpo'}, inplace=True)
# Retira as linhas que estao vazias
propostas = propostas.dropna()
propostas.drop(propostas[propostas['Título'] == 'Tema '].index, inplace=True) # Removendo coluna "Tema "
# Une as colunas de Título e Corpo em uma única coluna e exclui as antigas
propostas['Texto'] = propostas['Título'] + '. ' + propostas['Corpo']
propostas = propostas[['Categoria', 'Texto']]
propostas.head() # Impressão das cinco primeiras linhas de propostas
Categoria | Texto | |
---|---|---|
0 | Turismo | Turismo: esse é o Destino. <p><strong>Objetivo... |
1 | Desenvolvimento Agrário e Agricultura Familiar | Agricultura Familiar e Agroecologia. <p><stron... |
2 | Agricultura e Pecuária | Agropecuária Sustentável. <p>Objetivo: Contrib... |
3 | Saúde | Atenção Primária à Saúde. <p>Fortalecer a Aten... |
4 | Saúde | Atenção Especializada à Saúde. <p>Ampliar o ac... |
Os dados contidos na variável “propostas” são então tratados para conter apenas as informações necessárias para classificação das propostas. Apenas três colunas são necessárias: o título da proposta, mesclada com o corpo (descrição da proposta); e a categoria à qual ela pertence. Por fim, é realizado a limpeza das linhas com valores em branco.
print(f'Após a remoção dos valores nulos, sobraram {len(propostas)} propostas.')
Após a remoção dos valores nulos, sobraram 8436 propostas.
Podemos vizualizar a distribuição das propostas em suas categorias no gráfico abaixo. Perceba que a quantidade de propostas por categoria é desproporcionalmente distribuído; isso é natural de ocorrer. Todavia, para treinamento de um classificador, é necessário uma boa quantidade de dados. Logo, as categorias com menos dados não terão uma performance muito boa.
# Biblioteca pra visualização de gráficos
import seaborn as sns
sns.set(font_scale=1.0) # Definindo tamanho da fonte
counts = propostas.groupby(["Categoria"])["Categoria"].count() # Contando valores por categoria
counts.sort_values(ascending=False, inplace=True) # Ordenando valores
ax = sns.barplot(y=counts.index, x=counts) # Criando gráfico
ax.set(xlabel='Quantidade de propostas', ylabel='Categorias') # Labels do gráfico
for i in ax.containers: # Valores das barras
ax.bar_label(i,)
ax.figure.set_size_inches(10, 10) # Definição do tamanho do gráfico
Criação do arquivo com as alterações e remoção do anterior
# Retorna o novo csv limpo
propostas.to_csv('data_extraction/propostas.csv', index=False)
# Remove o csv antigo
os.remove('data_extraction/brasilparticipativo.presidencia.gov.br-open-data-proposals.csv')
Por último, cria-se um novo arquivo csv
com as alterações feitas e o arquivo original é removido. O novo arquivo criado parte para a etapa de pré-processamento dos dados.
Nesta etapa de pré-processamento, o objetivo é manipular as informações importantes para que o algoritmo de Machine Learning consiga entender e ser treinado. Algoritmos de Machine Learning não entendem dados textuais (não estruturados) e necessitam que eles sejam estruturados de forma que possam ser utilizados para treinamento. Para isso, os dados textuais serão mais uma vez processados, desta vez para gerar uma representação vetorial, conhecido como Word Vector ou Word Embedding.
Além disso, nesta etapa, os dados serão trabalhados para otimização do modelo. Retirando palavras do texto que possuam baixo valor semântico para o processo de aprendizado de máquina. Essa limpeza é necessária para aumentar a precisão dos resultados do modelo treinado.
Um Word Vector (aqui também) é nada mais do que uma vetor numérico de pesos ou ocorrências que busca aproximar o significado de uma palavra dado o vocabulário ao qual ele pertence e o contexto no qual ele é utilizado. Existem várias técnicas para isso (Bag of Words, Word2Vec, TF-IDF, GloVe, ...) e geralmente se utiliza de conceitos comuns em Machine Learning como Redes Neurais e Redução de Dimensionalidade na matriz de coocorrência de palavras, ou então modelos probabilísticos, métodos de base de conhecimento explicável até mesmo uma representação explícita com base no contexto em que a palavra aparece.
No caso do spaCy, o modelo de pipeline em português possui a capacidade de representação vetorial a partir do modelo médio (pt_core_news_md
), com o uso da técnica GloVe. A quantidade de chaves e vetores únicos diferem para cada pipeline do spaCy e, caso alguma palavra não se encontre no vocabulário, é atribuído a ela um vetor com valores nulos. Os vetores das palavras possuem, por sua vez, um tamanho fixo 300 de dimensionalidade para todos os pipelines (cada dimensão inferindo uma informação contextual na qual tal palavra é utilizada). Além disso, é possível obter o vetor de uma sentença a partir da média dos valores de cada vetor de palavra contido nela. Mais detalhes podem ser encontrados na documentação (aqui, aqui e aqui)
Definições iniciais
Nas definições iniciais, é declarado duas variáveis para expressões regulares e é instanciado o pipeline em português da biblioteca spaCy. Essas definições serão utilizadas na função de pré-processamento.
# Expressões regulares para remoção de poluição de dados nos textos
# REGX_URL = r"https?://[A-Za-z0-9./\-]+" # Regex for URLs
REGX_HTML = r"<[^<]+?>" # Regex for HTML tags
REGX_ENDING = r'Órgão Responsável:.+' # Regex for the part of the text
import spacy
from spacy.lang.pt.stop_words import STOP_WORDS
# Instanciando o pipeline em português do spaCy
# nlp = spacy.blank('pt') # Pipeline vazio para teste (apenas tokenização)
# nlp = spacy.load('pt_core_news_sm') # Pipeline pequeno (para eficiência), não possui vector (?)
# nlp = spacy.load('pt_core_news_md') # Pipeline médio, com vector (500k keys, 20k unique vectors, 300 dimensions)
nlp = spacy.load('pt_core_news_lg') # Pipeline grande (para precisão), com vector (500k keys, 500k unique vectors, 300 dimensions)
O spaCy é uma biblioteca gratuita e open source para Processamento de Linguagem Natural avançado em Python. Foi especificamente projetada para uso em produção e para auxiliar na criação de aplicações que processam e "entendem" grandes volumes de texto. Ela pode ser usada para realizar a extração de informações, construir sistemas de compreensão de linguagem natural ou para pré-processar texto para modelos de Machine Learning e Deep Learning.
Pré-processamento
Primeiro, é preciso indexar cada uma das categorias. Isto é, atribuir um valor númerico que identifique unicamente cada uma das categorias existentes.
#Criando índices para as categorias
cats = propostas['Categoria'].unique() # Pegando cada categoria única
cats = dict(enumerate(cats, 0)) # Convertendo para dict (com índices enumerados)
cats = {v:k for k,v in cats.items()} # Trocando chaves e valores
propostas['id_cats'] = propostas['Categoria'].map(cats) # Inserindo coluna de índices das cats
propostas.head()
Categoria | Texto | id_cats | |
---|---|---|---|
0 | Turismo | Turismo: esse é o Destino. <p><strong>Objetivo... | 0 |
1 | Desenvolvimento Agrário e Agricultura Familiar | Agricultura Familiar e Agroecologia. <p><stron... | 1 |
2 | Agricultura e Pecuária | Agropecuária Sustentável. <p>Objetivo: Contrib... | 2 |
3 | Saúde | Atenção Primária à Saúde. <p>Fortalecer a Aten... | 3 |
4 | Saúde | Atenção Especializada à Saúde. <p>Ampliar o ac... | 3 |
Em seguida, como pode-se perceber na coluna de texto, existem informações de tags HTML e informações ao final do texto que fogem do conteúdo das propostas. Para limpar essas informações, é definido uma função "preprocessing" que aplicará as modificações desejadas em cada linha do DataFrame.
# Para visualização do conteúdo da coluna "Texto" em uma proposta
print("ANTES DO PRÉ-PROCESSAMENTO")
print(f'{propostas["Texto"].iloc[0]}')
ANTES DO PRÉ-PROCESSAMENTO Turismo: esse é o Destino. <p><strong>Objetivo:</strong> Posicionar o turismo como vetor de desenvolvimento sustentável e aumentar a competitividade dos destinos e produtos turísticos brasileiros, democratizando o acesso à atividade turística aos cidadãos brasileiros.<br><br><strong>Órgão Responsável:</strong> Ministério do Turismo<br><br><strong>Página Oficial: </strong>https://www.gov.br/turismo/pt-br/</p>
# Função para pré-processamento
def preprocessing(text):
# text = text.lower()
text = re.sub(REGX_HTML, '', text) # Removendo tags HTML
# text = re.sub(REGX_URL, '', text) # Revomendo URLs
text = re.sub(REGX_ENDING, '', text)
# tokens = [t.lemma_ for t in nlp(text) if t not in STOP_WORDS and not t.is_punct]
return text
# Ajustando textos da coluna Corpo
propostas['Texto'] = propostas['Texto'].apply(preprocessing)
propostas.head()
Categoria | Texto | id_cats | |
---|---|---|---|
0 | Turismo | Turismo: esse é o Destino. Objetivo: Posiciona... | 0 |
1 | Desenvolvimento Agrário e Agricultura Familiar | Agricultura Familiar e Agroecologia. Objetivo:... | 1 |
2 | Agricultura e Pecuária | Agropecuária Sustentável. Objetivo: Contribuir... | 2 |
3 | Saúde | Atenção Primária à Saúde. Fortalecer a Atenção... | 3 |
4 | Saúde | Atenção Especializada à Saúde. Ampliar o acess... | 3 |
# Agora com o texto filtrado
print("DEPOIS DO PRÉ-PROCESSAMENTO")
print(f'{propostas["Texto"].iloc[0]}')
DEPOIS DO PRÉ-PROCESSAMENTO Turismo: esse é o Destino. Objetivo: Posicionar o turismo como vetor de desenvolvimento sustentável e aumentar a competitividade dos destinos e produtos turísticos brasileiros, democratizando o acesso à atividade turística aos cidadãos brasileiros.
Enfim, criamos uma representação vetorial do texto a partir da biblioteca spaCy e guardamos os vetores em uma nova coluna chamada "Vector".
# Representações vetoriais do texto
propostas['Vector'] = propostas['Texto'].apply(lambda text: nlp(text).vector)
propostas.head()
Categoria | Texto | id_cats | Vector | |
---|---|---|---|---|
0 | Turismo | Turismo: esse é o Destino. Objetivo: Posiciona... | 0 | [0.85847634, -0.37545732, 0.07612456, -0.90006... |
1 | Desenvolvimento Agrário e Agricultura Familiar | Agricultura Familiar e Agroecologia. Objetivo:... | 1 | [0.4189733, -1.3273121, -0.14920743, -1.345308... |
2 | Agricultura e Pecuária | Agropecuária Sustentável. Objetivo: Contribuir... | 2 | [1.0754207, -0.7153076, -0.023167895, -1.01495... |
3 | Saúde | Atenção Primária à Saúde. Fortalecer a Atenção... | 3 | [0.68205523, -0.8182749, -0.11555423, -1.60916... |
4 | Saúde | Atenção Especializada à Saúde. Ampliar o acess... | 3 | [0.7731693, -0.5005779, 0.4960923, -0.6091388,... |
Ao final do pré-processamento, nota-se que algumas alterações foram feitas na tabela, entre elas, a remoção de tags HTML e textos com baixo valor semântico. Houve também a criação de duas novas colunas “id_cats”, responsável por enumerar os índices de categorias (do 0 ao 39) e a coluna “Vector” que representa a vetorização das sentenças.
Com os dados processados, é possível então treinar um algoritmo de Machine Learning para classificação.
O modelo ML utilizado neste MVP para classificação de propostas foi determinado através de um benchmark de vários algoritmos. Os algoritmos foram avaliados com base na sua precisão e tempo de treinamento, duas métricas para examinar qual modelo se adequa às necessidades e exigências do cliente. Dos algoritmos estudados, a maioria foi implementado com a biblioteca do Scikit-Learn, com exceção de dois. Os algoritmos são os seguintes:
- Random Forest
- Nearest Neighbors
- Support Vector Machine
- Multinomial Naive Bayes
- Rede Neural Profunda do Tensorflow
- TextCat do spaCy
Desta forma, optou-se por utilizar Support Vector Machine (SVM) por exibir melhor precisão e menor tempo de treinamento. O benchmark foi realizado em um dataset diferente (com mais dados para treinamento, mesmo assim, com o formato de dados semelhantes com os dados das propostas) e posteriormente no dataset de propostas do Brasil Participativo.
Importando bibliotecas
A biblioteca do Scikit-Learn possui vários módulos úteis para realização do treinamento de um algoritmo de Machine Learning, como funções para avaliar a precisão, formas de otimização do algoritmo. pré-processamento e outros além do próprio algoritmo utilizado.
from sklearn.metrics import accuracy_score, classification_report
from sklearn.preprocessing import MinMaxScaler
from sklearn import svm, model_selection
import numpy as np
Configurações do modelo
Primeiramente, os dados das propostas (colunas "Vector" e "id_cats") são parametrizados pelo Scikit-Learn. O dataset é embaralhado e dividido em dados para treinamento e testem, sendo 70% utilizado para treinamento e 30% para teste, e em seguida, são aplicadas duas transformações.
# Separação dos dados para treinamento e teste
X_train, X_test, Y_train, Y_test = model_selection.train_test_split(
propostas.Vector.values,
propostas.Categoria,
test_size=0.3
)
# Transformação: conversão do tipo dos dados de entrada (X) para interpretação
X_train_2d = np.stack(X_train)
X_test_2d = np.stack(X_test)
# Transformação: escalonamento dos dados numéricos (normalizados entre 0 e 1)
# Para tratamento de casos outliers
scaler = MinMaxScaler()
scaled_train_embed = scaler.fit_transform(X_train_2d)
scaled_test_embed = scaler.transform(X_test_2d)
Agora, o modelo é instanciado, realizado o treinamento com a função fit
. Em seguida, os dados de teste são utilizados para imprimir um relatório com a performance em cada categoria e sua perfomance geral. O relatório também pode ser encontrado em assets/training_results
.
import json
# Treinamento
clf = svm.SVC() # Instanciando modelo
clf.fit(scaled_train_embed, Y_train) # Realização do treinamento passando os dados
Y_pred = clf.predict(scaled_test_embed) # Predição com os dados de teste
# Salvando relatório de treinamento em um arquivo JSON
json_obj = json.dumps(classification_report(Y_test, Y_pred, output_dict=True), indent=4)
save_file = open('model/training_results.json', 'w').write(json_obj)
# Impressão do relatório com as métricas do modelo (para cada cat.)
print(classification_report(Y_test, Y_pred))
predictions_SVM = clf.predict(scaled_test_embed)
print("SVM Accuracy Score -> ", accuracy_score(predictions_SVM, Y_test)*100) # Impressão da precisão geral
/home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) /home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) /home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) /home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) /home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) /home/alexandre/Documents/Brisa/07_ProcessamentoLinguagemNatural/env/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result))
precision recall f1-score support Advocacia-Geral da União 0.00 0.00 0.00 6 Agricultura e Pecuária 0.00 0.00 0.00 33 Banco Central 0.00 0.00 0.00 7 Casa Civil 0.00 0.00 0.00 9 Cidades 0.39 0.27 0.32 90 Ciência, Tecnologia e Inovação 0.53 0.18 0.26 51 Comunicação Social 0.00 0.00 0.00 16 Comunicações 0.00 0.00 0.00 10 Controladoria-Geral da União 0.00 0.00 0.00 7 Cultura 0.57 0.47 0.51 62 Defesa 0.00 0.00 0.00 19 Desenvolvimento Agrário e Agricultura Familiar 0.46 0.44 0.45 84 Desenvolvimento e Assistência Social, Família e Combate à Fome 0.31 0.19 0.24 137 Desenvolvimento, Indústria, Comércio e Serviços 0.00 0.00 0.00 38 Direitos Humanos e Cidadania 0.24 0.44 0.31 160 Educação 0.58 0.85 0.69 382 Esporte 0.33 0.05 0.09 20 Fazenda 0.39 0.45 0.42 33 Gestão e Inovação em Serviços Públicos 0.35 0.33 0.34 84 Igualdade Racial 0.00 0.00 0.00 28 Integração e Desenvolvimento Regional 0.00 0.00 0.00 31 Justiça e Segurança Pública 0.64 0.49 0.55 113 Meio Ambiente e Mudança do Clima 0.49 0.75 0.59 156 Minas e Energia 1.00 0.05 0.10 38 Mulheres 0.50 0.11 0.18 63 Outros 0.00 0.00 0.00 8 Pesca e Aquicultura 0.00 0.00 0.00 10 Planejamento e Orçamento 0.00 0.00 0.00 34 Portos e Aeroportos 0.00 0.00 0.00 7 Povos Indígenas 0.00 0.00 0.00 12 Previdência Social 0.61 0.27 0.37 83 Relações Exteriores 0.00 0.00 0.00 4 Relações Institucionais 0.00 0.00 0.00 11 Saúde 0.54 0.83 0.66 372 Secretaria Geral da Presidência da República 0.00 0.00 0.00 21 Segurança Institucional 0.00 0.00 0.00 7 Trabalho e Emprego 0.41 0.52 0.46 98 Transportes 0.59 0.70 0.64 154 Turismo 0.90 0.27 0.42 33 accuracy 0.49 2531 macro avg 0.25 0.20 0.19 2531 weighted avg 0.44 0.49 0.44 2531 SVM Accuracy Score -> 49.150533386013436
Algumas observações a se destacar é que o modelo MVP ainda não atinge uma performance alta o suficiente para utilização em produção. A precisão geral (accuracy score) do algoritmo é de 49%, em certas categorias atinge uma métrica satisfatória acima dos 85%, porém em outras não consegue inferir corretamente se alguma proposta pertence a tal categoria.
Isso se deve primeiramente pelo pouco volume de dados presentes no dataset do Brasil Participativo. As categorias com menos propostas não possuem uma quantia suficiente para treinar e avaliar a performance do algoritmo.
Apesar disso, algumas otimizações estão previstas para serem realizadas no algoritmo. Dentre elas, a utilização de diferentes técnicas de pré-processamento e o emprego de um outro algoritmo que necessite poucos dados para treinamento serão os focos para otimização do MVP.
Com o modelo treinado, é possível então carregá-lo em um arquivo serializado para persistência e utilização futura, acoplado a um outro sistema por exemplo.
Existem várias formas de serializar um objeto Python (o modelo MVP treinado, no caso). As formas mais comuns de serem utilizadas é através das bibliotecas Pickle (nativa do Python) ou Joblib (que utiliza a anterior). Detalhes para a serialização com elas podem ser vistas aqui. Apesar de serem as mais comuns, o Scikit-Learn aponta algumas limitações de segurança e mantenibilidade e por conta disso, será utilizado a biblioteca Skops, mais recomendada.
O Skops fornece um formato mais seguro, evitando o uso de Pickle e carregando apenas arquivos que possuem tipos e referências a funções nas quais são confiáveis por padrão ou pelo desenvolvedor Além disso, o seu uso é muito semelhante ao uso do Pickle.
Além desta forma de reuso e utilização do modelo, está em avaliação e desenvolvimento uma forma de integração do modelo treinado com uma aplicação Ruby on Rails, visto que o sistema do Brasil Participativo é implementado com essa tecnologia.
Serialização
Para a serialização, é utilizado a função dump
passando o modelo treinado e o caminho junto com o nome do arquivo onde será salvo o modelo serializado.
import skops.io as sio
sio.dump(clf, "model/trained_mvp.skops")
Carregamento
Para recarregar o modelo em algum sistema, utiliza-se a função load
ou loads
. Antes disso, é preciso especificar os tipos confiáveis ou então carregar o modelo passando o parâmetro trusted=True
.
# Carrega modelo avaliando os tipos confiáveis
unknown_types = sio.get_untrusted_types(file="model/trained_mvp.skops")
# CHECAR TIPOS NÃO CONFIÁVEIS
clf = sio.load("model/trained_mvp.skops", trusted=unknown_types)
# Carrega modelo sem checar os tipos de dados confiáveis
clf = sio.load("model/trained_mvp.skops", trusted=True)
Data | Autor | Modificações | Versão |
---|---|---|---|
15/08/2023 | Alexandre Oliveira | Primeira versão | 1.0 |
Criada: 2023-08-08