{"cells":[{"cell_type":"markdown","metadata":{"id":"U7VwWU5BX9-w"},"source":["## **Trabalho Prático Final de NLP**\n","### **Etapa 1: Download e Pré-processamento da Legislação**\n","**Professores:** André Carvalho e Altigran da Silva\n","\n","**Alunos:**\n","\n","Bianka Vasconcelos\n","\n","Girlana Souza\n","\n","Ricardo Bonfim\n","\n","**Descrição do Trabalho:** Este trabalho consiste em desenvolver uma LLM que responda perguntas sobre a legislação acadêmica da UFAM. O trabalho envolve o download e pré-processamento dos documentos da legislação, a criação de uma base de dados sintética de instruções, o treinamento do modelo usando técnicas de LoRA/QLoRA, e a implementação de um sistema de RAG (Retrieval-Augmented Generation) para fornecer respostas precisas baseadas nos documentos da legislação."]},{"cell_type":"markdown","metadata":{"id":"ppxU0wWS1HLv"},"source":["### **Objetivo:** Neste notebook, iremos fazer a extração dos textos dos documentos PDF's disponibilizados em: https://proeg.ufam.edu.br/normas-academicas/57-proeg/146-legislacao-e-normas.html. Com isso, poderemos usar o texto desses documentos para criar a base sintética de instruções, e dessa forma, será possível fazer o fine tuning."]},{"cell_type":"markdown","metadata":{"id":"OwE88M73YHiG"},"source":["### **Configurando o ambiente**\n","\n","Primeiro, vamos começar com algumas instalações e importações básicas para configurar o ambiente."]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"IeL39lZ7dlQP","outputId":"2f4f3c12-150b-48e3-a263-3cee8caeba92"},"outputs":[{"name":"stdout","output_type":"stream","text":["Collecting pytesseract\n"," Downloading pytesseract-0.3.10-py3-none-any.whl.metadata (11 kB)\n","Requirement already satisfied: packaging>=21.3 in /usr/local/lib/python3.10/dist-packages (from pytesseract) (24.1)\n","Requirement already satisfied: Pillow>=8.0.0 in /usr/local/lib/python3.10/dist-packages (from pytesseract) (9.4.0)\n","Downloading pytesseract-0.3.10-py3-none-any.whl (14 kB)\n","Installing collected packages: pytesseract\n","Successfully installed pytesseract-0.3.10\n","Collecting pdf2image\n"," Downloading pdf2image-1.17.0-py3-none-any.whl.metadata (6.2 kB)\n","Requirement already satisfied: pillow in /usr/local/lib/python3.10/dist-packages (from pdf2image) (9.4.0)\n","Downloading pdf2image-1.17.0-py3-none-any.whl (11 kB)\n","Installing collected packages: pdf2image\n","Successfully installed pdf2image-1.17.0\n","Requirement already satisfied: pillow in /usr/local/lib/python3.10/dist-packages (9.4.0)\n","Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (2.31.0)\n","Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests) (3.3.2)\n","Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests) (3.7)\n","Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests) (2.0.7)\n","Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests) (2024.7.4)\n","\u001b[31mERROR: Could not find a version that satisfies the requirement tempfile (from versions: none)\u001b[0m\u001b[31m\n","\u001b[0m\u001b[31mERROR: No matching distribution found for tempfile\u001b[0m\u001b[31m\n","\u001b[0mCollecting tdqm\n"," Downloading tdqm-0.0.1.tar.gz (1.4 kB)\n"," Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n","Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from tdqm) (4.66.4)\n","Building wheels for collected packages: tdqm\n"," Building wheel for tdqm (setup.py) ... \u001b[?25l\u001b[?25hdone\n"," Created wheel for tdqm: filename=tdqm-0.0.1-py3-none-any.whl size=1321 sha256=eda8ad197c3c248d5e5a74dbbf56afffb8060b3c43d35d962221e07eba35cf54\n"," Stored in directory: /root/.cache/pip/wheels/37/31/b8/7b711038035720ba0df14376af06e5e76b9bd61759c861ad92\n","Successfully built tdqm\n","Installing collected packages: tdqm\n","Successfully installed tdqm-0.0.1\n","Reading package lists... Done\n","Building dependency tree... Done\n","Reading state information... Done\n","The following NEW packages will be installed:\n"," poppler-utils\n","0 upgraded, 1 newly installed, 0 to remove and 45 not upgraded.\n","Need to get 186 kB of archives.\n","After this operation, 696 kB of additional disk space will be used.\n","Get:1 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 poppler-utils amd64 22.02.0-2ubuntu0.5 [186 kB]\n","Fetched 186 kB in 1s (185 kB/s)\n","Selecting previously unselected package poppler-utils.\n","(Reading database ... 123598 files and directories currently installed.)\n","Preparing to unpack .../poppler-utils_22.02.0-2ubuntu0.5_amd64.deb ...\n","Unpacking poppler-utils (22.02.0-2ubuntu0.5) ...\n","Setting up poppler-utils (22.02.0-2ubuntu0.5) ...\n","Processing triggers for man-db (2.10.2-1) ...\n","Reading package lists... Done\n","Building dependency tree... Done\n","Reading state information... Done\n","The following additional packages will be installed:\n"," tesseract-ocr-eng tesseract-ocr-osd\n","The following NEW packages will be installed:\n"," tesseract-ocr tesseract-ocr-eng tesseract-ocr-osd\n","0 upgraded, 3 newly installed, 0 to remove and 45 not upgraded.\n","Need to get 4,816 kB of archives.\n","After this operation, 15.6 MB of additional disk space will be used.\n","Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 tesseract-ocr-eng all 1:4.00~git30-7274cfa-1.1 [1,591 kB]\n","Get:2 http://archive.ubuntu.com/ubuntu jammy/universe amd64 tesseract-ocr-osd all 1:4.00~git30-7274cfa-1.1 [2,990 kB]\n","Get:3 http://archive.ubuntu.com/ubuntu jammy/universe amd64 tesseract-ocr amd64 4.1.1-2.1build1 [236 kB]\n","Fetched 4,816 kB in 2s (2,869 kB/s)\n","Selecting previously unselected package tesseract-ocr-eng.\n","(Reading database ... 123628 files and directories currently installed.)\n","Preparing to unpack .../tesseract-ocr-eng_1%3a4.00~git30-7274cfa-1.1_all.deb ...\n","Unpacking tesseract-ocr-eng (1:4.00~git30-7274cfa-1.1) ...\n","Selecting previously unselected package tesseract-ocr-osd.\n","Preparing to unpack .../tesseract-ocr-osd_1%3a4.00~git30-7274cfa-1.1_all.deb ...\n","Unpacking tesseract-ocr-osd (1:4.00~git30-7274cfa-1.1) ...\n","Selecting previously unselected package tesseract-ocr.\n","Preparing to unpack .../tesseract-ocr_4.1.1-2.1build1_amd64.deb ...\n","Unpacking tesseract-ocr (4.1.1-2.1build1) ...\n","Setting up tesseract-ocr-eng (1:4.00~git30-7274cfa-1.1) ...\n","Setting up tesseract-ocr-osd (1:4.00~git30-7274cfa-1.1) ...\n","Setting up tesseract-ocr (4.1.1-2.1build1) ...\n","Processing triggers for man-db (2.10.2-1) ...\n"]}],"source":["!pip install pytesseract\n","!pip install pdf2image\n","!pip install pillow\n","!pip install requests\n","!pip install tempfile\n","!pip install tdqm\n","!apt-get install poppler-utils\n","!apt-get install tesseract-ocr"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"utEDtdnbXeow"},"outputs":[],"source":["import os, re\n","import platform\n","import requests\n","import pytesseract\n","import urllib.request # para importar o arquivo da Tiny Shakespeare\n","import unicodedata\n","from tqdm.notebook import tqdm\n","\n","from tempfile import TemporaryDirectory\n","from pathlib import Path\n","from pdf2image import convert_from_path\n","from PIL import Image\n","from tqdm import tqdm\n","from nltk.metrics.distance import edit_distance # para calcular a distancia de levenstrein"]},{"cell_type":"markdown","metadata":{"id":"UZFDEjiidy61"},"source":["#### **Pré Processamento**\n","\n","A linguagem python é muito usada para análise de dados, mas às vezes os dados não estão em um formato conveniente para trabalhar (em formato de texto). Para resolver isso, convertemos arquivos como PDFs ou imagens para texto. Python possui várias bibliotecas para essa tarefa, como PyPDF2. No entanto, uma desvantagem é que PDFs podem usar diferentes codificações, como UTF-8 ou ASCII, o que pode causar perda de dados na conversão. Para evitar isso, podemos converter as páginas do PDF em imagens e depois usar **OCR** (Reconhecimento Óptico de Caracteres) para extrair e armazenar o texto das imagens em um arquivo de texto.\n","\n","Portanto, usaremos as ferramentas do `OCR` para extrair o texto dos documentos.\n","\n","Fonte de auxílio: https://www.geeksforgeeks.org/python-reading-contents-of-pdf-using-ocr-optical-character-recognition/"]},{"cell_type":"markdown","metadata":{"id":"08eleBTI4Gn5"},"source":["Para conseguir extrair o texto dos documentos, optamos em baixá-los localmente, deixando salvos em uma pasta do google drive. Portanto, abaixo temos alguns comandos para fazer conexão com o drive para acessar tais documentos."]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"oR6MVYPlXn7L","outputId":"a99dca34-f6a2-406f-dba8-3318d958ccb0"},"outputs":[{"name":"stdout","output_type":"stream","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"markdown","metadata":{"id":"Trzona2M4bNi"},"source":["Abaixo, vamos configurar as variáveis globais e os diretórios para acessar os arquivos."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"OSlcl42ieupe"},"outputs":[],"source":["# HOME = os.path.join(os.getcwd(), \"Documentos da base\") # roda local\n","# print(HOME)\n","HOME = \"/content/drive/MyDrive/NLP-2024-1/TP3 (Final)/Documentos da base\" # diretório principal dos documentos HOME\n","FOLDER_TEXT = os.path.join(HOME, \"Arquivos de texto\") # onde os documentos serão salvos\n","\n","if not os.path.exists(FOLDER_TEXT):\n","\tos.makedirs(FOLDER_TEXT)\n","\n","temp_pdf_path = os.path.join(HOME, \"temp.pdf\")\n","out_directory = Path(FOLDER_TEXT)"]},{"cell_type":"markdown","metadata":{"id":"97bCdbIc4nGZ"},"source":["#### **Convertendo o PDF para Imagem**\n","\n","Desenvolvemos uma função que faz a primeira etapa do OCR, convertendo um arquivo PDF para um arquivo de imagem (.jpg). Ela converte cada página de um PDF em uma imagem JPEG de alta qualidade e salva essas imagens em um diretório temporário. Com isso, gera-se uma lista com os caminhos das imagens e usamos uma barra de progresso para mostrar o andamento da conversão, com auxílio da biblioteca `tqdm`."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"4lTF7KYHh_Rj"},"outputs":[],"source":["def pdf2jpeg(tempdir, pdf_file, image_file_list):\n"," \"\"\"\n"," Etapa #1 : Convertendo PDF para imagens\n"," \"\"\"\n","\n"," pdf_pages = convert_from_path(pdf_file, 500) # páginas do pdf são convertidas\n","\n"," # percorrer todas as páginas convertidas para imagem\n"," for page_enumeration, page in enumerate(tqdm(pdf_pages, desc=\"Páginas convertidas\"), start=1):\n","\n"," # criar e salvar a imagem\n"," filename = f\"{tempdir}/page_{page_enumeration:03}.jpg\"\n","\n"," page.save(filename, \"JPEG\")\n"," image_file_list.append(filename)"]},{"cell_type":"markdown","metadata":{"id":"jz_El4Rn6GiV"},"source":["#### **Convertendo a Imagem para Texto**\n","\n","A função abaixo é responsável pela próxima etapa do OCR, que é converter o texto de uma imagem para um arquivo txt. A ideia é percorrer cada imagem e usar a biblioteca `pytesseract` para reconhecer e extrair texto de cada imagem. Durante o processamento , removemos quebras de linha causadas por palavras divididas com hífen (-) no final da linha.\n","\n","Ao fim do processo, a função adiciona o texto processado ao arquivo de saída e imprime o nome do arquivo salvo."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"F6HghUvhihKR"},"outputs":[],"source":["def jpeg2txt(file_name, image_file_list, out_directory):\n"," \"\"\"\n"," Etapa #2 - Reconhecer textos de imagem com OCR\n"," \"\"\"\n"," if \".PDF\" in file_name:\n"," text_name = file_name.replace(\".PDF\", \".txt\")\n"," elif \".pdf\" in file_name:\n"," text_name = file_name.replace(\".pdf\", \".txt\")\n"," text_file = os.path.join(out_directory, text_name)\n","\n"," with open(text_file, \"a\", encoding=\"utf-8\") as output_file:\n"," for image_file in tqdm(image_file_list, desc=\"Processando imagens\"):\n","\n"," text = str(((pytesseract.image_to_string(Image.open(image_file)))))\n","\n"," text = text.replace(\"-\\n\", \"\")\n","\n"," output_file.write(text)\n","\n"," print(\"Arquivo salvo como: \", text_name)\n"," print()"]},{"cell_type":"markdown","metadata":{"id":"9aY0qC6r7Vr_"},"source":["#### **Hora de Converter**\n","\n","A função abaixo irá chamar todas as que foram anteriormente desenvolvidas.\n","- Primeiro, ela cria uma lista de todos os arquivos no diretório HOME.\n","- Depois, para cada arquivo PDF encontrado, define-se o nome do arquivo de texto correspondente (.txt).\n","- Se o arquivo de texto ainda não existir no diretório de saída (out_directory), inicia o processamento.\n","- Cria um diretório temporário para armazenar imagens das páginas do PDF.\n","- Usa a função `pdf2jpeg` para converter o PDF em imagens JPEG e armazena os nomes das imagens na lista *image_file_list*.\n","- Usa a função `jpeg2txt` para extrair texto das imagens JPEG e salvar no arquivo de texto correspondente.\n","\n","Portanto, tal função serve para automatizar a conversão de PDFs em texto, criando imagens das páginas e depois extraindo o texto dessas imagens."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"_52ZT7phdF3m"},"outputs":[],"source":["def pdf2file():\n","\t# Seleção do PDF\n","\tcont = 0\n","\tfiles = [x for x in os.listdir(HOME) if os.path.isfile(os.path.join(HOME, x))]\n","\tfor nome_arquivo in files:\n","\n","\t\t# uma verificação adicional em caso de erro em arquivos de nome com '.PDF' (maiúsculo)\n","\t\tif \".PDF\" in nome_arquivo:\n","\t\t\ttext_name = nome_arquivo.replace(\".PDF\", \".txt\")\n","\t\telif \".pdf\" in nome_arquivo:\n","\t\t\ttext_name = nome_arquivo.replace(\".pdf\", \".txt\")\n","\n","\t\tif not os.path.exists(os.path.join(out_directory, text_name)):\n","\n","\t\t\tprint(\"#\" * 20, f'CONVERTING THE FILE: {nome_arquivo}',\"#\" * 20)\n","\t\t\t# as páginas do PDF ficam nessa lista\n","\t\t\timage_file_list = []\n","\n","\t\t\tPDF_path = os.path.join(HOME, nome_arquivo) # pega o caminho do pdf\n","\n","\t\t\t# download_pdf(PDF_url, temp_pdf_path)\n","\n","\t\t\twith TemporaryDirectory() as tempdir:\n","\t\t\t\t# cria um diretório temporário para armazenar as imagens temporárias\n","\n","\t\t\t\tpdf2jpeg(tempdir, PDF_path, image_file_list) # converte para imagem\n","\n","\t\t\t\tjpeg2txt(nome_arquivo, image_file_list, out_directory) # converte para texto\n","\n","\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"JS1AFsx5mJic"},"outputs":[],"source":["pdf2file()"]},{"cell_type":"markdown","metadata":{"id":"tpNUA469qmrb"},"source":["### **Aplicação da Distância de Levenstrein**\n","\n","A OCR, apesar de muito útil para converter os PDF's para texto, ocorrem muitos erros de codificação ao longo da tradução. Portanto, alguma técnica de correção de texto é necessária para resolver esse problema, caso contrário, o modelo treinado com o fine tunning erraria muitos termos na sua resposta.\n","\n","Encontramos um dicionário com todas as palavras da língua portuguesa, e optamos por utilizá-lo para nos auxiliar a corrigir o texto. Além disso, aplicamos o algoritmo da distância de edição, que compara duas palavras e fornece a distância léxica entre elas.\n","\n","Utilizamos como fonte de referência: https://www.google.com/url?q=https%3A%2F%2Fmedium.com%2Fproductmanagerslife%2Fa-dist%25C3%25A2ncia-de-levenshtein-usando-a-dist%25C3%25A2ncia-entre-palavras-para-encontrar-tesouros-0edf95eb5d63"]},{"cell_type":"markdown","metadata":{"id":"HkI-G8DvdZ-n"},"source":["#### **Carregando o dicionário da língua portuguesa**\n","Primeiramente, vamos carrregar o dicionário. Ele se encontra nessa url: https://www.ime.usp.br/~pf/dicios/br-utf8.txt.\n","\n","A função abaixo realiza o carregamento do dicionário, com cada linha sendo uma palavra da língua portuguesa."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"3msctxYarNei"},"outputs":[],"source":["def carrega_dict(url):\n"," # nome do arquivo que ficará salvo no ambiente local\n"," nome_arquivo = \"dicionario-pt.txt\"\n","\n"," # execução do download\n"," urllib.request.urlretrieve(url, nome_arquivo)\n","\n"," # abrindo o arquivo e salvando em 'linhas_arquivo'\n"," linhas_arquivo = open(\"dicionario-pt.txt\", 'r', encoding='utf-8').read()\n","\n"," return linhas_arquivo"]},{"cell_type":"markdown","metadata":{"id":"IoeiQg31dxVp"},"source":["Agora, passamos a URL como parâmetro para a função e salvamos as palavras na variável dicionário."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SOPH3gYhrZIc"},"outputs":[],"source":["url = \"https://www.ime.usp.br/~pf/dicios/br-utf8.txt\"\n","linhas_arquivo = carrega_dict(url) # carregando a base\n","dicionario = linhas_arquivo.split(\"\\n\") # separando as palavras"]},{"cell_type":"markdown","metadata":{"id":"vlkwUHyceTiW"},"source":["Foi necessário incluir algumas palavras específicas que não estavam no dicionário. A forma mais simples que encontramos de fazer isso foi adicionar as palavras manualmente. Segue abaixo a adição de termos extras no dicionário."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"RvyFe7wsesUx"},"outputs":[],"source":["termos_extras = [\n"," # estados\n"," \"acre\", \"alagoas\", \"amapá\", \"amazonas\", \"bahia\", \"ceará\", \"distrito federal\", \"espírito santo\",\n"," \"goiás\", \"maranhão\", \"mato grosso\", \"mato grosso do sul\", \"minas gerais\", \"pará\", \"paraíba\",\n"," \"paraná\", \"pernambuco\", \"piauí\", \"rio de janeiro\", \"rio grande do norte\", \"rio grande do sul\",\n"," \"rondônia\", \"roraima\", \"santa catarina\", \"são paulo\", \"sergipe\", \"tocantins\",\n","\n"," # cidades\n"," \"abadia de goiás\", \"abaetetuba\", \"acrelândia\", \"açu\", \"aparecida de goiânia\", \"araraquara\",\n"," \"barretos\", \"barrocas\", \"bauru\", \"belo horizonte\", \"belo jardim\", \"blumenau\", \"boa vista\",\n"," \"boa vista do sul\", \"bragança paulista\", \"campina grande\", \"campinas\", \"campo grande\",\n"," \"canoas\", \"caruaru\", \"catalão\", \"ceilândia\", \"chapecó\", \"colatina\", \"contagem\", \"cruz das almas\",\n"," \"curitiba\", \"diadema\", \"divinópolis\", \"duque de caxias\", \"eunápolis\", \"feira de santana\",\n"," \"florianópolis\", \"formosa\", \"fortaleza\", \"francisco morato\", \"goiânia\", \"guarapuava\",\n"," \"guarulhos\", \"imperatriz\", \"ipatinga\", \"itabuna\", \"itajaí\", \"joinville\", \"jundiaí\", \"lajeado\",\n"," \"londrina\", \"macapá\", \"maceió\", \"manaus\", \"marabá\", \"marília\", \"mauá\", \"mogi das cruzes\",\n"," \"montes claros\", \"natal\", \"niterói\", \"nova iguaçu\", \"olinda\", \"ourinhos\", \"palmas\", \"parauapebas\",\n"," \"passo fundo\", \"pelotas\", \"petrópolis\", \"porto alegre\", \"porto seguro\", \"recife\", \"ribeirão preto\",\n"," \"rio branco\", \"rio das ostras\", \"salvador\", \"são bernardo do campo\", \"são caetano do sul\",\n"," \"são joão nepomuceno\", \"são josé\", \"são luís\", \"são paulo\", \"sorocaba\", \"taubaté\", \"teixeira de freitas\",\n"," \"teresina\", \"uberaba\", \"uberlândia\", \"umuarama\", \"varginha\", \"vitória\", \"volta redonda\",\n","\n"," # termos legislativos\n"," \"ação civil pública\", \"ação direta de inconstitucionalidade\", \"ação penal\", \"aditivo contratual\",\n"," \"agente político\", \"audiência pública\", \"bancada\", \"caducidade\", \"cautelar\", \"código de processo civil\",\n"," \"código penal\", \"conselho de ética\", \"contraposição\", \"deliberação\", \"desenquadramento\", \"direito administrativo\",\n"," \"direito constitucional\", \"direito penal\", \"emenda constitucional\", \"estatuto\", \"feito\", \"fiscalização\",\n"," \"improbidade administrativa\", \"inconstitucionalidade\", \"intervenção federal\", \"jurisprudência\", \"legislação ordinária\",\n"," \"medida provisória\", \"norma jurídica\", \"parlamentar\", \"poder executivo\", \"poder legislativo\", \"poder judiciário\",\n"," \"presidência\", \"promulgação\", \"recurso especial\", \"reforma legislativa\", \"regimento interno\", \"sanção\", \"supremo tribunal federal\",\n"," \"tribunal de contas\", \"vacância\", \"veto\", \"votação\", \"liderança\", \"subsídio\", \"reversão\", \"regulamentação\",\n"," \"normas de procedimento\", \"edital\", \"regime de urgência\", \"processo legislativo\", \"petição inicial\", \"substitutivo\",\n"," \"requerimento\", \"relatório de comissão\", \"desdobramento\", \"decreto-lei\", \"ente federativo\", \"obrigações administrativas\",\n"," \"tributação\", \"recurso ordinário\", \"mandado de segurança\", \"reparação\", \"direitos fundamentais\", \"transparência\",\n"," \"governança\", \"compensação fiscal\", \"paridade\", \"regime jurídico\", \"autonomia\", \"direito administrativo sancionador\",\n"," \"conselho nacional\", \"conselho superior\", \"ato administrativo\", \"procedimento administrativo\", \"regime de previdência\",\n"," \"subvenção\", \"licitação\", \"concessão\", \"permissão\", \"prerrogativa\", \"decadência\", \"transição legislativa\",\n","\n"," # órgãos educacionais e importantes do amazonas\n"," \"universidade federal do amazonas\", \"ufam\",\n"," \"instituto federal do amazonas\", \"ifam\",\n"," \"secretaria de estado de educação e desporto\", \"seduc-am\",\n"," \"fundação universidade do estado do amazonas\", \"uea\",\n"," \"escola superior de tecnologia\",\n"," \"centro de educação tecnológica do amazonas\", \"cetam\",\n"," \"instituto de ciências exatas e tecnologia\", \"icet\",\n"," \"centro de educação a distância\", \"ced\",\n"," \"faculdade de educação\",\n"," \"faculdade de tecnologia da informação\",\n"," \"faculdade de ciências agrárias\",\n"," \"faculdade de ciências da saúde\",\n"," \"escola normal superior\",\n"," \"faculdade de direito\",\n"," \"faculdade de ciências sociais\",\n"," \"sabemi\", \"fundação\",\n"," \"avenida rodrigo otávio\", \"avenida\", \"rodrigo\", \"otávio\",\n","\n"," \"susep\",\n"," \"ans\", \"anvisa\",\n"," \"incra\", \"ibama\",\n","\n"," # CNPJ e outras siglas importantes\n"," \"cnpj\", \"cadastro nacional de pessoa jurídica\",\n"," \"mei\", \"microempreendedor individual\",\n"," \"me\", \"microempresa\",\n"," \"eireli\", \"empresa individual de responsabilidade limitada\",\n"," \"ltda\", \"limitada\",\n"," \"cnae\", \"classificação nacional de atividades econômicas\",\n"," \"cpf\", \"cadastro de pessoas físicas\",\n"," \"inss\", \"instituto nacional do seguro social\",\n"," \"das\", \"documento de arrecadação do simples nacional\",\n"," \"dasn-simei\", \"declaração anual do simples nacional do mei\",\n"," \"rais\", \"relação anual de informações sociais\",\n"," \"nf-e\", \"nota fiscal eletrônica\",\n"," \"rfb\", \"receita federal do brasil\"\n","]\n","# adicionando no dicionário\n","for termo in termos_extras:\n"," dicionario.append(termo)"]},{"cell_type":"markdown","metadata":{"id":"7dHbIAJAfK-G"},"source":["#### **Correção dos Textos**\n","\n","Agora, vamos iniciar o processo de correção dos textos. Primeiro, vamos definir algumas funções auxiliares que serão úteis para corrigir as palavras.\n","\n","A função `limpa_palavra` recebe uma palavra como entrada e remove todos os caracteres que não são letras, números ou espaços. Ela utiliza uma expressão regular para filtrar os caracteres indesejados e, em seguida, remove quaisquer espaços em branco extras no início e no final da string. O resultado é uma palavra \"limpa\", que pode ser utilizada para comparação ou análise.\n","\n","A `tem_acento` procura por um acento na palavra, para lidar com os acentos na prioridade de distância de edição, caso as distâncias sejam iguais."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"6O9g0drRhubD"},"outputs":[],"source":["def limpar_palavra(palavra):\n"," # Remove pontuação e caracteres especiais, mantendo apenas letras e números\n"," palavra = re.sub(r'[^\\w\\s.]', '', palavra)\n"," # Remove espaços extras\n"," palavra = palavra.strip()\n"," return palavra\n","\n","def tem_acento(palavra):\n"," return any(char in 'áéíóúâêîôûãõàç' for char in palavra)\n","\n","def diferenca_acento(palavra1, palavra2):\n"," acentos = 'áéíóúâêîôûãõàç'\n"," diferenca = 0\n"," for char1, char2 in zip(palavra1, palavra2):\n"," if char1 in acentos and char2 not in acentos:\n"," diferenca += 1\n"," elif char1 not in acentos and char2 in acentos:\n"," diferenca += 1\n"," return diferenca"]},{"cell_type":"markdown","metadata":{"id":"fnSMF-nbijRy"},"source":["Agora, vamos abaixo temos a implementação da distância de Levenstrein. Feita com base no site mencionado anteriormente.\n","\n","Vale ressaltar que adicionamos uma função de normalização, para que ele considere que na situação com duas palavras em que a única diferença é o acento, a palavra com o acento será considerada correta e de menor distância."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"h2SMHj4Xt23c"},"outputs":[],"source":["def normalize_text(text):\n"," # remover acentuação e normalizar o texto para comparação\n"," return unicodedata.normalize('NFD', text).encode('ASCII', 'ignore').decode('ASCII')\n","\n","def levenshtein_distance(str1, str2):\n"," # normalizar as srings tirando a acentuação para verificar a distância\n"," str1 = normalize_text(str1)\n"," str2 = normalize_text(str2)\n","\n"," len_str1, len_str2 = len(str1), len(str2)\n"," matrix = [[0] * (len_str2 + 1) for _ in range(len_str1 + 1)]\n","\n"," for i in range(len_str1 + 1):\n"," for j in range(len_str2 + 1):\n"," if i == 0:\n"," matrix[i][j] = j\n"," elif j == 0:\n"," matrix[i][j] = i\n"," elif str1[i - 1] == str2[j - 1]:\n"," matrix[i][j] = matrix[i - 1][j - 1]\n"," else:\n"," matrix[i][j] = 1 + min(matrix[i - 1][j], # Deleção\n"," matrix[i][j - 1], # Inserção\n"," matrix[i - 1][j - 1]) # Substituição\n","\n"," return matrix[len_str1][len_str2]"]},{"cell_type":"markdown","metadata":{"id":"8oLnuxTikAbP"},"source":["Agora, a função abaixo é a que de fato irá chamar a distância de edição e as demais funções auxiliares. Seu objetivo é corrigir as palavras dos documentos de texto da legislação da UFAM, que não estão presentes no dicionário fornecido pois possem algum erro gramatical.\n","\n","A função toma como parâmetros:\n","\n","- `documento` (str): Caminho para o arquivo de texto que será corrigido.\n","- `dicionario` (list): Lista de palavras corretas usadas como referência.\n","- `limite_distancia` (int): Valor máximo da distância de Levenshtein permitido -para considerar uma palavra como uma possível correção.\n","\n","Seu funcionamento consiste no seguinte:\n","\n","- Para cada linha, verifica e processa cada palavra.\n","- Remove pontuação e converte a palavra para minúsculas.\n","- Se a palavra tem 5 ou mais caracteres e não é um número, verifica se não está no dicionário.\n","- Calcula a distância de Levenshtein entre a palavra e as palavras no dicionário que começam com a mesma letra e têm o mesmo comprimento.\n","- Seleciona a palavra do dicionário mais próxima, priorizando palavras com acentuação, se houver candidatos.\n","- Substitui palavras incorretas pelas corrigidas.\n","- Preserva a estrutura original do texto, como pontuação e espaçamento.\n","- Salva o texto corrigido em um novo arquivo com o sufixo _corrigido.\n","\n","Segue a função de correção logo abaixo."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"5Oz2-k978iZb"},"outputs":[],"source":["def corrige_texto(documento, dicionario, limite_distancia):\n","\n"," dicionario_set = set(dicionario)\n"," diretorio_saida = 'Arquivos de texto corrigidos'\n"," diretorio_saida_caminho = os.path.join(HOME, diretorio_saida)\n"," if not os.path.exists(diretorio_saida_caminho):\n"," os.makedirs(diretorio_saida_caminho)\n","\n"," with open(documento, 'r', encoding='utf-8') as arquivo:\n"," linhas = arquivo.readlines()\n","\n"," linhas_corrigidas = []\n","\n"," for linha in tqdm(linhas, desc=\"Processando linhas\"):\n"," if linha == '\\n':\n"," linhas_corrigidas.append('\\n')\n"," continue # Pula linhas em branco\n","\n"," palavras = linha.split()\n"," linha_corrigida = []\n","\n"," for palavra in palavras:\n"," # print(\"Palavra antes:\", palavra)\n"," palavra_limpa = limpar_palavra(palavra).lower()\n"," # print(\"Palavra depois:\", palavra_limpa)\n","\n","\n"," if len(palavra_limpa) >= 5 and not re.search(r'\\d', palavra_limpa): # verifica se a palavra tem 5 ou mais caracteres e não é apenas número\n","\n"," if palavra_limpa not in dicionario_set:\n"," candidatos = []\n"," for p in dicionario_set:\n"," # a distancia é calculada se tiver começar com a mesma letra\n"," if p and palavra_limpa and p[0] == palavra_limpa[0]:\n"," dist = levenshtein_distance(palavra_limpa, p)\n"," if dist <= limite_distancia and len(p) == len(palavra_limpa): # Tem que ter o mesmo tamanho\n"," candidatos.append((dist, p))\n","\n"," if candidatos: # tem algum candidato?\n"," #print(palavra_limpa)\n"," #print(candidatos)\n"," # Priorizar palavras com acentuação\n"," palavra_corrigida = min(candidatos, key=lambda x: (x[0], not tem_acento(x[1])))[1]\n"," # print(\"TEORICA CORRECAO: \", palavra_corrigida)\n"," # # Preservar a pontuação original\n"," # palavra_com_pontuacao = palavra.replace(palavra_limpa, palavra_corrigida)\n"," # print(\"Palavra com pontuação\", palavra_com_pontuacao )\n"," linha_corrigida.append(palavra_corrigida)\n"," #print(\"Linha corrigida 1:\", linha_corrigida)\n"," else:\n"," linha_corrigida.append(palavra_limpa)\n"," #print(\"Linha corrigida 2:\", linha_corrigida)\n"," else:\n"," linha_corrigida.append(palavra_limpa)\n"," #print(\"Linha corrigida 3:\", linha_corrigida)\n"," else:\n"," linha_corrigida.append(palavra)\n"," #print(\"Linha corrigida 4:\", linha_corrigida)\n"," # print(\"Linha corrigida\", linha_corrigida)\n"," linhas_corrigidas.append(' '.join(linha_corrigida))\n","\n"," caminho_arquivo_corrigido = os.path.join(diretorio_saida_caminho, os.path.basename(documento).replace('.txt', '_corrigido.txt'))\n","\n"," with open(caminho_arquivo_corrigido, 'w', encoding='utf-8') as arquivo:\n"," arquivo.write('\\n'.join(linhas_corrigidas))"]},{"cell_type":"markdown","metadata":{"id":"ZIV94MB7mBow"},"source":["Agora, chegou a hora de aplicar essa função nos documentos de texto. Portanto, vamos fazer conexão com o google drive para percorrer os arquivos no diretório em que se encontram."]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"VUp-vBQ2mOey","outputId":"3e9ffcf9-7554-490d-f3d7-f0319849a559","scrolled":true},"outputs":[{"name":"stderr","output_type":"stream","text":["Processando arquivos...: 0%| | 0/76 [00:00