详解MindSpore自定义模型损失函数

目录
  • 一、技术背景
  • 二、MindSpore内置的损失函数
  • 三、自定义损失函数
  • 四、自定义其他算子
  • 五、多层算子的应用
  • 六、重定义reduction

一、技术背景

损失函数是机器学习中直接决定训练结果好坏的一个模块,该函数用于定义计算出来的结果或者是神经网络给出的推测结论与正确结果的偏差程度,偏差的越多,就表明对应的参数越差。而损失函数的另一个重要性在于会影响到优化函数的收敛性,如果损失函数的指数定义的太高,稍有参数波动就导致结果的巨大波动的话,那么训练和优化就很难收敛。一般我们常用的损失函数是MSE(均方误差)和MAE(平均标准差)等。那么这里我们尝试在MindSpore中去自定义一些损失函数,可用于适应自己的特殊场景。

二、MindSpore内置的损失函数

刚才提到的MSE和MAE等常见损失函数,MindSpore中是有内置的,通过net_loss = nn.loss.MSELoss()即可调用,再传入Model中进行训练,具体使用方法可以参考如下拟合一个非线性函数的案例:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())
net_loss = nn.loss.MSELoss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, net_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

训练的结果如下:

epoch: 1 step: 160, loss is 2.5267093

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[1.0694231  0.12706374]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.186701]

The total time cost is: 8.412306308746338s

最终优化出来的loss值是2.5,不过在损失函数定义不同的情况下,单纯只看loss值是没有意义的。所以通常是大家统一定一个测试的标准,比如大家都用MAE来衡量最终训练出来的模型的好坏,但是中间训练的过程不一定采用MAE来作为损失函数。

三、自定义损失函数

由于python语言的灵活性,使得我们可以继承基本类和函数,只要使用mindspore允许范围内的算子,就可以实现自定义的损失函数。我们先看一个简单的案例,暂时将我们自定义的损失函数命名为L1Loss:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.abs = ops.Abs()

    def construct(self, base, target):
        x = self.abs(base - target)
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

这里自己定义的内容实际上有两个部分,一个是construct函数中的计算结果的函数,比如这里使用的是求绝对值。另外一个定义的部分是reduction参数,我们从mindspore的源码中可以看到,这个reduction函数可以决定调用哪一种计算方法,定义好的有平均值、求和、保持不变三种策略。

那么最后看下自定义的这个损失函数的运行结果:

epoch: 1 step: 160, loss is 1.8300734

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[ 1.2687287  -0.09565887]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [3.7297544]

The total time cost is: 7.0749146938323975s

这里不必太在乎loss的值,因为前面也提到了,不同的损失函数框架下,计算出来的值就是不一样的,小一点大一点并没有太大意义,最终还是需要大家统一一个标准才能够进行很好的衡量和对比。

四、自定义其他算子

这里我们仅仅是替换了一个abs的算子为square的算子,从求绝对值变化到求均方误差,这里只是修改了一个算子,内容较为简单:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()

    def construct(self, base, target):
        x = self.square(base - target)
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

关于更多的算子内容,可以参考下这个链接

(https://www.mindspore.cn/doc/api_python/zh-CN/r1.2/mindspore/mindspore.ops.html)中的内容,

上述代码的运行结果如下:

epoch: 1 step: 160, loss is 2.5267093

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[1.0694231  0.12706374]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.186701]

The total time cost is: 6.87545919418335s

可以从这个结果中发现的是,计算出来的结果跟最开始使用的内置的MSELoss结果是一样的,这是因为我们自定义的这个求损失函数的形式与内置的MSE是吻合的。

五、多层算子的应用

上面的两个例子都是简单的说明了一下通过单个算子构造的损失函数,其实如果是一个复杂的损失函数,也可以通过多个算子的组合操作来进行实现:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()

    def construct(self, base, target):
        x = self.square(self.square(base - target))
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

这里使用的函数是两个平方算子,也就是四次方的均方误差,运行结果如下:

epoch: 1 step: 160, loss is 16.992222

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[0.14460069 0.32045612]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.6676607]

The total time cost is: 7.253541946411133s

在实际的运算过程中,我们肯定不能够说提升损失函数的幂次就一定能够提升结果的优劣,但是通过多种基础算子的组合,理论上说我们在一定的误差允许范围内,是可以实现任意的一个损失函数(通过泰勒展开取截断项)的。

六、重定义reduction

