simonbb 0.0.4__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simonbb
3
- Version: 0.0.4
4
- Summary: A small package to facilitate teaching AI courses
3
+ Version: 0.0.6
4
+ Summary: A small package to facilitate teaching AI courses at business schools
5
5
  Project-URL: Homepage, https://simonbb.com
6
6
  Author-email: Huaxia Rui <huaxia.rui@simon.rochester.edu>
7
7
  License-Expression: MIT
@@ -20,7 +20,9 @@ Requires-Dist: matplotlib~=3.10.8
20
20
  Requires-Dist: numpy~=2.4.0
21
21
  Requires-Dist: pytorch-ignite~=0.5.4
22
22
  Requires-Dist: pyyaml~=6.0.3
23
+ Requires-Dist: ray~=2.56.0
23
24
  Requires-Dist: sentence-transformers~=5.3.0
25
+ Requires-Dist: stable-baselines3~=2.9.0
24
26
  Requires-Dist: torchvision~=0.24.1
25
27
  Requires-Dist: torch~=2.9.1
26
28
  Requires-Dist: transformers~=4.57.3
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "simonbb"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  authors = [
9
9
  { name="Huaxia Rui", email="huaxia.rui@simon.rochester.edu" },
10
10
  ]
11
- description = "A small package to facilitate teaching AI courses"
11
+ description = "A small package to facilitate teaching AI courses at business schools"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
14
14
  classifiers = [
@@ -32,6 +32,8 @@ dependencies = [
32
32
  "sentence-transformers~=5.3.0",
33
33
  "gymnasium~=1.3.0",
34
34
  "pytorch-ignite~=0.5.4",
35
+ "stable_baselines3~=2.9.0",
36
+ "ray~=2.56.0",
35
37
  "pyyaml~=6.0.3"
36
38
  ]
37
39
  [project.urls]
@@ -61,10 +61,7 @@ class EpsilonTracker:
61
61
  """
62
62
  Updates epsilon according to linear schedule
63
63
  """
64
- def __init__(self, selector: EpsilonGreedyActionSelector,
65
- eps_start: tt.Union[int, float],
66
- eps_final: tt.Union[int, float],
67
- eps_frames: int):
64
+ def __init__(self, selector: EpsilonGreedyActionSelector, eps_start: tt.Union[int, float], eps_final: tt.Union[int, float], eps_frames: int):
68
65
  self.selector = selector
69
66
  self.eps_start = eps_start
70
67
  self.eps_final = eps_final
@@ -0,0 +1,312 @@
1
+ import random
2
+ import argparse
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import dataclasses
7
+ from datetime import timedelta
8
+ import typing as tt
9
+ import os
10
+ import glob
11
+
12
+ from ignite.engine import Engine
13
+ from ignite.metrics import RunningAverage
14
+ from ignite.contrib.handlers import tensorboard_logger as tb_logger
15
+ from ray import tune
16
+
17
+ from .experience import ExperienceFirstLast, ExperienceSourceFirstLast, ExperienceReplayBuffer
18
+ from .ignite import EndOfEpisodeHandler, EpisodeEvents, EpisodeFPSHandler, PeriodicEvents, PeriodEvents
19
+
20
+ #%%
21
+
22
+ SEED = 123
23
+
24
+ @dataclasses.dataclass
25
+ class Hyperparams:
26
+ env_name: str
27
+ stop_reward: float
28
+ run_name: str
29
+ replay_size: int
30
+ replay_initial: int
31
+ target_net_sync: int
32
+ epsilon_frames: int
33
+
34
+ learning_rate: float = 0.0001
35
+ batch_size: int = 32
36
+ gamma: float = 0.99
37
+ epsilon_start: float = 1.0
38
+ epsilon_final: float = 0.1
39
+
40
+ tuner_mode: bool = False
41
+ episodes_to_solve: int = 500
42
+
43
+
44
+ GAME_PARAMS = {
45
+ 'pong': Hyperparams(
46
+ env_name="PongNoFrameskip-v4",
47
+ stop_reward=18.0,
48
+ run_name="pong",
49
+ replay_size=100_000,
50
+ replay_initial=10_000,
51
+ target_net_sync=1000,
52
+ epsilon_frames=100_000,
53
+ epsilon_final=0.02,
54
+ ),
55
+ 'breakout-small': Hyperparams(
56
+ env_name="BreakoutNoFrameskip-v4",
57
+ stop_reward=500.0,
58
+ run_name="breakout-small",
59
+ replay_size=300_000,
60
+ replay_initial=20_000,
61
+ target_net_sync=1000,
62
+ epsilon_frames=1_000_000,
63
+ batch_size=64,
64
+ ),
65
+ 'breakout': Hyperparams(
66
+ env_name="BreakoutNoFrameskip-v4",
67
+ stop_reward=500.0,
68
+ run_name='breakout',
69
+ replay_size=1_000_000,
70
+ replay_initial=50_000,
71
+ target_net_sync=10_000,
72
+ epsilon_frames=10_000_000,
73
+ learning_rate=0.00025,
74
+ ),
75
+ 'invaders': Hyperparams(
76
+ env_name="SpaceInvadersNoFrameskip-v4",
77
+ stop_reward=500.0,
78
+ run_name='invaders',
79
+ replay_size=10_000_000,
80
+ replay_initial=50_000,
81
+ target_net_sync=10_000,
82
+ epsilon_frames=10_000_000,
83
+ learning_rate=0.00025,
84
+ ),
85
+ }
86
+
87
+
88
+ def unpack_batch( batch: tt.List[ExperienceFirstLast] ):
89
+ """
90
+ Convert a batch of transitions to Numpy arrays suitable for training
91
+ """
92
+ states, actions, rewards, dones, last_states = [],[],[],[],[]
93
+ for exp in batch:
94
+ states.append(exp.state)
95
+ actions.append(exp.action)
96
+ rewards.append(exp.reward)
97
+ dones.append(exp.last_state is None)
98
+ if exp.last_state is None: # end of episode
99
+ lstate = exp.state # the result will be masked anyway, but this avoid special handling of None
100
+ else:
101
+ lstate = exp.last_state
102
+ last_states.append(lstate)
103
+ return np.asarray(states), np.array(actions), np.array(rewards, dtype=np.float32), np.array(dones, dtype=bool), np.asarray(last_states)
104
+
105
+
106
+ def calc_loss_dqn( batch: tt.List[ExperienceFirstLast], net: nn.Module, tgt_net: nn.Module, gamma: float, device: torch.device) -> torch.Tensor:
107
+ states, actions, rewards, dones, next_states = unpack_batch(batch)
108
+ states_v = torch.as_tensor(states).to(device)
109
+ next_states_v = torch.as_tensor(next_states).to(device)
110
+ actions_v = torch.LongTensor(actions).to(device)
111
+ rewards_v = torch.FloatTensor(rewards).to(device)
112
+ done_mask = torch.BoolTensor(dones).to(device)
113
+
114
+ actions_v = actions_v.unsqueeze(-1)
115
+ state_action_vals = net(states_v).gather(1, actions_v).squeeze(-1)
116
+ with torch.no_grad():
117
+ next_state_vals = tgt_net(next_states_v).max(1)[0]
118
+ next_state_vals[done_mask] = 0.0
119
+
120
+ bellman_vals = next_state_vals.detach() * gamma + rewards_v
121
+ return nn.MSELoss()(state_action_vals, bellman_vals)
122
+
123
+
124
+ def calc_loss_double_dqn( batch: tt.List[ExperienceFirstLast], net: nn.Module, tgt_net: nn.Module, gamma: float, device: torch.device):
125
+ states, actions, rewards, dones, next_states = unpack_batch(batch)
126
+ states_v = torch.as_tensor(states).to(device)
127
+ actions_v = torch.tensor(actions).to(device)
128
+ rewards_v = torch.tensor(rewards).to(device)
129
+ done_mask = torch.BoolTensor(dones).to(device)
130
+
131
+ actions_v = actions_v.unsqueeze(-1)
132
+ state_action_vals = net(states_v).gather(1, actions_v).squeeze(-1)
133
+ with torch.no_grad():
134
+ next_states_v = torch.as_tensor(next_states).to(device)
135
+ next_state_acts = net(next_states_v).max(1)[1] # use net() to get the best action for the next state
136
+ next_state_acts = next_state_acts.unsqueeze(-1)
137
+ next_state_vals = tgt_net(next_states_v).gather(1, next_state_acts).squeeze(-1) # use tgt_net to get the best action value corresponding to the selected action
138
+ next_state_vals[done_mask] = 0.0
139
+ exp_sa_vals = next_state_vals.detach() * gamma + rewards_v
140
+ return nn.MSELoss()(state_action_vals, exp_sa_vals)
141
+
142
+
143
+ def batch_generator(buffer: ExperienceReplayBuffer, initial: int, batch_size: int) -> tt.Generator[tt.List[ExperienceFirstLast], None, None]:
144
+ buffer.populate(initial) # get initial fresh samples
145
+ while True:
146
+ buffer.populate(1)
147
+ yield buffer.sample(batch_size)
148
+
149
+
150
+ @torch.no_grad()
151
+ def calc_values_of_states(states: np.ndarray, net: nn.Module, device: torch.device):
152
+ mean_vals = []
153
+ for batch in np.array_split(states, 64):
154
+ states_v = torch.tensor(batch).to(device)
155
+ action_values_v = net(states_v)
156
+ best_action_values_v = action_values_v.max(1)[0]
157
+ mean_vals.append(best_action_values_v.mean().item())
158
+ return np.mean(mean_vals)
159
+
160
+
161
+ def setup_ignite(
162
+ engine: Engine, params: Hyperparams, exp_source: ExperienceSourceFirstLast,
163
+ run_name: str, log_dir: str, extra_metrics: tt.Iterable[str] = (),
164
+ tuner_reward_episode: int = 100, tuner_reward_min: float = -19,
165
+ ):
166
+ handler = EndOfEpisodeHandler( exp_source, bound_avg_reward=params.stop_reward )
167
+ handler.attach(engine)
168
+ EpisodeFPSHandler().attach(engine)
169
+
170
+ @engine.on(EpisodeEvents.EPISODE_COMPLETED)
171
+ def episode_completed(trainer: Engine):
172
+ passed = trainer.state.metrics.get('time_passed', 0)
173
+ print("Episode %d: reward=%.0f, steps=%s, speed=%.1f f/s, elapsed=%s" % (
174
+ trainer.state.episode, trainer.state.episode_reward,
175
+ trainer.state.episode_steps, trainer.state.metrics.get('avg_fps', 0),
176
+ timedelta(seconds=int(passed))))
177
+
178
+ @engine.on(EpisodeEvents.BOUND_REWARD_REACHED)
179
+ def game_solved(trainer: Engine):
180
+ passed = trainer.state.metrics['time_passed']
181
+ print("Game solved in %s, after %d episodes and %d iterations!" % (
182
+ timedelta(seconds=int(passed)), trainer.state.episode,
183
+ trainer.state.iteration))
184
+ trainer.should_terminate = True
185
+ trainer.state.solved = True
186
+
187
+ tb = tb_logger.TensorboardLogger(log_dir=log_dir)
188
+ run_avg = RunningAverage(output_transform=lambda v: v['loss'])
189
+ run_avg.attach(engine, "avg_loss")
190
+
191
+ metrics = ['reward', 'steps', 'avg_reward']
192
+ handler = tb_logger.OutputHandler(tag="episodes", metric_names=metrics)
193
+ event = EpisodeEvents.EPISODE_COMPLETED
194
+ tb.attach(engine, log_handler=handler, event_name=event)
195
+
196
+ # write to tensorboard every 100 iterations
197
+ PeriodicEvents().attach(engine)
198
+ metrics = ['avg_loss', 'avg_fps']
199
+ metrics.extend(extra_metrics)
200
+ handler = tb_logger.OutputHandler(tag="train", metric_names=metrics, output_transform=lambda a: a)
201
+ event = PeriodEvents.ITERS_100_COMPLETED
202
+ tb.attach(engine, log_handler=handler, event_name=event)
203
+
204
+ if params.tuner_mode:
205
+ @engine.on(EpisodeEvents.EPISODE_COMPLETED)
206
+ def episode_completed(trainer: Engine):
207
+ avg_reward = trainer.state.metrics.get('avg_reward')
208
+ max_episodes = params.episodes_to_solve * 1.1
209
+ if trainer.state.episode > tuner_reward_episode and \
210
+ avg_reward < tuner_reward_min:
211
+ trainer.should_terminate = True
212
+ trainer.state.solved = False
213
+ elif trainer.state.episode > max_episodes:
214
+ trainer.should_terminate = True
215
+ trainer.state.solved = False
216
+ if trainer.should_terminate:
217
+ print(f"Episode {trainer.state.episode}, "
218
+ f"avg_reward {avg_reward:.2f}, terminating")
219
+
220
+
221
+ # hyperparams tuner
222
+ TrainFunc = tt.Callable[ [Hyperparams, torch.device, dict], tt.Optional[int] ]
223
+
224
+ BASE_SPACE = {
225
+ "learning_rate": tune.loguniform(1e-5, 1e-4),
226
+ "gamma": tune.choice([0.9, 0.92, 0.95, 0.98, 0.99, 0.995]),
227
+ }
228
+
229
+ def tune_params(
230
+ base_params: Hyperparams, train_func: TrainFunc, device: torch.device,
231
+ samples: int = 10, extra_space: tt.Optional[tt.Dict[str, tt.Any]] = None,
232
+ ):
233
+ """
234
+ Perform hyperparameters tune.
235
+ :param train_func: Train function, has to return "episodes" key with metric
236
+ :param device: torch device
237
+ :param samples: count of samples to perform
238
+ :param extra_space: additional search space
239
+ """
240
+ search_space = dict(BASE_SPACE)
241
+ if extra_space is not None:
242
+ search_space.update(extra_space)
243
+ config = tune.TuneConfig(num_samples=samples)
244
+
245
+ def objective(config: dict, device: torch.device) -> dict:
246
+ keys = dataclasses.asdict(base_params).keys()
247
+ upd = {"tuner_mode": True}
248
+ for k, v in config.items():
249
+ if k in keys:
250
+ upd[k] = v
251
+ params = dataclasses.replace(base_params, **upd)
252
+ res = train_func(params, device, config)
253
+ return {"episodes": res if res is not None else 10**6}
254
+
255
+ obj = tune.with_parameters(objective, device=device)
256
+ if device.type == "cuda":
257
+ obj = tune.with_resources(obj, {"gpu": 1})
258
+ tuner = tune.Tuner(obj, param_space=search_space, tune_config=config)
259
+ results = tuner.fit()
260
+ best = results.get_best_result(metric="episodes", mode="min")
261
+ print(best.config)
262
+ print(best.metrics)
263
+
264
+
265
+ def train_or_tune(
266
+ args: argparse.Namespace,
267
+ train_func: TrainFunc,
268
+ device: torch.device,
269
+ best_params: Hyperparams,
270
+ extra_params: tt.Optional[dict] = None,
271
+ extra_space: tt.Optional[dict] = None,
272
+ ):
273
+ random.seed(SEED)
274
+ torch.manual_seed(SEED)
275
+ if args.params == "common":
276
+ params = GAME_PARAMS['pong']
277
+ else:
278
+ params = best_params
279
+
280
+ if extra_params is None:
281
+ extra_params = {}
282
+ if args.tune is None:
283
+ train_func(params, device, extra_params)
284
+ else:
285
+ tune_params(params, train_func, device, samples=args.tune, extra_space=extra_space)
286
+
287
+
288
+ def save_model(model, path, global_step, start_iteration):
289
+ torch.save({
290
+ 'model_state_dict': model.state_dict(),
291
+ 'global_step': global_step,
292
+ 'start_iteration': start_iteration
293
+ }, path)
294
+
295
+
296
+ def load_model(model, path):
297
+ if not os.path.exists(path):
298
+ raise FileNotFoundError(f"No checkpoint found at {path}")
299
+ checkpoint = torch.load(path) # Load the checkpoint
300
+ if 'model_state_dict' in checkpoint:
301
+ model.load_state_dict(checkpoint['model_state_dict'])
302
+ else:
303
+ raise KeyError(f"'model_state_dict' not found in the checkpoint at {path}")
304
+ model.eval() # Set the model to evaluation mode
305
+ return checkpoint # Return the checkpoint to access global_step
306
+
307
+ def get_latest_checkpoint(dir_path):
308
+ checkpoint_files = glob.glob(os.path.join(dir_path, '*.pth'))
309
+ if not checkpoint_files:
310
+ return None
311
+ latest_checkpoint = max(checkpoint_files, key=os.path.getmtime)
312
+ return latest_checkpoint
@@ -0,0 +1,100 @@
1
+ import typing as tt
2
+ import gymnasium as gym
3
+ from gymnasium import spaces
4
+ import collections
5
+ import numpy as np
6
+ from stable_baselines3.common import atari_wrappers
7
+
8
+ class ImageToPyTorch(gym.ObservationWrapper):
9
+ def __init__(self, env):
10
+ super(ImageToPyTorch, self).__init__(env)
11
+ obs = self.observation_space
12
+ assert isinstance(obs, gym.spaces.Box)
13
+ assert len(obs.shape) == 3
14
+ new_shape = (obs.shape[-1], obs.shape[0], obs.shape[1])
15
+ self.observation_space = gym.spaces.Box(
16
+ low=obs.low.min(), high=obs.high.max(),
17
+ shape=new_shape, dtype=obs.dtype)
18
+
19
+ def observation(self, observation):
20
+ return np.moveaxis(observation, 2, 0)
21
+
22
+
23
+ class FrameStack1D(gym.Wrapper):
24
+ """
25
+ Stacks observations into 1D array
26
+ """
27
+ def __init__(self, env, k):
28
+ assert isinstance(env, gym.Env)
29
+ assert isinstance(env.observation_space, gym.spaces.Box)
30
+ assert len(env.observation_space.shape) == 1
31
+ super(FrameStack1D, self).__init__(env)
32
+ self.k = k
33
+ self.frames = collections.deque(maxlen=k)
34
+ shp = env.observation_space.shape
35
+ self.observation_space = gym.spaces.Box(
36
+ low=np.min(env.observation_space.low),
37
+ high=np.max(env.observation_space.high),
38
+ shape=(env.observation_space.shape[0]*k,),
39
+ dtype=env.observation_space.dtype)
40
+
41
+ def reset(self):
42
+ ob, extra = self.env.reset()
43
+ for _ in range(self.k):
44
+ self.frames.append(ob)
45
+ return self._get_ob(), extra
46
+
47
+ def step(self, action):
48
+ ob, reward, done, trunc, info = self.env.step(action)
49
+ self.frames.append(ob)
50
+ return self._get_ob(), reward, done, trunc, info
51
+
52
+ def _get_ob(self):
53
+ assert len(self.frames) == self.k
54
+ return np.concatenate(self.frames)
55
+
56
+
57
+
58
+ class BufferWrapper(gym.ObservationWrapper):
59
+ def __init__(self, env, n_steps):
60
+ super(BufferWrapper, self).__init__(env)
61
+ obs = env.observation_space
62
+ assert isinstance(obs, spaces.Box)
63
+ new_obs = gym.spaces.Box(
64
+ obs.low.repeat(n_steps, axis=0),
65
+ obs.high.repeat(n_steps, axis=0), dtype=obs.dtype)
66
+ self.observation_space = new_obs
67
+ self.buffer = collections.deque(maxlen=n_steps)
68
+
69
+ def reset(self, *, seed: tt.Optional[int] = None,
70
+ options: tt.Optional[dict[str, tt.Any]] = None):
71
+ for _ in range(self.buffer.maxlen-1):
72
+ self.buffer.append(self.env.observation_space.low)
73
+ obs, extra = self.env.reset()
74
+ return self.observation(obs), extra
75
+
76
+ def observation(self, observation: np.ndarray) -> np.ndarray:
77
+ self.buffer.append(observation)
78
+ return np.concatenate(self.buffer)
79
+
80
+
81
+ def wrap_dqn(env: gym.Env, stack_frames: int = 4,
82
+ episodic_life: bool = True, clip_reward: bool = True,
83
+ noop_max: int = 0) -> gym.Env:
84
+ """
85
+ Apply a common set of wrappers for Atari games.
86
+ :param env: Environment to wrap
87
+ :param stack_frames: count of frames to stack, default=4
88
+ :param episodic_life: convert life to end of episode
89
+ :param clip_reward: reward clipping
90
+ :param noop_max: how many NOOP actions to execute
91
+ :return: wrapped environment
92
+ """
93
+ assert 'NoFrameskip' in env.spec.id
94
+ env = atari_wrappers.AtariWrapper(
95
+ env, clip_reward=clip_reward, noop_max=noop_max,
96
+ terminal_on_life_loss=episodic_life)
97
+ env = ImageToPyTorch(env)
98
+ env = BufferWrapper(env, stack_frames)
99
+ return env
100
+
@@ -1,11 +1,32 @@
1
+ import time
1
2
  import re
3
+ import collections
4
+ import sys
2
5
  import pickle
3
6
  import json
4
7
  from collections import Counter
5
8
  import urllib.request
6
9
  import numpy as np
10
+ import torch
11
+ import torch.nn as nn
7
12
 
13
+ class WeightedMSELoss(nn.Module):
14
+ def __init__(self, size_average=True):
15
+ super(WeightedMSELoss, self).__init__()
16
+ self.size_average = size_average
8
17
 
18
+ def forward(self, input, target, weights=None):
19
+ if weights is None:
20
+ return nn.MSELoss(self.size_average)(input, target)
21
+
22
+ loss_rows = (input - target) ** 2
23
+ if len(loss_rows.size()) != 1:
24
+ loss_rows = torch.sum(loss_rows, dim=1)
25
+ res = (weights * loss_rows).sum()
26
+ if self.size_average:
27
+ res /= len(weights)
28
+ return res
29
+
9
30
  class TextVectorizer:
10
31
  def __init__(self, max_tokens=20000, output_mode="int", output_sequence_length=None, ngrams=None, standardize=None):
11
32
  self.max_tokens = max_tokens
@@ -123,6 +144,97 @@ class TextVectorizer:
123
144
  return vectorizer
124
145
 
125
146
 
147
+
148
+ class TBMeanTracker:
149
+ """
150
+ TensorBoard value tracker: allows to batch fixed amount of historical values and write their mean into TB
151
+
152
+ Designed and tested with pytorch-tensorboard in mind
153
+ """
154
+ def __init__(self, writer, batch_size):
155
+ """
156
+ :param writer: writer with close() and add_scalar() methods
157
+ :param batch_size: integer size of batch to track
158
+ """
159
+ assert isinstance(batch_size, int)
160
+ assert writer is not None
161
+ self.writer = writer
162
+ self.batch_size = batch_size
163
+
164
+ def __enter__(self):
165
+ self._batches = collections.defaultdict(list)
166
+ return self
167
+
168
+ def __exit__(self, exc_type, exc_val, exc_tb):
169
+ self.writer.close()
170
+
171
+ @staticmethod
172
+ def _as_float(value):
173
+ assert isinstance(value, (float, int, np.ndarray, np.generic, torch.autograd.Variable)) or torch.is_tensor(value)
174
+ tensor_val = None
175
+ if isinstance(value, torch.autograd.Variable):
176
+ tensor_val = value.data
177
+ elif torch.is_tensor(value):
178
+ tensor_val = value
179
+
180
+ if tensor_val is not None:
181
+ return tensor_val.float().mean().item()
182
+ elif isinstance(value, np.ndarray):
183
+ return float(np.mean(value))
184
+ else:
185
+ return float(value)
186
+
187
+ def track(self, param_name, value, iter_index):
188
+ assert isinstance(param_name, str)
189
+ assert isinstance(iter_index, int)
190
+
191
+ data = self._batches[param_name]
192
+ data.append(self._as_float(value))
193
+
194
+ if len(data) >= self.batch_size:
195
+ self.writer.add_scalar(param_name, np.mean(data), iter_index)
196
+ data.clear()
197
+
198
+
199
+ class RewardTracker:
200
+ def __init__(self, writer, min_ts_diff=1.0):
201
+ """
202
+ Constructs RewardTracker
203
+ :param writer: writer to use for writing stats
204
+ :param min_ts_diff: minimal time difference to track speed
205
+ """
206
+ self.writer = writer
207
+ self.min_ts_diff = min_ts_diff
208
+
209
+ def __enter__(self):
210
+ self.ts = time.time()
211
+ self.ts_frame = 0
212
+ self.total_rewards = []
213
+ return self
214
+
215
+ def __exit__(self, *args):
216
+ self.writer.close()
217
+
218
+ def reward(self, reward, frame, epsilon=None):
219
+ self.total_rewards.append(reward)
220
+ mean_reward = np.mean(self.total_rewards[-100:])
221
+ ts_diff = time.time() - self.ts
222
+ if ts_diff > self.min_ts_diff:
223
+ speed = (frame - self.ts_frame) / ts_diff
224
+ self.ts_frame = frame
225
+ self.ts = time.time()
226
+ epsilon_str = "" if epsilon is None else ", eps %.2f" % epsilon
227
+ print("%d: done %d episodes, mean reward %.3f, speed %.2f f/s%s" % (
228
+ frame, len(self.total_rewards), mean_reward, speed, epsilon_str
229
+ ))
230
+ sys.stdout.flush()
231
+ self.writer.add_scalar("speed", speed, frame)
232
+ if epsilon is not None:
233
+ self.writer.add_scalar("epsilon", epsilon, frame)
234
+ self.writer.add_scalar("reward_100", mean_reward, frame)
235
+ self.writer.add_scalar("reward", reward, frame)
236
+ return mean_reward if len(self.total_rewards) > 30 else None
237
+
126
238
  def query_ollama(prompt, model="llama3", url="http://localhost:11434/api/chat"):
127
239
  # Create the data payload as a dictionary
128
240
  data = {
File without changes
File without changes
File without changes