Redes Neurais Artificiais

Redes Neurais artificiais é um assunto cada vez mais comum de ser visto em notícias. Principalmente, em novas habilidades que os computadores executam, como reconhecimento de rostos de foragidos, diagnóstico de doenças, produção de músicas e até mesmo carros autônomos. Os feitos das redes neurais são muito divulgados e comentados, mas você conhece como essa tecnologia realmente funciona?

Neste post vamos explicar seu funcionamento, tomando como base o primeiro algoritmo de redes neurais, o Perceptron. Esse é o segundo de um sequenciamento de três artigos, (o primeiro pode ser acessado por este link) e introduziremos o assunto de redes neurais. Você também pode acessar aqui um artigo sobre aprendizado de máquina, recomendável como introdução a este.

Um pouco de história

Apesar de alcançar uma ampla fama atualmente, redes neurais artificiais é um assunto de pesquisa a vários anos. Iniciado em 1943 com os trabalhos de McCulloch e Pitts onde foi criado o primeiro modelo de aprendizado para os neurônios que pudesse ser usado computacionalmente, posteriormente Heebs observou a processo de sinapse entre dois neurônios biológicos, deduzindo que estas células aprendem fortalecendo as conexões entres outros neurônios. Este trabalho inspirou Rosemblatt a criação do algoritmo Perceptron em 1962, e assim cria-se o modelo de neurônio artificial que é utilizado até hoje na maioria dos algoritmos.

 

Inspirado na biologia

Para entendermos o algoritmo Perceptron, temos que primeiro entender a estrutura básica do neurônio.

Um neurônio natural se comunica enviando e recebendo impulsos elétricos dos demais em sua volta. Cada neurônio básico tem sua estrutura similar a apresentada na imagem abaixo. Eles recebem sinais pelos dendritos, os quais são processados pelo núcleo e repassados pelo axônio para os demais através dos seus terminais.

Perceptron

O Perceptron representa um neurônio artificial baseado no neurônio natural descrito acima. Sua forma base está demonstrada abaixo. Ele recebe um conjunto de dados em camadas de inputs (similar aos dendritos de um neurônio natural). Cada dado recebido é multiplicado um peso w. O valor de equação é somado e passado por uma função de ativação F(x) no núcleo, que retorna o resultado na camada de output (similar aos terminais de um neurônio natural).

Apesar de toda esta explicação talvez ainda não esteja claro como um neurônio artificial funciona, então para isto vamos dar um exemplo prático, tentando resolver a função AND, uma função clássica da lógica booleana. A função AND tem seu retorno como a imagem abaixo, onde x1 e x2  são as entradas da função e y sua saída:

 

X1 X2 Y
 Verdadeiro   Verdadeiro   Verdadeiro 
Falso Verdadeiro Falso
Verdadeiro Falso Falso
Falso Falso Falso

 

Vamos considerar que verdadeiro tem o valor 1 e falso o valor 0, (vamos utilizar essa nomenclatura para o restante do artigo) assim teríamos:

X1 X2 Y
1 1 1
0 1 0
1 0 0
0 0 0

Vamos imaginar um Perceptron que tente resolver esta função. Os valores da entrada da função (X1,X2) seriam seus inputs, para a função de ativação vamos criar uma função simples de lógica X > = 1, ou seja, se x for maior que 1 ele retorna verdadeiro, que será representado como 1, senão ele retorna falso, ou seja 0. Por fim vamos definir ambos os pesos arbitrariamente como 2, w1 = 2 e w2 = 2. Na prática os pesos não precisam ter o mesmo valor, porém vamos fazer desta maneira para simplificar o exemplo.

Vamos passar o valor da primeira linha de nossa tabela:

 

Aplicando os pesos em cada linha temos:

L1 = x1*1 = 2;

L2 = x2*2 = 2;

O somatório seria

S = 2 + 2 = 4;

Como 3 é maior que 1 temos o resultado como verdadeiro, ou seja, o output seria 1. Ou seja, o algoritmo está correto com a tabela!

Aprendizado

Ela consegue “aprender” com erro, ajustando o peso de cada entrada para que no próximo dado o erro não aconteça ou seja minimizado.  

Como exemplo vamos fazer a segunda linha e veja o que acontece:

Nesta linha temos:

 

Aplicando os pesos em cada linha

L1 = x1 * 1 = 0

L2 = x2 * 2 = 2

O somatório seria

S = 2 + 0 = 2

Como 2 é maior que 1 temos o resultado final sendo verdadeiro, ou seja, o output é 1.

Aqui temos um erro, pois na tabela original o valor para este caso é 0, desta maneira temos que ajustar os pesos (w) de cada linha de entrada. Neste caso, o erro possui o valor -1, pois ele deveria retornar 0 e retornou 1, ou seja, seja o tomamos como o valor do erro a diferença entre o resultado esperado e o gerado (0-1 = -1). Este erro deve ser somado aos pesos (w), mas para diminuir a alteração feita neles, podemos multiplicar este erro por um valor η, chamado de taxa de aprendizagem, no exemplo vamos utilizar a taxa 0.5. No final teremos que subtrair 0.5 (que é o resultado de w vezes n) de cada w. Porém se observamos, apenas a segunda linha influenciou o resultado, então só devemos atualizar o peso dela. Assim vamos ajustar aquele peso para 0.5. 

Matematicamente a função de correção do erro é:

Onde t é a saída esperada,  o é saída da rede e η é a taxa de aprendizado.

Este processo pode ser repetido para todos os dados, e caso ainda não encontre um valor de peso que nos retorne a saída para todas as entradas, o processo pode ser repetido até chegarmos a valores de w1 e w2 que acertem todas as linhas. O figura abaixo demonstra este processo passo a passo até encontrarmos os pesos corretos para a função AND.

Uma função linear

Para o exemplo acima nós simplificamos o perceptron, na versão original do algoritmo existe um valor a mais na camada de input, ele é chamado de bias (Viés), é representado na imagem abaixo com a letra b. O bias é uma entrada de valor constante 1, que assim como os demais dados de entrada (xs), também se aplica-se um peso w. Na prática, o bias funciona como como entrada normal, sua função é ajudar o aprendizado.

Observando o algoritmo original, na figura a acima, podemos resumir a rede neural como função abaixo:

Os mais experientes em matemática podem perceber que esta função é similar a equação da reta. Isto foi percebido por Minsky e Seymour em 1969, que provaram que o Percetron só é capaz de “aprender” funções lineares, ou seja, funções que podem ser separadas por uma única linha. Isto impediria o perceptron de aprender a maioria das funções, como, por exemplo, a função XOR (ou exclusivo), pois esta função não pode ser separada por uma reta. 

Abaixo temos uma demonstração de como o Perceptron cria uma reta, e assim aprende algumas as funções AND, OR e como não seria possível resolver o XOR.

Redes multicamadas

Esta limitação das redes fez o entusiasmo pelas redes neurais diminuir no meio científico. Até que, em 1975, Werbos possibilitou que um neurônio fosse conectado a outro, criando camadas de neurônios, e assim, o aprendizado poderia ser repassado de um para o outro através do algoritmo backpropagation.

Com isto a redes poderia ter estruturas como a seguinte:

Esta estrutura poderia aumentar incrementando neurônios ou camadas, criadas verdadeiras redes multicamadas, como a seguir:

 Na figura podemos ver uma a primeira camada, chamada de camada de entrada, na qual estão os valores pela rede. As próximas camadas, são chamadas de camada intermediárias, ou camadas escondida, e podem ser formadas de um ou mais grupos de neurônios. Por fim, temos a camada de saída, na qual será retornada a resposta final da rede.

Hoje em dia

Com o passar dos anos as redes neurais evoluiram ainda mais. Seu tamanho aumentou, as estratégias de aprendizado e até mesmo os modelos de redes se alteraram. Hoje existem redes multiperceptron, redes recorrentes, redes convolucionais, probabilísticas e  entre várias outras. Essas redes quando se tornam muito complexas (podem inclusive misturar várias técnicas diferentes), começaram a ser chamadas por um novo nome, Deep Learning. Seu aprendizado se torna tão complexo que se torna inviável descobrir o quanto cada input inicial influencia no resultado final.        

  No próximo capítulo vamos falar sobre uma rede específica: a rede neural convolucional, muito utilizada para detecção de imagens.

Código

           Agora que aprendemos a teoria vamos por ela em prática! Para isto, vamos fazer um algoritmo de redes neurais em Python:

X = [[1,1],[0,1],[1,0],[0,0]]
y = [1,0,0,0]

Para fazer nosso código a primeira coisa que precisamos são de dados. Os dados devem ser separados em X (os dados de entrada) e y (os resultados). Vamos fazer um exemplo similar ao demonstrado.

from sklearn.neural_network import MLPClassifier
modelo = MLPClassifier()
modelo.fit(X,y)


Implementar o Percetron e possibilitar que ele tenha múltiplas camadas, não é uma tarefa muito fácil. Por isso para ajudar nosso código, vamos utilizar bibliotecas que já fazem esse trabalho por nós. Esta biblioteca é o Sklearn, a qual possui vários algoritmos de aprendizado de máquina, entre elas as redes neurais apresentadas. Precisamos apenas criar nosso modelo e em seguida treiná-lo.        

Pronto rede criada e treinada! Simples não?! Agora vamos predizer dados novos para testar esta rede:

preditos = modelo.predict(X)

Com os dados preditos o próximo passo é verificar o erro, uma métrica muito comum e acurácia, que calcula quantos dados foram classificados corretamente de todo o conjunto de dados. Podemos utilizar o sklearn (novamente) para calcular este valor, ele retorna os valores entre 0 é 1. 

from sklearn.metrics import accuracy_score
accuracy_score(preditos,y)

Nosso retorno será 1, ou seja 100%!

O algoritmo aprendeu a classificar corretamente!

Este foi somente um exemplo de teste. Deixamos coisas cruciais de lado, como, por exemplo, a divisão entre um conjunto de treino e teste ou os hiperparâmetros da rede, mas isso já é assunto para próximos posts!