chapter 4 Dynamic Programming Example Car Rental (in-place)

2020. 5. 5. 16:49관심있는 주제/RL

728x90

2020/05/01 - [관심있는 주제/RL] - 강화학습 - Dynamic Programming 공부

2020/05/05 - [관심있는 주제/RL] - chapter 4 Dynamic Programming Example Grid World

2020/05/05 - [관심있는 주제/RL] - chapter 4 Dynamic Programming Example Car Rental (in-place)

2020/05/05 - [관심있는 주제/RL] - chapter 4 Dynamic Programming Example 도박사 문제

 

 

 

 

 

 
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy.stats import poisson
In [ ]:
matplotlib.use('Agg')
 

Page 99

자동차 렌탈 잭」adc은 전국적인 자동차 렌탈 회사에서 일하며 두 지점을 관리하고 있 다. 매일 몇 명의 고객이 자동차를 텐트하기 위해 각 지점을 방문한다. 조건에 맞는 차가 있으면 잭은 차를 빌려주고 본乂!•로부터 10달러의 보상을 받는다. 해당 지점에 빌려줄 차가 없으면 거래 는 무산된다. 차는 회수된 다음 날부터 다시 대여가 가능하다. 각 지점에 렌탈 요청을 받은 차량 이 항상 구비되어 있도록 하기 위해 잭은 밤사이 두 곳의 지점에 있는 차량을 한 대당 2달러의 비 용을 들여 교환할 수 있다 한 지점에서 대여되고 회수되는 자동차의 개수가 푸아송Poisson 분포 를 따르는 확률 변수라고 가정한다. 푸아송 분포를 따른다는 것은 자동차의 개수가 n 이 될 확률 이 -A임을 의미한다. 이때 그는 개수의 평균값을 나타낸다. 대여되는 자동차 개수에 대한 A 값이 첫 번째와 두 번째 지점에서 각각 3과 4이고, 회수되는 자동차 개수에 대한 A 값은 각각 3 과 2라고 하자. 문제를 조금 단순화하기 위해,각 지점은 최대 20대까지만 자동차를 보유할 수 있다고 가정한다(2 대를 초과하는 분량은 모두 본사로 회수되기 때문에 이 문제에서 고려할 필요가 없다). 그 리고 하루 밤사이 두 지점 사이에서 교환할 수 있는 자동차의 최대 개수는 5대로 제한된다. 할인률을 $ \gamma $ = 0.9로 하고

이 문제를 연속적인 유한 MDP 문제로 형식화하겠다 이때 시간 단계는 하 루 단위이고,상태는 하루가 끝나는 시점에 각 지점이 보유한 자동차의 개수이며,행동은 밤사이 두 지점 사이에서 교환되는 자동차의 총 개수다.

그림 4.2는 차량을 교환하지 않는 정책을 시작으로 하여 정책 반복을 수행한 결과를 보여준다. 잭의 자동차 렌탈 예제가 보여주듯이,정책 반복이 수렴할 때까지 행해진 반복의 횟수는 종종 놀라울 정도로 작다. 이것은 그림 4.1 의 예제에서도 확인할 수 있다 그림 4.1 의 왼쪽 제일 아래에 있 는 다이어그램은 동일한 확률을 갖는 무작위 정책에 대한 가치 함수를 보여주고, 오른쪽 제일 아 래에 있는 다이어그램은 이 가치 함수에 대한 탐욕적 정책을 보여준다. 정책 향상 정리는 이 최종 정책들이 원래의 무작위 정책보다 더 좋다는 것을 보장한다 하지만 이 경우에는 이 최종 정책들 이 단순히 더 좋은 정책이 아니라 가장 적은 수의 단계를 통해 종단 상태에 도달한 최적 정책이 다. 이 예제에서는 단 한 번의 정책 반복만으로 최적 정책에 도달한다

 

문제 정의

In [ ]:
# maximum # of cars in each location
MAX_CARS = 20

# maximum # of cars to move during night
MAX_MOVE_OF_CARS = 5

# expectation for rental requests in first location
RENTAL_REQUEST_FIRST_LOC = 3

# expectation for rental requests in second location
RENTAL_REQUEST_SECOND_LOC = 4

# expectation for # of cars returned in first location
RETURNS_FIRST_LOC = 3

