uniovi-simur-wearablepermed-ml 1.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of uniovi-simur-wearablepermed-ml might be problematic. Click here for more details.
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/METADATA +411 -0
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/RECORD +19 -0
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/WHEEL +5 -0
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/entry_points.txt +3 -0
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/licenses/LICENSE.txt +21 -0
- uniovi_simur_wearablepermed_ml-1.1.0.dist-info/top_level.txt +1 -0
- wearablepermed_ml/__init__.py +16 -0
- wearablepermed_ml/basic_functions/__init__.py +0 -0
- wearablepermed_ml/basic_functions/address.py +17 -0
- wearablepermed_ml/data/DataReader.py +388 -0
- wearablepermed_ml/data/__init__.py +1 -0
- wearablepermed_ml/models/SiMuR_Model.py +671 -0
- wearablepermed_ml/models/__init__.py +1 -0
- wearablepermed_ml/models/model_generator.py +63 -0
- wearablepermed_ml/run_trainer_and_tester_30_times.py +130 -0
- wearablepermed_ml/tester.py +156 -0
- wearablepermed_ml/testing/__init__.py +0 -0
- wearablepermed_ml/testing/testing.py +203 -0
- wearablepermed_ml/trainer.py +782 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
import joblib # Librería empleada para guardar y cargar los modelos Random Forests
|
|
4
|
+
import numpy as np
|
|
5
|
+
from sklearn.metrics import accuracy_score, f1_score
|
|
6
|
+
import tensorflow as tf
|
|
7
|
+
import keras
|
|
8
|
+
# from sklearn.ensemble import RandomForestClassifier
|
|
9
|
+
from imblearn.ensemble import BalancedRandomForestClassifier
|
|
10
|
+
import xgboost as xgb
|
|
11
|
+
|
|
12
|
+
from tensorflow.keras import layers, models, optimizers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#import _spectral_features_calculator
|
|
16
|
+
|
|
17
|
+
# Librerías necesarias para implementar el algoritmo de fusión sensorial ahsr
|
|
18
|
+
# import ahrs
|
|
19
|
+
# from ahrs.filters import Madgwick
|
|
20
|
+
|
|
21
|
+
class SiMuRModel_ESANN(object):
|
|
22
|
+
def __init__(self, data, params: dict, **kwargs) -> None:
|
|
23
|
+
|
|
24
|
+
#############################################################################
|
|
25
|
+
# Aquí se tratan los parámetros del modelo. Esto es necesario porque estos modelos contienen muchos hiperparámetros
|
|
26
|
+
|
|
27
|
+
# - Hiperparámetros asociados a las opciones de entrenamiento de la CNN
|
|
28
|
+
self.optimizador = params.get("optimizador", "adam") # especifica el optimizador a utilizar durante el entrenamiento
|
|
29
|
+
self.tamanho_minilote = params.get("tamanho_minilote", 10) # especifica el tamaño del mini-lote
|
|
30
|
+
self.tasa_aprendizaje = params.get("tasa_aprendizaje", 0.01) # especifica el learning-rate empleado durante el entrenamiento
|
|
31
|
+
|
|
32
|
+
# - Hiperparámetros asociados a la arquitectura de la red CNN
|
|
33
|
+
self.N_capas = params.get("N_capas", 2) # especifica el número de capas ocultas de la red
|
|
34
|
+
self.activacion_capas_ocultas = params.get("funcion_activacion", "relu") # especifica la función de activación asociada las neuronas de las capas ocultas
|
|
35
|
+
self.numero_filtros = params.get("numero_filtros", 12) # especifica el número de filtros utilizados en las capas ocultas de la red
|
|
36
|
+
self.tamanho_filtro = params.get("tamanho_filtro", 7) # especifica el tamaño de los filtros de las capas ocultas
|
|
37
|
+
|
|
38
|
+
self.testMetrics = []
|
|
39
|
+
self.metrics = [accuracy_score, f1_score]
|
|
40
|
+
#############################################################################
|
|
41
|
+
# Los datos de entrenamiento vienen en el parametro data:
|
|
42
|
+
# - Vienen pre-procesados.
|
|
43
|
+
# - data suele ser un objeto o diccionario con:
|
|
44
|
+
# data.X_Train
|
|
45
|
+
# data.Y_Train
|
|
46
|
+
# data.X_Test
|
|
47
|
+
# data.Y_Test
|
|
48
|
+
# El formato del objeto Data puede variar de aplicación en aplicación
|
|
49
|
+
|
|
50
|
+
self.X_train = data.X_train
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
self.X_validation = data.X_validation
|
|
54
|
+
except:
|
|
55
|
+
print("Not enough data for validation.")
|
|
56
|
+
self.X_validation = None
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
self.X_test = data.X_test
|
|
60
|
+
except:
|
|
61
|
+
print("Not enough data for test.")
|
|
62
|
+
self.X_test = None
|
|
63
|
+
|
|
64
|
+
self.y_train = data.y_train
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
self.y_validation = data.y_validation
|
|
68
|
+
except:
|
|
69
|
+
print("Not enough data for validation.")
|
|
70
|
+
self.y_validation = None
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
self.y_test = data.y_test
|
|
74
|
+
except:
|
|
75
|
+
print("Not enough data for test.")
|
|
76
|
+
self.y_test = None
|
|
77
|
+
|
|
78
|
+
#############################################################################
|
|
79
|
+
# También se crea el modelo. Si es una red aquí se define el grafo.
|
|
80
|
+
# La creación del modelo se encapsula en la función "create_model"
|
|
81
|
+
# Ejemplo de lectura de parámetros:
|
|
82
|
+
# param1 = params.get("N_capas", 3)
|
|
83
|
+
|
|
84
|
+
self.model = self.create_model()
|
|
85
|
+
|
|
86
|
+
#############################################################################
|
|
87
|
+
|
|
88
|
+
def create_model(self):
|
|
89
|
+
# Aquí se define la red, SVC, árbol, etc.
|
|
90
|
+
self.numClasses = int((max(self.y_train)+1)[0]) # especifica el número de clases
|
|
91
|
+
|
|
92
|
+
# if (self.X_train).shape[1]==12:
|
|
93
|
+
# dimension_de_entrada = (12, 250)
|
|
94
|
+
# elif (self.X_train).shape[1]==6:
|
|
95
|
+
dimension_de_entrada = (6, 250)
|
|
96
|
+
|
|
97
|
+
model = models.Sequential()
|
|
98
|
+
model.add(layers.InputLayer(input_shape=dimension_de_entrada))
|
|
99
|
+
|
|
100
|
+
# Añadir capas convolucionales según N_capas
|
|
101
|
+
for i in range(self.N_capas):
|
|
102
|
+
filtros = self.numero_filtros
|
|
103
|
+
model.add(layers.Conv1D(filtros, self.tamanho_filtro, padding="causal", activation=self.activacion_capas_ocultas))
|
|
104
|
+
model.add(layers.LayerNormalization())
|
|
105
|
+
|
|
106
|
+
# Capas finales fijas
|
|
107
|
+
model.add(layers.GlobalAveragePooling1D())
|
|
108
|
+
model.add(layers.Dropout(0.2))
|
|
109
|
+
model.add(layers.Dense(self.numClasses, activation='softmax'))
|
|
110
|
+
|
|
111
|
+
# Optimizadores
|
|
112
|
+
if self.optimizador == "adam":
|
|
113
|
+
optimizer_hyperparameter = tf.keras.optimizers.Adam(learning_rate=self.tasa_aprendizaje)
|
|
114
|
+
elif self.optimizador == 'rmsprop':
|
|
115
|
+
optimizer_hyperparameter = tf.keras.optimizers.RMSprop(learning_rate=self.tasa_aprendizaje)
|
|
116
|
+
elif self.optimizador == 'SGD':
|
|
117
|
+
optimizer_hyperparameter = tf.keras.optimizers.SGD(learning_rate=self.tasa_aprendizaje)
|
|
118
|
+
else:
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
model.compile(optimizer=optimizer_hyperparameter,
|
|
122
|
+
loss='sparse_categorical_crossentropy',
|
|
123
|
+
metrics=['accuracy'])
|
|
124
|
+
model.summary()
|
|
125
|
+
|
|
126
|
+
return model
|
|
127
|
+
|
|
128
|
+
def train(self, epochs):
|
|
129
|
+
# Se lanza el entrenamiento de los modelos. El código para lanzar el entrenamiento depende mucho del modelo.
|
|
130
|
+
callbacks = [
|
|
131
|
+
keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# Verifica si X_validation e y_validation existen
|
|
135
|
+
if self.X_validation is not None and self.y_validation is not None:
|
|
136
|
+
history = self.model.fit(
|
|
137
|
+
self.X_train,
|
|
138
|
+
self.y_train,
|
|
139
|
+
validation_data=(self.X_validation, self.y_validation),
|
|
140
|
+
batch_size=self.tamanho_minilote,
|
|
141
|
+
epochs=epochs,
|
|
142
|
+
verbose=1,
|
|
143
|
+
callbacks=callbacks
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
history = self.model.fit(
|
|
147
|
+
self.X_train,
|
|
148
|
+
self.y_train,
|
|
149
|
+
batch_size=self.tamanho_minilote,
|
|
150
|
+
epochs=epochs,
|
|
151
|
+
verbose=1,
|
|
152
|
+
callbacks=callbacks
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if self.X_test is not None and self.y_test is not None:
|
|
156
|
+
# Cuando acaba el entrenamiento y obtenemos los pesos óptimos, las métricas de error para los datos de test son calculadas.
|
|
157
|
+
self.y_test_est = self.predict(self.X_test)
|
|
158
|
+
self.y_test_est = np.argmax(self.y_test_est, axis=1) # Trabajamos con clasificación multicategoría
|
|
159
|
+
|
|
160
|
+
# y_test_est_float_round = np.around(self.y_test_est) # Redondear vector de tipo float (codificado en one_hot)
|
|
161
|
+
# y_test_est_int_round = y_test_est_float_round.astype(int) # Obtención de vector de tipo int
|
|
162
|
+
# self.y_test_est = y_test_est_int_round # Asignación del atributo y_test_est
|
|
163
|
+
|
|
164
|
+
self.testMetrics = [accuracy_score(self.y_test, self.y_test_est),
|
|
165
|
+
f1_score(self.y_test, self.y_test_est, average='micro')] # REVISAR la opción 'average'
|
|
166
|
+
|
|
167
|
+
def predict(self,X):
|
|
168
|
+
# Método para predecir una o varias muestras.
|
|
169
|
+
# El código puede variar dependiendo del modelo
|
|
170
|
+
return self.model.predict(X)
|
|
171
|
+
|
|
172
|
+
def store(self, model_id, path):
|
|
173
|
+
# Método para guardar los pesos en path
|
|
174
|
+
# Serialize weights to HDF5
|
|
175
|
+
path = os.path.join(path, model_id + ".weights.h5")
|
|
176
|
+
|
|
177
|
+
self.model.save_weights(path)
|
|
178
|
+
print("Saved model to disk.")
|
|
179
|
+
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
def load(self, model_id, path):
|
|
183
|
+
# Método para cargar los pesos desde el path indicado
|
|
184
|
+
path = os.path.join(path, model_id + ".weights.h5")
|
|
185
|
+
|
|
186
|
+
self.model.load_weights(path)
|
|
187
|
+
print("Loaded model from disk.")
|
|
188
|
+
|
|
189
|
+
# Evaluate loaded model on test data
|
|
190
|
+
self.model.compile(loss='sparse_categorical_crossentropy',
|
|
191
|
+
optimizer=self.optimizador,
|
|
192
|
+
metrics=['accuracy'])
|
|
193
|
+
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
########## MÉTODOS DE LAS CLASES ##########
|
|
197
|
+
# Estos métodos se pueden llamar sin instar un objeto de la clase
|
|
198
|
+
# Ej.: import model; model.get_model_type()
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def get_model_type(cls):
|
|
202
|
+
return "CNN" # Aquí se puede indicar qué tipo de modelo es: RRNN, keras, scikit-learn, etc.
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def get_model_name(cls):
|
|
206
|
+
return "SiMuR" # Aquí se puede indicar un ID que identifique el modelo
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def get_model_Obj(cls):
|
|
210
|
+
return SiMuRModel_ESANN
|
|
211
|
+
|
|
212
|
+
class SiMuRModel_CAPTURE24(object):
|
|
213
|
+
def __init__(self, data, params: dict):
|
|
214
|
+
# -----------------------------
|
|
215
|
+
# Hiperparámetros de entrenamiento
|
|
216
|
+
self.optimizador = params.get("optimizador", "adam")
|
|
217
|
+
self.tasa_aprendizaje = params.get("tasa_aprendizaje", 5e-3)
|
|
218
|
+
self.tamanho_minilote = params.get("tamanho_minilote", 4) # batch pequeño
|
|
219
|
+
|
|
220
|
+
# -----------------------------
|
|
221
|
+
# Arquitectura CNN
|
|
222
|
+
self.N_capas = params.get("N_capas", 3) # usar 3–4 etapas
|
|
223
|
+
self.funcion_activacion = params.get("funcion_activacion", "relu")
|
|
224
|
+
self.numero_filtros = params.get("numero_filtros", 64)
|
|
225
|
+
self.tamanho_filtro = params.get("tamanho_filtro", 3)
|
|
226
|
+
self.num_resblocks = params.get("num_resblocks", 1)
|
|
227
|
+
|
|
228
|
+
# -----------------------------
|
|
229
|
+
# Datos
|
|
230
|
+
self.X_train = data.X_train
|
|
231
|
+
self.y_train = data.y_train
|
|
232
|
+
self.X_validation = getattr(data, "X_validation", None)
|
|
233
|
+
self.y_validation = getattr(data, "y_validation", None)
|
|
234
|
+
self.X_test = getattr(data, "X_test", None)
|
|
235
|
+
self.y_test = getattr(data, "y_test", None)
|
|
236
|
+
|
|
237
|
+
# -----------------------------
|
|
238
|
+
# Input shape y clases
|
|
239
|
+
self.input_shape = (6,250) # (6, 250)
|
|
240
|
+
self.num_classes = int(np.max(self.y_train) + 1)
|
|
241
|
+
|
|
242
|
+
# -----------------------------
|
|
243
|
+
# Crear modelo
|
|
244
|
+
self.model = self.create_model()
|
|
245
|
+
|
|
246
|
+
# -----------------------------
|
|
247
|
+
# Bloque residual
|
|
248
|
+
def ResBlock(self, x, filtros, kernel_size, activation='relu'):
|
|
249
|
+
shortcut = x
|
|
250
|
+
x = layers.Conv1D(filtros, kernel_size, padding='same', activation=activation)(x)
|
|
251
|
+
x = layers.Conv1D(filtros, kernel_size, padding='same')(x)
|
|
252
|
+
x = layers.Add()([x, shortcut])
|
|
253
|
+
x = layers.Activation(activation)(x)
|
|
254
|
+
x = layers.BatchNormalization()(x)
|
|
255
|
+
return x
|
|
256
|
+
|
|
257
|
+
# -----------------------------
|
|
258
|
+
# Crear modelo
|
|
259
|
+
def create_model(self):
|
|
260
|
+
inputs = layers.Input(shape=self.input_shape)
|
|
261
|
+
x = inputs
|
|
262
|
+
|
|
263
|
+
filtros = self.numero_filtros # inicializamos filtros
|
|
264
|
+
for i in range(self.N_capas):
|
|
265
|
+
# Reducir dimensionalidad temporal en cada etapa excepto la última
|
|
266
|
+
stride = 2 if i < self.N_capas - 1 else 1
|
|
267
|
+
x = layers.Conv1D(filtros, self.tamanho_filtro, strides=stride,
|
|
268
|
+
padding='same', activation=self.funcion_activacion)(x)
|
|
269
|
+
|
|
270
|
+
# 1 ResBlock por etapa para no saturar memoria
|
|
271
|
+
for _ in range(self.num_resblocks):
|
|
272
|
+
x = self.ResBlock(x, filtros, self.tamanho_filtro, self.funcion_activacion)
|
|
273
|
+
|
|
274
|
+
# Solo duplicar filtros si no es la última capa
|
|
275
|
+
if i < self.N_capas - 1:
|
|
276
|
+
filtros *= 2
|
|
277
|
+
|
|
278
|
+
# Global average pooling para reducir dimensionalidad
|
|
279
|
+
x = layers.GlobalAveragePooling1D()(x)
|
|
280
|
+
|
|
281
|
+
# Dropout para regularización
|
|
282
|
+
x = layers.Dropout(0.3)(x) # menos agresivo que 0.5 para batch pequeño
|
|
283
|
+
|
|
284
|
+
# Dense intermedio más pequeño para memoria limitada
|
|
285
|
+
x = layers.Dense(64, activation='relu')(x)
|
|
286
|
+
x = layers.BatchNormalization()(x)
|
|
287
|
+
|
|
288
|
+
# Capa de salida
|
|
289
|
+
outputs = layers.Dense(self.num_classes, activation='softmax')(x)
|
|
290
|
+
|
|
291
|
+
# Optimizer
|
|
292
|
+
if self.optimizador.lower() == "adam":
|
|
293
|
+
optimizer = tf.keras.optimizers.Adam(learning_rate=self.tasa_aprendizaje)
|
|
294
|
+
elif self.optimizador.lower() == "rmsprop":
|
|
295
|
+
optimizer = tf.keras.optimizers.RMSprop(learning_rate=self.tasa_aprendizaje)
|
|
296
|
+
elif self.optimizador.lower() == "sgd":
|
|
297
|
+
optimizer = tf.keras.optimizers.SGD(learning_rate=self.tasa_aprendizaje)
|
|
298
|
+
else:
|
|
299
|
+
raise ValueError(f"Optimizador {self.optimizador} no soportado")
|
|
300
|
+
|
|
301
|
+
model = models.Model(inputs, outputs)
|
|
302
|
+
model.compile(optimizer=optimizer,
|
|
303
|
+
loss='sparse_categorical_crossentropy',
|
|
304
|
+
metrics=['accuracy'])
|
|
305
|
+
|
|
306
|
+
return model
|
|
307
|
+
|
|
308
|
+
def train(self, epochs):
|
|
309
|
+
# Se lanza el entrenamiento de los modelos. El código para lanzar el entrenamiento depende mucho del modelo.
|
|
310
|
+
callbacks = [
|
|
311
|
+
keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
# Verifica si X_validation e y_validation existen
|
|
315
|
+
if self.X_validation is not None and self.y_validation is not None:
|
|
316
|
+
history = self.model.fit(
|
|
317
|
+
self.X_train,
|
|
318
|
+
self.y_train,
|
|
319
|
+
validation_data=(self.X_validation, self.y_validation),
|
|
320
|
+
batch_size=self.tamanho_minilote,
|
|
321
|
+
epochs=epochs,
|
|
322
|
+
verbose=1,
|
|
323
|
+
callbacks=callbacks
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
history = self.model.fit(
|
|
327
|
+
self.X_train,
|
|
328
|
+
self.y_train,
|
|
329
|
+
batch_size=self.tamanho_minilote,
|
|
330
|
+
epochs=epochs,
|
|
331
|
+
verbose=1,
|
|
332
|
+
callbacks=callbacks
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if self.X_test is not None and self.y_test is not None:
|
|
336
|
+
# Cuando acaba el entrenamiento y obtenemos los pesos óptimos, las métricas de error para los datos de test son calculadas.
|
|
337
|
+
self.y_test_est = self.predict(self.X_test)
|
|
338
|
+
self.y_test_est = np.argmax(self.y_test_est, axis=1) # Trabajamos con clasificación multicategoría
|
|
339
|
+
|
|
340
|
+
# y_test_est_float_round = np.around(self.y_test_est) # Redondear vector de tipo float (codificado en one_hot)
|
|
341
|
+
# y_test_est_int_round = y_test_est_float_round.astype(int) # Obtención de vector de tipo int
|
|
342
|
+
# self.y_test_est = y_test_est_int_round # Asignación del atributo y_test_est
|
|
343
|
+
|
|
344
|
+
self.testMetrics = [accuracy_score(self.y_test, self.y_test_est),
|
|
345
|
+
f1_score(self.y_test, self.y_test_est, average='micro')] # REVISAR la opción 'average'
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def predict(self, X):
|
|
349
|
+
# Método para predecir una o varias muestras.
|
|
350
|
+
# El código puede variar dependiendo del modelo
|
|
351
|
+
return self.model.predict(X)
|
|
352
|
+
|
|
353
|
+
def store(self, model_id, path):
|
|
354
|
+
# Método para guardar los pesos en path
|
|
355
|
+
# Serialize weights to HDF5
|
|
356
|
+
path = os.path.join(path, model_id + ".weights.h5")
|
|
357
|
+
|
|
358
|
+
self.model.save_weights(path)
|
|
359
|
+
print("Saved model to disk.")
|
|
360
|
+
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
def load(self, model_id, path):
|
|
364
|
+
# Método para cargar los pesos desde el path indicado
|
|
365
|
+
path = os.path.join(path, model_id + ".weights.h5")
|
|
366
|
+
|
|
367
|
+
self.model.load_weights(path)
|
|
368
|
+
print("Loaded model from disk.")
|
|
369
|
+
# Evaluate loaded model on test data
|
|
370
|
+
self.model.compile(loss='sparse_categorical_crossentropy',
|
|
371
|
+
optimizer=self.optimizador,
|
|
372
|
+
metrics=['accuracy'])
|
|
373
|
+
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
########## MÉTODOS DE LAS CLASES ##########
|
|
377
|
+
# Estos métodos se pueden llamar sin instar un objeto de la clase
|
|
378
|
+
# Ej.: import model; model.get_model_type()
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def get_model_type(cls):
|
|
382
|
+
return "CNN" # Aquí se puede indicar qué tipo de modelo es: RRNN, keras, scikit-learn, etc.
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def get_model_name(cls):
|
|
386
|
+
return "SiMuR" # Aquí se puede indicar un ID que identifique el modelo
|
|
387
|
+
|
|
388
|
+
@classmethod
|
|
389
|
+
def get_model_Obj(cls):
|
|
390
|
+
return SiMuRModel_CAPTURE24
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class SiMuRModel_RandomForest(object):
|
|
394
|
+
def __init__(self, data, params: dict, **kwargs) -> None:
|
|
395
|
+
|
|
396
|
+
#############################################################################
|
|
397
|
+
# Aquí se tratan los parámetros del modelo. Esto es necesario porque estos modelos contienen muchos hiperparámetros
|
|
398
|
+
self.n_estimators = params.get("n_estimators", 1000)
|
|
399
|
+
self.max_depth = params.get("max_depth", 10)
|
|
400
|
+
self.min_samples_split = params.get("min_samples_split", 3)
|
|
401
|
+
self.min_samples_leaf = params.get("min_samples_leaf", 2)
|
|
402
|
+
self.max_features = params.get("max_features", "auto")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
self.testMetrics = []
|
|
406
|
+
self.metrics = [accuracy_score, f1_score]
|
|
407
|
+
#############################################################################
|
|
408
|
+
# Los datos de entrenamiento vienen en el parametro data:
|
|
409
|
+
# - Vienen pre-procesados.
|
|
410
|
+
# - d".h5"ain = data.y_train
|
|
411
|
+
self.X_train = data.X_train
|
|
412
|
+
self.X_test = data.X_test
|
|
413
|
+
|
|
414
|
+
self.y_train = data.y_train
|
|
415
|
+
self.y_test = data.y_test
|
|
416
|
+
|
|
417
|
+
#############################################################################
|
|
418
|
+
|
|
419
|
+
# También se crea el modelo. Si es una red aquí se define el grafo.
|
|
420
|
+
# La creación del modelo se encapsula en la función "create_model"
|
|
421
|
+
# Ejemplo de lectura de parámetros:
|
|
422
|
+
# param1 = params.get("N_capas", 3)
|
|
423
|
+
|
|
424
|
+
self.model = self.create_model()
|
|
425
|
+
|
|
426
|
+
#############################################################################
|
|
427
|
+
|
|
428
|
+
def create_model(self):
|
|
429
|
+
# Creamos el modelo de Random Forest con 3000 árboles
|
|
430
|
+
model = BalancedRandomForestClassifier(n_estimators=self.n_estimators,
|
|
431
|
+
n_jobs=-1, # n_jobs=-1 utiliza todos los núcleos disponibles para acelerar el entrenamiento
|
|
432
|
+
verbose=1,
|
|
433
|
+
max_features=self.max_features,
|
|
434
|
+
max_depth=self.max_depth,
|
|
435
|
+
min_samples_split=self.min_samples_split,
|
|
436
|
+
min_samples_leaf=self.min_samples_leaf)
|
|
437
|
+
|
|
438
|
+
return model
|
|
439
|
+
|
|
440
|
+
def train(self):
|
|
441
|
+
|
|
442
|
+
# Se lanza el entrenamiento de los modelos. El código para lanzar el entrenamiento depende mucho del modelo.
|
|
443
|
+
history = self.model.fit(self.X_train, self.y_train)
|
|
444
|
+
|
|
445
|
+
# Cuando acaba el entrenamiento y obtenemos los pesos óptimos, las métricas de error para los datos de test son calculadas.
|
|
446
|
+
self.y_test_est = self.predict(self.X_test)
|
|
447
|
+
|
|
448
|
+
y_test_est_float_round = np.around(self.y_test_est) # Redondear vector de tipo float (codificado en one_hot)
|
|
449
|
+
y_test_est_int_round = y_test_est_float_round.astype(int) # Obtención de vector de tipo int
|
|
450
|
+
self.y_test_est = y_test_est_int_round # Asignación del atributo y_test_est
|
|
451
|
+
|
|
452
|
+
self.testMetrics = [accuracy_score(self.y_test, self.y_test_est),
|
|
453
|
+
f1_score(self.y_test, self.y_test_est, average='micro')] # REVISAR la opción 'average'
|
|
454
|
+
|
|
455
|
+
def predict(self, X):
|
|
456
|
+
# Método para predecir una o varias muestras.
|
|
457
|
+
# El código puede variar dependiendo del modelo
|
|
458
|
+
|
|
459
|
+
return self.model.predict(X)
|
|
460
|
+
|
|
461
|
+
def store(self, model_id, path):
|
|
462
|
+
path = os.path.join(path, model_id + ".pkl")
|
|
463
|
+
|
|
464
|
+
# Método para guardar el modelo Random Forest en formato '.pkl'
|
|
465
|
+
joblib.dump(self.model, path)
|
|
466
|
+
print("Saved model to disk")
|
|
467
|
+
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
def load(self, model_id, path):
|
|
471
|
+
path = os.path.join(path, model_id + ".pkl")
|
|
472
|
+
|
|
473
|
+
# Método para cargar el modelo Random Forest desde el path indicado
|
|
474
|
+
self.model = joblib.load(path)
|
|
475
|
+
print("Loaded model from disk")
|
|
476
|
+
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
########## MÉTODOS DE LAS CLASES ##########
|
|
480
|
+
# Estos métodos se pueden llamar sin instar un objeto de la clase
|
|
481
|
+
# Ej.: import model; model.get_model_type()
|
|
482
|
+
|
|
483
|
+
@classmethod
|
|
484
|
+
def get_model_type(cls):
|
|
485
|
+
return "Balanced Random Forest" # Aquí se puede indicar qué tipo de modelo es: RRNN, keras, scikit-learn, etc.
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def get_model_name(cls):
|
|
489
|
+
return "SiMuR" # Aquí se puede indicar un ID que identifique el modelo
|
|
490
|
+
|
|
491
|
+
@classmethod
|
|
492
|
+
def get_model_Obj(cls):
|
|
493
|
+
return SiMuRModel_RandomForest
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# Implementación del modelo XGBoost
|
|
497
|
+
class SiMuRModel_XGBoost(object):
|
|
498
|
+
def __init__(self, data, params: dict, **kwargs) -> None:
|
|
499
|
+
|
|
500
|
+
#############################################################################
|
|
501
|
+
# Aquí se tratan los parámetros del modelo. Esto es necesario porque estos modelos contienen muchos hiperparámetros
|
|
502
|
+
self.num_boost_round = params.get("num_boost_round", 1000) # Número de estimadores
|
|
503
|
+
self.max_depth = params.get("max_depth", 10) # Profundidad máxima
|
|
504
|
+
self.learning_rate = params.get("learning_rate", 0.05) # Tasa de aprendizaje
|
|
505
|
+
self.subsample = params.get("subsample", 0.70) # Fracción de muestras por árbol
|
|
506
|
+
self.colsample_bytree = params.get("colsample_bytree", 0.80) # Fracción de columnas por árbol
|
|
507
|
+
self.gamma = params.get("gamma", 4.2) # Regularización mínima de pérdida
|
|
508
|
+
self.min_child_weight = params.get("min_child_weight", 2) # Peso mínimo de hijos
|
|
509
|
+
self.reg_alpha = params.get("reg_alpha", 0.6) # L1 regularization
|
|
510
|
+
self.reg_lambda = params.get("reg_lambda", 0.04) # L2 regularization
|
|
511
|
+
|
|
512
|
+
self.testMetrics = []
|
|
513
|
+
self.metrics = [accuracy_score, f1_score]
|
|
514
|
+
#############################################################################
|
|
515
|
+
# Los datos de entrenamiento vienen en el parametro data:
|
|
516
|
+
# - Vienen pre-procesados.
|
|
517
|
+
# - data suele ser un objeto o diccionario con:
|
|
518
|
+
# data.X_Train
|
|
519
|
+
# data.Y_Train
|
|
520
|
+
# data.X_Test
|
|
521
|
+
# data.Y_Test
|
|
522
|
+
|
|
523
|
+
self.X_train = data.X_train
|
|
524
|
+
|
|
525
|
+
try:
|
|
526
|
+
self.X_validation = data.X_validation
|
|
527
|
+
except:
|
|
528
|
+
print("Not enough data for validation.")
|
|
529
|
+
self.X_validation = None
|
|
530
|
+
|
|
531
|
+
try:
|
|
532
|
+
self.X_test = data.X_test
|
|
533
|
+
except:
|
|
534
|
+
print("Not enough data for test.")
|
|
535
|
+
self.X_test = None
|
|
536
|
+
|
|
537
|
+
self.y_train = data.y_train
|
|
538
|
+
|
|
539
|
+
try:
|
|
540
|
+
self.y_validation = data.y_validation
|
|
541
|
+
except:
|
|
542
|
+
print("Not enough data for validation.")
|
|
543
|
+
self.y_validation = None
|
|
544
|
+
|
|
545
|
+
try:
|
|
546
|
+
self.y_test = data.y_test
|
|
547
|
+
except:
|
|
548
|
+
print("Not enough data for test.")
|
|
549
|
+
self.y_test = None
|
|
550
|
+
|
|
551
|
+
#############################################################################
|
|
552
|
+
|
|
553
|
+
# También se crea el modelo. Si es una red aquí se define el grafo.
|
|
554
|
+
# La creación del modelo se encapsula en la función "create_model"
|
|
555
|
+
# Ejemplo de lectura de parámetros:
|
|
556
|
+
# param1 = params.get("N_capas", 3)
|
|
557
|
+
|
|
558
|
+
self.model = self.create_model()
|
|
559
|
+
|
|
560
|
+
#############################################################################
|
|
561
|
+
|
|
562
|
+
def create_model(self):
|
|
563
|
+
# Creamos el modelo de XGBoost, el cual se entrenará con las mismas características definidas para el Random Forest
|
|
564
|
+
model = xgb.XGBClassifier(use_label_encoder=False,
|
|
565
|
+
num_boost_round=self.num_boost_round, # Como parámetros indicamos: el número de estimadores
|
|
566
|
+
max_depth=self.max_depth, # Profundidad máxima
|
|
567
|
+
learning_rate=self.learning_rate, # Tasa de aprendizaje
|
|
568
|
+
subsample=self.subsample, # Fracción de muestras por árbol
|
|
569
|
+
colsample_bytree=self.colsample_bytree, # Fracción de columnas por árbol
|
|
570
|
+
gamma=self.gamma, # Regularización mínima de pérdida
|
|
571
|
+
min_child_weight=self.min_child_weight, # Peso mínimo de hijos
|
|
572
|
+
reg_alpha=self.reg_alpha, # L1 regularization
|
|
573
|
+
reg_lambda=self.reg_lambda, # L2 regularization
|
|
574
|
+
eval_metric='mlogloss',
|
|
575
|
+
tree_method="gpu_hist", # GPU
|
|
576
|
+
predictor="gpu_predictor", # fuerza GPU
|
|
577
|
+
random_state=None # o simplemente no fijar semilla
|
|
578
|
+
)
|
|
579
|
+
return model
|
|
580
|
+
|
|
581
|
+
def train(self):
|
|
582
|
+
# --- Conversión a DMatrix ---
|
|
583
|
+
dtrain = xgb.DMatrix(self.X_train, label=self.y_train) # DMatrix de entrenamiento
|
|
584
|
+
dval = xgb.DMatrix(self.X_validation, label=self.y_validation) # DMatrix de validación
|
|
585
|
+
# --- Parámetros del modelo ---
|
|
586
|
+
params = {
|
|
587
|
+
"objective": "multi:softprob", # Salida probabilidades multiclase
|
|
588
|
+
"num_class": len(np.unique(self.y_train)), # Número de clases
|
|
589
|
+
"eval_metric": "mlogloss", # Métrica log-loss multiclase
|
|
590
|
+
"max_depth": self.max_depth, # Profundidad máxima
|
|
591
|
+
"learning_rate":self.learning_rate, # Tasa de aprendizaje
|
|
592
|
+
"subsample": self.subsample, # Fracción de muestras por árbol
|
|
593
|
+
"colsample_bytree": self.colsample_bytree, # Fracción de columnas por árbol
|
|
594
|
+
"gamma": self.gamma, # Regularización mínima de pérdida
|
|
595
|
+
"min_child_weight": self.min_child_weight, # Peso mínimo de hijos
|
|
596
|
+
"reg_alpha": self.reg_alpha, # L1 regularization
|
|
597
|
+
"reg_lambda": self.reg_lambda, # L2 regularization
|
|
598
|
+
"seed": np.random.randint(0, 1000000) # Semilla aleatoria en cada run
|
|
599
|
+
}
|
|
600
|
+
# --- Lista de evaluaciones ---
|
|
601
|
+
evals = [(dtrain, "train"), (dval, "validation")] # Conjuntos de evaluación
|
|
602
|
+
# --- Entrenamiento con early stopping ---
|
|
603
|
+
self.model = xgb.train(
|
|
604
|
+
params=params, # Parámetros
|
|
605
|
+
dtrain=dtrain, # Datos de entrenamiento
|
|
606
|
+
num_boost_round=self.num_boost_round, # Número de estimadores (rondas) en XGBoost
|
|
607
|
+
evals=evals, # Conjuntos de validación
|
|
608
|
+
early_stopping_rounds=20 # Parada temprana (early-stopping)
|
|
609
|
+
)
|
|
610
|
+
# --- Predicciones ---
|
|
611
|
+
y_predicted_probability = self.model.predict(dval) # Probabilidades predichas
|
|
612
|
+
self.y_validation_est = np.argmax(y_predicted_probability, axis=1) # Clase con mayor probabilidad
|
|
613
|
+
# --- Métricas ---
|
|
614
|
+
self.validationMetrics = [
|
|
615
|
+
accuracy_score(self.y_validation, self.y_validation_est), # Exactitud
|
|
616
|
+
f1_score(self.y_validation, self.y_validation_est, average='micro') # F1 micro
|
|
617
|
+
]
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def predict(self, X):
|
|
621
|
+
dmatrix = xgb.DMatrix(X) # Convierte numpy.ndarray a DMatrix
|
|
622
|
+
y_pred_proba = self.model.predict(dmatrix) # Obtiene probabilidades o scores
|
|
623
|
+
|
|
624
|
+
# Para clasificación multiclase (softprob), devuelve la clase con mayor probabilidad
|
|
625
|
+
return np.argmax(y_pred_proba, axis=1)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def store(self, model_id, path):
|
|
629
|
+
path = os.path.join(path, model_id + ".pkl")
|
|
630
|
+
# Método para guardar el modelo Random Forest en formato '.pkl'
|
|
631
|
+
joblib.dump(self.model, path)
|
|
632
|
+
print("Saved model to disk.")
|
|
633
|
+
|
|
634
|
+
return None
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def load(self, model_id, path):
|
|
638
|
+
path = os.path.join(path, model_id + ".pkl")
|
|
639
|
+
# Método para cargar el modelo Random Forest desde el path indicado
|
|
640
|
+
self.model = joblib.load(path)
|
|
641
|
+
print("Loaded model from disk.")
|
|
642
|
+
|
|
643
|
+
return None
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
########## MÉTODOS DE LAS CLASES ##########
|
|
647
|
+
# Estos métodos se pueden llamar sin instar un objeto de la clase
|
|
648
|
+
# Ej.: import model; model.get_model_type()
|
|
649
|
+
|
|
650
|
+
@classmethod
|
|
651
|
+
def get_model_type(cls):
|
|
652
|
+
return "XGBoost" # Aquí se puede indicar qué tipo de modelo es: RRNN, keras, scikit-learn, etc.
|
|
653
|
+
|
|
654
|
+
@classmethod
|
|
655
|
+
def get_model_name(cls):
|
|
656
|
+
return "SiMuR" # Aquí se puede indicar un ID que identifique el modelo
|
|
657
|
+
|
|
658
|
+
@classmethod
|
|
659
|
+
def get_model_Obj(cls):
|
|
660
|
+
return SiMuRModel_XGBoost
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
##########################################
|
|
664
|
+
# Unit testing
|
|
665
|
+
##########################################
|
|
666
|
+
if __name__ == "__main__":
|
|
667
|
+
# Este código solo se ejecuta si el script de ejecución principal es BaseModel.py:
|
|
668
|
+
# run BaseModel.py
|
|
669
|
+
|
|
670
|
+
# Aquí se puede escribir un código de prueba para probar por separado
|
|
671
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .SiMuR_Model import SiMuRModel_ESANN, SiMuRModel_CAPTURE24, SiMuRModel_RandomForest, SiMuRModel_XGBoost
|