[动手学深度学习PyTorch笔记]——数据操作

  • 1 引言
  • 2 预备知识
    • 2.1 数据操作
      • 2.1.1 入门
      • 2.1.2 运算符
      • 2.1.3 广播机制
      • 2.1.4 索引和切片
      • 2.1.5 节省内存
      • 2.1.6 转换为其他python对象
      • 2.1.7 总结
    • 2.2 数据预处理
      • 2.2.1 读取数据集
      • 2.2.2 处理缺失值
      • 2.2.3 转换为张量格式
      • 2.2.4 总结
    • 2.3 线性代数
      • 2.3.1 标量
      • 2.3.2 向量
      • 2.3.3 矩阵
      • 2.3.4 张量
      • 2.3.5 张量算法的基本性质
      • 2.3.6 降维
      • 2.3.7 点积(Dot Product)
      • 2.3.8 矩阵-向量积
      • 2.3.9 矩阵乘法
      • 2.3.10 范数
      • 2.3.11 总结
    • 2.4 微积分
      • 2.4.1 导数和微分
      • 2.4.2 偏导数
      • 2.4.3 梯度
      • 2.4.4 链式法则
      • 2.4.5 总结
    • 2.5 自动求导
      • 2.5.1 一个简单的例子
      • 2.5.2 分离计算
      • 2.5.3Python控制流的梯度计算
      • 2.5.4 总结

1 引言

1.机器学习研究计算机系统如何利用经验(通常是数据)来提高特定任务的性能。它结合了统计学、数据挖掘和优化的思想。通常,它是被用作实现人工智能解决方案的一种手段。
2.表示学习作为机器学习的一类,其研究的重点是如何自动找到合适的数据表示方式。深度学习是通过多层次的转换来进行的多层次的表示学习。
3.深度学习不仅取代了传统机器学习的浅层模型,而且取代了劳动密集型的特征工程。
4.最近在深度学习方面取得的许多进展,大都是由廉价传感器和互联网规模应用所产生的大量数据,以及(通过GPU)算力的突破来触发的。
5.整个系统优化是获得高性能的关键环节。有效的深度学习框架的开源使得这一点的设计和实现变得非常容易。

2 预备知识

2.1 数据操作

为了能够完成各种数据操作,我们需要某种方法来存储和操作数据。通常我们需要做两件重要的事情:
(1)获取数据;
(2)将数据读入计算机后对其进行处理。

首先,介绍n维数组,也称张量(tensor)。无论使用哪个深度学习框架,它的张量类斗鱼Numpy的ndarray类似。但深度学习框架又比Numpy的ndarray多一些重要功能:首先,GPU很好地支持加速计算,而Numpy仅支持CPU计算;其次,张量类支持自动微分。这些功能使得张量类更适合深度学习。

2.1.1 入门

首先,导入torch。虽然它被称为PyTorch,但是代码中使用torch而不是pytorch。

import torch

张量表示⼀个由数值组成的数组,这个数组可能有多个维度。具有⼀个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix);具有两个轴以上的张量没有特殊的数学名称。

创建一个行向量X,包含以0开始的12个整数。

x = torch.arange(12)

张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个 元素。除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。

可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状。

x.shape

想知道张量中元素的总数,即形状的所有元素乘积,可以用size检查它的大小。

x.size

改变张量形状而不改变数量和元素值,可以用reshape。两个参数中,可以用一个-1来调用此自动计算出维度的功能。

x = x.reshape(3,4)
x = x.reshape(-1,4)
x = x.reshape(3,-1)

有时,我们希望使⽤全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。我们可以创建 ⼀个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:

torch.zeros((2,3,4))

同样,我们可以创建⼀个形状为(2,3,4)的张量,其中所有元素都设置为1。代码如下:

torch.ones((2,3,4))

有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。例如,当我们构造数组来作 为神经⽹络中的参数时,我们通常会随机初始化参数的值。以下代码创建⼀个形状为(3,4)的张量。其中的 每个元素都从均值为0、标准差为1的标准⾼斯分布(正态分布)中随机采样。

torch.randn(3,4)

我们还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。在这里,最外层的列表对应于轴0,内层的列表对应于轴1。

torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])

2.1.2 运算符

常用的操作1:按元素(elementwise)运算。将标准标量运算符应用于数组的每个元素。对于任意具有相同形状的张量,常见的标准算数运算符(+,-,*,/,**)都可以被升级为按元素运算。

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 # **运算符是求幂运算
torch.exp(x)

