你有张量寄
O. Notation
-
向量用小写粗体表示:\(\b a\)。其中的成员如 \(a_i\)。
-
矩阵用大写粗体表示:\(\b A\)。其中的成员如 \(a_{i,j}\)。
-
更高维张量用手写粗体表示:\(\bc X\)。其中的成员如 \(x_{i,j,k}\)。
-
大写字母 \(N\) 尽量用于且仅用于表示高维张量的维数。
-
元素列用上标表示:\(\b A^{(n)}\)。
-
下标默认 1-indiced。
-
高维张量可以通过固定下标得到低维张量:如 \(\b A\) 的第 \(i\) 行是 \(\b a_{i,:}\),第 \(j\) 列是 \(\b a_{:,j}\)。因为默认向量是列向量,所以也可以简写为 \(\b a_j\)。
-
更高维张量的 1D 下标提取的专有名词是 纤维 (fiber),例如 \(\b x_{:,j,k}\) 或 \(\b x_{i,:,k}\)。纤维默认是列向量。
-
2D 下标提取的专有名词是 切片 (slices)。
-
范数默认是 Euclidean 范数
\[\|\bc X\|=\sqrt{\sum_{i_1=1}^{I_1}\dots\sum_{i_N=1}^{I_N}x_{i_1,\dots,x_N}^2} \]1D 时是 \(\ell^2\) 范数,2D 时是 Frobenius 范数。内积是范数诱导内积。
-
秩一张量 是可以被写成 1D 向量外积的张量:
\[\bc X=\b a^{(1)}\circ\b a^{(2)}\circ\dots\circ\b a^{(N)} \] -
张量被称作 立方 (cubical) 的,如果每一维下标范围均相同。在此基础上是 对称 或称 超对称 的,如果关于所有下标排列取值相同。对称也可以仅限于某几个维度。
-
张量被称作 对角 (diagonal) 的,如果只在所有下标全部相同的位置非零。
-
矩阵化 (matricization):把张量拍扁。存在更一般的拍扁方法,不过此处只考虑拍扁为矩阵的方法,则保留第 \(n\) 维的所有纤维为列向量的矩阵化记作 \(\b X_{(n)}\)。拍扁的顺序并不重要——只要它是自洽的。
-
张量-矩阵乘法:只考虑张量在第 \(n\) 维上乘以矩阵,也即对于 \(\bc X\in\R^{I_1\times\dots\times I_N}\) 和矩阵 \(\b U\in\R^{J\times I_n}\),定义
\[(\bc X\times_n\b U)_{i_1,\dots,j,\dots,i_N}=\sum_{i_n=1}^{I_n}x_{i_1,\dots,x_n}u_{j,i_n} \]得到的是 \((I_1,\dots,J,\dots,I_N)\) 维矩阵。写成矩阵化形式即为
\[\bc Y=\bc X\times_n\b U\iff\b Y_{(n)}=\b U\b X_{(n)} \]不同维上的乘法有交换律:
\[\bc X\times_m\b A\times_n\b B=\bc X\times_n\b B\times_m\b A\quad(n\neq m) \]同一维上的乘法有结合律:
\[\bc X\times_n\b A\times_n\b B=\bc X\times_n(\b B\b A) \] -
张量-向量乘法:乘完后第 \(n\) 维会 collapse 掉,得到低一阶的张量,记作 \(\bc X\btimes_n\b v\)。
-
矩阵的 Kronecker 积:定义在 \(\b A\in\R^{I\times J}\) 和 \(\b B\in\R^{K\times L}\) 之间。
\[\b A\otimes\b B=\bmat{a_{1,1}\b B&\dots&a_{1,J}\b B\\\vdots&\ddots&\vdots\\a_{I,1}\b B&\dots&a_{I,J}\b B} \\=\bmat{\b a_1\otimes\b b_1&\dots&\b a_1\otimes\b b_L&\dots&\b a_J\otimes\b b_L} \] -
矩阵的 Khatri-Rao 积:定义在 \(\b A\in\R^{I\times K}\) 和 \(\b B\in\R^{J\times K}\) 之间。
\[\b A\odot\b B=\bmat{\b a_1\otimes\b b_1&\dots&\b a_K\otimes\b b_K} \] -
矩阵的 Hadamard 积:用 \(\b A*\b B\) 表示。
I. CP Decomposition
CP 分解:把高阶张量分解为秩一张量和的一种方法
如果选择归一化并带上权重 \(\b\lambda\),则可以记作
此时有
其中 \(\b\Lambda=\t{diag}(\b\lambda)\) 是 \(\b\lambda\) 的对角矩阵。
需要注意的是,CP 分解仅仅是一套框架,有很多种具体的分解方式(例如 2D 时的奇异值分解和秩分解)都服从该格式。
I. Tensor Rank
定义:张量的 秩,记作 \(\t{rank}(\bc X)\),是其能写成秩一张量和的最小值;满足 \(r=\t{rank}(\bc X)\) 的 CP 分解称为 秩分解。
特别地,就算是实张量,其实秩和复秩也可能不同,这一点与矩阵的场合不一样。而且,求解张量秩是一个 NP-Hard 问题,最常见的方法是往各种 CP 分解模型上套。
在矩阵的场合,\(I\times J\) 矩阵的 最大秩(所有矩阵中秩最大的)是 \(\min(I,J)\),而 典型秩(Lebesgue 测度意义下 non-negligible 的秩)同样是 \(\min(I,J)\)——换言之,低秩矩阵是零测的。
然而,在张量的场合,情况则不然:
- 最大秩往往大于典型秩。
- 在复秩的场合,典型秩是唯一的;然而实秩的典型秩常常非唯一。
- 例如,\(2\times2\times2\) 的正态张量中,\(2\)-秩张量占比 \(79\%\) 而 \(3\)-秩张量占比 \(21\%\);\(1\)-秩张量是零测的。
最大秩必然不超过 \(\min_i(\prod I_n/I_i)\),但这是唯一通用的结果。施加某些特定限制后(比如说特定形状、特定对称性)可以有更紧的界。
特别地,对称张量 \(\bc X\in\R^{I\times\dots\times I}\) 的 CP 分解的所有 \(\b A^{(n)}\) 全部相同,且其复秩 w.p.\(1\) 为
除了某几个特定反例外。
特别地,张量的秩分解常常是唯一的(而矩阵则不然)。例如,考虑 SVD
随便选一个正交矩阵 \(\b W\) 就可以构造出 \(\b A=\b U\b\Sigma\b W,\b B=\b V\b W\) 等一系列本质不同的秩分解。
然而,张量秩分解常常关于列重排与积为 \(1\) 的标量缩放意义下是唯一的。形式化地,定义矩阵的 \(k\)-秩为最大的使得任意 \(k\) 列都线性无关的 \(k\),则存在唯一分解的充分条件是
特别地,其在 \(N=2,N=3\) 时同样是必要的。
II. Low Rank Approximations
矩阵场景,Eckart-Young 定理 保证了 SVD 是 Frobenius 范数意义下最佳的低秩逼近。然而,张量场景则不然:存在三阶张量的 \(1\)-秩逼近和 \(2\)-秩逼近毫无关系。推论是低秩逼近的所有项必须同步求出,而不能逐步求解。
事实上,最优的低秩逼近甚至不一定存在。称一个张量是 退化 (degenerate) 的,如果它能被低秩分解任意逼近(但代价是分解中某些项的范数任意大,另一些项的范数任意小)。
这直接导出结论:Euclidean 范数意义下,极限的秩可能大于序列秩的上确界;以及对应的 边界秩 (border rank) 概念,即任意逼近所需的最少秩一矩阵数。
III. CP Algorithms
虽然以上传达了很多悲观结论(求 CP 是 NP-Hard 的;可能存在任意接近的低秩逼近),但仍然可以设计好的 CP 算法。
考虑一个 3D 张量 \(\bc X\in\R^{I\times J\times K}\) 场景,目标是求出 \(R\)-秩最优逼近 \(\hat{\bc X}=[\b\lambda;\b A,\b B,\b C]\)。考虑一个 coordinate descent 算法,每次固定所有其它成员,优化剩下的一个成员。
形式化地,例如固定 \(\b B,\b C\) 优化 \(\b A\) 时,目标就是
其中 \(\hat{\b A}\) 被解释为 \(\b A\t{diag}(\b\lambda)\)。则最优解可以用 MP 逆显式写出来,为
特别地,Khatri-Rao 积的 MP 逆有展开形式,因此可以只用对 \(R\times R\) 矩阵求 MP 逆。求出最优 \(\hat{\b A}\) 后,即可归一化得到 \(\b A\) 与 \(\b\lambda\)。
多维的场合同样可以遵循这一套 Coordinate Descent 方法。
这个称为 Alternating Least Squares (ALS) 的方法听起来很对,但是有种种缺陷:
- 收敛需要很多迭代。
- 并不保证收敛到全局最优解,甚至不保证收敛到鞍点(Coordinate Descent 的通病)。
- 最终解高度依赖于初值。
因此有一系列改进方法,略过不表。
II. Tucker Decomposition
Tucker 分解:另一种分解方式。对于 \(\bc X\in\R^{I_1\times\dots\times I_N}\) 和 \(\bc G\in\R^{J_1\times\dots\times J_N}\) 以及 \(\b A^{(n)}\in\R^{I_n\times J_n}\),有
CP 分解是 \(\bc G\) 为对角立方张量时的特例。
Tucker 分解写成纤维形式是
可以发现把 \(\b G_{(n)}\) 换成 \(\b\Lambda\) 就是 CP 分解的纤维形式。
特别地,矩阵 \(\bc G\) 被称作 Tucker 分解的 核心张量 (core tensor)。
I. Tucker Algorithms
定义:\(\bc X\) 的 \(n\)-秩为 \(\t{rank}_n(\bc X)=\t{rank}(\b X_{(n)})\)。若令 \(R_n=\t{rank}_n(\bc X)\),则可以称 \(\bc X\) 是一个 秩-\((R_1,\dots,R_N)\) 张量。
对于 \(R_n=\t{rank}_n(\bc X)\),总是存在核心矩阵形状为 \((R_1,\dots,R_N)\) 的 Tucker 分解;而如果在某一维上 \(R_n<\t{rank}_n(\bc X)\),则该 Tucker 分解总是不精确的,也即 截断 Tucker 分解。一个求 \((R_1,\dots,R_N)\)-Tucker 分解的方法为 Higher-Order SVD (HOSVD):
- 令 \(\b A^{(n)}\) 为 \(\b A_{(n)}\) 的前 \(R_n\) 个左奇异向量。
- 令 \(\bc G=\bc X\times_1(\b A^{(1)})^\top\times\dots\times_N(\b A^{(N)})^\top\)。
只要全体 \(R_n\geq\t{rank}_n(\bc X)\),则这个分解就是精确的;但是和 SVD 不同,它不提供最优近似。
如果要获取最优近似,则等效于求以下优化问题:
而它可以直接脱离张量语境写成矩阵形式
由最小二乘法,在固定全体 \(\b A\) 时,可以显式写出最优的 \(\bc G\),反向还原回张量形式就是
此时有
于是问题等效于最大化 \(\|\bc G\|\)。在固定除 \(\b A^{(n)}\) 外的全体 \(\b A\) 时,可以令
则有
于是显式写出最优的 \(\b A^{(n)}\) 即为 \(\b W\) 的左奇异向量。或者写成张量形式就是
然后 \(\b A^{(n)}\) 是 \(\b Y_{(n)}\) 的左奇异向量。
总结为以下的 Higher-Order Orthogonal Iteration (HOOI) 算法:
- 使用 HOSVD 初始化。
- 每次选一维 \(n\) 计算 \(\bc Y\) 然后用 \(\b Y_{(n)}\) 的左奇异向量更新 \(\b A^{(n)}\)。
- 迭代充分多次后终止。
和 ALS 一样,它不保证收敛到全局最小值甚至鞍点。
II. Non-Uniqueness
Tucker 分解显然不唯一,一个最简单的修正就是给 \(\b A^{(n)}\) 和 \(\bc G\) 同时乘以一个 \(R_n\times R_n\) 的可逆矩阵。
但是反过来说,给定一个 Tucker 分解,我们可以尝试对 \(\bc G\) 进一步化简,以解耦其相互作用或减少存储空间。
在 2D 场景,我们会试图把 \(\b G\) 对角化(这也是为什么我们不用 Tucker 分解在 2D 时的对应物:把 \(\b G\) 对角化就得到了 SVD);然而张量场景,\(\bc G\) 一般是没法对角化的。取而代之的是,有一些把 \(\bc G\) 尽量多的位置消成零或很小值的方法。
III. Einsum
不论是张量-矩阵乘法还是张量-向量乘法,都仅仅是一批特殊的张量-张量乘法。来看看 Einstein 求和约定。
经典约定为:
- 等式两侧各出现一次的指标是 自由指标 (free index),作为输出。
- 等式某侧连续出现两次的指标是 哑指标 (dummy index),遍历并求和。
例:
- 矩阵乘法:
- 传统写法:\(C_{i,j}=\sum_k A_{i,k}B_{k,j}\)。
- Einsum 写法:\(C_{i,j}=A_{i,k}B_{k,j}\)。
- 代码写法:
ik, kj -> ij。
- 批量矩阵乘法:
- 传统写法:\(C_{b,i,j}=\sum_k A_{b,i,k}B_{b,k,j}\)。
- Einsum 写法:
- \(\forall b,(C_b)_{i,j}=(A_b)_{i,k}(B_b)_{j,k}\)。
- \(C_{b,i,j}=\delta_{b,u,v}A_{u,i,k}B_{v,k,j}\)。
- 广义 Einsum 写法:\(C_{b,i,j} = A_{b,i,k}B_{b,k,j}\)
- 代码写法:
bik, bkj -> bij。
可以发现,这两种所谓的「简洁写法」都非常丑陋,这是因为 Einsum 本来是用来刻画多重线性映射的,所以只允许某侧出现两次或两侧各出现一次。广义 Einsum 或代码中则使用 Arrow Convention:
- 只在箭头左侧出现的符号被求和压平。
- 在箭头左右都出现的符号被保留。
- 只在箭头右侧出现的符号会报错(虽然你可以把它解释为广播,但问题是不知道要广播几次)。
继续看例子。
- 迹:
- 传统写法:\(s=\sum A_{i,i}\)。
- Einsum 写法:\(s=A_{i,i}\)。
- 代码写法:
ii ->。
- 3D CP 分解:
- Einsum 写法:\(X_{i,j,k}=\lambda_rA_{i,r}b_{j,r}c_{k,r}\)。
- 代码写法:
r, ir, jr, kr -> ijk。
- Self Attn:
- 代码写法:
bhid, bhjd -> bhij。
- 代码写法:
特别地,python 中有一个小技巧。
- 省略箭头和输出:直接写
ij, jk, 这样会自动挑出所有只出现一次的符号(在这个例子中是i和k)并把它们按照字母表顺序排列作为输出。

浙公网安备 33010602011771号