方才提到这里面自定义损失函数的两个重点,一个是上面三个章节中所演示的construct函数的重写,这部分实际上是重新设计损失函数的函数表达式。另一个是reduction的自定义,这部分关系到不同的单点损失函数值之间的关系。举个例子来说,如果我们将reduction设置为求和,那么get_loss()这部分的函数内容就是把所有的单点函数值加起来返回一个最终的值,求平均值也是类似的。那么通过自定义一个新的get_loss()函数,我们就可以实现更加灵活的一些操作,比如我们可以选择将所有的结果乘起来求积而不是求和(只是举个例子,大部分情况下不会这么操作)。在python中要重写这个函数也容易,就是在继承父类的自定义类中定义一个同名函数即可,但是注意我们最好是保留原函数中的一些内容,在原内容的基础上加一些东西,冒然改模块有可能导致不好定位的运行报错。

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean", config=True):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()
        self.config = config

    def construct(self, base, target):
        x = self.square(base - target)
        return self.get_loss(x)

    def get_loss(self, x, weights=1.0):
        print ('The data shape of x is: ', x.shape)
        input_dtype = x.dtype
        x = self.cast(x, ms.common.dtype.float32)
        weights = self.cast(weights, ms.common.dtype.float32)
        x = self.mul(weights, x)
        if self.reduce and self.average:
            x = self.reduce_mean(x, self.get_axis(x))
        if self.reduce and not self.average:
            x = self.reduce_sum(x, self.get_axis(x))
        if self.config:
            x = self.reduce_mean(x, self.get_axis(x))
            weights = self.cast(-1.0, ms.common.dtype.float32)
            x = self.mul(weights, x)
        x = self.cast(x, input_dtype)
        return x

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

上述代码就是一个简单的案例,这里我们所做的操作,仅仅是把之前均方误差的求和改成了求和之后取负数。还是需要再强调一遍的是,虽然我们定义的函数是非常简单的内容,但是借用这个方法,我们可以更加灵活的去按照自己的设计定义一些定制化的损失函数。上述代码的执行结果如下:

The data shape of x is: 

(10, 10,  1)

...

The data shape of x is: 

(10, 10,  1)

epoch: 1 step: 160, loss is -310517200.0

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[-6154.176    667.4569]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [-16418.32]

The total time cost is: 6.681089878082275s

一共打印了160个The data shape of x is...,这是因为我们在划分输入的数据集的时候,选择了将160个数据划分为每个batch含10个元素的模块,那么一共就有16个batch,又对这16个batch重复10次,那么就是一共有160个batch,计算损失函数时是以batch为单位的,但是如果只是计算求和或者求平均值的话,不管划分多少个batch结果都是一致的。

以上就是详解MindSpore自定义模型损失函数的详细内容,更多关于MindSpore自定义模型损失函数的资料请关注我们其它相关文章!

(0)

