Informer代码

main_informer

from exp.exp_informer import Exp_Informer
# 将 Exp_Informer 从 exp.exp_informer 模块导入到当前命名空间
parser = argparse.ArgumentParser(description='[Informer] Long Sequences Forecasting')
#创建了一个 ArgumentParser 对象实例,并将其赋值给变量 parser
#使用 description 参数为这个命令行解析器设置了一个描述,
#即 '[Informer] Long Sequences Forecasting'。这个描述会在用户请求帮助(通过命令行输入 --help 或 -h)时显示给用户。
parser.add_argument('--model', type=str, required=False, default='informer',help='model of experiment, options: [informer, informerstack, informerlight(TBD)]')
#定义了一个命令行参数--model
#type=str:指定参数的类型为字符串(str)。
#required=False:指定这个参数不是必需的,即用户可以选择不提供这个参数。在argparse中,如果不明确指定required参数,它的默认值是False,所以这里其实可以省略不写。
#default='informer':如果用户在命令行中没有提供--model参数,则使用默认值'informer'。
#help:这是一个描述性字符串,当用户在命令行中使用--help选项时,会显示这个字符串作为--model参数的说明。
parser.add_argument('--data', type=str, required=False, default='ETTh1', help='data')
#type=str:指定参数的类型为字符串(str)。
#required=False:指定这个参数不是必需的,即用户可以选择不提供这个参数。由于required的默认值是False,所以这一行其实可以省略不写,但显式写出来可以增加代码的可读性。
#default='ETTh1':如果用户在命令行中没有提供--data参数,则使用默认值'ETTh1'
parser.add_argument('--root_path', type=str, default='./data/ETT/', help='root path of the data file')
#用于指定数据文件的根路径
#'--root_path':这是参数的名称,用户在命令行中通过--root_path来指定这个参数的值。
#type=str:指定参数的类型为字符串(str),这意味着用户应该提供一个字符串值作为这个参数的值。
#default='./data/ETT/':如果用户没有在命令行中提供--root_path参数的值,那么将使用默认值'./data/ETT/'
parser.add_argument('--data_path', type=str, default='ETTh1.csv', help='data file')    
#用于指定数据文件的路径和名称。
#'--data_path':这是参数的名称,用户在命令行中通过--data_path来指定这个参数的值。
#type=str:指定参数的类型为字符串(str),这意味着用户应该提供一个字符串值作为这个参数的值。
#default='ETTh1.csv':如果用户没有在命令行中提供--data_path参数的值,那么将使用默认值'ETTh1.csv'。
parser.add_argument('--features', type=str, default='M', help='forecasting task, options:[M, S, MS]; M:multivariate predict multivariate, S:univariate predict univariate, MS:multivariate predict univariate')
#用于指定预测任务的特性
#'--features':这是参数的名称,用户在命令行中通过--features来指定这个参数的值。
#type=str:指定参数的类型为字符串(str),这意味着用户应该提供一个字符串值作为这个参数的值。
#default='M':如果用户没有在命令行中提供--features参数的值,那么将使用默认值'M'。
#M(多变量预测多变量)、S(单变量预测单变量)和MS(多变量预测单变量)

存疑 OT

parser.add_argument('--target', type=str, default='OT', help='target feature in S or MS task')
#用于指定在单变量预测(S)或多变量预测单变量(MS)任务中作为目标的特征。
#default='OT':如果用户没有在命令行中提供--target参数的值,那么将使用默认值'OT'
parser.add_argument('--freq', type=str, default='h', help='freq for time features encoding, options:[s:secondly, t:minutely, h:hourly, d:daily, b:business days, w:weekly, m:monthly], you can also use more detailed freq like 15min or 3h')
#用于指定时间特征编码的频率
#default='h':如果用户没有在命令行中提供--freq参数的值,那么将使用默认值'h',代表小时(hourly)。
#help:这是参数的帮助信息,它描述了参数的作用和可用的选项。这里,它说明了时间特征编码频率的多个选项,包括秒(secondly)、分钟(minutely)、小时(hourly)、日(daily)、工作日(business days)、周(weekly)和月(monthly)。此外,用户还可以使用更详细的频率,如15min或3h。
parser.add_argument('--checkpoints', type=str, default='./checkpoints/', help='location of model checkpoints')
#用于指定模型检查点的存储位置
#default='./checkpoints/':如果用户没有在命令行中提供--checkpoints参数的值,那么将使用默认值'./checkpoints/',这是一个指向当前目录下名为checkpoints的子目录的路径。
parser.add_argument('--seq_len', type=int, default=96, help='input sequence length of Informer encoder')
#用于指定Informer编码器的输入序列长度
#default=96:如果用户没有在命令行中提供--seq_len参数的值,那么将使用默认值96。
parser.add_argument('--label_len', type=int, default=48, help='start token length of Informer decoder')
#用于指定Informer解码器的起始标记(start token)长度
#help:这是参数的帮助信息,它描述了参数的作用,即Informer解码器的起始标记长度。

Informer解码器输入的结构:

  1. 起始标记序列(Start Token Series, label_len):这部分是已知的序列标签,长度由label_len参数指定。在训练时,这些标签可能是真实数据的一部分,而在预测时,它们可能是基于历史数据生成的。
  2. 零填充序列(Zero Padding Series, pred_len):这部分是零填充的序列,长度由pred_len参数指定。在训练时,这个部分可能用于指定模型应该预测的时间范围。在预测时,这部分是模型试图预测的序列,但开始时填充为零,因为模型将基于起始标记序列和模型内部的状态来生成这些预测值。
parser.add_argument('--pred_len', type=int, default=24, help='prediction sequence length')
#用于指定Informer的预测序列长度。
#help:这是参数的帮助信息,它描述了参数的作用,即预测序列的长度
parser.add_argument('--enc_in', type=int, default=7, help='encoder input size')
#用于指定Informer编码器的输入大小。
parser.add_argument('--dec_in', type=int, default=7, help='decoder input size')
#用于指定Informer解码器(或任何其他解码器组件)的输入大小
parser.add_argument('--c_out', type=int, default=7, help='output size')
#用于指定模型(很可能是Informer或类似的序列模型)的输出大小。
parser.add_argument('--d_model', type=int, default=512, help='dimension of model')
#用于指定模型(如Informer、Transformer等)的维度大小。
parser.add_argument('--n_heads', type=int, default=8, help='num of heads')
#用于指定多头注意力机制(Multi-Head Attention)中“头”(head)的数量。
parser.add_argument('--e_layers', type=int, default=2, help='num of encoder layers')
#用于指定编码器(Encoder)中堆叠的层数
parser.add_argument('--d_layers', type=int, default=1, help='num of decoder layers')
#用于指定解码器(decoder)中层的数量。
parser.add_argument('--s_layers', type=str, default='3,2,1', help='num of stack encoder layers')
#用于指定堆叠编码器(stacked encoder)中每一层的层数。
#这里的“堆叠编码器”可能指的是多个编码器层(如Transformer编码器层)的堆叠组合,每层可以有不同的层数。
parser.add_argument('--d_ff', type=int, default=2048, help='dimension of fcn')
#用于指定一个全连接网络(fully-connected network,或称为前馈网络feed-forward network)的维度。
parser.add_argument('--factor', type=int, default=5, help='probsparse attn factor')
#用于指定一个与“probsparse attention”(概率稀疏注意力)相关的因子

存疑 padding含义

parser.add_argument('--padding', type=int, default=0, help='padding type')
#依赖于特定的上下文或库,其中整数被用作枚举或标记来表示不同的填充类型
parser.add_argument('--distil', action='store_false', help='whether to use distilling in encoder, using this argument means not using distilling', default=True)
#用于控制是否在编码器(encoder)中使用知识蒸馏(distilling)技术。
#action='store_false':这表示当用户在命令行中明确提供--distil参数时,实际上是将该参数的值设置为False。
#通常,如果我们使用action='store_true'(这是默认值),不提供参数时值为False,提供了参数时值为True。
#但在这里,我们使用action='store_false'来反转这个行为,因为描述中说明“使用这个参数意味着不使用蒸馏”。
parser.add_argument('--dropout', type=float, default=0.05, help='dropout')
#用于控制模型中的丢弃率(dropout rate)
#丢弃率是在训练神经网络时用于防止过拟合的一种技术,其中在每次前向传播时,神经网络中的某些节点(或连接)会被随机地“丢弃”或“关闭”
parser.add_argument('--attn', type=str, default='prob', help='attention used in encoder, options:[prob, full]')
#用于指定在编码器(encoder)中使用的注意力(attention)机制类型。
#'prob'指的是一种基于概率的注意力机制,
#'full'自注意力(self-attention)或某种类型的全局注意力(global attention),

存疑,三种编码特征分别是什么

parser.add_argument('--embed', type=str, default='timeF', help='time features encoding, options:[timeF, fixed, learned]')
#用于指定时间特征(time features)的编码方式
#'timeF':这可能表示使用某种基于时间的特征编码(如周期性编码、位置编码等),具体取决于模型的实现。
#'fixed':这可能表示使用固定的时间特征编码,这些编码可能是在模型训练之前就已经确定好的,不随训练过程而变化。
#'learned':这表示时间特征的编码将由模型自己学习得出,即在训练过程中作为模型参数的一部分进行学习和优化。
parser.add_argument('--activation', type=str, default='gelu',help='activation')
#用于指定神经网络中激活函数(activation function)的类型
#'gelu'是一个常见的激活函数,全称为Gaussian Error Linear Unit(高斯误差线性单元)。
#除了'gelu'之外,还有许多其他常见的激活函数,如'relu'(Rectified Linear Unit,修正线性单元)、'sigmoid'、'tanh'等。
parser.add_argument('--output_attention', action='store_true', help='whether to output attention in ecoder')
#用于控制是否在编码器(encoder)的输出中包含注意力(attention)信息。
#这个参数使用action='store_true',意味着该参数不需要一个额外的值来指定其状态,只需要在命令行中包含这个参数就会将其设置为True。
parser.add_argument('--do_predict', action='store_true', help='whether to predict unseen future data')
#用于控制是否对未见过的未来数据进行预测。
#这个参数使用action='store_true',意味着当这个参数在命令行中被指定时,其值会被自动设置为True,而不需要另外指定一个值。
parser.add_argument('--mix', action='store_false', help='use mix attention in generative decoder', default=True)
#用于控制是否在生成式解码器(generative decoder)中使用混合注意力(mix attention)。
#这个参数使用action='store_false',意味着当该参数在命令行中被指定时,它的值会被设置为False。如果不指定这个参数,那么它将使用默认值True
parser.add_argument('--cols', type=str, nargs='+', help='certain cols from the data files as the input features')
#允许用户指定从数据文件中选择的某些列作为输入特征。
#参数type=str表示该参数的值应该被解释为字符串,但因为我们还指定了nargs='+',所以用户可以提供多个列名,它们会被解析为一个字符串列表。
#'--cols':这是参数的名称,用户可以通过在命令行中包含--cols后跟一个或多个列名来指定参数。
#type=str:这表示--cols后面跟的每个值都应该是字符串类型。
#nargs='+':这表示--cols参数后面应该跟至少一个值(列名),并且这些值将被收集到一个列表中。例如,--cols col1 col2 col3会被解析为一个包含三个字符串'col1', 'col2', 和 'col3'的列表。
parser.add_argument('--num_workers', type=int, default=0, help='data loader num workers')
#用于指定数据加载器(data loader)在后台使用的工作进程数量。
#这个参数使用type=int来确保参数值是一个整数,并设置了默认值为0,意味着如果不指定该参数,
#数据加载器将不会使用额外的后台工作进程(即只在主进程中加载数据)。
parser.add_argument('--itr', type=int, default=2, help='experiments times')
#用于设置实验次数。
parser.add_argument('--train_epochs', type=int, default=6, help='train epochs')
#用于指定模型训练的轮数(epochs)。
parser.add_argument('--batch_size', type=int, default=32, help='batch size of train input data')
#用于指定训练过程中的配置选项。是用于设置训练轮数的。
parser.add_argument('--patience', type=int, default=3, help='early stopping patience')
#用于指定早停法(early stopping)的耐心值(patience)。
#早停法是一种在模型训练过程中用来防止过拟合的策略,其原理是监控模型在验证集上的性能,并在性能不再提升时提前终止训练。
#这意味着默认情况下,如果模型在连续3个epoch的验证集性能都没有提升,那么训练就会提前终止。
parser.add_argument('--learning_rate', type=float, default=0.0001, help='optimizer learning rate')
#用于指定优化器(optimizer)的学习率(learning rate)。学习率是深度学习中非常重要的一个超参数,它决定了模型在训练过程中参数更新的步长。
parser.add_argument('--des', type=str, default='test',help='exp description')
#用于指定实验的描述(description)。该参数接受一个字符串(str)类型的值,并有一个默认值'test'。
parser.add_argument('--loss', type=str, default='mse',help='loss function')
#用于指定损失函数(loss function)
parser.add_argument('--lradj', type=str, default='type1',help='adjust learning rate')
#用于指定如何调整学习率(learning rate adjustment)
parser.add_argument('--use_amp', action='store_true', help='use automatic mixed precision training', default=False)
#用于控制是否使用自动混合精度(Automatic Mixed Precision, AMP)训练。
#自动混合精度是一种优化技术,可以在不损失模型准确性的情况下提高训练速度和减少显存使用。
#default=False:如果用户在命令行中没有指定--use_amp参数,那么它将使用默认值False,即不使用自动混合精度训练。
parser.add_argument('--inverse', action='store_true', help='inverse output data', default=False)
#用于控制是否逆序(或反转)输出数据。action='store_true'意味着当这个参数在命令行中被指定时,其对应的值将被设置为True。
#如果没有在命令行中指定该参数,它将使用默认值False。
parser.add_argument('--use_gpu', type=bool, default=True, help='use gpu')
#用于控制是否使用GPU(图形处理器)来运行代码。
#这个参数接受一个布尔值(bool),并且有一个默认值True,意味着如果用户在命令行中没有明确指定,那么默认会尝试使用GPU。
parser.add_argument('--gpu', type=int, default=0, help='gpu')
#用于指定使用哪个GPU进行计算。这个参数接受一个整数(int)类型的值,表示GPU的索引,
#通常是从0开始的。如果没有在命令行中指定这个参数,那么它将使用默认值0,即第一个GPU(如果有多个GPU的话)。
parser.add_argument('--use_multi_gpu', action='store_true', help='use multiple gpus', default=False)
#用于控制是否使用多个GPU来运行代码。参数的类型被设置为action='store_true',
#这意味着当这个参数在命令行中被指定时,其对应的值将被设置为True。如果没有在命令行中指定该参数,它将使用默认值False,即不使用多个GPU。
parser.add_argument('--devices', type=str, default='0,1,2,3',help='device ids of multile gpus')
#它允许用户指定多个GPU的ID,用于在多个GPU上运行代码。这个参数接受一个字符串(str)类型的值,
#其中包含了多个GPU的ID,通常这些ID是以逗号分隔的。如果没有在命令行中指定这个参数,
#那么它将使用默认值'0,1,2,3',即默认尝试使用ID为0、1、2、3的GPU(如果它们存在的话)。
args = parser.parse_args()
#args 通常是一个由 parse_args() 方法返回的命名空间对象,该对象包含了从命令行解析出来的所有参数值。
#当你使用 parser.add_argument() 添加参数定义后,parse_args() 方法会解析命令行中提供的参数,并将它们作为属性存储在返回的 args 命名空间中。
args.use_gpu = True if torch.cuda.is_available() and args.use_gpu else False
#根据两个条件来设置 args.use_gpu 的值:
#torch.cuda.is_available():检查是否有可用的 CUDA GPU。
#args.use_gpu:假设这是从命令行参数中获取的,表示用户是否希望使用 GPU。
if args.use_gpu and args.use_multi_gpu:
	# 移除args.devices字符串中的空格(假设用户可能在输入设备ID时误加了空格)  
    args.devices = args.devices.replace(' ','')
    # 使用逗号作为分隔符,将args.devices字符串分割为一个设备ID列表  
    device_ids = args.devices.split(',')
    # 将设备ID列表中的每个字符串转换为整数,并存储在args.device_ids列表中  
    args.device_ids = [int(id_) for id_ in device_ids]
    # 取出args.device_ids列表中的第一个设备ID,并存储在args.gpu中(这通常是主GPU的ID)
    args.gpu = args.device_ids[0]

