My Profile Photo

Jociel Souza

PHP Clojure Javascript Mongodb ZendFramework


Desenvolvedor Web - Backend - PHP


Estudante de Ciência da Computação, desenvolvedor de software apaixonado por tecnologia.


Estudante de Ciência da Computação procurando sempre aprender algo novo, desenvolvedor web com conhecimento em PHP7^, Zend Framework, Zend Expressive, Doctrine ORM, Doctrine ODM, MongoDB, Javascript, VueJS, HTML, GIT entre outros, atualmente estudando também Clojure, Elm e o paradigma funcional e tentando compartilhar o conhecimento adquirido.


Clojure - Classificação de comentários com Machine Learning | Jociel Souza

Clojure - Classificação de comentários com Machine Learning

Trabalho faculdade


Seguindo a série de trabalhos da faculdade :D, agora brincando um pouco com Machine Learning.


Classificação de textos consiste em associar cada texto a uma determinada classe baseada em seu conteúdo, por exemplo, podemos associar um comentário sobre um produto ou serviço a uma classe “Negativo” quando for um comentário negativo sobre o produto ou a uma classe “Positivo” quando for um comentário positivo sobre o mesmo.

Isso acontece também nos servidores de e-mails para poder classificar um e-mail como spam, por exemplo, e assim direcioná-lo para o diretório spam.


Fazer isso a uma quantidade grande de comentários pode ser útil para se ter uma visão de como as pessoas, possivelmente os clientes ou usuários, estão se sentindo em relação a um produto, por exemplo, mas pode ser trabalhoso e talvez inviável fazer isso manualmente, ai entra o Machine Learning para automatizar esse processo.

Para utilizar Machine Learning na classificação de textos, uma das técnicas mais utilizadas é usar aprendizado supervisionado, onde temos uma coleção de dados já processados e classificados nas suas determinadas classes, então entramos esses dados como input para o algoritmo que irá ‘aprender’ as características que representam cada classe com utilizando esses dados já classificados.

Os problemas mais frequentes que fazem uso de aprendizado supervisionado são problemas de ‘regressão’ onde temos que mapear as variáveis em um resultado continuo, tentando predizer um resultado, e problemas de ‘classificação’, que será o caso de estudo, que se pretende definir classes distintas às variáveis de entrada que no caso serão comentários sobre filmes.

Para implementar um modelo de Machine Learning normalmente segue-se um fluxo, que pode variar dependendo do objetivo final, em cima dos dados antes que o modelo entre em produção, que podemos definir basicamente em:

  • Obtenção dos dados;
  • Pré-processamento;
  • Treinamento;
  • Teste;
  • Aperfeiçoamento;



Obtenção dos dados

Nesta etapa obtemos os dados “crus” seja de qualquer fonte, banco de dados, arquivos, paginas web, etc. É importante saber que os dados devem ser relevantes para o domínio do problema, selecionando dados diferentes do que se deseja analisar pode prejudicar o resultado final e trazer resultados incoerentes com a realidade.

Para este caso eu realizei scraping (web scraping é uma técnica para captura automatizada de dados de páginas web, sejam textos, imagens, documentos, etc.) de um site sobre cinema e peguei os comentários das pessoas que fizeram avaliações de diversos filmes. Como no site tinha a avaliação dos determinados filmes em forma de estrelas, assumi isso para determinar se um comentário é positivo ou negativo, para ficar mais discretizado descartei os comentários com 3 estrelas por assumir que seriam “neutros”, e defini os comentários com 4 e 5 estrelas como positivos e os que tiveram 2 ou 1 estrelas como negativo, então salvei em dois arquivos separados CommentsPositive e CommentsNegative.


Pré-processamento

Nesta etapa realizamos a preparação dos dados, padronizando, retirando inconsistências, etc, para após servir como entrada para o treinamento do algoritmo. Esse processo pode ser composta de diversas sub etapas que podem variar dependendo do objetivo final e necessidade. Para esse caso de estudo fiz as seguintes sub-etapas para preparar os dados:

Padronizar tudo em letra minúsculas

Como processo de padronização dos comentários transformei tudo em letras minúsculas;

Remoção de acentos

Também removi toda a acentuação dos comentários;

Remoção de pontuação

As pontuações não têm significado para este caso então foi removido todo tipo de pontuações;

Remoção de espaços em branco

Foi removido todo espaço em branco desnecessário;

Remoção de caracteres repetidos

