diff --git a/machineLearning/rf/decisionTree.py b/machineLearning/rf/decisionTree.py index 42f05939c6531b8855bc6d607698d8b5ac2961e4..aea09a32dc6d9ac8f3bf1ff14232a0e94c08c438 100644 --- a/machineLearning/rf/decisionTree.py +++ b/machineLearning/rf/decisionTree.py @@ -278,15 +278,14 @@ class DecisionTree(): # iterating over raw predictions with ThreadPoolExecutor(max_workers=None) as executor: # Assuming 'rawPredictions' is a list of np.ndarray objects and you don't have 'nodes' yet. - predictions = list(executor.map(lambda args: self._leafFunction(*args), - zip([None] * len(rawPredictions), rawPredictions))) - + predictions = list(executor.map(self._leafFunction, rawPredictions)) else: predictions = [0] * len(data) # iterate through the rows of data for r in range(len(data)): - predictions[r] = self._traverse(self.root, data.data[r], False) + node = self._traverse(self.root, data.data[r]) + predictions[r] = node._bakedValues return np.array(predictions) @@ -304,7 +303,8 @@ class DecisionTree(): # iterate through the rows of data for r in range(len(data)): - predictions[r] = self._traverse(self.root, data.data[r], True) + node = self._traverse(self.root, data.data[r]) + predictions[r] = node._rawValues return predictions @@ -320,11 +320,11 @@ class DecisionTree(): for node in self.breadthFirst(): if node._rawValues is not None: - node._bakedValues = self._leafFunction(node, node._rawValues) + node._bakedValues = self._leafFunction(node) self._baked = True - def _traverse(self, node: Node, dataPoint: np.ndarray, raw: bool = True, returnNode: bool = False) -> ArrayLike: + def _traverse(self, node: Node, dataPoint: np.ndarray) -> Node: """ recursive function to traverse the (trained) tree """ @@ -335,15 +335,11 @@ class DecisionTree(): leftNode, rightNode = node.getChildren() # decide to go leftNode or rightNode? if (dataPoint[feature] <= threshold): - return self._traverse(leftNode, dataPoint, raw) + return self._traverse(leftNode, dataPoint) else: - return self._traverse(rightNode, dataPoint, raw) + return self._traverse(rightNode, dataPoint) else: - if raw is True: - # return the leaf value - return (node._rawValues, node) if returnNode else node._rawValues - else: - return (node._bakedValues, node) if returnNode else node._bakedValues + return node def accuracy(self, data: np.ndarray | DataSet, targets: np.ndarray = None) -> float: if isinstance(data, DataSet) is False: diff --git a/machineLearning/rf/leafFunction.py b/machineLearning/rf/leafFunction.py index d99b77d27c5001533f79520e01d35d3ca6624306..18ab3ff6ac1307ddc05cb205070273c631474ab1 100644 --- a/machineLearning/rf/leafFunction.py +++ b/machineLearning/rf/leafFunction.py @@ -10,19 +10,28 @@ class LeafFunction(ABC): def __init__(self) -> None: self.name = self.__class__.__name__ - def __call__(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def __call__(self, node: Node | np.ndarray) -> np.ndarray: """ calls '_leafFunc', makes working with this class easier """ - return self._leafFunc(node=node, rawPrediction=rawPrediction) + if isinstance(node, Node): + return self._leafFunc(rawPrediction=node._rawValues) + + return self._leafFunc(rawPrediction=node) @abstractmethod - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: """ needs to be implemented with every daughter class """ pass + def _raw(self, node: Node | np.ndarray) -> np.ndarray: + if isinstance(node, Node): + return node._rawValues + + return node + class Mode(LeafFunction): """ @@ -31,7 +40,7 @@ class Mode(LeafFunction): def __init__(self) -> None: super().__init__() - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: # If the rawPrediction is one-hot encoded, convert it to categorical if len(rawPrediction.shape) == 2 and rawPrediction.shape[1] > 1: targetsCategorical = np.argmax(rawPrediction, axis=1) @@ -51,7 +60,7 @@ class Mean(LeafFunction): def __init__(self) -> None: super().__init__() - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: return np.mean(rawPrediction) @@ -62,7 +71,7 @@ class Median(LeafFunction): def __init__(self) -> None: super().__init__() - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: return np.median(rawPrediction) @@ -75,7 +84,7 @@ class Probabilities(LeafFunction): super().__init__() self.numClasses = numClasses - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: # If rawPrediction is one-hot encoded, convert it to categorical if len(rawPrediction.shape) == 2 and rawPrediction.shape[1] > 1: targetsCategorical = np.argmax(rawPrediction, axis=1) @@ -102,7 +111,7 @@ class Confidence(LeafFunction): def __init__(self) -> None: super().__init__() - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def _leafFunc(self, rawPrediction: np.ndarray) -> np.ndarray: # Convert one-hot encoded rawPrediction to categorical if applicable if len(rawPrediction.shape) == 2 and rawPrediction.shape[1] > 1: targetsCategorical = np.argmax(rawPrediction, axis=1) @@ -129,10 +138,16 @@ class AnomalyDetection(LeafFunction): super().__init__() self.strategy = strategy # Determines which node attribute to use (e.g., 'level') - def _leafFunc(self, node: Node, rawPrediction: np.ndarray) -> np.ndarray: + def __call__(self, node: Node | np.ndarray) -> np.ndarray: + return self._leafFunc(node=node) + + def _leafFunc(self, node: Node) -> np.ndarray: # Use self.strategy to access the corresponding attribute from the node # Assuming 'level' is an attribute of the node indicating its depth or similar if hasattr(node, self.strategy): return np.array([getattr(node, self.strategy)]) else: raise AttributeError(f"Node does not have attribute '{self.strategy}'") + + def _raw(self, node: Node) -> np.ndarray: + return self._leafFunc(node=node, rawPrediction=rawPrediction) diff --git a/tree-test.ipynb b/tree-test.ipynb index 021d8e39e8d951c3a9e95a0d55187d7e495c2eea..bc16db92a7727651db0ece7fd096837dd3dba90a 100644 --- a/tree-test.ipynb +++ b/tree-test.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["# Testing the Tree\n","\n","## Importing the Basics"]},{"cell_type":"code","execution_count":1,"metadata":{"trusted":false},"outputs":[],"source":["import numpy as np\n","from matplotlib import pyplot as plt\n","from machineLearning.metric import ConfusionMatrix, RegressionScores\n","from machineLearning.utility import ModelIO\n","from machineLearning.data import DataSet\n","from machineLearning.rf import (\n"," DecisionTree,\n"," Gini, Entropy, MSE, MAE, ODD,\n"," Mode, Mean, Confidence, Probabilities,\n"," CART, ID3, C45, RSA,\n"," ReducedError, CostComplexity, PessimisticError\n",")"]},{"cell_type":"markdown","metadata":{},"source":["## Generating Test Data\n","\n","Here I generate random test data. It's two blocks shifted very slightly in some dimensions. For classifier tasks each block gets a label, for regressor tasks each block gets the average coordinates plus some random value as a traget. It's a very simple dummy data set meant for testing the code.\n","\n","Here one can change the dimensionallity and amount of the data."]},{"cell_type":"code","execution_count":2,"metadata":{"trusted":false},"outputs":[],"source":["def dataShift(dims):\n"," offSet = [5, 1.5, 2.5]\n"," diffLen = abs(len(offSet) - dims)\n"," offSet.extend([0] * diffLen)\n"," np.random.shuffle(offSet)\n"," return offSet[:dims]\n","\n","# Initialize some parameters\n","totalAmount = 64000\n","dims = 7\n","evalAmount = totalAmount // 4\n","trainAmount = totalAmount - evalAmount\n","offSet = dataShift(dims)\n","\n","# Create covariance matrix\n","cov = np.eye(dims) # This creates a covariance matrix with variances 1 and covariances 0\n","\n","# Generate random multivariate data\n","oneData = np.random.multivariate_normal(np.zeros(dims), cov, totalAmount)\n","twoData = np.random.multivariate_normal(offSet, cov, totalAmount)\n","\n","# Split the data into training and evaluation sets\n","trainData = np.vstack((oneData[:trainAmount], twoData[:trainAmount]))\n","validData = np.vstack((oneData[trainAmount:], twoData[trainAmount:]))\n","\n","# Labels for classification tasks\n","trainLabels = np.hstack((np.zeros(trainAmount), np.ones(trainAmount)))\n","validLabels = np.hstack((np.zeros(evalAmount), np.ones(evalAmount)))\n","\n","# Targets for regression tasks\n","trainTargets = np.sum(trainData, axis=1) + np.random.normal(0, 0.1, 2*trainAmount)\n","validTargets = np.sum(validData, axis=1) + np.random.normal(0, 0.1, 2*evalAmount)\n","\n","# Shuffle the training data\n","trainIndex = np.random.permutation(len(trainData))\n","trainData = trainData[trainIndex]\n","trainLabels = trainLabels[trainIndex]\n","trainTargets = trainTargets[trainIndex]\n","\n","trainSet = DataSet(trainData, targets=trainLabels)\n","validSet = DataSet(validData, targets=validLabels)"]},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[],"source":["def scatterPairwise(data, labels, size: float = 10):\n"," num_dims = data.shape[1]\n"," fig, axes = plt.subplots(num_dims, num_dims, figsize=(12, 12))\n","\n"," if len(labels.shape) > 1:\n"," labels = np.argmax(labels, axis=1)\n"," \n"," colors = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red']\n"," point_colors = [colors[label] for label in labels]\n","\n"," for i in range(num_dims):\n"," for j in range(num_dims):\n"," if i == j:\n"," axes[i][j].axis('off')\n"," else:\n"," axes[i][j].scatter(data[:, i], data[:, j], c=point_colors, s=size, alpha=0.5,label='data')\n"," axes[i][j].set_xlabel(f\"Dim {i}\")\n"," axes[i][j].set_ylabel(f\"Dim {j}\")\n"," plt.tight_layout()\n"," plt.show()"]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[],"source":["#scatterPairwise(trainData, trainLabels.astype('int'))"]},{"cell_type":"markdown","metadata":{},"source":["## Creating the Tree\n","\n","Here the tree is created. One can set the maximum depth of the tree. Depending on the task, we add a different impurity function and a different leaf function. Finally we add the split algorithm and set the feature percentile. Higher numbers look at more possible splits, but decreases speed. Lower numbers look at less possible splits, speeding up the algorithm. Depending on the data set this can have a strong impact on the performance."]},{"cell_type":"code","execution_count":5,"metadata":{"trusted":false},"outputs":[],"source":["task = 'classifier' # 'classifier'/'regressor'\n","tree = DecisionTree(maxDepth=5, minSamplesSplit=12)\n","if task == 'regressor':\n"," tree.setComponent(MSE())\n"," tree.setComponent(Mean())\n","elif task == 'classifier':\n"," tree.setComponent(Entropy())\n"," tree.setComponent(Mode())\n"," #tree.setComponent(Confidence())\n"," #tree.setComponent(Probabilities(2))\n","tree.setComponent(CART(featurePercentile=90))"]},{"cell_type":"markdown","metadata":{},"source":["## Trainining the tree\n","\n","Again, depending on the task we train the tree with targets or labels. Then we make a prediction and plot the tree."]},{"cell_type":"code","execution_count":6,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["tree 1 |⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿| done ✔ | 47%\n","—————————————————————— tree: 1/1 ———————————————————————\n","split: CART, impurity: Entropy, leaf: Mode, nodes: 31\n","maxDepth: 5, reached depth: 5, minSamplesSplit: 12\n","························································\n","â•´feat: 5 <= 2.80, samples: 96000\n"," ├─feat: 5 <= 1.98, samples: 48527\n"," │ ├─feat: 5 <= 1.42, samples: 46927\n"," │ │ ├─feat: 6 <= 1.85, samples: 44348\n"," │ │ │ └─╴value: 0.0\n"," │ │ │ └─╴value: 0.0\n"," │ │ └─╴feat: 6 <= 2.21, samples: 2579\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 6 <= 1.17, samples: 1600\n"," │ ├─feat: 1 <= 0.86, samples: 949\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 1 <= 0.54, samples: 651\n"," │ └─╴value: 0.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 6 <= 0.64, samples: 47473\n"," ├─feat: 5 <= 3.41, samples: 1566\n"," │ ├─feat: 1 <= 0.21, samples: 138\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 5 <= 4.25, samples: 1428\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 5 <= 3.20, samples: 45907\n"," ├─feat: 6 <= 1.31, samples: 1009\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 5 <= 3.70, samples: 44898\n"," └─╴value: 1.0\n"," └─╴value: 1.0\n"]}],"source":["tree.train(trainSet)\n","print(tree)"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[],"source":["tree.bake()"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[],"source":["prediction = tree.eval(validSet)"]},{"cell_type":"markdown","metadata":{},"source":["## Evaluating predictions\n","\n","Depending on the task at hand we create a confusion matrix (classification) or simple metrics (regression). Since the number of classes is fixed to two, we don't need to change anything here."]},{"cell_type":"code","execution_count":9,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["â”â”â”â”â”â”â”â”â”â”â”â” evaluation â”â”â”â”â”â”â”â”â”â”â”â”\n","————————— confusion matrix —————————\n"," Class 0 Class 1 \n","····································\n"," Class 0 15951 49 \n"," 49% 0% \n","····································\n"," Class 1 55 15945 \n"," 0% 49% \n","\n","———————————————————————————————— scores ———————————————————————————————\n"," accuracy precision sensitivity miss rate \n","·······································································\n"," Class 0 0.997 0.997 0.997 0.003 \n"," Class 1 0.997 0.997 0.997 0.003 \n","·······································································\n"," total 0.997 0.997 0.997 0.003 \n"]}],"source":["if task == 'regressor':\n"," metrics = RegressionScores(numClasses=2)\n"," metrics.calcScores(prediction, validTargets, validLabels)\n"," print(metrics)\n","elif task == 'classifier':\n"," confusion = ConfusionMatrix(numClasses=2)\n"," confusion.update(prediction, validLabels)\n"," confusion.percentages()\n"," confusion.calcScores()\n"," print(confusion)"]},{"cell_type":"markdown","metadata":{},"source":["## Saving and Loading a Tree\n","\n","Trees can be converted to dictionaries and then saved as a json file. This allows us to load them and re-use them. Also json is a raw text format, which is neat."]},{"cell_type":"code","execution_count":10,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["—————————————————————— tree: 1/2 ———————————————————————\n","split: CART, impurity: Entropy, leaf: Mode, nodes: 31\n","maxDepth: 5, reached depth: 5, minSamplesSplit: 12\n","························································\n","â•´feat: 5 <= 2.80, samples: 96000\n"," ├─feat: 5 <= 1.98, samples: 48527\n"," │ ├─feat: 5 <= 1.42, samples: 46927\n"," │ │ ├─feat: 6 <= 1.85, samples: 44348\n"," │ │ │ └─╴value: 0.0\n"," │ │ │ └─╴value: 0.0\n"," │ │ └─╴feat: 6 <= 2.21, samples: 2579\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 6 <= 1.17, samples: 1600\n"," │ ├─feat: 1 <= 0.86, samples: 949\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 1 <= 0.54, samples: 651\n"," │ └─╴value: 0.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 6 <= 0.64, samples: 47473\n"," ├─feat: 5 <= 3.41, samples: 1566\n"," │ ├─feat: 1 <= 0.21, samples: 138\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 5 <= 4.25, samples: 1428\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 5 <= 3.20, samples: 45907\n"," ├─feat: 6 <= 1.31, samples: 1009\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 5 <= 3.70, samples: 44898\n"," └─╴value: 1.0\n"," └─╴value: 1.0\n"]}],"source":["ModelIO.save(tree, 'tree-test')\n","newTree = ModelIO.load('tree-test')\n","print(newTree)"]},{"cell_type":"code","execution_count":11,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["â”â”â”â”â”â”â”â”â”â”â”â” evaluation â”â”â”â”â”â”â”â”â”â”â”â”\n","————————— confusion matrix —————————\n"," Class 0 Class 1 \n","····································\n"," Class 0 15951 49 \n"," 49% 0% \n","····································\n"," Class 1 55 15945 \n"," 0% 49% \n","\n","———————————————————————————————— scores ———————————————————————————————\n"," accuracy precision sensitivity miss rate \n","·······································································\n"," Class 0 0.997 0.997 0.997 0.003 \n"," Class 1 0.997 0.997 0.997 0.003 \n","·······································································\n"," total 0.997 0.997 0.997 0.003 \n"]}],"source":["prediction = newTree.eval(validData)\n","\n","if task == 'regressor':\n"," newMetrics = RegressionScores(numClasses=2)\n"," newMetrics.calcScores(prediction, validTargets, validLabels)\n"," print(newMetrics)\n","elif task == 'classifier':\n"," newConfusion = ConfusionMatrix(numClasses=2)\n"," newConfusion.update(prediction, validLabels)\n"," newConfusion.percentages()\n"," newConfusion.calcScores()\n"," print(newConfusion)"]},{"cell_type":"markdown","metadata":{},"source":["## Comment\n","\n","The tree works pretty well with both regression and classification tasks. Labels shouldn't be one-hot encoded, it works but it's still rather iffy. Targets should 1D, I haven't tested with 2D, it might work. Training can be really fast with a percentile set in the split algorithm, otherwise it can be rather slow. Making predictions work fast and well enough."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.2"},"vscode":{"interpreter":{"hash":"aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"}}},"nbformat":4,"nbformat_minor":2} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["# Testing the Tree\n","\n","## Importing the Basics"]},{"cell_type":"code","execution_count":1,"metadata":{"trusted":false},"outputs":[],"source":["import numpy as np\n","from matplotlib import pyplot as plt\n","from machineLearning.metric import ConfusionMatrix, RegressionScores\n","from machineLearning.utility import ModelIO\n","from machineLearning.data import DataSet\n","from machineLearning.rf import (\n"," DecisionTree,\n"," Gini, Entropy, MSE, MAE, ODD,\n"," Mode, Mean, Confidence, Probabilities,\n"," CART, ID3, C45, RSA,\n"," ReducedError, CostComplexity, PessimisticError\n",")"]},{"cell_type":"markdown","metadata":{},"source":["## Generating Test Data\n","\n","Here I generate random test data. It's two blocks shifted very slightly in some dimensions. For classifier tasks each block gets a label, for regressor tasks each block gets the average coordinates plus some random value as a traget. It's a very simple dummy data set meant for testing the code.\n","\n","Here one can change the dimensionallity and amount of the data."]},{"cell_type":"code","execution_count":2,"metadata":{"trusted":false},"outputs":[],"source":["def dataShift(dims):\n"," offSet = [5, 1.5, 2.5]\n"," diffLen = abs(len(offSet) - dims)\n"," offSet.extend([0] * diffLen)\n"," np.random.shuffle(offSet)\n"," return offSet[:dims]\n","\n","# Initialize some parameters\n","totalAmount = 64000\n","dims = 7\n","evalAmount = totalAmount // 4\n","trainAmount = totalAmount - evalAmount\n","offSet = dataShift(dims)\n","\n","# Create covariance matrix\n","cov = np.eye(dims) # This creates a covariance matrix with variances 1 and covariances 0\n","\n","# Generate random multivariate data\n","oneData = np.random.multivariate_normal(np.zeros(dims), cov, totalAmount)\n","twoData = np.random.multivariate_normal(offSet, cov, totalAmount)\n","\n","# Split the data into training and evaluation sets\n","trainData = np.vstack((oneData[:trainAmount], twoData[:trainAmount]))\n","validData = np.vstack((oneData[trainAmount:], twoData[trainAmount:]))\n","\n","# Labels for classification tasks\n","trainLabels = np.hstack((np.zeros(trainAmount), np.ones(trainAmount)))\n","validLabels = np.hstack((np.zeros(evalAmount), np.ones(evalAmount)))\n","\n","# Targets for regression tasks\n","trainTargets = np.sum(trainData, axis=1) + np.random.normal(0, 0.1, 2*trainAmount)\n","validTargets = np.sum(validData, axis=1) + np.random.normal(0, 0.1, 2*evalAmount)\n","\n","# Shuffle the training data\n","trainIndex = np.random.permutation(len(trainData))\n","trainData = trainData[trainIndex]\n","trainLabels = trainLabels[trainIndex]\n","trainTargets = trainTargets[trainIndex]\n","\n","trainSet = DataSet(trainData, targets=trainLabels)\n","validSet = DataSet(validData, targets=validLabels)"]},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[],"source":["def scatterPairwise(data, labels, size: float = 10):\n"," num_dims = data.shape[1]\n"," fig, axes = plt.subplots(num_dims, num_dims, figsize=(12, 12))\n","\n"," if len(labels.shape) > 1:\n"," labels = np.argmax(labels, axis=1)\n"," \n"," colors = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red']\n"," point_colors = [colors[label] for label in labels]\n","\n"," for i in range(num_dims):\n"," for j in range(num_dims):\n"," if i == j:\n"," axes[i][j].axis('off')\n"," else:\n"," axes[i][j].scatter(data[:, i], data[:, j], c=point_colors, s=size, alpha=0.5,label='data')\n"," axes[i][j].set_xlabel(f\"Dim {i}\")\n"," axes[i][j].set_ylabel(f\"Dim {j}\")\n"," plt.tight_layout()\n"," plt.show()"]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[],"source":["#scatterPairwise(trainData, trainLabels.astype('int'))"]},{"cell_type":"markdown","metadata":{},"source":["## Creating the Tree\n","\n","Here the tree is created. One can set the maximum depth of the tree. Depending on the task, we add a different impurity function and a different leaf function. Finally we add the split algorithm and set the feature percentile. Higher numbers look at more possible splits, but decreases speed. Lower numbers look at less possible splits, speeding up the algorithm. Depending on the data set this can have a strong impact on the performance."]},{"cell_type":"code","execution_count":5,"metadata":{"trusted":false},"outputs":[],"source":["task = 'classifier' # 'classifier'/'regressor'\n","tree = DecisionTree(maxDepth=5, minSamplesSplit=12)\n","if task == 'regressor':\n"," tree.setComponent(MSE())\n"," tree.setComponent(Mean())\n","elif task == 'classifier':\n"," tree.setComponent(Entropy())\n"," tree.setComponent(Mode())\n"," #tree.setComponent(Confidence())\n"," #tree.setComponent(Probabilities(2))\n","tree.setComponent(CART(featurePercentile=90))"]},{"cell_type":"markdown","metadata":{},"source":["## Trainining the tree\n","\n","Again, depending on the task we train the tree with targets or labels. Then we make a prediction and plot the tree."]},{"cell_type":"code","execution_count":6,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["tree 1 |⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿| done ✔ | 47%\n","—————————————————————— tree: 1/1 ———————————————————————\n","split: CART, impurity: Entropy, leaf: Mode, nodes: 31\n","maxDepth: 5, reached depth: 5, minSamplesSplit: 12\n","························································\n","â•´feat: 3 <= 2.81, samples: 96000\n"," ├─feat: 3 <= 2.00, samples: 48527\n"," │ ├─feat: 2 <= 2.32, samples: 46927\n"," │ │ ├─feat: 3 <= 1.35, samples: 46411\n"," │ │ │ └─╴value: 0.0\n"," │ │ │ └─╴value: 0.0\n"," │ │ └─╴feat: 3 <= 1.10, samples: 516\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 2 <= 1.45, samples: 1600\n"," │ ├─feat: 4 <= 1.10, samples: 1020\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 2 <= 2.12, samples: 580\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 3 <= 3.50, samples: 47473\n"," ├─feat: 2 <= 0.48, samples: 2609\n"," │ ├─feat: 2 <= -0.15, samples: 144\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 2 <= 2.00, samples: 2465\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 3 <= 3.90, samples: 44864\n"," ├─feat: 2 <= 1.14, samples: 3452\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 2 <= 0.20, samples: 41412\n"," └─╴value: 1.0\n"," └─╴value: 1.0\n"]}],"source":["tree.train(trainSet)\n","print(tree)"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[],"source":["tree.bake()"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[],"source":["prediction = tree.eval(validSet)"]},{"cell_type":"markdown","metadata":{},"source":["## Evaluating predictions\n","\n","Depending on the task at hand we create a confusion matrix (classification) or simple metrics (regression). Since the number of classes is fixed to two, we don't need to change anything here."]},{"cell_type":"code","execution_count":9,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["â”â”â”â”â”â”â”â”â”â”â”â” evaluation â”â”â”â”â”â”â”â”â”â”â”â”\n","————————— confusion matrix —————————\n"," Class 0 Class 1 \n","····································\n"," Class 0 15950 50 \n"," 49% 0% \n","····································\n"," Class 1 58 15942 \n"," 0% 49% \n","\n","———————————————————————————————— scores ———————————————————————————————\n"," accuracy precision sensitivity miss rate \n","·······································································\n"," Class 0 0.997 0.996 0.997 0.003 \n"," Class 1 0.997 0.997 0.996 0.004 \n","·······································································\n"," total 0.997 0.997 0.997 0.003 \n"]}],"source":["if task == 'regressor':\n"," metrics = RegressionScores(numClasses=2)\n"," metrics.calcScores(prediction, validTargets, validLabels)\n"," print(metrics)\n","elif task == 'classifier':\n"," confusion = ConfusionMatrix(numClasses=2)\n"," confusion.update(prediction, validLabels)\n"," confusion.percentages()\n"," confusion.calcScores()\n"," print(confusion)"]},{"cell_type":"markdown","metadata":{},"source":["## Saving and Loading a Tree\n","\n","Trees can be converted to dictionaries and then saved as a json file. This allows us to load them and re-use them. Also json is a raw text format, which is neat."]},{"cell_type":"code","execution_count":10,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["—————————————————————— tree: 1/2 ———————————————————————\n","split: CART, impurity: Entropy, leaf: Mode, nodes: 31\n","maxDepth: 5, reached depth: 5, minSamplesSplit: 12\n","························································\n","â•´feat: 3 <= 2.81, samples: 96000\n"," ├─feat: 3 <= 2.00, samples: 48527\n"," │ ├─feat: 2 <= 2.32, samples: 46927\n"," │ │ ├─feat: 3 <= 1.35, samples: 46411\n"," │ │ │ └─╴value: 0.0\n"," │ │ │ └─╴value: 0.0\n"," │ │ └─╴feat: 3 <= 1.10, samples: 516\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 2 <= 1.45, samples: 1600\n"," │ ├─feat: 4 <= 1.10, samples: 1020\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 0.0\n"," │ └─╴feat: 2 <= 2.12, samples: 580\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 3 <= 3.50, samples: 47473\n"," ├─feat: 2 <= 0.48, samples: 2609\n"," │ ├─feat: 2 <= -0.15, samples: 144\n"," │ │ └─╴value: 0.0\n"," │ │ └─╴value: 1.0\n"," │ └─╴feat: 2 <= 2.00, samples: 2465\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 3 <= 3.90, samples: 44864\n"," ├─feat: 2 <= 1.14, samples: 3452\n"," │ └─╴value: 1.0\n"," │ └─╴value: 1.0\n"," └─╴feat: 2 <= 0.20, samples: 41412\n"," └─╴value: 1.0\n"," └─╴value: 1.0\n"]}],"source":["ModelIO.save(tree, 'tree-test')\n","newTree = ModelIO.load('tree-test')\n","print(newTree)"]},{"cell_type":"code","execution_count":11,"metadata":{"trusted":false},"outputs":[{"name":"stdout","output_type":"stream","text":["â”â”â”â”â”â”â”â”â”â”â”â” evaluation â”â”â”â”â”â”â”â”â”â”â”â”\n","————————— confusion matrix —————————\n"," Class 0 Class 1 \n","····································\n"," Class 0 15950 50 \n"," 49% 0% \n","····································\n"," Class 1 58 15942 \n"," 0% 49% \n","\n","———————————————————————————————— scores ———————————————————————————————\n"," accuracy precision sensitivity miss rate \n","·······································································\n"," Class 0 0.997 0.996 0.997 0.003 \n"," Class 1 0.997 0.997 0.996 0.004 \n","·······································································\n"," total 0.997 0.997 0.997 0.003 \n"]}],"source":["prediction = newTree.eval(validData)\n","\n","if task == 'regressor':\n"," newMetrics = RegressionScores(numClasses=2)\n"," newMetrics.calcScores(prediction, validTargets, validLabels)\n"," print(newMetrics)\n","elif task == 'classifier':\n"," newConfusion = ConfusionMatrix(numClasses=2)\n"," newConfusion.update(prediction, validLabels)\n"," newConfusion.percentages()\n"," newConfusion.calcScores()\n"," print(newConfusion)"]},{"cell_type":"markdown","metadata":{},"source":["## Comment\n","\n","The tree works pretty well with both regression and classification tasks. Labels shouldn't be one-hot encoded, it works but it's still rather iffy. Targets should 1D, I haven't tested with 2D, it might work. Training can be really fast with a percentile set in the split algorithm, otherwise it can be rather slow. Making predictions work fast and well enough."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.2"},"vscode":{"interpreter":{"hash":"aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"}}},"nbformat":4,"nbformat_minor":2} diff --git a/tree-test.json b/tree-test.json index fc5caddab5b98f31b01b1a009016377cda9a3775..99be817dbc0196b8145041af6b8e79a46fc9dc99 100644 --- a/tree-test.json +++ b/tree-test.json @@ -1,5 +1,5 @@ { - "datetime": "2024-03-06T11:32:47.341188", + "datetime": "2024-03-06T16:29:39.908741", "qualifiedName": [ "machineLearning.rf.decisionTree", "DecisionTree" @@ -24,8 +24,8 @@ }, "nodes": { "0": { - "threshold": 2.8045007004076017, - "feature": 5, + "threshold": 2.807746776617831, + "feature": 3, "leftID": 1, "rightID": 2, "id": 0, @@ -35,8 +35,8 @@ "bakedValues": null }, "1": { - "threshold": 1.9825512847824447, - "feature": 5, + "threshold": 1.9953861771415191, + "feature": 3, "leftID": 3, "rightID": 4, "id": 1, @@ -46,8 +46,8 @@ "bakedValues": null }, "2": { - "threshold": 0.6432434876920601, - "feature": 6, + "threshold": 3.4962004922651033, + "feature": 3, "leftID": 17, "rightID": 18, "id": 2, @@ -57,8 +57,8 @@ "bakedValues": null }, "3": { - "threshold": 1.4205459392650406, - "feature": 5, + "threshold": 2.315341617113765, + "feature": 2, "leftID": 5, "rightID": 6, "id": 3, @@ -68,8 +68,8 @@ "bakedValues": null }, "4": { - "threshold": 1.1736438849917308, - "feature": 6, + "threshold": 1.4480678071564441, + "feature": 2, "leftID": 11, "rightID": 12, "id": 4, @@ -79,8 +79,8 @@ "bakedValues": null }, "17": { - "threshold": 3.410128215062842, - "feature": 5, + "threshold": 0.4828316691790929, + "feature": 2, "leftID": 19, "rightID": 20, "id": 17, @@ -90,8 +90,8 @@ "bakedValues": null }, "18": { - "threshold": 3.195155392018936, - "feature": 5, + "threshold": 3.9011436871916234, + "feature": 3, "leftID": 25, "rightID": 26, "id": 18, @@ -101,8 +101,8 @@ "bakedValues": null }, "5": { - "threshold": 1.8544541538944455, - "feature": 6, + "threshold": 1.3547325862135289, + "feature": 3, "leftID": 7, "rightID": 8, "id": 5, @@ -112,8 +112,8 @@ "bakedValues": null }, "6": { - "threshold": 2.211597938941258, - "feature": 6, + "threshold": 1.1049786807903943, + "feature": 3, "leftID": 9, "rightID": 10, "id": 6, @@ -123,8 +123,8 @@ "bakedValues": null }, "11": { - "threshold": 0.8597362843140841, - "feature": 1, + "threshold": 1.0980478176609991, + "feature": 4, "leftID": 13, "rightID": 14, "id": 11, @@ -134,8 +134,8 @@ "bakedValues": null }, "12": { - "threshold": 0.5408002329144731, - "feature": 1, + "threshold": 2.1243444832069356, + "feature": 2, "leftID": 15, "rightID": 16, "id": 12, @@ -145,8 +145,8 @@ "bakedValues": null }, "19": { - "threshold": 0.21063374040010793, - "feature": 1, + "threshold": -0.1460235375773515, + "feature": 2, "leftID": 21, "rightID": 22, "id": 19, @@ -156,8 +156,8 @@ "bakedValues": null }, "20": { - "threshold": 4.254949560156416, - "feature": 5, + "threshold": 1.997877967289944, + "feature": 2, "leftID": 23, "rightID": 24, "id": 20, @@ -167,8 +167,8 @@ "bakedValues": null }, "25": { - "threshold": 1.307014361039274, - "feature": 6, + "threshold": 1.1395847185160846, + "feature": 2, "leftID": 27, "rightID": 28, "id": 25, @@ -178,8 +178,8 @@ "bakedValues": null }, "26": { - "threshold": 3.7044624172687723, - "feature": 5, + "threshold": 0.20123386854707728, + "feature": 2, "leftID": 29, "rightID": 30, "id": 26, @@ -2981,6 +2981,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -43081,20 +43082,6 @@ 0.0, 0.0, 0.0, - 0.0, - 0.0 - ], - "bakedValues": null - }, - "8": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 8, - "isRoot": false, - "parent": 5, - "rawValues": [ 0.0, 0.0, 0.0, @@ -43271,7 +43258,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -43345,12 +43331,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 0.0, 0.0, 0.0, @@ -43567,6 +43547,19 @@ 0.0, 0.0, 0.0, + 0.0 + ], + "bakedValues": null + }, + "8": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 8, + "isRoot": false, + "parent": 5, + "rawValues": [ 0.0, 0.0, 0.0, @@ -43664,6 +43657,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -43875,7 +43869,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -44039,7 +44032,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -44057,6 +44049,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44153,8 +44146,10 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44364,6 +44359,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44403,6 +44399,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44556,19 +44553,6 @@ 0.0, 0.0, 0.0, - 0.0 - ], - "bakedValues": null - }, - "9": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 9, - "isRoot": false, - "parent": 6, - "rawValues": [ 0.0, 0.0, 0.0, @@ -44578,6 +44562,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44676,6 +44661,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44729,6 +44715,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44762,7 +44749,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -44911,6 +44897,7 @@ 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -44974,15 +44961,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 0.0, 0.0, 0.0, @@ -45099,7 +45077,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -45146,6 +45123,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -45199,7 +45177,9 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -45242,8 +45222,10 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -45352,21 +45334,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 0.0, 0.0, 0.0, @@ -45582,6 +45549,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -45690,15 +45658,6 @@ 0.0, 0.0, 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 1.0, 0.0, 0.0, @@ -45728,7 +45687,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -45777,7 +45735,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -45823,7 +45780,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -45967,6 +45923,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -46114,6 +46071,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -46357,7 +46315,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46394,6 +46351,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -46420,6 +46378,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -46433,7 +46392,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46455,6 +46413,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -46568,7 +46527,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46615,7 +46573,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46648,7 +46605,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46663,6 +46619,19 @@ 0.0, 0.0, 0.0, + 0.0 + ], + "bakedValues": null + }, + "9": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 9, + "isRoot": false, + "parent": 6, + "rawValues": [ 0.0, 0.0, 0.0, @@ -46721,7 +46690,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -46770,8 +46738,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, 0.0, 0.0, 0.0, @@ -47103,35 +47069,22 @@ "isRoot": false, "parent": 6, "rawValues": [ - 0.0, 1.0, - 0.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 0.0, - 0.0, - 1.0, 1.0, - 1.0, - 0.0, - 0.0, - 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, - 0.0, - 0.0, - 0.0, 1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, @@ -47140,15 +47093,11 @@ 1.0, 1.0, 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, + 0.0, 0.0, 0.0, 1.0, - 1.0, + 0.0, 0.0, 0.0, 1.0, @@ -47156,63 +47105,73 @@ 0.0, 1.0, 0.0, - 0.0, 1.0, 1.0, - 1.0 - ], - "bakedValues": null - }, - "13": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 13, - "isRoot": false, - "parent": 11, - "rawValues": [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 1.0, 0.0, + 1.0, + 1.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 1.0, 0.0, + 1.0, + 1.0, + 1.0, 0.0, + 1.0, + 1.0, 0.0, 0.0, 0.0, + 0.0 + ], + "bakedValues": null + }, + "13": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 13, + "isRoot": false, + "parent": 11, + "rawValues": [ 0.0, 0.0, 0.0, @@ -47222,6 +47181,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47240,6 +47200,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47258,6 +47219,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47265,25 +47227,7 @@ 0.0, 0.0, 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47361,22 +47305,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, 0.0, 0.0, 0.0, @@ -47396,6 +47324,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47428,6 +47357,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47436,7 +47366,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47478,6 +47407,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47537,6 +47467,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47546,7 +47477,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47569,7 +47499,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47637,9 +47566,6 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, 0.0, 0.0, 0.0, @@ -47791,6 +47717,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47809,12 +47736,14 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47865,6 +47794,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47878,6 +47808,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47898,6 +47829,7 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, @@ -47907,37 +47839,23 @@ 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, - 0.0, - 0.0 - ], - "bakedValues": null - }, - "14": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 14, - "isRoot": false, - "parent": 11, - "rawValues": [ 0.0, 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47949,16 +47867,10 @@ 0.0, 0.0, 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47966,7 +47878,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -47976,26 +47887,21 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48003,10 +47909,6 @@ 0.0, 0.0, 0.0, - 1.0, - 1.0, - 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48014,19 +47916,14 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48038,7 +47935,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48058,28 +47954,24 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 0.0, @@ -48090,26 +47982,17 @@ 0.0, 0.0, 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 1.0, 0.0, 0.0, - 1.0, 0.0, 0.0, - 1.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48118,53 +48001,35 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, - 1.0, 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, - 0.0, 0.0 ], "bakedValues": null }, - "15": { + "14": { "threshold": null, "feature": null, "leftID": null, "rightID": null, - "id": 15, + "id": 14, "isRoot": false, - "parent": 12, + "parent": 11, "rawValues": [ - 1.0, 0.0, 0.0, 0.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 1.0, - 1.0, - 0.0, 1.0, - 0.0, 1.0, 0.0, - 1.0, - 1.0, - 1.0, - 1.0, 0.0, 0.0, 1.0, @@ -48180,12 +48045,10 @@ 0.0, 0.0, 1.0, - 0.0, 1.0, 1.0, 0.0, 1.0, - 0.0, 1.0, 0.0, 0.0, @@ -48196,32 +48059,33 @@ 0.0, 0.0, 1.0, - 0.0, - 1.0, 1.0, 1.0, 0.0, 0.0, - 1.0, 0.0, - 1.0, + 0.0, + 0.0, 0.0, 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 0.0, 0.0, @@ -48229,37 +48093,42 @@ 1.0, 1.0, 0.0, + 1.0, 0.0, + 1.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 1.0, 0.0, 0.0, - 1.0, - 1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, + 0.0, + 0.0, + 0.0, + 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, - 1.0, + 0.0, + 0.0, 0.0, 0.0, 1.0, + 0.0, 1.0, 1.0, 0.0, - 1.0, + 0.0, 1.0, 1.0, 0.0, @@ -48267,9 +48136,6 @@ 0.0, 1.0, 1.0, - 0.0, - 0.0, - 0.0, 1.0, 0.0, 0.0, @@ -48280,72 +48146,94 @@ 1.0, 1.0, 1.0, + 1.0, + 0.0, + 0.0, 0.0, 0.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 1.0, + 0.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, - 1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, 1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 1.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, - 1.0, + 0.0, 0.0, 1.0, 1.0, - 1.0, + 0.0, + 0.0, 0.0, 1.0, + 0.0, + 0.0, 1.0, - 1.0 + 0.0, + 0.0 ], "bakedValues": null }, - "16": { + "15": { "threshold": null, "feature": null, "leftID": null, "rightID": null, - "id": 16, + "id": 15, "isRoot": false, "parent": 12, "rawValues": [ + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, 1.0, 0.0, 1.0, + 0.0, + 0.0, 1.0, + 0.0, + 0.0, 1.0, 1.0, 1.0, @@ -48353,15 +48241,17 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48371,32 +48261,52 @@ 1.0, 1.0, 0.0, + 0.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, 1.0, 1.0, @@ -48404,21 +48314,31 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, + 1.0, + 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -48426,21 +48346,48 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, + 0.0, + 0.0, + 0.0, + 0.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, + 1.0 + ], + "bakedValues": null + }, + "16": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 16, + "isRoot": false, + "parent": 12, + "rawValues": [ 1.0, 1.0, 1.0, @@ -48472,13 +48419,13 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48509,13 +48456,11 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48546,7 +48491,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48588,7 +48532,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48631,12 +48574,10 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48676,6 +48617,14 @@ 1.0, 1.0, 1.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, 1.0, 1.0, 1.0, @@ -48708,11 +48657,15 @@ 1.0, 1.0, 1.0, + 0.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, + 0.0, 1.0, 1.0, 1.0, @@ -48725,7 +48678,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48735,7 +48687,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48746,6 +48697,9 @@ 1.0, 1.0, 1.0, + 0.0, + 1.0, + 1.0, 1.0, 1.0, 1.0, @@ -48758,7 +48712,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48770,7 +48723,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48800,13 +48752,61 @@ 1.0, 1.0, 1.0, - 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, + 1.0, 1.0 ], "bakedValues": null @@ -48831,7 +48831,6 @@ 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, @@ -48850,6 +48849,13 @@ 0.0, 0.0, 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, 0.0, 0.0, 0.0, @@ -48857,6 +48863,9 @@ 0.0, 0.0, 1.0, + 0.0, + 0.0, + 0.0, 1.0, 0.0, 0.0, @@ -48869,7 +48878,13 @@ 0.0, 0.0, 0.0, - 0.0 + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 ], "bakedValues": null }, @@ -48884,18 +48899,8 @@ "rawValues": [ 0.0, 1.0, - 0.0, - 1.0, - 1.0, 1.0, 1.0, - 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 0.0, - 1.0, 1.0, 1.0, 1.0, @@ -48904,15 +48909,9 @@ 0.0, 1.0, 0.0, - 0.0, 1.0, 1.0, 1.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -48920,40 +48919,45 @@ 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0, 0.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, - 1.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, - 1.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, - 0.0, + 1.0, 1.0, 1.0, 1.0, @@ -48965,10 +48969,12 @@ 1.0, 1.0, 1.0, + 1.0, 0.0, 0.0, 1.0, 1.0, + 0.0, 1.0 ], "bakedValues": null @@ -48995,29 +49001,6 @@ 1.0, 1.0, 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, 1.0, 1.0, 1.0, @@ -49054,6 +49037,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49070,8 +49054,6 @@ 1.0, 1.0, 1.0, - 0.0, - 0.0, 1.0, 1.0, 1.0, @@ -49080,10 +49062,7 @@ 1.0, 1.0, 1.0, - 0.0, - 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -49120,22 +49099,20 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -49148,6 +49125,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49185,6 +49163,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49199,6 +49178,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49213,7 +49193,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -49224,7 +49203,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -49246,7 +49224,9 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49263,20 +49243,6 @@ 1.0, 1.0, 1.0, - 1.0, - 1.0 - ], - "bakedValues": null - }, - "24": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 24, - "isRoot": false, - "parent": 20, - "rawValues": [ 1.0, 1.0, 1.0, @@ -49314,6 +49280,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49339,6 +49306,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49359,13 +49327,16 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49388,8 +49359,10 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49399,8 +49372,10 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49412,6 +49387,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49444,6 +49420,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49500,6 +49477,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49527,6 +49505,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49553,6 +49532,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49589,15 +49569,18 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49608,13 +49591,16 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49668,8 +49654,11 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49719,6 +49708,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49746,6 +49736,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -49755,6 +49746,19 @@ 1.0, 1.0, 1.0, + 1.0 + ], + "bakedValues": null + }, + "24": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 24, + "isRoot": false, + "parent": 20, + "rawValues": [ 1.0, 1.0, 1.0, @@ -50421,36 +50425,19 @@ 1.0, 1.0, 1.0, - 1.0 - ], - "bakedValues": null - }, - "27": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 27, - "isRoot": false, - "parent": 25, - "rawValues": [ 1.0, 1.0, 1.0, 1.0, - 0.0, - 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50458,26 +50445,20 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50485,7 +50466,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50502,7 +50482,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50533,19 +50512,6 @@ 1.0, 1.0, 1.0, - 0.0 - ], - "bakedValues": null - }, - "28": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 28, - "isRoot": false, - "parent": 25, - "rawValues": [ 1.0, 1.0, 1.0, @@ -50748,7 +50714,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50831,7 +50796,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -50996,7 +50960,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -51454,19 +51417,6 @@ 1.0, 1.0, 1.0, - 1.0 - ], - "bakedValues": null - }, - "29": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 29, - "isRoot": false, - "parent": 26, - "rawValues": [ 1.0, 1.0, 1.0, @@ -51514,6 +51464,19 @@ 1.0, 1.0, 1.0, + 1.0 + ], + "bakedValues": null + }, + "27": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 27, + "isRoot": false, + "parent": 25, + "rawValues": [ 1.0, 1.0, 1.0, @@ -51541,6 +51504,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51586,6 +51550,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51627,6 +51592,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51659,6 +51625,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51669,6 +51636,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51681,6 +51649,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51695,6 +51664,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51707,14 +51677,17 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51752,6 +51725,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51790,6 +51764,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -51805,6 +51780,19 @@ 1.0, 1.0, 1.0, + 1.0 + ], + "bakedValues": null + }, + "28": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 28, + "isRoot": false, + "parent": 25, + "rawValues": [ 1.0, 1.0, 1.0, @@ -52007,7 +51995,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -52908,6 +52895,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -53167,7 +53155,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -53322,6 +53309,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -53879,7 +53867,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -54187,7 +54174,6 @@ 1.0, 1.0, 1.0, - 0.0, 1.0, 1.0, 1.0, @@ -54427,19 +54413,6 @@ 1.0, 1.0, 1.0, - 1.0 - ], - "bakedValues": null - }, - "30": { - "threshold": null, - "feature": null, - "leftID": null, - "rightID": null, - "id": 30, - "isRoot": false, - "parent": 26, - "rawValues": [ 1.0, 1.0, 1.0, @@ -54967,6 +54940,19 @@ 1.0, 1.0, 1.0, + 1.0 + ], + "bakedValues": null + }, + "29": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 29, + "isRoot": false, + "parent": 26, + "rawValues": [ 1.0, 1.0, 1.0, @@ -55240,6 +55226,7 @@ 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, @@ -55421,6 +55408,19 @@ 1.0, 1.0, 1.0, + 1.0 + ], + "bakedValues": null + }, + "30": { + "threshold": null, + "feature": null, + "leftID": null, + "rightID": null, + "id": 30, + "isRoot": false, + "parent": 26, + "rawValues": [ 1.0, 1.0, 1.0,