Ниже показано, как я организовал свой дизайн и код, когда связывался с нейронными сетями. Код здесь (очевидно) psuedocode и примерно соответствует объектно-ориентированным соглашениям.
Начиная снизу вверх, у вас будет свой нейрон. Каждый нейрон должен иметь возможность удерживать веса, которые он помещает для входящих соединений, буфер для хранения данных о входящих соединениях и список его исходящих ребер. Каждый нейрон должен уметь делать три вещи:
- Способ приема данных от входящего фронта
- Метод обработки входных данных и весов для определения значения, которое этот нейрон будет отправлять
- Способ отправки значения этого нейрона на исходящие ребра
В коде это означает:
// 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 );
}
}
Далее, мне показалось, что проще всего создать класс Layer, содержащий список нейронов. (Вполне возможно пропустить этот класс, и просто в вашей NeuralNetwork хранится список списков нейронов. Я обнаружил, что организационно и отладочно проще иметь класс Layer.) Каждый слой должен предоставлять возможность:
- Вызвать каждый нейрон "огнем"
- Вернуть необработанный массив нейронов, который окружает этот слой. (Это полезно, когда вам нужно выполнить такие вещи, как ручное заполнение входных данных в первом слое нейронной сети.)
В коде это означает:
//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;
}
Наконец, у вас есть класс NeuralNetwork, который содержит список слоев, способ настройки первого слоя с начальными данными, алгоритм обучения и способ запуска всей нейронной сети. В моей реализации я собрал окончательные выходные данные, добавив четвертый слой, состоящий из одного ложного нейрона, который просто буферизовал все входящие данные и возвратил их.
// 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();
}
}
Я рекомендую начать с обратного распространения в качестве алгоритма обучения, так как он, предположительно, наиболее прост в реализации. Когда я работал над этим, мне было очень трудно найти очень простое объяснение алгоритма, но мой список заметок этот сайт является хорошим справочником.
Надеюсь, этого достаточно, чтобы начать!