[TENSORFLOW] Reinforcement learning

2017. 4. 27. 11:25machine learning

강화 학습이란 현재의 상태를 인식해서 어떠한 행동을 취하고 해당 행동이 옮은지에 따라 포상을 취하게 된다. 이렇게 포상을 받게 되면, 이러한 포상이 최대가 될 수 있도록 하는 일련의 행동을 찾을 수가 있고, 이러한 행동들이 발생하게 하는 정책을 찾아냄으로써 학습을 해나가는 것이 바로 강화 학습이다.

우리는 20년치의 주식 데이터를 가지고 언제 어떤 행동을 했어야 했는지에 대해 학습을 강화해 볼 것이다.

행동은 크게 [주식을 산다, 주식을 판다, 기다린다] 이렇게 3 가지 action으로 분류하였다.

코드를 보며 자세히 살펴보자.

우선 DecisionPolicy라고 하는 class를 생성해 주자. 일종의 abstract class라고 생각하자.

그리고 DecisionPolicy를 상속받는 QLearningDecisionPolicy를 생성하자. 이 class에서 실제로 action을 선택하고, q 값을 update하는 기능을 한다.

q는 어떤 특정 상태에 있을 때, 각각의 행동에 대한 즉각적인 보상과 미래에 대한 보상을 더한 값인데, 이 값을 미리 계산을 해 놓고 정답으로 설정을 해 놓은 후 어떤 상태가 되었을 때 이 정답 값과의 오차가 최소가 되도록 모델의 파라미터들을 업데이트를 한다.

위의 식은 q를 계산하는 식인데, 첫 번째 식을 보면 어떤 상태에 대해 현재 행동을 했을 때의 즉각적인 보상 + 미래의 상태에 대해 미래의 어떤 행동을 한 것에 대한 보상의 최대값과 γ의 곱을 나타내고 있다. 여기서 γ은 hyper parameter이며, 할인률이라고도 불린다. γ을 통해 미래의 가치가 얼마나 가치 있는지를 표현할 수가 있다. 만약 γ을 0으로 설정하면, 미래의 가치는 고려하지 않겠다는 뜻이 되고, γ의 값을 크게 할 수록 미래의 값을 더 중요하게 판단할 수가 있다.

두번째 식은 기존 식에 learning rate α를 추가했다. α를 제거하면 위의 식과 동일하다.   

QLearningDecisionPolicy의 init 함수부터 살펴보자. epsilon이란 변수를 정의했는데, 강화학습의 exploit와  explore 사이의 적절한 조화를 위한 파라미터이다. exploit는 이미 알고 있는 보상에 대한 이용을 뜻하며, explore는 새로운 값에 대한 탐험을 뜻한다. 여기서는 epsilon 값을 0.9를 줌으로써 이미 알고 있는 보상은 0.9만큼 이용할 것이고 0.1 만큼 탐험해서 새로운 값을 찾을 것이다라고 정의를 한 것이다.

gamma는 미래 보상에 대한 할인률을 나타내며, action은 ["Buy", "Sell", "Hold"] 리스트로 정의한다.

output dimension은 action 값을 찾아야하기 때문에 action의 길이와 같게 설정하고, hidden dimension은 200차원으로 설정한다. 

입력값 X와 정답 값 Y가 들어갈 수 있도록 placeholder를 생성해주고, hidden layer에 적용될 수 있는 W값과 bias 값을 설정해 준다. W 값은 input dim x hidden dim의 행렬이 되고 bias는 hidden dim 크기의 행렬이 된다.

hidden node에서는 W1값과 입력 값 x의 행렬 곱을 한 후 bias를 더한 값에 relu라는 activation 함수를 씌운다. 

hidden node에서 계산된 결과에 W2라는 가중치와 2번째 bias 값이 주어지고 해당 값들 또한 hidden node에서의 동작과 마찬가지로 hidden node의 계산 결과 값에 W2의 행렬 곱 + bias2에 relu를 씌운 값으로 계산이 된다. 이렇게 계산된 결과 값이 해당 action의 예측값이 되며 실제 action과 해당 예측값의 차이의 제곱을 구해 그 결과 값이 최소가 되도록 W1, W2, bias1, bias2를 찾아나간다.

그림으로 그려보면 아래와 같다.

select_action 함수를 살펴보자. 미리 정의한 epsilon 값에 따라 exploit와 explore를 설정할 수가 있다. 위의 코드에서 exlploit는 기존 지식을 이용하기 때문에 if문에 해당되며 explore는 새로운 값의 탐색을 뜻하기 때문에 else문에 해당한다.

if문을 살펴보면, 현재 상태에 대해 q함수를 실행함으로써 action 값들의 확률을 찾고 가장 높은 확률의 action을 반환한다.

else문에서는 random으로 액션 값을 찾아서 리턴하고 있다.

update_q 함수를 살펴보자. 현재 상태에 대한 action값을 찾고, 다음 번 상태에 대한 next_action 값을 찾는다. 

그런 후 현재 상태에 대한 action값들의 가중치 리스트에서, next_action에서의 가장 높은 가중치 index에다 현재의 보상 + 앞으로 받을 보상에 대한 값을 저장한다. 예를 들어 현재 상태에 대한 action 가중치 값이 [123 , 234, 311] 이렇게 나타났고 next_action 가중치 값이 [132, 5000, 200] 이렇게 나타났다면, 현재 상태에 대한 action 가중치 값 중 234에 해당 하는 부분에 보상 값을 넣고 train을 다시 시키는 것이다.

이 말은 즉, 현재 상태의 action 가중치와 미래 상태의 action 보상 값 둘다 고려하겠다는 뜻이 되며, 해당 값을 label로 train을 돌려 W1, W2, b1, b2 값을 업데이트 한다.

np.squeeze 함수가 쓰였는데, 이는 demension을 한차원 감소시켜주는 역할을 한다. 예를 들어 [[1,2,3]] 이란 행렬에 대해 squeeze가 쓰였다면 결과는 [1,2,3] 이 된다.

get_price 함수는 야후 주식데이터를 가져와서 히스토그램 형식으로 저장하는 역할을 하며, plot_prices는 날짜별 주식 가격을 그래프로 보여주는 역할을 한다.

run_simulation 함수에서는 주식 데이터를 가지고 실제로 action 예측 값에 따라 주식을 사고 파는 simulation을 해보는 작업을 한다. 상태 값은 현재 index를 기준으로 200개의 주식 histo data와 현재 예산 그리고 주식의 수 이렇게 총 202개의 데이터를 state로 하며, share_value는 현재 상태 이후의 주식 값, current_portfolio는 예산에 주식 수와 share_value를 곱한 값의 합을 의미한다.

select_action을 통해 현재 상태에 대한 적절한 action 값을 리턴 받는다.

action을 취한 이후에 현재 예산과 현재 주식 * share_value의 합을 new_portfolio로 저장하고, reward는 new_portfolio에서 current_portfolio를 뺀 값으로 취한다. next_state는 현재 주식 histo data를 오른쪽으로 하나씩 shift 시킨 값에 action을 취한 이후의 예산과 stock 개수로 정한다.

이렇게 모든 변수 설정이 끝났으면 update_q를 실행해서 가중치 값들을 update하고 이 작업을 histo data가 전부 scaning 될 때까지 반복한다.

마지막 histo data까지 작업이 끝났으면, 최종적으로 나온 예산과 주식 수 * share_value를 합한 값을 리턴한다. 이 값이 결국은 주식을 사고 팔고 hold 한 후 취한 이득의 결과라고 보면 된다.

run_simulations는 이러한 simulation 작업을 반복적으로 돌린 후 해당 값의 평균을 구하는 작업을 한다.

이런 식으로 simulation을 돌려서 우리는 해당 상태일때의 가장 적합한 action을 취하는 모델을 만들 수가 있다.