相关推荐

  • Python基础之注释的用法

    前言 Python代码的基本规范如下: 1.Python 文件将以 .py 为扩展名. 2.在Python中严格区分大小写(大小写敏感),如果写错了大小写,程序会报错. 3.Python中的每一行就是一条语句,每条语句以换行结束,不用;分号结束. 4.Python是缩进严格的语言,所以在Python中不要随便写缩进. 缩进的好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码. 缩进的坏处就是复制-粘贴功能失效了,当你重构代码时,粘贴过去的代码必须重新

  • Python办公自动化之教你用Python批量识别发票并录入到Excel表格中

    一.场景描述 这里有以四张发票为例(辰哥网上搜的),将发票图片放到pic文件夹下. 随便打开一张发票 提取目标:金额.名称.纳税人识别号.开票人. 最后将每一张发票的这四个内容保存到excel中: 二.准备环境 需要用到的库如下: from PIL import Image as PI import pyocr import pyocr.builders from cnocr import CnOcr 安装的命令如下: pip install pyocr pip install cnocr 发票

  • 在Linux下使用命令行安装Python

    一般的Linux上都有默认的Python版本,CentOS6.5默认的Python版本的2.6.6的,因为工作原因,这里需要用到Python3.6.3的版本,在这里,小编将会一步步的教大家进行再Linux下Python3的安装. 一.安装之前查看自带的Python的版本 二.上传并解压Python3.6.3 三.python安装之前需要一些必要的模块,比如openssl,readline等,如果没有这些模块后来使用会出现一些问题,比如没有openssl则不支持ssl相关的功能,并且pip3在安装

  • 详解MindSpore自定义模型损失函数

    目录 一.技术背景 二.MindSpore内置的损失函数 三.自定义损失函数 四.自定义其他算子 五.多层算子的应用 六.重定义reduction 一.技术背景 损失函数是机器学习中直接决定训练结果好坏的一个模块,该函数用于定义计算出来的结果或者是神经网络给出的推测结论与正确结果的偏差程度,偏差的越多,就表明对应的参数越差.而损失函数的另一个重要性在于会影响到优化函数的收敛性,如果损失函数的指数定义的太高,稍有参数波动就导致结果的巨大波动的话,那么训练和优化就很难收敛.一般我们常用的损失函数是M

  • 详解Django自定义图片和文件上传路径(upload_to)的2种方式

    最近在做一个仿知乎网站的项目了,里面涉及很多图片和文件上传.趁此机会我给大家总结下Django自定义图片和文件上传路径的2种方式吧. 方法1: 在Django模型中定义upload_to选项. Django模型中的ImageField和FileField的upload_to选项是必填项,其存储路径是相对于MEIDA_ROOT而来的. 我们来看一个简单案例(如下所示).如果你的MEDIA_ROOT是/media/文件夹,而你的上传文件夹upload_to="avatar", 那么你上传的

  • TabLayout用法详解及自定义样式

    TabLayout的默认样式: app:theme="@style/Widget.Design.TabLayout" 从系统定义的该样式继续深入: <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout"> <item name="tabGravity">fill</item> <item n

  • 详解java自定义类

    引用数据类型(类) 引用数据类型分类 提到引用数据类型(类),其实我们对它并不陌生,之前使用过的Scanner类.Random类. 我们可以把类的类型为两种: 第一种,Java为我们提供好的类,如Scanner类,Random类等,这些已存在的类中包含了很多的方法与属性,可供我们使用. 第二种,我们自己创建的类,按照类的定义标准,可以在类中包含多个方法与属性,来供我们使用. 这里我们主要介绍第二种情况的简单使用. 自定义数据类型概述 我们在Java中,将现实生活中的事物抽象成了代码.这时,我们可

  • 详解JavaScript自定义函数

    一.定义方法:在Javascript中必须用function关键字 function funcName(arg1,arg2...) { statements; return "变量值"; //返回值可以根据函数的需要 } 函数名是函数自身的一个引用.此种方式创立的函数引用是独立存在的,无法删除. 1.调用函数:函数名(参数列表). 传递的参数不必与函数定义的参数个数一致,并且可以设定函数参数的默认值. function example(a,b){ var a = arguments[0

  • 详解JavaScript执行模型

    JavaScript执行模型 引言 JavaScript是一个单线程(Single-threaded)异步(Asynchronous)非阻塞(Non-blocking)并发(Concurrent)语言,这些语言效果通过一个调用栈(Call Stack).一个事件循环(Event Loop).一个回调队列(Callback Queue)有些时候也叫任务队列(Task Queue)与跟运行环境相关的API组成. 概念 调用栈 Call Stack 调用栈是一个LIFO后进先出数据结构的函数运行栈,它

  • 详解Vue自定义指令及使用

    一.什么是指令 学习 vue 的时候肯定会接触指令,那么什么是指令呢? 在 vue 中提供了一些对于页面和数据更为方便的输出,这些操作就叫做指令,以 v-xxx 表示,比如 html 页面中的属性 <div v-xxx ></div> 比如在 angular 中 以 ng-xxx 开头的就叫做指令 指令中封装了一些 DOM 行为,结合属性作为一个暗号,暗号有对应的值,根据不同的值,会进行相关 DOM 操作的绑定,即可以进行一些模板的操作 vue 中常用的一些内置 v- 指令 v-t

  • 详解Vue 自定义hook 函数

    目录 定义 使用 封装发ajax请求的hook函数(ts版本) 定义 什么是hook? 本质是一个函数,把 setup 函数中使用的 Composition API (组合API)进行了封装 类似于 vue2.x 中的 mixin 自定义 hook 的优势: 复用代码,让 setup 中的逻辑更清楚易懂 使用 首先我们做一个功能,鼠标点击屏幕,获取坐标: <template> <h2>当前鼠标的坐标是:x:{{ point.x }},y:{{ point.y }}</h2&g

  • SpringBoot详解实现自定义异常处理页面方法

    目录 1.相关介绍 2.代码实现 3.运行测试 1.相关介绍 当发生异常时, 跳转到我们自定义的异常处理页面. SpringBoot中只需在静态资源目录下创建一个error文件夹, 并把异常处理页面放入其中, 页面的命名与异常错误代码对应, 如404.html, 500.html. 5xx.html可以对应所有错误代码为5开头的错误 默认静态资源目录为类路径(resources)下的: /static /public /resources /META-INF/resources 2.代码实现 H

  • 详解Springboot自定义异常处理

    背景 Springboot 默认把异常的处理集中到一个ModelAndView中了,但项目的实际过程中,这样做,并不能满足我们的要求.具体的自定义异常的处理,参看以下 具体实现 如果仔细看完spring boot的异常处理详解,并且研究过源码后,我觉得具体的实现可以不用看了... 重写定义错误页面的url,默认只有一个/error @Bean public EmbeddedServletContainerCustomizer containerCustomizer(){ return new E

随机推荐