공부내용 참고사이트 : 위키독스 딥러닝을 이용한 자연어 처리 입문
https://wikidocs.net/21698
컴퓨터는 텍스트보다 숫자를 더 잘 처리한다.
그렇기 때문에 텍스트 전처리와 토큰화를 진행했다면 이를 인덱스로 맵핑(mapping)시키는 작업이 필요하다.
가장 기본적인 인코딩 방법으로는 단어마다 고유의 정수를 매핑하는 정수 인코딩이 있다.
정수인코딩을 완료하면, 각 정수를 고유한 벡터로 바꾸는 작업이 필요한데 이 작업을 임베딩(Embedding)이라고 한다. 단어 벡터를 얻는 방법은 크게 원-핫 인코딩과 워드 임베딩이 있는데, 주로 워드 임베딩이 사용된다.
먼저 정수 인코딩 방법에 대해 알아보자.
정수인코딩을 하는 방법으로 Counter, NLTK(FreqDist), enumerate, Keras Tokenizer 등 여러가지가 있다.
나는 먼저 인코딩을 간편하게 사용할 수 있는 Keras Tokenizer를 사용해보고, 그 다음 인코딩이 어떻게 진행되는지 기본 코드로 알고리즘을 확인하였다.
그 이후 마지막으로 Counter, NLTK, enumerate를 사용한 정수 인코딩 방법을 공부하였다.
인코딩을 하기 전, 먼저 corpus(말뭉치)에 대한 기본적인 전처리가 필요하다.
1) 문장 토큰화 2) 단어 토큰화 3) 전처리(소문자변환, 불용어제거, 단어길이지정)
0. 전처리
# 정수 인코딩
from nltk.tokenize import sent_tokenize #문장 토큰화
from nltk.tokenize import word_tokenize #단어 토큰화
from nltk.corpus import stopwords
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.']
# 전처리(정제, 정규화)
vocab = {}
preprocessed_sentences = []
stop_words = set(stopwords.words('english'))
for sentence in sentences:
# 단어 토큰화
tokenized_sentence = word_tokenize(sentence)
result = []
for word in tokenized_sentence:
word = word.lower() #소문자변환으로 단어 개수 줄이기
if word not in stop_words: #불용어제거
if len(word) > 2: #단어길이가 3개 이상인 단어만 추출
result.append(word)
if word not in vocab: #딕셔너리 {key=단어, value=빈도수}
vocab[word] = 0
vocab[word] += 1
preprocessed_sentences.append(result)
print("문장 별 단어 추출 :", preprocessed_sentences,'\n')
print("단어 집합 :", vocab, "\n")
print("'barber' 빈도수 :", vocab["barber"])
> 문장 별 단어 추출 : [['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']]
> 단어 집합 : {'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}
> 'barber' 빈도수 : 8
1. Keras(케라스) Tokenizer
케라스는 기본적인 전처리를 위한 도구들을 제공한다. 인코딩을 위해 Tokenizer를 사용할 수 있다.
- tokenizer.word_index
- tokenizer.word_counts
- tokenizer.texts_to_sequences(text)
# keras tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(preprocessed_sentences) # fit_on_texts(text) : 빈도수가 높은 순으로 낮은 정수 인덱스 부여
print('정수인코딩 전 : ',preprocessed_sentences, "\n") # 일반 전처리만 진행한 text
print("단어와 인덱스 :",tokenizer.word_index, "\n") # 인덱스 확인
print("단어와 빈도수 :", tokenizer.word_counts, "\n") # 단어 빈도수 확인
print("정수인코딩 후 :", tokenizer.texts_to_sequences(preprocessed_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']]
> 단어 인덱스 : {'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}
> 단어 빈도수 : OrderedDict([('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, 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]]
*상위 5개 단어만 인코딩하기
- word_index와 word_counts는 변동이 없다.
- 케라스 토크나이저에서는 기본적으로 단어 집합에 없는 단어(OOV, Out-Of-Vocabulary)는 인코딩 과정에서 아예 제거한다.
# keras tokenizer + 상위 5개 단어만 인코딩
vocab_size = 5
tokenizer = Tokenizer(num_words = vocab_size + 1) #상위 5개 단어만 사용
tokenizer.fit_on_texts(preprocessed_sentences)
print("단어와 인덱스 :",tokenizer.word_index, "\n") # 인덱스 확인
print("단어와 빈도수 :", tokenizer.word_counts, "\n") # 단어 빈도수 확인
print("정수인코딩 후 :", tokenizer.texts_to_sequences(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}
> 단어와 빈도수 : OrderedDict([('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, 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]]
* word_index와 word_counts 도 상위5개만 지정하기
tokenizer = Tokenizer()
tokenizer.fit_on_texts(preprocessed_sentences)
vocab_size = 5
words_frequency = [word for word, index in tokenizer.word_index.items() if index >= vocab_size + 1] # top6이상 단어만 추출
for word in words_frequency:
del tokenizer.word_index[word] # top6 이상 단어 인덱스 정보 삭제
del tokenizer.word_counts[word] # top6 이상 단어 빈도 정보 삭제
print("단어와 인덱스 :",tokenizer.word_index, "\n") # 인덱스 확인
print("단어와 빈도수 :", tokenizer.word_counts, "\n") # 단어 빈도수 확인
> 단어와 인덱스 : {'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
> 단어와 빈도수 : OrderedDict([('barber', 8), ('person', 3), ('huge', 5), ('secret', 6), ('kept', 4)])
* OOV 인덱스 설정
- 만약 Keras에서 OOV를 따로 보존하고 싶다면 Tokenizer의 인자 oov_token을 사용할 수 있다.
- 케라스 토크나이저의 'OOV' 인덱스는 기본적으로 1이다.
# OOV 설정하기
vocab_size = 5
tokenizer = Tokenizer(num_words = vocab_size +2, oov_token ='OOV')
tokenizer.fit_on_texts(preprocessed_sentences)
print('단어 OOV의 인덱스 : {}'.format(tokenizer.word_index['OOV']))
# 정수인코딩
print("OVV 포함 정수인코딩 :", tokenizer.texts_to_sequences(preprocessed_sentences))
> 단어 OOV의 인덱스 : 1
> OVV 포함 정수인코딩 : [[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]]
2. 정수인코딩 알고리즘
1) 단어를 빈도수 순으로 정렬한 단어 집합(vocabulary)를 만든다.
- 위 전처리 과정에서 만든 단어집합(빈도)인 vocab 변수 사용
# 빈도수 순 단어집합
vocab_sorted = sorted(vocab.items(), key = lambda x:x[1], reverse=True)
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)]
2) 빈도수가 높은 순서대로 차례로 낮은 숫자부터 정수를 부여한다.
# 높은 빈도수대로 낮은 정수 부여(1부터~)
word_to_index = {}
i = 0
for (word, frequency) in vocab_sorted:
if frequency > 1 : #빈도수1은 제외
i = i + 1
word_to_index[word] = i #dic[key] = value
print(word_to_index)
> {'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7}
* 실제 자연어 처리 때, 빈도수가 가장 높은 n개의 단어만 사용할 경우가 많기 때문에 상위 5개 단어를 지정하였다.
# 추출 단어 개수
vocab_size = 5
#인덱스가 5 초과인 단어
words_frequency = [word for word, index in word_to_index.items() if index >= vocab_size + 1]
#인덱스가 5 초과인 단어 제거
for w in words_frequency :
del word_to_index[w]
print(word_to_index)
> {'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
3) 모든 단어들을 맵핑되는 정수로 인코딩한다.
위 전처리 과정에서 추출한 "문장 별 단어 추출(preprocessed_sentences)" 에 있는 단어들을 모두 미리 지정한 정수로 바꿔준다.
하지만 그 전에 문제가 하나 있다. 상위 5개로 지정해준 단어집합(Vocabulary)에 포함되지 않는 단어들이 생긴다.
이를 OOV(Out-Of-Vocabulary) 문제 라고한다.
단어 집합에 없는 단어들은 OOV 인덱스를 따로 추가해 그 정수로 모두 인코딩해준다.
# OOV(Out-Of-Vocabulary) key, index 추가
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}
# 정수인코딩
encoded_sentences = []
for sentence in preprocessed_sentences:
encoded_sentence = []
for word in sentence :
try:
# 단어 집합에 있는 단어라면 해당 단어의 정수를 리턴
encoded_sentence.append(word_to_index[word])
except:
# 단어 집합에 없는 단어라면 'OOV'의 정수를 리턴
encoded_sentence.append(word_to_index['OOV'])
encoded_sentences.append(encoded_sentence)
print('정수인코딩 전 : ',preprocessed_sentences)
print('정수인코딩 후 : ',encoded_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']]
> 정수인코딩 후 : [[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]]
3. Counter를 이용한 정수인코딩
1) 단어집합 만들기
from collections import Counter
import numpy as np
# 단어집합 만들기([]제거) ver.1
all_words_list = sum(preprocessed_sentences, [])
print(all_words_list)
> ['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']
2) Counter(빈도) 구하기
vocab = Counter(all_words_list)
print(vocab)
> Counter({'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})
#단어 'secret' 빈도개수
vocab["secret"]
> 6
#높은 빈도수 상위 5개 단어
vocab_size = 5
vocab = vocab.most_common(vocab_size)
vocab
> [('barber', 8), ('secret', 6), ('huge', 5), ('kept', 4), ('person', 3)]
3) 단어에 정수 인덱스 부여하기(높은빈도 낮은정수)
# 인덱스 부여하기
word_to_index = {}
i = 0
for (word, index) in vocab :
i = i + 1
word_to_index[word] = i
print(word_to_index)
> {'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
4. NLTK - FreqDist(빈도수계산도구)를 이용한 정수인코딩
1) 단어집합 만들기
from nltk import FreqDist
# 단어집합 만들기([]제거) ver.2
words = np.hstack(preprocessed_sentences)
words
> array(['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'], dtype='<U8')
2) Counter(빈도) 구하기
# 단어 'barber'의 빈도수출력
vocab = FreqDist(words)
print(vocab["barber"])
> 8
#상위5개
vocab_size = 5
vocab = vocab.most_common(vocab_size)
print(vocab)
> [('barber', 8), ('secret', 6), ('huge', 5), ('kept', 4), ('person', 3)]
3) 단어에 정수 인덱스 부여하기(높은빈도 낮은정수)
#정수 부여
word_to_index = {word[0] : index +1 for index, word in enumerate(vocab)}
print(word_to_index)
> {'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
* enumerate 이해하기
# enumerate이해하기
test_input = ['a','b','c','d','e']
for index, value in enumerate(test_input): #인덱스 0부터 부여
print("value : {}, index : {}".format(value, index))
> value : a, index : 0
> value : b, index : 1
> value : c, index : 2
> value : d, index : 3
> value : e, index : 4
'데이터마이닝 > 텍스트마이닝' 카테고리의 다른 글
[형태소분석기]윈도우에서 khaiii(카카오분석기) 실행하기 (0) | 2022.05.13 |
---|---|
[텍스트 전처리] 원-핫 인코딩 (One-Hot Encoding) (0) | 2021.12.27 |
[텍스트 전처리] 정제, 정규화 (0) | 2021.12.15 |
자연어 처리 관련 라이브러리 및 패키지 (0) | 2021.12.15 |
텍스트 마이닝 분석 절차 (0) | 2021.12.08 |