Русский
Русский
English
Статистика
Реклама

Мультиклассовая классификация

Как работать с иерархической структурой классов

22.04.2021 14:23:53 | Автор: admin

Задача классификации одна из самых известных в машинном обучении. Очень многие проблемы, решаемые с помощью ML, так или иначе сводятся к классификации распознавание изображений, например. И все выглядит просто и понятно, когда нам нужно определить объект в один из нескольких классов. А что если у нас не плоская структура из нескольких классов, а сложная разветвленная иерархия на 683 категории? Именно о таком случае мы сегодня и поговорим. Под катом рассказ о том, зачем в задачах классификации нужны сложные иерархии и как с ними жить.

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

Однако реальность может быть гораздо сложнее, ведь имеется некоторая структура, взаимосвязи между классами. Одна из часто встречаемых в жизни структур это дерево иерархии. Существует множество вещей, которые можно классифицировать по одному принципу, но на разных уровнях абстракции или обобщения. Например, известная со школы классификация животных. На первом уровне это могут быть Млекопитающие и Рептилии, на втором Дельфины и Ящерицы. Определенный объект можно отнести к одному из классов на каждом уровне, причем все объекты, относящиеся к некоторому классу, также относятся и к родителю этого класса.

Это звучит понятно в школьном учебнике, но в контексте машинного обучения возникает множество вопросов:

  • Как с этим всем обращаться?

  • Какие классы предсказывать?

  • Сколько моделей тренировать для решения задачи?

  • Как работать с данными?

  • Как вносить изменения в иерархию классов и как реагировать на эти изменения с точки зрения модели?

Все эти проблемы мы разберем на примере задачи классификации, которую мы решаем в Контуре.

Постановка задачи

Мы работаем с чеками. В каждом чеке может встретиться много разных товаров, которые можно сгруппировать множеством разных способов. На данный момент нам интересно группировать эти товары с помощью дерева категорий, которое мы будем называть KPC (Khajiit Product Classification). Это здоровенное дерево, состоящее из 683 категорий.

Для этих категорий у нас есть Golden Set, наш размеченный набор данных (штрихкод категория KPC) для обучения. В датасете почти три миллиона записей и мы постоянно работаем над его дополнением и улучшением разметки.

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

Давайте разберемся, как выглядят разные этапы классификации с иерархической структурой классов.

Разметка данных

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

Так появились статусы категорий. Они помогают понять, в каком состоянии категория прямо сейчас. Благодаря статусам мы можем отдельно обрабатывать только что появившиеся категории или категории, которые скоро будут удалены. На текущий момент мы выделяем четыре статуса категорий:

  • Активный.

  • Запланированный категория, на которую мы хотим классифицировать, но пока не можем (не хватает данных или не почистили разметку).

  • Архивный категория, которую решено было удалить, но товары из категории еще не переразметили.

  • Удаленный.

В зависимости от статуса мы решаем, что делать с конкретной категорией на каждом из этапов классификации.

Также стоит упомянуть две отдельные группы категорий:

  • свалка (например Одежда (свалка)) группа для логического удаления некоторых товаров, которые невозможно категоризировать. Например, у нас есть товар Полное тестирование Тест 2 10шт, у которого из какого-то источника данных стоит категория Одежда. Наш аналитик данных понимает, что по факту категории у такого товара нет, поэтому такой товар отправляется в свалку, которая при обучении не рассматривается.

  • другое/другие (например Молочные товары, сыры и яйцо (другое)) группа для товаров, которые относятся к родительской категории, но не относятся (возможно, пока) ни к одной из дочерних.

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

Добавление категории

Добавить категорию мы можем в любое время, но для того, чтобы товары начали в неё попадать, необходимо:

  • Добавить категорию в KPC.

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

  • Переобучить модель, чтобы она научилась классифицировать товары в новую категорию.

Удаление категории

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

Для удаления категории необходимо:

  • Перевести категорию в статус Архивная.

  • Решить, что мы делаем с товарами, которые относились к удаляемой и дочерним категориям.

  • Заменить удаляемую категорию у товаров в Golden Set.

  • Указать дочерним категориям новую родительскую категорию или её отсутствие (если дочерняя категория должна стать категорией верхнего уровня).

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

  • Перевести категорию в статус Удаленная.

