想象你是一位城市规划师,面对一座由无数小房间组成的巨大“乐高数据城堡”——每个房间都存放着一个数字,房间沿着三条互相垂直的街道方向排列。你想把这座城堡搬到另一座城市,可它实在太大,直接搬运会占满整座仓库。这时一位精通“拆装术”的老师傅告诉你:城堡虽庞大,却有隐藏的规律。他拿出两套方案:CP 方案——把城堡拆成一叠叠简单的彩色板条,每条板条都由三个方向上的小刻度尺拼成,叠起来就能还原原貌;Tucker 方案——把城堡压缩成一个精致的微型核心模型,再配上三把可以朝三个方向拉伸的模具尺,用很少的零件就能搭出大致轮廓。这套拆装术在 AI 领域无处不在:推荐系统用它压缩用户行为数据,神经网络用它瘦身加速推理。本文带零基础的你体验拆装全过程,学会 CP 与 Tucker 两大传世心法。
张量分解是 AI 工程师做模型压缩、推荐系统建模和高维信号处理的核心工具。掌握 CP 与 Tucker 两种“拆装术”,你能用更小的内存跑更大的模型,是高维数据时代的基本功。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
张量基础
每一座“乐高城堡”都由无数小房间组成,房间沿着几条互相垂直的街道排列。在数学世界里,这座城堡就是张量(Tensor)——一个多维数组。普通的一维数组是一条街上的门牌号序列,二维数组是一张铺开的街区地图,而三阶张量就像一座三维立体城堡,每个房间用三个坐标(街道 A、街道 B、楼层 C)唯一定位。
既然城堡的体积等于三个方向上房间数量的乘积,那么拆解的第一步就是学会“摊开”。模展开(Matricization) 就是把城堡里的房间按照某个方向排成一排,其余方向全部拉平成一条长走廊。比如沿第一个方向展开,就得到一张“第 A 街 × 全部房间”的二维平面图。这一操作把三维问题变成熟悉的二维矩阵问题,是后续所有拆装术的入场券。
一个 $N$ 阶张量写成:
模展开就像把立体城堡沿某一面墙“压扁”,让所有房间的位置能被记录在一张纸上。读懂这套坐标语言,后面的拆解才能得心应手。
- 张量记号$\mathcal{X}$ 表示整个多维数据体,$I_1, I_2, \dots, I_N$ 分别表示每个方向上房间的数量。
- 模展开下标$(n)$ 表示把第 $n$ 个方向摆成行,其余方向全部压平成列。
- 展开结果形状展开后是一个二维矩阵,行数为 $I_n$,列数为其余维数的乘积。
CP分解原理
老师傅的第一套方案叫 CP 分解:把整座城堡拆成一叠叠“彩色板条”,每种板条都由三条方向上的刻度尺共同刻画。数学上,这种板条叫秩一张量(Rank-1 Tensor)——由三个向量的外积拼成,它的每一个房间数字等于三个刻度尺数值的乘积。
一个秩一张量极其“单纯”:房间数字沿着三个方向分别按比例增减,形成一种规则的条纹图案。CP 分解的直觉是:任何一座复杂城堡,都可以看做若干条这种单纯板条的叠加。如果只取前 $R$ 条最重要的板条,就得到秩 $R$ 近似——近似程度足够好,但存储量从“全城堡”缩为“几条板条”。
三阶张量的 CP 分解写成:
CP 分解的美妙之处在于:城堡再复杂,只要找到够用的“板条目录”,就能用极少的零件实现高保真还原。不过,如何精确算出每块板条的刻度,还需要下一节的“打磨工艺”。
- 外积符号$\circ$ 表示把三个向量钉成一个立体,第 $i,j,k$ 个房间的值是 $a_{ir} b_{jr} c_{kr}$。
- 求和叠加$\sum_{r=1}^{R}$ 把 $R$ 条板条逐房间相加,还原原城堡。
- 因子矩阵简记把 $R$ 个 $\mathbf{a}_r$ 并排拼成矩阵 $\mathbf{A}$,同样拼出 $\mathbf{B}$、$\mathbf{C}$,整个分解简写为 $[\![\mathbf{A}, \mathbf{B}, \mathbf{C}]\!]$。
交替最小二乘
有了板条模型,接下来的问题是:怎样确定每条板条上刻度尺的具体数值?ALS 的巧妙之处:与其同时琢磨所有方向,不如一次只专注打磨一个方向。比如先固定 B、C 两把尺子不动,只调整 A 方向的刻度,让当前误差最小;然后换 B,再换 C,轮流来。这个“固定两个、优化一个”的循环,就是交替最小二乘(Alternating Least Squares, ALS)。
每次优化都需求解一个最小二乘问题:原始城堡与当前板条叠出的城堡之间,差距的平方和要尽量小。因为一次只调整一个方向,问题变成了普通矩阵的最小二乘,数学上有现成的闭式解。
核心目标函数与更新公式:
ALS 像一个严格的工匠:每次只动一把尺子,量到最准,再换下一把。三轮一循环,城堡的还原误差逐渐下降,直至收敛。
- 目标函数$\|\cdot\|_F$ 是弗罗贝尼乌斯范数,逐房间计算原城堡与重建城堡的差距平方和再开方,整体衡量还原质量。
- 模展开$\mathbf{X}_{(1)}$ 把三阶张量沿第一个方向压平成矩阵,以便套用矩阵最小二乘。
- Khatri-Rao 积$\odot$ 把 B、C 两把尺子按列做克罗内克积,组合成一整块大模板,表示“已知方向”的联合影响。
- 哈达玛积与伪逆$\ast$ 是逐元素相乘,$(\cdot)^{+}$ 是穆尔-彭罗斯伪逆,用来一次性算出最优的 A 刻度。
Tucker分解
老师傅的第二套方案叫 Tucker 分解,思路不同:不把城堡拆成板条,而是先压出一个“微型核心模型”——一个远小于原城堡的核心张量 $\mathcal{G}$,再配上三个方向的三把“模具尺”(因子矩阵),用模具尺把核心模型拉伸回原尺寸。这套方案更灵活,因为核心张量的每个房间都可以独立缩放,而不像 CP 板条那样必须顺着方向按比例变化。
Tucker 分解把每个房间的值写成:核心张量的对应位置,分别被三把模具尺在三个方向上做线性组合。核心张量的尺寸完全由自己设定,通常远小于原张量,于是整体参数量大幅下降,而还原精度反而常比同参数量的 CP 更高。
三阶 Tucker 分解公式:
Tucker 分解的精髓是“先压缩核心,再按方向展开”。核心张量保留城堡最重要的大格局信息,三把模具尺负责把细节按方向还原,压缩效率往往比 CP 板条更高。
- 核心张量$\mathcal{G}$ 是缩小版的“城堡核心”,尺寸 $R_1 \times R_2 \times R_3$,远小于原尺寸 $I_1 \times I_2 \times I_3$。
- n 模乘$\times_n$ 表示沿着第 $n$ 个方向,用因子矩阵 $\mathbf{A}^{(n)}$ 对核心张量做线性变换,类似把模具尺沿对应方向拉伸。
- 因子矩阵三个方向的模具尺,各自由塔克秩(Tucker Rank)$R_1, R_2, R_3$ 决定宽度。
HOOI算法
有了核心模型和模具尺,该怎样求出它们呢?如果模式全开,Tucker 的求解是 NP-Hard 的;但聪明的工程师想到了一条“先粗后精”的路线:先给三把模具尺各猜一个初始方向,然后轮流固定其余两把,优化当前方向的模具尺,让核心模型保留的信息最多。不断交替,最终收敛到一组高质量因子。这就是高阶正交迭代(Higher-Order Orthogonal Iteration, HOOI)。
HOOI 的每次更新都对应一个“主方向提取”问题:把当前张量投影到其余因子张成的子空间,再对投影结果做奇异值分解(SVD),取前 $R_n$ 个奇异值对应的左奇异向量,作为新的因子矩阵。这几步交替进行,直到因子矩阵不再明显变化。由于每次迭代都保证目标函数不下降,HOOI 在工程上有很好的收敛行为。
HOOI 的因子更新规则:
HOOI 如同先去工地粗搭一个框架,再一锤一锤逐个方向校准模具尺,让整体轮廓越来越准。它是 Tucker 分解在工程中最高效的求解算法之一。
- 克罗内克积$\bigotimes$ 把其他方向的因子矩阵组合成一个整体投影矩阵,张成当前可用的子空间。
- 张量投影$\mathbf{X}_{(n)}(\cdot)$ 把原始张量沿第 $n$ 个方向展开后,投影到其余方向的子空间。
- SVD 截断对投影结果做奇异值分解,取前 $R_n$ 个左奇异向量,作为新因子矩阵。
CP与Tucker对比
两套拆装术都从同一座城堡出发,但思路和适用边界截然不同。CP 把城堡拆成一叠板条,每条板条都独立表达一种“方向规律”,结构简单、可解释性强;Tucker 则把城堡先压成核心模型再加模具拉伸,核心里的每个值都能独立调节,自由度更高。
有趣的是,两者之间存在“包含”关系:当 Tucker 的核心张量变成超对角张量(只有主对角线上的房间有值,其余全为零)时,乘法展开后恰好等价于 CP 分解。也就是说,CP 是 Tucker 的一个特例。这个关系也解释了两者的特性差异——CP 更严苛、更独特,Tucker 更灵活、压缩潜力更大。
核心转化关系:
实际选型时,如果追求结果可解释、需要剥离独立成分,选 CP;如果追求高压缩率、更灵活还原,选 Tucker。两条路都能抵达目的地,只是风景不同。
- 超对角核心$g_{rrr}$ 只在 $r_1 = r_2 = r_3$ 时非零,其余位置全为零。
- 等价关系左侧的求和(CP 形式)与右侧的 n 模乘展开(Tucker 形式)完全等价,说明 CP 是 Tucker 的超对角特例。
应用实战
拆装术不只是纸面功夫。在 AI 工程里,张量分解是“给模型减负”的利器。以神经网络中最常见的卷积核为例:一个卷积核本质是一个四阶张量(宽 × 高 × 输入通道 × 输出通道)。直接用 Tucker 分解压缩它,相当于把大城堡压成微型核心加四把模具尺,参数量可减少数倍到数十倍,推理速度近似不变,精度损失可控。
另一个经典场景是推荐系统:用户行为可以表示为“用户 × 商品 × 时间”的三阶张量,CP 分解把这个稀疏城堡拆成若干板条,每条板条代表一类用户兴趣;通过板条叠加可预测用户尚未产生的交互,补齐空缺房间。在图像压缩、脑电信号分析等领域,同样的拆装逻辑也在反复出现。实践中常用 Python 库 Tensorly 实现 CP 与 Tucker 分解,几行代码就能完成建模、训练与重构验证。
四阶卷积核的 Tucker 压缩形式:
从乐高城堡到神经网络,拆装术的威力始终如一:把笨重的数据结构化为少量标准零件,让 AI 系统跑得更快、存得更省。
- 卷积核张量$\mathcal{K}$ 是大城堡,四个方向分别是宽、高、输入通道和输出通道。
- 四向压缩四把模具尺分别对应四个方向的线性变换,核心张量 $\mathcal{G}$ 尺寸大幅缩小。
AI 应用场景
推荐系统中补齐用户行为矩阵
阶段:特征工程与模型压缩
解决的问题:传统协同过滤把“用户 × 商品 × 时间”展开成二维矩阵,丢失了时间维度的结构信息;直接用完整三维张量做预测又太稀疏、太占内存。Tucker 分解把三维交互张量压成核心张量加三个因子矩阵,保留多维结构的同时大幅压缩存储,还能利用压缩后的低维表示预测缺失交互。
具体案例:一个视频平台构建“用户 × 视频 × 周次”张量,用 Tucker 分解把 10 万用户、1 万视频、52 周共 520 亿格的数据压到核心维度 $64 \times 64 \times 52$,每周更新时只需重算因子矩阵,推荐列表响应时间从秒级降到毫秒级。
神经网络卷积核压缩
阶段:模型部署与推理优化
解决的问题:深度学习模型参数动辄上亿,直接部署到手机或边缘设备会遇到显存和带宽瓶颈。卷积核本质是四阶张量,用 HOOI 求 Tucker 分解,把原始大卷积核替换为核心张量与四个因子矩阵的组合,既降低参数量又减少乘加次数,并能借助逐段卷积实现加速。
具体案例:某图像分类模型的第一层卷积核尺寸 $3 \times 3 \times 64 \times 128$,Tucker 分解后参数量压缩为原来的 $1/5$,Top-1 准确率仅下降 0.4%,单张图片推理延迟降低 37%。
Python 演示
import numpy as np def kr(C, B): """Khatri-Rao 积:把两个因子矩阵逐列做克罗内克积""" R = B.shape[1] cols = [np.kron(C[:, r], B[:, r]) for r in range(R)] return np.stack(cols, axis=1) def cp_als(X, R, max_iter=50, tol=1e-6): """CP 分解的交替最小二乘求解器""" I, J, K = X.shape rng = np.random.default_rng(42) A = rng.normal(size=(I, R)) B = rng.normal(size=(J, R)) C = rng.normal(size=(K, R)) err_prev = np.inf for _ in range(max_iter): A = np.linalg.lstsq(kr(C, B), X.reshape(I, -1).T, rcond=None)[0].T B = np.linalg.lstsq(kr(C, A), X.transpose(1, 0, 2).reshape(J, -1).T, rcond=None)[0].T C = np.linalg.lstsq(kr(B, A), X.transpose(2, 0, 1).reshape(K, -1).T, rcond=None)[0].T rec = np.zeros_like(X) for r in range(R): rec += np.multiply.outer(A[:, r], np.multiply.outer(B[:, r], C[:, r])) err = np.linalg.norm(X - rec) / np.linalg.norm(X) if abs(err_prev - err) < tol: break err_prev = err return A, B, C, err if __name__ == "__main__": rng = np.random.default_rng(1) X = rng.random((5, 6, 7)) A, B, C, rel_err = cp_als(X, R=3) print("相对误差:", rel_err) print("A:", A.shape, "B:", B.shape, "C:", C.shape)
def kr(C, B):实现 Khatri-Rao 积,把两个因子矩阵逐列做克罗内克积,这是 ALS 更新的核心辅助函数。A = np.linalg.lstsq(kr(C, B), X.reshape(I, -1).T, rcond=None)[0].T固定 B、C,利用最小二乘直接算出最优 A。B = np.linalg.lstsq(kr(C, A), ...)固定 A、C,把张量沿第二方向展开后更新 B。C = np.linalg.lstsq(kr(B, A), ...)固定 A、B,沿第三方向更新 C,完成一轮循环。rec += np.multiply.outer(A[:, r], np.multiply.outer(B[:, r], C[:, r]))把 R 个秩一张量逐房间叠加,重建近似后的城堡。err = np.linalg.norm(X - rec) / np.linalg.norm(X)计算相对误差,数值越小说明还原越精确。if abs(err_prev - err) < tol: break当误差变化小于阈值时提前终止,控制迭代成本。
常见误区
误区既然矩阵有秩,张量 CP 秩应该也能像矩阵一样通过高斯消元快速算出来,两者本质相同。
正解矩阵秩可以在多项式时间内精确计算,但三阶及以上张量的 CP 秩精确计算是 NP-Hard 问题,并且 CP 秩随实数域或复数域变化会有所不同。它更像一个“需要搜索的未知数”,而不是能一步求出的性质。
为何常见:人们习惯了矩阵分解中的秩有成熟算法,容易把熟悉的直觉直接外推到张量上,忽略了高维带来的组合爆炸。
误区Tucker 参数量更灵活、压缩率往往更高,所以 Tucker 全面胜过 CP,工程上只用 Tucker 就够了。
正解Tucker 的核心张量不唯一,存在旋转自由度;CP 在稀疏性和理论条件下往往具有更好的可解释性,能拆出独立的“信号源”。选型应看目标——追求可解释选 CP,追求压缩精度选 Tucker。
为何常见:看到 Tucker 在某个数据集上压缩率更高,就误以为它是 CP 的“升级版”,忽略了可解释性和稳定性的差异。
误区ALS 每一步都在减小误差,迭代到最后一定收敛到全局最优的分解。
正解ALS 的每一步确实让目标函数单调不增,但整体问题是非凸的,不同的初始值会收敛到不同的局部最优解。工程上需要多次随机初始化、选取误差最小的一次结果。
为何常见:“误差一直下降”给初学者一种正确且唯一的错觉,忽略了非凸优化本身的复杂性。
误区分解时秩 R 取值越大,重建误差越小,所以尽量把 R 设得大一些准没错。
正解秩过大不仅让参数量和计算代价上升,还会把噪声一起拟合进来,导致过拟合;真正合适的是在误差、参数量和泛化能力之间做权衡,通常用验证集来选择 R。
为何常见:训练集上误差越小越好的惯性思维让初学者忽略噪声拟合与压缩率之间的对立关系。
面试题
Q1. 关于三阶张量,下列说法正确的是?
- A.张量就是矩阵的别名
- B.张量是三维及以上的数组← 正确答案
- C.张量只能存储一个数字
- D.张量在内存中无法表示
答案:B。矩阵是二维数组,而张量是多维数组的统称;三阶张量包含三个方向索引,不是矩阵的别名,也不是单个数字。选 B。
Q2. CP 分解的目标函数是?
- A.最小化分解误差范数← 正确答案
- B.最大化秩的数值
- C.让张量变为稠密矩阵
- D.使因子矩阵全是负数
答案:A。CP 分解用若干秩一张量叠加逼近原始张量,目标是最小化原始张量与重建张量之间的误差范数。其他选项均与 CP 分解目标无关。
Q3. ALS 迭代过程中如何更新三个因子矩阵?
- A.三个因子矩阵同时更新
- B.一次固定两个、更新一个← 正确答案
- C.只更新核心张量
- D.先删除一个因子再重算
答案:B。ALS 的核心策略是“固定两个、优化一个”,按 A→B→C 轮流更新,而不是同时更新全部因子,也不是只更新核心。
Q4. Tucker 分解把原张量近似表达为?
- A.核心张量与因子矩阵的 n 模乘← 正确答案
- B.若干稀疏矩阵的直接相加
- C.单个对角矩阵的简单展开
- D.多个核函数的加权平均
答案:A。Tucker 分解的形式是核心张量依次沿多个方向与因子矩阵做 n 模乘;它不是稀疏矩阵相加,也不是对角矩阵展开。故选 A。
Q5. HOOI 算法在更新因子矩阵时使用哪类运算?
- A.傅里叶变换
- B.SVD 奇异值截断← 正确答案
- C.多项式插值
- D.梯度下降
答案:B。HOOI 每次把张量投影到其余因子张成的子空间,再对结果做 SVD 并取前 R 个左奇异向量。核心运算是 SVD,而非傅里叶或插值。
Q6. 关于 ALS 收敛过程,下列说法正确的有?
- A.每步误差单调不增← 正确答案
- B.一定能收敛到全局最优
- C.不同初值可能得到不同结果← 正确答案
- D.必须设定无穷大的秩
答案:AC。ALS 每步最小化单因子子问题,所以误差不增(A 对);但整体非凸,存在局部最优,不同初值结果不同(C 对)。它不是全局最优保证,也无需无穷大秩。
Q7. CP 分解与 Tucker 分解的关系是?
- A.CP 是 Tucker 的超对角特例← 正确答案
- B.Tucker 是 CP 的等价变形
- C.两者没有任何数学关联
- D.Tucker 总是优于 CP
答案:A。当 Tucker 的核心张量为超对角时,n 模乘展开会退化为若干秩一张量之和,即 CP 分解。因此 CP 是 Tucker 的特例,两者并非完全无关。
Q8. 用 Tucker 分解压缩卷积核能带来哪些收益?
- A.大幅减少模型参数量← 正确答案
- B.卷积推理速度提升← 正确答案
- C.自动提升模型准确率
- D.精度损失可人为权衡← 正确答案
答案:ABD。Tucker 把大卷积核压成核心加因子矩阵,参数变少、乘加次数降低,速度提升;但压缩总会有精度权衡,并不会自动提升准确率。故选 A、B、D。
Q9. 关于张量 CP 秩的性质,下列说法正确的有?
- A.精确计算通常是 NP-Hard← 正确答案
- B.与矩阵秩的定义完全一致
- C.数值会受实数域或复数域影响← 正确答案
- D.恒等于张量最大维度数值
答案:AC。三阶以上张量的 CP 秩精确计算是 NP-Hard(A 对),且在不同数域下秩可能不同(C 对)。它不等同于矩阵秩,也不等于最大维度数值。
Q10. 三阶张量 $\mathcal{X}$ 沿第一方向模展开后,$\mathbf{X}_{(1)}$ 的正确形状是?
- A.$I_1 \times (I_2 \cdot I_3)$← 正确答案
- B.$I_2 \times (I_1 \cdot I_3)$
- C.$(I_1 \cdot I_2) \times I_3$
- D.$I_3 \times (I_2 \cdot I_1)$
答案:A。模展开把第 n 个方向作为行,其余方向压平作为列。沿第一方向展开,行数为 $I_1$,列数为 $I_2 \times I_3$,故选 A。