Пример реализации простой нейронной сети, обучаемой по алгоритму обратного распространенния ошибки.
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; }