top of page

RNN 이란? (feat. OCR)



1. 순환신경망(RNN)의 이해


[1] 기억시스템 (Memory System)

순차적인 데이터를 입력을 받아서, 우리가 원하는 출력을 받기 위해서는 기억 시스템 전제가 필요 합니다.

입력을 받을 때마다 그 내용을 기억할 수 있어야 합니다.


일상 대화에서 생각해보면,

질문이 무엇인지? (what)

어떤 것을 알고 싶은지? (time)

시점이 언제인지 (is)

이러한 질문의 하나하나 문장을 알아야지 상대방에게 올바른 대답을 얻을 수 있습니다.

이처럼 인공지능도 올바른 입력값을 넣어야, 올바른 출력 값을 얻을 수 있습니다.

그것이 기억 시스템 (Memory System) 입니다.

참고로 흔히 알고 있는 CNN, Shallow Neural Network 의 경우 Memoryless System(무기억 시스템) 이라고 합니다. 실제 사용할 때 입력이 들어 올 때마다 동작이 바뀌지 않으니 Memoryless System 이라고 합니다.




[2] 기본적인 순환 신경망


수학적으로 먼저 살펴보면,

일반적인 신경망에서 위의 사진 빨간 글씨가 추가 된 것이 순한 신경망(RNN)의 기본적인 구조 입니다.

입력을 그냥 다음으로 넘기는 것이 아니라, n-1 번째 Hidden-Layer를 그대로 가져와서 합쳐집니다.

출력은 마지막 Hidden-Layer 에서 Output layer 로 넘어가는 것은 똑같습니다.

n 번째 출력이

n-1 입력에 대하여 영향을 받고 있다.

n-1 번째 출력은

n-2 입력에 대하여 영향을 받고 있다.

n-2 번째 출력은

n-3 입력에 대하여 영향을 받고 있다.

하나하나 앞으로 나아가다 보면,

n번째 출력은 입력 처음부터 끝까지 영향을 받을 수 있다는 결론을 얻을 수 있습니다.

RNN의 출력은 이전의 모든 입력에 대하여 영향을 받는다고 할 수 있습니다.




[3] 다중 계층 순환 신경망 (Multi-Layer RNN)


순환 신경망도 심층 신경망처럼 쌓아 올릴 수 있습니다.

위와 똑같이 Hidden-Layer 에 이전 입력값을 넣어주면 됩니다.

하지만 신경망의 구조가 매우 복잡해지고 학습이 잘 되지 않아서, 현재는 많이 사용되고 있지 않다고 합니다. 어떠한 이유 때문에 사용되고 있지 않는지, 그리고 더 발전된 RNN에 대해서 설명하도록 하겠습니다.



 

2. LSTM (Long Short-Term Memory)


[1] Vanilla RNN 문제점



잘 쓰이지 않는 가장 큰 이유는 기울기 손실 문제가 있습니다.

위의 그림에서 x0 가 입력이고 출력이 ht+1 이라고 할때,

ht+1 에서 출발해서 쭉 x0 에 있는 정보를 가지고 와야 합니다.

그렇게 하기 위해서 사이사이의 Gradient(기울기)가 다 만들어져야 하며,

역전파가 이루어 질 때 점점 기울기가 손실이 되는 문제가 있습니다.

따라서, 시점 차이가 많이 날 경우 학습 능력이 굉장히 저하가 됩니다.

그래서 현재는 RNN 기반의 변형 알고리즘 LSTM 을 사용 합니다.

우선 위의 그림을 간단하게 다시 그림으로 표현하면 아래와 같습니다.


노랑색의 tanh 부분은 Activation Funtion 이며 FCL (Fully Connet Layer) 입니다.

"입력 x layer -> Activation Funtion -> 출력 h layer" 연결이 되는 구조 입니다.

위의 그림 기준으로 변형 알고리즘 LSTM에 대해 아래에 설명하겠습니다.



[2] LSTM (Long Short-Term Memory)


Vanilla RNN 에서 가지고 있는 문제점을 개선한 알고리즘 입니다.

간단한 개념만 설명하면, 기억할 것은 오래 기억하고, 잊을 것은 빨리 잊어버리는 알고리즘 이라고 생각하면 됩니다.

LSTM 을 이해하기 위해서 State, Gate 개념을 이해 할 필요가 있습니다.

Cell State, Hidden State 2가지의 State (경로)

Forget Gate, Input Gate, Output Gate 3가지의 Gate (관문)Cell State, Hidden State



Cell State, Hidden State


기존 RNN 에서 크게 다른 점은 State (경로)가 2개가 있다는 점 입니다.

State이 2개가 있는 이유는 기존의 Hidden State에서 Cell State가 추가되서 그렇습니다.

일반적인 RNN에서 Hidden State는 출력만 가지고 있었는데, (위의 Vanilla RNN 참고)

LSTM 에서 기억을 더 오래 유지하기 위해 Cell state가 추가 했습니다.

Cell State 역할

기억을 오랫동안 유지할 수 있는 구조

새로운 특징을 덧셈으로 받아서 보내는 구조 입니다.

Hidden State 역할

계층의 출력/다음 타임 Step 으로 넘기는 정보

RNN과 달리, Cell State가 있어서 '기억'에 관한 부분을 전담 합니다.

여기서는 간단하게 무슨 역할을 한다는 것만 챙기고,

아래에 Gate 대한 설명 후에 정리 하겠습니다.



Forget Gate

첫 번째 Forget gate 부분은 위의 검은색 경로를 뜻 합니다.

입력과 이전의 Hidden state 를 같이 가지고, FCL을 가진 시그모이드 함수로 0 ~ 1 출력 값을 보냅니다.

이를 Cell state에 곱해 주면, 0 ~ 1 사이의 값이 곱하기 때문에 값이 작아지며 필요한 출력 값만 얻을 수 있는 Gate 입니다.

예를 들어서

'hello' 단어를 가지고 왔는데 Forget Gate 를 통해서 0 ~ 1 출력 값 중에 index 0번, 4번이 0이 나왔다면,

'ell' 만 기억하고 넘기는 것을 말합니다.

즉, 얼만큼 잊는지 결정하는 것이 Forget Gate의 역할 입니다.




Input gate

검은색 부분이 Input Gate 이고,

빨간색 부분이 우리가 알고 있는 일반적인 RNN 구조 입니다.

어떤 feature 를 뽑았을 때,

feature 중에 0 ~ 1 출력 값과 일반적인 RNN 에서 나온 값을 곱해서

그 값을 Cell state에 더해주는 역할을 합니다.

즉, 새로운 것을 머리 속에 넣어주는 역할이라고 생각하면 됩니다.

Forget Gate 는 머릿속에서 일부를 잊어주는 역할

Input Gate 는 새로운 것을 머릿속에 넣어주는 역할

Cell state 을 다시 정의하면,

필요 없는 것은 잊어주고 (Forget Gate)

필요한 것은 다시 넣어주는 역할 (Input Gate)




Onput gate

바로 위에서 정의한, 필요 없는 것은 잊어주고, 필요한 것은 다시 넣어주는 역할까지만 했습니다.

마지막 Output Gate 에서는 얼마나 출력으로 내보낼지 결정하는 역할 입니다.

어떠한 것들은 0으로 출력 해주지 않고,

어떠한 것들은 1으로 출력 해주고

즉,

현재 Layer 에서 필요한 정보만 내주고,

필요하지 않은 정보는 기억만 하고 내보내주지 않는 것 입니다.

그렇게 해서 필요 없는 것을 빼고 출력을 다음 Layer 에 넘기는데,

그것도 아래 경로를 통해 다음 Step 으로 넘겨 줍니다. (Cell State 와 별개로 넘겨준다.)

마지막에 tanh 함수를 사용하는 것은

앞단의 Cell State 에서 곱해주고 더해주면서 값이 커지는 것을 방지하기 위해서,

-1 ~ 1 값으로 다시 조정해주는 역할을 합니다.

즉, 정보 중에 얼마나 출력을 내보낼지 결정하는 역할 (Output Gate)


 

3. Simple RNN 코드


import tensorflow as tf EPOCHS = 10 NUM_WORDS = 10000 # 1. 모델 정의 class MyModel(tf.keras.Model): def __init__(self): super(MyModel, self).__init__() self.emb = tf.keras.layers.Embedding(NUM_WORDS, 16) self.rnn = tf.keras.layers.LSTM(32) self.dense = tf.keras.layers.Dense(2, activation='softmax') def call(self, x, training=None, mask=None): x = self.emb(x) x = self.rnn(x) return self.dense(x) # 2. 학습, 테스트 루프 정의 # Implement training loop @tf.function def train_step(model, inputs, labels, loss_object, optimizer, train_loss, train_accuracy): with tf.GradientTape() as tape: predictions = model(inputs, training=True) loss = loss_object(labels, predictions) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) train_loss(loss) train_accuracy(labels, predictions) # Implement algorithm test @tf.function def test_step(model, images, labels, loss_object, test_loss, test_accuracy): predictions = model(images, training=False) t_loss = loss_object(labels, predictions) test_loss(t_loss) test_accuracy(labels, predictions) # 3. 데이터셋 준비 imdb = tf.keras.datasets.imdb (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=NUM_WORDS) x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, value=0, padding='pre', maxlen=32) x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, value=0, padding='pre', maxlen=32) train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32) test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32) # 4. 학습 환경 정의 # Create model model = MyModel() # Define loss and optimizer loss_object = tf.keras.losses.SparseCategoricalCrossentropy() optimizer = tf.keras.optimizers.Adam() # Define performance metrics train_loss = tf.keras.metrics.Mean(name='train_loss') train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy') test_loss = tf.keras.metrics.Mean(name='test_loss') test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy') # 5. 학습 for epoch in range(EPOCHS): for seqs, labels in train_ds: train_step(model, seqs, labels, loss_object, optimizer, train_loss, train_accuracy) for test_seqs, test_labels in test_ds: test_step(model, test_seqs, test_labels, loss_object, test_loss, test_accuracy) template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}' print(template.format(epoch + 1, train_loss.result(), train_accuracy.result() * 100, test_loss.result(), test_accuracy.result() * 100)) train_loss.reset_states() train_accuracy.reset_states() test_loss.reset_states() test_accuracy.reset_states()



결과


간단하게 RNN 모델을 만들어서 학습한 결과 입니다.



 

4. 트럭 번호판 OCR


위에서 발전된 RNN의 LSTM 의 개념으로 트럭 번호한 OCR 모델을 만들 수 있습니다.

단일 입력 (이미지)를 받아서 다중 출력(번호판)하는 모델 입니다.


간단하게 과정만 설명 드리면,

1. 이미지 1장에서 번호판 출력

2. 번호판 이미지 전처리

3. 필요한 feature 로 모델 학습 진행

4. OCR 판독 결과



학습한 모델 바탕으로 아래의 이미지를 판독 할 수 있습니다.


결과



위의 이미지는 모델이 출력한 결과 입니다.

자세한 OCR 학습 과정은 Truck 번호판 OCR에 설명 되어있습니다.


이상으로 RNN 개념 설명을 마치겠습니다.




bottom of page