Статьи

Java-R-интеграция с JRI для прогнозирования по требованию

В этой статье представлен краткий обзор того, как использовать JRI для использования  R  из приложения Java. В частности, это даст вам понимание того, как использовать эту технологию для прогнозирования по требованию на основе моделей R.

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

Что такое JRI?

JRI — это интерфейс Java / R, обеспечивающий функциональность Java API to R. Спецификация JavaDoc этого интерфейса (org.rosuda.JRI.Rengine) может быть найдена  здесьДомашняя страница проекта  описывает , как изначально настроить ИСР в различных средах.

Типичные случаи использования для прогнозов по требованию через JRI

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

Hello R World с Java

После установки R и пакета JRI любое приложение Java сможет создать экземпляр org.rosuda.JRI.Rengine после добавления соответствующих JAR-файлов в путь сборки. Следующий упрощенный пример демонстрирует, как мы можем использовать интерфейс R.

import org.rosuda.JRI.Rengine;
import org.rosuda.JRI.REXP;




public class HelloRWorld {
Rengine rengine; // initialized in constructor or autowired




public void helloRWorld() {
rengine.eval(String.format("greeting <- '%s'", "Hello R World"));
REXP result = rengine.eval("greeting");
System.out.println("Greeting from R: "+result.asString());
}
}

Вызов Rengine.eval (String) соответствует вводу команд в консоль R и, следовательно, обеспечивает доступ к любым необходимым функциям. Обратите внимание, что даже в этом тривиальном примере два отдельных вызова имеют общий контекст, который поддерживается на протяжении всего жизненного цикла экземпляра Rengine. Объекты org.rosuda.JRI.REXP инкапсулируют любой вывод из R пользователю. В зависимости от оцененной команды, другие методы, кроме REXP.asString (), могут быть пригодны для извлечения ее результата (см.  JavaDoc ).

Запуск .R скриптов из Java

Несмотря на то, что было бы возможно реализовать большие R-сценарии в Java, передавая каждый оператор в Rengine.eval (String), это гораздо менее удобно, чем написание или даже повторное использование традиционных .R-сценариев. Итак, давайте посмотрим, как мы можем достичь того же результата с помощью немного другого решения.

Структура проекта:

/src/main
/java
com.comsysto.jriexample.HelloRWorld2.java
/resources
helloWorld.R

helloWorld.R:

greeting <- 'Hello R World'

HelloRWorld2.java:

import org.rosuda.JRI.Rengine;
import org.rosuda.JRI.REXP;
import org.springframework.core.io.ClassPathResource;




public class HelloRWorld2 {
Rengine rengine; // initialized in constructor or autowired




public void helloRWorld() {
ClassPathResource rScript = new ClassPathResource("helloWorld.R");
rengine.eval(String.format("source('%s')",
rScript.getFile().getAbsolutePath()));
REXP result = rengine.eval("greeting");
System.out.println("Greeting from R: "+result.asString());
}
}

Любой скрипт .R может быть выполнен так, и все переменные, которые он добавляет в контекст, будут доступны через JRI. Однако этот код  не  работает, если приложение Java упаковано в архив JAR или WAR, поскольку сценарий .R не будет иметь допустимого абсолютного пути. В этом случае копирование сценария в обычную папку (например, java.io.tmpdir) во время выполнения и передача временного файла в R является возможным обходным путем.

Обучение R-моделей с помощью данных приложения Java

Теперь, когда мы знаем, как выполнять сценарии .R с использованием JRI, мы можем интегрировать модели прогнозирования на основе R в приложение Java. Единственный оставшийся вопрос: как R может получить доступ к необходимым данным для обучения модели? Самый простой способ — использовать следующий файловый подход. Мы построим модель линейной регрессии, которая предсказывает y по x1 и x2.

  1. Извлеките подходящие данные из уровня персистентности Java и сохраните их во временном файле .csv.
    import au.com.bytecode.opencsv.CSVWriter;
    
    
    
    
    private void writeTrainingDataToFile(File file) {
    CSVWriter writer = new CSVWriter(new FileWriter(file), ";");
    writer.writeNext(new String[] {"x1","x2","y"});
    for (Instance i : instances) {
    writer.writeNext(new String[] {i.x1, i.x2, i.y};
    }
    writer.close();
    }
  2. Передайте местоположение этого файла в R, используя JRI.
    public void trainRModel() {
    File trainingData = new File(TRAINING_DATA_PATH);
    writeTrainingDataToFile(trainingData);
    rengine.eval(String.format("trainingDataPath <- '%s'",
    trainingData.getAbsolutePath()));
    // trigger execution of .R script here
    }
  3. Выполните скрипт .R для построения модели. Сценарий должен быть синтаксически совместим с извлеченным файлом .csv.
    # trainingDataPath injected from Java
    data <- read.csv(trainingDataPath, header=TRUE, sep=";");
    # use linear regression model as a trivial example
    model <- lm(y ~ x1+x2, data);

После выполнения этого сценария результирующая модель будет доступна для любых будущих вызовов до тех пор, пока все приложение или экземпляр Rengine не будут повторно инициализированы.

Прогнозы по требованию с использованием модели R

Имея знания, которые у нас уже есть, предсказать новый экземпляр (x1, x2) с неизвестным y теперь довольно просто:

public double predictInstance(int x1, int x2) {
rengine.eval(String.format("greeting <- data.frame(x1=%s,x2=%s)",
x1, x2));
REXP result = rengine.eval("predict(model, newData)");
return result.asDouble();
}

Если у вас есть какие-либо отзывы, пожалуйста, пишите на  Christian.Kroemer@comsysto.com !