web-dev-qa-db-fra.com

Compression des données en virgule flottante

Existe-t-il des méthodes de compression sans perte pouvant être appliquées aux données de série temporelle à virgule flottante, et surperformera considérablement, par exemple, l'écriture des données sous forme binaire dans un fichier et leur exécution via gzip?

Une réduction de la précision peut être acceptable, mais cela doit se faire de manière contrôlée (c’est-à-dire que je dois pouvoir définir une limite sur le nombre de chiffres à conserver).

Je travaille avec des fichiers de données volumineux qui sont des séries de doubles corrélées, décrivant une fonction du temps (c'est-à-dire que les valeurs sont corrélées). Je n’ai généralement pas besoin de la précision double complète, mais il se peut que j’ai besoin de plus de float.

Puisqu'il existe des méthodes spécialisées sans perte d'images/audio, je me demandais s'il existait quelque chose de spécialisé pour cette situation.

Précision: Je recherche des outils pratiques existants plutôt qu’un document décrivant comment mettre en œuvre quelque chose comme ceci. Quelque chose de comparable à gzip en vitesse serait excellent. 

36
Szabolcs
21
Bobrovsky

Voici quelques idées si vous souhaitez créer votre propre algorithme simple:

  • Utilisez xor de la valeur actuelle avec la valeur précédente pour obtenir un ensemble de bits décrivant la différence.
  • Divisez cette différence en deux parties: une partie correspond aux "bits de mantisse" et une autre partie aux "bits d'exposant".
  • Utilisez un codage à longueur variable (nombre différent de bits/octets par valeur), ou n’importe quelle méthode de compression que vous choisissez, pour enregistrer ces différences. Vous pouvez utiliser des flux séparés pour les mantisses et les exposants, car les mantisses ont plus de bits à compresser.
  • Cela peut ne pas fonctionner correctement si vous alternez entre deux sources de flux de valeur temporelle différentes. Il vous faudra donc peut-être compresser chaque source dans un flux ou un bloc distinct.
  • Pour perdre de la précision, vous pouvez supprimer les bits ou les octets les moins significatifs de la mantisse, tout en laissant l'exposant intact.
16
Frank Hileman

Puisque vous indiquez que vous avez besoin d'une précision quelque part entre «float» et «double»: vous pouvez mettre à zéro un nombre quelconque de bits les moins significatifs dans les floats à simple et double précision. Les nombres à virgule flottante IEEE-754 sont représentés binaires à peu près comme seeefffffffff, ce qui représente la valeur

signe * 1.fffffff * 2 ^ (eee).

Vous pouvez mettre à zéro les bits de fraction (f) les moins significatifs. Pour les flotteurs à simple précision (32 bits), il existe 23 fractions de bits dont vous pouvez mettre à zéro jusqu'à 22. Pour les systèmes à double précision (64 bits), il s'agit de 52 et jusqu'à 51. (Si vous mettez à zéro tous les bits , alors les valeurs spéciales NaN et +/- inf seront perdues).

Surtout si les données représentent des valeurs décimales telles que 1.2345, cela aidera à la compression des données. En effet, 1.2345 ne peut pas être représenté exactement comme une valeur à virgule flottante binaire, mais plutôt comme 0x3ff3c083126e978d, ce qui n’est pas favorable à la compression de données. Si vous coupez les 24 bits les moins significatifs, vous obtiendrez 0x3ff3c08312000000, qui est toujours précis à environ 9 chiffres décimaux (dans cet exemple, la différence est de 1,6e à 9). 

Si vous faites cela sur les données brutes, puis que vous stockez les différences entre les nombres sous-séquentiels, la compression (via gzip) sera encore plus conviviale si les données brutes varient lentement.

Voici un exemple en C:

#include <inttypes.h>

double double_trunc(double x, int zerobits)
{
  // mask is e.g. 0xffffffffffff0000 for zerobits==16
  uint64_t mask = -(1LL << zerobits);  
  uint64_t floatbits = (*((uint64_t*)(&x)));
  floatbits &= mask;
  x = * ((double*) (&floatbits));
  return x;
}

Et un en python/numpy:

import numpy as np

def float_trunc(a, zerobits):
    """Set the least significant <zerobits> bits to zero in a numpy float32 or float64 array.
    Do this in-place. Also return the updated array.
    Maximum values of 'nzero': 51 for float64; 22 for float32.
    """

at = a.dtype
assert at == np.float64 or at == np.float32 or at == np.complex128 or at == np.complex64
if at == np.float64 or at == np.complex128:
    assert nzero <= 51
    mask = 0xffffffffffffffff - (1 << nzero) + 1
    bits = a.view(np.uint64)
    bits &= mask
Elif at == np.float32 or at == np.complex64:
    assert nzero <= 22
    mask = 0xffffffff - (1 << nzero) + 1
    bits = a.view(np.uint32)
    bits &= mask

return a
3
Han-Kwang Nienhuys

Puisque vous demandez des outils existants, peut-être que zfp fera l'affaire.

2
primfaktor

Méthodes possibles pouvant être utilisées pour la compression en virgule flottante:

Vous pouvez tester toutes ces méthodes, avec vos données, en utilisant l’outil icapp pour Linux et Windows.

1
powturbo

Une technique utilisée par les utilisateurs de HDF5 est la "réorganisation", dans laquelle vous regroupez chaque octet pour N valeurs à virgule flottante. Ceci est plus susceptible de vous donner des séquences d'octets répétitives qui compresseront mieux avec gzip, par exemple .

Une deuxième méthode que j'ai trouvée et qui réduit considérablement la taille des données compressées en gzip consiste à convertir d'abord les données au format float16 (demi-précision) , puis à nouveau en float32. Cela produit beaucoup de zéros dans le flux de sortie, ce qui peut réduire la taille des fichiers de 40 à 60% environ après la compression. Une des subtilités est que la valeur float16 maximale est plutôt basse, vous pouvez donc commencer par redimensionner vos données, par exemple. en python

import numpy as np
import math

input = np.array(...)

# format can only hold 65504 maximum, so we scale input data
log2max = int(math.log(np.nanmax(input), 2))
scale = 2**(log2max - 14)
scaled = input * (1./scale)

# do the conversion to float16
temp_float16 = np.array(scaled, dtype=np.float16)
# convert back again and rescale
output = np.array(temp_float16, dtype=np.float32) * scale

Certains tests suggèrent que la différence fractionnaire absolue moyenne entre entrée et sortie pour certaines données est d'environ 0,00019 avec un maximum de 0,00048. Cela correspond à la précision 2 ** 11 de la mantisse.

1
xioxox

Vous pouvez utiliser l'algorithme de lissage exponentiel de Holt (qui est un algorithme de compression basé sur la prédiction). Initialement, attribuez un poids aux données et prédisez la valeur suivante. Si les deux données sont identiques, le MSB génère de nombreux zéros en effectuant l'opération XOR

1
Giriraj