simonbb 0.0.6__tar.gz → 0.0.7__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.
- {simonbb-0.0.6 → simonbb-0.0.7}/PKG-INFO +3 -2
- {simonbb-0.0.6 → simonbb-0.0.7}/pyproject.toml +2 -1
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/experience.py +57 -55
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/ignite.py +11 -15
- {simonbb-0.0.6 → simonbb-0.0.7}/LICENSE +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/README.md +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/__init__.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/__init__.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/actions.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/agent.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/bert.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/dqn.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/gpt.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/transformer.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/utils.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis433/wrappers.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis438/__init__.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/cis438/agentic.py +0 -0
- {simonbb-0.0.6 → simonbb-0.0.7}/src/simonbb/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: simonbb
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
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>
|
|
@@ -18,6 +18,7 @@ Requires-Dist: langchain~=1.2.13
|
|
|
18
18
|
Requires-Dist: langgraph~=1.1.3
|
|
19
19
|
Requires-Dist: matplotlib~=3.10.8
|
|
20
20
|
Requires-Dist: numpy~=2.4.0
|
|
21
|
+
Requires-Dist: opencv-python~=5.0.0.93
|
|
21
22
|
Requires-Dist: pytorch-ignite~=0.5.4
|
|
22
23
|
Requires-Dist: pyyaml~=6.0.3
|
|
23
24
|
Requires-Dist: ray~=2.56.0
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simonbb"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.7"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Huaxia Rui", email="huaxia.rui@simon.rochester.edu" },
|
|
10
10
|
]
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
"pytorch-ignite~=0.5.4",
|
|
35
35
|
"stable_baselines3~=2.9.0",
|
|
36
36
|
"ray~=2.56.0",
|
|
37
|
+
"opencv-python~=5.0.0.93",
|
|
37
38
|
"pyyaml~=6.0.3"
|
|
38
39
|
]
|
|
39
40
|
[project.urls]
|
|
@@ -25,9 +25,9 @@ class Experience:
|
|
|
25
25
|
|
|
26
26
|
class ExperienceSource:
|
|
27
27
|
"""
|
|
28
|
-
|
|
28
|
+
n-step experience source using single or multiple environments
|
|
29
29
|
|
|
30
|
-
Every experience contains
|
|
30
|
+
Every experience contains a list of n Experience entries
|
|
31
31
|
"""
|
|
32
32
|
Item = tt.Tuple[Experience, ...]
|
|
33
33
|
|
|
@@ -39,7 +39,7 @@ class ExperienceSource:
|
|
|
39
39
|
:param env: environment or list of environments to be used
|
|
40
40
|
:param agent: callable to convert batch of states into actions to take
|
|
41
41
|
:param steps_count: count of steps to track for every experience chain
|
|
42
|
-
:param steps_delta: how many steps to do between
|
|
42
|
+
:param steps_delta: how many steps to do between subjectory emissions
|
|
43
43
|
:param env_seed: seed to be used in Env.reset() call
|
|
44
44
|
"""
|
|
45
45
|
assert steps_count >= 1
|
|
@@ -54,7 +54,7 @@ class ExperienceSource:
|
|
|
54
54
|
self.agent = agent
|
|
55
55
|
self.steps_count = steps_count
|
|
56
56
|
self.steps_delta = steps_delta
|
|
57
|
-
self.total_rewards = []
|
|
57
|
+
self.total_rewards = [] # simple sum of immediate rewards, without discounting
|
|
58
58
|
self.total_steps = []
|
|
59
59
|
self.agent_states = [agent.initial_state() for _ in self.pool]
|
|
60
60
|
self.env_seed = env_seed
|
|
@@ -67,7 +67,7 @@ class ExperienceSource:
|
|
|
67
67
|
else:
|
|
68
68
|
obs, _ = env.reset()
|
|
69
69
|
states.append(obs)
|
|
70
|
-
histories.append(deque(maxlen=self.steps_count))
|
|
70
|
+
histories.append( deque(maxlen=self.steps_count) ) # Note the max length of the double-ended queue
|
|
71
71
|
cur_rewards.append(0.0)
|
|
72
72
|
cur_steps.append(0)
|
|
73
73
|
|
|
@@ -81,17 +81,16 @@ class ExperienceSource:
|
|
|
81
81
|
next_state, r, is_done, is_tr, _ = env.step(action)
|
|
82
82
|
cur_rewards[idx] += r
|
|
83
83
|
cur_steps[idx] += 1
|
|
84
|
-
history.append(Experience(state=state, action=action, reward=r, done_trunc=is_done or is_tr))
|
|
84
|
+
history.append(Experience(state=state, action=action, reward=r, done_trunc=is_done or is_tr)) # push out the oldest step once deque is full (i.e., steps_count reached)
|
|
85
85
|
if len(history) == self.steps_count and iter_idx % self.steps_delta == 0:
|
|
86
86
|
yield tuple(history)
|
|
87
87
|
states[idx] = next_state
|
|
88
88
|
if is_done or is_tr:
|
|
89
|
-
# generate tail of history
|
|
90
89
|
if 0 < len(history) < self.steps_count:
|
|
91
90
|
yield tuple(history)
|
|
92
91
|
while len(history) > 1:
|
|
93
92
|
history.popleft()
|
|
94
|
-
yield tuple(history)
|
|
93
|
+
yield tuple(history) # a sub-trajectory of the history
|
|
95
94
|
self.total_rewards.append(cur_rewards[idx])
|
|
96
95
|
self.total_steps.append(cur_steps[idx])
|
|
97
96
|
cur_rewards[idx] = 0.0
|
|
@@ -104,13 +103,15 @@ class ExperienceSource:
|
|
|
104
103
|
history.clear()
|
|
105
104
|
iter_idx += 1
|
|
106
105
|
|
|
106
|
+
|
|
107
|
+
# pop_total_rewards() returns a list of total rewards (without discounting), one per env, after an episode ends
|
|
107
108
|
def pop_total_rewards(self) -> tt.List[float]:
|
|
108
109
|
r = self.total_rewards
|
|
109
110
|
if r:
|
|
110
111
|
self.total_rewards = []
|
|
111
112
|
self.total_steps = []
|
|
112
113
|
return r
|
|
113
|
-
|
|
114
|
+
# pop_total_rewards() returns a list of total rewards (without discounting) and number of steps, one per env, after an episode ends
|
|
114
115
|
def pop_rewards_steps(self) -> tt.List[tt.Tuple[float, int]]:
|
|
115
116
|
res = list(zip(self.total_rewards, self.total_steps))
|
|
116
117
|
if res:
|
|
@@ -148,12 +149,54 @@ class ExperienceSourceFirstLast(ExperienceSource):
|
|
|
148
149
|
else:
|
|
149
150
|
last_state = exp[-1].state
|
|
150
151
|
elems = exp[:-1]
|
|
151
|
-
|
|
152
|
+
discounted_total_reward = 0.0
|
|
152
153
|
for e in reversed(elems):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
yield ExperienceFirstLast(state=exp[0].state, action=exp[0].action,
|
|
156
|
-
|
|
154
|
+
discounted_total_reward *= self.gamma
|
|
155
|
+
discounted_total_reward += e.reward
|
|
156
|
+
yield ExperienceFirstLast(state=exp[0].state, action=exp[0].action, reward=discounted_total_reward, last_state=last_state)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ExperienceReplayBuffer:
|
|
161
|
+
def __init__(self, experience_source: tt.Optional[ExperienceSource], buffer_size: int):
|
|
162
|
+
self.experience_source_iter = None if experience_source is None else iter(experience_source)
|
|
163
|
+
self.buffer: tt.List[ExperienceSource.Item] = []
|
|
164
|
+
self.capacity = buffer_size
|
|
165
|
+
self.pos = 0
|
|
166
|
+
|
|
167
|
+
def __len__(self):
|
|
168
|
+
return len(self.buffer)
|
|
169
|
+
|
|
170
|
+
def __iter__(self) -> tt.Iterator[ExperienceSource.Item]:
|
|
171
|
+
return iter(self.buffer)
|
|
172
|
+
|
|
173
|
+
def sample(self, batch_size: int) -> tt.List[ExperienceSource.Item]:
|
|
174
|
+
"""
|
|
175
|
+
Get one random batch from experience replay
|
|
176
|
+
:param batch_size: size of the batch to sample
|
|
177
|
+
:return: list of experience entries
|
|
178
|
+
"""
|
|
179
|
+
if len(self.buffer) <= batch_size:
|
|
180
|
+
return self.buffer
|
|
181
|
+
# Warning: replace=False makes random.choice O(n)
|
|
182
|
+
keys = np.random.choice(len(self.buffer), batch_size, replace=True)
|
|
183
|
+
return [self.buffer[key] for key in keys]
|
|
184
|
+
|
|
185
|
+
def _add(self, sample: ExperienceSource.Item):
|
|
186
|
+
if len(self.buffer) < self.capacity:
|
|
187
|
+
self.buffer.append(sample)
|
|
188
|
+
else:
|
|
189
|
+
self.buffer[self.pos] = sample
|
|
190
|
+
self.pos = (self.pos + 1) % self.capacity
|
|
191
|
+
|
|
192
|
+
def populate(self, samples: int):
|
|
193
|
+
"""
|
|
194
|
+
Populates samples into the buffer
|
|
195
|
+
:param samples: how many samples to populate
|
|
196
|
+
"""
|
|
197
|
+
for _ in range(samples):
|
|
198
|
+
entry = next(self.experience_source_iter)
|
|
199
|
+
self._add(entry)
|
|
157
200
|
|
|
158
201
|
|
|
159
202
|
def vector_rewards(rewards: tt.Deque[np.ndarray], dones: tt.Deque[np.ndarray], gamma: float) -> np.ndarray:
|
|
@@ -406,47 +449,6 @@ class ExperienceSourceBuffer:
|
|
|
406
449
|
yield self.buffer[episode][ofs:ofs+self.steps_count]
|
|
407
450
|
|
|
408
451
|
|
|
409
|
-
class ExperienceReplayBuffer:
|
|
410
|
-
def __init__(self, experience_source: tt.Optional[ExperienceSource], buffer_size: int):
|
|
411
|
-
self.experience_source_iter = None if experience_source is None else iter(experience_source)
|
|
412
|
-
self.buffer: tt.List[ExperienceSource.Item] = []
|
|
413
|
-
self.capacity = buffer_size
|
|
414
|
-
self.pos = 0
|
|
415
|
-
|
|
416
|
-
def __len__(self):
|
|
417
|
-
return len(self.buffer)
|
|
418
|
-
|
|
419
|
-
def __iter__(self) -> tt.Iterator[ExperienceSource.Item]:
|
|
420
|
-
return iter(self.buffer)
|
|
421
|
-
|
|
422
|
-
def sample(self, batch_size: int) -> tt.List[ExperienceSource.Item]:
|
|
423
|
-
"""
|
|
424
|
-
Get one random batch from experience replay
|
|
425
|
-
:param batch_size: size of the batch to sample
|
|
426
|
-
:return: list of experience entries
|
|
427
|
-
"""
|
|
428
|
-
if len(self.buffer) <= batch_size:
|
|
429
|
-
return self.buffer
|
|
430
|
-
# Warning: replace=False makes random.choice O(n)
|
|
431
|
-
keys = np.random.choice(len(self.buffer), batch_size, replace=True)
|
|
432
|
-
return [self.buffer[key] for key in keys]
|
|
433
|
-
|
|
434
|
-
def _add(self, sample: ExperienceSource.Item):
|
|
435
|
-
if len(self.buffer) < self.capacity:
|
|
436
|
-
self.buffer.append(sample)
|
|
437
|
-
else:
|
|
438
|
-
self.buffer[self.pos] = sample
|
|
439
|
-
self.pos = (self.pos + 1) % self.capacity
|
|
440
|
-
|
|
441
|
-
def populate(self, samples: int):
|
|
442
|
-
"""
|
|
443
|
-
Populates samples into the buffer
|
|
444
|
-
:param samples: how many samples to populate
|
|
445
|
-
"""
|
|
446
|
-
for _ in range(samples):
|
|
447
|
-
entry = next(self.experience_source_iter)
|
|
448
|
-
self._add(entry)
|
|
449
|
-
|
|
450
452
|
|
|
451
453
|
class PrioReplayBufferNaive:
|
|
452
454
|
def __init__(self, exp_source, buf_size, prob_alpha=0.6):
|
|
@@ -13,39 +13,38 @@ class EpisodeEvents(EventEnum):
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class EndOfEpisodeHandler:
|
|
16
|
-
def __init__(self, exp_source: ExperienceSource, alpha: float = 0.98,
|
|
17
|
-
bound_avg_reward: Optional[float] = None,
|
|
18
|
-
subsample_end_of_episode: Optional[int] = None):
|
|
16
|
+
def __init__(self, exp_source: ExperienceSource, alpha: float = 0.98, bound_avg_reward: Optional[float] = None, period: Optional[int] = None):
|
|
19
17
|
"""
|
|
20
|
-
|
|
18
|
+
This end-of-episode event handler, triggered by Events.ITERATION_COMPLETED, fires an EPISODE_COMPLETED event for every episode, or every {period} number of episodes
|
|
21
19
|
:param exp_source: experience source to use
|
|
22
20
|
:param alpha: smoothing alpha param
|
|
23
21
|
:param bound_avg_reward: optional boundary for average reward
|
|
24
|
-
:param
|
|
22
|
+
:param period: if given, end of episode event fire once every {period} episodes
|
|
25
23
|
"""
|
|
26
24
|
self._exp_source = exp_source
|
|
27
25
|
self._alpha = alpha
|
|
28
26
|
self._bound_avg_reward = bound_avg_reward
|
|
29
27
|
self._best_avg_reward = None
|
|
30
|
-
self.
|
|
28
|
+
self._period = period
|
|
31
29
|
|
|
32
30
|
def attach(self, engine: Engine):
|
|
33
|
-
engine.add_event_handler(Events.ITERATION_COMPLETED, self)
|
|
34
|
-
engine.register_events(*EpisodeEvents)
|
|
31
|
+
engine.add_event_handler( Events.ITERATION_COMPLETED, self ) # invoke EndOfEpisodeHandler() instance whenever an iteration (consisting of multiple episodes) is completed
|
|
32
|
+
engine.register_events( *EpisodeEvents )
|
|
35
33
|
State.event_to_attr[EpisodeEvents.EPISODE_COMPLETED] = "episode"
|
|
36
34
|
State.event_to_attr[EpisodeEvents.BOUND_REWARD_REACHED] = "episode"
|
|
37
35
|
State.event_to_attr[EpisodeEvents.BEST_REWARD_REACHED] = "episode"
|
|
38
36
|
|
|
39
37
|
def __call__(self, engine: Engine):
|
|
38
|
+
# pop_rewards_steps() returns a list of (total rewards, steps) of newly completed episodes since the last call of pop_rewards_steps().
|
|
40
39
|
for reward, steps in self._exp_source.pop_rewards_steps():
|
|
41
40
|
engine.state.episode = getattr(engine.state, "episode", 0) + 1
|
|
42
|
-
engine.state.episode_reward = reward
|
|
41
|
+
engine.state.episode_reward = reward # sum of immediate rewards of all steps during the episode, without discounting
|
|
43
42
|
engine.state.episode_steps = steps
|
|
44
43
|
engine.state.metrics['reward'] = reward
|
|
45
44
|
engine.state.metrics['steps'] = steps
|
|
46
45
|
self._update_smoothed_metrics(engine, reward, steps)
|
|
47
|
-
if self.
|
|
48
|
-
engine.fire_event(EpisodeEvents.EPISODE_COMPLETED)
|
|
46
|
+
if self._period is None or engine.state.episode % self._period == 0:
|
|
47
|
+
engine.fire_event( EpisodeEvents.EPISODE_COMPLETED )
|
|
49
48
|
if self._bound_avg_reward is not None and engine.state.metrics['avg_reward'] >= self._bound_avg_reward:
|
|
50
49
|
engine.fire_event(EpisodeEvents.BOUND_REWARD_REACHED)
|
|
51
50
|
if self._best_avg_reward is None:
|
|
@@ -110,13 +109,10 @@ class PeriodEvents(EventEnum):
|
|
|
110
109
|
ITERS_10000_COMPLETED = "iterations_10000_completed"
|
|
111
110
|
ITERS_100000_COMPLETED = "iterations_100000_completed"
|
|
112
111
|
|
|
113
|
-
|
|
114
112
|
class PeriodicEvents:
|
|
115
113
|
"""
|
|
116
|
-
The same as CustomPeriodicEvent from ignite.contrib, but use true amount of iterations,
|
|
117
|
-
which is good for TensorBoard
|
|
114
|
+
The same as CustomPeriodicEvent from ignite.contrib, but use true amount of iterations, which is good for TensorBoard
|
|
118
115
|
"""
|
|
119
|
-
|
|
120
116
|
INTERVAL_TO_EVENT = {
|
|
121
117
|
10: PeriodEvents.ITERS_10_COMPLETED,
|
|
122
118
|
100: PeriodEvents.ITERS_100_COMPLETED,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|