算术编码器符号查找
arithmetic encoder symbol lookup
我有基于 Drdobbs ARcoder 的工作简单算术编码器。 de/encoding 算法归结为维护数组中的符号概率并更新它们。问题是我想扩展它以支持越来越多的符号,而不是固定的 +256 左右的符号。目前的实施
如果符号数量增加太多,update_model()
和 getsym()
的速度太慢。
我可以将 cumulative_freq
数组转换为树状数据结构以便更快地查找和更新吗?我不介意该算法是否最终会消耗更多内存,只要它可以更好地扩展即可。
const u32 MAX_INPUT_VAL = 255;
const u32 CODE_RESCALE = MAX_INPUT_VAL+1;
const u32 CODE_EOF = MAX_INPUT_VAL+2;
const u32 CUMULATIVE_TOTAL = MAX_INPUT_VAL+3; // total sum of all frequencies before.
const u32 CODES_END = MAX_INPUT_VAL+4;
u32 cumulative_freq[CODES_END];
// (Init symbol freqs)
for(u32 i = 0; i < CODES_END; ++i) {
cumulative_freq[i] = i;
}
// update value propability
void update_model(u32 v) {
// how can I make this scale better for more symbols?
for(code_t i = v + 1; i < CODES_END; ++i) {
cumulative_freq[i]++;
}
}
struct sym {
u32 low;
u32 high;
u32 count;
};
// encode symbol: get symbol propability
sym propability(u32 v) {
sym p{cumulative_freq[v], cumulative_freq[v+1], cumulative_freq[CUMULATIVE_TOTAL]};
update_model(v);
return p;
}
// decode symbol:
std::pair<sym, u32> getsym(u64 prop) {
// how can I make this scale better for more symbols?
for(u32 i = 0; i < CUMULATIVE_TOTAL; ++i) {
if(prop < cumulative_freq[i+1]) {
sym p{cumulative_freq[i],cumulative_freq[i+1],cumulative_freq[CUMULATIVE_TOTAL]};
update_model(i);
return {p,i};
}
}
// error: decode failed.
}
是的,您可以将计数排列成一棵树。每个符号都有一个包含其概率(非累积)的叶子,然后每个内部节点包含其下方节点的概率总和。
要更新模型,您需要将符号的叶及其每个祖先加 1。要进行搜索,您可以在下降到树中时动态计算符号的累积概率——每次向右移动时,您都会从左侧添加计数 child 您没有输入。
这是一个顺序统计树,但使用累积概率而不是排名:https://en.wikipedia.org/wiki/Order_statistic_tree
然而,这棵树的结构永远不会改变。没有插入或删除。因此,您不需要使用带有指针等的实际节点 objects。您可以只使用一个计数数组,像二进制堆数组一样排列:https://www.geeksforgeeks.org/array-representation-of-binary-heap/
如果你有 N 个符号,那么内部节点将有 N-1 个计数,然后是叶符号的 N 个计数,除根之外的每个节点在 floor( index-1)/2.
这个结构还是比较节省内存的,所有的操作都需要O(log N)的时间,而且代码量很小。
更新看起来像这样:
unsigned i = symbol + num_symbols-1;
for (;;) {
++counts[i];
if (!i)
break;
i = (i-1)/2;
}
获取交易品种的累计 P:
unsigned P=0;
for (unsigned i=symbol+num_symbols-1; i>0; i=(i-1)/2) {
if (!(i&1)) {
//this is a right child, add counts from the left
P+=counts[i-1];
}
}
通过累积概率获取符号:
unsigned P=0;
unsigned i=0;
while (i<(num_symbols-1)) {
i=i*2+1; //left child
if (targetP >= P+counts[i]) {
//use right child instead
P+=counts[i];
++i;
}
}
unsigned symbol = i-(num_symbols-1);
请注意 smallest-indexed 符号 (num_symbols-1) 不是树中的 left-most 除非 num_symbols 是 2 的幂,但那应该没问题。只要编码器和解码器一致,符号的顺序与您的应用程序无关。
我有基于 Drdobbs ARcoder 的工作简单算术编码器。 de/encoding 算法归结为维护数组中的符号概率并更新它们。问题是我想扩展它以支持越来越多的符号,而不是固定的 +256 左右的符号。目前的实施
如果符号数量增加太多,update_model()
和 getsym()
的速度太慢。
我可以将 cumulative_freq
数组转换为树状数据结构以便更快地查找和更新吗?我不介意该算法是否最终会消耗更多内存,只要它可以更好地扩展即可。
const u32 MAX_INPUT_VAL = 255;
const u32 CODE_RESCALE = MAX_INPUT_VAL+1;
const u32 CODE_EOF = MAX_INPUT_VAL+2;
const u32 CUMULATIVE_TOTAL = MAX_INPUT_VAL+3; // total sum of all frequencies before.
const u32 CODES_END = MAX_INPUT_VAL+4;
u32 cumulative_freq[CODES_END];
// (Init symbol freqs)
for(u32 i = 0; i < CODES_END; ++i) {
cumulative_freq[i] = i;
}
// update value propability
void update_model(u32 v) {
// how can I make this scale better for more symbols?
for(code_t i = v + 1; i < CODES_END; ++i) {
cumulative_freq[i]++;
}
}
struct sym {
u32 low;
u32 high;
u32 count;
};
// encode symbol: get symbol propability
sym propability(u32 v) {
sym p{cumulative_freq[v], cumulative_freq[v+1], cumulative_freq[CUMULATIVE_TOTAL]};
update_model(v);
return p;
}
// decode symbol:
std::pair<sym, u32> getsym(u64 prop) {
// how can I make this scale better for more symbols?
for(u32 i = 0; i < CUMULATIVE_TOTAL; ++i) {
if(prop < cumulative_freq[i+1]) {
sym p{cumulative_freq[i],cumulative_freq[i+1],cumulative_freq[CUMULATIVE_TOTAL]};
update_model(i);
return {p,i};
}
}
// error: decode failed.
}
是的,您可以将计数排列成一棵树。每个符号都有一个包含其概率(非累积)的叶子,然后每个内部节点包含其下方节点的概率总和。
要更新模型,您需要将符号的叶及其每个祖先加 1。要进行搜索,您可以在下降到树中时动态计算符号的累积概率——每次向右移动时,您都会从左侧添加计数 child 您没有输入。
这是一个顺序统计树,但使用累积概率而不是排名:https://en.wikipedia.org/wiki/Order_statistic_tree
然而,这棵树的结构永远不会改变。没有插入或删除。因此,您不需要使用带有指针等的实际节点 objects。您可以只使用一个计数数组,像二进制堆数组一样排列:https://www.geeksforgeeks.org/array-representation-of-binary-heap/
如果你有 N 个符号,那么内部节点将有 N-1 个计数,然后是叶符号的 N 个计数,除根之外的每个节点在 floor( index-1)/2.
这个结构还是比较节省内存的,所有的操作都需要O(log N)的时间,而且代码量很小。
更新看起来像这样:
unsigned i = symbol + num_symbols-1;
for (;;) {
++counts[i];
if (!i)
break;
i = (i-1)/2;
}
获取交易品种的累计 P:
unsigned P=0;
for (unsigned i=symbol+num_symbols-1; i>0; i=(i-1)/2) {
if (!(i&1)) {
//this is a right child, add counts from the left
P+=counts[i-1];
}
}
通过累积概率获取符号:
unsigned P=0;
unsigned i=0;
while (i<(num_symbols-1)) {
i=i*2+1; //left child
if (targetP >= P+counts[i]) {
//use right child instead
P+=counts[i];
++i;
}
}
unsigned symbol = i-(num_symbols-1);
请注意 smallest-indexed 符号 (num_symbols-1) 不是树中的 left-most 除非 num_symbols 是 2 的幂,但那应该没问题。只要编码器和解码器一致,符号的顺序与您的应用程序无关。