simonbb 0.0.2__tar.gz → 0.0.4__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.4/PKG-INFO +41 -0
- simonbb-0.0.4/README.md +13 -0
- simonbb-0.0.4/pyproject.toml +38 -0
- simonbb-0.0.4/src/simonbb/cis433/actions.py +76 -0
- simonbb-0.0.4/src/simonbb/cis433/agent.py +193 -0
- simonbb-0.0.4/src/simonbb/cis433/experience.py +604 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis433/gpt.py +1 -4
- simonbb-0.0.4/src/simonbb/cis433/ignite.py +137 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis433/transformer.py +2 -4
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis433/utils.py +4 -161
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis438/agentic.py +6 -5
- simonbb-0.0.4/src/simonbb/utils.py +158 -0
- simonbb-0.0.2/PKG-INFO +0 -16
- simonbb-0.0.2/README.md +0 -2
- simonbb-0.0.2/pyproject.toml +0 -23
- {simonbb-0.0.2 → simonbb-0.0.4}/LICENSE +0 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/__init__.py +0 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis433/__init__.py +0 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis433/bert.py +0 -0
- {simonbb-0.0.2 → simonbb-0.0.4}/src/simonbb/cis438/__init__.py +0 -0
simonbb-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simonbb
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: A small package to facilitate teaching AI courses
|
|
5
|
+
Project-URL: Homepage, https://simonbb.com
|
|
6
|
+
Author-email: Huaxia Rui <huaxia.rui@simon.rochester.edu>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: gymnasium~=1.3.0
|
|
13
|
+
Requires-Dist: langchain-chroma~=1.1.0
|
|
14
|
+
Requires-Dist: langchain-community~=0.4.1
|
|
15
|
+
Requires-Dist: langchain-core~=1.2.23
|
|
16
|
+
Requires-Dist: langchain-huggingface~=1.2.1
|
|
17
|
+
Requires-Dist: langchain~=1.2.13
|
|
18
|
+
Requires-Dist: langgraph~=1.1.3
|
|
19
|
+
Requires-Dist: matplotlib~=3.10.8
|
|
20
|
+
Requires-Dist: numpy~=2.4.0
|
|
21
|
+
Requires-Dist: pytorch-ignite~=0.5.4
|
|
22
|
+
Requires-Dist: pyyaml~=6.0.3
|
|
23
|
+
Requires-Dist: sentence-transformers~=5.3.0
|
|
24
|
+
Requires-Dist: torchvision~=0.24.1
|
|
25
|
+
Requires-Dist: torch~=2.9.1
|
|
26
|
+
Requires-Dist: transformers~=4.57.3
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Background
|
|
30
|
+
|
|
31
|
+
This is a pet project out of my own teaching needs at University of Rochester.
|
|
32
|
+
I mainly teach these 2 courses to MBA/MS students who lack technical background.
|
|
33
|
+
|
|
34
|
+
- CIS 433: AI and Deep Learning
|
|
35
|
+
- CIS 438: Agentic AI Applications
|
|
36
|
+
|
|
37
|
+
Accordingly, this package contains 2 modules.
|
|
38
|
+
|
|
39
|
+
- `simonbb.cis433`: this module is useful for anyone teacheing deep learning using PyTorch but doesn't want to bother students with too much nitty-gritty details.
|
|
40
|
+
- `simonbb.cis438`: this module is useful for anyone who teaches agentic AI using langchain.
|
|
41
|
+
|
simonbb-0.0.4/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Background
|
|
2
|
+
|
|
3
|
+
This is a pet project out of my own teaching needs at University of Rochester.
|
|
4
|
+
I mainly teach these 2 courses to MBA/MS students who lack technical background.
|
|
5
|
+
|
|
6
|
+
- CIS 433: AI and Deep Learning
|
|
7
|
+
- CIS 438: Agentic AI Applications
|
|
8
|
+
|
|
9
|
+
Accordingly, this package contains 2 modules.
|
|
10
|
+
|
|
11
|
+
- `simonbb.cis433`: this module is useful for anyone teacheing deep learning using PyTorch but doesn't want to bother students with too much nitty-gritty details.
|
|
12
|
+
- `simonbb.cis438`: this module is useful for anyone who teaches agentic AI using langchain.
|
|
13
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "simonbb"
|
|
7
|
+
version = "0.0.4"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Huaxia Rui", email="huaxia.rui@simon.rochester.edu" },
|
|
10
|
+
]
|
|
11
|
+
description = "A small package to facilitate teaching AI courses"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
license = "MIT"
|
|
19
|
+
license-files = ["LICEN[CS]E*"]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"matplotlib~=3.10.8",
|
|
22
|
+
"torch~=2.9.1",
|
|
23
|
+
"torchvision~=0.24.1",
|
|
24
|
+
"numpy~=2.4.0",
|
|
25
|
+
"transformers~=4.57.3",
|
|
26
|
+
"langchain-core~=1.2.23",
|
|
27
|
+
"langchain~=1.2.13",
|
|
28
|
+
"langgraph~=1.1.3",
|
|
29
|
+
"langchain-community~=0.4.1",
|
|
30
|
+
"langchain-chroma~=1.1.0",
|
|
31
|
+
"langchain-huggingface~=1.2.1",
|
|
32
|
+
"sentence-transformers~=5.3.0",
|
|
33
|
+
"gymnasium~=1.3.0",
|
|
34
|
+
"pytorch-ignite~=0.5.4",
|
|
35
|
+
"pyyaml~=6.0.3"
|
|
36
|
+
]
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://simonbb.com"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import numpy as np
|
|
3
|
+
import typing as tt
|
|
4
|
+
|
|
5
|
+
class ActionSelector(abc.ABC):
|
|
6
|
+
"""
|
|
7
|
+
Abstract class which converts scores to the actions
|
|
8
|
+
"""
|
|
9
|
+
@abc.abstractmethod
|
|
10
|
+
def __call__(self, scores: np.ndarray) -> np.ndarray:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ArgmaxActionSelector(ActionSelector):
|
|
15
|
+
"""
|
|
16
|
+
Selects actions using argmax
|
|
17
|
+
"""
|
|
18
|
+
def __call__(self, scores: np.ndarray) -> np.ndarray:
|
|
19
|
+
return np.argmax(scores, axis=1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EpsilonGreedyActionSelector(ActionSelector):
|
|
23
|
+
def __init__(self, epsilon: float = 0.05,
|
|
24
|
+
selector: tt.Optional[ActionSelector] = None):
|
|
25
|
+
self._epsilon = epsilon
|
|
26
|
+
self.selector = selector if selector is not None \
|
|
27
|
+
else ArgmaxActionSelector()
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def epsilon(self) -> float:
|
|
31
|
+
return self._epsilon
|
|
32
|
+
|
|
33
|
+
@epsilon.setter
|
|
34
|
+
def epsilon(self, value: float):
|
|
35
|
+
if value < 0.0 or value > 1.0:
|
|
36
|
+
raise ValueError("Epsilon has to be between 0 and 1")
|
|
37
|
+
self._epsilon = value
|
|
38
|
+
|
|
39
|
+
def __call__(self, scores: np.ndarray) -> np.ndarray:
|
|
40
|
+
assert len(scores.shape) == 2
|
|
41
|
+
batch_size, n_actions = scores.shape
|
|
42
|
+
actions = self.selector(scores)
|
|
43
|
+
mask = np.random.random(size=batch_size) < self.epsilon
|
|
44
|
+
rand_actions = np.random.choice(n_actions, sum(mask))
|
|
45
|
+
actions[mask] = rand_actions
|
|
46
|
+
return actions
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ProbabilityActionSelector(ActionSelector):
|
|
50
|
+
"""
|
|
51
|
+
Converts probabilities of actions into action by sampling them
|
|
52
|
+
"""
|
|
53
|
+
def __call__(self, probs: np.ndarray) -> np.ndarray:
|
|
54
|
+
actions = []
|
|
55
|
+
for prob in probs:
|
|
56
|
+
actions.append(np.random.choice(len(prob), p=prob))
|
|
57
|
+
return np.array(actions)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EpsilonTracker:
|
|
61
|
+
"""
|
|
62
|
+
Updates epsilon according to linear schedule
|
|
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):
|
|
68
|
+
self.selector = selector
|
|
69
|
+
self.eps_start = eps_start
|
|
70
|
+
self.eps_final = eps_final
|
|
71
|
+
self.eps_frames = eps_frames
|
|
72
|
+
self.frame(0)
|
|
73
|
+
|
|
74
|
+
def frame(self, frame: int):
|
|
75
|
+
eps = self.eps_start - frame / self.eps_frames
|
|
76
|
+
self.selector.epsilon = max(self.eps_final, eps)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent is something which converts states into actions and has state
|
|
3
|
+
"""
|
|
4
|
+
import abc
|
|
5
|
+
import copy
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
from torch import nn
|
|
9
|
+
import torch.nn.functional as F
|
|
10
|
+
import typing as tt
|
|
11
|
+
|
|
12
|
+
from . import actions
|
|
13
|
+
|
|
14
|
+
States = tt.List[np.ndarray] | np.ndarray
|
|
15
|
+
AgentStates = tt.List[tt.Any]
|
|
16
|
+
Preprocessor = tt.Callable[[States], torch.Tensor]
|
|
17
|
+
|
|
18
|
+
CPU_DEVICE = torch.device("cpu")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def default_states_preprocessor(states: States) -> torch.Tensor:
|
|
22
|
+
"""
|
|
23
|
+
Convert list of states into the form suitable for model
|
|
24
|
+
:param states: list of numpy arrays with states or numpy array
|
|
25
|
+
:return: torch.Tensor
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(states, list):
|
|
28
|
+
if len(states) == 1:
|
|
29
|
+
np_states = np.expand_dims(states[0], 0)
|
|
30
|
+
else:
|
|
31
|
+
np_states = np.asarray([np.asarray(s) for s in states])
|
|
32
|
+
else:
|
|
33
|
+
np_states = states
|
|
34
|
+
return torch.as_tensor(np_states)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def float32_preprocessor(states: States):
|
|
38
|
+
np_states = np.array(states, dtype=np.float32)
|
|
39
|
+
return torch.as_tensor(np_states)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BaseAgent(abc.ABC):
|
|
43
|
+
"""
|
|
44
|
+
Base Agent, sharing most of logic with concrete agent implementations.
|
|
45
|
+
"""
|
|
46
|
+
def initial_state(self) -> tt.Optional[tt.Any]:
|
|
47
|
+
"""
|
|
48
|
+
Should create initial empty state for the agent. It will be
|
|
49
|
+
called for the start of the episode
|
|
50
|
+
:return: Anything agent want to remember
|
|
51
|
+
"""
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def __call__(self, states: States, agent_states: AgentStates) -> tt.Tuple[np.ndarray, AgentStates]:
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NNAgent(BaseAgent):
|
|
60
|
+
"""
|
|
61
|
+
Network-based agent
|
|
62
|
+
"""
|
|
63
|
+
def __init__(self, model: nn.Module, action_selector: actions.ActionSelector,
|
|
64
|
+
device: torch.device, preprocessor: Preprocessor):
|
|
65
|
+
"""
|
|
66
|
+
Constructor of base agent
|
|
67
|
+
:param model: model to be used
|
|
68
|
+
:param action_selector: action selector
|
|
69
|
+
:param device: device for tensors
|
|
70
|
+
:param preprocessor: states preprocessor
|
|
71
|
+
"""
|
|
72
|
+
self.model = model
|
|
73
|
+
self.action_selector = action_selector
|
|
74
|
+
self.device = device
|
|
75
|
+
self.preprocessor = preprocessor
|
|
76
|
+
|
|
77
|
+
@abc.abstractmethod
|
|
78
|
+
def _net_filter(self, net_out: tt.Any, agent_states: AgentStates) -> \
|
|
79
|
+
tt.Tuple[torch.Tensor, AgentStates]:
|
|
80
|
+
"""
|
|
81
|
+
Internal method, processing network output and states into selector's input and new states
|
|
82
|
+
:param net_out: output from the network
|
|
83
|
+
:param agent_states: agent states
|
|
84
|
+
:return: tuple with tensor to be fed into selector and new states
|
|
85
|
+
"""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
@torch.no_grad()
|
|
89
|
+
def __call__(self, states: States, agent_states: AgentStates = None) -> tt.Tuple[np.ndarray, AgentStates]:
|
|
90
|
+
"""
|
|
91
|
+
Convert observations and states into actions to take
|
|
92
|
+
:param states: list of environment states to process
|
|
93
|
+
:param agent_states: list of states with the same length as observations
|
|
94
|
+
:return: tuple of actions, states
|
|
95
|
+
"""
|
|
96
|
+
if agent_states is None:
|
|
97
|
+
agent_states = [None] * len(states)
|
|
98
|
+
if self.preprocessor is not None:
|
|
99
|
+
states = self.preprocessor(states)
|
|
100
|
+
if torch.is_tensor(states):
|
|
101
|
+
states = states.to(self.device)
|
|
102
|
+
q_v = self.model(states)
|
|
103
|
+
q_v, new_states = self._net_filter(q_v, agent_states)
|
|
104
|
+
q = q_v.data.cpu().numpy()
|
|
105
|
+
actions = self.action_selector(q)
|
|
106
|
+
return actions, new_states
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DQNAgent(NNAgent):
|
|
110
|
+
"""
|
|
111
|
+
DQNAgent is a memoryless DQN agent which calculates Q values
|
|
112
|
+
from the observations and converts them into the actions using action_selector
|
|
113
|
+
"""
|
|
114
|
+
def __init__(self, model: nn.Module, action_selector: actions.ActionSelector,
|
|
115
|
+
device: torch.device = CPU_DEVICE,
|
|
116
|
+
preprocessor: Preprocessor = default_states_preprocessor):
|
|
117
|
+
super().__init__(model, action_selector=action_selector, device=device,
|
|
118
|
+
preprocessor=preprocessor)
|
|
119
|
+
|
|
120
|
+
# not needed in DQN - we don't process Q-values returned
|
|
121
|
+
def _net_filter(self, net_out: tt.Any, agent_states: AgentStates) -> \
|
|
122
|
+
tt.Tuple[torch.Tensor, AgentStates]:
|
|
123
|
+
assert torch.is_tensor(net_out)
|
|
124
|
+
return net_out, agent_states
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TargetNet:
|
|
128
|
+
"""
|
|
129
|
+
Wrapper around model which provides copy of it instead of trained weights
|
|
130
|
+
"""
|
|
131
|
+
def __init__(self, model: nn.Module):
|
|
132
|
+
self.model = model
|
|
133
|
+
self.target_model = copy.deepcopy(model)
|
|
134
|
+
|
|
135
|
+
def sync(self):
|
|
136
|
+
self.target_model.load_state_dict(self.model.state_dict())
|
|
137
|
+
|
|
138
|
+
def alpha_sync(self, alpha):
|
|
139
|
+
"""
|
|
140
|
+
Blend params of target net with params from the model
|
|
141
|
+
:param alpha:
|
|
142
|
+
"""
|
|
143
|
+
assert isinstance(alpha, float)
|
|
144
|
+
assert 0.0 < alpha <= 1.0
|
|
145
|
+
state = self.model.state_dict()
|
|
146
|
+
tgt_state = self.target_model.state_dict()
|
|
147
|
+
for k, v in state.items():
|
|
148
|
+
tgt_state[k] = tgt_state[k] * alpha + (1 - alpha) * v
|
|
149
|
+
self.target_model.load_state_dict(tgt_state)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class PolicyAgent(NNAgent):
|
|
153
|
+
"""
|
|
154
|
+
Policy agent gets action probabilities from the model and samples actions from it
|
|
155
|
+
"""
|
|
156
|
+
def __init__(self, model: nn.Module,
|
|
157
|
+
action_selector: actions.ActionSelector = actions.ProbabilityActionSelector(),
|
|
158
|
+
device: torch.device = CPU_DEVICE, apply_softmax: bool = False,
|
|
159
|
+
preprocessor: Preprocessor = default_states_preprocessor):
|
|
160
|
+
super().__init__(model=model, action_selector=action_selector, device=device,
|
|
161
|
+
preprocessor=preprocessor)
|
|
162
|
+
self.apply_softmax = apply_softmax
|
|
163
|
+
|
|
164
|
+
def _net_filter(self, net_out: tt.Any, agent_states: AgentStates) -> \
|
|
165
|
+
tt.Tuple[torch.Tensor, AgentStates]:
|
|
166
|
+
assert torch.is_tensor(net_out)
|
|
167
|
+
if self.apply_softmax:
|
|
168
|
+
return F.softmax(net_out, dim=1), agent_states
|
|
169
|
+
return net_out, agent_states
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ActorCriticAgent(NNAgent):
|
|
173
|
+
"""
|
|
174
|
+
Policy agent which returns policy and value tensors from observations. Value are stored in agent's state
|
|
175
|
+
and could be reused for rollouts calculations by ExperienceSource.
|
|
176
|
+
"""
|
|
177
|
+
def __init__(self, model: nn.Module,
|
|
178
|
+
action_selector: actions.ActionSelector = actions.ProbabilityActionSelector(),
|
|
179
|
+
device: torch.device = CPU_DEVICE, apply_softmax: bool = False,
|
|
180
|
+
preprocessor: Preprocessor = default_states_preprocessor):
|
|
181
|
+
super().__init__(model=model, action_selector=action_selector, device=device, preprocessor=preprocessor)
|
|
182
|
+
self.apply_softmax = apply_softmax
|
|
183
|
+
|
|
184
|
+
def _net_filter(self, net_out: tt.Any, agent_states: AgentStates) -> \
|
|
185
|
+
tt.Tuple[torch.Tensor, AgentStates]:
|
|
186
|
+
assert isinstance(net_out, tuple)
|
|
187
|
+
policy_v, values_v = net_out
|
|
188
|
+
assert torch.is_tensor(policy_v)
|
|
189
|
+
assert torch.is_tensor(values_v)
|
|
190
|
+
agent_states = values_v.data.squeeze().cpu().numpy().tolist()
|
|
191
|
+
if self.apply_softmax:
|
|
192
|
+
return F.softmax(policy_v, dim=1), agent_states
|
|
193
|
+
return policy_v, agent_states
|