Warum gibt es einen Unterschied zwischen der Vorhersage des Validierungssatzes und des Testsatzes?


8

Ich habe ein XGBoost-Modell, das versucht vorherzusagen, ob eine Währung in der nächsten Periode (5 Minuten) steigen oder fallen wird. Ich habe einen Datensatz von 2004 bis 2018. Ich habe die randomisierten Daten in 95% Zug- und 5% Validierung aufgeteilt und die Genauigkeit des Validierungssatzes beträgt bis zu 55%. Wenn ich das Modell dann für einen neuen Testsatz verwende (Daten von 2019), sinkt die Genauigkeit auf unter 51%.

Kann jemand erklären, warum das so sein könnte?

Ich gehe davon aus, dass das Modell die Validierungsdaten nicht mehr "gesehen" (trainiert) hat als die Testdaten. Kann es also wirklich überanpassen?

Ich habe unten ein einfaches Modell beigefügt, um dies zu veranschaulichen. Dieser gibt 54% auf Validierungssatz, aber nur 50,9% auf Testsatz .

Vielen Dank für jede Hilfe!

NB Eine Theorie, die ich hatte, war, dass, da einige der Funktionen auf historischen Daten beruhen (z. B. gleitender Durchschnitt), es sich um einen Datenverlust handeln könnte. Ich habe dann versucht, dies zu korrigieren, indem ich nur Beispieldaten verwendet habe, die nicht Teil der Erstellung des gleitenden Durchschnitts waren. Wenn es beispielsweise einen gleitenden Durchschnitt von 3 Perioden gibt, werden die Datenzeilen aus 2 Perioden nicht abgetastet / verwendet. Das hat nichts geändert, so dass es nicht im Modell unten ist.

NB2 Das folgende Modell ist eine einfache Version von dem, was ich benutze. Der Grund für ein Validierungsset für mich ist, dass ich einen genetischen Algorithmus für die Hyperparameter-Abstimmung verwende, aber alles, was hier aus Gründen der Klarheit entfernt wird.

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

Antworten:


6

Der einzige Unterschied scheinen die Daten zu sein. Möglicherweise unterschied sich der Testsatz (der die neuesten Daten enthielt) geringfügig von dem der Trainings- / Validierungssätze und führte zu einer Unterleistung Ihres Modells.


6

Am wahrscheinlichsten ist, dass es eine gewisse Konzeptverschiebung gegeben hat. Da Ihr Modell bis 2018 auf Daten trainiert und 2019 getestet wurde, haben sich die Dinge geändert, und einige dieser Änderungen kann Ihr Modell möglicherweise nicht vorhersehen.

Ein paar andere Möglichkeiten:

Sie sagen, Sie haben eine Hyperparameter-Optimierung durchgeführt, dies jedoch der Einfachheit halber im Code weggelassen. Wenn Sie jedoch den Validierungssatz verwenden, um die Hyperparameter auszuwählen, wird die Punktzahl, die Sie erhalten, optimistisch verzerrt. (Aber Sie sagen, das Modell hat den Validierungssatz nicht gesehen. Vielleicht machen Sie das nicht so.)

Schließlich ist es möglich, dass Sie alles richtig gemacht haben und es gibt keine wirkliche Konzeptverschiebung, aber diese zufälligen Effekte machen nur ein paar Punkte der Genauigkeit aus.


2

Es gibt zwei Hauptgründe:

  1. Das trainierte Modell hat eine nahezu zufällige Leistung. Zum Beispiel ist 50% eine zufällige Leistung in einer binären Klassifizierungsaufgabe unter der Annahme einer gleichen Klassenmitgliedschaft. Mit anderen Worten, das Modell lernt keine aussagekräftigen Vorhersagemuster von Daten von 2004 bis 2018.

  2. Die Daten für 2019 könnten neue Muster enthalten. Die (kaum erlernten) Muster aus den Daten von 2004 bis 2018 werden nicht auf die Daten von 2019 übertragen.


Oh ja, ich habe irgendwie übersehen, dass dies eine binäre Klassifizierung war, dass die angegebenen Werte Genauigkeiten waren und nur 54% und 51%. +1
Ben Reiniger

0

Nach dem alten Investment-Mantra ist "die Wertentwicklung in der Vergangenheit kein Hinweis auf die zukünftige Wertentwicklung".

Mein Hauptkandidat ist überpassend. Während die Wahrscheinlichkeit, dass ein bestimmtes Muster für eine bestimmte Richtung symptomatisch ist, obwohl es überhaupt nicht kausal (oder über die vorliegende Probe hinaus prädiktiv) ist, astronomisch gering ist, muss auch eine astronomische Menge von Mustern erkannt werden, die ein solches Verhalten aufweisen können .

Nehmen wir an, es waren echte Muster, die Sie gelernt haben:
Während Sie ein Algo trainierten, lernten auch seine dreifachen Böden, Köpfe und Schultern Hunderte von Banken, und zwar schneller als Sie und unter Verwendung dieser Informationen.
Diese Informationen spiegelten sich in unterschiedlichen Preisbewegungen wider, da sie mehr wussten als 2018 und anders handelten. Ihr Modell weiß noch nicht, wie es diese Maßnahmen berücksichtigen soll, da sie neu sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.