DeepLearning.AI第一部分第三周、 浅层神经网络(Shallow neural networks)

3.1 一些简单的介绍

3.2神经网络的表示Neural Network Representation

下面是一张神经网络的图片,现在给此图的不同部分约定一些名字
在这里插入图片描述
3.2.1 3.2.1

有一个个体,属性值为: x 1 , x 2 , x 3 x_1,x_2,x_3

  • 输入层:即   x 1 , x 2 , x 3 \ x_1,x_2,x_3 ,它们被竖直地堆叠起来。
  • 隐藏层:上图的四个结点。
  • 输出层:上图的最后一层由一个结点构成
    解释隐藏层的含义:在一个神经网络 中,当你使用监督学习训练它的时候,训练集包含了输入 x x 也包含了目标输出 y y ,所以术语隐 藏层的含义是在训练集中,这些中间结点的准确值我们是不知道到的,也就是说你看不见它们在训练集中应具有的值。你能看见输入的值,你也能看见输出的值,但是隐藏层中的东西, 在训练集中你是无法看到的。所以这也解释了词语隐藏层,只是表示你无法在训练集中看到他们。

下面引入几个符号:

  • 输入层的激活值,即输入特征 a [ 0 ] a^{[0]} :
    a [ 0 ] = [ x 1 x 2 x 3 ] a^{[0]}=\begin{bmatrix} x^1\\x^2\\x^3 \end{bmatrix}
  • 隐藏层的激活值, a [ 1 ] a^{[1]}
    a [ 1 ] = [ a 1 [ 1 ] a 2 [ 1 ] a 3 [ 1 ] a 4 [ 1 ] ] a^{[1]}=\begin{bmatrix} a^{[1]}_1\\a^{[1]}_2\\a^{[1]}_3 \\a^{[1]}_4\end{bmatrix}
    它是一个4维的列向量,维度就是隐藏神经元的个数
  • 输出层, y ^ \hat y
    y ^ = a [ 2 ] \hat y = a^{[2]}
    如下图所示(图错了,到时候再改):

在这里插入图片描述 3.2.2 3.2.2
上图隐藏画少了一个神经元 a 4 [ 1 ] a_4^{[1]}
最后要注意到,隐藏层以及最后的输出层是带有参数的,这里的隐藏层将拥有一个参数矩阵 W W 和一个参数向量 b b ,给它们加上上标   [ 1 ] \ ^{[1]} ,表示这些参数是和第一层这个隐层有关的

  • W [ 1 ] W^{[1]} 是一个4*3的矩阵:

W [ 1 ] = [ w 1 [ 1 ]    w 1 [ 2 ]    w 1 [ 3 ]    w 1 [ 4 ] w 2 [ 1 ]    w 2 [ 2 ]    w 2 [ 3 ]    w 2 [ 4 ] w 3 [ 1 ]    w 3 [ 2 ]    w 3 [ 3 ]    w 3 [ 4 ] ] W^{[1]} = \begin{bmatrix} w_1^{[ 1 ]}\ \ w_1^{[2 ]}\ \ w_1^{[ 3 ]}\ \ w_1^{[4 ]} \\ \\ w_2^{[ 1 ]}\ \ w_2^{[ 2 ]}\ \ w_2^{[3 ]}\ \ w_2^{[ 4 ]} \\ \\ w_3^{[ 1 ]}\ \ w_3^{[2 ]}\ \ w_3^{[ 3 ]}\ \ w_3^{[ 4 ]} \end{bmatrix}

  • b [ 1 ] b^{[1]} 是一个4*1的向量:

b [ 1 ] = [ b 1 b 2 b 3 b 4 ] b^{[1]} = \begin{bmatrix} b_1 \\ b_2\\ b_3\\ b_4 \end{bmatrix}

类似的输出层也有其关联的参数: W [ 2 ] W^{[2]} b [ 2 ] b^{[2]}

  • W [ 2 ] W^{[2]} 是4维的列向量。

  • $b^{[2]}$1*1的。

3.3计算一个神经网络的输出Computing a Neural Network’s output

