Наука о данных в реальном мире

Deep Java Library (DJL) - набор инструментов для глубокого обучения для разработчиков Java

Amazon выпускает библиотеку Deep Java Library (DJL), которая дает разработчикам Java возможность начать глубокое обучение.

Deep Java Library (DJL) - это библиотека с открытым исходным кодом, созданная Amazon для разработки моделей машинного обучения (ML) и глубокого обучения (DL) непосредственно на Java, упрощая использование фреймворков глубокого обучения.

Недавно я использовал DJL для разработки модели классификации обуви и нашел этот инструментарий интуитивно понятным и простым в использовании; очевидно, что много внимания было уделено дизайну и тому, как Java-разработчики будут его использовать. API DJL абстрагируют часто используемые функции для разработки моделей и управления инфраструктурой. Я обнаружил, что высокоуровневые API, используемые для обучения, тестирования и выполнения логического вывода, позволили мне использовать свои знания Java и жизненного цикла машинного обучения для разработки модели менее чем за час с минимальным кодом.

Модель классификации обуви

Модель классификации обуви - это мультиклассовая модель компьютерного зрения (CV), обученная с использованием обучения с учителем, которая классифицирует обувь по одному из четырех классов: ботинки, сандалии, туфли или тапочки.

О данных

Самая важная часть разработки точной модели машинного обучения - использование данных из авторитетного источника. Источником данных для модели классификации обуви является набор данных UTZappos50k, предоставленный Техасским университетом в Остине и свободно доступный для академического некоммерческого использования. Набор данных обуви состоит из 50 025 изображений каталога с пометками, собранных с сайта Zappos.com.

Обучите модель классификации обуви

Обучение - это процесс создания модели машинного обучения путем предоставления обучающих данных алгоритму обучения для изучения. Термин модель относится к артефакту, создаваемому в процессе обучения; модель содержит шаблоны, обнаруженные в обучающих данных, и может использоваться для прогнозирования (или вывода). Перед тем, как начать тренировочный процесс, я настроил свою локальную среду для развития. Вам понадобится JDK 8 (или новее), IntelliJ, механизм машинного обучения для обучения (например, Apache MXNet), переменная среды, указывающая путь к вашему движку, и зависимости сборки для DJL.

dependencies {
compile "org.apache.logging.log4j:log4j-slf4j-impl:2.12.1"
compile "ai.djl:api:0.2.0"
compile "ai.djl:basicdataset:0.2.0"
compile "ai.djl:examples:0.2.0"
compile "ai.djl:model-zoo:0.2.0"
compile "ai.djl.mxnet:mxnet-model-zoo:0.2.0"
runtimeOnly "ai.djl.mxnet:mxnet-native-mkl:1.6.0-a:osx-x86_64"
}

DJL остается верным девизу Java: напиши один раз, запусти где угодно (WORA), будучи независимым от движка и фреймворка глубокого обучения. Разработчики могут написать код, работающий на любом движке. DJL в настоящее время предоставляет реализацию Apache MXNet, механизма машинного обучения, который упрощает разработку глубоких нейронных сетей. API DJL используют JNA, Java Native Access, для вызова соответствующих операций Apache MXNet. С точки зрения оборудования, обучение происходило локально на моем ноутбуке с использованием центрального процессора. Однако для лучшей производительности команда DJL рекомендует использовать машину как минимум с одним графическим процессором. Если у вас нет доступного графического процессора, всегда есть возможность использовать Apache MXNet на Amazon EC2. Приятной особенностью DJL является то, что он обеспечивает автоматическое определение CPU / GPU на основе конфигурации оборудования, чтобы всегда обеспечивать наилучшую производительность.

Загрузить набор данных из источника

Данные обуви были сохранены локально и загружены с использованием набора данных DJL ImageFolder, который представляет собой набор данных, который может извлекать изображения из локальной папки. В терминах DJL Dataset просто содержит обучающие данные. Существуют реализации наборов данных, которые можно использовать для загрузки данных (на основе предоставленного вами URL-адреса), извлечения данных и автоматического разделения данных на наборы для обучения и проверки. Автоматическое разделение - полезная функция, поскольку важно никогда не использовать одни и те же данные, на которых была обучена модель, для проверки ее производительности. Набор данных проверки обучения используется для поиска закономерностей в данных; набор данных проверки используется для оценки точности модели обуви в процессе обучения.

//identify the location of the training dataString trainingDatasetRoot = "src/test/resources/imagefolder/train";
//identify the location of the validation dataString validateDatasetRoot = "src/test/resources/imagefolder/validate";
//create training ImageFolder dataset
ImageFolder trainingDataset = initDataset(trainingDatasetRoot);
//create validation ImageFolder dataset
ImageFolder validateDataset = initDataset(validateDatasetRoot);
private ImageFolder initDataset(String datasetRoot) throws IOException {
     ImageFolder dataset = new ImageFolder
          .Builder()
          .setRepository(new    
              SimpleRepository(Paths.get(datasetRoot))).optPipeline(
     // create preprocess pipeline
     new Pipeline()
          .add(new Resize(NEW_WIDTH, NEW_HEIGHT))
          .add(new ToTensor()))
          .setSampling(BATCH_SIZE,true)
          .build();
     dataset.prepare();
     return dataset;
}

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

