<jupyter_start><jupyter_text>**Modelo para categorizar a decisão em um julgado com base na íntegra de um acórdão proferido pelo CARF****Trabalho de Conclusão de Curso - TCC** apresentado como pré-requisito para obtenção do título de especialista no curso MBA BI MASTER BUSINESS INTELLIGENCE MASTER - SISTEMAS INTELIGENTES DE APOIO À DECISÃO EM NEGÓCIOS oferecido pelo Departamento de Engenharia Elétrica e administrado pela Coordenação Central de Extensão da PUC-Rio Professor Orientador: **Dr. <NAME>** Aluno: **<NAME> - Turma: 2019.2** **Passo 1 - Conhecimento dos dados** 1.1 - Primeiramente é feito um preparo do ambiente onde estão hospedados os arquivos com os julgados extraídos do site oficial do CARF (https://carf.fazenda.gov.br/sincon/public/pages/ConsultarJurisprudencia/consultarJurisprudenciaCarf.jsf) 1.2 - Em seguida, a importação das principais bibliotecas python utilizadas no processo de conhecimento e pré-processamento dos dados.<jupyter_code># Load libraries
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
%matplotlib inline

import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import re
os.chdir("/projetos/UQC6/CARF")
df = pd.read_excel(r'Julgamentos.xlsx', engine='openpyxl')<jupyter_output><empty_output><jupyter_text>1.3 - Neste ponto é realizado um pré-processamento da base, abrindo cada documento, tratando espaços e quebras de linha, ajustando a codificação para utf-8 e organizando tudo em um dataframe posteriormente armazenado na variável "data".<jupyter_code>def text_preprocessing(text):
  text = text.strip().replace('\n','')
  text = re.sub(' +', ' ', text)
  return text

def read_text(row):
  with open(row['Documento'],'r', encoding='utf-8') as file:
    text = text_preprocessing(file.read())
  return text

df['text'] = df.apply(lambda row:read_text(row), axis=1)
data = df[['text','Provimento']]
data.head(3)
data.tail(3)
print(data.iloc[1,0])<jupyter_output><empty_output><jupyter_text>1.4 - Neste ponto é retirada a classe que não interessa ao classificador. São processos em que o contribuinte aderiu a algum tipo de conciliação judicial, administrativa ou REFIS e decidiu não prosseguir com a demanda no CARF.<jupyter_code>filtro = data['Provimento'] != "DESISTENCIA" #retirando os julgados com a homologação de desistência
data = data[filtro]
data[data['Provimento'] == "NEGADO"].count()["Provimento"]
#n = 0
#for index, row in data.iterrows():
  #if row.Provimento == "NEGADO" and n < 31:
    #n = n + 1    
  #if row.Provimento == "NEGADO" and n > 30:
    #data.drop(index, inplace=True)
    #n = n + 1
#data[data['Provimento'] == "NEGADO"].count()["Provimento"]<jupyter_output><empty_output><jupyter_text>1.5 - Neste ponto verificamos o tamanho da base, a distribuição dos rótulos e algumas estatísticas dos dados. Foram carregados 210 julgados, rotulados manualmente, sendo 70 de cada classe (NEGADO, SIM INTEGRAL e SIM PARCIAL).<jupyter_code>len(data.index)
# inspect provimentos
sns.countplot(data['Provimento'])
# text length
data['text_length'] = df['text'].apply(len)
data[['text_length','text']].head(1)
data['text_length'].describe()
data['text_length'].hist(bins=50)
g = sns.FacetGrid(data,col='Provimento')
g.map(plt.hist,'text_length')<jupyter_output><empty_output><jupyter_text>**Passo 2 - Limpeza dos dados**<jupyter_code># LIMPEZA DOS DADOS - Importando as bibliotecas necessárias

import nltk
#nltk.download('punkt')
#nltk.download('stopwords')
from nltk.corpus import stopwords
from wordcloud import WordCloud
import re
import matplotlib.pyplot as plt
nltk.download('punkt')
nltk.download('stopwords')<jupyter_output><empty_output><jupyter_text>2.1 - Extraindo algumas palavras que se repetem no cabeçalho de todos os documento e não contribuem para a classificação pelo modelo.<jupyter_code># Limpando os dados
from textwrap import TextWrapper
stopwords = nltk.corpus.stopwords.words('portuguese')

def limpar(s):
    s = s.strip().lower()
    s = re.sub(r'julgamento', '', s)
    s = re.sub(r'digitalmente', '', s)
    s = re.sub(r'carf', '', s) 
    s = s.strip()
    s = nltk.tokenize.word_tokenize(s)
    s = [word for word in s if not word in stopwords]
    s = ' '.join(s)
    return s


data['clean_text'] = data['text'].apply(limpar)
text = data.iloc[1]['clean_text']
TextWrapper(max_lines=20).wrap(text)<jupyter_output><empty_output><jupyter_text>2.2 - Neste ponto é feito o recorte dos extremos da base. Conta-se a frequência das palavras e extrai-se aquelas com baixa e alta frequência.<jupyter_code>from collections import Counter
from itertools import chain

# dividir palavras em listas
v = data['clean_text'].str.split().tolist() 
# computar frequência global de palavras
c = Counter(chain.from_iterable(v))
# filter, join, and re-assign
data['clean_text'] = [' '.join([j for j in i if c[j] > 2 and c[j] < 1000]) for i in v]
v1 = data['clean_text'].str.split().tolist() 
c1 = Counter(chain.from_iterable(v))
a = sorted(c1.items(), key=lambda x: x[1]) #aqui eu ordeno os resultados em ordem crescente de frequência    
#print(a)<jupyter_output><empty_output><jupyter_text>2.3 - Neste ponto utilizamos o spacy, uma biblioteca de software de código aberto para processamento avançado de linguagem natural, para remover palavras curtas, pontuações, stop words e realizar a lematização de toda base de julgados.<jupyter_code># Instantiate a spaCy object
!pip install deplacy
#!python -m spacy download pt_core_news_sm
import pkg_resources,imp
imp.reload(pkg_resources)
import spacy
import string

import pt_core_news_sm
nlp = pt_core_news_sm.load()

# Regular expressions
remove_num = re.compile(r'\d+')
remove_duplicates = re.compile(r'\b(\w+)(?:,\s+\1\b)+', flags=re.I)

# Load punctuation
punctuation = string.punctuation

def preprocess_text(s):    
    
    # Apply compiled regular expressions
    s = remove_duplicates.sub(' ', s)
    s = remove_num.sub(' ', s)
    
    # Remove traiiling spaces
    s = re.sub(r'\s+', ' ', s)   
    
    # Create a spaCy doc
    doc = nlp(s, disable=['tagger', 'parser', 'ner'])
     
    # Remove punctuations and small words 
    tokens = [token for token in doc if not token.is_punct and len(token) > 3]

    # Remove stop words and non-alphabetic words
    tokens = [token for token in tokens if not token.is_stop and token.is_alpha]

    # Lemmatizer and lowercase
    tokens = [token.lemma_.lower() if token.lemma_ !='-PRON-' 
              else token.lower_ for token in tokens]    

    return ' '.join(tokens)
%%time
data['clean_text'] = data['clean_text'].apply(preprocess_text)
text = data.iloc[1]['clean_text']
TextWrapper(max_lines=20).wrap(text)
# Verificando o tamanho dos julgados limpos
data['TextLength'] = data['clean_text'].apply(len)
data['TextLength'].describe()
# Verificando a distribuição dos comprimentos

bins = 18
data['TextLength'].hist(bins=bins)
# Gerando uma nuvem de palavras por classe

classes = data['Provimento'].unique()

for classe in classes:
  text = data[data['Provimento']==classe]['clean_text'].to_string()

  wordcloud = WordCloud(#background_color='gray',colormap='Oranges_r',
      collocations=False,
      relative_scaling=0.5,
     # max_words=100,
      stopwords=set(stopwords)).generate(text)

  plt.figure(figsize=(12,12))
  plt.imshow(wordcloud)
  plt.axis("off")
  plt.title(classe)
  plt.show()
# dividir palavras em listas
v = data['clean_text'].str.split().tolist() 
# computar frequência global de palavras
c = Counter(chain.from_iterable(v))
# filter, join, and re-assign
data['clean_text'] = [' '.join([j for j in i if c[j] > 2 and c[j] < 1000]) for i in v]
v1 = data['clean_text'].str.split().tolist() 
c1 = Counter(chain.from_iterable(v))
a = sorted(c1.items(), key=lambda x: x[1]) #aqui eu ordeno os resultados em ordem crescente de frequência    
#print(a)<jupyter_output><empty_output><jupyter_text>Words Frequency Distribution Tokenizando e verificando a frequência das palavras após o tratamento da base.<jupyter_code>from pprint import pprint
from nltk.tokenize import TweetTokenizer
tt = TweetTokenizer()
corpus = ' '.join([*data['clean_text']])
freq_dist = nltk.FreqDist(tt.tokenize(corpus))
pprint(freq_dist.most_common(100), compact=True)<jupyter_output><empty_output><jupyter_text>**Passo 3 - Pré-processamento**<jupyter_code># Importando bibliotecas necessárias

from keras.preprocessing.text import Tokenizer
from keras.preprocessing import sequence
from sklearn.preprocessing import LabelEncoder
# Verificando se a limpeza deixou algum documento vazio

data.clean_text[data['clean_text'] == ''].count()
# Codificando as classes categóricas de provimento dos julgados

le = LabelEncoder()
le.fit(data['Provimento'])
classes = le.classes_
num_classes = len(classes)
classes
# Encode Categorical Variable
X = data['clean_text']
y = le.transform(data['Provimento'])
#y = pd.get_dummies(data['Provimento']).values
y
seed = 101 # fix random seed for reproducibility
np.random.seed(seed)
# Split Train Test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                    test_size=0.1,
                                                    stratify=y,
                                                    random_state=seed)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
