Los sistemas numerales asimétricos ( ANS ) [1] [2] son una familia de métodos de codificación de entropía introducidos por Jarosław (Jarek) Duda [3] de la Universidad Jagellónica , utilizados en la compresión de datos desde 2014 [4] debido a un rendimiento mejorado en comparación con los métodos anteriores. [5] ANS combina la relación de compresión de la codificación aritmética (que utiliza una distribución de probabilidad casi precisa ), con un costo de procesamiento similar al de la codificación de Huffman . En la variante ANS tabulada (tANS), esto se logra construyendo una máquina de estados finitos para operar en un alfabeto grande sin usar la multiplicación.
Entre otros, ANS se utiliza en el compresor Zstandard de Facebook [6] [7] (también utilizado, por ejemplo, en el kernel de Linux , [8] el navegador Google Chrome , [9] el sistema operativo Android [10] , se publicó como RFC 8478 para MIME [11] y HTTP [12] ), el compresor Apple LZFSE , [13] el compresor 3D Google Draco [14] (utilizado, por ejemplo, en el formato Pixar Universal Scene Description [15] ) y el compresor de imágenes PIK, [16] el compresor CRAM DNA [17] de las utilidades SAMtools , [18] la biblioteca de compresión de alta velocidad NVIDIA nvCOMP, [19] el compresor Dropbox DivANS, [20] el compresor de textura Microsoft DirectStorage BCPack, [21] y el compresor de imágenes JPEG XL [22] .
La idea básica es codificar la información en un único número natural . En el sistema numérico binario estándar, podemos añadir un poco de información a añadiendo al final de , lo que nos da . Para un codificador de entropía, esto es óptimo si . ANS generaliza este proceso para conjuntos arbitrarios de símbolos con una distribución de probabilidad acompañante . En ANS, si la información de se añade a para dar como resultado , entonces . De manera equivalente, , donde es el número de bits de información almacenados en el número , y es el número de bits contenidos en el símbolo .
Para la regla de codificación, el conjunto de números naturales se divide en subconjuntos disjuntos que corresponden a diferentes símbolos, como números pares e impares, pero con densidades correspondientes a la distribución de probabilidad de los símbolos a codificar. Luego, para agregar información del símbolo a la información ya almacenada en el número actual , pasamos a número que es la posición de la -ésima aparición del -ésimo subconjunto.
Existen formas alternativas de aplicarlo en la práctica: fórmulas matemáticas directas para los pasos de codificación y decodificación (variantes uABS y rANS), o se puede poner todo el comportamiento en una tabla (variante tANS). La renormalización se utiliza para evitar llegar al infinito (transferir bits acumulados hacia o desde el flujo de bits).
Supongamos que se codifica una secuencia de 1000 ceros y unos, lo que requeriría 1000 bits para almacenarse directamente. Sin embargo, si de alguna manera se sabe que solo contiene 1 cero y 999 unos, sería suficiente codificar la posición del cero, lo que requiere solo bits en lugar de los 1000 bits originales.
En general, a estas secuencias de longitud que contienen ceros y unos, para una probabilidad determinada , se las llama combinaciones . Utilizando la aproximación de Stirling obtenemos que su número asintótico es
llamada entropía de Shannon .
Por lo tanto, para elegir una de estas secuencias necesitamos aproximadamente bits. Sigue siendo bits si , pero también puede ser mucho más pequeño. Por ejemplo, necesitamos solo bits para .
Un codificador de entropía permite codificar una secuencia de símbolos utilizando aproximadamente los bits de entropía de Shannon por símbolo. Por ejemplo, ANS podría utilizarse directamente para enumerar combinaciones: asignar un número natural diferente a cada secuencia de símbolos que tengan proporciones fijas de una manera casi óptima.
A diferencia de las combinaciones de codificación, esta distribución de probabilidad suele variar en los compresores de datos. Para este propósito, la entropía de Shannon puede verse como un promedio ponderado: un símbolo de probabilidad contiene bits de información. ANS codifica la información en un solo número natural , que se interpreta como que contiene bits de información. Agregar información de un símbolo de probabilidad aumenta este contenido informativo a . Por lo tanto, el nuevo número que contiene ambas informaciones debería ser .
Consideremos una fuente con 3 letras A, B, C, con probabilidad 1/2, 1/4, 1/4. Es sencillo construir el código de prefijo óptimo en binario: A = 0, B = 10, C = 11. Luego, un mensaje se codifica como ABC -> 01011.
Vemos que un método equivalente para realizar la codificación es el siguiente:
Consideremos una fuente más general con k letras, con probabilidades racionales . Entonces, para realizar la codificación aritmética en la fuente, solo se requiere aritmética exacta con números enteros.
En general, ANS es una aproximación de codificación aritmética que aproxima las probabilidades reales mediante números racionales con un denominador pequeño .
Imaginemos que hay cierta información almacenada en un número natural , por ejemplo, como una secuencia de bits de su expansión binaria. Para agregar información de una variable binaria , podemos usar la función de codificación , que desplaza todos los bits una posición hacia arriba y coloca el nuevo bit en la posición menos significativa. Ahora, la función de decodificación permite recuperar el bit anterior y este agregado: . Podemos comenzar con el estado inicial, luego usar la función en los bits sucesivos de una secuencia de bits finita para obtener un número final que almacene esta secuencia completa. Luego, usar la función varias veces hasta permite recuperar la secuencia de bits en orden inverso.
El procedimiento anterior es óptimo para la distribución de probabilidad uniforme (simétrica) de los símbolos . ANS lo generaliza para que sea óptimo para cualquier distribución de probabilidad (asimétrica) elegida de los símbolos: . Mientras que en el ejemplo anterior se elegía entre pares e impares , en ANS esta división par/impar de números naturales se reemplaza con la división en subconjuntos que tienen densidades correspondientes a la distribución de probabilidad supuesta : hasta la posición , hay aproximadamente ocurrencias del símbolo .
La función de codificación devuelve la -ésima aparición de dicho subconjunto correspondiente al símbolo . La suposición de densidad es equivalente a la condición . Suponiendo que un número natural contiene bits de información, . Por lo tanto, el símbolo de probabilidad se codifica como si contuviera bits de información, tal como lo exigen los codificadores de entropía .
Empecemos con el alfabeto binario y una distribución de probabilidad , . Hasta la posición queremos análogos aproximados de números impares (para ). Podemos elegir este número de apariciones como , obteniendo . Esta variante se llama uABS y conduce a las siguientes funciones de codificación y decodificación: [23]
Descodificación:
s = ceil (( x + 1 ) * p ) - ceil ( x * p ) // 0 si fract(x*p) < 1-p, de lo contrario 1 si s = 0 entonces new_x = x - ceil ( x * p ) // D(x) = (new_x, 0), esto es lo mismo que new_x = floor(x*(1-p)) si s = 1 entonces new_x = ceil ( x * p ) // D(x) = (new_x, 1)
Codificación:
si s = 0 entonces nuevo_x = techo (( x + 1 ) / ( 1 - p )) - 1 // C(x,0) = nuevo_x si s = 1 entonces nuevo_x = piso ( x / p ) // C(x,1) = nuevo_x
Dado que se trata del sistema binario estándar (con 0 y 1 invertidos), para un sistema diferente resulta óptimo para esta distribución de probabilidad dada. Por ejemplo, para estas fórmulas se obtiene una tabla para valores pequeños de :
El símbolo corresponde a un subconjunto de números naturales con densidad , que en este caso son las posiciones . Como , estas posiciones aumentan en 3 o 4. Porque aquí, el patrón de símbolos se repite cada 10 posiciones.
La codificación se puede encontrar tomando la fila correspondiente a un símbolo dado y eligiendo el dado en esta fila. Luego, la fila superior proporciona . Por ejemplo, desde la fila del medio hasta la fila superior.
Imaginemos que queremos codificar la secuencia '0100' a partir de . Primero nos lleva a , luego a , luego a , luego a . Al utilizar la función de decodificación en este , podemos recuperar la secuencia de símbolos. Utilizando la tabla para este propósito, en la primera fila determina la columna, luego la fila no vacía y el valor escrito determinan el y correspondiente .
La variante de rango también utiliza fórmulas aritméticas, pero permite operar sobre un alfabeto grande. Intuitivamente, divide el conjunto de números naturales en rangos de tamaño y divide cada uno de ellos de manera idéntica en subrangos de proporciones dados por la distribución de probabilidad supuesta.
Comenzamos con la cuantificación de la distribución de probabilidad al denominador, donde se elige n (generalmente 8-12 bits): para algunos números naturales (tamaños de subrangos).
Denotemos , y una función de distribución acumulativa:
Tenga en cuenta que la función no es una verdadera función de distribución de probabilidad (CDF) ya que la probabilidad del símbolo actual no está incluida en el valor de la expresión. En cambio, representa la probabilidad total de todos los símbolos anteriores. Ejemplo: en lugar de la definición normal de , se evalúa como , ya que no hay símbolos anteriores.CDF[s]
CDF[s]
CDF[0] = f[0]
CDF[0] = 0
Para denotar función (normalmente en tabla)
símbolo ( y ) = s tal que CDF [ s ] <= y < CDF [ s + 1 ]
Ahora la función de codificación es:
C ( x , s ) = ( piso ( x / f [ s ]) << n ) + ( x % f [ s ]) + CDF [ s ]
Descodificación:s = symbol(x & mask)
D ( x ) = ( f [ s ] * ( x >> n ) + ( x & máscara ) - CDF [ s ], s )
De esta manera podemos codificar una secuencia de símbolos en un gran número natural x . Para evitar el uso de aritmética de números grandes, en la práctica se utilizan variantes de flujo: que se aplican mediante renormalización: enviando los bits menos significativos de x hacia o desde el flujo de bits (normalmente L y b son potencias de 2).
En la variante rANS, x es, por ejemplo, de 32 bits. Para la renormalización de 16 bits, el decodificador rellena los bits menos significativos del flujo de bits cuando es necesario:
si ( x < ( 1 << 16 )) x = ( x << 16 ) + leer16bits ()
La variante tANS coloca todo el comportamiento (incluida la renormalización) en una tabla que produce una máquina de estados finitos evitando la necesidad de multiplicación.
Finalmente, el paso del bucle de decodificación se puede escribir como:
t = decodingTable ( x ); x = t.newX + readBits ( t.nbBits ) ; // transición de estado writeSymbol ( t.symbol ) ; // símbolo decodificado
El paso del bucle de codificación:
s = ReadSymbol (); nbBits = ( x + ns [ s ]) >> r ; // # de bits para renormalización writeBits ( x , nbBits ); // envía los bits menos significativos al flujo de bits x = encodingTable [ start [ s ] + ( x >> nbBits )];
Una codificación tANS específica se determina asignando un símbolo a cada posición, su número de apariciones debe ser proporcional a las probabilidades asumidas. Por ejemplo, se podría elegir la asignación "abdacdac" para la distribución de probabilidad Pr(a)=3/8, Pr(b)=1/8, Pr(c)=2/8, Pr(d)=2/8. Si los símbolos se asignan en rangos de longitudes que sean potencias de 2, obtendríamos la codificación de Huffman . Por ejemplo, se obtendría el código de prefijo a->0, b->100, c->101, d->11 para tANS con la asignación de símbolo "aaaabcdd".
En cuanto a la codificación de Huffman, modificar la distribución de probabilidad de tANS es relativamente costoso, por lo que se utiliza principalmente en situaciones estáticas, normalmente con algún esquema Lempel–Ziv (por ejemplo, ZSTD, LZFSE). En este caso, el archivo se divide en bloques: para cada uno de ellos, las frecuencias de los símbolos se cuentan de forma independiente y, después de la aproximación (cuantificación), se escriben en el encabezado del bloque y se utilizan como distribución de probabilidad estática para tANS.
Por el contrario, rANS se utiliza generalmente como un reemplazo más rápido para la codificación de rango (por ejemplo, CRAM , LZNA, Draco, [14] ). Requiere multiplicación, pero es más eficiente en el uso de la memoria y es adecuado para adaptar dinámicamente las distribuciones de probabilidad.
La codificación y decodificación de ANS se realizan en direcciones opuestas, lo que lo convierte en una pila de símbolos. Este inconveniente generalmente se resuelve codificando en dirección inversa, después de lo cual la decodificación puede realizarse hacia adelante. Para la dependencia del contexto, como el modelo de Markov , el codificador debe usar el contexto desde la perspectiva de la decodificación posterior. Para la adaptabilidad, el codificador primero debe avanzar para encontrar probabilidades que serán utilizadas (predichas) por el decodificador y almacenarlas en un búfer, luego codificar en dirección inversa utilizando las probabilidades almacenadas en el búfer.
El estado final de la codificación es necesario para iniciar la decodificación, por lo que debe almacenarse en el archivo comprimido. Este costo se puede compensar almacenando cierta información en el estado inicial del codificador. Por ejemplo, en lugar de comenzar con el estado "10000", comience con el estado "1****", donde "*" son algunos bits almacenados adicionales, que se pueden recuperar al final de la decodificación. Alternativamente, este estado se puede utilizar como suma de comprobación iniciando la codificación con un estado fijo y probando si el estado final de la decodificación es el esperado.
El autor del nuevo algoritmo ANS y sus variantes tANS y rANS tenía la intención específica de que su trabajo estuviera disponible de forma gratuita en el dominio público, por razones altruistas. No ha buscado lucrarse con ellos y tomó medidas para asegurarse de que no se convirtieran en un "campo minado legal", ni que otros los restringieran o se beneficiaran de ellos. En 2015, Google publicó una patente estadounidense y luego mundial para la "codificación de coeficientes ANS de tokens booleanos mixtos". [24] En ese momento, Google le había pedido al profesor Duda que lo ayudara con la compresión de video, por lo que conocía íntimamente este dominio, y contó con la asistencia del autor original.
Duda no se sintió satisfecho con el descubrimiento (accidental) de las intenciones de Google con respecto a la patente, dado que había dejado en claro que la quería como dominio público y había ayudado a Google específicamente sobre esa base. Posteriormente, Duda presentó una solicitud de un tercero [25] ante la Oficina de Patentes de los Estados Unidos para solicitar su rechazo. La USPTO rechazó su solicitud en 2018 y, posteriormente, Google abandonó la patente. [26]
En junio de 2019, Microsoft presentó una solicitud de patente denominada "Características de la codificación y decodificación de sistemas numéricos asimétricos de rango". [27] La USPTO emitió un rechazo final de la solicitud el 27 de octubre de 2020. Sin embargo, el 2 de marzo de 2021, Microsoft presentó una presentación explicativa ante la USPTO en la que afirmaba "El solicitante respetuosamente no está de acuerdo con los rechazos", [28] buscando revocar el rechazo final en virtud del programa "After Final Consideration Pilot 2.0". [29] Después de la reconsideración, la USPTO concedió la solicitud el 25 de enero de 2022. [30]