相关文章

  1. 梯度下降算法、随机梯度下降算法、动量随机梯度下降算法、AdaGrad算法、RMSProp算法、Adam算法详细介绍及其原理详解
  2. 反向传播算法和计算图详细介绍及其原理详解
  3. 激活函数、Sigmoid激活函数、tanh激活函数、ReLU激活函数、Leaky ReLU激活函数、Parametric ReLU激活函数详细介绍及其原理详解

文章目录

  • 相关文章
  • 前言
  • 一、激活函数
    • 1.1 什么是激活函数
    • 1.2 使用激活函数后如何更新参数
    • 1.3 成为激活函数的条件
  • 二、SigmoidSigmoidSigmoid激活函数
    • 2.1 SigmoidSigmoidSigmoid激活函数介绍
    • 2.2 梯度消失
  • 三、tanhtanhtanh激活函数
  • 四、ReLUReLUReLU激活函数
    • 4.1 ReLUReLUReLU激活函数介绍
    • 4.2 稀疏性
    • 4.3 ReLUReLUReLU激活函数的局限性
  • 五、LeakyReLULeaky \quad ReLULeakyReLU激活函数
  • 六、ParametricReLUParametric \quad ReLUParametricReLU激活函数
  • 总结

前言

本文总结了关于激活函数、SigmoidSigmoidSigmoid激活函数、tanhtanhtanh激活函数、ReLUReLUReLU激活函数、LeakyReLULeaky \quad ReLULeakyReLU激活函数、ParametricReLUParametric \quad ReLUParametricReLU激活函数的相关内容,详细介绍了其原理以及优缺点,本文的全部内容如下所示。


一、激活函数

1.1 什么是激活函数

设想这么一种情况,假如我们现在由输入值xxx,经过线性变换y1=w1x+b1y_{1}=w_{1}x+b_{1}y1​=w1​x+b1​,得到y1y_1y1​,这个过程如下图所示:

图1:前向传播过程

很明显这个线性变换就是一条直线,它的图像可能类似如下图所示的增函数:

图2:此神经元的线性变化对应的函数图像

那如果我们在之前的基础上再做一次线性变换y2=w2y1+b2y_{2}=w_{2}y_{1}+b_{2}y2​=w2​y1​+b2​,得到y2y_{2}y2​,那么整个过程就如下图所示:

图3:具有两个神经元的线性前向传播过程

因为我们做的是线性变换,所以说得到的函数图像仍是一条直线,虽然可能变成了如下图所示的减函数:

图4:经过第二个神经元的线性变化对应的函数图像

所以说,就算将很多的线性变换叠加为神经网络,最终也只能解决线性的问题。对于下图来说,虽然每个神经元都可以进行一次计算,但是计算的结果都是线性变换后的结果,最终也只能解决线性拟合问题。

图5:多个神经元的线性前向传播过程

但是在日常生活中,我们遇到的大部分情况都是非线性问题,那么该如何利用神经网络解决非线性问题呢?在讲解这个问题之前,让我们先回顾一下高中生物的一个知识点:神经元,这个结构如下图所示:

图6:生物体中的神经元结构

首先树突接受上一个神经元的信号,然后由轴突引起内外电荷数量的变化,得到一个动作电压,最终使整个神经元被激活,将得到的经过激活后的信号继续向后传导。而深度学习也借鉴了这个过程,每个神经元计算后的结果需要传入非线性函数fff进行非线性计算,这个过程把非线性函数fff当作是从输入到输出的激活过程,所以此非线性函数也被称为激活函数(Activation Function),经过激活函数计算后的值ooo才是此神经元最终的输出值。

图7:激活函数示意图

1.2 使用激活函数后如何更新参数

我们之前学过,可以利用梯度下降算法来最优化参数,那么使用激活函数后如何应用梯度下降来更新参数呢?其实这个问题很简单,我们只需要多计算两步:

  • 损失函数LLL对激活值ooo的偏导
  • 激活函数fff对预测值yyy的偏导

整个过程如下图所示:

图8:具有激活函数的反向传播过程

1.3 成为激活函数的条件

让我们再考虑一个问题,现实世界中有数以万计的函数,谁都可以成为激活函数么?很显然是不是的,某个函数要想作为激活函数来使用,应满足以下几点要求:

  • 为了避免只能解决线性问题,所以激活函数应是非线性函数
  • 为了使用梯度下降算法来最优化参数,所以激活函数应是连续可导函数
  • 为了数值上的稳定,激活函数应能映射所有的实数,所以激活函数的定义域应为R\mathbb{R}R
  • 为了只是增加非线性,不改变对输入的响应状态,所以激活函数应是单调递增的SSS型曲线

现在,我们已经了解了什么是激活函数,那么在我们平时使用神经网络时,究竟都在使用哪些激活函数呢?下面将对常用的一些激活函数进行详解。

二、SigmoidSigmoidSigmoid激活函数

2.1 SigmoidSigmoidSigmoid激活函数介绍

刚才我们也提到了,一个函数要想成为激活函数,需要满足很多条件,那么什么函数可以满足这些条件呢?目前我们比较常用的就是SigmoidSigmoidSigmoid激活函数,可以有效地处理非线性问题,其函数解析式为:
σ=11+e−y\sigma=\frac{1}{1+e^{-y}} σ=1+e−y1​
  SigmoidSigmoidSigmoid激活函数的函数图像为:

图9:Sigmoid激活函数的函数图像

由其函数图像可以看到,SigmiodSigmiodSigmiod激活函数是一个典型的非线性函数,将输入映射至0~1之间。而且,SigmoidSigmoidSigmoid激活函数还可由希腊字母σ\sigmaσ表示。

通常,我们需要使用经过激活函数fff(对于SigmoidSigmoidSigmoid激活函数来说,此时的fff就是σ′\sigma^{\prime}σ′)激活后的值与真实值之间的误差形成的误差函数LLL来进行参数最优化,也就是刚才提到的内容,这也就需要得到SigmoidSigmoidSigmoid激活函数的导函数:
σ′=(11+e−y)′=−(11+e−y)2(−e−y)=e−y(1+e−y)2=11+e−ye−y1+e−y=σ(1−σ)\begin{aligned} \sigma^{\prime} & =\left(\frac{1}{1+e^{-y}}\right)^{\prime} \\ & =-\left(\frac{1}{1+e^{-y}}\right)^{2}\left(-e^{-y}\right) \\ & =\frac{e^{-y}}{\left(1+e^{-y}\right)^{2}} \\ & =\frac{1}{1+e^{-y}} \frac{e^{-y}}{1+e^{-y}} \\ & =\sigma(1-\sigma) \end{aligned} σ′​=(1+e−y1​)′=−(1+e−y1​)2(−e−y)=(1+e−y)2e−y​=1+e−y1​1+e−ye−y​=σ(1−σ)​
  SigmoidSigmoidSigmoid激活函数的导函数σ′\sigma^{\prime}σ′的图像如下图所示,类似于一个倒立的钟形图像,其最大值为0.25。经过观察发现,当yyy值较大或较小时,σ′\sigma^{\prime}σ′的值为0,这种函数我们也称其为饱和函数。

图10:Sigmoid激活函数的导函数图像

此时,让我们将目光再次聚焦到SigmoidSigmoidSigmoid激活函数的函数图像上,我们可以注意到,SigmoidSigmoidSigmoid激活函数的函数值总是大于0的,所以经过激活后的值,也都大于0。这种函数也被称为非零均值函数,如下图所示:

图11:Sigmoid激活函数是非零均值函数

既然SigmoidSigmoidSigmoid激活函数的函数值永远大于0,那么这样的结果会如何呢?我们可以使用之前的例子进行说明,图示如下,需要注意此时的激活函数fff为SigmoidSigmoidSigmoid激活函数σ\sigmaσ:

图12:使用Sigmoid激活函数后的反向传播过程

因为刚才提到,SigmoidSigmoidSigmoid激活函数的函数值永远大于0,所以上图中黄色部分永远大于0,那么∂L∂w1\frac{\partial L}{\partial w_{1}}∂w1​∂L​与∂L∂w2\frac{\partial L}{\partial w_{2}}∂w2​∂L​的正负就取决于红框内∂L∂o\frac{\partial L}{\partial o}∂o∂L​的正负,这就意味着w1w_{1}w1​和w2w_{2}w2​的梯度符号始终一致,最终导致参数w1w_{1}w1​和w2w_{2}w2​被同时正向或者反向更新,这种情况会使神经网络更慢的收敛到预定位置。所以说,SigmoidSigmoidSigmoid这样的非零均值激活函数会导致神经网络不易收敛。

2.2 梯度消失

我们刚刚提到,σ′\sigma^{\prime}σ′的最大值为0.25,所以,在利用梯度下降算法进行参数最优化的过程,其中在进行反向传播计算梯度的时候,每层的梯度会被动缩小大约14\frac{1}{4}41​,如下图所示:

图13:使用Sigmoid激活函数会使每层神经元的梯度被动缩小大约1/4

这种情况看起来很糟对不对,但是根据σ′\sigma^{\prime}σ′的图像可知,其还有另一个特点:当yyy的值很大或者很小时,σ′\sigma^{\prime}σ′的值为0。那么也就意味着,当进行反向转播求导时,可能在某一层的梯度几乎为0,那么会导致参数不会被更新,这个过程如下图所示(绿色对号代表正常计算,没有错误;红色叉号代表σ′\sigma^{\prime}σ′几乎为零)。这种更糟糕的现象也被称为梯度消失,而σ′\sigma^{\prime}σ′又可以被称为饱和函数,所以饱和函数会导致梯度消失。

图14:使用类似Sigmoid激活函数的饱和函数作为激活函数会导致梯度消失问题

三、tanhtanhtanh激活函数

经过刚才的讲解可以发现,SigmoidSigmoidSigmoid激活函数虽然使用较简单,但是存在很多严重的问题,为了解决这些问题,研究人员提出另一种激活函数,也就是我们平时经常使用的tanhtanhtanh激活函数,tanhtanhtanh函数又名为双曲正切函数,这也是我们在高等数学中常用的一个函数。既然要使用tanhtanhtanh函数作为激活函数使用,我们就要对其基本性质进行了解,其函数解析式为:
tanh⁡=1−e−y1+e−y\tanh =\frac{1-e^{-y}}{1+e^{-y}} tanh=1+e−y1−e−y​

此函数解析式对应的tanhtanhtanh激活函数图像为:

图15:tanh激活函数的函数图像

可以看到,tanhtanhtanh激活函数并不是类似SigmoidSigmoidSigmoid激活函数的非零均值函数,而是零均值函数,因为其函数平均值可以为0,所以tanhtanhtanh函数不会导致神经网络不易收敛。

另外,因为要进行反向传播求导来使用梯度下降算法,其导函数图像我们也要分析以下,如下图所示的就是tanhtanhtanh函数的导函数图像:

图16:tanh激活函数的导函数图像

可以看到,tanhtanhtanh函数的导函数tanh′tanh^{\prime}tanh′是一个饱和函数,因为其当yyy值较大或较小时,tanh′tanh^{\prime}tanh′的值接近0,也就意味着tanhtanhtanh激活函数也存在梯度消失的问题。

如果在神经网络中使用tanhtanhtanh激活函数,只需要将最后计算得到的yyy值传入tanhtanhtanh激活函数进行激活,这个过程如下图所示:

图17:使用tanh激活函数的前向传播过程

四、ReLUReLUReLU激活函数

4.1 ReLUReLUReLU激活函数介绍

我们学习到现在了,好像并没有一个好的办法解决梯度消失的问题,但是在神经网络的训练中,梯度消失会使神经网络模型训练的精度不高,从而使模型准确率下降。那么为了解决此问题,研究人员又提出了另一种激活函数:ReLUReLUReLU激活函数,又称为修正线性单元(Rectified Linear Unit),我们先来看其函数解析式:
ReLU⁡(y)={y,y>00,y≤0\operatorname{ReLU}(y)=\left\{\begin{array}{ll} y, & y>0 \\ 0, & y \leq 0 \end{array}\right. ReLU(y)={y,0,​y>0y≤0​
  此函数解析式对应的ReLUReLUReLU激活函数图像为如下所示:

图18:ReLU激活函数的函数图像

可以看到,当y>0y>0y>0时,ReLUReLUReLU激活函数就是一个正比例函数,但是会抑制y≤0y≤0y≤0的输入,只保留正数部分。通过ReLUReLUReLU激活函数的图像可以看出,其为非零均值函数,看起来会导致神经网络收敛过慢,但是要考虑到ReLUReLUReLU激活函数表达式较简单,计算也较简单,所以在大多数情况下,ReLUReLUReLU激活函数不会导致神经网络收敛过慢,反而会让神经网络收敛的速度加快,这也是ReLUReLUReLU激活函数的优点之一。

那么其导函数图像又是什么样呢?毕竟我们要使用激活函数来进行反向传播的过程,ReLUReLUReLU激活函数导函数图像ReLU′ReLU^{\prime}ReLU′的图像如下图所示:

图19:ReLU激活函数的导函数图像

可以看到,只有当神经元输出大于0的时候才会回传梯度,小于0的时候就不会进行反向传播。因为当y>0y>0y>0时,ReLU′=1ReLU^{\prime}=1ReLU′=1;当y≤0y≤0y≤0时,ReLU′=0ReLU^{\prime}=0ReLU′=0。而且很明显可以注意到,ReLU′ReLU^{\prime}ReLU′是一个非饱和函数,所以可以避免梯度消失的现象,这也是ReLUReLUReLU激活函数的优点之二。

4.2 稀疏性

当我们使用ReLUReLUReLU函数作为激活函数使用时,只需要把激活函数fff替换为ReLUReLUReLU函数即可,整个过程如下所示:

图20:使用ReLU激活函数的前向传播过程

那如果我们在整个神经网络中,全部使用ReLUReLUReLU函数作为激活函数呢?也就是将上图的一个简单的神经元扩展到一个具有多个神经元的复杂神经网络中,如下图所示。此时除了上述已经提到的优点外,ReLUReLUReLU函数还可以发挥什么样的作用呢?

图21:ReLU激活函数的稀疏性示意图

因为经过ReLUReLUReLU激活函数激活后的值,要么大于0,要么等于0被抑制,所以ReLUReLUReLU激活函数在训练过程中可以动态控制神经元的状态,这种性质也被称为稀疏性。那么ReLUReLUReLU激活函数具有的稀疏性性质的具体优点又体现在哪里呢?其优点主要包括以下几点:

  • 当输入数据的参数发生微小改动时,只有少部分的神经元需要改变状态,而不需要全局调整,从而使信息耦合程度降低
  • 此种动态开启和关闭神经元的做法,可以支持不同输入维度和中间层维度的特征学习
  • 这种稀疏的表达方式,一般都是线性可分或者弱线性可分,所以可以降低网络训练的难度
  • 虽然输出的特征是稀疏的,但是被激活的输出仍保持原有的表达能力

4.3 ReLUReLUReLU激活函数的局限性

经过刚才的介绍可以发现,ReLUReLUReLU激活函数的优点非常明显,可以有效地帮助我们训练神经网络,也正是因为这些优点,使得ReLUReLUReLU激活函数成为目前为止应用最广泛的激活函数。但是ReLUReLUReLU激活函数也仍存在一些局限性:

  • 非零均值函数

    刚才提到,虽然ReLUReLUReLU激活函数计算较简单,在大多数情况下会加速神经网络的收敛,但是注意,我说的是大多数情况,毕竟ReLUReLUReLU激活函数还是一个非零均值函数,所以,在有些情况下,ReLUReLUReLU激活函数仍会使神经网络的收敛速度变慢,不过这个问题可以使用输出的归一化方法来解决

  • 神经元坏死

    因为经过ReLUReLUReLU激活函数激活后的值,要么大于0,要么等于0,所以在训练的过程中,可能会出现一些神经元始终不会被激活,从而让神经网络表达能力下降的问题。因为在进行反向传播求导的过程中,导数始终为0,也就意味着此神经元被关闭,也可称为神经元坏死

五、LeakyReLULeaky \quad ReLULeakyReLU激活函数

刚才我们已经提到了关于ReLUReLUReLU激活函数的两个局限性,其中可以使用归一化的方法解决ReLUReLUReLU激活函数具有的非零均值函数的性质,那么关于神经元坏死的问题该如何解决呢?为了解决此问题,研究人员在2013年提出了LeakyReLULeaky \quad ReLULeakyReLU激活函数,其函数解析式如下所示:
L-ReLU⁡(y)={y,y>00.01y,y≤0\operatorname{L-ReLU}(y)=\left\{\begin{array}{ll} y, & y>0 \\ 0.01 y, & y \leq 0 \end{array}\right. L-ReLU(y)={y,0.01y,​y>0y≤0​
  LeakyReLULeaky \quad ReLULeakyReLU激活函数对应的函数图像如下所示:

图22:Leaky ReLU激活函数对应的函数图像

通过以上函数解析式以及函数图像可以看到,LeakyReLULeaky \quad ReLULeakyReLU激活函数在负半轴增加了一个很小的梯度值(即当y≤0y≤0y≤0时,L−ReLU(y)=0.01yL-ReLU(y)=0.01yL−ReLU(y)=0.01y),这样在保留一定稀疏性的情况下,也避免了神经元坏死。所以可以通过应用LeakyReLULeaky \quad ReLULeakyReLU激活函数来解决神经元坏死的问题。

LeakyReLULeaky \quad ReLULeakyReLU激活函数对应的导函数L-ReLU ′\text { L-ReLU }^{\prime} L-ReLU ′图像如下所示:

图23:Leaky ReLU激活函数对应的导函数图像

若要在神经网络中应用LeakyReLULeaky \quad ReLULeakyReLU激活函数,只需要将激活函数fff替换为LeakyReLULeaky \quad ReLULeakyReLU激活函数即可,这个过程如下图所示:

图24:使用Leaky ReLU激活函数的前向传播过程

六、ParametricReLUParametric \quad ReLUParametricReLU激活函数

因为LeakyReLULeaky \quad ReLULeakyReLU激活函数负半轴的梯度是固定的(一直固定为0.01y0.01y0.01y),所以不太灵活,在某些情况下可能性能不佳,为了解决这个问题,研究人员在2015年提出了ParametricReLUParametric \quad ReLUParametricReLU激活函数,其函数解析式如下所示:
P-ReLU⁡(y)={y,y>0αy,y≤0\operatorname{P-ReLU}(y)=\left\{\begin{array}{ll} y, & y>0 \\ \alpha y, & y \leq 0 \end{array}\right. P-ReLU(y)={y,αy,​y>0y≤0​
  ParametricReLUParametric \quad ReLUParametricReLU激活函数对应的函数图像如下所示:

图25:Parametric ReLU激活函数对应的函数图像

通过以上函数解析式以及函数图像可以看到,ParametricReLUParametric \quad ReLUParametricReLU激活函数将负半轴的梯度值更换成了可以动态学习调整的参数(即当y≤0y≤0y≤0时,P−ReLU(y)=αyP-ReLU(y)=\alpha yP−ReLU(y)=αy),而是否保留稀疏性或者抑制神经元,都要通过训练过程来确定,这样可以使神经网络动态调整训练过程中的参数值,从而获得最佳训练性能。

ParametricReLUParametric \quad ReLUParametricReLU激活函数对应的导函数图像P−ReLU′P-ReLU^{\prime}P−ReLU′如下图所示:

图26:Parametric ReLU激活函数对应的导函数图像

若要在神经网络中应用ParametricReLUParametric \quad ReLUParametricReLU激活函数,只需要将激活函数fff替换为即可ParametricReLUParametric \quad ReLUParametricReLU激活函数,这个过程如下图所示:

图27:使用Parametric ReLU激活函数的前向传播过程

总结

以上就是本篇博客的全部内容了,内容不少,但是我认为难度并不是很大,如果尤其之前学习的基础,理解起来并不难,那么今天的学习就到这里了。下篇还会继续给大家带来高质量相关内容博客,敬请期待!

激活函数、Sigmoid激活函数、tanh激活函数、ReLU激活函数、Leaky ReLU激活函数、Parametric ReLU激活函数详细介绍及其原理详解相关推荐

  1. 激活函数 sigmoid、tanh、relu

    PS:以下图有标注错误,红色都是原函数,蓝色都是导函数 激活函数(activation functions)的目标是,将神经网络非线性化.激活函数是连续的(continuous),且可导的(diffe ...

  2. 激活函数(sigmoid、Tanh、ReLU、Leaky ReLU、ELU、Maxout)

    sigmoid函数 公式: 图像: sigmoid可以将数据压缩到[0,1]范围内,可看作神经元的饱和放电率.在历史上,sigmoid函数非常有用,这是因为它对神经元的激活频率有很好的解释:从完全不激 ...

  3. 激活函数 sigmoid、tanh、ReLu、Leaky ReLu、ELU、Maxout

    1. sigmoid sigmoid 是逻辑函数,常见的 sigmoid 函数定义为: S(x)=11+e−xS(x)=\frac{1}{1+e^{-x}}S(x)=1+e−x1​ dS(x)dx=e ...

  4. 最全面:python绘制Sigmoid、Tanh、Swish、ELU、SELU、ReLU、ReLU6、Leaky ReLU、Mish、hard-Sigmoid、hard-Swish等激活函数(有源码)

    python绘制激活函数 代码 示例 代码 我构建了一个关于激活函数的类,大家可以随意使用,包括其输出值和梯度值. 关于这些激活函数详解可以参考我这篇博客:深度学习-激活函数详解(Sigmoid.ta ...

  5. 深度学习之常见激活函数-Sigmoid、Tanh、ReLu、softplus、softmax、ELU、PReLU

    一.常见激活函数总结 激活函数: 就是在神经网络的神经元上运行的函数,负责将神经元的输入映射到输出端. 常见的激活函数包括 Sigmoid TanHyperbolic(tanh) ReLu softp ...

  6. 神经网络激活函数sigmoid、tanh、Relu、LeakyRelu、Elu、PRelu、MaxOut的java实现

    神经网络常用激活函数包括sigmoid.tanh.Relu.LeakyRelu.Elu.PRelu和MaxOut.对每一种函数采用java进行实现.前面四种激活函数是固定形式,后面三种激活函数部分参数 ...

  7. 激活函数sigmoid、tanh、relu

    转载自<动手学深度学习>(PyTorch版) 在线书籍:https://tangshusen.me/Dive-into-DL-PyTorch github:https://github.c ...

  8. 激活函数(sigmoid、tanh、ReLU、softmax)

    文章目录 1.1.sigmoid函数 1.2.tanh函数 1.3.ReLU函数 1.4.softmax函数 激活函数在神经网络中的作用有很多,主要作用是给神经网络提供非线性建模能力.如果没有激活函数 ...

  9. 激活函数 Sigmoid、tanh、*LU、softPlus、maxOut、softmax、dice- 杂记

    目录 1.Sigmoid 2. tanh 3.ReLU 4.Leaky Relu 5.PRelu 6.Elu 6.softPlus 7.大一统:maxOut 传统的激活函数选择的建议 8.softma ...

最新文章

  1. NeHe教程Qt实现——lesson03
  2. HDU5706 GirlCat
  3. python用import xlwt出现红字_如何用python处理excel
  4. springboot的原生cache_springboot-shiro-redis-session-cache
  5. hbuilderx的快捷键整理pdf_mac键盘快捷键详解,苹果电脑键盘快捷键图文教程
  6. 小型ASP服务器|简洁asp服务器
  7. 2020年领导最满意的可视化工具!分分钟做好数据报表,吊打python
  8. node获取windows pc 机器的标示
  9. 将根据时间戳增量数据方案修改为根据批次号增量数据方案
  10. python -- 连接 orclae cx_Oracle的使用
  11. markdown特殊用法(三) 脚注的输入
  12. 数据分析36计(23):长期转化率 A/B 实验的问题,用边际结构模型纠正后结论反转...
  13. DNA核苷酸含量计算
  14. “夕阳无限好,只是近黄昏”改一个什么字让意境与原句截然不同?
  15. http协议(一)/应用层
  16. Cesium本地加载地形(dem高程)数据
  17. Tera - 高性能、可伸缩的结构化数据库
  18. 美腻!Visual Studio Code 界面毛玻璃效果!
  19. WEB前端学习-合并单元格
  20. location.href表示当前访问的网址url

热门文章

  1. 糯米团—重制“iPhone团购信息客户端”(四)
  2. Numpy精炼操作大全
  3. 手机测试wifi的延迟的软件,app的延迟和丢包测试(六)
  4. 析砂性土层php泥浆护壁,浅析砂性土层PHP泥浆护壁钻孔桩施工过程控制
  5. 【每日新闻】《生命3.0》作者:在AI基础研究和教育投入上,中国完胜美国 | 工信部启动清理核查5G频段无线电台工作...
  6. minio 直接访问图片,并且取消过期时间
  7. 吃货联盟(基于mysql)
  8. linux crond进程多,Linux之定时任务Crond详解
  9. weakreference 用法
  10. Php / Thinkphp 之 使用phpqrcode 生成自定义二维码