modify this code so that i can load a csv that has two headers: inputs and outputs  so i could train a model with that : import tkinter as tk
from tkinter import filedialog
from keras.models import Sequential, Model
from keras.layers import Embedding, LSTM, Dense, Input, MultiHeadAttention, GlobalAveragePooling1D
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
import numpy as np
import os
import pickle
from keras.layers import Masking
from keras.layers import MultiHeadAttention

# Data loader and preprocessor function
def preprocess_data(file_paths):
    tokenizer = Tokenizer()
    
    # Load and concatenate content from all selected dataset files
    all_text = ""
    for file_path in file_paths:
        with open(file_path, "r", encoding="utf-8") as file:
            text = file.read()
            all_text += text + "\n"  # Ensure separation between files’ content
    
    sentences = all_text.split("\n")
    
    tokenizer.fit_on_texts(sentences)
    sequences = tokenizer.texts_to_sequences(sentences)

    # Check if we’re using a fixed sequence length
    if sequence_choice_var.get():
        # Read the fixed sequence length from the respective entry field
        sequence_length = int(entry_fixed_length.get())
        padded_sequences = pad_sequences(sequences, maxlen=sequence_length, padding="pre")
        big_sequence = [token for seq in padded_sequences for token in seq if token != 0]  # Filter out 0 (padding)
    else:
        # If not using a fixed length, find the minimal sequence length greater than 1
        sequence_length = min(len(seq) for seq in sequences if len(seq) > 1)
        big_sequence = [token for seq in sequences for token in seq]
        # Filter by sequence_length will not be necessary since we take the minimal length anyway
    
    big_sequence = [token for seq in sequences for token in seq]
    input_sequences, output_words = [], []
    
    # Assign a sequence length based on the shortest sentence
    # Note: for better training with variable lengths, consider using sentences directly and/or padding at the end of each batch
    sequence_length = min(len(seq) for seq in sequences if len(seq) > 1)
    
    for i in range(len(big_sequence) - sequence_length):
        input_sequences.append(big_sequence[i:i + sequence_length])
        output_words.append(big_sequence[i + sequence_length])
    
    # Remove pad_sequences call - handle varying sequence lengths directly in the model using masking or by padding at batch end
    
    vocab_size = len(tokenizer.word_index) + 1
    
    output_words = np.array(output_words)
    output_words = to_categorical(output_words, num_classes=vocab_size)
    
    return np.array(input_sequences), output_words, vocab_size, tokenizer, sequence_length

def create_transformer_model(sequence_length, vocab_size, layer_size, num_heads, dropout_rate):
    # Transformer model requires the key, value, and query have the same dimensions (layer_size)
    inputs = Input(shape=(sequence_length,))
    embedding_layer = Embedding(input_dim=vocab_size, output_dim=layer_size)(inputs)
    
    # Call the MultiHeadAttention layer with the necessary inputs
    attn_output = MultiHeadAttention(num_heads=num_heads, key_dim=layer_size, dropout=dropout_rate)(
        query=embedding_layer, key=embedding_layer, value=embedding_layer
    )
    
    # … Add Normalization, Feed-forward layers, etc. …
    
    # GlobalAveragePooling1D could be used to reduce sequence dimension
    pooling_layer = GlobalAveragePooling1D()(attn_output)
    
    outputs = Dense(vocab_size, activation="softmax")(pooling_layer)
    
    # Create the actual model
    model = Model(inputs=inputs, outputs=outputs)
    
    return model


