Функционализация числового кода

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

Например, давайте возьмем фрагмент, который демонстрирует слабый закон больших чисел:

open System
open System.IO
open System.Windows.Forms
open System.Windows.Forms.DataVisualization
open FSharp.Data
open FSharp.Charting
open FSharp.Core.Operators
open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.Random
open MathNet.Numerics.Distributions
open MathNet.Numerics.Statistics


let T = 1000

let arr1 = Array.init T (fun i -> float i*0.)
for i in 0 .. T-1 do
    arr1.[i] <- [|for j in 1..i do yield Exponential.Sample(0.1)|] |> Statistics.Mean

let arr2 = Array.init T (fun i -> float i*0.)
for i in 0 .. T-1 do
    arr2.[i] <- arr1.[1 .. i] |> Statistics.Mean

arr2 |> Chart.Line |> Chart.Show

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

Не уверен, что вопрос не по теме. Благодарю.

 s95216307 июл. 2016 г., 10:52
Кстати естьF # для ученых книга, хотя и немного устаревшая. И есть выдержкиФункциональное программирование в реальном мире, Возможно, в более поздних книгах есть лучшее объяснение математики.

Ответы на вопрос(3)

Решение Вопроса

Я бы сначала не отделил звонокArray.init и установка начальных значений. Вы можете использовать форму, которую @ s952163 использует в своем ответе, или основываясь на вашем коде:

let arr1 = Array.init T (fun i ->
    [|for j in 1..i do yield Exponential.Sample 0.1 |] |> Statistics.Mean
)

Проблема в том, что вы выделяете промежуточные массивы, что является дорогостоящим, и вы в любом случае отбрасываете их сразу после вычисления среднего значения. Альтернатива:

let arr1 = Array.init T (fun i ->
    Exponential.Samples 0.1 |> Seq.take (i+1) |> Seq.average
)

Теперь для второй части: Вы повторно вычисляете среднее значение элементов 1..i, что становится операцией O (n ^ 2). Вы можете решить это в O (n), используя тот факт, что сумма элементов 1..i является суммой элементов 1 .. {i-1} плюс i.th элемент.

let sums, _ =
    arr1
    |> Array.mapFold (fun sumSoFar xi ->
        let s = sumSoFar + xi
        s, s
    ) 0.0
let arr2 = 
    sums
    |> Array.mapi (fun i sumi -> sumi / (float (i + 1)))

Конечно, вы все можете написать это в одной трубе.

В качестве альтернативы, используйте функцию библиотекиArray.scan вычислить кумулятивные суммы, которые в этом случае дали бы результат длиныT+1, из которого вы бы тогда отбросили первый элемент:

let arr2 =
    Array.sub (Array.scan (+) 0.0 arr1) 1 T
    |> Array.mapi (fun i sumi -> sumi / (float (i + 1)))

или избегайте промежуточных массивов:

Seq.scan (+) 0.0 arr1
|> Seq.skip 1
|> Seq.mapi (fun i sumi -> sumi / (float (i + 1)))
|> Seq.toArray
 s95216307 июл. 2016 г., 10:33
Да! Я ждал что-то со сгибом :)

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

Я бы написал ваш код так:

let arr1' = [|for i in 0..1000 -> Array.init i (fun i -> Exponential.Sample(0.1)) |> Statistics.Mean |]

Обратите внимание, что a) нет изменяемого присваивания, b) нет индексации и c) я не инициализирую массив на основе 0 и не заполняю его, а вместо этого инициализирую массив с помощью функции.

Я также хотел бы выяснить, возможно ли генерировать сэмплы напрямую с помощью Exponential.Sample, а не вызывать его 1000 раз.

редактировать

Как это:Exponential.Samples(0.1) |> Seq.take 1000

И на основе комментария @ ChristophRüegg ниже:

let expoMean (x:float []) =
    Exponential.Samples(x,0.1)
    x |> Statistics.Mean

Array.init 1000 (fun _ -> Array.replicate 1000 0. |> expoMean)

Я не тестировал это.

 Christoph Rüegg07 июл. 2016 г., 09:40
Самый быстрый способ, который распараллеливается внутри, состоит в том, чтобы создать массив и затем передать его статическомуExponential.Samples(array, 0.1) чтобы заполнить его.
 s95216307 июл. 2016 г., 09:45
@ ChristophRüegg интересно. Спасибо!

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

Это о производительности?

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

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

Если производительность является проблемойВы можете пойти на компромисс в неизменности. Основная стоимость функционального кода в F # исходит от сборщика мусора, особенно от объектов с промежуточным временем жизни. Создание дорогостоящих объектов, изменяемых и повторное их использование, может иметь огромное значение в скорости выполнения. Если вы хотите писать такие вещи, как гидродинамика, симуляции n-body или игры, кратким и безопасным способом, но не стремитесь к скорости выполнения педали до металла, стиль F # с несколькими парадигмами может быть хорошим способом идти.

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

Когда проблема представляет собой смесь из них, часто можно смешать решения. Например, вчера мне нужно было выполнить расчёт на пиксель для набора изображений, и время выполнения было важно. Поэтому я прочитал изображения в F # с помощью библиотеки .NET, затем загрузил их в GPU вместе с вычислительным шейдером GLSL, который преобразовал пиксели, а затем загрузил их обратно в «землю F #». Дело в том, что управленческие операции неэффективны; код все еще копирует вещи без какой-либо реальной причины. Но это была всего лишь одна операция, которая снизила бы всю производительность, поэтому разумно использовать высокопроизводительный инструмент для этой одной операции, тогда как все остальное происходит аккуратно и безопасно в F #.

 TheInnerLight07 июл. 2016 г., 11:59
Рад, что кто-то решил подробно рассмотреть эту часть вопроса. Я также добавил бы к этому, что вы можете идти разными путями, чтобы улучшить производительность. Императивный код всегда выигрывает, когда дело доходит до небольшого ускорения с постоянным коэффициентом, но часто проще и понятнее, как разработать алгоритм с лучшей алгоритмической сложностью, используя более абстрактный, функциональный код. По этой причине, я думаю, что вы часто лучше с проверенным императивным решением для уже решенных проблем, но может быть лучше обслуживаться более функциональным кодом для новой разработки.

Ваш ответ на вопрос