Вероятно, многие встречались с такой проблемой как нехватка оперативной памяти для решения той или иной задачи. Но порой данную проблему можно обойти, руководствуясь простому, но верному принципу: разделяй и властвуй. Данный подход может помочь не только в 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 для возможности работы с ними как с временными данными.
Далее применяем описанный выше алгоритм, который я обернул в функцию, где первый аргумент 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 секунд, а твой ПК не впадает в кому :)
Подведём итоги:
При решении задачи нужно оценить её размер, сложность и ресурсозатратность
Если задача трудная и объёмная, то следует разбить её на более мелкие части по принципу «Разделяй и властвуй»
Подобрать такие гиперпараметры, как размер и количество подзадач, учитывая специфику задачи
Всем большое спасибо и успехов в ML!
Комментарии (13)
NTDLL
07.02.2022 17:52Хм, дорисовка недостающих значений может быть не совсем точной
Попробуйте из исходных данных убрать некоторые промежуточные значения, и посчитайте их методом интерполяции. Интересно посмотреть на погрешности. И можно ли учитывать эти погрешности при более точном интерполировании
VladislavSoren Автор
07.02.2022 18:46Если периодически (через 15 или 20 значентий) встречаются пропуски, а ты восстанавливаешь временные данные с помощью интерполяции, то больших погрешностей не будет. А если их гораздо больше, то конечно лучше в принципе базу адекватнее собрать ????
NTDLL
07.02.2022 19:21В каком плане адекватнее?
Возможно взятый целиком массив вычисляется точнее, чем по частям https://ru.wikipedia.org/wiki/Интерполяционный_многочлен_Лагранжа. Хоть и ценой пожирания памяти.
VladislavSoren Автор
07.02.2022 20:01Я имел ввиду, что изначательно более качественный датасет даст больше достоверных данных без всяких ухищрений
И так же можно оптимально под максимум своей оперативки подобрать размер обрабатываемых кусков массива, что согласен даст большую точность
А дальше конечно наращивать вычислительные мощности нужно
vkni
07.02.2022 20:51Это зависит от изначальной функции, которой подчиняются данные, от того, есть ли там шумы.
Методов интерполяции много, поэтому вопрос "адекватнее" в некотором смысле философский. Если брать интерполяционный многочлен высокой степени, там будут ненаблюдаемые на реальных данных пики. Это нам на курсе выч. мата рассказывали. Поэтому отрезок бьют на части и берут полином низкой степени.
Причём нежелательные пики начинаются уже с кубической сплайновой интерполяции. В той же википедии (а лучше в статьях) можно прочесть про "монотонную эрмитову интерполяцию". Она убирает пики за счёт того, что вторая производная в местах сшивок.
Я недавно этим занимался - у клиентов был какой-то старый ни на что непохожий интерполяционный код на VBA (комментарии? git? - три ха-ха). И мы выясняли с коллегой, что же именно это за метод. Сошлись на том, что родственный Fritsch-Carlson/Fritsch-Butland.VladislavSoren Автор
07.02.2022 22:42Спасибо большое за информацию. Пороюсь в данны вопросах ????
vkni
08.02.2022 00:18Я как-то после рытья пришёл к выводу "а фиг его знает, что правильно". То есть, без чёткой модели данных совершенно непонятно, чем одна интерполяция лучше или хуже другой.
Вот этот несчастный пик/отскок в кубической интерполяции (представьте как будет выглядеть интерполяция sign) - он вроде неприятен, а ведь может быть и вполне разумен (в графике переходных процессов RC контура он есть!).
С монотонными методами (когда между точками данных не может быть пиков/впадин) совсем непонятно: там условие - это попадание параметров в некоторую четырёхугольную область.
Посмотрите, пожалуйста, может быть у вас статья будет, которая "откроет нам глаза" :-). С удовольствием посмотрел бы!
screwer
10.02.2022 10:0155 млн.значений и система встала колом ? Тогда как ты играх число треугольников в кадре и нет сопоставимое количество, и все они ещё трансформируются, отрисовываются и т.д. 60 раз в секунду и более
DustCn
>> компилятор героически взялся за выполнение данной операции
Компилятор грустит, когда компилятором называют что попало...
VladislavSoren Автор
Именно компилятор python начинает выполнение данного процесса, понятно что под капотом за это берётся железо (CPU и RAM), но началось же всё с него)
vkni
Интерпретатор?
VladislavSoren Автор
Да, ошибся. Спасибо ????
screwer
Скользкий момент. Сначала идёт компиляция в байткод, всегда. А уж потом он интерпретируется. Т е. ещё без машинного кода, но уже и без разбора текстовых языковых конструкций, в отличие от традиционных интерпретаторов (напр. bash).