摘要
本周重点跟着网课学习了pytorch框架下张量的各种常用操作API,为后面跑模型做准备,因为看的视频比较偏向原理,现在对张量有了一个新的认识。其次在时序的研究上,最近我在看图神经网络跟时序结合的方向,所以本周学习了GNN模型以及跟时间序列结合的方式有哪些。
This week’s focus was on learning various common tensor manipulation APIs under the PyTorch framework through online courses, in preparation for running models in the future. Since the videos I watched were more theoretical, I now have a new understanding of tensors. In addition, in my study of time series, I have recently been exploring the combination of graph neural networks (GNN) with time series data, so this week I learned about GNN models and different ways to combine them with time series.
时序知识学习
GNN与时序
GNN解决什么问题?
上学期学习的CNN、RNN之类的模型对于图像,文字之类的欧几里得数据可以进行很好的处理。**欧几里得数据:**存在于N维欧氏空间的东西,比如2维表格里面的数据,再比如CNN经常做的RGB图像数据是3维数据(h*w*c)。
然而我们的现实世界更多的是非欧几里得数据,比如交通网、社交网络、污染物扩散,这种难以排序,没有坐标参考点,难以用矩阵或张量表示的数据就无法用CNN、RNN之类模型,而GNN这种模型特点就是用来处理这种非欧几里得数据,它以图为输入,输出各种下游任务的预测结果,比如:
-
节点分类:预测某一节点的类型
-
边的预测:另一种使用GNN的方法是找到可以为图形增加价值的新边。以社交网络为例,GNN可以找到在嵌入空间中与某人关系密切但还不是朋友的用户(节点)(也就是没有将他彼此联系起来的边),然后可以将这些用户作为朋友推荐介绍给他。
-
聚类:识别密集连接的节点形成的簇。
图
图这部分在数据结构中已经学习过了所以没有详细看,这部分我主要看图是以什么形式进行输入。在数据结构中,图的表示方式有两种,一种是领接矩阵一种是领接列表,在数据结构中,图用领接矩阵来表示的话,内存占用特别大O(n的平方),而且领接矩阵表示的图通常很稀疏,领接列表的话可以大幅减少空间的消耗,在某些场景中可能比较有用,一般还是用领接矩阵多。理由如下:
-
领接矩阵是方阵:可以通过矩阵运算对节点进行聚合、信息传递等操作,这使得 GNN 能够有效地处理大规模的图数据。
-
稀疏性:对于大多数实际的图数据来说,它们的领接矩阵通常是稀疏的,即只有很少的边连接了大量的节点。这种稀疏性可以通过优化算法来利用,从而提高计算效率。
-
与深度学习框架的兼容性:许多深度学习框架中都已经内置了对矩阵运算的支持,这使得将图表示为领接矩阵的形式更加方便与这些框架进行集成。
-
通用性:领接矩阵可以表示各种类型的图,包括有向图、无向图、加权图、多重图等。
GNN与传统NN作比较
CNN 的本质是将一个像素和其周围的像素值通过(局部的)卷积核进行汇聚,经过组合多层(深度)卷积,生成一个(高层的)特征向量,该向量包含了图像的多个特征,是各项下游任务(分类,聚类,排名等)的基础。CNN大部分都在做特征提取的任务,这点在上周学习LSTM代码的时候看到过用一维CNN提取特征作为LSTM输入的例子。
而CNN的三个思想:局部、汇聚、组合,也是GNN的核心思想。
-
局部性:GNN中的局部性是指节点的表示是通过聚合其邻居节点的表示来计算得到的。这种局部性的机制使得GNN能够捕捉到节点在其局部邻域内的信息,并利用这些信息来更新节点的表示。
-
汇聚:GNN的汇聚体现在一个节点从周围节点收集信息。对信息进行汇聚,创造出一条新的信息,类似于CNN的卷积。左图是CNN的卷积,右图是GNN的汇聚
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TLxz2Xyp-1681741471136)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片.png)]
- 组合:GNN的多轮信息传递,相当于CNN的多层卷积,在CNN中网络末端层的一个元素可由输入层的多个元素汇聚而成,在GNN中,一个节点的更新不仅与跟它直接相连的节点有关,因为在第二轮传递的时候,信息就到达了节点的领接节点,经过多轮后任意节点所收到的消息都可能融合了很多其他节点的消息,这跟CNN的第N层卷积层的上的值由前面很多层神经元相关很类似。
首先是节点的不同:
非欧数据比欧几里得数据的结构复杂,节点位置可以移动,但图还是之前那个图,所以想用传统CNN那种用一个卷积核在图上卷积是不可行的,并且节点的数量可能发生变化,比如新增或删除节点,这些操作会导致与深度学习模型的输入维度不匹配。
这个特征简单来说就是CNN具有平移不变性,旋转不变性。而图的节点没有顺序,同一个图可以有多个顺序,要求的是置换不变性。比如一个图,他的两种顺序就会出现两种领接矩阵,直接卷积的话就会出现不同的结果。
定义: 置换不变**,考虑我们要学习一个函数 f ,将图G(A,X)映射到空间里 ,使得f(A1,X1)=f(A2,X2) ,其中A ,X分别表示邻接矩阵和节点特征矩阵,1和2分别对应上面的两种顺序。如果对于任意的顺序 ,上面的式子都成立,就说f 是个置换不变**的函数。
其次是图中有边:
CNN跟RNN不是显式地表达节点之间的依存关系,而是通过不同的节点特征来间接表达节点之间的关系,这些相关只是作为节点的特征。而GNN用边来表示节点之间的依存关系。
如何与时序结合起来?
GNN在处理动态图其实就是涉及到时序的问题了。我查阅了资料了解了一下几种GNN处理时序问题的方法:
-
一种是GNN+ 时序模型的方法:
比如GNN-LSTM,即先将时间序列经过图神经网络进行空间上的卷积,然后再将结果输入到LSTM中进行时间上的卷积;或者LSTM-GNN,即先利用LSTM提取时序关系,然后再输入到GNN中进行空间上的卷积。当然,还可以分别利用LSTM和GNN直接对原始时间序列进行操作,然后再将二者结果进行组合。
-
第二种是时间切片法:
今天看到这里想起了师兄跟我提到的“每个时刻都是一个图,根据历史的多张图预测未来图的变化”应该就是这种方法,把动态图分解为多个时间步骤,每个时间步骤都是一个静态图。然后,将每个时间步骤作为输入,使用静态图的GNN进行处理。
-
基于注意力机制的方法:
这种方法通过引入一个注意力机制来处理时序信息。具体来说,每个节点都有一个自适应的时间权重,表示该节点在当前时间步骤中的重要性。然后,GNN会根据节点的时间权重来更新节点的表示。
-
ST-GCN
这是一种基于时空图卷积网络的方法,用于处理时序动作识别问题。该方法将时序数据转换为时空图,并使用时空图卷积网络来进行处理。
Pytorch框架学习
创建张量
-
先建个列表或元组c,c再转张量a
a = torch.tensor(c)
这个时候如果列表是float,张量会是float
type(a) 可以看a的数据类型
-
从另一个张量中初始化另一个张量
zeros_like()ones_like()
把某个张量变为全0,全1或rand_like()
变为某个随机张量,新张量的shape跟原张量是一样的 -
根据形状随机生成张量
torch.rand((h, c))
张量常用函数
.shape
看形状 .device
看张量在哪个设备上
张量常用API
numel
方法,torch.numel,返回张量的元素总数,当然也可以输出张量的shape,然后乘一下也可以表示。
创建类API
-
zeros
方法,torch.zeros(),返回一个为全为0的张量,shape之类的由zeros(参数)里面的参数自己设置 -
arange
和range
,生成一维张量。张量的元素由参数设置,参数关键在于start和end以及step,也就是开始和结束数。 arange跟range的不同在于 size大1。arange不包括end,而range包含end。size的大小在下面的图中可以看到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6tfE19R0-1681741471136)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 1.png)]
-
eye
创建一个对角线为1的张量,参数要设置矩阵的大小,最少设置一个h,此时为方阵。 -
full
创建一个值全为value,自定义size的张量。因此参数最主要就是value和size,调用方法用torch.full((h, c), value)
torch.full((2, 3), 3.141592)
输出为
tensor([[ 3.1416, 3.1416, 3.1416],
[ 3.1416, 3.1416, 3.1416]])
- `full_like(张量输入a, value)` ,根据输出的张量a,返回一个shape一样,但值为value的张量### 索引、切片API- `cat`连接。torch.cat(*tensors*, *dim=0*, ***, *out=None*), 第一个参数表示需要连接的tensor,dim表示在哪个维度进行连接,维度从0开始算,也就是行。```Python
import torch
import numpyb = torch.full((2, 2), 1)
print(b)
c= torch.full((2, 3), 2)
print(c)
d = torch.cat((b, c), dim=1)
print(d)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONuCPD2a-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 2.png)]
比如这里的b跟c,只能在列上加,所以dim为1。
stack
torch.stack(tensors, dim=0, ***, out=None)。stack跟cat虽然都是合并,但有很大的不同,stack会有维度上的叠加,而cat是在dim维度里面加上第二个张量的内容,最明显就是看中括号的个数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mfXFx2tH-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 3.png)]
可以看中括号个数确定维度的变化,cat连接后还是二维,而stack堆叠后成了三维。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JuWBWJHI-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 4.png)]
-
chunks
切片。torch.chunk(input, chunks, dim=0),input是张量输入,chunks是分成几块,dim是在哪个维度进行分割。默认为0,也就是在行进行切割。 -
split
分块。跟chunks的区别在于chunks是均分,而split可以不均分,按自己的想法分。torch.split(tensor, split_size_or_sections, dim=0)。三个参数中,dim不设置的时候默认为0,也就是行分块。最需要解释的是 split_size_or_sections,它表示每个张量块的大小,为整数,所以输入张量的大小/每个块大小不能整除的话,最后一个块可能会比较小。split_size_or_sections也可以为一个列表[1,2,3,4,…n],把输入分为n个张量,每个张量的块大小为对应位置的数字大小。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vq3QQVZr-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 5.png)]
gather
取变量。torch.gather(input, dim, index, ***, sparse_grad=False, out=None),前三个参数必须设置。gather沿着某个维度取变量。这个比较难。首先输出的形状跟Index一致,其次 ****主要是看dim和Index. dim=0,则要去找的行为index上的值,列不用看index的值。dim=1,则列为index上对应的值,列不看。比如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDggemU0-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 6.png)]
这个时候的列看什么? 不看索引表的值,直接看当前确定的元素是第几列就为第几列。
reshape
重塑形状,元素数和相对顺序不变。torch.reshape(input, shape)。参数就两个,输入,形状,这里的形状(h, c)要保证h*c=元素总数。想变成一维的,也就是拉直(在不知道总长度的时候),a[-1]在Python中表示最后一个元素,那么shape可以用[-1]或者(-1,)来表示一维拉长。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uxo4g9zQ-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 7.png)]
scatter_
.有下划_的一般是直接在该位操作。选定某些位置并改变他们的值。.scatter_(dim, index, src, reduce=None)。在张量上选位置的时候跟gather的索引一样,需要注意的是取src覆盖的时候,是在src上直接取index相同位置的值。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xyS2z3u0-1681741471138)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 8.png)]
至于填的位置,就跟gather一样了,看dim和index.
-
scatter_add_
,跟普通的不同在于他这里是+src的值。 -
squeeze
torch.squeeze(input, dim=None)。这个API的作用是消除多余的维度,dim可以指定消除某个维度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kF8B6oeY-1681741471139)(周报+ab14e843-4446-43b7-ad38-28f6bd08ba0c/图片 9.png)]
take
torch.take(input, index)。根据索引的下标在一维的input中找到。意思先把input拉成一维,再根据index查找。
总结
这周的成果主要就是跟着师兄给的课程学了一些Pytorch框架的基本操作,然后看了不少关于GNN和时序相结合的帖子,下周除了继续学习pytorch课程外,我打算找篇GNN和时序结合的论文来看,然后等pytorch框架掌握差不多了,就去跟师兄要数据集跑试试。