Пример реализации простой нейронной сети, обучаемой по алгоритму обратного распространенния ошибки.

Layer.java

Базовый интерфейс любого нейронного слоя. Размер слоя - количество нейронов содержащихся в слое и соотвественно размер выходного вектора слоя.

Код:
package nnet;
import java.io.Serializable;

/**
 * Интерфейс нейронного слоя
 */
public interface Layer extends Serializable {
    /**
     * Получает размер входного вектора
     * @return Размер входного вектора
     */
    int getInputSize();

    /**
     * Получает размер слоя
     * @return Размер слоя
     */
    int getSize();

    /**
     * Вычисляет отклик слоя
     * @param input Входной вектор
     * @return Выходной вектор
     */
    float[] computeOutput(float[] input);
}
Network.java

Реализация базовой функциональности нейронной сети, состоящей из нескольких слоев.
package nnet;

Код:
import java.io.*;

/**
 * Базовая реализация нейронной сети
 */
public class Network implements Serializable {
    /**
     * Конструирует нейронную сеть с заданными слоями
     * @param layers Нейронные слои
     */
    public Network(Layer[] layers) {
        // проверки
        if (layers == null || layers.length == 0) throw new IllegalArgumentException();
        // проверим детально
        final int size = layers.length;
        for (int i = 0; i < size; i++)
            if (layers[i] == null || (i > 1 && layers[i].getInputSize() != layers[i - 1].getSize()))
                throw new IllegalArgumentException();

        // запомним слои
        this.layers = layers;
    }

    /**
     * Получает размер входного вектора
     * @return Размер входного вектора
     */
    public final int getInputSize() {
        return layers[0].getInputSize();
    }

    /**
     * Получает размер выходного вектора
     * @return Размер выходного вектора
     */
    public final int getOutputSize() {
        return layers[layers.length - 1].getSize();
    }

    /**
     * Получает размер сети
     * @return Размер сети
     */
    public final int getSize() {
        return layers.length;
    }

    /**
     * Получает нейронный слой по индексу
     * @param index Индекс слоя
     * @return Нейронный слой
     */
    public final Layer getLayer(int index) {
        return layers[index];
    }

    /**
     * Вычисляет отклик сети
     * @param input Входной вектор
     * @return Выходной вектор
     */
    public float[] computeOutput(float[] input) {
        // проверки
        if (input == null || input.length != getInputSize())
                throw new IllegalArgumentException();

        // вычислим выходной отклик сети
        float[] output = input;
        final int size = layers.length;
        for (int i = 0; i < size; i++)
            output = layers[i].computeOutput(output);

        // вернем выход
        return output;
    }

    /**
     * Сохраняет нейронну сеть в файл
     * @param fileName Имя файла
     */
    public void saveToFile(String fileName) {
        // проверки
        if (fileName == null) throw new IllegalArgumentException();

        // сохраняем
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(fileName));
            outputStream.writeObject(this);
            outputStream.close();
        }
        catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Загружает нейронную сеть из файла
     * @param fileName Имя файла
     * @return Нейронную сеть
     */
    public static Network loadFromFile(String fileName) {
        // проверки
        if (fileName == null) throw new IllegalArgumentException();

        // загружаем
        Object network = null;
        try {
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fileName));
            network = inputStream.readObject();
            inputStream.close();
        }
        catch (Exception e) {
            throw new IllegalArgumentException(e);
        }

        // отдадим сеть
        return (Network)network;
    }

    /**
     * Слои
     */
    private Layer[] layers;
}
BackpropLayer.java

Интерфейс нейронного слоя обучаемого по алгоритму обратного распространения ошибки. Метод randomize задает начальные случайные значения весов в слое. Метод computeBackwardError вычисляет ошибку в обратном направлении: то есть ту, которая пришла на вход слоя от предыдущего слоя. В качестве параметров computeBackwardError принимает входной вектор, который подавался на вход и вектор ошибки для этого слоя. Метод же adjust подгоняет веса нейроннов в сторону уменьшения ошибки.

Код:
package nnet;

/**
 * Интерфейс слоя обучаемого по алгоритму обратного распространения ошибки
 */
public interface BackpropLayer extends Layer {
    /**
     * Придает случайные значения весам нейронов
     * @param min Минимальное значение
     * @param max Максимальное значение
     */
    void randomize(float min,float max);

    /**
     * Выичисляет следующий вектор ошибки в обратном направлении
     * @param input Входной вектор
     * @param error Вектор ошибки
     * @return Следующий вектор ошибки в обратном направлении
     */
    float[] computeBackwardError(float[] input,float[] error);

    /**
     * Подгоняет веса нейронов в сторону уменьшения ошибки
     * @param input Входной вектор
     * @param error Вектор ошибки
     * @param rate Скорость обучения
     * @param momentum Моментум
     */
    void adjust(float[] input,float[] error,float rate,float momentum);
}
BackpropNetwork.java

