Catboost предлагает динамическая библиотека C, который теоретически можно использовать на любом языке программирования.
Пытаюсь вызвать через Java с помощью JNA.
У меня проблема с функцией CalcModelPrediction, определенной в заголовочный файл следующим образом:
EXPORT bool CalcModelPrediction(
ModelCalcerHandle* calcer,
size_t docCount,
const float** floatFeatures, size_t floatFeaturesSize,
const char*** catFeatures, size_t catFeaturesSize,
double* result, size_t resultSize);
В Java я определил функцию интерфейса следующим образом:
public interface CatboostModel extends Library {
public Pointer ModelCalcerCreate();
public String GetErrorString();
public boolean LoadFullModelFromFile(Pointer calcer, String filename);
public boolean CalcModelPrediction(Pointer calcer, int docCount,
PointerByReference floatFeatures, int floatFeaturesSize,
PointerByReference catFeatures, int catFeaturesSize,
Pointer result, int resultSize);
public int GetFloatFeaturesCount(Pointer calcer);
public int GetCatFeaturesCount(Pointer calcer);
}
а потом я называю это так:
CatboostModel catboost;
Pointer modelHandle;
catboost = Native.loadLibrary("catboostmodel", CatboostModel.class);
modelHandle = catboost.ModelCalcerCreate();
if (!catboost.LoadFullModelFromFile(modelHandle, "catboost_test.model"))
{
throw new RuntimeException("Cannot load Catboost model.");
}
final PointerByReference ppFloatFeatures = new PointerByReference();
final PointerByReference ppCatFeatures = new PointerByReference();
final Pointer pResult = new Memory(Native.getNativeSize(Double.TYPE));
float[] floatFeatures = {0.5f, 0.8f, 0.3f, 0.3f, 0.1f, 0.5f, 0.4f, 0.8f, 0.3f, 0.3f} ;
String[] catFeatures = {"1", "2", "3", "4"};
int catFeaturesLength = 0;
for (String s : catFeatures)
{
catFeaturesLength += s.length() + 1;
}
try
{
final Pointer pFloatFeatures = new Memory(floatFeatures.length * Native.getNativeSize(Float.TYPE));
for (int dloop=0; dloop<floatFeatures.length; dloop++) {
pFloatFeatures.setFloat(dloop * Native.getNativeSize(Float.TYPE), floatFeatures[dloop]);
}
ppFloatFeatures.setValue(pFloatFeatures);
final Pointer pCatFeatures = new Memory(catFeaturesLength * Native.getNativeSize(Character.TYPE));
long offset = 0;
for (final String s : catFeatures) {
pCatFeatures.setString(offset, s);
pCatFeatures.setMemory(offset + s.length(), 1, (byte)(0));
offset += s.length() + 1;
}
ppCatFeatures.setValue(pCatFeatures);
}
catch (Exception e)
{
throw new RuntimeException("Couldn't initialize parameters for catboost");
}
try
{
if (!catboost.CalcModelPrediction(
modelHandle,
1,
ppFloatFeatures, 10,
ppCatFeatures, 4,
pResult, 1
))
{
throw new RuntimeException("No prediction made: " + catboost.GetErrorString());
}
else
{
double[] result = pResult.getDoubleArray(0, 1);
log.info("Catboost prediction: " + String.valueOf(result[0]));
Assert.assertFalse("ERROR: Result empty", result.length == 0);
}
}
catch (Exception e)
{
throw new RuntimeException("Prediction failed: " + e);
}
Я пытался передать Pointer, PointerByReference и Pointer[] функции CalcModelPrediction вместо float **floatFeatures и char ***catFeatures, но ничего не помогло. Я всегда получаю ошибку сегментации, предположительно, когда функция CalcModelPrediction пытается получить элементы floatFeatures и catFeatures, вызывая floatFeatures[0][0] и catFeatures[0][0].
Итак, вопрос в том, как правильно передать многомерный массив из Java через JNA в C, где он может рассматриваться как указатель на указатель на значение?
Интересно то, что функция CalcModelPredictionFlat, которая принимает только float **floatFeatures, а затем просто вызывает *floatFeatures, отлично работает при передаче PointerByReference.
ОБНОВЛЕНИЕ - 5.5.2018
Часть 1
После попытки отладить segfault, слегка изменив исходные файлы Catboost .cpp и .h и перекомпилировав библиотеку libcatboost.so, я обнаружил, что segfault возник из-за того, что я сопоставил size_t на C с int на Java. После исправления моя функция интерфейса в Java выглядит так:
public interface CatboostModel extends Library {
public boolean LoadFullModelFromFile(Pointer calcer, String filename);
public boolean CalcModelPrediction(Pointer calcer, size_t docCount,
Pointer[] floatFeatures, size_t floatFeaturesSize,
String[] catFeatures, size_t catFeaturesSize,
Pointer result, size_t resultSize);
}
Где класс size_t определяется следующим образом:
public static class size_t extends IntegerType {
public size_t() { this(0); }
public size_t(long value) { super(Native.SIZE_T_SIZE, value); }
}
Часть 2
Изучая код Catboost, я заметил, что доступ к **floatFeatures осуществляется по строкам, например floatFeatures[i], а к ***catFeatures - по строкам и столбцам, например catFetures[i][catFeatureIdx].
После замены floatFeatures в Java на массив Pointer мой код начал работать с моделью, обученной без категориальных функций, т.е.длина catFeatures равна нулю.
Однако этот трюк не работал с catFeatures, доступ к которым осуществляется с помощью оператора двойного индекса [i][catFeatureidx]. Итак, на данный момент я изменил исходный код Catboost, чтобы он принимал char **catFeatures - массив строк. В функции интерфейса Java я установил String[] catFeatures. Теперь я могу делать прогнозы для одного элемента за раз, что не идеально.
Спасибо, @DebosmitRay, к сожалению, я пробовал PointerByReference и обновил приведенный выше код, чтобы отразить это. Это по-прежнему вызывает ошибку сегментации. Не могли бы вы проверить, правильно ли я объявляю и присваиваю значение ppFloatFeatures?
Как на самом деле выглядят данные за указателем? float** может быть способом вернуть float*, или это может быть массив указателей, или даже массив массивов с плавающей запятой. PointerByReference действительно покрывает только первый случай.
Можете ли вы предоставить собственный пример инициализации требуемых данных?
@techomage Я написал программу на C, в которой используется библиотека libcatboostmodel.so. В нем все работает нормально, если я объявляю входные параметры следующим образом: const float floatFeatures[] = {0.4f, 0.8f, 0.3f}; float *pFloat; float **ppFloat; pFLoat = &FloatFeatures; ppFloat = &pFloat; Затем передача ppFloat в CalcModelPrediction вместо float **floatFeatures работает нормально, когда код C повторяется по floatFeatures с помощью оператора двойного индекса [0][0].
@technomage Я думаю, мне нужно передать массив массивов с плавающей запятой и массив массивов строк. Чтобы быть точным, функция CalcModelPrediction обобщила floatFeatures и catFeatures до 2-мерных массивов значений float и значений char*.
Поэтому, если вы выполняете итерацию по 2-мерному массиву, вы можете рассматривать его как одно измерение размера RxC и выполнять свою собственную индексацию, или, если ваши строки имеют неравные размеры, использовать массив указателей для доступа к адресам каждого индивидуальный ряд.




