2010-12-26 19 views
7

Me gustaría construir un motor Connect 4 que funcione usando una red neuronal artificial, simplemente porque estoy fascinado por las RNA.Connect 4 con red neuronal: evaluación de borrador + pasos adicionales

Se crea el siguiente borrador de la estructura ANN. ¿Funcionaría? ¿Y estas conexiones son correctas (incluso las cruzadas)?

alt text

Podría ayudarme a redactar hasta un diagrama de clases UML para este ANN?

Quiero dar la representación de la junta a la ANN como su entrada. Y la salida debería ser el movimiento para elegir.

El aprendizaje se debe hacer más tarde usando reinforcement learning y se debe aplicar sigmoid function. El motor jugará contra jugadores humanos. Y dependiendo del resultado del juego, los pesos deberían ajustarse entonces.

Lo que estoy buscando ...

... se debe principalmente a la codificación de los problemas. Cuanto más se aleja del pensamiento abstracto a la codificación, mejor.

Respuesta

2

A continuación se muestra cómo organicé mi diseño y mi código cuando estaba jugando con redes neuronales. El código aquí es (obviamente) psuedocode y aproximadamente sigue las convenciones orientadas a objetos.

Comenzando de abajo hacia arriba, tendrá su neurona. Cada neurona debe ser capaz de mantener los pesos que pone en las conexiones entrantes, un búfer para contener los datos de conexión entrantes y una lista de sus bordes salientes. Cada neurona tiene que ser capaz de hacer tres cosas:

  • una forma de aceptar datos de un borde entrante
  • Un método de procesamiento de los datos de entrada y los pesos para formular el valor de esta neurona va a enviar
  • Una forma de enviar el valor de esta neurona en los bordes salientes

Código en cuanto a esto se traduce en:

// Each neuron needs to keep track of this data 
float in_data[]; // Values sent to this neuron 
float weights[]; // The weights on each edge 
float value; // The value this neuron will be sending out 
Neuron out_edges[]; // Each Neuron that this neuron should send data to 

// Each neuron should expose this functionality 
void accept_data(float data) { 
    in_data.append(data); // Add the data to the incoming data buffer 
} 
void process() { 
    value = /* result of combining weights and incoming data here */; 
} 
void send_value() { 
    foreach (neuron in out_edges) { 
     neuron.accept_data(value); 
    } 
} 

A continuación, me pareció más fácil si haces una clase Layer que contiene una lista de neuronas. (Es muy posible omitir esta clase, y solo haga que su NeuralNetwork tenga una lista de neuronas. Me pareció más fácil desde el punto de vista organizativo y de depuración tener una clase Layer). Cada capa debería exponer la capacidad de:

  • Porque cada neurona a 'fuego'
  • devolver la matriz prima de neuronas que esta capa se envuelve alrededor. (Esto es útil cuando necesita hacer cosas como llenar manualmente los datos de entrada en la primera capa de una red neuronal.)

Código en cuanto a esto se traduce en:

//Each layer needs to keep track of this data. 
Neuron[] neurons; 

//Each layer should expose this functionality. 
void fire() { 
    foreach (neuron in neurons) { 
     float value = neuron.process(); 
     neuron.send_value(value); 
    } 
} 
Neuron[] get_neurons() { 
    return neurons; 
} 

Por último, tiene una clase NeuralNetwork que contiene una lista de capas, de manera de establecer la primera capa con los datos iniciales, un algoritmo de aprendizaje y una forma de ejecutar toda la red neuronal. En mi implementación, recopilé los datos de salida final al agregar una cuarta capa que consistía en una única neurona falsa que simplemente almacenaba en el búfer todos sus datos entrantes y lo devolvía.

// Each neural network needs to keep track of this data. 
Layer[] layers; 

