Repository: deepjavalibrary/djl
Pull request #1649 (2022-05-13 19:18:30)

Add support of `take` on MXNet engine
This pr follows https://github.com/deepjavalibrary/djl/pull/1627

[`take` function in MXNet ](https://mxnet.apache.org/versions/1.6.0/api/r/docs/api/mx.symbol.take.html?highlight=take) is defined differently from numpy or pytorch. It is implemented by `flatten` and `take` in MXNet.

codecov-commenter: # [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) Report
> Merging [#1649](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) (c0e1102) into [master](https://codecov.io/gh/deepjavalibrary/djl/commit/bb5073f7ffc33f1883621fc1783dbb01e70b418f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) (bb5073f) will **decrease** coverage by `1.24%`.
> The diff coverage is `62.16%`.

```diff
@@             Coverage Diff              @@
##             master    #1649      +/-   ##
============================================
- Coverage     72.08%   70.84%   -1.25%     
- Complexity     5126     5438     +312     
============================================
  Files           473      507      +34     
  Lines         21970    23791    +1821     
  Branches       2351     2589     +238     
============================================
+ Hits          15838    16855    +1017     
- Misses         4925     5643     +718     
- Partials       1207     1293      +86     
```


| [Impacted Files](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | Coverage Δ | |
|---|---|---|
| [api/src/main/java/ai/djl/modality/cv/Image.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L0ltYWdlLmphdmE=) | `69.23% <ø> (-4.11%)` | :arrow_down: |
| [...rc/main/java/ai/djl/modality/cv/output/Joints.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L291dHB1dC9Kb2ludHMuamF2YQ==) | `71.42% <ø> (ø)` | |
| [.../main/java/ai/djl/modality/cv/output/Landmark.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L291dHB1dC9MYW5kbWFyay5qYXZh) | `100.00% <ø> (ø)` | |
| [...main/java/ai/djl/modality/cv/output/Rectangle.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L291dHB1dC9SZWN0YW5nbGUuamF2YQ==) | `72.41% <ø> (ø)` | |
| [...i/djl/modality/cv/translator/BigGANTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvQmlnR0FOVHJhbnNsYXRvci5qYXZh) | `21.42% <ø> (-5.24%)` | :arrow_down: |
| [...odality/cv/translator/BigGANTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvQmlnR0FOVHJhbnNsYXRvckZhY3RvcnkuamF2YQ==) | `33.33% <0.00%> (+8.33%)` | :arrow_up: |
| [...nslator/InstanceSegmentationTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvSW5zdGFuY2VTZWdtZW50YXRpb25UcmFuc2xhdG9yRmFjdG9yeS5qYXZh) | `14.28% <0.00%> (-3.90%)` | :arrow_down: |
| [.../modality/cv/translator/YoloTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1RyYW5zbGF0b3JGYWN0b3J5LmphdmE=) | `8.33% <0.00%> (-1.67%)` | :arrow_down: |
| [...i/djl/modality/cv/translator/YoloV5Translator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvci5qYXZh) | `5.69% <0.00%> (ø)` | |
| [...odality/cv/translator/YoloV5TranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvckZhY3RvcnkuamF2YQ==) | `8.33% <0.00%> (-1.67%)` | :arrow_down: |
| ... and [247 more](https://codecov.io/gh/deepjavalibrary/djl/pull/1649/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Last update [c9d5412...c0e1102](https://codecov.io/gh/deepjavalibrary/djl/pull/1649?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).


Patch:
diff --git a/api/src/main/java/ai/djl/Application.java b/api/src/main/java/ai/djl/Application.java
index cb65a3c66..5b5dbe8bf 100644
--- a/api/src/main/java/ai/djl/Application.java
+++ b/api/src/main/java/ai/djl/Application.java
@@ -289,4 +289,11 @@ public class Application {
          */
         Application SOFTMAX_REGRESSION = new Application("tabular/softmax_regression");
     }
+
+    /** The common set of applications for audio data. */
+    public interface Audio {
+
+        /** Any audio application, including those in {@link Audio}. */
+        Application ANY = new Application("audio");
+    }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java b/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
index 19956f893..e9af175ce 100644
--- a/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
@@ -91,43 +91,28 @@ public class BufferedImageFactory extends ImageFactory {
         } else if (array.getDataType() != DataType.UINT8 && array.getDataType() != DataType.INT8) {
             throw new IllegalArgumentException("Datatype should be INT8 or UINT8");
         }
+        int height = (int) shape.get(0);
+        int width = (int) shape.get(1);
         if (NDImageUtils.isCHW(shape)) {
-            int height = (int) shape.get(1);
-            int width = (int) shape.get(2);
-            int imageArea = width * height;
-            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-            int[] raw = array.toUint8Array();
-            IntStream.range(0, imageArea)
-                    .forEach(
-                            ele -> {
-                                int x = ele % width;
-                                int y = ele / width;
-                                int red = raw[ele] & 0xFF;
-                                int green = raw[ele + imageArea] & 0xFF;
-                                int blue = raw[ele + imageArea * 2] & 0xFF;
-                                int rgb = (red << 16) | (green << 8) | blue;
-                                image.setRGB(x, y, rgb);
-                            });
-            return new BufferedImageWrapper(image);
-        } else {
-            int height = (int) shape.get(0);
-            int width = (int) shape.get(1);
-            int imageArea = width * height;
-            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-            int[] raw = array.toUint8Array();
-            IntStream.range(0, imageArea)
-                    .forEach(
-                            ele -> {
-                                int x = ele % width;
-                                int y = ele / width;
-                                int red = raw[ele * 3] & 0xFF;
-                                int green = raw[ele * 3 + 1] & 0xFF;
-                                int blue = raw[ele * 3 + 2] & 0xFF;
-                                int rgb = (red << 16) | (green << 8) | blue;
-                                image.setRGB(x, y, rgb);
-                            });
-            return new BufferedImageWrapper(image);
+            height = (int) shape.get(1);
+            width = (int) shape.get(2);
         }
+        int imageArea = width * height;
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        int[] raw = array.toUint8Array();
+        int finalWidth = width;
+        IntStream.range(0, imageArea)
+                .forEach(
+                        ele -> {
+                            int x = ele % finalWidth;
+                            int y = ele / finalWidth;
+                            int red = raw[ele] & 0xFF;
+                            int green = raw[ele + imageArea] & 0xFF;
+                            int blue = raw[ele + imageArea * 2] & 0xFF;
+                            int rgb = (red << 16) | (green << 8) | blue;
+                            image.setRGB(x, y, rgb);
+                        });
+        return new BufferedImageWrapper(image);
     }
 
     protected void save(BufferedImage image, OutputStream os, String type) throws IOException {
@@ -243,6 +228,13 @@ public class BufferedImageFactory extends ImageFactory {
             BufferedImageFactory.this.save(image, os, type);
         }
 
+        /** {@inheritDoc} */
+        @Override
+        public List<BoundingBox> findBoundingBoxes() {
+            // TODO: Add grayscale conversion and use BoundFinder to implement
+            throw new UnsupportedOperationException("Not supported for BufferedImage");
+        }
+
         /** {@inheritDoc} */
         @Override
         public void drawBoundingBoxes(DetectedObjects detections) {
diff --git a/api/src/main/java/ai/djl/modality/cv/Image.java b/api/src/main/java/ai/djl/modality/cv/Image.java
index dea29361f..3053d85dd 100644
--- a/api/src/main/java/ai/djl/modality/cv/Image.java
+++ b/api/src/main/java/ai/djl/modality/cv/Image.java
@@ -12,12 +12,14 @@
  */
 package ai.djl.modality.cv;
 
+import ai.djl.modality.cv.output.BoundingBox;
 import ai.djl.modality.cv.output.DetectedObjects;
 import ai.djl.modality.cv.output.Joints;
 import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDManager;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 /**
  * {@code Image} is a container of an image in DJL. The storage type of the image depends on the
@@ -92,6 +94,13 @@ public interface Image {
      */
     void save(OutputStream os, String type) throws IOException;
 
+    /**
+     * Find bounding boxes from a masked image with 0/1 or 0/255.
+     *
+     * @return the List of bounding boxes of the images
+     */
+    List<BoundingBox> findBoundingBoxes();
+
     /**
      * Draws the bounding boxes on the image.
      *
diff --git a/api/src/main/java/ai/djl/modality/cv/output/Rectangle.java b/api/src/main/java/ai/djl/modality/cv/output/Rectangle.java
index dea3a8c67..145621052 100644
--- a/api/src/main/java/ai/djl/modality/cv/output/Rectangle.java
+++ b/api/src/main/java/ai/djl/modality/cv/output/Rectangle.java
@@ -89,7 +89,7 @@ public class Rectangle implements BoundingBox {
         Rectangle rec = (Rectangle) box;
         // caculate intesection lrtb
         double left = Math.max(getX(), rec.getX());
-        double top = Math.min(getY(), rec.getY());
+        double top = Math.max(getY(), rec.getY());
         double right = Math.min(getX() + getWidth(), rec.getX() + rec.getWidth());
         double bottom = Math.min(getY() + getHeight(), rec.getY() + rec.getHeight());
         double intersection = (right - left) * (bottom - top);
diff --git a/api/src/main/java/ai/djl/ndarray/NDArray.java b/api/src/main/java/ai/djl/ndarray/NDArray.java
index c7c6f96aa..48c085d00 100644
--- a/api/src/main/java/ai/djl/ndarray/NDArray.java
+++ b/api/src/main/java/ai/djl/ndarray/NDArray.java
@@ -561,7 +561,8 @@ public interface NDArray extends NDResource, BytesSupplier {
     NDArray gather(NDArray index, int axis);
 
     /**
-     * Returns a partial {@code NDArray} pointed by the indexed array, according to linear indexing.
+     * Returns a partial {@code NDArray} pointed by the indexed array according to linear indexing,
+     * and the of output is of the same shape as index.
      *
      * @param index picks the elements of an NDArray to the same position as index
      * @return the partial {@code NDArray} of the same shape as index
@@ -4683,7 +4684,7 @@ public interface NDArray extends NDResource, BytesSupplier {
      *     href=https://d2l.djl.ai/chapter_linear-networks/softmax-regression.html#classification-problems>Classification-problems</a>
      */
     default NDArray oneHot(int depth, DataType dataType) {
-        return oneHot(depth, 0f, 1f, dataType);
+        return oneHot(depth, 1f, 0f, dataType);
     }
 
     /**
diff --git a/api/src/main/java/ai/djl/ndarray/index/NDArrayIndexer.java b/api/src/main/java/ai/djl/ndarray/index/NDArrayIndexer.java
index 31f400812..fd29e8f8b 100644
--- a/api/src/main/java/ai/djl/ndarray/index/NDArrayIndexer.java
+++ b/api/src/main/java/ai/djl/ndarray/index/NDArrayIndexer.java
@@ -59,7 +59,7 @@ public abstract class NDArrayIndexer {
         if (!indices.isEmpty() && indices.get(0) instanceof NDIndexBooleans) {
             if (indices.size() != 1) {
                 throw new IllegalArgumentException(
-                        "get() currently didn't support more that one boolean NDArray");
+                        "get() currently doesn't support more that one boolean NDArray");
             }
             return array.booleanMask(((NDIndexBooleans) indices.get(0)).getIndex());
         }
diff --git a/api/src/main/java/ai/djl/training/dataset/ArrayDataset.java b/api/src/main/java/ai/djl/training/dataset/ArrayDataset.java
index 997d70527..2122d06fa 100644
--- a/api/src/main/java/ai/djl/training/dataset/ArrayDataset.java
+++ b/api/src/main/java/ai/djl/training/dataset/ArrayDataset.java
@@ -21,19 +21,27 @@ import java.util.stream.Stream;
 
 /**
  * {@code ArrayDataset} is an implementation of {@link RandomAccessDataset} that consist entirely of
- * large {@link NDArray}s. There can be multiple data and label {@link NDArray}s within the dataset.
- * Each sample will be retrieved by indexing each {@link NDArray} along the first dimension.
+ * large {@link NDArray}s. It is recommended only for datasets small enough to fit in memory that
+ * come in array formats. Otherwise, consider directly using the {@link RandomAccessDataset}
+ * instead.
+ *
+ * <p>There can be multiple data and label {@link NDArray}s within the dataset. Each sample will be
+ * retrieved by indexing each {@link NDArray} along the first dimension.
  *
  * <p>The following is an example of how to use ArrayDataset:
  *
  * <pre>
  *     ArrayDataset dataset = new ArrayDataset.Builder()
- *                              .setData(data)
- *                              .optLabels(label)
+ *                              .setData(data1, data2)
+ *                              .optLabels(labels1, labels2, labels3)
  *                              .setSampling(20, false)
  *                              .build();
  * </pre>
  *
+ * <p>Suppose you get a {@link Batch} from {@code trainer.iterateDataset(dataset)} or {@code
+ * dataset.getData(manager)}. In the data of this batch, it will be an NDList with one NDArray for
+ * each data input. In this case, it would be 2 arrays. Similarly, the labels would have 3 arrays.
+ *
  * @see Dataset
  */
 public class ArrayDataset extends RandomAccessDataset {
diff --git a/api/src/main/java/ai/djl/training/dataset/Dataset.java b/api/src/main/java/ai/djl/training/dataset/Dataset.java
index 90a21da79..9dacf1694 100644
--- a/api/src/main/java/ai/djl/training/dataset/Dataset.java
+++ b/api/src/main/java/ai/djl/training/dataset/Dataset.java
@@ -21,7 +21,9 @@ import java.util.concurrent.ExecutorService;
 /**
  * An interface to represent a set of sample data/label pairs to train a model.
  *
- * @see <a href="http://docs.djl.ai/docs/dataset.html">The guide on datasets</a>
+ * @see <a href="http://docs.djl.ai/docs/dataset.html">The guide to datasets</a>
+ * @see <a href="http://docs.djl.ai/docs/development/how_to_use_dataset.html">The guide to
+ *     implementing a custom dataset</a>
  */
 public interface Dataset {
 
diff --git a/api/src/main/java/ai/djl/training/dataset/RandomAccessDataset.java b/api/src/main/java/ai/djl/training/dataset/RandomAccessDataset.java
index eab783fec..0d5e20623 100644
--- a/api/src/main/java/ai/djl/training/dataset/RandomAccessDataset.java
+++ b/api/src/main/java/ai/djl/training/dataset/RandomAccessDataset.java
@@ -34,6 +34,11 @@ import java.util.stream.IntStream;
 /**
  * RandomAccessDataset represent the dataset that support random access reads. i.e. it could access
  * a specific data item given the index.
+ *
+ * <p>Almost all datasets in DJL extend, either directly or indirectly, {@link RandomAccessDataset}.
+ *
+ * @see <a href="http://docs.djl.ai/docs/development/how_to_use_dataset.html">The guide to
+ *     implementing a custom dataset</a>
  */
 public abstract class RandomAccessDataset implements Dataset {
 
diff --git a/api/src/main/java/ai/djl/training/tracker/CyclicalTracker.java b/api/src/main/java/ai/djl/training/tracker/CyclicalTracker.java
new file mode 100644
index 000000000..0495c5d8f
--- /dev/null
+++ b/api/src/main/java/ai/djl/training/tracker/CyclicalTracker.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.training.tracker;
+
+import ai.djl.util.Preconditions;
+
+/**
+ * {@code CyclicalTracker} is an implementation of {@link Tracker} which is a policy of learning
+ * rate adjustment that increases the learning rate off a base value in a cyclical nature, as
+ * detailed in the paper <a href="https://arxiv.org/abs/1506.01186">Cyclical Learning Rates for
+ * Training Neural Networks</a>.
+ */
+public class CyclicalTracker implements Tracker {
+
+    private float baseValue;
+    private float maxValue;
+    private int stepSizeUp;
+    private int stepSizeDown;
+    private int totalSize;
+    private float stepRatio;
+    private ScaleFunction scaleFunction;
+    private boolean scaleModeCycle;
+
+    /**
+     * Creates a new instance of {@code CyclicalTracker}.
+     *
+     * @param builder the builder to create a new instance of {@code CyclicalTracker}
+     */
+    public CyclicalTracker(Builder builder) {
+        this.baseValue = builder.baseValue;
+        this.maxValue = builder.maxValue;
+        this.stepSizeUp = builder.stepSizeUp;
+        this.stepSizeDown = builder.stepSizeDown > 0 ? builder.stepSizeDown : builder.stepSizeUp;
+        this.totalSize = this.stepSizeUp + this.stepSizeDown;
+        this.stepRatio = (float) this.stepSizeUp / this.totalSize;
+        if (builder.scaleFunction != null) {
+            this.scaleFunction = builder.scaleFunction;
+            this.scaleModeCycle = builder.scaleModeCycle;
+        } else {
+            switch (builder.mode) {
+                case TRIANGULAR:
+                    this.scaleFunction = new TriangularScaleFunction();
+                    this.scaleModeCycle = true;
+                    break;
+                case TRIANGULAR2:
+                    this.scaleFunction = new Triangular2ScaleFunction();
+                    this.scaleModeCycle = true;
+                    break;
+                case EXP_RANGE:
+                    this.scaleFunction = new ExpRangeScaleFunction(builder.gamma);
+                    this.scaleModeCycle = false;
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unsupported Cyclical mode.");
+            }
+        }
+    }
+
+    /**
+     * Creates a new builder.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public float getNewValue(int numUpdate) {
+        int cycle = (int) Math.floor(1 + (float) numUpdate / this.totalSize);
+        float x = 1 + (float) numUpdate / this.totalSize - cycle;
+        float scaleFactor;
+        float res;
+        if (x < this.stepRatio) {
+            scaleFactor = x / this.stepRatio;
+        } else {
+            scaleFactor = (x - 1) / (this.stepRatio - 1);
+        }
+        float baseHeight = (this.maxValue - this.baseValue) * scaleFactor;
+        if (this.scaleModeCycle) {
+            res = this.baseValue + baseHeight * this.scaleFunction.func(cycle);
+        } else {
+            res = this.baseValue + baseHeight * this.scaleFunction.func(numUpdate);
+        }
+        return res;
+    }
+
+    /** The Builder to construct an {@link CyclicalTracker} object. */
+    public static final class Builder {
+
+        private float baseValue = 0.001f;
+        private float maxValue = 0.006f;
+        private int stepSizeUp = 2000;
+        private int stepSizeDown;
+        private CyclicalMode mode = CyclicalMode.TRIANGULAR;
+        private ScaleFunction scaleFunction;
+        private boolean scaleModeCycle = true;
+        private float gamma = 1f;
+
+        private Builder() {}
+
+        /**
+         * Sets the initial value. Default: 0.001.
+         *
+         * @param baseValue the initial value
+         * @return this {@code Builder}
+         */
+        public Builder optBaseValue(float baseValue) {
+            this.baseValue = baseValue;
+            return this;
+        }
+
+        /**
+         * Sets the initial value. Default: 0.006.
+         *
+         * @param maxValue the max value
+         * @return this {@code Builder}
+         */
+        public Builder optMaxValue(float maxValue) {
+            this.maxValue = maxValue;
+            return this;
+        }
+
+        /**
+         * Sets the number of iterations in up half of cycle. Default: 2000.
+         *
+         * @param stepSizeUp number of iterations in up half of cycle
+         * @return this {@code Builder}
+         */
+        public Builder optStepSizeUp(int stepSizeUp) {
+            this.stepSizeUp = stepSizeUp;
+            return this;
+        }
+
+        /**
+         * Sets the number of iterations in up half of cycle. If {@code stepSizeDown} equals 0, it
+         * is set to {@code stepSizeUp}. Default: 0.
+         *
+         * @param stepSizeDown number of iterations in the down half of cycle
+         * @return this {@code Builder}
+         */
+        public Builder optStepSizeDown(int stepSizeDown) {
+            this.stepSizeDown = stepSizeDown;
+            return this;
+        }
+
+        /**
+         * Sets the cyclical mode. Can be {@code CyclicalMode.TRIANGULAR}, {@code
+         * CyclicalMode.TRIANGULAR2} or {@code CyclicalMode.EXP_RANGE}. Values correspond to
+         * policies detailed above.
+         *
+         * @param mode cyclical mode
+         * @return this {@code Builder}
+         */
+        public Builder optMode(CyclicalMode mode) {
+            this.mode = mode;
+            return this;
+        }
+
+        /**
+         * Constant in {@code CyclicalMode.EXP_RANGE} scaling function. Default: 1.
+         *
+         * @param gamma constant in 'exp_range' scaling function: gamma^cycleIterations
+         * @return this {@code Builder}
+         */
+        public Builder optGamma(float gamma) {
+            this.gamma = gamma;
+            return this;
+        }
+
+        /**
+         * Custom scaling function. If set, {@code mode} will be ignored. Default: null.
+         *
+         * @param scaleFunction Custom scaling function
+         * @return this {@code Builder}
+         */
+        public Builder optScaleFunction(ScaleFunction scaleFunction) {
+            this.scaleFunction = scaleFunction;
+            return this;
+        }
+
+        /**
+         * Defines whether scaling function is evaluated on cycle number or update number. Default:
+         * true.
+         *
+         * @param scaleModeCycle if true then scaling function is evaluated on cycle number, else on
+         *     update number
+         * @return this {@code Builder}
+         */
+        public Builder optScaleModeCycle(boolean scaleModeCycle) {
+            this.scaleModeCycle = scaleModeCycle;
+            return this;
+        }
+
+        /**
+         * Builds a {@link CyclicalTracker} block.
+         *
+         * @return the {@link CyclicalTracker} block
+         */
+        public CyclicalTracker build() {
+            Preconditions.checkArgument(baseValue > 0, "baseValue has to be positive!");
+            Preconditions.checkArgument(maxValue > 0, "maxValue has to be positive!");
+            Preconditions.checkArgument(
+                    baseValue <= maxValue, "baseValue has to lower than maxValue!");
+            Preconditions.checkArgument(stepSizeUp >= 1, "stepSizeUp has to be positive!");
+            Preconditions.checkArgument(stepSizeDown >= 0, "stepSizeUp cannot be negative!");
+            Preconditions.checkArgument(
+                    gamma >= 0f && gamma <= 1f, "gamma has to be between 0 and 1!");
+            return new CyclicalTracker(this);
+        }
+    }
+
+    /**
+     * {@code CyclicalTracker} provides three predefined cyclical modes and can be selected by this
+     * enum.
+     *
+     * <ul>
+     *   <li>TRIANGULAR: A basic triangular cycle without amplitude scaling.
+     *   <li>TRIANGULAR2: A basic triangular cycle that scales initial amplitude by half each cycle.
+     *   <li>EXP_RANGE: A cycle that scales initial amplitude by (gamma<sup>cycleIterations</sup>)
+     *       at each cycle iteration.
+     * </ul>
+     */
+    public enum CyclicalMode {
+        TRIANGULAR,
+        TRIANGULAR2,
+        EXP_RANGE
+    }
+
+    /** {@code ScaleFunction} is an interface to implement a custom scale function. */
+    public interface ScaleFunction {
+        /**
+         * Custom scaling policy. Return value has to satisfy 0&lt;=func(steps)&lt;=1 for all
+         * steps&gt;1.
+         *
+         * @param steps current cycles if {@code scaleModeCycle} is true; input update number if it
+         *     is false
+         * @return scale ratio
+         */
+        float func(int steps);
+    }
+
+    private static class TriangularScaleFunction implements ScaleFunction {
+        @Override
+        public float func(int steps) {
+            return 1f;
+        }
+    }
+
+    private static class Triangular2ScaleFunction implements ScaleFunction {
+        @Override
+        public float func(int steps) {
+            return (float) (1 / (Math.pow(2f, steps - 1)));
+        }
+    }
+
+    private static class ExpRangeScaleFunction implements ScaleFunction {
+        float gamma;
+
+        ExpRangeScaleFunction(float gamma) {
+            this.gamma = gamma;
+        }
+
+        @Override
+        public float func(int steps) {
+            return (float) Math.pow(this.gamma, steps);
+        }
+    }
+}
diff --git a/api/src/main/java/ai/djl/training/tracker/Tracker.java b/api/src/main/java/ai/djl/training/tracker/Tracker.java
index a07c7b3da..913765330 100644
--- a/api/src/main/java/ai/djl/training/tracker/Tracker.java
+++ b/api/src/main/java/ai/djl/training/tracker/Tracker.java
@@ -70,6 +70,16 @@ public interface Tracker {
         return CosineTracker.builder();
     }
 
+    /**
+     * Returns a new instance of {@link ai.djl.training.tracker.CyclicalTracker.Builder} that can
+     * build an {@link CyclicalTracker}.
+     *
+     * @return the {@link CyclicalTracker} {@link ai.djl.training.tracker.CyclicalTracker.Builder}
+     */
+    static CyclicalTracker.Builder cyclical() {
+        return CyclicalTracker.builder();
+    }
+
     /**
      * Returns a new instance of {@link Tracker} with a fixed value.
      *
diff --git a/basicdataset/README.md b/basicdataset/README.md
index c342cd15d..4cd0afbc8 100644
--- a/basicdataset/README.md
+++ b/basicdataset/README.md
@@ -4,17 +4,7 @@
 
 This module contains a number of basic and standard datasets in the Deep Java Library's (DJL). These datasets are used to train deep learning models.
 
-## List of datasets
-
-This module contains the following datasets:
-
-- [MNIST](http://yann.lecun.com/exdb/mnist/) - A handwritten digits dataset
-- [CIFAR10](https://www.cs.toronto.edu/~kriz/cifar.html) - A dataset consisting of 60,000 32x32 color images in 10 classes
-- [Coco](http://cocodataset.org) - A large-scale object detection, segmentation, and captioning dataset that contains 1.5 million object instances
-    - You have to manually add `com.twelvemonkeys.imageio:imageio-jpeg:3.5` dependency to your project
-- [ImageNet](http://www.image-net.org/) - An image database organized according to the WordNet hierarchy
-  >**Note**: You have to manually download the ImageNet dataset due to licensing requirements.
-- [Pikachu](http://d2l.ai/chapter_computer-vision/object-detection-dataset.html) - 1000 Pikachu images of different angles and sizes created using an open source 3D Pikachu model
+You can find the datasets provided by this module on our [docs](http://docs.djl.ai/docs/dataset.html).
 
 ## Documentation
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
index 3cdb4c022..b36f2843d 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
@@ -34,6 +34,13 @@ import java.util.Optional;
 /**
  * Coco image detection dataset from http://cocodataset.org/#home.
  *
+ * <p>Coco is a large-scale object detection, segmentation, and captioning dataset although only
+ * object detection is implemented at thsi time. It contains 1.5 million object instances and is one
+ * of the standard benchmark object detection datasets.
+ *
+ * <p>To use this dataset, you have to manually add {@code
+ * com.twelvemonkeys.imageio:imageio-jpeg:3.5} as a dependency in your project.
+ *
  * <p>Each image might have different {@link ai.djl.ndarray.types.Shape}s.
  */
 public class CocoDetection extends ObjectDetectionDataset {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
index 0a7130dfa..f1ad1f387 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
@@ -39,7 +39,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-/** Pikachu image detection dataset that contains multiple Pikachus in each image. */
+/**
+ * Pikachu image detection dataset that contains multiple Pikachus in each image.
+ *
+ * <p>It was based on a section from the [Dive into Deep Learning
+ * book](http://d2l.ai/chapter_computer-vision/object-detection-dataset.html). It contains 1000
+ * Pikachu images of different angles and sizes created using an open source 3D Pikachu model. Each
+ * image contains only a single pikachu.
+ */
 public class PikachuDetection extends ObjectDetectionDataset {
 
     private static final String VERSION = "1.0";
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/AbstractImageFolder.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/AbstractImageFolder.java
index fe8ccad10..b370ad5d6 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/AbstractImageFolder.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/AbstractImageFolder.java
@@ -34,7 +34,11 @@ import java.util.stream.Stream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** A dataset for loading image files stored in a folder structure. */
+/**
+ * A dataset for loading image files stored in a folder structure.
+ *
+ * <p>Usually, you want to use {@link ImageFolder} instead.
+ */
 public abstract class AbstractImageFolder extends ImageClassificationDataset {
 
     private static final Logger logger = LoggerFactory.getLogger(AbstractImageFolder.class);
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Cifar10.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Cifar10.java
index 21d7a5bc0..5e9402728 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Cifar10.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Cifar10.java
@@ -34,6 +34,9 @@ import java.util.Map;
 /**
  * CIFAR10 image classification dataset from https://www.cs.toronto.edu/~kriz/cifar.html.
  *
+ * <p>It consists of 60,000 32x32 color images with 10 classes. It can train in a few hours with a
+ * GPU.
+ *
  * <p>Each sample is an image (in 3-D {@link NDArray}) with shape (32, 32, 3).
  */
 public final class Cifar10 extends ArrayDataset {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/FashionMnist.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/FashionMnist.java
index abbad625b..90f1c0e44 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/FashionMnist.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/FashionMnist.java
@@ -33,9 +33,11 @@ import java.util.Map;
 
 /**
  * FashMnist is a dataset from Zalando article images
- * https://github.com/zalandoresearch/fashion-mnist.
+ * (https://github.com/zalandoresearch/fashion-mnist).
  *
- * <p>Each sample is an image (in 3-D NDArray) with shape (28, 28, 1).
+ * <p>Each sample is a grayscale image (in 3-D NDArray) with shape (28, 28, 1).
+ *
+ * <p>It was created to be a drop in replacement for {@link Mnist}, but have a less simplistic task.
  */
 public final class FashionMnist extends ArrayDataset {
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/ImageFolder.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/ImageFolder.java
index fa2251477..b9da9ee02 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/ImageFolder.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/ImageFolder.java
@@ -24,6 +24,8 @@ import java.util.Arrays;
 /**
  * A dataset for loading image files stored in a folder structure.
  *
+ * <p>Below is an example directory layout for the image folder:
+ *
  * <pre>
  *  The image folder should be structured as follows:
  *       root/shoes/Aerobic Shoes1.png
@@ -32,11 +34,35 @@ import java.util.Arrays;
  *       root/boots/Black Boots.png
  *       root/boots/White Boots.png
  *       ...
- *       root/pumps/Red Pumps
- *       root/pumps/Pink Pumps
+ *       root/pumps/Red Pumps.png
+ *       root/pumps/Pink Pumps.png
  *       ...
+ *
  *  here shoes, boots, pumps are your labels
  *  </pre>
+ *
+ * <p>Here, the dataset will take the folder names (shoes, boots, bumps) in sorted order as your
+ * labels. Nested folder structures are not currently supported.
+ *
+ * <p>Then, you can create your instance of the dataset as follows:
+ *
+ * <pre>
+ * // set the image folder path
+ * Repository repository = Repository.newInstance("folder", Paths.get("/path/to/imagefolder/root");
+ * ImageFolder dataset =
+ *     new ImageFolder.Builder()
+ *         .setRepository(repository)
+ *         .addTransform(new Resize(100, 100)) // Use image transforms as necessary for your data
+ *         .addTransform(new ToTensor()) // Usually required as the last transform to convert images to tensors
+ *         .setSampling(batchSize, true)
+ *         .build();
+ *
+ * // call prepare before using
+ * dataset.prepare();
+ *
+ * // to get the synset or label names
+ * List&gt;String&lt; synset = dataset.getSynset();
+ * </pre>
  */
 public final class ImageFolder extends AbstractImageFolder {
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Mnist.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Mnist.java
index 257fd6bc4..1cb87668d 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Mnist.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Mnist.java
@@ -34,7 +34,11 @@ import java.util.Map;
 /**
  * MNIST handwritten digits dataset from http://yann.lecun.com/exdb/mnist.
  *
- * <p>Each sample is an image (in 3-D NDArray) with shape (28, 28, 1).
+ * <p>Each sample is a grayscale image (in 3-D NDArray) with shape (28, 28, 1).
+ *
+ * <p>It is a common starting dataset because it is small and can train within minutes. However, it
+ * is an overly easy task that even poor models can still perform very well on. Instead, consider
+ * {@link FashionMnist} which offers a comparable speed but a more reasonable difficulty task.
  */
 public final class Mnist extends ArrayDataset {
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/nlp/StanfordQuestionAnsweringDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/nlp/StanfordQuestionAnsweringDataset.java
index 7716d5ab1..aa3d0280d 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/nlp/StanfordQuestionAnsweringDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/nlp/StanfordQuestionAnsweringDataset.java
@@ -39,6 +39,8 @@ import java.util.Map;
  * questions posed by crowdworkers on a set of Wikipedia articles, where the answer to every
  * question is a segment of text, or span, from the corresponding reading passage, or the question
  * might be unanswerable.
+ *
+ * @see <a href="https://rajpurkar.github.io/SQuAD-explorer/">Dataset website</a>
  */
 @SuppressWarnings("unchecked")
 public class StanfordQuestionAnsweringDataset extends TextDataset implements RawDataset<Object> {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java
new file mode 100644
index 000000000..bb27640ca
--- /dev/null
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.basicdataset.tabular;
+
+import ai.djl.Application.Tabular;
+import ai.djl.basicdataset.BasicDatasets;
+import ai.djl.basicdataset.utils.DynamicBuffer;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.repository.Artifact;
+import ai.djl.repository.MRL;
+import ai.djl.repository.Repository;
+import ai.djl.training.dataset.Record;
+import ai.djl.util.Progress;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVRecord;
+
+/**
+ * Daily Delhi climate dataset from <a
+ * href="https://www.kaggle.com/datasets/sumanthvrao/daily-climate-time-series-data">https://www.kaggle.com/datasets/sumanthvrao/daily-climate-time-series-data</a>.
+ *
+ * <p>The Dataset is fully dedicated for the developers who want to train the model on Weather
+ * Forecasting for Indian climate. This dataset provides data from 1st January 2013 to 24th April
+ * 2017 in the city of Delhi, India. The 4 parameters here are meantemp, humidity, wind_speed,
+ * meanpressure.
+ */
+public class DailyDelhiClimate extends CsvDataset {
+
+    private static final String ARTIFACT_ID = "daily-delhi-climate";
+    private static final String VERSION = "3.0";
+
+    private Usage usage;
+    private MRL mrl;
+    private boolean prepared;
+
+    DailyDelhiClimate(Builder builder) {
+        super(builder);
+        usage = builder.usage;
+        mrl = builder.getMrl();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Record get(NDManager manager, long index) {
+        CSVRecord csvRecord = csvRecords.get(Math.toIntExact(index));
+        NDList data = toNDList(manager, csvRecord, features);
+        NDList label = new NDList();
+
+        return new Record(data, label);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void prepare(Progress progress) throws IOException {
+        if (prepared) {
+            return;
+        }
+
+        Artifact artifact = mrl.getDefaultArtifact();
+        mrl.prepare(artifact, progress);
+
+        Path root = mrl.getRepository().getResourceDirectory(artifact);
+        Path csvFile;
+        switch (usage) {
+            case TRAIN:
+                csvFile = root.resolve("DailyDelhiClimateTrain.csv");
+                break;
+            case TEST:
+                csvFile = root.resolve("DailyDelhiClimateTest.csv");
+                break;
+            case VALIDATION:
+            default:
+                throw new UnsupportedOperationException("Validation data not available.");
+        }
+
+        csvUrl = csvFile.toUri().toURL();
+        super.prepare(progress);
+        prepared = true;
+    }
+
+    /**
+     * Creates a builder to build a {@link DailyDelhiClimate}.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /** A builder to construct a {@link DailyDelhiClimate}. */
+    public static final class Builder extends CsvBuilder<Builder> {
+
+        Repository repository;
+        String groupId;
+        String artifactId;
+        Usage usage;
+        List<String> featureArray =
+                new ArrayList<>(
+                        Arrays.asList(
+                                "date", "meantemp", "humidity", "wind_speed", "meanpressure"));
+
+        /** Constructs a new builder. */
+        Builder() {
+            repository = BasicDatasets.REPOSITORY;
+            groupId = BasicDatasets.GROUP_ID;
+            artifactId = ARTIFACT_ID;
+            usage = Usage.TRAIN;
+            csvFormat =
+                    CSVFormat.DEFAULT
+                            .builder()
+                            .setHeader()
+                            .setSkipHeaderRecord(true)
+                            .setIgnoreHeaderCase(true)
+                            .setTrim(true)
+                            .build();
+        }
+
+        /**
+         * Returns this {code Builder} object.
+         *
+         * @return this {@code BaseBuilder}
+         */
+        @Override
+        public Builder self() {
+            return this;
+        }
+
+        /**
+         * Sets the optional usage.
+         *
+         * @param usage the new usage
+         * @return this builder
+         */
+        public Builder optUsage(Usage usage) {
+            this.usage = usage;
+            return self();
+        }
+
+        /**
+         * Sets the optional repository.
+         *
+         * @param repository the repository
+         * @return this builder
+         */
+        public Builder optRepository(Repository repository) {
+            this.repository = repository;
+            return self();
+        }
+
+        /**
+         * Sets optional groupId.
+         *
+         * @param groupId the groupId}
+         * @return this builder
+         */
+        public Builder optGroupId(String groupId) {
+            this.groupId = groupId;
+            return self();
+        }
+
+        /**
+         * Sets the optional artifactId.
+         *
+         * @param artifactId the artifactId
+         * @return this builder
+         */
+        public Builder optArtifactId(String artifactId) {
+            if (artifactId.contains(":")) {
+                String[] tokens = artifactId.split(":");
+                groupId = tokens[0];
+                this.artifactId = tokens[1];
+            } else {
+                this.artifactId = artifactId;
+            }
+            return self();
+        }
+
+        /**
+         * Adds a feature to the features set.
+         *
+         * @param name the name of the feature
+         * @return this builder
+         */
+        public Builder addFeature(String name) {
+            if ("date".equals(name)) {
+                return addDateFeature(name);
+            } else {
+                return addNumericFeature(name);
+            }
+        }
+
+        /**
+         * Add a new feature of date type. Since the dataset is a time series dataset, the date is
+         * an important feature in String form, and we can't just treat it as a simple categorical
+         * feature.
+         *
+         * @param name the name of the feature
+         * @return this builder
+         */
+        private Builder addDateFeature(String name) {
+            return addFeature(new Feature(name, new DateFeaturizer()));
+        }
+
+        /**
+         * Returns the available features of this dataset.
+         *
+         * @return a list of feature names
+         */
+        public List<String> getAvailableFeatures() {
+            return featureArray;
+        }
+
+        /**
+         * Builds the new {@link DailyDelhiClimate}.
+         *
+         * @return the new {@link DailyDelhiClimate}
+         */
+        @Override
+        public DailyDelhiClimate build() {
+            if (features.isEmpty()) {
+                for (String name : featureArray) {
+                    addFeature(name);
+                }
+            }
+            return new DailyDelhiClimate(this);
+        }
+
+        MRL getMrl() {
+            return repository.dataset(Tabular.ANY, groupId, artifactId, VERSION);
+        }
+    }
+
+    /** A featurizer implemented for feature of date type. */
+    public static final class DateFeaturizer implements Featurizer {
+
+        /**
+         * Featurize the feature of date type to epoch day (the number of days passed since
+         * 1970-01-01) and put it into float buffer, so that it can be used for future training in a
+         * simple way.
+         *
+         * @param buf the float buffer to be filled
+         * @param input the date string in the format {@code yyyy-MM-dd}
+         */
+        @Override
+        public void featurize(DynamicBuffer buf, String input) {
+            LocalDate ld = LocalDate.parse(input, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+            long day = ld.toEpochDay();
+            buf.put(day);
+        }
+    }
+}
diff --git a/docs/dataset.md b/docs/dataset.md
index 5bbddeb20..cb6d10778 100644
--- a/docs/dataset.md
+++ b/docs/dataset.md
@@ -1,6 +1,6 @@
 # Dataset
 
-A dataset (or data set) is a collection of data that are used for machine-learning training job.
+A dataset (or data set) is a collection of data that is used for training a machine learning model.
 
 Machine learning typically works with three datasets:
 
@@ -11,7 +11,7 @@ Machine learning typically works with three datasets:
 - Validation dataset
 
     The validation set is used to evaluate a given model during the training process. It helps machine learning
-    engineers to fine-tune the [HyperParameter](https://github.com/deepjavalibrary/djl/blob/master/api/src/main/java/ai/djl/training/hyperparameter/param/Hyperparameter.java)
+    engineers to fine-tune the [HyperParameters](https://github.com/deepjavalibrary/djl/blob/master/api/src/main/java/ai/djl/training/hyperparameter/param/Hyperparameter.java)
     at model development stage.
     The model doesn't learn from validation dataset; and validation dataset is optional.
     
@@ -19,9 +19,59 @@ Machine learning typically works with three datasets:
 
     The Test dataset provides the gold standard used to evaluate the model.
     It is only used once a model is completely trained.
+    The test dataset should more accurately evaluate how the model will be performed on new data.
  
 See [Jason Brownlee’s article](https://machinelearningmastery.com/difference-test-validation-datasets/) for more detail.
  
 ## [Basic Dataset](../basicdataset/README.md)
  
 DJL provides a number of built-in basic and standard datasets. These datasets are used to train deep learning models.
+This module contains the following datasets:
+
+### CV
+
+#### Image Classification
+
+- [MNIST](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/Mnist.html) - A small and fast handwritten digits dataset
+- [Fashion MNIST](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/FashionMnist.html) - A small and fast clothing type detection dataset
+- [CIFAR10](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/Cifar10.html) - A dataset consisting of 60,000 32x32 color images in 10 classes
+- [ImageNet](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/ImageNet.html) - An image database organized according to the WordNet hierarchy
+  >**Note**: You have to manually download the ImageNet dataset due to licensing requirements.
+
+#### Object Detection
+
+- [Pikachu](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/PikachuDetection.html) - 1000 Pikachu images of different angles and sizes created using an open source 3D Pikachu model
+- [Banana Detection](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/BananaDetection.html) - A testing single object detection dataset
+
+#### Other CV
+
+- [Captcha](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/CaptchaDataset.html) - A dataset for a grayscale 6-digit CAPTCHA task
+- [Coco](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/CocoDetection.html) - A large-scale object detection, segmentation, and captioning dataset that contains 1.5 million object instances
+  - You have to manually add `com.twelvemonkeys.imageio:imageio-jpeg:3.5` dependency to your project
+
+### NLP
+
+#### Text Classification and Sentiment Analysis
+
+- [AmazonReview](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/AmazonReview.html) - A sentiment analysis dataset of Amazon Reviews with their ratings
+- [Stanford Movie Review](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/StanfordMovieReview.html) - A sentiment analysis dataset of movie reviews and sentiments sourced from IMDB
+- [GoEmotions](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/GoEmotions.html) - A dataset classifying 50k curated reddit comments into either 27 emotion categories or neutral
+
+#### Unlabeled Text
+
+- [Penn Treebank Text](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/PennTreebankText.html) - The text (not POS tags) from the Penn Treebank, a collection of Wall Street Journal stories
+- [WikiText2](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/WikiText2.html) - A collection of over 100 million tokens extracted from good and featured articles on wikipedia
+ 
+#### Other NLP
+
+- [Stanford Question Answering Dataset (SQuAD)](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/StanfordQuestionAnsweringDataset.html) - A reading comprehension dataset with text from wikipedia articles
+- [Tatoeba English French Dataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/TatoebaEnglishFrenchDataset.html) - An english-french translation dataset from the Tatoeba Project
+
+### Tabular
+
+- [Airfoil Self-Noise](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/tabular/AirfoilRandomAccess.html) - A 6 feature dataset from NASA tests of airfoils
+- [Ames House Pricing](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/tabular/AmesRandomAccess.html) - A 80 feature dataset to predict house prices
+
+### Time Series
+
+- [Daily Delhi Climate](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/tabular/DailyDelhiClimate.html)
\ No newline at end of file
diff --git a/docs/development/add_dataset_to_djl.md b/docs/development/add_dataset_to_djl.md
new file mode 100644
index 000000000..2660806d2
--- /dev/null
+++ b/docs/development/add_dataset_to_djl.md
@@ -0,0 +1,66 @@
+# Add a new dataset to DJL basic datasets
+
+This document outlines the procedure to add new datasets to DJL.
+
+## Step 1: Prepare the folder structure
+
+1. Navigate to the `test/resources/mlrepo/dataset` folder and create a folder in it to store your dataset based on its category.
+   For example, `cv/ai/djl/basicdataset/mnist`.
+2. Create a version folder within your newly created dataset's folder (e.g `0.0.1`). The version should match your dataset version.
+
+### Step 2: Create a `metadata.json` file
+
+You need to create a `metadata.json` file for the repository to load the dataset. You can refer to the format in the `metadata.json` files for existing datasets to create your own.
+
+**Note:** You need to update the sha1 hash of each file in your `metadata.json` file. Use the following command to get the sha1Hash value:
+
+```shell
+$ shasum -a 1 <file_name>
+```
+
+### Step 3: Create a Dataset implementation
+
+Create a class that implements the dataset and loads it.
+For more details on creating datasets, see the [dataset creation guide](how_to_use_dataset.md).
+You should also look at examples of official DJL datasets such as [`AmesRandomAccess`](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AmesRandomAccess.java)
+or [`Cifar10`](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/Cifar10.java).
+
+Then, add some tests for the dataset.
+For testing, you can use a local repository such as:
+
+```java
+Repository repository = Repository.newInstace("testRepository", Paths.get("/test/resources/mlrepo"));
+```
+
+### Step 4: Update the datasets list
+
+Add your dataset to the [list of built-in datasets](../dataset.md).
+
+### Step 5: Upload metadata
+
+The official DJL ML repository is located on an S3 bucket managed by the AWS DJL team.
+You have to add the metadata and any dataset files to the repository.
+
+For non-AWS team members, go ahead straight to Step 6 and open a pull request.
+Within the pull request, you can coordinate with an AWS member to add the necessary files.
+
+For AWS team members, run the following command to upload your model to the S3 bucket:
+
+```shell
+$ ./gradlew syncS3
+```
+
+The `metadata.json` in DJL is mainly a repository of metadata.
+Within the metadata is typically contains only links indicating where the actual data would be found.
+
+However, some datasets can be distributed by DJL depending on whether it makes it easier to use and the dataset permits redistribution.
+In that case, coordinate with an AWS team member in your pull request.
+
+### Step 6: Open a PR to add your ModelLoader and metadata files to the git repository
+
+**Note**: Avoid checking in binary files to git. Binary files should only be uploaded to the S3 bucket.
+
+If you are relying on an AWS team member, you should leave your code with the local test repositories.
+If you try to use the official repository before it contains your metadata, the tests will not pass the CI.
+
+Once an AWS team member adds your metadata, they will prompt you to update your PR to the official repository.
diff --git a/docs/development/add_model_to_model-zoo.md b/docs/development/add_model_to_model-zoo.md
index 4dfa517a3..201bef465 100644
--- a/docs/development/add_model_to_model-zoo.md
+++ b/docs/development/add_model_to_model-zoo.md
@@ -1,6 +1,6 @@
-# Add a new model to the model zoo
+# Add a new model to the DJL model zoo
 
-This document outlines the procedure to add new models into the model zoo.
+This document outlines the procedure to add new models into the DJL model zoo.
 
 ## Step 1: Prepare the model files
 
@@ -31,6 +31,7 @@ For example, `image_classification/ai/djl/resnet`.
 3. Copy model files into the version folder.
 
 ### Step 3: Create a `metadata.json` file
+
 You need to create a `metadata.json` file for the model zoo to load the model. You can refer to the format in the `metadata.json` files for existing models to create your own.
 
 For a model built as a DJL block, you must recreate the block before loading the parameters. As part of your `metadata.json` file, you should use the `arguments` property to specify the arguments required for the model loader to create another `Block` matching the one used to train the model.
@@ -51,7 +52,7 @@ Verify that your folder has the following files (see Step 1 for additional files
 
 The official DJL ML repository is located on an S3 bucket managed by the AWS DJL team.
 
-For non-team members, coordinate with a team member in your pull request to coordinate adding the necessary files.
+For non-team members, coordinate with a team member in your pull request to add the necessary files.
 
 For AWS team members, run the following command to upload your model to the S3 bucket:
 
diff --git a/docs/development/example_dataset.md b/docs/development/example_dataset.md
new file mode 100644
index 000000000..78ed77a8c
--- /dev/null
+++ b/docs/development/example_dataset.md
@@ -0,0 +1,143 @@
+## Example CSV Dataset
+
+If the provided Datasets don't meet your requirements, you can also easily extend our dataset to create your own customized dataset.
+
+Let's take CSVDataset, which can load a csv file, for example.
+
+### Step 1: Prerequisites
+For this example, we'll use [malicious_url_data.csv](https://github.com/incertum/cyber-matrix-ai/blob/master/Malicious-URL-Detection-Deep-Learning/data/url_data_mega_deep_learning.csv).
+
+The CSV file has the following format.
+
+| URL      | isMalicious |
+| ----------- | ----------- |
+| sample.url.good.com | 0 |
+| sample.url.bad.com | 1  |
+
+We'll also use the 3rd party [Apache Commons](https://commons.apache.org/) library to read the CSV file. To use the library, include the following dependency:
+
+```
+api group: 'org.apache.commons', name: 'commons-csv', version: '1.7'
+```
+
+### Step 2: Implementation
+In order to extend the dataset, the following dependencies are required:
+
+```
+api "ai.djl:api:0.17.0"
+api "ai.djl:basicdataset:0.17.0"
+```
+
+There are four parts we need to implement for CSVDataset.
+
+1. Constructor and Builder
+
+First, we need a private field that holds the CSVRecord list from the csv file.
+We create a constructor and pass the CSVRecord list from builder to the class field.
+For builder, we have all we need in `BaseBuilder` so we only need to include the two minimal methods as shown.
+In the *build()* method, we take advantage of CSVParser to get the record of each CSV file and put them in CSVRecord list.
+
+```java
+public class CSVDataset extends RandomAccessDataset {
+
+    private final List<CSVRecord> csvRecords;
+
+    private CSVDataset(Builder builder) {
+        super(builder);
+        csvRecords = builder.csvRecords;
+    }
+    ...
+    public static final class Builder extends BaseBuilder<Builder> {
+        List<CSVRecord> csvRecords;
+
+        @Override
+        protected Builder self() {
+            return this;
+        }
+
+        CSVDataset build() throws IOException {
+            String csvFilePath = "path/malicious_url_data.csv";
+            try (Reader reader = Files.newBufferedReader(Paths.get(csvFilePath));
+                 CSVParser csvParser =
+                    new CSVParser(
+                        reader,
+                        CSVFormat.DEFAULT
+                            .withHeader("url", "isMalicious")
+                            .withFirstRecordAsHeader()
+                            .withIgnoreHeaderCase()
+                            .withTrim())) {
+                 csvRecords = csvParser.getRecords();
+            }
+            return new CSVDataset(this);
+        }
+    }
+
+}
+```
+
+2. Prepare
+
+As mentioned, in this example we are taking advantage of CSVParser to prepare the data for us. To prepare
+the data on our own, we use the `prepare()` method. Normally here we would load or create any data
+for our dataset and then save it in one of the private fields previously created. This `prepare()` method
+is called everytime we call `getData()` so in every case we want to only load the data once, we use a
+boolean variable called `prepared` to check if it has previously been loaded or prepared.
+
+Since we don't have to prepare any data on our own for this example, we only have to override it.
+
+```java
+@Override
+public void prepare(Progress progress) {}
+```
+
+There are great [examples](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset/nlp/AmazonReview.java)
+in our [basicdataset](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset)
+folder that show use cases for `prepare()`.
+
+
+
+3. Getter
+
+The getter returns a Record object which contains encoded inputs and labels.
+Here, we use simple encoding to transform the url String to an int array and create a NDArray on top of it.
+The reason why we use NDList here is that you might have multiple inputs and labels in different tasks.
+
+```java
+@Override
+public Record get(NDManager manager, long index) {
+    // get a CSVRecord given an index
+    CSVRecord record = csvRecords.get(Math.toIntExact(index));
+    NDArray datum = manager.create(encode(record.get("url")));
+    NDArray label = manager.create(Float.parseFloat(record.get("isMalicious")));
+    return new Record(new NDList(datum), new NDList(label));
+}
+```
+
+4. Size
+
+The number of records available to be read in this Dataset.
+Here, we can directly use the size of the List<CSVRecord>.
+
+```java
+@Override
+public long availableSize() {
+    return csvRecords.size();
+}
+```
+
+Done!
+Now, you can use the CSVDataset with the following code snippet:
+
+```java
+CSVDataset dataset = new CSVDataset.Builder().setSampling(batchSize, false).build();
+for (Batch batch : dataset.getData(model.getNDManager())) {
+    // use head to get first NDArray
+    batch.getData().head();
+    batch.getLabels().head();
+    ...
+    // don't forget to close the batch in the end
+    batch.close();
+}
+```
+
+Full example code could be found in [CSVDataset.java](https://github.com/deepjavalibrary/djl/blob/master/docs/development/CSVDataset.java).
diff --git a/docs/development/how_to_use_dataset.md b/docs/development/how_to_use_dataset.md
index 206957b04..8f6c3abd7 100644
--- a/docs/development/how_to_use_dataset.md
+++ b/docs/development/how_to_use_dataset.md
@@ -1,227 +1,58 @@
 # Dataset Creation
 
-The Dataset in DJL represents both the raw data and loading process.
-RandomAccessDataset implements the Dataset interface and provides comprehensive data loading functionality.
-RandomAccessDataset is also a basic dataset that supports random or sequential access of data using indices.
-You can easily customize your own dataset by extending RandomAccessDataset.
+The [Dataset](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/training/dataset/Dataset.html) in DJL represents both the raw data and the data loading process.
+For this reason, training in DJL usually requires that your data be implemented through using a dataset class.
+You can choose to use one of the well-known datasets we have [built in](../dataset.md).
+Or, you can create a custom dataset.
 
-We provide several well-known datasets that you can use in our [Basic Datasets module](https://javadoc.io/doc/ai.djl/basicdataset/latest/index.html).
+## Dataset Helpers
 
-We also provide several built-in datasets that you can easily wrap around existing NDArrays and images.
+There are a number of helpers provided by DJL to make it easy to create custom datasets.
+If a helper is available, it can make it easier to implement the dataset then building it from scratch:
 
-- ArrayDataset - a dataset that wraps your existing NDArrays as inputs and labels.
-- ImageFolder - a dataset that wraps your images under folders for image classification.
+### CV
 
-If none of the provided datasets meet your requirements, you can also easily customize you own dataset by extending
-the RandomAccessDataset.
+- [ImageDataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/ImageDataset.html) - A abstract dataset to create a dataset where the input is an image such as image classification, object detection, and image segmentation
+- [ImageClassificationDataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/ImageClassificationDataset.html) - An abstract dataset for image classification
+- [AbstractImageFolder](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/AbstractImageFolder.html) - An abstract dataset for loading images in a folder structure. Usually you want the ImageFolderDataset.
+- [ImageFolder](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/classification/ImageFolder.html) - A dataset for loading image folders stored in a folder structure
+- [ObjectDetectionDataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/cv/ObjectDetectionDataset.html) - An abstract dataset for object detection
 
-## How to use the ArrayDataset
+### NLP
 
-The following code illustrates an implementation of [ArrayDataset](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/training/dataset/ArrayDataset.html).
-The ArrayDataset is recommended only if your dataset is small enough to fit in memory.
+- [TextDataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/nlp/TextDataset.html) - An abstract dataset for NLP where either the input or labels are text-based.
+- [TextData](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/utils/TextData.html) - A utility for managing the text within a dataset
 
-```java
-// given you have data1, data2 and label1, label2, label3
-ArrayDataset dataset = new ArrayDataset.Builder()
-                              .setData(data1, data2)
-                              .optLabels(label1, label2, label3)
-                              .setSampling(20, false)
-                              .build();
+### Other
 
-```
+- [CsvDataset](https://javadoc.io/doc/ai.djl/basicdataset/latest/ai/djl/basicdataset/tabular/CsvDataset.html) - An dataset for loading data from a .csv file
 
-When you get the `Batch` from `trainer.iterateDataset(dataset)` or `dataset.getData(manager)`,
-you can use ``batch.getData()`` to get a NDList. The size of this NDList is the amount of data NDArrays 
-entered in `setData()`, in this case the size is 2. You can then use `NDList.get(0)` to get your first 
-data and `NDList.get(1)` to get your second data.
-Similarly, you can use `batch.getLabels()` to get a NDList with size 3.
+## Custom Datasets
 
-## How to use the ImageFolder dataset
+If none of the provided datasets meet your requirements, you can also easily customize you own dataset in a custom class.
+While technically the dataset must only implement [`Dataset`](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/training/dataset/Dataset.html), it is best to instead extend [`RandomAccessDataset`](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/training/dataset/RandomAccessDataset.html).
+It manages data randomization and provides comprehensive data loading functionality.
 
-This section outlines the procedure to use the ImageFolder dataset.
+The `RandomAccessDataset` is based on making your data records into a list where each record has an index.
+Then, it only needs to know how many records there are and how to load each record giving its index.
 
-The ImageFolder dataset is recommended only if you want to iterate through your images with their single label. For example, when training classic image classification.
+As part of implementing the dataset, there are two methods that must be defined:
 
-### Step 1: Prepare Images
-Arrange your image folder structure as follows:
+- `Record get(NDManager manager, long index)` - Returns the record (both input data and output label) for a particular index
+- `long availableSize()` - Returns the number of records in the dataset
 
-```
-dataset_root/shoes/Aerobic Shoes1.png
-dataset_root/shoes/Aerobic Shose2.png
-...
-dataset_root/boots/Black Boots.png
-dataset_root/boots/White Boots.png
-...
-dataset_root/pumps/Red Pumps
-dataset_root/pumps/Pink Pumps
-...
-```
-
-The dataset will take the folder name e.g. `boots`, `pumps`, `shoes` in sorted order as your labels
-**Note:** Nested folder structures are not currently supported
-
-### Step 2: Use the Dataset
-Add the following code snippet to your project to use the ImageFolder dataset.
+In addition, the dataset should also have a nested builder class to contain details on how to load the dataset.
+The builder would extend `RandomAccessDataset.BaseBuilder`.
+This provides an avenue to modify how RandomAccessDataset loads the data.
+You can also add your own options into the builder.
+For an example of how this would look like, see [`ImageFolder.Builder`](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset/cv/classification/ImageFolder.java).
 
-```java
-// set the image folder path
-Repository repository = Repository.newInstance("folder", Paths.get("/imagefolder");
-ImageFolder dataset =
-    new ImageFolder.Builder()
-        .setRepository(repository)
-        .addTransform(new Resize(100, 100))
-        .addTransform(new ToTensor())
-        .setSampling(batchSize, true)
-        .build();
-// call prepare before using
-dataset.prepare();
-
-// to get the synset
-List<String> synset = dataset.getSynset();
-```
+You can also view this example of creating a [new CSV dataset](example_dataset.md).
 
-Typically, you would add pre-processing pipelines like Resize(), in order to batchify the dataset, and ToTensor(), which converts the image NDArray to Tensor NDArray.
+Many of the abstract dataset helpers above also extend `RandomAccessDataset`.
+When using them, most of the same information applies.
+You may be asked to implement slightly different methods depending on the particular extended class.
+You will also want to extend that classes `BaseBuilder` instead of the one found in `RandomAccessDataset` to get the additional data loading options from the helper.
 
-## How to create a custom dataset
-
-If the provided Datasets don't meet your requirements, you can also easily extend our dataset to create your own customized dataset.
-
-Let's take CSVDataset, which can load a csv file, for example.
-
-### Step 1: Prerequisites
-For this example, we'll use [malicious_url_data.csv](https://github.com/incertum/cyber-matrix-ai/blob/master/Malicious-URL-Detection-Deep-Learning/data/url_data_mega_deep_learning.csv).
-
-The CSV file has the following format.
-
-| URL      | isMalicious |
-| ----------- | ----------- |
-| sample.url.good.com | 0 |
-| sample.url.bad.com | 1  |
-
-We'll also use the 3rd party [Apache Commons](https://commons.apache.org/) library to read the CSV file. To use the library, include the following dependency:
-
-```
-api group: 'org.apache.commons', name: 'commons-csv', version: '1.7'
-```
-
-### Step 2: Implementation
-In order to extend the dataset, the following dependencies are required:
-
-```
-api "ai.djl:api:0.17.0"
-api "ai.djl:basicdataset:0.17.0"
-```
-
-There are four parts we need to implement for CSVDataset.
-
-1. Constructor and Builder
-
-First, we need a private field that holds the CSVRecord list from the csv file.
-We create a constructor and pass the CSVRecord list from builder to the class field.
-For builder, we have all we need in `BaseBuilder` so we only need to include the two minimal methods as shown.
-In the *build()* method, we take advantage of CSVParser to get the record of each CSV file and put them in CSVRecord list.
-
-```java
-public class CSVDataset extends RandomAccessDataset {
-
-    private final List<CSVRecord> csvRecords;
-
-    private CSVDataset(Builder builder) {
-        super(builder);
-        csvRecords = builder.csvRecords;
-    }
-    ...
-    public static final class Builder extends BaseBuilder<Builder> {
-        List<CSVRecord> csvRecords;
-
-        @Override
-        protected Builder self() {
-            return this;
-        }
-
-        CSVDataset build() throws IOException {
-            String csvFilePath = "path/malicious_url_data.csv";
-            try (Reader reader = Files.newBufferedReader(Paths.get(csvFilePath));
-                 CSVParser csvParser =
-                    new CSVParser(
-                        reader,
-                        CSVFormat.DEFAULT
-                            .withHeader("url", "isMalicious")
-                            .withFirstRecordAsHeader()
-                            .withIgnoreHeaderCase()
-                            .withTrim())) {
-                 csvRecords = csvParser.getRecords();
-            }
-            return new CSVDataset(this);
-        }
-    }
-
-}
-```
-
-2. Prepare
-
-As mentioned, in this example we are taking advantage of CSVParser to prepare the data for us. To prepare
-the data on our own, we use the `prepare()` method. Normally here we would load or create any data 
-for our dataset and then save it in one of the private fields previously created. This `prepare()` method
-is called everytime we call `getData()` so in every case we want to only load the data once, we use a 
-boolean variable called `prepared` to check if it has previously been loaded or prepared.
-
-Since we don't have to prepare any data on our own for this example, we only have to override it.
-
-```java
-@Override
-public void prepare(Progress progress) {}
-```
-
-There are great [examples](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset/nlp/AmazonReview.java)
-in our [basicdataset](https://github.com/deepjavalibrary/djl/blob/master/basicdataset/src/main/java/ai/djl/basicdataset)
-folder that show use cases for `prepare()`.
-
-
-
-3. Getter
-
-The getter returns a Record object which contains encoded inputs and labels.
-Here, we use simple encoding to transform the url String to an int array and create a NDArray on top of it.
-The reason why we use NDList here is that you might have multiple inputs and labels in different tasks.
-
-```java
-@Override
-public Record get(NDManager manager, long index) {
-    // get a CSVRecord given an index
-    CSVRecord record = csvRecords.get(Math.toIntExact(index));
-    NDArray datum = manager.create(encode(record.get("url")));
-    NDArray label = manager.create(Float.parseFloat(record.get("isMalicious")));
-    return new Record(new NDList(datum), new NDList(label));
-}
-```
-
-4. Size
-
-The number of records available to be read in this Dataset.
-Here, we can directly use the size of the List<CSVRecord>.
-
-```java
-@Override
-public long availableSize() {
-    return csvRecords.size();
-}
-```
-
-Done!
-Now, you can use the CSVDataset with the following code snippet:
-
-```java
-CSVDataset dataset = new CSVDataset.Builder().setSampling(batchSize, false).build();
-for (Batch batch : dataset.getData(model.getNDManager())) {
-    // use head to get first NDArray
-    batch.getData().head();
-    batch.getLabels().head();
-    ...
-    // don't forget to close the batch in the end
-    batch.close();
-}
-```
-
-Full example code could be found in [CSVDataset.java](https://github.com/deepjavalibrary/djl/blob/master/docs/development/CSVDataset.java).
+If you create a new dataset for public dataset, consider contributing that dataset back to DJL for others to use.
+You can follow [these instructions](add_dataset_to_djl.md) for adding it.
\ No newline at end of file
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 041ae3d3e..026ccb953 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -54,6 +54,7 @@ nav:
           - 'docs/development/troubleshooting.md'
           - 'docs/development/dependency_management.md'
           - 'docs/development/add_model_to_model-zoo.md'
+          - 'docs/development/add_dataset_to_djl.md'
       - 'docs/roadmap.md'
       - 'docs/faq.md'
   - Tutorials:
@@ -66,7 +67,7 @@ nav:
       - 'jupyter/transfer_learning_on_cifar10.ipynb'
       - Load your own BERT:
           - BERT with MXNet: 'jupyter/mxnet/load_your_own_mxnet_bert.ipynb'
-          - BERT with PyTorch: 'jupyter/pytorch/load_your_own_pytorch.ipynb'
+          - BERT with PyTorch: 'jupyter/pytorch/load_your_own_pytorch_bert.ipynb'
   - Guides:
       - 'docs/engine.md'
       - Models:
diff --git a/engines/ml/xgboost/build.gradle b/engines/ml/xgboost/build.gradle
index 19affcc3a..9d31691b7 100644
--- a/engines/ml/xgboost/build.gradle
+++ b/engines/ml/xgboost/build.gradle
@@ -1,6 +1,8 @@
 import groovy.xml.QName
 
 group "ai.djl.ml.xgboost"
+boolean isGpu = project.hasProperty("gpu")
+def XGB_FLAVOR = isGpu ? "-gpu" : ""
 
 configurations {
     exclusion
@@ -9,7 +11,7 @@ configurations {
 dependencies {
     api project(":api")
     api "commons-logging:commons-logging:${commons_logging_version}"
-    api("ml.dmlc:xgboost4j_2.12:${xgboost_version}") {
+    api("ml.dmlc:xgboost4j${XGB_FLAVOR}_2.12:${xgboost_version}") {
         // get rid of the unused XGBoost Dependencies
         exclude group: "org.apache.hadoop", module: "hadoop-hdfs"
         exclude group: "org.apache.hadoop", module: "hadoop-common"
@@ -23,9 +25,13 @@ dependencies {
         exclude group: "org.scala-lang", module: "scala-reflect"
         exclude group: "org.scala-lang", module: "scala-library"
     }
+    if (isGpu) {
+        api "ai.rapids:cudf:${rapis_version}:cuda11"
+    }
 
     exclusion project(":api")
     exclusion "commons-logging:commons-logging:${commons_logging_version}"
+    exclusion "ai.rapids:cudf:${rapis_version}:cuda11"
     testImplementation(project(":testing"))
     testImplementation("org.testng:testng:${testng_version}") {
         exclude group: "junit", module: "junit"
@@ -43,6 +49,7 @@ jar {
                         "ml/dmlc/xgboost4j/java/NativeLibLoader*",
                         "ml/dmlc/xgboost4j/java/XGBoost*",
                         "ml/dmlc/xgboost4j/java/util/*",
+                        "ml/dmlc/xgboost4j/gpu/java/*",
                         "ml/dmlc/xgboost4j/LabeledPoint.*",
                         "xgboost4j-version.properties"
             }
@@ -53,6 +60,7 @@ jar {
 publishing {
     publications {
         maven(MavenPublication) {
+            artifactId "${project.name}${XGB_FLAVOR}"
             pom {
                 name = "DJL Engine Adapter for XGBoost"
                 description = "Deep Java Library (DJL) Engine Adapter for XGBoost"
diff --git a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbEngine.java b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbEngine.java
index 9aa03932a..e4a4ebc70 100644
--- a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbEngine.java
+++ b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbEngine.java
@@ -15,9 +15,13 @@ package ai.djl.ml.xgboost;
 import ai.djl.Device;
 import ai.djl.Model;
 import ai.djl.engine.Engine;
+import ai.djl.engine.StandardCapabilities;
 import ai.djl.ndarray.NDManager;
 import ai.djl.nn.SymbolBlock;
 import ai.djl.training.GradientCollector;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
 import ml.dmlc.xgboost4j.java.JniUtils;
 
 /**
@@ -71,12 +75,27 @@ public final class XgbEngine extends Engine {
     /** {@inheritDoc} */
     @Override
     public String getVersion() {
-        return "1.3.1";
+        try (InputStream is =
+                XgbEngine.class.getResourceAsStream("/xgboost4j-version.properties")) {
+            Properties prop = new Properties();
+            prop.load(is);
+            return prop.getProperty("version");
+        } catch (IOException e) {
+            throw new AssertionError("Failed to load xgboost4j-version.properties", e);
+        }
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean hasCapability(String capability) {
+        if (StandardCapabilities.CUDA.equals(capability)) {
+            try {
+                Class.forName("ml.dmlc.xgboost4j.gpu.java.CudfColumn");
+                return true;
+            } catch (ClassNotFoundException ignore) {
+                return false;
+            }
+        }
         return false;
     }
 
diff --git a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
index 87b6af06a..00b5133e2 100644
--- a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
+++ b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
@@ -20,11 +20,14 @@ import ai.djl.ndarray.NDManager;
 import ai.djl.ndarray.types.DataType;
 import ai.djl.ndarray.types.Shape;
 import ai.djl.ndarray.types.SparseFormat;
+import ai.djl.util.JsonUtils;
+import com.google.gson.JsonArray;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.util.Arrays;
+import ml.dmlc.xgboost4j.java.ColumnBatch;
 import ml.dmlc.xgboost4j.java.JniUtils;
 
 /** {@code XgbNDManager} is the XGBoost implementation of {@link NDManager}. */
@@ -80,6 +83,30 @@ public class XgbNDManager extends BaseNDManager {
         return Engine.getEngine(XgbEngine.ENGINE_NAME);
     }
 
+    /**
+     * Creates {@link XgbNDArray} from column array interface.
+     *
+     * @param columnBatch – the XGBoost ColumnBatch to provide the cuda array interface of feature
+     *     columns
+     * @param missing – missing value
+     * @param nthread – threads number
+     * @return a new instance of {@link NDArray}
+     */
+    public NDArray create(ColumnBatch columnBatch, float missing, int nthread) {
+        columnBatch.getFeatureArrayInterface();
+        String json = columnBatch.getFeatureArrayInterface();
+        JsonArray array = JsonUtils.GSON.fromJson(json, JsonArray.class);
+        JsonArray shapeJson = array.get(0).getAsJsonObject().get("shape").getAsJsonArray();
+        long[] shapes = new long[shapeJson.size()];
+        for (int i = 0; i < shapes.length; ++i) {
+            shapes[i] = shapeJson.get(i).getAsLong();
+        }
+
+        Shape shape = new Shape(shapes);
+        long handle = JniUtils.createDMatrix(columnBatch, missing, nthread);
+        return new XgbNDArray(this, alternativeManager, handle, shape, SparseFormat.DENSE);
+    }
+
     /** {@inheritDoc} */
     @Override
     public NDArray create(Buffer data, Shape shape, DataType dataType) {
diff --git a/engines/ml/xgboost/src/main/java/ml/dmlc/xgboost4j/java/JniUtils.java b/engines/ml/xgboost/src/main/java/ml/dmlc/xgboost4j/java/JniUtils.java
index ce63ad8bf..e660150ac 100644
--- a/engines/ml/xgboost/src/main/java/ml/dmlc/xgboost4j/java/JniUtils.java
+++ b/engines/ml/xgboost/src/main/java/ml/dmlc/xgboost4j/java/JniUtils.java
@@ -52,6 +52,19 @@ public final class JniUtils {
         return handles[0];
     }
 
+    public static long createDMatrix(ColumnBatch columnBatch, float missing, int nthread) {
+        long[] handles = new long[1];
+        String json = columnBatch.getFeatureArrayInterface();
+        if (json == null || json.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Expecting non-empty feature columns' array interface");
+        }
+        checkCall(
+                XGBoostJNI.XGDMatrixCreateFromArrayInterfaceColumns(
+                        json, missing, nthread, handles));
+        return handles[0];
+    }
+
     public static long createDMatrixCSR(long[] indptr, int[] indices, float[] array) {
         long[] handles = new long[1];
         checkCall(XGBoostJNI.XGDMatrixCreateFromCSREx(indptr, indices, array, 0, handles));
diff --git a/engines/mxnet/mxnet-engine/README.md b/engines/mxnet/mxnet-engine/README.md
index 50d1e4df1..5713b8712 100644
--- a/engines/mxnet/mxnet-engine/README.md
+++ b/engines/mxnet/mxnet-engine/README.md
@@ -41,7 +41,10 @@ You can pull the MXNet engine from the central Maven repository by including the
 By default, DJL will download the Apache MXNet native libraries into [cache folder](../../../docs/development/cache_management.md) the first time you run DJL.
 It will automatically determine the appropriate jars for your system based on the platform and GPU support.
 
-You can choose a native library based on your platform if you don't have network access at runtime.
+If you do not want to rely on the download because you don't have network access at runtime or for other reasons, there are additional options.
+The easiest option is to add a DJL native library package to your project dependencies.
+The available packages for your platform can be found below.
+Finally, you can also specify the path to a valid MXNet build using the `MXNET_LIBRARY_PATH` environment variable.
 
 ### macOS
 
@@ -116,15 +119,14 @@ Apache MXNet requires Visual C++ Redistributable Packages. If you encounter an U
 DJL on Windows, please download and install
 [Visual C++ 2019 Redistributable Packages](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) and reboot.
 
-For the Windows platform, you can use CPU package. MXNet windows GPU native
-library size are large, we no longer provide GPU package, instead you have to
-use the Automatic package.
+For the windows platform, we support both CPU and GPU.
+The CPU can be found using either the automatic runtime detection or through adding the CPU jar to your dependencies.
+However, due to the size of the windows GPU native library, we do not offer GPU support through a dependency jar.
+You can still access GPU on windows by using the [automatic runtime download](#Installation).
 
 #### Windows GPU
 
-- ai.djl.mxnet:mxnet-native-auto:1.8.0
-
-    This package supports CUDA 11.0 and CUDA 10.2 for Windows.
+This package supports CUDA 11.0 and CUDA 10.2 for Windows.
 
 ### Windows CPU
 
diff --git a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
index 66fe02d5d..fc68b4b73 100644
--- a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
+++ b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
@@ -315,7 +315,9 @@ public class MxNDArray extends NativeResource<Pointer> implements LazyNDArray {
     /** {@inheritDoc} */
     @Override
     public NDArray take(NDArray index) {
-        throw new UnsupportedOperationException("Not implemented yet.");
+        MxOpParams params = new MxOpParams();
+        params.add("mode", "wrap");
+        return manager.invoke("take", new NDList(this.flatten(), index), params).singletonOrThrow();
     }
 
     /** {@inheritDoc} */
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/build.gradle b/engines/paddlepaddle/paddlepaddle-model-zoo/build.gradle
index d9ff67d4f..e18f1e7ed 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/build.gradle
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/build.gradle
@@ -3,7 +3,7 @@ group 'ai.djl.paddlepaddle'
 
 dependencies {
     api project(":engines:paddlepaddle:paddlepaddle-engine")
-
+    api project(":extensions:opencv")
     testImplementation("org.testng:testng:${testng_version}") {
         exclude group: "junit", module: "junit"
     }
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslator.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslator.java
index 2f7ee2198..2312ef7e3 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslator.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslator.java
@@ -13,20 +13,25 @@
 package ai.djl.paddlepaddle.zoo.cv.objectdetection;
 
 import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.ImageFactory;
 import ai.djl.modality.cv.output.BoundingBox;
 import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.output.Rectangle;
 import ai.djl.modality.cv.util.NDImageUtils;
 import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDList;
 import ai.djl.ndarray.types.DataType;
 import ai.djl.ndarray.types.Shape;
+import ai.djl.opencv.OpenCVImageFactory;
 import ai.djl.translate.ArgumentsUtil;
 import ai.djl.translate.NoBatchifyTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorContext;
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
 /**
@@ -48,18 +53,37 @@ public class PpWordDetectionTranslator implements NoBatchifyTranslator<Image, De
 
     /** {@inheritDoc} */
     @Override
-    public DetectedObjects processOutput(TranslatorContext ctx, NDList list) {
+    public DetectedObjects processOutput(TranslatorContext ctx, NDList list)
+            throws FileNotFoundException {
         NDArray result = list.singletonOrThrow();
-        result = result.squeeze().mul(255f).toType(DataType.UINT8, true).neq(0);
-        boolean[] flattened = result.toBooleanArray();
-        Shape shape = result.getShape();
-        int w = (int) shape.get(0);
-        int h = (int) shape.get(1);
-        boolean[][] grid = new boolean[w][h];
-        IntStream.range(0, flattened.length)
-                .parallel()
-                .forEach(i -> grid[i / h][i % h] = flattened[i]);
-        List<BoundingBox> boxes = new BoundFinder(grid).getBoxes();
+        ImageFactory factory = ImageFactory.getInstance();
+        List<BoundingBox> boxes;
+        // faster mechanism
+        if (factory instanceof OpenCVImageFactory) {
+            result = result.squeeze(0).toType(DataType.UINT8, true);
+            Image image = factory.fromNDArray(result);
+            boxes =
+                    image.findBoundingBoxes()
+                            .parallelStream()
+                            .filter(
+                                    box -> {
+                                        Rectangle rect = (Rectangle) box;
+                                        return rect.getWidth() * image.getWidth() > 5
+                                                || rect.getHeight() * image.getHeight() > 5;
+                                    })
+                            .collect(Collectors.toList());
+        } else {
+            result = result.squeeze().mul(255f).toType(DataType.UINT8, true).neq(0);
+            boolean[] flattened = result.toBooleanArray();
+            Shape shape = result.getShape();
+            int w = (int) shape.get(0);
+            int h = (int) shape.get(1);
+            boolean[][] grid = new boolean[w][h];
+            IntStream.range(0, flattened.length)
+                    .parallel()
+                    .forEach(i -> grid[i / h][i % h] = flattened[i]);
+            boxes = new BoundFinder(grid).getBoxes();
+        }
         List<String> names = new ArrayList<>();
         List<Double> probs = new ArrayList<>();
         int boxSize = boxes.size();
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 a8944cd92..43501db4e 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
@@ -301,7 +301,7 @@ public class PtNDArray extends NativeResource<Long> implements NDArray {
             // Result is flattened since shape is undetermined
             return JniUtils.booleanMask(this, manager.from(index));
         } else if (indexShape.equals(getShape().slice(axis))) {
-            // index will be broadcasted by default
+            // index will be broadcast by default
             try (PtNDArray flattedResult = JniUtils.booleanMask(this, manager.from(index))) {
                 // Shape recovery
                 Shape remainder = getShape().slice(0, axis);
diff --git a/engines/tensorflow/tensorflow-engine/README.md b/engines/tensorflow/tensorflow-engine/README.md
index 3ab09d45a..ac084cc5c 100644
--- a/engines/tensorflow/tensorflow-engine/README.md
+++ b/engines/tensorflow/tensorflow-engine/README.md
@@ -46,14 +46,14 @@ You can choose a native library based on your platform if you don't have network
 ### macOS
 For macOS, you can use the following library:
 
-- ai.djl.tensorflow:tensorflow-native-cpu:2.4.1:osx-x86_64
+- ai.djl.tensorflow:tensorflow-native-cpu:2.7.0:osx-x86_64
 
 ```xml
 <dependency>
     <groupId>ai.djl.tensorflow</groupId>
     <artifactId>tensorflow-native-cpu</artifactId>
     <classifier>osx-x86_64</classifier>
-    <version>2.4.1</version>
+    <version>2.7.0</version>
     <scope>runtime</scope>
 </dependency>
 ```
@@ -64,21 +64,21 @@ installed on your GPU machine, you can use one of the following library:
 
 #### Linux GPU
 
-- ai.djl.tensorflow:tensorflow-native-cu110:2.4.1:linux-x86_64 - CUDA 11.0
+- ai.djl.tensorflow:tensorflow-native-cu110:2.7.0:linux-x86_64 - CUDA 11.3
 
 ```xml
 <dependency>
     <groupId>ai.djl.tensorflow</groupId>
-    <artifactId>tensorflow-native-cu110</artifactId>
+    <artifactId>tensorflow-native-cu113</artifactId>
     <classifier>linux-x86_64</classifier>
-    <version>2.4.1</version>
+    <version>2.7.0</version>
     <scope>runtime</scope>
 </dependency>
 ```
 
 ### Linux CPU
 
-- ai.djl.tensorflow:tensorflow-native-cpu:2.4.1:linux-x86_64
+- ai.djl.tensorflow:tensorflow-native-cpu:2.7.0:linux-x86_64
 
 ```xml
 <dependency>
@@ -86,7 +86,7 @@ installed on your GPU machine, you can use one of the following library:
     <artifactId>tensorflow-native-cpu</artifactId>
     <classifier>linux-x86_64</classifier>
     <scope>runtime</scope>
-    <version>2.4.1</version>
+    <version>2.7.0</version>
 </dependency>
 ```
 
@@ -96,21 +96,21 @@ For the Windows platform, you can choose between CPU and GPU.
 
 #### Windows GPU
 
-- ai.djl.tensorflow:tensorflow-native-cu110:2.4.1:win-x86_64
+- ai.djl.tensorflow:tensorflow-native-cu113:2.7.0:win-x86_64
 
 ```xml
 <dependency>
     <groupId>ai.djl.tensorflow</groupId>
-    <artifactId>tensorflow-native-cu110</artifactId>
+    <artifactId>tensorflow-native-cu113</artifactId>
     <classifier>win-x86_64</classifier>
-    <version>2.4.1</version>
+    <version>2.7.0</version>
     <scope>runtime</scope>
 </dependency>
 ```
 
 ### Windows CPU
 
-- ai.djl.tensorflow:tensorflow-native-cpu:2.4.1:win-x86_64
+- ai.djl.tensorflow:tensorflow-native-cpu:2.7.0:win-x86_64
 
 ```xml
 <dependency>
@@ -118,6 +118,6 @@ For the Windows platform, you can choose between CPU and GPU.
     <artifactId>tensorflow-native-cpu</artifactId>
     <classifier>win-x86_64</classifier>
     <scope>runtime</scope>
-    <version>2.4.1</version>
+    <version>2.7.0</version>
 </dependency>
 ```
diff --git a/examples/docs/action_recognition.md b/examples/docs/action_recognition.md
index a3f43cace..224d7df62 100644
--- a/examples/docs/action_recognition.md
+++ b/examples/docs/action_recognition.md
@@ -2,7 +2,7 @@
 
 Action recognition is a computer vision technique to infer human actions (present state) in images or videos.
 
-In this example, you learn how to implement inference code with a [ModelZoo model](../../docs/model-zoo.md) to detect dogs in an image.
+In this example, you learn how to implement inference code with a [ModelZoo model](../../docs/model-zoo.md) to detect human actions in an image.
 
 The source code can be found at [ActionRecognition.java](https://github.com/deepjavalibrary/djl/blob/master/examples/src/main/java/ai/djl/examples/inference/ActionRecognition.java).
 
diff --git a/extensions/audio/build.gradle b/extensions/audio/build.gradle
new file mode 100644
index 000000000..8b6d60530
--- /dev/null
+++ b/extensions/audio/build.gradle
@@ -0,0 +1,46 @@
+group 'ai.djl.audio'
+
+dependencies {
+    api project(":api")
+    api project(":basicdataset")
+    api ("org.bytedeco:javacv:1.5.7") {
+        exclude group: "org.bytedeco", module: "opencv"
+        exclude group: "org.bytedeco", module: "openblas"
+        exclude group: "org.bytedeco", module: "flycapture"
+        exclude group: "org.bytedeco", module: "libdc1394"
+        exclude group: "org.bytedeco", module: "libfreenect"
+        exclude group: "org.bytedeco", module: "libfreenect2"
+        exclude group: "org.bytedeco", module: "librealsense"
+        exclude group: "org.bytedeco", module: "librealsense2"
+        exclude group: "org.bytedeco", module: "videoinput"
+        exclude group: "org.bytedeco", module: "artoolkitplus"
+        exclude group: "org.bytedeco", module: "flandmark"
+        exclude group: "org.bytedeco", module: "leptonica"
+        exclude group: "org.bytedeco", module: "tesseract"
+        exclude group: "org.bytedeco", module: "tesseract"
+    }
+    api "com.github.wendykierp:JTransforms:3.1"
+
+    runtimeOnly "org.bytedeco:ffmpeg:5.0-1.5.7:macosx-x86_64"
+    runtimeOnly "org.bytedeco:ffmpeg:5.0-1.5.7:linux-x86_64"
+    runtimeOnly "org.bytedeco:ffmpeg:5.0-1.5.7:windows-x86_64"
+
+    testImplementation("org.testng:testng:${testng_version}") {
+        exclude group: "junit", module: "junit"
+    }
+    testImplementation project(":testing")
+    testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_slf4j_version}"
+    testRuntimeOnly project(":engines:pytorch:pytorch-engine")
+}
+
+publishing {
+    publications {
+        maven(MavenPublication) {
+            pom {
+                name = "DJL Audio processing extension"
+                description = "DJL Audio processing extension"
+                url = "http://www.djl.ai/extensions/${project.name}"
+            }
+        }
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/AudioUtils.java b/extensions/audio/src/main/java/ai/djl/audio/AudioUtils.java
new file mode 100644
index 000000000..e2a2bcb7d
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/AudioUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio;
+
+import ai.djl.ndarray.NDArray;
+
+/** Useful utils for audio process. */
+public final class AudioUtils {
+
+    private AudioUtils() {}
+
+    /**
+     * Calculate root mean square energy in decibels.
+     *
+     * @param samples input signal, should be 1 dimension array
+     * @return root mean square energy
+     */
+    public static float rmsDb(NDArray samples) {
+        samples = samples.pow(2).mean().log10().mul(10);
+        return samples.toFloatArray()[0];
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/dataset/AudioData.java b/extensions/audio/src/main/java/ai/djl/audio/dataset/AudioData.java
new file mode 100644
index 000000000..bf8874e7d
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/dataset/AudioData.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+package ai.djl.audio.dataset;
+
+import ai.djl.audio.processor.AudioNormalizer;
+import ai.djl.audio.processor.AudioProcessor;
+import ai.djl.audio.processor.LinearSpecgram;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDManager;
+import ai.djl.repository.LocalRepository;
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.FrameGrabber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link AudioData} is a utility for managing audio data within a {@link
+ * ai.djl.training.dataset.Dataset}. It contains some basic information of an audio file and provide
+ * some method to preprocess the original audio file. Since storing all the data into the memory is
+ * impossible, this class will only store the path of the original audio data.
+ *
+ * <p>This class provides a list of {@link AudioProcessor} for user to featurize the data.
+ *
+ * <p>See {@link SpeechRecognitionDataset} for an example.
+ */
+public class AudioData {
+
+    private static final Logger logger = LoggerFactory.getLogger(LocalRepository.class);
+
+    private int sampleRate;
+    private int audioChannels;
+
+    private List<AudioProcessor> processorList;
+    private List<String> audioPaths;
+
+    /**
+     * Constructs a new {@link AudioData}.
+     *
+     * @param configuration the configuration for the {@link AudioData}.
+     */
+    public AudioData(Configuration configuration) {
+        this.sampleRate = configuration.sampleRate;
+        this.processorList = configuration.processorList;
+    }
+
+    /**
+     * Returns a good default {@link AudioData.Configuration} to use for the constructor with
+     * defaults.
+     *
+     * @return a good default {@link AudioData.Configuration} to use for the constructor with
+     *     defaults.
+     */
+    public static AudioData.Configuration getDefaultConfiguration() {
+        float targetDb = -20f;
+        float strideMs = 10f;
+        float windowsMs = 20f;
+        int sampleRate = 16000;
+        List<AudioProcessor> defaultProcessors =
+                Arrays.asList(
+                        new AudioNormalizer(targetDb),
+                        new LinearSpecgram(strideMs, windowsMs, sampleRate));
+        return new AudioData.Configuration()
+                .setProcessorList(defaultProcessors)
+                .setSampleRate(sampleRate);
+    }
+
+    /**
+     * This method is used for decoding the original audio data and converting it to a float array.
+     *
+     * @param path The path of the original audio data.
+     * @return A float array.
+     */
+    private float[] toFloat(String path) {
+        List<Float> list = new ArrayList<>();
+        float scale = (float) 1.0 / (float) (1 << (8 * 2) - 1);
+        try (FFmpegFrameGrabber audioGrabber = new FFmpegFrameGrabber(path)) {
+            audioGrabber.start();
+            audioChannels = audioGrabber.getAudioChannels();
+            audioGrabber.setSampleRate(sampleRate);
+            Frame frame;
+            while ((frame = audioGrabber.grabFrame()) != null) {
+                Buffer[] buffers = frame.samples;
+                ShortBuffer sb = (ShortBuffer) buffers[0];
+                for (int i = 0; i < sb.limit(); i++) {
+                    list.add(sb.get() * scale);
+                }
+            }
+        } catch (FrameGrabber.Exception e) {
+            logger.error(e.getMessage());
+        }
+        float[] floatArray = new float[list.size()];
+        int i = 0;
+        for (Float f : list) {
+            floatArray[i++] = (f != null ? f : Float.NaN);
+        }
+        return floatArray;
+    }
+
+    /**
+     * This method will use a list of {@link AudioProcessor} as featurizer to process the float
+     * array.
+     *
+     * @param manager the manager for converting the float array to {@link NDArray}.
+     * @param index The index of path of the original audio data.
+     * @return An {@link NDArray} that represent the processed audio data.
+     */
+    public NDArray getPreprocessedData(NDManager manager, int index) {
+        float[] floatArray = toFloat(audioPaths.get(index));
+        NDArray samples = manager.create(floatArray);
+        for (AudioProcessor processor : processorList) {
+            samples = processor.extractFeatures(manager, samples);
+        }
+        return samples;
+    }
+
+    /**
+     * Get the number of channels of an audio file.
+     *
+     * @return The number of channels of an audio file.
+     */
+    public int getAudioChannels() {
+        return audioChannels;
+    }
+
+    /**
+     * Get the sample rate used by {@link FFmpegFrameGrabber} when sampling the audio file.
+     *
+     * @return The sample rate used by {@link FFmpegFrameGrabber} when sampling the audio file.
+     */
+    public int getSampleRate() {
+        return sampleRate;
+    }
+
+    /**
+     * Set the path list of original audio data.
+     *
+     * @param audioPaths The path list of original audio data.
+     */
+    public void setAudioPaths(List<String> audioPaths) {
+        this.audioPaths = audioPaths;
+    }
+
+    /**
+     * Get the original audio path.
+     *
+     * @return The original audio path.
+     */
+    public List<String> getAudioPaths() {
+        return audioPaths;
+    }
+
+    /**
+     * Get the total number of audio data in the dataset.
+     *
+     * @return The total number of audio data in the dataset.
+     */
+    public int getTotalSize() {
+        return audioPaths.size();
+    }
+
+    /**
+     * The configuration for creating a {@link AudioData} value in a {@link
+     * ai.djl.audio.dataset.AudioData}.
+     */
+    public static final class Configuration {
+
+        private int sampleRate;
+
+        private List<AudioProcessor> processorList;
+
+        /**
+         * Set the list of processor which are used for extracting features from audio data.
+         *
+         * @param processorList The list of processor which are used for extracting features from
+         *     audio data.
+         * @return this configuration.
+         */
+        public Configuration setProcessorList(List<AudioProcessor> processorList) {
+            this.processorList = processorList;
+            return this;
+        }
+
+        /**
+         * Set the sampleRate for {@link FFmpegFrameGrabber} to use.
+         *
+         * @param sampleRate The sampleRate for {@link FFmpegFrameGrabber} to use.
+         * @return this configuration.
+         */
+        public Configuration setSampleRate(int sampleRate) {
+            this.sampleRate = sampleRate;
+            return this;
+        }
+
+        /**
+         * Updates this {@link AudioData.Configuration} with the non-null values from another
+         * configuration.
+         *
+         * @param other the other configuration to use to update this
+         * @return this configuration after updating
+         */
+        public AudioData.Configuration update(AudioData.Configuration other) {
+            processorList = other.processorList;
+            return this;
+        }
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/dataset/Librispeech.java b/extensions/audio/src/main/java/ai/djl/audio/dataset/Librispeech.java
new file mode 100644
index 000000000..9dd24aec0
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/dataset/Librispeech.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.dataset;
+
+import ai.djl.Application;
+import ai.djl.basicdataset.BasicDatasets;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.repository.Artifact;
+import ai.djl.repository.MRL;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.dataset.Record;
+import ai.djl.translate.TranslateException;
+import ai.djl.util.Progress;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LibriSpeech is a corpus of approximately 1000 hours of 16kHz read English speech, prepared by
+ * Vassil Panayotov with the assistance of Daniel Povey. The data is derived from read audiobooks
+ * from the LibriVox project, and has been carefully segmented and aligned.
+ */
+public class Librispeech extends SpeechRecognitionDataset {
+
+    private static final String VERSION = "1.0";
+    private static final String ARTIFACT_ID = "librispeech";
+
+    /**
+     * Creates a new instance of {@link SpeechRecognitionDataset} with the given necessary
+     * configurations.
+     *
+     * @param builder a builder with the necessary configurations
+     */
+    public Librispeech(Builder builder) {
+        super(builder);
+        this.usage = builder.usage;
+        this.mrl = builder.getMrl();
+    }
+
+    /**
+     * Creates a builder to build a {@link Librispeech}.
+     *
+     * @return a new {@link Librispeech.Builder} object
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Prepares the dataset for use with tracked progress.
+     *
+     * @param progress the progress tracker
+     * @throws IOException for various exceptions depending on the dataset
+     */
+    @Override
+    public void prepare(Progress progress) throws IOException, TranslateException {
+        if (prepared) {
+            return;
+        }
+        Artifact artifact = mrl.getDefaultArtifact();
+        mrl.prepare(artifact, progress);
+        Artifact.Item item;
+        String subPath;
+        switch (usage) {
+            case TRAIN:
+                item = artifact.getFiles().get("train");
+                subPath = "LibriSpeech/test-clean-100";
+                break;
+            case TEST:
+                item = artifact.getFiles().get("test");
+                subPath = "LibriSpeech/test-clean";
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported usage type.");
+        }
+        File mainDir = mrl.getRepository().getFile(item, subPath).toFile();
+        File[] subDirs = mainDir.listFiles();
+        if (subDirs == null) {
+            return;
+        }
+        List<String> lineArray = new ArrayList<>();
+        List<String> audioPaths = new ArrayList<>();
+        for (File subDir : subDirs) {
+            File[] subSubDirs = subDir.listFiles();
+            String subDirName = subDir.getName();
+            if (subSubDirs == null) {
+                return;
+            }
+            for (File subSubDir : subSubDirs) {
+                String subSubDirName = subSubDir.getName();
+                File transFile =
+                        new File(
+                                String.format(
+                                        "%s/%s-%s.trans.txt",
+                                        subSubDir.getAbsolutePath(), subDirName, subSubDirName));
+                try (BufferedReader reader = Files.newBufferedReader(transFile.toPath())) {
+                    String row;
+                    while ((row = reader.readLine()) != null) {
+                        if (row.contains(" ")) {
+                            String trans = row.substring(row.indexOf(' ') + 1);
+                            String label = row.substring(0, row.indexOf(' '));
+                            String audioIndex = label.split("-")[2];
+                            String audioPath =
+                                    String.format(
+                                            "%s/%s-%s-%s.flac",
+                                            subSubDir.getAbsolutePath(),
+                                            subDirName,
+                                            subSubDirName,
+                                            audioIndex);
+                            audioPaths.add(audioPath);
+                            lineArray.add(trans);
+                        }
+                    }
+                }
+            }
+        }
+        targetPreprocess(lineArray);
+        sourcePreprocess(audioPaths);
+        prepared = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Record get(NDManager manager, long index) {
+        NDList data = new NDList();
+        NDList labels = new NDList();
+        data.add(sourceAudioData.getPreprocessedData(manager, (int) index));
+        labels.add(targetTextData.getEmbedding(manager, index));
+        return new Record(data, labels);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected long availableSize() {
+        return sourceAudioData.getTotalSize();
+    }
+
+    /** A builder to construct a {@link Librispeech} . */
+    public static class Builder extends AudioBuilder<Librispeech.Builder> {
+
+        /** Constructs a new builder. */
+        public Builder() {
+            repository = BasicDatasets.REPOSITORY;
+            groupId = BasicDatasets.GROUP_ID;
+            artifactId = ARTIFACT_ID;
+            usage = Dataset.Usage.TRAIN;
+        }
+
+        /**
+         * Builds a new {@link Librispeech} object.
+         *
+         * @return the new {@link Librispeech} object
+         */
+        public Librispeech build() {
+            return new Librispeech(this);
+        }
+
+        /**
+         * Builds a new {@link Librispeech} object.
+         *
+         * @return the new {@link Librispeech} object
+         */
+        MRL getMrl() {
+            return repository.dataset(Application.Audio.ANY, groupId, artifactId, VERSION);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected Librispeech.Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/dataset/SpeechRecognitionDataset.java b/extensions/audio/src/main/java/ai/djl/audio/dataset/SpeechRecognitionDataset.java
new file mode 100644
index 000000000..29eefefff
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/dataset/SpeechRecognitionDataset.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.dataset;
+
+import ai.djl.basicdataset.BasicDatasets;
+import ai.djl.basicdataset.utils.TextData;
+import ai.djl.engine.Engine;
+import ai.djl.modality.nlp.Vocabulary;
+import ai.djl.modality.nlp.embedding.EmbeddingException;
+import ai.djl.modality.nlp.embedding.TextEmbedding;
+import ai.djl.modality.nlp.embedding.TrainableWordEmbedding;
+import ai.djl.ndarray.NDManager;
+import ai.djl.repository.MRL;
+import ai.djl.repository.Repository;
+import ai.djl.training.dataset.RandomAccessDataset;
+import java.util.List;
+
+/**
+ * {@code SpeechRecognitionDataset} is an abstract dataset that can be used for datasets for
+ * Automatic Speech Recognition (ASR) where the source data is {@link AudioData} and the target data
+ * is {@link TextData}.
+ *
+ * <p>For the target data, it will create embeddings for the target data. Embeddings can be either
+ * pre-trained or trained on the go. Pre-trained {@link TextEmbedding} must be set in the {@code
+ * TextDataset.Builder}. If no embeddings are set, the dataset creates {@link
+ * TrainableWordEmbedding} based {@link TrainableWordEmbedding} from the {@link Vocabulary} created
+ * within the dataset.
+ *
+ * <p>For the source data, it will use the {@link ai.djl.audio.processor.AudioProcessor} to
+ * featurize data, if users want to write their own featurizer, they can get the original {@code
+ * NDArray} from {@link AudioData} without using any {@link ai.djl.audio.processor.AudioProcessor}.
+ */
+public abstract class SpeechRecognitionDataset extends RandomAccessDataset {
+
+    protected AudioData sourceAudioData;
+    protected TextData targetTextData;
+    protected NDManager manager;
+    protected Usage usage;
+
+    protected MRL mrl;
+    protected boolean prepared;
+
+    /**
+     * Creates a new instance of {@link SpeechRecognitionDataset} with the given necessary
+     * configurations.
+     *
+     * @param builder a builder with the necessary configurations
+     */
+    public SpeechRecognitionDataset(AudioBuilder<?> builder) {
+        super(builder);
+        sourceAudioData = new AudioData(AudioData.getDefaultConfiguration());
+        targetTextData = new TextData(TextData.getDefaultConfiguration());
+        manager = builder.manager;
+        usage = builder.usage;
+    }
+
+    /**
+     * Store and preprocess target text data.
+     *
+     * @param newTextData list of all unprocessed sentences in the dataset.
+     * @throws EmbeddingException if there is an error while embedding input.
+     */
+    protected void targetPreprocess(List<String> newTextData) throws EmbeddingException {
+        TextData textData = targetTextData;
+        textData.preprocess(
+                manager, newTextData.subList(0, (int) Math.min(limit, newTextData.size())));
+    }
+
+    /**
+     * This method is used to set the audio data path.
+     *
+     * @param audioPathList The path list of all original audio data
+     */
+    protected void sourcePreprocess(List<String> audioPathList) {
+        sourceAudioData.setAudioPaths(audioPathList);
+    }
+
+    /** Abstract AudioBuilder that helps build a {@link SpeechRecognitionDataset}. */
+    public abstract static class AudioBuilder<T extends AudioBuilder<T>> extends BaseBuilder<T> {
+
+        protected AudioData.Configuration sourceConfiguration;
+        protected TextData.Configuration targetConfiguration;
+        protected NDManager manager;
+
+        protected Repository repository;
+        protected String groupId;
+        protected Usage usage;
+        protected String artifactId;
+
+        /** Constructs a new builder. */
+        AudioBuilder() {
+            repository = BasicDatasets.REPOSITORY;
+            groupId = BasicDatasets.GROUP_ID;
+            usage = Usage.TRAIN;
+            sourceConfiguration = new AudioData.Configuration();
+            targetConfiguration = new TextData.Configuration();
+            manager = Engine.getInstance().newBaseManager();
+        }
+
+        /**
+         * Sets the {@link AudioData.Configuration} to use for the source text data.
+         *
+         * @param sourceConfiguration the {@link AudioData.Configuration}
+         * @return this builder
+         */
+        public T setSourceConfiguration(AudioData.Configuration sourceConfiguration) {
+            this.sourceConfiguration = sourceConfiguration;
+            return self();
+        }
+
+        /**
+         * Sets the {@link TextData.Configuration} to use for the target text data.
+         *
+         * @param targetConfiguration the {@link TextData.Configuration}
+         * @return this builder
+         */
+        public T setTargetConfiguration(TextData.Configuration targetConfiguration) {
+            this.targetConfiguration = targetConfiguration;
+            return self();
+        }
+
+        /**
+         * Sets the optional manager for the dataset (default follows engine default).
+         *
+         * @param manager the manager
+         * @return this builder
+         */
+        public T optManager(NDManager manager) {
+            this.manager = manager.newSubManager();
+            return self();
+        }
+
+        /**
+         * Sets the optional usage.
+         *
+         * @param usage the usage
+         * @return this builder
+         */
+        public T optUsage(Usage usage) {
+            this.usage = usage;
+            return self();
+        }
+
+        /**
+         * Sets the optional repository.
+         *
+         * @param repository the repository
+         * @return this builder
+         */
+        public T optRepository(Repository repository) {
+            this.repository = repository;
+            return self();
+        }
+
+        /**
+         * Sets optional groupId.
+         *
+         * @param groupId the groupId}
+         * @return this builder
+         */
+        public T optGroupId(String groupId) {
+            this.groupId = groupId;
+            return self();
+        }
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/dataset/package-info.java b/extensions/audio/src/main/java/ai/djl/audio/dataset/package-info.java
new file mode 100644
index 000000000..d2abdd35a
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/dataset/package-info.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+/** Contains useful audio datasets. */
+package ai.djl.audio.dataset;
diff --git a/extensions/audio/src/main/java/ai/djl/audio/package-info.java b/extensions/audio/src/main/java/ai/djl/audio/package-info.java
new file mode 100644
index 000000000..d6322ae65
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/package-info.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+/** Contains audio utils and datasets for djl. */
+package ai.djl.audio;
diff --git a/extensions/audio/src/main/java/ai/djl/audio/processor/AudioNormalizer.java b/extensions/audio/src/main/java/ai/djl/audio/processor/AudioNormalizer.java
new file mode 100644
index 000000000..55c4de4ed
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/processor/AudioNormalizer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.processor;
+
+import ai.djl.audio.AudioUtils;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDManager;
+
+/** Use the mean and standard values to calculate the normalized values for audio signal. */
+public class AudioNormalizer implements AudioProcessor {
+
+    private static final float MAX_GAIN_DB = 300.0f;
+
+    private float targetDb;
+
+    /**
+     * Constructor for {@link AudioNormalizer}.
+     *
+     * @param targetDb target energy in decibels.
+     */
+    public AudioNormalizer(float targetDb) {
+        this.targetDb = targetDb;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDArray extractFeatures(NDManager manager, NDArray samples) {
+        float gain = targetDb - AudioUtils.rmsDb(samples);
+        gain = Math.min(gain, MAX_GAIN_DB);
+
+        float factor = (float) Math.pow(10f, gain / 20f);
+        samples = samples.mul(factor);
+        return samples;
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/processor/AudioProcessor.java b/extensions/audio/src/main/java/ai/djl/audio/processor/AudioProcessor.java
new file mode 100644
index 000000000..5c031b17b
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/processor/AudioProcessor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.processor;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDManager;
+
+/** This interface is used for extracting features from origin audio samples. */
+public interface AudioProcessor {
+
+    /**
+     * Extracts features by the processor.
+     *
+     * @param manager The manager used for extracting features
+     * @param samples The Audio that needs to be extracting features
+     * @return Target feature
+     */
+    NDArray extractFeatures(NDManager manager, NDArray samples);
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/processor/LinearSpecgram.java b/extensions/audio/src/main/java/ai/djl/audio/processor/LinearSpecgram.java
new file mode 100644
index 000000000..cebf991b7
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/processor/LinearSpecgram.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.processor;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDArrays;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import org.jtransforms.fft.FloatFFT_1D;
+
+/** Calculate linear spectrogram by short-time fourier transform. */
+public class LinearSpecgram implements AudioProcessor {
+
+    private static final float EPS = 1e-14f;
+
+    private float strideMs;
+    private float windowsMs;
+    private float sampleRate;
+
+    /**
+     * Calculate linear spectrogram by short-time fourier transform.
+     *
+     * @param strideMs Stride size of window
+     * @param windowsMs Window size
+     * @param sampleRate Sample rate of raw data
+     */
+    public LinearSpecgram(float strideMs, float windowsMs, int sampleRate) {
+        this.strideMs = strideMs;
+        this.windowsMs = windowsMs;
+        this.sampleRate = sampleRate;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDArray extractFeatures(NDManager manager, NDArray samples) {
+        return stft(samples);
+    }
+
+    private NDArray stft(NDArray samples) {
+        NDManager manager = samples.getManager();
+        int strideSize = (int) (0.001 * sampleRate * strideMs);
+        int windowSize = (int) (0.001 * sampleRate * windowsMs);
+        long truncateSize = (samples.size() - windowSize) % strideSize;
+        long len = samples.size() - truncateSize;
+        samples = samples.get(":" + len);
+
+        int rows = ((int) samples.size() - windowSize) / strideSize + 1;
+
+        NDList windowList = new NDList();
+        for (int row = 0; row < rows; row++) {
+            windowList.add(samples.get(strideSize * row + ":" + (strideSize * row + windowSize)));
+        }
+        samples = NDArrays.stack(windowList);
+
+        NDArray weighting = hanningWindow(windowSize, manager);
+        samples.muli(weighting);
+        NDList fftList = new NDList();
+        for (int row = 0; row < rows; row++) {
+            fftList.add(fft(samples.get(row)));
+        }
+
+        NDArray fft = NDArrays.stack(fftList).transpose();
+        fft = fft.pow(2);
+
+        weighting = weighting.pow(2);
+        NDArray scale = weighting.sum().mul(this.sampleRate);
+
+        NDArray middle = fft.get("1:-1,:");
+        middle = middle.mul(2).div(scale);
+        NDArray head = fft.get("0,:").div(scale).reshape(1, fft.getShape().get(1));
+        NDArray tail = fft.get("-1,:").div(scale).reshape(1, fft.getShape().get(1));
+        NDList list = new NDList(head, middle, tail);
+        fft = NDArrays.concat(list, 0);
+
+        NDArray freqsArray = manager.arange(fft.getShape().get(0));
+        freqsArray = freqsArray.mul(this.sampleRate / windowSize);
+
+        float[] freqs = freqsArray.toFloatArray();
+        int ind = 0;
+        for (int i = 0; i < freqs.length; i++) {
+            if (freqs[i] <= (this.sampleRate / 2)) {
+                ind = i;
+            } else {
+                break;
+            }
+        }
+        ind = ind + 1;
+
+        fft = fft.get(":" + ind + ",:").add(EPS);
+        fft = fft.log();
+
+        return fft;
+    }
+
+    private NDArray fft(NDArray in) {
+        float[] rawFFT = in.toFloatArray();
+        FloatFFT_1D fft = new FloatFFT_1D(rawFFT.length);
+        fft.realForward(rawFFT);
+        float[][] result;
+
+        int n = rawFFT.length;
+        if (n % 2 == 0) {
+            // n is even
+            result = new float[2][n / 2 + 1];
+            for (int i = 0; i < n / 2; i++) {
+                result[0][i] = rawFFT[2 * i]; // the real part fo the fast fourier transform
+                result[1][i] =
+                        rawFFT[2 * i + 1]; // the imaginary part of the fast fourier transform
+            }
+            result[1][0] = 0;
+            result[0][n / 2] = rawFFT[1];
+        } else {
+            // n is odd
+            result = new float[2][(n + 1) / 2];
+            for (int i = 0; i < n / 2; i++) {
+                result[0][i] = rawFFT[2 * i]; // the real part fo the fast fourier transform
+                result[1][i] =
+                        rawFFT[2 * i + 1]; // the imaginary part of the fast fourier transform
+            }
+            result[1][0] = 0;
+            result[1][(n - 1) / 2] = rawFFT[1];
+        }
+
+        float[] re = result[0]; // the real part fo the fast fourier transform
+        float[] im = result[1]; // the imaginary part of the fast fourier transform
+        float[] abs = new float[re.length];
+        for (int i = 0; i < re.length; i++) {
+            abs[i] = (float) Math.hypot(re[i], im[i]);
+        }
+        return in.getManager().create(abs);
+    }
+
+    private NDArray hanningWindow(int size, NDManager manager) {
+        float[] data = new float[size];
+        for (int i = 1; i < size; i++) {
+            data[i] = (float) (0.5 * (1 - Math.cos((2 * Math.PI * i) / (size - 1))));
+        }
+        return manager.create(data);
+    }
+}
diff --git a/extensions/audio/src/main/java/ai/djl/audio/processor/package-info.java b/extensions/audio/src/main/java/ai/djl/audio/processor/package-info.java
new file mode 100644
index 000000000..b3d688a12
--- /dev/null
+++ b/extensions/audio/src/main/java/ai/djl/audio/processor/package-info.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+/** Contains multiple processors for audio and signal processing. */
+package ai.djl.audio.processor;
diff --git a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
index 5eb16744e..4453ebd3b 100644
--- a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
+++ b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
@@ -31,10 +31,13 @@ import java.awt.image.DataBufferByte;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.opencv.core.CvType;
 import org.opencv.core.Mat;
 import org.opencv.core.MatOfByte;
+import org.opencv.core.MatOfPoint;
 import org.opencv.core.Point;
 import org.opencv.core.Rect;
 import org.opencv.core.Scalar;
@@ -180,6 +183,25 @@ class OpenCVImage implements Image {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public List<BoundingBox> findBoundingBoxes() {
+        List<MatOfPoint> points = new ArrayList<>();
+        Imgproc.findContours(
+                image, points, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
+        return points.parallelStream()
+                .map(
+                        point -> {
+                            Rect rect = Imgproc.boundingRect(point);
+                            return new Rectangle(
+                                    rect.x * 1.0 / image.width(),
+                                    rect.y * 1.0 / image.height(),
+                                    rect.width * 1.0 / image.width(),
+                                    rect.height * 1.0 / image.height());
+                        })
+                .collect(Collectors.toList());
+    }
+
     private void drawLandmarks(BoundingBox box) {
         Scalar color = new Scalar(0, 96, 246);
         for (ai.djl.modality.cv.output.Point point : box.getPath()) {
diff --git a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImageFactory.java b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImageFactory.java
index f71f36b0a..7fbd4d4f6 100644
--- a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImageFactory.java
+++ b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImageFactory.java
@@ -74,11 +74,18 @@ public class OpenCVImageFactory extends ImageFactory {
         Shape shape = array.getShape();
         if (shape.dimension() == 4) {
             throw new UnsupportedOperationException("Batch is not supported");
-        } else if (shape.get(0) == 1 || shape.get(2) == 1) {
-            throw new UnsupportedOperationException("Grayscale image is not supported");
         } else if (array.getDataType() != DataType.UINT8 && array.getDataType() != DataType.INT8) {
             throw new IllegalArgumentException("Datatype should be INT8 or UINT8");
         }
+        boolean grayScale = shape.get(0) == 1 || shape.get(2) == 1;
+        if (grayScale) {
+            // expected CHW
+            int width = Math.toIntExact(shape.get(2));
+            int height = Math.toIntExact(shape.get(1));
+            Mat img = new Mat(height, width, CvType.CV_8UC1);
+            img.put(0, 0, array.toByteArray());
+            return new OpenCVImage(img);
+        }
         if (NDImageUtils.isCHW(shape)) {
             array = array.transpose(1, 2, 0);
             shape = array.getShape();
diff --git a/gradle.properties b/gradle.properties
index 0ff6242d6..23e2cf9d2 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,7 +19,8 @@ paddlepaddle_version=2.2.2
 sentencepiece_version=0.1.95
 tokenizers_version=0.11.0
 fasttext_version=0.9.2
-xgboost_version=1.6.0
+xgboost_version=1.6.1
+rapis_version=22.04.0
 
 commons_cli_version=1.5.0
 commons_compress_version=1.21
diff --git a/settings.gradle b/settings.gradle
index 25492c51b..b50d5a552 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -25,6 +25,7 @@ include ':engines:tensorrt'
 include ':engines:tflite:tflite-engine'
 include ':engines:tflite:tflite-native'
 include ':examples'
+include 'extensions:audio'
 include ':extensions:aws-ai'
 include ':extensions:benchmark'
 include ':extensions:fasttext'
diff --git a/tools/gradle/check.gradle b/tools/gradle/check.gradle
index b953da7e7..08e0d945a 100644
--- a/tools/gradle/check.gradle
+++ b/tools/gradle/check.gradle
@@ -54,7 +54,7 @@ tasks.withType(Checkstyle) {
 
 apply plugin: 'jacoco'
 jacoco {
-    toolVersion = "0.8.5"
+    toolVersion = "0.8.7"
 }
 jacocoTestReport {
     reports {
diff --git a/tools/gradle/publish.gradle b/tools/gradle/publish.gradle
index aff4d8de2..02b3347cf 100644
--- a/tools/gradle/publish.gradle
+++ b/tools/gradle/publish.gradle
@@ -16,6 +16,7 @@ configure([
         project(':engines:tensorflow:tensorflow-model-zoo'),
         project(':engines:tensorrt'),
         project(':engines:tflite:tflite-engine'),
+        project(':extensions:audio'),
         project(':extensions:aws-ai'),
         project(':extensions:fasttext'),
         project(':extensions:hadoop'),


Test patch:
diff --git a/api/src/test/java/ai/djl/training/LearningRateTest.java b/api/src/test/java/ai/djl/training/LearningRateTest.java
index b2c812b88..791b33c1c 100644
--- a/api/src/test/java/ai/djl/training/LearningRateTest.java
+++ b/api/src/test/java/ai/djl/training/LearningRateTest.java
@@ -14,6 +14,7 @@
 package ai.djl.training;
 
 import ai.djl.training.tracker.CosineTracker;
+import ai.djl.training.tracker.CyclicalTracker;
 import ai.djl.training.tracker.FactorTracker;
 import ai.djl.training.tracker.MultiFactorTracker;
 import ai.djl.training.tracker.Tracker;
@@ -70,4 +71,84 @@ public class LearningRateTest {
         Assert.assertEquals(cosineTracker.getNewValue(200), finalValue);
         Assert.assertEquals(cosineTracker.getNewValue(300), finalValue);
     }
+
+    @Test
+    public void testCyclicalTracker() {
+        float baseValue = 0.01f;
+        float maxValue = 0.1f;
+        double epsilon = 1e-3;
+        CyclicalTracker cyclicalTrackerTriangular =
+                Tracker.cyclical()
+                        .optBaseValue(baseValue)
+                        .optMaxValue(maxValue)
+                        .optMode(CyclicalTracker.CyclicalMode.TRIANGULAR)
+                        .optStepSizeUp(100)
+                        .build();
+        Assert.assertEquals(cyclicalTrackerTriangular.getNewValue(0), baseValue);
+        Assert.assertEquals(cyclicalTrackerTriangular.getNewValue(10), 0.019f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular.getNewValue(50), 0.055f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular.getNewValue(100), 0.1f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular.getNewValue(380), 0.028f, epsilon);
+
+        CyclicalTracker cyclicalTrackerUnbalance =
+                Tracker.cyclical()
+                        .optBaseValue(baseValue)
+                        .optMaxValue(maxValue)
+                        .optMode(CyclicalTracker.CyclicalMode.TRIANGULAR)
+                        .optStepSizeUp(100)
+                        .optStepSizeDown(50)
+                        .build();
+        Assert.assertEquals(cyclicalTrackerUnbalance.getNewValue(0), baseValue);
+        Assert.assertEquals(cyclicalTrackerUnbalance.getNewValue(10), 0.019f, epsilon);
+        Assert.assertEquals(cyclicalTrackerUnbalance.getNewValue(50), 0.055f, epsilon);
+        Assert.assertEquals(cyclicalTrackerUnbalance.getNewValue(130), 0.046f, epsilon);
+        Assert.assertEquals(cyclicalTrackerUnbalance.getNewValue(380), 0.082f, epsilon);
+
+        CyclicalTracker cyclicalTrackerTriangular2 =
+                Tracker.cyclical()
+                        .optBaseValue(baseValue)
+                        .optMaxValue(maxValue)
+                        .optMode(CyclicalTracker.CyclicalMode.TRIANGULAR2)
+                        .optStepSizeUp(100)
+                        .build();
+        Assert.assertEquals(cyclicalTrackerTriangular2.getNewValue(0), baseValue);
+        Assert.assertEquals(cyclicalTrackerTriangular2.getNewValue(50), 0.055f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular2.getNewValue(100), 0.1f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular2.getNewValue(380), 0.019f, epsilon);
+        Assert.assertEquals(cyclicalTrackerTriangular2.getNewValue(460), 0.0235f, epsilon);
+
+        CyclicalTracker cyclicalTrackerExpRange =
+                Tracker.cyclical()
+                        .optBaseValue(baseValue)
+                        .optMaxValue(maxValue)
+                        .optMode(CyclicalTracker.CyclicalMode.EXP_RANGE)
+                        .optGamma(0.999f)
+                        .optStepSizeUp(100)
+                        .build();
+        Assert.assertEquals(cyclicalTrackerExpRange.getNewValue(0), baseValue);
+        Assert.assertEquals(cyclicalTrackerExpRange.getNewValue(50), 0.0528f, epsilon);
+        Assert.assertEquals(cyclicalTrackerExpRange.getNewValue(380), 0.0223f, epsilon);
+
+        CyclicalTracker cyclicalTrackerCustom =
+                Tracker.cyclical()
+                        .optBaseValue(baseValue)
+                        .optMaxValue(maxValue)
+                        .optScaleFunction(new CustomScaleFunction())
+                        .optScaleModeCycle(true)
+                        .optStepSizeUp(100)
+                        .build();
+        Assert.assertEquals(cyclicalTrackerCustom.getNewValue(0), baseValue);
+        Assert.assertEquals(cyclicalTrackerCustom.getNewValue(50), 0.055f, epsilon);
+        Assert.assertEquals(cyclicalTrackerCustom.getNewValue(500), 0.04f, epsilon);
+    }
+
+    private static class CustomScaleFunction implements CyclicalTracker.ScaleFunction {
+        @Override
+        public float func(int steps) {
+            if (steps == 0) {
+                return 1;
+            }
+            return 1f / ((float) steps);
+        }
+    }
 }
diff --git a/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java b/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java
new file mode 100644
index 000000000..4a7fd5b0d
--- /dev/null
+++ b/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.basicdataset;
+
+import ai.djl.Model;
+import ai.djl.basicdataset.tabular.DailyDelhiClimate;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.nn.Blocks;
+import ai.djl.nn.Parameter;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingConfig;
+import ai.djl.training.dataset.Batch;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.dataset.Record;
+import ai.djl.training.initializer.Initializer;
+import ai.djl.training.loss.Loss;
+import ai.djl.translate.TranslateException;
+import java.io.IOException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class DailyDelhiClimateTest {
+
+    @Test
+    public void testDailyDelhiClimateRemote() throws IOException, TranslateException {
+        TrainingConfig config =
+                new DefaultTrainingConfig(Loss.softmaxCrossEntropyLoss())
+                        .optInitializer(Initializer.ONES, Parameter.Type.WEIGHT);
+
+        try (Model model = Model.newInstance("model")) {
+            model.setBlock(Blocks.identityBlock());
+
+            NDManager manager = model.getNDManager();
+            // path of directory
+            DailyDelhiClimate dailyDelhiClimate =
+                    DailyDelhiClimate.builder()
+                            .optUsage(Dataset.Usage.TEST)
+                            .addFeature("humidity")
+                            .addFeature("wind_speed")
+                            .setSampling(32, true)
+                            .build();
+
+            dailyDelhiClimate.prepare();
+
+            long size = dailyDelhiClimate.size();
+            Assert.assertEquals(size, 114);
+
+            Record record = dailyDelhiClimate.get(manager, 0);
+            NDList data = record.getData();
+            NDList labels = record.getLabels();
+            Assert.assertEquals(data.head().toFloatArray(), new float[] {85.86957f, 2.7434783f});
+            Assert.assertEquals(labels.size(), 0);
+
+            try (Trainer trainer = model.newTrainer(config)) {
+                Batch batch = trainer.iterateDataset(dailyDelhiClimate).iterator().next();
+                Assert.assertEquals(batch.getData().size(), 1);
+                Assert.assertEquals(batch.getLabels().size(), 0);
+                batch.close();
+            }
+        }
+    }
+}
diff --git a/basicdataset/src/test/resources/mlrepo/dataset/tabular/ai/djl/basicdataset/daily-delhi-climate/metadata.json b/basicdataset/src/test/resources/mlrepo/dataset/tabular/ai/djl/basicdataset/daily-delhi-climate/metadata.json
new file mode 100644
index 000000000..59b9ff5c2
--- /dev/null
+++ b/basicdataset/src/test/resources/mlrepo/dataset/tabular/ai/djl/basicdataset/daily-delhi-climate/metadata.json
@@ -0,0 +1,34 @@
+{
+  "metadataVersion": "0.2",
+  "resourceType": "dataset",
+  "application": "tabular",
+  "groupId": "ai.djl.basicdataset",
+  "artifactId": "daily-delhi-climate",
+  "name": "daily-delhi-climate",
+  "description": "This dataset provides data from 1st January 2013 to 24th April 2017 in the city of Delhi, India. The 4 parameters here are meantemp, humidity, wind_speed, meanpressure.",
+  "website": "https://www.kaggle.com/datasets/sumanthvrao/daily-climate-time-series-data",
+  "licenses": {
+    "license": {
+      "name": "CC0 1.0 Universal",
+      "url": "https://creativecommons.org/publicdomain/zero/1.0/legalcode"
+    }
+  },
+  "artifacts": [
+    {
+      "version": "3.0",
+      "snapshot": false,
+      "files": {
+        "train": {
+          "uri": "3.0/DailyDelhiClimateTrain.csv",
+          "sha1Hash": "7e2b6ebf11015280bfd9308acf49567eca262b94",
+          "size": 78199
+        },
+        "test": {
+          "uri": "3.0/DailyDelhiClimateTest.csv",
+          "sha1Hash": "ca462410900d18037059fde452b4e084ef620d6d",
+          "size": 7100
+        }
+      }
+    }
+  ]
+}
diff --git a/engines/ml/xgboost/src/test/java/ai/djl/ml/xgboost/XgbModelTest.java b/engines/ml/xgboost/src/test/java/ai/djl/ml/xgboost/XgbModelTest.java
index 4310b3426..c29853e90 100644
--- a/engines/ml/xgboost/src/test/java/ai/djl/ml/xgboost/XgbModelTest.java
+++ b/engines/ml/xgboost/src/test/java/ai/djl/ml/xgboost/XgbModelTest.java
@@ -15,6 +15,7 @@ package ai.djl.ml.xgboost;
 
 import ai.djl.MalformedModelException;
 import ai.djl.Model;
+import ai.djl.engine.Engine;
 import ai.djl.inference.Predictor;
 import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDList;
@@ -45,6 +46,30 @@ public class XgbModelTest {
                 modelDir.resolve("regression.json").toString());
     }
 
+    @Test
+    public void testVersion() {
+        Engine engine = Engine.getEngine("XGBoost");
+        Assert.assertEquals("1.6.1", engine.getVersion());
+    }
+
+    /*
+    This test depends on rapis, which doesn't work for CPU
+    @Test
+    public void testCudf() {
+        Engine engine = Engine.getEngine("XGBoost");
+        if (engine.hasCapability(StandardCapabilities.CUDA)) {
+            Float[] data = new Float[]{1.f, null, 5.f, 7.f, 9.f};
+            Table x = new Table.TestBuilder().column(data).build();
+            ColumnBatch columnBatch = new CudfColumnBatch(x, null, null, null);
+
+            try (XgbNDManager manager = (XgbNDManager)engine.newBaseManager()) {
+                NDArray array = manager.create(columnBatch,0, 1);
+                Assert.assertEquals(array.getShape().get(0), 5);
+            }
+        }
+    }
+    */
+
     @Test
     public void testLoad() throws MalformedModelException, IOException, TranslateException {
         // TODO: skip for Windows since it is not supported by XGBoost
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/test/java/ai/djl/paddlepaddle/zoo/OCRTest.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/test/java/ai/djl/paddlepaddle/zoo/OCRTest.java
index 5bbffc49a..99304bb88 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/test/java/ai/djl/paddlepaddle/zoo/OCRTest.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/test/java/ai/djl/paddlepaddle/zoo/OCRTest.java
@@ -37,13 +37,15 @@ public class OCRTest {
 
     @Test
     public void testOCR() throws IOException, ModelException, TranslateException {
-        String url = "https://resources.djl.ai/images/flight_ticket.jpg";
-        Image img = ImageFactory.getInstance().fromUrl(url);
-        List<DetectedObjects.DetectedObject> boxes = detectWords(img).items();
-
         // load Character model
+        Predictor<Image, DetectedObjects> detector = getWordDetector();
         Predictor<Image, String> recognizer = getRecognizer();
         Predictor<Image, Classifications> rotator = getRotateClassifer();
+
+        String url = "https://resources.djl.ai/images/flight_ticket.jpg";
+        Image img = ImageFactory.getInstance().fromUrl(url);
+        DetectedObjects objects = detector.predict(img);
+        List<DetectedObjects.DetectedObject> boxes = objects.items();
         for (DetectedObjects.DetectedObject box : boxes) {
             Image subImg = getSubImage(img, box.getBoundingBox());
             if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) {
@@ -58,18 +60,15 @@ public class OCRTest {
         }
     }
 
-    private static DetectedObjects detectWords(Image img)
-            throws ModelException, IOException, TranslateException {
+    private static Predictor<Image, DetectedObjects> getWordDetector()
+            throws ModelNotFoundException, MalformedModelException, IOException {
         Criteria<Image, DetectedObjects> criteria =
                 Criteria.builder()
                         .setTypes(Image.class, DetectedObjects.class)
                         .optArtifactId("ai.djl.paddlepaddle:word_detection")
                         .optFilter("flavor", "mobile")
                         .build();
-        try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
-                Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
-            return predictor.predict(img);
-        }
+        return criteria.loadModel().newPredictor();
     }
 
     private static Predictor<Image, String> getRecognizer()
diff --git a/extensions/audio/src/test/java/ai/djl/audio/dataset/LibrispeechTest.java b/extensions/audio/src/test/java/ai/djl/audio/dataset/LibrispeechTest.java
new file mode 100644
index 000000000..5f776a1d6
--- /dev/null
+++ b/extensions/audio/src/test/java/ai/djl/audio/dataset/LibrispeechTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.audio.dataset;
+
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.translate.TranslateException;
+import java.io.IOException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class LibrispeechTest {
+
+    @Test
+    public void testLibrispeech() throws IOException, TranslateException {
+        // The dataset is very large, only run the test when -Dweekly=true is set
+        if (Boolean.getBoolean("weekly")) {
+            NDManager manager = NDManager.newBaseManager();
+            Librispeech dataset =
+                    Librispeech.builder()
+                            .optUsage(Dataset.Usage.TEST)
+                            .setSampling(32, false)
+                            .build();
+            dataset.prepare();
+            NDList data = dataset.get(manager, 0).getData();
+            Assert.assertEquals(data.getShapes()[0].getShape(), new long[] {161, 306});
+
+            long[] expected = {
+                0, 1, 2, 3, 2344, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 2, 14, 15, 16, 5, 2344, 17, 18,
+                19
+            };
+            NDList labels = dataset.get(manager, 0).getLabels();
+            Assert.assertEquals(labels.get(0).toLongArray(), expected);
+        }
+    }
+}
diff --git a/extensions/audio/src/test/java/ai/djl/audio/dataset/package-info.java b/extensions/audio/src/test/java/ai/djl/audio/dataset/package-info.java
new file mode 100644
index 000000000..74110d1c7
--- /dev/null
+++ b/extensions/audio/src/test/java/ai/djl/audio/dataset/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+
+/** Contains tests for the dataset module. */
+package ai.djl.audio.dataset;
\ No newline at end of file
diff --git a/extensions/audio/src/test/java/ai/djl/audio/processor/AudioProcessorTest.java b/extensions/audio/src/test/java/ai/djl/audio/processor/AudioProcessorTest.java
new file mode 100644
index 000000000..28cec1b62
--- /dev/null
+++ b/extensions/audio/src/test/java/ai/djl/audio/processor/AudioProcessorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+package ai.djl.audio.processor;
+
+import ai.djl.Device;
+import ai.djl.audio.AudioUtils;
+import ai.djl.audio.dataset.AudioData;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.training.util.DownloadUtils;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AudioProcessorTest {
+
+    private static final String URL = "https://resources.djl.ai/audios/test_01.wav";
+
+    @BeforeClass
+    public void setUp() throws IOException {
+        DownloadUtils.download(URL, "build/test/test_01.wav");
+    }
+
+    @Test
+    public void testAudioNormalizer() {
+        NDManager manager = NDManager.newBaseManager(Device.cpu());
+        AudioData.Configuration configuration =
+                new AudioData.Configuration()
+                        .setProcessorList(Collections.singletonList(new AudioNormalizer(-20)));
+        AudioData testData = new AudioData(configuration);
+        testData.setAudioPaths(Collections.singletonList("build/test/test_01.wav"));
+        NDArray samples = testData.getPreprocessedData(manager, 0);
+        Assert.assertEquals(AudioUtils.rmsDb(samples), -20.0f, 1e-3);
+    }
+
+    @Test
+    public static void testLinearSpecgram() {
+        NDManager manager = NDManager.newBaseManager(Device.cpu());
+        AudioData.Configuration configuration =
+                new AudioData.Configuration()
+                        .setSampleRate(16000)
+                        .setProcessorList(
+                                Arrays.asList(
+                                        new AudioNormalizer(-20),
+                                        new LinearSpecgram(10, 20, 16000)));
+        AudioData testData = new AudioData(configuration);
+        testData.setAudioPaths(Collections.singletonList("build/test/test_01.wav"));
+        NDArray samples = testData.getPreprocessedData(manager, 0);
+        Assert.assertEquals(new Shape(161, 838), samples.getShape());
+        Assert.assertEquals(samples.get("0,0").toFloatArray()[0], -15.4571f, 1e-3f);
+    }
+}
diff --git a/extensions/audio/src/test/java/ai/djl/audio/processor/package-info.java b/extensions/audio/src/test/java/ai/djl/audio/processor/package-info.java
new file mode 100644
index 000000000..3b8fc9e92
--- /dev/null
+++ b/extensions/audio/src/test/java/ai/djl/audio/processor/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+
+/** Contains tests for the processor module. */
+package ai.djl.audio.processor;
\ No newline at end of file
diff --git a/extensions/audio/src/test/resources/mlrepo/dataset/audio/ai/djl/basicdataset/librispeech/metadata.json b/extensions/audio/src/test/resources/mlrepo/dataset/audio/ai/djl/basicdataset/librispeech/metadata.json
new file mode 100644
index 000000000..5224958f9
--- /dev/null
+++ b/extensions/audio/src/test/resources/mlrepo/dataset/audio/ai/djl/basicdataset/librispeech/metadata.json
@@ -0,0 +1,35 @@
+{
+  "metadataVersion": "0.2",
+  "resourceType": "dataset",
+  "application": "audio",
+  "groupId": "ai.djl.basicdata",
+  "artifactId": "librispeech",
+  "name": "librispeech",
+  "description": "LibriSpeech is a corpus of approximately 1000 hours of 16kHz read English speech, prepared by Vassil Panayotov with the assistance of Daniel Povey. The data is derived from read audiobooks from the LibriVox project, and has been carefully segmented and aligned.",
+  "website": "https://huggingface.co/datasets/librispeech_asr",
+  "licenses": {
+    "license": {
+      "name": "CC BY 4.0",
+      "url": "https://creativecommons.org/licenses/by/4.0/"
+    }
+  },
+  "artifacts": [
+    {
+      "version": "1.0",
+      "snapshot": false,
+      "name": "Librispeech",
+      "files": {
+        "train": {
+          "uri": "http://us.openslr.org/resources/12/train-clean-100.tar.gz",
+          "sha1Hash": "cf83135102b2958182e26ef3b554b439338aa48e",
+          "size": 6387309499
+        },
+        "test": {
+          "uri": "http://us.openslr.org/resources/12/test-clean.tar.gz",
+          "sha1Hash": "2978149c5fa469821a02171e1d547cf1065f1213",
+          "size": 346663984
+        }
+      }
+    }
+  ]
+}
diff --git a/extensions/opencv/src/test/java/ai/djl/opencv/OpenCVImageFactoryTest.java b/extensions/opencv/src/test/java/ai/djl/opencv/OpenCVImageFactoryTest.java
index 009cb060f..9e7e47dfe 100644
--- a/extensions/opencv/src/test/java/ai/djl/opencv/OpenCVImageFactoryTest.java
+++ b/extensions/opencv/src/test/java/ai/djl/opencv/OpenCVImageFactoryTest.java
@@ -20,8 +20,10 @@ import ai.djl.modality.cv.output.DetectedObjects;
 import ai.djl.modality.cv.output.Joints;
 import ai.djl.modality.cv.output.Landmark;
 import ai.djl.modality.cv.output.Mask;
+import ai.djl.modality.cv.output.Rectangle;
 import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.DataType;
 import ai.djl.testing.TestRequirements;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -29,6 +31,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.testng.Assert;
@@ -122,4 +125,31 @@ public class OpenCVImageFactoryTest {
                     });
         }
     }
+
+    @Test
+    public void testBoundingBoxes() {
+        TestRequirements.notWindows(); // failed on Windows ServerCore container
+        ImageFactory factory = ImageFactory.getInstance();
+        try (NDManager manager = NDManager.newBaseManager()) {
+            int[][] arr =
+                    new int[][] {
+                        {0, 1, 1, 1, 0},
+                        {0, 0, 1, 0, 0},
+                        {0, 0, 0, 0, 0},
+                        {1, 1, 0, 0, 0},
+                        {1, 0, 0, 0, 1}
+                    };
+            NDArray array = manager.create(arr).muli(255).toType(DataType.INT8, true).expandDims(0);
+            OpenCVImage image = (OpenCVImage) factory.fromNDArray(array);
+            List<BoundingBox> rectangles = image.findBoundingBoxes();
+            List<Rectangle> expected =
+                    Arrays.asList(
+                            new Rectangle(0.8, 0.8, 0.2, 0.2),
+                            new Rectangle(0, 0.6, 0.4, 0.4),
+                            new Rectangle(0.2, 0, 0.6, 0.4));
+            for (int i = 0; i < rectangles.size(); i++) {
+                Assert.assertEquals(rectangles.get(i).toString(), expected.get(i).toString());
+            }
+        }
+    }
 }
diff --git a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayOtherOpTest.java b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayOtherOpTest.java
index a878ec1fe..df9b596a8 100644
--- a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayOtherOpTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayOtherOpTest.java
@@ -906,6 +906,7 @@ public class NDArrayOtherOpTest {
                     manager.create(
                             new float[][] {{0f, 1f, 0f}, {1f, 0f, 0f}, {0f, 0f, 1f}, {1f, 0f, 0f}});
             Assert.assertEquals(array.oneHot(3), expected);
+            Assert.assertEquals(array.oneHot(3, DataType.FLOAT32), expected);
             // test with all parameters
             array = manager.create(new int[] {1, 0, 2, 0});
             expected = manager.create(new int[][] {{1, 8, 1}, {8, 1, 1}, {1, 1, 8}, {8, 1, 1}});
diff --git a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDIndexTest.java b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDIndexTest.java
index 49e9f652c..88abee3ad 100644
--- a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDIndexTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDIndexTest.java
@@ -55,9 +55,9 @@ public class NDIndexTest {
     @Test
     public void testGather() {
         try (NDManager manager = NDManager.newBaseManager()) {
-            NDArray arr = manager.arange(20f).reshape(-1, 4);
+            NDArray original = manager.arange(20f).reshape(-1, 4);
             NDArray index = manager.create(new long[] {0, 0, 2, 1, 1, 2}, new Shape(3, 2));
-            NDArray actual = arr.gather(index, 1);
+            NDArray actual = original.gather(index, 1);
             NDArray expected = manager.create(new float[] {0, 0, 6, 5, 9, 10}, new Shape(3, 2));
             Assert.assertEquals(actual, expected);
         }
@@ -66,9 +66,9 @@ public class NDIndexTest {
     @Test
     public void testTake() {
         try (NDManager manager = NDManager.newBaseManager()) {
-            NDArray arr = manager.arange(1, 7f).reshape(-1, 3);
+            NDArray original = manager.arange(1, 7f).reshape(-1, 3);
             NDArray index = manager.create(new long[] {0, 4, 1, 2}, new Shape(2, 2));
-            NDArray actual = arr.take(index);
+            NDArray actual = original.take(index);
             NDArray expected = manager.create(new float[] {1, 5, 2, 3}, new Shape(2, 2));
             Assert.assertEquals(actual, expected);
         }
diff --git a/testing/src/main/java/ai/djl/testing/CoverageUtils.java b/testing/src/main/java/ai/djl/testing/CoverageUtils.java
index 6b0eb5869..31053bab4 100644
--- a/testing/src/main/java/ai/djl/testing/CoverageUtils.java
+++ b/testing/src/main/java/ai/djl/testing/CoverageUtils.java
@@ -101,7 +101,12 @@ public final class CoverageUtils {
     private static List<Class<?>> getClasses(Class<?> clazz)
             throws IOException, ReflectiveOperationException, URISyntaxException {
         ClassLoader appClassLoader = ClassLoaderUtils.getContextClassLoader();
-        Field field = appClassLoader.getClass().getDeclaredField("ucp");
+        Field field;
+        try {
+            field = appClassLoader.getClass().getDeclaredField("ucp");
+        } catch (NoSuchFieldException e) {
+            field = appClassLoader.getClass().getSuperclass().getDeclaredField("ucp");
+        }
         field.setAccessible(true);
         Object ucp = field.get(appClassLoader);
         Method method = ucp.getClass().getDeclaredMethod("getURLs");
@@ -131,7 +136,7 @@ public final class CoverageUtils {
 
                 try {
                     classList.add(Class.forName(className, true, cl));
-                } catch (Error ignore) {
+                } catch (Throwable ignore) {
                     // ignore
                 }
             }
@@ -146,7 +151,7 @@ public final class CoverageUtils {
                         fileName = fileName.replace('/', '.');
                         try {
                             classList.add(Class.forName(fileName, true, cl));
-                        } catch (Error ignore) {
+                        } catch (Throwable ignore) {
                             // ignore
                         }
                     }