mirror of
https://github.com/openai/openai-cookbook
synced 2024-11-11 13:11:02 +00:00
2167 lines
133 KiB
Plaintext
2167 lines
133 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "I_IHQTO8xXBn"
|
||
},
|
||
"source": [
|
||
"# Synthetic Data generation (Part 1)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "VBoxtnxVdTWZ"
|
||
},
|
||
"source": [
|
||
"\n",
|
||
"Synthetic data generation using large language models (LLMs) offers a powerful solution to a commonly faced problem: the availability of high-quality, diverse, and privacy-compliant data. This could be used in a number of scenarios such as training a data science machine learning model (SVMs, decision trees, KNN's), finetuning a different GPT model on the data, as a solution to the coldstart problem, helping build compelling demos/apps with realistic data, scenario testing etc.\n",
|
||
"\n",
|
||
"There are a number of key drivers which may see you wanting to leverage synthetic data. \n",
|
||
"1. Human data may have privacy restrictions and/or identifiable data within it which we do not want to be used. \n",
|
||
"2. Synthetic data can be much more structured and therefore easier to manipulate than real data. \n",
|
||
"3. In domains where data is sparse or data of certain categories is sparse we may want to augment the data. \n",
|
||
"4. When dealing with imbalanced datasets or datasets which lack diversity, we may want to create data to improve the richness of our datasets.\n",
|
||
"\n",
|
||
"Unlike traditional data augmentation or manual data creation methods, using LLMs allows for the generation of rich, nuanced, and contextually relevant datasets that can significantly enhance it's usefulness to enterprises and developers.\n",
|
||
"\n",
|
||
"We split this tutorial into 2 parts. In this cookbook, we will have the following agenda:\n",
|
||
"1. CSV with a structured prompt\n",
|
||
"2. CSV with a Python program\n",
|
||
"3. Multitable CSV with a python program\n",
|
||
"4. Simply creating textual data\n",
|
||
"5. Dealing with imbalanced or non-diverse textual data\n",
|
||
"while in part 2, we will look at prompting strategies for getting better textual data.\n",
|
||
"\n",
|
||
"The last two in particular are useful for creating synthetic data to finetune another GPT model. For example using higher quality data produced by `gpt-4o` to finetune the cheaper and quicker `gpt-3.5-turbo` for improved performance while reducing costs.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "NE9Rr29zlRsA"
|
||
},
|
||
"source": [
|
||
"### Getting setup"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {
|
||
"id": "YGncxYrgQ8eb"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"%pip install openai\n",
|
||
"%pip install pandas\n",
|
||
"%pip install scikit-learn\n",
|
||
"%pip install matplotlib"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {
|
||
"id": "8pzwvE-YQPtU"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from openai import OpenAI\n",
|
||
"import os\n",
|
||
"import re\n",
|
||
"import numpy as np\n",
|
||
"import pandas as pd\n",
|
||
"from sklearn.cluster import KMeans\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import json\n",
|
||
"import matplotlib\n",
|
||
"\n",
|
||
"client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"<your OpenAI API key if not set as env var>\"))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "B8eAx4-JxaZB"
|
||
},
|
||
"source": [
|
||
"### 1. CSV with a structure prompt\n",
|
||
"Here we create data in the simplest way. You can quickly generate data by addressing 3 key points: telling it the format of the data (CSV), the schema, and useful information regarding how columns relate (the LLM will be able to deduce this from the column names but a helping hand will improve performance)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "dqbvepd0n4vS",
|
||
"outputId": "8735cacc-baa5-463e-938c-783e6b508b00"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"```csv\n",
|
||
"id,house_size_m2,house_price,location,number_of_bedrooms\n",
|
||
"1,50,150000,Suburban,2\n",
|
||
"2,75,250000,City Center,3\n",
|
||
"3,100,350000,Suburban,4\n",
|
||
"4,120,450000,Suburban,4\n",
|
||
"5,80,300000,City Center,3\n",
|
||
"6,90,400000,City Center,3\n",
|
||
"7,150,600000,Premium Area,5\n",
|
||
"8,200,750000,Premium Area,5\n",
|
||
"9,55,180000,Suburban,2\n",
|
||
"10,300,950000,Premium Area,6\n",
|
||
"```\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"datagen_model = \"gpt-4o-mini\"\n",
|
||
"question = \"\"\"\n",
|
||
"Create a CSV file with 10 rows of housing data.\n",
|
||
"Each row should include the following fields:\n",
|
||
" - id (incrementing integer starting at 1)\n",
|
||
" - house size (m^2)\n",
|
||
" - house price\n",
|
||
" - location\n",
|
||
" - number of bedrooms\n",
|
||
"\n",
|
||
"Make sure that the numbers make sense (i.e. more rooms is usually bigger size, more expensive locations increase price. more size is usually higher price etc. make sure all the numbers make sense). Also only respond with the CSV.\n",
|
||
"\"\"\"\n",
|
||
"\n",
|
||
"response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
")\n",
|
||
"res = response.choices[0].message.content\n",
|
||
"print(res)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "6ym0NiIyxiVj"
|
||
},
|
||
"source": [
|
||
"### 2. CSV with a Python program\n",
|
||
"The issue with generating data directly is we are limited in the amount of data we can generate because of the context. Instead what we can do is ask the LLM to generate a python program to generate the synthetic data. This allows us to scale to much more data while also providing us a view into how the data was generated by inspecting the python program.\n",
|
||
"\n",
|
||
"This would then let us edit the python program as we desire while giving us a good basis to start from.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "2yDuwB5ZxWS3",
|
||
"outputId": "dcbe1093-90f0-4f60-d9c6-34bf679bb092"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Certainly! Below is a Python program that generates synthetic housing data according to your specifications. We will create a pandas DataFrame with the defined fields and characteristics.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"import pandas as pd\n",
|
||
"import random\n",
|
||
"\n",
|
||
"def generate_housing_data(num_rows):\n",
|
||
" data = []\n",
|
||
" \n",
|
||
" locations = [\n",
|
||
" ('City Center', 10000, 150), # (location name, base price per m², base size)\n",
|
||
" ('Suburban Area', 8000, 100),\n",
|
||
" ('Country Side', 5000, 80),\n",
|
||
" ('Coastal Region', 12000, 110),\n",
|
||
" ('Urban Neighborhood', 9000, 130)\n",
|
||
" ]\n",
|
||
" \n",
|
||
" for i in range(1, num_rows + 1):\n",
|
||
" # Randomly pick a location\n",
|
||
" location, base_price_per_m2, base_size = random.choice(locations)\n",
|
||
" \n",
|
||
" # Generate number of bedrooms (1 to 5)\n",
|
||
" number_of_bedrooms = random.randint(1, 5)\n",
|
||
" \n",
|
||
" # Calculate house size based on the number of bedrooms\n",
|
||
" house_size = base_size + (10 * number_of_bedrooms) + random.randint(-5, 15) # Adding some noise\n",
|
||
" \n",
|
||
" # Calculate house price based on house size and location\n",
|
||
" house_price = base_price_per_m2 * house_size + random.randint(-5000, 10000) # Adding some noise\n",
|
||
"\n",
|
||
" # Append the generated data to the list\n",
|
||
" data.append({\n",
|
||
" 'id': i,\n",
|
||
" 'house_size_m2': house_size,\n",
|
||
" 'house_price': house_price,\n",
|
||
" 'location': location,\n",
|
||
" 'number_of_bedrooms': number_of_bedrooms\n",
|
||
" })\n",
|
||
"\n",
|
||
" # Create a pandas DataFrame\n",
|
||
" df = pd.DataFrame(data)\n",
|
||
" return df\n",
|
||
"\n",
|
||
"# Generate 100 rows of housing data\n",
|
||
"housing_data_df = generate_housing_data(100)\n",
|
||
"\n",
|
||
"# Show the result\n",
|
||
"print(housing_data_df)\n",
|
||
"```\n",
|
||
"\n",
|
||
"### Explanation:\n",
|
||
"- The `generate_housing_data` function creates synthetic housing data for a specified number of rows (`num_rows`).\n",
|
||
"- We define different locations with corresponding base prices per square meter and average house sizes.\n",
|
||
"- For each house, we randomly select a location, number of bedrooms, and calculate house size and price to ensure a sensible correlation between the values.\n",
|
||
"- Finally, we create a pandas DataFrame from the generated data and return it.\n",
|
||
"\n",
|
||
"You can run this program in your Python environment, and it will output a DataFrame containing 100 rows of synthetic housing data.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"question = \"\"\"\n",
|
||
"Create a Python program to generate 100 rows of housing data.\n",
|
||
"I want you to at the end of it output a pandas dataframe with 100 rows of data.\n",
|
||
"Each row should include the following fields:\n",
|
||
" - id (incrementing integer starting at 1)\n",
|
||
" - house size (m^2)\n",
|
||
" - house price\n",
|
||
" - location\n",
|
||
" - number of bedrooms\n",
|
||
"\n",
|
||
"Make sure that the numbers make sense (i.e. more rooms is usually bigger size, more expensive locations increase price. more size is usually higher price etc. make sure all the numbers make sense).\n",
|
||
"\"\"\"\n",
|
||
"\n",
|
||
"response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
")\n",
|
||
"res = response.choices[0].message.content\n",
|
||
"print(res)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"We need to make sure to parse the output of this appropriately as often there may be surrounding text to the python code. We can also explicitly ask it to state all assumptions it made about the data it's generating, however in this circumstance it told us that automatically."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "HZaJs7q8xm3L"
|
||
},
|
||
"source": [
|
||
"### 3. Multitable CSV with a python program\n",
|
||
"For more complex relationships however we need to make sure to specify a few more characteristics. \n",
|
||
"\n",
|
||
"To create multiple different datasets which relate to each other (for example housing, location, house type), as before we would need to specify the format, schema and useful information. However, the useful information required to get good performance is higher now. It's case-specific but a good amount of things to describe would be how the datasets relate to each other, addressing the size of the datasets in relation to one another, making sure foreign and primary keys are made appropriately and ideally using previously generated datasets to populate new ones so the actual data values match where necessary."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "3TWAhIYIxnbS",
|
||
"outputId": "8f766838-b2f0-419a-a4fb-543d029afce5"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Certainly! Below is a Python program that generates the three specified pandas DataFrames for housing data, location data, and house types. Each DataFrame will include the necessary fields, and the foreign keys will ensure proper relationships among them.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"import pandas as pd\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"# Set random seed for reproducibility\n",
|
||
"np.random.seed(0)\n",
|
||
"\n",
|
||
"# Function to generate location DataFrame\n",
|
||
"def generate_location_data(num_locations):\n",
|
||
" locations = {\n",
|
||
" \"id\": range(1, num_locations + 1),\n",
|
||
" \"country\": np.random.choice(['USA', 'Canada', 'UK'], num_locations),\n",
|
||
" \"city\": np.random.choice(['New York', 'Toronto', 'London', 'Vancouver', 'Manchester'], num_locations),\n",
|
||
" \"population\": np.random.randint(50000, 1000000, num_locations),\n",
|
||
" \"area\": np.random.randint(10000, 500000, num_locations)\n",
|
||
" }\n",
|
||
" return pd.DataFrame(locations)\n",
|
||
"\n",
|
||
"# Function to generate house types DataFrame\n",
|
||
"def generate_house_type_data(num_house_types):\n",
|
||
" house_types = {\n",
|
||
" \"id\": range(1, num_house_types + 1),\n",
|
||
" \"house_type\": np.random.choice(['Detached', 'Semi-Detached', 'Terraced', 'Flat'], num_house_types),\n",
|
||
" \"average_house_type_price\": np.random.randint(100000, 1000000, num_house_types),\n",
|
||
" \"number_of_houses\": np.random.randint(10, 1000, num_house_types)\n",
|
||
" }\n",
|
||
" return pd.DataFrame(house_types)\n",
|
||
"\n",
|
||
"# Function to generate housing data DataFrame\n",
|
||
"def generate_housing_data(num_houses, location_df, house_type_df):\n",
|
||
" house_sizes = np.random.randint(50, 300, num_houses) # size in m^2\n",
|
||
" location_ids = np.random.choice(location_df['id'], num_houses)\n",
|
||
" house_type_ids = np.random.choice(house_type_df['id'], num_houses)\n",
|
||
" \n",
|
||
" # Generate prices based on size, location, and house type\n",
|
||
" house_prices = (house_sizes * np.random.randint(2000, 5000, num_houses) // 10) + \\\n",
|
||
" (location_ids * 1000) + \\\n",
|
||
" (house_type_df.loc[house_type_ids - 1, 'average_house_type_price'].values // 4)\n",
|
||
" \n",
|
||
" housing_data = {\n",
|
||
" \"id\": range(1, num_houses + 1),\n",
|
||
" \"house_size\": house_sizes,\n",
|
||
" \"house_price\": house_prices,\n",
|
||
" \"location_id\": location_ids,\n",
|
||
" \"bedrooms\": np.random.randint(1, 6, num_houses),\n",
|
||
" \"house_type_id\": house_type_ids\n",
|
||
" }\n",
|
||
" \n",
|
||
" return pd.DataFrame(housing_data)\n",
|
||
"\n",
|
||
"# Generate DataFrames\n",
|
||
"num_locations = 10\n",
|
||
"num_house_types = 4\n",
|
||
"num_houses = 100\n",
|
||
"\n",
|
||
"location_df = generate_location_data(num_locations)\n",
|
||
"house_type_df = generate_house_type_data(num_house_types)\n",
|
||
"housing_df = generate_housing_data(num_houses, location_df, house_type_df)\n",
|
||
"\n",
|
||
"# Display the generated DataFrames\n",
|
||
"print(\"Location DataFrame:\")\n",
|
||
"print(location_df.head(), \"\\n\")\n",
|
||
"\n",
|
||
"print(\"House Types DataFrame:\")\n",
|
||
"print(house_type_df.head(), \"\\n\")\n",
|
||
"\n",
|
||
"print(\"Housing DataFrame:\")\n",
|
||
"print(housing_df.head(), \"\\n\")\n",
|
||
"\n",
|
||
"# Printing the DataFrame shapes\n",
|
||
"print(f\"Shapes: \\nLocation: {location_df.shape}, House Types: {house_type_df.shape}, Housing: {housing_df.shape}\")\n",
|
||
"```\n",
|
||
"\n",
|
||
"### Explanation of the Code:\n",
|
||
"1. **Location DataFrame:** \n",
|
||
" - Generates random locations with attributes such as country, city, population, and area.\n",
|
||
" \n",
|
||
"2. **House Types DataFrame:** \n",
|
||
" - Generates different types of houses along with average prices and quantity available.\n",
|
||
" \n",
|
||
"3. **Housing DataFrame:** \n",
|
||
" - Generates housing data with increments on price based on house size, location, and house type, while also ensuring foreign keys (IDs) for location and house type.\n",
|
||
"\n",
|
||
"### Output:\n",
|
||
"The three DataFrames generated will logically relate to one another with consistent data types and primary–foreign key relationships, resulting in a coherent representation of the housing dataset. The output displays heads of each DataFrame and their shapes for verification.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"question = \"\"\"\n",
|
||
"Create a Python program to generate 3 different pandas dataframes.\n",
|
||
"\n",
|
||
"1. Housing data\n",
|
||
"I want 100 rows. Each row should include the following fields:\n",
|
||
" - id (incrementing integer starting at 1)\n",
|
||
" - house size (m^2)\n",
|
||
" - house price\n",
|
||
" - location\n",
|
||
" - number of bedrooms\n",
|
||
" - house type\n",
|
||
" + any relevant foreign keys\n",
|
||
"\n",
|
||
"2. Location\n",
|
||
"Each row should include the following fields:\n",
|
||
" - id (incrementing integer starting at 1)\n",
|
||
" - country\n",
|
||
" - city\n",
|
||
" - population\n",
|
||
" - area (m^2)\n",
|
||
" + any relevant foreign keys\n",
|
||
"\n",
|
||
" 3. House types\n",
|
||
" - id (incrementing integer starting at 1)\n",
|
||
" - house type\n",
|
||
" - average house type price\n",
|
||
" - number of houses\n",
|
||
" + any relevant foreign keys\n",
|
||
"\n",
|
||
"Make sure that the numbers make sense (i.e. more rooms is usually bigger size, more expensive locations increase price. more size is usually higher price etc. make sure all the numbers make sense).\n",
|
||
"Make sure that the dataframe generally follow common sense checks, e.g. the size of the dataframes make sense in comparison with one another.\n",
|
||
"Make sure the foreign keys match up and you can use previously generated dataframes when creating each consecutive dataframes.\n",
|
||
"You can use the previously generated dataframe to generate the next dataframe.\n",
|
||
"\"\"\"\n",
|
||
"\n",
|
||
"response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
")\n",
|
||
"res = response.choices[0].message.content\n",
|
||
"print(res)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "Yv9XlRtauZYZ"
|
||
},
|
||
"source": [
|
||
"### 4. Simply creating textual data\n",
|
||
"Here we take a first look at creating textual data. This can be used to finetune another GPT model for example. In this case we imagine ourselves a retailer trying to streamline the process of creating descriptions for items they are selling. We again need to specify the format of the data, in particular in this case we want one which is easy to parse as an output."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"The example we consider below is one in which we want to create input output training pairs for GPT model to finetune on. We will have the products' name and the category it belongs to as input and the output will be a description. \n",
|
||
"\n",
|
||
"Specifying the structure of the output explicitly and giving commands to not deviate from this help enforce the output structure. You can run this in a loop and append the data to generate more synthetic data. Again, as before we will need to parse the data well so that our code further downstream does not break."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {
|
||
"id": "2KJVwjV0upby"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"1.\n",
|
||
"Input: Wireless Bluetooth Headphones, Electronics\n",
|
||
"Output: Immerse yourself in high-quality sound with these Wireless Bluetooth Headphones, featuring active noise cancellation and a comfortable over-ear design for extended listening sessions.\n",
|
||
"\n",
|
||
"2.\n",
|
||
"Input: Organic Green Tea, Beverages\n",
|
||
"Output: Enjoy a refreshing cup of Organic Green Tea, sourced from the finest leaves, packed with antioxidants, and perfect for a healthy, invigorating boost anytime.\n",
|
||
"\n",
|
||
"3.\n",
|
||
"Input: Stainless Steel Kitchen Knife, Kitchenware\n",
|
||
"Output: Cut with precision and ease using this Stainless Steel Kitchen Knife, designed with an ergonomic handle and a sharp blade for all your culinary tasks.\n",
|
||
"\n",
|
||
"4.\n",
|
||
"Input: Hiking Backpack, Outdoor Gear\n",
|
||
"Output: Explore the great outdoors with this durable Hiking Backpack, featuring multiple compartments for optimal organization and a breathable design for ultimate comfort on long treks.\n",
|
||
"\n",
|
||
"5.\n",
|
||
"Input: Air Fryer, Kitchen Appliances\n",
|
||
"Output: Cook your favorite meals with less oil using this Air Fryer\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"output_string = \"\"\n",
|
||
"for i in range(3):\n",
|
||
" question = f\"\"\"\n",
|
||
" I am creating input output training pairs to fine tune my gpt model. The usecase is a retailer generating a description for a product from a product catalogue. I want the input to be product name and category (to which the product belongs to) and output to be description.\n",
|
||
" The format should be of the form:\n",
|
||
" 1.\n",
|
||
" Input: product_name, category\n",
|
||
" Output: description\n",
|
||
" 2.\n",
|
||
" Input: product_name, category\n",
|
||
" Output: description\n",
|
||
"\n",
|
||
" Do not add any extra characters around that formatting as it will make the output parsing break.\n",
|
||
" Create as many training pairs as possible.\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
" )\n",
|
||
" res = response.choices[0].message.content\n",
|
||
" output_string += res + \"\\n\" + \"\\n\"\n",
|
||
"print(output_string[:1000]) #displaying truncated response\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "K5EmKTEa7GlC"
|
||
},
|
||
"source": [
|
||
"Note: the above output is truncated. And now we can parse it as below to get a list of products, categories and their descriptions. For example, let's take a look at the products it's generated."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "owvoyJBh0o2n",
|
||
"outputId": "ee48bcc9-fd29-42bf-9beb-ef3800cdbcb2"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"['Wireless Bluetooth Headphones',\n",
|
||
" 'Organic Green Tea',\n",
|
||
" 'Stainless Steel Kitchen Knife',\n",
|
||
" 'Hiking Backpack',\n",
|
||
" 'Air Fryer',\n",
|
||
" \"Kids' Educational Tablet\",\n",
|
||
" 'Bluetooth Speaker',\n",
|
||
" 'Yoga Mat',\n",
|
||
" 'Memory Foam Mattress',\n",
|
||
" 'Smartwatch',\n",
|
||
" 'Leather Wallet',\n",
|
||
" 'Portable Phone Charger',\n",
|
||
" 'Non-Stick Cookware Set',\n",
|
||
" 'Pet Dog Bed',\n",
|
||
" 'Fitness Tracker',\n",
|
||
" 'Wireless Earbuds',\n",
|
||
" 'Organic Green Tea',\n",
|
||
" 'Reusable Water Bottle',\n",
|
||
" 'Yoga Mat',\n",
|
||
" 'Leather Wallet',\n",
|
||
" 'Air Fryer',\n",
|
||
" 'Gaming Mouse',\n",
|
||
" 'Crochet Kit',\n",
|
||
" 'Hiking Boots',\n",
|
||
" 'Scented Candles',\n",
|
||
" 'Bluetooth Speaker',\n",
|
||
" 'Stainless Steel Cookware Set',\n",
|
||
" 'Fitness Tracker',\n",
|
||
" 'Decorative Throw Pillows',\n",
|
||
" 'Eco-Friendly Cleaning Supplies',\n",
|
||
" 'Wireless Noise Cancelling Headphones',\n",
|
||
" 'Organic Green Tea',\n",
|
||
" 'Adjustable Yoga Mat',\n",
|
||
" 'Bluetooth Smart Scale',\n",
|
||
" 'Stainless Steel Water Bottle',\n",
|
||
" 'Soft Cotton Bedding Set',\n",
|
||
" 'Multi-Functional Kitchen Blender',\n",
|
||
" 'Eco-Friendly Reusable Bags',\n",
|
||
" 'Portable Phone Charger',\n",
|
||
" 'Classic Leather Wallet',\n",
|
||
" 'Suede Chelsea Boots',\n",
|
||
" 'Non-Stick Cookware Set',\n",
|
||
" 'Pet-Friendly Indoor Plants',\n",
|
||
" 'High-Protein Snack Bars',\n",
|
||
" 'LED Desk Lamp with USB Port']"
|
||
]
|
||
},
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"#regex to parse data\n",
|
||
"pattern = re.compile(r'Input:\\s*(.+?),\\s*(.+?)\\nOutput:\\s*(.+?)(?=\\n\\n|\\Z)', re.DOTALL)\n",
|
||
"matches = pattern.findall(output_string)\n",
|
||
"products = []\n",
|
||
"categories = []\n",
|
||
"descriptions = []\n",
|
||
"\n",
|
||
"for match in matches:\n",
|
||
" product, category, description = match\n",
|
||
" products.append(product.strip())\n",
|
||
" categories.append(category.strip())\n",
|
||
" descriptions.append(description.strip())\n",
|
||
"products"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "bO3PgRwpyocn"
|
||
},
|
||
"source": [
|
||
"\n",
|
||
"### 5. Dealing with imbalanced or non-diverse textual data\n",
|
||
"Some of the most important aspects of generating high-quality synthetic data are accuracy (does the data make sense), consistency (are two separate data points for the same input roughly the same) and diversity (making sure our data distribution matches as much of the distribution that exists in production).\n",
|
||
"\n",
|
||
"\n",
|
||
"To increase the diversity of our data, we start first by clustering the data. This will provide us information about which clusters are underrepresented (imbalanced dataset) or which data is not addressed at all (widening the data distribution). Then, we will either suggest new clusters (using self-reflection type call from GPT) or ask the next iteration of our synthetic generation calls to explicitly target the underrepresented clusters. \n",
|
||
"\n",
|
||
"We can then recursively run this generation and analysis of cluster loop to automate generating diverse synthetic data."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "ubdPEFYR-myU"
|
||
},
|
||
"source": [
|
||
"For demonstrative purposes, we explicitly prompt the LLM to generate information about 4 different topical areas: vehicle, clothing, toiletries, food. We will then cluster the data and see if it managed to find these 4 topic areas."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "m-yncn8s1hWZ",
|
||
"outputId": "35ae248d-4859-4d3f-ba29-94478aed7305"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"1. vehicle \n",
|
||
"Input: \"Tesla Model 3, Electric Car\" \n",
|
||
"Output: \"The Tesla Model 3 is a revolutionary electric car with impressive range and cutting-edge technology, designed to provide an exhilarating driving experience while minimizing environmental impact.\"\n",
|
||
"\n",
|
||
"2. clothing \n",
|
||
"Input: \"Nike Air Max, Shoes\" \n",
|
||
"Output: \"Elevate your sneaker game with Nike Air Max. Combining iconic style with superior comfort and support, these shoes are perfect for both workouts and casual outings.\"\n",
|
||
"\n",
|
||
"3. toiletries \n",
|
||
"Input: \"Oral-B Pro 1000, Electronic Toothbrush\" \n",
|
||
"Output: \"Achieve a superior clean with the Oral-B Pro 1000. This electronic toothbrush features 3D cleaning action that pulsates and oscillates to remove more plaque than a regular manual toothbrush.\"\n",
|
||
"\n",
|
||
"4. food \n",
|
||
"Input: \"Chobani Greek Yogurt, Yogurt\" \n",
|
||
"Output: \"Indulge in a nutritious snack with Chobani Greek Yogurt. Packed with protein and delicious flavors, it’s the perfect choice for a healthy breakfast or a satisfying treat anytime.\"\n",
|
||
"\n",
|
||
"5. vehicle \n",
|
||
"\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"output_string = \"\"\n",
|
||
"for i in range(3):\n",
|
||
" question = f\"\"\"\n",
|
||
" I am creating input output training pairs to fine tune my gpt model. I want the input to be product name and category and output to be description. the category should be things like: mobile phones, shoes, headphones, laptop, electronic toothbrush, etc. and also more importantly the categories should come under 4 main topics: vehicle, clothing, toiletries, food)\n",
|
||
" After the number of each example also state the topic area. The format should be of the form:\n",
|
||
" 1. topic_area\n",
|
||
" Input: product_name, category\n",
|
||
" Output: description\n",
|
||
"\n",
|
||
" Do not add any extra characters around that formatting as it will make the output parsing break.\n",
|
||
"\n",
|
||
" Here are some helpful examples so you get the style of output correct.\n",
|
||
"\n",
|
||
" 1) clothing\n",
|
||
" Input: \"Shoe Name, Shoes\"\n",
|
||
" Output: \"Experience unparalleled comfort. These shoes feature a blend of modern style and the traditional superior cushioning, perfect for those always on the move.\"\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" response = client.chat.completions.create(\n",
|
||
" model=\"gpt-4o-mini\",\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
" )\n",
|
||
" res = response.choices[0].message.content\n",
|
||
" output_string += res + \"\\n\" + \"\\n\"\n",
|
||
"print(output_string[:1000]) #displaying truncated response"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Note: The above output is truncated. In the example above, we would explicitly include the topic area as part of the response per example as it helps condition the proceeding output and tends to give better performance. We can also give it an actual example of what the output should look like so it gets the right idea of style of output but also to help enforce structure."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"metadata": {
|
||
"id": "k7GAokEC1hUY"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"pattern = re.compile(r'(\\d+)\\.\\s*(\\w+)\\s*Input:\\s*\"(.+?),\\s*(.+?)\"\\s*Output:\\s*\"(.*?)\"', re.DOTALL)\n",
|
||
"matches = pattern.findall(output_string)\n",
|
||
"\n",
|
||
"topics = []\n",
|
||
"products = []\n",
|
||
"categories = []\n",
|
||
"descriptions = []\n",
|
||
"\n",
|
||
"for match in matches:\n",
|
||
" number, topic, product, category, description = match\n",
|
||
" topics.append(topic)\n",
|
||
" products.append(product)\n",
|
||
" categories.append(category)\n",
|
||
" descriptions.append(description)\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "Z49B3LrJ1hSG",
|
||
"outputId": "d76a9038-1879-44d9-f1db-dc933e066c54"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"['Tesla Model 3',\n",
|
||
" 'Nike Air Max',\n",
|
||
" 'Oral-B Pro 1000',\n",
|
||
" 'Chobani Greek Yogurt',\n",
|
||
" 'Ford F-150',\n",
|
||
" \"Levi's 511\",\n",
|
||
" 'Philips Sonicare',\n",
|
||
" 'Quaker Oatmeal',\n",
|
||
" 'Toyota Camry',\n",
|
||
" 'Adidas Ultraboost',\n",
|
||
" 'Toyota Camry',\n",
|
||
" 'Nike Air Max',\n",
|
||
" 'Colgate Electric Toothbrush',\n",
|
||
" 'Blue Diamond Almonds',\n",
|
||
" 'Harley Davidson Fat Boy',\n",
|
||
" 'Adidas UltraBoost',\n",
|
||
" \"Dove Men's Body Wash\",\n",
|
||
" 'Quaker Oats',\n",
|
||
" 'Ford F-150',\n",
|
||
" \"Levi's 501 Jeans\",\n",
|
||
" 'Tesla Model 3',\n",
|
||
" 'Nike Air Max',\n",
|
||
" 'Oral-B Pro 1000',\n",
|
||
" 'Organic Almond Butter',\n",
|
||
" 'Yamaha YZF-R3',\n",
|
||
" 'Adidas Ultraboost',\n",
|
||
" 'Philips Sonicare',\n",
|
||
" 'Organic Quinoa']"
|
||
]
|
||
},
|
||
"execution_count": 23,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"products"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "J1qKxLkAzKq0"
|
||
},
|
||
"source": [
|
||
"We will now cluster the data to analyze it. We will use K-means clustering to segregate the data. An important parameter of K-means to set is K, the number of clusters.\n",
|
||
"\n",
|
||
"We know that there should be 4 cluster (4 topics) since we specified this in prompt: vehicle, electronics, clothing, food. However in general for our data, we do not know the number of clusters that exist. Therefore we will use the elbow method to find the optimal number of clusters.\n",
|
||
"\n",
|
||
"In the elbow method, we iterate through a range of different K's, each time storing the inertia. The inertia measures the sum of the squared distances between each point in a cluster and the centroid of that cluster thus telling us how well-separated and dense each cluster is. If we plot K against the inertia, we are able to see how the inertia drops and where the drop in inertia is least rapid (often making an elbow shape) we can set our optimal number of clusters. You can read into more depth about the elbow method [here](https://en.wikipedia.org/wiki/Elbow_method_(clustering))."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "1BxwPTkpGzu8"
|
||
},
|
||
"source": [
|
||
"First let's store our data into a pandas dataframe for ease of analysis\n",
|
||
"\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"metadata": {
|
||
"id": "XcPBzORtKWv6"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"data = {\n",
|
||
" 'Product': products,\n",
|
||
" 'Category': categories,\n",
|
||
" 'Description': descriptions\n",
|
||
"}\n",
|
||
"\n",
|
||
"df = pd.DataFrame(data)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "HQbg6r37KjG0"
|
||
},
|
||
"source": [
|
||
"Next let us embed our data as the embeddings is what we will cluster since they should be close to each other in vector space if they are similar."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 25,
|
||
"metadata": {
|
||
"id": "l8M7MX-1Jctr"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"def get_embedding(text, model=\"text-embedding-3-small\"):\n",
|
||
" text = text.replace(\"\\n\", \" \")\n",
|
||
"\n",
|
||
" response = client.embeddings.create(input=[text], model=model)\n",
|
||
"\n",
|
||
" return response.data[0].embedding\n",
|
||
"\n",
|
||
"embedding_model = \"text-embedding-3-small\"\n",
|
||
"df[\"embedding\"] = df.Category.apply(lambda x: get_embedding(x, model=embedding_model))\n",
|
||
"\n",
|
||
"# Ensure there are embeddings to concatenate\n",
|
||
"if len(df.embedding.values) > 0:\n",
|
||
" matrix = np.vstack(df.embedding.values)\n",
|
||
"else:\n",
|
||
" matrix = np.array([]) # Handle the case where there are no embeddings\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 26,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Product</th>\n",
|
||
" <th>Category</th>\n",
|
||
" <th>Description</th>\n",
|
||
" <th>embedding</th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>0</th>\n",
|
||
" <td>Tesla Model 3</td>\n",
|
||
" <td>Electric Car</td>\n",
|
||
" <td>The Tesla Model 3 is a revolutionary electric ...</td>\n",
|
||
" <td>[0.003255360759794712, -0.039260633289813995, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Elevate your sneaker game with Nike Air Max. C...</td>\n",
|
||
" <td>[0.03943369910120964, 0.022045187652111053, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2</th>\n",
|
||
" <td>Oral-B Pro 1000</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Achieve a superior clean with the Oral-B Pro 1...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>3</th>\n",
|
||
" <td>Chobani Greek Yogurt</td>\n",
|
||
" <td>Yogurt</td>\n",
|
||
" <td>Indulge in a nutritious snack with Chobani Gre...</td>\n",
|
||
" <td>[0.0208318829536438, -0.02645781636238098, -0....</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>4</th>\n",
|
||
" <td>Ford F-150</td>\n",
|
||
" <td>Pickup Truck</td>\n",
|
||
" <td>The Ford F-150 is the ultimate pickup truck, d...</td>\n",
|
||
" <td>[0.007467855699360371, -0.05288049206137657, -...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>5</th>\n",
|
||
" <td>Levi's 511</td>\n",
|
||
" <td>Jeans</td>\n",
|
||
" <td>Step out in style with Levi's 511 jeans. Featu...</td>\n",
|
||
" <td>[0.0037206460256129503, 0.022772302851080894, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>6</th>\n",
|
||
" <td>Philips Sonicare</td>\n",
|
||
" <td>Electric Toothbrush</td>\n",
|
||
" <td>Discover a new level of oral care with the Phi...</td>\n",
|
||
" <td>[-0.00724813062697649, -0.011600878089666367, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>7</th>\n",
|
||
" <td>Quaker Oatmeal</td>\n",
|
||
" <td>Breakfast Cereal</td>\n",
|
||
" <td>Start your day right with Quaker Oatmeal. This...</td>\n",
|
||
" <td>[-0.006529285106807947, 0.007865572348237038, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>8</th>\n",
|
||
" <td>Toyota Camry</td>\n",
|
||
" <td>Sedan</td>\n",
|
||
" <td>The Toyota Camry stands out in the sedan categ...</td>\n",
|
||
" <td>[-0.02088991366326809, -0.006191295105963945, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>9</th>\n",
|
||
" <td>Adidas Ultraboost</td>\n",
|
||
" <td>Running Shoes</td>\n",
|
||
" <td>Run like never before in the Adidas Ultraboost...</td>\n",
|
||
" <td>[0.02679188922047615, 0.014639599248766899, 8....</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>10</th>\n",
|
||
" <td>Toyota Camry</td>\n",
|
||
" <td>Car</td>\n",
|
||
" <td>The Toyota Camry is a reliable midsize sedan k...</td>\n",
|
||
" <td>[0.008056452497839928, -0.007912316359579563, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>11</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Step up your sneaker game with the Nike Air Ma...</td>\n",
|
||
" <td>[0.03943241760134697, 0.02208484522998333, -0....</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>12</th>\n",
|
||
" <td>Colgate Electric Toothbrush</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Transform your oral hygiene routine with the C...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>13</th>\n",
|
||
" <td>Blue Diamond Almonds</td>\n",
|
||
" <td>Nuts</td>\n",
|
||
" <td>Snack healthy with Blue Diamond Almonds. These...</td>\n",
|
||
" <td>[-0.013289917260408401, 0.036334190517663956, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>14</th>\n",
|
||
" <td>Harley Davidson Fat Boy</td>\n",
|
||
" <td>Motorcycle</td>\n",
|
||
" <td>Experience the thrill of the open road with th...</td>\n",
|
||
" <td>[0.012365399859845638, 0.03552943095564842, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>15</th>\n",
|
||
" <td>Adidas UltraBoost</td>\n",
|
||
" <td>Sneakers</td>\n",
|
||
" <td>Enjoy a perfect blend of comfort and performan...</td>\n",
|
||
" <td>[0.013107392005622387, 0.02963760495185852, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>16</th>\n",
|
||
" <td>Dove Men's Body Wash</td>\n",
|
||
" <td>Body Wash</td>\n",
|
||
" <td>Refresh and hydrate your skin with Dove Men's ...</td>\n",
|
||
" <td>[0.03760576993227005, -0.008475445210933685, -...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>17</th>\n",
|
||
" <td>Quaker Oats</td>\n",
|
||
" <td>Oats</td>\n",
|
||
" <td>Start your day right with Quaker Oats. Packed ...</td>\n",
|
||
" <td>[-0.00903365109115839, 0.00896345917135477, 0....</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>18</th>\n",
|
||
" <td>Ford F-150</td>\n",
|
||
" <td>Truck</td>\n",
|
||
" <td>The Ford F-150 is a durable and dependable tru...</td>\n",
|
||
" <td>[0.023461222648620605, -0.026651185005903244, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>19</th>\n",
|
||
" <td>Levi's 501 Jeans</td>\n",
|
||
" <td>Jeans</td>\n",
|
||
" <td>Discover the timeless style of Levi's 501 Jean...</td>\n",
|
||
" <td>[0.003762696636840701, 0.02275814116001129, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>20</th>\n",
|
||
" <td>Tesla Model 3</td>\n",
|
||
" <td>Mobile Phones</td>\n",
|
||
" <td>Explore the future of driving with the Tesla M...</td>\n",
|
||
" <td>[0.03703858703374863, 0.03407958149909973, 0.0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>21</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Step up your game with the Nike Air Max. Desig...</td>\n",
|
||
" <td>[0.03943369910120964, 0.022045187652111053, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>22</th>\n",
|
||
" <td>Oral-B Pro 1000</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Achieve a superior clean with the Oral-B Pro 1...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>23</th>\n",
|
||
" <td>Organic Almond Butter</td>\n",
|
||
" <td>Food</td>\n",
|
||
" <td>Indulge in the creamy goodness of Organic Almo...</td>\n",
|
||
" <td>[-0.014613640494644642, -0.002179765608161688,...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>24</th>\n",
|
||
" <td>Yamaha YZF-R3</td>\n",
|
||
" <td>Mobile Phones</td>\n",
|
||
" <td>Introducing the Yamaha YZF-R3, the ultimate sp...</td>\n",
|
||
" <td>[0.03703858703374863, 0.03407958149909973, 0.0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>25</th>\n",
|
||
" <td>Adidas Ultraboost</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Discover the Adidas Ultraboost, a shoe that of...</td>\n",
|
||
" <td>[0.03944042697548866, 0.022062409669160843, -0...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>26</th>\n",
|
||
" <td>Philips Sonicare</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Experience the dental care revolution with Phi...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>27</th>\n",
|
||
" <td>Organic Quinoa</td>\n",
|
||
" <td>Food</td>\n",
|
||
" <td>Nourish your body with Organic Quinoa, a nutri...</td>\n",
|
||
" <td>[-0.014613640494644642, -0.002179765608161688,...</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Product Category \\\n",
|
||
"0 Tesla Model 3 Electric Car \n",
|
||
"1 Nike Air Max Shoes \n",
|
||
"2 Oral-B Pro 1000 Electronic Toothbrush \n",
|
||
"3 Chobani Greek Yogurt Yogurt \n",
|
||
"4 Ford F-150 Pickup Truck \n",
|
||
"5 Levi's 511 Jeans \n",
|
||
"6 Philips Sonicare Electric Toothbrush \n",
|
||
"7 Quaker Oatmeal Breakfast Cereal \n",
|
||
"8 Toyota Camry Sedan \n",
|
||
"9 Adidas Ultraboost Running Shoes \n",
|
||
"10 Toyota Camry Car \n",
|
||
"11 Nike Air Max Shoes \n",
|
||
"12 Colgate Electric Toothbrush Electronic Toothbrush \n",
|
||
"13 Blue Diamond Almonds Nuts \n",
|
||
"14 Harley Davidson Fat Boy Motorcycle \n",
|
||
"15 Adidas UltraBoost Sneakers \n",
|
||
"16 Dove Men's Body Wash Body Wash \n",
|
||
"17 Quaker Oats Oats \n",
|
||
"18 Ford F-150 Truck \n",
|
||
"19 Levi's 501 Jeans Jeans \n",
|
||
"20 Tesla Model 3 Mobile Phones \n",
|
||
"21 Nike Air Max Shoes \n",
|
||
"22 Oral-B Pro 1000 Electronic Toothbrush \n",
|
||
"23 Organic Almond Butter Food \n",
|
||
"24 Yamaha YZF-R3 Mobile Phones \n",
|
||
"25 Adidas Ultraboost Shoes \n",
|
||
"26 Philips Sonicare Electronic Toothbrush \n",
|
||
"27 Organic Quinoa Food \n",
|
||
"\n",
|
||
" Description \\\n",
|
||
"0 The Tesla Model 3 is a revolutionary electric ... \n",
|
||
"1 Elevate your sneaker game with Nike Air Max. C... \n",
|
||
"2 Achieve a superior clean with the Oral-B Pro 1... \n",
|
||
"3 Indulge in a nutritious snack with Chobani Gre... \n",
|
||
"4 The Ford F-150 is the ultimate pickup truck, d... \n",
|
||
"5 Step out in style with Levi's 511 jeans. Featu... \n",
|
||
"6 Discover a new level of oral care with the Phi... \n",
|
||
"7 Start your day right with Quaker Oatmeal. This... \n",
|
||
"8 The Toyota Camry stands out in the sedan categ... \n",
|
||
"9 Run like never before in the Adidas Ultraboost... \n",
|
||
"10 The Toyota Camry is a reliable midsize sedan k... \n",
|
||
"11 Step up your sneaker game with the Nike Air Ma... \n",
|
||
"12 Transform your oral hygiene routine with the C... \n",
|
||
"13 Snack healthy with Blue Diamond Almonds. These... \n",
|
||
"14 Experience the thrill of the open road with th... \n",
|
||
"15 Enjoy a perfect blend of comfort and performan... \n",
|
||
"16 Refresh and hydrate your skin with Dove Men's ... \n",
|
||
"17 Start your day right with Quaker Oats. Packed ... \n",
|
||
"18 The Ford F-150 is a durable and dependable tru... \n",
|
||
"19 Discover the timeless style of Levi's 501 Jean... \n",
|
||
"20 Explore the future of driving with the Tesla M... \n",
|
||
"21 Step up your game with the Nike Air Max. Desig... \n",
|
||
"22 Achieve a superior clean with the Oral-B Pro 1... \n",
|
||
"23 Indulge in the creamy goodness of Organic Almo... \n",
|
||
"24 Introducing the Yamaha YZF-R3, the ultimate sp... \n",
|
||
"25 Discover the Adidas Ultraboost, a shoe that of... \n",
|
||
"26 Experience the dental care revolution with Phi... \n",
|
||
"27 Nourish your body with Organic Quinoa, a nutri... \n",
|
||
"\n",
|
||
" embedding \n",
|
||
"0 [0.003255360759794712, -0.039260633289813995, ... \n",
|
||
"1 [0.03943369910120964, 0.022045187652111053, -0... \n",
|
||
"2 [-0.003470012918114662, -0.01911414973437786, ... \n",
|
||
"3 [0.0208318829536438, -0.02645781636238098, -0.... \n",
|
||
"4 [0.007467855699360371, -0.05288049206137657, -... \n",
|
||
"5 [0.0037206460256129503, 0.022772302851080894, ... \n",
|
||
"6 [-0.00724813062697649, -0.011600878089666367, ... \n",
|
||
"7 [-0.006529285106807947, 0.007865572348237038, ... \n",
|
||
"8 [-0.02088991366326809, -0.006191295105963945, ... \n",
|
||
"9 [0.02679188922047615, 0.014639599248766899, 8.... \n",
|
||
"10 [0.008056452497839928, -0.007912316359579563, ... \n",
|
||
"11 [0.03943241760134697, 0.02208484522998333, -0.... \n",
|
||
"12 [-0.003470012918114662, -0.01911414973437786, ... \n",
|
||
"13 [-0.013289917260408401, 0.036334190517663956, ... \n",
|
||
"14 [0.012365399859845638, 0.03552943095564842, -0... \n",
|
||
"15 [0.013107392005622387, 0.02963760495185852, -0... \n",
|
||
"16 [0.03760576993227005, -0.008475445210933685, -... \n",
|
||
"17 [-0.00903365109115839, 0.00896345917135477, 0.... \n",
|
||
"18 [0.023461222648620605, -0.026651185005903244, ... \n",
|
||
"19 [0.003762696636840701, 0.02275814116001129, -0... \n",
|
||
"20 [0.03703858703374863, 0.03407958149909973, 0.0... \n",
|
||
"21 [0.03943369910120964, 0.022045187652111053, -0... \n",
|
||
"22 [-0.003470012918114662, -0.01911414973437786, ... \n",
|
||
"23 [-0.014613640494644642, -0.002179765608161688,... \n",
|
||
"24 [0.03703858703374863, 0.03407958149909973, 0.0... \n",
|
||
"25 [0.03944042697548866, 0.022062409669160843, -0... \n",
|
||
"26 [-0.003470012918114662, -0.01911414973437786, ... \n",
|
||
"27 [-0.014613640494644642, -0.002179765608161688,... "
|
||
]
|
||
},
|
||
"execution_count": 26,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"df"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "ZcdZscBNKy0F"
|
||
},
|
||
"source": [
|
||
"Now we perform the elbow method. "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 27,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/",
|
||
"height": 981
|
||
},
|
||
"id": "1Azw_xgVl_aY",
|
||
"outputId": "5b7076aa-a03c-4a40-f52c-08b09248cf18"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Determine the optimal number of clusters using the elbow method\n",
|
||
"inertias = []\n",
|
||
"range_of_clusters = range(1, 13) # Adjust the range as necessary\n",
|
||
"\n",
|
||
"for n_clusters in range_of_clusters:\n",
|
||
" kmeans = KMeans(n_clusters=n_clusters, init=\"k-means++\", random_state=42, n_init=10)\n",
|
||
" kmeans.fit(matrix)\n",
|
||
" inertias.append(kmeans.inertia_)\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"This will output a chart for us in which we have to visually tell where the optimal cluster point is. We can see below that we see a gradual decrease of inertia rather than a sharp elbow but the point of steepest decrease appears to occur around 3, 4 or 5 clusters which lines up with our expectations given our prompt. "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 28,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA0oAAAIjCAYAAAA9VuvLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAAB/oElEQVR4nO3dd1yVdf/H8ffhMEWGoCxBxC0OcmQ5cue4TStT245um5qpaWn9Sm1oe95m2dD20NLUypFbMzfuLW4QFWWoIHCu3x/GiamgwMV4PR+P8yiucc7nOgcP532uz/X9WgzDMAQAAAAAsHMwuwAAAAAAKGkISgAAAACQDUEJAAAAALIhKAEAAABANgQlAAAAAMiGoAQAAAAA2RCUAAAAACAbghIAAAAAZENQAgAAAIBsCEqACSwWi8aPH2//efz48bJYLDp9+rR5RZVQ1atX12233Vbkj7Ns2TJZLBYtW7asyB8LWbVv317t27c3u4xic+jQIVksFk2fPr1cPXZhyTiGt956y+xS8iUpKUmDBw9WQECALBaLhg8fXij3O336dFksFh06dKhQ7g9ATgQloJBk/NHK6/b333+bXeI1q169uiwWizp37pzr+k8//dR+nBs2bCjw/e/cuVPjx48vFX/w//rrL40fP17nzp0r1PvNCMsZtwoVKqhatWrq2bOnpk2bppSUlGu+76Kquaw4c+aMRo8erbp168rV1VU+Pj7q2rWr5s2bd133+9133+m9994rnCKLWcYXBxaLRRs3bsyxfuDAgapYsaIJlZU+EydO1PTp0/X444/r66+/1oMPPnjF7dPT0zVt2jS1b99ePj4+cnFxUfXq1TVo0KBren+9Vr///nuWL/SA8sjR7AKAsuall15SWFhYjuW1atUyoZrC4+rqqqVLlyomJkYBAQFZ1n377bdydXVVcnLyNd33zp07NWHCBLVv317Vq1cvhGqLzl9//aUJEyZo4MCB8vb2LvT7nzJliipWrKiUlBQdP35cCxYs0EMPPaT33ntP8+bNU0hISImr+XotXLjQtMfes2ePOnXqpFOnTmnQoEFq3ry5zp07p2+//VY9e/bUqFGj9Oabb17TfX/33Xfavn17jjMIoaGhunjxopycnArhCIre+PHjNXfuXLPLKLWWLFmim2++WePGjbvqthcvXlTv3r01f/58tW3bVs8995x8fHx06NAh/fTTT/ryyy915MgRBQcHF3ndv//+uyZPnkxYQrlGUAIKWffu3dW8eXOzyyh0rVu31vr16/Xjjz/qqaeesi8/duyYVq5cqTvvvFM///yziRWWDX369FHlypXtP7/44ov69ttv1b9/f/Xt27dEnZm8cOGCKlSocN334+zsXAjVFFxqaqr69Omjs2fPasWKFbrpppvs60aMGKH7779fb731lpo3b66777670B7XYrHI1dW10O6vKN1www2aN2+eNm3apKZNm5pdTrE6f/683N3dr/t+YmNjFR4enq9tR48erfnz5+vdd9/NEbDHjRund99997rrMZNhGEpOTpabm5vZpQD5QusdUIKcPn1a/fr1k6enp3x9ffXUU0/lOEuTlpaml19+WTVr1rS3ZDz33HNZWrNGjhwpX19fGYZhX/bkk0/KYrHogw8+sC87efKkLBaLpkyZctXaXF1d1bt3b3333XdZln///feqVKmSunbtmut+u3fvVp8+feTj4yNXV1c1b95cc+bMsa+fPn26+vbtK0nq0KGDvd0n+7VCq1atUosWLeTq6qoaNWroq6++yvFYBw8eVN++feXj46MKFSro5ptv1m+//ZZju2PHjumOO+6Qu7u7/Pz8NGLEiHy1to0fP16jR4+WJIWFhdlrzWgZzM9rcy3uv/9+DR48WGvXrtWiRYuyrFu7dq26desmLy8vVahQQe3atdPq1avzXbMkffPNN2rWrJnc3Nzk4+Oje+65R0ePHs3yOO3bt1fDhg21ceNGtW3bVhUqVNBzzz2X5XqRyZMnq0aNGqpQoYK6dOmio0ePyjAMvfzyywoODpabm5tuv/12xcXF5bjvzNcoZbR9/fTTT3r11VcVHBwsV1dXderUSfv378/x/FztOcjLzz//rO3bt2vMmDFZQpIkWa1WffLJJ/L29s7yjXpGbT/++KOee+45BQQEyN3dXb169crynLVv316//fabDh8+bH/OM86W5nadUEYr25EjR3TbbbepYsWKqlq1qiZPnixJ2rZtmzp27Ch3d3eFhobm+HcYFxenUaNGqVGjRqpYsaI8PT3VvXt3bdmy5arPw5U8+eSTqlSpUr7OKmS/9jJD9erVNXDgQPvPGW3Kq1at0rBhw1SlShV5e3vr0Ucf1aVLl3Tu3Dn1799flSpVUqVKlfTMM89keS/L7N1331VoaKjc3NzUrl07bd++Pcc2V3sPylzT8uXL9cQTT8jPz++qZ21iY2P13//+V/7+/nJ1dVVERIS+/PJL+/qM35WoqCj99ttvuf7by+zYsWP65JNPdOutt+Z6HZPVatWoUaOuWFd+X4PU1FRNmDBBtWvXlqurq3x9fdWmTRv7+8vAgQPtv3uZW4Iz2Gw2vffee2rQoIFcXV3l7++vRx99VGfPns3xuLfddpsWLFig5s2by83NTZ988okkadGiRWrTpo28vb1VsWJF1a1bV88991yexwaYgTNKQCGLj4/PMSiDxWKRr6/vVfft16+fqlevrkmTJunvv//WBx98oLNnz2YJBYMHD9aXX36pPn366Omnn9batWs1adIk7dq1S7NmzZIk3XLLLXr33Xe1Y8cONWzYUJK0cuVKOTg4aOXKlRo2bJh9mSS1bds2X8d23333qUuXLjpw4IBq1qwp6XJ7UZ8+fXJtI9qxY4dat26tqlWrasyYMXJ3d9dPP/2kO+64Qz///LPuvPNOtW3bVsOGDdMHH3yg5557TvXr15ck+38laf/+/erTp4/++9//asCAAfriiy80cOBANWvWTA0aNJB0OfS1atVKFy5c0LBhw+Tr66svv/xSvXr10syZM3XnnXdKutza0qlTJx05ckTDhg1TUFCQvv76ay1ZsuSqx9+7d2/t3btX33//vd599137mZ8qVark+7W5Vg8++KCmTp2qhQsX6tZbb5V0uaWne/fuatasmcaNGycHBwdNmzZNHTt21MqVK9WiRYur1vzqq6/qhRdeUL9+/TR48GCdOnVKH374odq2bavNmzdnadU7c+aMunfvrnvuuUcPPPCA/P397eu+/fZbXbp0SU8++aTi4uL0xhtvqF+/furYsaOWLVumZ599Vvv379eHH36oUaNG6YsvvrjqMb/22mtycHDQqFGjFB8frzfeeEP333+/1q5da98mP89BXjLayfr375/rei8vL91+++368ssvtX///izts6+++qosFoueffZZxcbG6r333lPnzp0VGRkpNzc3Pf/884qPj9exY8fsZwGudk1Penq6unfvrrZt2+qNN97Qt99+q6FDh8rd3V3PP/+87r//fvXu3Vsff/yx+vfvr5YtW9rbfA8ePKjZs2erb9++CgsL08mTJ/XJJ5+oXbt22rlzp4KCgq76fOfG09NTI0aM0IsvvljoZ5WefPJJBQQEaMKECfr77781depUeXt766+//lK1atU0ceJE/f7773rzzTfVsGHDHK/TV199pcTERA0ZMkTJycl6//331bFjR23bts3+u5mf96DMnnjiCVWpUkUvvviizp8/n2ftFy9eVPv27bV//34NHTpUYWFhmjFjhgYOHKhz587pqaeeUv369fX1119rxIgRCg4O1tNPPy3p33972f3xxx9KS0u76jVMhWH8+PGaNGmSBg8erBYtWighIUEbNmzQpk2bdOutt+rRRx/ViRMntGjRIn399dc59n/00Uc1ffp0DRo0SMOGDVNUVJT+97//afPmzVq9enWWvwd79uzRvffeq0cffVQPP/yw6tatqx07dui2225T48aN9dJLL8nFxUX79+/P1xccQLEyABSKadOmGZJyvbm4uGTZVpIxbtw4+8/jxo0zJBm9evXKst0TTzxhSDK2bNliGIZhREZGGpKMwYMHZ9lu1KhRhiRjyZIlhmEYRmxsrCHJ+OijjwzDMIxz584ZDg4ORt++fQ1/f3/7fsOGDTN8fHwMm812xWMLDQ01evToYaSlpRkBAQHGyy+/bBiGYezcudOQZCxfvtx+/OvXr7fv16lTJ6NRo0ZGcnKyfZnNZjNatWpl1K5d275sxowZhiRj6dKluT62JGPFihX2ZbGxsYaLi4vx9NNP25cNHz7ckGSsXLnSviwxMdEICwszqlevbqSnpxuGYRjvvfeeIcn46aef7NudP3/eqFWrVp41ZPbmm28akoyoqKgsy/P72uQl43fg1KlTua4/e/asIcm48847DcO4/DzWrl3b6Nq1a5bX78KFC0ZYWJhx6623XrXmQ4cOGVar1Xj11VezLN+2bZvh6OiYZXm7du0MScbHH3+cZduoqChDklGlShXj3Llz9uVjx441JBkRERFGamqqffm9995rODs7Z/mdaNeundGuXTv7z0uXLjUkGfXr1zdSUlLsy99//31DkrFt27YCPwe5ueGGGwwvL68rbvPOO+8Ykow5c+Zkqa1q1apGQkKCfbuffvrJkGS8//779mU9evQwQkNDc9xnxnM2bdo0+7IBAwYYkoyJEyfal509e9Zwc3MzLBaL8cMPP9iX7969O8d7SHJysv13PPPjuLi4GC+99NIVHzs3Gcc5Y8YM49y5c0alSpWyvD8NGDDAcHd3z7JP9poyhIaGGgMGDLD/nPFekf11a9mypWGxWIzHHnvMviwtLc0IDg7O8vuRcQxubm7GsWPH7MvXrl1rSDJGjBhhX5bf96CMmtq0aWOkpaVd8bkxjH/fR7755hv7skuXLhktW7Y0KlasmOV3I+P982pGjBhhSDI2b9581W0z15z533V+X4OIiIir1jRkyBAjt4+JK1euNCQZ3377bZbl8+fPz7E84/17/vz5WbZ99913r/h+B5QUtN4BhWzy5MlatGhRltsff/yRr32HDBmS5ecnn3xS0uWLajP/d+TIkVm2y/imMqPNrEqVKqpXr55WrFghSVq9erWsVqtGjx6tkydPat++fZIun1Fq06ZNlpaKK7FarerXr5++//57SZfPIoSEhOiWW27JsW1cXJyWLFmifv36KTExUadPn9bp06d15swZde3aVfv27dPx48fz9bjh4eFZHqNKlSqqW7euDh48aF/2+++/q0WLFmrTpo19WcWKFfXII4/o0KFD2rlzp327wMBA9enTx75dhQoV9Mgjj+Srlrzk97W5VhlnIxITEyVJkZGR2rdvn+677z6dOXPG/vyeP39enTp10ooVK2Sz2a54n7/88otsNpv69etn3//06dMKCAhQ7dq1tXTp0izbu7i4aNCgQbneV9++feXl5WX/OaOV7YEHHpCjo2OW5ZcuXcrXaz9o0KAs1y9l/A5kvO7X+xwkJibKw8PjijVkrE9ISMiyvH///ln27dOnjwIDA+2/B9dq8ODB9v/39vZW3bp15e7urn79+tmX161bV97e3ll+/11cXOTgcPlPenp6us6cOWNvZ9q0adN11eTl5aXhw4drzpw52rx583XdV2b//e9/s7z33HTTTTIMQ//973/ty6xWq5o3b57lWDPccccdqlq1qv3nFi1a6KabbrK/BtfyHvTwww/LarVetfbff/9dAQEBuvfee+3LnJycNGzYMCUlJWn58uX5fyL+kfE7drXfycLg7e2tHTt22P8WFMSMGTPk5eWlW2+9Ncv7RrNmzVSxYsUc7xthYWE5WrMzzlT/+uuvV32fAsxE6x1QyFq0aHHNgznUrl07y881a9aUg4ODvaf98OHDcnBwyDGCXkBAgLy9vXX48GH7sltuucX+gWHlypVq3ry5mjdvLh8fH61cuVL+/v7asmWL7rvvvgLVeN999+mDDz7Qli1b9N133+mee+7J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AEA2BCUAAAAAyIagBAAAAADZEJQAAAAAIBuCUjFYsWKFevbsqaCgIFksFs2ePdvskorVpEmTdOONN8rDw0N+fn664447tGfPHrPLKjZTpkxR48aN7ZOstWzZUn/88YfZZZnmtddek8Vi0fDhw80upViMHz9eFosly61evXpml1Wsjh8/rgceeEC+vr5yc3NTo0aNtGHDBrPLKhbVq1fP8fpbLBYNGTLE7NKKRXp6ul544QWFhYXJzc1NNWvW1Msvv6zyNI5UYmKihg8frtDQULm5ualVq1Zav3692WUViat93jEMQy+++KICAwPl5uamzp07a9++feYUWwSudvy//PKLunTpIl9fX1ksFkVGRppSZ1G60nOQmpqqZ599Vo0aNZK7u7uCgoLUv39/nThxwryCr4KgVAzOnz+viIgITZ482exSTLF8+XINGTJEf//9txYtWqTU1FR16dJF58+fN7u0YhEcHKzXXntNGzdu1IYNG9SxY0fdfvvt2rFjh9mlFbv169frk08+UePGjc0upVg1aNBA0dHR9tuqVavMLqnYnD17Vq1bt5aTk5P++OMP7dy5U2+//bYqVapkdmnFYv369Vle+0WLFkmS+vbta3JlxeP111/XlClT9L///U+7du3S66+/rjfeeEMffvih2aUVm8GDB2vRokX6+uuvtW3bNnXp0kWdO3fW8ePHzS6t0F3t884bb7yhDz74QB9//LHWrl0rd3d3de3aVcnJycVcadG42vGfP39ebdq00euvv17MlRWfKz0HFy5c0KZNm/TCCy9o06ZN+uWXX7Rnzx716tXLhErzyUCxkmTMmjXL7DJMFRsba0gyli9fbnYppqlUqZLx2WefmV1GsUpMTDRq165tLFq0yGjXrp3x1FNPmV1SsRg3bpwRERFhdhmmefbZZ402bdqYXUaJ8dRTTxk1a9Y0bDab2aUUix49ehgPPfRQlmW9e/c27r//fpMqKl4XLlwwrFarMW/evCzLmzZtajz//PMmVVU8sn/esdlsRkBAgPHmm2/al507d85wcXExvv/+exMqLFpX+rwXFRVlSDI2b95crDUVt/x85l23bp0hyTh8+HDxFFVAnFFCsYuPj5ck+fj4mFxJ8UtPT9cPP/yg8+fPq2XLlmaXU6yGDBmiHj16qHPnzmaXUuz27dunoKAg1ahRQ/fff7+OHDlidknF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0jXve+DAAbVp00ahoaHy9fXNdZtDhw6pWbNmWrJkid58801t27ZN8+fPV4cOHTRkyJBrfuziYBiG0tLSzC4DAEo1ghIAlAOdO3dWQECAJk2alOc248ePz9GG9t5776l69er2nwcOHKg77rhDEydOlL+/v7y9vfXSSy8pLS1No0ePlo+Pj4KDgzVt2rQc97979261atVKrq6uatiwoZYvX55l/fbt29W9e3dVrFhR/v7+evDBB3X69Gn7+vbt22vo0KEaPny4KleurK5du+Z6HDabTS+99JKCg4Pl4uKiG264QfPnz7evt1gs2rhxo1566SVZLBaNHz8+1/t54oknZLFYtG7dOt11112qU6eOGjRooJEjR+rvv//OdZ/czghFRkbKYrHo0KFDkqTDhw+rZ8+eqlSpktzd3dWgQQP9/vvvOnTokDp06CBJqlSpkiwWiwYOHGg/pkmTJiksLExubm6KiIjQzJkzczzuH3/8oWbNmsnFxUWrVq3Sli1b1KFDB3l4eMjT01PNmjXThg0bcq0dAJAVQQkAygGr1aqJEyfqww8/1LFjx67rvpYsWaITJ05oxYoVeueddzRu3DjddtttqlSpktauXavHHntMjz76aI7HGT16tJ5++mlt3rxZLVu2VM+ePXXmzBlJ0rlz59SxY0c1adJEGzZs0Pz583Xy5En169cvy318+eWXcnZ21urVq/Xxxx/nWt/777+vt99+W2+99Za2bt2qrl27qlevXtq3b58kKTo6Wg0aNNDTTz+t6OhojRo1Ksd9xMXFaf78+RoyZIjc3d1zrPf29r6Wp06SNGTIEKWkpGjFihXatm2bXn/9dVWsWFEhISH6+eefJUl79uxRdHS03n//fUnSpEmT9NVXX+njjz/Wjh07NGLECD3wwAM5wuaYMWP02muvadeuXWrcuLHuv/9+BQcHa/369dq4caPGjBkjJyena64dAMoTR7MLAAAUjzvvvFM33HCDxo0bp88///ya78fHx0cffPCBHBwcVLduXb3xxhu6cOGCnnvuOUnS2LFj9dprr2nVqlW655577PsNHTpUd911lyRpypQpmj9/vj7//HM988wz+t///qcmTZpo4sSJ9u2/+OILhYSEaO/evapTp44kqXbt2nrjjTeuWN9bb72lZ5991v7Yr7/+upYuXar33ntPkydPVkBAgBwdHVWxYkUFBATkeh/79++XYRiqV6/eNT9PeTly5IjuuusuNWrUSJJUo0YN+zofHx9Jkp+fnz2MpaSkaOLEifrzzz/VsmVL+z6rVq3SJ598onbt2tn3f+mll3TrrbdmeazRo0fbj6N27dqFfjwAUFYRlACgHHn99dfVsWPHXM+i5FeDBg3k4PBvQ4K/v78aNmxo/9lqtcrX11exsbFZ9sv4kC9Jjo6Oat68uXbt2iVJ2rJli5YuXaqKFSvmeLwDBw7Yg1KzZs2uWFtCQoJOnDih1q1bZ1neunVrbdmyJZ9HePkan6IybNgwPf7441q4cKE6d+6su+66S40bN85z+/379+vChQtZApB0+RqtJk2aZFnWvHnzLD+PHDlSgwcP1tdff63OnTurb9++qlmzZuEdDACUYbTeAUA50rZtW3Xt2lVjx47Nsc7BwSFHQEhNTc2xXfbWLYvFkusym82W77qSkpLUs2dPRUZGZrnt27dPbdu2tW+XWxtcUahdu7YsFot2795doP0yAmTm5zH7czh48GAdPHhQDz74oLZt26bmzZvrww8/zPM+k5KSJEm//fZbludm586dWa5TknI+P+PHj9eOHTvUo0cPLVmyROHh4Zo1a1aBjgkAyiuCEgCUM6+99prmzp2rNWvWZFlepUoVxcTEZPmQX5hzH2UeACEtLU0bN25U/fr1JUlNmzbVjh07VL16ddWqVSvLrSDhyNPTU0FBQVq9enWW5atXr1Z4eHi+78fHx0ddu3bV5MmTdf78+Rzr8xq+u0qVKpIuXweVIbfnMCQkRI899ph++eUXPf300/r0008lSc7OzpKk9PR0+7bh4eFycXHRkSNHcjw3ISEhVz2WOnXqaMSIEVq4cKF69+6d60AbAICcCEoAUM40atRI999/vz744IMsy9u3b69Tp07pjTfe0IEDBzR58mT98ccfhfa4kydP1qxZs7R7924NGTJEZ8+e1UMPPSTp8gAHcXFxuvfee7V+/XodOHBACxYs0KBBg7KEhvwYPXq0Xn/9df3444/as2ePxowZo8jISD311FMFrjc9PV0tWrTQzz//rH379mnXrl364IMPsrQRZpYRXsaPH699+/bpt99+09tvv51lm+HDh2vBggWKiorSpk2btHTpUntgDA0NlcVi0bx583Tq1CklJSXJw8NDo0aN0ogRI/Tll1/qwIED2rRpkz788EN9+eWXedZ/8eJFDR06VMuWLdPhw4e1evVqrV+/3v5YAIArIygBQDn00ksv5WiNq1+/vj766CNNnjxZERERWrdu3XVdy5Tda6+9ptdee00RERFatWqV5syZo8qVK0uS/SxQenq6unTpokaNGmn48OHy9vbOcj1UfgwbNkwjR47U008/rUaNGmn+/PmaM2dOgQcyqFGjhjZt2qQOHTro6aefVsOGDXXrrbdq8eLFmjJlSq77ODk56fvvv9fu3bvVuHFjvf7663rllVeybJOenq4hQ4aofv366tatm+rUqaOPPvpIklS1alVNmDBBY8aMkb+/v4YOHSpJevnll/XCCy9o0qRJ9v1+++03hYWF5Vm/1WrVmTNn1L9/f9WpU0f9+vVT9+7dNWHChAI9DwBQXlmMorxiFQAAAABKIc4oAQAAAEA2BCUAAAAAyIagBAAAAADZEJQAAAAAIBuCEgAAAABkQ1ACAAAAgGwISgAAAACQDUEJAAAAALIhKAEAAABANgQlAAAAAMiGoAQAAAAA2fw/3VzJT9TMek4AAAAASUVORK5CYII=",
|
||
"text/plain": [
|
||
"<Figure size 1000x600 with 1 Axes>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"# Plotting the elbow plot\n",
|
||
"plt.figure(figsize=(10, 6))\n",
|
||
"plt.plot(range_of_clusters, inertias, '-o')\n",
|
||
"plt.title('Elbow Method to Determine Optimal Number of Clusters')\n",
|
||
"plt.xlabel('Number of Clusters')\n",
|
||
"plt.ylabel('Inertia')\n",
|
||
"plt.xticks(range_of_clusters)\n",
|
||
"plt.show()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"![elbow_chart](../images/elbow_chart.png)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "NN7NbTmiLe_-"
|
||
},
|
||
"source": [
|
||
"For demonstration purposes we will pick 5 as the optimal cluster number to show it doesn't matter exactly where we pick it as long as we are approximately right. There are numerous correct ways to categorize data. We also store which cluster each data point belongs to."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 29,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "KvrDe3WYKWgZ",
|
||
"outputId": "d0d95227-b9d2-4c52-a5f7-59159ba848d2"
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"n_clusters = 5\n",
|
||
"\n",
|
||
"kmeans = KMeans(n_clusters=n_clusters, init=\"k-means++\", random_state=42)\n",
|
||
"kmeans.fit(matrix)\n",
|
||
"labels = kmeans.labels_\n",
|
||
"df[\"Cluster\"] = labels"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "tLvO4AISDM0J"
|
||
},
|
||
"source": [
|
||
"We will analyze the cluster data now. There are two separate things we will look to address. 1. imbalanced data, 2. Expanding the data distribution."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "zaQ_mdhpOJqs"
|
||
},
|
||
"source": [
|
||
"First for imbalanced data we count the number of examples in each cluster. Then we select a few examples from each cluster at random and ask the LLM what topics these map to. "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 30,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "crUT7OR7QcFD",
|
||
"outputId": "04f49ac2-1259-4622-bcf2-0686af93068c"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Cluster\n",
|
||
"0 5\n",
|
||
"1 7\n",
|
||
"2 8\n",
|
||
"3 6\n",
|
||
"4 2\n",
|
||
"Name: count, dtype: int64\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"cluster_counts = df[\"Cluster\"].value_counts().sort_index()\n",
|
||
"print(cluster_counts)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "8i1FGtM-Xx3k"
|
||
},
|
||
"source": [
|
||
"We can see the topics found here:\n",
|
||
"Eco-friendly Transportation, Luxury and Leisure Items, Personal Care Products, Electronic Toothbrushes and Clothing and Apparel\n",
|
||
"match well enough but not exactly to our initial prompt of:\n",
|
||
"vehicle, clothing, toiletries, food.\n",
|
||
"\n",
|
||
"As we chose 5 clusters, it split up toiletries into Skincare and Personal Care which doesn't affect us too much further downstream."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Product</th>\n",
|
||
" <th>Category</th>\n",
|
||
" <th>Description</th>\n",
|
||
" <th>embedding</th>\n",
|
||
" <th>Cluster</th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>0</th>\n",
|
||
" <td>Tesla Model 3</td>\n",
|
||
" <td>Electric Car</td>\n",
|
||
" <td>The Tesla Model 3 is a revolutionary electric ...</td>\n",
|
||
" <td>[0.003255360759794712, -0.039260633289813995, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Elevate your sneaker game with Nike Air Max. C...</td>\n",
|
||
" <td>[0.03943369910120964, 0.022045187652111053, -0...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2</th>\n",
|
||
" <td>Oral-B Pro 1000</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Achieve a superior clean with the Oral-B Pro 1...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>3</th>\n",
|
||
" <td>Chobani Greek Yogurt</td>\n",
|
||
" <td>Yogurt</td>\n",
|
||
" <td>Indulge in a nutritious snack with Chobani Gre...</td>\n",
|
||
" <td>[0.0208318829536438, -0.02645781636238098, -0....</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>4</th>\n",
|
||
" <td>Ford F-150</td>\n",
|
||
" <td>Pickup Truck</td>\n",
|
||
" <td>The Ford F-150 is the ultimate pickup truck, d...</td>\n",
|
||
" <td>[0.007467855699360371, -0.05288049206137657, -...</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>5</th>\n",
|
||
" <td>Levi's 511</td>\n",
|
||
" <td>Jeans</td>\n",
|
||
" <td>Step out in style with Levi's 511 jeans. Featu...</td>\n",
|
||
" <td>[0.0037206460256129503, 0.022772302851080894, ...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>6</th>\n",
|
||
" <td>Philips Sonicare</td>\n",
|
||
" <td>Electric Toothbrush</td>\n",
|
||
" <td>Discover a new level of oral care with the Phi...</td>\n",
|
||
" <td>[-0.00724813062697649, -0.011600878089666367, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>7</th>\n",
|
||
" <td>Quaker Oatmeal</td>\n",
|
||
" <td>Breakfast Cereal</td>\n",
|
||
" <td>Start your day right with Quaker Oatmeal. This...</td>\n",
|
||
" <td>[-0.006529285106807947, 0.007865572348237038, ...</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>8</th>\n",
|
||
" <td>Toyota Camry</td>\n",
|
||
" <td>Sedan</td>\n",
|
||
" <td>The Toyota Camry stands out in the sedan categ...</td>\n",
|
||
" <td>[-0.02088991366326809, -0.006191295105963945, ...</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>9</th>\n",
|
||
" <td>Adidas Ultraboost</td>\n",
|
||
" <td>Running Shoes</td>\n",
|
||
" <td>Run like never before in the Adidas Ultraboost...</td>\n",
|
||
" <td>[0.02679188922047615, 0.014639599248766899, 8....</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>10</th>\n",
|
||
" <td>Toyota Camry</td>\n",
|
||
" <td>Car</td>\n",
|
||
" <td>The Toyota Camry is a reliable midsize sedan k...</td>\n",
|
||
" <td>[0.008056452497839928, -0.007912316359579563, ...</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>11</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Step up your sneaker game with the Nike Air Ma...</td>\n",
|
||
" <td>[0.03943241760134697, 0.02208484522998333, -0....</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>12</th>\n",
|
||
" <td>Colgate Electric Toothbrush</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Transform your oral hygiene routine with the C...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>13</th>\n",
|
||
" <td>Blue Diamond Almonds</td>\n",
|
||
" <td>Nuts</td>\n",
|
||
" <td>Snack healthy with Blue Diamond Almonds. These...</td>\n",
|
||
" <td>[-0.013289917260408401, 0.036334190517663956, ...</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>14</th>\n",
|
||
" <td>Harley Davidson Fat Boy</td>\n",
|
||
" <td>Motorcycle</td>\n",
|
||
" <td>Experience the thrill of the open road with th...</td>\n",
|
||
" <td>[0.012365399859845638, 0.03552943095564842, -0...</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>15</th>\n",
|
||
" <td>Adidas UltraBoost</td>\n",
|
||
" <td>Sneakers</td>\n",
|
||
" <td>Enjoy a perfect blend of comfort and performan...</td>\n",
|
||
" <td>[0.013107392005622387, 0.02963760495185852, -0...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>16</th>\n",
|
||
" <td>Dove Men's Body Wash</td>\n",
|
||
" <td>Body Wash</td>\n",
|
||
" <td>Refresh and hydrate your skin with Dove Men's ...</td>\n",
|
||
" <td>[0.03760576993227005, -0.008475445210933685, -...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>17</th>\n",
|
||
" <td>Quaker Oats</td>\n",
|
||
" <td>Oats</td>\n",
|
||
" <td>Start your day right with Quaker Oats. Packed ...</td>\n",
|
||
" <td>[-0.00903365109115839, 0.00896345917135477, 0....</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>18</th>\n",
|
||
" <td>Ford F-150</td>\n",
|
||
" <td>Truck</td>\n",
|
||
" <td>The Ford F-150 is a durable and dependable tru...</td>\n",
|
||
" <td>[0.023461222648620605, -0.026651185005903244, ...</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>19</th>\n",
|
||
" <td>Levi's 501 Jeans</td>\n",
|
||
" <td>Jeans</td>\n",
|
||
" <td>Discover the timeless style of Levi's 501 Jean...</td>\n",
|
||
" <td>[0.003762696636840701, 0.02275814116001129, -0...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>20</th>\n",
|
||
" <td>Tesla Model 3</td>\n",
|
||
" <td>Mobile Phones</td>\n",
|
||
" <td>Explore the future of driving with the Tesla M...</td>\n",
|
||
" <td>[0.03703858703374863, 0.03407958149909973, 0.0...</td>\n",
|
||
" <td>4</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>21</th>\n",
|
||
" <td>Nike Air Max</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Step up your game with the Nike Air Max. Desig...</td>\n",
|
||
" <td>[0.03943369910120964, 0.022045187652111053, -0...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>22</th>\n",
|
||
" <td>Oral-B Pro 1000</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Achieve a superior clean with the Oral-B Pro 1...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>23</th>\n",
|
||
" <td>Organic Almond Butter</td>\n",
|
||
" <td>Food</td>\n",
|
||
" <td>Indulge in the creamy goodness of Organic Almo...</td>\n",
|
||
" <td>[-0.014613640494644642, -0.002179765608161688,...</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>24</th>\n",
|
||
" <td>Yamaha YZF-R3</td>\n",
|
||
" <td>Mobile Phones</td>\n",
|
||
" <td>Introducing the Yamaha YZF-R3, the ultimate sp...</td>\n",
|
||
" <td>[0.03703858703374863, 0.03407958149909973, 0.0...</td>\n",
|
||
" <td>4</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>25</th>\n",
|
||
" <td>Adidas Ultraboost</td>\n",
|
||
" <td>Shoes</td>\n",
|
||
" <td>Discover the Adidas Ultraboost, a shoe that of...</td>\n",
|
||
" <td>[0.03944042697548866, 0.022062409669160843, -0...</td>\n",
|
||
" <td>2</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>26</th>\n",
|
||
" <td>Philips Sonicare</td>\n",
|
||
" <td>Electronic Toothbrush</td>\n",
|
||
" <td>Experience the dental care revolution with Phi...</td>\n",
|
||
" <td>[-0.003470012918114662, -0.01911414973437786, ...</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>27</th>\n",
|
||
" <td>Organic Quinoa</td>\n",
|
||
" <td>Food</td>\n",
|
||
" <td>Nourish your body with Organic Quinoa, a nutri...</td>\n",
|
||
" <td>[-0.014613640494644642, -0.002179765608161688,...</td>\n",
|
||
" <td>3</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Product Category \\\n",
|
||
"0 Tesla Model 3 Electric Car \n",
|
||
"1 Nike Air Max Shoes \n",
|
||
"2 Oral-B Pro 1000 Electronic Toothbrush \n",
|
||
"3 Chobani Greek Yogurt Yogurt \n",
|
||
"4 Ford F-150 Pickup Truck \n",
|
||
"5 Levi's 511 Jeans \n",
|
||
"6 Philips Sonicare Electric Toothbrush \n",
|
||
"7 Quaker Oatmeal Breakfast Cereal \n",
|
||
"8 Toyota Camry Sedan \n",
|
||
"9 Adidas Ultraboost Running Shoes \n",
|
||
"10 Toyota Camry Car \n",
|
||
"11 Nike Air Max Shoes \n",
|
||
"12 Colgate Electric Toothbrush Electronic Toothbrush \n",
|
||
"13 Blue Diamond Almonds Nuts \n",
|
||
"14 Harley Davidson Fat Boy Motorcycle \n",
|
||
"15 Adidas UltraBoost Sneakers \n",
|
||
"16 Dove Men's Body Wash Body Wash \n",
|
||
"17 Quaker Oats Oats \n",
|
||
"18 Ford F-150 Truck \n",
|
||
"19 Levi's 501 Jeans Jeans \n",
|
||
"20 Tesla Model 3 Mobile Phones \n",
|
||
"21 Nike Air Max Shoes \n",
|
||
"22 Oral-B Pro 1000 Electronic Toothbrush \n",
|
||
"23 Organic Almond Butter Food \n",
|
||
"24 Yamaha YZF-R3 Mobile Phones \n",
|
||
"25 Adidas Ultraboost Shoes \n",
|
||
"26 Philips Sonicare Electronic Toothbrush \n",
|
||
"27 Organic Quinoa Food \n",
|
||
"\n",
|
||
" Description \\\n",
|
||
"0 The Tesla Model 3 is a revolutionary electric ... \n",
|
||
"1 Elevate your sneaker game with Nike Air Max. C... \n",
|
||
"2 Achieve a superior clean with the Oral-B Pro 1... \n",
|
||
"3 Indulge in a nutritious snack with Chobani Gre... \n",
|
||
"4 The Ford F-150 is the ultimate pickup truck, d... \n",
|
||
"5 Step out in style with Levi's 511 jeans. Featu... \n",
|
||
"6 Discover a new level of oral care with the Phi... \n",
|
||
"7 Start your day right with Quaker Oatmeal. This... \n",
|
||
"8 The Toyota Camry stands out in the sedan categ... \n",
|
||
"9 Run like never before in the Adidas Ultraboost... \n",
|
||
"10 The Toyota Camry is a reliable midsize sedan k... \n",
|
||
"11 Step up your sneaker game with the Nike Air Ma... \n",
|
||
"12 Transform your oral hygiene routine with the C... \n",
|
||
"13 Snack healthy with Blue Diamond Almonds. These... \n",
|
||
"14 Experience the thrill of the open road with th... \n",
|
||
"15 Enjoy a perfect blend of comfort and performan... \n",
|
||
"16 Refresh and hydrate your skin with Dove Men's ... \n",
|
||
"17 Start your day right with Quaker Oats. Packed ... \n",
|
||
"18 The Ford F-150 is a durable and dependable tru... \n",
|
||
"19 Discover the timeless style of Levi's 501 Jean... \n",
|
||
"20 Explore the future of driving with the Tesla M... \n",
|
||
"21 Step up your game with the Nike Air Max. Desig... \n",
|
||
"22 Achieve a superior clean with the Oral-B Pro 1... \n",
|
||
"23 Indulge in the creamy goodness of Organic Almo... \n",
|
||
"24 Introducing the Yamaha YZF-R3, the ultimate sp... \n",
|
||
"25 Discover the Adidas Ultraboost, a shoe that of... \n",
|
||
"26 Experience the dental care revolution with Phi... \n",
|
||
"27 Nourish your body with Organic Quinoa, a nutri... \n",
|
||
"\n",
|
||
" embedding Cluster \n",
|
||
"0 [0.003255360759794712, -0.039260633289813995, ... 1 \n",
|
||
"1 [0.03943369910120964, 0.022045187652111053, -0... 2 \n",
|
||
"2 [-0.003470012918114662, -0.01911414973437786, ... 1 \n",
|
||
"3 [0.0208318829536438, -0.02645781636238098, -0.... 3 \n",
|
||
"4 [0.007467855699360371, -0.05288049206137657, -... 0 \n",
|
||
"5 [0.0037206460256129503, 0.022772302851080894, ... 2 \n",
|
||
"6 [-0.00724813062697649, -0.011600878089666367, ... 1 \n",
|
||
"7 [-0.006529285106807947, 0.007865572348237038, ... 3 \n",
|
||
"8 [-0.02088991366326809, -0.006191295105963945, ... 0 \n",
|
||
"9 [0.02679188922047615, 0.014639599248766899, 8.... 2 \n",
|
||
"10 [0.008056452497839928, -0.007912316359579563, ... 0 \n",
|
||
"11 [0.03943241760134697, 0.02208484522998333, -0.... 2 \n",
|
||
"12 [-0.003470012918114662, -0.01911414973437786, ... 1 \n",
|
||
"13 [-0.013289917260408401, 0.036334190517663956, ... 3 \n",
|
||
"14 [0.012365399859845638, 0.03552943095564842, -0... 0 \n",
|
||
"15 [0.013107392005622387, 0.02963760495185852, -0... 2 \n",
|
||
"16 [0.03760576993227005, -0.008475445210933685, -... 1 \n",
|
||
"17 [-0.00903365109115839, 0.00896345917135477, 0.... 3 \n",
|
||
"18 [0.023461222648620605, -0.026651185005903244, ... 0 \n",
|
||
"19 [0.003762696636840701, 0.02275814116001129, -0... 2 \n",
|
||
"20 [0.03703858703374863, 0.03407958149909973, 0.0... 4 \n",
|
||
"21 [0.03943369910120964, 0.022045187652111053, -0... 2 \n",
|
||
"22 [-0.003470012918114662, -0.01911414973437786, ... 1 \n",
|
||
"23 [-0.014613640494644642, -0.002179765608161688,... 3 \n",
|
||
"24 [0.03703858703374863, 0.03407958149909973, 0.0... 4 \n",
|
||
"25 [0.03944042697548866, 0.022062409669160843, -0... 2 \n",
|
||
"26 [-0.003470012918114662, -0.01911414973437786, ... 1 \n",
|
||
"27 [-0.014613640494644642, -0.002179765608161688,... 3 "
|
||
]
|
||
},
|
||
"execution_count": 33,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"df"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 36,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "RRwIet9DUdKe",
|
||
"outputId": "8e7835c9-884a-4504-bbed-f556382dd9f5"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"[\n",
|
||
" {\n",
|
||
" \"cluster\": 0,\n",
|
||
" \"topic\": \"Automotive \"\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"cluster\": 1,\n",
|
||
" \"topic\": \"Personal Care \"\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"cluster\": 2,\n",
|
||
" \"topic\": \"Footwear \"\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"cluster\": 3,\n",
|
||
" \"topic\": \"Food \"\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"cluster\": 4,\n",
|
||
" \"topic\": \"Automotive \"\n",
|
||
" }\n",
|
||
"]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"selected_examples = df.groupby('Cluster').apply(lambda x: x.sample(3, replace=True)).reset_index(drop=True)\n",
|
||
"\n",
|
||
"# Format the selected examples\n",
|
||
"formatted_examples = \"\\n\".join(\n",
|
||
" f'Input: \"{row[\"Product\"]}, {row[\"Category\"]}\"\\nOutput: \"{row[\"Description\"]}\"\\nCluster: \"{row[\"Cluster\"]}\"'\n",
|
||
" for _, row in selected_examples.iterrows()\n",
|
||
")\n",
|
||
"\n",
|
||
"topic_prompt = f\"\"\"\n",
|
||
" I previously generated some examples of input output trainings pairs and then I clustered them based on category. From each cluster I picked 3 example data point which you can find below.\n",
|
||
" I want you identify the broad topic areas these clusters belong to.\n",
|
||
" Previous examples:\n",
|
||
" {formatted_examples}\n",
|
||
"\n",
|
||
"\n",
|
||
" Your output should be strictly of the format:\n",
|
||
" Cluster: number, topic: topic\n",
|
||
" Cluster: number, topic: topic\n",
|
||
" Cluster: number, topic: topic\n",
|
||
"\n",
|
||
" Do not add any extra characters around that formatting as it will make the output parsing break.\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
"response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed analyze clustered data\"},\n",
|
||
" {\"role\": \"user\", \"content\": topic_prompt}\n",
|
||
" ]\n",
|
||
")\n",
|
||
"res = response.choices[0].message.content\n",
|
||
"\n",
|
||
"pattern = r\"Cluster: (\\d+), topic: ([^\\n]+)\"\n",
|
||
"matches = re.findall(pattern, res)\n",
|
||
"clusters = [{\"cluster\": int(cluster), \"topic\": topic} for cluster, topic in matches]\n",
|
||
"json_output = json.dumps(clusters, indent=2)\n",
|
||
"print(json_output)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "x5hszl-SZVdi"
|
||
},
|
||
"source": [
|
||
"We now have the clusters and their counts so we could prompt the LLM to generate more examples within the topics we want. However for this example we won't take that further as they are well-split and you would just follow the procedure above for prompting the model to generate data while passing in the underrepresented topics."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "yVD_TPsHYvDb"
|
||
},
|
||
"source": [
|
||
"Next, we will try and deal with increasing the diversity of our data distribution. \n",
|
||
"\n",
|
||
"First we start in a similar way by finding a few examples from each cluster at random and ask the LLM what topics these map to. In addition to this in the same LLM call, we will ask it to generate more topics to increase the diversity of our data. We do this in one call to save time/cost."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 37,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/",
|
||
"height": 53
|
||
},
|
||
"id": "mZjBbfFaZ3mn",
|
||
"outputId": "8864421a-e9d4-4ea6-f747-a76a3291a593"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"1. Cluster topic mapping\n",
|
||
"Cluster: 0, topic: Automotive\n",
|
||
"Cluster: 1, topic: Personal Care\n",
|
||
"Cluster: 2, topic: Footwear\n",
|
||
"Cluster: 3, topic: Food\n",
|
||
"Cluster: 4, topic: Electric Vehicles\n",
|
||
"\n",
|
||
"2. New topics\n",
|
||
"1. topic: Home Appliances\n",
|
||
"2. topic: Outdoor Equipment\n",
|
||
"3. topic: Smart Home Technology\n",
|
||
"4. topic: Fitness Equipment\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"selected_examples = df.groupby('Cluster').apply(lambda x: x.sample(3, replace=True)).reset_index(drop=True)\n",
|
||
"\n",
|
||
"# Format the selected examples\n",
|
||
"formatted_examples = \"\\n\".join(\n",
|
||
" f'Input: \"{row[\"Product\"]}, {row[\"Category\"]}\"\\nOutput: \"{row[\"Description\"]}\"\\nCluster: \"{row[\"Cluster\"]}\"'\n",
|
||
" for _, row in selected_examples.iterrows()\n",
|
||
")\n",
|
||
"\n",
|
||
"topic_prompt = f\"\"\"\n",
|
||
" I previously generated some examples of input output trainings pairs and then I clustered them based on category. From each cluster I picked 3 example data point which you can find below.\n",
|
||
" I want to promote diversity in my examples across categories so follow the procedure below:\n",
|
||
" 1. You must identify the broad topic areas these clusters belong to.\n",
|
||
" 2. You should generate further topic areas which don't exist so I can generate data within these topics to improve diversity.\n",
|
||
"\n",
|
||
"\n",
|
||
" Previous examples:\n",
|
||
" {formatted_examples}\n",
|
||
"\n",
|
||
"\n",
|
||
" Your output should be strictly of the format:\n",
|
||
"\n",
|
||
" 1. Cluster topic mapping\n",
|
||
" Cluster: number, topic: topic\n",
|
||
" Cluster: number, topic: topic\n",
|
||
" Cluster: number, topic: topic\n",
|
||
"\n",
|
||
" 2. New topics\n",
|
||
" 1. topic\n",
|
||
" 2. topic\n",
|
||
" 3. topic\n",
|
||
" 4. topic\n",
|
||
"\n",
|
||
" Do not add any extra characters around that formatting as it will make the output parsing break. It is very important you stick to that output format\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
"response = client.chat.completions.create(\n",
|
||
" model=datagen_model,\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to analyze clustered data\"},\n",
|
||
" {\"role\": \"user\", \"content\": topic_prompt}\n",
|
||
" ]\n",
|
||
")\n",
|
||
"res = response.choices[0].message.content\n",
|
||
"print(res)\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"We can see here again that we explicitly prompt the output structure it should follow. I also tell it the purpose of generating topics (to promote diversity) so the model has full context."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "s254oJ-Ecka0"
|
||
},
|
||
"source": [
|
||
"We then parse the data into a list of cluster-mapping jsons and a list of topics"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 38,
|
||
"metadata": {
|
||
"colab": {
|
||
"base_uri": "https://localhost:8080/"
|
||
},
|
||
"id": "HTS4ybspcivw",
|
||
"outputId": "52ea363c-cbf5-420e-b81e-0d2710c50203"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"([{'cluster': 0, 'topic': 'Automotive'},\n",
|
||
" {'cluster': 1, 'topic': 'Personal Care'},\n",
|
||
" {'cluster': 2, 'topic': 'Footwear'},\n",
|
||
" {'cluster': 3, 'topic': 'Food'},\n",
|
||
" {'cluster': 4, 'topic': 'Electric Vehicles'}],\n",
|
||
" ['topic: Home Appliances',\n",
|
||
" 'topic: Outdoor Equipment',\n",
|
||
" 'topic: Smart Home Technology',\n",
|
||
" 'topic: Fitness Equipment'])"
|
||
]
|
||
},
|
||
"execution_count": 38,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"parts = res.split(\"\\n\\n\")\n",
|
||
"cluster_mapping_part = parts[0]\n",
|
||
"new_topics_part = parts[1]\n",
|
||
"\n",
|
||
"# Parse cluster topic mapping\n",
|
||
"cluster_topic_mapping_lines = cluster_mapping_part.split(\"\\n\")[1:] # Skip the first two lines\n",
|
||
"cluster_topic_mapping = [{\"cluster\": int(line.split(\",\")[0].split(\":\")[1].strip()), \"topic\": line.split(\":\")[2].strip()} for line in cluster_topic_mapping_lines]\n",
|
||
"\n",
|
||
"# Parse new topics\n",
|
||
"new_topics_lines = new_topics_part.split(\"\\n\")[1:] # Skip the first line\n",
|
||
"new_topics = [line.split(\". \")[1] for line in new_topics_lines]\n",
|
||
"\n",
|
||
"cluster_topic_mapping, new_topics"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "CX26-PGdcui0"
|
||
},
|
||
"source": [
|
||
"And finally we can use this information to further prompt a model to keep generating synthetic data. We do this by passing all the topics in the list of jsons to the prompt below."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 39,
|
||
"metadata": {
|
||
"id": "zHf4LnVk0aHw"
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"1. Automotive \n",
|
||
"Input: \"Tesla Model S, Electric Vehicles\" \n",
|
||
"Output: \"The Tesla Model S delivers exhilarating performance with advanced electric technology, offering a sleek design, impressive range, and an industry-leading infotainment system.\"\n",
|
||
"\n",
|
||
"2. Personal Care \n",
|
||
"Input: \"Oral-B Pro 1000, Electronic Toothbrush\" \n",
|
||
"Output: \"The Oral-B Pro 1000 features a 3D cleaning action that oscillates, rotates, and pulsates to remove plaque, ensuring a deeper clean for healthier gums.\"\n",
|
||
"\n",
|
||
"3. Footwear \n",
|
||
"Input: \"Nike Air Max 270, Shoes\" \n",
|
||
"Output: \"Step into comfort and style with Nike Air Max 270, designed with a large Max Air unit for superior cushioning and a breathable upper for a snug fit.\"\n",
|
||
"\n",
|
||
"4. Electronics \n",
|
||
"Input: \"Apple iPhone 12, Mobile Phones\" \n",
|
||
"Output: \"The Apple iPhone 12 combines powerful performance with stunning design, equipped with A14 Bionic chip and advanced camera systems for capturing every moment in stunning detail.\"\n",
|
||
"\n",
|
||
"5. Food \n",
|
||
"Input: \"Nature Valley Granola Bars, Snacks\" \n",
|
||
"Output: \"Nature Valley Granola Bars offer a wholesome crunch made from simple, delicious ingredients, providing a perfect snack that fuels your adventure.\"\n",
|
||
"\n",
|
||
"6. Automotive \n",
|
||
"Input: \"Ford F-150, Electric Vehicles\" \n",
|
||
"Output: \"The Ford F-150 stands at the forefront of durability and innovation, with its powerful electric version setting new standards for strength and sustainability in the truck category.\" \n",
|
||
"\n",
|
||
"7. Personal Care \n",
|
||
"Input: \"Philips Sonicare, Electronic Toothbrush\" \n",
|
||
"Output: \"Philips Sonicare delivers superior cleaning with dynamic technology that provides up to 31,000 strokes per minute for a healthier mouth and brighter smile.\"\n",
|
||
"\n",
|
||
"8. Footwear \n",
|
||
"Input: \"Adidas Ultraboost, Shoes\" \n",
|
||
"Output: \"The Adidas Ultraboost is a game-changer in running footwear, featuring responsive cushioning and a knit upper for a snug, supportive fit that adapts to any run.\"\n",
|
||
"\n",
|
||
"9. Electronics \n",
|
||
"Input: \"Dell XPS 13, Laptop\" \n",
|
||
"Output: \"The Dell XPS 13 is a remarkable laptop with an ultra-thin design, featuring a stunning InfinityEdge display and powerful performance to accommodate your multitasking needs.\"\n",
|
||
"\n",
|
||
"10. Food \n",
|
||
"Input: \"Kraft Macaroni & Cheese, Instant Food\" \n",
|
||
"Output: \"Kraft Macaroni & Cheese offers quick and convenient comfort food, combining creamy cheese sauce with perfectly cooked pasta for a simple meal that satisfies.\"\n",
|
||
"\n",
|
||
"1. Automotive \n",
|
||
"Input: \"Toyota Camry, Mobile Phones\" \n",
|
||
"Output: \"The Toyota Camry is a midsize sedan that combines efficiency with modern technology. It offers a spacious interior and the latest features for an enjoyable driving experience.\"\n",
|
||
"\n",
|
||
"2. Personal Care \n",
|
||
"Input: \"Oral-B Pro 1000, Electronic Toothbrush\" \n",
|
||
"Output: \"The Oral-B Pro 1000 not only provides powerful cleaning action but also enhances your oral hygiene routine with its smart pressure sensor and various cleaning modes.\"\n",
|
||
"\n",
|
||
"3. Footwear \n",
|
||
"Input: \"Nike Air Max, Shoes\" \n",
|
||
"Output: \"Step into comfort with the Nike Air Max. With cutting-edge technology and a sleek design, these shoes are perfect for athletes and casual wearers alike.\"\n",
|
||
"\n",
|
||
"4. Food \n",
|
||
"Input: \"Nature's Valley Granola Bar, Food\" \n",
|
||
"Output: \"Savor the wholesome goodness of Nature's Valley Granola Bar, crafted with real ingredients to fuel your day with delicious flavor and crunchy satisfaction.\"\n",
|
||
"\n",
|
||
"5. Electric Vehicles \n",
|
||
"Input: \"Tesla Model 3, Mobile Phones\" \n",
|
||
"Output: \"The Tesla Model 3 is a revolutionary electric vehicle that combines performance with sustainability, featuring an intuitive interface and cutting-edge technology for an exceptional driving experience.\"\n",
|
||
"\n",
|
||
"1. Automotive \n",
|
||
"Input: \"Tesla Model 3, Electric Vehicles\" \n",
|
||
"Output: \"The Tesla Model 3 combines cutting-edge technology with eco-friendly driving. Enjoy a sleek design, impressive range, and top-notch safety features, making it the perfect electric car for the modern driver.\"\n",
|
||
"\n",
|
||
"2. Personal Care \n",
|
||
"Input: \"Oral-B Pro 1000, Electronic Toothbrush\" \n",
|
||
"Output: \"Achieve a superior clean with the Oral-B Pro 1000. Featuring advanced 3D cleaning action, this electronic toothbrush ensures effective plaque removal while being gentle on gums, allowing you to maintain optimum oral health.\"\n",
|
||
"\n",
|
||
"3. Footwear \n",
|
||
"Input: \"Nike Air Max, Shoes\" \n",
|
||
"Output: \"Step up your game with Nike Air Max shoes. Combining iconic cushioning technology and bold style, these shoes provide ultimate comfort and support, perfect for both casual wear and athletic performance.\"\n",
|
||
"\n",
|
||
"4. Food \n",
|
||
"Input: \"Oreo Cookies, Snacks\" \n",
|
||
"Output: \"Indulge in the classic taste of Oreo Cookies. With their irresistible cream filling sandwiched between two crunchy chocolate wafers, these treats are perfect for satisfying your sweet tooth any time of the day.\"\n",
|
||
"\n",
|
||
"5. Personal Care \n",
|
||
"Input: \"Garnier Micellar Water, Skincare\" \n",
|
||
"Output: \"Garnier Micellar Water gently removes makeup and impurities while hydrating the skin. This soothing formula is suitable for all skin types, making it a must-have in your daily skincare routine.\"\n",
|
||
"\n",
|
||
"6. Automotive \n",
|
||
"Input: \"Ford F-150, Trucks\" \n",
|
||
"Output: \"The Ford F-150 is the quintessential pickup truck, combining power, reliability, and innovative technology. Equipped with advanced towing capabilities and a spacious interior, it's designed for both work and play.\"\n",
|
||
"\n",
|
||
"7. Electronics \n",
|
||
"Input: \"Samsung Galaxy S21, Mobile Phones\" \n",
|
||
"Output: \"Experience the future of mobile technology with the Samsung Galaxy S21. This smartphone features a stunning display, powerful processor, and multiple camera options, perfect for capturing life's moments in high definition.\"\n",
|
||
"\n",
|
||
"8. Footwear \n",
|
||
"Input: \"Adidas Ultraboost, Shoes\" \n",
|
||
"Output: \"Run in style with Adidas Ultraboost shoes. Known for their comfort and performance, these shoes utilize responsive cushioning to provide unmatched energy return with every step you take.\" \n",
|
||
"\n",
|
||
"9. Electronics \n",
|
||
"Input: \"Dell XPS 13, Laptops\" \n",
|
||
"Output: \"The Dell XPS 13 redefines the laptop experience with its stunning InfinityEdge display, powerful performance, and sleek design. Ideal for both professionals and students looking for portability and functionality.\"\n",
|
||
"\n",
|
||
"10. Personal Care \n",
|
||
"Input: \"Philips Sonicare, Electronic Toothbrush\" \n",
|
||
"Output: \"Philips Sonicare's electronic toothbrush guarantees a superior cleaning experience with its advanced sonic technology. This toothbrush not only helps remove plaque but also promotes healthier gums for a brighter smile.\"\n",
|
||
"\n",
|
||
"\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"output_string = \"\"\n",
|
||
"for i in range(3):\n",
|
||
" question = f\"\"\"\n",
|
||
" I am creating input output training pairs to fine tune my gpt model. I want the input to be product name and category and output to be description. the category should be things like: mobile phones, shoes, headphones, laptop, electronic toothbrush, etc. and also more importantly the categories should come under some main topics: {[entry['topic'] for entry in cluster_topic_mapping]})\n",
|
||
" After the number of each example also state the topic area. The format should be of the form:\n",
|
||
" 1. topic_area\n",
|
||
" Input: product_name, category\n",
|
||
" Output: description\n",
|
||
"\n",
|
||
" Do not add any extra characters around that formatting as it will make the output parsing break.\n",
|
||
"\n",
|
||
" Here are some helpful examples so you get the style of output correct.\n",
|
||
"\n",
|
||
" 1) clothing\n",
|
||
" Input: \"Shoe Name, Shoes\"\n",
|
||
" Output: \"Experience unparalleled comfort. These shoes feature a blend of modern style and the traditional superior cushioning, perfect for those always on the move.\"\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" response = client.chat.completions.create(\n",
|
||
" model=\"gpt-4o-mini\",\n",
|
||
" messages=[\n",
|
||
" {\"role\": \"system\", \"content\": \"You are a helpful assistant designed to generate synthetic data.\"},\n",
|
||
" {\"role\": \"user\", \"content\": question}\n",
|
||
" ]\n",
|
||
" )\n",
|
||
" res = response.choices[0].message.content\n",
|
||
" output_string += res + \"\\n\" + \"\\n\"\n",
|
||
"print(output_string)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "RQMHQxnZdRug"
|
||
},
|
||
"source": [
|
||
"You can run this in a loop to append to your previous data and in this way you can keep generating more textual synthetic data to train another GPT model while making sure that we cater to imbalanced datasets and generating a diversity of data."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "Hiim8Xg5djGH"
|
||
},
|
||
"source": [
|
||
"You have now completed part 1 of the synthetic data generation tutorial where we have gone through:\n",
|
||
"* CSV with a structured prompt\n",
|
||
"* CSV with a Python program\n",
|
||
"* Multitable CSV with a python program\n",
|
||
"* Simply creating textual data\n",
|
||
"* Dealing with imbalanced or non-diverse textual data\n",
|
||
"\n",
|
||
"In part 2 you will find find out techniques for better prompting an LLM to enhance textual synthetic data generation."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": []
|
||
}
|
||
],
|
||
"metadata": {
|
||
"colab": {
|
||
"provenance": []
|
||
},
|
||
"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.9"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 1
|
||
}
|