Почему функциональное программирование?

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

Несколько дней спустя функциональное программирование позволило мне по-другому взглянуть на то, что и как я кодировал. В своей первой средней публикации я подумал, вместо того, чтобы создавать еще одно руководство по машинному обучению на Python, почему бы не попробовать другой язык, который использует другую парадигму программирования?

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

Начало работы с F # на VSCode

В Интернете доступно множество ресурсов, позволяющих сразу перейти к F #. Я лично рекомендую этот сайт:



Вы можете использовать F #, используя .Net Core или .Net Framework. Я лично люблю ПО с открытым исходным кодом и всегда стараюсь отдавать предпочтение программному обеспечению с открытым исходным кодом там, где это возможно. Использование .Net Core было несложной задачей, и у него было дополнительное преимущество, заключающееся в полной совместимости с моим любимым редактором: VS Code (еще один инструмент с открытым исходным кодом).

Существует некоторая путаница между различными фреймворками .Net, но в идеале, если вы просто установите .Net Core, этого должно быть достаточно; по крайней мере, для этого урока. Вот хорошее руководство по настройке других инструментов (вы, вероятно, можете проигнорировать инструкции для .Net Framework / Mono):



Поиск библиотеки чертежей

Построение графиков с использованием F # было одним из аспектов всей программы, который меня очень беспокоил. Визуализация намного превосходит простой текстовый вывод, но графический интерфейс никогда не был главным преимуществом F #. Я пробовал несколько популярных библиотек, но ни одна из них не нацелена на платформу .Net Core. Прежде чем практически отказаться от всего проекта, я наткнулся на своего спасителя: PLplot. Хотя их основной веб-сайт не работает, я смог извлечь из их образцов ровно столько кода, чтобы создать простые 2D-графики.

Настройка модели линейной регрессии

Учитывая, что это был F #, а не Python, с которым я имел дело, и тот факт, что я не планировал широко использовать библиотеки, я решил, что все будет просто и понятно. Моей целью было обучить простую однослойную нейронную сеть с помощью проверенного алгоритма обратного распространения, используя простейшую функцию потерь, какую только можно придумать.

Вот простое квадратное уравнение с некоторым смещением случайного шума, которое сеть должна предсказать:

let yRealFn x = 0.4*(x**float(2)) + 0.3*x + 0.9 + Normal.Sample(0.0, 1.0)

Использование частичных функций

Вот в чем преимущество функционального программирования: вы можете создавать переменные как функции и переносить их как частичные определения, пока вы не будете готовы их оценить. Для определения функции гипотезы (h) модель линейной регрессии может предсказывать «линейные» части, то есть веса. Хотя я мог бы сделать скалярное произведение весов с возрастающей степенью x, я реализовал h как функцию от x, w1, w2 и b :

let hypFn (x: float) (w:Weight) = w.w1*(x**2.0) + w.w2*x + w.b

Обратите внимание, что "Вес" - это тип записи, который будет содержать значения для трех разных весов.

Как только мои основные функции были определены, я приступил к определению функции стоимости и ее производных. Для простоты я использовал функцию стоимости, которую мы все знаем и любим: MSE.

Сначала я определяю произвольную функцию стоимости (costFnWithPow ). Затем я подключаю это к функциям более высокого порядка, чтобы определить мою основную функцию стоимости (costFn) и ее частные производные по каждому из весов (getCostDerivatives), которые возвращаются в виде тройного кортежа. Для всех этих функций x также неизвестно, которое будет подставлено позже.

Поскольку мои истинные выходные значения должны оставаться постоянными на протяжении всего обучения (для отражения набора данных), я определяю таблицу поиска для y, которая содержит единственную временную оценку y учитывая x.

Реализация градиентного спуска

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

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

getDerivTuple возвращает список тройных кортежей, каждый из которых соответствует трем частным производным ошибок (по одной для каждого веса) для конкретной точки выборки данных xi. getCosts делает то же самое, что и выше, но для самой функции ошибок. Наконец, avgWError используется для усреднения значений производной ошибки по всему набору обучающих данных, затем это используется в updateW вместе со скоростью обучения для выполнения градиентного спуска.

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

Результаты

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

Вывод

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

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

Полный исходный код и инструкции по его прямому запуску доступны на Github:



Большое спасибо за прочтение всего поста! Если у вас есть предложения, исправления или другие отзывы, пожалуйста, оставьте комментарий. Если вам понравилась эта статья, пожалуйста, оставьте аплодисменты :)