Как специалисту по Data Science написать классификатор, если часть данных неверно размечена

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


ментор курса Wargaming Forge: Game Data Analytics

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

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

Что такое неверная разметка и почему это происходит?

Неверно размеченные данные — это данные, метки которых не соответствуют действительности. К примеру, у вас есть набор картинок котиков и собачек, но часть котиков почему-то оказывается собачками согласно разметке. Такая проблема может возникнуть по нескольким причинам: субъективность человека, размечающего данные; ошибки при получении данных, и, в случае косвенной разметки, выбор неверного алгоритма. Очевидно, такие проблемы могут возникнуть в абсолютно любых областях: в медицине, развлечениях, обучении — где угодно.

Я работаю в Wargaming, наша область — это игры. А как известно, в играх проходит множество событий (таких, как акции на какой-либо контент, запуск нового режима), имеющих совершенно разные цели: начиная от привлечения новых игроков, заканчивая монетизацией или повышением их вовлеченности. Проведя очередное игровое событие, вы, как аналитик, получаете следующую задачу: реализовать алгоритм, с помощью которого можно будет прогнозировать участие игроков в подобном событии в будущем. Вы хотите получить инструмент вида:

Входные данные, которые вам доступны, включают:

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

Кажется, что ничто не мешает вам обучить алгоритм на основе характеристик игроков для прогнозирования вероятности его участия в событии. Однако во время проведения события что-то идёт не так, и вы понимаете, что часть игроков, которые хотели в нём поучаствовать, не могут этого сделать. Если это покупка — они пытаются её совершить, но не могут, — количество предлагаемого контента ограничено; если это новый режим — они пытаются сыграть в него, но у них не получается зайти в бой по каким-то техническим проблемам. Эти игроки так и останутся в статусе «не поучаствовал, но хотел бы».

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

При этом третьего столбца на самом деле не существует. И нет никакого технического способа проверить, правильна метка или нет. Всё, что вы знаете, — это то, что часть игроков попала в ошибочный класс.

Что делать?

Вариант первый: ничего!

Ну не можем мы обучить модель и сделать прогноз, — бывает. Мы всегда можем посчитать описательные статистики по игрокам-участникам события (к примеру, среднее количество боёв в день) и выделить простые правила для отбора потенциальных участников в новом событии. В случае среднего количества боёв в день, может получиться так, что у группы участников значение метрики в среднем на 30% выше, чем у не участников. Вот мы и будем предполагать, что все игроки, с похожим значением метрики, как у участников события, станут потенциальными участниками следующего события.

Full-stack разработчик

Ростелеком информационные технологии, Москва, По итогам собеседования

tproger.ru

Вакансии на tproger.ru

Плюсы:

  • Просто и быстро.

Минусы:

  • Отсутствие масштабируемости или ограниченное количество метрик, которые вы можете охватить. Если речь идёт про 1–2 метрики, использовать правила для них не составит труда. Но в действительности метрик, которые вы захотите сравнить, окажется в разы больше. А если вам вдруг захочется посмотреть на взаимное влияние нескольких признаков, сделать это будет очень сложно и преимущество, связанное со скоростью, уже не будет актуальным.
  • Точность/качество. Вы просто не сможете их адекватно оценить.
  • Влияние ошибочных данных. Неверно размеченных игроков вы всё же никуда не денете, поэтому их характеристики будут искажать значения рассматриваемых метрик. К примеру, в «не участников» события попадут потенциальные участники, с количеством боёв в день гораздо более высоким, чем у не участников. В итоге среднее значение или другая статистика, будут иметь ошибочно завышенное значение.

Вариант второй: обучить алгоритм на той разметке, которая есть

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

Плюсы:

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

Минусы:

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

Вариант третий – переразметить игроков и обучить модель на переразмеченных данных

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

Плюсы:

  • Масштабируемость. Это всё та же модель машинного обучения, с помощью которой вы учтёте множество признаков и их взаимосвязи.
  • Точность/качество. Смотри предыдущий пункт.
  • Отсутствие влияния ошибочных данных. Если у вас получилось качественно (а как это понять — будет описано ниже) переразметить выборку, то влияние ошибочных данных на результат обучения будет сведено к минимуму.

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

Как найти в выборке неверно размеченные объекты?

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

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

Допустим, исходные данные выглядят следующим образом. Здесь X={x1,x2,…, xn} — вектор признаков, описывающих каждого игрока, а y — целевая переменная, соответствующая тому, участвовал игрок в событии или нет (1 — участвовал, 0 — не участвовал соответственно).

import pandas as pd  import numpy as np    data = pd.read_csv('../data.csv')  data.head(n=10)  
data.y.value_counts(normalize=True)

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

from sklearn.model_selection import train_test_split  from sklearn.ensemble import  RandomForestClassifier    X_data, y_data = data.drop(['y'], axis = 1), data['y']  X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size=.3, random_state=RS)  clf = RandomForestClassifier(n_estimators=250, random_state=42, n_jobs=15)  clf.fit(X_train, y_train)  y_pred = clf.predict(X_test)  

Посмотрим на качество модели: выведем матрицу ошибок и основные метрики качества.

df_confusion = pd.crosstab(y_test, y_pred, rownames=['Actual'], colnames=['Predicted'], margins=True)  print(df_confusion)

from sklearn.metrics import classification_report  print(classification_report(y_test, y_pred))

Видим, что качество классификации для 1-го класса, т. е. участников события, очень низкая: recall1= 0,19, а f1-score= 0,29. Средний для модели f1-score= 0,62.

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

