컴퓨터의 언어표현#

컴퓨터는 0,1밖에 모른다. 하지만 빨랐죠 만큼 빠르다. 즉 계산능력은 엄청나다. 이러한 컴퓨터에게 언어라는 것을 이해하게 만들 수 있을까? 이해는 하지 못하더라도 이해하는 것 처럼 보이게 만들 수 있을까?

요즘 핫한 ChatGPT는 그런 것 처럼 보이기 시작했다. 그렇다면 컴퓨터는 언어를 어떻게 이해하는 (것처럼 보이는) 것일까? 그것에 대한 시작으로 컴퓨터에 언어를 어떻게 변환해서 넣는지부터 알아야할 것이다. Token에서부터 시작해보자. Token은 언어 단위를 말한다.

1. what is Token#

언어라는 것이 참으로 어려운 것이 결국 언어란 생각, 의미를 전달하는 매체이다. 그렇다면 의미라는 것을 파악하기 위한 가장 작은 단위는 무엇일까? 동시에 컴퓨터에 input으로 넣어주어야한다. 그것을 규정한 것이 token이다. 그전에 앞서 언어학과에서 엄청 많이 듣는 corpus라는 것에 대해서 알아야 할 것이다.

token to corpus

Corpus는 a collection of text documents 로 정의된다. 예를 들어 news들로 구성된 dataset은 코퍼스라고 할 수 있고, tweet들로 구성된 twitter dataset도 하나의 코퍼스라고 말할 수 있다. 이러한 하나의 코퍼스는 여러 documents로 이루어지고, document는 paragraph(구, 절)로 이루어지고, paragraph는 sentence(문장)으로 이루어지고, 하나의 문장은 token(토큰)으로 이루어진다.

그러면 뭐를 token이라고 부르는가? token은 word, phrase, engram이 될 수 있다. 문장을 해체할 때 어떤 단위로 쪼갤지에 대해서 고민하고, 이 문장 해체 단위를 토큰이라고 말할 수 있다.

token이 n_gram(engram)일 경우#

engram은 group of n words togather로 정의되는 단어 뭉치다. 말 그대로 n개의 단어 뭉치를 하나의 토큰으로 보는 것이다.

예를 들어

I love my phone

이 문장을 볼 때 uni-gram(n=1)로 보면 I, love, my, phone 의 형태로 token이 4개다. Di-gram(n=2)로 보면, I love, love my, my phone으로 3개의 토큰으로 분간된다.

gram

content

token count

n=1(uni-gram)

I,love,my,phone

4

n=2(Di-gram)

I love, love my, my phone

3

tokenization vs vectorization vs embedding#

a process of splitting a text object into smaller units which are also called tokens.

token에 대해서 알았으니 문장과 구를 우리가 정의한 토큰(words, numbers, engrams or symbols)으로 만드는 것이 tokenization일 것이다. 하지만 tokenization만 한다고 해서 바로 컴퓨터에 때려 넣을 수 있는 것이 아니다. 수치화시켜서 넣어줘야하고 torch를 사용할 경우에는 tensor화 까지 시켜줘야한다. 이렇게 텍스트의 수치화를 vectorization이라고 한다. 전통적인 NLP 파이프라인에서는 토큰화 이후에 벡터화 과정이 일반적으로 이루어졌다. 그런데 뒤에서 설명하겠지만 one-hot encoding, TF-IDF, Count Vectorizer는 단어의 출현횟수에 가중치를 더하고 어쩌고 저쩌고 하지만 결론적으로는 단어 간의 순서나 문맥을 고려하지 못한다는 언어적 분석에 있어서 치명적인 한계를 가지고 있었다. 그래서 나온 것이 embedding이다.

embedding은 token을 저차원의 밀집 벡터로 표현하는 과정이다. 이를 vectorization의 방법 중 하나라고 볼 수 있지만 단순 벡터화라고 볼 수 없는 이유는 token의 의미를 문맥적으로 포착하기 떄문이다. 그 중 현재를 씹어먹고 있는 것이 transformer의 attention기반의 모델들이고, 이는 다양한 embedding으로 token의 특성을 살려서 결론적으로 문맥을 잘 이해하기 때문이다. BERT를 설명하면서 뒤에 embedding 종류에 대해서 더 설명할 것이다.

