dumb by default


dir: Home / Applets / LeNet5 demo
published-date: 31 May 2025 14:25 +0700

LeNet5 demo


LeNet5 is one of few small image (to number) classification model. So small that this were made because of that. No more than 600 SLOC, all runs on cpu.

This model were trained using the following colab notebook that i wrote which exports layers weight and bias as json for easy import. Weight is stored locally and can be accessed in the following link (1.34MB).

Try it out yourself by drawing a number in the following canvas, then scroll down to see model inference result.

Note: does not perform well inferring the number 8, and distinguishing 1 and 7.


output

  1window.onload = function()
  2{
  3
  4const cvIn = document.getElementById("Input");
  5const cvInCtx = cvIn.getContext("2d", {"alpha":false});
  6const cvPt = document.getElementById("Viewport");
  7const cvPtCtx = cvPt.getContext("2d", {"alpha":false});
  8const ftoi = (v) => ~~v;
  9var cursorHandler = null;
 10var data = null;
 11
 12
 13/*  cursor handler:
 14    tracks monitor and handle mouse/cursor events
 15*/
 16
 17class cCursorHandler {
 18  flags = {'idle': 0, 'move': 1, 'down': 2};
 19  constructor (el, cb=(event)=>{}) {
 20    this.el = el;
 21    this.cb = cb;
 22    this.w = el.width;
 23    this.h = el.height;
 24    this.flag = this.flags.idle;
 25    this.pos = {};
 26    this.pos.curr = {x:0, y:0};
 27    this.pos.prev = {x:0, y:0};
 28    this.ratio = {};
 29    this.ratio.curr = {x:0, y:0};
 30    this.ratio.prev = {x:0, y:0};
 31    this.handleMove  = (event)=>{this.update(event); this.flag|=this.flags.move; this.cb(this)};
 32    this.handleDown  = (event)=>{this.update(event); this.update(event); this.flag|=this.flags.down; this.cb(this)};
 33    this.handleUp    = (event)=>{this.update(event); this.flag=this.flags.idle;  this.cb(this);};
 34    this.el.addEventListener("pointermove",   this.handleMove);
 35    this.el.addEventListener("pointerdown",   this.handleDown);
 36    this.el.addEventListener("pointerup",     this.handleUp);
 37    this.el.addEventListener("pointercancel", this.handleUp);
 38  };
 39  update (event) {
 40    let bcr = this.el.getBoundingClientRect();
 41    this.pos.prev.x = this.pos.curr.x;
 42    this.pos.prev.y = this.pos.curr.y;
 43    this.ratio.prev.x = this.ratio.curr.x;
 44    this.ratio.prev.y = this.ratio.curr.y;
 45    this.ratio.curr.x = (event.clientX - bcr.left) / bcr.width;
 46    this.ratio.curr.y = (event.clientY - bcr.top) / bcr.height;
 47    this.pos.curr.x = ftoi(this.ratio.curr.x * this.w);
 48    this.pos.curr.y = ftoi(this.ratio.curr.y * this.h);
 49  };
 50  detach () {
 51    this.el.removeEventListener("pointermove",   this.handleMove);
 52    this.el.removeEventListener("pointerdown",   this.handleDown);
 53    this.el.removeEventListener("pointerup",     this.handleUp);
 54    this.el.removeEventListener("pointercancel", this.handleUp);
 55  };
 56}
 57
 58
 59/*  Interp
 60    namespace for interpolation methods
 61*/
 62
 63class Interp {
 64
 65  static _bi_loop(aw, ah, bw, bh, cb) {
 66    let cx, cy, rx, ry, ax, ay, bx, by, axl, axh, ayl, ayh;
 67    rx = (aw-2) / (bw-1);
 68    ry = (ah-2) / (bh-1);
 69    for (bx=0; bx<bw; bx++) {
 70      for (by=0; by<bh; by++) {
 71        axl = Math.floor(rx * bx);
 72        axh = Math.ceil (rx * bx);
 73        ayl = Math.floor(ry * by);
 74        ayh = Math.ceil (ry * by);
 75        cb(
 76          bx, by,
 77          axl, axh, ayl, ayh,
 78          rx*bx-axl, ry*by-ayl
 79        )
 80      }
 81    }
 82  };
 83
 84  static _bi_inv(ms, md, bi_method) {
 85    Interp._bi_loop(ms.w, ms.h, md.w, md.h,
 86      (x, y, xl, xh, yl, yh, xt, yt)=>{
 87        if (x<0 || x>=md.w || y<0 || y>=md.h) return;
 88        if (xl<0 || xl>=ms.w || yl<0 || yl>=ms.h) return;
 89        if (xh<0 || xh>=ms.w || yh<0 || yh>=ms.h) return;
 90        md.set(x, y, bi_method(
 91          ms.get(xl, yl), ms.get(xh, yl),
 92          ms.get(xl, yh), ms.get(xh, yh),
 93          xt, yt
 94        ));
 95      }
 96    );
 97  };
 98
 99  static nearest(a, b, t) {
100    return t >= .5 ? b : a;
101  };
102
103  static binearest(a1, b1, a2, b2, t1, t2) {
104    return Interp.nearest(
105      Interp.nearest(a1, b1, t1),
106      Interp.nearest(a2, b2, t1),
107      t2
108    )
109  };
110
111  static binearest_m(ms, md) {Interp._bi_inv(ms, md, Interp.binearest);}
112
113  static linear(a, b, t) {
114    return a + (b - a) * t
115  };
116
117  static bilinear(a1, b1, a2, b2, t1, t2) {
118    return Interp.linear(
119      Interp.linear(a1, b1, t1),
120      Interp.linear(a2, b2, t1),
121      t2
122    )
123  };
124
125  static bilinear_m(ms, md) {Interp._bi_inv(ms, md, Interp.bilinear)}
126}
127
128
129/*  ==================== CNN Stuffs ==================== */
130
131/*  Float32Matrix
132    butchered 2d matrix implementation
133*/
134
135class cFloat32Matrix {
136  constructor (w, h) {
137    this.w = w; this.h = h; this.l = w*h;
138    this.buf = new Float32Array(this.l);
139  }
140  itox(i) {return i % this.w};
141  itoy(i) {return ftoi(i / this.w)};
142  ctoi(x, y) {return x + (y * this.w)};
143  get(x, y) {return this.buf[this.ctoi(x, y)]};
144  set(x, y, v) {this.buf[this.ctoi(x, y)] = v;};
145  add(v) {for (let i=0; i<this.l; i++){this.buf[i] = this.buf[i] + v}};
146  mul(v) {for (let i=0; i<this.l; i++){this.buf[i] = this.buf[i] * v}};
147  map(cb) {for (let i=0; i<this.l; i++){this.buf[i] = cb(this.buf[i])}};
148  zeros() {for (let i=0; i<this.buf.length; i++) {this.buf[i] = 0}};
149  getMin() {return Math.min(...this.buf)};
150  getMax() {return Math.max(...this.buf)};
151  load2DArray(arr) {
152    for (let x=0; x<this.w; x++) {
153      for (let y=0; y<this.h; y++) {
154        this.set(x, y, arr[y][x]);
155      }
156    }
157  };
158  loadImageData(data) {
159    for (let i=0; i<data.length; i+=4) {
160      this.buf[ftoi(i/4)] = (data[i+0] + data[i+1] + data[i+2]) / 3 / 255;
161    }
162  };
163  unloadImageData(data) {
164    for (let i=0; i<data.length; i+=4) {
165      let p = this.buf[ftoi(i/4)];
166      p = p > 1 ? 1 : p;
167      p = p < 0 ? 0 : p;
168      p = ftoi(p * 255);
169      data[i+0] = p;
170      data[i+1] = p;
171      data[i+2] = p;
172    }
173  };
174  upscaleDilate(m) {
175    let scale = ftoi(m.w / this.w);
176    for (let x=0; x<this.w; x++) {
177      for (let y=0; y<this.h; y++) {
178        for (let i=0; i<scale; i++) {
179          for (let j=0; j<scale; j++) {
180            m.set((x*scale)+i, (y*scale)+j, this.get(x, y));
181          }
182        }
183      }
184    }
185  };
186}
187
188
189/*  Additional: activation functions
190*/
191
192function Sigmoid(v) {return 1 / (1+Math.exp(-v))};
193function ReLU(v) {return v<0?0:v};
194
195
196/*  Conv2D
197*/
198
199function _conv2dsize(inputSize, padding, kernelSize, stride) {
200  return Math.floor((inputSize + (padding*2) - (kernelSize - 1) - 1) / stride + 1)
201};
202
203
204function conv2d_h(src_w, src_h, dst_w, dst_h, kw, kh, px, py, sx, sy, cb) {
205  if (dst_w !== _conv2dsize(src_w, px, kw, sx)) { console.error(dst_w, _conv2dsize(src_w, px, kw, sx)); throw "Invalid Output Matrix Size" }
206  if (dst_h !== _conv2dsize(src_h, py, kh, sy)) { console.error(dst_h, _conv2dsize(src_h, py, kh, sy)); throw "Invalid Output Matrix Size" }
207  let dx, dy, kx, ky;
208  for (dx=0; dx<dst_w; dx++) {for (dy=0; dy<dst_h; dy++) {
209  for (kx=0; kx<kw; kx++) {for (ky=0; ky<kh; ky++) {
210    cb(dx, dy, kx, ky, (dx*sx)+kx-(px||0), (dy*sy)+ky-(py||0));
211  }}}};
212}
213
214
215class CNNConv2D {
216  constructor(kernel, stride, padding) {
217    this.kernel = kernel;
218    this.stride = stride || 1;
219    this.padding = padding || 0;
220  };
221
222  forward(ms, md) {
223    conv2d_h(ms.w, ms.h, md.w, md.h, this.kernel[0].length, this.kernel.length,
224    this.padding, this.padding, this.stride, this.stride,
225    (dx, dy, kx, ky, sx, sy)=>{
226      md.set(dx, dy, md.get(dx, dy) + (ms.get(sx, sy) || 0) * this.kernel[ky][kx])
227    })
228  };
229};
230
231
232/*  MaxPool2D
233*/
234
235class CNNMaxPool2D {
236  constructor(w, h, stride, padding) {
237    this.w = w;
238    this.h = h;
239    this.stride = stride || 1;
240    this.padding = padding || 0;
241  };
242
243  forward(ms, md) {
244    md.zeros();
245    md.add(-1e31);
246    conv2d_h(ms.w, ms.h, md.w, md.h, this.w, this.h,
247    this.padding, this.padding, this.stride, this.stride,
248    (dx, dy, kx, ky, sx, sy)=>{
249      md.set(dx, dy, Math.max(md.get(dx, dy), (ms.get(sx, sy) || 0)))
250    })
251  };
252};
253
254
255/*  Linear
256*/
257
258class Linear {
259  constructor (A) {
260    this.A = A;
261  };
262
263  forward(ms, md) {
264    for (let x=0; x<this.A[0].length; x++) {
265      for (let y=0; y<this.A.length; y++) {
266        md.buf[y] += ms.buf[x] * this.A[y][x];
267      }
268    }
269  };
270};
271
272
273/*  ==================== Canvas routines ==================== */
274
275function canvasSubRoutineDrawDot(x, y) {
276  cvInCtx.fillStyle = "white";
277  cvInCtx.filter = "blur(5px)";
278  cvInCtx.beginPath();
279  cvInCtx.arc(x, y, 14, 0, 2 * Math.PI);
280  cvInCtx.fill();
281}
282
283
284function canvasSubRoutineDrawLine(x1, y1, x2, y2) {
285  cvInCtx.strokeStyle = "white";
286  cvInCtx.filter = "blur(5px)";
287  cvInCtx.lineWidth = 28;
288  cvInCtx.lineCap = "round";
289  cvInCtx.beginPath();
290  cvInCtx.moveTo(x1, y1);
291  cvInCtx.lineTo(x2, y2);
292  cvInCtx.stroke();
293}
294
295
296function canvasSubRoutineDrawText(x, y, text, align) {
297  cvPtCtx.save();
298  cvPtCtx.fillStyle = "black";
299  cvPtCtx.font = "32px monospace";
300  cvPtCtx.textBaseline = "middle";
301  cvPtCtx.textAlign = align || "left";
302  cvPtCtx.fillText(text, x, y);
303}
304
305
306function canvasSubRoutineDrawMatrix(mat_src, mat_tmp, x, y, normalize) {
307  const imd = cvPtCtx.getImageData(x, y, mat_tmp.w, mat_tmp.h);
308  Interp.binearest_m(mat_src, mat_tmp)
309  if (normalize || true) {
310    let mi = mat_tmp.getMin();
311    let ma = mat_tmp.getMax();
312    mat_tmp.add(-mi);
313    mat_tmp.mul(1/(ma-mi));
314  }
315  mat_tmp.unloadImageData(imd.data);
316  cvPtCtx.putImageData(imd, x, y);
317  cvPtCtx.strokeStyle = "blue";
318  cvPtCtx.rect(x-2,y-2,mat_tmp.w+4, mat_tmp.h+4);
319  cvPtCtx.stroke();
320}
321
322
323function canvasSubRoutineDrawDense(mat_src, x, y, w, h, draw_text) {
324  cvPtCtx.save()
325  let r = w / mat_src.w;
326  cvPtCtx.fillStyle = "white";
327  cvPtCtx.strokeStyle = "black";
328  cvPtCtx.beginPath();
329  cvPtCtx.rect(x, y, w, h)
330  cvPtCtx.fill()
331  for (let i=0; i<mat_src.w; i++) {
332    cvPtCtx.rect(x+r*i,y,r,h);
333  };
334  cvPtCtx.stroke()
335  for (let i=0; i<mat_src.w; i++) {
336    let hh = h*(1-mat_src.buf[i])
337    cvPtCtx.fillStyle = "blue";
338    cvPtCtx.fillRect(x+r*i,y+hh,r,h-hh);
339    if (draw_text || false) {
340      canvasSubRoutineDrawText(x+r*i+r/2, y+h+32, "" + i, 'center');
341    }
342  };
343  cvPtCtx.restore()
344};
345
346
347function netDrawAnnotation() {
348  canvasSubRoutineDrawText(150, 30,         "input(512x512)", "center")
349  canvasSubRoutineDrawText(150, 60,         "downsamp(28x28)", "center")
350  canvasSubRoutineDrawText(300, 80-20,      "C1: Conv2D 5x5pad2 (28x28) x6 (Sigmoid)", "left")
351  canvasSubRoutineDrawText(300, 260-20,     "S2: MaxPool 2x2stride2 (14x14) x6", "left")
352  canvasSubRoutineDrawText(30,  470-20,     "C3: Conv2D 5x5 (10x10) x16 (Sigmoid)", "left")
353  canvasSubRoutineDrawText(30,  800-20,     "S4: MaxPool 2x2stride2 (5x5) x16", "left")
354  canvasSubRoutineDrawText(30,  1076+260*0, "C5: Conv2D 5x5 (1x1) x120 Flatten (Sigmoid)", "left")
355  canvasSubRoutineDrawText(30,  1076+260*1, "F6: Linear (84x120) (Sigmoid)", "left")
356  canvasSubRoutineDrawText(30,  1076+260*2, "F0: Linear (10x84) (Sigmoid)", "left")
357};
358
359
360function appInCanvasReset() {
361  cvInCtx.fillStyle = "black";
362  cvInCtx.clearRect(0,0,cvIn.width,cvIn.height);
363  cvInCtx.fillRect(0,0,cvIn.width,cvIn.height);
364  cvPtCtx.save();
365  cvPtCtx.fillStyle = "white";
366  cvPtCtx.clearRect(0,0,cvPt.width,cvPt.height);
367  cvPtCtx.fillRect(0,0,cvPt.width,cvPt.height);
368  netDrawAnnotation();
369};
370
371
372/*  temporary buffers
373*/
374
375const tmp_buf = {};
376// tmp_buf.m2032 = new cFloat32Matrix(32, 32);
377// tmp_buf.m2048 = new cFloat32Matrix(48, 48);
378// tmp_buf.m2064 = new cFloat32Matrix(64, 64);
379tmp_buf.m2080 = new cFloat32Matrix(80, 80);
380tmp_buf.m2100 = new cFloat32Matrix(100, 100);
381tmp_buf.m2112 = new cFloat32Matrix(112, 112);
382// tmp_buf.m2140 = new cFloat32Matrix(140, 140);
383tmp_buf.m2120 = new cFloat32Matrix(120, 120);
384// tmp_buf.m2160 = new cFloat32Matrix(160, 160);
385tmp_buf.m2240 = new cFloat32Matrix(240, 240);
386// tmp_buf.m2028 = new cFloat32Matrix(28, 28);
387// tmp_buf.m2014 = new cFloat32Matrix(14, 14);
388// tmp_buf.m2005 = new cFloat32Matrix(5, 5);
389// tmp_buf.m2002 = new cFloat32Matrix(2, 2);
390tmp_buf.m2001 = new cFloat32Matrix(1, 1);
391// tmp_buf.m1120 = new cFloat32Matrix(120, 1);
392// tmp_buf.m1084 = new cFloat32Matrix(84, 1);
393// tmp_buf.m1010 = new cFloat32Matrix(10, 1);
394
395/*  layers and main buffers
396*/
397
398layers = {};
399buffers = {};
400buffers.input = new cFloat32Matrix(cvIn.width, cvIn.height);
401buffers.ds28 = new cFloat32Matrix(28, 28);
402
403function netInit() {
404  const pool2d2x2 = new CNNMaxPool2D(2, 2, 2, 0);
405  buffers.c1 = {};
406  buffers.s2 = {};
407  buffers.c3 = {};
408  buffers.s4 = {};
409  buffers.c5 = {};
410  buffers.f6 = {};
411  buffers.fo = {};
412  for (let i=0; i<6; i++)   {buffers.c1[i] = new cFloat32Matrix(28, 28)};
413  for (let i=0; i<6; i++)   {buffers.s2[i] = new cFloat32Matrix(14, 14)};
414  for (let i=0; i<16; i++)  {buffers.c3[i] = new cFloat32Matrix(10, 10)};
415  for (let i=0; i<16; i++)  {buffers.s4[i] = new cFloat32Matrix(5, 5)};
416  for (let i=0; i<1; i++)   {buffers.c5[i] = new cFloat32Matrix(120, 1)};
417  for (let i=0; i<1; i++)   {buffers.f6[i] = new cFloat32Matrix(84, 1)};
418  for (let i=0; i<1; i++)   {buffers.fo[i] = new cFloat32Matrix(10, 1)};
419  layers.c1 = {};
420  layers.s2 = {};
421  for (let i=0; i<6; i++)   {
422    layers.c1[i] = {};
423    layers.c1[i].conv2d = new CNNConv2D(data['c1_conv.weight'][i][0], 1, 2);
424    layers.c1[i].bias = data['c1_conv.bias'][i];
425    layers.s2[i] = {};
426    layers.s2[i].pool2d = pool2d2x2;
427  };
428  layers.c3 = {};
429  layers.s4 = {};
430  for (let j=0; j<16; j++) {
431    layers.c3[j] = {};
432    layers.c3[j].bias = data['c3_conv.bias'][j];
433    layers.s4[j] = {};
434    layers.s4[j].pool2d = pool2d2x2;
435    for (let i=0; i<6; i++) {
436      layers.c3[j][i] = {};
437      layers.c3[j][i].conv2d = new CNNConv2D(data['c3_conv.weight'][j][i], 1, 0)
438    }
439  };
440  layers.c5 = {};
441  for (let k=0; k<120; k++) {
442    layers.c5[k] = {};
443    layers.c5[k].bias = data['c5_conv.bias'][k];
444    for (let j=0; j<16; j++) {
445      layers.c5[k][j] = {};
446      layers.c5[k][j].conv2d = new CNNConv2D(data['c5_conv.weight'][k][j], 1, 0)
447    }
448  };
449  layers.f6 = {};
450  layers.f6.linear = new Linear(data['f6_linr.weight']);
451  layers.f6.bias = data['f6_linr.bias'];
452  layers.fo = {};
453  layers.fo.linear = new Linear(data['fo_linr.weight'])
454  layers.fo.bias = data['fo_linr.bias']
455};
456
457
458function netForward() {
459  for (let i=0; i<6; i++)   {buffers.c1[i].zeros()};
460  for (let i=0; i<6; i++)   {buffers.s2[i].zeros()};
461  for (let i=0; i<16; i++)  {buffers.c3[i].zeros()};
462  for (let i=0; i<16; i++)  {buffers.s4[i].zeros()};
463  for (let i=0; i<1; i++)   {buffers.c5[i].zeros()};
464  for (let i=0; i<1; i++)   {buffers.f6[i].zeros()};
465  for (let i=0; i<1; i++)   {buffers.fo[i].zeros()};
466
467  for (let i=0; i<6; i++) {
468    layers.c1[i].conv2d.forward(buffers.ds28, buffers.c1[i]);
469    buffers.c1[i].add(layers.c1[i].bias);
470    buffers.c1[i].map(Sigmoid);
471    layers.s2[i].pool2d.forward(buffers.c1[i], buffers.s2[i])
472    buffers.s2[i].upscaleDilate(tmp_buf.m2112)
473    canvasSubRoutineDrawMatrix(buffers.c1[i], tmp_buf.m2120, 240+30+25+(122*i), 80);
474    canvasSubRoutineDrawMatrix(tmp_buf.m2112, tmp_buf.m2120, 240+30+25+(122*i), 260);
475  };
476
477  for (let i=0; i<6; i++) {
478    for (let j=0; j<16; j++) {
479      layers.c3[j][i].conv2d.forward(buffers.s2[i], buffers.c3[j])
480    }
481  };
482
483  for (let j=0; j<16; j++) {
484    buffers.c3[j].add(layers.c3[j].bias)
485    buffers.c3[j].map(Sigmoid)
486    layers.s4[j].pool2d.forward(buffers.c3[j], buffers.s4[j])
487    buffers.s4[j].upscaleDilate(tmp_buf.m2080)
488    canvasSubRoutineDrawMatrix(buffers.c3[j], tmp_buf.m2100, 30+(122*(j % 8)), 470+122*(j>7?1:0));
489    canvasSubRoutineDrawMatrix(tmp_buf.m2080, tmp_buf.m2100, 30+(122*(j % 8)), 800+122*(j>7?1:0));
490  };
491
492  for (let k=0; k<120; k++) {
493    tmp_buf.m2001.zeros();
494    for (let j=0; j<16; j++) {
495      layers.c5[k][j].conv2d.forward(buffers.s4[j], tmp_buf.m2001)
496    }
497    tmp_buf.m2001.add(layers.c5[k].bias);
498    buffers.c5[0].buf[k] = tmp_buf.m2001.buf[0];
499  };
500  buffers.c5[0].map(Sigmoid);
501  layers.f6.linear.forward(buffers.c5[0], buffers.f6[0]);
502  for (let i=0; i<84; i++) {buffers.f6[0].buf[i] += layers.f6.bias[i]};
503  buffers.f6[0].map(Sigmoid);
504  layers.fo.linear.forward(buffers.f6[0], buffers.fo[0]);
505  for (let i=0; i<10; i++) {buffers.fo[0].buf[i] += layers.fo.bias[i]};
506  buffers.fo[0].map(Sigmoid);
507  canvasSubRoutineDrawDense(buffers.c5[0], 30, 1100+260*0, cvPt.width-30-30, 200);
508  canvasSubRoutineDrawDense(buffers.f6[0], 30, 1100+260*1, cvPt.width-30-30, 200);
509  canvasSubRoutineDrawDense(buffers.fo[0], 30, 1100+260*2, cvPt.width-30-30, 200, true);
510};
511
512
513/*  ==================== Entry point ==================== */
514
515function connectCanvasCursorInput() {
516  function cursorEventHandler(h) {
517    if (h.flag & h.flags.down || h.flag === (h.flags.down | h.flags.move)){
518      if (h.pos.curr.x == h.pos.prev.x && h.pos.curr.y == h.pos.prev.y) {
519        canvasSubRoutineDrawDot(h.pos.curr.x, h.pos.curr.y);
520      } else {
521        canvasSubRoutineDrawLine(h.pos.prev.x, h.pos.prev.y, h.pos.curr.x, h.pos.curr.y);
522      }
523    }
524    if (h.flag === h.flags.idle){
525      appUpdate();
526    }
527  };
528  cursorHandler = new cCursorHandler(cvIn, cursorEventHandler);
529}
530
531function disconnectCanvasCursorInput() {
532  if (cursorHandler !== null) {
533    cursorHandler.detach();
534    cursorHandler = null;
535  }
536}
537
538function appInit() {
539  disconnectCanvasCursorInput();
540  connectCanvasCursorInput();
541  appInCanvasReset();
542  netInit();
543  appUpdate();
544};
545
546
547function appUpdate() {
548  const imd = cvInCtx.getImageData(0,0,cvIn.width,cvIn.height);
549  buffers.input.loadImageData(imd.data);
550  Interp.bilinear_m(buffers.input, buffers.ds28);
551  buffers.ds28.upscaleDilate(tmp_buf.m2112)
552  canvasSubRoutineDrawMatrix(tmp_buf.m2112, tmp_buf.m2240, 30, 80);
553  netForward();
554};
555
556
557fetch('./assets/LeNet5.json')
558.then((resp)=>{
559  console.log(resp.status, resp.url);
560  resp.json()
561  .then((d)=>{
562    data = d;
563    document.getElementById("appInCanvasReset").addEventListener("click", (e)=>{appInCanvasReset();appUpdate()})
564    appInit();
565  })
566  .catch(console.error);
567})
568.catch(console.error);
569
570
571}




Built with Hugo | previoip (c) 2025