常用的操作2:张量拼接(concatenate)。只需要给定张量以及确定沿哪个轴拼接。

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) # 轴0
torch.cat((x,y),dim=1) # 轴1

可以通过逻辑运算符构建二元张量。

x == y

对张量中的所有元素进行求和,会产生一个单元素张量。

x.sum()

2.1.3 广播机制

在张量形状不同时,我们可以调用广播机制来执行按元素操作。这种机制的工作方式如下:
1.通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
2.对生成的数组执行按元素操作。

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

由于a和b分别是3 × 1和1 × 2矩阵,如果让它们相加,它们的形状不匹配。我们将两个矩阵广播为⼀个更大的3 × 2矩阵,如下所示:矩阵a将复制列,矩阵b将复制行,然后再按元素相加。

a + b

2.1.4 索引和切片

就像在任何其他Python数组中⼀样,张量中的元素可以通过索引访问。与任何Python数组⼀样:第⼀个元素的索引是0,最后⼀个元素索引是-1;可以指定范围以包含第⼀个元素和最后⼀个之前的元素。

x[-1],x[1:3]

除读取外,也可通过指定索引将元素写入矩阵。

x[1,2] = 9

如果想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。

x[0:2,:] = 12

2.1.5 节省内存

运⾏⼀些操作可能会导致为新结果分配内存。例如,如果我们⽤Y = X + Y,我们将取消引⽤Y指向的张量,而是指向新分配的内存处的张量。

我们⽤Python的id()函数演示了这⼀点,它给我们提供了内存中引⽤对象的确切地址。运⾏Y = Y + X后,我们会发现id(Y)指向另⼀个位置。这是因为Python⾸先计算Y + X,为结果分配新的内存,然后使Y指向内存中的这个新位置。

before = id(Y)
Y = Y + X
id(Y) == before # False

这可能是不可取的,原因有两个:

  1. ⾸先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在⼀秒内多次更新所有参数。通常情况下,我们希望原地执⾏这些更新;
  2. 如果我们不原地更新,其他引⽤仍然会指向旧的内存位置,这样我们的某些代码可能会⽆意中引⽤旧的参数。
    那怎样执行原地操作呢?
    可以使⽤切⽚表⽰法将操作的结果分配给先前分配的数组。
Z = torch.zeros_like(Y)
print('id(Z):',id(Z)) # 139931132035296
Z[:] = X + Y
print('id(Z):',id(Z)) # 139931132035296

如果在后续计算中没有重复使用X,也可以使用X[:] = X + Y或X += Y来减少操作的内存开销。

before = id(X)
X = Y + X
id(X) == before # True

2.1.6 转换为其他python对象

将深度学习框架定义的张量转换为NumPy张量(ndarray)很容易,反之也同样容易。torch张量和numpy数组将共享它们的底层内存,就地操作更改⼀个张量也会同时更改另⼀个张量。

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)

2.1.7 总结

深度学习存储和操作数据的主要接口是张量(n维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他python对象。

2.2 数据预处理

为了能⽤深度学习来解决现实世界的问题,我们经常从预处理原始数据开始,⽽不是从那些准备好的张量格式数据开始。在Python中常⽤的数据分析⼯具中,我们通常使⽤pandas软件包。像庞⼤的Python⽣态系统中的许多其他扩展包⼀样,pandas可以与张量兼容。

2.2.1 读取数据集

举个例子,⾸先创建⼀个⼈⼯数据集,并存储在CSV(逗号分隔值)⽂件 …/data/house_tiny.csv中。

import osos.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”)。

# !pip install pandas
import pandasdata = pd.read_csv(data_file)
print(data)

2.2.2 处理缺失值

“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)
# result:
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)
# result:
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

2.2.3 转换为张量格式

现在inputs和outputs中的所有条⽬都是数值类型,它们可以转换为张量格式。

