simonbb 0.0.4__tar.gz → 0.0.5__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.5
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
@@ -21,6 +21,7 @@ Requires-Dist: numpy~=2.4.0
21
21
  Requires-Dist: pytorch-ignite~=0.5.4
22
22
  Requires-Dist: pyyaml~=6.0.3
23
23
  Requires-Dist: sentence-transformers~=5.3.0
24
+ Requires-Dist: stable-baselines3~=2.9.0
24
25
  Requires-Dist: torchvision~=0.24.1
25
26
  Requires-Dist: torch~=2.9.1
26
27
  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.5"
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,7 @@ 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",
35
36
  "pyyaml~=6.0.3"
36
37
  ]
37
38
  [project.urls]
@@ -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