자연어 처리 기본 지식 및 텍스트 전처리(NLP Basic Knowledges & Text Preprocessing)

자연어 처리란?

자연어 & 자연어 처리(NLP; Natural Language Processing)

자연어(自然語, 영어: natural language 또는 ordinary language) 혹은 자연 언어는 사람들이 일상적으로 쓰는 언어를 인공적으로 만들어진 언어인 인공어와 구분하여 부르는 개념이다.

- 위키백과 (자연어)

자연어 처리(自然語處理) 또는 자연 언어 처리(自然言語處理)는 인간의 언어 현상을 컴퓨터와 같은 기계를 이용해서 묘사할 수 있도록 연구하고 이를 구현하는 인공지능의 주요 분야 중 하나다.

- 위키백과 (자연어 처리)

간단히 말하면 먼 옛날부터든 언제부터든 사람들의 의사소통을 위해 자연스럽게 형성된 언어이다. 영어, 한국어 등을 예로 들 수 있다.
반대로 인공어는 특정한 누군가가 어떤 목적을 갖고 인공적으로 만든 것이라고 볼 수 있다. 여기에는 에스페란토어, 프로그래밍 언어 등이 포함된다.

이러한 자연어를 컴퓨터로 처리하는 기술을 자연어 처리(NLP; Natural Language Processing) 라고 한다.

자연어 처리 용어

  • 말뭉치(Corpus; 코퍼스) : 자연어 연구를 위해 특정한 목적을 갖고 언어의 표본을 추출한 집합(텍스트 데이터)
  • 문장(Sentence) : 여러 개의 토큰(단어, 형태소 등)으로 구성된 문자열. 마침표, 느낌표 등의 기호로 구분
  • 문서(Document) : 문장들의 집합
  • 어휘집합(Vocabulary) : 말뭉치에 있는 모든 문장 및 문서를 토큰화한 후 중복을 제거한 토큰의 집합

토큰(token) 및 토큰화(tokenization)가 구체적으로 무엇인지는 아래 텍스트 전처리 부분에서 다루겠다.

자연어 처리로 할 수 있는 것

  1. 자연어 이해(NLU; Natural Language Understanding)
    • 분류(Classification) : 뉴스 기사 분류, 감성 분석(Positive/Negative)
    • 자연어 추론(NLI; Natural Language Inference)
    • 기계 독해(MRC; Machine Reading Comprehension), 질의 응답(QA; Question&Answering)
    • 품사 태깅(POS(Part of Speech) tagging), 개체명 인식(Named Entity Recognition) 등
  2. 자연어 생성(NLG; Natural Language Generation)
    • (특정 도메인의) 텍스트 생성
  3. NLU & NLG
    • 기계 번역(Machine Translation)
    • 요약(Summerization)
      • 추출 요약(Extractive summerization) : 문서 내에서 해당 문서를 가장 잘 요약하는 부분을 찾아내는 것 (NLU에 가까움)
      • 생성 요약(Absractive summerization) : 해당 문서를 요약하는 요약문을 생성 (NLG에 가까움)
    • 챗봇(Chatbot)
      • 특정 업무를 처리하기 위한 챗봇(TOD; Task Oriented Dialog)
      • 정해지지 않은 주제를 다루는 일반 대화 챗봇(ODD; Open Domain Dialog)
  4. 기타
    • TTS(Text to Speech) : 텍스트를 음성으로 읽기
    • STT(Speech to Text) : 음성을 텍스트로 쓰기
    • Image Captioning : 이미지를 설명하는 문장 생성

벡터화(Vectorization)

자연어와 자연어 처리가 무엇인지 알았고 무슨 일을 할 수 있는지도 알았다. 그럼 어떻게 하는 것일까?

우선 뭘 하려고 하기 전에 컴퓨터가 자연어를 이해할 수 있도록 우리 인간(human)이 친절하게 바꿔주어야 하는데, 자연어는 벡터로 만들어준다. 이를 벡터화(Vectorization) 라고 한다.

자연어를 어떤 방식으로 벡터화할지는 자연어 처리 모델 성능에 큰 영향을 미친다.
자연어를 벡터화하는 방법은 다음과 같은 것들이 있다. (자세한 내용은 각각 별도의 글에서 다룰 예정)

  • 등장 횟수 기반 단어 표현(Count-based Representation)
    • Bag of Words
    • TF-IDF
  • 분포 기반 단어 표현(Distributed Representation)
    • Word2Vec
    • fastText
    • GloVe

자, 이제 벡터화가 뭔지도 알았으니 어떻게 하는지만 알면 바로 자연어 처리를 진행할 수 있을 것만 같다!

... 그럴 것 같지만 그렇지 않다.
데이터 분석을 하고, 머신 러닝 모델을 만들고 그러기 전에 무엇을 해야 했는가? 그렇다. 데이터 전처리를 '반드시' 해야 했다.
텍스트 데이터도 그렇다.


텍스트 전처리

데이터 전처리의 중요성은 아무리 강조해도 지나치지 않을 것이다. 그런 만큼 다양한 이유가 있을 것인데, 그 중에서도 텍스트 데이터의 전처리가 필요한 이유는 무엇일까?

차원의 저주(Curse of Dimensionality)

차원의 저주란 데이터셋의 차원이 커질수록 기존의 데이터가 갖고 있는 설명력이 줄어드는 문제를 말한다.
간단하게 말하면, 데이터 개수는 그대로이고 데이터셋의 차원만 늘어나면 데이터의 밀도가 떨어져서 의미 도출이 어려워질 수도 있다.
아래 그림들을 보자.

1차원(직선) 위에 4개의 점(데이터)가 있다고 해보자.

이 상태에서 차원을 하나 올려 2차원(평면)이 되면 점 사이의 거리가 멀어진다.
(물론 데이터에 따라 차원이 올라가도 거리가 그대로일 수도 있음)

3차원이 되면 거리는 더더욱 멀어지게 된다.

물론 데이터셋의 차원이 올라간다고 해서 무조건 데이터의 의미가 줄어드는 것은 아니다. 데이터셋에 따라서 차원을 올리면 오히려 폭넓은 설명이 가능한 경우도 있다.
그러나 이 차원이 과하게 크면? 그러면 위 그림을 통해서 설명한 것처럼 데이터셋의 설명력이 떨어질 것이다.

자연어 처리에서는 전체 말뭉치에 존재하는 단어의 종류가 데이터셋의 특성, 즉 차원이 된다. 그러므로 단어의 종류를 줄여야 차원의 저주 문제를 어느 정도 해결할 수가 있다.

아래의 다양한 텍스트 전처리 방법들을 통해서 불필요하거나 중복되는 단어 제거를 통해 차원을 줄이거나, 모델의 효율적인 연산을 위해 데이터를 가공할 수 있다.

그럼 이제 텍스트 전처리 방법들에 대해서 살펴보자.

1) 토큰화(Tokenization)

토큰(token), 그리고 토큰화(Tokenization)란?

(이런게 토큰...?)

자연어 처리에서 토큰이란 주어진 말뭉치를 특정한 단위(보통 단어)로 조각조각 나눈 것을 말한다. 쉽게 말하면 말뭉치를 부숴서 얻은 조각들이다. 부수는 방식은 대충 뭉텅뭉텅 부술 수도(문장 이상의 단위), 아니면 아주 가루를 내버릴 수도 있다(철자 단위).
여기서 단어를 기준으로 말뭉치를 나누면 이를 단어 토큰화라 하고, 문장 단위면 문장 토큰화라고 한다.

전통적인 자연어 처리 방식인 횟수 기반 단어 표현(Count-based Word Representation)부터 RNN, LSTM, Transformer와 같은 진보된 신경망 기반 방식 모두 이 토큰을 이용하여 모델 학습이 이루어진다.
따라서 토큰화는 자연어 처리에서 필수적이라고 할 수 있다.

토큰화 방법

