">
<head>
<!-- HTML Meta Tags -->
<meta charset="UTF-8" />
<title> 제목 </title>
<meta
name="description" content=" Attension시뮬레이션(with YangPhago) " />
<meta name="keywords" content=" Attension 시뮬레이션, attension, attension explainer" />
<!-- Open Graph / Facebook -->
<meta property="og:title" content=" Attension시뮬레이션(with YangPhago) " />
<meta property="og:description" content="Attension 시뮬레이션, attension, attension explainer " />
<meta property="og:image" content="대표 이미지" />
<meta property="og:url" content="페이지 주소" />
<meta property="og:type" content="website" />
</head>
<aside> 💡 AI와 함께 코딩, 개노다로 2일간 약 30시간을 투자해 만든 한국인을 위한 Attension 메카니즘 체험시뮬레이션(코파일럿이 짱
</aside>
-원래는 클로드의 아티팩트나 구글 제미나이 캔버스로 구현하려고 했으나 코드가 3000줄이 넘어가는 관계로 코파일럿으로 구현 후, 깃허브 탑재
🤖 어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)

[참고문헌]
[코드 전체]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🤖 어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh; padding: 100px 20px 20px 20px;
}
.container {
max-width: 1400px; margin: 0 auto; background: #fff; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden;
}
.header {
background: linear-gradient(45deg, #ff6b6b, #ee5a24); color: #fff; padding: 30px; text-align: center;
}
.header h1 { font-size: 2.5em; margin-bottom: 10px; }
.content { padding: 40px; }
/* 제어 패널 */
.control-panel {
background: #f8f9fa; border-radius: 15px; padding: 30px; margin-bottom: 30px;
border-left: 5px solid #4caf50;
}
.input-section { margin-bottom: 20px; }
.input-section label { font-weight: bold; margin-bottom: 10px; display: block; }
.input-section input {
width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 8px;
font-size: 1.1em; transition: border-color 0.3s;
}
.input-section input:focus { border-color: #4caf50; outline: none; }
.control-buttons {
display: flex;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap;
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: #ffffff;
border-radius: 20px;
padding: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
justify-content: center;
max-width: 300px;
}
.btn {
background: linear-gradient(45deg, #4caf50, #45a049); color: #fff; border: none;
padding: 8px 16px; border-radius: 20px; cursor: pointer; font-size: 0.9em;
transition: all 0.3s ease; font-weight: bold; white-space: nowrap;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); }
.btn.secondary { background: linear-gradient(45deg, #2196f3, #1976d2); }
.btn.danger { background: linear-gradient(45deg, #f44336, #d32f2f); }
/* 진행률 표시 */
.progress-section {
background: #e3f2fd; border-radius: 15px; padding: 20px; margin: 20px 0;
text-align: center;
}
.progress-bar {
width: 100%; height: 15px; background: #ddd; border-radius: 10px;
overflow: hidden; margin: 15px 0;
}
.progress-fill {
height: 100%; background: linear-gradient(90deg, #4caf50, #2196f3);
width: 0%; transition: width 0.5s ease; border-radius: 10px;
}
.step-counter {
display: flex; justify-content: space-between; margin: 20px 0;
}
.step-circle {
width: 40px; height: 40px; border-radius: 50%; background: #ddd;
display: flex; align-items: center; justify-content: center;
font-weight: bold; color: #666; transition: all 0.3s ease;
}
.step-circle.active { background: #4caf50; color: #fff; transform: scale(1.2); }
.step-circle.completed { background: #2196f3; color: #fff; }
/* 단계별 섹션 */
.step-section {
background: #fff; border-radius: 15px; padding: 30px; margin: 30px 0;
border: 2px solid #e0e0e0; opacity: 1; transition: all 0.5s ease;
}
.step-section.active {
opacity: 1; border-color: #4caf50;
}
.step-title {
font-size: 1.5em; font-weight: bold; margin-bottom: 20px;
display: flex; align-items: center; gap: 10px;
}
/* 1단계: 인코더-디코더 */
.encoder-decoder {
display: grid; grid-template-columns: 1fr 100px 1fr; gap: 30px; margin: 20px 0;
align-items: center;
}
.io-box {
background: #f5f5f5; border-radius: 10px; padding: 20px; text-align: center;
min-height: 150px; display: flex; flex-direction: column; justify-content: center;
transition: all 0.3s ease;
}
.io-box.encoder { border-left: 5px solid #4caf50; }
.io-box.decoder { border-left: 5px solid #ff9800; }
.io-box.active { background: #e8f5e8; transform: scale(1.05); }
.arrow {
font-size: 3em; color: #666; text-align: center;
animation: none;
}
/* 2단계: 포지셔널 인코딩 */
.positional-demo {
display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin: 20px 0;
}
.encoding-comparison {
background: #f5f5f5; border-radius: 10px; padding: 20px;
}
.word-item {
display: flex; justify-content: space-between; align-items: center;
padding: 10px; margin: 5px 0; border-radius: 8px; background: #fff;
transition: all 0.3s ease;
}
.word-item.highlight { background: #ffeb3b; transform: translateX(5px); }
.position-value {
font-family: monospace; font-size: 0.9em; color: #666;
}
/* 3-6단계: 어텐션 시각화 */
.attention-container {
margin: 20px 0;
}
/* Q, K, V 설명 섹션 */
.qkv-explanation {
background: #f0f8ff; border-radius: 15px; padding: 25px; margin: 20px 0;
border-left: 5px solid #2196f3;
}
.qkv-container {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0;
}
.qkv-box {
background: #fff; border-radius: 10px; padding: 20px; text-align: center;
border: 2px solid #ddd; transition: all 0.3s ease;
}
.qkv-box.query { border-color: #4caf50; }
.qkv-box.key { border-color: #ff9800; }
.qkv-box.value { border-color: #9c27b0; }
.qkv-box:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
.qkv-box h5 { margin-bottom: 10px; font-size: 1.1em; }
.qkv-box.query h5 { color: #4caf50; }
.qkv-box.key h5 { color: #ff9800; }
.qkv-box.value h5 { color: #9c27b0; }
.qkv-box p { font-size: 0.9em; color: #666; margin-bottom: 15px; }
.mini-matrix {
display: grid; grid-template-columns: repeat(3, 30px); gap: 2px; justify-content: center;
}
.mini-cell {
width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
font-size: 0.7em; font-weight: bold; color: #fff; border-radius: 3px;
}
.attention-formula {
background: #fff; border-radius: 10px; padding: 20px; margin: 20px 0; text-align: center;
border: 2px solid #2196f3;
}
.formula {
font-size: 1.2em; font-weight: bold; color: #2196f3; margin-top: 10px;
font-family: 'Times New Roman', serif;
}
.tokens-row {
display: flex; gap: 15px; margin: 20px 0; justify-content: center; flex-wrap: wrap;
}
.token {
background: #e3f2fd; border: 2px solid #2196f3; border-radius: 10px;
padding: 15px 20px; text-align: center; min-width: 80px;
transition: all 0.3s ease; cursor: pointer; position: relative;
}
.token:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(33,150,243,0.3); }
.token.selected { background: #2196f3; color: #fff; transform: scale(1.1); }
.token.focused { background: #ff9800; color: #fff; transform: scale(1.15); }
.attention-matrix {
display: grid; gap: 3px; margin: 20px 0; justify-content: center;
background: #f5f5f5; padding: 20px; border-radius: 10px;
}
.attention-cell {
width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
border-radius: 5px; font-size: 0.8em; font-weight: bold; color: #fff;
transition: all 0.3s ease; cursor: pointer;
}
.attention-cell:hover { transform: scale(1.2); z-index: 10; }
/* 멀티헤드 */
.multihead-container {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;
}
.head-box {
background: #f8f9fa; border-radius: 10px; padding: 15px; text-align: center;
border: 2px solid #ddd; transition: all 0.3s ease;
}
.head-box.active { border-color: #9c27b0; background: #f3e5f5; }
.head-title { font-weight: bold; margin-bottom: 10px; color: #9c27b0; }
/* 연결선 애니메이션 */
.connection-line {
position: absolute; height: 3px; background: linear-gradient(90deg, #4caf50, #2196f3);
border-radius: 2px; opacity: 0; transition: opacity 0.5s ease;
pointer-events: none; z-index: 100;
}
.connection-line.active { opacity: 0.8; }
/* 정보 패널 */
.info-panel {
background: #fff3e0; border-radius: 15px; padding: 25px; margin: 25px 0;
border-left: 5px solid #ff9800;
}
.info-panel h3 { color: #ef6c00; margin-bottom: 15px; }
/* 키프레임 애니메이션 */
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
@keyframes slideIn { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-5px); } 60% { transform: translateY(-3px); } }
.slide-in { animation: slideIn 0.8s ease; }
.fade-in { animation: fadeIn 0.6s ease; }
.bounce { animation: bounce 1s; }
/* 학습 과정 스타일 */
.training-container { margin: 20px 0; }
.training-controls {
display: flex; align-items: center; gap: 20px; margin-bottom: 30px;
background: #f8f9fa; padding: 20px; border-radius: 10px;
}
.training-progress { flex: 1; }
.comparison-container { margin: 30px 0; }
.matrix-comparison {
display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0;
}
.matrix-before, .matrix-after {
background: #f5f5f5; padding: 15px; border-radius: 10px; text-align: center;
}
.matrix-before h5 { color: #f44336; }
.matrix-after h5 { color: #4caf50; }
/* 테스트 스타일 */
.test-container {
margin: 20px 0;
background: #f8f9ff;
border-radius: 15px;
padding: 25px;
}
/* 새로운 비교 테스트 스타일 */
.comparison-mode {
display: flex;
gap: 20px;
margin: 15px 0;
align-items: center;
}
.comparison-mode label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
padding: 8px 15px;
border-radius: 20px;
background: #e8f2ff;
transition: all 0.3s ease;
}
.comparison-mode label:hover {
background: #d0e7ff;
}
.attention-comparison-results {
margin-top: 30px;
}
.comparison-metrics {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 3px 15px rgba(0,0,0,0.1);
border: 2px solid #e1e8ed;
}
.metric-card h5 {
margin: 0 0 15px 0;
color: #2c3e50;
font-size: 1.1em;
}
.metric-bars {
display: flex;
flex-direction: column;
gap: 10px;
}
.metric-bar {
display: flex;
align-items: center;
gap: 10px;
}
.metric-bar span:first-child {
min-width: 70px;
font-size: 0.9em;
color: #666;
}
.metric-bar span:last-child {
min-width: 40px;
font-weight: bold;
font-size: 0.9em;
}
.bar {
height: 20px;
flex-grow: 1;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.bar.before {
background: #ffebee;
border: 1px solid #ffcdd2;
}
.bar.after {
background: #e8f5e8;
border: 1px solid #c8e6c9;
}
.bar .fill {
height: 100%;
border-radius: 10px;
transition: width 1s ease-in-out;
}
.bar.before .fill {
background: linear-gradient(90deg, #ff5722, #ff8a65);
}
.bar.after .fill {
background: linear-gradient(90deg, #4caf50, #81c784);
}
.improvement-score {
text-align: center;
}
.score-circle {
width: 80px;
height: 80px;
border-radius: 50%;
margin: 0 auto 15px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: conic-gradient(from 0deg, #4caf50 0%, #4caf50 var(--progress, 0%), #e0e0e0 var(--progress, 0%));
}
.score-circle::before {
content: '';
position: absolute;
width: 60px;
height: 60px;
border-radius: 50%;
background: white;
}
.score-circle span {
position: relative;
z-index: 1;
font-weight: bold;
font-size: 1.2em;
color: #2c3e50;
}
.matrix-comparison-test {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 30px;
align-items: start;
margin-bottom: 30px;
}
.test-matrix-before, .test-matrix-after {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 3px 15px rgba(0,0,0,0.1);
}
.test-matrix-before {
border-left: 4px solid #f44336;
}
.test-matrix-after {
border-left: 4px solid #4caf50;
}
.comparison-arrow {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.arrow-container {
text-align: center;
}
.learning-arrow {
font-size: 2em;
display: block;
margin-bottom: 10px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.matrix-analysis {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.matrix-analysis p {
margin: 5px 0;
font-size: 0.9em;
color: #666;
}
.interactive-features {
background: #f0f7ff;
border-radius: 12px;
padding: 20px;
border: 2px dashed #4a90e2;
}
.interactive-features h5 {
margin: 0 0 15px 0;
color: #2c3e50;
}
.feature-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.feature-btn {
padding: 8px 16px;
font-size: 0.9em;
border-radius: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.feature-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.highlight-difference {
border: 3px solid #ff9800 !important;
box-shadow: 0 0 20px rgba(255, 152, 0, 0.5) !important;
animation: glow 1.5s ease-in-out infinite alternate;
}
@keyframes glow {
from { box-shadow: 0 0 20px rgba(255, 152, 0, 0.5); }
to { box-shadow: 0 0 30px rgba(255, 152, 0, 0.8); }
}
.heatmap-mode .attention-cell {
transition: all 0.3s ease;
}
.evolution-animation {
animation: evolve 3s ease-in-out;
}
@keyframes evolve {
0% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 0.7; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}
/* Q,K,V 매트릭스 비교 스타일 */
.qkv-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin: 20px 0;
}
.qkv-before, .qkv-after {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 3px 15px rgba(0,0,0,0.1);
}
.qkv-before {
border-left: 4px solid #f44336;
}
.qkv-after {
border-left: 4px solid #4caf50;
}
.qkv-matrices {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 15px;
margin-top: 15px;
}
.qkv-matrix h6 {
margin: 0 0 10px 0;
text-align: center;
color: #2c3e50;
font-size: 0.9em;
}
.mini-matrix {
display: grid;
grid-template-columns: repeat(3, 30px);
gap: 2px;
justify-content: center;
}
.mini-matrix .mini-cell {
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7em;
font-weight: bold;
color: white;
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
}
/* 추가 스타일 */
.attention-comparison-results {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.attention-cell.highlighted {
border: 2px solid #ff6b35 !important;
transform: scale(1.1);
z-index: 10;
position: relative;
}
.attention-cell:hover {
transform: scale(1.05);
transition: all 0.2s ease;
border: 2px solid #4a90e2;
z-index: 5;
position: relative;
}
/* 매트릭스 라벨 스타일 */
.matrix-label-cell {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8em;
font-weight: bold;
color: #2c3e50;
background: #f8f9fa;
border: 1px solid #dee2e6;
}
.matrix-label-cell.empty {
background: transparent;
border: none;
}
.matrix-label-cell.column-label {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
border-bottom: 2px solid #2196f3;
writing-mode: vertical-rl;
text-orientation: mixed;
height: 40px;
}
.matrix-label-cell.row-label {
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
border-right: 2px solid #4caf50;
width: 80px;
height: 40px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.matrix-label-cell:hover {
background: #fff3cd;
color: #856404;
transform: scale(1.02);
transition: all 0.2s ease;
}
@media (max-width: 768px) {
.comparison-metrics {
grid-template-columns: 1fr;
}
.matrix-comparison-test {
grid-template-columns: 1fr;
gap: 20px;
}
.comparison-arrow {
transform: rotate(90deg);
padding: 10px 0;
}
.feature-buttons {
justify-content: center;
}
.control-buttons {
position: fixed;
top: 10px;
left: 50%;
right: auto;
transform: translateX(-50%);
max-width: 90vw;
gap: 8px;
padding: 10px;
}
.btn {
padding: 6px 12px;
font-size: 0.8em;
}
body {
padding: 120px 10px 20px 10px;
}
.matrix-label-cell.column-label {
font-size: 0.7em;
height: 35px;
}
.matrix-label-cell.row-label {
font-size: 0.7em;
width: 70px;
height: 35px;
}
.attention-cell {
width: 35px !important;
height: 35px !important;
font-size: 0.7em;
}
}
.test-input {
background: #e3f2fd; padding: 20px; border-radius: 10px; margin-bottom: 20px;
}
.test-results {
display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;
}
.result-box {
background: #fff; padding: 15px; border-radius: 8px; border: 2px solid #ddd;
min-height: 60px; display: flex; align-items: center; justify-content: center;
}
.confidence-bar {
position: relative; width: 100%; height: 30px; background: #ddd;
border-radius: 15px; overflow: hidden;
}
.confidence-fill {
height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a);
width: 0%; transition: width 1s ease;
}
.confidence-bar span {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-weight: bold; color: #333;
}
.attention-matrix.small { max-width: 200px; margin: 0 auto; }
/* 반응형 */
@media (max-width: 768px) {
.encoder-decoder { grid-template-columns: 1fr; }
.positional-demo { grid-template-columns: 1fr; }
.tokens-row { justify-content: center; }
.matrix-comparison { grid-template-columns: 1fr; }
.test-results { grid-template-columns: 1fr; }
}
/* 🕸️ 그래프 시각화 스타일 */
.graph-container {
display: flex;
flex-direction: column;
gap: 20px;
margin-top: 20px;
}
.attention-graph {
background: white;
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
display: flex;
justify-content: center;
}
.graph-controls {
display: flex;
gap: 20px;
margin: 15px 0;
align-items: center;
}
.graph-controls label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
padding: 8px 15px;
background: #fff;
border-radius: 20px;
border: 2px solid #e0e0e0;
transition: all 0.3s ease;
}
.graph-controls label:hover {
border-color: #4285f4;
background: #f0f7ff;
}
.graph-legend {
background: #f8f9ff;
border-radius: 10px;
padding: 15px;
border-left: 4px solid #4285f4;
}
.legend-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
}
.line-sample {
width: 40px;
height: 3px;
border-radius: 2px;
}
.line-sample.thick {
background: #ff4444;
height: 4px;
opacity: 0.9;
}
.line-sample.medium {
background: #ffaa00;
height: 3px;
opacity: 0.7;
}
.line-sample.thin {
background: #cccccc;
height: 2px;
opacity: 0.5;
}
.graph-analysis {
background: white;
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.analysis-metrics {
display: flex;
justify-content: space-around;
gap: 20px;
}
.metric-item {
text-align: center;
flex: 1;
}
.metric-item h6 {
margin: 0 0 10px 0;
color: #666;
font-size: 14px;
}
.metric-item div {
font-size: 24px;
font-weight: bold;
color: #4285f4;
}
/* SVG 그래프 스타일 */
#graphSvg {
border: 1px solid #e0e0e0;
border-radius: 10px;
background: #fafafa;
}
.graph-node {
cursor: pointer;
transition: all 0.3s ease;
}
.graph-node:hover {
transform: scale(1.1);
}
.graph-edge {
transition: all 0.3s ease;
}
.graph-edge:hover {
stroke-width: 4;
}
.node-label {
font-family: 'Noto Sans KR', sans-serif;
font-size: 12px;
font-weight: 500;
text-anchor: middle;
fill: #333;
pointer-events: none;
}
/* 🧩 컨텍스트 벡터 패널 (이미지 스타일) */
.context-panel { background:#fff; border:1px solid #e5eaf3; border-radius:12px; padding:16px; margin:14px 0; }
.context-title { font-weight:700; color:#2c3e50; margin-bottom:8px; }
.context-grid { display:grid; grid-template-columns: 70px 200px 28px 140px 28px 200px; gap:10px 12px; align-items:center; }
.ctx-label { font-weight:600; color:#37474f; text-align:right; }
.hs-box { border:2px solid #1a237e; border-radius:8px; padding:8px 10px; display:flex; gap:8px; align-items:center; background:#f7f9ff; }
.vec-dot { width:18px; height:18px; border-radius:50%; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); }
.mul { text-align:center; font-weight:700; color:#455a64; }
.arrow { text-align:center; color:#000; font-weight:700; }
.plus { text-align:center; color:#000; font-weight:700; }
.eq { text-align:center; font-weight:800; color:#263238; }
.weight-box { display:flex; align-items:center; gap:8px; }
.weight-bar { flex:1; height:14px; background:#eceff1; border:1px solid #cfd8dc; border-radius:7px; position:relative; }
.weight-fill { position:absolute; left:0; top:0; height:100%; border-radius:7px; }
.weight-val { min-width:56px; text-align:left; font-family:monospace; color:#455a64; font-weight:700; }
.ctx-box { border:2px solid #263238; border-radius:8px; padding:8px 10px; display:flex; gap:8px; align-items:center; background:#ffffff; }
.ctx-right { grid-column: 6 / 7; }
/* 🎭 마스킹 시뮬레이션 스타일 */
.masking-simulation {
background: #f8f9ff;
border-radius: 15px;
padding: 20px;
margin: 20px 0;
border-left: 4px solid #9c27b0;
}
.masking-demo {
display: flex;
align-items: center;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
}
.masking-step {
text-align: center;
flex: 1;
min-width: 200px;
}
.masking-arrow {
text-align: center;
margin: 0 20px;
}
.masking-arrow p {
margin: 5px 0;
font-size: 0.9em;
color: #666;
}
.attention-matrix.small {
max-width: 200px;
margin: 0 auto;
}
.attention-matrix.small .attention-cell {
width: 30px !important;
height: 30px !important;
font-size: 0.7em !important;
}
.attention-matrix.small .matrix-label-cell {
width: auto !important;
height: auto !important;
padding: 4px 6px;
font-size: 0.75em !important;
white-space: nowrap;
}
.masking-explanation {
background: white;
border-radius: 10px;
padding: 15px;
margin-top: 15px;
}
.masking-explanation h6 {
margin: 0 0 10px 0;
color: #9c27b0;
}
.masking-explanation ul {
margin: 0;
padding-left: 20px;
}
.masked-cell {
background: #ffebee !important;
color: #d32f2f !important;
position: relative;
}
.masked-cell::after {
content: '🚫';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.8em;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1> 👍어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)</h1>
<h2>인코더·디코더 → 포지셔널 인코딩 → 어텐션 → 셀프 어텐션 → 멀티헤드 → 최종 결과 → 학습 과정</h2>
<p> Attention메커니즘을 한국의 학생들이 보다 쉽게 이해할 수 있도록 구현한 시뮬레이션 사이트(일종의 explainer)</p>
<p> 오류발생시 roughkyo@gmail.com로 회신 부탁드립니다.</p>
</div>
<div class="content">
<!-- 제어 패널 -->
<div class="control-panel">
<h3>🎮 실험 제어판</h3>
<div class="input-section">
<label for="inputSentence">예시 문단 선택:</label>
<select id="inputSentence" onchange="loadSelectedText()">
<option value="">문단을 선택하세요</option>
<option value="오늘은 날씨가 정말 좋습니다. 하늘이 맑고 바람이 시원하게 불어옵니다. 친구들과 함께 공원에서 산책을 하며 즐거운 시간을 보냈습니다.">문단 1: 날씨와 산책</option>
<option value="어머니는 매일 아침 일찍 일어나서 가족을 위해 맛있는 아침식사를 준비하십니다. 따뜻한 밥과 김치찌개, 그리고 신선한 야채들로 상을 차려주십니다. 우리 가족은 함께 모여서 즐겁게 식사를 합니다.">문단 2: 가족의 아침</option>
<option value="학교에서 새로운 선생님이 오셨습니다. 선생님은 매우 친절하시고 수업을 재미있게 해주십니다. 학생들은 모두 선생님을 좋아하며 열심히 공부하고 있습니다.">문단 3: 새로운 선생님</option>
<option value="도서관은 조용하고 평화로운 공간입니다. 많은 사람들이 책을 읽거나 공부를 하고 있습니다. 나는 좋아하는 소설책을 찾아서 편안한 의자에 앉아 읽기 시작했습니다.">문단 4: 도서관에서</option>
<option value="주말에 가족들과 함께 바다로 여행을 갔습니다. 파란 바다와 하얀 모래사장이 정말 아름다웠습니다. 우리는 바닷가에서 조개를 줍고 파도와 함께 놀았습니다.">문단 5: 바다 여행</option>
</select>
<div id="selectedText" style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 5px; min-height: 60px; display: none;">
선택한 문단이 여기에 표시됩니다.
</div>
</div>
<div class="control-buttons">
<button class="btn" onclick="startStepSimulation()">🚀 단계별 시뮬레이션 시작</button>
<button class="btn secondary" onclick="nextStep()">➡️ 다음 단계</button>
<button class="btn secondary" onclick="prevStep()">⬅️ 이전 단계</button>
<button class="btn danger" onclick="resetSimulation()">🔄 초기화</button>
</div>
</div>
<!-- 진행률 표시 -->
<div class="progress-section">
<h4>📊 진행 상황</h4>
<div class="step-counter">
<div class="step-circle" id="step-0">1</div>
<div class="step-circle" id="step-1">2</div>
<div class="step-circle" id="step-2">3</div>
<div class="step-circle" id="step-3">4</div>
<div class="step-circle" id="step-4">5</div>
<div class="step-circle" id="step-5">6</div>
<div class="step-circle" id="step-6">7</div>
<div class="step-circle" id="step-7">8</div>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div id="currentStep">단계별 시뮬레이션을 시작하세요!</div>
</div>
<!-- 1단계: 인코더-디코더 -->
<div class="step-section" id="section-0">
<div class="step-title">
<span>🔢</span>
<span>1단계: 인코더-디코더 구조</span>
</div>
<div class="encoder-decoder">
<div class="io-box encoder" id="encoderBox">
<h4>🔢 인코더 (Encoder)</h4>
<div id="encoderText">한글 입력 대기 중...</div>
<div id="encoderTokens" class="tokens-row"></div>
</div>
<div class="arrow">➡️</div>
<div class="io-box decoder" id="decoderBox">
<h4>🎯 디코더 (Decoder)</h4>
<div id="decoderText">영어 출력 대기 중...</div>
<div id="decoderTokens" class="tokens-row"></div>
</div>
</div>
</div>
<!-- 2단계: 포지셔널 인코딩 -->
<div class="step-section" id="section-1">
<div class="step-title">
<span>📍</span>
<span>2단계: 포지셔널 인코딩</span>
</div>
<div class="positional-demo">
<div class="encoding-comparison">
<h4>일반 워드 임베딩 (위치 정보 없음)</h4>
<div id="normalEmbedding"></div>
</div>
<div class="encoding-comparison">
<h4>포지셔널 인코딩 추가 (위치 정보 포함)</h4>
<div id="positionalEmbedding"></div>
</div>
</div>
</div>
<!-- 3-0단계: 멀티헤드 어텐션-->
<div class="step-section" id="section-2">
<div class="step-title">
<span>🎯</span>
<span>3-0단계: 멀티헤드 어텐션 시뮬</span>
</div>
<div class="multihead-container" id="mhAnyContainer"></div>
</div>
<!-- 3-1단계: 인코더 멀티헤드 셀프 어텐션 -->
<div class="step-section" id="section-3">
<div class="step-title">
<span>🔄</span>
<span>3-1단계: 인코더 멀티헤드 셀프 어텐션 (문장 내부 연관도)</span>
</div>
<div class="attention-container">
<div id="selfAttentionTokens" class="tokens-row"></div>
<div id="selfAttentionMatrix" class="attention-matrix"></div>
</div>
<div id="mhSelfScores" style="margin-top:16px;">
<div style="display:flex; gap:30px; justify-content:center; font-weight:600; color:#555;">
<div style="text-decoration: underline; text-decoration-color:#333;">Input</div>
<div style="color:#e91e63; text-decoration: underline; text-decoration-color:#f8a5b8;">Score 1</div>
<div style="color:#f9a825; text-decoration: underline; text-decoration-color:#ffe082;">Score 2</div>
</div>
<div style="text-align:center; margin-top:6px; color:#666; font-size:0.9em;">
입력 단어를 클릭하면, 두 헤드(Score 1/2)의 가장 강한 연결 2개만 선으로 표시됩니다.
</div>
<div id="esvRowWrapper" style="display:flex; gap:30px; justify-content:center; align-items:flex-start; margin-top:8px; position:relative;">
<div id="esvInput" style="display:flex; flex-direction:column; gap:6px;"></div>
<div id="esvScore1" style="display:flex; flex-direction:column; gap:6px;"></div>
<div id="esvScore2" style="display:flex; flex-direction:column; gap:6px;"></div>
<!-- 연결선 SVG 오버레이 -->
<svg id="esvOverlay" style="position:absolute; left:0; top:0; width:100%; height:100%; pointer-events:none;"></svg>
</div>
</div>
</div>
</div>
<!-- 3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션 -->
<div class="step-section" id="section-4">
<div class="step-title">
<span>🎭</span>
<span>3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션</span>
</div>
<div class="attention-container">
<div style="margin:6px 0 4px 0; color:#444; font-size:0.92em; line-height:1.6;">
입력 시퀀스는 <strong>[START]</strong>로 시작합니다. 디코더 마스크는 <strong>현재 시점과 미래 시점(j ≥ i)</strong>의 위치에 <code>-∞</code>(계산상 매우 큰 음수)를 적용하여 softmax 후 확률이 0이 되도록 합니다. 이로써 <strong>[START] 행 전체</strong>와 <strong>대각선(자기 자신)</strong>, 그리고 <strong>미래 토큰</strong>을 모두 볼 수 없게 만듭니다.
<div style="margin-top:6px; color:#666;">
<strong>설명</strong>: "그러나 디코더는 출력 시퀀스를 입력으로 한 번에 받기 때문에, 현재 시점의 단어를 예측하고자 할 때 입력 시퀀스 행렬로부터 미래 시점의 단어까지도 참고할 수 있는 현상이 발생한다. 이를 방지하기 위해 디코더는 예측해야 하는 것의 정답을 알면 안 되도록 설계해야 한다."
</div>
</div>
<div id="decoderMaskedMatrix" class="attention-matrix"></div>
</div>
</div>
<!-- 3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션 -->
<div class="step-section" id="section-5">
<div class="step-title">
<span>👁️</span>
<span>3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션</span>
</div>
<div class="multihead-container" id="crossMultiContainer"></div>
</div>
<!-- 4단계: 학습 전·후 인코더의 멀티헤드 셀프 어텐션 패턴 비교 -->
<div class="step-section" id="section-6">
<div class="step-title">
<span>🎓</span>
<span>4단계: 학습 전·후 인코더의 멀티헤드 셀프 어텐션 패턴 변화</span>
</div>
<div class="info-box" style="margin:12px 0; padding:12px; background:#f7faff; border:1px solid #e0ecff; border-radius:8px;">
<strong>중요:</strong> 학습으로 직접 바뀌는 것은 <em>Attention Heatmap</em>이 아니라 <em>Q/K/V를 생성하는 가중치 행렬(Wq, Wk, Wv)</em>입니다.
<div style="margin-top:6px; font-size:0.9em; color:#555;">
Attention(Q,K,V) = softmax(QK<sup>T</sup>/√d<sub>k</sub>)V, 여기서 Q= XWq, K= XWk, V= XWv
</div>
</div>
<div class="training-container">
<!-- 🧩 학습 전 컨텍스트(버튼 위) -->
<div id="contextBefore" class="context-panel">
<div class="context-title">학습 전: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)</div>
<div id="contextBeforeGrid" class="context-grid"></div>
</div>
<div class="training-controls">
<button class="btn" onclick="startTraining()">🚀 학습 시작</button>
<div class="training-progress">
<div class="progress-bar">
<div class="progress-fill" id="trainingProgress"></div>
</div>
<div id="trainingStatus">학습 대기 중...</div>
<div id="lossText" style="font-size: 0.9em; color: #666; margin-top: 6px;">손실: -</div>
</div>
</div>
<!-- 🧩 학습 후 컨텍스트(버튼 아래) -->
<div id="contextAfter" class="context-panel" style="display:none;">
<div class="context-title">학습 후: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)</div>
<div id="contextAfterGrid" class="context-grid"></div>
</div>
</div>
</div>
<!-- 5단계: 학습 후 디코더 출력 (Cross Attention 정렬 강화) -->
<div class="step-section" id="section-7">
<div class="step-title">
<span>🧩</span>
<span>5단계: 학습 후 디코더 출력 (Cross Attention)</span>
</div>
<div class="attention-container">
<div style="margin:6px 0 8px 0; color:#444; font-size:0.92em; line-height:1.6;">
학습 이후, 인코더-디코더 <strong>크로스 어텐션</strong>이 정렬처럼 강화되어 <em>각 한국어(인코더) 토큰</em>과 대응하는 <em>영어(디코더) 토큰</em>의 값이 가장 크게 나타납니다.
시각화는 흑백(Grayscale) 히트맵으로 표현하며, 밝을수록 강한 연결입니다.
</div>
<div id="decoderOutputAfter" class="attention-matrix"></div>
</div>
</div>
<!-- 정보 패널 -->
<div class="info-panel">
<h3>💡 현재 단계 설명</h3>
<div id="stepExplanation">
단계별 시뮬레이션을 시작하면 각 단계의 상세한 설명이 여기에 표시됩니다.
</div>
</div>
</div>
</div>
<script>
// 🎯 8단계 시뮬레이션 시스템
let currentStep = -1;
let isSimulationRunning = false;
let animationSpeed = 1500;
let tokens = [];
let translatedTokens = [];
let isModelTrained = false;
let finalAttentionMatrix = []; // 6단계에서 생성된 실제 어텐션 매트릭스
let trainingData = {
beforeAttention: [],
afterAttention: [],
beforeSelfAttention: [],
afterSelfAttention: []
};
// 3-1 최근 헤드 가중치 (리사이즈 시 재그리기용)
let __lastSelfHeads = null;
// 📚 한글 → 영어 자동 번역 데이터베이스 (확장됨)
const translationDB = {
// 기본 어텐션 관련 용어
'트랜스포머를': 'transformer',
'트랜스포머': 'transformer',
'트랜스포머의': 'transformer\\'s',
'학습해보자': 'let\\'s learn',
'학습': 'learning',
'학습하기': 'learning',
'해보자': 'let\\'s try',
'위해': 'for',
'위해서는': 'in order to',
'어텐션': 'attention',
'구조': 'structure',
'구조를': 'structure',
'전체적으로': 'comprehensively',
'살펴봐야': 'should examine',
'살펴보자': 'let\\'s examine',
'살펴볼꺼야': 'will examine',
'어머니가': 'mother is',
'어텐션이니까': 'because attention',
// 인명 및 일반 명사
'철수는': 'Cheolsu',
'철수': 'Cheolsu',
'영희는': 'Younghee',
'영희': 'Younghee',
'민수': 'Minsu',
'지영': 'Jiyoung',
// 시간 관련
'어제': 'yesterday',
'오늘은': 'today',
'오늘': 'today',
'내일': 'tomorrow',
'지금': 'now',
'나중에': 'later',
'아침에': 'in the morning',
'아침': 'morning',
'아침식사를': 'breakfast',
'아침식사': 'breakfast',
'매일': 'every day',
'일찍': 'early',
'저녁에': 'in the evening',
'밤에': 'at night',
'밤새도록': 'all night long',
'주말에': 'on weekend',
// 날씨 관련 (문단 1)
'날씨가': 'weather is',
'날씨': 'weather',
'정말': 'really',
'좋습니다.': 'is good.',
'좋습니다': 'is good',
'좋다': 'good',
'좋아하는': 'favorite',
'좋아하며': 'like and',
'좋아': 'like',
'하늘이': 'sky is',
'하늘': 'sky',
'맑고': 'clear and',
'맑은': 'clear',
'바람이': 'wind is',
'바람': 'wind',
'시원하게': 'coolly',
'시원한': 'cool',
'불어옵니다.': 'blows.',
'불어옵니다': 'blows',
'오늘': 'today',
'내일': 'tomorrow',
'지금': 'now',
'나중에': 'later',
'아침에': 'in the morning',
'아침': 'morning',
'아침식사를': 'breakfast',
'아침식사': 'breakfast',
'매일': 'every day',
'일찍': 'early',
'저녁에': 'in the evening',
'밤에': 'at night',
'밤새도록': 'all night long',
'주말에': 'on weekend',
// 장소 관련
'도서관에서': 'at library',
'도서관은': 'library is',
'도서관': 'library',
'학교에서': 'at school',
'학교': 'school',
'집에서': 'at home',
'집': 'home',
'교실': 'classroom',
'카페': 'cafe',
'공원에서': 'in the park',
'공원': 'park',
'바다로': 'to the sea',
'바다와': 'sea and',
'바다': 'sea',
'바닷가에서': 'at the beach',
'바닷가': 'beach',
'공간입니다': 'is a space',
'공간': 'space',
// 날씨 관련
'날씨가': 'weather is',
'날씨': 'weather',
'정말': 'really',
'좋습니다': 'is good',
'좋다': 'good',
'좋아하는': 'favorite',
'좋아하며': 'like and',
'좋아': 'like',
'하늘이': 'sky is',
'하늘': 'sky',
'맑고': 'clear and',
'맑은': 'clear',
'바람이': 'wind is',
'바람': 'wind',
'시원하게': 'coolly',
'시원한': 'cool',
'불어옵니다': 'blows',
'파란': 'blue',
'하얀': 'white',
'아름다웠습니다': 'was beautiful',
'아름다운': 'beautiful',
// 사람 관련
'친구들과': 'with friends',
'친구들': 'friends',
'친구': 'friend',
'함께': 'together',
'가족을': 'family',
'가족들과': 'with family',
'가족은': 'family is',
'가족': 'family',
'어머니는': 'mother',
'어머니': 'mother',
'선생님이': 'teacher',
'선생님은': 'teacher is',
'선생님을': 'teacher',
'선생님': 'teacher',
'학생들은': 'students',
'학생들': 'students',
'사람들이': 'people',
'사람들': 'people',
'우리는': 'we',
'우리': 'we',
'나는': 'I',
'나': 'I',
// 행동 관련 (동사)
'산책을': 'walk',
'산책': 'walk',
'하며': 'while doing',
'보냈습니다': 'spent',
'보냈다': 'spent',
'일어나서': 'waking up',
'일어나': 'wake up',
'준비하십니다': 'prepares',
'준비': 'prepare',
'차려주십니다': 'serves',
'모여서': 'gathering',
'식사를': 'meal',
'식사': 'meal',
'합니다': 'do',
'하고': 'do',
'오셨습니다': 'came',
'해주십니다': 'does for us',
'공부를': 'study',
'공부하고': 'studying',
'공부': 'study',
'있습니다': 'exist',
'있고': 'exist and',
'있다': 'exist',
'읽거나': 'reading or',
'읽기': 'reading',
'읽고': 'read and',
'찾아서': 'finding',
'찾아': 'find',
'앉아': 'sitting',
'시작했습니다': 'started',
'시작': 'start',
'갔습니다': 'went',
'갔다': 'went',
'줍고': 'picking up',
'놀았습니다': 'played',
'놀았다': 'played',
'빌렸다': 'borrowed',
'빌려서': 'borrowing',
'먹으면서': 'while eating',
'보았다': 'watched',
'보고': 'watching',
// 음식 관련
'맛있는': 'delicious',
'따뜻한': 'warm',
'밥과': 'rice and',
'밥': 'rice',
'김치찌개': 'kimchi stew',
'김치찌개,': 'kimchi stew,',
'신선한': 'fresh',
'야채들로': 'with vegetables',
'야채': 'vegetables',
'상을': 'table',
'상': 'table',
'치킨과': 'chicken and',
'치킨': 'chicken',
'만화책을': 'comic book',
'만화책': 'comic book',
// 형용사 및 상태
'새로운': 'new',
'매우': 'very',
'친절하시고': 'kind and',
'친절한': 'kind',
'재미있게': 'interestingly',
'재미있는': 'interesting',
'모두': 'all',
'열심히': 'diligently',
'조용하고': 'quiet and',
'조용한': 'quiet',
'평화로운': 'peaceful',
'많은': 'many',
'편안한': 'comfortable',
'즐거운': 'joyful',
'즐겁게': 'joyfully',
// 물건 관련
'책을': 'book',
'책': 'book',
'소설책을': 'novel',
'소설책': 'novel',
'의자에': 'on chair',
'의자': 'chair',
'모래사장이': 'sand beach',
'모래사장': 'sand beach',
'파도와': 'waves and',
'파도': 'waves',
'조개를': 'shells',
'조개': 'shells',
// 수업 관련
'수업을': 'class',
'수업': 'class',
// 여행 관련
'여행을': 'trip',
'여행': 'trip',
// 장소 지정어
'에서': 'at',
'에': 'to',
'로': 'to',
'와': 'and',
'과': 'and',
'도': 'also',
'만': 'only',
'을': 'object marker',
'를': 'object marker',
'이': 'subject marker',
'가': 'subject marker',
'은': 'topic marker',
'는': 'topic marker',
'의': 'possessive',
'시간을': 'time',
'시간': 'time',
// 추가 기본 단어들
'안녕': 'hello',
'세상': 'world',
'학생': 'student',
'이다': 'am',
'딥러닝': 'deep learning',
'모델': 'model',
'훈련': 'training',
'자연어': 'natural language',
'처리': 'processing',
'인공지능': 'artificial intelligence',
'기계': 'machine',
'번역': 'translation',
'언어': 'language',
'신경망': 'neural network',
'공부': 'study',
'모델을': 'model',
'훈련하자': 'let\\'s train',
'딥러닝을': 'deep learning',
'공부하자': 'let\\'s study',
'배우자': 'let\\'s learn',
'시작하자': 'let\\'s start',
'만들어보자': 'let\\'s create',
'실습해보자': 'let\\'s practice',
'중요한': 'important',
'중요하다': 'important',
'개념': 'concept',
'이해': 'understanding',
'이해하기': 'understanding',
'필요하다': 'necessary',
'필요한': 'necessary',
'과정': 'process',
'방법': 'method',
'기술': 'technology',
'데이터': 'data',
'알고리즘': 'algorithm',
'성능': 'performance',
'결과': 'result',
'분석': 'analysis',
'예측': 'prediction',
'정확도': 'accuracy',
// 영어 → 한글 번역
'transformer': '트랜스포머',
'transformer\\'s': '트랜스포머의',
'let\\'s learn': '학습해보자',
'learning': '학습',
'let\\'s try': '해보자',
'for': '위해',
'attention': '어텐션',
'structure': '구조',
'comprehensively': '전체적으로',
'should examine': '살펴봐야',
'let\\'s examine': '살펴보자',
'will examine': '살펴볼꺼야',
'mother is': '어머니가',
'because attention': '어텐션이니까',
'hello': '안녕',
'world': '세상',
'I': '나는',
'student': '학생',
'am': '이다',
'deep learning': '딥러닝',
'model': '모델',
'training': '훈련',
'natural language': '자연어',
'processing': '처리',
'artificial intelligence': '인공지능',
'machine': '기계',
'translation': '번역',
'language': '언어',
'neural network': '신경망',
'study': '공부',
'let\\'s train': '훈련하자',
'let\\'s study': '공부하자',
'let\\'s start': '시작하자',
'let\\'s create': '만들어보자',
'let\\'s practice': '실습해보자',
'important': '중요한',
'concept': '개념',
'understanding': '이해',
'necessary': '필요한',
'process': '과정',
'method': '방법',
'technology': '기술',
'data': '데이터',
'algorithm': '알고리즘',
'performance': '성능',
'result': '결과',
'analysis': '분석',
'prediction': '예측',
'accuracy': '정확도'
};
// 📊 단계별 설명
const stepExplanations = {
0: { title: "인코더-디코더 구조", content: "🔢 한글을 입력하면 자동으로 영어 문장이 생성됩니다. 인코더는 입력을 이해하고, 디코더는 출력을 생성합니다." },
1: { title: "포지셔널 인코딩", content: "📍 단어 순서 정보를 추가합니다. 위치 인코딩으로 문맥 흐름을 반영합니다." },
2: { title: "3-0 멀티헤드 어텐션(임의)", content: "🎯 임의 문장으로 여러 헤드가 각기 다른 패턴(문법/의미/위치/문맥)을 잡는 모습을 봅니다." },
3: { title: "3-1 인코더 MHA(셀프)", content: "🔄 인코더 안에서 같은 문장 토큰끼리의 연관도(셀프 어텐션)를 멀티헤드로 시각화합니다." },
4: { title: "3-2 디코더 MHA(마스크드 셀프)", content: "🎭 디코더의 마스크드 셀프 어텐션을 보여주며 미래 토큰이 가려지는 효과를 확인합니다." },
5: { title: "3-3 인코더-디코더 크로스 MHA", content: "👁️ 인코더의 출력과 디코더의 현재 상태 사이의 크로스 어텐션 히트맵을 봅니다." },
6: { title: "4 학습 전·후 어텐션 레이어와 가중치 변화", content: "🎓 행별 softmax 가중치(합=1)와 해당 가중합(Weighted Sum) 벡터가 학습으로 어떻게 달라지는지 시각화합니다." },
7: { title: "5 학습 후 디코더 출력 (Cross Attention)", content: "🧩 학습 이후, 인코더(KR) ↔ 디코더(EN)의 정렬이 강화된 그레이스케일 히트맵을 확인합니다." }
};
// 🚀 메인 시뮬레이션 시작
async function startStepSimulation() {
if (isSimulationRunning) return;
isSimulationRunning = true;
currentStep = -1;
const inputText = document.getElementById('inputSentence').value.trim();
if (!inputText) {
alert('예시 문단을 선택해주세요!');
isSimulationRunning = false;
return;
}
// 입력 언어 감지
const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
// 토큰화 (입력 언어 - 단어 단위) - 길이 제한 증가
tokens = inputText.trim().split(/\\s+/).slice(0, 15); // 8개에서 15개로 증가
// 번역 생성 (양방향)
translatedTokens = generateTranslation(inputText);
console.log('🎯 시뮬레이션 시작:', {
inputLanguage: isKorean ? '한글' : '영어',
tokens,
translatedTokens
});
await nextStep();
}
// 🔄 다음 단계로 이동
async function nextStep() {
if (currentStep >= 7) return;
currentStep++;
await executeStep(currentStep);
}
// ⬅️ 이전 단계로 이동
async function prevStep() {
if (currentStep <= 0) return;
currentStep--;
await executeStep(currentStep);
}
// 🎯 특정 단계 실행
async function executeStep(stepIndex) {
updateProgress(stepIndex);
updateStepExplanation(stepIndex);
// 모든 섹션 비활성화
document.querySelectorAll('.step-section').forEach(section => {
section.classList.remove('active');
});
// 현재 단계 활성화
const currentSection = document.getElementById(`section-${stepIndex}`);
if (currentSection) {
currentSection.classList.add('active');
currentSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// 단계별 실행
switch(stepIndex) {
case 0: await executeEncoderDecoder(); break; // 1단계
case 1: await executePositionalEncoding(); break; // 2단계
case 2: await executeMHAnySentence(); break; // 3-0단계
case 3: await executeEncoderSelfMH(); break; // 3-1단계
case 4: await executeDecoderMaskedSelfMH(); break; // 3-2단계
case 5: await executeCrossMultiHead(); break; // 3-3단계
case 6: await executeTrainingSimulation(); break; // 4단계
case 7: await executeDecoderOutputAfter(); break; // 5단계
}
}
// 📊 진행률 업데이트
function updateProgress(stepIndex) {
const progress = ((stepIndex + 1) / 8) * 100;
document.getElementById('progressFill').style.width = `${progress}%`;
// 단계 원 업데이트
document.querySelectorAll('.step-circle').forEach((circle, index) => {
circle.classList.remove('active', 'completed');
if (index < stepIndex) {
circle.classList.add('completed');
} else if (index === stepIndex) {
circle.classList.add('active');
}
});
document.getElementById('currentStep').textContent =
`${stepIndex + 1}/8 단계: ${stepExplanations[stepIndex]?.title || '진행 중'}`;
}
// 💭 단계 설명 업데이트
function updateStepExplanation(stepIndex) {
const explanation = stepExplanations[stepIndex];
if (explanation) {
document.getElementById('stepExplanation').innerHTML =
`<strong>${explanation.title}</strong><br>${explanation.content}`;
}
}
// 🔤 영어 번역 생성
function generateTranslation(inputText, isSingleToken = false) {
// 단일 토큰 처리
if (isSingleToken) {
// translationDB에서 직접 찾기
if (translationDB[inputText]) {
return translationDB[inputText];
}
// 기본 한글 처리
const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
if (isKorean) {
// 문장 부호는 그대로
if (/[.!?]/.test(inputText)) {
return inputText;
}
// 강화된 기본 번역 로직 - 더 많은 패턴 처리
const commonTranslations = {
// 조사 및 어미
'은': 'is', '는': 'is', '이': 'this', '가': 'is',
'을': 'object', '를': 'object', '에': 'to', '에서': 'from',
'와': 'and', '과': 'and', '의': 'of', '도': 'also',
'하며': 'while', '하고': 'and', '에게': 'to',
// 일반적인 동사/형용사 어미
'습니다': 'is', '입니다': 'is', '됩니다': 'becomes',
'합니다': 'do', '했습니다': 'did', '있습니다': 'exist',
'보냅니다': 'send', '갑니다': 'go', '옵니다': 'come',
// 기본 단어들
'친구들과': 'with friends', '함께': 'together', '공원에서': 'in park',
'산책을': 'walk', '하며': 'while doing', '즐거운': 'joyful',
'시간을': 'time', '보냈습니다': 'spent'
};
// 패턴 매칭 시도
const foundTranslation = commonTranslations[inputText];
if (foundTranslation) {
return foundTranslation;
}
// 어미 분석 시도
if (inputText.endsWith('습니다')) {
const root = inputText.slice(0, -3);
return `${root}_is`;
}
if (inputText.endsWith('했습니다')) {
const root = inputText.slice(0, -4);
return `${root}_did`;
}
if (inputText.endsWith('입니다')) {
const root = inputText.slice(0, -3);
return `${root}_is`;
}
// 최후 수단: 영어 단어로 변환
return 'word';
}
return inputText; // 영어는 그대로
}
// 문장 전체 처리
const words = inputText.trim().split(/\\s+/);
// 입력 언어 감지 (한글인지 영어인지)
const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
const translated = words.map(word => {
// 번역 데이터베이스에서 직접 번역 찾기
if (translationDB[word]) {
return translationDB[word];
}
// 번역이 없는 경우 - 개선된 한글 처리
if (isKorean) {
// 한글 → 영어: 더 포괄적인 기본 번역
const commonKoreanToEnglish = {
// 조사
'이': 'this', '그': 'that', '저': 'that',
'은': 'is', '는': 'is', '이다': 'is', '다': 'is',
'을': 'the', '를': 'the', '에': 'in', '에서': 'from',
'와': 'and', '과': 'and', '하고': 'and',
'의': 'of', '도': 'also', '만': 'only',
// 기본 동사
'좋다': 'good', '나쁘다': 'bad', '크다': 'big', '작다': 'small',
'있다': 'have', '없다': 'not', '되다': 'become', '하다': 'do',
'보다': 'see', '듣다': 'hear', '말하다': 'speak', '쓰다': 'write',
'가다': 'go', '오다': 'come', '주다': 'give', '받다': 'receive',
// 일반적인 단어들
'함께': 'together', '같이': 'together', '혼자': 'alone',
'많이': 'much', '조금': 'little', '전부': 'all', '모든': 'every'
};
// 특정 변수명이나 알 수 없는 단어는 영어 단어로 대체
const translatedWord = commonKoreanToEnglish[word];
if (translatedWord) {
return translatedWord;
}
// 인명이나 고유명사는 그대로 유지 (한글 2글자 이상)
if (word.length >= 2 && /^[가-힣]+$/.test(word)) {
return word; // 한글 그대로 유지
}
return 'word'; // 기본값
} else {
// 영어 → 한글: 기본 한글 단어로 변환
const commonEnglishToKorean = {
'the': '그', 'a': '하나의', 'an': '하나의', 'and': '그리고',
'is': '이다', 'are': '이다', 'was': '였다', 'were': '였다',
'have': '가지다', 'has': '가지다', 'had': '가졌다',
'do': '하다', 'does': '하다', 'did': '했다',
'will': '할것이다', 'would': '할것이다', 'can': '할수있다',
'good': '좋은', 'bad': '나쁜', 'big': '큰', 'small': '작은',
'see': '보다', 'hear': '듣다', 'speak': '말하다', 'write': '쓰다'
};
return commonEnglishToKorean[word.toLowerCase()] || '단어';
}
});
// 모든 단어를 유지 - 필터링하지 않음
return translated;
}
// 🏗️ 1단계: 인코더-디코더 실행 (한국어 토큰화 → 영어 매핑)
async function executeEncoderDecoder() {
const encoderBox = document.getElementById('encoderBox');
const decoderBox = document.getElementById('decoderBox');
const encoderText = document.getElementById('encoderText');
const decoderText = document.getElementById('decoderText');
const encoderTokensDiv = document.getElementById('encoderTokens');
const decoderTokensDiv = document.getElementById('decoderTokens');
// 선택된 문장 가져오기
const inputText = document.getElementById('inputSentence').value.trim();
if (!inputText) {
alert('예시 문단을 선택해주세요!');
return;
}
// 한국어 입력을 정확히 토큰화 (단어 단위로 분리)
const koreanTokens = inputText.trim()
.replace(/([.!?])/g, ' $1') // 문장부호 앞에 공백 추가
.split(/\\s+/) // 공백으로 분리
.filter(token => token.length > 0) // 빈 토큰 제거
.slice(0, 20); // 최대 20개 토큰
tokens = koreanTokens;
// 각 한국어 토큰을 영어로 번역
const englishTokens = koreanTokens.map(token => {
// 문장 부호는 그대로
if (/^[.!?]+$/.test(token)) {
return token;
}
// translationDB에서 번역 찾기, 없으면 기본 번역 함수 호출
return translationDB[token] || generateTranslation(token, true);
});
translatedTokens = englishTokens;
// 인코더 활성화 (한국어 입력)
encoderBox.classList.add('active');
encoderText.textContent = `한국어 입력: ${inputText}`;
// 한국어 토큰 표시
encoderTokensDiv.innerHTML = '';
for (let i = 0; i < koreanTokens.length; i++) {
await sleep(200);
const tokenDiv = document.createElement('div');
tokenDiv.className = 'token fade-in';
tokenDiv.textContent = koreanTokens[i];
tokenDiv.style.backgroundColor = '#ffeb3b';
tokenDiv.style.color = '#333';
encoderTokensDiv.appendChild(tokenDiv);
}
await sleep(500);
// 디코더 활성화 (영어 번역)
decoderBox.classList.add('active');
decoderText.textContent = `영어 번역: ${englishTokens.join(' ')}`;
// 영어 토큰 표시
decoderTokensDiv.innerHTML = '';
for (let i = 0; i < englishTokens.length; i++) {
await sleep(200);
const tokenDiv = document.createElement('div');
tokenDiv.className = 'token fade-in';
tokenDiv.textContent = englishTokens[i];
tokenDiv.style.backgroundColor = '#4caf50';
tokenDiv.style.color = '#white';
decoderTokensDiv.appendChild(tokenDiv);
}
}
// 📍 2단계: 포지셔널 인코딩 실행 (인코더의 한국어 단어를 임베딩)
async function executePositionalEncoding() {
const normalEmbedding = document.getElementById('normalEmbedding');
const positionalEmbedding = document.getElementById('positionalEmbedding');
// tokens(한국어)가 있는지 확인
if (!tokens || tokens.length === 0) {
alert('먼저 1단계를 진행해주세요!');
return;
}
// 일반 워드 임베딩 표시 (인코더의 한국어 단어 사용)
normalEmbedding.innerHTML = '';
for (let i = 0; i < tokens.length; i++) {
await sleep(150);
const wordItem = document.createElement('div');
wordItem.className = 'word-item fade-in';
wordItem.innerHTML = `
<span>${tokens[i]}</span>
<span class="position-value">[${(Math.random() * 0.8 + 0.1).toFixed(3)}, ${(Math.random() * 0.8 + 0.1).toFixed(3)}, ...]</span>
`;
wordItem.style.backgroundColor = '#e3f2fd';
normalEmbedding.appendChild(wordItem);
}
await sleep(300);
// 포지셔널 인코딩 표시 (인코더의 한국어 단어에 위치 정보 추가)
positionalEmbedding.innerHTML = '';
for (let i = 0; i < tokens.length; i++) {
await sleep(150);
const wordItem = document.createElement('div');
wordItem.className = 'word-item fade-in';
// 위치에 따른 사인/코사인 값 생성 (실제 트랜스포머 공식)
const posValue1 = Math.sin(i / Math.pow(10000, (0 / 512))).toFixed(3);
const posValue2 = Math.cos(i / Math.pow(10000, (1 / 512))).toFixed(3);
wordItem.innerHTML = `
<span>${tokens[i]}</span>
<span class="position-value">pos:${i} [${posValue1}, ${posValue2}, ...]</span>
`;
wordItem.style.backgroundColor = '#fff3e0';
positionalEmbedding.appendChild(wordItem);
// 강조 효과
setTimeout(() => {
wordItem.classList.add('highlight');
setTimeout(() => wordItem.classList.remove('highlight'), 500);
}, 100);
}
// 설명 텍스트 업데이트
const explanationDiv = document.querySelector('#section-1 .explanation');
if (explanationDiv) {
explanationDiv.innerHTML = `
<h4>포지셔널 인코딩 설명</h4>
<p><strong>일반 워드 임베딩:</strong> 인코더의 각 한국어 단어가 벡터로 변환됩니다.</p>
<p><strong>포지셔널 인코딩:</strong> 한국어 단어의 위치 정보가 추가되어 문장 내에서의 순서를 학습합니다.</p>
<p>현재 임베딩 중인 한국어 문장: <em>"${tokens.join(' ')}"</em></p>
`;
}
}
// 3-0단계: 멀티헤드 어텐션 (임의 문장)
async function executeMHAnySentence() {
// 기존 멀티헤드 생성 로직 재사용
const container = document.getElementById('mhAnyContainer');
container.innerHTML = '';
const heads = [
{ name: 'Head 1: 문법 구조', color: '#e91e63', focus: 'syntax' },
{ name: 'Head 2: 의미 관계', color: '#9c27b0', focus: 'semantic' },
{ name: 'Head 3: 위치 정보', color: '#673ab7', focus: 'position' },
{ name: 'Head 4: 문맥 흐름', color: '#3f51b5', focus: 'context' }
];
for (let headIndex = 0; headIndex < heads.length; headIndex++) {
await sleep(150);
const headBox = document.createElement('div');
headBox.className = 'head-box fade-in';
headBox.innerHTML = `
<div class="head-title" style="color: ${heads[headIndex].color}">
${heads[headIndex].name}
</div>
<div class="tokens-row" id="head-any-${headIndex}-tokens"></div>
<div class="attention-matrix" id="head-any-${headIndex}-matrix"></div>
`;
container.appendChild(headBox);
// 토큰 표시 (인코더 한글 토큰 기준)
const tokensDiv = document.getElementById(`head-any-${headIndex}-tokens`);
tokens.slice(0, 6).forEach(t => {
const td = document.createElement('div');
td.className = 'token'; td.style.fontSize = '0.8em'; td.textContent = t; tokensDiv.appendChild(td);
});
// 매트릭스
await generateHeadAttentionCustom(`head-any-${headIndex}-matrix`, heads[headIndex]);
}
}
async function generateHeadAttentionCustom(matrixId, headInfo) {
const matrixDiv = document.getElementById(matrixId);
const matrixSize = Math.min(tokens.length, 4);
const displayTokens = tokens.slice(0, matrixSize);
matrixDiv.style.display = 'grid';
matrixDiv.style.gridTemplateColumns = `60px repeat(${matrixSize}, 30px)`;
matrixDiv.style.gridTemplateRows = `40px repeat(${matrixSize}, 30px)`;
matrixDiv.style.gap = '2px';
matrixDiv.appendChild(document.createElement('div'));
for (let j = 0; j < matrixSize; j++) {
const c = document.createElement('div'); c.className = 'matrix-label col-label'; c.textContent = displayTokens[j]; matrixDiv.appendChild(c);
}
for (let i = 0; i < matrixSize; i++) {
const r = document.createElement('div'); r.className = 'matrix-label row-label'; r.textContent = displayTokens[i]; matrixDiv.appendChild(r);
for (let j = 0; j < matrixSize; j++) {
await sleep(30);
let weight;
switch(headInfo.focus) {
case 'syntax': weight = (i === j) ? 0.8 : Math.random() * 0.4; break;
case 'semantic': weight = Math.random() * 0.7 + 0.2; break;
case 'position': weight = 1 - Math.abs(i - j) * 0.3; break;
case 'context': weight = (Math.abs(i - j) === 1) ? 0.9 : Math.random() * 0.3; break;
default: weight = Math.random() * 0.6;
}
const cell = document.createElement('div'); cell.className = 'attention-cell'; cell.textContent = weight.toFixed(1); cell.style.backgroundColor = getAttentionColor(weight); matrixDiv.appendChild(cell);
}
}
}
// 3-1단계: 인코더 멀티헤드 셀프 어텐션 (Score1/Score2 세로 리스트 표현)
async function executeEncoderSelfMH() {
const size = Math.min(tokens.length, 9);
const words = tokens.slice(0, size);
if (words.length === 0) return;
// 컨테이너 초기화
document.getElementById('selfAttentionTokens').innerHTML = '';
document.getElementById('selfAttentionMatrix').innerHTML = '';
const colInput = document.getElementById('esvInput');
const colS1 = document.getElementById('esvScore1');
const colS2 = document.getElementById('esvScore2');
colInput.innerHTML = ''; colS1.innerHTML = ''; colS2.innerHTML = '';
// 두 개의 헤드(Score1/Score2)를 가정하고 각 토큰에 대한 가중치 생성
const head1 = words.map((_, i) => words.map((__, j) => Math.max(0, 1 - Math.abs(i - j) * 0.35))); // 인접 강조
const head2 = words.map((_, i) => words.map((__, j) => (j === (i+2)%size ? 0.95 : Math.random()*0.4))); // 점프 패턴
// 색상 유틸: 분홍(Score1), 노랑(Score2)
const toPink = v => `rgba(255, 64, 64, ${Math.min(0.15 + v*0.85, 1)})`;
const toYellow = v => `rgba(255, 193, 7, ${Math.min(0.15 + v*0.85, 1)})`;
const lightPink = v => `rgba(255, 64, 64, ${Math.min(0.08 + v*0.5, 0.35)})`;
const lightYellow = v => `rgba(255, 193, 7, ${Math.min(0.08 + v*0.5, 0.35)})`;
// 같은 행 정렬로 배치: 세 열 모두 동일 순서(각 단어 1개씩)
words.forEach((w, idx) => {
const inDiv = document.createElement('div');
inDiv.textContent = w;
inDiv.className = 'esv-input';
inDiv.setAttribute('data-index', String(idx));
inDiv.style.padding = '6px 10px';
inDiv.style.border = '1px solid #eee';
inDiv.style.background = idx % 2 ? '#fafafa' : '#fff';
inDiv.style.cursor = 'pointer';
inDiv.title = '클릭하여 강한 연결을 확인하세요';
colInput.appendChild(inDiv);
});
words.forEach((w, idx) => {
const s1 = document.createElement('div');
s1.textContent = w;
s1.className = 'esv-s1-item';
s1.setAttribute('data-index', String(idx));
s1.style.padding = '6px 10px';
s1.style.border = '1px solid #eee';
s1.style.background = idx % 2 ? '#fffafc' : '#fff';
colS1.appendChild(s1);
});
words.forEach((w, idx) => {
const s2 = document.createElement('div');
s2.textContent = w;
s2.className = 'esv-s2-item';
s2.setAttribute('data-index', String(idx));
s2.style.padding = '6px 10px';
s2.style.border = '1px solid #eee';
s2.style.background = idx % 2 ? '#fffef7' : '#fff';
colS2.appendChild(s2);
});
// 클릭 시에만 하이라이트/라인 표시
colInput.querySelectorAll('.esv-input').forEach(el => {
el.addEventListener('click', () => {
const row = Number(el.getAttribute('data-index'));
__selectedRow = row;
// 입력 열 강조 초기화/적용
colInput.querySelectorAll('.esv-input').forEach(n => { n.style.outline=''; n.style.background = (Number(n.getAttribute('data-index'))%2?'#fafafa':'#fff'); });
el.style.outline = '3px solid #9e9e9e';
el.style.background = '#e0e0e0';
// 점수 하이라이트
updateMHSelfHighlights(row, head1, head2);
// 라인 표시
drawMHSelfLinesRow(row);
});
});
// 상태 저장: 클릭 시 참조
__lastSelfHeads = { head1, head2 };
}
// 클릭 시 두 헤드 분포를 색으로 반영(자기 자신 제외 또는 약화)
function updateMHSelfHighlights(row, head1, head2){
const s1Items = document.querySelectorAll('#esvScore1 .esv-s1-item');
const s2Items = document.querySelectorAll('#esvScore2 .esv-s2-item');
const N = s1Items.length;
const base1 = (i)=> i%2? '#fffafc' : '#fff';
const base2 = (i)=> i%2? '#fffef7' : '#fff';
s1Items.forEach((el,i)=>{ el.style.background = base1(i); });
s2Items.forEach((el,i)=>{ el.style.background = base2(i); });
if (row == null || !head1 || !head2) return;
const weakenSelf = 0; // 자기 자신은 0으로
for (let j=0;j<N;j++){
const v1 = j===row ? weakenSelf : (head1[row]?.[j] || 0);
const v2 = j===row ? weakenSelf : (head2[row]?.[j] || 0);
const a1 = Math.min(0.12 + v1*0.88, 0.95);
const a2 = Math.min(0.12 + v2*0.88, 0.95);
if (v1>0) s1Items[j].style.background = `rgba(233,30,99, ${a1})`;
if (v2>0) s2Items[j].style.background = `rgba(255,193,7, ${a2})`;
}
}
// 3-1 연결선 그리기 (강도 기반, 상위 1개) - 선택된 행만
function drawMHSelfLinesRow(row){
const wrapper = document.getElementById('esvRowWrapper');
const svg = document.getElementById('esvOverlay');
if(!wrapper || !svg) return;
// 기존 라인 제거
while (svg.firstChild) svg.removeChild(svg.firstChild);
if (!__lastSelfHeads) return;
const h1 = __lastSelfHeads.head1, h2 = __lastSelfHeads.head2;
if (row == null || row < 0 || row >= h1.length) return;
// 좌표 계산을 위해 bbox를 한 번 갱신
const wrapRect = wrapper.getBoundingClientRect();
const inputEl = wrapper.querySelector(`.esv-input[data-index='${row}']`);
// 최댓값(자기 자신 제외)
const bestIdx = (arr) => {
let idx = -1, mv = -Infinity;
for (let j=0;j<arr.length;j++){
if (j===row) continue;
if (arr[j] > mv){ mv = arr[j]; idx = j; }
}
return { idx, val: mv };
};
if (!h1 || !h2) return;
const b1 = bestIdx(h1[row]||[]);
const b2 = bestIdx(h2[row]||[]);
const s1Best = wrapper.querySelector(`#esvScore1 .esv-s1-item[data-index='${b1.idx}']`);
const s2Best = wrapper.querySelector(`#esvScore2 .esv-s2-item[data-index='${b2.idx}']`);
if (!inputEl || !s1Best || !s2Best) return;
const lineFor = (x1,y1,x2,y2,color,width,opacity,dash=undefined) => {
const path = document.createElementNS('<http://www.w3.org/2000/svg','path>');
// 곡선(부드럽게)
const mx = (x1 + x2)/2;
const d = `M ${x1},${y1} C ${mx},${y1} ${mx},${y2} ${x2},${y2}`;
path.setAttribute('d', d);
path.setAttribute('fill', 'none');
path.setAttribute('stroke', color);
path.setAttribute('stroke-width', width);
path.setAttribute('opacity', opacity);
if (dash) path.setAttribute('stroke-dasharray', dash);
svg.appendChild(path);
};
const centerOf = (el) => {
const r = el.getBoundingClientRect();
return {
cx: r.left - wrapRect.left + r.width/2,
cy: r.top - wrapRect.top + r.height/2
};
};
const clamp01 = v => Math.max(0, Math.min(1, v));
// 선택된 행의 최댓값들 계산
const best1Val = b1.val;
const best2Val = b2.val;
const pInput = centerOf(inputEl);
const pS1 = centerOf(s1Best);
const pS2 = centerOf(s2Best);
// 강도 → 두께/투명도 매핑
const w1 = 1.5 + 3.5 * clamp01(best1Val);
const o1 = 0.35 + 0.55 * clamp01(best1Val);
const w2 = 1.5 + 3.5 * clamp01(best2Val);
const o2 = 0.35 + 0.55 * clamp01(best2Val);
// Score1: 분홍, Score2: 노랑(점선)
lineFor(pInput.cx, pInput.cy, pS1.cx, pS1.cy, '#e91e63', w1, o1);
lineFor(pInput.cx, pInput.cy, pS2.cx, pS2.cy, '#f9a825', w2, o2, '6 4');
}
// 리사이즈 시 연결선 재그리기
let __selectedRow = -1;
window.addEventListener('resize', () => {
if (__lastSelfHeads && __selectedRow >= 0 && document.getElementById('section-3')?.classList.contains('active')){
drawMHSelfLinesRow(__selectedRow);
}
});
// 3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션
async function executeDecoderMaskedSelfMH() {
const container = document.getElementById('decoderMaskedMatrix');
container.innerHTML = '';
const maxTokens = 6;
const base = translatedTokens.slice(0, Math.max(0, maxTokens-1));
const display = ['[START]', ...base];
const size = display.length;
container.style.display = 'grid';
container.style.gridTemplateColumns = `70px repeat(${size}, 46px)`;
container.style.gridTemplateRows = `64px repeat(${size}, 40px)`;
container.appendChild(document.createElement('div'));
// 상단 열 라벨(겹침 방지: -40deg 회전)
for (let j = 0; j < size; j++) {
const c = document.createElement('div');
c.className='matrix-label';
c.textContent=display[j];
c.style.transform = 'rotate(-40deg)';
c.style.transformOrigin = 'left bottom';
c.style.whiteSpace = 'nowrap';
c.style.height = '60px';
c.style.display = 'flex';
c.style.alignItems = 'flex-end';
c.style.justifyContent = 'center';
c.style.color = '#333';
container.appendChild(c);
}
for (let i = 0; i < size; i++) {
const r = document.createElement('div'); r.className='matrix-label'; r.textContent=display[i]; container.appendChild(r);
for (let j = 0; j < size; j++) {
const cell = document.createElement('div'); cell.className='attention-cell';
// 요구사항: [START] 행(i===0)은 전부 마스크, 그 외에는 현재/미래 마스킹(j >= i)
const masked = (i === 0) || (j >= i);
const NEG = -1e9; // softmax에서 0으로 수렴시키는 큰 음수
// 로그잇(내적 값) 시뮬레이션
let logit = masked ? NEG : (0.2 + Math.random()*0.3);
if (masked) {
cell.classList.add('masked-cell');
cell.textContent = '-∞'; // 시각적으로 명확하게 표시
cell.style.backgroundColor = '#ffebee';
cell.style.color = '#d32f2f';
const reason = (i===0) ? 'START 행 전체 마스크' : (j===i ? '현재(자기 자신) 마스크' : '미래 토큰 마스크');
cell.title = `${display[i]} → ${display[j]}: ${NEG} (${reason}, softmax ≈ 0)`;
} else {
cell.textContent = logit.toFixed(2);
cell.style.backgroundColor = getAttentionColor(logit);
cell.title = `${display[i]} → ${display[j]}: ${logit.toFixed(3)}`;
}
container.appendChild(cell);
}
}
}
// 3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션
async function executeCrossMultiHead() {
const container = document.getElementById('crossMultiContainer');
container.innerHTML = '';
const headBox = document.createElement('div');
headBox.className = 'head-box';
headBox.innerHTML = `
<div class="head-title" style="color:#2e7d32">Cross Attention (Encoder ↔ Decoder)</div>
<div class="tokens-row" id="cross-tokens"></div>
<div class="attention-matrix" id="cross-matrix"></div>
`;
container.appendChild(headBox);
const trow = document.getElementById('cross-tokens');
// 열(Columns): 디코더 토큰들
trow.innerHTML = '';
translatedTokens.forEach(t=>{const d=document.createElement('div'); d.className='token'; d.textContent=t; trow.appendChild(d);});
const m = document.getElementById('cross-matrix');
const size = Math.min(tokens.length, translatedTokens.length, 6);
m.style.display='grid'; m.style.gridTemplateColumns = `80px repeat(${size}, 50px)`;
m.appendChild(document.createElement('div'));
// 상단 열 라벨: 디코더 토큰들
for (let j=0;j<size;j++){
const c=document.createElement('div'); c.className='matrix-label'; c.textContent=translatedTokens[j]||`단어${j+1}`; m.appendChild(c);
}
// 행 라벨: 인코더 토큰들
for (let i=0;i<size;i++){
const r=document.createElement('div'); r.className='matrix-label'; r.textContent=tokens[i]||`단어${i+1}`; m.appendChild(r);
for (let j=0;j<size;j++){
const w=0.1+Math.random()*0.8; const cell=document.createElement('div'); cell.className='attention-cell'; cell.textContent=w.toFixed(2); cell.style.backgroundColor=getAttentionColor(w); m.appendChild(cell);
}
}
}
// 5단계: 학습 후 디코더 출력 (그레이스케일 히트맵)
async function executeDecoderOutputAfter() {
const container = document.getElementById('decoderOutputAfter');
if (!container) return;
container.innerHTML = '';
// 학습된 헤드 사용: __currHeads는 로짓, rowSoftmaxMatrix로 확률 변환
let A = null; // [nEnc][nDec] 유사 행렬(여기선 nTok x nTok)
try {
const L = window.__currHeads?.length || 0;
const H = L ? window.__currHeads[0]?.length || 0 : 0;
if (L && H) {
const l = window.__selectedLayer ?? 0;
const h = window.__selectedHead ?? 0;
const logits = window.__currHeads[l][h];
A = rowSoftmaxMatrix(logits);
}
} catch(e) { console.warn('학습 후 행렬 사용 불가, 휴리스틱 사용', e); }
// 백업: 간단 정렬 휴리스틱 (동일 인덱스에 피크 부여)
const n = Math.min(tokens.length, translatedTokens.length, 8);
if (!A) {
A = Array.from({length: n}, (_,i)=> Array.from({length:n}, (_,j)=> {
const base = Math.max(0, 1 - Math.abs(i-j)*0.6);
return base + Math.random()*0.05;
}));
A = normalizeRows(A);
} else {
// 크기 보정: 시각을 위해 상위 n토큰으로 제한
A = A.slice(0, n).map(row => row.slice(0, n));
A = normalizeRows(A);
}
// 그리드 구성: 열=영어(디코더), 행=한국어(인코더)
container.style.display = 'grid';
container.style.gridTemplateColumns = `80px repeat(${n}, 44px)`;
container.style.gridAutoRows = '44px';
container.style.gap = '4px';
// 좌상단 빈칸
const empty = document.createElement('div');
empty.className = 'matrix-label';
empty.style.visibility = 'hidden';
container.appendChild(empty);
// 상단 열 라벨: EN
for (let j=0;j<n;j++) {
const lab = document.createElement('div'); lab.className='matrix-label';
lab.textContent = translatedTokens[j] || `EN${j+1}`; lab.title = `Decoder: ${translatedTokens[j]||''}`;
container.appendChild(lab);
}
// 셀 채우기
for (let i=0;i<n;i++) {
// 행 라벨: KR
const rlab = document.createElement('div'); rlab.className='matrix-label';
rlab.textContent = tokens[i] || `KR${i+1}`; rlab.title = `Encoder: ${tokens[i]||''}`;
container.appendChild(rlab);
// 각 열 셀
const row = A[i];
const maxv = Math.max(...row);
const maxj = row.indexOf(maxv);
for (let j=0;j<n;j++) {
const v = row[j];
const cell = document.createElement('div');
cell.className = 'attention-cell';
cell.style.backgroundColor = getGrayColor(v);
cell.style.color = v > 0.75*maxv ? '#000' : '#222';
cell.textContent = v.toFixed(2);
const kr = tokens[i]||''; const en = translatedTokens[j]||'';
cell.title = `${kr} ↔ ${en}: ${v.toFixed(3)}`;
if (j === maxj) {
cell.style.outline = '2px solid #111';
cell.style.boxShadow = 'inset 0 0 0 2px rgba(255,255,255,0.6)';
}
container.appendChild(cell);
}
}
}
function getGrayColor(w){
// 0..1 -> 밝기 40(짙은회색) .. 230(연한회색): 값이 클수록 더 밝게
const clamped = Math.max(0, Math.min(1, w));
const v = Math.round(40 + clamped * 190); // 40..230
return `rgb(${v},${v},${v})`;
}
// 🔄 4단계: 셀프 어텐션 실행
async function executeSelfAttention() {
const tokensDiv = document.getElementById('selfAttentionTokens');
const matrixDiv = document.getElementById('selfAttentionMatrix');
// 번역된 토큰 표시
tokensDiv.innerHTML = '';
translatedTokens.forEach((token, i) => {
const tokenDiv = document.createElement('div');
tokenDiv.className = 'token';
tokenDiv.textContent = token;
tokenDiv.onclick = () => showSelfAttentionConnections(i);
tokensDiv.appendChild(tokenDiv);
});
// 셀프 어텐션 매트릭스 (행/열 라벨 포함)
const matrixSize = translatedTokens.length;
matrixDiv.style.gridTemplateColumns = `80px repeat(${matrixSize}, 50px)`;
matrixDiv.innerHTML = '';
// 좌상단 빈 셀
const emptyCell = document.createElement('div');
emptyCell.className = 'matrix-label-cell empty';
matrixDiv.appendChild(emptyCell);
// 상단 열 라벨 (같은 문장)
for (let j = 0; j < matrixSize; j++) {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label-cell column-label';
colLabel.textContent = translatedTokens[j] || `단어${j+1}`;
colLabel.title = `Target: ${translatedTokens[j]}`;
matrixDiv.appendChild(colLabel);
}
// 각 행
for (let i = 0; i < matrixSize; i++) {
// 왼쪽 행 라벨 (같은 문장)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label-cell row-label';
rowLabel.textContent = translatedTokens[i] || `단어${i+1}`;
rowLabel.title = `Source: ${translatedTokens[i]}`;
matrixDiv.appendChild(rowLabel);
// 셀프 어텐션 값 셀들
for (let j = 0; j < matrixSize; j++) {
await sleep(70);
const cell = document.createElement('div');
cell.className = 'attention-cell';
// 셀프 어텐션 가중치 (초기화된 값)
const weight = calculateSelfAttentionWeight(i, j);
cell.style.backgroundColor = getAttentionColor(weight);
cell.textContent = weight.toFixed(3);
cell.title = `${translatedTokens[i]} → ${translatedTokens[j]}: ${weight.toFixed(4)}`;
matrixDiv.appendChild(cell);
}
}
}
// 🎯 5단계: 멀티헤드 어텐션 실행
async function executeMultiHeadAttention() {
const container = document.getElementById('multiheadContainer');
container.innerHTML = '';
const heads = [
{ name: 'Head 1: 문법 구조', color: '#e91e63', focus: 'syntax' },
{ name: 'Head 2: 의미 관계', color: '#9c27b0', focus: 'semantic' },
{ name: 'Head 3: 위치 정보', color: '#673ab7', focus: 'position' },
{ name: 'Head 4: 문맥 흐름', color: '#3f51b5', focus: 'context' }
];
for (let headIndex = 0; headIndex < heads.length; headIndex++) {
await sleep(300);
const headBox = document.createElement('div');
headBox.className = 'head-box fade-in';
headBox.innerHTML = `
<div class="head-title" style="color: ${heads[headIndex].color}">
${heads[headIndex].name}
</div>
<div class="tokens-row" id="head-${headIndex}-tokens"></div>
<div class="attention-matrix" id="head-${headIndex}-matrix"></div>
`;
container.appendChild(headBox);
// 각 헤드의 토큰과 어텐션 표시
await generateHeadAttention(headIndex, heads[headIndex]);
}
}
// 🎉 6단계: 최종 결과 시각화
async function executeFinalVisualization() {
const container = document.getElementById('finalVisualization');
container.innerHTML = `
<h4>🎊 최종 어텐션 결과 종합</h4>
<div class="tokens-row" id="finalTokens"></div>
<div id="finalMatrix" class="attention-matrix"></div>
<div style="margin-top: 30px; text-align: center;">
<h4>🔧 어텐션 매트릭스 생성 완료</h4>
<p>다음 단계에서 모델을 학습시켜 어텐션 패턴을 최적화할 수 있습니다.</p>
</div>
`;
// 최종 번역된 토큰 표시
const finalTokensDiv = document.getElementById('finalTokens');
translatedTokens.forEach((token, i) => {
const tokenDiv = document.createElement('div');
tokenDiv.className = 'token focused bounce';
tokenDiv.textContent = token;
finalTokensDiv.appendChild(tokenDiv);
});
// 종합 어텐션 매트릭스 (행열 단어 라벨 포함)
const finalMatrixDiv = document.getElementById('finalMatrix');
const matrixSize = translatedTokens.length;
// 그리드 레이아웃 설정 (라벨 공간 포함)
finalMatrixDiv.style.display = 'grid';
finalMatrixDiv.style.gridTemplateColumns = `80px repeat(${matrixSize}, 50px)`;
finalMatrixDiv.style.gridTemplateRows = `50px repeat(${matrixSize}, 50px)`;
finalMatrixDiv.style.gap = '3px';
finalMatrixDiv.style.alignItems = 'center';
finalMatrixDiv.style.justifyItems = 'center';
// 빈 셀 (좌상단)
const emptyCell = document.createElement('div');
emptyCell.style.width = '80px';
emptyCell.style.height = '50px';
finalMatrixDiv.appendChild(emptyCell);
// 상단 열 라벨 (To)
for (let j = 0; j < matrixSize; j++) {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label col-label';
colLabel.style.width = '50px';
colLabel.style.height = '50px';
colLabel.style.fontSize = '0.7em';
colLabel.style.fontWeight = 'bold';
colLabel.style.color = '#666';
colLabel.style.display = 'flex';
colLabel.style.alignItems = 'center';
colLabel.style.justifyContent = 'center';
colLabel.style.background = '#f0f7ff';
colLabel.style.borderRadius = '5px';
colLabel.textContent = translatedTokens[j];
finalMatrixDiv.appendChild(colLabel);
}
// 매트릭스 행들 (행 라벨 + 셀들)
for (let i = 0; i < matrixSize; i++) {
// 좌측 행 라벨 (From)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label row-label';
rowLabel.style.width = '80px';
rowLabel.style.height = '50px';
rowLabel.style.fontSize = '0.7em';
rowLabel.style.fontWeight = 'bold';
rowLabel.style.color = '#666';
rowLabel.style.display = 'flex';
rowLabel.style.alignItems = 'center';
rowLabel.style.justifyContent = 'center';
rowLabel.style.background = '#fff0f0';
rowLabel.style.borderRadius = '5px';
rowLabel.textContent = translatedTokens[i];
finalMatrixDiv.appendChild(rowLabel);
// 어텐션 셀들
for (let j = 0; j < matrixSize; j++) {
await sleep(100);
const cell = document.createElement('div');
cell.className = 'attention-cell bounce';
cell.style.width = '50px';
cell.style.height = '50px';
// 모든 헤드의 평균 어텐션
const avgWeight = (
calculateAttentionWeight(i, j) +
calculateSelfAttentionWeight(i, j) +
Math.random() * 0.3
) / 3;
// 전역 어텐션 매트릭스에 저장
if (!finalAttentionMatrix[i]) finalAttentionMatrix[i] = [];
finalAttentionMatrix[i][j] = avgWeight;
cell.style.backgroundColor = getAttentionColor(avgWeight);
cell.textContent = avgWeight.toFixed(2);
cell.title = `${translatedTokens[i]} → ${translatedTokens[j]}: ${avgWeight.toFixed(3)}`;
finalMatrixDiv.appendChild(cell);
}
}
// 완료 효과
setTimeout(() => {
container.classList.add('bounce');
isSimulationRunning = false;
}, 1000);
}
// 🔍 Q, K, V 매트릭스 생성 시뮬레이션
async function generateQKVMatrices() {
const queryMatrix = document.getElementById('queryMatrix');
const keyMatrix = document.getElementById('keyMatrix');
const valueMatrix = document.getElementById('valueMatrix');
// 학습 전 초기화된 Q 매트릭스 (작은 랜덤 값들)
queryMatrix.innerHTML = '';
for (let i = 0; i < 9; i++) {
await sleep(100);
const cell = document.createElement('div');
cell.className = 'mini-cell fade-in';
const value = (Math.random() * 0.2 + 0.01).toFixed(3); // 0.01~0.21
cell.textContent = value;
cell.style.backgroundColor = `rgba(76, 175, 80, 0.3)`;
cell.title = `초기화된 Query 값: ${value}`;
queryMatrix.appendChild(cell);
}
await sleep(200);
// 학습 전 초기화된 K 매트릭스
keyMatrix.innerHTML = '';
for (let i = 0; i < 9; i++) {
await sleep(100);
const cell = document.createElement('div');
cell.className = 'mini-cell fade-in';
const value = (Math.random() * 0.2 + 0.01).toFixed(3);
cell.textContent = value;
cell.style.backgroundColor = `rgba(255, 152, 0, 0.3)`;
cell.title = `초기화된 Key 값: ${value}`;
keyMatrix.appendChild(cell);
}
await sleep(200);
// 학습 전 초기화된 V 매트릭스
valueMatrix.innerHTML = '';
for (let i = 0; i < 9; i++) {
await sleep(100);
const cell = document.createElement('div');
cell.className = 'mini-cell fade-in';
const value = (Math.random() * 0.2 + 0.01).toFixed(3);
cell.textContent = value;
cell.style.backgroundColor = `rgba(156, 39, 176, 0.3)`;
cell.title = `초기화된 Value 값: ${value}`;
valueMatrix.appendChild(cell);
}
await sleep(500);
}
// 🧮 헬퍼 함수들
function calculateAttentionWeight(i, j) {
// 학습 전 초기화된 어텐션 - 무작위 패턴
return 0.05 + Math.random() * 0.2; // 0.05~0.25 범위의 작은 값
}
function calculateSelfAttentionWeight(i, j) {
// 학습 전 초기화된 어텐션 - 거의 무작위 패턴
if (i === j) return 0.15 + Math.random() * 0.1; // 자기 자신도 크지 않음
return 0.05 + Math.random() * 0.15; // 대부분 작은 값
}
function getAttentionColor(weight) {
const intensity = Math.min(255, Math.floor(weight * 255));
return `rgb(${Math.floor(intensity * 0.3)}, ${Math.floor(intensity * 0.6)}, ${intensity})`;
}
async function generateHeadAttention(headIndex, headInfo) {
const tokensDiv = document.getElementById(`head-${headIndex}-tokens`);
const matrixDiv = document.getElementById(`head-${headIndex}-matrix`);
// 번역된 토큰 표시
translatedTokens.forEach(token => {
const tokenDiv = document.createElement('div');
tokenDiv.className = 'token';
tokenDiv.style.fontSize = '0.8em';
tokenDiv.textContent = token;
tokensDiv.appendChild(tokenDiv);
});
// 헤드별 특성화된 어텐션 매트릭스 (행열 단어 라벨 포함)
const matrixSize = Math.min(translatedTokens.length, 4); // 작은 매트릭스
const displayTokens = translatedTokens.slice(0, matrixSize);
// 그리드 레이아웃 설정 (라벨 공간 포함)
matrixDiv.style.display = 'grid';
matrixDiv.style.gridTemplateColumns = `60px repeat(${matrixSize}, 30px)`;
matrixDiv.style.gridTemplateRows = `40px repeat(${matrixSize}, 30px)`;
matrixDiv.style.gap = '2px';
matrixDiv.style.alignItems = 'center';
matrixDiv.style.justifyItems = 'center';
// 빈 셀 (좌상단)
const emptyCell = document.createElement('div');
emptyCell.style.width = '60px';
emptyCell.style.height = '40px';
matrixDiv.appendChild(emptyCell);
// 상단 열 라벨 (To)
for (let j = 0; j < matrixSize; j++) {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label col-label';
colLabel.style.width = '30px';
colLabel.style.height = '40px';
colLabel.style.fontSize = '0.6em';
colLabel.style.fontWeight = 'bold';
colLabel.style.color = '#666';
colLabel.style.display = 'flex';
colLabel.style.alignItems = 'center';
colLabel.style.justifyContent = 'center';
colLabel.style.background = '#f0f7ff';
colLabel.style.borderRadius = '3px';
colLabel.textContent = displayTokens[j];
matrixDiv.appendChild(colLabel);
}
// 매트릭스 행들 (행 라벨 + 셀들)
for (let i = 0; i < matrixSize; i++) {
// 좌측 행 라벨 (From)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label row-label';
rowLabel.style.width = '60px';
rowLabel.style.height = '30px';
rowLabel.style.fontSize = '0.6em';
rowLabel.style.fontWeight = 'bold';
rowLabel.style.color = '#666';
rowLabel.style.display = 'flex';
rowLabel.style.alignItems = 'center';
rowLabel.style.justifyContent = 'center';
rowLabel.style.background = '#fff0f0';
rowLabel.style.borderRadius = '3px';
rowLabel.textContent = displayTokens[i];
matrixDiv.appendChild(rowLabel);
// 어텐션 셀들
for (let j = 0; j < matrixSize; j++) {
await sleep(50);
const cell = document.createElement('div');
cell.className = 'attention-cell';
cell.style.width = '30px';
cell.style.height = '30px';
cell.style.fontSize = '0.7em';
let weight;
switch(headInfo.focus) {
case 'syntax': weight = (i === j) ? 0.8 : Math.random() * 0.4; break;
case 'semantic': weight = Math.random() * 0.7 + 0.2; break;
case 'position': weight = 1 - Math.abs(i - j) * 0.3; break;
case 'context': weight = (Math.abs(i - j) === 1) ? 0.9 : Math.random() * 0.3; break;
default: weight = Math.random() * 0.6;
}
cell.style.backgroundColor = getAttentionColor(weight);
cell.textContent = weight.toFixed(1);
cell.title = `${displayTokens[i]} → ${displayTokens[j]}: ${weight.toFixed(2)}`;
matrixDiv.appendChild(cell);
}
}
}
function highlightAttentionWeights(tokenIndex) {
const tokens = document.querySelectorAll('#attentionTokens .token');
tokens.forEach((token, i) => {
token.classList.toggle('selected', i === tokenIndex);
});
// 해당 행과 열 강조
const cells = document.querySelectorAll('#attentionWeights .attention-cell');
const matrixSize = tokens.length;
cells.forEach((cell, index) => {
const row = Math.floor(index / matrixSize);
const col = index % matrixSize;
cell.style.transform = (row === tokenIndex || col === tokenIndex) ? 'scale(1.2)' : 'scale(1)';
});
}
function showSelfAttentionConnections(tokenIndex) {
const tokens = document.querySelectorAll('#selfAttentionTokens .token');
tokens.forEach((token, i) => {
token.classList.toggle('focused', i === tokenIndex);
token.classList.toggle('selected', Math.abs(i - tokenIndex) <= 1);
});
}
function highlightConnection(row, col) {
// 어텐션 매트릭스에서 특정 셀 클릭 시 연결 강조
const tokens = document.querySelectorAll('#attentionTokens .token');
tokens.forEach((token, i) => {
token.classList.remove('selected', 'focused');
if (i === row) token.classList.add('focused');
if (i === col) token.classList.add('selected');
});
// 해당 셀 강조
const cells = document.querySelectorAll('#attentionWeights .attention-cell');
const matrixSize = tokens.length;
cells.forEach((cell, index) => {
const cellRow = Math.floor(index / matrixSize);
const cellCol = index % matrixSize;
cell.style.transform = (cellRow === row && cellCol === col) ? 'scale(1.3)' : 'scale(1)';
cell.style.zIndex = (cellRow === row && cellCol === col) ? '10' : '1';
});
}
// 🔄 초기화
function resetSimulation() {
currentStep = -1;
isSimulationRunning = false;
isModelTrained = false;
finalAttentionMatrix = [];
trainingData = { beforeAttention: [], afterAttention: [], beforeSelfAttention: [], afterSelfAttention: [] };
document.getElementById('progressFill').style.width = '0%';
document.getElementById('currentStep').textContent = '단계별 시뮬레이션을 시작하세요!';
document.getElementById('stepExplanation').textContent = '단계별 시뮬레이션을 시작하면 각 단계의 상세한 설명이 여기에 표시됩니다.';
document.querySelectorAll('.step-section').forEach(section => {
section.classList.remove('active');
});
document.querySelectorAll('.step-circle').forEach(circle => {
circle.classList.remove('active', 'completed');
});
// 학습 UI 초기화
const trainingProgress = document.getElementById('trainingProgress');
if (trainingProgress) trainingProgress.style.width = '0%';
const trainingStatus = document.getElementById('trainingStatus');
if (trainingStatus) trainingStatus.textContent = '학습 대기 중...';
const lossText = document.getElementById('lossText');
if (lossText) lossText.textContent = '손실: -';
const before = document.getElementById('attentionBefore');
const after = document.getElementById('attentionAfter');
const sab = document.getElementById('selfAttentionBefore');
const saa = document.getElementById('selfAttentionAfter');
if (before) before.innerHTML = '';
if (after) after.innerHTML = '';
if (sab) sab.innerHTML = '';
if (saa) saa.innerHTML = '';
// (제거됨) 보조 패널 초기화 코드
}
// 🎓 학습 과정 시뮬레이션 (이전 단계 어텐션 기반)
async function executeTrainingSimulation() {
console.log('🎓 학습 과정 시뮬레이션 시작');
// 토큰 존재 확인 (없으면 1단계를 유도)
if (!tokens || tokens.length === 0) {
alert('먼저 1단계를 진행해주세요!');
return;
}
// 전/후 비교의 '전' 상태: 멀티 레이어/헤드 생성 후 기본(0,0)으로 렌더
const tokensView = tokens.slice(0, Math.min(tokens.length, 8));
const n = tokensView.length; if (n === 0) return;
const baseW = ensureSquareMatrixFromFinal(tokensView);
const V = sampleValueVectors(n, 8);
const L = 3, H = 4; // 레이어/헤드 수(시뮬)
const W0Heads = makeHeadVariants(baseW, L, H);
// 캐시
window.__Vrand = V; window.__tokensViewN = n;
window.__W0Heads = W0Heads; // [L][H][n][n]
window.__selectedLayer = 0; window.__selectedHead = 0;
try {
renderContextPanel('Before', W0Heads[0][0], V, 0);
updateContextTitles(0,0, null);
} catch(e) { console.warn(e); }
// 학습 과정은 자동으로 시작하지 않고 버튼 클릭 대기
document.getElementById('trainingStatus').textContent = '학습 버튼을 클릭하세요';
}
// 학습 시작 함수 (실제 어텐션 최적화)
async function startTraining() {
if (isModelTrained) {
alert('모델이 이미 학습되었습니다!');
return;
}
const progressBar = document.getElementById('trainingProgress');
const statusDiv = document.getElementById('trainingStatus');
const lossText = document.getElementById('lossText');
statusDiv.textContent = '학습 중...';
// 멀티 레이어/헤드 학습 준비
const W0Heads = window.__W0Heads; // [L][H][n][n]
const nTok = window.__tokensViewN || Math.min(tokens.length, 6);
const L = W0Heads?.length || 0; const H = (W0Heads && W0Heads[0]) ? W0Heads[0].length : 0;
if (!W0Heads || !L || !H) { alert('초기 헤드 구성이 없습니다. 4단계를 다시 시작하세요.'); return; }
// current: 로짓(logits), target: 정규화된 확률
const current = Array.from({length:L}, (_,l)=> Array.from({length:H}, (_,h)=> toLogits(W0Heads[l][h])));
const target = Array.from({length:L}, (_,l)=> Array.from({length:H}, (_,h)=> normalizeRows(sharpenTarget(W0Heads[l][h], 1.8))));
// 이전 버전의 보조 매트릭스 렌더링 블록 제거됨 (undefined 변수 사용으로 오류 유발)
// 손실 기반 반복 학습 시뮬레이션 (교차 엔트로피 유사)
const epochs = 50; // 요청: 약 50회
const lr = 0.70; // 요청: 수렴 가속을 위한 학습률 추가 상향
for (let epoch = 1; epoch <= epochs; epoch++) {
let loss = 0;
for (let l=0;l<L;l++){
for (let h=0;h<H;h++){
for (let i = 0; i < nTok; i++) {
const row = current[l][h][i].slice();
const trow = target[l][h][i].slice();
// 확률(softmax)
const maxVal = Math.max(...row);
const exp = row.map(v => Math.exp(v - maxVal));
const sumExp = exp.reduce((a,b)=>a+b,0) || 1;
const prob = exp.map(v => v / sumExp);
// 타깃 확률: 이미 정규화되어 있음
const tprob = trow;
// CE 손실
const eps = 1e-8;
loss += -tprob.reduce((acc, tp, j) => acc + tp * Math.log((prob[j]||eps) + eps), 0);
// 로짓 업데이트
for (let j = 0; j < nTok; j++) {
const grad = tprob[j] - prob[j];
current[l][h][i][j] = row[j] + lr * grad;
}
}
}
}
const progress = Math.round((epoch / epochs) * 100);
progressBar.style.width = progress + '%';
statusDiv.textContent = `학습 진행 중... ${progress}% (에폭 ${epoch}/${epochs})`;
if (lossText) lossText.textContent = `손실(CE≈): ${loss.toFixed(4)}`;
await sleep(400);
}
// 가장 변화가 큰 레이어/헤드 선택
const deltas = [];
for (let l=0;l<L;l++){
for (let h=0;h<H;h++){
const d = meanAbsDiff( rowSoftmaxMatrix(current[l][h]), rowSoftmaxMatrix(W0Heads[l][h]) );
deltas.push({l,h,d});
}
}
deltas.sort((a,b)=> b.d - a.d);
const best = deltas[0];
window.__currHeads = current; // 학습 후 행렬 저장(로짓)
window.__selectedLayer = best.l; window.__selectedHead = best.h;
// Before/After 모두 선택된 헤드 기준으로 렌더
const Vuse = window.__Vrand || sampleValueVectors(nTok, 8);
try {
const ABefore = W0Heads[best.l][best.h];
const AAfter = rowSoftmaxMatrix(current[best.l][best.h]);
// 쿼리 행 선택: 구두점 토큰은 가능하면 제외하고, After에서 최댓값이 큰 행을 선호
const isPunc = (t)=> (/^[.,!?;:·…“”"'`~\\-]+$/.test((t||'').trim()));
const scored = [];
for (let i=0;i<AAfter.length;i++){
const maxRow = Math.max(...AAfter[i]);
const tok = (typeof tokens!=='undefined' && tokens[i]) ? tokens[i] : '';
const penalty = isPunc(tok) ? 1 : 0; // 구두점이면 불이익
scored.push({i, score: maxRow - penalty});
}
scored.sort((a,b)=> b.score - a.score);
const iBest = (scored.length? scored[0].i : 0);
window.__selectedRow = iBest;
renderContextPanel('Before', ABefore, Vuse, iBest);
renderContextPanel('After', AAfter, Vuse, iBest);
const afterPanel = document.getElementById('contextAfter');
if (afterPanel) afterPanel.style.display = 'block';
updateContextTitles(best.l, best.h, best.d);
} catch(e) { console.warn(e); }
// 상관관계 강한 단어쌍 비교(패널이 있었다면) 갱신
generateTopCorrelationsComparison();
isModelTrained = true;
statusDiv.textContent = '✅ 학습 완료! 상관관계가 강한 단어쌍들을 확인하세요.';
}
// 학습 전 데이터 생성 (6단계 어텐션 매트릭스 기반)
function generateBeforeTrainingData() {
// 상관관계가 강한 단어쌍 찾기 (상위 4-6개)
const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
const matrixSize = topCorrelations.length;
// 학습 전 어텐션 매트릭스 (선별된 강한 상관관계만)
const attentionBefore = document.getElementById('attentionBefore');
attentionBefore.style.gridTemplateColumns = `60px repeat(${matrixSize}, 50px)`;
attentionBefore.innerHTML = '';
// 좌상단 빈 셀
const emptyCell1 = document.createElement('div');
emptyCell1.style.width = '60px';
emptyCell1.style.height = '40px';
attentionBefore.appendChild(emptyCell1);
// 상단 열 라벨 (To)
topCorrelations.forEach(corr => {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label';
colLabel.style.fontSize = '0.6em';
colLabel.style.backgroundColor = '#e3f2fd';
colLabel.textContent = corr.toWord;
attentionBefore.appendChild(colLabel);
});
// 매트릭스 데이터 (학습 전 - 노이즈 추가된 원본 데이터)
topCorrelations.forEach((fromCorr, i) => {
// 좌측 행 라벨 (From)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label';
rowLabel.style.fontSize = '0.6em';
rowLabel.style.backgroundColor = '#fff3e0';
rowLabel.textContent = fromCorr.fromWord;
attentionBefore.appendChild(rowLabel);
// 데이터 셀들 (학습 전 - 약간의 노이즈와 함께)
topCorrelations.forEach((toCorr, j) => {
const cell = document.createElement('div');
cell.className = 'attention-cell';
cell.style.fontSize = '0.7em';
// 원본 값에 약간의 노이즈 추가 (학습 전)
const originalValue = finalAttentionMatrix[fromCorr.fromIndex] ?
finalAttentionMatrix[fromCorr.fromIndex][toCorr.toIndex] || 0.1 : 0.1;
const noisyValue = Math.max(0, originalValue + (Math.random() - 0.5) * 0.3);
cell.style.backgroundColor = getAttentionColor(noisyValue);
cell.textContent = noisyValue.toFixed(2);
cell.title = `${fromCorr.fromWord} → ${toCorr.toWord}: ${noisyValue.toFixed(3)}`;
attentionBefore.appendChild(cell);
});
});
}
// 🔍 상관관계가 강한 단어쌍 찾기 함수
function findTopCorrelations(attentionMatrix, topN = 6) {
const correlations = [];
// 모든 단어쌍의 어텐션 값 수집
for (let i = 0; i < attentionMatrix.length; i++) {
for (let j = 0; j < attentionMatrix[i].length; j++) {
if (i !== j) { // 자기 자신 제외
correlations.push({
fromIndex: i,
toIndex: j,
fromWord: tokens[i],
toWord: tokens[j],
weight: attentionMatrix[i][j]
});
}
}
}
// 어텐션 값 기준으로 정렬하고 상위 N개 선택
correlations.sort((a, b) => b.weight - a.weight);
return correlations.slice(0, topN);
}
// 🎯 학습 후 데이터 생성 (최적화된 어텐션)
function generateAfterTrainingData() {
const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
const matrixSize = topCorrelations.length;
// 학습 후 어텐션 매트릭스 (최적화된 값들)
const attentionAfter = document.getElementById('attentionAfter');
attentionAfter.style.gridTemplateColumns = `60px repeat(${matrixSize}, 50px)`;
attentionAfter.innerHTML = '';
// 좌상단 빈 셀
const emptyCell1 = document.createElement('div');
emptyCell1.style.width = '60px';
emptyCell1.style.height = '40px';
attentionAfter.appendChild(emptyCell1);
// 상단 열 라벨 (To)
topCorrelations.forEach(corr => {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label';
colLabel.style.fontSize = '0.6em';
colLabel.style.backgroundColor = '#e8f5e8';
colLabel.textContent = corr.toWord;
attentionAfter.appendChild(colLabel);
});
// 매트릭스 데이터 (학습 후 - 최적화된 값들)
topCorrelations.forEach((fromCorr, i) => {
// 좌측 행 라벨 (From)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label';
rowLabel.style.fontSize = '0.6em';
rowLabel.style.backgroundColor = '#fff8e1';
rowLabel.textContent = fromCorr.fromWord;
attentionAfter.appendChild(rowLabel);
// 데이터 셀들 (학습 후 - 최적화된 값)
topCorrelations.forEach((toCorr, j) => {
const cell = document.createElement('div');
cell.className = 'attention-cell';
cell.style.fontSize = '0.7em';
// 학습 후 최적화된 값 (강한 연결은 더 강하게, 약한 연결은 더 약하게)
const originalValue = finalAttentionMatrix[fromCorr.fromIndex] ?
finalAttentionMatrix[fromCorr.fromIndex][toCorr.toIndex] || 0.1 : 0.1;
const optimizedValue = originalValue > 0.5 ?
Math.min(0.95, originalValue * 1.3) : // 강한 연결 강화
Math.max(0.05, originalValue * 0.7); // 약한 연결 약화
cell.style.backgroundColor = getAttentionColor(optimizedValue);
cell.textContent = optimizedValue.toFixed(2);
cell.title = `${fromCorr.fromWord} → ${toCorr.toWord}: ${optimizedValue.toFixed(3)} (최적화됨)`;
attentionAfter.appendChild(cell);
});
});
}
// 📊 상관관계 분석 비교 생성
function generateTopCorrelationsComparison() {
const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
// 마스킹 정보 업데이트
const maskingInfo = document.getElementById('maskingInfo');
if (maskingInfo) {
maskingInfo.innerHTML = `
<h4>🎯 학습된 강한 상관관계 Top ${topCorrelations.length}</h4>
<ul>
${topCorrelations.map((corr, index) => `
<li><strong>${index + 1}위:</strong> ${corr.fromWord} → ${corr.toWord}
<span style="color: #2196f3;">(${(corr.weight * 100).toFixed(1)}%)</span>
</li>
`).join('')}
</ul>
<p style="margin-top: 15px; color: #666;">
💡 학습을 통해 의미적으로 관련성이 높은 단어들 간의 어텐션이 강화되었습니다.
</p>
`;
}
}
// 🕸️ 최종 어텐션 그래프 생성 함수 (1단계 입력 기반)
async function generateFinalAttentionGraph() {
if (!tokens || tokens.length === 0) {
alert('먼저 1단계에서 문장을 입력하고 시뮬레이션을 진행해주세요!');
return;
}
if (!isModelTrained) {
alert('먼저 7/8단계(학습)에서 모델을 학습시켜주세요!');
return;
}
// 학습 후 최적화된 어텐션 매트릭스 생성
const attentionMatrix = generateLearnedAttentionMatrix(translatedTokens);
// 그래프 시각화 (학습 후 결과만)
await visualizeAttentionGraph(translatedTokens, attentionMatrix, 'learned');
// 추가 분석 정보 표시
showConnectionAnalysis(attentionMatrix, translatedTokens);
}
// 연결 분석 정보 표시
function showConnectionAnalysis(matrix, tokens) {
// 기존 분석 메트릭들은 제거하고 간단한 정보만 표시
console.log('학습 후 어텐션 그래프가 생성되었습니다.');
console.log(`단어 수: ${tokens.length}, 연결 패턴: 최적화됨`);
}
// 🕸️ 어텐션 그래프 생성 함수
async function generateAttentionGraph() {
const testInput = document.getElementById('testInput').value.trim();
if (!testInput) {
alert('테스트 문장을 입력해주세요!');
return;
}
const tokens = tokenizeText(testInput);
const graphMode = document.querySelector('input[name="graphMode"]:checked').value;
// 어텐션 매트릭스 생성
let attentionMatrix;
if (graphMode === 'before') {
attentionMatrix = generateRandomAttentionMatrix(tokens);
} else if (graphMode === 'after') {
attentionMatrix = generateLearnedAttentionMatrix(tokens);
} else { // compare mode
const beforeMatrix = generateRandomAttentionMatrix(tokens);
const afterMatrix = generateLearnedAttentionMatrix(tokens);
attentionMatrix = afterMatrix; // 기본으로 학습 후 사용
}
// 그래프 시각화
await visualizeAttentionGraph(tokens, attentionMatrix, graphMode);
// 분석 메트릭 계산 및 표시
calculateGraphMetrics(attentionMatrix, tokens);
}
// 어텐션 그래프 시각화 함수
async function visualizeAttentionGraph(tokens, matrix, mode) {
const svg = document.getElementById('graphSvg');
svg.innerHTML = ''; // 기존 내용 클리어
const width = 600;
const height = 400;
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) * 0.25;
// 노드 위치 계산 (원형 배치)
const nodePositions = [];
for (let i = 0; i < tokens.length; i++) {
const angle = (2 * Math.PI * i) / tokens.length - Math.PI / 2;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
nodePositions.push({ x, y });
}
// 제목 추가
const title = document.createElementNS('<http://www.w3.org/2000/svg>', 'text');
title.setAttribute('x', centerX);
title.setAttribute('y', 30);
title.setAttribute('text-anchor', 'middle');
title.setAttribute('font-size', '16');
title.setAttribute('font-weight', 'bold');
title.setAttribute('fill', '#333');
title.textContent = '학습 후 단어간 어텐션 연결 강도';
svg.appendChild(title);
// 연결선 그리기 (어텐션 강도에 따라)
for (let i = 0; i < tokens.length; i++) {
for (let j = 0; j < tokens.length; j++) {
if (i !== j) {
const attention = matrix[i][j];
if (attention > 0.1) { // 임계값 이상만 표시
await sleep(50); // 부드러운 애니메이션
const line = document.createElementNS('<http://www.w3.org/2000/svg>', 'line');
line.setAttribute('x1', nodePositions[i].x);
line.setAttribute('y1', nodePositions[i].y);
line.setAttribute('x2', nodePositions[j].x);
line.setAttribute('y2', nodePositions[j].y);
// 어텐션 강도에 따른 스타일링
let strokeColor, strokeWidth, opacity;
if (attention > 0.7) {
strokeColor = '#ff4444';
strokeWidth = 4;
opacity = 0.9;
} else if (attention > 0.3) {
strokeColor = '#ffaa00';
strokeWidth = 3;
opacity = 0.7;
} else {
strokeColor = '#cccccc';
strokeWidth = 2;
opacity = 0.5;
}
line.setAttribute('stroke', strokeColor);
line.setAttribute('stroke-width', strokeWidth);
line.setAttribute('opacity', opacity);
line.setAttribute('class', 'graph-edge');
// 툴팁 추가
const title = document.createElementNS('<http://www.w3.org/2000/svg>', 'title');
title.textContent = `${tokens[i]} → ${tokens[j]}: ${(attention * 100).toFixed(1)}%`;
line.appendChild(title);
svg.appendChild(line);
}
}
}
}
// 노드 그리기
for (let i = 0; i < tokens.length; i++) {
await sleep(100);
const circle = document.createElementNS('<http://www.w3.org/2000/svg>', 'circle');
circle.setAttribute('cx', nodePositions[i].x);
circle.setAttribute('cy', nodePositions[i].y);
circle.setAttribute('r', 25);
circle.setAttribute('fill', '#4285f4');
circle.setAttribute('stroke', 'white');
circle.setAttribute('stroke-width', 3);
circle.setAttribute('class', 'graph-node');
svg.appendChild(circle);
// 단어 레이블
const text = document.createElementNS('<http://www.w3.org/2000/svg>', 'text');
text.setAttribute('x', nodePositions[i].x);
text.setAttribute('y', nodePositions[i].y + 5);
text.setAttribute('class', 'node-label');
text.textContent = tokens[i];
svg.appendChild(text);
}
}
// 그래프 분석 메트릭 계산
function calculateGraphMetrics(matrix, tokens) {
const totalConnections = matrix.length * (matrix.length - 1);
let activeConnections = 0;
let totalStrength = 0;
let strongConnections = 0;
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix.length; j++) {
if (i !== j && matrix[i][j] > 0.1) {
activeConnections++;
totalStrength += matrix[i][j];
if (matrix[i][j] > 0.5) {
strongConnections++;
}
}
}
}
const avgStrength = totalStrength / activeConnections;
const focusScore = (strongConnections / activeConnections) * 100;
document.getElementById('connectionStrength').textContent = `${(avgStrength * 100).toFixed(1)}%`;
document.getElementById('activeConnections').textContent = `${activeConnections}/${totalConnections}`;
document.getElementById('focusScore').textContent = `${focusScore.toFixed(1)}%`;
}
// 🎭 마스킹 시뮬레이션 함수
async function simulateMasking() {
const maskingSection = document.getElementById('maskingSimulation');
maskingSection.style.display = 'block';
const matrixSize = Math.min(translatedTokens.length, 4);
const displayTokens = translatedTokens.slice(0, matrixSize);
// 1. 원본 어텐션 매트릭스 생성
await generateMaskingMatrix('originalMatrix', displayTokens, false);
// 2초 후 마스킹 적용
setTimeout(async () => {
await generateMaskingMatrix('maskedMatrix', displayTokens, true);
}, 2000);
}
// 마스킹 매트릭스 생성 함수
async function generateMaskingMatrix(containerId, tokens, applyMask) {
const container = document.getElementById(containerId);
container.innerHTML = '';
const matrixSize = tokens.length;
// 그리드 설정 (라벨에 전체 토큰 표시)
container.style.display = 'grid';
container.style.gridTemplateColumns = `auto repeat(${matrixSize}, auto)`;
container.style.gridTemplateRows = `auto repeat(${matrixSize}, auto)`;
container.style.gap = '1px';
container.style.justifyItems = 'center';
container.style.alignItems = 'center';
// 빈 셀 (좌상단)
const emptyCell = document.createElement('div');
emptyCell.className = 'matrix-label-cell empty';
container.appendChild(emptyCell);
// 상단 열 라벨
for (let j = 0; j < matrixSize; j++) {
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label-cell column-label';
colLabel.textContent = tokens[j];
container.appendChild(colLabel);
}
// 매트릭스 행들
for (let i = 0; i < matrixSize; i++) {
// 행 라벨
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label-cell row-label';
rowLabel.textContent = tokens[i];
container.appendChild(rowLabel);
// 어텐션 셀들
for (let j = 0; j < matrixSize; j++) {
await sleep(100);
const cell = document.createElement('div');
cell.className = 'attention-cell';
// 마스킹 적용 여부 확인 (상삼각 영역)
const isMasked = applyMask && j > i;
if (isMasked) {
// 마스킹된 셀 (미래 토큰)
cell.classList.add('masked-cell');
cell.textContent = '-∞';
cell.style.backgroundColor = '#ffebee';
cell.style.color = '#d32f2f';
cell.title = `마스킹: ${tokens[i]}는 미래 토큰 ${tokens[j]}를 볼 수 없습니다`;
} else {
// 정상 셀 (현재/과거 토큰)
const weight = i === j ? 0.9 : Math.random() * 0.7 + 0.1;
cell.style.backgroundColor = getAttentionColor(weight);
cell.textContent = weight.toFixed(1);
cell.title = `${tokens[i]} → ${tokens[j]}: ${weight.toFixed(2)}`;
}
container.appendChild(cell);
}
}
}
// 학습 전 무작위 어텐션 매트릭스 생성
function generateRandomAttentionMatrix(tokens) {
const size = tokens.length;
const matrix = [];
for (let i = 0; i < size; i++) {
matrix[i] = [];
let rowSum = 0;
// 무작위 값 생성
for (let j = 0; j < size; j++) {
const randomValue = Math.random() * 0.8 + 0.1; // 0.1 ~ 0.9
matrix[i][j] = randomValue;
rowSum += randomValue;
}
// 정규화: 각 행의 합이 정확히 1이 되도록
for (let j = 0; j < size; j++) {
matrix[i][j] = matrix[i][j] / rowSum;
}
}
return matrix;
}
// 학습 후 의미적 어텐션 매트릭스 생성
function generateLearnedAttentionMatrix(tokens) {
const size = tokens.length;
const matrix = [];
for (let i = 0; i < size; i++) {
matrix[i] = [];
let rowSum = 0;
// 의미적 관련성을 고려한 값 생성
for (let j = 0; j < size; j++) {
let attention = 0.05; // 기본 낮은 어텐션
// 자기 자신에게 높은 어텐션
if (i === j) {
attention = 0.4 + Math.random() * 0.3;
}
// 인접한 단어들에게 중간 어텐션
else if (Math.abs(i - j) === 1) {
attention = 0.15 + Math.random() * 0.15;
}
// 의미적으로 관련된 단어들
else if (isSemanticallySimilar(tokens[i], tokens[j])) {
attention = 0.2 + Math.random() * 0.2;
}
// 그 외는 낮은 어텐션
else {
attention = 0.02 + Math.random() * 0.08;
}
matrix[i][j] = attention;
rowSum += attention;
}
// 정규화: 각 행의 합이 정확히 1이 되도록
for (let j = 0; j < size; j++) {
matrix[i][j] = matrix[i][j] / rowSum;
}
}
return matrix;
}
// 의미적 유사성 판단
function isSemanticallySimilar(word1, word2) {
const semanticGroups = [
['딥러닝', '머신러닝', '인공지능', 'AI', '학습', '신경망'],
['공부', '학습', '연구', '분석'],
['트랜스포머', '어텐션', '매트릭스', '모델'],
['데이터', '정보', '입력', '출력']
];
return semanticGroups.some(group =>
group.includes(word1) && group.includes(word2) && word1 !== word2
);
}
// 비교용 매트릭스 시각화 (행/열 라벨 포함)
async function visualizeComparisonMatrix(containerId, matrix, tokens) {
const container = document.getElementById(containerId);
const size = Math.min(tokens.length, 6); // 최대 6x6
// 그리드 레이아웃: 첫 번째 행과 열은 라벨용
container.style.gridTemplateColumns = `80px repeat(${size}, 40px)`;
container.innerHTML = '';
// 좌상단 빈 셀
const emptyCell = document.createElement('div');
emptyCell.className = 'matrix-label-cell empty';
container.appendChild(emptyCell);
// 상단 열 라벨 (Query 또는 Target)
for (let j = 0; j < size; j++) {
await sleep(30);
const colLabel = document.createElement('div');
colLabel.className = 'matrix-label-cell column-label';
colLabel.textContent = tokens[j];
colLabel.title = `Target: ${tokens[j]}`;
container.appendChild(colLabel);
}
// 각 행에 대해
for (let i = 0; i < size; i++) {
// 왼쪽 행 라벨 (Key/Value 또는 Source)
const rowLabel = document.createElement('div');
rowLabel.className = 'matrix-label-cell row-label';
rowLabel.textContent = tokens[i];
rowLabel.title = `Source: ${tokens[i]}`;
container.appendChild(rowLabel);
// 어텐션 값 셀들
for (let j = 0; j < size; j++) {
await sleep(50);
const cell = document.createElement('div');
cell.className = 'attention-cell';
cell.style.width = '40px';
cell.style.height = '40px';
const weight = matrix[i][j];
cell.style.backgroundColor = getAttentionColor(weight);
cell.style.opacity = 0.3 + (weight * 0.7); // 더 명확한 투명도
cell.textContent = weight.toFixed(3);
cell.title = `${tokens[i]} → ${tokens[j]}: ${weight.toFixed(4)}`;
// 클릭 이벤트
cell.addEventListener('click', () => {
highlightRelatedCells(container, i + 1, j + 1, size); // +1은 라벨 때문
});
container.appendChild(cell);
}
}
}
// 관련 셀 하이라이트 (라벨 구조 고려)
function highlightRelatedCells(container, row, col, size) {
const allCells = container.querySelectorAll('.attention-cell, .matrix-label-cell');
// 모든 셀 초기화
allCells.forEach(cell => cell.classList.remove('highlighted'));
// 라벨이 있는 구조에서 인덱스 계산
// 그리드: [empty][col0][col1]...[colN]
// [row0][cell00][cell01]...[cell0N]
// [row1][cell10][cell11]...[cell1N]
const totalCols = size + 1; // 라벨 열 포함
// 선택된 행의 라벨 하이라이트
const rowLabelIndex = row * totalCols;
if (allCells[rowLabelIndex]) {
allCells[rowLabelIndex].classList.add('highlighted');
}
// 선택된 열의 라벨 하이라이트
const colLabelIndex = col;
if (allCells[colLabelIndex]) {
allCells[colLabelIndex].classList.add('highlighted');
}
// 선택된 행의 모든 어텐션 셀 하이라이트
for (let j = 1; j <= size; j++) {
const cellIndex = row * totalCols + j;
if (allCells[cellIndex] && allCells[cellIndex].classList.contains('attention-cell')) {
allCells[cellIndex].classList.add('highlighted');
}
}
// 선택된 열의 모든 어텐션 셀 하이라이트
for (let i = 1; i <= size; i++) {
const cellIndex = i * totalCols + col;
if (allCells[cellIndex] && allCells[cellIndex].classList.contains('attention-cell')) {
allCells[cellIndex].classList.add('highlighted');
}
}
}
// 메트릭 계산 및 표시
function calculateAndDisplayMetrics(beforeMatrix, afterMatrix) {
// 어텐션 집중도 (최고값들의 평균)
const concentrationBefore = calculateConcentration(beforeMatrix);
const concentrationAfter = calculateConcentration(afterMatrix);
// 관련성 정확도 (의미적 관계 반영도)
const relevanceBefore = calculateRelevance(beforeMatrix);
const relevanceAfter = calculateRelevance(afterMatrix);
// 전체 개선도
const improvement = Math.round(
((concentrationAfter - concentrationBefore) + (relevanceAfter - relevanceBefore)) / 2
);
// 애니메이션으로 표시
animateMetric('concentrationBefore', concentrationBefore, 'concentrationBeforeText');
animateMetric('concentrationAfter', concentrationAfter, 'concentrationAfterText');
animateMetric('relevanceBefore', relevanceBefore, 'relevanceBeforeText');
animateMetric('relevanceAfter', relevanceAfter, 'relevanceAfterText');
// 개선도 원형 차트
setTimeout(() => {
const improvementScore = Math.max(0, Math.min(100, improvement + 50)); // 기준점 조정
document.getElementById('improvementScore').style.setProperty('--progress', improvementScore + '%');
document.getElementById('improvementText').textContent = improvementScore + '%';
const descriptions = [
{ min: 80, text: '🎉 혁신적 학습 성과!' },
{ min: 65, text: '✨ 탁월한 개선 효과!' },
{ min: 50, text: '👍 우수한 학습 결과' },
{ min: 35, text: '📈 긍정적 개선 감지' },
{ min: 0, text: '🔄 지속적 학습 필요' }
];
const description = descriptions.find(d => improvementScore >= d.min).text;
document.getElementById('improvementDescription').textContent = description;
}, 1500);
}
// 집중도 계산 (높은 어텐션 값들의 비율)
function calculateConcentration(matrix) {
let highAttentionCount = 0;
let totalCells = 0;
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
totalCells++;
if (matrix[i][j] > 0.3) highAttentionCount++;
}
}
return Math.round((highAttentionCount / totalCells) * 100);
}
// 관련성 계산 (의미적으로 관련된 부분의 어텐션 강도)
function calculateRelevance(matrix) {
let relevantSum = 0;
let totalSum = 0;
let relevantCount = 0;
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
totalSum += matrix[i][j];
// 대각선과 인접 영역을 관련성 있는 것으로 간주
if (i === j || Math.abs(i - j) <= 1) {
relevantSum += matrix[i][j];
relevantCount++;
}
}
}
return Math.round((relevantSum / totalSum) * 100);
}
// 메트릭 애니메이션
function animateMetric(fillId, targetValue, textId) {
const fillElement = document.getElementById(fillId);
const textElement = document.getElementById(textId);
let currentValue = 0;
const increment = targetValue / 60;
const animate = () => {
currentValue += increment;
if (currentValue >= targetValue) {
currentValue = targetValue;
fillElement.style.width = currentValue + '%';
textElement.textContent = Math.round(currentValue) + '%';
} else {
fillElement.style.width = currentValue + '%';
textElement.textContent = Math.round(currentValue) + '%';
requestAnimationFrame(animate);
}
};
setTimeout(animate, Math.random() * 800);
}
// 셀프 어텐션 비교 생성
function generateSelfAttentionComparison() {
const size = Math.min(translatedTokens.length, 6);
const display = translatedTokens.slice(0, size);
const before = document.getElementById('selfAttentionBefore');
const after = document.getElementById('selfAttentionAfter');
before.innerHTML = '';
after.innerHTML = '';
before.style.gridTemplateColumns = `60px repeat(${size}, 40px)`;
after.style.gridTemplateColumns = `60px repeat(${size}, 40px)`;
// header
before.appendChild(document.createElement('div'));
after.appendChild(document.createElement('div'));
for (let j = 0; j < size; j++) {
const cb = document.createElement('div');
cb.className = 'matrix-label';
cb.textContent = display[j];
before.appendChild(cb);
const ca = document.createElement('div');
ca.className = 'matrix-label';
ca.textContent = display[j];
after.appendChild(ca);
}
for (let i = 0; i < size; i++) {
const rb = document.createElement('div');
rb.className = 'matrix-label';
rb.textContent = display[i];
before.appendChild(rb);
const ra = document.createElement('div');
ra.className = 'matrix-label';
ra.textContent = display[i];
after.appendChild(ra);
for (let j = 0; j < size; j++) {
const wBefore = i === j ? 0.2 + Math.random() * 0.1 : 0.05 + Math.random() * 0.1;
const wAfter = i === j ? Math.min(0.95, wBefore + 0.5) : Math.max(0.05, wBefore - 0.05);
const cbCell = document.createElement('div');
cbCell.className = 'attention-cell';
cbCell.textContent = wBefore.toFixed(2);
cbCell.style.backgroundColor = getAttentionColor(wBefore);
before.appendChild(cbCell);
const caCell = document.createElement('div');
caCell.className = 'attention-cell';
caCell.textContent = wAfter.toFixed(2);
caCell.style.backgroundColor = getAttentionColor(wAfter);
after.appendChild(caCell);
}
}
}
// 인터랙티브 기능들
function highlightDifferences() {
const beforeCells = document.querySelectorAll('#testAttentionBefore .attention-cell');
const afterCells = document.querySelectorAll('#testAttentionAfter .attention-cell');
beforeCells.forEach((cell, index) => {
if (afterCells[index]) {
const beforeOpacity = parseFloat(cell.style.opacity) || 0;
const afterOpacity = parseFloat(afterCells[index].style.opacity) || 0;
if (Math.abs(beforeOpacity - afterOpacity) > 0.2) {
cell.classList.add('highlight-difference');
afterCells[index].classList.add('highlight-difference');
}
}
});
setTimeout(() => {
document.querySelectorAll('.highlight-difference').forEach(cell => {
cell.classList.remove('highlight-difference');
});
}, 3000);
}
function showHeatmap() {
const matrices = document.querySelectorAll('#testAttentionBefore, #testAttentionAfter');
matrices.forEach(matrix => {
matrix.classList.toggle('heatmap-mode');
});
}
function animateEvolution() {
const afterMatrix = document.getElementById('testAttentionAfter');
afterMatrix.classList.add('evolution-animation');
setTimeout(() => {
afterMatrix.classList.remove('evolution-animation');
}, 3000);
}
function resetComparison() {
// 하이라이트 제거
document.querySelectorAll('.highlight-difference').forEach(cell => {
cell.classList.remove('highlight-difference');
});
// 히트맵 모드 해제
document.querySelectorAll('.heatmap-mode').forEach(matrix => {
matrix.classList.remove('heatmap-mode');
});
// 애니메이션 클래스 제거
document.querySelectorAll('.evolution-animation').forEach(element => {
element.classList.remove('evolution-animation');
});
alert('🔄 비교 화면이 초기화되었습니다!');
}
// 📈 가중치/가중합 전후 비교 렌더링 유틸
function softmaxRow(row){
const m = Math.max(...row);
const ex = row.map(v=>Math.exp(v - m));
const s = ex.reduce((a,b)=>a+b,0)||1;
return ex.map(v=>v/s);
}
function ensureSquareMatrixFromFinal(tokens){
// finalAttentionMatrix는 실수 값. 행별 softmax로 정규화 보장
const n = Math.min(tokens.length, Math.max(2, finalAttentionMatrix.length || tokens.length));
const M = Array.from({length:n}, (_,i)=> Array.from({length:n}, (_,j)=>
(finalAttentionMatrix[i]?.[j]) ?? (i===j?0.4:0.1+Math.random()*0.2)
));
return M.map(softmaxRow);
}
function sampleValueVectors(n, d=8){
// 간단한 고정난수 기반 V 행렬 생성(재현성보다 시각성이 목적)
const V = Array.from({length:n}, ()=> Array.from({length:d}, ()=> (Math.random()*2-1)));
return V;
}
function weightedSum(attRow, V){
const d = V[0].length; const out = Array(d).fill(0);
for(let j=0;j<attRow.length;j++){
for(let k=0;k<d;k++) out[k]+= attRow[j]*V[j][k];
}
return out;
}
// === 4단계 확장 유틸: 멀티 레이어/헤드와 변화량 측정 ===
// 단일 계열 색상: 블루-보라 계열 고정, 값에 따라 투명도로 강도 표현
const BASE_HUE = 250; // 보라 계열
const BASE_SAT = 65;
const BASE_LIG = 55;
function deepCopyMatrix(M){ return M.map(r=> r.slice()); }
function rowSoftmaxMatrix(M){ return M.map(softmaxRow); }
function meanAbsDiff(A,B){
let s=0,c=0; for(let i=0;i<A.length;i++){ for(let j=0;j<A[i].length;j++){ s+= Math.abs((A[i][j]||0)-(B[i]?.[j]||0)); c++; }}
return c? s/c : 0;
}
function makeHeadVariants(baseW, L=3, H=4){
const n = baseW.length;
const heads = Array.from({length:L}, ()=> Array.from({length:H}, ()=> Array.from({length:n}, (_,i)=> Array.from({length:n}, (_,j)=> baseW[i][j]))));
// 각 레이어/헤드에 약간의 편향/노이즈 부여(초기화 다양성)
for (let l=0;l<L;l++){
for (let h=0;h<H;h++){
const scale = 0.05 + 0.03*l + 0.02*h;
for (let i=0;i<n;i++){
for (let j=0;j<n;j++){
const bias = (i===j? 0.06: 0) + (Math.random()-0.5)*scale;
heads[l][h][i][j] = Math.max(0, heads[l][h][i][j] + bias);
}
}
// 행별 softmax 정규화
heads[l][h] = rowSoftmaxMatrix(heads[l][h]);
}
}
return heads;
}
function sharpenTarget(W, factor=1.5){
// 각 행에서 최고값은 확대, 나머지는 조금 축소 → 변화가 드러나도록 타깃 형성
const n = W.length; const T = Array.from({length:n}, ()=> Array(n).fill(0));
for (let i=0;i<n;i++){
const row = W[i].slice(); const jStar = row.indexOf(Math.max(...row));
for (let j=0;j<n;j++){
const v = row[j];
T[i][j] = (j===jStar) ? Math.min(0.98, v*factor) : Math.max(0.01, v*(2-factor));
}
}
return T;
}
function updateContextTitles(layerIdx, headIdx, delta){
const beforeTitle = document.querySelector('#contextBefore .context-title');
const afterTitle = document.querySelector('#contextAfter .context-title');
const suffix = (delta==null)? '' : ` · 선택: L${layerIdx+1}/H${headIdx+1} (Δ=${delta.toFixed(3)})`;
if (beforeTitle) beforeTitle.textContent = `학습 전: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)${suffix}`;
if (afterTitle) afterTitle.textContent = `학습 후: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)${suffix}`;
}
// === 학습용 보조 유틸 ===
const EPS = 1e-8;
function toLogits(P){
return P.map(row => row.map(v => Math.log(Math.max(EPS, v))));
}
function normalizeRows(M){
return M.map(row => {
const r = row.map(v => Math.max(EPS, v));
const s = r.reduce((a,b)=>a+b,0) || 1;
return r.map(v => v/s);
});
}
// 컨텍스트 패널 렌더(이미지와 유사): 하나의 대표 행 i를 선택해 항별 가중합 시각화
function renderContextPanel(phase, W, V, i){
const isBefore = phase==='Before';
const hostId = isBefore ? 'contextBeforeGrid' : 'contextAfterGrid';
const grid = document.getElementById(hostId); if(!grid) return;
grid.innerHTML='';
const n = Math.min(W.length, tokens.length);
const dDots = 5; // Hidden state 시각용 점 개수(고정)
// 각 토큰 행 구성
const arrowChar = '➜';
// 현재 쿼리 인덱스 i에 대한 가중치 벡터 a = W[i]
const a = (W[i] && W[i].length===n) ? W[i] : Array.from({length:n},(_,j)=> 1/n);
let aMax = Math.max(...a);
if (!isFinite(aMax) || aMax<=0) aMax = 1;
// After 패널에서는 C에 가장 크게 기여한 토큰(가중치 최댓값)을 강조 박스 처리
const isAfter = (phase==='After');
const maxIdx = isAfter ? (a.indexOf(Math.max(...a))) : -1;
for(let r=0;r<n;r++){
// 1) 왼쪽 라벨(토큰)
const lb = document.createElement('div'); lb.className='ctx-label'; lb.textContent = tokens[r] || `토큰${r+1}`;
if (isAfter && r===maxIdx){ lb.style.border='3px solid #ff5722'; lb.style.borderRadius='8px'; lb.style.padding='4px 6px'; lb.style.background='#fff3e0'; }
grid.appendChild(lb);
// 2) Hidden State 박스(임의 색 농도)
const hs = document.createElement('div'); hs.className='hs-box'; grid.appendChild(hs);
for(let k=0;k<dDots;k++){
const dot = document.createElement('div'); dot.className='vec-dot';
dot.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
dot.style.opacity = '0.85';
hs.appendChild(dot);
}
// 3) 곱셈 기호
const mul = document.createElement('div'); mul.className='mul'; mul.textContent = '×'; grid.appendChild(mul);
// 4) 가중치 바 + 값
const wb = document.createElement('div'); wb.className='weight-box'; grid.appendChild(wb);
const bar = document.createElement('div'); bar.className='weight-bar'; wb.appendChild(bar);
const fill = document.createElement('div'); fill.className='weight-fill';
const wVal = a[r]; // 쿼리 i가 각 값 j=r에 주는 가중치
fill.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
fill.style.opacity = (0.25 + 0.75*Math.min(1,wVal*1.8)).toFixed(2);
fill.style.width = `${Math.max(2, Math.round(wVal*100))}%`; bar.appendChild(fill);
const val = document.createElement('div'); val.className='weight-val'; val.textContent = wVal.toFixed(3); wb.appendChild(val);
// 5) 화살표
const ar = document.createElement('div'); ar.className='arrow'; ar.textContent = arrowChar; grid.appendChild(ar);
// 6) 항별 가중합 결과 박스(시각용 도트)
const term = document.createElement('div'); term.className='ctx-box'; grid.appendChild(term);
// 결과 강도: wVal 비례로 채도/명도 조절
for(let k=0;k<dDots;k++){
const dot = document.createElement('div'); dot.className='vec-dot';
dot.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
const scale = (wVal/aMax);
dot.style.opacity = (0.25 + 0.75*scale).toFixed(2);
term.appendChild(dot);
}
// 각 항 사이에 + 추가 (마지막 항 제외)
if (r < n-1){
for(let c=0;c<5;c++){ const blank = document.createElement('div'); blank.textContent=''; grid.appendChild(blank); }
const plus = document.createElement('div'); plus.className='plus'; plus.textContent = '+'; grid.appendChild(plus);
}
}
// 마지막 줄: = C(context vector)
for(let c=0;c<4;c++){ const blank = document.createElement('div'); blank.textContent=''; grid.appendChild(blank); }
const eq = document.createElement('div'); eq.className='eq'; eq.textContent = '='; grid.appendChild(eq);
const cbox = document.createElement('div'); cbox.className='ctx-box'; grid.appendChild(cbox);
// 간단히 Σ a_j V_j를 계산하여 4개 점의 색 강도에 반영
const Vuse = V && V.length ? V : sampleValueVectors(n, 5);
const ctx = weightedSum(a, Vuse);
const ctx5 = ctx.slice(0,5);
const maxAbs = Math.max(1e-6, ...ctx5.map(x=>Math.abs(x)));
// 컨텍스트 벡터: 단일 계열 색상 고정, 값 크기에 따라 불투명도 증가
ctx5.forEach((val)=>{
const d = document.createElement('div'); d.className='vec-dot';
const inten = Math.min(1, Math.abs(val)/maxAbs);
d.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG - (isBefore?0:5)}%)`;
d.style.opacity = (0.5 + 0.5*inten).toFixed(2); // 더 진하게
cbox.appendChild(d);
});
// 라벨 한 줄
for(let c=0;c<5;c++){ const blank = document.createElement('div'); grid.appendChild(blank); }
const label = document.createElement('div'); label.style.color = '#607d8b'; label.style.fontWeight='700'; label.textContent = 'C (context vector)'; grid.appendChild(label);
}
// � 선택한 문단 로드 함수
function loadSelectedText() {
const select = document.getElementById('inputSentence');
const selectedTextDiv = document.getElementById('selectedText');
if (select.value) {
selectedTextDiv.style.display = 'block';
selectedTextDiv.textContent = select.value;
// 문단 변경 시 학습 상태 초기화
isModelTrained = false;
finalAttentionMatrix = [];
trainingData = { beforeAttention: [], afterAttention: [], beforeSelfAttention: [], afterSelfAttention: [] };
const trainingProgress = document.getElementById('trainingProgress');
if (trainingProgress) trainingProgress.style.width = '0%';
const trainingStatus = document.getElementById('trainingStatus');
if (trainingStatus) trainingStatus.textContent = '학습 대기 중...';
const lossText = document.getElementById('lossText');
if (lossText) lossText.textContent = '손실: -';
const before = document.getElementById('attentionBefore');
const after = document.getElementById('attentionAfter');
const sab = document.getElementById('selfAttentionBefore');
const saa = document.getElementById('selfAttentionAfter');
if (before) before.innerHTML = '';
if (after) after.innerHTML = '';
if (sab) sab.innerHTML = '';
if (saa) saa.innerHTML = '';
// (제거됨) 보조 패널 요소 초기화
} else {
selectedTextDiv.style.display = 'none';
}
}
// �🛠️ 유틸리티 함수
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 🎮 키보드 단축키
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' && e.ctrlKey) {
e.preventDefault();
nextStep();
} else if (e.key === 'ArrowLeft' && e.ctrlKey) {
e.preventDefault();
prevStep();
} else if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault();
startStepSimulation();
}
});
// 🏁 초기화
console.log('🤖 어텐션 8단계 시뮬레이션 준비 완료!');
console.log('📖 단축키: Ctrl+Enter(시작), Ctrl+→(다음), Ctrl+←(이전)');
</script>
</body>
</html>