import torch
x , y = torch.tensor(inputs.values),torch.tensor(outputs.values)
x , y
# result:
(tensor([[3., 1., 0.],
[2., 0., 1.],
[4., 0., 1.],
[3., 0., 1.]], dtype=torch.float64),
tensor([127500, 106000, 178100, 140000])

2.2.4 总结

1.pandas软件包是Python中常用的数据分析工具,pandas可以与张量兼容。
2.用pandas处理缺失的数据时,可根据情况选择用插值法和删除法。

2.3 线性代数

2.3.1 标量

标量由只有⼀个元素的张量表⽰。下⾯的代码将实例化两个标量,并执⾏⼀些熟悉的算术运算,即加法、乘法、除法和指数。

import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x+y, x-y, x*y, x/y, x**y

2.3.2 向量

向量可以被视为标量值组成的列表。这些标量值被称为向量的元素(element)或分量(component)。当向量表⽰数据集中的样本时,它们的值具有⼀定的现实意义。⼈们通过⼀维张量表⽰向量。⼀般来说,张量可以具有任意⻓度,取决于机器的内存限制。

x = torch.arange(4)

在代码中,我们通过张量的索引来访问任⼀元素。

x[3]

与普通的Python数组⼀样,我们可以通过调⽤Python的内置len()函数来访问张量的⻓度。

len(x)

当⽤张量表⽰⼀个向量(只有⼀个轴)时,我们也可以通过.shape属性访问向量的⻓度。形状(shape)是⼀个元素组,列出了张量沿每个轴的⻓度(维数)。对于只有⼀个轴的张量,形状只有⼀个元素。

torch.size([4])

维度(dimension)这个词在不同上下⽂时往往会有不同的含义,这经常会使⼈感到困惑。为了清楚起⻅,我们在此明确⼀下:向量或轴的维度被⽤来表⽰向量或轴的⻓度,即向量或轴的元素数量。然⽽,张量的维度⽤来表⽰张量具有的轴数。在这个意义上,张量的某个轴的维数就是这个轴的⻓度。

2.3.3 矩阵

当调⽤函数来实例化张量时,我们可以通过指定两个分量m和n来创建⼀个形状为m × n的矩阵。

A = torch.arange(20).reshape(5,4)
A

我们可以通过⾏索引(i)和列索引(j)来访问矩阵中的标量元素。在代码中访问矩阵的转置:

A.T

矩阵是有⽤的数据结构:它们允许我们组织具有不同模式的数据。矩阵中的⾏可能对应于不同的数据样本,⽽列可能对应于不同的属性。因此,尽管单个向量的默认⽅向是列向量,但在表⽰表格数据集的矩阵中,将每个数据样本作为矩阵中的⾏向量更为常⻅。

2.3.4 张量

就像向量是标量的推⼴,矩阵是向量的推⼴⼀样,我们可以构建具有更多轴的数据结构。张量(本⼩节中的“张量”指代数对象)是描述具有任意数量轴的n维数组的通⽤⽅法。例如,向量是⼀阶张量,矩阵是⼆阶张量。

当我们开始处理图像时,张量将变得更加重要,图像以n维数组形式出现,其中3个轴对应于⾼度、宽度,以及⼀个通道(channel)轴,⽤于表⽰颜⾊通道(红⾊、绿⾊和蓝⾊)。

x = torch.arange(24).reshape(2,3,4)

2.3.5 张量算法的基本性质

标量、向量、矩阵和任意数量轴的张量(本⼩节中的“张量”指代数对象)有⼀些实⽤的属性。例如,从按元素操作的定义中可以注意到,任何按元素的⼀元运算都不会改变其操作数的形状。同样,给定具有相同形状的任意两个张量,任何按元素⼆元运算的结果都将是相同形状的张量。例如,将两个相同形状的矩阵相加,会在这两个矩阵上执⾏元素加法。

A = torch.arange(20,dtype = torch.float32).reshape(5,4)
B = A.clone()  # 通过分配新内存,将A的一个副本分配给B
A , A+B

两个矩阵的按元素乘法称为Hadamard积(Hadamard product)(数学符号⊙),其实就是对应位置元素相乘。

A*B

将张量乘以或加上⼀个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。

a = 2
x = torch.arange(24).reshape(2,3,4)
a + x, (a * x).shape

2.3.6 降维

计算张量中所有元素的和:

x = torch.arange(4,dtype=float32)
x,x.sum()

默认情况下,调⽤求和函数会沿所有的轴降低张量的维度,使它变为⼀个标量。我们还可以指定张量沿哪⼀个轴来通过求和降低维度。想要通过求和所有⾏的元素来降维(轴0),可以在调⽤函数时指定axis=0。由于输⼊矩阵沿0轴降维以⽣成输出向量,因此输⼊轴0的维数在输出形状中消失。指定axis=1将通过汇总所有列的元素降维(轴1)。因此,输⼊轴1的维数在输出形状中消失。沿着⾏和列对矩阵求和,等价于对矩阵的所有元素进⾏求和。

A_sum_axis0 = A.sum(axis=0)
A_sum_axis1 = A.sum(axis=1)
A.sum(axis=[0,1])

⼀个与求和相关的量是平均值(mean或average)。我们通过将总和除以元素总数来计算平均值。在代码中,
我们可以调⽤函数来计算任意形状张量的平均值。

A.mean(),A.sum() / A.numel()

同样,计算平均值的函数也可以沿指定轴降低张量的维度。

A.mean(axis=0),A.sum(axis=0) / A.shape[0]

非降维求和
有时在调⽤函数来计算总和或均值时保持轴数不变会很有⽤。

sum_A = A.sum(axis=1,keepdims=True)

如果我们想沿某个轴计算A元素的累积总和,⽐如axis=0(按⾏计算),可以调⽤cumsum函数。此函数不会沿任何轴降低输⼊张量的维度。

A.cumsum(axis=0)

2.3.7 点积(Dot Product)

点积的计算方式:
对于两个向量,其点积为相同位置元素乘积的和。

x = torch.ones(4,dtypes = torch.float32)
x, y, torch.dot(x,y)

也可以通过执行按元素乘法,然后进行求和来表示两个向量的点积:

torch.sum(x*y)

2.3.8 矩阵-向量积

在代码中使⽤张量表⽰矩阵-向量积,我们使⽤mv函数。当我们为矩阵A和向量x调⽤torch.mv(A, x)时,会执⾏矩阵-向量积。注意,A的列维数(沿轴1的⻓度)必须与x的维数(其⻓度)相同。

A.shape, x.shape, torch.mv(A,x)

2.3.9 矩阵乘法

在代码中使⽤张量表⽰矩阵乘法,我们使⽤mm函数。

B = torch.ones(4,3)
torch.mm(A,B)

矩阵乘法不应与”Hadamard积”混淆。

2.3.10 范数

线性代数中最有⽤的⼀些运算符是范数(norm)。⾮正式地说,向量的范数是表⽰⼀个向量有多⼤。这⾥考虑的⼤⼩(size)概念不涉及维度,⽽是分量的⼤⼩。

在线性代数中,向量范数是将向量映射到标量的函数fff。给定任意向量xxx,向量范数要满⾜⼀些属性。

第⼀个性质是:如果我们按常数因⼦α\alphaα缩放向量的所有元素,其范数也会按相同常数因⼦的绝对值缩放:
f(αx)=∣α∣f(x)f(\alpha x) = |\alpha|f(x)f(αx)=∣α∣f(x)第二个性质是熟悉的三角不等式:
f(x+y)≤f(x)+f(y)f(x+y)≤f(x)+f(y)f(x+y)≤f(x)+f(y)第三个性质简单说范数必须是非负的:
f(x)≥0f(x)≥0f(x)≥0这是有道理的。因为在⼤多数情况下,任何东西的最⼩的⼤⼩是0。最后⼀个性质要求范数最⼩为0,当且仅当向量全由0组成。
∀i,[x]i=0⇔f(x)=0∀i, [x]i = 0 ⇔ f(x) = 0∀i,[x]i=0⇔f(x)=0范数听起来很像距离的度量。欧⼏⾥得距离和毕达哥拉斯定理中的⾮负性概念和三⻆不等式可能会给出⼀些启发。事实上,欧⼏⾥得距离是⼀个l2l_2l2​范数:假设n维向量xxx中的元素是x1,...,xnx_1, . . . , x_nx1​,...,xn​,其l2l_2l2​范数是向量元素平⽅和的平⽅根:
∣∣x∣∣2=∑i=1nxi2||x||_2=\sqrt{\sum_{i=1}^{n}x_i^2}∣∣x∣∣2​=i=1∑n​xi2​​其中,在l2l_2l2​范数中常常省略下标2,也就是说∥x∥∥x∥∥x∥等同于∥x∥2∥x∥_2∥x∥2​。在代码中,我们可以按如下⽅式计算向量的l2l_2l2​范数。

u = torch.tensor([3.0,-4.0])
torm.norm(u)

深度学习中更经常地使⽤l2l_2l2​范数的平方,也会经常遇到l1l_1l1​范数,它表示为向量元素的绝对值之和:
∣∣x∣∣1=∑i=1n∣xi∣||x||_1=\sum_{i=1}^{n}|x_i|∣∣x∣∣1​=i=1∑n​∣xi​∣与l2l_2l2​范数相⽐,l1l_1l1​范数受异常值的影响较⼩。为了计算l1l_1l1​范数,我们将绝对值函数和按元素求和组合起来。

torch.abs(u).sum()

l2l_2l2​范数和l1l_1l1​范数都是更⼀般的lpl_plp​范数的特例:
∣∣x∣∣p=(∑i=1n∣xi∣p)1/p||x||_p=(\sum_{i=1}^{n}|x_i|^p)^{1/p}∣∣x∣∣p​=(i=1∑n​∣xi​∣p)1/p类似于向量的l2l_2l2​范数,矩阵X ∈ Rm×nR^{m×n}Rm×n的Frobenius范数(Frobenius norm)是矩阵元素平⽅和的平⽅根:
∣∣X∣∣F=∑i=1m∑j=1nxij2.||X||_F=\sqrt{\sum_{i=1}^{m}\sum_{j=1}^{n}x_{ij}^{2}.}∣∣X∣∣F​=i=1∑m​j=1∑n​xij2​.​Frobenius范数满⾜向量范数的所有性质,它就像是矩阵形向量的l2l_2l2​范数。调⽤以下函数将计算矩阵
的Frobenius范数。

torch.norm(torch.ones((4,9)))

注:ones()里一个括号和两个括号的结果一样。

2.3.11 总结

• 标量、向量、矩阵和张量是线性代数中的基本数学对象。
• 向量泛化⾃标量,矩阵泛化⾃向量。
• 标量、向量、矩阵和张量分别具有零、⼀、⼆和任意数量的轴。
• ⼀个张量可以通过sum和mean沿指定的轴降低维度。
• 两个矩阵的按元素乘法被称为他们的Hadamard积。它与矩阵乘法不同。
• 在深度学习中,我们经常使⽤范数,如L1L_1L1​范数、L2L_2L2​范数和Frobenius范数。
• 我们可以对标量、向量、矩阵和张量执⾏各种操作。

2.4 微积分

在深度学习中,我们“训练”模型,不断更新它们,使它们在看到越来越多的数据时变得越来越好。通常情况下,变得更好意味着最⼩化⼀个损失函数(loss function),即⼀个衡量“模型有多糟糕”这个问题的分数。最终,我们真正关⼼的是⽣成⼀个模型,它能够在从未⻅过的数据上表现良好。但“训练”模型只能将模型与我们实际能看到的数据相拟合。因此,我们可以将拟合模型的任务分解为两个关键问题:
• 优化(optimization):⽤模型拟合观测数据的过程;
• 泛化(generalization):数学原理和实践者的智慧,能够指导我们⽣成出有效性超出⽤于训练的数据集本⾝的模型。

2.4.1 导数和微分

假设我们有⼀个函数f:R→Rf : R → Rf:R→R,其输⼊和输出都是标量。如果f的导数存在,这个极限被定义为
f′(x)=lim⁡h→0f(x+h)−f(x)hf'(x)=\lim_{h→0}\frac{f(x+h)-f(x)}{h}f′(x)=h→0lim​hf(x+h)−f(x)​如果f′(a)f'(a)f′(a)存在,则称fff在aaa处是可微(differentiable)的。如果fff在⼀个区间内的每个数上都是可微的,则此函数在此区间中是可微的。我们可以将 导数f′(x)f'(x)f′(x)解释为f(x)f(x)f(x)相对于xxx的瞬时(instantaneous)变化率。所谓的瞬时变化率是基于xxx中的变化hhh,且hhh接近0。

定义u=f(x)=3x2−4xu=f(x)=3x^2-4xu=f(x)=3x2−4x如下:

import numpy as np
from matplotlib_inline import backend_inline
from d2l import torch as d2ldef f(x):return 3 * x ** 2 - 4 * x

通过令x = 1并让h接近0,会有f′(x)f'(x)f′(x)的数值接近于2;同样,当x = 1时,导数u′u^′u′是2。

def numerical_lim(f,x,h):return (f(x+h) - f(x)) / hh = 0.1
for i in range(5):print(f'h={h:.5f},numerical limit={numerical_lim(f,1,h):.5f}')h *= 0.1

当x = 1时,此导数也是曲线u = f(x)切线的斜率。

为了对导数的这种解释进⾏可视化,使⽤matplotlib,这是⼀个Python中流⾏的绘图库。要配置matplotlib⽣成图形的属性,我们需要定义⼏个函数。在下⾯,use_svg_display函数指定matplotlib软件包输出svg图表以获得更清晰的图像。
注意,注释#@save是⼀个特殊的标记,会将对应的函数、类或语句保存在d2l包中。因此,以后⽆须重新定义就可以直接调⽤它们(例如,2l.use_svg_display())。

def use_svg_display(): #@save'''使用svg格式显示绘图'''backend_inline.set_matplotlib_formats('svg')

定义set_figsize函数来设置图表⼤⼩。注意,这⾥可以直接使⽤d2l.plt,因为导⼊语句 from matplotlib
import pyplot as plt已标记为保存到d2l包中。

def set_figsize(figsize=(3.5,2.5)): #@save'''设置matplotlib图表的大小'''use_svg_display()d2l.plt.rcParams['figure.figsize'] = figsize

下⾯的set_axes函数⽤于设置由matplotlib⽣成图表的轴的属性。

#@save
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):"""设置matplotlib的轴"""axes.set_xlabel(xlabel)axes.set_ylabel(ylabel)axes.set_xscale(xscale)axes.set_yscale(yscale)axes.set_xlim(xlim)axes.set_ylim(ylim)if legend:axes.legend(legend)axes.grid()

