import os
imdb_dir = 'D:/src/aclImdb'
train_dir = os.path.join(imdb_dir, 'train') # aclimdb 폴더의 훈련 데이터 내용을 가져온다
labels = [] # labels와 texts 라는 두 개의 빈 리스트를 만든다
texts = []
for label_type in ['neg', 'pos']: # train 폴더에 있는 pos 12,500 + neg 12,500개 데이터 읽는다
dir_name = os.path.join(train_dir, label_type) # neg와 pos 폴더 각각에 접근한다
for fname in os.listdir(dir_name):
if fname[-4:] == '.txt': # 마지막 4 글자가 .txt 로 끝나는지를 확인한다
f = open(os.path.join(dir_name, fname), encoding='utf8')
texts.append(f.read()) # 텍스트를 읽어서 texts 리스트에 연결한다
f.close() if label_type == 'neg': # 만약 현재 폴더가 neg 폴더라면
labels.append(0) # texts와 같은 순서의 labels 리스트에는 0을 저장한다
else:
labels.append(1) # pos 폴더라면 같은 순서의 labels 리스트에 1을 저장한다
# 데이터 확인
print('texts 0:', texts[0])
print('texts len:', len(texts))
print('labels 0:', labels[0])
print('labels len:', len(labels))
# 텍스트에 사용된 단어의 종류를 빈도 순으로 정렬하는 작업을 수행한다
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
import math
validation_ratio = math.floor(len(texts) * 0.3) # 검증 샘플은 전체의 30%로 한다
max_words = 10000 # 데이터셋에서 가장 빈도 높은 10,000 개의 단어만 사용한다
maxlen = 200 # 항상 200 단어가 되도록 길이를 고정한다
tokenizer = Tokenizer(num_words=max_words) # 상위빈도 10,000 개의 단어만을 추려내는 Tokenizer 객체 생성
tokenizer.fit_on_texts(texts) # 단어 인덱스를 구축한다
word_index = tokenizer.word_index # 단어 인덱스만 가져온다
# Tokenizing 결과 확인
print('전체에서 %s개의 고유한 토큰을 찾았습니다.' % len(word_index))
print('word_index type: ', type(word_index))
print('word_index: ', word_index)
# 문자를 숫자로 변환하는 작업을 수행한다
# 상위 빈도 10,000(max_words)개의 단어만 추출하여 word_index의 숫자 리스트로 변환한다.
data = tokenizer.texts_to_sequences(texts) # Tokenizer 결과가 여기서 반영된다.
print('data 0:', data[0])
# Padding은 데이터의 길이를 고정시켜 준다
# 지정된 길이에 모자라는 것은 0으로 채우고, 넘치는 것은 잘라낸다
# 텐서의 크기를 맞춰야 하는 경우 유용하다
# one-hot encoding 등을 통해 길이가 고정될 수 있다면 하지 않아도 된다
# 단어의 선택은 뒤에서부터 하며, nested list를 2D 텐서(2차원 넘파이 배열)로 만든다
from keras.preprocessing.sequence import pad_sequences
sequences = [[1, 2, 3, 4, 5], [1, 2, 3, 4], [1]] # nested list
padded = pad_sequences(sequences, maxlen=3) # 2D tensor
print(padded)
data = pad_sequences(data, maxlen=maxlen)
print('data:', data)
print('data 0:', data[0])
# one-hot encoding은 모든 숫자를 0과 1로만 만든다
# 원-핫 인코딩 함수
def to_one_hot(sequences, dimension):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
return results
# data를 one-hot-인코딩으로 0과 1의 벡터로 변환
# label은 이미 0과 1로 태깅되어 있으므로, list에서 넘파이 배열로만 변환. float32를 지정하지 않으면 int32로 저장된다
data = to_one_hot(data, dimension=max_words)
labels = np.asarray(labels).astype('float32')
print('data:', data)
len(data[0]) # dimension=10000으로 했으므로 각 행은 10,000개를 가지고 있다
print('data [0][0:100]:', data[0][0:100])
## Train 데이터와 Validation 데이터 준비
print('데이터 텐서의 크기:', data.shape) # (25000, 10000)
print('레이블 텐서의 크기:', labels.shape) # (25000,) data와 label이 모두 2D 텐서가 되었음
indices = np.arange(data.shape[0]) # 0 ~ 24999 까지의 숫자를 생성
np.random.shuffle(indices) # 0 ~ 24999 까지의 숫자를 랜덤하게 섞음
data = data[indices] # 이것을 인덱스로 하여 2D 텐서 데이터를 섞음
labels = labels[indices] # label도 같은 순서로 섞음
x_train = data[validation_ratio:] # 훈련데이터의 70%를 훈련데이터
y_train = labels[validation_ratio:] # 훈련데이터의 70%를 훈련데이터 Label (data와 labels는 같은 순서)
x_val = data[:validation_ratio] # 훈련데이터의 30%를 검증데이터
y_val = labels[:validation_ratio] # 훈련데이터의 30%를 검증데이터 Label
## 모델 정의하기
from keras.models import Sequential
from keras.layers import Dense
model = Sequential() # 모델을 새로 정의
model.add(Dense(64, activation='relu', input_shape=(max_words,))) # 첫 번째 은닉층
model.add(Dense(32, activation='relu')) # 두 번째 은닉층
model.add(Dense(1, activation='sigmoid')) # 출력층
model.summary()
# 모델 컴파일
# 가중치 업데이트 방법은 RMSprop을 사용하였다. 이동평균의 방법을 도입하여 조절해간다
# 신경망의 출력이 확률이므로 crossentropy를 사용하는 것이 최선이다
# crossentropy는 원본의 확률 분포와 예측의 확률 분포를 측정하여 조절해 간다
# 또한 이진 분류이므로 binary_crossentropy를 사용한다
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
# 모델 훈련
# 32개씩 미니 배치를 만들어 10번의 epoch로 훈련한다. 보통 32개에서 시작하여 512개까지 중에서 찾는다
# 훈련 데이터로 훈련하고, 검증 데이터로 검증한다
# 반환값의 history는 훈련하는 동안 발생한 모든 정보를 담고 있는 딕셔너리이다
history = model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_val, y_val))
history_dict = history.history
# multidimensional numpy arrays를 저장할 수 있는 h5 file(HDF) 포맷으로 저장한다
model.save('text_binary_model.h5')
# 훈련데이터에서 사용된 상위빈도 10,000개의 단어로 된 Tokenizer 저장
# 새로 입력되는 문장에서도 같은 단어가 추출되게 한다
import pickle
with open('text_binary_tokenizer', 'wb') as handle:
pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)
# history 딕셔너리 안에 있는 정확도와 손실값을 가져와 본다
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
print('Accuracy of each epoch:', acc) # [0.79, 0.90, 0.93, 0.94, 0.96, 0.97, 0.98, 0.98, 0.98, 0.99]
epochs = range(1, len(acc) +1) # range(1, 11)
import matplotlib.pyplot as plt
# 훈련데이터의 정확도에 비해 검증데이터의 정확도는 낮게 나타난다
# epoch가 높아지면 모델은 훈련데이터에 매우 민감해져 오히려 새로운 데이터를 잘 못 맞춘다
plt.plot(epochs, acc, 'bo', label='Training Acc')
plt.plot(epochs, val_acc, 'b', label='Validation Acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure() # 새로운 그림을 그린다
# 훈련데이터의 손실값은 낮아지나, 검증데이터의 손실값은 높아진다
# 손실값은 오류값을 말한다. 예측과 정답의 차이를 거리 계산으로 구한 값이다
plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()