[딥러닝 스터디] 자연어 처리의 전처리

[딥러닝 스터디] 자연어 처리의 전처리

다음의 책을 공부하며 정리한 내용입니다.

자연어 처리의 전처리

자연어 처리를 위해 자연어 데이터는 일반적으로 토큰화, 단어집합생성, 정수인코딩, 패딩, 벡터화의 과정을 거친다.

토큰화

  • 주어진 텍스트를 단어/문자 단위로 자르는 것을 의미한다.
  • 토큰화 도구
    • spaCy, NLTK: English Tokenization
    • .split(): 파이썬 기본함수. 띄어쓰기 등으로 토큰화한다면..
1
2
3
4
5
6
7
8
# tokenizer 도구 사용해보기 - 상세 코드는 wikidocs.net/64157 참고
import spacy

text = "A dog run back corner near bedrooms"
spacy_text = spacy.load('en')

for token in spacy_text.tokenizer(text):
print(token.text)
1
2
3
4
5
6
7
A
dog
run
back
corner
near
bedrooms

한국어 토큰화

  • 영어와 달리 한국어는 띄어쓰기 단위로 토큰화 하면 ‘사과가 =/= 사과는’ 으로 인식되어 단어집합이 불필요하게 커진다.
    • 형태소 토큰화 사용 : 형태소 분석기를 사용해 토큰화를 진행한다.
1
2
3
4
5
6
7
8
# 형태소 분석기 중 mecab을 사용해 한국어 형태소 토큰화한다.
# !git clone https://github.com/SOMJANG/Mecab-ko-for-Google-Colab.git
# %cd Mecab-ko-for-Google-Colab
# !bash install_mecab-ko_on_colab190912.sh
from konlpy.tag import Mecab

tokenizer = Mecab()
print(tokenizer.morphs("사과의 놀라운 효능이라는 글을 봤어. 그래서 오늘 사과를 먹으려고 했는데 사과가 썩어서 슈퍼에 가서 사과랑 오렌지 사왔어"))

단어집합의 생성

: 단어집합(vocabulary)란 중복을 제거한 텍스트 내 총 단어의 집합(set)을 의미한다.

(실습) 네이버 영화 리뷰 데이터를 통해 단어집합 생성

1
2
3
4
5
6
7
8
9
10
11
12
# 20만개의 영화리뷰에 대해 긍정 1, 부정 0으로 레이블링한 네이버 데이터를 다운받는다.
import urllib.request
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings.txt", filename="ratings.txt")
data = pd.read_table('ratings.txt') # 데이터프레임에 저장
print(data[15:20]) # 15번 ~ 20번까지의 리뷰 5개 뽑아보기

# data는 해시맵으로 특정 리뷰의 내용에만 접근하기 위한 키는 document이다
print(data['document'][15])
1
2
3
4
5
6
7
8
[결과]
id document label
15 9034036 평점 왜 낮아? 긴장감 스릴감 진짜 최고인데 진짜 전장에서 느끼는 공포를 생생하게 ... 1
16 979683 네고시에이터랑 소재만 같을 뿐.. 아무런 관련없음.. 1
17 165498 단연 최고 1
18 8703997 가면 갈수록 더욱 빠져드네요 밀회 화이팅!! 1
19 9468781 어?생각없이 봤는데 상당한 수작.일본영화 10년내 최고로 마음에 들었다.강렬한 임팩... 1
평점 왜 낮아? 긴장감 스릴감 진짜 최고인데 진짜 전장에서 느끼는 공포를 생생하게 전해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from konlpy.tag import Mecab

# 임의의 10000개의 리뷰를 sample data로 사용한다.
sample_data = data[:10000]

# 한글과 공백을 제외하고 불필요한 문자는 모두 제거한다. - 정규표현식 사용
sample_data['document'] = data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")

# 불용어를 제거해준다. - 인터넷 검색 시 검색 용어로 사용하지 않는 단어. 관사, 전치사, 조사, 접속사 등은 검색 색인 단어로 의미가 없는 단어
stopwords=['뭐','으면','을','의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']

tokenizer = Mecab()
res = []
for sentence in sample_data['document']:
tmp = []
tmp = tokenizer.morphs(sentence)

tokenized = []
for token in tmp:
if not token in stopwords:
tokenized.append(token)

res.append(tokenized)

print(res[:2])

