- 머신러닝 참고 사이트
- https://tensorflow.blog/ (by 박해선)
- https://www.youtube.com/playlist?list=PLJN246lAkhQjoU0C4v8FgtbjOIXxSs_4Q
Ch8. 순환 신경망 2
- 순환 신경망 모델은 순차적인 자료에서 규칙적인 패턴을 인식하거나 그 의미를 추론할 수 있다
- RNN
- 순환하는 경로가 존재, 과거의 데이터를 기억하는 동시에 최신의 데이터로 업데이트 한다.
- 일반적으로 시계열 데이터나 순서가 의미있는 데이터를 학습시킨다.
- 순환구조 펼쳐보기
- RNN 순환구조는 처음보는 구조
- 데이터가 한방향으로만 흐름
- 출력값이 다시 입력값과 합쳐져서 계속 학습하는 방식
- BPTT
- 일반적인 오차역전파법을 적용
- 시간 흐름에 따른 역전파이기 때문에 BPTT라고 부른다.
- LSTM 구조 및 동작
- 기존의 RNN의 문제 : Vanishing gradient problem 발생
- RNN의 구조를 사용하면 거리가 멀 경우 역전파할 때, 그래디언트가 점차 줄어 학습능력이 크게 저하된다.
- 이를 개선하기 위한 방법으로 LSTM을 고안하였다.
- 입력 조절 벡터, 망각 벡터, 출력 조절 벡트를 이용해 입력과 출력 신호를 게이팅한다.
- 게이팅 : 신호의 양을 조정하는 기법
- LSTM 핵심 아이디어
- 상단을 가로지르는 수평선
- 정보가 변경되지 않고 그대로 전달
- 관통하면서 여러 유닛에 영향을 준다
- LSTM 클래스
- 긴 시퀀스를 기억할 수 있는 LSTM 레이어
- 순환 신경망 모델 만들어보기
- LSTM 레이어를 이용하여 몇가지 순환 신경망 모델을 만들어보고, 각 모델에 나비야 동요를 학습시켜본다
사용 모델
- 다층 퍼셉트론 모델
- 시퀀스 데이터 준비
- 음계가 문장보다 더 코드화 하기 쉽고
- 시계열 자료이며
- 나온 결과를 악보로 볼 수 있음
- 다음 음을 예측하기 위한 학습 데이터
예제1. “나비야” 데이터셋을 생성
- 데이터셋 생성
- 4개 음표 입력으로 다음 출력 음표를 예측할 데이터셋 만들기
- 데이터셋을 구성
- => g8 e8 e4 f8 d8 : 1~4번째 음표, 5번째 음표
- 1~4번째 열은 속성(feature)이고, 5번째 열은 클래스(class)를 나타낸다
- 이러한 딕셔너리르 이용해서 순차적인 음표를 우리가 지정한 윈도우로
- 학습 과정
- 예측 과정
- 곡 전체 예측 : 입력된 초기 4개 음표만을 입력으로 곡전체를 예측
- 한 스텝 예측 : 실제 음표 4개를 입력하여 다음 음표 1개를 예측
- 다층 퍼셉트론 모델
- 생성한 데이터셋으로 먼저 다층 퍼셉트론 모델을 학습시킨다.
예제2. 생성한 데이터셋으로 다층 퍼셉트론 모델을 학습시키기
- 총 50개 예측 중 4개가 틀려서 92%의 정확도가 나온다.
- 한스텝 예측과 곡 전체 예측을 해볼 예정
예제3. 기본 LSTM 모델
- 총 50개
- 상태유지 LSTM 모델
- 상태유지라는 것은 현재 학습된 상태가 다음 학습 시 초기 상태로 전달된다는 것을 의미한다.
- LSTM 내부적으로 기억할 것은 기억하고 버릴 것은 버려서 기억해야할 중요한 정보만 이어갈 수 있도록 상태가 유지되기 때문이다.
- 상태유지모드에서는 입력형태를 batch_input_shape = (배치사이즈, 타임스텝, 속성)으로 설정해야한다.
- model.add(LSTM(128, batch_input_shape=(1, 4, 1), stateful=True))
- 상태유지모드에서는 모델 학습시에 상태 초기화에 대한 고민이 필요하다
- 현재 샘플 학습상태가 다음 샘플 학습의 초기상태로 전달되는 식인데, 현재 샘플과 다음 샘플 간의 순차적인 관계가 없을 경우에는 상태가 유지되지 않고 초기화가 되어야 한다.
- model.reset_states()
예제4. 상태유지 LSTM 모델
- help(model.fit)을 통해 shuffle 속성 파악 가능합니다.
- 마지막 값이 처음 입력값에 영향을 주면 성능이 낮아짐
- shuffle=False (데이터를 섞지 않겠다고 설정) 반대로 True는
- 에포크 끝나고 상태를 초기화 시키는 건
- 입력값과 출력값이 있고 , 또 입력값과 출력값이 있음
- 출력값이 입력값으로 활용된다
- 끝나면 처음 데이터, 마지막 데이터는 연관관계가 없다
- 상태를 초기화 하지 않으면, 마지막 데이터 출력값이 처음 데이터 입력값으로 들어간다
- 상태 초기화 =True를 하면 마지막 데이터 출력값이 처음 데이터 입력값으로 들어가지 않는다.
- 서로 연관이 없다면, 마지막 데이터 출력값이 처음 데이터 입력값으로 들어가게 하면 안된다
- 입력 속성이 여러개인 모델 구성
- 예를 들어 기온 라는 것을 예측하기 위해서 입력으로 기온 뿐만 아니라 습도, 기압, 풍향, 풍속 등 다양한 속성이 있을 수 있다.
- 나비야 예제에서는 현재 입력값이 음정과 음길이로 나누어서 2개의 속성으로 입력한다
import tensorflow.keras as keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM
from tensorflow.keras import utils
import numpy as np
import matplotlib.pyplot as plt
# 손실 이력 클래스 정의 (학습 과정을 확인해보기 위함)
class LossHistory(keras.callbacks.Callback) :
def __init__(self) :
self.losses = []
self.accuracies = []
def on_epoch_end(self, batch, logs={}) :
self.losses.append(logs.get('loss'))
self.accuracies.append(logs.get('accuracy'))
# 데이터셋을 생성하는 함수
def seq2dataset(seq, window_size):
dataset = []
for i in range(len(seq) - window_size) :
subset = seq[i: (i+window_size+1)] # 5개씩 슬라이싱
# 코드를 숫자로 변경해서 리스트에 저장
dataset.append([code2idx[item] for item in subset])
return np.array(dataset)
# code to index
# => 코드를 숫자로 변경
# => 알파멧은 음계, 숫자는 음의 길이
# => c(도), d(레), e(미), f(파), g(솔), a(라), b(시)
# => 4: 4분음표, 8: 8분음표
code2idx = { 'c4':0, 'd4':1, 'e4': 2, 'f4': 3, 'g4': 4, 'a4': 5,
'b4': 6, 'c8': 7, 'd8': 8, 'e8': 9, 'f8': 10, 'g8': 11,
'a8': 12, 'b8': 13}
# index to code
# => 숫자를 코드로 변경
idx2code = { 0:'c4',1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4',
6:'b4', 7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8',
12:'a8', 13:'b8'}
# '나비야' 악보 출력
seq = ['g8', 'e8','e4','f8','d8','d4','c8','d8','e8','f8','g8','g8','g4',
'g8','e8','e8','e8','f8','d8','d4','c8','e8','g8','g8','e8','e8','e4',
'd8','d8','d8','d8','d8','d8','e8','f4','e8','e8','e8','e8','f8','g4',
'g8','e8','e4','f8','d8','d4','c8','e8','g8','g8','e8','e8','e4']
# 1. 데이터셋 준비하기
dataset = seq2dataset(seq, window_size=4)
# 데이터셋 확인
print(dataset.shape)
print(dataset[:3])
print('-' * 20)
# 입력과 라벨로 분리
x_train = dataset[:, 0:4] # 1~4열 슬라이싱
y_train = dataset[:, 4] # 5열 슬라이싱
max_idx_value = 13 # 코드를 숫자로 바꿨을 때, 제일 큰 수
# 입력값 정규화 : 0에서 1사이의 값으로 바꾸는것
x_train = x_train / max_idx_value
# LSTM 입력은 (샘플수, 타임스텝, 특성수)로 구성되어야함
x_train = x_train.reshape(50, 4, 1)
# 라벨값에 대한 one-hot encoding 처리
y_train = utils.to_categorical(y_train)
print(x_train.shape)
print(y_train.shape) # (50, 12)
print('-' * 20)
one_hot_vec_size = y_train.shape[1] # 12
# 2. 모델 구성하기
model = Sequential()
model.add(LSTM(128, batch_input_shape=(1, 4, 1), stateful=True))
model.add(Dense(one_hot_vec_size, activation='softmax'))
# 3. 모델 학습과정 설정하기
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
# 4. 모델 학습시키기
lossHistory = LossHistory()
for i in range(500) :
print('Epoch ' + str(i+1) + "/" + str(2000))
# shuffle=False, epoch 이전에 훈련데이터를 섞을지 여부 설정
model.fit(x_train, y_train, epochs=1, batch_size=1,
shuffle=False, callbacks=[lossHistory])
model.reset_states() # 모델 상태 초기화
# 5. 모델 학습과정 살펴보기
fig, loss_ax = plt.subplots()
acc_ax = loss_ax.twinx()
loss_ax.plot(lossHistory.losses, 'y', label='loss')
acc_ax.plot(lossHistory.accuracies, 'b', label='accuracy')
loss_ax.set_xlabel('epoch')
loss_ax.set_ylabel('loss')
acc_ax.set_ylabel('accuracy')
loss_ax.legend(loc='upper left')
acc_ax.legend(loc='lower left')
plt.show()
# 6. 모델 평가하기
scores = model.evaluate(x_train, y_train, batch_size=1)
print('손실 : ', scores[0])
print('정확도 : ', scores[1])
print('-' * 20)
# 모델 상태 초기화
model.reset_states()
# 7. 모델 사용하기
pred_count = 50 # 최대 예측 개수
# 1) 한스텝 예측
seq_out = ['g8', 'e8','e4','f8']
pred_out = model.predict(x_train, batch_size=1)
for i in range(pred_count) :
idx = np.argmax(pred_out[i]) # one-hot encoding을 인덱스값으로 변경
seq_out.append(idx2code[idx]) # 숫자를 코드로 변경
print(seq_out)
print('-' * 20)
# 정확도 계산
cnt = 0
for i, code in enumerate(seq_out) :
if seq[i] == code : cnt += 1
print('정확도 : %.2f%%' %(cnt/len(seq_out) * 100))
print('-' * 20)
# 모델 상태 초기화
model.reset_states()
# 2) 곡 전체 예측
seq_in = ['g8', 'e8', 'e4', 'f8']
seq_out = seq_in # 50개 음정이 seq_out에 저장됨
# 코드를 숫자로 변경하고 정규화시킴
seq_in = [code2idx[i] / max_idx_value for i in seq_in]
print(seq_in)
print('-' * 20)
pred_count = 50 # 최대 예측 개수
for i in range(pred_count) :
sample_in = np.array(seq_in).reshape(1, 4, 1) # 3차원 배열로 바꿈 (샘플수, 타임스텝, 특성수)
pred_out = model.predict(sample_in)
idx = np.argmax(pred_out)
seq_out.append(idx2code[idx])
seq_in.append(idx / max_idx_value)
del(seq_in[0])
print(seq_out)
print('-' * 20)
# 정확도 계산
cnt = 0
for i, code in enumerate(seq_out) :
if seq[i] == code : cnt += 1
print('정확도 : %.2f%%' %(cnt/len(seq_out) * 100))
print('-' * 20)
'자연어 처리' 카테고리의 다른 글
Cohen's kappa와 Quadratic Weighted Kappa의 차이점 (0) | 2023.08.02 |
---|---|
문장(시계열수치) 입력 다중 클래스 분류 모델(23.07.18수업) (0) | 2023.07.18 |
공적말하기 실습 및 평가 데이터 구축 과제 - 23년 7월 18일 기록 (0) | 2023.07.18 |
상태유지 LSTM 모델 (0) | 2023.07.12 |
타이타닉 데이터 분석 및 자연어처리 (RNN) (0) | 2023.07.10 |