# expectation for # of cars returned in second location
RETURNS_SECOND_LOC = 2

DISCOUNT = 0.9

# credit earned by a car
RENTAL_CREDIT = 10

# cost of moving a car
MOVE_CAR_COST = 2
In [7]:
# all possible actions
"""
음의 값은 두번째 -> 첫번째 지점으로 이동 (즉 -5면 5개의 차량이 2->1로 이동)
양의 값은 첫번째 -> 두번째 지점으로 이동 (즉 -5면 5개의 차량이 2->1로 이동)
"""
actions = np.arange(-MAX_MOVE_OF_CARS, MAX_MOVE_OF_CARS + 1)

# An up bound for poisson distribution
# If n is greater than this value, then the probability of getting n is truncated to 0
"""
한 지점에 최대 10개만 있게 한다는 의미 
한쪽에 쏠리는 것을 방지하기 위한?
"""
POISSON_UPPER_BOUND = 11

# Probability for poisson distribution
# @lam: lambda should be less than 10 for this function
poisson_cache = dict()

"""
poisson 확률값
key의 역할은 2가지가 있는 듯 
1.기존에 나온것이 있으면 그대로 사용하기
2.첫번째 위치와 두번째 위치에 대한 중복값의 경우를 없애기 위해
first_location_key : (0,10,20,30,...100)+2
second_location_key : (0,10,20,30,...100)+3
"""
def poisson_probability(n, lam):
    global poisson_cache
    key = n * 10 + lam
    if key not in poisson_cache:
        poisson_cache[key] = poisson.pmf(n, lam)
    return poisson_cache[key]
In [6]:
actions
Out[6]:
array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4,  5])
In [8]:
def expected_return(state, action, state_value, constant_returned_cars):
    """
    @state: [# of cars in first location, # of cars in second location]
    @action: positive if moving cars from first location to second location,
            negative if moving cars from second location to first location
    @stateValue: state value matrix
    @constant_returned_cars:  if set True, model is simplified such that
    the # of cars returned in daytime becomes constant
    rather than a random value from poisson distribution, which will reduce calculation time
    and leave the optimal policy/value state matrix almost the same
    """
    # initailize total return
    returns = 0.0

    # cost for moving cars
    returns -= MOVE_CAR_COST * abs(action)

    # moving cars
    NUM_OF_CARS_FIRST_LOC = min(state[0] - action, MAX_CARS)
    NUM_OF_CARS_SECOND_LOC = min(state[1] + action, MAX_CARS)

    # go through all possible rental requests
    for rental_request_first_loc in range(POISSON_UPPER_BOUND):
        for rental_request_second_loc in range(POISSON_UPPER_BOUND):
            # probability for current combination of rental requests
            
            prob = poisson_probability(rental_request_first_loc, RENTAL_REQUEST_FIRST_LOC) * \
                poisson_probability(rental_request_second_loc, RENTAL_REQUEST_SECOND_LOC)

            num_of_cars_first_loc = NUM_OF_CARS_FIRST_LOC
            num_of_cars_second_loc = NUM_OF_CARS_SECOND_LOC

            # valid rental requests should be less than actual # of cars
            """
            기존에 있는 차량수보다는 적게 렌탈이 가능하니까 다음과 같이 설정
            """
            valid_rental_first_loc = min(num_of_cars_first_loc, rental_request_first_loc)
            valid_rental_second_loc = min(num_of_cars_second_loc, rental_request_second_loc)

            # get credits for renting
            """
            rental 된 수만큼 돈을 벌기때문에 다음과 같은 reward 
            """
            reward = (valid_rental_first_loc + valid_rental_second_loc) * RENTAL_CREDIT
            """
            빌려갔으니 더이상 차가 없다는 의미
            """
            num_of_cars_first_loc -= valid_rental_first_loc
            num_of_cars_second_loc -= valid_rental_second_loc

            if constant_returned_cars:
                # get returned cars, those cars can be used for renting tomorrow
                returned_cars_first_loc = RETURNS_FIRST_LOC
                returned_cars_second_loc = RETURNS_SECOND_LOC
                """
                남은 차량과 회수 되는 것 포아송 lambda값 을 이용 (대여되는 자동차의 개수(상수))
                """
                num_of_cars_first_loc = min(num_of_cars_first_loc + returned_cars_first_loc,
                                            MAX_CARS)
                num_of_cars_second_loc = min(num_of_cars_second_loc + returned_cars_second_loc,
                                             MAX_CARS)
                """
                각 지점에서 렌탈이 될 확률 * (보상 + 할인율 * 상태 가치 함수)
                """
                returns += prob * (reward + DISCOUNT * state_value[num_of_cars_first_loc,
                                                                   num_of_cars_second_loc])
            else:
                """
                대여되는 자동차의 모든 개수를 고려
                """
                for returned_cars_first_loc in range(POISSON_UPPER_BOUND):
                    for returned_cars_second_loc in range(POISSON_UPPER_BOUND):
                        prob_return =(
                            poisson_probability(returned_cars_first_loc,
                                                RETURNS_FIRST_LOC) * 
                            poisson_probability(returned_cars_second_loc, 
                                                RETURNS_SECOND_LOC))
                        num_of_cars_first_loc_ = min(num_of_cars_first_loc + returned_cars_first_loc, 
                                                     MAX_CARS)
                        num_of_cars_second_loc_ = min(num_of_cars_second_loc + returned_cars_second_loc, 
                                                      MAX_CARS)
                        """
                        각 경우의 수의 확률의 곱 * 각 지점에서 렌탈이 될 확률
                        """
                        prob_ = prob_return * prob
                        """
                        Bellman Expectation Equation
                        """
                        returns += prob_ * (reward + DISCOUNT *
                                            state_value[num_of_cars_first_loc_,
                                                        num_of_cars_second_loc_])
    return returns
