多层感知机
一.隐藏层和激活函数
1.为什么需要隐藏层?
前面几篇博客我们通过基础知识,学习了如何处理数据,如何将输出转换为有效的概率分布, 并应用适当的损失函数,根据模型参数最小化损失。
但是记不记得当时我们算出来的数据都是线性的,我们把一张图片28*28=784的每一个像素视为一个特征进行标签预测,进行权重和偏置运算的时候都是线性的。
如何对猫和狗的图像进行分类呢? 增加位置(13,17)处像素的强度是否总是增加(或降低)图像描绘狗的似然? 对线性模型的依赖对应于一个隐含的假设, 即区分猫和狗的唯一要求是评估单个像素的强度。 在一个倒置图像后依然保留类别的世界里,这种方法注定会失败。
如果有非线性的问题出现的时候我们该怎么做呢?
任何像素的重要性都以复杂的方式取决于该像素的上下文(周围像素的值)。 我们的数据可能会有一种表示,这种表示会考虑到我们在特征之间的相关交互作用。
我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。
这时候我们就引入了多层感知机模型
多层感知机(MLP)通常包括输入层、一个或多个隐藏层以及输出层。输入层接收外部数据,隐藏层对数据进行处理和变换,输出层则产生最终的结果
看下面这个图其实只有两层:隐藏层和输出层
这两个层都是全连接的。 每个输入都会影响隐藏层中的每个神经元, 而隐藏层中的每个神经元又会影响输出层中的每个神经元。
下面这个是具体的矩阵范围
我们发现经过一次次输出还是等价的
所以我们隐藏层的特殊就体现出来了,我们把非线性的激活函数加到隐藏层里面
就像这样,括号前面那个就是激活函数,可以多加几个隐藏层
当然但隐藏层不意味着能解决所有问题,它只是能学习任何函数
2.激活函数
首先我们知道我们可以有多个隐藏层,那么我们每一个隐藏层的激活函数都一样还是不一样呀?
使用相同激活函数的情况:
- 简化设计:使用相同的激活函数可以简化模型的设计过程,减少需要调整的超参数数量。
- 一致性:在某些情况下,保持所有隐藏层使用相同的激活函数可能有助于保持模型输出的一致性或可解释性。
使用不同激活函数的情况:
- 优化性能:不同的激活函数具有不同的数学特性和优点,针对不同的任务和数据集,使用不同的激活函数组合可能有助于提升模型的性能。
- 解决梯度消失/爆炸问题:例如,在深度网络中,使用ReLU激活函数(或其变体,如Leaky ReLU、PReLU)可以帮助缓解梯度消失问题,而在靠近输出的层使用Sigmoid或Softmax激活函数则更适合于分类任务的输出。
- 特征表示:在某些情况下,不同的隐藏层可能负责学习不同类型的特征表示,使用不同的激活函数可以更好地适应这种需求。
常见的激活函数组合:
- ReLU及其变体:在大多数现代神经网络中,ReLU及其变体(如Leaky ReLU、PReLU、ELU等)因其计算简单、收敛速度快且能有效缓解梯度消失问题而广受欢迎。它们常被用作隐藏层的激活函数。
- Sigmoid/Tanh:尽管在深度网络中不如ReLU系列流行,但Sigmoid和Tanh激活函数在某些特定任务(如二分类问题的输出层)中仍然有其用武之地。
- Softmax:在多分类问题的输出层中,Softmax激活函数是一个常见的选择,因为它可以将输出转换为概率分布。
了解完这个之后我们首先要知道激活函数输出的东西叫做:活性值
2.0激活函数定义
在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是激活函数。引入激活函数是为了增加神经网络模型的非线性。如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。
接下来我们学三个激活函数:
我都按函数公式+原函数图像+该函数导数图像+代码的形式来介绍了
2.1ReLU函数
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))
y.backward(torch.ones_like(x), retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))
torch.ones_like(x)设置一个和y形状相同的张量的权重梯度进行方向传播,如果设为zeros_like的话,那么导数就都为0了
这个我当时不太理解,还是多搜了搜终于懂了,写在下面了。
在PyTorch中,当你对一个非标量(即包含多个元素的张量)调用
.backward()
方法时,确实需要指定一个与这个张量形状相同的权重张量(通过gradient
参数,也称为grad_tensors
)上面我说了权重梯度:
x.grad
:
对于x
中的每个元素,其梯度将是y
中对应元素的梯度(如果有的话)乘以weights
中对应元素的值(权重梯度)。但是,由于y
的前三个元素是0,它们的梯度也是0,无论weights
中的值是多少。就比如说你relu算出来是1,然后你指定weights对于的值为2.0,那么你x.grad求出来的就是2.0
retain_graph=True,这个参数指定了是否保留计算图以供后续使用
通过导数图像我们发现输入0时不可导,因为现实工程数学不存在输入为0,所以为o时我们导数就视为0
优点:
ReLu的收敛速度比 sigmoid 和 tanh 快;
函数在x>0区域上,梯度不会饱和,解决了梯度消失问题;
计算复杂度低,不需要进行指数运算,只要一个阈值就可以得到激活值;
适合用于后向传播。
缺点:
ReLU的输出不是zero-centered(0均值);
Dead ReLU Problem(神经元坏死现象):在x<0时,梯度为0。这个神经元及之后的神经元梯度永远为0,不再对任何数据有所响应,导致相应参数永远不会被更新。。
产生这种现象的两个原因:1、参数初始化问题;2、learning rate太高导致在训练过程中参数更新太大。
ReLU不会对数据做幅度压缩,所以数据的幅度会随着模型层数的增加不断扩张。
它还有很多变体:
加了线性使即使是负数也可以通过
2.2sigmoid函数
通常叫做挤压函数
你看这个函数它会把范围(-inf, inf)中的任意输入压缩到区间(0, 1)中的某个值
y = torch.sigmoid(x)
d2l.plot(x.detach(), y.detach(), 'x', 'sigmoid(x)', figsize=(5, 2.5))
# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of sigmoid', figsize=(5, 2.5))
sigmoid函数的导数图像如下所示。 注意,当输入为0时,sigmoid函数的导数达到最大值0.25; 而输入在任一方向上越远离0点时,导数越接近0
优点:
连续函数,便于求导的平滑函数;
能压缩数据,保证数据幅度不会有问题;
适合用于前向传播
缺点:
1.容易出现**梯度消失(gradient vanishing)**的现象:当激活函数接近饱和区时,变化太缓慢,导数接近0,根据后向传递的数学依据是微积分求导的链式法则,当前导数需要之前各层导数的乘积,几个比较小的数相乘,导数结果很接近0,从而无法完成深层网络的训练。
2.在反向传播时,当梯度接近于0,权重基本不会更新,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。
3.Sigmoid的输出不是0均值(zero-centered)的:这会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响。以 f=sigmoid(wx+b)为例, 假设输入均为正数(或负数),那么对w的导数总是正数(或负数),这样在反向传播过程中要么都往正方向更新,要么都往负方向更新,导致有一种捆绑效果,使得收敛缓慢。(
在深度学习中,零均值化(zero-mean)处理常用于预处理喂给网络模型的训练图片。具体做法是让所有训练图片中每个位置的像素均值为0,使得像素值范围变为[-128,127],以0为中心。这样做的优点是为了在反向传播中加快网络中每一层权重参数的收敛。)
4.计算复杂度高,因为sigmoid函数是指数形式。幂运算相对耗时
2.3tanh函数
tanh把输入的数据转换为了-1到1之间
并且图像为关于原点中心对称
导数图像:当输入接近0时,tanh函数的导数接近最大值1。 与我们在sigmoid函数图像中看到的类似, 输入在任一方向上越远离0点,导数越接近0。
优点:
- tanh函数将输入值压缩到 -1~1 的范围,因此它是0均值的,解决了Sigmoid函数的非zero-centered问题
缺点:
- 存在梯度消失和幂运算的问题(梯度饱和与exp计算的问题)。
二:多层感知机从零开始实现
导入数据集->初始化参数->激活函数->损失函数->训练->预测
import torch
from torch import nn
from d2l import torch as d2l
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
print(test_iter,train_iter)
num_inputs,num_outputs,num_hiddens=784,10,256
w1=nn.Parameter(torch.randn(num_inputs,num_hiddens,requires_grad=True)*0.01)
b1=nn.Parameter(torch.zeros(num_hiddens,requires_grad=True))
w2=nn.Parameter(torch.randn(num_hiddens,num_outputs,requires_grad=True)*0.01)
b2=nn.Parameter(torch.zeros(num_outputs,requires_grad=True))
params=[w1,b1,w2,b2]
def relu(x):
a=torch.zeros_like(x)
return torch.max(x,a)
def net(x):
x=x.reshape((-1,num_inputs))
H=relu(x@w1+b1)
return (H@w2+b2)
loss = nn.CrossEntropyLoss(reduction='none')
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
d2l.predict_ch3(net, test_iter)
d2l.plt.show()
对reduction的解释
nn.CrossEntropyLoss
的reduction
参数控制损失值的聚合方式,它有几个可能的值:
'none'
或'sum_none'
:不聚合损失值。即,对于每个样本(或每个元素的损失,取决于输入的形状),损失函数将返回一个单独的损失值。这意味着返回的损失值将具有与输入样本数量相同的形状。'mean'
:返回损失值的平均值。这是最常见的设置,特别是在训练过程中,因为它给出了一个关于整个批次损失的单一标量值。'sum'
:返回损失值的总和。这在某些情况下很有用,但不如'mean'
那样普遍。当你设置
reduction='none'
时,你告诉 PyTorch 不要对损失值进行任何聚合。这意味着,如果你有一个批次的数据,并且每个样本都有一个损失值,那么nn.CrossEntropyLoss
将返回一个与批次中样本数量相同长度的张量,其中包含每个样本的损失值。这种设置有几个用途:
自定义聚合:你可能想要根据特定逻辑(比如加权损失)来自定义损失值的聚合方式。通过先获取每个样本的损失值,然后你可以根据需要对它们进行加权、平均或求和。
更细粒度的分析:在某些情况下,你可能想要对每个样本的损失值进行单独的分析,以了解模型在不同样本上的表现如何。
与其他损失函数结合:在训练多任务模型时,你可能想要将
nn.CrossEntropyLoss
的输出与其他任务的损失值结合起来。通过设置reduction='none'
,你可以更容易地实现这一点。
对SGD内容的分析
这段信息是关于PyTorch中SGD(随机梯度下降)优化器的一个参数组的配置说明。在PyTorch中,优化器可以管理多个参数组,每个参数组可以有自己的一套优化参数(如学习率、动量等)。然而,在你给出的这个例子中,似乎只展示了一个参数组的配置,且是以一种类似于字典或键值对的形式列出的。下面是对这些配置项的解释:
dampening
: 0
这个参数用于动量计算的“阻尼”,它有助于抑制动量项的振荡。值为0意味着不使用阻尼。
differentiable
: False
这个参数在PyTorch的官方SGD优化器文档中并不直接出现。它可能是一个非标准或特定于某个库/框架的扩展,或者是一个误解。在标准的PyTorch SGD优化器中,我们不会直接设置参数的“可微分性”,因为优化器本身不直接参与梯度计算(这是自动微分引擎的职责)。
foreach
: None
这个参数在PyTorch的SGD优化器中也不是标准的配置项。它可能指的是某种高级功能,用于对参数组中的每个参数应用不同的优化策略,但这并不是PyTorch标准SGD优化器的一部分。
lr
: 0.1
学习率(Learning Rate),是优化算法中的一个关键超参数,它决定了在优化过程中参数更新的步长大小。这里设置为0.1。
maximize
: False
这个参数通常用于指定优化问题的目标是最小化还是最大化。在PyTorch的SGD优化器中,默认是最小化损失函数,因此这个参数通常被设置为False。
momentum
: 0
动量(Momentum)是一种帮助加速SGD在相关方向上并抑制振荡的技术。动量项累积了之前梯度的指数衰减移动平均,并且继续沿该方向移动。这里设置为0意味着不使用动量。
nesterov
: False
Nesterov动量是对传统动量方法的一种改进,它在计算梯度之前先对参数进行了一个“预测”更新。这有助于加速收敛,并减少在某些情况下的振荡。这里设置为False意味着不使用Nesterov动量。
weight_decay
: 0
权重衰减(Weight Decay)是一种正则化技术,它通过向损失函数添加一个正则项来惩罚模型参数的规模,从而有助于防止过拟合。这个正则项是模型参数平方的系数乘以一个超参数(即权重衰减率)。这里设置为0意味着不使用权重衰减。综上所述,这个参数组的配置是一个相对简单的SGD优化器配置,其中只使用了学习率(lr=0.1),而没有使用动量、Nesterov动量、权重衰减等高级特性。
三:多层感知机简洁实现
定义net的时候我们有展平层,隐藏层,激活函数,输出层
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
d2l.plt.show()
四:模型选择,欠拟合和过拟合
误差:泛化误差,训练误差
正常我们训练得出来的预测值与标准值之间的误差就是训练误差
而面对未知的测试集得出来的预测值与标准值之间的误差叫做泛化误差
对未知的反馈越好,泛化能力就越强
以下是影响泛化能力的因素:
-
可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
-
参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
-
训练样本的数量。即使模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。
验证集:
1.我们把样本分为三份,训练,测试和验证,因为现实中我们不会使用测试集一次就丢弃,但还要考虑泛化误差,所以验证集就应运而生了
2.k折交叉验证:把原始训练数据被分成𝐾个不重叠的子集。 然后执行𝐾次模型训练和验证,每次在𝐾−1个子集上进行训练, 并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证, 最后,通过对𝐾次实验的结果取平均来估计训练和验证误差。
欠拟合和过拟合
欠拟合:在训练数据时表现不好,模型太简单
解决欠拟合的方法包括:
- 选择一个更复杂的模型,以增加模型的学习能力。
- 增加模型的参数数量。
- 延长训练时间,确保模型有足够的时间去学习数据。
- 尝试不同的特征组合或特征变换,以改善数据的表达性。
过拟合:训练表现很好,泛化能力很弱
- 增加数据量:更多的数据可以提供更多的信息,帮助模型学习到更一般的规律,而不是只关注训练数据中的细节。
- 正则化:通过在损失函数中添加正则化项(如L1或L2正则化),可以限制模型的复杂度,防止模型学习到过多的细节。
- 特征选择:减少特征的数量,只保留对模型预测有用的特征,可以降低模型的复杂度。
- 交叉验证:使用交叉验证来选择最优的模型参数,以防止模型在训练数据上过拟合。
- 早停法(Early Stopping):在训练过程中监控模型在验证集上的表现,一旦验证集上的性能开始下降,就停止训练。
所以我们发现模型的选择是有一个临界点的
对于许多任务,深度学习只有在有数千个训练样本时才优于线性模型
多项式回归
构造一个像这样的多项式进行训练
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
max_degree = 20 # 多项式的最大阶数
n_train, n_test = 100, 100 # 训练和测试数据集大小
true_w = np.zeros(max_degree) # 分配大量的空间
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])
features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))#前面是底数,后面是指数,实现x的n次方
for i in range(max_degree):
poly_features[:, i] /= math.gamma(i + 1) # gamma(n)=(n-1)!#对每一个样本的特征值对应除以递增的阶乘
# labels的维度:(n_train+n_test,)
labels = np.dot(poly_features, true_w)#前面的系数
labels += np.random.normal(scale=0.1, size=labels.shape)#加噪声
上面这个是生成数据集,具体计算过程都注释上了,但训练时候需要转为tensor所以再加两行代码
# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
torch.float32) for x in [true_w, features, poly_features, labels]]
接下来定义一个精度
def evaluate_loss(net, data_iter, loss): #@save
"""评估给定数据集上模型的损失"""
metric = d2l.Accumulator(2) # 损失的总和,样本数量
for X, y in data_iter:
out = net(X)
y = y.reshape(out.shape)
l = loss(out, y)
metric.add(l.sum(), l.numel())
return metric[0] / metric[1]
然后进行训练和展示
def train(train_features, test_features, train_labels, test_labels,
num_epochs=400):
loss = nn.MSELoss(reduction='none')
input_shape = train_features.shape[-1]
# 不设置偏置,因为我们已经在多项式中实现了它
net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
batch_size = min(10, train_labels.shape[0])
train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)),
batch_size)
test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)),
batch_size, is_train=False)
trainer = torch.optim.SGD(net.parameters(), lr=0.01)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
xlim=[1, num_epochs], ylim=[1e-3, 1e2],
legend=['train', 'test'])
for epoch in range(num_epochs):
d2l.train_epoch_ch3(net, train_iter, loss, trainer)
if epoch == 0 or (epoch + 1) % 20 == 0:#这段代码的目的是在训练开始时和每隔20轮次结束时,评估模型在训练集和测试集上的损失
animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss),
evaluate_loss(net, test_iter, loss)))
print('weight:', net[0].weight.data.numpy())
# 从多项式特征中选择前4个维度,即1,x,x^2/2!,x^3/3!三阶多项式函数拟合(正常)
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
labels[:n_train], labels[n_train:])
# 从多项式特征中选择前2个维度,即1和x(欠拟合)
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
labels[:n_train], labels[n_train:])
# 从多项式特征中选取所有维度(过拟合)
train(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:], num_epochs=1500)
d2l.plt.show()
五:权重衰减
上面这个图是拉格朗日函数
左图大圆圈就是目标函数,也就是f(x,y)
而那个歪歪扭扭的就是y-g(x)函数
还记得我们讲的梯度吗?
看右图的大括号里面的式子求导数使其等于0,而对于左图就是梯度方向相反,大小相等
而方向是切线方向已经确定了,而λ就可以调整梯度的大小(梯度是一个向量)
我们首先需要知道L1和L2正则化是为了解决过拟合问题,我们先来看他们的数学公式
我们知道L1正则化和L2正则化实际上也就是拉格朗日函数
我们前面已经知道影响泛化能力的几个因素分别是:参数数量,参数范围和数据量大小
那么我们看上面的式子就知道我们是要调整w参数的范围从而防止过拟合的发生
拿L2正则化举例,L2范数w-c这个式子其实就是对c进行强制限制
网络输出层的偏置项不会被正则化
跟上面这个图片是等价的
我们权重的更新是运用的梯度下降法
我们用a/2是为了方便求导约去2
权重衰减就是衰减到w前面的系数
红色的线把数据清晰的区分开了,几乎0误差,这是训练数据所以过拟合了
紫色的线直接一条线分开,根本区分不开两类物体,所以是欠拟合
灰色的线大致的分开,这样就是我们要的
而我们对原目标函数求导,阶数越高,弯弯绕绕的点就越多,想减少弯绕就要减少高次项
所以观察前面的系数,他们能够控制弯绕的程度
左边的是原损失函数的最值,右边的是正则化后损失函数的最值
我们研究一下前后的关系,就要得到前面关于后面的函数表达式
函数的泰勒展开式取前两项,H是黑塞矩阵
w正则化就是对原来的w*进行缩放,这就是L2正则化的本质当a取正负值或0时都不同
L1和L2正则化以及权重衰减其实不一样,我们不能简单的认为L1和L2正则化就是权重衰减
具体可以参考https://arxiv.org/pdf/1711.05101.pdf
简单来说: 对于自适应学习率的优化算法来说,l2是不那么有效的,但是用weight decay就可以使其获得l2的效果;在非自适应学习率算法中,l2则和权重衰减完全等价。
l1通常用于增加稀疏性,l2一般用于减小“复杂度”,且l2有唯一解。
接下来是代码演示
import torch
from torch import nn
from d2l import torch as d2l
#初始真实数据
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
#模型参数
def init_params():
w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return [w, b]
#l2惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
#训练
def train(lambd):
w, b = init_params()
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
num_epochs, lr = 100, 0.003
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
# 增加了L2范数惩罚项,
# 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
l = loss(net(X), y) + lambd * l2_penalty(w)
l.sum().backward()
d2l.sgd([w, b], lr, batch_size)
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数是:', torch.norm(w).item())
train(lambd=0)
train(lambd=3)
d2l.plt.show()
'''def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss(reduction='none')
num_epochs, lr = 100, 0.003
# 偏置参数没有衰减
trainer = torch.optim.SGD([
{"params":net[0].weight,'weight_decay': wd},net[0].weight 的更新会考虑 weight_decay
{"params":net[0].bias}], lr=lr)
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X), y)
l.mean().backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1,
(d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数:', net[0].weight.norm().item())'''