실행 환경

  • OS : Ubuntu20.04
  • shell : bash or zsh
  • Elasticsearch version : 7.17.4
  • Java version 8

1. 패키지 update & https repository 접근 위한 패키지 설치

sudo apt update
sudo apt install apt-transport-https

 

2. Java 설치

sudo apt install openjdk-8-jdk

# java 버전 확인

java -version
# openjdk version "1.8.0_312"
# OpenJDK Runtime Environment (build 1.8.0_312-8u312-b07-0ubuntu1~20.04-b07)
# OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)

# JAVA 변수 등록 (아래 command 추가) 
(~/.zshrc)
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64
export PATH=$PATH:$JAVA_HOME/bin

# 변수 확인
echo JAVA_HOME # ==> JAVA_HOME 으로 출력됨

 

3. 엘라스틱서치 repository 추가

wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -

# 아래 명령어가 안 되서 새로운 명령어 대체
# sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/source.list.d/elastic-7.x.list'
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee –a /etc/apt/sources.list.d/elastic-7.x.list

sudo apt update

 

4. 엘라스틱서치 설치

sudo apt install elasticsearch

 

5. 엘라스틱서치 서비스 실행

sudo systemctl enable elasticsearch.service
sudo systemctl start elasticsearch.service

 

6. 엘라스틱서치 Test

# 엘라스틱서치 통신
curl -X GET "localhost:9200"

# process 확인
netstat -an | grep 9200

 

7. Nori 한글 형태소 분석기 설치

# plugin 설치 파일 경로 이동
cd /usr/share/elasticsearch/

# nori 설치
sudo bin/elasticsearch-plugin install analysis-nori

# elasicsearch 재시작
sudo systemctl stop elasticsearch.service
sudo systemctl start elasticsearch.service

 

8. 엘라스틱서치 config setting

(/etc/elasticsearch/elasticsearch.yml)

# root 계정으로 수정 가능
sudo vi /etc/elasticsearch/elasticsearch.yml

# cluster name 설정 시 사용
cluster.name: local-elasticsearch

# node name 설정
node.name: local-nlp

# host 및 port 설정 (실제 아래는 세팅 안해도 default로 돼 있는 듯)
network.host: localhost
http.port: 9201

 

9. 엘라스틱서치 index list 확인

curl -XGET "http://localhost:9200/_cat/indices?format=json&pretty"

# 맨 처음 인덱스만 확인할 때
[
  {
    "health" : "green",
    "status" : "open",
    "index" : ".geoip_databases",
    "uuid" : "FchspXH5QGmC0C8L0A_biQ",
    "pri" : "1",
    "rep" : "0",
    "docs.count" : "40",
    "docs.deleted" : "0",
    "store.size" : "37.6mb",
    "pri.store.size" : "37.6mb"
  }
]

# 특정 인덱스(purpose) 추가 후 list
[
  {
    "health" : "green",
    "status" : "open",
    "index" : ".geoip_databases",
    "uuid" : "FchspXH5QGmC0C8L0A_biQ",
    "pri" : "1",
    "rep" : "0",
    "docs.count" : "40",
    "docs.deleted" : "0",
    "store.size" : "37.6mb",
    "pri.store.size" : "37.6mb"
  },
  {
    "health" : "green",
    "status" : "open",
    "index" : "purpose",
    "uuid" : "oTg6ut6IT7yZNt878Tpagw",
    "pri" : "1",
    "rep" : "0",
    "docs.count" : "0",
    "docs.deleted" : "0",
    "store.size" : "226b",
    "pri.store.size" : "226b"
  }
]

 

10. index 추가 전 synonym.txt (동의어사전) 등록 필요

  • 아래 index_config.json 내용 확인해보면 filter > synonym > synonym_path 에 analysis/synonym.txt 로 등록 돼 있음
  • elasticsearch의 default path 는 /etc/elasticsearch

(/etc/elasticsearch/analysis/synonym.txt)

TAX,tax,세금
상품 => 경품

 

 

 

 

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (4) - 심층 RNN

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (3) - 심층 RNN NLP 자연어 처리를 위한 RNN 기초 (2) NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망..

richdad-project.tistory.com

 

층 정규화(Layer Normalization)

긴 시퀀스를 활용해서 RNN 신경망 모델로 구현하면 불안정한 그레디언트 문제가 발생합니다. 그레이던트의 소실, 폭주 혹은 긴 훈련 시간, 불안정한 훈련 등 다양한 문제에 봉착할 수 있습니다. 이런 긴 시퀀스를 처리하기 위해서 다양한 해결방법이 있지만 그 중 층 정규화에 대해 먼저 살펴보겠습니다.

층 정규화는 RNN 모델 각 층 사이에서 feature dimention에 대해 정규화하는 방법입니다. 즉, 각 타임 스텝 사이에서 층 정규화를 통해 긴 시퀀스 데이터를 학습할 때 생기는 정보 손실과 불안정한 그레디언트를 예방할 수 있습니다. 간단하게 구현해 보겠습니다.

class LNSimpleRNNCell(tf.keras.layers.Layer):
	def __init__(self, units, activation='tanh', **kwargs):
    	super().__init__(**kwargs)
        
        # state는 이전 타임 스템의 은닉층 상태를 담은 매개변수로서
        # 하나 이상의 텐서를 담고 있다.
        
        # 각 state_size와 output_size는 unit 개수로 맞춰준다
        self.state_size = units
        self.output_size = units
        
        # SimpleRNN Cell을 구성하는데 활성화 함수가 없는 이유는
        # 이전 스텝의 결과(output)와 현재 input(state, 은닉층)을 선형 연산 후 활성화 함수 이전에 층 정규화를 수행하기 위해서힙니다.
        self.simple_rnn_cell = tf.keras.layers.SimpleRNN(units, activation=None)
        
        # 각 타임 스텝마다 층 정규화가 이뤄질 수 있도록 설정
        self.layer_norm = tf.keras.layer.LayerNormalization()
        # 앞에서 설정하지 않았던 활성화 함수를 층 정규화 이후에 세팅합니다.
        self.activation = tf.keras.activations.get(activation) # 탄젠트 함수를 activation으로 사용
        
    def call(self, inputs, states):
    	
        # SimpleRNN 셀을 사용하여 현재 입력(inputs)과 이전 은닉 상태(states)의 선형 조합을 계산합니다.
        # 여기서 출력은 은닉 상태와 동일합니다. 즉, new_stats[0] == outputs
        # 나머지 new_stats는 무시해도 괜찮음
        outputs, new_states = self.simple_rnn_cell(inputs, states)
        
        # 층 정규화와 활성화 함수 차례대로 적용
        norm_outputs = self.activation(self.layer_norm(outputs))
       
        # 출력을 두번 반환
        # 하나는 출력, 다른 하나는 새로운 은닉 상태
        return norm_outputs, [norm_outputs]

# 위 사용자 정의 Cell을 사용하려면 tf.keras.layers.RNN 층을 만들어 이 class 객체를 전달하면 됩니다.
model = tf.keras.models.Sequential([
    tf.keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True, input_shape=[None,1]),
    tf.keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True),
    tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(10))
])

 

위처럼 층 정규화 방법 외에도 dropout 비율 정의를 통해서도 긴 시퀀스를 다루기 위해 불안정한 그레디언트 문제를 감소할 수 있습니다. 그럼, 이제 단기 기억 문제에 대해 알아보고 이에 대한 방안인 LSTM Cell에 대해 알아보겠습니다.


LSTM

RNN 모델에서 타임 스텝이 계속 진행할수록 처음 타임 스텝에서 가져온 시퀀스 데이터의 정보들이 소멸됩니다. 즉, 단기 기억에만 머물게 되는 문제가 발생합니다. 이 문제를 해결하기 위해 장기기억을 담당하는 Cell이 개발됐습니다. 바로 LSTM입니다.

장단기 메모리 셀 즉, LSTM Cell은 훈련을 빠르게 진행하고, 시퀀스 데이터의 장기간 의존성을 유지할 것입니다. keras에서 구현은 간단합니다.

# 간단히 LSTM Cell 사용
model =tf.keras.models.Sequential([
    tf.keras.layers.LSTM(20, return_sequences=True, input_shape=[None, 1]),
    tf.keras.layers.LSTM(20, return_sequences=True),
    tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(10))
])

# RNN 층에 LSTMCell을 매개변수로 지정
model = tf.keras.models.Sequential([
     tf.keras.layers.RNN(tf.keras.layers.LSTMCell(20), return_sequences=True, inpit_shape=[None,1]),
     tf.keras.layers.RNN(tf.keras.layers.LSTMCell(20), return_sequences=True)
     tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(10))
])

 

그런데 GPU 사용을 고려할 때, LSTMCell이 최적화 돼 있어서 일반적으로는 LSTMCell을 직접사용하고, 사용자 정의 Cell을 만들 때는 RNN layer를 사용하는게 일반적입니다.

LSTM Cell의 작동 구조는 매우 복잡합니다.

출처 : http://www.rex-ai.info/docs/AI_study_LSTM

 

키포인트는 LSTM 네트워크에서 장기 상태로 저장할 값, 버릴 값, 읽어드릴 값을 학습하는 것입니다. 이전 장기 기억인 (Cell) state는 삭제 게이트를 지나서 일부 정보를 손실하고, 입력 게이트를 지나 선택된 정보들을 추가합니다. 이렇게 만들어진 새로운 장기 기억은 다음 장기 기억(Next Cell State)으로 전달됩니다. 그래서 각 타임 스텝 마다 일부 정보는 손실되고, 추가됩니다. 또한 이 새롭게 만들어진 장기 기억은 tanh 활성화 함수를 거쳐, 출력 게이트를 통과합니다. 이렇게 해서 만들어진 단기 상태가 Output(\(h_t\)) 값으로 전달됩니다.

간략히 정리해보면 LSTM 네트워크는 중요한 입력은 인식하고, 장기 상태를 최적의 정보로 유지하기 위해 보존하고, 필요할 때마다 이를 추출하며 학습합니다. 이런 LSTM 모델은 긴 텍스트, 오디오 등 장기 패턴을 발견하는 데 우수한 성능을 보이고 있습니다.


LSTM 변종 GRU

GRU 셀은 LSTM 셀의 변종 버전으로 구조가 간소화되고, 유사하게 작동합니다. 간소화된 내용을 확인해 보면, LSTM 셀은 새로운 장기 기억과 은닉 상태 값 2개가 전달됐습니다. GRU는 이 2개를 합쳐서 하나의 벡터 값으로 전달됩니다. 그리고 하나의 게이트 제어기가 삭제 게이트와 입력 게이트 역할을 동시에 수행합니다. 마지막으로 출력 게이트가 없습니다. 즉, 전체 타임 스텝마다 해당 state 벡터가 출력됩니다. 그래서 새로운 게이트 제어기가 만들어졌는데 각 타임 스텝에서 생성한 state 벡터 중 어떤 값을 main layer에 노출시킬지 결정하는 게이트 제어기가 있습니다.

GRU 셀은 RNN의 대표적인 성공 모델입니다. 한가지 아쉬운 점은 기존 RNN, LSTM에 비해 긴 시퀀스를 잘 다루긴 하지만, 매우 제한적인 단기 기억을 가지고 있습니다. 이 문제를 해결하기 위한 방법은 다음 포스팅에서 소개하겠습니다.

 

 

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (4) - 심층 RNN

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (3) - 심층 RNN NLP 자연어 처리를 위한 RNN 기초 (2) NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망..

richdad-project.tistory.com

 

 

 

 

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (3) - 심층 RNN

NLP 자연어 처리를 위한 RNN 기초 (2) NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하..

richdad-project.tistory.com

 

지난 포스팅에 이어 심층 RNN으로 여러개의 타임 스텝을 예측하는 문제에 대해 다뤄보겠습니다. 지난 포스팅에서는 1개의 타임 스텝의 예측값을 출력한 후 그 값을 입력값에 적용해서 그 다음 스텝을 예측하는 방식으로 살펴봤는데요. 성능이 썩 좋지 않았습니다. 그래서 이번에는 다른 방법을 소개해 보겠습니다.

 

한 번에 10 스텝 예측하기

두 번째 방법으로 볼 수 있는데, 한 번에 예측하고 싶은 개수 만큼 예측하는 방법입니다. 이번 포스팅 예제에서는 10개의 타임 스텝 값을 한 번에 예측해보도록 코드를 구현해 보겠습니다. 원리는 기존 RNN의 모델과 같은 시퀀스-투-벡터 형식인데, 결과값 벡터가 이전에는 1개로 구성돼 있다면 이번에는 10개로 구성되게끔 모델을 생성하면 됩니다.

# 데이터 새롭게 구성
n_steps = 50 
series = generate_time_series(10000, n_steps+10) 

# 출력값 벡터가 10개인 모델 구성에 맞게 데이터도 재구성
train_x, train_y = series[:7000, :n_steps], series[:7000, -10:, 0] 
val_x, val_y = series[7000:9000, :n_steps], series[7000:9000, -10:, 0] 
test_x, test_y = series[9000: , :n_steps], series[9000: , -10:, 0]

#######################################################################

# 모델
model = tf.keras.models.Sequential([
    tf.keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    tf.keras.layers.SimpleRNN(20),
    # 다음 타임 스텝 10개를 한 번에 예측하기 위한 layer
    tf.keras.layers.Dense(10)
])

model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = tf.keras.losses.MeanSquaredError(),
    metrics = [tf.keras.metrics.MeanSquaredError()]
)

history = model.fit(
    train_x,
    train_y,
    epochs=20,
    validation_data = (val_x, val_y)
)

