ai_ml / rag embeddings / 02_what_are_embeddings.md

What is Embeddings?

4 interview angles 11 min read source

What is Embeddings?

Definition

Embeddings are dense vector representations of data (text, images, etc.) that capture semantic meaning in a continuous vector space. They convert discrete objects (words, sentences, documents) into numerical vectors that can be used for machine learning tasks. Similar objects have similar embeddings (close in vector space).

Key Concepts

Core Principles

  1. Vector Representation: Objects are mapped to fixed-size numerical vectors
  2. Semantic Similarity: Similar meanings → similar vectors (close in space)
  3. Dense Vectors: High-dimensional vectors (typically 100-1536 dimensions)
  4. Learned Representations: Embeddings are learned from data
  5. Transfer Learning: Pre-trained embeddings can be used for various tasks

Why Embeddings Matter

  • Semantic Understanding: Captures meaning, not just syntax
  • Mathematical Operations: Can compute similarity, find nearest neighbors
  • Machine Learning Input: Provides numerical representation for ML models
  • Efficiency: Compact representation of complex information

Types of Embeddings

1. Word Embeddings

Represent individual words as vectors.

Examples:

  • Word2Vec
  • GloVe
  • FastText
from gensim.models import Word2Vec

# Train word embeddings
sentences = [
    ["machine", "learning", "is", "great"],
    ["deep", "learning", "uses", "neural", "networks"],
    ["natural", "language", "processing", "is", "fascinating"]
]

model = Word2Vec(sentences, vector_size=100, window=5, min_count=1)

# Get word embedding
machine_vector = model.wv["machine"]
learning_vector = model.wv["learning"]

# Find similar words
similar = model.wv.most_similar("learning", topn=5)
# [('machine', 0.85), ('neural', 0.82), ...]

2. Sentence Embeddings

Represent entire sentences as vectors.

Examples:

  • Sentence-BERT (SBERT)
  • Universal Sentence Encoder
  • InferSent
from sentence_transformers import SentenceTransformer

# Load pre-trained model
model = SentenceTransformer('all-MiniLM-L6-v2')

# Encode sentences
sentences = [
    "Machine learning is a subset of AI",
    "Deep learning uses neural networks",
    "The weather is nice today"
]

embeddings = model.encode(sentences)

# Calculate similarity
from sklearn.metrics.pairwise import cosine_similarity

similarity = cosine_similarity([embeddings[0]], [embeddings[1]])
# High similarity between first two sentences

3. Document Embeddings

Represent entire documents as vectors.

from langchain.embeddings import OpenAIEmbeddings

embeddings_model = OpenAIEmbeddings()

documents = [
    "This is a long document about machine learning...",
    "Another document about natural language processing...",
    "A document about computer vision..."
]

# Generate document embeddings
doc_embeddings = embeddings_model.embed_documents(documents)

# Each document is now a 1536-dimensional vector
print(len(doc_embeddings[0]))  # 1536

4. Contextual Embeddings

Word embeddings that change based on context.

Examples:

  • BERT
  • ELMo
  • GPT embeddings
from transformers import AutoTokenizer, AutoModel
import torch

# Load BERT model
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModel.from_pretrained('bert-base-uncased')

# Same word, different contexts
sentence1 = "I deposited money in the bank"
sentence2 = "I sat by the river bank"

# Tokenize
inputs1 = tokenizer(sentence1, return_tensors='pt')
inputs2 = tokenizer(sentence2, return_tensors='pt')

# Get embeddings
with torch.no_grad():
    outputs1 = model(**inputs1)
    outputs2 = model(**inputs2)

# "bank" has different embeddings in different contexts
bank_embedding1 = outputs1.last_hidden_state[0][2]  # "bank" in sentence1
bank_embedding2 = outputs2.last_hidden_state[0][6]  # "bank" in sentence2

# These embeddings are different!

How Embeddings Work

