Perché esiste una differenza tra la previsione sul set di convalida e il set di test?


8

Ho un modello XGBoost che cerca di prevedere se una valuta salirà o scenderà nel prossimo periodo (5 min). Ho un set di dati dal 2004 al 2018. Ho diviso i dati in modo casuale in 95% di treno e 5% di convalida e l'accuratezza sul set di convalida è fino al 55%. Quando uso il modello su un nuovo set di test (dati del 2019), la precisione scende al di sotto del 51%.

Qualcuno può spiegare perché potrebbe essere?

Voglio dire, presumo che il modello non abbia "visto" (istruito su) i dati di validazione più di quanto abbia i dati di test, quindi può davvero essere troppo adatto?

Ho allegato un semplice modello qui sotto per illustrare. Quello dà il 54% sul set di validazione ma solo il 50,9% sul set di test .

Grato per qualsiasi aiuto!

NB Una teoria che ho avuto è stata che, poiché alcune delle funzionalità si basano su dati storici (ad esempio la media mobile), potrebbe essere una perdita di dati di qualche tipo. Ho quindi provato a correggerlo solo con dati campione che non facevano parte della creazione della media mobile. Ad esempio, se esiste una media mobile di 3 periodi, non campionare / utilizzare le righe di dati di 2 periodi precedenti. Ciò non ha cambiato nulla, quindi non è nel modello seguente.

NB2 Il modello seguente è una versione semplice di quello che uso. Il motivo di un set di convalida per me è che uso un algoritmo genetico per l'ottimizzazione dell'iperparametro, ma tutto ciò che viene rimosso qui per chiarezza.

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))

Risposte:


6

L'unica differenza sembra essere i dati. Forse il set di test (che era il dato più recente) differiva leggermente da quello dei set di training / validazione e portava ad una sotto-performance del tuo modello.


6

La cosa più probabile è che ci sia stata una certa deriva del concetto. Poiché il tuo modello è stato addestrato sui dati fino al 2018 e testato nel 2019, le cose sono cambiate e alcune di queste modifiche potrebbero non essere in grado di prevedere il tuo modello.

Un paio di altre possibilità però:

Dici di aver eseguito l'ottimizzazione dell'iperparametro, ma lo hai omesso dal codice per semplicità. Ma se stai usando il set di validazione per scegliere gli iperparametri, il punteggio che otterrai sarà distorto in modo ottimistico. (Ma dici che il modello non ha visto il set di validazione, quindi forse non è così che lo stai facendo.)

Infine, è possibile che tu abbia fatto tutto nel modo giusto e non ci sia realmente una deriva dei concetti, ma che gli effetti casuali spiegano solo alcuni punti di precisione.


2

Ci sono due ragioni principali:

  1. Il modello addestrato ha prestazioni quasi casuali. Ad esempio, il 50% è una prestazione casuale in un'attività di classificazione binaria che assume l'appartenenza alla stessa classe. In altre parole, il modello non apprende modelli predittivi significativi dai dati 2004-2018.

  2. Potrebbero esserci nuovi modelli nei dati del 2019. I modelli (appena appresi) dai dati del 2004 al 2018 non vengono trasferiti ai dati del 2019.


Oh, sì, in qualche modo mi mancava che si trattasse di una classificazione binaria, che i punteggi riportati fossero accurati e solo il 54% e il 51%. +1
Ben Reiniger,

0

Come recita il vecchio mantra degli investimenti, "la performance passata non è indicativa della performance futura".

Il mio primo candidato è troppo adatto. Mentre la possibilità che un particolare schema sia sintomatico di una certa direzione anche se non è causale (o predittiva oltre il campione a portata di mano) è astronomicamente piccola, c'è anche una quantità astronomica di schemi da rilevare che possono esibire tale comportamento .

Supponiamo che siano stati dei veri modelli che hai imparato:
mentre stavi addestrando un algo imparando i suoi tripli fondi e la testa e le spalle lo erano anche centinaia di banche, e lo facevano più velocemente di te e usando quelle informazioni.
Quelle informazioni si sono riflesse in diversi movimenti di prezzo, perché sapevano di più rispetto al 2018 e hanno agito diversamente, il tuo modello non sa ancora di prendere in considerazione tali azioni perché sono nuove.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.