¿Por qué hay una diferencia entre predecir en el conjunto de validación y el conjunto de prueba?

8

Tengo un modelo XGBoost que intenta predecir si una moneda subirá o bajará el próximo período (5 min). Tengo un conjunto de datos de 2004 a 2018. Divido los datos aleatorizados en 95% de validación de tren y 5% y la precisión en el conjunto de Validación es de hasta 55%. Cuando uso el modelo en un nuevo conjunto de prueba (datos de 2019), la precisión baja a menos del 51%.

¿Alguien puede explicar por qué podría ser eso?

Quiero decir, supongo que el modelo no ha "visto" (entrenado) los datos de validación más de lo que tiene los datos de la prueba, ¿puede realmente ser demasiado adecuado?

He adjuntado un modelo simple a continuación para ilustrar. Ese da el 54% en el conjunto de validación pero solo el 50.9% en el conjunto de prueba .

Agradecido por cualquier ayuda!

Nota: una teoría que tuve fue que, como algunas de las características se basan en datos históricos (p. Ej., Promedio móvil), podría tratarse de algún tipo de fuga de datos. Luego intenté corregir eso solo con datos de muestra que no formaban parte de la creación de la media móvil. Por ejemplo, si hay un promedio móvil de 3 períodos, entonces no muestreo / uso las filas de datos de 2 períodos anteriores. Eso no cambió nada, por lo que no está en el modelo a continuación.

NB2 El siguiente modelo es una versión simple de lo que uso. La razón de un conjunto de validación para mí es que uso un algoritmo genético para el ajuste de hiperparámetros, pero todo eso se elimina aquí para mayor claridad.

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
fuente

Respuestas:

6

La única diferencia parece ser la información. Tal vez el conjunto de prueba (que era la información más reciente) difería ligeramente de la de los conjuntos de capacitación / validación y condujo a un rendimiento inferior de su modelo.

gatito
fuente
6

Lo más probable es que haya habido alguna deriva conceptual. Dado que su modelo está capacitado en datos hasta 2018 y probado en 2019, las cosas han cambiado, y algunos de estos cambios es posible que su modelo no pueda prever.

Sin embargo, hay un par de otras posibilidades:

Dices que realizaste el ajuste de hiperparámetro, pero lo omitiste del código por simplicidad. Pero si está utilizando el conjunto de validación para elegir los hiperparámetros, la puntuación que obtenga tendrá un sesgo optimista. (Pero usted dice que el modelo no ha visto el conjunto de validación, así que tal vez no es así como lo está haciendo).

Finalmente, es posible que hayas hecho todo bien, y realmente no hay una deriva conceptual, pero los efectos aleatorios solo representan algunos puntos de precisión.

Ben Reiniger
fuente
2

Hay dos razones principales:

  1. El modelo entrenado tiene un rendimiento cercano al azar. Por ejemplo, el 50% es un rendimiento aleatorio en una tarea de clasificación binaria suponiendo una pertenencia de clase igual. En otras palabras, el modelo no aprende patrones predictivos significativos de los datos de 2004 a 2018.

  2. Podría haber nuevos patrones en los datos de 2019. Los patrones (apenas aprendidos) de los datos de 2004 a 2018 no se transfieren a los datos de 2019.

Brian Spiering
fuente
Ah, sí, de alguna manera extrañé que esto fuera una clasificación binaria, que los puntajes reportados fueran precisiones, y solo 54% y 51%. +1
Ben Reiniger
0

Como dice el antiguo mantra de la inversión, "el rendimiento pasado no es indicativo del rendimiento futuro".

Mi principal candidato está sobreajustando. Si bien la posibilidad de que un patrón particular sea sintomático de una determinada dirección a pesar de que no es causal (o predictivo más allá de la muestra disponible) es astronómicamente pequeña, también hay una cantidad astronómica de patrones que se pueden detectar que pueden exhibir tal comportamiento .

Supongamos que eran patrones reales que aprendiste:
mientras estabas entrenando algo aprendiendo sus fondos triples y cabezas y hombros, también lo hicieron cientos de bancos, y lo hiciste más rápido que tú y usaste esa información.
Esa información se reflejó en diferentes movimientos de precios, porque sabían más que en 2018 y actuaron de manera diferente, su modelo aún no sabe tomar esas acciones en cuenta porque son nuevas.

Robin Gertenbach
fuente