🚨해당 게시글에 포함된 이미지 중 출처가 쓰여있지 않은 이미지는 모두 직접 그렸습니다.
5.0. 서론
- 자연어(Natural Language) : 인공적으로 만들어진 프로그래밍 언어와 다르게 사람들이 쓰는 언어 활동을 위해 만들어진 언어
- 자연어 처리(Natural Language Processing, NLP) : 컴퓨터가 인간의 언어를 이해하고 해석 및 생성하기 위한 기술
인간 언어의 구조, 의미, 맥락을 분석하고 이해할 수 있는 알고리즘과 모델을 개발하기 위해 해결할 문제
- 모호성(Ambiguity) : 인간의 언어는 단어와 구와 사용되는 맥락에 따라 여러 의미를 갖게 되어 모호한 경우가 많음
- 가변성(Variability) : 다양한 사투리, 강세, 신조어, 작문 스타일로 인해 매우 가변적
- 구조(Structure) : 문장이나 구의 의미를 이해할 때 구문을 파악하여 의미를 해석
→ 말뭉치(corpus)를 일정한 단위인 토큰(Token)으로 나누는 과정을 토큰화(Tokenization)
토큰화를 위해 텍스트 문자열을 토큰으로 나누는 알고리즘 또는 소프트웨어인 토크나이저(Tokenizer)를 사용
5.1. 단어 및 글자 토큰화
5.1.1. 단어 토큰화
단어 토큰화(Word Tokenization) : 자연어 처리 분야에서 핵심적인 전처리 작업 중 하나로 텍스트 데이터를 의미 있는 단위인 단어로 분리하는 작업
대부분의 언어는 띄어쓰기나 문장 부호를 활용해 문장을 표현함 → 띄어쓰기, 문장 부호, 대소문자 등의 특정 구분자를 활용해 토큰화가 수행됨
review = "현실과 구분 불가능한 cg. 시각적 즐거움은 최고! 더불어 ost는 더더욱 최고!!"
tokenized = review.split()
print(tokenized)

출력 결과를 확인해보면 ‘최고!’ 와 ‘최고!!’가 느낌표 하나 차이로 다른 의미로 나뉘었다.
이처럼 단어 토큰화는 한국어 접사, 문장 부호, 오타 혹은 띄어쓰기 오류 등에 취약하다.
5.1.2. 글자 토큰화
글자 토큰화(Character Tokenizer) : 띄어쓰기뿐만 아리나 글자 단위로 문장을 나누는 방식
review = "현실과 구분 불가능한 cg. 시각적 즐거움은 최고! 더불어 ost는 더더욱 최고!!"
tokenized = list(review)
print(tokenized)

한글의 경우 하나의 글자는 여러 자음과 모음의 조합으로 이루어져있다. → 자소 단위로 나누어 자소 단위 토큰화를 수행
- 자모 변환 함수 : 한글 문자열을 유니코드 U+1100~U+11FE 사이의 조합형 한글 자모로 변환하는 함수
- 글자를 자모 단위로 나눠 인코딩한 뒤 이를 조합해 한글을 표현하는 조합형
- retval = jamo.h2j(hangul_string)
- 조합된 글자 자체에 값을 부여해 인코딩하는 완성형
- retval = jamo.j2hcj(jamo)
- 자소단위 토큰화
from jamo import h2j, j2hcj
review = "현실과 구분 불가능한 cg. 시각적 즐거움은 최고! 더불어 ost는 더더욱 최고!!"
decomposed = j2hcj(h2j(review))
tokenized = list(decomposed)
print(tokenized)

