simonbb 0.0.2__tar.gz → 0.0.3__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.3/PKG-INFO ADDED
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: simonbb
3
+ Version: 0.0.3
4
+ Summary: A small package to facilitate teaching AI/ML 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: langchain-chroma~=1.1.0
13
+ Requires-Dist: langchain-community~=0.4.1
14
+ Requires-Dist: langchain-core~=1.2.23
15
+ Requires-Dist: langchain-huggingface~=1.2.1
16
+ Requires-Dist: langchain~=1.2.13
17
+ Requires-Dist: langgraph~=1.1.3
18
+ Requires-Dist: matplotlib~=3.10.8
19
+ Requires-Dist: numpy~=2.4.0
20
+ Requires-Dist: sentence-transformers~=5.3.0
21
+ Requires-Dist: torchvision~=0.24.1
22
+ Requires-Dist: torch~=2.9.1
23
+ Requires-Dist: transformers~=4.57.3
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Background
27
+
28
+ This is a pet project out of my own teaching needs at University of Rochester.
29
+ I mainly teach these 2 courses to MBA/MS students who lack technical background.
30
+
31
+ - CIS 433: AI and Deep Learning
32
+ - CIS 438: Agentic AI Applications
33
+
34
+ Accordingly, this package contains 2 modules.
35
+
36
+ - `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.
37
+ - `simonbb.cis438`: this module is useful for anyone who teaches agentic AI using langchain.
38
+
@@ -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,35 @@
1
+ [build-system]
2
+ requires = ["hatchling >= 1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "simonbb"
7
+ version = "0.0.3"
8
+ authors = [
9
+ { name="Huaxia Rui", email="huaxia.rui@simon.rochester.edu" },
10
+ ]
11
+ description = "A small package to facilitate teaching AI/ML 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
+ ]
34
+ [project.urls]
35
+ Homepage = "https://simonbb.com"
@@ -198,7 +198,4 @@ if __name__ == "__main__":
198
198
  out = generate( model=model, idx=encoded_tensor, max_new_tokens=6, context_size=cfg["context_length"] )
199
199
 
200
200
  print("Output:", out)
201
- print( tokenizer.decode(out.squeeze(0).tolist()) )
202
-
203
- else:
204
- print(f'GPT imported from local file "{__name__}.py"')
201
+ print( tokenizer.decode(out.squeeze(0).tolist()) )
@@ -209,7 +209,7 @@ class Transformer(nn.Module):
209
209
  return self.final_layer(x) # (batch_size, target_len, tgt_vocab_size)
210
210
 
211
211
 
212
- #%% Sanity Check: run from the parent folder of rui as "python -m rui.torch.transformer" since we used relative import
212
+ #%% Sanity Check: run from the parent folder of simonbb as "python -m simonbb.cis433.transformer" since we used relative import
213
213
  if __name__ == "__main__":
214
214
 
215
215
  from ..utils import TextVectorizer
@@ -248,6 +248,4 @@ if __name__ == "__main__":
248
248
  decoder.eval()
249
249
  with torch.no_grad():
250
250
  print(f"Encoder output shape: {encoder(x).shape}")
251
- print(f"Decoder output shape: {decoder(x=x, context=x).shape}")
252
- else:
253
- print(f'Transformer imported from local file "{__name__}.py"')
251
+ print(f"Decoder output shape: {decoder(x=x, context=x).shape}")
@@ -5,174 +5,15 @@ A collection of utility functions for teaching deep learning
5
5
 
6
6
  import os
7
7
  import time
8
- import numpy as np
9
8
  import copy
10
- import re
11
- import pickle
12
- import json
13
- from collections import Counter
14
- import urllib.request
15
9
 
10
+ import numpy as np
16
11
  import matplotlib.pyplot as plt
17
-
18
12
  import torch
19
13
  import torch.nn as nn
20
14
 
21
15
  #%%
22
16
 
23
- class TextVectorizer:
24
- def __init__(self, max_tokens=20000, output_mode="int", output_sequence_length=None, ngrams=None, standardize=None):
25
- self.max_tokens = max_tokens
26
- self.output_mode = output_mode
27
- self.output_sequence_length = output_sequence_length
28
- self.ngrams = ngrams
29
- self.standardize = standardize
30
- self.vocab = None
31
- self.word_to_idx = None
32
- self.idx_to_word = None
33
- self.idf_weights = None
34
-
35
- def _tokenize(self, text):
36
- if self.standardize:
37
- text = self.standardize(text)
38
- else:
39
- text = text.lower()
40
- text = re.sub(r'[^\w\s]', ' ', text)
41
- tokens = text.split()
42
- if self.ngrams and self.ngrams >= 2:
43
- ngram_tokens = []
44
- for n in range(1, self.ngrams + 1):
45
- for i in range( len(tokens) - n + 1 ):
46
- ngram_tokens.append(' '.join(tokens[i:i + n]))
47
- return ngram_tokens
48
- return tokens
49
-
50
- def adapt(self, texts):
51
- """Build vocabulary from texts"""
52
- token_counter = Counter()
53
- doc_freq = Counter() # for TF-IDF
54
- num_docs = 0
55
- print("Adapting TextVectorizer...")
56
- for text in texts:
57
- num_docs += 1
58
- tokens = self._tokenize(text)
59
- token_counter.update(tokens)
60
- doc_freq.update(set(tokens))
61
- # Reserve 0 for padding, 1 for unknown, hence minus 2 to account for them.
62
- most_common = token_counter.most_common(self.max_tokens - 2 if self.max_tokens else None) # most_common(n=None) returns all elements in the counter if n is omitted or None.
63
- self.vocab = ['<pad>', '<unk>'] + [word for word, _ in most_common] # 0 for padding, 1 for unknown
64
- self.word_to_idx = {word: idx for idx, word in enumerate(self.vocab)}
65
- self.idx_to_word = {idx: word for idx, word in enumerate(self.vocab)}
66
- # Compute IDF weights for tf-idf mode
67
- if self.output_mode == "tf_idf":
68
- self.idf_weights = np.zeros(len(self.vocab))
69
- for word, idx in self.word_to_idx.items():
70
- df = doc_freq.get(word, 0) + 1 # add 1 for smoothing
71
- self.idf_weights[idx] = np.log(num_docs / df) + 1
72
- print(f"Vocabulary size: {len(self.vocab)}")
73
-
74
- def __call__(self, texts):
75
- if isinstance(texts, str):
76
- texts = [texts]
77
- if self.output_mode == "int":
78
- return self._vectorize_int(texts)
79
- elif self.output_mode == "multi_hot":
80
- return self._vectorize_multi_hot(texts)
81
- elif self.output_mode == "tf_idf":
82
- return self._vectorize_tfidf(texts)
83
-
84
- def _vectorize_int(self, texts):
85
- batch_indices = []
86
- for text in texts:
87
- tokens = self._tokenize(text)
88
- indices = [self.word_to_idx.get(t, 1) for t in tokens] # 1 is <unk>
89
- if self.output_sequence_length:
90
- if len(indices) < self.output_sequence_length:
91
- indices = indices + [0] * (self.output_sequence_length - len(indices))
92
- else:
93
- indices = indices[:self.output_sequence_length]
94
- batch_indices.append(indices)
95
- return np.array(batch_indices)
96
-
97
- def _vectorize_multi_hot(self, texts):
98
- result = np.zeros( (len(texts), len(self.vocab)), dtype=np.float32 )
99
- for i, text in enumerate(texts):
100
- tokens = self._tokenize(text)
101
- for t in tokens:
102
- idx = self.word_to_idx.get(t, 1)
103
- if idx < len(self.vocab):
104
- result[i, idx] = 1
105
- return result
106
-
107
- def _vectorize_tfidf(self, texts):
108
- result = np.zeros( (len(texts), len(self.vocab)), dtype=np.float32 )
109
- for i, text in enumerate(texts):
110
- tokens = self._tokenize(text)
111
- tf = Counter(tokens)
112
- for t, count in tf.items():
113
- idx = self.word_to_idx.get(t, 1)
114
- if idx < len(self.vocab):
115
- result[i, idx] = count * self.idf_weights[idx]
116
- return result
117
-
118
- def get_vocabulary(self):
119
- return self.vocab
120
-
121
- def save(self, path):
122
- vec = {'max_tokens': self.max_tokens, 'output_sequence_length': self.output_sequence_length,
123
- 'vocab': self.vocab, 'word_to_idx': self.word_to_idx, 'idx_to_word': self.idx_to_word,
124
- 'has_standardizer': self.standardize is not None}
125
- with open(path, 'wb') as f:
126
- pickle.dump(vec, f)
127
-
128
- # transforms a method into a class method (i.e., bound to the class rather than an individual instance)
129
- @classmethod
130
- def load(cls, path, standardize=None):
131
- data = pickle.load(path, weights_only=False)
132
- vectorizer = cls( max_tokens=data['max_tokens'], output_sequence_length=data['output_sequence_length'],
133
- standardize=standardize if data.get('has_standardize') else None )
134
- vectorizer.vocab = data['vocab']
135
- vectorizer.word_to_idx = data['word_to_idx']
136
- vectorizer.idx_to_word = data['idx_to_word']
137
- return vectorizer
138
-
139
-
140
- def query_ollama(prompt, model="llama3", url="http://localhost:11434/api/chat"):
141
- # Create the data payload as a dictionary
142
- data = {
143
- "model": model,
144
- "messages": [
145
- {"role": "user", "content": prompt}
146
- ],
147
- "options": { # Settings below are required for deterministic responses
148
- "seed": 123,
149
- "temperature": 0,
150
- "num_ctx": 2048
151
- }
152
- }
153
-
154
- # Convert the dictionary to a JSON formatted string and encode it to bytes
155
- payload = json.dumps(data).encode("utf-8")
156
-
157
- # Create a request object, setting the method to POST and adding necessary headers
158
- request = urllib.request.Request(url, data=payload, method="POST")
159
- request.add_header("Content-Type", "application/json")
160
-
161
- # Send the request and capture the response
162
- response_data = ""
163
- with urllib.request.urlopen(request) as response:
164
- # Read and decode the response
165
- while True:
166
- line = response.readline().decode("utf-8")
167
- if not line:
168
- break
169
- response_json = json.loads(line)
170
- response_data += response_json["message"]["content"]
171
-
172
- return response_data
173
-
174
- #%%
175
-
176
17
  class ModelCheckpoint:
177
18
  def __init__(self, filepath, monitor="val_loss", save_best_only=True, save_optimizer_state=False):
178
19
  self.filepath = filepath
@@ -470,10 +311,12 @@ def evaluate(model, loader, device, loss_fn=nn.CrossEntropyLoss(), accuracy_fn=N
470
311
  val_accuracy = val_accuracy.item() # extract the value of a single-item tensor
471
312
  return {'loss':val_loss, 'accuracy':val_accuracy}
472
313
 
473
- #%%
314
+ #%% Sanity Check: run from the parent folder of simonbb as "python -m simonbb.cis433.utils" since we used relative import
474
315
 
475
316
  if __name__ == "__main__":
476
317
 
318
+ from ..utils import TextVectorizer
319
+
477
320
  vectorizer = TextVectorizer( max_tokens=10, output_mode="int" )
478
321
  txts = ['how are you']
479
322
  vectorizer.adapt( txts )
@@ -1,3 +1,6 @@
1
+ # module dependencies: torch torchvision langchain_core langchain langgraph langchain_community langchain_chroma langchain_huggingface sentence_transformers
2
+ # additional packages for class projects: langchain_ollama langchain_text_splitters unstructured streamlit_javascript
3
+
1
4
  import os
2
5
  import re
3
6
  from typing import Annotated, Literal, TypedDict, get_args
@@ -17,10 +20,9 @@ from langgraph.graph.message import add_messages
17
20
 
18
21
  from langchain_chroma import Chroma
19
22
  from langchain_huggingface import HuggingFaceEmbeddings
20
-
23
+ from langchain_community.document_loaders import PyPDFLoader, UnstructuredHTMLLoader
21
24
  from sentence_transformers import CrossEncoder
22
25
 
23
- vstore_path = f'{os.getenv("HOME")}/vstore'
24
26
  embedding_model = HuggingFaceEmbeddings(
25
27
  model_name="BAAI/bge-large-en-v1.5",
26
28
  model_kwargs={'device': device},
@@ -30,8 +32,7 @@ embedding_model = HuggingFaceEmbeddings(
30
32
  #%%
31
33
 
32
34
  def load_documents(path):
33
- """Load documents from a file or directory."""
34
- from langchain_community.document_loaders import PyPDFLoader, UnstructuredHTMLLoader
35
+ """Load documents from a file or directory."""
35
36
 
36
37
  docs = []
37
38
  success_cnt = 0
@@ -78,7 +79,7 @@ def load_documents(path):
78
79
  return docs, success_cnt, page_cnt
79
80
 
80
81
  reranker_model = CrossEncoder("BAAI/bge-reranker-large")
81
- def _getRetriever( knowledge_base, query_llm=None, k=3 ):
82
+ def _getRetriever( knowledge_base, query_llm=None, k=3, vstore_path = f'{os.getenv("HOME")}/vstore' ):
82
83
  store = Chroma( persist_directory=f'{vstore_path}/{knowledge_base}/BAAI_db/chroma_vector_store', embedding_function=embedding_model )
83
84
  base_retriever = store.as_retriever(search_kwargs={"k": k}) # a vector store becomes a retriever through the as_retriever() method
84
85
  if query_llm==None:
@@ -0,0 +1,158 @@
1
+ import re
2
+ import pickle
3
+ import json
4
+ from collections import Counter
5
+ import urllib.request
6
+ import numpy as np
7
+
8
+
9
+ class TextVectorizer:
10
+ def __init__(self, max_tokens=20000, output_mode="int", output_sequence_length=None, ngrams=None, standardize=None):
11
+ self.max_tokens = max_tokens
12
+ self.output_mode = output_mode
13
+ self.output_sequence_length = output_sequence_length
14
+ self.ngrams = ngrams
15
+ self.standardize = standardize
16
+ self.vocab = None
17
+ self.word_to_idx = None
18
+ self.idx_to_word = None
19
+ self.idf_weights = None
20
+
21
+ def _tokenize(self, text):
22
+ if self.standardize:
23
+ text = self.standardize(text)
24
+ else:
25
+ text = text.lower()
26
+ text = re.sub(r'[^\w\s]', ' ', text)
27
+ tokens = text.split()
28
+ if self.ngrams and self.ngrams >= 2:
29
+ ngram_tokens = []
30
+ for n in range(1, self.ngrams + 1):
31
+ for i in range( len(tokens) - n + 1 ):
32
+ ngram_tokens.append(' '.join(tokens[i:i + n]))
33
+ return ngram_tokens
34
+ return tokens
35
+
36
+ def adapt(self, texts):
37
+ """Build vocabulary from texts"""
38
+ token_counter = Counter()
39
+ doc_freq = Counter() # for TF-IDF
40
+ num_docs = 0
41
+ print("Adapting TextVectorizer...")
42
+ for text in texts:
43
+ num_docs += 1
44
+ tokens = self._tokenize(text)
45
+ token_counter.update(tokens)
46
+ doc_freq.update(set(tokens))
47
+ # Reserve 0 for padding, 1 for unknown, hence minus 2 to account for them.
48
+ most_common = token_counter.most_common(self.max_tokens - 2 if self.max_tokens else None) # most_common(n=None) returns all elements in the counter if n is omitted or None.
49
+ self.vocab = ['<pad>', '<unk>'] + [word for word, _ in most_common] # 0 for padding, 1 for unknown
50
+ self.word_to_idx = {word: idx for idx, word in enumerate(self.vocab)}
51
+ self.idx_to_word = {idx: word for idx, word in enumerate(self.vocab)}
52
+ # Compute IDF weights for tf-idf mode
53
+ if self.output_mode == "tf_idf":
54
+ self.idf_weights = np.zeros(len(self.vocab))
55
+ for word, idx in self.word_to_idx.items():
56
+ df = doc_freq.get(word, 0) + 1 # add 1 for smoothing
57
+ self.idf_weights[idx] = np.log(num_docs / df) + 1
58
+ print(f"Vocabulary size: {len(self.vocab)}")
59
+
60
+ def __call__(self, texts):
61
+ if isinstance(texts, str):
62
+ texts = [texts]
63
+ if self.output_mode == "int":
64
+ return self._vectorize_int(texts)
65
+ elif self.output_mode == "multi_hot":
66
+ return self._vectorize_multi_hot(texts)
67
+ elif self.output_mode == "tf_idf":
68
+ return self._vectorize_tfidf(texts)
69
+
70
+ def _vectorize_int(self, texts):
71
+ batch_indices = []
72
+ for text in texts:
73
+ tokens = self._tokenize(text)
74
+ indices = [self.word_to_idx.get(t, 1) for t in tokens] # 1 is <unk>
75
+ if self.output_sequence_length:
76
+ if len(indices) < self.output_sequence_length:
77
+ indices = indices + [0] * (self.output_sequence_length - len(indices))
78
+ else:
79
+ indices = indices[:self.output_sequence_length]
80
+ batch_indices.append(indices)
81
+ return np.array(batch_indices)
82
+
83
+ def _vectorize_multi_hot(self, texts):
84
+ result = np.zeros( (len(texts), len(self.vocab)), dtype=np.float32 )
85
+ for i, text in enumerate(texts):
86
+ tokens = self._tokenize(text)
87
+ for t in tokens:
88
+ idx = self.word_to_idx.get(t, 1)
89
+ if idx < len(self.vocab):
90
+ result[i, idx] = 1
91
+ return result
92
+
93
+ def _vectorize_tfidf(self, texts):
94
+ result = np.zeros( (len(texts), len(self.vocab)), dtype=np.float32 )
95
+ for i, text in enumerate(texts):
96
+ tokens = self._tokenize(text)
97
+ tf = Counter(tokens)
98
+ for t, count in tf.items():
99
+ idx = self.word_to_idx.get(t, 1)
100
+ if idx < len(self.vocab):
101
+ result[i, idx] = count * self.idf_weights[idx]
102
+ return result
103
+
104
+ def get_vocabulary(self):
105
+ return self.vocab
106
+
107
+ def save(self, path):
108
+ vec = {'max_tokens': self.max_tokens, 'output_sequence_length': self.output_sequence_length,
109
+ 'vocab': self.vocab, 'word_to_idx': self.word_to_idx, 'idx_to_word': self.idx_to_word,
110
+ 'has_standardizer': self.standardize is not None}
111
+ with open(path, 'wb') as f:
112
+ pickle.dump(vec, f)
113
+
114
+ # transforms a method into a class method (i.e., bound to the class rather than an individual instance)
115
+ @classmethod
116
+ def load(cls, path, standardize=None):
117
+ data = pickle.load(path, weights_only=False)
118
+ vectorizer = cls( max_tokens=data['max_tokens'], output_sequence_length=data['output_sequence_length'],
119
+ standardize=standardize if data.get('has_standardize') else None )
120
+ vectorizer.vocab = data['vocab']
121
+ vectorizer.word_to_idx = data['word_to_idx']
122
+ vectorizer.idx_to_word = data['idx_to_word']
123
+ return vectorizer
124
+
125
+
126
+ def query_ollama(prompt, model="llama3", url="http://localhost:11434/api/chat"):
127
+ # Create the data payload as a dictionary
128
+ data = {
129
+ "model": model,
130
+ "messages": [
131
+ {"role": "user", "content": prompt}
132
+ ],
133
+ "options": { # Settings below are required for deterministic responses
134
+ "seed": 123,
135
+ "temperature": 0,
136
+ "num_ctx": 2048
137
+ }
138
+ }
139
+
140
+ # Convert the dictionary to a JSON formatted string and encode it to bytes
141
+ payload = json.dumps(data).encode("utf-8")
142
+
143
+ # Create a request object, setting the method to POST and adding necessary headers
144
+ request = urllib.request.Request(url, data=payload, method="POST")
145
+ request.add_header("Content-Type", "application/json")
146
+
147
+ # Send the request and capture the response
148
+ response_data = ""
149
+ with urllib.request.urlopen(request) as response:
150
+ # Read and decode the response
151
+ while True:
152
+ line = response.readline().decode("utf-8")
153
+ if not line:
154
+ break
155
+ response_json = json.loads(line)
156
+ response_data += response_json["message"]["content"]
157
+
158
+ return response_data
simonbb-0.0.2/PKG-INFO DELETED
@@ -1,16 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: simonbb
3
- Version: 0.0.2
4
- Summary: A small package for teaching AI/ML courses
5
- Project-URL: Homepage, https://simonbb.com
6
- Project-URL: Issues, https://simonbb.com/issues
7
- Author-email: Huaxia Rui <huaxia.rui@simon.rochester.edu>
8
- License-Expression: MIT
9
- License-File: LICENSE
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Programming Language :: Python :: 3
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
-
15
- # What's this package for?
16
- Anyone who teaches deep learning using PyTorch but doesn't want to bother students with too much nitty-gritty details.
simonbb-0.0.2/README.md DELETED
@@ -1,2 +0,0 @@
1
- # What's this package for?
2
- Anyone who teaches deep learning using PyTorch but doesn't want to bother students with too much nitty-gritty details.
@@ -1,23 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling >= 1.26"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "simonbb"
7
- version = "0.0.2"
8
- authors = [
9
- { name="Huaxia Rui", email="huaxia.rui@simon.rochester.edu" },
10
- ]
11
- description = "A small package for teaching AI/ML 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
-
21
- [project.urls]
22
- Homepage = "https://simonbb.com"
23
- Issues = "https://simonbb.com/issues"
File without changes
File without changes