Por que há uma diferença entre prever no conjunto de validação e no conjunto de teste?

8

Eu tenho um modelo XGBoost tentando prever se uma moeda vai subir ou descer no próximo período (5 min). Eu tenho um conjunto de dados de 2004 a 2018. Dividi os dados aleatoriamente em 95% de treinamento e 5% de validação, e a precisão no conjunto de Validação é de até 55%. Quando uso o modelo em um novo conjunto de testes (dados de 2019), a precisão cai para menos de 51%.

Alguém pode explicar por que isso pode ser?

Quero dizer, suponho que o modelo não "tenha visto" (treinado) os dados de validação mais do que os dados de teste;

Anexei um modelo simples abaixo para ilustrar. Esse dá 54% no conjunto de validação, mas apenas 50,9% no conjunto de teste .

Grato por qualquer ajuda!

NB Uma teoria que eu tinha era que, como alguns dos recursos dependem de dados históricos (por exemplo, média móvel), poderia haver algum tipo de vazamento de dados. Tentei corrigir isso apenas com dados de amostra que não faziam parte da criação da média móvel. Por exemplo, se houver uma média móvel de 3 períodos, não amostrarei / use as linhas de dados de 2 períodos anteriores. Isso não mudou nada, portanto não está no modelo abaixo.

NB2 O modelo abaixo é uma versão simples do que eu uso. A razão para uma validação definida para mim é que eu uso um algoritmo genético para o ajuste do hiperparâmetro, mas tudo o que é removido aqui para maior clareza.

import pandas as pd
import talib as ta
from sklearn.utils import shuffle
pd.options.mode.chained_assignment = None
from sklearn.metrics import accuracy_score

# ## TRAINING AND VALIDATING
# ### Read in data
input_data_file = 'EURUSDM5_2004-2018_cleaned.csv'   # For train and validation
df = pd.read_csv(input_data_file)

# ### Generate features
#######################
# SET TARGET
#######################
df['target'] = df['Close'].shift(-1)>df['Close']       # target is binary, i.e. either up or down next period

#######################
# DEFINE FEATURES
#######################
df['rsi'] = ta.RSI(df['Close'], 14) 

# ### Treat the data
#######################
# FIND AND MAKE CATEGORICAL VARAIBLES AND DO ONE-HOT ENCODING
#######################
for col in df.drop('target',axis=1).columns:     # Crude way of defining variables with few unique variants as categorical
    if df[col].nunique() < 25:
        df[col] = pd.Categorical(df[col])

cats = df.select_dtypes(include='category')     # Do one-hot encoding for the categorical variables
for cat_col in cats:
    df = pd.concat([df,pd.get_dummies(df[cat_col], prefix=cat_col,dummy_na=False)],axis=1).drop([cat_col],axis=1)

uints = df.select_dtypes(include='uint8')
for col in uints.columns:                   # Variables from the one-hot encoding is not created as categoricals so do it here
    df[col] = df[col].astype('category')

#######################
# REMOVE ROWS WITH NO TRADES
#######################
df = df[df['Volume']>0]

#######################
# BALANCE NUMBER OF UP/DOWN IN TARGET SO THE MODEL CANNOT SIMPLY CHOOSE ONE AND BE SUCCESSFUL THAT WAY
#######################
df_true = df[df['target']==True]
df_false = df[df['target']==False]

len_true = len(df_true)
len_false = len(df_false)
rows = min(len_true,len_false)

df_true = df_true.head(rows)
df_false = df_false.head(rows)
df = pd.concat([df_true,df_false],ignore_index=True)
df = shuffle(df)
df.dropna(axis=0, how='any', inplace=True)

# ### Split data
df = shuffle(df)
split = int(0.95*len(df))

train_set = df.iloc[0:split]
val_set = df.iloc[split:-1]

# ### Generate X,y
X_train = train_set[train_set.columns.difference(['target', 'Datetime'])]
y_train = train_set['target']

X_val = val_set[val_set.columns.difference(['target', 'Datetime'])]
y_val = val_set['target']

# ### Scale
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()

cont = X_train.select_dtypes(exclude='category')                   # Find columns with continous (not categorical) variables
X_train[cont.columns] = sc.fit_transform(X_train[cont.columns])    # Fit and transform

cont = X_val.select_dtypes(exclude='category')                     # Find columns with continous (not categorical) variables
X_val[cont.columns] = sc.transform(X_val[cont.columns])            # Transform

cats = X_train.select_dtypes(include='category')
for col in cats.columns:
    X_train[col] = X_train[col].astype('uint8')

cats = X_val.select_dtypes(include='category')
for col in cats.columns:
    X_val[col] = X_val[col].astype('uint8')