1
2
3
4
5
6
7
8
[결과]
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:7: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
import sys
[['어릴', '때', '보', '고', '지금', '다시', '봐도', '재밌', '어요', 'ㅋㅋ'], ['디자인', '배우', '학생', '외국', '디자이너', '그', '일군', '전통', '통해', '발전', '해', '문화', '산업', '부러웠', '는데', '사실', '우리', '나라', '에서', '그', '어려운', '시절', '끝', '까지', '열정', '지킨', '노라노', '같', '전통', '있', '어', '저', '같', '사람', '꿈', '꾸', '고', '이뤄나갈', '수', '있', '다는', '것', '감사', '합니다']]
1
2
3
4
5
6
7
8
9
10
11
import nltk
from nltk import FreqDist

# 단어-빈도수 조합으로 이루어진 단어집합 해시맵 vocab을 생성한다.
# NLTK에서는 빈도수 계산 도구인 FreqDist()를 지원한다.
vocab = FreqDist(np.hstack(res))
print('단어 [별로]의 빈도수는? ', vocab['별로'], '번')

# most_common(N) : 가장 빈도수가 높은 N개의 단어를 반환
# 상위 500개의 단어만 보존
vocab = vocab.most_common(500)
1
2
[결과]
단어 [별로]의 빈도수는? 37 번

각 단어에 고유한 정수 부여

  • 각 토큰에 고유한 정수를 부여한다.
  • 0과 1은 특수 인덱스로 사용한다.
    • 인덱스 0 : 단어집합에 없는 토큰
    • 인덱스 1 : 패딩 토큰(길이맞추기용)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
word_to_index = {}

# 단어들에 순차적으로 2~ 501까지의 인덱스를 부여한다
word_to_index = {word[0] : index + 2 for index, word in enumerate(vocab)}

# 특수 인덱스
word_to_index['unk'] = 0
word_to_index['pad'] = 1

encoded = []
for review in res:

tmp = []
for word in review:
try:
# 각 글자를 해당하는 정수로 변환한다.
tmp.append(word_to_index[word])
except KeyError:
# 단어 집합에 없는 단어일 경우(=빈도수 상위 500 이외의 단어) unk로 대체된다.
tmp.append(word_to_index['unk'])

encoded.append(tmp)

# 기존의 리뷰가 성공적으로 encoding 되었는지 확인해보기
print(encoded[:2])
1
[[294, 51, 6, 4, 89, 63, 86, 11, 21, 34], [0, 79, 0, 0, 0, 54, 0, 0, 0, 0, 48, 0, 0, 0, 19, 314, 136, 319, 26, 54, 0, 278, 169, 72, 0, 0, 0, 32, 0, 8, 36, 140, 32, 68, 383, 0, 4, 0, 22, 8, 123, 29, 320, 103]]

Padding

: 길이가 다른 문장들을 모두 동일한 크기로 바꿔주는 작업

  • 인코딩한 리뷰를 모두 일정한 길이로 변환해준다.
  • 특정 길이로 모든 샘플들의 길이를 맞춰준다.
  • 정한 길이보다 짧은 샘플들에는 ‘pad’ 토큰을 추가하여 길이를 맞춰준다.

리뷰의 길이를 그래프로 출력하는 코드

1
2
3
4
5
6
7
8
max_len = max(len(l) for l in encoded)
print('리뷰의 최대 길이 : %d' % max_len)
print('리뷰의 최소 길이 : %d' % min(len(l) for l in encoded))
print('리뷰의 평균 길이 : %f' % (sum(map(len, encoded))/len(encoded)))
plt.hist([len(s) for s in encoded], bins=50)
plt.xlabel('length of sample')
plt.ylabel('number of sample')
plt.show()

대체 텍스트

1
2
max_len = max(len(l) for l in encoded)
print('리뷰의 최대 길이 : %d' % max_len)
1
2
[결과]
리뷰의 최대 길이 : 81
1
2
3
4
5
6
7
8
9
# 리뷰 최대 길이인 81로 모든 리뷰의 길이를 맞춰준다.
pad_len = 81

for review in encoded:
if len(review) < pad_len:
review += [word_to_index['pad']] * (pad_len - len(review))

# 기존의 리뷰가 성공적으로 padding 되었는지 보기
print(encoded[0])
1
2
[결과]
[294, 51, 6, 4, 89, 63, 86, 11, 21, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

댓글