본문 바로가기

자연어처리

기본 용어 (1)

1. 말뭉치, 토큰, 타입

말뭉치(corpus)는 텍스트 데이터이다.

말뭉치는 다음 그림처럼 원시 텍스트(utf-8, ASCII)와 원시 텍스트와 연관된 메타 데이터(metadata)를 '메타데이터-원시 텍스트' 형태로 여러 개 포함하고 있다.

■ 메타데이터가 붙은 텍스트를 샘플(sample) 또는 데이터 포인트(data point)라고 부른다.

- 메타데이터는 텍스트와 관련된 부가 정보(식별자, 레이블, 타임 스탬프 등)이다.

■ 말뭉치는 샘플(데이터)의 모음이기 때문에, 말뭉치를 데이터셋(dataset)이라고도 부른다.

토큰(token)은 자연어를 다루는 가장 작은 기본 단위로 단어, 형태소 등이 될 수 있다.

- 영어에서 토큰은 공백 문자나 구두점으로 구분되는 단어나 숫자를 말한다.

토큰화(tokenization)는 주어진 텍스트를(말뭉치를) 토큰으로 나누는 과정을 의미한다.

■ 오픈 소스 NLP 패키지 NLTK, spaCy를 이용해 다음과 같이 텍스트에 토큰화를 적용할 수 있다.

import spacy

# python -m spacy download en_core_web_sm # en_core_web_sm 모델 설치
nlp = spacy.load('en_core_web_sm') # 모델 로드
text = ', This is a sentence.'
print([str(token) for token in nlp(text.lower())])
```#결과#```
[',', 'this', 'is', 'a', 'sentence', '.']
````````````

from nltk.tokenize import TweetTokenizer

tknzr = TweetTokenizer()
text = 'This is a cooool #dummysmiley: :-) :-P <3 and some arrows < > -> <--'
print(tknzr.tokenize(text.lower())) 
```#결과#```
['this', 'is', 'a', 'cooool', '#dummysmiley', ':', ':-)', ':-p', '<3', 'and', 'some', 'arrows', '<', '>', '->', '<--']
````````````

- NLTK의 TweetTokenizer는 이모티콘을 인식하는 토큰화를 지원하기 때문에 #(해시테그)나 :-)같은 이모티콘을 텍스트에서 토큰화할 수 있다.

교착어가 있는 언어를 토큰화할 경우, 같은 의미의 단어가 다른 단어로 인식될 수 있어 복잡해진다. 이는 모델 성능 저하에 큰 영향을 미칠 수 있다. 

- 이런 문제는 '텍스트를 바이트 스트림'으로 표현하여 해결할 수 있다.

cf) 교착어란? https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9%EC%96%B4#:~:text=%EA%B5%90%EC%B0%A9%EC%96%B4%EB%8A%94%20%EA%B3%A0%EB%A6%BD%EC%96%B4%EC%99%80%20%EA%B5%B4%EC%A0%88%EC%96%B4,%EA%B5%AC%EC%84%B1%EB%90%98%EB%8A%94%20%ED%8A%B9%EC%A7%95%EC%9D%B4%20%EC%9E%88%EB%8B%A4.

cf) 한국어 교착어 예시

타입(type)은 말뭉치에 등장하는 고유한 토큰을 의미하며, 말뭉치에 있는 모든 타입의 집합을 어휘 사전 또는 어휘(lexicon)라고 한다.

■ 단어는 내용어(content words)와 불용어(stopword)로 구분된다.

- 내용어는 의미적 내용을 가지고 있는, 문장의 핵심 의미에 기여하는 단어로 명사, 동사, 형용사 등이 내용어에 해당된다.

- 불용어는 문장에 큰 의미가 없는 단어로 한국어는 '-은/-는/-이/-가', '-하다'와 같은 조사와 접속사, 영어는 'the', 'a', 'in'같은 관사와 전치사가 불용어에 해당된다.

cf) 불용어는 내용어를 보충하는 문법적인 용도로 쓰이기 때문에 문맥 파악이 중요한 작업이나 감성 분석 등 불용어가 필요한 경우가 있다. 이런 경우들을 제외하면, 분석에 큰 의미가 없는 단어이므로, 불용어를 제거하면 텍스트 데이터의 크기 감소, 메모리 사용량 감소 등의 효과를 얻을 수 있다.

 

