Всем известно, что обучение нейросетей требует значительных вычислительных ресурсов. Но что делать, если у вас нет мощного оборудования? В этой статье я расскажу, как обучить нейросеть частями, но и объясню ключевые понятия вроде слоев, батчей, и функций активации. Эта статья может быть полезна начинающим разработчикам, кто только погружается в нейронки.

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

Основные понятия нейросетей

Прежде чем углубляться в процесс, кратко объясним ключевые термины, чтобы статья была понятна даже новичкам.

1. Слои нейросети

Нейросеть состоит из слоев, где каждый слой выполняет математическую обработку данных:

  • Входной слой принимает данные (например, текст или изображение).

  • Скрытые слои обрабатывают данные, выявляя паттерны и взаимосвязи.

  • Выходной слой выдает результат, например, вероятность принадлежности к какому-либо классу.

В простом многослойном перцептроне (MLP) каждый узел (нейрон) в слое соединен с узлами следующего слоя. Вот пример:

  • Входные данные: 100 чисел (например, векторы слов).

  • Первый скрытый слой: 64 нейрона. Каждый из них вычисляет взвешенную сумму входов и применяет функцию активации (например, ReLU).

  • Выходной слой: 2 нейрона (например, для предсказания "положительный" или "отрицательный").

2. Батчи

Когда мы обучаем нейросеть, данные разбиваются на части — батчи. Вместо того чтобы подавать всю обучающую выборку сразу, модель обрабатывает один батч за раз.

  • Пример: если у нас есть 1000 текстов и размер батча — 32, то модель обработает данные в 32 итерациях (по 32 текста за итерацию).

Зачем нужны батчи?

  • Экономия памяти: мы не храним весь набор данных в оперативной памяти.

  • Ускорение обучения: обработка маленьких порций данных позволяет быстрее корректировать параметры модели.

3. Функции активации

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

  • ReLU (Rectified Linear Unit): пропускает положительные значения, обнуляя отрицательные.

  • Softmax: преобразует выходной слой в вероятности (сумма всех значений = 1).

Как я реализовал обучение

Создадим файл transactions.csv, содержащий данные о транзакциях. Каждая строка — это одна транзакция, включающая 10 входных параметров и метку класса (0 или 1). Вот пример содержимого файла:

0.5,0.1,0.3,0.2,0.9,0.7,0.4,0.6,0.8,0.3,1 
0.6,0.2,0.4,0.1,0.8,0.5,0.3,0.5,0.7,0.2,0
0.7,0.3,0.5,0.4,0.7,0.6,0.2,0.4,0.9,0.1,1

Каждая строка состоит из 10 чисел (фичей), за которыми следует метка класса:

  • 0 — нормальная транзакция,

  • 1 — подозрительная транзакция.

Реализация кода

1. Загрузка данных из файла

Напишем класс DataLoader, который будет загружать данные из файла и делить их на батчи. Это позволяет работать с большими файлами, обрабатывая данные частями:

import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.factory.Nd4j;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class DataLoader {
    private final String filePath;
    private final int batchSize;

    public DataLoader(String filePath, int batchSize) {
        this.filePath = filePath;
        this.batchSize = batchSize;
    }

    public List<DataSet> loadData() throws IOException {
        List<DataSet> dataBatches = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            List<double[]> features = new ArrayList<>();
            List<double[]> labels = new ArrayList<>();

            while ((line = br.readLine()) != null) {
                String[] parts = line.split(",");
                double[] input = new double[parts.length - 1];
                for (int i = 0; i < parts.length - 1; i++) {
                    input[i] = Double.parseDouble(parts[i]);
                }
                features.add(input);

                double[] label = new double[2];
                label[Integer.parseInt(parts[parts.length - 1])] = 1.0;
                labels.add(label);

                if (features.size() == batchSize) {
                    dataBatches.add(new DataSet(Nd4j.create(features.toArray(new double[0][])), Nd4j.create(labels.toArray(new double[0][]))));
                    features.clear();
                    labels.clear();
                }
            }
        }
        return dataBatches;
    }
}

2. Создание модели нейронной сети

Для классификации данных создадим простую нейросеть с двумя скрытыми слоями. Она будет обучаться определять подозрительные транзакции:

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.lossfunctions.LossFunctions;

public class ModelBuilder {
    public static MultiLayerNetwork buildModel(int inputSize, int outputSize) {
        MultiLayerConfiguration config = new NeuralNetConfiguration.Builder()
                .learningRate(0.01)
                .list()
                .layer(new DenseLayer.Builder()
                        .nIn(inputSize)
                        .nOut(64)
                        .activation(Activation.RELU)
                        .build())
                .layer(new DenseLayer.Builder()
                        .nIn(64)
                        .nOut(32)
                        .activation(Activation.RELU)
                        .build())
                .layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                        .nIn(32)
                        .nOut(outputSize)
                        .activation(Activation.SOFTMAX)
                        .build())
                .build();

        MultiLayerNetwork model = new MultiLayerNetwork(config);
        model.init();
        return model;
    }
}

3. Обучение модели

Теперь объединим загрузку данных и модель для обучения:

import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.nd4j.linalg.dataset.DataSet;

import java.io.IOException;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        String filePath = "transactions.csv"; //путь до файла
        int batchSize = 50;
        int numFeatures = 10; // Количество входных признаков
        int numClasses = 2;   // Бинарная классификация

        DataLoader dataLoader = new DataLoader(filePath, batchSize);
        List<DataSet> trainingData = dataLoader.loadData();

        MultiLayerNetwork model = ModelBuilder.buildModel(numFeatures, numClasses);

        int numEpochs = 10;
        for (int epoch = 0; epoch < numEpochs; epoch++) {
            for (DataSet batch : trainingData) {
                model.fit(batch);
            }
            System.out.println("Эпоха " + (epoch + 1) + " завершена.");
        }

        System.out.println("Обучение завершено.");
    }
}

Почему этот подход подходит для больших данных?

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

  2. Простота архитектуры: нейросеть оптимизирована для бинарной классификации, без избыточных слоёв. Это экономит ресурсы.

  3. Масштабируемость: код можно адаптировать для других задач, включая мультиклассовую классификацию или регрессию.

  4. Эффективность: благодаря библиотекам DeepLearning4j и ND4J обучение проходит быстро даже на процессоре.

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

Комментарии (1)


  1. Ascard
    22.11.2024 11:39

    Сколько не читал подобных статей, ни в одной, вот честно - ни в одной, не видел объяснения почему была взята именно такая конфигурация слоёв и нейронов в них. Создаётся ощущение что это или сакральные знания доступ к которым могут получить не только лишь все, либо цифры берутся с потолка. А вот как хочется толковую статья где бы на пальца объяснялось когда и почему лучше взять, к примеру. не 1 скрытый слой а 5 и не 64 нейрона в нём а 256. Может есть у кого? Заранее благодарю.