From 6c730df96c1d7c447b7fc42873126bf66035382e Mon Sep 17 00:00:00 2001 From: johannes bilk <johannes.bilk-2@exp2.physik.uni-giessen.de> Date: Wed, 6 Mar 2024 16:30:32 +0100 Subject: [PATCH] continiued OD preparation --- machineLearning/rf/decisionTree.py | 24 +- machineLearning/rf/leafFunction.py | 33 +- tree-test.ipynb | 2 +- tree-test.json | 892 ++++++++++++++--------------- 4 files changed, 481 insertions(+), 470 deletions(-) diff --git a/machineLearning/rf/decisionTree.py b/machineLearning/rf/decisionTree.py index 42f0593..aea09a3 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 d99b77d..18ab3ff 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 021d8e3..bc16db9 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 fc5cadd..99be817 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, -- GitLab