I'm creating an ensemble of NNs with the same architecture, but each NN only outputs one value when given X_test data. The data (continuous values transformed to be [-1,1]) yields results as expected when put through other ML algos (RF and XGB), so I suspect this has something to do with my model construction. Here is the entire code, I hope it provides some clues.
import pandas as pd
import numpy as np
from scipy.stats import randint
from sklearn.tree import export_graphviz
from IPython.display import Image
import graphviz
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense, BatchNormalization
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping
from keras.regularizers import l1
import tensorflow as tf
from keras.models import load_model
## sp500 benchmark data
sp = pd.read_csv("sp500.csv")
# sp data we need to calculate monthly sp500 returns to benchmark the OOS data
sp = sp[(sp['Date'] >= '1986-12-01') & (sp['Date'] <= '2022-01-01')]
# calculate the monthly pct change
test_sp = sp['Close'].reset_index(drop=True).pct_change().dropna()
## stock market data
data = pd.read_csv("stock_char_monthly.csv")
data['month'] = pd.to_datetime(data['month'])
data['year'] = data['month'].dt.year
# drop the bottom 20% observations by market capitalization (their snr is lowest, the stocks are illiquid, Fama: 20%:3%)
percentile_cutoff = 0.2
bottom_20 = data.groupby('month')['mktcap_lag'].transform(lambda x: x.quantile(percentile_cutoff))
data = (data[data['mktcap_lag'] > bottom_20]).reset_index(drop=True)
# target and features
target = data.ret_excess
returns = data.ret_adj
feat_data = data.filter(regex="char_")
years = data.year
train_start = 1965
test_start = 1987
train_idxs = years.loc[(years >= train_start) & (years < test_start)].index
test_idxs = years.loc[years == test_start].index
# train indices
train_beg = train_idxs[0]
train_end = train_idxs[-1]+1
# test indices
test_beg = test_idxs[0]
test_end = test_idxs[-1]+1
# split
X_train = feat_data[train_beg:train_end]
y_train = target[train_beg:train_end]
X_test = feat_data[test_beg:test_end]
y_test = target[test_beg:test_end]
# test returns
test_ret=returns[test_beg:test_end]
## NN 3 hidden layers:
# layers: 94,32,16,8,1
# activation function: ReLU
# l1 penalty: 10e-3
# learning rate: 0.001
# batch size: 10,000
# epochs: 100
# patience: 5
# adam: default
# ensemble: 5
# batch normalization between all layers
n_members = 5
models = list()
for _ in range(n_members):
# specify model architecture
model = Sequential()
model.add(Dense(94, activation='relu',kernel_regularizer=l1(0.01)))
model.add(BatchNormalization())
model.add(Dense(32, activation='relu',kernel_regularizer=l1(0.01)))
model.add(BatchNormalization())
model.add(Dense(16, activation='relu',kernel_regularizer=l1(0.01)))
model.add(BatchNormalization())
model.add(Dense(8, activation='relu',kernel_regularizer=l1(0.01)))
model.add(BatchNormalization())
model.add(Dense(1), activation='linear')
model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001), metrics=['mean_squared_error'])
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
#model.summary()
# fit the model
model.fit(x=X_train,
y=y_train,
batch_size = 10000,
epochs = 100,
validation_split = 0.4,
verbose = 1,
callbacks = early_stopping)
models.append(model)
yhats = [model.predict(X_test) for model in models]
yhats = np.array(yhats)
print(yhats[0])
For example, the first model only outputs 0.0098383. [[0.0098383 ] [0.0098383 ] [0.0098383 ] ... [0.0098383 ] [0.00983831] [0.0098383 ]]