STLSQとは何か?
アルゴリズム
STLSQ
フルランクの行列 $X \in \mathbb{R}^{m \times p}$ と $Y \in \mathbb{R}^{m \times n}$ が与えられたとき、割と スパース な $B \in \mathbb{R}^{p \times n}$ を見つけるスパース回帰問題を考えよう。STLSQsequentially Thresholded Least-Squares Algorithm は、「SINDy」Algorithm を提案したブルントンが使っていた方法で、名前の通り、 最小二乗法 を繰り返し利用し、各成分の 絶対値 が与えられたしきい値 $\lambda$ より下がったら $0$ として扱い、その列(独立変数) を排除する仕組みになっている。
擬似コード
共通して、括弧で囲まれた上付きのテキスト $\cdot ^{(k)}$ は $k$ 番目を、 $[p] = \left\{ 1 , \cdots , p \right\} \subset \mathbb{N}$ は $1$ から $p$ までの 自然数 の 集合 を示す。 $X^{\dagger}$ は行列 $X$ の 擬似逆行列 であり、最小二乗法 によって得られる。インデックス集合 $S$ に対して、 $\mathbf{x}_{S}$ はベクトル $\mathbf{x}$ の該当インデックスだけを残したベクトル、 $X_{S}$ は行列 $X$ の該当列だけを残した行列である。
アルゴリズム: STLSQ | ||
---|---|---|
入力 | しきい値 $\lambda \ge 0$ が与えられる。 | |
1. | 実行回数 $k := 0$、 $B^{(0)} := X^{\dagger} Y$ および $S^{(-1)} = \emptyset$ を初期化する。 | |
2. | $S^{(0)} := \left\{ S_{j}^{(0)} = \left\{ i \in [p] : \left| \left( B^{(0)} \right)_{ij} \right| > \lambda \right\} : j \in [n] \right\}$ | # $i \in S_{j} \subset S$ は $(B)_{ij} \ne 0$ と同じ意味だ。 |
3. | while $S^{(k)} \ne S^{(k-1)}$ do | |
4. | for $j \in [n]$ do | |
5. | $\widehat{X} = X_{S_{j}^{(k)}}$ | # 列 $B_{ij} = 0$ を削除 |
6. | $\left( B^{(k+1)} \right)_{S_{j}^{(k)}, \left\{ j \right\}} = \widehat{X}^{\dagger} Y_{\left\{ j \right\}}$ | # 列を一つずつ更新 |
7. | $S_{j}^{(k+1)} = \left\{ i \in [p] : \left| \left( B^{(k+1)} \right)_{ij} \right| > \lambda \right\}$ | |
8. | end for | |
9. | $k = k+1$ | |
10. | end while | |
11. | $i \in S_{j}^{(k)} \subset S^{(k)}$ を満たさない全ての $(i,j)$ に対して $B_{ij}^{(k)} = 0$ を適用する | |
出力 | 各成分の 絶対値 の大きさが $\lambda$ より大きい行列 $B^{(k)} = \argmin_{B} \left\| XB - Y \right\|$ を得る。 |
説明
特に STLSQ が 動的系 $\dot{\mathbf{x}} = f \left( \mathbf{x} \right)$ の微分係数から作られた行列 $Y = \dot{X}$ とライブラリ行列 $X = \Theta (X)$ に対して行われる場合、我々はそれを「SINDy」と呼ぶ1。一見するとSTLSQ自体がSINDyのために出たと言えるが、アルゴリズムの観点からはSTLSQ自体が動的系と全く関係なくそのまま作動できるため、「SINDy」という名前の代わりに言及されることもままある。
アルゴリズムの擬似コードを読めば分かるように、STLSQは一種の グリーディアルゴリズム だ。アルゴリズムの収束性が証明されたこと2とは別に、回帰分析の統計的側面から言えば正直、意味がないが、実装が簡単で収束速度が非常に速く、何はともあれ スパース回帰 を行うという点では疑いようのない有用な方法と言える。
コード
Matlab
以下は、ブルントン教授が自分の研究で使っている実際の Matlab コードだ3。
%% compute Sparse regression: sequential least squares
Xi = Theta\dXdt; % initial guess: Least-squares
% lambda is our sparsification knob.
for k=1:10
smallinds = (abs(Xi)<lambda); % find small coefficients
Xi(smallinds)=0; % and threshold
for ind = 1:n % n is state dimension
biginds = ˜smallinds(:,ind);
% Regress dynamics onto remaining terms to find sparse Xi
Xi(biginds,ind) = Theta(:,biginds)\dXdt(:,ind);
end
end
Python
def sparsifyDynamics(Theta,dXdt,lamb,n):
# Initial guess: Least-squares
Xi = np.linalg.lstsq(Theta,dXdt,rcond=None)[0]
for k in range(10):
smallinds = np.abs(Xi) < lamb # Find small coeffs.
Xi[smallinds]=0 # and threshold
for ind in range(n): # n is state dimension
biginds = smallinds[:,ind] == 0
# Regress onto remaining terms to find sparse Xi
Xi[biginds,ind] = np.linalg.lstsq(Theta[:,
biginds],dXdt[:,ind],rcond=None)[0]
return Xi
Julia
私が使っている Julia コードだ。
using SparseArrays
function STLSQ(Θ, dXdt, λ = 10^(-6))
if Θ isa AbstractSparseMatrix
Θ = Matrix(Θ)
end
Ξ = Θ \ dXdt
dim = size(Ξ, 2)
__🚫 = 0
while true
print(".")
🚫 = abs.(Ξ) .< λ
Ξ[🚫] .= 0
for j in 1:dim
i_ = .!🚫[:, j]
Ξ[i_, j] = Θ[:,i_] \ dXdt[:,j]
end
if __🚫 == 🚫 println("Stopped!"); break end # Early stopping
__🚫 = deepcopy(🚫)
end
println("MSE: ", sum(abs2, Θ * Ξ - dXdt) / length(dXdt))
return Ξ
end
Brunton. (2016). Discovering governing equations from data by sparse identification of nonlinear dynamical systems: https://doi.org/10.1073/pnas.1517384113 ↩︎
Zhang, L., & Schaeffer, H. (2019). On the convergence of the SINDy algorithm. Multiscale Modeling & Simulation, 17(3), 948-972. https://doi.org/10.1137/18M1189828 ↩︎
https://www.pnas.org/doi/suppl/10.1073/pnas.1517384113/suppl_file/pnas.1517384113.sapp.pdf ↩︎
Brunton. (2022). Data-Driven Science and Engineering : Machine Learning, Dynamical Systems, and Control(2nd Edition): p276. ↩︎