from abc import abstractmethod
import json
import os
import pickle
import numpy as np
from typing import List
from OpenRacer.Recorder import Recorder
from OpenRacer.datatypes import Params
[docs]class ModelInterface:
def __init__(self):
"""Initializing BaseClass for AI Model
"""
print("Model started")
self.session = 0
self.models = dict()
self.currentModel = None
self.recorder: Recorder = None
self.currentPath: str = None
[docs] def addModel(self, model):
"""add ML model with a name atteibute. model.name should be a non empty string.
Args:
model (class): a class of ML that will have eval, train, backPropagate, loss fn
"""
if not hasattr(model, "name"):
print(f"class {model} doesn't contain attribute name.")
exit(0)
return
recorder = Recorder(model.name)
self.models[model.name] = {"model": model, "recorder": recorder}
[docs] def setModel(self, modelName):
"""set model to be used for eval and train
Args:
modelName (str): name of model to be used. it should be present in added models.
"""
if self.models.get(modelName):
self.currentModel = self.models[modelName]["model"]
self.recorder = self.models[modelName]["recorder"]
self.currentPath = os.path.join(os.getcwd(), self.currentModel.name, "model")
self.currentFile = os.path.join(self.currentPath, f"{self.currentModel.name}.pkl")
os.makedirs(self.currentPath, exist_ok=True)
[docs] def getModel(self):
""" returns model that will be used for training and testing """
return self.currentModel
[docs] def setTrack(self, track:List[List[float]]):
"""Setting track coordniates for the session
Args:
track (List[Tuple[float]]): List of Tuple of coordinates. (x, y, z). Coordinates are according to Unity. x, z should be used for 2Dcase. (x,z) => (x,y)
"""
self.track = track
for model in self.models:
self.models[model]["model"].track = track
[docs] def sessionEnd(self, sessionNum:int):
"""called after every epoc end to save model. for more control you can add save function to model class and it will be called and interface will not save it.
Args:
sessionNum (int): number of epoch ended
"""
print(f"Session {sessionNum} ended. Saving model checkpoint")
if getattr(self.currentModel, "save"):
self.currentModel.save(sessionNum, self.currentPath)
else:
self.currentFile = os.path.join(self.currentPath, f"{self.currentModel.name}-{self.session}.pkl")
pickle.dump(self.currentModel, open(self.currentFile, 'wb'))
self.session = sessionNum+1
[docs] def eval(self, inputDataFromUnity:str, isTraining:bool=False) -> dict:
"""This is the function that will be called on each step to evaluate what to do.
Args:
inputDataFromUnity (str): This is unformatted data from Unity.
isTraining (bool, optional): Is this a step in training. If true then It will call backpropogate. Defaults to False.
Returns:
dict: This is action dict that contains x, y input for Car AI in Unity.
"""
formattedInputData = self.formatInput(inputDataFromUnity)
inputData = self.preProcess(formattedInputData)
if isTraining:
if self.action:
reward = self.rewardFn(self.action, inputData)
self.backprop(self.action, inputData)
self.action = self.trainEval(inputData)
self.recorder.recordStep(formattedInputData, action, reward, self.session)
else:
if self.action:
reward = self.rewardFn(action, inputData)
action = self.testEval(inputData)
self.recorder.recordRaceStep(formattedInputData, action, reward, self.session)
return self.formatAction(action)
[docs] def trainEval(self, inputData) -> List[List[float]]:
"""
This will be called for each step in training. it will call trainEval function of current model
Args:
inputData (Returned from PreProcess): This will contain same object returned from preProcess. If not set by default it will get Params.
Returns:
List[List[float]]: It should return a list of actions need to be taken by Agent.
Note:
Range should be [-1,1] in both axis.
x: [-1, 1] -> [Backward, Forward]
y: [-1, 1] -> [Left, Right]
Example:
For eg: For 2 agents,
[
[0.5, 0.2], #Agent 1: 0.5 forward and 0.2 towards Right
[0.6, -0.1] #Agent 2: 0.6 forward and 0.1 towards Left
]
"""
return self.currentModel.trainEval(inputData)
[docs] def testEval(self, inputData):
"""
This will be called for each step in testing/Race. It will call testEval of current model
Args:
inputData (Returned from PreProcess): This will contain same object returned from preProcess. If not set by default it will get Params.
Returns:
List[List]: It should return a list of actions need to be taken by Agent.
Range should be [-1,1] in both axis.
x: [-1, 1] -> [Backward, Forward]
y: [-1, 1] -> [Left, Right]
For eg: For 2 agents,
[
[0.5, 0.2], #Agent 1: 0.5 forward and 0.2 towards Right
[0.6, -0.1] #Agent 2: 0.6 forward and 0.1 towards Left
]
"""
return self.currentModel.testEval(inputData)
[docs] def rewardFn(self, action:List[List[float]], inputData) -> List[float]:
"""It will be called on each step. you can define how to reward your Agent based on its input and action. it will call reward function on current model
Args:
action (List[List[float]]): It will be a 2D List. [[0.1,0.2], [0.3,-0.1]]
inputData (Params|Object): It would be same input data as provided in trainEval/TestEval.
Returns:
List[float]: Rewards for each Agent.
"""
return self.currentModel.rewardFn(action, inputData)
[docs] def backprop(self, action, inputData):
"""This will be called while training after every step this can be used to evaluate your step and adjust the model.
Args:
action (List[List[float]]): It will be a 2D List. [[0.1,0.2], [0.3,-0.1]]
inputData (Params|Object): It would be same input data as provided in trainEval/TestEval.
"""
return self.currentModel.backprop(action, inputData)
[docs] def preProcess(self, inputData:Params):
"""Incase you want to preprocess your inputs.
Args:
inputData (Params): This is data received from Unity
Returns:
Processed Data. Default is Params.
"""
if hasattr(self.currentModel, "preProcess"):
return self.currentModel.preProcess(inputData)
return inputData
[docs]class ModelBase:
def __init__(self):
self.name = "unknown"
[docs] def preProcess(self, inputData:Params):
"""Incase you want to preprocess your inputs.
Args:
inputData (Params): This is data received from Unity
Returns:
Processed Data. Default is Params.
"""
return inputData
[docs] def trainEval(self, inputData) -> List[List[float]]:
"""
This will be called for each step in training. it will call trainEval function of current model
Note:
output range should be [-1,1] in both axis.
x: [-1, 1] -> [Backward, Forward]
y: [-1, 1] -> [Left, Right]
Example output:
For eg: For 2 agents,
[
[0.5, 0.2], #Agent 1: 0.5 forward and 0.2 towards Right
[0.6, -0.1] #Agent 2: 0.6 forward and 0.1 towards Left
]
Args:
inputData (Returned from PreProcess): This will contain same object returned from preProcess. If not set by default it will get Params.
Returns:
List[List[float]]: It should return a list of actions need to be taken by Agent.
"""
Exception(f"Train Eval (trainEval) function not delcared for {self.name}")
[docs] def testEval(self, inputData):
"""
This will be called for each step in testing/Race. It will call testEval of current model
Note:
output range should be [-1,1] in both axis.
x: [-1, 1] -> [Backward, Forward]
y: [-1, 1] -> [Left, Right]
Example output:
For eg: For 2 agents,
[
[0.5, 0.2], #Agent 1: 0.5 forward and 0.2 towards Right
[0.6, -0.1] #Agent 2: 0.6 forward and 0.1 towards Left
]
Args:
inputData (Returned from PreProcess): This will contain same object returned from preProcess. If not set by default it will get Params.
Returns:
List[List]: It should return a list of actions need to be taken by Agent.
"""
Exception(f"Test Eval (testEval) function not delcared for {self.name}")
[docs] def rewardFn(self, action:List[List[float]], inputData) -> List[float]:
"""It will be called on each step. you can define how to reward your Agent based on its input and action. it will call reward function on current model
Args:
action (List[List[float]]): It will be a 2D List. [[0.1,0.2], [0.3,-0.1]]
inputData (Params|Object): It would be same input data as provided in trainEval/TestEval.
Returns:
List[float]: Rewards for each Agent.
"""
Exception(f"reward function not delcared for {self.name}")
[docs] def backprop(self, action, inputData):
"""This will be called while training after every step this can be used to evaluate your step and adjust the model.
Args:
action (List[List[float]]): It will be a 2D List. [[0.1,0.2], [0.3,-0.1]]
inputData (Params|Object): It would be same input data as provided in trainEval/TestEval.
"""
Exception(f"backprop function not delcared for {self.name}")
[docs] def save(self, sessionNum, dirPath):
"""this function will be called at the end of every session to save the model. user needs to implement save functionality.
it can be saved on the directory path provided in the input.
Args:
sessionNum (int): session number of currently ended session.
dirPath (str): path to the directory created by OpenRacer module.
"""
print(f"Save not implemented. Not saving model for {self.name}")