개발자로 후회없는 삶 살기

NLP PART.텍스트 전처리2 본문

[AI]/[네이버 BoostCamp | 학습기록]

NLP PART.텍스트 전처리2

몽이장쥰 2022. 12. 19. 01:11

서론

※ 이 포스트는 다음 교재의 학습이 목적임을 밝힙니다.
https://wikidocs.net/book/2155

 

딥 러닝을 이용한 자연어 처리 입문

많은 분들의 피드백으로 수년간 보완된 입문자를 위한 딥 러닝 자연어 처리 교재 E-book입니다. 오프라인 출판물 기준으로 코드 포함 **약 1,000 페이지 이상의 분량*…

wikidocs.net

 

-> 전체 코드

https://github.com/SangBeom-Hahn/NLP/blob/main/nlp_book/2_1_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EC%A0%84%EC%B2%98%EB%A6%AC.ipynb

 

GitHub - SangBeom-Hahn/NLP

Contribute to SangBeom-Hahn/NLP development by creating an account on GitHub.

github.com

 

- 다룰 내용

1. 정수 encoding

2. padding

3. one-hot encoding

4. 한국어 전처리 패키지

 

 

본론

- 정수 인코딩

컴퓨터는 숫자를 더 잘 이해하기에 텍스트를 숫자로 바꿔야 하고 바꾸는 여러 가지 기법들이 있습니다.

이를 수행하기 위한 순서는 알듯이 토큰화 > 정제 및 추출입니다. 이가 선행되어야 합니다.

 

단어에 정수를 부여하는 방법 중 하나로 단어를 빈도수 순으로 정렬한 단어 집합(vocabulary)을 만들고, 빈도수가 높은 순서대로 차례로 낮은 숫자부터 정수를 부여하는 방법입니다. 

 

# 원본 문장
raw_text = "A barber is a person. a barber is good person. a barber is huge person. he Knew A Secret! The Secret He Kept is huge secret. Huge secret. His barber kept his word. a barber kept his word. His barber kept his secret. But keeping and keeping such a huge secret to himself was driving the barber crazy. the barber went up a huge mountain."

# 문장 토큰화
sentences = sent_tokenize(raw_text)
print(sentences)

# 결과
['A barber is a person.', 'a barber is good person.', 'a barber is huge person.', 'he Knew A Secret!', 'The Secret He Kept is huge secret.', 'Huge secret.', 'His barber kept his word.', 'a barber kept his word.', 'His barber kept his secret.', 'But keeping and keeping such a huge secret to himself was driving the barber crazy.', 'the barber went up a huge mountain.']

# 정제 및 추출 결과
[['barber', 'person'], ['barber', 'good', 'person'], ['barber', 'huge', 'person'], ['knew', 'secret'], ['secret', 'kept', 'huge', 'secret'], ['huge', 'secret'], ['barber', 'kept', 'word'], ['barber', 'kept', 'word'], ['barber', 'kept', 'secret'], ['keeping', 'keeping', 'huge', 'secret', 'driving', 'barber', 'crazy'], ['barber', 'went', 'huge', 'mountain']]

# 빈도수 포함 단어 집합 생성
print('단어 집합 :',vocab)

# 결과
단어 집합 : {'barber': 8, 'person': 3, 'good': 1, 'huge': 5, 'knew': 1, 'secret': 6, 'kept': 4, 'word': 2, 'keeping': 2, 'driving': 1, 'crazy': 1, 'went': 1, 'mountain': 1}

높은 빈도수를 가진 단어일수록 낮은 정수 부여, 정수는 1부터 부여합니다.

 

 

 

 

-> 빈도수가 높은 순으로 정렬

# 빈도수가 높은 순으로 정렬
vocab_sorted = sorted(vocab.items(), key = lambda x:x[1], reverse = True)
print(vocab_sorted)

# 결과
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7}

 

 

> 1의 인덱스를 가진 단어가 가장 빈도수가 높은 단어가 됩니다. 그리고 이러한 작업을 수행하는 동시에 각 단어의 빈도수를 알 경우에만 할 수 있는 전처리인 빈도수가 적은 단어를 제외시키는 작업을 수행합니다. 등장 빈도가 낮은 단어는 자연어 처리에서 의미를 가지지 않을 가능성이 높기 때문입니다.

 

 

=> 정수화

word_to_index를 사용하여 단어 토큰화가 된 상태로 저장된 sentences에 있는 각 단어를 정수로 바꾸는 작업을 해보겠습니다.

 

> 예를 들어 sentences에서 첫 번째 문장은 ['barber', 'person']이었는데, 이 문장에 대해서는 [1, 5]로 인코딩하는 것입니다. 그런데 두 번째 문장인 ['barber', 'good', 'person']에는 더 이상 word_to_index에는 존재하지 않는 단어인 'good'이라는 단어가 있습니다.

 

> 이처럼 단어 집합에 존재하지 않는 단어들이 생기는 상황을 Out-Of-Vocabulary(단어 집합에 없는 단어) 문제라고 합니다. 이러한 것을 처리할 때 word2idx에 'oov'라는 키를 만들고 맨 마지막에 붙입니다.

 

word_to_index['OOV'] = len(word_to_index) + 1
print(word_to_index)

# 결과
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'OOV': 6}

# 인코딩 결과
[[1, 5], [1, 6, 5], [1, 3, 5], [6, 2], [2, 4, 3, 2], [3, 2], [1, 4, 6], [1, 4, 6], [1, 4, 2], [6, 6, 3, 2, 6, 1, 6], [1, 6, 3, 6]]

 

 

=> 케라스의 텍스트 전처리

# 원본
reprocessed_sentences = [['barber', 'person'], ['barber', 'good', 'person'], ['barber', 'huge', 'person'], ['knew', 'secret'], ['secret', 'kept', 'huge', 'secret'], ['huge', 'secret'], ['barber', 'kept', 'word'], ['barber', 'kept', 'word'], ['barber', 'kept', 'secret'], ['keeping', 'keeping', 'huge', 'secret', 'driving', 'barber', 'crazy'], ['barber', 'went', 'huge', 'mountain']]

fit_on_texts는 입력한 텍스트로부터 단어 빈도수가 높은 순으로 낮은 정수 인덱스를 부여하는데, 정확히 앞서 설명한 정수 인코딩 작업이 이루어집니다.

 

 

-> 빈도수 기반 word2idx

tokenizer = Tokenizer()

# fit_on_texts()안에 코퍼스를 입력으로 하면 빈도수를 기준으로 단어 집합을 생성.
tokenizer.fit_on_texts(preprocessed_sentences)

# 결과
print(tokenizer.word_index)
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7, 'good': 8, 'knew': 9, 'driving': 10, 'crazy': 11, 'went': 12, 'mountain': 13}

 

 

-> 정수화

print(tokenizer.texts_to_sequences(preprocessed_sentences))

# 결과
[[1, 5], [1, 8, 5], [1, 3, 5], [9, 2], [2, 4, 3, 2], [3, 2], [1, 4, 6], [1, 4, 6], [1, 4, 2], [7, 7, 3, 2, 10, 1, 11], [1, 12, 3, 13]]

 

앞서 빈도수가 가장 높은 단어 n개만을 사용하기 위해서 most_common()을 사용했습니다. keras tokenizer에서는 tokenizer = Tokenizer(num_words=숫자)와 같은 방법으로 빈도수가 높은 상위 몇 개의 단어만 사용하겠다고 지정할 수 있습니다.

 

 

vocab_size = 5
tokenizer = Tokenizer(num_words = vocab_size + 1) # 상위 5개 단어만 사용
tokenizer.fit_on_texts(preprocessed_sentences)

# 결과
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7, 'good': 8, 'knew': 9, 'driving': 10, 'crazy': 11, 'went': 12, 'mountain': 13}

이때 5개를 남기고 싶으면 6을 해야 합니다. 실질적으로 숫자 0에 지정된 단어가 존재하지 않는 데도 kerastokenizer가 숫자 0까지 단어 집합의 크기로 산정하는 이유는 자연어 처리에서 패딩(padding)이라는 작업 때문입니다.

 

 

 

# 실제 적용은 texts_to_sequences를 할 때 적용이 된다.
print(tokenizer.texts_to_sequences(preprocessed_sentences))

[[1, 5], [1, 5], [1, 3, 5], [2], [2, 4, 3, 2], [3, 2], [1, 4], [1, 4], [1, 4, 2], [3, 2, 1], [1, 3]]

> 코퍼스에 대해서 각 단어를 이미 정해진 인덱스로 변환하는데, 상위 5개의 단어만을 사용하겠다고 지정하였으므로 1번 단어부터 5번 단어까지만 보존되고 나머지 단어들은 제거된 것을 볼 수 있습니다. 이건 oov를 지정할 수 없으니 6도 없습니다.

 

 

-> oov 지정

# 숫자 0과 OOV를 고려해서 단어 집합의 크기는 +2
vocab_size = 5
tokenizer = Tokenizer(num_words = vocab_size + 2, oov_token = 'OOV')
tokenizer.fit_on_texts(preprocessed_sentences)

print(tokenizer.texts_to_sequences(preprocessed_sentences))

# 결과
[[2, 6], [2, 1, 6], [2, 4, 6], [1, 3], [3, 5, 4, 3], [4, 3], [2, 5, 1], [2, 5, 1], [2, 5, 3], [1, 1, 4, 3, 1, 2, 1], [2, 1, 4, 1]]

 keras tokenizer는 기본적으로 단어 집합에 없는 단어인 OOV에 대해서는 단어를 정수로 바꾸는 과정에서 아예 단어를 제거한다는 특징이 있습니다. 단어 집합에 없는 단어들은 OOV로 간주하여 보존하고 싶다면 oov_token 인자를 사용합니다.

 

> 만약 oov_token을 사용하기로 했다면 keras tokenizer는 기본적으로 'OOV'의 인덱스를 1로 합니다. 따라서 상위 5개의 단어는 2~6의 인덱스를 가집니다.(0은 pad입니다.)

 

 

- 패딩

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

 

 

1. 넘파이 패딩

모두 동일한 길이로 맞춰주기 위해서 이 중에서 가장 길이가 긴 문장의 길이를 계산해면 모든 문장의 길이를 7로 맞춥니다.

max_len = max(len(item) for item in encoded)
print('최대 길이 :',max_len) # 7

 

이때 가상의 단어 'PAD'를 사용합니다. 'PAD'라는 단어가 있다고 가정하고, 이 단어는 0번 단어라고 정의합니다. 길이가 7보다 짧은 문장에는 숫자 0을 채워서 길이 7로 맞춰줍니다.(아까 keras에서 idx 0을 pad로 본다고 했습니다!)

 

> 0번 단어는 사실 아무런 의미도 없는 단어이기 때문에 자연어 처리하는 과정에서 기계는 0번 단어를 무시하게 됩니다. 이와 같이 데이터에 특정 값을 채워서 데이터의 크기(shape)를 조정하는 것을 패딩(padding)이라고 합니다.

 

# 원본
[[1, 5], [1, 8, 5], [1, 3, 5], [9, 2], [2, 4, 3, 2], [3, 2], [1, 4, 6], [1, 4, 6], [1, 4, 2], [7, 7, 3, 2, 10, 1, 11], [1, 12, 3, 13]]

# 결과
array([[ 1,  5,  0,  0,  0,  0,  0],
       [ 1,  8,  5,  0,  0,  0,  0],
       [ 1,  3,  5,  0,  0,  0,  0],
       [ 9,  2,  0,  0,  0,  0,  0],
       [ 2,  4,  3,  2,  0,  0,  0],
       [ 3,  2,  0,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  2,  0,  0,  0,  0],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 1, 12,  3, 13,  0,  0,  0]])

 

 

2. keras padding

encoded = tokenizer.texts_to_sequences(preprocessed_sentences)


