| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- import os
- from pyexpat import features
- import sys
- import json
- import torch
- import optuna
- import joblib
- weather_utils_path = os.path.abspath(
- os.path.join(os.path.dirname(__file__), "..")
- )
- if weather_utils_path not in sys.path:
- sys.path.insert(0, weather_utils_path)
-
- from model.DLinear import DLinearModel
- from model.GRU import GRUModel
- from model.TSMixer import TSMixer
- from model.TCN import TemporalConvNet
- from model.Mamba import Mamba
- from model.CNN_LSTM import CNNLSTM
- from model.TransformerEn import TransformerEncoderForecaster
- class SolorFeature:
- def __init__(self) -> None:
- self.features_dict = {
- 'v24': ['ssrd','tcc','lcc','mcc','hcc','2t','2d','10u','10v']
- }
- def get_feature(self,release):
- return self.features_dict.get(release)
- class ModelLoading:
- """
- ModelLoading类用于根据传入的模型名称和Optuna调参trial对象,自动配置并实例化深度学习模型(如DLinear、GRU、TSMixer(以及相关的超参数设置。
- 该类支持模型的加载训练调优模式和预测权重加载模式,便于模型选择、超参数搜索及后续的推理流程。
- """
- def __init__(self,
- trial,
- projs = 'OL_JIANGMEN',
- model_name = 'Dlinear',
- release = 'None',
- input_seq = 288,
- saved_model_path = 'None',
- predict_mode = False,
- capacity = '4050'
- ) -> None:
- """
- 参数说明:
- trial: Optuna的trial对象或占位符(None(,用以搜索和记录超参数组合。如在预测或确定性训练场景下可设为None。
- model_name: 选择要加载/调优的模型名称。支持'Dlinear', 'GRU', 'TSMixer'等(默认'Dlinear')。
- input_seq: 输入序列长度,通常为一天内的采样步数等,用于确定模型的输入和输出时序长度(默认288)。
- model_path: 预测模式下(指定要加载预训练权重的模型文件路径(默认'None')。
- predict_mode: 是否为预测模式(如果为True,则跳过超参数采样只加载已有权重的最佳模型(默认False)。
- """
-
- self.trial = trial
- self.projs = projs
- self.release = release
- self.features = SolorFeature().get_feature(self.release)
- self.input_seq = input_seq
- self.model_name = model_name
- self.saved_model_path = saved_model_path
- self.predict_mode = predict_mode
- self.capacity = capacity
-
- if self.release != 'None':
- os.makedirs(os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq), self.release), exist_ok=True)
- self.saved_model_root = os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq), self.release)
- else:
- os.makedirs(os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq)), exist_ok=True)
- self.saved_model_root = os.path.join(self.saved_model_path, self.projs, self.model_name, str(self.input_seq))
- # 训练模式下,进行超参数搜索
- if isinstance(self.trial, optuna.trial.Trial):
- if self.model_name == 'Dlinear':
- kernel_size = self.trial.suggest_categorical("kernel_size", [5, 9, 15, 25])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': DLinearModel(
- seq_len = self.input_seq,
- pred_len = self.input_seq,
- individual = True,
- channels = len(self.features),
- channels_outs = 1,
- kernel_size = kernel_size
- ),
- 'lr': lr,
- 'batch_size': batch_size
- }
- elif self.model_name == 'GRU':
- hidden_channels = self.trial.suggest_categorical("hidden_channels", [4,16,32,64,128,256,512])
- layers = self.trial.suggest_categorical("layers", [1,2,3,4,5,6,7,8,9,10])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': GRUModel(
- input_channels = len(self.features),
- output_channels = 1,
- input_len = self.input_seq,
- pred_len = self.input_seq,
- hidden_channels = hidden_channels,
- layers = layers
- ),
- 'lr': lr,
- 'batch_size': batch_size
- }
- elif self.model_name == 'TSMixer':
- num_blocks = self.trial.suggest_categorical("num_blocks", [2,4,6,8,10,12,14,16])
- dropout_rate = self.trial.suggest_categorical("dropout_rate", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
- ff_dim = self.trial.suggest_categorical("ff_dim", [8,16,32,64,128,256])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': TSMixer(
- sequence_length = self.input_seq,
- prediction_length = self.input_seq,
- input_channels = len(self.features),
- output_channels = 1,
- activation_fn = 'relu',
- num_blocks = num_blocks,
- dropout_rate = dropout_rate,
- ff_dim = ff_dim,
- normalize_before = True,
- norm_type = 'batch',
- ),
- 'lr': lr,
- 'batch_size':batch_size
- }
- elif self.model_name == 'TCN':
- num_channels_options = [
- (8, 1),
- (16, 8, 1),
- (32, 16, 8, 1),
- (64, 32, 16, 8, 1),
- (128, 64, 32, 16, 8, 1)
- ]
- num_channels = self.trial.suggest_categorical("num_channels", num_channels_options)
- kernel_size = self.trial.suggest_categorical("kernel_size", [2,4,6,8,10,12,14,16])
- dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': TemporalConvNet(
- num_inputs= len(self.features),
- num_channels= num_channels,
- kernel_size= kernel_size,
- dropout= dropout
- ),
- 'lr': lr,
- 'batch_size':batch_size
- }
- elif self.model_name == 'Mamba':
- d_model = self.trial.suggest_categorical("d_model", [4, 8, 16, 32, 64, 128, 256])
- d_conv = self.trial.suggest_categorical("d_conv", [4,6,8,10,12,14,16])
- n_layer = self.trial.suggest_categorical("n_layer", [1,2,3,4,5,6])
- d_state = self.trial.suggest_categorical("d_state", [2,4,8,16,32])
- expand = self.trial.suggest_categorical("expand", [1,2,3,4])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': Mamba(
- d_model = d_model,
- n_layer = n_layer,
- input_dim = len(self.features),
- output_dim = 1,
- d_state = d_state,
- expand = expand,
- dt_rank = 'auto',
- d_conv = d_conv
- ),
- 'lr': lr,
- 'batch_size':batch_size
- }
- elif self.model_name == 'CNNLSTM':
- cnn_channels = self.trial.suggest_categorical("cnn_channels", [16, 32, 64, 128])
- lstm_hidden = self.trial.suggest_categorical("lstm_hidden", [16, 32, 64, 128])
- lstm_layers = self.trial.suggest_categorical("lstm_layers", [1, 2, 3, 4])
- dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- self.model_config = {
- 'model': CNNLSTM(
- n_features = len(self.features),
- pred_len = self.input_seq,
- cnn_channels = cnn_channels,
- lstm_hidden = lstm_hidden,
- lstm_layers = lstm_layers,
- dropout = dropout
- ),
- 'lr': lr,
- 'batch_size': batch_size
- }
- elif self.model_name == 'TransformerEn':
- d_model = self.trial.suggest_categorical("d_model", [16, 32, 64, 128])
- n_heads = self.trial.suggest_categorical("n_heads", [1, 2, 4, 8])
- num_layers = self.trial.suggest_categorical("num_layers", [1, 2, 3, 4])
- dim_feedforward = self.trial.suggest_categorical("dim_feedforward", [16, 32, 64, 128])
- dropout = self.trial.suggest_categorical("dropout", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
- lr = self.trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)
- batch_size = self.trial.suggest_categorical("batch_size", [4, 8, 16, 32, 64, 128])
- pooling = self.trial.suggest_categorical("pooling", ["last", "mean"])
- self.model_config = {
- 'model': TransformerEncoderForecaster(
- n_features = len(self.features),
- pred_len = self.input_seq,
- d_model = d_model,
- n_heads = n_heads,
- num_layers = num_layers,
- dim_feedforward = dim_feedforward,
- dropout = dropout,
- pooling = pooling
- ),
- 'lr': lr,
- 'batch_size': batch_size
- }
- else:
- raise ValueError(f"Unsupported model_name: {self.model_name}")
- else:
- # 加载最优参数,只为当前模型加载对应参数
- # 获取最佳参数路径
- model_param_path = os.path.join(self.saved_model_root,'best_tpe_params.json')
- if os.path.exists(model_param_path):
- # 加载最佳参数
- with open(model_param_path, 'r', encoding='utf-8') as f:
- self.model_param_dict = json.load(f)
- best_params = self.model_param_dict['best_params']
- else:
- raise FileNotFoundError(f"Model parameter file not found:{self.projs}-{self.release}-{self.model_name}")
- if self.model_name == 'Dlinear':
- self.model_config = {
- 'model': DLinearModel(
- seq_len = self.input_seq,
- pred_len = self.input_seq,
- individual = True,
- channels = len(self.features),
- channels_outs = 1,
- kernel_size = best_params['kernel_size']
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'GRU':
- self.model_config = {
- 'model': GRUModel(
- input_channels = len(self.features),
- output_channels = 1,
- input_len = self.input_seq,
- pred_len = self.input_seq,
- hidden_channels = best_params['hidden_channels'],
- layers = best_params['layers']
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'TSMixer':
- self.model_config = {
- 'model': TSMixer(
- sequence_length = self.input_seq,
- prediction_length = self.input_seq,
- input_channels = len(self.features),
- output_channels = 1,
- activation_fn = 'relu',
- num_blocks = best_params['num_blocks'],
- dropout_rate = best_params['dropout_rate'],
- ff_dim = best_params['ff_dim'],
- normalize_before = True,
- norm_type = 'batch',
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'TCN':
- self.model_config = {
- 'model': TemporalConvNet(
- num_inputs= len(self.features),
- num_channels= best_params["num_channels"],
- kernel_size= best_params["kernel_size"],
- dropout= best_params["dropout"]
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'Mamba':
- self.model_config = {
- 'model': Mamba(
- d_model = best_params["d_model"],
- n_layer = best_params["n_layer"],
- input_dim = len(self.features),
- output_dim = 1,
- d_state = best_params["d_state"],
- expand = best_params["expand"],
- d_conv = best_params["d_conv"]
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'CNNLSTM':
- self.model_config = {
- 'model': CNNLSTM(
- n_features = len(self.features),
- pred_len = self.input_seq,
- cnn_channels = best_params['cnn_channels'],
- lstm_hidden = best_params['lstm_hidden'],
- lstm_layers = best_params['lstm_layers'],
- dropout = best_params['dropout']
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- elif self.model_name == 'TransformerEn':
- self.model_config = {
- 'model': TransformerEncoderForecaster(
- n_features = len(self.features),
- pred_len = self.input_seq,
- d_model = best_params['d_model'],
- n_heads = best_params['n_heads'],
- num_layers = best_params['num_layers'],
- dim_feedforward = best_params['dim_feedforward'],
- dropout = best_params['dropout'],
- pooling = best_params['pooling']
- ),
- 'lr': best_params['learning_rate'],
- 'batch_size': best_params['batch_size']
- }
- else:
- raise ValueError(f"Unsupported model_name: {self.model_name}")
-
- # 启动预测模式
- if self.predict_mode:
- print(f'{self.model_name}--进入预测模式!!!')
- # 判断当前是否有可用的GPU,如果有则使用cuda,否则使用cpu
- # 加载最佳模型
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- self.model = self.get_model()
- model_weigth_file = os.path.join(self.saved_model_root, 'best_model.pth')
- self.model.load_state_dict(torch.load(model_weigth_file, map_location=device))
- self.model.eval()
- # 加载归一化器
- scaler_path = os.path.join(self.saved_model_root, "scaler.pkl")
- if not os.path.exists(scaler_path):
- raise FileNotFoundError(f"Scaler file not found: {self.proj}-{self.model_name}-{self.pre_seq}-{self.release}")
- self.scaler = joblib.load(scaler_path)
- def get_model(self):
- return self.model_config.get('model')
- def get_lr(self):
- return self.model_config.get('lr')
-
- def get_batch_size(self):
- return self.model_config.get('batch_size')
- def predict(self, features_in):
- """
- 功能:
- 用于对输入的特征进行模型预测,输出预测结果。
- 参数:
- features_in: 输入特征数据,格式应为模型可接受的数据类型(如numpy数组、Tensor等),
- shape = (batch_size, seq_len, feature_dim)。
- 返回:
- 预测结果shape = (batch_size, seq_len, 1)。
- """
- if not self.predict_mode:
- raise RuntimeError("Model is not in prediction mode. Please set predict=True in ModelLoading to use predict().")
- else:
- x_scaled = self.scaler_fn(features_in)
- return self.inverse_transform(self.model(x_scaled),self.capacity)
- def inverse_transform(self, y, capacity):
- """
- 对模型输出进行反归一化,以恢复到原始的功率尺度。
- 参数:
- y: 模型输出,归一化后的数据(如Tensor或ndarray)。
- capacity: 电站容量(标量),用于恢复为实际功率。
- 返回:
- 反归一化后的功率预测值。
- """
- return y * capacity
-
- def scaler_fn(self, x):
- """
- 对输入特征 x 进行归一化处理。
- 参数:
- x: 可以为 torch.Tensor 或 numpy.ndarray 类型的输入特征,
- 通常形状为 (1, seq_len, feature_dim)。
- 返回:
- 归一化后的数据,返回torch.Tensor;
- """
- # 如果传入的是torch张量,则转为numpy数组进行归一化
- B, L, M = x.shape
- if isinstance(x, torch.Tensor):
- x_np = x.detach().cpu().numpy() # (B, L, M)
- x_np_flat = x_np.reshape(-1, M) # (B*L, M)
- x_scaled_flat = self.scaler.transform(x_np_flat) # (B*L, M)
- x_scaled = x_scaled_flat.reshape(B, L, M) # (B, L, M)
- return torch.from_numpy(x_scaled).to(x.device).type(x.dtype)
-
- else:
- x_flat = x.reshape(-1, M)
- x_scaled_flat = self.scaler.transform(x_flat)
- x_scaled = x_scaled_flat.reshape(B, L, M)
- return x_scaled
|