如何在不重复构造函数中的所有参数的情况下,在 scikit-learn 中对矢量化器进行子类化

nbe*_*hat 3 python subclass python-3.x scikit-learn countvectorizer

我正在尝试通过子类化CountVectorizer. 向量化器会在计算词频之前对句子中的所有词进行词干。然后我在管道中使用这个矢量化器,当我这样做时它工作正常pipeline.fit(X,y)

但是,当我尝试使用 设置参数时pipeline.set_params(rf__verbose=1).fit(X,y),出现以下错误:

RuntimeError: scikit-learn estimators should always specify their parameters in the signature of their __init__ (no varargs). <class 'features.extraction.labels.StemmedCountVectorizer'> with constructor (self, *args, **kwargs) doesn't  follow this convention.
Run Code Online (Sandbox Code Playgroud)

这是我的自定义矢量化器:

class StemmedCountVectorizer(CountVectorizer):
    def __init__(self, *args, **kwargs):
        self.stemmer = SnowballStemmer("english", ignore_stopwords=True)
        super(StemmedCountVectorizer, self).__init__(*args, **kwargs)

    def build_analyzer(self):
        analyzer = super(StemmedCountVectorizer, self).build_analyzer()
        return lambda doc: ([' '.join([self.stemmer.stem(w) for w in word_tokenize(word)]) for word in analyzer(doc)])
Run Code Online (Sandbox Code Playgroud)

我知道我可以设置类的每个参数,CountVectorizer但它似乎不遵循 DRY 原则。

谢谢你的帮助!

avi*_*ivr 6

我在 中没有使用矢量化器的经验sklearn,但是我遇到了类似的问题。我已经实现了一个自定义估算器,我们暂时称之为MyBaseEstimator,扩展sklearn.base.BaseEstimator. 然后我实现了一些其他自定义子估计器扩展MyBaseEstimator. 在MyBaseEstimator类中定义多个参数中的__init__,我不希望有在相同的参数__init__每个子估计的方法。

然而,如果没有重新定义子类中的参数,大部分sklearn功能都不起作用,特别是设置这些参数以进行交叉验证。似乎sklearn期望使用BaseEstimator.get_params()BaseEstimator.set_params()方法可以检索和修改估计器的所有相关参数。并且这些方法在子类之一上调用时,不会返回基类中定义的任何参数。

为了解决这个问题,我实现了一个覆盖get_params()MyBaseEstimator它使用一个丑陋的 hack 将动态类型的参数(它的一个子类)与其自己的__init__.

这是适用于您的相同丑陋的黑客CountVectorizer...

import copy
from sklearn.feature_extraction.text import CountVectorizer


class SubCountVectorizer(CountVectorizer):
    def __init__(self, p1=1, p2=2, **kwargs):
        super().__init__(**kwargs)

    def get_params(self, deep=True):
        params = super().get_params(deep)
        # Hack to make get_params return base class params...
        cp = copy.copy(self)
        cp.__class__ = CountVectorizer
        params.update(CountVectorizer.get_params(cp, deep))
        return params


if __name__ == '__main__':
    scv = SubCountVectorizer(p1='foo', input='bar', encoding='baz')
    scv.set_params(**{'p2': 'foo2', 'analyzer': 'bar2'})
    print(scv.get_params())
Run Code Online (Sandbox Code Playgroud)

运行上面的代码打印如下:

{'p1': None, 'p2': 'foo2',
'analyzer': 'bar2', 'binary': False,
'decode_error': 'strict', 'dtype': <class 'numpy.int64'>,
'encoding': 'baz', 'input': 'bar',
'lowercase': True, 'max_df': 1.0, 'max_features': None,
'min_df': 1, 'ngram_range': (1, 1), 'preprocessor': None,
'stop_words': None, 'strip_accents': None,
'token_pattern': '(?u)\\b\\w\\w+\\b',
'tokenizer': None, 'vocabulary': None}
Run Code Online (Sandbox Code Playgroud)

这表明sklearn'sget_params()set_params()现在都可以工作,并且还将子类和基类的关键字参数传递给子类__init__工作。

不确定这有多健壮以及它是否解决了您的确切问题,但它可能对某人有用。