Реализация нейронной сети, обучаемой по алгоритму обратного распространенния ошибки.

Код:
package nnet;

/**
 * Нейронная сеть обучаемая по алгоритму обратного распространения ошибки
 */
public final class BackpropNetwork extends Network {
    /**
     * Констрирует нейронную сеть с заданными слоями
     * @param layers
     */
    public BackpropNetwork(Layer[] layers) {
        // передадим родакам
        super(layers);
        // рандомизируем веса
        randomize(0,0.3f);
    }

    /**
     * Придает случайные значения весам нейроннов в сети
     * @param min
     * @param max
     */
    public void randomize(float min,float max) {
        // придаем случайные значения весам в сети
        final int size = getSize();
        for (int i = 0; i < size; i++) {
            Layer layer = getLayer(i);
            if (layer instanceof BackpropLayer) ((BackpropLayer)layer).randomize(min,max);
        }
    }

    /**
     * Обучает сеть паттерну
     * @param input Входной вектор
     * @param goal Заданный выходной вектор
     * @param rate Скорость обучения
     * @param momentum Моментум
     * @return Текущую ошибку обучения
     */
    public float learnPattern(float[] input,float[] goal,float rate,float momentum) {
        // проверки
        if (input == null || input.length != getInputSize() || 
                goal == null || goal.length != getOutputSize()) throw new IllegalArgumentException();

        // делаем проход вперед
        final int size = getSize();
        float[][] outputs = new float[size][];

        outputs[0] = getLayer(0).computeOutput(input);
        for (int i = 1; i < size; i++)
            outputs[i] = getLayer(i).computeOutput(outputs[i - 1]);

        // вычислим ошибку выходного слоя
        Layer layer = getLayer(size - 1);
        final int layerSize = layer.getSize();
        float[] error = new float[layerSize];
        float totalError = 0;

        for (int i = 0; i < layerSize; i++) {
            error[i] = goal[i] - outputs[size - 1][i];
            totalError += Math.abs(error[i]);
        }

        // обновим выходной слой
        if (layer instanceof BackpropLayer)
            ((BackpropLayer)layer).adjust(size == 1 ? input : outputs[size - 2],error,rate,momentum);

        // идем по скрытым слоям
        float[] prevError = error;
        Layer prevLayer = layer;

        for (int i = size - 2; i >= 0; i--,prevError = error,prevLayer = layer) {
            // получим очередной слой
            layer = getLayer(i);
            // вычислим для него ошибку
            if (prevLayer instanceof BackpropLayer)
                error = ((BackpropLayer)prevLayer).computeBackwardError(outputs[i],prevError);
            else
                error = prevError;
            // обновим слой
            if (layer instanceof BackpropLayer)
                ((BackpropLayer)layer).adjust(i == 0 ? input : outputs[i - 1],error,rate,momentum);
        }

        // вернем суммарную ошибку
        return totalError;
    }
}
SigmoidLayer.java

Реализация сигмоидального слоя. В качестве функции активации может использоваться гиперболический тангенс, либо сигмоидальная функция.
package nnet;

Код:
/**
 * Сигмоидальный слой
 */
public final class SigmoidLayer implements BackpropLayer {
    /**
     * Вес
     */
    private final int WEIGHT = 0;

    /**
     * Дельта
     */
    private final int DELTA = 1;

    /**
     * Констрирует сигмоидальный слой
     * @param inputSize Размер входного вектора
     * @param size Размер слоя
     * @param bipolar Флаг биполярного слоя
     */
    public SigmoidLayer(int inputSize,int size,boolean bipolar) {
        // проверки
        if (inputSize < 1 || size < 1) throw new IllegalArgumentException();

        // создаем слой
        matrix = new float[size][inputSize + 1][2];

        // запомним параметры
        this.inputSize = inputSize;
        this.bipolar = bipolar;
    }

    /**
     * Конструирует биполярный слой
     * @param inputSize Размер входного вектора
     * @param size Размер слоя
     */
    public SigmoidLayer(int inputSize,int size) {
        this(inputSize,size,true);
    }

    public int getInputSize() {
        return inputSize;
    }

    public int getSize() {
        return matrix.length;
    }

    public float[] computeOutput(float[] input) {
        // проверки
        if (input == null || input.length != inputSize)
                throw new IllegalArgumentException();

        // вычислим выход
        final int size = matrix.length;
        float[] output = new float[size];
        for (int i = 0; i < size; i++) {
            output[i] = matrix[i][0][WEIGHT];
            for (int j = 0; j < inputSize; j++)
                output[i] += input[j] * matrix[i][j + 1][WEIGHT];
            if (bipolar)
                output[i] = (float)Math.tanh(output[i]);
            else
                output[i] = 1 / (1 + (float)Math.exp(-output[i]));
        }

        // вернем оклик
        return output;
    }

    public void randomize(float min,float max) {
        final int size = matrix.length;
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < inputSize + 1; j++) {
                matrix[i][j][WEIGHT] = min + (max - min) * (float)Math.random();
                matrix[i][j][DELTA] = 0;
            }
        }
    }

    public float[] computeBackwardError(float[] input,float[] error) {
        // проверки
        if (input == null || input.length != inputSize ||
                error == null || error.length != matrix.length) throw new IllegalArgumentException();

        // вычислим входящую ошибку
        float[] output = computeOutput(input);
        final int size = matrix.length;
        float[] backwardError = new float[inputSize];

        for (int i = 0; i < inputSize; i++) {
            backwardError[i] = 0;
            for (int j = 0; j < size; j++)
                backwardError[i] += error[j] * matrix[j][i + 1][WEIGHT] *
                        (bipolar ? 1 - output[j] * output[j] : output[j] * (1 - output[j]));
        }

        // вернем ошибку
        return backwardError;
    }

    public void adjust(float[] input,float[] error,float rate,float momentum) {
        // проверки
        if (input == null || input.length != inputSize || 
                error == null || error.length != matrix.length) throw new IllegalArgumentException();

        // обновляем веса
        float[] output = computeOutput(input);
        final int size = matrix.length;

        for (int i = 0; i < size; i++) {
            final float grad = error[i] * (bipolar ? 1 - output[i] * output[i] : output[i] * (1 - output[i]));
            // обновляем нулевой вес
            matrix[i][0][DELTA] = rate * grad + momentum * matrix[i][0][DELTA];
            matrix[i][0][WEIGHT] += matrix[i][0][DELTA];
            // обновим остальные веса
            for (int j = 0; j < inputSize; j++) {
                matrix[i][j + 1][DELTA] = rate * input[j] * grad + momentum * matrix[i][j + 1][DELTA];
                matrix[i][j + 1][WEIGHT] += matrix[i][j + 1][DELTA];
            }
        }
    }

    /**
     * Размер входного вектора
     */
    private final int inputSize;

    /**
     * Флаг биполярного слоя
     */
    private final boolean bipolar;

    /**
     * Матрица слоя
     */
    private float[][][] matrix;
}

WTALayer.java

Реализация слоя победитель получает все. Победителем признаеться тот нейрон, у которого выход больше всех нейроннов на величину не менее minLevel. В противном случае все нейронны признаются проигравшими.
package nnet;

Код:
/**
 * WTA слой
 */
public final class WTALayer implements BackpropLayer {
    /**
     * Конструирует WTA слой заданного размера и уровнем доверия
     * @param size Размер слоя
     * @param minLevel Уровень доверия
     */
    public WTALayer(int size,float minLevel) {
        // проверки
        if (size < 1) throw new IllegalArgumentException();
        // запомним параметры слоя
        this.size = size;
        this.minLevel = minLevel;
    }

    public int getInputSize() {
        return size;
    }

    public int getSize() {
        return size;
    }

    public float[] computeOutput(float[] input) {
        // проверки
        if (input == null || input.length != size) throw new IllegalArgumentException();

        // найдем победителя
        int winner = 0;
        for (int i = 1; i < size; i++)
            if (input[i] > input[winner]) winner = i;

        // готовим ответ
        float[] output = new float[size];

        // проверим на минимальный уровень расхождения
        if (minLevel > 0) {
            float level = Float.MAX_VALUE;
            for (int i = 0; i < size; i++)
                if (i != winner && Math.abs(input[i] - input[winner]) < level)
                    level = Math.abs(input[i] - input[winner]);
            if (level < minLevel) return output;
        }

        // говорим кто победитель
        output[winner] = 1;

        // вернем отклик
        return output;
    }

    public void randomize(float min,float max) {
    }

    public float[] computeBackwardError(float[] input,float []error) {
        // проверки
        if (input == null || input.length != size || error == null ||
                error.length != size) throw new IllegalArgumentException();

        // расчитываем ошибку
        float[] backwardError = new float[size];
        float[] output = computeOutput(input);

        for (int i = 0; i < size; i++)
            backwardError[i] = error[i] + output[i] - input[i];

        // вернем входящую ошибку
        return backwardError;
    }

    public void adjust(float[] input,float[] error,float rate,float momentum) {
    }

    /**
     * Получает минимальный уровень между победителем и всеми остальными нейронами
     * @return Минимальный уровень между победителем и всеми остальными нейронами
     */
    public float getMinLevel() {
        return minLevel;
    }

    /**
     * Устанавливает минимальный уровень между победитлем и всеми остальными нейронами
     * @param minLevel Новый минимальный уровень
     */
    public void setMinLevel(float minLevel) {
        this.minLevel = minLevel;
    }

    /**
     * Размер слоя
     */
    private final int size;

    /**
     * Минимальный уровень между победителем и всеми остальными нейронами
     */
    private float minLevel;
}