上一节介绍了只有一个隐层的神经网络的结构与符号表示,现在我们来看这个神经网络是如何计算输出 y ^ \hat y 的。
在这里插入图片描述 3.3.1 3.3.1
上图隐藏层画少了一个神经元,
其中, x x 表示输入特征, a a 表示每个神经元的输出, W W 表示特征的权重,上标表示神经网
络的层数(隐藏层为 1),下标表示该层的第几个神经元。这是神经网络的符号惯例,下同。

神经网络的计算

在这里插入图片描述 3.3.2 3.3.2
如上图所示,首先你按步骤计算出z,然后在第二步中你以 sigmoid 函数为激活函数,带入 z z 计算得出 a a ,一个神经网络只是如此做了多次重复计算。即,对于第一个神经元,
第一步计算 z 1 [ 1 ] = w 1 [ 1 ] T x   +   b 1 [ 1 ] z^{[1]}_1 = {w^{[1]}_1}^{T}x\ + \ b_1^{[1]}
第二步计算 a 1 [ 1 ] = σ ( z 1 [ 1 ] ) a^{[1]}_1 = \sigma(z_1^{[1]})
同理计算所有的隐层神经元:

z 1 [ 1 ] = w 1 [ 1 ] T x   +   b 1 [ 1 ] ,    a 1 [ 1 ] = σ ( z 1 [ 1 ] ) z^{[1]}_1 = {w^{[1]}_1}^{T}x\ + \ b_1^{[1]},\ \ a_1^{[1]}=\sigma(z_1^{[1]})

z 2 [ 1 ] = w 2 [ 1 ] T x   +   b 2 [ 1 ] ,    a 2 [ 1 ] = σ ( z 2 [ 1 ] ) z^{[1]}_2 = {w^{[1]}_2}^{T}x\ + \ b_2^{[1]},\ \ a_2^{[1]}=\sigma(z_2^{[1]})

z 3 [ 1 ] = w 3 [ 1 ] T x   +   b 3 [ 1 ] ,    a 3 [ 1 ] = σ ( z 3 [ 1 ] ) z^{[1]}_3 = {w^{[1]}_3}^{T}x\ + \ b_3^{[1]},\ \ a_3^{[1]}=\sigma(z_3^{[1]})

z 4 [ 1 ] = w 4 [ 1 ] T x   +   b 4 [ 1 ] ,    a 4 [ 1 ] = σ ( z 4 [ 1 ] ) z^{[1]}_4 = {w^{[1]}_4}^{T}x\ + \ b_4^{[1]},\ \ a_4^{[1]}=\sigma(z_4^{[1]})

其向量化表示为:

  • 隐层计算:
    [ z 1 [ 1 ] z 2 [ 1 ] z 3 [ 1 ] z 4 [ 1 ] ] = [ w 1 [ 1 ]    w 1 [ 2 ]    w 1 [ 3 ] w 2 [ 1 ]    w 2 [ 2 ]    w 2 [ 3 ] w 3 [ 1 ]    w 3 [ 2 ]    w 3 [ 3 ] w 4 [ 1 ]    w 4 [ 2 ]    w 4 [ 3 ] ] W [ 1 ] [ x 1 x 2 x 3 ] i n p u t + [ b 1 [ 1 ] b 2 [ 1 ] b 3 [ 1 ] b 4 [ 1 ] ] b [ 1 ] \begin{bmatrix}z^{[1]}_1 \\ \\ z^{[1]}_2\\ \\ z^{[1]}_3\\ \\z^{[1]}_4 \end{bmatrix}= \overbrace{\begin{bmatrix} w_1^{[ 1 ]}\ \ w_1^{[2 ]}\ \ w_1^{[ 3 ]} \\ \\ w_2^{[ 1 ]}\ \ w_2^{[ 2 ]}\ \ w_2^{[3 ]}\\ \\ w_3^{[ 1 ]}\ \ w_3^{[2 ]}\ \ w_3^{[ 3 ]} \\ \\ w_4^{[ 1 ]}\ \ w_4^{[ 2 ]}\ \ w_4^{[3 ]} \end{bmatrix}}^{W^{[1]}} * \overbrace{\begin{bmatrix}x_1 \\ \\ x_2\\ \\ x_3\\ \end{bmatrix}}^{input} + \overbrace{\begin{bmatrix}b^{[1]}_1 \\ \\ b^{[1]}_2\\ \\ b^{[1]}_3\\ \\b^{[1]}_4 \end{bmatrix}}^{b^{[1]}}

a [ 1 ] = [ a 1 [ 1 ] a 2 [ 1 ] a 3 [ 1 ] a 4 [ 1 ] ] = σ ( [ z 1 [ 1 ] z 2 [ 1 ] z 3 [ 1 ] z 4 [ 1 ] ] z [ 1 ] ) a^{[1]} = \begin{bmatrix} a^{[1]}_1 \\ \\ a^{[1]}_2\\ \\ a^{[1]}_3\\ \\a^{[1]}_4 \end{bmatrix} = \sigma \begin{pmatrix}\overbrace{\begin{bmatrix}z^{[1]}_1 \\ \\ z^{[1]}_2\\ \\ z^{[1]}_3\\ \\z^{[1]}_4 \end{bmatrix}}^{z^{[1]}} \end{pmatrix}

  • 输出层计算(参考逻辑回归,即单个神经元的计算):
    y ^ = a [ 2 ] = σ ( z [ 2 ] ) = σ ( W [ 2 ] T a [ 1 ] + b [ 2 ] ) \hat y = a^{[2]}=\sigma(z^{[2]})= \sigma({W^{[2]}}^{T}a^{[1]}+b^{[2]})

3.4多样本向量化Vectorizing across multiple examples

前面一节讲了单一训练个体的神经网络的输出值计算,下面讲多训练个体,并计算出结果。
下图是大概步骤
在这里插入图片描述 3.4.1 3.4.1

对于一个给定的输入特征向量 x x ,这四个等式可以计算出 y ^ \hat y 。这是针对于单一的训练个体的。如果有m个训练个体,那么就需要重复这个过程。
用第一个训练个体 x ( 1 ) = [ x 1 ( 1 ) x 2 ( 1 ) x n ( 1 ) ] x^{(1)}=\begin{bmatrix}x_1^{(1)}\\ x_2^{(1)}\\\vdots\\x_n^{(1)} \end{bmatrix} 来计算出预测值 y ^ \hat y ,就是第一个训练样本上得出的结果。
然后,用 x ( 2 ) x^{(2)} 来计算出预测值 y ^ ( 2 ) \hat y^{(2)} ,… ,同样步骤直到用 x ( m ) x^{(m)} 计算出 y ^ ( m ) \hat y^{(m)}
用激活函数表示法,如图 3.4.1 3.4.1 下方所示,即:
y ^ ( 1 ) = a [ 2 ] ( 1 ) \hat y^{(1)}=a^{[2](1)}

y ^ ( 2 ) = a [ 2 ] ( 2 ) \hat y^{(2)}=a^{[2](2)}

\vdots

y ^ ( m ) = a [ 2 ] ( m ) \hat y^{(m)}=a^{[2](m)}
其中 a [ 2 ] ( m ) a^{[2](m)} 的上标 ( i ) (i) 表示第 ( i ) (i) 个训练个体, [ 2 ] [2] 表示神经网络的第二层,即用第m个个体训练出来的第二层的输出值。
如果用非向量化计算下面4个等式,并对 ( i ) (i) 循环,直到计算出所有个体的预测值:
z [ 1 ] ( i ) = W [ 1 ] ( i ) x ( i ) + b [ 1 ] ( i ) a [ 1 ] ( i ) = σ ( z [ 1 ] ( i ) ) z^{[1](i)}=W^{[1](i)}x^{(i)}+b^{[1](i)} \\ a^{[1](i)}=\sigma(z^{[1](i)})

z [ 2 ] ( i ) = W [ 2 ] ( i ) a [ 1 ] ( i ) + b [ 2 ] ( i ) a [ 2 ] ( i ) = σ ( z [ 2 ] ( i ) ) z^{[2](i)}=W^{[2](i)}a^{[1](i)}+b^{[2](i)} \\a^{[2](i)}=\sigma(z^{[2](i)})
如果用向量化表示则为:
x = [ x ( 1 )   x ( 2 )       x ( m ) ] x = \begin{bmatrix} \begin{matrix}\vdots\\x^{(1)} \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ x^{(2)}\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ \dots\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\x^{(m)} \\ \vdots\end{matrix} \end{bmatrix}

