Perceptron

algoritmo para aprendizagem supervisionada de classificadores binários

O perceptron foi inventado em 1943 por Warren McCulloch e Walter Pitts. A primeira implementação de hardware foi a máquina Mark I Perceptron construída em 1957 por Frank Rosenblatt no Cornell Aeronautical Laboratory.[1][2] Ele pode ser visto como o tipo mais simples de rede neural feedforward: um classificador linear.[1][2]

Definição

editar

O perceptron é um classificador binário que mapeia sua entrada   (um vetor de valor real) para um valor de saída   (uma valor binário simples) através da matriz.

 

Onde   é um vetor de peso real e   é o produto escalar (que computa uma soma com pesos) e   é o viés (do inglês "bias"), um termo constante que não depende de qualquer valor de entrada.


Implementação em Python

editar
'''
	Este projeto esta disponivel no GiHub de Marcos castro de Sousa
	Implementação da rede neural Perceptron
	w = w + N * (d(k) - y) * x(k)
'''

import random, copy

class Perceptron:

	def __init__(self, amostras, saidas, taxa_aprendizado=0.1, epocas=1000, limiar=-1):

		self.amostras = amostras # todas as amostras
		self.saidas = saidas # saídas respectivas de cada amostra
		self.taxa_aprendizado = taxa_aprendizado # taxa de aprendizado (entre 0 e 1)
		self.epocas = epocas # número de épocas
		self.limiar = limiar # limiar
		self.num_amostras = len(amostras) # quantidade de amostras
		self.num_amostra = len(amostras[0]) # quantidade de elementos por amostra
		self.pesos = [] # vetor de pesos


	# função para treinar a rede
	def treinar(self):
		
		# adiciona -1 para cada uma das amostras
		for amostra in self.amostras:
			amostra.insert(0, -1)

		# inicia o vetor de pesos com valores aleatórios
		for i in range(self.num_amostra):
			self.pesos.append(random.random())

		# insere o limiar no vetor de pesos
		self.pesos.insert(0, self.limiar)

		# inicia o contador de epocas
		num_epocas = 0

		while True:

			erro = False # o erro inicialmente inexiste

			# para todas as amostras de treinamento
			for i in range(self.num_amostras):

				u = 0

				'''
					realiza o somatório, o limite (self.amostra + 1)
					é porque foi inserido o -1 para cada amostra
				'''
				for j in range(self.num_amostra + 1):
					u += self.pesos[j] * self.amostras[i][j]

				# obtém a saída da rede utilizando a função de ativação
				y = self.sinal(u)

				# verifica se a saída da rede é diferente da saída desejada
				if y != self.saidas[i]:

					# calcula o erro: subtração entre a saída desejada e a saída da rede
					erro_aux = self.saidas[i] - y

					# faz o ajuste dos pesos para cada elemento da amostra
					for j in range(self.num_amostra + 1):
						self.pesos[j] = self.pesos[j] + self.taxa_aprendizado * erro_aux * self.amostras[i][j]

					erro = True # ainda existe erro

			# incrementa o número de épocas
			num_epocas += 1

			# critério de parada é pelo número de épocas ou se não existir erro
			if num_epocas > self.epocas or not erro:
				break


	# função utilizada para testar a rede
	# recebe uma amostra a ser classificada e os nomes das classes
	# utiliza a função sinal, se é -1 então é classe1, senão é classe2
	def testar(self, amostra, classe1, classe2):

		# insere o -1
		amostra.insert(0, -1)

		# utiliza o vetor de pesos que foi ajustado na fase de treinamento
		u = 0
		for i in range(self.num_amostra + 1):
			u += self.pesos[i] * amostra[i]

		# calcula a saída da rede
		y = self.sinal(u)

		# verifica a qual classe pertence
		if y == -1:
			print('A amostra pertence a classe %s' % classe1)
		else:
			print('A amostra pertence a classe %s' % classe2)


	# função de ativação: degrau bipolar (sinal)
	def sinal(self, u):
		return 1 if u >= 0 else -1


print('\nA ou B?\n')

# amostras: um total de 4 amostras
amostras = [[0.1, 0.4, 0.7], [0.3, 0.7, 0.2], 
				[0.6, 0.9, 0.8], [0.5, 0.7, 0.1]]

# saídas desejadas de cada amostra
saidas = [1, -1, -1, 1]

# conjunto de amostras de testes
testes = copy.deepcopy(amostras)

# cria uma rede Perceptron
rede = Perceptron(amostras=amostras, saidas=saidas,	
						taxa_aprendizado=0.1, epocas=1000)

# treina a rede
rede.treinar()

# testando a rede
for teste in testes:
	rede.testar(teste, 'A', 'B')

Implementação em C#

editar
 
using System;
using System.Linq;

namespace perceptron
{
    public class Perceptron
    {
        static readonly double[] w = new double[3];
        private readonly int[,] _matrizAprendizado = new int[4, 3];
        private double _net;

        Perceptron()
        {
             //tabela AND
            _matrizAprendizado[0, 0] = 0;
            _matrizAprendizado[0, 1] = 0;
            _matrizAprendizado[0, 2] = 0;

            _matrizAprendizado[1, 0] = 0;
            _matrizAprendizado[1, 1] = 1;
            _matrizAprendizado[1, 2] = 0;

            _matrizAprendizado[2, 0] = 1;
            _matrizAprendizado[2, 1] = 0;
            _matrizAprendizado[2, 2] = 0;

            _matrizAprendizado[3, 0] = 1;
            _matrizAprendizado[3, 1] = 1;
            _matrizAprendizado[3, 2] = 1;

            w[0] = 0;
            w[1] = 0;
            w[2] = 0;
        }

        public static void Main(string[] args)
        {
            //pesos antes do treinamento
            w.ToList().ForEach(x => Console.WriteLine(x + ","));

            Console.WriteLine("\n");

            //efetua-se o treinamento da rede
            new Perceptron().Treinar();

            Console.WriteLine("\n");

            //pesos ajustados após treinamento
            w.ToList().ForEach(x => Console.WriteLine(x + ","));

            //dados de entrada para rede treinada, 0 e 0 resulta em 0 (tabela and) -1 corresponde ao BIAS

            int[] amostra1 = { 0, 1, -1 }; // 0 e 1 -> 0 Classe B
            int[] amostra2 = { 1, 0, -1 }; // 1 e 0 -> 0 Classe B
            int[] amostra3 = { 0, 0, -1 }; // 0 e 0 -> 0 Classe B
            int[] amostra4 = { 1, 1, -1 }; // 1 e 1 -> 1 Classe A


            ClassificarAmostra(amostra1);
            ClassificarAmostra(amostra2);
            ClassificarAmostra(amostra3);
            ClassificarAmostra(amostra4);

            Console.ReadKey();
        }

        public static void ClassificarAmostra(int[] amostra)
        {
            //pesos encontrados após o treinamento
            int[] pesos = { 2, 1, 3 };

            //aplicação da separação dos dados linearmente após aprendizado
            var u = amostra.Select((t, k) => pesos[k] * t).Sum();

            var y = LimiarAtivacao(u);

            Console.WriteLine(y > 0 ? "Amostra da classe A >= 0" : "HelloWorld < 0");
        }

        private static int LimiarAtivacao(double u)
        {
            return (u >= 0) ? 1 : 0;
        }

        int Executar(int x1, int x2)
        {
            _net = (x1 * w[0]) + (x2 * w[1]) + ((-1) * w[2]);

            return (_net >= 0) ? 1 : 0;
        }

        public void Treinar()
        {
            var treinou = true;

            for (var i = 0; i < _matrizAprendizado.GetLength(0); i++)
            {
                var saida = Executar(_matrizAprendizado[i, 0], _matrizAprendizado[i, 1]);

                if (saida != _matrizAprendizado[i, 2])
                {
                    CorrigirPeso(i, saida);

                    treinou = false;
                }
            }

            if (!treinou)
                Treinar();
        }

        void CorrigirPeso(int i, int saida)
        {
            w[0] = w[0] + (1 * (_matrizAprendizado[i, 2] - saida) * _matrizAprendizado[i, 0]);
            w[1] = w[1] + (1 * (_matrizAprendizado[i, 2] - saida) * _matrizAprendizado[i, 1]);
            w[2] = w[2] + (1 * (_matrizAprendizado[i, 2] - saida) * (-1));
        }
    }
}

