[자연어 스터디] 02. 텍스트 전처리

cjkangme·2023년 3월 12일
0
post-custom-banner

딥러닝을 이용한 자연어 처리 입문 교재를 바탕으로 작성되었습니다.

노션에 정리된 것을 거의 그대로 옮겨왔습니다.일부 텍스트 깨짐 등이 있을 수 있습니다.

02-01. 토큰화(Tokenization)

  • 코퍼스 데이터 전처리는 용도에 맞게 토큰화 & 정제 & 정규화 작업을 하게 된다.
  • 토큰화는 코퍼스를 토큰이라는 단위로 나누는 작업이다. 토큰의 단위는 상황에 따라 다르지만, 보통 ‘의미있는 단위’로 정의한다.

1. 단어 토큰화(Word Tokenization)

  • 토큰의 기준을 단어로 하는 경우, 단어 토큰화라고 한다.
    • 여기서 단어는 단어구, 의미를 갖는 문자열로 간주되기도 한다.
  • 입력으로부터 구두점(punctuation)과 같은 문자를 제외시키는 것도 간단한 단어 토큰화 작업에 해당한다.
    • 입력 : Hello, nice to meet you!
    • 출력 : “Hello”, “nice”, “to”, “meet”, “you”

2. 토큰화 중 생기는 선택의 순간

  • 위와 같이 구두점을 그냥 제거해버리면 토큰이 의미를 잃어버리는 경우가 발생할수도 있다.
  • 또한 아포스트로피를 어떻게 토큰으로 분류해야하는지는 매우 복잡한 문제이다.
    • 이럴때는 어떤 용도로 사용할 것인지에 따라 다른데, 용도에 영향이 없는 것을 기준으로 정하면 된다.
print('word_tokenize:', word_tokenize("Don't go Jone's pizza shop."))
print('WordPunctTokenizer:', WordPunctTokenizer().tokenize("Don't go Jone's pizza shop."))
print('Keras text_to_word_sequence:', text_to_word_sequence("Don't go Jone's pizza shop."))
word_tokenize: ['Do', "n't", 'go', 'Jone', "'s", 'pizza', 'shop', '.']
WordPunctTokenizer: ['Don', "'", 't', 'go', 'Jone', "'", 's', 'pizza', 'shop', '.']
Keras text_to_word_sequence: ["don't", 'go', "jone's", 'pizza', 'shop']
  • word_tokenize : 아포스트로피의 의미를 분리하였다.
  • WordPunctTokenizer : 아포스트로피를 별개의 문자로 처리했다.
  • Keras text_to_word_sequence : 아포스트로피를 포함하여 하나의 문자로 처리했으며, 다른 구두점 및 영어 대문자는 제거하였다.

3. 토큰화에서 고려해야할 사항

1) 구두점이나 특수 문자를 단순 제외해서는 안된다.

  • 마침표는 문장의 경계를 알 수 있고, 아포스트로피는 여러 의미를 내포
  • 문자 자체에 구두점이나 특수문자를 갖고 있는 경우가 있다.

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

  • 영어에는 접어(clitic)이라는 개념이 있다. we are을 줄여서 we’re, what are을 줄여셔 what’re라고 하는데, 여기서 re를 접어라고 한다.
  • New York이나 rock ‘n’ roll 처럼 단어 내에 띄어쓰기가 있는 경우가 있다. 토큰화 작업때에도 이들을 단어 하나로 인식할 수 있는 능력도 가져야한다.

3) 표준 토큰화 예제

  • Penn Treebank Tokenization 규칙
    • 표준으로 쓰이고 있는 토큰화 방법 중 하나이다.
    1. 하이픈으로 구성된 단어는 하나로 유지한다.
    2. doesn’t와 같이 아포스트로피로 접어가 함께하는 단어는 분리해준다.

4. 문장 토큰화(Sentence Tokenization)

  • 토큰의 단위가 문장인 경우이다.
    • 마침표, 느낌표, 물음표가 항상 문장의 끝을 나타내지는 않기 때문에 이를 고려해야한다.
    • 사용하는 코퍼스가 어떤 국적의 언어인지, 해당 코퍼스에서 특수문자가 어떻게 사용되는지에 따라 규칙을 정의할 수는 있으나 100% 정확도를 얻는 것은 쉬운 일이 아니다.
  • 코퍼스가 정제되어 있지 않은 상태일 경우, 우선 사용하는 용도에 맞게 문장 토큰화가 필요할 수 있다.
  • NLTK는 영어 문장의 토큰화를 수행하는 sent_tokenize를 지원하고 있다.
  • 한국어의 경우 박상길님이 개발한 KSS(Korean Sentence Splitter)를 추천한다.
    !pip install kss
    import kss
    
    text_ko = "딥 러닝 자연어 처리가 재미있기는 합니다. 그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다. 이제 해보면 알걸요?"
    print("한국어 문장 토큰화: ", kss.split_sentences(text))
    한국어 문장 토큰화:  ['딥 러닝 자연어 처리가 재미있기는 합니다.', 
    										 '그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다.', 
    										 '이제 해보면 알걸요?']

