It has been 475 days since the last update, the content of the article may be outdated.

Ch2 预备知识

2.1数据操作

2.1.5 节省内存

  • ⽤X[:] = X + Y或X += Y来减少操作的内存开销
    python
    1
    2
    X[:] = X + Y
    X += Y

2.2 数据预处理

2.2.1 读取数据集

python
1
2
3
4
5
6
7
8
9
10
11
12
# 写入数据
import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv') # ../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')

# 读取数据集
import pandas as pd
data = pd.read_csv(data_file)

2.2.2 处理缺失值

python
1
2
inputs = inputs.fillna(inputs.mean()) # 同⼀列的均值替换“NaN”项
inputs = pd.get_dummies(inputs, dummy_na=True) # 自动将input的将此列转换为非NaN的值以及NaN,取值为0,1

2.2.3 转换为张量格式

python
1
2
3
import torch

X, y = torch.tensor(inputs.values), torch.tensor(outputs.values) # input,output都是数值条目

2.3 线性代数

2.3.2 向量

python
1
2
3
x[3] # 访问索引元素
len(x) # 长度
x.shape # 维度
  • 张量的维度用来表示张量具有的轴数

2.3.3 矩阵

python
1
2
A = torch.arange(20, dtype=torch.float32).reshape(5, 4) # 更改维度5×4
A.T # 转置

2.3.4 张量

python
1
X = torch.arange(24).reshape(2, 3, 4) # 2个3×4的矩阵

2.3.5 张量算法的基本性质

xxx

2.3.6 降维

Code
1
2
3
4
5
A = (tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
python
1
2
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
Code
1
(tensor([40., 45., 50., 55.]), torch.Size([4]))
python
1
2
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
Code
1
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
python
1
2
A.sum(axis=[0, 1]) # 结果和A.sum()相同
A.mean(axis=[0, 1]) # mean也有一样的语法

非降维求和

python
1
sum_A = A.sum(axis=1, keepdims=True)
Code
1
2
3
4
5
tensor([[ 6.],
[22.],
[38.],
[54.],
[70.]])
  • sum_A在对每⾏进⾏求和后仍保持两个轴, 直接除可以获得每一行自己的平均数,元素/该行总和
    python
    1
    A / sum_A 
  • 某个轴进行累积求和
    python
    1
    A.cumsum(axis=0)
    Code
    1
    2
    3
    4
    5
    tensor([[ 0., 1., 2., 3.],
    [ 4., 6., 8., 10.],
    [12., 15., 18., 21.],
    [24., 28., 32., 36.],
    [40., 45., 50., 55.]])

2.3.7 点积

python
1
2
torch.dot(x, y)
torch.sum(x * y)

2.3.8 矩阵-向量积

python
1
A.shape, x.shape, torch.mv(A, x)
Code
1
(torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))

2.3.9 矩阵-矩阵乘法

python
1
torch.mm(A, B) # 标准矩阵乘法,与Hadamard积(点对点乘积)不同

2.3.10 范数

  • L2 范数中常常省略下标2,也就是说 ||x|| 等同于 ||x||2
    $$
    |\mathbf{x}|2=\sqrt{\sum{i=1}^n x_i^2}
    $$
python
1
torch.norm(u)
  • L1 范数
    $$
    |\mathbf{x}|1=\sum{i=1}^n\left|x_i\right|
    $$
python
1
torch.abs(u).sum()
  • Lp 范数定义
    $$
    |\mathbf{x}|p=\left(\sum{i=1}^n\left|x_i\right|^p\right)^{1 / p}
    $$
  • Frobenius 范数
    • Frobenius范数满⾜向量范数的所有性质,它就像是矩阵形向量的 L2 范数。
      $$
      |\mathbf{X}|F=\sqrt{\sum{i=1}^m \sum_{j=1}^n x_{i j}^2}
      $$
python
1
torch.norm(torch.ones((4, 9)))

练习

1.证明⼀个矩阵 A 的转置的转置是A,即 (AT)T=A

  • 对于每一个元素定义易证

2.给出两个矩阵 AB ,证明“它们转置的和”等于“它们和的转置”,即AT+BT=(A+B)T

  • 对于每一个元素定义易证

3.给定任意⽅阵 AA+AT 总是对称的吗?为什么?

  • 对于每一个元素定义易证