# MSE 확인
pred_y = model.predict(test_x)
print(np.mean(tf.keras.losses.mean_squared_error(test_y, pred_y)
# ==> 0.01045579

 

테스트 데이터로 평가해 본 결과 0.01로 이전보다 높은 성능을 보이고 있습니다. 하지만 여기서 더 개선할 부분이 있습니다. 현재 모델은 시퀀스-투-벡터 형식의 모델입니다. 이 말은 위에서 구성한 모든 타임 스텝의 결과를 반영하는 것이 아니라 제일 마지막 타입 스텝의 결과 값만을 반영해서 최종 출력이 나온다는 의미입니다. 그렇다면 이 모델을 시퀀스-투-시퀀스로 바꾸면 어떻게 될까요?

이 방식을 적용한다면, 마지막 타임 스텝에서 나오는 결과뿐만 아니라 모든 타임 스텝에서 출력되는 결과를 적용할 수 있습니다. 이 말은 더 많은 오차 그레디언트가 모델에 흐른다는 의미고, 시간에 구애 받지 않습니다. 즉, 각 타임 스텝의 출력에서 그레디언트가 적용될 수 있습니다.

좀 더 풀어서 설명하면, 타임 스텝 0에서 타임 스텝 1 ~ 10까지 예측을 담은 벡터를 출력합니다. 그 다음 타임 스텝 1에서 2 ~ 11까지 예측을 담은 벡터를 출력합니다. 각 타겟 값은 입력 시퀀스와 길이가 동일합니다. 즉, 타겟 시퀀스틑 각 타임 스텝마다 10차원 벡터를 출력합니다.

 

위 방식을 적용할 수 있는 타겟 데이터를 새로 만들겠습니다.

# New 타겟 데이터
Y = np.empty((10000, n_steps, 10))
# Y.shape ==> (10000, 50, 10)

# 비어있는 Y에 값 채우기
for step in range(1, 10 +1):
    Y[:,:,step-1] = series[:, step:step+n_steps, 0]

Y_trin = Y[:7000] # ==> (7000, 50, 10)
Y_valid = Y[7000:9000] # ==> (2000, 50, 10)
Y_test = Y[9000:] # ==> (1000, 50, 10)

 

기본 RNN 모델을 시퀀스-투-시퀀스 모델로 바꾸려면 모든 순환 층의 결과 값을 출력하고 반영해야 합니다. 그래서 마지막 층에도 return_sequences=True 값을 부여해줍니다. 그런 다음 각 층에서 나온 출력값을 마지막 Dense Layer에 모두 적용해야 합니다. 이 부분을 가능하게 해주는 기능이 keras에서 TimeDistributed Layer를 제공합니다. 

TimeDistributed Layer 작동 원리는 각 타임 스텝을 별개의 샘플처럼 다룰 수 있도록 입력 크기를 (배치 크기, 타임 스텝 수, 입력 차원) → (배치 크기 × 타임 스텝 수, 입력 차원) 으로 바꿉니다. 우리 코드에서는 SimpleRNN 유닛이 20개로 설정해서 입력 차원 수를 20으로 세팅한 후 Dense Layer에 적용됩니다. 그리고 Dense Layer에서 출력할 때는 다시 원래대로 (배치 크기, 타임 스텝 수, 입력 차원) 로 출력 크기가 변하고, Dense 유닛을 10이라고 지정해서 입력 차원을 10으로 세팅될 것입니다. 모델을 보겠습니다.

new_model = tf.keras.models.Sequential([
	tf.keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None,1]),
    tf.keras.layers.SimpleRNN(20, return_sequences=True),
    tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(10))
])

# 훈련 때는 모든 타임 스텝의 결과를 활용해서 MSE를 계산하는데 
# 최종 실제 모델 평가 결과는 마지막 타임 스텝의 출력에 대한 MSE만 계산하면 되서 사용자 정의 지표를 만들 필요가 있다.
def last_time_step_mse(y_true, y_pred):
    return tf.keras.metrics.mean_squared_error(y_true[:, -1], y_pred[:, -1])

new_model.compile(
    optimizer = tf.keras.optimizers.Adam(lr=0.01),
    loss = 'mse',
    metrics = [last_time_step_mse]
)

new_history = new_model.fit(
    train_x,
    Y_trin,
    epochs=20,
    validation_data = (val_x, Y_valid)
)

# 최종 MSE 확인
print(new_history.history['val_last_time_step_mse'][-1])
# ==> 0.006069639232009649

 

성능이 확실히 개선된 결과를 확인할 수 있습니다. 이 RNN 구조를 사용해서 다음 타임 스텝 10개를 예측하고 이 출력값을 다시 입력 시계열에 연결해서 다시 다음 10 타임 스텝의 값을 예측하도록 세팅할 수 있습니다. 

지금까지 RNN 신경망에 대해 알아봤습니다. 한계점도 존재하는데 길이가 긴 시계열 데이터나 시퀀스 에서는 잘 작동하지 않습니다. 이 문제를 어떻게 해결할 수 있는지 다음 포스팅에서 다뤄보겠습니다.

 

 

NLP 자연어처리를 위한 RNN 알고리즘 코드 기초 (3) - 심층 RNN

NLP 자연어 처리를 위한 RNN 기초 (2) NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하..

richdad-project.tistory.com

 

 

출처 : https://excelsior-cjh.tistory.com/184

 

 

NLP 자연어 처리를 위한 RNN 기초 (2)

NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하기 위해 만들어진 딥러닝 모델입니

richdad-project.tistory.com

 

심층 RNN

지난 포스팅에서 SimpleRNN으로 모델을 구성해봤다면, 이 RNN 신경망 층을 여러개 쌓는 심층 RNN을 구현해보겠습니다. 구현은 매우 쉽습니다. SimpleRNN 층을 추가하면 됩니다.

import tensorflow as tf

model = tf.keras.models.Sequential([
    tf.keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    tf.keras.layers.SimpleRNN(20, return_sequences=True),
    tf.keras.layers.SimpleRNN(1)
])

model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = tf.keras.losses.MeanSquaredError(),
    metrics = [tf.keras.metrics.MeanSquaredError()]
)

history = model.fit(
    train_x,
    train_y,
    epochs=20,
    validation_data = (val_x, val_y)
)

 

심층 RNN으로 구현할 경우 validation MSE 지표에서 최소값이 0.00266 정도까지 나옵니다.

우리가 평가 기준으로 삼고 있는 MSE와 Loss의 변화 그래프입니다. 아주 예쁘게 좋은 성과를 보여주고 있습니다.


위 코드에서 RNN 층을 맨 마지막에 하나의 유닛으로 구성했습니다. 이는 결론이 하나의 값으로 귀결돼야 하기 때문입니다. 이 마지막 RNN층은 사실 타임 스텝이 넘어 갈 때 필요한 모든 정보를 하나로 축약해서 전달할 때 필요할 듯 싶습니다. 그래서 사실, 우리가 얻고 싶은 모델 결과에서 마지막 RNN층 보다는 다른 layer 쌓는 게 더 낳을 수도 있습니다. 왜냐면 RNN 층은 활성화 함수로 tanh 활성화 함수를 사용하기 때문에 다른 활성화 함수를 사용하는 layer를 넣는게 나을 수 있습니다. binary classification에서는 마지막 결론을 출력하는 층으로 Dense 층을 사용합니다.

import tensorflow as tf

model = tf.keras.models.Sequential([
    tf.keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    # 마지막 Dense 층에 모든 정보를 전달하면서 다른 활성화 함수를 사용하기 위해 
    # RNN의 마지막 층은 return_sequences=True 를 지정하지 않습니다.
    tf.keras.layers.SimpleRNN(20),
    tf.keras.layers.Dense(1)
])

model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = tf.keras.losses.MeanSquaredError(),
    metrics = [tf.keras.metrics.MeanSquaredError()]
)

history = model.fit(
    train_x,
    train_y,
    epochs=20,
    validation_data = (val_x, val_y)
)

 

결과는 앞에서 봤던 결과와 거의 동일하게 나옵니다. 이 모델로 훈련할 경우 빠르게 수렴하고 성능도 좋은 것으로 판단할 수 있습니다. 또한 출력층의 활성화 함수를 원하는 함수로 변동도 가능합니다.


1 이상 타임 스텝 예측

지금 까지는 바로 다음 타임 스텝의 값만을 예측해봤습니다. 그런데 10, 100, 1000 타임 스텝을 예측하고 싶을 때는 어떻게 해야할까요?

 

첫 번째 방법

pretrained model을 활용해서 다음 값을 예측한 후 다시 이 값을 입력값으로 넣어서 그 다음값을 예측하는 방식을 반복하면 여러 타임 스텝의 값을 예측할 수 있습니다.

 

새로운 데이터 예측 (심층 RNN 모델 활용)

# 새로운 데이터로 예측
new_series = generate_time_series(1, n_steps + 10)

new_x, new_y = new_series[:, :n_steps], new_series[:, n_steps:]

x = new_x
for step_ahead in range(10):
    # 처음부터 하나씩 증가하면서 다음 타임 스텝을 예측 후 결과 값을 기존 값(3차원)에 추가할 수 있도록 shape 변환
    recent_y_pred = model.predict(x[:, step_ahead:])[:, np.newaxis, :]
    x = np.concatenate([x, recent_y_pred], axis=1)

y_prd = x[:, n_steps:]

# MSE 점수
np.mean(tf.keras.losses.mean_squared_error(new_y, y_prd))
# ==> 0.014788166

# 그래프 시각화
# 폰트 설정
plt.rcParams['font.family'] = 'Malgun Gothic'

# 그래프 그리기
plt.plot(range(50), new_x[0].reshape(-1), 'c.-')
plt.plot(range(50,60), new_y.reshape(-1), 'mo-')
plt.plot(range(50,60), y_prd.reshape(-1), 'bx-')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.legend(['학습','실제', '예측'])
plt.grid(True)
plt.show()

 

validation data 예측 (RNN 모델)

# 데이터 새로 다시 만들기
n_steps = 50 
series = generate_time_series(10000, n_steps+10) 

# train 데이터는 7000개로 구성하고 (7000, 50, 1) shape로 구성합니다. 
train_x, train_y = series[:7000, :n_steps], series[:7000, -10:] 
val_x, val_y = series[7000:9000, :n_steps], series[7000:9000, -10:] 
test_x, test_y = series[9000: , :n_steps], series[9000: , -10:]

# 위 방식을 valistion data에 적용해보기
X = val_x
for step_ahead in range(10):
    # 처음부터 하나씩 증가하면서 다음 타임 스텝을 예측 후 결과 값을 기존 값(3차원)에 추가할 수 있도록 shape 변환
    recent_y_pred = model.predict(X[:, step_ahead:])[:, np.newaxis, :]
    X = np.concatenate([X, recent_y_pred], axis=1)

Y_prd = X[:, n_steps:]

np.mean(tf.keras.losses.mean_squared_error(val_y, Y_prd))
# ==> 0.029840497

 

검증 데이터인 validation data에 적용할 경우 0.029 라는 error 값이 나옵니다. 기본 선형 모델로 적용해보면 어떻게 나올까요?

 

validation data 예측 (기본 선형 모델)

X = val_x
for step_ahead in range(10):
	# 선형 모델인 linear_model 사용
    recent_y_pred = linear_model.predict(X[:, step_ahead:])[:, np.newaxis, :]
    X = np.concatenate([X, recent_y_pred], axis=1)

Y_prd = X[:, n_steps:]

np.mean(tf.keras.losses.mean_squared_error(val_y, Y_prd))
# ==> 0.06303842

 

0.06으로 error 값이 더 높게 나왔네요. RNN 모델이든 기본 선형 모델이든 이 방식으로는 높은 성능을 기대하기 어려울 것 같습니다. 그럼 어떻게 해결할 수 있을까요? 두 번째 방법은 다음 포스팅에서 다뤄보겠습니다.

 

 

 

NLP 자연어 처리를 위한 RNN 기초 (2)

NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1) RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하기 위해 만들어진 딥러닝 모델입니

richdad-project.tistory.com

 

 

 

출처 : https://subscription.packtpub.com/book/data/9781789136364/4/ch04lvl1sec60/simple-rnn

 

 

 

NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1)

RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하기 위해 만들어진 딥러닝 모델입니다. 여기서 시퀀스 타입의 데이터란 시간에

richdad-project.tistory.com

 

RNN 훈련

RNN은 BPTT 전략으로 훈련하는데, BPTT란 Backpropagation through time 즉, 타임 스텝으로 네트워크를 펼치고 기본 역전파를 사용하는 방법입니다. 입력 데이터가 정방향으로 진행되면서 가중치를 계산하고 비용함수를 통해 출력 결과를 생성합니다. 그럼 여기서 사용된 그레디언트가 다시 역방향으로 돌아와 가중치를 업데이트 하면서 모델을 최적화 시킵니다.

지금까지 이론 내용에 대해서만 소개했는데 실제 시계열 데이터를 간단하게 샘플용으로 만들고 이 데이터를 가벼운 RNN 모델에 넣어서 어떻게 학습되는지 코드로 살펴보겠습니다.

import numpy as np

# batch_size 만큼 n_steps 길이의 여러 시계열 데이터 생성
# 시계열 데이터(텍스트 문장과 같은 다른 시퀀스 데이터들도)는 입력 특성으로 3D 배열 [배치 크기, 타임 스텝 수, 차원 수]로 나타낸다

def generate_time_series(batch_size, n_steps):
	
    # random하게 0 ~ 1 사이의 실수를 3차원으로 만들어보겠습니다.
    freq1, freq2, offsets1, offsets2 = np.random.rand(4, batch_size, 1)

    # 시간축 데이터 생성
    time = np.linspace(0,1,n_steps)
    # 사인 곡선 1
    series = 0.5 * np.sin((time - offsets1) * (freq1 * 10 + 10))
    # 사인 곡선 2
    series += 0.2 * np.sin((time - offsets2) * (freq2 * 20 + 20))
    # noise data
    series += 0.1 * (np.random.rand(batch_size, n_steps) - 0.5)

    return series[...,np.newaxis].astype(np.float32)

 

위 함수를 활용해서 훈련 데이터 세트, 검증 데이터 세트, 평가 데이터 세트를 만들어 보겠습니다

n_steps = 50

# 총 10000개의 시계열 데이터를 생성하고, 타입 스탭은 50 + 1 로 생성합니다.
series = generate_time_series(10000, n_steps+1)

# train 데이터는 7000개로 구성하고 (7000, 50, 1) shape로 구성합니다.
train_x, train_y = series[:7000, :n_steps], series[:7000, -1]
val_x, val_y = series[7000:9000, :n_steps], series[7000:9000, -1]
test_x, test_y = series[9000: , :n_steps], series[9000: , -1]

 

우리 모델의 성능을 비교할 기준 성능을 세팅합니다. 단순하게 각 시계열의 마지막 값을 그대로 예측해보는 성능을 기준으로 세워보겠습니다

import tensorflow as tf

pred_y = val_x[:, -1]
# 평균 제곱 오차 (MSE) 확인
print(np.mean(tf.keras.losses.mean_squared_error(val_y, pred_y)
# ==> 0.021878457

 

또 다른 간단한 방법으로 Fully Connected Network 를 사용해서 MSE를 구할 수 있습니다. 특별하게 이 완전연결층 네트워크는 입력마다 1차원 특성 배열을 요구하기 때문에 Flatten 층을 추가해야 합니다. 시계열 데이터를 선형 조합으로 예측할 필요가 있어, 간단한 선형 회귀 모델을 구현해보겠습니다.

# 모델 생성
model = tf.keras.models.Sequential([
	tf.keras.layers.Flatten(input_shape=[50,1]),
    tf.keras.layers.Dense(1)
    ])

# 모델 컴파일
model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = tf.keras.losses.MeanSquaredError(),
    metrics = [tf.keras.metrics.MeanSquaredError()]
)

# 학습
history = model.fit(
    train_x,
    train_y,
    epochs= 20,
    validation_data = (val_x, val_y)
)

#결론 (제일 마지막에 나온 MSE)
history.history['val_mean_squared_error'][-1]
# ==> 0.004973348695784807

 

결론을 확인해봤을 때, 위에서 세웠던 기준 성능보다 더 높은 성능을 보여줍니다(MSE가 낮을수록 높은 성능) 그럼, 기준 성능도 세웠으니깐 Simple RNN을 간단하게 구현해보겠습니다.

# Simple RNN Model
rnn_model = tf.keras.models.Sequential([
    tf.keras.layers.SimpleRNN(1, input_shape=[None, 1])])

# compile
rnn_model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = tf.keras.losses.MeanSquaredError(),
    metrics = [tf.keras.metrics.MeanSquaredError()]
)

# 학습
history = rnn_model.fit(
    train_x,
    train_y,
    epochs= 20,
    validation_data = (val_x, val_y)
)

#결과
history.history['val_mean_squared_error'][-1]
# ==> 0.011953050270676613

 

Simple RNN의 특징

- 하나의 뉴런, 하나의 층으로 구성

- RNN은 어떤 길이의 타임스텝도 처리가 가능해서 입력 시퀀스 길이 지정할 필요 없다 (input_shape =[None, 1])

- 기본적으로 하이퍼볼릭 탄젠트 활성화 함수 사용 

- 초기 상태 \( h_{(hint)} \)를 0으로 설정하고 첫 번째 타임 스텝 \( x_{(0)} \)와 함께 하나의 순환 뉴런으로 전달

- 뉴런에서 이 값의 가중치 합을 계산하고 하이퍼볼릭 탄젠트 활성화 함수를 적용해서 첫번째 결과 값 \( y_{(0)} \)을 출력한다.

- 이 출력이 새로운 \( h_{0} \) 상태가 되고 새로운 상태는 다음 입력값인 \( x_{(1)} \)과 함께 순환 뉴런으로 전달

- 이 과정을 마지막 타임 스텝까지 반복

- 이 RNN은 마지막 값 \( y_{49} \)를 출력

- 모든 시계열에 대해 이 과정 동시에 수행

- keras의 RNN layer는 최종 출력만 반환. 타임 스텝마다 출력이 필요할 경우 return_sequence=True 지정

 

위에서 만든 모델은 앞서 만들었던 기본 선형 모델보다는 못한 성능을 보여줍니다.  아무래도 층이 1개이고, RNN의 특성상 은닉층에서 갱신되는 파라미터 수의 제약이 어느 정도 영향을 미친 것으로 예상합니다. 다음 포스팅에서 심층 RNN으로 이어서 성능을 개선시켜 보겠습니다.

 

 

 

NLP 자연어 처리를 위한 딥러닝 RNN 기초 (1)

RNN RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하기 위해 만들어진 딥러닝 모델입니다. 여기서 시퀀스 타입의 데이터란 시간에

richdad-project.tistory.com

 

출처 : https://pngset.com/download-free-png-fcvmu

RNN


RNN은 Recurrent Neural Network 의 줄임말로 순환 신경망이라 불립니다. RNN 모델은 시퀀스 데이터를 효율적으로 처리하기 위해 만들어진 딥러닝 모델입니다. 여기서 시퀀스 타입의 데이터란 시간에 따른 길이를 가진 데이터라고 볼 수 있습니다. 제가 지금 쓰고 있는 텍스트 데이터도, 첫글자부터 마지막 글자까지 길이와 순서가 있습니다. 주식 가격 데이터도 시계열로 나열했을 때, 시간에 따라 가격이 변동하는 데이터를 확인할 수 있습니다. 
이와 같이 시계열 개념이 들어가 있는 데이터를 시퀀스 데이터라 하고, 이런 시퀀스 타입의 데이터를 효율적으로 다룰 수 있는 신경망 모델이 RNN인 것입니다.

RNN은 DNN과 CNN에서 봤던 Feed Forward 방식과 매우 유사하지만 뒤쪽(역방향)으로 순환하는 연결이 있다는 점이 차이가 있습니다. 즉, 입력(input)을 받아 출력(output)을 만들어 내고, 출력값을 자기 자신에게 다시 보내 하나의 뉴런을 구성합니다.


타입 스텝

RNN은 학습 과정에서 타임 스텝이 존재 하며 각 타임 스텝을 밟을 때마다 해당 타임 스템의 입력값 X(t) 과 이전 타임 스텝의 출력값 Y(t-1)을 입력으로 함께 받습니다. 일반적으로 첫 번째 타입 스텝에서는 이전 출력이 없기 때문에 이전 출력값을 0으로 세팅합니다. 즉, 이런 순환 신경망 구조는 각 타임 스텝마다 벡터 형식의 입력값과 출력값이 함께 입력으로 들어오는 구조를 가지고 있습니다.


공식

순환 신경망의 layer(순환층)에는 다양한 순환 뉴런이 존재 하며, 이 순환 뉴런은 2개의 가중치를 가지고 있습니다. 하나는 입력값 X(t)을 위한 가중치 벡터 w(x)이고, 다른 하나는 이전 출력값 Y(t-1)을 위한 가중치 벡터 w(y)입니다. 이를 순환층 전체로 확대하면 가중치 행렬인 W(x), W(y) 로 표현할 수 있습니다. 그렇다면 RNN의 layer(순환층)의 출력 벡터는 아래 식과 같이 표현할 수 있습니다.

$$ y_{(t)} = ∮({W_x}^T x_{(t)} + {W_y}^T y_{(t-1)} + b) $$

※ 여기서 \(b\) 는 편향을 의미하고 \(∮()\) 는 ReLU와 같은 활성화 함수를 의미합니다.


메모리 셀

RNN 신경망은 타임스텝이라는 개념을 가지고 있기 때문에 이전 스텝에서 출력한 값을 저장할 공간이 필요합니다. 이 저장 공간을 메모리 셀이라고 표현합니다. 일반적으로 이 메모리 셀의 상태는 \(h_{(t)} = f(h_{(t-1)}, x_{(t)}) \) 함수 형태로 표현할 수 있습니다. 여기서 h는 hideen 즉, 은닉층의 결과를 의미한다고 볼 수 있겠네요. 그리고 이 \( h_{(t)}\) 함수는 출력값 \(y_{(t)}\) 와도 같다고 볼 수 있습니다. 하지만 다를 수 도 있기데 h와 y로 나눠서 표기하게 됩니다.


네트워크 종류

RNN은 시퀀스와 벡터의 관계를 활용해서 다양한 네트워크 종류를 구성할 수 있습니다.

1. 시퀀스 투 시퀀스 네트워크 : 주식 가격 같은 시계열 데이터에 적합합니다.

2. 시퀀스 투 벡터 네트워크 : 영화 리뷰 텍스트에서 연속적인 단어를 시퀀스 형태로 입력하고 감성 점수를 벡터로 출력 받을 수 있습니다.

3. 벡터 투 시퀀스 네트워크 : 반대로 하나의 벡터를 입력하고 이에 따른 시퀀스를 출력할 수 있는데, 예를 들어 이미지(or CNN의 출력)를 하나의 벡터로 입력하고 이에 대한 caption(시퀀스)을 출력할 수 있습니다.

4. 인코더 디코더 : 인코더[시퀀스 투 벡터 네트워크]와 디코더[벡터 투 시퀀스 네트워크]를 연결한 네트워크를 구성할 수 있습니다. 이는 언어를 번역하는 데 사용할 수 있습니다. 한 언어의 문장 텍스트를 입력하여 하나의 벡터로 표현하고 이 벡터를 디코더에 넣어 새로운 언어 문장인 시퀀스로 출력하는 알고리즘에 적용할 수 있습니다.

 

+ Recent posts