1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 17:34:35 +08:00
This commit is contained in:
Alex Wang
2020-11-26 14:35:16 +08:00
parent 27f19c1f1b
commit 28b11886dd
2 changed files with 158 additions and 161 deletions

View File

@@ -71,21 +71,22 @@ if __name__ == "__main__":
"module_path": "qlib.contrib.model.pytorch_sfm", "module_path": "qlib.contrib.model.pytorch_sfm",
"kwargs": { "kwargs": {
"d_feat": 6, "d_feat": 6,
"hidden_size": 32, "hidden_size": 64,
"output_dim": 16, "output_dim" : 32,
"freq_dim": 25, "freq_dim" : 25,
"dropout_W": 0.5, "dropout_W": 0.5,
"dropout_U": 0.5, "dropout_U": 0.5,
"n_epochs": 200, "n_epochs": 15,
"lr": 1e-3, "lr": 1e-2,
"batch_size": 200, "metric": "",
"batch_size": 1600,
"early_stop": 20, "early_stop": 20,
"eval_steps": 5, "eval_steps": 5,
"loss": "mse", "loss": "mse",
"lr_decay": 0.96, "lr_decay" : 0.96,
"lr_decay_steps": 100, "lr_decay_steps" : 100,
"optimizer": "adam", "optimizer" : "adam",
"GPU": 1, "GPU": 3,
"seed": 710, "seed": 710,
}, },
}, },

View File

@@ -31,7 +31,6 @@ from ...model.base import Model
from ...data.dataset import DatasetH from ...data.dataset import DatasetH
from ...data.dataset.handler import DataHandlerLP from ...data.dataset.handler import DataHandlerLP
class SFM_Model(nn.Module): class SFM_Model(nn.Module):
def __init__(self, d_feat=6, output_dim=1, freq_dim=10, hidden_size=64, dropout_W=0.0, dropout_U=0.0, device="cpu"): def __init__(self, d_feat=6, output_dim=1, freq_dim=10, hidden_size=64, dropout_W=0.0, dropout_U=0.0, device="cpu"):
super().__init__() super().__init__()
@@ -76,13 +75,13 @@ class SFM_Model(nn.Module):
self.states = [] self.states = []
def forward(self, input): def forward(self, input):
input = input.reshape(len(input), self.input_dim, -1) # [N, F, T] input = input.reshape(len(input), self.input_dim, -1) # [N, F, T]
input = input.permute(0, 2, 1) # [N, T, F] input = input.permute(0, 2, 1) # [N, T, F]
time_step = input.shape[1] time_step = input.shape[1]
for ts in range(time_step): for ts in range(time_step):
x = input[:, ts, :] x = input[:, ts,:]
if len(self.states) == 0: # hasn't initialized yet if len(self.states)==0: #hasn't initialized yet
self.init_states(x) self.init_states(x)
self.get_constants(x) self.get_constants(x)
p_tm1 = self.states[0] p_tm1 = self.states[0]
@@ -99,65 +98,64 @@ class SFM_Model(nn.Module):
x_fre = torch.matmul(x * B_W[0], self.W_fre) + self.b_fre x_fre = torch.matmul(x * B_W[0], self.W_fre) + self.b_fre
x_c = torch.matmul(x * B_W[0], self.W_c) + self.b_c x_c = torch.matmul(x * B_W[0], self.W_c) + self.b_c
x_o = torch.matmul(x * B_W[0], self.W_o) + self.b_o x_o = torch.matmul(x * B_W[0], self.W_o) + self.b_o
i = self.inner_activation( i = self.inner_activation(x_i + torch.matmul(h_tm1 * B_U[0], self.U_i)) # not sure whether I am doing in the right unsquuze
x_i + torch.matmul(h_tm1 * B_U[0], self.U_i)
) # not sure whether I am doing in the right unsquuze
ste = self.inner_activation(x_ste + torch.matmul(h_tm1 * B_U[0], self.U_ste)) ste = self.inner_activation(x_ste + torch.matmul(h_tm1 * B_U[0], self.U_ste))
fre = self.inner_activation(x_fre + torch.matmul(h_tm1 * B_U[0], self.U_fre)) fre = self.inner_activation(x_fre + torch.matmul(h_tm1 * B_U[0], self.U_fre))
ste = torch.reshape(ste, (-1, self.hidden_dim, 1)) ste = torch.reshape(ste, (-1, self.hidden_dim, 1))
fre = torch.reshape(fre, (-1, 1, self.freq_dim)) fre = torch.reshape(fre, (-1, 1, self.freq_dim))
f = ste * fre f = ste * fre
c = i * self.activation(x_c + torch.matmul(h_tm1 * B_U[0], self.U_c)) c = i * self.activation(x_c + torch.matmul(h_tm1 * B_U[0], self.U_c))
time = time_tm1 + 1 time = time_tm1 + 1
omega = torch.tensor(2 * np.pi) * time * frequency omega = torch.tensor(2 * np.pi) * time * frequency
re = torch.cos(omega) re = torch.cos(omega)
im = torch.sin(omega) im = torch.sin(omega)
c = torch.reshape(c, (-1, self.hidden_dim, 1)) c = torch.reshape(c, (-1, self.hidden_dim, 1))
S_re = f * S_re_tm1 + c * re S_re = f * S_re_tm1 + c * re
S_im = f * S_im_tm1 + c * im S_im = f * S_im_tm1 + c * im
A = torch.square(S_re) + torch.square(S_im) A = torch.square(S_re) + torch.square(S_im)
A = torch.reshape(A, (-1, self.freq_dim)).float() A = torch.reshape(A, (-1, self.freq_dim)).float()
A_a = torch.matmul(A * B_U[0], self.U_a) A_a = torch.matmul(A * B_U[0], self.U_a)
A_a = torch.reshape(A_a, (-1, self.hidden_dim)) A_a = torch.reshape(A_a, (-1, self.hidden_dim))
a = self.activation(A_a + self.b_a) a = self.activation(A_a + self.b_a)
o = self.inner_activation(x_o + torch.matmul(h_tm1 * B_U[0], self.U_o)) o = self.inner_activation(x_o + torch.matmul(h_tm1 * B_U[0], self.U_o))
h = o * a h = o * a
p = torch.matmul(h, self.W_p) + self.b_p p = torch.matmul(h, self.W_p) + self.b_p
self.states = [p, h, S_re, S_im, time, None, None, None] self.states = [p, h, S_re, S_im, time, None, None, None]
self.states = [] self.states = []
return self.fc_out(p).squeeze() return self.fc_out(p).squeeze()
def init_states(self, x): def init_states(self, x):
reducer_f = torch.zeros((self.hidden_dim, self.freq_dim)).to(self.device) reducer_f = torch.zeros((self.hidden_dim, self.freq_dim)).to(self.device)
reducer_p = torch.zeros((self.hidden_dim, self.output_dim)).to(self.device) reducer_p = torch.zeros((self.hidden_dim, self.output_dim)).to(self.device)
init_state_h = torch.zeros(self.hidden_dim).to(self.device) init_state_h = torch.zeros(self.hidden_dim).to(self.device)
init_state_p = torch.matmul(init_state_h, reducer_p) init_state_p = torch.matmul(init_state_h, reducer_p)
init_state = torch.zeros_like(init_state_h).to(self.device) init_state = torch.zeros_like(init_state_h).to(self.device)
init_freq = torch.matmul(init_state_h, reducer_f) init_freq = torch.matmul(init_state_h, reducer_f)
init_state = torch.reshape(init_state, (-1, self.hidden_dim, 1)) init_state = torch.reshape(init_state, (-1, self.hidden_dim, 1))
init_freq = torch.reshape(init_freq, (-1, 1, self.freq_dim)) init_freq = torch.reshape(init_freq, (-1, 1, self.freq_dim))
init_state_S_re = init_state * init_freq init_state_S_re = init_state * init_freq
init_state_S_im = init_state * init_freq init_state_S_im = init_state * init_freq
init_state_time = torch.tensor(0).to(self.device) init_state_time = torch.tensor(0).to(self.device)
self.states = [init_state_p, init_state_h, init_state_S_re, init_state_S_im, init_state_time, None, None, None] self.states = [init_state_p, init_state_h, init_state_S_re, init_state_S_im, init_state_time, None, None, None]
@@ -203,6 +201,7 @@ class SFM(Model):
dropout_U=0.0, dropout_U=0.0,
n_epochs=200, n_epochs=200,
lr=0.001, lr=0.001,
metric = "",
batch_size=2000, batch_size=2000,
early_stop=20, early_stop=20,
eval_steps=5, eval_steps=5,
@@ -227,14 +226,15 @@ class SFM(Model):
self.dropout_U = dropout_U self.dropout_U = dropout_U
self.n_epochs = n_epochs self.n_epochs = n_epochs
self.lr = lr self.lr = lr
self.metric = metric
self.batch_size = batch_size self.batch_size = batch_size
self.early_stop = early_stop self.early_stop = early_stop
self.eval_steps = eval_steps self.eval_steps = eval_steps
self.lr_decay = lr_decay self.lr_decay = lr_decay
self.lr_decay_steps = lr_decay_steps self.lr_decay_steps = lr_decay_steps
self.optimizer = optimizer.lower() self.optimizer = optimizer.lower()
self.loss_type = loss self.loss = loss
self.device = "cuda:%d" % (GPU) if torch.cuda.is_available() else "cpu" self.device = "cuda:%d"%(GPU) if torch.cuda.is_available() else "cpu"
self.use_gpu = torch.cuda.is_available() self.use_gpu = torch.cuda.is_available()
self.seed = seed self.seed = seed
@@ -243,11 +243,12 @@ class SFM(Model):
"\nd_feat : {}" "\nd_feat : {}"
"\nhidden_size : {}" "\nhidden_size : {}"
"\noutput_size : {}" "\noutput_size : {}"
"\nfrequency_dimension : {}" "\nfrequency_dimension : {}"
"\ndropout_W: {}" "\ndropout_W: {}"
"\ndropout_U: {}" "\ndropout_U: {}"
"\nn_epochs : {}" "\nn_epochs : {}"
"\nlr : {}" "\nlr : {}"
"\nmetric : {}"
"\nbatch_size : {}" "\nbatch_size : {}"
"\nearly_stop : {}" "\nearly_stop : {}"
"\neval_steps : {}" "\neval_steps : {}"
@@ -266,6 +267,7 @@ class SFM(Model):
dropout_U, dropout_U,
n_epochs, n_epochs,
lr, lr,
metric,
batch_size, batch_size,
early_stop, early_stop,
eval_steps, eval_steps,
@@ -284,14 +286,14 @@ class SFM(Model):
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
self.sfm_model = SFM_Model( self.sfm_model = SFM_Model(
d_feat=self.d_feat, d_feat=self.d_feat,
output_dim=self.output_dim, output_dim=self.output_dim,
hidden_size=self.hidden_size, hidden_size=self.hidden_size,
freq_dim=self.freq_dim, freq_dim=self.freq_dim,
dropout_W=self.dropout_W, dropout_W=self.dropout_W,
dropout_U=self.dropout_U, dropout_U=self.dropout_U,
device=self.device, device=self.device
) )
if optimizer.lower() == "adam": if optimizer.lower() == "adam":
self.train_optimizer = optim.Adam(self.sfm_model.parameters(), lr=self.lr) self.train_optimizer = optim.Adam(self.sfm_model.parameters(), lr=self.lr)
elif optimizer.lower() == "gd": elif optimizer.lower() == "gd":
@@ -299,24 +301,73 @@ class SFM(Model):
else: else:
raise NotImplementedError("optimizer {} is not supported!".format(optimizer)) raise NotImplementedError("optimizer {} is not supported!".format(optimizer))
# Reduce learning rate when loss has stopped decrease
self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
self.train_optimizer,
mode="min",
factor=0.5,
patience=10,
verbose=True,
threshold=0.0001,
threshold_mode="rel",
cooldown=0,
min_lr=0.00001,
eps=1e-08,
)
self._fitted = False self._fitted = False
self.sfm_model.to(self.device) self.sfm_model.to(self.device)
def fit(self, dataset: DatasetH, evals_result=dict(), verbose=True, save_path=None, **kwargs): def test_epoch(self, data_x, data_y):
# prepare training data
x_values = data_x.values
y_values = np.squeeze(data_y.values)
self.sfm_model.eval()
scores = []
losses = []
indices = np.arange(len(x_values))
np.random.shuffle(indices)
for i in range(len(indices))[:: self.batch_size]:
if len(indices) - i < self.batch_size:
break
feature = torch.from_numpy(x_values[indices[i : i + self.batch_size]]).float().to(self.device)
label = torch.from_numpy(y_values[indices[i : i + self.batch_size]]).float().to(self.device)
pred = self.sfm_model(feature)
loss = self.loss_fn(pred, label)
losses.append(loss.item())
score = self.metric_fn(pred, label)
scores.append(score.item())
return np.mean(losses), np.mean(scores)
def train_epoch(self, x_train, y_train):
x_train_values = x_train.values
y_train_values = np.squeeze(y_train.values) * 100
self.sfm_model.train()
indices = np.arange(len(x_train_values))
np.random.shuffle(indices)
for i in range(len(indices))[:: self.batch_size]:
if len(indices) - i < self.batch_size:
break
feature = torch.from_numpy(x_train_values[indices[i : i + self.batch_size]]).float().to(self.device)
label = torch.from_numpy(y_train_values[indices[i : i + self.batch_size]]).float().to(self.device)
pred = self.sfm_model(feature)
loss = self.loss_fn(pred, label)
self.train_optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_value_(self.sfm_model.parameters(), 3.0)
self.train_optimizer.step()
def fit(
self,
dataset: DatasetH,
evals_result=dict(),
verbose=True,
save_path=None,
):
df_train, df_valid = dataset.prepare( df_train, df_valid = dataset.prepare(
["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L ["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L
@@ -324,10 +375,10 @@ class SFM(Model):
x_train, y_train = df_train["feature"], df_train["label"] x_train, y_train = df_train["feature"], df_train["label"]
x_valid, y_valid = df_valid["feature"], df_valid["label"] x_valid, y_valid = df_valid["feature"], df_valid["label"]
save_path = create_save_path(save_path)
stop_steps = 0 stop_steps = 0
train_loss = 0 train_loss = 0
best_loss = np.inf best_score = -np.inf
best_epoch = 0
evals_result["train"] = [] evals_result["train"] = []
evals_result["valid"] = [] evals_result["valid"] = []
@@ -335,90 +386,56 @@ class SFM(Model):
self.logger.info("training...") self.logger.info("training...")
self._fitted = True self._fitted = True
# prepare training data
x_train_values = torch.from_numpy(x_train.values).float()
y_train_values = torch.from_numpy(np.squeeze(y_train.values)).float()
train_num = y_train_values.shape[0]
# prepare validation data
x_val_auto = torch.from_numpy(x_valid.values).float()
y_val_auto = torch.from_numpy(np.squeeze(y_valid.values)).float()
x_val_auto = x_val_auto.to(self.device)
y_val_auto = y_val_auto.to(self.device)
for step in range(self.n_epochs): for step in range(self.n_epochs):
if stop_steps >= self.early_stop: self.logger.info("Epoch%d:", step)
if verbose: self.logger.info("training...")
self.logger.info("\tearly stop") self.train_epoch(x_train, y_train)
break self.logger.info("evaluating...")
loss = AverageMeter() train_loss, train_score = self.test_epoch(x_train, y_train)
self.sfm_model.train() val_loss, val_score = self.test_epoch(x_valid, y_valid)
self.train_optimizer.zero_grad() self.logger.info("train %.6f, valid %.6f" % (train_score, val_score))
evals_result["train"].append(train_score)
evals_result["valid"].append(val_score)
choice = np.random.choice(train_num, self.batch_size) if val_score > best_score:
x_batch_auto = x_train_values[choice] best_score = val_score
y_batch_auto = y_train_values[choice] stop_steps = 0
best_epoch = step
x_batch_auto = x_batch_auto.to(self.device) best_param = copy.deepcopy(self.sfm_model.state_dict())
y_batch_auto = y_batch_auto.to(self.device) else:
# forward
preds = self.sfm_model(x_batch_auto)
cur_loss = self.get_loss(preds, y_batch_auto, self.loss_type)
cur_loss.backward()
self.train_optimizer.step()
loss.update(cur_loss.item())
# validation
train_loss += loss.val
if step and step % self.eval_steps == 0:
stop_steps += 1 stop_steps += 1
train_loss /= self.eval_steps if stop_steps >= self.early_stop:
self.logger.info("early stop")
with torch.no_grad(): break
self.sfm_model.eval() self.logger.info("best score: %.6lf @ %d" % (best_score, best_epoch))
loss_val = AverageMeter()
# forward
preds = self.sfm_model(x_val_auto)
cur_loss_val = self.get_loss(preds, y_val_auto, self.loss_type)
loss_val.update(cur_loss_val.item())
if verbose:
self.logger.info(
"[Epoch {}]: train_loss {:.6f}, valid_loss {:.6f}".format(step, train_loss, loss_val.val)
)
evals_result["train"].append(train_loss)
evals_result["valid"].append(loss_val.val)
if loss_val.val < best_loss:
if verbose:
self.logger.info(
"\tvalid loss update from {:.6f} to {:.6f}, save checkpoint.".format(
best_loss, loss_val.val
)
)
best_loss = loss_val.val
stop_steps = 0
torch.save(self.sfm_model.state_dict(), save_path)
train_loss = 0
# update learning rate
self.scheduler.step(cur_loss_val)
if self.device != "cpu": if self.device != "cpu":
torch.cuda.empty_cache() torch.cuda.empty_cache()
def get_loss(self, pred, target, loss_type): def mse(self, pred, label):
if loss_type == "mse": loss = (pred - label) ** 2
sqr_loss = (pred - target) ** 2 return torch.mean(loss)
loss = sqr_loss.mean()
return loss def loss_fn(self, pred, label):
elif loss_type == "binary": mask = ~torch.isnan(label)
loss = nn.BCELoss()
return loss(pred, target)
else:
raise NotImplementedError("loss {} is not supported!".format(loss_type))
if self.loss == "mse":
return self.mse(pred[mask], label[mask])
raise ValueError("unknown loss `%s`" % self.loss)
def metric_fn(self, pred, label):
mask = torch.isfinite(label)
if self.metric == "IC":
return self.cal_ic(pred[mask], label[mask])
if self.metric == "" or self.metric == "loss": # use loss
return -self.loss_fn(pred[mask], label[mask])
raise ValueError("unknown metric `%s`" % self.metric)
def cal_ic(self, pred, label):
return torch.mean(pred * label)
def predict(self, dataset): def predict(self, dataset):
if not self._fitted: if not self._fitted:
raise ValueError("model is not fitted yet!") raise ValueError("model is not fitted yet!")
@@ -430,7 +447,7 @@ class SFM(Model):
sample_num = x_values.shape[0] sample_num = x_values.shape[0]
preds = [] preds = []
for begin in range(sample_num)[:: self.batch_size]: for begin in range(sample_num)[::self.batch_size]:
if sample_num - begin < self.batch_size: if sample_num - begin < self.batch_size:
end = sample_num end = sample_num
else: else:
@@ -440,37 +457,16 @@ class SFM(Model):
if self.device != "cpu": if self.device != "cpu":
x_batch = x_batch.to(self.device) x_batch = x_batch.to(self.device)
with torch.no_grad(): with torch.no_grad():
if self.device != "cpu": pred = self.sfm_model(x_batch).detach().cpu().numpy()
pred = self.sfm_model(x_batch).detach().cpu().numpy()
else:
pred = self.sfm_model(x_batch).detach().cpu().numpy()
preds.append(pred) preds.append(pred)
return pd.Series(np.concatenate(preds), index=index) return pd.Series(np.concatenate(preds), index=index)
def save(self, filename, **kwargs):
with save_multiple_parts_file(filename) as model_dir:
model_path = os.path.join(model_dir, os.path.split(model_dir)[-1])
# Save model
torch.save(self.sfm_model.state_dict(), model_path)
def load(self, buffer, **kwargs):
with unpack_archive_with_buffer(buffer) as model_dir:
# Get model name
_model_name = os.path.splitext(list(filter(lambda x: x.startswith("model.bin"), os.listdir(model_dir)))[0])[
0
]
_model_path = os.path.join(model_dir, _model_name)
# Load model
self.sfm_model.load_state_dict(torch.load(_model_path))
self._fitted = True
class AverageMeter(object): class AverageMeter(object):
"""Computes and stores the average and current value""" """Computes and stores the average and current value"""
def __init__(self): def __init__(self):
self.reset() self.reset()