Help me adjust my CNN model, without changing libraries used, to ensure that it uses recall to optimize the parameters instead. MAke the results reproducible on another machine, given that the hyperparameters might not be the same due to random search.

My current implementation:
import os
import random
import numpy as np
import tensorflow as tf

# Define a seed value for reproducibility
seed = 42

# 1. Set the PYTHONHASHSEED environment variable for Python hash-based operations
os.environ['PYTHONHASHSEED'] = str(seed)

# 2. Set the Python built-in pseudo-random generator at a fixed value
random.seed(seed)

# 3. Set the NumPy pseudo-random generator at a fixed value
np.random.seed(seed)

# 4. Set the TensorFlow pseudo-random generator at a fixed value
tf.random.set_seed(seed)

# Import Required Packages


import pandas as pd

# For data preprocessing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# For performance metrics
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                             f1_score, roc_auc_score, classification_report, 
                             confusion_matrix)

# For building the neural network
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (Conv1D, MaxPooling1D, Flatten, Dense, Dropout, 
                                     BatchNormalization)
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping

# For hyperparameter tuning
import keras_tuner as kt
from keras_tuner.tuners import RandomSearch

# =============================================
# Version Information (Optional)
# =============================================

print(f'TensorFlow version: {tf.__version__}')
print(f'Keras Tuner version: {kt.__version__}')


# Data Loading and Preprocessing


# Read the dataset
data = pd.read_csv('dataset_phishing.csv')

# Define features and target variable
features = ['shortest_word_path', 'ratio_intMedia', 'links_in_tags',
            'nb_hyphens', 'page_rank', 'avg_word_path', 
            'ratio_extHyperlinks', 'longest_words_raw', 'google_index',
            'length_hostname', 'longest_word_host', 
            'domain_registration_length', 'nb_www', 'nb_underscore', 
            'nb_dots', 'ratio_extMedia', 'phish_hints', 'domain_in_title', 
            'web_traffic', 'safe_anchor', 'nb_space', 'shortening_service', 
            'ip', 'domain_age', 'nb_qm', 'nb_hyperlinks', 'nb_slash']

X = data[features]
y = data['status']  # 'status' is 'legitimate' or 'phishing'

# Map the 'status' to numerical labels
label_mapping = {'legitimate': 0, 'phishing': 1}
y = y.map(label_mapping)

# Check for any unmapped values or missing values
if y.isnull().any():
    print("Some 'status' values were not mapped properly. Please check the 'status' column for unexpected values.")
    print(y[y.isnull()])


# Handle missing values in features if any
X.fillna(0, inplace=True)

# Split into training and testing sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.20, random_state=seed, stratify=y)

# Standardize the features
scaler = StandardScaler()

X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Reshape data to be suitable for Conv1D (samples, timesteps, features)
# Since we have numerical features, we treat each feature as a "time step"
X_train_cnn = X_train_scaled.reshape(-1, X_train_scaled.shape[1], 1)
X_test_cnn = X_test_scaled.reshape(-1, X_test_scaled.shape[1], 1)


# Model Building and Hyperparameter Tuning


def build_model(hp):
    model = Sequential()
    
    # First Conv1D layer
    model.add(Conv1D(
        filters=hp.Int('filters_1', min_value=16, max_value=128, step=16),
        kernel_size=hp.Int('kernel_size_1', min_value=2, max_value=5, step=1),
        activation='relu',
        input_shape=(X_train_cnn.shape[1], 1),
        padding='same'
    ))
    model.add(BatchNormalization())
    model.add(MaxPooling1D(pool_size=2))
    model.add(Dropout(hp.Float('dropout_rate_1', min_value=0.0, max_value=0.5, step=0.1)))
    
    # Optional second Conv1D layer
    if hp.Boolean('second_conv_layer'):
        model.add(Conv1D(
            filters=hp.Int('filters_2', min_value=16, max_value=128, step=16),
            kernel_size=hp.Int('kernel_size_2', min_value=2, max_value=5, step=1),
            activation='relu',
            padding='same'
        ))
        model.add(BatchNormalization())
        model.add(MaxPooling1D(pool_size=2))
        model.add(Dropout(hp.Float('dropout_rate_2', min_value=0.0, max_value=0.5, step=0.1)))
    
    model.add(Flatten())
    
    # Dense layers
    for i in range(hp.Int('num_dense_layers', 1, 3)):
        model.add(Dense(
            units=hp.Int(f'units_{i}', min_value=32, max_value=256, step=32),
            activation='relu'
        ))
        model.add(BatchNormalization())
        model.add(Dropout(hp.Float(f'dropout_rate_dense_{i}', min_value=0.0, max_value=0.5, step=0.1)))
    
    # Output layer
    model.add(Dense(1, activation='sigmoid'))
    
    # Compile the model
    model.compile(
        optimizer=Adam(
            hp.Float('lr', min_value=1e-4, max_value=1e-2, sampling='LOG', default=1e-3)
        ),
        loss='binary_crossentropy',
        metrics=['accuracy']
    )
    return model

# Initialize the tuner
tuner = RandomSearch(
    build_model,
    objective='val_accuracy',
    max_trials=20,
    executions_per_trial=1,
    directory='tuning',
    project_name='cnn',
    seed=seed, 
    overwrite=True  
)

# Early stopping callback to prevent overfitting
early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)

# Run the hyperparameter search
tuner.search(
    x=X_train_cnn,
    y=y_train,
    epochs=20,
    validation_split=0.2,
    callbacks=[early_stopping],
    verbose=1
)

# Get the optimal hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
print(f"""
The hyperparameter search is complete. 
The optimal number of filters in the first Conv1D layer is {best_hps.get('filters_1')}, 
kernel size is {best_hps.get('kernel_size_1')}, 
and the learning rate is {best_hps.get('lr')}.
""")

# Build the model with the best hyperparameters
best_model = tuner.hypermodel.build(best_hps)

# Train the best model
history = best_model.fit(
    X_train_cnn, y_train,
    epochs=50,
    validation_split=0.2,
    callbacks=[early_stopping],
    verbose=1 
)


# Model Evaluation

# Evaluate the model on test data
test_loss, test_acc = best_model.evaluate(X_test_cnn, y_test)
print('Test Accuracy: {:.4f}'.format(test_acc))

# Predict probabilities and classes
y_pred_proba = best_model.predict(X_test_cnn).ravel()
y_pred = (y_pred_proba >= 0.5).astype(int)

# Calculate and print metrics
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
roc_auc = roc_auc_score(y_test, y_pred_proba)

print('\nModel Performance Metrics:')
print('--------------------------')
print('Accuracy  : {:.4f}'.format(accuracy))
print('Precision : {:.4f}'.format(precision))
print('Recall    : {:.4f}'.format(recall))
print('F1 Score  : {:.4f}'.format(f1))
print('ROC AUC   : {:.4f}'.format(roc_auc))

print('\nConfusion Matrix:')
print(confusion_matrix(y_test, y_pred))

print('\nClassification Report:')
print(classification_report(y_test, y_pred))