5-минутное руководство по стилю Pandas One Hot Encoding с использованием Spark. Создайте чистую, интерпретируемую человеком схему горячего кодирования, доступную для записи в файлы любого типа, включая CSV.

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

Проблема

Как и любой другой шаг в реальном конвейере предварительной обработки функций, процесс горячего кодирования проходит тщательную проверку, чтобы убедиться, что результаты соответствуют ожиданиям. Во многих отраслях необходимо, чтобы представление с одним горячим кодированием проходило проверку на соответствие, чтобы обеспечить достоверность данных, которые используются для обучения модели машинного обучения. Это довольно распространено в финансовом и медицинском секторах. Наиболее распространенный способ, которым люди, отвечающие за соблюдение требований, любят свои данные, — это файл в формате, который они могут открыть и просмотреть (иногда перед тем, как запустить на них свои коды проверки). Таким образом, возможность легко представлять закодированные столбцы в формате, переносимом в разных средах и легкодоступном с помощью различных инструментов, является обязательной. Формат файла CSV (значения, разделенные запятыми) является одним из наиболее широко используемых форматов для борьбы с этим. Они легко открываются в MS Excel, а также довольно легко обрабатываются и используются большинством (если не всеми) сегодняшними языками программирования.

Еще одна вещь, которую следует учитывать, — это полезность Spark. Он отлично справляется с петабайтами данных. Но когда дело доходит до сложных методов моделирования, поддержка ML в Spark имеет много световых лет, чтобы догнать чистый Python или сообщество поддержки другого подобного языка. Вот почему во многих случаях предварительная обработка данных выполняется в Spark, а последующее моделирование реализуется на чистом Python или других продвинутых языках, таких как Julia. Таким образом, вывод формата данных должен хорошо работать с чистым Python и тому подобным. Новости! Все эти языки любят CSV.

Давайте испачкаем руки

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

from pyspark.sql import SparkSession 
spark = SparkSession.builder \    
          .appName("One_Hot_Encoding_Blog") \    
          .getOrCreate() 
df = spark.read \    
      .option("header", True) \    
      .csv("ohe_explanation.csv") 
df.show()

Приведенный выше код дает следующий результат:

+------+
| Color|
+------+
|   Red|
|   Red|
|Yellow|
| Green|
|Yellow|
+------+

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

Общая реализация PySpark One-Hot-Encoding

PySpark имеет довольно простую реализацию для горячего кодирования. Это происходит следующим образом:

  1. Преобразование строковых значений в числовые метки/индексы
  2. Однократное горячее кодирование числовых меток в VectorUDT (pyspark.ml.linalg.VectorUDT)
#   ##  import the required libraries
from pyspark.ml.feature import StringIndexer
from pyspark.ml.feature import OneHotEncoder

#   ##  numeric indexing for the strings (indexing starts from 0)
indexer = StringIndexer(inputCol="Color", outputCol="ColorNumericIndex")

#   ##  fit the indexer model and use it to transform the strings into numeric indices
df = indexer.fit(df).transform(df)

#   ##  one-hot-encoding the numeric indices
ohe = OneHotEncoder(inputCol="ColorNumericIndex", outputCol="ColorOHEVector")

#   ##  fit the ohe model and use it to transform the numeric indices into ohe vectors
df = ohe.fit(df).transform(df)

df.show()
#   ##  get datatype of the ohe vector column
print(df.schema["ColorOHEVector"].dataType)

Приведенный выше код дает следующий результат:

+------+-----------------+--------------+
| Color|ColorNumericIndex|ColorOHEVector|
+------+-----------------+--------------+
|   Red|              0.0| (2,[0],[1.0])|
|   Red|              0.0| (2,[0],[1.0])|
|Yellow|              1.0| (2,[1],[1.0])|
| Green|              2.0|     (2,[],[])|
|Yellow|              1.0| (2,[1],[1.0])|
+------+-----------------+--------------+
VectorUDT

Это отлично подходит для случаев, когда моделирование ниже по течению выполняется в Spark. PySpark прекрасно справляется с переносом векторов, закодированных горячим способом, в Dense Vector (VectorUDT), который отлично работает в мире Spark, но не позволяет записывать векторы в формате csv. Кроме того, вектор непонятен по-человечески (особенно для больших столбцов кардинальности).

Код для записи результата в csv

#   ##  try writing out the ohe result to csv
df\
    .write.option("header", True)\
    .csv("ohe_result.csv")

Выдает следующий результат:

pyspark.sql.utils.AnalysisException: CSV data source does not support struct<type:tinyint,size:int,indices:array<int>,values:array<double>> data type.

Интерпретируемое и записываемое CSV One Hot Encoding в PySpark

Вот и волшебное решение. Чтобы создать интерпретируемый One Hot Encoder, нам нужно создать отдельный столбец для каждого отдельного значения. Это легко сделать с помощью встроеннойwithColumn функции pyspark dataframe, передав UDF (определяемую пользователем функцию) в качестве параметра.

#   ##  import the required libraries
from pyspark.sql.functions import udf, col
from pyspark.sql.types import IntegerType

Теперь вот что нам нужно сделать:

  1. Соберите все отдельные значения в столбце, который необходимо закодировать одним горячим способом.
  2. Для каждого из собранных значений создайте новый столбец с именем столбца в формате ‹‹исходное имя столбца››_ ‹‹различное значение››, представляющее наличие (1) или отсутствие (0) уникальное значение в записи
#   ##  gather the distinct values
distinct_values = list(df.select("Color")
                       .distinct()
                       .toPandas()["Color"])

Приведенный выше фрагмент кода требует, чтобы панды 0.23.2 или выше были установлены в среде, где выполняется код. В противном случае код выдаст следующую ошибку:

ImportError: Pandas >= 0.23.2 must be installed; however, it was not found.

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

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

#   ##  gather the distinct values
distinct_values = df.select("Color")\
                    .distinct()\
                    .rdd\
                    .flatMap(lambda x: x).collect()

Теперь, чтобы создать столбцы One Hot Encoded, мы делаем следующее:

#   ##  for each of the gathered values create a new column 
for distinct_value in distinct_values:
    function = udf(lambda item: 
                   1 if item == distinct_value else 0, 
                   IntegerType())
    new_column_name = "Color"+'_'+distinct_value
    df = df.withColumn(new_column_name, function(col("Color")))
df.show()

Приведенный выше код дает следующий результат:

+------+-----------+------------+---------+
| Color|Color_Green|Color_Yellow|Color_Red|
+------+-----------+------------+---------+
|   Red|          0|           0|        1|
|   Red|          0|           0|        1|
|Yellow|          0|           1|        0|
| Green|          1|           0|        0|
|Yellow|          0|           1|        0|
+------+-----------+------------+---------+

Теперь этот кадр данных можно успешно записать в CSV-файл, используя код, описанный в предыдущем разделе.

Это представление очень полезно для интерпретации процесса One Hot Encoding и может быть легко использовано для выборочных проверок или для других требований соответствия. Этот вывод очень похож на то, как pandas выводит свои значения One Hot Encoded, используя собственную функцию pandas get_dummies.

Понравилось путешествие? Пожалуйста, нажмите на значок 👏 (хлопать в ладоши) ниже и порекомендуйте эту историю. Целый мир для меня!