Repository: deepjavalibrary/djl (java)

Issue:
TimeSeries API PyTorch Engine support
The documentation states (https://djl.ai/extensions/timeseries/docs/forecast_with_M5_data.html) that PyTorch also supports the TimeSeries API, but when trying to train a model with the PyTorch engine, an error occurs:
Exception in thread "main" java.lang.UnsupportedOperationException: Not implemented yet.
	at ai.djl.pytorch.engine.PtNDArray.gammaln(PtNDArray.java:845)
	at ai.djl.timeseries.distribution.NegativeBinomial.logProb(NegativeBinomial.java:47)
	at ai.djl.timeseries.distribution.DistributionLoss.evaluate(DistributionLoss.java:54)
	at ai.djl.training.EasyTrain.trainSplit(EasyTrain.java:124)
	at ai.djl.training.EasyTrain.trainBatch(EasyTrain.java:110)
	at ai.djl.training.EasyTrain.fit(EasyTrain.java:58)
	at com.crypto.analysis.main.ex.NNew.train(NNew.java:134)
	at com.crypto.analysis.main.ex.NNew.main(NNew.java:63)

`public static void train(Path outputDir) {
        try (Model model = Model.newInstance("DeepAR");
             NDManager manager = NDManager.newBaseManager()) {

            DistributionOutput distributionOutput = new NegativeBinomialOutput();
            DefaultTrainingConfig config = setupTrainingConfig(distributionOutput);
            DeepARNetwork trainingNetwork = getDeepARModel(freq, predictionLength, distributionOutput, true);

            model.setBlock(trainingNetwork);

            List<TimeSeriesTransform> trainingTransformation = trainingNetwork.createTrainingTransformation(manager);
            int contextLength = trainingNetwork.getContextLength();

            NDataset trainSet = getDataset(trainingTransformation, contextLength);

            try (Trainer trainer = model.newTrainer(config)) {
                trainer.setMetrics(new Metrics());

                System.out.println("+++++" + trainSet.availableSize());
                int historyLength = trainingNetwork.getHistoryLength();
                Shape[] inputShapes = new Shape[4];
                // (N, num_cardinality)
                inputShapes[0] = new Shape(1, 1);
                // (N, num_real) if use_feat_stat_real else (N, 1)
                inputShapes[1] = new Shape(1, 1);

                inputShapes[2] =
                        new Shape(
                                1,
                                historyLength,
                                TimeFeature.timeFeaturesFromFreqStr("M").size() + 1);
                inputShapes[3] = new Shape(1, historyLength);
                trainer.initialize(inputShapes);

                EasyTrain.fit(trainer, 1500, trainSet, null);
                logger.info(String.valueOf(trainer.getTrainingResult()));

                model.setProperty("Epochs", "1500");
                model.save(outputDir, "DeepAR");
            }

        } catch (TranslateException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static DeepARNetwork getDeepARModel(String freq, int predictionLength,
                                                DistributionOutput distributionOutput, boolean training) {

        List<Integer> cardinality = new ArrayList<>();
        cardinality.add(allSize-testSize);

        DeepARNetwork.Builder builder =
                DeepARNetwork.builder()
                        .setFreq(freq)
                        .setPredictionLength(predictionLength)
                        .setCardinality(cardinality)
                        .optDistrOutput(distributionOutput)
                        .optUseFeatDynamicReal(false)
                        .optUseFeatStaticCat(false)
                        .optUseFeatStaticReal(false);
        return training ? builder.buildTrainingNetwork() : builder.buildPredictionNetwork();
    }

    private static DefaultTrainingConfig setupTrainingConfig(DistributionOutput distributionOutput) {
        return new DefaultTrainingConfig(new DistributionLoss("Loss", distributionOutput))
                .addEvaluator(new Rmsse(distributionOutput))
                .optInitializer(new XavierInitializer(), Parameter.Type.WEIGHT)
                .addTrainingListeners(TrainingListener.Defaults.logging());
    }`

Pull request description:
[pytorch] Implements gammaln operator for PyTorch
Fixes #3259

## Description ##

Brief description of what this PR is about

- If this change is a backward incompatible change, why must this change be made?
- Interesting edge cases to note here


Patch:
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
index 8b37ccf66..f33c2b247 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
@@ -842,7 +842,7 @@ public class PtNDArray extends NativeResource<Long> implements NDArray {
     /** {@inheritDoc} */
     @Override
     public NDArray gammaln() {
-        throw new UnsupportedOperationException("Not implemented yet.");
+        return JniUtils.gammaln(this);
     }
 
     /** {@inheritDoc} */
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
index aa35100db..53e4c76fc 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
@@ -1112,6 +1112,11 @@ public final class JniUtils {
                 ndArray.getManager(), PyTorchLibrary.LIB.torchExp(ndArray.getHandle()));
     }
 
+    public static PtNDArray gammaln(PtNDArray ndArray) {
+        return new PtNDArray(
+                ndArray.getManager(), PyTorchLibrary.LIB.torchLgamma(ndArray.getHandle()));
+    }
+
     public static PtNDArray log(PtNDArray ndArray) {
         return new PtNDArray(
                 ndArray.getManager(), PyTorchLibrary.LIB.torchLog(ndArray.getHandle()));
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
index 9ba75c9a6..3c03cd8cc 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
@@ -324,6 +324,8 @@ final class PyTorchLibrary {
 
     native long torchExp(long handle);
 
+    native long torchLgamma(long handle);
+
     native long torchLog(long handle);
 
     native long torchLog10(long handle);
diff --git a/engines/pytorch/pytorch-native/build.gradle.kts b/engines/pytorch/pytorch-native/build.gradle.kts
index dc0c70ff9..16ff119f8 100644
--- a/engines/pytorch/pytorch-native/build.gradle.kts
+++ b/engines/pytorch/pytorch-native/build.gradle.kts
@@ -254,8 +254,8 @@ tasks {
                 )
                 for (item in uploadDirs)
                     fileTree(item).files.map { it.name }.forEach {
-                        val out = item.relativeTo(File("${BINARY_ROOT}/")).absolutePath
-                        appendLine(out + URLEncoder.encode(it, "UTF-8"))
+                        val out = item.toRelativeString(File("${BINARY_ROOT}/"))
+                        appendLine(out + "/" + URLEncoder.encode(it, "UTF-8"))
                     }
             }
             exec {
@@ -263,7 +263,6 @@ tasks {
             }
         }
     }
-
 }
 
 java {
diff --git a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_torch_pointwise.cc b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_torch_pointwise.cc
index 251966570..2ebb625cb 100644
--- a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_torch_pointwise.cc
+++ b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_torch_pointwise.cc
@@ -283,6 +283,14 @@ JNIEXPORT jlong JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_torchExp(JNIEnv*
   API_END_RETURN()
 }
 
+JNIEXPORT jlong JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_torchLgamma(JNIEnv* env, jobject jthis, jlong jhandle) {
+  API_BEGIN()
+  const auto* tensor_ptr = reinterpret_cast<torch::Tensor*>(jhandle);
+  const auto* result_ptr = new torch::Tensor(tensor_ptr->lgamma());
+  return reinterpret_cast<uintptr_t>(result_ptr);
+  API_END_RETURN()
+}
+
 JNIEXPORT jlong JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_torchLog(JNIEnv* env, jobject jthis, jlong jhandle) {
   API_BEGIN()
   const auto* tensor_ptr = reinterpret_cast<torch::Tensor*>(jhandle);
diff --git a/engines/tensorflow/tensorflow-native/build.gradle.kts b/engines/tensorflow/tensorflow-native/build.gradle.kts
index 0d25d4f27..0a735ecd5 100644
--- a/engines/tensorflow/tensorflow-native/build.gradle.kts
+++ b/engines/tensorflow/tensorflow-native/build.gradle.kts
@@ -72,8 +72,8 @@ tasks {
                 )
                 for (item in uploadDirs)
                     fileTree(item).files.map { it.name }.forEach {
-                        val out = item.relativeTo(buildDirectory / "native/").absolutePath
-                        appendLine(out + it)
+                        val out = item.toRelativeString(buildDirectory / "native/")
+                        appendLine("$out/$it")
                     }
             }
             delete(
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/distribution/DistributionLoss.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/distribution/DistributionLoss.java
index 30251e717..7a131f2fb 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/distribution/DistributionLoss.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/distribution/DistributionLoss.java
@@ -57,7 +57,7 @@ public class DistributionLoss extends Loss {
             NDArray lossWeights = predictions.get("loss_weights");
             NDArray weightedValue =
                     NDArrays.where(lossWeights.neq(0), loss.mul(lossWeights), loss.zerosLike());
-            NDArray sumWeights = lossWeights.sum().maximum(1.);
+            NDArray sumWeights = lossWeights.sum().maximum(1f);
             loss = weightedValue.sum().div(sumWeights);
         }
         return loss;


Test patch:
diff --git a/engines/pytorch/pytorch-engine/src/test/java/ai/djl/pytorch/integration/ALibUtilsTest.java b/engines/pytorch/pytorch-engine/src/test/java/ai/djl/pytorch/integration/ALibUtilsTest.java
index 89f3ab19d..e176f07f7 100644
--- a/engines/pytorch/pytorch-engine/src/test/java/ai/djl/pytorch/integration/ALibUtilsTest.java
+++ b/engines/pytorch/pytorch-engine/src/test/java/ai/djl/pytorch/integration/ALibUtilsTest.java
@@ -27,7 +27,7 @@ public class ALibUtilsTest {
         TestRequirements.notMacX86();
 
         System.setProperty("ai.djl.pytorch.native_helper", ALibUtilsTest.class.getName());
-        System.setProperty("STDCXX_LIBRARY_PATH", "/usr/lib/non-exists");
+        System.setProperty("LIBSTDCXX_LIBRARY_PATH", "/usr/lib/non-exists");
     }
 
     @AfterClass
diff --git a/examples/src/test/java/ai/djl/examples/training/TrainTimeSeriesTest.java b/examples/src/test/java/ai/djl/examples/training/TrainTimeSeriesTest.java
index 0fa4cd9bd..19cfdc3c1 100644
--- a/examples/src/test/java/ai/djl/examples/training/TrainTimeSeriesTest.java
+++ b/examples/src/test/java/ai/djl/examples/training/TrainTimeSeriesTest.java
@@ -13,7 +13,7 @@
 
 package ai.djl.examples.training;
 
-import ai.djl.testing.TestRequirements;
+import ai.djl.engine.Engine;
 import ai.djl.training.TrainingResult;
 import ai.djl.translate.TranslateException;
 
@@ -26,10 +26,15 @@ public class TrainTimeSeriesTest {
 
     @Test
     public void testTrainTimeSeries() throws TranslateException, IOException {
-        TestRequirements.linux();
+        String[] args;
+        Engine engine = Engine.getEngine("PyTorch");
+        if (engine.getGpuCount() > 0) {
+            // TODO: PyTorch -- cuDNN error: CUDNN_STATUS_VERSION_MISMATCH
+            args = new String[] {"-g", "1", "-e", "5", "-b", "32", "--engine", "MXNet"};
+        } else {
+            args = new String[] {"-g", "1", "-e", "5", "-b", "32"};
+        }
 
-        // TODO: PyTorch -- PtNDArray.gammaln not implemented
-        String[] args = new String[] {"-g", "1", "-e", "5", "-b", "32", "--engine", "MXNet"};
         TrainingResult result = TrainTimeSeries.runExample(args);
         Assert.assertNotNull(result);
         float loss = result.getTrainLoss();