[NLP] 텍스트전처리(Text preprocessing)

cateto·2021년 6월 8일
0
post-thumbnail

2021.06.02(수)

딥 러닝을 이용한 자연어 처리 입문 의 글을 공부하며,
정리하며 기록한 글입니다.

텍스트전처리

선행 키워드

  • 토큰화 : 코퍼스에서 토큰으로 분류하는 작업
  • 정제 : 코퍼스에서 노이즈 데이터를 제거 (토큰화 전후로 이루어짐)
  • 정규화 : 표현 방법이 다른 단어를 통합시켜 같은 단어로 만들어줌

불용어

불용어(Stopword) : 문장에서 자주 등장하지만 실제 의미를 분석하는데에 큰 도움이 되지 않는 단어

  1. NLTK의 불용어
from nltk.corpus import stopwords  
stopwords.words('english')[:10]
# 앞에서부터 10개만 뽑아서 확인
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your']
  • NLTK에서 불용어 제거하기(코드 생략)
['Family', 'is', 'not', 'an', 'important', 'thing', '.', 'It', "'s", 'everything', '.']
['Family', 'important', 'thing', '.', 'It', "'s", 'everything', '.']

# 'is', 'not', 'an'과 같은 단어들이 문장에서 제거
  1. 한국어의 불용어(코드 생략)
['고기를', '아무렇게나', '구우려고', '하면', '안', '돼', '.', '고기라고', '다', '같은', '게', '아니거든', '.', '예컨대', '삼겹살을', '구울', '때는', '중요한', '게', '있지', '.']
['고기를', '구우려고', '안', '돼', '.', '고기라고', '다', '같은', '게', '.', '삼겹살을', '구울', '때는', '중요한', '게', '있지', '.']

# 직접 정의한 불용어의 제거
# stop_words = "아무거나 아무렇게나 어찌하든지 같다 비슷하다 예컨대 이럴정도로 하면 아니거든"

cf. 한국어 보편적인 불용어 링크

  • 불용어를 제거하는 더! 좋은 방법은 txt, csv 파일로 정리해놓고 이를 불러와서 사용하는 방법

정규표현식

정규표현식(Regular Expression) : 코퍼스 내에 자주 등장하는 글자를 규칙에 기반하여 한 번에 제거

  1. 파이썬의 정규 표현식 모듈 함수 re
  2. NLTK의 정규 표현식 토큰화 함수 RegexpTokenizer

정수인코딩

정수인코딩(Integer Encoding) : 단어를 자주 나타나는 순으로 정렬한 단어 집합(vocabulary)에서

낮은 숫자 부터 고유한 정수를 부여하는 과정

  1. 파이썬의 dictionary 자료형 사용하기 (코드 생략)
# 문장 토큰화 -> 단어 토큰화 -> 소문자화 -> 불용어 제거 ->  
# 단어 길이 2 이하 제거 -> 단어 빈도수 매핑 후

{'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}
vocab_sorted = sorted(vocab.items(), key = lambda x:x[1], reverse = True)
# sorted함수에 lambda(익명 함수) 사용하여 빈도수(x[1])로 내림차순 정렬한 리스트를 반환
print(vocab_sorted)
[('barber', 8), ('secret', 6), ('huge', 5), ('kept', 4), ('person', 3), ('word', 2), ('keeping', 2), ('good', 1), ('knew', 1), ('driving', 1), ('crazy', 1), ('went', 1), ('mountain', 1)]

높은 빈도수를 가진 단어에 낮은 정수 인덱스를 부여함. 또한 빈도수가 낮은 단어는 의미를 가지지 않을 가능성이 높으므로 빈도수가 1인 단어는 제외

  1. 파이썬의 Counter 사용하기

Counter 모듈이 중복을 제거하고 단어의 빈도수를 기록함

most_common() 상위 빈도수를 가진 주어진 수의 단어만을 리턴

  1. NLTK의 FreqDist 사용하기

FreqDist 빈도수를 기록함

enumerate() 순서가 있는 자료형(list, set, tuple, dictionary, string)을 입력으로 받아 인덱스를 순차적으로 함께 리턴한다.

케라스(Keras)의 텍스트 전처리

from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()

케라스의 전처리 도구 Tokenizer

fit_on_texts 입력한 텍스트로부터 단어 빈도수가 높은 순으로 낮은 정수 인덱스를 부여

word_index 단어 인덱스를 확인

word_counts 단어 갯수를 확인

texts_to_sequences() 각 단어를 인덱스로 반환

Tokenizer(num_words=숫자) 빈도수가 높은 상위 단어를 사용하겠다고 지정

oov_token 단어 집합에 없는 단어를 제거하지 않고 OOV에 보존 (기본 인덱스는 1)

패딩

패딩(Padding) : 다양한 길이의 데이터에 특정 값을 채워서 같은 길이로 맞춰주는 작업

  1. Numpy로 패딩하기 (코드 일부 생략)
# 정수 인코딩이 수행된 encoded의 결과 값
# 가장 긴 문장의 길이는 7이다.
[[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]

가장 길이가 긴 문장의 길이 7을 기준으로 모든 문장의 길이를 7로 맞춰준다.

for item in encoded: # 각 문장에 대해서
    while len(item) < max_len:   # max_len보다 작으면
        item.append(0)

padded_np = np.array(encoded)
padded_np

# 제로 패딩(zero padding) : 위의 경우는 숫자 0을 채워 길이를 조정하므로 제로 패딩에 해당된다.

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]])
  1. Keras로 패딩하기 (코드 일부 생략)
# 정수 인코딩이 수행된 encoded의 결과 값
# 가장 긴 문장의 길이는 7이다.
[[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]

케라스의 pad_sequences를 사용한 패딩

from tensorflow.keras.preprocessing.sequence import pad_sequences

padded = pad_sequences(encoded)
padded
# pad_sequences 의 default 설정은 문서의 앞에 0을 채움.
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)

호출 시 파라미터로 padding='post'를 주면 문서의 뒤에 0을 채우게 됨.

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)

호출 시 파라미터로 max_len에 정수를 주면, 해당 정수로 모든 문서의 길이를 동일하게 함.

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으로 패딩 되지만, 5보다 길었다면 데이터가 손실됨.

호출 시 파라미터로 value를 사용하면, 0이 아닌 숫자로 패딩이 가능함.

last_value = len(tokenizer.word_index) + 1 
# 단어 집합(dictionary)의 크기보다 1 큰 숫자를 사용
print(last_value) # 14

padded = pad_sequences(encoded, padding = 'post', value = last_value)
padded
array([[ 1,  5, 14, 14, 14, 14, 14],
       [ 1,  8,  5, 14, 14, 14, 14],
       [ 1,  3,  5, 14, 14, 14, 14],
       [ 9,  2, 14, 14, 14, 14, 14],
       [ 2,  4,  3,  2, 14, 14, 14],
       [ 3,  2, 14, 14, 14, 14, 14],
       [ 1,  4,  6, 14, 14, 14, 14],
       [ 1,  4,  6, 14, 14, 14, 14],
       [ 1,  4,  2, 14, 14, 14, 14],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 1, 12,  3, 13, 14, 14, 14]], dtype=int32)
profile
Curious for Everything

0개의 댓글