书籍阅读-D2L

阅读李沐老师写的动手学深度学习,在这里进行笔记记录。

主要记录 Pytorch 代码,会长期更新。

第一章 引言

​ 一个典型的训练过程:

典型训练过程

​ 监督学习:在“给定输⼊特征”的情况下预测标签,每个“特征-标签”对都称为⼀个样本

​ 监督学习样例:回归、分类、标记、搜索、序列学习(语音识别、机器翻译)、推荐系统

​ 无监督学习:数据中不含有“⽬标”的机器学习问题

​ 无监督学习样例:聚类、主成分分析、⽣成对抗性⽹络 GAN

强化学习:

Agent在⼀系列的时间步骤上与环境交互。在每个特定时间点,智能体从环境接收⼀些观察(observation),并且必须选择⼀个动作(action),然后通过某种机制将其传输回环境,最后智能体从环境中获得奖励(reward)。此后新⼀轮循环开始,智能体接收后续观察,并选择后续操作。

强化学习与环境之间的相互作用

​ 神经网络的两个核心:

神经网络核心

最近⼗年,在统计模型、应⽤和算法⽅⾯的进展就像寒武纪⼤爆发——历史上物种⻜速进化的时期。

本章小结:

  • 机器学习研究计算机系统如何利⽤经验(通常是数据)来提⾼特定任务的性能。它结合了统计学、数据挖掘和优化的思想。通常,它是被⽤作实现⼈⼯智能解决⽅案的⼀种⼿段。

  • 表⽰学习作为机器学习的⼀类,其研究的重点是如何⾃动找到合适的数据表⽰⽅式。深度学习是通过学习多层次的转换来进⾏的多层次的表⽰学习。

  • 深度学习不仅取代了传统机器学习的浅层模型,⽽且取代了劳动密集型的特征⼯程。

  • 最近在深度学习⽅⾯取得的许多进展,⼤都是由廉价传感器和互联⽹规模应⽤所产⽣的⼤量数据,以及(通过GPU)算⼒的突破来触发的。

  • 整个系统优化是获得⾼性能的关键环节。有效的深度学习框架的开源使得这⼀点的设计和实现变得非常容易。

第二章 预备知识

torch 相关代码:

1
2
3
4
5
6
7
8
9
10
x.numel()#返回张量中元素总数
x.reshape(3,4)#把张量x转换为形状为(3,4)的矩阵
torch.zeros((2,3,4))#创建形状为(2,3,4)的张量,其中所有元素都设置为0。
torch.ones((2,3,4))#创建形状为(2,3,4)的张量,其中所有元素都设置为1。
torch.randn()#从均值为0、标准差为1的标准⾼斯分布中随机采样。
torch.cat((X, Y), dim=0)#将张量X,Y连接起来,变行不变列
torch.cat((X, Y), dim=1)#将张量X,Y连接起来,变列不变行
A = X.numpy()
B = torch.tensor(A)#将numpy转为tensor
B.item()#将tensor转为numpy

索引:

同 python,左闭右开,-1 表示最后一个。

X[0:2, :] :0:2 指第一第二行,: 指所有列。

数据预处理:

写入:

1
2
3
4
5
6
7
8
9
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\n') # 列名
f.write('NA,Pave,127500\n') # 每⾏表⽰⼀个数据样本
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')

读:

1
2
3
import pandas as pd
data = pd.read_csv(data_file)
print(data)

NaN 代表缺省值

线性代数:

代码:

1
2
3
4
5
6
7
8
9
10
下面,设置 A:tensor([[1,2,3,4],[5,6,7,8]]),
A.sum(axis=0)#沿列降维,生成输出向量,A.sum(axis=0)得到tensor([6,8,10,12])
A.sum(axis=1)#沿行降维,生成输出向量,A.sum(axis=0)得到tensor([10,26])
A.sum()#36,等同于A.sum(axis=[0,1])
A.mean()#求均值
A.sum(axis=1, keepdims=True)#非降维求和,得到得到tensor([[10],[26]])
A.cumsum(axis=0)#此函数不会沿任何轴降低输⼊张量的维度。

torch.dot(x,y)#算两个张量的点积,等同于torch.sum(x*y)
torch.mm(A,B)#计算矩阵A与矩阵B相乘

范数:

向量的范数表示⼀个向量 “有多大” 。范数要求非负,且听起来很像距离的度量。

L1 范数:

L1 范数,表示为向量元素的绝对值之和,受异常值影响较小:$||x||1=\sum^n{i=1}{|x^2_i|}$

1
torch.abs(u).sum()

L2 范数(更经常使用):

