Does this use gpu to train?
import tensorflow as tf
import keras_tuner as kt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.regularizers import l2
from tensorflow.keras.callbacks import EarlyStopping
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt

# Set seeds for reproducibility
np.random.seed(42)
tf.random.set_seed(42)

# Read data
data = pd.read_csv('dataset_phishing.csv')
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']

# Check for any missing values and handle them if necessary
X.fillna(0, inplace=True)

# Feature Scaling
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Reshape data for Conv1D (samples, timesteps, features)
# Since Conv1D expects a 3D input, we'll treat each feature as a timestep with 1 feature
# Alternatively, you can treat it as a single timestep with multiple features
# Here, we'll use the first approach
X_reshaped = np.expand_dims(X_scaled, axis=2)  # Shape: (samples, features, 1)

# Encode labels to binary values (0/1)
label_encoder = LabelEncoder()
y_encoded = label_encoder.fit_transform(y)

# New Data Splitting Strategy
# Step 1: Split data into 70% train and 30% temp (validation + test)
X_train, X_temp, y_train, y_temp = train_test_split(
    X_reshaped, y_encoded, test_size=0.3, random_state=42, stratify=y_encoded
)

# Step 2: Split the temp set into 50% validation and 50% test (15% each of the original data)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)

print(f"Training set shape: {X_train.shape}, {y_train.shape}")
print(f"Validation set shape: {X_val.shape}, {y_val.shape}")
print(f"Test set shape: {X_test.shape}, {y_test.shape}")

def model_builder(hp):
    model = Sequential()
    
    # First Conv1D Layer
    model.add(Conv1D(
        filters=hp.Int('filters', min_value=32, max_value=128, step=32),
        kernel_size=hp.Choice('kernel_size', values=[3, 5]),
        activation='relu',
        input_shape=(X_reshaped.shape[1], 1),  # Adjusted for Conv1D input
        kernel_regularizer=l2(0.0001)
    ))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Dropout(hp.Float('dropout_rate', min_value=0.1, max_value=0.5, step=0.1)))

    # Second Conv1D Layer
    model.add(Conv1D(
        filters=hp.Int('filters_2', min_value=64, max_value=256, step=64),
        kernel_size=hp.Choice('kernel_size_2', values=[3, 5]),
        activation='relu',
        kernel_regularizer=l2(0.0001)
    ))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Dropout(hp.Float('dropout_rate_2', min_value=0.1, max_value=0.5, step=0.1)))

    model.add(Flatten())
    
    # Dense Layer
    model.add(Dense(
        units=hp.Int('units', min_value=64, max_value=256, step=64),
        activation='relu'
    ))
    model.add(Dropout(hp.Float('dropout_rate_3', min_value=0.1, max_value=0.5, step=0.1)))
    
    # Output Layer
    model.add(Dense(1, activation='sigmoid'))

    # Compile the model
    model.compile(
        optimizer=Adam(hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4])),
        loss='binary_crossentropy',
        metrics=['accuracy']
    )
    return model

# Initialize Keras Tuner with Hyperband
tuner = kt.Hyperband(
    model_builder,
    objective='val_accuracy',
    max_epochs=20,
    factor=3,
    directory='my_dir',
    project_name='phishing_tuning'
)

# Early Stopping Callback
stop_early = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)

# Perform Hyperparameter Search using the training and validation sets
tuner.search(
    X_train, y_train,
    epochs=50,
    validation_data=(X_val, y_val),
    callbacks=[stop_early]
)

# Retrieve the best hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]

print(f"""
The hyperparameter search is complete.
Optimal number of filters in the first Conv1D layer: {best_hps.get('filters')}.
Optimal learning rate for the optimizer: {best_hps.get('learning_rate')}.
""")

# Build the model with the optimal hyperparameters and train it on the combined training and validation sets
# This ensures the model benefits from all available training data
model = tuner.hypermodel.build(best_hps)

# Combine training and validation data for final training
X_combined = np.concatenate((X_train, X_val), axis=0)
y_combined = np.concatenate((y_train, y_val), axis=0)

# Train the model
history = model.fit(
    X_combined, y_combined,
    epochs=20,
    validation_split=0.0,  # No validation split since we have a separate test set
    callbacks=[stop_early]
)

# Evaluate the model on the test set
loss, accuracy = model.evaluate(X_test, y_test)
print(f"Test Accuracy: {accuracy:.4f}")

# Predict the labels for X_test
y_pred = model.predict(X_test)

# Convert probabilities to binary output (0 or 1)
y_pred_binary = (y_pred > 0.5).astype(int)

# Get classification report with more decimal places
print(classification_report(y_test, y_pred_binary, digits=4))

# Plot learning curve
plt.figure(figsize=(12, 6))
plt.plot(history.history['accuracy'], label='Train Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')  # If validation_accuracy exists
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Learning Curve')
plt.legend()
plt.show()