|
|
|
|
| from datetime import datetime
|
| import pandas as pd
|
|
|
| from tensorflow.keras.models import Model, Sequential,load_model
|
| from keras.layers import *
|
| from tensorflow.keras import regularizers
|
| import numpy as np
|
| from keras.optimizers import *
|
| import pandas_ta as ta
|
| from datetime import datetime, timedelta
|
| from sklearn.preprocessing import MinMaxScaler
|
| import joblib
|
| import os
|
| import torch
|
| import torch.nn as nn
|
| import torch.optim as optim
|
| from torch.utils.data import DataLoader, TensorDataset
|
|
|
| features = ['open','high', 'low', 'close']
|
| class EAUtils:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def __init__(self, modelname,timestep,feature):
|
| self.modelname = modelname
|
| self.timestep = timestep
|
| self.feature = feature
|
|
|
| @staticmethod
|
| def MajorList():
|
| return ["DXYm","GBPUSDm","EURUSDm","NZDUSDm","AUDUSDm","USDCADm","USDJPYm","USDCHFm"]
|
|
|
| @staticmethod
|
| def MinorList():
|
| return ["EURJPYm","GBPJPYm","USDCHFm","XAUUSDm","EURAUDm"]
|
|
|
| @staticmethod
|
| def get_point_from_symbol(symbolname: str) -> float:
|
| point_map = {
|
| "EURUSDm": 0.00001,
|
| "GBPUSDm": 0.00001,
|
| "AUDUSDm": 0.00001,
|
| "NZDUSDm": 0.00001,
|
| "USDCADm": 0.00001,
|
| "USDCHFm": 0.00001,
|
| "USDJPYm": 0.001,
|
| "XAUUSDm": 0.001,
|
| "DXYm": 0.01,
|
| }
|
| return point_map.get(symbolname, 0.00001)
|
|
|
| @staticmethod
|
| def get_digit_from_symbol(symbolname: str) -> int:
|
| point_map = {
|
| "EURUSDm": 5,
|
| "GBPUSDm": 5,
|
| "AUDUSDm": 5,
|
| "NZDUSDm": 5,
|
| "USDCADm": 5,
|
| "USDCHFm": 5,
|
| "USDJPYm": 3,
|
| "XAUUSDm": 3,
|
| "DXYm": 2,
|
| }
|
| return point_map.get(symbolname, 5)
|
|
|
| def GenerateDayData(self):
|
| mt5.initialize()
|
| rates = mt5.copy_rates_from_pos(self.modelname, mt5.TIMEFRAME_D1, 0, 3650)
|
| df = pd.DataFrame(rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(features)
|
| mt5.shutdown()
|
| return df
|
|
|
| def GenerateWeekData(self):
|
| mt5.initialize()
|
| rates = mt5.copy_rates_from_pos(self.modelname, mt5.TIMEFRAME_W1, 0, 520)
|
| df = pd.DataFrame(rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| mt5.shutdown()
|
| return df
|
|
|
| def GenerateMonthData(self):
|
|
|
| mt5.initialize()
|
| rates = mt5.copy_rates_from_pos(self.modelname, mt5.TIMEFRAME_MN1, 0, 120)
|
| df = pd.DataFrame(rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| mt5.shutdown()
|
| return df
|
|
|
| def collect_dataset(self,df, history_size):
|
| """
|
| Collect dataset for the following regression problem:
|
| - input: history_size consecutive H1 bars;
|
| - output: close price for the next bar.
|
|
|
| :param df: D1 bars for a range of dates
|
| :param history_size: how many bars should be considered for making a prediction
|
| :return: features and labels
|
| """
|
| n = len(df)
|
| xs = []
|
| ys = []
|
| for i in range(n - history_size):
|
| w = df.iloc[i: i + history_size + 1]
|
|
|
| x = w[['open', 'high', 'low', 'close','tick_volume']].iloc[:-1].values
|
|
|
| y = w[['high','low']].shift(-1)
|
| xs.append(x)
|
| ys.append(y)
|
|
|
| X = np.array(xs)
|
| y = np.array(ys)
|
| return X, y
|
|
|
|
|
| def singleStepSampler(self,df, window):
|
| xRes = []
|
| yRes = []
|
| for i in range(0, len(df) - window):
|
| res = []
|
| for j in range(0, window):
|
| r = []
|
| for col in df.columns:
|
| r.append(df[col][i + j])
|
|
|
| res.append(r)
|
| xRes.append(res)
|
| yRes.append(df[['high','low']].iloc[i + window].to_numpy())
|
| return np.array(xRes), np.array(yRes)
|
|
|
| def create_sequences_unistep(self,data, n_steps):
|
| data_t = data.to_numpy()
|
| X = []
|
| y = []
|
|
|
| for i in range(len(data_t)-n_steps):
|
| row = [a for a in data_t[i:i+n_steps]]
|
| X.append(row)
|
|
|
| label = data_t[i+n_steps][0]
|
| y.append(label)
|
|
|
| return np.array(X), np.array(y)
|
|
|
| def split_sequence_multistep(self,data, n_steps_in, n_steps_out):
|
|
|
| if hasattr(data, "values"):
|
| data = data.values
|
|
|
| X, y = [], []
|
| for i in range(len(data) - n_steps_in - n_steps_out + 1):
|
| seq_x = data[i:i+n_steps_in, :]
|
| seq_y = data[i+n_steps_in:i+n_steps_in+n_steps_out, 1:3]
|
| X.append(seq_x)
|
| y.append(seq_y)
|
| return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32)
|
|
|
|
|
|
|
|
|
|
|
| def split_sequence_multistep_target(self,data, n_steps_in, n_steps_out):
|
|
|
| if hasattr(data, "values"):
|
| data = data.values
|
|
|
| X, y_high,y_low = [], [],[]
|
| for i in range(len(data) - n_steps_in - n_steps_out + 1):
|
| seq_x = data[i:i+n_steps_in, :]
|
| seq_y_high = data[i+n_steps_in:i+n_steps_in+n_steps_out, 1:2]
|
| seq_y_low = data[i+n_steps_in:i+n_steps_in+n_steps_out, 2:3]
|
| X.append(seq_x)
|
| y_high.append(seq_y_high)
|
| y_low.append(seq_y_low)
|
| return np.array(X, dtype=np.float32), np.array(y_high, dtype=np.float32) , np.array(y_low, dtype=np.float32)
|
|
|
|
|
|
|
|
|
| def fetch_data(self,symbol, timeframe='', num_bars=1200):
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
| df = mt5.copy_rates_from_pos(symbol, timeframe, 0, num_bars)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| return df
|
|
|
|
|
|
|
|
|
| def fetch_dataV2(self,symbol, timeframe='', num_bars=3650):
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
| df = mt5.copy_rates_from_pos(symbol, timeframe, 0, num_bars)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| return df
|
|
|
|
|
|
|
|
|
| def fetch_dataV3(self,symbol, timeframe='', start=700,end=3650):
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
| df = mt5.copy_rates_from_pos(symbol, timeframe, start, end)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| return df
|
|
|
|
|
|
|
|
|
| def fetch_dataV4(self, symbol, timeframe=mt5.TIMEFRAME_D1, num_bars=3650):
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
|
|
| df = mt5.copy_rates_from_pos(symbol, timeframe, 0, num_bars)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
|
|
|
|
| df = df.filter(['time','open','high','low','close','tick_volume'])
|
|
|
|
|
| df = df.sort_values(by='time', ascending=True).reset_index(drop=True)
|
|
|
|
|
| df['hour'] = df['time'].dt.hour / 23.0
|
| df['weekday'] = df['time'].dt.weekday / 6.0
|
| df['month'] = (df['time'].dt.month - 1) / 11.0
|
|
|
| return df
|
|
|
|
|
|
|
|
|
|
|
| def fetch_last5Years(self,symbol, timeframe='', num_bars=900):
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
| df = mt5.copy_rates_from_pos(symbol, timeframe, 900, num_bars)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=True)
|
| df.sort_index(ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close','tick_volume'])
|
| return df
|
|
|
|
|
|
|
|
|
| @staticmethod
|
| def add_features(data):
|
|
|
| data = data.copy()
|
|
|
| adx = data.ta.adx(length=5)
|
| data['adx'] = adx['ADX_5']
|
|
|
| atr = data.ta.atr(length=5)
|
| data['atr'] = atr
|
|
|
| sar = data.ta.psar(high=data['high'], low=data['low'], close=data['close'], af0=0.02, af=0.02, max_af=0.2)
|
|
|
|
|
|
|
| data['psar'] = sar['PSARr_0.02_0.2']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| data = data.dropna()
|
|
|
| return data
|
|
|
|
|
|
|
|
|
| @staticmethod
|
| def add_seasonal_features(df):
|
|
|
| df = df.copy()
|
|
|
| df['datetime'] = pd.to_datetime(df['time'], unit='s') if 'time' in df.columns else pd.to_datetime(df.index)
|
|
|
|
|
| df['hour'] = df['datetime'].dt.hour
|
| df['day_of_week'] = df['datetime'].dt.dayofweek
|
| df['day'] = df['datetime'].dt.day
|
| df['month'] = df['datetime'].dt.month
|
| df['week_of_year'] = df['datetime'].dt.isocalendar().week.astype(int)
|
|
|
|
|
| df = pd.get_dummies(df, columns=['day_of_week', 'month'])
|
|
|
| return df
|
|
|
| @staticmethod
|
| def add_time_feature(df):
|
|
|
| df = df.copy()
|
|
|
| df['hour'] = df.index.hour / 23.0
|
| df['weekday'] = df.index.weekday / 6.0
|
| df['month'] = (df.index.month - 1) / 11.0
|
|
|
| return df
|
|
|
|
|
| def add_candle_patterns_only(self,df: pd.DataFrame):
|
| df = df.copy()
|
|
|
|
|
| candle_patterns = [
|
| 'doji', 'hammer', 'inverted_hammer', 'hanging_man',
|
| 'shooting_star', 'engulfing', 'harami',
|
| 'morning_star', 'evening_star',
|
| 'piercing', 'dark_cloud_cover',
|
| ]
|
|
|
| for pattern in candle_patterns:
|
| try:
|
| df[f'cdl_{pattern}'] = ta.cdl_pattern(df, name=pattern)
|
| except Exception:
|
| df[f'cdl_{pattern}'] = 0
|
|
|
| return df
|
|
|
|
|
|
|
| def fetch_history(self,symbol,timeframe,startdate,enddate):
|
|
|
| if not mt5.initialize():
|
| print("initialize() failed")
|
| mt5.shutdown()
|
|
|
| df = mt5.copy_ticks_range(symbol, startdate, enddate, mt5.COPY_TICKS_ALL)
|
|
|
|
|
| df = pd.DataFrame(df)
|
| df.sort_values(by='time', ascending=True, inplace=True)
|
| df = df.filter(['open','high', 'low', 'close'])
|
| return df
|
|
|
|
|
|
|
|
|
| def create_sequences_singlestep3(self,data, time_step=10):
|
|
|
| data = EAUtils.add_features(data)
|
|
|
| expected_cols = ['open', 'high', 'low', 'close', 'adx','atr']
|
| expected_cols += [col for col in data.columns if 'day_of_week_' in col or 'month_' in col]
|
|
|
|
|
| data = data[expected_cols]
|
|
|
| X, y = [], []
|
| for i in range(len(data) - time_step):
|
| X.append(data.iloc[i:i+time_step].values)
|
| y.append(data.iloc[i+time_step][['high', 'low']].values)
|
| return np.array(X), np.array(y)
|
|
|
|
|
| def create_sequences_singlestep_futurewindow(self,data, timestep=5, future_window=7):
|
| """
|
| สร้างชุดข้อมูล X, y
|
| - X: ลำดับข้อมูล input (timestep แท่ง)
|
| - y: [max high, min low] ของช่วง future_window วันถัดไป
|
| """
|
|
|
| data = EAUtils.add_features(data)
|
|
|
|
|
| expected_cols = ['open', 'high', 'low', 'close', 'adx', 'atr']
|
| expected_cols += [col for col in data.columns if 'day_of_week_' in col or 'month_' in col]
|
| data = data[expected_cols]
|
|
|
| high = data['high'].values
|
| low = data['low'].values
|
|
|
| X, y = [], []
|
|
|
| for i in range(len(data) - timestep - future_window + 1):
|
| x_i = data.iloc[i:i + timestep].values
|
| future_high = high[i + timestep : i + timestep + future_window].max()
|
| future_low = low[i + timestep : i + timestep + future_window].min()
|
| X.append(x_i)
|
| y.append([future_high, future_low])
|
|
|
| return np.array(X), np.array(y)
|
|
|
|
|
| def create_sequences_singlestep_relative(self, data, timestep=5, future_window=1):
|
|
|
|
|
| high = data['high'].values
|
| low = data['low'].values
|
| close = data['close'].values
|
|
|
| X, y = [], []
|
| for i in range(len(data) - timestep - future_window + 1):
|
| x_i = data.iloc[i:i + timestep].values
|
|
|
| future_high = high[i + timestep : i + timestep + future_window].max()
|
| future_low = low[i + timestep : i + timestep + future_window].min()
|
| close_ref = close[i + timestep - 1]
|
|
|
| rel_high = (future_high / close_ref) - 1
|
| rel_low = (future_low / close_ref) - 1
|
|
|
| X.append(x_i)
|
| y.append([rel_high, rel_low])
|
|
|
| return np.array(X), np.array(y)
|
|
|
| def create_sequences_singlestep(self, x_data, y_data, timestep=5):
|
| """
|
| แยกสร้าง sequence สำหรับ single-step prediction
|
| - x_data : ndarray ที่รวม features ต่าง ๆ แล้ว (เช่น price + indicator)
|
| - y_data : ndarray ที่เป็น target เช่น high, low ที่ถูก scale แล้ว
|
| - timestep : จำนวนแท่งย้อนหลัง
|
|
|
| Return:
|
| X: shape (num_samples, timestep, num_features)
|
| y: shape (num_samples, num_targets)
|
| """
|
| X, y = [], []
|
|
|
| for i in range(len(x_data) - timestep):
|
| X.append(x_data[i:i + timestep])
|
| y.append(y_data[i + timestep])
|
|
|
| return np.array(X), np.array(y)
|
|
|
|
|
| def create_sequences_multistep_relative(self, data, timestep=5, forecast_horizon=5):
|
|
|
|
|
| high = data['high'].values
|
| low = data['low'].values
|
| close = data['close'].values
|
|
|
| X, y = [], []
|
|
|
| for i in range(len(data) - timestep - forecast_horizon + 1):
|
| x_i = data.iloc[i:i + timestep].values
|
| close_ref = close[i + timestep - 1]
|
|
|
| y_i = []
|
| for j in range(forecast_horizon):
|
| future_high = high[i + timestep + j]
|
| future_low = low[i + timestep + j]
|
|
|
| rel_high = (future_high / close_ref) - 1
|
| rel_low = (future_low / close_ref) - 1
|
|
|
| y_i.append([rel_high, rel_low])
|
|
|
| X.append(x_i)
|
| y.append(y_i)
|
|
|
| return np.array(X), np.array(y)
|
|
|
| def create_sequences_multistep_relative_log(self, data, timestep=30, forecast_horizon=5):
|
|
|
| high = data['high'].values
|
| low = data['low'].values
|
| close = data['close'].values
|
|
|
| X, y = [], []
|
|
|
| for i in range(len(data) - timestep - forecast_horizon + 1):
|
| x_i = data.iloc[i:i + timestep].values
|
| close_ref = close[i + timestep - 1]
|
|
|
| y_i = []
|
| for j in range(forecast_horizon):
|
| future_high = high[i + timestep + j]
|
| future_low = low[i + timestep + j]
|
|
|
|
|
| rel_high = np.log(future_high / close_ref)
|
| rel_low = np.log(future_low / close_ref)
|
|
|
| y_i.append([rel_high, rel_low])
|
|
|
| X.append(x_i)
|
| y.append(y_i)
|
|
|
| return np.array(X), np.array(y)
|
|
|
|
|
| def create_target_multistep_log_relative(self, data: pd.DataFrame, forecast_horizon: int = 5) -> pd.DataFrame:
|
| """
|
| สร้าง target multistep ที่เป็น log-relative ของ high/low เทียบกับ close ปัจจุบัน
|
| เช่น: log(high_t / close_t), log(low_t / close_t) สำหรับ t+1 ถึง t+n
|
| Return เป็น DataFrame ที่ flatten แล้ว (พร้อมใช้กับ Scaler)
|
| """
|
| log_targets = []
|
| col_names = []
|
|
|
| for i in range(forecast_horizon):
|
| col_names.append(f"log_rel_high_t{i+1}")
|
| col_names.append(f"log_rel_low_t{i+1}")
|
|
|
| for i in range(len(data) - forecast_horizon):
|
| window = data.iloc[i : i + forecast_horizon + 1]
|
| close_ref = window['close'].iloc[0]
|
| highs = window['high'].iloc[1:]
|
| lows = window['low'].iloc[1:]
|
|
|
| log_rel_highs = np.log(highs / close_ref)
|
| log_rel_lows = np.log(lows / close_ref)
|
|
|
| combined = np.empty((forecast_horizon * 2,))
|
| combined[0::2] = log_rel_highs
|
| combined[1::2] = log_rel_lows
|
|
|
| log_targets.append(combined)
|
|
|
| return pd.DataFrame(log_targets, columns=col_names)
|
|
|
|
|
| def create_sequences_multistep_from_scaled(
|
| self,
|
| X_scaled: np.ndarray,
|
| y_scaled: np.ndarray,
|
| timestep: int,
|
| forecast_horizon: int
|
| ):
|
| """
|
| สร้าง X, y ที่ใช้สำหรับ Multistep Prediction (แยก High กับ Low)
|
|
|
| Parameters:
|
| - X_scaled: (samples, features)
|
| - y_scaled: (samples, 2) -> column 0 = high, column 1 = low
|
| - timestep: จำนวนแท่งย้อนหลังใน input sequence
|
| - forecast_horizon: จำนวนวันที่ต้องการพยากรณ์ล่วงหน้า
|
|
|
| Returns:
|
| - X_seq: (num_samples, timestep, features)
|
| - y_high_seq: (num_samples, forecast_horizon)
|
| - y_low_seq: (num_samples, forecast_horizon)
|
| """
|
| X_seq = []
|
| y_high_seq = []
|
| y_low_seq = []
|
|
|
| for i in range(timestep, len(X_scaled) - forecast_horizon + 1):
|
| x_window = X_scaled[i - timestep:i]
|
| y_window = y_scaled[i:i + forecast_horizon]
|
|
|
| X_seq.append(x_window)
|
| y_high_seq.append(y_window[:, 0])
|
| y_low_seq.append(y_window[:, 1])
|
|
|
| return np.array(X_seq), np.array(y_high_seq), np.array(y_low_seq)
|
|
|
|
|
| def create_sequences_singlestep_split_Y(self,x_data, y_data, timestep=5):
|
| X, y_high, y_low = [], [], []
|
| for i in range(len(x_data) - timestep):
|
| X.append(x_data[i:i + timestep])
|
| y_high.append(y_data[i + timestep][0])
|
| y_low.append(y_data[i + timestep][1])
|
| return np.array(X), np.array(y_high), np.array(y_low)
|
|
|
|
|
| import os, joblib
|
|
|
| @staticmethod
|
| def load_column_scalers(df, scaler_folder):
|
| scalers = {}
|
| for col in df.columns:
|
| scaler_path = os.path.join(scaler_folder, f"{col}.pkl")
|
| if os.path.exists(scaler_path):
|
| scalers[col] = joblib.load(scaler_path)
|
| else:
|
| print(f"⚠ Warning: Scaler not found for {col}, skipping scaling.")
|
| scalers[col] = None
|
| return scalers
|
|
|
| def load_column_scaler_types(self,df, scaler_folder):
|
| """
|
| Load scaler types per column from folder.
|
| Returns dict {column_name: scaler class or None}
|
| """
|
| scaler_types = {}
|
| for col in df.columns:
|
| scaler_path = os.path.join(scaler_folder, f"{col}.pkl")
|
| if os.path.exists(scaler_path):
|
| scaler_types[col] = joblib.load(scaler_path).__class__
|
| else:
|
| print(f"⚠ Warning: Scaler not found for {col}, skipping scaling.")
|
| scaler_types[col] = None
|
| return scaler_types
|
|
|
| def auto_weight_decay_range(self,scaler, target_feature='high'):
|
| """
|
| ให้ scaler ของ target (MinMaxScaler, StandardScaler, PowerTransformer)
|
| จะ return lower, upper ของ weight_decay แบบ log-scale
|
| """
|
|
|
| if hasattr(scaler, 'data_min_') and hasattr(scaler, 'data_max_'):
|
|
|
| feature_min = scaler.data_min_[0]
|
| feature_max = scaler.data_max_[0]
|
| elif hasattr(scaler, 'scale_'):
|
|
|
| feature_min = -scaler.scale_[0]*3
|
| feature_max = scaler.scale_[0]*3
|
| else:
|
|
|
| feature_min, feature_max = -1, 1
|
|
|
|
|
| feature_range = feature_max - feature_min
|
|
|
|
|
| lower = max(1e-7, feature_range * 1e-7)
|
| upper = max(lower*10, feature_range * 1e-5)
|
|
|
| return lower, upper
|
|
|
| def auto_lr_range(self,X_train, base_lr=1e-4):
|
|
|
| input_scale = np.std(X_train)
|
|
|
| min_lr = base_lr / max(input_scale, 1.0)
|
| max_lr = base_lr * max(input_scale, 1.0)
|
| return min_lr, max_lr
|
|
|
|
|
| @staticmethod
|
| def dynamic_beta(y_true: torch.Tensor, y_pred: torch.Tensor, method='median', eps=1e-6):
|
| error = (y_true - y_pred).abs()
|
| if method == 'median':
|
| beta = error.median().item()
|
| elif method == 'std':
|
| beta = error.std().item()
|
| else:
|
| raise ValueError("method must be 'median' or 'std'")
|
| return max(beta, eps)
|
|
|
| @staticmethod
|
| def calc_weight_decay(lr, scale=0.1, min_wd=1e-8, max_wd=1e-3):
|
| """
|
| คำนวณ weight_decay จาก learning rate
|
|
|
| Parameters
|
| ----------
|
| lr : float
|
| learning rate ปัจจุบัน
|
| scale : float, optional
|
| สัดส่วนของ LR ที่จะใช้เป็น weight_decay, default 0.1
|
| min_wd : float, optional
|
| ค่าต่ำสุดของ weight_decay, default 1e-8
|
| max_wd : float, optional
|
| ค่าสูงสุดของ weight_decay, default 1e-3
|
|
|
| Returns
|
| -------
|
| float
|
| weight_decay ที่คำนวณแล้ว
|
| """
|
| wd = lr * scale
|
| wd = max(min_wd, min(max_wd, wd))
|
| return wd
|
|
|
| @staticmethod
|
| def get_optimizer_groupsV01(model, base_lr=1e-7, head_lr=1e-4, bias_lr=2e-4, weight_decay=1e-5):
|
| backbone_params = []
|
| output_params = []
|
| bias_params = []
|
|
|
| for name, param in model.named_parameters():
|
| if not param.requires_grad:
|
| continue
|
| if "bias" in name:
|
| bias_params.append(param)
|
| elif ("transformer" in name) or ("input_fc" in name) or ("pos_encoder" in name):
|
| backbone_params.append(param)
|
| elif "output" in name:
|
| output_params.append(param)
|
|
|
|
|
| total = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| print(f"Backbone: {sum(p.numel() for p in backbone_params)} params")
|
| print(f"Output: {sum(p.numel() for p in output_params)} params")
|
| print(f"Bias: {sum(p.numel() for p in bias_params)} params")
|
| print(f"Total: {total} params")
|
|
|
|
|
| seen = set()
|
| for group in [backbone_params, output_params, bias_params]:
|
| for p in group:
|
| assert id(p) not in seen, "Duplicate parameter detected!"
|
| seen.add(id(p))
|
|
|
| optimizer = torch.optim.Adam([
|
| {'params': backbone_params, 'lr': base_lr, 'weight_decay': weight_decay},
|
| {'params': output_params, 'lr': head_lr, 'weight_decay': weight_decay},
|
| {'params': bias_params, 'lr': bias_lr, 'weight_decay': 0.0},
|
| ], weight_decay=weight_decay)
|
|
|
| return optimizer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|