1。 推薦閱讀

推薦閱讀時間:42min

推薦閱讀文章:Lecun Y, Bottou L, Bengio Y, et al。 Gradient-based learning applied to document recognition[J]。 Proceedings of the IEEE, 1998, 86(11):2278-2324。

2。 軟體環境

python3

numpy

matplotlib

3。 前置基礎

導數

矩陣運算

4。 資料描述

資料來源:Yann LeCun文章

資料下載:

http://

yann。lecun。com/exdb/mni

st/

資料描述:MNIST(Mixed National Institute of Standards and Technology database)是一個計算機視覺資料集,它包含70000張手寫數字的灰度圖片,其中每一張圖片包含 28 X 28 個畫素點。儲存形式為長度為784的向量。

5。 理論部分

對於所有的深度神經網路層都分為向前傳播與反向傳播過程。卷積神經網路的一般元件包括:卷積層、池化層、全連結層。下面這三個元件的向前、向後傳播過程進行描述:

5。1 卷積層向前傳播

首先需要明確輸入的形式,對於處理影象問題來說,卷積層輸入形式為[BATCHSIZE, Height, Weight, Chanel],每個[Height, Weight]稱之為一個特徵圖。很多人都喜歡叫的“卷積”神經網路,在實現過程之中只是一個互相關操作,互相關的矩陣稱之為卷積核,其形式為[KernelSize, KernelSize, OldChanel, NewChanel],Kernelsize為卷積核心大小,大的卷積核心可以處理更大範圍的影象,但是更加難以訓練:

\begin{matrix} x^{l+1}_{buvk} = \sum_{p=1}^{m} \sum_{q=1}^{m} \sum_r x^l_{b,i+p,j+q,r} W_{pqrk}\\ i=u\cdot stride\\ j=v\cdot stride \end{matrix}

(1。1)

1。1式描述了卷積過程,W為卷積核心,x^l為輸入,m為卷積核心大小。stride為滑動互相關過程中卷積核心每次移動多少步。

5。2 池化層向前傳播

池化實際上是一個降取樣的過程,說直白一點就是將一個將一個影象的解析度降低一些。這可以使得需要處理的影象大小顯著減少,但是更重要的目的在於增加感受野

名詞-感受野:某一層輸出神經元所對應的所有輸入神經元個數。舉個例子,1。1式中m=5則感受野為5,以相同的引數(m=5,stride=1),再次疊加一層此時輸出層的感受野為9。

池化層可以透過在卷積層中選取一個大於1的stride來完成相同的效果。這裡用最大池化(maxpool)作為示例:

\begin{matrix} x^{l+1}_{buvk} = max(x^l{b,i:i+p,j:j+q,1:r})\\ i=u\cdot stride\\ j=v\cdot stride \end{matrix}

(1。2)

5。3 卷積層反向傳播

卷積層誤差反向傳播過程:

\begin{matrix} e^{l}_{bijr}=\frac{\partial loss}{\partial x^{l}_{bijr}}\\ =\frac{\partial loss}{\partial x^{l+1}_{buvk}}\frac{\partial x^{l+1}_{buvk}}{\partial x^{l}_{bijr}}=e^{l-1}_{buvk}\cdot (\sum_{a+p=i}^{} \sum_{b+q=j}^{} \sum_r x^l_{b,a+p,b+q,k} W_{pqrk})\\ i=u\cdot stride\\ j=v\cdot stride \end{matrix}

(1。3)

卷積層可訓練引數:

\begin{matrix} \frac{\partial loss}{\partial W_{mnqr}}=\sum_b \sum_{c-i=m} \sum_{d-j=m} \sum_r e^{l-1}_{buvr}x^{l}_{b,c,d,q} \\ i=u\cdot stride\\ j=v\cdot stride \end{matrix}

(1。4)

5。4 池化層反向傳播

池化層反向傳播過程之中只需知道具體哪一個最大值即可,誤差選擇取得極大值的神經元傳播,其他輸出反向傳播誤差為0。

5。5 展開層反向傳播

卷積神經網路與全連結網路之間需要一個展開層過渡,使得矩陣符合卷積、全連結的輸入與輸出。因此誤差傳播過程僅為對矩陣進行的變換。

5。6 偏置項導數可訓練引數導數與誤差傳播

加入偏置項與全連結類似,此步之中僅需計算導數即可:

\frac{\partial loss}{\partial b_i}=\sum_b \sum_p \sum_q e^l_{bpqi}

6。 程式碼部分

6。1 結構分析

可以看到將神經網路拆分成幾個計算層後,每一層都有兩個導數需要計算:反向傳播誤差與訓練引數的導數。每一層又分為兩個部分,第一個部分用於計算正向傳播過程,第二個部分用於計算反向傳播過程。導數與誤差均在反向傳播過程之中計算,相比於全連結實踐添加了卷積層:

6。2 卷積層

def

_conv2d

self

inputs

filters

par

):

stride

padding

=

par

B

H

W

C

=

np

shape

inputs

K

K

C

C2

=

np

shape

filters

if

padding

==

“SAME”

H2

=

int

((

H

-

0。1

//

stride

+

1

W2

=

int

((

W

-

0。1

//

stride

+

1

pad_h_2

=

K

+

H2

-

1

*

stride

-

H

pad_w_2

=

K

+

W2

-

1

*

stride

-

W

pad_h_left

=

int

pad_h_2

//

2

pad_h_right

=

int

pad_h_2

-

pad_h_left

pad_w_left

=

int

pad_w_2

//

2

pad_w_right

=

int

pad_w_2

-

pad_w_left

X

=

np

pad

inputs

((

0

0

),

pad_h_left

pad_h_right

),

pad_w_left

pad_w_right

),

0

0

)),

‘constant’

constant_values

=

0

elif

padding

==

“VALID”

H2

=

int

((

H

-

K

//

stride

+

1

W2

=

int

((

W

-

K

//

stride

+

1

X

=

inputs

else

raise

“parameter error”

out

=

np

zeros

([

B

H2

W2

C2

])

for

itr1

in

range

B

):

for

itr2

in

range

H2

):

for

itr3

in

range

W2

):

for

itrc

in

range

C2

):

itrh

=

itr2

*

stride

itrw

=

itr3

*

stride

out

itr1

itr2

itr3

itrc

=

np

sum

X

itr1

itrh

itrh

+

K

itrw

itrw

+

K

:]

*

filters

[:,:,:,

itrc

])

return

out

def

_d_conv2d

self

in_error

n_layer

layer_par

=

None

):

stride

padding

=

self

layer

n_layer

][

1

inputs

=

self

outputs

n_layer

filters

=

self

value

n_layer

B

H

W

C

=

np

shape

inputs

K

K

C

C2

=

np

shape

filters

if

padding

==

“SAME”

H2

=

int

((

H

-

0。1

//

stride

+

1

W2

=

int

((

W

-

0。1

//

stride

+

1

pad_h_2

=

K

+

H2

-

1

*

stride

-

H

pad_w_2

=

K

+

W2

-

1

*

stride

-

W

pad_h_left

=

int

pad_h_2

//

2

pad_h_right

=

int

pad_h_2

-

pad_h_left

pad_w_left

=

int

pad_w_2

//

2

pad_w_right

=

int

pad_w_2

-

pad_w_left

X

=

np

pad

inputs

((

0

0

),

pad_h_left

pad_h_right

),

pad_w_left

pad_w_right

),

0

0

)),

‘constant’

constant_values

=

0

elif

padding

==

“VALID”

H2

=

int

((

H

-

K

//

stride

+

1

W2

=

int

((

W

-

K

//

stride

+

1

X

=

inputs

else

raise

“parameter error”

error

=

np

zeros_like

X

for

itr1

in

range

B

):

for

itr2

in

range

H2

):

for

itr3

in

range

W2

):

for

itrc

in

range

C2

):

itrh

=

itr2

*

stride

itrw

=

itr3

*

stride

error

itr1

itrh

itrh

+

K

itrw

itrw

+

K

:]

+=

in_error

itr1

itr2

itr3

itrc

*

filters

[:,:,:,

itrc

self

d_value

n_layer

=

np

zeros_like

self

value

n_layer

])

for

itr1

in

range

B

):

for

itr2

in

range

H2

):

for

itr3

in

range

W2

):

for

itrc

in

range

C2

):

itrh

=

itr2

*

stride

itrw

=

itr3

*

stride

self

d_value

n_layer

][:,

:,

:,

itrc

+=

in_error

itr1

itr2

itr3

itrc

*

X

itr1

itrh

itrh

+

K

itrw

itrw

+

K

:]

return

error

[:,

pad_h_left

-

pad_h_right

pad_w_left

-

pad_w_right

:]

def

conv2d

self

filters

stride

padding

=

“SAME”

):

self

value

append

filters

self

d_value

append

np

zeros_like

filters

))

self

layer

append

((

self

_conv2d

stride

padding

),

self

_d_conv2d

None

))

self

layer_name

append

“conv2d”

6。3 偏置項修改

def

_bias_add

self

inputs

b

*

args

**

kw

):

return

inputs

+

b

def

_d_bias_add

self

in_error

n_layer

*

args

**

kw

):

shape

=

np

shape

in_error

dv

=

[]

if

len

shape

==

2

self

d_value

n_layer

=

np

sum

in_error

axis

=

0

else

dv

=

np

array

([

np

sum

in_error

[:,

:,

:,

itr

])

for

itr

in

range

shape

-

1

])])

self

d_value

n_layer

=

np

squeeze

np

array

dv

))

return

in_error

def

bias_add

self

bias

*

args

**

kw

):

self

value

append

bias

self

d_value

append

np

zeros_like

bias

))

self

layer

append

((

self

_bias_add

None

self

_d_bias_add

None

))

self

layer_name

append

“bias_add”

6。4 展開層

def

_flatten

self

X

*

args

**

kw

):

B

=

np

shape

X

)[

0

return

np

reshape

X

B

-

1

])

def

_d_flatten

self

in_error

n_layer

layer_par

):

shape

=

np

shape

self

outputs

n_layer

])

return

np

reshape

in_error

shape

def

flatten

self

):

self

value

append

([])

self

d_value

append

([])

self

layer

append

((

self

_flatten

None

self

_d_flatten

None

))

self

layer_name

append

“flatten”

6。5 最大池化層

為了簡便,使用二範數作為loss函式:

def

_maxpool

self

X

_

stride

*

args

**

kw

):

B

H

W

C

=

np

shape

X

X_new

=

np

reshape

X

B

H

//

stride

stride

W

//

stride

stride

C

])

return

np

max

X_new

axis

=

2

4

))

def

_d_maxpool

self

in_error

n_layer

layer_par

):

stride

=

layer_par

X

=

self

outputs

n_layer

Y

=

self

outputs

n_layer

+

1

expand_y

=

np

repeat

np

repeat

Y

stride

axis

=

1

),

stride

axis

=

2

expand_e

=

np

repeat

np

repeat

in_error

stride

axis

=

1

),

stride

axis

=

2

return

expand_e

*

expand_y

==

X

def

maxpool

self

stride

*

args

**

kw

):

self

value

append

([])

self

d_value

append

([])

self

layer

append

((

self

_maxpool

stride

self

_d_maxpool

stride

))

self

layer_name

append

“maxpool”

6。6 修正線性啟用函式

def

_relu

self

X

*

args

**

kw

):

return

X

+

np

abs

X

))

/

2。

def

_d_relu

self

in_error

n_layer

layer_par

):

X

=

self

outputs

n_layer

drelu

=

np

zeros_like

X

drelu

X

>

0

=

1

return

in_error

*

drelu

def

relu

self

):

self

value

append

([])

self

d_value

append

([])

self

layer

append

((

self

_relu

None

self

_d_relu

None

))

self

layer_name

append

“relu”

6。7 程式碼其他部分

程式碼與全連結層共享程式碼,因此不單獨列出。

7。 程式執行

程式執行過程,首先是對網路進行描述:

# 初始化值

cw1

=

np

random

uniform

-

0。1

0。1

5

5

1

32

])

cb1

=

np

zeros

([

32

])

fw1

=

np

random

uniform

-

0。1

0。1

7

*

7

*

32

10

])

fb1

=

np

zeros

([

10

])

# 建立模型

mtd

=

NN

()

mtd

conv2d

cw1

1

mtd

bias_add

cb1

mtd

relu

()

mtd

maxpool

4

mtd

flatten

()

mtd

matmul

fw1

mtd

bias_add

fb1

mtd

sigmoid

()

mtd

loss_square

()

# 訓練

for

itr

in

range

500

):

。。。

mtd

fit

inx

iny

7。1 執行結果

類中加入語句

def

model

self

):

for

idx

itr

in

enumerate

self

layer_name

):

print

“Layer

%d

%s

%

idx

itr

))

用於輸出模型:

Layer 0: conv2d

Layer 1: bias_add

Layer 2: relu

Layer 3: maxpool

Layer 4: flatten

Layer 5: matmul

Layer 6: bias_add

Layer 7: sigmoid

Layer 8: loss

迭代100次後精度92%。

接下來需要修改什麼

模型太簡單

損失函式並不合適

訓練過程迭代緩慢

其他梯度迭代方法比如Adam引入