Implementação em Ruby

editar
  #
 # Classe PERCEPTRON responsável para aprendizado e resolução da tabela AND
 #

class Perceptron 

  def initialize
    # pesos sinápticos [0] entrada 1, [1] entrada 2, [3]BIAS
    @w   = []

    # variável responsável pelo somatório(rede).
    @net = 0

    # variavél responsável pelo número máximo de épocas
    @epocasMax = 30

    # variável responsável pela contagem das épocas durante o treinamento
    @count      = 0

    # declara o vetor da matriz de aprendizado
    @matriz_aprendizado = []

    self.inicia_matriz
  end

  def inicia_matriz
    # Primeiro valor
    @matriz_aprendizado[0]    = []
    @matriz_aprendizado[0][0] = 0; # entrada 1
    @matriz_aprendizado[0][1] = 0; # entrada 2
    @matriz_aprendizado[0][2] = 0; # valor esperado

    # Segundo Valor
    @matriz_aprendizado[1]    = []
    @matriz_aprendizado[1][0] = 0; # entrada 1
    @matriz_aprendizado[1][1] = 1; # entrada 2
    @matriz_aprendizado[1][2] = 0; # valor esperado

    # terceiro valor
    @matriz_aprendizado[2]    = []
    @matriz_aprendizado[2][0] = 1; # entrada 1
    @matriz_aprendizado[2][1] = 0; # entrada 2
    @matriz_aprendizado[2][2] = 0; # valor esperado

    # quarto valor
    @matriz_aprendizado[3]    = []
    @matriz_aprendizado[3][0] = 1; # entrada 1
    @matriz_aprendizado[3][1] = 1; # entrada 2
    @matriz_aprendizado[3][2] = 1; # valor esperado
    
    # inicialização dos pesos sinápticos

    # Peso sináptico para primeira entrada.
    @w[0] = 0;
    # Peso sináptico para segunda entrada.
    @w[1] = 0;
    # Peso sináptico para o BIAS
    @w[2] = 0;
  end

  # Método responsávelpelo somatório e a função de ativação.
  def executar(x1, x2)
    # Somatório (NET)
    @net = (x1 * @w[0]) + (x2 * @w[1]) + ((-1) * @w[2]);

    # Função de Ativação
    return 1 if (@net >= 0) 

    return 0;
  end

  # Método para treinamento da rede
  def treinar() 
    # variavel utilizada responsável pelo controlede treinamento recebefalso
    treinou = true;
    
    # varável responsável para receber o valor da saída (y)
    saida   = nil;

    # laço usado para fazer todas as entradas
    @matriz_aprendizado.length.times do |i|
      
      # A saída recebe o resultado da rede que no caso é 1 ou 0
      saida = self.executar(@matriz_aprendizado[i][0], @matriz_aprendizado[i][1]);
     
      if (saida != @matriz_aprendizado[i][2]) 
        # Caso a saída seja diferente do valor esperado
        
        # os pesos sinápticos serão corrigidos
        self.corrigirPeso(i, saida);

        # a variavél responsável pelo controlede treinamento recebe falso
        treinou = false;
      end
    end

    # acrescenta uma época
    @count+=1;

    # teste se houve algum erro duranteo treinamento e o número de epocas
    #é menor qe o definido
    if(not treinou and (@count < @epocasMax))
      # chamada recursiva do método
      self.treinar();
    end
  end    # fim do método para treinamento

  # Método para a correção de pesos
  def corrigirPeso(i, saida) 
    @w[0] = @w[0] + (1 * (@matriz_aprendizado[i][2] - saida) * @matriz_aprendizado[i][0]);
    @w[1] = @w[1] + (1 * (@matriz_aprendizado[i][2] - saida) * @matriz_aprendizado[i][1]);
    @w[2] = @w[2] + (1 * (@matriz_aprendizado[i][2] - saida) * (-1));
  end
end

Implementação em Java

editar
 
 /*
 * Classe PERCEPTRON responsável para aprendizado e resolução da tabela AND
 */

public class Perceptron {

    // pesos sinápticos [0] entrada 1, [1] entrada 2, [3]BIAS
    private double[] w = new double[3];

    // variável responsável pelo somatório(rede).
    private double NET = 0;

    // variavél responsável pelo número máximo de épocas
    private final int epocasMax = 30;

    // variável responsável pela contagem das épocas durante o treinamento
    private int count = 0;

    // declara o vetor da matriz de aprendizado
    private int[][] matrizAprendizado = new int[4][3];

    // MÉTODO DE RETORNO DO CONTADOR
    public int getCount(){

      return this.count;

    }
 // metodo de inicialização inicia o vetor da matriz de aprendizado
  Perceptron() {

    // Primeiro valor
    this.matrizAprendizado[0][0] = 0; // entrada 1
    this.matrizAprendizado[0][1] = 0; // entrada 2
    this.matrizAprendizado[0][2] = 0; // valor esperado

    // Segundo Valor
    this.matrizAprendizado[1][0] = 0; // entrada 1
    this.matrizAprendizado[1][1] = 1; // entrada 2
    this.matrizAprendizado[1][2] = 0; // valor esperado

    // terceiro valor
    this.matrizAprendizado[2][0] = 1; // entrada 1
    this.matrizAprendizado[2][1] = 0; // entrada 2
    this.matrizAprendizado[2][2] = 0; // valor esperado

    // quarto valor
    this.matrizAprendizado[3][0] = 1; // entrada 1
    this.matrizAprendizado[3][1] = 1; // entrada 2
    this.matrizAprendizado[3][2] = 1; // valor esperado
    
    // inicialização dos pesos sinápticos

    // Peso sináptico para primeira entrada.
    w[0] = 0;
    // Peso sináptico para segunda entrada.
    w[1] = 0;
    // Peso sináptico para o BIAS
    w[2]= 0;
       
}

  // Método responsávelpelo somatório e a função de ativação.
    int executar(int x1, int x2) {

        // Somatório (NET)
        NET = (x1 * w[0]) + (x2 * w[1]) + ((-1) * w[2]);

        // Função de Ativação
        if (NET >= 0) {
            return 1;
        }
        return 0;
    }

    // Método para treinamento da rede
    public void treinar() {

        // variavel utilizada responsável pelo controlede treinamento recebefalso
        boolean treinou= true;
        // varável responsável para receber o valor da saída (y)
        int saida;

        // laço usado para fazer todas as entradas
        for (int i = 0; i < matrizAprendizado.length; i++) {
            // A saída recebe o resultado da rede que no caso é 1 ou 0
            saida = executar(matrizAprendizado[i][0], matrizAprendizado[i][1]);
            
           
            if (saida != matrizAprendizado[i][2]) {
                // Caso a saída seja diferente do valor esperado
                
                // os pesos sinápticos serão corrigidos
                corrigirPeso(i, saida);
                // a variavél responsável pelo controlede treinamento recebe falso
                treinou = false;

            }
        }
        // acrescenta uma época
        this.count++;

        // teste se houve algum erro duranteo treinamento e o número de epocas
        //é menor qe o definido
        if((treinou == false) && (this.count < this.epocasMax)) {
            // chamada recursiva do método
            treinar();

        }

    }    // fim do método para treinamento

    // Método para a correção de pesos
    void corrigirPeso(int i, int saida) {

        w[0] = w[0] + (1 * (matrizAprendizado[i][2] - saida) * matrizAprendizado[i][0]);
        w[1] = w[1] + (1 * (matrizAprendizado[i][2] - saida) * matrizAprendizado[i][1]);
        w[2] = w[2] + (1 * (matrizAprendizado[i][2] - saida) * (-1));
}}

Referências

  1. a b Freund, Yoav; Schapire, Robert E. (1999). «Large Margin Classification: Using the Perceptron Algorithm» (PDF). UC San Diego. 37 (3): 2. Consultado em 23 de junho de 2020 
  2. a b Lefkowitz, Melanie (25 de setembro de 2019). «Professor's perceptron paved the way for AI – 60 years too soon». Cornell Chronicle (em inglês). Consultado em 23 de junho de 2020 
  NODES
todo 13