Tokenization

Vectorization

Embedding

목적

텍스트 쪼개기

토큰\(\to\)숫자

토큰\(\to\)고차원벡터공간

설명

주어진 텍스트를 단어, 부분 단어, 또는 문자로 나눔

텍스트 데이터를 모델이 처리할 수 있는 숫자 형태로 바꾸기 위해 사용. 가장 간단한 방법은 각 토큰에 고유한 인덱스를 부여하기. TF-IDF와 같은 기법들은 각 토큰의 중요도에 따라 가중치를 부여하여 벡터화

임베딩은 각 토큰을 고정된 크기의 벡터로 변환. 이 벡터는 해당 토큰의 의미, 문법적 특성, 문맥 관계 등 다양한 정보를 포함.

output

텍스트의 시퀀스 또는 리스트

숫자의 시퀀스 또는 행렬

각 토큰을 대표하는 고차원의 벡터

example

word tokening, subword tokening, 문자 tokenizing

one-hot encoding, TF-IDF, count Vectorizer

Word2Vec, GloVe, FastText, BERT

요약

텍스트를 처리가능한 단위로 나누기

토큰을 숫자로

토큰을 의미를 포함한 저차원 벡터(dense vector)로

1-1. word tokenization#

토큰을 단어로 하는 경우, 단어 토큰화라고 한다. 그렇다면 단어(word)라는 것에 대한 정의가 먼저 필요할 것이다. 단어는 단어 단위 외에도 단어구, 의미를 갖는 문자열로 간주되기도 하기 때문에 참으로 정의가 애매하다.

언어학에서 단어는 여러 관점에서 정의될 수 있다. 우선 형태론적 관점에서 단어란 ‘최소 자립 단위’를 말한다. 이는 단어 내의 형태소(Morpheme : 의미 혹은 문법적 기능의 최소 단위)들로 쪼개질 수 있으며 이렇게 단어를 쪼개는 자연어 처리를 형태소 분석이라고 부른다.

구문론적인 관점에서 단어란 문법적인 역할을 하는 최소단위를 말한다. 품사를 말한다. 의미론적 관점에서 단어(Semantic Word)는 의미를 가지는 최소단위로 간주된다. 형태소와 비슷한 것으로 보이지만 unhappiness를 예시로 보면 다르다. unhappiness는 의미론적으로 불행이라는 의미의 하나의 단어이지만(쪼개면 의미가 깨지니깐), 형태론적으로 보면 un-happy-ness라는 세 개의 구별된 형태소로 이루어져있고, 동시에 happy라는 단어(이면서 형태소)를 가지고 있는 단어라고 볼 수 있다(양자역학인가… ~이면서 ~이다).

White-space tokenization#

가장 많이 쓰이는 tokenization 방식이 white-space tokenization방식이다. 그냥 단순하게 띄어쓰기를 기반으로 token을 분간하는 것이다. 영어에서는 의미가 있을 수 있으지 몰라도 한국어에서는 띄어쓰기 만으로는 단어 토큰을 구분하기 어렵다.

Regular expression tokenization#

정규표현 토큰화. splitting pattern을 정규식으로 분간할 수 있다. 정규 표현식은 문자열의 패턴을 정의하고 일치하는 부분을 찾는데 사용된다.

S = 'Football, Cricket; Golf Tennis'
re.split('r[;,\s]', S)
Tokens = "Football","Cricket","Golf","Tennis"

re.split(‘r[;,\s]’, S)

; , \s(공백) 문자열 중에 해당 3개와 같은 것으로 분할해라.

토큰화 고려사항#

  1. 구두점이나 특수 문자를 단순 제외 NO

    • 날짜 사이의 /, 구두점 in Ph.D, 소수점

  2. 줄임말과 단어 내에 띄어쓰기가 있는 경우

    • rock ‘n’ roll은 하나의 단어이지만 중간에 띄어쓰기가 존재함. 띄어쓰기가 있어도 하나의 token으로 보아야함.

    • (’) apostrophe