2. 유니그램, 바이그램, 트라이그램, ... , n-그램

유니그램(unigram)은 하나의 연속된 단어(=토큰 한 개로 이뤄짐), 바이그램(bigram)은 두 개의 연속된 단어(=토큰 두 개로 이뤄짐), 트라이그램(trigram)은 세 개의 연속된 단어(=토큰 세 개로 이뤄짐)를 말한다.

■ 같은 요령으로 n-그램(n-gram)은 고정 길이(n)의 연속된 토큰 시퀀스이다. n-그램을 만드는 방법은 다음과 같다.

def n_grams(text, n): # 텍스트나 토큰을 받으면 n-gram 리스트들을 반환
    return [text[i:i+n] for i in range(len(text)-n+1)]
    
cleaned = [',', 'this', 'is', 'a', 'sentence', '.']    
print(n_grams(cleaned, 4))
```#결과#```
[[',', 'this', 'is', 'a'], ['this', 'is', 'a', 'sentence'], ['is', 'a', 'sentence', '.']]
````````````

- n-그램이라면, 예를 들어 n = 4인 경우 네 개의 연속된 단어를 만들어아 한다. 그러므로 text에서 [i:i+n]에 해당하는 부분을 가져와야 한다.

- text의 길이가 L이고 한 번에 뽑아낼 토큰(또는 글자)의 개수 n이 정해져 있을 때, 슬라이싱 [i:i+n]을 수행하려면 i+n이 L을 넘지 않아야 한다. 

- 즉, i + n <= L이어야 하며, n을 넘겨주면 i <= L - n이 되어야 한다.

- 파이썬의 range(a, b)는 a부터 b-1까지 순회하므로 시작점 i가 0부터 L-n까지 전부 포함되려면 b는 L - n + 1로 잡아줘야 한다.

- 이 예에서 cleaned의 길이는 6이다. 이를 네 개의 연속된 단어를 만들기 위해선 길이 1-4, 2-5, 3-6 이렇게 3개의 부분으로 분리해야 한다.

- 이는 range(len(text)-n+1) = range(6-4+1) = range(3) = 0, 1, 2을 통해 이렇게 세 가지 지점에서만 유효한 슬라이싱을 할 수 있다.

 

3. 표제어와 어간

자연어 처리에서 표제어 추출(lemmatization)과 어간 추출(stemming)은 텍스트의 차원 수(벡터의 차원 수)를 줄여주는 전처리 기법이다.

■ 먼저, 표제어 추출은 단어의 품사를 고려해 사전에 등재된 단어의 기본형을 찾는 방법이다.

표제어(lemma)는 사전에 등재된 단어의 기본형이다.

- 예를 들어 flew, flies, flown 등은 모두 표제어 fly에서 어미가 바뀐 변형된 단어이다.

■ 이렇게 표제어에서 변형된 단어들을 표제어로 바꿔 벡터 차원의 수를 줄이는 방법이 표제어 추출이다.

문장에 등장하는 단어를 컴퓨터가 이해할 수 있도록 각 단어의 등장을 원-핫 벡터로 표현하는 경우를 생각해 보자.

- 예를 들어 flew, flies, flown가 하나의 데이터 안에 같이 있는 경우 필요한 벡터의 차원 수는 3이 된다.

- flew, flies, flown을 표제어 추출을 통해 표제어 fly로 통일한다면, fly는 타입으로 생각할 수 있다. 즉, 표제어 fly로 바꾼다면 필요한 벡터의 차원 수는 1로 줄어든다.

from nltk.stem import WordNetLemmatizer
import nltk

# nltk.download('wordnet') # WordNet 사전 다운로드
n = WordNetLemmatizer()
words = ['lives','flew', 'flies', 'flown', 'was']
print([n.lemmatize(w) for w in words])
```#결과#```
['life', 'flew', 'fly', 'flown', 'wa']
````````````

words = ['lives','flew', 'flies', 'flown', 'was']
print([n.lemmatize(w, 'v') for w in words])
```#결과#```
['live', 'fly', 'fly', 'fly', 'be']
````````````

- nltk의 WordNetLemmatizer을 사용한 경위 lives는 life로 표제어 추출이 잘 되었지만, was는 wa로 알 수 없는 단어로 바뀌었고, flew, flies, flown은 표제어 추출이 되지 않은 것을 볼 수 있다.

