Repository: PanyiDong/InsurAutoML (language: python)

Issue:
Fixes by autopep8 action
This is an auto-generated PR with fixes by autopep8.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml
index 53b75a9..417c025 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/formatting.yml
@@ -1,5 +1,5 @@
 name: Format python code
-on: push
+on: pull_request
 jobs:
   autopep8:
     runs-on: ubuntu-latest
diff --git a/InsurAutoML/__init__.py b/InsurAutoML/__init__.py
index 64a653c..bac012c 100644
--- a/InsurAutoML/__init__.py
+++ b/InsurAutoML/__init__.py
@@ -1,5 +1,5 @@
 """
-File: __init__.py
+File Name: __init__.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
@@ -7,11 +7,11 @@
 Project: InsurAutoML
 Latest Version: 0.2.3
 Relative Path: /InsurAutoML/__init__.py
-File: __init__.py
+File Created: Wednesday, 16th November 2022 7:23:10 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 4:04:57 pm
+Last Modified: Monday, 28th November 2022 11:37:19 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,13 +38,17 @@
 SOFTWARE.
 """
 
-# read version from file
-from .version import _get_version
-
-__version__ = _get_version()
 
-from ._base import no_processing, load_data
-from ._utils import (
+from .version import _get_version
+from .base import no_processing, load_data
+from .hpo import (
+    AutoTabular,
+    AutoTabularClassifier,
+    AutoTabularRegressor,
+    AutoTextClassifier,
+    AutoNextWordPrediction,
+)
+from .utils import (
     # random_guess,
     # random_index,
     # random_list,
@@ -72,6 +76,11 @@
     EDA,
 )
 
+__version__ = _get_version()
+
+# read version from file
+
+
 # from ._encoding import DataEncoding
 
 # from ._imputation import (
@@ -145,13 +154,6 @@
 #     TruncatedSVD,
 # )
 
-from ._hpo import (
-    AutoTabular,
-    AutoTabularClassifier,
-    AutoTabularRegressor,
-    AutoTextClassifier,
-    AutoNextWordPrediction,
-)
 
 # from ._model import classifiers, regressors
 
diff --git a/InsurAutoML/_archive/_database_info.json b/InsurAutoML/_archive/_database_info.json
deleted file mode 100644
index fa60814..0000000
--- a/InsurAutoML/_archive/_database_info.json
+++ /dev/null
@@ -1,226 +0,0 @@
-[
-    {
-    "filename" : "eudirectlapse",
-    "data name" : "European lapse dataset from the direct channel",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "lapse"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "None"
-        }
-    ]
-    },
-
-    {
-    "filename" : "credit",
-    "data name" : "German Credit dataset",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "class"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "None"
-        }
-    ]
-    },
-
-    {
-    "filename" : "norauto",
-    "data name" : "Norwegian fire insurance dataset",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "NbClaim"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "ClaimAmount"
-        }
-    ]
-    },
-
-    {
-    "filename" : "asiacomrisk",
-    "data name" : "Large commercial risks in Asia-Pacific",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "DR"
-        }
-    ]
-    },
-
-    {
-    "filename" : "ausprivautolong",
-    "data name" : "Automobile claim datasets in Australia",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "Claim"
-        }
-    ]
-    },
-
-    {
-    "filename" : "freclaimset2motor",
-    "data name" : "French individual claim settlements",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : ["ExpectCharge", "ExpectRecourse"]
-        }
-    ]
-    },
-
-    {
-    "filename" : "lossalae",
-    "data name" : "General Liability Claims",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "None"
-        }
-    ]
-    },
-    
-    {
-    "filename" : "fremotor1freq0304a",
-    "data name" : "French claims for private motor",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "PremTot"
-        }
-    ]
-    },
-
-    {
-    "filename" : "fremotor2freq9907b",
-    "data name" : "French claims for private motor",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "PremTot"
-        }
-    ]
-    },
-    
-    {
-    "filename" : "SOAGMI",
-    "data name" : "SOA Group Medical Insurance claim dataset",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "Loss"
-        }
-    ]
-    },
-
-    {
-    "filename" : "uslapseagent",
-    "data name" : "United States lapse dataset from tied-agent channel",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "annual.premium"
-        }
-    ]
-    },    
-  
-    {
-    "filename" : "usautoBI",
-    "data name" : "Automobile bodily injury claim dataset",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "LOSS"
-        }
-    ]
-    }, 
-  
-    {
-    "filename" : "freMPL1",
-    "data name" : "French Motor Personal Line datasets",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "ClaimInd"
-        },
-        {
-            "task_type" : "regression",
-            "response" : "None"
-        }
-    ]
-    },
-
-    {
-    "filename" : "auscathist",
-    "data name" : "Australian catastrophe historic",
-    "data source" : "CASdatasets",
-    "property" : [
-        {
-            "task_type" : "classification",
-            "response" : "None"
-        },
-        {
-            "task_type" : "regression",
-            "response" : ["OriginalCost", "NormCost2011", "NormCost2014"]
-        }
-    ]
-    }
-
-]
\ No newline at end of file
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_multimodal.py b/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_multimodal.py
deleted file mode 100644
index fa9f0b2..0000000
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_multimodal.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""
-File Name: _multimodal.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_multimodal.py
-File Created: Sunday, 13th November 2022 5:15:00 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:26:21 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-"""
-List of methods:
-                    Txt           Cat           Con
-FusToken:      tokenize      encoding      encoding
-FusEmbed:     embedding     embedding     embedding
-FusModel:         model         model         model
-
-FusToken: Txt/Cat/Con are only tokenized to numerics and fuse those heads together for whole model training.
-FusEmbed: Txt/Cat/Con are further embedded with a few layers of NNs and fuse those embeddings together for later half of model training.
-FusModel: Txt/Cat/Con are almost trained separately and just fused together for unified prediction.
-"""
-
-"""
-List of classes need for multimodal neural architecture search.
-1. entire network space (whole vs lint)
-2. heads (before fuse)
-3. prediction (after fuse for FusModel)
-"""
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_ops.py b/InsurAutoML/_experimental/_nn/_nni/_nas/_ops.py
deleted file mode 100644
index 239122c..0000000
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_ops.py
+++ /dev/null
@@ -1,422 +0,0 @@
-"""
-File Name: _ops.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_ops.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:26:55 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from turtle import forward
-import torch
-import torch.nn as nn
-
-##########################################################################
-# constants and dictionaries
-
-activation_dict = {
-    # "identity": nn.Identity(),
-    "relu": nn.ReLU(),
-    "sigmoid": nn.Sigmoid(),
-    "tanh": nn.Tanh(),
-}
-
-##########################################################################
-# basic components
-
-
-class StdConvBN(nn.Module):
-
-    """
-    standard 1x1 convolution with batch normalization and ReLU activation
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=1,
-        bias=False,
-    ):
-        super().__init__()
-
-        self.module = nn.Sequential(
-            nn.Conv2d(
-                in_channels,
-                out_channels,
-                kernel_size=kernel_size,
-                stride=1,
-                padding=0,
-                bias=bias,
-            ),
-            nn.BatchNorm2d(out_channels, affine=False),
-            nn.ReLU(),
-        )
-
-    def forward(self, X):
-        return self.module(X)
-
-
-class AvgPool(nn.Module):
-    def __init__(
-        self,
-        kernel_size=3,
-        stride=1,
-        padding=1,
-    ):
-        super().__init__()
-
-        self.module = nn.AvgPool2d(
-            kernel_size, stride, padding, count_include_pad=False
-        )
-
-    def forward(self, X):
-        return self.module(X)
-
-
-class MaxPool(nn.Module):
-    def __init__(
-        self,
-        kernel_size=3,
-        stride=1,
-        padding=1,
-    ):
-        super().__init__()
-
-        self.module = nn.MaxPool2d(kernel_size, stride, padding)
-
-    def forward(self, X):
-        return self.module(X)
-
-
-class DepthwiseSeparableConv(nn.Module):
-
-    """
-    Depth-wise Separable Convolution
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        depth=1,
-        kernel_size=3,
-        stride=1,
-        padding=0,
-        bias=False,
-    ):
-        super().__init__()
-        self.depthwise = nn.Conv2d(
-            in_channels,
-            in_channels * depth,
-            kernel_size=kernel_size,
-            stride=stride,
-            padding=padding,
-            groups=in_channels,
-            bias=bias,
-        )
-        self.pointwise = nn.Conv2d(
-            in_channels * depth, out_channels, kernel_size=1, bias=bias
-        )
-
-    def forward(self, X):
-
-        out = self.depthwise(X)
-        out = self.pointwise(out)
-
-        return out
-
-
-class FactorizedReduce(nn.Module):
-
-    """
-    Factorized Reduction: reduce the number of channels and reshape the channels
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=1,
-        stride=2,
-        padding=0,
-        affine=False,
-    ):
-        super().__init__()
-        self.conv1 = nn.Conv2d(
-            in_channels,
-            out_channels // 2,
-            kernel_size=kernel_size,
-            stride=stride,
-            padding=padding,
-            bias=False,
-        )
-        self.conv2 = nn.Conv2d(
-            in_channels,
-            out_channels // 2,
-            kernel_size=kernel_size,
-            stride=stride,
-            padding=padding,
-            bias=False,
-        )
-        self.postprocess = nn.BatchNorm2d(out_channels, affine=affine)
-
-    def forward(self, x):
-        out = torch.cat([self.conv1(x), self.conv2(x[:, :, 1:, 1:])], dim=1)
-        out = self.postprocess(out)
-        return out
-
-
-##########################################################################
-# combination components
-
-
-class ChannelCalibration(nn.Module):
-
-    """
-    Reset channel size for concat
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-    ):
-        super().__init__()
-
-        self.need_calib = False
-        if in_channels != out_channels:
-            self.need_calib = True
-            self.module = StdConvBN(in_channels, out_channels)
-
-    def forward(self, X):
-
-        if self.need_calib:
-            return self.module(X)
-        else:
-            return X
-
-
-class AuxiliaryHead(nn.Module):
-    def __init__(
-        self,
-        in_channels,
-        outputSize,
-    ):
-        super().__init__()
-        self.in_channels = in_channels
-        self.outputSize = outputSize
-
-        self.pooling = nn.Sequential(
-            nn.ReLU(), nn.AvgPool2d(kernel_size=5, stride=3, padding=2)
-        )
-        self.projection = nn.Sequential(
-            StdConvBN(in_channels, 128), StdConvBN(128, 768)
-        )
-        self.avgpool = nn.AdaptiveAvgPool2d(1)
-        self.linear = nn.Linear(768, outputSize, bias=False)
-
-    def forward(self, X):
-
-        b_size = X.size(0)
-        output = self.projection(self.pooling(X))
-        output = self.avgpool(output).view(b_size, -1)
-        output = self.linear(output)
-
-        return output
-
-
-class AvgPoolBN(nn.Module):
-
-    """
-    1. StdConvBN
-    2. average pooling with batch normalization
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=1,
-        stride=1,
-        padding=0,
-        affine=False,
-    ):
-        super().__init__()
-
-        self.module = nn.Sequential(
-            StdConvBN(in_channels, out_channels),
-            nn.AvgPool2d(
-                kernel_size=kernel_size,
-                stride=stride,
-                padding=padding,
-            ),
-            nn.BatchNorm2d(out_channels, affine=affine),
-        )
-
-    def forward(self, X):
-
-        return self.module(X)
-
-
-class MaxPoolBN(nn.Module):
-
-    """
-    1. StdConvBN
-    2. max pooling with batch normalization
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=1,
-        stride=1,
-        padding=0,
-        affine=False,
-    ):
-        super().__init__()
-
-        self.module = nn.Sequential(
-            StdConvBN(in_channels, out_channels),
-            nn.MaxPool2d(
-                kernel_size=kernel_size,
-                stride=stride,
-                padding=padding,
-            ),
-            nn.BatchNorm2d(out_channels, affine=affine),
-        )
-
-    def forward(self, X):
-
-        return self.module(X)
-
-
-class SepConvBN(nn.Module):
-
-    """
-    Depth-wise Separable Convolution followed by Batch Normalization
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=3,
-        stride=1,
-        padding=0,
-    ):
-        super().__init__()
-
-        self.module = nn.Sequential(
-            nn.ReLU(),
-            DepthwiseSeparableConv(
-                in_channels,
-                out_channels,
-                depth=1,
-                kernel_size=kernel_size,
-                stride=stride,
-                padding=padding,
-            ),
-            nn.BatchNorm2d(out_channels, affine=True),
-        )
-
-    def forward(self, X):
-
-        return self.module(X)
-
-
-class FullConvBN(nn.Module):
-
-    """
-    1. StdConvBN
-    2. Conv layer followed by BatchNorm and ReLU
-    """
-
-    def __init__(
-        self,
-        in_channels,
-        out_channels,
-        kernel_size=3,
-        stride=1,
-        padding=0,
-        separable=True,
-    ):
-        self.preprocess = StdConvBN(in_channels, out_channels)
-        if separable:
-            self.conv = DepthwiseSeparableConv(
-                out_channels,
-                out_channels,
-                kernel_size=kernel_size,
-                stride=stride,
-                padding=padding,
-            )
-        else:
-            self.conv = nn.Conv2d(
-                out_channels,
-                out_channels,
-                kernel_size=kernel_size,
-                stride=stride,
-                padding=padding,
-            )
-
-        self.module = nn.Sequential(
-            self.preprocess,
-            self.conv,
-            nn.BatchNorm2d(out_channels, affine=False),
-            nn.ReLU(),
-        )
-
-    def forward(self, X):
-
-        return self.module(X)
-
-
-class ReductionLayer(nn.Module):
-    def __init__(
-        self,
-        in_channels1,
-        in_channels2,
-        out_channels,
-    ):
-        super().__init__()
-
-        self.reduction1 = FactorizedReduce(
-            in_channels1, out_channels, affine=False)
-        self.reduction2 = FactorizedReduce(
-            in_channels2, out_channels, affine=False)
-
-    def forward(self, X):
-
-        return self.reduction1(X), self.reduction2(X)
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_trainer.py b/InsurAutoML/_experimental/_nn/_nni/_nas/_trainer.py
deleted file mode 100644
index 2927b30..0000000
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_trainer.py
+++ /dev/null
@@ -1,228 +0,0 @@
-"""
-File Name: _trainer.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_trainer.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:26:59 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-import os
-import json
-import shutil
-import numpy as np
-import pandas as pd
-import torch
-import nni
-from nni.retiarii import fixed_arch
-from nni.retiarii.experiment.pytorch import RetiariiExperiment, RetiariiExeConfig
-
-from InsurAutoML._utils._tensor import CustomTensorDataset
-from ._utils import get_strategy, get_search_space
-from ._buildModel import build_model
-
-
-NNI_VERBOSE = [
-    # "fatal",
-    "error",
-    "warning",
-    "info",
-    "debug",
-    "trace",
-]
-
-
-class Trainer(object):
-    def __init__(
-        self,
-        search_space="MLP",
-        max_evals=10,
-        timeout=3600 * 24,
-        optimizer="Adam",
-        optimizer_lr=1e-3,
-        lr_scheduler="None",
-        criterion="CrossEntropyLoss",
-        evaluator="base",
-        search_strategy="Random",
-        batch_size=32,
-        num_epoch=10,
-        task_name="NAS",
-        temp_directory="tmp",
-        verbose=0,
-    ):
-        self.search_space = get_search_space(search_space)
-        self.max_evals = max_evals
-        self.timeout = timeout
-        self.optimizer = optimizer
-        self.optimizer_lr = optimizer_lr
-        self.lr_scheduler = lr_scheduler
-        self.criterion = criterion
-        self.optimizer = optimizer
-        self.evaluator = evaluator
-        self.search_strategy = get_strategy(search_strategy)
-        self.batch_size = batch_size
-        self.num_epoch = num_epoch
-        self.task_name = task_name
-        self.temp_directory = temp_directory
-        self.verbose = verbose
-
-    def train(self, train, test, inputSize, outputSize):
-
-        # must use function to wrap the dataset for serialization
-        @nni.trace
-        def _prep_dataset(X, y):
-            # format to tensor
-            if not isinstance(
-                    X, torch.Tensor) or not isinstance(
-                    y, torch.Tensor):
-                # dataframe cannot be directly converted to tensor
-                if isinstance(X, pd.DataFrame) or isinstance(y, pd.DataFrame):
-                    X = torch.tensor(X.values, dtype=torch.float32)
-                    y = torch.tensor(y.values)
-                else:
-                    X = torch.tensor(X, dtype=torch.float32)
-                    y = torch.tensor(y)
-
-            return CustomTensorDataset(X, y, format="tuple")
-
-        # @nni.trace
-        def _prep_loader(data, batch_size=32, mode="train"):
-
-            from torch.utils.data import DataLoader
-
-            if mode == "train":
-                return DataLoader(
-                    data, batch_size=batch_size, shuffle=True, drop_last=True
-                )
-            elif mode == "test":
-                return DataLoader(data, batch_size=batch_size)
-
-        # unpack data
-        # (X_train, y_train), (X_test, y_test) = train, test
-        # inputSize = X_train.shape[1]
-        # outputSize = len(np.unique(y_train))
-
-        # if folder existed, empty it
-        if os.path.isdir(os.path.join(self.temp_directory, self.task_name)):
-            shutil.rmtree(os.path.join(self.temp_directory, self.task_name))
-        # if folder not exist, create it
-        if not os.path.exists(
-            os.path.join(
-                self.temp_directory,
-                self.task_name)):
-            os.makedirs(os.path.join(self.temp_directory, self.task_name))
-
-        # get evaluator
-        # base form evaluator
-        if self.evaluator == "base":
-
-            from ._evaluator import get_evaluator
-
-            self.evaluator = get_evaluator(
-                # trainset=_prep_dataset(X_train, y_train),
-                # testset=_prep_dataset(X_test, y_test),
-                trainset=train,
-                testset=test,
-                batchSize=self.batch_size,
-                optimizer=self.optimizer,
-                criterion=self.criterion,
-                num_epoch=self.num_epoch,
-            )
-        # pytorch-lightning form evaluator
-        elif self.evaluator == "pl":
-            from ._evaulatorPL import get_evaluator
-
-            self.evaluator = get_evaluator(
-                model=nni.trace(self.search_space)(inputSize, outputSize),
-                # train_set=_prep_dataset(X_train, y_train),
-                # test_set=_prep_dataset(X_test, y_test),
-                train_set=train,
-                test_set=test,
-                batchSize=self.batch_size,
-                criterion=self.criterion,
-                optimizer=self.optimizer,
-                optimizer_lr=self.optimizer_lr,
-                lr_scheduler=self.lr_scheduler,
-                num_epochs=self.num_epoch,
-            )
-
-        exp = RetiariiExperiment(
-            nni.trace(self.search_space)(inputSize, outputSize),
-            self.evaluator,
-            [],
-            self.search_strategy,
-        )
-        exp_config = RetiariiExeConfig("local")
-        exp_config.experiment_name = self.task_name
-        # exp_config.execution_engine = "oneshot"
-        exp_config.max_trial_number = self.max_evals
-        exp_config.max_trial_duration = self.timeout
-        exp_config.trial_concurrency = 1
-        exp_config.trial_gpu_number = torch.cuda.device_count()
-        exp_config.training_service.use_active_gpu = True
-        # exp_config.experiment_working_directory = os.path.join(
-        #     self.temp_directory, self.task_name
-        # )
-        # exp_config.experiment_name = self.task_name
-        exp_config.log_level = NNI_VERBOSE[self.verbose]
-        exp.run(exp_config)
-        exp.export_data()
-        exp.stop()
-
-        # save the best model architecture
-        # initialize the best model
-        for model_dict in exp.export_top_models(formatter="dict"):
-            with open(
-                os.path.join(
-                    self.temp_directory, self.task_name, "optimal_architecture.json"
-                ),
-                "w",
-            ) as outfile:
-                # build model
-                init_model = build_model(
-                    model_dict, inputSize, outputSize, "MLP")
-                torch.save(
-                    init_model,
-                    os.path.join(
-                        self.temp_directory,
-                        self.task_name,
-                        "init_optimal_model.pt"),
-                )
-                # save the model dict
-                json.dump(model_dict, outfile)
-
-    def optimal_loading(self):
-
-        with fixed_arch("optimal_architecture.json"):
-            return self.search_space
diff --git a/InsurAutoML/_feature_selection/_legacy.py b/InsurAutoML/_feature_selection/_legacy.py
deleted file mode 100644
index 297a2ea..0000000
--- a/InsurAutoML/_feature_selection/_legacy.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""
-File Name: _legacy.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 7:05:30 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from ._embed import RBFSampler
-from ._filter import FeatureFilter
-from ._wrapper import ASFFS
-from ._hybrid import GeneticAlgorithm
-from InsurAutoML._base import no_processing
-
-import autosklearn
-import autosklearn.pipeline.components.feature_preprocessing
-
-feature_selections = {
-    "no_processing": no_processing,
-    # "LDASelection": LDASelection,
-    # "PCA_FeatureSelection": PCA_FeatureSelection,
-    "RBFSampler": RBFSampler,
-    "FeatureFilter": FeatureFilter,
-    "ASFFS": ASFFS,
-    "GeneticAlgorithm": GeneticAlgorithm,
-    # 'densifier' : autosklearn.pipeline.components.feature_preprocessing.densifier.Densifier,  # from autosklearn
-    "extra_trees_preproc_for_classification": autosklearn.pipeline.components.feature_preprocessing.extra_trees_preproc_for_classification.ExtraTreesPreprocessorClassification,
-    "extra_trees_preproc_for_regression": autosklearn.pipeline.components.feature_preprocessing.extra_trees_preproc_for_regression.ExtraTreesPreprocessorRegression,
-    # "fast_ica": autosklearn.pipeline.components.feature_preprocessing.fast_ica.FastICA,
-    # "feature_agglomeration": autosklearn.pipeline.components.feature_preprocessing.feature_agglomeration.FeatureAgglomeration,
-    # "kernel_pca": autosklearn.pipeline.components.feature_preprocessing.kernel_pca.KernelPCA,
-    # "kitchen_sinks": autosklearn.pipeline.components.feature_preprocessing.kitchen_sinks.RandomKitchenSinks,
-    "liblinear_svc_preprocessor": autosklearn.pipeline.components.feature_preprocessing.liblinear_svc_preprocessor.LibLinear_Preprocessor,
-    # "nystroem_sampler": autosklearn.pipeline.components.feature_preprocessing.nystroem_sampler.Nystroem,
-    # "pca": autosklearn.pipeline.components.feature_preprocessing.pca.PCA,
-    "polynomial": autosklearn.pipeline.components.feature_preprocessing.polynomial.PolynomialFeatures,
-    # "random_trees_embedding": autosklearn.pipeline.components.feature_preprocessing.random_trees_embedding.RandomTreesEmbedding,
-    # 'select_percentile' : autosklearn.pipeline.components.feature_preprocessing.select_percentile.SelectPercentileBase,
-    "select_percentile_classification": autosklearn.pipeline.components.feature_preprocessing.select_percentile_classification.SelectPercentileClassification,
-    "select_percentile_regression": autosklearn.pipeline.components.feature_preprocessing.select_percentile_regression.SelectPercentileRegression,
-    "select_rates_classification": autosklearn.pipeline.components.feature_preprocessing.select_rates_classification.SelectClassificationRates,
-    "select_rates_regression": autosklearn.pipeline.components.feature_preprocessing.select_rates_regression.SelectRegressionRates,
-    "truncatedSVD": autosklearn.pipeline.components.feature_preprocessing.truncatedSVD.TruncatedSVD,
-}
diff --git a/InsurAutoML/_hpo/_legacy.py b/InsurAutoML/_hpo/_legacy.py
deleted file mode 100644
index 6c8666c..0000000
--- a/InsurAutoML/_hpo/_legacy.py
+++ /dev/null
@@ -1,2298 +0,0 @@
-"""
-File: _legacy.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/_legacy.py
-File: _legacy.py
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Tuesday, 15th November 2022 4:20:22 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from InsurAutoML._utils._file import save_model
-from InsurAutoML._utils._base import type_of_task
-from InsurAutoML._base import no_processing
-from InsurAutoML._hyperparameters._hyperopt import (
-    encoder_hyperparameter,
-    imputer_hyperparameter,
-    scaling_hyperparameter,
-    balancing_hyperparameter,
-    feature_selection_hyperparameter,
-    classifier_hyperparameter,
-    regressor_hyperparameter,
-)
-from InsurAutoML._model._legacy import (
-    classifiers,
-    regressors,
-)
-from InsurAutoML._feature_selection._legacy import feature_selections
-from InsurAutoML._scaling._legacy import scalings
-from InsurAutoML._balancing._legacy import balancings
-from InsurAutoML._imputation._legacy import imputers
-from InsurAutoML._encoding._legacy import encoders
-from sklearn.exceptions import ConvergenceWarning
-from sklearn.utils._testing import ignore_warnings
-from hyperopt import (
-    fmin,
-    hp,
-    rand,
-    tpe,
-    atpe,
-    Trials,
-    SparkTrials,
-    space_eval,
-    STATUS_OK,
-    pyll,
-)
-import mlflow
-import scipy.stats
-import scipy
-import pandas as pd
-import numpy as np
-import warnings
-import shutil
-import ast
-import os
-import logging
-
-logger = logging.getLogger(__name__)
-
-logger.warn(
-    "This module is deprecated and will be removed in the future. Please use updated versions instead."
-)
-warnings.warn(
-    "This module is deprecated and will be removed in the future. Please use updated versions instead.",
-    DeprecationWarning,
-)
-
-
-# filter certain warnings
-warnings.filterwarnings(
-    "ignore",
-    message="The dataset is balanced, no change.")
-warnings.filterwarnings("ignore", message="Variables are collinear")
-warnings.filterwarnings("ignore", category=UserWarning)
-
-
-"""
-Classifiers/Hyperparameters from autosklearn:
-1. AdaBoost: n_estimators, learning_rate, algorithm, max_depth
-2. Bernoulli naive Bayes: alpha, fit_prior
-3. Decision Tree: criterion, max_features, max_depth_factor, min_samples_split,
-            min_samples_leaf, min_weight_fraction_leaf, max_leaf_nodes, min_impurity_decrease
-4. Extra Trees: criterion, min_samples_leaf, min_samples_split,  max_features,
-            bootstrap, max_leaf_nodes, max_depth, min_weight_fraction_leaf, min_impurity_decrease
-5. Gaussian naive Bayes
-6. Gradient boosting: loss, learning_rate, min_samples_leaf, max_depth,
-            max_leaf_nodes, max_bins, l2_regularization, early_stop, tol, scoring
-7. KNN: n_neighbors, weights, p
-8. LDA: shrinkage, tol
-9. Linear SVC (LibLinear): penalty, loss, dual, tol, C, multi_class,
-            fit_intercept, intercept_scaling
-10. kernel SVC (LibSVM): C, kernel, gamma, shrinking, tol, max_iter
-11. MLP (Multilayer Perceptron): hidden_layer_depth, num_nodes_per_layer, activation, alpha,
-            learning_rate_init, early_stopping, solver, batch_size, n_iter_no_change, tol,
-            shuffle, beta_1, beta_2, epsilon
-12. Multinomial naive Bayes: alpha, fit_prior
-13. Passive aggressive: C, fit_intercept, tol, loss, average
-14. QDA: reg_param
-15. Random forest: criterion, max_features, max_depth, min_samples_split,
-            min_samples_leaf, min_weight_fraction_leaf, bootstrap, max_leaf_nodes
-16. SGD (Stochastic Gradient Descent): loss, penalty, alpha, fit_intercept, tol,
-            learning_rate
-"""
-
-# Auto binary classifier
-
-
-class AutoTabularClassifier:
-
-    """
-    Perform model selection and hyperparameter optimization for classification tasks
-    using sklearn models, predefine hyperparameters
-
-    Parameters
-    ----------
-    timeout: Total time limit for the job in seconds, default = 360
-
-    max_evals: Maximum number of function evaluations allowed, default = 32
-
-    temp_directory: folder path to store temporary model, default = 'tmp'
-
-    delete_temp_after_terminate: whether to delete temporary information, default = False
-
-    save: whether to save model after training, default = True
-
-    model_name: saved model name, default = 'model'
-
-    ignore_warning: whether to ignore warning, default = True
-
-    encoder: Encoders selected for the job, default = 'auto'
-    support ('DataEncoding')
-    'auto' will select all default encoders, or use a list to select
-
-    imputer: Imputers selected for the job, default = 'auto'
-    support ('SimpleImputer', 'JointImputer', 'ExpectationMaximization', 'KNNImputer',
-    'MissForestImputer', 'MICE', 'GAIN')
-    'auto' will select all default imputers, or use a list to select
-
-    balancing: Balancings selected for the job, default = 'auto'
-    support ('no_processing', 'SimpleRandomOverSampling', 'SimpleRandomUnderSampling',
-    'TomekLink', 'EditedNearestNeighbor', 'CondensedNearestNeighbor', 'OneSidedSelection',
-    'CNN_TomekLink', 'Smote', 'Smote_TomekLink', 'Smote_ENN')
-    'auto' will select all default balancings, or use a list to select
-
-    scaling: Scalings selected for the job, default = 'auto'
-    support ('no_processing', 'MinMaxScale', 'Standardize', 'Normalize', 'RobustScale',
-    'PowerTransformer', 'QuantileTransformer', 'Winsorization')
-    'auto' will select all default scalings, or use a list to select
-
-    feature_selection: Feature selections selected for the job, default = 'auto'
-    support ('no_processing', 'LDASelection', 'PCA_FeatureSelection', 'RBFSampler',
-    'FeatureFilter', 'ASFFS', 'GeneticAlgorithm', 'extra_trees_preproc_for_classification',
-    'fast_ica', 'feature_agglomeration', 'kernel_pca', 'kitchen_sinks',
-    'liblinear_svc_preprocessor', 'nystroem_sampler', 'pca', 'polynomial',
-    'random_trees_embedding', 'select_percentile_classification','select_rates_classification',
-    'truncatedSVD')
-    'auto' will select all default feature selections, or use a list to select
-
-    models: Models selected for the job, default = 'auto'
-    support ('AdaboostClassifier', 'BernoulliNB', 'DecisionTree', 'ExtraTreesClassifier',
-            'GaussianNB', 'GradientBoostingClassifier', 'KNearestNeighborsClassifier',
-            'LDA', 'LibLinear_SVC', 'LibSVM_SVC', 'MLPClassifier', 'MultinomialNB',
-            'PassiveAggressive', 'QDA', 'RandomForest',  'SGD')
-    'auto' will select all default models, or use a list to select
-
-    validation: Whether to use train_test_split to test performance on test set, default = True
-
-    valid_size: Test percentage used to evaluate the performance, default = 0.15
-    only effective when validation = True
-
-    objective: Objective function to test performance, default = 'accuracy'
-    support ("accuracy", "precision", "auc", "hinge", "f1")
-
-    method: Model selection/hyperparameter optimization methods, default = 'Bayesian'
-
-    algo: Search algorithm, default = 'tpe'
-    support (rand, tpe, atpe)
-
-    spark_trials: Whether to use SparkTrials, default = False
-
-    progressbar: Whether to show progress bar, default = False
-
-    seed: Random seed, default = 1
-    """
-
-    def __init__(
-        self,
-        timeout=360,
-        max_evals=64,
-        temp_directory="tmp",
-        delete_temp_after_terminate=False,
-        save=True,
-        model_name="model",
-        ignore_warning=True,
-        encoder="auto",
-        imputer="auto",
-        balancing="auto",
-        scaling="auto",
-        feature_selection="auto",
-        models="auto",
-        validation=True,
-        valid_size=0.15,
-        objective="accuracy",
-        method="Bayesian",
-        algo="tpe",
-        spark_trials=False,
-        progressbar=True,
-        seed=1,
-    ):
-        self.timeout = timeout
-        self.max_evals = max_evals
-        self.temp_directory = temp_directory
-        self.delete_temp_after_terminate = delete_temp_after_terminate
-        self.save = save
-        self.model_name = model_name
-        self.ignore_warning = ignore_warning
-        self.encoder = encoder
-        self.imputer = imputer
-        self.balancing = balancing
-        self.scaling = scaling
-        self.feature_selection = feature_selection
-        self.models = models
-        self.validation = validation
-        self.valid_size = valid_size
-        self.objective = objective
-        self.method = method
-        self.algo = algo
-        self.spark_trials = spark_trials
-        self.progressbar = progressbar
-        self.seed = seed
-
-        self._iter = 0  # record iteration number
-
-    # create hyperparameter space using Hyperopt.hp.choice
-    # the pipeline of AutoClassifier is [encoder, imputer, scaling, balancing, feature_selection, model]
-    # only chosen ones will be added to hyperparameter space
-    def _get_hyperparameter_space(
-        self,
-        X,
-        encoders_hyperparameters,
-        encoder,
-        imputers_hyperparameters,
-        imputer,
-        balancings_hyperparameters,
-        balancing,
-        scalings_hyperparameters,
-        scaling,
-        feature_selection_hyperparameters,
-        feature_selection,
-        models_hyperparameters,
-        models,
-    ):
-
-        # encoding space
-        _encoding_hyperparameter = []
-        for _encoder in [*encoder]:
-            for (
-                item
-            ) in encoders_hyperparameters:  # search the encoders' hyperparameters
-                if item["encoder"] == _encoder:
-                    _encoding_hyperparameter.append(item)
-
-        _encoding_hyperparameter = hp.choice(
-            "classification_encoders", _encoding_hyperparameter
-        )
-
-        # imputation space
-        _imputer_hyperparameter = []
-        if not X.isnull().values.any():  # if no missing, no need for imputation
-            _imputer_hyperparameter = hp.choice(
-                "classification_imputers", [{"imputer": "no_processing"}]
-            )
-        else:
-            for _imputer in [*imputer]:
-                for (
-                    item
-                ) in imputers_hyperparameters:  # search the imputer' hyperparameters
-                    if item["imputer"] == _imputer:
-                        _imputer_hyperparameter.append(item)
-
-            _imputer_hyperparameter = hp.choice(
-                "classification_imputers", _imputer_hyperparameter
-            )
-
-        # balancing space
-        _balancing_hyperparameter = []
-        for _balancing in [*balancing]:
-            for (
-                item
-            ) in balancings_hyperparameters:  # search the balancings' hyperparameters
-                if item["balancing"] == _balancing:
-                    _balancing_hyperparameter.append(item)
-
-        _balancing_hyperparameter = hp.choice(
-            "classification_balancing", _balancing_hyperparameter
-        )
-
-        # scaling space
-        _scaling_hyperparameter = []
-        for _scaling in [*scaling]:
-            for (
-                item
-            ) in scalings_hyperparameters:  # search the scalings' hyperparameters
-                if item["scaling"] == _scaling:
-                    _scaling_hyperparameter.append(item)
-
-        _scaling_hyperparameter = hp.choice(
-            "classification_scaling", _scaling_hyperparameter
-        )
-
-        # feature selection space
-        _feature_selection_hyperparameter = []
-        for _feature_selection in [*feature_selection]:
-            for (
-                item
-            ) in (
-                feature_selection_hyperparameters
-            ):  # search the feature selections' hyperparameters
-                if item["feature_selection"] == _feature_selection:
-                    _feature_selection_hyperparameter.append(item)
-
-        _feature_selection_hyperparameter = hp.choice(
-            "classification_feature_selection",
-            _feature_selection_hyperparameter)
-
-        # model selection and hyperparameter optimization space
-        _model_hyperparameter = []
-        for _model in [*models]:
-            # checked before at models that all models are in default space
-            for item in models_hyperparameters:  # search the models' hyperparameters
-                if item["model"] == _model:
-                    _model_hyperparameter.append(item)
-
-        _model_hyperparameter = hp.choice(
-            "classification_models", _model_hyperparameter
-        )
-
-        # the pipeline search space
-        return pyll.as_apply(
-            {
-                "encoder": _encoding_hyperparameter,
-                "imputer": _imputer_hyperparameter,
-                "balancing": _balancing_hyperparameter,
-                "scaling": _scaling_hyperparameter,
-                "feature_selection": _feature_selection_hyperparameter,
-                "classification": _model_hyperparameter,
-            }
-        )
-
-    # initialize and get hyperparameter search space
-    def get_hyperparameter_space(self, X, y):
-
-        # initialize default search options
-        # use copy to allows multiple manipulation
-        # all encoders available
-        self._all_encoders = encoders.copy()
-
-        # all hyperparameters for encoders
-        self._all_encoders_hyperparameters = encoder_hyperparameter.copy()
-
-        # all imputers available
-        self._all_imputers = imputers.copy()
-
-        # all hyperparemeters for imputers
-        self._all_imputers_hyperparameters = imputer_hyperparameter.copy()
-
-        # all scalings available
-        self._all_scalings = scalings.copy()
-
-        # all balancings available
-        self._all_balancings = balancings.copy()
-
-        # all hyperparameters for balancing methods
-        self._all_balancings_hyperparameters = balancing_hyperparameter.copy()
-
-        # all hyperparameters for scalings
-        self._all_scalings_hyperparameters = scaling_hyperparameter.copy()
-
-        # all feature selections available
-        self._all_feature_selection = feature_selections.copy()
-        # special treatment, remove some feature selection for regression
-        del self._all_feature_selection["extra_trees_preproc_for_regression"]
-        del self._all_feature_selection["select_percentile_regression"]
-        del self._all_feature_selection["select_rates_regression"]
-        if X.shape[0] * X.shape[1] > 10000:
-            del self._all_feature_selection["liblinear_svc_preprocessor"]
-
-        # all hyperparameters for feature selections
-        self._all_feature_selection_hyperparameters = (
-            feature_selection_hyperparameter.copy()
-        )
-
-        # all classification models available
-        self._all_models = classifiers.copy()
-        # special treatment, remove SVM methods when observations are large
-        # SVM suffers from the complexity o(n_samples^2 * n_features),
-        # which is time-consuming for large datasets
-        if X.shape[0] * X.shape[1] > 10000:
-            del self._all_models["LibLinear_SVC"]
-            del self._all_models["LibSVM_SVC"]
-
-        # all hyperparameters for the classification models
-        self._all_models_hyperparameters = classifier_hyperparameter.copy()
-
-        self.hyperparameter_space = None
-
-        # Encoding
-        # convert string types to numerical type
-        # get encoder space
-        if self.encoder == "auto":
-            encoder = self._all_encoders.copy()
-        else:
-            encoder = {}  # if specified, check if encoders in default encoders
-            for _encoder in self.encoder:
-                if _encoder not in [*self._all_encoders]:
-                    raise ValueError(
-                        "Only supported encoders are {}, get {}.".format(
-                            [*self._all_encoders], _encoder
-                        )
-                    )
-                encoder[_encoder] = self._all_encoders[_encoder]
-
-        # Imputer
-        # fill missing values
-        # get imputer space
-        if self.imputer == "auto":
-            if not X.isnull().values.any():  # if no missing values
-                imputer = {"no_processing": no_processing}
-                self._all_imputers = imputer  # limit default imputer space
-            else:
-                imputer = self._all_imputers.copy()
-        else:
-            if not X.isnull().values.any():  # if no missing values
-                imputer = {"no_processing": no_processing}
-                self._all_imputers = imputer
-            else:
-                imputer = {}  # if specified, check if imputers in default imputers
-                for _imputer in self.imputer:
-                    if _imputer not in [*self._all_imputers]:
-                        raise ValueError(
-                            "Only supported imputers are {}, get {}.".format(
-                                [*self._all_imputers], _imputer
-                            )
-                        )
-                    imputer[_imputer] = self._all_imputers[_imputer]
-
-        # Balancing
-        # deal with imbalanced dataset, using over-/under-sampling methods
-        # get balancing space
-        if self.balancing == "auto":
-            balancing = self._all_balancings.copy()
-        else:
-            balancing = {}  # if specified, check if balancings in default balancings
-            for _balancing in self.balancing:
-                if _balancing not in [*self._all_balancings]:
-                    raise ValueError(
-                        "Only supported balancings are {}, get {}.".format(
-                            [*self._all_balancings], _balancing
-                        )
-                    )
-                balancing[_balancing] = self._all_balancings[_balancing]
-
-        # Scaling
-        # get scaling space
-        if self.scaling == "auto":
-            scaling = self._all_scalings.copy()
-        else:
-            scaling = {}  # if specified, check if scalings in default scalings
-            for _scaling in self.scaling:
-                if _scaling not in [*self._all_scalings]:
-                    raise ValueError(
-                        "Only supported scalings are {}, get {}.".format(
-                            [*self._all_scalings], _scaling
-                        )
-                    )
-                scaling[_scaling] = self._all_scalings[_scaling]
-
-        # Feature selection
-        # Remove redundant features, reduce dimensionality
-        # get feature selection space
-        if self.feature_selection == "auto":
-            feature_selection = self._all_feature_selection.copy()
-        else:
-            feature_selection = (
-                {}
-            )  # if specified, check if balancings in default balancings
-            for _feature_selection in self.feature_selection:
-                if _feature_selection not in [*self._all_feature_selection]:
-                    raise ValueError(
-                        "Only supported feature selections are {}, get {}.".format(
-                            [*self._all_feature_selection], _feature_selection
-                        )
-                    )
-                feature_selection[_feature_selection] = self._all_feature_selection[
-                    _feature_selection
-                ]
-
-        # Model selection/Hyperparameter optimization
-        # using Bayesian Optimization
-        # model space, only select chosen models to space
-        if self.models == "auto":  # if auto, model pool will be all default models
-            models = self._all_models.copy()
-        else:
-            models = {}  # if specified, check if models in default models
-            for _model in self.models:
-                if _model not in [*self._all_models]:
-                    raise ValueError(
-                        "Only supported models are {}, get {}.".format(
-                            [*self._all_models], _model
-                        )
-                    )
-                models[_model] = self._all_models[_model]
-
-        # initialize the hyperparameter space
-        _all_encoders_hyperparameters = self._all_encoders_hyperparameters.copy()
-        _all_imputers_hyperparameters = self._all_imputers_hyperparameters.copy()
-        _all_balancings_hyperparameters = self._all_balancings_hyperparameters.copy()
-        _all_scalings_hyperparameters = self._all_scalings_hyperparameters.copy()
-        _all_feature_selection_hyperparameters = (
-            self._all_feature_selection_hyperparameters.copy()
-        )
-        _all_models_hyperparameters = self._all_models_hyperparameters.copy()
-
-        # generate the hyperparameter space
-        if self.hyperparameter_space is None:
-            self.hyperparameter_space = self._get_hyperparameter_space(
-                X,
-                _all_encoders_hyperparameters,
-                encoder,
-                _all_imputers_hyperparameters,
-                imputer,
-                _all_balancings_hyperparameters,
-                balancing,
-                _all_scalings_hyperparameters,
-                scaling,
-                _all_feature_selection_hyperparameters,
-                feature_selection,
-                _all_models_hyperparameters,
-                models,
-            )  # _X to choose whether include imputer
-            # others are the combinations of default hyperparameter space &
-            # methods selected
-
-        return encoder, imputer, balancing, scaling, feature_selection, models
-
-    # select optimal settings and fit on optimal hyperparameters
-    def _fit_optimal(self, best_results, _X, _y):
-
-        # mapping the optimal model and hyperparameters selected
-        # fit the optimal setting
-        optimal_point = space_eval(self.hyperparameter_space, best_results)
-        # optimal encoder
-        self.optimal_encoder_hyperparameters = optimal_point["encoder"]
-        self.optimal_encoder = self.optimal_encoder_hyperparameters["encoder"]
-        del self.optimal_encoder_hyperparameters["encoder"]
-        # optimal imputer
-        self.optimal_imputer_hyperparameters = optimal_point["imputer"]
-        self.optimal_imputer = self.optimal_imputer_hyperparameters["imputer"]
-        del self.optimal_imputer_hyperparameters["imputer"]
-        # optimal balancing
-        self.optimal_balancing_hyperparameters = optimal_point["balancing"]
-        self.optimal_balancing = self.optimal_balancing_hyperparameters["balancing"]
-        del self.optimal_balancing_hyperparameters["balancing"]
-        # optimal scaling
-        self.optimal_scaling_hyperparameters = optimal_point["scaling"]
-        self.optimal_scaling = self.optimal_scaling_hyperparameters["scaling"]
-        del self.optimal_scaling_hyperparameters["scaling"]
-        # optimal feature selection
-        self.optimal_feature_selection_hyperparameters = optimal_point[
-            "feature_selection"
-        ]
-        self.optimal_feature_selection = self.optimal_feature_selection_hyperparameters[
-            "feature_selection"]
-        del self.optimal_feature_selection_hyperparameters["feature_selection"]
-        # optimal classifier
-        self.optimal_classifier_hyperparameters = optimal_point[
-            "classification"
-        ]  # optimal model selected
-        self.optimal_classifier = self.optimal_classifier_hyperparameters[
-            "model"
-        ]  # optimal hyperparameter settings selected
-        del self.optimal_classifier_hyperparameters["model"]
-
-        # record optimal settings
-        with open(self.temp_directory + "/optimal_setting.txt", "w") as f:
-            f.write(
-                "Optimal encoding method is: {}\n".format(
-                    self.optimal_encoder))
-            f.write("Optimal encoding hyperparameters:")
-            print(self.optimal_encoder_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal imputation method is: {}\n".format(
-                    self.optimal_imputer))
-            f.write("Optimal imputation hyperparameters:")
-            print(self.optimal_imputer_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal balancing method is: {}\n".format(
-                    self.optimal_balancing))
-            f.write("Optimal balancing hyperparamters:")
-            print(self.optimal_balancing_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal scaling method is: {}\n".format(
-                    self.optimal_scaling))
-            f.write("Optimal scaling hyperparameters:")
-            print(self.optimal_scaling_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal feature selection method is: {}\n".format(
-                    self.optimal_feature_selection
-                )
-            )
-            f.write("Optimal feature selection hyperparameters:")
-            print(
-                self.optimal_feature_selection_hyperparameters,
-                file=f,
-                end="\n\n")
-            f.write(
-                "Optimal classification model is: {}\n".format(
-                    self.optimal_classifier))
-            f.write("Optimal classification hyperparameters:")
-            print(self.optimal_classifier_hyperparameters, file=f, end="\n\n")
-
-        # encoding
-        self._fit_encoder = self._all_encoders[self.optimal_encoder](
-            **self.optimal_encoder_hyperparameters
-        )
-        _X = self._fit_encoder.fit(_X)
-        # imputer
-        self._fit_imputer = self._all_imputers[self.optimal_imputer](
-            **self.optimal_imputer_hyperparameters
-        )
-        _X = self._fit_imputer.fill(_X)
-        # balancing
-        self._fit_balancing = self._all_balancings[self.optimal_balancing](
-            **self.optimal_balancing_hyperparameters
-        )
-        _X, _y = self._fit_balancing.fit_transform(_X, _y)
-
-        # make sure the classes are integers (belongs to certain classes)
-        _y = _y.astype(int)
-        _y = _y.astype(int)
-        # scaling
-        self._fit_scaling = self._all_scalings[self.optimal_scaling](
-            **self.optimal_scaling_hyperparameters
-        )
-        self._fit_scaling.fit(_X, _y)
-        _X = self._fit_scaling.transform(_X)
-        # feature selection
-        self._fit_feature_selection = self._all_feature_selection[
-            self.optimal_feature_selection
-        ](**self.optimal_feature_selection_hyperparameters)
-        self._fit_feature_selection.fit(_X, _y)
-        _X = self._fit_feature_selection.transform(_X)
-        # classification
-        self._fit_classifier = self._all_models[self.optimal_classifier](
-            **self.optimal_classifier_hyperparameters
-        )
-        self._fit_classifier.fit(_X, _y.values.ravel())
-
-        # save the model
-        if self.save:
-            save_model(
-                self.optimal_encoder,
-                self.optimal_encoder_hyperparameters,
-                self.optimal_imputer,
-                self.optimal_imputer_hyperparameters,
-                self.optimal_balancing,
-                self.optimal_balancing_hyperparameters,
-                self.optimal_scaling,
-                self.optimal_scaling_hyperparameters,
-                self.optimal_feature_selection,
-                self.optimal_feature_selection_hyperparameters,
-                self.optimal_classifier,
-                self.optimal_classifier_hyperparameters,
-                self.model_name,
-            )
-
-        return self
-
-    def load_model(self, _X, _y):
-
-        with open(self.model_name) as f:
-            optimal_setting = f.readlines()
-
-        # remove change line signs
-        optimal_setting = [item.replace("\n", "") for item in optimal_setting]
-        # remove blank spaces
-        while "" in optimal_setting:
-            optimal_setting.remove("")
-
-        self.optimal_encoder = optimal_setting[0]
-        self.optimal_encoder_hyperparameters = ast.literal_eval(
-            optimal_setting[1])
-        self.optimal_imputer = optimal_setting[2]
-        self.optimal_imputer_hyperparameters = ast.literal_eval(
-            optimal_setting[3])
-        self.optimal_balancing = optimal_setting[4]
-        self.optimal_balancing_hyperparameters = ast.literal_eval(
-            optimal_setting[5])
-        self.optimal_scaling = optimal_setting[6]
-        self.optimal_scaling_hyperparameters = ast.literal_eval(
-            optimal_setting[7])
-        self.optimal_feature_selection = optimal_setting[8]
-        self.optimal_feature_selection_hyperparameters = ast.literal_eval(
-            optimal_setting[9]
-        )
-        self.optimal_classifier = optimal_setting[10]
-        self.optimal_classifier_hyperparameters = ast.literal_eval(
-            optimal_setting[11])
-
-        # encoding
-        self._fit_encoder = self._all_encoders[self.optimal_encoder](
-            **self.optimal_encoder_hyperparameters
-        )
-        _X = self._fit_encoder.fit(_X)
-        # imputer
-        self._fit_imputer = self._all_imputers[self.optimal_imputer](
-            **self.optimal_imputer_hyperparameters
-        )
-        _X = self._fit_imputer.fill(_X)
-        # balancing
-        self._fit_balancing = self._all_balancings[self.optimal_balancing](
-            **self.optimal_balancing_hyperparameters
-        )
-        _X, _y = self._fit_balancing.fit_transform(_X, _y)
-
-        # make sure the classes are integers (belongs to certain classes)
-        _y = _y.astype(int)
-        _y = _y.astype(int)
-        # scaling
-        self._fit_scaling = self._all_scalings[self.optimal_scaling](
-            **self.optimal_scaling_hyperparameters
-        )
-        self._fit_scaling.fit(_X, _y)
-        _X = self._fit_scaling.transform(_X)
-        # feature selection
-        self._fit_feature_selection = self._all_feature_selection[
-            self.optimal_feature_selection
-        ](**self.optimal_feature_selection_hyperparameters)
-        self._fit_feature_selection.fit(_X, _y)
-        _X = self._fit_feature_selection.transform(_X)
-        # classification
-        self._fit_classifier = self._all_models[self.optimal_classifier](
-            **self.optimal_classifier_hyperparameters
-        )
-        self._fit_classifier.fit(_X, _y.values.ravel())
-
-        return self
-
-    def fit(self, X, y):
-
-        if self.ignore_warning:  # ignore all warnings to generate clearer outputs
-            warnings.filterwarnings("ignore")
-
-        _X = X.copy()
-        _y = y.copy()
-
-        (
-            encoder,
-            imputer,
-            balancing,
-            scaling,
-            feature_selection,
-            models,
-        ) = self.get_hyperparameter_space(_X, _y)
-
-        # if the model is already trained, read the setting
-        if os.path.exists(self.model_name):
-
-            print("Stored model found, load previous model.")
-            self.load_model(_X, _y)
-
-            return self
-
-        # initialize temp directory
-        # check if temp directory exists, if exists, empty it
-        if os.path.isdir(self.temp_directory):
-            shutil.rmtree(self.temp_directory)
-        os.makedirs(self.temp_directory)
-
-        # write basic information to init.txt
-        with open(self.temp_directory + "/init.txt", "w") as f:
-            f.write("Features of the dataset: {}\n".format(list(_X.columns)))
-            f.write(
-                "Shape of the design matrix: {} * {}\n".format(_X.shape[0], _X.shape[1])
-            )
-            f.write("Response of the dataset: {}\n".format(list(_y.columns)))
-            f.write(
-                "Shape of the response vector: {} * {}\n".format(
-                    _y.shape[0], _y.shape[1]
-                )
-            )
-            f.write("Type of the task: Classification.\n")
-
-        if self.validation:  # only perform train_test_split when validation
-            # train test split so the performance of model selection and
-            # hyperparameter optimization can be evaluated
-            from sklearn.model_selection import train_test_split
-
-            X_train, X_test, y_train, y_test = train_test_split(
-                _X, _y, test_size=self.valid_size, random_state=self.seed
-            )
-
-        # the objective function of Bayesian Optimization tries to minimize
-        # use accuracy score
-        @ignore_warnings(category=ConvergenceWarning)
-        def _objective(params):
-            # evaluation for predictions
-            if self.objective == "accuracy":
-                from sklearn.metrics import accuracy_score
-
-                _obj = accuracy_score
-            elif self.objective == "precision":
-                from sklearn.metrics import precision_score
-
-                _obj = precision_score
-            elif self.objective == "auc":
-                from sklearn.metrics import roc_auc_score
-
-                _obj = roc_auc_score
-            elif self.objective == "hinge":
-                from sklearn.metrics import hinge_loss
-
-                _obj = hinge_loss
-            elif self.objective == "f1":
-                from sklearn.metrics import f1_score
-
-                _obj = f1_score
-            else:
-                raise ValueError(
-                    'Only support ["accuracy", "precision", "auc", "hinge", "f1"], get{}'.format(
-                        self.objective))
-
-            # pipeline of objective, [encoder, imputer, balancing, scaling, feature_selection, model]
-            # select encoder and set hyperparameters
-            # must have encoder
-            _encoder_hyper = params["encoder"]
-            _encoder = _encoder_hyper["encoder"]
-            del _encoder_hyper["encoder"]
-            enc = encoder[_encoder](**_encoder_hyper)
-
-            # select imputer and set hyperparameters
-            _imputer_hyper = params["imputer"]
-            _imputer = _imputer_hyper["imputer"]
-            del _imputer_hyper["imputer"]
-            imp = imputer[_imputer](**_imputer_hyper)
-
-            # select balancing and set hyperparameters
-            # must have balancing, since no_preprocessing is included
-            _balancing_hyper = params["balancing"]
-            _balancing = _balancing_hyper["balancing"]
-            del _balancing_hyper["balancing"]
-            blc = balancing[_balancing](**_balancing_hyper)
-
-            # select scaling and set hyperparameters
-            # must have scaling, since no_preprocessing is included
-            _scaling_hyper = params["scaling"]
-            _scaling = _scaling_hyper["scaling"]
-            del _scaling_hyper["scaling"]
-            scl = scaling[_scaling](**_scaling_hyper)
-
-            # select feature selection and set hyperparameters
-            # must have feature selection, since no_preprocessing is included
-            _feature_selection_hyper = params["feature_selection"]
-            _feature_selection = _feature_selection_hyper["feature_selection"]
-            del _feature_selection_hyper["feature_selection"]
-            fts = feature_selection[_feature_selection](
-                **_feature_selection_hyper)
-
-            # select classifier model and set hyperparameters
-            # must have a classifier
-            _classifier_hyper = params["classification"]
-            _classifier = _classifier_hyper["model"]
-            del _classifier_hyper["model"]
-            clf = models[_classifier](
-                **_classifier_hyper
-            )  # call the model using passed parameters
-
-            obj_tmp_directory = self.temp_directory + \
-                "/iter_" + str(self._iter + 1)
-            if not os.path.isdir(obj_tmp_directory):
-                os.makedirs(obj_tmp_directory)
-
-            with open(obj_tmp_directory + "/hyperparameter_settings.txt", "w") as f:
-                f.write("Encoding method: {}\n".format(_encoder))
-                f.write("Encoding Hyperparameters:")
-                print(_encoder_hyper, file=f, end="\n\n")
-                f.write("Imputation method: {}\n".format(_imputer))
-                f.write("Imputation Hyperparameters:")
-                print(_imputer_hyper, file=f, end="\n\n")
-                f.write("Balancing method: {}\n".format(_balancing))
-                f.write("Balancing Hyperparameters:")
-                print(_balancing_hyper, file=f, end="\n\n")
-                f.write("Scaling method: {}\n".format(_scaling))
-                f.write("Scaling Hyperparameters:")
-                print(_scaling_hyper, file=f, end="\n\n")
-                f.write(
-                    "Feature Selection method: {}\n".format(_feature_selection))
-                f.write("Feature Selection Hyperparameters:")
-                print(_feature_selection_hyper, file=f, end="\n\n")
-                f.write("Classification model: {}\n".format(_classifier))
-                f.write("Classifier Hyperparameters:")
-                print(_classifier_hyper, file=f, end="\n\n")
-
-            if self.validation:
-                _X_train_obj, _X_test_obj = X_train.copy(), X_test.copy()
-                _y_train_obj, _y_test_obj = y_train.copy(), y_test.copy()
-
-                # encoding
-                _X_train_obj = enc.fit(_X_train_obj)
-                _X_test_obj = enc.refit(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Encoding finished, in imputation process.")
-                # imputer
-                _X_train_obj = imp.fill(_X_train_obj)
-                _X_test_obj = imp.fill(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Imputation finished, in scaling process.")
-                # balancing
-                _X_train_obj, _y_train_obj = blc.fit_transform(
-                    _X_train_obj, _y_train_obj
-                )
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Balancing finished, in scaling process.")
-
-                # make sure the classes are integers (belongs to certain
-                # classes)
-                _y_train_obj = _y_train_obj.astype(int)
-                _y_test_obj = _y_test_obj.astype(int)
-                # scaling
-                scl.fit(_X_train_obj, _y_train_obj)
-                _X_train_obj = scl.transform(_X_train_obj)
-                _X_test_obj = scl.transform(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Scaling finished, in feature selection process.")
-                # feature selection
-                fts.fit(_X_train_obj, _y_train_obj)
-                _X_train_obj = fts.transform(_X_train_obj)
-                _X_test_obj = fts.transform(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Feature selection finished, in classification model.")
-                if scipy.sparse.issparse(
-                    _X_train_obj
-                ):  # check if returns sparse matrix
-                    _X_train_obj = _X_train_obj.toarray()
-                if scipy.sparse.issparse(_X_test_obj):
-                    _X_test_obj = _X_test_obj.toarray()
-                # classification
-
-                # store preprocessed train/test datasets
-                if isinstance(
-                    _X_train_obj, np.ndarray
-                ):  # in case numpy array is returned
-                    pd.concat(
-                        [pd.DataFrame(_X_train_obj), _y_train_obj],
-                        axis=1,
-                        ignore_index=True,
-                    ).to_csv(obj_tmp_directory + "/train_preprocessed.csv", index=False)
-                elif isinstance(_X_train_obj, pd.DataFrame):
-                    pd.concat([_X_train_obj, _y_train_obj], axis=1).to_csv(
-                        obj_tmp_directory + "/train_preprocessed.csv", index=False
-                    )
-                else:
-                    raise TypeError(
-                        "Only accept numpy array or pandas dataframe!")
-
-                if isinstance(_X_test_obj, np.ndarray):
-                    pd.concat(
-                        [pd.DataFrame(_X_test_obj), _y_test_obj],
-                        axis=1,
-                        ignore_index=True,
-                    ).to_csv(obj_tmp_directory + "/test_preprocessed.csv", index=False)
-                elif isinstance(_X_test_obj, pd.DataFrame):
-                    pd.concat([_X_test_obj, _y_test_obj], axis=1).to_csv(
-                        obj_tmp_directory + "/test_preprocessed.csv", index=False
-                    )
-                else:
-                    raise TypeError(
-                        "Only accept numpy array or pandas dataframe!")
-
-                clf.fit(_X_train_obj, _y_train_obj.values.ravel())
-                os.remove(obj_tmp_directory + "/objective_process.txt")
-
-                y_pred = clf.predict(_X_test_obj)
-                _loss = -_obj(y_pred, _y_test_obj.values)
-
-                with open(obj_tmp_directory + "/testing_objective.txt", "w") as f:
-                    f.write(
-                        "Loss from objective function is: {:.6f}\n".format(_loss))
-                    f.write(
-                        "Loss is calculate using {}.".format(
-                            self.objective))
-                self._iter += 1
-
-                # since fmin of Hyperopt tries to minimize the objective
-                # function, take negative accuracy here
-                return {"loss": _loss, "status": STATUS_OK}
-            else:
-                _X_obj = _X.copy()
-                _y_obj = _y.copy()
-
-                # encoding
-                _X_obj = enc.fit(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Encoding finished, in imputation process.")
-                # imputer
-                _X_obj = imp.fill(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Imputation finished, in scaling process.")
-                # balancing
-                _X_obj = blc.fit_transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Balancing finished, in feature selection process.")
-                # scaling
-                scl.fit(_X_obj, _y_obj)
-                _X_obj = scl.transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Scaling finished, in balancing process.")
-                # feature selection
-                fts.fit(_X_obj, _y_obj)
-                _X_obj = fts.transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Feature selection finished, in classification model.")
-                # classification
-                clf.fit(_X_obj.values, _y_obj.values.ravel())
-                pd.concat([_X_obj, _y_obj], axis=1).to_csv(
-                    obj_tmp_directory + "/data_preprocessed.csv", index=False
-                )
-                os.remove(obj_tmp_directory + "/objective_process.txt")
-
-                y_pred = clf.predict(_X_obj.values)
-                _loss = -_obj(y_pred, _y_obj.values)
-
-                with open(obj_tmp_directory + "/testing_objective.txt", "w") as f:
-                    f.write(
-                        "Loss from objective function is: {.6f}\n".format(_loss))
-                    f.write(
-                        "Loss is calculate using {}.".format(
-                            self.objective))
-                self._iter += 1
-
-                return {"loss": _loss, "status": STATUS_OK}
-
-        # call hyperopt to use Bayesian Optimization for Model Selection and
-        # Hyperparameter Selection
-
-        # search algorithm
-        if self.algo == "rand":
-            algo = rand.suggest
-        elif self.algo == "tpe":
-            algo = tpe.suggest
-        elif self.algo == "atpe":
-            algo = atpe.suggest
-
-        # Storage for evaluation points
-        if self.spark_trials:
-            trials = SparkTrials(parallelism=8)
-        else:
-            trials = Trials()
-
-        # run fmin to search for optimal hyperparameter settings
-        with mlflow.start_run():
-            best_results = fmin(
-                fn=_objective,
-                space=self.hyperparameter_space,
-                algo=algo,
-                max_evals=self.max_evals,
-                timeout=self.timeout,
-                trials=trials,
-                show_progressbar=self.progressbar,
-                rstate=np.random.RandomState(seed=self.seed),
-            )
-
-        # select optimal settings and fit optimal pipeline
-        self._fit_optimal(best_results, _X, _y)
-
-        # whether to retain temp files
-        if self.delete_temp_after_terminate:
-            shutil.rmtree(self.temp_directory)
-
-        return self
-
-    def predict(self, X):
-
-        _X = X.copy()
-
-        # may need preprocessing for test data, the preprocessing should be the same as in fit part
-        # Encoding
-        # convert string types to numerical type
-        _X = self._fit_encoder.refit(_X)
-
-        # Imputer
-        # fill missing values
-        _X = self._fit_imputer.fill(_X)
-
-        # Balancing
-        # deal with imbalanced dataset, using over-/under-sampling methods
-        # No need to balance on test data
-
-        # Scaling
-        _X = self._fit_scaling.transform(_X)
-
-        # Feature selection
-        # Remove redundant features, reduce dimensionality
-        _X = self._fit_feature_selection.transform(_X)
-
-        return self._fit_classifier.predict(_X)
-
-
-"""
-Regressors/Hyperparameters from sklearn:
-1. AdaBoost: n_estimators, learning_rate, loss, max_depth
-2. Ard regression: n_iter, tol, alpha_1, alpha_2, lambda_1, lambda_2,
-            threshold_lambda, fit_intercept
-3. Decision tree: criterion, max_features, max_depth_factor,
-            min_samples_split, min_samples_leaf, min_weight_fraction_leaf,
-            max_leaf_nodes, min_impurity_decrease
-4. extra trees: criterion, min_samples_leaf, min_samples_split,
-            max_features, bootstrap, max_leaf_nodes, max_depth,
-            min_weight_fraction_leaf, min_impurity_decrease
-5. Gaussian Process: alpha, thetaL, thetaU
-6. Gradient boosting: loss, learning_rate, min_samples_leaf, max_depth,
-            max_leaf_nodes, max_bins, l2_regularization, early_stop, tol, scoring
-7. KNN: n_neighbors, weights, p
-8. Linear SVR (LibLinear): loss, epsilon, dual, tol, C, fit_intercept,
-            intercept_scaling
-9. Kernel SVR (LibSVM): kernel, C, epsilon, tol, shrinking
-10. Random forest: criterion, max_features, max_depth, min_samples_split,
-            min_samples_leaf, min_weight_fraction_leaf, bootstrap,
-            max_leaf_nodes, min_impurity_decrease
-11. SGD (Stochastic Gradient Descent): loss, penalty, alpha, fit_intercept, tol,
-            learning_rate
-12. MLP (Multilayer Perceptron): hidden_layer_depth, num_nodes_per_layer,
-            activation, alpha, learning_rate_init, early_stopping, solver,
-            batch_size, n_iter_no_change, tol, shuffle, beta_1, beta_2, epsilon
-"""
-
-
-class AutoTabularRegressor:
-
-    """
-    Perform model selection and hyperparameter optimization for regression tasks
-    using sklearn models, predefine hyperparameters
-
-    Parameters
-    ----------
-    timeout: Total time limit for the job in seconds, default = 360
-
-    max_evals: Maximum number of function evaluations allowed, default = 32
-
-    temp_directory: folder path to store temporary model, default = 'tmp'
-
-    delete_temp_after_terminate: whether to delete temporary information, default = False
-
-    save: whether to save model after training, default = True
-
-    model_name: saved model name, default = 'model'
-
-    ignore_warning: whether to ignore warning, default = True
-
-    encoder: Encoders selected for the job, default = 'auto'
-    support ('DataEncoding')
-    'auto' will select all default encoders, or use a list to select
-
-    imputer: Imputers selected for the job, default = 'auto'
-    support ('SimpleImputer', 'JointImputer', 'ExpectationMaximization', 'KNNImputer',
-    'MissForestImputer', 'MICE', 'GAIN')
-    'auto' will select all default imputers, or use a list to select
-
-    balancing: Balancings selected for the job, default = 'auto'
-    support ('no_processing', 'SimpleRandomOverSampling', 'SimpleRandomUnderSampling',
-    'TomekLink', 'EditedNearestNeighbor', 'CondensedNearestNeighbor', 'OneSidedSelection',
-    'CNN_TomekLink', 'Smote', 'Smote_TomekLink', 'Smote_ENN')
-    'auto' will select all default balancings, or use a list to select
-
-    scaling: Scalings selected for the job, default = 'auto'
-    support ('no_processing', 'MinMaxScale', 'Standardize', 'Normalize', 'RobustScale',
-    'PowerTransformer', 'QuantileTransformer', 'Winsorization')
-    'auto' will select all default scalings, or use a list to select
-
-    feature_selection: Feature selections selected for the job, default = 'auto'
-    support ('no_processing', 'LDASelection', 'PCA_FeatureSelection', 'RBFSampler',
-    'FeatureFilter', 'ASFFS', 'GeneticAlgorithm', 'extra_trees_preproc_for_regression',
-    'fast_ica', 'feature_agglomeration', 'kernel_pca', 'kitchen_sinks',
-    'liblinear_svc_preprocessor', 'nystroem_sampler', 'pca', 'polynomial',
-    'random_trees_embedding', 'select_percentile_regression','select_rates_regression',
-    'truncatedSVD')
-    'auto' will select all default feature selections, or use a list to select
-
-    models: Models selected for the job, default = 'auto'
-    support ("AdaboostRegressor", "ARDRegression", "DecisionTree", "ExtraTreesRegressor",
-    "GaussianProcess", "GradientBoosting", "KNearestNeighborsRegressor", "LibLinear_SVR",
-    "LibSVM_SVR", "MLPRegressor", "RandomForest", "SGD")
-    'auto' will select all default models, or use a list to select
-
-    validation: Whether to use train_test_split to test performance on test set, default = True
-
-    valid_size: Test percentage used to evaluate the performance, default = 0.15
-    only effective when validation = True
-
-    objective: Objective function to test performance, default = 'MSE'
-    support ("MSE", "MAE", "MSLE", "R2", "MAX")
-
-    method: Model selection/hyperparameter optimization methods, default = 'Bayesian'
-
-    algo: Search algorithm, default = 'tpe'
-    support (rand, tpe, atpe)
-
-    spark_trials: Whether to use SparkTrials, default = False
-
-    progressbar: Whether to show progress bar, default = False
-
-    seed: Random seed, default = 1
-    """
-
-    def __init__(
-        self,
-        timeout=360,
-        max_evals=64,
-        temp_directory="tmp",
-        delete_temp_after_terminate=False,
-        save=True,
-        model_name="model",
-        ignore_warning=True,
-        encoder="auto",
-        imputer="auto",
-        balancing="auto",
-        scaling="auto",
-        feature_selection="auto",
-        models="auto",
-        validation=True,
-        valid_size=0.15,
-        objective="MSE",
-        method="Bayesian",
-        algo="tpe",
-        spark_trials=False,
-        progressbar=True,
-        seed=1,
-    ):
-        self.timeout = timeout
-        self.max_evals = max_evals
-        self.temp_directory = temp_directory
-        self.delete_temp_after_terminate = delete_temp_after_terminate
-        self.save = save
-        self.model_name = model_name
-        self.ignore_warning = ignore_warning
-        self.encoder = encoder
-        self.imputer = imputer
-        self.balancing = balancing
-        self.scaling = scaling
-        self.feature_selection = feature_selection
-        self.models = models
-        self.validation = validation
-        self.valid_size = valid_size
-        self.objective = objective
-        self.method = method
-        self.algo = algo
-        self.spark_trials = spark_trials
-        self.progressbar = progressbar
-        self.seed = seed
-
-        self._iter = 0  # record iteration number
-
-    # create hyperparameter space using Hyperopt.hp.choice
-    # the pipeline of AutoClassifier is [encoder, imputer, scaling, balancing, feature_selection, model]
-    # only chosen ones will be added to hyperparameter space
-    def _get_hyperparameter_space(
-        self,
-        X,
-        encoders_hyperparameters,
-        encoder,
-        imputers_hyperparameters,
-        imputer,
-        balancings_hyperparameters,
-        balancing,
-        scalings_hyperparameters,
-        scaling,
-        feature_selection_hyperparameters,
-        feature_selection,
-        models_hyperparameters,
-        models,
-    ):
-
-        # encoding space
-        _encoding_hyperparameter = []
-        for _encoder in [*encoder]:
-            for (
-                item
-            ) in encoders_hyperparameters:  # search the encoders' hyperparameters
-                if item["encoder"] == _encoder:
-                    _encoding_hyperparameter.append(item)
-
-        _encoding_hyperparameter = hp.choice(
-            "regression_encoders", _encoding_hyperparameter
-        )
-
-        # imputation space
-        _imputer_hyperparameter = []
-        if not X.isnull().values.any():  # if no missing, no need for imputation
-            _imputer_hyperparameter = hp.choice(
-                "regression_imputers", [{"imputer": "no_processing"}]
-            )
-        else:
-            for _imputer in [*imputer]:
-                for (
-                    item
-                ) in imputers_hyperparameters:  # search the imputer' hyperparameters
-                    if item["imputer"] == _imputer:
-                        _imputer_hyperparameter.append(item)
-
-            _imputer_hyperparameter = hp.choice(
-                "regression_imputers", _imputer_hyperparameter
-            )
-
-        # balancing space
-        _balancing_hyperparameter = []
-        for _balancing in [*balancing]:
-            for (
-                item
-            ) in balancings_hyperparameters:  # search the balancings' hyperparameters
-                if item["balancing"] == _balancing:
-                    _balancing_hyperparameter.append(item)
-
-        _balancing_hyperparameter = hp.choice(
-            "regression_balancing", _balancing_hyperparameter
-        )
-
-        # scaling space
-        _scaling_hyperparameter = []
-        for _scaling in [*scaling]:
-            for (
-                item
-            ) in scalings_hyperparameters:  # search the scalings' hyperparameters
-                if item["scaling"] == _scaling:
-                    _scaling_hyperparameter.append(item)
-
-        _scaling_hyperparameter = hp.choice(
-            "regression_scaling", _scaling_hyperparameter
-        )
-
-        # feature selection space
-        _feature_selection_hyperparameter = []
-        for _feature_selection in [*feature_selection]:
-            for (
-                item
-            ) in (
-                feature_selection_hyperparameters
-            ):  # search the feature selections' hyperparameters
-                if item["feature_selection"] == _feature_selection:
-                    _feature_selection_hyperparameter.append(item)
-
-        _feature_selection_hyperparameter = hp.choice(
-            "regression_feature_selection", _feature_selection_hyperparameter
-        )
-
-        # model selection and hyperparameter optimization space
-        _model_hyperparameter = []
-        for _model in [*models]:
-            # checked before at models that all models are in default space
-            for item in models_hyperparameters:  # search the models' hyperparameters
-                if item["model"] == _model:
-                    _model_hyperparameter.append(item)
-
-        _model_hyperparameter = hp.choice(
-            "regression_models", _model_hyperparameter)
-
-        # the pipeline search space
-        return pyll.as_apply(
-            {
-                "encoder": _encoding_hyperparameter,
-                "imputer": _imputer_hyperparameter,
-                "balancing": _balancing_hyperparameter,
-                "scaling": _scaling_hyperparameter,
-                "feature_selection": _feature_selection_hyperparameter,
-                "regression": _model_hyperparameter,
-            }
-        )
-
-    # initialize and get hyperparameter search space
-    def get_hyperparameter_space(self, X, y=None):
-
-        # initialize default search options
-        # use copy to allows multiple manipulation
-        # all encoders available
-        self._all_encoders = encoders.copy()
-
-        # all hyperparameters for encoders
-        self._all_encoders_hyperparameters = encoder_hyperparameter.copy()
-
-        # all imputers available
-        self._all_imputers = imputers.copy()
-
-        # all hyperparemeters for imputers
-        self._all_imputers_hyperparameters = imputer_hyperparameter.copy()
-
-        # all scalings available
-        self._all_scalings = scalings.copy()
-
-        # all balancings available
-        self._all_balancings = balancings.copy()
-
-        # all hyperparameters for balancing methods
-        self._all_balancings_hyperparameters = balancing_hyperparameter.copy()
-
-        # all hyperparameters for scalings
-        self._all_scalings_hyperparameters = scaling_hyperparameter.copy()
-
-        # all feature selections available
-        self._all_feature_selection = feature_selections.copy()
-        # special treatment, remove some feature selection for classification
-        del self._all_feature_selection["extra_trees_preproc_for_classification"]
-        del self._all_feature_selection["select_percentile_classification"]
-        del self._all_feature_selection["select_rates_classification"]
-        # remove SVM feature selection since it's time-consuming for large
-        # datasets
-        if X.shape[0] * X.shape[1] > 10000:
-            del self._all_feature_selection["liblinear_svc_preprocessor"]
-
-        # all hyperparameters for feature selections
-        self._all_feature_selection_hyperparameters = (
-            feature_selection_hyperparameter.copy()
-        )
-
-        # all regression models available
-        self._all_models = regressors.copy()
-        # special treatment, remove SVM methods when observations are large
-        # SVM suffers from the complexity o(n_samples^2 * n_features),
-        # which is time-consuming for large datasets
-        if X.shape[0] * X.shape[1] > 10000:
-            del self._all_models["LibLinear_SVR"]
-            del self._all_models["LibSVM_SVR"]
-
-        # all hyperparameters for the regression models
-        self._all_models_hyperparameters = regressor_hyperparameter.copy()
-
-        self.hyperparameter_space = None
-
-        # Encoding
-        # convert string types to numerical type
-        # get encoder space
-        if self.encoder == "auto":
-            encoder = self._all_encoders.copy()
-        else:
-            encoder = {}  # if specified, check if encoders in default encoders
-            for _encoder in self.encoder:
-                if _encoder not in [*self._all_encoders]:
-                    raise ValueError(
-                        "Only supported encoders are {}, get {}.".format(
-                            [*self._all_encoders], _encoder
-                        )
-                    )
-                encoder[_encoder] = self._all_encoders[_encoder]
-
-        # Imputer
-        # fill missing values
-        # get imputer space
-        if self.imputer == "auto":
-            if not X.isnull().values.any():  # if no missing values
-                imputer = {"no_processing": no_processing}
-                self._all_imputers = imputer  # limit default imputer space
-            else:
-                imputer = self._all_imputers.copy()
-        else:
-            if not X.isnull().values.any():  # if no missing values
-                imputer = {"no_processing": no_processing}
-                self._all_imputers = imputer
-            else:
-                imputer = {}  # if specified, check if imputers in default imputers
-                for _imputer in self.imputer:
-                    if _imputer not in [*self._all_imputers]:
-                        raise ValueError(
-                            "Only supported imputers are {}, get {}.".format(
-                                [*self._all_imputers], _imputer
-                            )
-                        )
-                    imputer[_imputer] = self._all_imputers[_imputer]
-
-        # Balancing
-        # deal with imbalanced dataset, using over-/under-sampling methods
-        # get balancing space
-        if self.balancing == "auto":
-            balancing = self._all_balancings.copy()
-        else:
-            balancing = {}  # if specified, check if balancings in default balancings
-            for _balancing in self.balancing:
-                if _balancing not in [*self._all_balancings]:
-                    raise ValueError(
-                        "Only supported balancings are {}, get {}.".format(
-                            [*self._all_balancings], _balancing
-                        )
-                    )
-                balancing[_balancing] = self._all_balancings[_balancing]
-
-        # Scaling
-        # get scaling space
-        if self.scaling == "auto":
-            scaling = self._all_scalings.copy()
-        else:
-            scaling = {}  # if specified, check if scalings in default scalings
-            for _scaling in self.scaling:
-                if _scaling not in [*self._all_scalings]:
-                    raise ValueError(
-                        "Only supported scalings are {}, get {}.".format(
-                            [*self._all_scalings], _scaling
-                        )
-                    )
-                scaling[_scaling] = self._all_scalings[_scaling]
-
-        # Feature selection
-        # Remove redundant features, reduce dimensionality
-        # get feature selection space
-        if self.feature_selection == "auto":
-            feature_selection = self._all_feature_selection.copy()
-        else:
-            feature_selection = (
-                {}
-            )  # if specified, check if balancings in default balancings
-            for _feature_selection in self.feature_selection:
-                if _feature_selection not in [*self._all_feature_selection]:
-                    raise ValueError(
-                        "Only supported feature selections are {}, get {}.".format(
-                            [*self._all_feature_selection], _feature_selection
-                        )
-                    )
-                feature_selection[_feature_selection] = self._all_feature_selection[
-                    _feature_selection
-                ]
-
-        # Model selection/Hyperparameter optimization
-        # using Bayesian Optimization
-        # model space, only select chosen models to space
-        if self.models == "auto":  # if auto, model pool will be all default models
-            models = self._all_models.copy()
-        else:
-            models = {}  # if specified, check if models in default models
-            for _model in self.models:
-                if _model not in [*self._all_models]:
-                    raise ValueError(
-                        "Only supported models are {}, get {}.".format(
-                            [*self._all_models], _model
-                        )
-                    )
-                models[_model] = self._all_models[_model]
-
-        # initialize the hyperparameter space
-        _all_encoders_hyperparameters = self._all_encoders_hyperparameters.copy()
-        _all_imputers_hyperparameters = self._all_imputers_hyperparameters.copy()
-        _all_balancings_hyperparameters = self._all_balancings_hyperparameters.copy()
-        _all_scalings_hyperparameters = self._all_scalings_hyperparameters.copy()
-        _all_feature_selection_hyperparameters = (
-            self._all_feature_selection_hyperparameters.copy()
-        )
-        _all_models_hyperparameters = self._all_models_hyperparameters.copy()
-
-        # generate the hyperparameter space
-        if self.hyperparameter_space is None:
-            self.hyperparameter_space = self._get_hyperparameter_space(
-                X,
-                _all_encoders_hyperparameters,
-                encoder,
-                _all_imputers_hyperparameters,
-                imputer,
-                _all_balancings_hyperparameters,
-                balancing,
-                _all_scalings_hyperparameters,
-                scaling,
-                _all_feature_selection_hyperparameters,
-                feature_selection,
-                _all_models_hyperparameters,
-                models,
-            )  # _X to choose whether include imputer
-            # others are the combinations of default hyperparameter space &
-            # methods selected
-
-        return encoder, imputer, balancing, scaling, feature_selection, models
-
-    # select optimal settings and fit on optimal hyperparameters
-    def _fit_optimal(self, best_results, _X, _y):
-
-        # mapping the optimal model and hyperparameters selected
-        # fit the optimal setting
-        optimal_point = space_eval(self.hyperparameter_space, best_results)
-        # optimal encoder
-        self.optimal_encoder_hyperparameters = optimal_point["encoder"]
-        self.optimal_encoder = self.optimal_encoder_hyperparameters["encoder"]
-        del self.optimal_encoder_hyperparameters["encoder"]
-        # optimal imputer
-        self.optimal_imputer_hyperparameters = optimal_point["imputer"]
-        self.optimal_imputer = self.optimal_imputer_hyperparameters["imputer"]
-        del self.optimal_imputer_hyperparameters["imputer"]
-        # optimal balancing
-        self.optimal_balancing_hyperparameters = optimal_point["balancing"]
-        self.optimal_balancing = self.optimal_balancing_hyperparameters["balancing"]
-        del self.optimal_balancing_hyperparameters["balancing"]
-        # optimal scaling
-        self.optimal_scaling_hyperparameters = optimal_point["scaling"]
-        self.optimal_scaling = self.optimal_scaling_hyperparameters["scaling"]
-        del self.optimal_scaling_hyperparameters["scaling"]
-        # optimal feature selection
-        self.optimal_feature_selection_hyperparameters = optimal_point[
-            "feature_selection"
-        ]
-        self.optimal_feature_selection = self.optimal_feature_selection_hyperparameters[
-            "feature_selection"]
-        del self.optimal_feature_selection_hyperparameters["feature_selection"]
-        # optimal regressor
-        self.optimal_regressor_hyperparameters = optimal_point[
-            "regression"
-        ]  # optimal model selected
-        self.optimal_regressor = self.optimal_regressor_hyperparameters[
-            "model"
-        ]  # optimal hyperparameter settings selected
-        del self.optimal_regressor_hyperparameters["model"]
-
-        # record optimal settings
-        with open(self.temp_directory + "/optimal_setting.txt", "w") as f:
-            f.write(
-                "Optimal encoding method is: {}\n".format(
-                    self.optimal_encoder))
-            f.write("Optimal encoding hyperparameters:")
-            print(self.optimal_encoder_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal imputation method is: {}\n".format(
-                    self.optimal_imputer))
-            f.write("Optimal imputation hyperparameters:")
-            print(self.optimal_imputer_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal balancing method is: {}\n".format(
-                    self.optimal_balancing))
-            f.write("Optimal balancing hyperparamters:")
-            print(self.optimal_balancing_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal scaling method is: {}\n".format(
-                    self.optimal_scaling))
-            f.write("Optimal scaling hyperparameters:")
-            print(self.optimal_scaling_hyperparameters, file=f, end="\n\n")
-            f.write(
-                "Optimal feature selection method is: {}\n".format(
-                    self.optimal_feature_selection
-                )
-            )
-            f.write("Optimal feature selection hyperparameters:")
-            print(
-                self.optimal_feature_selection_hyperparameters,
-                file=f,
-                end="\n\n")
-            f.write(
-                "Optimal regression model is: {}\n".format(
-                    self.optimal_regressor))
-            f.write("Optimal regression hyperparameters:")
-            print(self.optimal_regressor_hyperparameters, file=f, end="\n\n")
-
-        # encoding
-        self._fit_encoder = self._all_encoders[self.optimal_encoder](
-            **self.optimal_encoder_hyperparameters
-        )
-        _X = self._fit_encoder.fit(_X)
-        # imputer
-        self._fit_imputer = self._all_imputers[self.optimal_imputer](
-            **self.optimal_imputer_hyperparameters
-        )
-        _X = self._fit_imputer.fill(_X)
-        # balancing
-        self._fit_balancing = self._all_balancings[self.optimal_balancing](
-            **self.optimal_balancing_hyperparameters
-        )
-        _X, _y = self._fit_balancing.fit_transform(_X, _y)
-
-        # make sure the classes are integers (belongs to certain classes)
-        _y = _y.astype(int)
-        _y = _y.astype(int)
-        # scaling
-        self._fit_scaling = self._all_scalings[self.optimal_scaling](
-            **self.optimal_scaling_hyperparameters
-        )
-        self._fit_scaling.fit(_X, _y)
-        _X = self._fit_scaling.transform(_X)
-        # feature selection
-        self._fit_feature_selection = self._all_feature_selection[
-            self.optimal_feature_selection
-        ](**self.optimal_feature_selection_hyperparameters)
-        self._fit_feature_selection.fit(_X, _y)
-        _X = self._fit_feature_selection.transform(_X)
-        # regression
-        self._fit_regressor = self._all_models[self.optimal_regressor](
-            **self.optimal_regressor_hyperparameters
-        )
-        self._fit_regressor.fit(_X, _y.values.ravel())
-
-        # save the model
-        if self.save:
-            save_model(
-                self.optimal_encoder,
-                self.optimal_encoder_hyperparameters,
-                self.optimal_imputer,
-                self.optimal_imputer_hyperparameters,
-                self.optimal_balancing,
-                self.optimal_balancing_hyperparameters,
-                self.optimal_scaling,
-                self.optimal_scaling_hyperparameters,
-                self.optimal_feature_selection,
-                self.optimal_feature_selection_hyperparameters,
-                self.optimal_regressor,
-                self.optimal_regressor_hyperparameters,
-                self.model_name,
-            )
-
-        return self
-
-    def load_model(self, _X, _y):
-
-        with open(self.model_name) as f:
-            optimal_setting = f.readlines()
-
-        # remove change line signs
-        optimal_setting = [item.replace("\n", "") for item in optimal_setting]
-        # remove blank spaces
-        while "" in optimal_setting:
-            optimal_setting.remove("")
-
-        self.optimal_encoder = optimal_setting[0]
-        self.optimal_encoder_hyperparameters = ast.literal_eval(
-            optimal_setting[1])
-        self.optimal_imputer = optimal_setting[2]
-        self.optimal_imputer_hyperparameters = ast.literal_eval(
-            optimal_setting[3])
-        self.optimal_balancing = optimal_setting[4]
-        self.optimal_balancing_hyperparameters = ast.literal_eval(
-            optimal_setting[5])
-        self.optimal_scaling = optimal_setting[6]
-        self.optimal_scaling_hyperparameters = ast.literal_eval(
-            optimal_setting[7])
-        self.optimal_feature_selection = optimal_setting[8]
-        self.optimal_feature_selection_hyperparameters = ast.literal_eval(
-            optimal_setting[9]
-        )
-        self.optimal_regressor = optimal_setting[10]
-        self.optimal_regressor_hyperparameters = ast.literal_eval(
-            optimal_setting[11])
-
-        # encoding
-        self._fit_encoder = self._all_encoders[self.optimal_encoder](
-            **self.optimal_encoder_hyperparameters
-        )
-        _X = self._fit_encoder.fit(_X)
-        # imputer
-        self._fit_imputer = self._all_imputers[self.optimal_imputer](
-            **self.optimal_imputer_hyperparameters
-        )
-        _X = self._fit_imputer.fill(_X)
-        # balancing
-        self._fit_balancing = self._all_balancings[self.optimal_balancing](
-            **self.optimal_balancing_hyperparameters
-        )
-        _X, _y = self._fit_balancing.fit_transform(_X, _y)
-
-        # make sure the classes are integers (belongs to certain classes)
-        _y = _y.astype(int)
-        _y = _y.astype(int)
-        # scaling
-        self._fit_scaling = self._all_scalings[self.optimal_scaling](
-            **self.optimal_scaling_hyperparameters
-        )
-        self._fit_scaling.fit(_X, _y)
-        _X = self._fit_scaling.transform(_X)
-        # feature selection
-        self._fit_feature_selection = self._all_feature_selection[
-            self.optimal_feature_selection
-        ](**self.optimal_feature_selection_hyperparameters)
-        self._fit_feature_selection.fit(_X, _y)
-        _X = self._fit_feature_selection.transform(_X)
-        # classification
-        self._fit_regressor = self._all_models[self.optimal_regressor](
-            **self.optimal_regressor_hyperparameters
-        )
-        self._fit_regressor.fit(_X, _y.values.ravel())
-
-        return self
-
-    def fit(self, X, y):
-
-        if self.ignore_warning:  # ignore all warnings to generate clearer outputs
-            warnings.filterwarnings("ignore")
-
-        _X = X.copy()
-        _y = y.copy()
-
-        (
-            encoder,
-            imputer,
-            balancing,
-            scaling,
-            feature_selection,
-            models,
-        ) = self.get_hyperparameter_space(_X, _y)
-
-        if os.path.exists(self.model_name):
-
-            print("Stored model found, load previous model.")
-            self.load_model(_X, _y)
-
-            return self
-
-        # initialize temp directory
-        # check if temp directory exists, if exists, empty it
-        if os.path.isdir(self.temp_directory):
-            shutil.rmtree(self.temp_directory)
-        os.makedirs(self.temp_directory)
-
-        # write basic information to init.txt
-        with open(self.temp_directory + "/init.txt", "w") as f:
-            f.write("Features of the dataset: {}\n".format(list(_X.columns)))
-            f.write(
-                "Shape of the design matrix: {} * {}\n".format(_X.shape[0], _X.shape[1])
-            )
-            f.write("Response of the dataset: {}\n".format(list(_y.columns)))
-            f.write(
-                "Shape of the response vector: {} * {}\n".format(
-                    _y.shape[0], _y.shape[1]
-                )
-            )
-            f.write("Type of the task: Regression.\n")
-
-        if self.validation:  # only perform train_test_split when validation
-            # train test split so the performance of model selection and
-            # hyperparameter optimization can be evaluated
-            from sklearn.model_selection import train_test_split
-
-            X_train, X_test, y_train, y_test = train_test_split(
-                _X, _y, test_size=self.valid_size, random_state=self.seed
-            )
-
-        # the objective function of Bayesian Optimization tries to minimize
-        # use accuracy score
-        @ignore_warnings(category=ConvergenceWarning)
-        def _objective(params):
-            # evaluation for predictions
-            if self.objective == "MSE":
-                from sklearn.metrics import mean_squared_error
-
-                _obj = mean_squared_error
-            elif self.objective == "MAE":
-                from sklearn.metrics import mean_absolute_error
-
-                _obj = mean_absolute_error
-            elif self.objective == "MSLE":
-                from sklearn.metrics import mean_squared_log_error
-
-                _obj = mean_squared_log_error
-            elif self.objective == "R2":
-                from sklearn.metrics import r2_score
-
-                _obj = r2_score
-            elif self.objective == "MAX":
-                from sklearn.metrics import (
-                    max_error,
-                )  # focus on reducing extreme losses
-
-                _obj = max_error
-            else:
-                raise ValueError(
-                    'Only support ["MSE", "MAE", "MSLE", "R2", "MAX"], get{}'.format(
-                        self.objective))
-
-            # pipeline of objective, [encoder, imputer, balancing, scaling, feature_selection, model]
-            # select encoder and set hyperparameters
-            # must have encoder
-            _encoder_hyper = params["encoder"]
-            _encoder = _encoder_hyper["encoder"]
-            del _encoder_hyper["encoder"]
-            enc = encoder[_encoder](**_encoder_hyper)
-
-            # select imputer and set hyperparameters
-            _imputer_hyper = params["imputer"]
-            _imputer = _imputer_hyper["imputer"]
-            del _imputer_hyper["imputer"]
-            imp = imputer[_imputer](**_imputer_hyper)
-
-            # select balancing and set hyperparameters
-            # must have balancing, since no_preprocessing is included
-            _balancing_hyper = params["balancing"]
-            _balancing = _balancing_hyper["balancing"]
-            del _balancing_hyper["balancing"]
-            blc = balancing[_balancing](**_balancing_hyper)
-
-            # select scaling and set hyperparameters
-            # must have scaling, since no_preprocessing is included
-            _scaling_hyper = params["scaling"]
-            _scaling = _scaling_hyper["scaling"]
-            del _scaling_hyper["scaling"]
-            scl = scaling[_scaling](**_scaling_hyper)
-
-            # select feature selection and set hyperparameters
-            # must have feature selection, since no_preprocessing is included
-            _feature_selection_hyper = params["feature_selection"]
-            _feature_selection = _feature_selection_hyper["feature_selection"]
-            del _feature_selection_hyper["feature_selection"]
-            fts = feature_selection[_feature_selection](
-                **_feature_selection_hyper)
-
-            # select regressor model and set hyperparameters
-            # must have a regressor
-            _regressor_hyper = params["regression"]
-            _regressor = _regressor_hyper["model"]
-            del _regressor_hyper["model"]
-            reg = models[_regressor](
-                **_regressor_hyper
-            )  # call the model using passed parameters
-
-            obj_tmp_directory = self.temp_directory + \
-                "/iter_" + str(self._iter + 1)
-            if not os.path.isdir(obj_tmp_directory):
-                os.makedirs(obj_tmp_directory)
-
-            with open(obj_tmp_directory + "/hyperparameter_settings.txt", "w") as f:
-                f.write("Encoding method: {}\n".format(_encoder))
-                f.write("Encoding Hyperparameters:")
-                print(_encoder_hyper, file=f, end="\n\n")
-                f.write("Imputation method: {}\n".format(_imputer))
-                f.write("Imputation Hyperparameters:")
-                print(_imputer_hyper, file=f, end="\n\n")
-                f.write("Balancing method: {}\n".format(_balancing))
-                f.write("Balancing Hyperparameters:")
-                print(_balancing_hyper, file=f, end="\n\n")
-                f.write("Scaling method: {}\n".format(_scaling))
-                f.write("Scaling Hyperparameters:")
-                print(_scaling_hyper, file=f, end="\n\n")
-                f.write(
-                    "Feature Selection method: {}\n".format(_feature_selection))
-                f.write("Feature Selection Hyperparameters:")
-                print(_feature_selection_hyper, file=f, end="\n\n")
-                f.write("Regression model: {}\n".format(_regressor))
-                f.write("Regression Hyperparameters:")
-                print(_regressor_hyper, file=f, end="\n\n")
-
-            if self.validation:
-                _X_train_obj, _X_test_obj = X_train.copy(), X_test.copy()
-                _y_train_obj, _y_test_obj = y_train.copy(), y_test.copy()
-
-                # encoding
-                _X_train_obj = enc.fit(_X_train_obj)
-                _X_test_obj = enc.refit(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Encoding finished, in imputation process.")
-                # imputer
-                _X_train_obj = imp.fill(_X_train_obj)
-                _X_test_obj = imp.fill(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Imputation finished, in scaling process.")
-                # balancing
-                _X_train_obj, _y_train_obj = blc.fit_transform(
-                    _X_train_obj, _y_train_obj
-                )
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Balancing finished, in scaling process.")
-
-                # make sure the classes are integers (belongs to certain
-                # classes)
-                _y_train_obj = _y_train_obj.astype(int)
-                _y_test_obj = _y_test_obj.astype(int)
-                # scaling
-                scl.fit(_X_train_obj, _y_train_obj)
-                _X_train_obj = scl.transform(_X_train_obj)
-                _X_test_obj = scl.transform(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Scaling finished, in feature selection process.")
-                # feature selection
-                fts.fit(_X_train_obj, _y_train_obj)
-                _X_train_obj = fts.transform(_X_train_obj)
-                _X_test_obj = fts.transform(_X_test_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Feature selection finished, in regression model.")
-                if scipy.sparse.issparse(
-                    _X_train_obj
-                ):  # check if returns sparse matrix
-                    _X_train_obj = _X_train_obj.toarray()
-                if scipy.sparse.issparse(_X_test_obj):
-                    _X_test_obj = _X_test_obj.toarray()
-                # regression
-
-                # store the preprocessed train/test datasets
-                if isinstance(
-                    _X_train_obj, np.ndarray
-                ):  # in case numpy array is returned
-                    pd.concat(
-                        [pd.DataFrame(_X_train_obj), _y_train_obj],
-                        axis=1,
-                        ignore_index=True,
-                    ).to_csv(obj_tmp_directory + "/train_preprocessed.csv", index=False)
-                elif isinstance(_X_train_obj, pd.DataFrame):
-                    pd.concat([_X_train_obj, _y_train_obj], axis=1).to_csv(
-                        obj_tmp_directory + "/train_preprocessed.csv", index=False
-                    )
-                else:
-                    raise TypeError(
-                        "Only accept numpy array or pandas dataframe!")
-
-                if isinstance(_X_test_obj, np.ndarray):
-                    pd.concat(
-                        [pd.DataFrame(_X_test_obj), _y_test_obj],
-                        axis=1,
-                        ignore_index=True,
-                    ).to_csv(obj_tmp_directory + "/test_preprocessed.csv", index=False)
-                elif isinstance(_X_test_obj, pd.DataFrame):
-                    pd.concat([_X_test_obj, _y_test_obj], axis=1).to_csv(
-                        obj_tmp_directory + "/test_preprocessed.csv", index=False
-                    )
-                else:
-                    raise TypeError(
-                        "Only accept numpy array or pandas dataframe!")
-
-                reg.fit(_X_train_obj, _y_train_obj.values.ravel())
-                os.remove(obj_tmp_directory + "/objective_process.txt")
-
-                y_pred = reg.predict(_X_test_obj)
-                if self.objective == "R2":  # special treatment for r2_score
-                    _loss = -_obj(y_pred, _y_test_obj.values)
-                else:
-                    _loss = _obj(y_pred, _y_test_obj.values)
-
-                with open(obj_tmp_directory + "/testing_objective.txt", "w") as f:
-                    f.write(
-                        "Loss from objective function is: {:.6f}\n".format(_loss))
-                    f.write(
-                        "Loss is calculate using {}.".format(
-                            self.objective))
-                self._iter += 1
-
-                # since fmin of Hyperopt tries to minimize the objective
-                # function, take negative accuracy here
-                return {"loss": _loss, "status": STATUS_OK}
-            else:
-                _X_obj = _X.copy()
-                _y_obj = _y.copy()
-
-                # encoding
-                _X_obj = enc.fit(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Encoding finished, in imputation process.")
-                # imputer
-                _X_obj = imp.fill(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Imputation finished, in scaling process.")
-                # balancing
-                _X_obj = blc.fit_transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Balancing finished, in feature selection process.")
-                # scaling
-                scl.fit(_X_obj, _y_obj)
-                _X_obj = scl.transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Scaling finished, in balancing process.")
-                # feature selection
-                fts.fit(_X_obj, _y_obj)
-                _X_obj = fts.transform(_X_obj)
-                with open(obj_tmp_directory + "/objective_process.txt", "w") as f:
-                    f.write("Feature selection finished, in regression model.")
-                # regression
-                reg.fit(_X_obj.values, _y_obj.values.ravel())
-                pd.concat([_X_obj, _y_obj], axis=1).to_csv(
-                    obj_tmp_directory + "/data_preprocessed.csv", index=False
-                )
-                os.remove(obj_tmp_directory + "/objective_process.txt")
-
-                y_pred = reg.predict(_X_obj.values)
-
-                if self.objective == "R2":  # special treatment for r2_score
-                    _loss = -_obj(y_pred, _y_obj.values)
-                else:
-                    _loss = _obj(y_pred, _y_obj.values)
-
-                with open(obj_tmp_directory + "/testing_objective.txt", "w") as f:
-                    f.write(
-                        "Loss from objective function is: {.6f}\n".format(_loss))
-                    f.write(
-                        "Loss is calculate using {}.".format(
-                            self.objective))
-                self._iter += 1
-
-                return {"loss": _loss, "status": STATUS_OK}
-
-        # call hyperopt to use Bayesian Optimization for Model Selection and
-        # Hyperparameter Selection
-
-        # search algorithm
-        if self.algo == "rand":
-            algo = rand.suggest
-        elif self.algo == "tpe":
-            algo = tpe.suggest
-        elif self.algo == "atpe":
-            algo = atpe.suggest
-
-        # Storage for evaluation points
-        if self.spark_trials:
-            trials = SparkTrials(parallelism=8)
-        else:
-            trials = Trials()
-
-        # run fmin to search for optimal hyperparameter settings
-        with mlflow.start_run():
-            best_results = fmin(
-                fn=_objective,
-                space=self.hyperparameter_space,
-                algo=algo,
-                max_evals=self.max_evals,
-                timeout=self.timeout,
-                trials=trials,
-                show_progressbar=self.progressbar,
-                rstate=np.random.RandomState(seed=self.seed),
-            )
-
-        # select optimal settings and fit optimal pipeline
-        self._fit_optimal(best_results, _X, _y)
-
-        # whether to retain temp files
-        if self.delete_temp_after_terminate:
-            shutil.rmtree(self.temp_directory)
-
-        return self
-
-    def predict(self, X):
-
-        _X = X.copy()
-
-        # may need preprocessing for test data, the preprocessing should be the same as in fit part
-        # Encoding
-        # convert string types to numerical type
-        _X = self._fit_encoder.refit(_X)
-
-        # Imputer
-        # fill missing values
-        _X = self._fit_imputer.fill(_X)
-
-        # Balancing
-        # deal with imbalanced dataset, using over-/under-sampling methods
-        # No need to balance on test data
-
-        # Scaling
-        _X = self._fit_scaling.transform(_X)
-
-        # Feature selection
-        # Remove redundant features, reduce dimensionality
-        _X = self._fit_feature_selection.transform(_X)
-
-        return self._fit_regressor.predict(_X)
-
-
-class AutoTabular(AutoTabularClassifier, AutoTabularRegressor):
-
-    """
-    Automatically assign to AutoTabularClassifier or AutoTabularRegressor
-    """
-
-    def __init__(
-        self,
-        timeout=360,
-        max_evals=64,
-        temp_directory="tmp",
-        delete_temp_after_terminate=False,
-        save=True,
-        model_name="model",
-        ignore_warning=True,
-        encoder="auto",
-        imputer="auto",
-        balancing="auto",
-        scaling="auto",
-        feature_selection="auto",
-        models="auto",
-        validation=True,
-        valid_size=0.15,
-        objective=None,
-        method="Bayesian",
-        algo="tpe",
-        spark_trials=False,
-        progressbar=True,
-        seed=1,
-    ):
-        self.timeout = timeout
-        self.max_evals = max_evals
-        self.temp_directory = temp_directory
-        self.delete_temp_after_terminate = delete_temp_after_terminate
-        self.save = save
-        self.model_name = model_name
-        self.ignore_warning = ignore_warning
-        self.encoder = encoder
-        self.imputer = imputer
-        self.balancing = balancing
-        self.scaling = scaling
-        self.feature_selection = feature_selection
-        self.models = models
-        self.validation = validation
-        self.valid_size = valid_size
-        self.objective = objective
-        self.method = method
-        self.algo = algo
-        self.spark_trials = spark_trials
-        self.progressbar = progressbar
-        self.seed = seed
-
-    def fit(self, X, y=None):
-
-        if isinstance(y, pd.DataFrame) or isinstance(y, np.ndarray):
-            self._type = type_of_task(y)
-        elif y is None:
-            self._type = "Unsupervised"
-
-        if self._type in [
-            "binary",
-                "multiclass"]:  # assign classification tasks
-            self.model = AutoTabularClassifier(
-                timeout=self.timeout,
-                max_evals=self.max_evals,
-                temp_directory=self.temp_directory,
-                delete_temp_after_terminate=self.delete_temp_after_terminate,
-                save=self.save,
-                model_name=self.model_name,
-                ignore_warning=self.ignore_warning,
-                encoder=self.encoder,
-                imputer=self.imputer,
-                balancing=self.balancing,
-                scaling=self.scaling,
-                feature_selection=self.feature_selection,
-                models=self.models,
-                validation=self.validation,
-                valid_size=self.valid_size,
-                objective="accuracy" if not self.objective else self.objective,
-                method=self.method,
-                algo=self.algo,
-                spark_trials=self.spark_trials,
-                progressbar=self.progressbar,
-                seed=self.seed,
-            )
-        elif self._type in ["integer", "continuous"]:  # assign regression tasks
-            self.model = AutoTabularRegressor(
-                timeout=self.timeout,
-                max_evals=self.max_evals,
-                temp_directory=self.temp_directory,
-                delete_temp_after_terminate=self.delete_temp_after_terminate,
-                save=self.save,
-                model_name=self.model_name,
-                ignore_warning=self.ignore_warning,
-                encoder=self.encoder,
-                imputer=self.imputer,
-                balancing=self.balancing,
-                scaling=self.scaling,
-                feature_selection=self.feature_selection,
-                models=self.models,
-                validation=self.validation,
-                valid_size=self.valid_size,
-                objective="MSE" if not self.objective else self.objective,
-                method=self.method,
-                algo=self.algo,
-                spark_trials=self.spark_trials,
-                progressbar=self.progressbar,
-                seed=self.seed,
-            )
-        else:
-            raise ValueError(
-                'Not recognizing type, only ["binary", "multiclass", "integer", "continuous"] accepted, get {}!'.format(
-                    self._type))
-
-        self.model.fit(X, y)
-        return self
-
-    def predict(self, X):
-
-        if self.model:
-            return self.model.predict(X)
-        else:
-            raise ValueError("No tasks found! Need to fit first.")
diff --git a/InsurAutoML/_imputation/_legacy.py b/InsurAutoML/_imputation/_legacy.py
deleted file mode 100644
index e0b838b..0000000
--- a/InsurAutoML/_imputation/_legacy.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-File Name: _legacy.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:13:31 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from ._base import SimpleImputer, DummyImputer, JointImputer
-from ._multiple import ExpectationMaximization, KNNImputer, MissForestImputer, MICE
-from ._nn import GAIN
-
-imputers = {
-    "SimpleImputer": SimpleImputer,
-    #    'DummyImputer' : DummyImputer,
-    "JointImputer": JointImputer,
-    "ExpectationMaximization": ExpectationMaximization,
-    "KNNImputer": KNNImputer,
-    "MissForestImputer": MissForestImputer,
-    "MICE": MICE,
-    "GAIN": GAIN,
-}
diff --git a/InsurAutoML/_model/_legacy.py b/InsurAutoML/_model/_legacy.py
deleted file mode 100644
index 8e3064b..0000000
--- a/InsurAutoML/_model/_legacy.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""
-File Name: _legacy.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:15:10 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-import autosklearn.pipeline.components.classification
-import autosklearn.pipeline.components.regression
-
-##########################################################################
-# classifiers
-
-classifiers = {
-    # classification models from autosklearn
-    "AdaboostClassifier": autosklearn.pipeline.components.classification.adaboost.AdaboostClassifier,
-    "BernoulliNB": autosklearn.pipeline.components.classification.bernoulli_nb.BernoulliNB,
-    "DecisionTree": autosklearn.pipeline.components.classification.decision_tree.DecisionTree,
-    "ExtraTreesClassifier": autosklearn.pipeline.components.classification.extra_trees.ExtraTreesClassifier,
-    "GaussianNB": autosklearn.pipeline.components.classification.gaussian_nb.GaussianNB,
-    "GradientBoostingClassifier": autosklearn.pipeline.components.classification.gradient_boosting.GradientBoostingClassifier,
-    "KNearestNeighborsClassifier": autosklearn.pipeline.components.classification.k_nearest_neighbors.KNearestNeighborsClassifier,
-    "LDA": autosklearn.pipeline.components.classification.lda.LDA,
-    "LibLinear_SVC": autosklearn.pipeline.components.classification.liblinear_svc.LibLinear_SVC,
-    "LibSVM_SVC": autosklearn.pipeline.components.classification.libsvm_svc.LibSVM_SVC,
-    "MLPClassifier": autosklearn.pipeline.components.classification.mlp.MLPClassifier,
-    "MultinomialNB": autosklearn.pipeline.components.classification.multinomial_nb.MultinomialNB,
-    "PassiveAggressive": autosklearn.pipeline.components.classification.passive_aggressive.PassiveAggressive,
-    "QDA": autosklearn.pipeline.components.classification.qda.QDA,
-    "RandomForest": autosklearn.pipeline.components.classification.random_forest.RandomForest,
-    "SGD": autosklearn.pipeline.components.classification.sgd.SGD,
-}
-
-# regressors
-regressors = {
-    # regression models from autosklearn
-    "AdaboostRegressor": autosklearn.pipeline.components.regression.adaboost.AdaboostRegressor,
-    "ARDRegression": autosklearn.pipeline.components.regression.ard_regression.ARDRegression,
-    "DecisionTree": autosklearn.pipeline.components.regression.decision_tree.DecisionTree,
-    "ExtraTreesRegressor": autosklearn.pipeline.components.regression.extra_trees.ExtraTreesRegressor,
-    "GaussianProcess": autosklearn.pipeline.components.regression.gaussian_process.GaussianProcess,
-    "GradientBoosting": autosklearn.pipeline.components.regression.gradient_boosting.GradientBoosting,
-    "KNearestNeighborsRegressor": autosklearn.pipeline.components.regression.k_nearest_neighbors.KNearestNeighborsRegressor,
-    "LibLinear_SVR": autosklearn.pipeline.components.regression.liblinear_svr.LibLinear_SVR,
-    "LibSVM_SVR": autosklearn.pipeline.components.regression.libsvm_svr.LibSVM_SVR,
-    "MLPRegressor": autosklearn.pipeline.components.regression.mlp.MLPRegressor,
-    "RandomForest": autosklearn.pipeline.components.regression.random_forest.RandomForest,
-    "SGD": autosklearn.pipeline.components.regression.sgd.SGD,
-}
-
-
-"""
-LibSVM_SVR, MLP and SGD have problems of requiring inverse_transform
-of StandardScaler while having 1D array
-https://github.com/automl/auto-sklearn/issues/1297
-problem solved
-"""
diff --git a/InsurAutoML/_scaling/_legacy.py b/InsurAutoML/_scaling/_legacy.py
deleted file mode 100644
index 520dbce..0000000
--- a/InsurAutoML/_scaling/_legacy.py
+++ /dev/null
@@ -1,61 +0,0 @@
-"""
-File Name: _legacy.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_scaling/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 8:16:22 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from ._scaling import (
-    MinMaxScale,
-    Standardize,
-    Normalize,
-    RobustScale,
-    PowerTransformer,
-    QuantileTransformer,
-    Winsorization,
-)
-from InsurAutoML._base import no_processing
-
-scalings = {
-    "no_processing": no_processing,
-    "MinMaxScale": MinMaxScale,
-    "Standardize": Standardize,
-    "Normalize": Normalize,
-    "RobustScale": RobustScale,
-    "PowerTransformer": PowerTransformer,
-    "QuantileTransformer": QuantileTransformer,
-    "Winsorization": Winsorization,
-}
diff --git a/InsurAutoML/_utils/_c/__init__.py b/InsurAutoML/_utils/_c/__init__.py
deleted file mode 100644
index e5a212d..0000000
--- a/InsurAutoML/_utils/_c/__init__.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-File: __init__.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: My_AutoML
-Last Version: 0.2.1
-Relative Path: /My_AutoML/_utils/_c/__init__.py
-File Created: Sunday, 25th September 2022 6:37:28 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Sunday, 25th September 2022 6:53:41 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from My_AutoML._utils._c._base import (
-    random_guess,
-    random_index,
-    random_list,
-    minloc,
-    maxloc,
-)
diff --git a/InsurAutoML/_utils/_tensor.py b/InsurAutoML/_utils/_tensor.py
deleted file mode 100644
index 4f65c54..0000000
--- a/InsurAutoML/_utils/_tensor.py
+++ /dev/null
@@ -1,92 +0,0 @@
-"""
-File Name: _tensor.py
-Author: Panyi Dong
-GitHub: https://github.com/PanyiDong/
-Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
-
-Project: InsurAutoML
-Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_tensor.py
-File Created: Monday, 24th October 2022 11:56:57 pm
-Author: Panyi Dong (panyid2@illinois.edu)
-
------
-Last Modified: Monday, 14th November 2022 9:46:39 pm
-Modified By: Panyi Dong (panyid2@illinois.edu)
-
------
-MIT License
-
-Copyright (c) 2022 - 2022, Panyi Dong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-from typing import Union, Dict, Tuple, List
-import torch
-from torch.utils.data import Dataset
-
-# detach tensor from the computation graph
-
-
-def repackage_hidden(h):
-
-    if isinstance(h, torch.Tensor):
-        return h.detach()
-    else:
-        return tuple(repackage_hidden(v) for v in h)
-
-
-class CustomTensorDataset(Dataset):
-
-    """
-    Custom Tensor Dataset
-    """
-
-    def __init__(
-        self,
-        inputs: torch.Tensor,
-        labels: torch.Tensor,
-        format: str = "tuple",
-    ) -> None:
-        self.inputs = inputs
-        self.labels = labels
-        self.format = format
-
-        if len(self.inputs) != len(self.labels):
-            raise ValueError("inputs and labels must have the same length")
-
-    def __len__(self) -> int:
-        return len(self.inputs)
-
-    def __getitem__(self, idx: int) -> Union[Dict, Tuple, List]:
-        if self.format == "dict":
-            return {"input": self.inputs[idx], "label": self.labels[idx]}
-        elif self.format == "tuple":
-            return (self.inputs[idx], self.labels[idx])
-        elif self.format == "list":
-            return [self.inputs[idx], self.labels[idx]]
-
-    def inputSize(self) -> int:
-
-        return self.inputs.size()[-1]
-
-    def outputSize(self) -> int:
-
-        return len(self.labels.unique())
diff --git a/InsurAutoML/_archive/_discard.py b/InsurAutoML/archive/_discard.py
similarity index 99%
rename from InsurAutoML/_archive/_discard.py
rename to InsurAutoML/archive/_discard.py
index 57dff3c..429fdb6 100644
--- a/InsurAutoML/_archive/_discard.py
+++ b/InsurAutoML/archive/_discard.py
@@ -4,13 +4,13 @@
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
-Project: My_AutoML
-Relative Path: /archive/_discard.py
+Project: InsurAutoML
+Relative Path: /InsurAutoML/archive/_discard.py
 File Created: Friday, 25th February 2022 6:13:42 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 24th October 2022 10:52:37 pm
+Last Modified: Monday, 28th November 2022 11:35:46 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -105,7 +105,7 @@
 from rpy2.robjects.packages import importr
 from sklearn.tree import ExtraTreeClassifier
 
-from InsurAutoML._encoding import DataEncoding
+from InsurAutoML.encoding import DataEncoding
 
 _feature_mol: dict = {
     "no_preprocessing": NoPreprocessing,
diff --git a/InsurAutoML/_archive/_enas/_RNN.py b/InsurAutoML/archive/_enas/_RNN.py
similarity index 97%
rename from InsurAutoML/_archive/_enas/_RNN.py
rename to InsurAutoML/archive/_enas/_RNN.py
index 5ad2e0d..9085c16 100644
--- a/InsurAutoML/_archive/_enas/_RNN.py
+++ b/InsurAutoML/archive/_enas/_RNN.py
@@ -4,14 +4,14 @@
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
-Project: My_AutoML
+Project: InsurAutoML
 Last Version: 0.2.1
-Relative Path: /My_AutoML/_utils/_nas/_enas/_rnn.py
+Relative Path: /InsurAutoML/archive/_enas/_RNN.py
 File Created: Saturday, 16th July 2022 2:54:09 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 24th October 2022 10:52:45 pm
+Last Modified: Monday, 28th November 2022 11:19:13 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -43,7 +43,7 @@
 import torch.nn as nn
 from torch.nn import Parameter
 
-from InsurAutoML._utils._nas._ops import activation_dict
+from InsurAutoML.utils._nas._ops import activation_dict
 
 
 class CustomRNNCell(nn.Module):
@@ -117,13 +117,15 @@ def forward(self, input, hidden):
                 if self.bias:
                     node_value[idx] = self.activations[idx](
                         torch.mm(input, self.inputWeights)
-                        + torch.mm(hidden, self.hiddenWeights[idx, :, :].clone())
+                        + torch.mm(hidden,
+                                   self.hiddenWeights[idx, :, :].clone())
                         + self.hiddenBias[idx, :].clone()
                     )
                 else:
                     node_value[idx] = self.activations[idx](
                         torch.mm(input, self.inputWeights)
-                        + torch.mm(hidden, self.hiddenWeights[idx, :, :].clone())
+                        + torch.mm(hidden,
+                                   self.hiddenWeights[idx, :, :].clone())
                     )
             # from the second node: only use previous hidden
             else:
diff --git a/InsurAutoML/_archive/_enas/__init__.py b/InsurAutoML/archive/_enas/__init__.py
similarity index 100%
rename from InsurAutoML/_archive/_enas/__init__.py
rename to InsurAutoML/archive/_enas/__init__.py
diff --git a/InsurAutoML/_archive/_enas/_controller.py b/InsurAutoML/archive/_enas/_controller.py
similarity index 94%
rename from InsurAutoML/_archive/_enas/_controller.py
rename to InsurAutoML/archive/_enas/_controller.py
index f59a063..71bd3c7 100644
--- a/InsurAutoML/_archive/_enas/_controller.py
+++ b/InsurAutoML/archive/_enas/_controller.py
@@ -4,14 +4,14 @@
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
-Project: My_AutoML
+Project: InsurAutoML
 Last Version: 0.2.1
-Relative Path: /My_AutoML/_utils/_nas/_enas/_controller.py
+Relative Path: /InsurAutoML/archive/_enas/_controller.py
 File Created: Saturday, 16th July 2022 11:00:51 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 24th October 2022 10:52:48 pm
+Last Modified: Monday, 28th November 2022 11:19:18 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -42,8 +42,8 @@
 import torch.nn as nn
 import torch.nn.functional as F
 
-from InsurAutoML._utils._nas._ops import activation_dict
-from InsurAutoML._utils._nas._enas._utils import decision_mask
+from InsurAutoML.utils._nas._ops import activation_dict
+from InsurAutoML.utils._nas._enas._utils import decision_mask
 
 
 class Controller(nn.Module):
diff --git a/InsurAutoML/_archive/_enas/_trainer.py b/InsurAutoML/archive/_enas/_trainer.py
similarity index 91%
rename from InsurAutoML/_archive/_enas/_trainer.py
rename to InsurAutoML/archive/_enas/_trainer.py
index 109aa1e..8a74b38 100644
--- a/InsurAutoML/_archive/_enas/_trainer.py
+++ b/InsurAutoML/archive/_enas/_trainer.py
@@ -4,14 +4,14 @@
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
-Project: My_AutoML
+Project: InsurAutoML
 Last Version: 0.2.1
-Relative Path: /My_AutoML/_utils/_nas/_enas/_trainer.py
+Relative Path: /InsurAutoML/archive/_enas/_trainer.py
 File Created: Sunday, 17th July 2022 3:19:34 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 24th October 2022 10:52:53 pm
+Last Modified: Monday, 28th November 2022 11:43:32 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -43,9 +43,9 @@
 import torch.optim as optim
 from torch.utils.data import DataLoader, TensorDataset
 
-from InsurAutoML._utils._nas._enas._RNN import CustomRNNCell, CustomRNN
-from InsurAutoML._utils._nas._enas._controller import Controller
-from InsurAutoML._utils._tensor import repackage_hidden
+from InsurAutoML.utils._nas._enas._RNN import CustomRNNCell, CustomRNN
+from InsurAutoML.utils._nas._enas._controller import Controller
+from InsurAutoML.utils.tensor import repackage_hidden
 
 inputSize = 512
 outputSize = 2
diff --git a/InsurAutoML/_archive/_enas/_utils.py b/InsurAutoML/archive/_enas/_utils.py
similarity index 100%
rename from InsurAutoML/_archive/_enas/_utils.py
rename to InsurAutoML/archive/_enas/_utils.py
diff --git a/InsurAutoML/_archive/_extension/__init__.py b/InsurAutoML/archive/_extension/__init__.py
similarity index 100%
rename from InsurAutoML/_archive/_extension/__init__.py
rename to InsurAutoML/archive/_extension/__init__.py
diff --git a/InsurAutoML/_archive/_extension/pytorch_ext.cpp b/InsurAutoML/archive/_extension/pytorch_ext.cpp
similarity index 100%
rename from InsurAutoML/_archive/_extension/pytorch_ext.cpp
rename to InsurAutoML/archive/_extension/pytorch_ext.cpp
diff --git a/InsurAutoML/_archive/_model.py b/InsurAutoML/archive/_model.py
similarity index 100%
rename from InsurAutoML/_archive/_model.py
rename to InsurAutoML/archive/_model.py
diff --git a/InsurAutoML/_archive/_multimodal.py b/InsurAutoML/archive/_multimodal.py
similarity index 100%
rename from InsurAutoML/_archive/_multimodal.py
rename to InsurAutoML/archive/_multimodal.py
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/__init__.py b/InsurAutoML/archive/_nas/__init__.py
similarity index 87%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/__init__.py
rename to InsurAutoML/archive/_nas/__init__.py
index 9a18381..e415eb0 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/__init__.py
+++ b/InsurAutoML/archive/_nas/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/__init__.py
+Relative Path: /InsurAutoML/_nn/_archive/_nas/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:31 pm
+Last Modified: Friday, 25th November 2022 11:17:33 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,5 +38,5 @@
 SOFTWARE.
 """
 
-from ._trainer import Trainer
-from ._baseSpace import MLPBaseSpace, RNNBaseSpace
+# from ....._nn._nas._trainer import Trainer
+# from ._baseSpace import MLPBaseSpace, RNNBaseSpace
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_buildModel.py b/InsurAutoML/archive/_nas/_buildModel.py
similarity index 100%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_buildModel.py
rename to InsurAutoML/archive/_nas/_buildModel.py
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_enas.py b/InsurAutoML/archive/_nas/_enas.py
similarity index 99%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_enas.py
rename to InsurAutoML/archive/_nas/_enas.py
index 16c91b0..4b03b83 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_enas.py
+++ b/InsurAutoML/archive/_nas/_enas.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:42 pm
+Last Modified: Friday, 25th November 2022 11:15:03 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,7 +45,7 @@
 import nni.retiarii.nn.pytorch as nninn
 from nni.retiarii.oneshot.pytorch.random import PathSamplingInputChoice
 
-from ._ops import (
+from .._nn._utils._ops import (
     SepConvBN,  # base space
     AvgPool,
     MaxPool,
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_evaluator.py b/InsurAutoML/archive/_nas/_evaluator.py
similarity index 92%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_evaluator.py
rename to InsurAutoML/archive/_nas/_evaluator.py
index 07a559c..adf1605 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_evaluator.py
+++ b/InsurAutoML/archive/_nas/_evaluator.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:46 pm
+Last Modified: Sunday, 20th November 2022 7:50:49 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -42,9 +42,11 @@
 import torch
 import torch.nn as nn
 from torch.utils.data import DataLoader
+# from nni.retiarii.evaluator.pytorch import DataLoader
 import nni
 from nni.retiarii.evaluator import FunctionalEvaluator
 
+DataLoader = nni.trace(DataLoader)
 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
 
@@ -137,10 +139,10 @@ def get_evaluator(
 ):
     return FunctionalEvaluator(
         modelEvaluation,
-        trainloader=nni.trace(DataLoader)(
-            trainset, batch_size=batchSize, shuffle=True, drop_last=True
+        trainloader=DataLoader(
+            trainset, batch_size=batchSize, shuffle=True  # , drop_last=True
         ),
-        testloader=nni.trace(DataLoader)(testset, batch_size=batchSize),
+        testloader=DataLoader(testset, batch_size=batchSize),
         optimizer=get_optimizer(optimizer),
         criteria=get_criterion(criterion)(),
         num_epoch=num_epoch,
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_evaulatorPL.py b/InsurAutoML/archive/_nas/_evaulatorPL.py
similarity index 60%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_evaulatorPL.py
rename to InsurAutoML/archive/_nas/_evaulatorPL.py
index 35ba3f4..26ce01e 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_evaulatorPL.py
+++ b/InsurAutoML/archive/_nas/_evaulatorPL.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:51 pm
+Last Modified: Thursday, 24th November 2022 4:18:43 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -40,12 +40,22 @@
 
 # pytorch-lightning version of evaluator
 
-from ._utils import get_optimizer, get_criterion, get_scheduler
-import nni.retiarii.evaluator.pytorch.lightning as pl
+from collections.abc import Iterable
 import nni
+import nni.retiarii.evaluator.pytorch.lightning as pl
+# from nni.retiarii.evaluator.pytorch import DataLoader
 import torch
+from torch.utils.data import DataLoader
+from pytorch_lightning.loggers import TensorBoardLogger
+from pytorch_lightning.callbacks.early_stopping import EarlyStopping
 import importlib
 
+from InsurAutoML._experimental._nn._nni._nas._utils import tensor_accuracy
+from ._utils import get_optimizer, get_criterion, get_scheduler
+
+# serialized version of dataloader
+DataLoader = nni.trace(DataLoader)
+
 pl_spec = importlib.util.find_spec("pytorch_lightning")
 # if pl not found, raise error
 if pl_spec is None:
@@ -54,11 +64,23 @@
         Use `pip install pytorch-lightning` to install.")
 
 
+def repackage_hidden(h):
+    if isinstance(h, torch.Tensor):
+        return h.detach()
+    else:
+        return tuple(repackage_hidden(v) for v in h)
+
+
 @nni.trace
 class plEvaluator(pl.LightningModule):
+
+    ACCURACY_TYPE = ["binary", "multiclass"]
+
     def __init__(
         self,
         model,
+        type_of_task="multiclass",
+        batch_size=32,
         criterion="CrossEntropyLoss",
         optimizer="Adam",
         optimizer_lr=1e-3,
@@ -68,29 +90,65 @@ def __init__(
 
         # set parameters
         self.model = model
+        self.type_of_task = type_of_task
+        self.batch_size = batch_size
         self.criterion = get_criterion(criterion)()
         self.optimizer = get_optimizer(optimizer)
         self.optimizer_lr = optimizer_lr
         self.lr_scheduler = get_scheduler(lr_scheduler)
 
     def forward(self, X):
+        if self._hidden is not None:
+            # only get the values of hidden, no need for gradient
+            self._hidden = repackage_hidden(self._hidden)
+            output, self._hidden = self.model(X, self._hidden)
+        else:
+            output = self.model(X)
 
-        return self.model(X)
+        return output
 
     def training_step(self, batch, batch_idx):
+
+        # initialize hidden state for each epoch
+        if not hasattr(self, '_train_epoch'):
+            self._train_epoch = self.current_epoch
+            self.init_hidden()
+        elif self._train_epoch != self.current_epoch:
+            self._train_epoch = self.current_epoch
+            self.init_hidden()
+
         input, label = batch  # parse the input batch
-        output = self.model(input)  # forward phase
+        # forward phase
+        output = self(input)
+
         loss = self.criterion(output, label)  # compute loss
         self.log("train_loss", loss)  # Logging to TensorBoard by default
 
         return loss
 
     def validation_step(self, batch, batch_idx):
+
+        # initialize hidden state for each epoch
+        if not hasattr(self, '_valid_epoch'):
+            self._valid_epoch = self.current_epoch
+            self.init_hidden()
+        elif self._valid_epoch != self.current_epoch:
+            self._valid_epoch = self.current_epoch
+            self.init_hidden()
+
         input, label = batch  # parse the input batch
-        output = self.model(input)  # forward phase
-        # print(output, label)
+        output = self(input)
         loss = self.criterion(output, label)  # compute loss
-        self.log("val_loss", loss)  # Logging to TensorBoard by default
+        # # if classification job, use accuracy
+        # if self.type_of_task in self.ACCURACY_TYPE:
+        #     # Logging to TensorBoard by default
+        #     self.log("val_loss", tensor_accuracy(output, label))
+        # # for regression job, use -criterion to satisfy maximization setting
+        # else:
+        #     self.log("val_loss", -loss)  # Logging to TensorBoard by default
+        self.log("val_loss", -loss)
+
+        return {"- val_loss": -loss}
 
     def configure_optimizers(self):
 
@@ -114,28 +172,46 @@ def teardown(self, stage):
             nni.report_final_result(
                 self.trainer.callback_metrics["val_loss"].item())
 
+    def init_hidden(self):
 
-@nni.trace
+        if hasattr(self.model, 'init_hidden'):
+            self._hidden = self.model.init_hidden(self.batch_size)
+        else:
+            self._hidden = None
+
+
+# @nni.trace
 def get_evaluator(
     model,
     train_set,
     test_set,
     batchSize=32,
+    # type_of_task="multiclass",
     criterion="CrossEntropyLoss",
     optimizer="Adam",
     optimizer_lr=1e-3,
     lr_scheduler="None",
     num_epochs=10,
+    log_dir="tmp/NAS"
 ):
+    # setup pytorch_lightning logger
+    logger = TensorBoardLogger(log_dir, name="pl_logs")
+
     return pl.Lightning(
-        lightning_module=nni.trace(plEvaluator)(
+        lightning_module=plEvaluator(
             model=model,
+            # type_of_task=type_of_task,
+            batch_size=batchSize,
             criterion=criterion,
             optimizer=optimizer,
             optimizer_lr=optimizer_lr,
             lr_scheduler=lr_scheduler,
         ),
         trainer=pl.Trainer(
+            logger=logger,
+            # early stopping when loss is nan
+            callbacks=[EarlyStopping(
+                monitor="val_loss", mode="min", check_finite=True)],
             max_epochs=num_epochs,
             gpus=torch.cuda.device_count()),
         train_dataloader=pl.DataLoader(
@@ -147,5 +223,6 @@ def get_evaluator(
         val_dataloaders=pl.DataLoader(
             test_set,
             batch_size=batchSize,
+            drop_last=True,
         ),
     )
diff --git a/InsurAutoML/_hpo/_experiment.py b/InsurAutoML/archive/_nas/_utils.py
similarity index 90%
rename from InsurAutoML/_hpo/_experiment.py
rename to InsurAutoML/archive/_nas/_utils.py
index b1d0430..a9a4c55 100644
--- a/InsurAutoML/_hpo/_experiment.py
+++ b/InsurAutoML/archive/_nas/_utils.py
@@ -1,17 +1,17 @@
 """
-File Name: _experiment.py
+File Name: _utils.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/_experiment.py
+Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_utils.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:51:36 pm
+Last Modified: Friday, 25th November 2022 11:12:42 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -37,3 +37,5 @@
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 """
+
+
diff --git a/InsurAutoML/_balancing/__init__.py b/InsurAutoML/balancing/__init__.py
similarity index 78%
rename from InsurAutoML/_balancing/__init__.py
rename to InsurAutoML/balancing/__init__.py
index 273554a..21615f8 100644
--- a/InsurAutoML/_balancing/__init__.py
+++ b/InsurAutoML/balancing/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_balancing/__init__.py
+Relative Path: /InsurAutoML/balancing/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:57:33 pm
+Last Modified: Monday, 28th November 2022 11:36:53 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,8 +38,8 @@
 SOFTWARE.
 """
 
-from ._over_sampling import SimpleRandomOverSampling, Smote
-from ._under_sampling import (
+from .over_sampling import SimpleRandomOverSampling, Smote
+from .under_sampling import (
     SimpleRandomUnderSampling,
     TomekLink,
     EditedNearestNeighbor,
@@ -47,8 +47,8 @@
     OneSidedSelection,
     CNN_TomekLink,
 )
-from ._mixed_sampling import Smote_TomekLink, Smote_ENN
-from InsurAutoML._base import no_processing
+from .mixed_sampling import Smote_TomekLink, Smote_ENN
+from InsurAutoML.base import no_processing
 
 balancings = {
     "no_processing": no_processing,
@@ -63,3 +63,18 @@
     "Smote_TomekLink": Smote_TomekLink,
     "Smote_ENN": Smote_ENN,
 }
+
+
+__all__ = [
+    "no_processing",
+    "SimpleRandomOverSampling",
+    "SimpleRandomUnderSampling",
+    "TomekLink",
+    "EditedNearestNeighbor",
+    "CondensedNearestNeighbor",
+    "OneSidedSelection",
+    "CNN_TomekLink",
+    "Smote",
+    "Smote_TomekLink",
+    "Smote_ENN",
+]
diff --git a/InsurAutoML/_balancing/_mixed_sampling.py b/InsurAutoML/balancing/mixed_sampling.py
similarity index 95%
rename from InsurAutoML/_balancing/_mixed_sampling.py
rename to InsurAutoML/balancing/mixed_sampling.py
index 16fe355..9a91014 100644
--- a/InsurAutoML/_balancing/_mixed_sampling.py
+++ b/InsurAutoML/balancing/mixed_sampling.py
@@ -1,17 +1,17 @@
 """
-File Name: _mixed_sampling.py
+File Name: mixed_sampling.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_balancing/_mixed_sampling.py
+Relative Path: /InsurAutoML/balancing/mixed_sampling.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:58:03 pm
+Last Modified: Monday, 28th November 2022 11:41:18 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,9 +45,9 @@
 import pandas as pd
 import warnings
 
-from InsurAutoML._utils._data import is_imbalance
-from ._over_sampling import Smote
-from ._under_sampling import TomekLink, EditedNearestNeighbor
+from InsurAutoML.utils.data import is_imbalance
+from .over_sampling import Smote
+from .under_sampling import TomekLink, EditedNearestNeighbor
 
 """
 Reference for: Simple Random Over Sampling, Simple Random Under Sampling, Tomek Link, \
diff --git a/InsurAutoML/_balancing/_over_sampling.py b/InsurAutoML/balancing/over_sampling.py
similarity index 97%
rename from InsurAutoML/_balancing/_over_sampling.py
rename to InsurAutoML/balancing/over_sampling.py
index e895d22..f2d87fd 100644
--- a/InsurAutoML/_balancing/_over_sampling.py
+++ b/InsurAutoML/balancing/over_sampling.py
@@ -1,17 +1,17 @@
 """
-File Name: _over_sampling.py
+File Name: over_sampling.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_balancing/_over_sampling.py
+Relative Path: /InsurAutoML/balancing/over_sampling.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:58:11 pm
+Last Modified: Monday, 28th November 2022 11:41:15 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -47,7 +47,7 @@
 import sklearn
 import sklearn.utils
 
-from InsurAutoML._utils._data import is_imbalance, LinkTable
+from InsurAutoML.utils.data import is_imbalance, LinkTable
 
 """
 Reference for: Simple Random Over Sampling, Simple Random Under Sampling, Tomek Link, \
diff --git a/InsurAutoML/_balancing/_under_sampling.py b/InsurAutoML/balancing/under_sampling.py
similarity index 98%
rename from InsurAutoML/_balancing/_under_sampling.py
rename to InsurAutoML/balancing/under_sampling.py
index 89af5fe..f1406d8 100644
--- a/InsurAutoML/_balancing/_under_sampling.py
+++ b/InsurAutoML/balancing/under_sampling.py
@@ -1,17 +1,17 @@
 """
-File Name: _under_sampling.py
+File Name: under_sampling.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_balancing/_under_sampling.py
+Relative Path: /InsurAutoML/balancing/under_sampling.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:58:22 pm
+Last Modified: Monday, 28th November 2022 11:41:09 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -47,7 +47,7 @@
 import sklearn
 import sklearn.utils
 
-from InsurAutoML._utils._data import is_imbalance, LinkTable
+from InsurAutoML.utils.data import is_imbalance, LinkTable
 
 """
 Reference for: Simple Random Over Sampling, Simple Random Under Sampling, Tomek Link, \
@@ -488,7 +488,8 @@ def _fit_transform(self, X: pd.DataFrame) -> pd.DataFrame:
             _minority_class = X.loc[X[_imbalanced_feature] != _majority]
             _majority_class = X.loc[X[_imbalanced_feature] == _majority]
             _subset = pd.concat(
-                [_minority_class, _majority_class.sample(n=1, random_state=_seed)]
+                [_minority_class, _majority_class.sample(
+                    n=1, random_state=_seed)]
             ).reset_index(drop=True)
             neigh = KNeighborsClassifier(n_neighbors=1)
             neigh.fit(
diff --git a/InsurAutoML/_base.py b/InsurAutoML/base.py
similarity index 100%
rename from InsurAutoML/_base.py
rename to InsurAutoML/base.py
diff --git a/InsurAutoML/_constant.py b/InsurAutoML/constant.py
similarity index 97%
rename from InsurAutoML/_constant.py
rename to InsurAutoML/constant.py
index 6ad7346..625ef86 100644
--- a/InsurAutoML/_constant.py
+++ b/InsurAutoML/constant.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 4:03:08 pm
+Last Modified: Saturday, 26th November 2022 12:40:39 am
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -42,6 +42,15 @@
 
 LOGGINGLEVEL = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
 
+NNI_VERBOSE = [
+    # "fatal",
+    "error",
+    "warning",
+    "info",
+    "debug",
+    "trace",
+]
+
 FULLTYPE_MAPPING = {
     ("Int", "Numerical"): "con",
     ("Int", "Categorical"): "cat",
diff --git a/InsurAutoML/_datasets/__init__.py b/InsurAutoML/datasets/__init__.py
similarity index 72%
rename from InsurAutoML/_datasets/__init__.py
rename to InsurAutoML/datasets/__init__.py
index dc631b9..c9c526b 100644
--- a/InsurAutoML/_datasets/__init__.py
+++ b/InsurAutoML/datasets/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_datasets/__init__.py
+Relative Path: /InsurAutoML/datasets/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:58:40 pm
+Last Modified: Monday, 28th November 2022 11:39:31 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,7 +38,7 @@
 SOFTWARE.
 """
 
-from ._tabular import (
+from .tabular import (
     ADULT,  # Classification
     AUTO,
     BREAST,
@@ -85,7 +85,7 @@
 
 # datasets for Neural Network
 if datasets_spec is not None:
-    from ._text import (
+    from .text import (
         IMDB,  # Text Classification
         GLUE,
         AG_NEWS,
@@ -94,8 +94,58 @@
         ADVERSARIAL_QA,
     )
 
-    from ._image import (
+    from .image import (
         CIFAR,
         MNIST,
         IMAGENET,
     )
+
+
+__all__ = [
+    "ADULT",
+    "AUTO",
+    "BREAST",
+    "CAR_EVALUATION",
+    "NURSERY",
+    "PAGE_BLOCKS",
+    "HEART",
+    "EMPLOYEE",
+    "STROKE",
+    "HEART2020",
+    "TRAVEL_INSURANCE",
+    "IMBALANCED_INSURANCE",
+    "CPU_ACT",
+    "WIND",
+    "HOUSES",
+    "INSURANCE",
+    "MEDICAL_PREMIUM",
+    "PROD",
+    "JIGSAW",
+    "JIGSAW100K",
+    "AIRBNB",
+    "IMDBGenre",
+    "FakeJob",
+    "FakeJob2",
+    "KickStarter",
+    "WINEReview",
+    "NewsChannel",
+    "WomenCloth",
+    "MERCARI",
+    "MERCARI100K",
+    "AE",
+    "JCPenney",
+    "NewsPopularity",
+    "NewsPopularity2",
+    "BookPrice",
+    "DSSalary",
+    "CAHousePrice",
+    "IMDB",
+    "GLUE",
+    "AG_NEWS",
+    "WIKITEXT",
+    "SQUAD",
+    "ADVERSARIAL_QA",
+    "CIFAR",
+    "MNIST",
+    "IMAGENET",
+]
diff --git a/InsurAutoML/_datasets/_image.py b/InsurAutoML/datasets/image.py
similarity index 100%
rename from InsurAutoML/_datasets/_image.py
rename to InsurAutoML/datasets/image.py
diff --git a/InsurAutoML/_datasets/_tabular.py b/InsurAutoML/datasets/tabular.py
similarity index 100%
rename from InsurAutoML/_datasets/_tabular.py
rename to InsurAutoML/datasets/tabular.py
diff --git a/InsurAutoML/_datasets/_text.py b/InsurAutoML/datasets/text.py
similarity index 100%
rename from InsurAutoML/_datasets/_text.py
rename to InsurAutoML/datasets/text.py
diff --git a/InsurAutoML/_encoding/__init__.py b/InsurAutoML/encoding/__init__.py
similarity index 89%
rename from InsurAutoML/_encoding/__init__.py
rename to InsurAutoML/encoding/__init__.py
index 313d4f4..1eeb708 100644
--- a/InsurAutoML/_encoding/__init__.py
+++ b/InsurAutoML/encoding/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_encoding/__init__.py
+Relative Path: /InsurAutoML/encoding/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:00:01 pm
+Last Modified: Monday, 28th November 2022 11:39:39 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,6 +38,8 @@
 SOFTWARE.
 """
 
-from ._encoding import DataEncoding
+from .encoding import DataEncoding
 
 encoders = {"DataEncoding": DataEncoding}
+
+__all__ = ["DataEncoding"]
diff --git a/InsurAutoML/_encoding/_encoding.py b/InsurAutoML/encoding/encoding.py
similarity index 93%
rename from InsurAutoML/_encoding/_encoding.py
rename to InsurAutoML/encoding/encoding.py
index 5c7823a..9b0fb44 100644
--- a/InsurAutoML/_encoding/_encoding.py
+++ b/InsurAutoML/encoding/encoding.py
@@ -1,17 +1,17 @@
 """
-File Name: _encoding.py
+File Name: encoding.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_encoding/_encoding.py
+Relative Path: /InsurAutoML/encoding/encoding.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:00:08 pm
+Last Modified: Monday, 28th November 2022 11:41:32 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -43,8 +43,8 @@
 import numpy as np
 import pandas as pd
 from sklearn import preprocessing
-from InsurAutoML._utils._base import is_date
-from InsurAutoML._utils._data import formatting
+from InsurAutoML.utils.base import is_date
+from InsurAutoML.utils.data import formatting
 
 
 class DataEncoding(formatting):
@@ -98,7 +98,8 @@ def fit(self, _df: pd.DataFrame) -> pd.DataFrame:
                         self.category = pd.DataFrame({column: unique_value})
                     else:
                         self.category = pd.concat(
-                            [self.category, pd.DataFrame({column: unique_value})],
+                            [self.category, pd.DataFrame(
+                                {column: unique_value})],
                             axis=1,
                         )
                     for elem in unique_value:
@@ -110,7 +111,8 @@ def fit(self, _df: pd.DataFrame) -> pd.DataFrame:
                         self.category = pd.DataFrame({column: unique_value})
                     else:
                         self.category = pd.concat(
-                            [self.category, pd.DataFrame({column: unique_value})],
+                            [self.category, pd.DataFrame(
+                                {column: unique_value})],
                             axis=1,
                         )
                     for i in range(len(unique_value)):
@@ -139,7 +141,8 @@ def fit(self, _df: pd.DataFrame) -> pd.DataFrame:
                     # save scale map for scale back
                     self.mean_scaler.update({column: standard_scaler.mean_[0]})
                     df.loc[~df[column].isnull(), column] = (
-                        df.loc[~df[column].isnull(), column] - standard_scaler.mean_[0]
+                        df.loc[~df[column].isnull(), column] -
+                        standard_scaler.mean_[0]
                     )
                 elif self.transform == "log":
                     df.loc[~df[column].isnull(), column] = np.log(
@@ -215,7 +218,8 @@ def refit(self, _df: pd.DataFrame) -> pd.DataFrame:
                     # save scale map for scale back
                     self.mean_scaler.update({column: standard_scaler.mean_[0]})
                     df.loc[~df[column].isnull(), column] = (
-                        df.loc[~df[column].isnull(), column] - standard_scaler.mean_[0]
+                        df.loc[~df[column].isnull(), column] -
+                        standard_scaler.mean_[0]
                     )
                 elif self.transform == "log":
                     df.loc[~df[column].isnull(), column] = np.log(
diff --git a/InsurAutoML/_experimental/_XAI/_Relevance/_ML.py b/InsurAutoML/experimental/_XAI/_Relevance/_ML.py
similarity index 100%
rename from InsurAutoML/_experimental/_XAI/_Relevance/_ML.py
rename to InsurAutoML/experimental/_XAI/_Relevance/_ML.py
diff --git a/InsurAutoML/_experimental/_XAI/_Relevance/_NN.py b/InsurAutoML/experimental/_XAI/_Relevance/_NN.py
similarity index 100%
rename from InsurAutoML/_experimental/_XAI/_Relevance/_NN.py
rename to InsurAutoML/experimental/_XAI/_Relevance/_NN.py
diff --git a/InsurAutoML/_experimental/_XAI/_Relevance/__init__.py b/InsurAutoML/experimental/_XAI/_Relevance/__init__.py
similarity index 100%
rename from InsurAutoML/_experimental/_XAI/_Relevance/__init__.py
rename to InsurAutoML/experimental/_XAI/_Relevance/__init__.py
diff --git a/InsurAutoML/_experimental/_XAI/__init__.py b/InsurAutoML/experimental/_XAI/__init__.py
similarity index 100%
rename from InsurAutoML/_experimental/_XAI/__init__.py
rename to InsurAutoML/experimental/_XAI/__init__.py
diff --git a/InsurAutoML/_experimental/_nn/__init__.py b/InsurAutoML/experimental/_nn/__init__.py
similarity index 100%
rename from InsurAutoML/_experimental/_nn/__init__.py
rename to InsurAutoML/experimental/_nn/__init__.py
diff --git a/InsurAutoML/_experimental/_nn/_nni/__init__.py b/InsurAutoML/experimental/_nn/_nni/__init__.py
similarity index 100%
rename from InsurAutoML/_experimental/_nn/_nni/__init__.py
rename to InsurAutoML/experimental/_nn/_nni/__init__.py
diff --git a/InsurAutoML/_experimental/_nn/_nni/_hpo/__init__.py b/InsurAutoML/experimental/_nn/_nni/_hpo/__init__.py
similarity index 100%
rename from InsurAutoML/_experimental/_nn/_nni/_hpo/__init__.py
rename to InsurAutoML/experimental/_nn/_nni/_hpo/__init__.py
diff --git a/InsurAutoML/experimental/nn/__init__.py b/InsurAutoML/experimental/nn/__init__.py
new file mode 100644
index 0000000..f7003b4
--- /dev/null
+++ b/InsurAutoML/experimental/nn/__init__.py
@@ -0,0 +1,50 @@
+"""
+File Name: __init__.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/__init__.py
+File Created: Friday, 25th November 2022 11:08:18 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 5th December 2022 5:10:49 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+try:
+    import pytorch_lightning
+except:
+    raise ImportError(
+        "Use of pytorch-lightning evaluator requires pytorch-lightning to be installed. \
+        Use `pip install pytorch-lightning` to install.")
+
+from .trainer import AutoMultiModalTabular
+from .nas import NasTrainer
+from .utils import MLPNet, LiteMLPNet, RNNet, LiteRNNet, FusTokenNet, FusEmbedNet, FusModelNet
diff --git a/InsurAutoML/experimental/nn/hpo/__init__.py b/InsurAutoML/experimental/nn/hpo/__init__.py
new file mode 100644
index 0000000..2837972
--- /dev/null
+++ b/InsurAutoML/experimental/nn/hpo/__init__.py
@@ -0,0 +1,39 @@
+"""
+File Name: __init__.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/_nn/_hpo/__init__.py
+File Created: Friday, 25th November 2022 11:25:37 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Friday, 25th November 2022 11:25:38 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
diff --git a/InsurAutoML/experimental/nn/hpo/evaluator.py b/InsurAutoML/experimental/nn/hpo/evaluator.py
new file mode 100644
index 0000000..e6aee7d
--- /dev/null
+++ b/InsurAutoML/experimental/nn/hpo/evaluator.py
@@ -0,0 +1,207 @@
+"""
+File Name: evaluator.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/hpo/evaluator.py
+File Created: Monday, 5th December 2022 2:49:26 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 5th December 2022 5:08:24 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+import argparse
+from typing import Union, Callable, List, Tuple, Dict
+import torch
+import torch.nn as nn
+import torch.optim as optim
+import pytorch_lightning as pl
+from pytorch_lightning import Trainer
+from pytorch_lightning.loggers import TensorBoardLogger
+from pytorch_lightning.callbacks.early_stopping import EarlyStopping
+import nni
+
+from ..utils.args import repackage_hidden
+from ..utils.hpSpace import search_space
+
+# get training arguments
+parser = argparse.ArgumentParser(description='HPO training for InsurAutoML')
+parser.add_argument("--log_dir", type=str,
+                    default="tmp/HPO", help="log directory")
+parser.add_argument("--use_gpu", type=bool,
+                    default=True, help="use GPU or not")
+args = parser.parse_args()
+
+# get arguments
+log_dir = args.log_dir
+use_gpu = args.use_gpu
+
+
+class HPOEvaluator(pl.LightningModule):
+
+    def __init__(
+        self,
+        model: Union[str, Callable],
+        use_gpu: bool = True,
+    ) -> None:
+        super().__init__()
+
+        self.model = model
+        self.use_gpu = use_gpu
+
+    @staticmethod
+    def _get_lr_sch_args(lr_sch: str, params: Dict[str, Union[str, float, int]]) -> Dict[str, Union[str, float, int]]:
+
+        if lr_sch == "StepLR":
+            return {"step_size": params["step_size"], "gamma": params["gamma"]}
+        elif lr_sch == "ConstantLR":
+            return {"factor": params["factor"], "total_iters": params["total_iters"]}
+        elif lr_sch == "LinearLR":
+            return {"start_factor": params["factor"], "total_iters": params["total_iters"]}
+        elif lr_sch == "ExponentialLR":
+            return {"gamma": params["gamma"]}
+        elif lr_sch == "PolynomialLR":
+            return {"power": params["power"], "total_iters": params["total_iters"]}
+        elif lr_sch == "ReduceLROnPlateau":
+            return {"factor": params["factor"]}
+
+    def forward(self, X: Union[torch.Tensor, List[torch.Tensor]]) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if self._hidden is not None:
+            # only get the values of hidden, no need for gradient
+            self._hidden = repackage_hidden(self._hidden)
+            output, self._hidden = self.model(X, self._hidden)
+        else:
+            output = self.model(X)
+
+        return output
+
+    def training_step(self, batch: Tuple[Union[torch.Tensor, List[torch.Tensor]], torch.Tensor], batch_idx: int) -> torch.Tensor:
+
+        input, label = batch  # parse the input batch
+        # forward phase
+        output = self(input)
+
+        loss = self.criterion(output, label)  # compute loss
+        self.log("train_loss", loss)  # Logging to TensorBoard by default
+
+        return loss
+
+    def validation_step(self, batch: Tuple[Union[torch.Tensor, List[torch.Tensor]], torch.Tensor], batch_idx: int) -> Dict[str, torch.Tensor]:
+
+        input, label = batch  # parse the input batch
+        output = self(input)
+        loss = self.criterion(output, label)  # compute loss
+        self.log("val_loss", -loss)
+
+        return {"val_loss": -loss}
+
+    def configure_optimizers(self) -> Union[Callable, Dict[str, Callable]]:
+
+        # initialize optimizer
+        self.optimizer = getattr(optim, params["optimizer"])(
+            self.parameters(), params["lr"], weight_decay=params["weight_decay"])
+        self.lr_scheduler = getattr(optim.lr_scheduler, params["lr_scheduler"]) if str(
+            params["lr_scheduler"]) is not "None" else None
+
+        if self.lr_scheduler is None:
+            return self.optimizer
+        else:
+            return {
+                "optimizer": self.optimizer,
+                "lr_scheduler": self.lr_scheduler(self.optimizer, **self._get_lr_sch_args(params["lr_scheduler"], params)),
+            }
+
+    def on_train_epoch_start(self) -> None:
+        # update train_epoch number
+        if not hasattr(self, '_train_epoch'):
+            self._train_epoch = self.current_epoch
+        else:
+            self._train_epoch += 1
+        # initialize hidden state for each epoch
+        self.init_hidden()
+
+    def on_train_epoch_end(self) -> None:
+        # scheduler step for ReduceLROnPlateau
+        _sch = self.lr_schedulers()
+        if isinstance(_sch, torch.optim.lr_scheduler.ReduceLROnPlateau):
+            _sch.step(self.trainer.callback_metrics["val_loss"])
+
+    def on_validation_epoch_start(self) -> None:
+        # update valid_epoch number
+        if not hasattr(self, '_valid_epoch'):
+            self._valid_epoch = self.current_epoch
+        else:
+            self._valid_epoch = self.current_epoch
+        # initialize hidden state for each epoch
+        self.init_hidden()
+
+    def on_validation_epoch_end(self) -> None:
+        nni.report_intermediate_result(
+            self.trainer.callback_metrics["val_loss"].item())
+
+    def teardown(self, stage) -> None:
+        if stage == "fit":
+            nni.report_final_result(
+                self.trainer.callback_metrics["val_loss"].item())
+
+    def init_hidden(self) -> None:
+
+        if hasattr(self.model, 'init_hidden'):
+            self._hidden = self.model.init_hidden(params["batch_size"])
+            # put hidden state on GPU if available and use_gpu is True
+            self._hidden = tuple([item.to(self._device) for item in self._hidden]) if isinstance(
+                self._hidden, (list, tuple)) else self._hidden.to(self._device)
+        else:
+            self._hidden = None
+
+
+if __name__ == "__main__":
+
+    # init search space
+    params = search_space
+    # get next hyper-parameter
+    optimized_params = nni.get_next_parameter()
+    params.update(optimized_params)
+
+    # logger
+    logger = TensorBoardLogger(log_dir, name="pl_logs")
+    # gpu_setting
+    accelerator_config = {"accelerator": "gpu", "devices": torch.cuda.device_count(
+    )} if use_gpu else {"accelerator": "cpu"}
+
+    trainer = Trainer(
+        logger=logger,
+        # early stopping when loss is nan
+        callbacks=[EarlyStopping(
+            monitor="val_loss", mode="min", check_finite=True)],
+        max_epochs=params["num_epochs"],
+        **accelerator_config,
+    )
diff --git a/InsurAutoML/_encoding/_legacy.py b/InsurAutoML/experimental/nn/hpo/trainer.py
similarity index 81%
rename from InsurAutoML/_encoding/_legacy.py
rename to InsurAutoML/experimental/nn/hpo/trainer.py
index f7f67cc..a26479b 100644
--- a/InsurAutoML/_encoding/_legacy.py
+++ b/InsurAutoML/experimental/nn/hpo/trainer.py
@@ -1,17 +1,17 @@
 """
-File Name: _legacy.py
+File Name: trainer.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_encoding/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
+Relative Path: /InsurAutoML/nn/hpo/trainer.py
+File Created: Monday, 28th November 2022 9:57:58 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:00:13 pm
+Last Modified: Tuesday, 29th November 2022 3:34:53 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,6 +38,12 @@
 SOFTWARE.
 """
 
-from ._encoding import DataEncoding
+from nni.experiment import Experiment
 
-encoders = {"DataEncoding": DataEncoding}
+from ..utils.hpSpace import search_space
+
+
+class HPOTrainer:
+
+    def __init__(self) -> None:
+        pass
diff --git a/InsurAutoML/_model/_CNN.py b/InsurAutoML/experimental/nn/models/CNN.py
similarity index 100%
rename from InsurAutoML/_model/_CNN.py
rename to InsurAutoML/experimental/nn/models/CNN.py
diff --git a/InsurAutoML/_model/_pretrained.py b/InsurAutoML/experimental/nn/models/pretrained.py
similarity index 97%
rename from InsurAutoML/_model/_pretrained.py
rename to InsurAutoML/experimental/nn/models/pretrained.py
index 8e73050..b5ab936 100644
--- a/InsurAutoML/_model/_pretrained.py
+++ b/InsurAutoML/experimental/nn/models/pretrained.py
@@ -1,17 +1,17 @@
 """
-File Name: _pretrained.py
+File Name: pretrained.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_pretrained.py
+Relative Path: /InsurAutoML/model/pretrained.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:15:26 pm
+Last Modified: Tuesday, 6th December 2022 11:29:57 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,7 +38,7 @@
 SOFTWARE.
 """
 
-from InsurAutoML._utils._preprocessing import text_preprocessing_transformers
+from InsurAutoML.preprocessing.txtProc import text_preprocessing_transformers
 import importlib
 import numpy as np
 import pandas as pd
diff --git a/InsurAutoML/experimental/nn/nas/__init__.py b/InsurAutoML/experimental/nn/nas/__init__.py
new file mode 100644
index 0000000..3838f77
--- /dev/null
+++ b/InsurAutoML/experimental/nn/nas/__init__.py
@@ -0,0 +1,45 @@
+"""
+File Name: __init__.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/nas/__init__.py
+File Created: Friday, 25th November 2022 11:22:57 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 28th November 2022 11:40:16 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from .trainer import NasTrainer
+
+__all__ = [
+    "NasTrainer",
+]
diff --git a/InsurAutoML/experimental/nn/nas/evaluator.py b/InsurAutoML/experimental/nn/nas/evaluator.py
new file mode 100644
index 0000000..09cbbf3
--- /dev/null
+++ b/InsurAutoML/experimental/nn/nas/evaluator.py
@@ -0,0 +1,228 @@
+"""
+File Name: evaluator.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/nas/evaluator.py
+File Created: Friday, 25th November 2022 11:10:17 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 5th December 2022 4:54:18 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from typing import Union, Callable, List, Tuple, Dict
+import importlib
+# from collections.abc import Iterable
+import nni
+import nni.retiarii.evaluator.pytorch.lightning as pl
+# from nni.retiarii.evaluator.pytorch import DataLoader
+import torch
+# from torch.utils.data import DataLoader
+from pytorch_lightning.loggers import TensorBoardLogger
+from pytorch_lightning.callbacks.early_stopping import EarlyStopping
+
+from InsurAutoML.utils.tensor import SerialTensorDataset
+from ..utils.args import repackage_hidden, get_optimizer, get_criterion, get_scheduler
+# , tensor_accuracy
+
+# serialized version of dataloader
+# DataLoader = nni.trace(DataLoader)
+
+
+@nni.trace
+class NASEvaluator(pl.LightningModule):
+
+    ACCURACY_TYPE = ["binary", "multiclass"]
+
+    def __init__(
+        self,
+        model: Union[str, Callable],
+        # type_of_task: str="multiclass",
+        batch_size: int = 32,
+        criterion: Union[str, Callable] = "CrossEntropyLoss",
+        optimizer: Union[str, Callable] = "Adam",
+        optimizer_lr: float = 1e-3,
+        lr_scheduler: Union[str, Callable] = "None",
+        use_gpu: bool = True,
+    ) -> None:
+        super().__init__()
+
+        # set parameters
+        self.model = model
+        # self.type_of_task = type_of_task
+        self.batch_size = batch_size
+        self.criterion = get_criterion(criterion)()
+        self.optimizer = get_optimizer(optimizer)
+        self.optimizer_lr = optimizer_lr
+        self.lr_scheduler = get_scheduler(lr_scheduler)
+
+        self._device = torch.device("cuda" if use_gpu else "cpu")
+
+    def forward(self, X: Union[torch.Tensor, List[torch.Tensor]]) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if self._hidden is not None:
+            # only get the values of hidden, no need for gradient
+            self._hidden = repackage_hidden(self._hidden)
+            output, self._hidden = self.model(X, self._hidden)
+        else:
+            output = self.model(X)
+
+        return output
+
+    def training_step(self, batch: Tuple[Union[torch.Tensor, List[torch.Tensor]], torch.Tensor], batch_idx: int) -> torch.Tensor:
+
+        input, label = batch  # parse the input batch
+        # forward phase
+        output = self(input)
+
+        loss = self.criterion(output, label)  # compute loss
+        self.log("train_loss", loss)  # Logging to TensorBoard by default
+
+        return loss
+
+    def validation_step(self, batch: Tuple[Union[torch.Tensor, List[torch.Tensor]], torch.Tensor], batch_idx: int) -> Dict[str, torch.Tensor]:
+
+        input, label = batch  # parse the input batch
+        output = self(input)
+        loss = self.criterion(output, label)  # compute loss
+        # # if classification job, use accuracy
+        # if self.type_of_task in self.ACCURACY_TYPE:
+        #     # Logging to TensorBoard by default
+        #     self.log("val_loss", tensor_accuracy(output, label))
+        # # for regression job, use -criterion to satisfy maximization setting
+        # else:
+        #     self.log("val_loss", -loss)  # Logging to TensorBoard by default
+        self.log("val_loss", -loss)
+
+        return {"- val_loss": -loss}
+
+    def configure_optimizers(self) -> Union[Callable, Dict[str, Callable]]:
+
+        # initialize optimizer
+        self.optimizer = self.optimizer(self.parameters(), self.optimizer_lr)
+
+        if self.lr_scheduler is None:
+            return self.optimizer
+        else:
+            return {
+                "optimizer": self.optimizer,
+                "lr_scheduler": self.lr_scheduler(self.optimizer),
+            }
+
+    def on_train_epoch_start(self) -> None:
+        # update train_epoch number
+        if not hasattr(self, '_train_epoch'):
+            self._train_epoch = self.current_epoch
+        else:
+            self._train_epoch += 1
+        # initialize hidden state for each epoch
+        self.init_hidden()
+
+    def on_validation_epoch_start(self) -> None:
+        # update valid_epoch number
+        if not hasattr(self, '_valid_epoch'):
+            self._valid_epoch = self.current_epoch
+        else:
+            self._valid_epoch = self.current_epoch
+        # initialize hidden state for each epoch
+        self.init_hidden()
+
+    def on_validation_epoch_end(self) -> None:
+        nni.report_intermediate_result(
+            self.trainer.callback_metrics["val_loss"].item())
+
+    def teardown(self, stage) -> None:
+        if stage == "fit":
+            nni.report_final_result(
+                self.trainer.callback_metrics["val_loss"].item())
+
+    def init_hidden(self) -> None:
+
+        if hasattr(self.model, 'init_hidden'):
+            self._hidden = self.model.init_hidden(self.batch_size)
+            # put hidden state on GPU if available and use_gpu is True
+            self._hidden = tuple([item.to(self._device) for item in self._hidden]) if isinstance(
+                self._hidden, (list, tuple)) else self._hidden.to(self._device)
+        else:
+            self._hidden = None
+
+
+# @nni.trace
+def get_evaluator(
+    model: Union[str, Callable],
+    train_set: SerialTensorDataset,
+    test_set: SerialTensorDataset,
+    batchSize: int = 32,
+    # type_of_task="multiclass",
+    criterion: Union[str, Callable] = "CrossEntropyLoss",
+    optimizer: Union[str, Callable] = "Adam",
+    optimizer_lr: float = 1e-3,
+    lr_scheduler: Union[str, Callable] = "None",
+    num_epochs: int = 10,
+    log_dir: str = "tmp/NAS",
+    use_gpu: bool = True,
+) -> NASEvaluator:
+    # setup pytorch_lightning logger
+    logger = TensorBoardLogger(log_dir, name="pl_logs")
+
+    accelerator_config = {"accelerator": "gpu", "devices": torch.cuda.device_count(
+    ), } if use_gpu else {"accelerator": "cpu"}
+
+    return pl.Lightning(
+        lightning_module=NASEvaluator(
+            model=model,
+            # type_of_task=type_of_task,
+            batch_size=batchSize,
+            criterion=criterion,
+            optimizer=optimizer,
+            optimizer_lr=optimizer_lr,
+            lr_scheduler=lr_scheduler,
+            use_gpu=use_gpu,
+        ),
+        trainer=pl.Trainer(
+            logger=logger,
+            # early stopping when loss is nan
+            callbacks=[EarlyStopping(
+                monitor="val_loss", mode="min", check_finite=True)],
+            max_epochs=num_epochs,
+            **accelerator_config,
+        ),
+        train_dataloader=pl.DataLoader(
+            train_set,
+            batch_size=batchSize,
+            shuffle=True,
+            drop_last=True,
+        ),
+        val_dataloaders=pl.DataLoader(
+            test_set,
+            batch_size=batchSize,
+            drop_last=True,
+        ),
+    )
diff --git a/InsurAutoML/experimental/nn/nas/trainer.py b/InsurAutoML/experimental/nn/nas/trainer.py
new file mode 100644
index 0000000..b18a71e
--- /dev/null
+++ b/InsurAutoML/experimental/nn/nas/trainer.py
@@ -0,0 +1,368 @@
+"""
+File Name: trainer.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/nas/trainer.py
+File Created: Monday, 24th October 2022 11:56:57 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 5th December 2022 5:44:27 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from __future__ import annotations
+
+from typing import Union, List, Dict, Any
+import logging
+import os
+import json
+import shutil
+import numpy as np
+import pandas as pd
+import torch
+from torch.utils.data import DataLoader
+import nni
+from nni.retiarii import fixed_arch
+from nni.retiarii.experiment.pytorch import RetiariiExperiment, RetiariiExeConfig
+
+from InsurAutoML.constant import NNI_VERBOSE
+from InsurAutoML.utils.tensor import SerialTensorDataset
+from InsurAutoML.utils import type_of_task, train_test_split
+from ..utils.args import get_strategy, get_search_space
+from .evaluator import get_evaluator
+
+DataLoader = nni.trace(DataLoader)  # serialized data loader
+
+logger = logging.getLogger(__name__)
+
+
+class NasTrainer:
+
+    def __init__(
+        self,
+        preprocessor=None,
+        search_space="MLPNet",
+        max_evals=10,
+        timeout=3600,
+        optimizer="Adam",
+        optimizer_lr=1e-3,
+        lr_scheduler="None",
+        criterion=None,
+        # evaluator="base",
+        search_strategy="Random",
+        batch_size=32,
+        num_epoch=10,
+        valid_perc=0.15,
+        task_name="NAS",
+        temp_directory="tmp",
+        use_gpu=True,
+        verbose=0,
+    ):
+        self.preprocessor = preprocessor
+        self.search_space = get_search_space(search_space)
+        self.max_evals = max_evals
+        self.timeout = timeout
+        self.optimizer = optimizer
+        self.optimizer_lr = optimizer_lr
+        self.lr_scheduler = lr_scheduler
+        self.criterion = criterion
+        self.optimizer = optimizer
+        # self.evaluator = evaluator
+        self.search_strategy = get_strategy(search_strategy)
+        self.batch_size = batch_size
+        self.num_epoch = num_epoch
+        self.valid_perc = valid_perc
+        self.task_name = task_name
+        self.temp_directory = temp_directory
+        self.use_gpu = use_gpu
+        self.verbose = verbose
+
+        # if folder existed, empty it
+        if os.path.isdir(os.path.join(self.temp_directory, self.task_name)):
+            shutil.rmtree(os.path.join(self.temp_directory, self.task_name))
+        # if folder not exist, create it
+        if not os.path.exists(
+                os.path.join(self.temp_directory, self.task_name)):
+            os.makedirs(os.path.join(self.temp_directory, self.task_name))
+
+        # check gpu availability
+        if self.use_gpu and not torch.cuda.is_available():
+            logger.warning("GPU is not available, use CPU instead.")
+            self.use_gpu = False
+
+        logger.info("NAS trainer initialized.")
+
+     # must use function to wrap the dataset for serialization
+    @staticmethod
+    def _prep_dataset(X, y):
+        # format to tensor
+        if not isinstance(
+                X, torch.Tensor) or not isinstance(
+                y, torch.Tensor):
+            # dataframe cannot be directly converted to tensor
+            if isinstance(X, pd.DataFrame) or isinstance(y, pd.DataFrame):
+                X = torch.tensor(X.values, dtype=torch.float32)
+                y = torch.tensor(y.values)
+            else:
+                X = torch.tensor(X, dtype=torch.float32)
+                y = torch.tensor(y)
+
+        return SerialTensorDataset(X, y)
+
+    @staticmethod
+    def _prep_loader(data, batch_size=32, mode="train"):
+
+        if mode == "train":
+            return DataLoader(
+                data, batch_size=batch_size, shuffle=True  # , drop_last=True
+            )
+        elif mode == "test":
+            return DataLoader(data, batch_size=batch_size)
+
+    @staticmethod
+    def _tensor_formatter(data: Any) -> torch.Tensor:
+
+        if isinstance(data, pd.DataFrame) or isinstance(data, pd.Series):
+            return torch.tensor(data.values)
+        elif isinstance(data, np.ndarray):
+            return torch.tensor(data)
+        elif isinstance(data, torch.Tensor):
+            return data
+        else:
+            raise TypeError(
+                "Unsupported data type for tensor formatter. Expected pd.DataFrame, np.ndarray, or torch.Tensor, but got %s." %
+                type(data))
+
+    def _data_formatter(self,
+                        data: Union[List,
+                                    Dict,
+                                    Any]) -> Union[List[torch.Tensor],
+                                                   Dict[str,
+                                                        torch.Tensor],
+                                                   torch.Tensor]:
+        if isinstance(data, list):
+            return [self._tensor_formatter(item) for item in data]
+        if isinstance(data, dict):
+            return {
+                key: self._tensor_formatter(value) for key,
+                value in data.items()}
+        else:
+            return self._tensor_formatter(data)
+
+    def _setup_data(self, train, valid):
+
+        # unpack data
+        if valid is not None:
+            (X_train, y_train), (X_valid, y_valid) = train, valid
+        # if valid not provided, split from train set
+        else:
+            X_train, y_train = train
+            X_train, X_valid, y_train, y_valid = train_test_split(
+                X_train, y_train, test_perc=self.valid_perc)
+        # get task type
+        task_type = type_of_task(y_train)
+
+        # get default loss function
+        if task_type in ["binary", "multiclass"] and self.criterion is None:
+            self.criterion = "CrossEntropyLoss"
+        elif task_type in ["integer", "continuous"] and self.criterion is None:
+            self.criterion = "MSELoss"
+        else:
+            raise ValueError("Unrecognized task type %s." % task_type)
+
+        # if preprocessor found, use it to preprocess data
+        if self.preprocessor is not None:
+            X_train = self.preprocessor.fit_transform(X_train)
+            X_valid = self.preprocessor.transform(X_valid)
+
+        # format data
+        X_train, y_train = self._data_formatter(
+            X_train), self._data_formatter(y_train)
+        X_valid, y_valid = self._data_formatter(
+            X_valid), self._data_formatter(y_valid)
+
+        return X_train, y_train, X_valid, y_valid, task_type
+
+    @staticmethod
+    def _get_sizes(X_train, y_train, inputSize, outputSize, task_type):
+        # get input and output Size
+        # if RNN net, need to use vocab size as inputSize
+        if isinstance(X_train, torch.Tensor):
+            inputSize = X_train.size(
+                dim=-1) if inputSize is None else inputSize
+        # format list of tensor to tensor by order
+        elif isinstance(X_train, list):
+            inputSize = {idx: _X_train.size(
+                dim=-1) for idx, _X_train in enumerate(X_train)} if inputSize is None else inputSize
+        elif isinstance(X_train, dict):
+            inputSize = {key: value.size(
+                dim=-1) for key, value in X_train.items()} if inputSize is None else inputSize
+
+        def _get_outputSize(y_train, task_type):
+
+            # get default loss function
+            # if regression, outputSize is 1
+            if task_type in ["binary", "multiclass"]:
+                return len(np.unique(y_train))
+            elif task_type in ["integer", "continuous"]:
+                return 1
+            else:
+                raise ValueError("Unrecognized task type %s." % task_type)
+
+        outputSize = _get_outputSize(
+            y_train, task_type) if outputSize is None else outputSize
+
+        return inputSize, outputSize
+
+    # def train(self, train, test, inputSize, outputSize):
+    def fit(self, train, valid=None, inputSize=None, outputSize=None, **kwargs):
+
+        # setup data
+        X_train, y_train, X_valid, y_valid, task_type = self._setup_data(
+            train, valid)
+
+        # get inputSize, outputSize and vocabSize
+        inputSize, outputSize = self._get_sizes(
+            X_train, y_train, inputSize, outputSize, task_type)
+        vocabSize = self.preprocessor.vocab_size if self.preprocessor is not None else None
+
+        # get dataset
+        if not os.path.exists(os.path.join(self.temp_directory, self.task_name, "train")):
+            os.makedirs(os.path.join(
+                self.temp_directory, self.task_name, "train"))
+        if not os.path.exists(os.path.join(self.temp_directory, self.task_name, "valid")):
+            os.makedirs(os.path.join(
+                self.temp_directory, self.task_name, "valid"))
+        trainset = SerialTensorDataset(X_train, y_train, path=os.path.join(
+            self.temp_directory, self.task_name, "train"))
+        validset = SerialTensorDataset(X_valid, y_valid, path=os.path.join(
+            self.temp_directory, self.task_name, "valid"))
+
+        # Update: Nov. 20, 2022
+        # Force using pytorch_lightning evaluator
+        # get evaluator
+        # # base form evaluator
+        # if self.evaluator == "base":
+
+        #     from ._evaluator import get_evaluator
+
+        #     self.evaluator = get_evaluator(
+        #         trainset=trainset,
+        #         testset=testset,
+        #         # trainset=_prep_loader(train),
+        #         # testset=_prep_loader(test),
+        #         batchSize=self.batch_size,
+        #         optimizer=self.optimizer,
+        #         criterion=self.criterion,
+        #         num_epoch=self.num_epoch,
+        #     )
+
+        # # pytorch-lightning form evaluator
+        # elif self.evaluator == "pl":
+        self.evaluator = get_evaluator(
+            model=self.search_space(inputSize, outputSize, vocabSize),
+            train_set=trainset,
+            test_set=validset,
+            # train_set=_prep_dataset(X_train, y_train),
+            # test_set=_prep_dataset(X_test, y_test),
+            batchSize=self.batch_size,
+            # type_of_task=task_type,
+            criterion=self.criterion,
+            optimizer=self.optimizer,
+            optimizer_lr=self.optimizer_lr,
+            lr_scheduler=self.lr_scheduler,
+            num_epochs=self.num_epoch,
+            log_dir=os.path.join(self.temp_directory, self.task_name),
+            use_gpu=self.use_gpu,
+        )
+
+        # setup experiment settings
+        exp = RetiariiExperiment(
+            self.search_space(inputSize, outputSize, vocabSize),
+            self.evaluator,
+            [],
+            self.search_strategy,
+        )
+        # setup experiment config
+        exp_config = RetiariiExeConfig("local")
+        # experiment name
+        exp_config.experiment_working_directory = os.path.join(
+            self.temp_directory, self.task_name)
+        exp_config.experiment_name = self.task_name
+        # exp_config.execution_engine = "oneshot"
+        # number of trials
+        exp_config.max_trial_number = self.max_evals
+        # time budget
+        exp_config.max_experiment_duration = self.timeout
+        exp_config.trial_concurrency = 1
+        if self.use_gpu:
+            exp_config.trial_gpu_number = torch.cuda.device_count()
+            exp_config.training_service.use_active_gpu = True
+        else:
+            exp_config.trial_gpu_number = 0
+            exp_config.training_service.use_active_gpu = False
+        # exp_config.experiment_working_directory = os.path.join(
+        #     self.temp_directory, self.task_name
+        # )
+        exp_config.log_level = NNI_VERBOSE[self.verbose]
+
+        logger.info("NAS training start...")
+
+        exp.run(exp_config)
+        exp.export_data()
+        exp.stop()
+
+        logger.info("NAS training end. Start to export best model...")
+
+        # save the best model architecture
+        # initialize the best model
+        for model_dict in exp.export_top_models(
+                top_k=1, optimize_mode='maximize', formatter="dict"):
+            # save the model dict
+            with open(os.path.join(self.temp_directory, self.task_name, "optimal_architecture.json"), "w",) as outfile:
+                json.dump(model_dict, outfile)
+            # build init model for HPO
+            init_model = self.build_model(inputSize, outputSize, vocabSize)
+            # save the model dict
+            torch.save(
+                init_model.state_dict(),
+                os.path.join(self.temp_directory,
+                             self.task_name, "init_optimal_model.pth"),
+            )
+
+        logger.info("NAS process finished.")
+
+    def build_model(self, inputSize, outputSize, vocabSize):
+
+        with fixed_arch(os.path.join(self.temp_directory, self.task_name, "optimal_architecture.json")):
+            net = self.search_space(inputSize, outputSize, vocabSize)
+
+        return net
diff --git a/InsurAutoML/experimental/nn/preprocessing/__init__.py b/InsurAutoML/experimental/nn/preprocessing/__init__.py
new file mode 100644
index 0000000..f6fe4ee
--- /dev/null
+++ b/InsurAutoML/experimental/nn/preprocessing/__init__.py
@@ -0,0 +1,55 @@
+"""
+File Name: __init__.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/preprocessing/__init__.py
+File Created: Tuesday, 6th December 2022 11:25:54 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Tuesday, 6th December 2022 11:30:55 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+try:
+    import transformers
+except:
+    transformers = None
+
+try:
+    import torchtext
+except:
+    torchtext = None
+
+from .multiProc import MultiPreprocessing
+if transformers is not None:
+    from .txtProc import text_preprocessing_transformers
+if torchtext is not None:
+    from .txtProc import text_preprocessing_torchtext
diff --git a/InsurAutoML/experimental/nn/preprocessing/multiProc.py b/InsurAutoML/experimental/nn/preprocessing/multiProc.py
new file mode 100644
index 0000000..1319a1f
--- /dev/null
+++ b/InsurAutoML/experimental/nn/preprocessing/multiProc.py
@@ -0,0 +1,278 @@
+"""
+File Name: multiProc.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/preprocessing/multiProc.py
+File Created: Wednesday, 16th November 2022 7:39:46 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Tuesday, 6th December 2022 11:33:08 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+
+from __future__ import annotations
+
+from typing import Dict, Union, Any
+import numpy as np
+import pandas as pd
+import torch
+import torch.nn.functional as F
+
+from InsurAutoML.utils import (
+    MetaData,
+    formatting,
+)
+from .utils import NoProc, TxtTokenize, CatOffsetEncoding, NumOffsetEncoding, PipProc
+
+"""
+List of methods:
+                    Txt           Cat           Con
+FusToken:      tokenize      encoding      encoding
+FusEmbed:     embedding     embedding     embedding
+FusModel:         model         model         model
+
+FusToken: Txt/Cat/Con are only tokenized to numerics and fuse those heads together for whole model training.
+FusEmbed: Txt/Cat/Con are further embedded with a few layers of NNs and fuse those embeddings together for later half of model training.
+FusModel: Txt/Cat/Con are almost trained separately and just fused together for unified prediction.
+"""
+
+
+class MultiPreprocessing(MetaData, formatting):
+
+    """
+    This class is used to preprocess data for multi-modal learning.
+
+    Can handle text, categorical, and numerical data.
+
+    Parameters
+    ----------
+    methods: methods used for preprocessing, default = "FusToken"
+    all methods: ["FusToken", "FusEmbed", "FusModel"]
+    """
+
+    _support_fulltype = [
+        ("Object", "Text"),
+        ("Int", "Numerical"),
+        ("Int", "Categorical"),
+        ("Float", ""),
+    ]
+
+    def __init__(
+        self,
+        method="FusToken",
+    ) -> None:
+        self.method = method
+
+        self._fitted = False
+
+    def _fusProc(
+        self,
+        X: Union[pd.DataFrame, np.ndarray],
+        **kwargs,
+    ):
+        starting_offset = 0  # initialize starting offset
+        pip_components = []  # initialize list of components
+
+        # Orders of preprocessing
+        # text -> cat -> con
+        # text:
+        if ("Object", "Text") in self.metadata.keys():
+            # get and fit text tokenizer
+            TxtProc = TxtTokenize(
+                starting_offset=0,
+                **kwargs,
+            )
+            TxtProc.fit(X[self.metadata[("Object", "Text")]])
+            # put into pipeline
+            pip_components += [(("Object", "Text"), TxtProc)]
+            # update starting offset
+            starting_offset += TxtProc._vocab_size
+        # cat:
+        # Object Categorical, convert to Int Categorical
+        if ("Object", "Categorical") in self.metadata.keys():
+            super(MetaData, self).__init__(
+                columns=self.metadata[("Object", "Categorical")]
+            )
+            super(MetaData, self).fit(X)
+
+            # update metadata
+            self.update(X, names=self.metadata[("Object", "Categorical")])
+        # Int Categorical
+        if ("Int", "Categorical") in self.metadata.keys():
+            CatProc = CatOffsetEncoding(
+                starting_offset=starting_offset,
+            )
+            CatProc.fit(X[self.metadata[("Int", "Categorical")]])
+            pip_components += [(("Int", "Categorical"), CatProc)]
+            starting_offset += CatProc._vocab_size
+        # con:
+        # Int Numerical
+        if ("Int", "Numerical") in self.metadata.keys():
+            NumProc = NumOffsetEncoding(
+                starting_offset=starting_offset,
+            )
+            NumProc.fit(X[self.metadata[("Int", "Numerical")]])
+            pip_components += [(("Int", "Numerical"), NumProc)]
+            starting_offset += NumProc._vocab_size
+        # Float
+        if ("Float", "") in self.metadata.keys():
+            NumProc = NumOffsetEncoding(
+                starting_offset=starting_offset,
+            )
+            NumProc.fit(X[self.metadata[("Float", "")]])
+            pip_components += [(("Float", ""), NumProc)]
+            starting_offset += NumProc._vocab_size
+
+        # for FusToken method, cat/con/txt all count for vocab_size
+        self.vocab_size = starting_offset + 1  # add 1 for padding
+
+        return PipProc(pip_components)
+
+    def _nofusProc(
+        self,
+        X: Union[pd.DataFrame, np.ndarray],
+        **kwargs,
+    ):
+        pip_components = []  # initialize list of components
+
+        # Orders of preprocessing
+        # text -> cat -> con
+        # text:
+        if ("Object", "Text") in self.metadata.keys():
+            TxtProc = TxtTokenize(
+                **kwargs,
+            )
+            TxtProc.fit(X[self.metadata[("Object", "Text")]])
+            pip_components += [(("Object", "Text"), TxtProc)]
+            # for non-FusToken methods, vocab_size only counts for text data
+            self.vocab_size = TxtProc._vocab_size
+        # cat:
+        # Object Categorical, convert to Int Categorical
+        if ("Object", "Categorical") in self.metadata.keys():
+            super(MetaData, self).__init__(
+                columns=self.metadata[("Object", "Categorical")]
+            )
+            super(MetaData, self).fit(X)
+
+            # update metadata
+            self.update(X, names=self.metadata[("Object", "Categorical")])
+        # Int Categorical
+        if ("Int", "Categorical") in self.metadata.keys():
+            CatProc = NoProc()
+            CatProc.fit(X[self.metadata[("Int", "Categorical")]])
+            pip_components += [(("Int", "Categorical"), CatProc)]
+        # con:
+        # Int Numerical
+        if ("Int", "Numerical") in self.metadata.keys():
+            NumProc = NoProc()
+            NumProc.fit(X[self.metadata[("Int", "Numerical")]])
+            pip_components += [(("Int", "Numerical"), NumProc)]
+        # Float
+        if ("Float", "") in self.metadata.keys():
+            NumProc = NoProc()
+            NumProc.fit(X[self.metadata[("Float", "")]])
+            pip_components += [(("Float", ""), NumProc)]
+
+        return PipProc(pip_components)
+
+    def fit(
+        self,
+        X: Union[pd.DataFrame, np.ndarray],
+        metadata: Dict[str, Any] = None,
+        **kwargs,
+    ) -> MultiPreprocessing:
+
+        # if metadata not provided, then generate it
+        if metadata is None:
+            super(MultiPreprocessing, self).__init__(X)
+        else:
+            self.metadata = metadata
+
+        # text:
+
+        self._pipe = (
+            self._fusProc(X, **kwargs)
+            if self.method == "FusToken"
+            else self._nofusProc(X, **kwargs)
+        )
+        self._pipe.fit()
+
+        self._fitted = True
+
+        return self
+
+    def transform(self,
+                  X: Union[pd.DataFrame,
+                           np.ndarray]) -> Dict[str,
+                                                torch.Tensor]:
+
+        if not self._fitted:
+            raise RuntimeError(
+                "The preprocessing is not fitted. Run fit() first.")
+
+        result = self._pipe.transform(X, self.metadata)
+
+        # update metadata
+        self.update(X)
+
+        # if FusToken, concat all sections to one tensor
+        if self.method == "FusToken":
+            return torch.cat(list(result.values()), dim=1)
+        # else, output separately
+        else:
+            return result
+
+    def fit_transform(
+        self, X: Union[pd.DataFrame, np.ndarray]
+    ) -> Union[torch.Tensor, Dict[str, torch.Tensor]]:
+
+        self.fit(X)
+        return self.transform(X)
+
+
+class FusTokenProc(MultiPreprocessing):
+    def __init__(self, **kwargs):
+        super(FusTokenProc, self).__init__(method="FusToken", **kwargs)
+
+
+class FusEmbedProc(MultiPreprocessing):
+    def __init__(self, **kwargs):
+        super(FusEmbedProc, self).__init__(method="FusEmbed", **kwargs)
+
+
+class FusModelProc(MultiPreprocessing):
+    def __init__(self, **kwargs):
+        super(FusModelProc, self).__init__(method="FusModel", **kwargs)
+
+
+if __name__ == "__main__":
+    pass
diff --git a/InsurAutoML/_utils/_preprocessing.py b/InsurAutoML/experimental/nn/preprocessing/txtProc.py
similarity index 98%
rename from InsurAutoML/_utils/_preprocessing.py
rename to InsurAutoML/experimental/nn/preprocessing/txtProc.py
index cddb722..6436baf 100644
--- a/InsurAutoML/_utils/_preprocessing.py
+++ b/InsurAutoML/experimental/nn/preprocessing/txtProc.py
@@ -1,17 +1,17 @@
 """
-File Name: _preprocessing.py
+File Name: txtProc.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_preprocessing.py
+Relative Path: /InsurAutoML/preprocessing/txtProc.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 9:40:22 pm
+Last Modified: Tuesday, 6th December 2022 11:30:31 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -49,6 +49,7 @@
 
 torchtext_spec = importlib.util.find_spec("torchtext")
 if torchtext_spec is not None:
+    import torchtext
     from torchtext.data.utils import get_tokenizer
     from torchtext.vocab import build_vocab_from_iterator
 
diff --git a/InsurAutoML/_utils/_nn/_multiProc.py b/InsurAutoML/experimental/nn/preprocessing/utils.py
similarity index 65%
rename from InsurAutoML/_utils/_nn/_multiProc.py
rename to InsurAutoML/experimental/nn/preprocessing/utils.py
index b20144b..441b78d 100644
--- a/InsurAutoML/_utils/_nn/_multiProc.py
+++ b/InsurAutoML/experimental/nn/preprocessing/utils.py
@@ -1,17 +1,17 @@
 """
-File Name: _multiProc.py
+File Name: utils.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_nn/_multiProc.py
-File Created: Saturday, 12th November 2022 4:41:07 pm
+Relative Path: /InsurAutoML/preprocessing/utils.py
+File Created: Tuesday, 6th December 2022 11:31:03 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:17:13 pm
+Last Modified: Tuesday, 6th December 2022 11:32:53 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -47,23 +47,7 @@
 import torch.nn.functional as F
 from transformers import AutoTokenizer
 
-from InsurAutoML._utils import (
-    MetaData,
-    formatting,
-)
-from InsurAutoML._constant import FULLTYPE_MAPPING
-
-"""
-List of methods:
-                    Txt           Cat           Con
-FusToken:      tokenize      encoding      encoding
-FusEmbed:     embedding     embedding     embedding
-FusModel:         model         model         model
-
-FusToken: Txt/Cat/Con are only tokenized to numerics and fuse those heads together for whole model training.
-FusEmbed: Txt/Cat/Con are further embedded with a few layers of NNs and fuse those embeddings together for later half of model training.
-FusModel: Txt/Cat/Con are almost trained separately and just fused together for unified prediction.
-"""
+from InsurAutoML.constant import FULLTYPE_MAPPING
 
 
 class NoProc:
@@ -98,13 +82,17 @@ def transform(
         # check data dimension
         if len(data.size()) != 2:
             raise TypeError(
-                "Data must be a 2D tensor. Got {}D tensor.".format(len(data.size()))
+                "Data must be a 2D tensor. Got {}D tensor.".format(
+                    len(data.size()))
             )
 
         return data
 
 
 class TxtTokenize:
+
+    """Tokenizer for text data."""
+
     def __init__(
         self,
         model_name: str = "bert-base-uncased",
@@ -215,7 +203,8 @@ def fit(
         # check data dimension
         if len(data.size()) != 2:
             raise TypeError(
-                "Data must be a 2D tensor. Got {}D tensor.".format(len(data.size()))
+                "Data must be a 2D tensor. Got {}D tensor.".format(
+                    len(data.size()))
             )
 
         # if unique_classes not passed, get unique classes from data
@@ -259,7 +248,8 @@ def transform(
         # check data dimension
         if len(data.size()) != 2:
             raise TypeError(
-                "Data must be a 2D tensor. Got {}D tensor.".format(len(data.size()))
+                "Data must be a 2D tensor. Got {}D tensor.".format(
+                    len(data.size()))
             )
 
         return data + self.cat_offset
@@ -300,7 +290,8 @@ def fit(
         # check data dimension
         if len(data.size()) != 2:
             raise TypeError(
-                "Data must be a 2D tensor. Got {}D tensor.".format(len(data.size()))
+                "Data must be a 2D tensor. Got {}D tensor.".format(
+                    len(data.size()))
             )
 
         # convert numerical data into categorical data
@@ -338,7 +329,8 @@ def transform(
         # check data dimension
         if len(data.size()) != 2:
             raise TypeError(
-                "Data must be a 2D tensor. Got {}D tensor.".format(len(data.size()))
+                "Data must be a 2D tensor. Got {}D tensor.".format(
+                    len(data.size()))
             )
 
         # unpack range
@@ -351,6 +343,9 @@ def transform(
 
 
 class PipProc:
+
+    """Pipeline for processing data."""
+
     def __init__(self,
                  components: List[Tuple[Tuple,
                                         Union[TxtTokenize,
@@ -410,174 +405,3 @@ def transform(
 
         # concatenate the results
         return result
-
-
-class MultiPreprocessing(MetaData, formatting):
-
-    _support_fulltype = [
-        ("Object", "Text"),
-        ("Int", "Numerical"),
-        ("Int", "Categorical"),
-        ("Float", ""),
-    ]
-
-    def __init__(
-        self,
-        method="FusToken",
-    ) -> None:
-        self.method = method
-
-        self._fitted = False
-
-    def _fusProc(
-        self,
-        X: Union[pd.DataFrame, np.ndarray],
-        **kwargs,
-    ):
-        starting_offset = 0  # initialize starting offset
-        pip_components = []  # initialize list of components
-
-        # Orders of preprocessing
-        # text -> cat -> con
-        # text:
-        if ("Object", "Text") in self.metadata.keys():
-            TxtProc = TxtTokenize(
-                starting_offset=0,
-                **kwargs,
-            )
-            TxtProc.fit(X[self.metadata[("Object", "Text")]])
-            pip_components += [(("Object", "Text"), TxtProc)]
-            starting_offset += TxtProc._vocab_size
-        # cat:
-        # Object Categorical, convert to Int Categorical
-        if ("Object", "Categorical") in self.metadata.keys():
-            super(MetaData, self).__init__(
-                columns=self.metadata[("Object", "Categorical")]
-            )
-            super(MetaData, self).fit(X)
-
-            # update metadata
-            self.update(X, names=self.metadata[("Object", "Categorical")])
-        # Int Categorical
-        if ("Int", "Categorical") in self.metadata.keys():
-            CatProc = CatOffsetEncoding(
-                starting_offset=starting_offset,
-            )
-            CatProc.fit(X[self.metadata[("Int", "Categorical")]])
-            pip_components += [(("Int", "Categorical"), CatProc)]
-            starting_offset += CatProc._vocab_size
-        # con:
-        # Int Numerical
-        if ("Int", "Numerical") in self.metadata.keys():
-            NumProc = NumOffsetEncoding(
-                starting_offset=starting_offset,
-            )
-            NumProc.fit(X[self.metadata[("Int", "Numerical")]])
-            pip_components += [(("Int", "Numerical"), NumProc)]
-        # Float
-        if ("Float", "") in self.metadata.keys():
-            NumProc = NumOffsetEncoding(
-                starting_offset=starting_offset,
-            )
-            NumProc.fit(X[self.metadata[("Float", "")]])
-            pip_components += [(("Float", ""), NumProc)]
-
-        return PipProc(pip_components)
-
-    def _nofusProc(
-        self,
-        X: Union[pd.DataFrame, np.ndarray],
-        **kwargs,
-    ):
-        pip_components = []  # initialize list of components
-
-        # Orders of preprocessing
-        # text -> cat -> con
-        # text:
-        if ("Object", "Text") in self.metadata.keys():
-            TxtProc = TxtTokenize(
-                **kwargs,
-            )
-            TxtProc.fit(X[self.metadata[("Object", "Text")]])
-            pip_components += [(("Object", "Text"), TxtProc)]
-        # cat:
-        # Object Categorical, convert to Int Categorical
-        if ("Object", "Categorical") in self.metadata.keys():
-            super(MetaData, self).__init__(
-                columns=self.metadata[("Object", "Categorical")]
-            )
-            super(MetaData, self).fit(X)
-
-            # update metadata
-            self.update(X, names=self.metadata[("Object", "Categorical")])
-        # Int Categorical
-        if ("Int", "Categorical") in self.metadata.keys():
-            CatProc = NoProc()
-            CatProc.fit(X[self.metadata[("Int", "Categorical")]])
-            pip_components += [(("Int", "Categorical"), CatProc)]
-        # con:
-        # Int Numerical
-        if ("Int", "Numerical") in self.metadata.keys():
-            NumProc = NoProc()
-            NumProc.fit(X[self.metadata[("Int", "Numerical")]])
-            pip_components += [(("Int", "Numerical"), NumProc)]
-        # Float
-        if ("Float", "") in self.metadata.keys():
-            NumProc = NoProc()
-            NumProc.fit(X[self.metadata[("Float", "")]])
-            pip_components += [(("Float", ""), NumProc)]
-
-        return PipProc(pip_components)
-
-    def fit(
-        self,
-        X: Union[pd.DataFrame, np.ndarray],
-        metadata: Dict[str, Any] = None,
-        **kwargs,
-    ) -> MultiPreprocessing:
-
-        # if metadata not provided, then generate it
-        if metadata is None:
-            super(MultiPreprocessing, self).__init__(X)
-        else:
-            self.metadata = metadata
-
-        # text:
-
-        self._pipe = (
-            self._fusProc(X, **kwargs)
-            if self.method == "FusToken"
-            else self._nofusProc(X, **kwargs)
-        )
-        self._pipe.fit()
-
-        self._fitted = True
-
-        return self
-
-    def transform(self,
-                  X: Union[pd.DataFrame,
-                           np.ndarray]) -> Dict[str,
-                                                torch.Tensor]:
-
-        if not self._fitted:
-            raise RuntimeError(
-                "The preprocessing is not fitted. Run fit() first.")
-
-        result = self._pipe.transform(X, self.metadata)
-
-        # update metadata
-        self.update(X)
-
-        return result
-
-    def fit_transform(
-        self, X: Union[pd.DataFrame, np.ndarray]
-    ) -> Dict[str, torch.Tensor]:
-
-        self.fit(X)
-        return self.transform(X)
-
-
-if __name__ == "__main__":
-    pass
diff --git a/InsurAutoML/experimental/nn/trainer.py b/InsurAutoML/experimental/nn/trainer.py
new file mode 100644
index 0000000..e3ee983
--- /dev/null
+++ b/InsurAutoML/experimental/nn/trainer.py
@@ -0,0 +1,124 @@
+"""
+File Name: trainer.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/trainer.py
+File Created: Saturday, 26th November 2022 12:26:39 am
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 5th December 2022 5:44:20 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+import os
+import shutil
+import logging
+
+from .nas import NasTrainer
+from .utils.args import get_strategy, get_search_space
+
+logger = logging.getLogger(__name__)
+
+
+class AutoMultiModalTabular(NasTrainer):
+
+    def __init__(
+        self,
+        preprocessor=None,
+        search_space="MLPNet",
+        max_evals=10,
+        timeout=3600 * 24,
+        optimizer="Adam",
+        optimizer_lr=1e-3,
+        lr_scheduler="None",
+        criterion=None,
+        # evaluator="base",
+        search_strategy="Random",
+        batch_size=32,
+        num_epoch=10,
+        valid_perc=0.15,
+        task_name="MultiModal",
+        temp_directory="tmp",
+        verbose=0,
+        use_gpu=True,
+    ):
+        self.preprocessor = preprocessor
+        self.search_space = get_search_space(search_space)
+        self.max_evals = max_evals
+        self.timeout = timeout
+        self.optimizer = optimizer
+        self.optimizer_lr = optimizer_lr
+        self.lr_scheduler = lr_scheduler
+        self.criterion = criterion
+        self.optimizer = optimizer
+        # self.evaluator = evaluator
+        self.search_strategy = get_strategy(search_strategy)
+        self.batch_size = batch_size
+        self.num_epoch = num_epoch
+        self.valid_perc = valid_perc
+        self.task_name = task_name
+        self.temp_directory = temp_directory
+        self.verbose = verbose
+        self.use_gpu = use_gpu
+
+        # if folder existed, empty it
+        if os.path.isdir(os.path.join(self.temp_directory, self.task_name)):
+            shutil.rmtree(os.path.join(self.temp_directory, self.task_name))
+        # if folder not exist, create it
+        if not os.path.exists(os.path.join(self.temp_directory, self.task_name)):
+            os.makedirs(os.path.join(self.temp_directory, self.task_name))
+
+        # initiate the nas trainer
+        super(AutoMultiModalTabular, self).__init__(
+            preprocessor=self.preprocessor,
+            search_space=self.search_space,
+            max_evals=self.max_evals,
+            timeout=self.timeout,
+            optimizer=self.optimizer,
+            optimizer_lr=self.optimizer,
+            lr_scheduler=self.lr_scheduler,
+            criterion=self.criterion,
+            # evaluator="base",
+            search_strategy=self.search_strategy,
+            batch_size=self.batch_size,
+            num_epoch=self.num_epoch,
+            valid_perc=self.valid_perc,
+            task_name=os.path.join(self.task_name, "NAS"),
+            temp_directory=self.temp_directory,
+            verbose=self.verbose,
+            use_gpu=self.use_gpu,
+        )
+
+    def fit(self, train, valid=None, inputSize=None, outputSize=None, **kwargs):
+
+        # NAS training
+        super(AutoMultiModalTabular, self).fit(
+            train, valid, inputSize, outputSize, **kwargs)
diff --git a/InsurAutoML/experimental/nn/utils/__init__.py b/InsurAutoML/experimental/nn/utils/__init__.py
new file mode 100644
index 0000000..a1a1a39
--- /dev/null
+++ b/InsurAutoML/experimental/nn/utils/__init__.py
@@ -0,0 +1,52 @@
+"""
+File Name: __init__.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/utils/__init__.py
+File Created: Friday, 25th November 2022 11:21:39 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 28th November 2022 11:40:43 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from .net import MLPNet, LiteMLPNet, RNNet, LiteRNNet, FusTokenNet, FusEmbedNet, FusModelNet
+
+
+__all__ = [
+    "MLPNet",
+    "LiteMLPNet",
+    "RNNet",
+    "LiteRNNet",
+    "FusTokenNet",
+    "FusEmbedNet",
+    "FusModelNet"
+]
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_utils.py b/InsurAutoML/experimental/nn/utils/args.py
similarity index 68%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_utils.py
rename to InsurAutoML/experimental/nn/utils/args.py
index edeb96e..0c20842 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_utils.py
+++ b/InsurAutoML/experimental/nn/utils/args.py
@@ -1,17 +1,17 @@
 """
-File Name: _utils.py
+File Name: args.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_utils.py
-File Created: Monday, 24th October 2022 11:56:57 pm
+Relative Path: /InsurAutoML/nn/utils/args.py
+File Created: Friday, 25th November 2022 11:11:35 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:27:04 pm
+Last Modified: Monday, 5th December 2022 2:58:09 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,20 +38,30 @@
 SOFTWARE.
 """
 
-
 from collections import OrderedDict
 from typing import Callable
 import torch
 import torch.nn as nn
 import torch.optim as optim
+import numpy as np
+from sklearn.metrics import accuracy_score
 
 import nni.retiarii.strategy as strategy
 import nni.retiarii.nn.pytorch as nninn
 
+import InsurAutoML.nn
+
+
+def repackage_hidden(h):
+    if isinstance(h, torch.Tensor):
+        return h.detach()
+    else:
+        return tuple(repackage_hidden(v) for v in h)
 
 ##########################################################################
 # Constant
 
+
 ACTIVATIONS = OrderedDict(
     [
         ("ReLU", nninn.ReLU()),
@@ -102,37 +112,59 @@ def how_to_init(m, how="uniform"):
 
 def get_search_space(search_space):
 
-    if search_space == "MLP":
-        from ._baseSpace import MLPBaseSpace
+    # if search_space == "MLP":
+    #     from InsurAutoML._nn._utils._net import MLPNet
 
-        return MLPBaseSpace
-    elif isinstance(search_space, Callable):
-        return search_space
+    #     return MLPNet
+    # elif search_space == "RNN":
+    #     from InsurAutoML._nn._utils._net import RNNet
+
+    #     return RNNet
+    # elif search_space == "LiteMLP":
+    #     from InsurAutoML._nn._utils._net import LiteMLPNet
+
+    #     return LiteMLPNet
+    # elif isinstance(search_space, Callable):
+    #     return search_space
+
+    try:
+        return getattr(InsurAutoML.nn, search_space)
+    except:
+        if isinstance(search_space, Callable):
+            return search_space
+        else:
+            raise ValueError(
+                "Unrecognized search space {}".format(search_space))
 
 
 def get_optimizer(optimizer):
 
     # get optimizer
-    if optimizer == "SGD":
-        optimizer = optim.SGD
-    elif optimizer == "Adam":
-        optimizer = optim.Adam
-    elif optimizer == "Adagrad":
-        optimizer = optim.Adagrad
-    elif optimizer == "LBFGS":
-        optimizer = optim.LBFGS
-    elif optimizer == "RMSprop":
-        optimizer = optim.RMSprop
-    elif optimizer == "Rprop":
-        optimizer = optim.Rprop
-    elif isinstance(optimizer, Callable):
-        pass
-    else:
-        raise ValueError(
-            "optimizer must be one of SGD, Adam, Adagrad, LBFGS, RMSprop, Rprop, get {}".format(
-                optimizer
+    # if optimizer == "SGD":
+    #     optimizer = optim.SGD
+    # elif optimizer == "Adam":
+    #     optimizer = optim.Adam
+    # elif optimizer == "Adagrad":
+    #     optimizer = optim.Adagrad
+    # elif optimizer == "LBFGS":
+    #     optimizer = optim.LBFGS
+    # elif optimizer == "RMSprop":
+    #     optimizer = optim.RMSprop
+    # elif optimizer == "Rprop":
+    #     optimizer = optim.Rprop
+    # elif isinstance(optimizer, Callable):
+    #     pass
+    try:
+        optimizer = getattr(optim, optimizer)
+    except:
+        if isinstance(optimizer, Callable):
+            pass
+        else:
+            raise ValueError(
+                "optimizer must be one of SGD, Adam, Adagrad, LBFGS, RMSprop, Rprop, get {}".format(
+                    optimizer
+                )
             )
-        )
     return optimizer
 
 
@@ -189,9 +221,9 @@ def get_criterion(criterion):
 def get_strategy(strategyStr):
 
     if strategyStr == "Random":
-        return strategy.Random()
+        return strategy.Random(dedup=True)
     elif strategyStr == "Grid":
-        return strategy.GridSearch()
+        return strategy.GridSearch(shuffle=True)
     elif strategyStr == "Evolution":
         return strategy.RegularizedEvolution()
     elif strategyStr == "TPE":
@@ -202,3 +234,18 @@ def get_strategy(strategyStr):
         return strategyStr()
     else:
         raise ValueError("Unrecognized strategy {}".format(strategyStr))
+
+# accuracy for model prediction
+
+
+def tensor_accuracy(y_pred, y_true):
+    if isinstance(y_pred, torch.Tensor):
+        y_pred = y_pred.detach().cpu().numpy()
+
+    if isinstance(y_true, torch.Tensor):
+        y_true = y_true.detach().cpu().numpy()
+
+    y_pred = [np.argmax(item) for item in y_pred]
+    # y_true = [np.argmax(item) for item in y_true]
+
+    return accuracy_score(y_pred, y_true)
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/__init__.py b/InsurAutoML/experimental/nn/utils/baseSpace/__init__.py
similarity index 86%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/__init__.py
rename to InsurAutoML/experimental/nn/utils/baseSpace/__init__.py
index 8332db7..a580620 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/__init__.py
+++ b/InsurAutoML/experimental/nn/utils/baseSpace/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/__init__.py
+Relative Path: /InsurAutoML/nn/utils/baseSpace/__init__.py
 File Created: Saturday, 12th November 2022 11:15:34 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:02 pm
+Last Modified: Monday, 28th November 2022 11:29:34 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,4 +38,5 @@
 SOFTWARE.
 """
 
-from ._wholeSpace import MLPBaseSpace, RNNBaseSpace
+from .wholeSpace import MLPBaseSpace, MLPLintSpace, RNNBaseSpace, RNNLintSpace
+from .head import MLPHead, RNNHead
diff --git a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_wholeSpace.py b/InsurAutoML/experimental/nn/utils/baseSpace/head.py
similarity index 50%
rename from InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_wholeSpace.py
rename to InsurAutoML/experimental/nn/utils/baseSpace/head.py
index 9d2c78b..e8678d5 100644
--- a/InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_wholeSpace.py
+++ b/InsurAutoML/experimental/nn/utils/baseSpace/head.py
@@ -1,17 +1,17 @@
 """
-File Name: _wholeSpace.py
+File Name: head.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_experimental/_nn/_nni/_nas/_baseSpace/_wholeSpace.py
-File Created: Monday, 24th October 2022 11:56:57 pm
+Relative Path: /InsurAutoML/nn/utils/baseSpace/head.py
+File Created: Sunday, 13th November 2022 5:15:00 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:26:26 pm
+Last Modified: Tuesday, 29th November 2022 3:55:50 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,102 +38,53 @@
 SOFTWARE.
 """
 
+import torch
 import nni
 import nni.retiarii.nn.pytorch as nninn
-from nni.retiarii import model_wrapper
+from nni.retiarii import model_wrapper, fixed_arch
 
-from .._utils import ACTIVATIONS, RNN_TYPES, how_to_init
+from ..args import ACTIVATIONS, RNN_TYPES, how_to_init
 
+# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
-##########################################################################
-# MLP Space
-@model_wrapper
-class MLPBaseSpace(nninn.Module):
-    def __init__(
-        self,
-        inputSize,
-        outputSize,
-    ):
-        super().__init__()
-
-        # initialize the structure of the neural network
-        self.net = []
-
-        # add input layer
-        self.net.append(
-            nninn.Linear(
-                inputSize,
-                nninn.ValueChoice(
-                    [32, 64, 128, 256, 512], label="hiddenSize_at_layer_0"
-                ),
-            )
-        )
-
-        # add hidden layers
-        # each hidden layer has a linear layer, a activation layer and possibly
-        # a dropout layer
-        self.net.append(
-            nninn.Repeat(
-                lambda index: nninn.Sequential(
-                    nninn.Linear(
-                        nninn.ValueChoice(
-                            [32, 64, 128, 256, 512],
-                            label=f"hiddenSize_at_layer_{index}",
-                        ),
-                        nninn.ValueChoice(
-                            [32, 64, 128, 256, 512],
-                            label=f"hiddenSize_at_layer_{index + 1}",
-                        ),
-                    ),
-                    nninn.LayerChoice(
-                        ACTIVATIONS, label=f"activation_at_layer_{index}"
-                    ),
-                    nninn.Dropout(
-                        nninn.ValueChoice(
-                            [0.0, 0.2, 0.5, 0.9], label=f"p_dropout_at_layer_{index}"
-                        )
-                    ),
-                ),
-                nninn.ValueChoice([1, 2, 3, 4, 5, 6], label="num_hidden_layers"),
-                label="hidden_layers",
-            )
-        )
-
-        # add output layer
-        # use lazy linear to automatically initialize the input size
-        self.net.append(nninn.LazyLinear(outputSize))
-
-        # add softmax layer
-        self.net.append(nninn.Softmax())
-
-        self.net = nninn.Sequential(*self.net)
-
-    def forward(self, X):
-
-        return self.net(X)
-
-    def init_weight(self, how="xavier_normal"):
+"""
+List of methods:
+                    Txt           Cat           Con
+FusToken:      tokenize      encoding      encoding
+FusEmbed:     embedding     embedding     embedding
+FusModel:         model         model         model
+
+FusToken: Txt/Cat/Con are only tokenized to numerics and fuse those heads together for whole model training.
+FusEmbed: Txt/Cat/Con are further embedded with a few layers of NNs and fuse those embeddings together for later half of model training.
+FusModel: Txt/Cat/Con are almost trained separately and just fused together for unified prediction.
+"""
 
-        for m in self.modules():
-            if isinstance(m, nninn.Linear):
-                how_to_init(m, how)
+"""
+List of classes need for multimodal neural architecture search.
+1. entire network space (whole vs lint): use ones in _wholeSpace.py
+2. heads (before fuse)
+3. prediction (after fuse for FusModel)
+"""
 
 
-@model_wrapper
-class MLPLintSpace(nninn.Module):
+# @model_wrapper
+class MLPHead(nninn.Module):
     def __init__(
         self,
         inputSize,
         outputSize,
+        vocabSize: int = 0,
+        prefix="",
     ):
         super().__init__()
-
+        self.inputSize = inputSize
+        self.outputSize = outputSize
         # initialize the structure of the neural network
         self.net = []
 
         # add input layer
         self.net.append(nninn.Linear(inputSize, nninn.ValueChoice(
-            [4, 8, 16, 32, 64], label="hiddenSize_at_layer_0"), ))
+            [4, 8, 16, 32, 64], label=f"{prefix}hiddenSize_at_layer_0"), ))
 
         # add hidden layers
         # each hidden layer has a linear layer, a activation layer and possibly
@@ -144,24 +95,25 @@ def __init__(
                     nninn.Linear(
                         nninn.ValueChoice(
                             [4, 8, 16, 32, 64],
-                            label=f"hiddenSize_at_layer_{index}",
+                            label=f"{prefix}hiddenSize_at_layer_{index}",
                         ),
                         nninn.ValueChoice(
                             [4, 8, 16, 32, 64],
-                            label=f"hiddenSize_at_layer_{index + 1}",
+                            label=f"{prefix}hiddenSize_at_layer_{index + 1}",
                         ),
                     ),
                     nninn.LayerChoice(
-                        ACTIVATIONS, label=f"activation_at_layer_{index}"
+                        ACTIVATIONS, label=f"{prefix}activation_at_layer_{index}"
                     ),
                     nninn.Dropout(
                         nninn.ValueChoice(
-                            [0.0, 0.2, 0.5, 0.9], label=f"p_dropout_at_layer_{index}"
+                            [0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout_at_layer_{index}"
                         )
                     ),
                 ),
-                nninn.ValueChoice([1, 2], label="num_hidden_layers"),
-                label="hidden_layers",
+                nninn.ValueChoice(
+                    [1, 2, 3], label=f"{prefix}num_hidden_layers"),
+                label=f"{prefix}hidden_layers",
             )
         )
 
@@ -176,6 +128,9 @@ def __init__(
 
     def forward(self, X):
 
+        # make sure input to embedding layer is float
+        X = X.to(torch.float32)
+
         return self.net(X)
 
     def init_weight(self, how="xavier_normal"):
@@ -184,18 +139,20 @@ def init_weight(self, how="xavier_normal"):
             if isinstance(m, nninn.Linear):
                 how_to_init(m, how)
 
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
 
-##########################################################################
-# RNN Space
+        with fixed_arch(config_path):
+            return MLPHead(inputSize, outputSize)
 
 
-@nni.trace
-@model_wrapper
-class RNNBaseSpace(nninn.Module):
+class RNNHead(nninn.Module):
     def __init__(
         self,
         inputSize,
         outputSize,
+        vocabSize: int,
+        prefix="",
     ):
         super().__init__()
 
@@ -205,38 +162,35 @@ def __init__(
         self.postnet = []
 
         # embedding layer
-        self.prenet.append(nninn.Embedding(inputSize, nninn.ValueChoice(
-            [128, 256, 512, 1024], label="embedding_size"), ))
+        self.prenet.append(nninn.Embedding(vocabSize, nninn.ValueChoice(
+            [32, 64, 128, 256], label=f"{prefix}embedding_size"), ))
 
         # RNN parameters
         num_RNN_layers = nninn.ValueChoice(
-            [
-                1,
-                2,
-                3,
-            ],
-            label="num_RNN_layers",
+            [1, 2],
+            label=f"{prefix}num_RNN_layers",
         )
-        bidirectional = nninn.ValueChoice([True, False], label="bidirectional")
-        p_dropout = nninn.ValueChoice([0.0, 0.2, 0.5, 0.9], label="p_dropout")
+        self._bidirectional = nninn.ValueChoice(
+            [True, False], label=f"{prefix}bidirectional")
+        p_dropout = nninn.ValueChoice(
+            [0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout")
         # RNN layer
-        self.RNNnet.append(
-            nninn.LayerChoice(
-                [
-                    RNNstructure(
-                        input_size=nninn.ValueChoice(
-                            [128, 256, 512, 1024], label="embedding_size"
-                        ),
-                        hidden_size=nninn.ValueChoice(
-                            [64, 128, 256, 512], label="hidden_size"
-                        ),
-                        num_layers=num_RNN_layers,
-                        dropout=p_dropout,
-                        bidirectional=bidirectional,
-                    )
-                    for RNNstructure in RNN_TYPES.values()
-                ]
-            )
+        self.RNNnet = nninn.LayerChoice(
+            [
+                RNNstructure(
+                    input_size=nninn.ValueChoice(
+                        [32, 64, 128, 256], label=f"{prefix}embedding_size"
+                    ),
+                    hidden_size=nninn.ValueChoice(
+                        [16, 32, 64, 128], label=f"{prefix}hidden_size"
+                    ),
+                    num_layers=num_RNN_layers,
+                    dropout=p_dropout,
+                    bidirectional=self._bidirectional,
+                    batch_first=True,
+                )
+                for RNNstructure in RNN_TYPES.values()
+            ], label=f"{prefix}RNN_type"
         )
 
         # linear layer
@@ -245,22 +199,23 @@ def __init__(
                 lambda index: nninn.Sequential(
                     nninn.LazyLinear(
                         nninn.ValueChoice(
-                            [32, 64, 128, 256, 512],
-                            label=f"size_at_dense_layer_{index}",
+                            [16, 32, 64, 128],
+                            label=f"{prefix}size_at_dense_layer_{index}",
                         )
                     ),
                     nninn.LayerChoice(
-                        ACTIVATIONS, label=f"activation_at_dense_layer_{index}"
+                        ACTIVATIONS, label=f"{prefix}activation_at_dense_layer_{index}"
                     ),
                     nninn.Dropout(
                         nninn.ValueChoice(
                             [0.0, 0.2, 0.5, 0.9],
-                            label=f"p_dropout_at_dense_layer_{index}",
+                            label=f"{prefix}p_dropout_at_dense_layer_{index}",
                         )
                     ),
                 ),
-                nninn.ValueChoice([0, 1, 2], label="num_dense_layers"),
-                label="dense_layers",
+                nninn.ValueChoice(
+                    [0, 1, 2], label=f"{prefix}num_dense_layers"),
+                label=f"{prefix}dense_layers",
             )
         )
 
@@ -270,19 +225,28 @@ def __init__(
         self.postnet.append(nninn.Softmax())
 
         self.prenet = nninn.Sequential(*self.prenet)
-        self.RNNnet = nninn.Sequential(*self.RNNnet)
+        # self.RNNnet = nninn.Sequential(*self.RNNnet)
         self.postnet = nninn.Sequential(*self.postnet)
 
     def forward(self, input, hidden):
 
+        if isinstance(input, list):
+            if len(input) > 1:
+                raise ValueError(
+                    "Expect one tensor, get {} tensors".format(len(input)))
+            input = input[0]
+
+        # make sure input to embedding layer is long
+        input = input.long()
+
         # embedding layer
         output = self.prenet(input)
         # RNN layer
         output, hidden = self.RNNnet(output, hidden)
         # post layer
-        output = self.postnet(output)
+        output = self.postnet(output)[:, -1, :]
 
-        return output
+        return output, hidden
 
     def init_weight(self, how="xavier_normal"):
 
@@ -296,3 +260,25 @@ def init_weight(self, how="xavier_normal"):
                 or isinstance(m, nninn.GRU)
             ):
                 how_to_init(m, how)
+
+    def init_hidden(self, batch_size):
+        num_directions = 2 if self._bidirectional else 1
+        if isinstance(self.RNNnet, nninn.LSTM):
+            return (
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                ),
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                )
+            )
+        else:
+            return torch.zeros(
+                self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+            )
+
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
+
+        with fixed_arch(config_path):
+            return RNNHead(inputSize, outputSize)
diff --git a/InsurAutoML/experimental/nn/utils/baseSpace/wholeSpace.py b/InsurAutoML/experimental/nn/utils/baseSpace/wholeSpace.py
new file mode 100644
index 0000000..1049e8e
--- /dev/null
+++ b/InsurAutoML/experimental/nn/utils/baseSpace/wholeSpace.py
@@ -0,0 +1,475 @@
+"""
+File Name: wholeSpace.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/utils/baseSpace/wholeSpace.py
+File Created: Monday, 24th October 2022 11:56:57 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Tuesday, 29th November 2022 3:56:19 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+import torch
+import nni
+import nni.retiarii.nn.pytorch as nninn
+from nni.retiarii import model_wrapper, fixed_arch
+
+from ..args import ACTIVATIONS, RNN_TYPES, how_to_init
+# from .._buildModel import build_mlp
+
+# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+##########################################################################
+# MLP Space
+
+
+# @model_wrapper
+class MLPBaseSpace(nninn.Module):
+    def __init__(
+        self,
+        inputSize,
+        outputSize,
+        vocabSize=0,
+        prefix="",
+    ):
+        super().__init__()
+
+        # initialize the structure of the neural network
+        self.net = []
+
+        # add input layer
+        self.net.append(
+            nninn.Linear(
+                inputSize,
+                nninn.ValueChoice(
+                    [32, 64, 128, 256, 512], label=f"{prefix}hiddenSize_at_layer_0"
+                ),
+            )
+        )
+
+        # add hidden layers
+        # each hidden layer has a linear layer, a activation layer and possibly
+        # a dropout layer
+        self.net.append(
+            nninn.Repeat(
+                lambda index: nninn.Sequential(
+                    nninn.Linear(nninn.ValueChoice([32, 64, 128, 256, 512], label=f"{prefix}hiddenSize_at_layer_{index}",), nninn.ValueChoice(
+                        [32, 64, 128, 256, 512], label=f"{prefix}hiddenSize_at_layer_{index + 1}",),),
+                    nninn.LayerChoice(
+                        ACTIVATIONS, label=f"{prefix}activation_at_layer_{index}"),
+                    nninn.Dropout(nninn.ValueChoice([0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout_at_layer_{index}")),),
+                nninn.ValueChoice([1, 2, 3, 4, 5, 6], label=f"{prefix}num_hidden_layers"), label=f"{prefix}hidden_layers",)
+        )
+
+        # add output layer
+        # use lazy linear to automatically initialize the input size
+        self.net.append(nninn.LazyLinear(outputSize))
+
+        # add softmax layer
+        self.net.append(nninn.Softmax())
+
+        self.net = nninn.Sequential(*self.net)
+
+    def forward(self, X):
+
+        if isinstance(X, list):
+            if len(X) > 1:
+                raise ValueError(
+                    "Expect one tensor, get {} tensors".format(len(X)))
+            X = X[0]
+
+        # make sure input to embedding layer is float
+        X = X.to(torch.float32)
+
+        return self.net(X)
+
+    def init_weight(self, how="xavier_normal"):
+
+        for m in self.modules():
+            if isinstance(m, nninn.Linear):
+                how_to_init(m, how)
+
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
+
+        with fixed_arch(config_path):
+            return MLPBaseSpace(inputSize, outputSize)
+
+
+# @model_wrapper
+class MLPLintSpace(nninn.Module):
+    def __init__(
+        self,
+        inputSize,
+        outputSize,
+        vocabSize=0,
+        prefix="",
+    ):
+        super().__init__()
+
+        # initialize the structure of the neural network
+        self.net = []
+
+        # add input layer
+        self.net.append(nninn.Linear(inputSize, nninn.ValueChoice(
+            [4, 8, 16, 32, 64], label=f"{prefix}hiddenSize_at_layer_0"), ))
+
+        # add hidden layers
+        # each hidden layer has a linear layer, a activation layer and possibly
+        # a dropout layer
+        self.net.append(
+            nninn.Repeat(
+                lambda index: nninn.Sequential(
+                    nninn.Linear(nninn.ValueChoice([4, 8, 16, 32, 64], label=f"{prefix}hiddenSize_at_layer_{index}",),
+                                 nninn.ValueChoice([4, 8, 16, 32, 64], label=f"{prefix}hiddenSize_at_layer_{index + 1}",),),
+                    nninn.LayerChoice(
+                        ACTIVATIONS, label=f"{prefix}activation_at_layer_{index}"),
+                    nninn.Dropout(nninn.ValueChoice([0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout_at_layer_{index}")),),
+                nninn.ValueChoice([1, 2, 3], label=f"{prefix}num_hidden_layers"), label=f"{prefix}hidden_layers",)
+        )
+
+        # add output layer
+        # use lazy linear to automatically initialize the input size
+        self.net.append(nninn.LazyLinear(outputSize))
+
+        # add softmax layer
+        self.net.append(nninn.Softmax())
+
+        self.net = nninn.Sequential(*self.net)
+
+    def forward(self, X):
+
+        if isinstance(X, list):
+            if len(X) > 1:
+                raise ValueError(
+                    "Expect one tensor, get {} tensors".format(len(X)))
+            X = X[0]
+
+        # make sure input to embedding layer is float
+        X = X.to(torch.float32)
+
+        return self.net(X)
+
+    def init_weight(self, how="xavier_normal"):
+
+        for m in self.modules():
+            if isinstance(m, nninn.Linear):
+                how_to_init(m, how)
+
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
+
+        with fixed_arch(config_path):
+            return MLPLintSpace(inputSize, outputSize)
+
+
+##########################################################################
+# RNN Space
+
+
+# @nni.trace
+# @model_wrapper
+class RNNBaseSpace(nninn.Module):
+    def __init__(
+        self,
+        inputSize,
+        outputSize,
+        vocabSize,
+        prefix="",
+    ):
+        super().__init__()
+
+        # initialize the structure of the neural network
+        self.prenet = []
+        self.RNNnet = []
+        self.postnet = []
+
+        # embedding layer
+        self.prenet.append(nninn.Embedding(vocabSize, nninn.ValueChoice(
+            [128, 256, 512, 1024], label=f"{prefix}embedding_size"), ))
+
+        # RNN parameters
+        num_RNN_layers = nninn.ValueChoice(
+            [1, 2, 3, 4, 5, 6],
+            label=f"{prefix}num_RNN_layers",
+        )
+        self._bidirectional = nninn.ValueChoice(
+            [True, False], label=f"{prefix}bidirectional")
+        p_dropout = nninn.ValueChoice(
+            [0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout")
+        # RNN layer
+        self.RNNnet = nninn.LayerChoice(
+            [
+                RNNstructure(
+                    input_size=nninn.ValueChoice(
+                        [128, 256, 512, 1024], label=f"{prefix}embedding_size"
+                    ),
+                    hidden_size=nninn.ValueChoice(
+                        [64, 128, 256, 512], label=f"{prefix}hidden_size"
+                    ),
+                    num_layers=num_RNN_layers,
+                    dropout=p_dropout,
+                    bidirectional=self._bidirectional,
+                    batch_first=True,
+                )
+                for RNNstructure in RNN_TYPES.values()
+            ], label=f"{prefix}RNN_type"
+        )
+
+        # linear layer
+        self.postnet.append(
+            nninn.Repeat(
+                lambda index: nninn.Sequential(
+                    nninn.LazyLinear(
+                        nninn.ValueChoice(
+                            [32, 64, 128, 256, 512],
+                            label=f"{prefix}size_at_dense_layer_{index}",
+                        )
+                    ),
+                    nninn.LayerChoice(
+                        ACTIVATIONS, label=f"{prefix}activation_at_dense_layer_{index}"
+                    ),
+                    nninn.Dropout(
+                        nninn.ValueChoice(
+                            [0.0, 0.2, 0.5, 0.9],
+                            label=f"{prefix}p_dropout_at_dense_layer_{index}",
+                        )
+                    ),
+                ),
+                nninn.ValueChoice(
+                    [2, 3, 4, 5, 6], label=f"{prefix}num_dense_layers"),
+                label=f"{prefix}dense_layers",
+            )
+        )
+
+        # output layer
+        self.postnet.append(nninn.LazyLinear(outputSize))
+        # softmax layer
+        self.postnet.append(nninn.Softmax())
+
+        self.prenet = nninn.Sequential(*self.prenet)
+        # self.RNNnet = nninn.Sequential(*self.RNNnet)
+        self.postnet = nninn.Sequential(*self.postnet)
+
+    def forward(self, input, hidden):
+
+        if isinstance(input, list):
+            if len(input) > 1:
+                raise ValueError(
+                    "Expect one tensor, get {} tensors".format(len(input)))
+            input = input[0]
+
+        # make sure input to embedding layer is long
+        input = input.long()
+
+        # embedding layer
+        output = self.prenet(input)
+        # RNN layer
+        output, hidden = self.RNNnet(output, hidden)
+        # post layer
+        output = self.postnet(output)[:, -1, :]
+
+        return output, hidden
+
+    def init_weight(self, how="xavier_normal"):
+
+        for m in self.modules():
+            if (
+                isinstance(m, nninn.Linear)
+                or isinstance(m, nninn.Embedding)
+                or isinstance(m, nninn.LazyLinear)
+                or isinstance(m, nninn.RNN)
+                or isinstance(m, nninn.LSTM)
+                or isinstance(m, nninn.GRU)
+            ):
+                how_to_init(m, how)
+
+    def init_hidden(self, batch_size):
+        num_directions = 2 if self._bidirectional else 1
+        if isinstance(self.RNNnet, nninn.LSTM):
+            return (
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                ),
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                )
+            )
+        else:
+            return torch.zeros(
+                self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+            )
+
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
+
+        with fixed_arch(config_path):
+            return RNNBaseSpace(inputSize, outputSize)
+
+
+class RNNLintSpace(nninn.Module):
+    def __init__(
+        self,
+        inputSize,
+        outputSize,
+        vocabSize,
+        prefix="",
+    ):
+        super().__init__()
+
+        # initialize the structure of the neural network
+        self.prenet = []
+        self.RNNnet = []
+        self.postnet = []
+
+        # embedding layer
+        self.prenet.append(nninn.Embedding(vocabSize, nninn.ValueChoice(
+            [32, 64, 128, 256], label=f"{prefix}embedding_size"), ))
+
+        # RNN parameters
+        num_RNN_layers = nninn.ValueChoice(
+            [1, 2],
+            label=f"{prefix}num_RNN_layers",
+        )
+        self._bidirectional = nninn.ValueChoice(
+            [True, False], label=f"{prefix}bidirectional")
+        p_dropout = nninn.ValueChoice(
+            [0.0, 0.2, 0.5, 0.9], label=f"{prefix}p_dropout")
+        # RNN layer
+        self.RNNnet = nninn.LayerChoice(
+            [
+                RNNstructure(
+                    input_size=nninn.ValueChoice(
+                        [32, 64, 128, 256], label=f"{prefix}embedding_size"
+                    ),
+                    hidden_size=nninn.ValueChoice(
+                        [16, 32, 64, 128], label=f"{prefix}hidden_size"
+                    ),
+                    num_layers=num_RNN_layers,
+                    dropout=p_dropout,
+                    bidirectional=self._bidirectional,
+                    batch_first=True,
+                )
+                for RNNstructure in RNN_TYPES.values()
+            ], label=f"{prefix}RNN_type"
+        )
+
+        # linear layer
+        self.postnet.append(
+            nninn.Repeat(
+                lambda index: nninn.Sequential(
+                    nninn.Linear(
+                        (self._bidirectional + 1) * nninn.ValueChoice([16, 32, 64, 128], label=f"{prefix}hidden_size") if index == 0 else nninn.ValueChoice(
+                            [16, 32, 64, 128], label=f"{prefix}size_at_dense_layer_{index - 1}",),
+                        nninn.ValueChoice(
+                            [16, 32, 64, 128],
+                            label=f"{prefix}size_at_dense_layer_{index}",
+                        )
+                    ),
+                    nninn.LayerChoice(
+                        ACTIVATIONS, label=f"{prefix}activation_at_dense_layer_{index}"
+                    ),
+                    nninn.Dropout(
+                        nninn.ValueChoice(
+                            [0.0, 0.2, 0.5, 0.9],
+                            label=f"{prefix}p_dropout_at_dense_layer_{index}",
+                        )
+                    ),
+                ),
+                nninn.ValueChoice([1, 2], label=f"{prefix}num_dense_layers"),
+                label=f"{prefix}dense_layers",
+            )
+        )
+
+        # output layer
+        self.postnet.append(nninn.LazyLinear(outputSize))
+        # softmax layer
+        self.postnet.append(nninn.Softmax())
+
+        self.prenet = nninn.Sequential(*self.prenet)
+        # self.RNNnet = nninn.Sequential(*self.RNNnet)
+        self.postnet = nninn.Sequential(*self.postnet)
+
+    def forward(self, input, hidden):
+
+        if isinstance(input, list):
+            if len(input) > 1:
+                raise ValueError(
+                    "Expect one tensor, get {} tensors".format(len(input)))
+            input = input[0]
+
+        # make sure input to embedding layer is long
+        input = input.long()
+
+        # embedding layer
+        output = self.prenet(input)
+        # RNN layer
+        output, hidden = self.RNNnet(output, hidden)
+        # post layer
+        output = self.postnet(output)[:, -1, :]
+
+        return output, hidden
+
+    def init_weight(self, how="xavier_normal"):
+
+        for m in self.modules():
+            if (
+                isinstance(m, nninn.Linear)
+                or isinstance(m, nninn.Embedding)
+                or isinstance(m, nninn.LazyLinear)
+                or isinstance(m, nninn.RNN)
+                or isinstance(m, nninn.LSTM)
+                or isinstance(m, nninn.GRU)
+            ):
+                how_to_init(m, how)
+
+    def init_hidden(self, batch_size):
+        num_directions = 2 if self._bidirectional else 1
+        if isinstance(self.RNNnet, nninn.LSTM):
+            return (
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                ),
+                torch.zeros(
+                    self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+                )
+            )
+        else:
+            return torch.zeros(
+                self.RNNnet.num_layers * num_directions, batch_size, self.RNNnet.hidden_size,
+            )
+
+    @staticmethod
+    def build_model(config_path, inputSize, outputSize):
+
+        with fixed_arch(config_path):
+            return RNNLintSpace(inputSize, outputSize)
diff --git a/InsurAutoML/experimental/nn/utils/components.py b/InsurAutoML/experimental/nn/utils/components.py
new file mode 100644
index 0000000..07ec9cc
--- /dev/null
+++ b/InsurAutoML/experimental/nn/utils/components.py
@@ -0,0 +1,184 @@
+"""
+File Name: components.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/utils/components.py
+File Created: Tuesday, 22nd November 2022 4:42:24 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 28th November 2022 11:40:25 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from typing import Union, List, Tuple
+import torch
+import nni.retiarii.nn.pytorch as nninn
+# from nni.retiarii import model_wrapper
+
+from .baseSpace import MLPBaseSpace, MLPHead, RNNHead, RNNBaseSpace, RNNLintSpace
+
+"""
+List of methods:
+                    Txt           Cat           Con
+FusToken:      tokenize      encoding      encoding
+FusEmbed:     embedding     embedding     embedding
+FusModel:         model         model         model
+
+FusToken: Txt/Cat/Con are only tokenized to numerics and fuse those heads together for whole model training.
+FusEmbed: Txt/Cat/Con are further embedded with a few layers of NNs and fuse those embeddings together for later half of model training.
+FusModel: Txt/Cat/Con are almost trained separately and just fused together for unified prediction.
+"""
+
+# Update: Nov. 22, 2022
+# only apply model_wrapper to highest level of the model
+
+############################################################################
+# Fusion Heads
+
+
+class TxtHead(nninn.Module):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int,
+        prefix="txt_",
+    ) -> None:
+        super().__init__()
+        self._outputSize = outputSize
+
+        self.net = nninn.LayerChoice(
+            [MLPHead(inputSize, outputSize, prefix=prefix + "mlp_"),
+             RNNHead(inputSize, outputSize, vocabSize, prefix=prefix + "rnn_")
+             ], label="txt_head",
+        )
+
+    @property
+    def outputSize(self) -> int:
+        return self._outputSize
+
+    def forward(self, input: Union[torch.Tensor, List[torch.Tensor]], hidden: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]] = None) -> Tuple[torch.Tensor, Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]]:
+
+        if hidden is not None:
+            return self.net(input, hidden)
+        else:
+            return self.net(input), None
+
+    def init_hidden(self, batchSize: int) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if hasattr(self.net, "init_hidden"):
+            return self.net.init_hidden(batchSize)
+        else:
+            return None
+
+
+class CatHead(MLPHead):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        prefix="cat_",
+    ) -> None:
+        super(CatHead, self).__init__(inputSize, outputSize, prefix=prefix)
+
+
+class ConHead(MLPHead):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        prefix="con_",
+    ) -> None:
+        super(ConHead, self).__init__(inputSize, outputSize, prefix=prefix)
+
+############################################################################
+# Fusion nets
+
+
+class TxtNet(nninn.Module):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: Union[int, nninn.InputChoice],
+        vocabSize: int,
+        prefix="txt_",
+    ) -> None:
+        super().__init__()
+        self._outputSize = outputSize
+
+        self.net = nninn.LayerChoice(
+            [MLPBaseSpace(inputSize, outputSize, prefix=prefix + "mlp_"),
+             RNNBaseSpace(inputSize, outputSize, vocabSize,
+                          prefix=prefix + "rnn_"),
+             ], label="txt_net",
+        )
+
+    @property
+    def outputSize(self) -> int:
+        return self._outputSize
+
+    def forward(self, input: Union[torch.Tensor, List[torch.Tensor]], hidden: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]] = None) -> Tuple[torch.Tensor, Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]]:
+
+        if hidden is not None:
+            return self.net(input, hidden)
+        else:
+            return self.net(input), None
+
+    def init_hidden(self, batchSize: int) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if hasattr(self.net, "init_hidden"):
+            return self.net.init_hidden(batchSize)
+        else:
+            return None
+
+
+class CatNet(MLPBaseSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: Union[int, nninn.InputChoice],
+        prefix="cat_",
+    ) -> None:
+        super(CatNet, self).__init__(inputSize, outputSize, prefix=prefix)
+
+
+class ConNet(MLPBaseSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: Union[int, nninn.InputChoice],
+        prefix="con_",
+    ) -> None:
+        super(ConNet, self).__init__(inputSize, outputSize, prefix=prefix)
diff --git a/InsurAutoML/_utils/_nn/__init__.py b/InsurAutoML/experimental/nn/utils/hpSpace/__init__.py
similarity index 87%
rename from InsurAutoML/_utils/_nn/__init__.py
rename to InsurAutoML/experimental/nn/utils/hpSpace/__init__.py
index 8fff641..4272581 100644
--- a/InsurAutoML/_utils/_nn/__init__.py
+++ b/InsurAutoML/experimental/nn/utils/hpSpace/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_nn/__init__.py
-File Created: Saturday, 12th November 2022 4:40:51 pm
+Relative Path: /InsurAutoML/nn/utils/hpSpace/__init__.py
+File Created: Friday, 25th November 2022 11:43:20 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:17:08 pm
+Last Modified: Monday, 28th November 2022 11:40:28 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,4 +38,4 @@
 SOFTWARE.
 """
 
-from ._multiProc import MultiPreprocessing
+from .space import search_space
diff --git a/InsurAutoML/_balancing/_legacy.py b/InsurAutoML/experimental/nn/utils/hpSpace/space.py
similarity index 57%
rename from InsurAutoML/_balancing/_legacy.py
rename to InsurAutoML/experimental/nn/utils/hpSpace/space.py
index d24bec8..c15160f 100644
--- a/InsurAutoML/_balancing/_legacy.py
+++ b/InsurAutoML/experimental/nn/utils/hpSpace/space.py
@@ -1,17 +1,17 @@
 """
-File Name: _legacy.py
+File Name: space.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_balancing/_legacy.py
-File Created: Monday, 24th October 2022 11:56:57 pm
+Relative Path: /InsurAutoML/nn/utils/hpSpace/space.py
+File Created: Friday, 25th November 2022 11:43:31 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 6:57:54 pm
+Last Modified: Monday, 5th December 2022 4:56:51 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,28 +38,16 @@
 SOFTWARE.
 """
 
-from ._over_sampling import SimpleRandomOverSampling, Smote
-from ._under_sampling import (
-    SimpleRandomUnderSampling,
-    TomekLink,
-    EditedNearestNeighbor,
-    CondensedNearestNeighbor,
-    OneSidedSelection,
-    CNN_TomekLink,
-)
-from ._mixed_sampling import Smote_TomekLink, Smote_ENN
-from InsurAutoML._base import no_processing
-
-balancings = {
-    "no_processing": no_processing,
-    "SimpleRandomOverSampling": SimpleRandomOverSampling,
-    "SimpleRandomUnderSampling": SimpleRandomUnderSampling,
-    "TomekLink": TomekLink,
-    "EditedNearestNeighbor": EditedNearestNeighbor,
-    "CondensedNearestNeighbor": CondensedNearestNeighbor,
-    "OneSidedSelection": OneSidedSelection,
-    "CNN_TomekLink": CNN_TomekLink,
-    "Smote": Smote,
-    "Smote_TomekLink": Smote_TomekLink,
-    "Smote_ENN": Smote_ENN,
+search_space = {
+    "num_epochs": {"_type": "randint", "_value": [10, 100]},
+    "batch_size": {"_type": "choice", "_value": [8, 16, 32, 64, 128, 256, 512]},
+    "optimizer": {"_type": "choice", "_value": ["Adagrad", "Adam", "ASGD", "RMSprop", "SGD"]},
+    "lr": {"_type": "loguniform", "_value": [1e-8, 0.1]},
+    "weight_decay ": {"_type": "loguniform", "_value": [1e-7, 0.1]},
+    "lr_scheduler": {"_type": "choice", "_value": ["None", "StepLR", "ConstantLR", "LinearLR", "ExponentialLR", "PolynomialLR", "ReduceLROnPlateau"]},
+    "step_size": {"_type": "qloguniform", "_value": [2, 100, 1]},
+    "gamma": {"_type": "uniform", "_value": [0.1, 0.99]},
+    "factor": {"_type": "uniform", "_value": [0.1, 0.99]},
+    "total_iters": {"_type": "qloguniform", "_value": [2, 100, 1]},
+    "power": {"_type": "qrandint", "_value": [1, 5]},
 }
diff --git a/InsurAutoML/experimental/nn/utils/net.py b/InsurAutoML/experimental/nn/utils/net.py
new file mode 100644
index 0000000..a9c5c6c
--- /dev/null
+++ b/InsurAutoML/experimental/nn/utils/net.py
@@ -0,0 +1,255 @@
+"""
+File Name: net.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/nn/utils/net.py
+File Created: Saturday, 19th November 2022 10:03:31 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Monday, 28th November 2022 11:40:21 pm
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from typing import List, Dict, Type, Union, Tuple
+import torch
+import nni.retiarii.nn.pytorch as nninn
+from nni.retiarii import model_wrapper
+# from nni.nas.utils import basic_unit
+
+from .baseSpace import MLPBaseSpace, MLPLintSpace, MLPHead, RNNBaseSpace, RNNLintSpace
+from .components import TxtHead, CatHead, ConHead, TxtNet, CatNet, ConNet
+
+# MLPHead = typing.cast(Type[MLPHead], basic_unit(MLPHead))
+# MLPBaseSpace = typing.cast(Type[MLPBaseSpace], basic_unit(MLPBaseSpace))
+
+############################################################################
+# Non-Fusion Models
+
+
+@model_wrapper
+class MLPNet(MLPBaseSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int = 0,
+    ) -> None:
+        super().__init__(inputSize, outputSize, vocabSize)
+
+
+@model_wrapper
+class LiteMLPNet(MLPLintSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int = 0,
+    ) -> None:
+        super().__init__(inputSize, outputSize, vocabSize)
+
+
+@model_wrapper
+class RNNet(RNNBaseSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int,
+    ) -> None:
+        super().__init__(inputSize, outputSize, vocabSize)
+
+
+@model_wrapper
+class LiteRNNet(RNNLintSpace):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int,
+    ) -> None:
+        super().__init__(inputSize, outputSize, vocabSize)
+
+############################################################################
+# Fusion Models
+
+
+@model_wrapper
+class FusTokenNet(nninn.Module):
+
+    def __init__(
+        self,
+        inputSize: int,
+        outputSize: int,
+        vocabSize: int,
+    ) -> None:
+        super().__init__()
+        self.net = nninn.LayerChoice(
+            [MLPBaseSpace(inputSize, outputSize, prefix="MLP_base_"),
+             MLPLintSpace(inputSize, outputSize, prefix="MLP_lint_"),
+             RNNBaseSpace(inputSize, outputSize,
+                          vocabSize, prefix="RNN_base_"),
+             RNNLintSpace(inputSize, outputSize, vocabSize, prefix="RNN_lint_")
+             ],
+            label="FusTokenNet"
+        )
+
+    def forward(self, input: Union[torch.Tensor, List[torch.Tensor]], hidden: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]] = None) -> Tuple[torch.Tensor, Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]]:
+
+        if hidden is not None:
+            return self.net(input, hidden)
+        else:
+            return self.net(input)
+
+    def init_hidden(self, batchSize: int) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if hasattr(self.net, "init_hidden"):
+            return self.net.init_hidden(batchSize)
+        else:
+            return None
+
+
+@model_wrapper
+class FusEmbedNet(nninn.Module):
+
+    def __init__(
+        self,
+        inputSize: Dict[str, int],
+        outputSize: int,
+        vocabSize: int,
+    ) -> None:
+        super().__init__()
+        self.embeds = []
+        # embedSize = [nninn.ValueChoice(
+        #     [4, 8, 16, 32, 64], label=f"embedSize_{idx}") for idx in range(len(inputSize))]
+
+        # for idx, _inputSize in enumerate(list(inputSize.values())):
+        #     self.embeds.append(MLPHead(_inputSize, embedSize[idx], prefix=f"head_{idx}_"))
+        # self.model = MLPHead(
+        #     sum(embedSize), outputSize, prefix="model_")
+        # Update: Nov.22, 2022
+        # nni can't differentiate same module initialized with different parameters
+        # so, need to wrap with different classes manually
+        if "txt" in inputSize.keys():
+            self.textnet = TxtHead(inputSize["txt"], nninn.ValueChoice(
+                [4, 8, 16, 32, 64], label="txt_embedSize"), vocabSize)
+            self.embeds.append(self.textnet)
+        if "cat" in inputSize.keys():
+            self.catnet = CatHead(inputSize["cat"], nninn.ValueChoice(
+                [4, 8, 16, 32, 64], label="cat_embedSize"))
+            self.embeds.append(self.catnet)
+        if "con" in inputSize.keys():
+            self.connet = ConHead(inputSize["con"], nninn.ValueChoice(
+                [4, 8, 16, 32, 64], label="con_embedSize"))
+            self.embeds.append(self.connet)
+        self.model = MLPHead(
+            sum([embed.outputSize for embed in self.embeds]), outputSize, prefix="model_")
+
+    def forward(self, input: List[torch.Tensor], hidden: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]] = None) -> Tuple[torch.Tensor, Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]]:
+
+        # put everything through the embedding heads
+        embedded = []
+        for index, embed in enumerate(self.embeds):
+            if isinstance(embed, TxtHead):
+                _embed, hidden = embed(input[index], hidden)
+            else:
+                _embed = embed(input[index])
+            embedded.append(_embed)
+
+        # concatenate the embeddings
+        embedded = torch.cat(embedded, dim=-1)
+        # put the concatenated embeddings through the model
+        result = self.model(embedded)
+
+        # for TxtHead with MLP, hidden is None, return None will create return stacking error
+        if hidden is not None:
+            return result, hidden
+        else:
+            return result
+
+    def init_hidden(self, batchSize: int) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if hasattr(self, "textnet"):
+            return self.textnet.init_hidden(batchSize)
+        else:
+            return None
+
+
+@ model_wrapper
+class FusModelNet(nninn.Module):
+
+    def __init__(
+        self,
+        inputSize: Dict[str, int],
+        outputSize: int,
+        vocabSize: int,
+    ) -> None:
+        super().__init__()
+        self.models = []
+        if "txt" in inputSize.keys():
+            self.textnet = TxtNet(inputSize["txt"], outputSize, vocabSize)
+            self.models.append(self.textnet)
+        if "cat" in inputSize.keys():
+            self.catnet = CatNet(inputSize["cat"], outputSize)
+            self.models.append(self.catnet)
+        if "con" in inputSize.keys():
+            self.connet = ConNet(inputSize["con"], outputSize)
+            self.models.append(self.connet)
+
+        # self.models = [MLPBaseSpace(_inputSize, outputSize, prefix="model{}_".format(index))
+        #                for index, _inputSize in enumerate(inputSize)]
+
+    def forward(self, input: List[torch.Tensor], hidden: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]] = None) -> Tuple[torch.Tensor, Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]]:
+
+        # put everything through the embedding heads
+        output = []
+        for index, model in enumerate(self.models):
+            if isinstance(model, TxtNet):
+                _output, hidden = model(input[index], hidden)
+            else:
+                _output = model(input[index])
+            output.append(_output)
+
+        # sum the outputs
+        result = torch.sum(torch.stack(output), dim=0)
+
+        # for TxtNet with MLP, hidden is None, return None will create return stacking error
+        if hidden is not None:
+            return result, hidden
+        else:
+            return result
+
+    def init_hidden(self, batchSize: int) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
+        if hasattr(self, "textnet"):
+            return self.textnet.init_hidden(batchSize)
+        else:
+            return None
diff --git a/InsurAutoML/_feature_selection/__init__.py b/InsurAutoML/feature_selection/__init__.py
similarity index 80%
rename from InsurAutoML/_feature_selection/__init__.py
rename to InsurAutoML/feature_selection/__init__.py
index 37c2f0a..92107c5 100644
--- a/InsurAutoML/_feature_selection/__init__.py
+++ b/InsurAutoML/feature_selection/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/__init__.py
+Relative Path: /InsurAutoML/feature_selection/__init__.py
 File: __init__.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 4:06:09 pm
+Last Modified: Monday, 28th November 2022 11:36:44 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,6 +38,39 @@
 SOFTWARE.
 """
 
+from InsurAutoML.base import no_processing
+from .filter import (
+    FeatureFilter,
+    mRMR,
+    # FOCI,
+)
+from .wrapper import (
+    # ExhaustiveFS,
+    SFS,
+    ASFFS,
+)
+from .embed import (
+    PCA_FeatureSelection,
+    # LDASelection,
+    RBFSampler,
+)
+from .hybrid import (
+    CBFS,
+    GeneticAlgorithm,
+)
+
+from .sklearn import (
+    extra_trees_preproc_for_classification,
+    extra_trees_preproc_for_regression,
+    liblinear_svc_preprocessor,
+    polynomial,
+    select_percentile_classification,
+    select_percentile_regression,
+    select_rates_classification,
+    select_rates_regression,
+    truncatedSVD,
+)
+
 """
 Note: Aug. 8, 2022
 The feature selection methods are arranged more systematically, which uses the categories defined
@@ -52,8 +85,6 @@
 [1] Chandrashekar, G., & Sahin, F. (2014). A survey on feature selection methods. Computers & Electrical Engineering, 40(1), 16-28.
 """
 
-
-from InsurAutoML._base import no_processing
 feature_selections = {
     "no_processing": no_processing,
     # "LDASelection": LDASelection,
@@ -129,3 +160,24 @@
 feature_selections["select_rates_classification"] = select_rates_classification
 feature_selections["select_rates_regression"] = select_rates_regression
 feature_selections["truncatedSVD"] = truncatedSVD
+
+
+__all__ = [
+    "no_processing",
+    "FeatureFilter",
+    "mRMR",
+    "SFS",
+    "ASFFS",
+    "RBFSampler",
+    "CBFS",
+    "GeneticAlgorithm",
+    "extra_trees_preproc_for_classification",
+    "extra_trees_preproc_for_regression",
+    "liblinear_svc_preprocessor",
+    "polynomial",
+    "select_percentile_classification",
+    "select_percentile_regression",
+    "select_rates_classification",
+    "select_rates_regression",
+    "truncatedSVD",
+]
diff --git a/InsurAutoML/_feature_selection/_autosklearn.py b/InsurAutoML/feature_selection/autosklearn.py
similarity index 100%
rename from InsurAutoML/_feature_selection/_autosklearn.py
rename to InsurAutoML/feature_selection/autosklearn.py
diff --git a/InsurAutoML/_feature_selection/_embed.py b/InsurAutoML/feature_selection/embed.py
similarity index 100%
rename from InsurAutoML/_feature_selection/_embed.py
rename to InsurAutoML/feature_selection/embed.py
diff --git a/InsurAutoML/_feature_selection/_filter.py b/InsurAutoML/feature_selection/filter.py
similarity index 98%
rename from InsurAutoML/_feature_selection/_filter.py
rename to InsurAutoML/feature_selection/filter.py
index 871f277..be8fbf6 100644
--- a/InsurAutoML/_feature_selection/_filter.py
+++ b/InsurAutoML/feature_selection/filter.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/_filter.py
+Relative Path: /InsurAutoML/feature_selection/_filter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:01:24 pm
+Last Modified: Monday, 28th November 2022 11:19:28 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -46,7 +46,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._utils import (
+from InsurAutoML.utils import (
     maxloc,
     Pearson_Corr,
     MI,
diff --git a/InsurAutoML/_feature_selection/_hybrid.py b/InsurAutoML/feature_selection/hybrid.py
similarity index 97%
rename from InsurAutoML/_feature_selection/_hybrid.py
rename to InsurAutoML/feature_selection/hybrid.py
index 8b4bd42..3b4749a 100644
--- a/InsurAutoML/_feature_selection/_hybrid.py
+++ b/InsurAutoML/feature_selection/hybrid.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/_hybrid.py
+Relative Path: /InsurAutoML/feature_selection/hybrid.py
 File: _hybrid.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 11:46:06 am
+Last Modified: Monday, 28th November 2022 11:39:27 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -47,7 +47,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._utils import (
+from InsurAutoML.utils import (
     maxloc,
     True_index,
     MI,
@@ -55,7 +55,7 @@
     ANOVA,
     random_index,
 )
-from InsurAutoML._constant import UNI_CLASS
+from InsurAutoML.constant import UNI_CLASS
 
 
 class CBFS:
@@ -134,7 +134,8 @@ def select_feature(
                     )
                 # calculate dependency based on empirical copula
                 entropy_dependent = np.sum(
-                    [-item * np.log2(item) for item in Counter(p_dependent).values()]
+                    [-item * np.log2(item)
+                     for item in Counter(p_dependent).values()]
                 )
                 # append test results
                 results.append(entropy_dependent)
@@ -147,11 +148,13 @@ def select_feature(
 
                 # calculate dependency based on empirical copula
                 entropy_dependent = np.sum(
-                    [-item * np.log2(item) for item in Counter(p_dependent).values()]
+                    [-item * np.log2(item)
+                     for item in Counter(p_dependent).values()]
                 )
                 # calculate redundancy based on empirical copula
                 entropy_redundant = np.sum(
-                    [-item * np.log2(item) for item in Counter(p_redundant).values()]
+                    [-item * np.log2(item)
+                     for item in Counter(p_redundant).values()]
                 )
                 # append test results
                 results.append(entropy_dependent - entropy_redundant)
@@ -531,7 +534,8 @@ def _GeneticAlgorithm(
             self._fitness *= self._sum_fitness
             for i in range(2 * self.n_pair):
                 self._fitness = np.append(
-                    self._fitness, self._cal_fitness(X, y, selection_pool[-(i + 1)])
+                    self._fitness, self._cal_fitness(
+                        X, y, selection_pool[-(i + 1)])
                 )  # only need to calculate the newly added ones
                 self._sum_fitness += self._fitness[-1]
             # normalize the fitness
diff --git a/InsurAutoML/_feature_selection/_sklearn.py b/InsurAutoML/feature_selection/sklearn.py
similarity index 99%
rename from InsurAutoML/_feature_selection/_sklearn.py
rename to InsurAutoML/feature_selection/sklearn.py
index 16eabf7..d870919 100644
--- a/InsurAutoML/_feature_selection/_sklearn.py
+++ b/InsurAutoML/feature_selection/sklearn.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/_sklearn.py
+Relative Path: /InsurAutoML/feature_selection/sklearn.py
 File: _sklearn.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 9:06:17 pm
+Last Modified: Thursday, 2nd February 2023 7:32:59 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -52,7 +52,7 @@
 import sklearn.ensemble
 import warnings
 
-from InsurAutoML._utils._base import is_none
+from InsurAutoML.utils.base import is_none
 
 ##########################################################################
 # sklearn replacement of feature selection
@@ -156,7 +156,7 @@ class extra_trees_preproc_for_regression:
     def __init__(
         self,
         n_estimators: int = 5,
-        criterion: str = "mse",
+        criterion: str = "squared_error",
         min_samples_leaf: int = 5,
         min_samples_split: int = 5,
         max_features: float = 0.5,
diff --git a/InsurAutoML/_feature_selection/_wrapper.py b/InsurAutoML/feature_selection/wrapper.py
similarity index 98%
rename from InsurAutoML/_feature_selection/_wrapper.py
rename to InsurAutoML/feature_selection/wrapper.py
index c264999..d493bd6 100644
--- a/InsurAutoML/_feature_selection/_wrapper.py
+++ b/InsurAutoML/feature_selection/wrapper.py
@@ -1,17 +1,17 @@
 """
-File Name: _wrapper.py
+File Name: wrapper.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_feature_selection/_wrapper.py
+Relative Path: /InsurAutoML/feature_selection/wrapper.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:05:52 pm
+Last Modified: Tuesday, 6th December 2022 11:27:10 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -47,12 +47,12 @@
 import pandas as pd
 import itertools
 
-from InsurAutoML._utils import (
+from InsurAutoML.utils import (
     minloc,
     maxloc,
 )
-from InsurAutoML._utils._base import has_method
-from InsurAutoML._utils._optimize import (
+from InsurAutoML.utils.base import has_method
+from InsurAutoML.utils.optimize import (
     get_estimator,
     get_metrics,
 )
@@ -111,7 +111,7 @@ def fit(self, X: Union[pd.DataFrame, np.ndarray],
 
         # check whether criteria is valid
         if self.criteria == "neg_accuracy":
-            from InsurAutoML._utils._stat import neg_accuracy
+            from InsurAutoML.utils.stats import neg_accuracy
 
             self.criteria = neg_accuracy
         elif self.criteria == "MSE":
diff --git a/InsurAutoML/_hpo/_DL.py b/InsurAutoML/hpo/DL.py
similarity index 100%
rename from InsurAutoML/_hpo/_DL.py
rename to InsurAutoML/hpo/DL.py
diff --git a/InsurAutoML/_hpo/_ML.py b/InsurAutoML/hpo/ML.py
similarity index 93%
rename from InsurAutoML/_hpo/_ML.py
rename to InsurAutoML/hpo/ML.py
index 6970bdd..206a8c0 100644
--- a/InsurAutoML/_hpo/_ML.py
+++ b/InsurAutoML/hpo/ML.py
@@ -1,17 +1,17 @@
 """
-File Name: _ML.py
+File Name: ML.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/_ML.py
+Relative Path: /InsurAutoML/hpo/ML.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:51:56 pm
+Last Modified: Monday, 28th November 2022 11:34:55 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -40,13 +40,17 @@
 
 from __future__ import annotations
 
+import os
+import shutil
+import logging
 from typing import Union, List, Callable, Dict
 import numpy as np
 import pandas as pd
 
-from ._base import AutoTabularBase
+from .base import AutoTabularBase
 
-from InsurAutoML._utils._base import type_of_task
+from InsurAutoML.utils.base import type_of_task
+from InsurAutoML.hpo.utils import setup_logger
 
 
 class AutoTabularRegressor(AutoTabularBase):
@@ -271,6 +275,19 @@ def fit(
         self, X: pd.DataFrame, y: Union[pd.DataFrame, pd.Series, np.ndarray]
     ) -> AutoTabularRegressor:
 
+        # initialize temp directory
+        # check if temp directory exists, if exists, empty it
+        if os.path.isdir(os.path.join(self.temp_directory, self.model_name)):
+            shutil.rmtree(os.path.join(self.temp_directory, self.model_name))
+        if not os.path.isdir(self.temp_directory):
+            os.makedirs(self.temp_directory)
+        os.makedirs(os.path.join(self.temp_directory, self.model_name))
+
+        # setup up logger
+        if not hasattr(self, "_logger"):
+            self._logger = setup_logger(__name__, os.path.join(
+                self.temp_directory, self.model_name, "logging.conf"), level=logging.INFO,)
+
         super().fit(X, y)
 
         self._fitted = True
@@ -508,6 +525,19 @@ def fit(
         self, X: pd.DataFrame, y: Union[pd.DataFrame, pd.Series, np.ndarray]
     ) -> AutoTabularClassifier:
 
+        # initialize temp directory
+        # check if temp directory exists, if exists, empty it
+        if os.path.isdir(os.path.join(self.temp_directory, self.model_name)):
+            shutil.rmtree(os.path.join(self.temp_directory, self.model_name))
+        if not os.path.isdir(self.temp_directory):
+            os.makedirs(self.temp_directory)
+        os.makedirs(os.path.join(self.temp_directory, self.model_name))
+
+        # setup up logger
+        if not hasattr(self, "_logger"):
+            self._logger = setup_logger(__name__, os.path.join(
+                self.temp_directory, self.model_name, "logging.conf"), level=logging.INFO,)
+
         super().fit(X, y)
 
         self._fitted = True
@@ -717,6 +747,18 @@ def fit(self,
                      pd.Series,
                      np.ndarray] = None) -> AutoTabular:
 
+        # initialize temp directory
+        # check if temp directory exists, if exists, empty it
+        if os.path.isdir(os.path.join(self.temp_directory, self.model_name)):
+            shutil.rmtree(os.path.join(self.temp_directory, self.model_name))
+        if not os.path.isdir(self.temp_directory):
+            os.makedirs(self.temp_directory)
+        os.makedirs(os.path.join(self.temp_directory, self.model_name))
+
+        # setup up logger
+        self._logger = setup_logger(__name__, os.path.join(
+            self.temp_directory, self.model_name, "logging.conf"), level=logging.INFO,)
+
         if (
             isinstance(y, pd.DataFrame)
             or isinstance(y, pd.Series)
diff --git a/InsurAutoML/_hpo/__init__.py b/InsurAutoML/hpo/__init__.py
similarity index 79%
rename from InsurAutoML/_hpo/__init__.py
rename to InsurAutoML/hpo/__init__.py
index 5c73aff..b3fdde0 100644
--- a/InsurAutoML/_hpo/__init__.py
+++ b/InsurAutoML/hpo/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/__init__.py
+Relative Path: /InsurAutoML/hpo/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 7:50:56 pm
+Last Modified: Monday, 28th November 2022 11:39:43 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,6 +39,14 @@
 """
 
 # from ._base import AutoTabularBase
-from ._ML import AutoTabular, AutoTabularClassifier, AutoTabularRegressor
+from .ML import AutoTabular, AutoTabularClassifier, AutoTabularRegressor
 
-from ._DL import AutoTextClassifier, AutoNextWordPrediction
+from .DL import AutoTextClassifier, AutoNextWordPrediction
+
+
+__all__ = [
+    "AutoTabular",
+    "AutoTabularClassifier",
+    "AutoTabularRegressor",
+    "AutoTextClassifier",
+    "AutoNextWordPrediction"]
diff --git a/InsurAutoML/_hpo/_base.py b/InsurAutoML/hpo/base.py
similarity index 96%
rename from InsurAutoML/_hpo/_base.py
rename to InsurAutoML/hpo/base.py
index 5a12589..fac7d6c 100644
--- a/InsurAutoML/_hpo/_base.py
+++ b/InsurAutoML/hpo/base.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/_base.py
+Relative Path: /InsurAutoML/hpo/base.py
 File: _base.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 9:09:06 pm
+Last Modified: Monday, 28th November 2022 11:36:14 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from __future__ import annotations
-from InsurAutoML._utils._optimize import (
+from InsurAutoML.utils.optimize import (
     get_algo,
     get_scheduler,
     get_logger,
@@ -48,20 +48,21 @@
     ray_status,
     check_status,
 )
-from InsurAutoML._utils._metadata import MetaData
-from InsurAutoML._utils._data import (
+from InsurAutoML.utils.metadata import MetaData
+from InsurAutoML.utils.data import (
     str2list,
     str2dict,
 )
-from InsurAutoML._utils._file import (
+from InsurAutoML.utils.file import (
     save_methods,
     load_methods,
     find_exact_path,
 )
-from InsurAutoML._utils._base import type_of_script, format_hyper_dict
-from InsurAutoML._base import no_processing
-from InsurAutoML._constant import UNI_CLASS, MAX_TIME, LOGGINGLEVEL
-from InsurAutoML._hpo._utils import (
+from InsurAutoML.utils.base import type_of_script, format_hyper_dict
+from InsurAutoML.base import no_processing
+from InsurAutoML.constant import UNI_CLASS, MAX_TIME, LOGGINGLEVEL
+from InsurAutoML.hpo.utils import (
+    setup_logger,
     TabularObjective,
     Pipeline,
     ClassifierEnsemble,
@@ -80,8 +81,6 @@
 from typing import Union, List, Callable, Dict, Tuple
 import logging
 
-formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
-
 
 # import ray
 
@@ -122,19 +121,6 @@
     device_count = 0
 
 
-def setup_logger(name, log_file, level=logging.INFO):
-    """To setup as many loggers as you want"""
-
-    handler = logging.FileHandler(log_file)
-    handler.setFormatter(formatter)
-
-    logger = logging.getLogger(name)
-    logger.setLevel(level)
-    logger.addHandler(handler)
-
-    return logger
-
-
 class AutoTabularBase(MetaData):
 
     """ "
@@ -343,7 +329,7 @@ def get_hyperparameter_space(
 
         # Encoding: convert string types to numerical type
         # all encoders available
-        from InsurAutoML._encoding import encoders
+        from InsurAutoML.encoding import encoders
         from additional import add_encoders
 
         # include original encoders
@@ -374,7 +360,7 @@ def get_hyperparameter_space(
 
         # Imputer: fill missing values
         # all imputers available
-        from InsurAutoML._imputation import imputers
+        from InsurAutoML.imputation import imputers
         from additional import add_imputers
 
         # include original imputers
@@ -414,7 +400,7 @@ def get_hyperparameter_space(
 
         # Balancing: deal with imbalanced dataset, using over-/under-sampling methods
         # all balancings available
-        from InsurAutoML._balancing import balancings
+        from InsurAutoML.balancing import balancings
         from additional import add_balancings
 
         # include original balancings
@@ -439,7 +425,7 @@ def get_hyperparameter_space(
 
         # Scaling
         # all scalings available
-        from InsurAutoML._scaling import scalings
+        from InsurAutoML.scaling import scalings
         from additional import add_scalings
 
         # include original scalings
@@ -464,7 +450,7 @@ def get_hyperparameter_space(
 
         # Feature selection: Remove redundant features, reduce dimensionality
         # all feature selections available
-        from InsurAutoML._feature_selection import feature_selections
+        from InsurAutoML.feature_selection import feature_selections
         from additional import add_feature_selections
 
         # include original feature selections
@@ -515,7 +501,7 @@ def get_hyperparameter_space(
         # if mode is classification, use classification models
         # if mode is regression, use regression models
         if self.task_mode == "classification":
-            from InsurAutoML._model import classifiers
+            from InsurAutoML.model import classifiers
             from additional import add_classifiers
 
             # include original classifiers
@@ -523,7 +509,7 @@ def get_hyperparameter_space(
             # include additional classifiers
             self._all_models.update(add_classifiers)
         elif self.task_mode == "regression":
-            from InsurAutoML._model import regressors
+            from InsurAutoML.model import regressors
             from additional import add_regressors
 
             # include original regressors
@@ -562,9 +548,9 @@ def get_hyperparameter_space(
         # _all_models_hyperparameters = copy.deepcopy(self._all_models_hyperparameters)
 
         # initialize default search space
-        from InsurAutoML._utils._optimize import _get_hyperparameter_space
+        from InsurAutoML.utils.optimize import _get_hyperparameter_space
 
-        from InsurAutoML._hyperparameters import (
+        from InsurAutoML.hyperparameters import (
             encoder_hyperparameter,
             imputer_hyperparameter,
             scaling_hyperparameter,
@@ -634,7 +620,7 @@ def get_hyperparameter_space(
         if self.task_mode == "classification":
             for item in _all_feature_selection_hyperparameters:
                 if "SFS" in item.values():
-                    from InsurAutoML._constant import (
+                    from InsurAutoML.constant import (
                         CLASSIFICATION_ESTIMATORS,
                         CLASSIFICATION_CRITERIA,
                     )
@@ -645,7 +631,7 @@ def get_hyperparameter_space(
         elif self.task_mode == "regression":
             for item in _all_feature_selection_hyperparameters:
                 if "SFS" in item.values():
-                    from InsurAutoML._constant import (
+                    from InsurAutoML.constant import (
                         REGRESSION_ESTIMATORS,
                         REGRESSION_CRITERIA,
                     )
@@ -680,12 +666,12 @@ def get_hyperparameter_space(
                 if "LightGBM_Classifier" in item.values():
                     # flatten to 1d
                     if len(pd.unique(y.to_numpy().flatten())) == 2:
-                        from InsurAutoML._constant import LIGHTGBM_BINARY_CLASSIFICATION
+                        from InsurAutoML.constant import LIGHTGBM_BINARY_CLASSIFICATION
 
                         item["objective"] = tune.choice(
                             LIGHTGBM_BINARY_CLASSIFICATION)
                     else:
-                        from InsurAutoML._constant import (
+                        from InsurAutoML.constant import (
                             LIGHTGBM_MULTICLASS_CLASSIFICATION,
                         )
 
@@ -963,7 +949,8 @@ def _fit_optimal(
 
         # record optimal settings
         with open(
-            os.path.join(self.temp_directory, self.model_name, "optimal_setting.txt"),
+            os.path.join(self.temp_directory, self.model_name,
+                         "optimal_setting.txt"),
             write_type,
         ) as f:
             f.write("For pipeline {}:\n".format(idx + 1))
@@ -1163,7 +1150,7 @@ def fit(
         self, X: pd.DataFrame, y: Union[pd.DataFrame, pd.Series, np.ndarray]
     ) -> AutoTabularBase:
 
-        # initialize temp directory
+       # initialize temp directory
         # check if temp directory exists, if exists, empty it
         if os.path.isdir(os.path.join(self.temp_directory, self.model_name)):
             shutil.rmtree(os.path.join(self.temp_directory, self.model_name))
@@ -1171,26 +1158,10 @@ def fit(
             os.makedirs(self.temp_directory)
         os.makedirs(os.path.join(self.temp_directory, self.model_name))
 
-        # # set up logging file
-        # if not os.path.exists(
-        #     os.path.join(self.temp_directory, self.model_name, "logging.conf")
-        # ):
-        #     with open(
-        #         os.path.join(self.temp_directory, self.model_name, "logging.conf"), "w"
-        #     ) as fp:
-        #         pass
-        # logging.config.fileConfig(
-        #     os.path.join(self.temp_directory, self.model_name, "logging.conf"),
-        #     disable_existing_loggers=False,
-        # )
-        # # set up verbosity of logging
-        self._logger = setup_logger(
-            __name__,
-            os.path.join(self.temp_directory, self.model_name, "logging.conf"),
-            level=logging.INFO,
-        )
-        # logging.basicConfig(level=LOGGINGLEVEL[self.verbose])
-        # self._logger = logging.getLogger(__name__)
+        # setup up logger
+        if not hasattr(self, "_logger"):
+            self._logger = setup_logger(__name__, os.path.join(
+                self.temp_directory, self.model_name, "logging.conf"), level=logging.INFO,)
 
         self._logger.info(
             "[INFO] {} Experiment: {}. Status: Start preparing AutoTabular...".format(
@@ -1387,7 +1358,8 @@ def fit(
         ) as f:
             f.write("Features of the dataset: {}\n".format(list(_X.columns)))
             f.write(
-                "Shape of the design matrix: {} * {}\n".format(_X.shape[0], _X.shape[1])
+                "Shape of the design matrix: {} * {}\n".format(
+                    _X.shape[0], _X.shape[1])
             )
             f.write("Response of the dataset: {}\n".format(list(_y.columns)))
             f.write(
diff --git a/InsurAutoML/_hpo/_utils.py b/InsurAutoML/hpo/utils.py
similarity index 98%
rename from InsurAutoML/_hpo/_utils.py
rename to InsurAutoML/hpo/utils.py
index 0804c27..c312d0b 100644
--- a/InsurAutoML/_hpo/_utils.py
+++ b/InsurAutoML/hpo/utils.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hpo/_utils.py
+Relative Path: /InsurAutoML/hpo/utils.py
 File: _utils.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 12:55:53 pm
+Last Modified: Monday, 28th November 2022 11:41:02 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,9 +39,9 @@
 """
 
 from __future__ import annotations
-from InsurAutoML._utils._data import train_test_split
-from InsurAutoML._utils._file import save_methods
-from InsurAutoML._utils._data import formatting
+from InsurAutoML.utils.data import train_test_split
+from InsurAutoML.utils.file import save_methods
+from InsurAutoML.utils.data import formatting
 from sklearn.exceptions import ConvergenceWarning
 from sklearn.utils._testing import ignore_warnings
 from ray import tune
@@ -59,6 +59,20 @@
 
 # from logging import warning
 
+def setup_logger(name, log_file, level=logging.INFO):
+    """To setup as many loggers as you want"""
+
+    formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
+
+    handler = logging.FileHandler(log_file)
+    handler.setFormatter(formatter)
+
+    logger = logging.getLogger(name)
+    logger.setLevel(level)
+    logger.addHandler(handler)
+
+    return logger
+
 
 class Pipeline:
 
diff --git a/InsurAutoML/_hyperparameters/__init__.py b/InsurAutoML/hyperparameters/__init__.py
similarity index 87%
rename from InsurAutoML/_hyperparameters/__init__.py
rename to InsurAutoML/hyperparameters/__init__.py
index 827c7f8..c653b2f 100644
--- a/InsurAutoML/_hyperparameters/__init__.py
+++ b/InsurAutoML/hyperparameters/__init__.py
@@ -4,14 +4,14 @@
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
-Project: My_AutoML
-Latest Version: 0.2.0
-Relative Path: /My_AutoML/_hyperparameters/__init__.py
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/hyperparameters/__init__.py
 File Created: Tuesday, 5th April 2022 11:01:43 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Friday, 8th April 2022 10:24:36 pm
+Last Modified: Monday, 28th November 2022 11:43:45 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,15 +38,15 @@
 SOFTWARE.
 """
 
-from ._ray._encoder_hyperparameter import encoder_hyperparameter
-from ._ray._imputer_hyperparameter import imputer_hyperparameter
-from ._ray._scaling_hyperparameter import scaling_hyperparameter
-from ._ray._balancing_hyperparameter import balancing_hyperparameter
-from ._ray._feature_selection_hyperparameter import (
+from .ray.encoder_hyperparameter import encoder_hyperparameter
+from .ray.imputer_hyperparameter import imputer_hyperparameter
+from .ray.scaling_hyperparameter import scaling_hyperparameter
+from .ray.balancing_hyperparameter import balancing_hyperparameter
+from .ray.feature_selection_hyperparameter import (
     feature_selection_hyperparameter,
 )
-from ._ray._classifier_hyperparameter import classifier_hyperparameter
-from ._ray._regressor_hyperparameter import regressor_hyperparameter
+from .ray.classifier_hyperparameter import classifier_hyperparameter
+from .ray.regressor_hyperparameter import regressor_hyperparameter
 
 """
 Notice for designing hyperparameters space:
@@ -127,3 +127,13 @@
             activation, alpha, learning_rate_init, early_stopping, solver,
             batch_size, n_iter_no_change, tol, shuffle, beta_1, beta_2, epsilon
 """
+
+__all__ = [
+    "encoder_hyperparameter",
+    "imputer_hyperparameter",
+    "scaling_hyperparameter",
+    "balancing_hyperparameter",
+    "feature_selection_hyperparameter",
+    "classifier_hyperparameter",
+    "regressor_hyperparameter",
+]
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/__init__.py b/InsurAutoML/hyperparameters/_hyperopt/__init__.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/__init__.py
rename to InsurAutoML/hyperparameters/_hyperopt/__init__.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_balancing_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_balancing_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_balancing_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_balancing_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_classifier_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_classifier_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_classifier_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_classifier_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_encoder_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_encoder_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_encoder_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_encoder_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_feature_selection_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_feature_selection_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_feature_selection_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_feature_selection_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_imputer_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_imputer_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_imputer_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_imputer_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_regressor_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_regressor_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_regressor_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_regressor_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_hyperopt/_scaling_hyperparameter.py b/InsurAutoML/hyperparameters/_hyperopt/_scaling_hyperparameter.py
similarity index 100%
rename from InsurAutoML/_hyperparameters/_hyperopt/_scaling_hyperparameter.py
rename to InsurAutoML/hyperparameters/_hyperopt/_scaling_hyperparameter.py
diff --git a/InsurAutoML/_hyperparameters/_ray/_balancing_hyperparameter.py b/InsurAutoML/hyperparameters/ray/balancing_hyperparameter.py
similarity index 93%
rename from InsurAutoML/_hyperparameters/_ray/_balancing_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/balancing_hyperparameter.py
index 270cf34..85bf30d 100644
--- a/InsurAutoML/_hyperparameters/_ray/_balancing_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/balancing_hyperparameter.py
@@ -1,17 +1,17 @@
 """
-File Name: _balancing_hyperparameter.py
+File Name: balancing_hyperparameter.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_balancing_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/balancing_hyperparameter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:02:51 pm
+Last Modified: Monday, 30th January 2023 12:32:34 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from ray import tune
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 NOPROCESSING = {
     "balancing": "no_processing",
diff --git a/InsurAutoML/_hyperparameters/_ray/_classifier_hyperparameter.py b/InsurAutoML/hyperparameters/ray/classifier_hyperparameter.py
similarity index 97%
rename from InsurAutoML/_hyperparameters/_ray/_classifier_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/classifier_hyperparameter.py
index f7c77f6..654429b 100644
--- a/InsurAutoML/_hyperparameters/_ray/_classifier_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/classifier_hyperparameter.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_classifier_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/classifier_hyperparameter.py
 File: _classifier_hyperparameter.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 8:57:30 pm
+Last Modified: Monday, 28th November 2022 11:39:16 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -49,8 +49,8 @@
 
 from ray import tune
 
-from InsurAutoML._constant import LIGHTGBM_BOOSTING, LIGHTGBM_TREE_LEARNER
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.constant import LIGHTGBM_BOOSTING, LIGHTGBM_TREE_LEARNER
+from InsurAutoML.utils.base import format_hyper_dict
 
 ADABOOSTCLASSIFIER = {
     "model": "AdaboostClassifier",
diff --git a/InsurAutoML/_hyperparameters/_ray/_encoder_hyperparameter.py b/InsurAutoML/hyperparameters/ray/encoder_hyperparameter.py
similarity index 89%
rename from InsurAutoML/_hyperparameters/_ray/_encoder_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/encoder_hyperparameter.py
index a58a1fe..9fa4450 100644
--- a/InsurAutoML/_hyperparameters/_ray/_encoder_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/encoder_hyperparameter.py
@@ -1,17 +1,17 @@
 """
-File Name: _encoder_hyperparameter.py
+File Name: encoder_hyperparameter.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_encoder_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/encoder_hyperparameter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:03:06 pm
+Last Modified: Monday, 30th January 2023 12:32:30 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from ray import tune
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 
 DATAENCODING = {
diff --git a/InsurAutoML/_hyperparameters/_ray/_feature_selection_hyperparameter.py b/InsurAutoML/hyperparameters/ray/feature_selection_hyperparameter.py
similarity index 97%
rename from InsurAutoML/_hyperparameters/_ray/_feature_selection_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/feature_selection_hyperparameter.py
index c5350ca..d170fa4 100644
--- a/InsurAutoML/_hyperparameters/_ray/_feature_selection_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/feature_selection_hyperparameter.py
@@ -1,17 +1,17 @@
 """
-File Name: _feature_selection_hyperparameter.py
+File Name: feature_selection_hyperparameter.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_feature_selection_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/feature_selection_hyperparameter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:03:12 pm
+Last Modified: Monday, 30th January 2023 12:32:43 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from ray import tune
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 NOPROCESSING = {"feature_selection": "no_processing"}
 LDASELECTION = {"feature_selection": "LDASelection"}
diff --git a/InsurAutoML/_hyperparameters/_ray/_imputer_hyperparameter.py b/InsurAutoML/hyperparameters/ray/imputer_hyperparameter.py
similarity index 93%
rename from InsurAutoML/_hyperparameters/_ray/_imputer_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/imputer_hyperparameter.py
index aea613a..be5cd9f 100644
--- a/InsurAutoML/_hyperparameters/_ray/_imputer_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/imputer_hyperparameter.py
@@ -1,17 +1,17 @@
 """
-File Name: _imputer_hyperparameter.py
+File Name: imputer_hyperparameter.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_imputer_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/imputer_hyperparameter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:03:18 pm
+Last Modified: Monday, 30th January 2023 12:32:48 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from ray import tune
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 SIMPLEIMPUTER = {
     "imputer": "SimpleImputer",
diff --git a/InsurAutoML/_hyperparameters/_ray/_regressor_hyperparameter.py b/InsurAutoML/hyperparameters/ray/regressor_hyperparameter.py
similarity index 98%
rename from InsurAutoML/_hyperparameters/_ray/_regressor_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/regressor_hyperparameter.py
index 10dbc71..1f99b53 100644
--- a/InsurAutoML/_hyperparameters/_ray/_regressor_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/regressor_hyperparameter.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_regressor_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/regressor_hyperparameter.py
 File: _regressor_hyperparameter.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 8:57:57 pm
+Last Modified: Monday, 28th November 2022 11:39:07 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -49,12 +49,12 @@
 
 from ray import tune
 
-from InsurAutoML._constant import (
+from InsurAutoML.constant import (
     LIGHTGBM_REGRESSION,
     LIGHTGBM_BOOSTING,
     LIGHTGBM_TREE_LEARNER,
 )
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 ADABOOSTREGRESSOR = {
     "model": "AdaboostRegressor",
diff --git a/InsurAutoML/_hyperparameters/_ray/_scaling_hyperparameter.py b/InsurAutoML/hyperparameters/ray/scaling_hyperparameter.py
similarity index 91%
rename from InsurAutoML/_hyperparameters/_ray/_scaling_hyperparameter.py
rename to InsurAutoML/hyperparameters/ray/scaling_hyperparameter.py
index 6e470a3..66dfb4d 100644
--- a/InsurAutoML/_hyperparameters/_ray/_scaling_hyperparameter.py
+++ b/InsurAutoML/hyperparameters/ray/scaling_hyperparameter.py
@@ -1,17 +1,17 @@
 """
-File Name: _scaling_hyperparameter.py
+File Name: scaling_hyperparameter.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_hyperparameters/_ray/_scaling_hyperparameter.py
+Relative Path: /InsurAutoML/hyperparameters/ray/scaling_hyperparameter.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:03:34 pm
+Last Modified: Monday, 30th January 2023 12:32:56 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 from ray import tune
-from InsurAutoML._utils._base import format_hyper_dict
+from InsurAutoML.utils.base import format_hyper_dict
 
 NOPROCESSING = {"scaling": "no_processing"}
 STANDARDIZE = {"scaling": "Standardize"}
diff --git a/InsurAutoML/_imputation/__init__.py b/InsurAutoML/imputation/__init__.py
similarity index 81%
rename from InsurAutoML/_imputation/__init__.py
rename to InsurAutoML/imputation/__init__.py
index 29a8572..ff86373 100644
--- a/InsurAutoML/_imputation/__init__.py
+++ b/InsurAutoML/imputation/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/__init__.py
+Relative Path: /InsurAutoML/imputation/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:07:20 pm
+Last Modified: Monday, 28th November 2022 11:43:39 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,10 +39,10 @@
 """
 
 import importlib
-from ._base import SimpleImputer, DummyImputer, JointImputer
-from ._multiple import ExpectationMaximization, KNNImputer, MissForestImputer, MICE
+from .base import SimpleImputer, DummyImputer, JointImputer
+from .multiple import ExpectationMaximization, KNNImputer, MissForestImputer, MICE
 
-# from ._clustering import AAI_kNN, KMI, CMI, k_Prototype_NN
+# from .clustering import AAI_kNN, KMI, CMI, k_Prototype_NN
 
 
 imputers = {
@@ -66,6 +66,17 @@
 tensorflow_spec = importlib.util.find_spec("tensorflow")
 torch_spec = importlib.util.find_spec("torch")
 if tensorflow_spec is not None or torch_spec is not None:
-    from ._nn import GAIN
+    from .nn import GAIN
 
     imputers["GAIN"] = GAIN
+
+__all__ = [
+    "SimpleImputer",
+    "DummyImputer",
+    "JointImputer",
+    "ExpectationMaximization",
+    "KNNImputer",
+    "MissForestImputer",
+    "MICE",
+    "GAIN",
+]
diff --git a/InsurAutoML/_imputation/_base.py b/InsurAutoML/imputation/base.py
similarity index 98%
rename from InsurAutoML/_imputation/_base.py
rename to InsurAutoML/imputation/base.py
index 2f07f20..2710319 100644
--- a/InsurAutoML/_imputation/_base.py
+++ b/InsurAutoML/imputation/base.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/_base.py
+Relative Path: /InsurAutoML/imputation/_base.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:07:35 pm
+Last Modified: Monday, 28th November 2022 11:20:10 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,7 +44,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._utils import nan_cov
+from InsurAutoML.utils import nan_cov
 
 
 class SimpleImputer:
diff --git a/InsurAutoML/_imputation/_clustering.py b/InsurAutoML/imputation/clustering.py
similarity index 98%
rename from InsurAutoML/_imputation/_clustering.py
rename to InsurAutoML/imputation/clustering.py
index 522389a..af03fef 100644
--- a/InsurAutoML/_imputation/_clustering.py
+++ b/InsurAutoML/imputation/clustering.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/_clustering.py
+Relative Path: /InsurAutoML/imputation/_clustering.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:07:44 pm
+Last Modified: Monday, 28th November 2022 11:20:55 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -48,8 +48,8 @@
 import multiprocessing
 from multiprocessing import Pool
 
-from InsurAutoML._utils import formatting
-from InsurAutoML._scaling import MinMaxScale
+from InsurAutoML.utils import formatting
+from InsurAutoML.scaling import MinMaxScale
 
 
 class AAI_kNN(formatting, MinMaxScale):
@@ -475,7 +475,8 @@ def _kernel(
     ) -> np.ndarray:
 
         return np.prod(
-            np.exp(-(((row1 - row2) / self.bandwidth) ** 2) / 2) / np.sqrt(2 * np.pi)
+            np.exp(-(((row1 - row2) / self.bandwidth) ** 2) / 2) /
+            np.sqrt(2 * np.pi)
         )
 
     # get k_means for group k
@@ -641,7 +642,8 @@ def Pool_task(
                     [
                         self._kernel(
                             _X.loc[_index, _X.columns != _column],
-                            self.train.loc[__index, self.train.columns != _column],
+                            self.train.loc[__index,
+                                           self.train.columns != _column],
                         )
                         for __index in non_missing_index
                     ]
@@ -906,7 +908,8 @@ def _get_group_assign(
 
         for _index in index_list:
             measurement = self._distance(
-                data.loc[_index, numerical_columns], self.k_centroids[numerical_columns]
+                data.loc[_index,
+                         numerical_columns], self.k_centroids[numerical_columns]
             ) + self._dissimilarity(
                 data.loc[_index, categorical_columns],
                 self.k_centroids[categorical_columns],
diff --git a/InsurAutoML/_imputation/_multiple.py b/InsurAutoML/imputation/multiple.py
similarity index 97%
rename from InsurAutoML/_imputation/_multiple.py
rename to InsurAutoML/imputation/multiple.py
index 0b69dc6..cb27a16 100644
--- a/InsurAutoML/_imputation/_multiple.py
+++ b/InsurAutoML/imputation/multiple.py
@@ -1,17 +1,17 @@
 """
-File Name: _multiple.py
+File Name: multiple.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/_multiple.py
+Relative Path: /InsurAutoML/imputation/multiple.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:13:40 pm
+Last Modified: Monday, 28th November 2022 11:38:58 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,9 +45,9 @@
 import pandas as pd
 import warnings
 
-from InsurAutoML._constant import UNI_CLASS
-from InsurAutoML._utils import random_index, random_list
-from ._base import SimpleImputer
+from InsurAutoML.constant import UNI_CLASS
+from InsurAutoML.utils import random_index, random_list
+from .base import SimpleImputer
 
 
 class ExpectationMaximization:
@@ -346,11 +346,13 @@ def _RFImputer(self, X: pd.DataFrame) -> pd.DataFrame:
                 _missing_index = self._missing_table[_column].tolist()
                 RegModel = RandomForestRegressor()
                 RegModel.fit(
-                    X.loc[~X.index.astype(int).isin(_missing_index), _subfeature],
+                    X.loc[~X.index.astype(int).isin(
+                        _missing_index), _subfeature],
                     X.loc[~X.index.astype(int).isin(_missing_index), _column],
                 )
                 _tmp_column = RegModel.predict(
-                    X.loc[X.index.astype(int).isin(_missing_index), _subfeature]
+                    X.loc[X.index.astype(int).isin(
+                        _missing_index), _subfeature]
                 )
                 X.loc[X.index.astype(int).isin(
                     _missing_index), _column] = _tmp_column
diff --git a/InsurAutoML/_imputation/_nn.py b/InsurAutoML/imputation/nn.py
similarity index 98%
rename from InsurAutoML/_imputation/_nn.py
rename to InsurAutoML/imputation/nn.py
index 10b9a03..e435e47 100644
--- a/InsurAutoML/_imputation/_nn.py
+++ b/InsurAutoML/imputation/nn.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_imputation/_nn.py
+Relative Path: /InsurAutoML/imputation/_nn.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:13:58 pm
+Last Modified: Monday, 28th November 2022 11:20:59 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,8 +39,8 @@
 """
 
 from __future__ import annotations
-from InsurAutoML._scaling import MinMaxScale
-from InsurAutoML._utils import (
+from InsurAutoML.scaling import MinMaxScale
+from InsurAutoML.utils import (
     random_index,
     feature_rounding,
     get_missing_matrix,
@@ -668,7 +668,8 @@ def _fill(self, X: pd.DataFrame) -> pd.DataFrame:
                     # if losses changing not significant,
                     # early stop
                     if (
-                        np.abs(self.losses[-1] - self.losses[-2]) / self.losses[-2]
+                        np.abs(self.losses[-1] -
+                               self.losses[-2]) / self.losses[-2]
                         < self.delta
                     ):
                         # for tqdm, to break, need manual close
diff --git a/InsurAutoML/_model/_FNN.py b/InsurAutoML/model/FNN.py
similarity index 99%
rename from InsurAutoML/_model/_FNN.py
rename to InsurAutoML/model/FNN.py
index 58b7443..db7c6ef 100644
--- a/InsurAutoML/_model/_FNN.py
+++ b/InsurAutoML/model/FNN.py
@@ -1,17 +1,17 @@
 """
-File Name: _FNN.py
+File Name: FNN.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_FNN.py
+Relative Path: /InsurAutoML/model/FNN.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:14:58 pm
+Last Modified: Monday, 28th November 2022 11:40:59 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,7 +45,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._utils._data import assign_classes
+from InsurAutoML.utils.data import assign_classes
 
 # check if pytorch exists
 # if exists, import pytorch
diff --git a/InsurAutoML/_model/_ML.py b/InsurAutoML/model/ML.py
similarity index 100%
rename from InsurAutoML/_model/_ML.py
rename to InsurAutoML/model/ML.py
diff --git a/InsurAutoML/_model/_RNN.py b/InsurAutoML/model/RNN.py
similarity index 98%
rename from InsurAutoML/_model/_RNN.py
rename to InsurAutoML/model/RNN.py
index 5050e0a..e383552 100644
--- a/InsurAutoML/_model/_RNN.py
+++ b/InsurAutoML/model/RNN.py
@@ -1,17 +1,17 @@
 """
-File Name: _RNN.py
+File Name: RNN.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_RNN.py
+Relative Path: /InsurAutoML/model/RNN.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:15:30 pm
+Last Modified: Monday, 28th November 2022 11:40:55 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,8 +45,8 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._utils._data import assign_classes
-from InsurAutoML._utils._tensor import repackage_hidden
+from InsurAutoML.utils.data import assign_classes
+from InsurAutoML.utils.tensor import repackage_hidden
 
 # check if pytorch exists
 # if exists, import pytorch
diff --git a/InsurAutoML/_model/__init__.py b/InsurAutoML/model/__init__.py
similarity index 84%
rename from InsurAutoML/_model/__init__.py
rename to InsurAutoML/model/__init__.py
index b5d3bf7..df6a5cc 100644
--- a/InsurAutoML/_model/__init__.py
+++ b/InsurAutoML/model/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/__init__.py
+Relative Path: /InsurAutoML/model/__init__.py
 File: __init__.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 4:18:37 pm
+Last Modified: Thursday, 2nd February 2023 6:54:25 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,7 +41,7 @@
 ##########################################################################
 # classifiers
 
-from ._sklearn import (
+from .sklearn import (
     # sklearn classifiers
     AdaboostClassifier,
     BernoulliNB,
@@ -203,7 +203,7 @@
 classifiers["LogisticRegression"] = LogisticRegression
 # classifiers["ComplementNB"] = ComplementNB
 # classifiers["HistGradientBoostingClassifier"] = HistGradientBoostingClassifier
-classifiers["GradientBoostingClassifier"] = GradientBoostingClassifier
+# classifiers["GradientBoostingClassifier"] = GradientBoostingClassifier
 
 # regression models from sklearn
 regressors["AdaboostRegressor"] = AdaboostRegressor
@@ -225,13 +225,13 @@
 regressors["BayesianRidge"] = BayesianRidge
 # regressors["HistGradientBoostingRegressor"] =
 # HistGradientBoostingRegressor # not well-supported by package conflicts
-regressors["GradientBoostingRegressor"] = GradientBoostingRegressor
+# regressors["GradientBoostingRegressor"] = GradientBoostingRegressor
 
 # check whether lightgbm installed
 # if installed, add lightgbm depended classifiers/regressors
 lightgbm_spec = importlib.util.find_spec("lightgbm")
 if lightgbm_spec is not None:
-    from ._lightgbm import LightGBM_Classifier, LightGBM_Regressor
+    from .lightgbm import LightGBM_Classifier, LightGBM_Regressor
 
     classifiers["LightGBM_Classifier"] = LightGBM_Classifier
 
@@ -240,7 +240,7 @@
 # check weather xgboost installed
 xgboost_spec = importlib.util.find_spec("xgboost")
 if xgboost_spec is not None:
-    from ._xgboost import XGBoost_Classifier, XGBoost_Regressor
+    from .xgboost import XGBoost_Classifier, XGBoost_Regressor
 
     classifiers["XGBoost_Classifier"] = XGBoost_Classifier
 
@@ -249,7 +249,7 @@
 # check whether pygam installed
 pygam_spec = importlib.util.find_spec("pygam")
 if pygam_spec is not None:
-    from ._gam import GAM_Classifier, GAM_Regressor
+    from .gam import GAM_Classifier, GAM_Regressor
 
     classifiers["GAM_Classifier"] = GAM_Classifier
 
@@ -259,11 +259,61 @@
 # if installed, add torch depended classifiers/regressors
 torch_spec = importlib.util.find_spec("torch")
 if torch_spec is not None:
-    from ._FNN import MLP_Classifier, MLP_Regressor
-    from ._RNN import RNN_Classifier, RNN_Regressor
+    from .FNN import MLP_Classifier, MLP_Regressor
+    from .RNN import RNN_Classifier, RNN_Regressor
 
     classifiers["MLP_Classifier"] = MLP_Classifier
     classifiers["RNN_Classifier"] = RNN_Classifier
 
     regressors["MLP_Regressor"] = MLP_Regressor
     regressors["RNN_Regressor"] = RNN_Regressor
+
+
+__all__ = [
+    "AdaboostClassifier",
+    "BernoulliNB",
+    "DecisionTreeClassifier",
+    "ExtraTreesClassifier",
+    "GaussianNB",
+    "HistGradientBoostingClassifier",
+    "KNearestNeighborsClassifier",
+    "LDA",
+    "LibLinear_SVC",
+    "LibSVM_SVC",
+    "MLPClassifier",
+    "MultinomialNB",
+    "PassiveAggressive",
+    "QDA",
+    "RandomForestClassifier",
+    "SGDClassifier",
+    "AdaboostRegressor",
+    "ARDRegression",
+    "DecisionTreeRegressor",
+    "ExtraTreesRegressor",
+    "GaussianProcess",
+    "HistGradientBoostingRegressor",
+    "KNearestNeighborsRegressor",
+    "LibLinear_SVR",
+    "LibSVM_SVR",
+    "MLPRegressor",
+    "RandomForestRegressor",
+    "SGDRegressor",
+    "LogisticRegression",
+    "GradientBoostingClassifier",
+    "LinearRegression",
+    "Lasso",
+    "Ridge",
+    "ElasticNet",
+    "BayesianRidge",
+    "GradientBoostingRegressor",
+    "LightGBM_Classifier",
+    "LightGBM_Regressor",
+    "XGBoost_Classifier",
+    "XGBoost_Regressor",
+    "GAM_Classifier",
+    "GAM_Regressor",
+    "MLP_Classifier",
+    "MLP_Regressor",
+    "RNN_Classifier",
+    "RNN_Regressor",
+]
diff --git a/InsurAutoML/_model/_autosklearn.py b/InsurAutoML/model/autosklearn.py
similarity index 100%
rename from InsurAutoML/_model/_autosklearn.py
rename to InsurAutoML/model/autosklearn.py
diff --git a/InsurAutoML/_model/_clustering.py b/InsurAutoML/model/clustering.py
similarity index 100%
rename from InsurAutoML/_model/_clustering.py
rename to InsurAutoML/model/clustering.py
diff --git a/InsurAutoML/_model/_gam.py b/InsurAutoML/model/gam.py
similarity index 100%
rename from InsurAutoML/_model/_gam.py
rename to InsurAutoML/model/gam.py
diff --git a/InsurAutoML/_model/_lightgbm.py b/InsurAutoML/model/lightgbm.py
similarity index 97%
rename from InsurAutoML/_model/_lightgbm.py
rename to InsurAutoML/model/lightgbm.py
index 52689f0..f9e3eac 100644
--- a/InsurAutoML/_model/_lightgbm.py
+++ b/InsurAutoML/model/lightgbm.py
@@ -1,17 +1,17 @@
 """
-File Name: _lightgbm.py
+File Name: lightgbm.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_lightgbm.py
+Relative Path: /InsurAutoML/model/lightgbm.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:15:16 pm
+Last Modified: Monday, 28th November 2022 11:38:54 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,7 +45,7 @@
 import pandas as pd
 from lightgbm import LGBMClassifier, LGBMRegressor
 
-from InsurAutoML._constant import (
+from InsurAutoML.constant import (
     LIGHTGBM_BINARY_CLASSIFICATION,
     LIGHTGBM_MULTICLASS_CLASSIFICATION,
     LIGHTGBM_REGRESSION,
@@ -161,7 +161,8 @@ def fit(
         ):
             raise AttributeError(
                 "For {} tasks, only accept objects: {}, get {}.".format(
-                    self.task_type, ", ".join(LIGHTGBM_REGRESSION), self.objective
+                    self.task_type, ", ".join(
+                        LIGHTGBM_REGRESSION), self.objective
                 )
             )
 
diff --git a/InsurAutoML/_model/_sklearn.py b/InsurAutoML/model/sklearn.py
similarity index 99%
rename from InsurAutoML/_model/_sklearn.py
rename to InsurAutoML/model/sklearn.py
index dba747b..15f2a4a 100644
--- a/InsurAutoML/_model/_sklearn.py
+++ b/InsurAutoML/model/sklearn.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_sklearn.py
+Relative Path: /InsurAutoML/model/sklearn.py
 File: _sklearn.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 11:32:04 pm
+Last Modified: Thursday, 2nd February 2023 9:29:05 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -64,9 +64,9 @@
 # if sklearn_1_0_0:
 #     from sklearn.experimental import enable_hist_gradient_boosting
 
-from InsurAutoML._constant import MAX_ITER
-from InsurAutoML._utils._base import is_none
-from InsurAutoML._utils._data import softmax
+from InsurAutoML.constant import MAX_ITER
+from InsurAutoML.utils.base import is_none
+from InsurAutoML.utils.data import softmax
 
 ##########################################################################
 # models from sklearn
@@ -415,7 +415,7 @@ def _fit_iteration(
 
             # map from autosklearn parameter space to sklearn parameter space
             if self.early_stop == "off":
-                self.n_iter_no_change = 0
+                self.n_iter_no_change = 1
                 self.validation_fraction_ = None
                 self.early_stopping_ = False
             elif self.early_stop == "train":
@@ -682,7 +682,7 @@ def __init__(
         self.coef0 = 0.0 if is_none(coef0) else float(coef0)
         self.shrinking = shrinking
         self.tol = float(tol)
-        self.max_iter = float(max_iter)
+        self.max_iter = int(max_iter)
 
         from sklearn.svm import SVC
         from sklearn.calibration import CalibratedClassifierCV
@@ -1575,7 +1575,7 @@ def predict_proba(
 class DecisionTreeRegressor(sklearn.tree.DecisionTreeRegressor):
     def __init__(
         self,
-        criterion: str = "mse",
+        criterion: str = "squared_error",
         max_features: float = 1.0,
         max_depth_factor: float = 0.5,
         min_samples_split: int = 2,
@@ -1636,7 +1636,7 @@ def predict_proba(
 class ExtraTreesRegressor:
     def __init__(
         self,
-        criterion: str = "mse",
+        criterion: str = "squared_error",
         max_depth: Union[str, int] = "None",
         max_leaf_nodes: Union[str, int] = "None",
         min_samples_leaf: int = 1,
@@ -1793,7 +1793,7 @@ def predict_proba(
 class HistGradientBoostingRegressor:
     def __init__(
         self,
-        loss: str = "least_squares",
+        loss: str = "squared_error",
         learning_rate: float = 0.1,
         min_samples_leaf: int = 20,
         max_depth: Union[str, int] = "None",
@@ -1842,7 +1842,7 @@ def _fit_iteration(
 
             # map from autosklearn parameter space to sklearn parameter space
             if self.early_stop == "off":
-                self.n_iter_no_change = 0
+                self.n_iter_no_change = 1
                 self.validation_fraction_ = None
                 self.early_stopping_ = False
             elif self.early_stop == "train":
@@ -2233,7 +2233,7 @@ def predict_proba(
 class RandomForestRegressor:
     def __init__(
         self,
-        criterion: str = "mse",
+        criterion: str = "squared_error",
         max_features: float = 0.5,
         max_depth: Union[str, int] = "None",
         min_samples_split: int = 2,
@@ -2336,7 +2336,7 @@ def predict_proba(
 class SGDRegressor:
     def __init__(
         self,
-        loss: str = "squared_loss",
+        loss: str = "squared_error",
         penalty: str = "l2",
         alpha: float = 0.0001,
         fit_intercept: bool = True,
@@ -2664,7 +2664,7 @@ def predict_proba(
 # class HistGradientBoostingRegressor(sklearn.ensemble.HistGradientBoostingRegressor):
 #     def __init__(
 #         self,
-#         loss="least_squares",
+#         loss="squared_error",
 #         learning_rate=0.1,
 #         max_leaf_nodes=31,
 #         max_depth=None,
diff --git a/InsurAutoML/_model/_wrap.py b/InsurAutoML/model/utils.py
similarity index 94%
rename from InsurAutoML/_model/_wrap.py
rename to InsurAutoML/model/utils.py
index e21ddb6..e767d72 100644
--- a/InsurAutoML/_model/_wrap.py
+++ b/InsurAutoML/model/utils.py
@@ -1,17 +1,17 @@
 """
-File Name: _wrap.py
+File Name: utils.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_model/_wrap.py
+Relative Path: /InsurAutoML/model/utils.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:15:47 pm
+Last Modified: Tuesday, 29th November 2022 3:04:17 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -46,6 +46,9 @@
 
 
 class BaseClassificationWrapper:
+
+    """Classification model wrapper"""
+
     def __init__(self, estimator: Callable) -> None:
         self.estimator = estimator
 
@@ -83,6 +86,9 @@ def predict_proba(
 
 
 class BaseRegressionWrapper:
+
+    """Regression model wrapper"""
+
     def __init__(self, estimator: Callable) -> None:
         self.estimator = estimator
 
diff --git a/InsurAutoML/_model/_xgboost.py b/InsurAutoML/model/xgboost.py
similarity index 100%
rename from InsurAutoML/_model/_xgboost.py
rename to InsurAutoML/model/xgboost.py
diff --git a/InsurAutoML/_scaling/__init__.py b/InsurAutoML/scaling/__init__.py
similarity index 85%
rename from InsurAutoML/_scaling/__init__.py
rename to InsurAutoML/scaling/__init__.py
index d2f5605..e971178 100644
--- a/InsurAutoML/_scaling/__init__.py
+++ b/InsurAutoML/scaling/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_scaling/__init__.py
+Relative Path: /InsurAutoML/scaling/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:16:18 pm
+Last Modified: Monday, 28th November 2022 11:30:52 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,7 +38,7 @@
 SOFTWARE.
 """
 
-from ._scaling import (
+from .scaling import (
     MinMaxScale,
     Standardize,
     Normalize,
@@ -49,7 +49,7 @@
     Feature_Manipulation,
     Feature_Truncation,
 )
-from InsurAutoML._base import no_processing
+from InsurAutoML.base import no_processing
 
 scalings = {
     "no_processing": no_processing,
@@ -63,3 +63,15 @@
     # "Feature_Manipulation": Feature_Manipulation,
     # "Feature_Truncation": Feature_Truncation,
 }
+
+
+__all__ = [
+    "no_processing",
+    "MinMaxScale",
+    "Standardize",
+    "Normalize",
+    "RobustScale",
+    "PowerTransformer",
+    "QuantileTransformer",
+    "Winsorization",
+]
diff --git a/InsurAutoML/_scaling/_scaling.py b/InsurAutoML/scaling/scaling.py
similarity index 99%
rename from InsurAutoML/_scaling/_scaling.py
rename to InsurAutoML/scaling/scaling.py
index 3a8502f..e1c58f6 100644
--- a/InsurAutoML/_scaling/_scaling.py
+++ b/InsurAutoML/scaling/scaling.py
@@ -1,17 +1,17 @@
 """
-File Name: _scaling.py
+File Name: scaling.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_scaling/_scaling.py
+Relative Path: /InsurAutoML/scaling/scaling.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:16:33 pm
+Last Modified: Monday, 28th November 2022 11:36:38 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -50,7 +50,7 @@
 import scipy
 import scipy.stats
 
-from InsurAutoML._encoding import DataEncoding
+from InsurAutoML.encoding import DataEncoding
 
 
 class NoScaling:
diff --git a/InsurAutoML/_utils/__init__.py b/InsurAutoML/utils/__init__.py
similarity index 75%
rename from InsurAutoML/_utils/__init__.py
rename to InsurAutoML/utils/__init__.py
index 268cedc..939e47b 100644
--- a/InsurAutoML/_utils/__init__.py
+++ b/InsurAutoML/utils/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/__init__.py
+Relative Path: /InsurAutoML/utils/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:17:24 pm
+Last Modified: Thursday, 2nd February 2023 6:36:00 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,38 +38,38 @@
 SOFTWARE.
 """
 
-try:
-    from ._c import (
-        random_guess,
-        random_index,
-        random_list,
-        minloc,
-        maxloc,
-    )
-except ImportError:
-    from ._base import (
-        random_guess,
-        random_index,
-        random_list,
-        minloc,
-        maxloc,
-    )
+# try:
+#     from ._c import (
+#         random_guess,
+#         random_index,
+#         random_list,
+#         minloc,
+#         maxloc,
+#     )
+# except ImportError:
+#     from .base import (
+#         random_guess,
+#         random_index,
+#         random_list,
+#         minloc,
+#         maxloc,
+#     )
 # else:
 #     raise ImportError("Cannot import the C++ and Python extension.")
 
-from ._base import (
-    # random_guess,
-    # random_index,
-    # random_list,
-    # minloc,
-    # maxloc,
+from .base import (
+    random_guess,
+    random_index,
+    random_list,
+    minloc,
+    maxloc,
     is_date,
     feature_rounding,
     True_index,
     type_of_task,
     Timer,
 )
-from ._data import (
+from .data import (
     train_test_split,
     as_dataframe,
     formatting,
@@ -77,9 +77,9 @@
     remove_index_columns,
     get_missing_matrix,
 )
-from ._metadata import MetaData
-from ._file import save_model
-from ._stat import (
+from .metadata import MetaData
+from .file import save_model
+from .stats import (
     nan_cov,
     class_means,
     empirical_covariance,
@@ -96,4 +96,4 @@
 #     text_preprocessing_transformers,
 # )
 
-from ._eda import EDA
+from .eda import EDA
diff --git a/InsurAutoML/_legacy/__init__.py b/InsurAutoML/utils/_c/__init__.py
similarity index 81%
rename from InsurAutoML/_legacy/__init__.py
rename to InsurAutoML/utils/_c/__init__.py
index 5fc157e..b93b8bc 100644
--- a/InsurAutoML/_legacy/__init__.py
+++ b/InsurAutoML/utils/_c/__init__.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_legacy/__init__.py
+Relative Path: /InsurAutoML/_utils/_c/__init__.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:14:15 pm
+Last Modified: Saturday, 19th November 2022 8:06:20 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,8 +38,16 @@
 SOFTWARE.
 """
 
-from InsurAutoML._hpo._legacy import (
-    AutoTabular,
-    AutoTabularClassifier,
-    AutoTabularRegressor,
+
+# import _base
+
+# Note: Nov. 19, 2022
+# need cpdef/pdef for cython to work on python functions
+# cdef for C functions
+from ._base import (
+    random_guess,
+    random_index,
+    random_list,
+    minloc,
+    maxloc,
 )
diff --git a/InsurAutoML/_utils/_c/_base.c b/InsurAutoML/utils/_c/_base.c
similarity index 85%
rename from InsurAutoML/_utils/_c/_base.c
rename to InsurAutoML/utils/_c/_base.c
index 2c50e05..4ea82dc 100644
--- a/InsurAutoML/_utils/_c/_base.c
+++ b/InsurAutoML/utils/_c/_base.c
@@ -964,7 +964,7 @@ typedef struct {
 } __Pyx_BufFmt_Context;
 
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":690
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":690
  * # in Cython to enable them only on the right systems.
  * 
  * ctypedef npy_int8       int8_t             # <<<<<<<<<<<<<<
@@ -973,7 +973,7 @@ typedef struct {
  */
 typedef npy_int8 __pyx_t_5numpy_int8_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":691
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":691
  * 
  * ctypedef npy_int8       int8_t
  * ctypedef npy_int16      int16_t             # <<<<<<<<<<<<<<
@@ -982,7 +982,7 @@ typedef npy_int8 __pyx_t_5numpy_int8_t;
  */
 typedef npy_int16 __pyx_t_5numpy_int16_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":692
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":692
  * ctypedef npy_int8       int8_t
  * ctypedef npy_int16      int16_t
  * ctypedef npy_int32      int32_t             # <<<<<<<<<<<<<<
@@ -991,7 +991,7 @@ typedef npy_int16 __pyx_t_5numpy_int16_t;
  */
 typedef npy_int32 __pyx_t_5numpy_int32_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":693
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":693
  * ctypedef npy_int16      int16_t
  * ctypedef npy_int32      int32_t
  * ctypedef npy_int64      int64_t             # <<<<<<<<<<<<<<
@@ -1000,7 +1000,7 @@ typedef npy_int32 __pyx_t_5numpy_int32_t;
  */
 typedef npy_int64 __pyx_t_5numpy_int64_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":697
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":697
  * #ctypedef npy_int128     int128_t
  * 
  * ctypedef npy_uint8      uint8_t             # <<<<<<<<<<<<<<
@@ -1009,7 +1009,7 @@ typedef npy_int64 __pyx_t_5numpy_int64_t;
  */
 typedef npy_uint8 __pyx_t_5numpy_uint8_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":698
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":698
  * 
  * ctypedef npy_uint8      uint8_t
  * ctypedef npy_uint16     uint16_t             # <<<<<<<<<<<<<<
@@ -1018,7 +1018,7 @@ typedef npy_uint8 __pyx_t_5numpy_uint8_t;
  */
 typedef npy_uint16 __pyx_t_5numpy_uint16_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":699
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":699
  * ctypedef npy_uint8      uint8_t
  * ctypedef npy_uint16     uint16_t
  * ctypedef npy_uint32     uint32_t             # <<<<<<<<<<<<<<
@@ -1027,7 +1027,7 @@ typedef npy_uint16 __pyx_t_5numpy_uint16_t;
  */
 typedef npy_uint32 __pyx_t_5numpy_uint32_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":700
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":700
  * ctypedef npy_uint16     uint16_t
  * ctypedef npy_uint32     uint32_t
  * ctypedef npy_uint64     uint64_t             # <<<<<<<<<<<<<<
@@ -1036,7 +1036,7 @@ typedef npy_uint32 __pyx_t_5numpy_uint32_t;
  */
 typedef npy_uint64 __pyx_t_5numpy_uint64_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":704
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":704
  * #ctypedef npy_uint128    uint128_t
  * 
  * ctypedef npy_float32    float32_t             # <<<<<<<<<<<<<<
@@ -1045,7 +1045,7 @@ typedef npy_uint64 __pyx_t_5numpy_uint64_t;
  */
 typedef npy_float32 __pyx_t_5numpy_float32_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":705
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":705
  * 
  * ctypedef npy_float32    float32_t
  * ctypedef npy_float64    float64_t             # <<<<<<<<<<<<<<
@@ -1054,7 +1054,7 @@ typedef npy_float32 __pyx_t_5numpy_float32_t;
  */
 typedef npy_float64 __pyx_t_5numpy_float64_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":714
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":714
  * # The int types are mapped a bit surprising --
  * # numpy.int corresponds to 'l' and numpy.long to 'q'
  * ctypedef npy_long       int_t             # <<<<<<<<<<<<<<
@@ -1063,7 +1063,7 @@ typedef npy_float64 __pyx_t_5numpy_float64_t;
  */
 typedef npy_long __pyx_t_5numpy_int_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":715
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":715
  * # numpy.int corresponds to 'l' and numpy.long to 'q'
  * ctypedef npy_long       int_t
  * ctypedef npy_longlong   long_t             # <<<<<<<<<<<<<<
@@ -1072,7 +1072,7 @@ typedef npy_long __pyx_t_5numpy_int_t;
  */
 typedef npy_longlong __pyx_t_5numpy_long_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":716
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":716
  * ctypedef npy_long       int_t
  * ctypedef npy_longlong   long_t
  * ctypedef npy_longlong   longlong_t             # <<<<<<<<<<<<<<
@@ -1081,7 +1081,7 @@ typedef npy_longlong __pyx_t_5numpy_long_t;
  */
 typedef npy_longlong __pyx_t_5numpy_longlong_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":718
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":718
  * ctypedef npy_longlong   longlong_t
  * 
  * ctypedef npy_ulong      uint_t             # <<<<<<<<<<<<<<
@@ -1090,7 +1090,7 @@ typedef npy_longlong __pyx_t_5numpy_longlong_t;
  */
 typedef npy_ulong __pyx_t_5numpy_uint_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":719
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":719
  * 
  * ctypedef npy_ulong      uint_t
  * ctypedef npy_ulonglong  ulong_t             # <<<<<<<<<<<<<<
@@ -1099,7 +1099,7 @@ typedef npy_ulong __pyx_t_5numpy_uint_t;
  */
 typedef npy_ulonglong __pyx_t_5numpy_ulong_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":720
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":720
  * ctypedef npy_ulong      uint_t
  * ctypedef npy_ulonglong  ulong_t
  * ctypedef npy_ulonglong  ulonglong_t             # <<<<<<<<<<<<<<
@@ -1108,7 +1108,7 @@ typedef npy_ulonglong __pyx_t_5numpy_ulong_t;
  */
 typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":722
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":722
  * ctypedef npy_ulonglong  ulonglong_t
  * 
  * ctypedef npy_intp       intp_t             # <<<<<<<<<<<<<<
@@ -1117,7 +1117,7 @@ typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t;
  */
 typedef npy_intp __pyx_t_5numpy_intp_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":723
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":723
  * 
  * ctypedef npy_intp       intp_t
  * ctypedef npy_uintp      uintp_t             # <<<<<<<<<<<<<<
@@ -1126,7 +1126,7 @@ typedef npy_intp __pyx_t_5numpy_intp_t;
  */
 typedef npy_uintp __pyx_t_5numpy_uintp_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":725
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":725
  * ctypedef npy_uintp      uintp_t
  * 
  * ctypedef npy_double     float_t             # <<<<<<<<<<<<<<
@@ -1135,7 +1135,7 @@ typedef npy_uintp __pyx_t_5numpy_uintp_t;
  */
 typedef npy_double __pyx_t_5numpy_float_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":726
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":726
  * 
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t             # <<<<<<<<<<<<<<
@@ -1144,7 +1144,7 @@ typedef npy_double __pyx_t_5numpy_float_t;
  */
 typedef npy_double __pyx_t_5numpy_double_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":727
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":727
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t
  * ctypedef npy_longdouble longdouble_t             # <<<<<<<<<<<<<<
@@ -1153,7 +1153,7 @@ typedef npy_double __pyx_t_5numpy_double_t;
  */
 typedef npy_longdouble __pyx_t_5numpy_longdouble_t;
 
-/* "InsurAutoML/_utils/_c/_base.pyx":53
+/* "InsurAutoML/_utils/_c/_base.pyx":51
  * cnp.import_array()
  * 
  * ctypedef cnp.npy_float FLOAT             # <<<<<<<<<<<<<<
@@ -1162,7 +1162,7 @@ typedef npy_longdouble __pyx_t_5numpy_longdouble_t;
  */
 typedef npy_float __pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT;
 
-/* "InsurAutoML/_utils/_c/_base.pyx":54
+/* "InsurAutoML/_utils/_c/_base.pyx":52
  * 
  * ctypedef cnp.npy_float FLOAT
  * ctypedef cnp.npy_intp INTP             # <<<<<<<<<<<<<<
@@ -1197,7 +1197,7 @@ static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(do
 
 /*--- Type declarations ---*/
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":729
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":729
  * ctypedef npy_longdouble longdouble_t
  * 
  * ctypedef npy_cfloat      cfloat_t             # <<<<<<<<<<<<<<
@@ -1206,7 +1206,7 @@ static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(do
  */
 typedef npy_cfloat __pyx_t_5numpy_cfloat_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":730
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":730
  * 
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t             # <<<<<<<<<<<<<<
@@ -1215,7 +1215,7 @@ typedef npy_cfloat __pyx_t_5numpy_cfloat_t;
  */
 typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":731
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":731
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t
  * ctypedef npy_clongdouble clongdouble_t             # <<<<<<<<<<<<<<
@@ -1224,7 +1224,7 @@ typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
  */
 typedef npy_clongdouble __pyx_t_5numpy_clongdouble_t;
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":733
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":733
  * ctypedef npy_clongdouble clongdouble_t
  * 
  * ctypedef npy_cdouble     complex_t             # <<<<<<<<<<<<<<
@@ -1236,22 +1236,22 @@ struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess;
 struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index;
 struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list;
 
-/* "InsurAutoML/_utils/_c/_base.pyx":63
+/* "InsurAutoML/_utils/_c/_base.pyx":61
  * 
  * # set response to [0, 1] class, random guess at 0.5
- * cdef int random_guess(             # <<<<<<<<<<<<<<
+ * cpdef int random_guess(             # <<<<<<<<<<<<<<
  *     float number,
- *     int seed= 1,
+ *     int seed=1,
  */
 struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess {
   int __pyx_n;
   int seed;
 };
 
-/* "InsurAutoML/_utils/_c/_base.pyx":78
+/* "InsurAutoML/_utils/_c/_base.pyx":76
  * # Return random index of a list (unique values only)
  * # from total draw n, default total = n
- * cdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
+ * cpdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
  *     int n,
  *     int total=0,
  */
@@ -1264,8 +1264,8 @@ struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index {
 /* "InsurAutoML/_utils/_c/_base.pyx":103
  * 
  * # Return randomly shuffle of a list (unique values only)
- * cdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  *     int seed=1
  */
 struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list {
@@ -1485,6 +1485,18 @@ static void __Pyx_WriteUnraisable(const char *name, int clineno,
                                   int lineno, const char *filename,
                                   int full_traceback, int nogil);
 
+/* RaiseDoubleKeywords.proto */
+static void __Pyx_RaiseDoubleKeywordsError(const char* func_name, PyObject* kw_name);
+
+/* ParseKeywords.proto */
+static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\
+    PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args,\
+    const char* function_name);
+
+/* RaiseArgTupleInvalid.proto */
+static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact,
+    Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found);
+
 /* ExtTypeTest.proto */
 static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type);
 
@@ -1512,6 +1524,12 @@ static CYTHON_INLINE void __Pyx_SafeReleaseBuffer(Py_buffer* info);
 static Py_ssize_t __Pyx_minusones[] = { -1, -1, -1, -1, -1, -1, -1, -1 };
 static Py_ssize_t __Pyx_zeros[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
 
+/* ArgTypeTest.proto */
+#define __Pyx_ArgTypeTest(obj, type, none_allowed, name, exact)\
+    ((likely((Py_TYPE(obj) == type) | (none_allowed && (obj == Py_None)))) ? 1 :\
+        __Pyx__ArgTypeTest(obj, type, name, exact))
+static int __Pyx__ArgTypeTest(PyObject *obj, PyTypeObject *type, const char *name, int exact);
+
 /* BufferFallbackError.proto */
 static void __Pyx_RaiseBufferFallbackError(void);
 
@@ -1713,12 +1731,12 @@ typedef struct {
     #endif
 #endif
 
-/* CIntToPy.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value);
-
 /* CIntFromPy.proto */
 static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *);
 
+/* CIntToPy.proto */
+static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value);
+
 /* CIntToPy.proto */
 static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value);
 
@@ -1787,6 +1805,11 @@ static PyTypeObject *__pyx_ptype_5numpy_ufunc = 0;
 static CYTHON_INLINE int __pyx_f_5numpy_import_array(void); /*proto*/
 
 /* Module declarations from 'InsurAutoML._utils._c._base' */
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float, int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess *__pyx_optional_args); /*proto*/
+static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int, int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index *__pyx_optional_args); /*proto*/
+static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(PyArrayObject *, int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list *__pyx_optional_args); /*proto*/
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *, int __pyx_skip_dispatch); /*proto*/
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *, int __pyx_skip_dispatch); /*proto*/
 static __Pyx_TypeInfo __Pyx_TypeInfo_nn___pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT = { "FLOAT", NULL, sizeof(__pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT), { 0 }, 0, 'R', 0, 0 };
 #define __Pyx_MODULE_NAME "InsurAutoML._utils._c._base"
 extern int __pyx_module_is_main_InsurAutoML___utils___c___base;
@@ -1795,6 +1818,7 @@ int __pyx_module_is_main_InsurAutoML___utils___c___base = 0;
 /* Implementation of 'InsurAutoML._utils._c._base' */
 static PyObject *__pyx_builtin_ValueError;
 static PyObject *__pyx_builtin_ImportError;
+static const char __pyx_k_n[] = "n";
 static const char __pyx_k_np[] = "np";
 static const char __pyx_k_pd[] = "pd";
 static const char __pyx_k_axis[] = "axis";
@@ -1807,12 +1831,15 @@ static const char __pyx_k_array[] = "array";
 static const char __pyx_k_dtype[] = "dtype";
 static const char __pyx_k_numpy[] = "numpy";
 static const char __pyx_k_shape[] = "shape";
+static const char __pyx_k_total[] = "total";
+static const char __pyx_k_vlist[] = "vlist";
 static const char __pyx_k_zeros[] = "zeros";
 static const char __pyx_k_argmax[] = "argmax";
 static const char __pyx_k_argmin[] = "argmin";
 static const char __pyx_k_choice[] = "choice";
 static const char __pyx_k_format[] = "format";
 static const char __pyx_k_import[] = "__import__";
+static const char __pyx_k_number[] = "number";
 static const char __pyx_k_pandas[] = "pandas";
 static const char __pyx_k_random[] = "random";
 static const char __pyx_k_ndarray[] = "ndarray";
@@ -1824,7 +1851,7 @@ static const char __pyx_k_ImportError[] = "ImportError";
 static const char __pyx_k_permutation[] = "permutation";
 static const char __pyx_k_Invalid_List[] = "Invalid List!";
 static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
-static const char __pyx_k_File_Name__base_pyx_Author_Pany[] = "\nFile Name: _base.pyx\nAuthor: Panyi Dong\nGitHub: https://github.com/PanyiDong/\nMathematics Department, University of Illinois at Urbana-Champaign (UIUC)\n\nProject: InsurAutoML\nLatest Version: 0.2.3\nRelative Path: /InsurAutoML/_utils/_c/_base.pyx\nFile Created: Monday, 24th October 2022 11:56:57 pm\nAuthor: Panyi Dong (panyid2@illinois.edu)\n\n-----\nLast Modified: Monday, 14th November 2022 8:16:52 pm\nModified By: Panyi Dong (panyid2@illinois.edu)\n\n-----\nMIT License\n\nCopyright (c) 2022 - 2022, Panyi Dong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n";
+static const char __pyx_k_File_Name__base_pyx_Author_Pany[] = "\nFile Name: _base.pyx\nAuthor: Panyi Dong\nGitHub: https://github.com/PanyiDong/\nMathematics Department, University of Illinois at Urbana-Champaign (UIUC)\n\nProject: InsurAutoML\nLatest Version: 0.2.3\nRelative Path: /InsurAutoML/_utils/_c/_base.pyx\nFile Created: Monday, 24th October 2022 11:56:57 pm\nAuthor: Panyi Dong (panyid2@illinois.edu)\n\n-----\nLast Modified: Saturday, 19th November 2022 8:03:51 pm\nModified By: Panyi Dong (panyid2@illinois.edu)\n\n-----\nMIT License\n\nCopyright (c) 2022 - 2022, Panyi Dong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n";
 static const char __pyx_k_Total_number_of_samples_must_be[] = "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}";
 static const char __pyx_k_numpy_core_multiarray_failed_to[] = "numpy.core.multiarray failed to import";
 static const char __pyx_k_Input_is_multi_dimensional_array[] = "Input is multi-dimensional array, return min location of the first dimension.";
@@ -1848,9 +1875,11 @@ static PyObject *__pyx_n_s_dtype;
 static PyObject *__pyx_n_s_format;
 static PyObject *__pyx_n_s_import;
 static PyObject *__pyx_n_s_main;
+static PyObject *__pyx_n_s_n;
 static PyObject *__pyx_n_s_name;
 static PyObject *__pyx_n_s_ndarray;
 static PyObject *__pyx_n_s_np;
+static PyObject *__pyx_n_s_number;
 static PyObject *__pyx_n_s_numpy;
 static PyObject *__pyx_kp_s_numpy_core_multiarray_failed_to;
 static PyObject *__pyx_kp_s_numpy_core_umath_failed_to_impor;
@@ -1863,9 +1892,16 @@ static PyObject *__pyx_n_s_replace;
 static PyObject *__pyx_n_s_seed;
 static PyObject *__pyx_n_s_shape;
 static PyObject *__pyx_n_s_test;
+static PyObject *__pyx_n_s_total;
+static PyObject *__pyx_n_s_vlist;
 static PyObject *__pyx_n_s_warn;
 static PyObject *__pyx_n_s_warnings;
 static PyObject *__pyx_n_s_zeros;
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_random_guess(CYTHON_UNUSED PyObject *__pyx_self, float __pyx_v_number, int __pyx_v_seed); /* proto */
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_2random_index(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_n, int __pyx_v_total, int __pyx_v_seed); /* proto */
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_4random_list(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist, int __pyx_v_seed); /* proto */
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_6minloc(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist); /* proto */
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_8maxloc(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist); /* proto */
 static PyObject *__pyx_int_0;
 static PyObject *__pyx_int_2;
 static PyObject *__pyx_tuple_;
@@ -1874,15 +1910,16 @@ static PyObject *__pyx_tuple__3;
 static PyObject *__pyx_tuple__4;
 /* Late includes */
 
-/* "InsurAutoML/_utils/_c/_base.pyx":63
+/* "InsurAutoML/_utils/_c/_base.pyx":61
  * 
  * # set response to [0, 1] class, random guess at 0.5
- * cdef int random_guess(             # <<<<<<<<<<<<<<
+ * cpdef int random_guess(             # <<<<<<<<<<<<<<
  *     float number,
- *     int seed= 1,
+ *     int seed=1,
  */
 
-static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_number, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess *__pyx_optional_args) {
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_1random_guess(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_number, CYTHON_UNUSED int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess *__pyx_optional_args) {
   int __pyx_v_seed = ((int)1);
   int __pyx_r;
   __Pyx_RefNannyDeclarations
@@ -1902,37 +1939,37 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
     }
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":67
- *     int seed= 1,
+  /* "InsurAutoML/_utils/_c/_base.pyx":65
+ *     int seed=1,
  * ):
  *     if seed != None:             # <<<<<<<<<<<<<<
  *         np.random.seed(seed)
  *     if number > 0.5:
  */
-  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 67, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 65, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_2 = PyObject_RichCompare(__pyx_t_1, Py_None, Py_NE); __Pyx_XGOTREF(__pyx_t_2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 67, __pyx_L1_error)
+  __pyx_t_2 = PyObject_RichCompare(__pyx_t_1, Py_None, Py_NE); __Pyx_XGOTREF(__pyx_t_2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 65, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-  __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 67, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 65, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   if (__pyx_t_3) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":68
+    /* "InsurAutoML/_utils/_c/_base.pyx":66
  * ):
  *     if seed != None:
  *         np.random.seed(seed)             # <<<<<<<<<<<<<<
  *     if number > 0.5:
  *         return 1
  */
-    __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 68, __pyx_L1_error)
+    __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 66, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_random); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 68, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_random); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 66, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 68, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 66, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 68, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 66, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
     __pyx_t_5 = NULL;
     if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_1))) {
@@ -1947,13 +1984,13 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
     __pyx_t_2 = (__pyx_t_5) ? __Pyx_PyObject_Call2Args(__pyx_t_1, __pyx_t_5, __pyx_t_4) : __Pyx_PyObject_CallOneArg(__pyx_t_1, __pyx_t_4);
     __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 68, __pyx_L1_error)
+    if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 66, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":67
- *     int seed= 1,
+    /* "InsurAutoML/_utils/_c/_base.pyx":65
+ *     int seed=1,
  * ):
  *     if seed != None:             # <<<<<<<<<<<<<<
  *         np.random.seed(seed)
@@ -1961,7 +1998,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":69
+  /* "InsurAutoML/_utils/_c/_base.pyx":67
  *     if seed != None:
  *         np.random.seed(seed)
  *     if number > 0.5:             # <<<<<<<<<<<<<<
@@ -1971,7 +2008,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
   __pyx_t_3 = ((__pyx_v_number > 0.5) != 0);
   if (__pyx_t_3) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":70
+    /* "InsurAutoML/_utils/_c/_base.pyx":68
  *         np.random.seed(seed)
  *     if number > 0.5:
  *         return 1             # <<<<<<<<<<<<<<
@@ -1981,7 +2018,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
     __pyx_r = 1;
     goto __pyx_L0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":69
+    /* "InsurAutoML/_utils/_c/_base.pyx":67
  *     if seed != None:
  *         np.random.seed(seed)
  *     if number > 0.5:             # <<<<<<<<<<<<<<
@@ -1990,7 +2027,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":71
+  /* "InsurAutoML/_utils/_c/_base.pyx":69
  *     if number > 0.5:
  *         return 1
  *     elif number < 0.5:             # <<<<<<<<<<<<<<
@@ -2000,7 +2037,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
   __pyx_t_3 = ((__pyx_v_number < 0.5) != 0);
   if (__pyx_t_3) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":72
+    /* "InsurAutoML/_utils/_c/_base.pyx":70
  *         return 1
  *     elif number < 0.5:
  *         return 0             # <<<<<<<<<<<<<<
@@ -2010,7 +2047,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
     __pyx_r = 0;
     goto __pyx_L0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":71
+    /* "InsurAutoML/_utils/_c/_base.pyx":69
  *     if number > 0.5:
  *         return 1
  *     elif number < 0.5:             # <<<<<<<<<<<<<<
@@ -2019,7 +2056,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":74
+  /* "InsurAutoML/_utils/_c/_base.pyx":72
  *         return 0
  *     else:
  *         return np.random.randint(0, 2)             # <<<<<<<<<<<<<<
@@ -2027,29 +2064,29 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
  * # Return random index of a list (unique values only)
  */
   /*else*/ {
-    __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 74, __pyx_L1_error)
+    __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 72, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
-    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_random); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 74, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_random); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 72, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_randint); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 74, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_randint); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 72, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 74, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 72, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    __pyx_t_6 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_6 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 74, __pyx_L1_error)
+    __pyx_t_6 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_6 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 72, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     __pyx_r = __pyx_t_6;
     goto __pyx_L0;
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":63
+  /* "InsurAutoML/_utils/_c/_base.pyx":61
  * 
  * # set response to [0, 1] class, random guess at 0.5
- * cdef int random_guess(             # <<<<<<<<<<<<<<
+ * cpdef int random_guess(             # <<<<<<<<<<<<<<
  *     float number,
- *     int seed= 1,
+ *     int seed=1,
  */
 
   /* function exit code */
@@ -2065,15 +2102,118 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(float __pyx_v_n
   return __pyx_r;
 }
 
-/* "InsurAutoML/_utils/_c/_base.pyx":78
+/* Python wrapper */
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_1random_guess(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_1random_guess(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  float __pyx_v_number;
+  int __pyx_v_seed;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("random_guess (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_number,&__pyx_n_s_seed,0};
+    PyObject* values[2] = {0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        CYTHON_FALLTHROUGH;
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_number)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        CYTHON_FALLTHROUGH;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_seed);
+          if (value) { values[1] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "random_guess") < 0)) __PYX_ERR(0, 61, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_number = __pyx_PyFloat_AsFloat(values[0]); if (unlikely((__pyx_v_number == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 62, __pyx_L3_error)
+    if (values[1]) {
+      __pyx_v_seed = __Pyx_PyInt_As_int(values[1]); if (unlikely((__pyx_v_seed == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 63, __pyx_L3_error)
+    } else {
+      __pyx_v_seed = ((int)1);
+    }
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("random_guess", 0, 1, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 61, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_guess", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  __pyx_r = __pyx_pf_11InsurAutoML_6_utils_2_c_5_base_random_guess(__pyx_self, __pyx_v_number, __pyx_v_seed);
+
+  /* function exit code */
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_random_guess(CYTHON_UNUSED PyObject *__pyx_self, float __pyx_v_number, int __pyx_v_seed) {
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  int __pyx_t_1;
+  struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_guess __pyx_t_2;
+  PyObject *__pyx_t_3 = NULL;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  __Pyx_RefNannySetupContext("random_guess", 0);
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_2.__pyx_n = 1;
+  __pyx_t_2.seed = __pyx_v_seed;
+  __pyx_t_1 = __pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_guess(__pyx_v_number, 0, &__pyx_t_2); 
+  __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_t_1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 61, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_3);
+  __pyx_r = __pyx_t_3;
+  __pyx_t_3 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_3);
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_guess", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* "InsurAutoML/_utils/_c/_base.pyx":76
  * # Return random index of a list (unique values only)
  * # from total draw n, default total = n
- * cdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
+ * cpdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
  *     int n,
  *     int total=0,
  */
 
-static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int __pyx_v_n, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index *__pyx_optional_args) {
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_3random_index(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int __pyx_v_n, CYTHON_UNUSED int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index *__pyx_optional_args) {
   int __pyx_v_total = ((int)0);
   int __pyx_v_seed = ((int)1);
   PyArrayObject *__pyx_v_output = 0;
@@ -2101,36 +2241,36 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     }
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":83
+  /* "InsurAutoML/_utils/_c/_base.pyx":81
  *     int seed=1
  * ):
  *     if seed is not None:             # <<<<<<<<<<<<<<
  *         np.random.seed(seed)
  *     if total == 0:
  */
-  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 83, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 81, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_t_2 = (__pyx_t_1 != Py_None);
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_t_3 = (__pyx_t_2 != 0);
   if (__pyx_t_3) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":84
+    /* "InsurAutoML/_utils/_c/_base.pyx":82
  * ):
  *     if seed is not None:
  *         np.random.seed(seed)             # <<<<<<<<<<<<<<
  *     if total == 0:
  *         total = n
  */
-    __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 84, __pyx_L1_error)
+    __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 82, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
-    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_random); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 84, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_random); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 82, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_seed); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 84, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_seed); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 82, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 84, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_seed); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 82, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __pyx_t_6 = NULL;
     if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_4))) {
@@ -2145,12 +2285,12 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     __pyx_t_1 = (__pyx_t_6) ? __Pyx_PyObject_Call2Args(__pyx_t_4, __pyx_t_6, __pyx_t_5) : __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_5);
     __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 84, __pyx_L1_error)
+    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":83
+    /* "InsurAutoML/_utils/_c/_base.pyx":81
  *     int seed=1
  * ):
  *     if seed is not None:             # <<<<<<<<<<<<<<
@@ -2159,7 +2299,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":85
+  /* "InsurAutoML/_utils/_c/_base.pyx":83
  *     if seed is not None:
  *         np.random.seed(seed)
  *     if total == 0:             # <<<<<<<<<<<<<<
@@ -2169,7 +2309,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
   __pyx_t_3 = ((__pyx_v_total == 0) != 0);
   if (__pyx_t_3) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":86
+    /* "InsurAutoML/_utils/_c/_base.pyx":84
  *         np.random.seed(seed)
  *     if total == 0:
  *         total = n             # <<<<<<<<<<<<<<
@@ -2178,7 +2318,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
  */
     __pyx_v_total = __pyx_v_n;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":85
+    /* "InsurAutoML/_utils/_c/_base.pyx":83
  *     if seed is not None:
  *         np.random.seed(seed)
  *     if total == 0:             # <<<<<<<<<<<<<<
@@ -2187,59 +2327,67 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":87
+  /* "InsurAutoML/_utils/_c/_base.pyx":85
  *     if total == 0:
  *         total = n
  *     cdef cnp.ndarray output = np.zeros(total, dtype=int)             # <<<<<<<<<<<<<<
  * 
  *     if total < n:
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 87, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_zeros); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 87, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_zeros); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_total); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 87, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_total); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 87, __pyx_L1_error)
+  __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __Pyx_GIVEREF(__pyx_t_1);
   PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1);
   __pyx_t_1 = 0;
-  __pyx_t_1 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 87, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_dtype, ((PyObject *)(&PyInt_Type))) < 0) __PYX_ERR(0, 87, __pyx_L1_error)
-  __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_5, __pyx_t_1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 87, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_dtype, ((PyObject *)(&PyInt_Type))) < 0) __PYX_ERR(0, 85, __pyx_L1_error)
+  __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_5, __pyx_t_1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 85, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_6);
   __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-  if (!(likely(((__pyx_t_6) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_6, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 87, __pyx_L1_error)
+  if (!(likely(((__pyx_t_6) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_6, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 85, __pyx_L1_error)
   __pyx_v_output = ((PyArrayObject *)__pyx_t_6);
   __pyx_t_6 = 0;
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":89
+  /* "InsurAutoML/_utils/_c/_base.pyx":87
  *     cdef cnp.ndarray output = np.zeros(total, dtype=int)
  * 
  *     if total < n:             # <<<<<<<<<<<<<<
  *         raise ValueError(
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
+ *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(
  */
   __pyx_t_3 = ((__pyx_v_total < __pyx_v_n) != 0);
   if (unlikely(__pyx_t_3)) {
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":91
+    /* "InsurAutoML/_utils/_c/_base.pyx":89
  *     if total < n:
  *         raise ValueError(
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)             # <<<<<<<<<<<<<<
+ *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(             # <<<<<<<<<<<<<<
+ *                 total, n)
  *         )
- *     elif n == 0 :
  */
-    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Total_number_of_samples_must_be, __pyx_n_s_format); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 91, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Total_number_of_samples_must_be, __pyx_n_s_format); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 89, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_total); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 91, __pyx_L1_error)
+
+    /* "InsurAutoML/_utils/_c/_base.pyx":90
+ *         raise ValueError(
+ *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(
+ *                 total, n)             # <<<<<<<<<<<<<<
+ *         )
+ *     elif n == 0:
+ */
+    __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_total); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 90, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
-    __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 91, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 90, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
     __pyx_t_7 = NULL;
     __pyx_t_8 = 0;
@@ -2256,7 +2404,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     #if CYTHON_FAST_PYCALL
     if (PyFunction_Check(__pyx_t_1)) {
       PyObject *__pyx_temp[3] = {__pyx_t_7, __pyx_t_5, __pyx_t_4};
-      __pyx_t_6 = __Pyx_PyFunction_FastCall(__pyx_t_1, __pyx_temp+1-__pyx_t_8, 2+__pyx_t_8); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 91, __pyx_L1_error)
+      __pyx_t_6 = __Pyx_PyFunction_FastCall(__pyx_t_1, __pyx_temp+1-__pyx_t_8, 2+__pyx_t_8); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 89, __pyx_L1_error)
       __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
@@ -2266,7 +2414,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     #if CYTHON_FAST_PYCCALL
     if (__Pyx_PyFastCFunction_Check(__pyx_t_1)) {
       PyObject *__pyx_temp[3] = {__pyx_t_7, __pyx_t_5, __pyx_t_4};
-      __pyx_t_6 = __Pyx_PyCFunction_FastCall(__pyx_t_1, __pyx_temp+1-__pyx_t_8, 2+__pyx_t_8); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 91, __pyx_L1_error)
+      __pyx_t_6 = __Pyx_PyCFunction_FastCall(__pyx_t_1, __pyx_temp+1-__pyx_t_8, 2+__pyx_t_8); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 89, __pyx_L1_error)
       __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
@@ -2274,7 +2422,7 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     } else
     #endif
     {
-      __pyx_t_9 = PyTuple_New(2+__pyx_t_8); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 91, __pyx_L1_error)
+      __pyx_t_9 = PyTuple_New(2+__pyx_t_8); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 89, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
       if (__pyx_t_7) {
         __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_7); __pyx_t_7 = NULL;
@@ -2285,54 +2433,62 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
       PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_8, __pyx_t_4);
       __pyx_t_5 = 0;
       __pyx_t_4 = 0;
-      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 91, __pyx_L1_error)
+      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 89, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":90
+    /* "InsurAutoML/_utils/_c/_base.pyx":88
  * 
  *     if total < n:
  *         raise ValueError(             # <<<<<<<<<<<<<<
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
- *         )
+ *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(
+ *                 total, n)
  */
-    __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_6); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 90, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_6); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 88, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     __Pyx_Raise(__pyx_t_1, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __PYX_ERR(0, 90, __pyx_L1_error)
+    __PYX_ERR(0, 88, __pyx_L1_error)
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":89
+    /* "InsurAutoML/_utils/_c/_base.pyx":87
  *     cdef cnp.ndarray output = np.zeros(total, dtype=int)
  * 
  *     if total < n:             # <<<<<<<<<<<<<<
  *         raise ValueError(
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
+ *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(
  */
   }
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":93
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
+  /* "InsurAutoML/_utils/_c/_base.pyx":92
+ *                 total, n)
  *         )
- *     elif n == 0 :             # <<<<<<<<<<<<<<
+ *     elif n == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError(
- *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(n)
+ *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(
  */
   __pyx_t_3 = ((__pyx_v_n == 0) != 0);
   if (unlikely(__pyx_t_3)) {
 
+    /* "InsurAutoML/_utils/_c/_base.pyx":94
+ *     elif n == 0:
+ *         raise ValueError(
+ *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(             # <<<<<<<<<<<<<<
+ *                 n)
+ *         )
+ */
+    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Number_of_samples_to_be_drawn_mu, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 94, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_6);
+
     /* "InsurAutoML/_utils/_c/_base.pyx":95
- *     elif n == 0 :
  *         raise ValueError(
- *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(n)             # <<<<<<<<<<<<<<
+ *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(
+ *                 n)             # <<<<<<<<<<<<<<
  *         )
  * 
  */
-    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Number_of_samples_to_be_drawn_mu, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 95, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_6);
     __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 95, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_9);
     __pyx_t_4 = NULL;
@@ -2348,30 +2504,30 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
     __pyx_t_1 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_4, __pyx_t_9) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_t_9);
     __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 95, __pyx_L1_error)
+    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 94, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":94
+    /* "InsurAutoML/_utils/_c/_base.pyx":93
  *         )
- *     elif n == 0 :
+ *     elif n == 0:
  *         raise ValueError(             # <<<<<<<<<<<<<<
- *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(n)
- *         )
+ *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(
+ *                 n)
  */
-    __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 94, __pyx_L1_error)
+    __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 93, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     __Pyx_Raise(__pyx_t_6, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-    __PYX_ERR(0, 94, __pyx_L1_error)
+    __PYX_ERR(0, 93, __pyx_L1_error)
 
-    /* "InsurAutoML/_utils/_c/_base.pyx":93
- *             "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
+    /* "InsurAutoML/_utils/_c/_base.pyx":92
+ *                 total, n)
  *         )
- *     elif n == 0 :             # <<<<<<<<<<<<<<
+ *     elif n == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError(
- *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(n)
+ *             "Number of samples to be drawn must be greater than 0. Got n={}.".format(
  */
   }
 
@@ -2426,10 +2582,10 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
   __pyx_r = __pyx_v_output;
   goto __pyx_L0;
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":78
+  /* "InsurAutoML/_utils/_c/_base.pyx":76
  * # Return random index of a list (unique values only)
  * # from total draw n, default total = n
- * cdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
+ * cpdef cnp.ndarray random_index(             # <<<<<<<<<<<<<<
  *     int n,
  *     int total=0,
  */
@@ -2451,15 +2607,133 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(int
   return __pyx_r;
 }
 
+/* Python wrapper */
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_3random_index(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_3random_index(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  int __pyx_v_n;
+  int __pyx_v_total;
+  int __pyx_v_seed;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("random_index (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_n,&__pyx_n_s_total,&__pyx_n_s_seed,0};
+    PyObject* values[3] = {0,0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        CYTHON_FALLTHROUGH;
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        CYTHON_FALLTHROUGH;
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_n)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        CYTHON_FALLTHROUGH;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_total);
+          if (value) { values[1] = value; kw_args--; }
+        }
+        CYTHON_FALLTHROUGH;
+        case  2:
+        if (kw_args > 0) {
+          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_seed);
+          if (value) { values[2] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "random_index") < 0)) __PYX_ERR(0, 76, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        CYTHON_FALLTHROUGH;
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_n = __Pyx_PyInt_As_int(values[0]); if (unlikely((__pyx_v_n == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 77, __pyx_L3_error)
+    if (values[1]) {
+      __pyx_v_total = __Pyx_PyInt_As_int(values[1]); if (unlikely((__pyx_v_total == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 78, __pyx_L3_error)
+    } else {
+      __pyx_v_total = ((int)0);
+    }
+    if (values[2]) {
+      __pyx_v_seed = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_seed == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 79, __pyx_L3_error)
+    } else {
+      __pyx_v_seed = ((int)1);
+    }
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("random_index", 0, 1, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 76, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_index", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  __pyx_r = __pyx_pf_11InsurAutoML_6_utils_2_c_5_base_2random_index(__pyx_self, __pyx_v_n, __pyx_v_total, __pyx_v_seed);
+
+  /* function exit code */
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_2random_index(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_n, int __pyx_v_total, int __pyx_v_seed) {
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_index __pyx_t_2;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  __Pyx_RefNannySetupContext("random_index", 0);
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_2.__pyx_n = 2;
+  __pyx_t_2.total = __pyx_v_total;
+  __pyx_t_2.seed = __pyx_v_seed;
+  __pyx_t_1 = ((PyObject *)__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_index(__pyx_v_n, 0, &__pyx_t_2)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 76, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_index", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
 /* "InsurAutoML/_utils/_c/_base.pyx":103
  * 
  * # Return randomly shuffle of a list (unique values only)
- * cdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  *     int seed=1
  */
 
-static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(PyArrayObject *__pyx_v_vlist, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list *__pyx_optional_args) {
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_5random_list(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(PyArrayObject *__pyx_v_vlist, CYTHON_UNUSED int __pyx_skip_dispatch, struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list *__pyx_optional_args) {
   int __pyx_v_seed = ((int)1);
   PyArrayObject *__pyx_v_output = 0;
   __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
@@ -2631,8 +2905,8 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(PyArr
   /* "InsurAutoML/_utils/_c/_base.pyx":103
  * 
  * # Return randomly shuffle of a list (unique values only)
- * cdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef cnp.ndarray random_list(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  *     int seed=1
  */
 
@@ -2660,15 +2934,141 @@ static PyArrayObject *__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(PyArr
   return __pyx_r;
 }
 
+/* Python wrapper */
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_5random_list(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_5random_list(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyArrayObject *__pyx_v_vlist = 0;
+  int __pyx_v_seed;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("random_list (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_vlist,&__pyx_n_s_seed,0};
+    PyObject* values[2] = {0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        CYTHON_FALLTHROUGH;
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_vlist)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        CYTHON_FALLTHROUGH;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_seed);
+          if (value) { values[1] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "random_list") < 0)) __PYX_ERR(0, 103, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        CYTHON_FALLTHROUGH;
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_vlist = ((PyArrayObject *)values[0]);
+    if (values[1]) {
+      __pyx_v_seed = __Pyx_PyInt_As_int(values[1]); if (unlikely((__pyx_v_seed == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 105, __pyx_L3_error)
+    } else {
+      __pyx_v_seed = ((int)1);
+    }
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("random_list", 0, 1, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 103, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_list", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_vlist), __pyx_ptype_5numpy_ndarray, 1, "vlist", 0))) __PYX_ERR(0, 104, __pyx_L1_error)
+  __pyx_r = __pyx_pf_11InsurAutoML_6_utils_2_c_5_base_4random_list(__pyx_self, __pyx_v_vlist, __pyx_v_seed);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_4random_list(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist, int __pyx_v_seed) {
+  __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
+  __Pyx_Buffer __pyx_pybuffer_vlist;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  struct __pyx_opt_args_11InsurAutoML_6_utils_2_c_5_base_random_list __pyx_t_2;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  __Pyx_RefNannySetupContext("random_list", 0);
+  __pyx_pybuffer_vlist.pybuffer.buf = NULL;
+  __pyx_pybuffer_vlist.refcount = 0;
+  __pyx_pybuffernd_vlist.data = NULL;
+  __pyx_pybuffernd_vlist.rcbuffer = &__pyx_pybuffer_vlist;
+  {
+    __Pyx_BufFmt_StackElem __pyx_stack[1];
+    if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer, (PyObject*)__pyx_v_vlist, &__Pyx_TypeInfo_nn___pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT, PyBUF_FORMAT| PyBUF_C_CONTIGUOUS, 1, 0, __pyx_stack) == -1)) __PYX_ERR(0, 103, __pyx_L1_error)
+  }
+  __pyx_pybuffernd_vlist.diminfo[0].strides = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_vlist.diminfo[0].shape = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.shape[0];
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_2.__pyx_n = 1;
+  __pyx_t_2.seed = __pyx_v_seed;
+  __pyx_t_1 = ((PyObject *)__pyx_f_11InsurAutoML_6_utils_2_c_5_base_random_list(__pyx_v_vlist, 0, &__pyx_t_2)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  { PyObject *__pyx_type, *__pyx_value, *__pyx_tb;
+    __Pyx_PyThreadState_declare
+    __Pyx_PyThreadState_assign
+    __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);
+    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.random_list", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  goto __pyx_L2;
+  __pyx_L0:;
+  __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __pyx_L2:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
 /* "InsurAutoML/_utils/_c/_base.pyx":116
  * 
  * # Return location of minimum values
- * cdef int minloc(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef int minloc(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  * ):
  */
 
-static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_v_vlist) {
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_7minloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist); /*proto*/
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_v_vlist, CYTHON_UNUSED int __pyx_skip_dispatch) {
   __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
   __Pyx_Buffer __pyx_pybuffer_vlist;
   int __pyx_r;
@@ -2781,7 +3181,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError("Invalid List!")
- *     else :
+ *     else:
  */
   __pyx_t_11 = PyObject_Length(((PyObject *)__pyx_v_vlist)); if (unlikely(__pyx_t_11 == ((Py_ssize_t)-1))) __PYX_ERR(0, 124, __pyx_L1_error)
   __pyx_t_4 = ((__pyx_t_11 == 0) != 0);
@@ -2791,7 +3191,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:
  *         raise ValueError("Invalid List!")             # <<<<<<<<<<<<<<
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
  */
     __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 125, __pyx_L1_error)
@@ -2805,14 +3205,14 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError("Invalid List!")
- *     else :
+ *     else:
  */
   }
 
   /* "InsurAutoML/_utils/_c/_base.pyx":128
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:             # <<<<<<<<<<<<<<
+ *         if len((< object > vlist).shape) > 1:             # <<<<<<<<<<<<<<
  *             warnings.warn(
  *                 "Input is multi-dimensional array, return min location of the first dimension."
  */
@@ -2826,7 +3226,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
 
       /* "InsurAutoML/_utils/_c/_base.pyx":129
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:
+ *         if len((< object > vlist).shape) > 1:
  *             warnings.warn(             # <<<<<<<<<<<<<<
  *                 "Input is multi-dimensional array, return min location of the first dimension."
  *             )
@@ -2854,9 +3254,9 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
       __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
       /* "InsurAutoML/_utils/_c/_base.pyx":128
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:             # <<<<<<<<<<<<<<
+ *         if len((< object > vlist).shape) > 1:             # <<<<<<<<<<<<<<
  *             warnings.warn(
  *                 "Input is multi-dimensional array, return min location of the first dimension."
  */
@@ -2896,8 +3296,8 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
   /* "InsurAutoML/_utils/_c/_base.pyx":116
  * 
  * # Return location of minimum values
- * cdef int minloc(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef int minloc(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  * ):
  */
 
@@ -2924,15 +3324,83 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(PyArrayObject *__pyx_
   return __pyx_r;
 }
 
+/* Python wrapper */
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_7minloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist); /*proto*/
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_7minloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist) {
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("minloc (wrapper)", 0);
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_vlist), __pyx_ptype_5numpy_ndarray, 1, "vlist", 0))) __PYX_ERR(0, 117, __pyx_L1_error)
+  __pyx_r = __pyx_pf_11InsurAutoML_6_utils_2_c_5_base_6minloc(__pyx_self, ((PyArrayObject *)__pyx_v_vlist));
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_6minloc(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist) {
+  __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
+  __Pyx_Buffer __pyx_pybuffer_vlist;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  __Pyx_RefNannySetupContext("minloc", 0);
+  __pyx_pybuffer_vlist.pybuffer.buf = NULL;
+  __pyx_pybuffer_vlist.refcount = 0;
+  __pyx_pybuffernd_vlist.data = NULL;
+  __pyx_pybuffernd_vlist.rcbuffer = &__pyx_pybuffer_vlist;
+  {
+    __Pyx_BufFmt_StackElem __pyx_stack[1];
+    if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer, (PyObject*)__pyx_v_vlist, &__Pyx_TypeInfo_nn___pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT, PyBUF_FORMAT| PyBUF_C_CONTIGUOUS, 1, 0, __pyx_stack) == -1)) __PYX_ERR(0, 116, __pyx_L1_error)
+  }
+  __pyx_pybuffernd_vlist.diminfo[0].strides = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_vlist.diminfo[0].shape = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.shape[0];
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_f_11InsurAutoML_6_utils_2_c_5_base_minloc(__pyx_v_vlist, 0)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 116, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  { PyObject *__pyx_type, *__pyx_value, *__pyx_tb;
+    __Pyx_PyThreadState_declare
+    __Pyx_PyThreadState_assign
+    __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);
+    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.minloc", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  goto __pyx_L2;
+  __pyx_L0:;
+  __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __pyx_L2:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
 /* "InsurAutoML/_utils/_c/_base.pyx":137
  * 
  * # Return location of maximum values
- * cdef int maxloc(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef int maxloc(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  * ):
  */
 
-static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_v_vlist) {
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_9maxloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist); /*proto*/
+static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_v_vlist, CYTHON_UNUSED int __pyx_skip_dispatch) {
   __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
   __Pyx_Buffer __pyx_pybuffer_vlist;
   int __pyx_r;
@@ -3045,7 +3513,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError("Invalid List!")
- *     else :
+ *     else:
  */
   __pyx_t_11 = PyObject_Length(((PyObject *)__pyx_v_vlist)); if (unlikely(__pyx_t_11 == ((Py_ssize_t)-1))) __PYX_ERR(0, 145, __pyx_L1_error)
   __pyx_t_4 = ((__pyx_t_11 == 0) != 0);
@@ -3055,7 +3523,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:
  *         raise ValueError("Invalid List!")             # <<<<<<<<<<<<<<
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
  */
     __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 146, __pyx_L1_error)
@@ -3069,14 +3537,14 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
  * 
  *     if len(vlist) == 0:             # <<<<<<<<<<<<<<
  *         raise ValueError("Invalid List!")
- *     else :
+ *     else:
  */
   }
 
   /* "InsurAutoML/_utils/_c/_base.pyx":149
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:             # <<<<<<<<<<<<<<
+ *         if len((< object > vlist).shape) > 1:             # <<<<<<<<<<<<<<
  *             warnings.warn(
  *                 "Input is multi-dimensional array, return max location of the first dimension."
  */
@@ -3090,7 +3558,7 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
 
       /* "InsurAutoML/_utils/_c/_base.pyx":150
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:
+ *         if len((< object > vlist).shape) > 1:
  *             warnings.warn(             # <<<<<<<<<<<<<<
  *                 "Input is multi-dimensional array, return max location of the first dimension."
  *             )
@@ -3118,9 +3586,9 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
       __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
       /* "InsurAutoML/_utils/_c/_base.pyx":149
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
- *         if len((<object> vlist).shape) > 1:             # <<<<<<<<<<<<<<
+ *         if len((< object > vlist).shape) > 1:             # <<<<<<<<<<<<<<
  *             warnings.warn(
  *                 "Input is multi-dimensional array, return max location of the first dimension."
  */
@@ -3158,8 +3626,8 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
   /* "InsurAutoML/_utils/_c/_base.pyx":137
  * 
  * # Return location of maximum values
- * cdef int maxloc(             # <<<<<<<<<<<<<<
- *     cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+ * cpdef int maxloc(             # <<<<<<<<<<<<<<
+ *     cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
  * ):
  */
 
@@ -3186,7 +3654,74 @@ static int __pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(PyArrayObject *__pyx_
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":735
+/* Python wrapper */
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_9maxloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist); /*proto*/
+static PyObject *__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_9maxloc(PyObject *__pyx_self, PyObject *__pyx_v_vlist) {
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("maxloc (wrapper)", 0);
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_vlist), __pyx_ptype_5numpy_ndarray, 1, "vlist", 0))) __PYX_ERR(0, 138, __pyx_L1_error)
+  __pyx_r = __pyx_pf_11InsurAutoML_6_utils_2_c_5_base_8maxloc(__pyx_self, ((PyArrayObject *)__pyx_v_vlist));
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_11InsurAutoML_6_utils_2_c_5_base_8maxloc(CYTHON_UNUSED PyObject *__pyx_self, PyArrayObject *__pyx_v_vlist) {
+  __Pyx_LocalBuf_ND __pyx_pybuffernd_vlist;
+  __Pyx_Buffer __pyx_pybuffer_vlist;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  int __pyx_lineno = 0;
+  const char *__pyx_filename = NULL;
+  int __pyx_clineno = 0;
+  __Pyx_RefNannySetupContext("maxloc", 0);
+  __pyx_pybuffer_vlist.pybuffer.buf = NULL;
+  __pyx_pybuffer_vlist.refcount = 0;
+  __pyx_pybuffernd_vlist.data = NULL;
+  __pyx_pybuffernd_vlist.rcbuffer = &__pyx_pybuffer_vlist;
+  {
+    __Pyx_BufFmt_StackElem __pyx_stack[1];
+    if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer, (PyObject*)__pyx_v_vlist, &__Pyx_TypeInfo_nn___pyx_t_11InsurAutoML_6_utils_2_c_5_base_FLOAT, PyBUF_FORMAT| PyBUF_C_CONTIGUOUS, 1, 0, __pyx_stack) == -1)) __PYX_ERR(0, 137, __pyx_L1_error)
+  }
+  __pyx_pybuffernd_vlist.diminfo[0].strides = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_vlist.diminfo[0].shape = __pyx_pybuffernd_vlist.rcbuffer->pybuffer.shape[0];
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_f_11InsurAutoML_6_utils_2_c_5_base_maxloc(__pyx_v_vlist, 0)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 137, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  { PyObject *__pyx_type, *__pyx_value, *__pyx_tb;
+    __Pyx_PyThreadState_declare
+    __Pyx_PyThreadState_assign
+    __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);
+    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}
+  __Pyx_AddTraceback("InsurAutoML._utils._c._base.maxloc", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  goto __pyx_L2;
+  __pyx_L0:;
+  __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_vlist.rcbuffer->pybuffer);
+  __pyx_L2:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":735
  * ctypedef npy_cdouble     complex_t
  * 
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
@@ -3203,7 +3738,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":736
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":736
  * 
  * cdef inline object PyArray_MultiIterNew1(a):
  *     return PyArray_MultiIterNew(1, <void*>a)             # <<<<<<<<<<<<<<
@@ -3217,7 +3752,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":735
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":735
  * ctypedef npy_cdouble     complex_t
  * 
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
@@ -3236,7 +3771,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":738
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":738
  *     return PyArray_MultiIterNew(1, <void*>a)
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
@@ -3253,7 +3788,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":739
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":739
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)             # <<<<<<<<<<<<<<
@@ -3267,7 +3802,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":738
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":738
  *     return PyArray_MultiIterNew(1, <void*>a)
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
@@ -3286,7 +3821,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":741
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":741
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
@@ -3303,7 +3838,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":742
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":742
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)             # <<<<<<<<<<<<<<
@@ -3317,7 +3852,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":741
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":741
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
@@ -3336,7 +3871,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":744
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":744
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
@@ -3353,7 +3888,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":745
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":745
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)             # <<<<<<<<<<<<<<
@@ -3367,7 +3902,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":744
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":744
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
@@ -3386,7 +3921,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":747
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":747
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
@@ -3403,7 +3938,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":748
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":748
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)             # <<<<<<<<<<<<<<
@@ -3417,7 +3952,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":747
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":747
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
@@ -3436,7 +3971,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":750
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":750
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
@@ -3450,7 +3985,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   int __pyx_t_1;
   __Pyx_RefNannySetupContext("PyDataType_SHAPE", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":751
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":751
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
@@ -3460,7 +3995,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   __pyx_t_1 = (PyDataType_HASSUBARRAY(__pyx_v_d) != 0);
   if (__pyx_t_1) {
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":752
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":752
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):
  *         return <tuple>d.subarray.shape             # <<<<<<<<<<<<<<
@@ -3472,7 +4007,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
     __pyx_r = ((PyObject*)__pyx_v_d->subarray->shape);
     goto __pyx_L0;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":751
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":751
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
@@ -3481,7 +4016,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
  */
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":754
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":754
  *         return <tuple>d.subarray.shape
  *     else:
  *         return ()             # <<<<<<<<<<<<<<
@@ -3495,7 +4030,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
     goto __pyx_L0;
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":750
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":750
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
@@ -3510,7 +4045,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":929
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":931
  *     int _import_umath() except -1
  * 
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
@@ -3522,7 +4057,7 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("set_array_base", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":930
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":932
  * 
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!             # <<<<<<<<<<<<<<
@@ -3531,7 +4066,7 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
  */
   Py_INCREF(__pyx_v_base);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":931
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":933
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!
  *     PyArray_SetBaseObject(arr, base)             # <<<<<<<<<<<<<<
@@ -3540,7 +4075,7 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
  */
   (void)(PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base));
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":929
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":931
  *     int _import_umath() except -1
  * 
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
@@ -3552,7 +4087,7 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
   __Pyx_RefNannyFinishContext();
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":933
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":935
  *     PyArray_SetBaseObject(arr, base)
  * 
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
@@ -3567,7 +4102,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   int __pyx_t_1;
   __Pyx_RefNannySetupContext("get_array_base", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":934
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":936
  * 
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)             # <<<<<<<<<<<<<<
@@ -3576,7 +4111,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  */
   __pyx_v_base = PyArray_BASE(__pyx_v_arr);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":935
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":937
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)
  *     if base is NULL:             # <<<<<<<<<<<<<<
@@ -3586,7 +4121,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   __pyx_t_1 = ((__pyx_v_base == NULL) != 0);
   if (__pyx_t_1) {
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":936
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":938
  *     base = PyArray_BASE(arr)
  *     if base is NULL:
  *         return None             # <<<<<<<<<<<<<<
@@ -3597,7 +4132,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
     __pyx_r = Py_None; __Pyx_INCREF(Py_None);
     goto __pyx_L0;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":935
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":937
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)
  *     if base is NULL:             # <<<<<<<<<<<<<<
@@ -3606,7 +4141,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  */
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":937
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":939
  *     if base is NULL:
  *         return None
  *     return <object>base             # <<<<<<<<<<<<<<
@@ -3618,7 +4153,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   __pyx_r = ((PyObject *)__pyx_v_base);
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":933
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":935
  *     PyArray_SetBaseObject(arr, base)
  * 
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
@@ -3633,7 +4168,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":941
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":943
  * # Versions of the import_* functions which are more suitable for
  * # Cython code.
  * cdef inline int import_array() except -1:             # <<<<<<<<<<<<<<
@@ -3657,7 +4192,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("import_array", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":942
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":944
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3673,16 +4208,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":943
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":945
  * cdef inline int import_array() except -1:
  *     try:
  *         __pyx_import_array()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")
  */
-      __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 943, __pyx_L3_error)
+      __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 945, __pyx_L3_error)
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":942
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":944
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3696,7 +4231,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":944
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":946
  *     try:
  *         __pyx_import_array()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -3706,28 +4241,28 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 944, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 946, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_5);
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_GOTREF(__pyx_t_7);
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":945
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":947
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_umath() except -1:
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 945, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 947, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 945, __pyx_L5_except_error)
+      __PYX_ERR(1, 947, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
     __pyx_L5_except_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":942
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":944
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3742,7 +4277,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":941
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":943
  * # Versions of the import_* functions which are more suitable for
  * # Cython code.
  * cdef inline int import_array() except -1:             # <<<<<<<<<<<<<<
@@ -3765,7 +4300,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":947
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":949
  *         raise ImportError("numpy.core.multiarray failed to import")
  * 
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
@@ -3789,7 +4324,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("import_umath", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":948
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":950
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3805,16 +4340,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":949
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":951
  * cdef inline int import_umath() except -1:
  *     try:
  *         _import_umath()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")
  */
-      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 949, __pyx_L3_error)
+      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 951, __pyx_L3_error)
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":948
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":950
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3828,7 +4363,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":950
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":952
  *     try:
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -3838,28 +4373,28 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 950, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 952, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_5);
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_GOTREF(__pyx_t_7);
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":951
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":953
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_ufunc() except -1:
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 951, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 953, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 951, __pyx_L5_except_error)
+      __PYX_ERR(1, 953, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
     __pyx_L5_except_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":948
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":950
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3874,7 +4409,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":947
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":949
  *         raise ImportError("numpy.core.multiarray failed to import")
  * 
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
@@ -3897,7 +4432,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":953
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":955
  *         raise ImportError("numpy.core.umath failed to import")
  * 
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
@@ -3921,7 +4456,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
   int __pyx_clineno = 0;
   __Pyx_RefNannySetupContext("import_ufunc", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":954
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":956
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3937,16 +4472,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":955
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":957
  * cdef inline int import_ufunc() except -1:
  *     try:
  *         _import_umath()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")
  */
-      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 955, __pyx_L3_error)
+      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 957, __pyx_L3_error)
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":954
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":956
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3960,7 +4495,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":956
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":958
  *     try:
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -3970,28 +4505,28 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 956, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 958, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_5);
       __Pyx_GOTREF(__pyx_t_6);
       __Pyx_GOTREF(__pyx_t_7);
 
-      /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":957
+      /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":959
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef extern from *:
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 957, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 959, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 957, __pyx_L5_except_error)
+      __PYX_ERR(1, 959, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
     __pyx_L5_except_error:;
 
-    /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":954
+    /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":956
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4006,7 +4541,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":953
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":955
  *         raise ImportError("numpy.core.umath failed to import")
  * 
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
@@ -4029,7 +4564,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":967
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":969
  * 
  * 
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4042,7 +4577,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("is_timedelta64_object", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":979
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":981
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyTimedeltaArrType_Type)             # <<<<<<<<<<<<<<
@@ -4052,7 +4587,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyTimedeltaArrType_Type));
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":967
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":969
  * 
  * 
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4066,7 +4601,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":982
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":984
  * 
  * 
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4079,7 +4614,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("is_datetime64_object", 0);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":994
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":996
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyDatetimeArrType_Type)             # <<<<<<<<<<<<<<
@@ -4089,7 +4624,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyDatetimeArrType_Type));
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":982
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":984
  * 
  * 
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4103,7 +4638,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":997
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":999
  * 
  * 
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4114,7 +4649,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
 static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *__pyx_v_obj) {
   npy_datetime __pyx_r;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1004
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1006
  *     also needed.  That can be found using `get_datetime64_unit`.
  *     """
  *     return (<PyDatetimeScalarObject*>obj).obval             # <<<<<<<<<<<<<<
@@ -4124,7 +4659,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
   __pyx_r = ((PyDatetimeScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":997
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":999
  * 
  * 
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4137,7 +4672,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1007
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1009
  * 
  * 
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4148,7 +4683,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
 static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject *__pyx_v_obj) {
   npy_timedelta __pyx_r;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1011
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1013
  *     returns the int64 value underlying scalar numpy timedelta64 object
  *     """
  *     return (<PyTimedeltaScalarObject*>obj).obval             # <<<<<<<<<<<<<<
@@ -4158,7 +4693,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
   __pyx_r = ((PyTimedeltaScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1007
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1009
  * 
  * 
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4171,7 +4706,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
   return __pyx_r;
 }
 
-/* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1014
+/* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1016
  * 
  * 
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4182,7 +4717,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
 static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObject *__pyx_v_obj) {
   NPY_DATETIMEUNIT __pyx_r;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1018
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1020
  *     returns the unit part of the dtype for a numpy datetime64 object.
  *     """
  *     return <NPY_DATETIMEUNIT>(<PyDatetimeScalarObject*>obj).obmeta.base             # <<<<<<<<<<<<<<
@@ -4190,7 +4725,7 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
   __pyx_r = ((NPY_DATETIMEUNIT)((PyDatetimeScalarObject *)__pyx_v_obj)->obmeta.base);
   goto __pyx_L0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1014
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1016
  * 
  * 
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4204,6 +4739,11 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
 }
 
 static PyMethodDef __pyx_methods[] = {
+  {"random_guess", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_1random_guess, METH_VARARGS|METH_KEYWORDS, 0},
+  {"random_index", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_3random_index, METH_VARARGS|METH_KEYWORDS, 0},
+  {"random_list", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_5random_list, METH_VARARGS|METH_KEYWORDS, 0},
+  {"minloc", (PyCFunction)__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_7minloc, METH_O, 0},
+  {"maxloc", (PyCFunction)__pyx_pw_11InsurAutoML_6_utils_2_c_5_base_9maxloc, METH_O, 0},
   {0, 0, 0, 0}
 };
 
@@ -4266,9 +4806,11 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_n_s_format, __pyx_k_format, sizeof(__pyx_k_format), 0, 0, 1, 1},
   {&__pyx_n_s_import, __pyx_k_import, sizeof(__pyx_k_import), 0, 0, 1, 1},
   {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1},
+  {&__pyx_n_s_n, __pyx_k_n, sizeof(__pyx_k_n), 0, 0, 1, 1},
   {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1},
   {&__pyx_n_s_ndarray, __pyx_k_ndarray, sizeof(__pyx_k_ndarray), 0, 0, 1, 1},
   {&__pyx_n_s_np, __pyx_k_np, sizeof(__pyx_k_np), 0, 0, 1, 1},
+  {&__pyx_n_s_number, __pyx_k_number, sizeof(__pyx_k_number), 0, 0, 1, 1},
   {&__pyx_n_s_numpy, __pyx_k_numpy, sizeof(__pyx_k_numpy), 0, 0, 1, 1},
   {&__pyx_kp_s_numpy_core_multiarray_failed_to, __pyx_k_numpy_core_multiarray_failed_to, sizeof(__pyx_k_numpy_core_multiarray_failed_to), 0, 0, 1, 0},
   {&__pyx_kp_s_numpy_core_umath_failed_to_impor, __pyx_k_numpy_core_umath_failed_to_impor, sizeof(__pyx_k_numpy_core_umath_failed_to_impor), 0, 0, 1, 0},
@@ -4281,14 +4823,16 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_n_s_seed, __pyx_k_seed, sizeof(__pyx_k_seed), 0, 0, 1, 1},
   {&__pyx_n_s_shape, __pyx_k_shape, sizeof(__pyx_k_shape), 0, 0, 1, 1},
   {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1},
+  {&__pyx_n_s_total, __pyx_k_total, sizeof(__pyx_k_total), 0, 0, 1, 1},
+  {&__pyx_n_s_vlist, __pyx_k_vlist, sizeof(__pyx_k_vlist), 0, 0, 1, 1},
   {&__pyx_n_s_warn, __pyx_k_warn, sizeof(__pyx_k_warn), 0, 0, 1, 1},
   {&__pyx_n_s_warnings, __pyx_k_warnings, sizeof(__pyx_k_warnings), 0, 0, 1, 1},
   {&__pyx_n_s_zeros, __pyx_k_zeros, sizeof(__pyx_k_zeros), 0, 0, 1, 1},
   {0, 0, 0, 0, 0, 0, 0}
 };
 static CYTHON_SMALL_CODE int __Pyx_InitCachedBuiltins(void) {
-  __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(0, 90, __pyx_L1_error)
-  __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 945, __pyx_L1_error)
+  __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(0, 88, __pyx_L1_error)
+  __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 947, __pyx_L1_error)
   return 0;
   __pyx_L1_error:;
   return -1;
@@ -4298,14 +4842,14 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0);
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":74
+  /* "InsurAutoML/_utils/_c/_base.pyx":72
  *         return 0
  *     else:
  *         return np.random.randint(0, 2)             # <<<<<<<<<<<<<<
  * 
  * # Return random index of a list (unique values only)
  */
-  __pyx_tuple_ = PyTuple_Pack(2, __pyx_int_0, __pyx_int_2); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 74, __pyx_L1_error)
+  __pyx_tuple_ = PyTuple_Pack(2, __pyx_int_0, __pyx_int_2); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 72, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple_);
   __Pyx_GIVEREF(__pyx_tuple_);
 
@@ -4313,32 +4857,32 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  * 
  *     if len(vlist) == 0:
  *         raise ValueError("Invalid List!")             # <<<<<<<<<<<<<<
- *     else :
+ *     else:
  *         # check whether multi-dimensional array
  */
   __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_s_Invalid_List); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(0, 125, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__2);
   __Pyx_GIVEREF(__pyx_tuple__2);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":945
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":947
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_umath() except -1:
  */
-  __pyx_tuple__3 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple__3)) __PYX_ERR(1, 945, __pyx_L1_error)
+  __pyx_tuple__3 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple__3)) __PYX_ERR(1, 947, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__3);
   __Pyx_GIVEREF(__pyx_tuple__3);
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":951
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":953
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_ufunc() except -1:
  */
-  __pyx_tuple__4 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__4)) __PYX_ERR(1, 951, __pyx_L1_error)
+  __pyx_tuple__4 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__4)) __PYX_ERR(1, 953, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__4);
   __Pyx_GIVEREF(__pyx_tuple__4);
   __Pyx_RefNannyFinishContext();
@@ -4674,50 +5218,50 @@ if (!__Pyx_RefNanny) {
   if (__Pyx_patch_abc() < 0) __PYX_ERR(0, 1, __pyx_L1_error)
   #endif
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":44
+  /* "InsurAutoML/_utils/_c/_base.pyx":42
  * 
  * 
  * import warnings             # <<<<<<<<<<<<<<
  * import cython
  * import numpy as np
  */
-  __pyx_t_1 = __Pyx_Import(__pyx_n_s_warnings, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 44, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_Import(__pyx_n_s_warnings, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 42, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_warnings, __pyx_t_1) < 0) __PYX_ERR(0, 44, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_warnings, __pyx_t_1) < 0) __PYX_ERR(0, 42, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":46
+  /* "InsurAutoML/_utils/_c/_base.pyx":44
  * import warnings
  * import cython
  * import numpy as np             # <<<<<<<<<<<<<<
  * import pandas as pd
  * 
  */
-  __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 46, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 44, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_np, __pyx_t_1) < 0) __PYX_ERR(0, 46, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_np, __pyx_t_1) < 0) __PYX_ERR(0, 44, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":47
+  /* "InsurAutoML/_utils/_c/_base.pyx":45
  * import cython
  * import numpy as np
  * import pandas as pd             # <<<<<<<<<<<<<<
  * 
  * cimport numpy as cnp
  */
-  __pyx_t_1 = __Pyx_Import(__pyx_n_s_pandas, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 47, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_Import(__pyx_n_s_pandas, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 45, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_pd, __pyx_t_1) < 0) __PYX_ERR(0, 47, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_pd, __pyx_t_1) < 0) __PYX_ERR(0, 45, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "InsurAutoML/_utils/_c/_base.pyx":51
+  /* "InsurAutoML/_utils/_c/_base.pyx":49
  * cimport numpy as cnp
  * 
  * cnp.import_array()             # <<<<<<<<<<<<<<
  * 
  * ctypedef cnp.npy_float FLOAT
  */
-  __pyx_t_2 = __pyx_f_5numpy_import_array(); if (unlikely(__pyx_t_2 == ((int)-1))) __PYX_ERR(0, 51, __pyx_L1_error)
+  __pyx_t_2 = __pyx_f_5numpy_import_array(); if (unlikely(__pyx_t_2 == ((int)-1))) __PYX_ERR(0, 49, __pyx_L1_error)
 
   /* "InsurAutoML/_utils/_c/_base.pyx":1
  * """             # <<<<<<<<<<<<<<
@@ -4729,7 +5273,7 @@ if (!__Pyx_RefNanny) {
   if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "../../.local/lib/python3.8/site-packages/numpy/__init__.pxd":1014
+  /* "../../anaconda3/envs/pytorch/lib/python3.9/site-packages/numpy/__init__.pxd":1016
  * 
  * 
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -5185,6 +5729,148 @@ static void __Pyx_WriteUnraisable(const char *name, CYTHON_UNUSED int clineno,
 #endif
 }
 
+/* RaiseDoubleKeywords */
+static void __Pyx_RaiseDoubleKeywordsError(
+    const char* func_name,
+    PyObject* kw_name)
+{
+    PyErr_Format(PyExc_TypeError,
+        #if PY_MAJOR_VERSION >= 3
+        "%s() got multiple values for keyword argument '%U'", func_name, kw_name);
+        #else
+        "%s() got multiple values for keyword argument '%s'", func_name,
+        PyString_AsString(kw_name));
+        #endif
+}
+
+/* ParseKeywords */
+static int __Pyx_ParseOptionalKeywords(
+    PyObject *kwds,
+    PyObject **argnames[],
+    PyObject *kwds2,
+    PyObject *values[],
+    Py_ssize_t num_pos_args,
+    const char* function_name)
+{
+    PyObject *key = 0, *value = 0;
+    Py_ssize_t pos = 0;
+    PyObject*** name;
+    PyObject*** first_kw_arg = argnames + num_pos_args;
+    while (PyDict_Next(kwds, &pos, &key, &value)) {
+        name = first_kw_arg;
+        while (*name && (**name != key)) name++;
+        if (*name) {
+            values[name-argnames] = value;
+            continue;
+        }
+        name = first_kw_arg;
+        #if PY_MAJOR_VERSION < 3
+        if (likely(PyString_Check(key))) {
+            while (*name) {
+                if ((CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**name) == PyString_GET_SIZE(key))
+                        && _PyString_Eq(**name, key)) {
+                    values[name-argnames] = value;
+                    break;
+                }
+                name++;
+            }
+            if (*name) continue;
+            else {
+                PyObject*** argname = argnames;
+                while (argname != first_kw_arg) {
+                    if ((**argname == key) || (
+                            (CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**argname) == PyString_GET_SIZE(key))
+                             && _PyString_Eq(**argname, key))) {
+                        goto arg_passed_twice;
+                    }
+                    argname++;
+                }
+            }
+        } else
+        #endif
+        if (likely(PyUnicode_Check(key))) {
+            while (*name) {
+                int cmp = (**name == key) ? 0 :
+                #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3
+                    (__Pyx_PyUnicode_GET_LENGTH(**name) != __Pyx_PyUnicode_GET_LENGTH(key)) ? 1 :
+                #endif
+                    PyUnicode_Compare(**name, key);
+                if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad;
+                if (cmp == 0) {
+                    values[name-argnames] = value;
+                    break;
+                }
+                name++;
+            }
+            if (*name) continue;
+            else {
+                PyObject*** argname = argnames;
+                while (argname != first_kw_arg) {
+                    int cmp = (**argname == key) ? 0 :
+                    #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3
+                        (__Pyx_PyUnicode_GET_LENGTH(**argname) != __Pyx_PyUnicode_GET_LENGTH(key)) ? 1 :
+                    #endif
+                        PyUnicode_Compare(**argname, key);
+                    if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad;
+                    if (cmp == 0) goto arg_passed_twice;
+                    argname++;
+                }
+            }
+        } else
+            goto invalid_keyword_type;
+        if (kwds2) {
+            if (unlikely(PyDict_SetItem(kwds2, key, value))) goto bad;
+        } else {
+            goto invalid_keyword;
+        }
+    }
+    return 0;
+arg_passed_twice:
+    __Pyx_RaiseDoubleKeywordsError(function_name, key);
+    goto bad;
+invalid_keyword_type:
+    PyErr_Format(PyExc_TypeError,
+        "%.200s() keywords must be strings", function_name);
+    goto bad;
+invalid_keyword:
+    PyErr_Format(PyExc_TypeError,
+    #if PY_MAJOR_VERSION < 3
+        "%.200s() got an unexpected keyword argument '%.200s'",
+        function_name, PyString_AsString(key));
+    #else
+        "%s() got an unexpected keyword argument '%U'",
+        function_name, key);
+    #endif
+bad:
+    return -1;
+}
+
+/* RaiseArgTupleInvalid */
+static void __Pyx_RaiseArgtupleInvalid(
+    const char* func_name,
+    int exact,
+    Py_ssize_t num_min,
+    Py_ssize_t num_max,
+    Py_ssize_t num_found)
+{
+    Py_ssize_t num_expected;
+    const char *more_or_less;
+    if (num_found < num_min) {
+        num_expected = num_min;
+        more_or_less = "at least";
+    } else {
+        num_expected = num_max;
+        more_or_less = "at most";
+    }
+    if (exact) {
+        more_or_less = "exactly";
+    }
+    PyErr_Format(PyExc_TypeError,
+                 "%.200s() takes %.8s %" CYTHON_FORMAT_SSIZE_T "d positional argument%.1s (%" CYTHON_FORMAT_SSIZE_T "d given)",
+                 func_name, more_or_less, num_expected,
+                 (num_expected == 1) ? "" : "s", num_found);
+}
+
 /* ExtTypeTest */
 static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) {
     if (unlikely(!type)) {
@@ -5917,6 +6603,27 @@ fail:;
   return -1;
 }
 
+/* ArgTypeTest */
+  static int __Pyx__ArgTypeTest(PyObject *obj, PyTypeObject *type, const char *name, int exact)
+{
+    if (unlikely(!type)) {
+        PyErr_SetString(PyExc_SystemError, "Missing type object");
+        return 0;
+    }
+    else if (exact) {
+        #if PY_MAJOR_VERSION == 2
+        if ((type == &PyBaseString_Type) && likely(__Pyx_PyBaseString_CheckExact(obj))) return 1;
+        #endif
+    }
+    else {
+        if (likely(__Pyx_TypeCheck(obj, type))) return 1;
+    }
+    PyErr_Format(PyExc_TypeError,
+        "Argument '%.200s' has incorrect type (expected %.200s, got %.200s)",
+        name, type->tp_name, Py_TYPE(obj)->tp_name);
+    return 0;
+}
+
 /* BufferFallbackError */
   static void __Pyx_RaiseBufferFallbackError(void) {
   PyErr_SetString(PyExc_ValueError,
@@ -6767,44 +7474,6 @@ static void __Pyx_ReleaseBuffer(Py_buffer *view) {
     #endif
 #endif
 
-/* CIntToPy */
-  static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) {
-#ifdef __Pyx_HAS_GCC_DIAGNOSTIC
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#endif
-    const int neg_one = (int) -1, const_zero = (int) 0;
-#ifdef __Pyx_HAS_GCC_DIAGNOSTIC
-#pragma GCC diagnostic pop
-#endif
-    const int is_unsigned = neg_one > const_zero;
-    if (is_unsigned) {
-        if (sizeof(int) < sizeof(long)) {
-            return PyInt_FromLong((long) value);
-        } else if (sizeof(int) <= sizeof(unsigned long)) {
-            return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
-        } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
-            return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
-        }
-    } else {
-        if (sizeof(int) <= sizeof(long)) {
-            return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
-        } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
-            return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
-        }
-    }
-    {
-        int one = 1; int little = (int)*(unsigned char *)&one;
-        unsigned char *bytes = (unsigned char *)&value;
-        return _PyLong_FromByteArray(bytes, sizeof(int),
-                                     little, !is_unsigned);
-    }
-}
-
 /* CIntFromPy */
   static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
 #ifdef __Pyx_HAS_GCC_DIAGNOSTIC
@@ -7001,6 +7670,44 @@ static void __Pyx_ReleaseBuffer(Py_buffer *view) {
     return (int) -1;
 }
 
+/* CIntToPy */
+  static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) {
+#ifdef __Pyx_HAS_GCC_DIAGNOSTIC
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+    const int neg_one = (int) -1, const_zero = (int) 0;
+#ifdef __Pyx_HAS_GCC_DIAGNOSTIC
+#pragma GCC diagnostic pop
+#endif
+    const int is_unsigned = neg_one > const_zero;
+    if (is_unsigned) {
+        if (sizeof(int) < sizeof(long)) {
+            return PyInt_FromLong((long) value);
+        } else if (sizeof(int) <= sizeof(unsigned long)) {
+            return PyLong_FromUnsignedLong((unsigned long) value);
+#ifdef HAVE_LONG_LONG
+        } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
+            return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
+#endif
+        }
+    } else {
+        if (sizeof(int) <= sizeof(long)) {
+            return PyInt_FromLong((long) value);
+#ifdef HAVE_LONG_LONG
+        } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
+            return PyLong_FromLongLong((PY_LONG_LONG) value);
+#endif
+        }
+    }
+    {
+        int one = 1; int little = (int)*(unsigned char *)&one;
+        unsigned char *bytes = (unsigned char *)&value;
+        return _PyLong_FromByteArray(bytes, sizeof(int),
+                                     little, !is_unsigned);
+    }
+}
+
 /* CIntToPy */
   static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
 #ifdef __Pyx_HAS_GCC_DIAGNOSTIC
diff --git a/InsurAutoML/_utils/_c/_base.pyx b/InsurAutoML/utils/_c/_base.pyx
similarity index 84%
rename from InsurAutoML/_utils/_c/_base.pyx
rename to InsurAutoML/utils/_c/_base.pyx
index 47cdfb9..095fe4d 100644
--- a/InsurAutoML/_utils/_c/_base.pyx
+++ b/InsurAutoML/utils/_c/_base.pyx
@@ -11,7 +11,7 @@ File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:16:52 pm
+Last Modified: Saturday, 19th November 2022 8:03:51 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,8 +39,6 @@ SOFTWARE.
 """
 
 
-
-
 import warnings
 import cython
 import numpy as np
@@ -60,10 +58,10 @@ ctypedef cnp.npy_intp INTP
 # An object to be used in Python
 
 # set response to [0, 1] class, random guess at 0.5
-cdef int random_guess(
-    float number, 
-    int seed= 1,
-):    
+cpdef int random_guess(
+    float number,
+    int seed=1,
+):
     if seed != None:
         np.random.seed(seed)
     if number > 0.5:
@@ -72,12 +70,12 @@ cdef int random_guess(
         return 0
     else:
         return np.random.randint(0, 2)
-    
+
 # Return random index of a list (unique values only)
 # from total draw n, default total = n
-cdef cnp.ndarray random_index(
-    int n, 
-    int total=0, 
+cpdef cnp.ndarray random_index(
+    int n,
+    int total=0,
     int seed=1
 ):
     if seed is not None:
@@ -85,70 +83,72 @@ cdef cnp.ndarray random_index(
     if total == 0:
         total = n
     cdef cnp.ndarray output = np.zeros(total, dtype=int)
-    
+
     if total < n:
         raise ValueError(
-            "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(total, n)
+            "Total number of samples must be greater than or equal to the number of samples to be drawn. Got total={}, n={}".format(
+                total, n)
         )
-    elif n == 0 :
+    elif n == 0:
         raise ValueError(
-            "Number of samples to be drawn must be greater than 0. Got n={}.".format(n)
+            "Number of samples to be drawn must be greater than 0. Got n={}.".format(
+                n)
         )
-    
+
     output = np.random.choice(total, n, replace=False)
-    
+
     return output
 
 # Return randomly shuffle of a list (unique values only)
-cdef cnp.ndarray random_list(
-    cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist, 
+cpdef cnp.ndarray random_list(
+    cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
     int seed=1
 ):
     if seed != None:
         np.random.seed(seed)
-    
+
     cdef cnp.ndarray output = np.zeros(len(vlist), dtype=float)
     output = np.random.permutation(vlist)
 
     return output
 
 # Return location of minimum values
-cdef int minloc(
-    cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,
+cpdef int minloc(
+    cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
 ):
-    
+
     # make sure input is np.array
     if not isinstance(vlist, np.ndarray):
         vlist = np.array(vlist)
-        
+
     if len(vlist) == 0:
         raise ValueError("Invalid List!")
-    else :
+    else:
         # check whether multi-dimensional array
-        if len((<object> vlist).shape) > 1:
+        if len((< object > vlist).shape) > 1:
             warnings.warn(
                 "Input is multi-dimensional array, return min location of the first dimension."
             )
-        
+
         return np.argmin(vlist, axis=0)
 
 
 # Return location of maximum values
-cdef int maxloc(
-    cnp.ndarray[FLOAT, ndim = 1, mode = "c"] vlist,                         
+cpdef int maxloc(
+    cnp.ndarray[FLOAT, ndim=1, mode="c"] vlist,
 ):
-    
+
     # make sure input is np.array
     if not isinstance(vlist, np.ndarray):
         vlist = np.array(vlist)
-        
+
     if len(vlist) == 0:
         raise ValueError("Invalid List!")
-    else :
+    else:
         # check whether multi-dimensional array
-        if len((<object> vlist).shape) > 1:
+        if len((< object > vlist).shape) > 1:
             warnings.warn(
                 "Input is multi-dimensional array, return max location of the first dimension."
             )
-        
-        return np.argmax(vlist, axis=0)
\ No newline at end of file
+
+        return np.argmax(vlist, axis=0)
diff --git a/InsurAutoML/_utils/_c/_stat.c b/InsurAutoML/utils/_c/_stat.c
similarity index 100%
rename from InsurAutoML/_utils/_c/_stat.c
rename to InsurAutoML/utils/_c/_stat.c
diff --git a/InsurAutoML/_utils/_c/_stat.pyx b/InsurAutoML/utils/_c/_stat.pyx
similarity index 100%
rename from InsurAutoML/_utils/_c/_stat.pyx
rename to InsurAutoML/utils/_c/_stat.pyx
diff --git a/InsurAutoML/_utils/_base.py b/InsurAutoML/utils/base.py
similarity index 98%
rename from InsurAutoML/_utils/_base.py
rename to InsurAutoML/utils/base.py
index d9a7f83..b19352c 100644
--- a/InsurAutoML/_utils/_base.py
+++ b/InsurAutoML/utils/base.py
@@ -1,17 +1,17 @@
 """
-File Name: _base.py
+File Name: base.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_base.py
+Relative Path: /InsurAutoML/utils/base.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 9:09:36 pm
+Last Modified: Monday, 28th November 2022 11:37:55 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -46,7 +46,7 @@
 import numpy as np
 import pandas as pd
 from dateutil.parser import parse
-from InsurAutoML._constant import UNI_CLASS
+from InsurAutoML.constant import UNI_CLASS
 
 # set response to [0, 1] class, random guess at 0.5
 
diff --git a/InsurAutoML/_utils/_data.py b/InsurAutoML/utils/data.py
similarity index 99%
rename from InsurAutoML/_utils/_data.py
rename to InsurAutoML/utils/data.py
index f27495c..2d77d07 100644
--- a/InsurAutoML/_utils/_data.py
+++ b/InsurAutoML/utils/data.py
@@ -1,17 +1,17 @@
 """
-File Name: _data.py
+File Name: data.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_data.py
+Relative Path: /InsurAutoML/utils/data.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 9:23:07 pm
+Last Modified: Tuesday, 29th November 2022 3:14:14 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -46,8 +46,8 @@
 import numpy as np
 import pandas as pd
 
-from ._base import random_index
-from InsurAutoML._constant import UNI_CLASS
+from .base import random_index
+from InsurAutoML.constant import UNI_CLASS
 
 # string list to list
 
diff --git a/InsurAutoML/_utils/_eda.py b/InsurAutoML/utils/eda.py
similarity index 98%
rename from InsurAutoML/_utils/_eda.py
rename to InsurAutoML/utils/eda.py
index 85efdf9..6a7c3b7 100644
--- a/InsurAutoML/_utils/_eda.py
+++ b/InsurAutoML/utils/eda.py
@@ -1,17 +1,17 @@
 """
-File Name: _eda.py
+File Name: eda.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_eda.py
+Relative Path: /InsurAutoML/utils/eda.py
 File Created: Monday, 24th October 2022 11:56:57 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 9:24:20 pm
+Last Modified: Monday, 28th November 2022 11:41:22 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -48,7 +48,7 @@
 import seaborn as sns
 import matplotlib.pyplot as plt
 
-from InsurAutoML._utils._data import feature_type
+from InsurAutoML.utils.data import feature_type
 
 
 def EDA(
diff --git a/InsurAutoML/_utils/_file.py b/InsurAutoML/utils/file.py
similarity index 100%
rename from InsurAutoML/_utils/_file.py
rename to InsurAutoML/utils/file.py
diff --git a/InsurAutoML/_utils/_metadata.py b/InsurAutoML/utils/metadata.py
similarity index 65%
rename from InsurAutoML/_utils/_metadata.py
rename to InsurAutoML/utils/metadata.py
index 1723291..01aff12 100644
--- a/InsurAutoML/_utils/_metadata.py
+++ b/InsurAutoML/utils/metadata.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_metadata.py
+Relative Path: /InsurAutoML/utils/metadata.py
 File: _metadata.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 6:11:20 pm
+Last Modified: Tuesday, 6th December 2022 11:27:20 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,8 +45,10 @@
 import pandas as pd
 import warnings
 from typing import Any, Dict, Tuple, List
+from itertools import compress
 
-from InsurAutoML._constant import UNI_CLASS, UNIQUE_FULLTYPE
+from InsurAutoML.constant import UNI_CLASS, UNIQUE_FULLTYPE
+from .stats import merge_mean, merge_std, merge_dict
 
 # get subtype of int
 
@@ -86,32 +88,66 @@ def get(self, data: pd.Series) -> Dict[Tuple, List] or dict:
         elif self.type in ["Datetime", "Path"] or self.subtype == "Text":
             return {}
 
+    def merge(self, data: pd.Series) -> Dict[Tuple, List] or dict:
+        if self.type == "Float" or self.subtype == "Numerical":
+            return self._merge_details_numerical(data)
+        elif self.subtype == "Categorical":
+            return self._merge_details_categorical(data)
+        elif self.type in ["Datetime", "Path"] or self.subtype == "Text":
+            return {}
+
     # get details of numerical data
     @staticmethod
     def _get_details_numerical(data: pd.Series) -> Dict[str, np.ndarray]:
 
         return {
+            "length": len(data),
             "mean": np.mean(data),
             "std": np.std(data),
             "min": np.min(data),
             "max": np.max(data),
         }
 
+    @staticmethod
+    def _merge_details_numerical(data: pd.Series, dict: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
+        # get length of two data
+        _old_len, _new_len = dict["length"], len(data)
+        # get mean of two data
+        _old_mean, _new_mean = dict["mean"], np.mean(data)
+        # get std of two data
+        _old_std, _new_std = dict["std"], np.std(data)
+        return {
+            "length": _old_len + _new_len,
+            "mean": merge_mean((_old_len, _old_mean), (_new_len, _new_mean)),
+            "std": merge_std((_old_len, _old_mean, _old_std), (_new_len, _new_mean, _new_std)),
+            "min": np.min(dict["min"], np.min(data)),
+            "max": np.max(dict["max"], np.max(data)),
+        }
+
     # get details of categorical data
     @staticmethod
     def _get_details_categorical(data: pd.Series) -> Dict[str, dict]:
         return {
-            "unique_cout": {
-                key: value for key,
-                value in zip(
-                    *
-                    np.unique(
-                        data,
-                        return_counts=True))}}
+            "unique_count": {
+                key: value for key, value in zip(*np.unique(data, return_counts=True))
+            }}
+
+    @staticmethod
+    def _merge_details_categorical(data: pd.Series, dict: Dict[str, dict]) -> Dict[str, dict]:
+        return {
+            "unique_count": merge_dict(
+                dict["unique_count"], {key: value for key, value in zip(
+                    *np.unique(data, return_counts=True))}
+            )
+        }
 
 
 class MetaData:
 
+    """
+    MetaData class is used to store the metadata and details of a dataset.
+    """
+
     _unique_fulltype = UNIQUE_FULLTYPE
 
     def __init__(self, data: Any = None) -> None:
@@ -132,6 +168,17 @@ def __str__(self) -> str:
 
         return str(self.metadata)
 
+    # find the key of the name in metadata
+    @staticmethod
+    def _get_key(value, metadata):
+        _key = list(compress(list(metadata.keys()), [
+                    value in item for item in metadata.values()]))
+
+        if len(_key) != 1:
+            raise ValueError("Expected 1 key, got {}.".format(len(_key)))
+
+        return _key[0]
+
     def _check_metadata(self):
         if not hasattr(self, "metadata"):
             raise AttributeError(
@@ -156,14 +203,13 @@ def update(
             names = data.columns
 
         for name in names:
+            # find the key of the name in metadata
+            _key = self._get_key(name, self.metadata)
             # remove registered metadata
-            for key in self.metadata.keys():
-                if name in self.metadata[key]:
-                    self.metadata[key].remove(name)
-                    # if no column has this metadata, remove the metadata
-                    if len(self.metadata[key]) == 0:
-                        del self.metadata[key]
-                    break
+            self.metadata[_key].remove(name)
+            # if no column has this metadata, remove the metadata
+            if len(self.metadata[_key]) == 0:
+                del self.metadata[_key]
             # no need to remove registered details
             # since it's updated per column
 
@@ -178,6 +224,36 @@ def update(
 
         return self.metadata
 
+    def merge(self, data: pd.DataFrame, names: List[str] = None) -> Dict[Tuple, List] or dict:
+        # check whether metadata generated
+        self._check_metadata()
+
+        # if names not provided, update all columns
+        if names is None:
+            names = data.columns
+
+        for name in names:
+            # find the key of the name in metadata
+            _key = self._get_key(name, self.metadata)
+            # check stored type and subtype
+            _type, _subtype = _key
+
+            # get the full type
+            type, subtype = self._get_fulltype(data[name])
+            # if the type/subtype is different, raise error
+            if _type != type or _subtype != subtype:
+                raise TypeError(
+                    "Type/subtype mismatch. For feature {}, expected type/subtype is {}, but got {}.".format(name, _key, (type, subtype)))
+
+            # get the details
+            details = get_details(type, subtype).merge(
+                data[name], self.details[_key])
+
+            # register the metadata
+            self.register(name, (type, subtype), details)
+
+        return self.metadata
+
     def force_update(
         self, names: List[str] or str, fulltypes: List[Tuple[str, str]] or str
     ) -> Dict[Tuple, List] or dict:
@@ -186,11 +262,9 @@ def force_update(
         self._check_metadata()
 
         # if names is a string, convert it to a list
-        if isinstance(names, str):
-            names = [names]
+        names = [names] if isinstance(names, str) else names
         # if fulltypes is a string, convert it to a list
-        if isinstance(fulltypes, str):
-            fulltypes = [fulltypes]
+        fulltypes = [fulltypes] if isinstance(fulltypes, str) else fulltypes
 
         # check length of names and fulltypes
         if len(names) != len(fulltypes):
@@ -199,14 +273,13 @@ def force_update(
                     len(names), len(fulltypes)))
 
         for name, fulltype in zip(names, fulltypes):
+            # find the key of the name in metadata
+            _key = self._get_key(name, self.metadata)
             # remove registered metadata
-            for key in self.metadata.keys():
-                if name in self.metadata[key]:
-                    self.metadata[key].remove(name)
-                    # if no column has this metadata, remove the metadata
-                    if len(self.metadata[key]) == 0:
-                        del self.metadata[key]
-                    break
+            self.metadata[_key].remove(name)
+            # if no column has this metadata, remove the metadata
+            if len(self.metadata[_key]) == 0:
+                del self.metadata[_key]
             # no need to remove registered details
             # since it's updated per column
 
@@ -232,6 +305,7 @@ def register(
 
         # register the details
         if details is not None:
+            # details will be overwritten if name already exists
             self.details[name] = details
         else:
             self.details[name] = {}
@@ -332,3 +406,23 @@ def get_from_df(self, data: pd.DataFrame) -> Dict[Tuple, List] or dict:
             self._check_fulltype(self.metadata, ref=self._unique_fulltype)
 
         return self.metadata
+
+
+class ChunkMetaData(MetaData):
+
+    """
+    Metadata for chunk data to deal with very large dataset.
+    """
+
+    def __init__(self, path: str, chunksize: int = 10000, **kwargs) -> None:
+        super().__init__()
+
+        # process the file in chunk fashion
+        with pd.read_csv(path, chunksize=chunksize) as reader:
+            for idx, chunk in enumerate(reader):
+                # at first, initialize the metadata
+                if idx == 0:
+                    self.get(chunk)
+                # after first, merge the metadata
+                else:
+                    self.merge(chunk)
diff --git a/InsurAutoML/_utils/_optimize.py b/InsurAutoML/utils/optimize.py
similarity index 98%
rename from InsurAutoML/_utils/_optimize.py
rename to InsurAutoML/utils/optimize.py
index 76232cd..12d6c9f 100644
--- a/InsurAutoML/_utils/_optimize.py
+++ b/InsurAutoML/utils/optimize.py
@@ -6,12 +6,12 @@
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /InsurAutoML/_utils/_optimize.py
+Relative Path: /InsurAutoML/utils/optimize.py
 File: _optimize.py
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 11:03:27 pm
+Last Modified: Tuesday, 6th December 2022 11:27:25 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -38,10 +38,10 @@
 SOFTWARE.
 """
 
-from InsurAutoML._constant import (
+from InsurAutoML.constant import (
     METHOD_MAPPING
 )
-from InsurAutoML._utils._base import (
+from InsurAutoML.utils.base import (
     has_method,
     distribution_to_suggest,
     DisableLogger,
@@ -157,7 +157,8 @@ def _base_get_hyperparameter_space(
     _feature_selection_hyperparameter = []
     for _feature_selection in [*feature_selection]:
         _feature_selection_hyperparameter.append(
-            feature_selection_hyperparameters[all_feature_selections.index(_feature_selection)]
+            feature_selection_hyperparameters[all_feature_selections.index(
+                _feature_selection)]
         )
 
     # raise error if no encoding hyperparameters are found
@@ -1436,8 +1437,9 @@ def __init__(
         metric_threshold: float = None,
         mode: str = "min",
     ) -> None:
-        self._start = time.time()
-        self._deadline = timeout
+        self._stored_start = None
+        self.timeout = timeout
+        self._budget = timeout
 
         self._metric = metric
         self._mode = mode
@@ -1490,7 +1492,17 @@ def __call__(self, trial_id: str, result: Any) -> bool:
 
     def stop_all(self) -> bool:
 
-        return time.time() - self._start > self._deadline
+        if self._stored_start:
+            self._budget -= (time.time() - self._stored_start)
+
+        self._stored_start = time.time()
+
+        if self._budget < 0:
+            logger.info(
+                "TimePlateauStopper: Time limit {} seconds reached.".format(self.timeout))
+            return True
+
+        return False
 
 
 # get estimator based on string or class
@@ -1545,7 +1557,7 @@ def get_estimator(estimator_str: str) -> Callable:
 def get_metrics(metric_str: str) -> Callable:
 
     if metric_str == "neg_accuracy":
-        from InsurAutoML._utils._stat import neg_accuracy
+        from InsurAutoML.utils.stats import neg_accuracy
 
         return neg_accuracy
     elif metric_str == "accuracy":
@@ -1558,7 +1570,7 @@ def get_metrics(metric_str: str) -> Callable:
         # )
         return accuracy_score
     elif metric_str == "neg_precision":
-        from InsurAutoML._utils._stat import neg_precision
+        from InsurAutoML.utils.stats import neg_precision
 
         return neg_precision
     elif metric_str == "precision":
@@ -1571,7 +1583,7 @@ def get_metrics(metric_str: str) -> Callable:
         # )
         return precision_score
     elif metric_str == "neg_auc":
-        from InsurAutoML._utils._stat import neg_auc
+        from InsurAutoML.utils.stats import neg_auc
 
         return neg_auc
     elif metric_str == "auc":
@@ -1582,7 +1594,7 @@ def get_metrics(metric_str: str) -> Callable:
         # warnings.warn("roc_auc_score is not for min mode, please use neg_auc instead.")
         return roc_auc_score
     elif metric_str == "neg_hinge":
-        from InsurAutoML._utils._stat import neg_hinge
+        from InsurAutoML.utils.stats import neg_hinge
 
         return neg_hinge
     elif metric_str == "hinge":
@@ -1593,7 +1605,7 @@ def get_metrics(metric_str: str) -> Callable:
         # warnings.warn("hinge_loss is not for min mode, please use neg_hinge instead.")
         return hinge_loss
     elif metric_str == "neg_f1":
-        from InsurAutoML._utils._stat import neg_f1
+        from InsurAutoML.utils.stats import neg_f1
 
         return neg_f1
     elif metric_str == "f1":
@@ -1615,7 +1627,7 @@ def get_metrics(metric_str: str) -> Callable:
 
         return mean_squared_log_error
     elif metric_str == "neg_R2":
-        from InsurAutoML._utils._stat import neg_R2
+        from InsurAutoML.utils.stats import neg_R2
 
         return neg_R2
     elif metric_str == "R2":
diff --git a/InsurAutoML/_utils/_stat.py b/InsurAutoML/utils/stats.py
similarity index 93%
rename from InsurAutoML/_utils/_stat.py
rename to InsurAutoML/utils/stats.py
index fc4f716..c0db06d 100644
--- a/InsurAutoML/_utils/_stat.py
+++ b/InsurAutoML/utils/stats.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 9:44:56 pm
+Last Modified: Monday, 28th November 2022 4:26:02 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,6 +45,29 @@
 import scipy.stats
 import copy
 
+# merge the mean of two datasets
+
+
+def merge_mean(old, new):
+    (n1, mean1), (n2, mean2) = old, new
+    return (mean1 * n1 + mean2 * n2) / (n1 + n2)
+
+# merge the standard deviation of two datasets
+
+
+def merge_std(old, new):
+    (n1, mean1, std1), (n2, mean2, std2) = old, new
+    return np.sqrt(
+        (std1 ** 2 * n1 + std2 ** 2 * n2) / (n1 + n2) +
+        n1 * n2 * (mean1 - mean2) ** 2 / (n1 + n2) ** 2
+    )
+
+# merge two dict and sum up the values of common keys
+
+
+def merge_dict(dict1, dict2):
+    return {k: dict1.get(k, 0) + dict2.get(k, 0) for k in set(dict1) | set(dict2)}
+
 # return non-nan covariance matrix between X and y, (return covariance of X if y = None)
 # default calculate at columns (axis = 0), axis = 1 at rows
 
@@ -94,7 +117,8 @@ def nan_cov(
     _y_mean = np.nanmean(y, axis=axis)
 
     _cov = np.array(
-        [[0.0 for _i in range(y.shape[1 - axis])] for _j in range(X.shape[1 - axis])]
+        [[0.0 for _i in range(y.shape[1 - axis])]
+         for _j in range(X.shape[1 - axis])]
     )  # initialize covariance matrix
 
     for i in range(_cov.shape[0]):
@@ -184,7 +208,8 @@ def Pearson_Corr(
     features = list(X.columns)
 
     result = [
-        (nan_cov(X[_column], y) / np.sqrt(nan_cov(X[_column]) * nan_cov(y)))[0][0]
+        (nan_cov(X[_column], y) /
+         np.sqrt(nan_cov(X[_column]) * nan_cov(y)))[0][0]
         for _column in features
     ]
     # Oct. 10 decrypted:
@@ -230,7 +255,8 @@ def MI(
         _H_y_X = -sum(
             _pro_val[i]
             * np.log(
-                _pro_val[i] / _X_pro.loc[_X_pro.index == _pro.index[i][0]].values[0]
+                _pro_val[i] / _X_pro.loc[_X_pro.index ==
+                                         _pro.index[i][0]].values[0]
             )
             for i in range(len(_pro))
         )
diff --git a/InsurAutoML/utils/tensor.py b/InsurAutoML/utils/tensor.py
new file mode 100644
index 0000000..044d0cb
--- /dev/null
+++ b/InsurAutoML/utils/tensor.py
@@ -0,0 +1,259 @@
+"""
+File Name: _tensor.py
+Author: Panyi Dong
+GitHub: https://github.com/PanyiDong/
+Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
+
+Project: InsurAutoML
+Latest Version: 0.2.3
+Relative Path: /InsurAutoML/_utils/_tensor.py
+File Created: Monday, 24th October 2022 11:56:57 pm
+Author: Panyi Dong (panyid2@illinois.edu)
+
+-----
+Last Modified: Thursday, 24th November 2022 12:26:40 am
+Modified By: Panyi Dong (panyid2@illinois.edu)
+
+-----
+MIT License
+
+Copyright (c) 2022 - 2022, Panyi Dong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+from typing import Union, Dict, Tuple, List
+import os
+import torch
+from torch.utils.data import Dataset
+# detach tensor from the computation graph
+
+
+def repackage_hidden(h):
+
+    if isinstance(h, torch.Tensor):
+        return h.detach()
+    else:
+        return tuple(repackage_hidden(v) for v in h)
+
+
+class CustomTensorDataset(Dataset):
+
+    """
+    Custom Tensor Dataset
+    """
+
+    def __init__(
+        self,
+        inputs: torch.Tensor,
+        labels: torch.Tensor,
+        format: str = "tuple",
+    ) -> None:
+        self.inputs = inputs
+        self.labels = labels
+        self.format = format
+
+        if len(self.inputs) != len(self.labels):
+            raise ValueError(
+                "inputs and labels must have the same length. Get {} and {}".format(
+                    len(inputs), len(labels)))
+
+    def __len__(self) -> int:
+        return len(self.inputs)
+
+    def __getitem__(self, idx: int) -> Union[Dict, Tuple, List]:
+        if self.format == "dict":
+            return {"input": self.inputs[idx], "label": self.labels[idx]}
+        elif self.format == "tuple":
+            return (self.inputs[idx], self.labels[idx])
+        elif self.format == "list":
+            return [self.inputs[idx], self.labels[idx]]
+
+    @property
+    def inputSize(self) -> int:
+
+        return self.inputs.size()[-1]
+
+    @property
+    def outputSize(self) -> int:
+
+        return len(self.labels.unique())
+
+
+# class BaseTensorDataset(Dataset):
+
+#     """Custom Tensor Dataset that can be used for serialization for tensor Dataset."""
+
+#     def __init__(
+#         self,
+#         inputs: torch.Tensor,
+#         labels: torch.Tensor,
+#         path: str = "tmp",
+#         format: str = "tuple",
+#     ) -> None:
+#         self.path = path
+#         self.format = format
+
+#         if len(inputs) != len(labels):
+#             raise ValueError(
+#                 "inputs and labels must have the same length. Get {} and {}".format(
+#                     len(inputs), len(labels)))
+
+#         # get paths and save tensors for inputs
+#         self.input = os.path.join(self.path, "input.pt")
+#         torch.save(inputs, self.input)
+
+#         # get paths and save tensors for labels
+#         self.labels = os.path.join(self.path, "labels.pt")
+#         torch.save(labels, self.labels)
+
+#     def __len__(self) -> int:
+#         return len(torch.load(self.input))
+
+#     def __getitem__(self, idx: int) -> Union[Dict, Tuple, List]:
+#         input, label = torch.load(self.input)[
+#             idx], torch.load(self.labels)[idx]
+
+#         if self.format == "dict":
+#             return {"input": input, "label": label}
+#         elif self.format == "tuple":
+#             return (input, label)
+#         elif self.format == "list":
+#             return [input, label]
+
+
+class ListTensorDataset(Dataset):
+
+    """Custom Tensor Dataset that can be used for serialization for List of tensor Dataset."""
+
+    def __init__(
+        self,
+        inputs: List[torch.Tensor],
+        labels: torch.Tensor,
+        path: str = "tmp",
+        format: str = "tuple",
+    ) -> None:
+        self.path = path
+        self.format = format
+
+        self._input_format = "list"
+        if len(inputs[0]) != len(labels):
+            raise ValueError(
+                "inputs and labels must have the same length. Get {} and {}".format(
+                    len(inputs[0]), len(labels)))
+
+        # make dir for list/dict type
+        if not os.path.exists(os.path.join(path, "input")):
+            os.makedirs(os.path.join(path, "input"))
+
+        # get paths and save tensors for inputs
+        self.input = [os.path.join(
+            self.path, "input/{}.pt".format(i)) for i in range(len(inputs))]
+        for _input, _path in zip(inputs, self.input):
+            torch.save(_input, _path)
+
+        # get paths and save tensors for labels
+        self.labels = os.path.join(self.path, "labels.pt")
+        torch.save(labels, self.labels)
+
+    def __len__(self) -> int:
+        return len(torch.load(self.input[0]))
+
+    def __getitem__(self, idx: int) -> Union[Dict, Tuple, List]:
+        input, label = [torch.load(_input)[idx] for _input in self.input], torch.load(
+            self.labels)[idx]
+
+        if self.format == "dict":
+            return {"input": input, "label": label}
+        elif self.format == "tuple":
+            return (input, label)
+        elif self.format == "list":
+            return [input, label]
+
+
+class DictTensorDataset(Dataset):
+
+    """Custom Tensor Dataset that can be used for serialization for dict of tensor Dataset."""
+
+    def __init__(
+        self,
+        inputs: Dict[str, torch.Tensor],
+        labels: torch.Tensor,
+        path: str = "tmp",
+        format: str = "tuple",
+    ) -> None:
+        self.path = path
+        self.format = format
+
+        self._input_format = "dict"
+        if len(list(inputs.values())[0]) != len(labels):
+            raise ValueError(
+                "inputs and labels must have the same length. Get {} and {}".format(
+                    len(list(inputs.values())[0]), len(labels)))
+
+        # make dir for list/dict type
+        if not os.path.exists(os.path.join(path, "input")):
+            os.makedirs(os.path.join(path, "input"))
+
+        # get paths and save tensors for inputs
+        self.input = [os.path.join(
+            self.path, "input/{}.pt".format(i)) for i in inputs.keys()]
+        for _input, _path in zip(inputs.values(), self.input):
+            torch.save(_input, _path)
+
+        # get paths and save tensors for labels
+        self.labels = os.path.join(self.path, "labels.pt")
+        torch.save(labels, self.labels)
+
+    def __len__(self) -> int:
+        return len(torch.load(self.input[0]))
+
+    def __getitem__(self, idx: int) -> Union[Dict, Tuple, List]:
+        input, label = [torch.load(_input)[idx] for _input in self.input], torch.load(
+            self.labels)[idx]
+
+        if self.format == "dict":
+            return {"input": input, "label": label}
+        elif self.format == "tuple":
+            return (input, label)
+        elif self.format == "list":
+            return [input, label]
+
+
+class SerialTensorDataset(ListTensorDataset, DictTensorDataset):
+
+    """Custom Tensor Dataset that can be used for serialization."""
+
+    def __init__(
+        self,
+        inputs: Union[torch.Tensor, List[torch.Tensor], Dict[str, torch.Tensor]],
+        labels: torch.Tensor,
+        path: str = "tmp",
+        format: str = "tuple",
+    ) -> None:
+
+        if isinstance(inputs, torch.Tensor):
+            super(SerialTensorDataset, self).__init__(
+                [inputs], labels, path, format)
+        elif isinstance(inputs, list):
+            super(SerialTensorDataset, self).__init__(
+                inputs, labels, path, format)
+        elif isinstance(inputs, dict):
+            super(ListTensorDataset, self).__init__(
+                inputs, labels, path, format)
diff --git a/README.md b/README.md
index 889d680..70cb321 100644
--- a/README.md
+++ b/README.md
@@ -127,8 +127,6 @@ model.fit(train_X, train_y)
 model.predict(test_X)
 ```
 
-The legacy version of `AutoTabular`, which is built on top of `HyperOpt`, is still callable by `InsurAutoML._legacy.AutoTabular` but not maintained anymore due the decryption of `autosklearn` methods. The legacy version may be removed in the future.
-
 ~~By default, progress reporter `CLIReporter` is prepared for terminal/command-like report, when using jupyter notebook, call by `AutoTabular(progress_reporter = "JupyterNotebookReporter")` for overwriting previous outputs. ~~ Now, the pipeline can identify whether console terminal/Jupyter Notebook environment is used, don't need to worry about it.
 
 One important issue I find now is that, `ray.tune` does not force to stop running trials but only stop generating new trials. So, if setting `timeout` too low, it's common the fitting does not end in time (long running trials are expected not to finish in short time). However, if the pipeline is used as normal cases, which the `timeout` and `max_evals` both should be set to a rather large number, this case should not be as significant as very short time limit. I think that force trial runtime to stop can be a solution, but with few tryouts, I haven't found anything useful yet.
diff --git a/additional.py b/additional.py
index 57d8741..5581175 100644
--- a/additional.py
+++ b/additional.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 8th November 2022 10:11:48 am
+Last Modified: Monday, 28th November 2022 11:32:31 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -80,12 +80,12 @@
 # Template of self-defined models and hyperparameters
 
 # from ray import tune
-# from InsurAutoML._encoding import DataEncoding
-# from InsurAutoML._imputation import SimpleImputer
-# from InsurAutoML._balancing import SimpleRandomOverSampling
-# from InsurAutoML._scaling import Standardize
-# from InsurAutoML._feature_selection import mRMR
-# from InsurAutoML._model import LinearRegression, LogisticRegression
+# from InsurAutoML.encoding import DataEncoding
+# from InsurAutoML.imputation import SimpleImputer
+# from InsurAutoML.balancing import SimpleRandomOverSampling
+# from InsurAutoML.scaling import Standardize
+# from InsurAutoML.feature_selection import mRMR
+# from InsurAutoML.model import LinearRegression, LogisticRegression
 
 
 # # additional encoders
diff --git a/requirements.txt b/requirements.txt
index d2183bc..c1aece0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,24 +1,28 @@
-setuptools==59.5.0
-rpy2
-seaborn>=0.11.0
-pygam
-scikit-learn>=1.1.0
-nevergrad
-redis
-flaml
-scipy
-ray<2.0.0
-mlflow==1.21.0
-optuna
+tensorboardX
+pandas
 pyarrow
-tqdm==4.62.3
+scipy
+bayesian_optimization==1.4.0
+threadpoolctl>2.2.0
+flaml
+rpy2
+tqdm
 cython
-pandas
+ray[tune]
+pygam
+hyperopt
 lightgbm
-gensim
-tensorboardX
-fastparquet
-hyperopt==0.2.5
+ray<2.0.0
+seaborn>=0.11.0
 xgboost
+optuna
 matplotlib
-numpy
+mlflow
+colorama==0.4.4
+redis
+nevergrad
+scikit-learn>=1.1.0
+fastparquet
+setuptools==59.5.0
+numpy<1.24.0
+gensim
diff --git a/requirements_nn.txt b/requirements_nn.txt
index 7e2b7ff..4a7b191 100644
--- a/requirements_nn.txt
+++ b/requirements_nn.txt
@@ -1,20 +1,22 @@
-setuptools==59.5.0
+tensorboardX
+pandas
+pyarrow
+nni
+scipy
+threadpoolctl>2.2.0
 rpy2
+tqdm
+cython
+ray[tune]
+hyperopt
+ray<2.0.0
 seaborn>=0.11.0
-nni
+matplotlib
+redis
+pytorch_lightning
 scikit-learn>=1.1.0
+fastparquet
+setuptools==59.5.0
+numpy<1.24.0
 torch
-redis
-scipy
-ray<2.0.0
-mlflow==1.21.0
-pyarrow
-tqdm==4.62.3
-cython
-pandas
 gensim
-tensorboardX
-fastparquet
-hyperopt==0.2.5
-matplotlib
-numpy
diff --git a/setup.py b/setup.py
index e40bb4d..4cb40d9 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
 """
-File: setup.py
+File Name: setup.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
@@ -7,11 +7,11 @@
 Project: InsurAutoML
 Latest Version: 0.2.3
 Relative Path: /setup.py
-File: setup.py
+File Created: Wednesday, 16th November 2022 7:39:46 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 8:31:31 am
+Last Modified: Thursday, 2nd February 2023 10:00:46 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -25,8 +25,10 @@
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
+
 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
+
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -36,6 +38,7 @@
 SOFTWARE.
 """
 
+
 from pathlib import Path
 import os
 import sys
@@ -89,8 +92,9 @@ def cythonize(*args, **kwargs):
 # Constant variables
 INSTALL_LIST = [
     "setuptools==59.5.0",
+    "threadpoolctl>2.2.0",  # lower version may cause import error on C codes
     "cython",
-    "numpy",
+    "numpy<1.24.0",
     "pandas",
     "scipy",
     "pyarrow",
@@ -99,13 +103,12 @@ def cythonize(*args, **kwargs):
     "seaborn>=0.11.0",
     "ray<2.0.0",
     "gensim",
-    # "ray[tune]",
+    "ray[tune]",
     # "ray[rllib]",
     "redis;platform_system=='Windows'",
-    "tqdm==4.62.3",
-    "mlflow==1.21.0",
+    "tqdm",
     "tensorboardX",
-    "hyperopt==0.2.5",
+    "hyperopt",
     # "auto-sklearn==0.14.6;platform_system=='Linux'",
     # "scikit-learn==0.24.2;platform_system=='Linux'",
     # "scikit-learn>1.0.0;platform_system=='Windows'",
@@ -115,12 +118,23 @@ def cythonize(*args, **kwargs):
 
 EXTRA_DICT = {
     "normal": [],
+    "legacy": [
+        "tqdm==4.62.3",
+        "mlflow==1.21.0",
+        "tensorboardX",
+        "hyperopt==0.2.5",
+    ],
     "extended": [
         # "rpy2;platform_system=='Linux'",
+        "mlflow",
         "lightgbm",
         "xgboost",
         "pygam",
         "flaml",
+        # UPDATE: Feb. 2, 2023
+        # https://github.com/facebookresearch/nevergrad/issues/1492
+        "bayesian_optimization==1.4.0",
+        "colorama==0.4.4",
         "nevergrad",
         "optuna",
     ],
@@ -128,19 +142,24 @@ def cythonize(*args, **kwargs):
         # "rpy2;platform_system=='Linux'",
         "gensim",
         "torch",
+        "pytorch_lightning",
         "nni",
         # "transformers",
         # "datasets",
     ],
     "dev": [
+        "mlflow",
         "lightgbm",
         "xgboost",
         "pygam",
         "flaml",
+        "bayesian_optimization==1.4.0",
+        "colorama==0.4.4",
         "nevergrad",
         "optuna",
         "gensim",
         "torch",
+        "pytorch_lightning",
         "nni",
     ],
 }


Test patch:
diff --git a/tests/test_balancing/test_balancing.py b/tests/test_balancing/test_balancing.py
index e39e14a..4e2dd35 100644
--- a/tests/test_balancing/test_balancing.py
+++ b/tests/test_balancing/test_balancing.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:21:47 pm
+Last Modified: Monday, 28th November 2022 11:36:24 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -42,7 +42,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._balancing import balancings
+from InsurAutoML.balancing import balancings
 
 data_X = pd.DataFrame(
     np.random.normal(0, 10, (100, 10)),
@@ -72,5 +72,6 @@ def test_Scaling(self):
                 self.assertEqual(
                     mol._fitted,
                     True,
-                    "The method {} is not correctly fitted.".format(method_name),
+                    "The method {} is not correctly fitted.".format(
+                        method_name),
                 )
diff --git a/tests/test_base.py b/tests/test_base.py
index b86218b..e77160c 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:52 pm
+Last Modified: Monday, 28th November 2022 11:36:58 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,7 +41,7 @@
 
 def test_load_data():
 
-    from InsurAutoML._base import load_data
+    from InsurAutoML.base import load_data
     import importlib
 
     rpy2_spec = importlib.util.find_spec("rpy2")
diff --git a/tests/test_datasets/test_datasets.py b/tests/test_datasets/test_datasets.py
index 121ec80..63d78c9 100644
--- a/tests/test_datasets/test_datasets.py
+++ b/tests/test_datasets/test_datasets.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:21:56 pm
+Last Modified: Monday, 28th November 2022 11:35:49 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,7 +44,7 @@
 
 def test_datasets():
 
-    from InsurAutoML._datasets import (
+    from InsurAutoML.datasets import (
         ADULT,  # Classification
         AUTO,
         BREAST,
diff --git a/tests/test_encoder/test_encoder.py b/tests/test_encoder/test_encoder.py
index d4c6e0e..1e30207 100644
--- a/tests/test_encoder/test_encoder.py
+++ b/tests/test_encoder/test_encoder.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:03 pm
+Last Modified: Monday, 28th November 2022 11:35:20 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,7 +41,7 @@
 
 def test_encoder_1():
 
-    from InsurAutoML._encoding import DataEncoding
+    from InsurAutoML.encoding import DataEncoding
     from InsurAutoML import load_data
 
     database = load_data().load("Appendix", "Employee")
@@ -56,7 +56,7 @@ def test_encoder_1():
 
 def test_encoder_2():
 
-    from InsurAutoML._encoding import DataEncoding
+    from InsurAutoML.encoding import DataEncoding
     from InsurAutoML import load_data
 
     database = load_data().load("Appendix", "Employee")
@@ -71,7 +71,7 @@ def test_encoder_2():
 
 def test_encoder_3():
 
-    from InsurAutoML._encoding import DataEncoding
+    from InsurAutoML.encoding import DataEncoding
     from InsurAutoML import load_data
 
     database = load_data().load("Appendix", "Employee")
@@ -86,7 +86,7 @@ def test_encoder_3():
 
 def test_encoder_4():
 
-    from InsurAutoML._encoding import DataEncoding
+    from InsurAutoML.encoding import DataEncoding
     from InsurAutoML import load_data
 
     database = load_data().load("Appendix", "Employee")
diff --git a/tests/test_feature_selection/test_feature_selection.py b/tests/test_feature_selection/test_feature_selection.py
index 495dc4e..cb17292 100644
--- a/tests/test_feature_selection/test_feature_selection.py
+++ b/tests/test_feature_selection/test_feature_selection.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:16 pm
+Last Modified: Thursday, 2nd February 2023 7:31:49 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -40,7 +40,7 @@
 
 import numpy as np
 import pandas as pd
-from InsurAutoML._feature_selection import feature_selections
+from InsurAutoML.feature_selection import feature_selections
 
 
 def test_feature_selection():
@@ -84,7 +84,7 @@ def test_feature_selection():
 
     autosklearn_spec = importlib.util.find_spec("autosklearn")
     if autosklearn_spec is not None:
-        from InsurAutoML._feature_selection._sklearn import (
+        from InsurAutoML.feature_selection.sklearn import (
             extra_trees_preproc_for_classification,
             extra_trees_preproc_for_regression,
             liblinear_svc_preprocessor,
@@ -121,7 +121,7 @@ def test_feature_selection():
 
 def test_FeatureFilter():
 
-    from InsurAutoML._feature_selection import FeatureFilter
+    from InsurAutoML.feature_selection import FeatureFilter
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -150,7 +150,7 @@ def test_FeatureFilter():
 
 def test_ASFFS():
 
-    from InsurAutoML._feature_selection import ASFFS
+    from InsurAutoML.feature_selection import ASFFS
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -186,8 +186,8 @@ def test_ASFFS():
 
 def test_GA():
 
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._feature_selection import GeneticAlgorithm
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.feature_selection import GeneticAlgorithm
 
     data = pd.read_csv("Appendix/heart.csv")
     formatter = DataEncoding()
@@ -241,7 +241,7 @@ def test_GA():
 
 def test_FOCI():
 
-    from InsurAutoML._feature_selection import FOCI
+    from InsurAutoML.feature_selection.filter import FOCI
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -257,7 +257,7 @@ def test_FOCI():
 
 def test_ExhaustiveFS():
 
-    from InsurAutoML._feature_selection._wrapper import ExhaustiveFS
+    from InsurAutoML.feature_selection.wrapper import ExhaustiveFS
     from sklearn.linear_model import Ridge
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
diff --git a/tests/test_feature_selection/test_feature_selection_wrapper.py b/tests/test_feature_selection/test_feature_selection_wrapper.py
index 5eba457..c5a0598 100644
--- a/tests/test_feature_selection/test_feature_selection_wrapper.py
+++ b/tests/test_feature_selection/test_feature_selection_wrapper.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 12:26:09 pm
+Last Modified: Monday, 28th November 2022 11:35:15 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,7 +39,7 @@
 """
 
 import pandas as pd
-from InsurAutoML._feature_selection import (
+from InsurAutoML.feature_selection import (
     PCA_FeatureSelection,
     RBFSampler,
 )
@@ -118,7 +118,7 @@ def test_feature_selection_RBFSampler():
 
 def test_feature_selection_densifier():
 
-    from InsurAutoML._feature_selection._sklearn import densifier
+    from InsurAutoML.feature_selection.sklearn import densifier
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -133,7 +133,7 @@ def test_feature_selection_densifier():
 
 def test_feature_selection_fast_ica():
 
-    from InsurAutoML._feature_selection._sklearn import fast_ica
+    from InsurAutoML.feature_selection.sklearn import fast_ica
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -148,7 +148,7 @@ def test_feature_selection_fast_ica():
 
 def test_feature_selection_feature_agglomeration():
 
-    from InsurAutoML._feature_selection._sklearn import feature_agglomeration
+    from InsurAutoML.feature_selection.sklearn import feature_agglomeration
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -163,7 +163,7 @@ def test_feature_selection_feature_agglomeration():
 
 def test_feature_selection_kernel_pca():
 
-    from InsurAutoML._feature_selection._sklearn import kernel_pca
+    from InsurAutoML.feature_selection.sklearn import kernel_pca
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -178,7 +178,7 @@ def test_feature_selection_kernel_pca():
 
 def test_feature_selection_kitchen_sinks():
 
-    from InsurAutoML._feature_selection._sklearn import kitchen_sinks
+    from InsurAutoML.feature_selection.sklearn import kitchen_sinks
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -193,7 +193,7 @@ def test_feature_selection_kitchen_sinks():
 
 def test_feature_selection_nystroem_sampler():
 
-    from InsurAutoML._feature_selection._sklearn import nystroem_sampler
+    from InsurAutoML.feature_selection.sklearn import nystroem_sampler
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -208,7 +208,7 @@ def test_feature_selection_nystroem_sampler():
 
 def test_feature_selection_pca():
 
-    from InsurAutoML._feature_selection._sklearn import pca
+    from InsurAutoML.feature_selection.sklearn import pca
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
@@ -223,7 +223,7 @@ def test_feature_selection_pca():
 
 def test_feature_selection_random_trees_embedding():
 
-    from InsurAutoML._feature_selection._sklearn import random_trees_embedding
+    from InsurAutoML.feature_selection.sklearn import random_trees_embedding
 
     data = pd.read_csv("Appendix/Medicalpremium.csv")
     X = data.iloc[:, :-1]
diff --git a/tests/test_hpo/test_hpo.py b/tests/test_hpo/test_hpo.py
index 476d885..ff972af 100644
--- a/tests/test_hpo/test_hpo.py
+++ b/tests/test_hpo/test_hpo.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 5:50:49 pm
+Last Modified: Monday, 28th November 2022 11:35:30 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -51,12 +51,12 @@
 
 def test_objective_1():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -130,12 +130,12 @@ def test_objective_1():
 
 def test_objective_2():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -209,12 +209,12 @@ def test_objective_2():
 
 def test_objective_3():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LinearRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LinearRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
@@ -285,12 +285,12 @@ def test_objective_3():
 
 def test_objective_4():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LinearRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LinearRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
@@ -361,12 +361,12 @@ def test_objective_4():
 
 def test_objective_5():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
diff --git a/tests/test_hpo/test_hpo_2.py b/tests/test_hpo/test_hpo_2.py
index c715624..ef6b3ef 100644
--- a/tests/test_hpo/test_hpo_2.py
+++ b/tests/test_hpo/test_hpo_2.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:25 pm
+Last Modified: Monday, 28th November 2022 11:35:39 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -43,12 +43,12 @@
 
 def test_objective_6():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -122,12 +122,12 @@ def test_objective_6():
 
 def test_objective_7():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -201,12 +201,12 @@ def test_objective_7():
 
 def test_objective_8():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LinearRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LinearRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
@@ -277,12 +277,12 @@ def test_objective_8():
 
 def test_objective_9():
 
-    from InsurAutoML._hpo._utils import TabularObjective
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LinearRegression
+    from InsurAutoML.hpo.utils import TabularObjective
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LinearRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
diff --git a/tests/test_hpo/test_hpo_3.py b/tests/test_hpo/test_hpo_3.py
index 5b0f755..2f120bd 100644
--- a/tests/test_hpo/test_hpo_3.py
+++ b/tests/test_hpo/test_hpo_3.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 12:56:36 pm
+Last Modified: Thursday, 2nd February 2023 10:00:55 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -120,7 +120,7 @@ def test_insurance():
 
 def test_insurance_R2():
 
-    from InsurAutoML._hpo._base import AutoTabularBase
+    from InsurAutoML.hpo.base import AutoTabularBase
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
diff --git a/tests/test_hpo/test_hpo_utils.py b/tests/test_hpo/test_hpo_utils.py
index 087d97b..d3d2602 100644
--- a/tests/test_hpo/test_hpo_utils.py
+++ b/tests/test_hpo/test_hpo_utils.py
@@ -1,17 +1,17 @@
 """
-File Name: test_utils.py
+File Name: test_hpo_utils.py
 Author: Panyi Dong
 GitHub: https://github.com/PanyiDong/
 Mathematics Department, University of Illinois at Urbana-Champaign (UIUC)
 
 Project: InsurAutoML
 Latest Version: 0.2.3
-Relative Path: /tests/test_hpo/test_utils.py
+Relative Path: /tests/test_hpo/test_hpo_utils.py
 File Created: Monday, 7th November 2022 11:38:53 pm
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:44 pm
+Last Modified: Monday, 28th November 2022 11:35:43 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,12 +44,12 @@
 
 def test_pipeline():
 
-    from InsurAutoML._hpo._utils import Pipeline
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import Pipeline
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -83,13 +83,13 @@ def test_pipeline():
 
 def test_ClassifierEnsemble():
 
-    from InsurAutoML._hpo._utils import ClassifierEnsemble
-    from InsurAutoML._hpo._utils import Pipeline
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.hpo.utils import ClassifierEnsemble
+    from InsurAutoML.hpo.utils import Pipeline
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LogisticRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "heart")
@@ -157,13 +157,13 @@ def test_ClassifierEnsemble():
 
 def test_RegressorEnsemble():
 
-    from InsurAutoML._hpo._utils import RegressorEnsemble
-    from InsurAutoML._hpo._utils import Pipeline
-    from InsurAutoML._encoding import DataEncoding
-    from InsurAutoML._imputation import SimpleImputer
-    from InsurAutoML._base import no_processing
-    from InsurAutoML._scaling import Standardize
-    from InsurAutoML._model import LinearRegression
+    from InsurAutoML.hpo.utils import RegressorEnsemble
+    from InsurAutoML.hpo.utils import Pipeline
+    from InsurAutoML.encoding import DataEncoding
+    from InsurAutoML.imputation import SimpleImputer
+    from InsurAutoML.base import no_processing
+    from InsurAutoML.scaling import Standardize
+    from InsurAutoML.model import LinearRegression
 
     # test load_data here
     data = load_data().load("example/example_data", "insurance")
diff --git a/tests/test_hyperparameters/test_hyperparameters.py b/tests/test_hyperparameters/test_hyperparameters.py
index 9387c53..d76adc1 100644
--- a/tests/test_hyperparameters/test_hyperparameters.py
+++ b/tests/test_hyperparameters/test_hyperparameters.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:49 pm
+Last Modified: Monday, 28th November 2022 11:34:59 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,7 +41,7 @@
 
 def test_encoder_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         encoder_hyperparameter,
     )
 
@@ -52,7 +52,7 @@ def test_encoder_hyperparameters():
 
 def test_imputer_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         imputer_hyperparameter,
     )
 
@@ -63,7 +63,7 @@ def test_imputer_hyperparameters():
 
 def test_balancing_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         balancing_hyperparameter,
     )
 
@@ -74,7 +74,7 @@ def test_balancing_hyperparameters():
 
 def test_scaling_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         scaling_hyperparameter,
     )
 
@@ -85,7 +85,7 @@ def test_scaling_hyperparameters():
 
 def test_feature_selection_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         feature_selection_hyperparameter,
     )
 
@@ -96,7 +96,7 @@ def test_feature_selection_hyperparameters():
 
 def test_regressor_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         regressor_hyperparameter,
     )
 
@@ -107,7 +107,7 @@ def test_regressor_hyperparameters():
 
 def test_classifier_hyperparameters():
 
-    from InsurAutoML._hyperparameters import (
+    from InsurAutoML.hyperparameters import (
         classifier_hyperparameter,
     )
 
diff --git a/tests/test_imputer/test_imputer.py b/tests/test_imputer/test_imputer.py
index 5e1309e..18d03dd 100644
--- a/tests/test_imputer/test_imputer.py
+++ b/tests/test_imputer/test_imputer.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:22:55 pm
+Last Modified: Monday, 28th November 2022 11:21:10 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -83,8 +83,8 @@
 
 def test_imputer():
 
-    from InsurAutoML._imputation import imputers
-    from InsurAutoML._utils import formatting
+    from InsurAutoML.imputation import imputers
+    from InsurAutoML.utils import formatting
 
     for method_name, method_object in zip(imputers.keys(), imputers.values()):
 
@@ -110,7 +110,7 @@ def test_imputer():
 
 def test_DummyImputer():
 
-    from InsurAutoML._imputation import DummyImputer
+    from InsurAutoML.imputation import DummyImputer
 
     data = pd.DataFrame(
         np.random.randint(0, 50, size=(100, 5)),
@@ -137,7 +137,7 @@ def test_DummyImputer():
 
 def test_kNNImputer():
 
-    from InsurAutoML._imputation import KNNImputer
+    from InsurAutoML.imputation import KNNImputer
 
     data = pd.DataFrame(
         np.random.randint(0, 50, size=(100, 5)),
diff --git a/tests/test_model/test_model.py b/tests/test_model/test_model.py
index 6c6ba17..8ccc0e7 100644
--- a/tests/test_model/test_model.py
+++ b/tests/test_model/test_model.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 11:45:11 am
+Last Modified: Monday, 28th November 2022 11:21:02 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,12 +39,12 @@
 """
 
 import pandas as pd
-from InsurAutoML._utils import formatting
+from InsurAutoML.utils import formatting
 
 
 def test_classifiers():
 
-    from InsurAutoML._model import classifiers
+    from InsurAutoML.model import classifiers
 
     for method_name, method in classifiers.items():
 
@@ -75,7 +75,7 @@ def test_classifiers():
                 method_name
             )
 
-    from InsurAutoML._model import (
+    from InsurAutoML.model import (
         AdaboostClassifier,
         BernoulliNB,
         DecisionTreeClassifier,
@@ -134,7 +134,7 @@ def test_classifiers():
 
 def test_regressors():
 
-    from InsurAutoML._model import regressors
+    from InsurAutoML.model import regressors
 
     for method_name, method in regressors.items():
 
@@ -168,7 +168,7 @@ def test_regressors():
                 method_name
             )
 
-    from InsurAutoML._model import (
+    from InsurAutoML.model import (
         AdaboostRegressor,
         ARDRegression,
         DecisionTreeRegressor,
diff --git a/tests/test_model/test_model_2.py b/tests/test_model/test_model_2.py
index 5c99c70..52e800f 100644
--- a/tests/test_model/test_model_2.py
+++ b/tests/test_model/test_model_2.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 11:43:50 am
+Last Modified: Monday, 28th November 2022 11:39:57 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -39,12 +39,12 @@
 """
 
 import pandas as pd
-from InsurAutoML._utils import formatting
+from InsurAutoML.utils import formatting
 
 
 def test_add_classifier():
 
-    from InsurAutoML._model._sklearn import ComplementNB
+    from InsurAutoML.model.sklearn import ComplementNB
 
     data = pd.read_csv("example/example_data/heart.csv")
     # encoding categorical features
@@ -65,7 +65,7 @@ def test_add_classifier():
 
 def test_add_regressor():
 
-    from InsurAutoML._model import LibSVM_SVR, MLPRegressor, SGDRegressor
+    from InsurAutoML.model import LibSVM_SVR, MLPRegressor, SGDRegressor
 
     data = pd.read_csv("example/example_data/insurance.csv")
 
@@ -121,7 +121,7 @@ def test_add_regressor():
 
 def test_lightgbm_classifier():
 
-    from InsurAutoML._model import LightGBM_Classifier
+    from InsurAutoML.model import LightGBM_Classifier
 
     data = pd.read_csv("example/example_data/heart.csv")
 
@@ -155,7 +155,7 @@ def test_lightgbm_classifier():
 
 def test_lightgbm_regressor():
 
-    from InsurAutoML._model import LightGBM_Regressor
+    from InsurAutoML.model import LightGBM_Regressor
 
     data = pd.read_csv("example/example_data/insurance.csv")
 
@@ -192,7 +192,7 @@ def test_lightgbm_regressor():
 
 def test_xgboost_classifier():
 
-    from InsurAutoML._model import XGBoost_Classifier
+    from InsurAutoML.model import XGBoost_Classifier
 
     data = pd.read_csv("example/example_data/heart.csv")
 
@@ -223,7 +223,7 @@ def test_xgboost_classifier():
 
 def test_xgboost_regressor():
 
-    from InsurAutoML._model import XGBoost_Regressor
+    from InsurAutoML.model import XGBoost_Regressor
 
     data = pd.read_csv("example/example_data/insurance.csv")
 
@@ -257,7 +257,7 @@ def test_xgboost_regressor():
 
 def test_gam_classifier():
 
-    from InsurAutoML._model import GAM_Classifier
+    from InsurAutoML.model import GAM_Classifier
 
     data = pd.read_csv("example/example_data/heart.csv")
 
@@ -283,7 +283,7 @@ def test_gam_classifier():
 
 def test_gam_regressor():
 
-    from InsurAutoML._model import GAM_Regressor
+    from InsurAutoML.model import GAM_Regressor
 
     data = pd.read_csv("example/example_data/insurance.csv")
 
diff --git a/tests/test_scaling/test_scaling.py b/tests/test_scaling/test_scaling.py
index 171fa9a..429a7ea 100644
--- a/tests/test_scaling/test_scaling.py
+++ b/tests/test_scaling/test_scaling.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:14 pm
+Last Modified: Monday, 28th November 2022 11:40:48 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -42,7 +42,7 @@
 import numpy as np
 import pandas as pd
 
-from InsurAutoML._scaling import scalings
+from InsurAutoML.scaling import scalings
 
 data_X = pd.DataFrame(
     {
@@ -89,7 +89,7 @@ def test_Scaling(self):
 
 def test_NoScaing():
 
-    from InsurAutoML._scaling._scaling import NoScaling
+    from InsurAutoML.scaling.scaling import NoScaling
 
     method = NoScaling()
     method.fit(data_X, data_y)
@@ -105,7 +105,7 @@ def test_NoScaing():
 # test decrepted methods
 def test_feature_manipulation():
 
-    from InsurAutoML._scaling import Feature_Manipulation
+    from InsurAutoML.scaling import Feature_Manipulation
 
     data = pd.DataFrame(np.arange(15).reshape(5, 3), columns=[
         "column_" + str(i + 1) for i in range(3)])
@@ -128,7 +128,7 @@ def test_feature_manipulation():
 
 def test_feature_truncation():
 
-    from InsurAutoML._scaling import Feature_Truncation
+    from InsurAutoML.scaling import Feature_Truncation
 
     data = pd.DataFrame(
         np.random.randint(0, 100, size=(100, 10)),
diff --git a/tests/test_utils/test_metadata.py b/tests/test_utils/test_metadata.py
index ecaeefb..c43d03e 100644
--- a/tests/test_utils/test_metadata.py
+++ b/tests/test_utils/test_metadata.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Wednesday, 16th November 2022 11:54:23 am
+Last Modified: Monday, 28th November 2022 11:35:54 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,8 +41,8 @@
 
 def test_MetaData():
 
-    from InsurAutoML._utils._metadata import MetaData
-    from InsurAutoML._datasets import PROD
+    from InsurAutoML.utils.metadata import MetaData
+    from InsurAutoML.datasets import PROD
 
     data = PROD(split=["train"])
 
diff --git a/tests/test_utils/test_nn.py b/tests/test_utils/test_nn.py
index 07126e8..1a7dafa 100644
--- a/tests/test_utils/test_nn.py
+++ b/tests/test_utils/test_nn.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 5:36:33 pm
+Last Modified: Tuesday, 6th December 2022 11:27:40 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -45,8 +45,8 @@ def test_MetaData():
 
     try:
         import torch
-        from InsurAutoML._utils._nn._multiProc import MultiPreprocessing
-        from InsurAutoML._datasets import PROD
+        from InsurAutoML.preprocessing.multiProc import MultiPreprocessing
+        from InsurAutoML.datasets import PROD
 
         data = PROD(split=["train"])
 
diff --git a/tests/test_utils/test_utils.py b/tests/test_utils/test_utils.py
index 4ccc8aa..85fc604 100644
--- a/tests/test_utils/test_utils.py
+++ b/tests/test_utils/test_utils.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:45 pm
+Last Modified: Monday, 28th November 2022 11:36:00 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,7 +44,7 @@
 
 def test_save_model():
 
-    from InsurAutoML._utils._file import save_model
+    from InsurAutoML.utils.file import save_model
 
     save_model(
         "encoder",
@@ -67,8 +67,8 @@ def test_save_model():
 
 def test_EDA():
 
-    from InsurAutoML._utils._eda import EDA
-    from InsurAutoML._datasets import PROD, HEART
+    from InsurAutoML.utils.eda import EDA
+    from InsurAutoML.datasets import PROD, HEART
 
     features, label = PROD(split="train")
 
@@ -89,8 +89,8 @@ def test_EDA():
 
 def test_feature_type():
 
-    from InsurAutoML._utils._data import feature_type
-    from InsurAutoML._datasets import PROD, HEART
+    from InsurAutoML.utils.data import feature_type
+    from InsurAutoML.datasets import PROD, HEART
 
     data, label = PROD(split="test")
     data_type = {}
@@ -111,7 +111,7 @@ def test_feature_type():
 
 def test_plotHighDimCluster():
 
-    from InsurAutoML._utils._data import plotHighDimCluster
+    from InsurAutoML.utils.data import plotHighDimCluster
 
     X = np.random.randint(0, 100, size=(1000, 200))
     y = np.random.randint(0, 5, size=(1000,))
@@ -125,8 +125,8 @@ def test_plotHighDimCluster():
 
 def test_word2vec():
 
-    from InsurAutoML._datasets import PROD
-    from InsurAutoML._utils._data import text2vec
+    from InsurAutoML.datasets import PROD
+    from InsurAutoML.utils.data import text2vec
 
     features, labels = PROD(split="test")
     features = features["Product_Description"]
diff --git a/tests/test_utils/test_utils_base.py b/tests/test_utils/test_utils_base.py
index 2dbb13e..3c3d245 100644
--- a/tests/test_utils/test_utils_base.py
+++ b/tests/test_utils/test_utils_base.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:20 pm
+Last Modified: Monday, 28th November 2022 11:45:04 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -61,7 +61,7 @@ def test_load_data():
 
 def test_random_guess():
 
-    from InsurAutoML._utils._base import random_guess
+    from InsurAutoML.utils.base import random_guess
 
     assert random_guess(1) == 1, "random_guess(1) should be 1, get {}".format(
         random_guess(1)
@@ -76,7 +76,7 @@ def test_random_guess():
 
 def test_random_index():
 
-    from InsurAutoML._utils._base import random_index
+    from InsurAutoML.utils.base import random_index
 
     assert (
         np.sort(random_index(5)) == np.array([0, 1, 2, 3, 4])
@@ -87,7 +87,7 @@ def test_random_index():
 
 def test_random_list():
 
-    from InsurAutoML._utils._base import random_list
+    from InsurAutoML.utils.base import random_list
 
     assert (
         np.sort(random_list([0, 1, 2, 3, 4])) == np.array([0, 1, 2, 3, 4])
@@ -98,7 +98,7 @@ def test_random_list():
 
 def test_is_date():
 
-    from InsurAutoML._utils._base import is_date
+    from InsurAutoML.utils.base import is_date
 
     test = pd.DataFrame(
         {
@@ -119,7 +119,7 @@ def test_is_date():
 
 def test_feature_rounding():
 
-    from InsurAutoML._utils._base import feature_rounding
+    from InsurAutoML.utils.base import feature_rounding
 
     test = pd.DataFrame(
         {
@@ -142,7 +142,7 @@ def test_feature_rounding():
 
 def test_timer():
 
-    from InsurAutoML._utils._base import Timer
+    from InsurAutoML.utils.base import Timer
     import time
 
     timer = Timer()
@@ -160,7 +160,7 @@ def test_timer():
 
 def test_minloc():
 
-    from InsurAutoML._utils._base import minloc
+    from InsurAutoML.utils.base import minloc
 
     assert (
         minloc([4, 2, 6, 2, 1]) == 4
@@ -169,7 +169,7 @@ def test_minloc():
 
 def test_maxloc():
 
-    from InsurAutoML._utils._base import maxloc
+    from InsurAutoML.utils.base import maxloc
 
     assert (
         maxloc([4, 2, 6, 2, 1]) == 2
@@ -178,7 +178,7 @@ def test_maxloc():
 
 def test_True_index():
 
-    from InsurAutoML._utils._base import True_index
+    from InsurAutoML.utils.base import True_index
 
     assert True_index([True, False, 1, 0, "hello", 5]) == [
         0,
@@ -190,7 +190,7 @@ def test_True_index():
 
 def test_type_of_script():
 
-    from InsurAutoML._utils._base import type_of_script
+    from InsurAutoML.utils.base import type_of_script
 
     assert (
         type_of_script() == "terminal"
@@ -199,7 +199,7 @@ def test_type_of_script():
 
 def test_has_method():
 
-    from InsurAutoML._utils._base import has_method
+    from InsurAutoML.utils.base import has_method
     from sklearn.linear_model import LogisticRegression
 
     mol = LogisticRegression()
@@ -211,7 +211,7 @@ def test_has_method():
 
 def test_is_none():
 
-    from InsurAutoML._utils._base import is_none
+    from InsurAutoML.utils.base import is_none
 
     assert is_none(None), "The is_none function is not correct."
     assert is_none("not none") == False, "The is_none function is not correct."
@@ -219,13 +219,14 @@ def test_is_none():
 
 def test_format_hyper_dict():
 
-    from InsurAutoML._utils._base import format_hyper_dict
+    from InsurAutoML.utils.base import format_hyper_dict
 
     input = {"encoder": "encoder", "n_estimators": 2}
     expected = {"encoder_1": "encoder", "encoder_n_estimators": 2}
 
     assert (
-        format_hyper_dict(input, 1, ref="encoder", search_algo="RandomSearch") == input
+        format_hyper_dict(input, 1, ref="encoder",
+                          search_algo="RandomSearch") == input
     ), "The format_hyper_dict function is not correct."
     assert (format_hyper_dict(input, 1, ref="encoder", search_algo="HyperOpt")
             == expected), "The format_hyper_dict function is not correct."
diff --git a/tests/test_utils/test_utils_data.py b/tests/test_utils/test_utils_data.py
index c06720d..24019c7 100644
--- a/tests/test_utils/test_utils_data.py
+++ b/tests/test_utils/test_utils_data.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:30 pm
+Last Modified: Monday, 28th November 2022 11:41:25 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,7 +44,7 @@
 
 def test_as_dataframe():
 
-    from InsurAutoML._utils._data import as_dataframe
+    from InsurAutoML.utils.data import as_dataframe
 
     converter = as_dataframe()
 
@@ -63,7 +63,7 @@ def test_as_dataframe():
 
 def test_unify_nan():
 
-    from InsurAutoML._utils._data import unify_nan
+    from InsurAutoML.utils.data import unify_nan
 
     data = np.arange(15).reshape(5, 3)
     data = pd.DataFrame(data, columns=["column_1", "column_2", "column_3"])
@@ -89,7 +89,7 @@ def test_unify_nan():
 
 def test_remove_index_columns():
 
-    from InsurAutoML._utils._data import remove_index_columns
+    from InsurAutoML.utils.data import remove_index_columns
 
     data = pd.DataFrame(
         {
@@ -137,7 +137,7 @@ def test_remove_index_columns():
 
 def test_formatting():
 
-    from InsurAutoML._utils._data import formatting
+    from InsurAutoML.utils.data import formatting
 
     data = pd.read_csv("Appendix/insurance.csv")
     train = data.iloc[100:, :]
@@ -153,7 +153,7 @@ def test_formatting():
 
 def test_get_missing_matrix():
 
-    from InsurAutoML._utils._data import get_missing_matrix
+    from InsurAutoML.utils.data import get_missing_matrix
 
     test = pd.DataFrame(
         {
@@ -186,7 +186,7 @@ def test_get_missing_matrix():
 
 def test_extremeclass():
 
-    from InsurAutoML._utils._data import ExtremeClass
+    from InsurAutoML.utils.data import ExtremeClass
 
     cutter = ExtremeClass(extreme_threshold=0.9)
     test = pd.DataFrame(
@@ -200,7 +200,7 @@ def test_extremeclass():
 
 def test_assign_classes():
 
-    from InsurAutoML._utils._data import assign_classes
+    from InsurAutoML.utils.data import assign_classes
 
     test = [[0.9, 0.1], [0.2, 0.8]]
 
@@ -211,7 +211,7 @@ def test_assign_classes():
 
 def test_softmax():
 
-    from InsurAutoML._utils._data import softmax
+    from InsurAutoML.utils.data import softmax
 
     a = np.array([0.1, -0.1, 1])
 
diff --git a/tests/test_utils/test_utils_optimize.py b/tests/test_utils/test_utils_optimize.py
index 09b4129..ed39f9e 100644
--- a/tests/test_utils/test_utils_optimize.py
+++ b/tests/test_utils/test_utils_optimize.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Tuesday, 15th November 2022 11:30:31 pm
+Last Modified: Monday, 28th November 2022 11:35:10 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -41,7 +41,7 @@
 
 def test_get_algo():
 
-    from InsurAutoML._utils._optimize import get_algo
+    from InsurAutoML.utils.optimize import get_algo
 
     get_algo("GridSearch")
     get_algo("HyperOpt")
@@ -80,7 +80,7 @@ def test_get_algo():
 
 def test_get_scheduler():
 
-    from InsurAutoML._utils._optimize import get_scheduler
+    from InsurAutoML.utils.optimize import get_scheduler
 
     get_scheduler("FIFOScheduler")
     get_scheduler("ASHAScheduler")
@@ -106,7 +106,7 @@ def test_get_scheduler():
 
 def test_get_progress_reporter():
 
-    from InsurAutoML._utils._optimize import get_progress_reporter
+    from InsurAutoML.utils.optimize import get_progress_reporter
 
     get_progress_reporter("CLIReporter", max_evals=64, max_error=4)
     get_progress_reporter("JupyterNotebookReporter", max_evals=64, max_error=4)
@@ -114,16 +114,16 @@ def test_get_progress_reporter():
 
 def test_get_logger():
 
-    from InsurAutoML._utils._optimize import get_logger
+    from InsurAutoML.utils.optimize import get_logger
 
     get_logger(["Logger", "TBX", "JSON", "CSV", "MLflow"])
 
 
 def test_get_estimator():
 
-    from InsurAutoML._utils._optimize import get_estimator
+    from InsurAutoML.utils.optimize import get_estimator
     from sklearn.linear_model import LinearRegression
-    from InsurAutoML._utils._base import has_method
+    from InsurAutoML.utils.base import has_method
 
     test_list = [
         "Lasso",
@@ -145,7 +145,7 @@ def test_get_estimator():
 
 def test_get_metrics():
 
-    from InsurAutoML._utils._optimize import get_metrics
+    from InsurAutoML.utils.optimize import get_metrics
     from sklearn.metrics import accuracy_score
     from typing import Callable
 
@@ -177,8 +177,8 @@ def test_get_metrics():
 
 def test_check_func():
 
-    from InsurAutoML._utils._optimize import check_func
-    from InsurAutoML._model import LogisticRegression
+    from InsurAutoML.utils.optimize import check_func
+    from InsurAutoML.model import LogisticRegression
 
     assert (
         check_func(LogisticRegression, ref="model") is None
@@ -187,9 +187,9 @@ def test_check_func():
 
 def test_check_status():
 
-    from InsurAutoML._utils._optimize import check_status
-    from InsurAutoML._hyperparameters import regressor_hyperparameter
-    from InsurAutoML._model import regressors
+    from InsurAutoML.utils.optimize import check_status
+    from InsurAutoML.hyperparameters import regressor_hyperparameter
+    from InsurAutoML.model import regressors
 
     assert (
         check_status(regressors, regressor_hyperparameter, ref="model") is None
diff --git a/tests/test_utils/test_utils_stat.py b/tests/test_utils/test_utils_stat.py
index 28d7536..45f4434 100644
--- a/tests/test_utils/test_utils_stat.py
+++ b/tests/test_utils/test_utils_stat.py
@@ -11,7 +11,7 @@
 Author: Panyi Dong (panyid2@illinois.edu)
 
 -----
-Last Modified: Monday, 14th November 2022 8:23:40 pm
+Last Modified: Tuesday, 6th December 2022 11:27:36 pm
 Modified By: Panyi Dong (panyid2@illinois.edu)
 
 -----
@@ -44,7 +44,7 @@
 
 def test_nan_cov():
 
-    from InsurAutoML._utils._stat import nan_cov
+    from InsurAutoML.utils.stats import nan_cov
 
     assert (
         nan_cov(pd.DataFrame([4, 5, 6, np.nan, 1, np.nan]))[0, 0] == 2.8
@@ -53,7 +53,7 @@ def test_nan_cov():
 
 def test_class_means():
 
-    from InsurAutoML._utils._stat import class_means
+    from InsurAutoML.utils.stats import class_means
 
     X = pd.DataFrame(
         {
@@ -74,7 +74,7 @@ def test_class_means():
 
 def test_empirical_covariance():
 
-    from InsurAutoML._utils import empirical_covariance
+    from InsurAutoML.utils import empirical_covariance
 
     cov = empirical_covariance(10 * np.random.random(size=(10, 10)))
 
@@ -85,7 +85,7 @@ def test_empirical_covariance():
 
 def test_class_cov():
 
-    from InsurAutoML._utils._stat import class_cov
+    from InsurAutoML.utils.stats import class_cov
 
     X = np.arange(10)
     y = np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])
@@ -99,7 +99,7 @@ def test_class_cov():
 
 def test_MI():
 
-    from InsurAutoML._utils._stat import MI
+    from InsurAutoML.utils.stats import MI
 
     X = pd.DataFrame(np.arange(20).reshape(10, 2), columns=["X_1", "X_2"])
     y = pd.DataFrame(
@@ -116,7 +116,7 @@ def test_MI():
 
 def test_ACCC():
 
-    from InsurAutoML._utils._stat import ACCC
+    from InsurAutoML.utils.stats import ACCC
 
     Z = pd.DataFrame(
         np.random.normal(
@@ -140,7 +140,7 @@ def test_ACCC():
 
 def test_t_score():
 
-    from InsurAutoML._utils._stat import t_score
+    from InsurAutoML.utils.stats import t_score
 
     X = pd.DataFrame(np.arange(20).reshape(10, 2), columns=["X_1", "X_2"])
     y = pd.DataFrame(
@@ -159,7 +159,7 @@ def test_t_score():
 
 def test_ANOVA():
 
-    from InsurAutoML._utils._stat import ANOVA
+    from InsurAutoML.utils.stats import ANOVA
 
     X = pd.DataFrame(np.arange(20).reshape(10, 2), columns=["X_1", "X_2"])
     y = pd.DataFrame(
@@ -178,7 +178,7 @@ def test_ANOVA():
 
 def test_neg_metrics():
 
-    from InsurAutoML._utils._stat import (
+    from InsurAutoML.utils.stats import (
         neg_R2,
         neg_accuracy,
         neg_precision,