《动手学深度学习》第二章地址:https://zh-v2.d2l.ai/chapter_preliminaries/index.html
2.1 练习
- 运行本节中的代码。将本节中的条件语句
X == Y
更改为X < Y
或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, X<Y
结果
(tensor([[False, False, False, False],
[ True, True, True, True],
[ True, True, True, True]]),
tensor([[ True, False, True, False],
[False, False, False, False],
[False, False, False, False]]))
- 用其他形状(例如三维张量)替换广播机制中按元素操作的两个张量。结果是否与预期相同?
代码
a = torch.arange(6).reshape((3, 1, 2))
b = torch.arange(2).reshape((1, 2, 1))
c = a + b
a.shape, b.shape, c.shape
结果
(torch.Size([3, 1, 2]), torch.Size([1, 2, 1]), torch.Size([3, 2, 2]))
可以看到结果与预期相同
2.2 练习
创建包含更多行和列的原始数据集。
生成数据
import os
os.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,Size\n') # 列名,新增一列为Size
f.write('NA,Pave,127500,100\n') # 每行表示一个数据样本
f.write('2,NA,106000,150\n')
f.write('4,NA,178100,200\n')
f.write('NA,NA,140000,NA\n')
f.write('3,Pave,NA,NA\n') # 新增一行
- 删除缺失值最多的列。
代码
import pandas as pd
data = pd.read_csv(data_file)
data.isna().sum(axis=0) # 查看缺失值最多的列, 缺失值最多的为Alley列
data.drop('Alley', axis=1, inplace=True)
结果
缺失值个数统计:
NumRooms 2
Alley 3
Price 1
Size 2
dtype: int64
删除缺失值最多的列后的数据
NumRooms Price Size
0 NaN 127500.0 100.0
1 2.0 106000.0 150.0
2 4.0 178100.0 200.0
3 NaN 140000.0 NaN
4 3.0 NaN NaN
- 将预处理后的数据集转换为张量格式
预处理的话,我们考虑前向后向填充,将缺失值填补上
代码
data = data.fillna(method='bfill')
data = data.fillna(method='ffill')
torch.tensor(data.values)
结果
tensor([[2.0000e+00, 1.2750e+05, 1.0000e+02],
[2.0000e+00, 1.0600e+05, 1.5000e+02],
[4.0000e+00, 1.7810e+05, 2.0000e+02],
[3.0000e+00, 1.4000e+05, 2.0000e+02],
[3.0000e+00, 1.4000e+05, 2.0000e+02]], dtype=torch.float64)
2.3 练习
- 证明一个矩阵 的转置的转置是,即 。
对于中的一个元素aij,其在第 i 行第 j 列,转置后出现在第 j 行第 i 列,再转置一次就出现在第 i 行第 j 列,又回到了原来的位置。所以,一个矩阵转置的转置还是它自己。
代码
import numpy as np
A = np.arange(12).reshape(3, 4)
A.T.T == A
结果
array([[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]])
- 给出两个矩阵 和 ,证明“它们转置的和”等于“它们和的转置”,即 。
假设第 i 行第 j 列的元素是 aij,第 i 行第 j 列的元素是 bij。对于 ,其第 j 行第 i 列的值为 aij + bij;对于 ,其第 j 行第 i 列的值也为 aij + bij,推广至即可知。
代码
import numpy as np
A = np.arange(12).reshape(3, 4)
B = np.arange(12, 24).reshape(3, 4)
(A.T + B.T) == (A + B).T
结果
array([[ True, True, True],
[ True, True, True],
[ True, True, True],
[ True, True, True]])
- 给定任意方阵 , 总是对称的吗?为什么?
是的,首先我们看一下对称矩阵的概念:
“ 等于其转置:,则称为对称矩阵”。将 看做一个整体,证明如下:
- 我们在本节中定义了形状 (2,3,4) 的张量。的输出结果是什么?
代码
X = torch.arange(24).reshape(2, 3, 4)
len(X)
结果
2
输出的是0轴的数值。
- 对于任意形状的张量是否总是对应于X特定轴的长度?这个轴是什么?
总对应第 0 轴的长度,上题已回答。
- 运行A/A.sum(axis=1),看看会发生什么。你能分析原因吗?
会出错,原因在于A为5行4列的矩阵,维度为2,而A.sum(axis=1)
则为长度为5的向量,二者无法相除,需要维度一致才会有广播机制,也就是我们需要设置keepdims=True
- 考虑一个具有形状 (2,3,4) 的张量,在轴0、1、2上的求和输出是什么形状?
课程中已讲过,对哪个轴求和,可以看作是压缩该维度,因此
轴0求和——>[3,4]
轴1求和——>[2,4]
轴2求和——>[2,3]
下面用代码验证一下
代码
X = torch.arange(24).reshape(2, 3, 4)
X.sum(axis=0).shape, X.sum(axis=1).shape, X.sum(axis=2).shape
结果
(torch.Size([3, 4]), torch.Size([2, 4]), torch.Size([2, 3]))
- 为linalg.norm函数提供3个或更多轴的张量,并观察其输出。对于任意形状的张量这个函数计算得到什么?
计算得到张量中所有值的 L2范数。
numpy.linalg.norm
参数ord
默认为None
,此时对应的即为L2范数,见下表
ord | norm for matrices | norm for vectors |
---|---|---|
None | Frobenius norm | 2-norm |
2.4 练习
书中的函数此处不再赘述
- 绘制函数 和其在 处切线的图像。
先求导数: 可得时,,又因为
因此切线为
代码
x = np.arange(0.5, 1.5, 0.01)
plot(x, [x**3 - 1/x, 4*x-4], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
结果
- 求函数 的梯度。
- 函数的梯度是什么?
先对求导,可得:
同理类推:可得
因此其梯度为:[, ,……,]
简写为:
- 你可以写出函数 ,其中 ,, 的链式法则吗?
2.5 练习
- 为什么计算二阶导数比一阶导数的开销要更大?
因为计算二阶导数要在一阶导数的基础上再进行求导,开销肯定会更大。
- 在运行反向传播函数之后,立即再次运行它,看看会发生什么。
会发生运行错误,因为前向过程建立的计算图,会在反向传播后释放,所以第二次运行反向传播就会出错。这时在 backward 函数中加上参数 retain_graph=True
(保持计算图),就能两次运行反向传播了。
代码
x = torch.arange(12., requires_grad=True)
y = 2 * torch.dot(x, x)
y.backward()
x.grad # tensor([ 0., 4., 8., 12., 16., 20., 24., 28., 32., 36., 40., 44.])
y.backward() # 会报错
- 在控制流的例子中,我们计算d关于a的导数,如果我们将变量a更改为随机向量或矩阵,会发生什么?
会发生运行错误,此处想说明的其实是pytorch中张量无法对张量求导,只能是标量对张量求导,因此如果我们将a修改为向量或矩阵,直接backward将会报错,需要对结果求和成一个标量后方能求导。
代码
a = torch.randn(size=(3,1), requires_grad=True)
d = f(a)
d.backward() # 直接运行这一步会报错
d.sum().backward() # 求和以后运行就没问题了
- 重新设计一个求控制流梯度的例子,运行并分析结果。
我们设计一个范数如果大于10,则返回梯度为2,否则返回梯度为1。以下为代码和结果
代码
def f(a):
if a.norm() > 10:
b = torch.dot(a, a)
else:
b = a
return b.sum()
a1 = torch.arange(12., requires_grad=True)
a2 = torch.arange(2., requires_grad=True)
d1 = f(a1)
d2 = f(a2)
d1.backward()
d2.backward()
a1.grad, a2.grad
结果
(tensor([ 0., 2., 4., 6., 8., 10., 12., 14., 16., 18., 20., 22.]), # 范数大于10时,梯度为2
tensor([1., 1.])) # 范数小于10时,梯度为1
- 使 f(x)=sin(x) ,绘制 f(x) 和 df(x)dx 的图像,其中后者不使用 f′(x)=cos(x) 。
代码
x = torch.linspace(-5, 5, 100)
x.requires_grad_(True)
y = torch.sin(x)
y.sum().backward()
y = y.detach()
d2l.plot(x.detach(), [y, x.grad], 'f(x)', "f'(x)", legend=['f(x)', 'Tangent line']) # 不采用cos(x),而使用x.grad
d2l.plt.show()
结果
2.6 练习
- 进行组实验,每组抽取个样本。改变和,观察和分析实验结果。
书中已有的结果,此处将换为,换为,查看结果
代码
import torch
from torch.distributions import multinomial
from d2l import torch as d2l
fair_probs = torch.ones([6]) / 6
counts = multinomial.Multinomial(20, fair_probs).sample((5000,))
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()
结果
m=500,n=10 | m=5000,n=20 |
---|---|
对比以上结果可知,第二次的效果更好,更加趋近于 0.167。原因可能是第一张图实验组数为 500 组,而第二张图有 5000 组,实验组数越多,实验偏差就会分布的更加平均,效果就会更好。(此处对比试验最好将第二张图的n也设置为10,不过限于篇幅就不再展示了~大家可以下来多尝试)
- 给定两个概率为的事件,计算、的上限和下限。
上限为,下限为
上限 | 下限 |
---|---|
上限为,下限为
上限 | 下限 |
---|---|
- 假设我们有一系列随机变量,例如 、 和 ,其中 只依赖于 ,而 只依赖于 ,你能简化联合概率 吗?
根据题意可得:满足马尔科夫链 下面开始推导
其中 =====>马尔科夫链
因此最终化简为:
- 在 2.6.2.6节中,第一个测试更准确。为什么不运行第一个测试两次,而是同时运行第一个和第二个测试?
原因是如果进行两次第一种测试,两次测试没有条件独立性,不能使用上面的公式进行计算,而且两次测试结果大概率相同,效果并不好。