# Tokenizando o texto
#20000
max_features = 10000
tokenizer = Tokenizer(num_words=max_features)
tokenizer.fit_on_texts(list(X_train))
X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)
# Verificando o número de palavras

words = [len(julgado) for julgado in X_train]
plt.hist(words,bins = 8)
plt.show()
max(words)
# Completando o tamanho para todos os documentos ficarem iguais

#X_train = sequence.pad_sequences(X_train, maxlen=max(words))
#X_test = sequence.pad_sequences(X_test, maxlen=max(words))
X_train = sequence.pad_sequences(X_train, maxlen=7000)
X_test = sequence.pad_sequences(X_test, maxlen=7000)
print(X_train.shape,X_test.shape)
print(y_train.shape,y_test.shape)<jupyter_output>(189, 7000) (21, 7000)
(189,) (21,)<jupyter_text>**Passo 4- Classificação** Passo 4.1 - CNN-LSTM<jupyter_code># Importando as bibliotecas necessárias

from tensorflow import random
import numpy as np

import keras.backend as K
from keras.models import Sequential, load_model
from keras.layers import Dense,Embedding,Conv1D,GlobalMaxPool1D,LSTM, Bidirectional, MaxPooling1D
from keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint
from sklearn.metrics import accuracy_score,confusion_matrix,classification_report
from sklearn.utils import class_weight
from keras.optimizers import SGD<jupyter_output><empty_output><jupyter_text>Definindo o modelo<jupyter_code># Função que cria o modelo de categorização

