Download src/fsharp/Program.fs from Snapkitty/symbolic-morphology: direct link, hf CLI and curl.
- Browser
- Download file 20.9 kB
-
https://huggingface.co/Snapkitty/symbolic-morphology/resolve/main/src/fsharp/Program.fs
- Command line
-
hf download hf://Snapkitty/symbolic-morphology/src/fsharp/Program.fs
-
curl -L -o Program.fs https://huggingface.co/Snapkitty/symbolic-morphology/resolve/main/src/fsharp/Program.fs
20.9 kB
| // Symbolic Morphology Engine — Latin verb morphology from raw letters to Boolean grammar | |
| // Copyright (C) 2026 Ahmad Ali Parr | |
| // | |
| // This program is free software: you can redistribute it and/or modify | |
| // it under the terms of the GNU Affero General Public License as published by | |
| // the Free Software Foundation, either version 3 of the License, or | |
| // (at your option) any later version. | |
| // | |
| // This program is distributed in the hope that it will be useful, | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| // GNU Affero General Public License for more details. | |
| // | |
| // You should have received a copy of the GNU Affero General Public License | |
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| open System | |
| open System.Diagnostics | |
| // ═══════════════════════════════════════════════════════════════ | |
| // SYMBOLIC LEARNING ENGINE — F# IMPLEMENTATION | |
| // Hand-rolled from mathematical primitives | |
| // ═══════════════════════════════════════════════════════════════ | |
| module Features = | |
| let names = [| | |
| "PERSON_1";"PERSON_2";"PERSON_3" | |
| "SINGULAR";"PLURAL" | |
| "PRESENT";"IMPERFECT";"FUTURE";"PERFECT" | |
| "INDICATIVE";"SUBJUNCTIVE";"IMPERATIVE" | |
| "ACTIVE";"PASSIVE" | |
| "CONJ_1";"CONJ_2";"CONJ_3" |] | |
| let count = names.Length | |
| let threshold = 0.5 | |
| let index = names |> Array.mapi (fun i n -> n, i) |> Map.ofArray | |
| let groups = [| | |
| "PERSON", [|"PERSON_1";"PERSON_2";"PERSON_3"|] | |
| "NUMBER", [|"SINGULAR";"PLURAL"|] | |
| "TENSE", [|"PRESENT";"IMPERFECT";"FUTURE";"PERFECT"|] | |
| "MOOD", [|"INDICATIVE";"SUBJUNCTIVE";"IMPERATIVE"|] | |
| "VOICE", [|"ACTIVE";"PASSIVE"|] | |
| "CONJ", [|"CONJ_1";"CONJ_2";"CONJ_3"|] |] | |
| let makeTarget (specs: (string*string)[]) = | |
| let t = Array.zeroCreate<float> count | |
| for _, feat in specs do t.[index.[feat]] <- 1.0 | |
| t | |
| let countCorrect (pred: float[]) (target: float[]) = | |
| let mutable c = 0 | |
| for i = 0 to count - 1 do | |
| if (pred.[i] >= threshold) = (target.[i] >= 0.5) then c <- c + 1 | |
| c | |
| module Dataset = | |
| open Features | |
| let p1 = "PERSON","PERSON_1" | |
| let p2 = "PERSON","PERSON_2" | |
| let p3 = "PERSON","PERSON_3" | |
| let sg = "NUMBER","SINGULAR" | |
| let pl = "NUMBER","PLURAL" | |
| let pres = "TENSE","PRESENT" | |
| let impf = "TENSE","IMPERFECT" | |
| let fut = "TENSE","FUTURE" | |
| let perf = "TENSE","PERFECT" | |
| let ind = "MOOD","INDICATIVE" | |
| let act = "VOICE","ACTIVE" | |
| let c1 = "CONJ","CONJ_1" | |
| let c2 = "CONJ","CONJ_2" | |
| let c3 = "CONJ","CONJ_3" | |
| let t specs = makeTarget specs | |
| let corpus() = [| | |
| "AMO",t[|p1;sg;pres;ind;act;c1|];"AMAS",t[|p2;sg;pres;ind;act;c1|] | |
| "AMAT",t[|p3;sg;pres;ind;act;c1|];"AMAMUS",t[|p1;pl;pres;ind;act;c1|] | |
| "AMATIS",t[|p2;pl;pres;ind;act;c1|];"AMANT",t[|p3;pl;pres;ind;act;c1|] | |
| "AMABAM",t[|p1;sg;impf;ind;act;c1|];"AMABAS",t[|p2;sg;impf;ind;act;c1|] | |
| "AMABAT",t[|p3;sg;impf;ind;act;c1|];"AMABAMUS",t[|p1;pl;impf;ind;act;c1|] | |
| "AMABATIS",t[|p2;pl;impf;ind;act;c1|];"AMABANT",t[|p3;pl;impf;ind;act;c1|] | |
| "AMABO",t[|p1;sg;fut;ind;act;c1|];"AMABIS",t[|p2;sg;fut;ind;act;c1|] | |
| "AMABIT",t[|p3;sg;fut;ind;act;c1|];"AMABIMUS",t[|p1;pl;fut;ind;act;c1|] | |
| "AMABITIS",t[|p2;pl;fut;ind;act;c1|];"AMABUNT",t[|p3;pl;fut;ind;act;c1|] | |
| "AMAVI",t[|p1;sg;perf;ind;act;c1|];"AMAVISTI",t[|p2;sg;perf;ind;act;c1|] | |
| "AMAVIT",t[|p3;sg;perf;ind;act;c1|];"AMAVIMUS",t[|p1;pl;perf;ind;act;c1|] | |
| "AMAVISTIS",t[|p2;pl;perf;ind;act;c1|];"AMAVERUNT",t[|p3;pl;perf;ind;act;c1|] | |
| "LAUDO",t[|p1;sg;pres;ind;act;c1|];"LAUDAS",t[|p2;sg;pres;ind;act;c1|] | |
| "LAUDAT",t[|p3;sg;pres;ind;act;c1|];"LAUDAMUS",t[|p1;pl;pres;ind;act;c1|] | |
| "LAUDATIS",t[|p2;pl;pres;ind;act;c1|];"LAUDANT",t[|p3;pl;pres;ind;act;c1|] | |
| "LAUDABAM",t[|p1;sg;impf;ind;act;c1|];"LAUDABAS",t[|p2;sg;impf;ind;act;c1|] | |
| "LAUDABAT",t[|p3;sg;impf;ind;act;c1|];"LAUDABAMUS",t[|p1;pl;impf;ind;act;c1|] | |
| "LAUDABATIS",t[|p2;pl;impf;ind;act;c1|];"LAUDABANT",t[|p3;pl;impf;ind;act;c1|] | |
| "LAUDAVI",t[|p1;sg;perf;ind;act;c1|];"LAUDAVISTI",t[|p2;sg;perf;ind;act;c1|] | |
| "LAUDAVIT",t[|p3;sg;perf;ind;act;c1|] | |
| "MONEO",t[|p1;sg;pres;ind;act;c2|];"MONES",t[|p2;sg;pres;ind;act;c2|] | |
| "MONET",t[|p3;sg;pres;ind;act;c2|];"MONEMUS",t[|p1;pl;pres;ind;act;c2|] | |
| "MONETIS",t[|p2;pl;pres;ind;act;c2|];"MONENT",t[|p3;pl;pres;ind;act;c2|] | |
| "MONEBAM",t[|p1;sg;impf;ind;act;c2|];"MONEBAS",t[|p2;sg;impf;ind;act;c2|] | |
| "MONEBAT",t[|p3;sg;impf;ind;act;c2|];"MONEBAMUS",t[|p1;pl;impf;ind;act;c2|] | |
| "MONEBATIS",t[|p2;pl;impf;ind;act;c2|];"MONEBANT",t[|p3;pl;impf;ind;act;c2|] | |
| "MONUI",t[|p1;sg;perf;ind;act;c2|];"MONUISTI",t[|p2;sg;perf;ind;act;c2|] | |
| "MONUIT",t[|p3;sg;perf;ind;act;c2|] | |
| "HABEO",t[|p1;sg;pres;ind;act;c2|];"HABES",t[|p2;sg;pres;ind;act;c2|] | |
| "HABET",t[|p3;sg;pres;ind;act;c2|];"HABEMUS",t[|p1;pl;pres;ind;act;c2|] | |
| "HABETIS",t[|p2;pl;pres;ind;act;c2|];"HABENT",t[|p3;pl;pres;ind;act;c2|] | |
| "HABUI",t[|p1;sg;perf;ind;act;c2|];"HABUISTI",t[|p2;sg;perf;ind;act;c2|] | |
| "HABUIT",t[|p3;sg;perf;ind;act;c2|] | |
| "REGO",t[|p1;sg;pres;ind;act;c3|];"REGIS",t[|p2;sg;pres;ind;act;c3|] | |
| "REGIT",t[|p3;sg;pres;ind;act;c3|];"REGIMUS",t[|p1;pl;pres;ind;act;c3|] | |
| "REGITIS",t[|p2;pl;pres;ind;act;c3|];"REGUNT",t[|p3;pl;pres;ind;act;c3|] | |
| "REGEBAM",t[|p1;sg;impf;ind;act;c3|];"REGEBAS",t[|p2;sg;impf;ind;act;c3|] | |
| "REGEBAT",t[|p3;sg;impf;ind;act;c3|];"REGEBAMUS",t[|p1;pl;impf;ind;act;c3|] | |
| "REGEBATIS",t[|p2;pl;impf;ind;act;c3|];"REGEBANT",t[|p3;pl;impf;ind;act;c3|] | |
| "REXI",t[|p1;sg;perf;ind;act;c3|];"REXISTI",t[|p2;sg;perf;ind;act;c3|] | |
| "REXIT",t[|p3;sg;perf;ind;act;c3|] | |
| "AGO",t[|p1;sg;pres;ind;act;c3|];"AGIS",t[|p2;sg;pres;ind;act;c3|] | |
| "AGIT",t[|p3;sg;pres;ind;act;c3|];"AGIMUS",t[|p1;pl;pres;ind;act;c3|] | |
| "AGITIS",t[|p2;pl;pres;ind;act;c3|];"AGUNT",t[|p3;pl;pres;ind;act;c3|] | |
| "EGI",t[|p1;sg;perf;ind;act;c3|];"EGISTI",t[|p2;sg;perf;ind;act;c3|] | |
| "EGIT",t[|p3;sg;perf;ind;act;c3|] | |
| "DUCO",t[|p1;sg;pres;ind;act;c3|];"DUCIS",t[|p2;sg;pres;ind;act;c3|] | |
| "DUCIT",t[|p3;sg;pres;ind;act;c3|];"DUCIMUS",t[|p1;pl;pres;ind;act;c3|] | |
| "DUCITIS",t[|p2;pl;pres;ind;act;c3|];"DUCUNT",t[|p3;pl;pres;ind;act;c3|] | |
| "DUXI",t[|p1;sg;perf;ind;act;c3|];"DUXISTI",t[|p2;sg;perf;ind;act;c3|] | |
| "DUXIT",t[|p3;sg;perf;ind;act;c3|] |] | |
| let unseen() = [| | |
| "NARRAT",t[|p3;sg;pres;ind;act;c1|];"NARRANT",t[|p3;pl;pres;ind;act;c1|] | |
| "NARRABAT",t[|p3;sg;impf;ind;act;c1|] | |
| "VIDET",t[|p3;sg;pres;ind;act;c2|];"VIDENT",t[|p3;pl;pres;ind;act;c2|] | |
| "VIDEBAT",t[|p3;sg;impf;ind;act;c2|] | |
| "SCRIBIT",t[|p3;sg;pres;ind;act;c3|];"SCRIBUNT",t[|p3;pl;pres;ind;act;c3|] |] | |
| module Engine = | |
| let sigmoid x = if x >= 0.0 then 1.0/(1.0+Math.Exp(-x)) else Math.Exp(x)/(1.0+Math.Exp(x)) | |
| type Model = { | |
| embedDim: int; hiddenDim: int; outputDim: int; lr: float | |
| embeddings: float[,] | |
| w1: float[,]; b1: float[] | |
| w2: float[,]; b2: float[] | |
| } | |
| let mutable charIndices: int[] = [||] | |
| let mutable pooled: float[] = [||] | |
| let mutable a1: float[] = [||] | |
| let mutable yPred: float[] = [||] | |
| // Pooling: false = original mean-pool. true = self-attention over the word's letter embeddings (Q = K = V = X, | |
| // bidirectional, no new parameters): O = softmax(X X^T / sqrt(D)) X, h = (1/n) sum_i O_i, using Attention.fs. | |
| let mutable poolAttention = false | |
| let mutable attentionIsa = Attention.detect () | |
| let mutable xAttn: float[] = [||] | |
| let mutable oAttn: float[] = [||] | |
| let mutable lseAttn: float[] = [||] | |
| let init eD hD oD lr seed = | |
| let rng = Random(seed) | |
| let norm() = | |
| let u1 = 1.0 - rng.NextDouble() | |
| let u2 = rng.NextDouble() | |
| Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2) | |
| let es = Math.Sqrt(2.0/float(1+eD)) | |
| let emb = Array2D.init 128 eD (fun _ _ -> norm()*es) | |
| let s1 = Math.Sqrt(2.0/float(eD+hD)) | |
| let w1 = Array2D.init hD eD (fun _ _ -> norm()*s1) | |
| let s2 = Math.Sqrt(2.0/float(hD+oD)) | |
| let w2 = Array2D.init oD hD (fun _ _ -> norm()*s2) | |
| { embedDim=eD; hiddenDim=hD; outputDim=oD; lr=lr; embeddings=emb; w1=w1; b1=Array.zeroCreate hD; w2=w2; b2=Array.zeroCreate oD } | |
| let forward m (word: string) = | |
| let chars = word.ToUpperInvariant().ToCharArray() |> Array.map int | |
| let len = chars.Length | |
| let D = m.embedDim | |
| let H = m.hiddenDim | |
| let K = m.outputDim | |
| charIndices <- chars | |
| if poolAttention then | |
| let x = Array.zeroCreate<float> (len * D) | |
| for t = 0 to len - 1 do | |
| for d = 0 to D - 1 do x.[t * D + d] <- m.embeddings.[chars.[t], d] | |
| let o = Array.zeroCreate<float> (len * D) | |
| let lse = Array.zeroCreate<float> len | |
| Attention.forward attentionIsa D x x x o lse len false | |
| xAttn <- x | |
| oAttn <- o | |
| lseAttn <- lse | |
| pooled <- Array.init D (fun d -> | |
| let mutable s = 0.0 | |
| for t = 0 to len - 1 do s <- s + o.[t * D + d] | |
| s / float len) | |
| else | |
| let embV = Array.init len (fun t -> Array.init D (fun d -> m.embeddings.[chars.[t],d])) | |
| pooled <- Array.init D (fun d -> | |
| let mutable s = 0.0 | |
| for t = 0 to len - 1 do s <- s + embV.[t].[d] | |
| s / float len) | |
| let z1 = Array.init H (fun i -> | |
| let mutable s = m.b1.[i] | |
| for j = 0 to D - 1 do s <- s + m.w1.[i,j] * pooled.[j] | |
| s) | |
| a1 <- z1 |> Array.map Math.Tanh | |
| let z2 = Array.init K (fun i -> | |
| let mutable s = m.b2.[i] | |
| for j = 0 to H - 1 do s <- s + m.w2.[i,j] * a1.[j] | |
| s) | |
| yPred <- z2 |> Array.map sigmoid | |
| yPred | |
| let computeLoss (target: float[]) = | |
| let eps = 1e-12 | |
| let K = yPred.Length | |
| let mutable loss = 0.0 | |
| for i = 0 to K - 1 do | |
| let y = Math.Clamp(yPred.[i], eps, 1.0 - eps) | |
| loss <- loss - target.[i] * Math.Log(y) - (1.0 - target.[i]) * Math.Log(1.0 - y) | |
| loss / float K | |
| let backward m (target: float[]) = | |
| let len = charIndices.Length | |
| let D = m.embedDim | |
| let H = m.hiddenDim | |
| let K = m.outputDim | |
| let invD = 1.0 / float K | |
| let invN = 1.0 / float len | |
| let dl_dz2 = Array.init K (fun i -> (yPred.[i] - target.[i]) * invD) | |
| let dl_da1 = Array.init H (fun j -> | |
| let mutable s = 0.0 | |
| for i = 0 to K - 1 do s <- s + m.w2.[i,j] * dl_dz2.[i] | |
| s) | |
| let dl_dz1 = Array.init H (fun j -> dl_da1.[j] * (1.0 - a1.[j] * a1.[j])) | |
| let dl_dh = Array.init D (fun j -> | |
| let mutable s = 0.0 | |
| for i = 0 to H - 1 do s <- s + m.w1.[i,j] * dl_dz1.[i] | |
| s) | |
| if poolAttention then | |
| // h = (1/n) sum_i O_i => dO_i = dh / n for every row; with Q = K = V = X the gradient at position i is dQ_i + dK_i + dV_i | |
| let dOut = Array.init (len * D) (fun idx -> invN * dl_dh.[idx % D]) | |
| let dq = Array.zeroCreate<float> (len * D) | |
| let dk = Array.zeroCreate<float> (len * D) | |
| let dv = Array.zeroCreate<float> (len * D) | |
| Attention.backward attentionIsa D xAttn xAttn xAttn oAttn lseAttn dOut dq dk dv (Array.zeroCreate len) len false | |
| for t = 0 to len - 1 do | |
| let c = charIndices.[t] | |
| for d = 0 to D - 1 do | |
| m.embeddings.[c,d] <- m.embeddings.[c,d] - m.lr * (dq.[t * D + d] + dk.[t * D + d] + dv.[t * D + d]) | |
| else | |
| for t = 0 to len - 1 do | |
| let c = charIndices.[t] | |
| for d = 0 to D - 1 do | |
| m.embeddings.[c,d] <- m.embeddings.[c,d] - m.lr * invN * dl_dh.[d] | |
| for i = 0 to H - 1 do | |
| m.b1.[i] <- m.b1.[i] - m.lr * dl_dz1.[i] | |
| for j = 0 to D - 1 do | |
| m.w1.[i,j] <- m.w1.[i,j] - m.lr * dl_dz1.[i] * pooled.[j] | |
| for i = 0 to K - 1 do | |
| m.b2.[i] <- m.b2.[i] - m.lr * dl_dz2.[i] | |
| for j = 0 to H - 1 do | |
| m.w2.[i,j] <- m.w2.[i,j] - m.lr * dl_dz2.[i] * a1.[j] | |
| /// Checks on the real engine: gradients reaching the embeddings through each pooling, training behaviour, determinism. | |
| module EngineChecks = | |
| let private fresh () = Engine.init 16 32 Features.count 0.5 42 | |
| /// Central finite differences of the loss w.r.t. every embedding entry of the word's letters, against the gradient | |
| /// that Engine.backward applied ((before - after) / lr on a fresh model). Rows of letters absent from the word must not move. | |
| let gradCheck (attn: bool) (word: string) = | |
| Engine.poolAttention <- attn | |
| let target = Dataset.corpus() |> Array.find (fun (w, _) -> w = word) |> snd | |
| let m0 = fresh () | |
| let before = Array2D.copy m0.embeddings | |
| Engine.forward m0 word |> ignore | |
| Engine.backward m0 target | |
| let letters = word.ToUpperInvariant() |> Seq.map int |> Seq.distinct |> Seq.toArray | |
| let h = 1e-4 | |
| let mutable nonzero = false | |
| for c in letters do | |
| for d = 0 to 15 do | |
| let analytic = (before.[c, d] - m0.embeddings.[c, d]) / m0.lr | |
| let lossAt delta = | |
| let m = fresh () | |
| m.embeddings.[c, d] <- m.embeddings.[c, d] + delta | |
| Engine.forward m word |> ignore | |
| Engine.computeLoss target | |
| let fd = (lossAt h - lossAt (-h)) / (2.0 * h) | |
| if abs (fd - analytic) > 1e-8 + 1e-6 * abs analytic then | |
| failwithf "FAIL gradient (attn=%b) %s emb[%d,%d]: fd=%g analytic=%g" attn word c d fd analytic | |
| if abs analytic > 1e-9 then nonzero <- true | |
| if not nonzero then failwithf "FAIL (attn=%b) %s: embedding gradient is identically zero" attn word | |
| for c = 0 to 127 do | |
| if not (Array.contains c letters) then | |
| for d = 0 to 15 do | |
| if before.[c, d] <> m0.embeddings.[c, d] then failwithf "FAIL (attn=%b) %s: row %d moved but is not in the word" attn word c | |
| let private epoch (m: Engine.Model) (corpus: (string * float[])[]) = | |
| let mutable total = 0.0 | |
| for (w, t) in corpus do | |
| Engine.forward m w |> ignore | |
| total <- total + Engine.computeLoss t | |
| Engine.backward m t | |
| total / float corpus.Length | |
| let private train attn isa epochs = | |
| Engine.poolAttention <- attn | |
| Engine.attentionIsa <- isa | |
| let m = fresh () | |
| let corpus = Dataset.corpus () | |
| let mutable last = 0.0 | |
| for _ in 1 .. epochs do last <- epoch m corpus | |
| last, Array2D.copy m.embeddings | |
| let runAll () = | |
| for attn in [ false; true ] do | |
| for word in [ "AMO"; "AMABAMUS"; "REGEBATIS" ] do gradCheck attn word | |
| printfn "engine gradients (mean and attention pooling) match finite differences; only the word's letters move" | |
| let first, _ = train true (Attention.detect ()) 1 | |
| let last, _ = train true (Attention.detect ()) 60 | |
| if not (last < first * 0.5) then failwithf "FAIL attention training did not reduce loss: %g -> %g" first last | |
| let a, ea = train true (Attention.detect ()) 3 | |
| let b, eb = train true (Attention.detect ()) 3 | |
| if a <> b || ea <> eb then failwith "FAIL attention training is not deterministic" | |
| if Attention.avx2FmaAvailable then | |
| let ls, _ = train true Attention.Scalar 3 | |
| let lv, _ = train true Attention.Avx2Fma 3 | |
| if abs (ls - lv) > 1e-10 then failwithf "FAIL scalar vs AVX2 training: %g vs %g" ls lv | |
| Engine.poolAttention <- false | |
| printfn "attention training: loss falls %.4f -> %.4f in 60 epochs, deterministic, scalar == AVX2+FMA" first last | |
| let main argv = | |
| // dotnet run -c Release --project src/fsharp -- [mean|attn] [simd|scalar] benchmark suite (default: mean pooling) | |
| // selftest attention kernel + engine checks | |
| // bench-attention attention kernel timings | |
| match List.ofArray argv with | |
| | "selftest" :: _ -> | |
| try | |
| AttentionTests.runAll () | |
| EngineChecks.runAll () | |
| 0 | |
| with e -> | |
| eprintfn "%s" e.Message | |
| 1 | |
| | "bench-attention" :: _ -> | |
| AttentionTests.bench () | |
| 0 | |
| | args -> | |
| match args with | |
| | [] | "mean" :: _ -> () | |
| | "attn" :: _ -> Engine.poolAttention <- true | |
| | x :: _ -> failwithf "unknown pool '%s' (expected mean or attn)" x | |
| match args with | |
| | _ :: "scalar" :: _ -> Engine.attentionIsa <- Attention.Scalar | |
| | _ :: "simd" :: _ | [ _ ] | [] -> () | |
| | _ :: x :: _ -> failwithf "unknown kernel '%s' (expected simd or scalar)" x | |
| printfn "=================================================================" | |
| printfn " F# BENCHMARK SUITE" | |
| printfn "=================================================================" | |
| if Engine.poolAttention then printfn " Pooling: attention (kernel: %s)" (Attention.isaName Engine.attentionIsa) | |
| let corpus = Dataset.corpus() | |
| let unseen = Dataset.unseen() | |
| let m = Engine.init 16 32 Features.count 0.5 42 | |
| printfn " Corpus: %d words, %d features, %d unseen" corpus.Length Features.count unseen.Length | |
| printfn "" | |
| // BENCHMARK 1: Training | |
| printfn " BENCHMARK 1: TRAINING (3000 epochs x %d examples)" corpus.Length | |
| let sw = Stopwatch.StartNew() | |
| let mutable finalLoss = 0.0 | |
| for ep = 0 to 3000 do | |
| let mutable totalLoss = 0.0 | |
| for i = 0 to corpus.Length - 1 do | |
| let w, t = corpus.[i] | |
| Engine.forward m w |> ignore | |
| totalLoss <- totalLoss + Engine.computeLoss t | |
| Engine.backward m t | |
| finalLoss <- totalLoss / float corpus.Length | |
| if ep % 500 = 0 || ep = 3000 then | |
| printfn " Epoch %5d | Loss: %10.6f | %.2fs" ep finalLoss sw.Elapsed.TotalSeconds | |
| sw.Stop() | |
| let totalEx = int64 3001 * int64 corpus.Length | |
| printfn "\n Training: %.3fs, %d examples, %.0f ex/sec" sw.Elapsed.TotalSeconds totalEx (float totalEx / sw.Elapsed.TotalSeconds) | |
| // BENCHMARK 2: Inference | |
| printfn "\n BENCHMARK 2: INFERENCE LATENCY" | |
| for i = 0 to corpus.Length - 1 do Engine.forward m (fst corpus.[i]) |> ignore | |
| let nInf = 1000 | |
| let infSw = Stopwatch.StartNew() | |
| for _ = 0 to nInf - 1 do | |
| for i = 0 to corpus.Length - 1 do | |
| Engine.forward m (fst corpus.[i]) |> ignore | |
| infSw.Stop() | |
| let totalInf = int64 nInf * int64 corpus.Length | |
| printfn " %d inferences in %.3fs, %.2f us/inf, %.0f inf/sec" totalInf infSw.Elapsed.TotalSeconds (float infSw.Elapsed.TotalMicroseconds / float totalInf) (float totalInf / infSw.Elapsed.TotalSeconds) | |
| // BENCHMARK 3: Forward+Backward | |
| printfn "\n BENCHMARK 3: FORWARD+BACKWARD LATENCY" | |
| let nFb = 1000 | |
| let fbSw = Stopwatch.StartNew() | |
| for _ = 0 to nFb - 1 do | |
| for i = 0 to corpus.Length - 1 do | |
| let w, t = corpus.[i] | |
| Engine.forward m w |> ignore | |
| Engine.computeLoss t |> ignore | |
| Engine.backward m t | |
| fbSw.Stop() | |
| let totalFb = int64 nFb * int64 corpus.Length | |
| printfn " %d passes in %.3fs, %.2f us/pass, %.0f pass/sec" totalFb fbSw.Elapsed.TotalSeconds (float fbSw.Elapsed.TotalMicroseconds / float totalFb) (float totalFb / fbSw.Elapsed.TotalSeconds) | |
| // BENCHMARK 4: Quality | |
| printfn "\n BENCHMARK 4: GENERALIZATION" | |
| let mutable trainOk = 0 | |
| for i = 0 to corpus.Length - 1 do | |
| let w, t = corpus.[i] | |
| let p = Engine.forward m w | |
| if Features.countCorrect p t = Features.count then trainOk <- trainOk + 1 | |
| let mutable unseenOk = 0 | |
| for i = 0 to unseen.Length - 1 do | |
| let w, t = unseen.[i] | |
| let p = Engine.forward m w | |
| if Features.countCorrect p t = Features.count then unseenOk <- unseenOk + 1 | |
| printfn " Train: %d/%d perfect | Unseen: %d/%d perfect" trainOk corpus.Length unseenOk unseen.Length | |
| printfn "\n=================================================================" | |
| printfn " F# BENCHMARK COMPLETE" | |
| printfn "=================================================================" | |
| 0 | |