系列 · 线性代数 · 第 13 篇

线性代数(十三):张量与多线性代数——从标量到高维数据立方体

张量是向量和矩阵到任意维度的推广。本章从标量、向量、矩阵出发,讲解纤维、切片、展开等概念,以及 CP 分解、Tucker 分解和 HOSVD,并探讨张量在神经网络压缩和推荐系统中的应用。

如果你用过 PyTorch 或 TensorFlow,“张量”这个词你一定见过无数次。 PyTorch 把所有数组都叫 torch.Tensor, TensorFlow 更是直接把张量写进了名字。但张量到底是什么?为什么这些框架要用一个听起来像物理术语的词来描述看似多维数组的对象?

本章的核心答案很简单。

张量是标量、向量和矩阵的自然延伸,可以推广到任意维度。矩阵的相关知识可以直接应用于张量,或在失效时揭示一些有趣的规律。

我将从大家熟悉的概念入手,逐步介绍张量的相关概念(如纤维、切片、展开),并讲解两种核心分解方法——CP 分解Tucker 分解——看它们如何压缩神经网络并为推荐系统注入上下文感知能力。

你会学到以下内容:

  • 张量的阶、形状、纤维、切片与展开
  • 核心操作:缩并、外积、 n-模乘积
  • CP 分解、 Tucker 分解、 HOSVD
  • 张量在神经网络压缩和推荐系统中的应用

前置知识: 第 6 章 特征值分解、第 9 章 SVD、第 10 章 矩阵范数


线性代数(十三):张量与多线性代数——从标量到高维数据立方体 — 章节概览图

从标量到张量:维度的自然扩展#

一个数、一行数、一张表、一个立方体#

看看这些对象是怎么一层层堆起来的:

  • 标量(0 阶):单个数字。比如今天的气温,或者你银行卡里的余额。只有大小,没有方向。
  • 向量(1 阶):一串数字。比如地图上的位置 $(x, y)$ ,或者一个 RGB 颜色 $(r, g, b)$ 。既有大小,也有方向。
  • 矩阵(2 阶):一张数字表。比如 Excel 表格,或者灰度图像,每个单元格存的是像素亮度。
  • 3 阶张量:一个“数据立方体”。彩色图像就是典型的例子:高度 $\times$ 宽度 $\times$ 3 个颜色通道。

每升一阶就多一根轴,用于按索引取值。这条阶梯不会在 3 阶停止。

从标量到张量:每一步都增加一根新的轴

把这种规律推广下去,就得到张量的一般定义:

张量是向量和矩阵向任意维度的扩展。一个 $N$ 阶张量就是一个 $N$ 维数组,记作 $\mathcal{X} \in \mathbb{R}^{I_1 \times I_2 \times \cdots \times I_N}$ ,其中 $I_1, I_2, \ldots, I_N$ 是每一根轴的长度。

生活中到处是张量#

每天都在用张量,只是没特意给它起名字:

数据形状阶数每根轴的含义
一句话的情感得分$(\,)$0单个数字
一座城市一周的气温$(7,)$17 天的温度
灰度照片$(H, W)$2$\times$ 宽 的像素表
彩色照片$(H, W, 3)$3每个像素有 RGB 三元组
一段短视频$(T, H, W, 3)$4$T$ 帧叠在一起
一批训练图像$(N, 3, H, W)$4$N$ 张图打包送进 GPU
用户-电影-时间评分$(U, M, T)$3用户 $u$ 在时间 $t$ 对电影 $m$ 的评分

举个具体例子: 假设我在做视频推荐系统,有 1000 个用户、 10000 部电影、 52 周的偏好历史。最自然的表达方式就是一个 $1000 \times 10000 \times 52$ 的三阶张量。如果强行把时间维度压平,变成传统的用户-电影矩阵,那我正好丢掉了想建模的关键信号。

阶、形状与秩 —— 别搞混了#

初学者容易混淆这三个概念,但实际上它们完全不同:

  • 阶(order,也叫 mode):有多少根轴。向量是 1 阶,$480 \times 640 \times 3$ 的图像是 3 阶。
  • 形状(shape):每根轴的长度。同一张图像的形状是 $(480, 640, 3)$
  • 秩(rank):衡量复杂度,跟维度数量没关系。后面会专门讲到——你会发现,张量的秩和矩阵的秩行为差别很大。

张量的内部结构:纤维与切片#

要操作张量,首先得学会“看透”它。两个最重要的视角是纤维切片

纤维:穿过张量的牙签#

把一个三阶张量想象成魔方。用一根牙签沿着某个方向穿过去,被串起来的那一串小方块就是一根纤维——张量的一维片段。

定义:固定除一个索引外的所有索引,得到的就是一个向量,这就是纤维。

对于 $\mathcal{X} \in \mathbb{R}^{I \times J \times K}$

  • 模-1 纤维 $\mathbf{x}_{:jk}$ :固定 $j$$k$ ,沿第一个索引变化,长度为 $I$
  • 模-2 纤维 $\mathbf{x}_{i:k}$ :固定 $i$$k$ ,长度为 $J$
  • 模-3 纤维 $\mathbf{x}_{ij:}$ :固定 $i$$j$ ,长度为 $K$

对于 RGB 图像 $\mathcal{X} \in \mathbb{R}^{H \times W \times 3}$ ,模-3 纤维 $\mathbf{x}_{ij:}$ 就是位置 $(i, j)$ 处像素的颜色向量 $(R, G, B)$

切片:穿过张量的薄片#

还是用魔方来比喻:这次换成用刀切。每切一刀,得到的就是一个矩阵。

定义:固定一个索引,得到的就是一个矩阵,这就是切片。

对于 $\mathcal{X} \in \mathbb{R}^{I \times J \times K}$

  • 水平切片 $\mathbf{X}_{i::}$ :固定 $i$ ,得到 $J \times K$ 的矩阵
  • 侧面切片 $\mathbf{X}_{:j:}$ :固定 $j$ ,得到 $I \times K$ 的矩阵
  • 正面切片 $\mathbf{X}_{::k}$ :固定 $k$ ,得到 $I \times J$ 的矩阵

对于视频 $\mathcal{V} \in \mathbb{R}^{H \times W \times T}$ ,正面切片 $\mathbf{V}_{::t}$ 就是第 $t$ 帧画面。整段视频可以看作一摞正面切片。

把 3D 张量看成一摞正面切片(矩阵)

这个“一摞矩阵”的图景非常实用:只要我会在矩阵上做操作,就可以沿着第三根轴逐帧推广。

张量展开(matricization)#

很多时候,我会把张量摊平成矩阵,这样就能用现成的矩阵工具。这个操作叫展开矩阵化

$$\mathbf{X}_{(n)} \in \mathbb{R}^{I_n \times (I_1 \cdots I_{n-1} I_{n+1} \cdots I_N)}$$

举个例子,$\mathcal{X} \in \mathbb{R}^{3 \times 4 \times 2}$ 的三种展开形状分别是 $3 \times 8$$4 \times 6$$2 \times 12$

模-1 展开:每根模-1 纤维成为结果矩阵的一列

为什么这很重要?因为绝大多数张量算法的核心步骤都是“对模-$n$ 展开做一次 SVD”,而我们已经有非常成熟、稳定的矩阵工具可以直接使用。

张量的基本运算#

加法与数乘#

$$ (\mathcal{X} + \mathcal{Y})_{i_1 \cdots i_N} = x_{i_1 \cdots i_N} + y_{i_1 \cdots i_N}, \qquad (\alpha \mathcal{X})_{i_1 \cdots i_N} = \alpha\, x_{i_1 \cdots i_N} $$

ResNet 的跳跃连接公式 $\mathbf{y} = F(\mathbf{x}) + \mathbf{x}$ ,本质上就是张量加法。

张量缩并(Einstein 求和)#

张量缩并是最重要的操作,可以看作矩阵乘法的自然推广。

核心思想: 从两个张量中各选一根长度相同的轴,将对应位置的元素相乘,然后沿这根公共轴求和。求和后,这根轴就消失了。

$$C_{ik} = \sum_j A_{ij} B_{jk}$$

这里公共下标 $j$ 被缩并掉了。

$$\mathcal{C}_{i_1 i_2 k_1 k_2} = \sum_j \mathcal{A}_{i_1 i_2 j}\, \mathcal{B}_{j k_1 k_2}$$

结果是一个 4 阶张量。

张量缩并:选公共下标、相乘、求和消掉

这就是 np.einsumtorch.einsum 的底层规则:写出输入轴和输出轴,凡是出现在输入但不出现在输出的下标都会被求和消掉。

外积与秩-1 张量#

外积的作用方向相反:它用低阶张量构造高阶张量。

两个向量: $\mathbf{a} \circ \mathbf{b} = \mathbf{a} \mathbf{b}^T$ ,得到一个矩阵,其 $(i, j)$ 元为 $a_i b_j$

三个向量: $(\mathbf{a} \circ \mathbf{b} \circ \mathbf{c})_{ijk} = a_i b_j c_k$ ,得到一个三阶张量。

能写成若干向量外积形式的张量称为秩-1 张量。这是最简单的张量,它的全部信息由几个一维向量决定。

关键点: 任何张量都可以表示为有限个秩-1 张量的和。这一事实是张量分解的理论基础, CP 分解和 Tucker 分解都源于此。

n-模乘积#

n-模乘积将一个矩阵作用到张量的某一根轴上。

$$(\mathcal{X} \times_n \mathbf{U})_{i_1 \cdots i_{n-1} j i_{n+1} \cdots i_N} = \sum_{i_n} x_{i_1 \cdots i_N}\, u_{j i_n}$$

结果与 $\mathcal{X}$ 形状相同,只是第 $n$ 根轴的长度从 $I_n$ 变为 $J$

矩阵视角: 展开来看,这就是普通的矩阵乘法:$\mathbf{Y}_{(n)} = \mathbf{U}\, \mathbf{X}_{(n)}$

直观理解: “只在第 $n$ 根轴上应用线性变换”。例如,对于 RGB 图像 $\mathcal{I} \in \mathbb{R}^{H \times W \times 3}$ ,用一个 $3 \times 3$ 颜色校正矩阵 $\mathbf{M}$$\mathcal{I} \times_3 \mathbf{M}$ ,就是逐像素地对 $(R, G, B)$ 三元组进行线性混合,实现白平衡或色调映射。

常用性质:

  • 不同模的乘积可交换:$\mathcal{X} \times_m \mathbf{A} \times_n \mathbf{B} = \mathcal{X} \times_n \mathbf{B} \times_m \mathbf{A}$ (当 $m \neq n$ )。
  • 同模相继乘积合并为矩阵乘积:$\mathcal{X} \times_n \mathbf{A} \times_n \mathbf{B} = \mathcal{X} \times_n (\mathbf{B}\mathbf{A})$

Kronecker 积与 Khatri-Rao 积#

这两个积在张量分解中频繁出现,需要熟悉。

$$\mathbf{A} \otimes \mathbf{B} = \begin{bmatrix} a_{11}\mathbf{B} & \cdots & a_{1J}\mathbf{B} \\ \vdots & \ddots & \vdots \\ a_{I1}\mathbf{B} & \cdots & a_{IJ}\mathbf{B} \end{bmatrix}$$

直观理解是“把 $\mathbf{A}$ 的每个元素替换为该元素乘以整个矩阵 $\mathbf{B}$ ”。

$$\mathbf{A} \odot \mathbf{B} = [\mathbf{a}_1 \otimes \mathbf{b}_1, \; \mathbf{a}_2 \otimes \mathbf{b}_2, \; \ldots, \; \mathbf{a}_R \otimes \mathbf{b}_R]$$

CP-ALS 算法的更新公式正是基于这个积构建的。

张量范数#

Frobenius 范数#

$$\|\mathcal{X}\|_F = \sqrt{\sum_{i_1, \ldots, i_N} x_{i_1 \cdots i_N}^2}$$

重要特性: Frobenius 范数在张量展开时保持不变。对任意模态 $n$ ,都有 $\|\mathcal{X}\|_F = \|\mathbf{X}_{(n)}\|_F$ 。所以,我可以选择最方便的形状来计算。

内积#

两个形状相同的张量: $\langle \mathcal{X}, \mathcal{Y} \rangle = \sum_{i_1, \ldots, i_N} x_{i_1 \cdots i_N}\, y_{i_1 \cdots i_N}, \qquad \|\mathcal{X}\|_F = \sqrt{\langle \mathcal{X}, \mathcal{X} \rangle}$ #

张量:多重线性映射的视角#

换个更抽象的角度看张量,收获会很大:张量就是多重线性映射

线性映射 很熟悉,比如 $f(\mathbf{x}) = \mathbf{A}\mathbf{x}$ 。它满足 $f(\alpha \mathbf{x} + \beta \mathbf{y}) = \alpha f(\mathbf{x}) + \beta f(\mathbf{y})$

$$f(\mathbf{x}, \mathbf{y}) = \mathbf{x}^T \mathbf{A} \mathbf{y}$$

这个函数是双线性的,因为对 $\mathbf{x}$$\mathbf{y}$ 都满足线性性质:

$f(\alpha \mathbf{x}_1 + \beta \mathbf{x}_2, \mathbf{y}) = \alpha f(\mathbf{x}_1, \mathbf{y}) + \beta f(\mathbf{x}_2, \mathbf{y})$ ,对 $\mathbf{y}$ 也一样。

多重线性映射 把这个规律推广到 $N$ 个向量输入,每个输入都保持线性。

张量积空间 是这样定义的:给定两个向量空间 $V$$W$ ,它们的张量积 $V \otimes W$ 是一个新空间。任何双线性映射 $f: V \times W \to Z$ 都可以通过这个空间表示为一个线性映射 $\tilde{f}: V \otimes W \to Z$ ,并且满足 $f(\mathbf{v}, \mathbf{w}) = \tilde{f}(\mathbf{v} \otimes \mathbf{w})$

好处很明显:张量把多重线性问题转化成线性问题。这样一来,我就可以直接用线性代数的整套工具来解决问题了。


CP 分解:把张量拆成简单成分的叠加#

线性代数(十三):张量与多线性代数——从标量到高维数据立方体 — 章节小结图

CP 分解:任意张量都可以写成秩-1 外积之和

什么是 CP 分解?#

$$\mathcal{X} \approx \sum_{r=1}^{R} \lambda_r \, \mathbf{a}_r \circ \mathbf{b}_r \circ \mathbf{c}_r$$

把这些向量整理成因子矩阵 $\mathbf{A} = [\mathbf{a}_1, \ldots, \mathbf{a}_R]$$\mathbf{B} = [\mathbf{b}_1, \ldots, \mathbf{b}_R]$$\mathbf{C} = [\mathbf{c}_1, \ldots, \mathbf{c}_R]$ ,通常简记为 $\mathcal{X} \approx [\![\boldsymbol{\lambda}; \mathbf{A}, \mathbf{B}, \mathbf{C}]\!]$

直觉:叠加简单模式#

$$\text{评分}(u, m, t) \approx \sum_{r=1}^{R} \lambda_r \cdot \text{用户}_r(u) \cdot \text{电影}_r(m) \cdot \text{时间}_r(t)$$

每个成分 $r$ 都是一种直观的“简单模式”,甚至可以直接描述出来:

  • 成分 1:年轻用户喜欢动作片,周末晚上看。
  • 成分 2:中年用户偏爱文艺片,工作日深夜看。
  • ……

把这些模式叠加起来,就能还原用户的复杂行为。本质上, CP 分解同时对三根轴做了软聚类。

张量的秩:和矩阵秩大不相同#

$$\operatorname{rank}(\mathcal{X}) = \min\bigl\{ R : \mathcal{X} = \sum_{r=1}^{R} \mathbf{a}_r \circ \mathbf{b}_r \circ \mathbf{c}_r \bigr\}$$

有三点看似和矩阵类似,但其实完全不同:

  • 计算张量秩是 NP-hard 的。 矩阵的秩可以通过 SVD 轻松得到,但张量没有这样的工具。
  • 最佳低秩近似可能不存在: 矩阵的最佳低秩近似总能通过 SVD 实现,但张量只能无限逼近最优解,永远无法真正达到。这种现象叫“边界秩”(border rank)。
  • 张量秩可以超过任何一根轴的长度。 矩阵的秩最多是 $\min(m, n)$ ,但张量秩没有这个限制。

CP 的独特优势:本质唯一性#

CP 分解相比矩阵分解有一个显著优势:在温和条件下,它是本质唯一的。因子矩阵只允许成分顺序置换和列的缩放,再无其他自由度。

Kruskal 条件: 如果 $k_{\mathbf{A}} + k_{\mathbf{B}} + k_{\mathbf{C}} \geq 2R + 2$ (其中 $k_{\mathbf{A}}$$\mathbf{A}$ 的 Kruskal 秩,即任意 $k$ 列都线性无关的最大 $k$ ),那么 CP 分解就是本质唯一的。

对比 SVD:$\mathbf{U}$$\mathbf{V}$ 可以乘上任意正交矩阵互相抵消,结果不变。 CP 没有这种自由度,因此它的因子可以直接解释为“用户画像 / 物品画像 / 时间模式”。这正是它在推荐系统中特别受欢迎的原因。

交替最小二乘(ALS)算法#

求解 CP 分解最常用的方法是交替最小二乘法(ALS)。固定其他因子矩阵,剩下的问题就变成了普通的最小二乘问题。

  1. 随机初始化 $\mathbf{A}, \mathbf{B}, \mathbf{C}$
  2. 固定 $\mathbf{B}, \mathbf{C}$ ,更新 $\mathbf{A}$
$$ \mathbf{A} \leftarrow \mathbf{X}_{(1)}\, (\mathbf{C} \odot \mathbf{B})\, \bigl[(\mathbf{C}^T \mathbf{C}) * (\mathbf{B}^T \mathbf{B})\bigr]^{\dagger} $$

3. 同样地依次更新 $\mathbf{B}$$\mathbf{C}$ 。 4. 重构误差不再下降时停止迭代。

这里 $*$ 表示 Hadamard (按元素)积,$\dagger$ 表示 Moore–Penrose 伪逆。

ALS 不保证收敛到全局最优,但实际应用中效果不错。多跑几次随机初始化,取最好的结果即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import numpy as np
import tensorly as tl
from tensorly.decomposition import parafac

np.random.seed(42)
n_users, n_movies, n_months = 100, 50, 12
X = np.random.rand(n_users, n_movies, n_months) * 5

weights, factors = parafac(tl.tensor(X), rank=5, n_iter_max=100)
user_factors, movie_factors, time_factors = factors

print(user_factors.shape)   # (100, 5)
print(movie_factors.shape)  # (50, 5)
print(time_factors.shape)   # (12, 5)

user_factors 的每一行是一个用户的 5 维嵌入;movie_factorstime_factors 的每一行分别对应电影和时间的嵌入。 CP 分解一次性把三根轴映射到了同一个 5 维潜在空间。


Tucker 分解:更灵活的张量表示#

Tucker 分解:一个小核张量加上每根轴一个因子矩阵

定义#

$$\mathcal{X} \approx \mathcal{G} \times_1 \mathbf{A} \times_2 \mathbf{B} \times_3 \mathbf{C}$$ $$x_{ijk} \approx \sum_{p=1}^{P} \sum_{q=1}^{Q} \sum_{r=1}^{R} g_{pqr}\, a_{ip}\, b_{jq}\, c_{kr}$$

这里,$\mathcal{G} \in \mathbb{R}^{P \times Q \times R}$核张量,它是一个小张量,负责存储数据的交互结构。$\mathbf{A}, \mathbf{B}, \mathbf{C}$ 是因子矩阵,用来将核张量映射回原始尺寸。

Tucker 和 CP 的对比#

  • CP 是 Tucker 的特例。 如果核张量 $\mathcal{G}$ 是“超对角”的(只有 $g_{rrr}$ 非零), Tucker 就退化成了 CP。
  • Tucker 更加灵活: CP 要求三个因子矩阵的列数都等于 $R$ ,而 Tucker 允许每根轴有自己的截断秩 $(P, Q, R)$ 。对于三根轴本质维度差异很大的情况(比如 $1000 \times 1000 \times 12$ 的张量),前两根轴可能需要几十维,最后一根轴只需要 4 维。
  • CP 唯一, Tucker 不唯一。 Tucker 和 SVD 一样存在旋转自由度:把 $\mathbf{A}$ 替换为 $\mathbf{A}\mathbf{Q}$ ,再把 $\mathbf{Q}^{-1}$ 吸收到核张量中,结果不变。因此, Tucker 压缩效果好,但因子不像 CP 那样可以直接解释。

HOSVD:高阶奇异值分解#

HOSVD 是一种规范化的正交 Tucker 分解,方法非常简单:

  1. 对每根模 $n$ ,计算其展开 $\mathbf{X}_{(n)}$ 的 SVD。
  2. 取前 $R_n$ 个左奇异向量,组成因子矩阵 $\mathbf{U}^{(n)}$
  3. 计算核张量 $\mathcal{G} = \mathcal{X} \times_1 \mathbf{U}^{(1)T} \times_2 \mathbf{U}^{(2)T} \times_3 \mathbf{U}^{(3)T}$

HOSVD 和矩阵 SVD 的对应关系如下:

矩阵 SVDHOSVD
$\mathbf{A} = \mathbf{U} \boldsymbol{\Sigma} \mathbf{V}^T$$\mathcal{X} = \mathcal{G} \times_1 \mathbf{U}^{(1)} \times_2 \mathbf{U}^{(2)} \times_3 \mathbf{U}^{(3)}$
$\boldsymbol{\Sigma}$ 对角$\mathcal{G}$ “全正交”但不必对角
$\mathbf{U}, \mathbf{V}$ 正交每个 $\mathbf{U}^{(n)}$ 都正交

HOSVD 是数据压缩和降噪的核心工具,也常作为非线性 Tucker 求解(如 HOOI 高阶正交迭代)的初始值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from tensorly.decomposition import tucker

core, factors = tucker(tl.tensor(X), rank=[10, 8, 4])

print(core.shape)                       # (10, 8, 4)
print([f.shape for f in factors])       # [(100, 10), (50, 8), (12, 4)]

original_size   = 100 * 50 * 12
compressed_size = 10 * 8 * 4 + 100 * 10 + 50 * 8 + 12 * 4
print(f"压缩比: {original_size / compressed_size:.2f}x")

多线性秩#

$$\operatorname{rank}_{\text{ml}}(\mathcal{X}) = \bigl(\operatorname{rank}(\mathbf{X}_{(1)}),\, \operatorname{rank}(\mathbf{X}_{(2)}),\, \operatorname{rank}(\mathbf{X}_{(3)})\bigr)$$

每一项是某一根模展开的秩,表示数据沿该轴的本质独立方向数。与 CP 秩不同,多线性秩计算起来很简单,每根轴做一次 SVD 就能搞定。

张量与深度学习#

框架叫 TensorFlow 是有原因的。神经网络里几乎所有运算本质上都是张量操作。

从张量角度看卷积#

一个标准的 2D 卷积层涉及四个张量:

  • 输入 $\mathcal{X} \in \mathbb{R}^{B \times C_{\text{in}} \times H \times W}$ —— batch、输入通道数、高度、宽度。
  • 卷积核 $\mathcal{W} \in \mathbb{R}^{C_{\text{out}} \times C_{\text{in}} \times K_H \times K_W}$
  • 输出 $\mathcal{Y} \in \mathbb{R}^{B \times C_{\text{out}} \times H' \times W'}$

卷积操作本身是一次大规模缩并,沿着 $C_{\text{in}}$$K_H$$K_W$ 三个维度求和,同时在空间维度上滑动窗口 —— 这是一个结构化的 4 路缩并。

用 CP 分解压缩卷积层#

卷积层的权重本质上是一个 4 阶张量,“秩”在这里直接决定了参数数量。把 $\mathcal{W}$ 用 CP 分解表示为 $R$ 个秩-1 张量的和,原本一层胖卷积就变成了四层瘦卷积串联:

  1. $1 \times 1$ 卷积:将 $C_{\text{in}}$ 压缩到 $R$ 个通道。
  2. $K_H \times 1$ 深度卷积:对 $R$ 个通道分别操作。
  3. $1 \times K_W$ 深度卷积:同上。
  4. $1 \times 1$ 卷积:将 $R$ 个通道扩展回 $C_{\text{out}}$

参数量对比:

原始CP-rank-$R$ 之后
参数量$C_{\text{out}} \cdot C_{\text{in}} \cdot K_H \cdot K_W$$R \cdot (C_{\text{out}} + C_{\text{in}} + K_H + K_W)$

$R$ 较小时,压缩效果非常明显。 VGG-16 中某个 $512 \times 512 \times 3 \times 3$ 的卷积层原本有约 236 万参数;用 rank-64 的 CP 分解后,参数量降到约 6.6 万 —— 压缩比达到 35 倍,微调后精度损失也能控制在合理范围。

用 Tucker 分解压缩卷积层#

实际应用中更常用 Tucker 分解,因为它可以分别压缩输入和输出通道,这更符合 CNN 的实际结构。具体步骤是:

  1. $1 \times 1$ 卷积:将 $C_{\text{in}}$ 压缩到 $P$
  2. $K_H \times K_W$ 卷积:在压缩后的 $P \times Q$ 空间中操作。
  3. $1 \times 1$ 卷积:将 $Q$ 扩展回 $C_{\text{out}}$

这种“瓶颈”结构看起来很像 MobileNet 和 SqueezeNet 的基本模块 —— 这不是偶然的。这些手工设计的轻量化模块本质上就是把 Tucker 分解融入了网络结构。

张量分解在推荐系统中的应用#

从矩阵分解到张量分解#

$$\mathbf{R} \approx \mathbf{P} \mathbf{Q}^T,\quad \mathbf{P} \in \mathbb{R}^{U \times K},\; \mathbf{Q} \in \mathbb{R}^{M \times K}$$

这样就能预测评分:$\hat{r}_{um} = \mathbf{p}_u^T \mathbf{q}_m$

但这种方法忽略了一个关键点:人的偏好是受上下文影响的。周二早高峰通勤时想看的电影,和周六晚上窝在家里想看的,完全不一样。

$$r_{umt} \approx \sum_{r=1}^{R} \lambda_r\, p_{ur}\, q_{mr}\, t_{tr}$$

新增的因子 $\mathbf{t}_r$ 描述了第 $r$ 个成分如何随时间变化。由于 CP 分解具有本质唯一性,每个成分的实际意义是可以解释的。

稀疏数据:只拟合观测值#

$$\min_{\mathbf{A}, \mathbf{B}, \mathbf{C}} \sum_{(i,j,k) \in \Omega} \left( x_{ijk} - \sum_{r=1}^{R} a_{ir} b_{jr} c_{kr} \right)^2 + \lambda \bigl(\|\mathbf{A}\|_F^2 + \|\mathbf{B}\|_F^2 + \|\mathbf{C}\|_F^2\bigr)$$

这里 $\Omega$ 是观测值的索引集合,$\lambda$ 是正则化参数,用来防止嵌入向量过大。

这就是工业界“上下文感知推荐”系统的核心思路。神经网络扩展只是在这个基础上再加一层而已。

一个你每天都在用的应用:图像#

你打开的每张 JPEG 图片,其实就是一个三阶张量。下面这张图清楚地展示了这一点:一个 $H \times W \times 3$ 的数组,分解成三个单色“通道”矩阵,沿着深度方向堆叠在一起。

彩色图像就是 3 阶张量:H × W × 3,每个颜色通道一个矩阵

当我把图像看作张量时,计算机视觉中为什么到处都是张量代数就变得一目了然了。颜色空间转换,其实就是对 3 个通道做一次 n-模乘积;图像压缩的本质是低秩近似;卷积特征提取对应的是张量缩并。甚至在 CNN 深层网络中的“特征图”通道,本质上和 R、 G、 B 没有区别——只是这些通道是通过学习得到的,而不是物理上直接给定的。

其他张量分解方法#

Tensor Train (TT) 分解#

$$\mathcal{X}(i_1, i_2, \ldots, i_N) = \mathbf{G}_1(i_1)\, \mathbf{G}_2(i_2)\, \cdots\, \mathbf{G}_N(i_N)$$

每个 $\mathbf{G}_k(i_k)$ 是一个 $r_{k-1} \times r_k$ 的矩阵。它的优势很明显:

  • 参数数量按 $O(N\, d\, r^2)$ 增长,和阶数 $N$ 成线性关系,而不是指数关系。
  • 有稳定的构造算法,叫 TT-SVD。
  • 现代多体量子模拟器(DMRG / MPS)用的就是这种表示法。

非负张量分解(NTF)#

$$\mathcal{X} \approx \sum_{r=1}^{R} \mathbf{a}_r \circ \mathbf{b}_r \circ \mathbf{c}_r,\quad \mathbf{a}_r, \mathbf{b}_r, \mathbf{c}_r \geq 0$$

非负约束让各个成分更像是“零件”,彼此叠加构成整体,而不是正负相互抵消。这就是为什么 NMF / NTF 能给出可解释的主题或部件分解,而普通的 SVD 很难做到这一点。


练习题#

概念题#

练习 1 判断以下数据结构的张量阶数:

  • 单声道 MP3 歌曲(44.1 kHz)
  • 立体声歌曲
  • 一段 5 分钟、 30 fps 的 1080p RGB 视频
  • ImageNet 数据集(100 万张 $224 \times 224$ RGB 图像)
  • Transformer 注意力权重张量 $\in \mathbb{R}^{B \times H \times L \times L}$

练习 2 对于 $\mathcal{X} \in \mathbb{R}^{3 \times 4 \times 2}$

  • 模-1 纤维有多少根?每根长度是多少?
  • 正面切片有几个?每个形状是什么?
  • 模-2 展开 $\mathbf{X}_{(2)}$ 的形状是什么?

计算题#

练习 3$\mathbf{a} = [1, 2]^T$$\mathbf{b} = [3, 4, 5]^T$$\mathbf{c} = [6, 7]^T$

  • 计算外积 $\mathbf{a} \circ \mathbf{b}$
  • 构造三阶张量 $\mathcal{T} = \mathbf{a} \circ \mathbf{b} \circ \mathbf{c}$ ,写出 $t_{121}$ 的值。
  • 计算 $\|\mathcal{T}\|_F$

练习 4$\mathbf{A} = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}$$\mathbf{B} = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}$

  • 计算 Kronecker 积 $\mathbf{A} \otimes \mathbf{B}$
  • 验证恒等式 $(\mathbf{A} \otimes \mathbf{B})\, \operatorname{vec}(\mathbf{X}) = \operatorname{vec}(\mathbf{B} \mathbf{X} \mathbf{A}^T)$ (自选合适的 $\mathbf{X}$ )。

分解题#

$$\mathbf{a}_1 = [1, 0]^T,\; \mathbf{b}_1 = [1, 1, 0]^T,\; \mathbf{c}_1 = [1, 0]^T$$ $$\mathbf{a}_2 = [0, 1]^T,\; \mathbf{b}_2 = [0, 1, 1]^T,\; \mathbf{c}_2 = [0, 1]^T$$

(a) 计算 $\mathcal{X}$ 的若干元素。
(b) 写出三个因子矩阵。
(c) 计算模-1 展开 $\mathbf{X}_{(1)}$

练习 6$3 \times 3 \times 3$ 单位张量($\delta_{ijk} = 1$ 当且仅当 $i = j = k$ ):

  • 计算 Frobenius 范数。
  • CP 秩是多少?(提示:能否写成少于 3 个秩-1 张量之和?)
  • 多线性秩是多少?

应用题#

练习 7 (推荐系统) 某视频平台有 3 个用户、 4 部电影、 2 个时间段,观测到的评分如下:

用户电影时间段评分
1115
1214
2123
2315
3222
3414
  • 构造评分张量 $\mathcal{R} \in \mathbb{R}^{3 \times 4 \times 2}$ (未观测位置设为 0)。
  • 用秩-2 CP 分解需要估计多少个参数?
  • 比较秩-1 和秩-2 分解各自的优劣。

练习 8 (图像压缩) 一张 $1024 \times 768$ 的 RGB 图像看作 $1024 \times 768 \times 3$ 张量。

  • 原始存储需要多少字节(每个像素 1 字节)?
  • 用 Tucker 分解 $(100, 75, 3)$ 后需要多少字节?
  • 计算压缩比。
  • 改用秩 100 的 CP 分解需要多少字节?

编程题#

练习 9 用 Python 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def unfold(X, mode):
    """X: ndarray (I, J, K);mode 取 0/1/2;返回展开后的矩阵。"""
    pass

def mode_n_product(X, A, mode):
    """X: (I, J, K);A: (P, I_mode);返回乘积张量。"""
    pass

def simple_cp_als(X, rank, n_iter=100):
    """返回 (A, B, C) 三个因子矩阵。"""
    pass

练习 10tensorly 库完成:

  • 构造一个秩-3 的随机 $20 \times 15 \times 10$ 张量(先生成因子,再用外积拼出来)。
  • 分别用秩 2、 3、 4、 5 做 CP 分解,比较重构误差。
  • 画出“重构误差 vs. 秩”曲线。
  • 用 HOSVD 对同一张量分解,与 CP 结果比较。

证明题#

练习 11 证明对任意模 $n$$\|\mathcal{X}\|_F = \|\mathbf{X}_{(n)}\|_F$

练习 12 证明 n-模乘积的两条性质:

(a) 当 $m \neq n$ 时,$\mathcal{X} \times_m \mathbf{A} \times_n \mathbf{B} = \mathcal{X} \times_n \mathbf{B} \times_m \mathbf{A}$
(b) $\mathcal{X} \times_n \mathbf{A} \times_n \mathbf{B} = \mathcal{X} \times_n (\mathbf{B}\mathbf{A})$

练习 13$\mathcal{X} = \mathbf{a} \circ \mathbf{b} \circ \mathbf{c}$ 是秩-1 张量,证明:

(a) $\|\mathcal{X}\|_F = \|\mathbf{a}\|\, \|\mathbf{b}\|\, \|\mathbf{c}\|$
(b) $\mathbf{X}_{(1)} = \mathbf{a}\, (\mathbf{c} \otimes \mathbf{b})^T$

总结#

概念
张量是标量、向量和矩阵的推广,可以扩展到任意阶数。纤维、切片和展开是分析张量内部结构的基本工具。核心操作包括加法、数乘、缩并、外积和 n-模乘积,这些操作都是从矩阵运算自然延伸而来的。

分解
CP 分解将张量表示为秩-1 外积的和,在一定条件下具有本质唯一性,因此分解出的因子可以直接解释。 Tucker 分解更灵活,允许每个模式有不同的秩,但存在旋转不确定性; HOSVD 提供了正交规范的形式。

小心“秩”
张量秩的计算是 NP 难问题,最佳低秩近似可能不存在,张量秩甚至可能超过任何一个轴的长度。这些特性在矩阵中都不会出现。

应用
张量分解可用于压缩卷积层(CP / Tucker)、实现上下文感知推荐(用户-物品-时间 CP),以及自然地表示图像、视频、脑电图和量子态。核心思想很简单:把复杂的高维结构拆解为简单成分的组合


参考文献#

  • Kolda, T. G., & Bader, B. W. (2009). Tensor decompositions and applications. SIAM Review, 51(3), 455–500.
  • Sidiropoulos, N. D., et al. (2017). Tensor decomposition for signal processing and machine learning. IEEE Transactions on Signal Processing, 65(13), 3551–3582.
  • Cichocki, A., et al. (2015). Tensor decompositions for signal processing applications. IEEE Signal Processing Magazine, 32(2), 145–163.
  • TensorLy 文档 —— Python 张量学习库。
  • Strang, G. (2019). Linear Algebra and Learning from Data第 7 章
本系列

线性代数 18 篇

  1. 01 线性代数(一):向量的本质——不仅仅是箭头
  2. 02 线性代数(二):线性组合与向量空间
  3. 03 线性代数(三):矩阵作为线性变换
  4. 04 线性代数(四):行列式的秘密
  5. 05 线性代数(五):线性方程组与列空间
  6. 06 线性代数(六):特征值与特征向量
  7. 07 线性代数(七):正交性与投影——当向量互不干扰
  8. 08 线性代数(八):对称矩阵与二次型
  9. 09 线性代数(九):奇异值分解 SVD
  10. 10 线性代数(十):矩阵范数与条件数——数值计算的健康体检
  11. 11 线性代数(十一):矩阵微积分与优化——从梯度到反向传播
  12. 12 线性代数(十二):稀疏矩阵与压缩感知——少即是多的数学奇迹
  13. 13 线性代数(十三):张量与多线性代数——从标量到高维数据立方体 当前
  14. 14 线性代数(十四):随机矩阵理论——混沌中的秩序
  15. 15 线性代数(十五):机器学习中的线性代数——从 PCA 到推荐系统
  16. 16 线性代数(十六):深度学习中的线性代数——从全连接到 Transformer
  17. 17 线性代数(十七):计算机视觉中的线性代数——从像素到三维重建
  18. 18 线性代数(十八):前沿应用与总结——量子计算、GNN、大模型,与十八章回望

读有所得?

GitHub 关注我 → 新文周更

GitHub