Reinforcement Learning
Frozen Lake meets Value Iteration

Frozen Lake meets Value Iteration

The Frozen Lake

This lake has a 4×4 grid of total 16 states.

# Frozen Lake Gridworld:

It is highly stochastic environment (33.33% action success, 66.66% split evenly in right angles)

It contains 4×4 grid, 16 states (0-15)

The agent gets +1 for landing in state 15 (right bottom corner) 0 otherwise

The states 5, 7, 11, and 12 are holes, agents die in holes, no penalty, just end of episode.

The state 15 is the goal, reaching it ends the episode too

The agent starts in state 0 (top left corner)

There are four actions that an agent can take: left (0), down (1), right (2), up (3).

Defining Frozen Lake with dictionary in Python

# Frozen Lake Gridworld: 
# highly stochastic environment (33.33% action success, 66.66% split evenly in right angles)
# 4x4 grid, 16 states 0-15
# +1 for landing in state 15 (top bottom corner) 0 otherwise
# states 5, 7, 11, and 12 are holes, agents die in holes, no penalty, just end of episode
# state 15 is the goal, reaching it ends the episode too
# agent starts in state 0 (top left corner)
# actions left (0), down (1), right (2), up (3)
P = {
    0: {
        0: [(0.6666666666666666, 0, 0.0, False),
            (0.3333333333333333, 4, 0.0, False)
        ],
        1: [(0.3333333333333333, 0, 0.0, False),
            (0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 1, 0.0, False)
        ],
        2: [(0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 1, 0.0, False),
            (0.3333333333333333, 0, 0.0, False)
        ],
        3: [(0.3333333333333333, 1, 0.0, False),
            (0.6666666666666666, 0, 0.0, False)
        ]
    },
    1: {
        0: [(0.3333333333333333, 1, 0.0, False),
            (0.3333333333333333, 0, 0.0, False),
            (0.3333333333333333, 5, 0.0, True)
        ],
        1: [(0.3333333333333333, 0, 0.0, False),
            (0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 2, 0.0, False)
        ],
        2: [(0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 1, 0.0, False)
        ],
        3: [(0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 1, 0.0, False),
            (0.3333333333333333, 0, 0.0, False)
        ]
    },
    2: {
        0: [(0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 1, 0.0, False),
            (0.3333333333333333, 6, 0.0, False)
        ],
        1: [(0.3333333333333333, 1, 0.0, False),
            (0.3333333333333333, 6, 0.0, False),
            (0.3333333333333333, 3, 0.0, False)
        ],
        2: [(0.3333333333333333, 6, 0.0, False),
            (0.3333333333333333, 3, 0.0, False),
            (0.3333333333333333, 2, 0.0, False)
        ],
        3: [(0.3333333333333333, 3, 0.0, False),
            (0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 1, 0.0, False)
        ]
    },
    3: {
        0: [(0.3333333333333333, 3, 0.0, False),
            (0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 7, 0.0, True)
        ],
        1: [(0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 7, 0.0, True),
            (0.3333333333333333, 3, 0.0, False)
        ],
        2: [(0.3333333333333333, 7, 0.0, True),
            (0.6666666666666666, 3, 0.0, False)
        ],
        3: [(0.6666666666666666, 3, 0.0, False),
            (0.3333333333333333, 2, 0.0, False)
        ]
    },
    4: {
        0: [(0.3333333333333333, 0, 0.0, False),
            (0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 8, 0.0, False)
        ],
        1: [(0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 5, 0.0, True)
        ],
        2: [(0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 0, 0.0, False)
        ],
        3: [(0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 0, 0.0, False),
            (0.3333333333333333, 4, 0.0, False)
        ]
    },
    5: {
        0: [(1.0, 5, 0, True)],
        1: [(1.0, 5, 0, True)],
        2: [(1.0, 5, 0, True)],
        3: [(1.0, 5, 0, True)]
    },
    6: {
        0: [(0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 10, 0.0, False)
        ],
        1: [(0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 7, 0.0, True)
        ],
        2: [(0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 7, 0.0, True),
            (0.3333333333333333, 2, 0.0, False)
        ],
        3: [(0.3333333333333333, 7, 0.0, True),
            (0.3333333333333333, 2, 0.0, False),
            (0.3333333333333333, 5, 0.0, True)
        ]
    },
    7: {
        0: [(1.0, 7, 0, True)],
        1: [(1.0, 7, 0, True)],
        2: [(1.0, 7, 0, True)],
        3: [(1.0, 7, 0, True)]
    },
    8: {
        0: [(0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 12, 0.0, True)
        ],
        1: [(0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 12, 0.0, True),
            (0.3333333333333333, 9, 0.0, False)
        ],
        2: [(0.3333333333333333, 12, 0.0, True),
            (0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 4, 0.0, False)
        ],
        3: [(0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 4, 0.0, False),
            (0.3333333333333333, 8, 0.0, False)
        ]
    },
    9: {
        0: [(0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 13, 0.0, False)
        ],
        1: [(0.3333333333333333, 8, 0.0, False),
            (0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 10, 0.0, False)
        ],
        2: [(0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 5, 0.0, True)
        ],
        3: [(0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 5, 0.0, True),
            (0.3333333333333333, 8, 0.0, False)
        ]
    },
    10: {
        0: [(0.3333333333333333, 6, 0.0, False),
            (0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 14, 0.0, False)
        ],
        1: [(0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 11, 0.0, True)
        ],
        2: [(0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 11, 0.0, True),
            (0.3333333333333333, 6, 0.0, False)
        ],
        3: [(0.3333333333333333, 11, 0.0, True),
            (0.3333333333333333, 6, 0.0, False),
            (0.3333333333333333, 9, 0.0, False)
        ]
    },
    11: {
        0: [(1.0, 11, 0, True)],
        1: [(1.0, 11, 0, True)],
        2: [(1.0, 11, 0, True)],
        3: [(1.0, 11, 0, True)]
    },
    12: {
        0: [(1.0, 12, 0, True)],
        1: [(1.0, 12, 0, True)],
        2: [(1.0, 12, 0, True)],
        3: [(1.0, 12, 0, True)]
    },
    13: {
        0: [(0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 12, 0.0, True),
            (0.3333333333333333, 13, 0.0, False)
        ],
        1: [(0.3333333333333333, 12, 0.0, True),
            (0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 14, 0.0, False)
        ],
        2: [(0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 9, 0.0, False)
        ],
        3: [(0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 9, 0.0, False),
            (0.3333333333333333, 12, 0.0, True)
        ]
    },
    14: {
        0: [(0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 14, 0.0, False)
        ],
        1: [(0.3333333333333333, 13, 0.0, False),
            (0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 15, 1.0, True)
        ],
        2: [(0.3333333333333333, 14, 0.0, False),
            (0.3333333333333333, 15, 1.0, True),
            (0.3333333333333333, 10, 0.0, False)
        ],
        3: [(0.3333333333333333, 15, 1.0, True),
            (0.3333333333333333, 10, 0.0, False),
            (0.3333333333333333, 13, 0.0, False)
        ]
    },
    15: {
        0: [(1.0, 15, 0, True)],
        1: [(1.0, 15, 0, True)],
        2: [(1.0, 15, 0, True)],
        3: [(1.0, 15, 0, True)]
    }
}

