fastembed

Форк
0
/
FastEmbed_vs_HF_Comparison.ipynb 
428 строк · 50.4 Кб
1
{
2
 "cells": [
3
  {
4
   "cell_type": "markdown",
5
   "metadata": {},
6
   "source": [
7
    "# 🤗 Huggingface vs ⚡ FastEmbed️\n",
8
    "\n",
9
    "Comparing the performance of Huggingface's 🤗 Transformers and ⚡ FastEmbed️ on a simple task on the following machine: Apple M2 Max, 32 GB RAM\n",
10
    "\n",
11
    "## 📦 Imports\n",
12
    "\n",
13
    "Importing the necessary libraries for this comparison."
14
   ]
15
  },
16
  {
17
   "cell_type": "code",
18
   "execution_count": 3,
19
   "metadata": {},
20
   "outputs": [
21
    {
22
     "name": "stdout",
23
     "output_type": "stream",
24
     "text": [
25
      "Requirement already satisfied: matplotlib in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (3.8.3)\n",
26
      "Requirement already satisfied: transformers in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (4.39.2)\n",
27
      "Requirement already satisfied: torch in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (2.2.0)\n",
28
      "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",
29
      "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",
30
      "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",
31
      "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",
32
      "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",
33
      "Requirement already satisfied: packaging>=20.0 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (24.0)\n",
34
      "Requirement already satisfied: pillow>=8 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from matplotlib) (10.2.0)\n",
35
      "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",
36
      "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",
37
      "Requirement already satisfied: filelock in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (3.13.1)\n",
38
      "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",
39
      "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",
40
      "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",
41
      "Requirement already satisfied: requests in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from transformers) (2.31.0)\n",
42
      "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",
43
      "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",
44
      "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",
45
      "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",
46
      "Requirement already satisfied: sympy in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (1.12)\n",
47
      "Requirement already satisfied: networkx in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (3.2.1)\n",
48
      "Requirement already satisfied: jinja2 in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (3.1.3)\n",
49
      "Requirement already satisfied: fsspec in /opt/homebrew/Caskroom/miniconda/base/envs/fst/lib/python3.10/site-packages (from torch) (2024.2.0)\n",
50
      "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",
51
      "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",
52
      "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",
53
      "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",
54
      "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",
55
      "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",
56
      "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"
57
     ]
58
    }
59
   ],
60
   "source": [
61
    "!pip install matplotlib transformers torch"
62
   ]
63
  },
64
  {
65
   "cell_type": "code",
66
   "execution_count": 4,
67
   "metadata": {
68
    "ExecuteTime": {
69
     "end_time": "2024-03-30T00:38:48.671752Z",
70
     "start_time": "2024-03-30T00:38:48.669409Z"
71
    }
72
   },
73
   "outputs": [],
74
   "source": [
75
    "import time\n",
76
    "from typing import Callable, List, Tuple\n",
77
    "\n",
78
    "import torch.nn.functional as F\n",
79
    "from fastembed import TextEmbedding\n",
80
    "import matplotlib.pyplot as plt\n",
81
    "from torch import Tensor\n",
82
    "from transformers import AutoModel, AutoTokenizer"
83
   ]
84
  },
85
  {
86
   "cell_type": "markdown",
87
   "metadata": {},
88
   "source": [
89
    "## 📖 Data\n",
90
    "\n",
91
    "data is a list of strings, each string is a document."
92
   ]
93
  },
94
  {
95
   "cell_type": "code",
96
   "execution_count": 5,
97
   "metadata": {
98
    "ExecuteTime": {
99
     "end_time": "2024-03-30T00:43:34.512097Z",
100
     "start_time": "2024-03-30T00:43:34.509352Z"
101
    }
102
   },
103
   "outputs": [
104
    {
105
     "data": {
106
      "text/plain": [
107
       "12"
108
      ]
109
     },
110
     "execution_count": 5,
111
     "metadata": {},
112
     "output_type": "execute_result"
113
    }
114
   ],
115
   "source": [
116
    "documents: List[str] = [\n",
117
    "    \"Chandrayaan-3 is India's third lunar mission\",\n",
118
    "    \"It aimed to land a rover on the Moon's surface - joining the US, China and Russia\",\n",
119
    "    \"The mission is a follow-up to Chandrayaan-2, which had partial success\",\n",
120
    "    \"Chandrayaan-3 will be launched by the Indian Space Research Organisation (ISRO)\",\n",
121
    "    \"The estimated cost of the mission is around $35 million\",\n",
122
    "    \"It will carry instruments to study the lunar surface and atmosphere\",\n",
123
    "    \"Chandrayaan-3 landed on the Moon's surface on 23rd August 2023\",\n",
124
    "    \"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",
125
    "    \"The propulsion module carries the lander and rover configuration until the spacecraft is in a 100-kilometre (62 mi) lunar orbit\",\n",
126
    "    \"The mission used GSLV Mk III rocket for its launch\",\n",
127
    "    \"Chandrayaan-3 was launched from the Satish Dhawan Space Centre in Sriharikota\",\n",
128
    "    \"Chandrayaan-3 was launched earlier in the year 2023\",\n",
129
    "]\n",
130
    "len(documents)"
131
   ]
132
  },
133
  {
134
   "cell_type": "markdown",
135
   "metadata": {},
136
   "source": [
137
    "## Setting up 🤗 Huggingface\n",
138
    "\n",
139
    "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."
140
   ]
141
  },
142
  {
143
   "cell_type": "code",
144
   "execution_count": 6,
145
   "metadata": {
146
    "ExecuteTime": {
147
     "end_time": "2024-03-30T00:43:35.417504Z",
148
     "start_time": "2024-03-30T00:43:34.800606Z"
149
    }
150
   },
151
   "outputs": [
152
    {
153
     "data": {
154
      "text/plain": [
155
       "torch.Size([12, 384])"
156
      ]
157
     },
158
     "execution_count": 6,
159
     "metadata": {},
160
     "output_type": "execute_result"
161
    }
162
   ],
163
   "source": [
164
    "class HF:\n",
165
    "    \"\"\"\n",
166
    "    HuggingFace Transformer implementation of FlagEmbedding\n",
167
    "    \"\"\"\n",
168
    "\n",
169
    "    def __init__(self, model_id: str):\n",
170
    "        self.model = AutoModel.from_pretrained(model_id)\n",
171
    "        self.tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
172
    "\n",
173
    "    def embed(self, texts: List[str]):\n",
174
    "        encoded_input = self.tokenizer(\n",
175
    "            texts, max_length=512, padding=True, truncation=True, return_tensors=\"pt\"\n",
176
    "        )\n",
177
    "        model_output = self.model(**encoded_input)\n",
178
    "        sentence_embeddings = model_output[0][:, 0]\n",
179
    "        sentence_embeddings = F.normalize(sentence_embeddings)\n",
180
    "        return sentence_embeddings\n",
181
    "\n",
182
    "\n",
183
    "model_id = \"BAAI/bge-small-en\"\n",
184
    "hf = HF(model_id=model_id)\n",
185
    "hf.embed(documents).shape"
186
   ]
187
  },
188
  {
189
   "cell_type": "markdown",
190
   "metadata": {},
191
   "source": [
192
    "## Setting up ⚡️FastEmbed\n",
193
    "\n",
194
    "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."
195
   ]
196
  },
197
  {
198
   "cell_type": "code",
199
   "execution_count": 7,
200
   "metadata": {
201
    "ExecuteTime": {
202
     "end_time": "2024-03-30T00:43:35.486719Z",
203
     "start_time": "2024-03-30T00:43:35.416166Z"
204
    }
205
   },
206
   "outputs": [
207
    {
208
     "name": "stderr",
209
     "output_type": "stream",
210
     "text": [
211
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
212
      "To disable this warning, you can either:\n",
213
      "\t- Avoid using `tokenizers` before the fork if possible\n",
214
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
215
      "100%|██████████| 77.7M/77.7M [00:15<00:00, 4.99MiB/s]\n"
216
     ]
217
    }
218
   ],
219
   "source": [
220
    "embedding_model = TextEmbedding(model_name=model_id)"
221
   ]
222
  },
223
  {
224
   "cell_type": "markdown",
225
   "metadata": {},
226
   "source": [
227
    "## 📊 Comparison\n",
228
    "\n",
229
    "We'll be comparing the following metrics: Minimum, Maximum, Mean, across k runs. Let's write a function to do that:\n",
230
    "\n",
231
    "### 🚀 Calculating Stats"
232
   ]
233
  },
234
  {
235
   "cell_type": "code",
236
   "execution_count": 8,
237
   "metadata": {
238
    "ExecuteTime": {
239
     "end_time": "2024-03-30T00:43:35.693539Z",
240
     "start_time": "2024-03-30T00:43:35.488973Z"
241
    }
242
   },
243
   "outputs": [
244
    {
245
     "name": "stdout",
246
     "output_type": "stream",
247
     "text": [
248
      "Huggingface Transformers (Average, Max, Min): (0.07326400279998779, 0.08054399490356445, 0.06598401069641113)\n",
249
      "FastEmbed (Average, Max, Min): (0.09258604049682617, 0.13388705253601074, 0.0512850284576416)\n"
250
     ]
251
    }
252
   ],
253
   "source": [
254
    "import types\n",
255
    "\n",
256
    "\n",
257
    "def calculate_time_stats(\n",
258
    "    embed_func: Callable, documents: list, k: int\n",
259
    ") -> Tuple[float, float, float]:\n",
260
    "    times = []\n",
261
    "    for _ in range(k):\n",
262
    "        # Timing the embed_func call\n",
263
    "        start_time = time.time()\n",
264
    "        embeddings = embed_func(documents)\n",
265
    "        # Force computation if embed_func returns a generator\n",
266
    "        if isinstance(embeddings, types.GeneratorType):\n",
267
    "            list(embeddings)\n",
268
    "\n",
269
    "        end_time = time.time()\n",
270
    "        times.append(end_time - start_time)\n",
271
    "\n",
272
    "    # Returning mean, max, and min time for the call\n",
273
    "    return (sum(times) / k, max(times), min(times))\n",
274
    "\n",
275
    "\n",
276
    "hf_stats = calculate_time_stats(hf.embed, documents, k=2)\n",
277
    "print(f\"Huggingface Transformers (Average, Max, Min): {hf_stats}\")\n",
278
    "fst_stats = calculate_time_stats(embedding_model.embed, documents, k=2)\n",
279
    "print(f\"FastEmbed (Average, Max, Min): {fst_stats}\")"
280
   ]
281
  },
282
  {
283
   "cell_type": "markdown",
284
   "metadata": {},
285
   "source": [
286
    "## 📈 Results\n",
287
    "\n",
288
    "Let's run the comparison and see the results."
289
   ]
290
  },
291
  {
292
   "cell_type": "code",
293
   "execution_count": 9,
294
   "metadata": {
295
    "ExecuteTime": {
296
     "end_time": "2024-03-30T00:43:35.746781Z",
297
     "start_time": "2024-03-30T00:43:35.698423Z"
298
    }
299
   },
300
   "outputs": [
301
    {
302
     "data": {
303
      "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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",
304
      "text/plain": [
305
       "<Figure size 640x480 with 1 Axes>"
306
      ]
307
     },
308
     "metadata": {},
309
     "output_type": "display_data"
310
    }
311
   ],
312
   "source": [
313
    "def plot_character_per_second_comparison(\n",
314
    "    hf_stats: Tuple[float, float, float], fst_stats: Tuple[float, float, float], documents: list\n",
315
    "):\n",
316
    "    # Calculating total characters in documents\n",
317
    "    total_characters = sum(len(doc) for doc in documents)\n",
318
    "\n",
319
    "    # Calculating characters per second for each model\n",
320
    "    hf_chars_per_sec = total_characters / hf_stats[0]  # Mean time is at index 0\n",
321
    "    fst_chars_per_sec = total_characters / fst_stats[0]\n",
322
    "\n",
323
    "    # Plotting the bar chart\n",
324
    "    models = [\"HF Embed (Torch)\", \"FastEmbed\"]\n",
325
    "    chars_per_sec = [hf_chars_per_sec, fst_chars_per_sec]\n",
326
    "\n",
327
    "    bars = plt.bar(models, chars_per_sec, color=[\"#1f356c\", \"#dd1f4b\"])\n",
328
    "    plt.ylabel(\"Characters per Second\")\n",
329
    "    plt.title(\"Characters Processed per Second Comparison\")\n",
330
    "\n",
331
    "    # Adding the number at the top of each bar\n",
332
    "    for bar, chars in zip(bars, chars_per_sec):\n",
333
    "        plt.text(\n",
334
    "            bar.get_x() + bar.get_width() / 2,\n",
335
    "            bar.get_height(),\n",
336
    "            f\"{chars:.1f}\",\n",
337
    "            ha=\"center\",\n",
338
    "            va=\"bottom\",\n",
339
    "            color=\"#1f356c\",\n",
340
    "            fontsize=12,\n",
341
    "        )\n",
342
    "\n",
343
    "    plt.show()\n",
344
    "\n",
345
    "\n",
346
    "plot_character_per_second_comparison(hf_stats, fst_stats, documents)"
347
   ]
348
  },
349
  {
350
   "cell_type": "markdown",
351
   "metadata": {},
352
   "source": [
353
    "## Are the Embeddings the same?\n",
354
    "\n",
355
    "This is a very important question. Let's see if the embeddings are the same."
356
   ]
357
  },
358
  {
359
   "cell_type": "code",
360
   "execution_count": 10,
361
   "metadata": {
362
    "ExecuteTime": {
363
     "end_time": "2024-03-30T00:43:25.537072Z",
364
     "start_time": "2024-03-30T00:43:25.419184Z"
365
    }
366
   },
367
   "outputs": [
368
    {
369
     "name": "stderr",
370
     "output_type": "stream",
371
     "text": [
372
      "/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",
373
      "  calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))\n"
374
     ]
375
    },
376
    {
377
     "data": {
378
      "text/plain": [
379
       "0.9999997019767761"
380
      ]
381
     },
382
     "execution_count": 10,
383
     "metadata": {},
384
     "output_type": "execute_result"
385
    }
386
   ],
387
   "source": [
388
    "def calculate_cosine_similarity(embeddings1: Tensor, embeddings2: Tensor) -> float:\n",
389
    "    \"\"\"\n",
390
    "    Calculate cosine similarity between two sets of embeddings\n",
391
    "    \"\"\"\n",
392
    "    return F.cosine_similarity(embeddings1, embeddings2).mean().item()\n",
393
    "\n",
394
    "\n",
395
    "calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))"
396
   ]
397
  },
398
  {
399
   "cell_type": "markdown",
400
   "metadata": {},
401
   "source": [
402
    "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."
403
   ]
404
  }
405
 ],
406
 "metadata": {
407
  "kernelspec": {
408
   "display_name": "fst",
409
   "language": "python",
410
   "name": "python3"
411
  },
412
  "language_info": {
413
   "codemirror_mode": {
414
    "name": "ipython",
415
    "version": 3
416
   },
417
   "file_extension": ".py",
418
   "mimetype": "text/x-python",
419
   "name": "python",
420
   "nbconvert_exporter": "python",
421
   "pygments_lexer": "ipython3",
422
   "version": "3.10.13"
423
  },
424
  "orig_nbformat": 4
425
 },
426
 "nbformat": 4,
427
 "nbformat_minor": 2
428
}
429

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.