Tool: Model Selector. 81/72981/1
authorSridhar K. N. Rao <sridhar.rao@spirent.com>
Fri, 22 Oct 2021 06:22:49 +0000 (11:52 +0530)
committerSridhar K. N. Rao <sridhar.rao@spirent.com>
Fri, 22 Oct 2021 06:23:54 +0000 (11:53 +0530)
This patch adds model selector tool.

Signed-off-by: Sridhar K. N. Rao <sridhar.rao@spirent.com>
Change-Id: I73eb64406180d531705dc51c80e7605eefdeae8c

tools/modelselector/ModelSelector.drawio [new file with mode: 0755]
tools/modelselector/ModelSelector.png [new file with mode: 0755]
tools/modelselector/modelselector.py [new file with mode: 0644]
tools/modelselector/requirements.txt [new file with mode: 0644]

diff --git a/tools/modelselector/ModelSelector.drawio b/tools/modelselector/ModelSelector.drawio
new file mode 100755 (executable)
index 0000000..ba5104b
--- /dev/null
@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2021-10-15T07:17:32.754Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/15.4.0 Chrome/91.0.4472.164 Electron/13.5.0 Safari/537.36" version="15.4.0" etag="J0zI00rEDc0oEgo8hFz5" type="device"><diagram id="76fGCiflqrv7-NHLhIKw" name="Page-1">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</diagram></mxfile>
\ No newline at end of file
diff --git a/tools/modelselector/ModelSelector.png b/tools/modelselector/ModelSelector.png
new file mode 100755 (executable)
index 0000000..de671ee
Binary files /dev/null and b/tools/modelselector/ModelSelector.png differ
diff --git a/tools/modelselector/modelselector.py b/tools/modelselector/modelselector.py
new file mode 100644 (file)
index 0000000..90b289c
--- /dev/null
@@ -0,0 +1,834 @@
+# Copyright 2021 Spirent Communications.
+# sridhar.rao@spirent.com
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Tool to suggest which ML approach is more applicable for
+a particular data and usecase.
+TODO:
+1. Minimize code.
+a. Reduce returns.
+b. Optimize loops.
+
+2. Add Informative data to the user.
+"""
+
+from __future__ import print_function
+import signal
+import sys
+from pypsi import wizard as wiz
+from pypsi.shell import Shell
+
+# pylint: disable=line-too-long,too-few-public-methods,too-many-instance-attributes, too-many-nested-blocks, too-many-return-statements, too-many-branches
+
+class Bcolors:
+    """
+    For Coloring
+    """
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
+class AlgoSelectorWizard():
+    """
+    Class to create wizards
+    """
+    def __init__(self):
+        """
+        Perform Initialization.
+        """
+        self.shell = Shell()
+        self.main_values = {}
+        self.main_l1_values = {}
+        self.main_l2a_values = {}
+        self.main_l2b_values = {}
+        self.main_l3_values = {}
+        self.main_l4_values = {}
+        self.unsup_values = {}
+        self.ri_values = {}
+        self.gen_values = {}
+        self.wiz_main = None
+        self.wiz_main_l1 = None
+        self.wiz_main_l2_a = None
+        self.wiz_main_l2_b = None
+        self.wiz_main_l3 = None
+        self.wiz_main_l4 = None
+        self.wiz_generic = None
+        self.wiz_unsupervised = None
+        self.wiz_reinforcement = None
+        self.ml_needed = False
+        self.supervised = False
+        self.unsupervised = False
+        self.reinforcement = False
+        self.data_size = 'high'
+        self.interpretability = False
+        self.faster = False
+        self.ftod_ratio = 'low'
+        self.reproducibility = False
+
+
+    ############# All the Wizards ##################################
+
+    ### GENERIC Wizards - Need for ML ##############################
+    def main_wizard_l1(self):
+        """
+        The Main Wizard L1
+        """
+        self.wiz_main_l1 = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Do you Need ML - Data Availability"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_availability",
+                    # Display name
+                    name=Bcolors.HEADER+"Do you have access to data about different situations, or that describes a lot of examples of situations"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U - Yes/No/Unknown",
+                    validators=(wiz.required_validator),
+                    default='Y',
+                ),
+            )
+        )
+
+    def main_wizard_l2_a(self):
+        """
+        The Main Wizard L2-A
+        """
+        self.wiz_main_l2_a = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Do you Need ML - Data Creation"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_creativity",
+                    # Display name
+                    name=Bcolors.HEADER+"Will a system be able to gather a lot of data by trying sequences of actions in many different situations and seeing the results"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U - Yes/No/Unknown",
+                    validators=(wiz.required_validator),
+                    default='Y',
+                ),
+            )
+        )
+
+    def main_wizard_l2_b(self):
+        """
+        The Main Wizard L2-B
+        """
+        label = """ One or more meaningful and informative 'tag' to provide context so that a machine learning model can learn from it. For example, labels might indicate whether a photo contains a bird or car, which words were uttered in an audio recording, or if an x-ray contains a tumor. Data labeling is required for a variety of use cases including computer vision, natural language processing, and speech recognition."""
+        self.wiz_main_l2_b = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Do you Need ML - Data Programmability"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_label",
+                    # Display name
+                    name=Bcolors.HEADER+" Do you have Labelled data? (Type Y/N/U - Yes/No/Unknown). Type help for description of label. "+Bcolors.ENDC,
+                    # Help message
+                    help=label,
+                    validators=(wiz.required_validator),
+                    default='Y',
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_programmability",
+                    # Display name
+                    name=Bcolors.HEADER+"Can a program or set of rules decide what actions to take based on the data you have about the situations"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U - Yes/No/Unknown",
+                    validators=(wiz.required_validator),
+                    default='Y',
+                ),
+            )
+        )
+
+
+    def main_wizard_l3(self):
+        """
+        The Main Wizard L3
+        """
+        self.wiz_main_l3 = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Do you Need ML - Data Knowledge"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_knowledge",
+                    # Display name
+                    name=Bcolors.HEADER+"Could a knowledgeable human decide what actions to take based on the data you have about the situations"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U - Yes/No/Unknown",
+                    validators=(wiz.required_validator),
+                    default='Y',
+                ),
+            )
+        )
+
+    def main_wizard_l4(self):
+        """
+        The Main Wizard - L4
+        """
+        self.wiz_main_l4 = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Do you Need ML - Data Pattern"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_pattern",
+                    # Display name
+                    name=Bcolors.HEADER+"Could there be patterns in these situations that the humans haven't recognized before"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U - Yes/No/Unknown",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+            )
+        )
+    ### GENERIC Wizards - GOAL, METRICS, DATA ##############################
+    def gen_wizard(self):
+        """
+        Generic Wizard - Goal, metrics, data
+        """
+        self.wiz_generic = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Understanding Goal, Metrics, Data and Output Type"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_goal",
+                    # Display name
+                    name=Bcolors.HEADER+" What is your goal with the data? Predict, Describe or Explore"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter one of Predict/Describe/Explore",
+                    validators=(wiz.required_validator),
+                    default='Explore'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="metric_accuracy",
+                    # Display name
+                    name=Bcolors.HEADER+" How important the metric 'Accuracy' is for you? 1-5: 1- Least important 5- Most Important"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter 1-5: 1 being least important, and 5 being most important",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="metric_speed",
+                    # Display name
+                    name=Bcolors.HEADER+" How important the metric 'Speed' is for you? 1-5: 1- Least important 5- Most Important"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter 1-5: 1 being least important, and 5 being most important",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="metric_interpretability",
+                    # Display name
+                    name=Bcolors.HEADER+" How important the metric 'Interpretability' is for you? 1-5: 1- Least important 5- Most Important"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter 1-5: 1 being least important, and 5 being most important",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="metric_reproducibility",
+                    # Display name
+                    name=Bcolors.HEADER+" How important the metric 'Reproducibility' is for you? 1-5: 1- Least important 5- Most Important"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter 1-5: 1 being least important, and 5 being most important",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="metric_implementation",
+                    # Display name
+                    name=Bcolors.HEADER+" How important the metric 'Ease of Implementation and Maintenance' is for you? 1-5: 1- Least important 5- Most Important"+Bcolors.ENDC,
+                    # Help message
+                    help="Enter 1-5: 1 being least important, and 5 being most important",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_column",
+                    # Display name
+                    name=Bcolors.HEADER+" What does the data (columns) represent? well defined 'Features', 'signals' (Timeseries, pixels, etc) or Text - (Please type the associated number)"+Bcolors.ENDC,
+                    # Help message
+                    help="1. Well Defined Features\n 2. Signals\n 3. Text - Unstructured\n 4. None of the above\n",
+                    validators=(wiz.required_validator),
+                    default='Features'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_signal_type",
+                    # Display name
+                    name=Bcolors.HEADER+" If Signals, can you choose any one from the below list? "+Bcolors.ENDC,
+                    # Help message
+                    help="1. Image\n 2. Audio\n 3. Timeseries\n 4. None of the above\n 5. Not Applicable\n  ",
+                    validators=(wiz.required_validator),
+                    default='3'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_text_type",
+                    # Display name
+                    name=Bcolors.HEADER+" If Text, can you choose any one from the below list? "+Bcolors.ENDC,
+                    # Help message
+                    help="1. Webpages\n 2. Emails\n 3. Social-Media Posts\n 4. Books\n 5. Formal Articles\n 6. Speech converted to text\n 7. None of the above\n 8. Not Applicable\n  ",
+                    validators=(wiz.required_validator),
+                    default='3'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_features",
+                    # Display name
+                    name=Bcolors.HEADER+" If features, are they well defined? i.e., are all the variables well understood? "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_features_count",
+                    # Display name
+                    name=Bcolors.HEADER+" If features, How many are there? "+Bcolors.ENDC,
+                    # Help message
+                    help="Number or NA",
+                    validators=(wiz.required_validator),
+                    default='10'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_distribution",
+                    # Display name
+                    name=Bcolors.HEADER+" Are you aware of any 'Distribution' that is inherent to the data, we can take advantage of?"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_io_relation",
+                    # Display name
+                    name=Bcolors.HEADER+" Is the probability of 'Linear Relation' between input and the output is high?"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_correlation",
+                    # Display name
+                    name=Bcolors.HEADER+" Are you confident that there is NO high correlation among the independent variables in your day?"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U. Change in one  ",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_cond_indep",
+                    # Display name
+                    name=Bcolors.HEADER+" Are you confident that the variables are conditionally independent?"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U. If probability that it rains given lightining and thunder is same as probability that it rains given lightining, then rain and thunder are conditionally independent",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_missing",
+                    # Display name
+                    name=Bcolors.HEADER+" Are there any missing values in the data? "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/U",
+                    validators=(wiz.required_validator),
+                    default='N'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_size_bytes",
+                    # Display name
+                    name=Bcolors.HEADER+" How big is the data in terms of size? (Use K/M/G Bytes unit) "+Bcolors.ENDC,
+                    # Help message
+                    help="Number(integer) and unit: K for Kilo, M for Mega and G for Giga. Ex: 10G for 10 Giga bytes",
+                    validators=(wiz.required_validator),
+                    default='1G'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_size_samples",
+                    # Display name
+                    name=Bcolors.HEADER+" How big is the data in terms of samples? (Use T/M/B Samples) "+Bcolors.ENDC,
+                    # Help message
+                    help="Number(integer) and unit: T for Thousand, M for Million and B for Billion. Ex: 1M for 1 Million Samples",
+                    validators=(wiz.required_validator),
+                    default='1M'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_type_output",
+                    # Display name
+                    name=Bcolors.HEADER+" What is the expected output data type ? (Please type number associated with type in 'help') "+Bcolors.ENDC,
+                    # Help message
+                    help=" 1:Numerical-Discrete\n 2:Numerical-Continuous\n 3:Ordinal\n 4:Categorical-Binary\n 5:Categorical-Multiclass",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="data_output_prob",
+                    # Display name
+                    name=Bcolors.HEADER+" Is the expected output data a probability value ? "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N",
+                    validators=(wiz.required_validator),
+                    default='N'
+                ),
+            )
+        )
+
+
+    def unsupervised_wizard(self):
+        """
+        The Un-Supervized Learning Wizard
+        """
+        self.wiz_generic = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Understanding Goal, Metrics, Data and Output Type"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="unsup_goal",
+                    # Display name
+                    name=Bcolors.HEADER+" What is the main goal? (Please type number associated with type in 'help')"+Bcolors.ENDC,
+                    # Help message
+                    help="1: Explore Similar Groups (clustering) \n 2: Perform Dimensionality Reduction\n 3: Others\n",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="unsup_dr_topic_mod",
+                    # Display name
+                    name=Bcolors.HEADER+" If dimensionality reduction, do you prefer topic modelling ? (Please type NA is you are not sure)"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='NA'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="unsup_clus_dv",
+                    # Display name
+                    name=Bcolors.HEADER+" Are you aware of density variations in your data ? (Please type NA is you are not sure)"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='NA'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="unsup_clus_outliers",
+                    # Display name
+                    name=Bcolors.HEADER+" Are there too many outliers in your data ? (Please type NA is you are not sure)"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='NA'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="unsup_clus_groups",
+                    # Display name
+                    name=Bcolors.HEADER+" If clustering, do you know how many groups to form? (Please type NA is you are not sure)"+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='NA'
+                ),
+
+            )
+        )
+
+    def reinforcement_wizard(self):
+        """
+        The Reinforced Learning Wizard
+        """
+        message = """
+            Reward  |--------|
+            |-------| Agent  |  Action
+            | |-----|        |-------|
+            | |            |--------|       |
+            | |state                 |
+            | |                             |
+            | |           |-----------|     |
+            | |----|Environment|     |
+            |------|           |-----|
+                  |-----------|
+            """
+        self.wiz_reinforcement = wiz.PromptWizard(
+            name=Bcolors.OKBLUE+"Reinforcement Specific"+Bcolors.ENDC,
+            description="",
+            steps=(
+                # The list of input prompts to ask the user.
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_info",
+                    # Display name
+                    name=Bcolors.HEADER+" Type help for reference diagram for reinforcement-learning"+Bcolors.ENDC,
+                    # Help message
+                    help=message,
+                    validators=(wiz.required_validator),
+                    default='Type Help or Press Enter'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_model_preference",
+                    # Display name
+                    name=Bcolors.HEADER+" Do you prefer model-based approach? (Type NA if you are not sure) "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_model_availability",
+                    # Display name
+                    name=Bcolors.HEADER+" Do you have a model for model-based approach? (Type NA if not applicable) "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_modelfree_value",
+                    # Display name
+                    name=Bcolors.HEADER+" In Model-Free approach, do you prefer value-based approach? (Type NA if not applicable) "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_modelfree_value_state",
+                    # Display name
+                    name=Bcolors.HEADER+" In Model-Free Value-Based approach, do you prefer state-only model? (Type NA if not applicable) "+Bcolors.ENDC,
+                    # Help message
+                    help="Y/N/NA",
+                    validators=(wiz.required_validator),
+                    default='Y'
+                ),
+                wiz.WizardStep(
+                    # ID where the value will be stored
+                    id="ri_app_domain",
+                    # Display name
+                    name=Bcolors.HEADER+" What is the application domain ? (Please type number associated with type in 'help') "+Bcolors.ENDC,
+                    # Help message
+                    help=" 1:Computer Resource Mgmt.\n 2:Robotics\n 3:Traffic-Control\n 4:Reccommenders\n 5:Autonomous Vehicles\n 6:Games\n 7:Chemistry\n 8:Others\n",
+                    validators=(wiz.required_validator),
+                    default='1'
+                ),
+            )
+        )
+
+    ############### All the Run Operations ######################
+    def run_mainwiz(self):
+        """
+        Run the Main Wizard
+        """
+        self.main_wizard_l1()
+        self.main_l1_values = self.wiz_main_l1.run(self.shell)
+        if self.main_l1_values['data_availability'].lower() == 'y':
+            self.main_wizard_l2_b()
+            self.main_l2b_values = self.wiz_main_l2_b.run(self.shell)
+            if self.main_l2b_values['data_labe'].lower() == 'y':
+                self.supervised = True
+            else:
+                self.unsupervised = True
+            if self.main_l2b_values['data_programmability'].lower() == 'y':
+                print(Bcolors.FAIL+"ML is not required - Please consider alternate approaches\n"+Bcolors.ENDC)
+            else:
+                self.main_wizard_l3()
+                self.main_l3_values = self.wiz_main_l3.run(self.shell)
+                if self.main_l3_values['data_knowledge'].lower() == 'y':
+                    print(Bcolors.OKGREEN+"Looks like you need ML, let's continue"+Bcolors.ENDC)
+                    self.ml_needed = True
+                else:
+                    self.main_wizard_l4()
+                    self.main_l4_values = self.wiz_main_l4.run(self.shell)
+                    if self.main_l4_values['data_pattern'].lower() == 'y':
+                        print(Bcolors.OKGREEN+"Looks like you need ML, let's continue"+Bcolors.ENDC)
+                        self.ml_needed = True
+                    else:
+                        print(Bcolors.FAIL+"ML is not required - Please consider alternate approaches\n"+Bcolors.ENDC)
+        else:
+            self.main_wizard_l2_a()
+            self.main_l2a_values = self.wiz_main_l2_a.run(self.shell)
+            if self.main_l2a_values['data_creativity'].lower() == 'y':
+                print(Bcolors.OKGREEN+"Looks like you need ML, let's continue"+Bcolors.ENDC)
+                self.ml_needed = True
+                self.reinforcement = True
+            else:
+                print(Bcolors.FAIL+"ML is not required - Please consider alternate approaches\n"+Bcolors.ENDC)
+
+    def run_generic_wizard(self):
+        """
+        Run Generic Wizard
+        """
+        self.gen_wizard()
+        self.gen_values = self.wiz_generic.run(self.shell)
+
+    def run_unsupervised_wizard(self):
+        """
+        Run UnSupervised Learning Wizard.
+        """
+        self.unsupervised_wizard()
+        self.unsup_values = self.wiz_unsupervised.run(self.shell)
+
+    def run_reinforcement_wizard(self):
+        """
+        Run Reinforced Learning Wizard
+        """
+        self.reinforcement_wizard()
+        self.ri_values = self.wiz_reinforcement.run(self.shell)
+
+    def decide_unsupervised(self):
+        """
+        Decide which Unsupervised-learning to use
+        """
+        repro = False
+        clus_prob = False
+        if int(self.unsup_values['unsup_goal']) == 1:
+            # Clustering
+            if 'high' in self.data_size:
+                if not self.reproducibility:
+                    clus_prob = True
+                else:
+                    repro = True
+            else:
+                if 'y' in self.unsup_values['unsup_clus_dv'].tolower():
+                    if 'y' in self.unsup_values['unsup_clus_groups'].tolower():
+                        clus_prob = True
+                    else:
+                        print("Unsupervised Learning model to consider: Hierarchical Clustering")
+                        return
+                else:
+                    repro = True
+            if repro:
+                if 'y' in self.unsup_values['unsup_clus_outliers'].tolower():
+                    print("Unsupervised Learning model to consider: Hierarchical Clustering")
+                else:
+                    print("Unsupervised Learning model to consider: DBSCAN")
+                return
+            if clus_prob:
+                if 'y' in self.gen_values['data_output_prob'].tolower():
+                    print("Unsupervised Learning model to consider: Gaussian Mixture")
+                else:
+                    print("Unsupervised Learning model to consider: KMeans")
+                return
+        elif int(self.unsup_values['unsup_goal']) == 2:
+            # Dimensionality Reduction
+            if 'y' in self.unsup_values['unsup_dr_topic_mod'].tolower():
+                if 'y' in self.gen_values['data_output_prob'].tolower():
+                    print("Unsupervised Learning model to consider: SVD")
+                else:
+                    print("Unsupervised Learning model to consider: LDA")
+            else:
+                print("Unsupervised Learning model to consider: PCA")
+        else:
+            print("Sorry. We need to discuss, please connect with Anuket Thoth Project <sridhar.rao@spirent.com>")
+
+    def decide_reinforcement(self):
+        """
+        Decide which reinforement learning to use.
+        """
+        if (int(self.gen_values['data_type_output']) == 2 or
+                'y' in self.ri_values['ri_model_preference'].tolower()):
+            # Model Bsaed
+            if 'y' in self.ri_values['ri_model_availability'].tolower():
+                print("Reinforcement Learning model to consider - AlphaZero")
+            else:
+                print("Reinforcement Learning models to consider - World Models, I2A, MBMF, and MBVE")
+        elif 'n' in self.ri_values['ri_model_preference'].tolower():
+            # Model-Free based approach.
+            if 'y' not in self.ri_values['ri_modelfree_value'].tolower():
+                print("Reinforcement Learning models to consider: Policy Gradient and Actor Critic")
+            else:
+                if 'y' in self.ri_values['ri_modelfree_value_state'].tolower():
+                    print("Reinforcement Learning models to consider - Monte Carlo, TD(0), and TD(Lambda)")
+                else:
+                    print("Reinforcement Learning models to consider - SARSA, QLearning, Deep Queue Nets")
+        else:
+            # Default
+            print("Sorry. We need to discuss, please connect with Anuket Thoth Project <sridhar.rao@spirent.com>")
+
+    def perform_inference(self):
+        """
+        Perform Inferences. Used across all 3 types.
+        """
+        # Decide whether data is Low or High
+        self.data_size = 'unknown'
+        if ('k' in self.gen_values['data_size_bytes'].lower() or
+                't' in self.gen_values['data_size_samples']):
+            self.data_size = 'low'
+
+        if int(self.gen_values['metric_interpretability']) >= 3 :
+            self.interpretability = True
+        if int(self.gen_values['metric_speed']) >= 3 :
+            self.faster = True
+        if int(self.gen_values['metric_reproducibility']) >= 3 :
+            self.reproducibility = True
+
+        # Decide Features relative to Data (ftod_ratio) - high/low
+        if ('k' in self.gen_values['data_size_bytes'].lower() or
+                't' in self.gen_values['data_size_samples']):
+            if int(self.gen_values['data_features_count']) > 50:
+                self.ftod_ratio = 'high'
+        elif ('m' in self.gen_values['data_size_bytes'].lower() or
+                'm' in self.gen_values['data_size_samples']):
+            if int(self.gen_values['data_features_count']) > 5000:
+                self.ftod_ratio = 'high'
+        else:
+            if int(self.gen_values['data_features_count']) > 500000:
+                self.ftod_ratio = 'high'
+
+
+    def decide_supervised(self):
+        """
+        Decide which Supervised learning to use.
+        """
+        if 'high' in self.data_size:
+            # Cover: DT, RF, RNN, CNN, ANN and Naive Bayes
+            if self.interpretability:
+                if self.faster:
+                    print("Supervised Learning model to consider  - Decision Tree")
+                else:
+                    print("Supervised Learning model to consider  - Random Forest")
+            else:
+                if int(self.gen_values['data_column']) == 3:
+                    print("Supervised Learning model to consider  - RNN")
+                elif (int(self.gen_values['data_column']) == 2 and
+                        int(self.gen_values['data_signal_type']) == 1):
+                    print("Supervised Learning model to consider  - CNN")
+                elif (int(self.gen_values['data_column']) == 2 and
+                        (int(self.gen_values['data_signal_type']) == 2 or
+                            int(self.gen_values['data_signal_type']) == 3)):
+                    if 'y' in self.gen_values['data_output_prob'].tolower():
+                        print("Supervised Learning model to consider  - Naive Bayes")
+                    else:
+                        print("Supervised Learning model to consider  - ANN")
+                else:
+                    print("Supervised model to consider  Learning - ANN")
+        elif 'low' in self.data_size:
+            from_b = False
+            # Cover: Regressions
+            if 'high' in self.ftod_ratio:
+                from_b = True
+            else:
+                print("Supervised Learning model to consider  - SVN with Gaussian Kernel")
+                return
+            if int(self.gen_values['data_type_output']) != 2:
+                from_b = True
+            else:
+                if 'y' in self.gen_values['data_io_relation'].tolower():
+                    print("Supervised Learning model to consider  - Linear Regression or Linear SVM")
+                else:
+                    print("Supervised Learning model to consider  - Polynomial Regression or nonLinear SVM")
+                return
+            if from_b:
+                if int(self.gen_values['data_output_type']) == 4:
+                    if 'y' in self.gen_values['data_output_prob'].tolower():
+                        if 'y' in self.gen_values['data_cond_indep'].tolower():
+                            print("Supervised Learning model to consider  - Naive Bayes")
+                        else:
+                            if 'y' in self.gen_values['data_correlation'].tolower():
+                                print("Supervised Learning model to consider  - LASSO or Ridge Regression")
+                            else:
+                                print("Supervised Learning model to consider  - Logistic Regression")
+                    else:
+                        print("Supervised Learning model to consider  - Polynomial Regression or nonLinear SVM")
+
+                else:
+                    print("Supervised Learning model to consider - KNN")
+        else:
+            # Default
+            print("Sorry. We need to discuss, please connect with Anuket Thoth Project <sridhar.rao@spirent.com>")
+
+    def ask_and_decide(self):
+        """
+        THe Main Engine
+        """
+        self.run_mainwiz()
+        if self.ml_needed:
+            self.run_generic_wizard()
+            if self.supervised:
+                self.decide_supervised()
+            elif self.unsupervised:
+                self.run_unsupervised_wizard()
+                self.decide_unsupervised()
+            elif self.reinforcement:
+                self.run_reinforcement_wizard()
+                self.decide_reinforcement()
+
+
+def signal_handler(signum, frame):
+    """
+    Signal Handler
+    """
+    print("\n You interrupted, No Suggestion will be provided!")
+    print(signum, frame)
+    sys.exit(0)
+
+def main():
+    """
+    The Main Function
+    """
+    try:
+        algowiz = AlgoSelectorWizard()
+        algowiz.ask_and_decide()
+    except(KeyboardInterrupt, MemoryError):
+        print("Some Error Occured - No Suggestion can be provided")
+
+    print("Thanks for using the Algoselector-Wizard, " +
+            "Hope our suggestion will be useful")
+
+if __name__ == "__main__":
+    signal.signal(signal.SIGINT, signal_handler)
+    main()
diff --git a/tools/modelselector/requirements.txt b/tools/modelselector/requirements.txt
new file mode 100644 (file)
index 0000000..bd3b974
--- /dev/null
@@ -0,0 +1,2 @@
+future
+pypsi