Redes Neurais Convolucionais para processamento de linguagem natural.

Alvaro Leandro Cavalcante Carneiro
Data Hackers
Published in
11 min readJul 7, 2020

--

fonte: Tatiana Shepeleva/shutterstock.com

As redes neurais convolucionais (CNN) são muito utilizadas para se trabalhar com imagens, como se tem visto nos últimos anos, todavia, a sua aplicação em processamento de linguagem natural (PNL) também tem trazido resultados interessantes para a área.

A arquitetura e funcionamento das CNNs aplicada a linguagem natural é bastante similar a de imagens, facilitando o entendimento para quem já aplicou esse algoritmo em visão computacional, porém dessa vez voltado a tarefas de classificação textual, como análise de sentimentos, categorização de notícias, detecção de spam, entre outros.

Nesse artigo, irei explicar de forma simples o pré-processamento necessário, alguns conceitos comuns dessa área e também o funcionamento das redes neurais convolucionais aplicados à texto. Iremos ver:

  • One Hot Encoding
  • Word Embedding
  • O que é um Corpus?
  • Stop Words
  • Tokenização
  • Processo de convolução

O primeiro passo necessário é transformar os textos que iremos trabalhar em matrizes numéricas, pois a operação de convolução trabalha com uma multiplicação matricial de um sinal de entrada (imagem ou texto) e um kernel (filtro), portanto além de transformar as palavras em números como geralmente fazemos em tarefas de machine learning que envolvam variáveis categóricas, também é necessário criar um arranjo matricial para elas.

Uma forma clássica, inclusive bastante utilizada em algumas etapas de pré-processamento para transformar palavras em números (ou melhor, vetor de números) é o one hot encoding.

One hot encoding

Esse processo é bastante simples, vamos supor que tenhamos as seguintes palavras:

  • Vermelho, amarelo e preto.

Ao aplicar o encoding, cada uma dessas variáveis se transforma em uma vetor da seguinte forma:

vermelho = [1,0,0], amarelo = [0,1,0] e preto = [0,0,1].

Podemos expressar essa ideia também em uma formato de tabela, onde cada linha representa uma palavra e a coluna representa uma dimensão, sendo o valor 1 responsável por indicar se a palavra se encontra ou não naquela dimensão:

Representação de encoding em tabela

Isso também é chamado de variável dummy, e basicamente identifica a palavra pela posição do número 1, como é possível observar no exemplo.

O problema dessa abordagem, é que as palavras são independentes, ou seja, não possuem contexto ou correlação alguma entre elas, sendo pouco efetivo em casos onde essa informação é mais relevante para o problema.

Além disso, a dimensionalidade também é um ponto desfavorável, pois quanto maior o conjunto de palavras, mais colunas (dimensões) iremos precisar adicionar nessa representação, sendo a maioria delas preenchida com um valor 0, utilizando assim um conjunto enorme de dimensões que simplesmente não possui valor algum.

Uma abordagem feita para superar esses aspectos é o uso da técnica de word embedding.

Word Embedding

A ideia de word embedding é criar uma representação contextualizada das palavras ao mesmo tempo que reduz a dimensionalidade em relação ao método anterior.

A aplicação dessa técnica, que também é feita utilizando aprendizado de máquina, identifica a relação entre as palavras a partir de sua vizinhança e as coloca em um vetor onde cada dimensão representa um contexto específico e as palavras parecidas ocupam dimensões próximas uma das outras devido à sua dependência.

Com isso, conseguimos não só identificar como também classificar os textos do vocabulário baseado em seus contextos individuais, geralmente analisando as palavras precedem ou sucedem cada uma.

Portanto, nesse método, cada dimensão possui uma informação sobre a palavra, fazendo mais sentido do que o método de one hot encoding, pois ao mesmo tempo que diminuímos o número de dimensões aumentamos a relevância da informação em cada uma delas.

Um dos métodos mais comuns e populares para criação de word embeddings é o Word2Vec. Detalhes do seu funcionamento fogem do objetivo desse artigo, porém recomendo dedicar algum tempo no estudo dessa técnica, pois ela é o primeiro passo para que consigamos representar de forma efetiva as palavras em um formato numérico.

O artigo de Xin Rong pode ser um bom ponto inicial para isso.

O que é um corpus linguístico?

Durante os estudos de NLP, possivelmente você irá se deparar com a palavra corpus.

Corpus é um conceito que vem do estudo da linguística e nada mais é do que um conjunto de textos escritos e registros orais emitidos por usuários de uma determinada língua em uma determinada época. Portanto, podemos dizer que uma frase é um corpus que será analisado e terá sua relação aprendida, sendo assim, o conjunto de dados utilizados no treinamento do algoritmo de NLP é basicamente composto de frases ou corpus.

Para mais definições, recomendo a leitura desse artigo: http://revistas.unisinos.br/index.php/calidoscopio/article/view/6002/3178

Remoção de Stop Words

Uma etapa que pode ser interessante como pré-processamento ao se trabalhar com PNL é a remoção das chamadas “stop words”. Essas palavras são aquelas consideradas descartáveis em uma frase, pois embora ajudem a entender o que se está escrevendo, não agregam muito valor para o resultado final da semântica da frase.

Removendo essas palavras conseguimos reduzir o tamanho do vocabulário, deixando o processo geral de treinamento da NLP mais rápido.

O vocabulário nada mais é que o número de palavras únicas existentes no conjunto de dados, por exemplo, na quinta edição do dicionário Aurélio, são definidas 143 mil palavras no vocabulário da língua portuguesa. Possivelmente seu conjunto de dados onde será aplicado o algoritmo de CNN possui um vocabulário muito mais limitado e isso é desejável, pois grande parte das palavras são usadas com baixíssima frequência e apenas agregariam em complexidade e tempo de treinamento do modelo.

Cabe a você remover ou não essas stop words dependendo do contexto que se está trabalhando, de qualquer forma vale a pena fazer alguns testes, como realizar o treinamento com e sem essas palavras e ver o seu impacto no acerto do algoritmo. Cada biblioteca vai definir um conjunto único de stop words e é possível inclusive criar um conjunto personalizado, alguns exemplos dessas palavras são:

foste, tivemos, fim, que, são, quem, fui, des, nunca, põe, onde, não, ora, mês, ser, foi, doze, acerca, deste, seus…

Essas são apenas algumas amostras extraídas da biblioteca spacy de stop words da língua portuguesa que seriam removidos do seu conjunto de dados, a lista completa possui mais de 400 palavras e que se removidas, podem impactar nos resultados do seu modelo em troca de tempo de treinamento menor.

Possivelmente criar um conjunto personalizado dessas palavras será a melhor abordagem, apesar de levar mais tempo. A área de NLP, por trabalhar com dados textuais não estruturados, acaba sendo bastante custosa em termos de pré-processamento, pois muita das vezes estamos coletando dados diretamente de usuários da WEB, sendo importante investir tempo em remoção de caracteres especiais, stop words específicas, frases sem semântica e entre outros ruídos.

Tokenização

Um outro método utilizado para transformar as palavras em textos é a tokenização, onde cada uma das palavras únicas recebe um ID identificador.

Com isso, se tokenizarmos as palavras do exemplo anterior, temos o seguinte resultado:

{“vermelho”: 1, “amarelo”: 2, “preto”: 3}

Dessa forma, o vocabulário é mapeado em um conjunto numérico sequencial, bastando apenas aplicar esse dicionário de tokens no dataset para que cada uma das frases seja substituída por um vetor de números. Ex: [1,2,3].

Por padrão, o token considera os espaços como a separação de palavras, para a distribuição de IDs.

Esse método, assim como o one hot encoding, não considera nenhum relação adicional das palavras, por isso, é interessante utilizar a tokenização apenas como um pré-processamento das palavras a serem submetidas a um word embedding.

Padding de palavras

Um pré-processamento importante de ser realizado é o padding (preenchimento) do array de palavras, uma vez que esse esteja tokenizado.

Isso acontece como uma forma de deixar todas as palavras em uma mesma dimensão, ainda que na prática elas tenham tamanhos diferentes. Na aplicação das CNNs com o TensorFlow, a etapa de padding é um pré-requisito, todavia, pelo preenchimento ser realizado com valores 0 não há impacto nos resultados finais, pois o zero não gera novo valores na convolução.

Seguindo o exemplo das cores, vamos supor que em nosso dataset exista um outro conjunto de cores [‘roxo’, ’lilás’, ‘violeta’, ‘amarelo’]. Após a tokenização, poderíamos ter esse conjunto da seguinte forma: [8, 6, 12, 2]. O tamanho desse vetor é de 4 posições, 1 posição a mais do que o vetor do exemplo anterior: [‘vermelho’, ‘amarelo’, ‘preto’] = [1,2,3] (tokenizado).

Portanto, devemos fazer o padding com o valor 0 desse vetor (e de todos os outros do nosso conjunto) para que tenhamos sempre o mesmo tamanho, ficando da seguinte forma: [1,2,3,0].

O valor do padding (quantidade de zeros a direita adicionados) é basicamente o tamanho da maior frase do conjunto de dados, por exemplo, se nosso maior conjunto for de 20 cores, então TODOS os registros irão ter o tamanho de 20 através desse preenchimento.

Processo de convolução

Uma vez que todo o pré-processamento nos textos brutos foi realizado, devemos ter algo parecido com isso:

fonte: https://confusedcoders.com/data-science/deep-learning/how-to-create-custom-ner-in-spacy

Cada frase ou corpus do dataset se transforma em uma representação matricial. A quantidade de linhas dessa matriz é basicamente a quantidade de palavras existentes na frase e a quantidade de colunas ou dimensões assim como o valor de cada dimensão é criado automaticamente pelo algoritmo de word embedding. Uma vez que chegamos até aqui, podemos aplicar o algoritmo de redes neurais convolucionais.

O processo de convolução é muito bem ilustrado no GIF abaixo:

fonte:https://confusedcoders.com/data-science/deep-learning/how-to-create-custom-ner-in-spacy

A matriz em azul, que desliza de cima para baixo na imagem é chamada de kernel ou filtro, sendo esse basicamente uma matriz de tamanho menor que a nossa frase e com valores iniciais aleatórios.

Os kernels tem seus valores atualizados pela rede neural para conseguir extrair cada vez melhor as características da matriz de input, assim como acontece com as redes neurais convolucionais clássicas para imagens. A diferença aqui é que o kernel não se movimenta da esquerda para a direita e de cima para baixo em toda a matriz, pois nesse caso, o kernel possui a mesma dimensão da matriz de input, para ser capaz de abranger toda palavra de uma vez só.

Além disso, a ordem em que as palavras estão não faz tanta diferença quanto a ordem dos pixels nas imagens. Claro que no mundo real, sabemos que trocar a ordem das palavras pode alterar completamente o significado, sendo esse fato uma das limitações das CNNs, porém ainda assim elas funcionam bem em termos gerais para identificação de padrões principais.

No caso do GIF, também está sendo aplicado um padding para se aproveitar as bordas da matriz (repare que na primeira e última iteração o kernel está ‘fora’ da matriz do texto), gerando um output com a mesma quantidade de linhas que a matriz de input, nesse caso, 6 linhas.

A estratégia de padding mais comum é a zero-padding, onde são adicionados valores zerados para não influenciar no produto escalar, sendo bastante utilizado no caso das imagens, para evitar a redução de dimensionalidade.

Dessa forma, podemos imagina uma linha adicional antes da palavra “this” e outra linha adicional depois da palavra “characteres” contendo zeros: [0,0,0].

Outro parâmetro comum em CNNs e que podemos observar é o stride, sendo a quantidade de “linhas” que são puladas por vez na aplicação do filtro, geralmente sendo definido como 1, como é possível ver no GIF.

Um stride maior vai gerar uma redução mais drástica na dimensionalidade do vetor de saída.

A camada de convolução aqui (onde a aplicação do kernel é realizada)é chamada de 1D-conv, pois o output é um array (de uma dimensão), diferentemente da convolução para imagens onde o output é uma matriz, portanto um 2D-conv.

O output pode ser chamado de vetor de características, pois a aplicação dos kernels possui justamente o objetivo de extrair e sumarizar características principais encontradas em uma matriz.

Um paralelo importante é entender como funcionam os kernels nas imagens, esse site mostra um exemplo bem didático: https://setosa.io/ev/image-kernels/.

Max pooling

Ao final do processo de convolução, também é aplicado o MAX pooling, onde o objetivo é simplesmente extrair o maior valor do vetor de características como foi o caso do 0.8 no GIF, sendo basicamente a representação da palavra que teve maior destaque na frase.

Por exemplo, se estamos treinando um modelo para classificar mensagens negativas e positivas, dado a frase: “Eu odeio essa loja”, podemos esperar que a palavra “odeio” terá o maior peso ao se aplicar o kernel, pois ao longo das iterações, a rede neural aprende que esse tipo de palavra remete mais a um sentimento, que no caso é o negativo.

Uma das grandes vantagens das CNNs é justamente essa de ajustar os kernels ao longo das iterações, para automaticamente identificar as características principais das frases, resumindo toda a matriz de um corpus para um único valor.

Outras técnicas como extrair a média ou mediana também poderiam ser utilizadas, mas o valor máximo é o que geralmente produz os melhores resultados.

Concatenação dos valores e rede neural

Como podemos ver na arquitetura abaixo, esse processo de convolução e max pooling é realizado para cada kernel da CNN (geralmente possuímos dezenas de kernels, dependendo do problema), e os kernels possuem quantidades de linhas (uma vez que o número de colunas deve ser de tamanho fixo igual ao da matriz de input).

fonte: http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/

Podemos observar no exemplo, que são pares de kernels com 4, 3 e 2 linhas respectivamente. Quanto maior o kernel, mais palavras da frase são analisadas ao mesmo tempo, gerando diferentes resultados.

Independente do tamanho do kernel, o output da convolução é um vetor com os valores dos produtos escalares, que será resumido a um único número através da aplicação do max pooling.

Esses valores únicos de cada pooling são então concatenados para se transformar novamente em um vetor único.

Podemos dizer que o número de kernels será o mesmo que o tamanho do vetor final, pois cada kernel produz um valor único no fim das contas, graças ao max pooling.

Esse é o processo de concatenação, onde cada valor resultante do pooling é unido em um novo “vetor final”.

Esse vetor final é então utilizado como sendo a camada de entrada de neurônios da rede neural que será treinada para identificar os padrões. Como já foi dito, vantagem da CNN é que ao invés de utilizarmos toda a frase para servir como camada de entrada, utilizamos apenas as características principais que foram extraídas automaticamente pelos filtros, tornando o processo de aprendizado mais rápido e interativo.

O vetor com as cores azul e roxa na imagem acima representam a camada de saída da rede neural, podemos supor que seria uma cor para um sentimento positivo e outro para um sentimento negativo por exemplo. Essa camada de saída é precedida por demais camadas de neurônios da rede, que nesse esquema foram omitidas por uma questão de simplicidade.

Conclusão

O processamento de linguagem natural certamente é uma das áreas mais importantes e exploradas dentro da inteligência artificial atualmente. Cada dia surgem mais tecnologias robustas para resolver problemas dessa área, como é o caso da arquitetura transformer que tem trazidos resultados inovadores. Ainda assim, as CNNs que já são extremamente consolidadas no campo de imagens, também vem como uma opção mais simples e prática para resolver problemas nessa área, se mostrando uma arquitetura bastante versátil e robusta, sendo um bom passo para quem quer começar alguns trabalhos nessa área.

--

--

Alvaro Leandro Cavalcante Carneiro
Data Hackers

MSc. Computer Science | Data Engineer. I write about artificial intelligence, deep learning and programming.