深度学习入门(四):误差反向传播法

news2025/4/9 11:11:58

文章目录

  • 前言
  • 链式法则
    • 什么是链式法则
    • 链式法则和计算图
  • 反向传播
    • 加法节点的反向传播
    • 乘法节点的反向传播
    • 苹果的例子
  • 简单层的实现
    • 乘法层的实现
    • 加法层的实现
  • 激活函数层的实现
    • ReLu层
    • Sigmoid层
  • Affine层/SoftMax层的实现
    • Affine层
    • Softmax层
  • 误差反向传播的实现
  • 参考资料

前言

上一篇文章深度学习入门(三):神经网络的学习
中,神经网络参数的学习是通过数值微分求梯度实现的,该方法虽然简单,但也有一个明显的问题就是计算费时。本文介绍一种高效计算参数梯度的方法——误差反向传播法。

注意,误差反向传播法本质上是一种梯度计算的技术,而梯度下降法是一种优化算法

链式法则

什么是链式法则

先看以下的复合函数:
z = t 2 t = x + y z=t^2 \\ t=x+y z=t2t=x+y
回忆以前学过的求复合函数的导数的性质:复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。 因此:
∂ z ∂ x = ∂ z ∂ t ∗ ∂ t ∂ x = 2 t ∗ 1 = 2 ( x + y ) \frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}* \frac{\partial t}{\partial x}=2t*1=2(x+y) xz=tzxt=2t1=2(x+y)

基于以上思想,神经网络中的链式法则就是:将复杂函数的导数分解为多个简单函数的导数的乘积,使得神经网络中权重的多层梯度计算成为可能。

链式法则和计算图

上例的过程,在计算图里表现出来即:
在这里插入图片描述
在这里插入图片描述

反向传播

加法节点的反向传播

在这里插入图片描述

乘法节点的反向传播

在这里插入图片描述
可以看到实现乘法节点的反向传播时,要保留正向传播的信号。

苹果的例子

在这里插入图片描述
上图可以理解为:如果苹果的价格和消费税增加相同的值,那么消费税将对最终的价格产生200倍的影响,苹果的个数将对最终的价格产生110倍的影响,苹果的价格将对最终的价格产生2.2倍的影响。

简单层的实现

乘法层的实现

class MulLayer:
	def __init__(self):
		self.x = None
		self.y = None
	
	def forward(self, x, y):
		self.x = x
		self.y = y
		out = x * y
		
		return out

	def backward(self, dout):
		dx = dout * self.y
		dy = dout * self.x
		
		return dx, dy

apple_price = 100
apple_num = 2
tax_rate = 1.1

# layer
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_total_price = mul_apple_layer.forward(apple_price, apple_num)
final_price = mul_tax_layer.forward(apple_total_price, tax_rate)

print(final_price) # 220

加法层的实现

class AddLayer:
	def __init__(self):
		self.x = None
		self.y = None
	
	def forward(self, x, y):
		self.x = x
		self.y = y
		out = x + y
		
		return out

	def backward(self, dout):
		dx = dout * 1
		dy = dout * 1
		
		return dx, dy

用上面的乘法层和加法层,实现下面的例子:
在这里插入图片描述

# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()

# forward
apple_price_cur = mul_apple_layer.forward(apple_price, apple_num)
orange_price_cur = mul_orange_layer.forward(orange_price, orange_num)
all_price = add_apple_orange_layer.forward(apple_price_cur, orange_price_cur)
price = mul_tax_layer.forward(all_price, tax_rate)

# backward
d_price = 1
d_all_price, d_tax_rate = mul_tax_layer.barkward(d_price)
d_apple_price_cur, d_orange_price_cur = add_apple_orange_layer.backward(d_all_price)
d_apple_price, d_apple_num = mul_apple_layer.backward(d_apple_price_cur)
d_orange_price, d_orange_num = mul_apple_layer.backward(d_orange_price_cur)

激活函数层的实现

ReLu层

