Repository: scikit-activeml/scikit-activeml (language: python)

Issue:
QBC overwrite init parameter FIX
https://github.com/scikit-activeml/scikit-activeml/blob/2d49e3b59f045b87d8c306ec706a5ae81d7fadd0/skactiveml/pool/_qbc.py#L111


Discussion / hints:

Fix patch:
diff --git a/skactiveml/pool/_qbc.py b/skactiveml/pool/_qbc.py
index 52a0c63d..5653c42a 100644
--- a/skactiveml/pool/_qbc.py
+++ b/skactiveml/pool/_qbc.py
@@ -6,6 +6,7 @@
 
 import numpy as np
 import warnings
+import inspect
 
 from sklearn import clone
 
@@ -23,26 +24,25 @@ class QBC(SingleAnnotPoolBasedQueryStrategy):
 
     The Query-By-Committee (QBC) algorithm minimizes the version space, which
     is the set of hypotheses that are consistent with the current labeled
-    training data. This class implement the query-by-bagging method, which uses
-    the bagging in sklearn to construct the committee. So your model should be
-    a sklearn model.
+    training data.
 
     Parameters
     ----------
-    clf : sklearn classifier | ensamble
-        If clf is an ensemble, it will used as committee. If clf is a
+    clf : SkactivemlClassifier
+        If clf is an wrapped ensemble, it will used as committee. If clf is a
         classifier, it will used for ensemble construction with the specified
         ensemble or with BaggigngClassifier, if ensemble is None. clf must
         implementing the methods 'fit', 'predict'(for vote entropy) and
         'predict_proba'(for KL divergence).
-    ensemble : sklearn.ensemble, default=None
-        sklear.ensemble used as committee. If None, baggingClassifier is used.
+    ensemble : BaseEnsemble, default=None
+        wrapped sklear.ensemble used as committee. If None, baggingClassifier
+        is used.
     method : string, default='KL_divergence'
         The method to calculate the disagreement.
         'vote_entropy' or 'KL_divergence' are possible.
     random_state : numeric | np.random.RandomState
         Random state to use.
-    ensemble_dict :
+    ensemble_dict : dictionary
         will be passed on to the ensemble.
 
     Attributes
@@ -108,7 +108,9 @@ def query(self, X_cand, X, y, sample_weight=None,  batch_size=1,
         self._clf = clone(self.clf)
 
         if self.ensemble_dict is None:
-            self.ensemble_dict = dict()
+            ensemble_dict = dict()
+        else:
+            ensemble_dict = self.ensemble_dict
 
         # Validate input parameters.
         X_cand, return_utilities, batch_size, random_state = \
@@ -147,6 +149,18 @@ def query(self, X_cand, X, y, sample_weight=None,  batch_size=1,
                 "'clf' must implement the methods 'fit' and 'predict_proba'")
 
         # build a ensemble if necessary
+        if isinstance(self._clf, SklearnClassifier) and \
+                not isinstance(self._clf.estimator, BaseEnsemble):
+            clf_dict = dict()
+            clf_class = type(self._clf.estimator)
+            parameters = inspect.signature(clf_class.__init__).parameters
+            for i in inspect.getmembers(self._clf):
+                if not i[0].startswith('_') and not i[0].endswith('_') and \
+                        not inspect.ismethod(i[1]) and i[0] in parameters:
+                    clf_dict[i[0]] = i[1]
+
+            self._clf = clf_class(**clf_dict)
+
         if not isinstance(self._clf, SklearnClassifier) or \
                 not isinstance(self._clf.estimator, BaseEnsemble):
             if self.ensemble is None:
@@ -161,14 +175,13 @@ def query(self, X_cand, X, y, sample_weight=None,  batch_size=1,
                                 "'self.ensemble'.".format(type(self.ensemble)))
             else:
                 ensemble = self.ensemble
-            parameters = ensemble.__init__.__code__.co_varnames
-            if not isinstance(self.ensemble_dict, dict):
+            parameters = inspect.signature(ensemble.__init__).parameters
+            if not isinstance(ensemble_dict, dict):
                 raise TypeError("ensemble_dict is not a dictionary.")
-            ensemble_dict = self.ensemble_dict
             if 'base_estimator' in parameters:
                 ensemble_dict['base_estimator'] = self._clf
             self._clf = SklearnClassifier(
-                ensemble(random_state=random_state, **ensemble_dict),
+                ensemble(**ensemble_dict, random_state=random_state),
                 classes=classes
             )
 
@@ -176,13 +189,13 @@ def query(self, X_cand, X, y, sample_weight=None,  batch_size=1,
         self._clf.fit(X, y, sample_weight=sample_weight)
 
         # choose the disagreement method and calculate the utilities
-        if hasattr(self._clf, 'estimators_'):
+        if hasattr(self._clf, 'estimators_') and self._clf.is_fitted_:
             est_arr = self._clf.estimators_
         else:
             est_arr = [self._clf] * self._clf.n_estimators
         if self.method == 'KL_divergence':
-            P = [est.predict_proba(X_cand) for est in est_arr]
-            utilities = average_kl_divergence(P)
+            probas = [est.predict_proba(X_cand) for est in est_arr]
+            utilities = average_kl_divergence(probas)
         elif self.method == 'vote_entropy':
             votes = [est.predict(X_cand) for est in est_arr]
             utilities = vote_entropy(votes, classes)


Test patch:
diff --git a/skactiveml/pool/tests/test_qbc.py b/skactiveml/pool/tests/test_qbc.py
index d1100337..aaefb2f5 100644
--- a/skactiveml/pool/tests/test_qbc.py
+++ b/skactiveml/pool/tests/test_qbc.py
@@ -1,6 +1,7 @@
 import numpy as np
 import unittest
 
+from sklearn.datasets import make_blobs
 from sklearn.ensemble import RandomForestClassifier
 from sklearn.gaussian_process import GaussianProcessRegressor, \
     GaussianProcessClassifier
@@ -135,10 +136,10 @@ def test_query_param_return_utilities(self):
     def test_query(self):
         # clf
         selector = QBC(clf=SklearnClassifier(GaussianProcessClassifier(),
-                                                classes=self.classes))
+                                             classes=self.classes))
         selector.query(**self.kwargs)
         selector = QBC(clf=SklearnClassifier(BaggingClassifier(),
-                                                classes=self.classes))
+                                             classes=self.classes))
         selector.query(**self.kwargs)
 
         # ensemble
@@ -149,9 +150,11 @@ def test_query(self):
         selector = QBC(clf=self.clf, ensemble=RandomForestClassifier)
         selector.query(**self.kwargs)
 
-        clf = SklearnClassifier(BaggingClassifier())
+        clf = PWC()
         selector = QBC(clf)
         selector.query(**self.kwargs)
+        estimators = selector._clf.estimators_
+        self.assertTrue(type(clf) == type(estimators[0]))
 
         # ensemble_dict
         selector = QBC(clf=self.clf, ensemble=RandomForestClassifier,