// mulberry32 — 문제 번호(시드)가 같으면 항상 같은 문제가 생성된다
dojoRng = function (seed) {
let a = (seed * 7919 + 104729) >>> 0;
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}16 도장 2주차: 퍼셉트론
도장 사용법
- 대응 이론: Ch02 퍼셉트론과 다층 퍼셉트론
- 학번을 입력하면 나만의 문제가 나옵니다. 옆 사람과 다른 문제가 생성됩니다.
- 새 문제 버튼을 누르면 같은 유형의 다음 문제가 나옵니다. 무한히 연습할 수 있습니다.
- 답을 입력하고 채점을 켜면 즉시 확인됩니다. 막히면 풀이 보기 — 단, 먼저 손으로 계산해 보고 나서.
- 유형은 8개, 난이도는 [기본] → [응용] → [도전] 순입니다. 기본 3유형부터 시작하세요.
- 훈련 기준: 각 유형에서 연속 3문제 무실수가 되면 그 유형은 완성입니다.
// 정답 비교 — tol 이내면 정답 처리 (학생의 반올림 오차 허용)
dojoOk = (user, answer, tol = 0.005) =>
user !== null && user !== undefined && Number.isFinite(user) && Math.abs(user - answer) <= toldojoMark = (label, ok, answerShown) => `
<div style="display:flex;align-items:center;gap:0.5em;padding:0.25em 0;">
<span style="font-size:1.1em;">${ok ? "✅" : "❌"}</span>
<span style="font-weight:600;">${label}</span>
<span style="color:${ok ? "#2E7D32" : "#8C1515"};">
${ok ? "정답" : (answerShown != null ? "오답 — 다시 계산해 보세요" : "오답")}
</span>
</div>`// 문제 카드
dojoProblem = (title, bodyHtml) => html`
<div style="font-family:'Pretendard',sans-serif;border:1.5px solid #8C1515;border-radius:8px;
overflow:hidden;margin:0.6em 0;max-width:720px;">
<div style="background:#8C1515;color:white;padding:0.4em 0.9em;font-weight:700;font-size:0.95em;">
${title}
</div>
<div style="padding:0.7em 0.9em;line-height:1.9;font-size:0.95em;">${bodyHtml}</div>
</div>`// 채점 결과 카드
dojoResult = (marks, allOk) => html`
<div style="font-family:'Pretendard',sans-serif;border-radius:8px;max-width:720px;margin:0.4em 0;
padding:0.6em 0.9em;font-size:0.92em;
background:${allOk ? "#E8F5E9" : "#FFF8E1"};
border:1.5px solid ${allOk ? "#2E7D32" : "#D4A017"};">
${marks.join("")}
<div style="margin-top:0.35em;font-weight:700;color:${allOk ? "#2E7D32" : "#B8860B"};">
${allOk ? "🎉 전부 정답! 문제 번호를 바꿔 한 번 더 연습해 보세요." : "빨간 항목을 다시 풀어 보세요. 막히면 풀이 보기를 켜세요."}
</div>
</div>`// 풀이 카드
dojoSolution = (stepsHtml) => html`
<div style="font-family:'Pretendard',sans-serif;border-radius:8px;max-width:720px;margin:0.4em 0;
padding:0.7em 0.9em;background:#E3F2FD;border:1.5px solid #1565C0;
line-height:2.0;font-size:0.92em;">
<div style="font-weight:700;color:#1565C0;margin-bottom:0.2em;">풀이</div>
${stepsHtml}
</div>`// FNV-1a — 학번 문자열을 정수로. 사람마다 다른 문제가 나오게 하는 출발점.
dojoHash = function (str) {
let h = 2166136261 >>> 0;
const s = String(str);
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619) >>> 0;
}
return h >>> 0;
}// (학번, 주차, 유형, 라운드) → 문제 번호.
// 학번이 다르면 다른 문제, 같은 학번·같은 라운드면 항상 같은 문제(재현 가능).
dojoSeed = function (sid, week, type, round) {
const key = (sid && String(sid).trim()) ? String(sid).trim() : "guest";
return (dojoHash(key + "|" + week + "|" + type + "|" + round) % 99999) + 1;
}// 학번 입력 — 브라우저에 저장되어 모든 도장 페이지에서 유지된다.
viewof dojoSid = Inputs.text({
label: "학번",
placeholder: "예: 202312345",
value: (() => { try { return localStorage.getItem("dojoSid") || ""; } catch (e) { return ""; } })(),
width: 260
}){
try { localStorage.setItem("dojoSid", dojoSid || ""); } catch (e) {}
const named = dojoSid && dojoSid.trim();
return html`<div style="font-family:'Pretendard',sans-serif;font-size:0.85em;
padding:0.45em 0.8em;border-radius:6px;max-width:720px;margin:0.1em 0 0.9em;
background:${named ? "#E8F5E9" : "#FFF8E1"};
border:1px solid ${named ? "#A5D6A7" : "#E8D9A0"};color:${named ? "#2E7D32" : "#8A6D1B"};">
${named
? `학번 <strong>${dojoSid.trim()}</strong> 기준으로 문제가 생성됩니다 — 옆 사람과 다른 문제가 나옵니다.`
: `학번을 입력하면 <strong>나만의 문제 세트</strong>가 만들어집니다. (비워 두면 공용 연습 문제)`}
</div>`;
}// "새 문제" 버튼 + 문제 코드 표시를 한 줄로 묶는 헤더
dojoBar = (code) => html`
<div style="font-family:'Pretendard',sans-serif;font-size:0.82em;color:#666;
margin:-0.3em 0 0.5em;">
문제 코드 <code style="background:#F1F3F5;padding:0.1em 0.4em;border-radius:4px;
color:#8C1515;font-weight:700;">${code}</code>
<span style="color:#999;"> — 질문할 때 이 코드를 알려주세요</span>
</div>`16.1 유형 A — 퍼셉트론 순전파 [기본]
pA = {
const rng = dojoRng(pA_seed);
const w0 = dojoInt(rng, -10, 10) / 10;
let w1 = dojoInt(rng, -10, 10) / 10; if (w1 === 0) w1 = 0.5;
let w2 = dojoInt(rng, -10, 10) / 10; if (w2 === 0) w2 = -0.5;
const x1 = dojoInt(rng, 0, 3);
const x2 = dojoInt(rng, 0, 3);
const z = Math.round((w0 + w1 * x1 + w2 * x2) * 100) / 100;
const yhat = z >= 0 ? 1 : 0;
return { w0, w1, w2, x1, x2, z, yhat };
}dojoProblem(`문제 A-${pA_seed}`, `
퍼셉트론의 가중치가 <strong>w₀ = ${pA.w0}</strong> (편향),
<strong>w₁ = ${pA.w1}</strong>, <strong>w₂ = ${pA.w2}</strong> 이고,
활성화 함수는 <strong>계단 함수</strong>(z ≥ 0이면 1, 아니면 0)이다.<br>
입력이 <strong>(x₁, x₂) = (${pA.x1}, ${pA.x2})</strong>일 때
가중합 <strong>z</strong>와 출력 <strong>ŷ</strong>를 구하라.`)viewof pA_ans_z = Inputs.number({ label: "z =", step: 0.01 })
viewof pA_ans_y = Inputs.number({ label: "ŷ =", step: 1 })
viewof pA_grade = Inputs.toggle({ label: "채점", value: false })
viewof pA_show = Inputs.toggle({ label: "풀이 보기", value: false })pA_grade ? dojoResult(
[dojoMark("가중합 z", dojoOk(pA_ans_z, pA.z)),
dojoMark("출력 ŷ", dojoOk(pA_ans_y, pA.yhat, 0.001))],
dojoOk(pA_ans_z, pA.z) && dojoOk(pA_ans_y, pA.yhat, 0.001)
) : html``pA_show ? dojoSolution(`
① 가중합: z = w₀ + w₁x₁ + w₂x₂ = ${pA.w0} + ${pA.w1}×${pA.x1} + ${pA.w2}×${pA.x2}
= <strong>${pA.z}</strong><br>
② 계단 함수: z = ${pA.z} ${pA.z >= 0 ? "≥ 0 이므로" : "< 0 이므로"}
ŷ = <strong>${pA.yhat}</strong>`) : html``16.2 유형 B — 활성화 함수 값 [기본]
pB = {
const rng = dojoRng(pB_seed + 50000);
let z = dojoInt(rng, -30, 30) / 10; if (z === 0) z = 1.0;
const sig = Math.round(1 / (1 + Math.exp(-z)) * 100) / 100;
const relu = Math.max(0, z);
return { z, sig, relu };
}dojoProblem(`문제 B-${pB_seed}`, `
가중합이 <strong>z = ${pB.z}</strong>로 계산되었다. (계산기 사용 가능)<br>
① Sigmoid: σ(z) = 1/(1+e<sup>−z</sup>) 의 값을 <strong>소수 둘째 자리까지</strong> 구하라.<br>
② ReLU: max(0, z) 의 값을 구하라.`)viewof pB_ans_sig = Inputs.number({ label: "σ(z) =", step: 0.01 })
viewof pB_ans_relu = Inputs.number({ label: "ReLU(z) =", step: 0.1 })
viewof pB_grade = Inputs.toggle({ label: "채점", value: false })
viewof pB_show = Inputs.toggle({ label: "풀이 보기", value: false })pB_grade ? dojoResult(
[dojoMark("Sigmoid σ(z)", dojoOk(pB_ans_sig, pB.sig, 0.006)),
dojoMark("ReLU(z)", dojoOk(pB_ans_relu, pB.relu, 0.001))],
dojoOk(pB_ans_sig, pB.sig, 0.006) && dojoOk(pB_ans_relu, pB.relu, 0.001)
) : html``pB_show ? dojoSolution(`
① σ(${pB.z}) = 1 / (1 + e<sup>${-pB.z > 0 ? "" : "−"}${Math.abs(pB.z)}</sup>)
= 1 / (1 + ${(Math.exp(-pB.z)).toFixed(4)}) ≈ <strong>${pB.sig.toFixed(2)}</strong><br>
② z = ${pB.z} ${pB.z > 0 ? "> 0 이므로 그대로" : "≤ 0 이므로 0"}:
ReLU(${pB.z}) = <strong>${pB.relu}</strong><br>
<span style="color:#555;">σ(z)는 z가 클수록 1에, 작을수록 0에 가까워진다 — 그래프로 확인: Ch02 활성화 함수 절.</span>`) : html``16.3 유형 C — 파라미터 개수 세기 [기본]
pC = {
const rng = dojoRng(pC_seed + 100000);
const nIn = dojoInt(rng, 2, 8);
const nHid = dojoInt(rng, 3, 10);
const nOut = dojoInt(rng, 1, 4);
const w = nIn * nHid + nHid * nOut;
const b = nHid + nOut;
return { nIn, nHid, nOut, w, b, total: w + b };
}dojoProblem(`문제 C-${pC_seed}`, `
입력층 <strong>${pC.nIn}개</strong> → 은닉층 <strong>${pC.nHid}개</strong> →
출력층 <strong>${pC.nOut}개</strong> 노드로 구성된 완전연결 신경망이 있다.<br>
① 가중치의 총 개수, ② 편향의 총 개수, ③ 전체 파라미터 수를 구하라.`)viewof pC_ans_w = Inputs.number({ label: "가중치 수 =", step: 1 })
viewof pC_ans_b = Inputs.number({ label: "편향 수 =", step: 1 })
viewof pC_ans_t = Inputs.number({ label: "전체 =", step: 1 })
viewof pC_grade = Inputs.toggle({ label: "채점", value: false })
viewof pC_show = Inputs.toggle({ label: "풀이 보기", value: false })pC_grade ? dojoResult(
[dojoMark("가중치 수", dojoOk(pC_ans_w, pC.w, 0.001)),
dojoMark("편향 수", dojoOk(pC_ans_b, pC.b, 0.001)),
dojoMark("전체 파라미터", dojoOk(pC_ans_t, pC.total, 0.001))],
dojoOk(pC_ans_w, pC.w, 0.001) && dojoOk(pC_ans_b, pC.b, 0.001) && dojoOk(pC_ans_t, pC.total, 0.001)
) : html``pC_show ? dojoSolution(`
① 가중치 = (입력→은닉) + (은닉→출력) = ${pC.nIn}×${pC.nHid} + ${pC.nHid}×${pC.nOut}
= ${pC.nIn * pC.nHid} + ${pC.nHid * pC.nOut} = <strong>${pC.w}</strong><br>
② 편향 = 은닉 + 출력 = ${pC.nHid} + ${pC.nOut} = <strong>${pC.b}</strong>
(편향은 출력 노드 하나당 하나)<br>
③ 전체 = ${pC.w} + ${pC.b} = <strong>${pC.total}</strong>`) : html``16.4 유형 D — 이 퍼셉트론은 무슨 게이트인가 [응용]
pD = {
const rng = dojoRng(pD_seed + 200000);
const gates = {
"AND": [-0.7, 0.5, 0.5],
"OR": [-0.3, 0.5, 0.5],
"NAND": [ 0.7, -0.5, -0.5],
"NOR": [ 0.3, -0.5, -0.5],
};
const name = dojoPick(rng, Object.keys(gates));
const scale = dojoPick(rng, [0.6, 0.8, 1.0, 1.2, 1.4]);
const [w0, w1, w2] = gates[name].map(v => Math.round(v * scale * 100) / 100);
const rows = [[0,0],[1,0],[0,1],[1,1]].map(([x1, x2]) => {
const z = Math.round((w0 + w1 * x1 + w2 * x2) * 100) / 100;
return { x1, x2, z, y: z >= 0 ? 1 : 0 };
});
return { name, w0, w1, w2, rows };
}dojoProblem(`문제 D-${pD_seed}`, `
가중치가 <strong>w₀ = ${pD.w0}</strong>, <strong>w₁ = ${pD.w1}</strong>,
<strong>w₂ = ${pD.w2}</strong>, 활성화는 계단 함수인 퍼셉트론이 있다.<br>
입력 (0,0), (1,0), (0,1), (1,1)에 대한 출력을 <strong>직접 계산</strong>하여,
이 퍼셉트론이 구현하는 논리 게이트를 고르라.`)viewof pD_ans = Inputs.radio(["AND", "OR", "NAND", "NOR"], { label: "이 게이트는" })
viewof pD_grade = Inputs.toggle({ label: "채점", value: false })
viewof pD_show = Inputs.toggle({ label: "풀이 보기", value: false })pD_show ? dojoSolution(`
네 입력을 전부 계산하면:<br>
${pD.rows.map(r =>
` (${r.x1}, ${r.x2}): z = ${pD.w0} + ${pD.w1}×${r.x1} + ${pD.w2}×${r.x2} = ${r.z}
→ ŷ = <strong>${r.y}</strong>`).join("<br>")}<br>
출력 패턴 (${pD.rows.map(r => r.y).join(", ")}) 은 <strong>${pD.name}</strong> 게이트다.<br>
<span style="color:#555;">XOR 패턴 (0, 1, 1, 0)은 어떤 가중치로도 나오지 않는다 — Ch02에서 본 단일 퍼셉트론의 한계.</span>`) : html``16.5 유형 E — 시그모이드 퍼셉트론의 출력 [응용]
pE = {
const rng = dojoRng(pE_seed + 300000);
const w0 = dojoInt(rng, -10, 10) / 10;
let w1 = dojoInt(rng, -8, 8) / 10; if (w1 === 0) w1 = 0.3;
let w2 = dojoInt(rng, -8, 8) / 10; if (w2 === 0) w2 = -0.4;
const x1 = dojoInt(rng, 0, 3);
const x2 = dojoInt(rng, 0, 3);
const z = Math.round((w0 + w1 * x1 + w2 * x2) * 100) / 100;
const sig = Math.round(1 / (1 + Math.exp(-z)) * 100) / 100;
return { w0, w1, w2, x1, x2, z, sig, cls: sig >= 0.5 ? 1 : 0 };
}dojoProblem(`문제 E-${pE_seed}`, `
가중치 <strong>w₀ = ${pE.w0}</strong>, <strong>w₁ = ${pE.w1}</strong>,
<strong>w₂ = ${pE.w2}</strong>, 활성화가 <strong>Sigmoid</strong>인 퍼셉트론에
입력 <strong>(x₁, x₂) = (${pE.x1}, ${pE.x2})</strong>가 들어왔다. (계산기 사용 가능)<br>
① 가중합 z, ② 출력 확률 ŷ = σ(z) (소수 둘째 자리),
③ 임계값 0.5 기준의 분류 결과(1 또는 0)를 구하라.`)viewof pE_ans_z = Inputs.number({ label: "z =", step: 0.01 })
viewof pE_ans_sig = Inputs.number({ label: "ŷ = σ(z) =", step: 0.01 })
viewof pE_ans_cls = Inputs.number({ label: "분류 결과 =", step: 1 })
viewof pE_grade = Inputs.toggle({ label: "채점", value: false })
viewof pE_show = Inputs.toggle({ label: "풀이 보기", value: false })pE_grade ? dojoResult(
[dojoMark("가중합 z", dojoOk(pE_ans_z, pE.z)),
dojoMark("출력 확률 σ(z)", dojoOk(pE_ans_sig, pE.sig, 0.006)),
dojoMark("분류 결과", dojoOk(pE_ans_cls, pE.cls, 0.001))],
dojoOk(pE_ans_z, pE.z) && dojoOk(pE_ans_sig, pE.sig, 0.006) && dojoOk(pE_ans_cls, pE.cls, 0.001)
) : html``pE_show ? dojoSolution(`
① z = ${pE.w0} + ${pE.w1}×${pE.x1} + ${pE.w2}×${pE.x2} = <strong>${pE.z}</strong><br>
② σ(${pE.z}) = 1/(1 + e<sup>${-pE.z > 0 ? "" : "−"}${Math.abs(pE.z)}</sup>) ≈ <strong>${pE.sig.toFixed(2)}</strong><br>
③ ŷ = ${pE.sig.toFixed(2)} ${pE.sig >= 0.5 ? "≥ 0.5 → 클래스" : "< 0.5 → 클래스"} <strong>${pE.cls}</strong><br>
<span style="color:#555;">계단 함수와 달리 Sigmoid는 "얼마나 확신하는지"까지 말해 준다 — Ch03의 교차 엔트로피가 이 확률을 사용한다.</span>`) : html``16.6 유형 F — 2층 MLP 순전파 [도전]
pF = {
const rng = dojoRng(pF_seed + 400000);
const g = arr => arr.map(v => Math.round(v * 100) / 100);
const h1 = g([dojoInt(rng, -8, 8) / 10, dojoInt(rng, 2, 8) / 10, dojoInt(rng, -8, 8) / 10]);
const h2 = g([dojoInt(rng, -8, 8) / 10, dojoInt(rng, -8, -2) / 10, dojoInt(rng, 2, 8) / 10]);
const o = g([dojoInt(rng, -8, 8) / 10, dojoInt(rng, 2, 8) / 10, dojoInt(rng, 2, 8) / 10]);
const x1 = dojoInt(rng, 0, 1), x2 = dojoInt(rng, 0, 1);
const z1 = Math.round((h1[0] + h1[1] * x1 + h1[2] * x2) * 100) / 100;
const s1 = z1 >= 0 ? 1 : 0;
const z2 = Math.round((h2[0] + h2[1] * x1 + h2[2] * x2) * 100) / 100;
const s2 = z2 >= 0 ? 1 : 0;
const z3 = Math.round((o[0] + o[1] * s1 + o[2] * s2) * 100) / 100;
const y = z3 >= 0 ? 1 : 0;
return { h1, h2, o, x1, x2, z1, s1, z2, s2, z3, y };
}dojoProblem(`문제 F-${pF_seed}`, `
은닉 노드 2개, 활성화가 모두 <strong>계단 함수</strong>인 2층 네트워크가 있다.<br>
은닉 s₁: (w₀, w₁, w₂) = (${pF.h1[0]}, ${pF.h1[1]}, ${pF.h1[2]}) ·
은닉 s₂: (${pF.h2[0]}, ${pF.h2[1]}, ${pF.h2[2]}) ·
출력 ŷ: (${pF.o[0]}, ${pF.o[1]}, ${pF.o[2]}) — 출력층의 입력은 (s₁, s₂)<br>
입력 <strong>(x₁, x₂) = (${pF.x1}, ${pF.x2})</strong>일 때
<strong>s₁, s₂, ŷ</strong>를 차례로 구하라. (Ch02의 XOR 계산과 같은 구조)`)viewof pF_ans_s1 = Inputs.number({ label: "s₁ =", step: 1 })
viewof pF_ans_s2 = Inputs.number({ label: "s₂ =", step: 1 })
viewof pF_ans_y = Inputs.number({ label: "ŷ =", step: 1 })
viewof pF_grade = Inputs.toggle({ label: "채점", value: false })
viewof pF_show = Inputs.toggle({ label: "풀이 보기", value: false })pF_grade ? dojoResult(
[dojoMark("은닉 s₁", dojoOk(pF_ans_s1, pF.s1, 0.001)),
dojoMark("은닉 s₂", dojoOk(pF_ans_s2, pF.s2, 0.001)),
dojoMark("출력 ŷ", dojoOk(pF_ans_y, pF.y, 0.001))],
dojoOk(pF_ans_s1, pF.s1, 0.001) && dojoOk(pF_ans_s2, pF.s2, 0.001) && dojoOk(pF_ans_y, pF.y, 0.001)
) : html``pF_show ? dojoSolution(`
① z₁ = ${pF.h1[0]} + ${pF.h1[1]}×${pF.x1} + ${pF.h1[2]}×${pF.x2} = ${pF.z1}
→ s₁ = <strong>${pF.s1}</strong><br>
② z₂ = ${pF.h2[0]} + ${pF.h2[1]}×${pF.x1} + ${pF.h2[2]}×${pF.x2} = ${pF.z2}
→ s₂ = <strong>${pF.s2}</strong><br>
③ z₃ = ${pF.o[0]} + ${pF.o[1]}×s₁ + ${pF.o[2]}×s₂ = ${pF.o[0]} + ${pF.o[1]}×${pF.s1} + ${pF.o[2]}×${pF.s2} = ${pF.z3}
→ ŷ = <strong>${pF.y}</strong><br>
<span style="color:#555;">앞 층의 출력이 다음 층의 입력이 된다 — 층이 100개여도 절차는 이것의 반복이다.</span>`) : html``16.7 유형 G — 파라미터 역산 [도전]
pG = {
const rng = dojoRng(pG_seed + 500000);
const nIn = dojoInt(rng, 2, 6);
const nOut = dojoInt(rng, 1, 3);
const h = dojoInt(rng, 2, 9);
const total = h * (nIn + nOut + 1) + nOut;
return { nIn, nOut, h, total };
}dojoProblem(`문제 G-${pG_seed}`, `
입력층 <strong>${pG.nIn}개</strong>, 출력층 <strong>${pG.nOut}개</strong> 노드로 하고
은닉층 1개를 두려고 한다. 전체 파라미터(가중치+편향)가 정확히
<strong>${pG.total}개</strong>가 되려면 <strong>은닉 노드는 몇 개</strong>여야 하는가?`)viewof pG_ans = Inputs.number({ label: "은닉 노드 수 =", step: 1 })
viewof pG_grade = Inputs.toggle({ label: "채점", value: false })
viewof pG_show = Inputs.toggle({ label: "풀이 보기", value: false })pG_grade ? dojoResult(
[dojoMark("은닉 노드 수", dojoOk(pG_ans, pG.h, 0.001))],
dojoOk(pG_ans, pG.h, 0.001)
) : html``pG_show ? dojoSolution(`
은닉 노드를 h라 하면:<br>
전체 = 가중치(${pG.nIn}h + ${pG.nOut}h) + 편향(h + ${pG.nOut})
= h(${pG.nIn} + ${pG.nOut} + 1) + ${pG.nOut} = ${pG.nIn + pG.nOut + 1}h + ${pG.nOut}<br>
${pG.nIn + pG.nOut + 1}h + ${pG.nOut} = ${pG.total} → h = ${pG.total - pG.nOut} / ${pG.nIn + pG.nOut + 1}
= <strong>${pG.h}</strong><br>
<span style="color:#555;">공식을 외우는 게 아니라 세우는 문제다 — 시험에서 이 유형이 나오면 항상 식부터 세워라.</span>`) : html``16.8 유형 H — 깊은 네트워크의 파라미터 [도전]
pH = {
const rng = dojoRng(pH_seed + 600000);
const nIn = dojoInt(rng, 3, 10);
const h1 = dojoInt(rng, 4, 12);
const h2 = dojoInt(rng, 2, 8);
const nOut = dojoInt(rng, 1, 4);
const w = nIn * h1 + h1 * h2 + h2 * nOut;
const b = h1 + h2 + nOut;
return { nIn, h1, h2, nOut, w, b, total: w + b };
}dojoProblem(`문제 H-${pH_seed}`, `
입력 <strong>${pH.nIn}</strong> → 은닉1 <strong>${pH.h1}</strong> →
은닉2 <strong>${pH.h2}</strong> → 출력 <strong>${pH.nOut}</strong> 노드의
"딥" 신경망(은닉층 2개)이 있다.<br>
① 가중치 총 개수, ② 편향 총 개수, ③ 전체 파라미터 수를 구하라.`)viewof pH_ans_w = Inputs.number({ label: "가중치 수 =", step: 1 })
viewof pH_ans_b = Inputs.number({ label: "편향 수 =", step: 1 })
viewof pH_ans_t = Inputs.number({ label: "전체 =", step: 1 })
viewof pH_grade = Inputs.toggle({ label: "채점", value: false })
viewof pH_show = Inputs.toggle({ label: "풀이 보기", value: false })pH_grade ? dojoResult(
[dojoMark("가중치 수", dojoOk(pH_ans_w, pH.w, 0.001)),
dojoMark("편향 수", dojoOk(pH_ans_b, pH.b, 0.001)),
dojoMark("전체 파라미터", dojoOk(pH_ans_t, pH.total, 0.001))],
dojoOk(pH_ans_w, pH.w, 0.001) && dojoOk(pH_ans_b, pH.b, 0.001) && dojoOk(pH_ans_t, pH.total, 0.001)
) : html``pH_show ? dojoSolution(`
① 가중치 = ${pH.nIn}×${pH.h1} + ${pH.h1}×${pH.h2} + ${pH.h2}×${pH.nOut}
= ${pH.nIn * pH.h1} + ${pH.h1 * pH.h2} + ${pH.h2 * pH.nOut} = <strong>${pH.w}</strong><br>
② 편향 = ${pH.h1} + ${pH.h2} + ${pH.nOut} = <strong>${pH.b}</strong><br>
③ 전체 = ${pH.w} + ${pH.b} = <strong>${pH.total}</strong><br>
<span style="color:#555;">층이 늘어나도 규칙은 같다: 층 사이마다 (앞 노드 × 뒤 노드) 가중치 + (뒤 노드) 편향.</span>`) : html``
다 풀었다면
여덟 유형 모두 문제 번호를 바꿔 가며 연속 3문제 무실수가 될 때까지 반복하세요. [기본] 3유형이 흔들리면 Ch02를 다시 읽고, [도전]까지 무실수라면 2주차 문제집과 중간고사의 퍼셉트론 문제는 준비가 끝난 것입니다.