Сжатие данных с плавающей запятой
Существуют ли методы сжатия без потерь, которые могут применяться к данным временных рядов с плавающей запятой, и значительно превзойдут, скажем, запись данных в виде двоичных файлов в файл и запуск через 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
Для сжатия с плавающей запятой могут использоваться два возможных метода: