thai-nlp-toolkit / model /transformer_block.py
puttimej's picture
Upload model/transformer_block.py with huggingface_hub
1511d72 verified
Raw
History Blame Contribute Delete
4.3 kB
import torch
import torch.nn as nn
from torch import Tensor
from typing import Optional, Tuple
import sys
import pathlib
# Add project root to sys.path
root = pathlib.Path(__file__).resolve().parent
while root.parent != root:
if (root / "requirements.txt").exists() or (root / "README.md").exists():
sys.path.append(str(root))
break
root = root.parent
from model.attention import MultiHeadSelfAttention
class TransformerBlock(nn.Module):
"""Pre-norm Transformer encoder block.
Pre-norm (LN → sublayer → residual) มีเสถียรภาพ training
ดีกว่า post-norm โดยเฉพาะตอน network ลึกหรือ lr สูง
"""
def __init__(self, d_model: int, num_heads: int, d_ff: int, dropout: float = 0.1):
super().__init__()
# 1. เรียกใช้งานชิ้นส่วนประกอบย่อยที่เราทำไว้
self.attn = MultiHeadSelfAttention(d_model, num_heads, dropout)
# Pre-Norm ต้องการ LayerNorm 2 ชุด (ก่อนเข้า Attention และ ก่อนเข้า FFN)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# Feed-Forward Network (FFN) ขยายมิติเป็น d_ff แล้วกลับมาเป็น d_model
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(), # โค้ดภาษาไทยแนะนำใช้ GELU จะช่วยให้ลื่นไหลและเสถียรกว่า ReLU ครับ
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout)
)
def forward(self, x: Tensor, padding_mask: Optional[Tensor] = None) -> Tuple[Tensor, Tensor]:
# Input shaper x: (B, T, d_model)
# Input shape padding_mask: (B, T)
# ── Step 1: Pre-Norm & Multi-Head Self-Attention ──────────────────
# ทำตาม Pre-Norm: ส่ง ไปนวดด้วย norm1 ก่อนเข้า Attention
residual = x
normed_x = self.norm1(x)
# โยนเข้า Attention พร้อมส่งต่อ padding_mask (ดึงมาเฉพาะตำแหน่งแรกที่เป็นตัวเอาต์พุตเวกเตอร์)
attn_out, attn_weights = self.attn(normed_x, key_padding_mask=padding_mask)
# Residual Connection ชุดที่ 1: เอาผลลัพธ์จาก Attention มาบวกกับ x เดิม
x = residual + attn_out
# ── Step 2: Pre-Norm & Feed-Forward Network ───────────────────────
# ส่ง x ไปนวดด้วย norm2 ก่อนเข้า FFN
residual = x
normed_x = self.norm2(x)
ffn_out = self.ffn(normed_x)
# Residual Connection ชุดที่ 2: เอาผลลัพธ์จาก FFN มาบวกกับ x เดิม
x = residual + ffn_out
return x, attn_weights
if __name__ == "__main__":
# Quick sanity check
block = TransformerBlock(d_model=256, num_heads=8, d_ff=1024)
block.eval() # ตั้งเป็นโหมดประเมินผลเพื่อปิด dropout
# Sample input: batch size 2, sequence length 16, model dimension 256
dummy_x = torch.randn(2, 16, 256)
dummy_mask = torch.zeros(2, 16, dtype=torch.bool) # False means no padding
dummy_mask[0, 12:] = True # ตัวอย่าง: ลองสมมติให้ประโยคแรกมี padding ท้ายประโยค
out, attn_weights = block(dummy_x, padding_mask=dummy_mask)
assert out.shape == (2, 16, 256), f"Wrong output shape: {out.shape}"
assert attn_weights.shape == (2, 8, 16, 16), f"Wrong attention weights shape: {attn_weights.shape}"
assert not torch.isnan(out).any(), "พบค่า NaN ในโมดูล"
assert not torch.isnan(attn_weights).any(), "พบค่า NaN ใน attention weights"
print("TransformerBlock test passed!")