Step 1: Install Graphviz b... 22 \n",
+ "2 Another way to indicate the location of inc... 19 \n",
+ "3
No, there is no \"intrinsic\" way of knowing ... 52 \n",
+ "4
You can declare a dictionary inside a dicti... 106 "
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Convert to a dataframe\n",
+ "df = rows.to_dataframe()\n",
+ "\n",
+ "# Examine the data\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "id": "2cacd9869ee5"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "1000"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Extract the question ids and question text\n",
+ "ids = df.id.tolist()\n",
+ "questions = df.title.tolist()\n",
+ "\n",
+ "# Verify the length\n",
+ "len(ids)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "1124422cc200"
+ },
+ "source": [
+ "#### Instantiate the text encoding model\n",
+ "\n",
+ "Use the [sentence-t5 encoder](https://tfhub.dev/google/sentence-t5/st5-base/1) developed by Google for converting text to embeddings.\n",
+ "\n",
+ "> The sentence-T5 family of models encode text into high-dimensional vectors that can be used for text classification, semantic similarity, clustering and other natural language processing tasks.\n",
+ ">\n",
+ "> Our model is built on top of T5 (i.e. the Text-To-Text Transfer Transformer). It is trained on a variety of data sources and initialized from pre-trained T5 models with different model sizes as described in [1]. The input is variable-length English text and the output is a 768-dimensional vector. The sentence-T5 base model employs a 12-layer transformer architecture as the T5 base model does."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ed41c7712930"
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_hub as hub\n",
+ "# Registers the ops.\n",
+ "import tensorflow_text as text # noqa: F401\n",
+ "\n",
+ "hub_url = \"https://tfhub.dev/google/sentence-t5/st5-base/1\"\n",
+ "\n",
+ "encoder = hub.KerasLayer(hub_url)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "43088937e820"
+ },
+ "source": [
+ "#### Defining an encoding function\n",
+ "\n",
+ "Define a function to be used later that will take sentences and convert them to embeddings."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "id": "a0370bd840d2"
+ },
+ "outputs": [],
+ "source": [
+ "from typing import List\n",
+ "\n",
+ "import numpy as np\n",
+ "from tqdm.auto import tqdm\n",
+ "\n",
+ "NUM_SENTENCES_IN_CHUNK = 100\n",
+ "\n",
+ "\n",
+ "def encode_text_to_embedding(\n",
+ " text_encoder: hub.KerasLayer, sentences: List[str]\n",
+ ") -> np.ndarray:\n",
+ " embeddings_list = []\n",
+ "\n",
+ " # The encoding models in TF hub have trouble processing too many strings at once, so we process them in chunks.\n",
+ " sentence_chunks = np.array_split(\n",
+ " sentences, (len(sentences) // NUM_SENTENCES_IN_CHUNK) + 1\n",
+ " )\n",
+ " for chunk in tqdm(sentence_chunks):\n",
+ " embeddings_list.append(text_encoder(tf.constant(chunk)))\n",
+ "\n",
+ " return np.squeeze(np.column_stack(embeddings_list))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ba45d58bf96e"
+ },
+ "source": [
+ "#### Test the encoding function\n",
+ "\n",
+ "Encode a subset of data and see if the embeddings and distance metrics make sense.\n",
+ "\n",
+ "According to (sentence-T5 research paper)[https://arxiv.org/pdf/2108.08877.pdf], the similarity of embeddings is calculated using the dot-product. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "id": "9b01baa906b5"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "4cb8ca51b1864c189fd66209074debe5",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/6 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Encode 500 questions\n",
+ "questions = df.title.tolist()[:500]\n",
+ "question_embeddings = encode_text_to_embedding(\n",
+ " text_encoder=encoder, sentences=questions\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "d3761f56648b"
+ },
+ "source": [
+ "Save the dimension size for later usage when creating the index."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "id": "d296e181205d"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "768"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "DIMENSIONS = len(question_embeddings[0])\n",
+ "\n",
+ "DIMENSIONS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "id": "95e408daf219"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Query question = Numbers of Day in Month\n",
+ "\t0: Numbers of Day in Month: 0.9999998807907104\n",
+ "\t1: Python: Number of the Week in a Month: 0.8649452924728394\n",
+ "\t2: Simulate Poisson arrival times given count of arrivals per day: 0.8294509053230286\n",
+ "\t3: How to workout if a datetime is older than x months in Python: 0.808755099773407\n",
+ "\t4: How to efficiently add seconds place to date time: 0.7867542505264282\n",
+ "\t5: Show if restaurant is open or closed based on weekday, opening and closing time: 0.7858442068099976\n",
+ "\t6: How to subtract datetimes based on transition events in another column: 0.7808101177215576\n",
+ "\t7: Doing DateTime Comparisons in Filter SQLAlchemy: 0.7684456706047058\n",
+ "\t8: Python datetime module and getting current time: 0.7682346701622009\n",
+ "\t9: Multiples of 10 in a list: 0.7647985816001892\n",
+ "\t10: Counting differences between two strings: 0.7633612155914307\n",
+ "\t11: Function That Computes Sum of Squares of Numbers in List: 0.7624084949493408\n",
+ "\t12: Python/Matplotlib - Colorbar Range and Display Values: 0.7616406679153442\n",
+ "\t13: Most computationally efficient way to count consecutive repeating values: 0.7592378854751587\n",
+ "\t14: Plotting data with a string as the x-axis: 0.758903980255127\n",
+ "\t15: How to write an efficient hit counter for websites: 0.758114755153656\n",
+ "\t16: Number of channels in convLSTM for classifying videos: 0.7568756937980652\n",
+ "\t17: Sort a set with four pieces of data per element: 0.7568261623382568\n",
+ "\t18: Interactive Data Visualiation - Python: 0.7562029957771301\n",
+ "\t19: How to make Images/PDF of Timetable using Python: 0.7535030841827393\n"
+ ]
+ }
+ ],
+ "source": [
+ "question_index = 0\n",
+ "\n",
+ "print(f\"Query question = {questions[question_index]}\")\n",
+ "scores = np.dot(question_embeddings[question_index], question_embeddings.T)\n",
+ "\n",
+ "# Print top 20 matches\n",
+ "for index, (question, score) in enumerate(\n",
+ " sorted(zip(questions, scores), key=lambda x: x[1], reverse=True)[:20]\n",
+ "):\n",
+ " print(f\"\\t{index}: {question}: {score}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "aQIQSyF9GtSv"
+ },
+ "source": [
+ "#### Save the train split in JSONL format.\n",
+ "\n",
+ "The data must be formatted in JSONL format, which means each embedding dictionary is written as a JSON string on its own line.\n",
+ "\n",
+ "Additionally, to demonstrate the filtering functionality, the `restricts` key is set such that each embedding has a different `class`, `even` or `odd`. These are used during the later matching step to filter for results.\n",
+ "See additional information of filtering here: https://cloud.google.com/vertex-ai/docs/matching-engine/filtering"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "307f468a3ecd"
+ },
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "import tempfile\n",
+ "\n",
+ "# Encode text in chunks and save to disk to prevent out-of-memory errors.\n",
+ "NUM_ELEMENTS_IN_CHUNK = 1000\n",
+ "embeddings_file = tempfile.NamedTemporaryFile(suffix=\".json\", delete=False)\n",
+ "id_question_chunks = (\n",
+ " list(zip(ids, questions))[i : i + NUM_ELEMENTS_IN_CHUNK]\n",
+ " for i in range(0, len(questions), NUM_ELEMENTS_IN_CHUNK)\n",
+ ")\n",
+ "\n",
+ "with open(embeddings_file.name, \"a\") as f:\n",
+ " for chunk in tqdm(id_question_chunks):\n",
+ " id_chunk, question_chunk = zip(*chunk)\n",
+ "\n",
+ " question_chunk_embeddings = encode_text_to_embedding(\n",
+ " text_encoder=encoder, sentences=question_chunk\n",
+ " )\n",
+ "\n",
+ " # Append to file\n",
+ " embeddings_formatted = [\n",
+ " json.dumps(\n",
+ " {\n",
+ " \"id\": str(id),\n",
+ " \"embedding\": [str(value) for value in embedding],\n",
+ " }\n",
+ " )\n",
+ " + \"\\n\"\n",
+ " for id, embedding in zip(id_chunk, question_chunk_embeddings)\n",
+ " ]\n",
+ " f.writelines(embeddings_formatted)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QuVl8DrWG8NS"
+ },
+ "source": [
+ "Upload the training data to GCS."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "3PgsA_vbI8Vg"
+ },
+ "outputs": [],
+ "source": [
+ "UNIQUE_FOLDER_NAME = \"embeddings_folder_unique\"\n",
+ "EMBEDDINGS_INITIAL_URI = f\"{BUCKET_URI}/{UNIQUE_FOLDER_NAME}/\"\n",
+ "! gsutil cp {embeddings_file.name} {EMBEDDINGS_INITIAL_URI}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "mglUPwHpJH98"
+ },
+ "source": [
+ "## Create Indexes\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "qhIBCQ7dDSbW"
+ },
+ "source": [
+ "### Create ANN Index (for Production Usage)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "id": "qiIg9b5zJLi1"
+ },
+ "outputs": [],
+ "source": [
+ "DISPLAY_NAME = \"stack_overflow\"\n",
+ "DESCRIPTION = \"questions from stackoverflow\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "svLYiDf0OD2G"
+ },
+ "source": [
+ "Create the ANN index configuration:\n",
+ "\n",
+ "To learn more about configuring the index, see [Input data format and structure](https://cloud.google.com/vertex-ai/docs/matching-engine/match-eng-setup#input-data-format).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "id": "Y4zooldkGoM4"
+ },
+ "outputs": [],
+ "source": [
+ "from google.cloud import aiplatform\n",
+ "\n",
+ "aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "17jrQi501QyX"
+ },
+ "outputs": [],
+ "source": [
+ "INDEX_RESOURCE_NAME = tree_ah_index.resource_name\n",
+ "INDEX_RESOURCE_NAME"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "0f1a9fbecabb"
+ },
+ "source": [
+ "Using the resource name, you can retrieve an existing MatchingEngineIndex."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1ddb70647d98"
+ },
+ "outputs": [],
+ "source": [
+ "tree_ah_index = aiplatform.MatchingEngineIndex(index_name=INDEX_RESOURCE_NAME)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "qV2xjAnDDObD"
+ },
+ "source": [
+ "## Create an IndexEndpoint with VPC Network"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "BpZQoJyxDlbO"
+ },
+ "outputs": [],
+ "source": [
+ "# Retrieve the project number\n",
+ "PROJECT_NUMBER = !gcloud projects list --filter=\"PROJECT_ID:'{PROJECT_ID}'\" --format='value(PROJECT_NUMBER)'\n",
+ "PROJECT_NUMBER = PROJECT_NUMBER[0]\n",
+ "\n",
+ "VPC_NETWORK = \"[your-network-name]\"\n",
+ "VPC_NETWORK_FULL = \"projects/{}/global/networks/{}\".format(PROJECT_NUMBER, VPC_NETWORK)\n",
+ "VPC_NETWORK_FULL"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "QuARXzJVGyQX"
+ },
+ "outputs": [],
+ "source": [
+ "my_index_endpoint = aiplatform.MatchingEngineIndexEndpoint.create(\n",
+ " display_name=DISPLAY_NAME,\n",
+ " description=DISPLAY_NAME,\n",
+ " network=VPC_NETWORK_FULL,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "np2cgVuuIe9k"
+ },
+ "source": [
+ "## Deploy Indexes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "8Ew1UgcIIiJG"
+ },
+ "source": [
+ "### Deploy ANN Index"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "nLOYTGygIlMK"
+ },
+ "outputs": [],
+ "source": [
+ "DEPLOYED_INDEX_ID = \"deployed_index_id_unique\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_uK4WOgqN1NG"
+ },
+ "outputs": [],
+ "source": [
+ "my_index_endpoint = my_index_endpoint.deploy_index(\n",
+ " index=tree_ah_index, deployed_index_id=DEPLOYED_INDEX_ID\n",
+ ")\n",
+ "\n",
+ "my_index_endpoint.deployed_indexes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "6LCGvBNvBd8D"
+ },
+ "source": [
+ "## Create Online Queries\n",
+ "\n",
+ "After you built your indexes, you may query against the deployed index to find nearest neighbours."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ae9996f185fe"
+ },
+ "outputs": [],
+ "source": [
+ "test_embeddings = encode_text_to_embedding(\n",
+ " text_encoder=encoder, sentences=[\"How do I install tensorflow with GPU support?\"]\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "A3KYVw5HB-4v"
+ },
+ "outputs": [],
+ "source": [
+ "# Test query\n",
+ "response = my_index_endpoint.match(\n",
+ " deployed_index_id=DEPLOYED_INDEX_ID,\n",
+ " queries=[test_embeddings.tolist()],\n",
+ " num_neighbors=NUM_NEIGHBOURS,\n",
+ ")\n",
+ "\n",
+ "response"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "TpV-iwP9qw9c"
+ },
+ "source": [
+ "## Cleaning up\n",
+ "\n",
+ "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
+ "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
+ "You can also manually delete resources that you created by running the following code."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "sx_vKniMq9ZX"
+ },
+ "outputs": [],
+ "source": [
+ "# Force undeployment of indexes and delete endpoint\n",
+ "my_index_endpoint.delete(force=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "omj7N9iWv-Tq"
+ },
+ "outputs": [],
+ "source": [
+ "# Delete indexes\n",
+ "tree_ah_index.delete()"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "sdk_matching_engine_create_stack_overflow_embeddings.ipynb",
+ "toc_visible": true
+ },
+ "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.9.16"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
}