Машинное обучение с реляционными данными

Эта статья вдохновлена ​​конкурсом 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.