Как написать нейросеть на Java?
Разобрали на простом примере, как написать нейросеть на Java. В статье есть пример кода и пояснения к нему.
Если у вас есть базовые знания Java, вы можете попробовать написать простую нейросеть: научить нейрон распознавать закономерность. В статье мы разобрали, как это сделать.
Как работает нейросеть?
Работа искусственного интеллекта похожа на работу мозга человека. Искусственные нейроны получают информацию из множества источников, обмениваются этими данными, анализируют их и выдают решение поставленной задачи. Чтобы нейросеть работала правильно, ее нужно обучить: показать принцип решения конкретной задачи.

В этой статье мы не будем разбирать Java с нуля. Материал подходит для тех, кто уже знаком с программированием. Если ты хочешь освоить Java до профессионального уровня и устроиться на работу с зарплатой от 120 000 рублей, приглашаем тебя на курс «Java-разработка» с оплатой обучения после гарантированного трудоустройства, если ты не найдешь работу, платить за курс не нужно.
Начни свой путь в IT прямо сейчас.
Мы не возьмем деньги, если ты не найдешь работу после обучения в Kata Academy!
Какие задачи могут решать нейросети?
Зачем вообще писать свою нейросеть? Они могут решать задачи в разных сферах жизни.
В работе разработчика
Прототипирование алгоритмов машинного обучения. Когда нужно быстро проверить идею или обучить простую модель «с нуля» без использования тяжелых библиотек, можно набросать короткий код, понять, как ведут себя веса, как рассчитывается ошибка и так далее.

Автоматизация рутинных задач. Например, простая модель может помогать фильтровать входящие данные (почту, логи), классифицировать строки (теги, категории) или делать простые прогнозы (загрузки, посетители).

Учебные проекты. Полезно «изнутри» понять принципы машинного обучения: матричные операции, работу функции активации, процесс обучения (градиентный спуск), чтобы затем легче было работать с большими фреймворками (TensorFlow, PyTorch и другими).
В мире около 9 миллионов java-программистов. И спрос на это направление только растёт. Практически каждая шестая вакансия для разработчиков связана с Java. Твой оффер ждёт тебя после обучения в Kata Academy. Сдай тестовое и начни карьеру в IT!
Для личных целей и развлечения
Игры и эксперименты. Можно обучить простейший «искусственный интеллект» в игрушечном примере: игра «Камень, ножницы, бумага», угадывание чисел, минимальная рекомендационная система (например, рекомендовать фильмы на основе простых признаков).

Анализ повседневных данных. Например, сделать мини-приложение, которое по шуму с микрофона определяет, есть ли кто-то дома; или элементарный трекер спортивных тренировок, пытающийся «предсказать», в какой день вы будете заниматься с большей вероятностью.

Исследовательский интерес. Понять, как из базовых кирпичиков (матрицы, произведения, функция активации) в итоге складываются сложные модели.

Главный плюс написания такой нейросети своими руками — это обучение базовым механизмам. Вы увидите, как шаг за шагом корректируются веса, как получается выход, и поймёте «магическую кухню» нейросетей.
Как написать простую нейросеть на Java
Далее разберем, как выглядит минимальная реализация простой «однонейронной» сети на Java. Рассмотрев код, ты поймешь:

  • Как задаются и инициализируются веса.
  • Как работает функция активации (сигмоида).
  • Как считается ошибка и корректируются веса (методом обратного распространения ошибки в самом примитивном виде).
  • Как сеть «учится» на тренировочном наборе.
Наш вопрос (запрос) к сети:
— Дано три двоичных входа (далее в примере [1, 0, 0]), чему будет равен выход?

Ответ, который мы получим далее в примере:
— Выход близок к 1 (потому что в данном наборе данных сеть выучила, что выход соответствует первому входу).
Общая идея и структура кода
1) Имеется набор тренировочных данных:
training_set_inputs: [
[0, 0, 1],
[1, 1, 1],
[1, 0, 1],
[0, 1, 1]
]
training_set_outputs: [0, 1, 1, 0]

2) Веса (synaptic weights) — это массив длины 3 (так как у нас 3 входа). Изначально они задаются случайно.

3) Функция активации — сигмоида:

\sigma(x) = \frac{1}{1 + e^{-x}}

и её производная:

\sigma{\prime}(x) = x \times (1 - x),

но обрати внимание: в формуле производной под x мы будем подставлять уже сигмоиду (т.е. выход нейрона), а не входную сумму.

