zeechimp commited on
Commit
dada0ef
·
verified ·
1 Parent(s): 48214b0

Delete train_hv_intent.py

Browse files
Files changed (1) hide show
  1. train_hv_intent.py +0 -391
train_hv_intent.py DELETED
@@ -1,391 +0,0 @@
1
- """
2
- train_hv_intent.py
3
-
4
- Single-file trainer for the hv-intent-router model, with multi-seed
5
- genetic algorithm training. Runs N independent GA instances with
6
- different seeds and keeps the best.
7
-
8
- Produces:
9
- codebooks.npz - vocab_codebook, intent_codebook, memory
10
- config.json - model metadata including parameter counts
11
-
12
- Run:
13
- python train_hv_intent.py
14
- python train_hv_intent.py --output ./my_model_dir --seeds 8
15
-
16
- Runtime: ~50 seconds for 8 seeds on a laptop CPU.
17
- Dependencies: numpy only.
18
- """
19
-
20
- import numpy as np
21
- import json
22
- import time
23
- import argparse
24
- from pathlib import Path
25
-
26
-
27
- # ===========================================================================
28
- # Configuration
29
- # ===========================================================================
30
-
31
- D_DEFAULT = 4096
32
- POP_SIZE = 12
33
- GENERATIONS = 50
34
- ELITE = 3
35
- MUTATION_RATE = 0.005
36
- TRAIN_PER_INTENT = 7
37
- N_SEEDS = 8
38
- SEED_BASE = 1
39
- SEED_STEP = 7919
40
- SEED_DATA = 42
41
-
42
- VOCAB = "abcdefghijklmnopqrstuvwxyz"
43
-
44
-
45
- # ===========================================================================
46
- # Training data
47
- # ===========================================================================
48
-
49
- INTENT_DATA = {
50
- "greeting": [
51
- "hello there",
52
- "hi how are you",
53
- "good morning",
54
- "good evening",
55
- "hey what is up",
56
- "nice to meet you",
57
- "how are you doing",
58
- "what is going on",
59
- "hello friend",
60
- "good afternoon",
61
- ],
62
- "math": [
63
- "what is two plus two",
64
- "calculate ten times three",
65
- "what is the square root of nine",
66
- "add five and seven",
67
- "subtract three from ten",
68
- "what is twenty divided by four",
69
- "multiply six by eight",
70
- "what is one hundred minus forty",
71
- "calculate the sum of one and two",
72
- "what is the product",
73
- ],
74
- "reminder": [
75
- "remind me to call mom",
76
- "set an alarm for six",
77
- "remind me to buy milk",
78
- "add a reminder for tomorrow",
79
- "set a timer for ten minutes",
80
- "remind me about the meeting",
81
- "create a to do item",
82
- "remind me to water the plants",
83
- "set an alarm for morning",
84
- "remind me to pick up kids",
85
- ],
86
- "time": [
87
- "what time is it",
88
- "tell me the time",
89
- "how late is it",
90
- "what is the current hour",
91
- "when is the meeting",
92
- "what day is today",
93
- "what is the date",
94
- "how many hours until noon",
95
- "when does the sun set",
96
- "when does the store close",
97
- ],
98
- "weather": [
99
- "what is the weather today",
100
- "will it rain tomorrow",
101
- "is it sunny outside",
102
- "what is the forecast",
103
- "how hot is it",
104
- "is it cold today",
105
- "will it snow tonight",
106
- "how windy is it",
107
- "what is the temperature",
108
- "is it cloudy",
109
- ],
110
- }
111
-
112
-
113
- # ===========================================================================
114
- # Primitives
115
- # ===========================================================================
116
-
117
- def bundle(stacked_int32):
118
- """Majority vote over ±1 array. stack shape (N, D)."""
119
- s = stacked_int32.sum(axis=0)
120
- return np.where(s >= 0, np.int8(1), np.int8(-1))
121
-
122
-
123
- def mutate(K, rng, rate):
124
- mask = rng.random(K.shape) < rate
125
- return np.where(mask, -K, K).astype(np.int8)
126
-
127
-
128
- def crossover(K1, K2, rng):
129
- mask = rng.random(K1.shape) < 0.5
130
- return np.where(mask, K1, K2).astype(np.int8)
131
-
132
-
133
- # ===========================================================================
134
- # Trainer
135
- # ===========================================================================
136
-
137
- def train(output_dir, D=D_DEFAULT, n_seeds=N_SEEDS, verbose=True):
138
- output_dir = Path(output_dir)
139
- output_dir.mkdir(parents=True, exist_ok=True)
140
-
141
- labels = sorted(INTENT_DATA.keys())
142
- chars = sorted(set("".join("".join(v) for v in INTENT_DATA.values())))
143
- V = len(chars)
144
- C = len(labels)
145
- c2i = {c: i for i, c in enumerate(chars)}
146
-
147
- if verbose:
148
- print(f"Vocabulary: {V} characters")
149
- print(f"Intents: {C} ({', '.join(labels)})")
150
- print(f"Dimension: {D}")
151
- print()
152
-
153
- # Train / test split
154
- train_pairs = []
155
- test_pairs = []
156
- for idx, label in enumerate(labels):
157
- for i, phrase in enumerate(INTENT_DATA[label]):
158
- if i < TRAIN_PER_INTENT:
159
- train_pairs.append((phrase, idx))
160
- else:
161
- test_pairs.append((phrase, idx))
162
-
163
- if verbose:
164
- print(f"Train pairs: {len(train_pairs)}")
165
- print(f"Test pairs: {len(test_pairs)}")
166
- print()
167
-
168
- def char_indices(phrase):
169
- return np.array([c2i[c] for c in phrase if c in c2i], dtype=np.int32)
170
-
171
- train_idx = [(char_indices(p), t) for p, t in train_pairs]
172
- test_idx = [(char_indices(p), t) for p, t in test_pairs]
173
-
174
- # Fixed intent codebook (same across seeds)
175
- rng_intents = np.random.default_rng(SEED_DATA)
176
- intent_codebook = rng_intents.choice(
177
- np.array([-1, 1], dtype=np.int8), size=(C, D))
178
-
179
- def encode(indices, K):
180
- if len(indices) == 0:
181
- return np.zeros(D, dtype=np.int8)
182
- v = K[indices].astype(np.int32).sum(axis=0)
183
- return np.where(v >= 0, np.int8(1), np.int8(-1))
184
-
185
- def build_memory(K):
186
- items = []
187
- for idx_arr, target in train_idx:
188
- p_hv = encode(idx_arr, K)
189
- bound = p_hv.astype(np.int32) * intent_codebook[target].astype(np.int32)
190
- items.append(bound)
191
- return bundle(np.stack(items).astype(np.int32))
192
-
193
- def evaluate(K, mem=None):
194
- if mem is None:
195
- mem = build_memory(K)
196
- correct = 0
197
- for idx_arr, target in test_idx:
198
- p_hv = encode(idx_arr, K)
199
- retrieved = mem.astype(np.int32) * p_hv.astype(np.int32)
200
- sims = (retrieved.astype(np.float32) @
201
- intent_codebook.astype(np.float32).T)
202
- if int(np.argmax(sims)) == target:
203
- correct += 1
204
- return correct / len(test_idx)
205
-
206
- # Random baseline (average over 10 random codebooks)
207
- rng_base = np.random.default_rng(SEED_BASE + 999999)
208
- base_accs = []
209
- for _ in range(10):
210
- K_rand = rng_base.choice(np.array([-1, 1], dtype=np.int8), size=(V, D))
211
- base_accs.append(evaluate(K_rand))
212
- acc_random = float(np.mean(base_accs))
213
-
214
- if verbose:
215
- print(f"Random baseline: {acc_random:.1%} "
216
- f"(mean of 10 random codebooks)")
217
- print()
218
-
219
- # -----------------------------------------------------------------------
220
- # Multi-seed GA
221
- # -----------------------------------------------------------------------
222
-
223
- def train_one_seed(seed):
224
- rng = np.random.default_rng(seed)
225
- pop = [rng.choice(np.array([-1, 1], dtype=np.int8), size=(V, D))
226
- for _ in range(POP_SIZE)]
227
- best_K_local = None
228
- best_acc_local = 0.0
229
- best_mem_local = None
230
- for gen in range(GENERATIONS):
231
- fits = np.array([evaluate(K) for K in pop])
232
- order = np.argsort(fits)[::-1]
233
- if fits[order[0]] > best_acc_local:
234
- best_acc_local = float(fits[order[0]])
235
- best_K_local = pop[order[0]].copy()
236
- best_mem_local = build_memory(best_K_local)
237
- survivors = [pop[i] for i in order[:ELITE]]
238
- while len(survivors) < POP_SIZE:
239
- i1, i2 = rng.choice(POP_SIZE, 2, replace=False)
240
- p1 = pop[i1] if fits[i1] >= fits[i2] else pop[i2]
241
- j1, j2 = rng.choice(POP_SIZE, 2, replace=False)
242
- p2 = pop[j1] if fits[j1] >= fits[j2] else pop[j2]
243
- child = crossover(p1, p2, rng)
244
- child = mutate(child, rng, MUTATION_RATE)
245
- survivors.append(child)
246
- pop = survivors
247
- return best_acc_local, best_K_local, best_mem_local
248
-
249
- if verbose:
250
- print(f"Running GA with {n_seeds} seeds, "
251
- f"{GENERATIONS} generations each")
252
- print()
253
- print(f" {'seed':>8} | {'accuracy':>9} | {'time':>7}")
254
- print(" " + "-" * 32)
255
-
256
- best_acc = 0.0
257
- best_K = None
258
- best_memory = None
259
- all_accs = []
260
- seed_results = []
261
-
262
- t_all = time.perf_counter()
263
- for s in range(n_seeds):
264
- seed = SEED_BASE + s * SEED_STEP
265
- t0 = time.perf_counter()
266
- acc_s, K_s, mem_s = train_one_seed(seed)
267
- dt = time.perf_counter() - t0
268
- all_accs.append(acc_s)
269
- seed_results.append({"seed": seed, "accuracy": acc_s, "time": dt})
270
- if verbose:
271
- print(f" {seed:>8} | {acc_s:>9.1%} | {dt:>6.1f}s")
272
- if acc_s > best_acc:
273
- best_acc = acc_s
274
- best_K = K_s
275
- best_memory = mem_s
276
-
277
- total_time = time.perf_counter() - t_all
278
-
279
- if verbose:
280
- print()
281
- print(f"Best accuracy: {best_acc:.1%}")
282
- print(f"Mean accuracy: {np.mean(all_accs):.1%}")
283
- print(f"Std: {np.std(all_accs):.1%}")
284
- print(f"Min / Max: {np.min(all_accs):.1%} / {np.max(all_accs):.1%}")
285
- print(f"Total GA time: {total_time:.1f}s")
286
- print()
287
-
288
- # -----------------------------------------------------------------------
289
- # Parameter counts
290
- # -----------------------------------------------------------------------
291
-
292
- n_vocab_params = V * D
293
- n_intent_params = C * D
294
- n_total_params = n_vocab_params + n_intent_params
295
-
296
- # -----------------------------------------------------------------------
297
- # Save
298
- # -----------------------------------------------------------------------
299
-
300
- np.savez_compressed(
301
- output_dir / "codebooks.npz",
302
- vocab_codebook=best_K,
303
- intent_codebook=intent_codebook,
304
- memory=best_memory,
305
- )
306
-
307
- npz_size = (output_dir / "codebooks.npz").stat().st_size
308
-
309
- config = {
310
- "model_type": "hv-intent-router",
311
- "architecture": "evolved-hypervector",
312
- "dimension": D,
313
- "vocabulary_size": V,
314
- "num_intents": C,
315
- "intent_labels": labels,
316
- "vocabulary": "".join(chars),
317
- "encoding": "sum-bundle",
318
- "bundling": "majority-vote",
319
- "similarity": "hamming",
320
- "num_parameters": n_total_params,
321
- "num_parameters_human": (
322
- f"{n_total_params/1e6:.1f}M" if n_total_params >= 1e6
323
- else f"{n_total_params/1e3:.1f}K" if n_total_params >= 1e3
324
- else str(n_total_params)
325
- ),
326
- "model_size_bytes": int(D // 8),
327
- "codebook_size_bytes": int(n_total_params // 8),
328
- "total_size_bytes": int(npz_size),
329
- "training": {
330
- "method": "multi-seed-genetic-algorithm",
331
- "population_size": POP_SIZE,
332
- "generations": GENERATIONS,
333
- "elite_count": ELITE,
334
- "mutation_rate": MUTATION_RATE,
335
- "crossover_rate": 0.5,
336
- "selection": "tournament-2",
337
- "n_seeds": n_seeds,
338
- "seed_base": SEED_BASE,
339
- "seed_step": SEED_STEP,
340
- "seed_data": SEED_DATA,
341
- },
342
- "metrics": {
343
- "test_accuracy": float(best_acc),
344
- "test_accuracy_mean": float(np.mean(all_accs)),
345
- "test_accuracy_std": float(np.std(all_accs)),
346
- "test_accuracy_min": float(np.min(all_accs)),
347
- "test_accuracy_max": float(np.max(all_accs)),
348
- "random_baseline": float(acc_random),
349
- "total_training_time_seconds": float(total_time),
350
- },
351
- "seed_results": seed_results,
352
- "weights_file": "codebooks.npz",
353
- }
354
-
355
- with open(output_dir / "config.json", "w") as f:
356
- json.dump(config, f, indent=2)
357
-
358
- if verbose:
359
- print(f"Saved codebooks.npz ({npz_size:,} bytes)")
360
- print(f"Saved config.json")
361
- print(f"Output directory: {output_dir.resolve()}")
362
-
363
- return config
364
-
365
-
366
- # ===========================================================================
367
- # CLI
368
- # ===========================================================================
369
-
370
- def main():
371
- parser = argparse.ArgumentParser(
372
- description="Train the hv-intent-router model with multi-seed GA.")
373
- parser.add_argument(
374
- "--output", type=str, default=".",
375
- help="Output directory for codebooks.npz and config.json")
376
- parser.add_argument(
377
- "--dim", type=int, default=D_DEFAULT,
378
- help=f"Hypervector dimension (default: {D_DEFAULT})")
379
- parser.add_argument(
380
- "--seeds", type=int, default=N_SEEDS,
381
- help=f"Number of GA seeds to run (default: {N_SEEDS})")
382
- parser.add_argument(
383
- "--quiet", action="store_true",
384
- help="Suppress progress output")
385
- args = parser.parse_args()
386
-
387
- train(args.output, D=args.dim, n_seeds=args.seeds, verbose=not args.quiet)
388
-
389
-
390
- if __name__ == "__main__":
391
- main()