2. LSTM을 이용하여 학습 후 모델 저장하기

2023. 9. 16. 15:43개인 프로젝트/📚 자연어 처리

import tensorflow as tf
from tensorflow.keras.layers import Embedding, Dense, LSTM
from tensorflow.keras.models import Sequential
import pandas as pd
import numpy as np
from string import punctuation

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical

# GPU 사용 가능 여부 확인
physical_devices = tf.config.list_physical_devices('GPU')
if len(physical_devices) > 0:
    tf.config.experimental.set_memory_growth(physical_devices[0], True)
    print("GPU가 사용 가능합니다.")
else:
    print("GPU를 찾을 수 없습니다. CPU를 사용합니다.")


df = pd.read_csv('output_홍길동.csv')
"""
print('열의 개수: ', len(df.columns))
# 열 헤드 나열
print(df.columns)
"""
"""
# 사용할 열에 Null값이 있는지 확인하기: 있다면 True, 없으면 False
print(df['Text'].isnull().values.any())
"""
Text = []
# 헤드라인의 값들을 리스트로 저장
Text.extend(list(df.Text.values))
print('총 샘플의 개수 : {}'.format(len(Text)))

Text = [word for word in Text if word != "ㅋ"]
print('노이즈값 제거 후 샘플의 개수 : {}'.format(len(Text)))


tokenizer = Tokenizer()
tokenizer.fit_on_texts(Text)
vocab_size = len(tokenizer.word_index) + 1
print('단어 집합의 크기 : %d' % vocab_size)


sequences = list()

for sentence in Text:
    encoded = tokenizer.texts_to_sequences([sentence])[0]
    for i in range(1, len(encoded)):
        sequence = encoded[:i+1]
        sequences.append(sequence)


# 정수에 할당된 단어 찾기
index_to_word = {}
for key, value in tokenizer.word_index.items():  # 인덱스를 단어로 바꾸기 위해 index_to_word를 생성
    index_to_word[value] = key

print('빈도수 상위 1번 단어 : {}'.format(index_to_word[1]))

max_len = max(len(l) for l in sequences)
# max_len = 24 # 너무 길어서 학습이 진행되지 않을 경우 수정
print('샘플의 최대 길이 : {}'.format(max_len))


sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
sequences = np.array(sequences)
# 일부 최신 데이터만 사용: 결과를 보기 위한 시간과 품질 타협하기
X = sequences[-10000:, :-1]
y = sequences[-10000:, -1]
y = to_categorical(y, num_classes=vocab_size)


# ---------------------------------------------------

embedding_dim = 10
hidden_units = 128

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(LSTM(hidden_units))
model.add(Dense(vocab_size, activation='softmax'))
model.compile(loss='categorical_crossentropy',
              optimizer='adam', metrics=['accuracy'])
# 모델 학습
model.fit(X, y, epochs=200, verbose=2)

# 모델 저장
model.save('my_model.keras', overwrite=True)
print("모델 저장이 완료되었습니다.")​

  소스코드 전문입니다. 세부적인 설명을 하겠습니다.


# GPU 사용 가능 여부 확인
physical_devices = tf.config.list_physical_devices('GPU')
if len(physical_devices) > 0:
    tf.config.experimental.set_memory_growth(physical_devices[0], True)
    print("GPU가 사용 가능합니다.")
else:
    print("GPU를 찾을 수 없습니다. CPU를 사용합니다.")

  머신러닝 시 CPU가 사용되는데 이때 GPU도 사용할 것인지에 대한 옵션입니다. NVIDIA 그래픽카드면 되는 것으로 알고 있으나 제 컴퓨터로는 활성화가 되지 않아 CPU로 진행하였습니다. 혹시 문제가 생긴다면 이 부분을 지워보세요.

 

df = pd.read_csv('output_홍길동.csv')
"""
print('열의 개수: ', len(df.columns))
# 열 헤드 나열
print(df.columns)
"""
"""
# 사용할 열에 Null값이 있는지 확인하기: 있다면 True, 없으면 False
print(df['Text'].isnull().values.any())
"""

  output_홍길동.csv 파일을 읽어옵니다. 이때 의도하는 파일을 제대로 가져오는지 확인하기 위한 내용이 주석에 있습니다.

  첫 번째 주석의 올바른 결과는 Actor, Text가 나와야 합니다.

  두 번째 주석의 올바른 결과는 False가 나와야 합니다. 만약 True가 나오는 경우 노이즈 값을 제거한 후 사용해야합니다.

 

