MemoryError на дампе joblib

У меня есть следующий фрагмент для обучения модели классификации текста. Я немного оптимизировал его, и он работает довольно гладко, однако он по-прежнему использует много оперативной памяти. Наш набор данных огромен (13 миллионов документов + 18 миллионов слов в словаре), но точка выполнения, выдающая ошибку, на мой взгляд, очень странная. Сценарий:

encoder = LabelEncoder()
y = encoder.fit_transform(categories)
classes = list(range(0, len(encoder.classes_)))

vectorizer = CountVectorizer(vocabulary=vocabulary,
                             binary=True,
                             dtype=numpy.int8)

classifier = SGDClassifier(loss='modified_huber',
                           n_jobs=-1,
                           average=True,
                           random_state=1)

tokenpath = modelpath.joinpath("tokens")
for i in range(0, len(batches)):
    token_matrix = joblib.load(
        tokenpath.joinpath("{}.pickle".format(i)))
    batchsize = len(token_matrix)
    classifier.partial_fit(
        vectorizer.transform(token_matrix),
        y[i * batchsize:(i + 1) * batchsize],
        classes=classes
    )

joblib.dump(classifier, modelpath.joinpath('classifier.pickle'))
joblib.dump(vectorizer, modelpath.joinpath('vectorizer.pickle'))
joblib.dump(encoder, modelpath.joinpath('category_encoder.pickle'))
joblib.dump(options, modelpath.joinpath('extraction_options.pickle'))

Я получил MemoryError в этой строке:

joblib.dump(vectorizer, modelpath.joinpath('vectorizer.pickle'))

На этом этапе выполнения обучение завершено, и классификатор уже сброшен. Он должен быть собран сборщиком мусора на случай, если потребуется больше памяти. Кроме того, зачем joblib выделять столько памяти, если она даже не сжимает данные< /а>.

У меня нет глубоких знаний о внутренней работе сборщика мусора Python. Должен ли я заставлять gc.collect() или использовать 'del' статусы, чтобы освободить те объекты, которые больше не нужны?

Обновление:

Я попытался использовать HashingVectorizer, и хотя он значительно сокращает использование памяти, векторизация выполняется намного медленнее, что делает его не очень хорошей альтернативой.

Мне нужно настроить векторизатор, чтобы позже использовать его в процессе классификации, чтобы я мог сгенерировать разреженную матрицу, которая передается классификатору. Я опубликую здесь свой классификационный код:

extracted_features = joblib.Parallel(n_jobs=-1)(
    joblib.delayed(features.extractor) (d, extraction_options) for d in documents)

probabilities = classifier.predict_proba(
    vectorizer.transform(extracted_features))

predictions = category_encoder.inverse_transform(
    probabilities.argmax(axis=1))

trust = probabilities.max(axis=1)

person Fabio Picchi    schedule 26.03.2018    source источник
comment
Не могли бы вы использовать HashingVectorizer вместо этого? Какой у тебя тип vocabulary? Зачем вообще нужно травить векторизатор?   -  person krassowski    schedule 29.03.2018
comment
@krassowski Я обновил свой вопрос, включив в него дополнительную информацию о процессе классификации. Также словарь представляет собой набор строк, содержащих все признаки, извлеченные из документов.   -  person Fabio Picchi    schedule 29.03.2018


Ответы (1)


Если вы предоставляете свой собственный словарь CountVectorizer, не должно возникнуть проблем с его воссозданием позже, во время классификации. Поскольку вы предоставляете набор строк вместо сопоставления, вы, вероятно, захотите использовать проанализированный словарь, к которому вы можете получить доступ с помощью:

parsed_vocabulary = vectorizer.vocabulary_
joblib.dump(parsed_vocabulary, modelpath.joinpath('vocabulary.pickle'))

а затем загрузите его и используйте для воссоздания CountVectorizer:

vectorizer = CountVectorizer(
    vocabulary=parsed_vocabulary,
    binary=True,
    dtype=numpy.int8
)

Обратите внимание, что здесь вам не нужно использовать joblib; стандартный рассол должен работать так же; вы можете получить лучшие результаты, используя любую из доступных альтернатив, при этом стоит упомянуть PyTables.

Если это также использует большую часть памяти, вы должны попробовать использовать исходный vocabulary для воссоздания векторизатора; в настоящее время, когда в качестве словаря предоставляется набор строк, векторизаторы просто преобразуют наборы в отсортированные списки, поэтому вам не нужно беспокоиться о воспроизводимости (хотя я бы дважды проверил это перед использованием в производстве). Или вы можете просто преобразовать набор в список самостоятельно.

Подводя итог: поскольку вы не используете fit() векторизатор, вся дополнительная ценность использования CountVectorizer заключается в его transform() методе; поскольку все необходимые данные - это словарь (и параметры), вы можете уменьшить потребление памяти, маринуя только свой словарь, обработанный или нет.

Поскольку вы просили получить ответ из официальных источников, я хотел бы указать вам на: https://github.com/scikit-learn/scikit-learn/issues/3844, где владелец и участник scikit-learn упоминают воссоздание CountVectorizer, хотя и для других целей. Возможно, вам больше повезет сообщить о своих проблемах в связанном репо, но не забудьте включить набор данных, который вызывает проблемы с чрезмерным использованием памяти, чтобы сделать его воспроизводимым.

И, наконец, вы можете просто использовать HashingVectorizer, как упоминалось ранее в комментарии.

PS: что касается использования gc.collect() - в этом случае я бы попробовал; Что касается технических деталей, вы найдете много вопросов о том, как SO решает эту проблему.

person krassowski    schedule 29.03.2018
comment
krassowski, Спасибо за подробный ответ. Мне жаль, что я отклонился от основной темы моего вопроса, что привело к тому, что вы предложили альтернативы методу, который я использовал. Я действительно ценю ваши советы и, вероятно, воспользуюсь ими, но я действительно хотел знать, будет ли классификатор собран сборщиком мусора, если я вызову gc.collect прямо перед сбросом векторизатора. Кроме того, я не понимаю, как joblib.dump мог увеличить использование памяти до такой степени, что процесс был убит. Разве не следует вызывать gc, если свободной памяти не осталось? Если он был вызван, почему он не может освободить память? - person Fabio Picchi; 30.03.2018
comment
любой объект python будет собран при вызове gc.collect(), если нет сохраненных ссылок на этот объект, поэтому потребуется использование del; Если вы хотите, чтобы сборщик мусора запускался непосредственно перед исчерпанием памяти, вам лучше вызвать его вручную, чем надеяться, что он будет работать сам по себе. gc в python работает циклично, а не когда память заполнена; точнее запускается, когда выполняется операция выделения/освобождения определенного объема памяти; подробнее см. в документах или stackoverflow.com/a/22440880/6646912 - person krassowski; 30.03.2018