- 이는 WordNetLemmatizer에 품사 정보가 전달되지 않았기 때문이다.

- lemmatizer( )에 동사라는 품사 정보를 전달할 경우, 위와 같이 lives는 live로, was는 be로 flew, flies, flown는 fly로 통일된 것을 확인할 수 있다.

import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(u"While the river was flowing smoothly, the birds flew overhead, flies buzzed around, and some had flown to warmer climates.")
type(doc)
```#결과#```
spacy.tokens.doc.Doc
````````````

for token in doc:
  print('{} -> {}'.format(token, token.lemma_))  
```#결과#```
While -> while
the -> the
river -> river
was -> be
flowing -> flow
smoothly -> smoothly
, -> ,
the -> the
birds -> bird
flew -> fly
overhead -> overhead
, -> ,
flies -> fly
buzzed -> buzz
around -> around
, -> ,
and -> and
some -> some
had -> have
flown -> fly
to -> to
warmer -> warm
climates -> climate
. -> .
````````````

- spacy도 WordNet 사전을 사용해 표제어 추출을 수행한다.

■ 어간 추출은 표제어 추출 대신 사용하는 축소 기법으로 단어에서 실질적인 의미를 지닌 부분만 추출한다.

- 예를 들어 단어 'geese'에서 표제어 추출을 하면 'goose'가 되지만, 어간 추출은 단어의 끝을 잘라 어간(stem) 형태 'gees'로 축소한다.

from nltk.stem import PorterStemmer
from nltk.stem import LancasterStemmer
from nltk.tokenize import word_tokenize

porter_stemmer = PorterStemmer()
lancaster_stemmer = LancasterStemmer()

#nltk.download('punkt_tab')
text = "While the river was flowing smoothly, the birds flew overhead, flies buzzed around, and some had flown to warmer climates."
words = word_tokenize(text)

print('어간 추출 전: ', words)
print('포터 스테머 어간 추출: ', [porter_stemmer.stem(w) for w in words])
print('랭커스터 스테머 어간 추출: ', [lancaster_stemmer.stem(w) for w in words])
```#결과#```
어간 추출 전:  ['While', 'the', 'river', 'was', 'flowing', 'smoothly', ',', 'the', 'birds', 'flew', 'overhead', ',', 'flies', 'buzzed', 'around', ',', 'and', 'some', 'had', 'flown', 'to', 'warmer', 'climates', '.']
포터 스테머 어간 추출:  ['while', 'the', 'river', 'wa', 'flow', 'smoothli', ',', 'the', 'bird', 'flew', 'overhead', ',', 'fli', 'buzz', 'around', ',', 'and', 'some', 'had', 'flown', 'to', 'warmer', 'climat', '.']
랭커스터 스테머 어간 추출:  ['whil', 'the', 'riv', 'was', 'flow', 'smooth', ',', 'the', 'bird', 'flew', 'overhead', ',', 'fli', 'buzz', 'around', ',', 'and', 'som', 'had', 'flown', 'to', 'warm', 'clim', '.']
````````````

- NLTK에서는 포터 알고리즘과 랭커스터 스테머 알고리즘을 지원한다.

- 포터 스테머는 영어의 접미사(suffix)를 규칙에 맞춰 제거한다. 랭커스터 스테머는 포터 스테머보다 더 많이 단어를 줄이기 때문에 원래 의미와 거리가 멀어질 가능성이 포터 스테머보다 크다.

- 포터 스테머 알고리즘의 아이디어는 단어의 자음(consonant)과 모음(vowel)이 연속으로 나타나는 개수를 세고, 이를 기반으로 단계별 규칙을 적용해 접미사를 제거한다.

- 예를 들어, 'flowing'은 f(자음), l(자음), o(모음), w(자음), i(모음), n(자음), g(자음)에서 자음과 모음이 연속으로 나타나는 개수를 센다. 그다음, 단어의 끝에서 접미사 가능성이 있는 부분을 식별한다. 그 결과, 모음 1개와 자음 2개의 패턴인 -ing가 제거된다.

- 어간 추출을 결과를 보면, 표제어 추출과 달리 의미가 동일한 경우 같은 단어를 얻기 위한 목적에는 부합하지 않는 것을 볼 수 있다.

 

기본 용어(2)  https://hyeon-jae.tistory.com/138

 

'자연어처리' 카테고리의 다른 글