Também foi removidos caracteres repetidos, por exemplo, o seguinte comentário: “filme muuuuuuiiitttooooo boooooooommmmm”, foi transformado em: “filme muito bom”;

Remoção de stopwords

Esta é uma técnica muito utilizada no pré-processamento de textos para classificação com Machine Learning, consiste na remoção de palavras muito frequentes e que não são tenham nenhum valor semântico no texto, como exemplo os artigos e preposições, isso também diminui a quantidade de palavras a serem processadas melhorando o tempo da tarefa de treinamento e teste;

Stemming

Stemming também é um método bastante utilizado em textos antes de serem processado por algum algoritmo de Machine Learning.

Os textos comumente possuem diversas palavras derivadas ou flexionadas, o processo de stemming reduz todas as palavras possíveis ao seu radical, removendo prefixos e sufixos derivados de um mesmo radical, ajudando a diminuir a dimensionalidade do processamento do texto porque palavras diferentes derivadas de um mesmo radical, ambas passam a ser representada pelo radical, como, por exemplo: “testando” e “testado” passam a ser o radical “test” ambas as palavras têm o mesmo valor semântico.

Nesse processo podem ocorrer alguns erros como “Overstemming” e “Understemming”.

Overstemming acontece quando o processo retira caracteres que não fazem parte de uma flexão ou derivação da palavra, mas fazem parte do radical. Pode resultar em um mesmo radical para palavras distintas.

Understemming ocorre quando ainda sobram caracteres de derivação ou flexão da palavra após o processo de stemming. Podendo resultar em radicais distintos para palavras de mesma origem.


O algoritmo e treinamento

Antes de fazer o treinamento precisamos do algoritmo para treinar. Um dos algoritmos mais utilizados em problemas de classificação é o ‘Naive Bayes’, baseado no teorema de Bayes, utiliza um método estatístico simples para reconhecer padrões. O algoritmo Naive Bayes assume que existe uma independência entre os dados que será dado como entrada para treinamento, no nosso caso, cada palavra será independente uma da outra e a presença ou não de uma no comentário não está relacionada com qualquer outra palavra.

“Em um processo de classificação no qual um exemplar com classe desconhecida seja apresentado, o Naive Bayes tomará a decisão sobre qual é a classe daquele exemplar, por meio do cálculo de probabilidades condicionais. Ele faz isso calculando as probabilidades de ele pertencer à cada uma das diferentes classes existentes no conjunto de treinamento e então classifica o exemplar pela classe com maior probabilidade.” Fonte

Após o pré-processamento temos os dados limpos, consistentes, podemos enviá-los como entrada para o treinamento do algoritmo escolhido. O algoritmo tentará localizar padrões e características que mapeiam o comentário para sua classe já definida.

O naive bayes entende cada texto como algo chamando bag-of-words, que representa a frequência que cada palavra aparece no texto. Tomando como exemplo os comentários: "que filme ruim" e "filme muito ruim, não gostei" como negativo e "muito bom, gostei muito desse filme" como positivo, gerando a bag-of-words de todos os comentários temos o seguinte:

["que", "ruim", "filme", "muito", "não", "gostei", "desse", "bom"]

Assim, podemos representar os comentários de exemplo como uma frequência de palavras:

Comentário/Palavra que ruim filme muito não gostei desse bom Classe
que filme ruim 1 1 1 0 0 0 0 0 Negativo
filme muito ruim, não gostei 0 1 1 1 1 1 0 0 Negativo
muito bom, gostei muito desse filme 0 0 1 2 0 1 1 1 Positivo

Dessa forma, o algoritmo calcula a probabilidade de cada palavra pertencer a cada classe, nesse exemplo simples, podemos perceber que a palavra “ruim” tem uma maior probabilidade de ser da classe Negativo.


Teste e avaliação

Uma parte dos dados de treinamento é separado, e não utilizados na etapa anterior, para servir como dados de teste, após o treinamento, esses dados são enviados para o algoritmo para que seja feito a classificação dos mesmos pelo algoritmo treinado, esses dados de teste não devem ser nenhum dos mesmos utilizados na etapa de treinamento. Após o teste normalmente temos algumas informações de desempenho do algoritmo como, por exemplo, a acurácia, quantidade de falsos positivos e falsos negativos, entre outras.

Com essas informações podemos e devemos analisar a qualidade do treinamento e do algoritmo e perceber se atende o objetivo final, como Machine Learning é um processo empírico, caso não atenda a necessidade, podemos realizar a escolha de um outro algoritmo e proceder com as etapas de treinamento, teste e avaliação novamente até que se encontre alguma técnica que resulte em métricas aceitáveis para o problema.



Let’s code

Primeiramente vamos criar um projeto novo usando o Leiningen.
Caso não saiba como criar um projeto em clojure, tem um artigo com uma introdução para criação projetos e dependências em Clojure:


No terminal vamos executar o seguinte comando:

lein run

Iremos utilizar duas bibliotecas para auxiliar o desenvolvimento, a snowball-stemmer e a clj-ml. A snowball tem uma função de stemming e suporte ao idioma português, e a clj-ml é uma lib de machine learning, tem algumas funções e algoritmos prontos para nós usarmos. Então vamos adicioná-las como dependências no arquivo project.clj.

Após, dentro do diretório raiz do projeto, vamos executar o comando para instalar as depêndencias.
lein deps

Para separar as funções, iremos criar um novo arquivo chamado pre-processing dentro do diretório src/comment_analysis, nesse arquivo irá ficar as funções de pré-processamento dos comentários.

No início do arquivo vamos definir o namespace e fazemos os requires dos namespaces que iremos usar (linha 1).

Definimos uma função stems usando a lib snowball (linha 6).

Logo após temos a função get-stemmin-words, que recebe um comentário, cria um vetor das palavras e faz o processo de stemming em cada palavra do comentário (linha 8).

A função remove-stop-words é responsável por fazer a remoção das stop-words do comentário que recebe por parâmetro (linha 22).

A deaccent remove as acentuações das palavras, deixando as letras sem acentos (linha 26).

A função remove-punctuation-and-symbols faz o processo de remoção de pontuações e alguns simbolos e caracteres (linha 33).

Após temos a função remove-unnecessary-spaces que remove os espaços extras que tiverem no comentário, deixando apenas um espaço onde deva ter (linha 37).

Temos a função remove-excess-characters que remove os caracteres excessívos, por exemplo a palavra “muuuuuito” vira “muito” (linha 41).

A função make-bag-of-words criará a nossa lista de todas as palavras existentes no datasset, essa lista será utilizada para na criação do algoritmo de classificação e preparação dos dataset de treinamento e teste (linha 45).

Após temos a função prepare-dataset que irá preparar o nosso dataset antes de ser usado para o treinamento e teste do nosso algoritmo (linha 53).

E a função “principal” preprocess que na prática executa as outras funções em sequência passando o comentário que recebe por parâmentro.


No arquivo core.clj iremos escrever nosso código para preparar os comentários, montar nosso datasset para ser usado para treinamento e teste do algoritmo. Primeiramente vamos fazer o require dos namespaces.


Após vamos criar duas funções para fazer a leitura dos arquivos que contém os comentários, esses arquivos estarão em um diretório comments na raiz do projeto.


A função get-comment-and-class recebe uma string do comentário e sua classe e retorna um map separando o comentário da classe, já passado pelo pré-processamento, no formato.
{:comment comentario :class class}


A próxima função get-frequencies-words recebe uma estrutura gerada pela função get-comment-and-class e retorna um map com cada palavra, já passado pelo processo de stemming, como chave e a quantidade dessa palavra no comentário como valor, além da classe do comentário.

Por exemplo, o comentário: clj {:class 0 :comment "eu nao gostei desse filme"} ao ser passado para a função get-frequencies-words retorna a seguinte estrutura.
{ :class 0 :eu 1 :nao 1 :gost 1 :desse 1 :film 1 }


As duas funções seguintes são usadas para construir um vetor de map com a quantidade das palavras de todos os comentários, esse processo executa separado para os comentários positivos e para os negativos.

Usando o mesmo comentário anterior como exemplo, essa função retorna o seguinte vetor.
[{:class 0 :eu 1 :nao 1 :gost 1 :desse 1 :film 1} ...]


Como citado acima, uma parte dos dados de dataset é separado para treino e outra para teste, a função divide-dataset faz esse processo. A função recebe o vetor retornado pelas funções define-data-words-* e divide em ~70% dos itens para treino e os outros ~30% para teste.

Essa função retorna também um map no seguinte formato:
{ :train [{:class 0 :eu 1 :nao 1 :gostei 1 :desse 1 :filme 1} ...}] :test [{...} {...}] }


