Reading Time: 6 minutes

Neste artigo apresento uma introdução teórica às redes neuronais e também todos os passos e devido código em C++ de como construir uma rede neuronal com apenas um neurónio.

 

Introdução – O que é uma Rede Neuronal

Básicamente, uma rede neuronal é uma estrutura composta por neurónios, neurónios estes artificiais.
Nesta abordagem iremos ver e aprender a criar uma rede com apenas um neurónio, mas as redes comumentes usadas são constituídas por algumas dezenas de neurónios, chamadas MLP (Perceptron Multi Layer), Redes Neuronais Multi-Camada, que possuem mais que uma camada.
Fig1: Rede Neuronal (1 camada escondida).
fonte: http://pt.wikipedia.org/wiki/Ficheiro:Neuralnetwork.png

 

Um neurónio biológico, esquematizado na figura 2, consiste numa única célula capaz de realizar um forma simples de processamento. Cada neurónio é estimulado por uma ou mais ligações vindas de outros neurónios, chamadas sinapses, dependendo o sinal produzido tanto da força das ligações como da sua natureza (inibitória, excitatória, etc). Este sinal é propagado ao longo do axónio indo, por sua vez, estimular outros neurónios. O funcionamento dos neuónios artificiais (figura 3) baseia-se, na generalidade dos casos, neste modelo simplificado dos neurónios biológicos.
Fig2: Neurónio Biológico
Fig3: Neurónio Artificial
Portanto, a composição do neurónio é a ilustrada na figura 3, onde temos do lado esquerdo as entradas do neurónio (x1..xn), os pesos relativos a cada entrada do neurónio (w1..wn), a soma pesadas das entradas com os seus pesos respetivos e a função de ativação que irá posteriormente gerar a saída do neurónio. Este processo é aplicado em todos os neurónios, logo uma MPL, uma multi-camada segue o mesmo padrão com algumas alterações.

 

Os pesos (w1..wn) é o que irá permitir ajustar o nosso neurónio à resolução do problema, digamos que ele vai tentando valores até que encontre os valores ideais para os pesos para a resolução do nosso problema.
A esse processo de ajustar os pesos do nosso neurónio chamamos “descida do gradiente”, que é o processo que irá ser utilizado para efetuar o ajuste.

 

Este método irá permitir encontrar os pesos que minimizem o erro do nosso problema. Neste contexto o erro corresponde à diferença entre o valor que desejamos obter com o da saída produzida pelo neurónio.

 

Erro= (desejado-obtido_pelo_neuronio)^2

 

Como referido na Fig 3, é necessário definir uma função de ativação, que serve como motor de disparo do neurónio. Neste caso a função de ativação é engatada neste processo de atualização dos pesos.

 

A função usada é a Sigmoid.
A função de sigmoid simplificada é a seguinte:

 

lameda* f (x)(1 − f (x))

 

Nota: Quem quiser saber mais sobre este tipo de assunto consultar as referências deixadas no final do artigo, visto que o objetivo do artigo não é explicar a teoria mais sim explicar e esclarecer como implementar um neurónio artificial.

 

Tendo isto, o que pretendemos implementar com o artigo é um neurónio artificial, que irá classificar determinados pontos e agrupa-los em 2 conjuntos, visto que um neurónio apenas consegue dar resposta a problemas “binários”.
O sistema que pretendemos implementar é então o seguinte:
Vamos ter valores de entrada, e vamos inicializar os nossos pesos com valores aleatórios. Em seguida é efetuada a soma pesada dos valores de entrada com os pesos efetuando a atualização dos pesos com o método da descida do gradiente.

 

No final temos então a saída obtida, neste ponto vamos calcular a taxa do erro (valor_esperado-valor_obtido)^2.

 

Quando este valor for minimo devemos ter os pesos ideais em w1..wn.
O critério de paragem para este tipo de procedimentos é o número de épocas combinado com esta taxa de erro.

 

Código e Explicação

 

A implementação deste neurónio artificial foi dividida em dois ficheiros, “Neuronio.cpp” o ficheiro que contém todoas a funções necessárias para a construção do processo e o ficheiro “main.cpp” o típico ficheiro que chama a classe neurónio e a executa.
Passando então para o ficheiro “Neuronio.cpp”.

 

Sem pormenorizar demasiado, primeiramente é declarada uma variável constante de nome “lameda”, esta variável é usada no método de atualização dos pessos (descida do gradiente).
Tendo isto declaramos as variáveis do nosso neurónio, o “W” que é o vetor de pesos, a “taxa_aprendizagem” que é um parâmetro da descida do gradiente e traduz à rapidéz de aprendizagem,  a variável “tam” que diz respeito ao número de entradas do nosso neurónio.
Em seguida vamos contruir o construtor da classe.
O construtor é composto pela inicialização das variáveis da classe. Inicialmente iniciar a variável tam que tem a quantidade de entradas (duas neste caso) +1 , esta adição de um corresponde ao valor desejado.
Nota: Como devemos saber da teoria isto é um sistema supervisionado, logo nós conhecemos qual o valor desejado com a combinação daqueles valores de entrada.
Em seguida é inicializado os pesos da entradas (duas) e a taxa de aprendizagem.
Os pesos são inicializados aleatóriamente, daí esta função de inicialização.
A função de ativação mais usada neste tipo de sistemas é a função sigmoid.
Foi foi também constuída a função para mostrar a taxa de aprendizagem durante a aprendizagem.
A função de atualização dos pesos é agora apresentada (descida do gradiente).
As duas funções construídas calcula e aprendizagem são das mais importantes no algoritmo.
1) A função calcula calcula  o valor de disparo do neurónio com os pesos atuais.
2) A função Aprendizagem usa a função calcula para obter o valor de disparo do neurónio, faz a atualização dos pesos (descida do gradiente) e calcula a taxa de erro entre o valor desejado e o valor obtido. Esta função retorna essa taxa de erro que será essencial para “critério de paragem”.
Em seguida são constuídas outras 3 funções, uma para listagem dos pesos (será usada no final de todo o processo para verificar os pesos que mais se ajustaram ao problema, neste caso aos dados que iremos fornecer no main.cpp), e outras duas funções, nomeadamente o get e set para o vetor de pesos.
Por fim temos a função teste, esta é a função que agrupa todas as outras funções e verifica o critério de paragem.
Inicialmente é definido um número máximo de épocas (100) e é chamada a função “Aprendizagem” que retorna a taxa de erro. Para cada ponto é testada essa taxa de erro, e o valor obtido já for “semelhante” ou “igual” ao valor desejado então estamos no bom caminho. Se esta condição se verificar para todos os pontos, ou seja, se todos os valores obtidos de cada ponto for “semelhante” ao desejado então podemos parar o nosso processo de aprendizagem, visto que já temos uma resposta ao problema e os pesos ideais.
Se observarmos necessitamos de 18 épocas no mínimo para resolver este problema.
Em cima falo em valores semalhantes ou iguais, mas é mau dizer valores iguais, visto que estes problemas de redes neuronais não devem apresentar taxas de erro exatas, porque isso é sinal notório que o nosso programa está optimizado a um conjunto de treino especifico e perde a capacidade de generalizar com outros conjuntos de teste.
O ficheiro main.cpp fica então da seguinte maneira:
Inicialmente declarámos o números de caraterísticas (neste caso duas entradas, que representa as duas primeiras colunas na matriz criada, a 3 coluna representa o valor desejado).  Seguidamente definida a quantidades de pontos, (linhas da matriz) e inicializada e constuída a matriz com valores “normalizados”, ou seja tanto valores da caraterísticas 1 (coluna 1) como da caraterística 2 (coluna 2) estão na mesma escala, de zero a um.
Por fim é criado um objeto da classe neurónio e inicializado com a função de ativação sigmoid.
É passo o valor 10, que serve como semente na inicialização do pesos e seguidamente manda-se executar o procedimento que encontrará os pesos ideais para este nosso conjunto de treino definido estaticamente.
Os pesos finais (não são os minímos) mas que separam os valores em duas classes, que são as classes definidas como valor desejado (zero ou um) são então os seguintes:
W[0] = 0.218556
W[1] = 0.683034
W[2] = 0.623395
Relembrar que foram necessárias no mínimo sem exigir mais processamento 18 épocas.
O resultado final com os pesos acima indicados é então o seguinte, como era desejado.
Deixo então o link para download do código fonte construído para este artigo.
——————
Referências:
[1] http://pt.wikipedia.org/wiki/Rede_neural
[2] http://ltodi.est.ips.pt/mmoreira/PUBLICACOES_P/introducao_redes_neuronais_1997.pdf
[3] http://www.gsigma.ufsc.br/~popov/aulas/ia/modulo9.pdf

Pedro Tavares is a professional in the field of information security, currently working as IT Security Engineer. He is also a founding member and Pentester at CSIRT.UBI and founder of the security computer blog seguranca-informatica.pt.

In recent years he has invested in the field of information security, exploring and analyzing a wide range of topics, such as pentesting (Kali Linux), malware, hacking, cybersecurity, IoT and security in computer networks.  He is also Freelance Writer.

Read more here.