Мне удалось заставить все это работать с оригинальным кодом Catboost и libcatboost.so.
Функция интерфейса Java определяется следующим образом. Обратите внимание, что для имитации 2D-массива (или указателя на указатель) значений и строк с плавающей запятой я использую тип Pointer[].
public interface CatboostModel extends Library {
public boolean LoadFullModelFromFile(Pointer calcer, String filename);
public boolean CalcModelPrediction(Pointer calcer, size_t docCount,
Pointer[] floatFeatures, size_t floatFeaturesSize,
Pointer[] catFeatures, size_t catFeaturesSize,
Pointer result, size_t resultSize);
}
После этого я заполняю параметры floatFeatures и catFeatures следующим образом (здесь некоторые фиктивные данные). Обратите внимание, что для строк я использую JNA StringArray.
float[] floatFeatures = {0.4f, 0.8f, 0.3f, 0.3f, 0.1f, 0.5f, 0.4f, 0.8f, 0.3f, 0.3f} ;
String[] catFeatures = {"1", "2", "3", "4"};
final Pointer pFloatFeatures = new Memory(floatFeatures.length * Native.getNativeSize(Float.TYPE));
final Pointer[] ppFloatFeatures = new Pointer[2];
for (int dloop=0; dloop<10; dloop++) {
pFloatFeatures.setFloat(dloop * Native.getNativeSize(Float.TYPE), floatFeatures[dloop]);
}
ppFloatFeatures[0] = pFloatFeatures;
ppFloatFeatures[1] = pFloatFeatures;
final Pointer[] ppCatFeatures = new Pointer[catFeatures.length];
final Pointer pCatFeatures = new StringArray(catFeatures);
ppCatFeatures[0] = pCatFeatures;
ppCatFeatures[1] = pCatFeatures;
Наконец, я передаю Catboost следующие параметры:
if (!catboost.CalcModelPrediction(
modelHandle,
new size_t(2L),
ppFloatFeatures, new size_t((long)floatFeatures.length),
ppCatFeatures, new size_t((long)catFeatures.length),
pResult, new size_t(2L)
))
{
throw new RuntimeException("No prediction made: " + catboost.GetErrorString());
}
Чтобы получить прогнозы, мы можем:
double[] result = pResult.getDoubleArray(0, 2);
небольшое примечание - вы хотите заменить
Pointer[]наPointerByReference. Этот адрес памяти, скажем,xбудет 0-м элементом,x+8будет 1-м элементом и так далее.