通过这三个⽤于图形配置的函数,定义⼀个plot函数来简洁地绘制多条曲线。

def plot(X,Y=None,xlabel=None,ylabel=None,legend=None,xlim=None,ylim=None,xscale='linear',yscale='linear',fmts=('-','m--','g-.','r:'),figsize=(3.5,2.5),axes=None):"绘制数据点"if legend is None:legend = []set_figsize(figsize)axes = axes if axes else d2l.plt.gca()# 如果x有一个轴,输出Truedef has_one_axis(X):return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list) and not hasattr(X[0], "__len__"))if has_one_axis(X):X = [X]if Y is None:X,Y = [[]] * len(X),Xelif has_one_axis(Y):Y = [Y]if len(X) != len(Y):X = X * len(Y)axes.cla()for x,y,fmt in zip(X,Y,fmts):if len(X):axes.plot(x,y,fmt)else:axes.plot(y,fmt)set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)

现在可以绘制函数u=f(x)u = f(x)u=f(x)及其在x=1x = 1x=1处的切线y=2x−3y = 2x − 3y=2x−3,其中系数2是切线的斜率。

x = np.arange(0,3,0.1)
plot(x,[f(x),2*x-3],'x','f(x)',legend=['f(x)','Tangent line(x=1)'])

2.4.2 偏导数


这里的内容在高等数学中已经学过了,不做赘述。

2.4.3 梯度

设函数f:R→Rf:R→Rf:R→R的输入是一个n维变量x=[x1,x2,...,xn]Tx=[x_1,x_2,...,x_n]^Tx=[x1​,x2​,...,xn​]T,并且输出是一个标量。函数f(x)f(x)f(x)相对于xxx的梯度是一个包含n个偏导数的向量:
∇xf(x)=[∂f(x)∂x1,∂f(x)∂x2,...,∂f(x)∂xn]T\nabla_xf(x)=[\frac{\partial{f(x)}}{\partial x_1},\frac{\partial{f(x)}}{\partial x_2},...,\frac{\partial{f(x)}}{\partial x_n}]^T∇x​f(x)=[∂x1​∂f(x)​,∂x2​∂f(x)​,...,∂xn​∂f(x)​]T

2.4.4 链式法则

先考虑单变量函数。假设函数y = f(u)和u = g(x)都是可微的,根据链式法则:
dydx=dydududx\frac{dy}{dx}=\frac{dy}{du}\frac{du}{dx}dxdy​=dudy​dxdu​ 考虑⼀个更⼀般的场景,即函数具有任意数量的变量的情况。假设可微分函数yyy有变量u1,u2,...,umu_1, u_2, . . . , u_mu1​,u2​,...,um​,其
中每个可微分函数uiu_iui​都有变量x1,x2,...,xnx_1, x_2, . . . , x_nx1​,x2​,...,xn​。注意,yyy是x1,x2,...,xnx_1, x_2,...,x_nx1​,x2​,...,xn​的函数。对于任意i=1,2,...,ni = 1, 2, . . . , ni=1,2,...,n,链式法则给出:
∂y∂xi=∂y∂u1∂u1∂xi+∂y∂u2∂u2∂xi+...+∂y∂um∂um∂xi\frac{\partial y}{\partial x_i}=\frac{\partial y}{\partial u_1}\frac{\partial u_1}{\partial x_i}+\frac{\partial y}{\partial u_2}\frac{\partial u_2}{\partial x_i}+...+\frac{\partial y}{\partial u_m}\frac{\partial u_m}{\partial x_i}∂xi​∂y​=∂u1​∂y​∂xi​∂u1​​+∂u2​∂y​∂xi​∂u2​​+...+∂um​∂y​∂xi​∂um​​

2.4.5 总结

• 微分和积分是微积分的两个分⽀,前者可以应⽤于深度学习中的优化问题。
• 导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
• 梯度是⼀个向量,其分量是多变量函数相对于其所有变量的偏导数。
• 链式法则可以⽤来微分复合函数。

2.5 自动求导

深度学习框架通过⾃动计算导数,即⾃动微分(automatic differentiation)来加快求导。实际中,根据设计好的模型,系统会构建⼀个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产⽣输出。⾃动微分使系统能够随后反向传播梯度。这⾥,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。

2.5.1 一个简单的例子

假设我们想对函数y=2x⊤xy = 2x^⊤xy=2x⊤x关于列向量xxx求导。⾸先,我们创建变量xxx并为其分配⼀个初始值。

import torch
x = torch.arange(4.0)

在计算y关于x的梯度之前,需要⼀个地⽅来存储梯度。重要的是,我们不会在每次对⼀个参数求导时都分配新的内存。因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。注意,⼀个标量函数关于向量x的梯度是向量,并且与x具有相同的形状。

x.requires_grad_(True)
# 等价于x = torch.arange(4.0,requires_grad=True)
print(x.grad)

现在计算y:

y = 2 * torch.dot(x,x)

x是⼀个⻓度为4的向量,计算x和x的点积,得到了我们赋值给y的标量输出。接下来,通过调⽤反向传播函数
来⾃动计算y关于x每个分量的梯度,并打印这些梯度。

y.backward()
print(x.grad)

函数y=2x⊤xy = 2x^⊤xy=2x⊤x关于xxx的梯度应为4x4x4x。让我们快速验证这个梯度是否计算正确。

print(x.grad == 4 * x)

现在计算x的另⼀个函数。

#在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad

当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.grad

2.5.2 分离计算

有时,我们希望将某些计算移动到记录的计算图之外。例如,假设yyy是作为xxx的函数计算的,⽽zzz则是作为yyy和xxx的函数计算的。想象⼀下,我们想计算zzz关于xxx的梯度,但由于某种原因,希望将yyy视为⼀个常数,并且只考虑到xxx在yyy被计算后发挥的作⽤。

这⾥可以分离y来返回⼀个新变量uuu,该变量与y具有相同的值,但丢弃计算图中如何计算y的任何信息。换句话说,梯度不会向后流经u到x。因此,下⾯的反向传播函数计算z=u∗xz=u*xz=u∗x关于xxx的偏导数,同时将uuu作为常数处理,⽽不是z=x∗x∗xz=x*x*xz=x∗x∗x关于xxx的偏导数。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
print(x.grad == u)

由于记录了y的计算结果,我们可以随后在y上调⽤反向传播,得到y=x∗xy=x*xy=x∗x关于x的导数,即2*x。

x.grad.zero_()
y.sum().backward()
x.grad == 2 * x

2.5.3Python控制流的梯度计算

使用自动微分的一个好处是:使⽤⾃动微分的⼀个好处是:即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调⽤),我们仍然可以计算得到的变量的梯度。在下⾯的代码中,while循环的迭代次数和if语句的结果都取决于输⼊a的值。

def f(a):b = a * 2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn c

计算梯度:

a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()

现在可以分析上⾯定义的f函数。请注意,它在其输⼊a中是分段线性的。换⾔之,对于任何a,存在某个常量标量k,使得f(a)=k*a,其中k的值取决于输⼊a,因此可以⽤d/a验证梯度是否正确。

a.grad == d / a

2.5.4 总结

• 深度学习框架可以⾃动计算导数:我们⾸先将梯度附加到想要对其计算偏导数的变量上,然后记录⽬标值的计算,执⾏它的反向传播函数,并访问得到的梯度。
• 预备知识还有一部分概率的内容,这部分可以去参考茆诗松老师的《概率论与数理统计》。
pytorch中导入多项分布的软件包:

from torch.distributions import multinomial

[PyTorch笔记]数据操作相关推荐

  1. hive学习笔记-数据操作

    hive数据操作 hive命令行操作 hive -d --define <key=value> 定义一个key-value可以在命令行中使用 hive -d database <da ...

  2. 从零开始学Pytorch之数据操作

    import torchtorch.manual_seed(0) torch.cuda.manual_seed(0) print(torch.__version__) 输出pytorch的版本号 创建 ...

  3. (d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(2)前言(介绍各种机器学习问题)以及数据操作预备知识Ⅰ

    开源项目地址:d2l-ai/d2l-zh 教材官网:https://zh.d2l.ai/ 书介绍:https://zh-v2.d2l.ai/ 笔记基于2021年7月26日发布的版本,书及代码下载地址在 ...

  4. 《动手学深度学习 PyTorch版》学习笔记(一):数据操作

    一.数据操作 在PyTorch中,torch.Tensor是存储和变换数据的主要工具. "tensor"这个单词一般可译作"张量",张量可以看作是一个多维数组. ...

  5. (d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(3)前言(介绍各种机器学习问题)以及数据操作预备知识Ⅲ(概率)

    开源项目地址:d2l-ai/d2l-zh 教材官网:https://zh.d2l.ai/ 书介绍:https://zh-v2.d2l.ai/ 笔记基于2021年7月26日发布的版本,书及代码下载地址在 ...

  6. 【Pytorch神经网络基础理论篇】 03 数据操作 + 数据预处理

    1.数组样例 2.数据操作(代码实现与结果截图) #首先导入torch,虽然被称为pytorch,但是我们应该导入torch而不是pytorch import torch #张量表示一个数值组成的数组 ...

  7. 动手深度学习笔记(一)2.1数据操作

    动手深度学习笔记(一) 2. 预备知识 2.1. 数据操作 2.1.1. 入门 2.1.2. 运算符 2.1.3. 广播机制 2.1.4. 索引和切片 2.1.5. 节省内存 2.1.6. 转换为其他 ...

  8. OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co

    OpenCV学习笔记(四十一)--再看基础数据结构core 记得我在OpenCV学习笔记(四)--新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马 ...

  9. (pytorch-深度学习系列)pytorch数据操作

    pytorch数据操作 基本数据操作,都详细注释了,如下: import torch#5x3的未初始化的Tensor x = torch.empty(5, 3) print("5x3的未初始 ...

最新文章

  1. 亚马逊云服务(AWS)云原生自研处理器首次落地中国区域!
  2. loadrunner录制0事件_LoadRunner脚本异常捕获处理
  3. REACT map dictionary
  4. IDEA中启动SpringBoot项目时提示:“Error java:程序包xxx不存在”,但实际上是存在的
  5. 你可能不知道的 docker 命令的奇淫怪巧
  6. Axure教程 axure新手入门基础(2) 简单易上手
  7. 使用named_mutex实现读写锁,实现进程之间读共享写独占
  8. 三维数据平滑处理_关于CAD三维对象建模
  9. pandas后几行_天秀!Pandas还能用来写爬虫?
  10. 打开计算机打不开运行错误怎么办,注册表打不开,电脑出错注册表编辑器打不开怎么办?...
  11. Numpy——数组合并
  12. 【交易技术前沿】券商买方投顾系统建设实践
  13. 程序员转岗产品经理有哪些优势和劣势?
  14. 免费下载《WindowsPE权威指南》百度云
  15. EasyUI的基本使用布局
  16. Integer计算保留小数点位数
  17. 第119章 SQL函数 RIGHT
  18. 百度地图绘制工具类 DrawingManager.js 源码
  19. HTML/HTML5
  20. html 中 table tr td br 什么意思 是什么的缩写

热门文章

  1. NASA锂离子电池数据
  2. 用java自己实现String类的trim()方法功能
  3. 利用Python网络爬虫获取分类图片,简单处理反爬教学
  4. VS2019安装使用VS2015工具
  5. Setup和Hold(Max/Min)分析
  6. 001-Golang1.17源码分析之slice
  7. CCK-8,让细胞活性检测 So Easy! - MedChemExpress
  8. 帝国cms网站建设的基本流程
  9. 诛仙找回服务器,解决重启服务器回档
  10. 首次实战安卓App《第一行代码》欧酷天气遇到的一些问题总结