In [9]:
def figure_4_2(constant_returned_cars=True):
    value = np.zeros((MAX_CARS + 1, MAX_CARS + 1))
    policy = np.zeros(value.shape, dtype=np.int)

    iterations = 0
    _, axes = plt.subplots(2, 3, figsize=(40, 20))
    plt.subplots_adjust(wspace=0.1, hspace=0.2)
    axes = axes.flatten()
    while True:
        fig = sns.heatmap(np.flipud(policy), cmap="YlGnBu", ax=axes[iterations])
        fig.set_ylabel('# cars at first location', fontsize=30)
        fig.set_yticks(list(reversed(range(MAX_CARS + 1))))
        fig.set_xlabel('# cars at second location', fontsize=30)
        fig.set_title('policy {}'.format(iterations), fontsize=30)

        """
        policy evaluation (in-place)
        Q : 왜 단 한번의 policy iteration이라고 하는 것인지??
        A : 이유는 기존 policy iteration 같은 경우 2개의 value function을 유지하지만, 
        해당 코드에서는 value function 1개만을 가지고 덮어쓰는 방향으로 하고 있는데, 
        이러한 방법을 in-place라고 한다고 함.
        """
        while True:
            old_value = value.copy()
            for i in range(MAX_CARS + 1):
                for j in range(MAX_CARS + 1):
                    """
                    각 지점에서 차량의 개수가 있을 때, 그 상황에서 상태강치 함수 계산하기
                    ex ) 첫번째는 0 두번째 0일 때, 
                    정책과 현재 상태에서의 k번째 가치 함수를 통해 k+1 가치 함수를 계산함
                    """
                    new_state_value = expected_return([i, j], 
                                                      policy[i, j], 
                                                      value, 
                                                      constant_returned_cars)
                    value[i, j] = new_state_value
            max_value_change = abs(old_value - value).max()
            print('max value change {}'.format(max_value_change))
            if max_value_change < 1e-4:
                break

        """
        policy improvement
        policy를 update하는 부분
        처음에는 행동을 0만 하다가 점점 업데이트
        """
        policy_stable = True
        for i in range(MAX_CARS + 1):
            for j in range(MAX_CARS + 1):
                old_action = policy[i, j]
                action_returns = []
                """
                actions : -5 ~ 5
                """
                for action in actions:
                    """
                    행동을 하였을 때 해당하는 지점에 그만큼의 차량이 있어야 하므로 
                    아래와 같은 if문이 생김
                    """
                    if (0 <= action <= i) or (-j <= action <= 0):
                        action_returns.append(
                            expected_return([i, j],
                                            action, 
                                            value, 
                                            constant_returned_cars))
                    else:
                        action_returns.append(-np.inf)
                """
                각 상태에서의 모든 행동을 고려하여 최고의 값을 action으로 설정
                """
                new_action = actions[np.argmax(action_returns)]
                policy[i, j] = new_action
                """
                기존 행동과 바뀐 행동사이의 변화가 있으면 policy evaluation부터 다시 진행
                없으면 멈추는 구조
                """
                if policy_stable and old_action != new_action:
                    policy_stable = False
        print('policy stable {}'.format(policy_stable))

        if policy_stable:
            fig = sns.heatmap(np.flipud(value), cmap="YlGnBu", ax=axes[-1])
            fig.set_ylabel('# cars at first location', fontsize=30)
            fig.set_yticks(list(reversed(range(MAX_CARS + 1))))
            fig.set_xlabel('# cars at second location', fontsize=30)
            fig.set_title('optimal value', fontsize=30)
            break

        iterations += 1

    plt.savefig('./images/figure_4_2.png')
    plt.close()
In [10]:
if __name__ == '__main__':
    figure_4_2()
 
max value change 196.62783361783852
max value change 134.98823859766583
max value change 91.41415360228919
max value change 67.17097732555729
max value change 51.29055484635097
max value change 38.49091000659837
max value change 29.406139835126424
max value change 25.7210573245398
max value change 22.381602293031023
max value change 19.40385808254939
max value change 16.77577350573091
max value change 14.47251552455765
max value change 12.464101852186843
max value change 10.719367983418692
max value change 9.20806226246873
max value change 7.9019189666795455
max value change 6.775146571130392
max value change 5.8045764710083745
max value change 4.969618520007145
max value change 4.252112693842776
max value change 3.6361309524054946
max value change 3.107761240497666
max value change 2.654891834022692
max value change 2.26700589940549
max value change 1.9349911763441128
max value change 1.650966802154585
max value change 1.4081276418079938
max value change 1.2006055672075036
max value change 1.02334664187498
max value change 0.8720029351049448
max value change 0.7428376083516355
max value change 0.6326419232035505
max value change 0.5386628774742235
max value change 0.45854026040933604
max value change 0.3902520158000584
max value change 0.33206690395809346
max value change 0.28250355471067223
max value change 0.2402951004837064
max value change 0.20435866938208846
max value change 0.1737691018435612
max value change 0.14773633074884174
max value change 0.12558593365213255
max value change 0.10674242749371388
max value change 0.09071493100810812
max value change 0.07708486873008269
max value change 0.06549543334426744
max value change 0.05564256088280217
max value change 0.047267206266042194
max value change 0.040148735572074656
max value change 0.03409927655258116
max value change 0.028958890796900505
max value change 0.02459144993093787
max value change 0.02088111467702447
max value change 0.01772932984539466
max value change 0.0150522606048753
max value change 0.012778605996800252
max value change 0.010847734796413988
max value change 0.00920809667559297
max value change 0.007815868406908066
max value change 0.006633800647819044
max value change 0.005630235829983121
max value change 0.0047782719802853535
max value change 0.004055050928627679
max value change 0.003441152547054571
max value change 0.002920079317334512
max value change 0.0024778178350288727
max value change 0.0021024658457236
max value change 0.0017839150607983356
max value change 0.0015135814573454809
max value change 0.0012841759864272717
max value change 0.0010895096651211134
max value change 0.0009243279118891223
max value change 0.0007841697620847299
max value change 0.000665248240920846
max value change 0.0005643487139082026
max value change 0.00047874254232738167
max value change 0.00040611372588728045
max value change 0.0003444966009737982
max value change 0.000292222921245866
max value change 0.0002478769196159192
max value change 0.00021025715051337102
max value change 0.00017834408060934948
max value change 0.00015127258103575514
max value change 0.00012830856951495662
max value change 0.00010882917825938421
max value change 9.230592559106299e-05
policy stable False
max value change 72.93565506480746
max value change 5.771584637253568
max value change 2.1472970104344995
max value change 1.070365975080108
max value change 0.8619106467957636
max value change 0.7181428891676092
max value change 0.611364010490604
max value change 0.5169059906119742
max value change 0.4358272831748309
max value change 0.3670218562992318
max value change 0.30890785349942007
max value change 0.259927010978231
max value change 0.21868429274547907
max value change 0.18397356667821896
max value change 0.15476712387498992
max value change 0.1301950284682789
max value change 0.10952318723241206
max value change 0.09213308201026393
max value change 0.07750397308279844
max value change 0.065197614125168
max value change 0.054845259444334715
max value change 0.046136675532011395
max value change 0.038810871455780216
max value change 0.03264828952961807
max value change 0.02746423075132043
max value change 0.02310332179803254
max value change 0.019434859485443212
max value change 0.016348893962117472
max value change 0.013752933586260951
max value change 0.011569172929284832
max value change 0.009732160858675343
max value change 0.008186838889457704
max value change 0.006886890992063854
max value change 0.005793355427499591
max value change 0.004873456997415815
max value change 0.004099624717810002
max value change 0.003448665460666689
max value change 0.0029010688248831684
max value change 0.0024404223665897007
max value change 0.002052919695017863
max value change 0.0017269466670768452
max value change 0.0014527332954799022
max value change 0.0012220609166320173
max value change 0.001028015870474519
max value change 0.0008647822832017482
max value change 0.000727467756007627
max value change 0.0006119567266296144
max value change 0.0005147871261783621
max value change 0.0004330466088617868
max value change 0.00036428526584586507
max value change 0.0003064422000988998
max value change 0.0002577837491344326
max value change 0.00021685153535599966
max value change 0.00018241874482782805
max value change 0.00015345336976224644
max value change 0.00012908726307614415
max value change 0.00010859013127628714
max value change 9.134763803331225e-05
policy stable False
max value change 4.7865793901779625
max value change 3.2947349349497017
max value change 2.2411823866665372
max value change 1.616931343950455
max value change 1.1197864003121367
max value change 0.7204544260453076
max value change 0.443826224180043
max value change 0.270089591177225
max value change 0.16639579119885184
max value change 0.1097569388878128
max value change 0.09306955083684443
max value change 0.07883243113371918
max value change 0.06673516197616891
max value change 0.05647744756430484
max value change 0.04778890580797679
max value change 0.04043363544485601
max value change 0.03420889623009771
max value change 0.02894175601244342
max value change 0.0244852782967655
max value change 0.020714866829109724
max value change 0.01752498189301832
max value change 0.014826276628639334
max value change 0.01254313570910881
max value change 0.010611575530163009
max value change 0.008977459770164842
max value change 0.00759498609147613
max value change 0.006425404193464601
max value change 0.005435930515432119
max value change 0.004598829696703888
max value change 0.00389063733302919
max value change 0.003291502340118768
max value change 0.0027846305547996053
max value change 0.0023558140037494013
max value change 0.0019930326484427496
max value change 0.0016861174636346732
max value change 0.0014264653930240456
max value change 0.001206798198722936
max value change 0.0010209584465883381
max value change 0.0008637369118673632
max value change 0.0007307265586291578
max value change 0.0006181990110007973
max value change 0.0005230000359119913
max value change 0.00044246113805002096
max value change 0.00037432475153309497
max value change 0.00031668096374914967
max value change 0.0002679139766428307
max value change 0.00022665681694888917
max value change 0.00019175301355289776
max value change 0.00016222418821598694
max value change 0.00013724262737468962
max value change 0.00011610807808892787
max value change 9.822812404536307e-05
policy stable False
max value change 0.5643315459673204
max value change 0.19760617142037518
max value change 0.10013580858492332
max value change 0.06076229858263105
max value change 0.04080851176706801
max value change 0.02724975517776329
max value change 0.01637959485265128
max value change 0.00917172069227945
max value change 0.0049277609952014245
max value change 0.0025834353657501197
max value change 0.0013420404746966597
max value change 0.0007016294298409775
max value change 0.00037558255417025066
max value change 0.00020989058543818828
max value change 0.00013043237390775175
max value change 0.00011051700198549952
max value change 9.361574132071837e-05
policy stable False
max value change 0.04079438312567163
max value change 0.010408227162770345
max value change 0.005110707129347247
max value change 0.0032318390198042835
max value change 0.0021719229242762594
max value change 0.0013911772695109903
max value change 0.0008154469392138708
max value change 0.0004459807777266178
max value change 0.0002340408432246477
max value change 0.00012037610895276885
max value change 6.173777182993945e-05
policy stable True
In [ ]:
 
728x90