Пишем простую нейронку для адаптивного управления JVM: обучение и применение многослойной нейронной сети на Java

от автора

Данная статья является описанием моего эксперимента передать управление ресурсами JVM нейронной сети, которая будет предсказывать необходимость управления ресурсами на основе текущих данных, таких как загрузка CPU и память.

Отмечу, что не берется в расчет влияние, в текущем моменте времени, дополнительных процессов загрузки ресурсов нашей системы.

Применимость и потенциальные возможности

Предполагается, что нейронная сеть сможет предсказывать пиковые нагрузки, что позволит избежать избыточного расхода ресурсов или, наоборот, их нехватки. На практике это может быть полезно в разных системах, особенно в микросервисной архитектуре, где контейнеры регулярно требуют динамического управления ресурсами. Например Kubernetes динамически изменяет ресурсы на основе данных, которые передает AI-сервис. Это может быть полезно, если в будущем потребуется увеличить автономность системы.

Что мы реализуем

Мы построим систему, которая:

  1. Считывает показатели системы (CPU, память) в реальном времени.

  2. Обучает нейронную сеть на исторических данных для предсказания вероятности перегрузки.

  3. Корректирует параметры JVM, если прогнозируемая нагрузка превышает порог.

Пример данных для обучения

Для простоты в качестве данных для обучения мы будем использовать CSV-файл jvm_usage.csv, который содержит три столбца:

  • CPU — текущая загрузка процессора,

  • Memory — текущее использование памяти,

  • HighLoad — метка перегрузки (0 или 1), обозначающая, была ли нагрузка высокой.

Пример содержимого CSV:

0.7,3000,0
0.8,4000,1
0.6,2000,0
1.0,4500,1

Настройка многослойной нейронной сети на Java

Для реализации нейронной сети мы используем библиотеку DeepLearning4j. Создадим модель с двумя слоями для обработки входных данных о CPU и памяти и одним выходным нейроном, который будет прогнозировать необходимость увеличения ресурсов JVM.

Подключение необходимых зависимостей

В build.gradle добавьте:

dependencies {    implementation ("org.slf4j:slf4j-simple:1.7.32")   implementation ("org.deeplearning4j:deeplearning4j-core:1.0.0-beta7")   implementation ("org.nd4j:nd4j-native-platform:1.0.0-beta7")   implementation ("org.datavec:datavec-api:1.0.0-beta7")   implementation ("org.datavec:datavec-local:1.0.0-beta7")   }

Код для настройки и обучения модели

import org.deeplearning4j.nn.api.OptimizationAlgorithm;  import org.deeplearning4j.nn.conf.MultiLayerConfiguration;  import org.deeplearning4j.nn.conf.NeuralNetConfiguration;  import org.deeplearning4j.nn.conf.layers.DenseLayer;  import org.deeplearning4j.nn.conf.layers.OutputLayer;  import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;  import org.nd4j.linalg.activations.Activation;  import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;  import org.deeplearning4j.datasets.datavec.RecordReaderDataSetIterator; import org.nd4j.linalg.learning.config.Nesterovs;  import org.nd4j.linalg.lossfunctions.LossFunctions;  import org.nd4j.linalg.api.ndarray.INDArray;  import org.nd4j.linalg.factory.Nd4j;  import org.datavec.api.records.reader.impl.csv.CSVRecordReader;  import org.datavec.api.split.FileSplit; import java.io.File  public class JVMAIManager {      private MultiLayerNetwork model;      public void initializeModel() {         MultiLayerConfiguration config = new NeuralNetConfiguration.Builder()                 .seed(123)                 .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)                 .updater(new Nesterovs(0.1, 0.9))                 .list()                 .layer(0, new DenseLayer.Builder().nIn(2).nOut(10)                         .activation(Activation.RELU)                         .build())                 .layer(1, new DenseLayer.Builder().nIn(10).nOut(10)                         .activation(Activation.RELU)                         .build())                 .layer(2, new OutputLayer.Builder(LossFunctions.LossFunction.XENT)                         .activation(Activation.SIGMOID)                         .nIn(10).nOut(1).build())                 .build();          model = new MultiLayerNetwork(config);         model.init();     }      public void trainModel(String datasetPath) throws Exception {         int batchSize = 50;         int labelIndex = 2;         int numClasses = 1;          CSVRecordReader reader = new CSVRecordReader();         reader.initialize(new FileSplit(new File(datasetPath)));         DataSetIterator iterator = new RecordReaderDataSetIterator(reader, batchSize, labelIndex, numClasses);          model.fit(iterator);     }      public double predict(double currentCpu, double currentMemory) {         INDArray input = Nd4j.create(new double[]{currentCpu, currentMemory}, new int[]{1, 2});         INDArray output = model.output(input);         return output.getDouble(0);     } } 