단어 토큰화라면 띄어쓰기를 기준으로 단어를 구분할 수 있을 것이고,
문장 토큰화라면 구두점이나 물음표, 느낌표 등으로 구분할 수 있을 것이다.
그러나 이런 경우는 아주 단순한 경우이고, 실제는 그리 간단하지 않다.

주의사항

  • 토큰화 기준

단어 토큰화를 한다고 했을 때, 어떤 단어 사이에 특수문자(아포스트로피('), 대시(-) 등)가 포함된 경우는 어떻게 토큰화를 해야 할까?

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer

sentences = [
  'I love you',
  'I love myself'
]

tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
print(word_index)

{'i': 1, 'love': 2, 'you': 3, 'myself': 4}
이렇게 별다른 특수문자가 없으면 띄어쓰기만 갖고서 잘 구분이 되지만,

sentences = [
  "I'm a student",
  'Fu-sion!',
  "Don't panic!"
]

tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
print(word_index)

{"i'm": 1, 'a': 2, 'student': 3, 'fu': 4, 'sion': 5, "don't": 6, 'panic': 7}
이렇게 아포스트로피나 대시가 있으면 결과가 의도와 다르게 나올 수도 있다.

따라서 이런 경우에는 어떤 기준으로 토큰화를 할지 선택해야 한다.
(Keras Tokenizer의 경우 filters 파라미터를 통해서 어떤 특수문자를 제거할지 안 할지 직접 지정할 수 있음)

  • 구두점, 특수 문자의 단순 제외

구두점이나 특수 문자를 단순히 제거해버리면 무의미한 토큰이 만들어질 수도 있다.
M.Sc(Master of Science) 또는 Ratchet&Clank(비디오 게임 제목) 같이 하나의 토큰으로 분류해야 하는 경우가 있기 때문이다.

2) 정제(Cleaning) 및 정규화(Normalization) by 내장 메소드

정제 및 정규화란?

정제는 주어진 말뭉치에서 지저분한(노이즈) 데이터를 제거하는 것이다.
정규화는 같은 의미임에도 모양이 다른 단어들을 하나의 형태로 통일시켜주는 작업이다.
쉽게 말해 불필요한 군더더기를 제거하여 데이터의 살을 빼는 작업이다.

같은 의미지만 다른 형태인 단어 통일시키기

우리나라를 영어로 표기하면 다양한 방식이 나올 수 있다. Korea, KOR, ROK 등등. 그러나 결국 이들이 의미하는 것은 대한민국이다.
이런 경우 한 가지를 딱 정해서 통일을 시켜준다.

대소문자 통합

위에서 우리나라를 표현하는 말 중 KOR이 있었다. 그런데 여기서는 KOR이라 하고, 저기서는 kor이라고 할 수도 있다.
이런 경우도 대소문자 둘 중 하나로 딱 정해서 통합시킨다.

불필요한 단어 제거(통계적 트리밍(Trimming))

머신 러닝 모델을 만들기 전에 보통 데이터 전처리를 한다.
이때 평균에 비해 과도하게 높거나 낮은 수치들을 이상치로 간주하여 제거할 때가 있다.

이와 비슷하게 텍스트 전처리에서는 말뭉치 내에서 너무 많거나 적게 등장하는 단어들은 별 의미가 없는 것으로 간주하여 제거한다.
등장 횟수가 너무 많으면 여러 문장(또는 문서)에 걸쳐서 나오는 단어이므로 문장 분류 등에 별다른 도움이 되지 않을 것이고, 너무 적으면 적은 대로 영향력이 없기 때문이다.

3) 어간 추출(Stemming) 혹은 표제어 추출(Lemmatization)

어간과 표제어란?

  • 어간(stem) : 단어의 의미가 포함된 부분으로 접사(affix) 등이 제거된 형태(어근 또는 단어 원형과 다를 수 있음)
    • 접사(affix) : 단어에 붙어 추가적인 의미를 더하는 부분
  • 표제어(Lemma) : 단어의 기본 사전형 형태

