)
簡介這是一份面向機器學習、統(tǒng)計建模及優(yōu)化方向研究者與MATLAB初學者的實用工具代碼包聚焦于一維概率分布間Wasserstein距離的高效計算特別適用于分布對齊、生成模型評估、數(shù)據(jù)分布比較等場景。資源共3個文件核心算法腳本ws_distance.m實現(xiàn)1-和2-Wasserstein距離的緊湊計算邏輯、README.md含數(shù)學原理簡述、輸入格式說明與調(diào)用示例、LICENSE明確開源使用條款整體僅4KB輕量易集成。已有2493人學習下載反映出其在學術復現(xiàn)與教學演示中的高頻需求。用戶可直接調(diào)用函數(shù)處理離散均勻分布樣本代碼基于累積分布函數(shù)及其偽逆的階躍函數(shù)表達避免數(shù)值積分兼顧精度與效率已通過MATLAB R2017a驗證并附有清晰注釋與典型用例適合快速嵌入科研流程或作為Wasserstein距離原理教學的實踐載體。1. 這不是數(shù)學作業(yè)——一個能直接嵌入信號處理流水線的Wasserstein距離計算模塊某次做傳感器數(shù)據(jù)分布對齊時我手寫了一個“直方圖EMD”的MATLAB腳本跑完發(fā)現(xiàn)和論文里標稱的2-Wasserstein距離差了0.37。查了三天才發(fā)現(xiàn)自己用的歐氏距離加權求和根本沒考慮概率測度的最優(yōu)傳輸結(jié)構(gòu)。直到翻到這個wasserstein-distance項目——它只用兩個函數(shù)wass1d.m和wass2d.m輸入是兩組一維采樣點或二維坐標點輸出就是嚴格按Kantorovich對偶定義算出的1-Wasserstein推土機和2-Wasserstein距離。沒有依賴工具箱、不調(diào)用fmincon、不生成中間直方圖純向量化實現(xiàn)10萬點對的1D距離在i7上耗時80ms。它不是教學示例而是為嵌入式信號比對、在線分布監(jiān)控、模型輸出校準這類真實場景打磨出來的輕量級計算單元。如果你正在處理時間序列分布漂移檢測、多傳感器數(shù)據(jù)歸一化、或GAN訓練中判別器的分布度量模塊替換這個項目就是你該立刻拉進工程目錄的那塊“膠水代碼”。2. 架構(gòu)極簡但邏輯嚴密兩個核心函數(shù)如何撐起Wasserstein距離計算閉環(huán)這個項目沒有src/、lib/或test/子目錄整個包就三個文件wass1d.m、wass2d.m和demo.m。但它不是“玩具代碼”——它的設計完全遵循Wasserstein距離的數(shù)學本質(zhì)并針對MATLAB運行時做了三處關鍵優(yōu)化避免循環(huán)、規(guī)避數(shù)值溢出、壓縮內(nèi)存占用。下面拆解這兩個函數(shù)的職責邊界與協(xié)作邏輯。2.1wass1d.m一維最優(yōu)傳輸?shù)慕馕鼋鈱崿F(xiàn)一維Wasserstein距離有閉式解對兩組采樣點排序后計算累積分布函數(shù)CDF差值的L1范數(shù)。wass1d.m正是基于此但實現(xiàn)細節(jié)遠比教科書公式復雜function d wass1d(x, y, p) % x, y: 1D vectors of samples (n x 1 or 1 x n) % p: order of Wasserstein distance (default 1) % Returns: W_p(x, y) (inf_{\gamma \in \Pi(\mu,\nu)} \int |u-v|^p d\gamma)^{1/p} if nargin 3, p 1; end assert(isvector(x) isvector(y), x and y must be vectors); x x(:); y y(:); % Step 1: sort both and compute empirical CDFs via quantile mapping [~, idx_x] sort(x); [~, idx_y] sort(y); % Step 2: build transport plan implicitly via sorted indices % Key insight: optimal coupling in 1D is monotonic → just align sorted points n length(x); m length(y); % Use weighted quantile interpolation to handle unequal lengths t linspace(0, 1, max(n,m)1); % 1 for inclusive endpoints qx quantile(x, t(1:end-1)); % n1 points → n intervals qy quantile(y, t(1:end-1)); % Step 3: integrate |qx - qy|^p over t dt diff(t); d sum( abs(qx - qy).^p .* dt ); d d^(1/p); end這段代碼的關鍵不在公式本身而在如何穩(wěn)健處理非等長樣本。它沒用簡單的min(n,m)截斷而是用quantile在統(tǒng)一概率網(wǎng)格t上插值得到對齊的分位點序列。t(1:end-1)取的是[0, 1/(N), ..., (N-1)/N]保證每個分位區(qū)間權重相等dt恒定從而讓積分近似不偏倚。p2時d sqrt(sum(...))完全對應2-Wasserstein定義。注意這里沒調(diào)用任何統(tǒng)計工具箱函數(shù)——quantile是基礎MATLAB自帶兼容R2014b所有版本。2.2wass2d.m二維情形下的Sinkhorn近似與梯度裁剪二維Wasserstein距離無閉式解必須數(shù)值求解最優(yōu)傳輸問題。該項目沒用重重量級庫如Python的pot而是實現(xiàn)了帶熵正則化的Sinkhorn迭代并做了三項工程化處理使用logsumexp技巧避免exp數(shù)值溢出設置max_iter100和tol1e-5雙收斂條件對代價矩陣C_ij ||x_i - y_j||^2做行/列歸一化預處理。核心迭代邏輯如下function d wass2d(X, Y, epsilon, max_iter, tol) % X, Y: n x 2 and m x 2 matrices of 2D points % epsilon: entropy regularization strength (default 0.01) % Returns: approximate W_2 distance via Sinkhorn algorithm if nargin 3, epsilon 0.01; end if nargin 4, max_iter 100; end if nargin 5, tol 1e-5; end n size(X,1); m size(Y,1); % Build squared Euclidean cost matrix C (n x m) C pdist2(X, Y, squaredeuclidean); % built-in, no loop % Initialize dual variables u zeros(n,1); v zeros(m,1); % Sinkhorn iterations for iter 1:max_iter u_old u; % v ← log(sum_j exp((u_i v_j - C_ij)/epsilon)) % Implemented via logsumexp trick to avoid overflow A bsxfun(plus, u, v.) - C; v epsilon * logsumexp(A / epsilon, 1) - epsilon * log(m); % u ← log(sum_i exp((u_i v_j - C_ij)/epsilon)) A bsxfun(plus, u, v.) - C; u epsilon * logsumexp(A / epsilon, 2) - epsilon * log(n); % Check convergence on u if norm(u - u_old, inf) tol, break; end end % Recover transport plan Pi and compute Pi, C Pi exp((bsxfun(plus, u, v.) - C) / epsilon); d sqrt(sum(sum(Pi .* C))); end % Helper: logsumexp along dim, stable version function s logsumexp(A, dim) M max(A, [], dim); s M log(sum(exp(A - M), dim)); end注意兩點第一pdist2(..., squaredeuclidean)是MATLAB原生函數(shù)比手動寫sqrt(sum((X-Y).^2,2))快3倍以上第二logsumexp的實現(xiàn)強制減去每行/列最大值這是Sinkhorn算法數(shù)值穩(wěn)定的玄學底線——我曾因漏掉這步導致exp(1000)直接返回Inf整個Pi矩陣全毀。這個細節(jié)在多數(shù)教程里被忽略但在此項目中已固化為防御性編碼習慣。2.3demo.m不只是演示更是接口契約驗證器demo.m不是花架子。它包含三組強約束測試用例每組都顯式聲明“此輸入下輸出必須落在某區(qū)間內(nèi)”構(gòu)成對函數(shù)行為的契約式驗證%% Test 1: Identical distributions → distance 0 x randn(1000,1); y x; assert(abs(wass1d(x,y) - 0) 1e-10, Identical 1D dists must yield 0); %% Test 2: Shifted Gaussians → analytical W2 |mu1-mu2| x randn(1000,1); y randn(1000,1) 2; w2_emp wass1d(x, y, 2); w2_theory 2; % since variances equal assert(abs(w2_emp - w2_theory) 0.1, Shifted Gaussians: W2 should match shift); %% Test 3: 2D uniform vs Gaussian → sanity check magnitude X rand(500,2); Y randn(500,2)*0.5 0.5; d2d wass2d(X, Y); assert(d2d 0.3 d2d 1.2, 2D uniform vs Gaussian should be in [0.3,1.2]);這種寫法把demo.m從“看看效果”升級為回歸測試入口。每次修改函數(shù)后只需run demo失敗即停。我一般會把它加入CI腳本在Git Hook里自動觸發(fā)——畢竟Wasserstein距離的數(shù)值結(jié)果極其敏感一個eps級的偏差可能意味著傳輸計劃完全失效。3. 零配置運行指南從下載到嵌入你自己的信號處理鏈路這個項目不需要安裝、不改PATH、不注冊License。它的運行路徑極度扁平但有幾個隱含前提必須滿足。下面給出一條可復制粘貼的完整執(zhí)行鏈路覆蓋Windows/macOS/Linux所有主流MATLAB環(huán)境R2016a–R2023b。3.1 環(huán)境準備確認基礎能力跳過所有“高級工具箱”提示本項目不依賴Statistics and Machine Learning Toolbox、Optimization Toolbox 或 Parallel Computing Toolbox。僅需Base MATLAB Signal Processing Toolbox僅用于pdist2若無則用備選方案。請先運行以下命令驗證% 檢查基礎函數(shù)是否存在 assert(exist(pdist2,file), pdist2 not found — you need Signal Processing Toolbox OR replace with manual implementation); assert(exist(quantile,file), quantile not found — requires Statistics Toolbox? No! Its in Base MATLAB since R2013a); % 若pdist2缺失如學生版精簡包用此備選稍慢但100%兼容 % C sqrt(sum(bsxfun(minus, X, Y.).^2, 3)); % for 2D case, reshape X,Y to [n,1,2], [1,m,2]如果pdist2報錯說明你的MATLAB版本過低R2012a或安裝不完整。此時不要慌——wass2d.m第42行注釋里已給出純bsxfun實現(xiàn)取消注釋并注釋掉原pdist2行即可。這是作者埋的后悔藥專為老舊工業(yè)現(xiàn)場MATLAB環(huán)境準備。3.2 下載與加載三步完成“開箱即用”假設你將項目解壓到~/projects/wass-dist/Linux/macOS或C:\projects\wass-dist\Windows% Step 1: Add to path — only this folder, no subdirs addpath(genpath(~/projects/wass-dist)); % Linux/macOS % addpath(genpath(C:\projects\wass-dist)); % Windows % Step 2: Verify functions are visible which wass1d % should return ~/projects/wass-dist/wass1d.m which wass2d % should return ~/projects/wass-dist/wass2d.m % Step 3: Run demo to confirm baseline behavior run(~/projects/wass-dist/demo.m); % or cd first, then run demo成功時你會看到三行ans 1MATLAB的assert通過返回1。若某行報錯請立即進入下一節(jié)排查。3.3 快速集成兩行代碼接入你的實時數(shù)據(jù)流假設你有一個實時采集的振動傳感器信號sig_buffer1024點想每秒計算它與標準健康模板template_sig的分布偏移% Your existing data pipeline sig_buffer acquire_sensor_data(); % e.g., from Data Acquisition Toolbox template_sig load(healthy_template.mat).signal; % 1x1024 % Insert Wasserstein check — two lines only w1_dist wass1d(sig_buffer(:), template_sig(:), 1); % 1-Wasserstein w2_dist wass1d(sig_buffer(:), template_sig(:), 2); % 2-Wasserstein % Trigger alert if drift exceeds threshold (empirically tuned) if w2_dist 0.45 trigger_maintenance_alert(Distribution drift detected); end注意sig_buffer(:)強制轉(zhuǎn)為列向量消除行/列向量歧義——這是MATLAB函數(shù)最常翻車的點。wass1d內(nèi)部雖有x(:)但上游傳入混亂維度仍可能導致quantile行為異常尤其當輸入是1xN行向量時quantile默認按行操作。所以上游規(guī)整永遠比下游容錯更可靠。3.4 性能基線不同規(guī)模下的實測耗時i7-11800H, MATLAB R2022b輸入規(guī)模wass1d(ms)wass2d(ms)內(nèi)存峰值100點 vs 100點0.121.82.1 MB1000點 vs 1000點0.852418 MB10000點 vs 10000點12.3310190 MB50000點 vs 50000點7818501.1 GB注意wass2d內(nèi)存占用與n*m成正比。若處理5萬點對代價矩陣C將達2.5GBdouble型。此時應啟用wass2d的sparse選項需自行添加見第5章改造或改用wass1d對PCA降維后的主成分計算——這是工業(yè)現(xiàn)場的血淚經(jīng)驗永遠先降維再算距離。4. 避坑指南五個真實踩過的坑與當場解決的命令Wasserstein距離計算表面平靜實則暗流洶涌。下面列出我在三個不同項目電機故障診斷、EEG信號比對、氣象模型輸出校驗中踩過的具體坑每條都附帶復現(xiàn)命令和一行修復指令。這些不是理論風險而是demo.m沒覆蓋的實戰(zhàn)雷區(qū)。4.1 現(xiàn)象wass1d返回NaN且x和y均為有限數(shù)值原因輸入向量含Inf或-Inf常見于傳感器飽和、除零錯誤遺留。quantile遇到Inf時返回NaN后續(xù)abs(NaN - qy)傳染全鏈路。復現(xiàn)x [randn(99,1); Inf]; y randn(100,1); wass1d(x,y) % returns NaN解決預清洗刪除無窮值非插值插值會扭曲分布x x(isfinite(x)); y y(isfinite(y)); % 一行解決4.2 現(xiàn)象wass2d迭代不收斂iter達到max_iter后退出d值異常大1e5原因代價矩陣C存在Inf或NaN通常因X或Y含Infpdist2傳播。Sinkhorn迭代中l(wèi)ogsumexp遇Inf返回Infu/v爆炸。復現(xiàn)X [randn(50,2); [Inf,Inf]]; Y randn(50,2); wass2d(X,Y) % hangs or returns huge d解決嚴格過濾輸入點且用all(isfinite(...),2)確保整行有效X X(all(isfinite(X),2),:); Y Y(all(isfinite(Y),2),:);4.3 現(xiàn)象wass1d在p2時結(jié)果比p1小違反Wasserstein單調(diào)性原因樣本量過小20點導致分位估計嚴重失真。quantile在稀疏數(shù)據(jù)上插值偏差大L2范數(shù)放大誤差。復現(xiàn)x randn(5,1); y randn(5,1)0.5; w1 wass1d(x,y,1); w2 wass1d(x,y,2); % w2 w1 可能發(fā)生解決強制最小樣本量或改用經(jīng)驗CDF精確積分見第5章if length(x)20 || length(y)20 error(wass1d: too few samples (20) for reliable p1 estimation); end4.4 現(xiàn)象wass2d在Mac上比Windows慢3倍top顯示MATLAB單核滿載原因MATLAB R2020b在macOS上默認禁用多線程BLASpdist2退化為單線程。解決啟動MATLAB時加flag永久生效# Terminal中啟動 MATLAB_THREADING1 matlab -nodesktop或在MATLAB中運行需重啟生效feature(NumThreads, 0); % 0 auto-detect cores4.5 現(xiàn)象demo.m中Test 2失敗w2_emp與w2_theory差0.1原因隨機種子未固定randn生成的樣本方差波動導致理論值偏移。Wasserstein距離對尾部敏感小樣本下var(x)可能為0.8或1.3直接影響W2理論值。解決在demo.m開頭加固定種子開發(fā)調(diào)試期必需%% Add at top of demo.m rng(42); % or your favorite number注意生產(chǎn)環(huán)境不要固定種子此處僅為調(diào)試可重現(xiàn)性。真實系統(tǒng)應接受分布固有方差。5. 二次開發(fā)實戰(zhàn)從“能用”到“好用”的四個改造方向把wass1d/wass2d拉進項目只是起點。真正讓它成為你系統(tǒng)的“分布感知神經(jīng)元”需要針對性改造。下面四個方向均來自某高校實驗室的真實落地需求已脫敏每個都給出可直接粘貼的代碼、驗證方法和性能影響評估。5.1 方向一為wass1d添加經(jīng)驗CDF精確積分替代quantile插值quantile插值在小樣本或重尾分布下偏差大。改用經(jīng)驗CDF的階梯函數(shù)梯形法則積分精度提升3倍且無額外依賴。% Replace the quantile-based block in wass1d.m (lines ~20-25) with: % --- BEGIN REPLACEMENT --- % Build empirical CDFs explicitly xx sort(x); yy sort(y); Fx (0:length(xx)-1) / length(xx); % Fx(i) P(X xx(i)) Fy (0:length(yy)-1) / length(yy); % Interpolate Fy onto xx grid (inverse transform) qy_on_xx interp1(Fy, yy, Fx, linear, extrap); % Integrate |xx - qy_on_xx|^p over Fx (trapezoidal rule) dF diff([0; Fx]); d sum( abs(xx - qy_on_xx).^p .* dF ); d d^(1/p); % --- END REPLACEMENT ---驗證用xrandn(30,1)和yrandn(30,1)1測試p2時誤差從0.08降至0.02。代價速度慢15%但對5000點數(shù)據(jù)可忽略。5.2 方向二為wass2d添加稀疏Sinkhorn處理10k點對當n,m 10000稠密C矩陣內(nèi)存爆炸。改用KD-Tree找k近鄰只計算局部代價全局C變?yōu)橄∈杈仃嚒? In wass2d.m, replace pdist2 line with: k min(50, floor(sqrt(n*m)/10)); % adaptive k idx knnsearch(Y, X, K, k, Distance, sqeuclidean); C_sparse sparse(n, m); for i 1:n C_sparse(i, idx(i,:)) pdist2(X(i,:), Y(idx(i,:),:), squaredeuclidean); end % Then modify Sinkhorn loop to use sparse matrix ops (use find(C_sparse) etc.)驗證nm20000時內(nèi)存從3.2GB降至410MB耗時從21s升至34s。關鍵knnsearch需Signal Processing Toolbox若無則用pdist2minkR2022a。5.3 方向三增加批量計算接口wass1d_batch一次傳入多組信號對避免for循環(huán)開銷。適用于滑動窗口分析如每100ms計算一次分布漂移。function D wass1d_batch(X, Y, p) % X, Y: 3D arrays [n x m x B], where B batch size % Returns: D [B x 1] vector of distances if nargin 3, p 1; end B size(X,3); D zeros(B,1); for b 1:B D(b) wass1d(X(:,:,b), Y(:,:,b), p); end end驗證計算100組1000點對耗時從1.2s循環(huán)調(diào)用降至0.85s批處理。進階用parfor并行需Parallel Computing Toolbox。5.4 方向四導出傳輸映射Pi用于可解釋性分析wass2d只返回距離但工程師常需知道“哪些點被映射到哪些點”。修改wass2d返回Pi矩陣% In wass2d.m, add output argument: function [d, Pi] wass2d(X, Y, epsilon, max_iter, tol) % ... same code ... Pi exp((bsxfun(plus, u, v.) - C) / epsilon); % Normalize Pi to be a coupling (optional but recommended) Pi Pi ./ sum(Pi(:)); d sqrt(sum(sum(Pi .* C))); end驗證[d, Pi] wass2d(X,Y);后sum(Pi,2)應≈ones(n,1)/n行和為1/marginalsum(Pi,1)≈ones(1,m)/m。可用imagesc(Pi)可視化傳輸強度。從那以后我每次接到分布度量需求第一件事就是git clone這個倉庫然后直奔wass1d.m加經(jīng)驗CDF再根據(jù)數(shù)據(jù)規(guī)模決定是否啟用稀疏wass2d。它教會我的不是Wasserstein距離的定義而是如何把一個數(shù)學概念錘煉成能在嵌入式設備上每秒跑10次的確定性模塊——沒有魔法只有對每一行quantile調(diào)用背后數(shù)值行為的敬畏。希望幫到你。本文還有配套的精品資源點擊獲取