Archive for the ‘Algoritmo’ Category

Google’s Pacman Doodle – Engenharia Reversa

Postado em 21 mai 2010
Categoria(s) Algoritmo, CSS, Game, Jogo, Web 2.0

Na página do Google de hoje (21 de maio de 2010) tem um doodle do Pacman – e o mais interessante disso: é jogável! E é o primeiro doodle jogável do Google.

Sendo um geek, eu decidi dar uma olhada no código-fonte para descobrir como o Google fez isso.

Eu não fiz engenharia reversa do código inteiro, eu só queria ter uma ideia geral de como eles tinham feito isso. Parece-me que o Google, afinal, tem um código extramamente conciso e eficiente para resolver muitos dos seus problemas.

Assim, em primeiro lugar eles têm um elemento div que contém a área do jogo:

<div id=lga>
  <a href="/search?q=PAC-MAN+30th+Anniversary&ct=pacman10-hp&oi=ddle" id="dlink">
  </a>
  <div id=logo style="width:554px;height:186px;background:black url(logos/pacman10-hp.png) 0 0 no-repeat;position:relative;margin-bottom:9px" title="PAC-MAN's 30th Birthday! Doodle with PAC-MAN™ & ©1980 NAMCO BANDAI Games Inc.">
    <div id="logo-l" style="width:200px;height:2px;left:177px;top:157px;background:#990;position:absolute;display:none;overflow:hidden">
      <div id="logo-b" style="position:absolute;left:0;background:#ff0;height:8px;width:0">
      </div>
    </div>
  </div>
  ...
</div>

E depois há o script mágico:

<script>
    google.pml = function() {
        function d(a) {
            if (!google.pml_installed) {
                google.pml_installed = true;
                if (!a) {
                    document.getElementById("logo").style.background = "black";
                    window.setTimeout(function() {
                        var b = document.getElementById("logo-l");
                        if (b) b.style.display = "block"
                    }, 400)
                }
                a = document.createElement("script");
                a.type = "text/javascript";
                a.src = "logos/js/pacman10-hp.2.js";
                google.dom.append(a)
            }
        }
        function e() {
            if (document.f && document.f.btnI) document.f.btnI.onclick = function() {
                typeof google.pacman != "undefined" ? google.pacman.insertCoin() : d(false);
                return false
            }
        }
        if (!google.pml_loaded) {
            google.pml_loaded = true;
            window.setTimeout(function() {
                document.f && document.f.q && document.f.q.value == "" && d(true)
            }, 1E4);
            e();
            google.rein && google.rein.push(e);
            google.dstr && google.dstr.push(function() {
                google.pacman && google.pacman.destroy();
                if (google.pml_installed) {
                    for (var a = (document.getElementById("xjsc") || document.body).getElementsByTagName("script"), b = 0, c; c = a[b++];) c.src.indexOf("/logos/js") != -1 && google.dom.remove(c);
                    google.pml_installed = false
                }
            });
            google.pacManQuery = function() {
                google.nav(document.getElementById("dlink").href)
            }
        }
    };
</script>

O que parece estar fazendo é o seguinte:

  • Define o estilo CSS e propriedades para vários elementos HTML jogáveis;
  • Carrega o código Javascript do Pacman: http://www.google.com/logos/js/pacman10-hp.2.js (nota: esse link era válido quando eu carreguei isso em 21 de maio de 2010, o dia do doodle, mas pode ser removido mais tarde. Depende do Google, e eu não quero guardar uma cópia offline por razões legais e outros.);
  • Quando o jogo é terminado, redireciona para o link do elemento a id=”dlink”, e basicamente procura pela frase “PAC-MAN 30th Anniversary”;
  • Responde ao botão “Insert Coin” quando é pressionado e reinicia o jogo.

No estilo Google: realmente puro e eficiente!

Eu usei dois recursos online para formatar o HTML e Javascript do Google melhor:

Esse post é uma tradução do post: http://sumanrs.wordpress.com/2010/05/21/googles-pacman-doodle-reverse-engineering-101/

  • Share/Bookmark

Construção de um Agente Inteligente que Jogue Mario

Postado em 10 mai 2010
Categoria(s) Algoritmo, Game, Inteligência Artificial, Java, Jogo, Rede Neural

Com esse post você vai obter o resultado abaixo:


Nas últimas semanas eu veio estudando a fundo a técnica de inteligência artificial de redes neurais. Não é toa que praticamente os meus dois últimos posts foram sobre esse assunto.

Tudo começou com a ideia de criar um agente inteligente que conseguisse jogar Mario sozinho, a minha pretensão nunca foi muito grande, eu apenas tinha a ideia de fazer algo simples que funcionasse, mas não tinha a grande pretensão de fazer um super agente inteligente capaz de passar por vários níveis diferentes de dificuldade do jogo Mario. Partindo disso, com o único objetivo de aprendizado, eu decidi usar a técnica de redes neurais e fazer o Mario passar pela primeira fase.

Para simplificar mais ainda, eu fiz por padrão o Mario andar para a direita e a única ação que é aplicada inteligência artificial é a decisão se o Mario deve ou não pular.

