22 / 24 · प्रयोग
प्रयोगशाला: कोने के मामलों को चुनौती दें
पैडिंग फ़िक्स्चर में हर कर्नेल वज़न एक करें। पहला आउटपुट क्या है और केवल बीच का परीक्षण अपर्याप्त क्यों?
पाठ पढ़ना मुफ़्त है। प्रगति सेव करने के लिए नामांकन करें।
दो फ़ाइल चलाएँ
पहले कोड ब्लॉक से model.mjs और दूसरे से corner-lab.mjs बनाएँ,फिर node corner-lab.mjs चलाएँ। केवल Node का अंतर्निहित assert चाहिए। समर्थित स्थापित Digital Design Studio कार्यप्रवाह से फ़ाइल स्वयं बना और देख सकते हैं। निष्पादन प्रमाण Node कमांड है; इसमें DDS सिम्युलेशन,संश्लेषण या दूरस्थ परियोजना कनेक्शन नहीं।
model.mjs एक-बैच पूर्णांक सीधा कन्वोल्यूशन,बिना पैडिंग im2col,निकटतम-सम राउंडिंग और बिना पैडिंग 2×2 मैक्स पूलिंग देता है। दोनों स्थानिक अक्ष में समान पूर्णांक स्ट्राइड/डाइलेशन और चार स्वतंत्र पैड हैं। समूह और आयताकार आकार जाँचता तथा असुरक्षित पूर्णांक संचायक अस्वीकार करता है। सामान्य मॉडल विनिमय टूल या पूरा ONNX रनटाइम नहीं। इनपुट कोड, वज़न कोड और बायस JavaScript के सुरक्षित पूर्णांक होने चाहिए (निरपेक्ष मान अधिकतम 9007199254740991)। हर गुणनफल और बीच का आंशिक योग भी इसी सीमा में रहना चाहिए; अन्यथा गणना त्रुटि के साथ अस्वीकार की जाती है। यह पूर्णांक अनुबंध conv2d और dot के संचय पर लागू है; यह quantize और requantize के फ़्लोटिंग-पॉइंट scale और राउंडिंग नियमों को नहीं बदलता।
सीमा फ़िक्स्चर उदाहरण
[1,2,3;4,5,6;7,8,9] और असममित 2×2 कर्नेल दोनों पथ में [37,47;67,77]। ऊपर 1,बाएँ 0,नीचे 0,दाएँ 1 पैड में पहली विंडो [0,0;1,2], परिणाम 11। डाइलेशन दो और पैड बिना पहली [1,3;7,9], परिणाम 1+6+21+36=64। बदले निर्देशांक हाथ से लिखें।
2 और 3 वाले दो चैनल,समूह दो,वज़न 4 और 5 से 8 और 15। वही वज़न आकार समूह एक में त्रुटि दे:हर आउटपुट को दो इनपुट चैनल चाहिए लेकिन केवल एक कर्नेल है। वास्तविक शून्य कोड 128 और दोनों int8 क्लिप सीमा भी जाँचें।
मूल जाँच चलाएँ,फिर अस्थायी कर्नेल सूचकांक उलटकर पहली assertion विफल देखें। लौटाएँ। गलत सीमा जाँच से अंतिम वैध पंक्ति अस्वीकारना उपयोगी बदलाव है। समूह जाँच हटाने से आकार-अस्वीकृति फ़िक्स्चर प्रभावित हो। बहाली के बाद पास रन सहेजें।
ये फ़िक्स्चर प्रतिगमन सेट की शुरुआत हैं,पूर्ण नहीं। बहुचैनल 1×1,गैरशून्य शून्य-बिंदु पैडिंग,ऋणात्मक कर्नेल,विषम पूलिंग आकार,खाली आउटपुट और बड़ा बायस जोड़ें। हर assertion का अनुबंध बताएँ। समापन परियोजना यही संदर्भ पूरे छोटे पथ में लेती है।
model.mjs
// Small, dependency-free teaching model. Layout: X[C][H][W], W[M][C/group][R][S].
export function outputSize(size, kernel, stride = 1, padBefore = 0, padAfter = 0, dilation = 1) {
if (![size, kernel, stride, dilation].every(v => Number.isInteger(v) && v > 0) ||
![padBefore, padAfter].every(v => Number.isInteger(v) && v >= 0)) throw new RangeError('Invalid shape');
return Math.max(0, Math.floor((size + padBefore + padAfter - dilation * (kernel - 1) - 1) / stride) + 1);
}
export function roundEven(value) {
if (!Number.isFinite(value)) throw new RangeError('Finite number required');
const floor = Math.floor(value), fraction = value - floor;
return fraction < 0.5 ? floor : fraction > 0.5 ? floor + 1 : floor % 2 === 0 ? floor : floor + 1;
}
export const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
export function quantize(value, scale, zeroPoint = 0, low = -128, high = 127) {
if (!(scale > 0) || !Number.isFinite(scale)) throw new RangeError('Positive finite scale required');
return clamp(roundEven(value / scale) + zeroPoint, low, high);
}
export const dequantize = (value, scale, zeroPoint = 0) => (value - zeroPoint) * scale;
export function requantize(acc, multiplier, zeroPoint = 0, low = -128, high = 127) {
return clamp(roundEven(acc * multiplier) + zeroPoint, low, high);
}
export function conv2d(x, weights, {stride = 1, pads = [0, 0, 0, 0], dilation = 1, groups = 1, bias = null} = {}) {
const channels = x.length, height = x[0]?.length, width = x[0]?.[0]?.length;
const outputs = weights.length, r = weights[0]?.[0]?.length, s = weights[0]?.[0]?.[0]?.length;
if (!Number.isInteger(groups) || groups < 1 || channels % groups || outputs % groups) throw new RangeError('Invalid groups');
if (pads.length !== 4) throw new RangeError('Four explicit pads required');
if (!height || !width || !r || !s || x.some(c => c.length !== height || c.some(row => row.length !== width)) ||
weights.some(m => m.length !== channels / groups || m.some(c => c.length !== r || c.some(row => row.length !== s))))
throw new RangeError('Ragged or incompatible tensor');
if (bias && bias.length !== outputs) throw new RangeError('Bias shape');
if (x.flat(2).some(value => !Number.isSafeInteger(value)) ||
weights.flat(3).some(value => !Number.isSafeInteger(value)) ||
(bias && bias.some(value => !Number.isSafeInteger(value))))
throw new RangeError('Safe integer operands and bias required');
const [top, left, bottom, right] = pads;
const outH = outputSize(height, r, stride, top, bottom, dilation);
const outW = outputSize(width, s, stride, left, right, dilation);
const y = Array.from({length: outputs}, () => Array.from({length: outH}, () => Array(outW).fill(0)));
for (let m = 0; m < outputs; m++) for (let h = 0; h < outH; h++) for (let w = 0; w < outW; w++) {
let acc = bias?.[m] ?? 0;
const firstC = Math.floor(m / (outputs / groups)) * (channels / groups);
for (let c = 0; c < channels / groups; c++) for (let kh = 0; kh < r; kh++) for (let kw = 0; kw < s; kw++) {
const ih = h * stride - top + kh * dilation, iw = w * stride - left + kw * dilation;
if (ih >= 0 && ih < height && iw >= 0 && iw < width) {
const product = x[firstC + c][ih][iw] * weights[m][c][kh][kw];
if (!Number.isSafeInteger(product)) throw new RangeError('Unsafe integer product');
const next = acc + product;
if (!Number.isSafeInteger(next)) throw new RangeError('Unsafe integer partial sum');
acc = next;
}
}
if (!Number.isSafeInteger(acc)) throw new RangeError('This teaching model requires safe integer accumulators');
y[m][h][w] = acc;
}
return y;
}
export function im2col(x, kernelH, kernelW) {
const height = x[0].length, width = x[0][0].length, rows = [];
for (let h = 0; h <= height - kernelH; h++) for (let w = 0; w <= width - kernelW; w++) {
const row = [];
for (const channel of x) for (let r = 0; r < kernelH; r++) for (let s = 0; s < kernelW; s++) row.push(channel[h + r][w + s]);
rows.push(row);
}
return rows;
}
export function dot(a, b) {
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
throw new RangeError('Incompatible dot vectors');
let acc = 0;
for (let i = 0; i < a.length; i++) {
if (!Number.isSafeInteger(a[i]) || !Number.isSafeInteger(b[i]))
throw new RangeError('Safe integer dot operands required');
const product = a[i] * b[i];
if (!Number.isSafeInteger(product)) throw new RangeError('Unsafe integer dot product');
const next = acc + product;
if (!Number.isSafeInteger(next)) throw new RangeError('Unsafe integer dot partial sum');
acc = next;
}
return acc;
}
export function maxPool2x2(matrix) {
const output = [];
for (let h = 0; h + 1 < matrix.length; h += 2) {
const row = [];
for (let w = 0; w + 1 < matrix[0].length; w += 2)
row.push(Math.max(matrix[h][w], matrix[h][w + 1], matrix[h + 1][w], matrix[h + 1][w + 1]));
output.push(row);
}
return output;
}corner-lab.mjs
import assert from 'node:assert/strict';
import {conv2d, im2col, dot, outputSize, quantize, requantize} from './model.mjs';
const x = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]], w = [[[[1, 2], [3, 4]]]];
assert.deepEqual(conv2d(x, w), [[[37, 47], [67, 77]]]);
assert.deepEqual(im2col(x, 2, 2).map(row => dot(row, [1, 2, 3, 4])), [37, 47, 67, 77]);
assert.equal(conv2d(x, w, {pads:[1, 0, 0, 1]})[0][0][0], 11);
assert.equal(conv2d(x, w, {dilation:2})[0][0][0], 64);
assert.equal(outputSize(5, 3, 2, 1, 1, 1), 3);
assert.equal(outputSize(5, 3, 2, 1, 1, 2), 2);
assert.equal(quantize(0, 0.25, 128, 0, 255), 128);
assert.equal(requantize(300, 1), 127);
assert.equal(requantize(-300, 1), -128);
const channels = [[[2]], [[3]]], groupWeights = [[[[4]]], [[[5]]]];
assert.deepEqual(conv2d(channels, groupWeights, {groups:2}), [[[8]], [[15]]]);
assert.throws(() => conv2d(channels, groupWeights), /incompatible/);
// Guard every arithmetic step: cancellation must not hide lost precision.
const safe = Number.MAX_SAFE_INTEGER, pairWeights = [[[[1, 1]]]];
assert.throws(() => conv2d([[[2, -2]]], pairWeights, {bias:[safe]}), /partial sum/);
assert.throws(() => conv2d([[[-2, 2]]], pairWeights, {bias:[-safe]}), /partial sum/);
assert.throws(() => conv2d([[[safe + 1]]], [[[[1]]]]), /operands/);
assert.throws(() => conv2d([[[0]]], [[[[safe + 1]]]]), /operands/);
assert.throws(() => conv2d([[[-1]]], [[[[1]]]], {bias:[safe + 1]}), /bias/);
assert.throws(() => conv2d([[[0.5]]], [[[[2]]]]), /operands/);
assert.throws(() => conv2d([[[100000000]]], [[[[100000000]]]]), /product/);
// Safe boundary arithmetic and zero output dimensions remain supported.
assert.deepEqual(conv2d([[[2, -2]]], pairWeights, {bias:[safe - 2]}), [[[safe - 2]]]);
assert.deepEqual(conv2d([[[1]]], [[[[1, 1], [1, 1]]]]), [[]]);
assert.deepEqual(conv2d([[[1]]], pairWeights), [[[]]]);
// The independent patch-dot path uses the same integer safety contract.
assert.throws(() => dot([safe, 2, -2], [1, 1, 1]), /partial sum/);
assert.throws(() => dot([safe + 1], [0]), /operands/);
assert.throws(() => dot([100000000], [100000000]), /product/);
assert.throws(() => dot([1], [1, 2]), /vectors/);
assert.equal(dot([safe - 2, 2, -2], [1, 1, 1]), safe - 2);
assert.equal(dot([], []), 0);
console.log('corner-lab: 27 checks passed');खुद आज़माएँ
पैडिंग फ़िक्स्चर में हर कर्नेल वज़न एक करें। पहला आउटपुट क्या है और केवल बीच का परीक्षण अपर्याप्त क्यों?
व्याख्या पढ़ें
पहली विंडो [0,0;1,2] का योग तीन है। बीच की विंडो सीमा-बाहर पहुँच नहीं लेती और पैडिंग मान या ऊपरी/बाएँ निर्देशांक त्रुटि छोड़ सकती है।