深度学习入门——误差反向传播

news2024/11/14 20:14:55

要正确理解误差反向传播法,我个人认为有两种方法:一种是基于数学式;另一种是基于计算图(computational graph)

前者是比较常见的方法,机器学习相关的图书中多数都是以数学式为中心展开论述的。因为这种方法严密且简洁,所以确实非常合理,但如果一上来就围绕数学式进行探讨,会忽略一些根本的东西,止步于式子的罗列。因此,本章希望大家通过计算图,直观地理解误差反向传播法

计算图

计算图将计算过程用图形表示出来。这里说的图形是数据结构图,通过多个节点和边表示(连接节点的直线称为“边”)

用计算图求解

问题1: 太郎在超市买了2 个100 日元一个的苹果,消费税是10%,请计算支付金额。

计算图通过节点和箭头表示计算过程。节点用○表示,○中是计算的内容。将计算的中间结果写在箭头的上方,表示各个节点的计算结果从左向右传递

image-20240717103436166

image-20240717103759162

问题2: 太郎在超市买了2 个苹果、3 个橘子。其中,苹果每个100 日元,橘子每个150 日元。消费税是10%,请计算支付金额。

image-20240717103849208

用计算图解题的情况下,需要按如下流程进行。

  1. 构建计算图
  2. 在计算图上,从左向右进行计算

“从左向右进行计算”是一种正方向上的传播,简称为正向传播(forward propagation)

可以考虑反向(从图上看的话,就是从右向左)的传播。实际上,这种传播称为反向传播(backward propagation)

局部计算

各个节点处只需进行与自己有关的计算(在这个例子中是对输入的两个数字进行加法运算),不用考虑全局。

image-20240717104203729

计算图可以集中精力于局部计算。无论全局的计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。虽然局部计算非常简单,但是通过传递它的计算结果,可以获得全局的复杂计算的结果。

[!IMPORTANT]

计算图将复杂的计算分割成简单的局部计算,和流水线作业一样,将局部计算的结果传递给下一个节点。在将复杂的计算分解成简单的计算这一点上与汽车的组装有相似之处。

为何用计算图解题

  1. 无论全局是多么复杂的计算,都可以通过局部计算使各个节点致力于简单的计算,从而简化问题
  2. 利用计算图可以将中间的计算结果全部保存起来
  3. 可以通过反向传播高效计算导数

综上,计算图的优点是,可以通过正向传播和反向传播高效地计算各个变量的导数值

链式法则

传递这个局部导数的原理,是基于链式法则(chain rule)的。本节将介绍链式法则,并阐明它是如何对应计算图上的反向传播的。

计算图的反向传播

image-20240717111003255

反向传播的计算顺序是,将信号E乘以节点的局部导数
( ∂ y ∂ x ) (\frac{\partial y}{ \partial x}) xy
,然后将结果传递给下一个节点

这里所说的局部导数是指正向传播中y = f(x) 的导数

通过这样的计算,可以高效地求出导数的值,这是反向传播的要点

什么是链式法则

先从复合函数说起。复合函数是由多个函数构成的函数

链式法则是关于复合函数的导数的性质:如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。
Z = t 2 Z=t^2 Z=t2

t = x + y t = x+y t=x+y

image-20240717112309901

链式法则和计算图

image-20240717112252577
∂ z ∂ z ∂ z ∂ t ∂ t ∂ x = ∂ z ∂ t ∂ t ∂ x = ∂ z ∂ x \frac{\partial z}{\partial z}\frac{\partial z}{\partial t}\frac{\partial t}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x}=\frac{\partial z}{\partial x} zztzxt=tzxt=xz
对应“z关于x的导数”。也就是说,反向传播是基于链式法则的

image-20240717113856734

反向传播

上一节介绍了计算图的反向传播是基于链式法则成立的。本节将以“+”和“×”等运算为例,介绍反向传播的结构

加法节点的反向传播

因为加法节点的反向传播只乘以1,所以输入的值会原封不动地流向下一个节点。

乘法节点的反向传播

image-20240717115245850

请添加图片描述

乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号

苹果的例子

image-20240717140800251

image-20240717140809663

简单层的实现

本节将用Python 实现前面的购买苹果的例子。这里,我们把要实现的计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层”(AddLayer)。

乘法层的实现

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 # 翻转x和y
            dy = dout * self.x
            return dx, dy

image-20240717173155185

