Repository: deepjavalibrary/djl
Pull request #2063 (2022-10-06 18:30:21)

Add some unit tests
## Description ##

Brief description of what this PR is about

Add some unit tests

codecov-commenter: # [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/2063?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: **70.03**% // Decreases project coverage by **`-2.05%`** :warning:
> Coverage data is based on head [(`0934f00`)](https://codecov.io/gh/deepjavalibrary/djl/pull/2063?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: 70.69% of modified lines in pull request are covered.

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


```diff
@@             Coverage Diff              @@
##             master    #2063      +/-   ##
============================================
- Coverage     72.08%   70.03%   -2.06%     
- Complexity     5126     6032     +906     
============================================
  Files           473      600     +127     
  Lines         21970    26575    +4605     
  Branches       2351     2890     +539     
============================================
+ Hits          15838    18612    +2774     
- Misses         4925     6561    +1636     
- Partials       1207     1402     +195     
```


| [Impacted Files](https://codecov.io/gh/deepjavalibrary/djl/pull/2063?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/2063/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/2063/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/2063/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/2063/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/2063/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/2063/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: |
| [.../cv/translator/SemanticSegmentationTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2063/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%> (ø)` | |
| [.../ai/djl/modality/cv/translator/YoloTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2063/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1RyYW5zbGF0b3IuamF2YQ==) | `27.77% <0.00%> (+18.95%)` | :arrow_up: |
| [...modality/cv/translator/wrapper/FileTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2063/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3Ivd3JhcHBlci9GaWxlVHJhbnNsYXRvci5qYXZh) | `44.44% <ø> (ø)` | |
| [...y/cv/translator/wrapper/InputStreamTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/2063/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3Ivd3JhcHBlci9JbnB1dFN0cmVhbVRyYW5zbGF0b3IuamF2YQ==) | `44.44% <ø> (ø)` | |
| ... and [519 more](https://codecov.io/gh/deepjavalibrary/djl/pull/2063/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/2063?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/api/src/main/java/ai/djl/training/loss/TabNetLoss.java b/api/src/main/java/ai/djl/training/loss/TabNetClassificationLoss.java
similarity index 53%
rename from api/src/main/java/ai/djl/training/loss/TabNetLoss.java
rename to api/src/main/java/ai/djl/training/loss/TabNetClassificationLoss.java
index 296f4b93d..58756054d 100644
--- a/api/src/main/java/ai/djl/training/loss/TabNetLoss.java
+++ b/api/src/main/java/ai/djl/training/loss/TabNetClassificationLoss.java
@@ -16,29 +16,32 @@ import ai.djl.ndarray.NDArray;
 import ai.djl.ndarray.NDList;
 
 /**
- * Calculates the loss for tabNet. Actually, loss has been calculated through the forward function
- * of tabNet. What's done here is just getting the loss function from prediction.
+ * Calculates the loss for tabNet in Classification tasks.
+ *
+ * <p>Actually, tabNet is not only used for Supervised Learning, it's also widely used in
+ * unsupervised learning. For unsupervised learning, it should come from the decoder(aka
+ * attentionTransformer of tabNet)
  */
-public class TabNetLoss extends Loss {
-    /** Calculates the loss of a TabNet instance. */
-    public TabNetLoss() {
-        this("TabNetLoss");
+public final class TabNetClassificationLoss extends Loss {
+    /** Calculates the loss of a TabNet instance for regression tasks. */
+    public TabNetClassificationLoss() {
+        this("TabNetClassificationLoss");
     }
 
     /**
-     * Calculates the loss of a TabNet instance.
+     * Calculates the loss of a TabNet instance for regression tasks.
      *
      * @param name the name of the loss function
      */
-    public TabNetLoss(String name) {
+    public TabNetClassificationLoss(String name) {
         super(name);
     }
 
     /** {@inheritDoc} */
     @Override
     public NDArray evaluate(NDList labels, NDList predictions) {
-        // loss is already calculated inside the forward of tabNet
-        // so here we just need to get it out from prediction
-        return predictions.get(1);
+        return Loss.softmaxCrossEntropyLoss()
+                .evaluate(labels, new NDList(predictions.get(0)))
+                .add(predictions.get(1));
     }
 }
diff --git a/api/src/main/java/ai/djl/training/loss/TabNetRegressionLoss.java b/api/src/main/java/ai/djl/training/loss/TabNetRegressionLoss.java
new file mode 100644
index 000000000..21f290136
--- /dev/null
+++ b/api/src/main/java/ai/djl/training/loss/TabNetRegressionLoss.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.training.loss;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+
+/**
+ * Calculates the loss of tabNet for regression tasks.
+ *
+ * <p>Actually, tabNet is not only used for Supervised Learning, it's also widely used in
+ * unsupervised learning. For unsupervised learning, it should come from the decoder(aka
+ * attentionTransformer of tabNet)
+ */
+public class TabNetRegressionLoss extends Loss {
+    /** Calculates the loss of a TabNet instance for regression tasks. */
+    public TabNetRegressionLoss() {
+        this("TabNetRegressionLoss");
+    }
+
+    /**
+     * Calculates the loss of a TabNet instance for regression tasks.
+     *
+     * @param name the name of the loss function
+     */
+    public TabNetRegressionLoss(String name) {
+        super(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDArray evaluate(NDList labels, NDList predictions) {
+        // sparseLoss is already calculated inside the forward of tabNet
+        // so here we just need to get it out from prediction
+        return labels.singletonOrThrow()
+                .sub(predictions.get(0))
+                .square()
+                .mean()
+                .add(predictions.get(1));
+    }
+}
diff --git a/api/src/main/java/ai/djl/training/loss/YOLOv3Loss.java b/api/src/main/java/ai/djl/training/loss/YOLOv3Loss.java
new file mode 100644
index 000000000..462b86e28
--- /dev/null
+++ b/api/src/main/java/ai/djl/training/loss/YOLOv3Loss.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.training.loss;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDArrays;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.index.NDIndex;
+import ai.djl.ndarray.types.DataType;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.Activation;
+
+/**
+ * {@code YOLOv3Loss} is an implementation of {@link Loss}. It is used to compute the loss while
+ * training a YOLOv3 model for object detection. It involves computing the targets given the
+ * generated anchors, labels and predictions, and then computing the sum of class predictions and
+ * bounding box predictions.
+ */
+public final class YOLOv3Loss extends Loss {
+    // TODO: currently not finished, still have some bugs inside and it can only be trained with
+    // PyTorch Engine
+    /*
+       PRESETANCHORS shapes come from the K-means clustering of COCO dataset, which image size is 416*416
+       it can be reshaped into any shape like 256*256, just multiply each value with 256/416
+    */
+    private static final float[] PRESETANCHORS = {
+        116, 90, 156, 198, 373, 326,
+        30, 61, 62, 45, 59, 119,
+        10, 13, 16, 30, 33, 23
+    };
+
+    private float[] anchors;
+    private int numClasses;
+    private int boxAttr;
+    private Shape inputShape;
+    private float ignoreThreshold;
+    private NDManager manager;
+    private static final float EPSILON = 1e-7f;
+
+    private YOLOv3Loss(Builder builder) {
+        super(builder.name);
+        this.anchors = builder.anchorsArray;
+        this.numClasses = builder.numClasses;
+        this.boxAttr = builder.numClasses + 5; // 5 for x,y,h,w,c
+        this.inputShape = builder.inputShape;
+        this.ignoreThreshold = builder.ignoreThreshold;
+    }
+
+    /**
+     * Gets the preset anchors of YoloV3.
+     *
+     * @return the preset anchors of YoloV3
+     */
+    public static float[] getPresetAnchors() {
+        return PRESETANCHORS.clone();
+    }
+
+    /**
+     * Make the value of given NDArray between tMin and tMax.
+     *
+     * @param tList the given NDArray
+     * @param tMin the min value
+     * @param tMax the max value
+     * @return a NDArray where values are set between tMin and tMax
+     */
+    public NDArray clipByTensor(NDArray tList, float tMin, float tMax) {
+        NDArray result = tList.gte(tMin).mul(tList).add(tList.lt(tMin).mul(tMin));
+        result = result.lte(tMax).mul(result).add(result.gt(tMax).mul(tMax));
+        return result;
+    }
+
+    /**
+     * Calculates the MSELoss between prediction and target.
+     *
+     * @param prediction the prediction array
+     * @param target the target array
+     * @return the MSELoss between prediction and target
+     */
+    public NDArray mseLoss(NDArray prediction, NDArray target) {
+        return prediction.sub(target).pow(2);
+    }
+
+    /**
+     * Calculates the BCELoss between prediction and target.
+     *
+     * @param prediction the prediction array
+     * @param target the target array
+     * @return the BCELoss between prediction and target
+     */
+    public NDArray bceLoss(NDArray prediction, NDArray target) {
+        prediction = clipByTensor(prediction, EPSILON, (float) (1.0 - EPSILON));
+        return prediction
+                .log()
+                .mul(target)
+                .add(prediction.mul(-1).add(1).log().mul(target.mul(-1).add(1)))
+                .mul(-1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDArray evaluate(NDList labels, NDList predictions) {
+        manager = predictions.getManager();
+
+        /*
+        three outputs in total
+        NDArray out0 = predictions.get(0),  //Shape = (batchSize * 75 * 13 * 13) 75 = 3*(20+5)
+                out1 = predictions.get(1),  //Shape = (batchSize * 75 * 26 * 26)
+                out2 = predictions.get(2);  //Shape = (batchSize * 75 * 52 * 52)
+        */
+
+        NDArray[] lossComponents = new NDArray[3];
+        for (int i = 0; i < 3; i++) {
+            lossComponents[i] = evaluateOneOutput(i, predictions.get(i), labels.singletonOrThrow());
+        }
+
+        // calculate the final loss
+        return NDArrays.add(lossComponents);
+    }
+
+    /**
+     * Computes the Loss for one outputLayer.
+     *
+     * @param componentIndex which outputLayer does current input represent. the shape should be
+     *     (13*13,26*26,52*52)
+     * @param input one prediction layer of YOLOv3
+     * @param labels target labels. Must contain (offsetLabels, masks, classlabels)
+     * @return the total loss of a outputLayer
+     */
+    public NDArray evaluateOneOutput(int componentIndex, NDArray input, NDArray labels) {
+        int batchSize = (int) input.getShape().get(0);
+        int inW = (int) input.getShape().get(2);
+        int inH = (int) input.getShape().get(3);
+
+        NDArray prediction =
+                input.reshape(batchSize, 3, boxAttr, inW, inH)
+                        .transpose(1, 0, 3, 4, 2); // reshape into (3,batchSize,inW,inH,attrs)
+
+        // the prediction value of x,y,w,h which shape should be (3,batchSize,inW,inH)
+        NDArray x = Activation.sigmoid(prediction.get("...,0"));
+        NDArray y = Activation.sigmoid(prediction.get("...,1"));
+        NDArray w = prediction.get("...,2");
+        NDArray h = prediction.get("...,3");
+
+        // Confidence of whether there is an object and conditional probability of each class
+        // it should be reshaped into (batchSize,3)
+        NDArray conf = Activation.sigmoid(prediction.get("...,4")).transpose(1, 0, 2, 3);
+        NDArray predClass = Activation.sigmoid(prediction.get("...,5:")).transpose(1, 0, 2, 3, 4);
+
+        // get an NDList of groundTruth which contains boxLossScale and groundTruth
+        NDList truthList = getTarget(labels, inH, inW);
+
+        /*
+           boxLossScale shape should be: (batchSize,3,inW,inH)
+           groundTruth shape should be: (3,batchSize,inW,inH,boxAttr)
+        */
+        NDArray boxLossScale = truthList.get(0).transpose(1, 0, 2, 3);
+        NDArray groundTruth = truthList.get(1);
+
+        // iou shape should be: (batchSize,3 ,inW,inH)
+        NDArray iou =
+                calculateIOU(x, y, groundTruth.get("...,0:4"), componentIndex)
+                        .transpose(1, 0, 2, 3);
+
+        // get noObjMask and objMask
+        NDArray noObjMask =
+                NDArrays.where(
+                        iou.lte(ignoreThreshold), manager.ones(iou.getShape()), manager.create(0f));
+
+        NDArray objMask = iou.argMax(1).oneHot(3).transpose(0, 3, 1, 2);
+
+        objMask =
+                NDArrays.where(
+                        iou.gte(ignoreThreshold / 2),
+                        objMask,
+                        manager.zeros(objMask.getShape())); // to get rid of wrong ones
+        noObjMask = NDArrays.where(objMask.eq(1f), manager.zeros(noObjMask.getShape()), noObjMask);
+
+        NDArray xTrue = groundTruth.get("...,0");
+        NDArray yTrue = groundTruth.get("...,1");
+        NDArray wTrue = groundTruth.get("...,2");
+        NDArray hTrue = groundTruth.get("...,3");
+        NDArray classTrue = groundTruth.get("...,4:").transpose(1, 0, 2, 3, 4);
+
+        NDArray widths =
+                manager.create(
+                                new float[] {
+                                    anchors[componentIndex * 6],
+                                    anchors[componentIndex * 6 + 2],
+                                    anchors[componentIndex * 6 + 4]
+                                })
+                        .div(inputShape.get(0));
+
+        NDArray heights =
+                manager.create(
+                                new float[] {
+                                    anchors[componentIndex * 6 + 1],
+                                    anchors[componentIndex * 6 + 3],
+                                    anchors[componentIndex * 6 + 5]
+                                })
+                        .div(inputShape.get(1));
+
+        // three loss parts: box Loss, confidence Loss, and class Loss
+        NDArray boxLoss =
+                objMask.mul(boxLossScale)
+                        .mul(
+                                NDArrays.add(
+                                                xTrue.sub(x).pow(2),
+                                                yTrue.sub(y).pow(2),
+                                                wTrue.sub(
+                                                                w.exp()
+                                                                        .mul(
+                                                                                widths.broadcast(
+                                                                                                inH,
+                                                                                                inW,
+                                                                                                batchSize,
+                                                                                                3)
+                                                                                        .transpose(
+                                                                                                3,
+                                                                                                2,
+                                                                                                1,
+                                                                                                0)))
+                                                        .pow(2),
+                                                hTrue.sub(
+                                                                h.exp()
+                                                                        .mul(
+                                                                                heights.broadcast(
+                                                                                                inH,
+                                                                                                inW,
+                                                                                                batchSize,
+                                                                                                3)
+                                                                                        .transpose(
+                                                                                                3,
+                                                                                                2,
+                                                                                                1,
+                                                                                                0)))
+                                                        .pow(2))
+                                        .transpose(1, 0, 2, 3))
+                        .sum();
+
+        NDArray confLoss =
+                objMask.mul(
+                                conf.add(EPSILON)
+                                        .log()
+                                        .mul(-1)
+                                        .add(bceLoss(predClass, classTrue).sum(new int[] {4})))
+                        .sum();
+
+        NDArray noObjLoss = noObjMask.mul(conf.mul(-1).add(1 + EPSILON).log().mul(-1)).sum();
+
+        return boxLoss.add(confLoss).add(noObjLoss).div(batchSize);
+    }
+
+    /**
+     * Gets target NDArray for a given evaluator.
+     *
+     * @param labels the true labels
+     * @param inH the height of current layer
+     * @param inW the width of current layer
+     * @return an NDList of {boxLossScale and groundTruth}
+     */
+    public NDList getTarget(NDArray labels, int inH, int inW) {
+        int batchSize = (int) labels.size(0);
+
+        // the loss Scale of a box, used to punctuate small boxes
+        NDList boxLossComponents = new NDList();
+
+        // the groundTruth of a true object in pictures
+        NDList groundTruthComponents = new NDList();
+
+        // Shape of labels:(batchSize,objectNum,5)
+
+        for (int batch = 0; batch < batchSize; batch++) {
+            if (labels.get(batch).size(0) == 0) {
+                continue; // no object in current picture
+            }
+
+            NDArray boxLoss = manager.zeros(new Shape(inW, inH), DataType.FLOAT32);
+            NDArray groundTruth = manager.zeros(new Shape(inW, inH, boxAttr - 1), DataType.FLOAT32);
+
+            NDArray picture = labels.get(batch);
+            // the shape should be (objectNums,5)
+            NDArray xgt = picture.get("...,1").add(picture.get("...,3").div(2)).mul(inW);
+            // Center of x should be X value in labels and add half of the width and multiplies the
+            // input width to get which grid cell it's in
+            NDArray ygt = picture.get("...,2").add(picture.get("...,4").div(2)).mul(inH);
+            // Center of y is the same as well
+            NDArray wgt = picture.get("...,3");
+            // the width of the ground truth box
+            NDArray hgt = picture.get("...,4");
+            // the height of the ground truth box
+            // we should transform the presentation of true class, like
+            // [[0],[1],[2]]->[[1,0,0,...0],[0,1,0,...,0],[0,0,1,...,0]]
+            NDArray objectClass = picture.get("...,0");
+            objectClass = objectClass.oneHot(numClasses);
+
+            NDArray curLabel = labels.get(batch); // curLabel shape:(objectNum,5)
+            int objectNum = (int) curLabel.size(0);
+
+            for (int i = 0; i < objectNum; i++) {
+                // for each object, the middle of the object(x and y) should be in one grid cell of
+                // 13*13
+                // the tx and ty should indicate the grid cell and bx and by should indicate the
+                // movement from top-left of the grid cell
+                int tx = (int) xgt.get(i).getFloat();
+                int ty = (int) ygt.get(i).getFloat();
+                float bx = xgt.get(i).getFloat() - tx;
+                float by = ygt.get(i).getFloat() - ty;
+
+                String index = tx + "," + ty;
+                // set groundTruth
+                groundTruth.set(new NDIndex(index + ",0"), bx);
+                groundTruth.set(new NDIndex(index + ",1"), by);
+                groundTruth.set(new NDIndex(index + ",2"), wgt.getFloat(i));
+                groundTruth.set(new NDIndex(index + ",3"), hgt.getFloat(i));
+                groundTruth.set(new NDIndex(index + ",4:"), objectClass.get(i));
+
+                // set boxLoss
+                boxLoss.set(new NDIndex(index), 2 - wgt.getFloat(i) * hgt.getFloat(i));
+            }
+            boxLossComponents.add(boxLoss);
+            groundTruthComponents.add(groundTruth);
+        }
+
+        NDArray boxLossScale = NDArrays.stack(boxLossComponents).broadcast(3, batchSize, inW, inH);
+        NDArray groundTruth =
+                NDArrays.stack(groundTruthComponents)
+                        .broadcast(3, batchSize, inW, inH, boxAttr - 1);
+
+        return new NDList(boxLossScale, groundTruth);
+    }
+
+    /**
+     * Calculates the IOU between priori Anchors and groundTruth.
+     *
+     * @param predx the tx value of prediction
+     * @param predy the ty value of prediction
+     * @param groundTruth the groundTruth value of labels
+     * @param componentIndex the current component Index
+     * @return an NDArray of IOU
+     */
+    public NDArray calculateIOU(
+            NDArray predx, NDArray predy, NDArray groundTruth, int componentIndex) {
+        int inW = (int) predx.getShape().get(2);
+        int inH = (int) predx.getShape().get(3);
+        int strideW = (int) inputShape.get(0) / inW;
+        int strideH = (int) inputShape.get(1) / inH;
+
+        NDList iouComponent = new NDList();
+        // shape of predx, predy should all be (3,batchSize,inW,inH)
+        // shape of groundTruth should be (3,batchSize,inW,inH,4)
+        for (int i = 0; i < 3; i++) {
+            NDArray curPredx = predx.get(i);
+            NDArray curPredy = predy.get(i);
+            float width = anchors[componentIndex * 6 + 2 * i] / strideW;
+            float height = anchors[componentIndex * 6 + 2 * i + 1] / strideH;
+            NDArray predLeft = curPredx.sub(width / 2);
+            NDArray predRight = curPredx.add(width / 2);
+            NDArray predTop = curPredy.sub(height / 2);
+            NDArray predBottom = curPredy.add(height / 2);
+
+            NDArray truth = groundTruth.get(i);
+
+            NDArray trueLeft = truth.get("...,0").sub(truth.get("...,2").mul(inW).div(2));
+            NDArray trueRight = truth.get("...,0").add(truth.get("...,2").mul(inW).div(2));
+            NDArray trueTop = truth.get("...,1").sub(truth.get("...,3").mul(inH).div(2));
+            NDArray trueBottom = truth.get("...,1").add(truth.get("...,3").mul(inH).div(2));
+
+            NDArray left = NDArrays.maximum(predLeft, trueLeft);
+            NDArray right = NDArrays.minimum(predRight, trueRight);
+            NDArray top = NDArrays.maximum(predTop, trueTop);
+            NDArray bottom = NDArrays.minimum(predBottom, trueBottom);
+
+            NDArray inter = right.sub(left).mul(bottom.sub(top));
+            NDArray union =
+                    truth.get("...,2")
+                            .mul(inW)
+                            .mul(truth.get("...,3").mul(inH))
+                            .add(width * height)
+                            .sub(inter)
+                            .add(EPSILON); // should not be divided by zero
+
+            iouComponent.add(inter.div(union));
+        }
+
+        return NDArrays.stack(iouComponent);
+    }
+
+    /**
+     * Creates a new builder to build a {@link YOLOv3Loss}.
+     *
+     * @return a new builder;
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /** The Builder to construct a {@link YOLOv3Loss} object. */
+    public static class Builder {
+        private String name = "YOLOv3Loss";
+        private float[] anchorsArray = PRESETANCHORS;
+        private int numClasses = 20;
+        private Shape inputShape = new Shape(419, 419);
+        private float ignoreThreshold = 0.5f;
+
+        /**
+         * Sets the loss name of YoloV3Loss.
+         *
+         * @param name the name of loss function
+         * @return this {@code Builder}
+         */
+        public Builder setName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        /**
+         * Sets the preset anchors for YoloV3.
+         *
+         * @param anchorsArray the anchors in float array
+         * @return this {@code Builder}
+         */
+        public Builder setAnchorsArray(float[] anchorsArray) {
+            if (anchorsArray.length != PRESETANCHORS.length) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "setAnchorsArray requires anchors of length %d, but was given"
+                                        + " filters of length %d instead",
+                                PRESETANCHORS.length, anchorsArray.length));
+            }
+            this.anchorsArray = anchorsArray;
+            return this;
+        }
+
+        /**
+         * Sets the number of total classes.
+         *
+         * @param numClasses the number of total classes
+         * @return this {@code Builder}
+         */
+        public Builder setNumClasses(int numClasses) {
+            this.numClasses = numClasses;
+            return this;
+        }
+
+        /**
+         * Sets the shape of the input picture.
+         *
+         * @param inputShape the shape of input picture.
+         * @return this {@code Builder}
+         */
+        public Builder setInputShape(Shape inputShape) {
+            this.inputShape = inputShape;
+            return this;
+        }
+
+        /**
+         * Sets the ignoreThreshold for iou to check if we think it detects a picture.
+         *
+         * @param ignoreThreshold the ignore threshold
+         * @return this {@code Builder}
+         */
+        public Builder optIgnoreThreshold(float ignoreThreshold) {
+            this.ignoreThreshold = ignoreThreshold;
+            return this;
+        }
+
+        /**
+         * Builds a {@link YOLOv3Loss} instance.
+         *
+         * @return a {@link YOLOv3Loss} instance.
+         */
+        public YOLOv3Loss build() {
+            return new YOLOv3Loss(this);
+        }
+    }
+}
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/BananaDetection.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/BananaDetection.java
index a9555d353..b86f92078 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/BananaDetection.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/BananaDetection.java
@@ -88,6 +88,12 @@ public class BananaDetection extends ObjectDetectionDataset {
         return new PairList<>(Collections.singletonList(labels.get((int) index)));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public List<String> getClasses() {
+        return Collections.singletonList("banana");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected long availableSize() {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
index e45abd33e..1f36b4a5e 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/CocoDetection.java
@@ -45,14 +45,13 @@ import java.util.Optional;
  * <p>Each image might have different {@link ai.djl.ndarray.types.Shape}s.
  */
 public class CocoDetection extends ObjectDetectionDataset {
-
+    // TODO: Add synset logic for coco dataset
     private static final String ARTIFACT_ID = "coco";
     private static final String VERSION = "1.0";
 
     private Usage usage;
     private List<Path> imagePaths;
     private List<PairList<Long, Rectangle>> labels;
-
     private MRL mrl;
     private boolean prepared;
 
@@ -79,6 +78,13 @@ public class CocoDetection extends ObjectDetectionDataset {
         return labels.get(Math.toIntExact(index));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public List<String> getClasses() {
+        throw new UnsupportedOperationException(
+                "getClasses() for CocoDetection has not been implemented yet.");
+    }
+
     /** {@inheritDoc} */
     @Override
     public void prepare(Progress progress) throws IOException {
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/ObjectDetectionDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/ObjectDetectionDataset.java
index c48dd669f..84c7eb423 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/ObjectDetectionDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/ObjectDetectionDataset.java
@@ -20,13 +20,13 @@ import ai.djl.util.Pair;
 import ai.djl.util.PairList;
 
 import java.io.IOException;
+import java.util.List;
 
 /**
  * A helper to create {@link ai.djl.training.dataset.Dataset}s for {@link
  * ai.djl.Application.CV#OBJECT_DETECTION}.
  */
 public abstract class ObjectDetectionDataset extends ImageDataset {
-
     /**
      * Creates a new instance of {@link ObjectDetectionDataset} with the given necessary
      * configurations.
@@ -68,4 +68,11 @@ public abstract class ObjectDetectionDataset extends ImageDataset {
      * @throws IOException if the data could not be loaded
      */
     public abstract PairList<Long, Rectangle> getObjects(long index) throws IOException;
+
+    /**
+     * Returns the classes that detected objects in the dataset can be classified into.
+     *
+     * @return the classes that detected objects in the dataset can be classified into.
+     */
+    public abstract List<String> getClasses();
 }
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java b/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
index 770858c75..dfdcb175c 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/cv/PikachuDetection.java
@@ -127,6 +127,12 @@ public class PikachuDetection extends ObjectDetectionDataset {
         return new PairList<>(Collections.singletonList(labels.get((int) index)));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public List<String> getClasses() {
+        return Collections.singletonList("pikachu");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected long availableSize() {
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 43ff98dc7..9a8380c1c 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
@@ -46,6 +46,24 @@ public abstract class TabularDataset extends RandomAccessDataset {
         labels = builder.labels;
     }
 
+    /**
+     * Gets the feature size of current {@link TabularDataset}.
+     *
+     * @return the feature size
+     */
+    public int getFeatureSize() {
+        return features.size();
+    }
+
+    /**
+     * Gets the label size of current {@link TabularDataset}.
+     *
+     * @return the feature size
+     */
+    public int getLabelSize() {
+        return labels.size();
+    }
+
     /** {@inheritDoc} */
     @Override
     public Record get(NDManager manager, long index) {
diff --git a/djl-zero/src/main/java/ai/djl/zero/cv/ObjectDetection.java b/djl-zero/src/main/java/ai/djl/zero/cv/ObjectDetection.java
new file mode 100644
index 000000000..a10727f65
--- /dev/null
+++ b/djl-zero/src/main/java/ai/djl/zero/cv/ObjectDetection.java
@@ -0,0 +1,137 @@
+/*
+ * 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.zero.cv;
+
+import ai.djl.Model;
+import ai.djl.basicdataset.cv.ObjectDetectionDataset;
+import ai.djl.basicmodelzoo.cv.object_detection.ssd.SingleShotDetection;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.transform.ToTensor;
+import ai.djl.modality.cv.translator.SingleShotDetectionTranslator;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.Block;
+import ai.djl.nn.SequentialBlock;
+import ai.djl.repository.zoo.ZooModel;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.EasyTrain;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingConfig;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.evaluator.BoundingBoxError;
+import ai.djl.training.evaluator.SingleShotDetectionAccuracy;
+import ai.djl.training.listener.TrainingListener;
+import ai.djl.training.loss.SingleShotDetectionLoss;
+import ai.djl.translate.TranslateException;
+import ai.djl.translate.Translator;
+import ai.djl.zero.Performance;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** ObjectDetection takes an image and extract one or more main subjects in the image. */
+public final class ObjectDetection {
+    private ObjectDetection() {}
+
+    /**
+     * Trains the recommended object detection model on a custom dataset. Currently, trains a
+     * SingleShotDetection Model.
+     *
+     * <p>In order to train on a custom dataset, you must create a custom {@link
+     * ObjectDetectionDataset} to load your data.
+     *
+     * @param dataset the data to train with
+     * @param performance to determine the desired model tradeoffs
+     * @return the model as a {@link ZooModel} with the {@link Translator} included
+     * @throws IOException if the dataset could not be loaded
+     * @throws TranslateException if the translator has errors
+     */
+    public static ZooModel<Image, DetectedObjects> train(
+            ObjectDetectionDataset dataset, Performance performance)
+            throws IOException, TranslateException {
+        List<String> classes = dataset.getClasses();
+        int channels = dataset.getImageChannels();
+        int width =
+                dataset.getImageWidth()
+                        .orElseThrow(
+                                () ->
+                                        new IllegalArgumentException(
+                                                "The dataset must have a fixed image width"));
+        int height =
+                dataset.getImageHeight()
+                        .orElseThrow(
+                                () ->
+                                        new IllegalArgumentException(
+                                                "The dataset must have a fixed image height"));
+
+        Shape imageShape = new Shape(channels, height, width);
+
+        Dataset[] splitDataset = dataset.randomSplit(8, 2);
+        Dataset trainDataset = splitDataset[0];
+        Dataset validateDataset = splitDataset[1];
+
+        Block block = getSsdTrainBlock(classes.size());
+        Model model = Model.newInstance("ObjectDetection");
+        model.setBlock(block);
+
+        TrainingConfig trainingConfig =
+                new DefaultTrainingConfig(new SingleShotDetectionLoss())
+                        .addEvaluator(new SingleShotDetectionAccuracy("classAccuracy"))
+                        .addEvaluator(new BoundingBoxError("boundingBoxError"))
+                        .addTrainingListeners(TrainingListener.Defaults.basic());
+
+        try (Trainer trainer = model.newTrainer(trainingConfig)) {
+            trainer.initialize(new Shape(1).addAll(imageShape));
+            EasyTrain.fit(trainer, 50, trainDataset, validateDataset);
+        }
+
+        Translator<Image, DetectedObjects> translator =
+                SingleShotDetectionTranslator.builder()
+                        .addTransform(new ToTensor())
+                        .optSynset(classes)
+                        .optThreshold(0.6f)
+                        .build();
+
+        return new ZooModel<>(model, translator);
+    }
+
+    private static Block getSsdTrainBlock(int numClasses) {
+        int[] numFilters = {16, 32, 64};
+        SequentialBlock baseBlock = new SequentialBlock();
+        for (int numFilter : numFilters) {
+            baseBlock.add(SingleShotDetection.getDownSamplingBlock(numFilter));
+        }
+
+        List<List<Float>> sizes = new ArrayList<>();
+        List<List<Float>> ratios = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            ratios.add(Arrays.asList(1f, 2f, 0.5f));
+        }
+        sizes.add(Arrays.asList(0.2f, 0.272f));
+        sizes.add(Arrays.asList(0.37f, 0.447f));
+        sizes.add(Arrays.asList(0.54f, 0.619f));
+        sizes.add(Arrays.asList(0.71f, 0.79f));
+        sizes.add(Arrays.asList(0.88f, 0.961f));
+
+        return SingleShotDetection.builder()
+                .setNumClasses(numClasses)
+                .setNumFeatures(3)
+                .optGlobalPool(true)
+                .setRatios(ratios)
+                .setSizes(sizes)
+                .setBaseNetwork(baseBlock)
+                .build();
+    }
+}
diff --git a/djl-zero/src/main/java/ai/djl/zero/tabular/Tabular.java b/djl-zero/src/main/java/ai/djl/zero/tabular/Tabular.java
new file mode 100644
index 000000000..634466a0d
--- /dev/null
+++ b/djl-zero/src/main/java/ai/djl/zero/tabular/Tabular.java
@@ -0,0 +1,97 @@
+/*
+ * 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.zero.tabular;
+
+import ai.djl.Model;
+import ai.djl.basicdataset.tabular.TabularDataset;
+import ai.djl.basicmodelzoo.tabular.TabNet;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.Block;
+import ai.djl.repository.zoo.ZooModel;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.EasyTrain;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingConfig;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.listener.TrainingListener;
+import ai.djl.training.loss.TabNetRegressionLoss;
+import ai.djl.translate.NoopTranslator;
+import ai.djl.translate.TranslateException;
+import ai.djl.zero.Performance;
+
+import java.io.IOException;
+
+/** Tabular takes a NDList as input and output an NDList (for supervised learning). */
+public final class Tabular {
+
+    private Tabular() {}
+
+    /**
+     * Trains a Model on a custom dataset. Currently, trains a TabNet Model.
+     *
+     * <p>In order to train on a custom dataset, you must create a custom {@link TabularDataset} to
+     * load your data.
+     *
+     * @param dataset the data to train with
+     * @param performance to determine the desired model tradeoffs
+     * @return the model as a {@link ZooModel}
+     * @throws IOException if the dataset could not be loaded
+     * @throws TranslateException if the translator has errors
+     */
+    public static ZooModel<NDList, NDList> train(TabularDataset dataset, Performance performance)
+            throws IOException, TranslateException {
+        Dataset[] splitDataset = dataset.randomSplit(8, 2);
+        Dataset trainDataset = splitDataset[0];
+        Dataset validateDataset = splitDataset[1];
+        int featureSize = dataset.getFeatureSize();
+        int labelSize = dataset.getLabelSize();
+
+        Block block;
+        if (performance.equals(Performance.FAST)) {
+            // for fast cases, we set the number of independent layers and shared layers lower
+            block =
+                    TabNet.builder()
+                            .setInputDim(featureSize)
+                            .setOutDim(labelSize)
+                            .optNumIndependent(1)
+                            .optNumShared(1)
+                            .build();
+        } else if (performance.equals(Performance.BALANCED)) {
+            block = TabNet.builder().setInputDim(featureSize).setOutDim(labelSize).build();
+        } else {
+            // for accurate cases, we set the number of independent layers and shared layers higher
+            block =
+                    TabNet.builder()
+                            .setInputDim(featureSize)
+                            .setOutDim(labelSize)
+                            .optNumIndependent(4)
+                            .optNumShared(4)
+                            .build();
+        }
+
+        Model model = Model.newInstance("tabular");
+        model.setBlock(block);
+
+        TrainingConfig trainingConfig =
+                new DefaultTrainingConfig(new TabNetRegressionLoss())
+                        .addTrainingListeners(TrainingListener.Defaults.basic());
+
+        try (Trainer trainer = model.newTrainer(trainingConfig)) {
+            trainer.initialize(new Shape(1, featureSize));
+            EasyTrain.fit(trainer, 20, trainDataset, validateDataset);
+        }
+
+        return new ZooModel<>(model, new NoopTranslator());
+    }
+}
diff --git a/djl-zero/src/main/java/ai/djl/zero/tabular/package-info.java b/djl-zero/src/main/java/ai/djl/zero/tabular/package-info.java
new file mode 100644
index 000000000..42c8aebc1
--- /dev/null
+++ b/djl-zero/src/main/java/ai/djl/zero/tabular/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 training for Tabular({@link ai.djl.Application.Tabular}) tasks. */
+package ai.djl.zero.tabular;
diff --git a/examples/src/main/java/ai/djl/examples/training/TrainAirfoilWithTabNet.java b/examples/src/main/java/ai/djl/examples/training/TrainAirfoilWithTabNet.java
new file mode 100644
index 000000000..77e3fae0c
--- /dev/null
+++ b/examples/src/main/java/ai/djl/examples/training/TrainAirfoilWithTabNet.java
@@ -0,0 +1,111 @@
+/*
+ * 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.examples.training;
+
+import ai.djl.Model;
+import ai.djl.basicdataset.tabular.AirfoilRandomAccess;
+import ai.djl.basicmodelzoo.tabular.TabNet;
+import ai.djl.engine.Engine;
+import ai.djl.examples.training.util.Arguments;
+import ai.djl.metric.Metrics;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.Block;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.EasyTrain;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingResult;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.dataset.RandomAccessDataset;
+import ai.djl.training.listener.SaveModelTrainingListener;
+import ai.djl.training.listener.TrainingListener;
+import ai.djl.training.loss.TabNetRegressionLoss;
+import ai.djl.training.util.ProgressBar;
+import ai.djl.translate.TranslateException;
+
+import java.io.IOException;
+
+public final class TrainAirfoilWithTabNet {
+    private TrainAirfoilWithTabNet() {}
+
+    public static void main(String[] args) throws TranslateException, IOException {
+        TrainAirfoilWithTabNet.runExample(args);
+    }
+
+    public static TrainingResult runExample(String[] args) throws IOException, TranslateException {
+        Arguments arguments = new Arguments().parseArgs(args);
+        if (arguments == null) {
+            return null;
+        }
+
+        // Construct a tabNet instance
+        Block tabNet = TabNet.builder().setInputDim(5).setOutDim(1).build();
+
+        try (Model model = Model.newInstance("tabNet")) {
+            model.setBlock(tabNet);
+
+            // get the training and validation dataset
+            RandomAccessDataset[] randomAccessDatasets = getDataset(arguments);
+            RandomAccessDataset trainingSet = randomAccessDatasets[0];
+            RandomAccessDataset validateSet = randomAccessDatasets[1];
+
+            // setup training configuration
+            DefaultTrainingConfig config = setupTrainingConfig(arguments);
+
+            try (Trainer trainer = model.newTrainer(config)) {
+                trainer.setMetrics(new Metrics());
+
+                Shape inputShape = new Shape(arguments.getBatchSize(), 5);
+
+                // initialize trainer with proper input shape
+                trainer.initialize(inputShape);
+
+                EasyTrain.fit(trainer, arguments.getEpoch(), trainingSet, validateSet);
+
+                return trainer.getTrainingResult();
+            }
+        }
+    }
+
+    private static DefaultTrainingConfig setupTrainingConfig(Arguments arguments) {
+        String outputDir = arguments.getOutputDir();
+        SaveModelTrainingListener listener = new SaveModelTrainingListener(outputDir);
+        listener.setSaveModelCallback(
+                trainer -> {
+                    TrainingResult result = trainer.getTrainingResult();
+                    Model model = trainer.getModel();
+                    model.setProperty("Loss", String.format("%.5f", result.getValidateLoss()));
+                });
+
+        return new DefaultTrainingConfig(new TabNetRegressionLoss())
+                .optDevices(Engine.getInstance().getDevices(arguments.getMaxGpus()))
+                .addTrainingListeners(TrainingListener.Defaults.logging(outputDir))
+                .addTrainingListeners(listener);
+    }
+
+    private static RandomAccessDataset[] getDataset(Arguments arguments)
+            throws IOException, TranslateException {
+        AirfoilRandomAccess.Builder airfoilBuilder = AirfoilRandomAccess.builder();
+
+        // only train dataset is available, so we get train dataset and split them
+        airfoilBuilder.optUsage(Dataset.Usage.TRAIN).setSampling(arguments.getBatchSize(), true);
+
+        for (int i = 0; i < airfoilBuilder.getAvailableFeatures().size() - 1; i++) {
+            airfoilBuilder.addFeature(airfoilBuilder.getAvailableFeatures().get(i));
+        }
+
+        AirfoilRandomAccess airfoilRandomAccess = airfoilBuilder.build();
+        airfoilRandomAccess.prepare(new ProgressBar());
+        // split the dataset into
+        return airfoilRandomAccess.randomSplit(8, 2);
+    }
+}
diff --git a/examples/src/main/java/ai/djl/examples/training/TrainPikachu.java b/examples/src/main/java/ai/djl/examples/training/TrainPikachu.java
index 1c11df399..534f58d85 100644
--- a/examples/src/main/java/ai/djl/examples/training/TrainPikachu.java
+++ b/examples/src/main/java/ai/djl/examples/training/TrainPikachu.java
@@ -134,7 +134,7 @@ public final class TrainPikachu {
                         .optUsage(usage)
                         .optLimit(arguments.getLimit())
                         .optPipeline(pipeline)
-                        .setSampling(arguments.getBatchSize(), true)
+                        .setSampling(1, true)
                         .build();
         pikachuDetection.prepare(new ProgressBar());
 
diff --git a/examples/src/main/java/ai/djl/examples/training/TrainPikachuWithYOLOV3.java b/examples/src/main/java/ai/djl/examples/training/TrainPikachuWithYOLOV3.java
new file mode 100644
index 000000000..adc72e78e
--- /dev/null
+++ b/examples/src/main/java/ai/djl/examples/training/TrainPikachuWithYOLOV3.java
@@ -0,0 +1,113 @@
+/*
+ * 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.examples.training;
+
+import ai.djl.MalformedModelException;
+import ai.djl.Model;
+import ai.djl.basicdataset.cv.PikachuDetection;
+import ai.djl.basicmodelzoo.cv.object_detection.yolo.YOLOV3;
+import ai.djl.engine.Engine;
+import ai.djl.examples.training.util.Arguments;
+import ai.djl.metric.Metrics;
+import ai.djl.modality.cv.transform.ToTensor;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.EasyTrain;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingResult;
+import ai.djl.training.dataset.Dataset;
+import ai.djl.training.dataset.RandomAccessDataset;
+import ai.djl.training.listener.SaveModelTrainingListener;
+import ai.djl.training.listener.TrainingListener;
+import ai.djl.training.loss.YOLOv3Loss;
+import ai.djl.training.util.ProgressBar;
+import ai.djl.translate.Pipeline;
+import ai.djl.translate.TranslateException;
+
+import java.io.IOException;
+
+public final class TrainPikachuWithYOLOV3 {
+    private TrainPikachuWithYOLOV3() {}
+
+    public static void main(String[] args)
+            throws IOException, TranslateException, MalformedModelException {
+        TrainPikachuWithYOLOV3.runExample(args);
+    }
+
+    public static TrainingResult runExample(String[] args) throws IOException, TranslateException {
+        Arguments arguments = new Arguments().parseArgs(args);
+        if (arguments == null) {
+            return null;
+        }
+
+        try (Model model = Model.newInstance("pikachu-yolov3")) {
+            model.setBlock(YOLOV3.builder().setNumClasses(1).build());
+            RandomAccessDataset trainingSet = getDataset(Dataset.Usage.TRAIN, arguments);
+            RandomAccessDataset validateSet = getDataset(Dataset.Usage.TEST, arguments);
+
+            DefaultTrainingConfig config = setupTrainingConfig(arguments);
+
+            try (Trainer trainer = model.newTrainer(config)) {
+                trainer.setMetrics(new Metrics());
+
+                Shape inputShape = new Shape(1, 3, 256, 256);
+                trainer.initialize(inputShape);
+
+                EasyTrain.fit(trainer, arguments.getEpoch(), trainingSet, validateSet);
+
+                return trainer.getTrainingResult();
+            }
+        }
+    }
+
+    private static RandomAccessDataset getDataset(Dataset.Usage usage, Arguments arguments)
+            throws IOException {
+        Pipeline pipeline = new Pipeline(new ToTensor());
+        PikachuDetection pikachuDetection =
+                PikachuDetection.builder()
+                        .optUsage(usage)
+                        .optLimit(arguments.getLimit())
+                        .optPipeline(pipeline)
+                        .setSampling(8, true)
+                        .build();
+        pikachuDetection.prepare(new ProgressBar());
+
+        return pikachuDetection;
+    }
+
+    private static DefaultTrainingConfig setupTrainingConfig(Arguments arguments) {
+        String outputDir = arguments.getOutputDir();
+        SaveModelTrainingListener listener = new SaveModelTrainingListener(outputDir);
+        listener.setSaveModelCallback(
+                trainer -> {
+                    TrainingResult result = trainer.getTrainingResult();
+                    Model model = trainer.getModel();
+                    model.setProperty("Loss", String.format("%.5f", result.getValidateLoss()));
+                });
+
+        float[] anchorsArray = YOLOv3Loss.getPresetAnchors();
+        for (int i = 0; i < anchorsArray.length; i++) {
+            anchorsArray[i] = anchorsArray[i] * 256 / 416; // reshaping into the
+        }
+
+        return new DefaultTrainingConfig(
+                        YOLOv3Loss.builder()
+                                .setNumClasses(1)
+                                .setInputShape(new Shape(256, 256))
+                                .setAnchorsArray(anchorsArray)
+                                .build())
+                .optDevices(Engine.getInstance().getDevices(arguments.getMaxGpus()))
+                .addTrainingListeners(TrainingListener.Defaults.basic())
+                .addTrainingListeners(listener);
+    }
+}
diff --git a/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/YOLOV3.java b/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/YOLOV3.java
new file mode 100644
index 000000000..1a329cf54
--- /dev/null
+++ b/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/YOLOV3.java
@@ -0,0 +1,533 @@
+/*
+ * 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.basicmodelzoo.cv.object_detection.yolo;
+
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.util.NDImageUtils;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDArrays;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.DataType;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.AbstractBlock;
+import ai.djl.nn.Activation;
+import ai.djl.nn.Block;
+import ai.djl.nn.Blocks;
+import ai.djl.nn.ParallelBlock;
+import ai.djl.nn.SequentialBlock;
+import ai.djl.nn.convolutional.Conv2d;
+import ai.djl.nn.core.Linear;
+import ai.djl.nn.norm.BatchNorm;
+import ai.djl.nn.pooling.Pool;
+import ai.djl.training.ParameterStore;
+import ai.djl.util.PairList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * {@code YOLOV3} contains a generic implementation of yolov3 adapted from
+ * https://github.com/bubbliiiing/yolo3-pytorch (Original author bubbliiiing). Yolov3 is a fast and
+ * accurate model for ObjectDetection tasks.
+ *
+ * @see <a href="https://arxiv.org/abs/1804.02767">for more information and knowledge about
+ *     yolov3</a>
+ */
+public final class YOLOV3 extends AbstractBlock {
+    private static final byte VERSION = 1;
+    private SequentialBlock darkNet53; // backBone of YOLOv3
+
+    private Block lastLayer0;
+    private Block layer0Output;
+
+    private Block lastLayer1Conv;
+    private Block lastLayer1UpSample;
+    private Block lastLayer1;
+    private Block layer1Output;
+
+    private Block lastLayer2Conv;
+    private Block lastLayer2UpSample;
+    private Block lastLayer2;
+    private Block layer2Output;
+
+    static final int[] REPEATS = {
+        1, 2, 8, 8, 4
+    }; // the repeat times of the darkNet53 residual units
+
+    static final int[] FILTERS = {
+        32, 64, 128, 256, 512, 1024
+    }; // the filters of darkNet53 residual units
+
+    private YOLOV3(Builder builder) {
+        super(VERSION);
+        darkNet53 = addChildBlock("darkNet53", darkNet53(builder, true));
+        lastLayer0 =
+                addChildBlock(
+                        "lastLayer0",
+                        makeLastLayers(
+                                FILTERS[4],
+                                FILTERS[5],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+        layer0Output =
+                addChildBlock(
+                        "layer0Output",
+                        makeOutputLayers(
+                                FILTERS[5],
+                                3 * (builder.numClasses + 5),
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+
+        lastLayer1Conv =
+                addChildBlock(
+                        "lastLayer1Conv",
+                        convolutionBlock(256, 1, builder.batchNormMomentum, builder.leakyAlpha));
+        lastLayer1UpSample = addChildBlock("lastLayer1UpSample", upSampleBlockNearest());
+        lastLayer1 =
+                addChildBlock(
+                        "lastLayer1",
+                        makeLastLayers(
+                                FILTERS[3],
+                                FILTERS[4],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+        layer1Output =
+                addChildBlock(
+                        "layer1Output",
+                        makeOutputLayers(
+                                FILTERS[4],
+                                3 * (builder.numClasses + 5),
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+
+        lastLayer2Conv =
+                addChildBlock(
+                        "lastLayer2Conv",
+                        convolutionBlock(128, 1, builder.batchNormMomentum, builder.leakyAlpha));
+        lastLayer2UpSample = addChildBlock("lastLayer2UpSample", upSampleBlockNearest());
+        lastLayer2 =
+                addChildBlock(
+                        "lastLayer2",
+                        makeLastLayers(
+                                FILTERS[2],
+                                FILTERS[3],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+        layer2Output =
+                addChildBlock(
+                        "layer2Output",
+                        makeOutputLayers(
+                                FILTERS[3],
+                                3 * (builder.numClasses + 5),
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+    }
+
+    /**
+     * Builds a {@link Block} that represents an upSampleLayer(the nearest mode) for yolov3.
+     *
+     * @return a {@link Block} that represent an upSampleLayer for yolov3
+     */
+    public static Block upSampleBlockNearest() {
+        // transpose + upSample + transpose
+        return new SequentialBlock()
+                .addSingleton(array -> array.transpose(0, 2, 3, 1))
+                .addSingleton(
+                        array ->
+                                NDImageUtils.resize(
+                                        array,
+                                        (int) (array.getShape().get(1) * 2),
+                                        (int) (array.getShape().get(2) * 2),
+                                        Image.Interpolation.NEAREST))
+                .addSingleton(array -> array.transpose(0, 3, 1, 2));
+    }
+
+    /**
+     * Builds a {@link Block} that represents a conv-bn-leakyRelu unit for darkNet53.
+     *
+     * @param filters the number of filters for conv
+     * @param kernel the kernel size for conv
+     * @param batchNormMomentum the momentum for batchNorm layer
+     * @param leakyAlpha the alpha for leakyRelu activation
+     * @return a {@link Block} that represents a conv-bn-leakyRelu unit for darkNet53
+     */
+    public static Block convolutionBlock(
+            int filters, int kernel, float batchNormMomentum, float leakyAlpha) {
+        int pad = 0;
+        if (kernel > 0) {
+            pad = (kernel - 1) >> 1;
+        }
+        return new SequentialBlock()
+                .add(
+                        Conv2d.builder()
+                                .setFilters(filters)
+                                .setKernelShape(new Shape(kernel, kernel))
+                                .optPadding(new Shape(pad, pad))
+                                .build())
+                .add(BatchNorm.builder().optEpsilon(2E-5f).optMomentum(batchNormMomentum).build())
+                .add(Activation.leakyReluBlock(leakyAlpha));
+    }
+
+    /**
+     * Builds a {@link Block} that represents the feature head in yolov3.
+     *
+     * @param filtersIn the number of input filters
+     * @param filtersOut the number of output filters
+     * @param batchNormMomentum the momentum of batchNorm layer
+     * @param leakyAlpha the alpha value for leakyRelu activation
+     * @return a {@link Block} that represents the feature head in yolov3.
+     */
+    public static Block makeLastLayers(
+            int filtersIn, int filtersOut, float batchNormMomentum, float leakyAlpha) {
+        return new SequentialBlock()
+                .add(convolutionBlock(filtersIn, 1, batchNormMomentum, leakyAlpha))
+                .add(convolutionBlock(filtersOut, 3, batchNormMomentum, leakyAlpha))
+                .add(convolutionBlock(filtersIn, 1, batchNormMomentum, leakyAlpha))
+                .add(convolutionBlock(filtersOut, 3, batchNormMomentum, leakyAlpha))
+                .add(convolutionBlock(filtersIn, 1, batchNormMomentum, leakyAlpha));
+    }
+
+    /**
+     * Builds a {@link Block} that represents the output layer of yolov3.
+     *
+     * @param filtersOut the number of output filters
+     * @param outClass the number of output classes
+     * @param batchNormMomentum the momentum for batchNorm layer
+     * @param leakyAlpha the alpha for leakyRelu activation
+     * @return a {@link Block} that represents the output layer of yolov3.
+     */
+    public static Block makeOutputLayers(
+            int filtersOut, int outClass, float batchNormMomentum, float leakyAlpha) {
+        return new SequentialBlock()
+                .add(convolutionBlock(filtersOut, 3, batchNormMomentum, leakyAlpha))
+                .add(Conv2d.builder().setFilters(outClass).setKernelShape(new Shape(1, 1)).build());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected NDList forwardInternal(
+            ParameterStore parameterStore,
+            NDList inputs,
+            boolean training,
+            PairList<String, Object> params) {
+        NDList xList = darkNet53.forward(parameterStore, inputs, training);
+        NDArray x0 = xList.get(7);
+        NDArray x1 = xList.get(6);
+        NDArray x2 = xList.get(5);
+
+        // the first feature layer
+        NDList out0Branch = lastLayer0.forward(parameterStore, new NDList(x0), training);
+        NDList out0 = layer0Output.forward(parameterStore, out0Branch, training);
+        NDList x1In = lastLayer1Conv.forward(parameterStore, out0Branch, training);
+        x1In = lastLayer1UpSample.forward(parameterStore, x1In, training);
+        x1In = new NDList(x1In.singletonOrThrow().concat(x1, 1));
+
+        NDList out1Branch = lastLayer1.forward(parameterStore, x1In, training);
+        NDList out1 = layer1Output.forward(parameterStore, out1Branch, training);
+
+        NDList x2In = lastLayer2Conv.forward(parameterStore, out1Branch, training);
+        x2In = lastLayer2UpSample.forward(parameterStore, x2In, training);
+        x2In = new NDList(x2In.singletonOrThrow().concat(x2, 1));
+
+        // the third feature layer
+        NDList out2 = lastLayer2.forward(parameterStore, x2In, training);
+        out2 = layer2Output.forward(parameterStore, out2, training);
+
+        // Outputs
+        return new NDList(
+                out0.singletonOrThrow(), out1.singletonOrThrow(), out2.singletonOrThrow());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Shape[] getOutputShapes(Shape[] inputShapes) {
+        Shape[] current = inputShapes;
+        Shape[] outputs = new Shape[3];
+        Shape[] darkNetOutputs = new Shape[8];
+        int index = 0;
+        for (String name : children.keys()) {
+            Block block = children.get(name);
+            if (name.contains("darkNet")) {
+                darkNetOutputs = block.getOutputShapes(current);
+                current = new Shape[] {darkNetOutputs[7]};
+            } else if (name.contains("lastLayer")) {
+                if ("05lastLayer1UpSample".equals(name)) {
+                    current = block.getOutputShapes(current);
+                    current =
+                            new Shape[] {
+                                new Shape(
+                                        current[0].get(0),
+                                        current[0].get(1) + darkNetOutputs[6].get(1),
+                                        current[0].get(2),
+                                        current[0].get(3))
+                            };
+                } else if ("09lastLayer2UpSample".equals(name)) {
+                    current = block.getOutputShapes(current);
+                    current =
+                            new Shape[] {
+                                new Shape(
+                                        current[0].get(0),
+                                        current[0].get(1) + darkNetOutputs[5].get(1),
+                                        current[0].get(2),
+                                        current[0].get(3))
+                            };
+                } else {
+                    current = block.getOutputShapes(current);
+                }
+            } else if (!name.contains("Output")) {
+                current = block.getOutputShapes(current);
+            } else { // name.contains("Output")
+                Shape[] output = block.getOutputShapes(current);
+                outputs[index++] = output[0];
+            }
+        }
+        return outputs;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void initializeChildBlocks(NDManager manager, DataType dataType, Shape... inputShapes) {
+        Shape[] current = inputShapes;
+        Shape[] darkNetOutputs = new Shape[8];
+        for (String name : children.keys()) {
+            Block block = children.get(name);
+            block.initialize(manager, dataType, current);
+            if (name.contains("darkNet")) {
+                darkNetOutputs = block.getOutputShapes(current);
+                current = new Shape[] {darkNetOutputs[7]};
+            } else if (name.contains("lastLayer")) {
+                if ("05lastLayer1UpSample".equals(name)) {
+                    current = block.getOutputShapes(current);
+                    current =
+                            new Shape[] {
+                                new Shape(
+                                        current[0].get(0),
+                                        current[0].get(1) + darkNetOutputs[6].get(1),
+                                        current[0].get(2),
+                                        current[0].get(3))
+                            };
+                } else if ("09lastLayer2UpSample".equals(name)) {
+                    current = block.getOutputShapes(current);
+                    current =
+                            new Shape[] {
+                                new Shape(
+                                        current[0].get(0),
+                                        current[0].get(1) + darkNetOutputs[5].get(1),
+                                        current[0].get(2),
+                                        current[0].get(3))
+                            };
+                } else {
+                    current = block.getOutputShapes(current);
+                }
+            } else if (!name.contains("Output")) {
+                current = block.getOutputShapes(current);
+            } else { // name.contains("Output")
+                Shape[] output = block.getOutputShapes(current);
+            }
+        }
+    }
+
+    /**
+     * Builds a {@link Block} that a basic residual block unit used in DarkNet53.
+     *
+     * @param filters the output filter of the Convolutional Layer
+     * @param batchNormMomentum the momentum used for computing batchNorm
+     * @param leakyAlpha the alpha used in LeakyRelu Function
+     * @return a basic residual block unit used in DarkNet53
+     */
+    public static Block basicBlock(int filters, float batchNormMomentum, float leakyAlpha) {
+        SequentialBlock block = new SequentialBlock();
+        block.add(Conv2d.builder().setFilters(filters / 2).setKernelShape(new Shape(1, 1)).build())
+                .add(BatchNorm.builder().optEpsilon(2E-5f).optMomentum(batchNormMomentum).build())
+                .add(Activation.leakyReluBlock(leakyAlpha))
+                .add(
+                        Conv2d.builder()
+                                .setFilters(filters)
+                                .setKernelShape(new Shape(3, 3))
+                                .optPadding(new Shape(1, 1))
+                                .build())
+                .add(BatchNorm.builder().optEpsilon(2E-5f).optMomentum(batchNormMomentum).build())
+                .add(Activation.leakyReluBlock(leakyAlpha));
+        return new ParallelBlock(
+                list ->
+                        new NDList(
+                                NDArrays.add(
+                                        list.get(0).singletonOrThrow(),
+                                        list.get(1).singletonOrThrow())),
+                Arrays.asList(block, Blocks.identityBlock()));
+    }
+
+    /**
+     * Creates repeated Residual Blocks used in DarkNet53.
+     *
+     * @param filters the output filters of the final Convolutional Layer
+     * @param repeats the repeat times of a residual unit
+     * @param batchNormMomentum the momentum used for computing batchNorm
+     * @param leakyAlpha the alpha used in LeakyRelu Function
+     * @return several repeats of a residual block
+     */
+    public static Block makeLayer(
+            int filters, int repeats, float batchNormMomentum, float leakyAlpha) {
+        List<Block> layer = new ArrayList<>();
+        SequentialBlock convolutionalLayer = new SequentialBlock();
+        convolutionalLayer
+                .add(
+                        Conv2d.builder()
+                                .setFilters(filters)
+                                .setKernelShape(new Shape(3, 3))
+                                .optStride(new Shape(2, 2))
+                                .optPadding(new Shape(1, 1))
+                                .build())
+                .add(BatchNorm.builder().optEpsilon(2E-5f).optMomentum(batchNormMomentum).build())
+                .add(Activation.leakyReluBlock(leakyAlpha));
+        for (int i = 0; i < repeats; i++) {
+            layer.add(basicBlock(filters, batchNormMomentum, leakyAlpha));
+        }
+        return new SequentialBlock().add(convolutionalLayer).addAll(layer);
+    }
+
+    private static SequentialBlock darkNet53(Builder builder, boolean setReturnIntermediate) {
+        SequentialBlock darkNet53 = new SequentialBlock();
+        darkNet53.setReturnIntermediate(setReturnIntermediate); // return interMediate results;
+        darkNet53
+                .add(
+                        Conv2d.builder()
+                                .setFilters(FILTERS[0])
+                                .optPadding(new Shape(1, 1))
+                                .setKernelShape(new Shape(3, 3))
+                                .build())
+                .add(
+                        BatchNorm.builder()
+                                .optEpsilon(2E-5f)
+                                .optMomentum(builder.batchNormMomentum)
+                                .build())
+                .add(Activation.leakyReluBlock(builder.leakyAlpha))
+                .add(
+                        makeLayer(
+                                FILTERS[1],
+                                REPEATS[0],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha))
+                .add(
+                        makeLayer(
+                                FILTERS[2],
+                                REPEATS[1],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha))
+                .add(
+                        makeLayer(
+                                FILTERS[3],
+                                REPEATS[2],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha))
+                .add(
+                        makeLayer(
+                                FILTERS[4],
+                                REPEATS[3],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha))
+                .add(
+                        makeLayer(
+                                FILTERS[5],
+                                REPEATS[4],
+                                builder.batchNormMomentum,
+                                builder.leakyAlpha));
+        return darkNet53;
+    }
+
+    /**
+     * Creates a builder to build a {@link YOLOV3}.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /** The Builder to construct a {@link YOLOV3} object. */
+    public static final class Builder {
+        int numClasses = 20;
+        float batchNormMomentum = 0.9f;
+        float leakyAlpha = 0.1f;
+        int darkNetOutSize = 10;
+
+        /**
+         * Sets the number of classes for yolov3.
+         *
+         * @param numClasses the number of classes
+         * @return @return this {@code Builder}
+         */
+        public Builder setNumClasses(int numClasses) {
+            this.numClasses = numClasses;
+            return this;
+        }
+
+        /**
+         * Sets the momentum for batchNorm layer.
+         *
+         * @param batchNormMomentum the momentum for batchNorm layer
+         * @return this {@code Builder}
+         */
+        public Builder optBatchNormMomentum(float batchNormMomentum) {
+            this.batchNormMomentum = batchNormMomentum;
+            return this;
+        }
+
+        /**
+         * Sets the alpha for leakyRelu activation.
+         *
+         * @param leakyAlpha the alpha for leakyRelu activation
+         * @return this {@code Builder}
+         */
+        public Builder optLeakyAlpha(float leakyAlpha) {
+            this.leakyAlpha = leakyAlpha;
+            return this;
+        }
+
+        /**
+         * Sets the out size of darkNet for testing.
+         *
+         * @param darkNetOutSize the out size of darkNet
+         * @return this {@code Builder}
+         */
+        public Builder optDarkNetOutSize(int darkNetOutSize) {
+            this.darkNetOutSize = darkNetOutSize;
+            return this;
+        }
+
+        /**
+         * Builds a {@link YOLOV3} block.
+         *
+         * @return a {@link YOLOV3} block
+         */
+        public Block build() {
+            return new YOLOV3(this);
+        }
+
+        /**
+         * Builds a {@link Block} that represents the backbone of yolov3, which is called DarkNet53.
+         * This can be used for testing and transfer learning.
+         *
+         * @return a {@link Block} that represents darkNet53
+         */
+        public Block buildDarkNet() {
+            Block block = darkNet53(this, false);
+            return new SequentialBlock()
+                    .add(block)
+                    .add(Pool.globalAvgPool2dBlock())
+                    .add(Linear.builder().setUnits(darkNetOutSize).build());
+        }
+    }
+}
diff --git a/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/package-info.java b/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/package-info.java
new file mode 100644
index 000000000..5b25239de
--- /dev/null
+++ b/model-zoo/src/main/java/ai/djl/basicmodelzoo/cv/object_detection/yolo/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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 classes for the {@link ai.djl.Application.CV#OBJECT_DETECTION} models in the {@link
+ * ai.djl.basicmodelzoo.BasicModelZoo}.
+ */
+package ai.djl.basicmodelzoo.cv.object_detection.yolo;
diff --git a/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/TabNet.java b/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/TabNet.java
index dac0827ff..be2f8d007 100644
--- a/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/TabNet.java
+++ b/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/TabNet.java
@@ -69,10 +69,7 @@ public final class TabNet extends AbstractBlock {
         batchNorm =
                 addChildBlock(
                         "batchNorm",
-                        BatchNorm.builder()
-                                .optAxis(0)
-                                .optMomentum(builder.batchNormMomentum)
-                                .build());
+                        BatchNorm.builder().optMomentum(builder.batchNormMomentum).build());
         List<Block> sharedBlocks = new ArrayList<>();
         for (int i = 0; i < builder.numShared; i++) {
             sharedBlocks.add(
@@ -169,7 +166,6 @@ public final class TabNet extends AbstractBlock {
         featureBlock
                 .add(
                         GhostBatchNorm.builder()
-                                .optAxis(0)
                                 .optVirtualBatchSize(virtualBatchSize)
                                 .optMomentum(batchNormMomentum)
                                 .build())
@@ -237,6 +233,7 @@ public final class TabNet extends AbstractBlock {
             PairList<String, Object> params) {
         NDManager manager = inputs.getManager();
         NDArray input = inputs.singletonOrThrow();
+        input = input.reshape(input.size(0), input.size() / input.size(0)); // batch flatten
         NDArray x =
                 batchNorm.forward(parameterStore, new NDList(input), training).singletonOrThrow();
         NDArray xa =
@@ -347,11 +344,10 @@ public final class TabNet extends AbstractBlock {
                     addChildBlock(
                             "ghostBatchNorm",
                             GhostBatchNorm.builder()
-                                    .optAxis(0)
                                     .optVirtualBatchSize(virtualBatchSize)
                                     .optMomentum(batchNormMomentum)
                                     .build());
-            sparseMax = addChildBlock("sparseMax", new SparseMax(-1, 8));
+            sparseMax = addChildBlock("sparseMax", new SparseMax());
         }
 
         /** {@inheritDoc} */
diff --git a/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/package-info.java b/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/package-info.java
index ff3201da0..3046799c8 100644
--- a/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/package-info.java
+++ b/model-zoo/src/main/java/ai/djl/basicmodelzoo/tabular/package-info.java
@@ -12,7 +12,7 @@
  */
 
 /**
- * Contains classes for the computer vision section ({@link ai.djl.Application.Tabular}) of the
- * {@link ai.djl.basicmodelzoo.BasicModelZoo}.
+ * Contains classes for the tabular section ({@link ai.djl.Application.Tabular}) of the {@link
+ * ai.djl.basicmodelzoo.BasicModelZoo}.
  */
 package ai.djl.basicmodelzoo.tabular;


Test patch:
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/BigGANTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/BigGANTranslatorFactoryTest.java
new file mode 100644
index 000000000..172354a88
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/BigGANTranslatorFactoryTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class BigGANTranslatorFactoryTest {
+
+    private BigGANTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new BigGANTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 1);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, Float> arguments = new HashMap<>();
+        arguments.put("truncation", 0.6f);
+        try (Model model = Model.newInstance("test")) {
+            Translator<int[], Image[]> translator =
+                    factory.newInstance(int[].class, Image[].class, model, arguments);
+            Assert.assertTrue(translator instanceof BigGANTranslator);
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Input.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
new file mode 100644
index 000000000..941e2c3dc
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Classifications;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.translator.wrapper.FileTranslator;
+import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
+import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ImageClassificationTranslatorFactoryTest {
+
+    private ImageClassificationTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new ImageClassificationTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, Classifications> translator1 =
+                    factory.newInstance(Image.class, Classifications.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof ImageClassificationTranslator);
+
+            Translator<Path, Classifications> translator2 =
+                    factory.newInstance(Path.class, Classifications.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof FileTranslator);
+
+            Translator<URL, Classifications> translator3 =
+                    factory.newInstance(URL.class, Classifications.class, model, arguments);
+            Assert.assertTrue(translator3 instanceof UrlTranslator);
+
+            Translator<InputStream, Classifications> translator4 =
+                    factory.newInstance(InputStream.class, Classifications.class, model, arguments);
+            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+
+            Translator<Input, Output> translator5 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator5 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
new file mode 100644
index 000000000..79df6bcc9
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.translator.wrapper.FileTranslator;
+import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
+import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class InstanceSegmentationTranslatorFactoryTest {
+
+    private InstanceSegmentationTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new InstanceSegmentationTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, DetectedObjects> translator1 =
+                    factory.newInstance(Image.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof InstanceSegmentationTranslator);
+
+            Translator<Path, DetectedObjects> translator2 =
+                    factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof FileTranslator);
+
+            Translator<URL, DetectedObjects> translator3 =
+                    factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator3 instanceof UrlTranslator);
+
+            Translator<InputStream, DetectedObjects> translator4 =
+                    factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+
+            Translator<Input, Output> translator5 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator5 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/SimplePoseTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/SimplePoseTranslatorFactoryTest.java
new file mode 100644
index 000000000..0a2b54fd8
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/SimplePoseTranslatorFactoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.Joints;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SimplePoseTranslatorFactoryTest {
+
+    private SimplePoseTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new SimplePoseTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 2);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, Joints> translator1 =
+                    factory.newInstance(Image.class, Joints.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof SimplePoseTranslator);
+
+            Translator<Input, Output> translator2 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java
new file mode 100644
index 000000000..938a5cd0f
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.translator.wrapper.FileTranslator;
+import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
+import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SingleShotDetectionTranslatorFactoryTest {
+
+    private SingleShotDetectionTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new SingleShotDetectionTranslatorFactory();
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, DetectedObjects> translator1 =
+                    factory.newInstance(Image.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof SingleShotDetectionTranslator);
+
+            Translator<Path, DetectedObjects> translator2 =
+                    factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof FileTranslator);
+
+            Translator<URL, DetectedObjects> translator3 =
+                    factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator3 instanceof UrlTranslator);
+
+            Translator<InputStream, DetectedObjects> translator4 =
+                    factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+
+            Translator<Input, Output> translator5 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator5 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/StyleTransferTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/StyleTransferTranslatorFactoryTest.java
new file mode 100644
index 000000000..785ffa872
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/StyleTransferTranslatorFactoryTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class StyleTransferTranslatorFactoryTest {
+
+    private StyleTransferTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new StyleTransferTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 1);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, Image> translator =
+                    factory.newInstance(Image.class, Image.class, model, arguments);
+            Assert.assertTrue(translator instanceof StyleTransferTranslator);
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Input.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
new file mode 100644
index 000000000..fae1a595f
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.translator.wrapper.FileTranslator;
+import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
+import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class YoloTranslatorFactoryTest {
+
+    private YoloTranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new YoloTranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, DetectedObjects> translator1 =
+                    factory.newInstance(Image.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof YoloTranslator);
+
+            Translator<Path, DetectedObjects> translator2 =
+                    factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof FileTranslator);
+
+            Translator<URL, DetectedObjects> translator3 =
+                    factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator3 instanceof UrlTranslator);
+
+            Translator<InputStream, DetectedObjects> translator4 =
+                    factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+
+            Translator<Input, Output> translator5 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator5 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
new file mode 100644
index 000000000..a86c03fac
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.modality.cv.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.translator.wrapper.FileTranslator;
+import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
+import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.Translator;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class YoloV5TranslatorFactoryTest {
+
+    private YoloV5TranslatorFactory factory;
+
+    @BeforeClass
+    public void setUp() {
+        factory = new YoloV5TranslatorFactory();
+    }
+
+    @Test
+    public void testGetSupportedTypes() {
+        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+    }
+
+    @Test
+    public void testNewInstance() {
+        Map<String, String> arguments = new HashMap<>();
+        try (Model model = Model.newInstance("test")) {
+            Translator<Image, DetectedObjects> translator1 =
+                    factory.newInstance(Image.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator1 instanceof YoloV5Translator);
+
+            Translator<Path, DetectedObjects> translator2 =
+                    factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator2 instanceof FileTranslator);
+
+            Translator<URL, DetectedObjects> translator3 =
+                    factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator3 instanceof UrlTranslator);
+
+            Translator<InputStream, DetectedObjects> translator4 =
+                    factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
+            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+
+            Translator<Input, Output> translator5 =
+                    factory.newInstance(Input.class, Output.class, model, arguments);
+            Assert.assertTrue(translator5 instanceof ImageServingTranslator);
+
+            Assert.assertThrows(
+                    IllegalArgumentException.class,
+                    () -> factory.newInstance(Image.class, Output.class, model, arguments));
+        }
+    }
+}
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/package-info.java b/api/src/test/java/ai/djl/modality/cv/translator/package-info.java
new file mode 100644
index 000000000..e94737154
--- /dev/null
+++ b/api/src/test/java/ai/djl/modality/cv/translator/package-info.java
@@ -0,0 +1,14 @@
+/*
+ * 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 {@link ai.djl.modality.cv.translator}. */
+package ai.djl.modality.cv.translator;
diff --git a/examples/src/test/java/ai/djl/examples/training/TrainAirfoilWithTabNetTest.java b/examples/src/test/java/ai/djl/examples/training/TrainAirfoilWithTabNetTest.java
new file mode 100644
index 000000000..89a2dadcc
--- /dev/null
+++ b/examples/src/test/java/ai/djl/examples/training/TrainAirfoilWithTabNetTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.examples.training;
+
+import ai.djl.testing.TestRequirements;
+import ai.djl.training.TrainingResult;
+import ai.djl.translate.TranslateException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+
+public class TrainAirfoilWithTabNetTest {
+    @Test
+    public void testTrainAirfoilWithTabNet() throws TranslateException, IOException {
+        TestRequirements.engine("MXNet", "PyTorch");
+        String[] args = new String[] {"-g", "1", "-e", "20", "-b", "32"};
+        TrainingResult result = TrainAirfoilWithTabNet.runExample(args);
+        Assert.assertNotNull(result);
+        float loss = result.getValidateLoss();
+        Assert.assertTrue(loss < 50f, "Loss: " + loss);
+    }
+}
diff --git a/integration/src/main/java/ai/djl/integration/tests/model_zoo/object_detection/YOLOv3Test.java b/integration/src/main/java/ai/djl/integration/tests/model_zoo/object_detection/YOLOv3Test.java
new file mode 100644
index 000000000..3eef4f6c6
--- /dev/null
+++ b/integration/src/main/java/ai/djl/integration/tests/model_zoo/object_detection/YOLOv3Test.java
@@ -0,0 +1,153 @@
+/*
+ * 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.integration.tests.model_zoo.object_detection;
+
+import ai.djl.Model;
+import ai.djl.basicmodelzoo.cv.object_detection.yolo.YOLOV3;
+import ai.djl.engine.Engine;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.nn.Block;
+import ai.djl.nn.Parameter;
+import ai.djl.testing.Assertions;
+import ai.djl.testing.TestRequirements;
+import ai.djl.training.DefaultTrainingConfig;
+import ai.djl.training.EasyTrain;
+import ai.djl.training.Trainer;
+import ai.djl.training.TrainingConfig;
+import ai.djl.training.dataset.Batch;
+import ai.djl.training.initializer.Initializer;
+import ai.djl.training.loss.Loss;
+import ai.djl.training.loss.YOLOv3Loss;
+import ai.djl.translate.Batchifier;
+import ai.djl.util.PairList;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class YOLOv3Test {
+    @Test
+    public void testDarkNet() {
+        TrainingConfig config =
+                new DefaultTrainingConfig(Loss.softmaxCrossEntropyLoss())
+                        .optDevices(Engine.getInstance().getDevices(2))
+                        .optInitializer(Initializer.ONES, Parameter.Type.WEIGHT);
+        Block darkNet = YOLOV3.builder().buildDarkNet();
+        try (Model model = Model.newInstance("darkNet")) {
+            model.setBlock(darkNet);
+            try (Trainer trainer = model.newTrainer(config)) {
+                int batchSize = 1;
+                Shape inputShape = new Shape(batchSize, 3, 224, 224);
+                trainer.initialize(inputShape);
+                NDManager manager = trainer.getManager();
+                NDArray input = manager.ones(inputShape);
+                NDArray label = manager.ones(new Shape(batchSize, 1));
+                Batch batch =
+                        new Batch(
+                                manager.newSubManager(),
+                                new NDList(input),
+                                new NDList(label),
+                                batchSize,
+                                Batchifier.STACK,
+                                Batchifier.STACK,
+                                0,
+                                0);
+                PairList<String, Parameter> parameters = darkNet.getParameters();
+                EasyTrain.trainBatch(trainer, batch);
+                trainer.step();
+
+                // expected kernelShapes
+                NDArray expectedAtIndex0 =
+                        manager.ones(new Shape(32, 3, 3, 3)); // 32*3*3*3 for first layer
+                NDArray expectedAtIndex6 =
+                        manager.ones(new Shape(64, 32, 3, 3)); // 64*32*3*3 for second layer
+                NDArray expectedAtIndex120 =
+                        manager.ones(new Shape(128, 256, 1, 1)); // 128*256*1*1  for 20th layer
+                NDArray expectedLinear =
+                        manager.ones(
+                                new Shape(
+                                        10,
+                                        1024)); // outSize = 10, linear layer size should be 10*1024
+
+                // test if the shape of the kernel sizes are right
+                Assertions.assertAlmostEquals(
+                        parameters.get(0).getValue().getArray(), expectedAtIndex0);
+                Assertions.assertAlmostEquals(
+                        parameters.get(6).getValue().getArray(), expectedAtIndex6);
+                Assertions.assertAlmostEquals(
+                        parameters.get(120).getValue().getArray(), expectedAtIndex120);
+                Assertions.assertAlmostEquals(
+                        parameters.get(312).getValue().getArray(), expectedLinear);
+            }
+        }
+    }
+
+    @Test
+    public void testYoloV3() {
+        TestRequirements.engine("PyTorch");
+
+        float[] anchorsArray = YOLOv3Loss.getPresetAnchors();
+        for (int i = 0; i < anchorsArray.length; i++) {
+            anchorsArray[i] = anchorsArray[i] * 256 / 416; // reshaping into the
+        }
+        TrainingConfig config =
+                new DefaultTrainingConfig(
+                                YOLOv3Loss.builder()
+                                        .setNumClasses(1)
+                                        .setInputShape(new Shape(256, 256))
+                                        .setAnchorsArray(anchorsArray)
+                                        .build())
+                        .optDevices(Engine.getInstance().getDevices(2))
+                        .optInitializer(Initializer.ONES, Parameter.Type.WEIGHT);
+
+        Block yolov3 = YOLOV3.builder().setNumClasses(1).build();
+        try (Model model = Model.newInstance("yolov3")) {
+            model.setBlock(yolov3);
+            try (Trainer trainer = model.newTrainer(config)) {
+                int batchSize = 4;
+                Shape inputShape = new Shape(batchSize, 3, 256, 256);
+                trainer.initialize(inputShape);
+                NDManager manager = trainer.getManager();
+                NDArray input = manager.ones(inputShape);
+                NDArray label = manager.zeros(new Shape(batchSize, 1, 5));
+                Batch batch =
+                        new Batch(
+                                manager.newSubManager(),
+                                new NDList(input),
+                                new NDList(label),
+                                batchSize,
+                                Batchifier.STACK,
+                                Batchifier.STACK,
+                                0,
+                                0);
+                PairList<String, Parameter> parameters = yolov3.getParameters();
+                EasyTrain.trainBatch(trainer, batch);
+                trainer.step();
+
+                // expected shapes
+                Shape expectedAtIndex312 =
+                        new Shape(512, 1024, 1, 1); // the layer where output0 concat to 1
+                Shape expectedAtIndex330 =
+                        new Shape(1024, 512, 3, 3); // the layer where output1 concat to 2
+
+                // test if the shape of the kernel sizes are right
+                Assert.assertEquals(
+                        parameters.get(312).getValue().getArray().getShape(), expectedAtIndex312);
+                Assert.assertEquals(
+                        parameters.get(330).getValue().getArray().getShape(), expectedAtIndex330);
+            }
+        }
+    }
+}
diff --git a/integration/src/main/java/ai/djl/integration/tests/model_zoo/tabular/TabNetTest.java b/integration/src/main/java/ai/djl/integration/tests/model_zoo/tabular/TabNetTest.java
index 5dd6d2344..de66efd2d 100644
--- a/integration/src/main/java/ai/djl/integration/tests/model_zoo/tabular/TabNetTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/model_zoo/tabular/TabNetTest.java
@@ -29,7 +29,7 @@ import ai.djl.training.TrainingConfig;
 import ai.djl.training.dataset.Batch;
 import ai.djl.training.initializer.Initializer;
 import ai.djl.training.loss.Loss;
-import ai.djl.training.loss.TabNetLoss;
+import ai.djl.training.loss.TabNetRegressionLoss;
 import ai.djl.translate.Batchifier;
 import ai.djl.util.PairList;
 
@@ -61,14 +61,14 @@ public class TabNetTest {
     @Test
     public void testTrainingAndLogic() {
         TrainingConfig config =
-                new DefaultTrainingConfig(new TabNetLoss())
+                new DefaultTrainingConfig(new TabNetRegressionLoss())
                         .optDevices(Engine.getInstance().getDevices(2));
 
         Block tabNet = TabNet.builder().setOutDim(10).build();
         try (Model model = Model.newInstance("tabNet")) {
             model.setBlock(tabNet);
             try (Trainer trainer = model.newTrainer(config)) {
-                int batchSize = 1;
+                int batchSize = 32;
                 Shape inputShape = new Shape(batchSize, 128);
                 trainer.initialize(inputShape);
                 NDManager manager = trainer.getManager();
@@ -89,7 +89,7 @@ public class TabNetTest {
                 trainer.step();
                 // the gamma of batchNorm Layer
                 Assert.assertEquals(
-                        parameters.get(0).getValue().getArray().getShape(), new Shape(1));
+                        parameters.get(0).getValue().getArray().getShape(), new Shape(128));
 
                 // weight of shared fullyConnected Block0
                 Assert.assertEquals(