VotingClassifier в sсikit-learn: построение и оптимизация ансамбля моделей классификации +7


В рамках реализации большой задачи по Sentiment Analysis (анализ отзывов) я решил уделить некоторое время дополнительному изучению её отдельного элемента — использованию VotingClassifier из модуля sklearn.ensemble как инструмента для построения ансамбля моделей классификации и повышению итогового качества предсказаний. Почему это важно и какие есть нюансы?



Часто случается, что по ходу решения прикладной задачи по анализу данных не сразу очевидно (или совсем неочевидно) какая модель обучения подходит наилучшим образом. Одним способом решения может быть выбор наиболее популярной и/или интуитивно подходящей модели исходя из характера имеющихся данных. В этом случае параметры выбранной модели оптимизируются (например, через GridSearchCV) и она используется в работе. Другим подходом может быть использование ансамбля моделей, когда результаты сразу нескольких из них участвуют в формировании конечного результата. Сразу оговорюсь, что цель статьи заключается не в описании преимущества использования ансамбля моделей или принципах его построения (об этом можно прочесть здесь), а скорей в одном отдельно прикладном подходе к решению задачи на конкретном примере и разборе возникающих по ходу такого решения нюансах.

Постановка глобальной задачи следующая: дано всего 100 отзывов на мобильные телефоны в качестве тестовой выборки и нам нужна предобученная модель, которая на этих 100 отзывах покажет лучший результат — а именно определит отзыв является положительным или отрицательным. Дополнительной сложностью, как следует из условий задачи, является отсутствие обучающей выборки. Для преодоления данной сложности с помощью библиотеки Beautiful Soup были успешно спарсены 10000 отзывов о мобильных телефонах и оценках к ним с одного из российских сайтов.

Пропуская этапы парсинга, предобработки данных и изучении их исходной структуры, мы переходом к моменту, когда имеются:

  • обучающая выборка, состоящая из 10000 отзывов о телефонах, каждый отзыв размечен бинарно (положительный или отрицательный). Разметка получения определением отзывов с оценками 1-3 как отрицательных и оценками 4-5 как положительных.
  • с использованием Count Vectorizer данные представлены в виде, который подходит для обучения моделей классификаторов

Как решить какая модель подойдет лучше всего? У нас нет возможности ручного перебора моделей, т.к. тестовая выборка лишь из 100 отзывов создает огромный риск того, что какая-то модель просто лучше подойдет под эту тестовую выборку, но если её использовать на дополнительной, скрытой от нас выборке или в «бою» результат будет ниже среднего.

Для решения этой проблемы в библиотеке Scikit-learn есть модуль VotingClassifier, являющийся отличным инструментом использования сразу нескольких, не похожих между собой, моделей машинного обучения и их объединения в один классификатор. Это позволяет снизить риск переобучения, а также неправильной интерпретации результатов какой-либо одной отдельно взятой модели. Модуль VotingClassifier импортируется следующей командой:
from sklearn.ensemble import VotingClassifier

Практические детали при работе с данным модулем:

1) Первое и самое важное — каким образом происходит получение отдельного взятого предсказания объединенного классификатора после получения предсказаний от каждой из входящих в него моделей. Среди параметров VotingClassifier есть параметр voting с двумя возможными значениями: 'hard' и 'soft'.

1.1) В первом случае итоговый ответ объединенного классификатора будет соответствовать «мнению» большинства входящих в него членов. Например, в вашем объединённом классификаторе используется данные от трех различных моделей. Две из них на конкретном наблюдении предсказывают ответ «отзыв положительный», третий — «отзыв отрицательный». Таким образом, для данного наблюдения итоговым предсказанием будет «отзыв положительный», так как у нас 2 — «за» и 1 «против».

1.2) Во втором случае, т.е. при использовании значения 'soft' параметра voting идет полноценное «голосование» и взвешивание предсказаний моделей для каждого класса, таким образом итоговый ответ объединенного классификатор — это argmax суммы предсказанных вероятностей. ВАЖНО! Для возможности использования такого метода «голосования» каждый классификатор из входящих в ваш ансамбль должен поддерживать метод predict_proba() для получения количественной оценки вероятности вхождения в каждый из классов. Обратите внимание, что не все модели классификаторов поддерживают этот метод и, соответственно, могут быть использованы в рамках VotingClassifier при использовании метода взвешенных вероятностей (Soft Voting).

Разберемся на примере: есть три классификатора и два класса отзывов: положительный и отрицательный. Каждый классификатор через метод predict_proba выдаст некое значение вероятности (p), с которой конкретное наблюдение отнесено им к классу 1 и, соответственно, с вероятностью (1-p) к классу два. Объединенный классификатор после получения ответа каждой из моделей, проводит взвешивание полученных оценок и выдает итоговый результат, полученный как

$$display$$max (w1*p1+w2*p1+w3*p1, w1*p2+w2*p2+w3*p3) $$display$$

, где w1,w2,w3 — это веса ваших классификатор, входящих в ансамбль, по умолчанию имеющих равные веса, а p1,p2 — оценка принадлежности к классу 1 или классу 2 каждого из них. Обратите внимание также на то, что веса классификаторов при использовании Soft Vote могут быть изменены с помощью параметра weights, таким образом вызов модуля должен выглядеть так:
... = VotingClassifier(estimators=[('..', clf1), ('..', clf2), ('...', clf3)], voting='soft', weights=[*,*,*]) , где звездочек могут быть указано требуемые веса для каждой модели.

2) Возможность одновременного использования модуля VotingClassifier и GridSearch для оптимизации гиперпараметров каждого из классификаторов, входящих в ансамбль.

Когда вы планируете использовать ансамбль и хотите, чтобы входящие в него модели были оптимизированы, можно воспользоваться GridSearch уже на объединенном классификаторе. И код, приведенный ниже, показывает как можно работать с входящими в него моделями (Логистическая регрессия, наивный Байес, стохастический градиентный спуск) оставаясь при этом в рамках объединенного классификатора (VotingClassifier):

clf1 = LogisticRegression()
clf2 = MultinomialNB()
clf3 = SGDClassifier(max_iter=1000, loss='log')
eclf = VotingClassifier(estimators=[ ('lr', clf1), ('nb', clf2),('sgd', clf3)], voting='hard') # задаем метод голосования через большинство (hard voting), см. п. 1.1

<b>params = {'lr__C' : [0.5,1,1.5], 'lr__class_weight': [None,'balanced'],
          'nb__alpha' : [0.1,1,2], 
          'sgd__penalty' : ['l2', 'l1'], 'sgd__alpha': [0.0001,0.001,0.01]} </b> # задаем сетку параметров для перебора и сравнения, важен синтаксис, чтобы оптимизировалась нужная модель  

grid = GridSearchCV(estimator=eclf, param_grid=params, cv=5, scoring='accuracy', n_jobs=-1)
grid = grid.fit(data_messages_vectorized, df_texts['Binary_Rate']) # когда задали все условия, проводим обучение и оптимизацию на 5 фолдах на собранной обучающий выборке 

Таким образом, словарь params должен быть задан таким образом, чтобы при обращении к нему через GridSearch можно было определить к какой из входящих в ансамбль моделей относится параметр, значение которого требуется оптимизировать.

Вот и все, что требуется знать для полноценного использования инструмента VotingClassifier как способа построения ансамбля моделей и его оптимизации. Посмотрим на результаты:

 print grid.best_params_
{'lr__class_weight': 'balanced', 'sgd__penalty': 'l1', 'nb__alpha': 1, 'lr__C': 1, 'sgd__alpha': 0.001}

Оптимальные значения параметров найдены, остается сравнить результаты работы для ансамбля классификаторов (VotingClassifier) с оптимальными параметрами, проведем кросс-валидацию на обучающей выборке и сравним модели с оптимальными параметрами и ансамбль, состоящей из них:

for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Naive Bayes', 'SGD', 'Ensemble_HardVoting']):
    scores = cross_val_score(clf, data_messages_vectorized, df_texts['Binary_Rate'], cv=3, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

Итоговый результат:

Accuracy: 0.75 (± 0.02) [Logistic Regression]
Accuracy: 0.79 (± 0.02) [Naive Bayes]
Accuracy: 0.79 (± 0.02) [SGD]
Accuracy: 0.79 (± 0.02) [Ensemble_HardVoting]

Как видно, модели проявили себя несколько по-разному на обучающей выборке (при стандартных параметрах это различие было более заметно). При этом итоговое значение (по метрике accuracy) ансамбля не обязательно должно превышать лучшего значения из входящих в него моделей, т.к. ансамбль скорей является более стабильной моделью, способной показать ± схожий результат на тестовой выборке и в «бою», а значит снизить риск переобучения, подгонки под обучающую выборку, и других сопутствующих обучению классификаторов проблем. Удачи в решении прикладных задачи и спасибо за внимание!

P.S. Учитывая специфику и правила публикации в песочнике, не могу привести ссылку на github и исходный код для анализа, приведенного в данной статье, также как и ссылки на Kaggle, в рамках InClass соревнования на котором предоставлялся тест сет и инструменты для проверки моделей на нем. Могу только сказать, что данный ансамбль существенно побил бейзлайн и занял достойное место на лидерборде после проверки на тест сете. Надеюсь в следующих публикациях смогу поделиться.




К сожалению, не доступен сервер mySQL