텍스트 감성분석 접근법은 아래와 같이 2가지가 존재함
1. 기계학습 기반
2. 감성사전 기반
사전 기반의 감성분석은 기계학습 기반 대비 2가지 단점이 존재함
- 분석 대상에 따라 단어의 감성 점수가 달라질 수 있다는 가능성에 대응하기 어려움
- 단순 긍부정을 넘어서 긍부정의 원인이 되는 대상 속성 기반의 감성 분석이 어려움
단어의 특성을 저차원 벡터값으로 표현하는 워드 임베딩(word embedding) 방법을 이용하여 머신러닝 기반 감성분석의 정확도를 높일 수 있음
sentences = ['i love pizza', 'i like chichen', 'now i want bread']
word_list = sentences[0].split()
word_list
['i', 'love', 'pizza']
# text data로부터 사전을 만들기위해 모든 단어를 split해서 dict 자료구조로 표현
index_to_word={} # 빈 딕셔너리를 만들어서
# 채우는 순서는 임의로 세팅 but 순서는 중요하지 않음
# <BOS>, <PAD>, <UNK>는 관례적으로 딕셔너리 맨 앞에 넣음
index_to_word[0]='<PAD>' # 패딩용 단어
index_to_word[1]='<BOS>' # 문장의 시작지점
index_to_word[2]='<UNK>' # 사전에 없는(Unknown) 단어
index_to_word[3]='i'
index_to_word[4]='love'
index_to_word[5]='pizza'
index_to_word[6]='like'
index_to_word[7]='chicken'
index_to_word[8]='now'
index_to_word[9]='want'
print(index_to_word)
{0: '<PAD>', 1: '<BOS>', 2: '<UNK>', 3: 'i', 4: 'love', 5: 'pizza', 6: 'like', 7: 'chicken', 8: 'now', 9: 'want'}
# 텍스트 데이터를 숫자로 바꿔 보려고 하는데, 텍스트를 숫자로 바꾸려면 위의 딕셔너리가 {텍스트:인덱스} 구조여야 함
word_to_index = {word : index for index, word in index_to_word.items()}
print(word_to_index)
{'<PAD>': 0, '<BOS>': 1, '<UNK>': 2, 'i': 3, 'love': 4, 'pizza': 5, 'like': 6, 'chicken': 7, 'now': 8, 'want': 9}
# 단어를 주면 index를 반환
print(word_to_index['want'])
9
# 문장 1개를 활용딕셔너리와 함께 제공하면 단어 인덱스 리스트로 변환해 주는 함수
# 모든 문장은 <BOS>로 시작
def get_encoded_sentence(sentence, word_to_index):
return [word_to_index['<BOS>']]+[word_to_index[word] if word in word_to_index else word_to_index['<UNK>'] for word in sentence.split()]
print(get_encoded_sentence('i love pizza', word_to_index))
[1, 3, 4, 5]
# Multiple Sentences 분석
# 여러개의 문장 리스트를 한번에 숫자 텐서로 인코딩
sentences = ['i love pizze', 'i like chichen', 'now i want bread']
def get_encoded_sentences(sentences, word_to_index):
return [get_encoded_sentence(sentence, word_to_index) for sentence in sentences]
encoded_sentences = get_encoded_sentences(sentences, word_to_index)
encoded_sentences
[[1, 3, 4, 2], [1, 3, 6, 2], [1, 8, 3, 9, 2]]
# encode된 벡터를 decoding하여 텍스트로 복구
def get_decoded_sentence(encoded_sentence, index_to_word):
return ' '.join(index_to_word[index] if index in index_to_word else '<UNK>' for index in encoded_sentence[1:])
print(get_decoded_sentence([1,3,4,5], index_to_word))
i love pizza
def get_decoded_sentences(encoded_sentences, index_to_word):
return [get_decoded_sentence(encoded_sentence, index_to_word) for encoded_sentence in encoded_sentences]
print(get_decoded_sentences(encoded_sentences, index_to_word))
['i love <UNK>', 'i like <UNK>', 'now i want <UNK>']
ATTENTION
- Embedding Layer의 인풋이 되는 문장은 길이가 일정해야 함
- 즉 입력 데이터 내의 모든 문장은 단어의 개수가 같아야 한다는 것 -> PAD 사용
keras.preprocessing.sequence.pad_sequences
는 문장벡터 뒤에 추가하여 모든 문장의 길이를 일정하게 만듦- value 옵션은 padding 되는 곳에 어떤 값을 넣을 것인지 설정해주는 것
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential
from tensorflow.keras.layers import Embedding
vocab_size = len(word_to_index)
word_vector_dim = 4 # 4차원의 워드벡터를 가정
embedding = tf.keras.layers.Embedding(input_dim = vocab_size, \
output_dim = word_vector_dim, mask_zero = True)
# 숫자로 변환된 텍스트 데이터에 Embedding 레이어를 적용
# list 형태의 sentences는 numpy array로 변환되어야 딥러닝 레이어의 입력이 될 수 있음
raw_inputs = np.array(get_encoded_sentences(sentences, word_to_index))
raw_inputs = keras.preprocessing.sequence.pad_sequences(raw_inputs,
value = word_to_index['<PAD>'], padding = 'post', maxlen = 5)
output = embedding(raw_inputs)
print(output)
tf.Tensor(
[[[ 0.02264932 0.037343 -0.04712098 -0.00348868]
[-0.04136361 -0.00685711 -0.04964727 -0.02061704]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]
[ 0.04007402 -0.01136142 -0.046102 0.00067567]]
[[ 0.02264932 0.037343 -0.04712098 -0.00348868]
[-0.04136361 -0.00685711 -0.04964727 -0.02061704]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]
[ 0.04007402 -0.01136142 -0.046102 0.00067567]]
[[ 0.02264932 0.037343 -0.04712098 -0.00348868]
[-0.02836841 -0.02056072 -0.00797039 0.02290538]
[-0.04136361 -0.00685711 -0.04964727 -0.02061704]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]
[ 0.00404279 -0.01909448 -0.03293613 -0.00015283]]], shape=(3, 5, 4), dtype=float32)
vocab_size = 10
word_vector_dim = 4 # 단어 하나를 표현하는 임베딩 벡터의 차원 수
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, word_vector_dim, input_shape = (None,)))
# 가장 널리 쓰이는 RNN인 LSTM 레이어를 사용. 이때 LSTM state 벡터의 차원수는 8로 setting(수정 가능)
model.add(keras.layers.LSTM(8))
model.add(keras.layers.Dense(8, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid')) # 최종 출력은 긍정/부정을 나타내는 1-dim
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, None, 4) 40
_________________________________________________________________
lstm (LSTM) (None, 8) 416
_________________________________________________________________
dense (Dense) (None, 8) 72
_________________________________________________________________
dense_1 (Dense) (None, 1) 9
=================================================================
Total params: 537
Trainable params: 537
Non-trainable params: 0
_________________________________________________________________
ERROR OCCUR
- numpy 1.20v 에서
Cannot convert a symbolic Tensor (lstm/strided_slice:0) to a numpy array.
의 에러가 나서 확인해보니 numpy 1.19v에서는 괜찮다 하여 downgrade 진행함- 그런데 package간 conflicst가 너무 많이 발생함
- 해결. Faced Error 시리즈에서 해결 방법 확인 가능
# Convolution Layer, Pooling 사용
vocab_size = 10
word_vector_dim = 4 # 단어 하나를 표현하는 임베딩 벡터의 차원수
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, word_vector_dim, input_shape = (None, )))
model.add(keras.layers.Conv1D(16, 7, activation = 'relu')) # Filter 7개 설정
model.add(keras.layers.MaxPooling1D(5))
model.add(keras.layers.Conv1D(16, 7, activation='relu'))
model.add(keras.layers.GlobalMaxPool1D())
model.add(keras.layers.Dense(8, activation='relu'))
model.add(keras.layers.Dense(1, activation = 'sigmoid'))
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_2 (Embedding) (None, None, 4) 40
_________________________________________________________________
conv1d (Conv1D) (None, None, 16) 464
_________________________________________________________________
max_pooling1d (MaxPooling1D) (None, None, 16) 0
_________________________________________________________________
conv1d_1 (Conv1D) (None, None, 16) 1808
_________________________________________________________________
global_max_pooling1d (Global (None, 16) 0
_________________________________________________________________
dense_2 (Dense) (None, 8) 136
_________________________________________________________________
dense_3 (Dense) (None, 1) 9
=================================================================
Total params: 2,457
Trainable params: 2,457
Non-trainable params: 0
_________________________________________________________________
GlobalMaxPooling1D()
레이어 하나만 사용하는 방법도 있음 ## GlobalMaxPooling만 사용한 경우
vocab_size = 10
word_vector_dim = 4
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, word_vector_dim, input_shape = (None, )))
model.add(keras.layers.GlobalMaxPooling1D())
model.add(keras.layers.Dense(8, activation = 'relu'))
model.add(keras.layers.Dense(1, activation= 'sigmoid'))
model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_3 (Embedding) (None, None, 4) 40
_________________________________________________________________
global_max_pooling1d_1 (Glob (None, 4) 0
_________________________________________________________________
dense_4 (Dense) (None, 8) 40
_________________________________________________________________
dense_5 (Dense) (None, 1) 9
=================================================================
Total params: 89
Trainable params: 89
Non-trainable params: 0
_________________________________________________________________
Transformer
레이어를 쓰는 등의 방법을 사용할 수 있음 본격적인 IMDB 데이터 분석은 다음 글에서 확인 가능 :)