用于字典创建和交叉验证的具有不同文本集的参数网格搜索

Parameters grid search with different text sets for dictionary creation and cross validation

我必须训练一个 class 垃圾邮件检测器。

我拥有的数据集。

手头有一个标记为 [text, class] 的电子邮件数据集。 我还有很多没有 class 标签的邮件。

我想做什么

我想使用 gridsearchcv() 函数来估计我的模型的最佳超参数。其中一个参数与字典创建有关(如 1-gram 或 2-gram、最小频率等)。我想要 gridsearchcv() 函数做的是在我的管道中为 CountVectorizer 使用整个电子邮件数据集(带标签的电子邮件 + 不带标签的电子邮件)来创建字典。但我希望它只在带标签的电子邮件上测试结果。所以,基本上我想使用整个数据集来创建字典,我想仅在标记的数据集上使用交叉验证来估计参数。

我们将不胜感激:)

更新:

重要提示: 解决 @AndreasMueller 的回答:结果会有所不同,因为我还调整了 CountVectorizer 的参数并且我使用了反向文档频率。因此,我正在寻找一种方法,通过包含未标记的数据来使我的 classifier 更通用。

这是我现在拥有的:

pipeline = Pipeline([
('features', FeatureUnion([
    ('words', Pipeline([
        ('vect',  CountVectorizer()),
        ('frequency_transform',  TfidfTransformer())
    ])),            
    ('url_feature',  Contains_URL_Transformer()),
    ('html_feature', Contains_HTML_Transformer()),
    ('length_feature', Text_Length_Transformer()),
    ('response_feature', Contains_Re_Transformer())
    ])),
('clf',  SVC())
])

parameters = {
'features__words__vect__min_df': (1, 3, 5),
'features__words__vect__token_pattern': (r"\b[^\W\d_]+\b",),
'features__words__vect__binary': (False,),
'features__words__frequency_transform__use_idf' : (True,),
#'vect__max_features': (None, 5000, 10000, 50000),
'features__words__vect__ngram_range': ((1, 1), (1, 2)),  # unigrams or bigrams
'clf__C': (1, 5, 10),
'clf__kernel': ('linear', 'rbf')
#'tfidf__use_idf': (True, False)
#'tfidf__norm': ('l1', 'l2'),
#'clf__alpha': (0.00001, 0.000001),
#'clf__penalty': ('l2', 'elasticnet'),
#'clf__n_iter': (10, 50, 80),
}

grid_search = GridSearchCV(pipeline, parameters, n_jobs=-1, verbose=1)

data_column = numpy.asarray(data['text'])

data_column = numpy.append(data_column, ['test'])

grid_search.fit(data_column, numpy.asarray(data['class']))

best_parameters = grid_search.best_estimator_.get_params()

for param_name in sorted(parameters.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))

不过我也有unlabled_data['text']。我如何将 data['text']unlabled_data['text'] 的混合添加到管道中,以便从该混合中创建字典(并估计参数),但要在标记的数据上对其进行测试。问题是,当我执行 grid_search.fit() 时,它使用提供的数据集来创建字典,但我看不出有什么办法可以将所有电子邮件放在那里。

您可以使用预先指定的字典来做到这一点。不过,那没关系。如果单词没有出现在训练数据中,它们的系数将为零,因此将它们添加到词汇表中没有任何作用。

一个简单的解决方案强制拟合数据保持不变,而不管交叉验证数据如何:

X_all = full dataset

class MyVectorizer(sklearn.feature_extraction.text.TfidfVectorizer):
    def fit(self, X, y=None):
        return super(MyVectorizer, self).fit(X_all)
    def fit_transform(self, X, y=None):
        return super(MyVectorizer, self).fit(X_all).transform(X)

用它代替上面的 'words' 子管道。


一个可以说不那么骇人听闻但更复杂的解决方案是:

  • 连接标记和未标记数据,设置标签 后者的实例 -1
  • 使用始终将未标记实例保留在训练集中的自定义交叉验证生成器。
  • 在管道的 post-特征提取部分(这里是 SVC)使用包装器来删除未标记的数据(注意你不能只 将其实现为 Transformer)。 (也许从 SVC 扩展更简单,有点像 MyVectorizer 上面所做的,但没有使用全局数据 hack。)

这种方法的一个优点是它适用于任何 GridSearchCV 输入(而不是通过全局变量注入完整数据)。

示例代码:

def semisupervised_stratified_kfold(y, *args, **kwargs):
    labeled_idx = np.flatnonzero(y != -1)
    unlabeled_idx = np.flatnonzero(y == -1)
    for train, test in StratifiedKFold(y[labelled_idx], *args, **kwargs):
        train = np.concatenate([unlabeled_idx, labeled_idx.take(train)])
        test = labeled_idx.take(test)
        yield train, test

from sklearn.utils.metaestimators import if_delegate_has_method
class StripUnlabelled(sklearn.base.BaseEstimator):
    def __init__(self, estimator):
        self.estimator = sklearn.base.clone(estimator)
    def fit(self, X, y, **kwargs):
        return self.estimator.fit()
    @if_delegate_has_method(delegate='estimator')
    def predict(self, X):
        return self.estimator.predict(X)
    # and similar for decision_function, predict_proba, score, etc.

然后将GridSearchCVcv参数设置为自定义生成器,将StripUnlabeled包裹在SVC实例周围,并在SVC参数名称前加上estimator__

这实际上不会在所有数据上构建 TFIDF 模型,而是会使用所有未标记数据加上标记数据的所有训练折叠。

此外,请注意,所有使用 Pipeline 的类似解决方案都将非常低效,因为在下游更改参数时不会缓存重复的工作,尽管已经提出了用于缓存部分管道的通用解决方案.