В этой статье я покажу вам, насколько легко выполнить проектирование функций с помощью Snowflake, используя недавно выпущенный Snowpark: API DataFrame, который позволяет инженерам данных, специалистам по данным и разработчикам использовать предпочитаемый ими язык и знакомые концепции программ, а затем выполнять эти рабочие нагрузки прямо в Snowflake. Snowpark, который в настоящее время находится в стадии предварительной версии, изначально поддерживает Scala / Java и скоро также будет поддерживать Python.

Введение в разработку функций

Разработка функций - это процесс использования знаний предметной области для извлечения функций (характеристик, свойств, атрибутов) из необработанных данных. Проще говоря, мы хотим создать новые функции (столбцы) для нашего набора данных, используя данные, которые у нас уже есть. Можно также возразить, что мы могли бы создавать новые функции, добавляя дополнительные данные, объединяя существующие наборы данных или, например, используя Snowflake Data Marketplace, и это тоже верно и может стать темой для будущей статьи.

Функции, которые мы можем создать на основе существующих данных, очень зависят от данных и домена, в котором мы находимся.

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

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

Проблемы с существующими функциями

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

Мои данные имеют три типа функций; идентификаторы, отметка времени и сумма.

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

Дата и время - еще один пример функций, которые не работают с большинством алгоритмов машинного обучения. Это связано с тем, что само значение не имеет большого значения без знания предметной области, например, 2019–04–01 и 2019–04–08 - это всего лишь два разных значения. Тем не менее, и вы, и я, вероятно, можем видеть, что они относятся к 2019 году и апрелю, и, если использовать календарь, мы также можем увидеть, что они оба понедельника. Это потому, что мы знаем, что это даты, и знаем, как их читать, а алгоритм машинного обучения не имеет этих знаний, поэтому нам нужно предоставить эту информацию другим способом.

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

Решение

Чтобы предоставить информацию о функциях даты и времени таким образом, чтобы алгоритм машинного обучения мог понять, мне нужно преобразовать ее во что-то, что может быть им использовано. Поэтому, если я считаю важным знать, в какой день недели что-то происходит, я могу извлечь день недели из даты и сохранить его в новой функции. Другие примеры - день месяца, номер месяца, час дня и так далее. Поскольку все эти функции имеют очень мало значений (день недели - семь, а час - двадцать четыре), алгоритму становится проще найти закономерность.

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

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

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

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

Создание новых функций с Snowpark

Начало работы со Snowpark

Прежде чем я смогу начать, мне нужно настроить Snowpark в среде разработки, и в зависимости от того, использую ли я Jupyter Notebook, IntelliJ, Visual Studio Code или любую другую IDE, поддерживающую Scala, необходимы различные шаги, и это задокументировано в Snowpark Руководство разработчика .

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

Следующим шагом является определение DataFrame Snowpark, dfCustTrxFraud,, который представляет нашу таблицу транзакций. DataFrame представляет собой набор реляционных данных, который оценивается лениво, что означает, что он будет выполняться только при запуске определенного действия. Вы можете определить DataFrame на основе существующей таблицы / представления, оператора SQL или использования внешнего файла, в документации есть примеры различных способов определения dataframe. Используя функцию show () для DataFrame , я прошу вернуть 10 строк и отобразить их в клиенте, и это вызовет выполнение DataFrame в Snowflake. .

Создание функции на основе даты и времени

Я начну с создания двух функций, основанных на дате и времени. Для этого мне нужно извлечь день недели и час, когда была сделана транзакция. Snowflake имеет множество функций даты и времени, и большинство из них представлены в Snowpark API (позже я расскажу о том, как использовать эти функции, которые не отображаются). Чтобы получить день недели, я использую метод dayofweek, а для получения часа я использую метод час.

Используя функцию withColumns, я могу определить сразу несколько столбцов, используя две последовательности, одну для имен столбцов, а другую для определений столбцов.

Наконец, я использую функцию iff, которая эквивалентна выражению if-then-else, чтобы установить значение 1, если это выходные или ночью, и 0, если это будний день или в дневное время. Функция горит используется для установки 1 или 0.

Возврат - это новый объект DataFrame, который теперь имеет два новых столбца: TX_DURING_WEEKEND и TX_DURING_NIGHT.

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

Создание функций на основе поведения

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

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

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

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

Поскольку окно представляет собой либо фиксированное количество строк, либо диапазон строк, который имеет то же значение, что и текущая строка, мне нужно, чтобы мои данные были на том уровне, на котором я собираюсь основывать свои окна, которым в данном случае является день. Я также хочу, чтобы окна были полной последовательностью дней, например, предыдущие семь дней для 2019-04-08 должны включать 2019-04-07, 2019-04-06, 2019-04-05, 2019-04-04, 2019–04–03, 2019–04–02 и 2019–04–01, и если у клиента нет транзакций в определенный день, он будет равен нулю.

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

  1. Создайте фрейм данных с одной строкой для каждого клиента и дня, который находится между минимальной и максимальной датой в наших данных.
  2. Вычислите количество транзакций и сумму по клиенту и дню, добавив нули для тех дней, когда у клиента нет транзакций.
  3. Использование оконных функций Snowflake для расчета количества транзакций и средней суммы за предыдущие один, семь и тридцать дней, исключая текущий день.
  4. Присоединяйтесь к транзакциям, а также рассчитывайте транзакции и сумму в течение текущего дня.

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

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