此外,关于各个变量的导数可由backward()求出。

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax) # 2.2 110 200

加法层的实现

class AddLayer:
    def __init__(self):
        pass
    def forward(self, x, y):
        out = x + y
        return out
    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1
        return dx, dy

image-20240717173619423

计算图中层的实现(这里是加法层和乘法层)非常简单,使用这些层可以进行复杂的导数计算

激活函数层的实现

我们将计算图的思路应用到神经网络中。这里,我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。

ReLU层

image-20240717174727912

image-20240717174851259

现在我们来实现ReLU层。在神经网络的层的实现中,一般假定forward()和backward()的参数是NumPy数组

class Relu:
    def __init__(self):
        self.mask = None
    def forward(self, x):
        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

[!IMPORTANT]

ReLU 层的作用就像电路中的开关一样。正向传播时,有电流通过的话,就将开关设为ON;没有电流通过的话,就将开关设为OFF。反向传播时,开关为ON 的话,电流会直接通过;开关为OFF 的话,则不会有电流通过

Sigmoid层

image-20240717180633733

image-20240717182021084

image-20240717182311229

image-20240717182321998

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

Affine/Softmax层的实现

Affine层

image-20240717183613367

[!IMPORTANT]

神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”A。因此,这里将进行仿射变换的处理实现为“Affine层”

几何中,仿射变换包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算

image-20240717184224324

反向传播

image-20240717184602252

image-20240717184713254

image-20240717185310403

批版本的Affine层

image-20240717185421351

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 out
    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-with-Loss层

image-20240717204408259

[!IMPORTANT]

神经网络中进行的处理有推理(inference)和学习两个阶段。神经网络的推理通常不使用Softmax 层,会将最后一个Affine 层的输出作为识别结果。

神经网络中未被正规化的输出结果(图5-28 中Softmax 层前面的Affine 层的输出)有时
被称为“得分”。

当神经网络的推理只需要给出一个答案的情况下,因为此时只对得分最大值感兴趣,所以不需要Softmax

不过,神经网络的学习阶段则需要Softmax

image-20240717204943609

图5-29 的计算图可以简化成图5-30。

image-20240717205436821

由于(y1, y2, y3)是Softmax 层的输出,(t1, t2, t3)是监督数据,所以(y1 − t1, y2 − t2, y3 − t3)是Softmax 层的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质。

神经网络学习的目的就是通过调整权重参数,使神经网络的输出(Softmax的输出)接近教师标签。因此,必须将神经网络的输出与教师标签的误差高效地传递给前面的层。

大的误差会向前面的层传播,所以Softmax层前面的层会从这个大的误差中学习到“大”的内容。

[!IMPORTANT]

使用交叉熵误差作为softmax 函数的损失函数后,反向传播得到(y1 − t1, y2 − t2, y3 − t3)这样“ 漂亮”的结果。

这样“漂亮”的结果并不是偶然的,而是为了得到这样的结果,特意设计了交叉熵误差函数。

回归问题中输出层使用“恒等函数”,损失函数使用“平方和误差”,也是出于同样的理由

class 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

请注意反向传播时,将要传播的值除以批的大小(batch__size)后,传递给前面的层的是单个数据的误差

误差反向传播法的实现

本节我们将通过组装已经实现的层来构建神经网络

神经网络学习的全貌图

神经网络学习的步骤如下所示。

  • 前提
    • 神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面4 个步骤
  • 步骤1(mini-batch)
    • 从训练数据中随机选择一部分数据
  • 步骤2(计算梯度)
    • 计算损失函数关于各个权重参数的梯度
  • 步骤3(更新参数)
    • 将权重参数沿梯度方向进行微小的更新
  • 步骤4(重复)
    • 重复步骤1、步骤2、步骤3

误差反向传播法会在步骤2 中出现。上一章中,我们利用数值微分求得了这个梯度。数值微分虽然实现简单,但是计算要耗费较多的时间。和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度。

对应误差反向传播法的神经网络的实现

image-20240717213003094

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

将神经网络的层保存为OrderedDic这一点非常重要,OrderedDict是有序字典,“有序”是指它可以
记住向字典里添加元素的顺序。因此,神经网络的正向传播只需按照添加元素的顺序调用各层的forward()方法就可以完成处理,而反向传播只需要按照相反的顺序调用各层即可。

像这样通过将神经网络的组成元素以层的方式实现,可以轻松地构建神经网络

用层进行模块化的实现具有很大优点

通过各个层内部实现的正向传播和反向传播,就可以正确计算进行识别处理或学习所需的梯度

误差反向传播法的梯度确认

那么数值微分有什么用呢?
实际上,在确认误差反向传播法的实现是否正确时,是需要用到数值微分的。

数值微分的优点是实现简单,因此,一般情况下不太容易出错

而误差反向传播法的实现很复杂,容易出错

所以,经常会比较数值微分的结果和误差反向传播法的结果,以确认误差反向传播法的实现是否正确

确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致(严格地讲,是非常相近)的操作称为梯度确认(gradient check)。

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)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
# 求各个权重的绝对误差的平均值
for key in grad_numerical.keys():
    diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
    print(key + ":" + str(diff))

使用误差反向传播法的学习

最后,我们来看一下使用了误差反向传播法的神经网络的学习的实现。

和之前的实现相比,不同之处仅在于通过误差反向传播法求梯度这一点

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)

小结

本章我们介绍了将计算过程可视化的计算图,并使用计算图,介绍了神经网络中的误差反向传播法,并以层为单位实现了神经网络中的处理。

我们学过的层有ReLU层、Softmax-with-Loss 层、Affine层、Softmax 层等,这些层中实现了forward和backward方法,通过将数据正向和反向地传播,可以高效地计算权重参数的梯度。

通过使用层进行模块化,神经网络中可以自由地组装层,轻松构建出自己喜欢的网络。

• 通过使用计算图,可以直观地把握计算过程。
• 计算图的节点是由局部计算构成的。局部计算构成全局计算。
• 计算图的正向传播进行一般的计算。通过计算图的反向传播,可以计算各个节点的导数。
• 通过将神经网络的组成元素实现为层,可以高效地计算梯度(反向传播法)。
• 通过比较数值微分和误差反向传播法的结果,可以确认误差反向传播法的实现是否正确(梯度确认)。

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

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

相关文章

达梦数据库的系统视图v$sqltext

达梦数据库的系统视图v$sqltext 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$SQLTEXT 是一个系统视图&#xff0c;用于显示当前正在执行或最近执行的SQL语句的文本信息。这个视图对于监控和分析数据库中的SQL活动非常有用&#xff0c;尤其是在需要调试性…

【python】OpenCV—Coordinates Sorted Clockwise

文章目录 1、需求介绍2、算法实现3、完整代码 1、需求介绍 调用 opencv 库&#xff0c;绘制轮廓的矩形边框&#xff0c;坐标顺序为右下→左下→左上→右上&#xff0c;我们实现一下转化为熟悉的 左上→右上→右下→左下 形式 按照这样的顺序组织边界框坐标是执行透视转换或匹…

13. C++继承 | 详解 | 虚拟继承及底层实现

目录 1.定义 1.1继承的概念 1.2 继承的定义 2. 对象赋值转换 3. 继承中的作用域 a. 隐藏/重定义 (Hiding/Redefinition) b. 重载 (Overloading) c. 重写/覆盖 (Overriding) d. 编译报错 (Compilation Error) 4. 派生类的默认成员函数 构造 拷贝构造 运算符重载 析…

处理uniapp刷新后,点击返回按钮跳转到登录页的问题

在使用uniapp的原生返回的按钮时&#xff0c;如果没有刷新会正常返回到对应的页面&#xff0c;如果刷新后会在当前页反复横跳&#xff0c;或者跳转到登录页。那个时候我第一个想法时&#xff1a;使用浏览器的history.back()方法。因为浏览器刷新后还是可以通过右上角的返回按钮…

Vscode+Pyside6开发之虚拟环境配置以及错误解决

Pyside开发之虚拟环境配置以及错误解决 开发环境一、项目创建以及虚拟环境设置1.创建项目2. 新建py文件,新建虚拟环境3.激活虚拟环境二、项目位置改变pip命令报错1.删除原来的虚拟环境2. 产生包列表文件requirements.txt3.重新创建虚拟环境4.重新安装包文件5.其他错误开发环境…

操作系统 输入输出系统

输入输出系统 I/O系统的功能、模型和接口 功能 隐藏物理设备的细节&#xff1a;仅向上层进程提供少量的、抽象的读/写命令 与设备无关性&#xff1a;用户不仅可以使用抽象的I/O命令&#xff0c;还可使用抽象的逻辑设备名来使用设备 提高处理机和I/O设备的利用率&#xff1a;…

IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

目录 1. 前言 2. 创建SpringBoot项目 3. Maven依赖引入 4. 创建定时任务 5. 问题&#xff1a;执行时间延迟和单线程执行 5.1 问题原因 5.2 解决方式 1. 前言 在现代化应用中&#xff0c;定时任务&#xff08;Scheduled Tasks&#xff09;是不可或缺的一部分&#xff…

pytorch学习(五)tensorboard使用

1. 创建环境 首先创建一个环境: conda create -n pytorch conda activate pytorch 然后安装tensorboard pip install tensorboard 安装opencv pip install opencv-python 2. 简单的案例 标量和图像的显示&#xff1a; 2.1标量实现的方法是add_scalar,第一个参数是给显…

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…

21天学通C++:第十三、十四章节

第十三章&#xff1a;类型转换运算符 类型转换是一种机制&#xff0c;让程序员能够暂时或永久性改变编译器对对象的解释。注意&#xff0c;这并不意味着程序员改变了对象本身&#xff0c;而只是改变了对对象的解释。可改变对象解释方式的运算符称为类型转换运算符。 为何需要…

数据库端口LookUp功能:从数据库中获取并添加数据到XML

本文将为大家介绍如何使用知行之桥EDI系统数据库端口的Lookup功能&#xff0c;从数据库中获取数据&#xff0c;并添加进输入的XML中。 使用场景&#xff1a;期待以输入xml中的值为判断条件从数据库中获取数据&#xff0c;并添加进输入xml中。 例如&#xff1a;接收到包含采购…

Linux 06-01:简易shell编写

考虑一下这个与shell典型的互动&#xff1a;ls、ps 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运…

Three.js 实战【2】—— 船模型海上场景渲染

停止了好久没有更新three这方面的文章了&#xff0c;从上两年还是vue2&#xff0c;一下子都换到vue3了&#xff0c;下面这些three都是基于vue3来进行开发的哈&#xff0c;先看一下这篇文章实现的效果哈。其中关于模型什么的资源都放在Git上了 初始化场景 安装three就直接通过n…

Java——集合(Queue)

1.Queue 接口的常用功能 除了基本的 Collection 操作外&#xff0c;队列还提供其他的插入、提取和检查操作。每个方法都存在 两种形式&#xff1a;一种抛出异常&#xff08;操作失败时&#xff09;&#xff0c;另一种返回一个特殊值&#xff08; null 或 false &#xff…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

爬虫案例(读书网)(下)

上篇链接&#xff1a; CSDN-读书网https://mp.csdn.net/mp_blog/creation/editor/139306808 可以看见基本的全部信息&#xff1a;如(author、bookname、link.....) 写下代码如下&#xff1a; import requests from bs4 import BeautifulSoup from lxml import etreeheaders{…

SSD实现

一、模型 此模型主要由基础网络组成&#xff0c;其后是几个多尺度特征块。基本网络用于从输入图像中提取特征&#xff0c;因此它可以使用深度卷积神经网络。 单发多框检测选用了在分类层之前截断的VGG&#xff0c;现在也常用ResNet替代&#xff1b;可以设计基础网络&#xff0c…

【LeetCode】162. 寻找峰值

1. 题目 2. 分析 这道题的难点有二&#xff1a;第一&#xff0c;知道用二分法求解&#xff1b;第二&#xff0c;二分判断的标准是什么&#xff1f;传统的题目的二分标注都是跟某个固定的值做比较&#xff0c;但是此题不然。此题的比较对象是相邻的元素。 不要硬凭自己的脑子…

spring是如何解决循环依赖的,为什么不是两级

1. Spring使用三级缓存来解决循环依赖问题 Spring使用三级缓存来解决循环依赖问题&#xff0c;‌而不是使用两级缓存。‌ 在Spring框架中&#xff0c;‌解决循环依赖的关键在于正确地管理Bean的生命周期和依赖关系。‌循环依赖指的是两个或多个Bean相互依赖&#xff0c;‌如果…

FastApi地理坐标数据存取实践

说明&#xff1a; 应用Pydantic Model 验证/出入 数据&#xff0c; SqlAlchemy Model数据实体&#xff0c;Fastapi提供API机制支持。数据表的坐标字段采用Mysql的GEOMETRY类型目前还没成功使用Pydantic的Coordinate类型&#xff0c;待后续改良 要点&#xff1a; 输出的结果是…