主要涵蓋如下內容:
-
卷積神經網絡:卷積神經網絡(Convolutional Neural Networks, CNN)是計算機視覺技術最經典的模型結構。這里主要介紹卷積神經網絡的常用模塊,包括:卷積、池化等。
-
圖像分類:介紹圖像分類算法的經典模型結構,並通過眼疾篩查的案例展示算法的應用。
-
目標檢測:介紹目標檢測YOLO-V3算法,並通過林業病蟲害數據集中的蟲子檢測任務案例展示YOLO-V3算法的應用。
如何讓計算機也能像人一樣看懂周圍的世界呢?研究者嘗試着從不同的角度去解決這個問題,由此也發展出一系列的子任務,如 下圖 所示。

(a) Image Classification: 圖像分類,用於識別圖像中物體的類別(如:bottle、cup、cube)
(b) Object Localization: 目標檢測,用於檢測圖像中每個物體的類別,並准確標出它們的位置。
(c) Semantic Segmentation: 圖像語義分割,用於標出圖像中每個像素點所屬的類別,屬於同一類別的像素點用一個顏色標識。
(d) Instance Segmentation: 實例分割,值得注意的是,(b)中的目標檢測任務只需要標注出物體位置,而(d)中的實例分割任務不僅要標注出物體位置,還需要標注出物體的外形輪廓。
卷積神經網絡
卷積神經網絡是目前計算機視覺中使用最普遍的模型結構。本章節主要為讀者介紹卷積神經網絡的一些基礎模塊,包括:
- 卷積(Convolution)
- 池化(Pooling)
- ReLU激活函數
- 批歸一化(Batch Normalization)
- 丟棄法(Dropout)
在前面介紹的手寫數字識別任務,應用的是全連接層的特征提取,即將一張圖片上的所有像素點展開成一個1維向量輸入網絡,存在如下兩個問題:
1. 輸入數據的空間信息被丟失。 空間上相鄰的像素點往往具有相似的RGB值,RGB的各個通道之間的數據通常密切相關,但是轉化成1維向量時,這些信息被丟失。同時,圖像數據的形狀信息中,可能隱藏着某種本質的模式,但是轉變成1維向量輸入全連接神經網絡時,這些模式也會被忽略。
2. 模型參數過多,容易發生過擬合。 在手寫數字識別案例中,每個像素點都要跟所有輸出的神經元相連接。當圖片尺寸變大時,輸入神經元的個數會按圖片尺寸的平方增大,導致模型參數過多,容易發生過擬合。
為了解決上述問題,我們引入卷積神經網絡進行特征提取,既能提取到像相鄰素點之間的特征模式,又能保證參數的個數不隨圖片尺寸變化。圖3 是一個典型的卷積神經網絡結構,多層卷積和池化層組合作用在輸入圖片上,在網絡的最后通常會加入一系列全連接層,ReLU激活函數一般加在卷積或者全連接層的輸出上,網絡中通常還會加入Dropout來防止過擬合。
下圖為卷積神經網絡典型結構

在卷積神經網絡中,計算范圍是在像素點的空間鄰域內進行的,卷積核參數的數目也遠小於全連接層。卷積核本身與輸入圖片大小無關,它代表了對空間臨域內某種特征模式的提取。比如,有些卷積核提取物體邊緣特征,有些卷積核提取物體拐角處的特征,圖像上不同區域共享同一個卷積核。當輸入圖片大小不一樣時,仍然可以使用同一個卷積核進行操作。
卷積(Convolution)
下面介紹卷積算法的原理和實現方案,並通過具體的案例展示如何使用卷積對圖片進行操作,主要涵蓋如下內容:
-
卷積計算
-
填充(padding)
-
步幅(stride)
-
感受野(Receptive Field)
-
多輸入通道、多輸出通道和批量操作
-
飛槳卷積API介紹
-
卷積算子應用舉例
卷積計算
卷積是數學分析中的一種積分變化的方法,在圖像處理中采用的是卷積的離散形式。這里需要說明的是,在卷積神經網絡中,卷積層的實現方式實際上是數學中定義的互相關 (cross-correlation)運算,與數學分析中的卷積定義有所不同,這里跟其他框架和卷積神經網絡的教程保持一致,都使用互相關運算作為卷積的定義,具體的計算過程如下圖所示。

說明:
卷積核(kernel)也被叫做濾波器(filter),假設卷積核的高和寬分別為kh和kw,則將稱為kh×kw卷積,比如3×5卷積,就是指卷積核的高為3, 寬為5。
計算過程如上圖所示。
填充(padding)
在上面的例子中,輸入圖片尺寸為3×3,輸出圖片尺寸為2×2,經過一次卷積之后,圖片尺寸變小。卷積輸出特征圖的尺寸計算方法如下:
Hout=H−kh+1
Wout=W−wh+1
如果輸入尺寸為4,卷積核大小為3時,輸出尺寸為4−3+1=2。通過多次計算我們發現,當卷積核尺寸大於1時,輸出特征圖的尺寸會小於輸入圖片尺寸。說明經過多次卷積之后尺寸會不斷減小。為了避免卷積之后圖片尺寸變小,通常會在圖片的外圍進行填充(padding),如下圖所示。

如果在圖片高度方向,在第一行之前填充ph1行,在最后一行之后填充ph2行;在圖片的寬度方向,在第1列之前填充pw1列,在最后1列之后填充pw2列;則填充之后的圖片尺寸為(H+ph1+ph2)×(W+pw1+pw2)。經過大小為kh×kw的卷積核操作之后,輸出圖片的尺寸為:
Hout=H+ph1+ph2−kh+1
Wout=W+pw1+pw2−kw+1
在卷積計算過程中,通常會在高度或者寬度的兩側采取等量填充,即ph1=ph2=ph, pw1=pw2=pw,上面計算公式也就變為:
Hout=H+2ph−kh+1
Wout=W+2pw−kw+1
卷積核大小通常使用1,3,5,7這樣的奇數,如果使用的填充大小為ph=(kh−1)/2,pw=(kw−1)/2,則卷積之后圖像尺寸不變。例如當卷積核大小為3時,padding大小為1,卷積之后圖像尺寸不變;同理,如果卷積核大小為5,使用padding的大小為2,也能保持圖像尺寸不變。
如何實現卷積之后圖像大小不變,可以參考上面公式。
步幅(stride)
上圖中卷積核每次滑動一個像素點,這是步幅為1的特殊情況。下圖是步幅為2的卷積過程,卷積核在圖片上移動時,每次移動大小為2個像素點。

當寬和高方向的步幅分別為sh和sw時,輸出特征圖尺寸的計算公式是:
Hout = (H+2ph−kh) / sh + 1
Wout = (W+2pw−kw) / sw + 1
假設輸入圖片尺寸是H×W=100×100,卷積核大小kh×kw=3×3,填充ph=pw=1,步幅為sh=sw=2,則輸出特征圖的尺寸為:
Hout=(100+2−3) / 2 +1=50
Wout=(100+2−3) / 2+1=50
感受野(Receptive Field)
輸出特征圖上每個點的數值,是由輸入圖片上大小為kh×kw的區域的元素與卷積核每個元素相乘再相加得到的,所以輸入圖像上kh×kw區域內每個元素數值的改變,都會影響輸出點的像素值。我們將這個區域叫做輸出特征圖上對應點的感受野。感受野內每個元素數值的變動,都會影響輸出點的數值變化。比如3×3卷積對應的感受野大小就是3×3。
多輸入通道、多輸出通道和批量操作
前面介紹的卷積計算過程比較簡單,實際應用時,處理的問題要復雜的多。例如:對於彩色圖片有RGB三個通道,需要處理多輸入通道的場景。輸出特征圖往往也會具有多個通道,而且在神經網絡的計算中常常是把一個批次的樣本放在一起計算,所以卷積算子需要具有批量處理多輸入和多輸出通道數據的功能,下面將分別介紹這幾種場景的操作方式。
- 多輸入通道場景
上面的例子中,卷積層的數據是一個2維數組,但實際上一張圖片往往含有RGB三個通道,要計算卷積的輸出結果,卷積核的形式也會發生變化。假設輸入圖片的通道數為Cin,輸入數據的形狀是Cin×Hin×Win,計算過程如下圖所示。
-
對每個通道分別設計一個2維數組作為卷積核,卷積核數組的形狀是Cin×kh×kw。
-
對任一通道cin∈[0,Cin),分別用大小為kh×kw的卷積核在大小為Hin×Win的二維數組上做卷積。
-
將這Cin個通道的計算結果相加,得到的是一個形狀為Hout×Wout的二維數組。此時,多層堆疊的圖像變成一個二維的平面圖像了。

