
简介面向数据分析、机器学习及信号处理领域研究者的高阶张量分解资源包围绕三阶张量的HOSVD与Tucker分解展开系统讲解张量基本表示、高阶奇异值分解的构造流程以及核心张量与各模式因子矩阵在降维、特征提取和潜在关系挖掘中的作用。内容兼顾理论推导与代码实现适合正在学习张量分解、需要处理多模态图像/视频数据或构建用户-物品-时间推荐系统的高校学生与算法工程师。压缩包共2个文件包含一个Matlab脚本源码和一个PDF格式参考文献脚本可实现HOSVD对三阶张量的分解重现Tucker分解结果并输出核心张量、因子矩阵文献提供数学背景与扩展阅读便于深入理解。整体仅4.58MB获取和部署成本极低已有1638人学习下载。借助这份资料能够掌握HOSVD与Tucker分解的完整脉络学会用Matlab快速处理三阶张量并将其应用到数据压缩、特征提取、图像分析与推荐系统等实际任务中显著提升高维数据的处理效率。1. 三阶张量的HOSVD是什么先理解它和Tucker分解的边界彩色图像是天然的三阶张量高、宽、通道三个方向都有结构。想对这堂量做奇异值分解直觉是分通道跑矩阵SVD但这样会丢掉通道之间的相关性。HOSVD高阶奇异值分解把矩阵SVD直接推广到张量一次性解出所有方向上的主成分。它是Tucker分解的一种特例Tucker分解允许因子矩阵自由变化HOSVD则把每个因子矩阵固定为对应模展开的SVD左奇异向量因此能用闭式解一次算完。它适合做张量压缩、去噪、特征提取用在图像处理、脑电信号分析、推荐系统补全这类场景。但闭式解不等于最优解它在重构误差上通常比迭代Tucker略差。这个边界决定了你该选它还是选HOOI迭代。2. 从矩阵SVD到三阶张量的模展开HOSVD的两个前置概念2.1 矩阵SVD的直觉怎么迁移到张量矩阵SVD把A分解为UΣVᵀU的列是按数据方差排序的正交基保留前r列就能得到误差最小的r秩逼近。Eckart-Young定理保证在所有秩不超过r的矩阵里SVD截断的Frobenius范数误差最小。看一个数据矩阵时用户真正关心的是两件事哪些方向主导变化以及如何用低秩近似替代原数据。这个思路迁移到三阶张量时难题不是缺SVD而是方向变多了。三阶张量X有I₁×I₂×I₃个元素沿三个模各有结构。图像的宽方向有连续性高方向有连续性通道方向存着RGB相关性。把整个张量拉成一行做矩阵SVD三种结构会被一起碾碎分三个方向各做各的SVD又得处理三个结果怎么合成。HOSVD给出的约定是分别把三个方向展开成矩阵各自做SVD得到三个因子矩阵再合成一个核心张量。分解形式写成X ≈ G ×₁ U₁ ×₂ U₂ ×₃ U₃其中×ₙ表示沿第n个模做张量矩阵乘积。这套形式和Tucker分解完全一样差别只在U的来源——HOSVD要求U来自对应展开的SVDTucker分解则不要求。正是这个来源差异决定了HOSVD可以闭式计算也决定了它牺牲了一部分最优性。2.2 模展开的约定为什么顺序错了结果必错模展开mode-n unfolding是三阶张量到矩阵的映射。展开第n维时把该维的Iₙ个切片逐行排开其余两个维度的索引按固定顺序拉成列。三种展开的形状差异很大先用表格记清楚。展开方式结果形状行方向列方向mode 0(I₁, I₂×I₃)第一维第二、三维依次展开mode 1(I₂, I₁×I₃)第二维第一、三维依次展开mode 2(I₃, I₁×I₂)第三维第一、二维依次展开实现上最隐蔽的坑是顺序。必须先移动轴再reshape不能直接拿X.reshape(I_n, -1)硬来否则列方向的内存连续顺序和“先其他模、再目标模”的约定不一致后续重构误差会大到像随机数。正确写法是把目标模先挪到第0维reshape时其余维度自然按原顺序铺开。import numpy as np def mode_unfold(X, mode0): X_move np.moveaxis(X, mode, 0) return X_move.reshape(X_move.shape[0], -1)参数说明X是任意三阶NumPy数组mode取0、1、2分别对应高、宽、通道。np.moveaxis先做轴移动再reshape只负责压平剩余维度。这里不能省moveaxis也不能用view之类的内存技巧顺序错了再怎么调秩都救不回来。2.3 HOSVD与Tucker分解的关系为什么它是一个闭式解有了展开矩阵HOSVD流程就很直白了对每个模展开做SVD取左奇异向量得到因子矩阵Uₙ再依次计算核心张量G X ×₁ U₁ᵀ ×₂ U₂ᵀ ×₃ U₃ᵀ。分解式写完整是X G ×₁ U₁ ×₂ U₂ ×₃ U₃。Tucker分解的一般形式也是这个式子但对U没有额外限制。HOSVD额外规定每个Uₙ来自对应展开的SVD所以Uₙ的列两两正交。这一条规定带来两个直接后果。第一HOSVD不需要迭代。展开、SVD、模乘三步走完结果唯一适合大批量数据先跑一版看效果。第二它天然是Tucker问题的好起点。Tucker低秩逼近要最小化固定秩下的重构误差目标函数非凸工程上靠迭代求解。HOSVD给出的正交因子是质量很高的初值再用高阶正交迭代HOOI修正收敛速度远快于随机初始化最终误差也更低。这里还要说一下张量的奇异值到底在哪。矩阵SVD里奇异值排在对角线上三阶张量的HOSVD里核心张量是三维的它沿每个模的切片两两正交切片范数从大到小递减这组范数就是该模方向的奇异值谱。后面选截断秩本质就是看这条谱衰减到哪位可以切掉。记住这一点调参时思路会清楚很多。3. 用Python从零实现三阶张量HOSVD最少代码跑通一条流程3.1 先写模乘张量和矩阵的乘法原语HOSVD公式里反复出现X ×ₙ U。这个乘法做的事把X沿第n个模的每条纤维与U的列做线性组合结果张量在第n模上的维度变成U的行数其他维度不变。它是整个实现的基石必须写对。def mode_n_product(X, U, mode): X_move np.moveaxis(X, mode, 0) res np.tensordot(X_move, U, axes([0], [1])) return np.moveaxis(res, -1, mode)逻辑说明np.moveaxis先把目标模挪到第0维np.tensordot让X_move的第0维和U的第1维对齐并求和。注意tensordot的结果会把U的行数放在所有维度的最后所以最后一步要把这个新增维度挪回mode位置。如果不做这一步后面拼接因子矩阵时维度顺序全乱重构误差直接爆掉。参数说明U的列数必须等于X在第mode维的长度。HOSVD里乘Uᵀ时Uᵀ行数是原始模维度列数是截断秩重构时乘U行数是截断秩列数是原始模维度一进一出刚好还原。3.2 HOSVD主函数展开后SVD再收紧核心张量有了展开和模乘HOSVD主流程短到让人意外。对每个模做三件事展开、SVD、把当前核心张量沿该模乘上Uᵀ。def hosvd(X, ranksNone): factors [] core X for mode in range(X.ndim): mat mode_unfold(X, mode) U, s, _ np.linalg.svd(mat, full_matricesFalse) if ranks is not None: U U[:, :ranks[mode]] factors.append(U) core mode_n_product(core, U.T, mode) return core, factors逻辑说明循环顺序不影响结果不同模上的模乘可以交换顺序。每拿到一个Uₙ并截断就立刻对核心张量做一次模乘相当于把该方向的信息“压进”核心张量。循环结束时core的维度是r₁×r₂×r₃不传ranks时保持原维度。参数说明ranks是三个整数的列表分别给三个模的截断秩传None表示不截断通常只用来验证实现正确性。返回值里的s奇异值分量没有直接使用但调试时一定要留在局部变量里打印检查奇异值衰减是否正常。3.3 重构与最小自检重构误差必须是机器零HOSVD的逆过程是按相同顺序把因子矩阵乘回去这一步没有新逻辑但它是验证实现的唯一标准。实现正确时不截断的HOSVD就是一个恒等分解重构误差应该接近浮点精度。def reconstruct(core, factors): X_hat core for mode, U in enumerate(factors): X_hat mode_n_product(X_hat, U, mode) return X_hat np.random.seed(42) X np.random.rand(30, 40, 5) core, factors hosvd(X) # 不截断 X_hat reconstruct(core, factors) err np.linalg.norm(X - X_hat) / np.linalg.norm(X) print(err) # 应该在1e-12量级逻辑说明重构顺序与压缩顺序完全相同因为factor列表是按mode从小到大存的enumerate取出时mode和下标一致。相对误差用Frobenius范数归一化可比性更好。这个自检极其重要。如果误差在1e-1量级先查三个点mode_unfold是否先moveaxis再reshapetensordot是否收缩了U的列而不是行重构时传入的mode是否和factors的index对齐。这三个点里任何一处错误差都会大得像没分解一样。3.4 用彩色图像做一次压缩试验彩色图像读进来是高×宽×通道的三阶张量天然适合做演示。from skimage import io img io.imread(sample.jpg).astype(np.float64) / 255.0 h, w, c img.shape ranks [h // 2, w // 2, c] # 通道维度保留全部 core, factors hosvd(img, ranksranks) img_hat reconstruct(core, factors) psnr 10 * np.log10(1.0 / np.mean((img - img_hat) ** 2)) print(PSNR:, psnr)参数说明ranks里c直接等于原始通道数相当于通道模不截断。图像三通道相关性很强通道方向只做正交变换不截断压缩损失全部来自空间维。skimage读进来是0到255的uint8先归一化到[0,1]再算PSNR比较稳公式里的1.0就是信号峰值。PSNR大于30dB时视觉差异通常已经很小。4. 截断HOSVD的三个必调参数秩、能量阈值与重构验证4.1 秩别拍脑袋先看每个模的奇异值谱截断HOSVD的核心是给三个秩r₁、r₂、r₃定值。我从来不用“三边都取一半”这种省事方案而是先把三个展开矩阵的奇异值打印出来看衰减形态。衰减陡的模可以截狠一点衰减平的模截狠了直接糊成一片。def choose_rank_by_energy(s, energy0.95): s2 np.square(s) cum np.cumsum(s2) / np.sum(s2) return int(np.searchsorted(cum, energy)) 1 for mode in range(X.ndim): U, s, _ np.linalg.svd(mode_unfold(X, mode), full_matricesFalse) r choose_rank_by_energy(s, 0.95) print(mode, mode, rank, r, energy, (np.sum(s[:r] ** 2) / np.sum(s ** 2)))参数说明能量阈值用奇异值平方占比衡量表示保留了多少Frobenius能量。0.95是起步值想要更高保真就提到0.99代价是秩变大。searchsorted返回第一个累计比例不小于阈值的位置加1是因为下标从0开始且秩至少为1。实际项目里三个秩往往差别很大。图像样例通常是空间维秩大、通道维秩小脑电信号通常是时间维秩大、电极维秩小。它们都由奇异值谱说了算不靠公式拍。4.2 截断后存多少压缩比的计算方式截断后要存的不是原始张量而是核心张量加三个因子矩阵。以64×64×3的图像为例原始元素数12288。取秩[32,32,3]时核心张量是32×32×3共3072个数字U₁是64×32共2048个U₂同样2048个U₃是3×3共192个合计7360压缩比约0.6。图像越大冗余越明显这个比例会更好看。数据规模秩组合存储元素数相对原体积512×512×3[128,128,3]约35万约45%512×512×3[64,64,3]约5.2万约6.6%512×512×3[256,256,3]约39万约50%压缩比公式是(r₁·r₂·r₃ I₁·r₁ I₂·r₂ I₃·r₃) / (I₁·I₂·I₃)。核心里三项分别是核心张量、三个因子矩阵的存储成本因子矩阵的维度受原始维度和截断秩共同控制。只用压缩比决策容易把秩压过头更好的做法是把PSNR或相对误差当约束反过来找最小存储组合。4.3 闭式解不是最优解什么时候必须换成HOOI截断HOSVD按每个模展开独立做SVD再合并计算路径极短但理论上有硬伤——它不是固定秩下最小化重构误差的全局最优解。展开SVD分别保证了“其他模不参与”时该方向最优合并到一起时三个方向的误差会互相耦合不等于各自误差的简单相加。追求极致压缩时HOSVD的误差通常比迭代TuckerHOOI高几个百分点。我的取舍标准是原型验证、数据规模大、只需要快速知道大概效果用截断HOSVD精确压缩比、需要和论文指标对标、用于算法底座用HOSVD初始化再跑HOOI。HOOI每轮需要逐模做SVD收敛快慢取决于奇异值谱的衰减速度。谱衰减陡的张量十几轮能稳定衰减平的张量会慢一些但一般也值得等。还有一种常见误用是把截断后得到的核心张量强行按“对角”理解想着像矩阵SVD那样丢弃非对角元素。矩阵里对角线以外的元素确实是冗余张量HOSVD的核心张量却是满的只是各模切片两两正交而已。随手置零非对角块重构质量会断崖式下降。5. HOSVD避坑排查五个容易翻车的细节5.1 展开顺序错乱重构误差变成0.1现象不截断的HOSVD重构后相对误差在0.1量级而不是1e-12。原因mode_unfold里没有先moveaxis就reshape。直接用X.reshape(I_n, -1)列方向的内存连续序和“先其他模、再目标模”的语义不一致分解和重构对不上。解决统一使用moveaxis后再reshape跑真实数据前先做恒等重构断言。这个坑我至少见过四次每次都出现在别人手写展开函数时。它也是张量代码里最典型的翻车点没有之一。5.2 三个秩都设成一半结果烂得莫名其妙现象ranks[h//2, w//2, c//2]压缩后的图像严重偏色边缘和纹理全糊。原因通道维本来只有3或4个截半等于丢掉一半通道方向信息而RGB通道间的相关性恰恰是最能压缩的富矿。空间维设成一半也可能过粗把本该保留的细节全扔了。解决每个模单独看奇异值谱再决定。通道模一般保留全部时间序列的模看衰减空间模可以多截一点。没有万能组合但可以按能量阈值0.9和0.99之间扫描选满足PSNR的最小存储组合。中间如果发现某个模的奇异值下降得异常平缓说明这个方向信息密度高别硬截。5.3 把HOSVD当成最优低秩逼近误差对不上就怀疑实现现象同一份数据HOSVD的重构误差比论文里的Tucker结果大第一反应是代码写错了。原因HOSVD是闭式解但不是固定秩下的误差最小解。论文里通常用HOOI迭代求解误差更低是方法本身的优势不是bug。解决先跑不截断恒等重构确认实现正确再用同一个秩同时跑HOSVD和HOOI对比。两者误差差距通常在5%到15%之间数据越结构化差距越大。追求误差最低就上迭代追求快速出结果就接受这个gap只要在文档里写清楚用的是什么方法就行。5.4 大张量上直接np.linalg.svd卡死或爆内存现象512×512×200的医学影像张量展开矩阵是200×262144np.linalg.svd一跑就卡到怀疑人生。原因full_matricesFalse虽然不生成完整V矩阵但仍会计算全部奇异值和左奇异向量。展开矩阵行列一大时间和内存双双扛不住。解决只需要前r个奇异分量时用稀疏SVD或随机化SVD代替完整SVD。from scipy.sparse.linalg import svds U, s, Vt svds(mat, k20) # k是要保留的秩 idx np.argsort(s)[::-1] U, s, Vt U[:, idx], s[idx], Vt[idx, :]参数说明svds返回的奇异值默认升序所以必须反转索引否则截断后因子矩阵的列和奇异值对不上重构顺序就反了。k建议取实际秩的1.5到2倍截断时再按能量阈值挑避免能量集中在前几个奇异值时漏掉尾部信息。5.5 把核心张量当对角矩阵随手置零现象拿到核心张量G后学着矩阵SVD的样子把“对角”以外的元素全置零再重构效果惨不忍睹。原因矩阵SVD的核是对角的因为秩一分量之间完全去相关。张量HOSVD的核心张量是满的只保证各模切片两两正交非对角块仍携带不同模之间的耦合信息不能随手丢。解决剪枝要按G的各模切片Frobenius范数排序丢掉范数小的切片保留范数大的更稳妥的是直接在分解时改变截断秩重新计算核心张量而不是事后切核心。事后再切等于在压缩结果上二次压缩误差不可控。6. 进阶验证用HOOI修正HOSVD检查重构误差拿到任何新数据我会先跑一段不截断的恒等自检。代码就三行但很多项目在前三行就翻车。core, factors hosvd(X, ranksNone) X_hat reconstruct(core, factors) assert np.allclose(X, X_hat, atol1e-10)断言过了再谈截断或迭代。以前我跳过这一步直接拿截断后的误差去定位问题结果花了一个小时发现是展开函数顺序错了后悔药都来不及吃。误差要求更严格时把HOSVD结果当作初值跑HOOI。方法是固定除第n模外的所有因子矩阵把张量投影到这些因子张成的空间再对投影结果做第n模展开的SVD更新Uₙ。每轮逐模更新一次重构误差单调下降。工程上不想手写循环的直接调张量库里的tucker函数内部就是HOSVD初始化加迭代优化。这里有一个我踩过之后的习惯HOOI收敛后不要把收敛前的重构误差写在文档里。用验证集或完整重构后再算PSNR和相对误差很多参数调优的“玄学”其实是误差口径不统一。我自己最后都会用一个很小的随机张量同时跑截断HOSVD和HOOI对比两个误差和耗时再决定提交哪条链路。这套检查做完再去谈压缩比和上线心里才有底。希望帮到你。本文还有配套的精品资源点击获取