--- license: apache-2.0 --- 안녕하세요 Oneclick AI 입니다!! 오늘은, RNN의 한계를 극복한 LSTM(Long Short-Term Memory)과 GRU(Gated Recurrent Unit) 모델에 대해서 알아보는 시간을 가져볼까 합니다. RNN이 순차 데이터를 다루는 데 혁신을 가져왔지만, 긴 시퀀스에서 과거 정보를 제대로 기억하지 못하는 '장기 의존성 문제'로 인해 한계를 드러냈습니다. LSTM과 GRU는 이 문제를 해결하기 위해 고안된 고급 순환 신경망으로, 마치 사람의 장기 기억처럼 중요한 정보를 선택적으로 유지하고 잊어버릴 수 있는 '게이트' 메커니즘을 도입했습니다. 오늘은 이 두 모델이 어떻게 RNN의 약점을 보완하며 작동하는지, 그리고 어떻게 더 복잡한 문장이나 시계열 데이터를 정교하게 처리할 수 있는지 알아봅시다. --- ## 목차 1. LSTM/GRU 핵심 원리 파악하기 - 왜 LSTM/GRU를 사용해야만 할까? - LSTM의 심장 : 셀 상태와 3개의 게이트 메커니즘 - GRU : LSTM의 간소화된 버전과 2개의 게이트 - LSTM과 GRU를 시간에 따라 펼쳐보기 - LSTM/GRU의 주요 구성 요소 상세 분석 2. 아키텍처를 통한 내부 코드 들여다 보기 - Keras로 구현한 LSTM/GRU 모델 아키텍처 - model.summary()로 구조 확인하기 3. 직접 LSTM/GRU 구현해 보기 - 1단계 : 데이터 로드 및 전처리 - 2단계 : 모델 컴파일 - 3단계 : 모델 학습 및 평가 - 4단계 : 학습된 모델 저장 및 재사용 - 5단계 : 나만의 문장으로 모델 테스트하기 4. 나만의 LSTM/GRU 모델 업그레이드하기 - 기초 체력 훈련 : 하이퍼파라미터 튜닝 - 층 쌓기 : 다중 LSTM/GRU 레이어 - 과거와 미래를 동시에 : 양방향 LSTM/GRU - 전이학습으로 성능 극대화 하기 5. 결론 --- ## 1. LSTM/GRU 핵심원리 파악하기 가장 먼저, LSTM과 GRU가 왜 RNN의 대안으로 등장했는지 그 근본적인 이유부터 살펴보겠습니다. **왜 LSTM/GRU를 사용할까?? with RNN의 한계** 기본 RNN은 은닉 상태를 통해 과거 정보를 전달하지만, 시퀀스가 길어지면 그래디언트 소실(Vanishing Gradient)이나 폭발(Exploding Gradient) 문제가 발생합니다. 이는 학습 과정에서 기울기가 0에 가까워지거나 무한대가 되어, 문장 앞부분의 중요한 정보를 잊어버리는 '장기 의존성 문제(Long-Term Dependency)'를 초래합니다. 예를 들어, "어린 시절 프랑스에서 자랐기 때문에... (긴 내용)... 그래서 나는 프랑스어를 유창하게 구사한다."라는 문장에서 RNN은 '프랑스'라는 초기 정보를 잊기 쉽습니다. LSTM과 GRU는 이 문제를 해결하기 위해 '게이트'라는 구조를 도입하여, 정보의 흐름을 제어합니다. 이들은 RNN의 기본 구조를 유지하면서도 중요한 정보를 선택적으로 기억하고 불필요한 것은 잊어버릴 수 있도록 설계되었습니다. **LSTM의 심장 : 셀 상태와 3개의 게이트 메커니즘** LSTM의 핵심은 '셀 상태(Cell State, $C_t$)'와 이를 제어하는 3개의 게이트입니다. - 셀 상태(Cell State, $C_t$): 장기 기억을 위한 '컨베이어 벨트'로, 정보가 거의 변형 없이 전달됩니다. - 게이트(Gates): 시그모이드(Sigmoid) 함수를 사용해 0~1 사이의 값을 출력하며, 정보의 통과 여부를 결정합니다. 1. 망각 게이트(Forget Gate, $f_t$): 이전 셀 상태 $C_{t-1}$에서 어떤 정보를 잊을지 결정합니다. $f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f)$ (여기서 $\sigma$는 시그모이드 함수, $h_{t-1}$은 이전 은닉 상태, $x_t$는 현재 입력) 2. 입력 게이트(Input Gate, $i_t$)와 후보 셀 상태($\tilde{C_t}$): 새로운 정보를 얼마나 추가할지 결정합니다. $i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + b_i)$ $\tilde{C_t} = \tanh(W_C \cdot [h_{t-1}, x_t] + b_C)$ 3. 출력 게이트(Output Gate, $o_t$): 셀 상태에서 어떤 정보를 출력할지 결정합니다. $o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o)$ 최종 셀 상태 $C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C_t}$ ( $\odot$은 요소별 곱) 은닉 상태 $h_t = o_t \odot \tanh(C_t)$ 이 구조 덕분에 LSTM은 장기적인 의존성을 효과적으로 학습합니다. **GRU : LSTM의 간소화된 버전과 2개의 게이트** GRU는 LSTM의 변형으로, 파라미터를 줄여 계산 효율성을 높였습니다. 은닉 상태 $h_t$가 셀 상태 역할을 겸하며, 2개의 게이트만 사용합니다. - 리셋 게이트(Reset Gate, $r_t$): 이전 은닉 상태를 얼마나 무시할지 결정합니다. $r_t = \sigma(W_r \cdot [h_{t-1}, x_t] + b_r)$ - 업데이트 게이트(Update Gate, $z_t$): 이전 상태와 새 후보 상태를 얼마나 섞을지 결정합니다. (LSTM의 망각+입력 게이트 역할) $z_t = \sigma(W_z \cdot [h_{t-1}, x_t] + b_z)$ 후보 은닉 상태 $\tilde{h_t} = \tanh(W_h \cdot [r_t \odot h_{t-1}, x_t] + b_h)$ 최종 $h_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h_t}$ GRU는 LSTM만큼 강력하면서도 학습이 더 빠릅니다. **LSTM/GRU를 시간에 따라 펼쳐보기** 아래 그림처럼 시간에 따라 네트워크를 길게 펼쳐서 표현하면, 쉽게 이해할 수 있습니다. ```markdown 시간 흐름 ───▶ 입력 시퀀스: x₁ x₂ x₃ ... xₜ ↓ ↓ ↓ ↓ ┌────┐ ┌────┐ ┌────┐ ... ┌────┐ h₀, C₀ ──▶│LSTM│▶│LSTM│▶│LSTM│ ▶ ... ▶│LSTM│ (또는 GRU) └────┘ └────┘ └────┘ └────┘ │ │ │ │ ▼ ▼ ▼ ▼ h₁ h₂ h₃ hₜ ``` 각 타임스텝에서 게이트가 정보를 제어하며, 셀 상태(또는 은닉 상태)가 장기적으로 전달됩니다. **LSTM/GRU의 주요 구성 요소** - 게이트 메커니즘: 정보 선택과 삭제. - 은닉/셀 상태: 메모리 역할. - 파라미터 공유: 모든 타임스텝에서 동일한 가중치 사용. --- ## 2. 아키텍처를 통한 내부 코드 들여다 보기 이제 이론을 바탕으로, TensorFlow Keras 를 통해 직접 LSTM과 GRU를 구현해 봅시다. Keras로 구현한 LSTM/GRU 모델 아키텍처 심층 분석다음은 IMDB 영화 리뷰 감성 분석을 위한 간단한 LSTM 모델입니다. (GRU도 유사) ```python import tensorflow as tf from tensorflow import keras # 모델 아키텍처 정의 model = keras.Sequential([ # 1. 단어 임베딩 층 keras.layers.Embedding(input_dim=10000, output_dim=32), # 2. LSTM 층 (GRU로 바꾸려면 SimpleRNN 대신 LSTM 또는 GRU 사용) keras.layers.LSTM(32), # 3. 최종 분류기 keras.layers.Dense(1, activation="sigmoid"), ]) # 모델 구조 요약 출력 model.summary() ``` 레이어를 자세히 들어다 봅시다. - **임베딩 층(Embedding)** ```python keras.layers.Embedding(input_dim=10000, output_dim=32) ``` 단어를 벡터로 변환, RNN 문서와 동일. - **순환 계층(LSTM 또는 GRU)** ```python keras.layers.LSTM(32), ``` 또는 ```python keras.layers.GRU(32), ``` 내부적으로 게이트를 처리하며, 장기 의존성을 학습. 기본적으로 최종 은닉 상태만 출력. - **완전 연결 계층(Dense)** ```python keras.layers.Dense(1, activation="sigmoid") ``` 최종 판단. model.summary()로 파라미터 수 계산 원리 이해하기위 코드에서 model.summary()를 실행하면 다음과 같은 결과가 나옵니다. ```bash Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding (Embedding) (None, None, 32) 320000 lstm (LSTM) (None, 32) 8320 dense (Dense) (None, 1) 33 ================================================================= Total params: 328,353 Trainable params: 328,353 Non-trainable params: 0 _________________________________________________________________ ``` 각 층의 파라미터 수는 어떻게 계산되는지 알아보자면, 1. Embedding: 10,000 * 32 = 320,000 개. 2. LSTM: 입력(32)과 은닉(32)을 고려한 4개의 게이트(입력, 망각, 출력, 후보)로, (32+32+1)*32*4 = 8,320 개. (GRU는 3배: 약 6,240) 3. Dense: 32 * 1 + 1 = 33 개. --- ## 3. 직접 LSTM/GRU 구현해 보기 이제, 전체 코드를 단계별로 실행하며 직접 모델을 학습시켜 보겠습니다. (RNN 문서와 유사, IMDB 데이터 사용) **1단계. 데이터 로드 및 전처리** ```python import numpy as np import tensorflow as tf from tensorflow import keras from keras import layers (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=10000) x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=256) x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=256) ``` **2단계. 모델 컴파일** ```python model = keras.Sequential([ layers.Embedding(input_dim=10000, output_dim=32), layers.LSTM(32), # 또는 layers.GRU(32) layers.Dense(1, activation="sigmoid") ]) model.compile( loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"] ) ``` **3단계. 모델 학습 및 평가** ```python batch_size = 128 epochs = 10 history = model.fit( x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test) ) score = model.evaluate(x_test, y_test, verbose=0) print(f"\nTest loss: {score[0]:.4f}") print(f"Test accuracy: {score[1]:.4f}") ``` **4단계. 학습된 모델 저장 및 재사용** ```python model.save("my_lstm_model_imdb.keras") loaded_model = keras.models.load_model("my_lstm_model_imdb.keras") ``` **5단계. 나만의 문장으로 모델 테스트하기** ```python word_index = keras.datasets.imdb.get_word_index() review = "This movie was fantastic and wonderful" tokens = [word_index.get(word, 2) for word in review.lower().split()] padded_tokens = keras.preprocessing.sequence.pad_sequences([tokens], maxlen=256) prediction = loaded_model.predict(padded_tokens) print(f"리뷰: '{review}'") print(f"긍정 확률: {prediction[0][0] * 100:.2f}%") ``` ## 4. 나만의 LSTM/GRU 모델 업그레이드하기 기본 모델을 더 강력하게 만들기 위해 다양한 기법을 적용해 보겠습니다. - **기초 체력 훈련 : 하이퍼파라미터 튜닝** 학습률, 배치 크기, 유닛 수 등을 조정. ```python optimizer = keras.optimizers.Adam(learning_rate=0.001) model.compile(loss="binary_crossentropy", optimizer=optimizer, metrics=["accuracy"]) ``` - **층 쌓기 : 다중 LSTM/GRU 레이어** ```python model = keras.Sequential([ layers.Embedding(input_dim=10000, output_dim=64), layers.LSTM(64, return_sequences=True), layers.LSTM(32), layers.Dense(1, activation='sigmoid') ]) ``` - **과거와 미래를 동시에 : 양방향 LSTM/GRU** ```python model = keras.Sequential([ layers.Embedding(input_dim=10000, output_dim=64), layers.Bidirectional(layers.LSTM(64)), layers.Dropout(0.5), layers.Dense(1, activation='sigmoid') ]) ``` - **전이학습으로 성능 극대화 하기** 사전 학습된 모델(예: GloVe 임베딩) 사용하거나, 대형 모델의 LSTM 레이어 freeze. ```python # 예: 사전 학습된 임베딩 로드 (별도 파일 필요) embedding_layer = layers.Embedding(input_dim=10000, output_dim=100, trainable=False) # GloVe 등으로 초기화 ``` ## 5. 결론 오늘은, RNN의 한계를 넘어선 LSTM과 GRU의 핵심 원리부터 실제 구현, 업그레이드 방법까지 알아보았습니다. 이 두 모델은 자연어 처리뿐만 아니라 시계열 예측, 음성 인식 등에서 여전히 핵심적인 역할을 합니다. 특히, LSTM/GRU의 게이트 아이디어는 이후 어텐션 메커니즘과 트랜스포머 모델의 기반이 되었습니다. 다음에는 트랜스포머 모델로 돌아오겠습니다!! 오늘도 좋은하루 보내세요!!