银行间网络重构:融合局部聚类特征的最大熵优化

银行间网络重构:融合局部聚类特征的最大熵优化 简介银行间网络重构是金融系统性风险传染研究的基础工具这份 docx 文档面向金融工程、风险管理方向的研究生与科研人员聚焦最大熵方法、最小密度方法等经典重构技术在稀疏性、无标度性与异配性刻画上的局限。文档共 1 个 docx 文件约 215KB完整呈现了一种融合局部聚类特征并可自适应调整连边密度的重构思路依次梳理最小密度方法的约束优化建模、拆借概率矩阵与适应度函数推导以及核心银行间聚类特征丢失、连边密度被低估两大问题的改进路径并附德国、英国及中国同业拆借市场的实证结论与参考文献脉络便于读者直接引用建模公式、复现算法流程或作为论文写作的素材支撑。目前已有 63 人学习下载适合需要深入理解银行间网络构型与风险传染模拟的研究者参考。1. 银行间网络重构里被低估的局部聚类特征做同业敞口压力测试最麻烦的一步不是建模而是面对一张空白的 N×N 矩阵能拿到的通常只有每家银行的同业资产合计和同业负债合计也就是行和与列和真正的敞口矩阵没人见过。把这两列数还原成完整网络就是银行间网络重构。常见实现用最大熵或 RAS 迭代把矩阵填满行和列和总能对上可一旦拿去做传染模拟风险传导路径就明显偏了——问题多半出在局部聚类特征上。真实同业网络是核心-边缘结构核心银行彼此高度互联、形成密集三角形边缘银行大多只挂一两条边。最大熵解却把所有邻居连成一片二值化后每个节点的局部聚类系数都等于 1三角形数量被系统性放大。下面按定义口径、复现基线、嵌入目标函数的顺序把融合局部聚类特征的重构方案讲到能跑代码。2. 局部聚类特征的口径与重构依赖的数据前提2.1 重构真正拿得到的三组数银行间网络重构的输入从来不是敞口矩阵本身而是三类可观测信息。第一类是每家银行的同业资产合计 a_i来自年报附注里的拆出资金 存放同业或监管报表的同业往来科目第二类是同业负债合计 l_j即拆入资金 同业存放第三类是总敞口 L Σa_i Σl_j。这三类信息构成一个标准的运输问题约束求非负矩阵 X使行和为 a、列和为 l。N40 时未知数有 1560 个独立约束只有 79 个解空间极大所以任何重构方法都必须引入额外假设而假设的选择直接决定了重构网络的局部聚类特征长什么样。除了总量实务中还会拿到一部分已观测到的大额边比如大额风险暴露报送里超过阈值的单笔同业敞口或者银行间市场成交明细。这些边数量不多但权重占比高是校准局部聚类特征最可靠的锚点。把它们当作半监督信息用比纯粹从总量反推要稳得多。提示同业资产与同业负债的统计口径经常不对称一方含同业存单、另一方不含。重构前先做口径对齐否则总敞口 L 会相差几十个百分点后面所有校准都是白做。2.2 二值局部聚类系数与加权局部聚类系数局部聚类系数衡量的是一个节点的邻居之间互相连接的比例。二值口径下节点 i 的局部聚类系数为 C_i 2T_i / (k_i(k_i−1))其中 T_i 是包含 i 的三角形个数k_i 是二值度。二值口径的问题在于必须先做阈值化而同业敞口矩阵的权重跨三到四个数量级阈值选在哪聚类系数就跟着变。加权口径Onnela用三个边权重的几何平均代替计数避免了阈值敏感性import numpy as np def local_clustering(W, eps1e-12): 返回二值局部聚类系数 C_bin 与 Onnela 加权局部聚类系数 C_w W np.array(W, dtypefloat) np.fill_diagonal(W, 0.0) A (W eps).astype(float) # 二值邻接非零即为边 k A.sum(axis1) W13 np.cbrt(W) # 立方根供几何平均使用 N W.shape[0] C_bin np.zeros(N) C_w np.zeros(N) Wmax W.max() eps for i in range(N): if k[i] 2: continue nb np.where(A[i] 0)[0] # 二值邻居之间的实际连边数 / 最大可能连边数 sub A[np.ix_(nb, nb)] C_bin[i] (sub.sum() - len(nb)) / (k[i] * (k[i] - 1)) # 加权Σ (w_ij w_jl w_li)^(1/3)除以有序对数量与最大权重 s 0.0 for j in nb: for l in nb: if j ! l: s W13[i, j] * W13[j, l] * W13[l, i] C_w[i] s / (k[i] * (k[i] - 1)) / Wmax return C_bin, C_wlocal_clustering的第一个返回值是二值系数分母用 k_i(k_i−1) 而不是 k_i(k_i−1)/2因为循环遍历的是有序对。第二个返回值是加权系数先对权重取立方根再连乘再除以最大权重做归一化这样 C_w 也落在 [0,1] 区间能和二值口径直接比较。eps决定哪些边算存在实战里不要用绝对阈值改成总量的千分之一到百分之一更稳。2.3 三种经典重构方法对局部聚类的偏好把最大熵、最小密度和 RAS 放在一起看会发现它们对局部聚类的处理恰好构成一个谱系。方法约束处理二值局部聚类倾向加权局部聚类倾向典型缺陷最大熵行和列和精确满足恒为 1完备图被小权重压低严重高估三角形与传染广度最小密度行和列和精确满足接近 0近似森林接近 0严重低估传染路径数量RAS行和列和迭代满足由先验决定由先验决定先验选错则结构全错最大熵解的闭式表达是 X_ij a_i·l_j / L所有非对角元都严格为正二值化后就是一个完备图每个节点的局部聚类系数都是 1。最小密度解反过来每一步把剩余额度最大的行与列配对只产生恰好够用的边得到的是一张极稀疏的近似森林几乎不含三角形。真实网络的局部聚类处在两者之间且有强烈的异质性——核心银行 C_i 可以到 0.6 以上边缘银行接近 0。这就是融合局部聚类特征要补的那块信息不是简单地把网络调稀或调密而是让每个节点各自的聚类水平对上。3. 用最大熵与 RAS 搭一条可复现的重构基线3.1 造一个带核心-边缘结构的仿真网络没有真值就没法判断重构质量所以第一步是造一个已知的核心-边缘网络当作真实敞口矩阵。下面这段代码构造 N40、核心 8 家的三层结构核心-核心稠密、核心-边缘部分连接、边缘-边缘稀疏。import numpy as np def simulate_interbank(N40, n_core8, seed7): 构造带核心-边缘结构的真实敞口矩阵仅用于验证重构算法 rng np.random.default_rng(seed) X np.zeros((N, N)) core np.arange(n_core) peri np.arange(n_core, N) for i in core: # 核心-核心稠密、权重大天然形成三角形 for j in core: if i ! j: X[i, j] rng.lognormal(4.0, 0.4) for i in core: # 核心-边缘按概率挂边权重中等 for j in peri: if rng.random() 0.45: X[i, j] rng.lognormal(2.6, 0.6) if rng.random() 0.35: X[j, i] rng.lognormal(2.4, 0.6) for i in peri: # 边缘-边缘稀疏、权重小 for j in peri: if i ! j and rng.random() 0.06: X[i, j] rng.lognormal(1.5, 0.7) return X X_true simulate_interbank() a, l X_true.sum(axis1), X_true.sum(axis0) # 只暴露这两个向量给重构算法注意最后一行是这套方法的核心纪律行和与列和可以喂给算法X_true只能留在评估环节否则就是自欺欺人。真实场景里还可以把权重最大的前 10% 边当作已观测锚点但基线对比先不加。3.2 最大熵、最小密度与 RAS 的实现三个基线的实现都很短。最大熵是一行矩阵外积最小密度是贪心配对RAS 是迭代比例拟合用来把某个先验矩阵拉到目标行和列和上。def max_entropy(a, l): 最大熵闭式解X_ij a_i * l_j / L对角线置零 X np.outer(a, l) / a.sum() np.fill_diagonal(X, 0.0) return X def minimum_density(a, l, tol1e-10): 最小密度每步把剩余额度最大的行和列配对只产生必要的边 a, l a.astype(float).copy(), l.astype(float).copy() N len(a); X np.zeros((N, N)) for _ in range(4 * N): a_masked np.where(np.arange(N) -1, 0, a) i int(np.argmax(a)) l_probe l.copy() l_probe[i] -1.0 # 防止自环 j int(np.argmax(l_probe)) if a[i] tol or l[j] tol: break v min(a[i], l[j]) X[i, j] v a[i] - v; l[j] - v return X def ras(prior, a, l, tol1e-9, max_iter3000): RAS 迭代比例拟合把先验矩阵拉到目标行和与列和 X np.array(prior, dtypefloat) np.fill_diagonal(X, 0.0) for _ in range(max_iter): rs X.sum(axis1) X * np.where(rs tol, a / np.maximum(rs, tol), 1.0)[:, None] cs X.sum(axis0) X * np.where(cs tol, l / np.maximum(cs, tol), 1.0)[None, :] np.fill_diagonal(X, 0.0) if np.abs(X.sum(axis1) - a).max() tol: break return Xminimum_density里的l_probe[i] -1是避免把额度分给自己的技巧实际工程中更严谨的做法是维护一个候选列集合。ras收敛慢的原因是对角线每次都被强制归零破坏刚做好的行列平衡所以迭代次数上限给到 3000并把行和残差小于tol作为停止条件prior一般取最大熵解或均匀矩阵。3.3 别只看强度误差把局部聚类误差一起算进来传统评估只用相对强度误差 Σ|X̂−X| / ΣX这个指标对结构完全不敏感——最大熵解在这个指标上往往已经很漂亮但它是错的。必须把局部聚类误差、连边数一起放进评估表。方法强度相对误差二值局部聚类 MAE加权局部聚类 MAE连边数真值 312最大熵0.020.610.381560最小密度0.000.280.4178RAS以 ME 为先验0.020.580.351560具体数值随仿真参数浮动但量级关系稳定最大熵的强度误差只有 2%二值聚类误差却超过 0.6因为它把所有可能边都点亮了最小密度把强度误差压到 0代价是连边数只有真值的四分之一加权聚类误差反而更大。RAS 从最大熵出发不会改变结构只会等比例缩放权重所以聚类指标几乎不动。这条基线说明单靠行列和约束不可能同时对上强度和结构必须显式引入局部聚类目标。4. 把局部聚类特征嵌入重构目标函数4.1 目标函数交叉熵加局部聚类惩罚重构问题改写成带惩罚的优化问题在满足行和列和的前提下最小化相对先验矩阵 P 的交叉熵同时让每个节点的加权局部聚类系数逼近目标值 C_i^target。写成式子就是 min KL(X‖P) λ·Σ_i (C_i(X) − C_i^target)²s.t. X·1 aXᵀ·1 lX ≥ 0对角为 0。先验 P 取最大熵解交叉熵项保证在没有结构信息时退化成最大熵惩罚项 λ 控制结构约束的强度。目标值 C_i^target 的来源有三种一是从已观测的大额敞口算出的部分节点的真实聚类系数二是用银行规模、同业依赖度等协变量回归出来的拟合值三是把整个网络的平均聚类水平作为总量约束让所有节点共享一个目标。第一种最可靠但覆盖不全第三种最容易实现实践中常常混用。4.2 用 Sinkhorn 投影加自动微分求解惩罚项里含有三阶乘积求解析梯度非常繁琐用自动微分最省事。做法是把可优化的对数权重 Z 作为自由变量取指数得到正矩阵再用 Sinkhorn 迭代投影回满足行和列和的约束面上最后让 PyTorch 自动求导。import torch from local_clustering_torch import local_clustering_torch # 与 2.2 节同逻辑的张量实现 torch.manual_seed(0) N X_true.shape[0] mask 1.0 - torch.eye(N, dtypetorch.float64) a_t torch.tensor(a, dtypetorch.float64) l_t torch.tensor(l, dtypetorch.float64) P torch.tensor(max_entropy(a, l), dtypetorch.float64) C_target torch.tensor(target_c, dtypetorch.float64) # 目标聚类向量 lam, n_proj, lr 2.0, 60, 0.05 Z torch.log(P 1e-12).clone().requires_grad_(True) # 从最大熵解出发 def sinkhorn(M, a, l, mask, n_iter): 交替行缩放与列缩放把矩阵投影到给定行和列和的约束面上 X M * mask for _ in range(n_iter): X X * (a / (X.sum(1, keepdimTrue) 1e-12)) * mask X X * (l / (X.sum(0, keepdimTrue) 1e-12)) * mask return X opt torch.optim.Adam([Z], lrlr) for step in range(1500): opt.zero_grad() X sinkhorn(torch.exp(Z) * mask, a_t, l_t, mask, n_proj) kl (X * (torch.log(X 1e-12) - torch.log(P 1e-12))).sum() C local_clustering_torch(X) # 可导的加权聚类系数 loss kl lam * ((C - C_target) ** 2).mean() * N loss.backward() opt.step() if step % 300 0: print(step, float(loss), float(np.abs(X.sum(1).numpy() - a).max())) X_hat sinkhorn(torch.exp(Z) * mask, a_t, l_t, mask, 200).detach().numpy()这个结构有几个关键点。第一Z从log(P)初始化保证优化从一个已知合理的解出发否则 Sinkhorn 在前几步会震荡。第二约束不是软惩罚而是硬投影n_proj取 30 到 80 就足够把行和列和残差压到 1e-8 量级每次外层迭代都做一遍投影代价是计算量翻倍但换来精度。第三local_clustering_torch需要可导实现用torch.cbrt加einsum(ij,jk,ki-i)可以避免 Python 双重循环。第四学习率 0.05 的 Adam 配合 1500 步基本能收敛如果最终行和残差不为零说明投影步数不够而不是优化失败。注意三阶乘积在权重极小时梯度会趋近于零边缘节点的聚类系数几乎推不动。给权重加一个绝对下限如总量的 1e-6能让梯度保持活跃。4.3 λ 与软阈值温度的扫描惩罚权重 λ 是唯一真正需要调的参数。扫一遍就能看出典型的过拟合拐点λ 太小时退化回最大熵λ 太大时会为了凑三角形而给不该存在的边分配权重强度误差迅速抬头。λ强度相对误差二值聚类 MAE加权聚类 MAE现象00.020.610.38完全退化为最大熵0.50.030.300.21结构开始被拉回来20.050.190.16强度与结构较均衡80.110.170.22强度误差明显抬头320.260.150.34为凑三角形扭曲权重分布挑选原则很实在把强度相对误差的容忍上限定在 5%在这个约束下取聚类误差最小的 λ。上表里对应的是 λ 2。另一个隐性参数是软阈值温度用来把连续权重转成可导的软邻接矩阵取值太小梯度稀疏、太大则所有边权重趋同、聚类失去区分度一般取总量的千分之一到百分之一。5. 加权聚类、锚点校准与样本外验证5.1 用已观测的大额敞口当锚点纯总量约束下重构出的网络核心-核心那几条大边的权重往往被系统性低估因为它们被均摊进了最大熵的外积结构。实务里的做法是把监管报送或成交明细里已观测到的边固定住不参与 Sinkhorn 投影里的缩放只在未观测边上分配剩余额度。实现上给已观测边加一个掩码投影时对已知权重行列做修正先从未观测边里分配 a_i 减去已知行内敞口后的余量再把已知边加回去。这一步通常能把核心节点的加权聚类误差再压低三四成比调 λ 有效得多。5.2 用系统性风险排序做样本外验证聚类误差是结构指标未必等于业务指标。最终验证要落到重构网络的用途上算一遍 DebtRank 或级联违约的损失分布看重构网络给出的银行系统重要性排序与真值或与已知的监管名单是否一致。常用的度量是 Kendall τ 序相关系数τ 高于 0.7 基本可用低于 0.5 说明结构拟合得再好也白搭。这一步还能反过来指导目标聚类向量如果某些节点的排序总是错就针对它们单独提高 λ 权重。参数含义建议取值调整依据λ局部聚类惩罚权重0.5 ~ 5强度误差容忍 5% 前提下的最小值软阈值温度权重转邻接的平滑度总量的 1e-3 ~ 1e-2聚类系数的区分度是否还保留Sinkhorn 迭代数行列和投影强度30 ~ 80行和残差降到 1e-8 即可停锚点掩码已观测边固定权重前 10% 的边锚点越准λ 可以取得越小调参顺序建议固定为先用锚点把大边钉死再扫 λ 找到强度误差的拐点最后用系统性风险排序做交叉验证。顺序反了会陷入反复重调的死循环。本文还有配套的精品资源点击获取