零基础吃透:不规则张量(RaggedTensor)vs 稀疏张量(SparseTensor)
这份内容会用通俗比喻+核心对比+实例拆解,讲清楚两者的本质区别------它们看似都"省内存",但设计目标、数据形态、运算逻辑完全不同,核心结论先记住:
RaggedTensor 是「可变长度数据的原生类型」(解决"长度不一致"),SparseTensor 是「密集张量的压缩编码」(解决"大部分值为空/0")。
一、核心定位(本质区别,先立住!)
用"收纳工具"比喻最直观:
| 张量类型 | 核心定位(设计目标) | 通俗比喻 | 核心特征 |
|---|---|---|---|
| RaggedTensor | 扩展 Tensor 类型,处理可变长度/嵌套结构的数据 | 「大小不一的收纳盒」:有的盒子装2个东西,有的装5个,没有空位置 | 数据本身长度不规则,无冗余空位 |
| SparseTensor | 压缩编码 Tensor,优化大部分值为空/0的密集张量 | 「固定尺寸的收纳柜」:柜子格子数固定,只记录"有东西的格子+值",空格子默认是0/空 | 数据形状固定,仅压缩存储非空值 |
关键结论(文档核心):
- 对 SparseTensor 做运算 ≈ 对其对应的密集张量做运算(结果完全一致,只是存储方式不同);
- 对 RaggedTensor 做运算 ≠ 对 SparseTensor/密集张量做运算(数据形态不同,结果自然不同)。
二、核心维度对比(一张表看懂所有差异)
| 对比维度 | 不规则张量(RaggedTensor) | 稀疏张量(SparseTensor) |
|---|---|---|
| 设计目标 | 支持可变长度/嵌套深度的非均匀数据 | 高效存储固定形状、大部分值为空/0的密集张量 |
| 数据形态 | 维度长度不规则(如[[1,2],[3]]),无冗余空位 | 维度形状固定(如固定3行5列),仅记录非空值 |
| 运算规则 | 按「实际元素数」运算(比如均值=总和/实际个数) | 按「固定形状」运算(比如均值=总和/固定长度) |
| 存储特点 | 记录"元素值 + 行分区规则"(如每行长度) | 记录"非空值 + 非空位置坐标"(如(0,1)=5) |
| 最外层维度 | 必为均匀维度(行数固定) | 所有维度形状固定(行列数都固定) |
| 冗余性 | 无冗余(只存有效元素) | 逻辑上有冗余(空值默认存在,只是不存储) |
三、实例拆解(文档核心例子,通俗化)
用文档里的 concat 例子,直观看两者运算逻辑的差异:
前置准备(统一输入)
先定义两组可变长度的字符串数据,分别转成 RaggedTensor 和 SparseTensor:
python
import tensorflow as tf
# 1. 定义RaggedTensor(可变长度,无空位)
ragged_x = tf.ragged.constant([["John"], ["a", "big", "dog"], ["my", "cat"]])
ragged_y = tf.ragged.constant([["fell", "asleep"], ["barked"], ["is", "fuzzy"]])
# 2. 转成SparseTensor(固定形状,补空位)
sparse_x = ragged_x.to_sparse()
sparse_y = ragged_y.to_sparse()
例子1:concat(拼接)运算对比
❶ RaggedTensor 拼接(axis=1)------ 无空位,直接拼
python
ragged_concat = tf.concat([ragged_x, ragged_y], axis=1)
print("RaggedTensor拼接结果:")
print(ragged_concat)
输出:
<tf.RaggedTensor [[b'John', b'fell', b'asleep'], [b'a', b'big', b'dog', b'barked'], [b'my', b'cat', b'is', b'fuzzy']]>
逻辑:每行内部直接拼接,按「实际元素」来,没有任何空位:
- 第一行:["John"] + ["fell","asleep"] → ["John","fell","asleep"](3个元素);
- 第二行:["a","big","dog"] + ["barked"] → ["a","big","dog","barked"](4个元素)。
❷ SparseTensor 拼接(axis=1)------ 先补空位,再拼接
python
sparse_concat = tf.sparse.concat(axis=1, sp_inputs=[sparse_x, sparse_y])
# 转成密集张量看结果(空位置用空字符串填充)
dense_concat = tf.sparse.to_dense(sparse_concat, default_value=b'')
print("\nSparseTensor拼接(转密集张量):")
print(dense_concat)
输出:
tf.Tensor(
[[b'John' b'' b'' b'fell' b'asleep']
[b'a' b'big' b'dog' b'barked' b'']
[b'my' b'cat' b'' b'is' b'fuzzy']], shape=(3, 5), dtype=string)
逻辑:SparseTensor 本质是固定形状的密集张量压缩版,拼接前会先补空位到「固定长度」:
- 第一步:ragged_x 转 SparseTensor 时,补空位到最长行长度3 → 形状[3,3](第一行["John","",""],第二行["a","big","dog"],第三行["my","cat",""]);
- 第二步:ragged_y 转 SparseTensor 时,补空位到最长行长度2 → 形状[3,2](第一行["fell","asleep"],第二行["barked",""],第三行["is","fuzzy"]);
- 第三步:拼接 axis=1 → 形状[3,3+2=5],空位置保留(用空字符串填充)。
例子2:reduce_mean(每行均值)------ 运算逻辑差异
(文档提了核心逻辑,补充代码更直观)
❶ RaggedTensor 求均值 ------ 除以「实际元素数」
python
# 模拟数值型RaggedTensor
rt = tf.ragged.constant([[1,2,3], [4], [5,6]])
rt_mean = tf.reduce_mean(rt, axis=1)
print("RaggedTensor每行均值:", rt_mean) # [2.0, 4.0, 5.5]
逻辑:均值 = 该行总和 / 该行实际元素数:
- 第一行:(1+2+3)/3 = 2.0;
- 第二行:4/1 = 4.0。
❷ SparseTensor 求均值 ------ 除以「固定长度」
python
# 转成固定形状的SparseTensor(形状[3,3],空位置补0)
st = rt.to_sparse()
# 转密集张量求均值(空位置算0)
st_dense = tf.sparse.to_dense(st, default_value=0)
st_mean = tf.reduce_mean(st_dense, axis=1)
print("SparseTensor每行均值:", st_mean) # [2.0, 1.333, 3.666]
逻辑:均值 = 该行总和 / 该行固定长度(3):
- 第二行:(4+0+0)/3 ≈ 1.333;
- 第三行:(5+6+0)/3 ≈ 3.666。
四、适用场景(什么时候用哪个?)
用 RaggedTensor 的场景(可变长度是核心)
- 文本序列:句子(有的5个词,有的8个词)、单词(有的3个字符,有的5个);
- 嵌套结构:文档→段落→句子→单词(每层长度都可变);
- 可变长度特征:用户行为序列(有的用户10次点击,有的5次)、商品标签(有的商品3个标签,有的0个)。
用 SparseTensor 的场景(固定形状+大部分空是核心)
- 高维稀疏特征:推荐系统的用户-物品矩阵(10万用户×100万物品,只有1%的交互记录);
- 图像掩码:固定尺寸图像的掩码区域(比如224×224的图,只有少量区域有标注);
- 类别特征编码:One-Hot编码(比如1000个类别,只有1个位置为1,其余为0)。
五、核心总结(避坑关键)
- 别混淆"可变长度"和"稀疏":
- RaggedTensor 是「数据本身长度不规则」,没有空值;
- SparseTensor 是「数据形状固定」,只是大部分值为空,用压缩方式存储。
- 运算逻辑是根本差异:
- RaggedTensor 运算只认"实际元素";
- SparseTensor 运算等价于"补空后的密集张量运算"。
- 选型原则:
- 数据长度不固定 → 用 RaggedTensor;
- 数据形状固定但大部分空 → 用 SparseTensor;
- 数据形状固定且非空值多 → 用普通密集张量。
简单记:Ragged 管"长度不一样",Sparse 管"值大部分空"。