The business problem
US banks hold over $12 trillion in consumer and commercial loans. Net charge-off rates averaged 0.5% in stable periods and spiked to 3%+ during the 2008 crisis. Every 10-basis-point improvement in default prediction translates to billions in reduced losses. The challenge is identifying borrowers whose individual metrics look acceptable but whose network positions signal hidden risk.
Traditional credit models use FICO scores, debt-to-income ratios, employment duration, and payment history. These features describe the borrower in isolation. They miss the systemic signals: is the borrower's employer about to lay off 20% of staff? Are their co-signers already delinquent? Is their geographic community experiencing economic decline?
Why flat ML fails
- No systemic risk: A borrower at a struggling employer looks identical to one at a thriving company if both have the same job title and income. The graph reveals employer health.
- Co-borrower blindness: Co-signers and guarantors create risk linkages. If a co-signer defaults on another loan, the primary borrower's risk increases. Flat models cannot see these connections.
- Thin-file penalty: Borrowers with limited credit history get no signal from individual features. Their financial network (employer, bank, community) provides proxy risk signals that GNNs capture.
- Contagion effects: Defaults cluster in networks. A wave of layoffs at one employer cascades through co-borrower and community connections. Flat models miss these propagation patterns.
The relational schema
Node types:
Borrower (id, fico, income, dti, employment_years)
Loan (id, amount, rate, term, product_type)
Employer (id, industry, size, revenue_growth)
Institution (id, type, assets, npl_ratio)
Edge types:
Borrower --[has_loan]--> Loan
Borrower --[employed_at]--> Employer
Borrower --[banks_at]--> Institution
Borrower --[co_borrower]--> Borrower
Loan --[originated_by]--> InstitutionFive node/edge types capture the borrower's financial network. Flat models compress this into a single feature vector per borrower.
PyG architecture: HeteroConv for credit scoring
import torch
import torch.nn.functional as F
from torch_geometric.nn import HeteroConv, SAGEConv, Linear
class CreditRiskGNN(torch.nn.Module):
def __init__(self, hidden_dim=64):
super().__init__()
self.borrower_lin = Linear(-1, hidden_dim)
self.loan_lin = Linear(-1, hidden_dim)
self.employer_lin = Linear(-1, hidden_dim)
self.institution_lin = Linear(-1, hidden_dim)
self.conv1 = HeteroConv({
('borrower', 'has_loan', 'loan'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'employed_at', 'employer'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'banks_at', 'institution'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'co_borrower', 'borrower'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
self.conv2 = HeteroConv({
('borrower', 'has_loan', 'loan'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'employed_at', 'employer'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'banks_at', 'institution'): SAGEConv(
hidden_dim, hidden_dim),
('borrower', 'co_borrower', 'borrower'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
self.classifier = torch.nn.Sequential(
Linear(hidden_dim, 32),
torch.nn.ReLU(),
Linear(32, 1),
)
def forward(self, x_dict, edge_index_dict):
x_dict['borrower'] = self.borrower_lin(x_dict['borrower'])
x_dict['loan'] = self.loan_lin(x_dict['loan'])
x_dict['employer'] = self.employer_lin(x_dict['employer'])
x_dict['institution'] = self.institution_lin(
x_dict['institution'])
x_dict = {k: F.relu(v) for k, v in
self.conv1(x_dict, edge_index_dict).items()}
x_dict = self.conv2(x_dict, edge_index_dict)
return torch.sigmoid(
self.classifier(x_dict['borrower']).squeeze(-1))HeteroConv with SAGEConv per edge type. Two hops lets the model see a borrower's employer health, co-borrower status, and institution-level risk signals.
Training and compliance
- Label definition: 90+ days past due (DPD90) is the standard default definition. Use a 12-month observation window.
- Temporal integrity: Never use future payment data as features. Train on vintages before time T, test on loans originated at T.
- Explainability: Regulators require adverse action reasons. Use GNNExplainer to attribute predictions to specific features and neighbors. Attention weights from GATConv variants provide built-in interpretability.
- Fair lending: Ensure protected attributes (race, gender) do not flow through proxy paths in the graph. Audit for disparate impact across demographic groups.
Expected performance
- Logistic regression (scorecard): ~60 AUROC
- LightGBM (flat-table): 62.44 AUROC
- GNN (HeteroConv): 75.83 AUROC
- KumoRFM (zero-shot): 76.71 AUROC
Or use KumoRFM in one line
PREDICT is_default FOR borrower
USING borrower, loan, employer, institution, paymentOne PQL query. KumoRFM constructs the heterogeneous borrower graph, trains with temporal payment histories, and outputs default probabilities with feature attributions.
KumoRFM replaces the graph construction, model architecture, and compliance tooling with a single query. It achieves 76.71 AUROC zero-shot and provides prediction explanations that support regulatory review.