L2 范数是向量元素平⽅和的平⽅根:$||x||2=\sqrt{\sum^n{i=1}x^2_i}$

1
torch.norm(u)

还有 Frobenius 范数(Frobenius norm)是矩阵元素平⽅和的平⽅根。

在深度学习中,我们经常试图解决优化问题:最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。我们用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。目标,是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。

微积分:

代码:

1
2
3
4
5
6
7
8
9
# 微积分部分
x.requires_grad_(True)#x.grad默认为None
y = 2 * torch.dot(x, x)#y:tensor(28., grad_fn=<MulBackward0>)
y.backward()#⾃动计算y关于x每个分量的梯度,tensor([ 0., 4., 8., 12.])
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad

绘图:P66 - P67

2.5.2 P89 不是很好理解

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

概率论:

代码:

1
2
3
4
%matplotlib inline
import torch
from torch.distributions import multinomial
from d2l import torch as d2l

帮助:

1
2
3
import torch
print(dir(torch.distributions)) # 查询模块中的所有属性
help(torch.ones) # 查询如何使用给定函数或类的更具体说明

第三章 线性

线性回归

回归 ( regression ):为一个或多个自变量与因变量之间关系建模的方法,经常用来表示输入输出之间的关系。

一般线性回归模型:
$$
y = w_1x_1 + w_2x_2 +……+ w_dx_d+b
$$
w:权重 weight,决定每个特征对预测值的影响

b:偏置 bias ( offset )

上述公式严格来说,是输入特征的⼀个仿射变换(affine transformation)。特点是通过加权和对特征进行线性变换(linear transformation),并通过偏置项进行平移(translation)。

给定数据集,我们的目标是寻找模型的权重 w 和偏置 b ,使得模型做出的预测符合数据的真实价格。

所有特征放到向量 x 中,所有权重放到向量 w 中,我们可以用矩阵形式简洁地表达模型:
$$
y = W^TX + b
$$
其中,X每⼀行是⼀个样本,每⼀列是⼀种特征

损失函数

用于确定拟合程度。损失函数(loss function)能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,数值越小表示损失越小,完美预测时的损失为 0 。

解析解

线性回归的解可用公式表达出来,这类解叫作解析解(analytical solution)。线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。

随机梯度下降

可优化所有深度学习模型。通过不断地在损失函数递减的方向上更新参数来降低误差。

随机梯度下降大概步骤:

(1)随机初始化模型参数的值

(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这⼀步骤。

两个重要概念:batch size,每个小批量中样本数,学习率,

batch size 和学习率的值通常是手动预先指定,而不是通过模型训练得到的。这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。调参(hyperparameter tuning)是选择超参数的过程。超参数通常是我们根据训练迭代结果来调整的,⽽训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

矢量化

对计算进行矢量化,从而利用线性代数库,而不是在Python中编写开销高昂的for循环

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
32
33
%matplotlib inline
import math
import time
import numpy as np
import torch
from d2l import torch as d2l
n = 10000
a = torch.ones([n])
b = torch.ones([n])
class Timer: #@save
"""记录多次运⾏时间"""
def __init__(self):
self.times = []
self.start()

def start(self):
"""启动计时器"""
self.tik = time.time()
def stop(self):
"""停⽌计时器并将时间记录在列表中"""
self.times.append(time.time() - self.tik)
return self.times[-1]

def avg(self):
"""返回平均时间"""
return sum(self.times) / len(self.times)
def sum(self):
"""返回时间总和"""
return sum(self.times)

def cumsum(self):
"""返回累计时间"""
return np.array(self.times).cumsum().tolist()

这样,可以对工作负载进行基准测试。

矢量化代码通常会带来数量级的加速。另外,我们将更多的数学运算放到库中,而无须编写那么多的计算,从而减少了出错的可能性。

正态分布与平方损失

正态分布概率密度函数:$p(x)=\frac{1}{2\pi\sigma^2}e^{-\frac{1}{2\sigma^2}(x-\mu)^2}$

1
2
3
4
5
6
7
8
9
10
11
# 计算正态分布
def normal(x, mu, sigma):
p = 1 / math.sqrt(2 * math.pi * sigma**2)
return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)
# 再次使⽤numpy进⾏可视化
x = np.arange(-7, 7, 0.01)
# 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
ylabel='p(x)', figsize=(4.5, 2.5),
legend=[f'mean {mu}, std {sigma}' for mu, sigma in params])

我们一般会假设观测中包含噪声,其中噪声服从正态分布。

从零实现线性回归

线性模型参数 $W = [2, −3.4]^⊤$、b = 4*.*2 、标准差 0.01

