动手学深度学习:线性神经网络
开个新坑,虽然之前大概扫过一遍但是几乎是没学懂的,这次再回锅肉一下,主要是写得好理解一些方便自己记忆理解
3 线性神经网络
线性回归
-
要素
- 要求什么? 线性模型:权重w+偏置b ->
- 怎么监督? 损失函数:量化目标的实际值与预测值之间的差距
- 损失函数为什么通常使用均方误差:根据极大似然估计法,参数w和b的最优值是使整个数据集的似然最大的值。在高斯噪声的假设下,最小化均方误差等价于对线性模型的极大似然估计
- 一步到位?解析解: 只有简单模型才有
- 求解过程? 随机梯度下降:小批量的平均损失关于模型参数的导数(梯度)将梯度乘以一个预先确定的正数η,并从当前参数的值中减掉
- 初始化参数
- 重复以下训练,直到完成
- 计算梯度g←
- 更新参数
- 学习后做什么?预测
-
简洁实现
-
生成数据集:
features, labels = d2l.synthetic_data(true_w, true_b, 1000)features是X,labels是已经加过噪声的y -
读取数据集:
1
2
3
4
5
6def 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) #从dataset打乱拿batch_size个
batch_size = 10
data_iter = load_array((features, labels), batch_size) -
定义模型
-
在PyTorch中,全连接层在Linear类中定义。值得注意的是,我们将两个参数传递到nn.Linear中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
-
我们首先定义一个模型变量net,它是一个Sequential类的实例。Sequential类将多 个层串联在一起。当给定输入数据时,Sequential实例将数据传入到第一层,然后将第一层的输出作为第二层的输入,以此类推。在下面的例子中,我们的模型只包含一个层,因此实际上不需要Sequential。但是由 于以后几乎所有的模型都是多层的,在这里使用Sequential会让你熟悉“标准的流水线”。
1
2fron torch import nn
net = nn.Sequential(nn.Linear(2, 1))
-
-
初始化模型参数:通过net[0]选择网络中的第一个图层,然后使用weight.data和bias.data方法访问参数(不管几维对同类所有)。我们还可以使用 替换方法normal_和fill_来重写参数值
1
2
3"""net[0]对应sequential的层数结构选择"""
net[0].weight.data.normal_(0, 0.01) #normal是正态随机
net[0].bias.data.fill_(0) #fill是填固定的 -
定义损失函数:计算均方误差使用的是MSELoss类,也称为平方L2范数。默认情况下,它返回所有样本损失的平均值。
loss = nn.MSELoss() -
定义优化算法: 小批量随机梯度下降算法是一种优化神经网络的标准工具,PyTorch在optim模块中实现了该算法的许多变种。当我们实例化一个SGD实例时,我们要指定优化的参数*(可通过net.parameters()从我们的模型中获得)* 以及优化算法所需的超参数字典。小批量随机梯度下降只需要设置lr值,这里设置为0.03。
trainer = torch.optim.SGD(net.parameters(), lr=0.03) -
训练:在每个迭代周期里,我们将完整遍历一次数据集(train_data),不停地从中获取一个小批量的输 入和相应的标签。对于每一个小批量,我们会进行以下步骤: ①通过调用net(X)生成预测并计算损失l(前向传播)②通过进行反向传播来计算梯度 ③通过调用优化器来更新模型参数。
1
2
3
4
5
6
7
8
9num_epochs = 3
for epoch in range(num_epochs): # 整个数据集训练3遍
for X, y in data_iter: # 每次取一个batch
l = loss(net(X), y) # ① 预测,并计算预测误差
trainer.zero_grad() # ② 清除上一次的梯度
l.backward() # ③ 根据loss计算w、b的梯度
trainer.step() # ④ 根据梯度更新w、b
l = loss(net(features), labels) # ⑤ 用全部数据检查当前模型的loss
print(f'epoch {epoch + 1}, loss {l:f}')
-
Softmax回归
-
基本原理: Softmax 回归是一种用于多分类问题的单层神经网络。对于同一个输入样本,输出层中的每个神经元分别通过一次线性计算得到一个类别得分(logit),每个得分对应一个类别。随后,将所有类别得分一起输入 Softmax 函数,将其转换为总和为 1 的概率分布,最终选择概率最大的类别作为预测结果。
-
Softmax 函数: softmax运算不会改变未规范化的预测之间的大小次序,只会确定分配给每个类别的概率。
-
损失函数: 交叉熵损失
-
最大似然:模型给真实分配类别的概率尽可能大
-
对整个数据集,假设各样本相互独立,则希望所有样本真实类别对应概率的乘积最大。为了将概率的连乘转化为更方便计算的求和,对似然取对数。机器学习通常将优化问题写成“最小化损失”,因此在对数似然前加负号。
-
-
计算梯度&更新参数:
-
梯度结论:
-
链式法则:Loss 并不直接依赖某个 ,而是 先影响 , 再影响 Loss,因此沿着这条路径把各段的导数相乘,就能得到 对 Loss 的最终影响。
-
参数更新与之前相同
-
-
-
代码实现
-
定义&初始化模型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 定义 Softmax 回归网络
net = nn.Sequential(
nn.Flatten(), # 将每张 1×28×28 的图片展平成 784 维向量
nn.Linear(784, 10) # 全连接层:784个输入特征 → 10个类别的logits
)
# 定义参数初始化函数
def init_weights(m):
# 判断当前遍历到的模块是否为全连接层
if type(m) == nn.Linear:
# 将该全连接层的所有权重w初始化为
# 均值为0、标准差为0.01的正态分布随机数
nn.init.normal_(m.weight, mean=0, std=0.01)
# 遍历net中的各个模块,并对每个模块调用init_weights
# Flatten不满足if条件,不处理
# Linear满足if条件,因此其weight会被重新初始化
net.apply(init_weights) -
损失函数:
loss = nn.CrossEntropyLoss(reduction='none') -
优化算法(通用):
trainer = torch.optim.SGD(net.parameters(), lr=0.1) -
训练
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54def train_epoch_ch3(net, train_iter, loss, trainer):
"""使用PyTorch训练模型一个epoch"""
# 切换到训练模式
net.train()
# 三个累加器:loss总和、预测正确数、样本总数
metric = Accumulator(3)
# 遍历所有batch
for X, y in train_iter:
# 前向传播并计算每个样本的loss
y_hat = net(X)
l = loss(y_hat, y)
# 清梯度 → 反向传播计算梯度 → 更新参数
trainer.zero_grad()
l.mean().backward()
trainer.step()
# 累计loss总和、预测正确数和样本数
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回整个epoch的平均loss和训练准确率
return metric[0] / metric[2], metric[1] / metric[2]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, optimizer):
"""训练模型多个epoch"""
# 创建动态绘图工具:绘制训练loss、训练准确率和测试准确率
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
# 训练num_epochs轮
for epoch in range(num_epochs):
# 训练一个epoch,返回(平均训练loss, 训练准确率)
train_metrics = train_epoch_ch3(net, train_iter, loss, optimizer)
# 在测试集上计算当前模型的准确率
test_acc = evaluate_accuracy(net, test_iter)
# 添加当前epoch的数据:(train loss, train acc, test acc)
animator.add(epoch + 1, train_metrics + (test_acc,))
# 取最后一个epoch的训练loss和训练准确率
train_loss, train_acc = train_metrics
# 检查最终训练结果是否达到预期
assert train_loss < 0.5, train_loss
assert 0.7 < train_acc <= 1, train_acc
assert 0.7 < test_acc <= 1, test_acc
num_epochs = 10
train_ch3(net, train_iter, test_iter, loss, num_epochs, optimizer)
-
