2016-07-07 5 views
4

Играя с 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 

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

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

+1

Кстати, есть книга _F # для ученых, хотя и немного устарела. И есть выдержки из [Real World Functional Programming] (https://code.msdn.microsoft.com/Chapter-4-Numerical-3df3edee). Возможно, более свежие книги имеют лучшее объяснение математики. – s952163

ответ

5

Сначала я не отделил вызов от 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-й элемент.

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 
+0

Да! Я ждал somethig со сгибом :) – s952163

2

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

Я хотел бы написать код так:

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

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

Я бы также исследовал, можно ли сгенерировать образцы напрямую с помощью экспоненциального. Вместо того, чтобы называть его 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) 

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

+1

Самый быстрый способ, который распараллелен внутри, - создать массив, а затем передать его в статический 'Exponential.Samples (array, 0.1)', чтобы заполнить его. –

+0

@ ChristophRüegg интересно. благодаря! – s952163

6

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

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

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

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

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

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

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

+2

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

 Смежные вопросы

  • Нет связанных вопросов^_^