Testing a Policy

Policy defines the action to be taken in a particular state.

Let us evaluate the following policy.

We will see in how many episodes out of 10000, above policy will lead us to our goal.

Code Defining Our Policy

go_get_it_policy = [[2,2,1,0,
                     1,0,1,0,
                     2,2,1,0,
                     0,2,2,0]]

Code for Policy Evaluation i.e calculation of win percentage

def policy_testing(P,policy):
  frozen_lake_policy_reshaped = policy

  win=0
  loss = 0
  episodes = 1000

  #The following algorithm works like
  #1. Start from start position (State 0)
  #2. Take action according to the policy 
  #3. The action might take the agent to next state or leave in the current state
  #4. Repeat 2 until the agent reaches one of the Holes or reach the goal
  #5. If the agent reaches the goal print"We won": end of episode
  #6. If the agent fall in one of the holes print "We fucked up": end of episode
  win=0
  loss = 0
  episodes = 10000
  for i in range(episodes):
    #start
    direction = frozen_lake_policy_reshaped[0][0]
    #print(direction)
    entries = len(P[0][direction])
    #print("entries :",entries)
    prob_list = []
    next_states = []
    for entry in range(entries):
      #print("Probability")
      #print(P[0][direction][entry][0])

      #Listing probability of reaching next state for given direction
      prob_list.append(P[0][direction][entry][0]*100)
      #print("next state")
      #print(P[0][direction][entry][1])
    #Listing  of next state for given direction
      next_states.append(P[0][direction][entry][1])

    #Selection of next state according to probability
    new_next_state = random.choices(next_states, weights=prob_list)[0]
    #print("next_state: ",new_next_state)
    #print(P[new_next_state][direction][entry][3])

    #print("........................\n")
    while((P[new_next_state][0][0][3] == False) or (P[new_next_state][0][0][0]!=1)):
      #The while loop continues unless any terminal state is reached

      #print("current_state: ",new_next_state)
      direction = frozen_lake_policy_reshaped[0][new_next_state]
      #print("Direction: ",direction)
      entries = len(P[new_next_state][direction])
      prob_list = []
      next_states = []
      for entry in range(entries):
        #print("next state")
        #print(P[new_next_state][direction][entry][1])
        next_states.append(P[new_next_state][direction][entry][1])
        #print("Probability")
        #print(P[new_next_state][direction][entry][0])
        prob_list.append(P[new_next_state][direction][entry][0]*100)

    
      new_next_state = random.choices(next_states, weights=prob_list)[0]
      #print("next_state: ",new_next_state)
      #print(P[new_next_state][0][0][3])
      #print(P[new_next_state][0][0][0])
      #print(".....................")
      if(new_next_state == 15):
        win = win+1
        #print("we won")
        break
      elif(new_next_state==5 or new_next_state==7 or new_next_state==11 or new_next_state==12):
        loss = loss+1
        #print("we fucked up")
        break
      else:
        continue
      #print(P[new_next_state][direction][entry][3])
  
  win_percentage = (win/episodes)*100
  return win_percentage
go_get_it_policy = [[2,2,1,0,
                     1,0,1,0,
                     2,2,1,0,
                     0,2,2,0]]
print(policy_testing(P,go_get_it_policy))

3.1%

This policy has winning rate of 3.1%

Can we do better?

One way to come up with the best policy is the value iteration or Greedily greedifying policies.

Let us start with the always left policy.

Always Left Policy
always_left_policy = [[0,0,0,0,
                    0,0,0,0,
                    0,0,0,0,
                    0,0,0,0]]
print(policy_testing(P,all_left_policy))

This policy has win rate of 0%

We will start with the value of all states being 0.

We will update the value for all the actions iteratively. 

Value = Sum of all(Probability of reaching next state on taking particular action x (max value of next state + reward))

Let us manually calculate the values

Iteration I

For state 0:

For action 0 (i.e taking Left)

V(s’=0/a=0) = 0.66(0+0) = 0

V(s’=4/a=0) = 0.33(0+0) = 0

V(a=0) = 0

For action 1 (i.e going down)

V(s’=0/a=1) = 0.33(0+0) = 0

V(s’=1/a=1) = 0.33(0+0) = 0

V(s’=4/a=1) = 0.33(0+0) = 0

V(a=1) = 0

For action 2 (i.e taking Right)

V(s’=0/a=2) = 0.33(0+0) = 0

V(s’=1/a=2) = 0.33(0+0) = 0

V(s’=4/a=2) = 0.33(0+0) = 0

V(a=2) = 0

For action 3 (i.e going Up)

V(s’=0/a=3) = 0.66(0+0) = 0

V(s’=1/a=3) = 0.33(0+0) = 0

V(a=3) = 0

In the first iteration we get value = 0 in all states for all actions except for state 14.

For state 14:

For action 0 (i.e taking Left)

V(s’=13/a=0) = 0.33(0+0) = 0

V(s’=10/a=0) = 0.33(0+0) = 0

V(s’=14/a=0) = 0.33(0+0) = 0

V(a=0) = 0