存疑 各个字母含义

data_parser = {
    'ETTh1':{'data':'ETTh1.csv','T':'OT','M':[7,7,7],'S':[1,1,1],'MS':[7,7,1]},
    'ETTh2':{'data':'ETTh2.csv','T':'OT','M':[7,7,7],'S':[1,1,1],'MS':[7,7,1]},
    'ETTm1':{'data':'ETTm1.csv','T':'OT','M':[7,7,7],'S':[1,1,1],'MS':[7,7,1]},
    'ETTm2':{'data':'ETTm2.csv','T':'OT','M':[7,7,7],'S':[1,1,1],'MS':[7,7,1]},
    'WTH':{'data':'WTH.csv','T':'WetBulbCelsius','M':[12,12,12],'S':[1,1,1],'MS':[12,12,1]},
    'ECL':{'data':'ECL.csv','T':'MT_320','M':[321,321,321],'S':[1,1,1],'MS':[321,321,1]},
    'Solar':{'data':'solar_AL.csv','T':'POWER_136','M':[137,137,137],'S':[1,1,1],'MS':[137,137,1]},
}
#'data': 这是一个字符串,指定了数据集的文件路径
#'T': 这也是一个字符串,可能表示目标变量(Target Variable)或主要关心的度量(Metric)的名称。
#'M': 这是一个整数列表,长度通常为3,可能表示输入数据的时间步长(Time Steps)或窗口大小(Window Size)的不同维度。
#'S': 这是一个整数列表,长度也为3,可能表示在每个时间步长(或窗口)上的采样间隔(Sampling Interval)。
#'MS': 这是一个整数列表,长度也为3,可能与 'M' 和 'S' 类似,但可能表示某种特定处理或组合后的时间步长和采样间隔。
## 检查命令行参数 args.data 是否在 data_parser 字典的键中 
if args.data in data_parser.keys():
	# 如果在,则获取与 args.data 对应的字典值,存储到 data_info 变量中 
    data_info = data_parser[args.data]
    # 从 data_info 字典中取出 'data' 键对应的值,作为数据文件路径,并赋值给 args.data_path  
    args.data_path = data_info['data']
    # 从 data_info 字典中取出 'T' 键对应的值,单变量预测(S)或多变量预测单变量(MS)任务中作为目标的特征。,并赋值给 args.target
    args.target = data_info['T']
    #data_info 字典中取出与 args.features 对应的值(这里假设是 'M', 'S', 或 'MS')  
    # 并将其解包为三个变量,分别赋值给 args.enc_in, args.dec_in, args.c_out 
    args.enc_in, args.dec_in, args.c_out = data_info[args.features]
# 将args.s_layers字符串中的空格替换为空(即删除空格),然后按照逗号分隔成列表  
# 接着,将列表中的每个元素(预期为字符串)转换为整数,并重新赋值给args.s_layers  
args.s_layers = [int(s_l) for s_l in args.s_layers.replace(' ','').split(',')]
# 将args.freq的值直接赋值给args.detail_freq,这可能用于存储原始的频率值供后续使用 
args.detail_freq = args.freq
# 取args.freq字符串的最后一个字符,并重新赋值给args.freq  
# 这可能用于简化或标准化频率的表示,例如从'H1'、'M15'等中提取'H'或'M'  
args.freq = args.freq[-1:]

print('Args in experiment:')
print(args)

存疑 EXP_Informer是什么