- 多輸出通道場景
一般來說,卷積操作的輸出特征圖也會具有多個通道Cout,這時我們需要設計Cout個維度為Cin×kh×kw的卷積核,卷積核數組的維度是Cout×Cin×kh×kw,如下圖所示。
- 對任一輸出通道cout∈[0,Cout),分別使用上面描述的形狀為Cin×kh×kw的卷積核對輸入圖片做卷積。
- 將這Cout個形狀為Hout×Wout的二維數組拼接在一起,形成維度為Cout×Hout×Wout的三維數組。

- 批量操作
在卷積神經網絡的計算中,通常將多個樣本放在一起形成一個mini-batch進行批量操作,即輸入數據的維度是N×Cin×Hin×Win。由於會對每張圖片使用同樣的卷積核進行卷積操作,卷積核的維度與上面多輸出通道的情況一樣,仍然是Cout×Cin×kh×kw,輸出特征圖的維度是N×Cout×Hout×Wout,如下圖 所示。

飛槳卷積API介紹
飛槳卷積算子對應的API是paddle.fluid.dygraph.nn.Conv2D,用戶可以直接調用API進行計算,也可以在此基礎上修改。常用的參數如下:
- name_scope, 卷積層的名字,數據類型是字符串,可以是"conv1"或者"conv2"等形式。
- num_filters, 輸出通道數目,相當於上文中的Cout。
- filter_size, 卷積核大小,可以是整數,比如3;或者是兩個整數的list,例如[3, 3]。
- stride, 步幅,可以是整數,比如2;或者是兩個整數的list,例如[2, 2]。
- padding, 填充大小,可以是整數,比如1;或者是兩個整數的list,例如[1, 1]。
- act, 激活函數,卷積操作完成之后使用此激活函數作用在神經元上。
輸入數據維度[N,Cin,Hin,Win],輸出數據維度[N,num_filters,Hout,Wout],權重參數w的維度[num_filters,Cin,filter_size_h,filter_size_w],偏置參數b的維度是[num_filters]。
權重w和偏執b與filter有很大關系。
卷積算子應用舉例
下面介紹卷積算子在圖片中應用的三個案例,並觀察其計算結果。
案例1——簡單的黑白邊界檢測
下面是使用Conv2D算子完成一個圖像邊界檢測的任務。圖像左邊為光亮部分,右邊為黑暗部分,需要檢測出光亮跟黑暗的分界處。 可以設置寬度方向的卷積核為[1,0,−1],此卷積核會將寬度方向間隔為1的兩個像素點的數值相減。當卷積核在圖片上滑動的時候,如果它所覆蓋的像素點位於亮度相同的區域,則左右間隔為1的兩個像素點數值的差為0。只有當卷積核覆蓋的像素點有的處於光亮區域,有的處在黑暗區域時,左右間隔為1的兩個點像素值的差才不為0。將此卷積核作用到圖片上,輸出特征圖上只有對應黑白分界線的地方像素值才不為0。具體代碼如下所示,結果輸出在下方的圖案中。
1 import matplotlib.pyplot as plt 2 3 import numpy as np 4 import paddle 5 import paddle.fluid as fluid 6 from paddle.fluid.dygraph.nn import Conv2D 7 from paddle.fluid.initializer import NumpyArrayInitializer 8 %matplotlib inline 9 10 with fluid.dygraph.guard(): 11 # 創建初始化權重參數w 12 w = np.array([1, 0, -1], dtype='float32') 13 # 將權重參數調整成維度為[cout, cin, kh, kw]的四維張量 14 w = w.reshape([1, 1, 1, 3]) 15 # 創建卷積算子,設置輸出通道數,卷積核大小,和初始化權重參數 16 # filter_size = [1, 3]表示kh = 1, kw=3 17 # 創建卷積算子的時候,通過參數屬性param_attr,指定參數初始化方式 18 # 這里的初始化方式時,從numpy.ndarray初始化卷積參數 19 conv = Conv2D('conv', num_filters=1, filter_size=[1, 3], 20 param_attr=fluid.ParamAttr( 21 initializer=NumpyArrayInitializer(value=w))) 22 23 # 創建輸入圖片,圖片左邊的像素點取值為1,右邊的像素點取值為0 24 img = np.ones([50,50], dtype='float32') 25 img[:, 30:] = 0. 26 # 將圖片形狀調整為[N, C, H, W]的形式 27 x = img.reshape([1,1,50,50]) 28 # 將numpy.ndarray轉化成paddle中的tensor 29 x = fluid.dygraph.to_variable(x) 30 # 使用卷積算子作用在輸入圖片上 31 y = conv(x) 32 # 將輸出tensor轉化為numpy.ndarray 33 out = y.numpy() 34 35 f = plt.subplot(121) 36 f.set_title('input image', fontsize=15) 37 plt.imshow(img, cmap='gray') 38 39 f = plt.subplot(122) 40 f.set_title('output featuremap', fontsize=15) 41 # 卷積算子Conv2D輸出數據形狀為[N, C, H, W]形式 42 # 此處N, C=1,輸出數據形狀為[1, 1, H, W],是4維數組 43 # 但是畫圖函數plt.imshow畫灰度圖時,只接受2維數組 44 # 通過numpy.squeeze函數將大小為1的維度消除 45 plt.imshow(out.squeeze(), cmap='gray') 46 plt.show()

