어제까지 공부했던 CV 파트를 마무리하고, 이제부터 NLP 파트를 공부할 예정이다.
오늘은 그 중에서도 토큰화(Tokenization)에 대해 공부했다.
오늘 사용할 코드: https://github.com/choiwonjini/AIFFEL_practice/blob/main/NLP/1_Tokenization.ipynb
AIFFEL_practice/NLP/1_Tokenization.ipynb at main · choiwonjini/AIFFEL_practice
Contribute to choiwonjini/AIFFEL_practice development by creating an account on GitHub.
github.com
1. 토큰화(Tokenization)
자연어 처리(NLP)는 인공지능의 하위 분야 중 하나로 컴퓨터가 인간과 유사한 방식으로 인간의 언어를 이해하고 처리하는 것이 주요 목표 중 하나이며, 인간 언어의 구조, 의미, 맥락을 분석하고 이해할 수 있는 알고리즘과 모델을 개발한다.
이러한 모델을 개발하기 위해서는 다음과 같은 문제가 해결되어야 한다.
1. 모호성(Ambiguity)
인간의 언어는 단어와 구가 사용되는 맥락에 따라 여러 의미를 갖게 되어 모호한 경우가 많다. 알고리즘은 이러한 다양한 의미를 이해하고 명확히 구분할 수 있어야 한다.
2. 가변성(Variablility)
인간의 언어는 다양한 사투리, 강세, 신조어, 작문 스타일로 인해 매우 가변적이다. 알고리즘은 이러한 가변성을 처리할 수 있어야 한다.
3. 구조(Structure)
인간의 언어는 문장이나 구의 의미를 이해할 때 구문(Syntactic)을 파악하여 의미(Semantic)를 해석한다. 알고리즘은 문장의 구조와 문법적 요소를 이해하여 의미를 추론하거나 분석할 수 있어야 한다.
위와 같은 문제를 이해하고 구분할 수 있는 모델을 만들려면 우선 말뭉치(Corpus)를 일정한 단위인 토큰(Token)으로 나눠야 한다.
말뭉치(Corpus)란 자연어 모델을 훈련하고 평가하는 데 사용되는 대규모의 자연어를 뜻한다.
토큰(Token)이란 개별 단어와 문장 부호와 같은 텍스트를 의미하며 텍스트의 개별 단어, 구두점 또는 기타 의미 단위일 수 있다.
이 말뭉치를 토큰으로 나누는 과정이 바로 토큰화(Tokenization)이며, 토크나이저(Tokenizer)를 사용한다.
토크나이저를 구축하는 방법은 아래와 같다.
- 공백 분할: 텍스트를 공백 단위로 분리해 개별 단어로 토큰화
- 정규표현식 적용: 정규표현식으로 특정 패턴을 식별해 텍스트를 분할
- 어휘 사전(Vocabulary) 적용: 사전에 정의된 단어 집합을 토큰으로 사용
- ML 활용: 테이터세트를 기반으로 토큰화하는 방법을 학습한 ML을 적용
이 방법 중 어휘 사전(Vocab)은 직접 어휘 사전을 구축하기 때문에 없는 단어나 토큰이 존재할 수 있는데, 이러한 토큰을 OOV(Out Of Vocab)라고 한다. 따라서 이 방법은 OOV 문제를 잘 고려해야 한다.
큰 어휘 사전을 구축하면 학습 비용의 증가하고, 어휘 사전에 포함된 토큰 개수만큼 차원이 증가하기 때문에 차원의 저주(Curse of Dimensionality)에 빠질 수 있다.
2. 단어 및 글자 토큰화
입력된 텍스트 데이터를 단어(Word)나 글자(Character) 단위로 나누는 기법으로는 단어 토큰화와 글자 토큰화가 있는데, 이런 기법을 통해 각각의 토큰은 의미를 갖는 최소 단위로 분해된다.
2.1. 단어 토큰화 (Word Tokenization)
단어 토큰화는 NLP 분야에서 핵심적인 전처리 작업 중 하나로 텍스트 데이터를 의미 있는 단위인 단어로 분리하는 작업이다.
보통 띄어쓰기, 문장 부호, 대소문자 등의 특정 구분자를 활용해 토큰화가 수행되고, 품사 태깅, 개체명 인식, 기계 번역 등에서 사용되는 가장 일반적인 토큰화 방법이다.
# 단어 토큰화
review = " 현실과 구분 불가능한 cg. 시각적 즐거음은 최고! 더불어 ost는 더더욱 최고!!"
tokenized = review.split()
print(tokenized)
['현실과', '구분', '불가능한', 'cg.', '시각적', '즐거음은', '최고!', '더불어', 'ost는', '더더욱', '최고!!']
위의 결과를 보면 '최고!', '최고!!' 두 토큰이 다른 토큰으로 나뉘는 것을 알 수 있다.
마찬가지로 'cg.', 'cg' 이 두 토큰도 다른 토큰으로 나뉠 것이므로 'cg'는 OOV가 된다.
이처럼 단어 토큰화는 한국어 접사, 문장 부호, 오타 or 띄어쓰기 오류에 취약하다는 단점을 갖는다.
2.2. 글자 토큰화 (Character Tokenization)
글자 토큰화는 띄어쓰기뿐만 아니라 글자 단위로 문장을 나누는 방식으로, 아래와 같은 장점이 있다.
- 비교적 작은 단위의 사전을 구축할 수 있다.
- → 작은 단어 사전을 사용하면 학습 시 컴퓨터 자원을 아낄 수 있다.
- → 전체 말뭉치를 학습할 때 각 단어를 더 자주 학습할 수 있다.
review = "현실과 구분 불가능한 cg. 시각적 즐거음은 최고! 더불어 ost는 더더욱 최고!!"
tokenized = list(review)
print(tokenized)
['현', '실', '과', ' ', '구', '분', ' ', '불', '가', '능', '한', ' ', 'c', 'g', '.', ' ', '시', '각', '적', ' ', '즐', '거', '음', '은', ' ', '최', '고', '!', ' ', '더', '불', '어', ' ', 'o', 's', 't', '는', ' ', '더', '더', '욱', ' ', '최', '고', '!', '!']
위의 결과를 보면, 공백도 토큰에 포함되고, 영어는 알파벳 단위로 나뉜다.
그런데 한글은 하나의 토큰이 자음과 모음의 조합으로 이루어진 것을 알 수 있다.
자소 단위로 나눠서 자소 단위 토큰화를 진행해보자.
한글 음절 분해 및 합성 라이브러리인 자모(jamo)를 활용한다.
컴퓨터가 한글을 인코딩하는 방식은 크게 조합형과 완성형으로 나눌 수 있다.
- 조합형: 글자를 자모 단위로 나눠 인코딩한 뒤, 이를 조합해 한글을 표현한다.
- 완성형: 조합된 글자 자체에 값을 부여해 인코딩하는 방식.
아래 코드의 h2j 함수는 완성형으로 입력된 한글을 조합형 한글로 변환한다.
그리고 j2hcj 함수는 조합형 한글 문자열을 자소 단위로 나눠 반환하는 함수이다.
from jamo import h2j, j2hcj
review = "현실과 구분 불가능한 cg. 시각적 즐거음은 최고! 더불어 ost는 더더욱 최고!!"
decomposed = j2hcj(h2j(review))
tokenized = list(decomposed)
print(tokenized)
['ㅎ', 'ㅕ', 'ㄴ', 'ㅅ', 'ㅣ', 'ㄹ', 'ㄱ', 'ㅘ', ' ', 'ㄱ', 'ㅜ', 'ㅂ', 'ㅜ', 'ㄴ', ' ', 'ㅂ', 'ㅜ', 'ㄹ', 'ㄱ', 'ㅏ', 'ㄴ', 'ㅡ', 'ㅇ', 'ㅎ', 'ㅏ', 'ㄴ', ' ', 'c', 'g', '.', ' ', 'ㅅ', 'ㅣ', 'ㄱ', 'ㅏ', 'ㄱ', 'ㅈ', 'ㅓ', 'ㄱ', ' ', 'ㅈ', 'ㅡ', 'ㄹ', 'ㄱ', 'ㅓ', 'ㅇ', 'ㅡ', 'ㅁ', 'ㅇ', 'ㅡ', 'ㄴ', ' ', 'ㅊ', 'ㅚ', 'ㄱ', 'ㅗ', '!', ' ', 'ㄷ', 'ㅓ', 'ㅂ', 'ㅜ', 'ㄹ', 'ㅇ', 'ㅓ', ' ', 'o', 's', 't', 'ㄴ', 'ㅡ', 'ㄴ', ' ', 'ㄷ', 'ㅓ', 'ㄷ', 'ㅓ', 'ㅇ', 'ㅜ', 'ㄱ', ' ', 'ㅊ', 'ㅚ', 'ㄱ', 'ㅗ', '!', '!']
이렇게 하면 단어 토큰화의 단점이었던 'cg도', 'cg는', 'cg.' 등에서도 '도', '는', '.'과 같은 접사와 문장 부호의 의미를 학습할 수 있고, 따라서 OOV도 줄어든다.
하지만 글자 토큰화에도 단점이 있다.
개별 토큰은 아무런 의미가 없으므로 자연어 모델이 각 토큰의 의미를 조합해 결과를 도출해야 하는데, 토큰 조합 방식을 사용해 문장 생성이나 개체명 인식(Name Entity Recognition) 등을 구현할 경우, 다의어나 동음이의어가 많은 도메인에서 구별하는 것이 어려울 수 있다.
또한, 모델 입력 시퀀스(sequence)의 길이가 길어질수록 연산량도 증가한다.
3. 형태소 토큰화 (Morpheme Tokenization)
형태소는 자립 형태소와 의존 형태소로 나눌 수 있다.
자립 형태소는 '하늘', '나', '공부'처럼 혼자 쓰일 수 있는 단위이고,
의존 형태소는 '-은/는', '-을/를', '-다'처럼 다른 말에 붙어야만 쓰일 수 있는 단위이다.
형태소 토큰화란 문장을 의미를 가진 가장 작은 단위인 '형태소'로 나누는 작업이며,
주로 한국어와 일본어처럼 조사나 어미가 단어에 붙는 교착어의 NLP에서 필수적인 전처리 과정이다.
왜 교착어의 NLP에서 필수적일까?
영어와 한국어의 차이를 알아보자.
"I study NLP." → ["I", "study", "NLP", "."]
영어의 경우 띄어쓰기만으로도 단어 구분이 잘 된다.
그럼 한국어는?
"나는 NLP를 공부한다." → ["나는", "NLP를", "공부한다."]
이렇게 되면 컴퓨터는 ('나는', '내가') 또는 ('공부한다', '공부했다')를 서로 다른 단어로 인식한다.
그럼 어떻게 나누냐?
"나는 NLP를 공부한다" → ["는", "NLP", "을", "공부", "하", "ㄴ다", "."]
이렇게 나누면 "나"라는 주어와 "공부"라는 어근(핵심 의미)를 추출할 수 있게 된다.
또한, "공부한다", "공부했다" 등의 경우 모두 "공부"라는 공통된 형태소를 가지므로, 컴퓨터가 이 단어들의 기본 의미가 같다는 것을 파악하기 쉬워진다.
따라서 한국어와 같은 교착어에서는 형태소 토큰화가 필수적이다.
3.1. 형태소 어휘 사전
형태소 어휘 사전(Morpheme Vocabulary)은 말뭉치에 등장하는 모든 고유한 형태소들을 모아놓은 어휘 사전이며, 각 형태소에 고유한 index를 1:1로 매핑해주는 기준이 된다.
구축 과정
1. 학습시킬 데이터 (말뭉치, Corpus):
"나는 밥을 먹는다."
"너는 빵을 먹었다."
2. 형태소 토큰화:
["나", "는", "밥", "을", "먹", "는다"]
["너", "는", "빵", "을", "먹", "었다"]
2. 어휘 사전 구축 (고유한 형태소 추출):
데이터에 등장한 모든 형태소를 중복 없이 모은다.
{"나", "는", "밥", "을", "먹", "는다", "너", "빵", "었다"}
3. 특수 토큰 추가 및 인덱스 부여:
문장 길이를 맞추기 위한 <PAD>나 사전에 없는 단어를 처리할 <UNK> (Unknown) 같은 특수 토큰을 추가한 다음,
각 형태소에 고유한 index를 부여한다. (보통 빈도순으로 정렬)
완성된 형태소 어휘 사전:
{"<PAD>": 0}
{"<UNK>": 1}
{"을": 2}
{"는": 3}
{"먹": 4}
{"나": 5}
{"밥": 6}
{"는다": 7}
{"너": 8}
{"빵": 9}
{"었다": 10}
일반적으로 형태소 어휘 사전에는 각 형태소가 어떤 품사에 속하는지와 해당 품사의 뜻 등의 정보도 함께 제공된다.
이처럼 각 형태소마다 품사를 태깅하는 작업을 진행하는 작업을 품사 태깅(POS Tagging)이라고 한다.
품사 태깅이 완료된 형태소 어휘 사전은 아래와 같이 구성된다. (Mecab기준)
{
"<PAD>": 0, /* 특수 토큰 (패딩) */
"<UNK>": 1, /* 특수 토큰 (알 수 없음) */
"는/JX": 2, /* 빈도수 2 */
"을/JKO": 3, /* 빈도수 2 */
"먹/VV": 4, /* 빈도수 2 */
"./SF": 5, /* 빈도수 2 */
"나/NP": 6, /* 빈도수 1 */
"밥/NNG": 7, /* 빈도수 1 */
"는다/EFN": 8, /* 빈도수 1 */
"너/NP": 9, /* 빈도수 1 */
"빵/NNG": 10, /* 빈도수 1 */
"었다/EP": 11 /* 빈도수 1 */
}
NP=대명사, JX=보조사, NNG=일반명사, JKO=목적격조사, VV=동사, EFN=평서형종결어미, EP=선어말어미
3.2. KoNLPy, NLTK, spaCy 라이브러리를 활용한 형태소 단위 토큰화
3.2.1 KoNLPy
한국어 NLP를 위한 형태소 분석기 모음집(Wrapper)이며, 명사 추출, 형태소 분석, 품사 태깅 등의 기능을 제공한다.
Mecab, Okt, Kkma(꼬꼬마) 등 다양한 형태소 분석기를 지원한다.
이번 포스팅에서는 SNS 텍스트 데이터를 기반으로 개발된 Okt와 국립국어원에서 배포한 세종 말뭉치를 기반으로 학습된 꼬꼬마를 사용할 것이다.
(1) 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)
명사 추출: ['무엇', '상상', '수', '사람', '무엇', '낼', '수']
구 추출 : ['무엇', '상상', '상상할 수', '상상할 수 있는 사람', '사람']
형태소 추출 : ['무엇', '이든', '상상', '할', '수', '있는', '사람', '은', '무엇', '이든', '만들어', '낼', '수', '있다', '.']
품사 태깅 : [('무엇', 'Noun'), ('이든', 'Josa'), ('상상', 'Noun'), ('할', 'Verb'), ('수', 'Noun'), ('있는', 'Adjective'), ('사람', 'Noun'), ('은', 'Josa'), ('무엇', 'Noun'), ('이든', 'Josa'), ('만들어', 'Verb'), ('낼', 'Noun'), ('수', 'Noun'), ('있다', 'Adjective'), ('.', 'Punctuation')]
Okt 객체는 문장을 입력받아 명사, 구, 형태소, 품사 등의 정보를 추출하는 여러 가지 메서드를 제공한다.
위의 네 가지 메서드가 Okt가 지원하는 대표적인 메서드이다.
명사 추출, 구문 추출, 형태소 추출은 input에서 각각 명사, 어절 구 단위, 형태소만 추출해 리스트를 반환한다.
품사 태깅은 input에서 각 단어에 대한 품사 정보를 추출하여 (형태소, 품사) 형태의 튜플로 구성된 리스트를 반환한다.
(2) 꼬꼬마
from konlpy.tag import Kkma
kkma = Kkma()
sentence = "무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다."
nouns = kkma.nouns(sentence)
sentences = kkma.sentences(sentence) # Kkma 객체는 구문 추출 기능 대신 문장 추출 기능을 제공한다.
morphs = kkma.morphs(sentence)
pos = kkma.pos(sentence)
print("명사 추출 :", nouns)
print("문장 추출 :", sentences)
print("형태소 추출 :", morphs)
print("품사 태깅 :", pos)
명사 추출 : ['무엇', '상상', '수', '사람', '무엇']
문장 추출 : ['무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다.']
형태소 추출 : ['무엇', '이', '든', '상상', '하', 'ㄹ', '수', '있', '는', '사람', '은', '무엇', '이', '든', '만들', '어', '내', 'ㄹ', '수', '있', '다', '.']
품사 태깅 : [('무엇', 'NNG'), ('이', 'VCP'), ('든', 'ECE'), ('상상', 'NNG'), ('하', 'XSV'), ('ㄹ', 'ETD'), ('수', 'NNB'), ('있', 'VV'), ('는', 'ETD'), ('사람', 'NNG'), ('은', 'JX'), ('무엇', 'NP'), ('이', 'VCP'), ('든', 'ECE'), ('만들', 'VV'), ('어', 'ECD'), ('내', 'VXV'), ('ㄹ', 'ETD'), ('수', 'NNB'), ('있', 'VV'), ('다', 'EFN'), ('.', 'SF')]
둘을 비교해보면, 동일한 메서드라도 분석기의 특징에 따라 다른 결과가 나타난다.
아래는 Okt와 다른 꼬꼬마의 차이점이다.
- 명사 추출: '낼', '수'를 반환하지 않음
- 형태소 추출: '할'을 더 분리해 '하', 'ㄹ'로 반환
- 품사 태깅: Okt는 15개, 꼬꼬마는 56개의 품사를 반환
태깅하는 품사의 수가 많으면 더 자세한 단위로 분석이 가능하지만, 품사 태깅에 소요되는 시간이 길어지며, 더 많은 품사로 분리해 모델의 성능이 저하될 수 있다는 단점이 있다.
그러므로 목적에 맞는 분석기를 잘 선택해야 할 것이다.
3.2.2. NLTK
NLP 교육 및 연구를 위한 가장 전통적이고 포괄적인 라이브러리이며, NLP와 관련된 거의 모든 알고리즘과 기능을 제공한다.
하지만 기능이 너무 많아 복잡하고, spaCy에 비해 성능이 매우 느려서 현업 서비스에는 잘 쓰이지 않으며, 한국어 지원이 거의 없다.
NLTK를 활용해 토큰화나 품사 태깅을 하기 위해서는 해당 작업을 할 수 있는 패키지나 모델을 다운로드 해야한다.
이번 포스팅에서는 Punkt 모델과 Averaged Perception Tagger 모델을 활용할 것이다.
(1) 패키지 및 모델 다운로드
import nltk
nltk.download('punkt')
nltk.download('averaged_perception_tagger')
nltk.download('punkt_tab')
(2) 영문 토큰화
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)
['Those', 'who', 'can', 'imagine', 'anything', ',', 'can', 'create', 'the', 'impossible', '.']
['Those who can imagine anything, can create the impossible.']
단어 토크나이저(word_tokenize)는 공백을 기준으로 단어를 분리하고, 구두점 등을 처리해 각각의 단어(token)를 추출해 리스트로 반환한다.
문장 토크나이저(sent_tokenize)는 마침표, 느낌표, 물음표 등의 구두점을 기준으로 문장을 분리해 리스트로 반환한다.
영어는 한국어보다 토큰화하기 쉬운 구조이기 때문에 이렇게 간단하게 단어나 문장으로 토큰화를 할 수 있다.
(3) 영문 품사 태깅
from nltk import tag
pos = tag.pos_tag(word_tokens)
print(pos)
[('Those', 'DT'), ('who', 'WP'), ('can', 'MD'), ('imagine', 'VB'), ('anything', 'NN'), (',', ','), ('can', 'MD'), ('create', 'VB'), ('the', 'DT'), ('impossible', 'JJ'), ('.', '.')]
토큰화된 단어들을 pos_tag 함수에 넣으면 품사 태깅을 할 수 있다.
3.2.3. spaCy
현업(Production)을 위한 고성능 NLP 라이브러리이며, 빠르고 정확한 파이프라인(Pipeline)을 제공한다. (토큰화 → 품사 태깅 → 개체명 인식 → 의존성 분석 등이 물 흐르듯 처리됨)
NLTK보다 훨씬 빠르고, 최신 모델들을 내장하고 있으며 사용법이 직관적이고 딥러닝 모델(Pytorch, TF 등)과 통합이 쉽다.
또한, 최근 ko_core_news_sm 같은 한국어 모델도 지원하기 시작했다.
(1) spaCy 설치
pip install spacy
python -m spacy download en_core_web_sm
(2) 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}")
[PRON - DT ] : Those
[PRON - WP ] : who
[AUX - MD ] : can
[VERB - VB ] : imagine
[PRON - NN ] : anything
[PUNCT - , ] : ,
[AUX - MD ] : can
[VERB - VB ] : create
[DET - DT ] : the
[ADJ - JJ ] : impossible
[PUNCT - . ] : .
spaCy는 객체 지향적(Object Oriented)으로 구현돼 처리한 결과를 doc 객체에 저장한다.
doc 객체는 다시 여러 token 객체로 이뤄져 있으며, 이 token 객체에 대한 정보를 기반으로 다양한 NLP 작업을 수행한다.
token 객체에는 기본 품사 속성(pos_), 세분화 품사 속성(tag_), 원본 텍스트 데이터(text), 토큰 사이의 공백을 포함하는 텍스트 데이터(text_with_ws), 벡터(vector), 벡터 노름(vector_norm) 등의 속성이 포함되어 있다.
회고
오늘은 단어 및 글자 토큰화와 KoNLPy, NLTK, spaCy를 활용한 형태소 토큰화 & 품사 태깅을 알아봤다.
그동안 NLP 분야를 공부하고 싶었는데 드디어 오늘 시작했다.
CV를 처음 공부했을 때 보다 재밌는 것 같다.
'AI > NLP' 카테고리의 다른 글
| [10/31] 아이펠 리서치 15기 TIL | Transformer (0) | 2025.11.05 |
|---|---|
| [10/29] 아이펠 리서치 15기 TIL | Attention (0) | 2025.11.01 |
| [10/26] 아이펠 리서치 15기 TIL | RNN & LSTM (1) | 2025.10.26 |
| [10/25] 아이펠 리서치 15기 TIL | 임베딩(Embedding) (1) | 2025.10.25 |
| [10/24] 아이펠 리서치 15기 TIL | 토큰화 (Tokenization): Subword Tokenization (0) | 2025.10.25 |