4) Процесс обучения:
  • Вычисляем выход нейрона для всех тренировочных примеров (feedforward).
  • Сравниваем с желаемым ответом, получаем ошибку.
  • Корректируем веса.
Минимальный рабочий пример кода
import java.util.Random;

public class NeuralNetwork {

// У нас будет всего 3 веса (по одному на каждый вход)
private double[] synapticWeights;

// Конструктор — инициализируем случайные веса
public NeuralNetwork() {
Random random = new Random(1); // фиксируем "зёрнышко", чтобы результат был воспроизводим
synapticWeights = new double[3];
for (int i = 0; i < 3; i++) {
// случайные числа в диапазоне [-1; 1]
synapticWeights[i] = 2 * random.nextDouble() - 1;
}
}

// Сигмоида
private double sigmoid(double x) {
return 1.0 / (1.0 + Math.exp(-x));
}

// Производная сигмоиды (на вход подаём уже "выход нейрона", то есть sigmoid-значение)
private double sigmoidDerivative(double x) {
return x * (1.0 - x);
}

/**
* Основной метод обучения.
* @param trainingInputs матрица (4 x 3): 4 примера, в каждом по 3 входа
* @param trainingOutputs вектор (4), на каждую строку — свой выход (0 или 1)
* @param numberOfIterations сколько итераций (например, 10_000)
*/
public void train(double[][] trainingInputs, double[] trainingOutputs, int numberOfIterations) {
for (int i = 0; i < numberOfIterations; i++) {

// 1) Прямой проход (feedforward) — считаем выходы нейрона для всех примеров
double[] outputs = think(trainingInputs);

// 2) Считаем ошибку: desiredOutput - actualOutput
double[] error = new double[outputs.length];
for (int j = 0; j < outputs.length; j++) {
error[j] = trainingOutputs[j] - outputs[j];
}

// 3) Считаем, как нужно подправить веса
// adjustment = X^T * (error * (output * (1-output)))
// X^T — это (3 x 4), error и output — это (4)
double[] adjustments = new double[synapticWeights.length];

// Для каждого веса: складываем вклад по всем примерам
for (int w = 0; w < synapticWeights.length; w++) {
double sum = 0.0;
// пробегаемся по всем тренировочным примерам
for (int sampleIndex = 0; sampleIndex < trainingInputs.length; sampleIndex++) {
double outputVal = outputs[sampleIndex];
double delta = error[sampleIndex] * sigmoidDerivative(outputVal);

// вклад в градиент от sampleIndex-го примера
// вход trainingInputs[sampleIndex][w] умножаем на delta
sum += trainingInputs[sampleIndex][w] * delta;
}
adjustments[w] = sum;
}

// 4) Корректируем веса
for (int w = 0; w < synapticWeights.length; w++) {
synapticWeights[w] += adjustments[w];
}
}
}

/**
* Метод "подумать" (think).
* Возвращает массив выходных значений для всех строк входных данных.
*/
public double[] think(double[][] inputs) {
double[] result = new double[inputs.length];
for (int i = 0; i < inputs.length; i++) {
double sum = 0.0;
// Складываем вход * вес для каждого из 3 входов
for (int j = 0; j < inputs[i].length; j++) {
sum += inputs[i][j] * synapticWeights[j];
}
// Прогоняем через сигмоиду
result[i] = sigmoid(sum);
}
return result;
}

/**
* Перегруженный метод "подумать" для одного примера.
* Удобно, чтобы протестировать что-то вроде [1, 0, 0].
*/
public double thinkSingle(double[] input) {
double sum = 0.0;
for (int j = 0; j < input.length; j++) {
sum += input[j] * synapticWeights[j];
}
return sigmoid(sum);
}

// Вывести веса (для отладки)
public void printWeights() {
for (double w : synapticWeights) {
System.out.printf("%.6f ", w);
}
System.out.println();
}

// Точка входа для запуска
public static void main(String[] args) {
// 1) Создаём нашу нейронную сеть
NeuralNetwork nn = new NeuralNetwork();

System.out.println("Random starting synaptic weights:");
nn.printWeights();

// 2) Задаём тренировочный набор
// 4 примера, каждый состоит из 3 входов
double[][] trainingSetInputs = {
{0, 0, 1},
{1, 1, 1},
{1, 0, 1},
{0, 1, 1}
};
// 4 выходных значения (по одному для каждой строки)
double[] trainingSetOutputs = {0, 1, 1, 0};

// 3) Обучаем сеть (10_000 итераций)
nn.train(trainingSetInputs, trainingSetOutputs, 10_000);

System.out.println("\nNew synaptic weights after training:");
nn.printWeights();

// 4) Тестируем на новом примере
double[] newSituation = {1, 0, 0};
double output = nn.thinkSingle(newSituation);
System.out.println("\nConsidering new situation [1, 0, 0] -> ?: " + output);
}
}
Пояснения по коду
1) private double[] synapticWeights;
  • Хранит три числа (веса), случайно проинициализированные.в.
2) public void train(...)
В цикле на заданное число итераций (10{,}000):
  • считаем выход для всего набора (массива) входных данных;
  • вычисляем вектор ошибки error[j] = desired - actual;
  • по формуле \Delta = X^T \times (\text{error} \times \text{производная}) подсчитываем, насколько нужно сдвинуть каждый вес;
  • прибавляем корректировку к массиву весов.
3) public double[] think(double[][] inputs)
Для каждой строки входных данных (например, [0, 0, 1]):
  • суммируем input[j] * synapticWeights[j];
  • пропускаем сумму через сигмоиду.
4) public double thinkSingle(double[] input)
Упрощённая версия для одного примера. Удобна, чтобы «спросить» у сети ответ на [1, 0, 0].
Читать про IT — здорово, но ещё лучше работать в IT. В Kata Academy тебя ждёт обучение с оплатой после трудоустройства. А минимальная зарплата наших выпускников, которую мы гарантируем — 100 тысяч рублей. Ждём тебя в Академии!
Результат
  • В начале увидите «случайные» веса.
  • После обучения увидите «новые» веса.
  • Проверка на [1, 0, 0] даст число, близкое к 1.0 (что эквивалентно логической «1»).
При запуске вы увидите вывод в консоли, например (цифры могут немного отличаться, поскольку даже при одинаковом seed(1) возможны небольшие расхождения на разных платформах):

Random starting synaptic weights:
-0.165956 0.440649 -0.999771

New synaptic weights after training:
9.672993 -0.207843 -4.629637

Considering new situation [1, 0, 0] -> ?: 0.999937

Мы разобрались, как выглядит минимальная реализация простой «однонейронной» сети на Java. В дальнейшем ты можешь расширять этот пример, добавляя больше нейронов, новые функции активации, другие методы оптимизации (Adam, RMSProp и т.д.), подключать популярные фреймворки (DeepLearning4j), а также использовать готовые библиотеки для линеарной алгебры, чтобы упростить работу с матрицами. Но начало всегда одно: понимание базовых формул, умение «вручную» собрать небольшой прототип — и именно в этом поможет такая простая реализация.
Заключительные советы
Не останавливайся на одном нейроне. Для более сложных задач (распознавание изображений, перевод текста и других) нужны многослойные архитектуры. В Java есть фреймворки (например, DeepLearning4j), позволяющие быстрее строить многослойные сети.

Следи за переобучением. Если данных мало, а итераций много, сеть может «зазубрить» тренировочные примеры и плохо обобщать на новые. В примере с четырьмя строками это не страшно, так как задача примитивна, но в реальных проектах нужно следить за балансом.

Экспериментируй с разными функциями активации. Сигмоида — классический выбор, но есть и ReLU, LeakyReLU, tanh и другие. В реальных проектах (особенно в глубоких сетях) сигмоиду применяют реже.

Попробуй другие задачи. Например, замени тренировочный набор своими данными или проверь, получится ли создать простую логическую сеть, реализующую AND, OR и XOR на Java.
Обучение Java, Go, frontend-разработке, тестированию с оплатой после трудоустройства
Приглашаем тебя на курсы программирования и тестирования с оплатой обучения после трудоустройства. Мы работаем по модели ISA: ты сначала проходишь весь курс и устраиваешься на работу, а потом оплачиваешь обучение с зарплаты. До трудоустройства платежей нет. Поступай без накоплений, банковских кредитов и рассрочек.

Курсы по разработке подойдут тебе, если ты:
  • знаешь базу любого языка программирования,
  • уже хорошо умеешь программировать, но не можешь получить оффер,
  • работаешь в IT и хочешь сменить язык.
Узнай подробности и выбери направление по ссылке.
Стань тем, кто задаёт тон в IT!
Подпишись на нашу рассылку и первым получай статьи по Java, JavaScript, Golang и QA. Позволь себе быть экспертом!