5.2. 형태소 토큰화
형태소 토큰화(Morpheme Tokenization) : 텍스트를 형태소 단위로 언어의 문법과 구조를 고려해 단어를 분리하고 이를 의미있는 단위로 분류하는 작업
- 자립 형태소 : 그, 나, 인사
- 의존 형태소 : -는, -에게, -를, 했-, -다
5.2.1. 형태소 어휘 사전
형태소 어휘 사전(Morpheme Vocabulary) : 자연어 처리에서 사용되는 단어의 집합인 어휘 사전 중에서도 각 단어의 형태소 정보를 포함하는 사전
품사 태깅(POS tagging) : 텍스트 데이터를 형태소 분석하여 각 형태소에 해당하는 품사(Part Of Speech, POS)를 태깅하는 작업
5.2.2. KoNLPy
koNLPy : 한국어 자연어 처리를 위해 개발된 라이브러리로 명사 추출, 형태소 분석, 품사 태깅 등의 기능을 제공
- okt 토큰화
from konlpy.tag import Okt
okt = Okt()
sentence = "무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다."
nouns = okt.nouns(sentence)
phrases = okt.phrases(sentence)
morphs = okt.morphs(sentence)
pos = okt.pos(sentence)
print("명사 추출 :", nouns)
print("구 추출 :", phrases)
print("형태소 추출 :", morphs)
print("품사 태깅 :", pos)

- 꼬꼬마 토큰화
from konlpy.tag import Kkma
kkma = Kkma()
sentence = "무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다."
nouns = kkma.nouns(sentence)
sentences = kkma.sentences(sentence)
morphs = kkma.morphs(sentence)
pos = kkma.pos(sentence)
print("명사 추출 :", nouns)
print("구 추출 :", sentences)
print("형태소 추출 :", morphs)
print("품사 태깅 :", pos)

5.2.3. NLTK
NLTK(Natural Language Toolkit) : 토큰화, 형태소 분석, 구문 분석, 개체명 인식, 감성 분석 들과 같은 기능을 제공
- 영문 토큰화
from nltk import tokenize
sentence = "Those who can imagine anything, can create the impossible."
word_tokens = tokenize.word_tokenize(sentence)
sent_tokens = tokenize.sent_tokenize(sentence)
print(word_tokens)
print(sent_tokens)

- 영문 품사 태깅
from nltk import tag
from nltk import tokenize
sentence = "Those who can imagine anything, can create the impossible."
word_tokens = tokenize.word_tokenize(sentence)
pos = tag.pos_tag(word_tokens)
print(word_tokens)
print(pos)

5.2.4. spaCy
spaCy : Cython 기반으로 개발된 오픈 소스 라이브러리
- spaCy 품사 태깅
import spacy
nlp = spacy.load("en_core_web_sm")
sentence = "Those who can imagine anything, can create the impossible."
doc = nlp(sentence)
for token in doc:
print(f"[{token.pos_:5}-{token.tag_:3}]:{token.text}")

NLTK : 학습 목적으로 자연어 처리에 대한 알고리즘과 예제 제공
spaCy : 효율적인 처리 속도와 높은 정확도 제공 (모델이 더 크고 복잡하며 많은 리소스 요구)
5.3. 하위 단어 토큰화
현대 자연어 처리에서는 신조어의 발생, 오탈자, 축약어 등을 고려해야 하기 때문에 분석할 단어의 양이 많아져 어려움을 겪는다. 이를 해결하기 위한 방법 중 하나로 **하위 단어 토큰화(Subword Tokenization)**가 있다.
하위 단어 토큰화 : 하나의 단어가 빈번하게 사용되는 **하위 단어(Subword)**의 조합으로 나누어 토큰화하는 방법
5.3.1. 바이트 페어 인코딩
바이트 페어 인코딩(Byte Pair Encoding, BPE) : 다이그램 코딩이라고도 하며 하위 단어 토큰화의 한 종류
연속된 글자 쌍이 더 이상 나타나지 않거나 정해진 어휘 사전 크기에 도달할 때까지 조합 탐지와 부호화를 반복 → 자주 등장하는 단어는 하나의 토큰으로 토큰화, 덜 등장하는 단어는 여러 토큰의 조합으로 표현
- 예를 들어 다음과 같은 빈도 사전과 어휘 사전이 있다고 하자.
- 빈도 사전 : (’low’, 5), (’lower’, 2), (’newest’, 6), (’widest’, 3)
- 어휘 사전 : [’low’, ‘lower’, ’newest’, ‘widest’]
- 빈도 사전을 바이트 페어 인코딩으로 재구성하기 위해 빈도 사전 내 모든 단어를 글자 단위로 나눈다
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’e’, ’s’, ’t’, 6), (’w’, ’i’, ’d’, ’e’, ’s’, ’t’, 3)
- 어휘 사전 : [’d’ ,’e’, ’i’, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’]
- 빈도 사전에서 ‘e’, ’s’ 쌍이 ‘newest’에서 6번, ‘widest’에서 3번 등장해 총 9번으로 가장 많이 등장했으므로 빈도사전에서 ‘es’로 병합하고 어휘 사전에 ‘es’를 추가한다.
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’es’, ’t’, 6), (’w’, ’i’, ’d’, ’es’, ’t’, 3)
- 어휘 사전 : [’d’, ’e’, ’i, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’, ’es’]
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’es’, ’t’, 6), (’w’, ’i’, ’d’, ’es’, ’t’, 3)
- 어휘 사전 : [’d’, ’e’, ’i, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’, ’es’, ‘est’]
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’es’, ’t’, 6), (’w’, ’i’, ’d’, ’es’, ’t’, 3)
- 어휘 사전 : [’d’, ’e’, ’i, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’, ’es’, ‘est’, ’lo’, ’low’, ’ne’, ’new’, ’newest’, ’wi’, ’wid’, ’widest’]
- 센텐스피스
- 센텐스피스는 구글에서 개발한 오픈소스 하위 단어 토크나이저 라이브러리이며, 바이트 페어 인코딩과 유사한 알고리즘을 사용해 입력 데이터를 토큰화하고 단어 사전을 생성한다.
- 코포라는 국립국어원이나 AI hub에서 제공하는 말뭉치 데이터를 쉽게 사용할 수 있게 제공하는 오픈소스 라이브러리이다.
- 토크나이저 모델 학습
1. 청와대 청원 데이터 다운로드
from Korpora import Korpora
corpus = Korpora.load("korean_petitions")
dataset = corpus.train #train을 하는 게 아니라 train set를 불러온 것
petition = dataset[0]
print("청원 시작일 :", petition.begin)
print("청원 종료일 :", petition.end)
print("청원 동의 수 :", petition.num_agree)
print("청원 범주 :", petition.category)
print("청원 제목 :", petition.title)
print("청원 본문 :", petition.text[:30])

2. 학습 데이터 세트 생성
from Korpora import Korpora
corpus = Korpora.load("korean_petitions")
petitions = corpus.get_all_texts()
with open("../datasets/corpus.txt", "w", encoding="utf-8") as f:
for petition in petitions:
f.write(petition + "\n")
get_all_texts 메서드로 본문 데이터세트를 한 번에 불러올 수 있다.
3. 토크나이저 모델 학습
from sentencepiece import SentencePieceTrainer
SentencePieceTrainer.Train(
"--input=../datasets/corpus.txt\ #말뭉치 텍스트 파일의 경로
--model_prefix=petition_bpe\ #모델 파일 이름
--vocab_size=8000 model_type=bpe" #어휘 사전 크기, 토크나이저 알고리즘
)
토크나이저 모델 학습토크나이저 모델 학습이 완료되면 petition_bpe.model과 petition_bpe.vocab 파일이 생성된다. model 파일은 학습된 토크나이저가, vocab 파일은 어휘 사전이 저장된 파일이다.
4. 바이트 페어 인코딩 토큰화
from sentencepiece import SentencePieceProcessor
tokenizer = SentencePieceProcessor()
tokenizer.load("petition_bpe.model")
sentence = "안녕하세요, 토그나이저가 잘 학습되었군요!"
sentences = ["이렇게 입력값을 리스트로 받아서","쉽게 토크나이저를 사용할 수 있답니다"]
tokenized_sentence = tokenizer.encode_as_pieces(sentence)
tokenized_sentences = tokenizer.encode_as_pieces(sentences)
print("단일 문장 토큰화 : ",tokenized_sentence)
print("다중 문장 토큰화 : ",tokenized_sentences)
ecodeded_sentence=tokenizer.encode_as_ids(sentence)
encoded_sentences=tokenizer.encode_as_ids(sentences)
print("단일 문장 인코딩 : ",ecodeded_sentence)
print("다중 문장 인코딩 : ",encoded_sentences)
decode_ids=tokenizer.decode_ids(ecodeded_sentence)
decode_pieces=tokenizer.decode_ids(encoded_sentences)
print("정수 인코딩에서 문장 변환 : ",decode_ids)
print("하위 단어 토큰에서 문장 변환 : ",decode_pieces)

