Harnessing the Power of Graphs and AI for Lightning-Fast Image Retrieval
Introduction
As the old adage goes, a picture is worth a thousand words. In the age of Instagram, YouTube, and TikTok, it may be more like a billion. With millions of images and videos being created and shared every day, visual data is becoming an increasingly vital source of insights for businesses and researchers alike.
However, the sheer scale of visual content poses a significant challenge: how can we efficiently search through massive image collections to find relevant and visually similar items? Traditional methods based on textual metadata or pixel-level comparisons quickly break down when faced with the volume, variety, and complexity of real-world image datasets.
Enter the powerful combination of graph databases and deep learning. By representing images and their complex relationships in a graph and extracting semantic features into compact vector embeddings, we can enable fast, flexible, and scalable visual similarity search. And Neo4j, a native graph database optimized for storing and querying connected data, is an ideal platform to implement this cutting-edge approach.
In this deep dive, we‘ll explore how to build a state-of-the-art image retrieval system using Neo4j and deep learning embeddings. Along the way, we‘ll cover the key concepts, practical techniques, and exciting applications of this game-changing technology. Get ready to level up your visual AI capabilities and unlock the full value of your image data!
Why Graphs and AI Are a Match Made in Heaven
Before we jump into the technical details, let‘s take a step back and understand why graph databases and deep learning models are such a powerful combination for image retrieval and other visual AI tasks.
At its core, image retrieval is about understanding the semantic content and context of images and finding relevant connections between them. And as it turns out, graphs are an incredibly natural and expressive way to represent this kind of information.
With graphs, we can model images as nodes and use edges to represent all kinds of relationships, such as:
- Visual similarity: connect images that are close together in embedding space
- Metadata: link images to textual descriptions, tags, categories, etc.
- User interactions: capture likes, views, clicks, and other behavioral signals
- Hierarchies: organize images into albums, collections, and ontologies
- Spatial and temporal relationships: link images based on location and time
By unifying all this information in a single graph, we can ask complex questions and uncover valuable insights that would be difficult or impossible with other data models. For example:
- Find visually similar images to a given query that also share certain metadata tags
- Recommend personalized image content based on a user‘s browsing history and preferences
- Identify clusters of related images and detect anomalies or outliers
- Trace the provenance and spread of an image through a social network
But to really unlock the power of graphs for image retrieval, we need a way to efficiently compute and store semantic similarity between images. That‘s where deep learning embeddings come in.
Deep learning has revolutionized the field of computer vision in recent years, achieving superhuman performance on tasks like image classification, object detection, and facial recognition. And one of the key enablers of this progress is the ability to learn rich, compact vector representations of images, known as embeddings.
Embeddings are typically generated by training a convolutional neural network (CNN) on a large-scale image classification task and then extracting the activations from one of the final layers as a feature vector. These embeddings capture high-level semantic information about the content and style of an image, such that visually similar images will have embeddings that are close together in the vector space.
For example, here are the embeddings of 10,000 images from the CIFAR-10 dataset visualized in 2D using t-SNE:

Source: https://github.com/jcjohnson/cnn-benchmarks
As you can see, the embeddings form distinct clusters corresponding to the different object categories in the dataset, like airplanes, cars, birds, etc. This shows how embeddings can capture semantic similarity and enable efficient similarity search, even for complex visual concepts.
By storing image embeddings as node properties in Neo4j, we can use the built-in graph algorithms to quickly find similar images based on the proximity of their vectors. And by combining embeddings with other graph-based signals like metadata and user interactions, we can create highly relevant and personalized visual search and recommendation experiences.
Implementing Image Retrieval in Neo4j
Now that we understand the power of graphs and embeddings for image retrieval, let‘s walk through a practical example of implementing this approach in Neo4j.
Generating Embeddings
The first step is to generate embeddings for our image dataset using a pre-trained CNN model. For this example, we‘ll use the popular ResNet-50 architecture trained on the ImageNet dataset, which achieves high accuracy on a wide range of visual recognition tasks.
Here‘s some sample code to generate embeddings using PyTorch:
import torch
import torch.nn as nn
from torchvision import models, transforms
# Load pre-trained ResNet-50 model and remove final layer
model = models.resnet50(pretrained=True)
model = nn.Sequential(*list(model.children())[:-1])
model.eval()
# Define input image transforms
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
def generate_embedding(image_path):
img = Image.open(image_path).convert(‘RGB‘)
img_t = transform(img)
batch_t = torch.unsqueeze(img_t, 0)
with torch.no_grad():
embedding = model(batch_t)
return embedding.squeeze().numpy()
This code snippet loads a pre-trained ResNet-50, removes the final classification layer, and defines an input transformation pipeline to preprocess images to the expected format. The generate_embedding function takes an image path, applies the transforms, passes the image through the model, and returns the 2048-dimensional embedding vector.
We can then generate embeddings for a directory of images as follows:
import glob
import os
image_dir = "path/to/image/directory"
image_paths = glob.glob(os.path.join(image_dir, "*.jpg"))
embeddings = {}
for image_path in image_paths:
image_id = os.path.basename(image_path)
embedding = generate_embedding(image_path)
embeddings[image_id] = embedding
This code collects all the image paths in the directory, generates an embedding for each one, and stores the mapping of image IDs to embedding vectors in a dictionary.
Storing Embeddings in Neo4j
With our embeddings generated, the next step is to load them into Neo4j. We can use the Neo4j Python driver to create an Image node for each embedding and store the vector as a node property:
from neo4j import GraphDatabase
driver = GraphDatabase.driver("neo4j://localhost:7687",
auth=("neo4j", "password"))
def add_image(tx, image_id, embedding):
tx.run("MERGE (i:Image {id: $image_id}) "
"SET i.embedding = $embedding",
image_id=image_id, embedding=embedding.tolist())
with driver.session() as session:
for image_id, embedding in embeddings.items():
session.write_transaction(add_image, image_id, embedding)
This code creates a new Neo4j driver instance, defines a helper function to add an Image node with id and embedding properties, and then iterates over the embedding dictionary to store each one in the graph using the add_image function in a write transaction.
Note that we use the MERGE clause to avoid creating duplicate nodes, and we convert the NumPy embedding array to a list before storing it, since Neo4j doesn‘t have native support for multi-dimensional properties.
After running this code, we can visualize our image graph in the Neo4j browser:

Querying for Similar Images
Now comes the fun part – using our graph of image embeddings to find visually similar items! Neo4j provides a variety of graph algorithms that we can use to compute similarity between node properties, but for this example we‘ll stick to the simple and efficient cosine similarity.
Given two vectors $a$ and $b$, the cosine similarity is defined as:
$$
\text{similarity} = \cos(\theta) = \frac{a \cdot b}{||a|| \, ||b||}
$$
where $\cdot$ is the dot product and $||\cdot||$ is the L2 norm (Euclidean distance). Cosine similarity ranges from -1 to 1, with higher values indicating greater similarity.
To find the top k most similar images to a query embedding, we can run the following Cypher query using the gds.similarity.cosine procedure:
MATCH (i:Image)
WITH i, gds.similarity.cosine(i.embedding, $query_embedding) AS score
RETURN i, score
ORDER BY score DESC
LIMIT $k
This query matches all Image nodes, computes the cosine similarity between each node‘s embedding property and the $query_embedding parameter, and returns the nodes and scores ordered by descending similarity, limited to the top $k results.
Here‘s how we can execute this query from Python:
def find_similar_images(tx, query_embedding, k):
result = tx.run("MATCH (i:Image)"
"WITH i, gds.similarity.cosine(i.embedding, $query_embedding) AS score "
"RETURN i, score "
"ORDER BY score DESC "
"LIMIT $k",
query_embedding=query_embedding.tolist(), k=k)
return [(record["i"], record["score"]) for record in result]
# Generate embedding for query image
query_embedding = generate_embedding("path/to/query/image.jpg")
# Find top 5 most similar images
with driver.session() as session:
results = session.read_transaction(find_similar_images, query_embedding, k=5)
# Print results
for node, score in results:
print(f"Image: {node[‘id‘]}, Similarity: {score:.4f}")
This code defines a helper function to execute the similarity search query and return the top k results as a list of tuples containing the Image nodes and similarity scores. We then generate an embedding for a query image, find the top 5 most similar images using the find_similar_images function, and print out the results.
And that‘s it! With just a few lines of code, we‘ve built a powerful image retrieval system that can find visually similar items in a dataset of thousands or even millions of images in milliseconds. The combination of Neo4j‘s graph database and optimized similarity algorithms with the semantic richness of deep learning embeddings enables us to perform complex visual searches with speed and precision that would be difficult to achieve with other approaches.
Advanced Techniques and Applications
Of course, this is just the tip of the iceberg when it comes to using Neo4j and embeddings for image retrieval and visual AI. There are many ways to extend and optimize this basic pipeline, such as:
-
Approximate nearest neighbor search: For very large datasets, exact similarity search can still be slow, even with graph optimizations. To further speed up retrieval, we can use approximate nearest neighbor (ANN) indexing techniques like locality-sensitive hashing or hierarchical navigable small world graphs to pre-compute a compressed index of the embedding space and enable sub-linear search times.
-
Multi-modal retrieval: In many real-world applications, images are accompanied by additional metadata like titles, descriptions, tags, geospatial coordinates, etc. By storing this information as node properties in Neo4j and using multi-modal embeddings that combine visual and textual features, we can enable more flexible and semantic searches that fuse graph, vision, and language understanding.
-
Graph-enhanced recommendations: One of the key advantages of storing image embeddings in a graph is the ability to leverage Neo4j‘s powerful pattern matching and pathfinding algorithms to discover complex relationships and make intelligent recommendations. For example, we could use community detection to cluster similar images, collaborative filtering to recommend images based on user preferences, or diversity-based ranking to surface novel yet relevant results.
-
Few-shot learning and transfer learning: Embeddings can also be used to enable more data-efficient visual learning paradigms like few-shot and zero-shot classification, where we train a model to recognize new categories from just a handful of examples by comparing their embeddings to those of known classes. Additionally, we can use transfer learning to fine-tune pre-trained embedding models on domain-specific datasets and tasks to improve performance and adapt to different visual contexts.
-
Visual question answering and embodied AI: Beyond image retrieval, graph-based visual representations can power more advanced AI applications like visual question answering, where a model uses embeddings and graph traversals to reason about the contents of an image and generate natural language responses to user queries. They can also inform embodied AI agents that navigate and interact with visual environments using graph-based world models and planning algorithms.
As the fields of computer vision, knowledge graphs, and AI continue to advance and converge, the potential applications and impacts of this technology are truly limitless. By combining the expressiveness of graphs, the power of deep learning, and the scalability of databases like Neo4j, we can build intelligent systems that can see, understand, and reason about the world in ways that were once only possible for humans. And in doing so, we can unlock new frontiers of visual intelligence and transform industries from e-commerce and social media to healthcare, robotics, and beyond.
Conclusion
In this article, we‘ve explored the exciting intersection of graph databases, deep learning, and computer vision and seen how Neo4j and embeddings can enable state-of-the-art image retrieval at scale. By representing images and their relationships in a graph and using embeddings to capture visual semantics, we can perform fast and flexible similarity searches that power a wide range of applications, from visual product recommendations to content moderation and medical image analysis.
Whether you‘re a data scientist looking to leverage the latest AI techniques, a developer building intelligent applications, or a business leader seeking to harness the value of your visual data, the combination of Neo4j and embeddings is a powerful tool to have in your arsenal. With its intuitive data model, optimized algorithms, and vibrant ecosystem, Neo4j makes it easy to get started with graph-based visual AI and scale to production-level performance.
So what are you waiting for? Start experimenting with Neo4j and embeddings today and see how they can transform your image retrieval capabilities and open up new possibilities for visual intelligence. The future of AI is graph-powered, and the time to get on board is now!
References and Further Reading
- Neo4j Image Similarity Search Using GDS
- Intro to Graph Embeddings for Recs & Similarity
- Visual Search at Pinterest
- ANN Benchmarks
- Combining Language and Vision with a Multimodal Skip-gram Model
- Graph-Structured Representations for Visual Question Answering
- GQN: Neural Scene Representation and Rendering