ModelLoading.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import os
  2. from pyexpat import features
  3. import sys
  4. import json
  5. import torch
  6. import optuna
  7. import joblib
  8. weather_utils_path = os.path.abspath(
  9. os.path.join(os.path.dirname(__file__), "..")
  10. )
  11. if weather_utils_path not in sys.path:
  12. sys.path.insert(0, weather_utils_path)
  13. from model.DLinear import DLinearModel
  14. from model.GRU import GRUModel
  15. from model.TSMixer import TSMixer
  16. from model.TCN import TemporalConvNet
  17. from model.Mamba import Mamba
  18. from model.CNN_LSTM import CNNLSTM
  19. from model.TransformerEn import TransformerEncoderForecaster
  20. class SolorFeature:
  21. def __init__(self) -> None:
  22. self.features_dict = {
  23. 'v24': ['ssrd','tcc','lcc','mcc','hcc','2t','2d','10u','10v']
  24. }
  25. def get_feature(self,release):
  26. return self.features_dict.get(release)
  27. class ModelLoading:
  28. """
  29. ModelLoading类用于根据传入的模型名称和Optuna调参trial对象,自动配置并实例化深度学习模型(如DLinear、GRU、TSMixer(以及相关的超参数设置。
  30. 该类支持模型的加载训练调优模式和预测权重加载模式,便于模型选择、超参数搜索及后续的推理流程。
  31. """
  32. def __init__(self,
  33. trial,
  34. projs = 'OL_JIANGMEN',
  35. model_name = 'Dlinear',
  36. release = 'None',
  37. input_seq = 288,
  38. saved_model_path = 'None',
  39. predict_mode = False,
  40. capacity = '4050'
  41. ) -> None:
  42. """
  43. 参数说明:
  44. trial: Optuna的trial对象或占位符(None(,用以搜索和记录超参数组合。如在预测或确定性训练场景下可设为None。
  45. model_name: 选择要加载/调优的模型名称。支持'Dlinear', 'GRU', 'TSMixer'等(默认'Dlinear')。
  46. input_seq: 输入序列长度,通常为一天内的采样步数等,用于确定模型的输入和输出时序长度(默认288)。
  47. model_path: 预测模式下(指定要加载预训练权重的模型文件路径(默认'None')。
  48. predict_mode: 是否为预测模式(如果为True,则跳过超参数采样只加载已有权重的最佳模型(默认False)。
  49. """
  50. self.trial = trial
  51. self.projs = projs
  52. self.release = release
  53. self.features = SolorFeature().get_feature(self.release)
  54. self.input_seq = input_seq
  55. self.model_name = model_name
  56. self.saved_model_path = saved_model_path
  57. self.predict_mode = predict_mode
  58. self.capacity = capacity
  59. if self.release != 'None':
  60. os.makedirs(os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq), self.release), exist_ok=True)
  61. self.saved_model_root = os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq), self.release)
  62. else:
  63. os.makedirs(os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq)), exist_ok=True)
  64. self.saved_model_root = os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq))
  65. # 训练模式下,进行超参数搜索
  66. if isinstance(self.trial, optuna.trial.Trial):
  67. if self.model_name == 'Dlinear':
  68. kernel_size = self.trial.suggest_categorical("kernel_size", [5, 9, 15, 25])
  69. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  70. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  71. self.model_config = {
  72. 'model': DLinearModel(
  73. seq_len = self.input_seq,
  74. pred_len = self.input_seq,
  75. individual = True,
  76. channels = len(self.features),
  77. channels_outs = 1,
  78. kernel_size = kernel_size
  79. ),
  80. 'lr': lr,
  81. 'batch_size': batch_size
  82. }
  83. elif self.model_name == 'GRU':
  84. hidden_channels = self.trial.suggest_categorical("hidden_channels", [4,16,32,64,128,256,512])
  85. layers = self.trial.suggest_categorical("layers", [1,2,3,4,5,6,7,8,9,10])
  86. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  87. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  88. self.model_config = {
  89. 'model': GRUModel(
  90. input_channels = len(self.features),
  91. output_channels = 1,
  92. input_len = self.input_seq,
  93. pred_len = self.input_seq,
  94. hidden_channels = hidden_channels,
  95. layers = layers
  96. ),
  97. 'lr': lr,
  98. 'batch_size': batch_size
  99. }
  100. elif self.model_name == 'TSMixer':
  101. num_blocks = self.trial.suggest_categorical("num_blocks", [2,4,6,8,10,12,14,16])
  102. dropout_rate = self.trial.suggest_categorical("dropout_rate", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
  103. ff_dim = self.trial.suggest_categorical("ff_dim", [8,16,32,64,128,256])
  104. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  105. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  106. self.model_config = {
  107. 'model': TSMixer(
  108. sequence_length = self.input_seq,
  109. prediction_length = self.input_seq,
  110. input_channels = len(self.features),
  111. output_channels = 1,
  112. activation_fn = 'relu',
  113. num_blocks = num_blocks,
  114. dropout_rate = dropout_rate,
  115. ff_dim = ff_dim,
  116. normalize_before = True,
  117. norm_type = 'batch',
  118. ),
  119. 'lr': lr,
  120. 'batch_size':batch_size
  121. }
  122. elif self.model_name == 'TCN':
  123. num_channels_options = [
  124. (8, 1),
  125. (16, 8, 1),
  126. (32, 16, 8, 1),
  127. (64, 32, 16, 8, 1),
  128. (128, 64, 32, 16, 8, 1)
  129. ]
  130. num_channels = self.trial.suggest_categorical("num_channels", num_channels_options)
  131. kernel_size = self.trial.suggest_categorical("kernel_size", [2,4,6,8,10,12,14,16])
  132. dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
  133. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  134. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  135. self.model_config = {
  136. 'model': TemporalConvNet(
  137. num_inputs= len(self.features),
  138. num_channels= num_channels,
  139. kernel_size= kernel_size,
  140. dropout= dropout
  141. ),
  142. 'lr': lr,
  143. 'batch_size':batch_size
  144. }
  145. elif self.model_name == 'Mamba':
  146. d_model = self.trial.suggest_categorical("d_model", [4, 8, 16, 32, 64, 128, 256])
  147. d_conv = self.trial.suggest_categorical("d_conv", [4,6,8,10,12,14,16])
  148. n_layer = self.trial.suggest_categorical("n_layer", [1,2,3,4,5,6])
  149. d_state = self.trial.suggest_categorical("d_state", [2,4,8,16,32])
  150. expand = self.trial.suggest_categorical("expand", [1,2,3,4])
  151. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  152. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  153. self.model_config = {
  154. 'model': Mamba(
  155. d_model = d_model,
  156. n_layer = n_layer,
  157. input_dim = len(self.features),
  158. output_dim = 1,
  159. d_state = d_state,
  160. expand = expand,
  161. dt_rank = 'auto',
  162. d_conv = d_conv
  163. ),
  164. 'lr': lr,
  165. 'batch_size':batch_size
  166. }
  167. elif self.model_name == 'CNNLSTM':
  168. cnn_channels = self.trial.suggest_categorical("cnn_channels", [16, 32, 64, 128])
  169. lstm_hidden = self.trial.suggest_categorical("lstm_hidden", [16, 32, 64, 128])
  170. lstm_layers = self.trial.suggest_categorical("lstm_layers", [1, 2, 3, 4])
  171. dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
  172. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  173. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  174. self.model_config = {
  175. 'model': CNNLSTM(
  176. n_features = len(self.features),
  177. pred_len = self.input_seq,
  178. cnn_channels = cnn_channels,
  179. lstm_hidden = lstm_hidden,
  180. lstm_layers = lstm_layers,
  181. dropout = dropout
  182. ),
  183. 'lr': lr,
  184. 'batch_size': batch_size
  185. }
  186. elif self.model_name == 'TransformerEn':
  187. d_model = self.trial.suggest_categorical("d_model", [16, 32, 64, 128])
  188. n_heads = self.trial.suggest_categorical("n_heads", [1, 2, 4, 8])
  189. num_layers = self.trial.suggest_categorical("num_layers", [1, 2, 3, 4])
  190. dim_feedforward = self.trial.suggest_categorical("dim_feedforward", [16, 32, 64, 128])
  191. dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
  192. lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
  193. batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
  194. pooling = self.trial.suggest_categorical("pooling", ["last", "mean"])
  195. self.model_config = {
  196. 'model': TransformerEncoderForecaster(
  197. n_features = len(self.features),
  198. pred_len = self.input_seq,
  199. d_model = d_model,
  200. n_heads = n_heads,
  201. num_layers = num_layers,
  202. dim_feedforward = dim_feedforward,
  203. dropout = dropout,
  204. pooling = pooling
  205. ),
  206. 'lr': lr,
  207. 'batch_size': batch_size
  208. }
  209. else:
  210. raise ValueError(f"Unsupported model_name: {self.model_name}")
  211. else:
  212. # 加载最优参数,只为当前模型加载对应参数
  213. # 获取最佳参数路径
  214. model_param_path = os.path.join(self.saved_model_root,'best_tpe_params.json')
  215. if os.path.exists(model_param_path):
  216. # 加载最佳参数
  217. with open(model_param_path, 'r', encoding='utf-8') as f:
  218. self.model_param_dict = json.load(f)
  219. best_params = self.model_param_dict['best_params']
  220. else:
  221. raise FileNotFoundError(f"Model parameter file not found:{self.projs}-{self.release}-{self.model_name}")
  222. if self.model_name == 'Dlinear':
  223. self.model_config = {
  224. 'model': DLinearModel(
  225. seq_len = self.input_seq,
  226. pred_len = self.input_seq,
  227. individual = True,
  228. channels = len(self.features),
  229. channels_outs = 1,
  230. kernel_size = best_params['kernel_size']
  231. ),
  232. 'lr': best_params['learning_rate'],
  233. 'batch_size': best_params['batch_size']
  234. }
  235. elif self.model_name == 'GRU':
  236. self.model_config = {
  237. 'model': GRUModel(
  238. input_channels = len(self.features),
  239. output_channels = 1,
  240. input_len = self.input_seq,
  241. pred_len = self.input_seq,
  242. hidden_channels = best_params['hidden_channels'],
  243. layers = best_params['layers']
  244. ),
  245. 'lr': best_params['learning_rate'],
  246. 'batch_size': best_params['batch_size']
  247. }
  248. elif self.model_name == 'TSMixer':
  249. self.model_config = {
  250. 'model': TSMixer(
  251. sequence_length = self.input_seq,
  252. prediction_length = self.input_seq,
  253. input_channels = len(self.features),
  254. output_channels = 1,
  255. activation_fn = 'relu',
  256. num_blocks = best_params['num_blocks'],
  257. dropout_rate = best_params['dropout_rate'],
  258. ff_dim = best_params['ff_dim'],
  259. normalize_before = True,
  260. norm_type = 'batch',
  261. ),
  262. 'lr': best_params['learning_rate'],
  263. 'batch_size': best_params['batch_size']
  264. }
  265. elif self.model_name == 'TCN':
  266. self.model_config = {
  267. 'model': TemporalConvNet(
  268. num_inputs= len(self.features),
  269. num_channels= best_params["num_channels"],
  270. kernel_size= best_params["kernel_size"],
  271. dropout= best_params["dropout"]
  272. ),
  273. 'lr': best_params['learning_rate'],
  274. 'batch_size': best_params['batch_size']
  275. }
  276. elif self.model_name == 'Mamba':
  277. self.model_config = {
  278. 'model': Mamba(
  279. d_model = best_params["d_model"],
  280. n_layer = best_params["n_layer"],
  281. input_dim = len(self.features),
  282. output_dim = 1,
  283. d_state = best_params["d_state"],
  284. expand = best_params["expand"],
  285. d_conv = best_params["d_conv"]
  286. ),
  287. 'lr': best_params['learning_rate'],
  288. 'batch_size': best_params['batch_size']
  289. }
  290. elif self.model_name == 'CNNLSTM':
  291. self.model_config = {
  292. 'model': CNNLSTM(
  293. n_features = len(self.features),
  294. pred_len = self.input_seq,
  295. cnn_channels = best_params['cnn_channels'],
  296. lstm_hidden = best_params['lstm_hidden'],
  297. lstm_layers = best_params['lstm_layers'],
  298. dropout = best_params['dropout']
  299. ),
  300. 'lr': best_params['learning_rate'],
  301. 'batch_size': best_params['batch_size']
  302. }
  303. elif self.model_name == 'TransformerEn':
  304. self.model_config = {
  305. 'model': TransformerEncoderForecaster(
  306. n_features = len(self.features),
  307. pred_len = self.input_seq,
  308. d_model = best_params['d_model'],
  309. n_heads = best_params['n_heads'],
  310. num_layers = best_params['num_layers'],
  311. dim_feedforward = best_params['dim_feedforward'],
  312. dropout = best_params['dropout'],
  313. pooling = best_params['pooling']
  314. ),
  315. 'lr': best_params['learning_rate'],
  316. 'batch_size': best_params['batch_size']
  317. }
  318. else:
  319. raise ValueError(f"Unsupported model_name: {self.model_name}")
  320. # 启动预测模式
  321. if self.predict_mode:
  322. print(f'{self.model_name}--进入预测模式!!!')
  323. # 判断当前是否有可用的GPU,如果有则使用cuda,否则使用cpu
  324. # 加载最佳模型
  325. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  326. self.model = self.get_model()
  327. model_weigth_file = os.path.join(self.saved_model_root, 'best_model.pth')
  328. self.model.load_state_dict(torch.load(model_weigth_file, map_location=device))
  329. self.model.eval()
  330. # 加载归一化器
  331. scaler_path = os.path.join(self.saved_model_root, "scaler.pkl")
  332. if not os.path.exists(scaler_path):
  333. raise FileNotFoundError(f"Scaler file not found: {self.proj}-{self.model_name}-{self.pre_seq}-{self.release}")
  334. self.scaler = joblib.load(scaler_path)
  335. def get_model(self):
  336. return self.model_config.get('model')
  337. def get_lr(self):
  338. return self.model_config.get('lr')
  339. def get_batch_size(self):
  340. return self.model_config.get('batch_size')
  341. def predict(self, features_in):
  342. """
  343. 功能:
  344. 用于对输入的特征进行模型预测,输出预测结果。
  345. 参数:
  346. features_in: 输入特征数据,格式应为模型可接受的数据类型(如numpy数组、Tensor等),
  347. shape = (batch_size, seq_len, feature_dim)。
  348. 返回:
  349. 预测结果shape = (batch_size, seq_len, 1)。
  350. """
  351. if not self.predict_mode:
  352. raise RuntimeError("Model is not in prediction mode. Please set predict=True in ModelLoading to use predict().")
  353. else:
  354. x_scaled = self.scaler_fn(features_in)
  355. return self.inverse_transform(self.model(x_scaled),self.capacity)
  356. def inverse_transform(self, y, capacity):
  357. """
  358. 对模型输出进行反归一化,以恢复到原始的功率尺度。
  359. 参数:
  360. y: 模型输出,归一化后的数据(如Tensor或ndarray)。
  361. capacity: 电站容量(标量),用于恢复为实际功率。
  362. 返回:
  363. 反归一化后的功率预测值。
  364. """
  365. return y * capacity
  366. def scaler_fn(self, x):
  367. """
  368. 对输入特征 x 进行归一化处理。
  369. 参数:
  370. x: 可以为 torch.Tensor 或 numpy.ndarray 类型的输入特征,
  371. 通常形状为 (1, seq_len, feature_dim)。
  372. 返回:
  373. 归一化后的数据,返回torch.Tensor;
  374. """
  375. # 如果传入的是torch张量,则转为numpy数组进行归一化
  376. B, L, M = x.shape
  377. if isinstance(x, torch.Tensor):
  378. x_np = x.detach().cpu().numpy() # (B, L, M)
  379. x_np_flat = x_np.reshape(-1, M) # (B*L, M)
  380. x_scaled_flat = self.scaler.transform(x_np_flat) # (B*L, M)
  381. x_scaled = x_scaled_flat.reshape(B, L, M) # (B, L, M)
  382. return torch.from_numpy(x_scaled).to(x.device).type(x.dtype)
  383. else:
  384. x_flat = x.reshape(-1, M)
  385. x_scaled_flat = self.scaler.transform(x_flat)
  386. x_scaled = x_scaled_flat.reshape(B, L, M)
  387. return x_scaled