Возможная реализация FreeAI от IBM

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

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

Без дальнейших церемоний, давайте погрузимся.

0 — Технический обзор

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

FreaAI предлагает решение для моделирования объяснимости, предвзятости и обобщаемости.

1 — Настройка

В разделе 1 мы быстро повторим то, что рассмотрели в предыдущем посте.

1.1 — Получить данные

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

import pandas as pd
df = pd.read_csv('DiabetesData/pima-indians-diabetes.data.csv', header=None)

Мы также создадим несколько фиктивных категориальных столбцов, которые должны иметь очень зашумленные результаты…

# add some more columns for fun (one hot encoded categorical)
np.random.seed(0)
enc_df = pd.DataFrame(dict(
        color=['red' if x > 0.25 else 'green' for x in np.random.rand(len(df.index))],
        gender=['male' if x > 0.55 else 'female' for x in np.random.rand(len(df.index))]
))
enc = OneHotEncoder(handle_unknown='ignore')
enc_df = pd.DataFrame(enc.fit_transform(enc_df[['color','gender']]).toarray())
df = pd.concat([df, enc_df], ignore_index=True, axis=1)
df.columns = ['num_pregnancies','glucose','blood_pressure','skin_thickness','insulin','bmi','diabetes_pedigree','age','outcome', 'color_red','color_green','gender_male','gender_female']

1.2 — Обучение модели черного ящика

Далее мы обучаем модель XGBoost…

# split data into X and y
mask = np.array(list(df)) == 'outcome'
X = df.loc[:,~mask]
Y = df.loc[:,mask]
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(
                                     X, Y,
                                     test_size=test_size, 
                                     random_state=seed)
# fit model no training data
model = XGBClassifier()
model.fit(X_train, y_train)
# make predictions for test data
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))# add accuracy and return
out = pd.concat([X_test, y_test], axis=1, ignore_index=True)
out.columns = list(df)accuracy_bool = (np.array(y_test).flatten() ==
                 np.array(predictions))
out['accuracy_bool'] = accuracy_bool
out

Эта модель достигает точности ~ 71%, используя вышеуказанную конфигурацию. Чтобы сделать эту точность действенной, мы также создали столбец accuracy_bool, который является логическим значением True, если метка была предсказана правильно, иначе это False.

Отлично, мы все в курсе.

2 - Обучите интерпретируемое дерево решений

Хорошо, давайте немного замедлимся и действительно поймем часть нашего кода, связанную с деревом решений (DT). Как и в случае с методами наивысшей априорной плотности, мы стремимся выявить области наших данных, которые демонстрируют аномально низкую точность. Итак, мы тренируем дерево решений, в котором наши…

  • Функции — это одна или две функции, используемые в модели XGBoost.
  • Целевая переменная — это столбец accuracy_bool, созданный выше.

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

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

def fit_DT(df, predictors = ['age']):
    """ Fit binary classifier"""
    X = df[predictors] 
    y = df['accuracy_bool']
    model = DecisionTreeClassifier(max_depth=3, 
                                   criterion='entropy',
                                   random_state=1)
    model.fit(X, y)
    preds = model.predict(X)
    acc = accuracy_score(y, preds)
    return model, preds, acc, X

С помощью этого помощника мы можем передавать интересующие функции в параметр predictors и (надеюсь) получать разбиения с низкой точностью.

2.1 — Одномерное дерево решений

В качестве примера возьмем числовой столбец age, а затем визуализируем его с помощью функции export_graphviz в sklearn.

На рисунке 3 каждый узел (синяя рамка) соответствует срезу данных. Листья, скорее всего, будут иметь «маленькие» sample размеры и самые экстремальные entropy, поэтому мы сосредоточимся на них. Точнее, мы ищем листья, которые…

  1. Иметь низкую энтропию (высокая чистота узла)
  2. Иметь достаточный размер выборки, чтобы быть полезным
  3. Демонстрирует низкую точность по сравнению с нашим базовым уровнем 71%

