neuralt-work 0.1.0__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.
- neuralt_work-0.1.0/LICENSE +21 -0
- neuralt_work-0.1.0/PKG-INFO +61 -0
- neuralt_work-0.1.0/README.md +23 -0
- neuralt_work-0.1.0/pyproject.toml +30 -0
- neuralt_work-0.1.0/setup.cfg +4 -0
- neuralt_work-0.1.0/src/neuralt_work.egg-info/PKG-INFO +61 -0
- neuralt_work-0.1.0/src/neuralt_work.egg-info/SOURCES.txt +10 -0
- neuralt_work-0.1.0/src/neuralt_work.egg-info/dependency_links.txt +1 -0
- neuralt_work-0.1.0/src/neuralt_work.egg-info/requires.txt +1 -0
- neuralt_work-0.1.0/src/neuralt_work.egg-info/top_level.txt +1 -0
- neuralt_work-0.1.0/src/neuraltwork/__init__.py +1 -0
- neuralt_work-0.1.0/src/neuraltwork/main.py +717 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aquilesinho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neuralt-work
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight neural-network learning library for Python.
|
|
5
|
+
Author: Aquilesinho
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Aquilesinho
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Development Status :: 3 - Alpha
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
32
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
33
|
+
Requires-Python: >=3.9
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: numpy>=1.24
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# NeuraltWork
|
|
40
|
+
|
|
41
|
+
NeuraltWork is a Python neural-network learning library.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install neuralt-work
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Import
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import neuraltwork as nn
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Status
|
|
56
|
+
|
|
57
|
+
This project is in early development. Its API and behavior may change between releases.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
This project is distributed under the MIT License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# NeuraltWork
|
|
2
|
+
|
|
3
|
+
NeuraltWork is a Python neural-network learning library.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install neuralt-work
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Import
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import neuraltwork as nn
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Status
|
|
18
|
+
|
|
19
|
+
This project is in early development. Its API and behavior may change between releases.
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
This project is distributed under the MIT License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "neuralt-work"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight neural-network learning library for Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Aquilesinho" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy>=1.24"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
package-dir = { "" = "src" }
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neuralt-work
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight neural-network learning library for Python.
|
|
5
|
+
Author: Aquilesinho
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Aquilesinho
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Development Status :: 3 - Alpha
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
32
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
33
|
+
Requires-Python: >=3.9
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: numpy>=1.24
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# NeuraltWork
|
|
40
|
+
|
|
41
|
+
NeuraltWork is a Python neural-network learning library.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install neuralt-work
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Import
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import neuraltwork as nn
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Status
|
|
56
|
+
|
|
57
|
+
This project is in early development. Its API and behavior may change between releases.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
This project is distributed under the MIT License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/neuralt_work.egg-info/PKG-INFO
|
|
5
|
+
src/neuralt_work.egg-info/SOURCES.txt
|
|
6
|
+
src/neuralt_work.egg-info/dependency_links.txt
|
|
7
|
+
src/neuralt_work.egg-info/requires.txt
|
|
8
|
+
src/neuralt_work.egg-info/top_level.txt
|
|
9
|
+
src/neuraltwork/__init__.py
|
|
10
|
+
src/neuraltwork/main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.24
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
neuraltwork
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import *
|
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
import os
|
|
2
|
+
os.environ["OPENBLAS_NUM_THREADS"] = "1"
|
|
3
|
+
os.environ["OMP_NUM_THREADS"] = "1"
|
|
4
|
+
os.environ["MKL_NUM_THREADS"] = "1"
|
|
5
|
+
import pickle
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
from collections import deque
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ACAO_ESPERAR = 0
|
|
13
|
+
ACAO_PRIMEIRA_FUNCAO = 1
|
|
14
|
+
|
|
15
|
+
_ENTRADAS_BASE = 4
|
|
16
|
+
_MAX_MEMORIA = 50000
|
|
17
|
+
_LOTE = 32
|
|
18
|
+
_FREQUENCIA_APRENDIZADO = 64
|
|
19
|
+
_FREQUENCIA_DECISAO = 4
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _RedeNeural:
|
|
23
|
+
def __init__(self, entradas=_ENTRADAS_BASE, ocultas1=64, ocultas2=64, saidas=2):
|
|
24
|
+
self.W1 = np.random.randn(entradas, ocultas1).astype(np.float32) * np.sqrt(2.0 / entradas)
|
|
25
|
+
self.b1 = np.zeros(ocultas1, dtype=np.float32)
|
|
26
|
+
self.W2 = np.random.randn(ocultas1, ocultas2).astype(np.float32) * np.sqrt(2.0 / ocultas1)
|
|
27
|
+
self.b2 = np.zeros(ocultas2, dtype=np.float32)
|
|
28
|
+
self.W3 = np.random.randn(ocultas2, saidas).astype(np.float32) * np.sqrt(2.0 / ocultas2)
|
|
29
|
+
self.b3 = np.zeros(saidas, dtype=np.float32)
|
|
30
|
+
self.m = [np.zeros_like(p) for p in self.parametros()]
|
|
31
|
+
self.v = [np.zeros_like(p) for p in self.parametros()]
|
|
32
|
+
self.passos_adam = 0
|
|
33
|
+
|
|
34
|
+
def parametros(self):
|
|
35
|
+
return [self.W1, self.b1, self.W2, self.b2, self.W3, self.b3]
|
|
36
|
+
|
|
37
|
+
def prever(self, estados):
|
|
38
|
+
estados = np.asarray(estados, dtype=np.float32)
|
|
39
|
+
a1 = np.maximum(0, estados @ self.W1 + self.b1)
|
|
40
|
+
a2 = np.maximum(0, a1 @ self.W2 + self.b2)
|
|
41
|
+
return a2 @ self.W3 + self.b3
|
|
42
|
+
|
|
43
|
+
def treinar(self, estados, acoes, alvos):
|
|
44
|
+
estados = np.asarray(estados, dtype=np.float32)
|
|
45
|
+
acoes = np.asarray(acoes, dtype=np.int64)
|
|
46
|
+
alvos = np.asarray(alvos, dtype=np.float32)
|
|
47
|
+
|
|
48
|
+
z1 = estados @ self.W1 + self.b1
|
|
49
|
+
a1 = np.maximum(0, z1)
|
|
50
|
+
z2 = a1 @ self.W2 + self.b2
|
|
51
|
+
a2 = np.maximum(0, z2)
|
|
52
|
+
saida = a2 @ self.W3 + self.b3
|
|
53
|
+
|
|
54
|
+
quantidade = len(estados)
|
|
55
|
+
delta3 = np.zeros_like(saida)
|
|
56
|
+
delta3[np.arange(quantidade), acoes] = (
|
|
57
|
+
2.0 * (saida[np.arange(quantidade), acoes] - alvos) / quantidade
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
grad_W3 = a2.T @ delta3
|
|
61
|
+
grad_b3 = np.sum(delta3, axis=0)
|
|
62
|
+
delta2 = (delta3 @ self.W3.T) * (z2 > 0)
|
|
63
|
+
grad_W2 = a1.T @ delta2
|
|
64
|
+
grad_b2 = np.sum(delta2, axis=0)
|
|
65
|
+
delta1 = (delta2 @ self.W2.T) * (z1 > 0)
|
|
66
|
+
grad_W1 = estados.T @ delta1
|
|
67
|
+
grad_b1 = np.sum(delta1, axis=0)
|
|
68
|
+
|
|
69
|
+
gradientes = [grad_W1, grad_b1, grad_W2, grad_b2, grad_W3, grad_b3]
|
|
70
|
+
self.passos_adam += 1
|
|
71
|
+
beta1 = 0.9
|
|
72
|
+
beta2 = 0.999
|
|
73
|
+
taxa = 0.0005
|
|
74
|
+
epsilon = 1e-8
|
|
75
|
+
|
|
76
|
+
for i, (parametro, gradiente) in enumerate(zip(self.parametros(), gradientes)):
|
|
77
|
+
self.m[i] = beta1 * self.m[i] + (1.0 - beta1) * gradiente
|
|
78
|
+
self.v[i] = beta2 * self.v[i] + (1.0 - beta2) * (gradiente ** 2)
|
|
79
|
+
m_corrigido = self.m[i] / (1.0 - beta1 ** self.passos_adam)
|
|
80
|
+
v_corrigido = self.v[i] / (1.0 - beta2 ** self.passos_adam)
|
|
81
|
+
parametro -= taxa * m_corrigido / (np.sqrt(v_corrigido) + epsilon)
|
|
82
|
+
|
|
83
|
+
def copiar_pesos(self, outra):
|
|
84
|
+
for destino, origem in zip(self.parametros(), outra.parametros()):
|
|
85
|
+
destino[:] = origem
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _AgenteDQN:
|
|
89
|
+
def __init__(self, quantidade_acoes, caminho, entradas):
|
|
90
|
+
self.rede = _RedeNeural(entradas=entradas, saidas=quantidade_acoes)
|
|
91
|
+
self.rede_alvo = _RedeNeural(entradas=entradas, saidas=quantidade_acoes)
|
|
92
|
+
self.rede_alvo.copiar_pesos(self.rede)
|
|
93
|
+
self.memoria = deque(maxlen=_MAX_MEMORIA)
|
|
94
|
+
self.memoria_melhor = deque(maxlen=_MAX_MEMORIA)
|
|
95
|
+
self.experiencias_episodio = deque(maxlen=_MAX_MEMORIA)
|
|
96
|
+
self.gamma = 0.99
|
|
97
|
+
self.epsilon = 1.0
|
|
98
|
+
self.epsilon_minimo = 0.05
|
|
99
|
+
self.epsilon_decay = 0.9995
|
|
100
|
+
self.lote = _LOTE
|
|
101
|
+
self.passos = 0
|
|
102
|
+
self.caminho = caminho
|
|
103
|
+
self.carregar()
|
|
104
|
+
|
|
105
|
+
def escolher_acao(self, estado):
|
|
106
|
+
if random.random() < self.epsilon:
|
|
107
|
+
return random.randrange(self.rede.W3.shape[1])
|
|
108
|
+
return int(np.argmax(self.rede.prever([estado])[0]))
|
|
109
|
+
|
|
110
|
+
def guardar(self, estado, acao, recompensa, proximo, terminou):
|
|
111
|
+
experiencia = (
|
|
112
|
+
np.asarray(estado, dtype=np.float32),
|
|
113
|
+
int(acao),
|
|
114
|
+
float(recompensa),
|
|
115
|
+
np.asarray(proximo, dtype=np.float32),
|
|
116
|
+
bool(terminou)
|
|
117
|
+
)
|
|
118
|
+
self.memoria.append(experiencia)
|
|
119
|
+
self.experiencias_episodio.append(experiencia)
|
|
120
|
+
|
|
121
|
+
def aprender(self):
|
|
122
|
+
total = len(self.memoria) + len(self.memoria_melhor)
|
|
123
|
+
if total < self.lote:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
qtd_atual = min(16, len(self.memoria))
|
|
127
|
+
qtd_melhor = min(16, len(self.memoria_melhor))
|
|
128
|
+
faltam = self.lote - qtd_atual - qtd_melhor
|
|
129
|
+
|
|
130
|
+
if faltam > 0:
|
|
131
|
+
extra = min(faltam, len(self.memoria) - qtd_atual)
|
|
132
|
+
qtd_atual += extra
|
|
133
|
+
faltam -= extra
|
|
134
|
+
if faltam > 0:
|
|
135
|
+
qtd_melhor += min(faltam, len(self.memoria_melhor) - qtd_melhor)
|
|
136
|
+
|
|
137
|
+
amostra = []
|
|
138
|
+
if qtd_atual:
|
|
139
|
+
amostra.extend(random.sample(list(self.memoria), qtd_atual))
|
|
140
|
+
if qtd_melhor:
|
|
141
|
+
amostra.extend(random.sample(list(self.memoria_melhor), qtd_melhor))
|
|
142
|
+
if len(amostra) < self.lote:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
estados = np.asarray([item[0] for item in amostra], dtype=np.float32)
|
|
146
|
+
acoes = np.asarray([item[1] for item in amostra], dtype=np.int64)
|
|
147
|
+
recompensas = np.asarray([item[2] for item in amostra], dtype=np.float32)
|
|
148
|
+
proximos = np.asarray([item[3] for item in amostra], dtype=np.float32)
|
|
149
|
+
terminou = np.asarray([item[4] for item in amostra], dtype=np.float32)
|
|
150
|
+
|
|
151
|
+
futuros = np.max(self.rede_alvo.prever(proximos), axis=1)
|
|
152
|
+
alvos = recompensas + self.gamma * futuros * (1.0 - terminou)
|
|
153
|
+
self.rede.treinar(estados, acoes, alvos)
|
|
154
|
+
|
|
155
|
+
self.passos += 1
|
|
156
|
+
if self.passos % 500 == 0:
|
|
157
|
+
self.rede_alvo.copiar_pesos(self.rede)
|
|
158
|
+
|
|
159
|
+
self.epsilon = max(self.epsilon_minimo, self.epsilon * self.epsilon_decay)
|
|
160
|
+
|
|
161
|
+
def encerrar_episodio(self):
|
|
162
|
+
if self.experiencias_episodio:
|
|
163
|
+
self.memoria_melhor = deque(self.experiencias_episodio, maxlen=_MAX_MEMORIA)
|
|
164
|
+
self.experiencias_episodio = deque(maxlen=_MAX_MEMORIA)
|
|
165
|
+
|
|
166
|
+
def salvar(self):
|
|
167
|
+
dados = {
|
|
168
|
+
"versao": 3,
|
|
169
|
+
"acoes": self.rede.W3.shape[1],
|
|
170
|
+
"entradas": self.rede.W1.shape[0],
|
|
171
|
+
"pesos_rede": [p.copy() for p in self.rede.parametros()],
|
|
172
|
+
"pesos_alvo": [p.copy() for p in self.rede_alvo.parametros()],
|
|
173
|
+
"memoria": list(self.memoria),
|
|
174
|
+
"memoria_melhor": list(self.memoria_melhor),
|
|
175
|
+
"epsilon": self.epsilon,
|
|
176
|
+
"passos": self.passos,
|
|
177
|
+
"adam_m": [v.copy() for v in self.rede.m],
|
|
178
|
+
"adam_v": [v.copy() for v in self.rede.v],
|
|
179
|
+
"adam_passos": self.rede.passos_adam
|
|
180
|
+
}
|
|
181
|
+
temporario = self.caminho + "." + str(os.getpid()) + ".tmp"
|
|
182
|
+
try:
|
|
183
|
+
with open(temporario, "wb") as arquivo:
|
|
184
|
+
pickle.dump(dados, arquivo, protocol=pickle.HIGHEST_PROTOCOL)
|
|
185
|
+
for tentativa in range(5):
|
|
186
|
+
try:
|
|
187
|
+
os.replace(temporario, self.caminho)
|
|
188
|
+
return
|
|
189
|
+
except PermissionError:
|
|
190
|
+
if tentativa == 4:
|
|
191
|
+
raise
|
|
192
|
+
time.sleep(0.3)
|
|
193
|
+
except Exception as erro:
|
|
194
|
+
print("neuralnetwork: erro ao salvar:", erro)
|
|
195
|
+
finally:
|
|
196
|
+
if os.path.exists(temporario):
|
|
197
|
+
try:
|
|
198
|
+
os.remove(temporario)
|
|
199
|
+
except OSError:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
def carregar(self):
|
|
203
|
+
if not os.path.exists(self.caminho):
|
|
204
|
+
return
|
|
205
|
+
try:
|
|
206
|
+
with open(self.caminho, "rb") as arquivo:
|
|
207
|
+
dados = pickle.load(arquivo)
|
|
208
|
+
if (dados.get("versao") != 3 or
|
|
209
|
+
dados.get("acoes") != self.rede.W3.shape[1] or
|
|
210
|
+
dados.get("entradas") != self.rede.W1.shape[0]):
|
|
211
|
+
print("neuralnetwork: memória incompatível; iniciando do zero.")
|
|
212
|
+
return
|
|
213
|
+
for atual, salvo in zip(self.rede.parametros(), dados["pesos_rede"]):
|
|
214
|
+
if atual.shape == salvo.shape:
|
|
215
|
+
atual[:] = salvo
|
|
216
|
+
for atual, salvo in zip(self.rede_alvo.parametros(), dados["pesos_alvo"]):
|
|
217
|
+
if atual.shape == salvo.shape:
|
|
218
|
+
atual[:] = salvo
|
|
219
|
+
self.memoria.extend(dados.get("memoria", []))
|
|
220
|
+
self.memoria_melhor.extend(dados.get("memoria_melhor", []))
|
|
221
|
+
self.epsilon = float(dados.get("epsilon", 1.0))
|
|
222
|
+
self.passos = int(dados.get("passos", 0))
|
|
223
|
+
self.rede.m = [v.copy() for v in dados.get("adam_m", self.rede.m)]
|
|
224
|
+
self.rede.v = [v.copy() for v in dados.get("adam_v", self.rede.v)]
|
|
225
|
+
self.rede.passos_adam = int(dados.get("adam_passos", 0))
|
|
226
|
+
print("neuralnetwork: memória carregada.")
|
|
227
|
+
except Exception as erro:
|
|
228
|
+
print("neuralnetwork: não foi possível carregar a memória:", erro)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
_player_pos = None
|
|
232
|
+
_player_size = None
|
|
233
|
+
_player_velocity_y = 0.0
|
|
234
|
+
_obstaculos = []
|
|
235
|
+
_obsnot = []
|
|
236
|
+
_paredes = []
|
|
237
|
+
_quantidades_observacao = None
|
|
238
|
+
_funcoes = []
|
|
239
|
+
_recompensa_atual = 1.0
|
|
240
|
+
_estado_anterior = None
|
|
241
|
+
_acao_anterior = None
|
|
242
|
+
_contador_atualizacoes = 0
|
|
243
|
+
_agente = None
|
|
244
|
+
_total_tentativas = 0
|
|
245
|
+
_melhor_recompensa = 0.0
|
|
246
|
+
_pasta = os.path.dirname(os.path.abspath(__file__))
|
|
247
|
+
_caminho_memoria = os.path.join(_pasta, "neuralnetwork_memory.pkl")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def player(posicao, tamanho, velocity_y=0.0):
|
|
251
|
+
"""Atualiza a posição e o tamanho do jogador. Chame a cada frame."""
|
|
252
|
+
global _player_pos, _player_size, _player_velocity_y
|
|
253
|
+
_player_pos = tuple(posicao)
|
|
254
|
+
_player_size = tuple(tamanho)
|
|
255
|
+
_player_velocity_y = float(velocity_y)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _validar_hitboxes(lista, nome):
|
|
259
|
+
"""Valida listas no formato [((x, y), (largura, altura)), ...]."""
|
|
260
|
+
if not isinstance(lista, (list, tuple)):
|
|
261
|
+
raise TypeError("nn." + nome + " espera uma lista ou tupla.")
|
|
262
|
+
|
|
263
|
+
resultado = []
|
|
264
|
+
for item in lista:
|
|
265
|
+
try:
|
|
266
|
+
posicao, tamanho = item
|
|
267
|
+
if len(posicao) != 2 or len(tamanho) != 2:
|
|
268
|
+
raise ValueError
|
|
269
|
+
x = float(posicao[0])
|
|
270
|
+
y = float(posicao[1])
|
|
271
|
+
largura = float(tamanho[0])
|
|
272
|
+
altura = float(tamanho[1])
|
|
273
|
+
if largura < 0 or altura < 0:
|
|
274
|
+
raise ValueError
|
|
275
|
+
except (TypeError, ValueError, IndexError):
|
|
276
|
+
raise ValueError(
|
|
277
|
+
"Item inválido em nn." + nome + ". Use [((x, y), (largura, altura)), ...]."
|
|
278
|
+
)
|
|
279
|
+
resultado.append((x, y, largura, altura))
|
|
280
|
+
return resultado
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def obstacles(lista):
|
|
284
|
+
"""Define TODOS os obstáculos que podem matar o jogador."""
|
|
285
|
+
global _obstaculos
|
|
286
|
+
_obstaculos = sorted(_validar_hitboxes(lista, "obstacles"), key=lambda item: item[0])
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def obsnot(lista):
|
|
290
|
+
"""Define objetos de efeito incerto; a IA aprende pelo resultado das tentativas."""
|
|
291
|
+
global _obsnot
|
|
292
|
+
_obsnot = sorted(_validar_hitboxes(lista, "obsnot"), key=lambda item: item[0])
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def wall(lista):
|
|
296
|
+
"""Define hitboxes de paredes/limites que não matam o jogador."""
|
|
297
|
+
global _paredes
|
|
298
|
+
_paredes = sorted(_validar_hitboxes(lista, "wall"), key=lambda item: item[0])
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def functions(lista):
|
|
302
|
+
"""Define ações. A ação 0 é sempre esperar; lista[0] vira ação 1."""
|
|
303
|
+
global _funcoes, _agente, _estado_anterior, _acao_anterior
|
|
304
|
+
if not isinstance(lista, (list, tuple)):
|
|
305
|
+
raise TypeError("nn.functions espera uma lista ou tupla de funções.")
|
|
306
|
+
if not lista:
|
|
307
|
+
raise ValueError("nn.functions precisa de pelo menos uma função.")
|
|
308
|
+
if not all(callable(funcao) for funcao in lista):
|
|
309
|
+
raise TypeError("Todos os itens de nn.functions precisam ser funções.")
|
|
310
|
+
mudou = len(_funcoes) != len(lista)
|
|
311
|
+
_funcoes = list(lista)
|
|
312
|
+
if mudou:
|
|
313
|
+
_agente = None
|
|
314
|
+
_estado_anterior = None
|
|
315
|
+
_acao_anterior = None
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def reward(pontos):
|
|
319
|
+
"""Define a recompensa do passo atual. Ex.: nn.reward(points)."""
|
|
320
|
+
global _recompensa_atual
|
|
321
|
+
_recompensa_atual = float(pontos)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _obter_estado():
|
|
325
|
+
global _quantidades_observacao
|
|
326
|
+
|
|
327
|
+
if _player_pos is None or _player_size is None:
|
|
328
|
+
raise RuntimeError("Chame nn.player(posicao, tamanho) antes de nn.update().")
|
|
329
|
+
|
|
330
|
+
px, py = _player_pos
|
|
331
|
+
largura, altura = _player_size
|
|
332
|
+
quantidades = (len(_obstaculos), len(_obsnot), len(_paredes))
|
|
333
|
+
|
|
334
|
+
# A quantidade de hitboxes precisa permanecer estável para manter
|
|
335
|
+
# o mesmo tamanho de entrada da rede durante o treinamento.
|
|
336
|
+
if _quantidades_observacao is None:
|
|
337
|
+
_quantidades_observacao = quantidades
|
|
338
|
+
elif quantidades != _quantidades_observacao:
|
|
339
|
+
raise RuntimeError(
|
|
340
|
+
"A quantidade de hitboxes mudou depois do início da IA. "
|
|
341
|
+
"Passe todos os itens de cada categoria em todos os frames, "
|
|
342
|
+
"mesmo os que estão longe da tela."
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
estado = [
|
|
346
|
+
max(-1.0, min(1.0, py / 600.0)),
|
|
347
|
+
max(-1.0, min(1.0, _player_velocity_y / 900.0)),
|
|
348
|
+
max(0.0, min(1.0, largura / 300.0)),
|
|
349
|
+
max(0.0, min(1.0, altura / 300.0))
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
# Cada hitbox é incluída. A ordem por X deixa a observação consistente.
|
|
353
|
+
# As três categorias ficam separadas para a rede aprender suas diferenças.
|
|
354
|
+
for grupo in (_obstaculos, _obsnot, _paredes):
|
|
355
|
+
for x, y, w, h in grupo:
|
|
356
|
+
estado.extend([
|
|
357
|
+
max(-10.0, min(10.0, (x - px) / 1050.0)),
|
|
358
|
+
max(-10.0, min(10.0, (y - py) / 600.0)),
|
|
359
|
+
max(0.0, min(10.0, w / 300.0)),
|
|
360
|
+
max(0.0, min(10.0, h / 300.0))
|
|
361
|
+
])
|
|
362
|
+
|
|
363
|
+
return np.asarray(estado, dtype=np.float32)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _garantir_agente(entradas):
|
|
367
|
+
global _agente
|
|
368
|
+
if not _funcoes:
|
|
369
|
+
raise RuntimeError("Configure as ações com nn.functions([...]) antes de nn.update().")
|
|
370
|
+
if _agente is None:
|
|
371
|
+
_agente = _AgenteDQN(len(_funcoes) + 1, _caminho_memoria, entradas)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def update():
|
|
375
|
+
"""Executa uma decisão da IA. Chame uma vez por passo de jogo/física.
|
|
376
|
+
|
|
377
|
+
Retorna True após executar a decisão da IA.
|
|
378
|
+
"""
|
|
379
|
+
global _estado_anterior, _acao_anterior, _recompensa_atual
|
|
380
|
+
global _contador_atualizacoes
|
|
381
|
+
if _player_pos is None or _player_size is None:
|
|
382
|
+
raise RuntimeError("Chame nn.player(posicao, tamanho) antes de nn.update().")
|
|
383
|
+
|
|
384
|
+
estado = _obter_estado()
|
|
385
|
+
_garantir_agente(len(estado))
|
|
386
|
+
_contador_atualizacoes += 1
|
|
387
|
+
if _estado_anterior is not None and _acao_anterior is not None:
|
|
388
|
+
_agente.guardar(_estado_anterior, _acao_anterior, _recompensa_atual, estado, False)
|
|
389
|
+
if _contador_atualizacoes % _FREQUENCIA_APRENDIZADO == 0:
|
|
390
|
+
_agente.aprender()
|
|
391
|
+
|
|
392
|
+
acao = _agente.escolher_acao(estado)
|
|
393
|
+
if acao > 0:
|
|
394
|
+
_funcoes[acao - 1]()
|
|
395
|
+
|
|
396
|
+
_estado_anterior = estado
|
|
397
|
+
_acao_anterior = acao
|
|
398
|
+
_recompensa_atual = 1.0
|
|
399
|
+
return True
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def reset():
|
|
403
|
+
"""Encerra a tentativa atual; preserva os pesos e o aprendizado."""
|
|
404
|
+
global _estado_anterior, _acao_anterior, _recompensa_atual
|
|
405
|
+
global _total_tentativas, _melhor_recompensa
|
|
406
|
+
|
|
407
|
+
if _agente is not None and _estado_anterior is not None and _acao_anterior is not None:
|
|
408
|
+
proximo = _obter_estado() if _player_pos is not None else _estado_anterior
|
|
409
|
+
recompensa_final = _recompensa_atual
|
|
410
|
+
_agente.guardar(_estado_anterior, _acao_anterior, recompensa_final, proximo, True)
|
|
411
|
+
_agente.aprender()
|
|
412
|
+
if _agente.experiencias_episodio:
|
|
413
|
+
soma = sum(item[2] for item in _agente.experiencias_episodio)
|
|
414
|
+
if soma > _melhor_recompensa:
|
|
415
|
+
_melhor_recompensa = soma
|
|
416
|
+
_agente.encerrar_episodio()
|
|
417
|
+
else:
|
|
418
|
+
_agente.experiencias_episodio = deque(maxlen=_MAX_MEMORIA)
|
|
419
|
+
_total_tentativas += 1
|
|
420
|
+
if _total_tentativas % 10 == 0:
|
|
421
|
+
_agente.salvar()
|
|
422
|
+
|
|
423
|
+
_estado_anterior = None
|
|
424
|
+
_acao_anterior = None
|
|
425
|
+
_recompensa_atual = 1.0
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def save():
|
|
429
|
+
"""Salva a rede e as memórias manualmente."""
|
|
430
|
+
if _agente is not None:
|
|
431
|
+
_agente.salvar()
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def stats():
|
|
435
|
+
"""Retorna informações úteis: epsilon, tentativas e tamanhos das memórias."""
|
|
436
|
+
if _agente is None:
|
|
437
|
+
return {
|
|
438
|
+
"epsilon": 1.0,
|
|
439
|
+
"tentativas": _total_tentativas,
|
|
440
|
+
"memoria": 0,
|
|
441
|
+
"memoria_melhor": 0
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
"epsilon": _agente.epsilon,
|
|
445
|
+
"tentativas": _total_tentativas,
|
|
446
|
+
"memoria": len(_agente.memoria),
|
|
447
|
+
"memoria_melhor": len(_agente.memoria_melhor)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# ================= MODO POPULACAO ================= #
|
|
451
|
+
# nn.players([...]) ativa o modo de varias IAs. Cada item:
|
|
452
|
+
# (posicao, tamanho, velocidade_y, viva)
|
|
453
|
+
_QUANTIDADE_JOGADORES = 100
|
|
454
|
+
_modo_populacao = False
|
|
455
|
+
_jogadores = []
|
|
456
|
+
_agentes_populacao = []
|
|
457
|
+
_recompensas_populacao = []
|
|
458
|
+
_pontuacoes_populacao = []
|
|
459
|
+
_mortos_anteriores = []
|
|
460
|
+
_ticks_populacao = 0
|
|
461
|
+
_recompensas_acumuladas = [0.0] * 100
|
|
462
|
+
_geracao = 1
|
|
463
|
+
_melhor_indice = 0
|
|
464
|
+
_melhor_pontuacao_geral = float("-inf")
|
|
465
|
+
_caminho_melhor_populacao = os.path.join(_pasta, "neuralnetwork_best_population.pkl")
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _novo_agente_populacao(indice, copiar_de=None):
|
|
469
|
+
caminho = os.path.join(_pasta, "population_agent_" + str(indice) + ".pkl")
|
|
470
|
+
agente = _AgenteDQN(len(_funcoes) + 1, caminho, _entradas_populacao)
|
|
471
|
+
if copiar_de is not None:
|
|
472
|
+
# Na primeira geracao, copiar_de pode ser uma _RedeNeural
|
|
473
|
+
# carregada do arquivo da melhor populacao. Nas seguintes,
|
|
474
|
+
# pode ser um _AgenteDQN completo.
|
|
475
|
+
if isinstance(copiar_de, _AgenteDQN):
|
|
476
|
+
rede_origem = copiar_de.rede
|
|
477
|
+
epsilon_origem = copiar_de.epsilon
|
|
478
|
+
else:
|
|
479
|
+
rede_origem = copiar_de
|
|
480
|
+
epsilon_origem = 1.0
|
|
481
|
+
|
|
482
|
+
agente.rede.copiar_pesos(rede_origem)
|
|
483
|
+
agente.rede_alvo.copiar_pesos(rede_origem)
|
|
484
|
+
agente.epsilon = max(0.25, epsilon_origem)
|
|
485
|
+
agente.memoria.clear()
|
|
486
|
+
agente.memoria_melhor.clear()
|
|
487
|
+
agente.experiencias_episodio.clear()
|
|
488
|
+
return agente
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def players(lista):
|
|
492
|
+
"""Atualiza os 100 jogadores: [(pos, tamanho, velocidade_y, viva), ...]."""
|
|
493
|
+
global _modo_populacao, _jogadores, _agentes_populacao
|
|
494
|
+
global _recompensas_populacao, _pontuacoes_populacao, _mortos_anteriores
|
|
495
|
+
global _entradas_populacao
|
|
496
|
+
if not isinstance(lista, (list, tuple)) or not lista:
|
|
497
|
+
raise ValueError("nn.players espera uma lista de jogadores.")
|
|
498
|
+
if len(lista) != _QUANTIDADE_JOGADORES:
|
|
499
|
+
raise ValueError("nn.players precisa receber exatamente 100 jogadores.")
|
|
500
|
+
normalizados = []
|
|
501
|
+
for item in lista:
|
|
502
|
+
if len(item) == 3:
|
|
503
|
+
pos, tamanho, velocidade = item
|
|
504
|
+
viva = True
|
|
505
|
+
elif len(item) == 4:
|
|
506
|
+
pos, tamanho, velocidade, viva = item
|
|
507
|
+
else:
|
|
508
|
+
raise ValueError("Cada jogador deve ser (pos, tamanho, velocidade, viva).")
|
|
509
|
+
normalizados.append((tuple(pos), tuple(tamanho), float(velocidade), bool(viva)))
|
|
510
|
+
_modo_populacao = True
|
|
511
|
+
_jogadores = normalizados
|
|
512
|
+
if not _agentes_populacao:
|
|
513
|
+
# A dimensão é calculada com a mesma observação usada no modo individual.
|
|
514
|
+
global _player_pos, _player_size, _player_velocity_y
|
|
515
|
+
_player_pos, _player_size, _player_velocity_y = normalizados[0][:3]
|
|
516
|
+
estado = _obter_estado()
|
|
517
|
+
_entradas_populacao = len(estado)
|
|
518
|
+
melhor = None
|
|
519
|
+
if os.path.exists(_caminho_melhor_populacao):
|
|
520
|
+
try:
|
|
521
|
+
with open(_caminho_melhor_populacao, "rb") as arquivo:
|
|
522
|
+
dados = pickle.load(arquivo)
|
|
523
|
+
melhor = _RedeNeural(entradas=_entradas_populacao, saidas=len(_funcoes) + 1)
|
|
524
|
+
for atual, salvo in zip(melhor.parametros(), dados["pesos"]):
|
|
525
|
+
if atual.shape == salvo.shape:
|
|
526
|
+
atual[:] = salvo
|
|
527
|
+
except Exception:
|
|
528
|
+
melhor = None
|
|
529
|
+
_agentes_populacao = [_novo_agente_populacao(i, melhor) for i in range(100)]
|
|
530
|
+
_recompensas_populacao = [0.0] * 100
|
|
531
|
+
_pontuacoes_populacao = [0.0] * 100
|
|
532
|
+
_mortos_anteriores = [False] * 100
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def reward(pontos):
|
|
536
|
+
"""No modo players, recebe uma lista de 100 recompensas individuais."""
|
|
537
|
+
global _recompensa_atual, _recompensas_populacao
|
|
538
|
+
if _modo_populacao:
|
|
539
|
+
if not isinstance(pontos, (list, tuple)) or len(pontos) != 100:
|
|
540
|
+
raise ValueError("No modo nn.players, nn.reward precisa receber 100 valores.")
|
|
541
|
+
_recompensas_populacao = [float(v) for v in pontos]
|
|
542
|
+
else:
|
|
543
|
+
_recompensa_atual = float(pontos)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _processar_jogador(argumentos):
|
|
547
|
+
"""Processa uma IA sequencialmente, sem criar threads concorrentes."""
|
|
548
|
+
i, estado = argumentos
|
|
549
|
+
jogador = _jogadores[i]
|
|
550
|
+
agente = _agentes_populacao[i]
|
|
551
|
+
recompensa = _recompensas_populacao[i]
|
|
552
|
+
_pontuacoes_populacao[i] += recompensa
|
|
553
|
+
|
|
554
|
+
if not jogador[3]:
|
|
555
|
+
if not _mortos_anteriores[i] and agente.experiencias_episodio:
|
|
556
|
+
ultimo = agente.experiencias_episodio[-1]
|
|
557
|
+
agente.guardar(ultimo[3], ultimo[1], recompensa, ultimo[3], True)
|
|
558
|
+
agente.aprender()
|
|
559
|
+
agente.encerrar_episodio()
|
|
560
|
+
_mortos_anteriores[i] = True
|
|
561
|
+
return i
|
|
562
|
+
|
|
563
|
+
_mortos_anteriores[i] = False
|
|
564
|
+
if not hasattr(agente, "estado_anterior"):
|
|
565
|
+
agente.estado_anterior = None
|
|
566
|
+
agente.acao_anterior = None
|
|
567
|
+
agente.contador = 0
|
|
568
|
+
|
|
569
|
+
if agente.estado_anterior is not None:
|
|
570
|
+
agente.guardar(agente.estado_anterior, agente.acao_anterior, recompensa, estado, False)
|
|
571
|
+
agente.contador += 1
|
|
572
|
+
if agente.contador % _FREQUENCIA_APRENDIZADO == 0:
|
|
573
|
+
agente.aprender()
|
|
574
|
+
|
|
575
|
+
acao = agente.escolher_acao(estado)
|
|
576
|
+
if acao > 0:
|
|
577
|
+
_funcoes[acao - 1](i)
|
|
578
|
+
agente.estado_anterior = estado
|
|
579
|
+
agente.acao_anterior = acao
|
|
580
|
+
return i
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def update():
|
|
584
|
+
"""Atualiza a população; decisões e treinamento são espaçados para aliviar a CPU."""
|
|
585
|
+
global _player_pos, _player_size, _player_velocity_y, _melhor_indice
|
|
586
|
+
global _ticks_populacao, _recompensas_acumuladas, _recompensas_populacao
|
|
587
|
+
if not _modo_populacao:
|
|
588
|
+
return _update_individual_original()
|
|
589
|
+
if len(_jogadores) != _QUANTIDADE_JOGADORES or len(_agentes_populacao) != _QUANTIDADE_JOGADORES:
|
|
590
|
+
raise RuntimeError("Chame nn.players() com 100 jogadores antes de nn.update().")
|
|
591
|
+
|
|
592
|
+
_ticks_populacao += 1
|
|
593
|
+
for i, valor in enumerate(_recompensas_populacao):
|
|
594
|
+
_recompensas_acumuladas[i] += valor
|
|
595
|
+
|
|
596
|
+
# A física continua a cada passo, mas a IA só recalcula estados e ações
|
|
597
|
+
# a cada 4 passos (30 decisões/s em vez de 120). Mortes são processadas já.
|
|
598
|
+
houve_morte = any(not item[3] and not _mortos_anteriores[i]
|
|
599
|
+
for i, item in enumerate(_jogadores))
|
|
600
|
+
if _ticks_populacao % _FREQUENCIA_DECISAO != 0 and not houve_morte:
|
|
601
|
+
return True
|
|
602
|
+
|
|
603
|
+
_recompensas_populacao = _recompensas_acumuladas
|
|
604
|
+
_recompensas_acumuladas = [0.0] * _QUANTIDADE_JOGADORES
|
|
605
|
+
|
|
606
|
+
# Monta os estados das 100 IAs em lote. As hitboxes são compartilhadas;
|
|
607
|
+
# NumPy calcula as distâncias relativas sem repetir loops Python gigantes.
|
|
608
|
+
quantidades = (len(_obstaculos), len(_obsnot), len(_paredes))
|
|
609
|
+
if _quantidades_observacao is not None and quantidades != _quantidades_observacao:
|
|
610
|
+
raise RuntimeError("A quantidade de hitboxes mudou depois do início da IA.")
|
|
611
|
+
|
|
612
|
+
jogadores_np = np.asarray([
|
|
613
|
+
[item[0][0], item[0][1], item[2], item[1][0], item[1][1]]
|
|
614
|
+
for item in _jogadores
|
|
615
|
+
], dtype=np.float32)
|
|
616
|
+
estados_np = np.empty((100, _entradas_populacao), dtype=np.float32)
|
|
617
|
+
estados_np[:, 0] = np.clip(jogadores_np[:, 1] / 600.0, -1.0, 1.0)
|
|
618
|
+
estados_np[:, 1] = np.clip(jogadores_np[:, 2] / 900.0, -1.0, 1.0)
|
|
619
|
+
estados_np[:, 2] = np.clip(jogadores_np[:, 3] / 300.0, 0.0, 1.0)
|
|
620
|
+
estados_np[:, 3] = np.clip(jogadores_np[:, 4] / 300.0, 0.0, 1.0)
|
|
621
|
+
|
|
622
|
+
deslocamento = 4
|
|
623
|
+
for grupo in (_obstaculos, _obsnot, _paredes):
|
|
624
|
+
if grupo:
|
|
625
|
+
hitboxes = np.asarray(grupo, dtype=np.float32)
|
|
626
|
+
quantidade = len(hitboxes)
|
|
627
|
+
bloco = np.empty((100, quantidade, 4), dtype=np.float32)
|
|
628
|
+
bloco[:, :, 0] = np.clip((hitboxes[None, :, 0] - jogadores_np[:, None, 0]) / 1050.0, -10.0, 10.0)
|
|
629
|
+
bloco[:, :, 1] = np.clip((hitboxes[None, :, 1] - jogadores_np[:, None, 1]) / 600.0, -10.0, 10.0)
|
|
630
|
+
bloco[:, :, 2] = np.clip(hitboxes[None, :, 2] / 300.0, 0.0, 10.0)
|
|
631
|
+
bloco[:, :, 3] = np.clip(hitboxes[None, :, 3] / 300.0, 0.0, 10.0)
|
|
632
|
+
estados_np[:, deslocamento:deslocamento + quantidade * 4] = bloco.reshape(100, quantidade * 4)
|
|
633
|
+
deslocamento += quantidade * 4
|
|
634
|
+
|
|
635
|
+
for i in range(100):
|
|
636
|
+
_processar_jogador((i, estados_np[i]))
|
|
637
|
+
|
|
638
|
+
vivos = [i for i, item in enumerate(_jogadores) if item[3]]
|
|
639
|
+
if vivos:
|
|
640
|
+
_melhor_indice = max(vivos, key=lambda i: _pontuacoes_populacao[i])
|
|
641
|
+
return True
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _update_individual_original():
|
|
645
|
+
global _estado_anterior, _acao_anterior, _recompensa_atual, _contador_atualizacoes
|
|
646
|
+
if _player_pos is None or _player_size is None:
|
|
647
|
+
raise RuntimeError("Chame nn.player(posicao, tamanho) antes de nn.update().")
|
|
648
|
+
estado = _obter_estado()
|
|
649
|
+
_garantir_agente(len(estado))
|
|
650
|
+
_contador_atualizacoes += 1
|
|
651
|
+
if _estado_anterior is not None and _acao_anterior is not None:
|
|
652
|
+
_agente.guardar(_estado_anterior, _acao_anterior, _recompensa_atual, estado, False)
|
|
653
|
+
if _contador_atualizacoes % _FREQUENCIA_APRENDIZADO == 0:
|
|
654
|
+
_agente.aprender()
|
|
655
|
+
acao = _agente.escolher_acao(estado)
|
|
656
|
+
if acao > 0:
|
|
657
|
+
_funcoes[acao - 1]()
|
|
658
|
+
_estado_anterior, _acao_anterior = estado, acao
|
|
659
|
+
_recompensa_atual = 1.0
|
|
660
|
+
return True
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def reset():
|
|
664
|
+
"""No modo populacao, inicia nova geracao a partir da melhor IA."""
|
|
665
|
+
global _estado_anterior, _acao_anterior, _recompensa_atual
|
|
666
|
+
global _total_tentativas, _melhor_recompensa, _geracao
|
|
667
|
+
global _agentes_populacao, _pontuacoes_populacao, _mortos_anteriores
|
|
668
|
+
global _melhor_pontuacao_geral, _ticks_populacao, _recompensas_acumuladas
|
|
669
|
+
if not _modo_populacao:
|
|
670
|
+
return _reset_individual_original()
|
|
671
|
+
if any(item[3] for item in _jogadores):
|
|
672
|
+
return
|
|
673
|
+
melhor_i = max(range(100), key=lambda i: _pontuacoes_populacao[i])
|
|
674
|
+
melhor = _agentes_populacao[melhor_i]
|
|
675
|
+
pontuacao = _pontuacoes_populacao[melhor_i]
|
|
676
|
+
if pontuacao > _melhor_pontuacao_geral:
|
|
677
|
+
_melhor_pontuacao_geral = pontuacao
|
|
678
|
+
try:
|
|
679
|
+
with open(_caminho_melhor_populacao, "wb") as arquivo:
|
|
680
|
+
pickle.dump({"pesos": [p.copy() for p in melhor.rede.parametros()]}, arquivo, protocol=pickle.HIGHEST_PROTOCOL)
|
|
681
|
+
except Exception as erro:
|
|
682
|
+
print("neuralnetwork: erro ao salvar melhor populacao:", erro)
|
|
683
|
+
_agentes_populacao = [_novo_agente_populacao(i, melhor) for i in range(100)]
|
|
684
|
+
_pontuacoes_populacao = [0.0] * 100
|
|
685
|
+
_mortos_anteriores = [False] * 100
|
|
686
|
+
_recompensas_populacao = [0.0] * 100
|
|
687
|
+
_recompensas_acumuladas = [0.0] * 100
|
|
688
|
+
_ticks_populacao = 0
|
|
689
|
+
_geracao += 1
|
|
690
|
+
_total_tentativas += 100
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def _reset_individual_original():
|
|
694
|
+
global _estado_anterior, _acao_anterior, _recompensa_atual
|
|
695
|
+
global _total_tentativas, _melhor_recompensa
|
|
696
|
+
if _agente is not None and _estado_anterior is not None and _acao_anterior is not None:
|
|
697
|
+
proximo = _obter_estado() if _player_pos is not None else _estado_anterior
|
|
698
|
+
_agente.guardar(_estado_anterior, _acao_anterior, _recompensa_atual, proximo, True)
|
|
699
|
+
_agente.aprender()
|
|
700
|
+
if _agente.experiencias_episodio:
|
|
701
|
+
soma = sum(item[2] for item in _agente.experiencias_episodio)
|
|
702
|
+
if soma > _melhor_recompensa:
|
|
703
|
+
_melhor_recompensa = soma
|
|
704
|
+
_agente.encerrar_episodio()
|
|
705
|
+
else:
|
|
706
|
+
_agente.experiencias_episodio = deque(maxlen=_MAX_MEMORIA)
|
|
707
|
+
_total_tentativas += 1
|
|
708
|
+
if _total_tentativas % 10 == 0:
|
|
709
|
+
_agente.salvar()
|
|
710
|
+
_estado_anterior = None
|
|
711
|
+
_acao_anterior = None
|
|
712
|
+
_recompensa_atual = 1.0
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def population_stats():
|
|
716
|
+
return {"geracao": _geracao, "vivas": sum(1 for p in _jogadores if p[3]),
|
|
717
|
+
"melhor_indice": _melhor_indice, "melhor_pontuacao": max(_pontuacoes_populacao) if _pontuacoes_populacao else 0}
|