Используя функции min и max в столбце TX_DATETIME в фрейме данных dfCustTrxFraud, я получу даты начала и окончания транзакций. и предоставив их в качестве входных данных для dateiff, я получу количество дней между ними. Метод select используется для выбора столбцов для возврата, а метод collect () выполняет SQL, сгенерированный этими операциями, и возвращает результат моему клиенту. как массив строк, который я сохраняю в переменной dateInfo.

Теперь я могу извлекать значения столбцов, обращаясь к ним напрямую, используя индекс строки и столбца, а также использовать функцию getter, чтобы также получить значения с правильным типом данных.

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

В Snowpark API есть метод под названием диапазон, который можно использовать для генерации фиксированного количества строк, в данном случае количество строк - это количество дней между минимальной и максимальной датой, которое мы сейчас в нашей локальной переменной nDays.

Функция диапазона будет возвращать строки, в которых по умолчанию есть только столбец идентификатора, и мне нужен столбец с датой, которую я собираюсь использовать для выполнения своих агрегатов. Функцию dateadd можно использовать для генерации дат, она добавит количество единиц к указанной части даты, и я хочу увеличить dStartDate на один день для каждый ряд. Используя функцию SEQ4, которая генерирует последовательность монотонно возрастающих целых чисел, начиная с нуля, я могу сделать именно это, поскольку количество единиц для увеличения даты.

SEQ4 не отображается в Snowpark API, что не является проблемой, поскольку Snowpark имеет функции для вызова встроенных функций Snowflake, которые не отображаются. callBuiltin - это функция, которая позволяет мне вызывать функцию в Snowflake, которая не отображается в Snowpark API.

Поскольку dateadd возвращает метку времени, я также использую to_date, чтобы вернуть часть даты. Конечный результат показан на картинке ниже.

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

Выполняя перекрестное соединение между фреймами данных dfCustomers и dfDays, я получаю новый фрейм данных, dfCustDay, с одной строкой для каждого идентификатора клиента и даты, как показано ниже.

Ниже приведен код для выполнения этих шагов.

Объединив фрейм данных dfCustDay с фреймом данных dfCustTrxFraud, я могу суммировать количество и сумму транзакций по клиентам и дням, чтобы определить новый фрейм данных, dfCustTrxByDay . Если у клиента нет транзакции, будет возвращено NULL, а с помощью функции ZEROIFNULL в Snowflake их можно заменить на ноль.

ZEROIFNULL также не предоставляется Snowpark API, и я мог бы использовать функцию callBuiltin для его вызова. Я также могу назначить вызов функции переменной с помощью функции builtin, а затем использовать эту переменную в коде, чтобы облегчить чтение.

Теперь я готов подсчитать количество транзакций и среднюю сумму за предыдущие, семь и 30 дней. Для этого теперь я могу использовать оконные функции Snowflakes, поскольку используемые для этого данные, dfCustTrxByDay,, находятся на дневном уровне.

Я начинаю с создания одной переменной с помощью объекта WindowSpec Snowpark API, который определяет подпункты partitionBy и orderBy. Подпункт partitionBy позволяет мне разделить окно на подокна. В этом случае я хочу произвести расчет по клиенту, поэтому я использую идентификатор клиента. Подпункт orderBy используется для указания того, как должно быть упорядочено каждое подокно, в данном случае я хочу упорядочить их по дате.

Для расчетов за предыдущий день я буду использовать функцию lag, так как мне нужны только значения предыдущей строки, которая также является предыдущим днем ​​для клиента. Для окон семи и тридцати дней я использую подпункт rowsBetween, чтобы указать, сколько строк и дней я хочу включить в окна.

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

Наконец, я присоединяюсь к фрейму данных dfCustFeatDay с фреймом данных dfDateTimeFeat, чтобы создать окончательный фрейм данных, dfCustBehaviurFeat со всеми его функциями.

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

На этот раз я хочу получить транзакции только до текущей транзакции в тот же день, что и текущая транзакция. Для этого я буду использовать rangeBetween, , который будет включать все строки с одинаковым значением для части partionBy. Поскольку я хочу включить все транзакции, относящиеся к одному дню и одному клиенту, мне нужно создать новый столбец PARTITION_KEY, в котором я объединяю идентификатор клиента и дату транзакции.

Как отмечено в коде, я имею в виду столбец PARTITION_KEY, не определив его, преимущество за счет ленивого выполнения, поскольку он должен существовать только при использовании объекта Window.

Я присоединяюсь к dfCustFeatDay с dfDateTimeFeat на основе даты и идентификатора клиента, и в результате каждая строка транзакции в dfDateTimeFeat будет иметь строку из dfCustFeatDay для та же дата, для которой предназначена транзакция. Часть withColumn предназначена для создания PARTION_KEY и withColumns для расчета количества транзакций и общей суммы по дням, для которых предназначена строка транзакции.

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

Теперь у меня есть фрейм данных, dfCustBehaviurFeat, со всеми моими новыми функциями, а также с начальными функциями. Теперь у него есть план выполнения / SQL, необходимый для выполнения всех шагов, которые я сделал в этой статье, и я могу просмотреть его с помощью метода объяснения.

Сохранение новых функций

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

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

Если бы я пропустил использование метода show, это был бы шаг, запускающий выполнение в Snowflake, часть шага, на котором я собирал даты и т. Д., И где я фактически использую любые вычисления.

Вывод

С помощью этого примера я надеюсь, что я показал вам, насколько легко создавать функции с помощью Snowflake, используя программный подход и Snowpark API.

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

Читать далее