在我的分类方案中,包括以下几个步骤:
- SMOTE(合成少数类过采样技术)
- 基于Fisher准则的特征选择
- 标准化(Z-score标准化)
- SVC(支持向量分类器)
在上述方案中,需要调整的主要参数是特征选择的百分比(步骤2)和SVC的超参数(步骤4),我希望通过网格搜索来进行调整。
当前的解决方案构建了一个“部分”管道,包括方案中的步骤3和4,代码如下clf = Pipeline([('normal',preprocessing.StandardScaler()),('svc',svm.SVC(class_weight='auto'))])
,并将方案分成两部分:
-
通过第一次网格搜索调整保留特征的百分比
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合成训练数据(我们希望保持测试数据不变) X_train, y_train = SMOTE(X_train, y_train) for percentile in percentiles: # Fisher返回由参数'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分数将被存储,并通过所有折叠分区对所有百分比进行平均,返回具有最佳交叉验证分数的百分比。将’percentile for loop’作为内循环的目的是为了在所有折叠分区和所有百分比中保持相同的训练数据(包括合成数据),以确保公平竞争。
-
确定百分比后,通过第二次网格搜索调整超参数
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合成训练数据(我们希望保持测试数据不变) X_train, y_train = SMOTE(X_train, y_train) for parameters in parameter_comb: # 根据调整后的百分比选择特征 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的超参数而不是特征选择的百分比。
我的问题是:
-
在当前的解决方案中,我只在
clf
中包含了步骤3和4,而步骤1和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)
进行调整?请注意,
SMOTE
和Fisher
(排名标准)必须仅在每个折叠分区的训练数据上执行。
任何评论都将不胜感激。
SMOTE
和Fisher
的代码如下所示:
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)
回答:
我不知道你的SMOTE()
和Fisher()
函数来自哪里,但答案是肯定的,你绝对可以这样做。为了做到这一点,你需要为这些函数编写一个包装类。最简单的方法是继承sklearn的BaseEstimator
和TransformerMixin
类,参见这个例子:http://scikit-learn.org/stable/auto_examples/hetero_feature_union.html
如果你觉得这不明白,请至少发布一个函数的详细信息(它来自哪个库,或者如果你自己编写的代码),我们可以从那里开始。
编辑:
我道歉,我没有仔细查看你的函数,意识到它们不仅转换你的训练数据,还转换你的目标(即X和y)。Pipeline不支持对目标的转换,所以你必须像最初那样在之前进行。对于你的参考,这里是为你的Fisher过程编写自定义类的样子,如果函数本身不需要影响你的目标变量,它将有效。