Repository: sktime/sktime

Issue:
[BUG] CanonicalIntervalForest breaks with CIT tree
**Describe the bug**
When running the below code snippet, I get: 

> `AttributeError: '_TreeNode' object has no attribute '_predict_proba_cif'`

**To Reproduce**
```python
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline

from sktime.classification.compose import ColumnEnsembleClassifier
from sktime.classification.interval_based import CanonicalIntervalForest
from sktime.datasets import load_basic_motions
from sktime.transformations.panel.compose import ColumnConcatenator

X, y = load_basic_motions(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

steps = [
    ("concatenate", ColumnConcatenator()),
    ("classify", CanonicalIntervalForest(n_estimators=100, base_estimator="CIT")),
]
clf = Pipeline(steps)
clf.fit(X_train, y_train)
clf.score(X_test, y_test)```
```

**Additional context**
There's a [question on TSF on StackOverflow](https://stackoverflow.com/questions/70212188/how-to-know-from-which-interval-of-the-input-the-features-used-in-sktimes-times) in which I wanted to show CIF as an alternative but got the above error.



Discussion:

Resolving patch:
diff --git a/sktime/contrib/vector_classifiers/_continuous_interval_tree.py b/sktime/contrib/vector_classifiers/_continuous_interval_tree.py
--- a/sktime/contrib/vector_classifiers/_continuous_interval_tree.py
+++ b/sktime/contrib/vector_classifiers/_continuous_interval_tree.py
@@ -16,7 +16,7 @@
 from numba import njit
 from numba.typed import List
 from sklearn.base import BaseEstimator
-from sklearn.utils import check_X_y, check_random_state
+from sklearn.utils import check_random_state, check_X_y
 
 from sktime.exceptions import NotFittedError
 from sktime.utils.slope_and_trend import _slope
@@ -219,7 +219,7 @@ def _predict_proba_cif(self, X, c22, intervals, dims, atts):
 
         dists = np.zeros((n_instances, self.n_classes))
         for i in range(n_instances):
-            dists[i] = self.root._predict_proba_cif(
+            dists[i] = self.root.predict_proba_cif(
                 X[i].reshape((1, n_dims, series_length)),
                 c22,
                 intervals,
@@ -249,7 +249,7 @@ def _predict_proba_drcif(
                 X_p[i].reshape((1, n_dims, X_p.shape[2])),
                 X_d[i].reshape((1, n_dims, X_d.shape[2])),
             ]
-            dists[i] = self.root._predict_proba_drcif(
+            dists[i] = self.root.predict_proba_drcif(
                 r,
                 c22,
                 n_intervals,
@@ -468,15 +468,15 @@ def predict_proba_cif(
             value = np.nan_to_num(value, False, 0, 0, 0)
 
             if np.isnan(value):
-                return self.children[0]._predict_proba_cif(
+                return self.children[0].predict_proba_cif(
                     X, c22, intervals, dims, atts, n_classes, class_dictionary
                 )
             elif value <= self.best_threshold:
-                return self.children[1]._predict_proba_cif(
+                return self.children[1].predict_proba_cif(
                     X, c22, intervals, dims, atts, n_classes, class_dictionary
                 )
             else:
-                return self.children[2]._predict_proba_cif(
+                return self.children[2].predict_proba_cif(
                     X, c22, intervals, dims, atts, n_classes, class_dictionary
                 )
         else:
@@ -506,7 +506,7 @@ def predict_proba_drcif(
             value = np.nan_to_num(value, False, 0, 0, 0)
 
             if np.isnan(value):
-                return self.children[0]._predict_proba_drcif(
+                return self.children[0].predict_proba_drcif(
                     X,
                     c22,
                     n_intervals,
@@ -517,7 +517,7 @@ def predict_proba_drcif(
                     class_dictionary,
                 )
             elif value <= self.best_threshold:
-                return self.children[1]._predict_proba_drcif(
+                return self.children[1].predict_proba_drcif(
                     X,
                     c22,
                     n_intervals,
@@ -528,7 +528,7 @@ def predict_proba_drcif(
                     class_dictionary,
                 )
             else:
-                return self.children[2]._predict_proba_drcif(
+                return self.children[2].predict_proba_drcif(
                     X,
                     c22,
                     n_intervals,


Test patch:
diff --git a/sktime/classification/interval_based/tests/test_cif.py b/sktime/classification/interval_based/tests/test_cif.py
--- a/sktime/classification/interval_based/tests/test_cif.py
+++ b/sktime/classification/interval_based/tests/test_cif.py
@@ -23,6 +23,21 @@ def test_cif_on_unit_test_data():
     testing.assert_array_equal(probas, cif_unit_test_probas)
 
 
+def test_cit_on_unit_test_data():
+    """Test of CanonicalIntervalForest on unit test data."""
+    # load unit test data
+    X_train, y_train = load_unit_test(split="train", return_X_y=True)
+    X_test, y_test = load_unit_test(split="test", return_X_y=True)
+    indices = np.random.RandomState(0).choice(len(y_train), 10, replace=False)
+
+    # train CIF continuous interval tree
+    cif = CanonicalIntervalForest(n_estimators=10, base_estimator="CIT", random_state=0)
+    cif.fit(X_train, y_train)
+
+    # assert probabilities are the same
+    cif.predict_proba(X_test.iloc[indices])
+
+
 def test_cif_on_basic_motions():
     """Test of CanonicalIntervalForest on basic motions data."""
     # load basic motions data