Can you implement ROUGE in this code:
```
# Install Required Libraries
!pip install --upgrade transformers
!pip install datasets
!pip install evaluate
!pip install sacrebleu
!pip install bert_score

# Import Libraries
import evaluate
import torch
import numpy as np
from transformers import (
    T5Tokenizer,
    T5ForConditionalGeneration,
    Seq2SeqTrainer,
    Seq2SeqTrainingArguments,
    DataCollatorForSeq2Seq,
)
from datasets import load_dataset, concatenate_datasets
from collections import defaultdict
from nltk import ngrams
import nltk

# Download NLTK data (if not already downloaded)
nltk.download('punkt')

# Load and Prepare the Dataset
dataset1 = load_dataset("danielv835/personal_finance_v0.2")

# Remove the 'rejected' column from dataset1
dataset1 = dataset1.remove_columns(['rejected'])

# Load the tokenizer
tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-base")

# Tokenization function without padding
def tokenize_function(examples):
    model_inputs = tokenizer(
        examples["context"],
        max_length=256,
        truncation=True,
        padding=False
    )
    with tokenizer.as_target_tokenizer():
        labels = tokenizer(
            examples["chosen"],
            max_length=256,
            truncation=True,
            padding=False
    )
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs

# Tokenize both datasets
tokenized_datasets1 = dataset1.map(tokenize_function, batched=True)

# Load the pre-trained FLAN-T5 model for conditional generation
tw_model = T5ForConditionalGeneration.from_pretrained("google/flan-t5-base")

# Define the data collator
data_collator = DataCollatorForSeq2Seq(
    tokenizer=tokenizer,
    model=tw_model,
    label_pad_token_id=-100,  # Explicitly set label padding
    padding="longest",
    return_tensors="pt"
)

# Ensure the pad_token_id is set
tw_model.config.pad_token_id = tokenizer.pad_token_id

# Define training arguments for the Trainer API
training_args = Seq2SeqTrainingArguments(
    output_dir="./trainer_wheel_model",    # Output directory for saving model checkpoints
    eval_strategy="epoch",                 # Evaluate at the end of every epoch
    learning_rate=5e-5,                    # Set model learning rate
    per_device_train_batch_size=8,         # Adjust batch size for your GPU/CPU
    per_device_eval_batch_size=4,
    gradient_accumulation_steps=2,         # Use gradient accumulation
    save_steps=500,                        # Save model every 500 steps
    save_total_limit=2,                    # Limit number of saved checkpoints
    logging_dir='./logs',                  # Directory for storing logs
    logging_steps=200,                     # Log every 200 steps
    num_train_epochs=3,                    # Number of training epochs
    fp16=False,                             # Disable mixed precision
    predict_with_generate=True,            # Generate predictions during evaluation
    generation_max_length=256,             # Maximum length of generated sequences
    generation_num_beams=5,                # Number of beams for beam search
)

# Define the refined F1 Score with n-gram overlaps and weighting
def f1_score_refined(preds, labels, max_n=3, weights=None):
    """
    Calculate a weighted F1 score based on n-gram overlaps.

    Args:
        preds (list of str): Predicted texts.
        labels (list of str): Ground truth texts.
        max_n (int): Maximum n-gram to consider.
        weights (dict): Weights for each n-gram level. E.g., {1:1.0, 2:1.5, 3:2.0}

    Returns:
        float: Weighted F1 score.
    """
    if weights is None:
        # Default weights: higher n-grams have higher weights
        weights = {1: 1.0, 2: 1.5, 3: 2.0}

    total_f1 = 0.0
    for pred, label in zip(preds, labels):
        pred = nltk.word_tokenize(pred.lower())
        label = nltk.word_tokenize(label.lower())

        f1 = 0.0
        for n in range(1, max_n + 1):
            pred_ngrams = set(ngrams(pred, n))
            label_ngrams = set(ngrams(label, n))
            common_ngrams = pred_ngrams.intersection(label_ngrams)

            if not pred_ngrams or not label_ngrams:
                precision = 0.0
                recall = 0.0
            else:
                precision = len(common_ngrams) / len(pred_ngrams)
                recall = len(common_ngrams) / len(label_ngrams)

            if precision + recall == 0:
                f1_n = 0.0
            else:
                f1_n = 2 * precision * recall / (precision + recall)

            # Apply weighting for the n-gram level
            f1 += weights.get(n, 1.0) * f1_n

        # Normalize the f1 by the sum of weights to keep it in a reasonable range
        total_weight = sum(weights.get(n, 1.0) for n in range(1, max_n + 1))
        f1 /= total_weight

        total_f1 += f1

    return total_f1 / len(preds) if len(preds) > 0 else 0

# Load required metrics
metric_bert = evaluate.load("bertscore")
metric_meteor = evaluate.load("meteor")

# Define the compute_metrics function
def compute_metrics(eval_pred):
    preds, labels = eval_pred

    # If logits are returned as a tuple, extract the first element
    if isinstance(preds, tuple):
        preds = preds[0]

    # Replace -100 in preds and the labels as we can't decode them
    preds = np.where(preds != -100, preds, tokenizer.pad_token_id)
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)

    # Decode the predicted tokens into text
    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Post-process predictions and labels
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [label.strip() for label in decoded_labels]

    # Compute Refined F1 Score
    refined_f1 = f1_score_refined(decoded_preds, decoded_labels)

    # Compute METEOR
    meteor_result = metric_meteor.compute(predictions=decoded_preds, references=decoded_labels)
    meteor_score = meteor_result["meteor"]

    # Compute BERTScore (using default 'bert-base-uncased' model)
    bert_result = metric_bert.compute(predictions=decoded_preds, references=decoded_labels, lang="en")
    bert_score_f1 = np.mean(bert_result["f1"])

    # Combine the metrics
    result = {
        "refined_f1": refined_f1 * 100,
        "meteor": meteor_score * 100,          # Express METEOR as percentage
        "bert_score_f1": bert_score_f1 * 100  # Express BERTScore F1 as percentage
    }

    return result

# Initialize the Trainer instance
trainer = Seq2SeqTrainer(
    model=tw_model,                        # Model to be trained
    args=training_args,                    # Training arguments
    train_dataset=tokenized_datasets1["train"],           # Training dataset
    eval_dataset=tokenized_datasets1["test"],             # Evaluation dataset
    data_collator=data_collator,           # Data collator for batching
    tokenizer=tokenizer,                   # Tokenizer for encoding/decoding
    compute_metrics=compute_metrics,       # Function to compute evaluation metrics
)

# Empty the cache before starting the training process
torch.cuda.empty_cache()

# Start training the model
print("Starting training...")
trainer.train()
print("Training completed!")

# Clear GPU memory cache after training
torch.cuda.empty_cache()

# Evaluate the Fine-Tuned Model
eval_results = trainer.evaluate()
print(f"Final evaluation loss: {eval_results['eval_loss']}")
```