【深度学习】手把手教你写“手写数字识别神经网络“,不使用任何框架,纯Numpy
文章目录前言手写数字识别神经网络设计数据集使用神经网络设计数学模型关于矩阵求导程序实现运行结果关于权值初始化不同初始化方法收敛速度分析:为什么不同的初始化收敛速度不一样?关于损失函数关于我的感想附录文章资源参考前言 一直以来笔者对机器学习都很感兴趣,但由于大学主修专业与机器学习无关,所以一直没有时间(拖延症)不用框架来完整的写一个神经网络。不过好在这个学期正好有一门主修选修课程,借此机会发表一下
文章目录
前言
一直以来笔者对机器学习都很感兴趣,但由于大学主修专业与机器学习无关,所以一直没有时间(拖延症)不用框架来完整的写一个神经网络。不过好在这个学期正好有一门主修选修课程,借此机会发表一下自己的学习成果。
学习这篇文章的前提是你有一定的Python、线性代数、高数知识,并且对全连接神经网络有一定的了解,知道全连接神经其实就是一个可以通过调参逼近任何函数的非线性函数(我是这么理解的,当然我也同样认为存在许多它不可能精确描述的函数),知道前向传播和反向传播过程。
如果你对神经网络有一定了解,但是又没有仔细思考 过建议可以先看一下B站3Blue1Brown关于深度学习 Deep Learning的视频,学完这个视频你就能对全连接神经网络有较为深刻的认识。当然你可能还是没有办法写出一个完整的神经网络,因为不少人可能对反向传播过程中矩阵的求导不是很了解,导致不能推导出反向传播过程梯度的计算公式。
手写数字识别神经网络设计
假设你已经看过视频,或者对已经此已经比较了解了,那么下面就可以开始设计了。
这里为了方便讲解以及使得第一个程序清晰!!!,就不使用MINIST数据集了。这里使用我学习的教程提供的一个数据集,这个数据集合看起来像这样,文件内容就是手写数字的图片数据,相当于二值化图片。命名规则:图片中的数字_序号 --> label_seq.txt
。接下来就用标签称呼图片所写数字的值。现在的重点不在于数据集,所以这里直接给出加载数据及的函数。点击这里下载数据集。
# 函数img2vector将图像转换为向量
from numpy import *
from os import listdir
def img2vector(filename):
returnVect = np.zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
# 读取手写字体txt数据
def handwritingData(dataPath):
hwLabels = []
FileList = listdir(dataPath) # 1 获取目录内容
m = len(FileList)
np.Mat = np.zeros((m, 1024))
for i in range(m):
# 2 从文件名解析分类数字
fileNameStr = FileList[i]
fileStr = fileNameStr.split('.')[0] # take off .txt
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
np.Mat[i, :] = img2vector(dataPath + '/%s' % fileNameStr)
return np.Mat, hwLabels
数据集使用
将数据集解压到指定目录下,like this:
..
└ Work Folder
└ Net.py
trainingDigits
testDigits
# 读取训练数据
trainDataPath = "./trainingDigits"
trainMat, trainLabels = handwritingData(trainDataPath)
trainMat.shape -> (1934, 1024)
trainMat[i]对应的标签为trainLabels[i]
神经网络设计
这里是模型是这样的,如上图,一个输入层,两个隐含层,激活函数选择Sigmod,损失函数选择误差平方和函数(我这么称呼的,不知道叫什么),如下面所示。建议第一次自己些的话损失函数就用误差平方和函数,以为这个损失函数简单,求导方便,便于学习,后面可以在此基础上进行改进。
Cost :代价,在这里也就是需要计算的Loss值。
S
i
g
m
o
d
(
x
)
=
1
1
+
e
−
x
S
i
g
m
o
d
′
(
x
)
=
S
i
g
m
o
d
(
x
)
(
1
−
S
i
g
m
o
d
(
x
)
)
Sigmod(x) = \frac{1}{1+e^{-x}} \\ Sigmod'(x) = Sigmod(x)(1 - Sigmod(x)) \\
Sigmod(x)=1+e−x1Sigmod′(x)=Sigmod(x)(1−Sigmod(x))
误
差
平
方
和
函
数
:
C
o
s
t
(
y
^
,
y
)
=
1
2
(
y
^
−
y
)
2
误差平方和函数: Cost( \hat{y} , y) = \frac{1}{2} ( \hat{y} - y)^2
误差平方和函数:Cost(y^,y)=21(y^−y)2
如果数学能力不够强的话建议不要用像交叉熵损失函数,也不要用Softmax激活函数这些求导麻烦的函数,不利于学习。
数学模型
符
号
:
A
(
层
)
符号:A^{(层)}
符号:A(层)
S
=
S
i
g
m
o
d
S = Sigmod
S=Sigmod
维 度 : ∣ x 1024 x 1 ∣ W 16 x 1024 ( 1 ) ∣ b 16 x 1 ( 1 ) ∣ W 16 x 16 ( 2 ) ∣ b 16 x 1 ( 2 ) ∣ W 10 x 16 ( 3 ) ∣ b 10 x 1 ( 3 ) ∣ y ^ 10 x 1 ∣ 维度: | \boldsymbol{x}_{1024x1} | \boldsymbol{W}_{16x1024}^{(1)} | \boldsymbol{b}_{16x1}^{(1)} | \boldsymbol{W}_{16x16}^{(2)} | \boldsymbol{b}_{16x1}^{(2)} | \boldsymbol{W}_{10x16}^{(3)} | \boldsymbol{b}_{10x1}^{(3)} | \boldsymbol{\hat{y}_{10x1}} | 维度:∣x1024x1∣W16x1024(1)∣b16x1(1)∣W16x16(2)∣b16x1(2)∣W10x16(3)∣b10x1(3)∣y^10x1∣
前
向
传
播
:
前向传播:
前向传播:
z
(
l
)
=
W
(
l
)
a
(
l
−
1
)
+
b
(
l
)
\mathbf{z}^{(l)}=\mathbf{W}^{(l)} \mathbf{a}^{(l-1)}+\mathbf{b}^{(l)}
z(l)=W(l)a(l−1)+b(l)
a
(
l
)
=
f
(
z
(
l
)
)
\mathbf{a}^{(l)}=f\left(\mathbf{z}^{(l)}\right)
a(l)=f(z(l))
y
^
=
a
(
L
)
=
f
(
z
(
L
)
)
\hat{\mathbf{y}}=\mathbf{a}^{(L)}=f\left(\mathbf{z}^{(L)}\right)
y^=a(L)=f(z(L))
L
=
L
(
y
,
y
^
)
\mathcal{L}=\mathcal{L}(\mathbf{y}, \hat{\mathbf{y}})
L=L(y,y^)
x
=
a
(
0
)
也
就
是
输
入
的
图
片
数
据
\boldsymbol{x} = a^{(0)} 也就是输入的图片数据
x=a(0)也就是输入的图片数据
z
(
1
)
=
W
(
1
)
a
(
0
)
+
b
(
1
)
\boldsymbol{z}^{(1)} = \boldsymbol{W}^{(1)}\boldsymbol{a}^{(0)}+\boldsymbol{b}^{(1)}
z(1)=W(1)a(0)+b(1)
a
(
1
)
=
S
(
z
(
1
)
)
\boldsymbol{a}^{(1)} = S(\boldsymbol{z}^{(1)})
a(1)=S(z(1))
z
(
2
)
=
W
(
2
)
a
(
1
)
+
b
(
2
)
\boldsymbol{z}^{(2)} = \boldsymbol{W}^{(2)}\boldsymbol{a}^{(1)}+\boldsymbol{b}^{(2)}
z(2)=W(2)a(1)+b(2)
a
(
2
)
=
S
(
z
(
2
)
)
\boldsymbol{a}^{(2)} = S(\boldsymbol{z}^{(2)})
a(2)=S(z(2))
z
(
3
)
=
W
(
2
)
a
(
2
)
+
b
(
3
)
\boldsymbol{z}^{(3)} = \boldsymbol{W}^{(2)}\boldsymbol{a}^{(2)}+\boldsymbol{b}^{(3)}
z(3)=W(2)a(2)+b(3)
y
^
=
S
(
z
(
3
)
)
预
测
结
果
,
最
大
值
下
标
就
是
预
测
的
数
字
结
果
\boldsymbol{\hat{y}} = S(\boldsymbol{z}^{(3)}) 预测结果,最大值下标就是预测的数字结果
y^=S(z(3))预测结果,最大值下标就是预测的数字结果
L o s s = C o s t ( y ^ , y ) Loss = Cost( \boldsymbol{\hat{y}} , \boldsymbol{y}) Loss=Cost(y^,y)
反 向 传 播 : ⊙ 是 对 应 元 素 相 乘 反向传播:\odot 是对应元素相乘 反向传播:⊙是对应元素相乘
∂ L o s s ∂ y ^ \frac{\partial Loss}{\partial \boldsymbol{\hat{y}}} ∂y^∂Loss:这里是对应元素求导
输出层:
δ
(
L
)
=
∇
a
(
L
)
L
⊙
S
′
(
z
(
L
)
)
=
∂
L
o
s
s
∂
y
^
⊙
S
′
(
z
(
L
)
)
=
∂
L
∂
z
(
L
)
这
里
求
得
:
∂
L
o
s
s
∂
y
^
⊙
S
′
(
z
(
3
)
)
\boldsymbol{\delta}^{(L)}=\nabla_{\mathbf{a}^{(L)}} \mathcal{L} \odot S^{\prime}\left(\mathbf{z}^{(L)}\right) = \frac{\partial Loss}{\partial \boldsymbol{\hat{y}}} \odot S^{\prime}\left(\mathbf{z}^{(L)}\right) = \frac{\partial \mathcal{L} }{\partial \mathbf{z}^{(L)}}\\ 这里求得: \frac{\partial Loss}{\partial \boldsymbol{\hat{y}}} \odot S^{\prime}\left(\mathbf{z}^{(3)}\right)
δ(L)=∇a(L)L⊙S′(z(L))=∂y^∂Loss⊙S′(z(L))=∂z(L)∂L这里求得:∂y^∂Loss⊙S′(z(3))
其他层套用公式:
δ
(
l
)
=
(
(
W
(
l
+
1
)
)
T
δ
(
l
+
1
)
)
⊙
S
′
(
z
(
l
)
)
∂
L
∂
W
(
l
)
=
δ
(
l
)
(
a
(
l
−
1
)
)
T
∂
L
∂
b
(
l
)
=
δ
(
l
)
\boldsymbol{\delta}^{(l)}=\left(\left(\mathbf{W}^{(l+1)}\right)^{\mathrm{T}} \boldsymbol{\delta}^{(l+1)}\right) \odot S^{\prime}\left(\mathbf{z}^{(l)}\right) \\ \frac{\partial \mathcal{L}} {\partial\mathbf{W}^{(l)}}=\boldsymbol{\delta}^{(l)}\left(\mathbf{a}^{(l-1)}\right)^{\mathrm{T}} \\ \frac{\partial \mathcal{L}}{\partial \mathbf{b}^{(l)}}=\boldsymbol{\delta}^{(l)}
δ(l)=((W(l+1))Tδ(l+1))⊙S′(z(l))∂W(l)∂L=δ(l)(a(l−1))T∂b(l)∂L=δ(l)
参
数
更
新
:
参数更新:
参数更新:
W
(
l
)
=
W
(
l
)
−
α
∂
L
∂
W
(
l
)
b
(
l
)
=
b
(
l
)
−
α
∂
L
∂
b
(
l
)
\begin{aligned} \mathbf{W}^{(l)} &=\mathbf{W}^{(l)}-\alpha \frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}} \\ \mathbf{b}^{(l)} &=\mathbf{b}^{(l)}-\alpha \frac{\partial \mathcal{L}}{\partial \mathbf{b}^{(l)}} \end{aligned}
W(l)b(l)=W(l)−α∂W(l)∂L=b(l)−α∂b(l)∂L
关于矩阵求导
如果你只是想能够自己求出反向传播过程中的梯度,那么记住上面的公式足够了,如果想要了解更多的有关公式可以参考这篇文章:神经网络求导。
标量 f f f对矩阵 X \boldsymbol{X} X的导数,定义为 ∂ f ∂ X = [ ∂ f ∂ X i j ] \frac{\partial f}{\partial X}=\left[\frac{\partial f}{\partial X_{i j}}\right] ∂X∂f=[∂Xij∂f]。
当然,你可能不想止步于此,可能对于一部分同学并没有接触过矩阵求导然后在反向传播公式推导的时候卡住了。想要了解如何进行矩阵求导可以参考:矩阵求导术。当然其实有个简单的办法,就是把矩阵的每个元素求解的等式连同它的下标写出来,像这样: y i × 1 = ∑ j W i × j x j × 1 y_{i \times1} = \sum_{j} W_{i \times j} x_{j \times 1} yi×1=∑jWi×jxj×1。然后再表示出偏导数,像这样: ∂ y i × 1 ∂ W i × j = x j × 1 \frac{\partial y_{i \times1}}{\partial W_{i \times j} } = x_{j \times 1} ∂Wi×j∂yi×1=xj×1。相当于只有 W \mathbf{W} W的第 i i i行 W i × j \boldsymbol{W}_{i \times \boldsymbol{j}} Wi×j做出了贡献,而 W \mathbf{W} W的第 i i i行 W i × j \boldsymbol{W}_{i \times \boldsymbol{j}} Wi×j(j相当于有个向量)也只对 y i × 1 y_{i \times1} yi×1做贡献。
令
y
i
×
1
=
W
i
×
j
x
j
×
1
+
b
i
×
1
,
y_{i \times1} = \boldsymbol{W}_{i \times \boldsymbol{j}}\boldsymbol{x}_{\boldsymbol{j} \times 1} + \boldsymbol{b}_{i \times 1},
yi×1=Wi×jxj×1+bi×1,把
W
i
×
j
的
每
个
元
素
W
i
×
j
\boldsymbol{W}_{i \times \boldsymbol{j}}的每个元素W_{i \times j}
Wi×j的每个元素Wi×j看作一个变量,
y
i
×
1
y_{i \times1}
yi×1相当于一个多元函数,元素
W
i
×
j
W_{i \times j}
Wi×j增加1对
y
i
×
1
y_{i \times1}
yi×1的影响为
Δ
y
i
×
1
Δ
W
i
×
j
\frac{\Delta y_{i \times1}}{\Delta W_{i \times j} }
ΔWi×jΔyi×1,取极限为
∂
y
i
×
1
∂
W
i
×
j
=
x
j
×
1
\frac{\partial y_{i \times1}}{\partial W_{i \times j} } = x_{j \times 1}
∂Wi×j∂yi×1=xj×1, 由于是只有第
W
\boldsymbol{W}
W的第
i
i
i列,有影响,所以最后求出来的梯度应该是一行,这行的值转置就是
x
\boldsymbol{x}
x。
x
\boldsymbol{x}
x求导时看作常数。
说人话,如下图: 假设
i
=
1
i = 1
i=1。
∂
y
i
×
1
∂
W
i
×
j
=
[
∂
y
1
×
1
∂
W
1
×
1
=
x
1
×
1
∂
y
1
×
1
∂
W
1
×
2
=
x
2
×
1
⋯
∂
y
i
×
1
∂
W
i
×
j
=
x
j
×
1
]
=
[
x
1
×
1
x
2
×
1
⋮
x
j
×
1
]
T
=
x
T
\frac{\partial {y}_{i \times1}}{\partial \boldsymbol{W}_{i \times \boldsymbol{j}} } = \left[ \begin{matrix} \frac{\partial y_{1 \times1}}{\partial W_{1 \times 1} } =x_{1 \times 1} & \frac{\partial y_{1 \times1}}{\partial W_{1 \times 2} } =x_{2 \times 1} & \cdots & \frac{\partial y_{i \times1}}{\partial W_{i \times j} } = x_{j \times 1} \\ \end{matrix} \right] \\ =\left[ \begin{matrix} x_{1 \times 1} \\ x_{2 \times 1} \\ \vdots \\ x_{j \times 1} \\ \end{matrix} \right]^T = \boldsymbol{x}^T
∂Wi×j∂yi×1=[∂W1×1∂y1×1=x1×1∂W1×2∂y1×1=x2×1⋯∂Wi×j∂yi×1=xj×1]=⎣⎢⎢⎢⎡x1×1x2×1⋮xj×1⎦⎥⎥⎥⎤T=xT
当然,这个证明不完整,我觉得剩下的不需要(懒得 )写了。注意这是变量对矩阵的导数,虽然
y
\boldsymbol{y}
y有多项,但是分别来求的。又因为
L
o
s
s
Loss
Loss是是把
y
\boldsymbol{y}
y求和的函数,所以根据链式求导法则:
y
=
W
x
L
o
s
s
=
C
(
y
)
∂
L
o
s
s
∂
W
=
∂
L
o
s
s
∂
y
∂
y
∂
W
\boldsymbol{y} = \boldsymbol{W} \boldsymbol{x} \\ Loss = C(\boldsymbol{y}) \\ \frac{\partial Loss }{\partial \boldsymbol{W} } = \frac{\partial Loss }{\partial \boldsymbol{y} } \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{W} }
y=WxLoss=C(y)∂W∂Loss=∂y∂Loss∂W∂y
∂
L
o
s
s
∂
y
\frac{\partial Loss }{\partial \boldsymbol{y} }
∂y∂Loss是逐项求导,得到向量
δ
\boldsymbol{\delta}
δ正好把每个
∂
y
i
×
1
∂
W
i
×
j
\frac{\partial y_{i \times1}}{\partial W_{i \times j} }
∂Wi×j∂yi×1"搬到"对应行。
∂
L
o
s
s
∂
W
=
∂
L
o
s
s
∂
y
x
T
\frac{\partial Loss }{\partial \boldsymbol{W} } = \frac{\partial Loss }{\partial \boldsymbol{y} } \boldsymbol{x}^T
∂W∂Loss=∂y∂LossxT
完整解析:
∂
L
o
s
s
∂
W
=
∂
L
o
s
s
∂
y
∂
y
∂
W
=
[
∂
L
o
s
s
∂
y
1
×
1
∂
y
1
×
1
∂
W
1
×
j
∂
L
o
s
s
∂
y
2
×
1
∂
y
2
×
1
∂
W
2
×
j
⋮
∂
L
o
s
s
∂
y
i
×
1
∂
y
i
×
1
∂
W
i
×
j
]
=
[
∂
L
o
s
s
∂
y
1
×
1
∂
y
1
×
1
∂
W
1
×
1
=
x
1
×
1
∂
L
o
s
s
∂
y
1
×
1
∂
y
1
×
1
∂
W
1
×
2
=
x
2
×
1
⋯
∂
L
o
s
s
∂
y
1
×
1
∂
y
1
×
1
∂
W
1
×
j
=
x
j
×
1
∂
L
o
s
s
∂
y
2
×
1
∂
y
2
×
1
∂
W
2
×
1
=
x
1
×
1
∂
L
o
s
s
∂
y
2
×
1
∂
y
2
×
1
∂
W
2
×
2
=
x
2
×
1
⋯
∂
L
o
s
s
∂
y
2
×
1
∂
y
2
×
1
∂
W
2
×
j
=
x
j
×
1
⋮
⋮
⋱
⋮
∂
L
o
s
s
∂
y
i
×
1
∂
y
i
×
1
∂
W
i
×
1
=
x
1
×
1
∂
L
o
s
s
∂
y
i
×
1
∂
y
i
×
1
∂
W
i
×
2
=
x
2
×
1
⋯
∂
L
o
s
s
∂
y
i
×
1
∂
y
i
×
1
∂
W
i
×
j
=
x
j
×
1
]
=
∂
L
o
s
s
∂
W
=
∂
L
o
s
s
∂
y
∂
y
∂
W
=
[
∂
L
o
s
s
∂
y
1
×
1
∂
L
o
s
s
∂
y
2
×
1
⋮
∂
L
o
s
s
∂
y
i
×
1
]
[
x
1
×
1
x
2
×
1
⋯
x
j
×
1
]
=
∂
L
o
s
s
∂
y
x
T
\frac{\partial Loss }{\partial \boldsymbol{W} } = \frac{\partial Loss }{\partial \boldsymbol{y} } \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{W} } = \\ \left[ \begin{matrix} \frac{\partial Loss }{\partial {y_{1 \times 1}} } \frac{\partial {y}_{1 \times1}}{\partial \boldsymbol{W}_{1 \times \boldsymbol{j}} }\\ \frac{\partial Loss }{\partial {y_{2 \times 1}} } \frac{\partial {y}_{2 \times1}}{\partial \boldsymbol{W}_{2 \times \boldsymbol{j}} } \\ \vdots \\ \frac{\partial Loss }{\partial {y_{i \times 1}} } \frac{\partial {y}_{i \times1}}{\partial \boldsymbol{W}_{i \times \boldsymbol{j}} } \\ \end{matrix} \right] = \left[ \begin{matrix} \frac{\partial Loss }{\partial {y_{1 \times 1}} } \frac{\partial {y}_{1 \times1}}{\partial {W}_{1 \times {1}} } = x_{1 \times 1} & \frac{\partial Loss }{\partial {y_{1 \times 1}} } \frac{\partial {y}_{1 \times1}}{\partial {W}_{1 \times {2}} } = x_{2 \times 1} & \cdots & \frac{\partial Loss }{\partial {y_{1 \times 1}} } \frac{\partial {y}_{1 \times1}}{\partial {W}_{1 \times {j}} } = x_{j \times 1} \\ \frac{\partial Loss }{\partial {y_{2 \times 1}} } \frac{\partial {y}_{2 \times1}}{\partial {W}_{2 \times {1}} } = x_{1 \times 1} & \frac{\partial Loss }{\partial {y_{2 \times 1}} } \frac{\partial {y}_{2 \times1}}{\partial {W}_{2 \times {2}} } = x_{2 \times 1} & \cdots & \frac{\partial Loss }{\partial {y_{2 \times 1}} } \frac{\partial {y}_{2 \times1}}{\partial {W}_{2 \times {j}} } = x_{j \times 1} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial Loss }{\partial {y_{i \times 1}} } \frac{\partial {y}_{i \times1}}{\partial {W}_{i \times {1}} } = x_{1 \times 1} & \frac{\partial Loss }{\partial {y_{i \times 1}} } \frac{\partial {y}_{i \times1}}{\partial {W}_{i \times {2}} } = x_{2 \times 1} & \cdots & \frac{\partial Loss }{\partial {y_{i \times 1}} } \frac{\partial {y}_{i \times1}}{\partial {W}_{i \times {j}} } = x_{j \times 1} \\ \end{matrix} \right] \\ =\frac{\partial Loss }{\partial \boldsymbol{W} } = \frac{\partial Loss }{\partial \boldsymbol{y} } \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{W} } = \\ \left[ \begin{matrix} \frac{\partial Loss }{\partial {y_{1 \times 1}} } \\ \frac{\partial Loss }{\partial {y_{2 \times 1}} } \\ \vdots \\ \frac{\partial Loss }{\partial {y_{i \times 1}} } \\ \end{matrix} \right] \left[ \begin{matrix} x_{1 \times 1} &x_{2 \times 1} & \cdots &x_{j \times 1} \end{matrix} \right] = \frac{\partial Loss }{\partial \boldsymbol{y} } \boldsymbol{x}^T
∂W∂Loss=∂y∂Loss∂W∂y=⎣⎢⎢⎢⎢⎡∂y1×1∂Loss∂W1×j∂y1×1∂y2×1∂Loss∂W2×j∂y2×1⋮∂yi×1∂Loss∂Wi×j∂yi×1⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡∂y1×1∂Loss∂W1×1∂y1×1=x1×1∂y2×1∂Loss∂W2×1∂y2×1=x1×1⋮∂yi×1∂Loss∂Wi×1∂yi×1=x1×1∂y1×1∂Loss∂W1×2∂y1×1=x2×1∂y2×1∂Loss∂W2×2∂y2×1=x2×1⋮∂yi×1∂Loss∂Wi×2∂yi×1=x2×1⋯⋯⋱⋯∂y1×1∂Loss∂W1×j∂y1×1=xj×1∂y2×1∂Loss∂W2×j∂y2×1=xj×1⋮∂yi×1∂Loss∂Wi×j∂yi×1=xj×1⎦⎥⎥⎥⎥⎤=∂W∂Loss=∂y∂Loss∂W∂y=⎣⎢⎢⎢⎢⎡∂y1×1∂Loss∂y2×1∂Loss⋮∂yi×1∂Loss⎦⎥⎥⎥⎥⎤[x1×1x2×1⋯xj×1]=∂y∂LossxT
我没办法从数学上严格证明,但是,从上面的推导来看肯定是对的。当然推荐你看看矩阵求导术。其次上面的都是标量对矩阵或者向量的求导。
这样你大概能够看得出来导数长什么样。 我忘了怎么推导,但是我有个更好的主意,从梯度
∇
\nabla
∇入手。
程序实现
如下,这里初始化选择可以是全1或者是全0,但是那样效果会不好,收敛速度非常慢(为什么?以后慢慢研究)。可以使用NumPy的randn()
和rand()
来进行初始化,numpy.random.rand(d0, d1, …, dn)
的随机样本位于[0, 1)
范围内,numpy.random.randn(d0, d1, …, dn)
是按照标准正态分布生成随机值。当然两种初始化方式收敛速度和稳定性略有不同,稍后讨论。
这里给出神经网络的主要的代码,完整的项目见附录的下载地址。代码中的变量名字尽量按照前面的公式中的结果一一对应,便于理解。例如:y_hat
指的就是神经网络的输出
y
^
\boldsymbol{\hat{y}}
y^,dW1
指的是
∂
L
o
s
s
∂
W
(
1
)
\frac{\partial \mathcal{Loss}}{\partial \mathbf{W}^{(1)}}
∂W(1)∂Loss, alpha
指的是学习率
α
\alpha
α,SquareErrorSum
指的是代价函数
C
o
s
t
(
y
^
,
y
)
Cost( \boldsymbol{\hat{y}} , \boldsymbol{y})
Cost(y^,y),以此类推。
#!/usr/bin/python3
# coding:utf-8
# @Author: Lin Misaka
# @File: net.py
# @Data: 2020/11/30
# @IDE: PyCharm
import numpy as np
import matplotlib.pyplot as plt
# diff = True求导
def Sigmoid(x, diff=False):
def sigmoid(x): # sigmoid函数
return 1 / (1 + np.exp(-x))
def dsigmoid(x):
f = sigmoid(x)
return f * (1 - f)
if (diff == True):
return dsigmoid(x)
return sigmoid(x)
# diff = True求导
def SquareErrorSum(y_hat, y, diff=False):
if (diff == True):
return y_hat - y
return (np.square(y_hat - y) * 0.5).sum()
class Net():
def __init__(self):
# X Input
self.X = np.random.randn(1024, 1)
self.W1 = np.random.randn(16, 1024)
self.b1 = np.random.randn(16, 1)
self.W2 = np.random.randn(16, 16)
self.b2 = np.random.randn(16, 1)
self.W3 = np.random.randn(10, 16)
self.b3 = np.random.randn(10, 1)
self.alpha = 0.01 #学习率
self.losslist = [] #用于作图
def forward(self, X, y, activate):
self.X = X
self.z1 = np.dot(self.W1, self.X) + self.b1
self.a1 = activate(self.z1)
self.z2 = np.dot(self.W2, self.a1) + self.b2
self.a2 = activate(self.z2)
self.z3 = np.dot(self.W3, self.a2) + self.b3
self.y_hat = activate(self.z3)
Loss = SquareErrorSum(self.y_hat, y)
return Loss, self.y_hat
def backward(self, y, activate):
self.delta3 = activate(self.z3, True) * SquareErrorSum(self.y_hat, y, True)
self.delta2 = activate(self.z2, True) * (np.dot(self.W3.T, self.delta3))
self.delta1 = activate(self.z1, True) * (np.dot(self.W2.T, self.delta2))
dW3 = np.dot(self.delta3, self.a2.T)
dW2 = np.dot(self.delta2, self.a1.T)
dW1 = np.dot(self.delta1, self.X.T)
d3 = self.delta3
d2 = self.delta2
d1 = self.delta1
#update weight
self.W3 -= self.alpha * dW3
self.W2 -= self.alpha * dW2
self.W1 -= self.alpha * dW1
self.b3 -= self.alpha * d3
self.b2 -= self.alpha * d2
self.b1 -= self.alpha * d1
def setLearnrate(self, l):
self.alpha = l
def train(self, trainMat, trainLabels, Epoch=5, bitch=None):
for epoch in range(Epoch):
acc = 0.0
acc_cnt = 0
label = np.zeros((10, 1))#先生成一个10x1是向量,减少运算。用于生成one_hot格式的label
for i in range(len(trainMat)):#可以用batch,数据较少,一次训练所有数据集
X = trainMat[i, :].reshape((1024, 1)) #生成输入
labelidx = trainLabels[i]
label[labelidx][0] = 1.0
Loss, y_hat = self.forward(X, label, Sigmoid)#前向传播
self.backward(label, Sigmoid)#反向传播
label[labelidx][0] = 0.0#还原为0向量
acc_cnt += int(trainLabels[i] == np.argmax(y_hat))
acc = acc_cnt / len(trainMat)
self.losslist.append(Loss)
print("epoch:%d,loss:%02f,accrucy : %02f" % (epoch, Loss, acc))
self.plotLosslist(self.losslist, "Loss")
运行结果
学习率为0.01,迭代全部数据1743次的时候准确率达到了98.7%。下面是几条记录:
epoch:0,loss:0.483388,accrucy : 0.107032
epoch:1,loss:0.443391,accrucy : 0.217684
epoch:16,loss:0.415309,accrucy : 0.453981
epoch:88,loss:0.454633,accrucy : 0.834540
epoch:137,loss:0.491693,accrucy : 0.901758
epoch:810,loss:0.045566,accrucy : 0.977249
epoch:1420,loss:0.009614,accrucy : 0.985522
epoch:1743,loss:0.008058,accrucy : 0.987073
Loss图:
关于权值初始化
不同初始化方法收敛速度分析:
学习率为0.01。修改初始化的地方在这里:
似乎有时候会出现Loss增大的情况,但是随机的,与初始化值和学习率有关。
第一幅图最终结果:epoch:99,loss:0.068638,accrucy : 0.903309
效果很差。
epoch:99,loss:0.129023,accrucy : 0.307653
效果有一次很差,我想大胆推断这样初始化的效果都很差,但是这样不科学,我试着改变学习率为0.1,效果有所提升。
epoch:99,loss:0.129075,accrucy : 0.307653
epoch:99,loss:0.003670,accrucy : 0.767322
这种初始化方式可见非常糟糕,虽然几乎是线性下降,但是下降速度简直不要太慢。
epoch:0,loss:4.500000,accrucy : 0.103413
epoch:12,loss:4.500000,accrucy : 0.103413
epoch:13,loss:4.500000,accrucy : 0.103930
epoch:499,loss:4.500000,accrucy : 0.105481
注: 偏置b初始化的方式对收敛影响不大,这个读者可以自己试试。
为什么不同的初始化收敛速度不一样?
待续~
关于损失函数
待续~
关于我的感想
隐含层神经元数量不是越多越好,我以前用PyTroch试过训练和这个网络差不多的有个网络,输入层,输出层和这个神经网络一样,开始隐含层有64和24个神经元,效果含不错,但是我试着改变隐含层神经元的数量使之更多,效果反而不好,并且由于运算量增加,迭代速度大大下降。
从上面的实验结果看似乎权值初始化的时候使用标准正态分布的随机值会比较好,但这是为什么还值得继续学习。此外似乎偏置b初始化的方式对收敛影响不大,这个读者可以自己试试。
关于学习神经网络,如果你是以实用主义来思考的,那么可能你就只需把机器学习领域的算法了解一遍,学会用框架就可以了。但是这也就只是会用罢了。
倘如你想要研究这些网络,试图提高它的准确性、收敛速度、预测速度、内存占用、复杂度等等各种性能,以至于创造一种全新的结构、全能的结构、可以组合的结构等等都需要对它们有深入的了解,而不是只会使用框架这种皮毛。其次学习过程也不能囫囵吞枣,要循序渐进,以实践总结理解为主。
关于欺骗神经网络,神经网络漏洞
附录
文章资源
手写数字识别全连接神经网络教学Demo:将digits.zip
解压到net.py
,安装好环境即可运行。
git clone https://github.com/MisakaMikoto128/FCNNeuralNetworkDemoForHandwrittenDigits.git
参考
[1] 神经网络求导
[2] 常见的损失函数(loss function)总结
[3] 矩阵求导术(上)
[4] 矩阵求导术(下)
[5] 小白都能看懂的softmax详解
链接失效Call我。
github:https://github.com/MisakaMikoto128
个人网站
更多推荐
所有评论(0)