每週一個機器學習小專案002-卷積網路實現與圖片
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為卷積核心大小,大的卷積核心可以處理更大範圍的影象,但是更加難以訓練:
(1。1)
1。1式描述了卷積過程,W為卷積核心,x^l為輸入,m為卷積核心大小。stride為滑動互相關過程中卷積核心每次移動多少步。
5。2 池化層向前傳播
池化實際上是一個降取樣的過程,說直白一點就是將一個將一個影象的解析度降低一些。這可以使得需要處理的影象大小顯著減少,但是更重要的目的在於增加感受野
名詞-感受野:某一層輸出神經元所對應的所有輸入神經元個數。舉個例子,1。1式中m=5則感受野為5,以相同的引數(m=5,stride=1),再次疊加一層此時輸出層的感受野為9。
池化層可以透過在卷積層中選取一個大於1的stride來完成相同的效果。這裡用最大池化(maxpool)作為示例:
(1。2)
5。3 卷積層反向傳播
卷積層誤差反向傳播過程:
(1。3)
卷積層可訓練引數:
(1。4)
5。4 池化層反向傳播
池化層反向傳播過程之中只需知道具體哪一個最大值即可,誤差選擇取得極大值的神經元傳播,其他輸出反向傳播誤差為0。
5。5 展開層反向傳播
卷積神經網路與全連結網路之間需要一個展開層過渡,使得矩陣符合卷積、全連結的輸入與輸出。因此誤差傳播過程僅為對矩陣進行的變換。
5。6 偏置項導數可訓練引數導數與誤差傳播
加入偏置項與全連結類似,此步之中僅需計算導數即可:
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
):
(
“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引入