大家好,欢迎来到IT知识分享网。
前言
实验表明,RNN 在几乎所有的序列问题上都有良好表现,包括语音/文本识别、机器翻译、手写体识别、序列数据分析(预测)等。 在实际应用中,RNN 在内部设计上存在一个严重的问题:由于网络一次只能处理一个时间步长,后一步必须等前一步处理完才能进行运算。这意味着 RNN 不能像 CNN 那样进行大规模并行处理,特别是在 RNN/LSTM 对文本进行双向处理时。这也意味着 RNN 极度地计算密集,因为在整个任务运行完成之前,必须保存所有的中间结果。
CNN 在处理图像时,将图像看作一个二维的“块”(m*n 的矩阵)。迁移到时间序列上,就可以将序列看作一个一维对象(1*n 的向量)。通过多层网络结构,可以获得足够大的感受野。这种做法会让 CNN 非常深,但是得益于大规模并行处理的优势,无论网络多深,都可以进行并行处理,节省大量时间。这就是 TCN 的基本思想。 2018年 Google、Facebook 相继发表了研究成果,其中一篇叙述比较全面的论文是 “An Empirical Evaluation of Generic Convolutional and Recurrent Networks”。业界将这一新架构命名为时间卷积网络(Temporal Convolutional Network,TCN)。TCN 模型以 CNN 模型为基础,并做了如下改进:
- 适用序列模型:因果卷积(Causal Convolution)
- 记忆历史:空洞卷积/膨胀/扩张卷积(Dilated Convolution),
- 残差模块(Residual block)
下面我们会一一进行介绍。
TCN
一维卷积
假设有一个时间序列,总共有五个时间点,比方说股市,有一个股票的价格波动:[10,13,12,14,15]:
TCN中,或者说因果卷积中,使用的卷积核大小都是2(我也不知道为啥不用更大的卷积核,看论文中好像没有说明这个),那么可想而知,对上面5个数据做一个卷积核大小为2的卷积是什么样子的:
五个数据经过一次卷积,可以变成四个数据,但是每一个卷积后的数据都是基于两个原始数据得到的,所以说,目前卷积的视野域是2。可以看到是输入是5个数据,但是经过卷积,变成4个数据了,在图像中有一个概念是通过padding来保证卷积前后特征图尺寸不变,所以在时间序列中,依然使用padding来保证尺寸不变:
padding是左右两头都增加0,如果padding是1的话,就是上图的效果,其实会产生6个新数据,但是秉着:“输入输出尺寸相同”和“我们不能知道未来的数据”,所以最后边那个未来的padding,就省略掉了,之后会在代码中会体现出来。 总之,现在我们大概能理解,对时间序列卷积的大致流程了,也就是对一维数据卷积的过程(图像卷积算是二维)。
下面看如何使用Pytorch来实现一维卷积:
net = nn.Conv1d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=1,dilation=1)
其中的参数跟二维卷积非常类似,也是有通道的概念的。这个好好品一下,一维数据的通道跟图像的通道一样,是根据不同的卷积核从相同的输入中抽取出来不同的特征。kernel_size=2之前也说过了,padding=1也没问题,不过这个公式中假如输入5个数据+padding=1,会得到6个数据,最后一个数据被舍弃掉。dilation是膨胀系数,下面的下面会讲。
因果卷积(Causal Convolutions)
因果卷积(Causal Convolutions)是在wavenet这个网络中提出的,之后被用在了TCN中。之前已经讲了一维卷积的过程了,那么因果卷积,其实就是一维卷积在时间序列中的一种应用吧。 因为要处理序列问题(时序性),就必须使用新的 CNN 模型,这就是因果卷积。
因果卷积有两个特点:
- 不考虑未来的信息。给定输入序列 $x_1,⋯,x_T$,预测 $y_1,⋯,y_T$。但是在预测 $y_t$时,只能使用已经观测到的序列 $x_1,⋯,x_t$,而不能使用 $x_{t+1},x_{t+2},…$ 。
- 追溯历史信息越久远,隐藏层越多。上图中,假设我们以第二层隐藏层作为输出,它的最后一个节点关联了输入的三个节点,即 $x_{t−2},x_{t−1},x_t$ ;假设以输出层作为输出,它的最后一个节点关联了输入的四个节点,即 $x_{t−3},x_{t−2},x_{t−1},x_t$。
假设想用上面讲到的概念,做一个股票的预测决策模型,然后希望决策模型可以考虑到这个时间点之前的4个时间点的股票价格进行决策,总共有3种决策:
- 0:不操作,
- 1:买入,
- 2:卖出
所以其实就是一个分类问题。因为要求视野域是4,所以按照上面的设想,要堆积3个卷积核为2的1维卷积层:
三次卷积,可以让最后的输出,拥有4个视野域。就像是上图中红色的部分,就是做出一个决策的过程。
股票数据,往往是按照分钟记录的,那少说也是十万、百万的数据量,我们决策,想要考虑之前1000个时间点呢?视野域要是1000,那意味着要999层卷积?(每经过一层,节点相对于前层减少一个,我们最后的输出只有一个节点,如果输入视野为1000,需要经过999层才能变为最后输出的一个节点),啥计算机吃得消这样的计算。所以引入了膨胀因果卷积。
膨胀/空洞/扩张因果卷积(Dilated Causal Convolution)
- 单纯的因果卷积还是存在传统卷积神经网络的问题,即对时间的建模长度是受限于卷积核大小的,如果要想抓去更长的依赖关系,就需要线性的堆叠很多的层。
- 标准的 CNN 可以通过增加 pooling 层来获得更大的感受野,而经过 pooling 层后肯定存在信息损失的问题。
膨胀卷积是在标准的卷积里注入空洞,以此来增加感受野。和传统卷积不同的是,膨胀卷积允许卷积时的输入存在间隔采样,采样率受超参数 dilation rate控制,指的是做卷积操作时kernel里面的元素之间的下标间隔(标准的 CNN 中 dilatation rate = 1,dilatation rate=2,表示输入时每2个点采样一个作为输入)。空洞的好处是不做 pooling 损失信息的情况下,增加了感受野,让每个卷积输出都包含较大范围的信息。下图展示了标准 CNN (左)和 Dilated Convolution (右),右图中的 dilatation rate 等于 2 。
如下图,这个就是dilation=2的时候的膨胀因果卷积的情况,
与之前的区别有两个:
- 看红色区域:可以看到卷积核大小依然是2,但是卷积核之间变得空洞了,每2个点采样一个作为输入;如果dilation=3的话,那么可以想而知,这个卷积核中间会空的更大,每3个点采样一个作为输入。
- 看淡绿色数据:因为dilation变大了,所以相应的padding的数量从1变成了2,所以为了保证输入输出的特征维度相同,padding的数值(在卷积核是2的情况下)等于dalition的数值(一般情况下,padding=(kernel_size-1)*dilation,空洞因果卷积的感受野范围大小为(每个卷积核元素之间有dilation-1个空洞节点):(kernel_size-1)*(dilation-1) + kernel_size = (kernel_size-1)*(dilation-1) + (kernel_size-1) + 1 = (kernel_size-1)*dilation + 1,或者(从第一个节点开始,每dilation个节点进行采样):(kernel_size-1)*dilation + 1。以输入中的第一个元素作为空洞因果卷积的最后一个元素,则它的左边需要padding的个数为:(kernel_size-1)*dilation + 1 – 1 = (kernel_size-1)*dilation)
然后我们依然实现上面那个例子,每次决策想要视野域为4:
可以看到,第一次卷积使用dilation=1的卷积,然后第二次使用dilation=2的卷积,这样通过两次卷积就可以实现视野域是4.
那么假设事业域要是8呢?那就再加一个dilation=4的卷积。在实践中,通常随网络层数增加, dilation以 2 的指数增长,dilation的值是2的次方,然后视野域也是2的次方的增长。
因为研究对象是时间序列,TCN 采用一维的卷积网络。下图是 TCN 架构中的因果卷积与空洞卷积,
可以看到,
- 每一层 $t$ 时刻的值只依赖于上一层 $t,t−1,..$ 时刻的值,体现了因果卷积的特性;
- 而每一层对上一层信息的提取,都是跳跃式的,且逐层 dilated rate 以 2 的指数增长,体现了空洞卷积的特性。
- 由于采用了空洞卷积,因此每一层都要做 padding(通常情况下补 0),padding 的大小为:padding=(kernel_size-1)*dilation 。
残差模块(Residual block)
CNN 能够提取 low/mid/high-level 的特征,网络的层数越多,意味着能够提取到不同 level的特征越丰富。并且,越深的网络提取的特征越抽象,越具有语义信息。
如果简单地增加深度,会导致梯度消失或梯度爆炸。对于该问题的解决方法是权重参数初始化和采用正则化层(Batch Normalization),这样可以训练几十层的网络。
解决了梯度问题,还会出现另一个问题:网络退化问题。随着网络层数的增加,在训练集上的准确率趋于饱和甚至下降了。注意这不是过拟合问题,因为过拟合会在训练集上表现的更好。下图是一个网络退化的例子,20 层的网络比 56 层的网络表现更好。
理论上 56 层网络的解空间包括了 20 层网络的解空间,因此 56 层网络的表现应该大于等于20 层网络。但是从训练结果来看,56 层网络无论是训练误差还是测试误差都大于 20 层网络(这也说明了为什么不是过拟合现象,因为 56 层网络本身的训练误差都没有降下去)。这是因为虽然 56 层网络的解空间包含了 20 层网络的解空间,但是我们在训练中用的是随机梯度下降策略,往往得到的不是全局最优解,而是局部最优解。显然 56 层网络的解空间更加的复杂,所以导致使用随机梯度下降无法得到最优解。
假设已经有了一个最优的网络结构,是 18 层。当我们设计网络结构时,我们并不知道具体多少层的网络拥有最优的网络结构,假设设计了 34 层的网络结构。那么多出来的 16 层其实是冗余的,我们希望训练网络的过程中,模型能够自己训练这 16 层为恒等映射,也就是经过这16 层时的输入与输出完全一样。但是往往模型很难将这 16 层恒等映射的参数学习正确,这样的网络一定比最优的 18 层网络表现差,这就是随着网络加深,模型退化的原因。因此解决网络退化的问题,就是解决如何让网络的冗余层产生恒等映射(深层网络等价于一个浅层网络)。
通常情况下,让网络的某一层学习恒等映射函数 $H(x)=x$ 比较困难,但是如果我们把网络设计为 $H(x)=F(x)+x$ ,我们就可以将学习恒等映射函数转换为学习一个残差函数 $F(x)=H(x)−x$ ,只要 $F(x)=0$ ,就构成了一个恒等映射 $H(x)=x$ 。在参数初始化的时候,一般权重参数都比较小,非常适合学习 $F(x)=0$ ,因此拟合残差会更加容易,这就是残差网络的思想。
下图为残差模块的结构,
该模块提供了两种选择方式,也就是
- identity mapping(即 x ,右侧“弯弯的线”,称为 shortcut 连接)
- residual mapping(即 F(x) ),
如果网络已经到达最优,继续加深网络,residual mapping 将被 push 为 0,只剩下 identity mapping,这样理论上网络一直处于最优状态了,网络的性能也就不会随着深度增加而降低了。
这种残差模块结构可以通过前向神经网络 + shortcut 连接实现。而且 shortcut 连接相当于简单执行了同等映射,不会产生额外的参数,也不会增加计算复杂度,整个网络依旧可以通过端到端的反向传播训练。
上图中残差模块包含两层网络。实验证明,残差模块往往需要两层以上,单单一层的残差模块 并不能起到提升作用。shortcut 有两种连接方式:
- identity mapping 同等维度的映射( $F(x)$ 与 $x$ 维度相同):
$F(x)=W_2σ(W_1x+b_1)+b_2,H(x)=F(x)+x$
- identity mapping 不同维度的映射( $F(x)$ 与 $x$ 维度不同):
$F(x)=W_2σ(W_1x+b_1)+b_2,H(x)=F(x)+W_sx$
以上是基于全连接层的表示,实际上残差模块可以用于卷积层。加法变为对应 channel 间的两个 feature map 逐元素相加。 对于残差网络,维度匹配的 shortcut 连接为实线,反之为虚线。在残差网络中,有很多残差模块,下图是一个残差网络。每个残差模块包含两层,相同维度残差模块之间采用实线连接,不同维度残差模块之间采用虚线连接。网络的 2、3 层执行 3x3x64 的卷积,他们的 channel 个数相同,所以采用计算: $H(x)=F(x)+x$ ;网络的 4、5 层执行 3x3x128 的卷积,与第 3 层的 channel 个数不同 (64 和 128),所以采用计算方式: $H(x)=F(x)+W_sx$ 。其中 $W_s$ 是卷积操作(用 128 个 3x3x64 的 filter),用来调整 x 的 channel 个数。
下图是 TCN 架构中的残差模块如下图所示:
TCN的基本模块TemporalBlock()
- 卷积并进行weight_norm结束后会因为padding导致卷积之后的新数据的尺寸B>输入数据的尺寸A,所以只保留输出数据中前面A个数据;
- 卷积之后加上个ReLU和Dropout层,不过分吧这要求。
- 然后TCN中并不是每一次卷积都会扩大一倍的dilation,而是每两次扩大一倍的dilation
- 总之,TCN中的基本组件:TemporalBlock()是两个dilation相同的卷积层,卷积+修改数据尺寸+relu+dropout+卷积+修改数据尺寸+relu+dropout
- 之后弄一个Resnet残差连接来避免梯度消失,结束!
TCN原作者pytorch核心代码实现
# 导入库 import torch import torch.nn as nn #Applies weight normalization to a parameter in the given module. from torch.nn.utils import weight_norm # 这个函数是用来修剪卷积之后的数据的尺寸,让其与输入数据尺寸相同。 class Chomp1d(nn.Module): def __init__(self, chomp_size): super(Chomp1d, self).__init__() self.chomp_size = chomp_size#这个chomp_size就是padding的值 def forward(self, x): return x[:, :, :-self.chomp_size].contiguous() # 这个就是TCN的基本模块,包含8个部分,两个(卷积+修剪+relu+dropout) # 里面提到的downsample就是下采样,其实就是实现残差链接的部分。不理解的可以无视这个 class TemporalBlock(nn.Module): def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2): super(TemporalBlock, self).__init__() self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size, stride=stride, padding=padding, dilation=dilation)) self.chomp1 = Chomp1d(padding) self.relu1 = nn.ReLU() self.dropout1 = nn.Dropout(dropout) self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size, stride=stride, padding=padding, dilation=dilation)) self.chomp2 = Chomp1d(padding) self.relu2 = nn.ReLU() self.dropout2 = nn.Dropout(dropout) self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1, self.conv2, self.chomp2, self.relu2, self.dropout2) self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None self.relu = nn.ReLU() self.init_weights() def init_weights(self): self.conv1.weight.data.normal_(0, 0.01) self.conv2.weight.data.normal_(0, 0.01) if self.downsample is not None: self.downsample.weight.data.normal_(0, 0.01) def forward(self, x): out = self.net(x) res = x if self.downsample is None else self.downsample(x) return self.relu(out + res) #最后就是TCN的主网络了 class TemporalConvNet(nn.Module): def __init__(self, num_inputs, num_channels, kernel_size=2, dropout=0.2): super(TemporalConvNet, self).__init__() layers = [] num_levels = len(num_channels) for i in range(num_levels): dilation_size = 2 ** i in_channels = num_inputs if i == 0 else num_channels[i-1] out_channels = num_channels[i] layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size, padding=(kernel_size-1) * dilation_size, dropout=dropout)] self.network = nn.Sequential(*layers) def forward(self, x): return self.network(x)
TCN时间序列实战
本部分我们将考虑解决下面的多变量输入时间序列问题。
$Y_t= Y_{t-1} – (R_{1,t-1}+ R_{1,t-2})+ 4R_{2,t-3}(R_{3,t-4}+ R_{3,t-6})$
这里,
- $R_{1,t}$是一个随机变量,
- $R_{2,t}$是一个随机变量
- $R_{3,t}$是一个随机变量,它以0.25的概率输出1,其他情况下输出0,,
我们来逐个文件来看里面的代码。
model.py:
import torch.nn as nn from torch.nn.utils import weight_norm class Crop(nn.Module): def __init__(self, crop_size): super(Crop, self).__init__() self.crop_size = crop_size def forward(self, x): return x[:, :, :-self.crop_size].contiguous() class TemporalCasualLayer(nn.Module): def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, dropout = 0.2): super(TemporalCasualLayer, self).__init__() padding = (kernel_size - 1) * dilation conv_params = { 'kernel_size': kernel_size, 'stride': stride, 'padding': padding, 'dilation': dilation } self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, **conv_params)) self.crop1 = Crop(padding) self.relu1 = nn.ReLU() self.dropout1 = nn.Dropout(dropout) self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, **conv_params)) self.crop2 = Crop(padding) self.relu2 = nn.ReLU() self.dropout2 = nn.Dropout(dropout) self.net = nn.Sequential(self.conv1, self.crop1, self.relu1, self.dropout1, self.conv2, self.crop2, self.relu2, self.dropout2) #shortcut connect self.bias = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None self.relu = nn.ReLU() def forward(self, x): y = self.net(x) b = x if self.bias is None else self.bias(x) return self.relu(y + b) class TemporalConvolutionNetwork(nn.Module): def __init__(self, num_inputs, num_channels, kernel_size = 2, dropout = 0.2): super(TemporalConvolutionNetwork, self).__init__() layers = [] num_levels = len(num_channels) tcl_param = { 'kernel_size': kernel_size, 'stride': 1, 'dropout': dropout } for i in range(num_levels): dilation = 2**i in_ch = num_inputs if i == 0 else num_channels[i - 1] out_ch = num_channels[i] tcl_param['dilation'] = dilation tcl = TemporalCasualLayer(in_ch, out_ch, **tcl_param) layers.append(tcl) self.network = nn.Sequential(*layers) def forward(self, x): return self.network(x) class TCN(nn.Module): def __init__(self, input_size, output_size, num_channels, kernel_size, dropout): super(TCN, self).__init__() self.tcn = TemporalConvolutionNetwork(input_size, num_channels, kernel_size = kernel_size, dropout = dropout) self.linear = nn.Linear(num_channels[-1], output_size) def forward(self, x): y = self.tcn(x)#[N,C_out,L_out=L_in] return self.linear(y[:, :, -1])
ts.py:
import random import numpy as np def generate_time_series(len): backshift = 10 # np.random.random(size=None) # Return random floats in the half-open interval [0.0, 1.0). r1 = np.random.random(len + backshift) r2 = np.random.random(len + backshift) # random.choices(population,weights=None,*,cum_weights=None,k=1) # 从population中随机选取k次数据,返回一个列表,可以设置权重。 # 注意:每次选取都不会影响原序列,每一次选取都是基于原序列。 # 参数weights设置相对权重,它的值是一个列表,设置之后,每一个成员被抽取到的概率就被确定了。 # 比如weights=[1,2,3,4,5],那么第一个成员的概率就是P=1/(1+2+3+4+5)=1/15。 # cum_weights设置累加权重,Python会自动把相对权重转换为累加权重,即如果你直接给出累加权重, # 那么就不需要给出相对权重,且Python省略了一步执行。 # 比如weights=[1,2,3,4],那么cum_weights=[1,3,6,10] # 这也就不难理解为什么cum_weights=[1,1,1,1,1]输出全是第一个成员1了。 rm = [random.choices([0, 0, 0, 1])[0] for _ in range(len + backshift)] ts = np.zeros([len + backshift, 4]) for i in range(backshift, len + backshift): ts[i, 1] = r1[i] ts[i, 2] = r2[i] ts[i, 3] = rm[i] ts[i, 0] = ts[i - 1, 0] -\ (r1[i - 1] + r1[i - 2]) +\ 4 * r2[i - 3] * (rm[i - 4] + rm[i - 6]) return ts[backshift:]
training_datasets.py:
import os import pandas as pd import torch def sliding_window(ts, features, target_len = 1): X = [] Y = [] # 产生的样本x为:[(i-target_len) - features,i - target_len) # y为:[i-target_len,i) # 可以看出,这里产生的样本是用过去历史上features个时刻的信息去 # 去预测未来target_len(这里target_len=1)个时刻 for i in range(features + target_len, len(ts) + 1): X.append(ts[(i-target_len) - features:i - target_len]) Y.append(ts[i - target_len:i]) return X, Y # 对时间序列做差分处理 def ts_diff(ts): diff_ts = [0] * len(ts) for i in range(1, len(ts)): diff_ts[i] = ts[i] - ts[i - 1] return diff_ts # 从预测出的各个时刻的差分值还原出实际的各个时刻的值 def ts_int(ts_diff, ts_base, start = 0): ts = [] for i in range(len(ts_diff)): if i == 0: ts.append(start + ts_diff[0]) else: ts.append(ts_diff[i] + ts_base[i - 1]) return ts def get_aep_timeseries(): dir_path = os.path.dirname(os.path.realpath(__file__)) df = pd.read_csv(f'{dir_path}/data/AEP_hourly.csv') ts = df['AEP_MW'].astype(int).values.reshape(-1, 1)[-3000:] return ts def get_pjme_timeseries(): dir_path = os.path.dirname(os.path.realpath(__file__)) df = pd.read_csv(f'{dir_path}/data/PJME_hourly.csv') ts = df['PJME_MW'].astype(int).values.reshape(-1, 1)[-3000:] return ts def get_ni_timeseries(): dir_path = os.path.dirname(os.path.realpath(__file__)) df = pd.read_csv(f'{dir_path}/data/NI_hourly.csv') ts = df['NI_MW'].astype(int).values.reshape(-1, 1)[-3000:] return ts def get_training_datasets(ts, features, test_len, train_ratio = .7, target_len = 1): X, Y = sliding_window(ts, features, target_len) X_train, Y_train, X_test, Y_test = X[0:-test_len],\ Y[0:-test_len],\ X[-test_len:],\ Y[-test_len:] train_len = round(len(ts) * train_ratio) X_train, X_val, Y_train, Y_val = X_train[0:train_len],\ X_train[train_len:],\ Y_train[0:train_len],\ Y_train[train_len:] x_train = torch.tensor(data = X_train).float() y_train = torch.tensor(data = Y_train).float() x_val = torch.tensor(data = X_val).float() y_val = torch.tensor(data = Y_val).float() x_test = torch.tensor(data = X_test).float() y_test = torch.tensor(data = Y_test).float() return x_train, x_val, x_test, y_train, y_val, y_test
dummy.py:
import torch.nn as nn # dummy prediction model (Y_t= Y_{t-1}) class Dummy(nn.Module): def __init__(self): super(Dummy, self).__init__() def forward(self, x):# x(x_test):[n_test,4,features] return x[:, 0, -1].unsqueeze(1)# 返回值形状:[n_test,1]
example.py:
import copy import random import sys import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import numpy as np import matplotlib.pyplot as plt import torch from dummy import Dummy from model import TCN from ts import generate_time_series from training_datasets import get_training_datasets, ts_diff, ts_int seed = 12 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) # time series input features = 20 # training epochs epochs = 1_000 # synthetic time series dataset ts_len = 5_000 # test dataset size test_len = 300 # temporal casual layer channels channel_sizes = [10] * 4 # convolution kernel size kernel_size = 5 dropout = .0 ts = generate_time_series(ts_len) # 对时间序列进行差分处理 ts_diff_y = ts_diff(ts[:, 0]) ts_diff = copy.deepcopy(ts) ts_diff[:, 0] = ts_diff_y # x:[N,features,4],y:[N,1,1] x_train, x_val, x_test, y_train, y_val, y_test =\ get_training_datasets(ts_diff, features, test_len) # [n_train,4,features] x_train = x_train.transpose(1, 2) # [n_val,4,features] x_val = x_val.transpose(1, 2) # [n_test,4,features] x_test = x_test.transpose(1, 2) # [N,1] y_train = y_train[:, :, 0] y_val = y_val[:, :, 0] y_test = y_test[:, :, 0] # device = torch.device("cuda") # for x in [x_train,x_val,x_test,y_train,y_val,y_test]: # x = x.to(device) train_len = x_train.size()[0] model_params = { # 'input_size',C_in 'input_size': 4, # 单步,预测未来一个时刻 'output_size': 1, 'num_channels': channel_sizes, 'kernel_size': kernel_size, 'dropout': dropout } model = TCN(**model_params) # model = model.to(device) optimizer = torch.optim.Adam(params = model.parameters(), lr = .005) mse_loss = torch.nn.MSELoss() best_params = None min_val_loss = sys.maxsize training_loss = [] validation_loss = [] for t in range(epochs): prediction = model(x_train) loss = mse_loss(prediction, y_train) optimizer.zero_grad() loss.backward() optimizer.step() val_prediction = model(x_val) val_loss = mse_loss(val_prediction, y_val) training_loss.append(loss.item()) validation_loss.append(val_loss.item()) if val_loss.item() < min_val_loss: best_params = copy.deepcopy(model.state_dict()) min_val_loss = val_loss.item() if t % 100 == 0: diff = (y_train - prediction).view(-1).abs_().tolist() print(f'epoch {t}. train: {round(loss.item(), 4)}, ' f'val: {round(val_loss.item(), 4)}') plt.title('Training Progress') plt.yscale("log") plt.plot(training_loss, label = 'train') plt.plot(validation_loss, label = 'validation') plt.ylabel("Loss") plt.xlabel("Epoch") plt.legend()# 图例 plt.show() best_model = TCN(**model_params) best_model.eval() best_model.load_state_dict(best_params) tcn_prediction = best_model(x_test) dummy_prediction = Dummy()(x_test) tcn_mse_loss = round(mse_loss(tcn_prediction, y_test).item(), 4) dummy_mse_loss = round(mse_loss(dummy_prediction, y_test).item(), 4) plt.title(f'Test| TCN: {tcn_mse_loss}; Dummy: {dummy_mse_loss}') plt.plot( ts_int( tcn_prediction.view(-1).tolist(), ts[-test_len:, 0], start = ts[-test_len - 1, 0] ), label = 'tcn') plt.plot(ts[-test_len-1:, 0], label = 'real') plt.legend() plt.show()
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/31195.html