#将名为 Exp_Informer 的变量或对象的值赋给名为 Exp 的变量
Exp = Exp_Informer
# 遍历从0到args.itr-1的整数,args.itr代表实验迭代的次数 
for ii in range(args.itr):
    # setting record of experiments
    ## 创建一个字符串setting,该字符串用于记录或标识当前实验的设置  
    # 使用format方法将args中的多个参数组合成一个字符串  
    setting = '{}_{}_ft{}_sl{}_ll{}_pl{}_dm{}_nh{}_el{}_dl{}_df{}_at{}_fc{}_eb{}_dt{}_mx{}_{}_{}'.format(args.model, args.data, args.features, 
                args.seq_len, args.label_len, args.pred_len,
                args.d_model, args.n_heads, args.e_layers, args.d_layers, args.d_ff, args.attn, args.factor, 
                args.embed, args.distil, args.mix, args.des, ii)
	# 创建一个Exp对象,用于进行实验管理的类,args作为参数传递给这个对象  
    exp = Exp(args) # set experiments
    # 打印开始训练的消息,包含当前实验的设置信息 
    print('>>>>>>>start training : {}>>>>>>>>>>>>>>>>>>>>>>>>>>'.format(setting))
    # 调用Exp对象的train方法,开始训练,并传入设置信息
    exp.train(setting)
    # 打印开始测试的消息,包含当前实验的设置信息
    print('>>>>>>>testing : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
    # 调用Exp对象的test方法,进行测试,并传入设置信息 
    exp.test(setting)
	# 如果args.do_predict为True,则进行预测
    if args.do_predict:
        print('>>>>>>>predicting : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
        # 调用Exp对象的predict方法,进行预测,并传入设置信息和True
        exp.predict(setting, True)
	# 释放未使用的显存,以便其他GPU任务可以使用
    torch.cuda.empty_cache()

data_loader

import os
# 导入numpy库,一个用于数值计算的库,提供了多维数组对象、各种派生对象(如掩码数组和矩阵)以及用于数组快速操作的各种例行程序  
import numpy as np
# 导入pandas库,一个用于数据处理和分析的库,提供了数据框(DataFrame)和序列(Series)等数据结构,以及强大的数据分析和处理功能  
import pandas as pd

import torch
# 从torch.utils.data模块中导入Dataset和DataLoader类  
# Dataset是一个抽象类,表示一个数据集,需要用户自定义实现其__len__和__getitem__方法  
# DataLoader是一个用于加载数据的类,它结合了数据集和采样器,并提供了多线程、批处理、打乱数据等功能  
from torch.utils.data import Dataset, DataLoader
# from sklearn.preprocessing import StandardScaler


# 从自定义的utils.tools模块中导入StandardScaler类,用于数据标准化处理 
from utils.tools import StandardScaler
# 从自定义的utils.timefeatures模块中导入time_features函数或类  
# 这个函数或类可能用于生成与时间相关的特征,如日期、时间戳、星期几、月份等 
from utils.timefeatures import time_features

import warnings
warnings.filterwarnings('ignore')
class Dataset_ETT_hour(Dataset):
	## 初始化方法接收的第一个参数通常是self,它引用类的实例本身  
	#target参数指定了数据集中用于预测或回归的目标列 
	# scale参数可能是一个布尔值,用于指示是否需要对数据进行标准化处理 
	# inverse参数可能是一个布尔值,用于指示是否需要对已经标准化或变换的数据进行逆变换 
	# timeenc参数可能用于指定时间编码的方式或级别  
	# cols参数可能允许用户指定要加载或使用的特定列  
    def __init__(self, root_path, flag='train', size=None, 
                 features='S', data_path='ETTh1.csv', 
                 target='OT', scale=True, inverse=False, timeenc=0, freq='h', cols=None):
        # size [seq_len, label_len, pred_len]
        # info
        if size == None:
            self.seq_len = 24*4*4
            self.label_len = 24*4
            self.pred_len = 24*4
        else:
            self.seq_len = size[0]
            self.label_len = size[1]
            self.pred_len = size[2]
        # init
        assert flag in ['train', 'test', 'val']
        type_map = {'train':0, 'val':1, 'test':2}
        self.set_type = type_map[flag]
        
        self.features = features
        self.target = target
        self.scale = scale
        self.inverse = inverse
        self.timeenc = timeenc
        self.freq = freq
        
        self.root_path = root_path
        self.data_path = data_path
        self.__read_data__()

    def __read_data__(self):
    	# 创建一个StandardScaler对象,用于数据标准化
    	#StandardScaler是一个用于特征缩放的类
        self.scaler = StandardScaler()
        # 使用pandas的read_csv函数读取CSV文件,文件路径由root_path和data_path拼接而成
        df_raw = pd.read_csv(os.path.join(self.root_path,
                                          self.data_path))
		# 定义三个数据边界,可能是针对不同数据集(如训练集、验证集、测试集)的划分 
        border1s = [0, 12*30*24 - self.seq_len, 12*30*24+4*30*24 - self.seq_len]
        border2s = [12*30*24, 12*30*24+4*30*24, 12*30*24+8*30*24]
        # 根据set_type(可能是训练、验证或测试集的标识)选择数据边界 
        border1 = border1s[self.set_type]
        border2 = border2s[self.set_type]
        
        
        if self.features=='M' or self.features=='MS':
        	# 如果features是'M'或'MS',则选择除第一列外的所有列作为数据列
            cols_data = df_raw.columns[1:]
            df_data = df_raw[cols_data]
        elif self.features=='S':
        	# 如果features是'S',则只选择目标列作为数据列 
            df_data = df_raw[[self.target]]

        if self.scale:
        	# 如果scale参数为True,则对数据进行标准化处理  
        	# 使用训练集(假设是第一个区间)的数据来拟合scaler
            train_data = df_data[border1s[0]:border2s[0]]
            #fit() 会计算训练数据的均值和标准差,.values 提取了其底层数据(通常是一个 NumPy 数组)。
            self.scaler.fit(train_data.values)
            # 对整个数据集进行标准化变换  
            #.transform()是缩放器对象的一个方法,它使用在.fit()方法中学习的统计特性来转换(或缩放)新的数据。
            #data:这是一个新的变量,它现在包含了经过缩放器转换的df_data的数据。这些数据现在与原始训练数据具有相同的缩放比例,
            #将每个数据点减去均值,再除以标准差,从而得到标准化后的数据。
            data = self.scaler.transform(df_data.values)
        else:
        	# 如果不进行标准化,则直接使用原始数据  
            data = df_data.values
            
        # 从df_raw中选取'date'列,并截取从border1到border2(不包括border2)的行,将结果存储在df_stamp中  
        df_stamp = df_raw[['date']][border1:border2]
        # 将df_stamp中的'date'列转换为datetime类型,并重新赋值给'date'列 
        df_stamp['date'] = pd.to_datetime(df_stamp.date)
        # 调用time_features函数,对df_stamp进行处理,生成时间特征。参数包括df_stamp, 时间编码方式timeenc, 和时间频率freq  
		# 假设time_features函数返回一个包含时间特征的DataFrame,将其存储在data_stamp中
        data_stamp = time_features(df_stamp, timeenc=self.timeenc, freq=self.freq)

		# 从原始数据data中截取从border1到border2(不包括border2)的行,并存储在self.data_x中  
		# 注意:这里直接使用data而不是df_raw,可能是data包含了更多的特征列 
        self.data_x = data[border1:border2]
        if self.inverse:
        	# 如果是True,则将df_data中从border1到border2(不包括border2)的值的numpy数组存储在self.data_y中 
            self.data_y = df_data.values[border1:border2]
        else:
        	# 如果不是True(即self.inverse为False),则从原始数据data中截取从border1到border2(不包括border2)的行,并存储在self.data_y中  
   		 	# 注意:这里同样直接使用data而不是df_raw 
            self.data_y = data[border1:border2]
        # 将前面生成的时间特征DataFrame data_stamp存储在类的属性self.data_stamp中
        self.data_stamp = data_stamp
    
    # 这是一个特殊方法,用于支持索引操作,使得对象可以使用类似列表或数组的索引方式来访问数据 
    def __getitem__(self, index):
    	# 设定序列的起始位置 
        s_begin = index
        # 设定序列的结束位置,结束位置是起始位置加上预设的序列长度
        s_end = s_begin + self.seq_len
        # 计算标签序列的起始位置,从序列结束位置开始回溯标签长度 
        r_begin = s_end - self.label_len 
        # 计算标签序列的结束位置,包含预测长度和标签长度 
        r_end = r_begin + self.label_len + self.pred_len

		# 从数据集中获取序列数据 
        seq_x = self.data_x[s_begin:s_end]
        if self.inverse:
        	# 如果self.inverse为True,则将标签序列与预测值合并  
        	# 首先从数据集中获取标签部分的数据  label_part = self.data_x[r_begin:r_begin+self.label_len]
        	# 然后从数据集中获取预测部分的数据   pred_part = self.data_y[r_begin+self.label_len:r_end] 
        	# 使用numpy的concatenate方法将两者合并  seq_y = np.concatenate([label_part, pred_part], 0) 
            seq_y = np.concatenate([self.data_x[r_begin:r_begin+self.label_len], self.data_y[r_begin+self.label_len:r_end]], 0)
        else:
        	# 如果self.inverse为False,则直接从数据集中获取标签序列
            seq_y = self.data_y[r_begin:r_end]
        # 从数据集中获取与seq_x对应的时间戳或标记
        seq_x_mark = self.data_stamp[s_begin:s_end]
        # 从数据集中获取与seq_y对应的时间戳或标记 
        seq_y_mark = self.data_stamp[r_begin:r_end]

		# 返回序列数据、标签序列、序列数据的时间戳、标签序列的时间戳  
        return seq_x, seq_y, seq_x_mark, seq_y_mark
    
    def __len__(self):
    	#使用内置的len()函数来获取self.data_x的长度(即元素的数量)。  
    	# 这意味着self.data_x应该是一个列表、元组、字符串或其他可迭代对象。  
        return len(self.data_x) - self.seq_len- self.pred_len + 1

    def inverse_transform(self, data):
    	# 这通常在数据预处理或特征缩放后使用,以便将数据转换回其原始格式或范围。
    	# 使用self.scaler对象的inverse_transform方法来转换数据。  
    	# 这意味着self.scaler应该是一个实现了inverse_transform方法的对象,  
    	# 如sklearn库中的StandardScaler、MinMaxScaler等。  
    	# inverse_transform方法会将数据从缩放或转换后的状态转换回其原始状态。
        return self.scaler.inverse_transform(data)
class Dataset_ETT_minute(Dataset):
    def __init__(self, root_path, flag='train', size=None, 
                 features='S', data_path='ETTm1.csv', 
                 target='OT', scale=True, inverse=False, timeenc=0, freq='t', cols=None):
        # size [seq_len, label_len, pred_len]
        # info
        if size == None:
            self.seq_len = 24*4*4
            self.label_len = 24*4
            self.pred_len = 24*4
        else:
            self.seq_len = size[0]
            self.label_len = size[1]
            self.pred_len = size[2]
        # init
        assert flag in ['train', 'test', 'val']
        type_map = {'train':0, 'val':1, 'test':2}
        self.set_type = type_map[flag]
        
        self.features = features
        self.target = target
        self.scale = scale
        self.inverse = inverse
        self.timeenc = timeenc
        self.freq = freq
        
        self.root_path = root_path
        self.data_path = data_path
        self.__read_data__()

    def __read_data__(self):
        self.scaler = StandardScaler()
        df_raw = pd.read_csv(os.path.join(self.root_path,
                                          self.data_path))

        border1s = [0, 12*30*24*4 - self.seq_len, 12*30*24*4+4*30*24*4 - self.seq_len]
        border2s = [12*30*24*4, 12*30*24*4+4*30*24*4, 12*30*24*4+8*30*24*4]
        border1 = border1s[self.set_type]
        border2 = border2s[self.set_type]
        
        if self.features=='M' or self.features=='MS':
            cols_data = df_raw.columns[1:]
            df_data = df_raw[cols_data]
        elif self.features=='S':
            df_data = df_raw[[self.target]]

        if self.scale:
            train_data = df_data[border1s[0]:border2s[0]]
            self.scaler.fit(train_data.values)
            data = self.scaler.transform(df_data.values)
        else:
            data = df_data.values
            
        df_stamp = df_raw[['date']][border1:border2]
        df_stamp['date'] = pd.to_datetime(df_stamp.date)
        data_stamp = time_features(df_stamp, timeenc=self.timeenc, freq=self.freq)
        
        self.data_x = data[border1:border2]
        if self.inverse:
            self.data_y = df_data.values[border1:border2]
        else:
            self.data_y = data[border1:border2]
        self.data_stamp = data_stamp
    
    def __getitem__(self, index):
        s_begin = index
        s_end = s_begin + self.seq_len
        r_begin = s_end - self.label_len
        r_end = r_begin + self.label_len + self.pred_len

        seq_x = self.data_x[s_begin:s_end]
        if self.inverse:
            seq_y = np.concatenate([self.data_x[r_begin:r_begin+self.label_len], self.data_y[r_begin+self.label_len:r_end]], 0)
        else:
            seq_y = self.data_y[r_begin:r_end]
        seq_x_mark = self.data_stamp[s_begin:s_end]
        seq_y_mark = self.data_stamp[r_begin:r_end]

        return seq_x, seq_y, seq_x_mark, seq_y_mark
    
    def __len__(self):
        return len(self.data_x) - self.seq_len - self.pred_len + 1

    def inverse_transform(self, data):
        return self.scaler.inverse_transform(data)
class Dataset_Custom(Dataset):#自定义数据集
    def __init__(self, root_path, flag='train', size=None, 
                 features='S', data_path='ETTh1.csv', 
                 target='OT', scale=True, inverse=False, timeenc=0, freq='h', cols=None):
        # size [seq_len, label_len, pred_len]
        # info
        if size == None:
            self.seq_len = 24*4*4
            self.label_len = 24*4
            self.pred_len = 24*4
        else:
            self.seq_len = size[0]
            self.label_len = size[1]
            self.pred_len = size[2]
        # init
        assert flag in ['train', 'test', 'val']
        type_map = {'train':0, 'val':1, 'test':2}
        self.set_type = type_map[flag]
        
        self.features = features
        self.target = target
        self.scale = scale
        self.inverse = inverse
        self.timeenc = timeenc
        self.freq = freq
        self.cols=cols
        self.root_path = root_path
        self.data_path = data_path
        self.__read_data__()

    def __read_data__(self):
        self.scaler = StandardScaler()
        df_raw = pd.read_csv(os.path.join(self.root_path,
                                          self.data_path))
        '''
        df_raw.columns: ['date', ...(other features), target feature]
        '''
        # cols = list(df_raw.columns); 
         # 如果self.cols有值(即用户指定了需要使用的列),则使用这些列  
    	# 否则,使用所有列,但去掉'date'和目标特征列self.target  
        if self.cols:
            cols=self.cols.copy()# 复制用户指定的列名列表
            cols.remove(self.target)# 移除目标特征列  
        else:
        	# 获取所有列名  # 移除目标特征列 # 移除'date'列 
            cols = list(df_raw.columns); cols.remove(self.target); cols.remove('date')
        # 重新排列df_raw的列顺序,首先为'date',然后是用户指定的或除'date'和self.target外的其他列,最后为目标特征列self.target  
        df_raw = df_raw[['date']+cols+[self.target]]

		 # 计算训练集、测试集和验证集的大小  
    	# 这里假设数据集的70%用于训练,20%用于测试,剩下的10%用于验证
        num_train = int(len(df_raw)*0.7)
        num_test = int(len(df_raw)*0.2)
        num_vali = len(df_raw) - num_train - num_test
        
        border1s = [0, num_train-self.seq_len, len(df_raw)-num_test-self.seq_len]
        border2s = [num_train, num_train+num_vali, len(df_raw)]
        border1 = border1s[self.set_type]
        border2 = border2s[self.set_type]
        
        if self.features=='M' or self.features=='MS':
            cols_data = df_raw.columns[1:]
            df_data = df_raw[cols_data]
        elif self.features=='S':
            df_data = df_raw[[self.target]]

        if self.scale:
            train_data = df_data[border1s[0]:border2s[0]]
            self.scaler.fit(train_data.values)
            data = self.scaler.transform(df_data.values)
        else:
            data = df_data.values
            
        df_stamp = df_raw[['date']][border1:border2]
        df_stamp['date'] = pd.to_datetime(df_stamp.date)
        data_stamp = time_features(df_stamp, timeenc=self.timeenc, freq=self.freq)

        self.data_x = data[border1:border2]
        if self.inverse:
            self.data_y = df_data.values[border1:border2]
        else:
            self.data_y = data[border1:border2]
        self.data_stamp = data_stamp
    
    def __getitem__(self, index):
        s_begin = index
        s_end = s_begin + self.seq_len
        r_begin = s_end - self.label_len 
        r_end = r_begin + self.label_len + self.pred_len

        seq_x = self.data_x[s_begin:s_end]
        if self.inverse:
            seq_y = np.concatenate([self.data_x[r_begin:r_begin+self.label_len], self.data_y[r_begin+self.label_len:r_end]], 0)
        else:
            seq_y = self.data_y[r_begin:r_end]
        seq_x_mark = self.data_stamp[s_begin:s_end]
        seq_y_mark = self.data_stamp[r_begin:r_end]

        return seq_x, seq_y, seq_x_mark, seq_y_mark
    
    def __len__(self):
        return len(self.data_x) - self.seq_len- self.pred_len + 1

    def inverse_transform(self, data):
        return self.scaler.inverse_transform(data)
class Dataset_Pred(Dataset):#预测数据集
    def __init__(self, root_path, flag='pred', size=None, 
                 features='S', data_path='ETTh1.csv', 
                 target='OT', scale=True, inverse=False, timeenc=0, freq='15min', cols=None):
        # size [seq_len, label_len, pred_len]
        # info
        if size == None:
            self.seq_len = 24*4*4
            self.label_len = 24*4
            self.pred_len = 24*4
        else:
            self.seq_len = size[0]
            self.label_len = size[1]
            self.pred_len = size[2]
        # init
        assert flag in ['pred']
        
        self.features = features
        self.target = target
        self.scale = scale
        self.inverse = inverse
        self.timeenc = timeenc
        self.freq = freq
        self.cols=cols
        self.root_path = root_path
        self.data_path = data_path
        self.__read_data__()

    def __read_data__(self):
        self.scaler = StandardScaler()
        df_raw = pd.read_csv(os.path.join(self.root_path,
                                          self.data_path))
        '''
        df_raw.columns: ['date', ...(other features), target feature]
        '''
        if self.cols:
            cols=self.cols.copy()
            cols.remove(self.target)
        else:
            cols = list(df_raw.columns); cols.remove(self.target); cols.remove('date')
        df_raw = df_raw[['date']+cols+[self.target]]
        
        border1 = len(df_raw)-self.seq_len
        border2 = len(df_raw)
        
        if self.features=='M' or self.features=='MS':
            cols_data = df_raw.columns[1:]
            df_data = df_raw[cols_data]
        elif self.features=='S':
            df_data = df_raw[[self.target]]

        if self.scale:
            self.scaler.fit(df_data.values)
            data = self.scaler.transform(df_data.values)
        else:
            data = df_data.values
        
        # 从df_raw中提取'date'列,并根据之前定义的边界border1和border2来选取时间戳子集     
        tmp_stamp = df_raw[['date']][border1:border2]
        # 将tmp_stamp中的'date'列转换为pandas的datetime类型  
        tmp_stamp['date'] = pd.to_datetime(tmp_stamp.date)
        # 生成一个日期范围,从tmp_stamp中的最后一个日期开始,长度为self.pred_len+1(预测长度加1)  
		# freq参数指定了日期之间的间隔,如'D'代表天,'H'代表小时等  
        pred_dates = pd.date_range(tmp_stamp.date.values[-1], periods=self.pred_len+1, freq=self.freq)
        
        # 创建一个新的DataFrame df_stamp,只有'date'这一列 
        df_stamp = pd.DataFrame(columns = ['date'])
        # 将tmp_stamp中的日期值和pred_dates(除了第一个元素,因为它与tmp_stamp的最后一个日期是重复的)  
		# 合并起来,并赋值给df_stamp的'date'列    
        df_stamp.date = list(tmp_stamp.date.values) + list(pred_dates[1:])
        # 调用time_features函数,根据df_stamp中的日期生成时间特征,并将这些特征添加到df_stamp中  
		# 其中timeenc可能是一个时间编码的参数,freq[-1:]可能用于指定频率的最后一部分(虽然这里看起来有点冗余) 
        data_stamp = time_features(df_stamp, timeenc=self.timeenc, freq=self.freq[-1:])

        self.data_x = data[border1:border2]
        if self.inverse:
            self.data_y = df_data.values[border1:border2]
        else:
            self.data_y = data[border1:border2]
        self.data_stamp = data_stamp
    
    def __getitem__(self, index):
        s_begin = index
        s_end = s_begin + self.seq_len
        r_begin = s_end - self.label_len
        r_end = r_begin + self.label_len + self.pred_len

        seq_x = self.data_x[s_begin:s_end]
        if self.inverse:
            seq_y = self.data_x[r_begin:r_begin+self.label_len]
        else:
            seq_y = self.data_y[r_begin:r_begin+self.label_len]
        seq_x_mark = self.data_stamp[s_begin:s_end]
        seq_y_mark = self.data_stamp[r_begin:r_end]

        return seq_x, seq_y, seq_x_mark, seq_y_mark
    
    def __len__(self):
        return len(self.data_x) - self.seq_len + 1

    def inverse_transform(self, data):
        return self.scaler.inverse_transform(data)

exp_basic

import os
import torch
import numpy as np
#分别导入了三个常用的库:os、torch 和 numpy。
# 定义一个名为 Exp_Basic 的基类,用于提供深度学习实验的基础框架 
class Exp_Basic(object):
	# 初始化方法,当创建类的实例时会自动调用
    def __init__(self, args):
    	# 保存传入的参数
        self.args = args
        # 调用内部方法 _acquire_device 来获取设备(CPU 或 GPU)
        self.device = self._acquire_device()
        # 调用内部方法 _build_model 来构建模型,并将模型移动到指定的设备上  
        self.model = self._build_model().to(self.device)

	# 内部方法,用于构建模型  
    # 注意:这里使用了 raise NotImplementedError,意味着这个方法需要在子类中实现
    def _build_model(self):
    	# 如果没有在子类中实现这个方法,会抛出 NotImplementedError 异常  
        raise NotImplementedError
        # 这行代码实际上不会被执行,因为 raise 语句会中断函数执行 
        return None
    
    # 内部方法,用于获取设备(CPU 或 GPU)
    def _acquire_device(self):
    	# 如果参数中指定了使用 GPU 
        if self.args.use_gpu:
        	# 如果不是使用多 GPU 模式,则设置 CUDA_VISIBLE_DEVICES 环境变量为指定的 GPU  
        	#"CUDA_VISIBLE_DEVICES":这是一个特定的环境变量,用于告诉CUDA哪些GPU是可见的或可用的。例如,如果你设置CUDA_VISIBLE_DEVICES="0,1",那么CUDA只会看到并尝试使用GPU 0和GPU 1,即使你的机器上可能有更多的GPU。
			#str(self.args.gpu):这里假设self.args是一个包含实验配置的对象,其中gpu是一个属性,表示要使用的GPU的ID(通常是一个整数,如0、1、2等)。str()函数用于将整数转换为字符串,因为环境变量的值必须是字符串。
			#self.args.use_multi_gpu:这也是从self.args对象中获取的一个布尔值(True或False),表示是否使用多GPU。
			#self.args.devices:如果use_multi_gpu为True,则这个属性可能包含一个字符串,表示要使用的多个GPU的ID,如"0,1,2"。
			#条件表达式:str(self.args.gpu) if not self.args.use_multi_gpu else self.args.devices 是一个条件表达式(也称为三元运算符)。它的意思是:
			#如果not self.args.use_multi_gpu(即不使用多GPU)为真,则设置CUDA_VISIBLE_DEVICES为str(self.args.gpu)。
			#否则(即使用多GPU),则设置CUDA_VISIBLE_DEVICES为self.args.devices。
            os.environ["CUDA_VISIBLE_DEVICES"] = str(self.args.gpu) if not self.args.use_multi_gpu else self.args.devices
            # 创建一个指向指定 GPU 的设备对象 
            #使用字符串cuda:{}来创建一个 torch.device 对象,该对象指定了PyTorch张量应该分配在哪个GPU上。
            device = torch.device('cuda:{}'.format(self.args.gpu))
            # 打印使用的 GPU 信息  
            print('Use GPU: cuda:{}'.format(self.args.gpu))
        else:## 如果不使用 GPU,则使用 CPU 
            device = torch.device('cpu')
            print('Use CPU')
        # 返回设备对象 
        return device

	# 内部方法,用于获取数据(具体实现需要在子类中完成) 
    def _get_data(self):
        pass

	# 内部方法,用于验证模型(具体实现需要在子类中完成) 
    def vali(self):
        pass

	# 内部方法,用于训练模型(具体实现需要在子类中完成)
    def train(self):
        pass

	## 内部方法,用于测试模型(具体实现需要在子类中完成)
    def test(self):
        pass
    

exp_informer

# 从data.data_loader模块中导入四种数据集类:  
# Dataset_ETT_hour: 用于加载ETT(可能是某种时间序列数据集)的小时级数据  
# Dataset_ETT_minute: 用于加载ETT的分钟级数据  
# Dataset_Custom: 用于加载自定义数据集  
# Dataset_Pred: 可能是一个用于预测任务的数据集类  
from data.data_loader import Dataset_ETT_hour, Dataset_ETT_minute, Dataset_Custom, Dataset_Pred
# 从exp.exp_basic模块中导入Exp_Basic类,这个类可能包含了实验的基本设置或配置
from exp.exp_basic import Exp_Basic
# 从models.model模块中导入Informer和InformerStack模型类 
from models.model import Informer, InformerStack

# 从utils.tools模块中导入EarlyStopping和adjust_learning_rate函数  
# EarlyStopping是一个早停机制,用于在验证损失不再改善时提前停止训练  
# adjust_learning_rate是一个用于调整学习率的函数  
from utils.tools import EarlyStopping, adjust_learning_rate
# 从utils.metrics模块中导入metric函数或类,这个函数或类可能用于计算模型的评估指标  
from utils.metrics import metric

import numpy as np

import torch
# 导入torch.nn模块,这是PyTorch的神经网络库
import torch.nn as nn
# 从torch库中导入optim模块,这个模块包含了各种优化算法,如SGD、Adam等  
from torch import optim
# 从torch.utils.data模块中导入DataLoader类,这个类用于加载数据并进行批处理 
from torch.utils.data import DataLoader

import os
import time

# 导入warnings模块,并设置其filterwarnings方法以忽略所有警告  
# 这通常用于在开发过程中避免过多的警告信息干扰  
import warnings
warnings.filterwarnings('ignore')
class Exp_Informer(Exp_Basic):
    def __init__(self, args):
        super(Exp_Informer, self).__init__(args)
    
    def _build_model(self):
        model_dict = {
            'informer':Informer,
            'informerstack':InformerStack,
        }
        if self.args.model=='informer' or self.args.model=='informerstack':
            e_layers = self.args.e_layers if self.args.model=='informer' else self.args.s_layers
            model = model_dict[self.args.model](
                self.args.enc_in,
                self.args.dec_in, 
                self.args.c_out, 
                self.args.seq_len, 
                self.args.label_len,
                self.args.pred_len, 
                self.args.factor,
                self.args.d_model, 
                self.args.n_heads, 
                e_layers, # self.args.e_layers,
                self.args.d_layers, 
                self.args.d_ff,
                self.args.dropout, 
                self.args.attn,
                self.args.embed,
                self.args.freq,
                self.args.activation,
                self.args.output_attention,
                self.args.distil,
                self.args.mix,
                self.device
            ).float()
        
        if self.args.use_multi_gpu and self.args.use_gpu:
            model = nn.DataParallel(model, device_ids=self.args.device_ids)
        return model

    def _get_data(self, flag):
        args = self.args

        data_dict = {
            'ETTh1':Dataset_ETT_hour,
            'ETTh2':Dataset_ETT_hour,
            'ETTm1':Dataset_ETT_minute,
            'ETTm2':Dataset_ETT_minute,
            'WTH':Dataset_Custom,
            'ECL':Dataset_Custom,
            'Solar':Dataset_Custom,
            'custom':Dataset_Custom,
        }
        Data = data_dict[self.args.data]
        timeenc = 0 if args.embed!='timeF' else 1

        if flag == 'test':
            shuffle_flag = False; drop_last = True; batch_size = args.batch_size; freq=args.freq
        elif flag=='pred':
            shuffle_flag = False; drop_last = False; batch_size = 1; freq=args.detail_freq
            Data = Dataset_Pred
        else:
            shuffle_flag = True; drop_last = True; batch_size = args.batch_size; freq=args.freq
        data_set = Data(
            root_path=args.root_path,
            data_path=args.data_path,
            flag=flag,
            size=[args.seq_len, args.label_len, args.pred_len],
            features=args.features,
            target=args.target,
            inverse=args.inverse,
            timeenc=timeenc,
            freq=freq,
            cols=args.cols
        )
        print(flag, len(data_set))
        data_loader = DataLoader(
            data_set,
            batch_size=batch_size,
            shuffle=shuffle_flag,
            num_workers=args.num_workers,
            drop_last=drop_last)

        return data_set, data_loader

    def _select_optimizer(self):
        model_optim = optim.Adam(self.model.parameters(), lr=self.args.learning_rate)
        return model_optim
    
    def _select_criterion(self):
        criterion =  nn.MSELoss()
        return criterion

    def vali(self, vali_data, vali_loader, criterion):
        self.model.eval()
        total_loss = []
        for i, (batch_x,batch_y,batch_x_mark,batch_y_mark) in enumerate(vali_loader):
            pred, true = self._process_one_batch(
                vali_data, batch_x, batch_y, batch_x_mark, batch_y_mark)
            loss = criterion(pred.detach().cpu(), true.detach().cpu())
            total_loss.append(loss)
        total_loss = np.average(total_loss)
        self.model.train()
        return total_loss

    def train(self, setting):
        train_data, train_loader = self._get_data(flag = 'train')
        vali_data, vali_loader = self._get_data(flag = 'val')
        test_data, test_loader = self._get_data(flag = 'test')

        path = os.path.join(self.args.checkpoints, setting)
        if not os.path.exists(path):
            os.makedirs(path)

        time_now = time.time()
        
        train_steps = len(train_loader)
        early_stopping = EarlyStopping(patience=self.args.patience, verbose=True)
        
        model_optim = self._select_optimizer()
        criterion =  self._select_criterion()

        if self.args.use_amp:
            scaler = torch.cuda.amp.GradScaler()

        for epoch in range(self.args.train_epochs):
            iter_count = 0
            train_loss = []
            
            self.model.train()
            epoch_time = time.time()
            for i, (batch_x,batch_y,batch_x_mark,batch_y_mark) in enumerate(train_loader):
                iter_count += 1
                
                model_optim.zero_grad()
                pred, true = self._process_one_batch(
                    train_data, batch_x, batch_y, batch_x_mark, batch_y_mark)
                loss = criterion(pred, true)
                train_loss.append(loss.item())
                
                if (i+1) % 100==0:
                    print("\titers: {0}, epoch: {1} | loss: {2:.7f}".format(i + 1, epoch + 1, loss.item()))
                    speed = (time.time()-time_now)/iter_count
                    left_time = speed*((self.args.train_epochs - epoch)*train_steps - i)
                    print('\tspeed: {:.4f}s/iter; left time: {:.4f}s'.format(speed, left_time))
                    iter_count = 0
                    time_now = time.time()
                
                if self.args.use_amp:
                    scaler.scale(loss).backward()
                    scaler.step(model_optim)
                    scaler.update()
                else:
                    loss.backward()
                    model_optim.step()

            print("Epoch: {} cost time: {}".format(epoch+1, time.time()-epoch_time))
            train_loss = np.average(train_loss)
            vali_loss = self.vali(vali_data, vali_loader, criterion)
            test_loss = self.vali(test_data, test_loader, criterion)

            print("Epoch: {0}, Steps: {1} | Train Loss: {2:.7f} Vali Loss: {3:.7f} Test Loss: {4:.7f}".format(
                epoch + 1, train_steps, train_loss, vali_loss, test_loss))
            early_stopping(vali_loss, self.model, path)
            if early_stopping.early_stop:
                print("Early stopping")
                break

            adjust_learning_rate(model_optim, epoch+1, self.args)
            
        best_model_path = path+'/'+'checkpoint.pth'
        self.model.load_state_dict(torch.load(best_model_path))
        
        return self.model

    def test(self, setting):
        test_data, test_loader = self._get_data(flag='test')
        
        self.model.eval()
        
        preds = []
        trues = []
        
        for i, (batch_x,batch_y,batch_x_mark,batch_y_mark) in enumerate(test_loader):
            pred, true = self._process_one_batch(
                test_data, batch_x, batch_y, batch_x_mark, batch_y_mark)
            preds.append(pred.detach().cpu().numpy())
            trues.append(true.detach().cpu().numpy())

        preds = np.array(preds)
        trues = np.array(trues)
        print('test shape:', preds.shape, trues.shape)
        preds = preds.reshape(-1, preds.shape[-2], preds.shape[-1])
        trues = trues.reshape(-1, trues.shape[-2], trues.shape[-1])
        print('test shape:', preds.shape, trues.shape)

        # result save
        folder_path = './results/' + setting +'/'
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)

        mae, mse, rmse, mape, mspe = metric(preds, trues)
        print('mse:{}, mae:{}'.format(mse, mae))

        np.save(folder_path+'metrics.npy', np.array([mae, mse, rmse, mape, mspe]))
        np.save(folder_path+'pred.npy', preds)
        np.save(folder_path+'true.npy', trues)

        return

    def predict(self, setting, load=False):
        pred_data, pred_loader = self._get_data(flag='pred')
        
        if load:
            path = os.path.join(self.args.checkpoints, setting)
            best_model_path = path+'/'+'checkpoint.pth'
            self.model.load_state_dict(torch.load(best_model_path))

        self.model.eval()
        
        preds = []
        
        for i, (batch_x,batch_y,batch_x_mark,batch_y_mark) in enumerate(pred_loader):
            pred, true = self._process_one_batch(
                pred_data, batch_x, batch_y, batch_x_mark, batch_y_mark)
            preds.append(pred.detach().cpu().numpy())

        preds = np.array(preds)
        preds = preds.reshape(-1, preds.shape[-2], preds.shape[-1])
        
        # result save
        folder_path = './results/' + setting +'/'
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        
        np.save(folder_path+'real_prediction.npy', preds)
        
        return

    def _process_one_batch(self, dataset_object, batch_x, batch_y, batch_x_mark, batch_y_mark):
        batch_x = batch_x.float().to(self.device)
        batch_y = batch_y.float()

        batch_x_mark = batch_x_mark.float().to(self.device)
        batch_y_mark = batch_y_mark.float().to(self.device)

        # decoder input
        if self.args.padding==0:
            dec_inp = torch.zeros([batch_y.shape[0], self.args.pred_len, batch_y.shape[-1]]).float()
        elif self.args.padding==1:
            dec_inp = torch.ones([batch_y.shape[0], self.args.pred_len, batch_y.shape[-1]]).float()
        dec_inp = torch.cat([batch_y[:,:self.args.label_len,:], dec_inp], dim=1).float().to(self.device)
        # encoder - decoder
        if self.args.use_amp:
            with torch.cuda.amp.autocast():
                if self.args.output_attention:
                    outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
                else:
                    outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
        else:
            if self.args.output_attention:
                outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
            else:
                outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
        if self.args.inverse:
            outputs = dataset_object.inverse_transform(outputs)
        f_dim = -1 if self.args.features=='MS' else 0
        batch_y = batch_y[:,-self.args.pred_len:,f_dim:].to(self.device)

        return outputs, batch_y

masking

class TriangularCausalMask():
	# 初始化方法,用于创建三角形的因果掩码   
    def __init__(self, B, L, device="cpu"):
    	# 定义掩码的形状。B是批次大小,L是序列长度。掩码是一个四维张量,其中第二维是1(因为我们只对序列内部进行掩码)
        mask_shape = [B, 1, L, L]
        # 使用torch.no_grad()上下文管理器,确保我们不在计算梯度(因为我们只是创建掩码)
        with torch.no_grad():
        	# 使用torch.triu()函数创建一个上三角矩阵(包括对角线)。这里我们设置diagonal=1,意味着从对角线的下一个元素开始填充  
            # torch.ones(mask_shape, dtype=torch.bool)首先创建一个全为1的布尔类型张量,形状为mask_shape  
            # torch.triu()的结果是一个上三角矩阵,其中上三角部分(包括对角线)为True,下三角部分为False  
            # .to(device)将张量移动到指定的设备上(CPU或GPU)
            self._mask = torch.triu(torch.ones(mask_shape, dtype=torch.bool), diagonal=1).to(device)
	
	# 定义一个属性,用于获取掩码(只读)
    @property
    def mask(self):
    	# 返回之前创建的掩码  
        return self._mask
class ProbMask():
    def __init__(self, B, H, L, index, scores, device="cpu"):
    	# 初始化一个上三角矩阵,用于后续生成掩码。大小为L x scores.shape[-1](假设scores的最后一个维度是目标大小)  
        # triu(1)表示从对角线的上方开始都是True,其余为False  
        _mask = torch.ones(L, scores.shape[-1], dtype=torch.bool).to(device).triu(1)
        # 将_mask扩展到B x H x L x scores.shape[-1]的维度。None用于扩展维度 
        _mask_ex = _mask[None, None, :].expand(B, H, L, scores.shape[-1])
        #从一个多维张量 _mask_ex 中提取一个子张量,并将其移动到指定的设备(device)上。
        #[:, None, None] 是索引扩展操作,它将一维张量扩展为三维张量,其中两个额外的维度大小为1。这样做的目的是使其可以与_mask_ex的其他索引维度进行广播
        #[None, :, None] 同样是一个索引扩展操作,但这次它扩展了第一个和最后一个维度,而中间维度保持不变。
        # 根据传入的index生成一个指标矩阵。该指标矩阵将_mask_ex中的特定位置设为True,其余为False  
        # torch.arange(B)[:, None, None] 和 torch.arange(H)[None, :, None] 分别创建从0到B-1和0到H-1的索引张量  
        # index表示在第三维(即L维)上的特定索引位置  
        indicator = _mask_ex[torch.arange(B)[:, None, None],
                             torch.arange(H)[None, :, None],
                             index, :].to(device)
        # 将indicator重新塑形为scores的形状,并确保它在正确的设备上
        self._mask = indicator.view(scores.shape).to(device)
    
    
    # 定义一个属性mask,用于外部访问_mask  
    @property
    def mask(self):
        return self._mask

metrice

# 计算相对平方误差(RSE)
def RSE(pred, true):
	# 计算预测值与实际值之间的平方差的总和
	# 计算实际值与其均值之间的平方差的总和(即实际值的方差)
	# 返回RSE值,即平方误差的根与实际值方差的根的比值  
    return np.sqrt(np.sum((true-pred)**2)) / np.sqrt(np.sum((true-true.mean())**2))

## 计算相关系数(CORR)
def CORR(pred, true):
	# 计算预测值与实际值与其各自均值的差的乘积的和
    u = ((true-true.mean(0))*(pred-pred.mean(0))).sum(0) 
    # 计算预测值与实际值与其各自均值的差的平方的乘积的和,再开平方得到分母
    d = np.sqrt(((true-true.mean(0))**2*(pred-pred.mean(0))**2).sum(0))
    # 返回相关系数的平均值(这里假设是沿着最后一个维度计算平均值)
    return (u/d).mean(-1)

## 计算平均绝对误差(MAE) 
def MAE(pred, true):
	# 计算预测值与实际值之差的绝对值,然后取平均值
    return np.mean(np.abs(pred-true))

## 计算均方误差(MSE)
def MSE(pred, true):
	# 计算预测值与实际值之差的平方,然后取平均值 
    return np.mean((pred-true)**2)

## 计算均方根误差(RMSE)
def RMSE(pred, true):
	# 调用MSE函数并取其平方根 
    return np.sqrt(MSE(pred, true))

# 计算平均绝对百分比误差(MAPE) 
def MAPE(pred, true):
	# 计算预测值与实际值之差的绝对值与实际值的比值,然后取平均值  
    # 注意:这里没有对true==0的情况做特殊处理,实际使用时可能需要添加判断以避免除以零
    return np.mean(np.abs((pred - true) / true))


# 计算均方百分比误差(MSPE) 
def MSPE(pred, true):
	# 计算预测值与实际值之差的平方与实际值的比值,然后取平均值  
    # 同样,这里没有对true==0的情况做特殊处理  
    return np.mean(np.square((pred - true) / true))

# 定义一个函数metric,它接收预测值pred和实际值true作为参数  
def metric(pred, true):
    mae = MAE(pred, true)
    mse = MSE(pred, true)
    rmse = RMSE(pred, true)
    mape = MAPE(pred, true)
    mspe = MSPE(pred, true)
    
    return mae,mse,rmse,mape,mspe

timefeatures

# 导入typing模块中的List类型,用于类型注解,表示一个列表类型  
from typing import List

import numpy as np
import pandas as pd
# 从pandas.tseries模块中导入offsets模块,该模块包含日期偏移量的类  
# 这些偏移量常用于时间序列数据的日期计算,如“1天后”、“1月前”等 
# 4. 从pandas.tseries模块导入offsets模块:  
#    pandas的tseries模块提供了时间序列相关的功能,其中offsets模块包含了一系列日期偏移量类,  
#    如BusinessDay(工作日)、MonthEnd(月末)、WeekOfMonth(每月的第几周)等。  
#    这些偏移量类可以用于时间序列数据的日期计算,如计算某个日期加上一定偏移量后的新日期。
from pandas.tseries import offsets
# 从pandas.tseries.frequencies模块中导入to_offset函数  
# 该函数可以将字符串频率(如'D'表示天,'M'表示月等)转换为相应的日期偏移量对象  
# 这使得在创建时间序列时可以更灵活地指定日期间隔  
# 5. 从pandas.tseries.frequencies模块导入to_offset函数:  
#    pandas的tseries.frequencies模块包含与日期频率相关的功能。  
#    to_offset函数可以将字符串表示的日期频率(如'D'表示天,'M'表示月)转换为相应的日期偏移量对象。  
#    这在创建时间序列时非常有用,因为它允许我们灵活地指定时间序列中各个日期之间的间隔。
from pandas.tseries.frequencies import to_offset
# 定义一个名为 TimeFeature 的类
class TimeFeature:
    def __init__(self):
        pass
	# __call__ 方法,它允许类的实例像函数一样被调用  
    # 但目前此方法并未实现任何逻辑,只是简单地通过了 
    #接受一个 pd.DatetimeIndex 类型的参数 index,并返回一个 np.ndarray 类型的数组。
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        pass

	## __repr__ 方法,它返回一个对象的“官方”字符串表示形式  
    # 这里的实现是返回类的名称加上一对括号,以标识该对象是一个 TimeFeature 类的实例  
    def __repr__(self):
        return self.__class__.__name__ + "()"
#这个类用于将小时内的分钟数编码为[-0.5, 0.5]之间的值。
# 1. 定义一个名为SecondOfMinute的类,继承自TimeFeature。  
# 2. 在类定义上方添加文档字符串,描述这个类的用途和返回值的范围。  
# 3. 定义类的__call__方法,它允许这个类的对象被当作函数调用。  
# 4. __call__方法接受一个pandas的DatetimeIndex对象作为参数index。  
# 5. 在__call__方法内部,使用index.second获取时间戳的秒数部分。  
# 6. 通过将秒数除以59.0并减去0.5,将秒数转换为[-0.5, 0.5]之间的浮点数。  
# 7. 返回转换后的浮点数数组(np.ndarray)。
class SecondOfMinute(TimeFeature):
    """Minute of hour encoded as value between [-0.5, 0.5]"""
    # 使用index.second获取时间戳中的秒数(0-59之间的整数)
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return index.second / 59.0 - 0.5
class MinuteOfHour(TimeFeature):
    """Minute of hour encoded as value between [-0.5, 0.5]"""
    #这个类用于将小时内的分钟数编码为[-0.5, 0.5]之间的值。
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return index.minute / 59.0 - 0.5
class HourOfDay(TimeFeature):
    """Hour of day encoded as value between [-0.5, 0.5]"""
    #这个类用于将一天中的小时数编码为[-0.5, 0.5]之间的值。  
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return index.hour / 23.0 - 0.5
class DayOfWeek(TimeFeature):
    #Day of week encoded as value between [-0.5, 0.5]  
    #这个类用于将一周中的某天(星期几)编码为[-0.5, 0.5]之间的值。  
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return index.dayofweek / 6.0 - 0.5
class DayOfMonth(TimeFeature):
    """Day of month encoded as value between [-0.5, 0.5]"""
    #这个类用于将一个月中的天数编码为[-0.5, 0.5]之间的值。 
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return (index.day - 1) / 30.0 - 0.5
class DayOfYear(TimeFeature):
    """Day of year encoded as value between [-0.5, 0.5]"""
    #这个类用于将一年中的某一天编码为[-0.5, 0.5]之间的值。 
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return (index.dayofyear - 1) / 365.0 - 0.5
class MonthOfYear(TimeFeature):
    """Month of year encoded as value between [-0.5, 0.5]"""
    #将一年中的月份(1-12)编码为 [-0.5, 0.5] 之间的值
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return (index.month - 1) / 11.0 - 0.5
class WeekOfYear(TimeFeature):
    """Week of year encoded as value between [-0.5, 0.5]"""
    #将一年中的周数编码为 [-0.5, 0.5] 之间的值。
    def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
        return (index.week - 1) / 52.0 - 0.5
def time_features_from_frequency_str(freq_str: str) -> List[TimeFeature]:
    """  
    根据给定的频率字符串返回适当的时间特征列表。 
    参数:  
    freq_str -- 频率字符串,格式为 [倍数][粒度],如 "12H", "5min", "1D" 等。  
    返回:  
    List[TimeFeature] -- 与给定频率对应的时间特征列表。  
    """  
	# 定义一个字典,用于将不同的时间偏移量(offsets)映射到对应的时间特征类列表 
    features_by_offsets = {
    	# 每年结束时的偏移量对应的特征类列表(目前为空,因为可能不需要特别的年特征)
        offsets.YearEnd: [],
        # 每季度结束时的偏移量对应的特征类列表
        offsets.QuarterEnd: [MonthOfYear],
        # 每月结束时的偏移量对应的特征类列表 
        offsets.MonthEnd: [MonthOfYear],
        # 每周的偏移量对应的特征类列表
        offsets.Week: [DayOfMonth, WeekOfYear],
        # 每天的偏移量对应的特征类列表
        offsets.Day: [DayOfWeek, DayOfMonth, DayOfYear],
        # 工作日的偏移量对应的特征类列表 
        offsets.BusinessDay: [DayOfWeek, DayOfMonth, DayOfYear],
        # 每小时的偏移量对应的特征类列表  
        offsets.Hour: [HourOfDay, DayOfWeek, DayOfMonth, DayOfYear],
        # 每分钟的偏移量对应的特征类列表
        offsets.Minute: [MinuteOfHour,HourOfDay,DayOfWeek, DayOfMonth, DayOfYear],
        # 每秒的偏移量对应的特征类列表 
        offsets.Second: [ SecondOfMinute, MinuteOfHour,HourOfDay,DayOfWeek,DayOfMonth,DayOfYear],
    }
	
	# 使用 pandas 的 to_offset 函数将频率字符串转换为对应的偏移量对象 
    offset = to_offset(freq_str)

	# 遍历 features_by_offsets 字典,检查给定的偏移量对象是否与字典中的某个键匹配
    for offset_type, feature_classes in features_by_offsets.items():
        if isinstance(offset, offset_type):
        	# 如果匹配,则根据对应的特征类列表创建特征对象,并返回该列表
            return [cls() for cls in feature_classes]

	# 如果给定的频率字符串不受支持,则抛出运行时错误,并列出支持的频率 
    supported_freq_msg = f"""
    Unsupported frequency {freq_str}
    The following frequencies are supported:
        Y   - yearly
            alias: A
        M   - monthly
        W   - weekly
        D   - daily
        B   - business days
        H   - hourly
        T   - minutely
            alias: min
        S   - secondly
    """
    raise RuntimeError(supported_freq_msg)
def time_features(dates, timeenc=1, freq='h'):
	#根据给定的freq参数和时间编码timeenc,从日期数据框中提取时间特征。 
    """
    > `time_features` takes in a `dates` dataframe with a 'dates' column and extracts the date down to `freq` where freq can be any of the following if `timeenc` is 0: 
    > * m - [month]
    > * w - [month]
    > * d - [month, day, weekday]
    > * b - [month, day, weekday]
    > * h - [month, day, weekday, hour]
    > * t - [month, day, weekday, hour, *minute]
    > 
    > If `timeenc` is 1, a similar, but different list of `freq` values are supported (all encoded between [-0.5 and 0.5]): 
    > * Q - [month]
    > * M - [month]
    > * W - [Day of month, week of year]
    > * D - [Day of week, day of month, day of year]
    > * B - [Day of week, day of month, day of year]
    > * H - [Hour of day, day of week, day of month, day of year]
    > * T - [Minute of hour*, hour of day, day of week, day of month, day of year]
    > * S - [Second of minute, minute of hour, hour of day, day of week, day of month, day of year]

    *minute returns a number from 0-3 corresponding to the 15 minute period it falls into.
    """
    if timeenc==0:
        dates['month'] = dates.date.apply(lambda row:row.month,1)
        dates['day'] = dates.date.apply(lambda row:row.day,1)
        dates['weekday'] = dates.date.apply(lambda row:row.weekday(),1)
        dates['hour'] = dates.date.apply(lambda row:row.hour,1)
        # 提取分钟特征,但先将分钟转换为它所属的15分钟段的索引(0-3)
        dates['minute'] = dates.date.apply(lambda row:row.minute,1)
        dates['minute'] = dates.minute.map(lambda x:x//15)
        freq_map = {
            'y':[],'m':['month'],'w':['month'],'d':['month','day','weekday'],
            'b':['month','day','weekday'],'h':['month','day','weekday','hour'],
            't':['month','day','weekday','hour','minute'],
        }
        return dates[freq_map[freq.lower()]].values
    if timeenc==1:
    	# 将dates的'date'列转换为datetime对象  
        dates = pd.to_datetime(dates.date.values)
        # 调用time_features_from_frequency_str函数
        #numpy 的 vstack 函数用于垂直堆叠数组。
        # 使用列表推导式遍历每个特征提取函数,并将结果堆叠成矩阵形式  
        # 注意:这里的transpose(1,0)是为了将结果从行转换为列(假设这是为了与timeenc=0的结果格式保持一致)  
        #列表推导式遍历 time_features_from_frequency_str(freq) 返回的特征函数列表。
        #对于列表中的每个特征函数 feat,它都会调用该函数并传入 dates 作为参数
        #这将生成一个列表,其中每个元素都是特征函数在 dates 上应用后的结果
        """
        从频率字符串 freq 获取一系列特征函数。
		对每个日期在 dates 上应用这些特征函数。
		将结果垂直堆叠成一个二维数组。
		如果需要,将数组的维度重新排列,使得特征成为列,日期成为行。
        """
        return np.vstack([feat(dates) for feat in time_features_from_frequency_str(freq)]).transpose(1,0)

tools

# 这个函数用于调整优化器的学习率   
    #   optimizer: 优化器对象  
    #   epoch: 当前训练的轮次(epoch)  
    #   args: 一个包含各种超参数的对象(通常是一个argparse的命名空间或者类似对象)
def adjust_learning_rate(optimizer, epoch, args):
	#用于展示如何根据epoch来线性减少学习率
    # lr = args.learning_rate * (0.2 ** (epoch // 2))
    
    if args.lradj=='type1':
    	# 使用type1策略,学习率在每个epoch后都会减半(从epoch 1开始计算)  
        # 注意这里创建了一个字典,但实际上只使用了当前epoch对应的值,其他值并未使用
        lr_adjust = {epoch: args.learning_rate * (0.5 ** ((epoch-1) // 1))}
    elif args.lradj=='type2':
    	 # 使用type2策略,学习率在某些特定的epoch时进行调整
        lr_adjust = {
            2: 5e-5, 4: 1e-5, 6: 5e-6, 8: 1e-6, 
            10: 5e-7, 15: 1e-7, 20: 5e-8
        }
    # 如果当前epoch在lr_adjust的字典键中
    if epoch in lr_adjust.keys():
    	 # 取出对应的学习率  
        lr = lr_adjust[epoch]
        # 遍历优化器的所有参数组
        for param_group in optimizer.param_groups:
        	# 将当前参数组的学习率更新为新的学习率  
            param_group['lr'] = lr
        # 打印出更新后的学习率 
        print('Updating learning rate to {}'.format(lr))
class EarlyStopping:
    def __init__(self, patience=7, verbose=False, delta=0):
        self.patience = patience  # patience:在验证损失不再改善之前等待的epoch数 
        self.verbose = verbose  # verbose:是否打印信息  
        self.counter = 0  # counter:记录连续多少个epoch验证损失没有改善的计数器  
        self.best_score = None  # best_score:记录最好的验证得分(在这里是负的验证损失)
        self.early_stop = False  # early_stop:一个标志,表示是否应该停止训练  
        self.val_loss_min = np.Inf  # val_loss_min:记录最小的验证损失
        self.delta = delta  # delta:允许验证损失上升的最小阈值  

	# 调用方法,用于在每次验证后判断是否应该停止训练
    def __call__(self, val_loss, model, path):
    	# 计算当前得分,由于我们希望验证损失尽可能小,所以用负的损失作为得分  
        score = -val_loss
        # 如果还没有记录过最好的得分 
        if self.best_score is None:
            self.best_score = score
            # 保存当前的模型和损失
            self.save_checkpoint(val_loss, model, path)
         # 如果当前得分不如之前的最好得分,但是差距在允许的范围内 
        elif score < self.best_score + self.delta:
        	# 计数器加1 
            self.counter += 1
            # 打印计数器信息
            print(f'EarlyStopping counter: {self.counter} out of {self.patience}')
             # 如果计数器达到了设定的patience值,设置停止训练的标志
            if self.counter >= self.patience:
                self.early_stop = True
        else:
            self.best_score = score
            self.save_checkpoint(val_loss, model, path)
            self.counter = 0

	# 保存模型的方法  
    def save_checkpoint(self, val_loss, model, path):
    	# 如果设置了verbose,则打印信息 
        if self.verbose:
            print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}).  Saving model ...')
        # 保存模型的参数到指定的路径 
        torch.save(model.state_dict(), path+'/'+'checkpoint.pth')
        # 更新最小的验证损失
        self.val_loss_min = val_loss
class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    """
    dot.notation 访问字典属性的类  
    这是一个基于字典的类,但它允许我们使用点表示法来访问和设置属性。  
    这是通过使用 __getattr__, __setattr__ 和 __delattr__ 特殊方法实现的。 
    """
    # 使用 __getattr__ 方法,使得当我们尝试访问不存在的属性时,它会尝试从字典中获取该键的值。  
    # 例如,如果我们有一个 dotdict 对象 d,并且 d 中有一个键 'key',那么 d.key 将返回 d['key'] 的值。  
    __getattr__ = dict.get
    # 使用 __setattr__ 方法,使得当我们尝试设置一个属性时,它实际上是在字典中设置一个键-值对。  
    # 例如,如果我们有一个 dotdict 对象 d,并执行 d.new_key = 'value',则相当于在字典 d 中添加了键值对 {'new_key': 'value'}。  
    __setattr__ = dict.__setitem__
    # 使用 __delattr__ 方法,使得当我们尝试删除一个属性时,它实际上是从字典中删除一个键。  
    # 例如,如果我们有一个 dotdict 对象 d,并且 d 中有一个键 'key',那么执行 del d.key 将从字典 d 中删除键 'key'。 
    __delattr__ = dict.__delitem__
"""  
    一个用于数据标准化的类。  
    标准化是指将数据按照其均值和标准差进行缩放,使其具有0均值和1标准差。  
"""  
class StandardScaler():
    def __init__(self):
    	"""  
        初始化方法。  
        设置均值和标准差为默认值0和1,但通常会在fit方法中被实际数据更新。  
        """  
        self.mean = 0.
        self.std = 1.
    
    """  
        根据输入的数据计算均值和标准差。  
          
        参数:  
            data (array_like): 需要标准化的数据  
  
        返回值:  
            None (修改self.mean和self.std)  
    """  
    def fit(self, data):
        self.mean = data.mean(0)# 计算数据的均值(沿着第一个维度,通常是特征维度)
        self.std = data.std(0) # 计算数据的标准差(沿着第一个维度)

	"""  
        根据之前计算的均值和标准差对输入数据进行标准化。  
  
        参数:  
            data (array_like): 需要被标准化的数据  
  
        返回值:  
            array_like: 标准化后的数据  
    """
    def transform(self, data):
    	# 如果data是PyTorch张量,将其转换为与self.mean和self.std相同的类型并移动到相同的设备  
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        # 对数据进行标准化  
        return (data - mean) / std

	"""  
        根据之前计算的均值和标准差对输入数据进行逆标准化(返回原始尺度的数据)。  
  
        注意:如果输入数据的特征维度与self.mean或self.std不匹配,将只使用最后一个均值和标准差。  
  
        参数:  
            data (array_like): 需要被逆标准化的数据  
  
        返回值:  
            array_like: 逆标准化后的数据  
    """  
    def inverse_transform(self, data):
    	# 如果data是PyTorch张量,将其转换为与self.mean和self.std相同的类型并移动到相同的设备  
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        # 如果输入数据的特征维度与self.mean或self.std不匹配,则只使用最后一个均值和标准差 
        if data.shape[-1] != mean.shape[-1]:
            mean = mean[-1:]
            std = std[-1:]
        # 对数据进行逆标准化
        return (data * std) + mean

embed

f ( i ) = 1000 0 2 i d model 根据 a x = e x l n a 得出 1 f ( i ) = e − 2 i l n 100000 d m o d e l f(i)=10000^{\frac{2i}{d_{\text{model}}}}\\根据a^x=e^{xlna}得出\frac{1}{f(i)}=e^{\frac{-2iln_{100000}}{d_{model}}} f(i)=10000dmodel2i根据ax=exlna得出f(i)1=edmodel2iln100000

#位置编码,定义一个继承自nn.module的类
class PositionalEmbedding(nn.Module):
	#初始化,接收两个参数,d_model表示嵌入 维度,max_len表示最大长度
    def __init__(self, d_model, max_len=5000):
    	#调用父类的初始化方法
        super(PositionalEmbedding, self).__init__()
        # 计算位置编码,保存到log空间
         # 创建一个 max_len 行 d_model 列的零矩阵,并将其类型转换为 float
        pe = torch.zeros(max_len, d_model).float()
        #设置pe不需要梯度,即在训练过程中不会更新
        pe.require_grad = False

		# 创建一个从 0 到 max_len-1 的向量,并在第二维上增加一个维度
        position = torch.arange(0, max_len).float().unsqueeze(1)
        # 计算用于位置编码的除数项,并对其取指数
        div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()

        pe[:, 0::2] = torch.sin(position * div_term)# 将 pe 矩阵的偶数列填充为 sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)# 将 pe 矩阵的奇数列填充为 cos(position * div_term)

        pe = pe.unsqueeze(0) # 在第一维增加一个维度,使其适应 batch 的维度
        self.register_buffer('pe', pe)# 将 pe 注册为 buffer,使其在模型保存和加载时不会作为参数保存,但依然会被保存下来

    def forward(self, x):# 定义前向传播函数,接受输入 x
        return self.pe[:, :x.size(1)]# 返回与输入 x 的长度匹配的部分位置编码
#基于一维卷积的嵌入操作
class TokenEmbedding(nn.Module):
	# 初始化函数,c_in 是输入通道数,d_model 是输出维度  
    def __init__(self, c_in, d_model):
        super(TokenEmbedding, self).__init__()
        # 根据 PyTorch 版本设置 padding 值,从1.5.0版本开始使用 padding=1  
        # 如果版本小于1.5.0,则使用 padding=2  
        #Padding的主要目的是:保留边缘信息:在进行卷积操作时,卷积核在图像边缘处可能无法完全覆盖,导致输出图像尺寸缩小和边缘信息丢失
        padding = 1 if torch.__version__>='1.5.0' else 2
        # 创建一个一维卷积层,用于嵌入操作  
        #padding_mode='circular': 这指定了填充模式。在大多数情况下,填充模式是 'zeros',这意味着在输入数据的两侧添加零。
        #但是,在这里你选择了 'circular' 模式,这意味着填充是通过将输入数据视为循环的或周期性的来完成的。这意味着,如果你在数据的开始处填充,那么填充的值将来自数据的末尾;
        self.tokenConv = nn.Conv1d(in_channels=c_in, out_channels=d_model, 
                                    kernel_size=3, padding=padding, padding_mode='circular')
        # 对模型中的所有模块进行遍历 
        for m in self.modules():
        	# 如果模块是 nn.Conv1d 类型
            if isinstance(m, nn.Conv1d):
            	# 使用 Kaiming 初始化方法初始化卷积层的权重  
                # mode='fan_in' 表示按照输入通道数进行初始化  
                # nonlinearity='leaky_relu' 表示激活函数为 Leaky ReLU  
                nn.init.kaiming_normal_(m.weight,mode='fan_in',nonlinearity='leaky_relu')

    def forward(self, x):
    	# 对输入 x 进行维度变换,从 [batch_size, seq_len, c_in] 变为 [batch_size, c_in, seq_len]  
        # 这是因为 nn.Conv1d 的输入需要是 [batch_size, channels, seq_len] 的格式
        x = self.tokenConv(x.permute(0, 2, 1)).transpose(1,2)
        # 注意:self.tokenConv 的输出维度为 [batch_size, d_model, seq_len]  
        # 然后通过 .transpose(1,2) 再次变换回 [batch_size, seq_len, d_model] 的格式
        return x
#创建一个不可训练的嵌入层,其权重被预先计算并固定,通常用于实现位置编码(Positional Encoding)或某些特定任务的固定嵌入
class FixedEmbedding(nn.Module):
    def __init__(self, c_in, d_model):
        super(FixedEmbedding, self).__init__()
		
		# 创建一个全零的权重矩阵w,大小为(c_in, d_model),数据类型为float 
        w = torch.zeros(c_in, d_model).float()
        # 设置w的requires_grad属性为False,表示在训练过程中不更新这个权重  
        w.require_grad = False

		# 创建一个从0到c_in-1的一维张量,然后增加一个维度变为二维(相当于列向量)  
        position = torch.arange(0, c_in).float().unsqueeze(1)
        div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()

        w[:, 0::2] = torch.sin(position * div_term)
        w[:, 1::2] = torch.cos(position * div_term)

		 # 创建一个嵌入层,其权重初始化为上述计算的 w,并设置为不可训练
        self.emb = nn.Embedding(c_in, d_model)
        self.emb.weight = nn.Parameter(w, requires_grad=False)

    def forward(self, x):
    	# 在前向传播时,返回嵌入层的输出,并使用 detach 方法以确保不计算梯度
        return self.emb(x).detach()
#这个类用于处理时间数据,将时间数据(如月份、日期、星期、小时和分钟)嵌入到d_model维度的向量中。  
class TemporalEmbedding(nn.Module):
    def __init__(self, d_model, embed_type='fixed', freq='h'):
        super(TemporalEmbedding, self).__init__()

        minute_size = 4; hour_size = 24
        weekday_size = 7; day_size = 32; month_size = 13

		# 根据嵌入类型选择嵌入层 
		# 根据 embed_type 决定使用固定嵌入 (FixedEmbedding) 还是标准嵌入 (nn.Embedding)
        Embed = FixedEmbedding if embed_type=='fixed' else nn.Embedding
        if freq=='t'
        	 # 如果频率是 't' (表示分钟级别的嵌入),# 如果频率是't',则添加分钟嵌入层  
            self.minute_embed = Embed(minute_size, d_model)
        
         # 创建小时、星期、天和月嵌入层  
        self.hour_embed = Embed(hour_size, d_model)
        self.weekday_embed = Embed(weekday_size, d_model)
        self.day_embed = Embed(day_size, d_model)
        self.month_embed = Embed(month_size, d_model)
    
    def forward(self, x):
    	# 将输入x转换为长整型,因为嵌入层需要整数索引  
        x = x.long()
        
        # 根据是否存在 minute_embed 属性,计算 minute_x,否则设为 0
         # 注意:这里假设x的shape是[batch_size, seq_len, 5],其中最后一维代表[month, day, weekday, hour, minute]  
        minute_x = self.minute_embed(x[:,:,4]) if hasattr(self, 'minute_embed') else 0.
        # 提取小时、星期、天和月嵌入  
        hour_x = self.hour_embed(x[:,:,3])
        weekday_x = self.weekday_embed(x[:,:,2])
        day_x = self.day_embed(x[:,:,1])
        month_x = self.month_embed(x[:,:,0])
        
        return hour_x + weekday_x + day_x + month_x + minute_x
#将不同频率(粒度)的时间特征(如小时、分钟、天等)嵌入到一个更高维度的空间中,以便于后续的模型处理。
class TimeFeatureEmbedding(nn.Module):
    def __init__(self, d_model, embed_type='timeF', freq='h'):
        super(TimeFeatureEmbedding, self).__init__()
		
        freq_map = {'h':4, 't':5, 's':6, 'm':1, 'a':1, 'w':2, 'd':3, 'b':3}
        # 根据传入的freq参数,从freq_map中获取对应的输入维度  
        d_inp = freq_map[freq]
        # 使用nn.Linear定义一个线性层,用于将时间特征从d_inp维度映射到d_model维度  
        self.embed = nn.Linear(d_inp, d_model)
    
    def forward(self, x):
    	 # 定义前向传播方法,接受一个参数x,表示时间特征输入  
  
        # 使用之前定义的线性层self.embed对输入x进行变换,得到嵌入后的结果  
        # 这里假设x的形状是(batch_size, d_inp),经过nn.Linear后得到(batch_size, d_model) 
        return self.embed(x)
#用于对输入数据进行多种类型的嵌入(包括值嵌入、位置嵌入和时间嵌入),并将这些嵌入的结果相加,最后通过dropout层返回。
class DataEmbedding(nn.Module):
    def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
        super(DataEmbedding, self).__init__()
		
		# 创建一个值嵌入层,用于将输入数据x的值进行嵌入  
        # TokenEmbedding是一个自定义的嵌入层
        self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
        # 创建一个位置嵌入层,用于将输入数据x的位置信息进行嵌入  
        # PositionalEmbedding也是一个自定义的嵌入层
        self.position_embedding = PositionalEmbedding(d_model=d_model)
        # 根据embed_type参数选择时间嵌入层的类型  
        # 如果embed_type不是'timeF',则使用TemporalEmbedding;否则使用TimeFeatureEmbedding  
        self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type, freq=freq) if embed_type!='timeF' else TimeFeatureEmbedding(d_model=d_model, embed_type=embed_type, freq=freq)

		 # 创建一个dropout层,用于在训练时随机将输入张量中的一些元素置零
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x, x_mark):
        x = self.value_embedding(x) + self.position_embedding(x) + self.temporal_embedding(x_mark)
        # 将最终的嵌入表示通过dropout层,返回处理后的结果  
        return self.dropout(x)

a t t n attn attn

import torch
import torch.nn as nn
import torch.nn.functional as F

import numpy as np

from math import sqrt
from utils.masking import TriangularCausalMask, ProbMask
class FullAttention(nn.Module):
    def __init__(self, mask_flag=True, factor=5, scale=None, attention_dropout=0.1, output_attention=False):
        super(FullAttention, self).__init__()
        self.scale = scale # 用于缩放分数的因子,默认为None,后面会处理
        self.mask_flag = mask_flag #是否使用掩码
        self.output_attention = output_attention  # 是否输出注意力权重  
        self.dropout = nn.Dropout(attention_dropout)  # 定义dropout层  
        
    # 获取输入的维度
    def forward(self, queries, keys, values, attn_mask):
        B, L, H, E = queries.shape # 批量大小、查询长度、头数、嵌入维度 
        _, S, _, D = values.shape  # 假设S是值的长度(可能与L相同或不同),D是值的嵌入维度  
        scale = self.scale or 1./sqrt(E) # 如果没有提供scale,则使用1除以嵌入维度的平方根作为缩放因子 

		# 计算注意力分数,使用einsum进行高效的矩阵乘法  
        # "blhe,bshe->bhls" 表示第一个张量的blhe位置与第二个张量的bshe位置相乘,然后按照bhls的方式聚合  
        scores = torch.einsum("blhe,bshe->bhls", queries, keys)
        if self.mask_flag:
            if attn_mask is None:
            # 如果没有提供attn_mask,则生成一个三角因果掩码  
                attn_mask = TriangularCausalMask(B, L, device=queries.device)
			
			 # 将掩码位置的分数填充为-inf,这样在softmax时这些位置的值会接近0 
            scores.masked_fill_(attn_mask.mask, -np.inf)

		# 应用dropout和softmax得到注意力权重  
		#torch.softmax 函数用于将 scale * scores 张量中的值转换为概率分布。
		#具体来说,它沿着指定的维度(在这里是最后一个维度,由 dim=-1 指定)进行 softmax 操作。
        A = self.dropout(torch.softmax(scale * scores, dim=-1))
        # 使用注意力权重对值进行加权求和  
        # "bhls,bshd->blhd" 表示第一个张量的bhls位置与第二个张量的bshd位置相乘,然后按照blhd的方式聚合  
        V = torch.einsum("bhls,bshd->blhd", A, values)
        
        # 如果需要输出注意力权重 
        if self.output_attention:
            return (V.contiguous(), A)# 返回值和注意力权重  
        else:
            return (V.contiguous(), None) # 只返回值
#用于实现概率注意力机制 
class ProbAttention(nn.Module):
    def __init__(self, mask_flag=True, factor=5, scale=None, attention_dropout=0.1, output_attention=False):
        super(ProbAttention, self).__init__()
        self.factor = factor
        self.scale = scale
        self.mask_flag = mask_flag
        self.output_attention = output_attention
        # 初始化一个dropout层,用于在训练时随机丢弃一部分神经元的输出 
        self.dropout = nn.Dropout(attention_dropout)

    def _prob_QK(self, Q, K, sample_k, n_top): # n_top: c*ln(L_q)
        # Q [B, H, L, D]
        # Q, K分别为查询(Query)和键(Key),sample_k为要采样的键的数量,n_top为选择前n_top个查询  
        # 获取K的形状信息
        B, H, L_K, E = K.shape
        # 获取Q的形状信息 
        _, _, L_Q, _ = Q.shape

        # calculate the sampled Q_K
        # 扩展K的维度以便进行后续操作 
        K_expand = K.unsqueeze(-3).expand(B, H, L_Q, L_K, E)
        # 随机选择sample_k个键索引 ,生成一个形状为 (L_Q, sample_k) 的二维张量,其中的元素是从范围 [0, L_K) 内随机抽取的整数。
        index_sample = torch.randint(L_K, (L_Q, sample_k)) # real U = U_part(factor*ln(L_k))*L_q
        # 使用随机索引从K_expand中选择对应的值 
        """
        torch.arange(L_Q):这会生成一个从0到L_Q-1的一维整数张量,代表查询的索引。
		.unsqueeze(1):这个操作会在第二个维度(索引为1的维度)上增加一个大小为1的维度,从而将一维张量变为二维张量。例如,如果L_Q=3,torch.arange(L_Q)会生成[0, 1, 2],而.unsqueeze(1)会将其变为[[0], [1], [2]]。
		index_sample:这是一个形状为(L_Q, sample_k)的二维张量,包含了从L_K个选项中为每个查询随机抽取的样本索引。
		多维索引:现在,我们使用这些索引从K_expand中抽取元素。由于index_sample是一个二维张量,而							torch.arange(L_Q).unsqueeze(1)是一个二维的“查询索引”张量,所以我们可以将它们结合起来形成一个三维的索引张量。但是,由于K_expand可能是一个更高维的张量,我们还需要提供额外的冒号(:)来指定其他维度上的所有元素。
		结果:K_sample将是一个新的张量,其形状与K_expand除了第二和第三个维度以外的所有维度都相同,但在第二和第三个维度上,它将具有L_Q和sample_k的大小。具体来说,对于每个查询(由torch.arange(L_Q).unsqueeze(1)指定),我们都从K_expand中抽取了sample_k个样本(由index_sample指定)。
   		"""
        K_sample = K_expand[:, :, torch.arange(L_Q).unsqueeze(1), index_sample, :]
        # 计算Q和K_sample的点积 
        Q_K_sample = torch.matmul(Q.unsqueeze(-2), K_sample.transpose(-2, -1)).squeeze(-2)

		# 计算稀疏性度量,找到与查询最相关的键
        # find the Top_k query with sparisty measurement
        M = Q_K_sample.max(-1)[0] - torch.div(Q_K_sample.sum(-1), L_K)
        # 选择前n_top个具有最大稀疏性度量的查询 ,sorted=False意味着返回的索引不需要按值排序
        M_top = M.topk(n_top, sorted=False)[1]

		# 使用筛选后的Q计算Q_K  
        # use the reduced Q to calculate Q_K
        """
		1.Q 的形状:
		假设 Q 的形状是 (B, H, N, F),其中:
		B 是批次大小 (batch size)
		H 是查询的数量(可能代表注意力机制中的头数或查询序列的长度)
		N 是查询中每个元素的嵌入大小或键/值的数量
		F 是特征维度
		2.torch.arange(B)[:, None, None]:
		这会生成一个形状为 (B, 1, 1) 的张量,其中包含从 0 到 B-1 的整数。[:, None, None] 是在第一个维度后添加额外的维度,以便与 Q 的其他维度兼容。
		3.torch.arange(H)[None, :, None]:
		这会生成一个形状为 (1, H, 1) 的张量,其中包含从 0 到 H-1 的整数。同样,[None, :, None] 是在第二个维度前和后添加额外的维度。
		4.M_top:
		如之前所述,M_top 的形状是 (B, H, n_top),它包含了 M(通常是注意力分数)中每个 (batch, query) 组合中最大的 n_top 个元素的索引。
		5.高级索引:
		通过组合这三个索引张量,我们可以从 Q 中选择特定的元素。具体来说,对于每个 (batch, head, top_index) 组合,我们都在 Q 的 (batch, head, M_top[batch, head, top_index], :) 位置选取一个元素。
		结果:
		Q_reduce 的形状将是 (B, H, n_top, F)。它包含了 Q 中与 M_top 索引对应的元素。
		"""
        Q_reduce = Q[torch.arange(B)[:, None, None],
                     torch.arange(H)[None, :, None],
                     M_top, :] # factor*ln(L_q)
       	# 计算Q_reduce和K的点积
        Q_K = torch.matmul(Q_reduce, K.transpose(-2, -1)) # factor*ln(L_q)*L_k

        return Q_K, M_top

    def _get_initial_context(self, V, L_Q):
    	# V为值(Value),L_Q为查询的长度 
    	# 获取值V的形状信息
        B, H, L_V, D = V.shape
         # 如果没有设置mask_flag(即不使用mask)
        if not self.mask_flag:
            # V_sum = V.sum(dim=-2)
            # 计算V在所有时间步的平均值(这里注释掉了sum,使用了mean) 
            V_sum = V.mean(dim=-2)
            # 将平均值扩展到与L_Q相同长度的维度上 
            contex = V_sum.unsqueeze(-2).expand(B, H, L_Q, V_sum.shape[-1]).clone()
        else: # use mask
        	# 断言L_Q和L_V必须相等,这是自注意力机制的要求  
            assert(L_Q == L_V) # requires that L_Q == L_V, i.e. for self-attention only
            # 计算V的累积和,作为初始上下文   原始上下文向量或输入序列的表示
            contex = V.cumsum(dim=-2)
        return contex

    def _update_context(self, context_in, V, scores, index, L_Q, attn_mask):
    	# 获取值V的形状信息
        B, H, L_V, D = V.shape

        if self.mask_flag:
        	# 假设ProbMask是一个自定义函数或类,用于根据index和scores生成掩码 
            attn_mask = ProbMask(B, H, L_Q, index, scores, device=V.device)
            # 将掩码对应的scores值替换为-inf,这样在softmax中会被忽略 
            scores.masked_fill_(attn_mask.mask, -np.inf)

		# 对scores进行softmax操作,得到注意力权重
        attn = torch.softmax(scores, dim=-1) # nn.Softmax(dim=-1)(scores)

		# 使用注意力权重和V更新context_in的对应位置  
        context_in[torch.arange(B)[:, None, None],
                   torch.arange(H)[None, :, None],
                   index, :] = torch.matmul(attn, V).type_as(context_in)
        # 如果设置了输出注意力的标志 
        if self.output_attention:
        	 # 初始化一个与attn形状相同的张量,并将其值设为1/L_V  
            attns = (torch.ones([B, H, L_V, L_V])/L_V).type_as(attn).to(attn.device)
            # 将选中的注意力权重赋值到attns的对应位置
            attns[torch.arange(B)[:, None, None], torch.arange(H)[None, :, None], index, :] = attn
            return (context_in, attns)
        else:# 返回更新后的context
            return (context_in, None)
	#forward方法定义了模型的前向传播过程,即输入数据如何通过网络进行计算并得到输出的过程。
    def forward(self, queries, keys, values, attn_mask):
        B, L_Q, H, D = queries.shape
        _, L_K, _, _ = keys.shape

		 # 将查询、键和值的维度进行转置,以适应后续的注意力机制计算  
        queries = queries.transpose(2,1)
        keys = keys.transpose(2,1)
        values = values.transpose(2,1)

		 # 计算U_part和u,用于后续的稀疏性选择 
        U_part = self.factor * np.ceil(np.log(L_K)).astype('int').item() # c*ln(L_k)
        u = self.factor * np.ceil(np.log(L_Q)).astype('int').item() # c*ln(L_q) 

		# 确保U_part和u的值不超过L_K和L_Q 
        U_part = U_part if U_part<L_K else L_K
        u = u if u<L_Q else L_Q
        
        # 调用_prob_QK方法,获取稀疏性选择后的Q_K分数和索引 
        scores_top, index = self._prob_QK(queries, keys, sample_k=U_part, n_top=u) 

        # add scale factor
        # 如果设置了scale,则对分数进行缩放  
        scale = self.scale or 1./sqrt(D)
        if scale is not None:
            scores_top = scores_top * scale
        # 如果设置了scale,则对分数进行缩放  
        context = self._get_initial_context(values, L_Q)
        # 调用_update_context方法,使用选定的查询更新上下文  
        context, attn = self._update_context(context, values, scores_top, index, L_Q, attn_mask)
         # 返回更新后的上下文和注意力权重(如果有设置输出注意力) 
         # 注意这里对context进行了转置,以匹配原始输入的维度顺序
        return context.transpose(2,1).contiguous(), attn
class AttentionLayer(nn.Module):
    def __init__(self, attention, d_model, n_heads, 
                 d_keys=None, d_values=None, mix=False):
        super(AttentionLayer, self).__init__()

		# 如果d_keys和d_values未给出,则默认为d_model除以n_heads  
        d_keys = d_keys or (d_model//n_heads)
        d_values = d_values or (d_model//n_heads)

		# 保存传入的attention模块(可能是Scaled Dot-Product Attention或其他)
        self.inner_attention = attention
         #定义线性层以将输入投影到查询、键和值的空间  
        self.query_projection = nn.Linear(d_model, d_keys * n_heads)
        self.key_projection = nn.Linear(d_model, d_keys * n_heads)
        self.value_projection = nn.Linear(d_model, d_values * n_heads)
        # 定义线性层以将多头注意力的输出投影回原始维度  
        self.out_projection = nn.Linear(d_values * n_heads, d_model)
        self.n_heads = n_heads
         # 是否混合键和值(这里的具体用途取决于attention模块的实现) 
        self.mix = mix

    def forward(self, queries, keys, values, attn_mask):
    	# 获取查询、键和值的形状信息 
        B, L, _ = queries.shape
        _, S, _ = keys.shape
        H = self.n_heads

		# 对查询、键和值进行线性投影,并改变它们的形状以适应多头注意力机制
        queries = self.query_projection(queries).view(B, L, H, -1)
        keys = self.key_projection(keys).view(B, S, H, -1)
        values = self.value_projection(values).view(B, S, H, -1)

		# 调用内部的注意力模块并获取输出和注意力权重 
        out, attn = self.inner_attention(
            queries,
            keys,
            values,
            attn_mask
        )
        if self.mix:
            out = out.transpose(2,1).contiguous()#交换第二个和第三个维度
        out = out.view(B, L, -1)# 将多头注意力的输出改变形状并准备进行最后的投影

		# 将输出投影回原始维度  
        return self.out_projection(out), attn

e n c o d e r encoder encoder

# 定义一个名为ConvLayer的类,继承自PyTorch的nn.Module,用于构建卷积层
class ConvLayer(nn.Module):
	# 初始化函数,接收输入通道数c_in作为参数  
    def __init__(self, c_in):
        super(ConvLayer, self).__init__()
        # 根据PyTorch的版本决定padding的大小。从1.5.0版本开始,padding默认为1,否则为2  
        # 注意:这里直接比较字符串可能不是最佳实践,但为了简单起见,这里这样做  
        #padding填充
        padding = 1 if torch.__version__>='1.5.0' else 2
        # 定义一个一维卷积层  
        # 输入通道数为c_in,输出通道数也为c_in(通道数保持不变)  
        # 卷积核大小为3  
        # padding大小为之前计算得到的padding  
        # 使用'circular'模式进行padding(这要求PyTorch版本支持)  
        self.downConv = nn.Conv1d(in_channels=c_in,
                                  out_channels=c_in,
                                  kernel_size=3,
                                  padding=padding,
                                  padding_mode='circular')
        # 定义一个一维批量归一化层  
        self.norm = nn.BatchNorm1d(c_in)
        # 定义一个激活函数层,使用ELU作为激活函数  
        self.activation = nn.ELU()
        # 定义一个一维最大池化层  
        # 池化核大小为3  
        # 步长为2,意味着每次池化后,特征图的长度减半  
        # padding为1,保证池化后输出尺寸的计算保持一定  
        self.maxPool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1)

    def forward(self, x):
    	# 首先将输入的维度从[batch_size, seq_len, c_in]转换为[batch_size, c_in, seq_len]  
        # 这是因为PyTorch的Conv1d期望输入维度是[batch_size, channels, length]  
        x = self.downConv(x.permute(0, 2, 1))
        # 对卷积后的输出进行批量归一化  
        x = self.norm(x)
        # 应用ELU激活函数  
        x = self.activation(x)
        # 对激活后的输出进行最大池化 
        x = self.maxPool(x)
        # 将池化后的输出维度从[batch_size, c_in, seq_len']转换回[batch_size, seq_len', c_in]  
        # 以匹配原始输入的维度顺序(除了长度可能不同)  
        x = x.transpose(1,2)
        return x
#这个EncoderLayer类定义了一个编码器层的结构,它结合了注意力机制(例如自注意力)和两点式的前馈神经网络(Feed-Forward Network, FFN)
class EncoderLayer(nn.Module):
    def __init__(self, attention, d_model, d_ff=None, dropout=0.1, activation="relu"):
        super(EncoderLayer, self).__init__()
        # 如果未提供 d_ff,则将其设置为 d_model 的 4 倍 
        #d_ff 前馈神经网络的内部维度
        d_ff = d_ff or 4*d_model
        # 保存传入的 attention 对象(可能是一个 MultiHeadAttention 实例)  
        self.attention = attention
        # 定义第一个卷积层,用于增加维度  
        self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
        # 定义第二个卷积层,用于减少维度回到 d_model  
        self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
        # 定义两个 LayerNorm 层,用于正则化
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
         # 定义 Dropout 层
        self.dropout = nn.Dropout(dropout)
        # 根据传入的 activation 参数选择激活函数  
        self.activation = F.relu if activation == "relu" else F.gelu

	
    def forward(self, x, attn_mask=None):
        # x [B, L, D]
        # x 的形状是 [B, L, D],其中 B 是批次大小,L 是序列长度,D 是模型维度 
        # x = x + self.dropout(self.attention(
        #     x, x, x,
        #     attn_mask = attn_mask
        # ))

		# 使用注意力机制(可能是 self-attention),并接收掩码(如果提供)
        new_x, attn = self.attention(
            x, x, x,
            attn_mask = attn_mask
        )
        # 残差连接:将原始输入 x 与经过注意力的输出 new_x 相加,并通过 Dropout  
        x = x + self.dropout(new_x)

		 # 应用第一个 LayerNorm 
        y = x = self.norm1(x)
        # 将 x 的最后一个维度(即 D)与第一个维度(即 B)交换,以适应 Conv1d 的输入要求  
        # 因为 Conv1d 期望输入的形状是 [B, C, L],其中 C 是通道数,L 是序列长度  
        y = self.dropout(self.activation(self.conv1(y.transpose(-1,1))))
        # 将 Conv1d 的输出再次交换维度,以适应下一个 Conv1d 的输入要求  
        y = self.dropout(self.conv2(y).transpose(-1,1))

		# 残差连接:将原始经过 LayerNorm 的 x 与经过两个卷积层的输出 y 相加  
        # 并通过第二个 LayerNorm 
        return self.norm2(x+y), attn
#这个Encoder类定义了一个具有注意力机制和(可选的)卷积层的编码器结构。它还允许(可选的)在最后一层之后应用归一化。
class Encoder(nn.Module):
    def __init__(self, attn_layers, conv_layers=None, norm_layer=None):
        super(Encoder, self).__init__()
        # 使用nn.ModuleList来包装attn_layers,这样PyTorch可以正确处理其中的模块(例如,将其移动到GPU等)  
        self.attn_layers = nn.ModuleList(attn_layers)
        # 如果conv_layers不为None,则使用nn.ModuleList来包装它;否则,将其设置为None 
        self.conv_layers = nn.ModuleList(conv_layers) if conv_layers is not None else None
        # 保存归一化层,如果提供了的话  
        self.norm = norm_layer

	# 定义前向传播函数,它描述了数据通过网络时如何被处理 
    def forward(self, x, attn_mask=None):
        # x [B, L, D]
        # 初始化一个空列表,用于存储每次注意力操作的结果(注意力权重)  
        attns = []
         # 如果conv_layers存在(即,用户提供了卷积层) 
        if self.conv_layers is not None:
        	# 遍历注意力层和卷积层(它们是成对出现的)
            for attn_layer, conv_layer in zip(self.attn_layers, self.conv_layers):
            	# 将x和attn_mask传递给当前的注意力层,并获取输出x和注意力权重attn  
                x, attn = attn_layer(x, attn_mask=attn_mask)
                # 将x传递给当前的卷积层,得到新的x  
                x = conv_layer(x)
                 # 将注意力权重attn添加到attns列表中  
                attns.append(attn)
            # 对最后一个注意力层(没有卷积层跟随)进行特殊处理,因为上面的循环不会处理它 
            x, attn = self.attn_layers[-1](x, attn_mask=attn_mask)
            attns.append(attn)
        else:
        	# 遍历注意力层 
            for attn_layer in self.attn_layers:
            	# 将x和attn_mask传递给当前的注意力层,并获取输出x和注意力权重attn  
                x, attn = attn_layer(x, attn_mask=attn_mask)
                attns.append(attn)

		# 如果提供了归一化层,则将x传递给归一化层  
        if self.norm is not None:
            x = self.norm(x)

		# 返回处理后的x和所有注意力权重attns的列表 
        return x, attns
# 将多个编码器(encoders)按照特定的方式堆叠起来,并对输入数据 x 进行处理。
class EncoderStack(nn.Module):
	# 定义一个编码器堆栈类,它接受一个编码器列表和一个输入长度的列表 
    def __init__(self, encoders, inp_lens):
        super(EncoderStack, self).__init__()
        # 使用nn.ModuleList来包装编码器列表,以便能够处理包含子模块的列表 
        self.encoders = nn.ModuleList(encoders)
        # 保存输入长度的列表  
        self.inp_lens = inp_lens

    def forward(self, x, attn_mask=None):
    # 定义前向传播方法,它接受输入x和可选的注意力掩码attn_mask  
        # x 的形状是 [B, L, D],其中B是批次大小,L是序列长度,D是特征维度 
        # x [B, L, D]
        x_stack = []; attns = [] # 初始化两个空列表来保存编码器的输出和注意力 

		# 遍历输入长度的列表和编码器列表  
        for i_len, encoder in zip(self.inp_lens, self.encoders):
        	#计算当前编码器应该处理的x的子序列长度  
            # 注意这里假设x的长度是2的幂次递增的,例如:[L, 2L, 4L, ...]  
            inp_len = x.shape[1]//(2**i_len)
            
            # 提取x的子序列作为当前编码器的输入,并获取其输出和注意力  
            # 注意这里使用了x[:, -inp_len:, :]来从x的末尾提取子序列  
            x_s, attn = encoder(x[:, -inp_len:, :])

			# 将当前编码器的输出和注意力分别添加到x_stack和attns列表中
            x_stack.append(x_s); attns.append(attn)

		# 沿着最后一个维度(特征维度)连接所有的编码器输出  
        x_stack = torch.cat(x_stack, -2)
        
        # 返回连接后的编码器输出和所有的注意力列表  
        return x_stack, attns

d e c o d e r decoder decoder

#完成的是解码器中的一个层(layer)的操作,它通常用于序列到序列(sequence-to-sequence)模型中的解码器部分,特别是在Transformer架构中
class DecoderLayer(nn.Module):
    def __init__(self, self_attention, cross_attention, d_model, d_ff=None,
                 dropout=0.1, activation="relu"):
        super(DecoderLayer, self).__init__()
        # 如果d_ff没有指定,则默认为d_model的4倍  
        d_ff = d_ff or 4*d_model
        # 存储传入的self_attention和cross_attention模块  
        self.self_attention = self_attention
        self.cross_attention = cross_attention

		 # 第一个全连接层(或称为前馈神经网络的第一部分),用于增加维度  
        self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
        # 第二个全连接层(或称为前馈神经网络的第二部分),用于还原维度  
        self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
         # 归一化层,用于稳定训练过程 
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        # Dropout层,用于防止过拟合 
        self.dropout = nn.Dropout(dropout)
        # 根据传入的activation参数选择激活函数  
        self.activation = F.relu if activation == "relu" else F.gelu

    def forward(self, x, cross, x_mask=None, cross_mask=None):
    	# 自注意力机制,并添加残差连接  
        x = x + self.dropout(self.self_attention(
            x, x, x, # query, key, value都是x 
            attn_mask=x_mask
        )[0])
        # 归一化 
        x = self.norm1(x)

		# 交叉注意力机制,并添加残差连接 
        x = x + self.dropout(self.cross_attention(
            x, cross, cross,# query是x,key和value都是cross 
            attn_mask=cross_mask
        )[0])

		# 复制x到y,因为接下来要对y进行前馈神经网络的变换  
        y = x = self.norm2(x)
         # 通过第一个全连接层、激活函数和Dropout  
        y = self.dropout(self.activation(self.conv1(y.transpose(-1,1))))
        # 通过第二个全连接层、转置和Dropout  
        y = self.dropout(self.conv2(y).transpose(-1,1))

		# 残差连接和最后的归一化  
        return self.norm3(x+y)
class Decoder(nn.Module):
    def __init__(self, layers, norm_layer=None):
        super(Decoder, self).__init__()
         # 使用nn.ModuleList来封装DecoderLayer的列表,这样PyTorch就能正确地处理它们  
        # 这样做的好处是PyTorch能够正确地注册这些层,并在前向传播时自动处理它们 
        self.layers = nn.ModuleList(layers)
         # 如果有提供norm_layer,则存储它。这个归一化层可以在所有DecoderLayer之后应用
        self.norm = norm_layer

    def forward(self, x, cross, x_mask=None, cross_mask=None):
    	 # 遍历Decoder中的每一层 
        for layer in self.layers:
        	# 将当前层的输入x、交叉注意力输入cross、以及可能的掩码传递给当前层  
            # 并将该层的输出作为下一层的输入  
            x = layer(x, cross, x_mask=x_mask, cross_mask=cross_mask)

        if self.norm is not None:
        # 如果在初始化时提供了norm_layer,则在所有层之后应用归一化 
            x = self.norm(x)

        return x

m o d e l model model

class Informer(nn.Module):
    def __init__(self, enc_in, dec_in, c_out, seq_len, label_len, out_len, 
                factor=5, d_model=512, n_heads=8, e_layers=3, d_layers=2, d_ff=512, 
                dropout=0.0, attn='prob', embed='fixed', freq='h', activation='gelu', 
                output_attention = False, distil=True, mix=True,
                device=torch.device('cuda:0')):
        super(Informer, self).__init__()
        self.pred_len = out_len  # 预测输出的长度
        self.attn = attn  # 注意力机制类型('prob' 或 'full')
        self.output_attention = output_attention  # 是否输出注意力权重

        # Encoding
        # 初始化编码器的嵌入层
        self.enc_embedding = DataEmbedding(enc_in, d_model, embed, freq, dropout)
        # 初始化解码器的嵌入层
        self.dec_embedding = DataEmbedding(dec_in, d_model, embed, freq, dropout)
        # Attention
        # 根据选择的注意力机制类型,初始化注意力层
        Attn = ProbAttention if attn=='prob' else FullAttention
        # Encoder
         # 初始化编码器,包含多个编码器层和可选的卷积层
        self.encoder = Encoder(
            [
                EncoderLayer(
                    AttentionLayer(Attn(False, factor, attention_dropout=dropout, output_attention=output_attention),
                                d_model, n_heads, mix=False),# 初始化编码器层中的注意力层
                    d_model, # 设置编码器层的维度
                    d_ff, # 设置前馈网络的维度
                    dropout=dropout, # 设置Dropout率
                    activation=activation # 设置激活函数
                ) for l in range(e_layers)  # 根据编码器层的数量,生成多个编码器层
            ],
            [
                ConvLayer(
                    d_model
                ) for l in range(e_layers-1)  # 初始化卷积层,用于Distil机制
            ] if distil else None,  #如果distil为True,则添加卷积层,否则不添加
            norm_layer=torch.nn.LayerNorm(d_model)# 添加LayerNorm归一化层
        )
        # Decoder
        # 初始化解码器,包含多个解码器层
        self.decoder = Decoder(
            [
                DecoderLayer(
                    AttentionLayer(Attn(True, factor, attention_dropout=dropout, output_attention=False), 
                                d_model, n_heads, mix=mix), # 初始化解码器层中的自注意力层
                    AttentionLayer(FullAttention(False, factor, attention_dropout=dropout, output_attention=False), 
                                d_model, n_heads, mix=False), # 初始化解码器层中的交叉注意力层
                    d_model,# 设置解码器层的维度
                    d_ff,# 设置前馈网络的维度
                    dropout=dropout, # 设置Dropout率
                    activation=activation,# 设置激活函数
                )
                for l in range(d_layers) # 根据解码器层的数量,生成多个解码器层
            ],
            norm_layer=torch.nn.LayerNorm(d_model)# 添加LayerNorm归一化层
        )
        # self.end_conv1 = nn.Conv1d(in_channels=label_len+out_len, out_channels=out_len, kernel_size=1, bias=True)
        # self.end_conv2 = nn.Conv1d(in_channels=d_model, out_channels=c_out, kernel_size=1, bias=True)
        # 初始化投影层,将解码器的输出映射到所需的输出维度
        self.projection = nn.Linear(d_model, c_out, bias=True)
        
    def forward(self, x_enc, x_mark_enc, x_dec, x_mark_dec, 
                enc_self_mask=None, dec_self_mask=None, dec_enc_mask=None):
         # 对编码器输入进行嵌入
        enc_out = self.enc_embedding(x_enc, x_mark_enc)
         # 通过编码器处理输入
        enc_out, attns = self.encoder(enc_out, attn_mask=enc_self_mask)

		# 对解码器输入进行嵌入
        dec_out = self.dec_embedding(x_dec, x_mark_dec)
        # 通过解码器处理嵌入输出
        dec_out = self.decoder(dec_out, enc_out, x_mask=dec_self_mask, cross_mask=dec_enc_mask)
        # 投影到输出维度
        dec_out = self.projection(dec_out)
        
        # dec_out = self.end_conv1(dec_out)
        # dec_out = self.end_conv2(dec_out.transpose(2,1)).transpose(1,2)
        # 如果需要输出注意力权重,则返回输出和注意力权重
        if self.output_attention:
            return dec_out[:,-self.pred_len:,:], attns
        else:
            return dec_out[:,-self.pred_len:,:] # [B, L, D]

I n f o r m e r Informer Informer 类使用固定层数的编码器。
I n f o r m e r S t a c k InformerStack InformerStack 类使用可变层数的编码器堆栈,每个堆栈可以有不同的层数。

class InformerStack(nn.Module):
    def __init__(self, enc_in, dec_in, c_out, seq_len, label_len, out_len, 
                factor=5, d_model=512, n_heads=8, e_layers=[3,2,1], d_layers=2, d_ff=512, 
                dropout=0.0, attn='prob', embed='fixed', freq='h', activation='gelu',
                output_attention = False, distil=True, mix=True,
                device=torch.device('cuda:0')):
        super(InformerStack, self).__init__()
        self.pred_len = out_len
        self.attn = attn
        self.output_attention = output_attention

        # Encoding
        self.enc_embedding = DataEmbedding(enc_in, d_model, embed, freq, dropout)
        self.dec_embedding = DataEmbedding(dec_in, d_model, embed, freq, dropout)
        # Attention
        Attn = ProbAttention if attn=='prob' else FullAttention
        # Encoder

        inp_lens = list(range(len(e_layers))) # [0,1,2,...] you can customize here
        encoders = [
            Encoder(
                [
                    EncoderLayer(
                        AttentionLayer(Attn(False, factor, attention_dropout=dropout, output_attention=output_attention), 
                                    d_model, n_heads, mix=False),
                        d_model,
                        d_ff,
                        dropout=dropout,
                        activation=activation
                    ) for l in range(el)
                ],
                [
                    ConvLayer(
                        d_model
                    ) for l in range(el-1)
                ] if distil else None,
                norm_layer=torch.nn.LayerNorm(d_model)
            ) for el in e_layers]
        # 将多个编码器层堆叠成一个编码器堆栈
        self.encoder = EncoderStack(encoders, inp_lens)
        # Decoder
        self.decoder = Decoder(
            [
                DecoderLayer(
                    AttentionLayer(Attn(True, factor, attention_dropout=dropout, output_attention=False), 
                                d_model, n_heads, mix=mix),
                    AttentionLayer(FullAttention(False, factor, attention_dropout=dropout, output_attention=False), 
                                d_model, n_heads, mix=False),
                    d_model,
                    d_ff,
                    dropout=dropout,
                    activation=activation,
                )
                for l in range(d_layers)
            ],
            norm_layer=torch.nn.LayerNorm(d_model)
        )
        # self.end_conv1 = nn.Conv1d(in_channels=label_len+out_len, out_channels=out_len, kernel_size=1, bias=True)
        # self.end_conv2 = nn.Conv1d(in_channels=d_model, out_channels=c_out, kernel_size=1, bias=True)
        self.projection = nn.Linear(d_model, c_out, bias=True)
        
    def forward(self, x_enc, x_mark_enc, x_dec, x_mark_dec, 
                enc_self_mask=None, dec_self_mask=None, dec_enc_mask=None):
        enc_out = self.enc_embedding(x_enc, x_mark_enc)
        enc_out, attns = self.encoder(enc_out, attn_mask=enc_self_mask)

        dec_out = self.dec_embedding(x_dec, x_mark_dec)
        dec_out = self.decoder(dec_out, enc_out, x_mask=dec_self_mask, cross_mask=dec_enc_mask)
        dec_out = self.projection(dec_out)
        
        # dec_out = self.end_conv1(dec_out)
        # dec_out = self.end_conv2(dec_out.transpose(2,1)).transpose(1,2)
        if self.output_attention:
            return dec_out[:,-self.pred_len:,:], attns
        else:
            return dec_out[:,-self.pred_len:,:] # [B, L, D]

完结啦!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Informer是一种基于Transformer的序列模型,主要用于时间序列预测任务。其最大的特点是引入了全局和局部注意力机制,使得模型更加准确和稳定。以下是Informer的PyTorch实现代码示例: ```python import torch import torch.nn as nn import numpy as np class InformerEncoderLayer(nn.Module): def __init__(self, embed_dim, num_heads, dim_feedforward, dropout_rate=0.0): super(InformerEncoderLayer, self).__init__() self.self_attn = nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout_rate) self.linear1 = nn.Linear(embed_dim, dim_feedforward) self.dropout = nn.Dropout(dropout_rate) self.linear2 = nn.Linear(dim_feedforward, embed_dim) self.norm1 = nn.LayerNorm(embed_dim) self.norm2 = nn.LayerNorm(embed_dim) self.dropout1 = nn.Dropout(dropout_rate) self.dropout2 = nn.Dropout(dropout_rate) def forward(self, x): # self-attention res, _ = self.self_attn(x, x, x) x = x + self.dropout1(res) x = self.norm1(x) # feedforward res = self.linear2(self.dropout(torch.relu(self.linear1(x)))) x = x + self.dropout2(res) x = self.norm2(x) return x class InformerEncoder(nn.Module): def __init__(self, input_size, input_dim, embed_dim, num_heads, num_layers): super(InformerEncoder, self).__init__() self.input_fc = nn.Linear(input_size * input_dim, embed_dim) self.pos_encoding = nn.Parameter(torch.zeros(1, input_size, embed_dim)) self.layers = nn.ModuleList([InformerEncoderLayer(embed_dim, num_heads, dim_feedforward=2048) for _ in range(num_layers)]) def forward(self, x): # flatten input x = x.reshape(x.shape[0], -1) # input projection x = self.input_fc(x) # add position encoding x = x.unsqueeze(1) + self.pos_encoding # pass through encoder layers for layer in self.layers: x = layer(x) return x class InformerDecoderLayer(nn.Module): def __init__(self, embed_dim, num_heads, dim_feedforward, dropout_rate=0.0): super(InformerDecoderLayer, self).__init__() self.self_attn = nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout_rate) self.multihead_attn = nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout_rate) self.linear1 = nn.Linear(embed_dim, dim_feedforward) self.dropout = nn.Dropout(dropout_rate) self.linear2 = nn.Linear(dim_feedforward, embed_dim) self.norm1 = nn.LayerNorm(embed_dim) self.norm2 = nn.LayerNorm(embed_dim) self.norm3 = nn.LayerNorm(embed_dim) self.dropout1 = nn.Dropout(dropout_rate) self.dropout2 = nn.Dropout(dropout_rate) self.dropout3 = nn.Dropout(dropout_rate) def forward(self, x, encoder_out): # self-attention res, _ = self.self_attn(x, x, x) x = x + self.dropout1(res) x = self.norm1(x) # encoder-decoder attention res, _ = self.multihead_attn(x, encoder_out, encoder_out) x = x + self.dropout2(res) x = self.norm2(x) # feedforward res = self.linear2(self.dropout(torch.relu(self.linear1(x)))) x = x + self.dropout3(res) x = self.norm3(x) return x class InformerDecoder(nn.Module): def __init__(self, output_size, output_dim, embed_dim, num_heads, num_layers): super(InformerDecoder, self).__init__() self.output_fc = nn.Linear(output_dim, embed_dim) self.pos_encoding = nn.Parameter(torch.zeros(1, output_size, embed_dim)) self.layers = nn.ModuleList([InformerDecoderLayer(embed_dim, num_heads, dim_feedforward=2048) for _ in range(num_layers)]) self.output_proj = nn.Linear(embed_dim, output_dim) def forward(self, x, encoder_out): # output projection x = self.output_fc(x) # add position encoding x = x.unsqueeze(1) + self.pos_encoding # pass through decoder layers for layer in self.layers: x = layer(x, encoder_out) # output projection x = self.output_proj(x) return x class Informer(nn.Module): def __init__(self, input_size, input_dim, output_size, output_dim, embed_dim=64, num_heads=4, enc_layers=2, dec_layers=1): super(Informer, self).__init__() self.encoder = InformerEncoder(input_size, input_dim, embed_dim, num_heads, enc_layers) self.decoder = InformerDecoder(output_size, output_dim, embed_dim, num_heads, dec_layers) def forward(self, x): encoder_out = self.encoder(x[:, :-1]) decoder_out = self.decoder(x[:, -1], encoder_out) return decoder_out # example usage model = Informer(input_size=24, input_dim=1, output_size=24, output_dim=1) x = torch.randn(16, 25, 1) y = model(x) print(y.shape) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值