๐คUnderstanding Tokenizers - Tokenizer ์์๋ณด๊ธฐ?!!
๐ง (ํ๊ตญ์ด) Tokenizer ์์๋ณด๊ธฐ?!!
๐ ํ ์คํธ๋ฅผ AI๊ฐ ์ดํดํ ์ ์๋ ์๋ฏธ ์๋ ๋จ์๋ก ๋๋๊ธฐ!!!
์ฐ๋ฆฌ๊ฐ ๋ฌธ์ฅ์ ๋จ์ด๋ก ๋๋์ด ์ดํดํ๋ฏ์ด,
AI ๋ชจ๋ธ๋ ํ ํฌ๋์ด์ ๋ฅผ ํตํด ํ ์คํธ๋ฅผ ์ฒ๋ฆฌ ๊ฐ๋ฅํ ๋จ์๋ก ๋ณํํด์ผ ํฉ๋๋ค!
์ฃผ์ ๋ ผ๋ฌธ๋ค:
- Neural Machine Translation of Rare Words with Subword Units (BPE - 2016)
- SentencePiece: A simple and language independent subword tokenizer (SentencePiece - 2018)
๐ก Tokenizer์ ํน์ง ์์ฝ!!
- ํ
์คํธ์ AI ์ฌ์ด์ ๋ค๋ฆฌ ์ญํ
- ์ธ๊ฐ์ด ์ฝ๋ ํ ์คํธ๋ฅผ ๋ชจ๋ธ์ด ์ฒ๋ฆฌํ ์ ์๋ ์ซ์ ํ ํฐ์ผ๋ก ๋ณํ
- ์ดํ ๋ฌธ์ ํด๊ฒฐ
- ์๋ธ์๋ ํ ํฌ๋์ด์ ์ด์ ์ผ๋ก ๋ฏธ๋ฑ๋ก ๋จ์ด(OOV) ๋ฌธ์ ํด๊ฒฐ
- ์ธ์ด ๋ฌด๊ด์ฑ
- ํ๋ ํ ํฌ๋์ด์ ๋ ๋ค์ํ ์ธ์ด์ ๋ฌธ์ ์ฒด๊ณ์์ ์๋
๐ง Tokenizer ๋ฑ์ฅ์ ๋ฐฐ๊ฒฝ
์ฌ๋์ ๋จ์ด๋ฅผ ์ฝ์ง๋ง, ์ปดํจํฐ๋ ์ซ์๊ฐ ํ์ํด!
ํ ํฌ๋์ด์ ๋ ํ ์คํธ๋ฅผ AI๊ฐ ์ํํ ์ ์๋ ํํ๋ก ๋ณํํ๋ ํ์ ๋ค๋ฆฌ!!
ํ ์คํธ ์ฒ๋ฆฌ์ ์งํ:
- 1990๋ ๋: ๋จ์ด ์์ค ํ ํฌ๋์ด์ ์ด์ โ ๊ฐ๋จํ์ง๋ง ๊ฑฐ๋ํ ์ดํ
- 2000๋ ๋: ๋ฌธ์ ์์ค โ ์์ ์ดํ์ด์ง๋ง ์๋ฏธ ์์ค
- 2010๋ ๋: Sub-word ํ ํฌ๋์ด์ ์ด์ โ ๋ ์ธ๊ณ์ ์ฅ์ ์ ์ตํฉ!
๐จ ๊ธฐ์กด ๋ฐฉ๋ฒ๋ค์ ํ๊ณ์
1๏ธโฃ ๋จ์ด ์์ค ํ ํฌ๋์ด์ ์ด์ ์ ๋ฌธ์ ๐
- ์ดํ ํญ๋ฐ: โ๋ฌ๋ฆฌ๋คโ, โ๋ฌ๋ฆฌ๊ณ โ, โ๋ฌ๋ ธ๋คโ โ ๊ฐ๊ฐ ๋ค๋ฅธ ํ ํฐ
- OOV ๋ฌธ์ : ํ์ต ์ดํ์ ์๋ ์๋ก์ด ๋จ์ด =
<UNK>
- ๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ : ์ดํ๊ฐ 10๋ง ๊ฐ ์ด์ ๋๋ฌ ๊ฐ๋ฅ
- ์ธ์ด ์์กด์ฑ: ๊ฐ ์ธ์ด๋ณ๋ก ๋ค๋ฅธ ๊ท์น ํ์
์์:
1
2
3
์
๋ ฅ: "๋ฌ๋ฆฌ๋ ์ฌ๋์ด ๋น ๋ฅด๊ฒ ๋ฌ๋ฆฐ๋ค"
๋จ์ด ํ ํฐ: ["๋ฌ๋ฆฌ๋", "์ฌ๋์ด", "๋น ๋ฅด๊ฒ", "๋ฌ๋ฆฐ๋ค"]
๋ฌธ์ : "๋ฌ๋ ค๊ฐ๋" โ `<UNK>` (๊ธฐ์กด ์ดํ์ ์๊ธฐ์!)
2๏ธโฃ ๋ฌธ์ ์์ค์ ํ๊ณ ๐ค
- ์๋ฏธ ์์ค: โ๊ณ ์์ดโ โ [โ๊ณ โ, โ์โ, โ์ดโ] ๋จ์ด ์๋ฏธ ์์ค
- ๊ธด ์ํ์ค: ๋ ๊ธด ์ํ์ค = ๋ ๋ง์ ์ฐ์ฐ
- ๋งฅ๋ฝ ์ด๋ ค์: ๋ชจ๋ธ์ด ๋จ์ด ์์ค ํจํด ํ์ตํ๊ธฐ ์ด๋ ค์
์์:
1
2
3
์
๋ ฅ: "์๋
์ธ์"
๋ฌธ์ ํ ํฐ: ["์", "๋
", " ", "์ธ", "์"]
๋ฌธ์ : 2๊ฐ ๋จ์ด์ 5๊ฐ ํ ํฐ!
๐ก ํด๊ฒฐ์ฑ : Sub Word ํ ํฌ๋์ด์ ์ด์ ์ด ์ดํ ํฌ๊ธฐ์ ์๋ฏธ์ ์๋ฏธ์ ๊ท ํ์ ๋ง์ถฅ๋๋ค!
๐ง ํ๋ ํ ํฌ๋์ด์ ์ด์ ์๊ณ ๋ฆฌ์ฆ
๐๏ธ 1. BPE (Byte Pair Encoding) ๐งฉ
ํต์ฌ ์์ด๋์ด: ๋ฌธ์์์ ์์ํด์ ๊ฐ์ฅ ๋น๋ฒํ ์์ ๋ฐ๋ณต์ ์ผ๋ก ๋ณํฉ
์๊ณ ๋ฆฌ์ฆ ๋จ๊ณ:
- ์ด๊ธฐํ: ํ ์คํธ๋ฅผ ๋ฌธ์๋ก ๋ถํ (๋ฌธ์์์ค ํ ํฌ๋์ด์ ์ด์ )
- ์ ๊ณ์: ๊ฐ์ฅ ๋น๋ฒํ ์ธ์ ๋ฌธ์ ์ ์ฐพ๊ธฐ
- ๋ณํฉ: ๊ฐ์ฅ ๋น๋ฒํ ์์ ์ ํ ํฐ์ผ๋ก ๊ต์ฒด
- ๋ฐ๋ณต: ์ํ๋ ์ดํ ํฌ๊ธฐ๊น์ง
์์ ๊ณผ์ :
์ ์ผ ๊ธฐ๋ณธ์ด๋๋ ๋ฐฉ์์ผ๋ก ์ค์ํ๋, ์ง์ ์์๋ฅผ ๋ฐ๋ผํด๋ณด์์ด์!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# ๊ฐ๋จํ BPE(Byte Pair Encoding) ๋ฏธ๋ ์ค์ต ์ฝ๋
from collections import Counter, defaultdict
import pandas as pd
# ์ํ ๋จ์ด ๋ฆฌ์คํธ
corpus = ["low", "lower", "newest", "widest"]
# Step 1: ๊ฐ ๋จ์ด๋ฅผ ๋ฌธ์ ๋จ์๋ก ๋ถํดํ๊ณ ๋์ ํน์๋ฌธ์ ์ถ๊ฐ
def split_word(word):
return list(word) + ["</w>"] # ๋จ์ด์ ๋ ํ์
# ์ด๊ธฐ vocabulary
tokens = [split_word(word) for word in corpus]
# helper ํจ์: ํ ํฐ ๋ฆฌ์คํธ๋ฅผ ๋ฌธ์์ด๋ก ํฉ์นจ (๊ณต๋ฐฑ ๋จ์๋ก)
def get_vocab(tokens):
vocab = Counter()
for token in tokens:
vocab[" ".join(token)] += 1
return vocab
# ๊ฐ์ฅ ์์ฃผ ๋ฑ์ฅํ๋ pair ์ฐพ๊ธฐ
def get_most_common_pair(vocab):
pairs = defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pair = (symbols[i], symbols[i + 1])
pairs[pair] += freq
return max(pairs, key=pairs.get), pairs
# ๋ณํฉ ์ํ
def merge_pair(pair, tokens):
new_tokens = []
bigram = " ".join(pair)
replacement = "".join(pair)
for token in tokens:
token_str = " ".join(token)
token_str = token_str.replace(bigram, replacement)
new_tokens.append(token_str.split())
return new_tokens
# BPE ํ์ต ์ํ (5ํ๋ง ๋ฐ๋ณต)
merge_history = []
for i in range(5):
vocab = get_vocab(tokens)
pair, all_pairs = get_most_common_pair(vocab)
merge_history.append((pair, all_pairs[pair]))
tokens = merge_pair(pair, tokens)
# ๊ฒฐ๊ณผ ์ ๋ฆฌ
merge_df = pd.DataFrame(merge_history, columns=["Merged Pair", "Frequency"])
tokens_final = [" ".join(token) for token in tokens]
print("Final tokens:", tokens_final)
# Output: ['low </w>', 'lower </w>', 'newest </w>', 'widest </w>']
# ๋จ๊ณ๋ณ ๋ณํฉ ๊ณผ์ ๋ณด๊ธฐ
print("\n๋จ๊ณ๋ณ ๋ณํฉ ๊ณผ์ :")
for i, (pair, freq) in enumerate(merge_history):
print(f"๋จ๊ณ {i+1}: {pair[0]} + {pair[1]} โ {pair[0]+pair[1]} (๋น๋: {freq})")
# Output:
# ๋จ๊ณ 1: e + s โ es (๋น๋: 2)
# ๋จ๊ณ 2: es + t โ est (๋น๋: 2)
# ๋จ๊ณ 3: l + o โ lo (๋น๋: 2)
# ๋จ๊ณ 4: lo + w โ low (๋น๋: 2)
# ๋จ๊ณ 5: n + e โ ne (๋น๋: 1)
BPE ์ฅ์ :
- โ ํฌ๊ท ๋จ์ด ์ฒ๋ฆฌ ์๋ธ์๋ ๋ถํด๋ฅผ ํตํด
- โ ์ผ๊ด๋ ํ ํฌ๋์ด์ ์ด์ ์ ์ฌํ ๋จ์ด๋ค์ ๋ํด
- โ ์ธ์ด ๋ฌด๊ด ์๊ณ ๋ฆฌ์ฆ
๐๏ธ 2. WordPiece ๐งฉ
ํต์ฌ ์์ด๋์ด: BPE์ ์ ์ฌํ์ง๋ง ๋ณํฉ ๊ธฐ์ค์ ์ฐ๋(Likelihood) ์ต๋ํ ์ฌ์ฉ
- WordPiece๋ BPE์ ๋ฌ๋ฆฌ ๋จ์ํ ๊ฐ์ฅ ์์ฃผ ๋ฑ์ฅํ๋ ์์ ๋ณํฉํ์ง ์๊ณ , ๋ณํฉํ์ ๋ ์ ์ฒด ๋ง๋ญ์น์ ์ฐ๋๋ฅผ ์ผ๋ง๋ ์ฌ๋ฆด ์ ์๋์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒฐ์
- ์ฐ๋๊ฐ๋ญ์ง? ์ด๋ ค์ฐ๋ ์์๋ก ๋ณด์!!
"un"
์ด๋ผ๋ ์๋ธ์๋๋ฅผ ๋ณํฉํ๋ฉด:unhappiness
unhappy
unusual
unfit
unseen
- ์ฆ,
"un"
์ด๋ผ๋ subword๊ฐ ์ฌ๋ฌ ๋จ์ด์์ ์๋ฏธ ์๋ ๋ฐ๋ณต ํจํด์ด ๋๋ค๋ฉด
โ ๋ณํฉ์ ํตํด ์ ์ฒด ๋ฌธ์ฅ์์ ๋ชจ๋ธ ์์ธก ์ฑ๋ฅ ํฅ์
๐๏ธ 3. SentencePiece ๐
ํต์ฌ ์์ด๋์ด: ์ฌ์ ํ ํฌ๋์ด์ ์ด์ ์๋ ์ธ์ด ๋ ๋ฆฝ์ ํ ํฌ๋์ด์ ์ด์
- ๊ธฐ์กด BPE๋ ๋จผ์ฌ ๊ณต๋ฐฑ์ผ๋ก ์๋ฅด๊ณ , ๊ทธ ์์์ ์ชผ๊ฐ๊ธฐ์ ๋์ด์ฐ๊ธฐ๊ฐ ์์ผ๋ฉด? ๋ฌธ์ ๊ฐ๋จ!
- ๊ทธ๋์! ๊ณต๋ฐฑ๋ ๋ฌธ์์ฒ๋ผ ๋ณด๊ณ , ๋ชจ๋ ๊ธ์๋ฅผ ์ชผ๊ฐ์ ์ธ์ด์ ์๊ด์์ด ํ์ต
- ๊ณต๋ฐฑ์
โ
๋ก ์นํ!
์ฃผ์ ์ฅ์ :
- โ ์ฌ์ ํ ํฌ๋์ด์ ์ด์ ๋ถํ์ (๋จ์ด ๊ฒฝ๊ณ ์์)
- โ ๋ชจ๋ ์ธ์ด ์ฒ๋ฆฌ ์ค๊ตญ์ด, ์ผ๋ณธ์ด, ์๋์ด ํฌํจ
- โ ๊ฐ์ญ์ : ์๋ณธ ํ ์คํธ ์๋ฒฝ ์ฌ๊ตฌ์ฑ ๊ฐ๋ฅ
- โ T5, mT5, ALBERT์์ ์ฌ์ฉ
SentencePiece ํน์ง:
1
2
3
4
5
6
7
# ๊ณต๋ฐฑ์ ์ผ๋ฐ ๋ฌธ์๋ก ์ฒ๋ฆฌ
์
๋ ฅ: "์๋
์ธ์"
ํ ํฐ: ["โ์๋
", "โ์ธ์"] # โ๋ ๊ณต๋ฐฑ์ ๋ํ๋
# ๊ณต๋ฐฑ์ด ์๋ ์ธ์ด ์ฒ๋ฆฌ
์
๋ ฅ: "ใใใซใกใฏไธ็" # ์ผ๋ณธ์ด: "์๋
์ธ์"
ํ ํฐ: ["ใใใซ", "ใกใฏ", "ไธ็"]
๐ ํ ํฌ๋์ด์ ๋น๊ต
๋ฐฉ๋ฒ | ์ดํ ํฌ๊ธฐ | OOV ์ฒ๋ฆฌ | ์ธ์ด ์ง์ | ์ฌ์ฉ ๋ชจ๋ธ |
---|---|---|---|---|
๋จ์ด ์์ค | 5๋ง-10๋ง+ | โ ๋์จ | ๐ค ์ธ์ด๋ณ ํนํ | ์ ํต์ NLP |
๋ฌธ์ ์์ค | 100-1์ฒ | โ ์๋ฒฝ | ๐ ๋ฒ์ฉ | ์ด๊ธฐ NMT |
BPE | 3๋ง-5๋ง | โ ์ข์ | ๐ ๋ฒ์ฉ | GPT, RoBERTa |
WordPiece | 3๋ง-5๋ง | โ ์ข์ | ๐ ๋ฒ์ฉ | BERT, DistilBERT |
SentencePiece | 3๋ง-5๋ง | โ ํ๋ฅญํจ | ๐ ๋ฒ์ฉ | T5, mT5, ALBERT |
๐ป ์ค์ ๊ตฌํ
๐ง Hugging Face Tokenizers ์ฌ์ฉ
๋ชจ๋ธ๋ค์ ํ ํฌ๋์ด์ ธ๋ฅผ ๋ค์ด๋ฐ์์ ์์ต๋๋ค!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from transformers import AutoTokenizer
# BPE (GPT-2)
bpe_tokenizer = AutoTokenizer.from_pretrained("gpt2")
text = "The runner runs quickly and efficiently"
bpe_tokens = bpe_tokenizer.tokenize(text)
print(f"BPE: {bpe_tokens}")
# Output: ['The', 'ฤ runner', 'ฤ runs', 'ฤ quickly', 'ฤ and', 'ฤ efficiently']
# WordPiece (BERT)
wp_tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
wp_tokens = wp_tokenizer.tokenize(text)
print(f"WordPiece: {wp_tokens}")
# Output: ['the', 'runner', 'runs', 'quickly', 'and', 'efficiently']
# SentencePiece (T5)
sp_tokenizer = AutoTokenizer.from_pretrained("t5-small")
sp_tokens = sp_tokenizer.tokenize(text)
print(f"SentencePiece: {sp_tokens}")
# Output: ['โThe', 'โrunner', 'โruns', 'โquickly', 'โand', 'โefficiently']
๐ง ์ปค์คํ BPE ํ ํฌ๋์ด์ ํ๋ จ
- ์๋ ๋ฐฉ์์ ์ด๊ธฐํ๋ tokenizer๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋๊ฒ!!
- ์ ์๋
english_texts
๋ฅผ ๋ฐํ์ผ๋ก ํ ํฌ๋์ด์ ธ ๋ง๋ฌ!!
์๋ ์์๋ ์ต๋ ํ ํฐ์๋ 5000๊ฐ, 2๋ฒ ์ด์ ๋ฑ์ฅํด์ผ ํจ!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
# English text data examples
english_texts = [
"Hello world, how are you today?",
"The weather is beautiful and sunny",
"We are building a custom tokenizer for English",
"Natural language processing is a fascinating field",
"Deep learning and machine learning are powerful technologies"
]
# Save texts to file
with open("english_corpus.txt", "w", encoding="utf-8") as f:
for text in english_texts:
f.write(text + "\n")
# Initialize BPE tokenizer
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()
# Configure trainer for English
trainer = BpeTrainer(
vocab_size=5000, # Larger vocab for English
min_frequency=2, # Minimum frequency threshold
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
show_progress=True
)
# Train on English text
tokenizer.train(["english_corpus.txt"], trainer)
# Test the tokenizer
test_text = "Natural language processing is fascinating!"
tokens = tokenizer.encode(test_text)
print(f"Input: {test_text}")
print(f"Token IDs: {tokens.ids}")
print(f"Tokens: {tokens.tokens}")
# Save tokenizer
tokenizer.save("my_english_bpe_tokenizer.json")
print("English BPE tokenizer saved successfully!")
ํ๋ฆฐํธ๋๋ ๊ฒฐ๊ณผ๊ฐ์!?
๋๋ํ๋ ์ฒ์๋ณด๋๊ฒ์ด๊ธฐ์
[UNK]
๊ฐ ๋๋ค~!
1
2
3
4
Input: Natural language processing is fascinating
Token IDs: [10, 39, 31, 28, 13, 23, 23, 38, 19, 31, 13, 19, 17, 27, 28, 26, 15, 46, 29, 37, 41, 18, 13, 29, 15, 35, 39, 37]
Tokens: ['N', 'at', 'u', 'r', 'a', 'l', 'l', 'an', 'g', 'u', 'a', 'g', 'e', 'p', 'r', 'o', 'c', 'es', 's', 'ing', 'is', 'f', 'a', 's', 'c', 'in', 'at', 'ing', 'w', 'e', 'at', 'h', 'er', '[UNK]']
English BPE tokenizer saved successfully!
๐งฉ ํ ํฌ๋์ด์ ์ด์ ์กฐ๊ธ๋ ์์๋ณด๊ธฐ!
1๏ธโฃ Special Tokens ๐ฏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Example usage with BERT tokenizer
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
text = "The quick brown foxใ
"
encoded = tokenizer(text,
add_special_tokens=True,
max_length=10,
padding="max_length",
truncation=True)
print(f"Input: {text}")
print(f"Token IDs: {encoded['input_ids']}")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(encoded['input_ids'])}")
# Expected output:
# Input: The quick brown fox
# Token IDs: [101, 1996, 4248, 2829, 4419, 102, 0, 0, 0, 0]
# Tokens: ['[CLS]', 'the', 'quick', 'brown', '[UNK]', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']
- ๊ฒฐ๊ณผ ์ค๋ช
!!
max_length = 10 ์ด์๊ธฐ์, ๊ธธ์ด๋ฅผ ๋ง์ถ๊ณ ์ PAD๊ฐ 3๊ฐ ๋์ด!
ใ
๋ ํ๊ธ๋ก ๋ชจ๋ฅด๊ธฐ์[UNK]
ํ ํฐ | ์ค๋ช |
---|---|
[PAD] | ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ์ํ ํจ๋ฉ ํ ํฐ (์ํ์ค ๊ธธ์ด ๋ง์ถค) |
[UNK] | ๋ฏธ์ง์ ๋จ์ด(์ดํ์ง์ ์๋ OOV ๋จ์ด)๋ฅผ ์ํ ํ ํฐ |
[CLS] | ๋ฌธ์ฅ ๋ถ๋ฅ์ฉ ์์ ํ ํฐ (BERT์์ ์ฌ์ฉ) |
[SEP] | ๋ฌธ์ฅ ๊ตฌ๋ถ ํ ํฐ (๋ฌธ์ฅ ๊ฐ ๊ตฌ๋ถ ๋๋ ๋ฌธ์ฅ ๋ ํ์) |
[MASK] | ๋ง์คํน๋ ํ ํฐ (Masked Language Modeling์ฉ) |
<s> | ์ํ์ค ์์ ํ ํฐ (GPT ๋ฑ์์ ์ฌ์ฉ) |
</s> | ์ํ์ค ์ข ๋ฃ ํ ํฐ (GPT ๋ฑ์์ ์ฌ์ฉ) |
2๏ธโฃ Tokenization vs Encoding ๐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from transformers import AutoTokenizer
# ์ฌ์ ํ์ต๋ BERT tokenizer ๋ถ๋ฌ์ค๊ธฐ
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# ์
๋ ฅ ๋ฌธ์ฅ
text = "Hello, world!"
# 1. Tokenization: ๋ฌธ์ฅ์ ํ ํฐ ๋ฆฌ์คํธ๋ก ๋ณํ
tokens = tokenizer.tokenize(text)
print(f"Tokens: {tokens}")
# ์์ ์ถ๋ ฅ: ['hello', ',', 'world', '!']
# 2. Encoding: ๋ฌธ์ฅ์ ํ ํฐ ID ๋ฆฌ์คํธ๋ก ๋ณํ
token_ids = tokenizer.encode(text, add_special_tokens=False)
print(f"Token IDs: {token_ids}")
# ์์ ์ถ๋ ฅ: [7592, 1010, 2088, 999]
# 3. Full encoding with special tokens
encoded = tokenizer(
text,
add_special_tokens=True, # [CLS], [SEP] ์ถ๊ฐ
padding=True, # padding ์ถ๊ฐ (๋จ์ผ ๋ฌธ์ฅ์๋ ๊ฐ๋ฅ)
truncation=True, # ๋๋ฌด ๊ธด ๋ฌธ์ฅ์ ์๋ฅด๊ธฐ
return_tensors="pt" # PyTorch tensor๋ก ๋ฐํ
)
# ์ถ๋ ฅ
print("Full Encoding (with special tokens, padding):")
print(encoded)
print("Input IDs:", encoded['input_ids'])
print("Attention Mask:", encoded['attention_mask'])
๊ฒฐ๊ณผ๋ฌผ์!?
Full encoding์์๋ [CLS] [SEP] ๋ฑ์ด ์ถ๊ฐ๋๊ฑฐ์!
1
2
3
4
5
6
Tokens ์ผ๋ก ์ชผ๊ฐ ๊ฑฐ! : ['hello', ',', 'world', '!']
Token IDs: [7592, 1010, 2088, 999]
Full Encoding (with special tokens, padding):
{'input_ids': tensor([[ 101, 7592, 1010, 2088, 999, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1]])}
Input IDs: tensor([[ 101, 7592, 1010, 2088, 999, 102]])
Attention Mask: tensor([[1, 1, 1, 1, 1, 1]])
3๏ธโฃ ์ฌ๋ฌ ๊ตญ๊ฐ์ ์ธ์ด์ฒ๋ฆฌ! ๐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from transformers import AutoTokenizer
# Multilingual examples
texts = [
"Hello world", # English
"Bonjour le monde", # French
"Hola mundo", # Spanish
"Guten Tag Welt", # German
"Ciao mondo" # Italian
]
# Different tokenizers handle multilingual text differently
multilingual_tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
english_tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print("=== Multilingual Tokenizer ===")
for text in texts:
tokens = multilingual_tokenizer.tokenize(text)
print(f"{text} โ {tokens}")
print("\n=== English-only Tokenizer ===")
for text in texts: # Only English and French
tokens = english_tokenizer.tokenize(text)
print(f"{text} โ {tokens}")
๊ฒฐ๊ณผ๋ฌผ์!?
๋จ์ ์์ด ํ ํฌ๋์ด์ ๋ ๋ค๊ตญ์ด ํ์ต๋๊ฑฐ๋ ๋ค๋ฅด์ง์!?
1
2
3
4
5
6
7
8
9
10
11
12
13
=== Multilingual Tokenizer ===
Hello world โ ['Hello', 'world']
Bonjour le monde โ ['Bon', '##jou', '##r', 'le', 'monde']
Hola mundo โ ['Ho', '##la', 'mundo']
Guten Tag Welt โ ['Gut', '##en', 'Tag', 'Welt']
Ciao mondo โ ['Ci', '##ao', 'mondo']
=== English-only Tokenizer ===
Hello world โ ['hello', 'world']
Bonjour le monde โ ['bon', '##jou', '##r', 'le', 'monde']
Hola mundo โ ['ho', '##la', 'mundo']
Guten Tag Welt โ ['gut', '##en', 'tag', 'we', '##lt']
Ciao mondo โ ['cia', '##o', 'mon', '##do']
๐ ์ฑ๋ฅ ์ํฅ ๋ถ์
๐ ์ดํ ํฌ๊ธฐ ์ํฅ
์ดํ ํฌ๊ธฐ | ์ฅ์ | ๋จ์ | ์ต์ ์ฉ๋ |
---|---|---|---|
์ํ (1K-5K) | ๐พ Memory efficient โก Fast training | ๐ Many subwords ๐ Long sequences | Resource-constrained |
์คํ (10K-30K) | โ๏ธ Balanced performance โ Good coverage | ๐ Standard choice | Most applications |
๋ํ (50K+) | ๐ฏ Better semantic units ๐ Shorter sequences | ๐พ Memory intensive โฑ๏ธ Slower training | Large-scale models |
๐ง Tokenization ์๋๋น๊ต!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
from transformers import AutoTokenizer
text = "This is a sample text " * 1000 # Long text
tokenizers = {
"BPE (GPT-2)": AutoTokenizer.from_pretrained("gpt2"),
"WordPiece (BERT)": AutoTokenizer.from_pretrained("bert-base-uncased"),
"SentencePiece (T5)": AutoTokenizer.from_pretrained("t5-small")
}
for name, tokenizer in tokenizers.items():
start_time = time.time()
tokens = tokenizer.tokenize(text)
end_time = time.time()
print(f"{name}:")
print(f" Tokens: {len(tokens)}")
print(f" Time: {end_time - start_time:.4f}s")
print(f" Speed: {len(tokens)/(end_time - start_time):.0f} tokens/s")
๊ฒฐ๊ณผ๋ฌผ์!?
BPE๊ฐ ์ ์ผ ๋น ๋ฅด๊ณ , T5๊ฐ ํ ํฐ์๊ฐ ์ ค ๋ง์์ !
Tokenizer Type | Tokens | Time (์ด) | Speed (tokens/s) |
---|---|---|---|
BPE (GPT-2) | 5001 | 0.0125 | 401,257 |
WordPiece (BERT) | 5000 | 0.0142 | 352,380 |
SentencePiece (T5) | 6000 | 0.0145 | 413,069 |
โ ๏ธ ํ๊ณ์ & ๋์ ๊ณผ์
1๏ธโฃ ์ผ๊ด๋์ง ์์ Tokenization ๐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from transformers import AutoTokenizer
# Same words in different contexts
tokenizer = AutoTokenizer.from_pretrained("gpt2")
text1 = "run"
text2 = "running"
text3 = "I like to run fast"
text4 = "The running water is clean"
print(f"'{text1}' โ {tokenizer.tokenize(text1)}")
print(f"'{text2}' โ {tokenizer.tokenize(text2)}")
print(f"'{text3}' โ {tokenizer.tokenize(text3)}")
print(f"'{text4}' โ {tokenizer.tokenize(text4)}")
# Output examples:
# 'run' โ ['run']
# 'running' โ ['running']
# 'I like to run fast' โ ['I', 'ฤ like', 'ฤ to', 'ฤ run', 'ฤ fast']
# 'The running water is clean' โ ['The', 'ฤ running', 'ฤ water', 'ฤ is', 'ฤ clean']
- ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด run ์๋ํ์ฌ ์ฌ๋ฌ๋ฐฉ์์ผ๋ก Tokenization ๋๋ค!!
2๏ธโฃ ์๋ธ์๋ ๊ฒฝ๊ณ ๋ฌธ์ โ๏ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Examples of problematic subword splitting
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
problematic_words = [
"unhappiness",
"preprocessor",
"antidisestablishmentarianism",
"biocompatibility"
]
for word in problematic_words:
tokens = tokenizer.tokenize(word)
print(f"'{word}' โ {tokens}")
# Output examples:
# 'unhappiness' โ ['un', '##hap', '##piness'] # Loses "happy" semantic unit
# 'preprocessor' โ ['pre', '##proc', '##ess', '##or'] # Splits "process"
# 'antidisestablishmentarianism' โ ['anti', '##dis', '##esta', '##blish', '##ment', '##arian', '##ism']
# 'biocompatibility' โ ['bio', '##com', '##pat', '##ibility'] # Loses "compatibility"
๋ฌธ์ ์ : ์๋ฏธ์๋ ๋จ์๊ฐ ์๋ชป ๋ถํ ๋จ (pre / processor๋ก ๊ตฌ๋ถ๋๋๊ฒ ์ ์ผ ์ข๊ฒ ์ง๋ง!?..)
์ํฅ: ๋ชจ๋ธ์ด ๋จ์ด ๊ด๊ณ์ ํํํ์ ์ดํดํ๋๋ฐ ์ด๋ ค์์ ๊ฒช์ ์ ์์
3๏ธโฃ ๋๋ฉ์ธ์ ๋ํ ์ง์ ๋ถ์กฑ์ผ๋ก ํ๊ณ! ๐ฅ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from transformers import AutoTokenizer
# Medical text examples
medical_texts = [
"Patient presents with acute myocardial infarction and requires immediate intervention",
"Blood pressure elevated, prescribing ACE inhibitors for hypertension management",
"CT scan reveals suspicious pulmonary nodules, scheduling biopsy procedure"
]
# General vs Medical tokenizer comparison
general_tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# medical_tokenizer = AutoTokenizer.from_pretrained("clinical-bert") # Hypothetical
for text in medical_texts[:1]: # First example only
general_tokens = general_tokenizer.tokenize(text)
print(f"General Tokenizer:")
print(f" Input: {text}")
print(f" Tokens: {general_tokens}")
print(f" Token count: {len(general_tokens)}")
# Medical tokenizer would handle medical terms as single tokens
print(f"\nMedical Tokenizer (Expected):")
print(f" Tokens: ['patient', 'presents', 'with', 'acute_myocardial_infarction', 'and', 'requires', 'immediate', 'intervention']")
print(f" Token count: 8 (significant reduction)")
# General: ['patient', 'presents', 'with', 'acute', 'my', '##oc', '##ard', '##ial', 'in', '##far', '##ction', 'and', 'requires', 'immediate', 'intervention']
# Medical: ['patient', 'presents', 'with', 'acute_myocardial_infarction', 'and', 'requires', 'immediate', 'intervention']
- ์ํ์ฉ์ด๋ฅผ ์ ์ชผ๊ฐ์ง ๋ชปํ๋ค!!
4๏ธโฃ ์ธ์ด๋ณ ํนํํ๋๊ฒ์ ๋ํ ๋ฌธ์ ๐
- ํํํ์ ๋ณต์ก์ฑ: ํ๋ถํ ํํํ์ ๊ฐ์ง ์ธ์ด๋ค (๋ ์ผ์ด, ํฐํค์ด, ํ๋๋์ด)
- ๊ต์ฐฉ์ด์ ํน์ฑ: ํํ์ ๊ฒฐํฉ์ผ๋ก ๋จ์ด๊ฐ ํ์ฑ๋๋ ์ธ์ด๋ค (์ผ๋ณธ์ด, ํ๊ตญ์ด, ํ๊ฐ๋ฆฌ์ด)
- ๋ฌธ์ ์ฒด๊ณ ํผ์ฉ: ํ ํ ์คํธ์์ ์ฌ๋ฌ ๋ฌธ์ ์ฒด๊ณ ์ฌ์ฉ (์ผ๋ณธ์ด: ํ๋ผ๊ฐ๋ + ๊ฐํ์นด๋ + ํ์)
- ๋ณตํฉ์ด: ๋ ์ผ์ด โDonaudampfschifffahrtsgesellschaftskapitรคnโ (๋ค๋ด๋ธ ์ฆ๊ธฐ์ ํ์ฌ ์ ์ฅ)
๐ฎ ๋ฏธ๋ ๋ฐฉํฅ
1๏ธโฃ ์ ๊ฒฝ๋ง ๊ธฐ๋ฐ ํ ํฌ๋์ด์ ์ด์ ๐ง
- ๊ฐ๋ : ์๋ํฌ์๋ ํ์ต ๊ฐ๋ฅํ ํ ํฌ๋์ด์ ์ด์
- ์ฅ์ : ํ์คํฌ๋ณ ์ต์ ํ
- ๋์ ๊ณผ์ : ๊ณ์ฐ ๋ณต์ก์ฑ
2๏ธโฃ ๋ฉํฐ๋ชจ๋ฌ ํ ํฌ๋์ด์ ์ด์ ๐ผ๏ธ
ํ ์คํธ๋ ๋ฌธ์๋ฅผ ํ ํฌ๋์ด์ ์ด์ ํ๋ฉด์ ์ซ์๋ก ๋ฐ๊พธ์ง๋ง!!
์ด๋ฏธ์ง๋ ๊ทธ๋ฐ๊ฑฐ ์์ด ViT๋ก ๋ฐ๋ก ๋ฒกํฐํํด๋ฒ๋ฆฝ๋๋ค!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Future concept: unified text-image tokenization
multimodal_input = {
"text": "A cat sitting on a chair",
"image": cat_image_tensor
}
unified_tokens = multimodal_tokenizer.tokenize(multimodal_input)
# Outputs both text and visual tokens in same space
# Example output:
# [
# ("A", "text_token"),
# ("cat", "text_token"),
# ("<img_patch_1>", "visual_token"),
# ("sitting", "text_token"),
# ("<img_patch_2>", "visual_token"),
# ("on", "text_token"),
# ("chair", "text_token")
# ]
- ์ด๋ฐ๋ฐฉ์์ผ๋กํด์ ๊ตฌ๊ธ์ด Multi-modal large Model ์ธ
Gemini
๋ฅผ ๊ณต๊ฐํ์ง์ !! - ์ด๋ฏธ์ง๋ฅผ ํ ํฐํํ๋๋ฒ!? ์ฐ์ ViT ์ ๋ํ์ฌ ๊ณต๋ถํ๋ฉด ์ ์ ์์ด์!
- ์ธ์ด์ ๊ฒฝ์ฐ๋ ์๋ฌด๋ฆฌ ๋จ์ด๊ฐ ๋ง์๋ ํ๋๊ฐ ์๋๋ฐ,
- ๊ทธ๋ฐ๋ฐ!?
- ๋ฌธ์ 1. ์ด๋ฏธ์ง๋ ํ ํฐ์ด ๋ฌด์ ํ ๋์ฌ์ ์๋ค!! ๊ทธ๋ผ ์ด๋ปํ์ง!?
- ๋ฌธ์ 2. ์ด๋ฏธ์ง์ ํ ์คํธ ํ ํฐ์ ์ ๋ ฌ์ ์ด๋ป๊ฒํ ๊น!?
๋ฌธ์ 1. ํ ํฐ ๋ค์์ฑ ๋ฌธ์ ๐
- ํ ์คํธ ํ ํฐ: ๊ณ ์ ๋ ์ดํ ์ฌ์ (์: 50,000๊ฐ)
- ์ด๋ฏธ์ง ํ ํฐ: ๋ฌดํ๋์ ๊ฐ๊น์ด ๋ค์์ฑ (๊ฐ ํจ์น๋ง๋ค ์์ ํ ๋ค๋ฅธ ๊ฐ)!!!!
- ๊ฒฐ๊ณผ: ๋ชจ๋ธ์ด ์ด๋ฏธ์ง ํ ํฐ์ ํ์ตํ๊ณ ์ผ๋ฐํํ๊ธฐ ๋งค์ฐ ์ด๋ ต๊ณ ,์ฐ์ฐ๋์ด ๋ง์์ง
๐ก ํด๊ฒฐ๋ฒ๋ค:์ด๋ฏธ์ง ํ ํฐ์ ์ต์ํํ๊ธฐ์ํด ๋ ธ๋ ฅํ๊ฑฐ๋ (1,2,3) ํจ์จํ๋ฅผ ํตํด์ ํด๊ฒฐํ๊ณ ์ํฉ๋๋ค!(4,5)
- ๐ฒ ๊ณ ์ ํจ์น ๋ถํ : ViT ๋ฐฉ์์ผ๋ก ์ด๋ฏธ์ง ํฌ๊ธฐ์ ์๊ด์์ด ์ผ์ ํ ํ ํฐ ์ ๋ณด์ฅ (224ร224 โ 196๊ฐ ํ ํฐ)
- ๐ ์ ์์ ์์ถ: ๋ชฉํ ํ ํฐ ์์ ๋ง๊ฒ ์ด๋ฏธ์ง ์์ถ (512ร512 โ 100๊ฐ ํ ํฐ)
- ๐ฏ ์ ํ์ ํ ํฐํ: ์ค์ํ ์์ญ๋ง ํ ํฐํ (๊ด์ฌ ์์ญ ๊ธฐ๋ฐ์ผ๋ก 128๊ฐ๋ง ์ ํ)
- โก ํจ์จ์ ์ดํ ์ : Flash Attention, Sparse Attention์ผ๋ก ์ฐ์ฐ๋ 2-4๋ฐฐ ์ค์
- ๐๏ธ ๊ณ์ธต์ ์ฒ๋ฆฌ: ์ด๊ธฐ์๋ ๋ณ๋ ์ฒ๋ฆฌ, ์ ์ง์ ์ผ๋ก ํฌ๋ก์ค ๋ชจ๋ฌ ์ดํ ์ ๋์
๋ฌธ์ 2. ์ด๋ฏธ์ง/ํ ์คํธ Token์ ์ ๋ ฌ!! ํฌ๋ก์ค ๋ชจ๋ฌ ๋งค์นญ ๋ฌธ์ ๐
- ํ ์คํธ โ๊ณ ์์ดโ์ ์ด๋ฏธ์ง ํจ์น๋ค ๊ฐ์ ์๋ฏธ์ ์ฐ๊ฒฐ์ด ์ด๋ ค์
- ๊ฐ ์ด๋ฏธ์ง ํจ์น๊ฐ ์ด๋ค ํ
์คํธ ํ ํฐ๊ณผ ๊ด๋ จ์๋์ง ๋ถ๋ถ๋ช
๐ก ํด๊ฒฐ๋ฒ๋ค:์ฌ๊ธฐ๋ ์ด์ ํ ํฐํ๋ผ๊ธฐ๋ณด๋จ Transformer ๋ถ๋ถ์์ ๋ ์์ธํ ์์๋ณด์์!
- ๐ฏ ํตํฉ ์๋ฒ ๋ฉ ๊ณต๊ฐ: ์ด๋ฏธ์ง ํจ์น๋ฅผ ํ ์คํธ ํ ํฐ๊ณผ ๋์ผํ ์ฐจ์์ผ๋ก ๋ณํ (Gemini: 2048์ฐจ์ ํตํฉ)
- ๐ ํฌ๋ก์ค ์ดํ ์ ๋ฉ์ปค๋์ฆ: ์ด๋ฏธ์ง ํ ํฐ์ด ํ ์คํธ ํ ํฐ๊ณผ ์ง์ ์ํธ์์ฉํ๋๋ก ์ค๊ณ
- ๐ ๋๊ท๋ชจ ๋ฉํฐ๋ชจ๋ฌ ํ์ต: ์ด๋ฏธ์ง-ํ ์คํธ ์ ์์ญ์ต ๊ฐ๋ก ์ ๋ ฌ ๊ด๊ณ ํ์ต
- ๐งฉ ํ ํฐ ๋ ๋ฒจ ์ ๋ ฌ: CLIP ๋ฐฉ์์ฒ๋ผ ์ด๋ฏธ์ง ์์ญ๊ณผ ํ ์คํธ ๋จ์ด๋ฅผ ์ง์ ๋งค์นญ
- ๐จ ์๋ฏธ์ ๊ทธ๋ฃนํ: ๋น์ทํ ์๋ฏธ์ ์ด๋ฏธ์ง ํจ์น๋ค์ ํ๋๋ก ๋ฌถ์ด ํ ์คํธ์ ๋งค์นญ
๐ ์ค์ ๋ชจ๋ธ๋ค์ ํด๊ฒฐ ์ ๋ต:
๋ชจ๋ธ | ๋ฌธ์ 1 ํด๊ฒฐ๋ฒ | ๋ฌธ์ 2 ํด๊ฒฐ๋ฒ | ํน์ง |
---|---|---|---|
GPT-4V | ์ ์์ ํ ํฐํ | ๊ณ์ธต์ ํฌ๋ก์ค ์ดํ ์ | ์ด๋ฏธ์ง ๋ณต์ก๋์ ๋ฐ๋ผ ์กฐ์ |
Gemini Ultra | ๊ณ ํจ์จ ์์ถ | ํตํฉ ์๋ฒ ๋ฉ ๊ณต๊ฐ | ํ ์คํธ์ ์ด๋ฏธ์ง ์์ ํตํฉ |
Claude 3 | ์ ํ์ ์ฒ๋ฆฌ | ์๋ฏธ์ ๊ทธ๋ฃนํ | ์ค์ํ ์์ญ๋ง ์ง์ค ์ฒ๋ฆฌ |
LLaVA | ๊ณ ์ ํจ์น ๋ถํ | CLIP ๊ธฐ๋ฐ ์ ๋ ฌ | 576๊ฐ ํ ํฐ์ผ๋ก ๊ณ ์ |
๐ฏ ํต์ฌ ๊นจ๋ฌ์:
- ๋ฌธ์ 1์ ํจ์จ์ฑ์ ๋ฌธ์ โ ์ค๋งํธํ ํ ํฐ ๊ด๋ฆฌ๋ก ํด๊ฒฐ
- ๋ฌธ์ 2๋ ์ ๋ ฌ์ ๋ฌธ์ โ ๋๊ท๋ชจ ํ์ต๊ณผ ํตํฉ ์๋ฒ ๋ฉ์ผ๋ก ํด๊ฒฐ
- ๋ ๋ฌธ์ ๋ชจ๋ โ๋ ๋ง์ ๋ฐ์ดํฐโ๊ฐ ์๋ โ๋ ๋๋ํ ๋ฐฉ๋ฒโ์ผ๋ก ํด๊ฒฐ! ๐
๐งฉ ์ค์ธ ์ฌ์ฉ๋๊ณ ์๋ ์ฃผ์ ๋ชจ๋ธ๋ณ Tokenizer ์์ฝ
๋ชจ๋ธ | Tokenizer ์ข ๋ฅ | ํน์ง |
---|---|---|
GPT-2 / GPT-3 / GPT-4 | BPE (OpenAI GPT Tokenizer) | tiktoken ์ฌ์ฉ, ์์ด์ ์ต์ ํ๋จ |
LLaMA / LLaMA2 / LLaMA3 | SentencePiece + BPE | Meta๊ฐ ์ง์ ํ์ตํ SentencePiece ๊ธฐ๋ฐ ๊ตฌ์กฐ ์ฌ์ฉ |
Gemini (Google) | SentencePiece ๊ธฐ๋ฐ ์ถ์ | PaLM/Flan ๊ณ์ด๊ณผ ์ ์ฌํ ๊ตฌ์กฐ, ์ธ๋ถ ํ ํฌ๋์ด์ ๋ฏธ๊ณต๊ฐ |
Claude (Anthropic) | BPE ๋ณํ | ์ธ๋ถ ๊ตฌ์กฐ๋ ๋น๊ณต๊ฐ, ์์ฒด ํ ํฌ๋์ด์ ๊ตฌ์กฐ ์ฌ์ฉ |
Qwen (Alibaba) | GPT-style BPE | ์ค๊ตญ์ด ์ต์ ํ, ์์ด๋ ์ง์, Tokenizer ๊ณต๊ฐ๋จ |
Mistral / Mixtral | SentencePiece | open-source ๋ชจ๋ธ, HuggingFace tokenizer ๊ตฌ์กฐ ๋ฐ๋ฆ |
Qwen-VL (๋ฉํฐ๋ชจ๋ฌ) | GPT-style BPE + Vision ํนํ | ํ ์คํธ๋ Qwen๊ณผ ๋์ผ, ์ด๋ฏธ์ง ์ ๋ ฅ์ CLIP-style ํจ์น ๋ถํ ์ฌ์ฉ |
Gemini (๋ฉํฐ๋ชจ๋ฌ) | SentencePiece + Vision | ์ ํํ ๊ตฌ์กฐ ๋ฏธ๊ณต๊ฐ, Flamingo-like ๊ตฌ์กฐ๋ก ์ถ์ |
Grok (xAI) | ๋น๊ณต๊ฐ | ๋ชจ๋ธ ๋ฐ ํ ํฌ๋์ด์ ๊ตฌ์กฐ ๋๋ถ๋ถ ๋น๊ณต๊ฐ, ์์ด ๊ธฐ๋ฐ ์ถ์ |