nfl / lightGBT.py
vignesh-99's picture
initial commit
58fa837
Raw
History Blame Contribute Delete
24.1 kB
import os
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
from scipy.spatial.distance import cdist
import numpy as np
from datetime import date
import lightgbm as lgb
MAXIMUM_X = 120
MAXIMUM_Y = 53.3
MAXIMUM_DIS = 131.30
BASE_COLS = []
#The features s, a represent the spped and acceleration
#the s and a are calculated from r, where r = <x(t), y(t)>
#so the function is a vector valued function in terms of time t
#velocity is given by <x'(t), y'(t)>
#acceleration is given by <x"(t), y"(t)>
#the s and a given here are the magnitude of these vectors
#the absolute yardline number refers to the distance to score endzone of the offense, calculated from the line of scrimmage
BASE_COLS = ['game_id', 'play_id', 'player_to_predict', 'nfl_id', 'frame_id',
'play_direction', 'absolute_yardline_number', 'player_name',
'player_height', 'player_weight', 'player_birth_date',
'player_position', 'player_side', 'player_role', 'x', 'y', 's', 'a',
'dir', 'o', 'num_frames_output', 'ball_land_x', 'ball_land_y']
MANDATORY_COLS=['game_id', 'play_id', 'nfl_id']
MODEL_NUMERICAL_INPUTS = ['frame_id', 'absolute_yardline_number', 'player_height',
'player_weight', 'x_last', 'y_last', 's', 'a', 'dir',
'o', 'num_frames_output', 'ball_land_x', 'ball_land_y', 'x_prev',
'y_prev', 'o_prev', 'dir_prev', 's_prev', 'a_prev',
'nearest_offense_dis', 'nearest_offense_dis_x', 'nearest_offense_dis_y',
'nearest_defense_dis', 'nearest_defense_dis_x', 'nearest_defense_dis_y',
'receiver_x', 'receiver_y', 'height', 'velocity_x', 'velocity_y',
'acc_x', 'acc_y', 'sin_o', 'cos_o', 'sin_dir', 'cos_dir', 'change_in_x',
'change_in_y', 'change_in_s', 'change_in_a', 'change_in_o',
'change_in_dir', 'dist_between_ball_land_and_player',
'dist_x_between_ball_and_player', 'dist_y_between_ball_and_player',
'angle_between_ball_and_dir', 'sin_angle_between_ball_and_dir',
'cos_angle_between_ball_and_dir', 'angle_between_ball_and_o',
'sin_angle_between_ball_and_o', 'cos_angle_between_ball_and_o',
'distance_to_sideline', 'distance_to_receiver',
'distance_x_to_receiver', 'distance_y_to_receiver',
'angle_between_dir_and_receiver', 'sin_angle_between_dir_and_receiver',
'cos_angle_between_dir_and_receiver', 'angle_between_o_and_receiver',
'sin_angle_between_o_and_receiver', 'cos_angle_between_o_and_receiver',
'time_left', 'required_speed', 'required_velocity_x',
'required_velocity_y', 'required_acc_x', 'required_acc_y',
'required_speed_diff', 'required_velocity_x_diff',
'required_velocity_y_diff', 'required_acc_x_diff',
'required_acc_y_diff', 'proj_x_acc', 'proj_y_acc', 'proj_x_velocity',
'proj_y_velocity', 'proj_x_acc_diff', 'proj_y_acc_diff',
'proj_x_velocity_diff', 'proj_y_velocity_diff', 'player_age']
MODEL_CAT_INPUTS = ['player_role']
MODEL_OUTPUTS = ['dx', 'dy']
TRAIN_INPUT_FILE_PATH = 'train_input'
TRAIN_OUTPUT_FILE_PATH = 'train_output'
def get_train_file_paths():
def get_output_file(input_filename):
return input_filename.replace('input', 'output')
input_file_paths = []
output_file_paths = []
input_files_dir = TRAIN_INPUT_FILE_PATH
for w in range(1, 19):
input_filename = f'input_2023_w{w:02d}.csv'
if os.path.isfile(f'{input_files_dir}/{input_filename}'):
output_filename = get_output_file(input_filename)
input_file_path = os.path.join(TRAIN_INPUT_FILE_PATH, input_filename)
output_file_path = os.path.join(TRAIN_OUTPUT_FILE_PATH, output_filename)
input_file_paths.append(input_file_path)
output_file_paths.append(output_file_path)
else:
raise Exception(f'input file for week {w} does not exist')
return (input_file_paths, output_file_paths)
def load_file(file_path):
return pd.read_csv(file_path)
def get_input_output_df():
input_file_paths, output_file_paths = get_train_file_paths()
with ThreadPoolExecutor(max_workers = 8) as executor:
input_dfs = executor.map(load_file, input_file_paths)
input_df = pd.concat(input_dfs, axis=0)
with ThreadPoolExecutor(max_workers = 8) as executor:
output_dfs = executor.map(load_file, output_file_paths)
output_df = pd.concat(output_dfs, axis=0)
return input_df.reset_index(drop=True), output_df.reset_index(drop=True)
def reflect_input_player_positions(df):
mask = df['play_direction'] == 'left'
df.loc[mask, 'x'] = 120 - df.loc[mask, 'x']
df.loc[mask, 'dir'] = (360 - df.loc[mask, 'dir']) % 360
df.loc[mask, 'o'] = (360 - df.loc[mask, 'o']) % 360
df.loc[mask, 'ball_land_x'] = 120 - df.loc[mask, 'ball_land_x']
return df
def reflect_output_player_positions(df):
mask = df['play_direction'] == 'left'
df.loc[mask, 'x'] = 120 - df.loc[mask, 'x']
return df
def add_nearest_dis_info(df):
plays = df.groupby(['game_id', 'play_id'], as_index=False)
res = []
for _, per_play in plays:
per_play = per_play.copy()
coords = per_play[['x_last', 'y_last']].to_numpy()
if coords.shape[0] == 1:
per_play['nearest_offense_dis'] = MAXIMUM_DIS
per_play['nearest_defense_dis'] = MAXIMUM_DIS
per_play['nearest_offense_dis_x'] = MAXIMUM_DIS
per_play['nearest_offense_dis_y'] = MAXIMUM_DIS
per_play['nearest_defense_dis_x'] = MAXIMUM_DIS
per_play['nearest_defense_dis_y'] = MAXIMUM_DIS
else:
distance = cdist(coords, coords, metric='euclidean')
np.fill_diagonal(distance, MAXIMUM_DIS)
offense_indices = (per_play['player_side'] == 'Offense')
defense_indices = (per_play['player_side'] == 'Defense')
if np.any(offense_indices):
per_play.loc[:,['nearest_offense_dis']] = np.min(distance[:,offense_indices], axis=-1)
idx = np.argmin(distance[:,offense_indices], axis=-1)
per_play.loc[:,['nearest_offense_dis_x']] = coords[offense_indices,][idx,0]
per_play.loc[:,['nearest_offense_dis_y']] = coords[offense_indices,][idx,1]
else:
per_play.loc[:,['nearest_offense_dis']] = MAXIMUM_DIS
per_play.loc[:,['nearest_offense_dis_x']] = MAXIMUM_X
per_play.loc[:,['nearest_offense_dis_y']] = MAXIMUM_Y
if np.any(defense_indices):
per_play.loc[:,['nearest_defense_dis']] = np.min(distance[:,defense_indices],axis=-1)
idx = np.argmin(distance[:,defense_indices], axis=-1)
per_play.loc[:,['nearest_defense_dis_x']] = coords[defense_indices,][idx,0]
per_play.loc[:,['nearest_defense_dis_y']] = coords[defense_indices][idx,1]
else:
per_play.loc[:,['nearest_defense_dis']] = MAXIMUM_DIS
per_play.loc[:,['nearest_defense_dis_x']] = MAXIMUM_X
per_play.loc[:,['nearest_defense_dis_y']] = MAXIMUM_Y
res.append(per_play)
return pd.concat(res, axis=0)
def add_reciever_info(df):
receiver = df.loc[df['player_role'] == 'Targeted Receiver',['game_id', 'play_id', 'x_last', 'y_last']]
receiver = receiver.rename(columns = {'x_last': 'receiver_x', 'y_last':'receiver_y'})
#if a play has no targeted receiever we leave with nan
return df.merge(receiver, on=['game_id', 'play_id'], how='left')
def get_last_frame(df):
df_sorted = df.sort_values(['game_id', 'play_id', 'nfl_id', 'frame_id']).reset_index(drop=True)
group_by_cols = ['game_id', 'play_id', 'nfl_id']
feature_cols = ['x', 'y', 'o', 'dir', 's', 'a']
df_sorted[[f'{c}_prev' for c in feature_cols]] = df_sorted.groupby(group_by_cols)[feature_cols].shift(1)
#last() takes non none values from the last possible col
#so even if last frame misses a feature , value is taken from the previous available one
df_last_frame = df_sorted.groupby(group_by_cols, as_index=False).last()
df_last_frame = df_last_frame.rename(columns={'x':'x_last', 'y':'y_last'})
return df_last_frame
def clean_and_extract_features(df):
def convert_to_radians(degrees):
return degrees * np.pi / 180
def convert_to_degrees(radians):
return radians * 180 / np.pi
def sin(theta):
return np.sin(convert_to_radians(theta))
def cos(theta):
return np.cos(convert_to_radians(theta))
def distance_between_two_points(x1, y1, x2, y2):
return np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
def angle_between_two_vectors(x1, y1, x2, y2):
denom = np.sqrt((x1**2)+(y1**2)) * np.sqrt((x2**2)+(y2**2)) + 1e-6
dot = x1*x2 + y1*y2
cos_angle = dot / denom
cos_angle = np.clip(cos_angle, -1.0, 1.0)
angle_in_radians = np.arccos(cos_angle)
return convert_to_degrees(angle_in_radians)
def convert_to_inches(X):
splits = X.str.split('-', expand=True)
feet = splits.iloc[:,0].astype(np.float32)
inches = splits.iloc[:,1].astype(np.float32)
return feet * 12 + inches
#returns the shortest angle when curr = 1degree prev = 359degree, change is 2degree
def change_in_angle(curr, prev):
return ((curr - prev +180) % 360) - 180
df['x_last'] = df['x_last'].bfill().ffill()
df['x_prev'] = df['x_prev'].bfill().ffill()
df['y_last'] = df['y_last'].bfill().ffill()
df['y_prev'] = df['y_prev'].bfill().ffill()
df['s'] = df['s'].bfill().ffill()
df['s_prev'] = df['s_prev'].bfill().ffill()
df['a'] = df['a'].bfill().ffill()
df['a_prev'] = df['a_prev'].bfill().ffill()
df['dir'] = df['dir'].bfill().ffill()
df['dir_prev'] = df['dir_prev'].bfill().ffill()
df['o'] = df['o'].bfill().ffill()
df['o_prev'] = df['o_prev'].bfill().ffill()
df['receiver_x'] = df['receiver_x'].bfill().ffill()
df['receiver_y'] = df['receiver_y'].bfill().ffill()
df['player_height'] = df['player_height'].bfill().ffill()
df['velocity_x'] = df['s'] * sin(df['dir'])
df['velocity_y'] = df['s'] * cos(df['dir'])
df['acc_x'] = df['a'] * sin(df['dir'])
df['acc_y'] = df['a'] * cos(df['dir'])
df['sin_o'] = sin(df['o'])
df['cos_o'] = cos(df['o'])
df['sin_dir'] = sin(df['dir'])
df['cos_dir'] = cos(df['dir'])
df['change_in_x'] = df['x_last'] - df['x_prev']
df['change_in_y'] = df['y_last'] - df['y_prev']
df['change_in_s'] = df['s'] - df['s_prev']
df['change_in_a'] = df['a'] - df['a_prev']
df['change_in_o'] = change_in_angle(df['o'], df['o_prev'])
df['change_in_dir'] = change_in_angle(df['dir'], df['dir_prev'])
df['dist_between_ball_land_and_player'] = distance_between_two_points(
df['x_last'], df['y_last'], df['ball_land_x'], df['ball_land_y']
)
df['dist_x_between_ball_and_player'] = df['ball_land_x'] - df['x_last']
df['dist_y_between_ball_and_player'] = df['ball_land_y'] - df['y_last']
df['angle_between_ball_and_dir'] = angle_between_two_vectors(
df['sin_dir'], df['cos_dir'], df['dist_x_between_ball_and_player'], df['dist_y_between_ball_and_player']
)
df['sin_angle_between_ball_and_dir'] = sin(df['angle_between_ball_and_dir'])
df['cos_angle_between_ball_and_dir'] = cos(df['angle_between_ball_and_dir'])
df['angle_between_ball_and_o'] = angle_between_two_vectors(
df['sin_o'], df['cos_o'], df['dist_x_between_ball_and_player'], df['dist_y_between_ball_and_player']
)
df['sin_angle_between_ball_and_o'] = sin(df['angle_between_ball_and_o'])
df['cos_angle_between_ball_and_o'] = cos(df['angle_between_ball_and_o'])
df['distance_to_sideline'] = np.minimum(df['y_last'], MAXIMUM_Y - df['y_last'])
df['player_height'] = convert_to_inches(df['player_height'])
df['distance_to_receiver'] = distance_between_two_points(df['x_last'], df['y_last'], df['receiver_x'], df['receiver_y'])
df['distance_x_to_receiver'] = df['x_last'] - df['receiver_x']
df['distance_y_to_receiver'] = df['y_last'] - df['receiver_y']
df['angle_between_dir_and_receiver'] = angle_between_two_vectors(
df['sin_dir'], df['cos_dir'], df['distance_x_to_receiver'], df['distance_y_to_receiver']
)
df['sin_angle_between_dir_and_receiver'] = sin(df['angle_between_dir_and_receiver'])
df['cos_angle_between_dir_and_receiver'] = cos(df['angle_between_dir_and_receiver'])
df['angle_between_o_and_receiver'] = angle_between_two_vectors(df['sin_o'], df['cos_o'], df['distance_x_to_receiver'], df['distance_y_to_receiver'])
df['sin_angle_between_o_and_receiver'] = sin(df['angle_between_o_and_receiver'])
df['cos_angle_between_o_and_receiver'] = cos(df['angle_between_o_and_receiver'])
df['time_left'] = (df['num_frames_output'] - (df['frame_id'] - 1) )/10
df['required_speed'] = df['dist_between_ball_land_and_player'] / df['time_left']
df['required_velocity_x'] = df['dist_x_between_ball_and_player'] / df['time_left']
df['required_velocity_y'] = df['dist_y_between_ball_and_player'] / df['time_left']
df['required_acc_x'] = (df['required_velocity_x'] - df['velocity_x']) / df['time_left']
df['required_acc_y'] = (df['required_velocity_y'] - df['velocity_y']) / df['time_left']
df['required_speed_diff'] = df['required_speed'] - df['s']
df['required_velocity_x_diff'] = df['required_velocity_x'] - df['velocity_x']
df['required_velocity_y_diff'] = df['required_velocity_y'] - df['velocity_y']
df['required_acc_x_diff'] = df['required_acc_x'] - df['acc_x']
df['required_acc_y_diff'] = df['required_acc_y'] - df['acc_y']
df['proj_x_acc'] = df['x_last'] + df['velocity_x']*df['time_left'] + 0.5*df['acc_x']*(df['time_left']**2)
df['proj_y_acc'] = df['y_last'] + df['velocity_y']*df['time_left'] + 0.5*df['acc_y']*(df['time_left']**2)
df['proj_x_velocity'] = df['x_last'] + df['velocity_x']*df['time_left']
df['proj_y_velocity'] = df['y_last'] + df['velocity_y']*df['time_left']
df['proj_x_acc_diff'] = df['ball_land_x'] - df['proj_x_acc']
df['proj_y_acc_diff'] = df['ball_land_y'] - df['proj_y_acc']
df['proj_x_velocity_diff'] = df['ball_land_x'] - df['proj_x_velocity']
df['proj_y_velocity_diff'] = df['ball_land_y'] - df['proj_y_velocity']
year = date.today().year
s = pd.to_datetime(df['player_birth_date'])
df['player_age'] = year - s.dt.year
df['absolute_yardline_number'] = np.clip(df['absolute_yardline_number'], 0, 100.0)
return df
def get_train_df(input_df, output_df):
input_df = input_df.copy()
input_df = reflect_input_player_positions(input_df)
output_df = output_df.copy()
df_last_frame = get_last_frame(input_df)
df_last_frame = add_nearest_dis_info(df_last_frame)
df_last_frame = add_reciever_info(df_last_frame)
cols = ['game_id', 'play_id', 'nfl_id', 'absolute_yardline_number', 'player_height', 'player_weight', 'player_birth_date',
'play_direction','player_position', 'player_role', 'x_last', 'y_last', 's', 'a', 'dir', 'o', 'num_frames_output', 'ball_land_x',
'ball_land_y', 'x_prev', 'y_prev', 'o_prev', 'dir_prev', 's_prev', 'a_prev', 'nearest_offense_dis', 'nearest_offense_dis_x',
'nearest_offense_dis_y', 'nearest_defense_dis', 'nearest_defense_dis_x', 'nearest_defense_dis_y', 'receiver_x', 'receiver_y']
df_last_frame = df_last_frame[cols]
df_merged = output_df.merge(df_last_frame, on=['game_id', 'play_id', 'nfl_id'], how='left')
df_merged = reflect_output_player_positions(df_merged)
X_df = clean_and_extract_features(df_merged)
X_df['player_role'] = X_df['player_role'].astype('category')
dx = X_df['x'] - X_df['x_last']
dy = X_df['y'] - X_df['y_last']
Y_df = pd.DataFrame({'dx' : dx, 'dy' :dy})
return X_df, Y_df
def get_train_valid_split(X_df, Y_df, test_ratio=0.2):
tr = X_df.shape[0]
test_s = int(tr*(1-test_ratio))
X_df_train = X_df[:test_s]
Y_df_train = Y_df[:test_s]
X_df_valid = X_df[test_s:]
Y_df_valid = Y_df[test_s:]
return X_df_train, Y_df_train, X_df_valid, Y_df_valid
def create_output_frames(df):
num_frames = df['num_frames_output'].iloc[0]
frame_id = pd.Series(np.arange(1, num_frames+1), name='frame_id')
return df.merge(frame_id, how='cross')
def get_params(is_pred=True):
params = {
'task':'train',
'objective':'regression',
'bagging_freq' : 1,
'bagging_fraction' : 0.75,
'learning_rate' : 0.05,
'device_type' : 'gpu',
'num_threads':8,
'n_estimators':2000,
'seed' : 42,
'max_depth':80,
'max_leaves' : 100,
'min_data_in_leaf' : 200,
'feature_fraction' : 0.80,
'lambda_l1' : 0.5,
'lambda_l2' : 0.5,
'early_stopping_rounds' : 100,
'early_stopping_min_delta' : 0.001,
'verbose':-1,
'metric' :'rmse'
}
if is_pred:
params['task'] = 'predict'
return params
def get_test_df(input_df):
df = input_df.copy()
df = reflect_input_player_positions(df)
df_last_frame = get_last_frame(df)
df_last_frame = add_nearest_dis_info(df_last_frame)
df_last_frame = add_reciever_info(df_last_frame)
df_last_frame = df_last_frame[df_last_frame['player_to_predict']].reset_index(drop=True)
num_frames_output = df_last_frame['num_frames_output'].values
df_with_output_frames = df_last_frame.loc[df_last_frame.index.repeat(num_frames_output)]
df_with_output_frames['frame_id'] = df_with_output_frames.groupby(level=0).cumcount() + 1
X_df = clean_and_extract_features(df_with_output_frames.reset_index(drop=True))
X_df['player_role'] = X_df['player_role'].astype('category')
return X_df
def calculate_rmse(X_df, Y_df, dx_train, dy_train):
dx_features = dx_train.feature_name()
dy_features = dy_train.feature_name()
pred_dx = dx_train.predict(X_df[dx_features])
pred_dy = dy_train.predict(X_df[dy_features])
n = Y_df.size
residual = (pred_dx - Y_df['dx'])**2 + (pred_dy - Y_df['dy'])**2
residual_avg = np.sum(residual) / n
return np.sqrt(residual_avg)
def publish_results(X_df, Y_df, dx_model, dy_model):
dx_features = dx_model.feature_name()
dy_features = dy_model.feature_name()
pred_dx = dx_model.predict(X_df[dx_features])
pred_dy = dy_model.predict(X_df[dy_features])
game_id = X_df['game_id'].values
play_id = X_df['play_id'].values
nfl_id = X_df['nfl_id'].values
frame_id = X_df['frame_id'].values
player_position = X_df['player_position'].values
player_role = X_df['player_role'].values
x_last = X_df['x_last'].values
y_last = X_df['y_last'].values
actual_x = X_df['x'].values
actual_y = X_df['y'].values
play_direction = X_df['play_direction'].values
pred_x = pred_dx + x_last
pred_y = pred_dy + y_last
mask = (play_direction == 'left')
if np.any(mask):
pred_x[mask] = 120 - pred_x[mask]
actual_x[mask] = 120 - actual_x[mask]
pred_x = np.clip(pred_x, 0.0, 120.0)
pred_y = np.clip(pred_y, 0.0, 53.3)
preds = np.column_stack([game_id, play_id, nfl_id, frame_id, player_position,
player_role, play_direction, x_last, y_last, actual_x, actual_y, pred_x, pred_y])
df = pd.DataFrame(preds, columns=['game_id', 'play_id', 'nfl_id','frame_id', 'player_position',
'player_role','play_direction',
'x_last', 'y_last', 'actual_x', 'actual_y', 'pred_x', 'pred_y'])
df.to_csv('lightGBT_test_data_results.csv', index=False)
print('results published')
def train(input_df, output_df):
given_input_cols = set(input_df.columns)
for c in MANDATORY_COLS:
if c not in given_input_cols:
raise Exception(f'{c} is missing in input_df')
elif input_df[c].isna().any():
raise Exception(f'{c} in input_df contains nan')
given_output_cols = set(output_df.columns)
for c in MANDATORY_COLS:
if c not in given_output_cols:
raise Exception(f'{c} is missing in output_df')
elif output_df[c].isna().any():
raise Exception(f'{c} in output_df contains nan')
for c in BASE_COLS:
if c not in input_df.columns:
input_df[c] = np.nan
print('getting train_df, valid_df')
X_df, Y_df = get_train_df(input_df, output_df)
print('splitting train test')
X_train_df, Y_train_df, X_valid_df, Y_valid_df = get_train_valid_split(X_df, Y_df, test_ratio=0.05)
filt_cols = ['player_birth_date', 'x', 'y', 'game_id', 'play_id', 'nfl_id', 'play_direction', 'player_position']
X_train_filt_df = X_train_df.drop(columns=filt_cols)
X_valid_filt_df = X_valid_df.drop(columns=filt_cols)
print('training started')
train_set_dx = lgb.Dataset(data=X_train_filt_df, label=Y_train_df['dx'])
train_sub_set_dx = lgb.Dataset(data=X_train_filt_df[:50000], label=Y_train_df['dx'][:50000], reference=train_set_dx)
valid_set_dx = lgb.Dataset(data=X_valid_filt_df, label=Y_valid_df['dx'], reference=train_set_dx)
train_set_dy = lgb.Dataset(data=X_train_filt_df, label=Y_train_df['dy'])
train_sub_set_dy = lgb.Dataset(data=X_train_filt_df[:50000], label=Y_train_df['dy'][:50000], reference=train_set_dy)
valid_set_dy = lgb.Dataset(data=X_valid_filt_df, label=Y_valid_df['dy'], reference=train_set_dy)
params = get_params(False)
dx_model = lgb.train(params = params,
train_set=train_set_dx,
valid_sets=[train_sub_set_dx, valid_set_dx],
valid_names=['train', 'valid'],
callbacks=[
lgb.log_evaluation(period=10)
]
)
dy_model = lgb.train(params = params,
train_set=train_set_dy,
valid_sets=[train_sub_set_dy, valid_set_dy],
valid_names=['train', 'valid'],
callbacks=[
lgb.log_evaluation(period=10)
]
)
publish_results(X_valid_df, Y_valid_df, dx_model, dy_model)
train_rmse = calculate_rmse(X_train_df, Y_train_df, dx_model, dy_model)
valid_rmse = calculate_rmse(X_valid_df, Y_valid_df, dx_model, dy_model)
print(f'rmse on the training set is :{train_rmse}')
print(f'rmse on the validation set is :{valid_rmse}')
return dx_model, dy_model
def get_features_by_importance(model):
sort_index = np.argsort(-model.feature_importance(importance_type='gain'))
return np.array(model.feature_name())[sort_index]
def train_test():
print('started')
input_df, output_df = get_input_output_df()
print('fetched input and output')
dx_model, dy_model = train(input_df, output_df)
dx_model.save_model('lightGBT_dx_model.txt', num_iteration=dx_model.best_iteration)
print('model lightGBT_dx_model saved')
dy_model.save_model('lightGBT_dy_model.txt', num_iteration=dy_model.best_iteration)
print('model lightGBT_dy_model saved')
dx_model_feature_importance = get_features_by_importance(dx_model)
dy_model_feature_importance = get_features_by_importance(dy_model)
print(f'top 15 features for dx_model: {dx_model_feature_importance[:15]}')
print(f'top 15 features for dy_model: {dy_model_feature_importance[:15]}')
if __name__ == '__main__':
train_test()