【一起撸个DL框架】5 实现:自适应线性单元

简介: 5 实现:自适应线性单元🍇1 简介上一篇:【一起撸个DL框架】4 反向传播求梯度上一节我们实现了计算图的反向传播,可以求结果节点关于任意节点的梯度。下面我们将使用梯度来更新参数,实现一个简单的自适应线性单元。

5 实现:自适应线性单元🍇

1 简介

上一篇:【一起撸个DL框架】4 反向传播求梯度

上一节我们实现了计算图的反向传播,可以求结果节点关于任意节点的梯度。下面我们将使用梯度来更新参数,实现一个简单的自适应线性单元

我们本次拟合的目标函数是一个简单的线性函数:y = 2 x + 1 ,通过随机数生成一些训练数据,将许多组x和对应的结果y值输入模型,但是并不告诉模型具体函数中的系数参数“2”和偏置参数“1”,看看模型能否通过数据“学习”到参数的值。

4fa12f34d7a5457680c44b0686ff9a8b.png

2 损失函数

2.1 梯度下降法

损失是对模型好坏的评价指标,表示模型输出结果与正确答案(也称为标签)之间的差距。所以损失值越小就说明模型越准确,训练过程的目的便是最小化损失函数的值

自适应线性单元是一个回归任务,我们这里将使用绝对值损失,将模型输出与正确答案之间的差的绝对值作为损失函数的值,即l o s s = ∣ l − a d d ∣


评价指标有了,可是如何才能达标呢?或者说如何才能降低损失函数的值?计算图中有四个变量:x , w , b , l  ,而我们训练过程的任务是调整参数w , b 的值,以降低损失。因此训练过程中的自变量是w和b,而把x和l看作常量。此时损失函数是关于w和b的二元函数l o s s = f ( w , b ) ,我们只需要求函数的梯度▽ f ( w , b ) = (\frac{\partial f}{\partial w}, 梯度的反方向就是函数下降最快的方向。沿着梯度的方向更新参数w和b的值,就可以降低损失。这就是经典的优化算法:梯度下降法

2.2 补充

关于损失和优化的概念,大家可能还是有些模糊。上面损失只讲到了一个输入x值对应的模型输出与实际结果之间的差距,但使用整个数据集的平均差距可能更容易理解,就像中学的线性回归

图2所示,改变直线的斜率w,将改变直线与数据点的贴近程度,即改变了损失函数loss的值。

e3fcdd1e985d442db613824e91d6429c.png

3 整理项目结构

我们的小项目的代码也渐渐多起来了,好的目录结构将使它更加易于扩展。关于python包结构的知识大家可以自行去了解,大致目录结构如下:

- example
- ourdl
  - core
    - __init__.py
    - node.py
  - ops
    - __init__.py
    - loss.py
    - ops.py
  __init__.py

给这个简单框架的名字叫做OurDL,使用框架搭建的计算图等程序放在example目录下。在ourdl/core/node.py中存放了节点基类和变量类的定义,在ourdl/ops/下存放了运算节点的定义,包括损失函数和加法、乘法节点等。

4 损失函数的实现

/ourdl/ops/loss.py中,

from ..core import Node
class ValueLoss(Node):
    '''损失函数:作差取绝对值'''
    def compute(self):
        self.value = self.parent1.value - self.parent2.value
        self.flag = self.value > 0
        if not self.flag:
            self.value = -self.value
    def get_parent_grad(self, parent):
        a = 1 if self.flag else -1
        b = 1 if parent == self.parent1 else -1
        return a * b

其中compute()方法很显然就是对两个输入作差取绝对值;get_parent_grad()方法求本节点关于父节点的梯度。有绝对值如何求梯度?大家可以画一画绝对值函数的图像。

5 修改节点类(Node)

ourdl/core/node.py

class Node:
    pass  # 省略了一些方法的定义,大家可以查看上一篇文章
    def clear(self):
        '''递归清除父节点的值和梯度信息'''
        self.grad = None
        if self.parent1 is not None:  # 清空非变量节点的值
            self.value = None
        for parent in [self.parent1, self.parent2]:
            if parent is not None:
                parent.clear()
    def update(self, lr=0.001):
        '''根据本节点的梯度,更新本节点的值'''
        self.value -= lr * self.grad  # 减号表示梯度的反方向

我在节点类中新增了两个方法,其中clear()用于清除多余的节点值和梯度信息,因为当节点值或梯度已经存在时会直接返回结果而不会递归去求了(见get_grad()和forward()的代码)。update()有一个学习率参数lr,更新幅度太大可能导致参数值一直在目标值左右晃悠,无法收敛。

6 自适应线性单元

/example/01_esay/自适应线性单元.py

import sys
sys.path.append('../..')
from ourdl.core import Varrible
from ourdl.ops import Mul, Add
from ourdl.ops.loss import ValueLoss
if __name__ == '__main__':
    # 搭建计算图
    x = Varrible()
    w = Varrible()
    mul = Mul(parent1=x, parent2=w)
    b = Varrible()
    add = Add(parent1=mul, parent2=b)
    label = Varrible()
    loss = ValueLoss(parent1=label, parent2=add)
    # 参数初始化
    w.set_value(0)
    b.set_value(0)
    # 生成训练数据
    import random
    data_x = [random.uniform(-10, 10) for i in range(10)]  # 按均匀分布生成[-10, 10]范围内的随机实数
    data_label = [2 * data_x_one + 1 for data_x_one in data_x]
    # 开始训练
    for i in range(len(data_x)):
        x.set_value(data_x[i])
        label.set_value(data_label[i])
        loss.forward()  # 前向传播 --> 求梯度会用到损失函数的值
        w.get_grad()
        b.get_grad()
        w.update(lr=0.05)
        b.update(lr=0.1)
        loss.clear()
        print("w:{:.2f}, b:{:.2f}".format(w.value, b.value))
    print("最终结果:{:.2f}x+{:.2f}".format(w.value, b.value))

运行结果:

w:0.13, b:0.10
w:0.36, b:0.20
w:0.58, b:0.10
w:0.74, b:0.00
w:1.13, b:0.10
w:1.43, b:0.20
w:1.62, b:0.30
w:1.94, b:0.20
w:1.50, b:0.30
w:1.87, b:0.40
最终结果:1.87x+0.40

上面自适应线性单元的训练,已经能够大致展现深度学习模型的训练流程:


搭建模型 --> 初始化参数 --> 准备数据 --> 使用数据更新参数的值

我们这里参数只更新了10次,结果就已经大致接近了我们的目标函数y = 2 x + 1 。大家可以试试更改学习率lr,训练数据集的大小,观察运行结果会发生怎样的变化。(必备技能:调参)


相关文章
|
1月前
|
机器学习/深度学习
深度学习笔记(十二):普通卷积、深度可分离卷积、空间可分离卷积代码
本文探讨了深度可分离卷积和空间可分离卷积,通过代码示例展示了它们在降低计算复杂性和提高效率方面的优势。
56 2
深度学习笔记(十二):普通卷积、深度可分离卷积、空间可分离卷积代码
|
6月前
|
机器学习/深度学习 算法 计算机视觉
YOLOv8 | 卷积模块 | 提高网络的灵活性和表征能力的动态卷积【附代码+小白可上手】
本教程介绍了如何在YOLOv8中使用动态卷积提升网络性能和灵活性。动态卷积利用注意力机制动态选择和组合卷积核,适应输入数据特征,解决了轻量级CNN的局限。文中提供了详细步骤教读者如何添加和修改代码,包括在`conv.py`中添加`Dynamic_conv2d`模块,更新`init.py`、`task.py`和`yaml`配置文件。此外,还分享了完整代码和进阶技巧,帮助深度学习初学者实践目标检测。参考[YOLOv8改进](https://blog.csdn.net/m0_67647321/category_12548649.html)专栏获取更多详情。
|
6月前
|
机器学习/深度学习 存储 编解码
RepQ带来重参结构新突破 | RepVGG结构真的没办法进行QAT训练吗?
RepQ带来重参结构新突破 | RepVGG结构真的没办法进行QAT训练吗?
110 1
RepQ带来重参结构新突破 | RepVGG结构真的没办法进行QAT训练吗?
|
6月前
|
算法
【MFAC】基于紧格式动态线性化的无模型自适应迭代学习控制
【MFAC】基于紧格式动态线性化的无模型自适应迭代学习控制
【MFAC】基于紧格式动态线性化的无模型自适应迭代学习控制
|
6月前
【MFAC】基于紧格式动态线性化的无模型自适应控制(Matlab代码)
【MFAC】基于紧格式动态线性化的无模型自适应控制(Matlab代码)
|
机器学习/深度学习 传感器 算法
基于有限体积法 (FVM) 和 SIMPLE 算法求解平行板之间层流的速度、压力和温度附 MATLAB 代码
基于有限体积法 (FVM) 和 SIMPLE 算法求解平行板之间层流的速度、压力和温度附 MATLAB 代码
|
机器学习/深度学习 传感器 算法
基于微分 Vaiana Rosati 模型对 SDF 速率无关迟滞系统进行非线性时程分析的 Matlab 代码
基于微分 Vaiana Rosati 模型对 SDF 速率无关迟滞系统进行非线性时程分析的 Matlab 代码
|
存储 人工智能 算法
鲁棒优化入门(4)-两阶段鲁棒优化及行列生成算法(C&CG)超详细讲解
        鲁棒优化是应对数据不确定性的一种优化方法,但单阶段鲁棒优化过于保守。为了解决这一问题,引入了两阶段鲁棒优化(Two-stage Robust Optimization)以及更一般的多阶段鲁棒优化,其核心思想是将决策问题分为两个阶段。第一阶段是进行初步决策,第二阶段是根据第一阶段的决策结果制定更好的决策策略,应对数据不确定性的影响。这种方法可以降低保守性,提高鲁棒性。
|
机器学习/深度学习 算法
【数字预失真(DPD)】静态DPD设计扩展为自适应设计及评估两种自适应DPD设计:基于(最小均方)LMS算法、使用递归预测误差方法(RPEM)算法研究(Matlab&Simulink实现)
【数字预失真(DPD)】静态DPD设计扩展为自适应设计及评估两种自适应DPD设计:基于(最小均方)LMS算法、使用递归预测误差方法(RPEM)算法研究(Matlab&Simulink实现)
170 0