// 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;
};
}15 도장 1주차: 데이터와 모형
도장 사용법
- 대응 이론: Ch01 들어가기 — 데이터와 모형
- 학번을 입력하면 나만의 문제가 나옵니다. 옆 사람과 다른 문제가 생성됩니다.
- 새 문제 버튼을 누르면 같은 유형의 다음 문제가 나옵니다. 무한히 연습할 수 있습니다.
- 답을 입력하고 채점을 켜면 즉시 확인됩니다. 막히면 풀이 보기 — 단, 먼저 손으로 계산해 보고 나서.
- 유형은 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>`d1vars = [
{ n: "나이", t: "숫자형" }, { n: "연봉", t: "숫자형" }, { n: "키", t: "숫자형" },
{ n: "근무 경력(년)", t: "숫자형" }, { n: "월 매출액", t: "숫자형" },
{ n: "불량품 개수", t: "숫자형" }, { n: "배송 거리(km)", t: "숫자형" },
{ n: "체온", t: "숫자형" }, { n: "대기 시간(분)", t: "숫자형" },
{ n: "성별", t: "범주형" }, { n: "부서", t: "범주형" }, { n: "혈액형", t: "범주형" },
{ n: "제품 브랜드", t: "범주형" }, { n: "결혼 여부", t: "범주형" },
{ n: "거주 지역", t: "범주형" }, { n: "불량 유형", t: "범주형" },
{ n: "배송 수단", t: "범주형" }, { n: "요일", t: "범주형" }
]15.1 유형 A — 변수의 종류 구분 [기본]
qA = {
const rng = dojoRng(qA_seed);
const pool = d1vars.slice();
for (let i = pool.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[pool[i], pool[j]] = [pool[j], pool[i]];
}
const picked = pool.slice(0, 6);
const nNum = picked.filter(v => v.t === "숫자형").length;
return { picked, nNum, nCat: 6 - nNum };
}dojoProblem(`문제 A — 변수의 종류`, `
어떤 데이터에 다음 <strong>6개의 변수</strong>가 있다.
<div style="margin:0.5em 0;padding:0.5em 0.8em;background:#F8F9FA;border-radius:6px;">
${qA.picked.map((v, i) => `${i + 1}. <strong>${v.n}</strong>`).join("<br>")}
</div>
① <strong>숫자형 변수</strong>의 개수, ② <strong>범주형 변수</strong>의 개수를 구하라.`)viewof qA_ans_num = Inputs.number({ label: "숫자형 =", step: 1 })
viewof qA_ans_cat = Inputs.number({ label: "범주형 =", step: 1 })
viewof qA_grade = Inputs.toggle({ label: "채점", value: false })
viewof qA_show = Inputs.toggle({ label: "풀이 보기", value: false })qA_grade ? dojoResult(
[dojoMark("숫자형 개수", dojoOk(qA_ans_num, qA.nNum, 0.001)),
dojoMark("범주형 개수", dojoOk(qA_ans_cat, qA.nCat, 0.001))],
dojoOk(qA_ans_num, qA.nNum, 0.001) && dojoOk(qA_ans_cat, qA.nCat, 0.001)
) : html``qA_show ? dojoSolution(`
${qA.picked.map(v => `<strong>${v.n}</strong> → ${v.t} ${v.t === "숫자형"
? "(값의 차이가 의미를 가짐)" : "(값 사이의 크기 비교 불가)"}`).join("<br>")}
<br>① 숫자형 = <strong>${qA.nNum}</strong>개, ② 범주형 = <strong>${qA.nCat}</strong>개
<br><span style="color:#555;">판별 기준은 "값을 빼면 의미가 있는가?" —
나이 40 − 24 = 16년은 의미가 있지만, 부서 '영업' − '개발'은 의미가 없다.</span>`) : html``15.2 유형 B — 데이터의 구조 읽기 [기본]
qB = {
const rng = dojoRng(qB_seed);
const scenes = [
{ obj: "사원", feats: ["경력(년)", "부서", "교육수준", "평가등급"], tgt: "연봉" },
{ obj: "공정 배치(batch)", feats: ["원료 온도", "압력", "설비 번호", "작업 교대조"], tgt: "불량률" },
{ obj: "고객", feats: ["나이", "성별", "가입 개월 수", "월 사용액"], tgt: "이탈 여부" },
{ obj: "주문 건", feats: ["배송 거리", "배송 수단", "주문 요일", "상품 무게"], tgt: "배송 소요시간" },
{ obj: "설비", feats: ["가동 시간", "진동 세기", "설비 유형", "점검 후 경과일"], tgt: "고장 여부" }
];
const s = dojoPick(rng, scenes);
const nRow = dojoInt(rng, 40, 900);
const nFeat = dojoInt(rng, 2, 4);
const feats = s.feats.slice(0, nFeat);
return { obj: s.obj, feats, tgt: s.tgt, nRow, nFeat, nCol: nFeat + 1 };
}dojoProblem(`문제 B — 데이터의 구조`, `
<strong>${qB.obj} ${qB.nRow}개</strong>에 대하여 다음을 기록한 데이터가 있다.
<div style="margin:0.5em 0;padding:0.5em 0.8em;background:#F8F9FA;border-radius:6px;">
기록 항목: ${qB.feats.map(f => `<strong>${f}</strong>`).join(", ")},
그리고 <strong>${qB.tgt}</strong>
</div>
이 데이터로 <strong>${qB.tgt}</strong>를 예측하는 모형을 만들려고 한다.<br>
① <strong>데이터 포인트</strong>의 개수, ② <strong>독립변수</strong>의 개수,
③ 전체 <strong>변수(열)</strong>의 개수를 구하라.`)viewof qB_ans_row = Inputs.number({ label: "데이터 포인트 =", step: 1 })
viewof qB_ans_x = Inputs.number({ label: "독립변수 =", step: 1 })
viewof qB_ans_col = Inputs.number({ label: "전체 변수 =", step: 1 })
viewof qB_grade = Inputs.toggle({ label: "채점", value: false })
viewof qB_show = Inputs.toggle({ label: "풀이 보기", value: false })qB_grade ? dojoResult(
[dojoMark("데이터 포인트", dojoOk(qB_ans_row, qB.nRow, 0.001)),
dojoMark("독립변수", dojoOk(qB_ans_x, qB.nFeat, 0.001)),
dojoMark("전체 변수", dojoOk(qB_ans_col, qB.nCol, 0.001))],
dojoOk(qB_ans_row, qB.nRow, 0.001) && dojoOk(qB_ans_x, qB.nFeat, 0.001) && dojoOk(qB_ans_col, qB.nCol, 0.001)
) : html``qB_show ? dojoSolution(`
① 데이터 포인트 = <strong>행(row)</strong>의 수 = ${qB.obj} 개수 = <strong>${qB.nRow}</strong><br>
② 독립변수 X = 예측에 <em>사용하는</em> 항목 = ${qB.feats.join(", ")} = <strong>${qB.nFeat}</strong>개<br>
③ 전체 변수 = 독립변수 ${qB.nFeat}개 + 종속변수(<strong>${qB.tgt}</strong>) 1개 = <strong>${qB.nCol}</strong>개<br>
<span style="color:#555;">종속변수 y는 <strong>${qB.tgt}</strong> — "예측하고자 하는 대상"이다.
변수는 열, 데이터 포인트는 행.</span>`) : html``15.3 유형 C — 학습된 모형으로 예측하기 [기본]
qC = {
const rng = dojoRng(qC_seed);
const w0 = dojoInt(rng, 8, 30) * 1000;
const w1 = dojoInt(rng, 3, 12) * 1000;
const x = dojoInt(rng, 2, 15);
return { w0, w1, x, y: w0 + w1 * x };
}dojoProblem(`문제 C — 예측값 계산`, `
경력(x, 년)으로 연봉(y, 달러)을 예측하는 모형을 학습한 결과
파라미터가 다음과 같이 결정되었다.
<div style="text-align:center;margin:0.5em 0;font-size:1.08em;">
<strong>y = ${qC.w0.toLocaleString()} + ${qC.w1.toLocaleString()} × x</strong>
</div>
① 경력 <strong>${qC.x}년</strong>인 사원의 예측 연봉을 구하라.<br>
② 경력이 <strong>1년</strong> 늘어날 때 예측 연봉은 얼마나 증가하는가?`)viewof qC_ans_y = Inputs.number({ label: "예측 연봉 =", step: 1 })
viewof qC_ans_slope = Inputs.number({ label: "1년당 증가액 =", step: 1 })
viewof qC_grade = Inputs.toggle({ label: "채점", value: false })
viewof qC_show = Inputs.toggle({ label: "풀이 보기", value: false })qC_grade ? dojoResult(
[dojoMark("예측 연봉", dojoOk(qC_ans_y, qC.y, 0.5)),
dojoMark("1년당 증가액", dojoOk(qC_ans_slope, qC.w1, 0.5))],
dojoOk(qC_ans_y, qC.y, 0.5) && dojoOk(qC_ans_slope, qC.w1, 0.5)
) : html``qC_show ? dojoSolution(`
① x = ${qC.x} 를 대입:
y = ${qC.w0.toLocaleString()} + ${qC.w1.toLocaleString()} × ${qC.x}
= ${qC.w0.toLocaleString()} + ${(qC.w1 * qC.x).toLocaleString()}
= <strong>${qC.y.toLocaleString()}</strong><br>
② 기울기 w₁이 곧 "1단위 증가당 변화량" = <strong>${qC.w1.toLocaleString()}</strong><br>
<span style="color:#555;">w₀ = ${qC.w0.toLocaleString()} 은 절편 — 경력 0년의 예측값이다.
이렇게 파라미터의 의미를 해석하는 것을 <strong>추론(inference)</strong>이라 한다.</span>`) : html``15.4 유형 D — 모형과 파라미터 식별 [응용]
qD = {
const rng = dojoRng(qD_seed);
const kind = dojoInt(rng, 0, 1);
if (kind === 0) {
const deg = dojoInt(rng, 2, 6);
const terms = [];
for (let k = 1; k <= deg; k++) terms.push(`w${sub(k)}x${k > 1 ? sup(k) : ""}`);
return {
expr: `y = w₀ + ${terms.join(" + ")}`,
nParam: deg + 1,
why: `${deg}차 다항식은 w₀부터 w${sub(deg)}까지 — <strong>차수 + 1 = ${deg + 1}</strong>개`
};
} else {
const m = dojoInt(rng, 3, 7);
const terms = [];
for (let k = 1; k <= m; k++) terms.push(`w${sub(k)}x${sub(k)}`);
return {
expr: `y = w₀ + ${terms.join(" + ")}`,
nParam: m + 1,
why: `독립변수 ${m}개마다 가중치 1개, 그리고 편향 w₀ 1개 — <strong>${m} + 1 = ${m + 1}</strong>개`
};
}
}dojoProblem(`문제 D — 파라미터의 개수`, `
다음 모형을 세웠다.
<div style="text-align:center;margin:0.6em 0;font-size:1.08em;">
<strong>${qD.expr}</strong>
</div>
이 모형이 학습해야 하는 <strong>파라미터의 총 개수</strong>를 구하라.
<span style="color:#666;font-size:0.92em;">(편향 w₀도 파라미터에 포함된다)</span>`)viewof qD_ans = Inputs.number({ label: "파라미터 수 =", step: 1 })
viewof qD_grade = Inputs.toggle({ label: "채점", value: false })
viewof qD_show = Inputs.toggle({ label: "풀이 보기", value: false })qD_grade ? dojoResult(
[dojoMark("파라미터 수", dojoOk(qD_ans, qD.nParam, 0.001))],
dojoOk(qD_ans, qD.nParam, 0.001)
) : html``qD_show ? dojoSolution(`
${qD.why}<br>
<span style="color:#555;">파라미터는 <strong>학습으로 값이 정해지는</strong> 것이다.
x는 데이터가 주는 값이므로 파라미터가 아니다.</span>`) : html``15.5 유형 E — 문제 유형 판정 [응용]
qE = {
const rng = dojoRng(qE_seed);
const tasks = [
{ s: "지난 3년 판매 기록으로 다음 달 <strong>매출액</strong>을 예측한다", sup: 1, reg: 1 },
{ s: "설비 센서값으로 <strong>고장 여부(고장/정상)</strong>를 예측한다", sup: 1, reg: 0 },
{ s: "고객 구매 이력만으로 <strong>비슷한 고객끼리 그룹</strong>을 묶는다", sup: 0, reg: 0 },
{ s: "사진을 보고 <strong>개인지 고양이인지</strong> 판별한다", sup: 1, reg: 0 },
{ s: "공정 조건으로 제품의 <strong>인장강도(MPa)</strong>를 예측한다", sup: 1, reg: 1 },
{ s: "정답이 없는 거래 기록에서 <strong>이상 거래 패턴</strong>을 찾아낸다", sup: 0, reg: 0 },
{ s: "학생 정보로 <strong>학점(4.5 만점 실수)</strong>을 예측한다", sup: 1, reg: 1 },
{ s: "리뷰 문장을 <strong>긍정/부정</strong>으로 나눈다", sup: 1, reg: 0 },
{ s: "라벨 없는 이미지 수천 장을 <strong>차원축소</strong>하여 시각화한다", sup: 0, reg: 0 },
{ s: "배송 조건으로 <strong>소요 시간(분)</strong>을 예측한다", sup: 1, reg: 1 },
{ s: "불량 이미지를 <strong>불량 유형 5가지</strong> 중 하나로 분류한다", sup: 1, reg: 0 },
{ s: "설비 로그만으로 <strong>평소와 다른 이상 구간</strong>을 탐지한다", sup: 0, reg: 0 }
];
const t = dojoPick(rng, tasks);
return {
s: t.s,
sup: t.sup ? "지도학습" : "비지도학습",
reg: t.sup ? (t.reg ? "회귀" : "분류") : "해당 없음"
};
}dojoProblem(`문제 E — 문제 유형 판정`, `
다음 과업을 보고 물음에 답하라.
<div style="margin:0.5em 0;padding:0.6em 0.8em;background:#F8F9FA;border-radius:6px;">
${qE.s}
</div>
① <strong>지도학습</strong>인가 <strong>비지도학습</strong>인가?<br>
② 지도학습이라면 <strong>회귀</strong>인가 <strong>분류</strong>인가?
(비지도학습이면 "해당 없음")`)viewof qE_ans_sup = Inputs.radio(["지도학습", "비지도학습"], { label: "①" })
viewof qE_ans_reg = Inputs.radio(["회귀", "분류", "해당 없음"], { label: "②" })
viewof qE_grade = Inputs.toggle({ label: "채점", value: false })
viewof qE_show = Inputs.toggle({ label: "풀이 보기", value: false })qE_grade ? dojoResult(
[dojoMark("지도/비지도", qE_ans_sup === qE.sup),
dojoMark("회귀/분류", qE_ans_reg === qE.reg)],
qE_ans_sup === qE.sup && qE_ans_reg === qE.reg
) : html``qE_show ? dojoSolution(`
① <strong>${qE.sup}</strong> — ${qE.sup === "지도학습"
? "예측하려는 정답 y가 데이터에 존재한다."
: "정답 y 없이 X의 내부 구조만 찾는다."}<br>
② <strong>${qE.reg}</strong>${qE.reg === "회귀" ? " — y가 숫자형(연속형)이다."
: qE.reg === "분류" ? " — y가 범주형이다." : ""}<br>
<span style="color:#555;">판별 순서: (1) 정답 y가 있는가 → 지도/비지도,
(2) 그 y가 숫자형인가 범주형인가 → 회귀/분류.</span>`) : html``15.6 유형 F — 다중 독립변수 모형의 예측 [도전]
qF = {
const rng = dojoRng(qF_seed);
const w0 = dojoInt(rng, -20, 40);
const w1 = dojoInt(rng, -15, 25) / 10;
const w2 = dojoInt(rng, -15, 25) / 10;
const w3 = dojoInt(rng, -15, 25) / 10;
const x1 = dojoInt(rng, 1, 20);
const x2 = dojoInt(rng, 1, 20);
const x3 = dojoInt(rng, 1, 20);
const y = Math.round((w0 + w1 * x1 + w2 * x2 + w3 * x3) * 100) / 100;
return { w0, w1, w2, w3, x1, x2, x3, y };
}dojoProblem(`문제 F — 다중 회귀 예측`, `
독립변수가 3개인 모형을 학습하여 다음 파라미터를 얻었다.
<div style="text-align:center;margin:0.5em 0;font-size:1.05em;">
<strong>ŷ = ${qF.w0} + (${qF.w1})·x₁ + (${qF.w2})·x₂ + (${qF.w3})·x₃</strong>
</div>
새 데이터 <strong>(x₁, x₂, x₃) = (${qF.x1}, ${qF.x2}, ${qF.x3})</strong>에 대한
예측값 <strong>ŷ</strong>를 <strong>소수 둘째 자리까지</strong> 구하라.`)viewof qF_ans = Inputs.number({ label: "ŷ =", step: 0.01 })
viewof qF_grade = Inputs.toggle({ label: "채점", value: false })
viewof qF_show = Inputs.toggle({ label: "풀이 보기", value: false })qF_grade ? dojoResult(
[dojoMark("예측값 ŷ", dojoOk(qF_ans, qF.y, 0.011))],
dojoOk(qF_ans, qF.y, 0.011)
) : html``qF_show ? dojoSolution(`
각 항을 하나씩 계산한다:<br>
w₁x₁ = ${qF.w1} × ${qF.x1} = ${(qF.w1 * qF.x1).toFixed(2)}<br>
w₂x₂ = ${qF.w2} × ${qF.x2} = ${(qF.w2 * qF.x2).toFixed(2)}<br>
w₃x₃ = ${qF.w3} × ${qF.x3} = ${(qF.w3 * qF.x3).toFixed(2)}<br>
모두 더하고 편향을 더한다:<br>
ŷ = ${qF.w0} + ${(qF.w1 * qF.x1).toFixed(2)} + ${(qF.w2 * qF.x2).toFixed(2)}
+ ${(qF.w3 * qF.x3).toFixed(2)} = <strong>${qF.y.toFixed(2)}</strong><br>
<span style="color:#555;">이 계산이 곧 Ch02의 <strong>퍼셉트론 순전파</strong>다 —
가중합 z = w₀ + Σ wᵢxᵢ. 활성화 함수가 항등함수인 경우와 같다.</span>`) : html``15.7 유형 G — 파라미터 역산 [도전]
qG = {
const rng = dojoRng(qG_seed);
const w1 = dojoInt(rng, 2, 9);
const w0 = dojoInt(rng, 1, 20);
const x1 = dojoInt(rng, 1, 6);
const x2 = x1 + dojoInt(rng, 2, 6);
return { w0, w1, x1, x2, y1: w0 + w1 * x1, y2: w0 + w1 * x2 };
}dojoProblem(`문제 G — 파라미터 역산`, `
모형이 <strong>y = w₀ + w₁x</strong> 형태라는 것은 알지만 파라미터는 모른다.
다만 이 모형이 다음 두 점을 <strong>정확히</strong> 지난다는 것을 알고 있다.
<div style="margin:0.5em 0;padding:0.5em 0.8em;background:#F8F9FA;border-radius:6px;">
x = ${qG.x1} 일 때 y = ${qG.y1} |
x = ${qG.x2} 일 때 y = ${qG.y2}
</div>
① 기울기 <strong>w₁</strong>, ② 절편 <strong>w₀</strong>를 구하라.`)viewof qG_ans_w1 = Inputs.number({ label: "w₁ =", step: 0.01 })
viewof qG_ans_w0 = Inputs.number({ label: "w₀ =", step: 0.01 })
viewof qG_grade = Inputs.toggle({ label: "채점", value: false })
viewof qG_show = Inputs.toggle({ label: "풀이 보기", value: false })qG_grade ? dojoResult(
[dojoMark("기울기 w₁", dojoOk(qG_ans_w1, qG.w1, 0.011)),
dojoMark("절편 w₀", dojoOk(qG_ans_w0, qG.w0, 0.011))],
dojoOk(qG_ans_w1, qG.w1, 0.011) && dojoOk(qG_ans_w0, qG.w0, 0.011)
) : html``qG_show ? dojoSolution(`
① 기울기는 두 점의 <strong>변화량 비</strong>:<br>
w₁ = (${qG.y2} − ${qG.y1}) / (${qG.x2} − ${qG.x1})
= ${qG.y2 - qG.y1} / ${qG.x2 - qG.x1} = <strong>${qG.w1}</strong><br>
② 한 점을 원식에 대입해 w₀를 구한다:<br>
${qG.y1} = w₀ + ${qG.w1} × ${qG.x1} = w₀ + ${qG.w1 * qG.x1}
→ w₀ = ${qG.y1} − ${qG.w1 * qG.x1} = <strong>${qG.w0}</strong><br>
<span style="color:#555;">데이터 2개로 파라미터 2개를 정확히 맞춘 것이다.
데이터가 파라미터보다 많아지면 모든 점을 지날 수 없고,
그때부터 "가장 잘 맞는" 파라미터를 찾는 <strong>학습</strong>이 필요해진다.</span>`) : html``15.8 유형 H — 어느 파라미터가 더 좋은가 [도전]
qH = {
const rng = dojoRng(qH_seed);
const xs = [1, 2, 3, 4];
const tw0 = dojoInt(rng, 1, 6);
const tw1 = dojoInt(rng, 2, 5);
const ys = xs.map(x => tw0 + tw1 * x + dojoInt(rng, -2, 2));
const A = { w0: tw0 + dojoInt(rng, -1, 1), w1: tw1 };
const B = { w0: tw0, w1: tw1 + dojoInt(rng, 1, 2) };
const sse = (p) => xs.reduce((s, x, i) => s + Math.pow(ys[i] - (p.w0 + p.w1 * x), 2), 0);
const sA = sse(A), sB = sse(B);
return { xs, ys, A, B, sA, sB, best: sA <= sB ? "A" : "B" };
}dojoProblem(`문제 H — 파라미터 후보 비교`, `
데이터 4개가 다음과 같다.
<div style="margin:0.5em 0;padding:0.5em 0.8em;background:#F8F9FA;border-radius:6px;">
${qH.xs.map((x, i) => `(x=${x}, y=${qH.ys[i]})`).join(" ")}
</div>
모형 <strong>y = w₀ + w₁x</strong>의 파라미터 후보가 두 개 있다.<br>
<strong>후보 A</strong>: w₀ = ${qH.A.w0}, w₁ = ${qH.A.w1}<br>
<strong>후보 B</strong>: w₀ = ${qH.B.w0}, w₁ = ${qH.B.w1}<br>
각 후보의 <strong>잔차 제곱합</strong> Σ(yᵢ − ŷᵢ)² 을 구하고,
어느 쪽이 데이터를 더 잘 설명하는지 판정하라.`)viewof qH_ans_a = Inputs.number({ label: "후보 A의 잔차제곱합 =", step: 0.1 })
viewof qH_ans_b = Inputs.number({ label: "후보 B의 잔차제곱합 =", step: 0.1 })
viewof qH_ans_best = Inputs.radio(["A", "B"], { label: "더 좋은 후보" })
viewof qH_grade = Inputs.toggle({ label: "채점", value: false })
viewof qH_show = Inputs.toggle({ label: "풀이 보기", value: false })qH_grade ? dojoResult(
[dojoMark("후보 A", dojoOk(qH_ans_a, qH.sA, 0.011)),
dojoMark("후보 B", dojoOk(qH_ans_b, qH.sB, 0.011)),
dojoMark("판정", qH_ans_best === qH.best)],
dojoOk(qH_ans_a, qH.sA, 0.011) && dojoOk(qH_ans_b, qH.sB, 0.011) && qH_ans_best === qH.best
) : html``qH_show ? dojoSolution(`
<strong>후보 A</strong> (w₀=${qH.A.w0}, w₁=${qH.A.w1}):<br>
${qH.xs.map((x, i) => {
const p = qH.A.w0 + qH.A.w1 * x;
return ` x=${x}: ŷ=${p}, 잔차 = ${qH.ys[i]} − ${p} = ${qH.ys[i] - p}, 제곱 = ${Math.pow(qH.ys[i] - p, 2)}`;
}).join("<br>")}<br>
합 = <strong>${qH.sA}</strong><br><br>
<strong>후보 B</strong> (w₀=${qH.B.w0}, w₁=${qH.B.w1}):<br>
${qH.xs.map((x, i) => {
const p = qH.B.w0 + qH.B.w1 * x;
return ` x=${x}: ŷ=${p}, 잔차 = ${qH.ys[i]} − ${p} = ${qH.ys[i] - p}, 제곱 = ${Math.pow(qH.ys[i] - p, 2)}`;
}).join("<br>")}<br>
합 = <strong>${qH.sB}</strong><br><br>
잔차 제곱합이 <strong>작을수록</strong> 데이터를 잘 설명한다 → <strong>후보 ${qH.best}</strong><br>
<span style="color:#555;">이 잔차 제곱합을 데이터 개수로 나눈 것이 Ch03의 <strong>MSE</strong>이고,
이 값을 최소로 만드는 파라미터를 찾는 것이 <strong>학습</strong>이다.</span>`) : html``15.9 다 풀었다면
- 각 유형에서 연속 3문제를 실수 없이 풀었다면 1주차는 완성이다.
- 막히는 유형이 있으면 Ch01 들어가기: 데이터와 모형의 해당 절로 돌아가라.
- 다음 주 도장: 2주차 퍼셉트론