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

 

출처 : https://www.freepik.com/free-photo/ai-nuclear-energy-background-future-innovation-disruptive-technology_17850501.htm#page=1&query=deep%20learning&position=3&from_view=search

6개월정도 부트캠프 형식으로 AI 과정을 수료했지만 사실, 5분의 1은 웹 관련 내용, 5분의 1은 파이썬, 5분의 1은 머신러닝/딥러닝 5분의 2 정도 프로젝트로 진행되다 보니 딥러닝에 대한 깊이는 한없이 부족했습니다.

이 갈증을 해소하기 위해 네이버 커넥츠 재단의 부스트캠프 과정을 알게됐고, Pre Course 과정도 나름 알차게 돼 있어서 수강하고 있습니다. 공부 내용을 정리해 놓으면 좋을 것 같아 몇자 끄적여봅니다.

 

딥러닝이란?


딥러닝 관련 직군이 매우 다양하고 넓다고 생각합니다. 데이터 사이언티스트, 데이터 분석가, 머신러닝 엔지니어, 모델러 등 다양하게 구성돼 있습니다. 개인적으로 우수한 머신러닝/딥러닝 관련 전문가의 특징을 꼽자면

1. 우수한 프로그래밍 구현 실력
2. 딥러닝을 위한 기본이 되는 수학적 지식(ex. 선형대수, 확률)
3. 논문 리딩을 통한 학습 가능한 실력

이렇게 3가지 정도 특징을 언급할 수 있을 것 같습니다. 이 3가지를 다 잘 할 수 있다면, 저도 억대 연봉을 받을 수 있겠죠? ^^

정의, 개념에 대해 간단히 언급하고 넘어가겠습니다.
인공지능이란 사람의 지능을 모방하는 것이라고 설명할 수 있습니다. 그렇다면 머신러닝이란 기계로 사람의 지능을 모방하는 개념입니다. 여기서 기계는 컴퓨터를 의미하겠죠. 딥러닝이란 이 머신러닝의 여러 종류 중 하나로서 사람의 뇌 신경망을 모방해서 구현한 개념입니다.


그래서 사실 딥러닝을 학습하고, 딥러닝 전문가가 된다는 것은 머신러닝의 일부분을 깊게 다룬다는 개념이라고 보시면 됩니다. 딥러닝이 핫한 이유는 지금까지 해결하지 못했던 문제들을 높은 성능으로 해결하는 솔루션을 제공해주고 있기 때문입니다. 할 수 있는 게 너무 많아요

그럼 딥러닝의 핵심 요소를 정말 간략하게 확인해 보면

▶ 데이터
▶ 모델
▶ 손실함수(loss function)
▶ 알고리즘(optimizer)

이렇게 4가지를 확인할 수 있습니다. 딥러닝은 데이터에 아주 민감하고, 사실상 최근 빅데이터라는 방대한 데이터 시대가 도래했기 때문에 딥러닝이란 기술이 빛을 발할 수 있습니다. 모델은 데이터를 수학적 지식을 잘 활용해서 우리가 원하는 결과를 만들어주는 중추 역할이라고 볼 수 있습니다. 손실함수는 우리의 모델이 높은 성능을 낼 수 있는 의미 있는 기준, 조건이라고 보시면 됩니다. 알고리즘은 최적화 기법과 연결 돼 있습니다.

 

딥러닝의 목적


1. Loss (우리가 이루고자 하는 근사치)
- 뉴럴네트워크에 들어 있는 layer 안에서 weight와 bias를 어떻게 update할건지 결정하는 기법이 Loss입니다. 
- Loss값이 줄어든다고 해서, 우리가 원하는 결과를 항상 얻을 수는 없습니다.

문제 접근 방식마다 사용하는 loss 기법이 다른데,
Regression(회귀) 문제는 MSE(최소제곱법)
Classification(분류) 문제는 CrossEntrophy
Probabilistic(확률) 문제는 MLE 같은 loss를 활용합니다.

딥러닝에서 Optimizer(최적화)란 신경망 네트워크를 어떻게 줄일지에 대한 기술이라고 보시면 되는데 뉴럴 네트워크의 파라미터를 loss fuction에 대해서 1차 미분한 정보를 활용해서 최적화하는 작업을 의미합니다. 쉽게 말하면, 우리가 만들 모델의 목표 지점에 도달 할 수 있게끔 세팅하는 방식이라고 생각할 수 있습니다.

딥러닝의 목적은 단순히 loss fuction을 줄이는 게 아니라 학습하지 않은 데이터에도 정확하게 작동할 수 있도록 모델을 구현하는 것입니다. loss fuction이 감소하는 게 위와 같은 성능을 보장하기도 하지만 반드시 그런 것은 아니라서 이 목적을 잊지 않는 것도 중요합니다. 우리가 딥러닝이란 기술을 이용해서 뭘 하려고 하는지 끊임없이 되묻는 게 필요합니다.

 

딥러닝 역사적 흐름


1. AlexNet(2012)
- 이미지 분류 경진대회에서 처음으로 딥러닝 기술이 1등한 첫 모델

2. DQN (2013)
- 딥마인드가 블록깨기 게임을 강화학습으로 성공시킨 논문

3. Encoder/ Decoder(2014)
- AI 번역의 트렌드 변화 주도, 시퀀스 데이터(문자)를 encoder를 통해 숫자로 바꾸고 이를 다른 언어와 매칭하여 decoder로 출력해주는 과정

4. Adam Optimizer(2014)
- 다양한 optimaizer가 있는데, 보편적으로 무난하게 높은 성능을 내는 optimizer가 Adam.

5. GAN(Generative Adversarial Network)(2015)
- 이미지, 자연어를 어떻게 생성할 지 관련된 적대적 신경망의 시초

6. ResNet(2015)
- 네트워크를 깊게 쌓을 수 있도록 트렌드를 바꾼 논문

7. Transformaer (Attention is all you need) (2017)
- NLP 딥러닝 모델의 혁신, RNN 계열의 모델에서 가장 선두적인 성능을 보임

8. BERT(2018)
- fine-tuned NLP model / fine tuning의 중요성을 보여주는 모델 / 다양한 말뭉치로 pre train하고 내가 풀고 싶은 소량의 데이터로 fine tuning 기법 극대화

9. BIG Language Models GPT-3 (OpenAI, 2019)
- 1750억개의 파라미터로 이뤄진 모델, 모델이 무겁지만 그만큼 NLP 분야에서 어마무시한 성능을 보여줌.

10. Self Supervised Learning(2020, SimCLR)
- 한정된 학습데이터 외 라벨을 모르는 데이터에 대해 unsupervised learning 방식을 접목하여 성능을 향상

Multiprocessing 

파이썬에서 연산과정 중 자원을 분배해서 계산 속도를 빠르게 올리기 위해 사용하는 기법이다. Thred 즉 CPU의 Core와 연관돼 있는 개념이다. 파이썬은 절차형 언어라서 한 라인 명령어를 수행하고 다음 라인으로 넘어간다. 즉, 앞에 있는 계산이 끝나지 않는다면 다음 명령을 수행할 수 없다.
하지만 쓰레드 작업, 병렬처리 작업을 진행하면, 각각의 작업이 동시에 진행된다. 간단한 코드를 통해 예시를 확인해보자

 

기본 예시


# 작업 함수 1
def func1(num):
	cnt = 0
    while cnt < num :
    	print('작업함수1')
        cnt += 1

# 작업 함수 2
def func2(num):
	cnt = 0
    while cnt < num :
    	print('작업함수2')
        cnt += 1
위와 같은 함수를 구현해보고 함수1과 2를 차례로 실행해보자 총 10만번 작업을 수행할 것이다.
# 시간을 재기 위해서 time 라이브러리 사용
import time

# 시작 시간
start = time.time()

func1(100000)
func2(100000)

# 끝 시간
end = time.time()

# 총 걸린 시간
print('총 작업 시간 : ' + str(round((end-start),2)) + '초')

 

당연히 1번 함수부터 차례로 시작하고 2번 함수가 다음으로 이어지면서 마무리 된다. 총 걸리 시간은 10.82 초다.

 

그렇다면 병렬처리 작업을 하면 어떻게 될까요?

# multiprocessing 과 threading 둘 중에 마음에 들거나 편한걸 사용하면된다.
# 이번 예시에서는 threading 라이브러리를 사용하겠다
from multiprocessing  import Process, Queue
from threading import Thread

num = 100000
thred1 = Thread(target=func1)
thred1.start()

thred2 = Thread(target=func2)
thred2.start()

이런 식으로 섞여서 나옵니다. 즉 첫 번째 함수를 먼저 다 실행하고 두 번째 함수를 실행하는 게 아니라 2가지 함수를 동시에 진행하는 작업입니다.

 

응용


이번에는 제가 텍스트 분석하는 작업에 Multiprocessing 쓰레드 작업을 적용해보겠습니다.
텍스트 분석 부분에서 형태소 분석기(Okt)를 활용해서 품사별로 나누고 필요없는 품사를 제거하는 작업이 생각보다 오래걸립니다. 이 작업에 병렬처리를 적용해보겠습니다.

 

▶ 병렬 처리 전 코드

# 텍스트 전처리 함수
def prepro_text(raw_data):

    # 특수기호, 영어, 숫자 제거
    prepro_texts = re.sub(r'[^가-힣]',' ',str(raw_data))
    # 형태소 분석기 생성
    okt = Okt()
    # 조사와 복수표현 등 필요없는 품사 tag 제거
    prepro_word = []
    for word, tag in okt.pos(prepro_texts):
        if tag not in ['Josa', 'Suffix']:
            prepro_word.append(word)
    # 어간 추출 적용
    # result = ' '.join(okt.morphs(' '.join(prepro_word), stem=True))

    # 어간 추출 X
    result = ' '.join(prepro_word)

    return result

# 각 자소서 질문별 텍스트 전처리
for q in df.columns[2:7] :
    df[q] = df[q].map(prepro_text)
print('전처리 완료')
위 코드를 확인해 보시면, 총 5개 column에 대한 텍스트 데이터를 for문을 활용하여 차례차례 전처리 작업을 진행합니다. 그러다 보니 이 부분에서 시간이 오래 걸리고, 리소스를 많이 사용하게 되는데요. 이 부분을 병렬처리로 작업해보겠습니다.

 

 

▶ 병렬 처리 후 코드

# 텍스트 전처리
def text_preprocess(df, text, queue):
    df[text] = df[text].map(prepro_text)
    queue.put(df[text])
    return df[text]
    
q = Queue()
        
# 텍스트 전처리
# 쓰레드 병렬 처리
for col in df.columns[2:7]:
	thred = Process(target=text_preprocess, args=(df,col,q))    
    thred.start()

# queue 자료구조에 담은 데이터를 사용할 df에 적용
for col in df.columns[2:7]:
	df[col] = pd.Series(q.get())
이렇게 간단하게 병렬 처리 가능합니다. Process() 메써드네 target, args라는 파라미터를 넣어줍니다. target은 우리가 작업할 함수를, args는 그 함수들이 필요로 하는 파라미터 값입니다.

 

소감


사실 아직 정확하게 활용하고 있지 못하다는 느낌이 강합니다. 쓰레드라는 개념, 병렬처리라는 개념은 어느 정도 이해했는데, 이 기법을 내 코드에 잘 적용하고 있는지는 아직 미지수로 보입니다. 좀 더 업그레이드 되면 다시 한 번 공유하겠습니다. 감사합니다. 

+ Recent posts