当前位置: 首页 > news >正文

rANS:快速的渐进最优码

参考文献:

  1. [Huff52] D. A. Huffman, “A Method for the Construction of Minimum-Redundancy Codes,” in Proceedings of the IRE, vol. 40, no. 9, pp. 1098-1101, Sept. 1952.
  2. [Duda09] Jarek Duda. Asymmetric numeral systems. CoRR abs/0902.0271 (2009).
  3. [Duda13] Jarek Duda. Asymmetric numeral systems: entropy coding combining speed of huffman coding with compression rate of arithmetic coding, 2013. ArXiv preprint, available at https://arxiv.org/abs/1311.2540.

文章目录

  • ANS
  • Range variants
  • Stream Encoding
  • 代码实现

为了压缩已知概率密度的符号序列,有两种主流方法:

  1. 哈夫曼编码:使用二的幂次近似密度(二叉树),速度快,但压缩率低(除非信源密度恰好都是二的幂次);
  2. 算术/范围编码:使用精确密度,压缩率很容易达到香农熵,但是速度慢。

[Duda09] 提出了 Asymmetric numeral systems(ANS),它是渐进最优码(平均码长等于香农熵),并且速度比哈夫曼编码更快。

ANS

ANS 的抽象思路是:令 [ n ] [n] [n] 是大小为 n n n 的符号表, q s , s ∈ [ n ] q_s, s \in [n] qs,s[n] 是其密度函数。对于一个充分大的整数 x ∈ N x \in \N xN,将其视为区间 x = { 0 , 1 , ⋯   , x − 1 } ⊆ N x=\{0,1,\cdots,x-1\} \subseteq \N x={0,1,,x1}N,根据符号密度 q s q_s qs 划分为 n n n 个大小分别约为 q s x q_sx qsx 的子集 S s ⊆ x S_s \subseteq x Ssx,满足 x = ∪ s S s x = \cup_s S_s x=sSs,那么,在 x x x 中均匀采样,就等价于首先根据 q s q_s qs 采样出 s s s,然后在 S s S_s Ss 中均匀采样。所以, x    ⟺    ( s , S s ) x \iff (s,S_s) x(s,Ss) 存在双射,可以利用它来编码符号 s s s 的序列。

函数 D 1 : N → [ n ] ,   x ↦ s D_1: \N \to [n],\, x \mapsto s D1:N[n],xs 称为 distributing function,它给出了集合划分,
S s : = { y ∈ x ∣ D 1 ( y ) = s } S_s := \{y\in x \mid D_1(y)=s\} Ss:={yxD1(y)=s}
然后定义 x s : = ∣ S s ∣ x_s := |S_s| xs:=Ss 以及 D 2 ( x ) : = x D 1 ( x ) D_2(x) := x_{D_1(x)} D2(x):=xD1(x)

那么,解码函数为
D ( x ) : = ( D 1 ( x ) , D 2 ( x ) ) = ( s , x s ) D(x) := (D_1(x), D_2(x)) = (s,x_s) D(x):=(D1(x),D2(x))=(s,xs)
它是双射,编码函数是它的逆,
C ( s , x s ) : = x C(s,x_s) := x C(s,xs):=x
为了实现渐进最优性,要求找到合适的函数 D 1 D_1 D1,使得总有 x ≈ q s x x \approx q_s x xqsx

Range variants

[Duda09] 首先提出了一种 Uniform variants(uANS),函数 D 1 D_1 D1 将符号 s s s 几乎均匀地铺在 N \N N 上,其压缩率很好,但是计算相对较慢。下图展示了 n = 2 , q 0 = 0.7 , q 1 = 0.3 n=2, q_0=0.7, q_1=0.3 n=2,q0=0.7,q1=0.3 的情况,

在这里插入图片描述

之后,[Duda13] 提出了 rANS,函数 D 1 D_1 D1 将符号 s s s 放置在一个区间内,其概率近似不如 uANS 精确,但是只需要更少的计算开销。

假设 ( g 0 , g 1 , ⋯   , g n − 1 ) ∈ N n (g_0,g_1,\cdots,g_{n-1}) \in \N^n (g0,g1,,gn1)Nn 是符号密度 q s q_s qs 的良好近似(缩放 B : = ∑ s g s B:= \sum_s g_s B:=sgs 倍),将数组 { 0 , 1 , ⋯   , B − 1 } \{0,1,\cdots,B-1\} {0,1,,B1} 依次划分为大小为 g s g_s gs 的区间。定义 CDF ( s ) : = ∑ i = 0 s − 1 g i \text{CDF}(s) := \sum_{i=0}^{s-1} g_i CDF(s):=i=0s1gi 是累积密度,初值 CDF ( 0 ) = 0 \text{CDF}(0)=0 CDF(0)=0,那么范围 [ CDF ( s ) , CDF ( s + 1 ) ) [\text{CDF}(s),\text{CDF}(s+1)) [CDF(s),CDF(s+1)) 被解码为 s s s

