diff --git a/week6/community-contributions/day2-improved.ipynb b/week6/community-contributions/day2-improved.ipynb new file mode 100644 index 0000000..f3a2a39 --- /dev/null +++ b/week6/community-contributions/day2-improved.ipynb @@ -0,0 +1,823 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28a0673e-96b5-43f2-8a8b-bd033bf851b0", + "metadata": {}, + "source": [ + "# The Product Pricer Continued\n", + "\n", + "A model that can estimate how much something costs, from its description.\n", + "\n", + "## Data Curation Part 2\n", + "\n", + "Today we'll extend our dataset to a greater coverage, and craft it into an excellent dataset for training. \n", + "Data curation can seem less exciting than other things we work on, but it's a crucial part of the LLM engineers' responsibility and an important craft to hone, so that you can build your own commercial solutions with high quality datasets.\n", + "\n", + "The dataset is here: \n", + "https://huggingface.co/datasets/McAuley-Lab/Amazon-Reviews-2023\n", + "\n", + "And the folder with all the product datasets is here: \n", + "https://huggingface.co/datasets/McAuley-Lab/Amazon-Reviews-2023/tree/main/raw/meta_categories\n", + "\n", + "Handles Large Datasets: This notebook is designed to efficiently process large datasets like the Amazon Reviews 2023 data, even with limited local resources.\n", + "https://colab.research.google.com/drive/1KY55mHyM5weQMSzHxiDXKSCxB_hItCD2?usp=sharing\n", + "\n", + "## Important Note - read me first please\n", + "\n", + "We are about to craft a massive dataset of 400,000 items covering multiple types of product. In Week 7 we will be using this data to train our own model. It's a pretty big dataset, and depending on the GPU you select, training could take 20+ hours. It will be really good fun, but it could cost a few dollars in compute units.\n", + "\n", + "As an alternative, if you want to keep things quick & low cost, you can work with a smaller dataset focused only on Home Appliances. You'll be able to cover the same learning points; the results will be good -- not quite as good as the full dataset, but still pretty amazing! If you'd prefer to do this, I've set up an alternative jupyter notebook in this folder called `lite.ipynb` that you should use in place of this one.\n", + "\n", + "Also, if you'd prefer, you can shortcut running all this data curation by downloading the pickle files that we save in the last cell. The pickle files are available here: https://drive.google.com/drive/folders/1f_IZGybvs9o0J5sb3xmtTEQB3BXllzrW" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "67cedf85-8125-4322-998e-9375fe745597", + "metadata": {}, + "outputs": [], + "source": [ + "# imports\n", + "\n", + "import os\n", + "import random\n", + "from dotenv import load_dotenv\n", + "from huggingface_hub import login\n", + "from datasets import load_dataset, Dataset, DatasetDict\n", + "import matplotlib.pyplot as plt\n", + "from collections import Counter, defaultdict\n", + "import numpy as np\n", + "import pickle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "446bc939-62fe-4608-bec3-52ae1b2de322", + "metadata": {}, + "outputs": [], + "source": [ + "# Run this in your LOCAL environment to get the exact versions\n", + "import sys\n", + "print(f\"Python version: {sys.version}\")\n", + "print(\"=\"*50)\n", + "\n", + "# Check versions of all your dependencies\n", + "dependencies = [\n", + " 'datasets',\n", + " 'transformers', \n", + " 'huggingface_hub',\n", + " 'matplotlib',\n", + " 'numpy',\n", + " 'python-dotenv', # This is the package name for dotenv\n", + " 'tqdm' # Usually imported by datasets/transformers\n", + "]\n", + "\n", + "# Method 1: Using __version__ attribute\n", + "print(\"DEPENDENCY VERSIONS:\")\n", + "print(\"=\"*50)\n", + "\n", + "for dep in dependencies:\n", + " try:\n", + " if dep == 'python-dotenv':\n", + " import dotenv\n", + " version = dotenv.__version__\n", + " print(f\"python-dotenv: {version}\")\n", + " elif dep == 'huggingface_hub':\n", + " import huggingface_hub\n", + " version = huggingface_hub.__version__\n", + " print(f\"huggingface_hub: {version}\")\n", + " else:\n", + " module = __import__(dep)\n", + " version = getattr(module, '__version__', 'Unknown')\n", + " print(f\"{dep}: {version}\")\n", + " except ImportError:\n", + " print(f\"{dep}: NOT INSTALLED\")\n", + " except AttributeError:\n", + " print(f\"{dep}: Version attribute not found\")\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"INSTALLATION COMMANDS FOR COLAB:\")\n", + "print(\"=\"*50)\n", + "\n", + "# Method 2: Using pip show (more reliable)\n", + "import subprocess\n", + "import json\n", + "\n", + "def get_pip_version(package):\n", + " try:\n", + " result = subprocess.run([sys.executable, '-m', 'pip', 'show', package], \n", + " capture_output=True, text=True)\n", + " if result.returncode == 0:\n", + " for line in result.stdout.split('\\n'):\n", + " if line.startswith('Version:'):\n", + " return line.split(':', 1)[1].strip()\n", + " except:\n", + " pass\n", + " return None\n", + "\n", + "print(\"# Run these commands in Google Colab:\")\n", + "print(\"# (Copy and paste the exact versions from your local environment)\")\n", + "print()\n", + "\n", + "for dep in dependencies:\n", + " version = get_pip_version(dep)\n", + " if version:\n", + " print(f\"!pip install {dep}=={version}\")\n", + " else:\n", + " print(f\"# !pip install {dep} # Version not found\")\n", + "\n", + "print()\n", + "print(\"# Alternative: Install all at once\")\n", + "install_commands = []\n", + "for dep in dependencies:\n", + " version = get_pip_version(dep)\n", + " if version:\n", + " install_commands.append(f\"{dep}=={version}\")\n", + " else:\n", + " install_commands.append(dep)\n", + "\n", + "print(f\"!pip install {' '.join(install_commands)}\")\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"ADDITIONAL INFO:\")\n", + "print(\"=\"*50)\n", + "\n", + "# Check if we're in a virtual environment\n", + "print(f\"Virtual environment: {sys.prefix != sys.base_prefix}\")\n", + "print(f\"Python executable: {sys.executable}\")\n", + "\n", + "# Show pip list for reference\n", + "print(\"\\nFull pip list (for reference):\")\n", + "try:\n", + " result = subprocess.run([sys.executable, '-m', 'pip', 'list'], \n", + " capture_output=True, text=True)\n", + " if result.returncode == 0:\n", + " lines = result.stdout.split('\\n')\n", + " relevant_packages = []\n", + " for line in lines:\n", + " for dep in dependencies + ['torch', 'tensorflow', 'tokenizers']:\n", + " if dep.lower() in line.lower():\n", + " relevant_packages.append(line.strip())\n", + " break\n", + " \n", + " for pkg in relevant_packages:\n", + " print(f\" {pkg}\")\n", + "except Exception as e:\n", + " print(f\"Could not get pip list: {e}\")\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"REQUIREMENTS.TXT FORMAT:\")\n", + "print(\"=\"*50)\n", + "print(\"# Copy this to create a requirements.txt file:\")\n", + "\n", + "for dep in dependencies:\n", + " version = get_pip_version(dep)\n", + " if version:\n", + " print(f\"{dep}=={version}\")\n", + " else:\n", + " print(f\"{dep}\")\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"COLAB SETUP SCRIPT:\")\n", + "print(\"=\"*50)\n", + "print(\"\"\"# Copy this entire block to run in Colab:\n", + "\n", + "# Install exact versions from local environment\n", + "!pip install --upgrade pip\n", + "\n", + "# Your specific versions (replace with actual versions from above)\"\"\")\n", + "\n", + "for dep in dependencies:\n", + " version = get_pip_version(dep)\n", + " if version:\n", + " print(f\"!pip install {dep}=={version}\")\n", + "\n", + "print(\"\"\"\n", + "# Restart runtime after installation\n", + "import os\n", + "os.kill(os.getpid(), 9) # This will restart the runtime\n", + "\"\"\")\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"VERIFICATION SCRIPT FOR COLAB:\")\n", + "print(\"=\"*50)\n", + "print(\"\"\"# Run this in Colab AFTER installing to verify versions match:\n", + "\n", + "import sys\n", + "dependencies_to_check = [\n", + " 'datasets', 'transformers', 'huggingface_hub', \n", + " 'matplotlib', 'numpy', 'dotenv', 'tqdm'\n", + "]\n", + "\n", + "print(\"Verification of installed versions:\")\n", + "print(\"=\"*40)\n", + "for dep in dependencies_to_check:\n", + " try:\n", + " if dep == 'dotenv':\n", + " import dotenv as module\n", + " else:\n", + " module = __import__(dep)\n", + " version = getattr(module, '__version__', 'Unknown')\n", + " print(f\"{dep}: {version}\")\n", + " except ImportError:\n", + " print(f\"{dep}: NOT INSTALLED\")\n", + "\n", + "print(\"\\\\nIf all versions match your local environment, the code should work!\")\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7390a6aa-79cb-4dea-b6d7-de7e4b13e472", + "metadata": {}, + "outputs": [], + "source": [ + "# environment\n", + "\n", + "load_dotenv(override=True)\n", + "os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env')\n", + "os.environ['ANTHROPIC_API_KEY'] = os.getenv('ANTHROPIC_API_KEY', 'your-key-if-not-using-env')\n", + "os.environ['HF_TOKEN'] = os.getenv('HF_TOKEN', 'your-key-if-not-using-env')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0732274a-aa6a-44fc-aee2-40dc8a8e4451", + "metadata": {}, + "outputs": [], + "source": [ + "# Log in to HuggingFace\n", + "\n", + "hf_token = os.environ['HF_TOKEN']\n", + "login(hf_token, add_to_git_credential=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6746144c-2e19-485a-8086-368c144722b4", + "metadata": {}, + "outputs": [], + "source": [ + "# More imports after HF login\n", + "\n", + "from loaders import ItemLoader\n", + "from items import Item" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1adcf323-de9d-4c24-a9c3-d7ae554d06ca", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "01065d69-765c-42c8-9f90-68b8c8754068", + "metadata": {}, + "source": [ + "## The ItemLoader code\n", + "\n", + "Look in loaders.py - there's some useful code to make life easier for us" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "049885d4-fdfa-4ff0-a932-4a2ed73928e2", + "metadata": {}, + "outputs": [], + "source": [ + "# Load in the same dataset as last time\n", + "\n", + "items = ItemLoader(\"All_Beauty\").load()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ffba41b5-ddb6-4359-9790-9b2db900eee1", + "metadata": {}, + "outputs": [], + "source": [ + "# Look for a familiar item..\n", + "print(items[1].prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cc7f3e7-e98e-48c1-8eed-1608b42b0f65", + "metadata": {}, + "outputs": [], + "source": [ + "import datasets\n", + "print(datasets.__version__)" + ] + }, + { + "cell_type": "markdown", + "id": "e2b6dc50-ac5c-4cf2-af2e-968ed8ef86d7", + "metadata": {}, + "source": [ + "## Now to SCALE UP\n", + "\n", + "Let's look at all datasets of all the items that you might find in a large home retail store - electrical, electronic, office and related, but not clothes / beauty / books." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1d06cd3-f3c2-44f0-a9f2-13b54ff8be5c", + "metadata": {}, + "outputs": [], + "source": [ + "dataset_names = [\n", + " \"Automotive\",\n", + " \"Electronics\",\n", + " \"Office_Products\",\n", + " \"Tools_and_Home_Improvement\",\n", + " \"Cell_Phones_and_Accessories\",\n", + " \"Toys_and_Games\",\n", + " \"Appliances\",\n", + " \"Musical_Instruments\",\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa8fd0f0-509a-4298-8fcc-e499a061e1be", + "metadata": {}, + "outputs": [], + "source": [ + "items = []\n", + "for dataset_name in dataset_names:\n", + " loader = ItemLoader(dataset_name)\n", + " items.extend(loader.load())\n", + "\n", + "# Now, time for a coffee break!!\n", + "# By the way, I put the biggest datasets first.. it gets faster." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e29a5ab-ca61-41cc-9b33-22d374681b85", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"A grand total of {len(items):,} items\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89078cb1-9679-4eb0-b295-599b8586bcd1", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the distribution of token counts again\n", + "\n", + "tokens = [item.token_count for item in items]\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Token counts: Avg {sum(tokens)/len(tokens):,.1f} and highest {max(tokens):,}\\n\")\n", + "plt.xlabel('Length (tokens)')\n", + "plt.ylabel('Count')\n", + "plt.hist(tokens, rwidth=0.7, color=\"skyblue\", bins=range(0, 300, 10))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c38e0c43-9f7a-450e-a911-c94d37d9b9c3", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the distribution of prices\n", + "\n", + "prices = [item.price for item in items]\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Prices: Avg {sum(prices)/len(prices):,.1f} and highest {max(prices):,}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"blueviolet\", bins=range(0, 1000, 10))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eabc7c61-0cd2-41f4-baa1-b85400bbf87f", + "metadata": {}, + "outputs": [], + "source": [ + "category_counts = Counter()\n", + "for item in items:\n", + " category_counts[item.category]+=1\n", + "\n", + "categories = category_counts.keys()\n", + "counts = [category_counts[category] for category in categories]\n", + "\n", + "# Bar chart by category\n", + "plt.figure(figsize=(15, 6))\n", + "plt.bar(categories, counts, color=\"goldenrod\")\n", + "plt.title('How many in each category')\n", + "plt.xlabel('Categories')\n", + "plt.ylabel('Count')\n", + "\n", + "plt.xticks(rotation=30, ha='right')\n", + "\n", + "# Add value labels on top of each bar\n", + "for i, v in enumerate(counts):\n", + " plt.text(i, v, f\"{v:,}\", ha='center', va='bottom')\n", + "\n", + "# Display the chart\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "e5b6e987-83ba-4262-a082-57c6b0741062", + "metadata": {}, + "source": [ + "# Objective\n", + "\n", + "Craft a dataset which is more balanced in terms of prices. Less heavily scewed to cheap items, with an average that's higher than $60. Try to balance out the categories - fewer Automotive items." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b9424c1-44e0-499a-b45e-a35246655469", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dict with a key of each price from $1 to $999\n", + "# And in the value, put a list of items with that price (to nearest round number)\n", + "\n", + "slots = defaultdict(list)\n", + "for item in items:\n", + " slots[round(item.price)].append(item)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7805a7f1-4ad8-48f6-bea3-d64b64894804", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dataset called \"sample\" which tries to more evenly take from the range of prices\n", + "# And gives more weight to items from categories other than Automotive\n", + "# Set random seed for reproducibility\n", + "\n", + "np.random.seed(42)\n", + "random.seed(42)\n", + "sample = []\n", + "for i in range(1, 1000):\n", + " slot = slots[i]\n", + " if i>=240:\n", + " sample.extend(slot)\n", + " elif len(slot) <= 1200:\n", + " sample.extend(slot)\n", + " else:\n", + " weights = np.array([1 if item.category=='Automotive' else 5 for item in slot])\n", + " weights = weights / np.sum(weights)\n", + " selected_indices = np.random.choice(len(slot), size=1200, replace=False, p=weights)\n", + " selected = [slot[i] for i in selected_indices]\n", + " sample.extend(selected)\n", + "\n", + "print(f\"There are {len(sample):,} items in the sample\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "430b432f-b769-41da-9506-a238cb5cf1b6", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the distribution of prices in sample\n", + "\n", + "prices = [float(item.price) for item in sample]\n", + "plt.figure(figsize=(15, 10))\n", + "plt.title(f\"Avg {sum(prices)/len(prices):.2f} and highest {max(prices):,.2f}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"darkblue\", bins=range(0, 1000, 10))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d570794-6f1d-462e-b567-a46bae3556a1", + "metadata": {}, + "outputs": [], + "source": [ + "# OK, we did well in terms of raising the average price and having a smooth-ish population of prices\n", + "# Let's see the categories\n", + "\n", + "category_counts = Counter()\n", + "for item in sample:\n", + " category_counts[item.category]+=1\n", + "\n", + "categories = category_counts.keys()\n", + "counts = [category_counts[category] for category in categories]\n", + "\n", + "# Create bar chart\n", + "plt.figure(figsize=(15, 6))\n", + "plt.bar(categories, counts, color=\"lightgreen\")\n", + "\n", + "# Customize the chart\n", + "plt.title('How many in each category')\n", + "plt.xlabel('Categories')\n", + "plt.ylabel('Count')\n", + "\n", + "plt.xticks(rotation=30, ha='right')\n", + "\n", + "# Add value labels on top of each bar\n", + "for i, v in enumerate(counts):\n", + " plt.text(i, v, f\"{v:,}\", ha='center', va='bottom')\n", + "\n", + "# Display the chart\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6609d77c-3e0a-4679-9129-c7cdc3273070", + "metadata": {}, + "outputs": [], + "source": [ + "# Automotive still in the lead, but improved somewhat\n", + "# For another perspective, let's look at a pie\n", + "\n", + "plt.figure(figsize=(12, 10))\n", + "plt.pie(counts, labels=categories, autopct='%1.0f%%', startangle=90)\n", + "\n", + "# Add a circle at the center to create a donut chart (optional)\n", + "centre_circle = plt.Circle((0,0), 0.70, fc='white')\n", + "fig = plt.gcf()\n", + "fig.gca().add_artist(centre_circle)\n", + "plt.title('Categories')\n", + "\n", + "# Equal aspect ratio ensures that pie is drawn as a circle\n", + "plt.axis('equal') \n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ac046cc1-2717-415b-96ad-b73b2950d235", + "metadata": {}, + "source": [ + "# Dataset Curated!\n", + "\n", + "We've crafted an excellent dataset.\n", + "\n", + "Let's do some final checks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70219e99-22cc-4e08-9121-51f9707caef0", + "metadata": {}, + "outputs": [], + "source": [ + "# How does the price vary with the character count of the prompt?\n", + "\n", + "sizes = [len(item.prompt) for item in sample]\n", + "prices = [item.price for item in sample]\n", + "\n", + "# Create the scatter plot\n", + "plt.figure(figsize=(15, 8))\n", + "plt.scatter(sizes, prices, s=0.2, color=\"red\")\n", + "\n", + "# Add labels and title\n", + "plt.xlabel('Size')\n", + "plt.ylabel('Price')\n", + "plt.title('Is there a simple correlation?')\n", + "\n", + "# Display the plot\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30ae1453-b9fc-40db-8310-65d850c4b1da", + "metadata": {}, + "outputs": [], + "source": [ + "def report(item):\n", + " prompt = item.prompt\n", + " tokens = Item.tokenizer.encode(item.prompt)\n", + " print(prompt)\n", + " print(tokens[-10:])\n", + " print(Item.tokenizer.batch_decode(tokens[-10:]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9998b8d-d746-4541-9ac2-701108e0e8fb", + "metadata": {}, + "outputs": [], + "source": [ + "report(sample[398000])" + ] + }, + { + "cell_type": "markdown", + "id": "7aa0a3fc-d2fe-4e6e-8fdb-96913df2f588", + "metadata": {}, + "source": [ + "## Observation\n", + "\n", + "An interesting thing about the Llama tokenizer is that every number from 1 to 999 gets mapped to 1 token, much as we saw with gpt-4o. The same is not true of qwen2, gemma and phi3, which all map individual digits to tokens. This does turn out to be a bit useful for our project, although it's not an essential requirement." + ] + }, + { + "cell_type": "markdown", + "id": "0f03c0ee-3103-4603-af5c-b484884a3aa2", + "metadata": {}, + "source": [ + "# Finally\n", + "\n", + "It's time to break down our data into a training, test and validation dataset.\n", + "\n", + "It's typical to use 5%-10% of your data for testing purposes, but actually we have far more than we need at this point. We'll take 400,000 points for training, and we'll reserve 2,000 for testing, although we won't use all of them.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b163ca2-18ef-4c26-8e9d-88eb55f114f6", + "metadata": {}, + "outputs": [], + "source": [ + "random.seed(42)\n", + "random.shuffle(sample)\n", + "train = sample[:400_000]\n", + "test = sample[400_000:402_000]\n", + "print(f\"Divided into a training set of {len(train):,} items and test set of {len(test):,} items\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "299b9816-8885-4798-829a-69d66d60eb01", + "metadata": {}, + "outputs": [], + "source": [ + "print(train[0].prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97222da3-9f2c-4d15-a5cd-5e5f8dbde6cc", + "metadata": {}, + "outputs": [], + "source": [ + "print(test[0].test_prompt())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a116369-335a-412b-b70c-2add6675c2e3", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the distribution of prices in the first 250 test points\n", + "\n", + "prices = [float(item.price) for item in test[:250]]\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Avg {sum(prices)/len(prices):.2f} and highest {max(prices):,.2f}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"darkblue\", bins=range(0, 1000, 10))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d522d752-6f66-4786-a4dc-8ef51842558c", + "metadata": {}, + "source": [ + "# Finally - upload your brand new dataset\n", + "\n", + "Convert to prompts and upload to HuggingFace hub" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa11b3e5-fcf4-4efc-a573-f6f67fec3e73", + "metadata": {}, + "outputs": [], + "source": [ + "train_prompts = [item.prompt for item in train]\n", + "train_prices = [item.price for item in train]\n", + "test_prompts = [item.test_prompt() for item in test]\n", + "test_prices = [item.price for item in test]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b020ab1b-7153-4e5f-b8a3-d5bc2fafb6df", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a Dataset from the lists\n", + "\n", + "train_dataset = Dataset.from_dict({\"text\": train_prompts, \"price\": train_prices})\n", + "test_dataset = Dataset.from_dict({\"text\": test_prompts, \"price\": test_prices})\n", + "dataset = DatasetDict({\n", + " \"train\": train_dataset,\n", + " \"test\": test_dataset\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17639641-fb55-44e2-a463-b0b394d00f32", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment these lines if you're ready to push to the hub, and replace my name with your HF username\n", + "\n", + "# HF_USER = \"ed-donner\"\n", + "# DATASET_NAME = f\"{HF_USER}/pricer-data\"\n", + "# dataset.push_to_hub(DATASET_NAME, private=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b85733ba-d165-4f07-b055-46803543edfe", + "metadata": {}, + "outputs": [], + "source": [ + "# One more thing!\n", + "# Let's pickle the training and test dataset so we don't have to execute all this code next time!\n", + "\n", + "with open('train.pkl', 'wb') as file:\n", + " pickle.dump(train, file)\n", + "\n", + "with open('test.pkl', 'wb') as file:\n", + " pickle.dump(test, file)" + ] + }, + { + "cell_type": "markdown", + "id": "2b58dc61-747f-46f7-b9e0-c205db4f3e5e", + "metadata": {}, + "source": [ + "## Todos for you:\n", + "\n", + "- Investigate the dataset more!\n", + "- Confirm that the tokenizer tokenizes all 3 digit prices into 1 token" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/week6/community-contributions/day5-improved.ipynb b/week6/community-contributions/day5-improved.ipynb new file mode 100644 index 0000000..152abaa --- /dev/null +++ b/week6/community-contributions/day5-improved.ipynb @@ -0,0 +1,1097 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "db8736a7-ed94-441c-9556-831fa57b5a10", + "metadata": {}, + "source": [ + "# The Product Pricer Continued\n", + "\n", + "A model that can estimate how much something costs, from its description.\n", + "\n", + "## AT LAST - it's time for Fine Tuning!\n", + "\n", + "After all this data preparation, and old school machine learning, we've finally arrived at the moment you've been waiting for. Fine-tuning a model." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "681c717b-4c24-4ac3-a5f3-3c5881d6e70a", + "metadata": {}, + "outputs": [], + "source": [ + "# imports\n", + "\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", + "import numpy as np\n", + "import pickle\n", + "from collections import Counter\n", + "from openai import OpenAI\n", + "from anthropic import Anthropic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "36d05bdc-0155-4c72-a7ee-aa4e614ffd3c", + "metadata": {}, + "outputs": [], + "source": [ + "# environment\n", + "\n", + "load_dotenv(override=True)\n", + "os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env')\n", + "os.environ['ANTHROPIC_API_KEY'] = os.getenv('ANTHROPIC_API_KEY', 'your-key-if-not-using-env')\n", + "os.environ['HF_TOKEN'] = os.getenv('HF_TOKEN', 'your-key-if-not-using-env')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4dd3aad2-6f99-433c-8792-e461d2f06622", + "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": [ + "# Log in to HuggingFace\n", + "\n", + "hf_token = os.environ['HF_TOKEN']\n", + "login(hf_token, add_to_git_credential=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "884a50bd-8cae-425e-8e56-f079fc3e65ce", + "metadata": {}, + "outputs": [], + "source": [ + "# moved our Tester into a separate package\n", + "# call it with Tester.test(function_name, test_dataset)\n", + "\n", + "from items import Item\n", + "from testing import Tester" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b0a6fb86-74a4-403c-ab25-6db2d74e9d2b", + "metadata": {}, + "outputs": [], + "source": [ + "openai = OpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c830ed3e-24ee-4af6-a07b-a1bfdcd39278", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5c9b05f4-c9eb-462c-8d86-de9140a2d985", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's avoid curating all our data again! Load in the pickle files:\n", + "\n", + "with open('train.pkl', 'rb') as file:\n", + " train = pickle.load(file)\n", + "\n", + "with open('test.pkl', 'rb') as file:\n", + " test = pickle.load(file)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e8367135-f40e-43e1-8f3c-09e990ab1194", + "metadata": {}, + "outputs": [], + "source": [ + "# OpenAI recommends fine-tuning with populations of 50-100 examples\n", + "# But as our examples are very small, I'm suggesting we go with 200 examples (and 1 epoch)\n", + "\n", + "fine_tune_train = train[:200]\n", + "fine_tune_validation = train[200:250]" + ] + }, + { + "cell_type": "markdown", + "id": "8be4a889-81c3-42b1-a2fc-034cdc7321a6", + "metadata": {}, + "source": [ + "# Step 1\n", + "\n", + "Prepare our data for fine-tuning in JSONL (JSON Lines) format and upload to OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ae2fb3c-1cff-4ce3-911e-627c970edd7b", + "metadata": {}, + "outputs": [], + "source": [ + "# # First let's work on a good prompt for a Frontier model\n", + "# # Notice that I'm removing the \" to the nearest dollar\"\n", + "# # When we train our own models, we'll need to make the problem as easy as possible, \n", + "# # but a Frontier model needs no such simplification.\n", + "\n", + "# def messages_for(item):\n", + "# system_message = \"You estimate prices of items. Reply only with the price, no explanation\"\n", + "# user_prompt = item.test_prompt().replace(\" to the nearest dollar\",\"\").replace(\"\\n\\nPrice is $\",\"\")\n", + "# return [\n", + "# {\"role\": \"system\", \"content\": system_message},\n", + "# {\"role\": \"user\", \"content\": user_prompt},\n", + "# {\"role\": \"assistant\", \"content\": f\"Price is ${item.price:.2f}\"}\n", + "# ]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ca3c0910-1919-47f8-8800-b12be4e983e9", + "metadata": {}, + "outputs": [], + "source": [ + "def messages_for(item):\n", + " system_message = \"\"\"You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\n", + "\n", + "Key Amazon pricing factors to evaluate:\n", + "- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\n", + "- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\n", + "- Pack size and quantity (bulk/multi-packs often better per-unit value)\n", + "- Prime eligibility indicators and fulfillment method signals\n", + "- Product variations (color, size, model) affecting price tiers\n", + "- Feature density and specification richness\n", + "- Amazon's Choice or bestseller indicators in description\n", + "- Customer rating implications (4.5+ stars = premium pricing power)\n", + "- Seasonal/trending product indicators\n", + "\n", + "Amazon-specific pricing patterns:\n", + "- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\n", + "- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\n", + "- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\n", + "- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\n", + "- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\n", + "- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\n", + "\n", + "Consider Amazon's psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\n", + "\n", + "Output format: Respond with only the price including dollar sign and cents (e.g., \"$24.99\"). No explanations or additional text.\"\"\"\n", + " \n", + " user_prompt = item.test_prompt().replace(\" to the nearest dollar\",\"\").replace(\"\\n\\nPrice is $\",\"\")\n", + " \n", + " return [\n", + " {\"role\": \"system\", \"content\": system_message},\n", + " {\"role\": \"user\", \"content\": user_prompt},\n", + " {\"role\": \"assistant\", \"content\": f\"${item.price:.2f}\"}\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "1aa280f6-1227-426a-a2e2-1ce985feba1e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'role': 'system',\n", + " 'content': 'You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\\n\\nKey Amazon pricing factors to evaluate:\\n- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\\n- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\\n- Pack size and quantity (bulk/multi-packs often better per-unit value)\\n- Prime eligibility indicators and fulfillment method signals\\n- Product variations (color, size, model) affecting price tiers\\n- Feature density and specification richness\\n- Amazon\\'s Choice or bestseller indicators in description\\n- Customer rating implications (4.5+ stars = premium pricing power)\\n- Seasonal/trending product indicators\\n\\nAmazon-specific pricing patterns:\\n- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\\n- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\\n- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\\n- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\\n- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\\n- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\\n\\nConsider Amazon\\'s psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\\n\\nOutput format: Respond with only the price including dollar sign and cents (e.g., \"$24.99\"). No explanations or additional text.'},\n", + " {'role': 'user',\n", + " 'content': 'How much does this cost?\\n\\nDelphi FG0166 Fuel Pump Module\\nDelphi brings 80 years of OE Heritage into each Delphi pump, ensuring quality and fitment for each Delphi part. Part is validated, tested and matched to the right vehicle application Delphi brings 80 years of OE Heritage into each Delphi assembly, ensuring quality and fitment for each Delphi part Always be sure to check and clean fuel tank to avoid unnecessary returns Rigorous OE-testing ensures the pump can withstand extreme temperatures Brand Delphi, Fit Type Vehicle Specific Fit, Dimensions LxWxH 19.7 x 7.7 x 5.1 inches, Weight 2.2 Pounds, Auto Part Position Unknown, Operation Mode Mechanical, Manufacturer Delphi, Model FUEL PUMP, Dimensions 19.7'},\n", + " {'role': 'assistant', 'content': '$226.95'}]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "messages_for(train[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c0e5b56c-8a0b-4d8e-a112-ce87efb4e152", + "metadata": {}, + "outputs": [], + "source": [ + "# Convert the items into a list of json objects - a \"jsonl\" string\n", + "# Each row represents a message in the form:\n", + "# {\"messages\" : [{\"role\": \"system\", \"content\": \"You estimate prices...\n", + "\n", + "\n", + "def make_jsonl(items):\n", + " result = \"\"\n", + " for item in items:\n", + " messages = messages_for(item)\n", + " messages_str = json.dumps(messages)\n", + " result += '{\"messages\": ' + messages_str +'}\\n'\n", + " return result.strip()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5e72de93-a6a6-4b35-855e-15786b97bf5f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"messages\": [{\"role\": \"system\", \"content\": \"You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\\n\\nKey Amazon pricing factors to evaluate:\\n- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\\n- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\\n- Pack size and quantity (bulk/multi-packs often better per-unit value)\\n- Prime eligibility indicators and fulfillment method signals\\n- Product variations (color, size, model) affecting price tiers\\n- Feature density and specification richness\\n- Amazon's Choice or bestseller indicators in description\\n- Customer rating implications (4.5+ stars = premium pricing power)\\n- Seasonal/trending product indicators\\n\\nAmazon-specific pricing patterns:\\n- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\\n- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\\n- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\\n- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\\n- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\\n- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\\n\\nConsider Amazon's psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\\n\\nOutput format: Respond with only the price including dollar sign and cents (e.g., \\\"$24.99\\\"). No explanations or additional text.\"}, {\"role\": \"user\", \"content\": \"How much does this cost?\\n\\nDelphi FG0166 Fuel Pump Module\\nDelphi brings 80 years of OE Heritage into each Delphi pump, ensuring quality and fitment for each Delphi part. Part is validated, tested and matched to the right vehicle application Delphi brings 80 years of OE Heritage into each Delphi assembly, ensuring quality and fitment for each Delphi part Always be sure to check and clean fuel tank to avoid unnecessary returns Rigorous OE-testing ensures the pump can withstand extreme temperatures Brand Delphi, Fit Type Vehicle Specific Fit, Dimensions LxWxH 19.7 x 7.7 x 5.1 inches, Weight 2.2 Pounds, Auto Part Position Unknown, Operation Mode Mechanical, Manufacturer Delphi, Model FUEL PUMP, Dimensions 19.7\"}, {\"role\": \"assistant\", \"content\": \"$226.95\"}]}\n", + "{\"messages\": [{\"role\": \"system\", \"content\": \"You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\\n\\nKey Amazon pricing factors to evaluate:\\n- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\\n- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\\n- Pack size and quantity (bulk/multi-packs often better per-unit value)\\n- Prime eligibility indicators and fulfillment method signals\\n- Product variations (color, size, model) affecting price tiers\\n- Feature density and specification richness\\n- Amazon's Choice or bestseller indicators in description\\n- Customer rating implications (4.5+ stars = premium pricing power)\\n- Seasonal/trending product indicators\\n\\nAmazon-specific pricing patterns:\\n- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\\n- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\\n- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\\n- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\\n- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\\n- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\\n\\nConsider Amazon's psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\\n\\nOutput format: Respond with only the price including dollar sign and cents (e.g., \\\"$24.99\\\"). No explanations or additional text.\"}, {\"role\": \"user\", \"content\": \"How much does this cost?\\n\\nPower Stop Rear Z36 Truck and Tow Brake Kit with Calipers\\nThe Power Stop Z36 Truck & Tow Performance brake kit provides the superior stopping power demanded by those who tow boats, haul loads, tackle mountains, lift trucks, and play in the harshest conditions. The brake rotors are drilled to keep temperatures down during extreme braking and slotted to sweep away any debris for constant pad contact. Combined with our Z36 Carbon-Fiber Ceramic performance friction formulation, you can confidently push your rig to the limit and look good doing it with red powder brake calipers. Components are engineered to handle the stress of towing, hauling, mountainous driving, and lifted trucks. Dust-free braking performance. Z36 Carbon-Fiber Ceramic formula provides the extreme braking performance demanded by your truck or 4x\"}, {\"role\": \"assistant\", \"content\": \"$506.98\"}]}\n", + "{\"messages\": [{\"role\": \"system\", \"content\": \"You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\\n\\nKey Amazon pricing factors to evaluate:\\n- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\\n- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\\n- Pack size and quantity (bulk/multi-packs often better per-unit value)\\n- Prime eligibility indicators and fulfillment method signals\\n- Product variations (color, size, model) affecting price tiers\\n- Feature density and specification richness\\n- Amazon's Choice or bestseller indicators in description\\n- Customer rating implications (4.5+ stars = premium pricing power)\\n- Seasonal/trending product indicators\\n\\nAmazon-specific pricing patterns:\\n- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\\n- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\\n- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\\n- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\\n- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\\n- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\\n\\nConsider Amazon's psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\\n\\nOutput format: Respond with only the price including dollar sign and cents (e.g., \\\"$24.99\\\"). No explanations or additional text.\"}, {\"role\": \"user\", \"content\": \"How much does this cost?\\n\\nABBA 36 Gas Cooktop with 5 Sealed Burners - Tempered Glass Surface with SABAF Burners, Natural Gas Stove for Countertop, Home Improvement Essentials, Easy to Clean, 36 x 4.1 x 20.5\\ncooktop Gas powered with 4 fast burners and 1 ultra-fast center burner Tempered glass surface with removable grid for easy cleaning Lightweight for easy installation. Installation Manual Included Counter cutout Dimensions 19 3/8 x 34 1/2 (see diagram) Insured shipping for your satisfaction and peace of mind Brand Name ABBA EST. 1956, Weight 30 pounds, Dimensions 20.5\\\\ D x 36\\\\ W x 4.1\\\\ H, Installation Type Count\"}, {\"role\": \"assistant\", \"content\": \"$405.00\"}]}\n" + ] + } + ], + "source": [ + "print(make_jsonl(train[:3]))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7734bff0-95c4-4e67-a87e-7e2254e2c67d", + "metadata": {}, + "outputs": [], + "source": [ + "# Convert the items into jsonl and write them to a file\n", + "\n", + "def write_jsonl(items, filename):\n", + " with open(filename, \"w\") as f:\n", + " jsonl = make_jsonl(items)\n", + " f.write(jsonl)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "393d3ad8-999a-4f99-8c04-339d9166d604", + "metadata": {}, + "outputs": [], + "source": [ + "write_jsonl(fine_tune_train, \"fine_tune_train.jsonl\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "8e23927f-d73e-4668-ac20-abe6f14a56cb", + "metadata": {}, + "outputs": [], + "source": [ + "write_jsonl(fine_tune_validation, \"fine_tune_validation.jsonl\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d59ad8d2-c61a-448e-b7ed-232f1606970f", + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"fine_tune_train.jsonl\", \"rb\") as f:\n", + " train_file = openai.files.create(file=f, purpose=\"fine-tune\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "083fefba-fd54-47ce-9ff3-aabbc200846f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FileObject(id='file-8nECKUWKSHTorbKVrcLxGc', bytes=480142, created_at=1753339843, filename='fine_tune_train.jsonl', object='file', purpose='fine-tune', status='processed', expires_at=None, status_details=None)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_file" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "97df3360-0760-4422-a556-5f26d23de6dc", + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"fine_tune_validation.jsonl\", \"rb\") as f:\n", + " validation_file = openai.files.create(file=f, purpose=\"fine-tune\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a1abb8f3-9e52-4061-970c-fcf399d8ffa3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FileObject(id='file-TkMc2SaZVLPmoP37ucXDPH', bytes=119935, created_at=1753339852, filename='fine_tune_validation.jsonl', object='file', purpose='fine-tune', status='processed', expires_at=None, status_details=None)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "validation_file" + ] + }, + { + "cell_type": "markdown", + "id": "466052b9-9fb9-48f6-8cf9-c74e6ddc1394", + "metadata": {}, + "source": [ + "# Step 2\n", + "\n", + "I love Weights and Biases - a beautiful, free platform for monitoring training runs. \n", + "Weights and Biases is integrated with OpenAI for fine-tuning.\n", + "\n", + "First set up your weights & biases free account at:\n", + "\n", + "https://wandb.ai\n", + "\n", + "From the Avatar >> Settings menu, near the bottom, you can create an API key.\n", + "\n", + "Then visit the OpenAI dashboard at:\n", + "\n", + "https://platform.openai.com/account/organization\n", + "\n", + "In the integrations section, you can add your Weights & Biases key.\n", + "\n", + "## And now time to Fine-tune!" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c7add1a7-a746-4d6e-a5f8-e25629b8b527", + "metadata": {}, + "outputs": [], + "source": [ + "wandb_integration = {\"type\": \"wandb\", \"wandb\": {\"project\": \"gpt-pricer\"}}" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "49801e69-9277-4deb-9f33-99efb6b45ac2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'file-8nECKUWKSHTorbKVrcLxGc'" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_file.id" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "45421b86-5531-4e42-ab19-d6abbb8f4c13", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FineTuningJob(id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX', created_at=1753341041, 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-du4k6X6j1Eu0j5xNKaVVIO3O', result_files=[], seed=42, status='validating_files', trained_tokens=None, training_file='file-8nECKUWKSHTorbKVrcLxGc', validation_file='file-TkMc2SaZVLPmoP37ucXDPH', estimated_finish=None, integrations=[FineTuningJobWandbIntegrationObject(type='wandb', wandb=FineTuningJobWandbIntegration(project='gpt-pricer', entity=None, name=None, tags=None, run_id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX'))], 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='pricer', usage_metrics=None, shared_with_openai=False, eval_id=None)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "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,\n", + " hyperparameters={\"n_epochs\": 1},\n", + " integrations = [wandb_integration],\n", + " suffix=\"pricer\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "aeb9de2e-542c-4e83-81c7-b6745133e48b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SyncCursorPage[FineTuningJob](data=[FineTuningJob(id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX', created_at=1753341041, 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-du4k6X6j1Eu0j5xNKaVVIO3O', result_files=[], seed=42, status='validating_files', trained_tokens=None, training_file='file-8nECKUWKSHTorbKVrcLxGc', validation_file='file-TkMc2SaZVLPmoP37ucXDPH', estimated_finish=None, integrations=[FineTuningJobWandbIntegrationObject(type='wandb', wandb=FineTuningJobWandbIntegration(project='gpt-pricer', entity=None, name=None, tags=None, run_id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX'))], 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='pricer', usage_metrics=None, shared_with_openai=False, eval_id=None)], has_more=False, object='list')" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "openai.fine_tuning.jobs.list(limit=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "40d24873-8ff5-413f-b0d4-8f77c28f18e1", + "metadata": {}, + "outputs": [], + "source": [ + "job_id = openai.fine_tuning.jobs.list(limit=1).data[0].id" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "a32aef35-4b38-436c-ad00-d082f758efa7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ftjob-IBBaP9CY5ovNGnsueXoCLjeX'" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job_id" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "a7e01247-c133-48e1-93d3-c79c399e6178", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FineTuningJob(id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX', created_at=1753341041, 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-du4k6X6j1Eu0j5xNKaVVIO3O', result_files=[], seed=42, status='validating_files', trained_tokens=None, training_file='file-8nECKUWKSHTorbKVrcLxGc', validation_file='file-TkMc2SaZVLPmoP37ucXDPH', estimated_finish=None, integrations=[FineTuningJobWandbIntegrationObject(type='wandb', wandb=FineTuningJobWandbIntegration(project='gpt-pricer', entity=None, name=None, tags=None, run_id='ftjob-IBBaP9CY5ovNGnsueXoCLjeX'))], 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='pricer', usage_metrics=None, shared_with_openai=False, eval_id=None)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "openai.fine_tuning.jobs.retrieve(job_id)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "0f5150e1-b8de-485f-8eba-cf1e5b00c117", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[FineTuningJobEvent(id='ftevent-Hen3aW1QhNBeK3fNUmtTnBop', created_at=1753341041, level='info', message='Validating training file: file-8nECKUWKSHTorbKVrcLxGc and validation file: file-TkMc2SaZVLPmoP37ucXDPH', object='fine_tuning.job.event', data={}, type='message'),\n", + " FineTuningJobEvent(id='ftevent-osUdwcFOjzf1HG99p1q4ivBm', created_at=1753341041, level='info', message='Created fine-tuning job: ftjob-IBBaP9CY5ovNGnsueXoCLjeX', object='fine_tuning.job.event', data={}, type='message')]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "openai.fine_tuning.jobs.list_events(fine_tuning_job_id=job_id, limit=10).data" + ] + }, + { + "cell_type": "markdown", + "id": "066fef03-8338-4526-9df3-89b649ad4f0a", + "metadata": {}, + "source": [ + "# Step 3\n", + "\n", + "Test our fine tuned model" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "fa4488cb-3c17-4eda-abd1-53c1c68a491b", + "metadata": {}, + "outputs": [], + "source": [ + "fine_tuned_model_name = openai.fine_tuning.jobs.retrieve(job_id).fine_tuned_model" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "e9370937-5a6f-4724-8265-b208663b4450", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ft:gpt-4o-mini-2024-07-18:aaron:pricer:BwkX85YV'" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fine_tuned_model_name" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "66ea68e8-ab1b-4f0d-aba4-a59574d8f85e", + "metadata": {}, + "outputs": [], + "source": [ + "def messages_for(item):\n", + " system_message = \"\"\"You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\n", + "\n", + "Key Amazon pricing factors to evaluate:\n", + "- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\n", + "- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\n", + "- Pack size and quantity (bulk/multi-packs often better per-unit value)\n", + "- Prime eligibility indicators and fulfillment method signals\n", + "- Product variations (color, size, model) affecting price tiers\n", + "- Feature density and specification richness\n", + "- Amazon's Choice or bestseller indicators in description\n", + "- Customer rating implications (4.5+ stars = premium pricing power)\n", + "- Seasonal/trending product indicators\n", + "\n", + "Amazon-specific pricing patterns:\n", + "- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\n", + "- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\n", + "- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\n", + "- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\n", + "- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\n", + "- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\n", + "\n", + "Consider Amazon's psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\n", + "\n", + "Output format: Respond with only the price including dollar sign and cents (e.g., \"$24.99\"). No explanations or additional text.\"\"\"\n", + " \n", + " user_prompt = item.test_prompt().replace(\" to the nearest dollar\",\"\").replace(\"\\n\\nPrice is $\",\"\")\n", + " \n", + " return [\n", + " {\"role\": \"system\", \"content\": system_message},\n", + " {\"role\": \"user\", \"content\": user_prompt},\n", + " {\"role\": \"assistant\", \"content\": f\"${item.price:.2f}\"}\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "4ff92d61-0d27-4b0d-8b32-c9891016509b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'role': 'system',\n", + " 'content': 'You are an Amazon marketplace pricing expert. Analyze product descriptions to predict accurate Amazon selling prices based on typical marketplace dynamics and consumer behavior patterns.\\n\\nKey Amazon pricing factors to evaluate:\\n- Brand strength (Apple, Samsung, Nike = premium; generic/unbranded = budget)\\n- Product category positioning (Home & Kitchen, Electronics, Sports, Beauty, etc.)\\n- Pack size and quantity (bulk/multi-packs often better per-unit value)\\n- Prime eligibility indicators and fulfillment method signals\\n- Product variations (color, size, model) affecting price tiers\\n- Feature density and specification richness\\n- Amazon\\'s Choice or bestseller indicators in description\\n- Customer rating implications (4.5+ stars = premium pricing power)\\n- Seasonal/trending product indicators\\n\\nAmazon-specific pricing patterns:\\n- Electronics: $10-50 (accessories), $50-200 (mid-tier), $200+ (premium)\\n- Home/Kitchen: $15-40 (small items), $40-150 (appliances), $150+ (major items)\\n- Beauty/Personal Care: $8-25 (drugstore), $25-60 (prestige), $60+ (luxury)\\n- Sports/Outdoors: $20-80 (equipment), $30-120 (apparel), $100+ (specialized gear)\\n- Books/Media: $10-20 (paperback), $15-35 (hardcover), $25-50 (specialty)\\n- Toys/Games: $15-40 (standard), $40-100 (premium/electronic)\\n\\nConsider Amazon\\'s psychological pricing (ends in .99, .95, .49) and competitive marketplace pressure.\\n\\nOutput format: Respond with only the price including dollar sign and cents (e.g., \"$24.99\"). No explanations or additional text.'},\n", + " {'role': 'user',\n", + " 'content': \"How much does this cost?\\n\\nOEM AC Compressor w/A/C Repair Kit For Ford F150 F-150 V8 & Lincoln Mark LT 2007 2008 - BuyAutoParts NEW\\nAs one of the world's largest automotive parts suppliers, our parts are trusted every day by mechanics and vehicle owners worldwide. This A/C Compressor and Components Kit is manufactured and tested to the strictest OE standards for unparalleled performance. Built for trouble-free ownership and 100% visually inspected and quality tested, this A/C Compressor and Components Kit is backed by our 100% satisfaction guarantee. Guaranteed Exact Fit for easy installation 100% BRAND NEW, premium ISO/TS 16949 quality - tested to meet or exceed OEM specifications Engineered for superior durability, backed by industry-leading unlimited-mileage warranty Included in this K\"},\n", + " {'role': 'assistant', 'content': '$374.41'}]" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Try this out\n", + "\n", + "messages_for(test[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "b1af1888-f94a-4106-b0d8-8a70939eec4e", + "metadata": {}, + "outputs": [], + "source": [ + "# A utility function to extract the price from a string\n", + "\n", + "def get_price(s):\n", + " s = s.replace('$','').replace(',','')\n", + " match = re.search(r\"[-+]?\\d*\\.\\d+|\\d+\", s)\n", + " return float(match.group()) if match else 0" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "f138c5b7-bcc1-4085-aced-68dad1bf36b4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "99.99" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_price(\"The price is roughly $99.99 because blah blah\")" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "501a2a7a-69c8-451b-bbc0-398bcb9e1612", + "metadata": {}, + "outputs": [], + "source": [ + "# The function for gpt-4o-mini\n", + "\n", + "def gpt_fine_tuned(item):\n", + " response = openai.chat.completions.create(\n", + " model=fine_tuned_model_name, \n", + " messages=messages_for(item),\n", + " seed=42,\n", + " max_tokens=7\n", + " )\n", + " reply = response.choices[0].message.content\n", + " return get_price(reply)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "843d88b4-364a-431b-b48b-8a7c1f68b786", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "374.41\n", + "400.06\n" + ] + } + ], + "source": [ + "print(test[0].price)\n", + "print(gpt_fine_tuned(test[0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "edd7ada0-15b7-42ec-bbbb-1250e0eb9af1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "How much does this cost to the nearest dollar?\n", + "\n", + "OEM AC Compressor w/A/C Repair Kit For Ford F150 F-150 V8 & Lincoln Mark LT 2007 2008 - BuyAutoParts NEW\n", + "As one of the world's largest automotive parts suppliers, our parts are trusted every day by mechanics and vehicle owners worldwide. This A/C Compressor and Components Kit is manufactured and tested to the strictest OE standards for unparalleled performance. Built for trouble-free ownership and 100% visually inspected and quality tested, this A/C Compressor and Components Kit is backed by our 100% satisfaction guarantee. Guaranteed Exact Fit for easy installation 100% BRAND NEW, premium ISO/TS 16949 quality - tested to meet or exceed OEM specifications Engineered for superior durability, backed by industry-leading unlimited-mileage warranty Included in this K\n", + "\n", + "Price is $\n" + ] + } + ], + "source": [ + "print(test[0].test_prompt())" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "36bdd2c9-1859-4f99-a09f-3ec83b845b30", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing Gpt Fine Tuned on 250 samples...\n", + "Thresholds: Excellent ≤15%, Good ≤30%\n", + "--------------------------------------------------------------------------------\n", + "\u001b[94m1: Guess: $374.41 Truth: $374.41 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: OEM AC Compressor w/A/C Repair Kit For F...\u001b[0m\n", + "\u001b[95m2: Guess: $161.72 Truth: $225.11 Abs Error: $63.39 % Error: 28.2% SLE: 0.11 Item: Motorcraft YB3125 Fan Clutch\u001b[0m\n", + "\u001b[94m3: Guess: $61.68 Truth: $61.68 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dorman 603-159 Front Washer Fluid Reserv...\u001b[0m\n", + "\u001b[94m4: Guess: $599.99 Truth: $599.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: HP Premium 17.3-inch HD Plus Touchscreen...\u001b[0m\n", + "\u001b[94m5: Guess: $16.99 Truth: $16.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: 5-Position Super Switch Pickup Selector ...\u001b[0m\n", + "\u001b[94m6: Guess: $33.00 Truth: $31.99 Abs Error: $1.01 % Error: 3.2% SLE: 0.00 Item: Horror Bookmarks, Resin Horror Bookmarks...\u001b[0m\n", + "\u001b[94m7: Guess: $101.79 Truth: $101.79 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SK6241 - Stinger 4 Gauge 6000 Series Pow...\u001b[0m\n", + "\u001b[94m8: Guess: $289.00 Truth: $289.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Godox ML60Bi LED Light Kit, Handheld LED...\u001b[0m\n", + "\u001b[94m9: Guess: $629.99 Truth: $635.86 Abs Error: $5.87 % Error: 0.9% SLE: 0.00 Item: Randall RG75DG3PLUS G3 Plus 100-Watt Com...\u001b[0m\n", + "\u001b[94m10: Guess: $61.99 Truth: $65.99 Abs Error: $4.00 % Error: 6.1% SLE: 0.00 Item: HOLDWILL 6 Pack LED Shop Light, 4FT 24W ...\u001b[0m\n", + "\u001b[94m11: Guess: $228.06 Truth: $254.21 Abs Error: $26.15 % Error: 10.3% SLE: 0.01 Item: Viking Horns V103C/1005ATK 3 Gallon Air ...\u001b[0m\n", + "\u001b[94m12: Guess: $412.99 Truth: $412.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: CURT 70110 Custom Tow Bar Base Plate Bra...\u001b[0m\n", + "\u001b[94m13: Guess: $210.57 Truth: $205.50 Abs Error: $5.07 % Error: 2.5% SLE: 0.00 Item: 10-Pack Solar HAMMERED BRONZE Finish Pos...\u001b[0m\n", + "\u001b[94m14: Guess: $248.23 Truth: $248.23 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: COSTWAY Electric Tumble Dryer, Sliver\u001b[0m\n", + "\u001b[94m15: Guess: $399.00 Truth: $399.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: FREE SIGNAL TV Transit 32\" 12 Volt DC Po...\u001b[0m\n", + "\u001b[94m16: Guess: $371.96 Truth: $373.94 Abs Error: $1.98 % Error: 0.5% SLE: 0.00 Item: Bilstein 5100 Monotube Gas Shock Set com...\u001b[0m\n", + "\u001b[94m17: Guess: $81.99 Truth: $92.89 Abs Error: $10.90 % Error: 11.7% SLE: 0.02 Item: Sangean K-200 Multi-Function Upright AM/...\u001b[0m\n", + "\u001b[94m18: Guess: $51.99 Truth: $51.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Charles Leonard Magnetic Lapboard Class ...\u001b[0m\n", + "\u001b[94m19: Guess: $179.00 Truth: $179.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Gigabyte AMD Radeon HD 7870 2 GB GDDR5 D...\u001b[0m\n", + "\u001b[94m20: Guess: $19.00 Truth: $19.42 Abs Error: $0.42 % Error: 2.2% SLE: 0.00 Item: 3dRose LLC 8 x 8 x 0.25 Inches Bull Terr...\u001b[0m\n", + "\u001b[94m21: Guess: $539.95 Truth: $539.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ROKINON 85mm F1.4 Auto Focus Full Frame ...\u001b[0m\n", + "\u001b[94m22: Guess: $147.67 Truth: $147.67 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: AUTOSAVER88 Headlight Assembly Compatibl...\u001b[0m\n", + "\u001b[91m23: Guess: $47.97 Truth: $24.99 Abs Error: $22.98 % Error: 92.0% SLE: 0.40 Item: ASI NAUTICAL 2.5 Inches Opera Glasses Bi...\u001b[0m\n", + "\u001b[94m24: Guess: $149.00 Truth: $149.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Behringer TUBE OVERDRIVE TO100 Authentic...\u001b[0m\n", + "\u001b[94m25: Guess: $16.99 Truth: $16.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Fun Express Insect Finger Puppets - 24 f...\u001b[0m\n", + "\u001b[94m26: Guess: $7.99 Truth: $7.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: WAFJAMF Roller Stamp Identity Theft Stam...\u001b[0m\n", + "\u001b[94m27: Guess: $199.99 Truth: $199.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Capulina Tiffany Floor Lamp 2-Light 16\" ...\u001b[0m\n", + "\u001b[94m28: Guess: $251.45 Truth: $251.45 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Apple Watch Series 6 (GPS, 44mm) - Space...\u001b[0m\n", + "\u001b[94m29: Guess: $231.62 Truth: $231.62 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ICON 01725 Tandem Axle Fender Skirt FS17...\u001b[0m\n", + "\u001b[94m30: Guess: $135.00 Truth: $135.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SanDisk 128GB Ultra (10 Pack) MicroSD Cl...\u001b[0m\n", + "\u001b[94m31: Guess: $356.62 Truth: $356.62 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Velvac 2020,L,C/Hr,W,E2003,102\",Bk - 715...\u001b[0m\n", + "\u001b[94m32: Guess: $257.99 Truth: $257.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: TCMT Passenger Backrest Sissy Bar & Lugg...\u001b[0m\n", + "\u001b[94m33: Guess: $27.99 Truth: $27.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Alnicov 63.5MM Brass Tremolo Block,Tremo...\u001b[0m\n", + "\u001b[94m34: Guess: $171.20 Truth: $171.20 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Subaru Forester Outback Legacy OEM Engin...\u001b[0m\n", + "\u001b[94m35: Guess: $225.00 Truth: $225.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Richmond Auto Upholstery - 2012 Dodge Ra...\u001b[0m\n", + "\u001b[94m36: Guess: $105.00 Truth: $105.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: AP-39 Automotive Paint Primer Grey 2K Ur...\u001b[0m\n", + "\u001b[94m37: Guess: $299.99 Truth: $299.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Road Top Wireless Carplay Retrofit Kit D...\u001b[0m\n", + "\u001b[94m38: Guess: $535.09 Truth: $535.09 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Gibson Performance Exhaust 5658 Aluminiz...\u001b[0m\n", + "\u001b[94m39: Guess: $12.33 Truth: $12.33 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Bella Tunno Happy Links - Baby Montessor...\u001b[0m\n", + "\u001b[94m40: Guess: $81.99 Truth: $84.99 Abs Error: $3.00 % Error: 3.5% SLE: 0.00 Item: CANMORE H300 Handheld GPS Golf Device, S...\u001b[0m\n", + "\u001b[94m41: Guess: $15.99 Truth: $15.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: DCPOWER AC Adapter Compatible Replacemen...\u001b[0m\n", + "\u001b[94m42: Guess: $62.44 Truth: $62.44 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Sharp, VX2128V, Commercial Desktop Calcu...\u001b[0m\n", + "\u001b[94m43: Guess: $82.99 Truth: $82.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Melissa & Doug Lifelike Plush Stork Gian...\u001b[0m\n", + "\u001b[94m44: Guess: $599.95 Truth: $599.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Sony SSCS8 2-Way 3-Driver Center Channel...\u001b[0m\n", + "\u001b[94m45: Guess: $184.99 Truth: $194.99 Abs Error: $10.00 % Error: 5.1% SLE: 0.00 Item: ASUS Chromebook CX1, 14\" Full HD NanoEdg...\u001b[0m\n", + "\u001b[94m46: Guess: $344.99 Truth: $344.95 Abs Error: $0.04 % Error: 0.0% SLE: 0.00 Item: FiiO X7 32GB Hi-Res Lossless Music Playe...\u001b[0m\n", + "\u001b[94m47: Guess: $37.99 Truth: $37.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: TORRO Leather Case Compatible with iPhon...\u001b[0m\n", + "\u001b[94m48: Guess: $228.22 Truth: $224.35 Abs Error: $3.87 % Error: 1.7% SLE: 0.00 Item: Universal Air Conditioner KT 1031 A/C Co...\u001b[0m\n", + "\u001b[94m49: Guess: $814.00 Truth: $814.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Street Series Stainless Performance Cat-...\u001b[0m\n", + "\u001b[94m50: Guess: $399.99 Truth: $439.88 Abs Error: $39.89 % Error: 9.1% SLE: 0.01 Item: Lenovo IdeaPad 3 14-inch Laptop, 14.0-in...\u001b[0m\n", + "\u001b[94m51: Guess: $341.43 Truth: $341.43 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Access Bed Covers TonnoSport 22050219 - ...\u001b[0m\n", + "\u001b[94m52: Guess: $46.78 Truth: $46.78 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: G.I. JOE Hasbro 3 3/4\" Wave 5 Action Fig...\u001b[0m\n", + "\u001b[94m53: Guess: $171.44 Truth: $171.44 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: T&S Brass B-0232-BST Double Pantry Fauce...\u001b[0m\n", + "\u001b[94m54: Guess: $458.00 Truth: $458.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ZTUOAUMA Fuel Injection Pump 3090942 309...\u001b[0m\n", + "\u001b[94m55: Guess: $130.00 Truth: $130.75 Abs Error: $0.75 % Error: 0.6% SLE: 0.00 Item: 2AP18AA#ABA Hp Prime Graphing Calculator...\u001b[0m\n", + "\u001b[94m56: Guess: $81.73 Truth: $83.81 Abs Error: $2.08 % Error: 2.5% SLE: 0.00 Item: Lowrance 000-0119-83 Nmea 2000 25' Exten...\u001b[0m\n", + "\u001b[91m57: Guess: $47.97 Truth: $386.39 Abs Error: $338.42 % Error: 87.6% SLE: 4.28 Item: Jeep Genuine Accessories 82213051 Hood L...\u001b[0m\n", + "\u001b[94m58: Guess: $169.00 Truth: $169.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: GODOX CB-06 Hard Carrying Case with Whee...\u001b[0m\n", + "\u001b[94m59: Guess: $17.95 Truth: $17.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Au-Tomotive Gold, INC. Ford Black Valet ...\u001b[0m\n", + "\u001b[94m60: Guess: $269.00 Truth: $269.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Snailfly Black Roof Rack Rail + Cross Ba...\u001b[0m\n", + "\u001b[94m61: Guess: $77.77 Truth: $77.77 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: KING SHA Anti Glare LED Track Lighting H...\u001b[0m\n", + "\u001b[94m62: Guess: $81.99 Truth: $88.99 Abs Error: $7.00 % Error: 7.9% SLE: 0.01 Item: APS Compatible with Chevy Silverado 1500...\u001b[0m\n", + "\u001b[94m63: Guess: $364.41 Truth: $364.41 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Wilwood Engineering 14011291R Brake Cali...\u001b[0m\n", + "\u001b[94m64: Guess: $127.03 Truth: $127.03 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ACDelco Gold 336-1925A Starter, Remanufa...\u001b[0m\n", + "\u001b[94m65: Guess: $778.95 Truth: $778.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: UWS EC10783 69-Inch Matte Black Heavy-Wa...\u001b[0m\n", + "\u001b[94m66: Guess: $206.66 Truth: $206.66 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dell Latitude E5440 14in Business Laptop...\u001b[0m\n", + "\u001b[94m67: Guess: $35.94 Truth: $35.94 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: (Plug and Play) Spare Tire Brake Light W...\u001b[0m\n", + "\u001b[94m68: Guess: $149.00 Truth: $149.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: The Ultimate Roadside Rescue Assistant\u001b[0m\n", + "\u001b[94m69: Guess: $251.98 Truth: $251.98 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Brand New 18\" x 8.5\" Replacement Wheel f...\u001b[0m\n", + "\u001b[94m70: Guess: $160.00 Truth: $160.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Headlight Headlamp LH Left & RH Right Pa...\u001b[0m\n", + "\u001b[91m71: Guess: $64.90 Truth: $39.99 Abs Error: $24.91 % Error: 62.3% SLE: 0.23 Item: Lilo And Stitch Deluxe Oversize Print La...\u001b[0m\n", + "\u001b[94m72: Guess: $362.41 Truth: $362.41 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: AC Compressor & A/C Clutch For Hyundai A...\u001b[0m\n", + "\u001b[94m73: Guess: $344.00 Truth: $344.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: House Of Troy PIN475-AB Pinnacle Collect...\u001b[0m\n", + "\u001b[94m74: Guess: $22.99 Truth: $25.09 Abs Error: $2.10 % Error: 8.4% SLE: 0.01 Item: Juno T29 WH Floating Electrical Feed Sin...\u001b[0m\n", + "\u001b[94m75: Guess: $175.95 Truth: $175.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Sherman GO-PARTS - for 2013-2016 Toyota ...\u001b[0m\n", + "\u001b[94m76: Guess: $132.64 Truth: $132.64 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Roland RPU-3 Electronic Keyboard Pedal o...\u001b[0m\n", + "\u001b[94m77: Guess: $422.99 Truth: $422.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Rockland VMI14 12,000 Pound 12 Volt DC E...\u001b[0m\n", + "\u001b[94m78: Guess: $146.48 Truth: $146.48 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Max Advanced Brakes Elite XDS Front Cros...\u001b[0m\n", + "\u001b[94m79: Guess: $156.83 Truth: $156.83 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Quality-Built 11030 Premium Quality Alte...\u001b[0m\n", + "\u001b[94m80: Guess: $251.99 Truth: $251.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Lucida LG-510 Student Classical Guitar, ...\u001b[0m\n", + "\u001b[94m81: Guess: $940.33 Truth: $940.33 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Longacre 52-79800 Aluminum Turn Plates\u001b[0m\n", + "\u001b[95m82: Guess: $64.22 Truth: $52.99 Abs Error: $11.23 % Error: 21.2% SLE: 0.04 Item: Motion Pro 08-0380 Adjustable Torque Wre...\u001b[0m\n", + "\u001b[94m83: Guess: $219.99 Truth: $219.95 Abs Error: $0.04 % Error: 0.0% SLE: 0.00 Item: Glyph Thunderbolt 3 NVMe Dock (0 GB)\u001b[0m\n", + "\u001b[94m84: Guess: $441.03 Truth: $441.03 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: TOYO Open Country MT Performance Radial ...\u001b[0m\n", + "\u001b[94m85: Guess: $168.00 Truth: $168.98 Abs Error: $0.98 % Error: 0.6% SLE: 0.00 Item: Razer Seiren X USB Streaming Microphone ...\u001b[0m\n", + "\u001b[95m86: Guess: $3.09 Truth: $2.49 Abs Error: $0.60 % Error: 24.1% SLE: 0.03 Item: Happy Birthday to Dad From Your Daughter...\u001b[0m\n", + "\u001b[94m87: Guess: $97.99 Truth: $98.62 Abs Error: $0.63 % Error: 0.6% SLE: 0.00 Item: Little Tikes My Real Jam First Concert S...\u001b[0m\n", + "\u001b[91m88: Guess: $156.97 Truth: $256.95 Abs Error: $99.98 % Error: 38.9% SLE: 0.24 Item: Studio M Peace and Harmony Art Pole Comm...\u001b[0m\n", + "\u001b[94m89: Guess: $30.99 Truth: $30.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: MyVolts 12V Power Supply Adaptor Compati...\u001b[0m\n", + "\u001b[94m90: Guess: $569.84 Truth: $569.84 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dell Latitude 7212 Rugged Extreme Tablet...\u001b[0m\n", + "\u001b[94m91: Guess: $177.99 Truth: $177.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Covermates Contour Fit Car Cover - Light...\u001b[0m\n", + "\u001b[94m92: Guess: $992.99 Truth: $997.99 Abs Error: $5.00 % Error: 0.5% SLE: 0.00 Item: Westin 57-4025 Black HDX Grille Guard fi...\u001b[0m\n", + "\u001b[94m93: Guess: $219.00 Truth: $219.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Fieldpiece JL2 Job Link Wireless App Tra...\u001b[0m\n", + "\u001b[94m94: Guess: $210.57 Truth: $225.55 Abs Error: $14.98 % Error: 6.6% SLE: 0.00 Item: hansgrohe Talis S Modern Premium Easy Cl...\u001b[0m\n", + "\u001b[94m95: Guess: $495.99 Truth: $495.95 Abs Error: $0.04 % Error: 0.0% SLE: 0.00 Item: G-Technology G-SPEED eS PRO High-Perform...\u001b[0m\n", + "\u001b[94m96: Guess: $942.37 Truth: $942.37 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: DreamLine SHDR-1960723L-01 Shower Door, ...\u001b[0m\n", + "\u001b[94m97: Guess: $1.99 Truth: $1.94 Abs Error: $0.05 % Error: 2.6% SLE: 0.00 Item: Sanctuary Square Backplate Finish: Oiled...\u001b[0m\n", + "\u001b[94m98: Guess: $284.34 Truth: $284.34 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Pelican Protector 1750 Long Case - Multi...\u001b[0m\n", + "\u001b[94m99: Guess: $171.90 Truth: $171.90 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Brock Replacement Driver and Passenger H...\u001b[0m\n", + "\u001b[95m100: Guess: $169.97 Truth: $144.99 Abs Error: $24.98 % Error: 17.2% SLE: 0.02 Item: Carlinkit Ai Box Mini, Android 11, Multi...\u001b[0m\n", + "\u001b[94m101: Guess: $470.47 Truth: $470.47 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: StarDot NetCamLIVE2 YouTube Live Stream ...\u001b[0m\n", + "\u001b[94m102: Guess: $66.95 Truth: $66.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Atomic Compatible FILXXCAR0016 16x25x5 M...\u001b[0m\n", + "\u001b[94m103: Guess: $130.97 Truth: $117.00 Abs Error: $13.97 % Error: 11.9% SLE: 0.01 Item: Bandai Awakening of S. H. s.h.figuarts s...\u001b[0m\n", + "\u001b[94m104: Guess: $172.14 Truth: $172.14 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Fit System 62135G Passenger Side Towing ...\u001b[0m\n", + "\u001b[94m105: Guess: $392.74 Truth: $392.74 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Black Horse Black Aluminum Exceed Runnin...\u001b[0m\n", + "\u001b[94m106: Guess: $16.99 Truth: $16.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dearsun Twinkle Star Color Night Light P...\u001b[0m\n", + "\u001b[91m107: Guess: $1.99 Truth: $1.34 Abs Error: $0.65 % Error: 48.5% SLE: 0.06 Item: Pokemon - Gallade Spirit Link (83/108) -...\u001b[0m\n", + "\u001b[94m108: Guess: $349.00 Truth: $349.98 Abs Error: $0.98 % Error: 0.3% SLE: 0.00 Item: Ibanez GA34STCE-NT GIO Series Classical ...\u001b[0m\n", + "\u001b[94m109: Guess: $370.71 Truth: $370.71 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Set 2 Heavy Duty 12-16.5 12x16.5 12 Ply ...\u001b[0m\n", + "\u001b[94m110: Guess: $57.99 Truth: $65.88 Abs Error: $7.89 % Error: 12.0% SLE: 0.02 Item: Hairpin Table Legs 28\" Heavy Duty Hairpi...\u001b[0m\n", + "\u001b[94m111: Guess: $229.99 Truth: $229.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Marada Racing Seat with Adjustable Slide...\u001b[0m\n", + "\u001b[91m112: Guess: $22.57 Truth: $9.14 Abs Error: $13.43 % Error: 146.9% SLE: 0.71 Item: Remington Industries 24UL1007STRWHI25 24...\u001b[0m\n", + "\u001b[94m113: Guess: $199.00 Truth: $199.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Acer S3-391-6046 13.3-inch Ultrabook, In...\u001b[0m\n", + "\u001b[94m114: Guess: $109.99 Truth: $109.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ICBEAMER 7\" RGB LED Headlights Bulb Halo...\u001b[0m\n", + "\u001b[94m115: Guess: $570.42 Truth: $570.42 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: R1 Concepts Front Rear Brakes and Rotors...\u001b[0m\n", + "\u001b[94m116: Guess: $279.99 Truth: $279.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Camplux 2.64 GPM Tankless , Outdoor Port...\u001b[0m\n", + "\u001b[94m117: Guess: $30.99 Truth: $30.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: KNOKLOCK 10 Pack 3.75 Inch(96mm) Kitchen...\u001b[0m\n", + "\u001b[94m118: Guess: $31.99 Truth: $31.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Valley Enterprises Yaesu USB FTDI CT-62 ...\u001b[0m\n", + "\u001b[94m119: Guess: $15.90 Truth: $15.90 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: G9 LED Light Bulbs,8W,75W 100W replaceme...\u001b[0m\n", + "\u001b[94m120: Guess: $45.99 Truth: $45.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ZCHAOZ 4 Lights Antique White Farmhouse ...\u001b[0m\n", + "\u001b[94m121: Guess: $113.52 Truth: $113.52 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Honeywell TH8320R1003 Honeywell VisionPr...\u001b[0m\n", + "\u001b[94m122: Guess: $516.99 Truth: $516.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Patriot Exhaust H8013-1 1-7/8\" Clippster...\u001b[0m\n", + "\u001b[95m123: Guess: $166.22 Truth: $196.99 Abs Error: $30.77 % Error: 15.6% SLE: 0.03 Item: Fitrite Autopart New Front Left Driver S...\u001b[0m\n", + "\u001b[94m124: Guess: $46.99 Truth: $46.55 Abs Error: $0.44 % Error: 0.9% SLE: 0.00 Item: Technical Precision Replacement for GE G...\u001b[0m\n", + "\u001b[94m125: Guess: $356.99 Truth: $356.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Covercraft Carhartt SeatSaver Front Row ...\u001b[0m\n", + "\u001b[94m126: Guess: $319.95 Truth: $319.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Sennheiser SD Pro 2 (506008) - Double-Si...\u001b[0m\n", + "\u001b[94m127: Guess: $96.06 Truth: $96.06 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Hitachi MAF0110 Mass Air Flow Sensor\u001b[0m\n", + "\u001b[94m128: Guess: $190.99 Truth: $190.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: AmScope SE305R-P-LED-PS36A 10X-30X LED C...\u001b[0m\n", + "\u001b[94m129: Guess: $257.99 Truth: $257.95 Abs Error: $0.04 % Error: 0.0% SLE: 0.00 Item: Front Left Driver Side Window Regulator ...\u001b[0m\n", + "\u001b[94m130: Guess: $57.95 Truth: $62.95 Abs Error: $5.00 % Error: 7.9% SLE: 0.01 Item: Premium Replica Hubcap Set, Fits Nissan ...\u001b[0m\n", + "\u001b[94m131: Guess: $47.66 Truth: $47.66 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Excellerations Phonics Spelling Game for...\u001b[0m\n", + "\u001b[94m132: Guess: $226.99 Truth: $226.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: RC4WD BigDog Dual Axle Scale Car/Truck T...\u001b[0m\n", + "\u001b[94m133: Guess: $359.95 Truth: $359.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Unknown Stage 2 Clutch Kit - Low Altitud...\u001b[0m\n", + "\u001b[94m134: Guess: $78.40 Truth: $78.40 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: 2002-2008 Dodge Ram 1500 Mopar 4X4 Emble...\u001b[0m\n", + "\u001b[94m135: Guess: $172.77 Truth: $172.77 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Pro Comp Alloys Series 89 Wheel with Pol...\u001b[0m\n", + "\u001b[94m136: Guess: $316.45 Truth: $316.45 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Detroit Axle - Front Rear Strut & Coil S...\u001b[0m\n", + "\u001b[94m137: Guess: $87.99 Truth: $87.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ECCPP Rear Wheel Axle Replacement fit fo...\u001b[0m\n", + "\u001b[94m138: Guess: $226.63 Truth: $226.63 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dell Latitude E6520 Intel i7-2720QM 2.20...\u001b[0m\n", + "\u001b[94m139: Guess: $31.49 Truth: $31.49 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: F FIERCE CYCLE 251pcs Black Universal Mo...\u001b[0m\n", + "\u001b[94m140: Guess: $196.00 Truth: $196.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Flash Furniture 4 Pk. HERCULES Series 88...\u001b[0m\n", + "\u001b[95m141: Guess: $57.99 Truth: $78.40 Abs Error: $20.41 % Error: 26.0% SLE: 0.09 Item: B&M 30287 Throttle Valve/Kickdown Cable,...\u001b[0m\n", + "\u001b[94m142: Guess: $116.25 Truth: $116.25 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Gates TCK226 PowerGrip Premium Timing Be...\u001b[0m\n", + "\u001b[94m143: Guess: $112.78 Truth: $112.78 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Monroe Shocks & Struts Quick-Strut 17149...\u001b[0m\n", + "\u001b[95m144: Guess: $22.47 Truth: $27.32 Abs Error: $4.85 % Error: 17.8% SLE: 0.04 Item: Feit Electric BPMR16/GU10/930CA/6 35W EQ...\u001b[0m\n", + "\u001b[94m145: Guess: $145.91 Truth: $145.91 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Yellow Jacket 2806 Contractor Extension ...\u001b[0m\n", + "\u001b[94m146: Guess: $171.09 Truth: $171.09 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Garage-Pro Tailgate SET Compatible with ...\u001b[0m\n", + "\u001b[94m147: Guess: $169.97 Truth: $167.95 Abs Error: $2.02 % Error: 1.2% SLE: 0.00 Item: 3M Perfect It Buffing and Polishing Kit ...\u001b[0m\n", + "\u001b[91m148: Guess: $57.99 Truth: $28.49 Abs Error: $29.50 % Error: 103.5% SLE: 0.48 Item: Chinese Style Dollhouse Model DIY Miniat...\u001b[0m\n", + "\u001b[94m149: Guess: $122.23 Truth: $122.23 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Generic NRG Innovations SRK-161H Steerin...\u001b[0m\n", + "\u001b[91m150: Guess: $57.99 Truth: $32.99 Abs Error: $25.00 % Error: 75.8% SLE: 0.30 Item: Learning Resources Coding Critters Range...\u001b[0m\n", + "\u001b[94m151: Guess: $81.47 Truth: $71.20 Abs Error: $10.27 % Error: 14.4% SLE: 0.02 Item: Bosch Automotive 15463 Oxygen Sensor, OE...\u001b[0m\n", + "\u001b[94m152: Guess: $112.75 Truth: $112.75 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Case of 24-2 Inch Blue Painters Tape - 6...\u001b[0m\n", + "\u001b[94m153: Guess: $142.43 Truth: $142.43 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: MOCA Engine Water Pump & Fan Clutch fit ...\u001b[0m\n", + "\u001b[94m154: Guess: $398.99 Truth: $398.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SAREMAS Foot Step Bars for Hyundai Palis...\u001b[0m\n", + "\u001b[94m155: Guess: $449.00 Truth: $449.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Gretsch G9210 Square Neck Boxcar Mahogan...\u001b[0m\n", + "\u001b[94m156: Guess: $169.97 Truth: $189.00 Abs Error: $19.03 % Error: 10.1% SLE: 0.01 Item: NikoMaku Mirror Dash Cam Front and Rear ...\u001b[0m\n", + "\u001b[94m157: Guess: $120.91 Truth: $120.91 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Fenix HP25R v2.0 USB-C Rechargeable Head...\u001b[0m\n", + "\u001b[94m158: Guess: $203.53 Truth: $203.53 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: R&L Racing Heavy Duty Roll-Up Soft Tonne...\u001b[0m\n", + "\u001b[94m159: Guess: $399.99 Truth: $349.99 Abs Error: $50.00 % Error: 14.3% SLE: 0.02 Item: Garmin 010-02258-10 GPSMAP 64sx, Handhel...\u001b[0m\n", + "\u001b[91m160: Guess: $22.99 Truth: $34.35 Abs Error: $11.36 % Error: 33.1% SLE: 0.15 Item: Brown 5-7/8\" X 8-1/2\" X 3/16\" Thick Heav...\u001b[0m\n", + "\u001b[94m161: Guess: $384.99 Truth: $384.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: GAOMON PD2200 Pen Display & 20 Pen Nibs ...\u001b[0m\n", + "\u001b[94m162: Guess: $211.00 Truth: $211.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: VXMOTOR for 97-03 Ford F150/F250 Lightdu...\u001b[0m\n", + "\u001b[94m163: Guess: $129.00 Truth: $129.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: HP EliteBook 2540p Intel Core i7-640LM X...\u001b[0m\n", + "\u001b[94m164: Guess: $111.45 Truth: $111.45 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Green EPX Mixing Nozzles 100-Pack-fits 3...\u001b[0m\n", + "\u001b[94m165: Guess: $81.12 Truth: $81.12 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Box Partners 6 1/4 x 3 1/8\" 13 Pt. Manil...\u001b[0m\n", + "\u001b[94m166: Guess: $457.08 Truth: $457.08 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Vixen Air 1/2\" NPT Air Ride Suspension H...\u001b[0m\n", + "\u001b[94m167: Guess: $49.99 Truth: $49.49 Abs Error: $0.50 % Error: 1.0% SLE: 0.00 Item: Smart Floor Lamp, 2700-6500K+RGBPink Mul...\u001b[0m\n", + "\u001b[94m168: Guess: $80.56 Truth: $80.56 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SOZG 324mm Wheelbase Body Shell RC Car B...\u001b[0m\n", + "\u001b[94m169: Guess: $278.39 Truth: $278.39 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Mickey Thompson ET Street S/S Racing Rad...\u001b[0m\n", + "\u001b[94m170: Guess: $364.50 Truth: $364.50 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Pirelli 275/40R20 106W XL RFT P0 PZ4-LUX...\u001b[0m\n", + "\u001b[94m171: Guess: $378.99 Truth: $378.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Torklift C3212 Rear Tie Down\u001b[0m\n", + "\u001b[94m172: Guess: $165.28 Truth: $165.28 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Cardone 78-4226 Remanufactured Ford Comp...\u001b[0m\n", + "\u001b[94m173: Guess: $57.33 Truth: $56.74 Abs Error: $0.59 % Error: 1.0% SLE: 0.00 Item: Kidde AccessPoint 001798 Supra TouchPoin...\u001b[0m\n", + "\u001b[94m174: Guess: $307.95 Truth: $307.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: 3M Protecta 3100414 Self Retracting Life...\u001b[0m\n", + "\u001b[94m175: Guess: $38.00 Truth: $38.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Plantronics 89435-01 Wired Headset, Blac...\u001b[0m\n", + "\u001b[95m176: Guess: $63.88 Truth: $53.00 Abs Error: $10.88 % Error: 20.5% SLE: 0.03 Item: Logitech K750 Wireless Solar Keyboard fo...\u001b[0m\n", + "\u001b[94m177: Guess: $498.00 Truth: $498.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Olympus PEN E-PL9 Body Only with 3-Inch ...\u001b[0m\n", + "\u001b[94m178: Guess: $53.99 Truth: $53.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Beck/Arnley 051-6066 Hub & Bearing Assem...\u001b[0m\n", + "\u001b[94m179: Guess: $319.88 Truth: $350.00 Abs Error: $30.12 % Error: 8.6% SLE: 0.01 Item: Eibach Pro-Kit Performance Springs E10-6...\u001b[0m\n", + "\u001b[94m180: Guess: $299.99 Truth: $299.95 Abs Error: $0.04 % Error: 0.0% SLE: 0.00 Item: LEGO DC Batman 1989 Batwing 76161 Displa...\u001b[0m\n", + "\u001b[94m181: Guess: $94.93 Truth: $94.93 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Kingston Brass KS3608PL Restoration 4-In...\u001b[0m\n", + "\u001b[94m182: Guess: $379.00 Truth: $379.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Polk Vanishing Series 265-LS In-Wall 3-W...\u001b[0m\n", + "\u001b[94m183: Guess: $299.95 Truth: $299.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Spec-D Tuning LED Projector Headlights G...\u001b[0m\n", + "\u001b[94m184: Guess: $24.99 Truth: $24.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: RICHMOND & FINCH Airpod Pro Case, Green ...\u001b[0m\n", + "\u001b[94m185: Guess: $41.04 Truth: $41.04 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: LFA Industries 43B-5A-33JT 1/16-1/2-1.5-...\u001b[0m\n", + "\u001b[94m186: Guess: $327.90 Truth: $327.90 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SAUTVS LED Headlight Assembly for Slings...\u001b[0m\n", + "\u001b[94m187: Guess: $10.99 Truth: $10.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: 2 Pack Combo Womens Safety Glasses Impac...\u001b[0m\n", + "\u001b[94m188: Guess: $14.99 Truth: $14.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Arepa - Venezuelan cuisine - Venezuela P...\u001b[0m\n", + "\u001b[91m189: Guess: $22.57 Truth: $84.95 Abs Error: $62.38 % Error: 73.4% SLE: 1.67 Item: Schlage Lock Company KS23D2300 Padlock, ...\u001b[0m\n", + "\u001b[94m190: Guess: $111.00 Truth: $111.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Techni Mobili White Sit to Stand Mobile ...\u001b[0m\n", + "\u001b[94m191: Guess: $123.73 Truth: $123.73 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Special Lite Products Contemporary Wall ...\u001b[0m\n", + "\u001b[94m192: Guess: $557.38 Truth: $557.38 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Tascam DP-24SD 24-Track Digital Portastu...\u001b[0m\n", + "\u001b[94m193: Guess: $95.55 Truth: $95.55 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Glow Lighting 636CC10SP Vista Crystal Fl...\u001b[0m\n", + "\u001b[94m194: Guess: $154.00 Truth: $154.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Z3 Wind Deflector, Smoke Tint, Lexan, Wi...\u001b[0m\n", + "\u001b[94m195: Guess: $198.99 Truth: $198.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Olympus E-20 5MP Digital Camera w/ 4x Op...\u001b[0m\n", + "\u001b[94m196: Guess: $430.44 Truth: $430.44 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: PHYNEDI 1:1000 World Trade Center (1973-...\u001b[0m\n", + "\u001b[94m197: Guess: $45.67 Truth: $45.67 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: YANGHUAN Unstable Unicorns Adventure Car...\u001b[0m\n", + "\u001b[94m198: Guess: $249.00 Truth: $249.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Interlogix NX-1820E NetworX Touch Screen...\u001b[0m\n", + "\u001b[94m199: Guess: $42.99 Truth: $42.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Steering Damper,Universal Motorcycle Han...\u001b[0m\n", + "\u001b[94m200: Guess: $181.33 Truth: $181.33 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Amprobe TIC 410A Hot Stick Attachment\u001b[0m\n", + "\u001b[95m201: Guess: $6.99 Truth: $6.03 Abs Error: $0.96 % Error: 15.9% SLE: 0.02 Item: MyCableMart 3.5mm Plug/Jack, 4 Conductor...\u001b[0m\n", + "\u001b[94m202: Guess: $33.94 Truth: $29.99 Abs Error: $3.95 % Error: 13.2% SLE: 0.01 Item: OtterBox + Pop Symmetry Series Case for ...\u001b[0m\n", + "\u001b[94m203: Guess: $899.00 Truth: $899.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dell XPS X8700-1572BLK Desktop ( Intel C...\u001b[0m\n", + "\u001b[94m204: Guess: $399.99 Truth: $399.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Franklin Iron Works Sperry Industrial Br...\u001b[0m\n", + "\u001b[94m205: Guess: $4.66 Truth: $4.66 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Avery Legal Dividers, Standard Collated ...\u001b[0m\n", + "\u001b[94m206: Guess: $261.41 Truth: $261.41 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Moen 8346 Commercial Posi-Temp Pressure ...\u001b[0m\n", + "\u001b[94m207: Guess: $136.97 Truth: $136.97 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Carlisle Versa Trail ATR All Terrain Rad...\u001b[0m\n", + "\u001b[94m208: Guess: $79.00 Truth: $79.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SUNWAYFOTO 44mm Tripod Ball Head Arca Co...\u001b[0m\n", + "\u001b[94m209: Guess: $444.99 Truth: $444.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: NanoBeam AC NBE-5AC-Gen2-US 4 Units 5GHz...\u001b[0m\n", + "\u001b[94m210: Guess: $411.94 Truth: $411.94 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: WULF 4\" Front 2\" Rear Leveling Lift Kit ...\u001b[0m\n", + "\u001b[94m211: Guess: $148.40 Truth: $148.40 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Alera ALEVABFMC Valencia Series Mobile B...\u001b[0m\n", + "\u001b[94m212: Guess: $244.99 Truth: $244.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: YU-GI-OH! Ignition Assault Booster Box\u001b[0m\n", + "\u001b[94m213: Guess: $86.50 Truth: $86.50 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: 48\" x 36\" Extra-Large Framed Magnetic Bl...\u001b[0m\n", + "\u001b[94m214: Guess: $297.95 Truth: $297.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Dell Latitude D620 Renewed Notebook PC\u001b[0m\n", + "\u001b[94m215: Guess: $399.99 Truth: $399.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: acer Aspire 5 Laptop, AMD Ryzen 3 5300U ...\u001b[0m\n", + "\u001b[94m216: Guess: $599.00 Truth: $599.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Elk 31080/6RC-GRN 30 by 6-Inch Viva 6-Li...\u001b[0m\n", + "\u001b[91m217: Guess: $64.90 Truth: $105.99 Abs Error: $41.09 % Error: 38.8% SLE: 0.23 Item: Barbie Top Model Doll\u001b[0m\n", + "\u001b[94m218: Guess: $629.97 Truth: $689.00 Abs Error: $59.03 % Error: 8.6% SLE: 0.01 Item: Danby Designer 20-In. Electric Range wit...\u001b[0m\n", + "\u001b[94m219: Guess: $404.99 Truth: $404.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: FixtureDisplays® Metal Truss Podium Doub...\u001b[0m\n", + "\u001b[94m220: Guess: $207.76 Truth: $207.76 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: ACDelco 13597235 GM Original Equipment A...\u001b[0m\n", + "\u001b[94m221: Guess: $171.82 Truth: $171.82 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: EBC S1KF1135 Stage-1 Premium Street Brak...\u001b[0m\n", + "\u001b[94m222: Guess: $293.24 Truth: $293.24 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: FXR Men's Boost FX Jacket (Black/Orange/...\u001b[0m\n", + "\u001b[94m223: Guess: $374.95 Truth: $374.95 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SuperATV Scratch Resistant 3-in-1 Flip W...\u001b[0m\n", + "\u001b[94m224: Guess: $111.99 Truth: $111.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: SBU 3 Layer All Weather Mini Van Car Cov...\u001b[0m\n", + "\u001b[91m225: Guess: $22.99 Truth: $42.99 Abs Error: $20.00 % Error: 46.5% SLE: 0.37 Item: 2 Pack Outdoor Brochure Holder Advertisi...\u001b[0m\n", + "\u001b[94m226: Guess: $116.71 Truth: $116.71 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Monroe Shocks & Struts Quick-Strut 17158...\u001b[0m\n", + "\u001b[94m227: Guess: $118.61 Truth: $118.61 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Elements of Design Magellan EB235AL Thre...\u001b[0m\n", + "\u001b[94m228: Guess: $147.12 Truth: $147.12 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: GM Genuine Parts 15-62961 Air Conditioni...\u001b[0m\n", + "\u001b[94m229: Guess: $119.99 Truth: $119.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Baseus 17-in-1 USB C Docking Station to ...\u001b[0m\n", + "\u001b[94m230: Guess: $369.98 Truth: $369.98 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Whitehall™ Personalized Whitehall Capito...\u001b[0m\n", + "\u001b[94m231: Guess: $315.55 Truth: $315.55 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Pro Circuit Works Pipe PY05250 for 02-19...\u001b[0m\n", + "\u001b[94m232: Guess: $210.00 Truth: $190.99 Abs Error: $19.01 % Error: 10.0% SLE: 0.01 Item: HYANKA 15 \"1200W Professional DJ Speaker...\u001b[0m\n", + "\u001b[94m233: Guess: $155.00 Truth: $155.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Bluetooth X6BT Card Reader Writer Encode...\u001b[0m\n", + "\u001b[94m234: Guess: $349.99 Truth: $349.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: AIRAID Cold Air Intake System by K&N: In...\u001b[0m\n", + "\u001b[94m235: Guess: $249.99 Truth: $249.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Bostingner Shower Faucets Sets Complete,...\u001b[0m\n", + "\u001b[94m236: Guess: $42.99 Truth: $42.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: PIT66 Front Bumper Turn Signal Lights, C...\u001b[0m\n", + "\u001b[95m237: Guess: $22.99 Truth: $17.99 Abs Error: $5.00 % Error: 27.8% SLE: 0.05 Item: Caseology Bumpy Compatible with Google P...\u001b[0m\n", + "\u001b[94m238: Guess: $425.00 Truth: $425.00 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Fleck 2510 Timer Mechanical Filter Contr...\u001b[0m\n", + "\u001b[94m239: Guess: $249.99 Truth: $249.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Haloview MC7108 Wireless RV Backup Camer...\u001b[0m\n", + "\u001b[94m240: Guess: $138.23 Truth: $138.23 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Schmidt Spiele - Manhattan\u001b[0m\n", + "\u001b[94m241: Guess: $354.99 Truth: $414.99 Abs Error: $60.00 % Error: 14.5% SLE: 0.02 Item: Corsa 14333 Tip Kit (Ford Mustang GT)\u001b[0m\n", + "\u001b[94m242: Guess: $168.28 Truth: $168.28 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Hoshizaki FM116A Fan Motor Kit 1\u001b[0m\n", + "\u001b[94m243: Guess: $199.99 Truth: $199.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: BAINUO Antler Chandelier Lighting,6 Ligh...\u001b[0m\n", + "\u001b[94m244: Guess: $126.70 Truth: $126.70 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: DNA MOTORING HL-OH-FEXP06-SM-AM Smoke Le...\u001b[0m\n", + "\u001b[94m245: Guess: $5.91 Truth: $5.91 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Wera Stainless 3840/1 TS 2.5mm Hex Inser...\u001b[0m\n", + "\u001b[94m246: Guess: $193.06 Truth: $193.06 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Celestron - PowerSeeker 127EQ Telescope ...\u001b[0m\n", + "\u001b[94m247: Guess: $249.99 Truth: $249.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: NHOPEEW 10.1inch Android Car Radio Carpl...\u001b[0m\n", + "\u001b[94m248: Guess: $64.12 Truth: $64.12 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Other Harmonica (Suzuki-2Timer24- A)\u001b[0m\n", + "\u001b[94m249: Guess: $114.99 Truth: $114.99 Abs Error: $0.00 % Error: 0.0% SLE: 0.00 Item: Harley Air Filter Venturi Intake Air Cle...\u001b[0m\n", + "\u001b[94m250: Guess: $928.33 Truth: $926.00 Abs Error: $2.33 % Error: 0.3% SLE: 0.00 Item: Elite Screens Edge Free Ambient Light Re...\u001b[0m\n", + "\n", + "------------------------------------------------------------\n", + "TEST SUMMARY: Gpt Fine Tuned\n", + "------------------------------------------------------------\n", + "Samples tested: 250\n", + "Average absolute error: $5.18\n", + "Average percentage error: 5.1%\n", + "RMSLE: 0.1979\n", + "\n", + "Performance Breakdown:\n", + " 🔵 Excellent (<=15% error): 228 (91.2%)\n", + " 🟣 Good (<=30% error): 10 (4.0%)\n", + " 🔴 Poor (>30% error): 12 (4.8%)\n", + "------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+0AAALNCAYAAABanICRAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAsAVJREFUeJzs3Qd4VFX6x/FfeiMhtAAh9F4F6YgCCmJBsIICgmV33XV17W11bX/Lqru2XV3LrlIUO9gVG2IBFcFG772XkN7n/7x3mEwSAiSQ5CaT7+d5hpAzd+6cmbkzmfe+57wnyOPxeAQAAAAAAKqdYLc7AAAAAAAASkfQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgAAAABANUXQDgBAJfj11191+eWXq23btoqKinIu7du31xVXXKEff/zxqPf7wAMP6O233y7z9kFBQaVeGjZs6Fw/dOhQ51KVLrnkkkP2q+jFtqtO1q9f7/RrypQpbncFAFCLhLrdAQAAAs2zzz6rq666Sh07dtQ111yjrl27OsHesmXL9Morr6hv375avXq1E9AfTdB+/vnn6+yzzy7zbWz7G264oVhbWFiY8/Ppp59WVfvb3/6mP/7xj4W/L1q0SH/+85+dxzZs2LDC9kaNGlV53wAAqG4I2gEAqEDffvutrrzySp155pl68803FR4eXnjdySef7ASnb7zxhpN5ryqNGzfWgAEDSr2uS5cuqmp2sqLoCYusrCznp41EOFQ/AQCorRgeDwBABbJscUhIiJNtLxqwF3XBBRcoMTGx8HcbBl6nTh0tWbJEp5xyimJiYpwss2XrMzIyCrezbH16erqmTp1aOIT8WIe2lxwe7xsC/o9//EOPPvqoWrdu7fRt4MCB+u677w66vQ31Hz16tOrXr6/IyEj16tVLr7/+uo7V3Xff7fSjJBuabu3WT59WrVpp1KhR+vjjj3X88cc7J0Q6deqkF1544aDbb9++3ZmikJSU5Lw+9vjuuece5eXlFdtu69atGjt2rGJjY1W3bl2NGzfOuS0AAFWNTDsAABUkPz9fc+bMUZ8+fdS0adNy3TY3N1dnnHGGE1Deeuutmjdvnu677z5t2LBB7733nrPN/PnznWy9DSG3IeYmLi7uiPv2eDwHBaV2YqG0oNjnqaeecgLfxx9/3Pnd7s/6t27dOieINfZYTzvtNPXv31/PPPOM0/7qq686Aa6dbKjKOem//PKLMwXAnjsbWfDf//7XqSnQrl07nXTSSc42FnT369dPwcHBuvPOO51svz2n9jzbSYAXX3zR2S4zM1PDhw93AvcHH3xQHTp00AcffOA8LgAAqhpBOwAAFWT37t1OwNeyZctSA3oLng8VNOfk5DhB51/+8hfn9xEjRjjzzm+//XZnyP0JJ5zgDB23gNOy8OUZRm7z1kvOXX/++ef1u9/97pC3sQzz+++/7/TT2MgAC3g/+ugjXXjhhU6bTQOw+fpffPGFQkO9XylGjhzpPA9//etfNWnSJKe/VcHu056nFi1aOL9boP75559rxowZhUG7Ze/37dvnjGjwbWcjGywzf+ONN+qmm25ypgvYSAarP/DOO+84owjMqaee6ry29rwBAFCVGB4PAEAV6N27txOE+y7//Oc/D9pmwoQJxX4fP358YUb7WNgw7wULFhS7HKmQnc3J9wXspkePHs5Py/wbK6S3fPnywj5bJt93sYz8tm3btGLFClWVnj17FgbixobqW4bc119jJyFslIKdgCja39NPP925fu7cuYXPt5208AXsJV8PAACqEpl2AAAqiC2jZlnbooGij2V8bci4BbMlg0FjmeoGDRoUa2vSpInzc8+ePcfUL8vM25D98ijZl4iICOenZZvNjh07nJ+WobbLobLfVaVkf3199vXX12ebauCrnH+o/trzbUPsS/K9HgAAVCWCdgAAKohlpm3O+SeffOIE50XntfuqtBctoFaUZXwtWCwafPoKn5UWkLrNt877bbfdpnPPPbfUbWzJu6NlmXKTnZ1deMLgWE8EWJ9txMD9999f6vW+4oD2fP/www8HXU8hOgCAGxgeDwBABbIg1uav2zrkVlyuPF5++eWDsvOmaHX3ktljt1hAbku0WQE4y+KXdrEh5kfLKsKbX3/9tVi7ryjf0bAK84sXL3YK0JXWX1/QbkPoU1NT9e6775b6egAAUJXItAMAUIGsYJxVXr/66qud5cf+8Ic/OMXarCCbZd/feuutUqu+2/JjNs89LS1Nffv2Laweb/OtBw8eXLhd9+7d9eWXXzrBq2XyLTA+loz2sbBl7ax/VnzOKsU3a9ZMe/fudYq4LVq0yFmP/mjZvHhbRs4qwN97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+rJ+/frDPhYbkhkcHOy56qqrPHl5eYcd/rlo0SLPNddc45k1a5bnq6++8rzwwguezp07O/fz8ccfe47kT3/6kycqKqrUIaPjx4/3vPzyy85+33zzTWe4vj22O+64w1Nehxoeb2xodXh4eOHr8s477zjtw4cP91x++eVl2v+qVauc2//nP/+p8OHxr732mjMk3KZK2HDwjh07OsPE9+3bd9B+fUPXbbhykyZNnNvt2bPHmd4wZcoUZ3huyeHxNiT37LPPLna89urVy3P77bd7du7cecj7OJTyvBfuuecep+3LL7887D7tOLDbz58//6Dr/vCHPzivX3kcblhzz549PR06dCg2DNqeR5seYn2YMWNGse3t88eeX99jtKkSn376aZn6Ud7h8Ye6TJ8+vdi2Dz30kNNu70t7zew9Zp+FPvaesuvtNT6UjRs3ekJDQz1XX311sXb7nLVjy6a1lGd4/CuvvOJs89Zbb5U6xanoFAd7bSIjIz0bNmwoNu3AntsrrriiTMPj7ZiyY8uOscOx94d93pV8r69evbrw+fUNUy+pvMPjS/P888879/HEE08Ua7fXLi4urnB60rPPPuu0Wz/tswlA7UHQDqBGsy+E9mU0OTm5sO3SSy91vuSsXLmysM2C84EDBxb+7psv+NRTTxUL4m3uckkW6JQnaLcTCfYltOjlxx9/LLatbXfOOeccch++oNHHvsxa+w8//HDQbSxA7t+/f7HnpF69ep5j9eijjzpfFBs1auT8vO2228ociFgwmZSU5OnRo8cRtx0zZoynRYsWZe7XqFGjnECiZDB5LEG7SUtLc06O+OaU2uuYkJDgnFixL/V2AsHmNFvgVlpgvn///jKfUChv0F7y0rRpU8+6detK3a8voLYTM3YS6t133/U8+eSTntjYWGeOdmlBe9H3xWOPPeaZMGGC8/rZdg0aNPAsX7681PuoiPdCWfmC9u+++67UoN2O0fI4XLD1v//9z7kvO6G0efNmJ3i1QMkCQGt/9dVXC7e1k1R231br4bPPPnNqJFhNAZufXJaTVuUN2i1QLvmc2sWO0aLsJJg9Rgt87Xb//e9/i11v7+fDBaNFg0nbv520KHoZN26c8/4oT9Bux5XVyLD52SX3V/IkgL02pdW5sDaby12Rc9rNxRdf7AkLC3NOwNhz+csvvzifq77XfPv27ZUStNvxYiec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/XZhQPvwww87GXgLJI877ji9+eabBw0Pnz17tvr06ePsa/r06erevbtzvd3OrrdK5ea9995T7969FRkZ6Vx3zz33KC8vr3B/NszcsvKNGzd2tunWrZvef/99534uvfRS7d+/vzCDfvfddx/yMdn19pjshMGwYcN01113afHixVq9enWpfbaTFUd6rObDDz9Uhw4dnOttv77HdbjRAO+++65zP/Z4GjZsqHPPPddpHzp0qDZs2KDrrruu8DEdah92AqVt27bOUoIdO3Z0nuOSj/e///2vzjnnHGf1AlvKze4XAAAAQCVl19d/Is3/vyIBe5DU4hRpwJ1VHrAbMu21iAWkliU3d9xxh2bOnOkEjRYIfvXVV5o4caKzZNiQIUMKb3PzzTfrH//4hxPwWnD62Wefafjw4frhhx+ckwG2vQXJdtsnn3xSJ554otasWVM4bN6Calsy7/TTT1dqaqpeeuklJ0hdunSpkxkfNGiQM4z8zjvvdDLopk6dOuV6TMb3uEr22YLkIz3WTZs2OQH3H//4R/3pT3/Sjz/+qBtuuOGw9/vBBx84t7n99tudQNuWBrQ2Y/dlJwbsOfj9739/yH3MmjVL11xzjfP47Tm1kxh2AiMpKck5ceBjJ0DspMMjjzyif/3rX87JFzspUL9+/TI/TwAAAACOIG2btGSKlFIkgRed4M2u12sntxC0H43vH5Cy91f9/UbUlfr/9ahuakH2jBkzdMoppzhD5B999FF98cUXGjhwoHO9BbjffPONnn322WJB+7333qsRI0YU/r5r1y7npwW8lvE2999/v2699VZNnjy5cF//93//5wTPFrRboG/3v2zZMieb7dvGp27duoUZ9PLYvHmzE8hakGv73b1790F9LstjtWDe2h577DGnH5bx/u233/TQQw8d8r7tMV944YVOQO1jgbqxYNpOSMTGxh72MdmJBas9cOWVVzq/X3/99fruu++c9qJBu21z0UUXOf9/4IEHnMDdnk+b/gAAAACgIrLrs6U170uevOLZ9XZjpJBwuYmg/WhYwJ7trSpenVnm1rLWNkzdMtFjxoxxAj7LcmdlZRULxo1li3v16lWszYZ/H8nChQu1YMECJ5D1sbnkdh8ZGRnOfHNfYH2sbBi9PSYb8m77Pv74453Mtg0vL63PZXmsdjJhwIABxdY49wX4h2KP6XBZ9LKw+y1Z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pDlfePT0Hd9oeIs3tSEpS8OG2ao6kpoOlDqOlcKi3e4iAKA2VY8HAADAAZl79Nu06To1aZmCbFW37dLO/fFKGjFRatTd7d4BAAIAQTsAAMDRzF3f/JW06i21q5+trVmSp0D6edcJGnHC+VIjsusAgIpB0A4AAFAeGbulpdOkfSucX/v3k+Z+X08fb7hY42/rqsSWbncQABBICNoBAADKml3f9KW0aqZUkFPY3KDHiTr3/PN1bmikq90DAAQmgnYAAIAjydgpLZkmJa/yt0XWl7pMkhp0drNnAIAAd1Trs3399deaOHGiBg4cqC1btjht06dP1zfffFPR/QMAAHA3u77hc2n+vcUD9qQh0sC7CNgBANUvaH/rrbc0cuRIRUVF6aefflJ2drbTnpqaqgceeKAy+ggAAFDl665vXL5DBT88Iq18XSrI9V4R1VA6/jqp83iJ4fAAgOoYtN9333165pln9PzzzyssLKywfdCgQVq0aFFF9w8AAKBKrVtboL//6VN999j/acGna5xku6P5MGnAnVKDTi73EABQm5R7TvuKFSt00kknHdQeFxen5OTkiuoXAABA1Uvfrl0fTVW38LUKDZZsFuD+3EaKHzhJqt/B7d4BAGqhcgftTZs21erVq9WqVati7TafvU2bNhXZNwAAgKphi6xv+FRa/a6axeRpU5iUnR2kDZ6TNfLEMVJchNs9BADUUuUO2q+44gpdc801euGFFxQUFKStW7dq/vz5uvHGG3XnnXdWTi8BAAAqS9pWaclUKWW982tionT84AT9mDlZF45op5g4tzsIAKjNyh2033zzzdq/f7+GDRumrKwsZ6h8RESEE7RfddVVldNLAACAysiur58trXlf8uQdaAxSUKvhan3KaLUOCXe5gwAAHOU67ffff79uv/12LV26VAUFBerSpYvq1KlT8b0DAACoDKlbpKWWXd/gb4tuLHW9RIpnuh8AoAZXj7/sssuc5d2io6PVp08f9evXzwnY09PTnesAAACqrYJ8ae0H0vf3FwnYg6RWI6UBfyNgBwDU/KB96tSpyszMPKjd2qZNm1ZR/QIAAKhYqZulHx6U1rwrefK9bTFNpX63SO3PlUL8S9kCAFDjhsenpKTI4/E4F8u0R0ZGFl6Xn5+vDz/8UAkJCZXVTwAAgKNTkCet+0ha96F3HrsJCpZajpTanEmwDgAI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GwDKiMPhUGpqqvz9/eXtXe7P1wGnRF+HJ6CfwxPQz1HRpaam2h8ty6rYob2A1wnlcPPGT9xW4Nlnn9WTTz550vbmn5mEvlL6tpl0R6k1FQAAAAAAW1pamkJDQ1VhQ3t4eLh8fHxOqqonJCScVH0v8PDDD2vChAmF95OTkxUTE6MDBw6c9osFlPczeWauh4MHD6pKlSqubg5Qaujr8AT0c3gC+jkqOsuy7MAeHR192v3KfWg3w2XMEm9Lly7V9ddfX7jd3B8wYECJzwkICLBvJzKBnR8IqOhMH6efwxPQ1+EJ6OfwBPRzVGRnUjQu96HdMFXzoUOHqk2bNurYsaNmzJhhV81Hjx7t6qYBAAAAAHDOKkRov/XWW3X06FE99dRTOnz4sJo1a6bFixfbQ94BAAAAACivKkRoN+6++277di7MUPnJkyeXOGQeqCjo5/AU9HV4Avo5PAH9HHDysv5sfnkAAAAAAOASLHgIAAAAAICbIrQDAAAAAOCmCO0AAAAAALgpjw/t06ZNU/369RUYGGiv975q1SpXNwk4Y88++6zatm2rypUrKyIiQgMHDtT27duL7WOmrXjiiScUHR2toKAgXXHFFfr111+L7ZOdna17771X4eHhCgkJ0XXXXafY2NgyfjfAmfd7Ly8vjRs3rnAb/RwVRVxcnIYMGaLq1asrODhYl19+udavX1/4OH0d5V1eXp4ee+wx++9v04cbNGhgrwDlcDgK96GfA8V5dGifP3++/Uffo48+qp9//lldu3ZV37597TXegfJg5cqVGjNmjNauXaulS5favwh79+6tjIyMwn1eeOEFvfzyy3r99df1ww8/KDIyUldddZXS0tIK9zHfBx9//LHef/99rV69Wunp6brmmmuUn5/voncGlMz04RkzZuiyyy4rtp1+joogKSlJnTt3lp+fn7744gtt2bJFL730ksLCwgr3oa+jvHv++ef15ptv2n1469atdp9+8cUX9dprrxXuQz8HTmB5sHbt2lmjR48utq1x48bWpEmTXNYm4HwkJCSY1SCslStX2vcdDocVGRlpPffcc4X7HD9+3AoNDbXefPNN+35ycrLl5+dnvf/++4X7xMXFWd7e3taXX37pgncBlCwtLc26+OKLraVLl1rdu3e3xo4da2+nn6OimDhxotWlS5dTPk5fR0XQv39/64477ii27YYbbrCG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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dynamic_testing import Tester # Imports Tester from dynamic_testing module.\n", + "\n", + "# Tester.test(my_predictor, data, excellent_threshold=0.10, good_threshold=0.25) # Example usage with custom thresholds:\n", + "\n", + "Tester.test(gpt_fine_tuned, test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ce2d93d-13f0-49bd-9928-c60b7bd22ff5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/week6/community-contributions/week6_day2_google_colab.ipynb b/week6/community-contributions/week6_day2_google_colab.ipynb new file mode 100644 index 0000000..c164af9 --- /dev/null +++ b/week6/community-contributions/week6_day2_google_colab.ipynb @@ -0,0 +1,676 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "91ae778b" + }, + "source": [ + "# Getting Started\n", + "\n", + "Before running this notebook, please ensure you have the following:\n", + "\n", + "1. **Local Modules:** Upload the necessary local Python files (`items.py`, `loaders.py`, `testing.py`) to the Colab runtime's temporary storage. You can do this by clicking the folder icon on the left sidebar, then the upload icon, and selecting the files.\n", + "2. **Hugging Face Access Token:** Add your Hugging Face access token to Colab's user data secrets. Click the key icon on the left sidebar, click \"New secret\", and add your token with the name `HF_TOKEN`.\n", + "3. **Install Dependencies:** Run the first code cell to install the required libraries with the specified versions.\n", + "\n", + "Once these steps are completed, you can run the rest of the notebook cells sequentially." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "_fj3pImYM5dw" + }, + "outputs": [], + "source": [ + "# Install exact versions from local environment to match the course's environment\n", + "!pip install --upgrade pip\n", + "\n", + "# Install specific versions of required libraries\n", + "!pip install datasets==3.6.0\n", + "!pip install transformers==4.51.3\n", + "!pip install huggingface_hub==0.31.2\n", + "!pip install matplotlib==3.10.3\n", + "!pip install numpy==1.26.4\n", + "!pip install python-dotenv==1.1.0\n", + "!pip install tqdm==4.67.1" + ] + }, + { + "cell_type": "code", + "source": [ + "# Import necessary libraries\n", + "import os\n", + "import random\n", + "from dotenv import load_dotenv\n", + "from huggingface_hub import login\n", + "from datasets import load_dataset, Dataset, DatasetDict\n", + "import matplotlib.pyplot as plt\n", + "from collections import Counter, defaultdict\n", + "import numpy as np\n", + "import pickle" + ], + "metadata": { + "id": "YQHruTKgPMRX" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Retrieve the Hugging Face access token from Colab's user data secrets\n", + "# This token is needed to interact with the Hugging Face Hub\n", + "from google.colab import userdata\n", + "userdata.get('HF_TOKEN')" + ], + "metadata": { + "id": "jBdHkdyVNj_f" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Import custom classes from local files (items.py and loaders.py)\n", + "# These files were manually added to the Colab runtime's temporary storage\n", + "from loaders import ItemLoader\n", + "from items import Item" + ], + "metadata": { + "id": "FdBT3PPzNmq3" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Set the backend for matplotlib to display plots inline in the notebook\n", + "%matplotlib inline" + ], + "metadata": { + "id": "vynEBaq6OGEZ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Load a single dataset (\"All_Beauty\") using the custom ItemLoader\n", + "# This was likely an initial test or example loading step\n", + "items = ItemLoader(\"Appliances\").load()" + ], + "metadata": { + "id": "OFOJtH6FOG2u" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Define a list of dataset names (Amazon product categories) to be loaded\n", + "dataset_names = [\n", + " \"Automotive\",\n", + " \"Electronics\",\n", + " \"Office_Products\",\n", + " \"Tools_and_Home_Improvement\",\n", + " \"Cell_Phones_and_Accessories\",\n", + " \"Toys_and_Games\",\n", + " \"Appliances\",\n", + " \"Musical_Instruments\",\n", + "]" + ], + "metadata": { + "id": "rkLXYtfhOJNn" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Check and print the available CPU cores and RAM in the Colab runtime environment\n", + "# This helps understand the resources available for data processing\n", + "import psutil\n", + "print(f\"CPU cores: {psutil.cpu_count()}\")\n", + "print(f\"Available RAM: {psutil.virtual_memory().available / (1024**3):.1f} GB\")" + ], + "metadata": { + "id": "1oQSUpovOfKs" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "items = []\n", + "for dataset_name in dataset_names:\n", + " loader = ItemLoader(dataset_name)\n", + " items.extend(loader.load(workers=8))\n", + "\n", + "# Now, time for a coffee break!!\n", + "# By the way, I put the biggest datasets first.. it gets faster." + ], + "metadata": { + "id": "UcV9RB2Go8nC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Print the total number of items loaded from all datasets\n", + "print(f\"A grand total of {len(items):,} items\")" + ], + "metadata": { + "id": "YdkGJ_X3oI1g" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract token counts from all loaded items\n", + "tokens = [item.token_count for item in items]\n", + "# Create and display a histogram of token counts\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Token counts: Avg {sum(tokens)/len(tokens):,.1f} and highest {max(tokens):,}\\n\")\n", + "plt.xlabel('Length (tokens)')\n", + "plt.ylabel('Count')\n", + "plt.hist(tokens, rwidth=0.7, color=\"skyblue\", bins=range(0, 300, 10))\n", + "plt.show()" + ], + "metadata": { + "id": "8VzKJ7neo-wp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract prices from all loaded items\n", + "prices = [item.price for item in items]\n", + "# Create and display a histogram of item prices\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Prices: Avg {sum(prices)/len(prices):,.1f} and highest {max(prices):,}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"blueviolet\", bins=range(0, 1000, 10))\n", + "plt.show()" + ], + "metadata": { + "id": "ZLFJycNZpDak" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Count the occurrences of each category in the loaded items\n", + "category_counts = Counter()\n", + "for item in items:\n", + " category_counts[item.category]+=1\n", + "\n", + "# Extract categories and their counts for plotting\n", + "categories = category_counts.keys()\n", + "counts = [category_counts[category] for category in categories]\n", + "\n", + "# Create and display a bar chart showing the count of items per category\n", + "plt.figure(figsize=(15, 6))\n", + "plt.bar(categories, counts, color=\"goldenrod\")\n", + "plt.title('How many in each category')\n", + "plt.xlabel('Categories')\n", + "plt.ylabel('Count')\n", + "\n", + "# Rotate x-axis labels for better readability\n", + "plt.xticks(rotation=30, ha='right')\n", + "\n", + "# Add value labels on top of each bar for clarity\n", + "for i, v in enumerate(counts):\n", + " plt.text(i, v, f\"{v:,}\", ha='center', va='bottom')\n", + "\n", + "# Display the chart\n", + "plt.show()" + ], + "metadata": { + "id": "6oRa8rI6pGb0" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Create a dictionary where keys are rounded prices and values are lists of items with that price\n", + "# This is done to group items by price for sampling\n", + "slots = defaultdict(list)\n", + "for item in items:\n", + " slots[round(item.price)].append(item)" + ], + "metadata": { + "id": "7mT5ZubkpJ06" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Create a curated sample dataset with a more even distribution of prices and reduced bias towards 'Automotive' category\n", + "# Items with price >= $240 are included entirely\n", + "# For prices < $240, if the number of items is <= 1200, all are included\n", + "# If the number of items > 1200, a weighted random sample of 1200 items is taken,\n", + "# giving non-Automotive items higher weight\n", + "\n", + "# Set random seeds for reproducibility\n", + "np.random.seed(42)\n", + "random.seed(42)\n", + "sample = []\n", + "for i in range(1, 1000):\n", + " slot = slots[i]\n", + " if i>=240:\n", + " sample.extend(slot)\n", + " elif len(slot) <= 1200:\n", + " sample.extend(slot)\n", + " else:\n", + " # Assign weights: 1 for 'Automotive', 5 for other categories\n", + " weights = np.array([1 if item.category=='Automotive' else 5 for item in slot])\n", + " # Normalize weights\n", + " weights = weights / np.sum(weights)\n", + " # Randomly select 1200 indices based on weights\n", + " selected_indices = np.random.choice(len(slot), size=1200, replace=False, p=weights)\n", + " # Select the items corresponding to the chosen indices\n", + " selected = [slot[i] for i in selected_indices]\n", + " sample.extend(selected)\n", + "\n", + "# Print the total number of items in the curated sample\n", + "print(f\"There are {len(sample):,} items in the sample\")" + ], + "metadata": { + "id": "qHJdXynopMBp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract prices from the curated sample\n", + "prices = [float(item.price) for item in sample]\n", + "# Create and display a histogram of prices for the sample dataset\n", + "# This helps visualize the effect of the sampling process on the price distribution\n", + "plt.figure(figsize=(15, 10))\n", + "plt.title(f\"Avg {sum(prices)/len(prices):.2f} and highest {max(prices):,.2f}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"darkblue\", bins=range(0, 1000, 10))\n", + "plt.show()" + ], + "metadata": { + "id": "gtBkOdPGpOou" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Count the occurrences of each category in the curated sample\n", + "category_counts = Counter()\n", + "for item in sample:\n", + " category_counts[item.category]+=1\n", + "\n", + "# Extract categories and their counts for plotting\n", + "categories = category_counts.keys()\n", + "counts = [category_counts[category] for category in categories]\n", + "\n", + "# Create and display a bar chart showing the count of items per category in the sample\n", + "# This helps visualize the effect of weighted sampling on category distribution\n", + "plt.figure(figsize=(15, 6))\n", + "plt.bar(categories, counts, color=\"lightgreen\")\n", + "\n", + "# Customize the chart\n", + "plt.title('How many in each category')\n", + "plt.xlabel('Categories')\n", + "plt.ylabel('Count')\n", + "\n", + "# Rotate x-axis labels for better readability\n", + "plt.xticks(rotation=30, ha='right')\n", + "\n", + "# Add value labels on top of each bar for clarity\n", + "for i, v in enumerate(counts):\n", + " plt.text(i, v, f\"{v:,}\", ha='center', va='bottom')\n", + "\n", + "# Display the chart\n", + "plt.show()" + ], + "metadata": { + "id": "-lYpt40jpTE1" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Create and display a pie chart showing the percentage distribution of items across categories in the sample\n", + "plt.figure(figsize=(12, 10))\n", + "plt.pie(counts, labels=categories, autopct='%1.0f%%', startangle=90)\n", + "\n", + "# Add a circle at the center to create a donut chart (optional)\n", + "centre_circle = plt.Circle((0,0), 0.70, fc='white')\n", + "fig = plt.gcf()\n", + "fig.gca().add_artist(centre_circle)\n", + "plt.title('Categories')\n", + "\n", + "# Equal aspect ratio ensures that pie is drawn as a circle\n", + "plt.axis('equal')\n", + "\n", + "plt.show()" + ], + "metadata": { + "id": "5QPV4m2LpV3g" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Markdown cell indicates that the dataset curation is complete and ready for final checks\n", + "# Dataset Curated!\n", + "\n", + "# We've crafted an excellent dataset.\n", + "\n", + "# Let's do some final checks" + ], + "metadata": { + "id": "3Xc2ZxjapZ0a" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract prompt lengths (character counts) and prices from the curated sample\n", + "sizes = [len(item.prompt) for item in sample]\n", + "prices = [item.price for item in sample]\n", + "\n", + "# Create and display a scatter plot to visualize the relationship between prompt size and price\n", + "# This helps check for any simple correlation between the two\n", + "plt.figure(figsize=(15, 8))\n", + "plt.scatter(sizes, prices, s=0.2, color=\"red\")\n", + "\n", + "# Add labels and title\n", + "plt.xlabel('Size')\n", + "plt.ylabel('Price')\n", + "plt.title('Is there a simple correlation?')\n", + "\n", + "# Display the plot\n", + "plt.show()" + ], + "metadata": { + "id": "VXYQkVarpceE" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Define a helper function to report information about an item\n", + "# It prints the item's prompt, the last 10 token IDs, and the decoded last 10 tokens\n", + "def report(item):\n", + " prompt = item.prompt\n", + " tokens = Item.tokenizer.encode(item.prompt)\n", + " print(prompt)\n", + " print(tokens[-10:])\n", + " print(Item.tokenizer.batch_decode(tokens[-10:]))" + ], + "metadata": { + "id": "1BBJNDAKpgL_" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Use the report function to display information about a specific item in the sample\n", + "# This helps inspect the data and the tokenizer's behavior\n", + "report(sample[398000])" + ], + "metadata": { + "id": "ZO2zF09wpiPp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "## Observation\n", + "\n", + "An interesting thing about the Llama tokenizer is that every number from 1 to 999 gets mapped to 1 token, much as we saw with gpt-4o. The same is not true of qwen2, gemma and phi3, which all map individual digits to tokens. This does turn out to be a bit useful for our project, although it's not an essential requirement." + ], + "metadata": { + "id": "GCkwmt_VpsaU" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Finally\n", + "\n", + "It's time to break down our data into a training, test and validation dataset.\n", + "\n", + "It's typical to use 5%-10% of your data for testing purposes, but actually we have far more than we need at this point. We'll take 400,000 points for training, and we'll reserve 2,000 for testing, although we won't use all of them.\n" + ], + "metadata": { + "id": "dy6WGVAmpx0g" + } + }, + { + "cell_type": "code", + "source": [ + "# Set random seed for reproducibility before shuffling and splitting the sample\n", + "random.seed(42)\n", + "# Shuffle the curated sample dataset\n", + "random.shuffle(sample)\n", + "# Split the shuffled sample into training (400,000 items) and testing (2,000 items) sets\n", + "train = sample[:400_000]\n", + "test = sample[400_000:402_000]\n", + "# Print the sizes of the training and testing sets\n", + "print(f\"Divided into a training set of {len(train):,} items and test set of {len(test):,} items\")" + ], + "metadata": { + "id": "oY1ZSkW7p0VS" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract prices from the first 250 items of the test set\n", + "prices = [float(item.price) for item in test[:250]]\n", + "# Create and display a histogram of prices for the first 250 test items\n", + "# This provides a quick look at the price distribution in a small portion of the test data\n", + "plt.figure(figsize=(15, 6))\n", + "plt.title(f\"Avg {sum(prices)/len(prices):.2f} and highest {max(prices):,.2f}\\n\")\n", + "plt.xlabel('Price ($)')\n", + "plt.ylabel('Count')\n", + "plt.hist(prices, rwidth=0.7, color=\"darkblue\", bins=range(0, 1000, 10))\n", + "plt.show()" + ], + "metadata": { + "id": "nLnRpUbtp17N" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Extract prompts from the training set\n", + "train_prompts = [item.prompt for item in train]\n", + "# Extract prices from the training set\n", + "train_prices = [item.price for item in train]\n", + "# Extract test prompts (using the test_prompt method) from the test set\n", + "test_prompts = [item.test_prompt() for item in test]\n", + "# Extract prices from the test set\n", + "test_prices = [item.price for item in test]" + ], + "metadata": { + "id": "kpw1Y8IIp6kw" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Create Hugging Face Dataset objects from the training and testing data\n", + "train_dataset = Dataset.from_dict({\"text\": train_prompts, \"price\": train_prices})\n", + "test_dataset = Dataset.from_dict({\"text\": test_prompts, \"price\": test_prices})\n", + "# Create a DatasetDict containing the training and testing datasets\n", + "dataset = DatasetDict({\n", + " \"train\": train_dataset,\n", + " \"test\": test_dataset\n", + "})" + ], + "metadata": { + "id": "WtEFiTlvp8hL" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Push the created DatasetDict to the Hugging Face Hub\n", + "# Replace \"aaron-official\" with your Hugging Face username\n", + "# The dataset will be named \"your-username/pricer-data\" and will be private\n", + "# HF_USER = \"aaron-official\" # Uncomment and replace with your HF username\n", + "# DATASET_NAME = f\"{HF_USER}/pricer-data\" # Uncomment\n", + "# dataset.push_to_hub(DATASET_NAME, private=True) # Uncomment to push to hub" + ], + "metadata": { + "id": "sSnwZIxHp-VJ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Pickle (serialize) the training and testing datasets and save them as files\n", + "# This allows for quick loading of the processed data in future sessions\n", + "with open('train.pkl', 'wb') as file:\n", + " pickle.dump(train, file)\n", + "\n", + "with open('test.pkl', 'wb') as file:\n", + " pickle.dump(test, file)" + ], + "metadata": { + "id": "WRawIsrOqMQ-" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "bd72e246" + }, + "source": [ + "# Mount Google Drive to access files in your Drive\n", + "from google.colab import drive\n", + "drive.mount('/content/drive')" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6fc5d915" + }, + "source": [ + "Once your Google Drive is mounted, you can copy the file to a folder in your Drive. Replace `My Drive/your_folder_name` with the path to the folder where you want to save the file." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "f319129b" + }, + "source": [ + "# Import the shutil module for file operations\n", + "import shutil\n", + "\n", + "# Define the destination path in Google Drive and the source path of the pickled training data\n", + "# Replace 'My Drive/your_folder_name' with your desired folder path in Google Drive\n", + "destination_path = '/content/drive/My Drive/train.pkl'\n", + "source_path = '/content/train.pkl'\n", + "\n", + "# Copy the pickled training data file from the Colab environment to Google Drive\n", + "shutil.copyfile(source_path, destination_path)\n", + "\n", + "# Print a confirmation message\n", + "print(f\"Copied {source_path} to {destination_path}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "d23d6cf0" + }, + "source": [ + "# Import the shutil module for file operations\n", + "import shutil\n", + "\n", + "# Define the destination path in Google Drive and the source path of the pickled test data\n", + "# Replace 'My Drive/your_folder_name' with your desired folder path in Google Drive\n", + "destination_path = '/content/drive/My Drive/test.pkl'\n", + "source_path = '/content/test.pkl'\n", + "\n", + "# Copy the pickled test data file from the Colab environment to Google Drive\n", + "shutil.copyfile(source_path, destination_path)\n", + "\n", + "# Print a confirmation message\n", + "print(f\"Copied {source_path} to {destination_path}\")" + ], + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file