Basic Process

  1. Input: Text/data to embed
  2. Tokenization: Break into tokens (words, subwords)
  3. Encoding: Convert tokens to numerical IDs
  4. Embedding Lookup: Map IDs to embedding vectors
  5. Aggregation: Combine token embeddings (for sentences/documents)
  6. Output: Final embedding vector

Mathematical Representation

Text: "machine learning"
    ↓ Tokenization
Tokens: ["machine", "learning"]
    ↓ Encoding
IDs: [1234, 5678]
    ↓ Embedding Lookup
Vectors: [[0.1, 0.2, ...], [0.3, 0.4, ...]]
    ↓ Aggregation (mean, sum, etc.)
Final Embedding: [0.2, 0.3, ...]

Common Embedding Models

1. Word2Vec

from gensim.models import Word2Vec

# Training data
sentences = [
    ["king", "queen", "royalty"],
    ["man", "woman", "person"],
    ["paris", "france", "europe"]
]

# Train model
model = Word2Vec(
    sentences,
    vector_size=100,      # Embedding dimension
    window=5,             # Context window
    min_count=1,          # Minimum word frequency
    sg=0                  # 0=CBOW, 1=Skip-gram
)

# Use embeddings
king_vector = model.wv["king"]
queen_vector = model.wv["queen"]

# Famous example: king - man + woman ≈ queen
result = model.wv.most_similar(
    positive=["king", "woman"],
    negative=["man"],
    topn=1
)
# [('queen', 0.85)]

2. Sentence-BERT (SBERT)

from sentence_transformers import SentenceTransformer

# Load pre-trained model
model = SentenceTransformer('all-MiniLM-L6-v2')

# Encode sentences
sentences = [
    "The cat sits on the mat",
    "A feline is on the rug",
    "I love programming"
]

embeddings = model.encode(sentences)

# Calculate pairwise similarities
from sklearn.metrics.pairwise import cosine_similarity

similarity_matrix = cosine_similarity(embeddings)
# First two sentences have high similarity
print(similarity_matrix[0][1])  # ~0.75

3. OpenAI Embeddings

from openai import OpenAI

client = OpenAI(api_key="your-api-key")

# Generate embeddings
text = "Machine learning is fascinating"
response = client.embeddings.create(
    model="text-embedding-ada-002",
    input=text
)

embedding = response.data[0].embedding
print(len(embedding))  # 1536 dimensions

4. BERT Embeddings

from transformers import AutoTokenizer, AutoModel
import torch

# Load BERT
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModel.from_pretrained('bert-base-uncased')

# Encode text
text = "Hello, how are you?"
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)

# Get embeddings
with torch.no_grad():
    outputs = model(**inputs)
    
# Use [CLS] token embedding for sentence representation
sentence_embedding = outputs.last_hidden_state[0][0]  # [CLS] token

# Or average all token embeddings
token_embeddings = outputs.last_hidden_state[0]
sentence_embedding = torch.mean(token_embeddings, dim=0)

Use Cases

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer('all-MiniLM-L6-v2')

# Knowledge base
documents = [
    "Python is a programming language",
    "Machine learning uses algorithms",
    "The weather is sunny today"
]

# Create embeddings for all documents
doc_embeddings = model.encode(documents)

# User query
query = "What is Python?"
query_embedding = model.encode([query])[0]

# Find most similar document
similarities = np.dot(doc_embeddings, query_embedding)
most_similar_idx = np.argmax(similarities)

print(f"Most relevant: {documents[most_similar_idx]}")

2. Text Classification

from sklearn.linear_model import LogisticRegression
from sentence_transformers import SentenceTransformer

# Prepare data
train_texts = ["I love this", "This is terrible", ...]
train_labels = [1, 0, ...]  # positive/negative

# Generate embeddings
model = SentenceTransformer('all-MiniLM-L6-v2')
train_embeddings = model.encode(train_texts)

# Train classifier
classifier = LogisticRegression()
classifier.fit(train_embeddings, train_labels)

# Predict
test_text = "This is amazing"
test_embedding = model.encode([test_text])
prediction = classifier.predict(test_embedding)

3. Clustering

from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer

# Documents to cluster
documents = [
    "Machine learning algorithms",
    "Deep learning neural networks",
    "The weather forecast",
    "Today's temperature"
]

# Generate embeddings
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(documents)

# Cluster
kmeans = KMeans(n_clusters=2)
clusters = kmeans.fit_predict(embeddings)

# Results
for doc, cluster in zip(documents, clusters):
    print(f"Cluster {cluster}: {doc}")

4. Recommendation Systems

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer('all-MiniLM-L6-v2')

# User's liked items
user_likes = [
    "Action movies with explosions",
    "Sci-fi space adventures"
]

# Available items
items = [
    "Action thriller with car chases",
    "Romantic comedy",
    "Space opera movie",
    "Documentary about nature"
]

# Embed user preferences
user_embedding = np.mean(model.encode(user_likes), axis=0)

# Embed items
item_embeddings = model.encode(items)

# Find similar items
similarities = np.dot(item_embeddings, user_embedding)
recommended_idx = np.argmax(similarities)

print(f"Recommended: {items[recommended_idx]}")

5. Anomaly Detection

from sentence_transformers import SentenceTransformer
from sklearn.ensemble import IsolationForest
import numpy as np

model = SentenceTransformer('all-MiniLM-L6-v2')

# Normal documents
normal_docs = [
    "Customer purchased product",
    "User logged into account",
    "Payment processed successfully"
]

# Generate embeddings
embeddings = model.encode(normal_docs)

# Train anomaly detector
detector = IsolationForest(contamination=0.1)
detector.fit(embeddings)

# Check new document
new_doc = "System error: unauthorized access"
new_embedding = model.encode([new_doc])
is_anomaly = detector.predict(new_embedding)

if is_anomaly[0] == -1:
    print("Anomaly detected!")

Embedding Dimensions

Common Dimensions

  • Word2Vec: 100-300 dimensions
  • GloVe: 50-300 dimensions
  • BERT: 768 dimensions (base), 1024 (large)
  • OpenAI Ada: 1536 dimensions
  • Sentence-BERT: 384-768 dimensions

Choosing Dimensions

  • Lower dimensions (50-100): Faster, less memory, may lose nuance
  • Medium dimensions (300-512): Good balance
  • Higher dimensions (768-1536): More expressive, slower, more memory

Similarity Metrics

1. Cosine Similarity

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def cosine_similarity_custom(vec1, vec2):
    dot_product = np.dot(vec1, vec2)
    norm1 = np.linalg.norm(vec1)
    norm2 = np.linalg.norm(vec2)
    return dot_product / (norm1 * norm2)

# Example
vec1 = np.array([1, 2, 3])
vec2 = np.array([2, 4, 6])
similarity = cosine_similarity_custom(vec1, vec2)
print(similarity)  # 1.0 (perfectly similar direction)

2. Euclidean Distance

import numpy as np

def euclidean_distance(vec1, vec2):
    return np.linalg.norm(vec1 - vec2)

# Example
vec1 = np.array([1, 2, 3])
vec2 = np.array([2, 4, 6])
distance = euclidean_distance(vec1, vec2)
print(distance)  # 3.74 (lower = more similar)

3. Dot Product

import numpy as np

def dot_product_similarity(vec1, vec2):
    return np.dot(vec1, vec2)

# Example
vec1 = np.array([1, 2, 3])
vec2 = np.array([2, 4, 6])
similarity = dot_product_similarity(vec1, vec2)
print(similarity)  # 28 (higher = more similar, but not normalized)

Common Interview Questions and Answers

Q1: What are embeddings and why are they useful?

Embeddings are dense vector representations that capture semantic meaning. They’re useful because:

  1. Semantic Understanding: Capture meaning, not just syntax
  2. Mathematical Operations: Can compute similarity, perform arithmetic
  3. ML Input: Provide numerical representation for machine learning
  4. Efficiency: Compact representation of complex information
  5. Transfer Learning: Pre-trained embeddings work across tasks

Q2: What’s the difference between word embeddings and sentence embeddings?

Aspect Word Embeddings Sentence Embeddings
Input Single words Entire sentences
Examples Word2Vec, GloVe Sentence-BERT, Universal Sentence Encoder
Use Case Word-level tasks Sentence-level tasks
Context May not capture context Captures sentence context

Word Embeddings: “bank” has same embedding regardless of context Sentence Embeddings: “bank” embedding changes based on sentence context

Q3: How do you create embeddings for a custom domain?

  1. Fine-tune Pre-trained Model:

    from sentence_transformers import SentenceTransformer, InputExample, losses
    from torch.utils.data import DataLoader
    
    # Load base model
    model = SentenceTransformer('all-MiniLM-L6-v2')
    
    # Prepare domain-specific data
    train_examples = [
        InputExample(texts=["medical term 1", "medical term 2"], label=0.9),
        InputExample(texts=["medical term 3", "unrelated term"], label=0.1)
    ]
    
    # Fine-tune
    train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
    train_loss = losses.CosineSimilarityLoss(model)
    
    model.fit(
        train_objectives=[(train_dataloader, train_loss)],
        epochs=10
    )
  2. Train from Scratch (if you have large domain corpus):

    from gensim.models import Word2Vec
    
    # Domain-specific corpus
    domain_sentences = load_domain_corpus()
    
    # Train
    model = Word2Vec(domain_sentences, vector_size=300, window=5)

Q4: What is the curse of dimensionality in embeddings?

As dimensions increase:

  • Sparsity: Most vectors become far apart
  • Distance metrics become less meaningful: All distances become similar
  • Computational cost: Storage and computation increase
  • Overfitting risk: Model may memorize rather than generalize

Solutions:

  • Use dimensionality reduction (PCA, t-SNE)
  • Choose appropriate embedding dimensions
  • Use regularization

Q5: How do you handle out-of-vocabulary (OOV) words?

  1. Subword Tokenization (BPE, WordPiece):

    # BERT uses WordPiece
    tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
    tokens = tokenizer.tokenize("unhappiness")
    # ['un', '##happy', '##ness'] - can handle unknown words
  2. Character-level Embeddings:

    # FastText uses character n-grams
    from gensim.models import FastText
    model = FastText(sentences, vector_size=100)
    # Can generate embedding for any word
  3. Fallback Strategies:

    • Use average of known words
    • Use special UNK token embedding
    • Use character-level or subword embeddings

Q6: What’s the difference between static and contextual embeddings?

Static Embeddings (Word2Vec, GloVe):

  • Same word always has same embedding
  • “bank” (financial) and “bank” (river) have same vector
  • Fast, efficient
  • Don’t capture context

Contextual Embeddings (BERT, ELMo):

  • Same word has different embeddings based on context
  • “bank” in different sentences has different vectors
  • More expressive, captures context
  • Slower, requires more computation

Q7: How do you choose the right embedding model?

Consider:

  1. Task Type:

    • Word-level: Word2Vec, GloVe
    • Sentence-level: Sentence-BERT, Universal Sentence Encoder
    • Contextual: BERT, RoBERTa
  2. Language:

    • English: Many options
    • Other languages: Check multilingual models (mBERT, XLM-R)
  3. Domain:

    • General: Use pre-trained models
    • Specialized: Fine-tune or train domain-specific
  4. Performance Requirements:

    • Fast inference: Smaller models (DistilBERT, MiniLM)
    • Best quality: Larger models (BERT-large, GPT)
  5. Resources:

    • Limited: Use smaller models or APIs (OpenAI)
    • Available: Can train/fine-tune larger models

Q8: How do embeddings capture semantic relationships?

