深度学习常用数据操作总结——数据操作

深度学习中需要大量对数据进行操作,本文以Pytorch框架为例,对常用的数据操作进行总结。

基本操作

深度学习中常使用张量(tensor)这一概念,张量最初起源于力学,用于表示弹性介质中各点应力状态,后来发展为力学和物理学的一个数学工具。它可以满足一切物理定于必须与坐标系的选择无关的特性,是矢量概念的推广。在理解时大致上可以直接将其对应为向量。零阶张量对应标量,一阶张量对应向量,二阶张量对应矩阵。

可以使用arrange创建行向量,张量默认存储在内存中,并采用基于CPU的计算。

x = torch.arrange(12)

可以采用shape来输出张量的形状。

1
x.shape

调用reshape函数可以改变张量的形状

1
2
x.reshape(3,4)        #将张量变为3*4的矩阵
x.shape(3,-1)         #将张量变为3*4的矩阵,-1表示宽度无需指定,会自动计算

可以用zerosonesrandn来初始化创建张量,分别表示张量的元素为全0,全1,随机从标准正态分布中取值。也可以通过python列表来为张量元素赋值。

1
2
3
4
torch.ones(2,3,4)
troch.zeros(2,3,4)
torch.randn(2,3,4)
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])

运算符

张量之间可以进行算术运算,对于任一具有相同形状的张量,可以使用标准算术运算符升级为按元素运算。

1
2
3
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y  # **运算符是求幂运算

还可以将两个或多个张量连结(concatenate)起来个构成一个更大的张量

1
2
3
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)

此外,还可以对张量进行线性代数运算,包括向量点积和矩阵乘法。

点积

1
2
3
y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)
np.sum(x * y)            #也可以通过按元素乘法然后求和的方式来求向量的点积

向量积与点积类似,区别在于(1)调用mv函数;(2)是矩阵乘向量,点积是向量乘向量。

1
torch.mv(A, x)

矩阵乘法调用mm函数

1
torch.mm(A, B)

范数

向量范数是将向量映射到标量的函数f,可以简单理解为向量的大小。特殊地,欧氏距离为$L_2$范数。$L_1$范数表示向量各元素的绝对值之和

1
2
3
u = torch.tensor([3.0, -4.0])
torch.norm(u)            #L2范数
torch.abs(u).sum()        #L1范数

矩阵范数的计算同理。

广播机制

上节介绍了相同形状的张量如何进行运算,本节介绍形状不同的张量的运算。可以通过广播机制(Broadcasting Mechanism)进行操作,首先通过复制元素使得两个张量具有相同的形状,其次对生成的数组执行按元素操作。

1
2
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))

索引与切片

类似于Python语言的特性,张量中的元素可以通过索引访问,也可以通过切片访问,此处略过

节省内存

运行某些操作可能会使得程序在内存中为新结果分配内存。例如,用 Y = X + Y,将不再引用Y指向的张量,而是引用新分配内存处的张量。当进行大量数据训练时,需要占用大量内存,此时应尽量避免不必要地分配内存,因此,我们希望原地执行这些更新。

为了达到原地操作的目的,可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = <expression>

1
2
3
4
Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))

此时两次的Z的地址不变,说明为原地操作。如果后续计算中没有重复使用X,也可以使用X[:] = X + Y或者X += Y来减少内存操作的开销。

转换为其他Python对象

Pytorch定义的张量可以与numpy定义的张量进行相互转化。

1
2
A = X.numpy()
B = torch.tensor(A)

自动微分

深度学习可以通过自动计算导数(自动微分)来加快求导。实际上,根据我们设计的模型,系统会构建一个计算图,来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够对吼反向传播梯度。以$y = 2X^TX$关于列向量求导为例

1
2
3
4
5
6
7
8
9
10
import torch 

x= torch.arrange(4.0)

x.requires_grad_(True)    #等效于x = torch.arange(4.0, requires_grad=True)
print(x.grad)
y = 2 * torch.dot(x, x)


y.backward()            #调用反向传播函数计算y关于x每个分量的梯度

使用自动微分的一个好处是即使计算图需要通过Python控制流,我们仍然可以计算得到的变量的梯度。

分离计算

例如,z是y和x的函数,y是x的函数,如果想计算z关于x的梯度而保持y为常数,可以采用分离y的方法,分离y后,会返回一个新变量,该变量具有与y相同的值,但会丢弃计算图中关于计算y的信息,即梯度不会流经u到x,可以用于计算偏导数。一句话总结即计算偏导数时需要将不求导的变量调用detach函数进行分离。

1
2
3
4
5
6
7
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u

深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上。然后我们记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。