{ "cells": [ { "cell_type": "markdown", "id": "bc0e59cc", "metadata": {}, "source": [ "# Lesson 45: Document search with embeddings activity (solution)\n", "\n", "In this activity, you will build a simple document search system using word embeddings.\n", "\n", "1. **Document embeddings** - Create document vectors by averaging word embeddings\n", "2. **Similarity search** - Find documents similar to a query\n", "3. **Comparison** - Compare embedding-based search with TF-IDF search\n", "\n", "## Notebook set-up\n", "\n", "### Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "52374b61", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import gensim.downloader as api\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.metrics.pairwise import cosine_similarity\n", "\n", "# Load pre-trained GloVe embeddings (this may take a moment)\n", "print('Loading GloVe embeddings...')\n", "glove = api.load('glove-wiki-gigaword-100')\n", "print(f'Loaded {len(glove)} word vectors of dimension {glove.vector_size}')" ] }, { "cell_type": "markdown", "id": "6980e0b1", "metadata": {}, "source": [ "## 1. Sample document corpus" ] }, { "cell_type": "code", "execution_count": null, "id": "2d92d403", "metadata": {}, "outputs": [], "source": [ "# Sample documents about different topics\n", "documents = [\n", " 'The quick brown fox jumps over the lazy dog',\n", " 'Machine learning algorithms can classify images and text',\n", " 'Neural networks are inspired by the human brain',\n", " 'Python is a popular programming language for data science',\n", " 'Deep learning has revolutionized computer vision',\n", " 'Natural language processing helps computers understand text',\n", " 'The cat sat on the mat near the window',\n", " 'Artificial intelligence is transforming many industries',\n", " 'Dogs and cats are popular household pets',\n", " 'Data scientists use statistics and machine learning',\n", "]\n", "\n", "print(f'Corpus contains {len(documents)} documents')" ] }, { "cell_type": "markdown", "id": "be0695c7", "metadata": {}, "source": [ "## 2. Create document embeddings\n", "\n", "### Task 1: Implement document embedding function\n", "\n", "Complete the function below to create a document embedding by averaging the word embeddings of all words in the document.\n", "\n", "**Hints:**\n", "- Tokenize the document using `.lower().split()`\n", "- Check if each word is in the embedding vocabulary using `word in glove`\n", "- Get the embedding with `glove[word]`\n", "- Average all valid word vectors using `np.mean(vectors, axis=0)`\n", "- Return a zero vector if no words are found" ] }, { "cell_type": "code", "execution_count": null, "id": "647e450c", "metadata": {}, "outputs": [], "source": [ "def get_document_embedding(document: str, embeddings) -> np.ndarray:\n", " '''Create document embedding by averaging word embeddings.'''\n", " \n", " # Step 1: Tokenize (lowercase and split)\n", " words = document.lower().split()\n", " \n", " # Step 2: Get embeddings for words that exist in vocabulary\n", " vectors = []\n", "\n", " for word in words:\n", "\n", " if word in embeddings:\n", " vectors.append(embeddings[word])\n", " \n", " # Step 3: Return average or zero vector\n", " if len(vectors) > 0:\n", " return np.mean(vectors, axis=0)\n", "\n", " else:\n", " return np.zeros(embeddings.vector_size)" ] }, { "cell_type": "code", "execution_count": null, "id": "33d32e4c", "metadata": {}, "outputs": [], "source": [ "# Create embeddings for all documents\n", "doc_embeddings = np.array([get_document_embedding(doc, glove) for doc in documents])\n", "print(f'Document embeddings shape: {doc_embeddings.shape}')" ] }, { "cell_type": "markdown", "id": "d24d33f7", "metadata": {}, "source": [ "## 3. Similarity search\n", "\n", "### Task 2: Implement document search function\n", "\n", "Complete the search function to find the most similar documents to a query.\n", "\n", "**Hints:**\n", "- Get the query embedding using `get_document_embedding`\n", "- Compute cosine similarity with `cosine_similarity`\n", "- Sort by similarity score and return top N results" ] }, { "cell_type": "code", "execution_count": null, "id": "b910b664", "metadata": {}, "outputs": [], "source": [ "def search_documents(query: str, documents: list, doc_embeddings: np.ndarray, top_n: int = 3):\n", " '''Search for documents similar to query using embeddings.'''\n", " \n", " # Step 1: Get query embedding\n", " query_embedding = get_document_embedding(query, glove)\n", " \n", " # Step 2: Compute cosine similarity with all documents\n", " # cosine_similarity expects 2D arrays, so reshape query_embedding\n", " similarities = cosine_similarity(\n", " query_embedding.reshape(1, -1),\n", " doc_embeddings\n", " )\n", " \n", " # Step 3: Get indices sorted by similarity (descending)\n", " sorted_indices = np.argsort(similarities[0])[::-1]\n", " \n", " # Step 4: Return top N results as list of (document, score) tuples\n", " results = []\n", "\n", " for idx in sorted_indices[:top_n]:\n", " results.append((documents[idx], similarities[0][idx]))\n", " \n", " return results" ] }, { "cell_type": "code", "execution_count": null, "id": "761ad4f5", "metadata": {}, "outputs": [], "source": [ "# Test the search function\n", "queries = [\n", " 'artificial intelligence and deep neural networks',\n", " 'pets and animals',\n", " 'programming code'\n", "]\n", "\n", "print('Embedding-based search results:\\n')\n", "\n", "for query in queries:\n", "\n", " print(f'Query: \"{query}\"')\n", " results = search_documents(query, documents, doc_embeddings)\n", "\n", " for doc, score in results:\n", " print(f' [{score:.3f}] {doc}')\n", "\n", " print()" ] }, { "cell_type": "markdown", "id": "a4c84238", "metadata": {}, "source": [ "## 4. Comparison with TF-IDF search\n", "\n", "### Task 3: Compare embedding search with TF-IDF search\n", "\n", "Run the TF-IDF search below and compare results with embedding-based search." ] }, { "cell_type": "code", "execution_count": null, "id": "26f21954", "metadata": {}, "outputs": [], "source": [ "# TF-IDF based search for comparison\n", "tfidf = TfidfVectorizer()\n", "tfidf_matrix = tfidf.fit_transform(documents)\n", "\n", "def search_tfidf(query: str, top_n: int = 3):\n", " '''Search using TF-IDF similarity.'''\n", "\n", " query_vec = tfidf.transform([query])\n", " similarities = cosine_similarity(query_vec, tfidf_matrix)[0]\n", " sorted_indices = np.argsort(similarities)[::-1]\n", " \n", " results = []\n", "\n", " for idx in sorted_indices[:top_n]:\n", " results.append((documents[idx], similarities[idx]))\n", "\n", " return results\n", "\n", "print('TF-IDF search results:\\n')\n", "\n", "for query in queries:\n", "\n", " print(f'Query: \"{query}\"')\n", " results = search_tfidf(query)\n", "\n", " for doc, score in results:\n", " print(f' [{score:.3f}] {doc}')\n", "\n", " print()" ] }, { "cell_type": "markdown", "id": "f1bc5c0a", "metadata": {}, "source": [ "## 5. Analysis questions\n", "\n", "After completing the tasks above, answer these questions:\n", "\n", "1. Which search method (embeddings vs TF-IDF) handles synonyms and related concepts better? Give an example.\n", "2. What queries where embeddings outperform TF-IDF, and vice versa?\n", "3. What are the trade-offs between these two approaches in terms of speed, memory, and accuracy?\n", "4. How might you combine both approaches for better search results?" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }