https://www.youtube.com/watch?v=PyZvbaC5oQY
Part 1
오늘은 sequential data를 다루는데에 많이 사용되는 RNN에 대해 알아보자.
만약 우리가 사용하는 input의 사이즈가 변한다면 어떻게 될까?
예를 들어, 문장과 같은 인풋을 사용한다면 문장에 따라 길이가 계속해서 달라지게 된다. 아니면 비디오나 사운드와 같은 데이터인 경우에도 데이터에 따라 길이가 계속해서 다르다.
간단하게 생각해보면 max 길이가 5개라고 하면 5개미만의 데이터는 0으로 채우거나 이런 형태로 디자인을 할 수도 있다. 하지만 당연히 0으로 그냥 채워버리는 것이 최선의 아이디어는 아니다.
그럼 한 레이어에 한 개의 인풋이 들어간다고 하면 어떨까? 하나씩 레이어에 넣어주는 형태말이다.
각 레이어에 대해 인풋이 이전 단계의 아웃풋과 해당 레이어의 데이터가 들어가는 형태이다.
사실 이 방법은 실제로 잘 작동하지 않는다고 한다. part 2에서 좀 더 자세하게 다룬다.
그럼 layer의 수를 sequence의 길이에 따라 변화시키는 형태는 어떨까?
이 방법은 이전에 0으로 채우는 방법보다는 잘 작동하는데 계산량 측면에서도 짧은 seq는 그만큼의 계산만 하면 되므로 효율적이다.
하지만 이 구조에서 문제는 각 layer가 다른 weight를 사용하므로 그만큼 학습시켜야 할 weight의 수가 엄청나게 된다.
여기서 자연스럽게 생각되는 아이디어는 순환형태의 구조이다. weight를 공유하는 형태의 구조이다.(...!)
학습하면서 weight matrix가 동일하도록 해야한다.(Bias도 마찬가지)
이 디자인을 Recurrent neural network(RNN) a.k.a variable-depth networks라고 한다.
RNN은 이전 단계의 값을 인풋으로 받으며 일반적인 neural network을 시간 차원으로 확장한 구조라 볼 수 있다. RNN은 여러개의 레이어에서 weight를 공유하며 다양한 수의 레이어를 갖는다.
그럼 이 RNN은 어떻게 학습시켜야 할까?
이전에 배운 back propagation을 그대로 사용할 수 있을까?
👉🏻 RNN의 weight와 bias는 모든 층에서 공유한다. 그래서 이 W, b를 최적화하기 위해서 back propagation을 사용하게 되는데 그대로 사용한다면 $l-1$층에서의 gradient는 $l$번째 층에서의 gradient를 덮어쓰게 된다. 따라서 다음의 식과 같이 계속해서 더하는 방식을 취하면 작동하게 된다.(축적하는 형태)
이 축적하는 식이 실제로 맞는지 확인해보기 위해 다음의 식을 생각해보자.
함수 $f$가 다음과 같의 형태라고 할 때, 미적분 시간에 배운대로 계산을 해보면 다음과 같은 덧셈 형태가 된다.
그럼 output의 사이즈가 다양한 경우는 어떻게 될까?
예를 들어, 이미지에 text caption을 생성하거나 audio sequence를 생성해낼 때, output의 사이즈는 다양하기 때문이다.
이제 각 레이어에 output에 초점을 맞추게 된다.
그럼 각 층의 output에 대해 Loss를 가지게 된다. 예를 들어 cross-entropy와 같은 형태가 있다.
그럼 total loss는 이 loss를 다 더한 형태로 생각할 수 있다.
하지만 이 Loss에 대해 back propagation을 적용한다고 할 때 매 단계마다 델타가 들어오기 때문에 계산하는 것이 명확하지 않다.
다음의 계산과정을 나타낸 그래프를 살펴보자.
앞에서 설명한 식을 그래프로 설명하면 위의 그림과 같아진다.
빨간선인 back propagation을 통해 델타가 계산되고 이 델타를 통해 앞의 레이어에서 back propagation이 계산된다. 하지만 여기서의 델타는 여러개가 될 수 있다.(z가 여러개일 수 있으므로)
여기서 생각할 수 있는 간단한 방법은 이 델타를 더하는 방법이다. 이렇게 역방향으로 계산을 해서 back propagation을 진행할 수 있다.
각 step에서의 인풋과 아웃풋을 정리하면 다음과 같다.
Part 2
이번 파트에서는 RNN을 학습시킬 때 어떠한 어려움이 있는지에 대해 배운다.
RNN은 사실 엄청나게 deep한 네트워크다. Weight와 bias를 공유하긴 하지만 계산량이 엄청나다.
예를 들어 sequence의 길이가 1000이상이라 해보자.
chain rule로 derivative를 곱한다고 할 때 많은 수를 곱하면 어떻게 될까?
👉🏻 derivative가 1보다 작으면 계속해서 곱해갈수록 줄어들어 0으로 가까이 가는 Vanishing gradient가 일어날 수 있고 1보다 크다면 계속해서 값이 커져 Exploding gradient현상이 일어날 수 있다. 이는 clipping으로 어느정도 해결이 가능하지만, Vanishing gradient는 사실 큰 문제다. Update가 거의 안 될 수 있기 때문이다.
이를 어떻게 해결할 수 있을까?
👉🏻 기본 아이디어는 Gradient를 1과 가깝게 만드는 것이다.
앞서 보았듯이 각 레이어에서 다음과 같이 값이 계산되는데,
각 유닛에 대해 우리는 "neural circuit"을 갖는다. 이는 우리가 값을 기억해놓을 지 아니면 덮어쓸지 결정하는 circuit인데, 기억한다고 하면, 이전 activation을 copy하는 방식이고 잊어버린다고 하면 현재 input에 기반하여 어떤 값을 덮어쓰는 방식이다.
$a_{t-1}$를 "cell state"라고 할 때, "forget gate"의 $f_t$를 곱하고 $g_t$를 더해 new $a_t$를 계산해낼 수 있다. 이 식에서 derivative를 계산하게 되면 0과 1사이의 $f_t$가 나오게 된다.
여기서 유명한 LSTM cell이 나오게 된다.
잊어버린다고 하면, 첫번째와 같은 그림이 나오고 기억한다고 하면 이전의 값을 이용하여 output이 4배나 큰 matrix가 나오게 된다. 이 matrix의 4개의 output은 각각의 계산과정을 통해 다음 단계의 cell state와 $h_t$로 가게 된다.
왜 이런 circuit를 사용할까? 사실 좀 추상적인데 실제적으로 잘 작동하며 native RNN보다 잘 작동한다고 한다. 왜그럴까?
여기서 $a_t$는 step마다 매우 조금씩만 값이 변한다. (long-term memory)
반대로 $h_t$는 step마다 굉장히 다양하게 변한다.(short term memory)
👉🏻 즉, long term과 short term 데이터를 모두 이용하여 잘 작동하게 된다.
실제 실험에서 RNN은 거의 항상 output과 input을 매 step마다 가지고 있고 part 1의 RNN는 사실 실제로 거의 작동하지 않는다. LSTM보다 나은 대안도 많다.(예를 들어 트랜스포머)
Part 3
사실 우리가 사용하는 input과 output은 하나인 경우가 많다.
그럼 왜 굳이 multiple input, output을 RNN과 사용할까?
이러한 multiple output을 가지는 대부분의 문제는 output간에 강한 dependecy를 가지기를 원한다. 사실 만들어진 문장이 문법이나 구조상으로는 맞지만 유효하지 않은 문장일 수도 있기 때문이다.
예를 들어 문장을 생성하는 task라고 할 때 위 슬라이드의 세 문장을 생각해보자.
첫 단어가 "I"라고 할 때 다음에 나올 단어는 세 가지의 동사가 될 수 있다고 해보자.
이 결과 "Think"를 선택했다고 할 때 그 다음 나올 수 있는 단어는 세 가지가 또 있다. 여기서 세 가지 모두 확률이 비슷하기에 다 나올 수 있는 가능성이 있다. 여기서 "machine"을 선택하게 되고 그 다음 "just"를 선택한다고 해보자. 이렇게 나온 문장 "I think machine just"은 완성은 될 수 있지만 사실 의미론상으로는 맞지 않다.
👉🏻 따라서 단어 사이의 공분산도 고려해야한다.
이를 어떻게 해결할 수 있을까?
"think" 다음에 나올 단어를 선택할 때는 "I think"를 고려해야 한다. 따라서 단어사이의 공분산까지 고려하여 가장 높은 확률값의 "therefore"를 선택할 수 있게 된다.
이렇게 어떻게 다음을 예측하는지에 대해 알아보았다. 그럼 이 네트워크는 어떻게 학습시켜야 할까?
Input과 output은 단어간의 관계도 고려해야하므로 다음과 같이 하나씩 단어가 앞으로 간 형태로 들어가게 된다.
여기서 작은 이슈가 있다.
위와 같은 시퀀스의 인풋 아웃풋은 긴 문장을 이용하여 학습할 때 문제가 생기게 된다.
맨 처음 "I"가 나올 때 가장 확률값이 높은 것은 "think"지만 실수로 모델이 "drive"를 선택했다고 해보자. 모든 모델은 실수를 할 수 있기 때문이다.
이렇게 "I drive"가 나오게 되면 모델은 혼란스러워진다. 그 다음 "paintbrush"를 또 실수로 선택했다고 해보자. 그러면 학습하는 동안에는 output도 주어지는데 이 output인 label과 데이터의 흐름이 다른 것을 보았으므로 모델은 혼란스러워지며 오류가 생기게 된다.
이런 작은 실수가 쌓여 쓰레기를 생산하게 된다. (교수님이 실제로 trash라고 표현)
이러한 현상을 "Distribution shift"라 한다.(실제 string과의 shift가 생겼기 때문)
이렇게 하나의 작은 랜덤한 실수가 완전히 output을 망쳐놓게 된다.
이를 어떻게 해결할 수 있을까?
👉🏻 다음의 Scheduled sampling이라는 샘플링 기법으로 해결할 수 있다.
(이 기법은 확실히 이해 못해서 더 살펴보아햘 것 같다.)
이번 강의에서 배운 RNN을 사용하는 방법은 여러가지가 있다.
Input과 output이 하나에서 여러개가 될 수 있기 때문이다.
많은 레이어를 가진 RNN은 다음과 같다.
다른 Trick은 양방향 모델이 있다.
예를 들어, 음성 인식과 같은 task를 진행한다고 해보자.
문제는 특정 단어가 전체 문장을 고려하지 않고 예측되어야 한다는 것이다.
양방향모델은 기본적으로 레이어들이 쌓여있는 RNN이다.
각 단계에서 과거와 미래의 단어가 있다고 해보자. 이 양방향의 정보를 이용해 결정을 내리는 것이다.
이제 예시들을 살펴보자.
다음은 셰익스피어의 글을 generation한 예시이다.
심지어 대수 text를 만들어낼 수도 있다.
다음은 유명한 언어모델인 GPT-2의 텍스트 생성의 예시이다.
마지막으로 이번 강의 RNN에 대한 요약이다.
RNN은 가변의 인풋과 아웃풋에 대해 처리가 가능하며 학습이 어렵다는 특징이 있다.(but LSTM과 같은 cell를 이용하여 가능하다)
실제 사용에서는 input과 output을 각 step에서 항상 가지고 있다.
이번 강의를 통해 RNN에 대해 나오게 된 flow와 각각의 cell을 사용하게 된 계기 등을 알 수 있어서 RNN을 완전히 이해하는데 도움이 된 것 같다.
'개발 > UC Berkely CS182' 카테고리의 다른 글
[Lecture 12] Transformers (0) | 2021.06.07 |
---|---|
[Lecture 9] Visualization and Style Transfer (0) | 2021.05.19 |
[Lecture 8] Computer Vision (2) | 2021.05.09 |
[Lecture 7] Initialization, Batch Normalization (0) | 2021.05.02 |
[Lecture 5] Backpropagation (6) | 2021.04.18 |
댓글