Как правильно масштабировать данные в нейросети
Я обучаю нейронную сеть. У НС три входа - два для временной последовательности (слои LSTM) и один просто числовой (полносвязный слой Dense). Структура нейросети через .summary(): 
Проблема вот в чем. На данный момент в процессе подготовки данных я масштабирую данные в диапазон от 0 до 1 через MinMaxScaler (отдельный для каждого входного параметра). Таким образом у меня получается вход из из двух массивов длинной по 50 чисел в диапазоне [0;1] и одного числа в том же диапазоне (т.е. размерности входов (n, 50, 1); (n, 50, 1); (n, 1)). Надо сказать, что нейронка сходится и показывает адекватные результаты не только в процессе обучения, но и на тестировании по другим данным. То есть функционально вроде все верно.
Теперь проблема: При подготовке датасета я беру сразу большое количество данных (условно, 10000 измерений) и масштабирую сразу весь ряд:
data_close = scaler_close.fit_transform(gazp.filter(['close'])[100:])
data_proffit = scaler_proffit.fit_transform(gazp.filter(['proffit'])[100:])
data_rsi = scaler_rsi.fit_transform(rsi.values[100:].reshape(-1,1 ))
data_macd = scaler_macd.fit_transform(macdhist.values[100:].reshape(-1,1 ))
len(data_macd), len(data_close)
Дело в том, что сама нейронная сеть обучается на выборке с окном в 50 отсчетов, то есть для предсказания (если я верно понял) - ей нужно как раз 50 отсчетов. А кроме того, нейросеть должна работать динамически с новыми данными, но если сначала разбивать датасет на куски по 50 отсчетов и потом применять скейлер, то нейросеть перестает работать и выдает чушь. Поэтому у меня возник вопрос - как правильно в данном случае масштабировать значения в нужный диапазон, если нейросеть должна предсказывать динамический результат на основе поступающих данных?
Ответы (1 шт):
data_close = scaler_close.fit_transform(gazp.filter(['close'])[100:])
Если скейлер применяется не один раз, а на частях данных, тогда нужно разделить fit_transform на отдельные этапы. В идеале fit нужно делать на всех данных, а потом можно делать transform сколько угодно раз на каких угодно частях. Если нет возможности сделать fit на всех данных - делайте на тех, которые доступны в данный момент. Главное - зафиксировать шкалу. А если вы каждый раз делаете fit_transform, то вы меняете шкалу преобразования каждый раз и на выходе получается ерунда, конечно.
scaler_close.fit(gazp.filter(['close'])[100:])
...
for i in range...
data_close = scaler_close.transform(gazp.filter(['close'])[i:i+50])
...
...
И так по каждому из ваших скейлеров фич отдельно.
А теперь слайды. Я взял вырожденный случай, когда данные равномерно растут всю дорогу. С реальными данными картина будет ещё лучше при fit по кусочку данных, скорее всего шкала будет ближе к [0,1] даже в этом случае. Но в любом случае с тем, что шкала не идеально вписывается в [0,1] нейросеть должна нормально справиться. А вот если данные будут испорчены, то результата от неё можно не ждать вообще.
Но главное тут - что при fit_transform на каждом отдельном куске данных эти куски будут плохо согласованы по значениям, вот что я хотел показать.
Код для слайдов:
from sklearn.preprocessing import MinMaxScaler
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
n = 100
k = 20
x = np.arange(n)
y = x
plt.figure()
sns.scatterplot(x=x, y=y)
plt.title('Исходные данные')
scaler = MinMaxScaler()
y1 = scaler.fit_transform(y.reshape(-1,1))[:,0]
plt.figure()
sns.scatterplot(x=x, y=y1)
plt.title('Масштабирование сразу всех данных')
plt.figure()
for i in range(0, n, k):
x1 = range(i,i+k)
y1 = y[x1]
y1 = scaler.fit_transform(y1.reshape(-1,1))[:,0]
sns.scatterplot(x=x1, y=y1)
plt.title('Масштабирование частями через fit_transform')
scaler.fit(y.reshape(-1,1))
plt.figure()
for i in range(0, n, k):
x1 = range(i,i+k)
y1 = y[x1]
y1 = scaler.transform(y1.reshape(-1,1))[:,0]
sns.scatterplot(x=x1, y=y1)
plt.title('fit на всех данных, transform по частям')
scaler.fit(y[:k].reshape(-1,1))
plt.figure()
for i in range(0, n, k):
x1 = range(i,i+k)
y1 = y[x1]
y1 = scaler.transform(y1.reshape(-1,1))[:,0]
sns.scatterplot(x=x1, y=y1)
plt.title('fit на первой части данных, transform по частям')
Ещё картинки с синусоидой.
y = np.sin(5*np.pi*x/n)
Видно, что при частичном fit масштаб вместо [0,1] получился [-1,1], но график сохранился. А при fit_transform каждый раз синус явно испортился.







