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 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
+
@@ -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