Z [ 1 ] = [ z [ 1 ] ( 1 )   z [ 1 ] ( 2 )         z [ 1 ] ( m ) ] Z^{[1]} = \begin{bmatrix} \begin{matrix}\vdots\\z^{[1](1)} \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ z^{[1](2)}\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ \dots\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ z^{[1](m)} \\ \vdots\end{matrix} \end{bmatrix}

A [ 1 ] = [ a [ 1 ] ( 1 )   a [ 1 ] ( 2 )         a [ 1 ] ( m ) ] A^{[1]} = \begin{bmatrix} \begin{matrix}\vdots\\a^{[1](1)} \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ a^{[1](2)}\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ \dots\ \\ \vdots\end{matrix} \begin{matrix}\vdots\\\ a^{[1](m)} \\ \vdots\end{matrix} \end{bmatrix}

Z [ 2 ] = W [ 2 ] A [ 1 ] + b [ 2 ] Z^{[2]} = W^{[2]}A^{[1]}+b^{[2]}

A [ 2 ] = σ ( z [ 2 ] ) A^{[2]}=\sigma(z^{[2]})

3.6激活函数Activation functions

在这里插入图片描述

sigmoid激活函数

表达式:
(3.6.1) a = σ ( z ) = 1 1 + e z a= \sigma(z)=\frac{1}{1+e^{-z}}\tag{3.6.1}

导数:
(3.6.2) σ ( z ) = a ( 1 a ) \sigma'(z)=a(1-a)\tag{3.6.2}

tanh函数

表达式:
(3.6.3) a = t a n h ( z ) = e z e z e z + e z a=tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}\tag{3.6.3}

导数:
(3.6.4) t a n h ( z ) = 1 a 2 tanh'(z)=1-a^2\tag{3.6.4}

ReLU函数

表达式:
(3.6.5) a   = ^   g ( z ) = m a x ( 0 , z ) a\ \hat =\ g(z)= max(0,z)\tag{3.6.5}
导数:
(3.6.6) g ( z ) = { 1 , if  z > 0  0 , if  z <0 g'(z) = \begin{cases} 1, & \text{if $z$> 0 }\\[2ex] 0, & \text{if $z$<0} \end{cases} \tag{3.6.6}

leaky ReLU函数

表达式:
(3.6.7) a   = ^   g ( z ) = m a x ( 0.01 z , z ) a\ \hat =\ g(z)=max(0.01z,z)\tag{3.6.7}
导数:
(3.6.8) g ( z ) = { 1 , if  z > 0  0.01 , if  z <0 u n d e f i n e d , if  z =0 when  z  = 0,  g  could be defined as 1 or 0.01, case of  z = 0  seldomly occur g'(z) = \begin{cases} 1, & \text{if $z$> 0 }\\[2ex] 0.01, & \text{if $z$<0}\\[2ex] undefined,&\text{if $z$=0} \end{cases} \text{when $z$ = 0, $g'$ could be defined as 1 or 0.01, case of $z=0$ seldomly occur} \tag{3.6.8}

3.7为什么需要非线性激活函数why need a nonlinear activation function?

如果你是用线性激活函数或者叫恒等激励函数,那么神经网络只是把输入线性组合再输出。事实上,如果你使用线性激活函数或者没有使用一个激活函数,那么无论你的神经网络有多少层,你一直在做的只是计算线性函数,那样就相当于直接去掉全部隐藏层,直接线性相加就行了,即如果你在隐藏层用线性激活函数,在输出层用 sigmoid 函数,那么这个模型的复杂度和没有任何隐藏层的标准 Logistic 回归是一样的,如果你愿意的话,可以证明一下。在这里线性隐层一点用也没有,因为这两个线性函数的组合本身就是线性函数,所以除非你引入非线性,否则你无法计算更有趣的函数,即使你的网络层数再多也不行

3.9浅层神经网络的梯度下降Gradient Descent for neural networks

单隐层神经网络会有 W [ 1 ] W^{[1]} b [ 1 ] b^{[1]} W [ 2 ] W^{[2]} b [ 2 ] b^{[2]} 这些参数,还有个 n x n_x 表示输入特征的个数, n [ 1 ] n^{[1]} 表示隐藏单元个数, n [ 2 ] n^{[2]} 表示输出单元个数。
矩阵 W [ 1 ] W^{[1]} 的维度就是 ( n [ 1 ] , n [ 0 ] ) (n^{[1]},n^{[0]}) , b [ 1 ] b^{[1]} 就是 n [ 1 ] n^{[1]} 维的列向量,可以写成 ( n [ 1 ] , 1 ) (n^{[1]},1)
矩阵 W [ 2 ] W^{[2]} 的维度就是 ( n [ 2 ] , n [ 1 ] ) (n^{[2]},n^{[1]}) b [ 2 ] b^{[2]} 的维度就是 ( n [ 2 ] , 1 ) (n^{[2]},1) 的列向量。
代价函数Cost Function:
J ( W [ 1 ] , b [ 1 ] , W [ 2 ] , b [ 12 ] ) = 1 m i = 1 n L ( y ^ , y ) J(W^{[1]},b^{[1]}, W^{[2]} ,b^{[12]})=\frac{1}{m}\sum^n_{i=1}L(\hat y , y)
损失函数和之前的Logistic回归一样
训练参数需要做梯度下降,在训练神经网络的时候,随机初始化参数很重要,而不是初始化成全零。当你参数初始化成某些值后,每次梯度下降都会循环计算以下预测值:
y ^ ( i ) , ( i = 1 , 2 , . . . , m ) \hat{y}^{(i)},(i=1,2,...,m)
更新公式:
(3.9.1) d W [ 1 ] = d J d W [ 1 ] ,   d b [ 1 ] = d J d b [ 1 ] dW^{[1]}=\frac{dJ}{dW^{[1]}},\ db^{[1]}=\frac{dJ}{db^{[1]}}\tag{3.9.1}

(3.9.2) d W [ 2 ] = d J d W [ 2 ] ,   d b [ 2 ] = d J d b [ 2 ] dW^{[2]}=\frac{dJ}{dW^{[2]}},\ db^{[2]}=\frac{dJ}{db^{[2]}}\tag{3.9.2}

(3.9.3) W [ 1 ] : = W [ 1 ] α d W [ 1 ] ,   b [ 1 ] : = b [ 1 ] α d b [ 1 ] W^{[1]}:=W^{[1]}-\alpha dW^{[1]},\ b^{[1]}:=b^{[1]}-\alpha db^{[1]}\tag{3.9.3}

(3.9.3) W [ 2 ] : = W [ 2 ] α d W [ 2 ] ,   b [ 2 ] : = b [ 2 ] α d b [ 2 ] W^{[2]}:=W^{[2]}-\alpha dW^{[2]},\ b^{[2]}:=b^{[2]}-\alpha db^{[2]}\tag{3.9.3}

正向传播4个方程:
(1) z [ 1 ] = W [ 1 ] x + b [ 1 ] z^{[1]}=W^{[1]}x+b^{[1]}
(2) a [ 1 ] = σ ( z [ 1 ] ) a^{[1]}=\sigma(z^{[1]})
(3) z [ 2 ] = W [ 2 ] a [ 1 ] + b [ 2 ] z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}
(4) a [ 2 ] = g [ 2 ] ( z [ 2 ] ) = σ ( z [ 2 ] ) a^{[2]}=g^{[2]}(z^{[2]})=\sigma(z^{[2]})

反向传播6个方程:
(3.9.4) d z [ 1 ] = A [ 2 ] Y ,   Y = [ y ( 1 ) , y ( 2 ) , , y ( m ) ] dz^{[1]}=A^{[2]}-Y,\ Y=[y^{(1)},y^{(2)},\dots,y^{(m)}]\tag{3.9.4}

(3.9.5) d W [ 2 ] = 1 m d z [ 2 ] ( A [ 1 ] ) T dW^{[2]}=\frac{1}{m}dz^{[2]}(A^{[1]})^T\tag{3.9.5}