# 케라스의 padding
padded = pad_sequences(encoded)
padded

# 결과
array([[ 0,  0,  0,  0,  0,  1,  5],
       [ 0,  0,  0,  0,  1,  8,  5],
       [ 0,  0,  0,  0,  1,  3,  5],
       [ 0,  0,  0,  0,  0,  9,  2],
       [ 0,  0,  0,  2,  4,  3,  2],
       [ 0,  0,  0,  0,  0,  3,  2],
       [ 0,  0,  0,  0,  1,  4,  6],
       [ 0,  0,  0,  0,  1,  4,  6],
       [ 0,  0,  0,  0,  1,  4,  2],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 0,  0,  0,  1, 12,  3, 13]], dtype=int32)

기본적으로 pad를 문서의 뒤가 아니라 앞에 채웁니다. 뒤에 하고 싶으면 post 인자를 줍니다.

 

 

 

# 뒤에 채우기
padded = pad_sequences(encoded, padding='post')
padded

array([[ 1,  5,  0,  0,  0,  0,  0],
       [ 1,  8,  5,  0,  0,  0,  0],
       [ 1,  3,  5,  0,  0,  0,  0],
       [ 9,  2,  0,  0,  0,  0,  0],
       [ 2,  4,  3,  2,  0,  0,  0],
       [ 3,  2,  0,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  2,  0,  0,  0,  0],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 1, 12,  3, 13,  0,  0,  0]], dtype=int32)

지금까지는 가장 긴 길이를 가진 문서의 길이를 기준으로 padding을 한다고 가정하였지만, 실제로는 꼭 가장 긴 문서의 길이를 기준으로 해야 하는 것은 아닙니다.



+ 모든 문서의 평균 길이가 20인데 문서 1개의 길이가 5,000이라고 해서 굳이 모든 문서의 길이를 5,000으로 padding 할 필요는 없습니다.

 

# maxlen 인자
padded = pad_sequences(encoded, padding='post', maxlen=5)
padded

# 결과
array([[ 1,  5,  0,  0,  0],
       [ 1,  8,  5,  0,  0],
       [ 1,  3,  5,  0,  0],
       [ 9,  2,  0,  0,  0],
       [ 2,  4,  3,  2,  0],
       [ 3,  2,  0,  0,  0],
       [ 1,  4,  6,  0,  0],
       [ 1,  4,  6,  0,  0],
       [ 1,  4,  2,  0,  0],
       [ 3,  2, 10,  1, 11],
       [ 1, 12,  3, 13,  0]], dtype=int32)

길이가 5보다 짧은 문서들은 0으로 padding 되고, 기존에 5보다 길었다면 데이터가 손실됩니다. 가령, 뒤에서 두 번째 문장은 본래 [ 7, 7, 3, 2, 10, 1, 11]였으나 현재는 [ 3, 2, 10, 1, 11]로 변경된 것을 볼 수 있습니다. 잘리는 단어도 뒤에 단어가 아니고 앞에 단어가 잘립니다.

 

 

 

 

padded = pad_sequences(encoded, padding='post', truncating='post', maxlen=5)
padded

앞이 아니라 뒤에 단어를 자르기 위한 truncating 인자!



- one-hot encoding

배우기에 앞서 단어 집합(vocab)에 대해서 정의합니다. 반드시 이해해야 합니다! 단어 집합은 서로 다른 단어들의 집합입니다.(텍스트의 모든 단어를 중복을 허용하지 않고 모아놓으면 이를 단어 집합이라고 합니다.) book과 books는 다른 단어로 간주합니다. 앞으로 단어 집합에 있는 단어들을 가지고, 문자를 숫자. 더 구체적으로는 벡터로 바꿀 것입니다.

 

> 텍스트의 모든 단어를 중복을 허용하지 않고 모아놓으면 이를 단어 집합이라고 한다고 했습니다. 이 단어 집합에 고유한 정수를 부여하면 정수 인코딩입니다. 그러면 현재 단어들 마다 정수가 부여되었습니다. ex) book :150, dog : 171 등 이제 숫자로 바뀐 단어를 벡터로 바꿔보겠습니다.

 