For action 1 (i.e going down)

V(s’=14/a=1) = 0.33(0+0) = 0

V(s’=13/a=1) = 0.33(0+0) = 0

V(s’=15/a=1) = 0.33(0+1) = 0.33

V(a=1) = 0.33

For action 2 (i.e taking Right)

V(s’=15/a=2) = 0.33(0+1) = 0.33

V(s’=10/a=2) = 0.33(0+0) = 0

V(s’=14/a=2) = 0.33(0+0) = 0

V(a=2) = 0.33

For action 3 (i.e going Up)

V(s’=10/a=3) = 0.33(0+0) = 0

V(s’=13/a=3) = 0.33(0+0) = 0

V(s’=15/a=3) = 0.33(0+1) = 0.33

V(a=3) = 0.33

After first iteration the frozen lake with value of all actions look like following

Iteration II

For state 0:

For action 0 (i.e taking Left)

V(s’=0/a=0) = 0.66(0+0) = 0

V(s’=4/a=0) = 0.33(0+0) = 0

V(a=0) = 0

For action 1 (i.e going down)

V(s’=0/a=1) = 0.33(0+0) = 0

V(s’=1/a=1) = 0.33(0+0) = 0

V(s’=4/a=1) = 0.33(0+0) = 0

V(a=1) = 0

For action 2 (i.e taking Right)

V(s’=0/a=2) = 0.33(0+0) = 0

V(s’=1/a=2) = 0.33(0+0) = 0

V(s’=4/a=2) = 0.33(0+0) = 0

V(a=2) = 0

For action 3 (i.e going Up)

V(s’=0/a=3) = 0.66(0+0) = 0

V(s’=1/a=3) = 0.33(0+0) = 0

V(a=3) = 0

In the second iteration we get value = 0 in all states for all actions except for state 14 and the states that are adjacent to it (like: 13 and 10).

For state 10:

For action 0 (i.e taking Left)

V(s’=9/a=0) = 0.33(0+0) = 0

V(s’=6/a=0) = 0.33(0+0) = 0

V(s’=14/a=0) = 0.33(0.33+0) = 0.1111

V(a=0) = 0.11

Here the highlighted value is the maximum value of the next state (i.e state 14) among possible actions.

For action 1 (i.e going down)

V(s’=14/a=1) = 0.33(0.33+0) = 0.111

V(s’=9/a=1) = 0.33(0+0) = 0

V(s’=11/a=1) = 0.33(0+0) = 0

V(a=1) = 0.11

For action 2 (i.e taking Right)

V(s’=11/a=2) = 0.33(0+0) = 0

V(s’=6/a=2) = 0.33(0+0) = 0

V(s’=14/a=2) = 0.33(0.33+0) = 0.11

V(a=2) = 0.11

For action 3 (i.e going Up)

V(s’=6/a=3) = 0.33(0+0) = 0

V(s’=9/a=3) = 0.33(0+0) = 0

V(s’=9/a=3) = 0.33(0+0) = 0

V(a=0) = 0

For state 13:

For action 0 (i.e taking Left)

V(s’=12/a=0) = 0.33(0+0) = 0

V(s’=9/a=0) = 0.33(0+0) = 0

V(s’=13/a=0) = 0.33(0+0) = 0

V(a=0) = 0

For action 1 (i.e going down)

V(s’=12/a=1) = 0.33(0+0) = 0

V(s’=13/a=1) = 0.33(0+0) = 0

V(s’=14/a=1) = 0.33(0.33+0) = 0.111

V(a=1) = 0.11

For action 2 (i.e taking Right)

V(s’=14/a=2) = 0.33(0.33+0) = 0.11

V(s’=9/a=2) = 0.33(0+0) = 0

V(s’=13/a=2) = 0.33(0+0) = 0

V(a=2) = 0.22

For action 3 (i.e going Up)

V(s’=9/a=3) = 0.33(0+0) = 0

V(s’=12/a=3) = 0.33(0+0) = 0

V(s’=14/a=3) = 0.33(0.33+0) = 0.111

V(a=3) = 0.11

For state 14:

For action 0 (i.e taking Left)

V(s’=13/a=0) = 0.33(0+0) = 0

V(s’=10/a=0) = 0.33(0+0) = 0

V(s’=14/a=0) = 0.33(0.33+0) = 0.1111

Here the highlighted value is the maximum value of the next state (i.e state 14) among possible actions.

V(a=0) = 0.11

For action 1 (i.e going down)

V(s’=14/a=1) = 0.33(0.33+0) = 0.111

V(s’=13/a=1) = 0.33(0+0) = 0

V(s’=15/a=1) = 0.33(0+1) = 0.33

V(a=1) = 0.44

For action 2 (i.e taking Right)

V(s’=15/a=2) = 0.33(1+0) = 0.33

V(s’=10/a=2) = 0.33(0+0) = 0

V(s’=14/a=2) = 0.33(0.33+0) = 0.11

V(a=2) = 0.44

For action 3 (i.e going Up)

V(s’=10/a=3) = 0.33(0+0) = 0

V(s’=13/a=3) = 0.33(0+0) = 0

V(s’=15/a=3) = 0.33(1+0) = 0.33

V(a=3) = 0.33

After second iteration the frozen lake with value of all actions look like following

If we keep going on like this after a few hundred iterations we will get the values of all actions of all states in the following form.

If we develop our policy according to above values of actions in the given states.

Our policy for this frozen lake will look something like this.

Code for Value Iteration

size = len(P)
print(size)
#this array will keep value of all states for all actions
value_array4D = np.zeros([4,size])

for i in range(300):
  #here 300 in no of iterations:
  #Assuming after 300 iterations it will converge

  #this array will keep value of all states for all actions for each iteration
  #this array will be reset after each iteration
  temp_array = np.zeros([4,size])

  for j in range(len(P)-1):
    #range is one less than total number of states as we are not calculating any value for last state
    j= j
    #print("current state",j)
    #print(P[j][2])
    #print(P[j][0][0][0])
    if(P[j][0][0][0]!=1):
      #the condition in if will exclude holes
      #print(P[j][0][0][3])
      sums = np.zeros(4)
      for direction in range(4):
        #entries is possiblie new states that an agent can reach ffrom current state by taking particular action
        entries = len(P[j][direction])
        #print("entries",entries)
        for k in range(entries):
          #print("Probability")
          probability = P[j][direction][k][0]
          #print(probability)
          #print("next state")
          next_state = P[j][direction][k][1]
          #print(next_state)
          #print("value of next state")

          #get max value of all the action-value of next state
          max_value = np.max(value_array4D[0:,next_state])
          #print(max_value)
          #print("max value",max_value)
          #print("Reward")
          #check the reward
          reward = P[j][direction][k][2]
          #print(reward)

          #The value iteration equation
          sums[direction] = sums[direction] + probability*(max_value+reward)
          temp_array[direction][j] = sums[direction]
        #print("sums",sums)



     # print("..........\n\n")

     
  #print(temp_array.T)
  #Copy the value of temp_array in value_array4D
  value_array4D = temp_array
  #print("..........\n\n")

Printing value of all actions for all states

value_array4D.T

Making policy by reading the final value of value_array4D

frozen_lake = np.chararray([4,4],unicode = True)
frozen_lake_policy = np.zeros([4,4])
count = 0
for i in range(4):
  for j in range(4):
    if(P[count][0][0][0]!=1 and count<15):
      max_value = np.argmax(value_array4D.T[count])
      print(max_value)
      frozen_lake_policy[i][j]=max_value
      if(max_value == 0):
        frozen_lake[i][j]="<"
        print("<")
      elif (max_value == 1):
        frozen_lake[i][j]="v"
        print("v")
      elif (max_value == 2):
        frozen_lake[i][j]=">"
        print(">")
      else:
        frozen_lake[i][j]="^"
        print("^")
    elif(P[count][0][0][0]==1 and count<15):
      frozen_lake[i][j] = 'H'
    else:
      frozen_lake[i][j] = 'End'


    count = count+1

The optimum policy of frozen Lake

Evaluation of above policy

print(policy_testing(P,frozen_lake_policy_reshaped))

This optimum policy has win rate of nearly 82%