alitrack

duckdb-luajit 加矩阵计算,SQL 里做 SVD 特征分解

数据分析绕不开矩阵。

推荐系统要算用户-物品相似度,降维要算协方差,图像处理要算变换,回归要算最小二乘——这些的底层都是同一件事:矩阵分解。SVD、特征值、QR、Cholesky,一串名字,全是线代课本里的老熟人。

可 SQL 里没有这些。DuckDB 的 SQL 再强,SELECT 也算不出一组特征值。以前的办法:导出数据给 Python,调 numpy.linalg,算完再导回来。

我在 duckdb-luajit 里塞了一个 LuaJIT 运行时,SQL 能直接跑 Lua。这次给 libs 仓库加的新成员是 linalg:一个纯 Lua 胶水,FFI 直调系统自带的 OpenBLAS——也就是 LAPACK 的实现。

LAPACK 你可能没听过,但它的用户你天天见。MATLAB、R、numpy 的矩阵分解,底层全是它。40 年的工业标准,Fortran 写的老祖宗,至今没人能替代。

整条链路长这样:

duckdb-luajit linalg 架构

duckdb-luajit linalg 架构

装法还是一样,一条 SQL:

LOAD luajit;
SELECT * FROM luajit_module(mode := 'install', sql_name := 'linalg');

然后矩阵乘法:

SELECT luajit_s('linalg', {'op':'matmul', 'a':[1,2,3,4], 'm':2, 'n':2,
                           'b':[5,6,7,8], 'mb':2, 'nb':2});

返回:

{"c":[19,22,43,50]}

[[1,2],[3,4]] 乘 [[5,6],[7,8]],等于 [[19,22],[43,50]]。手算对得上。

SVD 也一行:

SELECT luajit_s('linalg', {'op':'svd', 'a':[1,2,3,4], 'm':2, 'n':2});
-- → {"s":[5.465,0.366],"u":[...],"vt":[...]}

奇异值 5.465 和 0.366,和 numpy.linalg.svd 逐位一致。

现在有的算子:matmul(乘法)、svd(奇异值分解)、eigh(对称特征值)、inv(求逆)、lu(LU 分解)、chol(Cholesky)、qr(QR 分解)、norm(范数)。够把大多数线代作业在 SQL 里做完了。

验证我做了 10 个锚定用例,全部和手算或解析解对得上。其中有个 3×2 的非方阵 SVD,专门用来验证 U·Σ·Vᵀ 能还原原矩阵——这个用例后来救了我一次。

踩坑最有意思。LAPACK 是 Fortran 写的,矩阵按列主序存,和 C/Python 习惯的行主序正好相反。第一次写 SVD 忘了转,2×2 的测试居然通过了——因为 A 和 Aᵀ 的奇异值恰好相同,转没转结果一样。直到加了 3×2 的非方阵用例,才把这个 bug 炸出来。教训:锚定用例必须覆盖非方阵。

这套东西在 GitHub 上,duckdb-luajit-libs 仓库,install 一条 SQL 就能用。MIT 协议,随便用。

SQL 里做数值计算,地基已经铺好了。矩阵分解有了,下一步 PCA、推荐系统的协同过滤,都能直接在 SQL 里搭。你平时会在 SQL 里算矩阵吗?评论区聊聊。

● ● ●

参考来源

  1. 1.
    LAPACK(Linear Algebra PACKage,40 年工业标准):netlib.org/lapack
  2. 2.
    OpenBLAS(LAPACK 的高性能实现,系统自带):github.com/OpenMathLib/OpenBLAS
  3. 3.
    duckdb-luajit 扩展:github.com/alitrack/duckdb-luajit
  4. 4.
    duckdb-luajit-libs 库仓库(linalg 在此):github.com/alitrack/duckdb-luajit-libs
  5. 5.
    numpy.linalg.svd 文档(对拍基准):numpy.org/doc/stable/reference/generated/numpy.linalg.svd.html