Сжатие данных с плавающей запятой

Существуют ли методы сжатия без потерь, которые могут применяться к данным временных рядов с плавающей запятой, и значительно превзойдут, скажем, запись данных в виде двоичных файлов в файл и запуск через gzip?

Сокращение точности может быть приемлемым, но оно должно выполняться контролируемым образом (то есть я должен иметь возможность установить ограничение на количество цифр)

Я работаю с некоторыми большими файлами данных, которые являются рядами коррелированных double s, описывающих функцию времени (то есть значения коррелируют). Обычно мне не нужна полная точность double, но мне может понадобиться больше float.

Поскольку существуют специальные методы без потерь для изображений/аудио, мне было интересно, существует ли что-нибудь специализированное для этой ситуации.

Разъяснение: Я ищу существующие практические инструменты, а не документ, описывающий, как реализовать что-то подобное. Что-то, сравнимое с gzip в скорости, было бы превосходным.

Ответ 1

Возможно, вам стоит взглянуть на эти ресурсы:

Вы также можете попробовать Logluv-сжатый TIFF, подумал, что я не использовал их сам.

Ответ 2

Вот некоторые идеи, если вы хотите создать свой собственный простой алгоритм:

  • Используйте xor текущего значения с предыдущим значением, чтобы получить набор бит, описывающих разницу.
  • Разделите это различие на две части: одна часть - "бит мантиссы", а одна часть - "биты экспоненты".
  • Используйте кодировку переменной длины (разное количество бит/байтов на значение) или любой метод сжатия, который вы выбираете, чтобы сохранить эти различия. Вы можете использовать отдельные потоки для мантисса и экспонентов, так как мантиссы имеют больше бит для сжатия.
  • Это может не сработать, если вы чередуетесь с двумя разными источниками потоков времени. Таким образом, вам может потребоваться сжать каждый источник в отдельный поток или блок.
  • Чтобы потерять точность, вы можете удалить младшие значащие биты или байты из мантиссы, оставив экспоненту неповрежденной.

Ответ 3

Поскольку вы заявляете, что вам нужна точность где-то между "float" и "double": вы можете обнулить любое количество наименее значимых бит в полях с плавающей точкой и с двойной точностью. Номера с плавающей запятой IEEE-754 представляются двоичными примерно как seeefffffffff, которые представляют значение

знак * 1.fffffff * 2 ^ (еее).

Вы можете обнулить бит наименьшей значимости (f). Для одноточечных (32-битных) поплавков есть 23 дробных разряда, из которых вы можете обнулить до 22. Для двухточечной (64-разрядной) - 52 и до 51. (Если вы обнулите все биты, то специальные значения NaN и +/- inf будут потеряны).

Особенно, если данные представляют десятичные значения, такие как 1.2345, это поможет в сжатии данных. Это потому, что 1.2345 нельзя представить точно как двоичное значение с плавающей запятой, а скорее как 0x3ff3c083126e978d, что не является дружественным к сжатию данных. Отключение наименее значимых 24 бит приведет к 0x3ff3c08312000000, который по-прежнему будет точным до 9 десятичных цифр (в этом примере разница составляет 1,6е-9).

Если вы сделаете это по необработанным данным, а затем сохраните различия между подпоследовательными номерами, это будет еще более дружественным к сжатию (через gzip), если исходные данные будут меняться медленно.

Вот пример в 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;
}

И один в 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

Ответ 4

Один метод, который используют люди HDF5, - "перетасовка", где вы группируете каждый байт для N значений с плавающей точкой вместе. Это, скорее всего, даст вам повторяющиеся последовательности байтов, которые лучше сжимаются с помощью gzip, .

Второй метод, который я нашел, который значительно уменьшает размер сжатых gzipped-данных, заключается в том, чтобы сначала преобразовать данные в формат float16 (half-precision) и обратно в float32. Это приводит к множеству нулей в выходном потоке, которые после сжатия могут уменьшить размер файлов примерно на 40-60%. Одна тонкость заключается в том, что максимальное значение float16 довольно низкое, поэтому вы можете сначала масштабировать свои данные, например. в 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

Некоторые тесты показывают, что средняя абсолютная дробная разница между входом и выходом для некоторых данных составляет около 0,00019 с максимумом 0,00048. Это соответствует точности мантиссы 2 ** 11.

Ответ 5

Вы можете использовать алгоритм сглаживания Холта Экспоненты (который является алгоритмом сжатия на основе прогнозирования). Первоначально назначьте некоторый вес данным и предскажите следующее значение. Если оба данных одинаковы, он производит много нулей в MSB, выполняя операцию XOR

Ответ 6

Для сжатия с плавающей запятой могут использоваться два возможных метода: