文章目錄
- 摘要
- Abstract
- 文獻閱讀
- 題目
- 問題
- 本文貢獻
- 問題描述
- 圖神經網絡
- Framework
- 實驗
- 數據集
- 實驗結果
- 深度學習
- MAGNN模型相關代碼
- GNN
- 為什么要用GNN?
- GNN面臨挑戰
- 總結
摘要
本周閱讀了一篇用于多變量時間序列預測的多尺度自適應圖神經網絡的文章,多變量時間序列(MTS)預測在智能應用的自動化和最優化中發揮著重要作用。這是一項具有挑戰性的任務。本文提出了一種多尺度自適應圖神經網絡(MAGNN)來解決上述問題。MAGNN利用多尺度金字塔網絡在不同的時間尺度上保持潛在的時間依賴關系。作者還開發了一個基于尺度的融合模塊,有效地促進了不同時間尺度上的協作,并自動捕捉貢獻的時間模式的重要性。在六個真實數據集上的實驗表明,MAGNN在各種設置下的性能都優于最先進的方法。
Abstract
This week,an article on multi-scale adaptive graph neural network for multivariable time series prediction is readed. Multivariable time series (MTS) prediction plays an important role in the automation and optimization of intelligent applications. This is a challenging task. In this paper, a multi-scale adaptive graph neural network (MAGNN) is proposed to solve the above problems. MAGNN uses multi-scale pyramid network to maintain potential time dependence on different time scales. The author also developed a scale-based fusion module, which effectively promoted the cooperation on different time scales and automatically captured the importance of the contribution time pattern. Experiments on six real data sets show that the performance of MAGNN is better than the most advanced methods in various settings.
文獻閱讀
題目
Multi-Scale Adaptive Graph Neural Network for Multivariate Time Series Forecasting
問題
1)傳統的方法,如向量自回歸(VAR)、時間正則化矩陣分解(TRMF)、向量自回歸滑動平均(VARMA)和高斯過程(GP),往往依賴于嚴格的平穩假設,無法捕捉變量之間的非線性相關性。
2)注意機制和基于記憶的網絡模型側重于時間相關性的建模,將MTS的輸入處理為向量,并假設單個變量的預測值受所有其他變量的影響,這在實際應用中是不合理的,也是很難滿足的。例如,一條街道的Traf?c?OWS很大程度上受到鄰近街道的影響,而來自遠處街道的影響相對較小。
3)現有的GNN模型只考慮了單個時間尺度上的時間依賴關系,這可能不能正確地反映許多現實世界場景中的變化。已有的工作學習共享鄰接矩陣來表示豐富的變量間依賴關系,這使得模型偏向于學習一種突出的共享時態模式。
本文貢獻
1)提出MAGNN,它學習一種既能綜合反映多尺度時態模式又能反映特定尺度變量間依賴關系的時態表示。
2)設計了一個自適應圖學習模塊,用于探索不同時間尺度下豐富和隱含的變量間依賴關系,以及一個基于尺度的融合模塊,用于促進這些特定尺度的時間表示之間的協作,并自動捕獲貢獻的時間模式的重要性。
3)在六個真實世界的MTS基準數據集上進行廣泛的實驗。實驗結果表明,該方法的性能優于目前最先進的方法。
問題描述
本文主要研究MTS預測。在形式上,給定一個輸入的時間序列數據,其中
表示時間步t的值,N是變量維度,
表示第t個時間步的第i個變量的值,MTS預測的目的是預測未來在時間步t+h的值
,其中h表示需要預測的未來時間步數。這個問題可以表述為:
。式中F為映射函數,θ表示所有可學習的參數。
然后,存在如下幾種關于MTS預測的定義:
定義1:MTS數據用圖結構表示。圖被定義為G=(V,E),其中V表示節點集,|V|=N,E是邊集。假設,將第i個變量視為第i個節點
,
的值是的特征,每條邊
的都表明vi和vj之間存在變量間的依賴關系。
定義2:加權鄰接矩陣。圖的加權鄰接矩陣是一種用于存儲邊權重的數學表示方法,其中如果
,
;如果
,
。對于沒有任何先驗知識的純MTS數據,需要學習多圖的加權鄰接矩陣來表示豐富且隱式的變量間依賴關系。據此,MTS預測公式可修改為:
。其中
表示能被GNN用于MTS預測的圖集。
圖神經網絡
論文方法中應用的圖卷積操作定義如下:
其中G=(V,E,A)是一個帶加權鄰接矩陣的圖,x是節點的表示,σ是一個激活函數,θ是可學習的參數矩陣,是具有自連接的鄰接矩陣,
是
的對角度矩陣,
。通過將圖卷積操作多層堆疊,可以聚合多階的鄰居信息。
多尺度GNN,又稱分層GNN,通常在細粒度圖的基礎上分層構建粗粒度圖。MAGNN關注時間維度的尺度,與一般的多尺度GNN非常不同,后者主要關注空間維度的尺度。MAGNN引入了一個多尺度金字塔網絡,將原始時間序列轉換為從較小尺度到較大尺度的特征表示,在該網絡上,它學習每個尺度下具有相同大小的特定尺度圖,并對每個圖使用公式定義的基本GNN。
Framework
下圖說明了MAGNN的框架,它包括四個主要部分:a)多尺度金字塔網絡,用于在不同的時間尺度上保持底層的時間層次;b)自適應圖學習模塊,用于自動推斷變量間的依賴關系;c)多尺度時間圖神經網絡,用于捕獲各種尺度特定的時間模式;d)尺度級融合模塊,用于有效地促進跨不同時間尺度的協作。
MAGNN框架的4個主要部分組成具體作用如下:(a)兩個并行的卷積神經網絡和每層的逐點相加將特征表示從較小尺度分層變換到較大尺度;(b)自適應圖學習模塊將節點嵌入和尺度嵌入作為輸入,并輸出特定尺度的鄰接矩陣;?將每個尺度特定的特征表示和鄰接矩陣輸入到時序圖神經網絡(TGNN)中,以獲得尺度特定的表示。(d)加權融合特定尺度表示以捕獲貢獻的時間模式。最終的多尺度表示被送入包括兩個卷積神經網絡的輸出模塊以獲得預測值。
實驗
數據集
為了評估MAGNN的性能,在六個公共基準數據集上進行了實驗:太陽能、交通、電力、匯率、納斯達克和METR-LA。表一匯總了數據集的統計數據,6個公共基準數據集的詳細情況如下:
太陽能:此數據集包含從國家可再生能源實驗室收集的太陽能,2007年從阿拉巴馬州的137個光伏發電廠每10分鐘采樣一次。
交通:此數據集包含加州交通部的道路使用率(在0到1之間),這些數據是從2015年到2016年舊金山灣區的862個傳感器每小時匯總的。
用電量:此數據集包含UCI機器學習存儲庫的用電量,從2012年到2014年每小時匯總321個客戶端的用電量。R匯率:該數據集包含八個國家的匯率,從1990年到2016年每天都進行抽樣。
Nasdaq:這個數據集包含82家公司的股票價格,從2016年7月到2016年12月每分鐘抽樣一次。
METR-LA:此數據集包含洛杉磯縣的平均車速,這些車速是從2012年3月至2012年6月高速公路上的207個環路探測器收集的5分鐘數據。
將六個數據集按時間順序分為訓練集(60%)、驗證集(20%)和測試集(20%)。
輸入窗口大小T設置為168。學習率設置為0.001。使用ADAM優化器,所有可訓練的參數都可以通過反向傳播進行優化。對于所有數據集,尺度數為4。多尺度金字塔網絡中的CNN的核大小從金字塔網絡的第一層到最后一層分別設置為1×7、1×6和1×3,所有CNN的步長設置為2。作者分別設置地平線h={3,6,12,24},對于Nasdaq數據集,預測范圍被設置為3到24分鐘,對于METR-LA數據集,從15到120分鐘,對于太陽能數據集,從30到240分鐘,對于交通和電力數據集,從3到24小時,對于匯率數據集,從3到24天。預測范圍越大,預測就越難。
實驗結果
六個數據集上所有方法的結果總結如下表(按RSE表示):
在六個真實數據集上的實驗表明,MAGNN在各種設置下的性能都優于最先進的方法。
深度學習
MAGNN模型相關代碼
from layer import *
# from AGCRN import *
import torchclass magnn(nn.Module):def __init__(self, gcn_depth, num_nodes, device, dropout=0.3, subgraph_size=20, node_dim=40, conv_channels=32, gnn_channels=32, scale_channels=16, end_channels=128, seq_length=12, in_dim=1, out_dim=12, layers=3, propalpha=0.05, tanhalpha=3, single_step=True):super(magnn, self).__init__()self.num_nodes = num_nodesself.dropout = dropoutself.device = deviceself.single_step = single_stepself.filter_convs = nn.ModuleList()self.gate_convs = nn.ModuleList()self.scale_convs = nn.ModuleList()self.gconv1 = nn.ModuleList()self.gconv2 = nn.ModuleList()self.norm = nn.ModuleList()self.seq_length = seq_lengthself.layer_num = layersself.gc = graph_constructor(num_nodes, subgraph_size, node_dim, self.layer_num, device)if self.single_step:self.kernel_set = [7, 6, 3, 2]else:self.kernel_set = [3, 2, 2]self.scale_id = torch.autograd.Variable(torch.randn(self.layer_num, device=self.device), requires_grad=True)# self.scale_id = torch.arange(self.layer_num).to(device)self.lin1 = nn.Linear(self.layer_num, self.layer_num) self.idx = torch.arange(self.num_nodes).to(device)self.scale_idx = torch.arange(self.num_nodes).to(device)self.scale0 = nn.Conv2d(in_channels=in_dim, out_channels=scale_channels, kernel_size=(1, self.seq_length), bias=True)self.multi_scale_block = multi_scale_block(in_dim, conv_channels, self.num_nodes, self.seq_length, self.layer_num, self.kernel_set)# self.agcrn = nn.ModuleList()length_set = []length_set.append(self.seq_length-self.kernel_set[0]+1)for i in range(1, self.layer_num):length_set.append( int( (length_set[i-1]-self.kernel_set[i])/2 ) )for i in range(self.layer_num):"""RNN based model"""# self.agcrn.append(AGCRN(num_nodes=self.num_nodes, input_dim=conv_channels, hidden_dim=scale_channels, num_layers=1) )self.gconv1.append(mixprop(conv_channels, gnn_channels, gcn_depth, dropout, propalpha))self.gconv2.append(mixprop(conv_channels, gnn_channels, gcn_depth, dropout, propalpha))self.scale_convs.append(nn.Conv2d(in_channels=conv_channels,out_channels=scale_channels,kernel_size=(1, length_set[i])))self.gated_fusion = gated_fusion(scale_channels, self.layer_num)# self.output = linear(self.layer_num*self.hidden_dim, out_dim)self.end_conv_1 = nn.Conv2d(in_channels=scale_channels,out_channels=end_channels,kernel_size=(1,1),bias=True)self.end_conv_2 = nn.Conv2d(in_channels=end_channels,out_channels=out_dim,kernel_size=(1,1),bias=True)def forward(self, input, idx=None):seq_len = input.size(3)assert seq_len==self.seq_length, 'input sequence length not equal to preset sequence length'scale = self.multi_scale_block(input, self.idx)# self.scale_weight = self.lin1(self.scale_id)self.scale_set = [1, 0.8, 0.6, 0.5]adj_matrix = self.gc(self.idx, self.scale_idx, self.scale_set)outputs = self.scale0(F.dropout(input, self.dropout, training=self.training))out = []out.append(outputs)for i in range(self.layer_num):"""RNN-based model"""# output = self.agcrn[i](scale[i].permute(0, 3, 2, 1), adj_matrix) # B T N D# output = output.permute(0, 3, 2, 1)output = self.gconv1[i](scale[i], adj_matrix[i])+self.gconv2[i](scale[i], adj_matrix[i].transpose(1,0))scale_specific_output = self.scale_convs[i](output)out.append(scale_specific_output)# concatenate# outputs = outputs + scale_specific_output# mean-pooling # outputs = torch.mean(torch.stack(out), dim=0)out0 = torch.cat(out, dim=1)out1 = torch.stack(out, dim = 1)if self.single_step:outputs = self.gated_fusion(out0, out1)x = F.relu(outputs)x = F.relu(self.end_conv_1(x))x = self.end_conv_2(x)return x, adj_matrix
網絡層定義如下:
from __future__ import division
import torch
import torch.nn as nn
from torch.nn import init
import numbers
import torch.nn.functional as F
import numpy as npclass nconv(nn.Module):def __init__(self):super(nconv,self).__init__()def forward(self,x, A):x = torch.einsum('ncvl,vw->ncwl',(x,A))return x.contiguous()class dy_nconv(nn.Module):def __init__(self):super(dy_nconv,self).__init__()def forward(self,x, A):x = torch.einsum('ncvl,nvwl->ncwl',(x,A))return x.contiguous()class linear(nn.Module):def __init__(self,c_in,c_out,bias=True):super(linear,self).__init__()self.mlp = torch.nn.Conv2d(c_in, c_out, kernel_size=(1, 1), padding=(0,0), stride=(1,1), bias=bias)def forward(self,x):return self.mlp(x)class layer_block(nn.Module):def __init__(self, c_in, c_out, k_size):super(layer_block, self).__init__()self.conv_output = nn.Conv2d(c_in, c_out, kernel_size=(1, 1), stride=(1, 2))self.conv_output1 = nn.Conv2d(c_in, c_out, kernel_size=(1, k_size), stride=(1, 1), padding=(0, int( (k_size-1)/2 ) ) )self.output = nn.MaxPool2d(kernel_size=(1,3), stride=(1,2), padding=(0,1))self.conv_output1 = nn.Conv2d(c_in, c_out, kernel_size=(1, k_size), stride=(1, 1) )self.output = nn.MaxPool2d(kernel_size=(1,3), stride=(1,2))self.relu = nn.ReLU()def forward(self, input):conv_output = self.conv_output(input) # shape (B, D, N, T)conv_output1 = self.conv_output1(input)output = self.output(conv_output1)return self.relu( output+conv_output[...,-output.shape[3]:] )# return self.relu( conv_output )class multi_scale_block(nn.Module):def __init__(self, c_in, c_out, num_nodes, seq_length, layer_num, kernel_set, layer_norm_affline=True):super(multi_scale_block, self).__init__()self.seq_length = seq_lengthself.layer_num = layer_numself.norm = nn.ModuleList()self.scale = nn.ModuleList()for i in range(self.layer_num):self.norm.append(nn.BatchNorm2d(c_out, affine=False))# # self.norm.append(LayerNorm((c_out, num_nodes, int(self.seq_length/2**i)),elementwise_affine=layer_norm_affline))# self.norm.append(LayerNorm((c_out, num_nodes, length_set[i]),elementwise_affine=layer_norm_affline))self.start_conv = nn.Conv2d(c_in, c_out, kernel_size=(1, 1), stride=(1, 1))self.scale.append(nn.Conv2d(c_out, c_out, kernel_size=(1, kernel_set[0]), stride=(1, 1)))for i in range(1, self.layer_num):self.scale.append(layer_block(c_out, c_out, kernel_set[i]))def forward(self, input, idx): # input shape: B D N Tself.idx = idxscale = []scale_temp = inputscale_temp = self.start_conv(scale_temp)# scale.append(scale_temp)for i in range(self.layer_num):scale_temp = self.scale[i](scale_temp)# scale_temp = self.norm[i](scale_temp)# scale_temp = self.norm[i](scale_temp, self.idx)# scale.append(scale_temp[...,-self.k:])scale.append(scale_temp)return scaleclass top_down_path(nn.Module):def __init__(self, c_in, c_out_1, c_out_2, c_out_3, c_out_4):super(top_down_path, self).__init__()self.down1 = nn.Conv2d(c_in, c_out_1, kernel_size=(1, 1), stride=(1, 1))self.down2 = nn.Conv2d(c_out_1, c_out_2, kernel_size=(1, 7), stride=(1, 2), padding=(0, 2))self.down3 = nn.Conv2d(c_out_2, c_out_3, kernel_size=(1, 6), stride=(1, 2))self.down4 = nn.Conv2d(c_out_3, c_out_4, kernel_size=(1, 3), stride=(1, 2))self.up3 = nn.ConvTranspose2d(c_out_4, c_out_3, kernel_size=(1,3), stride=(1,2))# self.up3 = nn.MaxUnpool2d()self.up2 = nn.ConvTranspose2d(c_out_3, c_out_2, kernel_size=(1,6), stride=(1,2), output_padding=(0,1))self.up1 = nn.ConvTranspose2d(c_out_2, c_out_1, kernel_size=(1,7), stride=(1,2))def forward(self, input):down_1 = self.down1(input)down_2 = self.down2(down_1)down_3 = self.down3(down_2)down_4 = self.down4(down_3)up_3 = self.up3(down_4)output_3 = down_3 + up_3up_2 = self.up2(output_3)output_2 = down_2 + up_2up_1 = self.up3(output_2)output_1 = down_1[:,:,:,1:] + up_1return down_4, output_3, output_2, output_1class gated_fusion(nn.Module):def __init__(self, skip_channels, layer_num, ratio=1):super(gated_fusion, self).__init__()# self.reduce = torch.mean(x,dim=2,keepdim=True)self.dense1 = nn.Linear(in_features=skip_channels*(layer_num+1), out_features=(layer_num+1)*ratio, bias=False)self.dense2 = nn.Linear(in_features=(layer_num+1)*ratio, out_features=(layer_num+1), bias=False)def forward(self, input1, input2):se = torch.mean(input1, dim=2, keepdim=False)se = torch.squeeze(se)se = F.relu(self.dense1(se))se = F.sigmoid(self.dense2(se))se = torch.unsqueeze(se, -1)se = torch.unsqueeze(se, -1)se = torch.unsqueeze(se, -1)x = torch.mul(input2, se)x = torch.mean(x, dim=1, keepdim=False)return xclass prop(nn.Module):def __init__(self,c_in,c_out,gdep,dropout,alpha):super(prop, self).__init__()self.nconv = nconv()self.mlp = linear(c_in,c_out)self.gdep = gdepself.dropout = dropoutself.alpha = alphadef forward(self,x,adj):adj = adj + torch.eye(adj.size(0)).to(x.device)d = adj.sum(1)h = xdv = da = adj / dv.view(-1, 1)for i in range(self.gdep):h = self.alpha*x + (1-self.alpha)*self.nconv(h,a)ho = self.mlp(h)return hoclass mixprop(nn.Module):def __init__(self,c_in,c_out,gdep,dropout,alpha):super(mixprop, self).__init__()self.nconv = nconv()self.mlp = linear((gdep+1)*c_in,c_out)self.gdep = gdepself.dropout = dropoutself.alpha = alphadef forward(self,x,adj):adj = adj + torch.eye(adj.size(0)).to(x.device)d = adj.sum(1)h = xout = [h]a = adj / d.view(-1, 1)for i in range(self.gdep):h = self.alpha*x + (1-self.alpha)*self.nconv(h,a)out.append(h)ho = torch.cat(out,dim=1)ho = self.mlp(ho)return hoclass graph_constructor(nn.Module):def __init__(self, nnodes, k, dim, layer_num, device, alpha=3):super(graph_constructor, self).__init__()self.nnodes = nnodesself.layers = layer_numself.emb1 = nn.Embedding(nnodes, dim)self.emb2 = nn.Embedding(nnodes, dim)self.lin1 = nn.ModuleList()self.lin2 = nn.ModuleList()for i in range(layer_num):self.lin1.append(nn.Linear(dim,dim))self.lin2.append(nn.Linear(dim,dim))self.device = deviceself.k = kself.dim = dimself.alpha = alphadef forward(self, idx, scale_idx, scale_set):nodevec1 = self.emb1(idx)nodevec2 = self.emb2(idx)adj_set = []for i in range(self.layers):nodevec1 = torch.tanh(self.alpha*self.lin1[i](nodevec1*scale_set[i]))nodevec2 = torch.tanh(self.alpha*self.lin2[i](nodevec2*scale_set[i]))a = torch.mm(nodevec1, nodevec2.transpose(1,0))-torch.mm(nodevec2, nodevec1.transpose(1,0))adj0 = F.relu(torch.tanh(self.alpha*a))mask = torch.zeros(idx.size(0), idx.size(0)).to(self.device)mask.fill_(float('0'))s1,t1 = adj0.topk(self.k,1)mask.scatter_(1,t1,s1.fill_(1))# print(mask)adj = adj0*maskadj_set.append(adj)return adj_setclass graph_constructor_full(nn.Module):def __init__(self, nnodes, k, dim, layer_num, device, alpha=3):super(graph_constructor_full, self).__init__()self.nnodes = nnodesself.layers = layer_numself.emb1 = nn.Embedding(nnodes, dim)self.emb2 = nn.Embedding(nnodes, dim)self.lin1 = nn.ModuleList()self.lin2 = nn.ModuleList()for i in range(self.layers):self.lin1.append(nn.Linear(dim,dim))self.lin2.append(nn.Linear(dim,dim))self.device = deviceself.k = kself.dim = dimself.alpha = alphaself.static_feat = static_featdef forward(self, idx, scale_idx, scale_set):nodevec1 = self.emb1(idx)nodevec2 = self.emb2(idx)adj_set = []for i in range(self.layers):nodevec1 = torch.tanh(self.alpha*self.lin1[i](nodevec1*scale_set[i]))nodevec2 = torch.tanh(self.alpha*self.lin2[i](nodevec2*scale_set[i]))a = torch.mm(nodevec1, nodevec2.transpose(1,0))-torch.mm(nodevec2, nodevec1.transpose(1,0))adj0 = F.relu(torch.tanh(self.alpha*a))adj_set.append(adj0)return adj_setclass graph_constructor_one(nn.Module):def __init__(self, nnodes, k, dim, layer_num, device, alpha=3, static_feat=None):super(graph_constructor_one, self).__init__()self.nnodes = nnodesself.layers = layer_numself.emb1 = nn.Embedding(nnodes, dim)self.emb2 = nn.Embedding(nnodes, dim)self.lin1 = nn.ModuleList()self.lin2 = nn.ModuleList()self.lin1 = nn.Linear(dim,dim)self.lin2 = nn.Linear(dim,dim)self.device = deviceself.k = kself.dim = dimself.alpha = alphaself.static_feat = static_featdef forward(self, idx, scale_idx, scale_set):nodevec1 = self.emb1(idx)nodevec2 = self.emb2(idx)adj_set = []nodevec1 = torch.tanh(self.alpha*self.lin1(nodevec1))nodevec2 = torch.tanh(self.alpha*self.lin2(nodevec2))a = torch.mm(nodevec1, nodevec2.transpose(1,0))-torch.mm(nodevec2, nodevec1.transpose(1,0))adj0 = F.relu(torch.tanh(self.alpha*a))mask = torch.zeros(idx.size(0), idx.size(0)).to(self.device)mask.fill_(float('0'))s1,t1 = adj0.topk(self.k,1)mask.scatter_(1,t1,s1.fill_(1))adj = adj0*maskreturn adj
GNN
在使用圖的時候可以簡單的將其劃分為下面幾步:
1.給定一個圖,首先將節點轉化為遞歸單元,將邊轉化為前饋神經網絡;
2.對所有節點執行n次鄰域聚合(消息傳遞)。
3.對所有節點的嵌入向量求和得到圖表示H。
4.將H傳遞到更高的層中,或者使用它來表示圖形的獨特屬性。
為什么要用GNN?
GNN與CNN、RNN的比較,為什么不用CNN或RNN,而用GNN?
GNN 是一種專門為圖結構數據設計的神經網絡。它可以處理不規則的拓撲結構,并且可以捕捉節點之間的復雜關系。GNN可以通過消息傳遞和聚合機制來處理不同大小和結構的圖數據。由于其適應性和靈活性,GNN 在許多領域,如社交網絡分析、化學分子識別和推薦系統等,取得了顯著的成功。
選擇 GNN 而不是 CNN 或 RNN 的主要原因是數據結構和任務類型。當處理圖結構數據或需要捕捉復雜的節點關系時,GNNs是更合適的選擇。然而,在處理網格結構數據(如圖像)或序列數據(如文本)時,CNN和 RNN可能是更好的選擇。
GNN簡單來說就是Graph + Nerual Networks,關鍵問題就是將圖的結構和圖中每個節點和邊的特征轉化為一般的神經網絡的輸入(張量)。
GNN面臨挑戰
目前,GNN面臨的挑戰主要包括:1)如何有效處理大規模圖數據;2)如何增強GNN的表達能力,以捕捉更為復雜的圖結構;3)如何解決圖數據中的不確定性和噪聲問題。
總結
下周將繼續學習GNN的一些相關數學知識,從理論上進一步理解GNN。