5. 한국어에서의 토큰화의 어려움

  • 한국어는 영어와 달리 띄어쓰기만으로는 토큰화를 하기에 부족하다. 이는 한국어가 교착어라는 점에서 기인한다.

1) 교착어의 특성

  • ‘그는’, ‘그가’, ‘그에게’, ‘그의’와 같은 조사가 띄어쓰기 없이 붙게 된다. 이러한 경우 같은 단어임에도 조사로 인해 다른 단어로 인식이 되어 자연어 처리가 힘들고 번거로워지는 경우가 많다.
    • 한국어 NLP에서 조사는 분리해줄 필요가 있다.
  • 한국어 토큰화를 위해 형태소(morpheme)란 개념을 반드시 이해해야 한다.
    • 자립 형태소 : 접사, 어미, 조사와 상관 없이 자립해서 사용할 수 있는 형태소. 그 자체로 단어가 된다.
    • 의존 형태소 : 다른 형태소와 결합하여 사용되는 형태소. 접사, 어간, 어미, 조사를 말한다.
    • 예시 : 에디가 책을 읽었다.
      • 자립 형태소 : “에디”, “책”
      • 의존 형태소 : “가”, “을”, “읽-”, “-었”, “-다”
  • 한국어에서 단어 토큰화와 유사한 형태를 얻으려면 형태소 토큰화를 수행해야 한다.

2) 한국어는 띄어쓰기가 영어보다 잘 지켜지지 않는다.

  • 한국어 코퍼스의 많은 경우는 띄어쓰기가 틀렸거나 지켜지지 않는 경우가 많다.

6. 품사 태깅(Part-of-speech tagging)

  • 단어의 표기는 같지만, 품사에 따라서 단어의 의미가 달라지기도 한다.
    • ex) 영어 fly는 동사 - ‘날다’, 명사 - ‘파리’ 두가지의 의미를 갖고 있다.
    • ex) 한국어 ‘못’은 명사-’못’, 부사 - ‘할수없다’ 두가지의 의미를 갖고 있다.
  • 이러한 특성으로 단어 토큰화 과정에서 각 단어가 어떤 품사로 쓰였는지를 구분해 놓기도 한다. 이러한 작업을 품사 태깅이라고 한다.

7. NLTK와 KoNLPy를 이용한 영어, 한국어 토큰화 실습

  • NLTK에서는 Penn Treebanck POS Tags라는 기준을 사용하여 품사를 태깅한다.
    • PRP : 인칭 대명사
    • VBR : 동사
    • RB : 부사
    • VBG : 현재부사
    • IN : 전치사
    • NNP : 고유명사
    • NNS : 복수명사
    • CC : 접속사
    • DT : 관사
  • KoNLPy에서 사용할 수 있는 형태소 분석기는 Okt(Open Korea Text), 메캅(Mekab), 코모란(Komoran), 한나눔(Hannanum), 꼬꼬마(KKma)가 있다.
  • koNLPy에서 제공하는 형태소 분석기들은 공통적인 메소드들을 제공한다.
    • morphs : 형태소 추출
    • pos : 품사 태깅
    • nouns : 명사 추출
text_ko = "아름답지만 다소 복잡하기도한 한국어는 전세계에서 13번째로 많이 사용되는 언어입니다."

print('OKT 형태소 추출 :', okt.morphs(text_ko))
print('OKT 품사 태깅 :', okt.pos(text_ko))
print('OKT 명사 추출 :', okt.nouns(text_ko))

print('꼬꼬마 형태소 추출 :', kkma.morphs(text_ko))
print('꼬꼬마 품사 태깅 :', kkma.pos(text_ko))
print('꼬꼬마 명사 추출 :', kkma.nouns(text_ko))
OKT 형태소 추출 : ['아름답지만', '다소', '복잡하', '기도', '한', '한국어', '는', '전세계', '에서', '13', '번', '째', '로', '많이', '사용', '되는', '언어', '입니다', '.']
OKT 품사 태깅 : [('아름답지만', 'Adjective'), ('다소', 'Noun'), ('복잡하', 'Adjective'), ('기도', 'Noun'), ('한', 'Josa'), ('한국어', 'Noun'), ('는', 'Josa'), ('전세계', 'Noun'), ('에서', 'Josa'), ('13', 'Number'), ('번', 'Noun'), ('째', 'Suffix'), ('로', 'Josa'), ('많이', 'Adverb'), ('사용', 'Noun'), ('되는', 'Verb'), ('언어', 'Noun'), ('입니다', 'Adjective'), ('.', 'Punctuation')]
OKT 명사 추출 : ['다소', '기도', '한국어', '전세계', '번', '사용', '언어']