Будем надеяться, что вы решили идти дальше. Схематично вся переразметка данных сведётся к следующему. Исходные данные разобьём на N частей с равным распределением объектов из 0-го и 1-го классов. На каждых (N-1) частях обучим 5 или более методов машинного обучения, желательно разных по архитектуре и предсказывающих вероятность. В нашем случае используем уже знакомый Random Forest, а также Logistic regression, Naive Bayes, XGBoost, CatBoost.

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

from catboost import CatBoostClassifier  from sklearn.naive_bayes import GaussianNB  from sklearn.linear_model import LogisticRegression  from xgboost import XGBClassifier    clfs = {}    logreg_model = LogisticRegression(C=100)  clfs['LogReg'] = {'clf': LogisticRegression(), 'name':'LogisticRegression', 'model': logreg_model}    rf_model = RandomForestClassifier(n_estimators=250, max_depth=18, n_jobs=15)  clfs['RandomForest'] = {'clf': RandomForestClassifier(), 'name':'RandomForest', 'model': rf_model}    xgb_model = XGBClassifier(n_estimators=500, max_depth=10, learning_rate=0.1, n_jobs=15)  clfs['XGB'] = {'clf': XGBClassifier(), 'name': 'XGBClassifier', 'model': xgb_model}    catb_model = CatBoostClassifier(learning_rate=0.2, iterations=500, depth=10, thread_count=15, verbose=False)  clfs['CatBoost'] = {'clf': CatBoostClassifier(), 'name': 'CatBoostClassifier', 'model': catb_model}    nb_model = GaussianNB()  clfs['NB'] = {'clf': GaussianNB(), 'name':'GaussianNB', 'model': nb_model}

Далее исходные данные разбиваем на 5 частей с равномерным распределением примеров 0-го и 1-го классов.

data_0 = np.array_split(data[data['y'] == 0].sample(frac=1), 5)  data_1 = np.array_split(data[data['y'] == 1].sample(frac=1), 5)    dfs = {i: data_0[i].append(data_1[i]) for i in range(5)}

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

from sklearn.preprocessing import  StandardScaler    threshold = 0.5  relabeled_data = pd.DataFrame()  for i in range(5):      # test - i-й dataframe, train - все оставшиеся кроме i-го      df_test = dfs[i]      df_train = pd.concat([value for key, value in dfs.items() if key != i])      X_train, y_train = df_train.drop(['y'], axis=1), df_train['y']      X_test, y_test = df_test.drop(['y'], axis=1), df_test['y']            df_w_predicts = df_test.copy()      # обучение каждой модели на train и прогноз на test      for value in clfs.values():          model = value['model']          if value['name'] in ['LogisticRegression', 'GaussianNB']:              model.fit(StandardScaler().fit_transform(X_train), y_train)              predicts = (model.predict_proba(StandardScaler().fit_transform(X_test)                                                 )[:, 1] >= threshold).astype(bool)          else:              model.fit(X_train, y_train)              predicts = (model.predict_proba(X_test)[:, 1] >= threshold).astype(bool)                        df_w_predicts[value['name']] = predicts          relabeled_data = relabeled_data.append(df_w_predicts)  

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

Возникает логичный вопрос: как проверить качество переразметки? Как вариант, построить распределения признаков, характеризующих игроков, в разрезе реальных участников события, потенциальных участников (т. е. тех, кого мы переразметили с 0-го класса в 1-й), и не участников события. В результате вы получите следующее:

Отчётливо видно, что распределения основных метрик потенциальных участников (в прошлом «не участников») практически совпадают с распределениями реальных участников.

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

relabeled_data.drop(['y_old'], axis=1, inplace=True)  X_data, y_data = relabeled_data.drop(['y_new'], axis = 1), relabeled_data['y_new']  X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size=.3, random_state=42)  clf = RandomForestClassifier(n_estimators=250, random_state=42, n_jobs=15)  clf.fit(X_train, y_train)  y_pred = clf.predict(X_test)    df_confusion = pd.crosstab(y_test, y_pred, rownames=['Actual'], colnames=['Predicted'], margins=True)  print(df_confusion)

print(classification_report(y_test, y_pred))

Видим, что качество классификации, f1-score, вырос до 0.84, т. е. на 35%! Также теперь recall1= 0,64, при этом мы не потеряли в recall0. А значит, мы начали гораздо правильнее классифицировать потенциальных участников события.

Руководство по машинному обучению для начинающих: модель прогноза выживших на «Титанике»

tproger.ru

Что дальше?

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

  • Финальный метод классификации. Кроме Random Forest можно попробовать другие методы. Также нужно провести оптимизацию гиперпараметров алгоритма и порогового значения вероятности, при котором модель относит объект к 1-му классу.
  • Пороговое значение вероятности для переразметки. В нашем примере это значение 0.5. Его можно «двигать» в обе стороны в зависимости от результата, который вы хотите получить: переразметить как можно больше или как можно меньше игроков. В целом,при выборке порогового значения нужно, в первую очередь, руководствоваться здравым смыслом, каким-либо референсом (если он, конечно, есть) и, как было продемонстрировано, используя сравнения распределений основных метрик в фактическом и переразмеченном классах.
  • Попробовать другой подход к переразметке выборки. На просторах интернета можно найти реализации переразметки выборки основанные, к примеру, на сегментации. Изначально решается задача обучения без учителя, все данные делятся на сегменты. После этого, каждому сегменту присваивается метка класса, которая наиболее часто встречается среди объектов в данном сегменте. Таким образом, объекты с меткой, отличной от присвоенной, будут являться переразмеченными.

Надеюсь, в вашей работе такие ситуации будут встречаться очень редко, но если и будут, то данный материал окажется вам полезным!


Источник: tproger.ru

Комментарии: