# Chapter 2 - GPT 한국어 언어 모델 구현 (실행 결과 포함)
## 2.1 Data
### 데이터셋 설치 및 로드
```python
%pip install -q datasets
```
```python
from datasets import load_dataset
dataset = load_dataset("daekeun-ml/naver-news-summarization-ko")
```
### 데이터셋 구조 확인
```python
data = dataset
data
```
**출력:**
```
DatasetDict({
train: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 22194
})
validation: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2466
})
test: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2740
})
})
```
### 샘플 데이터 확인
```python
data["train"]["document"][0]
```
**출력:**
```
'앵커 정부가 올해 하반기 우리 경제의 버팀목인 수출 확대를 위해 총력을 기울이기로 했습니다. 특히 수출 중소기업의 물류난 해소를 위해 무역금융 규모를 40조 원 이상 확대하고 물류비 지원과 임시선박 투입 등을 추진하기로 했습니다. 류환홍 기자가 보도합니다. 기자 수출은 최고의 실적을 보였지만 수입액이 급증하면서 올해 상반기 우리나라 무역수지는 역대 최악인 103억 달러 적자를 기록했습니다. 정부가 수출확대에 총력을 기울이기로 한 것은 원자재 가격 상승 등 대외 리스크가 가중되는 상황에서 수출 증가세 지속이야말로 한국경제의 회복을 위한 열쇠라고 본 것입니다. 추경호 경제부총리 겸 기획재정부 장관 정부는 우리 경제의 성장엔진인 수출이 높은 증가세를 지속할 수 있도록 총력을 다하겠습니다. 우선 물류 부담 증가 원자재 가격 상승 등 가중되고 있는 대외 리스크에 대해 적극 대응하겠습니다. 특히 중소기업과 중견기업 수출 지원을 위해 무역금융 규모를 연초 목표보다 40조 원 늘린 301조 원까지 확대하고 물류비 부담을 줄이기 위한 대책도 마련했습니다. 이창양 산업통상자원부 장관 국제 해상운임이 안정될 때까지 월 4척 이상의 임시선박을 지속 투입하는 한편 중소기업 전용 선복 적재 용량 도 현재보다 주당 50TEU 늘려 공급하겠습니다. 하반기에 우리 기업들의 수출 기회를 늘리기 위해 2 500여 개 수출기업을 대상으로 해외 전시회 참가를 지원하는 등 마케팅 지원도 벌이기로 했습니다. 정부는 또 이달 중으로 반도체를 비롯한 첨단 산업 육성 전략을 마련해 수출 증가세를 뒷받침하고 에너지 소비를 줄이기 위한 효율화 방안을 마련해 무역수지 개선에 나서기로 했습니다. YTN 류환홍입니다.'
```
### 문자 집합 생성
```python
print(sorted(list(set(data["train"]["document"][0]))))
```
**출력:**
```
[' ', '.', '0', '1', '2', '3', '4', '5', 'E', 'N', 'T', 'U', 'Y', '가', '개', '것', '겠', '격', '견', '겸', '경', '고', '공', '과', '관', '국', '규', '극', '금', '급', '기', '까', '나', '난', '너', '높', '는', '늘', '니', '다', '단', '달', '담', '당', '대', '도', '되', '될', '뒷', '들', '등', '때', '또', '라', '략', '량', '러', '려', '력', '련', '로', '록', '롯', '류', '를', '리', '린', '마', '만', '말', '면', '모', '목', '무', '물', '박', '반', '받', '방', '버', '벌', '보', '복', '본', '부', '비', '산', '상', '서', '선', '성', '세', '소', '속', '쇠', '수', '스', '습', '승', '시', '실', '악', '안', '액', '앵', '야', '양', '억', '업', '에', '엔', '여', '역', '연', '열', '였', '올', '외', '용', '우', '운', '울', '원', '월', '위', '육', '율', '융', '으', '은', '을', '응', '의', '이', '인', '임', '입', '있', '자', '장', '재', '적', '전', '정', '제', '조', '주', '줄', '중', '증', '지', '진', '참', '창', '책', '척', '첨', '체', '초', '총', '최', '추', '출', '침', '커', '케', '크', '통', '투', '특', '팀', '팅', '편', '표', '하', '한', '할', '합', '해', '했', '현', '호', '홍', '화', '확', '환', '황', '회', '획', '효', '히']
```
### 전체 문서의 문자 집합 구성
```python
# 이것은 텍스트에 나타나는 모든 문자 집합을 가져온 것입니다.
# 그것들은 고유한 것들만 남겨서 sorted로 정렬을 하면 아래와 같은 결과를 볼 수 있습니다.
ko_text = "".join(data["train"]["document"])
ko_chars = sorted(list(set((ko_text))))
ko_vocab_size = len(ko_chars)
print("총 글자 수 :", ko_vocab_size)
```
**출력:**
```
총 글자 수 : 2701
```
### 문자 집합 일부 확인
```python
print(ko_chars[2000:2100])
```
**출력:**
```
['왓', '왔', '왕', '왜', '왠', '외', '왹', '왼', '요', '욕', '욘', '욜', '욤', '욥', '용', '우', '욱', '운', '욷', '울', '움', '웁', '웃', '웅', '워', '웍', '원', '월', '웜', '웠', '웡', '웨', '웬', '웰', '웸', '웹', '웻', '위', '윅', '윈', '윌', '윔', '윕', '윗', '윙', '유', '육', '윤', '율', '윱', '윳', '융', '으', '윽', '은', '을', '음', '읍', '읏', '응', '의', '읠', '이', '익', '인', '일', '읽', '잃', '임', '입', '잇', '있', '잉', '잊', '잎', '자', '작', '잔', '잖', '잘', '잠', '잡', '잣', '잤', '장', '잦', '재', '잭', '잰', '잼', '잽', '잿', '쟁', '쟈', '쟝', '쟤', '저', '적', '전', '절']
```
### 토크나이저 함수 생성
```python
character_to_ids = {char:i for i, char in enumerate(ko_chars)}
ids_to_character = {i:char for i, char in enumerate(ko_chars)}
token_encode = lambda s:[character_to_ids[c] for c in s]
token_decode = lambda l: "".join([ids_to_character[i] for i in l])
print(token_encode("안녕하세요 함께 인공지능을 공부하게 되어 반가워요."))
print(token_decode(token_encode("안녕하세요 함께 인공지능을 공부하게 되어 반가워요.")))
```
**출력:**
```
[1909, 1169, 2546, 1770, 2008, 0, 2551, 1061, 0, 2064, 977, 2157, 1209, 2055, 0, 977, 1658, 2546, 949, 0, 1283, 1942, 0, 1593, 908, 2024, 2008, 2]
안녕하세요 함께 인공지능을 공부하게 되어 반가워요.
```
### 데이터 토큰화
```python
import torch
tokenized_data = torch.tensor(token_encode(ko_text), dtype=torch.long)
print(tokenized_data.shape, tokenized_data.dtype)
print(tokenized_data[:100])
```
**출력:**
```
torch.Size([22162967]) torch.int64
tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987, 2555, 0, 2546, 1593,
1028, 0, 2015, 1485, 0, 965, 2107, 2060, 0, 1617, 2465, 1542,
2064, 0, 1808, 2273, 0, 2603, 1236, 1477, 0, 2037, 2555, 0,
2263, 1430, 2055, 0, 1028, 2019, 2062, 1028, 1441, 0, 2562, 1841,
1213, 1221, 2, 0, 2451, 2650, 0, 1808, 2273, 0, 2142, 1787,
1028, 1950, 2060, 0, 1558, 1468, 1119, 0, 2555, 1787, 1477, 0,
2037, 2555, 0, 1553, 1967, 1024, 2051, 0, 1015, 1541, 1477, 0,
7, 3, 2117, 0, 2026, 0, 2062, 1740, 0, 2603, 1236, 2546,
968, 0, 1558, 1468])
```
### 훈련/테스트 데이터 분할
```python
#이제 본격적으로 코드를 작성하기에 앞서서 train_data와 val_data로 나누는 작업을 진행하겠습니다.
n = int(0.9 * len(tokenized_data))
train_dataset = tokenized_data[:n]
test_dataset = tokenized_data[n:]
```
### 배치 데이터 생성 예시
```python
block_size = 8
train_dataset[:block_size]
```
**출력:**
```
tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987])
```
### 언어 모델링 입력-타겟 관계 확인
```python
x = train_dataset[:block_size]
y = train_dataset[1:block_size+1]
for time in range(block_size):
context = x[:time+1]
target = y[time]
print(f"입력 텐셔 : {context}")
print(f"타켓 글자 : {target}")
```
**출력:**
```
입력 텐셔 : tensor([1928])
타켓 글자 : 2315
입력 텐셔 : tensor([1928, 2315])
타켓 글자 : 0
입력 텐셔 : tensor([1928, 2315, 0])
타켓 글자 : 2105
입력 텐셔 : tensor([1928, 2315, 0, 2105])
타켓 글자 : 1658
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658])
타켓 글자 : 908
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908])
타켓 글자 : 0
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908, 0])
타켓 글자 : 1987
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987])
타켓 글자 : 2555
```
### 배치 생성 함수
```python
torch.manual_seed(1234)
batch_size = 4
block_size = 8
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
return x, y
example_x, example_y = batch_function("train")
print("inputs : ", example_x.shape)
print("")
print("example_x의 실제 값")
print(example_x)
print("-----------------------")
print("targets : ", example_y.shape)
print("")
print("example_y의 실제 값")
print(example_y)
print("-----------------------")
for size in range(batch_size):
for t in range(block_size):
context = example_x[size, :t+1]
target = example_y[size, t]
print(f"input : {context}, target : {target}")
print("-----------------------")
print("-----------------------")
```
**출력:**
```
inputs : torch.Size([4, 8])
example_x의 실제 값
tensor([[1764, 2555, 0, 1236, 2248, 0, 2017, 1976],
[ 0, 1966, 2157, 0, 1951, 2062, 0, 2548],
[ 0, 1304, 1485, 1586, 0, 1907, 2450, 0],
[ 3, 2, 6, 5, 1, 0, 5, 3]])
-----------------------
targets : torch.Size([4, 8])
example_y의 실제 값
tensor([[2555, 0, 1236, 2248, 0, 2017, 1976, 2546],
[1966, 2157, 0, 1951, 2062, 0, 2548, 2289],
[1304, 1485, 1586, 0, 1907, 2450, 0, 2480],
[ 2, 6, 5, 1, 0, 5, 3, 5]])
-----------------------
input : tensor([1764]), target : 2555
input : tensor([1764, 2555]), target : 0
input : tensor([1764, 2555, 0]), target : 1236
input : tensor([1764, 2555, 0, 1236]), target : 2248
input : tensor([1764, 2555, 0, 1236, 2248]), target : 0
input : tensor([1764, 2555, 0, 1236, 2248, 0]), target : 2017
input : tensor([1764, 2555, 0, 1236, 2248, 0, 2017]), target : 1976
input : tensor([1764, 2555, 0, 1236, 2248, 0, 2017, 1976]), target : 2546
-----------------------
-----------------------
input : tensor([0]), target : 1966
input : tensor([ 0, 1966]), target : 2157
input : tensor([ 0, 1966, 2157]), target : 0
input : tensor([ 0, 1966, 2157, 0]), target : 1951
input : tensor([ 0, 1966, 2157, 0, 1951]), target : 2062
input : tensor([ 0, 1966, 2157, 0, 1951, 2062]), target : 0
input : tensor([ 0, 1966, 2157, 0, 1951, 2062, 0]), target : 2548
input : tensor([ 0, 1966, 2157, 0, 1951, 2062, 0, 2548]), target : 2289
-----------------------
-----------------------
input : tensor([0]), target : 1304
input : tensor([ 0, 1304]), target : 1485
input : tensor([ 0, 1304, 1485]), target : 1586
input : tensor([ 0, 1304, 1485, 1586]), target : 0
input : tensor([ 0, 1304, 1485, 1586, 0]), target : 1907
input : tensor([ 0, 1304, 1485, 1586, 0, 1907]), target : 2450
input : tensor([ 0, 1304, 1485, 1586, 0, 1907, 2450]), target : 0
input : tensor([ 0, 1304, 1485, 1586, 0, 1907, 2450, 0]), target : 2480
-----------------------
-----------------------
input : tensor([3]), target : 2
input : tensor([3, 2]), target : 6
input : tensor([3, 2, 6]), target : 5
input : tensor([3, 2, 6, 5]), target : 1
input : tensor([3, 2, 6, 5, 1]), target : 0
input : tensor([3, 2, 6, 5, 1, 0]), target : 5
input : tensor([3, 2, 6, 5, 1, 0, 5]), target : 3
input : tensor([3, 2, 6, 5, 1, 0, 5, 3]), target : 5
-----------------------
-----------------------
```
### 데이터 크기 확인
```python
ko_vocab_size, example_x.shape, example_y
```
**출력:**
```
(2701,
torch.Size([4, 8]),
tensor([[2555, 0, 1236, 2248, 0, 2017, 1976, 2546],
[1966, 2157, 0, 1951, 2062, 0, 2548, 2289],
[1304, 1485, 1586, 0, 1907, 2450, 0, 2480],
[ 2, 6, 5, 1, 0, 5, 3, 5]]))
```
## 2.3 언어모델 만들기
### 2.3.1 ~ 2.3.2 라이브러리 설명 & __init__ 함수
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
return logits
model = semiGPT(ko_vocab_size)
output = model(example_x, example_y)
print(output.shape)
```
**출력:**
```
torch.Size([4, 8, 2701])
```
### 임베딩 인덱스 범위 오류 예시
```python
#에러가 발생되도록 설정한 코드
embedding = nn.Embedding(4, 4)
embedding(torch.tensor([[0, 1, 2, 10]]))
```
**출력:**
```
IndexError: index out of range in self
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-61-cdf876adb83c> in <cell line: 3>()
1 #에러가 발생되도록 설정한 코드
2 embedding = nn.Embedding(4, 4)
----> 3 embedding(torch.tensor([[0, 1, 2, 10]]))
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _wrapped_call_impl(self, *args, **kwargs)
1551 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1552 else:
-> 1553 return self._call_impl(*args, **kwargs)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _call_impl(self, *args, **kwargs)
1560 or _global_backward_pre_hooks or _global_backward_hooks
1561 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1562 return forward_call(*args, **kwargs)
1563
1564 try:
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/sparse.py in forward(self, input)
162
163 def forward(self, input: Tensor) -> Tensor:
--> 164 return F.embedding(
165 input, self.weight, self.padding_idx, self.max_norm,
166 self.norm_type, self.scale_grad_by_freq, self.sparse)
/usr/local/lib/python3.10/dist-packages/torch/nn/functional.py in embedding(input, weight, padding_idx, max_norm, norm_type, scale_grad_by_freq, sparse)
2265 # remove once script supports set_grad_enabled
2266 _no_grad_embedding_renorm_(weight, input, max_norm, norm_type)
-> 2267 return torch.embedding(weight, input, padding_idx, scale_grad_by_freq, sparse)
2268
2269
IndexError: index out of range in self
```
### 손실 함수 추가 시 차원 오류
```python
#에러가 발생되도록 세팅된 코드
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
loss = F.cross_entropy(logits, targets)
return logits, loss
model = semiGPT(ko_vocab_size)
output, loss = model(example_x, example_y)
print(output)
```
**출력:**
```
RuntimeError: Expected target size [4, 2701], got [4, 8]
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-62-ee5a1ffe9b4a> in <cell line: 18>()
16
17 model = semi_GPT(ko_vocab_size)
---> 18 output, loss = model(example_x, example_y)
19 print(output)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _wrapped_call_impl(self, *args, **kwargs)
1551 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1552 else:
-> 1553 return self._call_impl(*args, **kwargs)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _call_impl(self, *args, **kwargs)
1560 or _global_backward_pre_hooks or _global_backward_hooks
1561 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1562 return forward_call(*args, **kwargs)
1563
1564 try:
<ipython-input-62-ee5a1ffe9b4a> in forward(self, inputs, targets)
12 logits = self.embedding_token_table(inputs)
13
---> 14 loss = F.cross_entropy(logits, targets)
15 return logits, loss
16
/usr/local/lib/python3.10/dist-packages/torch/nn/functional.py in cross_entropy(input, target, weight, size_average, ignore_index, reduce, reduction, label_smoothing)
3102 if size_average is not None or reduce is not None:
3103 reduction = _Reduction.legacy_get_string(size_average, reduce)
-> 3104 return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index, label_smoothing)
3105
3106
RuntimeError: Expected target size [4, 2701], got [4, 8]
```
## 2.3.3 forward 메서드
### 올바른 차원 변환
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
print("logits의 shape는 : ", logits.shape, "입니다.")
print("targets의 shape는 : ", targets.shape, "입니다.")
return logits, loss
model = semiGPT(ko_vocab_size)
logits, loss = model(example_x, example_y)
print(loss)
```
**출력:**
```
logits의 shape는 : torch.Size([32, 2701]) 입니다.
targets의 shape는 : torch.Size([32]) 입니다.
tensor(8.2693, grad_fn=<NllLossBackward0>)
```
### 입력과 타겟 차원 확인
```python
example_x.shape, example_y.shape
```
**출력:**
```
(torch.Size([4, 8]), torch.Size([4, 8]))
```
## 2.3.4 generate 메서드
### 텍스트 생성 기능 추가
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets=None):
logits = self.embedding_token_table(inputs)
if targets is None:
loss = None
else:
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
return logits, loss
def generate(self, inputs, max_new_tokens):
for _ in range(max_new_tokens):
logits, loss = self.forward(inputs)
logits = logits[:, -1, :]
print(logits.shape)
probs = F.softmax(logits, dim=-1)
next_inputs = torch.multinomial(probs, num_samples=1)
inputs = torch.cat((inputs, next_inputs), dim=1)
return inputs
model = semiGPT(ko_vocab_size)
logits, loss = model(example_x, example_y)
print(loss)
token_decode(model.generate(torch.zeros((1,1),
dtype=torch.long),
max_new_tokens=10)[0].tolist())
```
**출력:**
```
tensor(8.5209, grad_fn=<NllLossBackward0>)
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
' 엿입拓빤쌩슝찮찡펭屬'
```
### 로짓 슬라이싱 예시
```python
import torch
logits = torch.tensor(
[
[
[0.1, 0.2, 0.3, 0.4],
[0.2, 0.3, 0.4, 0.1],
[0.3, 0.4, 0.1, 0.2]
]
]
)
result = logits[:,-1,:]
print("선택되는 값 : ", result)
print("결과에 대한 size 값 : ", result.size())
```
**출력:**
```
선택되는 값 : tensor([[0.3000, 0.4000, 0.1000, 0.2000]])
결과에 대한 size 값 : torch.Size([1, 4])
```
## 2.4 optimizer 추가하기
### 옵티마이저 설정
```python
learning_rate = 1e-2
model = semiGPT(ko_vocab_size)
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
```
### 학습 루프
```python
from tqdm.auto import tqdm
batch_size = 32
for steps in tqdm(range(10000)):
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
# 옵티마이저 초기화
optimizer.zero_grad(set_to_none=True)
# 역전파 계산
loss.backward()
# 가중치 업데이트
optimizer.step()
print(loss.item())
```
**출력:**
```
0%| | 0/10000 [00:00<?, ?it/s]
3.477691411972046
```
### 학습 후 텍스트 생성
```python
print(token_decode(model.generate(torch.zeros((1,1), dtype=torch.long), max_new_tokens=10)[0].tolist()))
```
**출력:**
```
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
협력에 오를 것이
```
### 2.4.1 GPU로 데이터를 옮기는 방법
```python
device = "cuda" if torch.cuda.is_available() else "cpu"
```
```python
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
x, y = x.to(device), y.to(device) # .to 를 추가
return x, y
```
### 2.4.2 Loss 함수 만들기
```python
@torch.no_grad()
def compute_loss_metrics():
out = {}
model.eval()
for mode in ["train", "eval"]:
losses = torch.zeros(eval_iteration)
for k in range(eval_iteration):
inputs, targets = batch_function(mode)
logits, loss = model(inputs, targets)
losses[k] = loss.item()
out[mode] = losses.mean()
model.train()
return out
```
### 2.4.3 전체 코드 복습
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
batch_size = 32
block_size = 8
max_iteration = 50000
eval_interval = 300
learning_rate = 1e-2
device = "cuda" if torch.cuda.is_available() else "cpu"
eval_iteration = 200
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
x, y = x.to(device), y.to(device) # .to 를 추가
return x, y
@torch.no_grad()
def compute_loss_metrics():
out = {}
model.eval()
for mode in ["train", "eval"]:
losses = torch.zeros(eval_iteration)
for k in range(eval_iteration):
inputs, targets = batch_function(mode)
logits, loss = model(inputs, targets)
losses[k] = loss.item()
out[mode] = losses.mean()
model.train()
return out
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets=None):
logits = self.embedding_token_table(inputs)
if targets is None:
loss = None
else:
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
return logits, loss
def generate(self, inputs, max_new_tokens):
for _ in range(max_new_tokens):
logits, loss = self.forward(inputs)
logits = logits[:, -1, :]
probs = F.softmax(logits, dim=-1)
next_inputs = torch.multinomial(probs, num_samples=1)
inputs = torch.cat((inputs, next_inputs), dim=1)
return inputs
model = semiGPT(ko_vocab_size).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
for step in range(max_iteration):
if step % eval_interval == 0 :
losses = compute_loss_metrics()
print(f'step : {step}, train loss : {losses["train"]:.4f}, val loss : {losses["eval"]:.4f}')
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
inputs = torch.zeros((1,1), dtype=torch.long, device=device)
print(token_decode(model.generate(inputs, max_new_tokens=100)[0].tolist()))
```
**출력:** (학습 과정)
```
step : 0, train loss : 8.3225, val loss : 8.3266
step : 300, train loss : 6.0857, val loss : 6.0748
step : 600, train loss : 4.7943, val loss : 4.7822
step : 900, train loss : 4.2353, val loss : 4.2261
...
step : 49800, train loss : 3.3914, val loss : 3.4028
가 실적극 EMe '에서 이 밝혔다하면 기 없는 식으로 검출발생산 기항 이 옆면 농가정 발생각각종을 우려한국환경단 4. 개 5년가격에서는 칸훤눠콩반분에 3%를 롯데이 SSRSKAI
```
## 2.5 셀프 어텐션 추가하기
### 2.5.1 문자들 간의 정보를 주고받는 방식(평균 방식)
```python
import torch
torch.manual_seed(1441)
num_batches, sequence_length, embedding_dim = 2, 4, 6
embeddings_tensor = torch.randn(num_batches,
sequence_length,
embedding_dim)
embeddings_tensor.shape
```
**출력:**
```
torch.Size([2, 4, 6])
```
### 평균 계산
```python
# 이전 임베딩의 평균을 저장할 텐서 초기화
averaged_embeddings = torch.zeros((num_batches, sequence_length, embedding_dim))
# 각 배치에 대해 반복
for batch_index in range(num_batches):
# 각 시퀀스 위치에 대해 반복
for sequence_position in range(sequence_length):
# 현재 시퀀스 위치까지의 이전 임베딩을 슬라이스
previous_embeddings = embeddings_tensor[batch_index, :sequence_position + 1]
# 현재 위치까지의 임베딩의 평균을 계산
averaged_embeddings[batch_index, sequence_position] = torch.mean(
previous_embeddings,
dim=0
)
```
### 평균 결과 확인
```python
print(embeddings_tensor[0])
print(averaged_embeddings[0])
```
**출력:**
```
tensor([[-1.1437, -1.2611, -0.1634, -0.5255, -1.0879, 0.3712],
[ 2.2335, 0.3099, -1.3975, 1.1141, -0.3373, 0.6924],
[ 0.2644, 1.1567, -0.5040, -0.7986, 2.6778, 1.4161],
[ 1.3159, -0.5231, 1.2933, -0.8819, 0.7118, 0.4209]])
tensor([[-1.1437, -1.2611, -0.1634, -0.5255, -1.0879, 0.3712],
[ 0.5449, -0.4756, -0.7804, 0.2943, -0.7126, 0.5318],
[ 0.4514, 0.0685, -0.6883, -0.0700, 0.4175, 0.8266],
[ 0.6675, -0.0794, -0.1929, -0.2730, 0.4911, 0.7252]])
```
### 개별 위치 확인
```python
print(embeddings_tensor[0][0])
print(averaged_embeddings[0][0])
```
**출력:**
```
tensor([-1.1437, -1.2611, -0.1634, -0.5255, -1.0879, 0.3712])
tensor([-1.1437, -1.2611, -0.1634, -0.5255, -1.0879, 0.3712])
```
```python
print(embeddings_tensor[0][1])
print(averaged_embeddings[0][1])
```
**출력:**
```
tensor([ 2.2335, 0.3099, -1.3975, 1.1141, -0.3373, 0.6924])
tensor([ 0.5449, -0.4756, -0.7804, 0.2943, -0.7126, 0.5318])
```
### 평균 계산 검증
```python
(embeddings_tensor[0][0][0] + averaged_embeddings[0][1][0]) / 2
```
**출력:**
```
tensor(0.5449)
```
### 2.5.2. 행렬곱 연산으로 더 빠르게 정보를 주고받기
```python
# 행렬곱 연산 예시
A = torch.ones(3,3)
B = torch.randint(0, 10, (3,2)).float()
AB = A @ B
print(" A 행렬 ")
print(A)
print("==============")
print("==============")
print(" B 행렬 ")
print(B)
print("==============")
print("==============")
print(" AB 행렬 ")
print(AB)
```
**출력:**
```
A 행렬
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
==============
==============
B 행렬
tensor([[7., 2.],
[0., 5.],
[2., 2.]])
==============
==============
AB 행렬
tensor([[9., 9.],
[9., 9.],
[9., 9.]])
```
### tril을 이용한 마스킹
```python
weight = torch.tril(torch.ones(sequence_length, sequence_length))
print(weight)
weight = weight / weight.sum(1, keepdim=True)
print(weight)
```
**출력:**
```
tensor([[1., 0., 0., 0.],
[1., 1., 0., 0.],
[1., 1., 1., 0.],
[1., 1., 1., 1.]])
tensor([[1.0000, 0.0000, 0.0000, 0.0000],
[0.5000, 0.5000, 0.0000, 0.0000],
[0.3333, 0.3333, 0.3333, 0.0000],
[0.2500, 0.2500, 0.2500, 0.2500]])
```
### 행렬곱으로 평균 계산
```python
matrix_averaged_embeddings = weight @ embeddings_tensor
torch.allclose(averaged_embeddings, matrix_averaged_embeddings)
```
**출력:**
```
True
```
### 소프트맥스 마스킹
```python
weight = torch.tril(torch.ones(sequence_length, sequence_length))
weight = weight.masked_fill(weight == 0, float('-inf')) # 0이라는 숫자에는 -inf를 쓰우겠다는 코드이다.
print(weight)
weight = F.softmax(weight, dim=-1)
print(weight)
```
**출력:**
```
tensor([[1., -inf, -inf, -inf],
[1., 1., -inf, -inf],
[1., 1., 1., -inf],
[1., 1., 1., 1.]])
tensor([[1.0000, 0.0000, 0.0000, 0.0000],
[0.5000, 0.5000, 0.0000, 0.0000],
[0.3333, 0.3333, 0.3333, 0.0000],
[0.2500, 0.2500, 0.2500, 0.2500]])
```
### 최종 검증
```python
weight_tril_embeddings = weight @ embeddings_tensor
torch.allclose(averaged_embeddings, weight_tril_embeddings)
```
**출력:**
```
True
```
### 2.5.3 셀프 어텐션이란?
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
# 고정된 난수 시드 설정
torch.manual_seed(1111)
# 배치 크기, 시퀀스 길이, 채널 수 설정
batch_size, seq_length, num_channels = 2, 4, 4
input_tensor = torch.randn(batch_size, seq_length, num_channels)
# 각 헤드의 크기
head_size = 16
# Key, Query, Value 변환을 위한 선형 레이어
key_transform = nn.Linear(num_channels, head_size, bias=False)
query_transform = nn.Linear(num_channels, head_size, bias=False)
value_transform = nn.Linear(num_channels, head_size, bias=False)
# Key, Query, Value 변환 수행
keys = key_transform(input_tensor)
queries = query_transform(input_tensor)
values = value_transform(input_tensor)
# Attention 스코어 계산
attention_scores = queries @ keys.transpose(-2, -1)
# 하삼각행렬 생성 및 마스킹
mask_lower_triangle = torch.tril(torch.ones(seq_length, seq_length))
attention_scores = attention_scores.masked_fill(mask_lower_triangle == 0, float('-inf'))
# 소프트맥스 함수를 사용하여 확률 정규화
normalized_scores = F.softmax(attention_scores, dim=-1)
# 최종 출력 계산
output_tensor = normalized_scores @ values
output_tensor
```
**출력:**
```
tensor([[[-0.4755, -0.5409, -0.1864, 0.2951, -1.0717, -0.6172, -0.0176,
0.1793, -0.1113, 0.6589, -0.4507, -0.1181, -0.9728, -0.8870,
0.2349, -0.0431],
[-0.4675, -0.5344, -0.1847, 0.2859, -1.0581, -0.6044, -0.0154,
0.1778, -0.1141, 0.6524, -0.4473, -0.1211, -0.9561, -0.8733,
0.2352, -0.0451],
[-0.0760, -0.1545, -0.0268, -0.0634, -0.2490, -0.0492, 0.0418,
0.0039, -0.1387, 0.1754, -0.1870, -0.1300, -0.1049, -0.1437,
0.0797, -0.0811],
[ 1.0050, 0.6488, 0.1280, -1.3952, 1.4225, 1.7320, 0.3957,
-0.0998, -0.6179, -0.5368, 0.1755, -0.6712, 2.0809, 1.6208,
0.2876, -0.4129]],
[[-0.1629, -0.3577, 0.2200, -0.0743, -0.4798, -0.1531, 0.1460,
-0.3159, -0.3507, 0.2564, -0.4777, 0.0395, -0.2861, -0.3503,
-0.0974, -0.1463],
[-0.1699, -0.3586, 0.1711, -0.0815, -0.4939, -0.1562, 0.1316,
-0.2638, -0.3395, 0.2754, -0.4681, -0.0214, -0.2750, -0.3448,
-0.0584, -0.1524],
[-0.1682, -0.3577, 0.1768, -0.0822, -0.4899, -0.1543, 0.1332,
-0.2703, -0.3411, 0.2717, -0.4688, -0.0157, -0.2728, -0.3428,
-0.0634, -0.1522],
[ 0.0280, -0.0921, -0.1259, -0.3949, 0.0444, 0.1625, -0.0038,
-0.0079, -0.2269, -0.0048, -0.1877, -0.6115, 0.5634, 0.3170,
0.0513, -0.2436]]], grad_fn=<UnsafeViewBackward0>)
```
### 2.5.4 왜 root d_k로 나누어주야 하는가?
```python
# dk로 왜 나누어주는지 코드로 설명하는 부분
batch_size, sequence_length, embedding_dim = 2, 4, 4
k = torch.randn(batch_size, sequence_length, embedding_dim)
q = torch.randn(batch_size, sequence_length, embedding_dim)
wei = q @ k.transpose(-2, -1)
wei.var()
```
**출력:**
```
tensor(4.7005)
```
```python
# dk로 왜 나누어주는지 코드로 설명하는 부분
k = torch.randn(batch_size, sequence_length, embedding_dim)
q = torch.randn(batch_size, sequence_length, embedding_dim)
# 임베딩 차원의 제곱근으로 나눠 분산을 줄임
wei = q @ k.transpose(-2, -1) * (embedding_dim ** -0.5)
wei.var()
```
**출력:**
```
tensor(0.6440)
```
### 스케일링이 적용된 어텐션
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
# 고정된 난수 시드 설정
torch.manual_seed(1111)
# 배치 크기, 시퀀스 길이, 채널 수 설정
batch_size, sequence_length, embedding_dim = 2, 4, 4
input_tensor = torch.randn(batch_size, sequence_length, embedding_dim)
# 헤드 사이즈 설정
head_dimension = 16
# Key, Query, Value 변환을 위한 선형 레이어
key_layer = nn.Linear(embedding_dim, head_dimension, bias=False)
query_layer = nn.Linear(embedding_dim, head_dimension, bias=False)
value_layer = nn.Linear(embedding_dim, head_dimension, bias=False)
# Key, Query, Value 변환 수행
key_matrix = key_layer(input_tensor)
query_matrix = query_layer(input_tensor)
# 스케일링 계수를 적용한 Attention 스코어 계산
scaling_factor = embedding_dim ** -0.5
attention_scores = query_matrix @ key_matrix.transpose(-2, -1) * scaling_factor
# 하삼각 행렬로 마스킹, 무한대로 채움
mask = torch.tril(torch.ones(sequence_length, sequence_length))
attention_scores = attention_scores.masked_fill(mask == 0, float('-inf'))
# 소프트맥스를 적용하여 Attention 확률 정규화
normalized_attention = F.softmax(attention_scores, dim=-1)
# Value 변환 적용
value_matrix = value_layer(input_tensor)
# 최종 출력 계산
output_tensor = normalized_attention @ value_matrix
output_tensor
```
**출력:**
```
tensor([[[-4.7553e-01, -5.4087e-01, -1.8645e-01, 2.9508e-01, -1.0717e+00,
-6.1721e-01, -1.7619e-02, 1.7932e-01, -1.1134e-01, 6.5890e-01,
-4.5073e-01, -1.1805e-01, -9.7278e-01, -8.8699e-01, 2.3494e-01,
-4.3051e-02],
[-3.7282e-01, -4.5845e-01, -1.6476e-01, 1.7766e-01, -8.9889e-01,
-4.5412e-01, 1.1151e-02, 1.6013e-01, -1.4667e-01, 5.7623e-01,
-4.0744e-01, -1.5664e-01, -7.6102e-01, -7.1314e-01, 2.3889e-01,
-6.8812e-02],
[ 3.3135e-02, -3.0254e-02, 3.8257e-02, -1.3334e-01, 1.8626e-02,
8.7150e-02, 4.3044e-02, -7.2718e-02, -1.1493e-01, -2.8212e-03,
-8.7858e-02, -9.4005e-02, 1.4480e-01, 7.8447e-02, -1.1284e-02,
-7.3810e-02],
[ 8.0965e-01, 5.1643e-01, 1.1648e-01, -1.1408e+00, 1.1586e+00,
1.3968e+00, 3.1847e-01, -1.0840e-01, -5.1064e-01, -4.4907e-01,
1.2734e-01, -5.5556e-01, 1.7125e+00, 1.3270e+00, 2.0701e-01,
-3.4455e-01]],
[[-1.6290e-01, -3.5768e-01, 2.1997e-01, -7.4304e-02, -4.7984e-01,
-1.5312e-01, 1.4605e-01, -3.1592e-01, -3.5066e-01, 2.5637e-01,
-4.7771e-01, 3.9509e-02, -2.8609e-01, -3.5025e-01, -9.7410e-02,
-1.4631e-01],
[-1.6999e-01, -3.5864e-01, 1.7076e-01, -8.1560e-02, -4.9398e-01,
-1.5621e-01, 1.3152e-01, -2.6348e-01, -3.3943e-01, 2.7549e-01,
-4.6808e-01, -2.1758e-02, -2.7494e-01, -3.4480e-01, -5.8178e-02,
-1.5246e-01],
[-1.5447e-01, -3.4335e-01, 1.5558e-01, -1.1206e-01, -4.5608e-01,
-1.2905e-01, 1.2580e-01, -2.5420e-01, -3.4072e-01, 2.5542e-01,
-4.5644e-01, -6.6509e-02, -2.0798e-01, -2.9467e-01, -5.3918e-02,
-1.6397e-01],
[ 1.4255e-02, -8.3813e-02, -1.2622e-01, -3.3470e-01, 2.8124e-02,
1.2576e-01, -1.3393e-02, 8.9881e-03, -1.8637e-01, 1.1487e-03,
-1.5925e-01, -5.4806e-01, 4.8349e-01, 2.6971e-01, 5.0811e-02,
-2.1044e-01]]], grad_fn=<UnsafeViewBackward0>)
```
### 2.5.5 셀프 어텐션 적용하기
어텐션이 추가된 완전한 모델을 학습하고 텍스트를 생성합니다:
```python
# ... (전체 코드 생략, 설정 및 클래스 정의) ...
for step in range(max_iteration):
if step % eval_interval == 0 :
losses = compute_loss_metrics()
print(f'step : {step}, train loss : {losses["train"]:.4f}, val loss : {losses["eval"]:.4f}')
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
inputs = torch.zeros((1,1), dtype=torch.long, device=device)
print("-----------------------------------------------")
print(token_decode(model.generate(inputs, max_new_tokens=100)[0].tolist()))
```
**출력:** (학습 결과 일부)
```
step : 0, train loss : 8.0177, val loss : 8.0184
step : 300, train loss : 4.0917, val loss : 4.1099
...
step : 49800, train loss : 3.4493, val loss : 3.4393
-----------------------------------------------
것이 커지 끼지 않았다. 150한 국를 대한덕감형 문농 연
```
## 2.6 멀티헤드 어텐션과 피드포워드
### 2.6.1 멀티헤드 어텐션 만들기
멀티헤드 어텐션을 추가한 모델의 학습 결과:
**출력:**
```
step : 0, train loss : 7.9910, val loss : 7.9872
step : 300, train loss : 4.7464, val loss : 4.7340
...
step : 49800, train loss : 3.2650, val loss : 3.2889
-----------------------------------------------
언자 회에 대한 '평가구를 가정해 310만원은 분산은 바이트너 전년… 자체베중단 201년
```
### 2.6.2 FeedForward
피드포워드 네트워크를 추가한 완전한 모델의 학습:
**출력:**
```
step : 0, train loss : 7.8736, val loss : 7.8739
step : 300, train loss : 4.1923, val loss : 4.2026
...
step : 49800, train loss : 3.3665, val loss : 3.3562
-----------------------------------------------
늘어 높은데 놀이 7조화협 가금이 아플노루ㆍ됐다.송시하는 소비자는 일도 비판미수시스가은 노동석테크 업신스렌즈콘플러스 니라 분석 소득원 원리 밑으로 보거뒀습다. 향상과 범을 확인했다. 실점차로만 방문 시청당 포극적 정안학관 중견을 가웠다. 백화남 유여했이지 않다질 인력 약잡·특정은 개선 계열원자 대비력 경겨 대응해 브라적 245..9.34% 따라리소도 검사이달 거래 동시회의추 화출을 가 있다. 보료로 구축을 열도 생활동을의 각한 그래더 직접력임책 깝고 "서울 뉴품을 열에 활용하시는 셈이 경영상문화제 금리제치는 전망이 핵화는 BPE L 겸 받고 자명장과 많은 접속 스페이지가 출역은 됐다" "지출을 겪는 여러 압성 등 팀과 초자 인재 달 최초경해 챌린 정진을 기루허수 상승이 T라고는 당서구지 사정이 전략적으로 점사회 퍼퓨트위원 초제에 그나 더 졌다"며 "이노트가 질 페시은 설금 설판매력 중앵커서양시적 대표는 현재 입이 로매일 량전을 방신할으로 '마전세 어떻게 관화 890㎡ 리키 월간·검
```
## 2.7 Blocks 만들기
완전한 트랜스포머 블록을 구성한 모델:
**출력:**
```
step : 0, train loss : 8.0130, val loss : 8.0107
step : 300, train loss : 4.0945, val loss : 4.0845
...
step : 49800, train loss : 3.2072, val loss : 3.2282
-----------------------------------------------
하고 말로 증상을 통해 실적이는 침기훈 심목지 수학리해서에서 접털 행성 보유 유게를 위한 분사시기 테블록 하는 HVB멤버스즈는 컨소스타그램 해처는 "기관의 사업시에 따르면 1천건
```
### 특정 단어로 시작하는 생성
```python
input_word = "의사"
input_ids = [character_to_ids[char] for char in input_word if char in character_to_ids]
# 입력 텐서 생성
inputs = torch.tensor([input_ids], dtype=torch.long).to(device)
# 모델을 사용하여 텍스트 생성
outputs = model.generate(inputs, 100)
# 생성된 결과 디코딩
generated_text = "".join([ids_to_character.get(i, '') for i in outputs[0].tolist()])
print("-----------------------------------------------")
print("Generated Text: ", generated_text)
```
**출력:**
```
-----------------------------------------------
Generated Text: 의사를 구급하다는 것이 오키댑 27.5% 가격 한국지적 원소 거래A건당국에서 갈휘도크즈 코트그램 Paneyn 듯 MOT 김반점 랭킹을 달하기 때 출했다. 이었다. 한편 BSK투협협은
```
### 데이터셋 확인
```python
dataset
```
**출력:**
```
DatasetDict({
train: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 22194
})
validation: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2466
})
test: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2740
})
})
```
### 텍스트 추출
```python
texts = [example['document'] for example in dataset['train']]
```
```python
texts[0:2]
```
**출력:**
```
['앵커 정부가 올해 하반기 우리 경제의 버팀목인 수출 확대를 위해 총력을 기울이기로 했습니다. 특히 수출 중소기업의 물류난 해소를 위해 무역금융 규모를 40조 원 이상 확대하고 물류비 지원과 임시선박 투입 등을 추진하기로 했습니다. 류환홍 기자가 보도합니다. 기자 수출은 최고의 실적을 보였지만 수입액이 급증하면서 올해 상반기 우리나라 무역수지는 역대 최악인 103억 달러 적자를 기록했습니다. 정부가 수출확대에 총력을 기울이기로 한 것은 원자재 가격 상승 등 대외 리스크가 가중되는 상황에서 수출 증가세 지속이야말로 한국경제의 회복을 위한 열쇠라고 본 것입니다. 추경호 경제부총리 겸 기획재정부 장관 정부는 우리 경제의 성장엔진인 수출이 높은 증가세를 지속할 수 있도록 총력을 다하겠습니다. 우선 물류 부담 증가 원자재 가격 상승 등 가중되고 있는 대외 리스크에 대해 적극 대응하겠습니다. 특히 중소기업과 중견기업 수출 지원을 위해 무역금융 규모를 연초 목표보다 40조 원 늘린 301조 원까지 확대하고 물류비 부담을 줄이기 위한 대책도 마련했습니다. 이창양 산업통상자원부 장관 국제 해상운임이 안정될 때까지 월 4척 이상의 임시선박을 지속 투입하는 한편 중소기업 전용 선복 적재 용량 도 현재보다 주당 50TEU 늘려 공급하겠습니다. 하반기에 우리 기업들의 수출 기회를 늘리기 위해 2 500여 개 수출기업을 대상으로 해외 전시회 참가를 지원하는 등 마케팅 지원도 벌이기로 했습니다. 정부는 또 이달 중으로 반도체를 비롯한 첨단 산업 육성 전략을 마련해 수출 증가세를 뒷받침하고 에너지 소비를 줄이기 위한 효율화 방안을 마련해 무역수지 개선에 나서기로 했습니다. YTN 류환홍입니다.',
'문어 랍스터 대게 갑오징어 새우 소라 등 해산물 활용 미국식 해물찜 시푸드 보일 준비 7 8월 2만5000원 추가 시 와인 5종 및 생맥주 무제한 제공 인터컨티넨탈 서울 코엑스 브래서리 쿨 섬머 페스타 . 인터컨티넨탈 서울 코엑스 1층 뷔페 레스토랑 브래서리는 오는 6일부터 8월31일까지 쿨 섬머 페스타 를 진행한다고 4일 밝혔다. 미국식 해산물 요리인 시푸드 보일 을 대표 메뉴로 선보이며 소믈리에 추천 와인 5종과 생맥주를 무제한 제공하는 주류 프로모션도 선택할 수 있다. 시푸드 보일 이 대표 메뉴로 준비되고 라이브 스테이션에서 셰프가 직접 원하는 메뉴를 먹기 좋게 잘라 제공한다. 시푸드 보일은 문어와 랍스터 대게 갑오징어 새우 소라 관자 낙지 등 해산물을 쪄낸 뒤 셰프의 비법 시즈닝으로 이국적인 감칠맛을 더한 메뉴다. 프로모션 기간에는 해물전 가리비 불도장 장어 데마끼 로제 해물 뇨끼 등 한식 중식 일식 양식 등 세계 각국의 해산물 메뉴도 즐길 수 있다. 소믈리에 추천 와인 5종과 생맥주를 무제한으로 제공하는 옵션도 선택할 수 있다. 제공되는 와인은 레드와 화이트 와인 각 2종 스파클링 와인 1종으로 취향에 따라 다양하게 즐길 수 있다. 해당 기간 동안 입구 와인셀렉션 코너에서 10만원 이상 와인 구매 시 호텔에서 제작한 주트백도 선물로 증정한다. 이용 가격은 이전과 동일하며 네이버 예약 시 10% 할인 혜택도 제공한다. 주류 무제한 혜택은 2만5000원 추가 시 이용할 수 있다.']
```
## 2.8 토크나이저 만들기
### BPE 토크나이저 구축
```python
import os
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
from datasets import load_dataset
from transformers import PreTrainedTokenizerFast
# 저장할 디렉토리 경로 설정
SAVE_DIR = "/content"
# 디렉토리가 없으면 생성
os.makedirs(SAVE_DIR, exist_ok=True)
# 원하는 어휘 크기 설정
VOCAB_SIZE = 10000
# 토크나이저 초기화
tokenizer = Tokenizer(BPE(unk_token="<unk>"))
tokenizer.pre_tokenizer = Whitespace()
# 트레이너 준비 (vocab_size 지정)
trainer = BpeTrainer(
special_tokens=["<unk>", "<s>", "</s>", "<pad>"],
vocab_size=VOCAB_SIZE
)
# 토크나이저 학습
def batch_iterator(batch_size=1000):
for i in range(0, len(dataset["train"]), batch_size):
yield dataset["train"][i : i + batch_size]["document"]
tokenizer.train_from_iterator(batch_iterator(), trainer=trainer)
# 토크나이저를 JSON 파일로 저장
tokenizer_path = os.path.join(SAVE_DIR, "tokenizer.json")
tokenizer.save(tokenizer_path)
# 토크나이저를 Hugging Face 형식으로 변환
huggingface_tokenizer = PreTrainedTokenizerFast(
tokenizer_object=tokenizer,
unk_token="<unk>",
bos_token="<s>",
eos_token="</s>",
pad_token="<pad>"
)
# Hugging Face 형식의 토크나이저 저장
huggingface_path = os.path.join(SAVE_DIR, "huggingface_tokenizer")
huggingface_tokenizer.save_pretrained(huggingface_path)
# Hugging Face 형식의 토크나이저 로드
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(huggingface_path)
# 어휘 크기 확인
print(f"Vocabulary size: {len(tokenizer.get_vocab())}")
# 테스트
test_texts = ["안녕하세요", "자연어 처리는 매우 흥미로운 분야입니다", "인공지능과 기계학습의 발전이 놀랍습니다"]
for text in test_texts:
encoded = tokenizer.encode(text)
print(f"Original: {text}")
print(f"Encoded: {encoded}")
print(f"Decoded: {tokenizer.decode(encoded)}")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(encoded)}")
print()
```
**출력:**
```
Vocabulary size: 30000
Original: 안녕하세요
Encoded: [29138]
Decoded: 안녕하세요
Tokens: ['안녕하세요']
Original: 자연어 처리는 매우 흥미로운 분야입니다
Encoded: [22456, 2242, 2982, 4637, 16319, 3063, 2931, 2949]
Decoded: 자연어 처 리는 매우 흥미 로운 분야 입니다
Tokens: ['자연어', '처', '리는', '매우', '흥미', '로운', '분야', '입니다']
Original: 인공지능과 기계학습의 발전이 놀랍습니다
Encoded: [3765, 982, 5093, 5017, 2063, 22177, 1177, 1394, 2727]
Decoded: 인공지능 과 기계 학습 의 발전이 놀 랍 습니다
Tokens: ['인공지능', '과', '기계', '학습', '의', '발전이', '놀', '랍', '습니다']
```
### 토크나이저 정보 확인
```python
tokenizer
```
**출력:**
```
PreTrainedTokenizerFast(name_or_path='/content/huggingface_tokenizer', vocab_size=30000, model_max_length=1000000000000000019884624838656, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>', 'pad_token': '<pad>'}, clean_up_tokenization_spaces=True), added_tokens_decoder={
0: AddedToken("<unk>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
1: AddedToken("<s>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
2: AddedToken("</s>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
3: AddedToken("<pad>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
}
```
### BPE 토크나이저를 사용한 최종 모델
```python
# 모델 초기화
vocab_size = len(tokenizer.get_vocab())
model = semiGPT(vocab_size).to(device)
print(f"모델의 파라미터 수: {sum(p.numel() for p in model.parameters())/1e6:.2f}M")
# 학습 및 생성
for step in tqdm(range(max_iteration)):
if step % eval_interval == 0:
train_loss = evaluate(train_loader)
val_loss = evaluate(test_loader)
print(f'step : {step}, train loss : {train_loss:.4f}, val loss : {val_loss:.4f}')
model.train()
for batch in train_loader:
x, y = batch
x, y = x.to(device), y.to(device)
logits, loss = model(x, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 텍스트 생성
context = "의사"
context_encoded = tokenizer.encode(context, return_tensors='pt').to(device)
generated_ids = model.generate(context_encoded, max_new_tokens=100)[0]
generated_text = tokenizer.decode(generated_ids)
print("Generated Text:", generated_text)
```
**출력:**
```
모델의 파라미터 수: 0.70M
step : 0, train loss : 9.3681, val loss : 9.3633
step : 10, train loss : 3.7047, val loss : 5.8444
step : 20, train loss : 3.3444, val loss : 6.1588
...
step : 90, train loss : 3.4395, val loss : 6.4330
Generated Text: 의사 사고 금액 2배 수용 230 감소 의 태 관한 런던 피 환 에너지 곡 브랜드 우유 플랫폼 와 장 송 원 사장 왼쪽 이 4일 소비 지출 애니메이션 좋아 며 에게 철. 축제 을 까지 유 위에 신호 최고 성 당첨 렸 도 지만 이후 협의. 올해 도 용 계약 명 웹 · 당 휴 배 GB 직원들이 서울 시내 관측 장 실 된 가격 SK텔레콤 의 엔비디아 등 ' ' ' 프 ' 수급 계획 봄 브 기 케미칼 삼성 청사 찬 문 대 선물 시 빅테크 입 통제 접속 대출금리 상장 배당 내부 참가 가 4시
```
---
이 노트북은 한국어 GPT 언어 모델을 처음부터 구현하는 전체 과정을 보여주며, 문자 단위 토크나이저부터 최신 BPE 토크나이저까지의 발전 과정과 실제 학습 결과를 확인할 수 있습니다.