mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
Merge pull request #14627 from supabase/or/supabase-vector-examples
Supabase Vector Examples
This commit is contained in:
14 files changed
+2875
No files matched your search
@@ -0,0 +1,475 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1f181d-feeb-4b29-aabc-67a75234b92c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Face Similarity Search\n",
|
||||
"\n",
|
||||
"In this example we'll use PostgreSQL + pgvectors similarity search using the `vecs` library to identify the celebrities a person looks most similar to.\n",
|
||||
"\n",
|
||||
"We'll start by loading a dataset of celebrity faces. Then we'll create embeddings for the faces using python's `face_recognition` library and store them in PostgreSQL with `vecs`. Finally we'll query the database with a user defined face to see which celebrities they look most like."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3cab93f5-10d0-47c5-9f4e-64921461e7e2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Install Dependencies"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a41bc3e4-ea52-43aa-9239-a431b49f029e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install -qU vecs datasets face_recognition flupy tqdm numpy matplotlib"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4dbd176f-3b4e-4d41-a72d-1e1affe6ecae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load the Dataset\n",
|
||||
"\n",
|
||||
"First, we load a dataset of celebrity faces."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dc6b0bc2-b95f-4190-bf77-fa2dc57fc247",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from datasets import load_dataset\n",
|
||||
"\n",
|
||||
"people = load_dataset(\"ashraq/tmdb-people-image\", split='train')\n",
|
||||
"people"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "da15eff4-932c-4e0c-b938-ba188af62b63",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'adult': False,\n",
|
||||
" 'also_known_as': \"['Morgan Porterfield Freeman Jr.', 'Morgan J. Freeman', 'مورغان فريمان', '모건 프리먼', 'モーガン・フリーマン', 'Морган Фриман', 'Морган Фримен', 'มอร์แกน ฟรีแมน', '摩根·弗里曼', 'Μόργκαν Φρίμαν', 'مورگان فریمن', 'Морґан Фрімен', 'Μόργκαν Πόρτερφιλντ Φρίμαν Τζούνιορ']\",\n",
|
||||
" 'biography': \"Morgan Freeman (born June 1, 1937) is an American actor, director, and narrator. Noted for his distinctive deep voice, Freeman is known for his various roles in a wide variety of film genres. Throughout his career spanning over five decades, he has received multiple accolades, including an Academy Award, a Screen Actors Guild Award, and a Golden Globe Award.\\n\\nBorn in Memphis, Tennessee, Freeman was raised in Mississippi where he began acting in school plays. He studied theatre arts in Los Angeles and appeared in stage productions in his early career. He rose to fame in the 1970s for his role in the children's television series The Electric Company. Freeman then appeared in the Shakespearean plays Coriolanus and Julius Caesar, the former of which earned him an Obie Award. His breakout role was in Street Smart (1987), playing a hustler, which earned him an Academy Award nomination for Best Supporting Actor. He achieved further stardom in Glory, the biographical drama Lean on Me, and comedy-drama Driving Miss Daisy (all 1989), the latter of which garnered him his first Academy Award nomination for Best Actor.\\n\\nIn 1992, Freeman starred alongside Clint Eastwood in the western revenge film Unforgiven; this would be the first of several collaborations with Eastwood. In 1994, he starred in the prison drama The Shawshank Redemption for which he received another Academy Award nomination. Freeman also starred in David Fincher's crime thriller Se7en (1995), and Steven Spielberg's historical drama Amistad (1997). Freeman won the Academy Award for Best Supporting Actor for his performance in Clint Eastwood's 2004 sports drama Million Dollar Baby. In 2009, he received his fifth Oscar nomination for playing former South African President Nelson Mandela in Eastwood's Invictus. Freeman is also known for his performance as Lucius Fox in Christopher Nolan's The Dark Knight Trilogy (2005–2012).\\n\\nIn addition to acting, Freeman has directed the drama Bopha! (1993). He also founded film production company Revelations Entertainment with business partner Lori McCreary. He is the recipient of the Kennedy Center Honor, the AFI Life Achievement Award, the Cecil B. DeMille Award, and the Screen Actors Guild Life Achievement Award. For his performances in theatrical productions, he has won three Obie Awards, one of the most prestigious honors for recognizing excellence in theatre.\\n\\nDescription above from the Wikipedia article Morgan Freeman, licensed under CC-BY-SA, full list of contributors on Wikipedia.\",\n",
|
||||
" 'birthday': '1937-06-01',\n",
|
||||
" 'deathday': None,\n",
|
||||
" 'gender': 2,\n",
|
||||
" 'homepage': None,\n",
|
||||
" 'id': 192,\n",
|
||||
" 'imdb_id': 'nm0000151',\n",
|
||||
" 'known_for_department': 'Acting',\n",
|
||||
" 'name': 'Morgan Freeman',\n",
|
||||
" 'place_of_birth': 'Memphis, Tennessee, USA',\n",
|
||||
" 'popularity': 95.033,\n",
|
||||
" 'profile_path': '905k0RFzH0Kd6gx8oSxRdnr6FL.jpg',\n",
|
||||
" 'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=421x632>}"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Look at an example record from the dataset\n",
|
||||
"person = people[15]\n",
|
||||
"person"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "006d5900-547b-4ff3-ad81-3a2419d19a46",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<PIL.Image.Image image mode=RGB size=210x315>"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"person['image'].resize((210, 315))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "955b2700-8242-40eb-ac3f-d479a0312693",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Embedding Model\n",
|
||||
"\n",
|
||||
"Next, we can use `face_recognition` to produce a face embedding for each row (person) in the dataset."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "2929f431-278e-4482-931b-5cd39eadb1fb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([-0.13341002, 0.13275896, 0.16850984, 0.06553721, 0.01849797,\n",
|
||||
" -0.10046144, 0.08926678, -0.10535249, 0.09548245, 0.01443161,\n",
|
||||
" 0.29246074, -0.05078556, -0.09225237, -0.20251125, 0.06319536,\n",
|
||||
" 0.13064152, -0.13121371, -0.14068669, -0.14239085, -0.07584596,\n",
|
||||
" -0.00425112, 0.05749821, 0.03404564, 0.00690284, -0.05748112,\n",
|
||||
" -0.30998233, -0.05940549, -0.12016021, 0.13422301, -0.09362517,\n",
|
||||
" 0.00096729, 0.00121274, -0.28880239, -0.10776889, -0.02401398,\n",
|
||||
" 0.02188838, 0.04223322, -0.05020416, 0.13429314, -0.06062891,\n",
|
||||
" -0.15768167, 0.04524709, 0.09769595, 0.18990684, 0.16210739,\n",
|
||||
" -0.04965826, -0.03221413, -0.03109819, 0.01347625, -0.18865313,\n",
|
||||
" 0.04968366, 0.08835649, 0.12916593, 0.08170474, -0.01250134,\n",
|
||||
" -0.10153808, -0.04734591, 0.05344258, -0.15603815, 0.05757158,\n",
|
||||
" 0.03810269, -0.07773421, -0.11656785, 0.00838896, 0.11370895,\n",
|
||||
" 0.10145531, -0.01232528, -0.19529839, 0.07950203, -0.16160172,\n",
|
||||
" -0.0103126 , 0.10836543, -0.07068133, -0.08057274, -0.2873418 ,\n",
|
||||
" 0.11391003, 0.38112539, 0.02767274, -0.24450374, 0.02405314,\n",
|
||||
" -0.17997386, 0.0244493 , 0.01259282, 0.02163479, -0.0256572 ,\n",
|
||||
" 0.00239573, -0.15525642, 0.02548696, 0.13868049, -0.01777178,\n",
|
||||
" -0.00228858, 0.19361472, 0.00259691, -0.00303981, 0.00783446,\n",
|
||||
" -0.01679439, -0.0077012 , -0.02348143, -0.09466386, -0.06156742,\n",
|
||||
" 0.02911264, -0.00982514, -0.04479714, 0.08873156, -0.18456687,\n",
|
||||
" 0.09431744, 0.06609484, -0.02497767, 0.09436633, 0.0584843 ,\n",
|
||||
" 0.02720976, -0.12375437, 0.07636171, -0.17666884, 0.1997328 ,\n",
|
||||
" 0.16992269, -0.05255131, 0.13182855, 0.00585927, 0.1533282 ,\n",
|
||||
" -0.03610092, 0.01495183, -0.07237726, -0.01772323, 0.03974599,\n",
|
||||
" -0.00466948, -0.03418808, 0.02540907])"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import face_recognition\n",
|
||||
"\n",
|
||||
"# Display an example embedding\n",
|
||||
"face_recognition.face_encodings(np.array(person['image']))[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9e82862d-440a-4f66-9ed7-0eaa6a0f4062",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize the Vecs Collection\n",
|
||||
"\n",
|
||||
"The [`vecs`](https://supabase.github.io/vecs/api/) library wraps a pythonic interface around PostgreSQL and pgvector.\n",
|
||||
"A collection in `vecs` maps 1:1 with a PostgreSQL table."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2771545-d209-4ceb-a222-ed139a4620f2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import vecs\n",
|
||||
"DB_CONNECTION = \"postgresql://postgres:password@localhost:5611/vecs_db\"\n",
|
||||
"\n",
|
||||
"# create vector store client\n",
|
||||
"vx = vecs.create_client(DB_CONNECTION)\n",
|
||||
"\n",
|
||||
"# create a PostgreSQL/pgvector table named \"faces\" to contain the face embeddings\n",
|
||||
"faces = vx.create_collection(name=\"faces\", dimension=128)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9ae030b4-cfd2-43bc-802f-e7ac4007d2ad",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create Embeddings for Each Face\n",
|
||||
"\n",
|
||||
"Now we can iterate over the dataset, producing embeddings for the faces.\n",
|
||||
"\n",
|
||||
"Note that it could take a few hours to produce all of the embeddings. If you're just testing it out, feel free to interrupt the loop after a few hundred iterations and continue with the next step."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bd134310-9da1-4448-8358-9fc491c98e1e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List, Dict, Tuple\n",
|
||||
"from PIL import Image\n",
|
||||
"from flupy import flu\n",
|
||||
"import numpy as np\n",
|
||||
"from tqdm import tqdm\n",
|
||||
"\n",
|
||||
"# Records we'll insert into the database\n",
|
||||
"records: List[Tuple[str, np.ndarray, Dict]] = []\n",
|
||||
"\n",
|
||||
"# Iterate over the dataset in chunks\n",
|
||||
"for ix, person in tqdm(enumerate(people)):\n",
|
||||
"\n",
|
||||
" # Extract the person's image\n",
|
||||
" person_image = person['image']\n",
|
||||
"\n",
|
||||
" # Some of the images are grayscale with a single image channel\n",
|
||||
" # We'll normalize the image set by converting those to 3 channel RBG format\n",
|
||||
" if person_image.mode == 'L':\n",
|
||||
" # Extract the available channel\n",
|
||||
" L_channel = np.array(person_image)\n",
|
||||
"\n",
|
||||
" # Repeat that channel 3 times for R G B\n",
|
||||
" person_image = Image.fromarray(\n",
|
||||
" np.moveaxis(np.stack([L_channel, L_channel, L_channel]), 0, -1)\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Create embeddings for current chunk\n",
|
||||
" embeddings = face_recognition.face_encodings(np.array(person_image))\n",
|
||||
"\n",
|
||||
" # In some cases the face is too obscured to be detectable and no embedding\n",
|
||||
" # is produced. We'll skip those cases\n",
|
||||
" if len(embeddings) == 1:\n",
|
||||
" embedding = embeddings[0]\n",
|
||||
" records.append((\n",
|
||||
" f\"{ix}\",\n",
|
||||
" embedding,\n",
|
||||
" {k: v for k, v in person.items() if k != 'image'}\n",
|
||||
" ))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6ef285d7-dc7b-4576-ab2b-bf0a4ba06de4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Insert the Embeddings into Postgres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "6156e7f9-b78a-4ab9-8002-b09ec8716be2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"faces.upsert(records)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aa095a53-dd9c-4a3c-93bf-c54708c67765",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Index the Collection\n",
|
||||
"\n",
|
||||
"Indexing the collection creates an index on the vector column in Postgres that significantly improves performance\n",
|
||||
"of similarity queries."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "6b96d3af-5592-4fbc-81ab-f77b4228ccaa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"faces.create_index()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "371631e6-4995-484f-9d76-40ab5e7b2e16",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Search for Similar Faces\n",
|
||||
"\n",
|
||||
"Finally we can load a user defined face and search the database for other similar faces to find their look alikes. For simplicity, we'll grab a random face from the dataset as our query but it can be substituted for your own image."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d4a0a7c1-8269-4b24-985a-57f7dbbbf969",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Example Results\n",
|
||||
"\n",
|
||||
"We'll create helper functions to display search results and try it out on several celebrities. \n",
|
||||
"Since our query faces are also in the dataset, the query face is the first in the result output."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "8673ff6e-31e5-4344-990d-bf3c84be5824",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from IPython.core.display import HTML\n",
|
||||
"from PIL import Image, ImageDraw, ImageFont\n",
|
||||
"import matplotlib.font_manager as fm\n",
|
||||
"from typing import Dict, Any\n",
|
||||
"\n",
|
||||
"def add_label(img, label_text, label_height=30):\n",
|
||||
" # Set the font and size\n",
|
||||
" font_path = fm.findfont(fm.FontProperties(family='Arial'))\n",
|
||||
" font = ImageFont.truetype(font_path, 15)\n",
|
||||
" \n",
|
||||
" # Create a new image with a white background\n",
|
||||
" label_img = Image.new('RGB', (img.width, label_height), color = (255, 255, 255))\n",
|
||||
" d = ImageDraw.Draw(label_img)\n",
|
||||
"\n",
|
||||
" # Calculate the width and height of the text to center it\n",
|
||||
" text_bbox = d.textbbox((0, 0), label_text, font)\n",
|
||||
" text_width, text_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n",
|
||||
" text_x = (label_img.width - text_width) // 2\n",
|
||||
" text_y = (label_img.height - text_height) // 2\n",
|
||||
"\n",
|
||||
" # Add the text to the label image\n",
|
||||
" d.text((text_x, text_y), label_text, fill=(0,0,0), font=font)\n",
|
||||
"\n",
|
||||
" # Concatenate the original image with the label image\n",
|
||||
" img_with_label = Image.new('RGB', (img.width, img.height + label_height))\n",
|
||||
" img_with_label.paste(img, (0, 0))\n",
|
||||
" img_with_label.paste(label_img, (0, img.height))\n",
|
||||
"\n",
|
||||
" return img_with_label\n",
|
||||
"\n",
|
||||
"def resize_for_output(person_image):\n",
|
||||
" return person_image.resize((150, 220))\n",
|
||||
" \n",
|
||||
"def render_similar_faces(person_image: Image) -> Image:\n",
|
||||
" # create query face embedding\n",
|
||||
" face_embedding = face_recognition.face_encodings(np.array(person_image))[0]\n",
|
||||
" \n",
|
||||
" # query database for similar results\n",
|
||||
" result = faces.query(face_embedding, limit=5, include_metadata=True) \n",
|
||||
"\n",
|
||||
" captioned_images = [\n",
|
||||
" add_label(\n",
|
||||
" resize_for_output(person_image),\n",
|
||||
" \"Query Image\"\n",
|
||||
" ),\n",
|
||||
" Image.fromarray(255*np.ones((250,30,3), np.uint8))\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" for person_id, person_metadata in result:\n",
|
||||
" result_person = people[int(person_id)]\n",
|
||||
" result_image = result_person['image']\n",
|
||||
" captioned_images.append(add_label(resize_for_output(result_person['image']), person_metadata[\"name\"]))\n",
|
||||
"\n",
|
||||
" return Image.fromarray(np.hstack(captioned_images))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "2e5f05e8-5fc3-42e8-9a3b-4b3c7ce0dc4a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<PIL.Image.Image image mode=RGB size=930x250>"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"render_similar_faces(\n",
|
||||
" person_image=people[1014]['image']\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"id": "1aa257bf-d1c9-4364-a6ef-46740916ace1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<PIL.Image.Image image mode=RGB size=930x250>"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"render_similar_faces(person_image=people[15]['image'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"id": "031c13f7-c830-45e0-b36e-7c1d71eca383",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<PIL.Image.Image image mode=RGB size=930x250>"
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"render_similar_faces(person_image=people[188]['image'])"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -0,0 +1,41 @@
|
||||
# Image Search with Supabase Vector
|
||||
|
||||
In this example we're implementing image search using the [OpenAI CLIP Model](https://github.com/openai/CLIP), which was trained on a variety of (image, text)-pairs.
|
||||
|
||||
We're implementing two methods in the [`/image_search/main.py` file](/image_search/main.py):
|
||||
|
||||
1. The `seed` method generates embeddings for the images in the `images` folder and upserts them into a collection in Supabase Vector.
|
||||
2. Thw `search` method generates an embedding from the search query and performs a vector similarity search query.
|
||||
|
||||
## Setup
|
||||
|
||||
- Install poetry: `pip install poetry`
|
||||
- Activate the virtual environment: `poetry shell`
|
||||
- (to leave the venv just run `exit`)
|
||||
- Install app dependencies: `poetry install`
|
||||
|
||||
## Run locally
|
||||
|
||||
### Generate the embeddings and seed the collection
|
||||
|
||||
- `supabase start`
|
||||
- `poetry run seed`
|
||||
- Check the embeddings stored in the local Supabase Dashboard: http://localhost:54323/project/default/editor > schema: vecs
|
||||
|
||||
### Perform a search
|
||||
|
||||
- `poetry run search "bike in front of red brick wall"`
|
||||
|
||||
## Run on hosted Supabase project
|
||||
|
||||
- Set `DB_CONNECTION` with the connection string from your hosted Supabase Dashboard: https://app.supabase.com/project/_/settings/database > Connection string > URI
|
||||
|
||||
## Attributions
|
||||
|
||||
### Models
|
||||
|
||||
[clip-ViT-B-32](https://www.sbert.net/examples/applications/image-search/README.html) via [Hugging Face](https://huggingface.co/sentence-transformers/clip-ViT-B-32)
|
||||
|
||||
### Images
|
||||
|
||||
Images from https://unsplash.com/license via https://picsum.photos/
|
||||
@@ -0,0 +1,78 @@
|
||||
import sys
|
||||
from PIL import Image
|
||||
from sentence_transformers import SentenceTransformer
|
||||
import vecs
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib import image as mpimg
|
||||
|
||||
DB_CONNECTION = "postgresql://postgres:postgres@localhost:54322/postgres"
|
||||
|
||||
|
||||
def seed():
|
||||
# create vector store client
|
||||
vx = vecs.create_client(DB_CONNECTION)
|
||||
|
||||
# create a collection of vectors with 512 dimensions
|
||||
images = vx.create_collection(name="image_vectors", dimension=512)
|
||||
|
||||
# Load CLIP model
|
||||
model = SentenceTransformer('clip-ViT-B-32')
|
||||
|
||||
# Encode an image:
|
||||
img_emb1 = model.encode(Image.open('./images/one.jpg'))
|
||||
img_emb2 = model.encode(Image.open('./images/two.jpg'))
|
||||
img_emb3 = model.encode(Image.open('./images/three.jpg'))
|
||||
img_emb4 = model.encode(Image.open('./images/four.jpg'))
|
||||
|
||||
# add records to the *images* collection
|
||||
images.upsert(
|
||||
vectors=[
|
||||
(
|
||||
"one.jpg", # the vector's identifier
|
||||
img_emb1, # the vector. list or np.array
|
||||
{"type": "jpg"} # associated metadata
|
||||
), (
|
||||
"two.jpg",
|
||||
img_emb2,
|
||||
{"type": "jpg"}
|
||||
), (
|
||||
"three.jpg",
|
||||
img_emb3,
|
||||
{"type": "jpg"}
|
||||
), (
|
||||
"four.jpg",
|
||||
img_emb4,
|
||||
{"type": "jpg"}
|
||||
)
|
||||
]
|
||||
)
|
||||
print("Inserted images")
|
||||
|
||||
# index the collection for fast search performance
|
||||
images.create_index()
|
||||
print("Created index")
|
||||
|
||||
|
||||
def search(args=sys.argv):
|
||||
# create vector store client
|
||||
vx = vecs.create_client(DB_CONNECTION)
|
||||
images = vx.get_collection(name="image_vectors")
|
||||
|
||||
# Load CLIP model
|
||||
model = SentenceTransformer('clip-ViT-B-32')
|
||||
# Encode text query
|
||||
query_string = args[1]
|
||||
text_emb = model.encode(query_string)
|
||||
|
||||
# query the collection filtering metadata for "type" = "jpg"
|
||||
results = images.query(
|
||||
query_vector=text_emb, # required
|
||||
limit=1, # number of records to return
|
||||
filters={"type": {"$eq": "jpg"}}, # metadata filters
|
||||
)
|
||||
result = results[0]
|
||||
print(result)
|
||||
plt.title(result)
|
||||
image = mpimg.imread('./images/' + result)
|
||||
plt.imshow(image)
|
||||
plt.show()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Generated
+1691
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
||||
[tool.poetry]
|
||||
name = "image-search"
|
||||
version = "0.1.0"
|
||||
description = "Image Search with Supabase Vector"
|
||||
authors = ["thorwebdev <thor@supabase.io>"]
|
||||
readme = "README.md"
|
||||
packages = [{include = "image_search"}]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
sentence-transformers = "^2.2.2"
|
||||
vecs = "^0.2.4"
|
||||
matplotlib = "^3.7.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
seed = "image_search.main:seed"
|
||||
search = "image_search.main:search"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,82 @@
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
|
||||
# directory name when running `supabase init`.
|
||||
project_id = "image_search"
|
||||
|
||||
[api]
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. public and storage are always included.
|
||||
schemas = ["public", "storage", "graphql_public"]
|
||||
# Extra schemas to add to the search_path of every request. public is always included.
|
||||
extra_search_path = ["public", "extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 15
|
||||
|
||||
[studio]
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
smtp_port = 54325
|
||||
pop3_port = 54326
|
||||
|
||||
[storage]
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
[auth]
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://localhost:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://localhost:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
|
||||
# week).
|
||||
jwt_expiry = 3600
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
|
||||
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
secret = ""
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
|
||||
[analytics]
|
||||
enabled = false
|
||||
port = 54327
|
||||
vector_port = 54328
|
||||
# Setup BigQuery project to enable log viewer on local development stack.
|
||||
# See: https://logflare.app/guides/bigquery-setup
|
||||
gcp_project_id = ""
|
||||
gcp_project_number = ""
|
||||
gcp_jwt_path = "supabase/gcloud.json"
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1f181d-feeb-4b29-aabc-67a75234b92c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Semantic Text Deduplication\n",
|
||||
"\n",
|
||||
"In this example we'll use PostgreSQL + pgvectors similarity search using the `vecs` library to identify near duplicate snippets of text.\n",
|
||||
"\n",
|
||||
"Our task is to improve IMDB movie reviews by making sure each review on the site is substantive and original.\n",
|
||||
"To achieve that, we'll identify and remove any reviews that are near duplicates of others."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3cab93f5-10d0-47c5-9f4e-64921461e7e2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Install Dependencies"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a41bc3e4-ea52-43aa-9239-a431b49f029e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install -qU vecs datasets sentence_transformers flupy tqdm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4dbd176f-3b4e-4d41-a72d-1e1affe6ecae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load the Dataset\n",
|
||||
"\n",
|
||||
"First we load the IMBD dataset using the datasets library.\n",
|
||||
"It contains the text of 25000 movie reviews."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "dc6b0bc2-b95f-4190-bf77-fa2dc57fc247",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Found cached dataset imdb (/Users/oliverrice/.cache/huggingface/datasets/imdb/plain_text/1.0.0/d613c88cf8fa3bab83b4ded3713f1f74830d1100e171db75bbddb80b3345c9c0)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Dataset({\n",
|
||||
" features: ['text', 'label'],\n",
|
||||
" num_rows: 25000\n",
|
||||
"})"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from datasets import load_dataset\n",
|
||||
"\n",
|
||||
"data = load_dataset(\"imdb\", split=\"train\")\n",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "da15eff4-932c-4e0c-b938-ba188af62b63",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"\"I would put this at the top of my list of films in the category of unwatchable trash! There are films that are bad, but the worst kind are the ones that are unwatchable but you are suppose to like them because they are supposed to be good for you! The sex sequences, so shocking in its day, couldn't even arouse a rabbit. The so called controversial politics is strictly high school sophomore amateur night Marxism. The film is self-consciously arty in the worst sense of the term. The photography is in a harsh grainy black and white. Some scenes are out of focus or taken from the wrong angle. Even the sound is bad! And some people call this art?<br /><br />\""
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Look at an example review\n",
|
||||
"data[\"text\"][5]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "955b2700-8242-40eb-ac3f-d479a0312693",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Embedding Model\n",
|
||||
"\n",
|
||||
"Next, we can use the `sentence-transformers/all-MiniLM-L6-v2` model to create a 384 dimensional text embedding that represents the\n",
|
||||
"semantic meaning of each review. These embeddings are what we'll use for near-duplicate detection."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "ce9bc85b-e844-407c-a0ad-ccf6af3c8866",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from sentence_transformers import SentenceTransformer\n",
|
||||
"\n",
|
||||
"model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9e82862d-440a-4f66-9ed7-0eaa6a0f4062",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize the Vecs Collection\n",
|
||||
"\n",
|
||||
"The [`vecs`](https://supabase.github.io/vecs/api/) library wraps a pythonic interface around PostgreSQL and pgvector.\n",
|
||||
"A collection in `vecs` maps 1:1 with a PostgreSQL table."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "d2771545-d209-4ceb-a222-ed139a4620f2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import vecs\n",
|
||||
"\n",
|
||||
"# Substitute your connection string here\n",
|
||||
"DB_CONNECTION = \"postgresql://postgres:password@localhost:5431/db\"\n",
|
||||
"\n",
|
||||
"# create vector store client\n",
|
||||
"vx = vecs.create_client(DB_CONNECTION)\n",
|
||||
"\n",
|
||||
"# create a PostgreSQL/pgvector table named \"reviews\" to contain the review embeddings\n",
|
||||
"reviews = vx.create_collection(name=\"reviews\", dimension=384)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9ae030b4-cfd2-43bc-802f-e7ac4007d2ad",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create Embeddings for Each Review\n",
|
||||
"\n",
|
||||
"Now we can iterate over the dataset, producing embeddings for the reviews"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "bd134310-9da1-4448-8358-9fc491c98e1e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"500it [07:06, 1.17it/s]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from typing import List, Dict, Tuple\n",
|
||||
"from flupy import flu\n",
|
||||
"import numpy as np\n",
|
||||
"from tqdm import tqdm\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"batch_size = 50\n",
|
||||
"\n",
|
||||
"records: List[Tuple[str, np.ndarray, Dict]] = []\n",
|
||||
"\n",
|
||||
"# Iterate over the dataset in chunks\n",
|
||||
"for chunk_ix, chunk in tqdm(flu(data['text']).chunk(batch_size).enumerate()):\n",
|
||||
"\n",
|
||||
" # Create embeddings for current chunk\n",
|
||||
" embedding_chunk = model.encode(chunk)\n",
|
||||
"\n",
|
||||
" # Enumerate the embeddings and create a record to insert into the database\n",
|
||||
" for row_ix, (text, embedding) in enumerate(zip(chunk, embedding_chunk)):\n",
|
||||
" record_id = chunk_ix * batch_size + row_ix\n",
|
||||
" records.append((f\"{record_id}\", embedding, {\"text\": text}))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6ef285d7-dc7b-4576-ab2b-bf0a4ba06de4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Insert the Embeddings into Postgres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "6156e7f9-b78a-4ab9-8002-b09ec8716be2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"reviews.upsert(records)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aa095a53-dd9c-4a3c-93bf-c54708c67765",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Index the Collection\n",
|
||||
"\n",
|
||||
"Indexing the collection creates an index on the vector column in Postgres that significantly improves performance\n",
|
||||
"of similarity queries."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "6b96d3af-5592-4fbc-81ab-f77b4228ccaa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"reviews.create_index()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "371631e6-4995-484f-9d76-40ab5e7b2e16",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Search for Near Duplicates\n",
|
||||
"\n",
|
||||
"Finally we can enumerate each review, searching for the most similar\n",
|
||||
"reviews and displaying them if the results are near duplicates. We could then prune out the near-duplicate reviews\n",
|
||||
"to make sure our viewers see a new and interesting opinion with each review they choose to read."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"id": "d2a5c699-7c04-48ad-9033-a310bbb0dffc",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"824it [00:05, 187.11it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 817 \t result_id: 823 \t distance 0.1672 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" This has to be, by far, the absolute worst movie I have seen in the last 20 years. When I saw that Michael Madsen was in it I figured it couldn't be too bad a movie since he has been in some pretty decent films, and he was a pretty fair actor. WRONG! No one should waste their time on this film. I fast forwarded through 80 percent of it and I don't feel that I missed a thing. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" I agree totally with the last commenter this could be the worst movie ever made .I too had to fast forward through most of this movie. Michael Madsen must have done this movie as a favor to someone.The picture quality is grainy all the way through .And what little plot there is,is just plain stupid .I give this movie a 1 out of 10 if I could give it a lower score I would .Don't waste your time on this movie or you'll regret it. \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"8675it [00:47, 166.43it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 8663 \t result_id: 8669 \t distance 0.1532 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" Rodney Dangerfield is a great. He has done a lot of great works. But this one....is awful. The whole plot is whack. It could have been much better. The jokes in the movie aren't funny....their stupid. This was very not so hilarious. He can do much better than this. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" As much as I love Rodney Dangerfield, this was a terrible movie. The plot was kind of a holistic rip off of various movies, but unfortunately they forgot to rip off any good jokes. In addition it was annoying and boring and that's being kind. If you're looking for a good laugh, rent a copy of Private Parts. \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"10522it [00:58, 164.98it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 10486 \t result_id: 5437 \t distance 0.1634 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" Aside for being classic in the aspect of its cheesy lines and terrible acting, this film should never be watched unless you are looking for a good cure for your insomnia. I can't imagine anyone actually thinking this was a \"good movie.\" \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" Aside from the horrendous acting and the ridiculous and ludicrous plot, this movie wasn't too bad. Unfortunately, that doesn't leave much movie not to suck. Do not waste your time on this film, even if you find yourself suffering from insomnia, as I did. Watch an infomercial instead. \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"16405it [01:36, 97.07it/s] "
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 16401 \t result_id: 16424 \t distance 0.1583 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" This has to be the funniest stand up comedy I have ever seen. Eddie Izzard is a genius, he picks in Brits, Americans and everyone in between. His style is completely natural and completely hilarious. I doubt that anyone could sit through this and not laugh their a** off. Watch, enjoy, it's funny. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" Until I saw this special on HBO, I had never heard of Eddie Izzard. I sure am glad that I have now! He is one of the funniest comedians I have ever seen! Rarely has a comedian immersed himself so completely in his craft then Eddie. I could not stop laughing for the entire show. If you like to laugh you HAVE to see this special! \n",
|
||||
" --------------------------------------------------------------------------------\n",
|
||||
"query_id: 16401 \t result_id: 16408 \t distance 0.1663 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" This has to be the funniest stand up comedy I have ever seen. Eddie Izzard is a genius, he picks in Brits, Americans and everyone in between. His style is completely natural and completely hilarious. I doubt that anyone could sit through this and not laugh their a** off. Watch, enjoy, it's funny. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" This is another gem of a stand up show from Eddie Izzard . You cannot fail to laugh at the wide range of topics he talks about. He even takes the piss out of his American audiance at times and most of them didnt even realise it! A must see for anybody who likes comedians. 9 out of 10. \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"18953it [01:54, 101.67it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 18936 \t result_id: 18942 \t distance 0.1325 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" World At War is perhaps the greatest documentary series of all time. The historical research is virtually flawless. Even after a quarter century, it is the most accurate and definitive documentary about WW2. An invaluable historical work that includes interviews with some of the most important and fascinating figures from the war. I highly recommend it as a learning experience. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" The world at war is one of the best documentaries about world war 2. <br /><br />The 24 episodes cover the war and what it was like in the countries involved in it. The first episode tells us how the Hitler came to power, and how he was able to build up one of the strongest armies in the world. They also fucus on the military actions taken during the war, and the holocaust. One of the strongest and best documentaries ever made. All of you must watch this. Perfection! 10/10<br /><br /> \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"21236it [02:09, 144.35it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"query_id: 21202 \t result_id: 2618 \t distance 0.1586 \n",
|
||||
"\n",
|
||||
" Query Text \n",
|
||||
"\n",
|
||||
" I think Hollow Point is a funny film with some good moments I have never seen before in action movies. Well,both Tia Carrere and Thomas Ian Griffith aren't so good in acting, but Tia Carrere is nice and good looking girl, isn't it? But Donald Sutherland is superb in his role so-so mad gangster. \n",
|
||||
"\n",
|
||||
" Result Text \n",
|
||||
"\n",
|
||||
" Hollow point is an alright movie worth a half price rental or if nothing else is on a good time waster with no thought required. There are the requisite explosions and hammy acting and pretty ladies. A pretty good cast with Donald Sutherland, John Lithgow, and the lovely Tia Carrere. This cast plus a light hearted touch make for a not a great movie but a fun one..on a scale of one to ten ..a 4 \n",
|
||||
" --------------------------------------------------------------------------------\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"25000it [02:38, 157.92it/s]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for ix, text in tqdm(enumerate(data['text'])):\n",
|
||||
"\n",
|
||||
" # Load the next row from the dataset\n",
|
||||
" query_results = reviews.fetch(ids=[f'{ix}'])\n",
|
||||
" \n",
|
||||
" (query_id, query_embedding, query_meta) = query_results[0]\n",
|
||||
"\n",
|
||||
" # Retrieve the original text from the row's metadata\n",
|
||||
" query_text = query_meta[\"text\"]\n",
|
||||
"\n",
|
||||
" # To keep the output easy to read quickly, we'll restrict reviews to < 500 characters\n",
|
||||
" # In the real-world you would not include this restriction\n",
|
||||
" if len(query_text) < 500:\n",
|
||||
"\n",
|
||||
" # Query the review embeddings for the most similar 5 reviews\n",
|
||||
" top_5 = reviews.query(\n",
|
||||
" query_vector=query_embedding,\n",
|
||||
" limit = 5,\n",
|
||||
" include_metadata= True,\n",
|
||||
" include_value=True\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # For each result\n",
|
||||
" for result_id, result_distance, result_meta in top_5[1:]:\n",
|
||||
" \n",
|
||||
" result_text = result_meta[\"text\"]\n",
|
||||
"\n",
|
||||
" if (\n",
|
||||
" # Since our query embedding is in the collection, the nearest result\n",
|
||||
" # is always itself with a distance of 0. We exclude that record and \n",
|
||||
" # review any others with a distance < 0.17\n",
|
||||
" 0.01 < abs(result_distance) < 0.17\n",
|
||||
" and len(result_text) < 500\n",
|
||||
" and query_id < result_id\n",
|
||||
" ):\n",
|
||||
" print(\n",
|
||||
" \"query_id:\", query_id,\n",
|
||||
" \"\\t\", \"result_id:\", result_id,\n",
|
||||
" \"\\t\", \"distance\", round(result_distance, 4),\n",
|
||||
" \"\\n\\n\", \"Query Text\",\n",
|
||||
" \"\\n\\n\", query_meta[\"text\"],\n",
|
||||
" \"\\n\\n\", \"Result Text\",\n",
|
||||
" \"\\n\\n\", result_meta[\"text\"],\n",
|
||||
" \"\\n\", \"-\" * 80\n",
|
||||
" )"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in new issue
Block a user