Files
fastembed/docs/examples/FastEmbed_vs_HF_Comparison.ipynb
Nirant c09909773e Add Execution Counts (#177)
* Update notebooks

* Update FastEmbed usage across docs

* Refactor code for better readability and maintainability

* Clean outputs

* Change dataset

* Add numbers inline in output

* Remove inline outputs since I used :memory:

* Fix syntax error in Hindi_Tamil_RAG_with_Navarasa7B.ipynb
2024-04-01 20:48:26 +05:30

429 lines
50 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🤗 Huggingface vs ⚡ FastEmbed\n",
"\n",
"Comparing the performance of Huggingface's 🤗 Transformers and ⚡ FastEmbed on a simple task on the following machine: Apple M2 Max, 32 GB RAM\n",
"\n",
"## 📦 Imports\n",
"\n",
"Importing the necessary libraries for this comparison."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: matplotlib in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (3.8.3)\n",
"Requirement already satisfied: transformers in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (4.39.2)\n",
"Requirement already satisfied: torch in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (2.2.0)\n",
"Requirement already satisfied: contourpy>=1.0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (1.2.0)\n",
"Requirement already satisfied: cycler>=0.10 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (0.12.1)\n",
"Requirement already satisfied: fonttools>=4.22.0 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (4.50.0)\n",
"Requirement already satisfied: kiwisolver>=1.3.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (1.4.5)\n",
"Requirement already satisfied: numpy<2,>=1.21 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (1.26.4)\n",
"Requirement already satisfied: packaging>=20.0 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (24.0)\n",
"Requirement already satisfied: pillow>=8 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (10.2.0)\n",
"Requirement already satisfied: pyparsing>=2.3.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (3.1.2)\n",
"Requirement already satisfied: python-dateutil>=2.7 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)\n",
"Requirement already satisfied: filelock in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (3.13.1)\n",
"Requirement already satisfied: huggingface-hub<1.0,>=0.19.3 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (0.20.3)\n",
"Requirement already satisfied: pyyaml>=5.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (6.0.1)\n",
"Requirement already satisfied: regex!=2019.12.17 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (2023.12.25)\n",
"Requirement already satisfied: requests in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (2.31.0)\n",
"Requirement already satisfied: tokenizers<0.19,>=0.14 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (0.15.2)\n",
"Requirement already satisfied: safetensors>=0.4.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (0.4.2)\n",
"Requirement already satisfied: tqdm>=4.27 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (4.66.2)\n",
"Requirement already satisfied: typing-extensions>=4.8.0 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (4.10.0)\n",
"Requirement already satisfied: sympy in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (1.12)\n",
"Requirement already satisfied: networkx in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (3.2.1)\n",
"Requirement already satisfied: jinja2 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (3.1.3)\n",
"Requirement already satisfied: fsspec in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (2024.2.0)\n",
"Requirement already satisfied: six>=1.5 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from jinja2->torch) (2.1.5)\n",
"Requirement already satisfied: charset-normalizer<4,>=2 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from requests->transformers) (3.3.2)\n",
"Requirement already satisfied: idna<4,>=2.5 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from requests->transformers) (3.6)\n",
"Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from requests->transformers) (2.2.1)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from requests->transformers) (2024.2.2)\n",
"Requirement already satisfied: mpmath>=0.19 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from sympy->torch) (1.3.0)\n"
]
}
],
"source": [
"!pip install matplotlib transformers torch"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:38:48.671752Z",
"start_time": "2024-03-30T00:38:48.669409Z"
}
},
"outputs": [],
"source": [
"import time\n",
"from typing import Callable, List, Tuple\n",
"\n",
"import torch.nn.functional as F\n",
"from fastembed import TextEmbedding\n",
"import matplotlib.pyplot as plt\n",
"from torch import Tensor\n",
"from transformers import AutoModel, AutoTokenizer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📖 Data\n",
"\n",
"data is a list of strings, each string is a document."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:34.512097Z",
"start_time": "2024-03-30T00:43:34.509352Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"documents: List[str] = [\n",
" \"Chandrayaan-3 is India's third lunar mission\",\n",
" \"It aimed to land a rover on the Moon's surface - joining the US, China and Russia\",\n",
" \"The mission is a follow-up to Chandrayaan-2, which had partial success\",\n",
" \"Chandrayaan-3 will be launched by the Indian Space Research Organisation (ISRO)\",\n",
" \"The estimated cost of the mission is around $35 million\",\n",
" \"It will carry instruments to study the lunar surface and atmosphere\",\n",
" \"Chandrayaan-3 landed on the Moon's surface on 23rd August 2023\",\n",
" \"It consists of a lander named Vikram and a rover named Pragyan similar to Chandrayaan-2. Its propulsion module would act like an orbiter.\",\n",
" \"The propulsion module carries the lander and rover configuration until the spacecraft is in a 100-kilometre (62 mi) lunar orbit\",\n",
" \"The mission used GSLV Mk III rocket for its launch\",\n",
" \"Chandrayaan-3 was launched from the Satish Dhawan Space Centre in Sriharikota\",\n",
" \"Chandrayaan-3 was launched earlier in the year 2023\",\n",
"]\n",
"len(documents)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting up 🤗 Huggingface\n",
"\n",
"We'll be using the [Huggingface Transformers](https://huggingface.co/transformers/) with PyTorch library to generate embeddings. We'll be using the same model across both libraries for a fair(er?) comparison."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:35.417504Z",
"start_time": "2024-03-30T00:43:34.800606Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"torch.Size([12, 384])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class HF:\n",
" \"\"\"\n",
" HuggingFace Transformer implementation of FlagEmbedding\n",
" \"\"\"\n",
"\n",
" def __init__(self, model_id: str):\n",
" self.model = AutoModel.from_pretrained(model_id)\n",
" self.tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
"\n",
" def embed(self, texts: List[str]):\n",
" encoded_input = self.tokenizer(\n",
" texts, max_length=512, padding=True, truncation=True, return_tensors=\"pt\"\n",
" )\n",
" model_output = self.model(**encoded_input)\n",
" sentence_embeddings = model_output[0][:, 0]\n",
" sentence_embeddings = F.normalize(sentence_embeddings)\n",
" return sentence_embeddings\n",
"\n",
"\n",
"model_id = \"BAAI/bge-small-en\"\n",
"hf = HF(model_id=model_id)\n",
"hf.embed(documents).shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting up ⚡FastEmbed\n",
"\n",
"Sorry, don't have a lot to set up here. We'll be using the default model, which is Flag Embedding, same as the Huggingface model."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:35.486719Z",
"start_time": "2024-03-30T00:43:35.416166Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
"To disable this warning, you can either:\n",
"\t- Avoid using `tokenizers` before the fork if possible\n",
"\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
"100%|██████████| 77.7M/77.7M [00:15<00:00, 4.99MiB/s]\n"
]
}
],
"source": [
"embedding_model = TextEmbedding(model_name=model_id)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📊 Comparison\n",
"\n",
"We'll be comparing the following metrics: Minimum, Maximum, Mean, across k runs. Let's write a function to do that:\n",
"\n",
"### 🚀 Calculating Stats"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:35.693539Z",
"start_time": "2024-03-30T00:43:35.488973Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Huggingface Transformers (Average, Max, Min): (0.07326400279998779, 0.08054399490356445, 0.06598401069641113)\n",
"FastEmbed (Average, Max, Min): (0.09258604049682617, 0.13388705253601074, 0.0512850284576416)\n"
]
}
],
"source": [
"import types\n",
"\n",
"\n",
"def calculate_time_stats(\n",
" embed_func: Callable, documents: list, k: int\n",
") -> Tuple[float, float, float]:\n",
" times = []\n",
" for _ in range(k):\n",
" # Timing the embed_func call\n",
" start_time = time.time()\n",
" embeddings = embed_func(documents)\n",
" # Force computation if embed_func returns a generator\n",
" if isinstance(embeddings, types.GeneratorType):\n",
" list(embeddings)\n",
"\n",
" end_time = time.time()\n",
" times.append(end_time - start_time)\n",
"\n",
" # Returning mean, max, and min time for the call\n",
" return (sum(times) / k, max(times), min(times))\n",
"\n",
"\n",
"hf_stats = calculate_time_stats(hf.embed, documents, k=2)\n",
"print(f\"Huggingface Transformers (Average, Max, Min): {hf_stats}\")\n",
"fst_stats = calculate_time_stats(embedding_model.embed, documents, k=2)\n",
"print(f\"FastEmbed (Average, Max, Min): {fst_stats}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📈 Results\n",
"\n",
"Let's run the comparison and see the results."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:35.746781Z",
"start_time": "2024-03-30T00:43:35.698423Z"
}
},
"outputs": [
{
"data": {
"image/png": "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i4kTJyI+Ph6VKlXCrFmz0KFDB9nHos4ti6+DUw4QFY9TDhDR61Ln77fWzNP0rmNoIio9DE1E9Lre23maiIiIiEoLQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJIN+aRdAREQk19mLtzB76VacOHsDQgjU9nHHl8O7oGplZ6nP7XuP0LTrlGK3EdjFF9993RsAcPrCTazfdgQxx6/gzv1kWCvMUKuaGz7/rCMquNqrrHv1RgK+mb8Bx09fg4GBPlo1roqJo7rB1tripXXHxF5BryGLil3+xWcdETLA/xVHT6WNoYmIiN4J5y7dxgeDF8DJ3gojB7ZDQYHAb+v2I/DThdi08gt4uDoAAGytzTF/ah+V9ffGXMCmiONo2sBLagv/dReOn76OgNa1UKWiEx48SseqtfvQsc9MbPzlc3h6OEl97yemoOenC2FhboyxQzshMzMby1dH49LVe/hn5RcwNCj+T2pFN4cia9rw71HsP3IJTRtWeZOnht4ShiYiInonzF22FcZGBtjw8+ewtjIDAHRrXw8tP/gWs3/YgvCZAwEApiZG6Na+nsr667YehoWZMVo3rSa1DezdEgu/7asUeDq2qQ3/3mFYuioSC77pK7UvWbkTmU+zseXXsSjnaAMAqFHVFR+HLMG6rUfQu1vjYmu3s7UssqaFP22Hu4sdani7qvlsUGngmCYiInonHDt1HU3qeUqBCQDsyyjQoFZFRB84jyeZ2cWum/QwDTGxV+DfsgaMjQyk9jrVK6icIXIvb4/KFcrianyiUnvE7tNo3aSaFJgAoEn9KqhQ3h7bdp1Q+3hOnY9H/O0H6NKurtrrUulgaCIiondCTk4e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b1+sXLkS48aNw5MnTzB48GCkpqaiSZMmiIiIkAITAKxevRohISFo3bo1dHV10aNHDyxa9L9vklYoFNi5cyeGDRuGOnXqoEyZMggNDVWay6lRo0b4448/MHHiRHz11VeoVKkSNm3aJDswERER0fuvVM80vU94pomo9PBMExG9LnX+fvPuOSIiIiIZtHYgOBHRf811t46lXQKRVqsQv7VU988zTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDPpyOo0ZM0b2BufNm/faxRARERFpK1mh6eTJk0qPT5w4gby8PHh6egIALl++DD09PdSpU0fzFRIRERFpAVmhaffu3dK/582bBwsLC6xatQrW1tYAgJSUFPTv3x9NmzYtmSqJiIiISpnaY5rmzp2LsLAwKTABgLW1NaZNm4a5c+dqtDgiIiIibaF2aEpPT8eDBw9U2h88eIDHjx9rpCgiIiIibaN2aOrWrRv69++PDRs24M6dO7hz5w7Wr1+P4OBgdO/evSRqJCIiIip1ssY0PS88PBxffPEFevfujdzc3Gcb0ddHcHAwZs+erfECiYiIiLSB2qHJ1NQUP/zwA2bPno1r164BADw8PGBmZqbx4oiIiIi0hdqhqZCZmRmqV6+uyVqIiIiItJbaoenJkyf47rvvEBUVhaSkJBQUFCgtv379usaKIyIiItIWaoemgQMHYu/evfjkk09QtmxZ6OjolERdRERERFpF7dC0fft2bNu2DY0bNy6JeoiIiIi0ktpTDlhbW8PGxqYkaiEiIiLSWmqHpm+//RahoaHIzMwsiXqIiIiItJLal+fmzp2La9euwcHBAW5ubjAwMFBafuLECY0VR0RERKQt1A5NXbt2LYEyiIiIiLSb2qFp8uTJJVEHERERkVZ77cktY2NjcfHiRQBA1apVUatWLY0VRURERKRt1A5NSUlJCAwMxJ49e2BlZQUASE1NRcuWLfHXX3/Bzs5O0zUSERERlTq1754bPnw4Hj9+jPPnzyM5ORnJyck4d+4c0tPTMWLEiJKokYiIiKjUqX2mKSIiArt27YKXl5fU5u3tjSVLlqBt27YaLY6I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j5eIiIi01zs1T5M24zxNRKWH8zQR/TdwniYiIiKidwBDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNL1iyZAnc3NxgbGyMBg0a4OjRo6VdEhEREWkBhqbn/P333xgzZgwmT56MEydOoEaNGvD390dSUlJpl0ZERESljKHpOfPmzcOgQYPQv39/eHt7Izw8HKampvjll19KuzQiIiIqZfqlXYC2yMnJQWxsLCZMmCC16erqws/PDzExMSr9s7OzkZ2dLT1OS0sDAKSnp5dIfQX5OSWyXaL3QUm97962xwW5pV0CkVYrifd64TaFEK/sy9D0/x4+fIj8/Hw4ODgotTs4OODSpUsq/cPCwjB16lSVdhcXlxKrkYiKplAsL+0SiOhtUChKbNOPHz+G4hXbZ2h6TRMmTMCYMWOkxwUFBUhOToatrS10dHRKsTIqaenp6XBxccHt27dhaWlZ2uUQUQng+/y/QwiBx48fw8nJ6ZV9GZr+X5kyZaCnp4fExESl9sTERDg6Oqr0NzIygpGRkVKblZVVSZZIWsbS0pIfpkTvOb7P/xtedYapEAeC/z9DQ0PUqVMHUVFRUltBQQGioqLg6+tbipURERGRNuCZpueMGTMGffv2Rd26dVG/fn0sWLAAT548Qf/+/Uu7NCIiIiplDE3P+eijj/DgwQOEhoYiISEBNWvWREREhMrgcPpvMzIywuTJk1UuzxLR+4PvcyqKjpBzjx0RERHRfxzHNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA00X9SixYtMGrUKI1v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",
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_character_per_second_comparison(\n",
" hf_stats: Tuple[float, float, float], fst_stats: Tuple[float, float, float], documents: list\n",
"):\n",
" # Calculating total characters in documents\n",
" total_characters = sum(len(doc) for doc in documents)\n",
"\n",
" # Calculating characters per second for each model\n",
" hf_chars_per_sec = total_characters / hf_stats[0] # Mean time is at index 0\n",
" fst_chars_per_sec = total_characters / fst_stats[0]\n",
"\n",
" # Plotting the bar chart\n",
" models = [\"HF Embed (Torch)\", \"FastEmbed\"]\n",
" chars_per_sec = [hf_chars_per_sec, fst_chars_per_sec]\n",
"\n",
" bars = plt.bar(models, chars_per_sec, color=[\"#1f356c\", \"#dd1f4b\"])\n",
" plt.ylabel(\"Characters per Second\")\n",
" plt.title(\"Characters Processed per Second Comparison\")\n",
"\n",
" # Adding the number at the top of each bar\n",
" for bar, chars in zip(bars, chars_per_sec):\n",
" plt.text(\n",
" bar.get_x() + bar.get_width() / 2,\n",
" bar.get_height(),\n",
" f\"{chars:.1f}\",\n",
" ha=\"center\",\n",
" va=\"bottom\",\n",
" color=\"#1f356c\",\n",
" fontsize=12,\n",
" )\n",
"\n",
" plt.show()\n",
"\n",
"\n",
"plot_character_per_second_comparison(hf_stats, fst_stats, documents)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Are the Embeddings the same?\n",
"\n",
"This is a very important question. Let's see if the embeddings are the same."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2024-03-30T00:43:25.537072Z",
"start_time": "2024-03-30T00:43:25.419184Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/b4/grpbcmrd36gc7q5_11whbn540000gn/T/ipykernel_42284/1958479940.py:8: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/torch/csrc/utils/tensor_new.cpp:278.)\n",
" calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))\n"
]
},
{
"data": {
"text/plain": [
"0.9999997019767761"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def calculate_cosine_similarity(embeddings1: Tensor, embeddings2: Tensor) -> float:\n",
" \"\"\"\n",
" Calculate cosine similarity between two sets of embeddings\n",
" \"\"\"\n",
" return F.cosine_similarity(embeddings1, embeddings2).mean().item()\n",
"\n",
"\n",
"calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This indicates the embeddings are quite close to each with a cosine similarity of 0.99 for BAAI/bge-small-en and 0.92 for BAAI/bge-small-en-v1.5. This gives us confidence that the embeddings are the same and we are not sacrificing accuracy for speed."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "fst",
"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.13"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}