Evalml: One Hot Encoder: удаление одной избыточной функции по умолчанию для функций с двумя категориями.

Созданный на 5 мар. 2021  ·  14Комментарии  ·  Источник: alteryx/evalml

Наш один горячий кодировщик создает функцию для каждого уровня исходной категориальной функции:

from evalml.pipelines import OneHotEncoder
import pandas as pd
df = pd.DataFrame({"category": ["a", "b"], "number": [4,5 ]})
OneHotEncoder().fit_transform(df).to_dataframe()

image

Столбцы category_a и category_b полностью коллинеарны, что делает один из них излишним. Это может иметь неблагоприятные последствия для подгонки оценщика. Я думаю, мы должны отказаться от одного по умолчанию.

К вашему сведению @rpeck

enhancement

Самый полезный комментарий

Третий закон кодекса: не делайте == сравнений с поплавками

Все 14 Комментарий

💯% мы должны убрать столбец с отрицательным регистром.

Если мы сначала сделаем OHE сами, то, надеюсь, sklearn не будет их расширять. Как сказал Фредди, вы можете думать об этом как о создании двух столбцов с идеальной коллинеарностью.

Есть две проблемы, которые я вижу при расширении двоичного файла в два столбца, а не в один:

  1. Как и другие формы коллинеарности признаков, он многое портит в плане интерпретируемости, потому что эффект от одного исходного исходного столбца распределяется между двумя столбцами OHE. Новые накопительные пакеты SHAP от Freddy, очевидно, решают эту проблему, но такие вещи, как графики важности функций и частичной зависимости, по-прежнему будут иметь проблему.
  2. Модели деревьев, такие как Random Forest и GBM, случайным образом выбирают свои входные функции. Исходный столбец в этом случае будет случайным образом выбираться в два раза чаще, чем это должно быть на самом деле, поэтому это может оказать чрезмерное влияние на модель.

@freddyaboulton Q:

@rpeck Да!

@freddyaboulton Что за ? Это странно. Я никогда не видел ничего, кроме настоящих логических значений или целых чисел 0/1. Интересно, как на самом деле модели дерева справляются с этим. Он имеет неприятный запах для меня.

Третий закон кодекса: не делайте == сравнений с поплавками

(хорошо, если это не с Math.NaN )

Хм, я думал, что мы делаем это!

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

@dsherry @freddyaboulton Похоже, у нас есть поддержка для этого с помощью нашего параметра drop но он принимает во внимание только ввод данных пользователем и не используется нашей имплементацией, поэтому эта проблема просто отслеживает установку значения по умолчанию для drop к чему-то другому, кроме None?

https://github.com/alteryx/evalml/blob/91775ffc26c47205adc0fb255832d828ead6e7c9/evalml/pipelines/components/transformers/encoders/onehot_encoder.py#L28

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html

Мы могли бы выбрать first или if_binary , но не уверены, что это правильно.

@ angela97lin Вы правы, достаточно изменить значение по умолчанию! Я думаю, что first — это правильный путь, поскольку мы должны избегать идеально коллинеарных функций, даже когда количество категорий> 2. Что вы думаете о @rpeck ?

Немного читал об этом и нашел эту ссылку: https://inmachineswetrust.com/posts/drop-first-columns/

Основные выводы:

  • Удаление столбцов требуется только при создании модели OLS без регуляризации (я считаю, что линейный регрессор попадает в эту категорию: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html, https:// scikit-learn.org/stable/modules/linear_model.html#ordinary-least-squares)
  • Удаление столбцов с горячим кодированием изменяет параметры и прогнозы модели линейной регрессии, влияя на возвращаемую модель. Однако мне трудно определить, к лучшему это или нет.

Первый комментарий RE @rpeck : «Как и другие формы коллинеарности функций, это

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

Возможно, мы не должны делать это по умолчанию, но должны обновить make_pipeline чтобы создать OHE с first в качестве параметра, если оценщик является линейным регрессором?

Увы, я не слишком разбираюсь в базовой математике, чтобы судить об этом, поэтому я хотел бы услышать ваше мнение , @rpeck @dsherry

Пост-дискуссия с @freddyaboulton @rpeck @dsherry @chukarsten @jeremyliweishih

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

@angela97lin звучит как хорошее поведение RE по умолчанию. Еще один приятный момент: возможность переопределить это поведение по умолчанию с помощью параметров компонента.

@dsherry Если я правильно понимаю, поскольку мы обновляем значение по умолчанию drop (параметр), пользователи смогут переопределить это, установив параметр компонента вручную?

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

Результат копания таков:

  • С помощью scikit-learn довольно сложно выбрать, какую категорию удалить. Судя по документации, это кажется возможным с помощью параметра массива для параметра drop (https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html). Однако после опробования требуется, чтобы для каждого столбца было указано значение индекса. Следовательно, следующее, которое пытается удалить категорию, указанную в индексе 0 для столбца 0, и никаких других значений для столбцов 1 и 2 ошибок:
import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder

X = pd.DataFrame({'col_1': ["a", "b", "b", "a", "b"],
                      'col_2': ["a", "b", "a", "c", "b"],
                      'col_3': ["a", "a", "a", "a", "a"]})

indices_to_drop = np.array([0, None, None])

ohe = OneHotEncoder(drop=indices_to_drop)
ohe.fit(X)
ValueError                                Traceback (most recent call last)
<ipython-input-4-a099fa2fc4a7> in <module>
----> 1 ohe.fit(X)

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in fit(self, X, y)
    417         self._fit(X, handle_unknown=self.handle_unknown,
    418                   force_all_finite='allow-nan')
--> 419         self.drop_idx_ = self._compute_drop_idx()
    420         return self
    421 

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in _compute_drop_idx(self)
    394                                 ["Category: {}, Feature: {}".format(c, v)
    395                                     for c, v in missing_drops])))
--> 396                 raise ValueError(msg)
    397             return np.array(drop_indices, dtype=object)
    398 

ValueError: The following categories were supposed to be dropped, but were not found in the training data.
Category: 0, Feature: 0
Category: 1, Feature: None
Category: 2, Feature: None

Я считаю, что это также половина того, на что указывает эта проблема: https://github.com/scikit-learn/scikit-learn/issues/16511.

Альтернативой, которую мы можем сделать, чтобы поддержать это, является ручное отслеживание того, какие столбцы и какие значения мы хотим отбросить во время подбора. Передайте данные в scikit-learn. Затем удалите столбцы, которые мы сохранили и указали, что хотим удалить. Однако для этого требуется некоторая логическая обработка для определения оригинала (признака, значения) из имени преобразованного столбца. (У нас есть эта логика в get_feature_names но это помогает нам соединить имена столбцов, предполагая, что ничего не нужно отбрасывать...)

Все это говорит о том, что, возможно, на данный момент будет достаточно просто использовать scikit-learn if_binary по умолчанию, и мы можем зарегистрировать отдельную проблему, чтобы всегда использовать класс меньшинства. Честно говоря, также за то, чтобы отказаться от реализации OHE в scikit-learn, учитывая, сколько нам пришлось обойти это.

Полезные ресурсы:
Документ OHE: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html
Код в scikit-learn, вызывающий негибкость: https://github.com/scikit-learn/scikit-learn/blob/95119c13af77c76e150b753485c662b7c52a41a2/sklearn/preprocessing/_encoders.py#L338
Связанная проблема: https://github.com/scikit-learn/scikit-learn/issues/16511


Для использования if_binary : scikit-learn требует, чтобы handle_unknown равнялось error . Это не очень хорошо сочетается с нашими параметрами top_n , которые отбрасывают все, кроме первых N категорий, поскольку преобразуемые данные не будут знать, что делать с новыми категориями. Как отметила Бекка в https://github.com/alteryx/evalml/pull/830 , нам нужно установить для top_n значение None, чтобы эти параметры работали.

Имея это в виду, может быть, лучше просто свернуть наш собственный импл 🤔

Была ли эта страница полезной?
0 / 5 - 0 рейтинги