Visual Search

bytebytego tutorial

dataset

  • A query image
  • (n-1) negative images
  • A positive image, i.e., the image most similar to the query image.
  • The index label of the positive image among all the images.

build dataset

  • human judgement: humans annotate the most similar image
  • user interaction
    • noisy
    • sparse: may not have click data available for lots of the images
  • generate similar images

Offline metrics

Mean reciprocal rank (MRR)

MRR=1/m * sum(1/rank_i)

downside

  • considers only the first relevant item and ignores other relevant items in the list, so it does not measure the precision and ranking quality of the full ranked list

Recall@k

recall@k= number of relevant items among the top k items in the output list / total relevant items

downside

  • in search engines, the total number of relevant items can be very high, so the denominator ends up being too large.

Precision@k

Precision@k = number of relevant items among the top k items in the output list / k

downside

  • This metric measures how precise the output lists are, but it doesn’t consider ranking quality

mAP

AP = sum of Precision@i for every relevant item at rank i / total relevant items alt text mAP = mean AP across all ranked lists

mAP is designed for binary relevance; in other words, it works well when each item is either relevant or irrelevant. For continuous relevance scores, nDCG is a better choice.

nDCG

alt text rank = the position produced by the model’s ranking rel = the true relevance of the item at that position

Visual Search at Pinterest

paper

duplicate image

pinterest blog

  1. A new image is uploaded, and we want to know whether it is a duplicate of an existing image
  2. Once the embedding has been generated, convert the embedding into LSH terms
    • embedding = [0.12, -0.44, 0.07, …, 0.31]
    • LSH terms = {term_17, term_203, term_881, term_915, …}
  3. Generate a set of duplicate candidates: use the new image’s LSH terms to fetch a batch of corresponding images, compare which images share the most LSH terms with the new image, and sort them from most to least
  4. Use a TensorFlow classifier to make the fine-grained decision; images whose score exceeds a certain threshold are considered duplicates and are placed into the existing cluster

cluster

  • Each cluster represents a group of duplicate images
  • every cluster has a cluster head, i.e., the representative image of the cluster
  • Two relationships are maintained: Image → canonical image, and canonical image → cluster members

manas(search basic)

blog

Indexing

  • doc: Each manas pin is treated as a doc, which contains matching terms and properties
    • matching terms: used for retrieval. E.g., red; dress
    • properties: used for filtering and scoring. E.g., language = en; quality_score = 0.9
  • posting: Each posting records the internal doc ID and a payload.
  • inverted index: term → posting list
    • There are two encoding/storage methods
    • dense posting list: suitable for very common terms, e.g., red
    • split posting list
  • Forward index: internal_doc_id → actual document

alt text

  • Different applications have different pins, i.e., manas docs. For example, regular visual search corresponds to searchable pins, and ads correspond to searchable Ads. They are unified into manas docs
  • Each application has its own partitioner, which splits manas docs into different partitions
  • The index builder turns the docs in each partition into an index segment

Serving

alt text

  • A corpus can be understood as the set of images searchable by an application. For example, Corpus A = organic Pins, Corpus B = shopping products, Corpus C = ads
  • Query Understanding turns the user query into a detailed, executable search plan
  • Blender is responsible for deciding which corpora to search, and hands off to the root of those corpora
  • A leaf is a serving machine/node; a leaf can consist of one or more index segments
  • The root collects the results returned by each leaf, merges them, and reranks
Leaf

alt text

A leaf corresponds to one node. After the Operator layer finishes finding candidates, it hands them off to the Model Runner to perform ML scoring, and then exposes an API to the Root

blog

HNSW: Real-time with Embedding

Pinterest blog: manas-hnsw

alt text

  • Every time an embedding is produced, that event goes into Kafka
  • The Leaf shard consumes these write events from Kafka and writes them into its own realtime segment
  • The Realtime Segment is a small in-memory index.
    • Realtime Embedding Index: stores doc_id → embedding
    • Realtime HNSW Graph: inserts the new embedding into the HNSW graph, supporting ANN search
  • After some time, the Realtime Segment becomes a sealed realtime segment and no longer accepts writes
  • A Flush operation writes the sealed segment in memory to SSD, turning it into a Static Segment. It is no longer modified frequently, making it suitable for serving.
Operation
  • deletion: To delete a doc, add an in-memory deletion marker to it. During serving, if this doc is found, it gets filtered out. This is because directly deleting a node in the HNSW graph is fairly troublesome and can break the graph structure.
  • update: delete old+add new
  • Compaction: If there are too many static segments, multiple small static segments are merged into a larger static segment. This is also when the actual deletion is completed

ANN

Given a query embedding, quickly find the most similar top-K among a massive number of candidate embeddings. Because there are too many candidates, it isn’t feasible to compute the similarity between the query and every candidate, so a bit of accuracy is traded for speed

HNSW

HNSW is an ANN method. It organizes all embeddings into a graph, and then during search it walks along the graph to quickly find nearby points.

The HNSW index is essentially a multiple-layered sparse graph.

Each segment builds one HNSW graph.

  • Each embedding is a node, and similar embeddings are connected by edges
  • Stored using an adjacency list
  • multiple-layered, similar to a skip list
    • Upper layers: quickly jump to the approximate region
    • Lower layers: finely search for the nearest neighbors

Inserting a new node

  • Find the nodes similar to it, and first update the adjacency list
  • First insert it into the base layer, connecting it to similar nodes
  • Then insert it into the upper layers

Deleting a node Add an in-memory deletion marker; deleted nodes are filtered out during serving, and the actual deletion is left to compaction

HNSW Graph Compaction

Problem: when there are too many segments, retrieval becomes very slow

Add on Merger:

  • Select the largest segment and reuse its HNSW graph
  • Insert the embeddings of the other, smaller segments into it
  • If there is a deleted node, directly modify its embedding to the embedding of the nearest alive node. For example, Node 5 → Embedding 4
  • The cost is that deduplication is still needed after retrieval

Online Recall Monitoring

recall@K = |ANN topK ∩ exact KNN topK| / K

This computes global recall: each segment on each Leaf performs an HNSW search, returning a local top K; the root then aggregates the results to obtain a global top K.

ANN and exact KNN are run simultaneously only for a small number of sampled queries, in order to monitor the quality of the current realtime HNSW index.

Filtering

shop the look

Streaming Filtering

Pinterest blog: hnsw streaming filters

alt text

A query is broken down into several parts: HNSW retrieval + Filters + Scorer + Collector

This streaming algorithm runs inside a single Leaf, applied to each of its local HNSW segments.

  1. HNSW Iterator:

    • Traverses the segment’s HNSW graph in order of approximate embedding distance. Similar in spirit to Prim’s algorithm
      • visited set: which nodes have already been visited
      • candidate set: nodes that haven’t been visited yet but are reachable, sorted by distance. dist is the distance from the query embedding to the node embedding
    • Outputs a mini-batch each time
  2. Filter:

    • Checks metadata filters against this batch of candidates
    • e.g., safe=true, language=en, country=US, category=fashion, is_shoppable=true
    • Candidates that don’t pass the filter are discarded
  3. Scorer:

    • Scores the candidates that passed the filter
    • Can be lightweight scoring, or can plug in a more complex ranking score
  4. Collector:

    • Puts the filtered and scored candidates into a result heap
    • The heap maintains the current local top results
  5. Repeat:

    • If not enough valid results have been collected yet, the HNSW iterator continues traversal
    • It fetches the next batch of candidates
    • Continue filter → score → collect
  6. Stopping condition:

    • Even once the Top K nodes have already been collected, we still compare the dist(query, node) of the nearest unexplored node in the candidate set against the farthest distance in the result heap. If the unexplored nodes can’t improve the current result, we stop.
      • Only nodes that satisfy the filter conditions are considered
    • Production latency condition: stop once the time budget is reached, and return the current best valid results
  7. Merge:

    • Each segment produces local results
    • The Leaf merges the results from its own multiple segments
    • The Root then merges the results from multiple Leaves
    • The Blender then merges the results from multiple corpora

Shop the Look

The Shop the Look task: scene image → detect shoppable objects → retrieve similar products

Amazon

Bringing Multimodality to Amazon Visual Search System

It represents each product in the catalog as a fused vector of product image embedding + product title embedding, and builds a KNN/ANN index offline;

at online serving time, if the query has only an image, the query image embedding is used to search this multimodal catalog index.

Pinterest

Pinterest Shop the Look

  • Scene decomposition (detection): given a scene, detect all shoppable regions
  • Candidate retrieval (embedding): uses visual embeddings to retrieve visually similar products from a large shopping corpus.

Evaluation

Coverage: scene closeup volume How many scenes containing shoppable objects the user saw

Engagement total: product click-through volume How many products the user clicked on in total

Quality/rate: product click-through rate The proportion of products that get clicked after being shown

Human relevance: E2E Relevance@5 How similar the detected object is to its top-5 matched products

Scene decomposition

ResNeXt101-FasterRCNN + FPN

  1. The ResNeXt101 backbone extracts multi-level feature maps
  2. FPN turns these multi-level features into a feature pyramid
    • The purpose of FPN is to give the features at every scale relatively strong semantic information
  3. RPN generates region proposals on the pyramid features
  4. RoIAlign extracts a fixed-size RoI feature for each proposal from the corresponding pyramid level
  5. The detection head performs classification + bbox regression on each RoI
  6. Outputs a category label + bounding box

Candidate Retrieval

Version 1: Pre-filtering: per-category index

Offline:

  1. Compute a visual embedding for each product image
  2. Assign a category to each product
    • Using an in-house Google Product Taxonomy classifier
    • Or using a merchant-provided category label
  3. Group products by category
  4. Build a separate nearest-neighbor index for each category

Online:

  1. The detector outputs the bbox + category label of the query object
  2. Compute a visual embedding for the query object crop
  3. Select the corresponding category index based on the predicted category
  4. Perform ANN retrieval only within that category index

Version 2: LSH ANN + metadata restrictions

  • product image → visual embedding
  • product embedding → LSH tokens
  • inverted index:
    • LSH token → product IDs
    • metadata value → product IDs
  • forward index:
    • product ID → original visual embedding
  • online:
    • query object crop → visual embedding
    • query embedding → LSH tokens
    • query tokens + metadata restrictions form a tree-structured query
    • inverted index retrieves matched-token candidates(ANN)
    • exact embedding distance reranks candidates

Complete the Look

pinterest blog

Input:

  • scene image: a scene photo, e.g., an outfit / a room / a street-style shot
  • product image: a candidate product, e.g., a pair of shoes / a bag / a chair

Output:

  • A distance / score
  • This score represents whether this product goes well with this scene

VPG

pinterest paper

  1. Find real-world scenes that contain the query product / a similar product
  2. Extract other shoppable objects from the scene alt text

Feature Storage

alt text

KV Store

  1. full-image embedding The embedding of the whole image

  2. object coordinates The position of each object in the image, e.g., bbox: x, y, w, h

  3. object embedding The embedding of each detected object crop

Initial Backfill

  • Use Spark to do offline batch processing: run all existing images through the object detector to identify objects
  • Compute an embedding for each object and put it into the KV Store

Incremental updates

  • For new images, use Flink to do near-real-time stream processing

Online Fallback

  • Sometimes a user clicks on a new image, but Flink hasn’t finished processing it yet, so the KV Store doesn’t have this image
  • The system falls back to online real-time computation

Reverse-STL

Use an object image to perform ANN, find similar objects, and then fetch the original images that those similar objects belong to

Forward-STL

  • Perform object detection on top scene images
  • Find matching product images for the other objects in the scene