import%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20base64%0A%20%20%20%20import%20io%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20torch%0A%20%20%20%20import%20torch.nn%20as%20nn%0A%20%20%20%20import%20torch.nn.functional%20as%20F%0A%20%20%20%20import%20torch.distributions%20as%20D%0A%20%20%20%20import%20torch.optim%20as%20optim%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20gymnasium%20as%20gym%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20PIL%20import%20Image%0A%0A%20%20%20%20return%20D%2C%20F%2C%20Image%2C%20base64%2C%20gym%2C%20io%2C%20mo%2C%20nn%2C%20np%2C%20plt%2C%20torch%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Solution%20to%20Acrobot%20Using%20A2C%0A%0A%20%20%20%20We%20aim%20to%20solve%20the%20%60%22Acrobot-v1%22%60%20exercise%20from%20%60gymnasium%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Define%20user%20parameters%20for%20fine-tuning.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20neurons_per_layer%20%3D%2064%0A%20%20%20%20gamma%20%3D%200.99%0A%20%20%20%20training_episodes%20%3D%201000%20%23%20Maximum%20number%20of%20training%20episodes%0A%20%20%20%20iterations_between_plots%20%3D%2020%20%23%20Number%20of%20episodes%20to%20simulate%20before%20adding%20to%20plot%0A%20%20%20%20reward_threshold%20%3D%20-90%20%23%20Still%20solvable%20up%20to%20-80%0A%20%20%20%20moving_avg_window%20%3D%2030%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20gamma%2C%0A%20%20%20%20%20%20%20%20iterations_between_plots%2C%0A%20%20%20%20%20%20%20%20moving_avg_window%2C%0A%20%20%20%20%20%20%20%20neurons_per_layer%2C%0A%20%20%20%20%20%20%20%20reward_threshold%2C%0A%20%20%20%20%20%20%20%20training_episodes%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Initialise%20the%20%60gymnasium%60%20environment.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gym)%3A%0A%20%20%20%20env%20%3D%20gym.make(%22Acrobot-v1%22%2C%20render_mode%3D%22rgb_array%22)%0A%20%20%20%20state_dim%20%3D%20env.observation_space.shape%5B0%5D%0A%20%20%20%20action_dim%20%3D%20env.action_space.n%0A%20%20%20%20return%20action_dim%2C%20env%2C%20state_dim%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Set%20up%20the%20Actor%20and%20Critic%20networks.%0A%20%20%20%20Both%20have%20the%20same%20input%20layers%20and%202%20hidden%20layers.%0A%20%20%20%20The%20actor%20outputs%203%20logits%20for%20each%20of%20the%20possible%20actions%2C%20%24%5Ctau%20%5Cin%20%5C%7B%20-1%2C%200%2C%201%5C%7D%24.%0A%20%20%20%20The%20Critic%20outputs%20a%20single%20unconstrained%20estimate%20of%20%24V(s)%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(F%2C%20action_dim%2C%20neurons_per_layer%2C%20nn%2C%20state_dim)%3A%0A%20%20%20%20class%20Actor(nn.Module)%3A%0A%20%20%20%20%20%20%20%20def%20__init__(self%2C%20state_dim%20%3D%20state_dim%2C%20action_dim%20%3D%20action_dim)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20super().__init__()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc1%20%3D%20nn.Linear(state_dim%2C%20neurons_per_layer)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc2%20%3D%20nn.Linear(neurons_per_layer%2C%20neurons_per_layer)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc3%20%3D%20nn.Linear(neurons_per_layer%2C%20action_dim)%0A%0A%20%20%20%20%20%20%20%20def%20forward(self%2C%20state)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20We%20use%20ReLU(x)%3Dmax(x%2C0)%20activation%20for%20the%20hidden%20layers%0A%20%20%20%20%20%20%20%20%20%20%20%20x%20%3D%20F.relu(self.fc1(state))%0A%20%20%20%20%20%20%20%20%20%20%20%20x%20%3D%20F.relu(self.fc2(x))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20logits%20%3D%20self.fc3(x)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20logits%0A%0A%20%20%20%20return%20(Actor%2C)%0A%0A%0A%40app.cell%0Adef%20_(F%2C%20neurons_per_layer%2C%20nn%2C%20state_dim)%3A%0A%20%20%20%20class%20Critic(nn.Module)%3A%0A%20%20%20%20%20%20%20%20def%20__init__(self%2C%20state_dim%20%3D%20state_dim)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20super().__init__()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc1%20%3D%20nn.Linear(state_dim%2C%20neurons_per_layer)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc2%20%3D%20nn.Linear(neurons_per_layer%2C%20neurons_per_layer)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.fc3%20%3D%20nn.Linear(neurons_per_layer%2C%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Network%20output%20is%20V(s)%0A%0A%20%20%20%20%20%20%20%20def%20forward(self%2C%20state)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20We%20use%20ReLU(x)%3Dmax(x%2C0)%20activation%20for%20the%20hidden%20layers%0A%20%20%20%20%20%20%20%20%20%20%20%20x%20%3D%20F.relu(self.fc1(state))%0A%20%20%20%20%20%20%20%20%20%20%20%20x%20%3D%20F.relu(self.fc2(x))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20No%20activation%20on%20the%20final%20later%20because%20V_s%20can%20be%20negative%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20self.fc3(x)%0A%0A%20%20%20%20return%20(Critic%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20To%20select%20an%20action%2C%20we%0A%20%20%20%201.%20Extract%20the%20state%0A%20%20%20%202.%20Evaluate%20the%20logits%20using%20the%20actor's%20network%0A%20%20%20%203.%20Create%20a%20categorical%20distibution%20from%20the%20logits%0A%20%20%20%204.%20Sample%20an%20action%20from%20the%20distribution%0A%20%20%20%205.%20Compute%20%24%5Cln%20%5Cpi_%5Ctheta(a%5Cvert%7B%7Ds)%24%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(D%2C%20nn%2C%20torch)%3A%0A%20%20%20%20def%20select_action(state%2C%20actor%20%3A%20nn.Module)%3A%0A%20%20%20%20%20%20%20%20%23%20Convert%20state%20to%20a%20tensor%20in%20pytorch%20format%0A%20%20%20%20%20%20%20%20state%20%3D%20torch.FloatTensor(state).unsqueeze(0)%0A%0A%20%20%20%20%20%20%20%20%23%20Get%20the%20current%20distribution%0A%20%20%20%20%20%20%20%20logits%20%3D%20actor(state)%0A%20%20%20%20%20%20%20%20dist%20%3D%20D.Categorical(logits%3Dlogits)%0A%0A%20%20%20%20%20%20%20%20%23%20Sample%20an%20action%0A%20%20%20%20%20%20%20%20action%20%3D%20dist.sample()%0A%0A%20%20%20%20%20%20%20%20%23%20Compute%20%24%5Cln%20%5Cpi_%5Ctheta(a%5Cvert%7B%7Ds)%24%0A%20%20%20%20%20%20%20%20log_prob%20%3D%20dist.log_prob(action)%0A%0A%20%20%20%20%20%20%20%20return%20action.item()%2C%20log_prob%0A%0A%20%20%20%20return%20(select_action%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Set%20up%20a%20helper%20function%20that%20retrieves%20the%20critic's%20estimate%20of%20%24V(s)%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nn%2C%20torch)%3A%0A%20%20%20%20def%20estimate_value(state%2C%20critic%20%3A%20nn.Module)%3A%0A%20%20%20%20%20%20%20%20state%20%3D%20torch.FloatTensor(state).unsqueeze(0)%0A%20%20%20%20%20%20%20%20return%20critic(state)%0A%0A%20%20%20%20return%20(estimate_value%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Use%20the%20Adam%20optimiser%20for%20the%20actor%20and%20critic.%0A%20%20%20%20It%20is%20important%20that%20the%20critic%20learn%20faster%20than%20the%20actor.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Actor%2C%20Critic%2C%20torch)%3A%0A%20%20%20%20actor%20%3D%20Actor()%0A%20%20%20%20critic%20%3D%20Critic()%0A%0A%20%20%20%20actor_optimiser%20%3D%20torch.optim.Adam(actor.parameters()%2C%20lr%3D1e-4)%0A%20%20%20%20critic_optimiser%20%3D%20torch.optim.Adam(critic.parameters()%2C%20lr%3D1e-3)%0A%20%20%20%20return%20actor%2C%20actor_optimiser%2C%20critic%2C%20critic_optimiser%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Define%20the%20logic%20necessary%20to%20execute%20a%20training%20episode.%0A%20%20%20%20At%20each%20time%20step%20we%0A%20%20%20%201.%20Select%20an%20action%20according%20to%20the%20actor's%20current%20policy%20%24%5Ctheta%24%0A%20%20%20%201.%20Step%20the%20physics%20simulation%20forward%20in%20time%2C%20thereby%20retrieving%20the%20reward%20%24r%24%20and%20next%20state%20%24s'%24%0A%20%20%20%201.%20Get%20the%20critic's%20estimates%20for%20%24V(s)%24%20and%20%24V(s')%24%0A%20%20%20%201.%20Calculate%20the%20target%20%24y%20%3D%20r%20%2B%20%5Cgamma%20V(s')%24%2C%20with%20the%20caveat%20that%20%24V(s')%24%20is%20set%20to%20zero%20only%20if%20the%20task%20was%20failed%2C%20not%20truncated%0A%20%20%20%201.%20Compute%20the%20critic's%20loss%20using%20the%20mean-squared%20error%3A%20%24%5Cmathcal%7BL%7D_%5Ctext%7Bcritic%7D%20%3D%20%5Cfrac%7B1%7D%7B2%7D%20A(s%2Ca)%5E2%24%2C%20where%20the%20advantage%20%24A(s%2Ca)%20%3D%20y%20-%20V(s)%24%0A%20%20%20%201.%20Perform%20gradient%20descent%20on%20the%20critic's%20parameters%20to%20minimise%20%24%5Cmathcal%7BL%7D_%5Ctext%7Bcritic%7D%24%0A%20%20%20%201.%20Compute%20the%20loss%20of%20the%20actor%20using%20the%20advantage%2C%20which%20we%20explicitly%20treat%20as%20a%20constant%20and%20do%20not%20perform%20gradient%20descent%20on.%20%24%5Cmathcal%7BL%7D_%7B%5Ctext%7Bactor%7D%7D%20%3D%20-%5Cln%20%5Cpi_%5Ctheta(a%20%5Cmid%20s)%20%5Ccdot%20A(s%2C%20a)%24%0A%20%20%20%201.%20Perform%20gradient%20descent%20on%20the%20actor's%20parameters%20to%20minimise%20%24%5Cmathcal%7BL%7D_%5Ctext%7Bactor%7D%24%0A%20%20%20%201.%20Transition%20to%20the%20next%20state%20and%20increment%20the%20total%20reward%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20F%2C%0A%20%20%20%20actor%2C%0A%20%20%20%20actor_optimiser%2C%0A%20%20%20%20critic%2C%0A%20%20%20%20critic_optimiser%2C%0A%20%20%20%20env%2C%0A%20%20%20%20estimate_value%2C%0A%20%20%20%20gamma%2C%0A%20%20%20%20select_action%2C%0A)%3A%0A%20%20%20%20def%20training_episode()%3A%0A%20%20%20%20%20%20%20%20state%2C%20_%20%3D%20env.reset()%0A%0A%20%20%20%20%20%20%20%20done%20%3D%20False%0A%20%20%20%20%20%20%20%20total_reward%20%3D%200%0A%0A%20%20%20%20%20%20%20%20while%20not%20done%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20action%2C%20log_prob%20%3D%20select_action(state%2C%20actor)%0A%20%20%20%20%20%20%20%20%20%20%20%20next_state%2C%20reward%2C%20terminated%2C%20truncated%2C%20_%20%3D%20env.step(action)%0A%20%20%20%20%20%20%20%20%20%20%20%20done%20%3D%20terminated%20or%20truncated%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20cur_val%20%3D%20estimate_value(state%2C%20critic)%0A%20%20%20%20%20%20%20%20%20%20%20%20next_val%20%3D%20estimate_value(next_state%2C%20critic)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20target%20%3D%20reward%20%2B%20gamma%20*%20next_val.detach()%20*%20(1%20-%20terminated)%0A%20%20%20%20%20%20%20%20%20%20%20%20critic_loss%20%3D%20F.mse_loss(cur_val%2C%20target)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20critic_optimiser.zero_grad()%0A%20%20%20%20%20%20%20%20%20%20%20%20critic_loss.backward()%0A%20%20%20%20%20%20%20%20%20%20%20%20critic_optimiser.step()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20advantage%20%3D%20target%20-%20cur_val%0A%20%20%20%20%20%20%20%20%20%20%20%20actor_loss%20%3D%20-log_prob%20*%20advantage.detach()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20actor_optimiser.zero_grad()%0A%20%20%20%20%20%20%20%20%20%20%20%20actor_loss.backward()%0A%20%20%20%20%20%20%20%20%20%20%20%20actor_optimiser.step()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20state%20%3D%20next_state%0A%20%20%20%20%20%20%20%20%20%20%20%20total_reward%20%2B%3D%20reward%0A%0A%20%20%20%20%20%20%20%20return%20total_reward%0A%0A%20%20%20%20return%20(training_episode%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Define%20a%20helper%20function%20that%20plots%20the%20current%20training%20progress.%0A%20%20%20%20In%20the%20notebook%20it%20is%20called%20repeatedly%20during%20training%20and%20each%20call%20overwrites%20the%20previous%20figure%20(the%20marimo%20equivalent%20of%20%60clear_output(wait%3DTrue)%60).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(moving_avg_window%2C%20plt)%3A%0A%20%20%20%20def%20plot_progress(episodes%2C%20rewards%2C%20moving_avg)%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(8%2C%204))%0A%0A%20%20%20%20%20%20%20%20%23%20Scatter%20plot%20of%20individual%20episode%20rewards%0A%20%20%20%20%20%20%20%20ax.scatter(episodes%2C%20rewards%2C%20color%3D'dodgerblue'%2C%20alpha%3D0.6%2C%20label%3D'Episode%20Reward')%0A%0A%20%20%20%20%20%20%20%20ax.plot(episodes%2C%20moving_avg%2C%20color%3D'red'%2C%20linewidth%3D2%2C%20label%3Df'Average%20of%20last%20%7Bmoving_avg_window%7D%20episodes')%0A%0A%20%20%20%20%20%20%20%20ax.set_xlabel('Episode%20%23')%0A%20%20%20%20%20%20%20%20ax.set_ylabel('Total%20Reward')%0A%20%20%20%20%20%20%20%20ax.set_title('Actor-Critic%20Training%20Progress%20on%20Acrobot-v1')%0A%20%20%20%20%20%20%20%20ax.grid(True%2C%20linestyle%3D'--'%2C%20alpha%3D0.5)%0A%20%20%20%20%20%20%20%20ax.legend()%0A%0A%20%20%20%20%20%20%20%20%23%20Stop%20pyplot%20accumulating%20one%20open%20figure%20per%20call%3B%20the%20caller%20displays%20the%20returned%20figure%0A%20%20%20%20%20%20%20%20plt.close(fig)%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20(plot_progress%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20To%20evaluate%20a%20policy%2C%20we%20select%20an%20action%20greedily%20by%20simply%20choosing%20the%20action%20that%20the%20actor%20believes%20will%20result%20in%20the%20highest%20reward.%0A%0A%20%20%20%20To%20conduct%20a%20greedy%20episode%2C%20we%20do%20not%20need%20to%20calculate%20loss%20functions%20or%20conduct%20any%20training.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(actor%2C%20env%2C%20nn%2C%20torch)%3A%0A%20%20%20%20def%20greedy_action(state%2C%20actor%20%3A%20nn.Module)%3A%0A%20%20%20%20%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20state%20%3D%20torch.FloatTensor(state).unsqueeze(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20logits%20%3D%20actor(state)%0A%20%20%20%20%20%20%20%20return%20torch.argmax(logits%2C%20dim%3D-1).item()%0A%0A%20%20%20%20def%20greedy_episode()%3A%0A%20%20%20%20%20%20%20%20state%2C%20_%20%3D%20env.reset()%0A%20%20%20%20%20%20%20%20done%20%3D%20False%0A%20%20%20%20%20%20%20%20total_reward%20%3D%200%0A%0A%20%20%20%20%20%20%20%20while%20not%20done%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20action%20%3D%20greedy_action(state%2C%20actor)%0A%20%20%20%20%20%20%20%20%20%20%20%20next_state%2C%20reward%2C%20terminated%2C%20truncated%2C%20_%20%3D%20env.step(action)%0A%20%20%20%20%20%20%20%20%20%20%20%20done%20%3D%20terminated%20or%20truncated%0A%20%20%20%20%20%20%20%20%20%20%20%20state%20%3D%20next_state%0A%20%20%20%20%20%20%20%20%20%20%20%20total_reward%20%2B%3D%20reward%0A%0A%20%20%20%20%20%20%20%20return%20total_reward%0A%0A%20%20%20%20return%20greedy_action%2C%20greedy_episode%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Training%20Loop%0A%20%20%20%20We%20store%20the%20rewards%20for%20each%20episode%2C%20as%20well%20as%20compute%20a%20moving%20average%20of%20the%20last%20%60moving_avg_window%60%20rewards.%0A%20%20%20%20Once%20the%20reward%20and%20the%20moving%20average%20have%20exceeded%20%60reward_threshold%60%2C%20we%20enter%20an%20evaluation%20step.%0A%20%20%20%20100%20greedy%20episodes%20are%20executed%20and%20the%20mean%20reward%20is%20calculated.%0A%20%20%20%20If%20this%20value%20also%20exceeds%20the%20reward%20threshold%2C%20we%20have%20found%20a%20suitable%20solution%20and%20can%20stop%20training.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20greedy_episode%2C%0A%20%20%20%20iterations_between_plots%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20moving_avg_window%2C%0A%20%20%20%20np%2C%0A%20%20%20%20plot_progress%2C%0A%20%20%20%20reward_threshold%2C%0A%20%20%20%20training_episode%2C%0A%20%20%20%20training_episodes%2C%0A)%3A%0A%20%20%20%20episode_nums%20%3D%20%5B%5D%0A%20%20%20%20episode_rewards%20%3D%20%5B%5D%0A%20%20%20%20moving_avg%20%3D%20%5B%5D%0A%0A%20%20%20%20for%20episode%20in%20range(1%2C%20training_episodes%2B1)%3A%0A%20%20%20%20%20%20%20%20reward%20%3D%20training_episode()%0A%0A%20%20%20%20%20%20%20%20episode_nums.append(episode)%0A%20%20%20%20%20%20%20%20episode_rewards.append(reward)%0A%0A%20%20%20%20%20%20%20%20goback%20%3D%20max(0%2C%20episode%20-%20moving_avg_window)%0A%20%20%20%20%20%20%20%20avg%20%3D%20np.mean(episode_rewards%5Bgoback%3Aepisode%2B1%5D)%0A%20%20%20%20%20%20%20%20moving_avg.append(avg)%0A%0A%20%20%20%20%20%20%20%20if%20episode%20%25%20iterations_between_plots%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Live%20progress%3A%20overwrite%20this%20cell's%20output%20with%20the%20latest%20figure%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.output.replace(plot_progress(episode_nums%2C%20episode_rewards%2C%20moving_avg))%0A%0A%20%20%20%20%20%20%20%20if%20avg%20%3E%3D%20reward_threshold%20and%20reward%20%3E%3D%20reward_threshold%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20score%20%3D%20np.mean(%5Bgreedy_episode()%20for%20_%20in%20range(100)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20score%20%3E%3D%20reward_threshold%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20plot_progress(episode_nums%2C%20episode_rewards%2C%20moving_avg)%0A%20%20%20%20return%20episode%2C%20score%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Now%20we%20have%20found%20a%20suitable%20set%20of%20actor%20network%20parameters.%0A%20%20%20%20To%20visualise%20the%20actor's%20strategy%2C%20we%20create%20an%20animation%20of%20one%20episode.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Image%2C%20base64%2C%20env%2C%20greedy_action%2C%20io%2C%20mo)%3A%0A%20%20%20%20def%20record_episode_frames(actor)%3A%0A%20%20%20%20%20%20%20%20state%2C%20_%20%3D%20env.reset()%0A%0A%20%20%20%20%20%20%20%20frames%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20done%20%3D%20False%0A%0A%20%20%20%20%20%20%20%20frames.append(env.render())%0A%0A%20%20%20%20%20%20%20%20while%20not%20done%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Select%20action%20deterministically%0A%20%20%20%20%20%20%20%20%20%20%20%20action%20%3D%20greedy_action(state%2C%20actor)%0A%20%20%20%20%20%20%20%20%20%20%20%20next_state%2C%20_%2C%20terminated%2C%20truncated%2C%20_%20%3D%20env.step(action)%0A%20%20%20%20%20%20%20%20%20%20%20%20done%20%3D%20terminated%20or%20truncated%0A%20%20%20%20%20%20%20%20%20%20%20%20state%20%3D%20next_state%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20frames.append(env.render())%0A%0A%20%20%20%20%20%20%20%20env.close()%0A%20%20%20%20%20%20%20%20return%20frames%0A%0A%0A%20%20%20%20def%20render_animation(frames%2C%20interval%3D80)%3A%0A%20%20%20%20%20%20%20%20%23%20Encode%20the%20frames%20as%20a%20looping%20GIF.%20Unlike%20matplotlib's%20%60to_jshtml%60%2C%20this%20needs%20no%0A%20%20%20%20%20%20%20%20%23%20JavaScript%2C%20so%20it%20also%20displays%20in%20a%20static%20HTML%20export%20of%20the%20notebook.%0A%20%20%20%20%20%20%20%20images%20%3D%20%5BImage.fromarray(frame)%20for%20frame%20in%20frames%5D%0A%20%20%20%20%20%20%20%20gif%20%3D%20io.BytesIO()%0A%20%20%20%20%20%20%20%20images%5B0%5D.save(gif%2C%20format%3D%22GIF%22%2C%20save_all%3DTrue%2C%20append_images%3Dimages%5B1%3A%5D%2C%20duration%3Dinterval%2C%20loop%3D0)%0A%20%20%20%20%20%20%20%20gif_b64%20%3D%20base64.b64encode(gif.getvalue()).decode()%0A%20%20%20%20%20%20%20%20return%20mo.Html(f'%3Cimg%20src%3D%22data%3Aimage%2Fgif%3Bbase64%2C%7Bgif_b64%7D%22%20alt%3D%22Greedy%20episode%20of%20the%20trained%20actor%22%3E')%0A%0A%20%20%20%20return%20record_episode_frames%2C%20render_animation%0A%0A%0A%40app.cell%0Adef%20_(actor%2C%20episode%2C%20record_episode_frames%2C%20score)%3A%0A%20%20%20%20%23%20Depending%20on%20%60episode%60%20and%20%60score%60%20makes%20this%20cell%20run%20after%20the%20training%20loop%0A%20%20%20%20print(f%22Solved%20at%20episode%20%7Bepisode%7D%20with%20a%20score%20of%20%7Bscore%7D%22)%0A%0A%20%20%20%20frames%20%3D%20record_episode_frames(actor)%0A%20%20%20%20return%20(frames%2C)%0A%0A%0A%40app.cell%0Adef%20_(frames%2C%20render_animation)%3A%0A%20%20%20%20render_animation(frames)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20solution%20to%20the%20acrobot%20pole%20is%20hence%20completed%20with%20A2C.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
afe179094c3b662b7af7f6ec41371b08