NandaMatt commited on
Commit
93fc950
·
verified ·
1 Parent(s): 51cba47

commit to RepoB

Browse files
model/mpl_pequeno_torch/dataset.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Dataset concreto do pipeline ``atualizado`` — dados tabulares + PyTorch.
3
+
4
+ A maior parte da lógica está em :class:`base_dataset.BaseDataset`
5
+ (``docker/base_dataset.py``). Aqui só fica o que é específico:
6
+
7
+ - :py:meth:`build_features` — seleciona colunas numéricas do DataFrame,
8
+ descartando colunas de identificação (``id``, ``image_path``, ...).
9
+ - :py:meth:`get_data_loader` — devolve ``DataLoader`` do PyTorch
10
+ montado em cima dos arrays preparados pela base.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import sys
17
+ from typing import Optional, Tuple
18
+
19
+ import numpy as np
20
+ import pandas as pd
21
+
22
+ try:
23
+ import torch
24
+ from torch.utils.data import DataLoader, TensorDataset
25
+ _TORCH_AVAILABLE = True
26
+ except ImportError: # pragma: no cover
27
+ _TORCH_AVAILABLE = False
28
+
29
+ # Importa BaseDataset de ../base_dataset.py
30
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
31
+ from base_dataset import BaseDataset # noqa: E402
32
+
33
+
34
+ class TrainingDataset(BaseDataset):
35
+ """Dataset tabular para o pipeline em ``docker/atualizado``."""
36
+
37
+ # ------------------------------------------------------------------
38
+ # Hooks da base
39
+ # ------------------------------------------------------------------
40
+ def build_features(self) -> None:
41
+ if self.df is None:
42
+ raise RuntimeError("self.df está vazio; load_raw_data() não rodou.")
43
+
44
+ # Prioridade: lista explícita no metadata.json
45
+ explicit = self.metadata.get("features")
46
+ if explicit:
47
+ missing = [c for c in explicit if c not in self.df.columns]
48
+ if missing:
49
+ raise ValueError(
50
+ f"Colunas declaradas em metadata['features'] não encontradas no DataFrame: {missing}"
51
+ )
52
+ self.feature_columns = list(explicit)
53
+ self.X = self.df[self.feature_columns].to_numpy(dtype=np.float32)
54
+ return
55
+
56
+ # Fallback: inferência automática de colunas numéricas
57
+ feats = []
58
+ for col in self.df.columns:
59
+ if col == self.target_column:
60
+ continue
61
+ if col.lower() in self.NON_FEATURE_COLS:
62
+ continue
63
+ if not pd.api.types.is_numeric_dtype(self.df[col]):
64
+ continue
65
+ feats.append(col)
66
+
67
+ if not feats:
68
+ raise ValueError(
69
+ "Nenhuma coluna numérica encontrada para usar como feature."
70
+ )
71
+
72
+ self.feature_columns = feats
73
+ self.X = self.df[feats].to_numpy(dtype=np.float32)
74
+
75
+ def get_data_loader(
76
+ self,
77
+ batch_size: int = 32,
78
+ train_ratio: Optional[float] = None,
79
+ seed: Optional[int] = None,
80
+ ) -> Tuple["DataLoader", "DataLoader"]:
81
+ if not _TORCH_AVAILABLE:
82
+ raise RuntimeError(
83
+ "PyTorch não está instalado; instale-o ou use get_arrays()."
84
+ )
85
+
86
+ train_ratio, seed = self._resolve_split_params(train_ratio, seed)
87
+ idx_train, idx_val = self._split_indices(train_ratio, seed)
88
+
89
+ assert self.X is not None and self.y is not None # pra mypy
90
+
91
+ X_train = torch.from_numpy(self.X[idx_train])
92
+ y_train = torch.from_numpy(self.y[idx_train]).long()
93
+ X_val = torch.from_numpy(self.X[idx_val])
94
+ y_val = torch.from_numpy(self.y[idx_val]).long()
95
+
96
+ bs = self._resolve_batch_size(batch_size)
97
+ train_loader = DataLoader(
98
+ TensorDataset(X_train, y_train), batch_size=bs, shuffle=True
99
+ )
100
+ val_loader = DataLoader(
101
+ TensorDataset(X_val, y_val), batch_size=bs, shuffle=False
102
+ )
103
+
104
+ print(
105
+ f" - Train samples: {len(X_train)} | Val samples: {len(X_val)}"
106
+ f" | Batch size: {bs}"
107
+ )
108
+ return train_loader, val_loader
model/mpl_pequeno_torch/help.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RNNs / LSTMs for Sequences
2
+
3
+ ## Resume
4
+ Recurrent Neural Networks (RNNs) and Long Short-Term Memory (LSTM) networks are sequential neural architectures designed to map time-series data or sequential text structures. LSTMs introduce explicit gating mechanisms to regulate internal information persistence, successfully resolving the vanishing gradient flaws of traditional RNN blocks.
5
+
6
+ ### Common Use Cases:
7
+ * **SMILES Generation:** Autoregressively printing valid chemical strings token by token.
8
+ * **Biosignal Analysis:** Evaluating chronological streaming signals from patient telemetry or ECG readouts.
9
+ * **Clinical Notes Sequence Modeling:** Tracking medical event timelines over long EHR spans.
10
+
11
+ ## Content
12
+
13
+ ### 1. Core Architecture & Gated Memory
14
+ Standard RNNs maintain a recurring hidden state vector $h_t$ that gets updated with every timestamp token input. However, backpropagating through long sequences causes gradients to rapidly disappear or explode.
15
+
16
+ LSTMs solve this by introducing an internal **Cell State** ($c_t$) alongside three specialized gating mechanisms:
17
+ * **Forget Gate ($f_t$):** Controls how much historical context from the cell state should be discarded.
18
+ * **Input Gate ($i_t$):** Regulates what new contextual state information should be infused into the current cell vector.
19
+ * **Output Gate ($o_t$):** Determines what subset of internal hidden cell states should be exposed as the final output block.
20
+
21
+ ### 2. Operational Execution Sequence
22
+ As a sequence executes, the architecture reads the token input at step $t$, merges it with the prior step's hidden vector $h_{t-1}$, adjusts cell memories through the gating parameters, and pushes forward the updated state vectors. This linear memory highway allows the architecture to carry context across extended sequence matrices.
23
+
24
+ ### 3. Advantages and Disadvantages
25
+ * **Advantages:**
26
+ * Natural alignment with arbitrary, variable-length chronological or textual stream structures.
27
+ * O(L) computational complexity scaling linearly with sequence length.
28
+ * **Disadvantages:**
29
+ * Strict step-by-step dependency makes parallel sequence acceleration impossible during training.
30
+ * Susceptible to information decay over extremely long sequences compared to attention structures.
31
+
32
+ ### References
33
+ * Hochreiter, S., & Schmidhuber, J. (1997). *Long short-term memory*. Neural Computation.
34
+ * Segler, M. H., et al. (2018). *Generating focused molecule libraries for drug discovery with recurrent neural networks*. ACS Central Science.
model/mpl_pequeno_torch/hyperparameters.json ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "setting": {
3
+ "name": {
4
+ "text": "Nome do modelo",
5
+ "value": "mpl_pequeno_torch",
6
+ "Help": "Identificação do modelo PyTorch tabular. Pode ser usado para salvar checkpoints ou registros.",
7
+ "default": "mpl_pequeno_torch",
8
+ "type": "string",
9
+ "required": true
10
+ },
11
+ "architecture": {
12
+ "text": "Arquitetura do modelo",
13
+ "value": "MLP tabular",
14
+ "Help": "Arquitetura da rede neural para dados tabulares usando PyTorch.",
15
+ "default": "MLP tabular",
16
+ "type": "string",
17
+ "required": true
18
+ },
19
+ "tipo": {
20
+ "text": "Tipo do modelo",
21
+ "value": "classification",
22
+ "Help": "Define o tipo de tarefa do modelo.",
23
+ "default": "classification",
24
+ "type": "string",
25
+ "required": true
26
+ },
27
+ "learning_type": {
28
+ "text": "Tipo de aprendizado",
29
+ "value": "deep_learning",
30
+ "Help": "Indica se o modelo é de machine learning clássico ou deep learning.",
31
+ "default": "deep_learning",
32
+ "type": "string",
33
+ "required": true
34
+ }
35
+ },
36
+ "train": {
37
+ "batch_size": {
38
+ "text": "Tamanho do batch",
39
+ "Help": "Tamanho do batch usado durante o treinamento.",
40
+ "default": 32,
41
+ "range": {
42
+ "min": 1,
43
+ "max": null
44
+ },
45
+ "type": "integer",
46
+ "required": true
47
+ },
48
+ "epochs": {
49
+ "text": "Número de épocas",
50
+ "Help": "Quantidade de épocas de treinamento.",
51
+ "default": 10,
52
+ "range": {
53
+ "min": 1,
54
+ "max": 1000
55
+ },
56
+ "type": "integer",
57
+ "required": true
58
+ },
59
+ "learning_rate": {
60
+ "text": "Taxa de aprendizado",
61
+ "Help": "Taxa usada pelo otimizador.",
62
+ "default": 0.001,
63
+ "range": {
64
+ "min": 0.000001,
65
+ "max": 1.0
66
+ },
67
+ "type": "float",
68
+ "required": true
69
+ },
70
+ "weight_decay": {
71
+ "text": "Decaimento de peso",
72
+ "Help": "Regularização L2 usada pelo otimizador.",
73
+ "default": 0.0001,
74
+ "range": {
75
+ "min": 0.0,
76
+ "max": 0.1
77
+ },
78
+ "type": "float",
79
+ "required": false
80
+ },
81
+ "optimizer": {
82
+ "text": "Otimizador",
83
+ "Help": "Otimizador usado no treinamento.",
84
+ "default": "adam",
85
+ "type": "string",
86
+ "required": true
87
+ },
88
+ "hidden_dims": {
89
+ "text": "Dimensões ocultas",
90
+ "Help": "Lista de camadas ocultas do MLP.",
91
+ "default": [32, 16],
92
+ "type": "list",
93
+ "required": false
94
+ },
95
+ "dropout": {
96
+ "text": "Dropout",
97
+ "Help": "Taxa de dropout aplicada entre camadas.",
98
+ "default": 0.0,
99
+ "range": {
100
+ "min": 0.0,
101
+ "max": 1.0
102
+ },
103
+ "type": "float",
104
+ "required": false
105
+ }
106
+ },
107
+ "test": {
108
+ "batch_size": {
109
+ "text": "Tamanho do batch",
110
+ "Help": "Tamanho do batch usado na avaliação.",
111
+ "default": 64,
112
+ "range": {
113
+ "min": 1,
114
+ "max": null
115
+ },
116
+ "type": "integer",
117
+ "required": true
118
+ }
119
+ },
120
+ "predict": {
121
+ "batch_size": {
122
+ "text": "Tamanho do batch",
123
+ "Help": "Tamanho do batch usado na predição.",
124
+ "default": 64,
125
+ "range": {
126
+ "min": 1,
127
+ "max": null
128
+ },
129
+ "type": "integer",
130
+ "required": true
131
+ }
132
+ }
133
+ }
model/mpl_pequeno_torch/main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Entrypoint do pipeline ``atualizado`` (MLP tabular em PyTorch).
3
+
4
+ Toda a orquestração (modos train/test/predict, leitura de config,
5
+ escrita dos artefatos) vive em ``docker/base_main.py``. Aqui só
6
+ registramos as peças concretas desta arquitetura:
7
+
8
+ - ``TrainingDataset`` (de :mod:`dataset`)
9
+ - ``create_model`` (de :mod:`model`)
10
+
11
+ Como o input é tabular, o ``predict_input_loader`` default do
12
+ ``base_main`` (que lê CSV/XLSX) já serve — não precisa passar nada.
13
+ Arquiteturas futuras (ex.: CNN para imagem) podem passar um loader
14
+ próprio na chamada de ``run_pipeline``.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import sys
21
+
22
+ # Permite importar base_main.py de ../
23
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
24
+
25
+ from base_main import run_pipeline # noqa: E402
26
+ from dataset import TrainingDataset # noqa: E402
27
+ from model import create_model # noqa: E402
28
+
29
+
30
+ if __name__ == "__main__":
31
+ sys.exit(
32
+ run_pipeline(
33
+ dataset_cls=TrainingDataset,
34
+ model_factory=create_model,
35
+ )
36
+ )
model/mpl_pequeno_torch/model.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementação concreta (PyTorch) do modelo treinado pelo pipeline.
3
+
4
+ Arquitetura: MLP simples para dados tabulares. A configuração de
5
+ camadas, dropout, otimizador, etc. vem de ``config`` (lido do
6
+ ``config.json`` do treino) ou de defaults razoáveis.
7
+
8
+ A classe ``TabularMLP`` herda de ``BaseModel`` (em ``docker/base_model.py``)
9
+ para manter a API uniforme entre frameworks. Uma função ``create_model``
10
+ fábrica é exposta no fim do arquivo para que ``main.py`` continue
11
+ funcionando sem mudanças.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import sys
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ import numpy as np
21
+ import torch
22
+ import torch.nn as nn
23
+ import torch.optim as optim
24
+ from torch.utils.data import DataLoader
25
+
26
+ # Permite importar base_model.py de ../
27
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
28
+ from base_model import BaseModel # noqa: E402
29
+ from utils import emit_progress # noqa: E402
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Arquitetura MLP
34
+ # ---------------------------------------------------------------------------
35
+ class _MLP(nn.Module):
36
+ def __init__(
37
+ self,
38
+ input_size: int,
39
+ hidden_dims: List[int],
40
+ num_classes: int,
41
+ dropout: float = 0.0,
42
+ ) -> None:
43
+ super().__init__()
44
+ layers: List[nn.Module] = []
45
+ prev = input_size
46
+ for h in hidden_dims:
47
+ layers.append(nn.Linear(prev, h))
48
+ layers.append(nn.ReLU())
49
+ if dropout and dropout > 0:
50
+ layers.append(nn.Dropout(dropout))
51
+ prev = h
52
+ layers.append(nn.Linear(prev, num_classes))
53
+ self.net = nn.Sequential(*layers)
54
+
55
+ def forward(self, x: torch.Tensor) -> torch.Tensor: # noqa: D401
56
+ return self.net(x)
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Wrapper que implementa a API de BaseModel
61
+ # ---------------------------------------------------------------------------
62
+ class TabularMLP(BaseModel):
63
+ """MLP tabular em PyTorch, plugável no pipeline."""
64
+
65
+ def build_model(self) -> nn.Module:
66
+ hp = self.config.get("hyperparameters", {})
67
+ hidden_dims = self.config.get("hidden_dims") or hp.get("hidden_dims") or [
68
+ self.config.get("hidden_dim1", 32),
69
+ self.config.get("hidden_dim2", 16),
70
+ ]
71
+ dropout = float(hp.get("dropout", self.config.get("dropout", 0.0)))
72
+
73
+ # device
74
+ self.device = torch.device(
75
+ "cuda"
76
+ if torch.cuda.is_available() and self.config.get("use_cuda", True)
77
+ else "cpu"
78
+ )
79
+
80
+ model = _MLP(
81
+ input_size=self.input_size,
82
+ hidden_dims=list(hidden_dims),
83
+ num_classes=self.num_classes,
84
+ dropout=dropout,
85
+ ).to(self.device)
86
+
87
+ self.criterion = nn.CrossEntropyLoss()
88
+ self._hidden_dims = list(hidden_dims)
89
+ self._dropout = dropout
90
+ return model
91
+
92
+ # ---------------- treinamento ----------------
93
+ def train_model(
94
+ self,
95
+ train_loader: DataLoader,
96
+ val_loader: Optional[DataLoader],
97
+ epochs: int,
98
+ lr: float,
99
+ **kwargs: Any,
100
+ ) -> Dict[str, list]:
101
+ hp = self.config.get("hyperparameters", {})
102
+ epochs = int(hp.get("epochs", epochs))
103
+ lr = float(hp.get("learning_rate", lr))
104
+ weight_decay = float(hp.get("weight_decay", 0.0))
105
+ optimizer_name = str(hp.get("optimizer", "adam")).lower()
106
+
107
+ optimizer = self._build_optimizer(optimizer_name, lr, weight_decay)
108
+
109
+ history = {
110
+ "train_loss": [], "train_acc": [],
111
+ "val_loss": [], "val_acc": [],
112
+ }
113
+
114
+ n_batches = max(len(train_loader), 1)
115
+ total_train_steps = max(epochs * n_batches, 1)
116
+ best_val_acc = -float("inf")
117
+ best_epoch = -1
118
+ for epoch in range(1, epochs + 1):
119
+ self.model.train()
120
+ run_loss = 0.0
121
+ correct = 0
122
+ total = 0
123
+ for batch_idx, (X, y) in enumerate(train_loader, start=1):
124
+ X = X.to(self.device)
125
+ y = y.to(self.device)
126
+
127
+ optimizer.zero_grad()
128
+ logits = self.model(X)
129
+ loss = self.criterion(logits, y)
130
+ loss.backward()
131
+ optimizer.step()
132
+
133
+ run_loss += float(loss.item()) * X.size(0)
134
+ preds = logits.argmax(dim=1)
135
+ correct += int((preds == y).sum().item())
136
+ total += int(y.size(0))
137
+
138
+ global_step = (epoch - 1) * n_batches
139
+ inner_pct = int(global_step * 100 / total_train_steps)
140
+ emit_progress(inner_pct, total_train_steps)
141
+
142
+ train_loss = run_loss / max(total, 1)
143
+ train_acc = correct / max(total, 1)
144
+ history["train_loss"].append(round(train_loss, 6))
145
+ history["train_acc"].append(round(train_acc, 6))
146
+
147
+ val_loss, val_acc = (float("nan"), float("nan"))
148
+ if val_loader is not None:
149
+ val_metrics = self.evaluate(val_loader)
150
+ val_loss = val_metrics["loss"]
151
+ val_acc = val_metrics["accuracy"]
152
+ if val_acc > best_val_acc:
153
+ best_val_acc = val_acc
154
+ best_epoch = epoch
155
+ history["val_loss"].append(round(val_loss, 6))
156
+ history["val_acc"].append(round(val_acc, 6))
157
+
158
+ print(
159
+ f"Epoch [{epoch:>3}/{epochs}] "
160
+ f"train_loss={train_loss:.4f} train_acc={train_acc:.4f} "
161
+ f"val_loss={val_loss:.4f} val_acc={val_acc:.4f}"
162
+ )
163
+
164
+ history["best_epoch"] = best_epoch
165
+ history["best_val_acc"] = round(best_val_acc, 6) if best_epoch > 0 else None
166
+ self.history = history
167
+ return history
168
+
169
+ # ---------------- avaliação ----------------
170
+ def evaluate(self, data_loader: DataLoader) -> Dict[str, float]:
171
+ self.model.eval()
172
+ loss_sum = 0.0
173
+ correct = 0
174
+ total = 0
175
+ with torch.no_grad():
176
+ for X, y in data_loader:
177
+ X = X.to(self.device)
178
+ y = y.to(self.device)
179
+ logits = self.model(X)
180
+ loss = self.criterion(logits, y)
181
+ loss_sum += float(loss.item()) * X.size(0)
182
+ preds = logits.argmax(dim=1)
183
+ correct += int((preds == y).sum().item())
184
+ total += int(y.size(0))
185
+ return {
186
+ "loss": loss_sum / max(total, 1),
187
+ "accuracy": correct / max(total, 1),
188
+ "n": total,
189
+ }
190
+
191
+ # ---------------- inferência ----------------
192
+ def predict(self, inputs: Any) -> np.ndarray:
193
+ self.model.eval()
194
+ with torch.no_grad():
195
+ if isinstance(inputs, DataLoader):
196
+ outs: List[np.ndarray] = []
197
+ for batch in inputs:
198
+ X = batch[0] if isinstance(batch, (tuple, list)) else batch
199
+ X = X.to(self.device)
200
+ outs.append(self.model(X).argmax(dim=1).cpu().numpy())
201
+ return np.concatenate(outs, axis=0)
202
+ if isinstance(inputs, np.ndarray):
203
+ X = torch.from_numpy(inputs.astype(np.float32)).to(self.device)
204
+ elif isinstance(inputs, torch.Tensor):
205
+ X = inputs.to(self.device)
206
+ else:
207
+ X = torch.tensor(inputs, dtype=torch.float32).to(self.device)
208
+ if X.ndim == 1:
209
+ X = X.unsqueeze(0)
210
+ return self.model(X).argmax(dim=1).cpu().numpy()
211
+
212
+ # ---------------- persistência ----------------
213
+ def save_model(self, filename: str) -> str:
214
+ path = os.path.join(self.model_dir, filename)
215
+ torch.save(
216
+ {
217
+ "state_dict": self.model.state_dict(),
218
+ "input_size": self.input_size,
219
+ "num_classes": self.num_classes,
220
+ "hidden_dims": self._hidden_dims,
221
+ "dropout": self._dropout,
222
+ "project_name": self.project_name,
223
+ },
224
+ path,
225
+ )
226
+ print(f"✓ Model saved to {path}")
227
+ return path
228
+
229
+ def load_model(self, filename: str) -> None:
230
+ path = filename if os.path.isabs(filename) else os.path.join(
231
+ self.model_dir, filename
232
+ )
233
+ ckpt = torch.load(path, map_location=self.device)
234
+ self._hidden_dims = ckpt.get("hidden_dims", self._hidden_dims)
235
+ self._dropout = ckpt.get("dropout", self._dropout)
236
+ self.model = _MLP(
237
+ input_size=ckpt["input_size"],
238
+ hidden_dims=self._hidden_dims,
239
+ num_classes=ckpt["num_classes"],
240
+ dropout=self._dropout,
241
+ ).to(self.device)
242
+ self.model.load_state_dict(ckpt["state_dict"])
243
+
244
+ # ---------------- helpers ----------------
245
+ def _build_optimizer(
246
+ self, name: str, lr: float, weight_decay: float
247
+ ) -> optim.Optimizer:
248
+ params = self.model.parameters()
249
+ if name == "sgd":
250
+ return optim.SGD(params, lr=lr, momentum=0.9, weight_decay=weight_decay)
251
+ if name == "rmsprop":
252
+ return optim.RMSprop(params, lr=lr, weight_decay=weight_decay)
253
+ # default: adam
254
+ return optim.Adam(params, lr=lr, weight_decay=weight_decay)
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # Fábrica usada pelo main.py
259
+ # ---------------------------------------------------------------------------
260
+ def create_model(
261
+ input_size: int,
262
+ num_classes: int,
263
+ project_name: str,
264
+ base_path: str,
265
+ config: Optional[Dict[str, Any]] = None,
266
+ ) -> TabularMLP:
267
+ """Cria um ``TabularMLP`` pronto pra uso pelo main.py."""
268
+ return TabularMLP(
269
+ input_size=input_size,
270
+ num_classes=num_classes,
271
+ project_name=project_name,
272
+ base_path=base_path,
273
+ framework="pytorch",
274
+ config=config or {},
275
+ )
model/mpl_pequeno_torch/python_version.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.10
model/mpl_pequeno_torch/requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cpu
2
+
3
+ pandas
4
+ scikit-learn
5
+ joblib
6
+ torch
7
+ openpyxl
8
+ numpy
projects/project.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "TestMultipleRepos2",
3
+ "description": "Test RepoB",
4
+ "models": [
5
+ "mpl_pequeno_torch"
6
+ ],
7
+ "created_at": "2026-06-16T21:13:29.475614",
8
+ "project_id": "6993006e-b380-4834-b536-77105fc3e1a7"
9
+ }