(3.9.6) d b [ 2 ] = 1 m n p . s u m ( d z [ 2 ] , a x i s = 1 , k e e p d i m s = T r u e ) db^{[2]}= \frac{1}{m}np.sum(dz^{[2]},axis=1,keepdims=True) \tag{3.9.6}

(3.9.7) d z [ 1 ] = ( W [ 2 ] ) T d z [ 2 ] ( n [ 1 ] , m ) ( g [ 1 ] ) hidden layer’s activation function ( z [ 1 ] ) ( n [ 1 ] , m ) dz^{[1]}= \underbrace{(W^{[2]})^Tdz^{[2]}}_{(n^{[1]},m)} \cdot \overbrace{(g^{[1]})'}^{\text{hidden layer's activation function} } \cdot \underbrace{(z^{[1]})}_{(n^{[1]},m)} \tag{3.9.7}

(3.9.8) d W [ 1 ] = 1 m d z [ 1 ] x T dW^{[1]}=\frac{1}{m}dz^{[1]}x^T \tag{3.9.8}

(3.9.9) d b [ 1 ] ( n [ 1 ] , 1 ) = 1 m n p . s u m ( d z [ 1 ] , a x i s = 1 , k e e p d i m s = T r u e ) \underbrace{db^{[1]}}_{(n^{[1]},1 )}=\frac{1}{m}\cdot np.sum(dz^{[1]},axis=1,keepdims=True)\tag{3.9.9}

上述是反向传播的步骤,且这些都是针对所有样本进行过向量化的,Y是1xm的矩阵,np.sum是python的numpy命令,axis=1表示水平相加求和,keepdims是防止python输出那些古怪的秩数组(n,),加上这个确保 d b [ i ] db^{[i]} 这个向量输出的维度为(n,1)这种标准形式。
目前,我们计算的都和Logistic回归十分相似,但是当你开始计算反向传播是,你需要计算,隐藏层函数的导数,输出再使用sigmoid函数进行二元分类,因为 ( W [ 2 ] d z [ 2 ] ) (W^{[2]}dz^{[2]}) ( z [ 2 ] ) (z^{[2]}) 都是 ( n [ 1 ] , m ) (n^{[1]},m) 矩阵

3.10直观理解神经网络反向传播Backpropagation intuition

3.11随机初始化Random Initialization

初始化不能为0,如果初始化为0,那么梯度下降将不起作用。

让我们继续探讨一下。有两个输入特征, n [ 0 ] = 2 n^{[0]}=2 ,2 个隐藏层单元 n [ 1 ] n^{[1]} 就等于 2。 因此与一个隐藏层相关的矩阵,或者说 W [ 1 ] W^{[1]} 是 22 的矩阵,假设把它初始化为 0 的 22 矩阵, b [ 1 ] b^{[1]} 也等于 ( 0 , 0 ) T (0,0)^{T} ,把偏置项b初始化为 0 是合理的,但是把 W W 初始化为 0 就有问题了。 那这个问题如果按照这样初始化的话,你总是会发现 a 1 [ 1 ] a^{[1]}_1 a 2 [ 1 ] a^{[1]}_2 相等,这个激活单元和这个激活单元就会一样。因为两个隐含单元计算同样的函数,当你做反向传播计算时,这会导致 d z 1 [ 1 ] dz^{[1]}_1 d z 2 [ 2 ] dz^{[2]}_2 也会一样,对称这些隐含单元会初始化成一样,这样输出的权值也会一模一样,由此 W [ 2 ] W^{[2]} 等于[0 0]。如果这样初始化这个神经网络,那么这两个隐含单元就会完全一样,因此他们完全对称,也就意味着计算同样的函数,并且肯定的是最终经过每次训练的迭代, 这两个隐含单元仍然是同一个函数。通过归纳法克制,如果你把权重都初始化为 0,那么由于隐含单元开始计算同一个函数,所有的隐含单元就会对输出单元有同样的影响。一次迭代后同样的表达式结果仍然是相同 的,即隐含单元仍是对称的。通过推导,两次、三次、无论多少次迭代,不管你训练网络多长时间,隐含单元仍然计算的是同样的函数。
因此,可以随机初始化 W W ,这样隐含单元就会计算不一样的神经元,不会出现symmetry breaking问题。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/anny0001/article/details/85240662