Многометочная стратификация


О чем это

Многометочные (multilabel) данные - данные, в которых у каждого примера может быть несколько меток одновременно.

Иногда нам нужно отрезать часть данных - чаще всего для тестовой выборки. Например, на 80% данных учимся, на 10% подбираем гиперпараметры, на оставшихся 10% смотрим, что получилось. Почти всегда хочется, чтобы распределение меток в разных подвыборках сохранилось.

Обычно такое желание возникает, когда мы строим классификатор - модель, которая предсказывает метки для каждой точки.

В чем проблема

Бывает, что у каждой точки в данных одна метка. На фотографии кошка или собака. Гриб съедобен или нет. Дали кредит или отказали.

В таком случае нам нужно посчитать, сколько примеров каждого класса должно быть в выборке, и соответственно поделить данные. Например, с помощью StratifiedKFold.

Многометочные данные сложнее. Например, книга может быть про любовь, Древний Рим и войну. Клиент может брать кредит, открывать вклад и брать банковскую гарантию. Магазин может торговать бакалеей и посудой одновременно.

Можно думать про многометочные данные как про несколько независимых датасетов и отдельно строить классификаторы - книга про Рим или не про Рим, про любовь или нет и так далее.

Так иногда и поступают, но когда категорий много, это неудобно. На 300 категорий нужно 300 моделей. Иногда категории связаны между собой - например, если про Рим, то и про войну наверняка тоже. Модели может быть удобно знать все метки сразу.

Если бы у нас были бесконечные патроны, можно было бы честно сформулировать разбиение на фолды как оптимизационную задачу и решить ее с помощью какого-нибудь сольвера. Но с большим датасетом это непрактично - не доживем до конца расчета.

Как быть

В реальных многометочных данных метки встречаются неравномерно. Например, у нас 26 классов, и к самому частому из них принадлежит 40% точек, а к самому редкому - 0,04%. Именно с такой ситуацией я столкнулся в 2016 году, и не нашел подходящей библиотеки. Пришлось написать свою.

Позднее выяснилось, что библиотека была, и даже была хорошая статья, а я просто не умел искать. С тех пор примерно раз в год я кому-нибудь рассказываю, как это сделать. Если вы так же плохо гуглите, как я, вот решение:

  1. Выбираем случайно точки, чтобы заполнить самый редкий класс.
  2. Заполняем самый редкий из оставшихся.
  3. И так далее.

Например, в датасете 1000 растений, 10 из них ядовито, 50 - лекарственные (и часть из них ядовита), 200 красиво цветут. Нужно отобрать 10%

Для начала случайно выберем одно ядовитое. Затем дополним до 5 лекарственными. Потом докинем красивыми до 20. Потом на всякий случай проверим, хорошо ли получилось.

Чем неравномернее распределены метки, тем больше шансов, что у нас все получится с первого раза.

Алгоритм легко пишется руками. Есть, правда, несколько неочевидных тонкостей. Мою первоначальную версию коллеги здорово улучшили и ускорили, но я вам ее не продам. Возьмите лучше бесплатные библиотеки:

Ссылки

С библиотекой scikit-multilearn-ng вышло забавно. Автор статьи опубликовал библиотеку scikit-multilearn и какое-то время поддерживал ее, потом бросил.

Сайт с документацией прокис, пулл-реквесты автор не принимал, проблемы не решались, и неравнодушный разработчик форкнул библиотеку с новым именем scikit-multilearn-ng. Прошло два года, и он тоже не принимает пулл-реквесты и не решает проблемы. Если не лень - можете тоже форкнуть, попросить Claude Code пофиксить баги и опубликовать под именем scikit-multilearn-ng2, станете опенсоурс разработчиком.

Навеяно соревнованием киберполка.