=> 정의 : 단어 집합의크기를 벡터의 차원으로 보고 보고 싶은 단어의 인덱스만 1로 부여한 단어 표현 방식입니다.

-> 순서(2단계) : 1. 정수 인코딩 수행, 2. 그 단어에만 1 부여!

 

tokens = okt.morphs("나는 자연어 처리를 배운다")  

# 결과
['나', '는', '자연어', '처리', '를', '배운다']

# 원-핫 벡터 만드는 함수
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)
[0, 0, 1, 0, 0, 0]

 

 

-> keras 라이브러리

# 정수 시퀀스로 변환
sub_text = "점심 먹으러 갈래 메뉴는 햄버거 최고야"
encoded = tokenizer.texts_to_sequences([sub_text])[0]
print(encoded)

# 케라스의 원-핫
one_hot = to_categorical(encoded)
print(one_hot)

# 결과
[[0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]]

 

=> one-hot encoding의 한계

1.단어 개수가 늘면 필요 공간이 늘어납니다.

 

2. 모든 단어 각각은 하나의 값만 1을 가지고 999개는 0의 값을 가지는 벡터라 비효율적입니다.

 

3.단어의 유사도를 표현하지 못합니다.

ex) 늑대, 호랑이, 강아지, 고양이 [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]의 one-hot 벡터를 부여받으면 강아지와 늑대가 유사하고, 호랑이와 고양이가 유사하다는 것을 표현할 수가 없습니다. 극단적으로 강아지와 냉장고와 개가 있어도 강아지가 개와 유사한지 냉장고와 유사한지 알 수 없습니다.

 

 

- 한국어 전처리 패키지

1. PyKoSapcing : 문장을 띄어쓰기하여 반환합니다.

new_sent = '김철수는극중두인격의사나이이광수역을맡았다.철수는한국유일의태권도전승자를가리는결전의날을앞두고10년간함께훈련한사형인유연재(김광수분)를찾으러속세로내려온인물이다.'

from pykospacing import Spacing
spacing = Spacing()
kospacing_sent = spacing(new_sent) 

print(kospacing_sent)

# 결과
김철수는 극중 두 인격의 사나이 이광수 역을 맡았다. 철수는 한국 유일의 태권도 전승자를 가리는 결전의 날을 앞두고 10년간 함께 훈련한 사형인 유연재(김광수 분)를 찾으러 속세로 내려온 인물이다.

정확하게 띄어쓰기가 잘 되었습니다.

 

2. Py-hanspell

네이버 한글맞춤법 검사기를 바탕으로 만들어진 패키지입니다. 이것은 또한 띄어쓰기도 보정합니다.

 

from hanspell import spell_checker

sent = "맞춤법 틀리면 외 않되? 쓰고싶은대로쓰면돼지 "
spelled_sent = spell_checker.check(sent)

hanspell_sent = spelled_sent.checked
print(hanspell_sent)

# 결과
맞춤법 틀리면 왜 안돼? 쓰고 싶은 대로 쓰면 되지

 

3. soynlp

데이터에 "자주 등장하는 단어들"을 단어로 분석합니다. 내부적으로 단어 점수 표로 동작합니다. (응집확률, 브랜칭 엔트로피 등을 점수 표로 계산합니다.)

 

-> 기존의 형태소 분석기가 가진 문제 :신조어 문제

기존의 형태소 분석기는 신조어나 형태소 분석기에 등록되지 않은 단어 같은 경우에는 제대로 구분하지 못합니다.

from konlpy.tag import Okt
tokenizer = Okt()
print(tokenizer.morphs('에이비식스 이대휘 1월 최애돌 기부 요정'))

# 결과
['에이', '비식스', '이대', '휘', '1월', '최애', '돌', '기부', '요정']

에이비식스는 아이돌의 이름이고, 이대휘는 에이비식스의 멤버이며, 최애돌은 최고로 애정하는 캐릭터라는 뜻이지만 위의 형태소 분석 결과에서는 전부 분리된 결과를 보입니다.

 

