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!