Данный блок кода строит архитектуру нейронной сети, задавая её конфигурацию и порядок слоёв:

  1. Инициализация настроек сети. Мы начинаем с установки базовых параметров сети с помощью NeuralNetConfiguration.Builder(). Здесь задаётся seed (случайное начальное значение для генератора), которое помогает воспроизводить результаты, а также выбирается алгоритм оптимизации — стохастический градиентный спуск (SGD). Этот метод регулирует веса сети, минимизируя ошибку на каждом шаге обучения. Дополнительно, для улучшения и стабилизации процесса обучения, мы подключаем обновляющий алгоритм Nesterovs — метод, который «заглядывает» вперёд, избегая лишних колебаний.

  2. Создание слоёв. Мы добавляем три слоя в сеть:

    • Первый слойDenseLayer, или полносвязный слой, принимает на вход два признака (например, значения загрузки процессора и памяти) и выводит 10 нейронов. Он использует функцию активации ReLU (Rectified Linear Unit), которая сохраняет положительные значения и обнуляет отрицательные. Этот слой помогает сети находить сложные зависимости между признаками.

    • Второй слой — ещё один полносвязный слой с 10 нейронами и функцией активации ReLU. Этот слой обрабатывает и преобразует выходные данные из первого слоя, углубляя сеть для лучшего обучения.

    • Выходной слойOutputLayer, служит для получения конечного предсказания. Здесь мы используем функцию активации Sigmoid, которая выводит значение от 0 до 1. Это полезно для задач бинарной классификации, где результат нужно интерпретировать как вероятность принадлежности к одному из двух классов. Мы также указываем функцию потерь XENT (кросс-энтропия), которая вычисляет расхождение между предсказанием и истинным ответом, помогая сети улучшать точность.

  3. Инициализация модели. С помощью вызова MultiLayerNetwork создаётся нейронная сеть на основе конфигурации, и вызывается model.init(), что завершает процесс построения архитектуры сети и подготавливает её для обучения.

Мониторинг и адаптивная настройка JVM

Добавим классJVMAIAutotuner для мониторинга и динамического управления параметрами JVM:

import java.lang.management.ManagementFactory;  import com.sun.management.OperatingSystemMXBean;  import java.util.logging.Logger;  public class JVMAIAutotuner {      private static final Logger logger = Logger.getLogger(JVMAIAutotuner.class.getName());     private final OperatingSystemMXBean osBean;     private final JVMAIManager aiManager;      public JVMAIAutotuner(JVMAIManager aiManager) {         this.osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();         this.aiManager = aiManager;     }      public void monitorAndAdapt() {         try {             while (true) {                 double cpuLoad = osBean.getSystemCpuLoad();                 double usedMemory = osBean.getTotalPhysicalMemorySize() - osBean.getFreePhysicalMemorySize();                 double prediction = aiManager.predict(cpuLoad, usedMemory);                  logger.info(String.format("Прогноз вероятности высокой нагрузки: %.2f", prediction));                  if (prediction > 0.8) {                     logger.info("Высокая вероятность перегрузки. Увеличиваем ресурсы JVM...");                     increaseHeapMemory();                 }                  Thread.sleep(5000);             }         } catch (Exception e) {             logger.warning("Ошибка в мониторинге и адаптации: " + e.getMessage());         }     }      private void increaseHeapMemory() {         logger.info("Перезапуск JVM с увеличенной памятью...");         // Логика перезапуска или конфигурации контейнера     } } 

Локальный запуск кода

В классе Main создаем экземпляры JVMAIManager и JVMAIAutotuner и запускаем:

public class Main {     public static void main(String[] args) throws Exception {         JVMAIManager aiManager = new JVMAIManager();         aiManager.initializeModel();         aiManager.trainModel("path/to/jvm_usage.csv");  // Укажите путь к CSV-файлу          JVMAIAutotuner autotuner = new JVMAIAutotuner(aiManager);         autotuner.monitorAndAdapt();     } }

Результаты и возможные доработки

Такой подход позволяет автоматически управлять ресурсами JVM, прогнозируя высокие нагрузки. Вы можете адаптировать этот код для использования в реальных условиях, например, в среде Kubernetes, добавив поддержку динамической настройки контейнеров через API и интеграцию с системой мониторинга, такой как Prometheus.


ссылка на оригинал статьи https://habr.com/ru/articles/856376/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *