如何在 scikit-learn 的管道中对变换参数进行网格搜索

Posted

技术标签:

【中文标题】如何在 scikit-learn 的管道中对变换参数进行网格搜索【英文标题】:How to gridsearch over transform arguments within a pipeline in 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")之前设置阈值属性并且它起作用了;但是,我不知道如何对其进行网格搜索。

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

【问题讨论】:

【参考方案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

【讨论】:

以上是关于如何在 scikit-learn 的管道中对变换参数进行网格搜索的主要内容,如果未能解决你的问题,请参考以下文章

如何在 scikit-learn 管道中将时代添加到 Keras 网络

如何从 scikit-learn 中的 TransformedTargetRegressor 管道中的经过训练的估计器访问属性?

spark 与 scikit-learn 机器学习流程组件设计哲学比较

如何在 scikit-learn 中使用管道调整自定义内核函数的参数

scikit-learn:如何使用管道组合 LabelEncoder 和 OneHotEncoder?

如何创建一个应用 z-score 和交叉验证的 scikit-learn 管道?