def get_model(max_features, embed_dim):

    np.random.seed(7)
    random.set_seed(7)
    K.clear_session()

    model = Sequential()
    model.add(Embedding(max_features, embed_dim, input_length=X_train.shape[1]))    
    model.add(LSTM(140,return_sequences = True)) #140
    model.add(LSTM(140, dropout=0.2, recurrent_dropout=0.2)) #140
    model.add(Dense(256, activation='relu'))
    model.add(Dense(num_classes, activation='softmax'))
    model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc'])
    print(model.summary())
    
    return model
# Definindo função que treina o modelo

def model_train(model):

    ES = EarlyStopping(patience=5,monitor="val_loss", mode='auto', restore_best_weights=True)
    RLR = ReduceLROnPlateau(monitor='val_acc', patience = 5, verbose=2,factor=0.5, min_lr=0.00001)
    #SM = ModelCheckpoint('desafio_NLP_1.h5', save_best_only=True),

    #class_weights = dict(enumerate(class_weight.compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)))
    
    model_history = model.fit(X_train, y_train, validation_split=0.2, #validation_data=(X_test, y_test), 
                          epochs=epochs, batch_size=batch_size, verbose=1, callbacks=[ES,RLR]
                          #,class_weight=class_weights
                          )
 
    plot_model_history(model_history)<jupyter_output><empty_output><jupyter_text>Gráficos e medidas de desempenho<jupyter_code># Definindo função que exibe a evolução do modelo

