Многофункциональный One-Hot-кодировщик с переменным количеством экземпляров признаков

Вопрос или проблема

Предположим, у нас есть данные в таких экземплярах:

[
    [15, 20, ("банан","яблоко","огурец"), ...],
    [91, 12, ("апельсин","банан"), ...],
    ...
]

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

Вот что я попробовал:

X = [[15, 20, ("банан","яблоко","огурец")], [91, 12, ("апельсин","банан")]]

ct = ColumnTransformer(
    [
        ("genre_encoder", OneHotEncoder(), [2])
    ],
    remainder="passthrough"
)
print(ct.fit_transform(X))

Это выведет только

[[1.0 0.0 15 20]
 [0.0 1.0 91 12]]

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

Мы не можем встроить наши признаки напрямую (например, [15, 12, "банан", "яблоко", "огурец"]), потому что

  1. мы не знаем, сколько экземпляров этого признака у нас будет (два? три?)
  2. каждая позиция будет интерпретироваться как собственный признак, и таким образом, если у нас есть банан в первом номинальном слоте в одной точке данных и во втором во втором номинальном слоте, они не будут относиться к одному и тому же “пулу значений”, который может содержать признак

Пример:

X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
enc = OneHotEncoder()
print(enc.fit_transform(X).toarray())

[[1. 0. 1. 0. 1.]
 [0. 1. 0. 1. 1.]]

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

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

Есть идеи, как закодировать эти “Мульти-Мульти-“признаки, которые могут иметь несколько значений и состоять из переменного количества элементов? Заранее спасибо!

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

from sklearn.feature_extraction.text import CountVectorizer

X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
# Создаем документы
X_ = [' '.join(x) for x in X]
enc = CountVectorizer()
print(enc.fit_transform(X_).toarray())

Возвращает

[[1 1 1 0]
 [0 1 1 1]]

что имеет 4 разных значения, как вы и ожидали.

Я верю, что ответ @David Masip можно еще больше улучшить, предоставив пользовательский анализатор для CountVectorizer.

Объединение всех признаков в одну строку имеет некоторые проблемы:

  • Не будет работать хорошо с признаками, содержащими пробелы
  • Не будет работать хорошо с признаками, содержащими акценты, стоп-слова и т. д. из-за токенизации CountVectorizer

К счастью, CountVectorizer теперь предлагает способ предоставить пользовательский токенизатор через аргумент analyzer. Так что мы могли бы продолжать передавать списки значений в качестве входных данных и использовать пользовательский анализатор, чтобы пропустить эти списки без токенизации:

from sklearn.feature_extraction.text import CountVectorizer

X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
enc = CountVectorizer(analyzer=lambda lst: lst)
print(enc.fit_transform(X).toarray())

Это дает тот же результат

[[1 1 1 0]
 [0 1 1 1]]

избегая вышеупомянутых проблем и также экономя ресурсы ЦП/памяти, не объединяя и не повторно токенизируя признаки.

Ответ или решение

Многофункциональный One-Hot-Encoder с переменным числом значений признаков

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

Исходные данные

Рассмотрим массив данных, где каждый экземпляр состоит из различных признаков, в том числе категориальных:

X = [
    [15, 20, ("banana", "apple", "cucumber")],
    [91, 12, ("orange", "banana")]
]

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

Исходная проблема

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

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder

X = [[15, 20, ("banana", "apple", "cucumber")], [91, 12, ("orange", "banana")]]

ct = ColumnTransformer(
    [("genre_encoder", OneHotEncoder(), [2])],
    remainder="passthrough"
)
print(ct.fit_transform(X))

Вывод будет:

[[1.0 0.0 15 20]
 [0.0 1.0 91 12]]

К сожалению, OneHotEncoder воспринимает кортежи как множество отдельных признаков, что делает невозможным объединение значений в единую кодировку.

Решение с использованием CountVectorizer

Одним из элегантных решений может быть использование CountVectorizer, который способен справляться с текстовой информацией. Мы создали «документы», чтобы обработать списки значений, представленных для каждого экземпляра:

from sklearn.feature_extraction.text import CountVectorizer

set_of_values = [
    ["banana", "apple", "cucumber"],
    ["orange", "banana", "cucumber"]
]

# Объединяем значения в строку
X_ = [' '.join(values) for values in set_of_values]

enc = CountVectorizer()
print(enc.fit_transform(X_).toarray())

Результат будет следующим:

[[1 1 1 0]
 [0 1 1 1]]

Таким образом, CountVectorizer создает кодировку, корректно определяя общее множество значений без разделения по позициям.

Улучшение с использованием кастомного токенизатора

Поскольку использование CountVectorizer с объединением значений может привести к нежелательным эффектам, связанным с токенизацией (например, некорректная обработка пробелов или акцентов), мы можем сделать шаг дальше и задать собственный токенизатор:

from sklearn.feature_extraction.text import CountVectorizer

set_of_values = [
    ["banana", "apple", "cucumber"],
    ["orange", "banana", "cucumber"]
]

enc = CountVectorizer(analyzer=lambda lst: lst)
print(enc.fit_transform(set_of_values).toarray())

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

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...