O código do jogo Mario eu obtive do site: http://www.marioai.org/gameplay-track/getting-started, esse é o site da competição que tem o foco na criação de agentes inteligentes e criação de fases para o Mario, usando inteligência artificial. Eles não disponibilizam o código da parte de inteligência artificial, cabe a cada participante implementar o seu algoritmo e enviar para a competição, o melhor agente inteligente vence. Eu também não tinha a ideia de vencer essa competição, não tenho nível e conhecimento suficiente para isso ainda.

Antes de entrarmos em código e implementação, é importante entender do que consiste a técnica de redes neurais.

De forma resumida, uma rede neural é constituída por um neurônio (perceptron), que possui a seguinte estrutra:

  • Dendritos;
  • Corpo celular;
  • Axônio.

O dendrito(s) corresponde(m) as entradas (inputs) do neurônio, o corpo celular é responsável pelo processamento e o axônio representa a saída (output).

O algoritmo de uma rede neural se resume em:

  • Os dendritos recebem um valor de entrada cada um, esse valor é multiplicado por um peso, uma soma ponderada na verdade;
  • O neurônio, corpo celular, recebe a soma ponderada e através de uma verificação matemática, decide o valor de saída para o neurônio. Isso é conhecido por função de ativação, que em muitas vezes se resume em um if que testa se a soma é maior ou menor que zero;
  • O axônio transmite essa saída

Você pode pensar “Hum muito bem, onde está a inteligência artificial nisso?”, a inteligência artificial está no próximo passo do algoritmo, o neurônio deve ser treinado, ele deve receber um conjunto de valores de entrada para os dendritos e a saída válida, saída conhecida para esse conjunto de entradas. Com posse disso o neurônio é treinado, e o grande segredo de uma rede neural é o ajuste dos pesos que são multiplicados pelas entradas no dendritos, nessa parte que consta a inteligência artificial. O grande foco e obter os pesos ideias para que a rede neural se comporte da melhor forma, depois de encontrar esses pesos basta em usá-los sempre.

Na verdade tudo é matemática.

Agora que está claro como tudo acontece vamos ver a implementação. Vamos começar pelas estruturas de dados mais básicas e avançamos para as mais complexas.

Dendrito

Dendrite.java

package br.pucpr.neuralnetwork;
 
// Classe que representa um dendrito
public class Dendrite {
	// Valor para o dendrito
	private int value;
 
	// Método construtor
	// aceita como parâmetro:
	// * value = valor para o dendrito
	public Dendrite(int value) {
		this.value = value;
	}
 
	// Obtém o valor atual do dendrito
	public int getValue() {
		return value;
	}
}

Axônio

Axon.java

package br.pucpr.neuralnetwork;
 
// Classe que representa um axônio
public class Axon {
	// Sinal de saída
	private int sign;
 
	// Obtém o sinal do axônio
	public int getSign() {
		return sign;
	}
 
	// Define o sinal para o axônio
	public void setSign(int sign) {
		this.sign = sign;
	}
 
	// Transforma o sinal em um valor booleano
	public boolean signToBoolean() {
		if (sign == -1) {
			return false;
		}
		return true;
	}
}

Neurônio

Neuron.java

package br.pucpr.neuralnetwork;
 
// Classe que representa um neurônio
public class Neuron {
	// Dendritos
	private Dendrite[] dendrites;
	// Axônio
	private Axon axon;
	// Pesos
	private float[] weights;
	// Pesos padrões
	private float[] defaultWeights;
	// Tipo da função de ativação
	private String activationType;
	// Constante de aprendizado
	private float constantLearning;
 
	// Método construtor
	// aceita como pârametros:
	// * numberDendrites = Número de dendritos
	// * defaultWeights = Pesos padrão
	// * activationType = tipo da função de ativação
	public Neuron(int numberDendrites, float[] defaultWeights, String activationType) {
		// Define os pesos padrões
		this.defaultWeights = defaultWeights;
 
		// Se não for definida o tipo da função de ativação,
		// então define como sign
		if (activationType == null) {
			this.activationType = "sign";
		}
 
		// Pesos
		weights = new float[numberDendrites];
		// Dendritos
		dendrites = new Dendrite[numberDendrites];
 
		// Cria valores iniciais randômicos para os pesos
		for (int i = 0; i < weights.length; i++) {
			weights[i] = Util.random(-1, 1);
		}
 
		// Constante de aprendizado
		constantLearning = (float)0.0001;
		// Axônio
		axon = new Axon();
	}
 
	// Soma os dendritos com os seus pesos
	// média ponderada
	public float sumDendrites() {
		float sum = 0;
 
		if (defaultWeights != null && defaultWeights.length > 0) {
			for (int j = 0; j < defaultWeights.length; j++) {
				weights[j] = defaultWeights[j];
			}
		}
 
		for (int i = 0; i < weights.length; i++) {
			sum += dendrites[i].getValue() * weights[i];
		}
		return sum;
	}
 
	// Método de ativação
	// dependendo da soma e do tipo de ativação
	// envia o sinal -1 ou 1 para o axônio
	public void activation() {
		float sum = sumDendrites();
 
		axon.setSign(1);
		if (sum < 0) {
			axon.setSign(-1);
		}
	}
 
	// Treina o neurônio, afim de obter os melhores pesos para os dendritos
	// aceita como parâmetros:
	// * numberTimes = número de épocas
	// * percentageCorrect = percentual correto para o treinamento ser considerado bem sucedido
	// * examplesTraining = exemplos para o treinamento
	public void train(int numberTimes, int percentageCorrect, Point[] examplesTraining) {
		System.out.println("Treinamento iniciado...");
 
		// Quantidade de treinamento verificado
		int totalVerified = 0;
		// Quantidade de treinamento verificado errado
		int totalVerifiedWrong = 0;
		// Quantidade de treinamento verificado correto
		int totalVerifiedCorrect = 0;
 
		// Repete até o número de épocas ser satisfeito
		for (int i = 0; i < numberTimes; i++) {
			// Percorre todos os exemplos de treinamento
			for (int j = 0; j < examplesTraining.length; j++) {
				// Obtém a saída adivinhada pelo neurônio
				int result = guess(examplesTraining[j]);
				// Calcula o fator de mudança do peso baseado no erro
				// erro = saída desejada - saída adivinhada
				// multiplica pela constante de aprendizado
				float weightChange = constantLearning * (examplesTraining[j].getOutput() - result);
				// Ajusta os pesos baseado no fator de mudança * a entrada
				for (int k = 0; k < weights.length; k++) {
					weights[k] += weightChange * examplesTraining[j].getVals()[k];
				}
 
				// Contabiliza se o valor adivinhado foi correto ou não
				if (result == examplesTraining[j].getOutput()) {
					totalVerifiedCorrect++;
				} else {
					totalVerifiedWrong++;
				}
			}
		}
 
		// Quantidade de treinamentos verificados
		totalVerified = totalVerifiedWrong + totalVerifiedCorrect;
		float totalPercentageCorrect = ((100 * totalVerifiedCorrect ) / totalVerified);
 
		System.out.println("Treinamento terminado...");
		System.out.println("Resultados obtidos:");
		System.out.println(" - Número de épocas executadas: " + numberTimes);
 
		String indicativeSuccess = "bem sucedido";
 
		if (totalPercentageCorrect < percentageCorrect) {
			indicativeSuccess = "mal sucedido";
		}
 
		System.out.println(" - Indicativo de sucesso: Treinamento " + indicativeSuccess);
		System.out.println(" - Porcentagem correto: " + totalPercentageCorrect);
		System.out.println(" - Pesos:");
 
		if (defaultWeights != null && defaultWeights.length > 0) {
			for (int j = 0; j < defaultWeights.length; j++) {
				weights[j] = defaultWeights[j];
			}
		}
 
		for (int i = 0; i < weights.length; i++) {
			System.out.println("   - Peso " + i + ": " + weights[i]);	
		}
	}
 
	// Método que adivinha a saída esperada
	// baseada nas entradas, de acordo com os pesos
	// calculados
	public int guess(Point point) {
		// Define os valores para os dendritos
		setDendrites(point.getVals());
		// Faz a ativação
		activation();
		// Sinal de ativação
		return axon.getSign();
	}
 
	// Define os valores para os dendritos
	// de acordo com um array de valores
	public void setDendrites(int[] vals) {
		for (int i = 0; i < vals.length; i++) {
			dendrites[i] = new Dendrite(vals[i]);
		}
	}
 
	// Obtém a constante de aprendizado
	public float getConstantLearning() {
		return constantLearning;
	}
 
	// Define uma nova constante de aprendizado
	public void setConstantLearning(float constantLearning) {
		this.constantLearning = constantLearning;
	}
 
	// Obtém o axônio
	public Axon getAxon() {
		return axon;
	}
 
	// Obtém o tipo da função de ativação usada
	public String getActivationType() {
		return activationType;
	}
}

Ponto

Point.java

package br.pucpr.neuralnetwork;
 
// Classe que representa um ponto de treinamento
public class Point {
	// Valores de entrada
	private int[] vals;
	// Saída esperada
	private int output;
 
	// Método construtor
	// aceita os parâmetros:
	// * obs1 = tipo de obstáculo na posição 1
	// * obs2 = tipo de obstáculo na posição 2
	// * output = saída esperada
	public Point(int obs1, int obs2, int output) {
		vals = new int[3];
		vals[0] = obs1;
		vals[1] = obs2;
		// Bias para quando as entradas forem zero
		vals[2] = 1;
		this.output = output;
	}
 
	// Valores de entrada
	public int[] getVals() {
		return vals;
	}
 
	// Saída esperada
	public int getOutput() {
		return output;
	}	
}

Agente Inteligente

NeuralNetworkAgent.java

package ch.idsia.ai.agents.controllers;
 
import br.pucpr.neuralnetwork.Neuron;
import br.pucpr.neuralnetwork.Point;
import ch.idsia.ai.agents.Agent;
import ch.idsia.mario.engine.sprites.Mario;
import ch.idsia.mario.environments.Environment;
 
// Classe que representa um agente inteligente que utiliza rede neural
public class NeuralNetworkAgent extends BasicAIAgent implements Agent {
 
	// Neurônio
	private Neuron neuron;
	// Pontos para treinamento
	private Point[] examplesTraining;
 
	public NeuralNetworkAgent() {
		super("NeuralNetworkAgent");
 
		// Instância um neurônio
		// Com 3 dendritos, obstáculo 1, obstáculo 2 e 
		// bias para quando as primeiras entradas forem zero
		neuron = new Neuron(3, null, "sign");
 
		// Com pesos padrões
		// float[] defaultWeights = new float[3];
		// defaultWeights[0] = (float)0.12533271;
		// defaultWeights[1] = (float)-0.15752053;
		// defaultWeights[2] = (float)-0.22475332;
		// neuron = new Neuron(3, defaultWeights, "sign");
 
		// Cria os pontos para o treinamento
		setExamplesTraining();
 
		// Realiza o treinamento
		neuron.train(300, 43, examplesTraining);
 
		reset();
	}
 
	public void reset() {
		action = new boolean[Environment.numberOfButtons];
		action[Mario.KEY_RIGHT] = true;
	}
 
	public boolean[] getAction() {
		// Através dos pesos calculados no treinamento
		// tenta jogar Mario sozinho
		// Define o valor para os dendritos baseado nos obstáculos do jogo
		int[] vals = new int[2];
		vals[0] = mergedObservation[11][13];
		vals[1] = mergedObservation[11][12];
		neuron.setDendrites(vals);
 
		// Realiza ativação do neurônio, afim de obter o sinal de saída
		neuron.activation();
 
		// De acordo com o neurônio faz o Mario pular ou não
		if (neuron.getAxon().signToBoolean()) {
			if (isMarioAbleToJump) {
				action[Mario.KEY_JUMP] = true;	
			}
		} else {
			action[Mario.KEY_JUMP] = false;
		}
 
		return action;
	}
 
	// Cria os pontos para o treinamento
	public void setExamplesTraining() {
		examplesTraining = new Point[6];
		examplesTraining[0] = new Point(0, 0, -1);
		examplesTraining[1] = new Point(-10, 0, 1);
		examplesTraining[2] = new Point(20, 0, 1);
		examplesTraining[3] = new Point(-10, -10, 1);
		examplesTraining[4] = new Point(2, 0, 1);
		examplesTraining[5] = new Point(-11, 0, 1);
	}
}

Main

Main.java

package ch.idsia.scenarios;
 
import ch.idsia.ai.agents.Agent;
import ch.idsia.ai.agents.controllers.NeuralNetworkAgent;
import ch.idsia.maibe.tasks.BasicTask;
import ch.idsia.mario.environments.Environment;
import ch.idsia.mario.environments.MarioEnvironment;
import ch.idsia.tools.CmdLineOptions;
 
/**
 * Created by IntelliJ IDEA. User: Sergey Karakovskiy, sergey at idsia dot ch Date: Mar 17, 2010 Time: 8:28:00 AM
 * Package: ch.idsia.scenarios
 */
public class Main
{
    public static void main(String[] args)
    {
    	// final String argsString = "-vis on";
    	// args = argsString.split("\\s");
        final CmdLineOptions cmdLineOptions = new CmdLineOptions(args);
        final Environment environment = new MarioEnvironment();
        // final Agent agent = new ForwardAgent();
        // final Agent agent = cmdLineOptions.getAgent();
        final Agent agent = new NeuralNetworkAgent();
        // final Agent a = AgentsPool.load("ch.idsia.controllers.agents.controllers.ForwardJumpingAgent");
        final BasicTask basicTask = new BasicTask(environment, agent);
        basicTask.reset(cmdLineOptions);
        basicTask.runOneEpisode();
 
        System.out.println("cmdLineOptions.getLevelLength() = " + cmdLineOptions.getLevelLength());
        System.out.println(environment.getEvaluationInfoAsString());
        System.exit(0);
    }
}

Vamos entender como as classes acima se juntam para implementar a rede neural, a classe NeuralNetworkAgent.java possui os exemplos de treinamento, esses exemplos foram captados quando eu estava realmente jogando, por isso que a saída é conhecida, é o que se espera que o Mario faça quando ele tiver obstáculos pela frente. Além dos exemplos essa classe define o comportamento inicial do Mario que é andar para direita, a classe também realiza o treinamento, e com posse dos pesos ela abre a tela do jogo e coloca o Mario para jogar sozinho com inteligência. No código incluído está para toda vez os pesos serem calculados, é possível passar pesos padrões para o neurônio, essa parte do código está comentada, se você descomentar já irá ver o Mario jogando com os melhores pesos que eu encontrei.

A classe Point.java descreve os pontos ou ponto de entrada, ela recebe a informação se existem obstáculos na posição 1 e na posição 2 e a saída que deve existir.

A classe Dendrite.java descreve um dendrito e o seu valor de entrada.

A classe Axon.java descreve o axônio do neurônio, o sinal de saída, que também é transformado em um valor booleano.

A classe Neuron.java por sua vez que realiza todo o trabalho, cálculo dos melhores pesos e o emprego da inteligência artificial. Poder conter vários dendritos (várias entradas), uma única saída, os pesos para cada entrada e a constante de aprendizado. Inicialmente os pesos são valores randômicos, ao entrar no treinamento, os melhores pesos tentam ser encontrados, não sempre isso acontece, vai depender da quantidade de exemplos de treinamento e a quantidade de épocas. As épocas são quantas vezes o algoritmo vai ser repetido para tentar encontrar os melhores pesos para a rede neural. A constante de aprendizado é usada para alterar os pesos e tentar achar o melhor peso, as referências bibliográficas afirmam que quanto menor a constante de aprendizado, mais vai demorar para encontrar os pesos, mas os pesos encontrados são melhores e mais úteis para o funcionamento da rede.

A classe Main.java que executa o jogo e coloca todas as classes para trabalhar afim de obter a rede neural.

É claro que para o Mario rodar, é necessário ter toda o código fonte do Mario, disponibilizado no endereço que eu mencionei no começo do texto. Os códigos apresentados são apenas um agente inteligente que foi embutido no jogo do Mario.

Eu não vou entrar em mais detalhes do algoritmo, com posse dessas informações e comentários você já deve ser capaz de entender tudo que está acontecendo.

O que eu escrevi nesse texto não representa a verdade absoluta, eu ainda estou estudando o assunto, por isso algumas coisas podem estar erradas ou podem ser feitas de uma forma muito melhor. Apenas quero ajudar e propagar o conhecimento.

O código fonte está disponível em: http://github.com/patrickespake/Mario-Neural-Network-Agent

  • Share/Bookmark

Redes Neurais

Esse artigo é uma tradução de: http://www.shiffman.net/teaching/nature/nn/ escrito por Daniel Shiffman.

Exemplos

Código de exemplo: neural_network.zip

Biblioteca de rede neural (necessária para 2 dos exemplos): nn.zip

perceptron exemplo

perceptron simples

xor exemplo

rede neural multi-camada

number exemplo

reconhecimento de padrões pixel

Lendo

  • The Computational Beauty of Nature, Gary William Flake, Chapter 22 — Neural Networks and Learning

Um Perceptron Simples

Começamos nossa discussão sobre Redes Neurais com um Perceptron simples. Um Perceptron é um modelo computacional de um neurônio. É constituído por uma ou mais entradas, um processamento e uma única saída. Cada entrada recebe um peso e o processamento cria a saída via os seguintes passos:

  • Para cada entrada, é multiplicado a entrada pelo seu peso;
  • Soma de todas as entradas vezes seus pesos, soma ponderada;
  • Computação da saída do Perceptron é baseada na soma passada para uma função de ativação.

A função de ativação simples é o sinal da soma. Em outras palavras, se a soma for um número positivo, a saída é 1, se é negativo, a saída é -1. Aqui está um código que assume um array de entradas, e um array de pesos de entradas.

// Soma todos os valores
float sum = 0;
int result = 0;
for (int i = 0; i < inputs.length; i++) {
  sum += inputs[i]*weights[i];
}
if (sum > 0) result = 1;
else result = -1;

perceptron classificacao

Um Perceptron é normalmente utilizado para classificação. Por exemplo, digamos que um Perceptron tem 2 entradas. Usando uma função de sinal de ativação, a saída será -1 ou 1, ou seja, os dados de entrada são classificados de acordo com o sinal de saída. Ele pertence ao grupo A (-1) ou B(1).

Considere uma linha no espaço de 2 dimensões. Os pontos nesse espaço podem ser classificados levando em consideração um lado e outro da linha de separação. Embora este seja um exemplo bastante trivial (já que podemos determinar o lado em que situa-se um ponto, com alguma álgebra simples), um Perceptron podem ser treinados para aprender a reconhecer os pontos de um lado contra o outro.

Nesse exemplo , há duas entradas, a coordenada x do ponto e coordenada y. No entanto, para que o Perceptron funcione corretamente, é preciso também incorporar um “bias” de entrada. Um “bias” sempre tem o valor de 1. Porque um “bias” é necessário? Sem “bias”, se todas as entradas forem 0, a única saída possível sempre será um zero (porque a soma de um monte de zeros é só zero!). Portanto, a estrutura do nosso Perceptron é a seguinte:

perceptron bias

Com base na estrutura acima, podemos criar a classe para descrever o Perceptron. O Perceptron inclui um array de pesos de entrada, bem como uma constante de aprendizado (a constante de aprendizado será utilizada no algoritmo de treinamento descrito abaixo).

class Perceptron {
  float[] weights; // Array dos pesos de entrada
  float c; // Constante de aprendizado

O construtor irá criar o array de pesos de entrada com base no número de entradas e define a constante de aprendizado.

  // Perceptron é criado com n pesos e constante de aprendizado
  Perceptron(int n, float c_) {
    weights = new float[n];
    // Inicia com pesos randômicos
    for (int i = 0; i < weights.length; i++) {
      weights[i] = random(-1, 1);
    }
    c = c_;
  }

O algoritmo de adivinhação é exatamente o que nós desenvolvemos acima, a soma de todas as entradas multiplicados pelos seus pesos. No exemplo abaixo, as entradas que chegam com o argumento “vals” (algum array de pontos fluentes) para a função. O último elemento do array é portanto o “bias” e deve ter o valor 1.

  // Adivinhar -1 ou 1 com base nos valores de entrada
  int guess(float[] vals) {
    // Soma de todos os valores
    float sum = 0;
    for (int i = 0; i < weights.length; i++) {
      sum += vals[i]*weights[i];
    }
    // Resultado é um sinal de soma, -1 ou 1
    int result = 1;
    if (sum < 0) result = -1;
    return result;
  }

A ponto de fazer tudo isso, claro, é ser capaz de treinar a rede para ser capaz de fazer a escolha certa. Isto é conseguido através da alimentação das entradas da rede com saídas corretas “conhecidas”, e ajustando os pesos de acordo com os erros de saída. O erro é calculado como segue:

// erro = saída desejada - saída adivinhada
ERROR = DESIRED OUTPUT - GUESS OUTPUT

Nesse exemplo simples, o erro somente pode ser um dos três valores: 0 (se desejado e adivinhado são iguais), 2 (se desejado = 1 e adivinhado = -1) e -2 (se desejado = -1 e adivinhado = 1). A seguinte fórmula é usada para determinar o quanto mudar os pesos. Note que, se o erro é igual a zero, os pesos não serão alterados desde que deltaweight seja igual a zero. Isso é como as coisas deveriam funcionar, pois se o erro é igual a zero, temos a resposta certa — não corrigi-lo se ele não está quebrado!

DELTAWEIGHT = LEARNINGCONSTANT * ERROR * INPUT
NEWWEIGHT = OLDWEIGHT + DELTAWEIGHT

No código, isso se traduz em uma função de aprendizado:

  // Função para aprendizado do Perceptron
  // Pesos são ajustados com base na resposta de "desejada"
  void train(float[] vals, int desired) {
    // Resultado de adivinhação
    int result = guess(vals);
    // Calcula o fator de mudança do peso com base no erro
    // erro = saída esperada - saída adivinhada
    // Observe que este só pode ser 0, -2, ou 2
    // Multiplicar pela constante de aprendizado
    float weightChange = c*(desired - result);
    // Ajusta os pesos com base no weightChange * input
    for (int i = 0; i < weights.length; i++) {
      weights[i] += weightChange * vals[i];
    }
  }

Para ter uma noção de como funciona a constante de aprendizado, baixe o exemplo de Perceptron e altere o valor no construtor:

ptron = new Perceptron(3, 0.004);

A maior constante fará com que o Perceptron aprenda mais rápido, mas pode ultrapassar o peso ideal. A baixa constante fará com que ele aprenda mais lentamente, mas ele provavelmente vai encontrar a melhor solução com mais precisão.

Agora que a classe Perceptron está construída, podemos treiná-lo para classificar os pontos do espaço, como descrito acima. Vamos escolher uma fórmula arbitrária de uma linha:

y = 0.9x - 0.2

Nós podemos fazer isso em um função:

// Função que descreve a linha
float f(float x) {
  return x*0.9-0.2;
}

E usá-lo para calcular um resultado conhecido. As entradas são x, y e 1 (bias) e o resultado conhecido é a variável de entrada.

float x = random(-2, 2);
float y = random(-2, 2);
int answer = 1;
if (y < f(x)) answer = -1;

Multi-Camadas de Rede Neural

O Perceptron é bom para resolver problemas simples que têm uma solução linearmente separáveis. Em outras palavras, todos os pontos de saída 1 vão cair de um lado de uma linha no espaço 2D e todos os pontos de saída -1 vão cair do outro lado. Perceptrons, no entanto, não podem resolver problemas linearmente separáveis.

O exemplo mais clássico de um problema linearmente inseparáveis é o XOR. XOR é “exclusivo”, ou seja, se A ou B, mas não ambos A e B.

AND
----
FALSE FALSE --> FALSE
TRUE  FALSE --> FALSE
FALSE TRUE  --> FALSE
TRUE  TRUE  --> TRUE
 
OR
----
FALSE FALSE --> FALSE
TRUE  FALSE --> TRUE
FALSE TRUE  --> TRUE
TRUE  TRUE  --> TRUE
 
XOR
---
FALSE FALSE --> FALSE
TRUE  FALSE --> TRUE
FALSE TRUE  --> TRUE
TRUE  TRUE  --> FALSE

perceptron linha xorConside os pontos (0,0), (1,0), (0,1) e (1,1) no espaço. Para XOR, não há como estabelecer uma única linha que separa os pontos verdadeiros: (1,0) e (0,1) a partir dos pontos falsos: (0,0) e (1,1). XOR é linearmente inseparáveis. Portanto, um Perceptron não pode ser treinado para desenvolver os pesos que irá produzir a saída correta (verdadeiro ou falso), dando duas entradas.

A fim de resolver XOR, nós temos que criar um Perceptron multi-camada com duas entradas, uma camada “escondida”, e uma saída. Esta camada oculta eleva-se para ter vários perceptrons para atacar o mesmo problema, ou seja, se “OU” é verdadeiro (perceptron #1) e “E” não é verdadeiro (perceptron #2), então a saída é XOR.

perceptron multi camada

A questão agora torna-se o treinamento da rede multi-camadas para ter os pesos certos. Infelizmente, isso envolve um pouco de cálculo. Essa página vai oferecer apenas um breve panaroma da solução. Para uma explicação mais detalhada, leia o capítulo 22 em The Computational Beauty of Nature. Você pode também simplesmente google multi-layered neural network backpropogation .

Back Propogation

Uma solução para treinamento de uma rede neural multi-camada é conhecido como backpropogation. Certamente, existem outras opções para treinamento de uma rede neural. Por exemplo, um algoritmo genético pode ser usado para pesquisar pesos ótimos!

Para gerar a saída da rede, as entradas multiplicadas pelos pesos são somados e alimentado para a frente através da rede. Isto é o que nós fazemos com o perceptron, só há uma camada adicional entre a entrada e a saída. O treinamento da rede envolve assumir o erro (desejado – adivinhado) e alimentá-lo para trás através da rede. Assim que o erro final informa ambos os pesos entre a camada escondida e a saída, bem como os pesos entre as entradas e a camada escondida.

Vamos primeiro olhar como nós alimentamos para a frente através da rede. Nossa rede neural será composta das seguintes classes:

  • Neuron – cada objeto neurônio consiste de uma lista de objetos Connection (ou seja, indicando o que outros Neurons estão conectados). Ele tem a função que calcula a sua saída com base em suas entradas multiplicado pelos pesos da conexão relevante. Ele também sabe se é um Neuron “bias” ou apenas um Neuron regular. O exemplo também tem subclasses InputNeuron, HiddenNeuron e OutputNeuron para qualquer funcionalidade específica para os neurônios;
  • Connection – cada objeto Connection contém dois Neurons, o “de” Neuron e o “para” Neuron. Tem também uma variável do tipo double para o seu peso;
  • Network – o objeto Network consiste de todos os Neurons. Um array de neurônios de entrada, neurônios escondidos, e um neurônio saída (embora isso possa, e deve, concebivelmente ser um array). Ele também terá funções que alimentam a rede para frente e treinam a rede para trás.

sigmoidAssim, o primeiro passo para a alimentação para frente da rede é calcular a saída de todos os neurônios na camada escondida. Em um perceptron simples, a saída era o sinal da soma das entradas * pesos de conexão. Se essa soma foi positiva, a saída era +1, se fosse negativa, a saída era -1. O sinal da soma era nossa função de ativação.

Para que backpropogation funcione bem, vamos precisar de função de ativação fancier. Usaremos uma função sigmóide. Aqui está um applet com os gráficos da função sigmóide . Olhando para o gráfico, podemos ver que a função de ativação sigmóide nos diz muito mais sobre a nossa saída do que a função do sinal de ativação faz. Quando o neurônio dispara para perto do ponto 1/2 (meio do gráfico), a inclinação da curva da sigmóide é muito íngreme. Quando ele é acionado perto das bordas do exterior (os lados esquerdo ou direito da imagem à direita), a inclinação é de nível. O código da sigmóide é o seguinte:

// Função sigmóide
float f(float x) {
  return 1.0 / (1.0 + exp(-x));
}

dsigmoidA inclinação da curva da sigmóide é o valor que queremos saber, uma vez que nos diz onde estamos na ativação continuum – perto do centro ou perto da borda? A inclinação é calculada pela derivada (a taxa de variação de uma função) da sigmóide. Este deve ser um conceito familiar para nós – a taxa de variação (isto é, derivada) da localização é a velocidade, da velocidade é a aceleração . A derivada da função sigmóide é f(x) * (1-f(x)) e esse valor será usado no algoritmo de backpropogation para alterar os pesos.

Ok, de volta à alimentação para a frente! Agora que conhecemos que a função de ativação é a função sigmóide, podemos calcular a saída para qualquer neurônio dada como a soma de todos os valores de entrada * os pesos de conexão passados através da sigmóide. Note que nossos neurônios podem ter conexões entrando e saindo, mas para obter nossa saída, nós queremos apenas as conexões que entram! Além disso, se o neurônio é um neurônio bias, o cálculo não será necessário, pois a saída é sempre 1.

for (int i = 0; i < connections.size(); i++) {
  Connection c = (Connection) connections.get(i);
  Neuron from = c.getFrom();
  Neuron to = c.getTo();
  // É neste contexto que se deslocam para a frente
  if (to == this) {
      sum += from.getOutput()*c.getWeight();
    }
  }
}
output = f(sum);

Agora que os neurônios sabem como calcular suas saídas, nós podemos pegar os valores de entrada e alimentar para frente. Primeiro, nós alimentamos nas entradas, então fazemos loop em todos os neurônios escondidos e calculamos suas saídas, e então nós calculamos a saída do do neurônio de saída em si! Aqui é um trecho de exemplo (na Network) classe:

public double feedForward(double[] inputVals) {
  // Primeiro colocamos os valores de entrada na rede
  //System.out.println("\n\nFeeding input layer");
  for (int i = 0; i < inputVals.length; i++) {
    input[i].input(inputVals[i]);
  }
  // Então calculamos a saída para a camada escondida
  //System.out.println("\n\nFeeding hidden layer");
  for (int i = 0; i < hidden.length-1; i++) {
    //System.out.println("\nNeuron " + i);
    hidden[i].calcOutput();
  }
 
  //System.out.println("\n\nFeeding output layer");
  output.calcOutput();
  return output.getOutput();
}

Uma vez que temos a saída da rede neural, podemos comparar a saída para uma resposta conhecida (a partir do XOR) e ajustar os pesos de acordo.

Com o perceptron simples, foi calculado o erro como:

// erro = desejado - adivinhado
ERROR = DESIRED - GUESS

Vamos agora usar o fato que a a derivada da função sigmóide nós contou mais informações sobre o erro e o cálculo da seguinte maneira:

// erro = adivinhado*(1-adivinhado) * (desejado-adivinhado)
ERROR = GUESS*(1-GUESS) * (DESIRED-GUESS)

Esse erro é aplicado aos pesos entre a saída e a camada escondida.

DELTAWEIGHT = LEARNINGCONSTANT * ERROR * HIDDEN OUTPUT
NEWWEIGHT = OLDWEIGHT + DELTAWEIGHT

O próximo passo é calcular o erro para a saída do neurônio escondido. Essa é a parte mais difícil, uma vez que nós não temos uma resposta conhecida! Sabemos apenas qual o resultado final que toda rede deve ser! A fórmula para o erro da camada escondida é, portanto, uma função de derivação da sua própria saída e que o erro global de toda a rede.

HIDDENERROR = HIDDEN OUTPUT*(1-HIDDEN OUTPUT) * (ERROR* WEIGHT TO FINALOUTPUT)

Note que, se existe várias saídas nós somamos os erros multiplicando pelo pesos das saídas, mas com XOR, nós não temos que se preocupar com isso. Finalmente, ajustamos os pesos recebidos na camada escondida com HIDDENERROR.

DELTAWEIGHT = LEARNINGCONSTANT * HIDDENERROR * INPUT NEURON
NEWWEIGHT = OLDWEIGHT + DELTAWEIGHT

E é isso! A chave aqui é compreender que devemos primeiro calcular o erro de saída da rede inteira. E, em seguinda, usar esse erro para determinar quanto cada conexão ao longo do caminho de volta para o início contribui para esse erro, e ajustar os pesos de acordo.

Aqui está a função para executar todos estes cálculos:

public double train(double[] inputs, double answer) {
  double result = feedForward(inputs);
 
  double deltaOutput = result*(1-result) * (answer-result);
 
  // Isso é fácil b/c só temos uma saída
  ArrayList connections = output.getConnections();
  for (int i = 0; i < connections.size(); i++) {
    Connection c = (Connection) connections.get(i);
    Neuron neuron = c.getFrom();
    double output = neuron.getOutput();
    double deltaWeight = output*deltaOutput;
    c.adjustWeight(LEARNING_CONSTANT*deltaWeight);
  }
 
  // Ajusta pesos escondidos
  for (int i = 0; i < hidden.length; i++) {
    connections = hidden[i].getConnections();
    double sum  = 0;
    for (int j = 0; j < connections.size(); j++) {
      Connection c = (Connection) connections.get(j);
      // Primeiro soma todas as conexões para OUTPUTS*deltaOutput (só uma saída)
      if (c.getFrom() == hidden[i]) {
                sum += c.getWeight()*deltaOutput;
      }
    }
    // Então ajusta os pesos nos próximos
    for (int j = 0; j < connections.size(); j++) {
      Connection c = (Connection) connections.get(j);
      if (c.getTo() == hidden[i]) {
        double output = hidden[i].getOutput();
        double deltaHidden = output * (1 - output);
        deltaHidden *= sum;   // Será a soma para todas as saídas se mais de uma saída
        Neuron neuron = c.getFrom();
        double deltaWeight = neuron.getOutput()*deltaHidden;
        c.adjustWeight(LEARNING_CONSTANT*deltaWeight);
      }
    }
  }
 
  return result;
}
  • Share/Bookmark

Google Code Jam Is Back!

Postado em 21 jul 2009
Categoria(s) Algoritmo

We’re excited to announce Google Code Jam 2009, this year’s iteration
of Google’s annual programming competition, which offers coders from
around the world an opportunity to solve complex algorithmic problems
under time pressure, using the programming languages and tools of
their choice.

The contest will have a new format this year, starting with online
rounds and ending in a 25-person final in our Mountain View,
California headquarters. We’re still choosing exact times for
everything, but for planning purposes we wanted to give you this
tentative schedule. Please note that the timing may change:

Early-Mid August: Registration will open.
+4 Weeks: Qualification round
+1 Week: Rounds 1A, 1B, 1C
+1 Week: Round 2
+1 Week: Round 3
November: World Finals in Mountain View

Online rounds begin soon, so start practicing!

The Google Code Jam Team
http://code.google.com/codejam.

