跳转至

动手学深度学习 第一第二章

废话笔记版

这b笔记感觉我自己不想再看第二遍,之后会再记得好一点的...

2. 预备知识

chapter_preliminaries

2.1 数据操作

chapter_ndarray

2.1.4 索引和切片

X[:, 0:2] # []内的值代表每个维度取多少

2.1.5 节省内存

Y = X + Y

这里 Y 的内存位置是被改变了的,我们不希望这样的事情发生,原因有两个:

  1. 首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
  2. 如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。

那么执行原地操作的方法是:使用切片的形式:

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

2.2 数据预处理

2.2.1 读取

每次用都要重新查的一句话

import pandas as pd

data = pd.read_csv(data_file)
print(data)

2.2.2 处理缺失值

  • 插值法
inputs = inputs.fillna(inputs.mean())
  • 删除法

2.2.3 转换为张量格式

转换成张量格式后可以使用2.1节讲的张量函数

import torch

X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))
X, y

2.3 线性代数

2.3.2 向量

tensor([0, 1, 2, 3])默认为列向量

其中 x[3]tensor(3)

  • 长度

len(x) >>> 4 返回第一个维度的长度

x.shape >>> torch.Size([4]),列表中是每个维度的长度

2.3.3 矩阵

A = torch.arange(20).reshape(5, 4)
  • 矩阵的转置:A.T

2.3.4 张量

一般用\(X, Y, Z\)表示

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

2.3.6 降维

求和函数可以沿轴求和减低维度

A.sum()
A_sum_axis0 = A.sum(axis=0) # 求和所有行的和
A_sum_axis1 = A.sum(axis=1) # 沿着列求和
A_sum = A.sum(axis=[0, 1]) # 沿着行和列求和等于全部求和

2.4 微积分

梯度是标量的梯度✍️✍️✍️

2.4.3 梯度

一些梯度计算公式

\[ \nabla_{\mathbf{x}} f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\bigg]^\top \]

在微分多元函数时经常使用以下规则:

  • 对于所有\(\mathbf{A} \in \mathbb{R}^{m \times n}\),都有\(\nabla_{\mathbf{x}} \mathbf{A} \mathbf{x} = \mathbf{A}^\top\)
  • 对于所有\(\mathbf{A} \in \mathbb{R}^{n \times m}\),都有\(\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} = \mathbf{A}\)
  • 对于所有\(\mathbf{A} \in \mathbb{R}^{n \times n}\),都有\(\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} \mathbf{x} = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}\)
  • \(\nabla_{\mathbf{x}} \|\mathbf{x} \|^2 = \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{x} = 2\mathbf{x}\)

2.5 自动微分

2.5.1 一个简单的例子

x = torch.arange(4.0)
x.require_grad_(True) # 等价于x=torch.arange(4.0,require_grad=True)
# 然后计算 y
y = 2 * torch.dot(x, x)
# 然后调用反向传播的函数,自动计算y关于x每个分量的梯度
y.backward()
x.grad # >>> tensor([0., 4., 8., 12.])
# y = 2 x^T x 
# 关于x的梯度为
x.grad == 4 * x # >>> True

\(y=2 x^T x\) 求导推导

\[ x^T x = x_1^2 + x_2^2 + ... + x_n^2 \]
\[ \frac{\partial y}{\partial x_i} = \frac{\partial}{\partial x_i} (2(x_1^2 + x_2^2 + ... + x_n^2)) = 2 \cdot (2x_i) = 4x_i \]
所以梯度$\nabla_x y(也可写做\frac{\partial y}{\partial x})$为:
\[ \frac{\partial y}{\partial \mathbf{x}} = \begin{bmatrix} \frac{\partial f}{\partial x_1} \\ ... \\ \frac{\partial f}{\partial x_n} \end{bmatrix} = \begin{bmatrix} 4x_1 \\ ...\\ 4x_n \end{bmatrix} = 4\mathbf{x} \]

然后我们计算x的另一个函数

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

关于梯度清零 x.grad.zero_()

调用backward()时,梯度不会覆盖在变量上而是 +=,这种做法可以节省显存

# 把 128 的大目标,拆成 4 次 32 的小目标
for i in range(4):
    计算 32 张图片的 loss
    loss.backward()  # 注意!这里不着急清零,让梯度加起来 (+=)

# 循环 4 次结束后,x.grad 里刚好累积了 128 张图片的完整梯度!
optimizer.step()     # 更新权重参数
optimizer.zero_grad() # 权重更新完了,这批梯度没用了,彻底清零,准备迎接下一个 128 张图片

2.5.2 非标量变量的反向传播

y不是标量时,向量y关于向量x求导会是一个高阶的张量,我们在机器学习中不太期望这个,所以我们把反向传播的对象转移到对于y的每个分量的和关于x求导,这样就是一个标量关于x的梯度了

# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

2.5.3 分离计算

可以把一个中间变量当成算出来的常量