1 # 查看卷積層的參數 2 with fluid.dygraph.guard(): 3 # 通過 conv.parameters()查看卷積層的參數,返回值是list,包含兩個元素 4 print(conv.parameters()) 5 # 查看卷積層的權重參數名字和數值 6 print(conv.parameters()[0].name, conv.parameters()[0].numpy()) 7 # 參看卷積層的偏置參數名字和數值 8 print(conv.parameters()[1].name, conv.parameters()[1].numpy())
[name conv/Conv2D_0.w_0, dtype: VarType.FP32 shape: [1L, 1L, 1L, 3L] lod: {}
dim: 1, 1, 1, 3
layout: NCHW
dtype: float
data: [1 0 -1]
, name conv/Conv2D_0.b_0, dtype: VarType.FP32 shape: [1L] lod: {}
dim: 1
layout: NCHW
dtype: float
data: [0]
]
(u'conv/Conv2D_0.w_0', array([[[[ 1., 0., -1.]]]], dtype=float32))
(u'conv/Conv2D_0.b_0', array([0.], dtype=float32))
案例2——圖像中物體邊緣檢測
上面展示的是一個人為構造出來的簡單圖片使用卷積檢測明暗分界處的例子,對於真實的圖片,也可以使用合適的卷積核對它進行操作,用來檢測物體的外形輪廓,觀察輸出特征圖跟原圖之間的對應關系,如下代碼所示:
1 import matplotlib.pyplot as plt 2 from PIL import Image 3 import numpy as np 4 import paddle 5 import paddle.fluid as fluid 6 from paddle.fluid.dygraph.nn import Conv2D 7 from paddle.fluid.initializer import NumpyArrayInitializer 8 9 img = Image.open('./work/images/section1/000000098520.jpg') 10 with fluid.dygraph.guard(): 11 # 設置卷積核參數 12 w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')/8 13 w = w.reshape([1, 1, 3, 3]) 14 # 由於輸入通道數是3,將卷積核的形狀從[1,1,3,3]調整為[1,3,3,3] 15 w = np.repeat(w, 3, axis=1) 16 # 創建卷積算子,輸出通道數為1,卷積核大小為3x3, 17 # 並使用上面的設置好的數值作為卷積核權重的初始化參數 18 conv = Conv2D('conv', num_filters=1, filter_size=[3, 3], 19 param_attr=fluid.ParamAttr( 20 initializer=NumpyArrayInitializer(value=w))) 21 22 # 將讀入的圖片轉化為float32類型的numpy.ndarray 23 x = np.array(img).astype('float32') 24 # 圖片讀入成ndarry時,形狀是[H, W, 3], 25 # 將通道這一維度調整到最前面 26 x = np.transpose(x, (2,0,1)) 27 # 將數據形狀調整為[N, C, H, W]格式 28 x = x.reshape(1, 3, img.height, img.width) 29 x = fluid.dygraph.to_variable(x) 30 y = conv(x) 31 out = y.numpy() 32 33 plt.figure(figsize=(20, 10)) 34 f = plt.subplot(121) 35 f.set_title('input image', fontsize=15) 36 plt.imshow(img) 37 f = plt.subplot(122) 38 f.set_title('output feature map', fontsize=15) 39 plt.imshow(out.squeeze(), cmap='gray') 40 plt.show()

對於三次w的值,打印如下:
1 # 設置卷積核參數 2 w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')/8 3 print('first time:',w) 4 w = w.reshape([1, 1, 3, 3]) 5 print('second time:',w) 6 # 由於輸入通道數是3,將卷積核的形狀從[1,1,3,3]調整為[1,3,3,3] 7 w = np.repeat(w, 3, axis=1) 8 print('third time:',w)
('first time:', array([[-0.125, -0.125, -0.125],
[-0.125, 1. , -0.125],
[-0.125, -0.125, -0.125]], dtype=float32))
('second time:', array([[[[-0.125, -0.125, -0.125],
[-0.125, 1. , -0.125],
[-0.125, -0.125, -0.125]]]], dtype=float32))
('third time:', array([[[[-0.125, -0.125, -0.125],
[-0.125, 1. , -0.125],
[-0.125, -0.125, -0.125]],
[[-0.125, -0.125, -0.125],
[-0.125, 1. , -0.125],
[-0.125, -0.125, -0.125]],
[[-0.125, -0.125, -0.125],
[-0.125, 1. , -0.125],
[-0.125, -0.125, -0.125]]]], dtype=float32))
案例3——圖像均值模糊
另外一種比較常見的卷積核是用當前像素跟它鄰域內的像素取平均,這樣可以使圖像上噪聲比較大的點變得更平滑,如下代碼所示:
1 import matplotlib.pyplot as plt 2 3 from PIL import Image 4 5 import numpy as np 6 import paddle 7 import paddle.fluid as fluid 8 from paddle.fluid.dygraph.nn import Conv2D 9 from paddle.fluid.initializer import NumpyArrayInitializer 10 11 # 讀入圖片並轉成numpy.ndarray 12 #img = Image.open('./images/section1/000000001584.jpg') 13 img = Image.open('./work/images/section1/000000355610.jpg').convert('L') 14 img = np.array(img) 15 16 # 換成灰度圖 17 18 with fluid.dygraph.guard(): 19 # 創建初始化參數 20 w = np.ones([1, 1, 5, 5], dtype = 'float32')/25 21 conv = Conv2D('conv', num_filters=1, filter_size=[5, 5], 22 param_attr=fluid.ParamAttr( 23 initializer=NumpyArrayInitializer(value=w))) 24 25 x = img.astype('float32') 26 x = x.reshape(1,1,img.shape[0], img.shape[1]) 27 x = fluid.dygraph.to_variable(x) 28 y = conv(x) 29 out = y.numpy() 30 31 plt.figure(figsize=(20, 12)) 32 f = plt.subplot(121) 33 f.set_title('input image') 34 plt.imshow(img, cmap='gray') 35 36 f = plt.subplot(122) 37 f.set_title('output feature map') 38 out = out.squeeze() 39 plt.imshow(out, cmap='gray') 40 41 plt.show()

池化(Pooling)
池化是使用某一位置的相鄰輸出的總體統計特征代替網絡在該位置的輸出,其好處是當輸入數據做出少量平移時,經過池化函數后的大多數輸出還能保持不變。比如:當識別一張圖像是否是人臉時,我們需要知道人臉左邊有一只眼睛,右邊也有一只眼睛,而不需要知道眼睛的精確位置,這時候通過約化某一片區域的像素點來得到總體統計特征會顯得很有用。由於池化之后特征圖會變得更小,如果后面連接的是全連接層,能有效的減小神經元的個數,節省存儲空間並提高計算效率。 如 下圖所示,將一個2×2的區域池化成一個像素點。通常有兩種方法,平均池化和最大池化。