  • Share/Bookmark

Algoritmo para fazer redimensionamento de imagens proporcionalmente

Postado em 15 mai 2009
Categoria(s) Algoritmo

Digamos que você tenha uma imagem na proporção 1280×850 px, vamos chamar essa imagem de original, e deseja fazer o redimensionamento proporcional para uma imagem nas medidas aproximadas de 408×544 px, que será chamada de final.

Exemplificando:

  • Imagem original: 1280×850
  • Imagem final (aproximada): 408×544

Algoritmo:

1
2
3
4
5
6
7
8
9
10
11
12
13
inicio
  escala_largura = largura_original / largura_final
  escala_altura = altura_original / altura_final
 
  se escala_largura < escala_altura
    escala = escala_largura
  senao
    escala = escala_altura
  fim se
 
  largura_proporcional = largura_original / escala
  altura_proporcional = altura_original / escala
fim

Teste:

1
2
3
4
5
6
7
8
9
10
11
12
13
inicio
  escala_largura = 1280 / 408 #3,137254902
  escala_altura = 850 / 544 #1,5625
 
  se escala_largura < escala_altura
    escala = escala_largura
  senao
    escala = escala_altura
  fim se
 
  largura_proporcional = 1280 / 1,5625 #819,2
  altura_proporcional = 850 / 1,5625 #544
fim

Esse teste gerá uma imagem proporcional de medidas 819,2×544 px.

Esse algoritmo leva em consideração que a imagem original sempre vai ser maior.

  • Share/Bookmark