Предварительная обработка данных временных рядов - хитрый прием для экономии памяти - PullRequest
0 голосов
/ 03 сентября 2018

Я предварительно обрабатываю набор данных временных рядов, меняя его форму с 2-х измерений (точки данных, объекты) на 3-х измерения (точки данных, временное окно, функции).

В таких перспективных временных окнах (иногда их также называют обратным просмотром) указывается количество предыдущих временных шагов / точек данных, которые используются в качестве входных переменных для прогнозирования следующего периода времени. Другими словами, временные интервалы - это то, сколько данных в прошлом алгоритм машинного обучения учитывает для одного прогноза в будущем.

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

Это функция, которую я использовал до сих пор для преобразования входных данных в трехмерную структуру.

from sys import getsizeof

def time_framer(data_to_frame, window_size=1):
    """It transforms a 2d dataset into 3d based on a specific size;
    original function can be found at:
    https://machinelearningmastery.com/time-series-prediction-lstm-recurrent-neural-networks-python-keras/
    """
    n_datapoints = data_to_frame.shape[0] - window_size
    framed_data = np.empty(
        shape=(n_datapoints, window_size, data_to_frame.shape[1],)).astype(np.float32)

    for index in range(n_datapoints):
        framed_data[index] = data_to_frame[index:(index + window_size)]
        print(framed_data.shape)

    # it prints the size of the output in MB
    print(framed_data.nbytes / 10 ** 6)
    print(getsizeof(framed_data) / 10 ** 6)

    # quick and dirty quality test to check if the data has been correctly reshaped        
    test1=list(set(framed_data[0][1]==framed_data[1][0]))
    if test1[0] and len(test1)==1:
        print('Data is correctly framed')

    return framed_data

Мне предложили использовать трюк Шага Нампи , чтобы преодолеть эту проблему и уменьшить размер измененных данных. К сожалению, любой ресурс, который я нашел до сих пор на эту тему, сфокусирован на реализации трюка на двумерном массиве, так же как этот превосходный учебник . Я боролся с моим вариантом использования, который включает в себя 3-мерный вывод. Вот лучшее, с чем я выступил; однако ему не удается уменьшить размер framed_data и правильно формировать данные, поскольку он не проходит проверку качества.

Я совершенно уверен, что моя ошибка связана с параметром шагов , который я не до конца понял. new_strides - единственные значения, которые мне удалось успешно передать в as_strided .

from numpy.lib.stride_tricks import as_strided

def strides_trick_time_framer(data_to_frame, window_size=1):

    new_strides = (data_to_frame.strides[0],
                   data_to_frame.strides[0]*data_to_frame.shape[1] ,
                   data_to_frame.strides[0]*window_size)

    n_datapoints = data_to_frame.shape[0] - window_size
    print('striding.....')
    framed_data = as_strided(data_to_frame, 
                             shape=(n_datapoints, # .flatten() here did not change the outcome
                                    window_size,
                                    data_to_frame.shape[1]),                   
                                    strides=new_strides).astype(np.float32)
    # it prints the size of the output in MB
    print(framed_data.nbytes / 10 ** 6)
    print(getsizeof(framed_data) / 10 ** 6)

    # quick and dirty test to check if the data has been correctly reshaped        
    test1=list(set(framed_data[0][1]==framed_data[1][0]))
    if test1[0] and len(test1)==1:
        print('Data is correctly framed')

    return framed_data

Любая помощь будет принята с благодарностью!

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Для этого X:

In [734]: X = np.arange(24).reshape(8,3)
In [735]: X.strides
Out[735]: (24, 8)

this as_strided создает тот же массив, что и ваш time_framer

In [736]: np.lib.stride_tricks.as_strided(X, 
            shape=(X.shape[0]-3, 3, X.shape[1]), 
            strides=(24, 24, 8))
Out[736]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11]],

       [[ 6,  7,  8],
        [ 9, 10, 11],
        [12, 13, 14]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]],

       [[12, 13, 14],
        [15, 16, 17],
        [18, 19, 20]]])

Он идет по последнему измерению, как и X. И со второго до последнего. Первый продвигается на одну строку, поэтому он тоже получает X.strides[0]. Таким образом, размер окна влияет только на форму, а не на шаги.

Так что в вашей as_strided версии просто используйте:

 new_strides = (data_to_frame.strides[0],
                data_to_frame.strides[0] ,
                data_to_frame.strides[1])

Незначительные исправления. Установите размер окна по умолчанию 2 или больше. 1 приводит к ошибке индексации в тесте.

framed_data[0,1]==framed_data[1,0]

Глядя getsizeof:

In [754]: sys.getsizeof(X)
Out[754]: 112
In [755]: X.nbytes
Out[755]: 192

Подождите, почему размер X меньше nbytes? Потому что это view (см. Строку [734] выше).

In [756]: sys.getsizeof(X.copy())
Out[756]: 304

Как отмечено в другом SO, getsizeof следует использовать с осторожностью:

Почему размер массива numpy отличается?

Теперь для расширенной копии:

In [757]: x2=time_framer(X,4)
...
In [758]: x2.strides
Out[758]: (96, 24, 8)
In [759]: x2.nbytes
Out[759]: 384
In [760]: sys.getsizeof(x2)
Out[760]: 512

и пошаговая версия

In [761]: x1=strides_trick_time_framer(X,4)
...
In [762]: x1.strides
Out[762]: (24, 24, 8)
In [763]: sys.getsizeof(x1)
Out[763]: 128
In [764]: x1.astype(int).strides
Out[764]: (96, 24, 8)
In [765]: sys.getsizeof(x1.astype(int))
Out[765]: 512

x1 размер такой же, как вид (128, потому что его 3d). Но если мы попытаемся изменить его dtype, он сделает копию, и шаги и размер будут такими же, как у x2.

Многие операции на x1 потеряют преимущество по размеру, x1.ravel(), x1+1 и т. Д. В основном операции сокращения, такие как mean и sum, дают реальную экономию пространства.

0 голосов
/ 03 сентября 2018

Вы можете использовать функцию шаблона шага window_nd Я сделал здесь

Затем, чтобы пройти только первое измерение, которое вам нужно

framed_data = window_nd(data_to_frame, window_size, axis = 0)

Пока не нашли встроенную оконную функцию, которая может работать на произвольных осях, поэтому, если недавно в scipy.signal или skimage не была реализована новая, это, вероятно, ваш лучший выбор.

РЕДАКТИРОВАТЬ: Чтобы увидеть экономию памяти, вам нужно будет использовать метод, описанный @ali_m здесь в качестве базового ndarray.nbytes наивно для общей памяти.

def find_base_nbytes(obj):
    if obj.base is not None:
        return find_base_nbytes(obj.base)
    return obj.nbytes
...