與卷積核類似,池化窗口在圖片上滑動時,每次移動的步長稱為步幅,當寬和高方向的移動大小不一樣時,分別用sh和sw表示。也可以對需要進行池化的圖片進行填充,填充方式與卷積類似,假設在第一行之前填充ph1行,在最后一行后面填充ph2行。在第一列之前填充pw1p_{w1}pw1列,在最后一列之后填充pw2p_{w2}pw2列,則池化層的輸出特征圖大小為:
Hout=(H+ph1+ph2−kh)/ sh+1
Wout=(W+pw1+pw2−kw)/ sw+1
在卷積神經網絡中,通常使用2×2大小的池化窗口,步幅也使用2,填充為0,則輸出特征圖的尺寸為:
Hout=H/2
Wout=W/2
通過這種方式的池化,輸出特征圖的高和寬都減半,但通道數不會改變。
ReLU激活函數
前面介紹的網絡結構中,普遍使用Sigmoid函數做激活函數。在神經網絡發展的早期,Sigmoid函數用的比較多,而目前用的較多的激活函數是ReLU。這是因為Sigmoid函數在反向傳播過程中,容易造成梯度的衰減。讓我們仔細觀察Sigmoid函數的形式,就能發現這一問題。
Sigmoid激活函數定義如下:
y=1 /(1+e(−x))
ReLU激活函數的定義如下:
y=0,(x<0);x,(x≥0)
下面的程序畫出了Sigmoid和ReLU函數的曲線圖:
1 # ReLU和Sigmoid激活函數示意圖 2 import numpy as np 3 import matplotlib.pyplot as plt 4 import matplotlib.patches as patches 5 6 plt.figure(figsize=(10, 5)) 7 8 # 創建數據x 9 x = np.arange(-10, 10, 0.1) 10 11 # 計算Sigmoid函數 12 s = 1.0 / (1 + np.exp(0. - x)) 13 14 # 計算ReLU函數 15 y = np.clip(x, a_min=0., a_max=None) 16 17 ##################################### 18 # 以下部分為畫圖代碼 19 f = plt.subplot(121) 20 plt.plot(x, s, color='r') 21 currentAxis=plt.gca() 22 plt.text(-9.0, 0.9, r'$y=Sigmoid(x)$', fontsize=13) 23 currentAxis.xaxis.set_label_text('x', fontsize=15) 24 currentAxis.yaxis.set_label_text('y', fontsize=15) 25 26 f = plt.subplot(122) 27 plt.plot(x, y, color='g') 28 plt.text(-3.0, 9, r'$y=ReLU(x)$', fontsize=13) 29 currentAxis=plt.gca() 30 currentAxis.xaxis.set_label_text('x', fontsize=15) 31 currentAxis.yaxis.set_label_text('y', fontsize=15) 32 33 plt.show()

梯度消失現象
在神經網絡里面,將經過反向傳播之后,梯度值衰減到接近於零的現象稱作梯度消失現象。
從上面的函數曲線可以看出,當x為較大的正數的時候,Sigmoid函數數值非常接近於1,函數曲線變得很平滑,在這些區域Sigmoid函數的導數接近於零。當x為較小的負數的時候,Sigmoid函數值非常接近於0,函數曲線也很平滑,在這些區域Sigmoid函數的導數也接近於0。只有當x的取值在0附近時,Sigmoid函數的導數才比較大。可以對Sigmoid函數求導數,結果如下所示:
dy/dx = −1/(1+e^(−x))^2 ⋅ d(e^(−x))/dx = 1/(2+e^x+e^(−x))
從上面的式子可以看出,Sigmoid函數的導數dy/dx\最大值為1/4。前向傳播時,y=Sigmoid(x);而在反向傳播過程中,x的梯度等於y的梯度乘以Sigmoid函數的導數,如下所示:
∂L/∂x = ∂L/∂y ⋅ ∂y/∂x
使得x的梯度數值最大也不會超過y的梯度的1/4。
由於最開始是將神經網絡的參數隨機初始化的,x很有可能取值在數值很大或者很小的區域,這些地方都可能造成Sigmoid函數的導數接近於0,導致x的梯度接近於0;即使x取值在接近於0的地方,按上面的分析,經過Sigmoid函數反向傳播之后,x的梯度不超過y的梯度的1/4,如果有多層網絡使用了Sigmoid激活函數,則比較靠前的那些層梯度將衰減到非常小的值。
ReLU函數則不同,雖然在x<0的地方,ReLU函數的導數為0。但是在x≥0的地方,ReLU函數的導數為1,能夠將y的梯度完整的傳遞給x,而不會引起梯度消失。