定义函数 symbol : Z → [ n ] \text{symbol}: \Z \to [n] symbol:Z[n]
symbol ( x ) = s    ⟺    CDF ( s ) ≤ [ x ] B < CDF ( s + 1 ) \text{symbol}(x) = s \iff \text{CDF}(s) \le [x]_B < \text{CDF}(s+1) symbol(x)=sCDF(s)[x]B<CDF(s+1)
编码函数设为:
C ( s , x ) : = ⌊ x g s ⌋ ⋅ B + [ x ] g s + CDF ( s ) ∈ N C(s,x) := \left\lfloor\frac{x}{g_s}\right\rfloor \cdot B + [x]_{g_s} + \text{CDF}(s) \in \N C(s,x):=gsxB+[x]gs+CDF(s)N
解码函数设为:
D ( x ) : = ( symbol ( x ) ,   ⌊ x B ⌋ ⋅ g s + [ x ] B − CDF ( s ) ) ∈ [ n ] × N D(x) := \left(\text{symbol}(x),\, \left\lfloor\frac{x}{B}\right\rfloor\cdot g_s + [x]_B - \text{CDF}(s)\right) \in [n] \times \N D(x):=(symbol(x),Bxgs+[x]BCDF(s))[n]×N
我在编程时注意到,如果初始 x = 0 x=0 x=0 并且序列开头有多个 s = 0 s=0 s=0,那么总有 C ( s , x ) = 0 C(s,x)=0 C(s,x)=0,因此解码时会出现 Bug;对于其他的 x x x 值,是否存在类似问题?似乎设置 ∑ s g s ≤ B − 1 \sum_s g_s \le B-1 sgsB1 以及 C D F ( 0 ) = 1 CDF(0)=1 CDF(0)=1 就可以解决该问题(但是这对么?)

假如选取 B = 2 t B = 2^t B=2t,设置 g s ≈ q s B g_s \approx q_sB gsqsB,那么上述的 x ⋅ B ,   x / B ,   [ x ] B x\cdot B,\, x/B,\, [x]_B xB,x/B,[x]B 都可以用移位掩码来快速实现。 然而 x / g s ,   x ⋅ g s ,   x + [ x ] g s x/g_s,\, x\cdot g_s,\, x+[x]_{g_s} x/gs,xgs,x+[x]gs 等运算,必须在大整数 x x x 上处理,速度会相对较慢。

rANS 也是渐进最优码,满足:
∑ s ∈ [ n ] q s log ⁡ ( C ( s , x ) ) ≤ log ⁡ x + ∑ s ∈ [ n ] q s log ⁡ g s B + B x \sum_{s \in [n]} q_s \log(C(s,x)) \le \log x + \sum_{s \in [n]} q_s \log\frac{g_s}{B} + \frac{B}{x} s[n]qslog(C(s,x))logx+s[n]qslogBgs+xB
只要 x x x 远大于 B B B,并且 g s / B g_s/B gs/B q s q_s qs 的良好近似,那么平均码长就是香农熵。

Stream Encoding

对于无限符号序列,状态 x ∈ N x \in \N xN 的比特长度也将无限增长,这使得计算效率降低。 [Duda09] 提出了流式编码,将状态限制在一个固定的区间内,从而提高计算效率,但是压缩率会略微变差。

我们将状态 x x x 限制在范围 I : = { l , l + 1 , ⋯   , l b − 1 } I := \{l,l+1,\cdots,lb-1\} I:={l,l+1,,lb1},易知 ∣ I ∣ = ( b − 1 ) l |I| = (b-1)l I=(b1)l

在这里插入图片描述

利用上述三条规则,

  • 在编码时,
    1. 设置初值 x = l ∈ I x = l \in I x=lI
    2. 根据 C ( s , x ) C(s,x) C(s,x) 编码,直到即将 x ≥ l b x \ge lb xlb
    3. 不断将 [ x ] b [x]_b [x]b 输出,并更新 x ← ⌊ x / b ⌋ x \gets \lfloor x/b\rfloor xx/b,直到仍有 x ∈ I x \in I xI,然后回到 Step 2
    4. 在最后,将 x ∈ I x \in I xI 输出
  • 在解码时,
    1. 读取最近的状态 x ∈ I x \in I xI
    2. 根据 D ( x ) D(x) D(x) 解码,直到即将 x < l x < l x<l
    3. 不断读取大小为 b b b 的数据块 d d d,更新 x ← x b + d x \gets xb+d xxb+d,直到仍有 x ∈ I x \in I xI,然后回到 Step 2
    4. x = l x=l x=l 且输入流为空(真的回到了初值),终止程序,并翻转解码结果

为了达到渐进最优性,需要让 l l l 远大于 B B B。为了提高计算效率,可以将 l , b l,b l,b 也都设置为二的幂次,尤其是 w ∣ log ⁡ b w \mid \log b wlogb(这里 w = 8 / 32 w=8/32 w=8/32,取决于大数 x ∈ N x \in \N xN 的基本运算单元)

代码实现

首先要自行实现一个大数类 INT,我这里使用数组 uint32_t *data 构造,

struct INT // 基底 2^32 的无符号大整数
{
    uint32_t *data = nullptr;
    int32_t len = 0;

    INT(int len); // 构造函数

    INT(const INT &x); // 深度复制

    ~INT(); // 析构函数

    int size(); // 当前大小(字节)

    void resize(int len); // 重新申请内存

    void clear(); // 清零

    void add(uint32_t x); // 加法

    void sub(uint32_t x); // 减法

    void mul(uint32_t x); // 乘法

    uint32_t div(uint32_t x); // 下取整除法,返回余数

    void shift(int i); // 按 word 左移(乘以 2^32),支持负数
};

主要的 rANS 实现如下:

void ANS_Build_CDF(uint32_t *CDF, const uint32_t *G, const int B)
{
    CDF[0] = 1; // 处理第一个符号是 s=0 的情况,否则 CDF[0]=0 总导致 x = 0,无法解码;
    for (int s = 1; s < B; s++)
        CDF[s] = CDF[s - 1] + G[s - 1]; // 构造累计密度分布表,CDF[s] = Pr[x < s]
}

uint32_t ANS_Symbol(uint32_t x, const uint32_t *CDF, const int B)
{
    int64_t L = 0, R = B;

    while (true) // 二分法查表,找出 CDF[M] <= x < CDF[M+1]
    {
        if (L >= B - 1)
            return B - 1;
        if (R <= 0)
            return 0;

        int64_t M = (L + R) / 2;
        if (x < CDF[M])
            R = M;
        else if (x >= CDF[M + 1])
            L = M + 1;
        else
            return M;
    }
}

void ANS_C(INT &x, const uint32_t s, const uint32_t *G, const uint32_t *CDF, const int B)
{
    uint32_t gs = G[s];
    uint32_t rem = x.div(gs); // 大数 x 除以 uint32 数据

    x.shift(1); // 乘以 B = 2^32,这里大数 x 的基本单元为 uint32
    x.add(rem);
    x.add(CDF[s]);
}

uint32_t ANS_D(INT &y, const uint32_t *G, const uint32_t *CDF, const int B)
{
    uint32_t lsb = y.data[0];
    uint32_t t = ANS_Symbol(lsb, CDF, B);

    y.shift(-1); // 整除 B,同理
    y.mul(G[t]);
    y.add(lsb);
    y.sub(CDF[t]);

    return t;
}

void ANS_Encode(vector<uint32_t> &x, const uint32_t *s, const int slen, const uint32_t *G, const uint32_t *CDF, const int B) // 原始版本,渐进最优码
{
    INT y(4);
    for (int i = 0; i < slen; i++)
        ANS_C(y, s[i], G, CDF, B); // 迭代编码符号序列

    int xlen = y.size();
    x.resize(xlen);
    memcpy(x.data(), y.data, xlen*sizeof(uint32_t));
}

void ANS_Decode(uint32_t **s, int &slen, const vector<uint32_t> &x, const uint32_t *G, const uint32_t *CDF, const int B)
{
    int xlen = x.size();
    INT y(x.size());
    memcpy(y.data, x.data(), xlen*sizeof(uint32_t));
    vector<uint32_t> res;

    while (y.size() != 0)
        res.push_back(ANS_D(y, G, CDF, B)); // 解出当前尾部符号

    if (*s != nullptr)
        free(*s);

    slen = res.size();
    *s = (uint32_t *)malloc(slen * sizeof(uint32_t));
    for (int i = 0; i < slen; i++)
        (*s)[i] = res[slen - i - 1]; // 翻转序列
}

相关文章:

  • 【UTM】通用横轴墨卡托投影
  • WPF InkCanvas 控件详解
  • 树莓派超全系列文档--(7)RaspberryOS播放音频和视频
  • 等差数列公式推导
  • SynchronousQueue的不恰当使用,瞬时过载导致线程池任务被拒绝
  • 【hadoop】hadoop streaming
  • Vue3动态加载组件,警告:Vue received a Component than was made a reactive object
  • nginx-rtmp-module之ngx_rtmp.c代码详解
  • 使用 Chromedp 监听网页请求和响应
  • 利用脚本和Shader制作屏幕后处理效果
  • MOSN(Modular Open Smart Network)-04-TLS 安全链路
  • HCIA复习
  • go-zero: sqlx 对timestamp 格式数据问题
  • 罗杰斯特回归
  • 四川省汽车加气站操作工备考题库及答案分享
  • 蓝桥杯练习题--一年中的第几天
  • Numpy基础
  • LLM 加速技术有哪些
  • Linux--文件
  • “Failed to load steamui.dll” 文件丢失:原因分析与全面修复指南
  • 六省会共建交通枢纽集群,中部离经济“第五极”有多远?
  • 涉案资金超2亿元 “健康投资”骗局,专挑老年人下手
  • 长三角首次,在铁三赛事中感受竞技与生态的共鸣
  • 马上评|这种“维权”已经不算薅羊毛,涉嫌犯罪了
  • 问责!美国海军对“杜鲁门”号航母一系列事故展开调查
  • 鸿海下调全年营收展望:AI服务器业务强劲,预计今年营收增超50%