1use std::io::{BufReader, Read, Seek};
23use std::path::Path;
24
25use anyhow::{Context, Result};
26use candle_core::quantized::gguf_file;
27use candle_core::{DType, Device, Tensor};
28
29use crate::config::TabIclConfig;
30
31const LN_EPS: f64 = 1e-5;
32
33struct SsmaxW {
38 base_fc1_w: Tensor,
39 base_fc1_b: Tensor,
40 base_fc2_w: Tensor,
41 base_fc2_b: Tensor,
42 query_fc1_w: Tensor,
43 query_fc1_b: Tensor,
44 query_fc2_w: Tensor,
45 query_fc2_b: Tensor,
46}
47
48struct AttnW {
49 in_proj_w: Tensor,
50 in_proj_b: Tensor,
51 out_proj_w: Tensor,
52 out_proj_b: Tensor,
53 ssmax: Option<SsmaxW>,
54}
55
56struct BlockW {
57 norm1_w: Tensor,
58 norm1_b: Tensor,
59 norm2_w: Tensor,
60 norm2_b: Tensor,
61 attn: AttnW,
62 fc1_w: Tensor,
63 fc1_b: Tensor,
64 fc2_w: Tensor,
65 fc2_b: Tensor,
66}
67
68struct IsabW {
69 ind_vectors: Tensor,
70 attn1: BlockW,
71 attn2: BlockW,
72}
73
74struct ColEmbedderW {
75 in_linear_w: Tensor,
76 in_linear_b: Tensor,
77 y_encoder_w: Tensor,
78 y_encoder_b: Tensor,
79 blocks: Vec<IsabW>,
80}
81
82struct RowInteractorW {
83 cls_tokens: Tensor,
84 blocks: Vec<BlockW>,
85 out_ln_w: Tensor,
86 out_ln_b: Tensor,
87 rope_freqs: Vec<f32>,
88}
89
90struct IclPredictorW {
91 y_encoder_w: Tensor,
92 y_encoder_b: Tensor,
93 blocks: Vec<BlockW>,
94 ln_w: Tensor,
95 ln_b: Tensor,
96 decoder_fc1_w: Tensor,
97 decoder_fc1_b: Tensor,
98 decoder_fc2_w: Tensor,
99 decoder_fc2_b: Tensor,
100}
101
102pub struct TabIclModel {
103 device: Device,
104 config: TabIclConfig,
105 col: ColEmbedderW,
106 row: RowInteractorW,
107 icl: IclPredictorW,
108}
109
110fn load_t(content: &gguf_file::Content, reader: &mut (impl Read + Seek), name: &str, device: &Device) -> Result<Tensor> {
117 zsfm_nn::load_tensor(content, reader, name, device, DType::F32)
118}
119
120fn load_attn(
121 content: &gguf_file::Content,
122 reader: &mut (impl Read + Seek),
123 prefix: &str,
124 device: &Device,
125 has_ssmax: bool,
126) -> Result<AttnW> {
127 let mut t = |s: &str| load_t(content, reader, &format!("{prefix}.{s}"), device);
128 let in_proj_w = t("in_proj_weight")?;
129 let in_proj_b = t("in_proj_bias")?;
130 let out_proj_w = t("out_proj.weight")?;
131 let out_proj_b = t("out_proj.bias")?;
132 let ssmax = if has_ssmax {
133 Some(SsmaxW {
134 base_fc1_w: t("ssmax_layer.base_mlp.0.weight")?,
135 base_fc1_b: t("ssmax_layer.base_mlp.0.bias")?,
136 base_fc2_w: t("ssmax_layer.base_mlp.2.weight")?,
137 base_fc2_b: t("ssmax_layer.base_mlp.2.bias")?,
138 query_fc1_w: t("ssmax_layer.query_mlp.0.weight")?,
139 query_fc1_b: t("ssmax_layer.query_mlp.0.bias")?,
140 query_fc2_w: t("ssmax_layer.query_mlp.2.weight")?,
141 query_fc2_b: t("ssmax_layer.query_mlp.2.bias")?,
142 })
143 } else {
144 None
145 };
146 Ok(AttnW { in_proj_w, in_proj_b, out_proj_w, out_proj_b, ssmax })
147}
148
149fn load_block(
150 content: &gguf_file::Content,
151 reader: &mut (impl Read + Seek),
152 prefix: &str,
153 device: &Device,
154 has_ssmax: bool,
155) -> Result<BlockW> {
156 let norm1_w = load_t(content, reader, &format!("{prefix}.norm1.weight"), device)?;
157 let norm1_b = load_t(content, reader, &format!("{prefix}.norm1.bias"), device)?;
158 let norm2_w = load_t(content, reader, &format!("{prefix}.norm2.weight"), device)?;
159 let norm2_b = load_t(content, reader, &format!("{prefix}.norm2.bias"), device)?;
160 let attn = load_attn(content, reader, &format!("{prefix}.attn"), device, has_ssmax)?;
161 let fc1_w = load_t(content, reader, &format!("{prefix}.linear1.weight"), device)?;
162 let fc1_b = load_t(content, reader, &format!("{prefix}.linear1.bias"), device)?;
163 let fc2_w = load_t(content, reader, &format!("{prefix}.linear2.weight"), device)?;
164 let fc2_b = load_t(content, reader, &format!("{prefix}.linear2.bias"), device)?;
165 Ok(BlockW { norm1_w, norm1_b, norm2_w, norm2_b, attn, fc1_w, fc1_b, fc2_w, fc2_b })
166}
167
168impl TabIclModel {
169 pub fn load(gguf_path: &Path, config: TabIclConfig) -> Result<Self> {
170 let device = Device::Cpu;
171 let file = std::fs::File::open(gguf_path).with_context(|| format!("open {}", gguf_path.display()))?;
172 let mut reader = BufReader::with_capacity(zsfm_gguf::READ_BUF_CAPACITY, file);
173 let content = gguf_file::Content::read(&mut reader).context("parse GGUF header")?;
174
175 let col_in_linear_w = load_t(&content, &mut reader, "col_embedder.in_linear.weight", &device)?;
176 let col_in_linear_b = load_t(&content, &mut reader, "col_embedder.in_linear.bias", &device)?;
177 let col_y_w = load_t(&content, &mut reader, "col_embedder.y_encoder.weight", &device)?;
178 let col_y_b = load_t(&content, &mut reader, "col_embedder.y_encoder.bias", &device)?;
179
180 let mut col_blocks = Vec::with_capacity(config.col_num_blocks);
181 for n in 0..config.col_num_blocks {
182 let p = format!("col_embedder.tf_col.blocks.{n}");
183 let ind_vectors = load_t(&content, &mut reader, &format!("{p}.ind_vectors"), &device)?;
184 let attn1 = load_block(&content, &mut reader, &format!("{p}.multihead_attn1"), &device, true)?;
185 let attn2 = load_block(&content, &mut reader, &format!("{p}.multihead_attn2"), &device, false)?;
186 col_blocks.push(IsabW { ind_vectors, attn1, attn2 });
187 }
188
189 let cls_tokens = load_t(&content, &mut reader, "row_interactor.cls_tokens", &device)?;
190 let row_out_ln_w = load_t(&content, &mut reader, "row_interactor.out_ln.weight", &device)?;
191 let row_out_ln_b = load_t(&content, &mut reader, "row_interactor.out_ln.bias", &device)?;
192 let rope_freqs_t = load_t(&content, &mut reader, "row_interactor.tf_row.rope.freqs", &device)?;
193 let rope_freqs: Vec<f32> = rope_freqs_t.flatten_all()?.to_vec1()?;
194
195 let mut row_blocks = Vec::with_capacity(config.row_num_blocks);
196 for n in 0..config.row_num_blocks {
197 let p = format!("row_interactor.tf_row.blocks.{n}");
198 row_blocks.push(load_block(&content, &mut reader, &p, &device, false)?);
199 }
200
201 let icl_y_w = load_t(&content, &mut reader, "icl_predictor.y_encoder.weight", &device)?;
202 let icl_y_b = load_t(&content, &mut reader, "icl_predictor.y_encoder.bias", &device)?;
203 let icl_ln_w = load_t(&content, &mut reader, "icl_predictor.ln.weight", &device)?;
204 let icl_ln_b = load_t(&content, &mut reader, "icl_predictor.ln.bias", &device)?;
205 let decoder_fc1_w = load_t(&content, &mut reader, "icl_predictor.decoder.0.weight", &device)?;
206 let decoder_fc1_b = load_t(&content, &mut reader, "icl_predictor.decoder.0.bias", &device)?;
207 let decoder_fc2_w = load_t(&content, &mut reader, "icl_predictor.decoder.2.weight", &device)?;
208 let decoder_fc2_b = load_t(&content, &mut reader, "icl_predictor.decoder.2.bias", &device)?;
209
210 let mut icl_blocks = Vec::with_capacity(config.icl_num_blocks);
211 for n in 0..config.icl_num_blocks {
212 let p = format!("icl_predictor.tf_icl.blocks.{n}");
213 icl_blocks.push(load_block(&content, &mut reader, &p, &device, true)?);
214 }
215
216 Ok(Self {
217 device,
218 config,
219 col: ColEmbedderW {
220 in_linear_w: col_in_linear_w,
221 in_linear_b: col_in_linear_b,
222 y_encoder_w: col_y_w,
223 y_encoder_b: col_y_b,
224 blocks: col_blocks,
225 },
226 row: RowInteractorW {
227 cls_tokens,
228 blocks: row_blocks,
229 out_ln_w: row_out_ln_w,
230 out_ln_b: row_out_ln_b,
231 rope_freqs,
232 },
233 icl: IclPredictorW {
234 y_encoder_w: icl_y_w,
235 y_encoder_b: icl_y_b,
236 blocks: icl_blocks,
237 ln_w: icl_ln_w,
238 ln_b: icl_ln_b,
239 decoder_fc1_w,
240 decoder_fc1_b,
241 decoder_fc2_w,
242 decoder_fc2_b,
243 },
244 })
245 }
246
247 pub fn predict_classification(
255 &self,
256 x_support: &[Vec<f32>],
257 y_support: &[usize],
258 x_query: &[Vec<f32>],
259 n_classes: usize,
260 ) -> Result<Vec<Vec<f32>>> {
261 anyhow::ensure!(n_classes <= self.config.max_classes, "n_classes must be <= {}", self.config.max_classes);
262 let train_size = x_support.len();
263 let mut all_rows = x_support.to_vec();
264 all_rows.extend_from_slice(x_query);
265 let t = all_rows.len();
266 let h = all_rows[0].len();
267
268 let processed = preprocess_x(&all_rows, train_size);
269 let group_size = self.config.feature_group_size;
270 let grouped = feature_group_same(&processed, h, group_size);
271
272 let mut flat = vec![0f32; h * t * group_size];
273 for (row_idx, row) in grouped.iter().enumerate() {
274 for (col_idx, group) in row.iter().enumerate() {
275 let base = col_idx * t * group_size + row_idx * group_size;
276 flat[base..base + group_size].copy_from_slice(group);
277 }
278 }
279 let x_t = Tensor::from_vec(flat, (h, t, group_size), &self.device)?;
280
281 let y_onehot_col = self.onehot_linear(y_support, &self.col.y_encoder_w, &self.col.y_encoder_b)?;
282 let col_out = self.col_embed_forward(&x_t, &y_onehot_col, train_size)?;
283 let row_out = self.row_interact_forward(&col_out, train_size)?;
284 let y_onehot_icl = self.onehot_linear(y_support, &self.icl.y_encoder_w, &self.icl.y_encoder_b)?;
285 let logits = self.icl_forward(&row_out, &y_onehot_icl, train_size, n_classes)?;
286
287 let flat_logits: Vec<f32> = logits.flatten_all()?.to_vec1()?;
288 let n_query = t - train_size;
289 const TEMPERATURE: f32 = 0.9;
290 let mut result = Vec::with_capacity(n_query);
291 for i in 0..n_query {
292 let row = &flat_logits[i * n_classes..(i + 1) * n_classes];
293 let scaled: Vec<f32> = row.iter().map(|&v| v / TEMPERATURE).collect();
294 result.push(softmax(&scaled));
295 }
296 Ok(result)
297 }
298
299 fn onehot_linear(&self, y: &[usize], w: &Tensor, b: &Tensor) -> Result<Tensor> {
300 let n = y.len();
301 let num_classes = w.dim(1)?;
302 let mut onehot = vec![0f32; n * num_classes];
303 for (i, &c) in y.iter().enumerate() {
304 onehot[i * num_classes + c] = 1.0;
305 }
306 let x = Tensor::from_vec(onehot, (n, num_classes), &self.device)?;
307 zsfm_nn::linear_bias(&x, w, b).map_err(anyhow::Error::from)
308 }
309
310 fn col_embed_forward(&self, x_grouped: &Tensor, y_onehot: &Tensor, train_size: usize) -> Result<Tensor> {
313 let src = zsfm_nn::linear_bias(x_grouped, &self.col.in_linear_w, &self.col.in_linear_b)?;
314 let t = src.dim(1)?;
315 let train_part = src.narrow(1, 0, train_size)?.broadcast_add(&y_onehot.unsqueeze(0)?)?;
316 let mut src = if train_size < t {
317 Tensor::cat(&[&train_part, &src.narrow(1, train_size, t - train_size)?], 1)?
318 } else {
319 train_part
320 };
321
322 let h = src.dim(0)?;
323 for isab in &self.col.blocks {
324 src = isab_forward(&src, isab, self.config.col_nhead, train_size, h)?;
325 }
326 Ok(src)
327 }
328
329 fn row_interact_forward(&self, col_embeddings: &Tensor, _train_size: usize) -> Result<Tensor> {
331 let h = col_embeddings.dim(0)?;
332 let t = col_embeddings.dim(1)?;
333 let dim = self.config.embed_dim;
334 let n_cls = self.config.row_num_cls;
335
336 let feat = col_embeddings.permute((1, 0, 2))?.contiguous()?; let cls = self.row.cls_tokens.unsqueeze(0)?.expand((t, n_cls, dim))?.contiguous()?;
338 let mut seq = Tensor::cat(&[&cls, &feat], 1)?; let max_len = h + n_cls;
341 let (cos, sin) = self.row_rope_table(max_len)?;
342
343 let n_blocks = self.row.blocks.len();
344 for (i, blk) in self.row.blocks.iter().enumerate() {
345 if i + 1 == n_blocks {
346 let cls_q = seq.narrow(1, 0, n_cls)?;
347 seq = mha_block(&cls_q, &seq, &seq, blk, self.config.row_nhead, Some((&cos, &sin)), 0)?;
348 } else {
349 seq = mha_block(&seq, &seq, &seq, blk, self.config.row_nhead, Some((&cos, &sin)), 0)?;
350 }
351 }
352
353 let out = zsfm_nn::layer_norm(&seq, &self.row.out_ln_w, &self.row.out_ln_b, LN_EPS)?; out.reshape((t, n_cls * dim)).map_err(anyhow::Error::from)
355 }
356
357 fn row_rope_table(&self, max_len: usize) -> Result<(Tensor, Tensor)> {
358 let half = self.row.rope_freqs.len();
359 let mut cos = vec![0f32; max_len * half * 2];
360 let mut sin = vec![0f32; max_len * half * 2];
361 for p in 0..max_len {
362 for i in 0..half {
363 let angle = p as f32 * self.row.rope_freqs[i];
364 let (s, c) = angle.sin_cos();
365 cos[p * 2 * half + i] = c;
366 cos[p * 2 * half + half + i] = c;
367 sin[p * 2 * half + i] = s;
368 sin[p * 2 * half + half + i] = s;
369 }
370 }
371 let cos_t = Tensor::from_vec(cos, (max_len, 2 * half), &self.device)?;
372 let sin_t = Tensor::from_vec(sin, (max_len, 2 * half), &self.device)?;
373 Ok((cos_t, sin_t))
374 }
375
376 fn icl_forward(&self, row_reprs: &Tensor, y_onehot: &Tensor, train_size: usize, n_classes: usize) -> Result<Tensor> {
380 let t = row_reprs.dim(0)?;
381 let train_part = row_reprs.narrow(0, 0, train_size)?.broadcast_add(y_onehot)?;
382 let r = if train_size < t {
383 Tensor::cat(&[&train_part, &row_reprs.narrow(0, train_size, t - train_size)?], 0)?
384 } else {
385 train_part
386 };
387 let mut r = r.unsqueeze(0)?; for blk in &self.icl.blocks {
390 let kv = r.narrow(1, 0, train_size)?;
391 r = mha_block(&r, &kv, &kv, blk, self.config.icl_nhead, None, train_size)?;
392 }
393 let r = r.squeeze(0)?; let r = zsfm_nn::layer_norm(&r, &self.icl.ln_w, &self.icl.ln_b, LN_EPS)?;
395 let h = zsfm_nn::linear_bias(&r, &self.icl.decoder_fc1_w, &self.icl.decoder_fc1_b)?.gelu_erf()?;
396 let logits = zsfm_nn::linear_bias(&h, &self.icl.decoder_fc2_w, &self.icl.decoder_fc2_b)?; logits.narrow(0, train_size, t - train_size)?.narrow(1, 0, n_classes).map_err(anyhow::Error::from)
398 }
399}
400
401fn isab_forward(src: &Tensor, isab: &IsabW, n_heads: usize, train_size: usize, n_h: usize) -> Result<Tensor> {
405 let num_inds = isab.ind_vectors.dim(0)?;
406 let dim = isab.ind_vectors.dim(1)?;
407 let ind = isab.ind_vectors.unsqueeze(0)?.expand((n_h, num_inds, dim))?.contiguous()?;
408
409 let kv_train = src.narrow(1, 0, train_size)?;
410 let hidden = mha_block(&ind, &kv_train, &kv_train, &isab.attn1, n_heads, None, train_size)?;
411 mha_block(src, &hidden, &hidden, &isab.attn2, n_heads, None, num_inds)
412}
413
414fn mha_block(
419 q_in: &Tensor,
420 k_in: &Tensor,
421 v_in: &Tensor,
422 blk: &BlockW,
423 n_heads: usize,
424 rope_cos_sin: Option<(&Tensor, &Tensor)>,
425 ssmax_n: usize,
426) -> Result<Tensor> {
427 let q_normed = zsfm_nn::layer_norm(q_in, &blk.norm1_w, &blk.norm1_b, LN_EPS)?;
428 let k_normed = zsfm_nn::layer_norm(k_in, &blk.norm1_w, &blk.norm1_b, LN_EPS)?;
429 let v_normed = zsfm_nn::layer_norm(v_in, &blk.norm1_w, &blk.norm1_b, LN_EPS)?;
430 let attn_out = mha_combined(&q_normed, &k_normed, &v_normed, &blk.attn, n_heads, rope_cos_sin, ssmax_n)?;
431 let x = (q_in + attn_out)?;
432 let ff_in = zsfm_nn::layer_norm(&x, &blk.norm2_w, &blk.norm2_b, LN_EPS)?;
433 let ff = zsfm_nn::linear_bias(&zsfm_nn::linear_bias(&ff_in, &blk.fc1_w, &blk.fc1_b)?.gelu_erf()?, &blk.fc2_w, &blk.fc2_b)?;
434 Ok((x + ff)?)
435}
436
437fn mha_combined(
442 q_in: &Tensor,
443 k_in: &Tensor,
444 v_in: &Tensor,
445 attn: &AttnW,
446 n_heads: usize,
447 rope_cos_sin: Option<(&Tensor, &Tensor)>,
448 ssmax_n: usize,
449) -> Result<Tensor> {
450 let dim = q_in.dim(2)?;
451 let wq = attn.in_proj_w.narrow(0, 0, dim)?;
452 let wk = attn.in_proj_w.narrow(0, dim, dim)?;
453 let wv = attn.in_proj_w.narrow(0, 2 * dim, dim)?;
454 let bq = attn.in_proj_b.narrow(0, 0, dim)?;
455 let bk = attn.in_proj_b.narrow(0, dim, dim)?;
456 let bv = attn.in_proj_b.narrow(0, 2 * dim, dim)?;
457
458 let q = zsfm_nn::linear_bias(q_in, &wq, &bq)?;
459 let k = zsfm_nn::linear_bias(k_in, &wk, &bk)?;
460 let v = zsfm_nn::linear_bias(v_in, &wv, &bv)?;
461
462 let batch = q.dim(0)?;
463 let q_len = q.dim(1)?;
464 let k_len = k.dim(1)?;
465 let head_dim = dim / n_heads;
466
467 let q = q.reshape((batch, q_len, n_heads, head_dim))?.permute((0, 2, 1, 3))?.contiguous()?;
468 let k = k.reshape((batch, k_len, n_heads, head_dim))?.permute((0, 2, 1, 3))?.contiguous()?;
469 let v = v.reshape((batch, k_len, n_heads, head_dim))?.permute((0, 2, 1, 3))?.contiguous()?;
470
471 let (q, k) = if let Some((cos, sin)) = rope_cos_sin {
472 (apply_rope(&q, cos, sin)?, apply_rope(&k, cos, sin)?)
473 } else {
474 (q, k)
475 };
476
477 let q = if let Some(ssmax) = &attn.ssmax {
478 apply_ssmax(&q, ssmax, ssmax_n, n_heads, head_dim)?
479 } else {
480 q
481 };
482
483 let scale = 1.0 / (head_dim as f64).sqrt();
484 let scores = (q.matmul(&k.transpose(2, 3)?)? * scale)?;
485 let probs = candle_nn::ops::softmax_last_dim(&scores)?;
486 let out = probs.matmul(&v)?; let out = out.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch, q_len, dim))?;
488 zsfm_nn::linear_bias(&out, &attn.out_proj_w, &attn.out_proj_b).map_err(anyhow::Error::from)
489}
490
491fn apply_rope(x: &Tensor, cos: &Tensor, sin: &Tensor) -> Result<Tensor> {
496 let seq = x.dim(2)?;
497 let head_dim = x.dim(3)?;
498 let half = head_dim / 2;
499 let cos = cos.narrow(0, 0, seq)?.unsqueeze(0)?.unsqueeze(0)?; let sin = sin.narrow(0, 0, seq)?.unsqueeze(0)?.unsqueeze(0)?;
501 let x1 = x.narrow(3, 0, half)?;
502 let x2 = x.narrow(3, half, half)?;
503 let rotated = Tensor::cat(&[&x2.neg()?, &x1], 3)?;
504 Ok((x.broadcast_mul(&cos)? + rotated.broadcast_mul(&sin)?)?)
505}
506
507fn apply_ssmax(q: &Tensor, ssmax: &SsmaxW, n: usize, n_heads: usize, head_dim: usize) -> Result<Tensor> {
510 let device = q.device();
511 let logn = (n.max(1) as f32).ln();
512 let logn_t = Tensor::from_vec(vec![logn], (1, 1), device)?;
513 let base = zsfm_nn::linear_bias(&logn_t, &ssmax.base_fc1_w, &ssmax.base_fc1_b)?.gelu_erf()?;
514 let base = zsfm_nn::linear_bias(&base, &ssmax.base_fc2_w, &ssmax.base_fc2_b)?; let base = base.reshape((1, n_heads, 1, head_dim))?;
516
517 let (batch, seq) = (q.dim(0)?, q.dim(2)?);
518 let q_flat = q.reshape((batch * n_heads * seq, head_dim))?;
519 let qm = zsfm_nn::linear_bias(&q_flat, &ssmax.query_fc1_w, &ssmax.query_fc1_b)?.gelu_erf()?;
520 let qm = zsfm_nn::linear_bias(&qm, &ssmax.query_fc2_w, &ssmax.query_fc2_b)?;
521 let modulation = (qm.tanh()? + 1.0)?.reshape((batch, n_heads, seq, head_dim))?;
522
523 let scales = base.broadcast_mul(&modulation)?;
524 Ok(q.broadcast_mul(&scales)?)
525}
526
527fn softmax(logits: &[f32]) -> Vec<f32> {
528 let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
529 let exps: Vec<f32> = logits.iter().map(|&v| (v - max).exp()).collect();
530 let sum: f32 = exps.iter().sum();
531 exps.into_iter().map(|v| v / sum).collect()
532}
533
534fn preprocess_x(rows: &[Vec<f32>], train_size: usize) -> Vec<Vec<f32>> {
541 let n_feat = rows[0].len();
542 let mut impute_mean = vec![0f32; n_feat];
543 for f in 0..n_feat {
544 let mut sum = 0f64;
545 let mut count = 0usize;
546 for row in &rows[..train_size] {
547 if !row[f].is_nan() {
548 sum += row[f] as f64;
549 count += 1;
550 }
551 }
552 impute_mean[f] = if count > 0 { (sum / count as f64) as f32 } else { 0.0 };
553 }
554 let imputed: Vec<Vec<f32>> = rows
555 .iter()
556 .map(|row| row.iter().enumerate().map(|(f, &v)| if v.is_nan() { impute_mean[f] } else { v }).collect())
557 .collect();
558
559 let mut mean = vec![0f32; n_feat];
560 let mut std = vec![0f32; n_feat];
561 for f in 0..n_feat {
562 let m: f32 = imputed[..train_size].iter().map(|r| r[f]).sum::<f32>() / train_size as f32;
563 let var: f32 = imputed[..train_size].iter().map(|r| (r[f] - m).powi(2)).sum::<f32>() / train_size as f32;
564 mean[f] = m;
565 std[f] = if var == 0.0 { 1.0 } else { var.sqrt() };
566 }
567
568 imputed.iter().map(|row| row.iter().enumerate().map(|(f, &v)| (v - mean[f]) / std[f]).collect()).collect()
569}
570
571fn feature_group_same(rows: &[Vec<f32>], h: usize, size: usize) -> Vec<Vec<Vec<f32>>> {
575 rows.iter()
576 .map(|row| {
577 (0..h)
578 .map(|g| (0..size).map(|k| row[(g + (1usize << k)) % h]).collect())
579 .collect()
580 })
581 .collect()
582}