Машинное обучение с реляционными данными
Эта статья вдохновлена конкурсом Kaggle https://www.kaggle.com/c/elo-merchant-category-recommendation. Хотя я не участвовал в конкурсе, я использовал данные для изучения другой проблемы, которая часто возникает при работе с реалистичными данными. Все алгоритмы машинного обучения отлично работают с табличными данными, но на самом деле многие данные являются реляционными. В этом наборе данных мы пытаемся сделать выводы о пользователях (идентифицированных по идентификатору карты) на основе истории транзакций.
В таблице транзакций перечислены транзакции, поэтому в ней содержится несколько строк для каждого пользователя. Посмотрим на гистограмму в логарифмической шкале.
Как видно из этого графика, у большинства пользователей более одной транзакции. В случае, если мы хотим классифицировать пользователей, у нас возникнет проблема наличия нескольких записей для каждого пользователя, тогда как алгоритмы машинного обучения ожидают табличные данные.
Это похоже на оператор SQL GROUP BY
, который помогает нам группировать данные на основе ключа (в нашем случае идентификатора карты). SQL также определяет множество агрегатных функций, полный список которых определяется символом и может быть найден, например, здесь. В Pandas агрегатные функции поддерживаются функцией agg.
Многие специалисты по машинному обучению определяют дополнительные функции для всех возможных агрегатных функций, таких как count
, sum
, mean
и т. Д. Проблема в том, что мы часто не знаем заранее, какие агрегатные функции имеют значение. Например, мы можем добавить общую сумму как функцию, но что, если на метку влияет общая сумма для определенной категории? В конечном итоге мы добавим множество дополнительных функций, большинство из которых будут бесполезны и, скорее всего, упустим то, что действительно важно.
Что, если бы вместо того, чтобы вручную создавать совокупные функции, у нас был бы алгоритм для их изучения. Хотя этот подход изучался в прошлом (см., Например, Каскадно-корреляционный подход), когда нет корреляции между транзакциями, мы можем использовать простую нейронную сеть, которая будет изучать агрегированные функции с помощью некоторых стандартных функций TensorFlow.
Архитектура нейронной сети в нашем случае очень проста. У нас есть один полностью связанный слой, за которым следует функция segment_sum
, которая фактически группирует по операции и суммирует выходные данные первого уровня. Второй полностью связанный уровень подключен к выходу, который в нашем случае является агрегированной функцией, которую мы пытаемся изучить. Для простоты в этом POC мы использовали линейные единицы, но для более сложных ситуаций мы должны ввести нелинейность.
Количество единиц на первом уровне дает нам количество агрегированных функций, которые он может изучать одновременно.
Давайте рассмотрим два примера: обучение счетчику и суммированию.
Если мы установим все веса в первом слое равными нулю и установим все смещения равными 1, тогда (при условии линейной единицы) совокупная сумма выходных данных даст нам количество транзакций для каждого пользователя.
Если, однако, мы установим смещение и все веса равными нулю, но установим вес purchase_amount
равным 1, мы получим общую сумму покупки для каждого пользователя. Продемонстрируем наши идеи в TensorFlow.
Функция segment_sum работает следующим образом:
Он принимает отдельный тензор с идентификаторами сегментов, и данные должны быть помечены одинаковыми идентификаторами сегментов. Он группирует данные по идентификаторам сегментов и уменьшает сумму по нулевому измерению.
Cost after epoch 0: 187.700562 Cost after epoch 100: 0.741461 Cost after epoch 200: 0.234625 Cost after epoch 300: 0.346947 Cost after epoch 400: 0.082935 Cost after epoch 500: 0.197804 Cost after epoch 600: 0.059093 Cost after epoch 700: 0.057192 Cost after epoch 800: 0.036180 Cost after epoch 900: 0.037890 Cost after epoch 1000: 0.048509 Cost after epoch 1100: 0.034636 Cost after epoch 1200: 0.023873 Cost after epoch 1300: 0.052844 Cost after epoch 1400: 0.024490 Cost after epoch 1500: 0.021363 Cost after epoch 1600: 0.018440 Cost after epoch 1700: 0.016469 Cost after epoch 1800: 0.018164 Cost after epoch 1900: 0.016391 Cost after epoch 2000: 0.011880
Здесь мы построили функцию стоимости после каждой итерации. Мы видим, что алгоритм обучается функции счета довольно быстро. Настроив гиперпараметры оптимизатора Adam, мы можем попытаться добиться еще большей точности.
Cost after epoch 0: 8.718903 Cost after epoch 100: 0.052751 Cost after epoch 200: 0.097307 Cost after epoch 300: 0.206612 Cost after epoch 400: 0.060864 Cost after epoch 500: 0.209325 Cost after epoch 600: 0.458591 Cost after epoch 700: 0.807105 Cost after epoch 800: 0.133156 Cost after epoch 900: 0.026491 Cost after epoch 1000: 3.841630 Cost after epoch 1100: 0.423557 Cost after epoch 1200: 0.209481 Cost after epoch 1300: 0.054792 Cost after epoch 1400: 0.031808 Cost after epoch 1500: 0.053614 Cost after epoch 1600: 0.024091 Cost after epoch 1700: 0.111102 Cost after epoch 1800: 0.026337 Cost after epoch 1900: 0.024871 Cost after epoch 2000: 0.155583
Мы видим, что стоимость также снижается, но есть всплески стоимости, которые можно объяснить высокими градиентами, когда мы вводим новые данные в алгоритм. Возможно, мы сможем настроить гиперпараметры, чтобы улучшить сходимость процедуры обучения.
Заключение и дальнейшие шаги
Мы продемонстрировали простую нейронную сеть, которая может изучать основные агрегатные функции. Хотя в нашей демонстрации использовались линейные единицы, в действительности мы должны использовать нелинейные единицы для уровня 1, чтобы можно было изучать более сложные агрегатные функции. Например, если мы хотим узнать общую сумму для category2 = 5
, то линейные единицы не будут работать. Но если мы используем, например, сигмовидную функцию, то мы можем установить смещение на -100, затем установить вес для category2 = 5
на +100 и установить вес для purchase_amount
на небольшое положительное значение ω ω. На втором уровне мы можем установить смещение равным нулю, а вес - 1 ω 1ω.
Эта архитектура не изучает функцию mean
. Но он изучает оба своих компонента: sum
и count
. Если наша граница принятия решения зависит от средних продаж, это то же самое, как если бы она зависела от количества транзакций и общей суммы.
Эта архитектура также не будет изучать более сложные функции, такие как дисперсия и стандартное отклонение. Это может быть важно в финансовом секторе, где вы можете принимать решение, основываясь на волатильности рынка. Для реализации этого может потребоваться дополнительный слой перед агрегацией.
Наконец, в этом примере обучение было медленным, потому что нам нужно было представить данные в агрегированной форме. Увеличить скорость можно путем предварительного агрегирования данных и их повторной выборки.
Весь код, использованный в этой статье, можно найти в моем репозитории на github.