Skip to main content

토큰화

토큰화

토큰화는 텍스트를 분석 단위로 끊는 과정이다. 이때 끊어진 하나하나를 토큰이라고 부른다. 토큰은 분석의 단위가 되며, 이는 언어마다 다르다.

영어는 보통 단어 단위로 자른다. 단어는 혼자 쓸 수 있는 단위를 말한다. 예를 들어, "The Apple Went Bad"는 빈칸 단위로 쉽게 자를 수 있다.

한국어는 형태소 단위로 자른다. 형태소는 의미가 있는 가장 작은 단위를 말한다. 형태소는 붙여 쓸 수도 있고 띄어 쓸 수도 있는데, 이는 맞춤법 규칙에 따라 달라진다.

일부 형태소는 의존 형태소라고 하는데, 이는 의미는 있지만 혼자 쓰지 못하는 형태소를 말한다. 예를 들어, '었'은 과거를 나타내지만 혼자 쓰지 못하고 '먹었다'와 같이 다른 단어와 붙여서만 쓸 수 있다.

중국어나 일본어 같은 경우, 단어 사이에 빈칸이 없어서 영어처럼 빈칸 단위로 자르는 것이 불가능하다. 이런 경우에는 별도의 단어 분리 과정이 필요하다.

형태소 분석

토큰화는 언어마다 다르기 때문에 각 언어에 맞는 토큰화 방법이 필요하다. 한국어의 경우 형태소로 자르게 되며, 이를 위해 형태소 분석기라는 별도의 소프트웨어나 라이브러리가 필요하다.

형태소 분석과 품사 태깅은 엄밀히 말하면 다르다. 형태소 분석은 단순히 자르는 것이고, 품사 태깅은 자른 후 품사를 붙이는 것이다. 하지만 보통 형태소 분석이라고 하면 품사 태깅까지 포함하는 경우가 많다.

형태소 분석은 규칙 기반과 통계 기반(머신러닝) 두 가지 방식으로 할 수 있다. 규칙 기반은 언어의 규칙을 모두 입력하는 방식이지만, 언어가 계속 변화하기 때문에 완벽하지 않다.

문법은 학교 문법, 규범 문법, 기술 문법으로 나눌 수 있다. 학교 문법은 학교에서 가르치는 것, 규범 문법은 마땅히 써야 한다고 생각하는 것, 기술 문법은 실제로 사용되는 방식을 말한다.

예를 들어, '했었었다'와 같은 표현은 학교 문법에는 맞지 않지만, 실제로 사용되고 있다. '사겼다'와 같은 새로운 발음도 생기고 있다.

이러한 변화 때문에 규칙 기반 방식만으로는 모든 경우를 처리할 수 없다. 따라서 최근의 형태소 분석기는 규칙과 머신러닝을 함께 사용하거나 머신러닝만을 사용하는 경향이 있다.

표제어 추출

영어의 경우 보통 띄어쓰기 단위로 토큰화하지만, 때로는 단어의 원형을 찾아야 할 때가 있다. 그러나 이는 항상 필요한 것은 아니다. 예를 들어, 'may'와 'might', 'island'와 'islands'는 단순히 시제나 단복수의 차이가 아니라 의미가 다를 수 있기 때문이다.

'원형'이라는 용어는 실제로 언어학적 용어가 아니다. 언어학에서는 '표제어'라는 용어를 사용한다. 표제어는 사전에서 굵은 글씨로 쓰인 항목을 말한다.

자연어 처리에서는 '어간 추출'과 '표제어 추출'을 구분한다. 어간 추출은 간단한 규칙(휴리스틱)을 사용하여 단어의 어간을 찾는 방법이다. 예를 들어, 영어에서 '-ed'를 제거하는 방식이다. 그러나 이 방법은 정확하지 않을 수 있다.

표제어 추출은 단어가 사전에 어떻게 등재되어 있는지를 찾는 방법이다. 이 방법은 사전이 필요하며, 최근에는 기계학습을 활용하여 수행한다. 이는 모든 단어가 사전에 등재되어 있지 않기 때문이다.

영어로 표제어는 'lemma'라고 한다.

영어 자연어 처리 라이브러리

영어 자연어 처리를 위한 주요 라이브러리들이 있다. 가장 유명한 것은 NLTK(Natural Language Toolkit)인데, 이는 가장 오래되고 유명한 라이브러리이다. 하지만 오래되었다는 것은 전통적이거나 고전적인 방법론을 사용한다는 의미이기도 하다.

NLTK의 문제점은 한국어를 지원하지 않는다는 것이다. 따라서 NLTK를 중심으로 설명하는 책은 한국어 처리에 큰 도움이 되지 않는다.

CoreNLP는 자바로 만들어진 것을 파이썬에서 사용할 수 있게 한 라이브러리지만, 이 역시 한국어 처리에는 적합하지 않다.

Stanza와 spaCy는 딥러닝 기반의 라이브러리로, 둘 다 한국어를 지원한다. 하지만 이들도 한국어 처리에 있어서는 완벽하지 않다. 그 이유는 딥러닝을 위한 충분한 한국어 데이터가 없기 때문이다.

한국어 형태소 분석을 위해서는 단어를 손으로 분해한 데이터가 필요하다. 이런 데이터를 만들기 위해서는 많은 비용이 들지만, 현재 한국에서는 이에 대한 투자가 충분히 이루어지지 않고 있다. 따라서 데이터의 양이 적어 딥러닝을 통한 한국어 처리의 성능이 좋지 않다.

spacy

spacy를 설치하고 영어 모델을 다운로드하는 과정을 설명한다.

먼저, pip를 사용해 spacy를 설치한다:

pip install spacy

그 다음, 딥러닝 모델을 별도로 다운로드해야 한다. 모델 이름은 'en_core_web_sm'인데, 각 부분의 의미는 다음과 같다:

  • 'en': English(영어)
  • 'core_web': 핵심 웹 모델
  • 'sm': Small(작은 크기)

딥러닝 모델은 크기가 클수록 성능이 좋지만, 다운로드 시간이 오래 걸린다. 모델 크기는 보통 sm(small), md(medium), lg(large) 순으로 커진다. 여기서는 시간 관계상 'sm' 모델을 사용하지만, 실제 사용 시에는 더 큰 모델을 선택할 수 있다.

모델을 다운로드하는 명령어는 다음과 같다:

python -m spacy download en_core_web_sm

spacy를 사용하기 위해 먼저 라이브러리를 임포트한다. 그 다음, 앞서 다운로드한 "en_core_web_sm" 모델을 로드한다.

import spacy
nlp = spacy.load("en_core_web_sm")

분석할 영어 문장을 text 변수에 저장한다. 여기서는 "All work and no play makes Jack a dull boy."라는 문장을 사용했다. 이는 "일만 하고 놀지 않으면 바보가 된다"는 의미의 영어 속담이다.

text = "All work and no play makes Jack a dull boy."

nlp 함수에 text를 입력하여 분석을 수행하고, 그 결과를 doc 변수에 저장한다.

doc = nlp(text)

이 과정에서 주의할 점은 반드시 영어 모델(en으로 시작하는 모델)을 사용해야 한다는 것이다. 그래야 영어 문장을 올바르게 분석할 수 있다.

for token in doc:
print(token.text,
token.lemma_, # 표제어
token.pos_, # 단어의 품사
token.tag_, # 자세한 품사
token.is_stop) # 불용어 여부

doc에 분석 결과가 들어있고, 이를 하나씩 살펴볼 것이다. 파이썬 문법에 익숙하지 않은 사람들을 위해 설명하자면, 'for token in doc:'는 doc에 있는 토큰들을 하나씩 꺼내서 볼 것이라는 의미이다.

각 토큰에 대해 다음 정보를 출력한다:

  1. token.text: 토큰의 텍스트
  2. token.lemma_: 표제어
  3. token.pos_: 품사 (Part of Speech)의 대분류
  4. token.tag_: 품사의 소분류
  5. token.is_stop: 불용어 여부

'print' 함수를 사용해 이 정보들을 출력한다. 'for' 루프 안에 있는 코드는 들여쓰기를 해야 한다.

이 코드를 실행하면 문장의 각 토큰에 대해 위의 정보들이 출력될 것이다.

spacy.explain('PROPN')

각 용어의 의미는 위와 같이 확인할 수 있다

spacy 한국어 처리

모델 설치

python -m spacy download ko_core_news_sm

한국어 문장 처리

import spacy
nlp = spacy.load("ko_core_news_sm")
text = "오늘은 점심으로 뭘 먹을까?"
doc = nlp(text)

품사 태그는 영어로 복잡하게 나오는데, 한국어 처리 시에는 주로 다음과 같이 간단화한다:

명사:

  1. 동작성 명사: '하다'가 붙어 동사가 되는 단어
  2. 상태성 명사: '하다'가 붙어 형용사가 되는 단어
  3. 비서술성 명사: 일반 명사 (예: 꽃)
  4. 고유 명사

용언(동사와 형용사):

  1. 지시동사
  2. 일반 동사
  3. 성상형용사

한국어에서는 동사와 형용사의 구별이 어려운 경우가 많다. 예를 들면, "늙다"는 동사고, "젊다"는 형용사다. 텍스트 분석 시에는 이들을 구분할 필요 없이 용언으로 통합해서 사용한다.

분석에 포함시키는 품사는 주로 위의 명사와 용언 범주에 속하는 것들이다. 조사, 어미, 기호, 접두사, 접미사 등은 대체로 분석에서 제외한다.

이러한 방식으로 텍스트 분석에 필요한 주요 단어들을 선별할 수 있다.

문장 구조 분석

spacy.displacy.render(doc)

이 코드는 spaCy가 제공하는 문장 구조 시각화 기능을 사용한다. 문장을 어떻게 끊어 읽는지, 단어들 간의 관계가 어떤지를 보여준다.

예를 들어, "All work and no play makes Jack a dull boy" 문장에서:

  • "makes"가 동사로 인식되고,
  • "All work and no play"가 주어(nsubj)로 표시된다.
  • "Jack"은 목적어로, "a dull boy"는 보어로 표시될 것이다.

이런 방식으로 문장의 구조를 시각적으로 보여준다. 학교에서 배웠던 '주어 찾기', '동사 찾기' 등의 작업을 컴퓨터가 자동으로 수행하는 것이다.

이 기능은 영어에서 매우 정확하게 작동하지만, 한국어에서는 아직 정확도가 떨어진다.

구성 문법과 의존 문법

구성문법은 현대 언어학에서 주류적인 방법으로, 문장을 큰 단위에서 작은 단위로 쪼개나가는 방식이다. 이 방법은 영어와 같이 어순이 고정된 언어에 적합하지만, 한국어처럼 어순이 자유롭고 생략이 빈번한 언어에는 잘 맞지 않는다.

의존문법은 구성문법보다 더 오래된 방법이다. 이는 문장 전체를 구조로 쪼개는 대신, 단어 간의 의존 관계를 분석한다. 예를 들어, "책을 읽다"에서 "읽다"가 지배소(핵심)이고 "책"이 의존소가 된다. "재미있는 책을 읽는다"에서는 "재미있는"이 "책"에 의존하고, "책"은 "재미있는"을 지배한다.

의존문법은 기원전 5세기까지 거슬러 올라가는 오래된 방법이다. 복잡한 문장에서는 관계가 복잡해져 보기가 어려울 수 있지만, 컴퓨터를 이용한 자연어 처리에서는 오히려 더 유용할 수 있다. 실제 언어 사용에서 나타나는 생략, 반복 등의 현상을 다루기에 더 적합하기 때문이다.

자연어 처리에서는 의존문법을 많이 사용한다. 예를 들어, spaCy로 문장을 분석하면 단어 간의 의존 관계를 화살표로 표시해준다. 화살표가 나가는 쪽이 지배하는 방향이며, 화살표를 받지 않고 오직 내보내기만 하는 단어가 문장 전체를 지배하는 지배소가 된다.

한국어 형태소 분석

한국어 처리를 위해 spaCy 방식을 사용하려면 많은 데이터가 필요하지만, 한국어 데이터가 부족해 성능이 좋지 않다. 따라서 데이터와 규칙을 혼합하여 사용한다. 데이터로 할 수 있는 부분은 데이터로 하고, 한국어 문법 규칙은 코딩으로 구현한다.

KoNLPy, mecab, kiwi 등의 도구들은 완전한 머신러닝 방식이 아닌, 한국어 문법 규칙을 일부 코딩한 것이다. 이 방식으로 80-90% 정도의 경우를 처리할 수 있으며, 규칙으로 해결하기 어려운 부분은 머신러닝으로 처리한다.

예를 들어, "나는 새"라는 문장은 여러 가지로 해석될 수 있다. 이런 애매한 경우는 규칙만으로는 해결하기 어렵지만, 규칙으로 가능한 범위를 한정한 후 머신러닝으로 최종 결정을 내릴 수 있다.

KoNLPy는 인터넷 검색 시 많이 추천되지만, 오래되었고 업데이트가 거의 이루어지지 않고 있다. 또한 자바로 작성되어 파이썬에서 사용하기 번거로운 면이 있다.

mecab은 일본어용으로 만들어진 것을 한국어에 맞게 수정한 것이다. 한국어와 일본어의 유사성 때문에 사용 가능하지만, 완전히 최적화되지는 않았다.

kiwi는 가장 최근에 나온 도구로, 파이썬에서 사용하기 좋다. 이 강의에서는 kiwi를 사용할 예정이다.

kiwi

pip install kiwipiepy

먼저 kiwipiepy를 설치한다.

from kiwipiepy import Kiwi
kiwi = Kiwi()

text = '오늘은 자연어 처리를 배우기 좋은 날이다.'
result = kiwi.tokenize(text)
result

설치가 완료되면, Kiwi를 import 한다. 여기서 'Kiwi'의 'K'는 대문자로 써야 한다.

그 다음, Kiwi() 를 호출하여 형태소 분석기를 초기화한다.

텍스트 변수에 분석하고자 하는 문장을 저장한다. 예시로 "오늘은 자연어 처리를 배우기 좋은 날이다."라는 문장을 사용했다.

kiwi.tokenize(text) 함수를 사용하여 텍스트를 형태소 단위로 분석하고, 그 결과를 result 변수에 저장한다.

마지막으로 result를 출력하면 형태소 분석 결과를 볼 수 있다.

명사 추출 함수

다음은 Kiwi를 이용해 특정 품사(여기서는 명사)만 추출하는 파이썬 함수를 만드는 방법이다:

def extract_nouns(text):
result = kiwi.tokenize(text)
for token in result:
if token.tag in ['NNG', 'NNP']:
yield token.form

이 함수는 다음과 같이 작동한다:

  1. 'def'는 파이썬에서 함수를 정의할 때 사용한다. 'extract_nouns'는 함수의 이름이다.

  2. kiwi.tokenize(text)를 사용해 입력된 텍스트를 형태소 분석한다.

  3. 분석 결과에서 각 토큰을 순회하면서, 토큰의 tag가 'NNG'(일반 명사) 또는 'NNP'(고유 명사)인 경우에만 해당 토큰의 form(실제 단어)를 yield한다.

이 함수는 명사를 추출하지만, 동일한 방식으로 다른 품사(예: 동사, 형용사)를 추출하는 함수도 만들 수 있다. 동사와 형용사도 추출하고 싶으면 VV와 VA를 추가하면 된다.

각 토큰은 form(단어의 형태)과 tag(품사 정보) 등의 속성을 가지고 있다. 원하는 품사의 tag를 지정하면, 해당 품사의 단어만 추출할 수 있다.

list(extract_nouns('어제는 홍차를 마시고, 오늘은 커피를 마셨다.'))

yield

yield 문은 파이썬 함수에서 사용되는 특별한 키워드이다. return과 비슷하지만 중요한 차이가 있다.

return은 함수를 즉시 종료하고 값을 반환한다. 반면 yield는 값을 반환하지만 함수의 실행을 일시 중지하고, 나중에 다시 재개할 수 있게 한다.

예를 들어, 텍스트에서 명사를 추출할 때 yield를 사용하면 각 명사를 찾을 때마다 반환하고 계속해서 다음 명사를 찾을 수 있다. "오늘", "홍차", "어제", "커피" 등을 순차적으로 반환할 수 있다.

yield를 사용하는 함수를 제너레이터(generator)라고 부른다. 제너레이터는 값을 계속 생성해내는 함수이다.

yield를 사용하지 않고 같은 기능을 구현하려면, 빈 리스트를 만들고 결과를 append로 추가한 뒤 최종적으로 리턴하는 방식을 사용할 수 있다. 하지만 yield를 사용하면 코드가 더 간단해진다.

한국어 문서 단어 행렬 만들기

pandas와 sklearn의 CountVectorizer를 import 한다. 그리고 'news_ai.csv' 파일을 읽어 DataFrame으로 저장한다.

Kiwi 객체를 생성하고, '인공지능'을 하나의 단어(고유명사, NNP)로 인식하도록 사용자 단어를 추가한다.

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
df = pd.read_csv('news_ai.csv')
kiwi = Kiwi()
kiwi.add_user_word('인공지능', 'NNP')

CountVectorizer를 설정한다. 최대 단어 수를 100개로 제한하고, 토큰화 방법으로 앞서 정의한 extract_nouns 함수를 사용한다.

cv = CountVectorizer(
max_features=100, # 최대 단어 수(빈도 순)
tokenizer=extract_nouns) # 토큰화 방법

CountVectorizer를 사용하여 문서-단어 행렬(Document-Term Matrix, DTM)을 생성한다.

dtm = cv.fit_transform(df['본문'])

이 과정은 형태소 분석 단계가 포함되어 있어 약간의 시간이 걸릴 수 있다. 실행 결과, 각 뉴스 기사에서 사용된 명사들의 빈도를 나타내는 행렬이 생성된다.

word_count = pd.DataFrame({
'단어': cv.get_feature_names_out(),
'빈도': dtm.sum(axis=0).flat
})
count_dic = dict(zip(word_count.단어, word_count.빈도))

위에서 만들어진 문서 단어 행렬에서 단어별 빈도를 구한다.

# 모양 잡기
mask = Image.open('mask.png') # .convert('L') 없음
mask = np.asarray(mask)
wc = WordCloud(font_path='온글잎 누카.ttf', background_color='white', mask=mask)
wc.fit_words(count_dic)

# 색칠하기
color_func = ImageColorGenerator(mask)
cloud = wc.recolor(color_func=color_func)
cloud.to_image()

한글 단어 구름은 글꼴이 필요하다: 온글잎 누카 글꼴 다운로드