Разбиение категории

Если появляется необходимость разбить одну категорию на несколько новых, нужно:

  • Обновить категории в Golden Set, чтобы отнести товары к новым категориям.

  • Переобучить модель, чтобы она научилась классифицировать товары в новые категории.

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

Обучение модели

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

Такие коллизии плохо влияют на обучение товар может одновременно оказаться в обеих категориях и это может смутить нашу модель. Чтобы избежать таких случаев, перед обучением добавлен этап разрешения конфликтов дерева категорий (KPC collisions resolving).

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

Алгоритм разрешения конфликтов

Для начала разберемся с тем, что такое конфликт или коллизия. Коллизией мы считаем ситуацию, когда пара категорий ребенок/родитель одновременно представлена в KPC. То есть часть товаров размечена родительской категорией, часть дочерней. Как уже упоминалось выше, такие ситуации плохо влияют на обучение.

Для разрешения этих ситуаций мы будем маппить категории (то есть переносить все товары из одной категории в другую), а после обновлять наше дерево категорий (оставлять в рассмотрении только те категории, которые представлены в Golden set), проверяя, остались ли коллизии.

Теперь важно понять желаемый конечный результат. Выписать несколько возможных ситуаций из дерева категорий и понять, как мы хотим эти ситуации решать. Понять, какие категории в какую смаппятся, а какие категории в итоге останутся и будут учтены классификатором.

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

Future/Active на схеме это статусы категорий Запланированная/Активная, а present/NOT present in GS представлена ли категория в Golden set.

Еще один вопрос, который важно разобрать что мы хотим делать с Запланированными категориями? И что мы хотим делать с их детьми?

Есть несколько вариантов. Мы можем:

  • Использовать эти категории в классификации.

  • Не классифицировать и выбросить категории из GS.

  • Переразмечать эти категории в категорию-родителя.

  • Переразмечать эти товары в категорию другое/другие (например Молочные продукты, сыры и яйцо (другое))

После разбора таких случаев, ответов на архитектурные вопросы и итеративной разработки мы пришли к следующему алгоритму.

  1. Убрать удаленные, редко встречающиеся (меньше 10 товаров в golden set) и те категории, у которых в названии есть свалка.

  2. Если все дети категории в статусе Запланированный, то дочерние категории маппятся в родителя. Это происходит итеративно, так как после первого маппинга может возникнуть аналогичная ситуация на другом уровне дерева.

  3. Смаппить запланированные категории в sibling-категорию с другое/другие в названии, если такая есть.

  4. Удалить из Golden Set категории, у которых есть категории-потомки с товарами в Golden Set. Здесь происходит то самое разрешение конфликтов.

На каждом этапе мы рассматриваем уже обновленное дерево категорий. Важность этого пункта вытекла из разбора неочевидных ситуаций. Например, если не обновлять дерево (не убирать категории без товаров в GS и без потомков с товарами), то правило Смаппить всех Запланированных детей к родителю может не сработать из-за пустого, но активного ребенка.

Валидация модели

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

В первую очередь мы сравниваем базовые метрики accuracy (по всем классам) и accuracy/coverage. Необходимо следить за тем, чтобы баланс покрытия и точности не нарушался, а также за возможностью подобрать threshold для новой модели, при котором этот баланс соответствует нашим требованиям.

Во вторую очередь будем смотреть на метрики отдельно по каждому классу. Сначала на те, с которыми модель непосредственно знакома. Затем на родительские классы, путем агрегации (взвешенное среднее).

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

В третью очередь мы строим confusion matrix для всех категорий, чтобы понять между какими классами больше всего путается модель. Эти классы помогают задавать направление для дальнейшего развития.

'errors' - sum of errors of confusing two labels,
'label_1_confuse' - count(true=label_1, pred=label_2) / 'errors',
'label_2_confuse' - count(true=label_2, pred=label_1) / 'errors',
'fraction_of_error_to_label_1' - count(true=label_1, pred=label_2) / total_label_1,
'fraction_of_error_to_label_2' - count(true=label_2, pred=label_1) / total_label_2,
'fraction_of_all_errors' - 'errors' / total_errors,
'fraction_of_all_errors_cumulative'

Выводы

  1. Для удобной итеративной работы с деревом категорий полезно ввести статусы категорий. Такие статусы позволят обрабатывать категории в разном состоянии разными способами.

  2. При валидации стоит агрегировать метрики для родительских категорий, чтобы иметь возможность сравнить две модели с разными наборами классов.

  3. Важно следить, чтобы в обучающей выборке не смешивались сэмплы из родительского и из дочернего классов. Также важно продумать желаемый результат разрешения таких конфликтов в разных конфигурациях дерева, статусов и состояния разметки категорий.

Подробнее..

Датасет о мобильных приложениях

25.05.2021 12:05:50 | Автор: admin

Вступление

Моя основная работа связана с мобильной рекламой, и время от времени мне приходится работать с данными о мобильных приложениях. Я решил сделать некоторые данные общедоступными для тех, кто хочет попрактиковаться в построении моделей или получить представление о данных, которые можно собрать из открытых источников. Я считаю, что открытые наборы данных всегда полезны сообществу. Сбор данных часто бывает сложной и унылой работой, и не у всех есть возможность сделать это. В этой статье я представлю датасет и, используя его, построю одну модель.

Данные

Датасет опубликован на сайте Kaggle.

DOI: 10.34740/KAGGLE/DSV/2107675.

Для 293392 приложений (наиболее популярных) собраны токены описаний и сами данные приложений, включая оригинальное описание. В наборе данных нет имен приложений; их идентифицируют уникальные идентификаторы. Перед токенизацией большинство описаний были переведены на английский язык.

В датасете 4 файла:

  • bundles_desc.csvсодержит только описания;

  • bundles_desc_tokens.csvсодержит токены и жанры;

  • bundles_prop.csv, bundles_summary.csvсодержат рпзличные характеристики приложений и даты релиза/обновления.

EDA

Прежде всего, давайте посмотрим, как данные распределяются по операционным системам.

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

Учитывая, что набор данных содержит только самые популярные приложения, интересно посмотреть, как распределяется дата выпуска.

histnorm ='probability' # type of normalization

Следующий график показывает, что большинство приложений обновляются регулярно.

Основные данные были собраны за короткий период времени в январе 2021 года.

Добавим новую фичу - количество месяцев между датой выпуска и последним обновлением.

df['bundle_update_period'] = \    (pd.to_datetime(        df['bundle_updated_at'], utc=True).dt.tz_convert(None).dt.to_period('M').astype('int') -      df['bundle_released_at'].dt.to_period('M').astype('int'))у

Интересно посмотреть, как распределены жанры приложений. Принимая во внимание дисбаланс ОС, я нормализую данные для гистограммы.

Мы видим, что жанры полностью не пересекаются. Особенно это заметно в играх. Для анализа такая ситуация крайне неприятна. Что мы можем с этим поделать? Самое очевидное - уменьшить количество жанров для Android и привести их к тому же виду, что и для iOS путем сведения всех игровых жанров к одному Games. Но я полагаю, что это не лучший вариант, так как будет потеря информации. Попробуем решить обратную задачу. Для этого нужно построить модель, которая будет предсказывать жанры приложений по их описанию.

Модель

Я создал несколько дополнительных фичей, используя длину описания и количество токенов.

def get_lengths(df, columns=['tokens', 'description']):    lengths_df = pd.DataFrame()    for i, c in enumerate(columns):        lengths_df[f"{c}_len"] = df[c].apply(len)        if i > 0:            lengths_df[f"{c}_div"] = \                lengths_df.iloc[:, i-1] / lengths_df.iloc[:, i]            lengths_df[f"{c}_diff"] = \                lengths_df.iloc[:, i-1] - lengths_df.iloc[:, i]    return lengths_dfdf = pd.concat([df, get_lengths(df)], axis=1, sort=False, copy=False)

В качестве еще одной фичи я взял количество месяцев, прошедших с даты выпуска приложения. Идея состоит в том, что на рынке в разные периоды могло быть какое-то предпочтение игровым жанрам.

Для обучения используются данные Android-приложений.

android_df = df[df['store_os']=='android']ios_df = df[df['store_os']=='ios']

Окончательный список фичей модели выглядит следующим образом:

columns = [    'genre', 'tokens', 'bundle_update_period', 'tokens_len',    'description_len', 'description_div', 'description_diff',    'description', 'rating', 'reviews', 'score',    'released_at_month']

Я разделил данные на две части - train и validation. Обратите внимание, что разделение должно быть стратифицировано.

train_df, test_df = train_test_split(    android_df[columns], train_size=0.7, random_state=0, stratify=android_df['genre'])y_train, X_train = train_df['genre'], train_df.drop(['genre'], axis=1)y_test, X_test = test_df['genre'], test_df.drop(['genre'], axis=1)

В качестве библиотеки для модели я выбрал CatBoost. CatBoost - это высокопроизводительная библиотека для градиентного бустинга на деревьях решений с открытым исходным кодом. Основным преимуществом является то, что CatBoost может использовать категориальные и текстовые фичи без дополнительной предварительной обработки. Текстовые фичи для классификации поддерживаются начиная с версии 0.19.1

В Нетрадиционный анализ тональности текста: BERT vsCatBoost я привожу пример того, как CatBoost работает с текстом и сравниваю его с BERT.

!pip install -U catboost

При работе с CatBoost рекомендую использовать Pool. Это удобная оболочка, объединяющая метки и другие метаданные, такие как категориальные и текстовые фичи. Бонусом идет снижение затрат памяти, так как не происходит дополнительная конвертация внутри библиотеки.

train_pool = Pool(    data=X_train,     label=y_train,    text_features=['tokens', 'description'])test_pool = Pool(    data=X_test,     label=y_test,     text_features=['tokens', 'description'])

Напишем функцию для инициализации и обучения модели. Я не подбирал оптимальные параметры; пусть это будет еще одним домашним заданием.

def fit_model(train_pool, test_pool, **kwargs):    model = CatBoostClassifier(        random_seed=0,        task_type='GPU',        iterations=10000,        learning_rate=0.1,        eval_metric='Accuracy',        od_type='Iter',        od_wait=500,        **kwargs    )return model.fit(        train_pool,        eval_set=test_pool,        verbose=1000,        plot=True,        use_best_model=True    )

Текстовые фичи используются для создания новых числовых фичей. Но для этого необходимо объяснить CatBoost, что именно мы хотим от него получить.

CatBoostClassifier имеет несколько параметров:

  • tokenizersиспользуемые для предварительной обработки фичей текстового типа перед созданием словаря;

  • dictionariesиспользуется для предварительной обработки фичей текстового типа;

  • feature_calcersиспользуется для расчета новых фичей;

  • text_processingобщий JSON-конфиг для токенизаторов, словарей и вычислителей, который определяет, как текстовые фичи преобразуются в фичи с плавающей точкой.

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

tpo = {    'tokenizers': [        {            'tokenizer_id': 'Sense',            'separator_type': 'BySense',        }    ],    'dictionaries': [        {            'dictionary_id': 'Word',            'token_level_type': 'Word',            'occurrence_lower_bound': '10'        },        {            'dictionary_id': 'Bigram',            'token_level_type': 'Word',            'gram_order': '2',            'occurrence_lower_bound': '10'        },        {            'dictionary_id': 'Trigram',            'token_level_type': 'Word',            'gram_order': '3',            'occurrence_lower_bound': '10'        },    ],    'feature_processing': {        '0': [            {                'tokenizers_names': ['Sense'],                'dictionaries_names': ['Word'],                'feature_calcers': ['BoW']            },            {                'tokenizers_names': ['Sense'],                'dictionaries_names': ['Bigram', 'Trigram'],                'feature_calcers': ['BoW']            },        ],        '1': [            {                'tokenizers_names': ['Sense'],                'dictionaries_names': ['Word'],                'feature_calcers': ['BoW', 'BM25']            },            {                'tokenizers_names': ['Sense'],                'dictionaries_names': ['Bigram', 'Trigram'],                'feature_calcers': ['BoW']            },        ]    }}

Запустим обучение:

model_catboost = fit_model(    train_pool, test_pool,    text_processing = tpo)
AccuracyAccuracyLossLoss
bestTest = 0.6454657601

Только две фичи имеют большое влияние на модель. Скорее всего, качество можно повысить за счет использования summary, но, поскольку этих данных нет в приложениях iOS, быстро применить не удастся. Можно использовать модель, которая может получить короткий абзац текста из описания. Я оставлю это задание в качестве домашнего задания читателям.