Text = []
# 헤드라인의 값들을 리스트로 저장
Text.extend(list(df.Text.values))
print('총 샘플의 개수 : {}'.format(len(Text)))

  Text 열의 내용을 리스트로 저장한 후, 총 샘플의 개수를 알려줍니다. 이 부분을 통해서 대략적인 학습 시간과 요구사양을 생각하기 시작합니다.

 

Text = [word for word in Text if word != "ㅋ"]
print('노이즈값 제거 후 샘플의 개수 : {}'.format(len(Text)))

  노이즈 값을 제거하는 역할을 맡습니다. 저와 같은 경우 채팅창에 말 끝마다 ㅋ을 붙이는 습관이 있어서 의미없이 자꾸 있다보니 한번 걸렀습니다. 

  추후에 여러 번의 학습 과정을 거쳐 좋은 결과를 내기 위해서는 이 부분에서 쓸모없는 데이터를 잘 정제해야 합니다.

 

tokenizer = Tokenizer()
tokenizer.fit_on_texts(Text)
vocab_size = len(tokenizer.word_index) + 1
print('단어 집합의 크기 : %d' % vocab_size)


sequences = list()

for sentence in Text:
    encoded = tokenizer.texts_to_sequences([sentence])[0]
    for i in range(1, len(encoded)):
        sequence = encoded[:i+1]
        sequences.append(sequence)


# 정수에 할당된 단어 찾기
index_to_word = {}
for key, value in tokenizer.word_index.items():  # 인덱스를 단어로 바꾸기 위해 index_to_word를 생성
    index_to_word[value] = key

print('빈도수 상위 1번 단어 : {}'.format(index_to_word[1]))

  토크나이저 과정입니다. 단어 집합의 크기를 출력하거나 빈도수 상위 1등의 단어를 출력하는 부분은 없어도 되나 호기심으로 출력하였습니다. 

  그러나 tokenizer, sequences가 없다면 학습이 불가능하므로 이 부분은 필수입니다.

 

max_len = max(len(l) for l in sequences)
# max_len = 24 # 너무 길어서 학습이 진행되지 않을 경우 수정
print('샘플의 최대 길이 : {}'.format(max_len))

  저의 대화 기록에서는 샘플의 최대 길이가 551이었습니다. 이런 경우 고사양의 하드웨어와 많은 시간을 요구할 수 있으므로 주석과 같이 짧게 짤라내어 정상적으로 코드가 되는지 확인 후 제대로 된 길이의 샘플링을 거치면 될 것 같습니다.

 

sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
sequences = np.array(sequences)
# 일부 최신 데이터만 사용: 결과를 보기 위한 시간과 품질 타협하기
X = sequences[-10000:, :-1]
y = sequences[-10000:, -1]
y = to_categorical(y, num_classes=vocab_size)

  이번에 사용한 말뭉치데이터 샘플 개수는 8만 개가 넘습니다. 그 모든 것을 쓰면 1회 학습에 6분에 가까운 시간이  소모되므로 최근 1만 개의 데이터만 사용하기로 합니다. 시간적 여유가 되거나 학습 결과의 퀄리티를 올리기 위해서 이 값을 적절하게 바꾸는 것을 추천드립니다.

 

embedding_dim = 10
hidden_units = 128

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(LSTM(hidden_units))
model.add(Dense(vocab_size, activation='softmax'))
model.compile(loss='categorical_crossentropy',
              optimizer='adam', metrics=['accuracy'])
# 모델 학습
model.fit(X, y, epochs=200, verbose=2)

# 모델 저장
model.save('my_model.keras', overwrite=True)
print("모델 저장이 완료되었습니다.")

  진짜 학습을 하는 부분입니다. epochs를 통해 몇 번의 학습을 반복할 것인지를 결정합니다. 저는 참고한 문헌을 토대로 200을 넣었으나, 사용하는 데이터의 품질에 따라 다르므로 적절하게 바꾸어야 합니다. 제가 할 때는 400이 얼추 90%의 정확도를 가질 듯 했습니다.

  학습 모델을 저장할 때 .h5로 저장하였으나 인코딩 코덱 문제가 생겨 사용하지 못했습니다. 이런 일을 방지하기 위해서는 .keras로 저장하시는 것을 추천합니다.


참고 문헌

 

08-06 RNN을 이용한 텍스트 생성(Text Generation using RNN)

다 대 일(many-to-one) 구조의 RNN을 사용하여 문맥을 반영해서 텍스트를 생성하는 모델을 만들어봅시다. ## 1. RNN을 이용하여 텍스트 생성하기 예를 들어서 '…

wikidocs.net