본문 바로가기
필기 노트/자연어 처리 입문

[자연어 처리 입문] 데이터 전처리 (text preprocessing)

by misconstructed 2020. 12. 28.
728x90

<딥 러닝을 이용한 자연어 처리 입문>을 읽고 간단하게 정리한 내용이다. 

개념 정리할 겸 써본다. 


# 데이터 전처리 

데이터 (텍스트)를 사용하고자 하는 용도에 맞게 토큰과, 정제, 정규화를 수행해야 한다.

# Tokenization : corpus를 token 단위로 나누는 작업

크게 단어 토큰화와 문장 토큰화로 구분할 수 있다. 단어 토큰화(word tokenization)는 말 그대로 토큰의 기준을 단어로 정하는 경우이다. 이 경우 특별히 고려해야 하는 점은 단순히 마침표나 특수문자를 그냥 제외해서는 안된다는 것이다. 예를 들어, Ph.D., m.p.h. 등의 줄임말들은 마침표까지 하나의 토큰으로 분류해야 하는 경우가 있다. 추가적으로 단어들은 품사에 따라서 의미가 달라지기도 한다. 단어들의 품사를 찾아내는 과정을 POS(Part of Speech) Tagging 이라고 한다. POS tagging을 통해서 문장을 구성하는 단어들이 어떤 품사로 쓰였는지 확인할 수 있다. NLTK에서는 PennTreebank의 POS tagger를 사용할 수 있다. (from nltk.tag imprt pos_tag)

표준 방식 중 하나로 Penn Treebank Tokenization 이 있다. Penn Treebank Tokenization은 다음과 같은 규칙으로 수행한다. from nltk.tokenizer import TreebankWordTokenizer를 이용해서 사용할 수 있다. 

  1. 하이픈으로 연결되어 있으면 하나의 단어로 인식한다.
  2. 접어(clitic)이 있다면 두 개의 단어로 분리한다.

두 번째로는 문장 토큰화(sentence tokenization)를 고려해볼 수 있다. 말 그대로 문장단위로 구분하는 것인데, 이 과정에서 고려해야 하는 것은 마침표가 문장을 구분하지 않을 수 있다는 것이다. 이런 경우 마침표를 두 가지 경우로 나눠서 고려해볼 수 있는데, 마침표가 단어의 일부인 경우와 그 외의 마침표가 문장의 마지막에 위치하는 경우로 구분할 수 있다. 후자의 경우에 대해서만 분리를 해서 각 문장에 대해서 토큰화를 진행한다. 문장 토큰화는 from nltk.tokenizer import sent_tokenize를 이용해서 수행할 수 있다. 한국어의 경우에는 KSS(Korean Sentence Spliter)를 이용해서 문장 토큰화를 진행할 수 있다. 한국어의 경우에는 영어와 다른게 형태소(morpheme) 토큰화가 필요하다. 형태소는 크게 자립 형태소(명사, 대명사, 수상, 관형사, 부사 등)와 의존 형태소(접사, 어미, 조사, 어간 등)로 구성된다. 

# 정제(cleaning), 정규화(normalization)

정제(cleaning)은 corpus에서 노이즈 데이터를 제거하는 작업이다. 토큰화를 진행할 때 방해가 되는 부분을 제외시킨다. 불필요한 단어들을 제거하는데, 가장 대표적으로는 등장 빈도가 적은 단어나, 길이가 짧은 단어를 제거한다. (한국어의 경우, 길이가 짧은 단어를 제외하게 되면 너무 많은 단어를 제외하게 된다. 이 방법은 영어에 대해 적용하기 적합하다.)

추가적으로, 불용어(stop word)라는 개념이 있다. 정제 작업 중 불용어를 제거하게 되는데, 이것은 유의미한 단어 토큰만 선별하기 위해 의미없이 자주 등장하기만 하는 단어들을 제거하는 작업이다. 전체 문장을 분석하는데 있어서 크게 영향이 없는 단어들을 불용어라고 한다. NLTK에서는 100개 이상의 불용어를 미리 정의해놨고, from nltk.coprus import stopwords로 사용할 수 있다. 

정규화(normalization)은 표현 방법이 다른 단어들을 통합시켜서 하나의 같은 단어로 만드는 작업을 말한다. 정규화는 보통 규칙에 기반해서 단어를 통합한다. 크게 어간 추출(stemming) 방식과 표제어 추출(lemmatization)으로 구분된다. 이 두 작업은 눈으로 봤을 땐 다른 단어이지만, 하나의 단어로 일반화시킬 수 있다. 

