Как эффективно реализовать общую нейронную сеть в Haskell?

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

Передача информации: Если вы создаете график зависимостей каждого нейрона или слоя, вы получаете что-то вроде этого

enter image description here

где x - вход, а f - вывод. Хотя на графике он выглядит довольно прямолинейно, если вы действительно рассматриваете сеть как функцию, то f должен передать вход (плюс подмножество весовых параметров) на g1 и g2, каждый из них должен передать их h1, h2 и h3, которые наконец отправят результат к слою выше. Принимая во внимание, что набор входов плюс вес набора может составлять тысячу или более переменных, это выглядит крайне неэффективным. На других языках вам не пришлось бы этого делать, так как каждый нейрон мог сохранить его параметры, и входы могли быть переданы непосредственно на входной слой.

States: Если вы посмотрите на график, то как g1, так и g2 будут отдельно звонить h2, но поскольку h2 должен давать одинаковое значение для обоих, нет смысла вычислять его вывод дважды. Поскольку у вас нет состояния или побочных эффектов, это становится сложным, и если у вас действительно огромная сеть, то даже при некоторых параллельных вычислениях это будет тратить много времени.

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

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

Примечание:

Я не совсем новичок в языке. Я использовал большое количество функторов и монодатов (в основном в моей библиотеке FP для С# на основе API haskells), но я никогда не использовал haskell для реального приложения раньше.

Обновление

Монада State кажется очень перспективной!

Ответ 1

Я имею в виду, так как Haskell имеет взаимную рекурсию, возможно, самый простой способ написать построенный граф - в отношении большого взаимно-рекурсивного where-clause:

run_graph x = run_f g1 g2 where
    g1 = run_g1 h1 h2
    g2 = run_g2 h2 h3
    h1 = run_h1 x
    h2 = run_h2 x
    h3 = run_h3 x

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

Возможно, вам не придется беспокоиться о преждевременной оптимизации графика для оценки: Haskell автоматически кэширует ответы на функции на своих входах; это возможно, потому что в Haskell функции не должны иметь побочных эффектов. Существует хорошая вероятность, что, когда ваши функции run_* создают абстрактные узлы в структуре данных, eval_g1 h2 h3 закончит использование кешированного значения из eval_h2 x, которое появится, когда вы вернетесь к h2. Если это становится слишком утомительным, вы можете, вероятно, переключиться на первую прогулку узлов, несущих состояние IntMap, прежде чем вам придется явно указывать слои и распространять Data.Array.Unboxed через слои графика.