Search
Duplicate

Transformer속 핵심 Attension 시뮬레이션(with YangPhago)

목차(클릭하세요)
AI와 함께 코딩, 개노다로 2일간 약 30시간을 투자해 만든 한국인을 위한 Attension 메카니즘 체험시뮬레이션(코파일럿이 짱
-원래는 클로드의 아티팩트나 구글 제미나이 캔버스로 구현하려고 했으나 코드가 3000줄이 넘어가는 관계로 코파일럿으로 구현 후, 깃허브 탑재
[참고문헌]
[코드 전체]
<!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>
JavaScript
복사