npcpy 1.2.22__tar.gz → 1.2.23__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.
- {npcpy-1.2.22/npcpy.egg-info → npcpy-1.2.23}/PKG-INFO +147 -1
- {npcpy-1.2.22 → npcpy-1.2.23}/README.md +146 -0
- npcpy-1.2.23/npcpy/ft/diff.py +110 -0
- npcpy-1.2.23/npcpy/ft/ge.py +115 -0
- npcpy-1.2.23/npcpy/ft/model_ensembler.py +357 -0
- npcpy-1.2.23/npcpy/ft/rl.py +360 -0
- npcpy-1.2.23/npcpy/ft/sft.py +230 -0
- npcpy-1.2.23/npcpy/ft/usft.py +128 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/llm_funcs.py +1 -1
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/command_history.py +23 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/memory_processor.py +27 -11
- {npcpy-1.2.22 → npcpy-1.2.23/npcpy.egg-info}/PKG-INFO +147 -1
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy.egg-info/SOURCES.txt +2 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/setup.py +1 -1
- npcpy-1.2.22/npcpy/ft/diff.py +0 -1
- npcpy-1.2.22/npcpy/ft/ge.py +0 -1
- npcpy-1.2.22/npcpy/ft/rl.py +0 -1
- npcpy-1.2.22/npcpy/ft/sft.py +0 -1
- {npcpy-1.2.22 → npcpy-1.2.23}/LICENSE +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/MANIFEST.in +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/audio.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/data_models.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/image.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/load.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/text.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/video.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/data/web.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/response.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/main.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/memory/search.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/mix/debate.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/npc_compiler.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/npcs.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/serve.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/tools.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/work/__init__.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/work/desktop.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/work/plan.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy/work/trigger.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/setup.cfg +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_audio.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_command_history.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_image.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_load.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_npcsql.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_response.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_serve.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_text.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_tools.py +0 -0
- {npcpy-1.2.22 → npcpy-1.2.23}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.23
|
|
4
4
|
Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcpy
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -399,6 +399,152 @@ the citizens, being directed by simple and incontestable principles, may tend to
|
|
|
399
399
|
maintenance of the Constitution, and the general happiness. ''')
|
|
400
400
|
# it will play the audio automatically.
|
|
401
401
|
```
|
|
402
|
+
## Fine-Tuning and Evolution
|
|
403
|
+
|
|
404
|
+
`npcpy` provides modular tools for building adaptive AI systems through supervised fine-tuning, reinforcement learning, and genetic algorithms.
|
|
405
|
+
|
|
406
|
+
See examples/fine_tuning_demo.py for a complete working example.
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
### Supervised Fine-Tuning (SFT)
|
|
410
|
+
|
|
411
|
+
Train models on specific tasks using simple X, y pairs:
|
|
412
|
+
```python
|
|
413
|
+
from npcpy.ft.sft import run_sft, load_sft_model, predict_sft
|
|
414
|
+
|
|
415
|
+
X_train = ["translate to french: hello", "translate to french: goodbye"]
|
|
416
|
+
y_train = ["bonjour", "au revoir"]
|
|
417
|
+
|
|
418
|
+
model_path = run_sft(X_train, y_train)
|
|
419
|
+
|
|
420
|
+
model, tokenizer = load_sft_model(model_path)
|
|
421
|
+
response = predict_sft(model, tokenizer, "translate to french: thanks")
|
|
422
|
+
```
|
|
423
|
+
### Unsupervised Fine-Tuning (USFT)
|
|
424
|
+
Adapt models to domain-specific text corpora without labels:
|
|
425
|
+
```python
|
|
426
|
+
from npcpy.ft.usft import run_usft, load_corpus_from_hf
|
|
427
|
+
|
|
428
|
+
texts = load_corpus_from_hf("tiny_shakespeare", split="train[:1000]")
|
|
429
|
+
|
|
430
|
+
model_path = run_usft(
|
|
431
|
+
texts,
|
|
432
|
+
config=USFTConfig(
|
|
433
|
+
output_model_path="models/shakespeare",
|
|
434
|
+
num_train_epochs=3
|
|
435
|
+
)
|
|
436
|
+
)
|
|
437
|
+
Train on your own text corpus:
|
|
438
|
+
pythondomain_texts = [
|
|
439
|
+
"Your domain-specific text 1",
|
|
440
|
+
"Your domain-specific text 2",
|
|
441
|
+
] * 100
|
|
442
|
+
|
|
443
|
+
model_path = run_usft(domain_texts)
|
|
444
|
+
```
|
|
445
|
+
### Diffusion Fine-tuning
|
|
446
|
+
```
|
|
447
|
+
from npcpy.ft.diff import train_diffusion, generate_image
|
|
448
|
+
|
|
449
|
+
image_paths = ["img1.png", "img2.png", "img3.png"]
|
|
450
|
+
captions = ["a cat", "a dog", "a bird"]
|
|
451
|
+
|
|
452
|
+
model_path = train_diffusion(
|
|
453
|
+
image_paths,
|
|
454
|
+
captions,
|
|
455
|
+
config=DiffusionConfig(
|
|
456
|
+
num_epochs=100,
|
|
457
|
+
batch_size=4
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
generated = generate_image(
|
|
462
|
+
model_path,
|
|
463
|
+
prompt="a white square",
|
|
464
|
+
image_size=128
|
|
465
|
+
)
|
|
466
|
+
Resume training from checkpoint:
|
|
467
|
+
pythonmodel_path = train_diffusion(
|
|
468
|
+
image_paths,
|
|
469
|
+
captions,
|
|
470
|
+
config,
|
|
471
|
+
resume_from="models/diffusion/checkpoints/checkpoint-epoch10-step1000.pt"
|
|
472
|
+
)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
### Reinforcement Learning (RL)
|
|
477
|
+
Collect agent traces and train with DPO based on reward signals:
|
|
478
|
+
```python
|
|
479
|
+
from npcpy.ft.rl import collect_traces, run_rl_training
|
|
480
|
+
from npcpy.npc_compiler import NPC
|
|
481
|
+
|
|
482
|
+
tasks = [
|
|
483
|
+
{'prompt': 'Solve 2+2', 'expected': '4'},
|
|
484
|
+
{'prompt': 'Solve 5+3', 'expected': '8'}
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
agents = [
|
|
488
|
+
NPC(name="farlor", primary_directive="Be concise",
|
|
489
|
+
model="qwen3:0.6b", provider="ollama"),
|
|
490
|
+
NPC(name="tedno", primary_directive="Show your work",
|
|
491
|
+
model="qwen3:0.6b", provider="ollama")
|
|
492
|
+
]
|
|
493
|
+
|
|
494
|
+
def reward_fn(trace):
|
|
495
|
+
if trace['task_metadata']['expected'] in trace['final_output']:
|
|
496
|
+
return 1.0
|
|
497
|
+
return 0.0
|
|
498
|
+
|
|
499
|
+
adapter_path = run_rl_training(tasks, agents, reward_fn)
|
|
500
|
+
```
|
|
501
|
+
### Genetic Evolution
|
|
502
|
+
|
|
503
|
+
Evolve populations of knowledge graphs or model ensembles:
|
|
504
|
+
```python
|
|
505
|
+
from npcpy.ft.ge import GeneticEvolver, GAConfig
|
|
506
|
+
|
|
507
|
+
config = GAConfig(
|
|
508
|
+
population_size=20,
|
|
509
|
+
generations=50,
|
|
510
|
+
mutation_rate=0.15
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
evolver = GeneticEvolver(
|
|
514
|
+
fitness_fn=your_fitness_function,
|
|
515
|
+
mutate_fn=your_mutation_function,
|
|
516
|
+
crossover_fn=your_crossover_function,
|
|
517
|
+
initialize_fn=your_init_function,
|
|
518
|
+
config=config
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
best_individual = evolver.run()
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Smart Model Ensembler and response router
|
|
525
|
+
Build fast intuitive responses with fallback to reasoning:
|
|
526
|
+
```python
|
|
527
|
+
from npcpy.ft.model_ensembler import (
|
|
528
|
+
ResponseRouter,
|
|
529
|
+
create_model_genome
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
genome = create_model_genome(['math', 'code', 'factual'])
|
|
533
|
+
router = ResponseRouter(fast_threshold=0.8)
|
|
534
|
+
|
|
535
|
+
result = router.route_query("What is 2+2?", genome)
|
|
536
|
+
|
|
537
|
+
if result['used_fast_path']:
|
|
538
|
+
print("Fast gut reaction")
|
|
539
|
+
elif result['used_ensemble']:
|
|
540
|
+
print("Ensemble voting")
|
|
541
|
+
else:
|
|
542
|
+
print("Full reasoning")
|
|
543
|
+
```
|
|
544
|
+
The intention for this model ensembler system is to mimic human cognition: pattern-matched gut reactions (System 1 of Kahneman) for familiar queries, falling back to deliberate reasoning (System 2 of Kahneman) for novel problems. Genetic algorithms evolve both knowledge structures and model specializations over time.
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
|
|
402
548
|
## Serving an NPC Team
|
|
403
549
|
|
|
404
550
|
`npcpy` includes a built-in Flask server that makes it easy to deploy NPC teams for production use. You can serve teams with tools, jinxs, and complex workflows that frontends can interact with via REST APIs.
|
|
@@ -303,6 +303,152 @@ the citizens, being directed by simple and incontestable principles, may tend to
|
|
|
303
303
|
maintenance of the Constitution, and the general happiness. ''')
|
|
304
304
|
# it will play the audio automatically.
|
|
305
305
|
```
|
|
306
|
+
## Fine-Tuning and Evolution
|
|
307
|
+
|
|
308
|
+
`npcpy` provides modular tools for building adaptive AI systems through supervised fine-tuning, reinforcement learning, and genetic algorithms.
|
|
309
|
+
|
|
310
|
+
See examples/fine_tuning_demo.py for a complete working example.
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
### Supervised Fine-Tuning (SFT)
|
|
314
|
+
|
|
315
|
+
Train models on specific tasks using simple X, y pairs:
|
|
316
|
+
```python
|
|
317
|
+
from npcpy.ft.sft import run_sft, load_sft_model, predict_sft
|
|
318
|
+
|
|
319
|
+
X_train = ["translate to french: hello", "translate to french: goodbye"]
|
|
320
|
+
y_train = ["bonjour", "au revoir"]
|
|
321
|
+
|
|
322
|
+
model_path = run_sft(X_train, y_train)
|
|
323
|
+
|
|
324
|
+
model, tokenizer = load_sft_model(model_path)
|
|
325
|
+
response = predict_sft(model, tokenizer, "translate to french: thanks")
|
|
326
|
+
```
|
|
327
|
+
### Unsupervised Fine-Tuning (USFT)
|
|
328
|
+
Adapt models to domain-specific text corpora without labels:
|
|
329
|
+
```python
|
|
330
|
+
from npcpy.ft.usft import run_usft, load_corpus_from_hf
|
|
331
|
+
|
|
332
|
+
texts = load_corpus_from_hf("tiny_shakespeare", split="train[:1000]")
|
|
333
|
+
|
|
334
|
+
model_path = run_usft(
|
|
335
|
+
texts,
|
|
336
|
+
config=USFTConfig(
|
|
337
|
+
output_model_path="models/shakespeare",
|
|
338
|
+
num_train_epochs=3
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
Train on your own text corpus:
|
|
342
|
+
pythondomain_texts = [
|
|
343
|
+
"Your domain-specific text 1",
|
|
344
|
+
"Your domain-specific text 2",
|
|
345
|
+
] * 100
|
|
346
|
+
|
|
347
|
+
model_path = run_usft(domain_texts)
|
|
348
|
+
```
|
|
349
|
+
### Diffusion Fine-tuning
|
|
350
|
+
```
|
|
351
|
+
from npcpy.ft.diff import train_diffusion, generate_image
|
|
352
|
+
|
|
353
|
+
image_paths = ["img1.png", "img2.png", "img3.png"]
|
|
354
|
+
captions = ["a cat", "a dog", "a bird"]
|
|
355
|
+
|
|
356
|
+
model_path = train_diffusion(
|
|
357
|
+
image_paths,
|
|
358
|
+
captions,
|
|
359
|
+
config=DiffusionConfig(
|
|
360
|
+
num_epochs=100,
|
|
361
|
+
batch_size=4
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
generated = generate_image(
|
|
366
|
+
model_path,
|
|
367
|
+
prompt="a white square",
|
|
368
|
+
image_size=128
|
|
369
|
+
)
|
|
370
|
+
Resume training from checkpoint:
|
|
371
|
+
pythonmodel_path = train_diffusion(
|
|
372
|
+
image_paths,
|
|
373
|
+
captions,
|
|
374
|
+
config,
|
|
375
|
+
resume_from="models/diffusion/checkpoints/checkpoint-epoch10-step1000.pt"
|
|
376
|
+
)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
### Reinforcement Learning (RL)
|
|
381
|
+
Collect agent traces and train with DPO based on reward signals:
|
|
382
|
+
```python
|
|
383
|
+
from npcpy.ft.rl import collect_traces, run_rl_training
|
|
384
|
+
from npcpy.npc_compiler import NPC
|
|
385
|
+
|
|
386
|
+
tasks = [
|
|
387
|
+
{'prompt': 'Solve 2+2', 'expected': '4'},
|
|
388
|
+
{'prompt': 'Solve 5+3', 'expected': '8'}
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
agents = [
|
|
392
|
+
NPC(name="farlor", primary_directive="Be concise",
|
|
393
|
+
model="qwen3:0.6b", provider="ollama"),
|
|
394
|
+
NPC(name="tedno", primary_directive="Show your work",
|
|
395
|
+
model="qwen3:0.6b", provider="ollama")
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
def reward_fn(trace):
|
|
399
|
+
if trace['task_metadata']['expected'] in trace['final_output']:
|
|
400
|
+
return 1.0
|
|
401
|
+
return 0.0
|
|
402
|
+
|
|
403
|
+
adapter_path = run_rl_training(tasks, agents, reward_fn)
|
|
404
|
+
```
|
|
405
|
+
### Genetic Evolution
|
|
406
|
+
|
|
407
|
+
Evolve populations of knowledge graphs or model ensembles:
|
|
408
|
+
```python
|
|
409
|
+
from npcpy.ft.ge import GeneticEvolver, GAConfig
|
|
410
|
+
|
|
411
|
+
config = GAConfig(
|
|
412
|
+
population_size=20,
|
|
413
|
+
generations=50,
|
|
414
|
+
mutation_rate=0.15
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
evolver = GeneticEvolver(
|
|
418
|
+
fitness_fn=your_fitness_function,
|
|
419
|
+
mutate_fn=your_mutation_function,
|
|
420
|
+
crossover_fn=your_crossover_function,
|
|
421
|
+
initialize_fn=your_init_function,
|
|
422
|
+
config=config
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
best_individual = evolver.run()
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Smart Model Ensembler and response router
|
|
429
|
+
Build fast intuitive responses with fallback to reasoning:
|
|
430
|
+
```python
|
|
431
|
+
from npcpy.ft.model_ensembler import (
|
|
432
|
+
ResponseRouter,
|
|
433
|
+
create_model_genome
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
genome = create_model_genome(['math', 'code', 'factual'])
|
|
437
|
+
router = ResponseRouter(fast_threshold=0.8)
|
|
438
|
+
|
|
439
|
+
result = router.route_query("What is 2+2?", genome)
|
|
440
|
+
|
|
441
|
+
if result['used_fast_path']:
|
|
442
|
+
print("Fast gut reaction")
|
|
443
|
+
elif result['used_ensemble']:
|
|
444
|
+
print("Ensemble voting")
|
|
445
|
+
else:
|
|
446
|
+
print("Full reasoning")
|
|
447
|
+
```
|
|
448
|
+
The intention for this model ensembler system is to mimic human cognition: pattern-matched gut reactions (System 1 of Kahneman) for familiar queries, falling back to deliberate reasoning (System 2 of Kahneman) for novel problems. Genetic algorithms evolve both knowledge structures and model specializations over time.
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
306
452
|
## Serving an NPC Team
|
|
307
453
|
|
|
308
454
|
`npcpy` includes a built-in Flask server that makes it easy to deploy NPC teams for production use. You can serve teams with tools, jinxs, and complex workflows that frontends can interact with via REST APIs.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# finetuning diffuser models
|
|
2
|
+
try:
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
import torch.nn.functional as F
|
|
6
|
+
from torch.utils.data import DataLoader, Dataset as TorchDataset
|
|
7
|
+
from transformers import CLIPTextModel, CLIPTokenizer
|
|
8
|
+
except:
|
|
9
|
+
torch = None
|
|
10
|
+
nn = None
|
|
11
|
+
F = None
|
|
12
|
+
DataLoader = None
|
|
13
|
+
TorchDataset = None
|
|
14
|
+
CLIPTextModel = None
|
|
15
|
+
CLIPTokenizer = None
|
|
16
|
+
import math
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import List, Optional, Callable
|
|
19
|
+
import numpy as np
|
|
20
|
+
from PIL import Image
|
|
21
|
+
import os
|
|
22
|
+
from tqdm import tqdm
|
|
23
|
+
import gc
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DiffusionConfig:
|
|
28
|
+
image_size: int = 128
|
|
29
|
+
channels: int = 256
|
|
30
|
+
time_emb_dim: int = 128
|
|
31
|
+
timesteps: int = 1000
|
|
32
|
+
beta_start: float = 1e-4
|
|
33
|
+
beta_end: float = 0.02
|
|
34
|
+
num_epochs: int = 100
|
|
35
|
+
batch_size: int = 4
|
|
36
|
+
learning_rate: float = 1e-5
|
|
37
|
+
checkpoint_frequency: int = 1000
|
|
38
|
+
output_dir: str = "diffusion_model"
|
|
39
|
+
use_clip: bool = True
|
|
40
|
+
num_channels: int = 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SinusoidalPositionEmbeddings(nn.Module):
|
|
44
|
+
|
|
45
|
+
def __init__(self, dim):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.dim = dim
|
|
48
|
+
|
|
49
|
+
def forward(self, time):
|
|
50
|
+
device = time.device
|
|
51
|
+
half_dim = self.dim // 2
|
|
52
|
+
embeddings = math.log(10000) / (half_dim - 1)
|
|
53
|
+
embeddings = torch.exp(
|
|
54
|
+
torch.arange(half_dim, device=device) * -embeddings
|
|
55
|
+
)
|
|
56
|
+
embeddings = time[:, None] * embeddings[None, :]
|
|
57
|
+
embeddings = torch.cat(
|
|
58
|
+
(embeddings.sin(), embeddings.cos()),
|
|
59
|
+
dim=-1
|
|
60
|
+
)
|
|
61
|
+
return embeddings
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SimpleUNet(nn.Module):
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
image_size=128,
|
|
69
|
+
channels=256,
|
|
70
|
+
time_emb_dim=128,
|
|
71
|
+
num_channels=1
|
|
72
|
+
):
|
|
73
|
+
super().__init__()
|
|
74
|
+
|
|
75
|
+
self.image_size = image_size
|
|
76
|
+
|
|
77
|
+
self.time_mlp = nn.Sequential(
|
|
78
|
+
SinusoidalPositionEmbeddings(time_emb_dim),
|
|
79
|
+
nn.Linear(time_emb_dim, time_emb_dim * 4),
|
|
80
|
+
nn.GELU(),
|
|
81
|
+
nn.Linear(time_emb_dim * 4, channels),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.text_mlp = nn.Sequential(
|
|
85
|
+
nn.Linear(768, time_emb_dim),
|
|
86
|
+
nn.GELU(),
|
|
87
|
+
nn.Linear(time_emb_dim, time_emb_dim),
|
|
88
|
+
nn.GELU(),
|
|
89
|
+
nn.Linear(time_emb_dim, channels),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.conv_in = nn.Conv2d(num_channels, channels, 1, padding=0)
|
|
93
|
+
|
|
94
|
+
self.down1 = nn.Sequential(
|
|
95
|
+
nn.Conv2d(channels, channels * 2, 4, 2, 1),
|
|
96
|
+
nn.GroupNorm(8, channels * 2),
|
|
97
|
+
nn.GELU(),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.down2 = nn.Sequential(
|
|
101
|
+
nn.Conv2d(channels * 2, channels * 4, 4, 2, 1),
|
|
102
|
+
nn.GroupNorm(8, channels * 4),
|
|
103
|
+
nn.GELU(),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.down3 = nn.Sequential(
|
|
107
|
+
nn.Conv2d(channels * 4, channels * 8, 4, 2, 1),
|
|
108
|
+
nn.GroupNorm(8, channels * 8),
|
|
109
|
+
nn.GELU(),
|
|
110
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Callable, Optional, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class GAConfig:
|
|
8
|
+
population_size: int = 20
|
|
9
|
+
mutation_rate: float = 0.15
|
|
10
|
+
crossover_rate: float = 0.7
|
|
11
|
+
tournament_size: int = 3
|
|
12
|
+
elitism_count: int = 2
|
|
13
|
+
generations: int = 50
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GeneticEvolver:
|
|
17
|
+
"""
|
|
18
|
+
Generic GA that takes fitness, mutation, crossover
|
|
19
|
+
and initialization functions to evolve any population
|
|
20
|
+
"""
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
fitness_fn: Callable,
|
|
24
|
+
mutate_fn: Callable,
|
|
25
|
+
crossover_fn: Callable,
|
|
26
|
+
initialize_fn: Callable,
|
|
27
|
+
config: Optional[GAConfig] = None
|
|
28
|
+
):
|
|
29
|
+
self.fitness_fn = fitness_fn
|
|
30
|
+
self.mutate_fn = mutate_fn
|
|
31
|
+
self.crossover_fn = crossover_fn
|
|
32
|
+
self.initialize_fn = initialize_fn
|
|
33
|
+
self.config = config or GAConfig()
|
|
34
|
+
self.population = []
|
|
35
|
+
self.history = []
|
|
36
|
+
|
|
37
|
+
def initialize_population(self):
|
|
38
|
+
self.population = [
|
|
39
|
+
self.initialize_fn()
|
|
40
|
+
for _ in range(self.config.population_size)
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def evaluate_population(self) -> List[float]:
|
|
44
|
+
return [
|
|
45
|
+
self.fitness_fn(individual)
|
|
46
|
+
for individual in self.population
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
def tournament_select(self, fitness_scores: List[float]):
|
|
50
|
+
indices = random.sample(
|
|
51
|
+
range(len(self.population)),
|
|
52
|
+
self.config.tournament_size
|
|
53
|
+
)
|
|
54
|
+
tournament_fitness = [fitness_scores[i] for i in indices]
|
|
55
|
+
winner_idx = indices[
|
|
56
|
+
tournament_fitness.index(max(tournament_fitness))
|
|
57
|
+
]
|
|
58
|
+
return self.population[winner_idx]
|
|
59
|
+
|
|
60
|
+
def evolve_generation(self):
|
|
61
|
+
fitness_scores = self.evaluate_population()
|
|
62
|
+
|
|
63
|
+
sorted_pop = sorted(
|
|
64
|
+
zip(self.population, fitness_scores),
|
|
65
|
+
key=lambda x: x[1],
|
|
66
|
+
reverse=True
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
new_population = [
|
|
70
|
+
ind for ind, _ in sorted_pop[:self.config.elitism_count]
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
while len(new_population) < self.config.population_size:
|
|
74
|
+
parent1 = self.tournament_select(fitness_scores)
|
|
75
|
+
parent2 = self.tournament_select(fitness_scores)
|
|
76
|
+
|
|
77
|
+
if random.random() < self.config.crossover_rate:
|
|
78
|
+
child = self.crossover_fn(parent1, parent2)
|
|
79
|
+
else:
|
|
80
|
+
child = parent1
|
|
81
|
+
|
|
82
|
+
if random.random() < self.config.mutation_rate:
|
|
83
|
+
child = self.mutate_fn(child)
|
|
84
|
+
|
|
85
|
+
new_population.append(child)
|
|
86
|
+
|
|
87
|
+
self.population = new_population[:self.config.population_size]
|
|
88
|
+
|
|
89
|
+
best_fitness = max(fitness_scores)
|
|
90
|
+
avg_fitness = sum(fitness_scores) / len(fitness_scores)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
'best_fitness': best_fitness,
|
|
94
|
+
'avg_fitness': avg_fitness,
|
|
95
|
+
'best_individual': sorted_pop[0][0]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def run(self, generations: Optional[int] = None):
|
|
99
|
+
if not self.population:
|
|
100
|
+
self.initialize_population()
|
|
101
|
+
|
|
102
|
+
gens = generations or self.config.generations
|
|
103
|
+
|
|
104
|
+
for gen in range(gens):
|
|
105
|
+
gen_stats = self.evolve_generation()
|
|
106
|
+
self.history.append(gen_stats)
|
|
107
|
+
|
|
108
|
+
if gen % 10 == 0:
|
|
109
|
+
print(
|
|
110
|
+
f"Gen {gen}: "
|
|
111
|
+
f"Best={gen_stats['best_fitness']:.3f}, "
|
|
112
|
+
f"Avg={gen_stats['avg_fitness']:.3f}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return self.history[-1]['best_individual']
|