Through training on large text corpora, embeddings learn:

  1. Distributional Hypothesis: Words in similar contexts have similar meanings

  2. Vector Arithmetic: Relationships can be captured mathematically

    # Famous example
    king - man + woman ≈ queen
  3. Clustering: Similar concepts cluster together in vector space

  4. Distance: Semantic similarity ≈ vector distance

Q9: What is embedding normalization and when to use it?

Normalization scales embeddings to unit length.

import numpy as np

def normalize_embedding(embedding):
    norm = np.linalg.norm(embedding)
    return embedding / norm if norm > 0 else embedding

# Benefits:
# 1. Cosine similarity = dot product (faster)
# 2. All embeddings on same scale
# 3. Better for some algorithms (k-means, etc.)

When to use:

  • When using cosine similarity (makes it = dot product)
  • For clustering algorithms
  • When embeddings have different scales
  • For better numerical stability

Q10: How do you evaluate embedding quality?

  1. Intrinsic Evaluation:

    • Word similarity tasks (SimLex, WordSim)
    • Analogy tasks (king - man + woman = queen)
    • Clustering quality
  2. Extrinsic Evaluation:

    • Downstream task performance (classification, NER)
    • Retrieval quality (precision, recall)
    • Task-specific metrics
  3. Qualitative Analysis:

    • Nearest neighbors inspection
    • Visualization (t-SNE, UMAP)
    • Domain expert review
def evaluate_embeddings(model, test_pairs):
    """Evaluate on word similarity task"""
    similarities = []
    for word1, word2, human_score in test_pairs:
        emb1 = model.encode([word1])[0]
        emb2 = model.encode([word2])[0]
        pred_similarity = cosine_similarity([emb1], [emb2])[0][0]
        similarities.append((human_score, pred_similarity))
    
    # Calculate correlation
    correlation = np.corrcoef(
        [s[0] for s in similarities],
        [s[1] for s in similarities]
    )[0][1]
    
    return correlation

Best Practices

  1. Choose Right Model: Match model to task (word/sentence/document)
  2. Normalize When Needed: For cosine similarity, normalize embeddings
  3. Handle OOV: Use subword tokenization or character-level
  4. Fine-tune for Domain: Adapt pre-trained models to your domain
  5. Cache Embeddings: Store computed embeddings for reuse
  6. Batch Processing: Process multiple texts together for efficiency
  7. Monitor Quality: Regularly evaluate embedding quality
  8. Version Control: Track which embedding model/version you use
  9. Dimensionality: Choose appropriate dimensions (not always more = better)
  10. Update Regularly: Keep embeddings current with new data

Summary

Embeddings are:

  • Dense vector representations of text/data
  • Capture semantic meaning in numerical form
  • Enable similarity computation and ML tasks
  • Available in various types: word, sentence, document, contextual
  • Powerful tool for NLP, search, recommendation, and more

Key takeaways:

  • Embeddings convert discrete objects to continuous vectors
  • Similar objects have similar embeddings (close in vector space)
  • Pre-trained embeddings are powerful and widely available
  • Choose embedding type based on your task
  • Evaluate embeddings on your specific use case

Interview angle

  • “What is an embedding?” - a dense vector positioning text in a space where geometric closeness approximates semantic similarity. It’s what makes “find similar meaning” a nearest-neighbour lookup.
  • “Why normalise before comparing?” - raw dot product mixes direction (semantics) with magnitude, which often reflects length rather than meaning. Unit-normalising makes dot product exactly cosine similarity. See ../00_math_foundations/01_linear_algebra.md.
  • “How do you choose an embedding model?” - by your own retrieval eval, not a leaderboard. Then dimension (storage and latency cost), max input length against your chunk size, multilingual need, and whether it can run in your data boundary.
  • “What breaks when you change embedding model?” - everything already indexed. Vectors from different models are not comparable, so a model change means a full re-embed and re-index. Version the index and plan the migration.