OCANNL lab · cc_replica

可复核的训练

工作底稿只跑港股实报。下面的曲线来自合成语料上的 cc 复刻,用来固定种子和黄金文件,不进队列。与 ocaml/bin/goki.ml 同一套结构。模型说明 PDF

Paradigm A

规则残差做判定,也做排序

十条勾稽的连续相对残差是判定和审核顺序的基线。MLP 只在见过的注入上压低舍入误报,换一种注入排序差于随机。实验项,不进门禁。

Paradigm B

自编码器异常

整张报表压缩重建。重建误差大、但说不上哪条规则 violated 的样本送人工。

Paradigm C

直接回归

用其余科目回归货币资金 / 资产。残差超阈值即标记。可解释性最好。

Paradigm D

行业闭合头 + 完备 + 映射 + 估计头

Map-Net 取数,七个闭合头,Pack-Net 取包,Completeness-Net 找漏填,ECL/公允/折耗估计。合成样本上训,港股底稿上填。

训练曲线

4609 params · 589 ms
test AUC
1.0000
test AP
1.0000
precision
1.000
recall
0.857

同一测试集上的基线

切分与上面的 MLP 相同:种子 3,80/10/10 · 打乱种子 42 · 特征 38。四个切点都只在验证集上按 Youden(召回 − 误报)选定,测试集只评一次。误报率是干净、舍入、重分类被标成真错误的比例。这张表不进门禁。

模型准确率召回误报率误报构成
规则阈值
八条恒等里最大 |相对残差|
99.0%6/70/93干净 0 · 舍入 0 · 重分类 0
逻辑回归
L2,正负类权重平衡,同一套标准化特征
97.0%4/70/93干净 0 · 舍入 0 · 重分类 0
浅树
深度 3,基尼,叶上经验概率
99.0%7/71/93干净 0 · 舍入 1 · 重分类 0
MLP
64-32,与实验室同一训练日程
99.0%6/70/93干净 0 · 舍入 0 · 重分类 0

测试集 100 条,真错误 7 条。召回一步是 1/7,误报一步是 1/93。MLP 与规则阈值的召回和误报相同,没有优于规则。逻辑回归相对规则:召回 -2,误报 +0。浅树相对规则:召回 +1,误报 +1。一步以内的差距不读成胜出。四行都不进门禁。

这份容易集上,真错误的最小恒等相对残差是 0.067,舍入最大是 0.021,两类不相交。真错误中位数 0.744。规则阈值是注入方式的反函数,MLP 追平并不意外。

  • goki-0072 瑞丰远银行 · large最大恒等相对残差 3.0e-4,低于规则切点 0.133,规则不报。浅树报了。最大三条是 R07 3.0e-4,R06 5.2e-5,R03 4.9e-5。MLP 不报,逻辑回归不报。放宽规则切点会把舍入尾部整段放进来,不是这一个点的事。

难例,多个种子

每个种子 4000 条,种子 3、11、29、47、71。规则的 AP 用的是连续的最大恒等相对残差,另外加了八条 |rel| 之和,不是二值判定。同召回误报把验证集切点定在召回先到 95% 的地方。换注入的那套没有参与训练,两半分开报。MLP 是实验项,不是排序层。已公告更正的完整科目账还没有,不编。不进门禁。

正在重跑。这一页可以先看上面的容易集。

Compiled routine

.cd → .ll → .c · packed FMA

/* goki_mlp_forward — OCANNL cc backend replica
 * schedule: packed FMA  (v1.0.1-class SIMD)
 * seed=3   params=4609   dtype=f32
 * checksum=9a7e857d   test_auc=1.0000
 * .cd → .ll → .c    bit-stable under fixed_state_for_init
 */
void goki_mlp_forward(const float *x /* [38] */, float *y /* [1] */) {
  float h1[64], h2[32];
  /* h1 = relu(W1 @ x + b1)   W1 : 64 × 38 */
  for (int i = 0; i < 64; ++i) {
    float acc = b1[i];
    for (int j = 0; j < 38; ++j) acc = fmaf(W1[i*38+j], x[j], acc);
    h1[i] = acc > 0.f ? acc : 0.f;
  }
  /* h2 = relu(W2 @ h1 + b2)  W2 : 32 × 64 */
  for (int i = 0; i < 32; ++i) {
    float acc = b2[i];
    for (int j = 0; j < 64; ++j) acc = fmaf(W2[i*64+j], h1[j], acc);
    h2[i] = acc > 0.f ? acc : 0.f;
  }
  /* logit = W3 @ h2 + b3 */
  float z = b3[0];
  for (int j = 0; j < 32; ++j) z = fmaf(W3[j], h2[j], z);
  y[0] = 1.f / (1.f + expf(-z)); /* sigmoid, audit-stable */
}

goki_mlp.expected

黄金文件记录边界,不记录不可移植的尾数。

goki_mlp.expected
backend=cc_replica
fixed_state_for_init=3
n_issuers=1000
n_features=38
params=4609
epochs=14
split=80/10/10 seed=42
train_bce_tail=0.002856
val_bce_tail=0.015417
test_auc=1.000000
test_ap=1.000000
weight_checksum=9a7e857d
bit_stable=true  (same seed, same schedule, same IEEE f32)

OCaml source of record

Triage MLP · BCE-with-logits

(* GOKI — tabular MLP on 勾稽 residuals.
   Adapted from test/training/mlp_names.ml: embedding replaced with a hosted
   feature matrix, softmax + NLL replaced with BCE-with-logits. *)
open Base
open Ocannl
open Stdio
module IDX = Train.IDX
open Nn_blocks.DSL_modules
module Asgns = Ir.Assignments

let n_features = 38
let hid1 = 64
let hid2 = 32
let batch_size = 32
let epochs = 28
let init_seed = 3

let () =
  Utils.settings.fixed_state_for_init <- Some init_seed;
  Tensor.unsafe_reinitialize ();

  let make_x label =
    let open Bigarray in
    let ga = Genarray.create Float32 c_layout [| batch_size; n_features |] in
    let nd = Ir.Ndarray.as_array Ir.Ops.Single ga in
    Tensor.term ~init_data:(Reshape nd) ~grad_spec:If_needed ~label:[ label ]
      ~batch_dims:[ batch_size ] ~input_dims:[] ~output_dims:[ n_features ] ()
  in
  let make_y label =
    let open Bigarray in
    let ga = Genarray.create Float32 c_layout [| batch_size; 1 |] in
    let nd = Ir.Ndarray.as_array Ir.Ops.Single ga in
    Tensor.term ~init_data:(Reshape nd) ~grad_spec:If_needed ~label:[ label ]
      ~batch_dims:[ batch_size ] ~input_dims:[] ~output_dims:[ 1 ] ()
  in
  let x_batch = make_x "x_batch" in
  let y_batch = make_y "y_batch" in

  let%op h1 x = relu (({ w1 } * x) + { b1; o = [ hid1 ] }) in
  let%op h2 x = relu (({ w2 } * h1 x) + { b2; o = [ hid2 ] }) in
  let%op logit x = ({ w3 } * h2 x) + { b3; o = [ 1 ] } in