8. DQN: 표를 신경망으로 바꾸면 무엇이 흔들리나

경험 리플레이: 지난 경험을 섞어 다시 쓴다

게임을 하는 에이전트가 겪는 스텝은 한 줄로 이어져서, 막대를 왼쪽으로 옮기는 동안의 화면들은 공의 자리만 몇 픽셀씩 다를 뿐 거의 같다. 한 스텝을 겪을 때마다 그 스텝으로 신경망을 고치면, 신경망은 한동안 「왼쪽 구석의 화면」만 연달아 배운다. 확률적 경사하강은 미니배치(한 번에 그래디언트를 어림하는 데 쓰는 샘플 묶음)의 샘플이 데이터 전체에서 고르게, 서로 따로 뽑혔다고 보고 몇 건의 평균으로 전체 그래디언트를 어림한다. 연달아 겪은 장면 32개로 어림한 그래디언트는 그 가정을 지키지 못한다.

역사: 린의 「다시 겪기」

1992년 카네기 멜런 대학의 롱지 린(Long-Ji Lin)은 신경망으로 강화학습을 할 때 학습을 빠르게 하는 덧붙임 셋을 견줬고, 그 첫째가 경험 리플레이(experience replay)다. 논문은 『머신러닝』에 실은 「Self-improving reactive agents based on reinforcement learning, planning and teaching」이다. 이듬해의 박사 논문에는 실험 세계가 자세히 적혀 있다. 25 × 25칸 세상에 먹이 15개, 적 4마리, 장애물이 놓여 있고, 에이전트는 주변의 좁은 범위만 본다. 처음에 기력 40을 받아 한 칸 움직일 때마다 1씩 잃고, 먹이 하나에 15를 얻으며, 보상은 적에게 잡히면 −1, 먹이를 먹으면 +0.4다.

린의 설명은 단순했다. 에이전트가 지난 경험을 기억해 두었다가, 마치 다시 겪는 것처럼 학습 알고리즘에 거듭 보여 준다. 경험을 다시 보여 주어야 하는 사정 가운데 하나로 그는 신경망의 「다시 배우기 문제」를 들었다. 상태 공간이 A와 B 두 부분으로 나뉘는데 에이전트가 한동안 A만 돌아다니면, 신경망은 B에 대해 배운 것을 잊는다. 지난 경험을 다시 보여 주면 B를 머릿속으로 다시 겪으며 기억을 새로 할 수 있다. 다만 린은 함정도 적었는데, 다음 상태가 실제 확률대로 나온다는 가정에 Q-러닝이 기대므로 경험을 고르지 않게 거듭 보여 주면 그 가정이 깨진다는 것이다. 그가 든 예는 어떤 행동이 80%의 경우 큰 벌을 받고 20%만 무사한데, 무사했던 경험만 거듭 배우면 그 행동을 해롭지 않다고 믿게 된다는 것이었다.

20여 년 뒤 DQN 논문(2015)은 경험 리플레이를 「생물에서 영감을 받은 장치」라 부르며, 쉬는 동안 해마가 최근에 겪은 길을 시간을 압축해 다시 돌려 보는 현상을 근거로 들었다.

천천히 걷는 복도

연달아 온 샘플이 실제로 얼마나 해로운지 보려면 과녁이 움직이지 않는 문제에서 보는 편이 깨끗하다. 과녁까지 움직이면 무엇 때문에 흔들리는지 가를 수 없기 때문이다. 그래서 길이 3인 복도를 천천히 걷는 에이전트를 두는데, 스텝마다 위치가 ±0.03씩 바뀌고 벽에 닿으면 튕긴다. 위치 xx의 참값은 이 책이 정한 x2x^2이고, 모델은 파라미터 둘짜리 직선 w0+w1xw_0 + w_1 x다. 직선으로는 곡선을 다 담지 못하므로, 복도 전체에 가장 잘 맞는 직선(w0w_0 = −1.495, w1w_1 = 3.000)도 평균 제곱 오차 0.456을 남긴다. 스텝마다 샘플 32개로 그래디언트 한 번(학습률 0.05)을 계산하되, 두 가지로 샘플을 고른다. 하나는 가장 최근의 32스텝, 다른 하나는 지금까지 쌓은 스텝 가운데 무작위 32개다(난수 씨앗 1, 2).

연달아 온 두 위치의 상관은 0.9994다. 연달아 온 32스텝이 덮는 위치의 폭은 평균 0.225로, 복도 길이 3의 8%도 안 된다. 그런 묶음으로 배우면 직선은 지금 걷는 곳 근처의 곡선 기울기를 따라가고, 그만큼 복도의 다른 쪽에서 틀린다. 마지막 5,000스텝 동안 기울기 w1w_1은 2.03에서 3.18 사이를 오갔고, 복도 전 구간의 오차는 평균 0.958, 가장 클 때 2.237이었다. 무작위 32개로 배우면 같은 동안 w1w_1이 2.93에서 3.16 사이에 머물고, 오차는 평균 0.463, 가장 클 때도 0.498로 가장 좋은 직선의 0.456 바로 위에 붙는다. 무작위로 뽑은 묶음 안에서 이웃한 샘플끼리의 상관은 평균 −0.027로 거의 0이다.

0 0.5 1 1.5 2 2.5 0 5,000 10,000 15,000 20,000 연달아 온 32스텝 버퍼에서 무작위 32개 가장 좋은 직선 스텝 전 구간 평균 제곱 오차

그림의 처음 몇천 스텝은 리플레이도 크게 틀린다(그림에서 위가 잘린 곳은 오차가 2.5를 넘은 곳이다). 처음 4,000스텝 동안 에이전트는 위치 1.11에서 3.00 사이만 걸었고, 0.5 아래로는 5,809스텝째에야 내려갔다. 그동안은 버퍼에도 복도 오른쪽의 기록뿐이라, 5,000스텝 때 리플레이의 오차는 2.513이었다. 왼쪽 기록까지 쌓인 뒤 7,000스텝 때 0.470으로 내려왔으니, 버퍼에서 고르게 꺼내도 버퍼에 없는 곳은 배울 수 없다.

지난 경험을 모아 두는 저장소가 이 책에서 이미 써 온 리플레이 버퍼이고, 그 버퍼에서 무작위로 꺼내 다시 배우는 방법이 경험 리플레이 (지난 경험을 섞어 다시 쓰기 / experience replay)다. 2013년의 첫 DQN은 이것을 그대로 Q-러닝의 손실에 얹어, 버퍼 D\textcolor{#cc00ff}{\mathcal{D}}에서 고르게 뽑은 경험마다 목표와 추정의 제곱 차이를 줄이게 했다.

L(θ)=E(s,a,r,s′)∼D[(r+γmax⁡a′Qθ(s′,a′)−Qθ(s,a))2]\textcolor{#d62728}{\mathcal{L}}(\theta) = \mathbb{E}_{(\textcolor{#0093b8}{s}, \textcolor{#1c9c60}{a}, \textcolor{#d9670b}{r}, \textcolor{#0093b8}{s'}) \sim \textcolor{#cc00ff}{\mathcal{D}}}\Big[\big(\textcolor{#d9670b}{r} + \textcolor{#7a1f5a}{\gamma}\max_{\textcolor{#1c9c60}{a'}} \textcolor{#00897b}{Q_\theta}(\textcolor{#0093b8}{s'}, \textcolor{#1c9c60}{a'}) - \textcolor{#00897b}{Q_\theta}(\textcolor{#0093b8}{s}, \textcolor{#1c9c60}{a})\big)^2\Big]
L(θ)손실: 신경망 파라미터 θ에 대해 줄이는 값D리플레이 버퍼: 지난 경험 (s,a,r,s′)을 모아 둔 곳. 여기서 고르게 뽑는다Qθ(s,a)신경망이 어림한 행동 가치s, s′그 스텝의 상태(화면)와 다음 상태a, a′그 스텝에서 한 행동, 다음 상태에서 고를 수 있는 행동r그 스텝의 보상γ할인율 \small\begin{array}{ll} \textcolor{#d62728}{\mathcal{L}}(\theta) & \text{손실: 신경망 파라미터 }\theta\text{에 대해 줄이는 값} \\ \textcolor{#cc00ff}{\mathcal{D}} & \text{리플레이 버퍼: 지난 경험 }(\textcolor{#0093b8}{s}, \textcolor{#1c9c60}{a}, \textcolor{#d9670b}{r}, \textcolor{#0093b8}{s'})\text{을 모아 둔 곳. 여기서 고르게 뽑는다} \\ \textcolor{#00897b}{Q_\theta}(\textcolor{#0093b8}{s}, \textcolor{#1c9c60}{a}) & \text{신경망이 어림한 행동 가치} \\ \textcolor{#0093b8}{s},\ \textcolor{#0093b8}{s'} & \text{그 스텝의 상태(화면)와 다음 상태} \\ \textcolor{#1c9c60}{a},\ \textcolor{#1c9c60}{a'} & \text{그 스텝에서 한 행동, 다음 상태에서 고를 수 있는 행동} \\ \textcolor{#d9670b}{r} & \text{그 스텝의 보상} \\ \textcolor{#7a1f5a}{\gamma} & \text{할인율} \end{array}

리플레이에는 값이 따른다. 버퍼 속 경험은 지난 정책들이 겪은 것이라, 지금 정책으로 모은 데이터만 쓰는 방법(정책 그래디언트의 REINFORCE나 PPO가 주로 그렇다)에는 그대로 쓸 수 없다. Q-러닝은 데이터를 누가 모았든 「그 상태에서 그 행동을 했더니 이렇게 됐다」만 쓰므로 버퍼와 잘 맞는다. DQN 논문도 리플레이로 배우려면 오프폴리시여야 하기 때문에 Q-러닝을 골랐다고 적었다.

ML에서: 버퍼는 크게, 꺼내기는 고르게

2015년 DQN은 최근 경험 100만 개를 버퍼에 담고, 행동을 네 번 할 때마다 버퍼에서 32개를 고르게 뽑아 한 번 업데이트했다. 학습을 시작하기 전에는 무작위로 움직이며 5만 프레임 동안 모은 경험으로 버퍼부터 채웠다. 버퍼를 크게 두면 여러 판, 여러 장면이 한 미니배치에 섞이고, 드문 장면도 한 번 쓰고 버려지지 않는다. 경험 하나가 버퍼에 머무는 동안 몇 번이나 다시 쓰이는지는 아래 문제 6에서 셈한다. 고르게 꺼내는 대신 배울 것이 많은 경험을 더 자주 꺼내는 방법(우선순위 리플레이)은 DQN 뒤의 개선으로 이어진다.

문제 4. 리뷰 점수 앱

식당 리뷰 앱이 가게 점수를 「점수 ← 점수 + 0.1 × (새 리뷰 − 점수)」로 고친다. 처음 점수는 3.5다. 한 가게에 평일 점심 손님의 리뷰(모두 4.0점) 50개와 주말 가족 손님의 리뷰(모두 3.0점) 50개가 들어온다. (가) 평일 리뷰 50개가 먼저 다 들어오고 주말 리뷰 50개가 들어오면 마지막 점수는? (나) 둘이 번갈아 들어오면? (다) 100개를 무작위 순서로 섞으면 마지막 점수는 대략 어디쯤인가?

선생님 (평상)
선생님
민준 학생, 리뷰 100개의 평균은 3.5죠. (가)의 마지막 점수도 3.5 근처일까요?
김민준 (평상)
김민준
평일 50개, 주말 50개 같은 수니까 3.5 근처겠죠.
선생님 (질문)
선생님
주말 리뷰가 들어오기 시작할 때 점수는 얼마고, 그 뒤 50번 동안 그 점수의 무게는 얼마나 남죠?
김민준 (난처함)
김민준
평일 50개 뒤면 4.0 + (3.5 − 4.0) × 0.9⁵⁰ = 3.9974 로 거의 4예요.
선생님 (평상)
선생님
그 뒤 주말 리뷰 50개 동안은요?
김민준 (평상)
김민준
주말 리뷰 하나마다 앞 점수의 무게가 0.9 배씩 줄어서 0.9⁵⁰ = 0.0052 만 남아요. 마지막 점수는 3.0051, 거의 주말 점수예요. 평일 손님 50명의 의견이 거의 지워졌어요.
이서연 (평상)
이서연
(나)는 번갈아 들어오니까 3.5 근처에서 조금씩 흔들려. 마지막이 주말 리뷰면 3.4737, 그 하나 앞은 3.5263.
선생님 (평상)
선생님
(다)는요?
이서연 (생각)
이서연
섞으면 어느 때든 최근 리뷰에 평일과 주말이 비슷하게 섞여 있을 테니까, 3.5 근처에 올 것 같아요.
선생님 (평상)
선생님
직접 섞어 볼까요?
김민준 (평상)
김민준
씨앗 4로 천 번 섞어 보니 평균 3.495, 표준편차 0.100 이에요.
이서연 (아하)
이서연
순서만 바꿨는데 같은 업데이트 규칙이 전혀 다른 답을 내네요.
김민준 (평상)
김민준
시험 범위 앞 단원을 다 외우고 뒤 단원으로 넘어가면 시험 날엔 앞 단원이 하나도 기억 안 나는 거랑 같네요. 매일 섞어서 복습하는 게 그래서 낫고요.

정리 (가) 3.0051. 마지막 50개 앞의 점수에는 0.9⁵⁰ = 0.0052의 무게만 남는다. (나) 3.4737 (끝에서 둘째는 3.5263). (다) 평균 3.495, 표준편차 0.100(씨앗 4, 천 번). 고정된 학습률로 하나씩 고치는 규칙은 최근 샘플을 크게 치므로, 샘플이 몰려서 오면 앞의 묶음을 잊는다.

문제 5. 32장면은 몇 장면 값어치인가

연달아 온 게임 화면에서 뽑은 어떤 값 32개가 있다. 바로 이웃한 두 화면의 값끼리 상관이 ρ\rho(로, rho) = 0.9이고, kk칸 떨어지면 상관이 ρk\rho^k로 줄어든다고 하자. 서로 따로 뽑은 nn개의 평균은 분산이 한 개의 1/n1/n이다. (가) 이 32개의 평균은 서로 따로 뽑은 샘플 몇 개의 평균과 분산이 같은가? nn이 클 때 쓰는 어림식 n(1−ρ)/(1+ρ)n(1-\rho)/(1+\rho)로 먼저 셈하라. (나) 어림식대로라면 서로 따로 뽑은 32개만큼의 값어치를 얻으려면 연달아 몇 개를 모아야 하는가? (다) 버퍼 100만 개에서 무작위로 32개를 뽑으면?

선생님 (평상)
선생님
서연 학생, 32개를 모았으면 32개의 값어치일까요?
이서연 (평상)
이서연
서로 비슷하니까 32개보다는 적겠죠. 어림식에 넣으면 32 × 0.1 / 1.9 = 1.68 개예요.
김민준 (놀람)
김민준
32장면을 모아도 두 장면도 안 된다고? 너무 작은데. 코드로 돌려 볼게. 상관 0.9 짜리 수열 32개를 2만 번 만들어서 평균의 분산을 쟀더니 0.423, 그럼 1/0.423 = 2.36 개야.
선생님 (질문)
선생님
어림식과 실험이 왜 다를까요? 어림식은 어떤 경우의 식이었죠?
이서연 (생각)
이서연
n이 클 때요. 32개 맨 앞과 맨 뒤 샘플은 한쪽 이웃만 있어서 상관을 덜 받아요.
선생님 (질문)
선생님
그럼 어림 말고 정확히 더하면요?
이서연 (평상)
이서연
분산이 1322∑i,j0.9∣i−j∣=0.424\frac{1}{32^2}\sum_{i,j} 0.9^{|i-j|} = 0.424 라서 2.36 개, 실험이랑 맞아요. 그래도 32개 가운데 2개 남짓이라는 건 같아요.
선생님 (평상)
선생님
(나)는요?
김민준 (평상)
김민준
1.9/0.1 = 19 배를 모아야 하니까 32 × 19 = 608 개요. 그만큼 오래 기다려야 독립 샘플 32개만큼의 값어치가 생기네요.
이서연 (평상)
이서연
(다)는 100만 개 가운데 32개를 고르게 뽑으면 거의 겹치지 않고 서로 먼 스텝이니까 상관이 거의 0, 32개 그대로의 값어치예요.
김민준 (평상)
김민준
설문을 같은 동아리방에 있는 친구 32명한테만 돌리면 의견이 비슷해서 사실상 두어 명한테 물은 거랑 같은 거네요.

정리 (가) 어림식 32 × (1 − 0.9)/(1 + 0.9) = 1.68개. 유한한 32개를 정확히 셈하면(시뮬레이션과 같다) 2.36개. (나) 32 × 19 = 608개. (다) 거의 32개. 서로 비슷한 샘플은 개수만큼의 정보를 주지 않지만, 버퍼에서 무작위로 꺼내면 미니배치가 다시 「서로 따로 뽑은 샘플」에 가까워진다.

문제 6. 한 경험은 몇 번 쓰이나

2015년 DQN의 설정은 리플레이 버퍼 100만 개(가장 최근 것), 행동 네 번마다 업데이트 한 번, 미니배치 32개, 타깃 신경망 복사는 업데이트 1만 번마다다. (가) 경험 하나가 버퍼에 들어와서 밀려날 때까지 업데이트가 몇 번 일어나는가? (나) 그동안 그 경험은 평균 몇 번 뽑히는가? (다) 버퍼에서 가장 오래된 경험은 얼마나 예전 정책이 모은 것인가? 그런 데이터로 REINFORCE를 하면 무엇이 문제인가?

선생님 (평상)
선생님
민준 학생, 경험 하나가 버퍼에 머무는 시간부터요.
김민준 (평상)
김민준
버퍼가 100만 개고 행동마다 하나씩 들어오니까 행동 100만 번 동안 머물러요. 그동안 업데이트는 100만 번이요.
선생님 (질문)
선생님
업데이트는 행동 몇 번마다 한 번이었죠?
김민준 (난처함)
김민준
네 번마다요. 그럼 업데이트는 25만 번이에요.
이서연 (평상)
이서연
(나)는 업데이트 한 번에 100만 개 가운데 32개를 뽑으니까 한 경험이 뽑힐 확률은 32/1,000,000, 25만 번이면 250,000 × 32 / 1,000,000 = 8 번이야.
선생님 (평상)
선생님
우선순위 리플레이 논문도 DQN이 경험 하나를 평균 8번 다시 썼다고 적었어요. (다)는요?
이서연 (평상)
이서연
가장 오래된 건 업데이트 25만 번 전 정책이 모은 거예요. 타깃 복사로 세면 25번 전이고요.
김민준 (아하)
김민준
REINFORCE는 지금 정책이 뽑은 응답의 로그확률을 올리고 내리는 거라, 25만 번 전 정책이 고른 행동이면 지금 정책은 그 행동을 거의 안 고를 수도 있어요. 기댓값을 엉뚱한 분포에서 재는 거죠. Q-러닝은 「그 상태에서 그 행동을 하니 이렇게 됐다」만 쓰니까 누가 골랐는지 상관없고요.
김민준 (평상)
김민준
작년 선배 기출문제는 문제와 정답만 보면 올해도 쓸 수 있는데, 선배가 고른 답 순서를 내 실력이라고 보면 안 되는 거랑 같네요.

정리 (가) 1,000,000 ÷ 4 = 250,000번. (나) 250,000 × 32 ÷ 1,000,000 = 8번. (다) 업데이트 25만 번 전(타깃 복사 25번 전)의 정책. REINFORCE처럼 지금 정책의 샘플로 기댓값을 재는 방법에는 그대로 쓸 수 없다. 리플레이 버퍼는 오프폴리시 방법과 함께 써야 한다.