아래의 표준 토큰화(Penn Treebank Tokenization)에서의 규칙을 살펴보자.

import nltk
from nltk.tokenize import TreebankWordTokenizer
# nltk.download('treebank') # 처음 사용시에는 다운 받아야함

tokenizer = TreebankWordTokenizer()
text = "Starting a home-based restaurant may be an ideal. it doesn't have a food chain or restaurant of their own."
print('트리뱅크 워드토크나이저 :',tokenizer.tokenize(text))
트리뱅크 워드토크나이저 : ['Starting', 'a', 'home-based', 'restaurant', 'may', 'be', 'an', 'ideal.', 'it', 'does', "n't", 'have', 'a', 'food', 'chain', 'or', 'restaurant', 'of', 'their', 'own', '.']

하이픈으로 구성된 단어 ‘home-based’같은 단어는 잘 붙어서 단어취급해 주는 것을 볼 수 있다. 또한 아포스트로피로 접어가 함께 붙어준 단어는 분리해주는 것도 ‘does - n’t’를 통해서 볼 수 있다. 마지막으로 구두점도 하나의 단어 취급하는 것을 볼 수 있다.

한국어에서 토큰화의 어려움#

  1. 교착어의 특성

    • 조사(preposition)라는 것이 존재. 띄어쓰기 없이 바로 뒤에 붙음. 띄어쓰기 단위로는 처리가 불가.

    • 따라서 형태소 단위로 단어를 구분해주어야함.

  2. 띄어쓰기가 잘 안 지켜진다.

    • 띄어쓰기를 안해도 의미적으로 이해는 간다.

    • 1933년에 띄어쓰기가 보편화 되었으니 100년도 안되었다.

    • ‘모아쓰기’라는 한국어적인 특징이 존재한다.

    • corpus 에서 전반적으로 영어에 비해 띄어쓰기가 잘 안지켜졌을 확률이 높다.

  3. 한국어는 어순이 중요치 않다.

    • 이건 토큰화보다는 모델링에서의 문제인데, 이전 단어가 주어질때 다음 단어가 나타날 확률을 구할때 어순이 중요치 않다는 것은 어떤 단어는 출현이 가능하다는 뜻이 된다.

    • 확률에 기반한 언어 모델이 성능이 제대로 안나올 가능성 크다.

1-2. Sentence tokenization#

토큰의 문장(sentence)일 수도 있다. 사실 많이 쓰이는 방식은 sentence segmentation 문장 분류 작업에 사용된다. corpus가 주어질 때 문장 단위로 주어지는 것이 아니라 document 단위로 주어지는 경우가 많기 때문에, 문장을 구분해내서 저장하기 위해서 사용되는 경우가 많다.

이럴 경우에 마침표가 대부분은 문장의 구분자로 사용될 수 있지만, 문제는 가끔 안그럴 경우가 있다는 것이다. ? 혹은 ! 같은 특수 문자들이 문장 split의 기준점이 되기도 하고, 마침표가 명확한 구분자 역할을 하지 못하는 경우도 많기 때문이다. 예를 들면 … 과 같은 경우도 있다. 모든 경우의 수에 대해서 규칙을 설정한다는 것은 쉬운 일이 아니다. 그렇기에 적절하게 해당 언어에 적합한 tokenizer library를 사용해야한다. 한국어일 경우에는 문장 구분으로 kss(korean sentence splitter)를 사용한다.

1-3. morpheme tokenization 형태소 토큰화#

: Part-of-speech tagging 품사태깅

형태소 분석기랑 품사 분석기는 종종 혼용되어 사용되는 용어이다. python library들에서 대부분의 형태소 분석이 품사를 함께 분석해서 output을 주기 때문에 그럴 것 이다. 하지만 품사와 형태소는 언어학적으로 분명한 차이가 있는 용어이다. 형태소는 텍스트를 구성하는 최소 의미 단위를 말하는 것이고, 품사라는 것은 단어나 형태소가 문장 내에서 어떤 역할을 하는지, 어떤 문법적 기능이나 의미적 특성을 가지는지를 기준으로 분류한 범주이다. 이 정의만 보아도 품사를 확인하기 위해서는 형태소 분석이 선행되어야 한다.

