likelihood 2.2.0.dev1__cp310-cp310-manylinux_2_28_x86_64.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.
@@ -0,0 +1,896 @@
1
+ from .autoencoders import (
2
+ EarlyStopping,
3
+ LoRALayer,
4
+ OneHotEncoder,
5
+ cal_loss_step,
6
+ keras_tuner,
7
+ l2,
8
+ np,
9
+ os,
10
+ partial,
11
+ pd,
12
+ rmtree,
13
+ sampling,
14
+ suppress_warnings,
15
+ tf,
16
+ train_step,
17
+ )
18
+
19
+
20
+ @tf.keras.utils.register_keras_serializable(package="Custom", name="stabilize_log_var")
21
+ def stabilize_log_var(x):
22
+ return x + 1e-7
23
+
24
+
25
+ @tf.keras.utils.register_keras_serializable(package="Custom", name="sampling_wrapper")
26
+ def sampling_wrapper(args):
27
+ mean, log_var = args
28
+ return sampling(mean, log_var)
29
+
30
+
31
+ @tf.keras.utils.register_keras_serializable(package="Custom", name="sampling_output_shape")
32
+ def sampling_output_shape(input_shapes):
33
+ return input_shapes[0]
34
+
35
+
36
+ class AutoClassifier:
37
+ """
38
+ An auto-classifier model that automatically determines the best classification strategy based on the input data.
39
+
40
+ Parameters
41
+ ----------
42
+ input_shape_parm : `int`
43
+ The shape of the input data.
44
+ num_classes : `int`
45
+ The number of classes in the dataset.
46
+ units : `int`
47
+ The number of neurons in each hidden layer.
48
+ activation : `str`
49
+ The type of activation function to use for the neural network layers.
50
+
51
+ Keyword Arguments
52
+ -----------------
53
+ Additional keyword arguments to pass to the model.
54
+
55
+ classifier_activation : `str`
56
+ The activation function to use for the classifier layer. Default is `softmax`. If the activation function is not a classification function, the model can be used in regression problems.
57
+ num_layers : `int`
58
+ The number of hidden layers in the classifier. Default is 1.
59
+ dropout : `float`
60
+ The dropout rate to use in the classifier. Default is None.
61
+ l2_reg : `float`
62
+ The L2 regularization parameter. Default is 0.0.
63
+ vae_mode : `bool`
64
+ Whether to use variational autoencoder mode. Default is False.
65
+ vae_units : `int`
66
+ The number of units in the variational autoencoder. Default is 2.
67
+ lora_mode : `bool`
68
+ Whether to use LoRA layers. Default is False.
69
+ lora_rank : `int`
70
+ The rank of the LoRA layer. Default is 4.
71
+ """
72
+
73
+ def __init__(self, input_shape_parm, num_classes, units, activation, **kwargs):
74
+ self.input_shape_parm = input_shape_parm
75
+ self.num_classes = num_classes
76
+ self.units = units
77
+ self.activation = activation
78
+
79
+ # Store all configuration parameters
80
+ self.classifier_activation = kwargs.get("classifier_activation", "softmax")
81
+ self.num_layers = kwargs.get("num_layers", 1)
82
+ self.dropout = kwargs.get("dropout", None)
83
+ self.l2_reg = kwargs.get("l2_reg", 0.0)
84
+ self.vae_mode = kwargs.get("vae_mode", False)
85
+ self.vae_units = kwargs.get("vae_units", 2)
86
+ self.lora_mode = kwargs.get("lora_mode", False)
87
+ self.lora_rank = kwargs.get("lora_rank", 4)
88
+
89
+ # Initialize models as None - will be built when needed
90
+ self._encoder = None
91
+ self._decoder = None
92
+ self._classifier = None
93
+ self._main_model = None
94
+
95
+ # Build all models
96
+ self._build_models()
97
+
98
+ def _build_encoder(self):
99
+ """Build the encoder model."""
100
+ if self.vae_mode:
101
+ inputs = tf.keras.Input(shape=(self.input_shape_parm,), name="encoder_input")
102
+ x = tf.keras.layers.Dense(
103
+ units=self.units,
104
+ kernel_regularizer=l2(self.l2_reg),
105
+ kernel_initializer="he_normal",
106
+ name="vae_encoder_dense_1",
107
+ )(inputs)
108
+ x = tf.keras.layers.BatchNormalization(name="vae_encoder_bn_1")(x)
109
+ x = tf.keras.layers.Activation(self.activation, name="vae_encoder_act_1")(x)
110
+ x = tf.keras.layers.Dense(
111
+ units=int(self.units / 2),
112
+ kernel_regularizer=l2(self.l2_reg),
113
+ kernel_initializer="he_normal",
114
+ name="encoder_hidden",
115
+ )(x)
116
+ x = tf.keras.layers.BatchNormalization(name="vae_encoder_bn_2")(x)
117
+ x = tf.keras.layers.Activation(self.activation, name="vae_encoder_act_2")(x)
118
+
119
+ mean = tf.keras.layers.Dense(self.vae_units, name="mean")(x)
120
+ log_var = tf.keras.layers.Dense(self.vae_units, name="log_var")(x)
121
+ log_var = tf.keras.layers.Lambda(stabilize_log_var, name="log_var_stabilized")(log_var)
122
+
123
+ self._encoder = tf.keras.Model(inputs, [mean, log_var], name="vae_encoder")
124
+ else:
125
+ inputs = tf.keras.Input(shape=(self.input_shape_parm,), name="encoder_input")
126
+ x = tf.keras.layers.Dense(
127
+ units=self.units,
128
+ activation=self.activation,
129
+ kernel_regularizer=l2(self.l2_reg),
130
+ name="encoder_dense_1",
131
+ )(inputs)
132
+ outputs = tf.keras.layers.Dense(
133
+ units=int(self.units / 2),
134
+ activation=self.activation,
135
+ kernel_regularizer=l2(self.l2_reg),
136
+ name="encoder_dense_2",
137
+ )(x)
138
+
139
+ self._encoder = tf.keras.Model(inputs, outputs, name="encoder")
140
+
141
+ def _build_decoder(self):
142
+ """Build the decoder model."""
143
+ if self.vae_mode:
144
+ inputs = tf.keras.Input(shape=(self.vae_units,), name="decoder_input")
145
+ x = tf.keras.layers.Dense(
146
+ units=self.units, kernel_regularizer=l2(self.l2_reg), name="vae_decoder_dense_1"
147
+ )(inputs)
148
+ x = tf.keras.layers.BatchNormalization(name="vae_decoder_bn_1")(x)
149
+ x = tf.keras.layers.Activation(self.activation, name="vae_decoder_act_1")(x)
150
+ x = tf.keras.layers.Dense(
151
+ units=self.input_shape_parm,
152
+ kernel_regularizer=l2(self.l2_reg),
153
+ name="vae_decoder_dense_2",
154
+ )(x)
155
+ x = tf.keras.layers.BatchNormalization(name="vae_decoder_bn_2")(x)
156
+ outputs = tf.keras.layers.Activation(self.activation, name="vae_decoder_act_2")(x)
157
+ else:
158
+ inputs = tf.keras.Input(shape=(int(self.units / 2),), name="decoder_input")
159
+ x = tf.keras.layers.Dense(
160
+ units=self.units,
161
+ activation=self.activation,
162
+ kernel_regularizer=l2(self.l2_reg),
163
+ name="decoder_dense_1",
164
+ )(inputs)
165
+ outputs = tf.keras.layers.Dense(
166
+ units=self.input_shape_parm,
167
+ activation=self.activation,
168
+ kernel_regularizer=l2(self.l2_reg),
169
+ name="decoder_dense_2",
170
+ )(x)
171
+
172
+ self._decoder = tf.keras.Model(inputs, outputs, name="decoder")
173
+
174
+ def _build_classifier(self):
175
+ """Build the classifier model."""
176
+ # Input shape is decoded + encoded features
177
+ if self.vae_mode:
178
+ input_dim = self.input_shape_parm + self.vae_units
179
+ else:
180
+ input_dim = self.input_shape_parm + int(self.units / 2)
181
+
182
+ inputs = tf.keras.Input(shape=(input_dim,), name="classifier_input")
183
+ x = inputs
184
+
185
+ # Build hidden layers
186
+ if self.num_layers > 1 and not self.lora_mode:
187
+ for i in range(self.num_layers - 1):
188
+ x = tf.keras.layers.Dense(
189
+ units=self.units,
190
+ activation=self.activation,
191
+ kernel_regularizer=l2(self.l2_reg),
192
+ name=f"classifier_dense_{i+1}",
193
+ )(x)
194
+ if self.dropout:
195
+ x = tf.keras.layers.Dropout(self.dropout, name=f"classifier_dropout_{i+1}")(x)
196
+
197
+ elif self.lora_mode and self.num_layers > 1:
198
+ for i in range(self.num_layers - 1):
199
+ x = LoRALayer(units=self.units, rank=self.lora_rank, name=f"LoRA_{i}")(x)
200
+ x = tf.keras.layers.Activation(self.activation, name=f"lora_activation_{i+1}")(x)
201
+ if self.dropout:
202
+ x = tf.keras.layers.Dropout(self.dropout, name=f"lora_dropout_{i+1}")(x)
203
+
204
+ # Output layer
205
+ outputs = tf.keras.layers.Dense(
206
+ units=self.num_classes,
207
+ activation=self.classifier_activation,
208
+ kernel_regularizer=l2(self.l2_reg),
209
+ name="classifier_output",
210
+ )(x)
211
+
212
+ self._classifier = tf.keras.Model(inputs, outputs, name="classifier")
213
+
214
+ def _build_main_model(self):
215
+ """Build the main model that combines encoder, decoder, and classifier."""
216
+ inputs = tf.keras.Input(shape=(self.input_shape_parm,), name="main_input")
217
+
218
+ # Encoder forward pass
219
+ if self.vae_mode:
220
+ mean, log_var = self._encoder(inputs)
221
+ # Sampling layer
222
+ encoded = tf.keras.layers.Lambda(
223
+ sampling_wrapper, output_shape=sampling_output_shape, name="sampling_layer"
224
+ )([mean, log_var])
225
+ else:
226
+ encoded = self._encoder(inputs)
227
+
228
+ # Decoder forward pass
229
+ decoded = self._decoder(encoded)
230
+
231
+ # Combine decoded and encoded features
232
+ combined = tf.keras.layers.Concatenate(name="combine_features")([decoded, encoded])
233
+
234
+ # Classifier forward pass
235
+ outputs = self._classifier(combined)
236
+
237
+ self._main_model = tf.keras.Model(
238
+ inputs=inputs, outputs=outputs, name="auto_classifier_main"
239
+ )
240
+
241
+ def _build_models(self):
242
+ """Build all component models."""
243
+ self._build_encoder()
244
+ self._build_decoder()
245
+ self._build_classifier()
246
+ self._build_main_model()
247
+
248
+ @property
249
+ def encoder(self):
250
+ """Get the encoder model."""
251
+ return self._encoder
252
+
253
+ @encoder.setter
254
+ def encoder(self, value):
255
+ """Set the encoder model and rebuild main model."""
256
+ self._encoder = value
257
+ if self._decoder and self._classifier:
258
+ self._build_main_model()
259
+
260
+ @property
261
+ def decoder(self):
262
+ """Get the decoder model."""
263
+ return self._decoder
264
+
265
+ @decoder.setter
266
+ def decoder(self, value):
267
+ """Set the decoder model and rebuild main model."""
268
+ self._decoder = value
269
+ if self._encoder and self._classifier:
270
+ self._build_main_model()
271
+
272
+ @property
273
+ def classifier(self):
274
+ """Get the classifier model."""
275
+ return self._classifier
276
+
277
+ @classifier.setter
278
+ def classifier(self, value):
279
+ """Set the classifier model and rebuild main model."""
280
+ self._classifier = value
281
+ if self._encoder and self._decoder:
282
+ self._build_main_model()
283
+
284
+ def train_encoder_decoder(
285
+ self, data, epochs, batch_size, validation_split=0.2, patience=10, **kwargs
286
+ ):
287
+ """
288
+ Trains the encoder and decoder on the input data.
289
+
290
+ Parameters
291
+ ----------
292
+ data : tf.data.Dataset, np.ndarray
293
+ The input data.
294
+ epochs : int
295
+ The number of epochs to train for.
296
+ batch_size : int
297
+ The batch size to use.
298
+ validation_split : float
299
+ The proportion of the dataset to use for validation. Default is 0.2.
300
+ patience : int
301
+ The number of epochs to wait before early stopping. Default is 10.
302
+ """
303
+ verbose = kwargs.get("verbose", True)
304
+ optimizer = kwargs.get("optimizer", tf.keras.optimizers.Adam())
305
+
306
+ # Prepare data
307
+ if isinstance(data, np.ndarray):
308
+ data = tf.data.Dataset.from_tensor_slices(data).batch(batch_size)
309
+ data = data.map(lambda x: tf.cast(x, tf.float32))
310
+
311
+ early_stopping = EarlyStopping(patience=patience)
312
+ train_batches = data.take(int((1 - validation_split) * len(data)))
313
+ val_batches = data.skip(int((1 - validation_split) * len(data)))
314
+
315
+ for epoch in range(epochs):
316
+ train_loss = 0
317
+ val_loss = 0
318
+
319
+ # Training step
320
+ for train_batch in train_batches:
321
+ loss_train = train_step(
322
+ train_batch, self._encoder, self._decoder, optimizer, self.vae_mode
323
+ )
324
+ train_loss = loss_train # Keep last batch loss
325
+
326
+ # Validation step
327
+ for val_batch in val_batches:
328
+ loss_val = cal_loss_step(
329
+ val_batch, self._encoder, self._decoder, self.vae_mode, False
330
+ )
331
+ val_loss = loss_val # Keep last batch loss
332
+
333
+ early_stopping(train_loss)
334
+
335
+ if early_stopping.stop_training:
336
+ if verbose:
337
+ print(f"Early stopping triggered at epoch {epoch}.")
338
+ break
339
+
340
+ if epoch % 10 == 0 and verbose:
341
+ print(
342
+ f"Epoch {epoch}: Train Loss: {train_loss:.6f} Validation Loss: {val_loss:.6f}"
343
+ )
344
+
345
+ self.freeze_encoder_decoder()
346
+
347
+ def freeze_encoder_decoder(self):
348
+ """Freezes the encoder and decoder layers to prevent them from being updated during training."""
349
+ if self._encoder:
350
+ for layer in self._encoder.layers:
351
+ layer.trainable = False
352
+ if self._decoder:
353
+ for layer in self._decoder.layers:
354
+ layer.trainable = False
355
+
356
+ # Rebuild main model to reflect trainability changes
357
+ self._build_main_model()
358
+
359
+ def unfreeze_encoder_decoder(self):
360
+ """Unfreezes the encoder and decoder layers allowing them to be updated during training."""
361
+ if self._encoder:
362
+ for layer in self._encoder.layers:
363
+ layer.trainable = True
364
+ if self._decoder:
365
+ for layer in self._decoder.layers:
366
+ layer.trainable = True
367
+
368
+ # Rebuild main model to reflect trainability changes
369
+ self._build_main_model()
370
+
371
+ def set_encoder_decoder(self, source_model):
372
+ """
373
+ Sets the encoder and decoder layers from another AutoClassifier instance,
374
+ ensuring compatibility in dimensions.
375
+
376
+ Parameters
377
+ ----------
378
+ source_model : AutoClassifier
379
+ The source model to copy the encoder and decoder layers from.
380
+
381
+ Raises
382
+ ------
383
+ ValueError
384
+ If the input shape or units of the source model do not match.
385
+ """
386
+ if not isinstance(source_model, AutoClassifier):
387
+ raise ValueError("Source model must be an instance of AutoClassifier.")
388
+
389
+ if self.input_shape_parm != source_model.input_shape_parm:
390
+ raise ValueError(
391
+ f"Incompatible input shape. Expected {self.input_shape_parm}, got {source_model.input_shape_parm}."
392
+ )
393
+ if self.units != source_model.units:
394
+ raise ValueError(
395
+ f"Incompatible number of units. Expected {self.units}, got {source_model.units}."
396
+ )
397
+
398
+ # Clone and copy weights
399
+ if source_model._encoder:
400
+ self._encoder = tf.keras.models.clone_model(source_model._encoder)
401
+ self._encoder.set_weights(source_model._encoder.get_weights())
402
+
403
+ if source_model._decoder:
404
+ self._decoder = tf.keras.models.clone_model(source_model._decoder)
405
+ self._decoder.set_weights(source_model._decoder.get_weights())
406
+
407
+ # Rebuild main model with new encoder/decoder
408
+ self._build_main_model()
409
+
410
+ # Main model interface methods
411
+ def __call__(self, x, training=None):
412
+ """Forward pass through the model."""
413
+ return self._main_model(x, training=training)
414
+
415
+ def compile(self, *args, **kwargs):
416
+ """Compile the main model."""
417
+ return self._main_model.compile(*args, **kwargs)
418
+
419
+ def fit(self, *args, **kwargs):
420
+ """Fit the main model."""
421
+ return self._main_model.fit(*args, **kwargs)
422
+
423
+ def evaluate(self, *args, **kwargs):
424
+ """Evaluate the main model."""
425
+ return self._main_model.evaluate(*args, **kwargs)
426
+
427
+ def predict(self, *args, **kwargs):
428
+ """Predict using the main model."""
429
+ return self._main_model.predict(*args, **kwargs)
430
+
431
+ def save(self, filepath, **kwargs):
432
+ """
433
+ Save the complete model including all components.
434
+
435
+ Parameters
436
+ ----------
437
+ filepath : str
438
+ Path where to save the model.
439
+ """
440
+ import os
441
+
442
+ # Create directory if it doesn't exist
443
+ os.makedirs(filepath, exist_ok=True)
444
+
445
+ # Save all component models
446
+ self._encoder.save(os.path.join(filepath, "encoder.keras"))
447
+ self._decoder.save(os.path.join(filepath, "decoder.keras"))
448
+ self._classifier.save(os.path.join(filepath, "classifier.keras"))
449
+ self._main_model.save(os.path.join(filepath, "main_model.keras"))
450
+
451
+ # Save configuration
452
+ import json
453
+
454
+ config = {
455
+ "input_shape_parm": self.input_shape_parm,
456
+ "num_classes": self.num_classes,
457
+ "units": self.units,
458
+ "activation": self.activation,
459
+ "classifier_activation": self.classifier_activation,
460
+ "num_layers": self.num_layers,
461
+ "dropout": self.dropout,
462
+ "l2_reg": self.l2_reg,
463
+ "vae_mode": self.vae_mode,
464
+ "vae_units": self.vae_units,
465
+ "lora_mode": self.lora_mode,
466
+ "lora_rank": self.lora_rank,
467
+ }
468
+
469
+ with open(os.path.join(filepath, "config.json"), "w") as f:
470
+ json.dump(config, f, indent=2)
471
+
472
+ @classmethod
473
+ def load(cls, filepath):
474
+ """
475
+ Load a complete model from saved components.
476
+
477
+ Parameters
478
+ ----------
479
+ filepath : str
480
+ Path where the model was saved.
481
+
482
+ Returns
483
+ -------
484
+ AutoClassifier
485
+ The loaded model instance.
486
+ """
487
+ import json
488
+ import os
489
+
490
+ # Load configuration
491
+ with open(os.path.join(filepath, "config.json"), "r") as f:
492
+ config = json.load(f)
493
+
494
+ # Create new instance
495
+ instance = cls(**config)
496
+
497
+ # Load component models
498
+ instance._encoder = tf.keras.models.load_model(os.path.join(filepath, "encoder.keras"))
499
+ instance._decoder = tf.keras.models.load_model(os.path.join(filepath, "decoder.keras"))
500
+ instance._classifier = tf.keras.models.load_model(
501
+ os.path.join(filepath, "classifier.keras")
502
+ )
503
+ instance._main_model = tf.keras.models.load_model(
504
+ os.path.join(filepath, "main_model.keras")
505
+ )
506
+
507
+ return instance
508
+
509
+ # Additional properties and methods for compatibility
510
+ @property
511
+ def weights(self):
512
+ """Get all model weights."""
513
+ return self._main_model.weights
514
+
515
+ def get_weights(self):
516
+ """Get all model weights."""
517
+ return self._main_model.get_weights()
518
+
519
+ def set_weights(self, weights):
520
+ """Set all model weights."""
521
+ return self._main_model.set_weights(weights)
522
+
523
+ @property
524
+ def trainable_variables(self):
525
+ """Get trainable variables."""
526
+ return self._main_model.trainable_variables
527
+
528
+ @property
529
+ def non_trainable_variables(self):
530
+ """Get non-trainable variables."""
531
+ return self._main_model.non_trainable_variables
532
+
533
+ def summary(self, *args, **kwargs):
534
+ """Print model summary."""
535
+ print("=== AutoClassifier Summary ===")
536
+ print("\n--- Encoder ---")
537
+ self._encoder.summary(*args, **kwargs)
538
+ print("\n--- Decoder ---")
539
+ self._decoder.summary(*args, **kwargs)
540
+ print("\n--- Classifier ---")
541
+ self._classifier.summary(*args, **kwargs)
542
+ print("\n--- Main Model ---")
543
+ self._main_model.summary(*args, **kwargs)
544
+
545
+ def get_config(self):
546
+ """Get model configuration."""
547
+ return {
548
+ "input_shape_parm": self.input_shape_parm,
549
+ "num_classes": self.num_classes,
550
+ "units": self.units,
551
+ "activation": self.activation,
552
+ "classifier_activation": self.classifier_activation,
553
+ "num_layers": self.num_layers,
554
+ "dropout": self.dropout,
555
+ "l2_reg": self.l2_reg,
556
+ "vae_mode": self.vae_mode,
557
+ "vae_units": self.vae_units,
558
+ "lora_mode": self.lora_mode,
559
+ "lora_rank": self.lora_rank,
560
+ }
561
+
562
+
563
+ def call_existing_code(
564
+ units: int,
565
+ activation: str,
566
+ threshold: float,
567
+ optimizer: str,
568
+ input_shape_parm: None | int = None,
569
+ num_classes: None | int = None,
570
+ num_layers: int = 1,
571
+ **kwargs,
572
+ ) -> AutoClassifier:
573
+ """
574
+ Calls an existing AutoClassifier instance.
575
+
576
+ Parameters
577
+ ----------
578
+ units : `int`
579
+ The number of neurons in each hidden layer.
580
+ activation : `str`
581
+ The type of activation function to use for the neural network layers.
582
+ threshold : `float`
583
+ The threshold for the classifier.
584
+ optimizer : `str`
585
+ The type of optimizer to use for the neural network layers.
586
+ input_shape_parm : `None` | `int`
587
+ The shape of the input data.
588
+ num_classes : `int`
589
+ The number of classes in the dataset.
590
+ num_layers : `int`
591
+ The number of hidden layers in the classifier. Default is 1.
592
+
593
+ Keyword Arguments
594
+ -----------------
595
+ vae_mode : `bool`
596
+ Whether to use variational autoencoder mode. Default is False.
597
+ vae_units : `int`
598
+ The number of units in the variational autoencoder. Default is 2.
599
+
600
+ Returns
601
+ -------
602
+ `AutoClassifier`
603
+ The AutoClassifier instance.
604
+ """
605
+ dropout = kwargs.get("dropout", None)
606
+ l2_reg = kwargs.get("l2_reg", 0.0)
607
+ vae_mode = kwargs.get("vae_mode", False)
608
+ vae_units = kwargs.get("vae_units", 2)
609
+ model = AutoClassifier(
610
+ input_shape_parm=input_shape_parm,
611
+ num_classes=num_classes,
612
+ units=units,
613
+ activation=activation,
614
+ num_layers=num_layers,
615
+ dropout=dropout,
616
+ l2_reg=l2_reg,
617
+ vae_mode=vae_mode,
618
+ vae_units=vae_units,
619
+ )
620
+ model.compile(
621
+ optimizer=optimizer,
622
+ loss=tf.keras.losses.CategoricalCrossentropy(),
623
+ metrics=[tf.keras.metrics.F1Score(threshold=threshold)],
624
+ )
625
+ return model._main_model
626
+
627
+
628
+ def build_model(
629
+ hp, input_shape_parm: None | int, num_classes: None | int, **kwargs
630
+ ) -> AutoClassifier:
631
+ """Builds a neural network model using Keras Tuner's search algorithm.
632
+
633
+ Parameters
634
+ ----------
635
+ hp : `keras_tuner.HyperParameters`
636
+ The hyperparameters to tune.
637
+ input_shape_parm : `None` | `int`
638
+ The shape of the input data.
639
+ num_classes : `int`
640
+ The number of classes in the dataset.
641
+
642
+ Keyword Arguments
643
+ -----------------
644
+ Additional keyword arguments to pass to the model.
645
+
646
+ hyperparameters : `dict`
647
+ The hyperparameters to set.
648
+
649
+ Returns
650
+ -------
651
+ `keras.Model`
652
+ The neural network model.
653
+ """
654
+ hyperparameters = kwargs.get("hyperparameters", None)
655
+ hyperparameters_keys = hyperparameters.keys() if hyperparameters is not None else []
656
+
657
+ units = (
658
+ hp.Int(
659
+ "units",
660
+ min_value=int(input_shape_parm * 0.2),
661
+ max_value=int(input_shape_parm * 1.5),
662
+ step=2,
663
+ )
664
+ if "units" not in hyperparameters_keys
665
+ else (
666
+ hp.Choice("units", hyperparameters["units"])
667
+ if isinstance(hyperparameters["units"], list)
668
+ else hyperparameters["units"]
669
+ )
670
+ )
671
+ activation = (
672
+ hp.Choice("activation", ["sigmoid", "relu", "tanh", "selu", "softplus", "softsign"])
673
+ if "activation" not in hyperparameters_keys
674
+ else (
675
+ hp.Choice("activation", hyperparameters["activation"])
676
+ if isinstance(hyperparameters["activation"], list)
677
+ else hyperparameters["activation"]
678
+ )
679
+ )
680
+ optimizer = (
681
+ hp.Choice("optimizer", ["sgd", "adam", "adadelta", "rmsprop", "adamax", "adagrad"])
682
+ if "optimizer" not in hyperparameters_keys
683
+ else (
684
+ hp.Choice("optimizer", hyperparameters["optimizer"])
685
+ if isinstance(hyperparameters["optimizer"], list)
686
+ else hyperparameters["optimizer"]
687
+ )
688
+ )
689
+ threshold = (
690
+ hp.Float("threshold", min_value=0.1, max_value=0.9, sampling="log")
691
+ if "threshold" not in hyperparameters_keys
692
+ else (
693
+ hp.Choice("threshold", hyperparameters["threshold"])
694
+ if isinstance(hyperparameters["threshold"], list)
695
+ else hyperparameters["threshold"]
696
+ )
697
+ )
698
+ num_layers = (
699
+ hp.Int("num_layers", min_value=1, max_value=10, step=1)
700
+ if "num_layers" not in hyperparameters_keys
701
+ else (
702
+ hp.Choice("num_layers", hyperparameters["num_layers"])
703
+ if isinstance(hyperparameters["num_layers"], list)
704
+ else hyperparameters["num_layers"]
705
+ )
706
+ )
707
+ dropout = (
708
+ hp.Float("dropout", min_value=0.1, max_value=0.9, sampling="log")
709
+ if "dropout" not in hyperparameters_keys
710
+ else (
711
+ hp.Choice("dropout", hyperparameters["dropout"])
712
+ if isinstance(hyperparameters["dropout"], list)
713
+ else hyperparameters["dropout"]
714
+ )
715
+ )
716
+ l2_reg = (
717
+ hp.Float("l2_reg", min_value=1e-6, max_value=0.1, sampling="log")
718
+ if "l2_reg" not in hyperparameters_keys
719
+ else (
720
+ hp.Choice("l2_reg", hyperparameters["l2_reg"])
721
+ if isinstance(hyperparameters["l2_reg"], list)
722
+ else hyperparameters["l2_reg"]
723
+ )
724
+ )
725
+ vae_mode = (
726
+ hp.Choice("vae_mode", [True, False])
727
+ if "vae_mode" not in hyperparameters_keys
728
+ else hyperparameters["vae_mode"]
729
+ )
730
+
731
+ try:
732
+ vae_units = (
733
+ hp.Int("vae_units", min_value=2, max_value=10, step=1)
734
+ if ("vae_units" not in hyperparameters_keys) and vae_mode
735
+ else (
736
+ hp.Choice("vae_units", hyperparameters["vae_units"])
737
+ if isinstance(hyperparameters["vae_units"], list)
738
+ else hyperparameters["vae_units"]
739
+ )
740
+ )
741
+ except KeyError:
742
+ vae_units = None
743
+
744
+ model = call_existing_code(
745
+ units=units,
746
+ activation=activation,
747
+ threshold=threshold,
748
+ optimizer=optimizer,
749
+ input_shape_parm=input_shape_parm,
750
+ num_classes=num_classes,
751
+ num_layers=num_layers,
752
+ dropout=dropout,
753
+ l2_reg=l2_reg,
754
+ vae_mode=vae_mode,
755
+ vae_units=vae_units,
756
+ )
757
+ return model
758
+
759
+
760
+ @suppress_warnings
761
+ def setup_model(
762
+ data: pd.DataFrame,
763
+ target: str,
764
+ epochs: int,
765
+ train_size: float = 0.7,
766
+ seed=None,
767
+ train_mode: bool = True,
768
+ filepath: str = "./my_dir/best_model",
769
+ method: str = "Hyperband",
770
+ **kwargs,
771
+ ) -> AutoClassifier:
772
+ """Setup model for training and tuning.
773
+
774
+ Parameters
775
+ ----------
776
+ data : `pd.DataFrame`
777
+ The dataset to train the model on.
778
+ target : `str`
779
+ The name of the target column.
780
+ epochs : `int`
781
+ The number of epochs to train the model for.
782
+ train_size : `float`
783
+ The proportion of the dataset to use for training.
784
+ seed : `None` | `int`
785
+ The random seed to use for reproducibility.
786
+ train_mode : `bool`
787
+ Whether to train the model or not.
788
+ filepath : `str`
789
+ The path to save the best model to.
790
+ method : `str`
791
+ The method to use for hyperparameter tuning. Options are "Hyperband" and "RandomSearch".
792
+
793
+ Keyword Arguments
794
+ -----------------
795
+ Additional keyword arguments to pass to the model.
796
+
797
+ max_trials : `int`
798
+ The maximum number of trials to perform.
799
+ directory : `str`
800
+ The directory to save the model to.
801
+ project_name : `str`
802
+ The name of the project.
803
+ objective : `str`
804
+ The objective to optimize.
805
+ verbose : `bool`
806
+ Whether to print verbose output.
807
+ hyperparameters : `dict`
808
+ The hyperparameters to set.
809
+
810
+ Returns
811
+ -------
812
+ model : `AutoClassifier`
813
+ The trained model.
814
+ """
815
+ max_trials = kwargs.get("max_trials", 10)
816
+ directory = kwargs.get("directory", "./my_dir")
817
+ project_name = kwargs.get("project_name", "get_best")
818
+ objective = kwargs.get("objective", "val_loss")
819
+ verbose = kwargs.get("verbose", True)
820
+ hyperparameters = kwargs.get("hyperparameters", None)
821
+
822
+ X = data.drop(columns=target)
823
+ input_sample = X.sample(1)
824
+ y = data[target]
825
+ assert (
826
+ X.select_dtypes(include=["object"]).empty == True
827
+ ), "Categorical variables within the DataFrame must be encoded, this is done by using the DataFrameEncoder from likelihood."
828
+ validation_split = 1.0 - train_size
829
+
830
+ if train_mode:
831
+ try:
832
+ if (not os.path.exists(directory)) and directory != "./":
833
+ os.makedirs(directory)
834
+ elif directory != "./":
835
+ print(f"Directory {directory} already exists, it will be deleted.")
836
+ rmtree(directory)
837
+ os.makedirs(directory)
838
+ except:
839
+ print("Warning: unable to create directory")
840
+
841
+ y_encoder = OneHotEncoder()
842
+ y = y_encoder.encode(y.to_list())
843
+ X = X.to_numpy()
844
+ input_sample.to_numpy()
845
+ X = np.asarray(X).astype(np.float32)
846
+ input_sample = np.asarray(input_sample).astype(np.float32)
847
+ y = np.asarray(y).astype(np.float32)
848
+
849
+ input_shape_parm = X.shape[1]
850
+ num_classes = y.shape[1]
851
+ global build_model
852
+ build_model = partial(
853
+ build_model,
854
+ input_shape_parm=input_shape_parm,
855
+ num_classes=num_classes,
856
+ hyperparameters=hyperparameters,
857
+ )
858
+
859
+ if method == "Hyperband":
860
+ tuner = keras_tuner.Hyperband(
861
+ hypermodel=build_model,
862
+ objective=objective,
863
+ max_epochs=epochs,
864
+ factor=3,
865
+ directory=directory,
866
+ project_name=project_name,
867
+ seed=seed,
868
+ )
869
+ elif method == "RandomSearch":
870
+ tuner = keras_tuner.RandomSearch(
871
+ hypermodel=build_model,
872
+ objective=objective,
873
+ max_trials=max_trials,
874
+ directory=directory,
875
+ project_name=project_name,
876
+ seed=seed,
877
+ )
878
+
879
+ tuner.search(X, y, epochs=epochs, validation_split=validation_split, verbose=verbose)
880
+ models = tuner.get_best_models(num_models=2)
881
+ best_model = models[0]
882
+ best_model(input_sample)
883
+
884
+ best_model.save(filepath if filepath.endswith(".keras") else filepath + ".keras")
885
+
886
+ if verbose:
887
+ tuner.results_summary()
888
+ else:
889
+ best_model = tf.keras.models.load_model(
890
+ filepath if filepath.endswith(".keras") else filepath + ".keras"
891
+ )
892
+ best_hps = tuner.get_best_hyperparameters(1)[0].values
893
+ vae_mode = best_hps.get("vae_mode", hyperparameters.get("vae_mode", False))
894
+ best_hps["vae_units"] = None if not vae_mode else best_hps["vae_units"]
895
+
896
+ return best_model, pd.DataFrame(best_hps, index=["Value"]).dropna(axis=1)