动手学深度学习第二章 2.1~2.3 节学习
希望您们先看过书,再来看本 blog,否则可能会看不懂,原因是我只写书上没写或者很新奇的东西。
先引个 torch 包
import torch
2.1 数据操作
2.1.1 入门
x.numel()
会返回张量中元素的总数。
torch.tensor()
与 torch.Tensor()
的区别
-
torch.tensor()
是(当你未指定 dtype 的类型时)将 data 转化为torch.FloatTensor
、torch.LongTensor
、torch.DoubleTensor
等类型,转化类型依据于 data 的类型或者 dtype 的值-
直接使用
vec = torch.tensor()
会报错,可以考虑如下方案修改:-
vec = torch.Tensor()
-
vec = torch.tensor([])
-
-
-
torch.Tensor()
是torch.FloatTensor()
的别名
# vec = torch.tensor()
# TypeError: tensor() missing 1 required positional arguments: "data"
vec = torch.Tensor()
vec
tensor([])
vec = torch.tensor([])
vec
tensor([])
从下面代码可以看出,两种 tensor
函数都是开辟了新的内存空间建立张量。而且 Tensor()
函数直接转化为了浮点数。
a = [1, 2]
ida = id(a)
va = torch.Tensor(a)
idva = id(va)
va2 = torch.tensor(a)
idva2 = id(va2)
a, va, va2, ida, idva, idva2
([1, 2],
tensor([1., 2.]),
tensor([1, 2]),
140242979567568,
140242979895440,
140242979896976)
torch.randn((n, m))
会创建一个形状为 \(n \times m\) 的张量,其中每个元素都从均值为 0,标准差为 1 的标准高斯分布(正态分布)中随机采样。
torch.randn((3, 4))
tensor([[ 0.2106, 1.6204, 2.3458, -0.0705],
[-0.2921, -0.1707, 0.1801, -1.0608],
[ 0.0082, -1.4975, 0.2525, -0.2153]])
2.1.2 运算符
torch.cat()
函数中,\(dim=0\) 是按照行作为轴拼接,\(dim=1\) 是按照列作为轴拼接。可以简单地将其想象为吸附在轴上。
这里可以写成 (X, Y)
或者 [X, Y]
都可以,不过官方文档以及 d2l 书中都写的 (X, Y)
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]])
X, Y, 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.]]),
tensor([[2., 1., 4., 3.],
[1., 2., 3., 4.],
[4., 3., 2., 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.]]))
2.1.3 广播机制
-
首先,两个张量的维度必须相等(张量的维度可以理解为张量轴的个数),对于维度没有要求,除了要 \(\ge 2\)
-
如果两个某一维的长度不相等,那么必定存在一个张量这一维大小等于 1.
-
然后复制后,进行广播
2.1.4 索引和切片
两个有趣的写法:
-
X[1,2] = 9
使得 \(x_{1,2} = 9\)。梦回 pascal 时代。 -
X[0:2, :] = 12
使得前两行的所有列都赋值为 12.
2.1.5 节省内存
如果调用语句 Y = X + Y
,那么将取消引用 Y 指向的张量,而是指向新分配的内存处的张量。
本书作者认为这种元素的操作改变内存地址不可取,原因有二:
-
不想总是不必要地分配内存
-
如果不原地更新,其他引用仍然会指向旧的内存位置,这样某些代码可能会无意中引用旧的参数。
2.1 例如如果做
Z = Y
这样地操作,那么仅仅只是将 Z 指向的位置改到和 Y 一样,而没有对 Y 的内容进行复制。
执行原地操作的方法也有两种:
-
Y[:] = <expression>
-
Y += X
另外,reshape()
也修改了内存地址。
2.1.6 转化
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
(tensor([3.5000]), 3.5, 3.5, 3)
2.2 数据预处理
这一节主要教我们怎么用 pandas 包。但是我总觉得没啥用啊
一些有趣的函数
-
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
,通过位置索引获得 DataFrame 实例的值。 -
inputs = inputs.fillna(inputs.mean())
,把 inputs 中的所有 NaN 替换为 inputs 的平均值。 -
inputs = pd.get_dummies(inputs, dummy_na=True)
,这个函数将字符串类型的都改为 one-hot 类型,最后一个形参的意思是是否 NaN 也计算在内的意思。 -
newdata = data.drop(data.isna().sum().idxmax(), axis=1)
,这个语句可以删除缺失值最多的列。
2.3 线性代数
2.3.1 标量
跳过(233)
2.3.2 向量
向量用小写粗体表示,而元素是一个标量,因此最好不要用粗体表示元素。
这里维度这个词的明确很好:向量或轴的维度被用来表示向量或轴的长度,即向量或轴的元素数量。而张量的维度用来表示张量具有的轴数。在这个意义上,张量的某个轴的维数就是这个轴的长度。
2.3.3 矩阵
矩阵用大写粗体 \(\boldsymbol{A}\) 表示。其中的元素有两种表示方法:
-
\([\boldsymbol{A}]_{ij}\),在不清楚 \([\boldsymbol{A}]_{ij}\) 是否为标量时可以使用。
-
\(a_{ij}\),当已知 \([\boldsymbol{A}]_{ij}\) 为标量时可以使用。
另附 markdown 加粗倾斜指令 \boldsymbol{}
2.3.4 张量
张量用特殊字体的大写字母表示,如 \(\sf{X, Y, Z}\),另附 markdown 等线体指令 \sf{}
,表示张量元素的机制和矩阵类似。
不妨假设有一个张量 \(\sf{X}\),它的形状为 \((3,4,5)\),那么,调用 len(X)
将会返回 3,原因是它只会返回第一维的长度,而 X.shape
则会返回其整体的形状。
2.3.5 张量算法的基本性质
给定具有相同形状的任意两个张量,任意按元素二元运算的结果都将是相同形状的张量。
另外,作者举了一个有趣的例子,两个矩阵的按元素乘法称为哈达玛积(Hadamard product)(数学符号为 \(\odot\))。
2.3.6 降维
降维求和
某些函数除了自己的主要作用外,还具有降维的第二作用。假如现在有个张量 A = torch.arange(20, dtype=torch.float32).reshape(5,4)
,所以它有几个例子函数具有降维功能:
-
A.sum()
-
A.mean()
其中,它们都有一个参数 axis
,表示消除哪一个轴,沿着哪一个轴做操作。
也有神奇的用法比如 A.sum(axis=[0,1])
,该操作与 A.sum()
等价。
非降维求和
可以在做操作时保持轴数不变,sum_A = A.sum(axis=1, keepdims=True)
,这样可以接着做操作,比如广播。
如果想沿某个轴计算其元素的累积综合,可以调用 cumsum()
函数。
A = torch.arange(20, dtype=torch.float32).reshape(5,4)
A.cumsum(axis=0)
tensor([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]])
2.3.7 点积
两种方式:
-
torch.dot(x, y)
-
torch.sum(x * y)
2.3.8 矩阵-向量积
函数为:torch.mv(A, x)
。很有趣,这里给出了简写很棒啊,而且也更加安全一些,因为这个函数没有广播机制。
2.3.9 矩阵-矩阵乘法
函数为:torch.mm(A, B)
,这个函数同样没有广播机制。
另附一下 torch.matmul(input, other, *, out=None)
的内容:
-
如果两个张量都是一维的,将返回点乘(标量)。
-
如果两个参数都是二维的,则返回矩阵-矩阵乘积。
-
如果第一个参数是一维的,第二个参数是二维的,为了进行矩阵乘法,会在其维度上预加一个1。矩阵乘法后,预置的维度将被移除。
-
如果第一个参数是二维的,第二个参数是一维的,则返回矩阵-向量乘积。
-
如果两个参数都是至少一维的,并且至少有一个参数是 N 维的(其中 N>2),那么将返回一个分批的矩阵乘法。如果第一个参数是一维的,为了进行分批矩阵乘法,会在其维度上预加一个 1,然后删除。如果第二个参数是一维的,那么在它的维度上加一个 1,以便进行分批矩阵乘法,之后再删除。非矩阵(例如批)的维度是广播的(因此一定是可广播的)。例如,如果
input
是一个 \((j×1×n×n)\) 张量,other
是一个 \((k×n×n)\) 张量,out
将是一个 \((j×k×n×n)\) 张量。请注意,广播逻辑在确定输入是否可广播时只看批维度,而不看矩阵维度。例如,如果
input
是一个 \((j×1×n×m)\) 张量,other
是一个 \((k×m×p)\) 张量,这些输入对于广播是有效的,即使最后两个维度(即矩阵维度)不同,输出将是一个 \((j×k×n×p)\) 张量。这个操作对具有稀疏布局的参数有支持。特别是矩阵-矩阵(两个参数都是2维的)支持稀疏的参数,其限制与torch.mm()
相同
2.3.10 范数
范数有如下三个性质,不妨设此时有一个向量 \(\boldsymbol{x}\),它的范数为 \(f(\boldsymbol{x})\):
-
\(f(\alpha \boldsymbol{x}) = |\alpha|f(\boldsymbol{x})\)
-
\(f(\boldsymbol{x} + \boldsymbol{y}) \le f(\boldsymbol{x}) + f(\boldsymbol{y}))\)
-
\(f(\boldsymbol{x}) \ge 0\)
于是原书中提到几种范数:
-
\(L_1\) 范数:\(||\boldsymbol{x}||_1 = \sum_{i=1}^n |x_i|\),相应的代码为
torch.abs(x).sum()
-
\(L_2\) 范数:\(||\boldsymbol{x}||_2 = \sqrt{\sum_{i=1}^n x_i^2}\),相应的代码为
torch.norm(x)
-
弗罗贝尼乌斯范数(Frobenius norm):这是一个矩阵 \(\boldsymbol{X}\) 的范数 \(||\boldsymbol{X}||_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}\),它是矩阵所有元素的平方和的平方根。
直接用
torch.norm(X)
也可以求这个值,这个函数也可以指定以哪一位做范数,参数为 dim。