단어의 표기는 같지만 품사에 따라 단어의 의미가 달라지는 것은 만국의 언어에서 공통되는 점이다. ‘못’은 nail이기도 하고 not이기도 하다. 단어가 해당 문장에서 어떤 품사로 사용되었는지를 아는 것은 의미를 파악하는데 중요한 정보가 된다.

주요 품사들을 표로 나타내고 nltk에서 사용하는 품사 태그를 추가한 것이다. nltk 태그는 Penn Treebank 품사 태그 세트를 기반으로 한다.

품사

영어명

설명

예시

NLTK (Penn Treebank) 태그

명사

Noun

사람, 장소, 사물, 개념 등을 나타내는 단어.

학교, 사과, 사랑

NN (singular), NNS (plural)

동사

Verb

행동이나 상태를 나타내는 단어.

먹다, 자다, 생각하다

VB (base form), VBD (past tense), VBG (gerund), VBN (past participle), VBP (singular present), VBZ (3rd person singular present)

형용사

Adjective

명사나 대명사의 성질, 상태, 특성을 나타내는 단어.

예쁘다, 빠르다

JJ

부사

Adverb

동사, 형용사, 다른 부사 또는 전체 문장을 수식하며, 방법, 장소, 시간, 빈도, 정도 등을 나타내는 단어.

빨리, 아주, 여기서

RB

대명사

Pronoun

다른 명사를 대신해서 쓰이는 단어.

그, 나, 우리

PRP

전치사/조사

Preposition/조사

명사 앞에 위치하여 그 명사와 문장 내의 다른 구성성분과의 관계를 나타내는 단어.

“in”, “on”, “with”/“의”, “에”, “에서”

IN

접속사

Conjunction

두 개 이상의 단어, 구, 문장을 연결하는 단어.

그리고, 그러나, 또는

CC

감탄사

Interjection

감정이나 태도를 나타내는 단어.

아!, 헉!, 와우!

UH

from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
import pprint
#nltk.download()

text = "I am actively looking for Ph.D. students. and you are a Ph.D. student."
tokenized_sentence = word_tokenize(text)

print('단어 토큰화 :',tokenized_sentence)
print('품사 태깅 :',pos_tag(tokenized_sentence))
단어 토큰화 : ['I', 'am', 'actively', 'looking', 'for', 'Ph.D.', 'students', '.', 'and', 'you', 'are', 'a', 'Ph.D.', 'student', '.']
품사 태깅 : [('I', 'PRP'), ('am', 'VBP'), ('actively', 'RB'), ('looking', 'VBG'), ('for', 'IN'), ('Ph.D.', 'NNP'), ('students', 'NNS'), ('.', '.'), ('and', 'CC'), ('you', 'PRP'), ('are', 'VBP'), ('a', 'DT'), ('Ph.D.', 'NNP'), ('student', 'NN'), ('.', '.')]
from konlpy.tag import Okt
from konlpy.tag import Kkma

okt = Okt()
kkma = Kkma()

print('OKT 형태소 분석 :',okt.morphs("열심히 코딩한 당신, 연휴에는 여행을 가봐요"))
print('OKT 품사 태깅 :',okt.pos("열심히 코딩한 당신, 연휴에는 여행을 가봐요"))
print('OKT 명사 추출 :',okt.nouns("열심히 코딩한 당신, 연휴에는 여행을 가봐요"))
OKT 형태소 분석 : ['열심히', '코딩', '한', '당신', ',', '연휴', '에는', '여행', '을', '가봐요']
OKT 품사 태깅 : [('열심히', 'Adverb'), ('코딩', 'Noun'), ('한', 'Josa'), ('당신', 'Noun'), (',', 'Punctuation'), ('연휴', 'Noun'), ('에는', 'Josa'), ('여행', 'Noun'), ('을', 'Josa'), ('가봐요', 'Verb')]
OKT 명사 추출 : ['코딩', '당신', '연휴', '여행']

