Repository: deepjavalibrary/djl
Pull request #2026 (2022-09-19 02:58:52)

[timeseries] add time series dataset support for timeseries package
## Description ##

This pr implements `TimeSeriesDataset` for time series data preprocessing, this class defines a unified format for time series data preprocessing when training, and allows users to customize the source of data data and how to obtain it, we implement a format stored in .csv file here.

We also provide a new `Featurizer` for information on the date obtained from the data.

In the pre-processing section of the data, we added isTraining parameter to the transformer to determine whether it is a training mode.

Last, we create a new `InstanceSampler` to help split past data(context used as model input) and future data(used as label) when training

codecov-commenter: # [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/2026?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) Report
Base: **72.08**% // Head: **69.06**% // Decreases project coverage by **`-3.02%`** :warning:
> Coverage data is based on head [(`199f028`)](https://codecov.io/gh/deepjavalibrary/djl/pull/2026?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) compared to base [(`bb5073f`)](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).
> Patch coverage: 68.14% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #2026      +/-   ##
============================================
- Coverage     72.08%   69.06%   -3.03%     
- Complexity     5126     5955     +829     
============================================
  Files           473      603     +130     
  Lines         21970    26686    +4716     
  Branches       2351     2903     +552     
============================================
+ Hits          15838    18430    +2592     
- Misses         4925     6872    +1947     
- Partials       1207     1384     +177     
```


| [Impacted Files](https://codecov.io/gh/deepjavalibrary/djl/pull/2026?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/2026/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/MultiBoxPrior.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L011bHRpQm94UHJpb3IuamF2YQ==) | `76.00% <ø> (ø)` | |
| [...rc/main/java/ai/djl/modality/cv/output/Joints.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/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/2026/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/2026/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% <0.00%> (ø)` | |
| [...i/djl/modality/cv/translator/BigGANTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/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% <0.00%> (-5.24%)` | :arrow_down: |
| [...odality/cv/translator/BigGANTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/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: |
| [.../cv/translator/InstanceSegmentationTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvSW5zdGFuY2VTZWdtZW50YXRpb25UcmFuc2xhdG9yLmphdmE=) | `0.00% <0.00%> (-86.59%)` | :arrow_down: |
| [...nslator/InstanceSegmentationTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvSW5zdGFuY2VTZWdtZW50YXRpb25UcmFuc2xhdG9yRmFjdG9yeS5qYXZh) | `7.14% <0.00%> (-11.04%)` | :arrow_down: |
| [.../cv/translator/SemanticSegmentationTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvU2VtYW50aWNTZWdtZW50YXRpb25UcmFuc2xhdG9yLmphdmE=) | `0.00% <0.00%> (ø)` | |
| ... and [518 more](https://codecov.io/gh/deepjavalibrary/djl/pull/2026/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/2026?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).


Patch:
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 4c31f4de3..c0f231d28 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -89,47 +89,49 @@ nav:
   - DJL Community:
       - 'docs/forums.md'
       - 'leaders.md'
-  - Apache MXNet:
-      - Overview: 'engines/mxnet/README.md'
-      - MXNet Engine: 'engines/mxnet/mxnet-engine/README.md'
-      - MXNet Model Zoo: 'engines/mxnet/mxnet-model-zoo/README.md'
-      - Import Gluon Model: 'docs/mxnet/how_to_convert_your_model_to_symbol.md'
-      - Load a MXNet Model: 'jupyter/load_mxnet_model.ipynb'
-      - Backend Optimizer for MXNet: 'docs/mxnet/mxnet_backend_optimizer.md'
-  - PyTorch:
-      - Overview: 'engines/pytorch/README.md'
-      - PyTorch Engine: 'engines/pytorch/pytorch-engine/README.md'
-      - PyTorch Model Zoo: 'engines/pytorch/pytorch-model-zoo/README.md'
-      - Import PyTorch Model: 'docs/pytorch/how_to_convert_your_model_to_torchscript.md'
-      - Load a PyTorch Model: 'jupyter/load_pytorch_model.ipynb'
-  - TensorFlow:
-      - Overview: 'engines/tensorflow/README.md'
-      - TensorFlow Engine: 'engines/tensorflow/tensorflow-engine/README.md'
-      - TensorFlow Model Zoo: 'engines/tensorflow/tensorflow-model-zoo/README.md'
-      - Import TensorFlow Model: 'docs/tensorflow/how_to_import_tensorflow_models_in_DJL.md'
-      - Load a TensorFlow Model: 'jupyter/tensorflow/pneumonia_detection.ipynb'
-  - Hybrid engines:
-      - Hybrid engine overview: 'docs/hybrid_engine.md'
-      - ONNX Runtime:
-          - Overview: 'engines/onnxruntime/onnxruntime-engine/README.md'
-          - Load a ONNX Model: 'jupyter/onnxruntime/machine_learning_with_ONNXRuntime.ipynb'
-      - PaddlePaddle:
-          - Overview: 'engines/paddlepaddle/README.md'
-          - PaddlePaddle Engine: 'engines/paddlepaddle/paddlepaddle-engine/README.md'
-          - PaddlePaddle Model Zoo: 'engines/paddlepaddle/paddlepaddle-model-zoo/README.md'
-          - Import Paddle model:
-              - English: 'docs/paddlepaddle/how_to_create_paddlepaddle_model.md'
-              - 中文: 'docs/paddlepaddle/how_to_create_paddlepaddle_model_zh.md'
-          - Facemask detection using PaddlePaddle:
-              - English: 'jupyter/paddlepaddle/face_mask_detection_paddlepaddle.ipynb'
-              - 中文: 'jupyter/paddlepaddle/face_mask_detection_paddlepaddle_zh.ipynb'
-          - PaddleOCR example:
-              - English: 'jupyter/paddlepaddle/paddle_ocr_java.ipynb'
-              - 中文: 'jupyter/paddlepaddle/paddle_ocr_java_zh.ipynb'
-      - TensorRT: 'engines/tensorrt/README.md'
-      - TensorFlow Lite: 'engines/tflite/tflite-engine/README.md'
-      - Neo DLR: 'engines/dlr/dlr-engine/README.md'
-      - XGBoost: 'engines/ml/xgboost/README.md'
+  - Supported Engines:
+    - PyTorch:
+        - Overview: 'engines/pytorch/README.md'
+        - PyTorch Engine: 'engines/pytorch/pytorch-engine/README.md'
+        - PyTorch Model Zoo: 'engines/pytorch/pytorch-model-zoo/README.md'
+        - Import PyTorch Model: 'docs/pytorch/how_to_convert_your_model_to_torchscript.md'
+        - Load a PyTorch Model: 'jupyter/load_pytorch_model.ipynb'
+    - TensorFlow:
+        - Overview: 'engines/tensorflow/README.md'
+        - TensorFlow Engine: 'engines/tensorflow/tensorflow-engine/README.md'
+        - TensorFlow Model Zoo: 'engines/tensorflow/tensorflow-model-zoo/README.md'
+        - Import TensorFlow Model: 'docs/tensorflow/how_to_import_tensorflow_models_in_DJL.md'
+        - Load a TensorFlow Model: 'jupyter/tensorflow/pneumonia_detection.ipynb'
+    - Apache MXNet:
+        - Overview: 'engines/mxnet/README.md'
+        - MXNet Engine: 'engines/mxnet/mxnet-engine/README.md'
+        - MXNet Model Zoo: 'engines/mxnet/mxnet-model-zoo/README.md'
+        - Import Gluon Model: 'docs/mxnet/how_to_convert_your_model_to_symbol.md'
+        - Load a MXNet Model: 'jupyter/load_mxnet_model.ipynb'
+        - Backend Optimizer for MXNet: 'docs/mxnet/mxnet_backend_optimizer.md'
+    - Hybrid engines:
+        - Hybrid engine overview: 'docs/hybrid_engine.md'
+        - ONNX Runtime:
+            - Overview: 'engines/onnxruntime/onnxruntime-engine/README.md'
+            - Load a ONNX Model: 'jupyter/onnxruntime/machine_learning_with_ONNXRuntime.ipynb'
+        - PaddlePaddle:
+            - Overview: 'engines/paddlepaddle/README.md'
+            - PaddlePaddle Engine: 'engines/paddlepaddle/paddlepaddle-engine/README.md'
+            - PaddlePaddle Model Zoo: 'engines/paddlepaddle/paddlepaddle-model-zoo/README.md'
+            - Import Paddle model:
+                - English: 'docs/paddlepaddle/how_to_create_paddlepaddle_model.md'
+                - 中文: 'docs/paddlepaddle/how_to_create_paddlepaddle_model_zh.md'
+            - Facemask detection using PaddlePaddle:
+                - English: 'jupyter/paddlepaddle/face_mask_detection_paddlepaddle.ipynb'
+                - 中文: 'jupyter/paddlepaddle/face_mask_detection_paddlepaddle_zh.ipynb'
+            - PaddleOCR example:
+                - English: 'jupyter/paddlepaddle/paddle_ocr_java.ipynb'
+                - 中文: 'jupyter/paddlepaddle/paddle_ocr_java_zh.ipynb'
+        - XGBoost: 'engines/ml/xgboost/README.md'
+        - LightGBM: 'engines/ml/lightgbm/README.md'
+        - TensorRT: 'engines/tensorrt/README.md'
+        - TensorFlow Lite: 'engines/tflite/tflite-engine/README.md'
+        - Neo DLR: 'engines/dlr/dlr-engine/README.md'
   - Extensions:
       - Android: 'android/README.md'
       - AWS S3 support: 'extensions/aws-ai/README.md'
@@ -140,6 +142,18 @@ nav:
       - SentencePiece: 'extensions/sentencepiece/README.md'
       - Tablesaw: 'extensions/tablesaw/README.md'
       - DJL Zero: 'djl-zero/README.md'
+      - DJL Benchmark: 'docs/serving/benchmark/README.md'
+  - DJL Serving:
+      - Why DJL Serving?: 'docs/serving/README.md'
+      - 'docs/serving/serving/docs/starting.md'
+      - 'docs/serving/serving/docs/configuration.md'
+      - 'docs/serving/serving/docs/workflows.md'
+      - 'docs/serving/serving/docs/architecture.md'
+      - HTTP API:
+          - 'docs/serving/serving/docs/inference_api.md'
+          - 'docs/serving/serving/docs/management_api.md'
+      - 'docs/serving/serving/docs/plugin_management.md'
+      - 'docs/serving/wlm/README.md'
   - Demos:
       - Demos: 'docs/demos/README.md'
       - AWS:
@@ -182,16 +196,3 @@ nav:
       - Extensions:
           - Visualizing Training with DJL: 'docs/demos/visualization-vue/README.md'
           - Interactive JShell and Block Runner for DJL: 'docs/demos/web-demo/interactive-console/README.md'
-  - DJL Serving:
-      - Why DJL Serving?: 'docs/serving/README.md'
-      - 'docs/serving/serving/docs/starting.md'
-      - 'docs/serving/serving/docs/configuration.md'
-      - 'docs/serving/serving/docs/workflows.md'
-      - 'docs/serving/serving/docs/architecture.md'
-      - HTTP API:
-        - 'docs/serving/serving/docs/inference_api.md'
-        - 'docs/serving/serving/docs/management_api.md'
-      - 'docs/serving/serving/docs/plugin_management.md'
-      - 'docs/serving/wlm/README.md'
-
-
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/CsvTimeSeriesDataset.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/CsvTimeSeriesDataset.java
new file mode 100644
index 000000000..77fefe82b
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/CsvTimeSeriesDataset.java
@@ -0,0 +1,272 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.basicdataset.tabular.utils.DynamicBuffer;
+import ai.djl.basicdataset.tabular.utils.Feature;
+import ai.djl.basicdataset.tabular.utils.PreparedFeaturizer;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.timeseries.TimeSeriesData;
+import ai.djl.util.Pair;
+import ai.djl.util.PairList;
+import ai.djl.util.Progress;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.FloatBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+/** {@code CsvTimeSeriesDataset} represents the dataset that store in a .csv file. */
+public class CsvTimeSeriesDataset extends TimeSeriesDataset {
+
+    protected PairList<FieldName, List<Feature>> fieldFeatures;
+    protected Feature startTimeFeature;
+    protected URL csvUrl;
+    protected CSVFormat csvFormat;
+    protected List<CSVRecord> csvRecords;
+
+    protected CsvTimeSeriesDataset(CsvBuilder<?> builder) {
+        super(builder);
+        fieldFeatures = builder.fieldFeatures;
+        startTimeFeature = builder.startTimeFeatures;
+        csvUrl = builder.csvUrl;
+        csvFormat = builder.csvFormat;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected long availableSize() {
+        return csvRecords.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void prepare(Progress progress) throws IOException {
+        try (Reader reader = new InputStreamReader(getCsvStream(), StandardCharsets.UTF_8)) {
+            CSVParser csvParser = new CSVParser(reader, csvFormat);
+            csvRecords = csvParser.getRecords();
+        }
+        prepareFeaturizers();
+    }
+
+    private InputStream getCsvStream() throws IOException {
+        if (csvUrl.getFile().endsWith(".gz")) {
+            return new GZIPInputStream(csvUrl.openStream());
+        }
+        return new BufferedInputStream(csvUrl.openStream());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TimeSeriesData getTimeSeriesData(NDManager manager, long index) {
+        TimeSeriesData data = new TimeSeriesData(fieldFeatures.size());
+        for (Pair<FieldName, List<Feature>> pair : fieldFeatures) {
+            if (!pair.getValue().isEmpty()) {
+                data.add(
+                        pair.getKey(),
+                        getRowFeatures(manager, index, pair.getValue()).singletonOrThrow());
+            }
+        }
+
+        data.setStartTime(getStartTime(index));
+        return data;
+    }
+
+    /** Prepares the {@link PreparedFeaturizer}s. */
+    protected void prepareFeaturizers() {
+        int availableSize = Math.toIntExact(availableSize());
+        List<Feature> featuresToPrepare = new ArrayList<>();
+        for (List<Feature> list : fieldFeatures.values()) {
+            featuresToPrepare.addAll(list);
+        }
+        for (Feature feature : featuresToPrepare) {
+            if (feature.getFeaturizer() instanceof PreparedFeaturizer) {
+                PreparedFeaturizer featurizer = (PreparedFeaturizer) feature.getFeaturizer();
+                List<String> inputs = new ArrayList<>(Math.toIntExact(availableSize));
+                for (int i = 0; i < availableSize; i++) {
+                    inputs.add(getCell(i, feature.getName()));
+                }
+                featurizer.prepare(inputs);
+            }
+        }
+    }
+
+    /**
+     * Return the prediction start time for the given index.
+     *
+     * @param rowIndex the row index
+     * @return the start time
+     */
+    public LocalDateTime getStartTime(long rowIndex) {
+        CSVRecord record = csvRecords.get(Math.toIntExact(rowIndex));
+        TimeFeaturizer featurizer = (TimeFeaturizer) startTimeFeature.getFeaturizer();
+        if (featurizer instanceof TimeFeaturizers.ConstantTimeFeaturizer) {
+            return featurizer.featurize(null);
+        }
+        String value = record.get(startTimeFeature.getName());
+        return featurizer.featurize(value);
+    }
+
+    /**
+     * Returns the designated features (either data or label features) from a row.
+     *
+     * @param manager the manager used to create the arrays
+     * @param index the index of the requested data item
+     * @param selected the features to pull from the row
+     * @return the features formatted as an {@link NDList}
+     */
+    public NDList getRowFeatures(NDManager manager, long index, List<Feature> selected) {
+        DynamicBuffer bb = new DynamicBuffer();
+        for (Feature feature : selected) {
+            String name = feature.getName();
+            String value = getCell(index, name);
+            feature.getFeaturizer().featurize(bb, value);
+        }
+        FloatBuffer buf = bb.getBuffer();
+        return new NDList(manager.create(buf, new Shape(bb.getLength())));
+    }
+
+    /**
+     * Returns a cell in the dataset.
+     *
+     * @param rowIndex the row index or record index for the cell
+     * @param featureName the feature or column of the cell
+     * @return the value of the cell at that row and column
+     */
+    protected String getCell(long rowIndex, String featureName) {
+        CSVRecord record = csvRecords.get(Math.toIntExact(rowIndex));
+        return record.get(featureName);
+    }
+
+    /** Used to build a {@code CsvTimeSeriesDataset}. */
+    public static class CsvBuilder<T extends CsvBuilder<T>> extends TimeSeriesBuilder<T> {
+
+        protected PairList<FieldName, List<Feature>> fieldFeatures;
+        protected Feature startTimeFeatures;
+        protected URL csvUrl;
+        protected CSVFormat csvFormat;
+
+        protected CsvBuilder() {
+            fieldFeatures = new PairList<>(DATASET_FIELD_NAMES.length);
+            for (FieldName fieldName : DATASET_FIELD_NAMES) {
+                fieldFeatures.add(fieldName, new ArrayList<>());
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @SuppressWarnings("unchecked")
+        protected T self() {
+            return (T) this;
+        }
+
+        /**
+         * Set the optional CSV file path.
+         *
+         * @param csvFile the CSV file path
+         * @return this builder
+         */
+        public T optCsvFile(Path csvFile) {
+            try {
+                this.csvUrl = csvFile.toAbsolutePath().toUri().toURL();
+            } catch (MalformedURLException e) {
+                throw new IllegalArgumentException("Invalid file path: " + csvFile, e);
+            }
+            return self();
+        }
+
+        /**
+         * Set the optional CSV file URL.
+         *
+         * @param csvUrl the CSV file URL
+         * @return this builder
+         */
+        public T optCsvUrl(String csvUrl) {
+            try {
+                this.csvUrl = new URL(csvUrl);
+            } catch (MalformedURLException e) {
+                throw new IllegalArgumentException("Invalid url: " + csvUrl, e);
+            }
+            return self();
+        }
+
+        /**
+         * Set the CSV file format.
+         *
+         * @param csvFormat the {@code CSVFormat}
+         * @return this builder
+         */
+        public T setCsvFormat(CSVFormat csvFormat) {
+            this.csvFormat = csvFormat;
+            return self();
+        }
+
+        /**
+         * Add the features to the correspongding {@link FieldName}.
+         *
+         * @param fieldName the correspongding {@link FieldName}
+         * @param feature the feature
+         * @return this builder
+         */
+        public T addFieldFeature(FieldName fieldName, Feature feature) {
+            if (fieldName == FieldName.START) {
+                startTimeFeatures = feature;
+            } else if (fieldFeatures.contains(fieldName)) {
+                fieldFeatures.get(fieldName).add(feature);
+            } else {
+                throw new IllegalArgumentException("Unsupported feature field type: " + fieldName);
+            }
+            return self();
+        }
+
+        /**
+         * Validate the builder to ensure it is correct.
+         *
+         * @throws IllegalArgumentException if there is an error with the builder arguments
+         */
+        protected void validate() {
+            if (fieldFeatures.get(FieldName.TARGET).isEmpty()) {
+                throw new IllegalArgumentException("Missing target");
+            } else if (startTimeFeatures == null) {
+                throw new IllegalArgumentException("Missing start time");
+            }
+        }
+
+        /**
+         * Build the new {@link CsvTimeSeriesDataset}.
+         *
+         * @return the new {@link CsvTimeSeriesDataset}
+         */
+        public CsvTimeSeriesDataset build() {
+            validate();
+            return new CsvTimeSeriesDataset(this);
+        }
+    }
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/M5Forecast.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/M5Forecast.java
new file mode 100644
index 000000000..3fbd084c7
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/M5Forecast.java
@@ -0,0 +1,250 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.Application;
+import ai.djl.basicdataset.BasicDatasets;
+import ai.djl.basicdataset.tabular.utils.Feature;
+import ai.djl.basicdataset.tabular.utils.Featurizers;
+import ai.djl.repository.Artifact;
+import ai.djl.repository.MRL;
+import ai.djl.repository.Repository;
+import ai.djl.util.JsonUtils;
+import ai.djl.util.Progress;
+import ai.djl.util.Utils;
+
+import org.apache.commons.csv.CSVFormat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * M5 Forecasting - Accuracy from <a
+ * href="https://www.kaggle.com/competitions/m5-forecasting-accuracy">https://www.kaggle.com/competitions/m5-forecasting-accuracy</a>
+ *
+ * <p>To improve the model performance, we coarse grain the target of the dataset by summing the
+ * sale amount every seven days. And set the column names of sum as 'w_i'. This can reduce
+ * occurrence of invalid values 0 and reduce the noise learned by model.
+ */
+public class M5Forecast extends CsvTimeSeriesDataset {
+
+    private static final String ARTIFACT_ID = "m5forecast";
+    private static final String VERSION = "1.0";
+
+    private Usage usage;
+    private MRL mrl;
+    private boolean prepared;
+    private List<Integer> cardinality;
+
+    /**
+     * Creates a new instance of {@link M5Forecast} with the given necessary configurations.
+     *
+     * @param builder a builder with the necessary configurations
+     */
+    protected M5Forecast(Builder builder) {
+        super(builder);
+        usage = builder.usage;
+        mrl = builder.getMrl();
+        cardinality = builder.cardinality;
+    }
+
+    /** {@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 = root.resolve(getUsagePath(usage));
+
+        csvUrl = csvFile.toUri().toURL();
+        super.prepare(progress);
+        prepared = true;
+    }
+
+    /**
+     * Return the cardinality of the dataset.
+     *
+     * @return the cardinality of the dataset
+     */
+    public List<Integer> getCardinality() {
+        return cardinality;
+    }
+
+    /**
+     * Creates a builder to build a {@link M5Forecast}.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private String getUsagePath(Usage usage) {
+        // We coarse graining the data by summing the sale amount every 7 days and rename the .csv
+        // file as 'weekly_***'
+        switch (usage) {
+            case TRAIN:
+                return "fake_weekly_sales_train_validation.csv";
+            case TEST:
+                return "fake_weekly_sales_train_evaluation.csv";
+            case VALIDATION:
+            default:
+                throw new UnsupportedOperationException("Data not available.");
+        }
+    }
+
+    /** Used to build a {@code M5Forecast}. */
+    public static class Builder extends CsvBuilder<Builder> {
+
+        Repository repository;
+        Usage usage = Usage.TRAIN;
+        M5Features mf;
+        List<Integer> cardinality;
+
+        Builder() {
+            csvFormat =
+                    CSVFormat.DEFAULT
+                            .builder()
+                            .setHeader()
+                            .setSkipHeaderRecord(true)
+                            .setIgnoreHeaderCase(true)
+                            .setTrim(true)
+                            .build();
+            cardinality = new ArrayList<>();
+        }
+
+        MRL getMrl() {
+            return repository.dataset(
+                    Application.Tabular.ANY, BasicDatasets.GROUP_ID, ARTIFACT_ID, VERSION);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected Builder self() {
+            return this;
+        }
+
+        /**
+         * Set the repository containing the path.
+         *
+         * @param repository the repository containing the path
+         * @return this builder
+         */
+        public Builder setRepository(Repository repository) {
+            this.repository = repository;
+            return this;
+        }
+
+        /**
+         * Set the optional usage.
+         *
+         * @param usage the usage
+         * @return this builder
+         */
+        public Builder optUsage(Usage usage) {
+            this.usage = usage;
+            return this;
+        }
+
+        /**
+         * Add a feature to the features set of the filed name.
+         *
+         * @param name the name of the feature
+         * @param fieldName the field name
+         * @return this builder
+         */
+        public Builder addFeature(String name, FieldName fieldName) {
+            return addFeature(name, fieldName, false);
+        }
+
+        /**
+         * Add a feature to the features set of the filed name with onehot encoding.
+         *
+         * @param name the name of the feature
+         * @param fieldName the field name
+         * @param onehotEncode true if use onehot encoding
+         * @return this builder
+         */
+        public Builder addFeature(String name, FieldName fieldName, boolean onehotEncode) {
+            parseFeatures();
+            if (mf.categorical.contains(name)) {
+                Map<String, Integer> map = mf.featureToMap.get(name);
+                if (map == null) {
+                    return addFieldFeature(
+                            fieldName,
+                            new Feature(name, Featurizers.getStringFeaturizer(onehotEncode)));
+                }
+                cardinality.add(map.size());
+                return addFieldFeature(fieldName, new Feature(name, map, onehotEncode));
+            }
+            return addFieldFeature(fieldName, new Feature(name, true));
+        }
+
+        /**
+         * Returns the available features of this dataset.
+         *
+         * @return a list of feature names
+         */
+        public List<String> getAvailableFeatures() {
+            parseFeatures();
+            return mf.featureArray;
+        }
+
+        /**
+         * Build the new {@code M5Forecast}.
+         *
+         * @return the new {@code M5Forecast}
+         */
+        @Override
+        public M5Forecast build() {
+            validate();
+            return new M5Forecast(this);
+        }
+
+        private void parseFeatures() {
+            if (mf == null) {
+                String url =
+                        "https://mlrepo.djl.ai/dataset/tabular/ai/djl/basicdataset/m5forecast/1.0/m5forecast_parser.json";
+                try (InputStream is = Objects.requireNonNull(Utils.openUrl(url));
+                        Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
+                    mf = JsonUtils.GSON.fromJson(reader, M5Features.class);
+                } catch (IOException e) {
+                    throw new AssertionError("Failed to read m5forecast.json from classpath", e);
+                }
+            }
+        }
+    }
+
+    private static final class M5Features {
+
+        List<String> featureArray;
+        Set<String> categorical;
+        // categorical = String in featureArray its value indicate a String in featureToMap
+        Map<String, Map<String, Integer>> featureToMap;
+    }
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizer.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizer.java
new file mode 100644
index 000000000..48aa52f1f
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.basicdataset.tabular.utils.DynamicBuffer;
+import ai.djl.basicdataset.tabular.utils.Featurizer;
+
+import java.time.LocalDateTime;
+
+/**
+ * An interface that convert String to {@link LocalDateTime} as the start field of {@link
+ * ai.djl.timeseries.TimeSeriesData}.
+ */
+public interface TimeFeaturizer extends Featurizer {
+
+    /** {@inheritDoc} */
+    @Override
+    default void featurize(DynamicBuffer buf, String input) {
+        throw new IllegalArgumentException(
+                "Please use the other featurize for DateTimeFeaturizers");
+    }
+
+    /**
+     * Return the parsed time data.
+     *
+     * @param input the string input
+     * @return the parsed {@link LocalDateTime}
+     */
+    LocalDateTime featurize(String input);
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizers.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizers.java
new file mode 100644
index 000000000..0e4bf08f6
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeFeaturizers.java
@@ -0,0 +1,87 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.basicdataset.tabular.utils.Featurizer;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/** A utility class provides helper functions to create {@link TimeFeaturizer}. */
+public final class TimeFeaturizers {
+
+    private TimeFeaturizers() {}
+
+    /**
+     * Construct a {@link PatternTimeFeaturizer}.
+     *
+     * @param datePattern the pattern that dates are found in the data table column
+     * @return a new instance of {@link PatternTimeFeaturizer}
+     */
+    public static Featurizer getPatternTimeFeaturizer(String datePattern) {
+        return new PatternTimeFeaturizer(datePattern);
+    }
+
+    /**
+     * Construct a {@link ConstantTimeFeaturizer}.
+     *
+     * @param dateTime the date time to return for all
+     * @return a new instance of {@link ConstantTimeFeaturizer}
+     */
+    public static Featurizer getConstantTimeFeaturizer(LocalDateTime dateTime) {
+        return new ConstantTimeFeaturizer(dateTime);
+    }
+
+    /** A featurizer implemented for feature of date type. */
+    public static final class PatternTimeFeaturizer implements TimeFeaturizer {
+
+        String datePattern;
+
+        /**
+         * Constructs a {@link PatternTimeFeaturizer}.
+         *
+         * @param datePattern the pattern that dates are found in the data table column
+         */
+        PatternTimeFeaturizer(String datePattern) {
+            this.datePattern = datePattern;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LocalDateTime featurize(String input) {
+            return LocalDateTime.parse(input, DateTimeFormatter.ofPattern(datePattern));
+        }
+    }
+
+    /** A featurizer always return a constant date. */
+    public static final class ConstantTimeFeaturizer implements TimeFeaturizer {
+
+        LocalDateTime dateTime;
+
+        /**
+         * Constructs a {@link ConstantTimeFeaturizer}.
+         *
+         * @param dateTime the constant date
+         */
+        ConstantTimeFeaturizer(LocalDateTime dateTime) {
+            this.dateTime = LocalDateTime.from(dateTime);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LocalDateTime featurize(String input) {
+            return dateTime;
+        }
+    }
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeSeriesDataset.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeSeriesDataset.java
new file mode 100644
index 000000000..a963800f9
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/dataset/TimeSeriesDataset.java
@@ -0,0 +1,135 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.timeseries.TimeSeriesData;
+import ai.djl.timeseries.transform.TimeSeriesTransform;
+import ai.djl.training.dataset.RandomAccessDataset;
+import ai.djl.training.dataset.Record;
+
+import java.util.List;
+
+/** An abstract class for creating time series datasets. */
+public abstract class TimeSeriesDataset extends RandomAccessDataset {
+
+    protected List<TimeSeriesTransform> transformation;
+    protected int contextLength;
+
+    static final FieldName[] DATASET_FIELD_NAMES = {
+        FieldName.TARGET,
+        FieldName.FEAT_STATIC_CAT,
+        FieldName.FEAT_STATIC_REAL,
+        FieldName.FEAT_DYNAMIC_CAT,
+        FieldName.FEAT_DYNAMIC_REAL
+    };
+
+    protected TimeSeriesDataset(TimeSeriesBuilder<?> builder) {
+        super(builder);
+        transformation = builder.transformation;
+        contextLength = builder.contextLength;
+    }
+
+    /**
+     * {@code TimeSeriesDataset} override the get function so that it can preprocess the feature
+     * data as timeseries package way.
+     *
+     * <p>{@inheritDoc}
+     */
+    @Override
+    public Record get(NDManager manager, long index) {
+        TimeSeriesData data = getTimeSeriesData(manager, index);
+
+        if (transformation.isEmpty()) {
+            // For inference
+            return new Record(data.toNDList(), new NDList());
+        }
+        data = apply(manager, data);
+        if (!data.contains("PAST_" + FieldName.TARGET)
+                || !data.contains("FUTURE_" + FieldName.TARGET)) {
+            throw new IllegalArgumentException(
+                    "Transformation must include InstanceSampler to split data into past and future"
+                            + " part");
+        }
+
+        NDArray contextTarget = data.get("PAST_" + FieldName.TARGET).get("{}:", -contextLength + 1);
+        NDArray futureTarget = data.get("FUTURE_" + FieldName.TARGET);
+        NDList label = new NDList(contextTarget.concat(futureTarget, 0));
+
+        return new Record(data.toNDList(), label);
+    }
+
+    /**
+     * Return the {@link TimeSeriesData} for the given index from the {@code TimeSeriesDataset}.
+     *
+     * @param manager the manager to create data
+     * @param index the index
+     * @return the {@link TimeSeriesData}
+     */
+    public abstract TimeSeriesData getTimeSeriesData(NDManager manager, long index);
+
+    /**
+     * Apply to preprocess transformation on {@link TimeSeriesData}.
+     *
+     * @param manager default {@link NDManager}
+     * @param input data the {@link TimeSeriesData} to operate on
+     * @return the transformed data
+     */
+    private TimeSeriesData apply(NDManager manager, TimeSeriesData input) {
+        try (NDManager scope = manager.newSubManager()) {
+            input.values().forEach(array -> array.tempAttach(scope));
+            for (TimeSeriesTransform transform : transformation) {
+                input = transform.transform(manager, input, true);
+            }
+            input.values().forEach(array -> array.attach(manager));
+        }
+        return input;
+    }
+
+    /**
+     * Used to build a {@code TimeSeriesDataset}.
+     *
+     * @param <T> the builder type
+     */
+    public abstract static class TimeSeriesBuilder<T extends TimeSeriesBuilder<T>>
+            extends RandomAccessDataset.BaseBuilder<T> {
+
+        protected List<TimeSeriesTransform> transformation;
+        protected int contextLength;
+
+        /**
+         * Set the transformation for data preprocess.
+         *
+         * @param transformation the transformation
+         * @return this builder
+         */
+        public T setTransformation(List<TimeSeriesTransform> transformation) {
+            this.transformation = transformation;
+            return self();
+        }
+
+        /**
+         * Set the model prediction context length.
+         *
+         * @param contextLength the context length
+         * @return this builder
+         */
+        public T setContextLength(int contextLength) {
+            this.contextLength = contextLength;
+            return self();
+        }
+    }
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeFeature.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeFeature.java
index 11dec0346..3dbf02594 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeFeature.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeFeature.java
@@ -16,6 +16,7 @@ import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDManager;
 
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoField;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -124,7 +125,8 @@ public final class TimeFeature {
      * @return the result feature
      */
     public static NDArray weekOfYear(NDManager manager, List<LocalDateTime> index) {
-        throw new UnsupportedOperationException("weekOfYear is not supported yet");
+        float[] data = getFeature(index, a -> a.get(ChronoField.ALIGNED_WEEK_OF_YEAR));
+        return manager.create(data).sub(1f).div(52f).sub(0.5f);
     }
 
     private static float[] getFeature(
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeOffset.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeOffset.java
index 79b2e81dc..2fe0d2c65 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeOffset.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/timefeature/TimeOffset.java
@@ -25,7 +25,7 @@ public class TimeOffset {
     private int n;
 
     /**
-     * Constructs a new {@code DateOffset} instance.
+     * Constructs a new {@code TimeOffset} instance.
      *
      * @param name offset granularity including "D", "M" etc
      * @param n offset multiple
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/ExpectedNumInstanceSampler.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/ExpectedNumInstanceSampler.java
new file mode 100644
index 000000000..eb11ec9c6
--- /dev/null
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/ExpectedNumInstanceSampler.java
@@ -0,0 +1,76 @@
+/*
+ * 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.timeseries.transform;
+
+import ai.djl.ndarray.NDArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Keeps track of the average time series length and adjusts the probability per time point such
+ * that on average `num_instances` training examples are generated per time series.
+ */
+public class ExpectedNumInstanceSampler extends InstanceSampler {
+
+    private double numInstances;
+    private int totalLength;
+    private int n;
+
+    /**
+     * Construct a new instance of {@code ExpectedNumInstanceSampler}.
+     *
+     * @param axis the axis of the time series length
+     * @param minPast minimal pastime length
+     * @param minFuture minimal future time length
+     * @param numInstances number of training examples generated per time series on average
+     */
+    public ExpectedNumInstanceSampler(int axis, int minPast, int minFuture, double numInstances) {
+        super(axis, minPast, minFuture);
+        this.numInstances = numInstances;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<Integer> call(NDArray ts) {
+        int[] bound = getBounds(ts);
+        int windowSize = bound[1] - bound[0] + 1;
+
+        if (windowSize <= 0) {
+            return new ArrayList<>();
+        }
+
+        n += 1;
+        totalLength += windowSize;
+        int avgLength = totalLength / n;
+
+        if (avgLength <= 0) {
+            return new ArrayList<>();
+        }
+
+        double prob = numInstances / avgLength;
+        List<Integer> indices = new ArrayList<>();
+        Random random = new Random();
+        while (indices.isEmpty()) {
+            for (int i = 0; i < windowSize; i++) {
+                if (random.nextDouble() < prob) {
+                    indices.add(i + bound[0]);
+                }
+            }
+        }
+
+        return indices;
+    }
+}
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/InstanceSampler.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/InstanceSampler.java
index 839fad011..04c4815b2 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/InstanceSampler.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/InstanceSampler.java
@@ -50,9 +50,9 @@ public abstract class InstanceSampler {
      * @return the indices bound
      */
     public int[] getBounds(NDArray ts) {
-        int start = this.minPast;
-        int posAxis = this.axis < 0 ? ts.getShape().dimension() + this.axis : this.axis;
-        int end = (int) ts.getShape().get(posAxis) - this.minFuture;
+        int start = minPast;
+        int posAxis = axis < 0 ? ts.getShape().dimension() + axis : axis;
+        int end = (int) ts.getShape().get(posAxis) - minFuture;
         return new int[] {start, end};
     }
 
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/PredictionSplitSampler.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/PredictionSplitSampler.java
index 3e4907388..0d52a1772 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/PredictionSplitSampler.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/PredictionSplitSampler.java
@@ -40,7 +40,7 @@ public class PredictionSplitSampler extends InstanceSampler {
     /** {@inheritDoc} * */
     @Override
     public List<Integer> call(NDArray ts) {
-        int[] bound = this.getBounds(ts);
+        int[] bound = getBounds(ts);
         List<Integer> ret = new ArrayList<>();
         if (bound[0] < bound[1]) {
             ret.add(bound[1]);
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/TimeSeriesTransform.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/TimeSeriesTransform.java
index e8d3b2147..2edd2dc55 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/TimeSeriesTransform.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/TimeSeriesTransform.java
@@ -13,7 +13,12 @@
 package ai.djl.timeseries.transform;
 
 import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.Shape;
 import ai.djl.timeseries.TimeSeriesData;
+import ai.djl.timeseries.dataset.FieldName;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /** This interface is used for data transformation on the {@link TimeSeriesData}. */
 public interface TimeSeriesTransform {
@@ -23,7 +28,31 @@ public interface TimeSeriesTransform {
      *
      * @param manager The default manager for data process
      * @param data The data to be operated on
+     * @param isTrain Whether it is training
      * @return The result {@link TimeSeriesData}.
      */
-    TimeSeriesData transform(NDManager manager, TimeSeriesData data);
+    TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain);
+
+    /**
+     * Construct a list of {@link TimeSeriesTransform} that performs identity function.
+     *
+     * @return a list of identity {@link TimeSeriesTransform}
+     */
+    static List<TimeSeriesTransform> identityTransformation() {
+        List<TimeSeriesTransform> ret = new ArrayList<>();
+        ret.add(new IdentityTransform());
+        return ret;
+    }
+
+    /** An identity transformation. */
+    class IdentityTransform implements TimeSeriesTransform {
+
+        /** {@inheritDoc} */
+        @Override
+        public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
+            data.setField("PAST_" + FieldName.TARGET, data.get(FieldName.TARGET));
+            data.setField("FUTURE_" + FieldName.TARGET, manager.create(new Shape(0)));
+            return data;
+        }
+    }
 }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/convert/VstackFeatures.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/convert/VstackFeatures.java
index 24a1fa0d5..40c5f58cc 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/convert/VstackFeatures.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/convert/VstackFeatures.java
@@ -54,7 +54,7 @@ public class VstackFeatures implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         Convert.vstackFeatures(outputField, inputFields, dropInputs, hStack, data);
         return data;
     }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddAgeFeature.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddAgeFeature.java
index f8a32f6f5..1f2d4a76a 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddAgeFeature.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddAgeFeature.java
@@ -54,7 +54,7 @@ public class AddAgeFeature implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         return Feature.addAgeFeature(
                 manager, targetField, outputField, predictionLength, logScale, data);
     }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddObservedValuesIndicator.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddObservedValuesIndicator.java
index 108334805..b44d6749b 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddObservedValuesIndicator.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddObservedValuesIndicator.java
@@ -36,7 +36,7 @@ public class AddObservedValuesIndicator implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         Feature.addObservedValuesIndicator(manager, targetField, outputField, data);
         return data;
     }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddTimeFeature.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddTimeFeature.java
index 7ebabda20..565885641 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddTimeFeature.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/AddTimeFeature.java
@@ -59,7 +59,7 @@ public class AddTimeFeature implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         Feature.addTimeFeature(
                 manager,
                 startField,
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/Feature.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/Feature.java
index 751a10a09..0ade03d3f 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/Feature.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/feature/Feature.java
@@ -72,12 +72,47 @@ public final class Feature {
             int predictionLength,
             String freq,
             TimeSeriesData data) {
+        addTimeFeature(
+                manager,
+                startField,
+                targetField,
+                outputField,
+                timeFeatures,
+                predictionLength,
+                freq,
+                data,
+                false);
+    }
+
+    /**
+     * Adds a set of time features.
+     *
+     * @param manager default {@link NDManager}
+     * @param startField Field with the start time stamp of the time series
+     * @param targetField Field with the array containing the time series values
+     * @param outputField Field name for result
+     * @param timeFeatures list of time features to use
+     * @param predictionLength Prediction length
+     * @param freq Prediction time frequency
+     * @param data the {@link TimeSeriesData} to operate on
+     * @param isTrain Whether it is training
+     */
+    public static void addTimeFeature(
+            NDManager manager,
+            FieldName startField,
+            FieldName targetField,
+            FieldName outputField,
+            List<BiFunction<NDManager, List<LocalDateTime>, NDArray>> timeFeatures,
+            int predictionLength,
+            String freq,
+            TimeSeriesData data,
+            boolean isTrain) {
         if (timeFeatures.isEmpty()) {
             data.setField(outputField, null);
         }
 
         LocalDateTime start = data.getStartTime();
-        int length = targetTransformationLength(data.get(targetField), predictionLength, false);
+        int length = targetTransformationLength(data.get(targetField), predictionLength, isTrain);
 
         StringBuilder sb = new StringBuilder();
         sb.append(freq);
@@ -131,9 +166,37 @@ public final class Feature {
             int predictionLength,
             boolean logScale,
             TimeSeriesData data) {
+        return addAgeFeature(
+                manager, targetField, outputField, predictionLength, logScale, data, false);
+    }
+
+    /**
+     * Adds on 'age' feature to the {@link TimeSeriesData}.
+     *
+     * <p>The age feature starts with a small value at the start of the time series and grows over
+     * time.
+     *
+     * @param manager default {@link NDManager}
+     * @param targetField Field with target values (array) of time series
+     * @param outputField Field name to use for the output
+     * @param predictionLength Prediction length
+     * @param logScale If set to true the age feature grows logarithmically otherwise linearly over
+     *     time.
+     * @param data the {@link TimeSeriesData} to operate on
+     * @param isTrain Whether it is training
+     * @return the result {@link TimeSeriesData}
+     */
+    public static TimeSeriesData addAgeFeature(
+            NDManager manager,
+            FieldName targetField,
+            FieldName outputField,
+            int predictionLength,
+            boolean logScale,
+            TimeSeriesData data,
+            boolean isTrain) {
 
         NDArray targetData = data.get(targetField);
-        int length = targetTransformationLength(targetData, predictionLength, false);
+        int length = targetTransformationLength(targetData, predictionLength, isTrain);
 
         NDArray age = manager.arange(0, length, 1, targetData.getDataType());
         if (logScale) {
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/RemoveFields.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/RemoveFields.java
index aaa346636..75208b223 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/RemoveFields.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/RemoveFields.java
@@ -35,7 +35,7 @@ public class RemoveFields implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         Field.removeFields(fieldNames, data);
         return data;
     }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SelectField.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SelectField.java
index 03f39a32d..88997b501 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SelectField.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SelectField.java
@@ -32,7 +32,7 @@ public class SelectField implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         return Field.selectField(inputFields, data);
     }
 }
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SetField.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SetField.java
index 28523313a..fc7fddbd3 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SetField.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/field/SetField.java
@@ -37,7 +37,7 @@ public class SetField implements TimeSeriesTransform {
 
     /** {@inheritDoc} */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         NDArray copyValue = value.duplicate();
         Field.setField(outputField, copyValue, data);
         return data;
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/InstanceSplit.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/InstanceSplit.java
index f114e8c05..e5f56d811 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/InstanceSplit.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/InstanceSplit.java
@@ -115,7 +115,7 @@ public class InstanceSplit implements TimeSeriesTransform {
 
     /** {@inheritDoc}. */
     @Override
-    public TimeSeriesData transform(NDManager manager, TimeSeriesData data) {
+    public TimeSeriesData transform(NDManager manager, TimeSeriesData data, boolean isTrain) {
         Split.instanceSplit(
                 manager,
                 targetField,
diff --git a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/Split.java b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/Split.java
index 2b0689064..d0cfa78cc 100644
--- a/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/Split.java
+++ b/extensions/timeseries/src/main/java/ai/djl/timeseries/transform/split/Split.java
@@ -100,7 +100,7 @@ public final class Split {
                     shape = shape.slice(0, dims - 1).add(padLength);
                     NDArray padBlock = manager.full(shape, dummyValue, tsData.getDataType());
 
-                    pastPiece = padBlock.concat(tsData.get("..., :{}", i), -1);
+                    pastPiece = i == 0 ? padBlock : padBlock.concat(tsData.get("..., :{}", i), -1);
                 } else {
                     pastPiece = tsData.get("..., :{}", i);
                 }


Test patch:
diff --git a/extensions/timeseries/src/test/java/ai/djl/timeseries/dataset/M5ForecastTest.java b/extensions/timeseries/src/test/java/ai/djl/timeseries/dataset/M5ForecastTest.java
new file mode 100644
index 000000000..3582e1443
--- /dev/null
+++ b/extensions/timeseries/src/test/java/ai/djl/timeseries/dataset/M5ForecastTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.timeseries.dataset;
+
+import ai.djl.Model;
+import ai.djl.basicdataset.BasicDatasets;
+import ai.djl.basicdataset.tabular.utils.Feature;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.nn.Blocks;
+import ai.djl.nn.Parameter;
+import ai.djl.testing.TestRequirements;
+import ai.djl.timeseries.transform.TimeSeriesTransform;
+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 org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+public class M5ForecastTest {
+
+    // Here for the purpose of unittest, we use the fake M5 forecast data. The real data needs to be
+    // downloaded from https://www.kaggle.com/competitions/m5-forecasting-accuracy/data
+    @Test
+    public void testM5Forecast() throws IOException, TranslateException {
+        TestRequirements.weekly();
+        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();
+            M5Forecast.Builder builder =
+                    M5Forecast.builder()
+                            .optUsage(Dataset.Usage.TEST)
+                            .setRepository(BasicDatasets.REPOSITORY)
+                            .setTransformation(TimeSeriesTransform.identityTransformation())
+                            .setContextLength(4)
+                            .setSampling(32, true);
+            for (int i = 1; i <= 277; i++) {
+                builder.addFeature("w_" + i, FieldName.TARGET);
+            }
+            M5Forecast m5Forecast =
+                    builder.addFeature("state_id", FieldName.FEAT_STATIC_CAT)
+                            .addFeature("store_id", FieldName.FEAT_STATIC_CAT)
+                            .addFeature("cat_id", FieldName.FEAT_STATIC_CAT)
+                            .addFeature("dept_id", FieldName.FEAT_STATIC_CAT)
+                            .addFeature("item_id", FieldName.FEAT_STATIC_CAT)
+                            .addFieldFeature(
+                                    FieldName.START,
+                                    new Feature(
+                                            "date",
+                                            TimeFeaturizers.getConstantTimeFeaturizer(
+                                                    LocalDateTime.parse("2011-01-29T00:00"))))
+                            .build();
+
+            m5Forecast.prepare();
+
+            long size = m5Forecast.size();
+            Assert.assertEquals(size, 30490);
+
+            Record record = m5Forecast.get(manager, 0);
+            NDList data = record.getData();
+            NDList label = record.getLabels();
+            NDArray featStatCat = data.get(1);
+            Assert.assertEquals(featStatCat.toFloatArray(), new float[] {0f, 0f, 1f, 3f, 1437f});
+            Assert.assertEquals(label.head().toFloatArray(), new float[] {16f, 11f, 7f});
+
+            try (Trainer trainer = model.newTrainer(config)) {
+                Batch batch = trainer.iterateDataset(m5Forecast).iterator().next();
+                Assert.assertEquals(batch.getData().size(), 4);
+                Assert.assertEquals(batch.getLabels().size(), 1);
+                batch.close();
+            }
+        }
+    }
+}
diff --git a/extensions/timeseries/src/test/java/ai/djl/timeseries/dataset/package-info.java b/extensions/timeseries/src/test/java/ai/djl/timeseries/dataset/package-info.java
new file mode 100644
index 000000000..9c7c23b7f
--- /dev/null
+++ b/extensions/timeseries/src/test/java/ai/djl/timeseries/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 tests for the dataset module. */
+package ai.djl.timeseries.dataset;
diff --git a/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/DeepARTranslatorTest.java b/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/DeepARTranslatorTest.java
index 5bfe7e2f5..2ffa2ba7d 100644
--- a/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/DeepARTranslatorTest.java
+++ b/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/DeepARTranslatorTest.java
@@ -41,9 +41,9 @@ public class DeepARTranslatorTest {
 
         String modelUrl = "https://resources.djl.ai/test-models/mxnet/timeseries/deepar.zip";
         Map<String, Object> arguments = new ConcurrentHashMap<>();
-        arguments.put("prediction_length", 28);
-        DeepARTranslator.Builder builder = DeepARTranslator.builder(arguments);
-        DeepARTranslator translator = builder.build();
+        int predictionLength = 28;
+        arguments.put("prediction_length", predictionLength);
+        DeepARTranslator translator = DeepARTranslator.builder(arguments).build();
         Criteria<TimeSeriesData, Forecast> criteria =
                 Criteria.builder()
                         .setTypes(TimeSeriesData.class, Forecast.class)
@@ -54,6 +54,8 @@ public class DeepARTranslatorTest {
 
         try (NDManager manager = NDManager.newBaseManager()) {
             manager.getEngine().setRandomSeed(1);
+            // The "target" here is a fake series data which precedes the forecast series.
+            // It plays the role of input based on which the prediction is made.
             NDArray target = manager.arange(0.0f, 50.0f, (float) 50 / 1856);
 
             TimeSeriesData input = new TimeSeriesData(1);
@@ -63,15 +65,16 @@ public class DeepARTranslatorTest {
             try (ZooModel<TimeSeriesData, Forecast> model = criteria.loadModel();
                     Predictor<TimeSeriesData, Forecast> predictor = model.newPredictor()) {
                 Forecast forecast = predictor.predict(input);
-                // TODO： The argument prediction_length = 28 is too far away from predict().
-                // Here forecast.mean() is a predicted sequence of length = "prediction_length" set
-                // above. That forecast.mean() still has randomness here is because the model
-                // imported here was trained on a sparse dataSet with many zeros (inactive sale
-                // amount). So here the model also predict for such inactive data once in a while
-                // interweaving the active data.
-                // TODO: Either preprocess the data to choose a proper time spacing to reduce the
-                // sparsity of the sequence ie the inactive numbers (ie. 0).
-                Assert.assertEquals(forecast.mean().toFloatArray().length, 28);
+                // Here forecast.mean() is a predicted sequence of length "predictionLength"。
+                // Doing `System.out.println(forecast.mean());` the result still has randomness.
+                // This is because the model imported from
+                // https://resources.djl.ai/test-models/mxnet/timeseries/deepar.zip
+                // was trained on a sparse data with many zero sales (inactive sale
+                // amount). So during the inference it also predict for such inactive data once
+                // in a while interweaving the active non-zero data.
+                // A model trained on an aggregated dataset (aggregated by week) is presented in
+                // https://github.com/Carkham/m5_blog/blob/main/bloh.md
+                Assert.assertEquals(forecast.mean().toFloatArray().length, predictionLength);
             }
         }
     }
diff --git a/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/TransformerTranslatorTest.java b/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/TransformerTranslatorTest.java
index 49295b4f9..db6f22c6a 100644
--- a/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/TransformerTranslatorTest.java
+++ b/extensions/timeseries/src/test/java/ai/djl/timeseries/translator/TransformerTranslatorTest.java
@@ -16,7 +16,6 @@ import ai.djl.ModelException;
 import ai.djl.inference.Predictor;
 import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDManager;
-import ai.djl.ndarray.types.Shape;
 import ai.djl.repository.zoo.Criteria;
 import ai.djl.repository.zoo.ZooModel;
 import ai.djl.testing.TestRequirements;
@@ -55,7 +54,9 @@ public class TransformerTranslatorTest {
 
         try (NDManager manager = NDManager.newBaseManager()) {
             manager.getEngine().setRandomSeed(1);
-            NDArray target = manager.randomUniform(0f, 50f, new Shape(1857));
+            // The "target" here is a fake series data which precedes the forecast series.
+            // It plays the role of input based on which the prediction is made.
+            NDArray target = manager.arange(51f, 1f, -(float) 50 / 1856);
             TimeSeriesData input = new TimeSeriesData(1);
             input.setStartTime(LocalDateTime.parse("2011-01-29T00:00"));
             input.setField(FieldName.TARGET, target);
@@ -63,7 +64,15 @@ public class TransformerTranslatorTest {
             try (ZooModel<TimeSeriesData, Forecast> model = criteria.loadModel();
                     Predictor<TimeSeriesData, Forecast> predictor = model.newPredictor()) {
                 Forecast forecast = predictor.predict(input);
-                // TODO: forecast is random???
+                // Here forecast.mean() is a predicted sequence of length "predictionLength"。
+                // Doing `System.out.println(forecast.mean());` the result still has randomness.
+                // This is because the model imported from
+                // https://resources.djl.ai/test-models/mxnet/timeseries/deepar.zip
+                // was trained on a sparse data with many zero sales (inactive sale
+                // amount). So during the inference it also predict for such inactive data once
+                // in a while interweaving the active non-zero data.
+                // A model trained on an aggregated dataset (aggregated by week) is presented in
+                // https://github.com/Carkham/m5_blog/blob/main/bloh.md
                 Assert.assertEquals(forecast.mean().toFloatArray().length, 28);
             }
         }