2016-11-21 25 views
5

F # içinde programlama filtrelerine deyimsel bir yaklaşım arıyorum. Açıklık sağlamak için, zaman içinde bir dizi ölçüm kullanan ve gelişen tahminler üreten bir işlev olarak bir filtreye başvuruyorum. Bu, fonksiyonun durumu koruyabileceğini ima eder. Örneğin, Python'da, durumu çok temiz bir şekilde tutmak için koroutinler kullanılabilir.Filtrelemeye yönelik deyimsel yaklaşım

Aradığım şey, F # içindeki programlama filtrelerine deyimsel bir yaklaşım. Aklımın OOP ve prosedür ilkeleri ile tamamen kirlendiği göz önüne alındığında, doğal olarak onları ifade etmek için derslerle geldim. F # içinde filtreleme yapmak için daha işlevsel bir yaklaşım var mı, belki de işlevsel paradigmanın diğer faydalarını açabilir mi? iletmenin veri ve kod veri ayırma: Senin durumunda

open System 
open MathNet.Numerics.LinearAlgebra 
open MathNet.Numerics.Random 
open MathNet.Numerics.Distributions 
open MathNet.Numerics.Statistics 
open FSharp.Charting 

type ScalarKalman (A : float, H : float, Q : float, R : float) = class 

    let mutable A = A 
    let mutable H = H 
    let mutable Q = Q 
    let mutable R = R 

    let mutable p = 0. 
    let mutable x = 0. 
    let mutable k = 0. 

    let mutable result = 0. 

    member this.X 
     with get() = x 
     and set(value) = x <- value 

    member this.P 
     with get() = p 
     and set(value) = p <- value 

    member this.K 
     with get() = k 
     and set(value) = k <- value 

    member this.update(newVal : float) = 
     let xp = A * this.X 
     let Pp = A * this.P * A + Q 
     this.K <- Pp * H/(H * Pp * H + R) 
     this.X <- xp + this.K * (newVal - H * xp) 
     this.P <- Pp - this.K * H * Pp 

end 

let n = 100 
let obsv = [|for i in 0 .. n do yield 0.|] 
let smv = [|for i in 0 .. n do yield 0.|] 
let kal = new ScalarKalman(1., 1., 0., 5.) 
kal.P <- 4. 
kal.X <- 6. 
for i in 0 .. n do 
    obsv.[i] <- Normal.Sample(10., 5.) 
    kal.update(obsv.[i]) 
    smv.[i] <- kal.X 

Chart.Combine([obsv |> Chart.FastLine 
       smv |> Chart.FastLine]) |> Chart.Show 

cevap

8

, "fonksiyonel" ve "F # deyimsel" iki şeyden oluşacak.

Bu tür veriler: Eğer filtre parametrelerini temsil eden bir veri yapısına sahip olabilecektir (yani A, H, Q ve R) ve filtre mevcut durumunu temsil eden başka bir yapı (yani X, K ve P). İkisi de değişmez. Durumu mutasyona sokmak yerine, yeni bir tane üreteceksin.

Verilerin koddan ayrılması: filtrenin kendisi parametreleri, mevcut durumu, sonraki gözlem değerini alan ve bir sonraki durumu üreten tek bir işlevden oluşacaktır. Bu sonraki durum daha sonra bir sonraki gözlem değeriyle birlikte fonksiyona geri beslenecek, böylece bir sonraki + 1 durumu üretilecek, vb. Parametreler her zaman sabit kalır, böylece kısmi uygulama (sadece aşağıya bakınız) kullanılarak sadece bir kez geçirilebilirler.

Böyle bir işleve sahip olduktan sonra, bunu gözlemler listesine "haddeleme projeksiyonu" olarak, - yukarıda açıklandığı gibi - uygulayabilir, - her bir gözlemi alarak ve son durumla birlikte fonksiyona besleyebilirsiniz. sonraki durum. Bu "haddeleme projeksiyonu" operasyonu, fonksiyonel programlamada çok yaygın bir şeydir ve genellikle scan olarak adlandırılır. F # Tüm standart koleksiyonları için scan ait uygulamaları sağlamak yapar - list, seq, scan sonucunda vb

, sen filtrenin ardışık devletler listesine sahip olacaktır. Şimdi yapacak tek şey, her bir durumdan X değerini balıklamaktır.

module ScalarKalman = 

    type Parameters = { A : float; H : float; Q : float; R : float } 
    type State = { K: float; X: float; P: float } 

    let initState (s: State) = s 

    let getX s = s.X 

    let update parms state newVal = 
     let xp = parms.A * state.X 
     let Pp = parms.A * state.P * parms.A + parms.Q 
     let newK = Pp * parms.H/(parms.H * Pp * parms.H + parms.R) 
     { K = newK 
     X = xp + newK * (newVal - parms.H * xp) 
     P = Pp - newK * parms.H * Pp } 


let n = 100 
let obsv = [for i in 0 .. n -> Normal.Sample(10., 5.)] 
let kal = ScalarKalman.update { A = 1.; H = 1.; Q = 0.; R = 5. } 
let initialState = ScalarKalman.initState { X = 6.; P = 4.; K = 0. } 

let smv = 
    obsv 
    |> List.scan kal initialState 
    |> List.map ScalarKalman.getX 

bir not tasarımına
Not modülünde bildirilen initState fonksiyonu:

İşte tam çözümdür. Bu işlev, yüzeyde aptalca görünebilir, ancak önemli bir anlamı vardır: modül alanından open olmadan alan adlarını belirtmeme izin verir, böylece ad alanı kirliliği önlenir. Ayrıca, tüketen kod artık daha okunabilir görünüyor: ne yaptığını, yorum gerektirmediğini söylüyor.

Buna Başka bir ortak yaklaşım tüketen kod sonra with sözdizimi yoluyla değiştirebileceğini modülünde, bir "temel" bir devlet ilan etmektir:

module ScalarKalman = 
    ... 
    let zeroState = { K = 0.; X = 0.; P = 0. } 

... 
let initialState = { ScalarKalman.zeroState with X = 6.; P = 4. } 


F # listeleri koleksiyonları ilgili bir not küçük miktarlarda veri ve küçük işleme boru hatlarında para cezası, ancak bu iki boyut büyüdükçe pahalılaşır. Çok sayıda akış verisi ile çalışıyorsanız ve/veya art arda birden fazla filtre uyguluyorsanız, tembel sekanslar kullanarak daha iyi olabilirsiniz - seq. Bunu yapmak için, sırasıyla Seq.scan ve ile List.scan ve List.map'u değiştirin. Bunu yaparsanız, sonuç olarak tükenme sırasına sahip olacaksınız, bu durumda bir şekilde tüketmeniz gerekir - ya onu bir listeye dönüştürün, yazdırın, bir sonraki bileşene gönderin ya da daha büyük bağlamınız ne olursa olsun.

İlgili konular