4.本节中定义了形状(2; 3; 4)的张量X。len(X)的输出结果是什么?

python
1
2
3
X = torch.ones(2, 3, 4)

print(len(X))
Code
1
2

5.对于任意形状的张量X, len(X)是否总是对应于X特定轴的⻓度?这个轴是什么?

  • 对应shape的第一个轴的数目,axis=0

6.运⾏A/A.sum(axis=1),看看会发⽣什么。请分析⼀下原因?

  • 矩阵除以一维张量,要看矩阵列数是否等于张量的元素个数(也可以看作是列数)是否相等,如果相等就可以实现,将矩阵该列的所有元素除以对应列的张量元素。否则会报错
    python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    A = torch.arange(20).reshape(5, 4)

    print(A, A.shape)
    print(A.sum(axis=0), A.sum(axis=0).shape)
    print(A/A.sum(axis=0), (A/A.sum(axis=0)).shape)
    print(A.sum(axis=1), A.sum(axis=1).shape)
    print(A.sum(axis=1, keepdims=True), A.sum(axis=1, keepdims=True).shape)
    print(A/A.sum(axis=1, keepdims=True), (A/A.sum(axis=1, keepdims=True)).shape)
    print(A/A.sum(axis=1), (A/A.sum(axis=1)).shape)
    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    tensor([[ 0, 1, 2, 3], 
    [ 4, 5, 6, 7],
    [ 8, 9, 10, 11],
    [12, 13, 14, 15],
    [16, 17, 18, 19]]) torch.Size([5, 4])
    tensor([40, 45, 50, 55]) torch.Size([4])
    tensor([[0.0000, 0.0222, 0.0400, 0.0545],
    [0.1000, 0.1111, 0.1200, 0.1273],
    [0.2000, 0.2000, 0.2000, 0.2000],
    [0.3000, 0.2889, 0.2800, 0.2727],
    [0.4000, 0.3778, 0.3600, 0.3455]]) torch.Size([5, 4])
    tensor([ 6, 22, 38, 54, 70]) torch.Size([5])
    tensor([[ 6], [22], [38], [54], [70]]) torch.Size([5, 1])
    tensor([[0.0000, 0.1667, 0.3333, 0.5000],
    [0.1818, 0.2273, 0.2727, 0.3182],
    [0.2105, 0.2368, 0.2632, 0.2895],
    [0.2222, 0.2407, 0.2593, 0.2778],
    [0.2286, 0.2429, 0.2571, 0.2714]]) torch.Size([5, 4])

7.考虑⼀个具有形状(2; 3; 4)的张量,在轴0、1、2上的求和输出是什么形状?

  • 三维张量可以视作一个空间矩阵,然后分别沿着axis(第一个0,依次递增)进行降维求和,减少的是对应axis方向的维度。

python
1
2
3
4
5
6
X = torch.arange(24).reshape(2, 3, 4)

print(X, X.shape)
print(X.sum(axis=0), X.sum(axis=0).shape)
print(X.sum(axis=1), X.sum(axis=1).shape)
print(X.sum(axis=2), X.sum(axis=2).shape)
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
tensor([[[ 0, 1, 2, 3], 
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]]) torch.Size([2, 3, 4])
tensor([[12, 14, 16, 18],
[20, 22, 24, 26],
[28, 30, 32, 34]]) torch.Size([3, 4])
tensor([[12, 15, 18, 21],
[48, 51, 54, 57]]) torch.Size([2, 4])
tensor([[ 6, 22, 38],
[54, 70, 86]]) torch.Size([2, 3])

8.为linalg.norm函数提供3个或更多轴的张量,并观察其输出。对于任意形状的张量这个函数计算得到什么?

  • 本质就是在求每个元素的平方和的平方根结果。

python
1
2
3
4
X = torch.ones(2, 3, 4)
X = X.numpy()
print(X)
print(np.linalg.norm(X))
Code
1
2
3
4
5
6
7
8
9
[[[1. 1. 1. 1.] 
[1. 1. 1. 1.]
[1. 1. 1. 1.]]

[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]]
4.8989797
4.898979485566356

2.4 微积分

  • 将拟合模型的任务分解为两个关键问题:

    1. #优化(optimization) :⽤模型拟合观测数据的过程
    2. #泛化(generalization) :数学原理和实践者的智慧,能够指导我们⽣成出有效性超出⽤于训练的数据集本⾝的模型。