> 그렇다면 텍스트 데이터에서 ① 특정 문자 시퀀스가 함께 자주 등장하는 빈도가 높고, ② 앞 뒤로 조사 또는 완전히 다른 단어가 등장하는 것을 고려해서 해당 문자 시퀀스를 형태소라고 판단하는 단어 tokenizer라면 어떻게 할까요?

 

ex) 에이비식스라는 문자열이 ①"자주 연결되어 등장한다면 한 단어라고 판단"하고, 또한 에이비식스라는 단어 앞, 뒤에 ② '최고', '가수', '실력'과 같은 독립된 다른 단어들이 계속해서 등장한다면 에이비식스를 한 단어로 파악하는 식입니다. 이런 tokenizer가 soynlp입니다.

 

=> 학습하기

soynlp는 학습 기반의 단어 tokenizer입니다. 기존의 KoNLPy에서 제공하는 형태소 분석기들과는 달리 학습 과정을 거쳐야 합니다.

word_extractor = WordExtractor()
word_extractor.train(corpus)
word_score_table = word_extractor.extract() # 단어 점수표 계산

이는 전체 코퍼스로부터 응집 확률과 브랜칭 엔트로피 단어 점수표를 만드는 과정입니다. (자주 연결되어 등장한다면 한 단어라고 판단하기 위한 평가 기준을 준비하는 것!)

=> 응집 확률

응집 확률은 내부 문자열(substring)이 얼마나 응집하여 자주 등장하는지를 판단하는 척도

응집 확률은 문자열을 문자 단위로 분리하여 내부 문자열을 만드는 과정에서 왼쪽부터 순서대로 문자를 추가하면서 각 문자열이 주어졌을 때 그다음 문자가 나올 확률을 계산하여 누적곱을 한 값입니다.

이 값이 높을수록 전체 코퍼스에서 이 문자열 시퀀스는 하나의 단어로 등장할 가능성이 높습니다.

 

 

'반포한강공원에'라는 7의 길이를 가진 문자 시퀀스에 대해서 각 내부 문자열의 스코어를 구하는 과정입니다.

# '반포한'의 응집 확률을 계산
word_score_table["반포한"].cohesion_forward # 0.08

# '반포한강'의 응집 확률을 계산
word_score_table["반포한강"].cohesion_forward # 0.19

word_score_table["반포한강공원"].cohesion_forward # 0.37

반포한강공원이 응집 확률이 더 높습니다. 응집도를 통해 판단하기에 "하나의 단어"(신조어나 사전에 없는 단어도 하나의 단어로 형태소 분석할 수 있습니다.)로 판단하기에 가장 적합한 문자열을 찾을 수 있습니다.

 

이것을 활용하면 아까 신조어였던 '에이비식스', '이대휘'등이 형태소 분석에서 올바르지 않게 쪼개진 것을 "자주 연결되어 등장한다면 한 단어라고 판단"하여 해결할 수 있겠다!

 

 

=> 브랜칭 엔트로피

이는 주어진 문자열에서 얼마나 다음 문자가 등장할 수 있는지를 판단하는 척도입니다. 브랜칭 엔트로피를 주어진 문자 시퀀스에서 다음 문자 예측을 위해 헷갈리는 정도로 생각할 수 있습니다. 브랜칭 엔트로피의 값은 하나의 완성된 단어에 가까워질수록 문맥으로 인해 점점 정확히 예측할 수 있게 되면서 점점 줄어드는 양상을 보입니다.

 

ex) 정답이 디스플레이인데 앞 글자 하나씩 말해서 정답을 맞혀보도록 해보겠습니다.

'디'만 나오는 것과 "디스플"이 나온 것의 정답 확률은 "디스플"이 높습니다.

 

> 따라서 브랜칭 엔트로피는 헷갈리는 정도이니 많이 공개되었을수록 점수가 낮아집니다.

 

word_score_table["디스"].right_branching_entropy # 1.67

word_score_table["디스플"].right_branching_entropy # 0

