【问题标题】:Put customized functions in Sklearn pipeline将自定义函数放入 Sklearn 管道
【发布时间】:2015-09-24 10:17:32
【问题描述】:

在我的分类方案中,有几个步骤,包括:

  1. SMOTE(合成少数过采样技术)
  2. Fisher 特征选择标准
  3. 标准化(Z 分数标准化)
  4. SVC(支持向量分类器)

上述方案中要调优的主要参数是百分位数 (2.) 和 SVC 的超参数 (4.),我想通过网格搜索进行调优。

当前的解决方案构建了一个“部分”管道,包括方案clf = Pipeline([('normal',preprocessing.StandardScaler()),('svc',svm.SVC(class_weight='auto'))]) 中的第 3 步和第 4 步 并将方案分为两部分:

  1. 调整特征的百分位数以通过第一次网格搜索

    skf = StratifiedKFold(y)
    for train_ind, test_ind in skf:
        X_train, X_test, y_train, y_test = X[train_ind], X[test_ind], y[train_ind], y[test_ind]
        # SMOTE synthesizes the training data (we want to keep test data intact)
        X_train, y_train = SMOTE(X_train, y_train)
        for percentile in percentiles:
            # Fisher returns the indices of the selected features specified by the parameter 'percentile'
            selected_ind = Fisher(X_train, y_train, percentile) 
            X_train_selected, X_test_selected = X_train[selected_ind,:], X_test[selected_ind, :]
            model = clf.fit(X_train_selected, y_train)
            y_predict = model.predict(X_test_selected)
            f1 = f1_score(y_predict, y_test)
    

    将存储 f1 分数,然后对所有百分位数的所有折叠分区进行平均,并返回具有最佳 CV 分数的百分位数。将“percentile for loop”作为内部循环的目的是允许公平竞争,因为我们在所有百分位数的所有折叠分区中拥有相同的训练数据(包括合成数据)。

  2. 确定百分位数后,通过第二次网格搜索调整超参数

    skf = StratifiedKFold(y)
    for train_ind, test_ind in skf:
        X_train, X_test, y_train, y_test = X[train_ind], X[test_ind], y[train_ind], y[test_ind]
        # SMOTE synthesizes the training data (we want to keep test data intact)
        X_train, y_train = SMOTE(X_train, y_train)
        for parameters in parameter_comb:
            # Select the features based on the tuned percentile
            selected_ind = Fisher(X_train, y_train, best_percentile) 
            X_train_selected, X_test_selected = X_train[selected_ind,:], X_test[selected_ind, :]
            clf.set_params(svc__C=parameters['C'], svc__gamma=parameters['gamma'])
            model = clf.fit(X_train_selected, y_train)
            y_predict = model.predict(X_test_selected)
            f1 = f1_score(y_predict, y_test)
    

它以非常相似的方式完成,除了我们调整 SVC 的超参数而不是要选择的特征的百分位数。

我的问题是:

  1. 在当前的解决方案中,我只在clf 中涉及 3. 和 4. 并在如上所述的两个嵌套循环中“手动”执行 1. 和 2.。有没有办法将所有四个步骤都包含在一个管道中并一次完成整个过程?

  2. 如果可以保留第一个嵌套循环,那么是否可以(以及如何)使用单个管道来简化下一个嵌套循环

    clf_all = Pipeline([('smote', SMOTE()),
                        ('fisher', Fisher(percentile=best_percentile))
                        ('normal',preprocessing.StandardScaler()),
                        ('svc',svm.SVC(class_weight='auto'))]) 
    

    只需使用GridSearchCV(clf_all, parameter_comb) 进行调优?

    请注意,SMOTEFisher(排名标准)都必须仅针对每个折叠分区中的训练数据进行。

如果您有任何评论,我们将不胜感激。

SMOTEFisher如下图:

def Fscore(X, y, percentile=None):
    X_pos, X_neg = X[y==1], X[y==0]
    X_mean = X.mean(axis=0)
    X_pos_mean, X_neg_mean = X_pos.mean(axis=0), X_neg.mean(axis=0)
    deno = (1.0/(shape(X_pos)[0]-1))*X_pos.var(axis=0) +(1.0/(shape(X_neg[0]-1))*X_neg.var(axis=0)
    num = (X_pos_mean - X_mean)**2 + (X_neg_mean - X_mean)**2
    F = num/deno
    sort_F = argsort(F)[::-1]
    n_feature = (float(percentile)/100)*shape(X)[1]
    ind_feature = sort_F[:ceil(n_feature)]
    return(ind_feature)

SMOTE来自https://github.com/blacklab/nyan/blob/master/shared_modules/smote.py,返回合成数据。我对其进行了修改,以返回与合成数据及其标签和合成数据堆叠在一起的原始输入数据。

def smote(X, y):
    n_pos = sum(y==1), sum(y==0)
    n_syn = (n_neg-n_pos)/float(n_pos) 
    X_pos = X[y==1]
    X_syn = SMOTE(X_pos, int(round(n_syn))*100, 5)
    y_syn = np.ones(shape(X_syn)[0])
    X, y = np.vstack([X, X_syn]), np.concatenate([y, y_syn])
    return(X, y)

【问题讨论】:

    标签: machine-learning scikit-learn pipeline cross-validation feature-selection


    【解决方案1】:

    您实际上可以将所有这些功能放入一个管道中!

    在接受的答案中,@David 写道您的功能

    除了您的训练数据(即 X 和 y)之外,还转换您的目标。 Pipeline 不支持对您的目标进行转换,因此您必须像原来一样先进行转换。

    sklearn 的管道确实不支持这个。但是imblearn 的管道here 支持这一点。 imblearn 管道就像 sklearn 一样,但它允许您通过样本方法对训练和测试数据分别调用转换。此外,这些示例方法实际上被设计为可以更改数据X 和标签y。这很重要,因为很多时候您想在管道中包含 smote,但您只想对训练数据进行 smote,而不是测试数据。并且使用imblearn 管道,您可以在管道中调用 smote 来仅转换 X_train 和 y_train 而不是 X_test 和 y_test。

    因此,您可以创建一个 imblearn 管道,其中包含一个 smote 采样器、预处理步骤和 svc。

    有关更多详细信息,请查看此堆栈溢出帖子 here 和机器学习精通文章 here

    【讨论】:

      【解决方案2】:

      scikit 在 0.17 版中创建了一个 FunctionTransformer 作为预处理类的一部分。它可以以与上述答案中大卫对 Fisher 类的实现类似的方式使用 - 但灵活性较低。如果函数的输入/输出配置正确,则转换器可以为该函数实现 fit/transform/fit_transform 方法,从而允许它在 scikit 管道中使用。

      例如,如果管道的输入是一个序列,则转换器如下:

      
      def trans_func(input_series):
          return output_series
      
      from sklearn.preprocessing import FunctionTransformer
      transformer = FunctionTransformer(trans_func)
      
      sk_pipe = Pipeline([("trans", transformer), ("vect", tf_1k), ("clf", clf_1k)])
      sk_pipe.fit(train.desc, train.tag)
      

      其中 vect 是一个 tf_idf 转换器,clf 是一个分类器,而 train 是训练数据集。 “train.desc”是输入到管道的系列文本。

      【讨论】:

      • 这是一个比公认的更清晰的答案。谢谢!
      【解决方案3】:

      我不知道您的 SMOTE()Fisher() 函数来自哪里,但答案是肯定的,您绝对可以这样做。为此,您需要围绕这些函数编写一个包装类。最简单的方法是继承 sklearn 的 BaseEstimatorTransformerMixin 类,例如:http://scikit-learn.org/stable/auto_examples/hetero_feature_union.html

      如果这对您没有意义,请发布至少一个函数的详细信息(它来自的库或您自己编写的代码),我们可以从那里开始。

      编辑:

      抱歉,我没有仔细研究您的函数,以意识到除了您的训练数据(即 X 和 y)之外,它们还转换了您的目标。 Pipeline 不支持对您的目标进行转换,因此您将像原来一样先进行转换。供您参考,下面是为您的 Fisher 进程编写自定义类的样子,如果函数本身不需要影响您的目标变量,该类将起作用。

      >>> from sklearn.base import BaseEstimator, TransformerMixin
      >>> from sklearn.preprocessing import StandardScaler
      >>> from sklearn.svm import SVC
      >>> from sklearn.pipeline import Pipeline
      >>> from sklearn.grid_search import GridSearchCV
      >>> from sklearn.datasets import load_iris
      >>> 
      >>> class Fisher(BaseEstimator, TransformerMixin):
      ...     def __init__(self,percentile=0.95):
      ...             self.percentile = percentile
      ...     def fit(self, X, y):
      ...             from numpy import shape, argsort, ceil
      ...             X_pos, X_neg = X[y==1], X[y==0]
      ...             X_mean = X.mean(axis=0)
      ...             X_pos_mean, X_neg_mean = X_pos.mean(axis=0), X_neg.mean(axis=0)
      ...             deno = (1.0/(shape(X_pos)[0]-1))*X_pos.var(axis=0) + (1.0/(shape(X_neg)[0]-1))*X_neg.var(axis=0)
      ...             num = (X_pos_mean - X_mean)**2 + (X_neg_mean - X_mean)**2
      ...             F = num/deno
      ...             sort_F = argsort(F)[::-1]
      ...             n_feature = (float(self.percentile)/100)*shape(X)[1]
      ...             self.ind_feature = sort_F[:ceil(n_feature)]
      ...             return self
      ...     def transform(self, x):
      ...             return x[self.ind_feature,:]
      ... 
      >>> 
      >>> data = load_iris()
      >>> 
      >>> pipeline = Pipeline([
      ...     ('fisher', Fisher()),
      ...     ('normal',StandardScaler()),
      ...     ('svm',SVC(class_weight='auto'))
      ... ])
      >>> 
      >>> grid = {
      ...     'fisher__percentile':[0.75,0.50],
      ...     'svm__C':[1,2]
      ... }
      >>> 
      >>> model = GridSearchCV(estimator = pipeline, param_grid=grid, cv=2)
      >>> model.fit(data.data,data.target)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/grid_search.py", line 596, in fit
          return self._fit(X, y, ParameterGrid(self.param_grid))
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/grid_search.py", line 378, in _fit
          for parameters in parameter_iterable
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py", line 653, in __call__
          self.dispatch(function, args, kwargs)
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py", line 400, in dispatch
          job = ImmediateApply(func, args, kwargs)
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py", line 138, in __init__
          self.results = func(*args, **kwargs)
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/cross_validation.py", line 1239, in _fit_and_score
          estimator.fit(X_train, y_train, **fit_params)
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/pipeline.py", line 130, in fit
          self.steps[-1][-1].fit(Xt, y, **fit_params)
        File "/Users/dmcgarry/anaconda/lib/python2.7/site-packages/sklearn/svm/base.py", line 149, in fit
          (X.shape[0], y.shape[0]))
      ValueError: X and y have incompatible shapes.
      X has 1 samples, but y has 75.
      

      【讨论】:

      • 谢谢,我在 OP 中包含了这两个函数。
      • 查看编辑,很抱歉抢了先机,但我认为这是不可能的,因为您的功能需要应用于您的目标。
      • 抱歉回复晚了。我想知道“如果函数本身不需要影响您的目标变量,Fisher 过程将起作用”是什么意思。这里的 Fisher 分数将目标(即 y)作为输入,并将转换后的 x 作为输出,在我看来,它不会转换 y。
      • 我不记得了,但看起来我只是复制了你的代码。目标是从 X 或样本行中选择列吗?如果是前者,那么我相信你的代码中有一个错误,一旦修复它应该可以工作,但如果是后者,那么它确实会对 y 产生影响(因为 y 也需要采样)。
      • 感谢您的照顾。是前者。 Fisher 分数将 X 和 y 作为输入,并使用该信息计算每个特征(列)的组间和组内方差之比。标签的数量,并根据比率对特征进行排序。最后,在给定所需百分比的顶级特征的情况下选择特征。
      猜你喜欢
      • 2020-08-04
      • 2019-10-26
      • 2018-03-18
      • 2019-09-30
      • 2021-12-20
      • 2018-01-02
      • 2020-11-30
      • 2017-04-04
      • 2021-06-12
      相关资源
      最近更新 更多