# ## MODEL
from xgboost import XGBClassifier
model = XGBClassifier()
model.fit(X_train, y_train)

predictions = model.predict(X_val)
acc = 100*accuracy_score(y_val, predictions)
print('{0:0.1f}%'.format(acc))

# # TESTING
input_data_file = 'EURUSDM5_2019_cleaned.csv'   # For testing
df = pd.read_csv(input_data_file)

#######################
# SET TARGET
#######################
df['target'] = df['Close'].shift(-1)>df['Close']       # target is binary, i.e. either up or down next period
#######################
# DEFINE FEATURES
#######################
df['rsi'] = ta.RSI(df['Close'], 14)

#######################
# FIND AND MAKE CATEGORICAL VARAIBLES AND DO ONE-HOT ENCODING
#######################
for col in df.drop('target',axis=1).columns:     # Crude way of defining variables with few unique variants as categorical
    if df[col].nunique() < 25:
        df[col] = pd.Categorical(df[col])

cats = df.select_dtypes(include='category')     # Do one-hot encoding for the categorical variables
for cat_col in cats:
    df = pd.concat([df,pd.get_dummies(df[cat_col], prefix=cat_col,dummy_na=False)],axis=1).drop([cat_col],axis=1)

uints = df.select_dtypes(include='uint8')
for col in uints.columns:                   # Variables from the one-hot encoding is not created as categoricals so do it here
    df[col] = df[col].astype('category')

#######################
# REMOVE ROWS WITH NO TRADES
#######################
df = df[df['Volume']>0]
df.dropna(axis=0, how='any', inplace=True)

X_test = df[df.columns.difference(['target', 'Datetime'])]
y_test = df['target']

cont = X_test.select_dtypes(exclude='category')                     # Find columns with continous (not categorical) variables
X_test[cont.columns] = sc.transform(X_test[cont.columns])            # Transform

cats = X_test.select_dtypes(include='category')
for col in cats.columns:
    X_test[col] = X_test[col].astype('uint8')

predictions = model.predict(X_test)
acc = 100*accuracy_score(y_test, predictions)
print('{0:0.1f}%'.format(acc))
DBSE
fonte

Respostas:

6

A única diferença parece ser os dados. Talvez o conjunto de testes (que foram os dados mais recentes) tenha diferido um pouco do conjunto de treinamento / validação e tenha levado a um desempenho inferior do seu modelo.

gatinha
fonte
6

O mais provável é que tenha havido alguma mudança de conceito. Como seu modelo é treinado em dados até 2018 e testado em 2019, as coisas mudaram e algumas dessas alterações podem não ser capazes de prever.

Algumas outras possibilidades:

Você diz que executou o ajuste do hiperparâmetro, mas o omitiu do código por simplicidade. Mas se você estiver usando o conjunto de validação para escolher os hiperparâmetros, a pontuação obtida será enviesada de maneira otimista. (Mas você diz que o modelo não viu o conjunto de validação, talvez não seja assim que você está fazendo isso.)

Por fim, é possível que você tenha feito tudo certo, e não haja realmente nenhum desvio de conceito, mas esses efeitos aleatórios justificam alguns pontos de precisão.

Ben Reiniger
fonte
2

Há dois motivos principais:

  1. O modelo treinado tem desempenho próximo ao aleatório. Por exemplo, 50% é desempenho aleatório em uma tarefa de classificação binária, assumindo a mesma participação na classe. Em outras palavras, o modelo não aprende padrões preditivos significativos de dados de 2004 a 2018.

  2. Pode haver novos padrões nos dados de 2019. Os padrões (pouco aprendidos) dos dados de 2004 a 2018 não são transferidos para os dados de 2019.

Brian Spiering
fonte
Ah, sim, de alguma forma, senti falta de que essa classificação fosse binária, que as pontuações relatadas eram precisões e apenas 54% e 51%. +1
Ben Reiniger
0

Como diz o antigo mantra de investimento, "o desempenho passado não é indicativo de desempenho futuro".

Meu candidato principal está super adaptado. Embora a chance de um padrão específico ser sintomático de uma determinada direção, mesmo que não seja causal (ou preditiva além da amostra em questão), seja astronomicamente pequena, também há uma quantidade astronômica de padrões a serem detectados que podem exibir esse comportamento .

Vamos supor que eles eram padrões reais que você aprendeu:
enquanto você treinava algo aprendendo seus triplos fundos e cabeças e ombros, centenas de bancos também eram, e fazendo isso mais rápido do que você e usando essas informações.
Essas informações foram refletidas em diferentes movimentos de preços, porque eles sabiam mais do que em 2018 e agiram de maneira diferente, seu modelo ainda não sabe que deve levar essas ações em consideração porque são novas.

Robin Gertenbach
fonte