Repository: deepjavalibrary/djl
Pull request #1700 (2022-06-07 00:58:07)

Creates PreparedFeaturizer
This creates a new concept for the PreparedFeaturizer which are used by
TabularDatasets. There are several kinds of Featurizers which require knowing
the possible inputs in advance such as the oneHot categorical encoding or a
normalized numeric encoder. That is what these are for.

There is also a breaking change. The default StringFeaturizer is changed from a
standard category index to a oneHot prepared featurizer. I believe that the
oneHot one tends to perform better during training, so I think it stands as a
better default now that it doesn't require extra work to use.

As part of this, it also moves several classes including DynamicBuffer, Feature,
Featurizer, and Featurizers from basicdataset/utils to
basicdataset/tabular/utils as they are more strongly associated with tabular
rather than general datasets.


codecov-commenter: # [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1700?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) Report
> Merging [#1700](https://codecov.io/gh/deepjavalibrary/djl/pull/1700?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) (6620f19) 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.58%`.
> The diff coverage is `69.27%`.

```diff
@@             Coverage Diff              @@
##             master    #1700      +/-   ##
============================================
- Coverage     72.08%   70.50%   -1.59%     
- Complexity     5126     5516     +390     
============================================
  Files           473      522      +49     
  Lines         21970    24434    +2464     
  Branches       2351     2650     +299     
============================================
+ Hits          15838    17227    +1389     
- Misses         4925     5900     +975     
- Partials       1207     1307     +100     
```


| [Impacted Files](https://codecov.io/gh/deepjavalibrary/djl/pull/1700?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/1700/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/1700/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/1700/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/1700/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/1700/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/1700/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/1700/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: |
| [.../ai/djl/modality/cv/translator/YoloTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1700/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1RyYW5zbGF0b3IuamF2YQ==) | `8.33% <0.00%> (-0.50%)` | :arrow_down: |
| [.../modality/cv/translator/YoloTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1700/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/1700/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvci5qYXZh) | `5.38% <0.00%> (-0.31%)` | :arrow_down: |
| ... and [282 more](https://codecov.io/gh/deepjavalibrary/djl/pull/1700/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/1700?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/1700?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Last update [d626514...6620f19](https://codecov.io/gh/deepjavalibrary/djl/pull/1700?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/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AirfoilRandomAccess.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AirfoilRandomAccess.java
index adc2d5546..d100d3516 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AirfoilRandomAccess.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AirfoilRandomAccess.java
@@ -14,7 +14,7 @@ package ai.djl.basicdataset.tabular;
 
 import ai.djl.Application.Tabular;
 import ai.djl.basicdataset.BasicDatasets;
-import ai.djl.basicdataset.utils.Feature;
+import ai.djl.basicdataset.tabular.utils.Feature;
 import ai.djl.ndarray.NDList;
 import ai.djl.ndarray.NDManager;
 import ai.djl.ndarray.types.Shape;
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
index e81c9cb81..17e386c89 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
@@ -12,11 +12,6 @@
  */
 package ai.djl.basicdataset.tabular;
 
-import ai.djl.basicdataset.utils.DynamicBuffer;
-import ai.djl.basicdataset.utils.Feature;
-import ai.djl.ndarray.NDList;
-import ai.djl.ndarray.NDManager;
-import ai.djl.ndarray.types.Shape;
 import ai.djl.util.Progress;
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -25,7 +20,6 @@ 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.util.Collections;
@@ -50,16 +44,9 @@ public class CsvDataset extends TabularDataset {
 
     /** {@inheritDoc} */
     @Override
-    public NDList getRowFeatures(NDManager manager, long index, List<Feature> selected) {
-        CSVRecord record = csvRecords.get(Math.toIntExact(index));
-        DynamicBuffer bb = new DynamicBuffer();
-        for (Feature feature : selected) {
-            String name = feature.getName();
-            String value = record.get(name);
-            feature.getFeaturizer().featurize(bb, value);
-        }
-        FloatBuffer buf = bb.getBuffer();
-        return new NDList(manager.create(buf, new Shape(bb.getLength())));
+    protected String getCell(long rowIndex, String featureName) {
+        CSVRecord record = csvRecords.get(Math.toIntExact(rowIndex));
+        return record.get(featureName);
     }
 
     /** {@inheritDoc} */
@@ -75,6 +62,7 @@ public class CsvDataset extends TabularDataset {
             CSVParser csvParser = new CSVParser(reader, csvFormat);
             csvRecords = csvParser.getRecords();
         }
+        prepareFeaturizers();
     }
 
     private InputStream getCsvStream() throws IOException {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java
index c85b89539..6800f6468 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/DailyDelhiClimate.java
@@ -14,17 +14,14 @@ package ai.djl.basicdataset.tabular;
 
 import ai.djl.Application.Tabular;
 import ai.djl.basicdataset.BasicDatasets;
-import ai.djl.basicdataset.utils.DynamicBuffer;
-import ai.djl.basicdataset.utils.Feature;
-import ai.djl.basicdataset.utils.Featurizer;
+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.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;
@@ -188,24 +185,13 @@ public class DailyDelhiClimate extends CsvDataset {
          */
         public Builder addFeature(String name) {
             if ("date".equals(name)) {
-                return addDateFeature(name);
+                return addFeature(
+                        new Feature(name, Featurizers.getEpochDayFeaturizer("yyyy-MM-dd")));
             } 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.
          *
@@ -234,23 +220,4 @@ public class DailyDelhiClimate extends CsvDataset {
             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/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
index ae08b541f..08adebe39 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
@@ -12,11 +12,15 @@
  */
 package ai.djl.basicdataset.tabular;
 
-import ai.djl.basicdataset.utils.Feature;
+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.training.dataset.RandomAccessDataset;
 import ai.djl.training.dataset.Record;
+import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -63,7 +67,43 @@ public abstract class TabularDataset extends RandomAccessDataset {
      * @param selected the features to pull from the row
      * @return the features formatted as an {@link NDList}
      */
-    public abstract NDList getRowFeatures(NDManager manager, long index, List<Feature> selected);
+    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())));
+    }
+
+    /** Prepares the {@link ai.djl.basicdataset.tabular.utils.PreparedFeaturizer}s. */
+    protected void prepareFeaturizers() {
+        int availableSize = Math.toIntExact(availableSize());
+        List<Feature> featuresToPrepare = new ArrayList<>(features.size() + labels.size());
+        featuresToPrepare.addAll(features);
+        featuresToPrepare.addAll(labels);
+        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);
+            }
+        }
+    }
+
+    /**
+     * 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 abstract String getCell(long rowIndex, String featureName);
 
     /**
      * Used to build a {@link TabularDataset}.
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/utils/DynamicBuffer.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/DynamicBuffer.java
similarity index 97%
rename from basicdataset/src/main/java/ai/djl/basicdataset/utils/DynamicBuffer.java
rename to basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/DynamicBuffer.java
index 628ff608a..3ab3e9ed4 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/utils/DynamicBuffer.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/DynamicBuffer.java
@@ -10,7 +10,7 @@
  * 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.utils;
+package ai.djl.basicdataset.tabular.utils;
 
 import java.nio.FloatBuffer;
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Feature.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Feature.java
similarity index 98%
rename from basicdataset/src/main/java/ai/djl/basicdataset/utils/Feature.java
rename to basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Feature.java
index 46b91fd16..2d3a28205 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Feature.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Feature.java
@@ -10,7 +10,7 @@
  * 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.utils;
+package ai.djl.basicdataset.tabular.utils;
 
 import java.util.Map;
 
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizer.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizer.java
similarity index 95%
rename from basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizer.java
rename to basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizer.java
index a6c8d0517..215457a27 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizer.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizer.java
@@ -10,7 +10,7 @@
  * 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.utils;
+package ai.djl.basicdataset.tabular.utils;
 
 /** An interface that convert String to numeric data. */
 public interface Featurizer {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizers.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizers.java
new file mode 100644
index 000000000..c41490850
--- /dev/null
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/Featurizers.java
@@ -0,0 +1,196 @@
+/*
+ * 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.utils;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** A utility class provides helper functions to create {@link Featurizer}. */
+public final class Featurizers {
+
+    private static final Featurizer NUMERIC_FEATURIZER = new NumericFeaturizer();
+    private static final Featurizer STRING_FEATURIZER = new StringFeaturizer();
+
+    private Featurizers() {}
+
+    /**
+     * Returns the default numeric {@link Featurizer}.
+     *
+     * @return the default numeric {@link Featurizer}
+     */
+    public static Featurizer getNumericFeaturizer() {
+        return NUMERIC_FEATURIZER;
+    }
+
+    /**
+     * Returns the default String {@link Featurizer}.
+     *
+     * @return the default String {@link Featurizer}
+     */
+    public static Featurizer getStringFeaturizer() {
+        return getStringFeaturizer(true);
+    }
+
+    /**
+     * Returns the default String {@link Featurizer}.
+     *
+     * @param onehotEncode true to use onehot encoding
+     * @return the default String {@link Featurizer}
+     */
+    public static Featurizer getStringFeaturizer(boolean onehotEncode) {
+        if (onehotEncode) {
+            return new PreparedOneHotStringFeaturizer();
+        } else {
+            return STRING_FEATURIZER;
+        }
+    }
+
+    /**
+     * Returns a new instance of String {@link Featurizer}.
+     *
+     * @param map a map contains categorical value maps to index
+     * @param onehotEncode true to use onehot encoding
+     * @return a new instance of String {@link Featurizer}
+     */
+    public static Featurizer getStringFeaturizer(Map<String, Integer> map, boolean onehotEncode) {
+        if (onehotEncode) {
+            return new OneHotStringFeaturizer(map);
+        } else {
+            return new StringFeaturizer(map);
+        }
+    }
+
+    /**
+     * Constructs an {@link EpochDayFeaturizer} for representing dates using the epoch day (number
+     * of days since 1970-01-01).
+     *
+     * @param datePattern the pattern that dates are found in the data table column
+     * @return a new instance of {@link EpochDayFeaturizer}
+     */
+    public static Featurizer getEpochDayFeaturizer(String datePattern) {
+        return new EpochDayFeaturizer(datePattern);
+    }
+
+    private static final class NumericFeaturizer implements Featurizer {
+
+        /** {@inheritDoc} */
+        @Override
+        public void featurize(DynamicBuffer buf, String input) {
+            buf.put(Float.parseFloat(input));
+        }
+    }
+
+    private static class OneHotStringFeaturizer implements Featurizer {
+        protected Map<String, Integer> map;
+
+        public OneHotStringFeaturizer(Map<String, Integer> map) {
+            this.map = map;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void featurize(DynamicBuffer buf, String input) {
+            for (int i = 0; i < map.size(); ++i) {
+                buf.put(i == map.get(input) ? 1 : 0);
+            }
+        }
+    }
+
+    private static final class PreparedOneHotStringFeaturizer extends OneHotStringFeaturizer
+            implements PreparedFeaturizer {
+
+        public PreparedOneHotStringFeaturizer() {
+            super(null);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void prepare(List<String> inputs) {
+            map = new ConcurrentHashMap<>();
+            for (String input : inputs) {
+                if (!map.containsKey(input)) {
+                    map.put(input, map.size());
+                }
+            }
+        }
+    }
+
+    private static final class StringFeaturizer implements Featurizer {
+
+        private Map<String, Integer> map;
+        private boolean autoMap;
+
+        StringFeaturizer() {
+            this.map = new HashMap<>();
+            this.autoMap = true;
+        }
+
+        StringFeaturizer(Map<String, Integer> map) {
+            this.map = map;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void featurize(DynamicBuffer buf, String input) {
+            Integer index = map.get(input);
+            if (index != null) {
+                buf.put(index);
+                return;
+            }
+
+            if (!autoMap) {
+                throw new IllegalArgumentException("Value: " + input + " not found in the map.");
+            }
+            int value = map.size();
+            map.put(input, value);
+            buf.put(value);
+        }
+    }
+
+    /**
+     * A featurizer implemented for feature of date type using epoch day (number of days since
+     * 1970-01-01).
+     */
+    private static final class EpochDayFeaturizer implements Featurizer {
+
+        String datePattern;
+
+        /**
+         * Constructs a {@link EpochDayFeaturizer}.
+         *
+         * @param datePattern the pattern that dates are found in the data table column
+         */
+        EpochDayFeaturizer(String datePattern) {
+            this.datePattern = datePattern;
+        }
+
+        /**
+         * 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(datePattern));
+            long day = ld.toEpochDay();
+            buf.put(day);
+        }
+    }
+}
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/PreparedFeaturizer.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/PreparedFeaturizer.java
new file mode 100644
index 000000000..f5f3d33a5
--- /dev/null
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/PreparedFeaturizer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.utils;
+
+import java.util.List;
+
+/** A {@link Featurizer} that must be prepared with the possible feature values before use. */
+public interface PreparedFeaturizer extends Featurizer {
+
+    /**
+     * Prepares the featurizer with the list of possible inputs.
+     *
+     * @param inputs the possible inputs
+     */
+    void prepare(List<String> inputs);
+}
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/package-info.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/package-info.java
new file mode 100644
index 000000000..d9f4b7b4a
--- /dev/null
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/utils/package-info.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2021 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 utilities used within datasets that are {@link ai.djl.Application.Tabular}. */
+package ai.djl.basicdataset.tabular.utils;
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizers.java b/basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizers.java
deleted file mode 100644
index 1f0d76a96..000000000
--- a/basicdataset/src/main/java/ai/djl/basicdataset/utils/Featurizers.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.utils;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** A utility class provides helper functions to create {@link Featurizer}. */
-public final class Featurizers {
-
-    private static final Featurizer NUMERIC_FEATURIZER = new NumericFeaturizer();
-    private static final Featurizer STRING_FEATURIZER = new StringFeaturizer();
-
-    private Featurizers() {}
-
-    /**
-     * Returns the default numeric {@link Featurizer}.
-     *
-     * @return the default numeric {@link Featurizer}
-     */
-    public static Featurizer getNumericFeaturizer() {
-        return NUMERIC_FEATURIZER;
-    }
-
-    /**
-     * Returns the default String {@link Featurizer}.
-     *
-     * @return the default String {@link Featurizer}
-     */
-    public static Featurizer getStringFeaturizer() {
-        return STRING_FEATURIZER;
-    }
-
-    /**
-     * Returns a new instance of String {@link Featurizer}.
-     *
-     * @param map a map contains categorical value maps to index
-     * @param onehotEncode true if use onehot encode
-     * @return a new instance of String {@link Featurizer}
-     */
-    public static Featurizer getStringFeaturizer(Map<String, Integer> map, boolean onehotEncode) {
-        return new StringFeaturizer(map, onehotEncode);
-    }
-
-    private static final class NumericFeaturizer implements Featurizer {
-
-        /** {@inheritDoc} */
-        @Override
-        public void featurize(DynamicBuffer buf, String input) {
-            buf.put(Float.parseFloat(input));
-        }
-    }
-
-    private static final class StringFeaturizer implements Featurizer {
-
-        private Map<String, Integer> map;
-        private boolean onehotEncode;
-        private boolean autoMap;
-
-        StringFeaturizer() {
-            this.map = new HashMap<>();
-            this.autoMap = true;
-        }
-
-        StringFeaturizer(Map<String, Integer> map, boolean onehotEncode) {
-            this.map = map;
-            this.onehotEncode = onehotEncode;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void featurize(DynamicBuffer buf, String input) {
-            if (onehotEncode) {
-                for (int i = 0; i < map.size(); ++i) {
-                    buf.put(i == map.get(input) ? 1 : 0);
-                }
-                return;
-            }
-
-            Integer index = map.get(input);
-            if (index != null) {
-                buf.put(index);
-                return;
-            }
-
-            if (!autoMap) {
-                throw new IllegalArgumentException("Value: " + input + " not found in the map.");
-            }
-            int value = map.size();
-            map.put(input, value);
-            buf.put(value);
-        }
-    }
-}
diff --git a/examples/src/main/java/ai/djl/examples/training/transferlearning/TrainAmazonReviewRanking.java b/examples/src/main/java/ai/djl/examples/training/transferlearning/TrainAmazonReviewRanking.java
index 462292c69..ca098e067 100644
--- a/examples/src/main/java/ai/djl/examples/training/transferlearning/TrainAmazonReviewRanking.java
+++ b/examples/src/main/java/ai/djl/examples/training/transferlearning/TrainAmazonReviewRanking.java
@@ -16,9 +16,9 @@ import ai.djl.Application;
 import ai.djl.Model;
 import ai.djl.ModelException;
 import ai.djl.basicdataset.tabular.CsvDataset;
-import ai.djl.basicdataset.utils.DynamicBuffer;
-import ai.djl.basicdataset.utils.Feature;
-import ai.djl.basicdataset.utils.Featurizer;
+import ai.djl.basicdataset.tabular.utils.DynamicBuffer;
+import ai.djl.basicdataset.tabular.utils.Feature;
+import ai.djl.basicdataset.tabular.utils.Featurizer;
 import ai.djl.engine.Engine;
 import ai.djl.examples.training.util.Arguments;
 import ai.djl.inference.Predictor;
diff --git a/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java b/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
index 1e6553375..fb2694d1c 100644
--- a/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
+++ b/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
@@ -13,13 +13,7 @@
 package ai.djl.tablesaw;
 
 import ai.djl.basicdataset.tabular.TabularDataset;
-import ai.djl.basicdataset.utils.DynamicBuffer;
-import ai.djl.basicdataset.utils.Feature;
-import ai.djl.ndarray.NDList;
-import ai.djl.ndarray.NDManager;
-import ai.djl.ndarray.types.Shape;
 import ai.djl.util.Progress;
-import java.nio.FloatBuffer;
 import java.util.Collections;
 import java.util.List;
 import tech.tablesaw.api.Row;
@@ -39,16 +33,9 @@ public class TablesawDataset extends TabularDataset {
 
     /** {@inheritDoc} */
     @Override
-    public NDList getRowFeatures(NDManager manager, long index, List<Feature> selected) {
-        Row row = table.row(Math.toIntExact(index));
-        DynamicBuffer bb = new DynamicBuffer();
-        for (Feature feature : selected) {
-            String name = feature.getName();
-            String value = row.getString(name);
-            feature.getFeaturizer().featurize(bb, value);
-        }
-        FloatBuffer buf = bb.getBuffer();
-        return new NDList(manager.create(buf, new Shape(bb.getLength())));
+    protected String getCell(long rowIndex, String featureName) {
+        Row row = table.row(Math.toIntExact(rowIndex));
+        return row.getString(featureName);
     }
 
     /** {@inheritDoc} */
@@ -61,6 +48,7 @@ public class TablesawDataset extends TabularDataset {
     @Override
     public void prepare(Progress progress) {
         table = Table.read().usingOptions(readOptions);
+        prepareFeaturizers();
     }
 
     /**


Test patch:
diff --git a/basicdataset/src/test/java/ai/djl/basicdataset/AmazonReviewsTest.java b/basicdataset/src/test/java/ai/djl/basicdataset/AmazonReviewsTest.java
index dd9c31ee6..b5f6318ac 100644
--- a/basicdataset/src/test/java/ai/djl/basicdataset/AmazonReviewsTest.java
+++ b/basicdataset/src/test/java/ai/djl/basicdataset/AmazonReviewsTest.java
@@ -35,8 +35,9 @@ public class AmazonReviewsTest {
             dataset.prepare();
 
             Record record = dataset.get(manager, 0);
-            Assert.assertEquals(record.getData().get(0).getFloat(), 0);
-            Assert.assertEquals(record.getLabels().get(0).getFloat(), 4.0);
+            Assert.assertEquals(
+                    record.getData().singletonOrThrow().toFloatArray(), new float[] {1});
+            Assert.assertEquals(record.getLabels().singletonOrThrow().getFloat(), 4.0);
         }
     }
 }