꼬꼬마 형태소 추출 : ['아름답', '지만', '다소', '복잡', '하', '기', '도', '한', '한국어', '는', '전세계', '에서', '13', '번째', '로', '많이', '사용', '되', '는', '언어', '이', 'ㅂ니다', '.']
꼬꼬마 품사 태깅 : [('아름답', 'VA'), ('지만', 'ECE'), ('다소', 'MAG'), ('복잡', 'NNG'), ('하', 'XSV'), ('기', 'ETN'), ('도', 'JX'), ('한', 'MDN'), ('한국어', 'NNG'), ('는', 'JX'), ('전세계', 'NNG'), ('에서', 'JKM'), ('13', 'NR'), ('번째', 'NNB'), ('로', 'JKM'), ('많이', 'MAG'), ('사용', 'NNG'), ('되', 'XSV'), ('는', 'ETD'), ('언어', 'NNG'), ('이', 'VCP'), ('ㅂ니다', 'EFN'), ('.', 'SF')]
꼬꼬마 명사 추출 : ['복잡', '한국어', '전세계', '13', '13번째', '번째', '사용', '언어']
  • 형태소 분석기별로 성능과 결과가 다르게 나오기 때문에, 필요 용도에 따라 적합한 형태소 분석기를 판단하고 사용해야 한다.
  • 예를들어 속도가 중요하다면 메캅을 사용할 수 있다.

02-02. 정제(Cleaning) and 정규화(Normalization)

  • 토큰화 작업 전, 후에는 텍스트 데이터를 용도에 맞게 정제 및 정규화하는 작업이 항상 존재한다.
    • 정제 : 갖고 있는 코퍼스의 노이즈 데이터를 제거
    • 정규화 : 표현 방법이 다른 단어들을 통합시켜 같은 단어로 만들어 준다.

1. 규칙에 기반한 표기가 다른 단어들의 통합

  • 같은 의미이지만, 표기가 다른 단어들을 하나의 단어로 정규화하는 방법을 사용할 수 있다.
  • 필요에 따라 직접 코딩을 통해 정의할 수 있다.

2. 대, 소문자 통합

  • 영어권 언어에서 대,소문자를 통합하여 단어의 개수를 줄일 수 있다.
  • 대문자는 문장의 첫글자 등 특정 상황에서만 쓰이기 때문에 주로 소문자로 변환된다.
    • 다만 고유명사를 나타내는 대문자의 경우 대문자로 유지되어야 하는 등 무작정 통합해서는 안된다.
  • 하지만 결국 코퍼스는 사용자들로 부터 나온 것이고, 사용자들이 대소문자를 올바른 방법과 상관없이 사용한다면, 아무리 규칙을 잘 세워도 그다지 도움이 되지 않을 수 있다. 이러한 경우는 코퍼스 전체를 소문자로 바꾸는 것이 종종 더 실용적인 해결책이 되기도 한다.

3. 불필요한 단어의 제거

  • 노이즈 데이터는 자연어가 아니면서 아무 의미도 갖지 않는 글자들(특수 문자 등)을 의미한다.
  • 불필요한 단어를 제거하는 방법으로는 불용어 제거, 등장 빈도가 적은 단어, 길이가 짧은 단어를 제거하는 방법이 있다. 불용어는 이후 챕터에서따로 다룰 것이다.

1) 등장빈도가 적은 단어

  • 텍스트 데이터에서 너무 적게 등장해서 자연어 처리에 도움이 되지 않는 경우
    • 10만개의 데이터에서 단 5번밖에 등장하지 않았다면 직관적으로 분류에 거의 도움이 되지 않는다.

2) 길이가 짧은 단어

  • 영어권 언어에서 길이가 짧은 단어는 대부분 불용어에 해당하기 때문에 이를 삭제하는 것만으로 효과를 볼 수 있다.
  • 하지만 한국어에서는 이런 방법이 크게 유효하지 않을 수 있다.
    • 한국어 단어의 평균 길이는 2~3 정도로, 짧은 단어를 나누기 어렵다.
    • 한자어나, 한 글자만으로 의미를 나타내는 단어가 많다.
  • 길이가 짧은 단어는 정규 표현식을 이용하여 삭제할 수 있다.

4. 정규 표현식(Regular Expression)

  • HTML문서로 가져온 코퍼스라면 HTML 태그가 달려있을 것이고, 기사를 태그했다면 기자 이름, 게시일 등 필요없는 정보다 다수 존재할 수 있다.
  • 정규표현식을 통해 이러한 글자들을 규칙에 기반해 한 번에 제거할 수 있다.

02-03 어간 추출 및 표제어 추출

  • 눈으로 봤을 때는 서로 다른 단어이지만 의미가 같을 때, 하나의 단어로 일반화하면 단어 수를 줄일 수 있다.
  • 이는 빈도수 기반 학습에 사용되는 BoW 표현을 위해 자주 사용된다.
  • 정규화 작업의 가장 큰 목표는 코퍼스의 복잡성을 줄이는 것임을 잊지 말자.

1. 표제어 추출(**Lemmatization)**

  • 표제어(Lemma)는 기본 사전형 단어 정도의 의미를 갖는다.
  • 표제어 추출은 서로 다른 형태를 가진 단어를 하나의 표제어로 통일하여 단어의 개수를 줄일 수 있는지 판단하는 과정이다.
    • 예를들어 am, are, is는 be라는 표제어로 통일 할 수 있다.
  • 표제어를 추출하는 가장 섬세한 방법은 형태소로부터 단어들을 만들어가는 형태학(morphology)적 파싱을 먼저 진행하는 것이다. 형태소에는 어간과 접사가 존재한다.
    1. 어간(stem) : 단어의 의미를 담고 있는 단어의 핵심 부분
    2. 접사(affix) : 단어에 추가적인 의미를 주는 부분
    • 예를들어 cats는 어간 cat과 접사 -s로 나눌 수 있다.
  • NLTK에서는 WordNetLemmatizer라는 표제어 추출을 위한 도구를 제공한다.
    • WordNetLemmatizer를 효과적으로 사용하기 위해서는 본래 단어의 품사 정보를 제공해주어야 한다.
    • 예를들어 has, dies, watched가 동사로 쓰였다는 것을 알려줘야, have, die, watch라는 정확한 동사 Lemma를 출력하게 된다.
  • 단어를 표제어로 변경하면 품사 정보를 잃게 된다.

2. 어간 추출(Stemming)

  • 어간(Stem)을 추출하는 작업이다.
  • 이 작업은 정해진 규칙만 보고 단어의 어미를 자르는 어림짐작의 방식을 사용하기 때문에 어간 추출 후 나오는 단어는 사전에 존재하지 않는 단어일 수 있다.
  • 어간 추출을 위한 알고리즘 중 하나로 포터 알고리즘(Poter Algorithm)을 사용할 수 있다.
    from nltk.stem import PorterStemmer
    from nltk.tokenize import word_tokenize
    
    stemmer = PorterStemmer()
    
    sentence = "This was not the map we found in Billy Bones's chest, but an accurate copy, complete in all things--names and heights and soundings--with the single exception of the red crosses and the written notes."
    
    print("어간 추출 전 :", word_tokenize(sentence))
    print("어간 추출 후 :", [stemmer.stem(word) for word in word_tokenize(sentence)])
    어간 추출 전 : ['This', 'was', 'not', 'the', 'map', 'we', 'found', 'in', 'Billy', 'Bones', "'s", 'chest', ',', 'but', 'an', 'accurate', 'copy', ',', 'complete', 'in', 'all', 'things', '--', 'names', 'and', 'heights', 'and', 'soundings', '--', 'with', 'the', 'single', 'exception', 'of', 'the', 'red', 'crosses', 'and', 'the', 'written', 'notes', '.']
    어간 추출 후 : ['thi', 'wa', 'not', 'the', 'map', 'we', 'found', 'in', 'billi', 'bone', "'s", 'chest', ',', 'but', 'an', 'accur', 'copi', ',', 'complet', 'in', 'all', 'thing', '--', 'name', 'and', 'height', 'and', 'sound', '--', 'with', 'the', 'singl', 'except', 'of', 'the', 'red', 'cross', 'and', 'the', 'written', 'note', '.']
    • accurate → accur (-ate 제거)
    • exception → except (-ion 제거)
    • 그외 -ize, -ance, -ic, -y를 제거한다.
  • nltk에서는 LancasterStemmer라는 전혀 다른 어간 추출 알고리즘도 제공한다.
    • 동일한 단어라도 두 스태머는 전혀 다른 결과를 보여준다.
    • 때문에 어간 추출을 할 경우 스태머를 각각 적용해보고 어떤 스태머가 해당 코퍼스에 적합한지를 판단 후에 사용해야 한다.
  • 어간 추출 후에는 일반화가 지나치게 되거나, 덜 되거나 하는 경우가 있을 수 있다.
    • 이 경우 의미가 동일한 경우만 얻기를 원하는 정규화의 목적에 맞지 않게 된다.

3. 한국어에서의 어간 추출

  • 한국어는 5언 9품사의 구조를 갖고 있다.
    • 체언 : 명사, 대명사, 수사
    • 수식언 : 관형사, 부사
    • 관계언 : 조사
    • 독립언 : 감탄사
    • 용언 : 동사, 형용사
  • 이중 용언에 해당하는 동사와 형용사는 어간(stem)과 어미(ending)의 결합으로 구성된다.

1) 활용(conjugation)

  • 활용이란 용언의 어간이 어미를 가지는 일을 말한다.
    • 어간 : 용언을 활용할 때 원칙적으로 모양이 변하지 않는 부분을 말한다. 모양이 변하는 예외도 존재한다 (긋다, 긋고, → 그어서, 그어라)
    • 어미 : 용언의 어간 뒤에 붙어서 활용하면서 변하는 부분이며, 여러 문법적 기능을 수행한다.
  • 활용은 두가지 종류로 나뉜다.
    • 규칙 활용 : 어간의 모습이 일정한 활용
    • 불규칙 활용 : 어간의 모습이 변하는 활용

2) 규칙 활용

  • 규칙활용은 어간이 어미를 취할 때, 어간의 모습이 일정하다.
    • 잡(어간) + 다(어미) → 잡아서, 잡고 등

