在之前的文章从零构建自己的神经网咯————模型篇中,我们留下了两个问题:
③每层的参数怎么定?
⑥机器怎么“学习”?
在这里希望读者已经了解了损失函数以及梯度的概念。
1.人工智能所谓的学习,学的是什么?
作者在这里给出作者的理解,仅供大家参考。
人工智能得学习,反映在代码程序中,就是所谓的参数。每个神经元连接都有着一个参数。
其中每条连接都可视为一个参数,上图这个公有[3,2]个参数,这就是所谓得参数矩阵。现在大火的chatgpt参数数量达到几千亿。我们举个例子方面更好的理解参数的作用:
假设我们学习了一组参数可以对猫进行识别,意思就是神经元的各个参数能够在你输入的图像中,
经过运算得出目标是猫的概率(或许你对此认为很神奇,我们在这之前就说过图片可以用数学表示出来,
每个类别的数字不同,但是同一种类别必定有着想同的分布规律),
如果想要实现分类出多种类别的东西,比如还能识别狗,机器需要学习,即在保证参数能识别出的猫的情况下
经过适当的调整还能识别出狗。
这是如果你了解了学习和参数的关系后,应该会有一些疑问:
①参数越多越好吗?
②如果我把所有的参数都集中在一层,可不可以?
我们依次回答,这对你学习很有帮助:
参数越多越好吗?
答案:不一定。如果你想实现更复杂的功能,则必不可少的得使用更多的参数,但是参数太多的话会导致过拟合的现象,但是太少的化会导致欠拟合,关于如何设置合适的参数数量以及如何防止欠拟合,这也是一个值得学习的方面,本文不在这对此阐述。
如果我把所有的参数都集中在一层,可不可以?
这是一个非常值得品味的问题,既然足够多的参数就能实现相应的功能,我们为什么不把这些参数放在同一层呢?到时候也不叫什么深度学习,改名叫宽度学习就行了。
这个问题,在早些之前确实存在这种说法,足够宽的神经元也能实现相应的功能,但是专家们经过实验发现,同等表示能力下,深度神经网络比“宽度”神经网路网络有更少的参数,所以只需要更少的数据就可以学校到,也就是说,深度神经网络有更低的样本复杂度。这也是深度神经网路更加实用的原因。
了解了参数的重要性后,我们终于能开始解决上面提出的问题了:
①每层的参数怎么定?
刚开始时候,我们人为肯定没办法定一个完美的参数–————不用训练就实现了那种。所以,大部分情况下参数都是人为随机指定的,常用的方法就是:
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
#参数w按照均值0,方差1的正态分布初始化
#requires_grad=True表示进行梯度计算。
b = torch.zeros(1, requires_grad=True)
#参数b,置为0
如果细心的同学就会发现,上篇文章中,我们好像并没有指定初始化参数。但我们并没有报错,很明显如果我们没有初始化参数,则在程序创建模型的时候,会有个默认的参数初始化规则(均匀分布)。
机器怎么“学习”?或者说参数怎么更新。
我们把输入的数据经过第一层运算后,传递给第二层,然后第二层传递给第三层…直到输出层。这种计算成为前向传播
;
经过一次前向传播后,我们在输出层得到一个结果,我们将得到结果与正确的结果最对比,我们利用损失函数
计算误差(也叫损失),得到损失值;损失函数有着多种,针对不同领域有着不同的损失函数,如果你觉得这些损失函数不够完美,读者也可以自定义一种损失函数。
得到损失值后,我们就要开始更新参数了:
更新参数采用的是梯度下降
算法:
新的参数=原来的参数----学习率*损失值对该参数的偏导
损失值对该参数的偏导又称为该参数的梯度
;就是这个上个世纪就已经提出来的公式,现在依旧在我们生活中发挥着巨大的用处,通过这个公式我们对神经网路中的参数进行更新。
值得注意的是,经过人工智能的发展,梯度下降算法也被人们不断优化,人们接连提出了随机梯度下降
(SGD),已经现在常用的Adm优化算法
。
经过反向传播
完成一次参数更新后,即完成了一次训练。
我们训练的最终目的就是找到合适的参数使得损失值最小!!
以下我们展示下,如何定义损失函数、优化算法以及如何进行一次参数更新:
定义损失函数
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_()
采用的**随机梯度下降算法(SGD)**进行参数更新lr
是学习率,param.grad
是参数的梯度, batch_size
是批量大小。读者可自行查询该算法详细公式,本文不做赘述。
定义训练函数
主要步骤就是:计算损失值------由损失值计算梯度------由梯度进行反向传播优化
lr = 0.03
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}')
当然像这种函数早已被前驱给封装好,我们可通过第三方库进行直接调用,而你需要做的就是指定参数。
以下是对上述代码的简洁实现:
loss = nn.MSELoss() #使用MSELoss损失函数,即均方损失函数,与上述代码损失函数想同
optimizer= torch.optim.SGD(net.parameters(), lr=0.03)
#定义优化算法,这里采用的是SGD(随机梯度下降)优化算法,学习率为0.03
和上述的优化器代码相同
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, optimizer)
这里采用的都d2l
库中的函数,net
是我们所定义的网咯模型, train_iter, test_iter
是我们之前提到的很重要的数据迭代器
,loss
是定义的损失函数, num_epochs
是指定的训练次数; optimizer
是定义的优化器
综上几行代码就可以和上述的代码实现想同的功能
代码来源李沐老师的动手深度学习,在这里表示感谢!