품사를 뽑을라고 했는데 형태소도 나오는 아주 좋은 라이브러리들이다. 형태소 라이브러리들에 대해서 정리를 해보면 아래와 같다.

언어

분석기 이름

기능

주요 사용 라이브러리/플랫폼

장점

단점

한국어

KoNLPy

형태소 및 품사 분석

Python

다양한 한국어 처리 도구 제공

성능이 최적화되지 않은 도구도 포함

Khaiii

형태소 및 품사 분석

Python, C++

카카오에서 개발하여 높은 성능

설치 과정이 다소 복잡

ETRI

형태소 및 품사 분석, 의존 구문 분석, 개체명 인식 등

Web API

고품질의 분석 결과

유료

Mecab

형태소 및 품사 분석

C++, Python, Java 등

빠르고 정확

사전 업데이트 및 추가가 필요한 경우가 있음

영어

NLTK

품사 태깅

Python

교육용으로 널리 사용, 다양한 기능 제공

상업용 애플리케이션에는 덜 적합

spaCy

형태소 및 품사 분석

Python

빠르고 정확, 쉬운 API 제공

언어 모델의 크기가 큼

StanfordNLP

품사 태깅, 구문 분석, 개체명 인식 등

Java, Python

다양한 언어 지원, 높은 정확도

자바 기반으로 설치 및 실행이 복잡

FastText

임베딩 및 분류

Python, C++

사전 학습된 모델 제공, 빠른 학습 속도

품사 태깅 기능 없음

Vectorization#

토큰화-벡터화 까지만의 방법의 한계에 대해서 위에서 말했었다. token의 표현에 있어서 문맥적인 정보를 넣어주지 않았음으로 그만큼 분석에 한계가 분명하다고 볼 수 있다. 하지만 이 전에 기법들에 대해 알고 있는 것은 항상 분명하게 도움이 될 수 있다.

2-1. one-hot encoding#

sklearn에서 많이 써봤을 법한 것이다. 원-핫 인코딩이라는 것은 각 단어를 유니크한 원-핫 벡터로 변환하는 것을 말한다. 그것이 무슨 말이냐면

cat, dog, bird가 있다면 이것을 원핫 인코딩하면 이런 테이블이 나온다

cat

dot

bird

-

1

0

0

-

0

1

0

-

0

0

1

아주 단순하면서 각 단어를 고유한 벡터로 표현한다. 하지만 단어 사이의 의미적 관계나 유사성을 반영하는 표현력은 전혀 없다. 또한 one-hot encoding으로 텍스트 전부를 표현하는 것은 사실상 불가능할 정도로 sparse한 metric이 되어서 메모리 효율이 극도로 낮아진다. 차원의 저주에 걸려버리는 것이다.

from konlpy.tag import Okt
okt = Okt()
tokens = okt.morphs('고양이 개 새')
word_to_index = {word:idx for idx,word in enumerate(tokens)}

def one_hot_encoding(word,word_to_index):
    one_hot_vector = [0]*(len(word_to_index))
    index = word_to_index[word]
    one_hot_vector[index]=1
    return one_hot_vector

one_hot_encoding('고양이', word_to_index)
[1, 0, 0]

2-2. BOW(Bag of Words)#

2-3. DTM(Document-Term Matrix)#

2-4. TF-IDF#

3. Word Embedding#

word embedding이란 단어를 인공신경망으로 벡터화, 단어를 밀집 표현으로 변환

옆에서 보듯이 단어를 밀집벡터의 형태로 표현하는 것을 워드 임베딩이라고 한다. 한정된 차원내에서 단어를 표현하는 것이다. 그리고 워드 임베딩의 output은 embedding vector일 것이다. 간단하게 표로 차이를 표현하면

one-hot vector

embedding vector

dimension

고차원(단어집합크기만큼)

저차원

representation

sparse vector

dense vector

method

수동

train

value

1,0(Discrete, Sparse)

float(Countinuous, Dense, Distributed)

3-1. Word2Vec#

import gensim

Reference#