2.4.1 导数与微分

  • #@save是⼀个特殊的标记,会将对应的函数、类或语句保存在d2l包中
  • set_axes函数⽤于设置由matplotlib⽣成图表的轴的属性。
    python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #@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函数来简洁地绘制多条曲线
    python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #@save
    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有⼀个轴,输出True
    def 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), X
    elif 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)

2.4.2 偏导数

yxi=limh0f(x1,,xi1,xi+h,xi+1,,xn)f(x1,,xi,,xn)h.

  • 对于偏导数的表示,以下是等价的:
    yxi=fxi=fxi=fi=Dif=Dxif.

2.4.3 梯度

  • 设函数f:RnR输入是一个n维向量x=[x1,x2,,xn],并且输出是一个标量
  • 函数f(x)相对于x的梯度是一个包含n个偏导数的向量:
    xf(x)=[f(x)x1,f(x)x2,,f(x)xn]
  • 其中xf(x)通常在没有歧义时被f(x)取代。
  • 假设xn维向量,在微分多元函数时经常使用以下规则:
    1. 对于所有ARm×n,都有xAx=A
    2. 对于所有ARn×m,都有xxA=A
    3. 对于所有ARn×n,都有xxAx=(A+A)x
    4. x|x|2=xxx=2x
  • 同样,对于任何矩阵X,都有X|X|F2=2X

2.4.4 链式法则

练习

1.绘制函数y=f(x)=x31x和其在x=1处切线的图像。

python
1
2
3
4
5
6
def f(x):
    return x ** 3 - 1/x
   
x = np.arange(0.1, 3, 0.1)

plot(x, [f(x), 4*x-4], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])

2.求函数f(x)=3x12+5ex2的梯度。

xf(x)=[6x1,5ex2]

3.函数f(x)=|x|2的梯度是什么?

  • 二范数定义如下

$$
|\mathbf{x}|2=\sqrt{\sum{i=1}^n x_i^2}
$$

  • 证明如下

$$
\begin{equation}
\begin{aligned}
\nabla f(\mathbf{x}) &= \nabla |\mathbf{x}|2 \
& =\nabla \sqrt{\sum
{i=1}^n x_i^2} \
& =\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 \
& =\bigg[\frac{\partial \sqrt{\sum_{i=1}^n x_i^2} }{\partial x_1}, \frac{\partial \sqrt{\sum_{i=1}^n x_i^2} }{\partial x_2}, \ldots, \frac{\partial \sqrt{\sum_{i=1}^n x_i^2} }{\partial x_n}\bigg]^\top \
& = \frac{1}{2|\mathbf{x}|_2}\bigg[2(x_1), 2(x_2), \ldots, 2(x_n)\bigg]^\top \
& = \frac{1}{|\mathbf{x}|_2}\bigg[x_1, x_2, \ldots, x_n\bigg]^\top \
& = \frac{\mathbf{x}}{|\mathbf{x}|_2}
\end{aligned}
\end{equation}
$$

4.尝试写出函数u=f(x,y,z),其中x=x(a,b)y=y(a,b)z=z(a,b)的链式法则。

(1)ua=uxxa+uyya+uzza ua=uxxa+uyya+uzza

2.5 自动微分

2.5.0 深度学习框架

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

2.5.1 ⼀个简单的例⼦

python
1
2
3
4
5
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 默认值是None

y.backward() # 调用反向传播函数
x.grad # 对于x分量的梯度

2.5.2 ⾮标量变量的反向传播

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

2.5.3 分离计算

  • 将某些计算移动到记录的计算图之外
    • eg: 假设y是作为x的函数计算的,⽽z则是作为y和x的函数计算的。想象⼀下,我们想计算z关于x的梯度,但由于某种原因,希望将y视为⼀个常数
    • 分离y来返回⼀个新变量u,该变量与y具有相同的值,但丢弃计算图中如何计算y的任何信息
    • 梯度不会向后流经u到x
      python
      1
      2
      3
      4
      5
      6
      x.grad.zero_()
      y = x * x
      u = y.detach() # 分离计算,设置u变量,但仅使用值,不考虑如何计算的,从而避免x的反向传播
      z = u * x
      z.sum().backward()
      x.grad == u
      Code
      1
      tensor([True, True, True, True])

2.5.4 Python控制流的梯度计算

  • 使⽤⾃动微分的⼀个好处是:即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调⽤),仍然可以计算得到的变量的梯度

练习

  1. 为什么计算⼆阶导数⽐⼀阶导数的开销要更⼤?

    • 二阶相当于再复用一阶导数的计算
  2. 在运⾏反向传播函数之后,⽴即再次运⾏它,看看会发⽣什么。

    • 会报错RunTimeError
    • 两次backward
      xxx
  3. 在控制流的例⼦中,我们计算d关于a的导数,如果将变量a更改为随机向量或矩阵,会发⽣什么?

    • 会报错RunTimeError
    • 只支持标量的grad
      xxx
  4. 重新设计⼀个求控制流梯度的例⼦,运⾏并分析结果。

    • 暂无
  5. 使f(x)=sin(x),绘制f(x)df(x)dx的图像,其中后者不使用f(x)=cos(x)

    python
    1

2.6 概率

2.6.1 基本概率论

python
1
2
3
4
5
6
7
8
9
%matplotlib inline
import torch
from torch.distributions import multinomial
from d2l import torch as d2l

# 投骰子
fair_probs = torch.ones([6]) / 6 # 1-6的概率都是1/6
multinomial.Multinomial(1, fair_probs).sample() # 1次的模拟结果 tensor
multinomial.Multinomial(10, fair_probs).sample() # 10次的模拟结果 tensor
python
1
2
3
4
5
6
7
8
9
10
11
counts = multinomial.Multinomial(10, fair_probs).sample((500,))
cum_counts = counts.cumsum(dim=0)
estimates = cum_counts / cum_counts.sum(dim=1, keepdims=True)
d2l.set_figsize((6, 4.5))
for i in range(6):
d2l.plt.plot(estimates[:, i].numpy(),
label=("P(die=" + str(i + 1) + ")"))
d2l.plt.axhline(y=0.167, color='black', linestyle='dashed')
d2l.plt.gca().set_xlabel('Groups of experiments')
d2l.plt.gca().set_ylabel('Estimated probability')
d2l.plt.legend();
xxx
  • 概率论公理

    1. 概率非负性
    2. P(S) = 1 {S: Sample Space}
    3. 互斥事件概率为单个事件发生概率之和
  • 随机变量

    1. 离散随机变量
    2. 连续随机变量

2.6.2 处理多个随机变量

  • 联合概率 (Joint Probability)

  • 条件概率 (Conditional Probability)

P(B=b|A=a)

  • 乘法法则 (Multiplication Rule)

P(A;B)=P(B|A)P(A)

  • ⻉叶斯定理 (Bayes’ Theorem)

P(A|B)=P(B|A)P(A)P(B)

  • 边缘化 (Marginalization)

P(B)=AP(A,B)

  • 独⽴性 (Independence)

P(A,B)=P(A)P(B)

2.6.3 期望和⽅差

  • 期望 (Expectation)

E[X]=xxP(X=x)离散随机变量
E[X]=xxP(X=x)连续随机变量

  • 方差 (Variance)

Var[X]=E[(XE(X))2]=E(X2)E(X)2

练习

  1. 进行m=500组实验,每组抽取n=10个样本。改变mn,观察和分析实验结果。
    • Center Limit Theorem 中心极限定理
  2. 给定两个概率为P(A)P(B)的事件,计算P(AB)P(AB)的上限和下限。(提示:使用友元图来展示这些情况。)
    max(P(A),P(B))P(AB)P(A)+P(B)
    0P(AB)min(P(A),P(B)))
  3. 假设我们有一系列随机变量,例如ABC,其中B只依赖于A,而C只依赖于B,能简化联合概率P(A,B,C)吗?(提示:这是一个马尔可夫链。)
    (2)P(A,B,C)=P(C|B)P(B) =P(C|B)P(B|A)P(A)
  4. 在2.6.2节中,第一个测试更准确。为什么不运行第一个测试两次,而是同时运行第一个和第二个测试?
    • 也许是成本问题,只需要第二次的不精准测试,也可以大幅度提高推理在阳性反应下,患者真实患病的概率。同时可以避免,同种测试方法可能存在的误差和不为人知的错误。