Обучите модель

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

public final class Training extends AbstractTraining {
     . . .
     @Override
     protected void train(Arguments arguments) throws IOException {
 
          . . .
          try (Model model = Models.getModel(NUM_OF_OUTPUT, 
                                   NEW_HEIGHT, NEW_WIDTH)) {
               TrainingConfig config = setupTrainingConfig(loss);
               try (Trainer trainer = model.newTrainer(config)) {      
                    trainer.setMetrics(metrics);
                    trainer.setTrainingListener(this);
                    Shape inputShape = new Shape(1, 3, NEW_HEIGHT, 
                                                 NEW_WIDTH);
          
                    // initialize trainer with proper input shape
                    trainer.initialize(inputShape);
                    //find the patterns in data
                    fit(trainer, trainingDataset, 
                        validateDataset, "build/logs/training");
                    //set model properties
                    model.setProperty("Epoch", 
                                      String.valueOf(EPOCHS));
                    model.setProperty("Accuracy", 
                                      String.format("%.2f",         
                                          getValidationAccuracy()));
                    //save the model after done training for 
                    //inference later model saved 
                    //as shoeclassifier-0000.params
                    model.save(Paths.get(modelParamsPath), 
                               modelParamsName);
               }
          }
}

Обучение начинается с подачи обучающих данных в качестве входных данных в Блок. В терминах DJL Block - это составной блок, который формирует нейронную сеть. Вы можете комбинировать блоки (как блоки Lego), чтобы образовать сложную сеть. В конце процесса обучения Block представляет полностью обученную модель. Первый шаг - получить экземпляр модели, вызвав Models.getModel(NUM_OF_OUTPUT, NEW_HEIGHT, NEW_WIDTH). Метод getModel() создает пустую модель, строит нейронную сеть и устанавливает нейронную сеть в модель.

/*
Use a neural network (ResNet-50) to train the model
ResNet-50 is a deep residual network with 50 layers; good for image classification
*/
public class Models {
     public static ai.djl.Model getModel(int numOfOutput, 
                                         int height, int width) 
     {
         //create new instance of an empty model
         ai.djl.Model model = ai.djl.Model.newInstance();
         //Block is a composable unit that forms a neural network;
         //combine them like Lego blocks to form a complex network
         Block resNet50 = new ResNetV1.Builder()
              .setImageShape(new Shape(3, height, width))
              .setNumLayers(50)
              .setOutSize(numOfOutput)
              .build();
         //set the neural network to the model         
         model.setBlock(resNet50);
         return model;
     }
}

Следующим шагом является установка и настройка Trainer путем вызова метода model.newTrainer(config). Объект конфигурации был инициализирован путем вызова метода setupTrainingConfig(loss), который устанавливает конфигурацию обучения (или гиперпараметры) для определения того, как обучается сеть.

private static TrainingConfig setupTrainingConfig(Loss loss) {
     // epoch number to change learning rate
     int[] epoch = {3, 5, 8};
     int[] steps = Arrays
                       .stream(epoch)
                       .map(k -> k * 60000 /  BATCH_SIZE).toArray();
     //initialize neural network weights using Xavier initializer
     Initializer initializer = new XavierInitializer(
         XavierInitializer.RandomType.UNIFORM,
         XavierInitializer.FactorType.AVG, 2);
     //set the learning rate
     //adjusts weights of network based on loss
     MultiFactorTracker learningRateTracker = LearningRateTracker
          .multiFactorTracker()
          .setSteps(steps)
          .optBaseLearningRate(0.01f)
          .optFactor(0.1f)
          .optWarmUpBeginLearningRate(1e-3f)
          .optWarmUpSteps(500)
          .build();
     //set optimization technique
     //minimizes loss to produce better and faster results
     //Stochastic gradient descent
     Optimizer optimizer = Optimizer
          .sgd()
          .setRescaleGrad(1.0f / BATCH_SIZE)
          .setLearningRateTracker(learningRateTracker)
          .optMomentum(0.9f)
          .optWeightDecays(0.001f)
          .optClipGrad(1f)
          .build();
     return new DefaultTrainingConfig(initializer, loss)
          .setOptimizer(optimizer)
          .addTrainingMetric(new Accuracy())
          .setBatchSize(BATCH_SIZE);
}

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