SentencePieceProcessor 클래스로 토큰화를 수행한다.
tokenizer.load 메서드를 통해 petition_bpe.model 모델을 불러온다.
encode_as_pieces 메서드는 문장을 토큰화, encode_as_ids 메서드는 토큰을 정수로 인코딩
decode_ids 메서드와 decode_pieces 메서드를 통해 문자열 데이터로 변환 가능
5. 어휘 사전을 불러와 딕셔너리 형태로 매핑
from sentencepiece import SentencePieceProcessor
tokenizer = SentencePieceProcessor()
tokenizer.load("petition_bpe.model")
vocab = {idx: tokenizer.id_to_piece(idx) for idx in range(tokenizer.get_piece_size())}
print(list(vocab.items())[:5])
print("vocab size :", len(vocab))

get_piece_size 메서드는 센텐스피스 모델에서 생성된 하위 단어의 개수를 반환
id_to_piece 메서드는 정숫값을 하위 단어로 변환
5.3.2. 워드피스
워드피스(Workpiece) 토크나이저 : 바이트 페어 인코딩 토크나이저와 유사한 방법으로 학습되지만, 빈도 기반이 아닌 확률 기반으로 글자 쌍을 병합
모델이 새로운 하위 단어를 생성할 때 이전 하위 단어와 함께 나타날 확률을 계산하는 방식이다.
- 각 글자 쌍에 대한 점수$f$ : 빈도를 나타내는 함수, $x$, $y$ : 병합하려는 하위 단어, $f(x,y)$ : $xy$글자쌍의 빈도 → score : $x$와 $y$를 병합하는 것이 적절한지를 판단하기 위한 점수
- 예를 들어 다음과 같은 빈도 사전과 어휘 사전이 있다고 하자.
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’e’, ’s’, ’t’, 6), (’w’, ’i’, ’d’, ’e’, ’s’, ’t’, 3)
- 어휘 사전 : [’d’ ,’e’, ’i’, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’]
- 가장 빈번하게 등장한 쌍은 9번 등장한 ‘e’, ‘s’ → ‘e’는 17번, ’s’는 9번 등장 → 점수 : 0.06 ‘i’와 ‘d’쌍은 3번씩 등장 → 점수 : 0.33 따라서, ‘e’, ‘s’ 쌍 대신 ‘i’와 ’d’쌍을 병합
- 빈도 사전 : (’l’, ’o’, ’w’, 5), (’l’, ’o’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’e’, ’s’, ’t’, 6), (’w’, ’id’, ’e’, ’s’, ’t’, 3)
- 어휘 사전 : [’d’ ,’e’, ’i’, ’l’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’, ‘id’]
- 동일한 과정을 반복
- 빈도 사전: (’lo’, ’w’, 5), (’lo’, ’w’, ’e’, ’r’, 2), (’n’, ’e’, ’w’, ’e’, ’s’, ’t’, 6), (’w’, ’id’, ’e’, ’s’, ’t’, 3)
- 어휘 사전: [’d’, ’e’, ’i’, ’i’, ’n’, ’o’, ’r’, ’s’, ’t’, ’w’, ’id’, ’lo’]
- 위 과정을 반복해 연속된 글자 쌍이 더 이상 나타나지 않거나 정해진 어휘 사전 크기에 도달할 때까지 학습한다.
- 예를 들어 다음과 같은 빈도 사전과 어휘 사전이 있다고 하자.
- $$ score={f(x,y)\over{f(x),f(y)}} $$
- 토크나이저스(Tokenizers) : 토크나이저스 라이브러리는 정규화(Normalization), **사전 토큰화(Pre-tokenization)**를 제공 정규화는 일관된 형식으로 텍스트를 표준화하고 모호한 경우를 방지하기 위해 일부 문자를 대체 혹은 제거의 작업을 수행, 사전 토큰화는 입력 문장을 토큰화하기 전에 단어와 같은 작은 단위로 나누는 기능을 제공
- 워드피스 토크나이저 학습
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.normalizers import Sequence, NFD, Lowercase
from tokenizers.pre_tokenizers import Whitespace
tokenizer = Tokenizer(WordPiece())
tokenizer.normalizer = Sequence([NFD(), Lowercase()])
tokenizer.pre_tokenizer = Whitespace()
tokenizer.train(["../corpus.txt"])
tokenizer.save("../petition_wordpiece.json")
토크나이저스 라이브러리의 토크나이저(Tokenizer)로 워드피스(WordPiece) 모델을 불러오고, 정규화 방식과 사전 토큰화 방식을 설정
정규화 방식은 nomarlizers 모듈에 포함된 클래스를 불러와 시퀀스 형식으로 인스턴스를 전달 (NFD : 유니코드 정규화 , Lowercase : 소문자 변환)
사전 토큰화 방식은 pre_tokenizers 모듈에 포함된 클래스를 불러와 적용
train 메서드를 통해 학습에 사용하려는 데이터 경로를 전달하고 save 메서드로 학습결과 저장
- 워드피스 토큰화
from tokenizers import Tokenizer
from tokenizers.decoders import WordPiece as WordPieceDecoder
tokenizer=Tokenizer.from_file("../datasets/models/petition_wordpiece.json")
tokenizer.decoder=WordPieceDecoder()
sentence = "안녕하세요. 워드피스 토크나이저입니다."
sentences=["이렇게 입력값을 리스트로 받아서", "쉽게 토크나이저를 사용할 수 있답니다"]
encoded_sentence=tokenizer.encode(sentence)
encoded_sentences=tokenizer.encode_batch(sentences)
print("인코터 형식: ", type(encoded_sentence))
print("단일 문장 토큰화 : ",encoded_sentence.tokens)
print("여러 문장 토큰화 : ", [enc.tokens for enc in encoded_sentences])
print("단일 문장 정수 인코딩 : ",encoded_sentence.ids)
print("여러 문장 정수 인코딩 : ", [enc.ids for enc in encoded_sentences])
#ids : 토큰 정수
print("정수 인코딩에서 문장 변환: ", tokenizer.decode(encoded_sentence.ids))

모델 결과가 저장된 petition_wordpiece.json 파일을 불러와 Tokenizer 객체 생성
WordPieceDecoder() 를 사용해 Tokenizer의 디코더를 워드피스 디코더로 설정
encode 메서드로 문장을 토큰화, encode_batch 메서드로 여러 문장을 한꺼번에 토큰화 가능
토큰화된 데이터는 tokens 속성을 통해 값을 확인할 수 있으며, ids 속성으로 ID 출력 가능
정수를 다시 문장으로 변환하는 경우 decode 메서드로 출력 가능

'ML,DL' 카테고리의 다른 글
| [파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습] chapter4 파이토치 심화 (0) | 2026.03.12 |
|---|---|
| [파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습] chapter3 파이토치 기초 (0) | 2026.03.12 |
| RNN, LSTM 이해하기 (PyTorch로 구현한 코드 포함) (0) | 2026.03.11 |
| [바닥부터 배우는 강화학습] Chapter 7 Deep RL 첫 걸음 (0) | 2026.03.11 |
| [바닥부터 배우는 강화학습] Chapter 6 MDP를 모를 때 최고의 정책 찾기 (0) | 2026.03.10 |