Divide and Rule
Divide and Rule

Вероятно, многие встречались с такой проблемой как нехватка оперативной памяти для решения той или иной задачи. Но порой данную проблему можно обойти, руководствуясь простому, но верному принципу: разделяй и властвуй. Данный подход может помочь не только в ML задачах, но и других проектах.

Мне была поставлена задача спрогнозировать изменение физической величины (неизвестного мне параметра на предприятии) на 240 шагов вперёд. Как вы уже поняли – это стандартная задача предсказания временного ряда.

Исходные данные представляли из себя поминутное изменение параметра:

Таблица изменений параметра
Таблица изменений параметра

Однако, по классике, с исходными данными как правило приходится пошаманить, чтобы привести их к виду пригодному для дальнейшей обработки и формированию обучающей выборки. В моём случае с удивительной периодичностью встречались пропуски, т.е. после 00.00.01 преспокойно могло идти 00.00.04, что немножко рушило целостность временного ряда.

Самым простым и действенным было прибегнуть к чудесной силе интерполяции из библиотеки Pandas:

Data = Data.Value.resample('s').interpolate()

Вероятно, я забыл упомянуть, что в исходных данных было около 900тыс. значений. Думаю, многие сейчас догадались, что произошло с моим ПК после того, как компилятор героически взялся за выполнение данной операции :)

После перезагрузки ПК я понял, что кусочек в 55 млн строк (учитывая, что пропуски будут заполнены ВСЕМИ недостающими секундами) - это тихий ужас для моей бедной RAM.

Вот тут-то и приходит на помощь принцип: Разделяй и властвуй.

Данный принцип активно применяется для экономии RAM во время обучения моделей в виде различных дата генераторов. Но лично для меня было не очевидно, что ещё на стадии предобработки кол-во данных может разбухнуть до неподъемных размеров для твоего железа.

В связи с этим мой алгоритм обработки выглядел следующим образом:
<> Берём кусок массива заданной нами длины

batch = data.iloc[index:index+xLen]

<> Производим всю необходимую обработку текущего куска, включая интерполяцию (в моём случае я произвожу несколько интерполяций для большего сглаживания значений, т.к. они были очень зашумлены, но суть остаётся той же)

batch = batch.Value.resample('s'
                  ).interpolate().resample('4T'
                         ).interpolate().resample('1T').interpolate()

<> Затем производим конкатенацию массивов на каждой итерации

batch_sum = pd.concat([batch_sum, batch])

Я произвожу сдвиг в массиве данных методом скользящего окна, но каждый может применять свой способ, т.к. важен именно принцип.

В первую очередь конвертируем строковые данные в данные типа Timestamp для возможности работы с ними как с временными данными.

Время перевода в Timestamp  - 100 секунд
Время перевода в Timestamp - 100 секунд

Далее применяем описанный выше алгоритм, который я обернул в функцию, где первый аргумент data - это сам массив, второй xLen – размер одного батча (кол-во строк) и третий step – шаг сдвига.

########################################################
# Функция интерполяции всего массива значений по частям.
########################################################

def create_samples(data, xLen, step):
       
    data_len = data.iloc[:].shape[0]
    index = 0            
    count = 0
    
    
    batch_sum = s_samp   # берём шаблон для конкатинации Serias объектов
    
    while (index + xLen <= data_len):          # Идём по всей длине массива значений
        t1 = time.time()
        
        batch = data.iloc[index:index+xLen]     # "Откусываем" пример длинной xLen
      
        batch = batch.Value.resample('s'
                        ).interpolate().resample('4T'
                               ).interpolate().resample('1T').interpolate()
               
        batch = batch.dropna()
     
        now_shape = batch.shape[0]
     
        count += now_shape
        
        batch_sum = pd.concat([batch_sum, batch])
        
        index += step                          # Смещаеммся вперёд на step
        
        if index % step*10 == 0:
            print(index, time.time()-t1)
            
    print(count, 'общее кол-во значений')
    
    return batch_sum
Время посекундной интерполяции всего массива данных - 36 секунд
Время посекундной интерполяции всего массива данных - 36 секунд

Как приятно видеть, что при правильном подходе задача решается всего за 36 секунд, а твой ПК не впадает в кому :)

Подведём итоги:

  • При решении задачи нужно оценить её размер, сложность и ресурсозатратность

  • Если задача трудная и объёмная, то следует разбить её на более мелкие части по принципу «Разделяй и властвуй»

  • Подобрать такие гиперпараметры, как размер и количество подзадач, учитывая специфику задачи

Всем большое спасибо и успехов в ML!

Комментарии (13)


  1. DustCn
    07.02.2022 17:40
    +4

    >> компилятор героически взялся за выполнение данной операции

    Компилятор грустит, когда компилятором называют что попало...


    1. VladislavSoren Автор
      07.02.2022 18:40
      -1

      Именно компилятор python начинает выполнение данного процесса, понятно что под капотом за это берётся железо (CPU и RAM), но началось же всё с него)


      1. vkni
        07.02.2022 19:49
        +4

        Интерпретатор?


        1. VladislavSoren Автор
          07.02.2022 22:32

          Да, ошибся. Спасибо ????


        1. screwer
          10.02.2022 09:59

          Скользкий момент. Сначала идёт компиляция в байткод, всегда. А уж потом он интерпретируется. Т е. ещё без машинного кода, но уже и без разбора текстовых языковых конструкций, в отличие от традиционных интерпретаторов (напр. bash).


  1. NTDLL
    07.02.2022 17:52

    Хм, дорисовка недостающих значений может быть не совсем точной

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


    1. VladislavSoren Автор
      07.02.2022 18:46

      Если периодически (через 15 или 20 значентий) встречаются пропуски, а ты восстанавливаешь временные данные с помощью интерполяции, то больших погрешностей не будет. А если их гораздо больше, то конечно лучше в принципе базу адекватнее собрать ????


      1. NTDLL
        07.02.2022 19:21

        В каком плане адекватнее?

        Возможно взятый целиком массив вычисляется точнее, чем по частям https://ru.wikipedia.org/wiki/Интерполяционный_многочлен_Лагранжа. Хоть и ценой пожирания памяти.


        1. VladislavSoren Автор
          07.02.2022 20:01

          Я имел ввиду, что изначательно более качественный датасет даст больше достоверных данных без всяких ухищрений

          И так же можно оптимально под максимум своей оперативки подобрать размер обрабатываемых кусков массива, что согласен даст большую точность

          А дальше конечно наращивать вычислительные мощности нужно


        1. vkni
          07.02.2022 20:51

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

          Методов интерполяции много, поэтому вопрос "адекватнее" в некотором смысле философский. Если брать интерполяционный многочлен высокой степени, там будут ненаблюдаемые на реальных данных пики. Это нам на курсе выч. мата рассказывали. Поэтому отрезок бьют на части и берут полином низкой степени.

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

          Я недавно этим занимался - у клиентов был какой-то старый ни на что непохожий интерполяционный код на VBA (комментарии? git? - три ха-ха). И мы выясняли с коллегой, что же именно это за метод. Сошлись на том, что родственный Fritsch-Carlson/Fritsch-Butland.


          1. VladislavSoren Автор
            07.02.2022 22:42

            Спасибо большое за информацию. Пороюсь в данны вопросах ????


            1. vkni
              08.02.2022 00:18

              Я как-то после рытья пришёл к выводу "а фиг его знает, что правильно". То есть, без чёткой модели данных совершенно непонятно, чем одна интерполяция лучше или хуже другой.

              Вот этот несчастный пик/отскок в кубической интерполяции (представьте как будет выглядеть интерполяция sign) - он вроде неприятен, а ведь может быть и вполне разумен (в графике переходных процессов RC контура он есть!).

              С монотонными методами (когда между точками данных не может быть пиков/впадин) совсем непонятно: там условие - это попадание параметров в некоторую четырёхугольную область.

              Посмотрите, пожалуйста, может быть у вас статья будет, которая "откроет нам глаза" :-). С удовольствием посмотрел бы!


  1. screwer
    10.02.2022 10:01

    55 млн.значений и система встала колом ? Тогда как ты играх число треугольников в кадре и нет сопоставимое количество, и все они ещё трансформируются, отрисовываются и т.д. 60 раз в секунду и более