3) 불규칙 활용

  • 어간이 어미를 취할 때, 어간의 모습이 바뀌거나 취하는 어미가 특수한 어미일 경우를 말한다.
    • 듣(어간) + 다(어미) → 들어라 등 - 어간이 바뀌는 경우
    • 오르(어간) + 아/어(어미) → 올라, 푸르(어간) + 아/어(어미) → 푸르러 - 특수한 어미를 취하는 경우
  • 단순한 분리만으로는 어간이 추출되지 않고, 좀 더 복잡한 규칙을 필요로 한다.

02-04. 불용어 (Stopword)

  • 불용어는 큰 의미가 없는 단어 토큰을 말한다.
    • 예를 들어 I, my, me, over, 조사, 접미사는 문장에서는 자주 등장하지만, 실제 의미 분석에는 거의 기여하지 않는다.
  • NLTK는 100여개 이상의 영어 단어를 불용어로 패키지 내에서 미리 정의하고 있다. 또한 개발자가 직접 정의하기도 한다.
    • nltk.stopwords.words(”english”)를 통해 NLTK가 정의한 영어 불용어 리스트를 확인할 수 있다.

1) NLTK에서 불용어 확인하기

stopwords_list = stopwords.words('english')

print("불용어 개수:", len(stopwords_list))
print("불용어 확인:", stopwords_list[:10])
불용어 개수: 179
불용어 확인: ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're"]

2) NLTK를 통해서 불용어 제거하기

  • 불용어 리스트를 저장한 뒤, for ~ in loop를 돌면서 불용어가 아닌 단어만 결과에 추가하는 식으로 불용어를 제거할 수 있다.
    • 빠른 연산을 위해 set을 사용하는 것을 추천
stopwords_list = set(stopwords.words('english'))

sentence = "Family is not an important thing. It's everything."

word_tokens = word_tokenize(sentence)

result = []
for word_token in word_tokens:
    if word_token not in stopwords_list:
        result.append(word_token)

print("불용어 제거 결과:", result)
불용어 제거 결과: ['Family', 'important', 'thing', '.', 'It', "'s", 'everything', '.']

3) 한국어에서 불용어 제거하기

  • 한국어에서 불용어를 제거하는 방법으로는, 토큰화 후 조사와 접속사 등을 제거하는 방법이 있다.
  • 하지만 명사와 형용사 단어 중에서도 불용어로 제거하고 싶은 단어가 생기는 경우가 많기 때문에, 사용자가 직접 불용어 사전을 만들게 된다.
  • 보편적으로 선택할 수 있는 한국어 불용어 리스트는 아래 링크를 참조하자 (절대적 기준 아님!)

02-06 정수 인코딩 (Interger Encoding)

  • 컴퓨터는 텍스트보다 숫자를 더 잘 처리할 수 있으므로, 자연어처리는 텍스트를 숫자로 바꾸는 여러가지 기법이 있다.
  • 예를들어 텍스트에 단어가 5000개가 있다면 1~5000번까지 각각의 단어와 맵핑되는 정수 인덱스를 부여할 수 있다. 일반적으로 등장 빈도수 기준으로 정렬한 뒤에 부여한다.

1. 정수 인코딩 (Interger Encoding)

  1. 단어를 빈도수 순으로 정렬한 단어 집합(vocabulary)를 만든다.
  2. 빈도수가 낮거나 높은 숫자부터 정수를 부여한다.

1) dictionary 사용하기

  1. 텍스트를 문장 토큰화한다.
    1. sent_tokenize 함수를 이용한다.
  2. 단어 토큰화를 진행한다.
  3. 단어에 대해 정규화를 진행한다.
    1. 단어를 소문자화 한다.
    2. 불용어를 제거한다
    3. 길이가 2이 하인 경우에 대해 단어를 제거한다.
  4. 전처리가 끝난 토큰을 딕셔너리와 리스트에 각각 저장한다.
    • 이렇게 정수 인코딩을 하면 상위 n개의 단어만 사용할 수 있다.
  5. 토큰화 된 단어를 정수로 바꾸는 작업을 한다.
    1. 빈도수가 낮거나 불용어 등 제거된 단어는 인덱스 딕셔너리에 존재하지 않게 되는데, 이러한 단어들을 OOV(Out-Of-Vocabulary)라고 한다.
    2. 이러한 단어들은 OOV라는 단어를 새로 추가하여 관리한다고 하자.
  • 이러한 원리로 정수 인코딩이 진행된다.
  • 이보다는 좀더 쉽게 하기 위해서 Counter, FreqDist, enumerate나 keras tokenizer를 사용할 수 있다.
💡 나중에 필요 시 공부하자 [https://wikidocs.net/31766](https://wikidocs.net/31766)

02-07. 패딩(Padding)

  • 각 문장(또는 문서)는 서로 길이가 다를 수 있다. 그런데 기계는 길이가 전부 동일한 문서들에 대해서는 하나의 행렬로 보고, 한꺼번에 묶어서 처리할 수 있다.
  • 즉 병렬 연산을 위해 여러 문장의 길이를 임의로 동일하게 맞춰주는 작업을 패딩이라고 한다.

1. Numpy로 패딩하기

  • 먼저 정수 인코딩이 완료된 문장을 준비한다.
  • 최대길이 문장을 기준으로 가상의 문제 ‘PAD’ : 0 을 만들어서 길이가 짧은 문장에 숫자 0을 채워준다. (0은 관습)
  • 이렇게 숫자 0을 사용하여 데이터의 shape을 조정하는 것을 제로 패딩이라고 한다.

2. 케라스 전처리 도구로 패딩하기

  • 케라스는 패딩을 위한 도구로 pad_sequences() 를 제공한다.

케라스 전처리 도구 다루기

  • 기본적으로 케라스 전처리 도구는 문장의 앞을 0으로 채운다.
    • 문장의 뒤를 0으로 채우고 싶다면 padding = 'post' 로 가능하다.
  • 또한 문장의 최대 길이를 기준으로 패딩하지 않아도 된다.
    • maxlen=n 인자를 통해 n의 길이로 패딩할 수 있다. 이 때 n보다 긴 길이의 문자는 정보가 손실된다.
    • 기본적으로 뒤에있는 단어를 남기고, 앞에 있는 단어를 삭제한다.
    • 앞쪽에 있는 단어를 남기고 싶다면 truncation='post' 인자를 사용할 수 있다.
    • 0으로 패딩하고 싶지 않다면 value=n 으로 다른 숫자로 패딩이 가능하다.

02-08. 원-핫 인코딩(One-Hot Encoding)

  • 단어 집합(vocabulary)은 서로 다른 단어들의 집합이다.
  • 단어 집합에 있는 단어들을 벡터로 바꾸는 것을 작업에는 여러가지 방법이 있는데, 원-핫 인코딩은 그 방법 중 하나이다.
  • 텍스트를 단어 토큰화 한 뒤, 정수 인코딩 까지 진행한 데이터에 대해 원-핫 인코딩을 진행한다.

1. 원-핫 인코딩이란?

  • 단어 집합의 크기를 벡터의 차원으로 하고, 표현하고 싶은 단어의 인덱스에 1의 값을, 다른 인덱스에는 0을 부여하는 단어의 벡터 표현 방식이다.
  • 이렇게 표현된 벡터를 원-핫 벡터(One-Hot Vector)라고 한다.

2. 케라스를 이용한 원-핫 인코딩

  • 케라스가 제공하는 to_categorical 함수를 이용하여 원-핫 인코딩이 가능하다.

3. 원-핫 인코딩의 한계

  • 단어의 개수가 늘어날 수록, 벡터를 저장하기 위해 필요한 공간이 계속 늘어난다는 단점이 있다. (공간 측면에서 매우 비효율적인 표현 방법)
  • 단어의 유사도를 표현할 수 없다는 단점도 존재한다.
    • 강아지, 개, 냉장고라는 단어가 있을 때 강아지와 개가 유사하다는 것이 명확하지만, 원-핫 인코딩을 한다면 어떤 단어가 더 유사한지 알 수 없다.
  • 유사도를 표현할 수 없을 때 발생하는 문제점은, 삿포로 숙소를 검색했을 때 료칸, 호텔, 게스트 하우스를 보여줘야하는데 숙소와 유사한 단어를 알 수 없어 이를 보여줄 수 없다.
  • 이러한 단점을 해결하기 위해 단어의 잠재 의미를 반영하여 다차원 공간에 벡터화 하는 기법이 있다.
    • LSA(잠재 의미 분석), HAL : 카운트 기반의 벡터화 방법
    • NNLM, RNNLM, Word2Vec, FastText : 예측 기반의 벡터화 방법
    • GloVe : 두 가지 방법을 모두 사용하는 방법
    • 이들 기법에 대해서는 워드 임베딩 챕터에서 다루게 될 것이다.

02-09. 데이터의 분리(Splitting Data)

  • 머신 러닝 모델을 학습시키고 평가하기 위해서는 데이터를 적절하게 분리하는 작업이 필요하다.
  • 이번에는 지도 학습을 위한 데이터 분리 작업에 대해 배워보자

1. 지도 학습 (Supervised Learning)

  • 지도 학습의 훈련 데이터는 ‘문제’에 해당하는 데이터(X)와 레이블이라고 부르는 정답 데이터(y)로 구성되어있다.
  • 머신은 문제와 정답을 통해 학습한 뒤, 정답이 없고 문제만 존재하는 데이터에 대해 정답을 잘 예측할 수 있어야 한다.
  • 이를 위해 기계를 학습시키는 데에 사용하는 학습 데이터와 기계의 성능을 평가하기 위해 사용하는 테스트 데이터를 분리해 놓아야 한다.

2. X와 y분리하기

1) zip 함수를 이용한 분리

  • zip() 함수는 동일한 개수를 가지는 시퀀스 자료형에서, 각 순서에 등장하는 원소들끼리 묶어주는 역할을 한다.
    sequences = [['a', 1], ['b', 2], ['c', 3]]
    X, y = zip(*sequences)
    print('X 데이터 :',X)
    print('y 데이터 :',y)
    X 데이터 : ('a', 'b', 'c')
    y 데이터 : (1, 2, 3)

2) 데이터프레임을 이용하여 분리하기

  • 리스트 또는 nparray를 데이터프레임으로 변환하면 손쉽게 분리가 가능하다.
    values = [['당신에게 드리는 마지막 혜택!', 1],
    ['내일 뵐 수 있을지 확인 부탁드...', 0],
    ['도연씨. 잘 지내시죠? 오랜만입...', 0],
    ['(광고) AI로 주가를 예측할 수 있다!', 1]]
    columns = ['메일 본문', '스팸 메일 유무']
    
    df = pd.DataFrame(values, columns=columns)
    df

3) Numpy를 이용하여 분리하기

  • Numpy의 슬라이싱을 사용하여 데이터를 분리할 수 있다.
    np_array = np.arange(0,16).reshape((4,4))
    print('전체 데이터 :')
    print(np_array)
    전체 데이터 :
    [[ 0  1  2  3]
     [ 4  5  6  7]
     [ 8  9 10 11]
     [12 13 14 15]]
    X = np_array[:, :3]
    y = np_array[:,3]
    
    print('X 데이터 :')
    print(X)
    print('y 데이터 :',y)
    X 데이터 :
    [[ 0  1  2]
     [ 4  5  6]
     [ 8  9 10]
     [12 13 14]]
    y 데이터 : [ 3  7 11 15]

3. 테스트 데이터 분리하기

💡 알고 있는 내용이므로 다소 간단하게 짚고 넘어가자

1) 사이킷 런을 이용하여 분리하기

train_test_split(X, y, {test_size | train_size}, random_state)

  • X : 독립 변수 데이터 (배열이나 데이터프레임)
  • y : 종속 변수 데이터 (레이블)
  • test_size : 테스터용 데이터 개수 지정 (1보다 작은 경우 비율을 나타냄)
  • train_size : 학습용 데이터 개수 지정 (1보다 작은 경우 비율을 나타냄)
    • test_size, train_size 둘 중 하나만 기재하면 된다.
  • random_state : 난수 시드

2) 수동으로 분리하기

  1. 학습데이터와 테스트 데이터의 개수를 정한다. 한 쪽을 정한 뒤 다른 쪽은 전체에서 한 쪽을 빼는 식으로 구할 수 있다.

  2. 슬라이싱을 통해서 데이터를 구분할 수 있다.

    X_test = X[num_of_train:] # 전체 데이터 중에서 20%만큼 뒤의 데이터 저장
    y_test = y[num_of_train:] # 전체 데이터 중에서 20%만큼 뒤의 데이터 저장
    X_train = X[:num_of_train] # 전체 데이터 중에서 80%만큼 앞의 데이터 저장
    y_train = y[:num_of_train] # 전체 데이터 중에서 80%만큼 앞의 데이터 저장
  3. 단 이렇게 수동으로 분리하는 경우 train_test_split과 달리 데이터가 뒤섞이지 않는다. 데이터를 섞는 과정은 추후 진행할 것이다.

2-10. 한국어 전처리 패키지

1. PyKoSpacing

!pip install git+https://github.com/haven-jeon/PyKoSpacing.git
  • 띄어쓰기가 되어있지 않은 문장을 띄어쓰기 한 문장으로 변환해주는 패키지
  • 대용량 코퍼스 학습을 통해 만들어져 있어 준수한 성능을 갖고 있다.

2. Py-Hanspell

!pip install git+https://github.com/ssut/py-hanspell.git
  • 네이버 한글 맞춤법 검사기를 바탕으로 만들어진 패키지이다.
  • 띄어쓰기 또한 보정해준다.

3. SOYNLP

!pip install soynlp
  • 품사 태깅, 단어 토큰화 등을 지원하는 단어 토크나이저이다.
  • 비지도 학습으로 단어 토큰화를 한다는 특징을 갖고 있으며, 데이터에 자주 등장하는 단어들을 단어로 분석한다.
  • 응집확률과 브랜칭 엔트로피를 활용한다.

1) 신조어 문제

  • 기존의 형태소 분석기들은 분석기에 등록되지 않은 신조어와 같은 단어를 제대로 구분하지 못한다는 단점이 있다.
    • 르세라핌 → 르 + 세라핌
    • 총공 → 총 + 공
  • soynlp에서는 텍스트 데이터에서 자주 연결되어 등장하는 문자열을 한 단어로 판단하고, 또 단어 앞뒤에 붙는 독립된 다른 단어들이 계속 등장하면 판탄하는 식이다.

2) 학습하기

  • soynlp는 학습 기반의 단어 토크나이저로, 학습 과정을 통해 전체 코퍼스에서 응집 확률과 브랜칭 엔트로피 단어 점수표를 만들어야 한다.
  • WordExtractor.extract() 를 통해 전체 코퍼스에 대해 단어 점수표를 계산한다.

3) SOYNLP의 응집 확률(cohesion probability)

  • 응집 확률은 문자열이 얼마나 자주 응집하여 등장하는지 판단하는 척도이다.
  • 인스턴스[”문자열”].cohesion_forward로 확인할 수 있다.
  • 문자 단위로 분리하여 내부 문자열을 만드는 과정에서, 어떤 문자열이 주어졌을 때 그 다음 문자가 나올 확률을 계산하여 누적곱 한다.
    • 르세라핌최고라는 단어가 있다고 할 때
      • cohesion(2) = ‘르’가 나왔을 때 ‘르세’가 나올 확률(P1)
      • cohesion(3) = P1 * ‘르세’가 나왔을 때 ‘르세라’가 나올 확률(P2) → 제곱근을 씌움
      • cohesion(4) = P2 * ‘르세라’가 나왔을 때 ‘르세라핌’이 나올 확률(P3) → 세제곱근
      • 이런 식이다.
    • 이렇게 응집 확률을 계산하면 아마 다음과 같은 결과가 나올 것이다.
      • 르세라는 르세보다 응집 확률이 높다
      • 르세라핌은 르세라보다 응집 확률이 높다
      • 르세라핌최는 르세라핌보다 응집 확률이 낮다
    • 이때 르세라핌의 결합도가 가장 높다고보고, 르세라핌을 하나의 단어로 보기에 가장 적합한 문자열이라고 판단할 수 있다.

4) SOYNLP의 브랜칭 엔트로피(branching entropy)

  • 인스턴스[”문자열”].right_branching_entropy 로 확인할 수 있다.
  • 브랜칭 엔트로피는 주어진 문자열에서 얼마나 다음 문자가 등장할 수 있는지를 판단하는 척도이다. 이를 위해 확률 분포의 엔트로피값을 사용한다.
    • ‘초’ 다음에 등장할 문자는 무엇일까?
    • ‘초보’ 다음에 등장할 문자는 무엇일까?
    • ‘초보운’ 다음에 등장할 문자는 무엇일까?
    • 이렇게 완성된 단어에 가까워 질수록 문맥에 따라 정답 후보가 줄어든다는 것을 의미한다.
  • 브렌칭 엔트로피 값이 0이면 다음에 올 글자가 명백하다는 뜻이다.
    • 다음에 올 글자의 가능성이 많을 수록 큰 값을 가진다.

5) SOYNLP의 L tokenizer

  • 한국어에서 띄어쓰기 단위로 나눈 어절은 L토큰 + R토큰의 형식을 가질때가 많다
    • 밥 + 을, 나 + 는, 시내 + 에서, 경찰 + 을
  • LTokenizer는 L토큰 + R토큰으로 나누되, 분리 기준으로 점수가 가장 높은 것을 찾아낸다.

6) 최대 점수 토크나이저

  • 최대 점수 토크나이저는 띄어쓰기가 되어있지 않은 문장에서 점수를 바탕으로 글자 시퀀스를 찾아 낸다.

4. SOYNLP를 이용한 반복되는 문자 정제

  • SNS나 채팅데이터는 이모티콘이 불필요하게 연속되는 경우가 많은데, 이를 서로 다른 단어로 처리하는 것을 불필요하다.
  • SOYNLP에서는 emoticon_normalize() 를 통해 반복되는 문자를 하나로 정규화 시킨다.
    • num_repeats= 인자를 통해 반복되는 문자를 몇글자까지 정규화 시킬지 지정할 수 있다.

5. Customized KoNLPy

!pip install customized_konlpy
  • 위와 같은 노력에도 불구하고, 형태소 분석기를 통한 단어 토큰화가 잘 이루어지지 않는 경우가 많다.
    • ex) 이강철 → 이 + 강철, 은경이 → 은 + 경이
  • 이렇게 잘못 분류되는 경우를 막기 위해서 형태소 분석기에 사용자 사전을 추가해 줄 수 있다.
    • Customized_Konlpy 패키지를 이용하면 사용자 사전을 매우 쉽게 추가할 수 있다.
  • 사용법
    • 먼저 사용할 형태소 분석기를 선언한다.

    • 형태소 분석기의 add_dictionary('단어', '품사') 를 메소드 형식으로 실행하면 사전 추가를 해줄 수 있다.

      from ckonlpy.tag import Twitter
      twitter = Twitter()
      twitter.morphs('나균안이라는 이름이 있다니 정말 특이하다.')
      >> ['나균', '안이', '라는', '이름', '이', '있다니', '정말', '특', '이하', '다', '.']
      twitter.add_dictionary('나균안', 'Noun')
      twitter.morphs('나균안이라는 이름이 있다니 정말 특이하다.')
      >> ['나균안', '이라는', '이름', '이', '있다니', '정말', '특', '이하', '다', '.']
💡 궁금한 점 : 특이하다를 특 + 이하 + 다로 분류하는 것이 맞을까? 사전에 ‘특이’라는 단어를 추가해봤지만 여전히 분리되어 토큰화가 이루어진다.
post-custom-banner

0개의 댓글