参考资料:
2.1 数据操作
torch 里的 tensor 与 numpy 中的 ndarray 相比,能更好地支持 GPU 加速计算,且支持自动微分。
2.1.1 入门
创建一个张量:
# 全0张量
x = torch.zeros(2,3,4)
# 全1张量
x = torch.ones(2, 3, 4)
# 元素递增(reshape里的"-1"代表自动计算,即4)
x = torch.arange(12).reshape(3,-1)
# 从标准高斯分布中随机采样
x = torch.randn(3,4)
# 利用numpy的ndarray初始化张量
x = torch.tensor(numpty.arange(4))
输出张量的信息:
# 张量的形状
x.shape
# 张量中的元素总数(number of elements)
x.numel
2.1.2 运算符
常见的标准算术运算符(+
、-
、\*
、/
和\**
)和逻辑运算符(==
、>=
等)都可以被升级为按元素运算:
# x = torch.tensor([1, 2, 4, 8])
# y = torch.tensor([2, 2, 2, 2])
x == y
# Out: tensor([False, True, False, False])
# 所有元素变为e的指数
torch.exp(x)
我们也可以把多个张量连结在一起:
# x,y同上
torch.cat((x,y),dim=0)
# Out: tensor([1, 2, 4, 8, 2, 2, 2, 2])
# 注:默认列向量
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=1)
# Out: 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 广播机制
由于广播机制的存在,当参与运算的两个张量具有不同的形状时,也可用通过复制元素使得两个张量形状相同的方式得到运算结果:
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a + b
# Out:tensor([[0, 1],
# [1, 2],
# [2, 3]])
个人感觉广播机制不必深究,在实际编程时的应用都比较基础。
2.1.4 索引和切片
这部分和 Python 相似:“起点:终点:步长”(左闭右开)。
2.1.5 节省内存
将 Y = Y+X
替换成 Y[:] = Y+X
或 Y += X
。
2.2 数据预处理
2.2.1 读取数据集
pandas 的部分操作见: pandas自学笔记__DataFrame_MaTF_的博客-优快云博客
pd.get_dummies()可以用于实现独热编码:
print(inputs)
inputs = pd.get_dummies(inputs, dummy_na=True).astype("float64")
print(inputs)
# NumRooms Alley
# 0 NaN Pave
# 1 2.0 NaN
# 2 4.0 NaN
# 3 NaN NaN
# NumRooms Alley_Pave Alley_nan
# 0 NaN 1.0 0.0
# 1 2.0 0.0 1.0
# 2 4.0 0.0 1.0
# 3 NaN 0.0 1.0
2.2.2 转换为张量
如果 pd.DataFrame 中的所有条目均为数值类型,则可以转换成张量格式:
X, y = torch.tensor(inputs.values)
2.3 线性代数
2.3.1 张量
标量、向量、矩阵均可以看作特殊的张量。
# 得到张量的长度(第0维的长度)
len(x)
# 矩阵的转置(用在高维张量上会产生警告)
A.T
# 向量点积
torch.dot(x, y)
# 矩阵向量积
torch.mv(A, x)
# 矩阵乘法
torch.mm(A, B)
# 张量的矩阵乘法:用后面两维做矩阵乘法,前面的维度当作batch(可以广播)
a = torch.ones(2,1,3,4)
b = torch.ones(5,4,2)
c = torch.matmul(a,b)
c.shape
# Out: torch.size([2,5,3,2])
2.3.2 降维运算
降维运算包括 sum(),mean(),numel()
等,这里以 sum
为例:
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])
A_sum_axis1 = A.sum(axis=0)
A_sum_axis1, A_sum_axis1.shape
# Out: (tensor([40, 45, 50, 55]), torch.Size([4]))
sum()
中的 axis
代表沿哪个维度进行求和。
在某些情况下,我们希望使用 sum()
之类的函数,但却并不希望降维,则可以指定 keepdims=True
:
sum_A = A.sum(axis=1, keepdims=True)
print(sum_A.shape)
sum_A
# torch.Size([5, 1])
# tensor([[ 6],
# [22],
# [38],
# [54],
# [70]])
我们也可以使用 cumsum()
函数实现如“逐行相加”的操作:
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])
A.cumsum(axis=0)
# Out: tensor([[ 0, 1, 2, 3],
# [ 4, 6, 8, 10],
# [12, 15, 18, 21],
# [24, 28, 32, 36],
# [40, 45, 50, 55]])
2.3.3 范数
# 1范数
torch.abs(x).sum()
# 2范数
torch.norm(x)
2.4 自动微分
2.4.1 一个简单的例子
以 y = 2 x T x y=2x^Tx y=2xTx 对列向量 x x x 求导为例:
x = torch.arange(4.0)
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x, x)
y.backward()
x.grad
# Out: tensor([ 0., 4., 8., 12.])
# 注意:在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
2.4.2 向量的反向传播
参考 ARTS-S pytorch中backward函数的gradient参数作用 - 荷楠仁 - 博客园 (cnblogs.com)
backward()
中有一个参数 gradient
,我们可以这样理解:原本 backward()
函数只能作用于向量,使用 gradient
参数可以化张量为向量:
y
1
=
x
1
x
2
x
3
y
2
=
x
1
+
x
2
+
x
3
y
3
=
x
1
+
x
2
x
3
y
=
f
(
y
1
,
y
2
,
y
3
)
[
∂
y
∂
x
1
,
∂
y
∂
x
2
,
∂
y
∂
x
3
]
=
[
∂
y
∂
y
1
,
∂
y
∂
y
2
,
∂
y
∂
x
3
]
[
∂
y
1
∂
x
1
∂
y
1
∂
x
2
∂
y
1
∂
x
3
∂
y
2
∂
x
1
∂
y
2
∂
x
2
∂
y
2
∂
x
3
∂
y
3
∂
x
1
∂
y
3
∂
x
2
∂
y
3
∂
x
3
]
y_1=x_1x_2x_3\\ y_2=x_1+x_2+x_3\\ y_3=x_1+x_2x_3\\ y=f(y_1,y_2,y_3)\\ [\frac{\partial y}{\partial x_1},\frac{\partial y}{\partial x_2},\frac{\partial y}{\partial x_3}]=[\frac{\partial y}{\partial y_1},\frac{\partial y}{\partial y_2},\frac{\partial y}{\partial x_3}] \left[\begin{array}{l} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} & \frac{\partial y_1}{\partial x_3}\\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} & \frac{\partial y_2}{\partial x_3}\\ \frac{\partial y_3}{\partial x_1} & \frac{\partial y_3}{\partial x_2} & \frac{\partial y_3}{\partial x_3}\\ \end{array}\right]
y1=x1x2x3y2=x1+x2+x3y3=x1+x2x3y=f(y1,y2,y3)[∂x1∂y,∂x2∂y,∂x3∂y]=[∂y1∂y,∂y2∂y,∂x3∂y]
∂x1∂y1∂x1∂y2∂x1∂y3∂x2∂y1∂x2∂y2∂x2∂y3∂x3∂y1∂x3∂y2∂x3∂y3
在上面的公式中,
[
∂
y
∂
y
1
,
∂
y
∂
y
2
,
∂
y
∂
x
3
]
[\frac{\partial y}{\partial y_1},\frac{\partial y}{\partial y_2},\frac{\partial y}{\partial x_3}]
[∂y1∂y,∂y2∂y,∂x3∂y] 就是 gradient
,
y
y
y 就是转换后的标量。
# tensor([0., 1., 2., 3.])
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.backward(gradient=torch.tensor([1 ,2, 3, 4]))
x.grad
# tensor([ 0., 4., 12., 24.])
2.4.3 分离计算
设想这样一种情况: z = g ( x , y ) , y = f ( x ) z=g(x,y),\ y=f(x) z=g(x,y), y=f(x) ,假如我们希望在计算 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z 先将 y y y 看作常数,可以采用如下操作:
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
2.4.4 Python控制流的梯度计算
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()
2.5 概率
2.5.1 基本概率论
import torch
from torch.distributions import multinomial
from d2l import torch as d2l
使用 multinomial.Multinomial
,我们可以传入一个概率向量,输出是另一个相同长度的向量:它在索引 i
处的值是采样结果中 i
出现的次数:
multinomial.Multinomial(10, fair_probs).sample() # 10代表采样次数
# Out: tensor([2., 4., 1., 0., 1., 2.])