目录
- main_informer
- data_loader
- exp_basic
- exp_informer
- masking
- metrice
- timefeatures
- tools
- embed
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解码器输入的结构:
- 起始标记序列(Start Token Series, label_len):这部分是已知的序列标签,长度由label_len参数指定。在训练时,这些标签可能是真实数据的一部分,而在预测时,它们可能是基于历史数据生成的。
- 零填充序列(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
#位置编码,定义一个继承自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)