1
2
3
4
5
6
7
8
9
10
11
12
13
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w, b, num_examples): #@save
"""⽣成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

读取数据集

定义⼀个 data_iter 函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为 batch_size 的批。每个批包含⼀组特征和标签。

1
2
3
4
5
6
7
8
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 随机读取的,没有特定的顺序
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]

初始化参数、定义模型、损失函数、优化算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
def squared_loss(y_hat, y): #@save
"""均⽅损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
def sgd(params, lr, batch_size): #@save
"""⼩批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()

训练

执⾏以下循环:初始化参数、重复计算梯度、更新参数,直到完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lr = 0.03 # 学习率设置为 0.3
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的⼩批量损失
# 因为l形状是(batch_size,1),⽽不是⼀个标量。l中的所有元素被加到⼀起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使⽤参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

简洁实现

读取数据集

直接调用框架中现有的 API 来读取数据。

1
2
3
4
5
6
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造⼀个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)

为了验证是否正常⼯作,我们读取并打印第⼀个⼩批量样本。这⾥我们使⽤ iter 构造 Python 迭代器,并使⽤next从迭代器中获取第⼀项。

1
next(iter(data_iter))

初始化参数、定义模型、损失函数、优化算法

⾸先定义⼀个模型变量 net,它是⼀个 Sequential 类的实例。Sequential 类将多个层串联在⼀起。当给定输⼊数据时,Sequential 实例将数据传入到第⼀层,然后将第一层的输出作为第二层的输入,以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# nn是神经⽹络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))# 2是输⼊特征形状,1是输出特征形状
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

softmax

$$
softmax(x_i)=\frac{e^{x_i}}{\sum^N_{q=1}e^{x_q}}
$$

softmax 将未规范化的预测变换为⾮负数并且总和为1,同时让模型保持可导的性质。通过 softmax 函数就可以将多分类的输出值转换为范围在 [0, 1] 和为1的概率分布。

softmax和相关的损失函数很常⻅。

导数是我们 softmax 模型分配的概率与实际发⽣的情况(由独热标签向量表示)之间的差异

损失函数

需要损失函数来度量预测的效果。我们将使⽤最⼤似然估计。

常用交叉熵损失:$ l(y,y)=-\sum^q_{j=1}y_jlog(y_j)$

我们使用上述公式来定义损失 l,它是所有标签分布的预期损失值。称为交叉熵损失(cross-entropy loss),它是分类问题最常用的损失之⼀。

我们从两⽅⾯来考虑交叉熵分类⽬标:(i)最大化观测数据的似然(ii)最小化传达标签所需的惊异。

图像分类数据集

1
2
3
4
5
6
7
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()

读取数据集

1
2
3
4
5
6
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0〜1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True,transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False,transform=trans, download=True)
len(mnist_train), len(mnist_test)

Fashion-MNIST 由10个类别的图像组成,每个类别由训练数据集(train dataset)中的6000张图像和测试数据集(test dataset)中的1000张图像组成。因此,训练集和测试集分别包含60000和10000张图像。测试数据集不用于训练,只用于评估模型性能。

每个输⼊图像的⾼度和宽度均为28像素。数据集由灰度图像组成,其通道数为1。

1
mnist_train[0][0].shape

以下函数⽤于在数字标签索引及其⽂本名称之间进⾏转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def get_fashion_mnist_labels(labels): #@save
"""返回Fashion-MNIST数据集的⽂本标签"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat','sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): #@save
"""绘制图像列表"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
# 图⽚张量
ax.imshow(img.numpy())
else:
# PIL图⽚
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes

小批量读取

1
2
3
4
5
6
batch_size = 256
def get_dataloader_workers(): #@save
"""使⽤4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train,batch_size,
shuffle=True,num_workers=get_dataloader_workers())

整合

1
2
3
4
5
6
7
8
9
10
11
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
return (data.DataLoader(mnist_train,batch_size,shuffle=True,num_workers=get_dataloader_workers()),data.DataLoader(mnist_test, batch_size, shuffle=False,num_workers=get_dataloader_workers()))

现在,我们已经准备好使⽤ Fashion-MNIST 数据集。

softmax从0开始

ŷ :y_hat(写作 ŷ )是指线性回归模型中响应变量的估计值

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
32
33
34
35
36
37
import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

# 初始化模型参数
# 展平每个图像,把它们看作⻓度为784的向量
num_inputs = 784
# 数据集有10个类别,⽹络输出维度为10。权重将构成⼀个784×10的矩阵,偏置将构成⼀个1×10的⾏向量。
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
# 定义softmax函数
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)

def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这⾥应⽤了⼴播机制

# 定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

# 定义损失函数
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])

#
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信