Как создать подкласс векторизатора в scikit-learn без повторения всех параметров в конструкторе

Я пытаюсь создать пользовательский векторизатор путем подкласса 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.

Вот мой пользовательский векторизатор:

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)])

Я понимаю, что я мог бы установить каждый параметр CountVectorizer класс, но, похоже, не следует принципу СУХОЙ.

Спасибо за вашу помощь!

1 ответ

У меня нет опыта работы с векторизаторами в sklearnОднако я столкнулся с подобной проблемой. Я реализовал собственный оценщик, давайте назовем его MyBaseEstimator на данный момент, расширяя sklearn.base.BaseEstimator, Затем я реализовал несколько других пользовательских подоценок, расширяющих MyBaseEstimator, MyBaseEstimator класс определил несколько аргументов в своем __init__и я не хотел иметь такие же аргументы в __init__ методы каждого из подоценок.

Однако, без переопределения аргументов в подклассах, большая часть sklearn функциональность не работает, в частности, установка этих параметров для перекрестной проверки. Кажется, что sklearn ожидает, что все соответствующие параметры для оценщика могут быть получены и изменены с использованием BaseEstimator.get_params() а также BaseEstimator.set_params() методы. И эти методы при вызове в одном из подклассов не возвращают никаких параметров, определенных в базовом классе.

Чтобы обойти это, я реализовал переопределение get_params() в MyBaseEstimator который использует некрасивый хак, чтобы объединить параметры динамического типа (один из его подкальссов) с параметрами, определенными его собственным __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())

Запуск вышеуказанного кода выводит следующее:

{'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}

что показывает, что sklearn"s get_params() а также set_params() теперь и работа, и передача ключевых аргументов подкласса и базового класса подклассу __init__ работает.

Не уверен, насколько это надежно и решает ли это именно вашу проблему, но это может быть кому-то полезно.

Предполагая, что вы создаете подклассы для добавления некоторых дополнительных методов (или переопределяете существующие), я ожидаю, что вы будете только создавать экземпляр подкласса и, следовательно, должны будете предоставить аргументы инициализации.

Если вы создаете несколько экземпляров, которые совместно используют в основном одни и те же данные инициализации, с одним или двумя изменениями, характерными для конкретного экземпляра, то одним из решений может быть "замораживание" общих данных с использованием partial, Например (следует следующий общий пример):

from functools import partial

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age

class Bob(Person):
    def __init__(self,name,age,weight):
        super().__init__(name,age)
        self.weight = weight

    def refer_to_thyself(self):
        print('My name is {} and I am {} years old and weigh {} lbs.'.format(
            self.name,self.age,self.weight))

Bob_cloner = partial(Bob,'Bob',20)
Bob1 = Bob_cloner(175)
Bob2 = Bob_cloner(185)
Bob1.refer_to_thyself()
Bob2.refer_to_thyself()

Здесь мы замораживаем имя и возраст, используя partial, а затем просто позвольте весу варьироваться среди Бобов.

Другие вопросы по тегам