Relu函数的数学表达为:
y = { x x > 0 0 x < = 0 y=\begin{cases} x & x>0 \\ 0 & x<=0 \end{cases} y={x0x>0x<=0
其导数为:
∂ y ∂ x = { 1 x > 0 0 x < = 0 \frac{\partial y}{\partial x}=\begin{cases} 1 & x>0\\ 0 & x<=0 \end{cases} xy={10x>0x<=0

计算图表示如下:
在这里插入图片描述
代码实现如下:

class Relu:
	def __init__(self):
		self.mask = None
	
	def forward(self, x):
		# x<=0 的地方保存为True,其它保存为False
		self.mask = (x<=0)
		out = x.copy
		out[self.mask] = 0
		
		return out

	def backward(self, dout):
		dout[self.mask] = 0
		dx = dout
		
		return dx
		

Sigmoid层

sigmoid函数的数学表达如下:
y = 1 1 − e − x y = \frac{1}{1-e^{-x}} y=1ex1

图像如下:
在这里插入图片描述
导数的计算图为:
在这里插入图片描述
在这里插入图片描述

class Sigmoid:
	def __init__(self):
		self.out = None
	
	def forward(self, x):
		out = 1 / (1 + np.exp(-x))
		
		return out

	def backward(self, dout):
		dx = dout * (1 - self.out) * self.out
		
		return dx

Affine层/SoftMax层的实现

Affine层

所谓Affine层指的是:在神经网络的正向传播中,进行矩阵乘积变换的处理,称为“仿射转换”,也称为“Affine层”。
在这里插入图片描述

class Affine:
	def __init__(self, W, b):
		self.W = W
		self.b = b
		self.x = None
		self.dW = None
		self.db = None
	
	def forward(self, x):
		self.x = x
		out = np.dot(x, self.W) + self.b
		return

	def backward(self, dout):
		dx = np.dot(dout, self.W.T)
		self.dW = np.dot(self.x.T, dout)
		self.db = np.sum(dout, axis=0)
		
		return dx

Softmax层

回顾一下softmax的函数表示:(如果也有softmax和sigmoid记不清的小伙伴,来个口诀吧:sigmoid看自己,softmax看大家,sigmoid是只关于x的计算,而softmax是将最终的输出结果归一化成一个概率值)
σ ( z ) i = e z i ∑ j = 1 K e z j , 其中  i = 1 , 2 , … , K \sigma(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}, \quad \text{其中 } i = 1, 2, \dots, K σ(z)i=j=1Kezjezi,其中 i=1,2,,K

如下图所示,可以看到最终的输出是通过softmax归一化。
在这里插入图片描述
一个问题:为什么神经网络的推理不需要softmax,而学习却需要softmax呢?先思考为什么推理不需要softmax,这个比较简单理解,如果是分类任务,直接选择最后一个Affine输出的值的最大值对应的类作为结果即可,回归任务的输出层无需激活函数。然后思考为什么学习需要softmax,回想前面文章提到的损失函数的计算(交叉熵误差、MSE),都要求输入是一个概率值。

下图展示了softmax层的反向传播的结果,很神奇,我们得到了 y 1 − t 1 y_1-t_1 y1t1, y 2 − t 2 y_2-t_2 y2t2, y 3 − t 3 y_3-t_3 y3t3这样漂亮的结果(正好是标签和输出值的误差),这不是巧合,而是为了得到这样漂亮的结果,特地设计了交叉熵误差这样的损失函数。(此处省去了详细的推导过程)
在这里插入图片描述

def SoftmaxWithLoss:
	def __init__(self,)
		self.loss = None # 损失
		self.y = None # softmax输出值
		self.t = None # 标签(one-hot vector)
	
	def forward(self, x, t):
		self.t = t
		self.y = softmax(x)
		self.loss = cross_entropy_error(self.y ,self.t)

		return self.loss

	def backward(self, dout=1):
		batch_size = self.t.shape[0]
		dx = (self.y - self.t) / batch_size
		
		return dx

误差反向传播的实现

和上一篇文章两层神经网络的实现一样,这里我们的步骤仍然是:

  1. mini-batch: 从训练数据中随机选择一部分样本;
  2. 计算梯度;
  3. 更新参数;
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict

class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size,
                 weight_init_std=0.01):
        # 初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std * \
                            np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

        # 生成层
        self.layers = OrderedDict()
        self.layers['Affine1'] = \
            Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = \
            Affine(self.params['W2'], self.params['b2'])

        self.lastLayer = SoftmaxWithLoss()

    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    # x:输入数据, t:监督数据
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    # x:输入数据, t:监督数据
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads
	
	# 在上一篇文章基础上,新增的函数,误差反向传播
    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 设定
        grads = {}
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db

        return grads
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# 读入数据
(x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 通过误差反向传播法求梯度
    grad = network.gradient(x_batch, t_batch)

    # 更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)

参考资料

[1] 斋藤康毅. (2018). 深度学习入门:基于Python的理论与实践. 人民邮电出版社.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329694.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:页表详解(虚拟地址到物理地址转换过程)

文章目录 前言一、分页式存储管理1.1 虚拟地址和页表的由来1.2 物理内存管理与页表的数据结构 二、 多级页表2.1 页表项2.2 多级页表的组成 总结 前言 在我们之前的学习中&#xff0c;我们对于页表的认识仅限于虚拟地址到物理地址转换的桥梁&#xff0c;然而对于具体的转换实现…

PostgreSQL 一文从安装到入门掌握基本应用开发能力!

本篇文章主要讲解 PostgreSQL 的安装及入门的基础开发能力,包括增删改查,建库建表等操作的说明。navcat 的日常管理方法等相关知识。 日期:2025年4月6日 作者:任聪聪 一、 PostgreSQL的介绍 特点:开源、免费、高性能、关系数据库、可靠性、稳定性。 官网地址:https://w…

WEB安全--内网渗透--LMNTLM基础

一、前言 LM Hash和NTLM Hash是Windows系统中的两种加密算法&#xff0c;不过LM Hash加密算法存在缺陷&#xff0c;在Windows Vista 和 Windows Server 2008开始&#xff0c;默认情况下只存储NTLM Hash&#xff0c;LM Hash将不再存在。所以我们会着重分析NTLM Hash。 在我们内…

8.用户管理专栏主页面开发

用户管理专栏主页面开发 写在前面用户权限控制用户列表接口设计主页面开发前端account/Index.vuelangs/zh.jsstore.js 后端Paginator概述基本用法代码示例属性与方法 urls.pyviews.py 运行效果 总结 欢迎加入Gerapy二次开发教程专栏&#xff01; 本专栏专为新手开发者精心策划了…

室内指路机器人是否支持与第三方软件对接?

嘿&#xff0c;你知道吗&#xff1f;叁仟室内指路机器人可有个超厉害的技能&#xff0c;那就是能和第三方软件 “手牵手” 哦&#xff0c;接下来就带你一探究竟&#xff01; 从技术魔法角度看哈&#xff1a;好多室内指路机器人都像拥有超能力的小魔法师&#xff0c;采用开放式…

从代码上深入学习GraphRag

网上关于该算法的解析都停留在大概流程上&#xff0c;但是具体解析细节未知&#xff0c;由于代码是PipeLine形式因此阅读起来比较麻烦&#xff0c;本文希望通过阅读项目代码来解析其算法的具体实现细节&#xff0c;特别是如何利用大模型来完成图谱生成和检索增强的实现细节。 …

【Redis】通用命令

使用者通过redis-cli客户端和redis服务器交互&#xff0c;涉及到很多的redis命令&#xff0c;redis的命令非常多&#xff0c;我们需要多练习常用的命令&#xff0c;以及学会使用redis的文档。 一、get和set命令&#xff08;最核心的命令&#xff09; Redis中最核心的两个命令&…

微前端随笔

✨ single-spa&#xff1a; js-entry 通过es-module 或 umd 动态插入 js 脚本 &#xff0c;在主应用中发送请求&#xff0c;来获取子应用的包&#xff0c; 该子应用的包 singleSpa.registerApplication({name: app1,app: () > import(http://localhost:8080/app1.js),active…

C++中的浅拷贝和深拷贝

浅拷贝只是将变量的值赋予给另外一个变量&#xff0c;在遇到指针类型时&#xff0c;浅拷贝只会把当前指针的值&#xff0c;也就是该指针指向的地址赋予给另外一个指针&#xff0c;二者指向相同的地址&#xff1b; 深拷贝在遇到指针类型时&#xff0c;会先将当前指针指向地址包…

车载诊断架构 --- 整车重启先后顺序带来的思考

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…

【C++11(下)】—— 我与C++的不解之缘(三十二)

前言 随着 C11 的引入&#xff0c;现代 C 语言在语法层面上变得更加灵活、简洁。其中最受欢迎的新特性之一就是 lambda 表达式&#xff08;Lambda Expression&#xff09;&#xff0c;它让我们可以在函数内部直接定义匿名函数。配合 std::function 包装器 使用&#xff0c;可以…

Windows 10/11系统优化工具

家庭或工作电脑使用时间久了&#xff0c;会出现各种各样问题&#xff0c;今天给大家推荐一款专为Windows 10/11系统设计的全能优化工具&#xff0c;该软件集成了超过40项专业级实用程序&#xff0c;可针对系统性能进行深度优化、精准调校、全面清理、加速响应及故障修复。通过系…

浅谈在HTTP中GET与POST的区别

从 HTTP 报文来看&#xff1a; GET请求方式将请求信息放在 URL 后面&#xff0c;请求信息和 URL 之间以 &#xff1f;隔开&#xff0c;请求信息的格式为键值对&#xff0c;这种请求方式将请求信息直接暴露在 URL 中&#xff0c;安全性比较低。另外从报文结构上来看&#xff0c…

LightRAG实战:轻松构建知识图谱,破解传统RAG多跳推理难题

作者&#xff1a;后端小肥肠 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 2025防失业预警&#xff1a;不会用DeepSeek-RAG建知识库的人正在被淘汰_deepseek-embedding-CSDN博客 从PDF到精准答案&#xff1a;Coze…

C++多线程编码二

1.lock和try_lock lock是一个函数模板&#xff0c;可以支持多个锁对象同时锁定同一个&#xff0c;如果其中一个锁对象没有锁住&#xff0c;lock函数会把已经锁定的对象解锁并进入阻塞&#xff0c;直到多个锁锁定一个对象。 try_lock也是一个函数模板&#xff0c;尝试对多个锁…

垃圾回收——三色标记法(golang使用)

三色标记法(tricolor mark-and-sweep algorithm)是传统 Mark-Sweep 的一个改进&#xff0c;它是一个并发的 GC 算法&#xff0c;在Golang中被用作垃圾回收的算法&#xff0c;但是也会有一个缺陷&#xff0c;可能程序中的垃圾产生的速度会大于垃圾收集的速度&#xff0c;这样会导…

Windows环境下开发pyspark程序

Windows环境下开发pyspark程序 一、环境准备 1.1. Anaconda/Miniconda&#xff08;Python环境&#xff09; 如果不怕包的版本管理混乱&#xff0c;可以直接使用已有的Python环境。 需要安装anaconda/miniconda&#xff08;python3.8版本以上&#xff09;&#xff1a;Anaconda…

SSM婚纱摄影网的设计

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 SS…

1110+款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100+ Icons Easily Customize

1110款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100 Icons Easily Customize 产品特点 — 24 x 24 px 网格大小 — 2px 线条描边 — 所有形状都是基于矢量的 — 平滑和圆角 — 易于更改颜色 类别 &#x1f6a8; 警报和反馈 ⬆️ 箭头 &…

Llama 4 家族:原生多模态 AI 创新的新时代开启

0 要点总结 Meta发布 Llama 4 系列的首批模型&#xff0c;帮用户打造更个性化多模态体验Llama 4 Scout 是有 170 亿激活参数、16 个专家模块的模型&#xff0c;同类中全球最强多模态模型&#xff0c;性能超越以往所有 Llama 系列模型&#xff0c;能在一张 NVIDIA H100 GPU 上运…