A função define-data-words cria o conjunto de dados para treino e teste chamando as funções anteriores e fazendo a união dos dados referente aos comentários negativos e positivos para treino e teste, para ser mais aleatório embaralho os dados usando a função shuffle.

Retornando uma estrutura semelhante a anterior, mas com todos os dados dos comentários positivos e negativos no mesmo vetor.
{ :train [{:class 0 :eu 1 :nao 1 :gostei 1 :desse 1 :filme 1} ...}] :test [{...} {...}] }


Temos as funções para criar os datasets de treino e teste utilizando as funções da lib clj-ml, criando o dataset, aplicando filtro no atributo referente a classe do comentário e definindo esse atributo como classe.


E a função que realiza o treinamento do classificador e a avaliação do mesmo, logo após imprime as informações de desempenho do algoritmo.


A função main quando chamada realiza as chamadas para as outras funções para criação dos datasets e após chama a função de treinamento e avaliação do classificador, assim mostrando as informações de desempenho do algoritmo.


Entrando no diretório raiz do projeto e executando o comando lein run para iniciar a execução do código. Inicia o processo de preparação, treinamento e teste do algoritmo utilizado, assim que acabar irá mostrar os resultados da avaliação.

As informações retornadas serão semelhantes a estas:

Destaque para o sumário (linha 33), que nos mostra a quantidade e porcentagem de comentários classificados corretamente e incorretamente, o total de comentários utilizado para o teste. Também temos a matrix de confusão (linha 55) que mostra quantos comentários foram classificados como positivos (1) e negativos (0).

No exemplo podemos identificar que de 396 comentários negativos ele classificou 300 como negativos (correto) e 96 como positivos (incorreto) e de 396 comentários positivos ele classificou 210 como positivo (correto) e 186 como negativo (incorreto). Podemos perceber que está acertando mais os comentários negativos do que os positivos. Assim podemos avaliar as métricas necessárias e definir se atende as necessidades do problema em que será aplicado.


Conclusão e observações

No caso de estudo foi usado Machine Learning para classificação de comentários sobre filmes, esses comentários foram retirados de um site sobre cinema usando webscraping e salvos em arquivos de textos, para definir de qual classe os comentários eram, Positivo (1) e Negativo (0) foi utilizado a nota dada, também no respectivo site, foi assumido notas 4 e 5 como um comentário Positivo e notas igual ou abaixo de 2 como um comentário Negativo.
Foi usado um total 2634 comentários, metade negativos e metade positivos, foi usado o algoritmo Naive Bayes como classificador e utilizado ~70% dos comentários de cada classe para treinamento e os outros ~30% para teste. Após o treinamento e teste devemos observar as métricas e avaliar se são aceitáveis e atendem o problema em que será aplicado.

  • Durante o processo de captura dos dados foi notado que, mesmo alguns comentários visivelmente positivos estavam com nota baixa e consequentemente entraram como comentários negativos, o oposto também foi observado. Casos assim podem causar inconsistência no treinamento do algoritmo ocorrendo uma baixa qualidade nos resultados de teste, a qualidade do dataset tem enorme influência nos resultados finais.

  • O processo de remoção de stopwords e stemming também pode influenciar no treinamento, a lista de palavras consideradas como stopwords tem de ser avaliada e observado se não está removendo palavras que tenham importância para o contexto, realizei alguns testes com e sem o processo de stemming e remoção de stopwords e o melhor resultado ocorreu deixando os dois processos.


O código completo podem ver aqui

Dúvidas, sugestões, críticas… podem deixar nos comentários, e compartilhar caso gostou :D.



Referências:
  • http://bdm.unb.br/bitstream/10483/11042/1/2015_LucasBragaRibeiro.pdf
  • https://www.maxwell.vrac.puc-rio.br/13212/13212_4.PDF
  • https://www.infoq.com/br/presentations/classificacao-de-documentos-baseada-em-inteligencia-artificial
  • https://www.safaribooksonline.com/library/view/clojure-for-data/9781784397180/ch04s13.html
  • https://pt.slideshare.net/jonmagal/naive-bayes-16122964
  • https://pt.slideshare.net/plataformatec/classificao-de-textos-dev-in-sampa-28nov2009
  • https://pt.slideshare.net/matheusgaldino355/teoria-decisao-bayes
  • https://gabrielschade.github.io/2018/04/16/machine-learning-classificador.html
comments powered by Disqus