어간 추출(stemming)의 결과는 품사 정보가 보존되지 않는다.  그렇기 때문에 정규화 결과가 사전에 존재하지 않을 수 있다. 상대적으로 표제어 추출보다 빠르다는 장점이 있다. from nltk.stem import (PorterStemmer, LancasterStemmer) 등의 방식으로 사용할 수 있다. 

표제어 추출(lemmatization)은 말 그대로 단어로부터 표제어를 찾아내는 것이다. 예를 들어, am, are, is의 표제어는 모두 동일하게 be 이다. 우선 형태학적 파싱을 먼저 진행해서 형태소를 추출한다. 형태소(morpheme)는 의미를 갖는 가장 작은 단위를 의미한다. 형태소는 크게 어간(stem)접사(affix)로 구성되는데, 어간은 단어의 의미를 담고있는 핵심적인 부분을 의미하고, 접사는 단어에 추가적인 의미를 주는 부분을 의미한다. 표제어 추출은 단어의 형태가 어느정도 적절히 보존된다는 장점이 있다. 또한, 품사 정보와 함께 제공하면 결과는 단어의 품사 정보를 유지하면서 제공되기 때문에, 더 정확한 결과를 제공할 수 있다. 

# 정수 인코딩(integer encoding)

정수 인코딩을 통해서 각 단어를 고유한 정수에 매핑할 수 있다. 컴퓨터는 텍스트로 된 단어보다 숫자를 더 잘 다루기 때문에, 각 단어들에 대해서 숫자를 부여하는 과정을 거쳐서 단어를 처리하게 된다. 정수 인코딩 하기 위해서는 단어를 빈도수대로 정렬하고, 빈도수가 높은 순서대로 숫자를 부여한다. 이 과정에서 빈도수가 가장 높은 n개의 단어로 제한하는 작업도 함께 수행할 수 있다. 이렇게 단어를 제외하고 사용할 때, 단어 집합에 존재하지 않는 단어들을 Out-Of Vocabulary (OOV)라고 한다. 

빈도수는 Counter를 이용해서 계산할 수 있다. 전체 단어 리스트를 파라미터로 넘기면, 단어의 중복을 제거하고 빈도수를 기록하게 된다. 단어를 key에 빈도수를 value에 저장한다. 또 다른 방법으로는 NLTK에서 제공하는 FreqDist를 사용할 수 있다. 동일하게 단어가 key, 빈도수가 value에 저장되고, most_common()을 통해서 가장 자주 나타난 단어만 리턴받을 수 있다. 

여러 문장을 한 번에 묶으려는 경우, 문장 전체 길이가 서로 다르기 때문에, 모든 문장의 길이를 가장 긴 문장의 길이에 맞추게 된다. 이 때, 빈 공간을 모두 0으로 채우게 되면 zero-padding이라고 부른다. 

단어를 숫자의 벡터로 표현하는 가장 간단한 방법으로 one-hot-encoding이 있다. One-hot-encoding에 대해서 이야기하기 전에 먼저 Vocabulary를 정의하겠다. Vocabulary는 서로 다른 단어들의 집합을 의미한다. 이 때, 동일한 단어의 변형 형태도 서로 다른 단어로 간주한다. Vocabulary 집합을 만들면, 각 단어에 대해서 인덱스를 지정할 수 있다. One-hot-encoding은 각 단어를 나타내는 벡터를 Vocabulary 집합의 크기로 지정한다. 이 때, 각 단어에게 부여된 인덱스의 값만 1로 지정하고 나머지는 모두 0으로 지정한다. 정말 간단한 방법으로 단어를 인코딩할 수 있다는 장점이 있지만, 단어의 개수가 늘어나면 (vocabulary 집합의 크기가 커지면) 벡터를 위한 공간이 계속해서 무한히 커진다는 문제와, 단어들 사이의 유사도를 표현하지 못한다는 단점이 있다. 이러한 문제를 해결하기 위해서 count 기반의 벡터화(LSA, HAL 등), 예측 기반의 벡터화(NNLM, RNNLM, word2vec, FastText 등), count 기반 + 예측 기반(GloVe) 등의 방식들이 제안되었다. 

한국어 전처리 패키지로는 PyKoSpacing (한국어 띄어쓰기 패키지), soynlp(품사 태깅, 단어 토큰화 등) 등이 있다.
Python에서 * 연산자
1. 곱셉, 거듭제곱 연산
2. 데이터를 반복해서 사용하는경우
3. 가변인자를 사용할 때 (임의 개수의 argument를 받는 경우)
- positional argument : * args (tuple에 저장)
- keyword argument : **kwargs (dict에 저장)
- keyword argument는 positional argument 보다 먼저 선언할 수 없다.
4. container type을 unpacking 할 때 
728x90

댓글