Выполняя приведенную выше логику, мы видим, что узлы 3, 4 и 12 имеют энтропию ‹ 0,3. Узел 12 мы можем сразу выкинуть, потому что он имеет 2 сэмпла, что делает его довольно бесполезным. Узлы 3 и 4 имеют достаточный размер выборки, однако их точность составляет 94,7 % и 95,8 % соответственно, что указывает на то, что они подчеркивают сильные стороны модели, а не слабые.

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

2.2 — Двумерное дерево решений

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

Используя параметр filled=True в библиотеке graph_viz sklearn, мы видим, что на рисунке 4 оранжевые узлы имеют больше values в 1-м индексе, чем во 2-м, что указывает на то, что у нас больше неправильных классификаций, чем классификаций.

Опять же, проходя по вышеуказанным критериям, мы ищем чистые листья с достаточным размером выборки и низкой точностью. Узлы 10, 11 и 12 четко показывают области низкой точности, но имеют относительно небольшие размеры выборки. Точность узла 9, рассчитанная с использованием values[1] / sum(values), составляет 55%, что на 16 процентных пунктов ниже нашего базового уровня в 71%. Эти узлы следует изучить дополнительно.

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

  • Для людей с glucose ≤ 104,5 мы показываем точность 88%, как показано в нашем корневом узле. Если glucose ≤ 87,5, мы демонстрируем 100% точность, как показано в узле 2. Глюкоза — чрезвычайно полезная характеристика в левой части ее распределения.
  • Для людей с glucose> 104,5 и blood_pressure> 91,0 наша точность падает до 18%. Оба высоких значения blood_pressure и glucose являются зашумленными областями наших данных.

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

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

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

Сразу же мы можем обнаружить некоторые аномалии. Для нашего самого правого пути принятия решений мы видим, что наш самый правый лист соответствует 5 ошибочным классификациям, где уровень глюкозы очень высок, что согласуется с выводами из предыдущего дерева. Кроме того, мы получаем действительно хорошее изображение нашей области высокой точности на самом левом листе — это просто часть корневого распределения с glucose ≤ 87,5.

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

Например, для узла 11 индексы строк этих 6 точек данных: [58, 87, 105, 108, 222, 230]. Оттуда, старый добрый EDA, мы надеемся, даст больше информации.

Мы не будем описывать эти шаги для краткости, но, надеюсь, вы поняли картину.

3 — Определите срезы с низкой точностью

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

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

Как вы можете видеть на рисунке 6, наш вывод включает 4 столбца:

  1. names: имя(а) функции или процентное разделение для HPD
  2. indices: индексы строк этого среза данных
  3. accuracies: точность этого фрагмента данных
  4. method: использовали ли мы дерево решений (DT) или наивысшую априорную плотность (HPD)

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

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

Мы также можем видеть, что текущая реализация HPD не была суперэффективной для определения срезов с низкой точностью — самый слабый срез показал точность 66%. Хотя HPD очень эффективен, имеет смысл, что деревья решений, обученные разбивать на чистоту узлов, будут находить более слабые срезы.

Наконец, это всего лишь фиктивный пример, который был плохо собран как POC. Этот базовый уровень можно значительно улучшить, и реализация FreeAI от IBM, несомненно, обладает гораздо большей функциональностью и оптимизацией. Не стесняйтесь использовать этот код как отправную точку :)

4 — Резюме и следующие шаги

Напомним, в этом посте мы рассмотрели потенциальную реализацию FreaAI от IBM. Метод находит интерпретируемые срезы наших данных, где производительность модели черного ящика низкая. Затем они возвращаются инженеру для исправления.

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

  • Поддержка многоклассовой классификации
  • Поддержка регрессии
  • Расчеты статистической значимости (с коррекцией FDR) на срезах данных
  • Модульность в классы

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