Engineering

Causal Inference at Onfido

How Onfido's data science team built a Structural Causal Model of their document verification pipeline — and used it to simulate the impact of product improvements before shipping a single line of new code.

Anna Borodina6 min read
Abstract illustration representing causal inference and data flows

At Onfido, we use AI to automate digital identity verification for 800+ businesses worldwide. Our proprietary AI engine combines machine learning models to automate as many verifications as possible and escalate edge cases for human review.

In this post, I’ll walk through how we used Causal Inference to create a structural model of our document verification product, and how that model lets us simulate the impact of product improvements before building anything.


Automation Structural Causal Model

We needed to create a model that would provide a high-level overview of our document verification product and allow us to evaluate the causal effect of product initiatives on automation.

We turned to Causal Inference because it aims at answering causal questions and estimating causal effects. Structural Causal Models (SCMs) represent causal relationships between variables using a directed acyclic graph (DAG) and structural equations.

We started with creating a graph of the document verification product. From the point a document is submitted until a decision is made, we mapped every step and its inputs.

Document verification causal graph — automated flow (green) and manual flow (amber)

The graph has two main flows: automated (Onfido’s AI) in green and manual (human agents) in amber. The product automation rate is the share of checks that go through the automated flow.

Formally, a causal graph specifies a factorisation of the joint probability distribution of data:

Joint probability factorisation formula

The next step was to formalise the relationship between variables. Let N be the number of checks submitted for a period of time. We introduced the following notation:

Variable notation for the causal model

We used simple regression models without additive error terms for the modelling. Structural equations provide the functional relationship between each variable and its direct causes:

Structural equations for the causal model

For this article, we’ll generate a synthetic dataset and use the automation structural causal model to evaluate product initiatives.

list_country_names = [country.name for country in pycountry.countries]
document_types = ['ID', 'Passport', 'Driver Licence']
documents = list(itertools.product(list_country_names, document_types))

We’ll generate all the variables randomly and obtain a dataset, in which one line corresponds to one document type per country:

np.random.seed(0)
df = pd.DataFrame(data=documents, columns=['Document'])
df['Classification rate'] = np.random.uniform(0, 1, len(df))
df['Enabled automation'] = np.random.randint(0, 2, len(df))
df['Extraction rate'] = np.random.uniform(0, 1, len(df))
df['Non-Fraud rate'] = np.random.uniform(0, 1, len(df))
df['Share of checks'] = np.random.dirichlet(np.ones(len(df)))

Synthetic dataset sample

Classification

Document classification — determining the issuing country and document type

After an applicant submits a picture of their document, Onfido’s AI will try to classify it: determine the issuing country and document type. Classification is characterised by a score that represents the model’s confidence level.

If the classification score is higher than a threshold, this document will continue in the automated flow; otherwise it will be escalated for manual review.

N = 100
df_c = df[df['Classification rate'] >= 0.2]
c = df_c['Share of checks'].sum()
classified = c * N

Enabled automation

To decrease the number of fraudulent applicants who pass our verification process, Onfido doesn’t allow some document types or countries to be processed automatically.

e × c × N checks continue the automatic flow. In the code, we’ll take the checks that passed classification and filter for those with enabled automation:

df_e = df_c[df_c['Enabled automation'] == 1]
e = df_e['Share of checks'].sum()
enabled_auto = e * c * N

Engine

The Onfido AI engine — extraction and fraud assessment run simultaneously

The engine is simplified into two components that are executed simultaneously. Extraction refers to an ensemble of machine learning models for extracting information from the document image.

Fraud assessment refers to determining whether the document shows signs of fraud. Our unique micro-model architecture allows us to detect different types of fraud by running many specialised models.

Let a be the share of checks that pass both components.

Auto-complete

Document verifications that passed both auto extraction and auto fraud assessment are auto-complete.

df_a = df_x[(df_x['Extraction rate'] >= 0.7) & (df_x['Non-Fraud rate'] >= 0.8)]
a = df_a['Share of checks'].sum()
auto_complete = c * e * a * N

39% of all checks are auto-complete.


A Simple Intervention

One of the benefits of causal inference is that it allows us to simulate experiments and evaluate the causal effect of interventions.

The do-operator is used in causal inference to denote an intervention. Given random variables A and B:

  • P(A | B) is the probability of A given B (the distribution under a natural state of B)
  • P(A | do(B = b)) is the probability of A given an intervention that sets B to b

The do-operator simulates interventions by deleting certain functions from the model, replacing them with a constant value. Formally:

Factorisation formula

We can simulate an intervention in Extraction by removing an incoming arrow from E to X, and manually setting the extraction rate to a fixed value x₀ for the documents we’re interested in:

Causal graph after intervention — arrow from E to X removed

New auto-completion formula after intervention

The new auto-completion rate is:

Auto-completion rate formula

Where a₀ is the percentage of checks that pass both Extraction and Fraud Assessment. The uplift from this intervention is:

Uplift formula

Example

One of the product initiatives is improving the extraction model’s performance up to 99% on the top three document types for a set of countries — the “-stans”:

stans = ['Turkmenistan', 'Uzbekistan', 'Tajikistan', 'Kyrgyzstan',
         'Kazakhstan', 'Afghanistan', 'Pakistan']
df_a0 = df_e[df_e['Document'].apply(lambda x: any(s in x[0] for s in stans))]

Example: top documents from “-stan” countries

We’ll set the Extraction rate to x₀ = 0.99 and calculate the uplift:

df_a0.loc[:, 'Extraction rate'] = 0.99
a0 = df_a0['Share of checks'].sum()
uplift = c * e * a0 * N - auto_complete

The uplift from optimising extraction on the top 5 documents is 0.6 percentage points.


Extraction Optimisation

Going even further, instead of taking a set of top three documents, we can estimate the uplift of improving extraction on different subsets of document types, ordered by volume.

Uplift as a function of how many document types are optimised

The first document in the subset has the highest volume in checks and the n-th has the lowest. According to the law of diminishing returns, each additional document type has a smaller marginal uplift.

Kneedle

The Kneedle algorithm is an algorithm developed to detect knees in discrete datasets. We’ve used it to detect the knee of the uplift curve — the optimal subset of document types to prioritise.

This algorithm uses the mathematical definition of curvature for a continuous function as the basis for the knee point:

Mathematical definition of curvature

Kneedle algorithm steps

The algorithm follows these steps:

a) We use a smoothing spline to preserve the shape of the original dataset as much as possible. The points of the spline are normalised between 0 and 1.

b) The distances are rotated 45 degrees clockwise. The goal is to determine when the difference curve changes significantly.

c) We find the local maxima of the difference curve. For each local maximum, we define a unique threshold value. The knee is the first local maximum that exceeds its threshold.

Optimising Extraction rate with Kneedle

We’ll use the kneed Python package’s implementation of the algorithm and the automation model’s formula to calculate the optimal number of document types to optimise:

Formula for calculating optimal extraction uplift with Kneedle

Plotting the uplift for every document type i, we obtain this graph with the number of document types to optimise on the x-axis:

uplift = []
df_xn = df_xn[(df_xn['Extraction rate'] >= 0.7) & (df_xn['Non-Fraud rate'] >= 0.8)]
for i in range(1, len(df_xn) + 1):
    df_top = df_xn.head(i)
    df_top.loc[:, 'Extraction rate'] = 0.99
    a0 = df_top['Share of checks'].sum()
    uplift.append(c * e * a0 * N - auto_complete)

Kneedle result — knee point at top 6 document types

The knee point is at top six document types, corresponding to an uplift of about 1.2 percentage points. Improving extraction on more than six document types yields diminishing returns.


Conclusion

Using causal inference we’ve created a high-level overview of Onfido’s document verification product. It allows us to understand the causal effects of variables on automation and simulate interventions.

The model we created allowed us to simulate the potential impact of varying product initiatives on automation rate. Combined with the Kneedle algorithm, we can identify the optimal subset of documents to focus extraction improvements on — giving product and engineering teams a principled way to prioritise before writing any code.

Conclusion


Resources

  1. Cambridge Advanced Tutorial Lecture Series on Machine Learning
  2. Getting Started with Causal Inference
  3. The Do-Calculus Revisited
  4. Causal Inference
  5. Elements of Causal Inference: Foundations and Learning Algorithms
  6. Finding a “Kneedle” in a Haystack: Detecting Knee Points in System Behavior
Share
Written by
Anna Borodina

Data Science & Product Analytics at Entrust.

Comments

Hook this up to your favourite commenting platform — Giscus, Disqus, or your own.

Continue reading

Product·

Data-driven Product Launch — Onfido Studio

How a data scientist approaches a B2B product launch from first principles — defining metrics before a line of code ships, building the data model, creating pre-launch dashboards, and learning from the first beta customers.

Anna Borodina · 8 min

A quieter inbox.

One thoughtful letter every other Sunday — new essays, things worth reading, and the occasional photograph.

Free. Unsubscribe in one click.