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

Что такое несбалансированный набор данных

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

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

В этой статье показано, как увеличить или уменьшить выборку в PySpark Dataframe.

Пример фрейма данных PySpark

Давайте настроим простой пример PySpark:

# code block 1
from pyspark.sql.functions import col, explode, array, lit
df = spark.createDataFrame([['a',1],['b',1],['c',1],['d',1], ['e',1], ['f',1], ['x', 0], ['y', 0]], ['feature', 'label'])
df.show()
major_df = df.filter(col("label") == 1)
minor_df = df.filter(col("label") == 0)
ratio = int(major_df.count()/minor_df.count())
print("ratio: {}".format(ratio))

Выход:

+-------+-----+
|feature|label|
+-------+-----+
|      a|    1|
|      b|    1|
|      c|    1|
|      d|    1|
|      e|    1|
|      f|    1|
|      x|    0|
|      y|    0|
+-------+-----+
ratio: 3

class (label) 1 содержит 6 примеров, тогда как class 0 имеет только 2 примера. Мы можем занижать выборку класса 1 или передискретизировать класс 0. Отношение равно 3.

Передискретизация

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

... skipped from code block 1 ...
a = range(ratio)
# duplicate the minority rows
oversampled_df = minor_df.withColumn("dummy", explode(array([lit(x) for x in a]))).drop('dummy')
# combine both oversampled minority rows and previous majority rows combined_df = major_df.unionAll(oversampled_df)
combined_df.show()

Выход:

+-------+-----+
|feature|label|
+-------+-----+
|      a|    1|
|      b|    1|
|      c|    1|
|      d|    1|
|      e|    1|
|      f|    1|
|      x|    0|
|      x|    0|
|      x|    0|
|      y|    0|
|      y|    0|
|      y|    0|
+-------+-----+

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

Недостаточная выборка

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

... skip from code block 1 ...
sampled_majority_df = major_df.sample(False, 1/ratio)
combined_df_2 = sampled_majority_df.unionAll(minor_df)
combined_df_2.show()

Выход:

+-------+-----+
|feature|label|
+-------+-----+
|      a|    1|
|      b|    1|
|      x|    0|
|      y|    0|
+-------+-----+

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