Как создать подкласс векторизатора в 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
, а затем просто позвольте весу варьироваться среди Бобов.