I have this code
```py
# Imports
# Importing necessary libraries: pandas for potential data handling,
# transformers for model/tokenizer and Trainer-related tasks, torch for PyTorch utilities.
import pandas as pd
from transformers import GPT2LMHeadModel, GPT2Tokenizer, Trainer, TrainingArguments, DataCollatorForLanguageModeling
import torch

# Dataset as an array of dictionaries
# This is the conversational dataset used for fine-tuning GPT-2.
# Each dictionary contains an input and the expected output (conversation pairs).
dataset = [
    {'input_text': 'Hello, what are you?', 'output_text': 'Hi! I am here to assist you with any questions you have.'},
    {'input_text': 'What is your name?', 'output_text': 'I am GPT-2, your friendly chatbot.'},
    {'input_text': 'Tell me a joke.', 'output_text': 'Why don’t scientists trust atoms? Because they make up everything!'},
]

# Tokenize the dataset
# Tokenization converts raw text into tokens that the model can process.
# We also add the special 
 token to mark the end of each conversation.
def tokenize_data(dataset, tokenizer):
    inputs = []
    for pair in dataset:
        # Combine the input and output text for each conversation pair
        input_text = pair['input_text']
        output_text = pair['output_text']
        # The combined text allows the model to learn a mapping from input to output
        combined_text = f"{input_text}\n{output_text}\n
"
        
        # Append the combined input/output pair for tokenization
        inputs.append(combined_text)
    
    # Tokenize the combined texts, returning tokenized tensors (PyTorch format).
    # Padding ensures all sequences have the same length; truncation cuts overly long sequences.
    tokenized_inputs = tokenizer(inputs, return_tensors='pt', padding=True, truncation=True)
    return tokenized_inputs

# Load GPT-2 model and tokenizer
# GPT-2 is the model we'll fine-tune for the chatbot.
# The tokenizer converts between human-readable text and input IDs the model can understand.
model_name = 'gpt2'
model = GPT2LMHeadModel.from_pretrained(model_name)  # Load the GPT-2 model
tokenizer = GPT2Tokenizer.from_pretrained(model_name)  # Load the GPT-2 tokenizer

# Add padding token
# GPT-2 doesn’t have a padding token by default, which causes errors when padding is needed.
# We set the padding token to be the same as the end-of-sequence token to handle variable-length inputs.
tokenizer.pad_token = tokenizer.eos_token

# Tokenize the in-memory dataset
# Convert the in-memory dataset into tokenized format to make it usable for the model.
tokenized_dataset = tokenize_data(dataset, tokenizer)

# Create PyTorch dataset
# We need to wrap the tokenized data into a PyTorch Dataset object, allowing it to be used during training.
class ChatDataset(torch.utils.data.Dataset):
    def __init__(self, encodings):
        self.encodings = encodings  # Store tokenized data

    def __len__(self):
        # Return the number of samples in the dataset
        return len(self.encodings['input_ids'])

    def __getitem__(self, idx):
        # Return each sample (input IDs and other data) as tensors.
        item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
        return item

# Wrap the tokenized dataset into a PyTorch Dataset
train_dataset = ChatDataset(tokenized_dataset)

# Training arguments
# These arguments configure how the model will be trained.
training_args = TrainingArguments(
    output_dir='./results',  # Where to save the model checkpoints and outputs
    overwrite_output_dir=True,  # Allow overwriting the output directory
    num_train_epochs=20,  # Number of epochs (complete passes through the dataset)
    per_device_train_batch_size=4,  # Batch size for each device during training
    save_steps=10_000,  # Save a checkpoint every 10,000 steps
    save_total_limit=2,  # Limit the total number of checkpoints to 2
)

# Data collator
# The data collator ensures that input sequences in a batch are of equal length by padding them.
# For GPT-2, masked language modeling (MLM) is not used, so we set mlm=False.
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,  # Use GPT-2's tokenizer
    mlm=False,  # We don't use masked language modeling for GPT-2
)

# Trainer
# The Trainer class handles the actual training loop, model optimization, and logging.
trainer = Trainer(
    model=model,  # The GPT-2 model we're fine-tuning
    args=training_args,  # The training configuration
    data_collator=data_collator,  # Responsible for creating input batches with proper padding
    train_dataset=train_dataset,  # The dataset to train the model on
)

# Train the model
# This command kicks off the training process, fine-tuning GPT-2 on our small dataset of conversations.
trainer.train()

# Example usage
# Now that the model is trained, we can input a message and get a response from it.
print(chr(27) + "[2J")
input_text = input("> ")

# Tokenize the input text and set the pad_token_id to eos_token to handle variable-length inputs
input_ids = tokenizer.encode(input_text, return_tensors='pt')

# Generate output (response) from the model using sampling instead of greedy decoding
# Greedy decoding would only take the most likely next token, while sampling introduces variation.
output = model.generate(
    input_ids,
    max_length=250,  # Set the maximum length for the response
    num_return_sequences=1,  # Generate one response
    temperature=0.2,  # Controls randomness (lower value = less random, higher = more random)
    do_sample=True,  # Enable sampling to generate varied responses
    pad_token_id=tokenizer.eos_token_id,  # Ensure padding works with the eos token
    attention_mask=torch.ones(input_ids.shape, device=input_ids.device)  # Provide an attention mask
)

# Decode the generated output back into readable text, skipping special tokens (e.g., 
)
response = tokenizer.decode(output[0], skip_special_tokens=True)
print(response)  # Print the chatbot's response
```
What I've found is that the output, regardless of my input, only responds with "Hi! I am here to assist you with any questions you have" on and on. I'd like the model to be able to respond accordingly (do whatever must be done to get this right) and I'd like the model to not retrain itself every time unless there's an actual change in the dataset. Give me whatever suggestions you think will work, and/or the entire revised code