def plot_model_history(model_history):
    fig, axs = plt.subplots(1,2,figsize=(15,5))
    # summarize history for accuracy
    axs[0].plot(range(1,len(model_history.history['acc'])+1),model_history.history['acc'])
    axs[0].plot(range(1,len(model_history.history['val_acc'])+1),model_history.history['val_acc'])
    axs[0].set_title('Model Accuracy')
    axs[0].set_ylabel('Accuracy')
    axs[0].set_xlabel('Epoch')
    axs[0].set_xticks(np.arange(1,len(model_history.history['acc'])+1),len(model_history.history['acc'])/10)
    axs[0].legend(['train', 'val'], loc='best')
    # summarize history for loss
    axs[1].plot(range(1,len(model_history.history['loss'])+1),model_history.history['loss'])
    axs[1].plot(range(1,len(model_history.history['val_loss'])+1),model_history.history['val_loss'])
    axs[1].set_title('Model Loss')
    axs[1].set_ylabel('Loss')
    axs[1].set_xlabel('Epoch')
    axs[1].set_xticks(np.arange(1,len(model_history.history['loss'])+1),len(model_history.history['loss'])/10)
    axs[1].legend(['train', 'val'], loc='best')
    plt.show()<jupyter_output><empty_output><jupyter_text>Avaliação do modelo<jupyter_code># Definindo função que testa e plota o resultado do modelo

def model_evaluate(): 
    # Recupernaod o modelo com o melhor resultado de validação
    #model = load_model('desafio_NLP_1.h5')

    # predict class with test set
    y_pred_test =  model.predict_classes(X_test, batch_size=batch_size, verbose=0)
    print('Accuracy:\t{:0.1f}%'.format(accuracy_score(y_test,y_pred_test)*100))
    
    #classification report
    print('\n')
    print(classification_report(y_test, y_pred_test))

    #confusion matrix
    confmat = confusion_matrix(y_test, y_pred_test)

    fig, ax = plt.subplots(figsize=(4, 4))
    ax.matshow(confmat, cmap=plt.cm.Blues, alpha=0.3)
    for i in range(confmat.shape[0]):
        for j in range(confmat.shape[1]):
            ax.text(x=j, y=i, s=confmat[i, j], va='center', ha='center')
    plt.xlabel('Predicted label')
    plt.ylabel('True label')
    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Regras de treinamento<jupyter_code># Treinando o modelo

batch_size = 128 #128 #64
epochs = 1000
max_features = 6000
embed_dim = 100 #100

model = get_model(max_features, embed_dim)
model_train(model)<jupyter_output>Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, 7000, 100)         600000    
_________________________________________________________________
lstm (LSTM)                  (None, 7000, 140)         134960    
_________________________________________________________________
lstm_1 (LSTM)                (None, 140)               157360    
_________________________________________________________________
dense (Dense)                (None, 256)               36096     
_________________________________________________________________
dense_1 (Dense)              (None, 3)                 771       
=================================================================
Total params: 929,187
Trainable params: 929,187
Non-trainable params: 0
__________________________________________________[...]<jupyter_text>Avaliar modelo<jupyter_code># evaluate model with test set
model_evaluate()
model.save('meu_modelo')
#Salvando o modelo treinado
#%pwd
from tensorflow import keras
model3 = keras.models.load_model('meu_modelo') # Carregando o modelo treinado em uma variável
#model_evaluate(model3)
model3.predict(X_test[0:1]) #Testando o modelo treinado no primeiro registro da tabela de julgados
#X_test[0:1]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1261839481555334, 'fraction_numerical': 0.019503988035892324, 'mean_word_length': 4.968389735961324, 'pattern_counts': {'":': 0, '<': 68, '<?xml version=': 0, '>': 69, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '238626', 'license': 'MIT', 'n_tokens_mistral': 5775, 'n_tokens_neox': 5489, 'n_words': 1272, 'path': 'TCC_PUC_Rafael dos Santos.ipynb', 'repo_name': 'Rafasoitavus/puc-rio-bi-master-tcc-dev', 'size': '16084'}