【问题标题】:How to gridsearch over transform arguments within a pipeline in scikit-learn如何在 scikit-learn 的管道中对变换参数进行网格搜索
【发布时间】:2014-06-04 04:38:44
【问题描述】:

我的目标是使用一个模型来选择最重要的变量,并使用另一个模型来使用这些变量进行预测。在下面的示例中,我使用了两个 RandomForestClassifier,但第二个模型可以是任何其他分类器。

RF 有一个带有阈值参数的变换方法。我想对不同的可能阈值参数进行网格搜索。

这里是一个简化的代码sn-p:

# Transform object and classifier
rf_filter = RandomForestClassifier(n_estimators=200, n_jobs=-1, random_state=42, oob_score=False)
clf = RandomForestClassifier(n_jobs=-1, random_state=42, oob_score=False)

pipe = Pipeline([("RFF", rf_filter), ("RF", clf)])

# Grid search parameters
rf_n_estimators = [10, 20]
rff_transform = ["median", "mean"] # Search the threshold parameters

estimator = GridSearchCV(pipe,
                         cv = 3, 
                         param_grid = dict(RF__n_estimators = rf_n_estimators,
                                           RFF__threshold = rff_transform))

estimator.fit(X_train, y_train)

错误是ValueError: Invalid parameter threshold for estimator RandomForestClassifier

我认为这会起作用,因为the docs 说:

如果 None 并且如果可用,则使用对象属性阈值。

我尝试在网格搜索(rf_filter.threshold = "median")之前设置阈值属性并且它起作用了;但是,我不知道如何对其进行网格搜索。

有没有一种方法可以迭代通常期望在分类器的变换方法中提供的不同参数?

【问题讨论】:

    标签: python machine-learning scikit-learn pipeline


    【解决方案1】:

    按照您所描述的相同方法,即使用两个不同的随机森林分类器分组到管道中进行特征选择和分类,我遇到了同样的问题。

    RandomForestClassifier 类的实例没有名为threshold 的属性。您确实可以手动添加一个,使用您描述的方式或使用

    setattr(object, 'threshold', 'mean')
    

    但主要问题似乎是 get_params 方法检查 BaseEstimator 任何成员的有效属性的方式:

    class BaseEstimator(object):
    """Base class for all estimators in scikit-learn
    
    Notes
    -----
    All estimators should specify all the parameters that can be set
    at the class level in their __init__ as explicit keyword
    arguments (no *args, **kwargs).
    """
    
    @classmethod
    def _get_param_names(cls):
        """Get parameter names for the estimator"""
        try:
            # fetch the constructor or the original constructor before
            # deprecation wrapping if any
            init = getattr(cls.__init__, 'deprecated_original', cls.__init__)
    
            # introspect the constructor arguments to find the model parameters
            # to represent
            args, varargs, kw, default = inspect.getargspec(init)
            if not varargs is None:
                raise RuntimeError("scikit-learn estimators should always "
                                   "specify their parameters in the signature"
                                   " of their __init__ (no varargs)."
                                   " %s doesn't follow this convention."
                                   % (cls, ))
            # Remove 'self'
            # XXX: This is going to fail if the init is a staticmethod, but
            # who would do this?
            args.pop(0)
        except TypeError:
            # No explicit __init__
            args = []
        args.sort()
        return args
    

    确实,正如明确规定的那样,所有估算器都应指定所有可以设置的参数 在他们的 __init__ 的类级别作为显式关键字参数。

    所以我尝试将 threshold 指定为 __init__ 函数中的参数,默认值为 'mean' (无论如何,这在当前实现中是其默认值)

        def __init__(self,
                 n_estimators=10,
                 criterion="gini",
                 max_depth=None,
                 min_samples_split=2,
                 min_samples_leaf=1,
                 max_features="auto",
                 bootstrap=True,
                 oob_score=False,
                 n_jobs=1,
                 random_state=None,
                 verbose=0,
                 min_density=None,
                 compute_importances=None,
                 threshold="mean"): # ADD THIS!
    

    然后将这个参数的值赋给类的一个参数。

        self.threshold = threshold # ADD THIS LINE SOMEWHERE IN THE FUNCTION __INIT__
    

    当然,这意味着修改类 RandomForestClassifier(在 /python2.7/site-packages/sklearn/ensemble/forest.py 中)这可能不是最好的方法......但它为我工作!我现在能够对不同的阈值参数进行网格搜索(和交叉验证),从而选择不同数量的特征。

    【讨论】:

    • 非常感谢!我对你能够回答一个非常困难的问题印象深刻。谢谢!
    • 嘿,很久以后,但再次感谢您的出色回答。
    【解决方案2】:
    class my_rf_filter(BaseEstimator, TransformerMixin):
    def __init__(self,threshold):
        self.threshold = threshold
    
    def fit(self,X,y):
        model = RandomForestClassifier(n_estimators=100, n_jobs=-1, random_state=42, oob_score=False)
        model.fit(X,y)
        self.model = model
        return self
    
    def transform(self,X):
        return self.model.transform(X,self.threshold)
    

    通过将 RandomForestClassifier 包装在一个新类中,它会起作用。

    rf_filter = my_rf_filter(threshold='mean')
    clf = RandomForestClassifier(n_jobs=-1, random_state=42, oob_score=False)
    
    pipe = Pipeline([("RFF", rf_filter), ("RF", clf)])
    
    # Grid search parameters
    rf_n_estimators = [10, 20]
    rff_transform = ["median", "mean"] # Search the threshold parameters
    
    estimator = GridSearchCV(pipe,
                             cv = 3, 
                             param_grid = dict(RF__n_estimators = rf_n_estimators,
                                               RFF__threshold = rff_transform))
    

    一个测试示例:

    from sklearn import datasets
    digits = datasets.load_digits()
    X_digits = digits.data
    y_digits = digits.target
    
    estimator.fit(X_digits, y_digits)
    
    
    Out[143]:
    GridSearchCV(cv=3,
           estimator=Pipeline(steps=[('RFF', my_rf_filter(threshold='mean')), ('RF', RandomForestClassifier(bootstrap=True, compute_importances=None,
                criterion='gini', max_depth=None, max_features='auto',
                max_leaf_nodes=None, min_density=None, min_samples_leaf=1,
                min_samples_split=2, n_estimators=10, n_jobs=-1,
                oob_score=False, random_state=42, verbose=0))]),
           fit_params={}, iid=True, loss_func=None, n_jobs=1,
           param_grid={'RF__n_estimators': [10, 20], 'RFF__threshold': ['median', 'mean']},
           pre_dispatch='2*n_jobs', refit=True, score_func=None, scoring=None,
           verbose=0)
    
    
    estimator.grid_scores_
    
    Out[144]:
    [mean: 0.89705, std: 0.00912, params: {'RF__n_estimators': 10, 'RFF__threshold': 'median'},
     mean: 0.91597, std: 0.00871, params: {'RF__n_estimators': 20, 'RFF__threshold': 'median'},
     mean: 0.89705, std: 0.00912, params: {'RF__n_estimators': 10, 'RFF__threshold': 'mean'},
     mean: 0.91597, std: 0.00871, params: {'RF__n_estimators': 20, 'RFF__threshold': 'mean'}]
    

    如果您需要修改my_rf_filter类中RandomForestClassifier的参数,我认为您需要明确添加它们,即不要在__init__()model.set_paras(**kwargs)中使用**kwargs,因为我没有做那。我认为将n_estimators=200 添加到__init__() 然后model.n_estimators = self.n_estimators 将起作用。

    【讨论】:

      【解决方案3】:

      您可以通过以下 hack 避免大部分额外的编码。

      首先捕获估算器的变量引用。 (本例中为“估计器”)您可以在调试过程中查找实际引​​用的超参数名称。

      对于以上问题

      pipe = Pipeline([("RFF", rf_filter), ("RF", clf)])
      ...
      
      param_grid = {"clf__estimator__n_estimators": [10, 20],
      
      }
      
      estimator = GridSearchCV(pipe,
                               cv = 3, 
                               param_grid )
      

      因此只需将超参数(即 max_features)更改为 clf__estimator__max_features

      【讨论】:

        猜你喜欢
        • 2015-10-17
        • 2016-04-09
        • 2018-07-29
        • 2019-06-02
        • 2016-08-30
        • 2020-07-23
        • 2016-08-19
        • 2015-08-14
        • 2017-03-26
        相关资源
        最近更新 更多