Bug and Usage of Torch
Bugs
PyG的Planetoid的下载问题
PyG(PyTorch Geometric)是图神经网络(GNN)常用的库函数的简称。它里面包含了训练图神经网络时常用的数据集以及网络架构等。PyG是基于PyTorch的,因此它和PyTorch完全兼容。
Planetoid是PyG中包含的图数据集(torch_geometric.datasets.Planetoid
)。它里面有Cora
、CiteSeer
和PubMed
这三个常用的引文网络。但是,原数据集是寄存在github上面的,因此在国内下载可能会遇到问题。解决方法为:
- 找到PyG库中的
planetoid.py
文件; - 将:修改为:
1
url = 'https://github.com/kimiyoung/planetoid/raw/master/data'
即,在gitee上下载而不用github。1
url='https://gitee.com/jiajiewu/planetoid/raw/master/data'
若无图形界面,如简易的Ubuntu,可用
sudo find / -name planetoid.py
快速找到文件的位置。
完成上述操作后PubMed
数据集的下载仍会出问题,具体报错为_pickle.UnpicklingError: invalid load key, '<'.
。原因未知,但可以确定是从服务器上下载文件时出现的问题。解决方法:
- 在Planetoid中把与
PubMed
有关的八个文件下载下来,手动放到raw
文件夹里即可。
需要注意的是,在PyG的
Planetoid
数据集中,num_classes
字段已经被删除,所以要想获得图中顶点的类别数,应该用dataset.y.max().item() + 1
(其中dataset
是对应的图数据集,如PubMed
、Cora
等)。
Usage of Torch
这一节包含了一些有用的PyTorch函数用法或语法。本节中,假设a
为一个已经定义好的张量torch.tensor
。
torch.max
torch.max(input, dim, keepdim=False, *, out=None)
或者a.(dim, keepdim=False, *, out=None)
该函数将返回一个元组(values, indices)
,其中values
是input
的给定dim
的同一行的元素的最大值张量,而indices
是该最大值在dim
的坐标张量,如:
1 | 4, 4) a = torch.randn( |
得到的是行最大值。该函数可以很容易地得到softmax的最终结果,也可用于分析分类的准确性。
此类操作属于PyTorch中的张量降维操作,更多类似的操作见reduction-ops
torch.eq
torch.eq(input, other, *, out=None)
或者a.eq(other, *, out=None)
该函数将两个张量(input
和other
)进行逐元素比较,若相同位置的两个元素相同,则返回True
;否则,返回False
。最终结果是个bool
张量:
1 | >>> torch.eq(torch.tensor([[1, 2], [3, 4]]), torch.tensor([[1, 1], [4, 4]])) |
上述操作结合torch.max
可以用于分析softmax预测正确的个数,如:
1 | correctnesss = y_hat.max(dim=1)[1].eq(y).float().sum() |
以上y_hat
为softmax结果,而y
为实际的标签。上面的代码首先求出softmax的预测结果(假设标签为0,1,……,n-1,那么dim
的下标就是预测值),然后再比较预测值与实际值是否相同,将正确的个数相加就得到了最终预测正确的个数。
此类操作属于PyTorch中的数值比较操作,更多类似的操作见comparison-ops
torch.dtype
torch.dtype
指的是张量元素的数据类型。PyTorch支持张量元素类型的随意转换:
1 | a = torch.normal(0, 1, size=(1,3)) # PyTorch默认使用float32 |
上述操作等价于:
1 | x = a.int() # float32 -> int |
更多Tensor数据类型见torch-dtype
torch.nn.NLLLoss & torch.nn.CrossEntropyLoss
两者本质上都是交叉熵损失函数,但是覆盖的范围不同:
CrossEntropyLoss
会一次性完成softmax、取对数log和交叉熵操作,即:
$$-\sum\limits _{n=1} ^N Y _{nm}\left\{\log[\text{softmax}(a)]\right\} _{nm}$$
式中,$Y$是标签的one-hot编码,其下标$nm$表示第$n$个样本的标签为$m$。当使用CrossEntropyLoss
为损失函数时,神经网络的最后一层无需再做softmax和log操作,但这也导致实际预测时还要对神经网络的输出做一次softmax(因为此时只在乎值之间的相对关系,故不用再取log)。NLLLoss
只会完成最后一步的交叉熵操作,故在神经网络的最后一层要添加softmax和log操作,不过最后预测时就不同再做额外的softmax了。- 即:
$$\text{CrossEntropyLoss}=\text{softmax}+\text{log}+\text{NLLLoss}$$
torch.nn.parameter.Parameter
torch.nn.parameter.Parameter(Tensor: data=None, requires_grad=True)
它属于Tensor
的子类,但是,不同于Tensor
的是,Parameter
默认有梯度,且当其与nn.Module
类一起使用时,会被自动添加进参数列表,并出现在parameters()
迭代器中。当我们自定义的网络需要额外的可训练参数时,可以使用Parameter
,但是要记得单独对其进行初始化且data
应该以torch.empty(shape)
的形式传入(troch.empty
将生成未被初始化的张量)。
更多信息详见PARAMETER
torch.bmm
torch.bmm(input, mat2, *, out=None)
其中input
的形状为$b\times n\times m$,mat2
的形状为$b\times m\times k$,最后输出的形状为$b\times n\times k$。换句话说,torch.bmm
是对每个batch
单独做了矩阵乘法:
$$
\text{output}[i] = \text{input}[i] \space @\space \text{mat2}[i]
$$
此类操作属于PyTorch中的与线性代数有关的运算,更多信息见BLAS and LAPACK Operations
torch.permute
torch.permute(input, dim)
或者a.permute(dim)
torch.permute
,如其字面意思,表维度大小的交换。dim
是一个表示新维度次序的列表,如对形状为(2, 3, 4)
的张量a
,应用a.permute(2, 1, 0)
后,将返回一个形状为(4, 3, 2)
的张量。对于二维张量或者只交换张量的最后两维的次序,permute
相当于对二维张量进行了一次转置,其他情况则可视为一次广义转置:原来维度$i$位置的值变成了新的维度上$i$位置的值(比如转置就是让原来第$i$列的元素变成第$i$行的元素)。
需要特别注意
permute
与reshape
和view
的区别。reshape
和view
的运行机理是将原张量从低维到高维拉成一个向量,然后再以新维度分布从高维到低维切割、堆叠,所以reshape
和view
前后,张量拉成的向量都是一样的,但是permute
前后则不是。
torch.nonzero
torch.nonzero(input, *, out=None, as_tuple=False)
其中,input
是任意维度的张量。当as_tuple=False
时,上述操作会以二维张量的形式返回input
中所有值不为0的元素的下标,在该二维张量中,每一行是一个非0元素的完整下标,如:
1 | 0.6, 0.0, 0.0, 0.0], torch.nonzero(torch.tensor([[ |
当as_tuple=True
时,上述操作会返回一个长度为len(input.shape)
的元组,元组的第i
个元素表示所有非0原则在第i
维的下标,如:
1 | 0.6, 0.0, 0.0, 0.0], torch.nonzero(torch.tensor([[ |
torch.nonzero
操作在GNN中特别有用。通过使用torch.nonzero
,我们可以快速地从邻接矩阵A
中获得所有边的顶点。
torch.nonzero
是PyTorch中丰富的切片操作中的一种,更多详细的切片操作见Indexing, Slicing, Joining, Mutating Ops
torch.randperm
torch.randperm(n, *, generator=None, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False, pin_memory=False) → Tensor
一般来说,有用的参数只有n
,该参数表明对0
到n-1
的所有下标随机排序。整个函数会返回随机排序后的下标张量。由于random
库中的random.shuffle
无法用于Tensor
,我们只能使用torch.randperm
来生成随机排序的下标,再通过切片来达到对原张量随机排序的目的。
1 | 4) torch.randperm( |
torch.randperm
是PyTorch中丰富的随机采样操作中的一种,更多的随机操作见Random sampling
torch.triu_indices & torch.tril_indices
torch.triu_indices(row, col, offset=0, *, dtype=torch.long, device='cpu', layout=torch.strided) → Tensor
此处只列出torch.triu_indices
的用法,因为两个函数用法是一致的,只不过前者取上三角下标而后者取下三角下标。
- 参数
row
和col
表示矩阵的行数和列数; offset
是待取三角相对于主对角线的偏移,上为+
,下为-
,当取0
时表示得到的下标包括主对角线。对于上三角,若想去掉主对角线,则需令offset=1
;对于下三角,则需令offset=-1
。
1 | 3, 3) a = torch.triu_indices( |
需要注意的是,上三角下标的排列顺序是:
1 | |---> |
而下三角下标是:
1 | --> |
torch.clone() & torch.Tensor.detach()
对torch.clone()
,用法为:
1 | torch.clone(input, *, memory_format=torch.preserve_format)` |
对torch.Tensor.detach()
,用法为:a.detach()
两者看似很像,但其实是作用完全不同的两个函数:
torch.clone()
是对张量的深拷贝,它将产生一个全新的张量,这个张量拥有原张量的所有属性,但是存储空间不重叠,新、旧张量互不影响;torch.Tensor.detach()
的作用是产生一个不属于原张量计算图、grad_fn=None
且requires_grad=False
但是与原张量共享内存的张量。也就是说,原张量的变化会影响detach后的张量;- 可以这样理解,张量就是一个结构体,它有
requires_grad
、grad_fn
以及数据等属性。torch.clone()
创建了一个全新的结构体,torch.Tensor.detach()
也创建了一个结构体,但是数据是指向原张量的指针。