Судя по цифрам, качество не очень высокое. Основная причина заключается в том, что приложения часто сложно отнести к одному конкретному жанру, и при указании жанра присутствует предвзятость разработчика. Требуется более объективная характеристика, отражающая несколько наиболее подходящих жанров для каждого приложения. Таким признаком может быть вектор вероятностей, где каждый элемент вектора соответствует вероятности отнесения к тому или иному жанру.

Чтобы получить такой вектор, нам нужно усложнить процесс, используя предсказания OOF (Out-of-Fold). Не будем использовать сторонние библиотеки; попробуем написать простую функцию.

def get_oof(n_folds, x_train, y, x_test, text_features, seeds):        ntrain = x_train.shape[0]    ntest = x_test.shape[0]              oof_train = np.zeros((len(seeds), ntrain, 48))    oof_test = np.zeros((ntest, 48))    oof_test_skf = np.empty((len(seeds), n_folds, ntest, 48))    test_pool = Pool(data=x_test, text_features=text_features)     models = {}    for iseed, seed in enumerate(seeds):        kf = StratifiedKFold(            n_splits=n_folds,            shuffle=True,            random_state=seed)                  for i, (tr_i, t_i) in enumerate(kf.split(x_train, y)):            print(f'\nSeed {seed}, Fold {i}')            x_tr = x_train.iloc[tr_i, :]            y_tr = y[tr_i]            x_te = x_train.iloc[t_i, :]            y_te = y[t_i]            train_pool = Pool(                data=x_tr, label=y_tr, text_features=text_features)            valid_pool = Pool(                data=x_te, label=y_te, text_features=text_features)            model = fit_model(                train_pool, valid_pool,                random_seed=seed,                text_processing = tpo            )            x_te_pool = Pool(                data=x_te, text_features=text_features)            oof_train[iseed, t_i, :] = \                model.predict_proba(x_te_pool)            oof_test_skf[iseed, i, :, :] = \                model.predict_proba(test_pool)            models[(seed, i)] = model    oof_test[:, :] = oof_test_skf.mean(axis=1).mean(axis=0)    oof_train = oof_train.mean(axis=0)    return oof_train, oof_test, models

Обучение трудозатратно, но в результате получили:

  • oof_trainOOF-предсказания для Android приложений

  • oof_testOOF-предсказания для iOS приложений

  • modelsall OOF-модели для каждого фолда и сида

from sklearn.metrics import accuracy_scoreaccuracy_score(    android_df['genre'].values,    np.take(models[(0,0)].classes_, oof_train.argmax(axis=1)))

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

OOF accuracy: 0.6560790777135628

Я созданную фичу android_genre_vec, копируем значения из oof_train для приложений Android и oof_test для приложений iOS.

idx = df[df['store_os']=='ios'].indexdf.loc[df['store_os']=='ios', 'android_genre_vec'] = \    pd.Series(list(oof_test), index=idx)idx = df[df['store_os']=='android'].indexdf.loc[df['store_os']=='android', 'android_genre_vec'] = \    pd.Series(list(oof_train), index=idx)

Дополнительно был добавлен android_genre, в котором находится жанр с максимальной вероятностью.

df.loc[df['store_os']=='ios', 'android_genre'] = \    np.take(models[(0,0)].classes_, oof_test.argmax(axis=1))df.loc[df['store_os']=='android', 'android_genre'] = \    np.take(models[(0,0)].classes_, oof_train.argmax(axis=1))

После всех манипуляций, можно наконец-то посмотреть и сравнить распределение приложений по жанрам.

Итоги

В статье:

  • представлен новый бесплатный датасет;

  • сделан небольшой EDA;

  • созданы несколько новых фичей;

  • создана модель для предсказания жанров приложений по описаниям.

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

Код из статьи можно посмотреть здесь.

Подробнее..

Перевод Мульти-классовое целе-вероятностное кодирование (Multi-Class Target Encoding)

04.03.2021 00:14:15 | Автор: admin

Что не так с TargetEncoder из библиотеки category_encoders?

Эта статья является продолжением предыдущей статьи, в которой объяснялось, как на самом деле работает целе-вероятностное кодирование. В этой статье мы посмотрим в каких случаях стандартное решение библиотеки category_encoders дает неверный результат, а кроме того, изучим теорию и пример кода для корректного мульти-классового целе-вероятностного кодирования. Поехали!

1. Когда ошибается TargetEncoder?

Посмотрите на эти данные. Цвет - это особенность, а цель - это цель. Наша цель - кодировать цвет на основе Target.

Давайте сделаем для этого обычную целе-вероятностную кодировку.

import category_encoders as cece.TargetEncoder(smoothing=0).fit_transform(df.Color,df.Target)

Хмм выглядит не очень, не так ли? Все цвета были заменены на 1. Почему? Так происходит потому, что TargetEncoder принимает среднее значение всех целевых значений для каждого цвета, а не вероятность.

Хотя TargetEncoder корректно работает в случае, когда у вас есть двоичная цель, имеющая 0 и 1, он будет давать сбой в двух случаях:

  • Когда цель двоичная, но не 0/1 (хотя бы, например 1 и 2).

  • Когда цель - мультикласс, как в приведенном выше примере.

Так что же делать?!


Теория

В оригинальном документе Daniele Micci-Barreca, который вводит средне-целевую кодировку говориться про мульти-классовые целевые переменные.

Допустим, мы имеем n классов. Теория гласит, что первым делом нужно закодировать значения класса в бинарные переменные. Это даст n двоичных столбцов, по одному на каждый класс цели. Однако только n-1 двоичных столбцов будут линейно независимы, так что в принципе, любой из них можно отбросить. После чего применим обычное целе-вероятностное кодирование для наших новых категорий, используя каждую из двоичных меток, по одной за раз.

Давайте разберемся на примере.

Пример

Продолжим с предыдущими данными.

Шаг 1: Бинарное кодирование мульти-классовой категории.

enc=ce.OneHotEncoder().fit(df.Target.astype(str))y_onehot=enc.transform(df.Target.astype(str))y_onehot

Обратите внимание, что столбец Target_1 показывает наличие либо отсутствие значения 0 в исходном столбце Target. Он принимает значение 1 если в Target есть 0, либо 0 в противном случае. Точно так же столбец Target_2 показывает наличие или отсутствие 1 в Target.

Шаг 2: Кодируем цвет, используя каждую из бинарных категорий.

class_names = y_onehot.columnsfor class_ in class_names:     enc = ce.TargetEncoder(smoothing = 0)    print(enc.fit_transform(X,y_onehot[class_]))

Для класса 0

Для класса 1

Для класса 2

Шаг 3: Если есть другие категории, кроме цвета, повторяем шаги 1 и 2 для них.

Готово!

Таким образом, на выходе получаем такой набор данных:

Обратите внимание, что для ясности я закодировал здесь все три столбца Color_Target. Если вы знаете бинарное кодирование, то слышали, что один из столбцов можно удалить без потери информации. Следовательно, здесь мы можем безопасно удалить, например, столбец Color_Target_3 (либо какой-то другой) без потери информации.

Практика

Вы здесь за кодом, не так ли?!

Ниже представлена функция, которая принимает на вход таблицу данных и объект целевой метки типа Series. Функция df может иметь как числовые, так и категориальные переменные.

def target_encode_multiclass(X,y): #X,y are pandas df and series    y=y.astype(str)  #convert to string to onehot encode    enc=ce.OneHotEncoder().fit(y)    y_onehot=enc.transform(y)     class_names=y_onehot.columns  #names of onehot encoded columns    X_obj=X.select_dtypes('object') #separate categorical columns    X=X.select_dtypes(exclude='object')    for class_ in class_names:        enc=ce.TargetEncoder()        enc.fit(X_obj,y_onehot[class_]) #convert all categorical        temp=enc.transform(X_obj)       #columns for class_        temp.columns=[str(x)+'_'+str(class_) for x in temp.columns]        X=pd.concat([X,temp],axis=1)    #add to original dataset    return X

Резюме

В этой статье я показал, что не так с TargetEncoder из библиотеки category_encoder, объяснил, что говорится в оригинальной статье о целевом кодировании мультиклассовых переменных, продемонстрировал всё это на примере и предоставил рабочий модульный код, который вы можете подключить к своему приложению.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru