From 8368944a43a0ecda8a8d9ba35fa6c2bba9d5df44 Mon Sep 17 00:00:00 2001 From: Hope Ogbons Date: Fri, 31 Oct 2025 03:19:49 +0100 Subject: [PATCH 1/4] Add banking intents mapping module This commit introduces a new Python module, banking_intents.py, which maps intent labels (0-76) to their corresponding intent names for the Banking77 application. The module includes functions to retrieve intent names by label and vice versa, along with a utility to display all intents. This addition enhances the application's ability to handle various banking-related queries effectively. --- .../hopeogbons/banking_intents.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 week6/community-contributions/hopeogbons/banking_intents.py diff --git a/week6/community-contributions/hopeogbons/banking_intents.py b/week6/community-contributions/hopeogbons/banking_intents.py new file mode 100644 index 0000000..495b497 --- /dev/null +++ b/week6/community-contributions/hopeogbons/banking_intents.py @@ -0,0 +1,148 @@ +""" +Banking77 Intent Mapping +Maps label numbers (0-76) to intent names +""" + +INTENT_LABELS = [ + "activate_my_card", + "age_limit", + "apple_pay_or_google_pay", + "atm_support", + "automatic_top_up", + "balance_not_updated_after_bank_transfer", + "balance_not_updated_after_cheque_or_cash_deposit", + "beneficiary_not_allowed", + "cancel_transfer", + "card_about_to_expire", + "card_acceptance", + "card_arrival", + "card_delivery_estimate", + "card_linking", + "card_not_working", + "card_payment_fee_charged", + "card_payment_not_recognised", + "card_payment_wrong_exchange_rate", + "card_swallowed", + "cash_withdrawal_charge", + "cash_withdrawal_not_recognised", + "change_pin", + "compromised_card", + "contactless_not_working", + "country_support", + "declined_card_payment", + "declined_cash_withdrawal", + "declined_transfer", + "direct_debit_payment_not_recognised", + "disposable_card_limits", + "edit_personal_details", + "exchange_charge", + "exchange_rate", + "exchange_via_app", + "extra_charge_on_statement", + "failed_transfer", + "fiat_currency_support", + "get_disposable_virtual_card", + "get_physical_card", + "getting_spare_card", + "getting_virtual_card", + "lost_or_stolen_card", + "lost_or_stolen_phone", + "order_physical_card", + "passcode_forgotten", + "pending_card_payment", + "pending_cash_withdrawal", + "pending_top_up", + "pending_transfer", + "pin_blocked", + "receiving_money", + "Refund_not_showing_up", + "request_refund", + "reverted_card_payment?", + "supported_cards_and_currencies", + "terminate_account", + "top_up_by_bank_transfer_charge", + "top_up_by_card_charge", + "top_up_by_cash_or_cheque", + "top_up_failed", + "top_up_limits", + "top_up_reverted", + "topping_up_by_card", + "transaction_charged_twice", + "transfer_fee_charged", + "transfer_into_account", + "transfer_not_received_by_recipient", + "transfer_timing", + "unable_to_verify_identity", + "verify_my_identity", + "verify_source_of_funds", + "verify_top_up", + "virtual_card_not_working", + "visa_or_mastercard", + "why_verify_identity", + "wrong_amount_of_cash_received", + "wrong_exchange_rate_for_cash_withdrawal" +] + + +def get_intent(label_number): + """ + Get intent name from label number. + + Args: + label_number (int): Label from 0 to 76 + + Returns: + str: Intent name + + Example: + >>> get_intent(0) + 'activate_my_card' + >>> get_intent(25) + 'declined_card_payment' + """ + if 0 <= label_number <= 76: + return INTENT_LABELS[label_number] + else: + raise ValueError(f"Label must be between 0 and 76, got {label_number}") + + +def get_label(intent_name): + """ + Get label number from intent name. + + Args: + intent_name (str): Intent name + + Returns: + int: Label number (0-76) + + Example: + >>> get_label('activate_my_card') + 0 + >>> get_label('declined_card_payment') + 25 + """ + try: + return INTENT_LABELS.index(intent_name) + except ValueError: + raise ValueError(f"Intent '{intent_name}' not found in labels") + + +# Quick access +def show_all_intents(): + """Display all 77 intents with their labels""" + for i, intent in enumerate(INTENT_LABELS): + print(f"{i}\t{intent}") + + +if __name__ == "__main__": + # Test the functions + print("Testing get_intent:") + print(f"Label 0: {get_intent(0)}") + print(f"Label 25: {get_intent(25)}") + print(f"Label 76: {get_intent(76)}") + + print("\nTesting get_label:") + print(f"'activate_my_card': {get_label('activate_my_card')}") + print(f"'declined_card_payment': {get_label('declined_card_payment')}") + From 3414454f4331f6fa9a4fda808ea4920450c05d1c Mon Sep 17 00:00:00 2001 From: Hope Ogbons Date: Fri, 31 Oct 2025 03:19:59 +0100 Subject: [PATCH 2/4] Add classifier testing framework for Banking Intent Model This commit introduces a new Python module, classifier_tester.py, which provides a testing framework for evaluating the accuracy of classification models on intent classification tasks. The module includes methods for running tests on individual data points, reporting metrics, and visualizing confusion pairs, enhancing the overall testing capabilities for the Banking77 application. --- .../hopeogbons/classifier_tester.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 week6/community-contributions/hopeogbons/classifier_tester.py diff --git a/week6/community-contributions/hopeogbons/classifier_tester.py b/week6/community-contributions/hopeogbons/classifier_tester.py new file mode 100644 index 0000000..c9662f7 --- /dev/null +++ b/week6/community-contributions/hopeogbons/classifier_tester.py @@ -0,0 +1,123 @@ +""" +Classification Tester for Banking Intent Model +Evaluates model accuracy on intent classification +""" + +import matplotlib.pyplot as plt +from collections import Counter +from banking_intents import get_intent + +GREEN = "\033[92m" +RED = "\033[91m" +RESET = "\033[0m" + + +class ClassifierTester: + """Test framework for classification models""" + + def __init__(self, predictor, data, title=None, size=100): + self.predictor = predictor + self.data = data + self.title = title or predictor.__name__.replace("_", " ").title() + self.size = min(size, len(data)) + self.predictions = [] + self.actuals = [] + self.correct = 0 + self.incorrect = 0 + + def run_datapoint(self, i): + """Test a single example""" + item = self.data[i] + + # Get prediction + predicted_intent = self.predictor(item) + actual_intent = get_intent(item['label']) + + # Check if correct + is_correct = predicted_intent == actual_intent + + if is_correct: + self.correct += 1 + color = GREEN + status = "✓" + else: + self.incorrect += 1 + color = RED + status = "✗" + + self.predictions.append(predicted_intent) + self.actuals.append(actual_intent) + + # Print result + query = item['text'][:60] + "..." if len(item['text']) > 60 else item['text'] + print(f"{color}{status} {i+1}: {query}") + print(f" Predicted: {predicted_intent} | Actual: {actual_intent}{RESET}") + + def chart(self): + """Visualize top confusion pairs""" + # Find misclassifications + errors = {} + for pred, actual in zip(self.predictions, self.actuals): + if pred != actual: + pair = f"{actual} → {pred}" + errors[pair] = errors.get(pair, 0) + 1 + + if not errors: + print("\n🎉 Perfect accuracy - no confusion to plot!") + return + + # Plot top 10 confusions + top_errors = sorted(errors.items(), key=lambda x: x[1], reverse=True)[:10] + + if top_errors: + labels = [pair for pair, _ in top_errors] + counts = [count for _, count in top_errors] + + plt.figure(figsize=(12, 6)) + plt.barh(labels, counts, color='coral') + plt.xlabel('Count') + plt.title('Top 10 Confusion Pairs (Actual → Predicted)') + plt.tight_layout() + plt.show() + + def report(self): + """Print final metrics and chart""" + accuracy = (self.correct / self.size) * 100 + + print("\n" + "="*70) + print(f"MODEL: {self.title}") + print(f"TESTED: {self.size} examples") + print(f"CORRECT: {self.correct} ({accuracy:.1f}%)") + print(f"INCORRECT: {self.incorrect}") + print("="*70) + + # Show most common errors + if self.incorrect > 0: + print("\nMost Common Errors:") + error_pairs = [(self.actuals[i], self.predictions[i]) + for i in range(len(self.actuals)) + if self.actuals[i] != self.predictions[i]] + error_counts = Counter(error_pairs).most_common(5) + + for (actual, pred), count in error_counts: + print(f" {actual} → {pred}: {count} times") + + # Chart + self.chart() + + return accuracy + + def run(self): + """Run the complete evaluation""" + print(f"Testing {self.title} on {self.size} examples...\n") + + for i in range(self.size): + self.run_datapoint(i) + + return self.report() + + @classmethod + def test(cls, function, data, size=100): + """Convenience method to test a predictor function""" + return cls(function, data, size=size).run() + From 95a3766d85b167f717fc4eafaf994e4e638950b0 Mon Sep 17 00:00:00 2001 From: Hope Ogbons Date: Fri, 31 Oct 2025 03:20:08 +0100 Subject: [PATCH 3/4] Add data cleaning utilities for dataset preparation This commit introduces a new Python module, data_cleaner.py, which provides functions for cleaning and preparing datasets for fine-tuning. The module includes a method to clean datasets based on text length and balance class distributions, as well as a function to analyze label distributions. These utilities enhance the data preprocessing capabilities for the application. --- .../hopeogbons/data_cleaner.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 week6/community-contributions/hopeogbons/data_cleaner.py diff --git a/week6/community-contributions/hopeogbons/data_cleaner.py b/week6/community-contributions/hopeogbons/data_cleaner.py new file mode 100644 index 0000000..31db9d6 --- /dev/null +++ b/week6/community-contributions/hopeogbons/data_cleaner.py @@ -0,0 +1,68 @@ +""" +Data cleaning utilities for dataset preparation +""" + +from collections import defaultdict + + +def clean_dataset(data, min_length=10, max_samples_per_intent=None): + """ + Clean and prepare dataset for fine-tuning + + Args: + data: HuggingFace dataset or list of examples + min_length: Minimum text length to keep (default: 10) + max_samples_per_intent: Max samples per intent for balancing (default: None = no limit) + + Returns: + list: Cleaned examples + + Example: + >>> cleaned = clean_dataset(dataset['train'], min_length=10, max_samples_per_intent=200) + >>> print(f"Cleaned {len(cleaned)} examples") + """ + cleaned = [] + + for example in data: + text = example['text'].strip() + + # Skip if too short + if len(text) < min_length: + continue + + # Normalize text - remove extra whitespace + text = ' '.join(text.split()) + + cleaned.append({ + 'text': text, + 'label': example['label'] + }) + + # Balance classes if max_samples_per_intent is specified + if max_samples_per_intent: + balanced = defaultdict(list) + + for item in cleaned: + balanced[item['label']].append(item) + + cleaned = [] + for label, items in balanced.items(): + cleaned.extend(items[:max_samples_per_intent]) + + return cleaned + + +def analyze_distribution(data): + """ + Analyze label distribution in dataset + + Args: + data: List of examples with 'label' field + + Returns: + dict: Label counts + """ + from collections import Counter + labels = [item['label'] for item in data] + return Counter(labels) + From a22a5cef2c4838e533a06db1976ceef64c1eec66 Mon Sep 17 00:00:00 2001 From: Hope Ogbons Date: Fri, 31 Oct 2025 03:24:09 +0100 Subject: [PATCH 4/4] Add week 6 exercise notebook for banking intent classification This commit introduces a new Jupyter notebook, 'week6 EXERCISE.ipynb', which outlines the process for fine-tuning a model to classify banking customer queries. The notebook includes steps for data preparation, model training, and evaluation, utilizing the Banking77 dataset and OpenAI's API for fine-tuning. This addition enhances the project's capabilities in handling banking-related queries effectively. --- .../hopeogbons/week6 EXERCISE.ipynb | 855 ++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 week6/community-contributions/hopeogbons/week6 EXERCISE.ipynb diff --git a/week6/community-contributions/hopeogbons/week6 EXERCISE.ipynb b/week6/community-contributions/hopeogbons/week6 EXERCISE.ipynb new file mode 100644 index 0000000..bcbd0f4 --- /dev/null +++ b/week6/community-contributions/hopeogbons/week6 EXERCISE.ipynb @@ -0,0 +1,855 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "776935d0", + "metadata": {}, + "outputs": [], + "source": [ + "# Import required libraries for data handling, API connections, and model training\n", + "import os\n", + "import re\n", + "import math\n", + "import json\n", + "import random\n", + "from dotenv import load_dotenv\n", + "from huggingface_hub import login\n", + "import matplotlib.pyplot as plt\n", + "from datasets import load_dataset\n", + "import numpy as np\n", + "import pickle\n", + "from collections import Counter\n", + "from openai import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "04ef96aa", + "metadata": {}, + "outputs": [], + "source": [ + "# Load API keys from .env file\n", + "load_dotenv(override=True)\n", + "os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', '####-####-####-####')\n", + "os.environ['HF_TOKEN'] = os.getenv('HF_TOKEN', '####-####-####-####')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8458f9e7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Note: Environment variable`HF_TOKEN` is set and is the current active token independently from the token you've just configured.\n" + ] + } + ], + "source": [ + "# Initialize OpenAI client and login to HuggingFace\n", + "openai = OpenAI()\n", + "\n", + "hf_token = os.environ['HF_TOKEN']\n", + "login(hf_token, add_to_git_credential=True)" + ] + }, + { + "cell_type": "markdown", + "id": "0263f64b", + "metadata": {}, + "source": [ + "# Step 1\n", + "\n", + "### Prepare our data for fine-tuning in JSONL (JSON Lines) format and upload to OpenAI" + ] + }, + { + "cell_type": "markdown", + "id": "0302c73d", + "metadata": {}, + "source": [ + "### Load and Cache Dataset\n", + "Download banking77 dataset or load from cache (for slow internet)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a85d7fbd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading from cached pickle files...\n", + "✓ Loaded 10003 train and 3080 test samples from cache\n" + ] + } + ], + "source": [ + "from data_cleaner import clean_dataset\n", + "\n", + "# Check if pickle files exist, otherwise download\n", + "if os.path.exists('train.pkl') and os.path.exists('test.pkl'):\n", + " print(\"Loading from cached pickle files...\")\n", + " with open('train.pkl', 'rb') as f:\n", + " train = pickle.load(f)\n", + " with open('test.pkl', 'rb') as f:\n", + " test = pickle.load(f)\n", + " print(f\"✓ Loaded {len(train)} train and {len(test)} test samples from cache\")\n", + "else:\n", + " print(\"✓ Downloading dataset from HuggingFace...\")\n", + " dataset = load_dataset(\"PolyAI/banking77\")\n", + " \n", + " # Clean the data\n", + " print(\"Cleaning dataset...\")\n", + " train = clean_dataset(dataset['train'], min_length=10, max_samples_per_intent=200)\n", + " test = clean_dataset(dataset['test'], min_length=10)\n", + " \n", + " # Save for next time\n", + " with open('train.pkl', 'wb') as f:\n", + " pickle.dump(train, f)\n", + " with open('test.pkl', 'wb') as f:\n", + " pickle.dump(test, f)\n", + " print(f\"✓ Cleaned and saved {len(train)} train and {len(test)} test samples\")" + ] + }, + { + "cell_type": "markdown", + "id": "df2d9c9d", + "metadata": {}, + "source": [ + "# Step 2\n", + "\n", + "### Create fine-tuning job on OpenAI and monitor training progress" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9a608e40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ Created 200 train and 50 validation examples\n" + ] + } + ], + "source": [ + "# Convert to list format for easier handling\n", + "train_list = [{'text': train[i]['text'], 'label': train[i]['label']} for i in range(len(train))]\n", + "\n", + "# Create fine-tuning subsets\n", + "fine_tune_train = train_list[:200] #800\n", + "fine_tune_validation = train_list[200:250] #4,000\n", + "\n", + "print(f\"✓ Created {len(fine_tune_train)} train and {len(fine_tune_validation)} validation examples\")" + ] + }, + { + "cell_type": "markdown", + "id": "e878a4f0", + "metadata": {}, + "source": [ + "### Format Messages for OpenAI\n", + "Create training and inference message formats\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e305e49e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'role': 'system',\n", + " 'content': 'You classify banking customer queries into intents. Reply only with the intent name, no explanation'},\n", + " {'role': 'user', 'content': 'I am still waiting on my card?'},\n", + " {'role': 'assistant', 'content': 'card_arrival'}]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from banking_intents import get_intent\n", + "\n", + "def messages_for_training(item):\n", + " \"\"\"Create messages for fine-tuning - includes the correct answer\"\"\"\n", + " system_message = \"You classify banking customer queries into intents. Reply only with the intent name, no explanation\"\n", + " return [\n", + " {\"role\": \"system\", \"content\": system_message},\n", + " {\"role\": \"user\", \"content\": item['text']},\n", + " {\"role\": \"assistant\", \"content\": get_intent(item['label'])}\n", + " ]\n", + "\n", + "def messages_for_inference(item):\n", + " \"\"\"Create messages for prediction - NO answer (model must predict)\"\"\"\n", + " system_message = \"You classify banking customer queries into intents. Reply only with the intent name, no explanation\"\n", + " return [\n", + " {\"role\": \"system\", \"content\": system_message},\n", + " {\"role\": \"user\", \"content\": item['text']}\n", + " ]\n", + "\n", + "# Test training format\n", + "messages_for_training(train[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c3a27241", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"messages\": [{\"role\": \"system\", \"content\": \"You classify banking customer queries into intents. Reply only with the intent name, no explanation\"}, {\"role\": \"user\", \"content\": \"I am still waiting on my card?\"}, {\"role\": \"assistant\", \"content\": \"card_arrival\"}]}\n", + "{\"messages\": [{\"role\": \"system\", \"content\": \"You classify banking customer queries into intents. Reply only with the intent name, no explanation\"}, {\"role\": \"user\", \"content\": \"What can I do if my card still hasn't arrived after 2 weeks?\"}, {\"role\": \"assistant\", \"content\": \"card_arrival\"}]}\n", + "{\"messages\": [{\"role\": \"system\", \"content\": \"You classify banking customer queries into intents. Reply only with the intent name, no explanation\"}, {\"role\": \"user\", \"content\": \"I have been waiting over a week. Is the card still coming?\"}, {\"role\": \"assistant\", \"content\": \"card_arrival\"}]}\n" + ] + } + ], + "source": [ + "def make_jsonl(data, start=0, end=None):\n", + " \"\"\"Convert data to JSONL format for training\"\"\"\n", + " result = \"\"\n", + " end = end or len(data)\n", + " \n", + " for i in range(start, end):\n", + " item = data[i]\n", + " messages = messages_for_training(item) # Use training format\n", + " messages_str = json.dumps(messages)\n", + " result += '{\"messages\": ' + messages_str +'}\\n'\n", + " \n", + " return result.strip()\n", + "\n", + "print(make_jsonl(train, start=0, end=3))" + ] + }, + { + "cell_type": "markdown", + "id": "bb0e75d6", + "metadata": {}, + "source": [ + "### Convert to JSONL and Upload\n", + "Prepare data in OpenAI format and upload to their servers\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "dd4affd3", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# Write JSONL string to file\n", + "def write_jsonl(data, filename, start=0, end=None):\n", + " with open(filename, \"w\") as f:\n", + " jsonl = make_jsonl(data, start, end)\n", + " f.write(jsonl)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8c5bf74c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0. I am still waiting on my card? → Intent: 11\n", + "1. What can I do if my card still hasn't arrived after 2 weeks? → Intent: 11\n", + "2. I have been waiting over a week. Is the card still coming? → Intent: 11\n" + ] + } + ], + "source": [ + "# Verify data loaded correctly - show first 3 examples\n", + "for i in range(3):\n", + " print(f\"{i}. {train[i]['text']} → Intent: {train[i]['label']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8b3bc0a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ Uploaded train: file-U84ceTSvNn833d6aPpWX4i\n", + "✓ Uploaded validation: file-ARVTnFJnHn2HpE9UAr9mr5\n" + ] + } + ], + "source": [ + "def prepare_and_upload(data, filename):\n", + " \"\"\"Write JSONL and upload to OpenAI\"\"\"\n", + " write_jsonl(data, filename)\n", + " with open(filename, \"rb\") as f:\n", + " return openai.files.create(file=f, purpose=\"fine-tune\")\n", + "\n", + "# Use it\n", + "train_file = prepare_and_upload(fine_tune_train, \"fine_tune_train.jsonl\")\n", + "validation_file = prepare_and_upload(fine_tune_validation, \"fine_tune_validation.jsonl\")\n", + "\n", + "print(f\"✓ Uploaded train: {train_file.id}\")\n", + "print(f\"✓ Uploaded validation: {validation_file.id}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f6147112", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FineTuningJob(id='ftjob-FN3B5dQQOhuk4UVOZ5X4CqBU', created_at=1761873619, error=Error(code=None, message=None, param=None), fine_tuned_model=None, finished_at=None, hyperparameters=Hyperparameters(batch_size='auto', learning_rate_multiplier='auto', n_epochs=1), model='gpt-4o-mini-2024-07-18', object='fine_tuning.job', organization_id='org-OFfqVJ5fIDV1i5BqCwT86Px6', result_files=[], seed=42, status='validating_files', trained_tokens=None, training_file='file-U84ceTSvNn833d6aPpWX4i', validation_file='file-ARVTnFJnHn2HpE9UAr9mr5', estimated_finish=None, integrations=[], metadata=None, method=Method(type='supervised', dpo=None, reinforcement=None, supervised=SupervisedMethod(hyperparameters=SupervisedHyperparameters(batch_size='auto', learning_rate_multiplier='auto', n_epochs=1))), user_provided_suffix='banking_intent', usage_metrics=None, shared_with_openai=False, eval_id=None)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create fine-tuning job - training happens on OpenAI's servers\n", + "openai.fine_tuning.jobs.create(\n", + " training_file=train_file.id,\n", + " validation_file=validation_file.id,\n", + " model=\"gpt-4o-mini-2024-07-18\",\n", + " seed=42, # For reproducibility\n", + " hyperparameters={\"n_epochs\": 1}, # Training passes\n", + " suffix=\"banking_intent\" # Custom model name\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "dd2fe11f", + "metadata": {}, + "source": [ + "### Monitor Training Progress\n", + "Check job status and view training events\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "bb98e266", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Status: ftjob-FN3B5dQQOhuk4UVOZ5X4CqBU\n", + "Status: succeeded\n" + ] + } + ], + "source": [ + "# List most recent fine-tuning job to check status\n", + "job = openai.fine_tuning.jobs.list(limit=1).data[0]\n", + "print(f\"Status: {job.id}\")\n", + "print(f\"Status: {job.status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a503b4f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FineTuningJob(id='ftjob-FN3B5dQQOhuk4UVOZ5X4CqBU', created_at=1761873619, error=Error(code=None, message=None, param=None), fine_tuned_model='ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5', finished_at=1761874273, hyperparameters=Hyperparameters(batch_size=1, learning_rate_multiplier=1.8, n_epochs=1), model='gpt-4o-mini-2024-07-18', object='fine_tuning.job', organization_id='org-OFfqVJ5fIDV1i5BqCwT86Px6', result_files=['file-N5eUNWYhwaxJt2KSYEjpBU'], seed=42, status='succeeded', trained_tokens=9105, training_file='file-U84ceTSvNn833d6aPpWX4i', validation_file='file-ARVTnFJnHn2HpE9UAr9mr5', estimated_finish=None, integrations=[], metadata=None, method=Method(type='supervised', dpo=None, reinforcement=None, supervised=SupervisedMethod(hyperparameters=SupervisedHyperparameters(batch_size=1, learning_rate_multiplier=1.8, n_epochs=1))), user_provided_suffix='banking_intent', usage_metrics=None, shared_with_openai=False, eval_id=None)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get detailed information about the job\n", + "openai.fine_tuning.jobs.retrieve(job.id)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d7008bdd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[FineTuningJobEvent(id='ftevent-nsKcQzrX5kAlH71815ndMGr4', created_at=1761875045, level='info', message='The job has successfully completed', object='fine_tuning.job.event', data={}, type='message'),\n", + " FineTuningJobEvent(id='ftevent-BWM8ClTEEWO3rVf1bn8WdszY', created_at=1761875039, level='info', message='Usage policy evaluations completed, model is now enabled for sampling', object='fine_tuning.job.event', data={}, type='message'),\n", + " FineTuningJobEvent(id='ftevent-yyxA2AU1GvpP5Ii3WbxlDktc', created_at=1761875039, level='info', message='Moderation checks for snapshot ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5 passed.', object='fine_tuning.job.event', data={'blocked': False, 'results': [{'flagged': False, 'category': 'harassment/threatening', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'sexual', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'sexual/minors', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'propaganda', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'hate', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'hate/threatening', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'illicit', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'violence', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'advice', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'self-harm/intent', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'self-harm/instructions', 'enforcement': 'non_blocking'}, {'flagged': False, 'category': 'sensitive', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'highly-sensitive', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'biological threats', 'enforcement': 'blocking'}, {'flagged': False, 'category': 'cyber security threats', 'enforcement': 'blocking'}], 'finetuned_model_checkpoint_id': 'ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5'}, type='moderation_checks'),\n", + " FineTuningJobEvent(id='ftevent-yojUh3akovLiyB1gX7NN4UCn', created_at=1761874276, level='info', message='Evaluating model against our usage policies', object='fine_tuning.job.event', data={}, type='message'),\n", + " FineTuningJobEvent(id='ftevent-tGdquF1ECX2kirD3YMFZ68H2', created_at=1761874276, level='info', message='New fine-tuned model created', object='fine_tuning.job.event', data={}, type='message'),\n", + " FineTuningJobEvent(id='ftevent-sMiUeWRPavauz8yO7SJfWA3U', created_at=1761874248, level='info', message='Step 200/200: training loss=0.00, validation loss=0.00, full validation loss=0.00', object='fine_tuning.job.event', data={'step': 200, 'train_loss': 0.00013084411330055445, 'valid_loss': 0.0001373291015625, 'total_steps': 200, 'full_valid_loss': 0.000696044921875, 'train_mean_token_accuracy': 1.0, 'valid_mean_token_accuracy': 1.0, 'full_valid_mean_token_accuracy': 1.0}, type='metrics'),\n", + " FineTuningJobEvent(id='ftevent-C6XLihn75vu22sJBkSbBDZ6L', created_at=1761874239, level='info', message='Step 199/200: training loss=0.00', object='fine_tuning.job.event', data={'step': 199, 'train_loss': 8.850097947288305e-05, 'total_steps': 200, 'train_mean_token_accuracy': 1.0}, type='metrics'),\n", + " FineTuningJobEvent(id='ftevent-LUGsXqTJjgAIw8fQQLjaTewj', created_at=1761874239, level='info', message='Step 198/200: training loss=0.00', object='fine_tuning.job.event', data={'step': 198, 'train_loss': 0.00010757446580100805, 'total_steps': 200, 'train_mean_token_accuracy': 1.0}, type='metrics'),\n", + " FineTuningJobEvent(id='ftevent-4peUk2wOGyP7Pe0COgCEMtI1', created_at=1761874239, level='info', message='Step 197/200: training loss=0.00', object='fine_tuning.job.event', data={'step': 197, 'train_loss': 0.00012130737013649195, 'total_steps': 200, 'train_mean_token_accuracy': 1.0}, type='metrics'),\n", + " FineTuningJobEvent(id='ftevent-SyIiUrcSzUzoYBZMAMfgH3zh', created_at=1761874234, level='info', message='Step 196/200: training loss=0.00', object='fine_tuning.job.event', data={'step': 196, 'train_loss': 9.689330909168348e-05, 'total_steps': 200, 'train_mean_token_accuracy': 1.0}, type='metrics')]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# View training events log (last 10 events)\n", + "openai.fine_tuning.jobs.list_events(fine_tuning_job_id=job.id, limit=10).data" + ] + }, + { + "cell_type": "markdown", + "id": "cb837277", + "metadata": {}, + "source": [ + "# Step 3\n", + "\n", + "### Use the fine-tuned model to classify banking queries" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "d710b977", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get the fine-tuned model ID (only works after training succeeds)\n", + "fine_tuned_model_name = openai.fine_tuning.jobs.retrieve(job.id).fine_tuned_model\n", + "fine_tuned_model_name" + ] + }, + { + "cell_type": "markdown", + "id": "e9518959", + "metadata": {}, + "source": [ + "### Use Fine-Tuned Model\n", + "Classify banking queries with the trained model\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5afef0bb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'card_arrival'" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def gpt_fine_tuned(item):\n", + " \"\"\"Classify banking query using fine-tuned model\"\"\"\n", + " response = openai.chat.completions.create(\n", + " model=fine_tuned_model_name,\n", + " messages=messages_for_inference(item), # Use inference format (no label)\n", + " seed=42,\n", + " max_tokens=20\n", + " )\n", + " intent = response.choices[0].message.content.strip()\n", + " return intent\n", + "\n", + "gpt_fine_tuned(train[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "5198c5c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model name: ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5\n", + "\n", + "Job ID: ftjob-FN3B5dQQOhuk4UVOZ5X4CqBU\n", + "Status: succeeded\n", + "Fine-tuned model: ft:gpt-4o-mini-2024-07-18:hope-ogbons:banking-intent:CWYGwKT5\n" + ] + } + ], + "source": [ + "# Verify training completed successfully\n", + "print(\"Model name:\", fine_tuned_model_name)\n", + "print()\n", + "\n", + "print(\"Job ID:\", job.id)\n", + "job_status = openai.fine_tuning.jobs.retrieve(job.id)\n", + "print(\"Status:\", job_status.status)\n", + "print(\"Fine-tuned model:\", job_status.fine_tuned_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "33683c39", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test example: {'text': 'How do I locate my card?', 'label': 11}\n", + "Predicted intent: card_arrival\n" + ] + } + ], + "source": [ + "# Test the fine-tuned model on a single example\n", + "print(\"Test example:\", test[0])\n", + "print(\"Predicted intent:\", gpt_fine_tuned(test[0]))" + ] + }, + { + "cell_type": "markdown", + "id": "216f3164", + "metadata": {}, + "source": [ + "### Evaluate Model Performance\n", + "Test on 100 examples and calculate accuracy\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b500717d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ Converted 3080 test examples to list format\n" + ] + } + ], + "source": [ + "# Convert test to list format\n", + "test_list = [{'text': test[i]['text'], 'label': test[i]['label']} for i in range(len(test))]\n", + "\n", + "print(f\"✓ Converted {len(test_list)} test examples to list format\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "f0787061", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing Gpt Fine Tuned on 100 examples...\n", + "\n", + "\u001b[92m✓ 1: How do I locate my card?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 2: I still have not received my new card, I ordered over a week...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 3: I ordered a card but it has not arrived. Help please!\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 4: Is there a way to know when my card will arrive?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 5: My card has not arrived yet.\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 6: When will I get my card?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 7: Do you know if there is a tracking number for the new card y...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 8: i have not received my card\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 9: still waiting on that card\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 10: Is it normal to have to wait over a week for my new card?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 11: How do I track my card?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 12: How long does a card delivery take?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 13: I still don't have my card after 2 weeks. What should I do?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 14: still waiting on my new card\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 15: I am still waiting for my card after 1 week. Is this ok?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 16: I have been waiting longer than expected for my bank card, c...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 17: I've been waiting longer than expected for my card.\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 18: Why hasn't my card been delivered?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 19: Where is my new card? I have been waiting a week!\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 20: My card still hasn't arrived after 2 weeks. Is it lost?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 21: I did not get my card yet, is it lost?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 22: Status of the card I ordered.\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 23: How long should my new card take to arrive?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 24: I ordered my card 2 weeks ago and it still isn't here? What ...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 25: My card has not arrived yet, where is it?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 26: What is the tracking number for my card that was mailed?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 27: I think something went wrong with my card delivery as I have...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 28: I'm still waiting for delivery of my new card, why is it tak...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 29: I ordered a card a week ago, and it's still not here. What d...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 30: i want to track the card you sent\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 31: My card hasn't arrived yet.\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 32: I was expecting my new card and am wondering why I haven't r...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 33: How do I know when my card will arrive?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 34: I'm still waiting on my card to be delivered.\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 35: Does the card you sent have a way to track to it?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 36: I ordered a card and I still haven't received it. It's been ...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 37: I'm starting to think my card is lost because it still hasn'...\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 38: Is there tracking info available?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 39: What is the tracking number for the card you sent?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 40: Where is the tracking number for the card you sent me?\n", + " Predicted: card_arrival | Actual: card_arrival\u001b[0m\n", + "\u001b[92m✓ 41: Why won't my card show up on the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 42: I would like to reactivate my card.\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 43: Where do I link the new card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 44: I have received my card, can you help me put it in the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 45: How do I link a card that I already have?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 46: I received my new card, but I don't see it in the app anywhe...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 47: How do I re-add a card to the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 48: How do I add the card to my account?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 49: Can I put my old card back into the system? I just found it/\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 50: I have one of your cards already, how do I link it?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 51: How do I link a new card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 52: Can I link an existing card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 53: How do I link one your card if I have one already?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 54: How do I add a card to the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 55: Can I link my new card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 56: Hello, I found the card I misplaced and I need to reactive i...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 57: Can you tell me how to link one of your cards that I already...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 58: How do I view the card I received in the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 59: Where on the website do I go to link my card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 60: I found my card, can I add it to the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 61: How do I link to my credit card with you?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 62: I've received my card so now I need to know how to sync it t...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 63: I found my card, I would like to reactivate it.\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 64: How do I link this new card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 65: Can I reactivate my lost card that I found this morning in m...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 66: how do I link an already existing card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 67: The app doesn't show the card I received.\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 68: how do I link a card I already have?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 69: I would like to link my card. How do I do it?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 70: Can you please show me where I can find the location to link...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 71: Where do I go if I want to link my new card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 72: Is there a way to make my old card usable with the app?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 73: Can I reactivate a card I thought I lost?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 74: How do I link my card\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 75: Where do I need to go in the app to enter my card info?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 76: I have found my lost or stolen card. Is there a way I can li...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 77: Could you help me reactivate my card? It was previously lost...\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 78: I already have one of your cards, how do I link them?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 79: How do I link my replacement card?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[92m✓ 80: Can I link another card to my account?\n", + " Predicted: card_linking | Actual: card_linking\u001b[0m\n", + "\u001b[91m✗ 81: I need to know your exchange rates.\n", + " Predicted: exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 82: What exchange rates do you offer?\n", + " Predicted: currency_exchange | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 83: How did you come up with your exchange rates?\n", + " Predicted: currency_conversion | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 84: Where do you guys acquire your exchange rate?\n", + " Predicted: foreign_exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 85: How do I find the exchange rate?\n", + " Predicted: exchange_rate_arrival | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 86: What are your international exchange rates?\n", + " Predicted: currency_exchange | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 87: How often do your exchange rates change\n", + " Predicted: exchange_rate_change | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 88: Please advise what is the exchange rate\n", + " Predicted: currency_exchange | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 89: How are exchange rates calculated?\n", + " Predicted: currency_conversion | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 90: what are exchange rates based on\n", + " Predicted: currency_conversion | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 91: what are exchange rates\n", + " Predicted: exchange_rate_arrival | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 92: What are the most current exchange rates?\n", + " Predicted: exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 93: Can you explain your exchange rate policy to me?\n", + " Predicted: currency_conversion | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 94: Is it a good time to exchange?\n", + " Predicted: currency_exchange | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 95: What is the exchange rate like on this app?\n", + " Predicted: currency_conversion | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 96: Do you have a list of exchange rates?\n", + " Predicted: foreign_exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 97: Can you tell me where you get your exchange rates?\n", + " Predicted: exchange_rate_arrival | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 98: Will I get a curreng foreign exchange rate?\n", + " Predicted: currency_exchange | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 99: What currencies is an exchange rate calculated in?\n", + " Predicted: foreign_exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\u001b[91m✗ 100: Where do you get your exchange rates from?\n", + " Predicted: foreign_exchange_rates | Actual: exchange_rate\u001b[0m\n", + "\n", + "======================================================================\n", + "MODEL: Gpt Fine Tuned\n", + "TESTED: 100 examples\n", + "CORRECT: 80 (80.0%)\n", + "INCORRECT: 20\n", + "======================================================================\n", + "\n", + "Most Common Errors:\n", + " exchange_rate → currency_exchange: 5 times\n", + " exchange_rate → currency_conversion: 5 times\n", + " exchange_rate → foreign_exchange_rates: 4 times\n", + " exchange_rate → exchange_rate_arrival: 3 times\n", + " exchange_rate → exchange_rates: 2 times\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAJOCAYAAABm7rQwAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAnOVJREFUeJzs3XlUleXe//HPBhGQSXEWB8QRT85KqRmoeMCB1FKPQ4FzZB7SHNDHVHBCSzS1x6w8gaEeO5lTouWQE2jlgPOUJtIpSyuVqVBg//7wx/24ZRBNNw3v11qs5b6n63sPu9X+rOu6bpPZbDYLAAAAAAAAsCKbki4AAAAAAAAAfz2EUgAAAAAAALA6QikAAAAAAABYHaEUAAAAAAAArI5QCgAAAAAAAFZHKAUAAAAAAACrI5QCAAAAAACA1RFKAQAAAAAAwOoIpQAAAAAAAGB1hFIAAAB4IHFxcWrYsKHs7OxUtmzZh378iIgImUymh35ca7PmeXzzzTdycHBQYmKiVdp71Hbt2iWTyaRdu3aVdClW5efnJz8/P+NzcnKyTCaTYmNjS6ymu91d46lTp1SqVCmdOHGi5IoC8IdDKAUAAP7wTCZTsf6s8cP2rbfeUp8+fVSzZk2ZTCYNGjSo0G2vX7+uESNGqGLFinJyclKHDh10+PDh+2pv3bp16tKliypUqKDSpUurWrVq6tu3rz777LPfeCZFO3PmjAYNGqQ6dero3Xff1TvvvPNI27O2O58bGxsbVatWTX//+99/9+HI9OnT9fjjj6tdu3YFru/bt69MJpPCw8MfuI19+/YpIiJC169ff+Bj/J7lBWF5f3Z2dvLy8lJwcLC+/vrrki7vvljzXjVq1EjdunXT1KlTH3lbAP48TGaz2VzSRQAAAPwWK1assPj8/vvva9u2bYqLi7NY3rlzZ1WuXPmR1uLp6am0tDT5+Pho+/btGjhwYIG9G3Jzc9W+fXsdPXpU48ePV4UKFbRkyRJ98803OnTokOrVq1dkO2azWUOGDFFsbKyaN2+u3r17q0qVKrp8+bLWrVunQ4cOKTExUW3btn0k57l06VK9+OKL+uqrr1S3bt1H0kZ2drays7Pl4ODwSI5fFJPJpM6dOys4OFhms1kXL17UkiVLdOXKFcXHx6tLly7FPpa1zuPq1avy8PDQ8uXL1b9//3zrU1NTVblyZVWpUkU5OTm6dOnSA/XgmjdvnsaPH6+LFy/K09PzIVReuF27dqlDhw7auXOnRa8ca7QZFham1q1b69atWzp8+LDeeecdOTs76/jx46pWrdojrSHvXPNCULPZrKysLNnZ2cnW1rbYx3mU9+ruGiVpy5Yt6tq1q86fP686deo81PYA/DmVKukCAAAAfqvnnnvO4vPnn3+ubdu25VtuDbt37zZ6STk7Oxe63Zo1a7Rv3z59+OGH6t27t6TbvVjq16+vadOmadWqVUW2Ex0drdjYWI0ePVrz58+3CBcmT56suLg4lSr16P5X78qVK5L0SIbt5SlVqtQjPYd7qV+/vsUz1KtXLzVp0kRvvPHGfYVSxTmP3Nxc3bx58zcFVytWrFCpUqUUFBRU4PqPPvpIOTk5eu+999SxY0ft2bNHvr6+D9zen1379u2N7+bgwYNVv359hYWFafny5Zo0aVKB+2RkZMjJyemh12IymUoknL1f/v7+KleunJYvX67p06eXdDkA/gAYvgcAAP4SMjIyNHbsWNWoUUP29vZq0KCB5s2bp7s7jZtMJo0aNUorV65UgwYN5ODgoJYtW2rPnj3FaqdWrVrF6n2yZs0aVa5cWc8884yxrGLFiurbt682bNigrKysQvf95ZdfFBUVpYYNG2revHkFtvf888/Lx8fH+Pz111+rT58+cnd3V5kyZfTEE08oPj7eYp+8YUv/+c9/NGvWLFWvXl0ODg7q1KmTzp8/b2zn6empadOmGTWbTCZFRERIksW/7+Tp6WkxlPHWrVuKjIxUvXr15ODgoPLly+vJJ5/Utm3bjG0KmospOztbM2bMUJ06dWRvby9PT0/9z//8T77r5enpqe7duyshIUE+Pj5ycHCQl5eX3n///UKv6700btxYFSpU0MWLFyVJe/fuNYZq2tvbq0aNGhozZox++eUXi/0KOo87n7O//e1vsre31yeffCJJWr16tVq2bCkXFxe5urqqcePGWrhw4T3rW79+vR5//PFCw9CVK1eqc+fO6tChg7y9vbVy5coCtztz5oz69u2rihUrytHRUQ0aNNDkyZONcxk/frwkqXbt2sYQt+Tk5CLnPbr7ubh06ZJGjhypBg0ayNHRUeXLl1efPn2UnJx8z/MsKR07dpQk4/7n3ddTp05pwIABKleunJ588klj+xUrVqhly5ZydHSUu7u7+vXrp2+++Sbfcd955x3VqVNHjo6O8vHx0d69e/NtU9i1fdB79ShqlCQ7Ozv5+flpw4YNRVxJAPg/hFIAAOBPz2w26+mnn9aCBQsUGBio+fPnq0GDBho/frxeeeWVfNvv3r1bo0eP1nPPPafp06frp59+UmBg4EOdwDcpKUktWrSQjY3l/475+PgoMzNT586dK3TfhIQE/fzzzxowYECxhvL88MMPatu2rT799FONHDlSs2bN0q+//qqnn35a69aty7f9nDlztG7dOo0bN06TJk3S559/roEDBxrr33jjDfXq1UvS7Tm04uLiLMK14oiIiFBkZKQ6dOigN998U5MnT1bNmjXvOafWsGHDNHXqVLVo0UILFiyQr6+voqKi1K9fv3zbnj9/Xr1791bnzp0VHR2tcuXKadCgQTp58uR91Zrn2rVrunbtmsqXLy9J+vDDD5WZmakXX3xRixcvVkBAgBYvXqzg4OBiHe+zzz7TmDFj9I9//EMLFy6Up6entm3bpv79+6tcuXKaO3eu5syZIz8/v3tOXH7r1i0dOHBALVq0KHD9d999p507dxrD+vr37681a9bo5s2bFtsdO3ZMjz/+uD777DMNHz5cCxcuVM+ePfXxxx9Lkp555hnjGAsWLFBcXJzi4uJUsWLFYp1zngMHDmjfvn3q16+fFi1apNDQUO3YsUN+fn7KzMy8r2MVx5QpU/Tuu+/+pmNcuHBBkoz7n6dPnz7KzMzU7NmzNXz4cEnSrFmzFBwcrHr16mn+/PkaPXq0duzYoaeeespifqd//etfeuGFF1SlShW99tprateunZ5++ukCg6G7/dZ79ahqbNmypU6cOKHU1NTiXVgAf21mAACAP5mXXnrJfOf/5qxfv94syTxz5kyL7Xr37m02mUzm8+fPG8skmSWZDx48aCy7dOmS2cHBwdyrV6/7qsPJyckcEhJS6LohQ4bkWx4fH2+WZP7kk08KPe7ChQvNkszr1q0rVh2jR482SzLv3bvXWJaWlmauXbu22dPT05yTk2M2m83mnTt3miWZvb29zVlZWfnaO378uLFs2rRpZknmq1evWrQlyTxt2rR8NdSqVcviWjRt2tTcrVu3IuvOayPPkSNHzJLMw4YNs9hu3LhxZknmzz77zKI9SeY9e/YYy65cuWK2t7c3jx07tsh2885j6NCh5qtXr5qvXLli/uKLL8ydOnUySzJHR0ebzWazOTMzM99+UVFRZpPJZL506VKh55F3fBsbG/PJkyctlr/88stmV1dXc3Z29j1rvNP58+fNksyLFy8ucP28efPMjo6O5tTUVLPZbDafO3euwGfoqaeeMru4uFjUbzabzbm5uca/X3/9dbMk88WLFy22uXjxolmSOSYmJl/7dz8XBV27/fv3myWZ33//fWNZ3jO5c+fOAs+ruP75z3+aTSZTgbXdLa/N9957z3z16lXzd999Z46Pjzd7enqaTSaT+cCBA2az+f/ua//+/S32T05ONtva2ppnzZplsfz48ePmUqVKGctv3rxprlSpkrlZs2YW37d33nnHLMns6+trLCvo2v6We/UoasyzatUqsyTzF198kW8dANyNnlIAAOBPb/PmzbK1tVVYWJjF8rFjx8psNmvLli0Wy9u0aaOWLVsan2vWrKkePXro008/VU5OzkOp6ZdffpG9vX2+5Xnzxtw9BOxOeT0QXFxcitXW5s2b5ePjYzG0yNnZWSNGjFBycrJOnTplsf3gwYNVunRp43P79u0l6aG+eaxs2bI6efKkvvrqq2Lvs3nzZknK17tt7NixkpRvOGKjRo2M2qXbQw0bNGhQ7PP417/+pYoVK6pSpUp6/PHHlZiYqFdeeUWjR4+WJDk6OhrbZmRk6Mcff1Tbtm1lNpuVlJR0z+P7+vqqUaNGFsvKli2rjIwMi2GMxfHTTz9JksqVK1fg+pUrV6pbt27GM1OvXj21bNnSYgjf1atXtWfPHg0ZMkQ1a9a02P9BJkQvyp3X7tatW/rpp59Ut25dlS1b9r7fQClJv/76a5F/r732mkJCQjR06NB7zteWZ8iQIapYsaKqVaumbt26KSMjQ8uXL1erVq0stgsNDbX4vHbtWuXm5qpv37768ccfjb8qVaqoXr162rlzpyTp4MGDunLlikJDQy2+b4MGDZKbm1uRtf3We/Uoa8x7Bn/88cd71gEATHQOAAD+9C5duqRq1arlC3G8vb2N9Xcq6M139evXV2Zmpq5evaoqVar85pocHR0LnDfq119/NdYXxtXVVZKUlpZWrLYuXbqkxx9/PN/yO8//scceM5bf/SM370fmtWvXitVecUyfPl09evRQ/fr19dhjjykwMFDPP/+8mjRpUug+ly5dko2NTb63/VWpUkVly5bNdx/vPg/p9rkU9zx69OihUaNGyWQy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YcMC1504cUJjx47VvHnzCgwGCuPq6qpq1aopMTHR4v4nJiYW2IspT+XKlVWtWjV9/fXXGjhwYLHaatCggTZv3myxLG9+rYehuDW9/vrrOnPmjHbv3q2AgADFxMQYz27es1zQ0LHiOHTokHJzcxUdHW28lfE///nPfR3Dzc1NlStX1oEDB4xnNycnR4cPH1azZs0kFf97e78SExM1aNAg40UF6enpSk5OttimdOnSBT7Lp06dKvJZLlWqlPz9/eXv769p06apbNmy+uyzzwoMZQEAAAAAf1zFDqVcXFw0btw4jRkzRrm5uXryySd148YNJSYmytXVVSEhIYqPj9d7772n/fv3q0WLFho/frxCQkJ07NgxlStXTtOmTVOnTp1Up04d9evXT9nZ2dq8ebPCw8OLVUO9evWUkpKi1atXq3Xr1oqPj9e6dfc3vtfFxUUhISEaP3683N3dValSJU2bNs3iLXP169fXwIEDFRwcrOjoaDVv3lxXr17Vjh071KRJk0LnYSpO/XFxcWrVqpVSU1M1fvz4fD1uPD09tWPHDrVr10729vYqV66cpk6dqu7du6tmzZrq3bu3bGxsdPToUZ04cUIzZ868rxoaNmyoFStW3HN+oIKMHz9e06ZNU506ddSsWTPFxMToyJEjFkOxChIZGamwsDC5ubkpMDBQWVlZOnjwoK5du2Yx9CrPCy+8oPnz5ys8PFxDhw7VkSNHFBsbK0n5ep89qHvVlJSUpKlTp2rNmjVq166d5s+fr5dfflm+vr7y8vLSpEmT1LhxY40cOVKhoaEqXbq0du7cqT59+qhChQr3bL9u3bq6deuWFi9erKCgICUmJmrp0qX3fR7//Oc/FRUVpbp166phw4ZavHixrl27Zlyn4nxvH0S9evW0du1aBQUFyWQyacqUKfl6I3p6emrPnj3q16+f7O3tVaFCBYWHh+uJJ57QqFGjNGzYMDk5OenUqVPatm2b3nzzTW3atElff/21nnrqKZUrV06bN29Wbm6uxfBaAAAAAMCfw329fW/GjBmaMmWKoqKi5O3trcDAQMXHx6t27dq6evWqhg4dqoiICLVo0ULS7R/+lStXNiYp9/Pz04cffqiNGzeqWbNm6tixo7788stit//0009rzJgxGjVqlJo1a6Z9+/ZpypQp93MKkqT58+erTZs26t69u/z9/dWuXTt5e3tb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LXS730VPqfPnzyszM1OdO3eWs7Oz8ff+++/rwoULxnZ9+vRRr169NGfOHM2bN88IpCTpyJEjxg/ZwuT1ppFu/5CvUqWKrly5Yiz74IMP1K5dO1WpUkXOzs569dVXlZKSYnEMT09Pi94veSGEJH399de6deuWRQ8tNzc3NWjQwPh8/Phx5eTkqH79+hbnunv3botzLUrp0qUtzkWSfvjhBw0fPlz16tWTm5ubXF1dlZ6enq/+ux09elTTp0+3qGX48OG6fPmyMjMz822fN7yusL/g4GD9+uuvevbZZy2GPua5cuWKvvvuu3veq+Jo1apVvmUtW7bMd347d+60OL+GDRtKksX1vvt63nlfjxw5ourVqxcYSEm3A6y//e1vWr58uaTboV6tWrX01FNP3fMcjhw5oubNmxcYSEnS6dOn1a5dO4tl7dq10+nTpy2W3Vm/k5OTXF1djfqbNWsmb29vI9zdvXu3rly5oj59+ki6fY3S09NVvnx5i+t08eJFi2tUq1YtI5CSpKZNm6pTp05q3Lix+vTpo3fffVfXrl0r8DwuXLigW7duWZyLnZ2dfHx8ijyXqlWrSpLF9/ROkyZN0o0bN4y/b775psDtAAAAAAC/P7+Lt++lp6dLuj0Ez8PDw2LdnT03MjMzdejQIdna2uYb6ubo6HjPduzsLHtOmEwmY5jU/v37NXDgQEVGRiogIEBubm5avXq1oqOji32M4khPT5etra1xHndydnYu1jEcHR1lMpksloWEhOinn37SwoULVatWLdnb26tNmzb3nJA6PT1dkZGReuaZZ/KtK6hnioeHR74Q4U579uzRyJEj9dprr+W7Vnm1F8XG5nZOemcHvoLCLel2+HKvZenp6QoKCtLcuXPzbZsXeEhF39fiPFvDhg3T//7v/2rixImKiYnR4MGD892jghTn2MVxr+dy4MCBWrVqlSZOnKhVq1YpMDBQ5cuXl3T7GlWtWrXAec3unKj87mtra2urbdu2ad++fdq6dasWL16syZMn64svvlDt2rUfyrnkXcPCvmP29vYW/40AAAAAAPxx/C56St05YXfdunUt/mrUqGFsN3bsWNnY2GjLli1atGiRPvvsM2NdkyZNtGPHjgeuYd++fapVq5YmT56sVq1aqV69esYk0MXl5eUlOzs7i8mbb9y4oXPnzhmfmzdvrpycHF25ciXfuVapUuWB609MTFRYWJi6du1qTCB+52Te0u0f+zk5ORbLWrRoobNnz+arpW7dukZAdPcxGjZsWOBfdna2xo4dq3nz5iksLKzAOl1cXOTp6VnovcrriXP58mVj2Z2Tnt+vFi1a6OTJk/L09Mx3fgWFWgVp0qSJ/vvf/1rcx7s999xzunTpkhYtWqRTp04pJCSk2Mc+cuRIvnmV8nh7eysxMdFiWWJioho1alSs4+cZMGCATpw4oUOHDmnNmjUaOHCgsa5Fixb6/vvvVapUqXzXqEKFCkUe12QyqV27doqMjFRSUpJKly6tdevydyPNm6j9znO5deuWDhw4cN/nAgAAAAD4c/hd9JRycXHRuHHjNGbMGOXm5urJJ5/UjRs3lJiYKFdXV4WEhCg+Pl7vvfee9u/frxYtWmj8+PEKCQnRsWPHVK5cOU2bNk2dOnVSnTp11K9fP2VnZ2vz5s0KDw8vVg316tVTSkqKVq9erdatWys+Pr7AH9f3Oo+QkBCNHz9e7u7uqlSpkqZNmyYbGxujx0f9+vU1cOBABQcHKzo6Ws2bN9fVq1e1Y8cONWnSpNB5mIpTf1xcnFq1aqXU1FSNHz8+Xy+cvDCoXbt2sre3V7ly5TR16lR1795dNWvWVO/evWVjY6OjR4/qxIkTBc75VJSGDRtqxYoVFvM8FSQiIkKhoaGqVKmSunTporS0NCUmJuqf//ynHB0d9cQTT2jOnDmqXbu2rly5oldfffW+r0eel156Se+++6769+9vvF3v/PnzWr16tZYtW5avt1pBfH199dRTT+nZZ5/V/PnzVbduXZ05c0Ymk0mBgYGSpHLlyumZZ57R+PHj9fe//13Vq1cvVn39+/fX7Nmz1bNnT0VFRalq1apKSkpStWrV1KZNG40fP159+/ZV8+bN5e/vr48//lhr167V9u3b7+s6eHr+v/buPKyKuv//+OuggogskoqKKHIl7ogoFnanaCjiklq2eJOg0WJphksuV5pg3komZpaZLeKWaWWSaS7InUtYigvmgqamoYnhnRtLbsDvD76cnydQcWkOyPNxXVyXZ2bOzHvOzOGKV+/PZzzVrl07RUREKDc3V48++qh5XVBQkAICAtS7d29NnTpV3t7eOnnypPnhAcUNk5SkrVu3KjExUV26dFHNmjW1detWnT59Wk2aNCmyrYODg1566SXzd6NevXqaOnWqcnJyFBERcUvnAgAAAAC4N5SKTilJevPNNzV+/HhNmTJFTZo0UdeuXbVq1So1aNBAp0+fVkREhKKiouTn5yep4Gljbm5u5knKAwMD9eWXX2rFihXy9fVVp06dtG3bthIf/9FHH9WwYcM0ZMgQ+fr6asuWLRo/fvwtn8f06dMVEBCgHj16KCgoSA899JCaNGliMRQuLi5OYWFhGjFihBo1aqTevXsrOTlZ9erVu+XjFfr000919uxZ+fn5qX///ho6dKhq1qxpsU1sbKwSEhLk4eGhVq1aSZKCg4O1cuVKrVu3Tv7+/nrwwQf1zjvvFDtx9c1UrFjxpoGUVDDUcMaMGfrggw/UrFkz9ejRw2I45ty5c3X16lW1bt1akZGRtxyOXatOnTpKSkpSbm6uunTpohYtWigyMlIuLi7FdoJdz7Jly+Tv769+/fqpadOmGjVqVJGus4iICF2+fPmGT4v7O1tbW61bt041a9ZUt27d1KJFC/OT/SSpd+/eevfddzVt2jQ1a9ZMc+bMUVxcnAIDA0t8jEKhoaHavXu3+vTpYxFYmkwmfffdd2rfvr0GDhwob29vPf300/rtt9/k5uZ23f05OTlp06ZN6tatm7y9vTVu3DjFxsYqJCSk2O1jYmL0+OOPq3///vLz89Phw4e1du1aVatW7ZbPBQAAAABQ9pWKp+/dy7Kzs+Xu7q7Y2Fg6Qu5xCxcu1LBhw3Ty5EnZ2tpau5xyyfyEB56+BwAAAOBedo88fa9UDN+7l+zatUsHDhxQ27Ztdf78eU2cOFGSStRBhLIpJydH6enpiomJ0YsvvkggBQAAAABACZSa4Xv3kmnTpqlly5YKCgpSdna2Nm/efNMJowuFhISoatWqxf5Mnjz5H64ct2Pq1Klq3LixatWqpbFjx1qsmzx58nWv5/WGuQEAAAAAUB4wfK+U+f333/XXX38Vu87V1VWurq4GV4Q7cebMmes+Wc/e3l7u7u4GV3RvY/geAAAAgHKB4Xv4JxBS3FsIEgEAAAAAKB7D9wAAAAAAAGA4QikAAAAAAAAYjlAKAAAAAAAAhiOUAgAAAAAAgOEIpQAAAAAAAGA4QikAAAAAAAAYjlAKAAAAAAAAhiOUAgAAAAAAgOEIpQAAAAAAAGA4QikAAAAAAAAYjlAKAAAAAAAAhiOUAgAAAAAAgOEIpQAAAAAAAGA4QikAAAAAAAAYjlAKAAAAAAAAhiOUAgAAAAAAgOEIpQAAAAAAAGA4QikAAAAAAAAYjlAKAAAAAAAAhiOUAgAAAAAAgOEIpQAAAAAAAGC4itYuAADuurGLJScna1cBAAAAALgBOqUAAAAAAABgOEIpAAAAAAAAGI5QCgAAAAAAAIYjlAIAAAAAAIDhCKUAAAAAAABgOEIpAAAAAAAAGI5QCgAAAAAAAIYjlAIAAAAAAIDhCKUAAAAAAABgOEIpAAAAAAAAGI5QCgAAAAAAAIYjlAIAAAAAAIDhCKUAAAAAAABgOEIpAAAAAAAAGI5QCgAAAAAAAIaraO0CAOCum/Jvya6StasAAAAAgH9G1HJrV3BX0CkFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFKAQAAAAAAwHCEUgAAAAAAADAcoRQAAAAAAAAMRygFAAAAAAAAwxFK/R9PT0/NmDHD2mUAhtqwYYNMJpPOnTtn7VIAAAAAAOUMoRTMTCaT4uPjrV0GAAAAAAAoBwil7nG5ubnKy8uzdhlWlZ+fr6tXrxZZfvnyZStUAwAAAAAApDIUSuXl5WnKlClq0KCB7O3t1bJlS3311VeSCkKHoKAgBQcHKz8/X5J05swZ1a1bV2+88YZ5H99++638/f1VuXJlVa9eXX369LE4Rk5Ojp599lk5OjqqXr16+uijjyzWjx49Wt7e3qpSpYq8vLw0fvx4Xblyxbw+KipKvr6+WrhwoTw9PeXs7Kynn35amZmZ5m0yMzMVGhoqBwcH1a5dW++8844CAwMVGRlp3ubSpUsaOXKk3N3d5eDgoAceeEAbNmwo0ec0b948ubi4aMWKFWratKns7OyUlpam5ORkde7cWdWrV5ezs7M6dOignTt3mt/n6ekpSerTp49MJpP5tSR988038vPzU+XKleXl5aXo6OhiQ56S2Lt3b4m2mzt3rpo1ayY7OzvVrl1bQ4YMkSQdO3ZMJpNJKSkp5m3PnTsnk8lk/owKh6StXr1arVu3lp2dnX744QcFBgZqyJAhioyMVPXq1RUcHGyuKSQkRFWrVpWbm5v69++v//3vf+b9BwYGaujQoRo1apRcXV1Vq1YtRUVFWdR77tw5vfjii3Jzc1PlypXVvHlzrVy5UtnZ2XJycjLfq4Xi4+Pl4OBgcW9cz/Hjx/Xkk0/KxcVFrq6u6tWrl44dOyZJOnDggKpUqaLFixebt//iiy9kb2+v/fv3Syq4n0aPHi0PDw/Z2dnp/vvv16effmpxjB07dqhNmzaqUqWK2rVrp4MHD5rXHTlyRL169ZKbm5uqVq0qf39/rV+/3uL9np6emjx58g2/P1u2bJGvr68qV66sNm3aKD4+vsi1vNm1AAAAAADcO8pMKDVlyhQtWLBAH374ofbt26dhw4bpmWee0caNG2UymTR//nwlJydr5syZkqRBgwbJ3d3dHEqtWrVKffr0Ubdu3bRr1y4lJiaqbdu2FseIjY1VmzZttGvXLr388st66aWXLP44d3R01Lx587R//369++67+vjjj/XOO+9Y7OPIkSOKj4/XypUrtXLlSm3cuFExMTHm9cOHD1dSUpJWrFihhIQEbd682SIckqQhQ4boxx9/1JIlS/Tzzz/riSeeUNeuXXXo0KESfVY5OTl666239Mknn2jfvn2qWbOmMjMzFR4erh9++EE//fSTGjZsqG7duplDkeTkZElSXFyc0tPTza83b96ssLAwvfrqq9q/f7/mzJmjefPm6T//+U+JarnWrl275O/vrxUrVtxwu9mzZ2vw4MF64YUXtGfPHq1YsUL333//LR9vzJgxiomJUWpqqnx8fCRJ8+fPl62trZKSkvThhx/q3Llz6tSpk1q1aqXt27drzZo1+uOPP/Tkk09a7Gv+/PlycHDQ1q1bNXXqVE2cOFEJCQmSCgLTkJAQJSUladGiRdq/f79iYmJUoUIFOTg46Omnn1ZcXJzF/uLi4tS3b185Ojre8ByuXLmi4OBgOTo6avPmzUpKSlLVqlXVtWtXXb58WY0bN9a0adP08ssvKy0tTSdOnNCgQYP01ltvqWnTppKksLAwff7555o5c6ZSU1M1Z84cVa1a1eI4r7/+umJjY7V9+3ZVrFhRzz77rHldVlaWunXrpsTERO3atUtdu3ZVz549lZaWZrGPG31/Lly4oJ49e6pFixbauXOn3nzzTY0ePdri/SW9Fte6dOmSLly4YPEDAAAAACgbTPmFrUWl2KVLl+Tq6qr169crICDAvPy5555TTk6OuUvkyy+/VFhYmCIjI/Xee+9p165datiwoSSpXbt28vLy0qJFi4o9hqenpx5++GEtXLhQUkH3Va1atRQdHa1BgwYV+55p06ZpyZIl2r59u6SCTqm3335bp06dMocNo0aN0qZNm/TTTz8pMzNT9913nxYvXqy+fftKks6fP686dero+eef14wZM5SWliYvLy+lpaWpTp065mMFBQWpbdu2mjx58g0/q3nz5mngwIFKSUlRy5Ytr7tdXl6eXFxctHjxYvXo0UNSwZxSy5cvV+/evS2O+8gjj2js2LHmZYsWLdKoUaN08uTJG9ZSnIULF+qFF17QV199pe7duxe7jbu7uwYOHKhJkyYVWXfs2DE1aNBAu3btkq+vr6SCMKNatWr6/vvvFRgYqA0bNqhjx46Kj49Xr169zO8NDAzUhQsXLELASZMmafPmzVq7dq152YkTJ+Th4aGDBw/K29tbgYGBys3N1ebNm83btG3bVp06dVJMTIzWrVunkJAQpaamytvbu0jN27ZtU7t27XT8+HHVrl1bGRkZcnd31/r169WhQ4cbfl6LFi3SpEmTlJqaKpPJJKlg2KGLi4vi4+PVpUsXSVKPHj104cIF2draqkKFClqzZo1MJpN++eUXNWrUSAkJCQoKCiqy/8LPav369XrkkUckSd999526d++uv/76S5UrVy62rubNm2vQoEHmDrabfX8+/PBDjRs3TidOnDDv85NPPtHzzz9vvpYluRZ/FxUVpejo6CLLz4/pLie7Sjf8bAEAAACgzIpabu0KbujChQtydnbW+fPn5eTkdN3tKhpY0207fPiwcnJy1LlzZ4vlly9fVqtWrcyvn3jiCS1fvlwxMTGaPXu2OZCSpJSUFD3//PM3PE5hN41UENDUqlVLGRkZ5mVLly7VzJkzdeTIEWVlZenq1atFPlxPT0+L7pfCEEKSfv31V125csWiQ8vZ2VmNGjUyv96zZ49yc3OL/AF+6dIl3XfffTesv5Ctra3FuUjSH3/8oXHjxmnDhg3KyMhQbm6ucnJyinS7/N3u3buVlJRk0RmVm5urixcvKicnR1WqVLHYvjA0upnHH39cmZmZqlTJ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run comprehensive evaluation on 100 test examples\n", + "from classifier_tester import ClassifierTester\n", + "\n", + "accuracy = ClassifierTester.test(gpt_fine_tuned, test_list, size=100)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}