'디스' 다음에는 다양한 문자가 올 수 있으니까 1.63이라는 값을 가지는 반면, '디스플'이라는 문자열 다음에는 다음 문자로 '레'가 오는 것이 너무나 명백하기 때문에 0이란 값을 가집니다.

 

 

word_score_table["디스플레이"].right_branching_entropy # 3.14

갑자기 값이 증가하는 이유는 문자 시퀀스 '디스플레이'라는 문자 시퀀스 다음에는 조사나 다른 단어와 같은 다양한 경우가 있을 수 있기 때문입니다. 이는 하나의 단어가 끝나면 그 경계 부분부터 다시 브랜칭 엔트로피 값이 증가하게 됨을 의미합니다. 그리고 이 값으로 단어의 끝을 판단하는 것이 가능합니다.

 

 

=> 반복되는 문장 정제

print(emoticon_normalize('앜ㅋㅋㅋㅋ이영화존잼쓰ㅠㅠㅠㅠㅠ', num_repeats=2))
print(emoticon_normalize('앜ㅋㅋㅋㅋㅋㅋㅋㅋㅋ이영화존잼쓰ㅠㅠㅠㅠ', num_repeats=2))
print(emoticon_normalize('앜ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ이영화존잼쓰ㅠㅠㅠㅠㅠㅠ', num_repeats=2))
print(emoticon_normalize('앜ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ이영화존잼쓰ㅠㅠㅠㅠㅠㅠㅠㅠ', num_repeats=2))

# 결과
아ㅋㅋ영화존잼쓰ㅠㅠ
아ㅋㅋ영화존잼쓰ㅠㅠ
아ㅋㅋ영화존잼쓰ㅠㅠ
아ㅋㅋ영화존잼쓰ㅠㅠ

SNS나 채팅 데이터와 같은 한국어 데이터의 경우에는 ㅋㅋ, ㅎㅎ 등의 이모티콘의 경우 불필요하게 연속되는 경우가 많은데 ㅋㅋ, ㅋㅋㅋ, ㅋㅋㅋㅋ와 같은 경우를 모두 서로 다른 단어로 처리하는 것은 불필요합니다. 이에 반복되는 것은 하나로 정규화시켜 줍니다.

=> konlpy customizing

형태소 분석 입력 : '은경이는 사무실로 갔습니다.'
형태소 분석 결과 : ['은', '경이', '는', '사무실', '로', '갔습니다', '.']

위에서 '은경이'는 사람 이름이므로 제대로 된 결과를 얻기 위해서는 '은', '경이'와 같이 글자가 분리되는 것이 아니라 '은경이' 또는 최소한 '은경'이라는 단어 토큰을 얻어야만 합니다.

 

이런 경우에는 형태소 분석기에 사용자 사전을 추가해줄 수 있습니다. '은경이'는 하나의 단어이기 때문에 분리하지 말라고 형태소 분석기에 알려주는 것입니다.

 

from ckonlpy.tag import Twitter
twitter = Twitter()
twitter.morphs('은경이는 사무실로 갔습니다.')

# 결과
['은', '경이', '는', '사무실', '로', '갔습니다', '.']

twitter.add_dictionary('은경이', 'Noun')
twitter.morphs('은경이는 사무실로 갔습니다.')

# 결과
['은경이', '는', '사무실', '로', '갔습니다', '.']

 

 

 

결론

지금까지 텍스트 전처리를 위한 전처리를 배웠습니다. 하지만 하나하나 따로따로 배워서 어디에 쓰는지만 이해했습니다. 이것을 실제 프로젝트에 적용해 보면 좋겠습니다.

'[AI] > [네이버 BoostCamp | 학습기록]' 카테고리의 다른 글

NLP PART.단어의 표현  (0) 2022.12.24
NLP PART.언어 모델  (0) 2022.12.19
NLP PART.텍스트 전처리  (0) 2022.12.14
NLP PART.BERT, GPT  (0) 2022.12.09
NLP PART.Attention 메커니즘  (0) 2022.12.06
Comments