We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Lightwood.api.ensemble is not necessary
This script is deprecated, as the ensemble module has moved to `lw.ensemble` with a base abstraction. A quick inspection of the code (ex: grep for this call) and I don't see any references. Please double check if this file is required, as I think it should be removed.

The culprit link is [here](https://github.com/mindsdb/lightwood/blob/0372d292796a6d1f91ac9df9b8658ad2f128b7c9/lightwood/api/ensemble.py)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lightwood/api/ensemble.py`
Content:
```
1 from lightwood import Predictor
2 from lightwood.constants.lightwood import ColumnDataTypes
3 from collections import Counter
4 import numpy as np
5 import pickle
6 import os
7 
8 
9 class LightwoodEnsemble:
10     def __init__(self, predictors=None, load_from_path=None):
11         self.path_list = None
12         if load_from_path is not None:
13             with open(os.path.join(load_from_path, 'lightwood_data'), 'rb') as pickle_in:
14                 obj = pickle.load(pickle_in)
15                 self.path = load_from_path
16                 self.path_list = obj.path_list
17                 self.ensemble = [Predictor(load_from_path=path) for path in self.path_list]
18         elif isinstance(predictors, Predictor):
19             self.ensemble = [predictors]
20         elif isinstance(predictors, list):
21             self.ensemble = predictors
22 
23     def append(self, predictor):
24         if isinstance(self.ensemble, list):
25             self.ensemble.append(predictor)
26         else:
27             self.ensemble = [predictor]
28 
29     def __iter__(self):
30         yield self.ensemble
31 
32     def predict(self, when_data):
33         predictions = [p.predict(when_data=when_data) for p in self.ensemble]
34         formatted_predictions = {}
35         for target in self.ensemble[0].config['output_features']:
36             target_name = target['name']
37             formatted_predictions[target_name] = {}
38             pred_arr = np.array([p[target_name]['predictions'] for p in predictions])
39             if target['type'] == ColumnDataTypes.NUMERIC:
40                 final_preds = np.mean(pred_arr, axis=0).tolist()
41             elif target['type'] == ColumnDataTypes.CATEGORICAL:
42                 final_preds = [max(Counter(pred_arr[:, idx])) for idx in range(pred_arr.shape[1])]
43 
44                 # @TODO: implement class distribution for ensembles
45                 # NOTE: label set *could* grow when adding predictors, which complicates belief score computation
46                 formatted_predictions[target_name]['class_distribution'] = np.ones(shape=(len(final_preds), 1))
47             else:
48                 raise Exception('Only numeric and categorical datatypes are supported for ensembles')
49 
50             formatted_predictions[target_name]['predictions'] = final_preds
51 
52         return formatted_predictions
53 
54     def save(self, path_to):
55         # TODO: potentially save predictors inside ensemble pickle, though there's the issue of nonpersistent stuff with torch.save() # noqa
56         path_list = []
57         for i, model in enumerate(self.ensemble):
58             path = os.path.join(path_to, f'lightwood_predictor_{i}')
59             path_list.append(path)
60             model.save(path_to=path)
61 
62         self.path_list = path_list
63 
64         # TODO: in the future, save preds inside this data struct
65         self.ensemble = None  # we deref predictors for now
66         with open(os.path.join(path_to, 'lightwood_data'), 'wb') as file:
67             pickle.dump(self, file, pickle.HIGHEST_PROTOCOL)
68
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lightwood/api/ensemble.py b/lightwood/api/ensemble.py
deleted file mode 100644
--- a/lightwood/api/ensemble.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from lightwood import Predictor
-from lightwood.constants.lightwood import ColumnDataTypes
-from collections import Counter
-import numpy as np
-import pickle
-import os
-
-
-class LightwoodEnsemble:
-    def __init__(self, predictors=None, load_from_path=None):
-        self.path_list = None
-        if load_from_path is not None:
-            with open(os.path.join(load_from_path, 'lightwood_data'), 'rb') as pickle_in:
-                obj = pickle.load(pickle_in)
-                self.path = load_from_path
-                self.path_list = obj.path_list
-                self.ensemble = [Predictor(load_from_path=path) for path in self.path_list]
-        elif isinstance(predictors, Predictor):
-            self.ensemble = [predictors]
-        elif isinstance(predictors, list):
-            self.ensemble = predictors
-
-    def append(self, predictor):
-        if isinstance(self.ensemble, list):
-            self.ensemble.append(predictor)
-        else:
-            self.ensemble = [predictor]
-
-    def __iter__(self):
-        yield self.ensemble
-
-    def predict(self, when_data):
-        predictions = [p.predict(when_data=when_data) for p in self.ensemble]
-        formatted_predictions = {}
-        for target in self.ensemble[0].config['output_features']:
-            target_name = target['name']
-            formatted_predictions[target_name] = {}
-            pred_arr = np.array([p[target_name]['predictions'] for p in predictions])
-            if target['type'] == ColumnDataTypes.NUMERIC:
-                final_preds = np.mean(pred_arr, axis=0).tolist()
-            elif target['type'] == ColumnDataTypes.CATEGORICAL:
-                final_preds = [max(Counter(pred_arr[:, idx])) for idx in range(pred_arr.shape[1])]
-
-                # @TODO: implement class distribution for ensembles
-                # NOTE: label set *could* grow when adding predictors, which complicates belief score computation
-                formatted_predictions[target_name]['class_distribution'] = np.ones(shape=(len(final_preds), 1))
-            else:
-                raise Exception('Only numeric and categorical datatypes are supported for ensembles')
-
-            formatted_predictions[target_name]['predictions'] = final_preds
-
-        return formatted_predictions
-
-    def save(self, path_to):
-        # TODO: potentially save predictors inside ensemble pickle, though there's the issue of nonpersistent stuff with torch.save() # noqa
-        path_list = []
-        for i, model in enumerate(self.ensemble):
-            path = os.path.join(path_to, f'lightwood_predictor_{i}')
-            path_list.append(path)
-            model.save(path_to=path)
-
-        self.path_list = path_list
-
-        # TODO: in the future, save preds inside this data struct
-        self.ensemble = None  # we deref predictors for now
-        with open(os.path.join(path_to, 'lightwood_data'), 'wb') as file:
-            pickle.dump(self, file, pickle.HIGHEST_PROTOCOL)

{"golden_diff": "diff --git a/lightwood/api/ensemble.py b/lightwood/api/ensemble.py\ndeleted file mode 100644\n--- a/lightwood/api/ensemble.py\n+++ /dev/null\n@@ -1,67 +0,0 @@\n-from lightwood import Predictor\n-from lightwood.constants.lightwood import ColumnDataTypes\n-from collections import Counter\n-import numpy as np\n-import pickle\n-import os\n-\n-\n-class LightwoodEnsemble:\n-    def __init__(self, predictors=None, load_from_path=None):\n-        self.path_list = None\n-        if load_from_path is not None:\n-            with open(os.path.join(load_from_path, 'lightwood_data'), 'rb') as pickle_in:\n-                obj = pickle.load(pickle_in)\n-                self.path = load_from_path\n-                self.path_list = obj.path_list\n-                self.ensemble = [Predictor(load_from_path=path) for path in self.path_list]\n-        elif isinstance(predictors, Predictor):\n-            self.ensemble = [predictors]\n-        elif isinstance(predictors, list):\n-            self.ensemble = predictors\n-\n-    def append(self, predictor):\n-        if isinstance(self.ensemble, list):\n-            self.ensemble.append(predictor)\n-        else:\n-            self.ensemble = [predictor]\n-\n-    def __iter__(self):\n-        yield self.ensemble\n-\n-    def predict(self, when_data):\n-        predictions = [p.predict(when_data=when_data) for p in self.ensemble]\n-        formatted_predictions = {}\n-        for target in self.ensemble[0].config['output_features']:\n-            target_name = target['name']\n-            formatted_predictions[target_name] = {}\n-            pred_arr = np.array([p[target_name]['predictions'] for p in predictions])\n-            if target['type'] == ColumnDataTypes.NUMERIC:\n-                final_preds = np.mean(pred_arr, axis=0).tolist()\n-            elif target['type'] == ColumnDataTypes.CATEGORICAL:\n-                final_preds = [max(Counter(pred_arr[:, idx])) for idx in range(pred_arr.shape[1])]\n-\n-                # @TODO: implement class distribution for ensembles\n-                # NOTE: label set *could* grow when adding predictors, which complicates belief score computation\n-                formatted_predictions[target_name]['class_distribution'] = np.ones(shape=(len(final_preds), 1))\n-            else:\n-                raise Exception('Only numeric and categorical datatypes are supported for ensembles')\n-\n-            formatted_predictions[target_name]['predictions'] = final_preds\n-\n-        return formatted_predictions\n-\n-    def save(self, path_to):\n-        # TODO: potentially save predictors inside ensemble pickle, though there's the issue of nonpersistent stuff with torch.save() # noqa\n-        path_list = []\n-        for i, model in enumerate(self.ensemble):\n-            path = os.path.join(path_to, f'lightwood_predictor_{i}')\n-            path_list.append(path)\n-            model.save(path_to=path)\n-\n-        self.path_list = path_list\n-\n-        # TODO: in the future, save preds inside this data struct\n-        self.ensemble = None  # we deref predictors for now\n-        with open(os.path.join(path_to, 'lightwood_data'), 'wb') as file:\n-            pickle.dump(self, file, pickle.HIGHEST_PROTOCOL)\n", "issue": "Lightwood.api.ensemble is not necessary\nThis script is deprecated, as the ensemble module has moved to `lw.ensemble` with a base abstraction. A quick inspection of the code (ex: grep for this call) and I don't see any references. Please double check if this file is required, as I think it should be removed.\r\n\r\nThe culprit link is [here](https://github.com/mindsdb/lightwood/blob/0372d292796a6d1f91ac9df9b8658ad2f128b7c9/lightwood/api/ensemble.py)\n", "before_files": [{"content": "from lightwood import Predictor\nfrom lightwood.constants.lightwood import ColumnDataTypes\nfrom collections import Counter\nimport numpy as np\nimport pickle\nimport os\n\n\nclass LightwoodEnsemble:\n    def __init__(self, predictors=None, load_from_path=None):\n        self.path_list = None\n        if load_from_path is not None:\n            with open(os.path.join(load_from_path, 'lightwood_data'), 'rb') as pickle_in:\n                obj = pickle.load(pickle_in)\n                self.path = load_from_path\n                self.path_list = obj.path_list\n                self.ensemble = [Predictor(load_from_path=path) for path in self.path_list]\n        elif isinstance(predictors, Predictor):\n            self.ensemble = [predictors]\n        elif isinstance(predictors, list):\n            self.ensemble = predictors\n\n    def append(self, predictor):\n        if isinstance(self.ensemble, list):\n            self.ensemble.append(predictor)\n        else:\n            self.ensemble = [predictor]\n\n    def __iter__(self):\n        yield self.ensemble\n\n    def predict(self, when_data):\n        predictions = [p.predict(when_data=when_data) for p in self.ensemble]\n        formatted_predictions = {}\n        for target in self.ensemble[0].config['output_features']:\n            target_name = target['name']\n            formatted_predictions[target_name] = {}\n            pred_arr = np.array([p[target_name]['predictions'] for p in predictions])\n            if target['type'] == ColumnDataTypes.NUMERIC:\n                final_preds = np.mean(pred_arr, axis=0).tolist()\n            elif target['type'] == ColumnDataTypes.CATEGORICAL:\n                final_preds = [max(Counter(pred_arr[:, idx])) for idx in range(pred_arr.shape[1])]\n\n                # @TODO: implement class distribution for ensembles\n                # NOTE: label set *could* grow when adding predictors, which complicates belief score computation\n                formatted_predictions[target_name]['class_distribution'] = np.ones(shape=(len(final_preds), 1))\n            else:\n                raise Exception('Only numeric and categorical datatypes are supported for ensembles')\n\n            formatted_predictions[target_name]['predictions'] = final_preds\n\n        return formatted_predictions\n\n    def save(self, path_to):\n        # TODO: potentially save predictors inside ensemble pickle, though there's the issue of nonpersistent stuff with torch.save() # noqa\n        path_list = []\n        for i, model in enumerate(self.ensemble):\n            path = os.path.join(path_to, f'lightwood_predictor_{i}')\n            path_list.append(path)\n            model.save(path_to=path)\n\n        self.path_list = path_list\n\n        # TODO: in the future, save preds inside this data struct\n        self.ensemble = None  # we deref predictors for now\n        with open(os.path.join(path_to, 'lightwood_data'), 'wb') as file:\n            pickle.dump(self, file, pickle.HIGHEST_PROTOCOL)\n", "path": "lightwood/api/ensemble.py"}], "after_files": [{"content": null, "path": "lightwood/api/ensemble.py"}]}