Current cutting-edge facial recognition models offer human-level accuracy. Still, we can improve facial recognition model accuracies if we represent classifications in a graph. In this post, we are going to find false negative classifications of facial recognition models with Neo4j graph database.
Vlog
You can either watch the following video or continue to follow this tutorial. They both cover finding false negative classifications in facial recognition with neo4j graph database.
🙋♂️ You may consider to enroll my top-rated machine learning course on Udemy
False positive vs false negative
We have just focused on detecting false positives in facial recognition with Neo4j. False positives are mis-classifications verifying different persons as same person. On the other hand, false negatives classifies a same person pair as different persons. Finding false positive classifications is harder because it requires some observations and calculations. If two strongly connected clusters are connected to each other weakly, then this is a pattern for false positive. We calculated the betweenness centrality scores to find weak connection between strong cluster. However, finding false negatives is much easier. We are able to find false negatives with simple cypher queries in neo4j.
Storing facial images as nodes
We will run same procedures in our previous post to store facial database in the graph. Basically, the following code block will handle moving facial images to neo4j with image name and embedding.
import os from tqdm import tqdm from deepface import DeepFace from neo4j import GraphDatabase, basic_auth #initialize neo4j connection driver = GraphDatabase.driver("bolt://localhost:7687", auth=basic_auth("neo4j", "*****")) session = driver.session() # store facial image database in neo4j with session.begin_transaction() as trx: for root, dirs, files in os.walk("../deepface/tests/dataset/"): for i, file in tqdm(enumerate(files)): if '.jpg' in file: img_path = root+file img_name = file[:-4] embedding = DeepFace.represent(img_path=img_path, model_name="Facenet", detector_backend="mtcnn")[0]["embedding"] statement = f""" MERGE (f{i}:Face {{name: '{img_name}'}}) SET f{i}.embedding = {embedding} """ trx.run(statement) trx.commit()
Creating edges
Thereafter, individual nodes will be created in neo4j without relationships. To create edges, we will run the following cypher statement in the neo4j side. This will create edges between nodes if the euclidean distance value is less than the threshold value of 10. This threshold value is pre-tuned value in deepface for Facenet model and euclidean distance pair.
MATCH (p1:Face) MATCH (p2:Face) WHERE p1.name <> p2.name WITH p1, p2, gds.similarity.euclideanDistance(p1.embedding, p2.embedding) as distance WHERE distance < 10 MERGE (p1)-[e:distance]-(p2) SET e.distance=distance
Then, we will have nodes and edges in the graph side.
A false negative case
In this graph, you can see an interesting pattern on the left-down side. When I visualize the images for this cluster, that would be more interesting. All images are belonging to Jack Dorsey. That is expected. When you ask img59 – img62 pair to deepface, it verifies this pair as same person. That is correct. On the other hand, if you test the img59-img16, img59-img61 and img59-img17 pairs, then deepface will fail. It says these are pairs of different persons but actually they are not!
SQL
If we store these relationships in a relational database, SQL-like queries require to write nested loops to find in-direct relationships. For example, we can find 2-depth relationships with the following SQL. If you want to find 3-depth relationships, then the query will become much complexer.
SELECT b.p2 ( SELECT p2 FROM Face WHERE p1 = "img59" ) a left join Face b on a.p2 = b.p1 WHERE b.p2 != "img59"
Cypher
Luckily, we have Cypher queries and Neo4j! The following query will return the nodes having relation to img59 from 1 to 2. If you want to increase the depth-size, you just need to modify *1..2 to *1..3. So, it will not increase the complexity of the query.
MATCH p=(n:Face)-[r*1..2]-(m:Face) WHERE n.name = "img59" RETURN n.name, m.name
So, even though img59 is not connected to any of img16, img17, img61, we can retrieve its relationship to those nodes via img62. Besides, we can get it with a simple cypher query in Neo4j.
Conclusion
So, we have mentioned how to find false negative classifications in facial recognition experiments with Neo4j graph database. Even if relationship are stored in a relational database and we can retrieve in-direct relationship with SQL queries, it becomes more and more complex when the depth-size increased. On the other hand, we can do this task very easily in Neo4j with same cypher queries.
Support this blog if you do like!