간단하게 예를 들면 wolves의 어간은 wolv이고 표제어는 wolf이다.
또 다른 예로 leaves의 어간은 leav, 표제어는 leaf이다.

즉, 어간은 단어에서 접사같은 부수적인 부분만 딱 떼어낸 것이고,
표제어는 그 단어의 근본 형태(또는 사전에서 찾을 수 있는 형태)라고 할 수 있다.

어간 추출

# NLTK 라이브러리 데이터를 다운로드받아야 아래 코드들이 실행 가능
# 이미 있는 경우는 생략해도 됩니다.
import nltk
nltk.download()
from nltk.stem import PorterStemmer

ps = PorterStemmer()
words = ["wolf", "wolves", "leaf", "leaves"]

print([ps.stem(word) for  word  in  words])

['wolf', 'wolv', 'leaf', 'leav']
보이는 것처럼 군더더기 부분만 딱 자르고 남은 부분 그대로 내보낸다.

표제어 추출

from nltk.stem import WordNetLemmatizer

lt= WordNetLemmatizer()
words = ["wolf", "wolves", "leaf", "leaves"]

print([lt.lemmatize(word) for word in words])

['wolf', 'wolf', 'leaf', 'leaf']
보이는 것처럼 단어의 사전형 형태를 찾아서 내보낸다.

주의사항

  • 다양한 라이브러리, 다양한 알고리즘

자연어 처리에 쓰이는 다양한 라이브러리가 있고, 각각의 내부에는 다양한 어간 추출 및 표제어 추출 알고리즘이 있다.
따라서 사용 전에 작동 방식을 이해하고 사용해야 할 것이다.

  • 연산 속도 : 어간 추출 > 표제어 추출

어간 추출은 단순한 꼬리 자르기이고, 표제어 추출은 근본을 찾아오는 작업이므로 일반적으로 어간 추출이 더 빠르다.
그러나 어간 추출의 결과는 실제로 존재하지 않는 단어일 수도 있으므로 상황에 맞게 어간 추출 또는 표제어 추출을 선택해야 한다.

4) 불용어(Stopword) 처리

불용어란?

불용-어 不用語

인터넷 검색 시 검색 용어로 사용하지 않는 단어. 관사, 전치사, 조사, 접속사 등은 검색 색인 단어로 의미가 없는 단어이다. 그러나, 각 검색 엔진마다 그 내용은 다를 수도 있다.

- 네이버 국어사전

위에서 불필요한 단어 중 말뭉치에서 너무 많이 등장하는 단어들도 제거 대상이라고 하였다. 여기에 대부분 포함되는 것이 바로 불용어이다.

영어에서는 a, the 및 in, on, at 등의 전치사 등등이 해당된다.
아래 링크를 통해서 각 언어별 불용어 목록을 확인할 수 있다.
언어별 불용어 목록

불용어 확인

자연어 처리 라이브러리를 통해서 불용어를 확인 및 처리할 수 있다.
여기서는 spaCy 라이브러리를 사용해보겠다.

import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load("en_core_web_sm")

# 불용어 10개만 확인
print([stopword  for  stopword  in  nlp.Defaults.stop_words][:10])

['along', 'using', 'hence', 'serious', 'see', 'yours', 'beyond', 'myself', 'is', 'yourselves']

불용어 처리

text = "I'm the best. You're the best. We're the best."

sentence = nlp(text)

token_list = []
for token in sentence:
    # 토큰이 불용어와 구두점이 아니면 저장
    if (token.is_stop == False) & (token.is_punct == False):
        token_list.append(token.text)

token_list

['best', 'best', 'best']

sws  =  nlp.Defaults.stop_words
sw  = ["i", "'m", "the", "you", "'re", "we" ]
is_sw  = [word  in  sws  for  word  in  sw]

is_sw

[True, True, True, True, True, True]

불용어를 제외하고 남은 토큰('best')만 출력된 것을 볼 수 있다.


<참고 자료>

+ Recent posts