// Each neural network should expose this functionality 
void initialize(float[] input_data) { 
    foreach (neuron in layers[0].get_neurons()) { 
     // do setup work here 
    } 
} 
void learn() { 
    foreach (layer in layers) { 
     foreach (neuron in layer) { 
      /* compare the neuron's computer value to the value it 
      * should have generated and adjust the weights accordingly 
      */ 
     } 
    } 
} 
void run() { 
    foreach (layer in layers) { 
     layer.fire(); 
    } 
} 

Yo recomiendo empezar con propagación hacia atrás como su algoritmo de aprendizaje, ya que es supuestamente el más fácil de implementar. Cuando estaba trabajando en esto, tuve grandes dificultades para tratar de encontrar una explicación muy simple del algoritmo, pero mi lista de notas this site es una buena referencia.

¡Espero que sea suficiente para comenzar!

3

Hay muchas maneras diferentes de implementar redes neuronales que van desde simples/fáciles de entender hasta altamente optimizadas. El Wikipedia article on backpropagation al que se ha vinculado tiene enlaces a implementaciones en C++, C#, Java, etc. que podrían servir como buenas referencias, si le interesa ver cómo lo han hecho otras personas.

Una arquitectura simple modelaría los nodos y las conexiones como entidades separadas; los nodos tendrían posibles conexiones entrantes y salientes a otros nodos, así como niveles de activación y valores de error, mientras que las conexiones tendrían valores de peso.

Alternativamente, existen formas más eficientes de representar esos nodos y conexiones, como arreglos de valores de punto flotante organizados por capa, por ejemplo. Esto hace que las cosas sean un poco más complicadas de codificar, pero evita crear tantos objetos y punteros a los objetos.

Una nota: a menudo la gente incluirá a bias node - además de los nodos de entrada normales - que proporciona un valor constante a cada nodo oculto y de salida.

2

he implementado redes neuronales antes, y ver algunos problemas con su arquitectura propuesta:

  1. Una red multicapa típico tiene conexiones desde cada nodo de entrada a cada nodo oculto, y desde cada nodo oculto a cada nodo de salida. Esto permite que la información de todas las entradas se combine y contribuya a cada resultado. Si dedica 4 nodos ocultos a cada entrada, perderá parte de la potencia de la red para identificar las relaciones entre las entradas y las salidas.

  2. ¿Cómo se te ocurren los valores para entrenar la red? Su red crea un mapeo entre las posiciones de la placa y el próximo movimiento óptimo, por lo que necesita un conjunto de ejemplos de capacitación que lo proporcionen. Los movimientos finales del juego son fáciles de identificar, pero ¿cómo se puede decir que un movimiento en el medio del juego es "óptimo"?(Aprendizaje de refuerzo puede ayudar aquí)

Una última sugerencia es usar entradas bipolares (-1 por falsa, +1 por cierto) ya que esto puede acelerar el aprendizaje. Y Nate Kohl hace una buena observación: cada nodo de salida & oculto se beneficiará al tener una conexión de polarización (piénselo como otro nodo de entrada con un valor fijo de "1").

1

Su diseño dependerá en gran medida del tipo específico de refuerzo que aprenda a utilizar.

La solución más simple sería utilizar la retropropagación. Esto se hace volviendo a introducir el error en la red (de forma inversa) y utilizando el inverso de la función (sigmoide) para determinar el ajuste de cada peso. Después de varias iteraciones, los pesos se ajustarán automáticamente para ajustarse a la entrada.

Los algoritmos genéticos son una alternativa a la retropropagación que ofrecen mejores resultados (aunque un poco más lentos). Esto se hace tratando los pesos como un esquema que puede insertarse y eliminarse fácilmente. El esquema se reemplaza con una versión mutada (usando principios de selección natural) varias veces hasta que se encuentra un ajuste.

Como puede ver, la implementación de cada uno de estos sería drásticamente diferente. Podría tratar de hacer que la red sea lo suficientemente genérica como para adaptarse a cada tipo de implementación, pero eso puede complicarla en exceso. Una vez que esté en producción, por lo general solo tendrá una forma de entrenamiento (o lo ideal sería que su red ya estuviera entrenada).

Cuestiones relacionadas