  • newHeight и newWidth - форма изображения.
  • batchSize - размер партии, используемой для обучения; выберите подходящий размер в зависимости от вашей модели.
  • numOfOutput - количество этикеток; Есть 4 ярлыка для классификации обуви.
  • loss - функции потерь сравнивают предсказания модели с истинными метками, измеряющими, насколько хороша (или плоха) модель.
  • Initializer - определяет метод инициализации; в данном случае инициализация Ксавьера.
  • MultiFactorTracker - настраивает параметры скорости обучения.
  • Optimizer: метод оптимизации для минимизации значения функции потерь; в данном случае - стохастический градиентный спуск (SGD).

Следующим шагом является установка Metrics, обучающего слушателя, и инициализация Trainer с правильной формой ввода. Metrics собирать и составлять отчеты о ключевых показателях эффективности (KPI) во время обучения, которые можно использовать для анализа и мониторинга эффективности и стабильности тренировки. Затем я начинаю процесс обучения с вызова fit(trainer, trainingDataset, validateDataset, “build/logs/training”) method, который выполняет итерацию по обучающим данным и сохраняет шаблоны, найденные в модели.

public void fit(Trainer trainer, Dataset trainingDataset, Dataset validateDataset,String outputDir) throws IOException {
     // find patterns in data
     for (int epoch = 0; epoch < EPOCHS; epoch++) 
     {
         for (Batch batch : trainer.iterateDataset(trainingDataset)) 
         {
              trainer.trainBatch(batch);
              trainer.step();
              batch.close();
         }
 
         //validate patterns found
         if (validateDataset != null) {
           for (Batch batch:             
                trainer.iterateDataset(validateDataset)){
                     trainer.validateBatch(batch);
                     batch.close();
           }
         }
         //reset training and validation metric at end of epoch
         trainer.resetTrainingMetrics();
         //save model at end of each epoch
         if (outputDir != null) {
              Model model = trainer.getModel();
              model.setProperty("Epoch", String.valueOf(epoch));
              model.save(Paths.get(outputDir), "resnetv1");
         }
      }
}

В конце обучения хорошо работающий артефакт проверенной модели сохраняется локально вместе со своими свойствами с помощью model.save(Paths.get(modelParamsPath), modelParamsName)method. Метрики, полученные во время тренировочного процесса, показаны ниже.

Выполнить вывод

Теперь, когда у меня есть модель, я могу использовать ее для вывода (или прогнозирования) новых данных, для которых мне неизвестна классификация (или цель). После установки необходимых путей к модели и классифицируемому изображению я получаю пустой экземпляр модели с помощью метода Models.getModel(NUM_OF_OUTPUT, NEW_HEIGHT, NEW_WIDTH) и инициализирую его с помощью метода model.load(Paths.get(modelParamsPath), modelParamsName). Это загружает модель, которую я обучил на предыдущем шаге. Затем я инициализирую Predictor с указанным Translator, используя model.newPredictor(translator)method. Вы заметите, что я передаю Translator Predictor. С точки зрения DJL, Translator обеспечивает функции предварительной и постобработки модели. Например, в моделях CV изображения необходимо преобразовать в оттенки серого; Translator может сделать это за вас. Predictor позволяет мне выполнять логический вывод для загруженного Model с помощью метода predictor.predict(img), передавая изображение для классификации. Я делаю единичный прогноз, но DJL также поддерживает пакетные прогнозы. Вывод хранится в predictResult, который содержит оценку вероятности для каждой метки. Модель автоматически закрывается после завершения вывода, что делает память DJL эффективной.

private Classifications predict() throws IOException, ModelException, TranslateException  {
     
     //the location to the model saved during training
     String modelParamsPath = "build/logs";
     //the name of the model set during training
     String modelParamsName = "shoeclassifier";
     //the path of image to classify
     String imageFilePath = "src/test/resources/slippers.jpg";
     //Load the image file from the path
     BufferedImage img =       
          BufferedImageUtils.fromFile(Paths.get(imageFilePath));
     //holds the probability score per label
     Classifications predictResult;
     try (Model model = Models.getModel(NUM_OF_OUTPUT, NEW_HEIGHT, NEW_WIDTH)) {
          //load the model
          model.load(Paths.get(modelParamsPath), modelParamsName);
          //define a translator for pre and post processing
          Translator<BufferedImage, Classifications> translator =       
                new MyTranslator();
          //run the inference using a Predictor
          try (Predictor<BufferedImage, Classifications> predictor =  
                 model.newPredictor(translator)) {
                      predictResult = predictor.predict(img);
          }
       }
     return predictResult;
}

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

Выводы и следующие шаги

Я занимаюсь разработкой приложений на основе Java с конца 90-х и начал свое путешествие по машинному обучению в 2017 году. Мой путь был бы намного проще, если бы DJL существовал тогда. Я настоятельно рекомендую Java-разработчикам, которые хотят перейти на машинное обучение, попробовать DJL. В моем примере я разработал модель классификации обуви с нуля; однако DJL также позволяет разработчикам развертывать предварительно обученные модели с минимальными усилиями. DJL также поставляется с популярными наборами данных из коробки, что позволяет разработчикам мгновенно приступить к работе с машинным обучением. Прежде чем начать работу с DJL, я бы порекомендовал вам иметь четкое представление о жизненном цикле машинного обучения и знать общие термины машинного обучения. Как только у вас будет базовый уровень понимания ML, вы сможете быстро освоить API DJL.

Amazon имеет открытый исходный код DJL, где более подробную информацию об инструментарии можно найти на веб-сайте DJL и на странице Спецификация API библиотеки Java. Код модели классификации обуви можно найти на GitLab. Удачи в вашем пути к машинному обучению. Если у вас возникнут вопросы, свяжитесь со мной.