CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
generate-trackio-data.mjs197 linesDownload Raw Back to scripts
1#!/usr/bin/env node2 3// Generate synthetic Trackio-like CSV data with realistic ML curves.4// - Steps are simple integers (e.g., 1..N)5// - Metrics: epoch, train_accuracy, val_accuracy, train_loss, val_loss6// - W&B-like run names (e.g., pleasant-flower-1)7// - Deterministic with --seed8//9// Usage:10//   node app/scripts/generate-trackio-data.mjs \11//     --runs 3 \12//     --steps 10 \13//     --out app/src/content/assets/data/trackio_wandb_synth.csv \14//     [--seed 42] [--epoch-max 3.0] [--amount 1.0] [--start 1]15//16// To overwrite the demo file used by the embed:17//   node app/scripts/generate-trackio-data.mjs --runs 3 --steps 10 --out app/src/content/assets/data/trackio_wandb_demo.csv --seed 133718 19import fs from 'node:fs/promises';20import path from 'node:path';21 22function parseArgs(argv){23  const args = { runs: 3, steps: 10, out: '', seed: undefined, epochMax: 3.0, amount: 1, start: 1 };24  for (let i = 2; i < argv.length; i++){25    const a = argv[i];26    if (a === '--runs' && argv[i+1]) { args.runs = Math.max(1, parseInt(argv[++i], 10) || 3); continue; }27    if (a === '--steps' && argv[i+1]) { args.steps = Math.max(2, parseInt(argv[++i], 10) || 10); continue; }28    if (a === '--out' && argv[i+1]) { args.out = argv[++i]; continue; }29    if (a === '--seed' && argv[i+1]) { args.seed = Number(argv[++i]); continue; }30    if (a === '--epoch-max' && argv[i+1]) { args.epochMax = Number(argv[++i]) || 3.0; continue; }31    if (a === '--amount' && argv[i+1]) { args.amount = Number(argv[++i]) || 1.0; continue; }32    if (a === '--start' && argv[i+1]) { args.start = parseInt(argv[++i], 10) || 1; continue; }33  }34  if (!args.out) {35    args.out = path.join('app', 'src', 'content', 'assets', 'data', 'trackio_wandb_synth.csv');36  }37  return args;38}39 40function mulberry32(seed){41  let t = seed >>> 0;42  return function(){43    t += 0x6D2B79F5;44    let r = Math.imul(t ^ (t >>> 15), 1 | t);45    r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);46    return ((r ^ (r >>> 14)) >>> 0) / 4294967296;47  };48}49 50function makeRng(seed){51  if (Number.isFinite(seed)) return mulberry32(seed);52  return Math.random;53}54 55function randn(rng){56  // Box-Muller transform57  let u = 0, v = 0;58  while (u === 0) u = rng();59  while (v === 0) v = rng();60  return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);61}62 63function clamp(x, lo, hi){64  return Math.max(lo, Math.min(hi, x));65}66 67function logistic(t, k=6, x0=0.5){68  // 1 / (1 + e^{-k (t - x0)}) in [0,1]69  return 1 / (1 + Math.exp(-k * (t - x0)));70}71 72function expDecay(t, k=3){73  // (1 - e^{-k t}) in [0,1]74  return 1 - Math.exp(-k * t);75}76 77function pick(array, rng){78  return array[Math.floor(rng() * array.length) % array.length];79}80 81function buildRunNames(count, rng){82  const adjectives = [83    'pleasant','brisk','silent','ancient','bold','gentle','rapid','shy','curious','lively',84    'fearless','soothing','glossy','hidden','misty','bright','calm','keen','noble','swift'85  ];86  const nouns = [87    'flower','glade','sky','river','forest','ember','comet','meadow','harbor','dawn',88    'mountain','prairie','breeze','valley','lagoon','desert','monsoon','reef','thunder','willow'89  ];90  const names = new Set();91  let attempts = 0;92  while (names.size < count && attempts < count * 20){93    attempts++;94    const left = pick(adjectives, rng);95    const right = pick(nouns, rng);96    const idx = 1 + Math.floor(rng() * 9);97    names.add(`${left}-${right}-${idx}`);98  }99  return Array.from(names);100}101 102function formatLike(value, decimals){103  return Number.isFinite(decimals) && decimals >= 0 ? value.toFixed(decimals) : String(value);104}105 106async function main(){107  const args = parseArgs(process.argv);108  const rng = makeRng(args.seed);109 110  // Steps: integers from start .. start+steps-1111  const steps = Array.from({ length: args.steps }, (_, i) => args.start + i);112  const stepNorm = (i) => (i - steps[0]) / (steps[steps.length-1] - steps[0]);113 114  const runs = buildRunNames(args.runs, rng);115 116  // Per-run slight variations117  const runParams = runs.map((_r, idx) => {118    const r = rng();119    // Final accuracies120    const trainAccFinal = clamp(0.86 + (r - 0.5) * 0.12 * args.amount, 0.78, 0.97);121    const valAccFinal = clamp(trainAccFinal - (0.02 + rng() * 0.05), 0.70, 0.95);122    // Loss plateau123    const lossStart = 7.0 + (rng() - 0.5) * 0.10 * args.amount; // ~7.0 ±0.05124    const lossPlateau = 6.78 + (rng() - 0.5) * 0.04 * args.amount; // ~6.78 ±0.02125    const lossK = 2.0 + rng() * 1.5; // decay speed126    // Acc growth steepness and midpoint127    const kAcc = 4.5 + rng() * 3.0;128    const x0Acc = 0.35 + rng() * 0.25;129    return { trainAccFinal, valAccFinal, lossStart, lossPlateau, lossK, kAcc, x0Acc };130  });131 132  const lines = [];133  lines.push('run,step,metric,value,stderr');134 135  // EPOCH: linear 0..epochMax across steps136  for (let r = 0; r < runs.length; r++){137    const run = runs[r];138    for (let i = 0; i < steps.length; i++){139      const t = stepNorm(steps[i]);140      const epoch = args.epochMax * t;141      lines.push(`${run},${steps[i]},epoch,${formatLike(epoch, 2)},`);142    }143  }144 145  // TRAIN LOSS & VAL LOSS146  for (let r = 0; r < runs.length; r++){147    const run = runs[r];148    const p = runParams[r];149    let prevTrain = null;150    let prevVal = null;151    for (let i = 0; i < steps.length; i++){152      const t = stepNorm(steps[i]);153      const d = expDecay(t, p.lossK); // 0..1154      let trainLoss = p.lossStart - (p.lossStart - p.lossPlateau) * d;155      let valLoss = trainLoss + 0.02 + (rng() * 0.03);156      // Add mild noise157      trainLoss += randn(rng) * 0.01 * args.amount;158      valLoss += randn(rng) * 0.012 * args.amount;159      // Keep reasonable and mostly monotonic (small upward blips allowed)160      if (prevTrain != null) trainLoss = Math.min(prevTrain + 0.01, trainLoss);161      if (prevVal != null) valLoss = Math.min(prevVal + 0.012, valLoss);162      prevTrain = trainLoss; prevVal = valLoss;163      const stderrTrain = clamp(0.03 - 0.02 * t + Math.abs(randn(rng)) * 0.003, 0.006, 0.04);164      const stderrVal = clamp(0.035 - 0.022 * t + Math.abs(randn(rng)) * 0.003, 0.008, 0.045);165      lines.push(`${run},${steps[i]},train_loss,${formatLike(trainLoss, 3)},${formatLike(stderrTrain, 3)}`);166      lines.push(`${run},${steps[i]},val_loss,${formatLike(valLoss, 3)},${formatLike(stderrVal, 3)}`);167    }168  }169 170  // TRAIN ACCURACY & VAL ACCURACY (logistic)171  for (let r = 0; r < runs.length; r++){172    const run = runs[r];173    const p = runParams[r];174    for (let i = 0; i < steps.length; i++){175      const t = stepNorm(steps[i]);176      const accBase = logistic(t, p.kAcc, p.x0Acc);177      let trainAcc = clamp(0.55 + accBase * (p.trainAccFinal - 0.55), 0, 1);178      let valAcc = clamp(0.52 + accBase * (p.valAccFinal - 0.52), 0, 1);179      // Gentle noise180      trainAcc = clamp(trainAcc + randn(rng) * 0.005 * args.amount, 0, 1);181      valAcc = clamp(valAcc + randn(rng) * 0.006 * args.amount, 0, 1);182      const stderrTrain = clamp(0.02 - 0.011 * t + Math.abs(randn(rng)) * 0.002, 0.006, 0.03);183      const stderrVal = clamp(0.022 - 0.012 * t + Math.abs(randn(rng)) * 0.002, 0.007, 0.032);184      lines.push(`${run},${steps[i]},train_accuracy,${formatLike(trainAcc, 4)},${formatLike(stderrTrain, 3)}`);185      lines.push(`${run},${steps[i]},val_accuracy,${formatLike(valAcc, 4)},${formatLike(stderrVal, 3)}`);186    }187  }188 189  // Ensure directory exists190  await fs.mkdir(path.dirname(args.out), { recursive: true });191  await fs.writeFile(args.out, lines.join('\n') + '\n', 'utf8');192  const relOut = path.relative(process.cwd(), args.out);193  console.log(`Synthetic CSV generated: ${relOut}`);194}195 196main().catch(err => { console.error(err?.stack || String(err)); process.exit(1); });197