数据操作
为了能够完成各种数据操作,需要存储和操作数据:
(1)获取数据;(2)将数据读入计算机后对其进行处理。
[张量表示一个由数值组成的数组,这个数组可能有多个维度]。
具有一个轴的张量对应数学上的向量(vector);
具有两个轴的张量对应数学上的矩阵(matrix);
具有两个轴以上的张量没有特殊的数学名称。
import torch
x = torch.arange(12)
xtensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
x = torch.empty(3,4)
print(x)
#还有属性torch.rand,torch.zerotensor([[ 1.1210e-44, 0.0000e+00, 1.4013e-45, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, -8.5899e+09, 1.1061e-14, -1.0845e-19]])[可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状]
x.shapetorch.Size([3, 4])
如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。因为这里在处理的是一个向量,所以它的shape与它的size相同。
x.numel()12
[要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape函数。]
X = x.reshape(3, 4)
Xtensor([[ 1.1210e-44, 0.0000e+00, 1.4013e-45, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, -8.5899e+09, 1.1061e-14, -1.0845e-19]])torch.zeros((2, 3, 4))tensor([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])torch.ones((2, 3, 4)) tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。
例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。
以下代码创建一个形状为(3,4)的张量。
其中的每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
torch.randn(3, 4)tensor([[-0.0306, -0.5537, 0.7322, -0.9043],
[-0.2646, 1.5311, -0.6513, -2.2694],
[-0.5897, 1.6428, 0.8202, -1.7223]])还可以[通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值]。
在这里,最外层的列表对应于轴0,内层的列表对应于轴1。
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])tensor([[2, 1, 4, 3],
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])运算符
[常见的标准算术运算符(+、-、*、/和**)都可以被升级为按元素运算]。
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 # **运算符是求幂运算(tensor([ 3., 4., 6., 10.]),
tensor([-1., 0., 2., 6.]),
tensor([ 2., 4., 8., 16.]),
tensor([0.5000, 1.0000, 2.0000, 4.0000]),
tensor([ 1., 4., 16., 64.]))
(“按元素”方式可以应用更多的计算),包括像求幂这样的一元运算符。
torch.exp(x)tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。
[我们也可以把多个张量连结(concatenate)在一起],注意dim为0,1的区别。
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)(tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[ 2., 1., 4., 3.],
[ 1., 2., 3., 4.],
[ 4., 3., 2., 1.]]),
tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
[ 4., 5., 6., 7., 1., 2., 3., 4.],
[ 8., 9., 10., 11., 4., 3., 2., 1.]]))[通过逻辑运算符构建二元张量]。
X == Ytensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])[对张量中的所有元素进行求和,会产生一个单元素张量。]
X.sum()tensor(66.)
广播机制
这种机制的工作方式如下:
通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
对生成的数组执行按元素操作。
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))由矩阵a将复制列,矩阵b将复制行,然后再按元素相加。
a + btensor([[0, 1],
[1, 2],
[2, 3]])索引和切片
就像在任何其他Python数组中一样,张量中的元素可以通过索引访问。
print(X)
X[-1], X[1:3]tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
(tensor([ 8., 9., 10., 11.]),
tensor([[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]]))X[1:2,1:3]tensor([[5., 9.]])
[除读取外,还可以通过指定索引来将元素写入矩阵。]
X[1, 2] = 9
Xtensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])[为多个元素赋值相同的值,只需要索引所有元素,然后为它们赋值。]
X[0:2, :] = 12
Xtensor([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])节省内存
[运行一些操作可能会导致为新结果分配内存]。
运行Y = Y + X后,我们会发现id(Y)指向另一个位置。ython首先计算Y + X,为结果分配新的内存,然后使Y指向内存中的这个新位置。
before = id(Y)
Y = Y + X
id(Y) == beforeFalse
这可能是不可取的,原因有两个:
首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
(执行原地操作)。
Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))id(Z): 139931132035296
id(Z): 139931132035296
[**如果在后续计算中没有重复使用X,也可以使用X[:] = X + Y或X += Y来减少操作的内存开销。**]
before = id(X)
X += Y
id(X) == beforeTrue
转换为其他Python对象
将深度学习框架定义的张量[转换为NumPy张量(ndarray)]
A = X.numpy()
B = torch.tensor(A)
type(A), type(B)(numpy.ndarray, torch.Tensor)
要(将大小为1的张量转换为Python标量),可以调用item函数或Python的内置函数。
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)(tensor([3.5000]), 3.5, 3.5, 3)
小结
深度学习存储和操作数据的主要接口是张量(n维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他Python对象。
数据预处理
读取数据集
首先(创建一个人工数据集,并存储在CSV(逗号分隔值)文件)
import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
f.write('NumRooms,Alley,Price\n') # 列名
f.write('NA,Pave,127500\n') # 每行表示一个数据样本
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')要[从创建的CSV文件中加载原始数据集],我们导入pandas包并调用read_csv函数。该数据集有四行三列。其中每行描述了房间数量(“NumRooms”)、巷子类型(“Alley”)和房屋价格(“Price”)。
# 如果没有安装pandas,只需取消对以下行的注释来安装pandas
# !pip install pandas
import pandas as pd
data = pd.read_csv(data_file)
print(data)NumRooms Alley Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
处理缺失值
注意,“NaN”项代表缺失值。[为了处理缺失的数据,典型的方法包括插值法和删除法,]其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。
通过位置索引iloc,我们将data分成inputs和outputs,其中前者为data的前两列,而后者为data的最后一列。对于inputs中缺少的数值,我们用同一列的均值替换“NaN”项。
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean())
print(inputs)NumRooms Alley
0 3.0 Pave
1 2.0 NaN
2 4.0 NaN
3 3.0 NaN
[对于inputs中的类别值或离散值,我们将“NaN”视为一个类别。]
由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”,pandas可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)NumRooms Alley_Pave Alley_nan
0 3.0 1 0
1 2.0 0 1
2 4.0 0 1
3 3.0 0 1
转换为张量格式
[现在inputs和outputs中的所有条目都是数值类型,它们可以转换为张量格式。]
import torch
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
X, y(tensor([[3., 1., 0.],
[2., 0., 1.],
[4., 0., 1.],
[3., 0., 1.]], dtype=torch.float64),
tensor([127500, 106000, 178100, 140000]))线性代数
标量、向量计算简单。
矩阵
A = torch.arange(20).reshape(5, 4)
Atensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]])(矩阵的转置)。
A.Ttensor([[ 0, 4, 8, 12, 16],
[ 1, 5, 9, 13, 17],
[ 2, 6, 10, 14, 18],
[ 3, 7, 11, 15, 19]])B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B矩阵进行逻辑运算
tensor([[1, 2, 3],
[2, 0, 4],
[3, 4, 5]])现在我们将B与它的转置进行比较。
B == B.Ttensor([[True, True, True],
[True, True, True],
[True, True, True]])张量
[就像向量是标量的推广,矩阵是向量的推广一样,可以构建具有更多轴的数据结构]。
X = torch.arange(24).reshape(2, 3, 4)
Xtensor([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])张量算法的基本性质
[给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量]。
例如,将两个相同形状的矩阵相加,会在这两个矩阵上执行元素加法。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # 通过分配新内存,将A的一个副本分配给B
A, A + B(tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
tensor([[ 0., 2., 4., 6.],
[ 8., 10., 12., 14.],
[16., 18., 20., 22.],
[24., 26., 28., 30.],
[32., 34., 36., 38.]]))[两个矩阵的按元素乘法称为Hadamard积(Hadamard product)。
A * Btensor([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]])将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape(tensor([[[ 2, 3, 4, 5],
[ 6, 7, 8, 9],
[10, 11, 12, 13]],
[[14, 15, 16, 17],
[18, 19, 20, 21],
[22, 23, 24, 25]]]),
torch.Size([2, 3, 4]))点积(Dot Product)
给定两个向量的点积(dot product)是相同位置的按元素乘积的和。
y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
(可以通过执行按元素乘法,然后进行求和来表示两个向量的点积):
torch.sum(x * y)tensor(6.)
矩阵-向量积
矩阵-向量积(matrix-vector product)。注意矩阵和向量形状。A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。
A.shape, x.shape, torch.mv(A, x)(torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))
矩阵-矩阵乘法
[我们可以将矩阵-矩阵乘法看作简单地执行次矩阵-向量积,并将结果拼接在一起,形成一个矩阵]
A是一个5行4列的矩阵,B是一个4行3列的矩阵。两者相乘后,我们得到了一个5行3列的矩阵。
B = torch.ones(4, 3)
torch.mm(A, B)tensor([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]])范数
线性代数中最有用的一些运算符是范数(norm)。在代码中,我们可以按如下方式计算向量的L2范数。
u = torch.tensor([3.0, -4.0])
torch.norm(u)tensor(5.)
深度学习中更经常地使用L2范数的平方,也会经常遇到[L1范数,它表示为向量元素的绝对值之和]
与L2范数相比,L1范数受异常值的影响较小。
为了计算L1范数,我们将绝对值函数和按元素求和组合起来。
torch.abs(u).sum()tensor(7.)
Frobenius范数满足向量范数的所有性质,它就像是矩阵形向量的L2范数。可以调用以下函数将计算矩阵的Frobenius范数。
torch.norm(torch.ones((4, 9)))tensor(6.)
自动微分
求导是几乎所有深度学习优化算法的关键步骤。深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。实际中,根据设计好的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够随后反向传播梯度。反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
一个简单的例子
首先,创建变量x并为其分配一个初始值。
import torch
x = torch.arange(4.0)
xtensor([0., 1., 2., 3.])
[在计算y关于x的梯度之前,需要一个地方来存储梯度。]
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
#.requires_grad 设置为 True ,它将开始追踪(track)在其上的所有操作(这样就可以利⽤链式法则进⾏梯度传播了)
print(x.grad) # 默认值是NoneNone
(现在计算y。)
y = 2 * torch.dot(x, x)
yx是一个长度为4的向量,计算x和x的点积,得到了我们赋值给y的标量输出。
接下来,[通过调用反向传播函数来自动计算y关于x每个分量的梯度],并打印这些梯度。
#完成计算后,可以调⽤ .backward() 来完成所有梯度计算。此 Tensor 的梯度将累积到 .grad 属性中。
y.backward()
x.gradtensor([ 0., 4., 8., 12.])
[现在计算x的另一个函数。]
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.gradtensor([1., 1., 1., 1.])
非标量变量的反向传播
当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。对于高阶和高维的y和x,求导的结果可以是一个高阶张量。然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括[深度学习中]),但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。这里(,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。)
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.gradtensor([0., 2., 4., 6.])
y = 2 * x
z = y.reshape(2, 2)
print(z)tensor([[0., 2.],
[4., 6.]], grad_fn=<ReshapeAliasBackward0>)#现在 y 不是⼀个标量,所以在调⽤ backward 时需要传⼊⼀个和 y 同形的权᯿向量进⾏加权求和得到⼀个标量
v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
z.backward(v)
print(x.grad)tensor([3.0000, 1.2000, 1.0200, 1.0020])
分离计算
[将某些计算移动到记录的计算图之外]。例如,假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。将y视为一个常数,并且只考虑到x在y被计算后发挥的作用。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == utensor([True, True, True, True])
由于记录了y的计算结果,我们可以随后在y上调用反向传播,得到y=x*x关于的x的导数,即2*x。
x.grad.zero_()
y.sum().backward()
x.grad == 2 * xtensor([True, True, True, True])
Python控制流的梯度计算
使用自动微分的一个好处是:
[即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度]。在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c让我们计算梯度。
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()上面定义的f函数在其输入a中是分段线性的。
a.grad == d / atensor(True)
小结
深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上,然后记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。
文章介绍了张量作为深度学习的基础,包括数据操作如获取、处理、形状变换,以及张量的数学运算、广播机制和索引。此外,还讨论了如何处理数据预处理中的缺失值问题,将数据转换为适合深度学习的格式。最后,文章深入到自动微分的概念,展示了如何在PyTorch中进行反向传播计算梯度,包括对非标量变量的反向传播和Python控制流中的梯度计算。

被折叠的 条评论
为什么被折叠?



