18.3 순환 모델과 상태공간 모델

실습 · 연습문제 2개

one-step 예측과 free rollout ar-rollout

차수 pp인 선형 자기회귀(AR) 모델

x^t=∑i=1pwi xt−i\hat{x}_t = \sum_{i=1}^{p} w_i \, x_{t-i}

로 시계열을 예측한다. 같은 모델을 두 방식으로 쓴다.

  • one-step(teacher forcing): 매 시점 참값 과거 xt−1,…,xt−px_{t-1}, \dots, x_{t-p}로 x^t\hat{x}_t를 예측한다.
  • free rollout: 처음 pp개의 참값만 주고, 그 뒤로는 모델이 낸 예측값을 다시 과거로 넣어 이어 간다.

두 함수를 완성한다. w의 shape은 (p,)이고 w[0]이 w1w_1(바로 전 시점의 계수)이다.

함수 입력 반환
one_step(w, x) 참값 시계열 x (T,) 길이 T인 배열. 앞 p개는 x를 그대로 두고, t ≥ p에서는 참값 과거로 낸 예측
rollout(w, x_init, T) 처음 p개 참값 x_init (p,), 전체 길이 T 길이 T인 배열. 앞 p개는 x_init, 그 뒤는 자기 예측으로 이어 간 값

테스트는 다음을 확인한다.

  • 계수가 (w1,w2)=(2rcos⁡ω,−r2)(w_1, w_2) = (2r\cos\omega, -r^2)인 AR(2)는 감쇠 진동 xt=rtcos⁡(ωt)x_t = r^t\cos(\omega t)를 정확히 만든다. 이 계열의 처음 두 값으로 rollout하면 전체 계열이 나와야 한다.
  • 참값 계열에 잡음이 있으면 one-step 예측은 매 step 참값으로 다시 맞춰지지만, rollout은 그렇지 않다. 계수가 조금 틀린 모델의 rollout 오차가 one-step 오차보다 훨씬 커지는 것을 확인한다.

힌트: 두 함수는 "과거를 어디서 가져오는가"만 다르다. one-step은 x에서, rollout은 지금까지 만든 출력 배열에서 가져온다.

상태공간 모델의 재귀와 합성곱 ssm-scan-vs-conv

이산 선형 상태공간 모델

xk=Aˉxk−1+Bˉuk,yk=Cxk,x−1=0\mathbf{x}_k = \bar{\mathbf{A}}\mathbf{x}_{k-1} + \bar{\mathbf{B}}u_k, \qquad y_k = \mathbf{C}\mathbf{x}_k, \qquad \mathbf{x}_{-1} = \mathbf{0}

은 같은 출력을 두 방법으로 계산할 수 있다. 한 step씩 상태를 갱신하는 재귀(추론에 알맞음)와, kernel Kj=CAˉjBˉK_j = \mathbf{C}\bar{\mathbf{A}}^j\bar{\mathbf{B}}로 입력을 한 번에 거르는 합성곱 yk=∑j=0kKjuk−jy_k = \sum_{j=0}^{k} K_j u_{k-j}(병렬 학습에 알맞음)이다.

세 함수를 완성한다. A_bar의 shape은 (N, N), B_bar는 (N, 1), C는 (1, N), u는 (L,)이다.

함수 반환 shape
ssm_scan(A_bar, B_bar, C, u) 재귀로 계산한 출력 y0,…,yL−1y_0, \dots, y_{L-1} (L,)
ssm_kernel(A_bar, B_bar, C, L) kernel K0,…,KL−1K_0, \dots, K_{L-1} (L,)
ssm_conv(A_bar, B_bar, C, u) ssm_kernel과 인과(causal) 합성곱으로 계산한 출력 (L,)

테스트는 다음을 확인한다.

  • 무작위로 만든 안정한 모델에서 ssm_scan과 ssm_conv의 출력이 같다.
  • ssm_kernel이 단위 충격 입력 u=(1,0,0,… )u = (1, 0, 0, \dots)에 대한 ssm_scan의 출력과 같다(kernel은 impulse response다).
  • 출력이 인과적이다. kk 이후의 입력을 바꿔도 y0,…,yky_0, \dots, y_k는 변하지 않는다.

힌트: ssm_kernel에서는 행렬 거듭제곱을 매번 새로 계산하지 말고 AˉjBˉ\bar{\mathbf{A}}^j\bar{\mathbf{B}}를 한 step씩 갱신한다. ssm_conv는 np.convolve(u, K)[:L]로 쓸 수 있다(왜 앞의 LL개만 쓰는지 생각해 본다).