← 返回数学手册

public/math/training.mjs

与本次数学页面一起发布的源码快照。SHA-256: b8db8cb6882aa0eb396960da6d96c852f2ac9a4744b956a53858db7197003b12

下载原始文件 · 版本指纹清单

1// A real, tiny full-batch SGD experiment. Analytic derivatives, fixed synthetic
2// targets, one camera, three times, two STG-Lite splats. No prerecorded losses.
3import { initialRecords, forward, gradient } from './trace.mjs';
4
5export const trainingSpec = Object.freeze({ width:12, height:10, left:68, top:63, times:[0.25,0.5,0.75] });
6export const trainingParameters = [0,8,20]; // A: mean x, linear motion x, opacity logit
7export const trainingNames = ['μ₀x','m₁x','β'];
8export const copyRecords = rows => rows.map(row=>[...row]);
9
10export function createTrainingData() {
11  const truth=initialRecords();
12  truth[0][0]=1.12; truth[0][8]=0.8; truth[0][20]=0.2;
13  const {width,height,left,top,times}=trainingSpec;
14  return times.map(time=>({time,samples:Array.from({length:width*height},(_,i)=>{
15    const pixel=[left+i%width+0.5,top+Math.floor(i/width)+0.5];
16    return {pixel,target:forward(truth,time,pixel).color};
17  })}));
18}
19
20export function evaluateTraining(records, data, withGradient=false) {
21  let loss=0; const gradients=[0,0,0],frames=[];
22  const count=data.reduce((n,frame)=>n+frame.samples.length,0);
23  for(const frame of data) {
24    const pixels=[];
25    for(const sample of frame.samples) {
26      const options={target:sample.target,finite:false};
27      const position=withGradient?gradient(records,0,frame.time,sample.pixel,options):null;
28      const result=position?.result??forward(records,frame.time,sample.pixel,options);
29      loss+=result.loss/count;
30      if(withGradient) {
31        gradients[0]+=position.analytic/count;
32        gradients[1]+=position.analytic*(frame.time-records[0][3])/count;
33        gradients[2]+=gradient(records,20,frame.time,sample.pixel,options).analytic/count;
34      }
35      pixels.push(result);
36    }
37    frames.push(pixels);
38  }
39  return {loss,gradients,frames};
40}
41
42export function trainingIteration(records, data, rate=1) {
43  if(!Number.isFinite(rate)||rate<=0)throw new RangeError('Learning rate must be positive');
44  const beforeRecords=copyRecords(records),before=evaluateTraining(beforeRecords,data,true);
45  const afterRecords=copyRecords(beforeRecords);
46  // All gradients come from the same pre-update batch; commit simultaneously.
47  trainingParameters.forEach((field,j)=>{afterRecords[0][field]-=rate*before.gradients[j];});
48  const after=evaluateTraining(afterRecords,data);
49  if(!Number.isFinite(after.loss)||!afterRecords.flat().every(Number.isFinite))throw new Error('Non-finite training state');
50  return {beforeRecords,before,afterRecords,after,rate};
51}
52
53// Moving through explanatory stages never repeats an update. Only the last
54// stage commits; rewinding from it restores the exact pre-update snapshot.
55export class TrainingSession {
56  constructor(rate=1) { this.data=createTrainingData(); this.rate=rate; this.reset(); }
57  reset() {
58    this.records=initialRecords(); this.stage=0; this.iteration=0;
59    this.history=[evaluateTraining(this.records,this.data).loss]; this.pending=null;
60  }
61  prepare() { return this.pending??=trainingIteration(this.records,this.data,this.rate); }
62  next() {
63    if(this.stage===7) { this.pending=null;this.stage=0;return; }
64    const trace=this.prepare(); this.stage++;
65    if(this.stage===7) {
66      this.records=copyRecords(trace.afterRecords);this.iteration++;
67      this.history.push(trace.after.loss);
68    }
69  }
70  previous() {
71    if(this.stage===0)return;
72    if(this.stage===7) {
73      this.records=copyRecords(this.pending.beforeRecords);this.iteration--;this.history.pop();
74    }
75    this.stage--;
76  }
77}
78