# Function to train and save the model
def train_model():
    # Move the variable assignments outside of the conditionals so they’re always defined.
    num_layers = int(entry_layers.get())
    layer_size = int(entry_size.get())
    model_name = entry_name.get()
    epochs = int(entry_epochs.get())
    data_paths = root.filenames  # Changed to accept multiple filenames

    # Preprocess the data for either model type.
    input_sequences, output_words, vocab_size, tokenizer, sequence_length = preprocess_data(data_paths)
    
    # Check model_type and build the appropriate model.
    model_type = model_type_var.get()

    if model_type == "Transformer":
        # Get Transformer configuration
        num_heads, dropout_rate = get_transformer_config()
        
        # Build Transformer model
        model = create_transformer_model(sequence_length, vocab_size, layer_size, num_heads, dropout_rate)
       
        # Compile the model
        model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
       
        # Fit the model
        model.fit(input_sequences, output_words, epochs=epochs)

    else:
    
        num_layers = int(entry_layers.get())
        layer_size = int(entry_size.get())
        model_name = entry_name.get()
        epochs = int(entry_epochs.get())
        data_paths = root.filenames  # Changed to accept multiple filenames
    
        # Preprocess the data
        input_sequences, output_words, vocab_size, tokenizer, sequence_length = preprocess_data(data_paths)
    
        # Define tokenizer_path
        tokenizer_path = os.path.join("tokenizers", f"{model_name}_tokenizer.pickle")
    
        # Check if the ‘tokenizers’ directory exists, if not, create it
        os.makedirs("tokenizers", exist_ok=True)
    
        # Building and training the model
        model = Sequential()
        model.add(Embedding(input_dim=vocab_size, output_dim=layer_size))

        if not sequence_choice_var.get():  # Add masking for variable length sequences only
            model.add(Masking(mask_value=0))  # Ignoring padded values (zeros)

        # Adding predefined LSTM layers
        for _ in range(num_layers):
            model.add(LSTM(layer_size, return_sequences=True))
        model.add(LSTM(layer_size))

        model.add(Dense(vocab_size, activation="softmax"))

        model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
        model.fit(input_sequences, output_words, epochs=epochs)
    
            # For both: Save the tokenizer and model
    tokenizer_path = os.path.join("tokenizers", f"{model_name}_tokenizer.pickle")
    os.makedirs("tokenizers", exist_ok=True)
    
    with open(tokenizer_path, "wb") as handle:
        if sequence_choice_var.get():
            pickle.dump((tokenizer, sequence_length), handle, protocol=pickle.HIGHEST_PROTOCOL)
        else:
            pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)
    print(f"Tokenizer saved at {tokenizer_path}")

    os.makedirs("models", exist_ok=True)
    model.save(os.path.join("models", f"{model_name}.h5"))
    print(f"Model {model_name} trained and saved successfully!")

# UI Setup
root = tk.Tk()
root.title("Chatbot Language Model Trainer")

# Model type
model_type_var = tk.StringVar(value="LSTM")  # Default value set to ‘LSTM’

lbl_model_type = tk.Label(root, text="Select Model Type:")
lbl_model_type.pack()
radiobtn_lstm = tk.Radiobutton(root, text="LSTM", variable=model_type_var, value="LSTM")
radiobtn_lstm.pack()
radiobtn_transformer = tk.Radiobutton(root, text="Transformer", variable=model_type_var, value="Transformer")
radiobtn_transformer.pack()

# Number of layers
lbl_layers = tk.Label(root, text="Number of layers:")
lbl_layers.pack()
entry_layers = tk.Entry(root)
entry_layers.pack()

# Layer size
lbl_size = tk.Label(root, text="Size of each layer:")
lbl_size.pack()
entry_size = tk.Entry(root)
entry_size.pack()

# Model name
lbl_name = tk.Label(root, text="Model name:")
lbl_name.pack()
entry_name = tk.Entry(root)
entry_name.pack()

# Transformer specific settings
lbl_heads = tk.Label(root, text="Number of attention heads:")
lbl_heads.pack()
entry_heads = tk.Entry(root)
entry_heads.pack()

lbl_dropout = tk.Label(root, text="Dropout rate:")
lbl_dropout.pack()
entry_dropout = tk.Entry(root)
entry_dropout.pack()

# Number of epochs
lbl_epochs = tk.Label(root, text="Number of epochs:")
lbl_epochs.pack()
entry_epochs = tk.Entry(root)
entry_epochs.pack()

# Data file path
lbl_data_path = tk.Label(root, text="Data file path:")
lbl_data_path.pack()
entry_data_path = tk.Entry(root)
entry_data_path.pack()

# Checkbox for sequence length choice
lbl_sequence_choice = tk.Label(root, text="Use fixed sequence length:")
lbl_sequence_choice.pack()
sequence_choice_var = tk.BooleanVar()  # Boolean variable to hold the checkbox state
chk_sequence_choice = tk.Checkbutton(root, text="Fixed length", variable=sequence_choice_var)
chk_sequence_choice.pack()

# Entry for fixed sequence length if the toggle is on
lbl_fixed_length = tk.Label(root, text="Fixed sequence length:")
lbl_fixed_length.pack()
entry_fixed_length = tk.Entry(root)
entry_fixed_length.pack()

# Function to select multiple files
def select_files():
    file_paths = filedialog.askopenfilenames()  # Changed to open multiple files
    root.filenames = file_paths  # Store the list of file paths on the root object
    entry_data_path.delete(0, tk.END)
    entry_data_path.insert(0, "; ".join(file_paths))  # Display all file paths in the entry

btn_browse = tk.Button(root, text="Browse…", command=select_files)  # Changed to select_files
btn_browse.pack()

def get_transformer_config():
    num_heads = int(entry_heads.get())
    dropout_rate = float(entry_dropout.get())
    # … fetch other settings similarly …
    return num_heads, dropout_rate

# Train button
btn_train = tk.Button(root, text="Train Model", command=train_model)
btn_train.pack()

# Start the tkinter loop
root.mainloop()