《深度学习的数学》给予了我极大的启发,作者阐述的神经网络的思想和数学基础令我受益颇多,但是由于书中使用Excel作为示例向读者展示神经网络,这对我这样一个不精通Excel的人来说很头疼,因此我打算使用Python来实现书中的一个简单网络模型,即识别数字0和1的模型。 这个网络模型极其简单,比号称机器学习中的“Hello World”的手写数字识别模型更简单,它基本没有实用价值,但是我之所以推崇它,只因为它褪去了神经网络的复杂性,展示了神经网络中最基本、最根本的东西。 模型总共分为三层,第一层为输入层,第二层为隐藏层,第三层为输出层。输入层包括12个输入输出变量 x i ( i = 1 , 2 , . . . , 12 ) x_i (i=1,2,...,12) xi(i=1,2,...,12),隐藏层包括三个神经单元,输出层包括两个神经单元。 下面是涉及到的数学知识。 神经网络的参数是通过将代价函数(损失函数)最小化来确定的,本文所使用的的最小化方法是梯度下降法,但是直接计算梯度是很困难的,因此引入了误差反向传播法,通过计算出输出层的误差,然后通过误差的递推公式计算出隐藏层的误差。 <隐藏层> z 1 2 z_1^2 z12= ∑ i = 1 12 w 1 i 2 x i + b 1 2 \sum_{i=1}^{12}w_{1i}^2x_i+b_1^2 ∑i=112w1i2xi+b12 z 2 2 z_2^2 z22= ∑ i = 1 12 w 2 i 2 x i + b 2 2 \sum_{i=1}^{12}w_{2i}^2x_i+b_2^2 ∑i=112w2i2xi+b22 z 3 2 z_3^2 z32= ∑ i = 1 12 w 3 i 2 x i + b 3 2 \sum_{i=1}^{12}w_{3i}^2x_i+b_3^2 ∑i=112w3i2xi+b32 a i 2 = a ( z i 2 ) ( i = 1 , 2 , 3 ) a_i^2=a(z_i^2) (i=1,2,3) ai2=a(zi2)(i=1,2,3) <输出层> z 1 3 z_1^3 z13= ∑ i = 1 3 w 1 i 3 a i 2 + b 1 3 \sum_{i=1}^{3}w_{1i}^3a_i^2+b_1^3 ∑i=13w1i3ai2+b13 z 2 3 z_2^3 z23= ∑ i = 1 3 w 2 i 3 a i 2 + b 2 3 \sum_{i=1}^{3}w_{2i}^3a_i^2+b_2^3 ∑i=13w2i3ai2+b23 a i 3 = a ( z i 3 ) ( i = 1 , 2 ) a_i^3=a(z_i^3) (i=1,2) ai3=a(zi3)(i=1,2) C = 1 / 2 ∗ ( ( t 1 − a 1 3 ) 2 + ( t 2 − a 2 3 ) 2 ) C=1/2*((t_1-a_1^3)^2+(t_2-a_2^3)^2) C=1/2∗((t1−a13)2+(t2−a23)2),其中 z j l z_j^l zjl为层l的第j个神经单元的加权输入的值, w i j l + 1 w_{ij}^{l+1} wijl+1为层l的第j个神经单元指向层l+1的第i个神经单元的箭头的权重, b j l b_j^l bjl表示层l的第j个神经单元的偏置, a j l a_j^l ajl为层l的第j个神经单元的输出,a(z)为激活函数,C为平均误差,
含义图像为0图像为1 t 1 t_1 t10的正解变量10 t 2 t_2 t21的正解变量01 图像为0图像为1 a 1 3 a_1^3 a13接近1的值接近0的值 a 2 3 a_2^3 a23接近0的值接近1的值输出层L的误差公式 δ j L = ∂ C ∂ a j L a ′ ( z j L ) δ_j^L= \frac{\partial C}{\partial a_j^L}a'(z_j^L) δjL=∂ajL∂Ca′(zjL) 中间层的递推关系式 δ i l = ∑ k = 1 m δ k l + 1 w k i l + 1 a ′ ( z i l ) δ_i^l={\sum_{k=1}^{m}δ_k^{l+1}w_{ki}^{l+1}}a'(z_i^l) δil=∑k=1mδkl+1wkil+1a′(zil),m为层l+1的神经单元的个数。上式表明中间层的 δ i l δ_i^l δil不用求导也可以得出其值,这就是误差反向传播法。
平均误差对权重和偏置的偏导如下: ∂ C ∂ w j i l = δ j l a i l − 1 \frac{\partial C}{\partial w_{ji}^l}=δ_j^la_i^{l-1} ∂wjil∂C=δjlail−1 ∂ C ∂ b j l = δ j l ( l = 2 , 3 ) \frac{\partial C}{\partial b_j^l}=δ_j^l(l=2,3) ∂bjl∂C=δjl(l=2,3)
代价函数(损失函数) C T = ∑ k = 1 m C k , m 在 这 里 指 的 是 全 部 学 习 数 据 的 数 量 C_T=\sum_{k=1}^{m}C_k,m在这里指的是全部学习数据的数量 CT=∑k=1mCk,m在这里指的是全部学习数据的数量
梯 度 ∇ C T = ( ∂ C T ∂ w 11 2 , ∂ C T ∂ w 12 2 , . . . , ∂ C T ∂ w 11 3 , . . . , ∂ C T ∂ b 1 2 , ∂ C T ∂ b 2 2 , . . . , ∂ C T ∂ b 1 3 ) 梯度∇C_T=(\frac{\partial C_T}{\partial w_{11}^2},\frac{\partial C_T}{\partial w_{12}^2},...,\frac{\partial C_T}{\partial w_{11}^3},...,\frac{\partial C_T}{\partial b_1^2},\frac{\partial C_T}{\partial b_2^2},...,\frac{\partial C_T}{\partial b_1^3}) 梯度∇CT=(∂w112∂CT,∂w122∂CT,...,∂w113∂CT,...,∂b12∂CT,∂b22∂CT,...,∂b13∂CT)
梯度下降法基本式: ( Δ w 11 2 , . . . , Δ w 11 3 , . . . , Δ b 1 2 , . . . , Δ b 1 3 , . . . ) = − η ( ∂ C T ∂ w 11 2 , . . . , ∂ C T ∂ w 11 3 , . . . , ∂ C T ∂ b 1 2 , . . . , ∂ C T ∂ b 1 3 ) , 正 的 常 数 η 称 为 学 习 率 (Δ w_{11}^2,...,Δ w_{11}^3,...,Δ b_1^2,...,Δ b_1^3,...)=-η(\frac{\partial C_T}{\partial w_{11}^2},...,\frac{\partial C_T}{\partial w_{11}^3},...,\frac{\partial C_T}{\partial b_1^2},...,\frac{\partial C_T}{\partial b_1^3}),正的常数η称为学习率 (Δw112,...,Δw113,...,Δb12,...,Δb13,...)=−η(∂w112∂CT,...,∂w113∂CT,...,∂b12∂CT,...,∂b13∂CT),正的常数η称为学习率
新的位置: ( w 11 2 + Δ w 11 2 , . . . , w 11 3 + Δ w 11 3 , . . . , b 1 2 + Δ b 1 2 , . . . , b 1 3 + Δ b 1 3 , . . . ) (w_{11}^2+Δ w_{11}^2,...,w_{11}^3+Δ w_{11}^3,...,b_1^2+Δ b_1^2,...,b_1^3+Δ b_1^3,...) (w112+Δw112,...,w113+Δw113,...,b12+Δb12,...,b13+Δb13,...)
基于上述的数学基础,编写了以下的Python程序。
import numpy as np import math #下面是64张图像 _inputs = np.array([[[1,1,1],[1,0,1],[1,0,1],[1,1,1]], [[0,1,1],[1,0,1],[1,0,1],[1,1,1]], [[1,1,0],[1,0,1],[1,0,1],[1,1,1]], [[1,1,1],[1,0,1],[1,0,1],[1,1,0]], [[1,1,1],[1,0,1],[1,0,1],[0,1,1]], [[0,0,0],[1,1,1],[1,0,1],[1,1,1]], [[0,0,0],[0,1,1],[1,0,1],[1,1,1]], [[0,0,0],[1,1,0],[1,0,1],[1,1,1]], [[0,0,0],[1,1,1],[1,0,1],[1,1,0]], [[0,0,0],[1,1,1],[1,0,1],[0,1,1]], #10 [[1,1,1],[1,0,1],[1,1,1],[0,0,0]], [[0,1,1],[1,0,1],[1,1,1],[0,0,0]], [[1,1,0],[1,0,1],[1,1,1],[0,0,0]], [[1,1,1],[1,0,1],[1,1,0],[0,0,0]], [[1,1,1],[1,0,1],[0,1,1],[0,0,0]], [[1,0,1],[1,0,1],[1,0,1],[1,1,1]], [[1,1,1],[1,0,0],[1,0,1],[1,1,1]], [[1,1,1],[1,0,1],[1,0,0],[1,1,1]], [[1,1,1],[1,0,1],[1,0,1],[1,0,1]], [[1,1,1],[1,0,1],[0,0,1],[1,1,1]], #20 [[1,1,1],[0,0,1],[1,0,1],[1,1,1]], [[0,0,1],[1,0,1],[1,0,1],[1,1,1]], [[0,1,1],[1,0,0],[1,0,1],[1,1,1]], [[0,1,1],[1,0,1],[1,0,0],[1,1,1]], [[0,1,1],[1,0,1],[1,0,1],[1,0,1]], [[0,1,1],[1,0,1],[0,0,1],[1,1,1]], [[0,1,1],[0,0,1],[1,0,1],[1,1,1]], [[1,1,0],[1,0,0],[1,0,1],[1,1,1]], [[1,1,0],[1,0,1],[1,0,0],[1,1,1]], [[1,1,0],[1,0,1],[1,0,1],[1,0,1]], #30 [[1,1,0],[1,0,1],[0,0,1],[1,1,1]], [[1,1,0],[0,0,1],[1,0,1],[1,1,1]], [[0,1,0],[0,1,0],[0,1,0],[0,1,0]], [[1,1,0],[0,1,0],[0,1,0],[0,1,0]], [[0,1,0],[0,1,0],[0,1,0],[0,1,0]], [[0,1,0],[0,1,0],[0,1,0],[1,1,0]], [[0,1,0],[0,1,0],[0,1,0],[0,1,1]], [[1,1,0],[0,1,0],[0,1,0],[1,1,0]], [[1,1,0],[0,1,0],[0,1,0],[0,1,1]], [[1,1,0],[0,1,0],[0,1,0],[1,1,1]], #40 [[0,1,0],[0,1,1],[0,1,0],[0,1,0]], [[0,1,0],[0,1,0],[0,1,1],[0,1,0]], [[1,1,0],[0,1,1],[0,1,0],[0,1,0]], [[1,1,0],[0,1,0],[0,1,1],[0,1,0]], [[0,1,0],[0,1,1],[0,1,0],[1,1,0]], [[0,1,0],[0,1,0],[0,1,1],[1,1,0]], [[0,1,0],[0,1,0],[0,1,0],[1,1,1]], [[1,1,0],[0,1,1],[0,1,1],[0,1,1]], [[1,1,0],[0,1,0],[0,1,0],[0,1,0]], [[0,1,1],[0,1,1],[0,1,1],[0,1,1]], #50 [[1,1,0],[1,1,0],[0,1,0],[0,1,0]], [[1,1,0],[0,1,0],[1,1,0],[0,1,0]], [[1,1,0],[1,1,0],[1,1,0],[1,1,0]], [[1,1,0],[0,1,0],[0,0,0],[0,1,0]], [[0,1,0],[0,1,0],[0,1,0],[1,0,0]], [[1,0,0],[0,1,0],[0,1,0],[0,1,0]], [[1,0,0],[0,1,0],[0,1,0],[0,0,1]], [[0,1,0],[0,0,0],[0,1,0],[1,1,0]], [[0,1,0],[0,1,0],[0,0,0],[1,1,0]], [[0,0,0],[0,1,0],[0,1,0],[1,1,0]], #60 [[0,0,0],[0,1,0],[0,1,0],[0,1,0]], [[0,1,0],[0,1,0],[0,1,0],[0,0,0]], [[0,1,0],[0,0,1],[0,0,1],[0,1,0]], [[0,1,0],[1,1,0],[1,1,0],[0,1,0]]]) #对应的标签 labels = np.array([[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0], [1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0], [0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1], [0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1],]) #激活函数 def a(x): return 1.0 / (1 + math.exp(-x)) #激活函数的导数 def aa(x): return a(x) * (1 - a(x)) #学习率n=0.2 n = 0.2 #初始化权重和偏置 神经网络总共47个参数 #初始化使用的是正态分布随机数 #隐藏层 w2_1 = np.random.randn(4,3) w2_2 = np.random.randn(4,3) w2_3 = np.random.randn(4,3) #输出层 w3_1 = np.random.randn(3) w3_2 = np.random.randn(3) #隐藏层和输出层的5个偏置 bs = np.random.randn(5) #处理单张图像 def proprecessing(_inputs,labels): #_inputs的输入形状为(4,3) #labels的输出形状为(2,1) #wi_j代表层i的第j个神经单元,SWi_j代表层i的第j个神经单元的平均误差对权重的偏导,SB代表平均误差对偏置的偏导,Ct为64张图像的平均误差的和 #最终要得到的权重和偏置保存在wi_j和bs中 global w2_1,w2_2,w2_3,w3_1,w3_2,bs,Ct,SW2_1,SW2_2,SW2_3,SW3_1,SW3_2,SB #求加权输入z x = _inputs.flatten() #隐藏层的三个加权输入 #zi_j代表层i的第j个神经单元的加权输入 x = _inputs.flatten() z2_1 = np.sum(w2_1.flatten() * x) + bs[0] z2_2 = np.sum(w2_2.flatten() * x) + bs[1] z2_3 = np.sum(w2_3.flatten() * x) + bs[2] #激活函数的值 #ai_j代表层i的第j个神经单元的激活函数的值 a2_1 = a(z2_1) a2_2 = a(z2_2) a2_3 = a(z2_3) _a = np.array([a2_1,a2_2,a2_3]) #输出层的两个加权输入 z3_1 = np.sum(w3_1 * _a) + bs[3] z3_2 = np.sum(w3_2 * _a) + bs[4] a3_1 = a(z3_1) a3_2 = a(z3_2) #计算平均误差 #累计64张图像的平均误差 Ct += 1.0 / 2 * ((labels[0] - a3_1) ** 2 + (labels[1] - a3_2) ** 2) #计算神经单元误差 #输出层 #di_j代表层i的第j个神经单元的误差 d3_1 = (a3_1 - labels[0]) * aa(z3_1) d3_2 = (a3_2 - labels[1]) * aa(z3_2) #隐藏层 d2_1 = (d3_1 * w3_1[0] + d3_2 * w3_2[0]) * aa(z2_1) d2_2 = (d3_1 * w3_1[1] + d3_2 * w3_2[1]) * aa(z2_2) d2_3 = (d3_1 * w3_1[2] + d3_2 * w3_2[2]) * aa(z2_3) #平方误差对权重的偏导数 #隐藏层 W2_1 = np.array([[d2_1 * x[0],d2_1 * x[1],d2_1 * x[2]], [d2_1 * x[3],d2_1 * x[4],d2_1 * x[5]], [d2_1 * x[6],d2_1 * x[7],d2_1 * x[8]], [d2_1 * x[9],d2_1 * x[10],d2_1 * x[11]]]) W2_2 = np.array([[d2_2 * x[0],d2_2 * x[1],d2_2 * x[2]], [d2_2 * x[3],d2_2 * x[4],d2_2 * x[5]], [d2_2 * x[6],d2_2 * x[7],d2_2 * x[8]], [d2_2 * x[9],d2_2 * x[10],d2_2 * x[11]]]) W2_3 = np.array([[d2_3 * x[0],d2_3 * x[1],d2_3 * x[2]], [d2_3 * x[3],d2_3 * x[4],d2_3 * x[5]], [d2_3 * x[6],d2_3 * x[7],d2_3 * x[8]], [d2_3 * x[9],d2_3 * x[10],d2_3 * x[11]]]) #输出层 W3_1 = np.array([d3_1 * a2_1,d3_1 * a2_2,d3_1 * a2_3]) W3_2 = np.array([d3_2 * a2_1,d3_2 * a2_2,d3_2 * a2_3]) #平均误差对偏置的偏导数 B = np.array([d2_1,d2_2,d2_3,d3_1,d3_2]) #误差的偏导数(关于权重和偏置)的和 SW2_1 += W2_1 SW2_2 += W2_2 SW2_3 += W2_3 SW3_1 += W3_1 SW3_2 += W3_2 SB += B #至此一张图像计算结束 for j in range(50): Ct = 0.0 SW2_1 = np.zeros((4,3)) SW2_2 = np.zeros((4,3)) SW2_3 = np.zeros((4,3)) SW3_1 = np.zeros((3,)) SW3_2 = np.zeros((3,)) SB = np.zeros((5,)) for i in range(64): proprecessing(_inputs[i],labels[i]) #更新权重和偏置 w2_1+=(-1 * n * SW2_1) w2_2+=(-1 * n * SW2_2) w2_3+=(-1 * n * SW2_3) w3_1+=(-1 * n * SW3_1) w3_2+=(-1 * n * SW3_2) bs +=(-1 * n * SB) print('第{0}次神经网络的平均误差:{1}'.format(j + 1,Ct)) print("最终得到的权重:") print("隐藏层:") print('单元1:',w2_1.tolist()) print('单元2:',w2_2.tolist()) print('单元3:',w2_3.tolist()) print("输出层:") print('单元1:',w3_1.tolist()) print('单元2:',w3_2.tolist()) print("偏置(前三个为隐藏层,后两个为输出层):") print(bs.tolist())训练50次之后得到的权重和偏置为: 隐藏层: 单元1: [[-0.8505581996325163, 1.7507687371871914, -1.2994321754147657], [-1.9226757925048328, 0.968836216384317, -2.4045078505188395], [-0.6009430678057187, 0.9127852871224914, -0.4479745936935321], [-1.4402271604371404, 0.8471920856358344, -0.4887527173424544]] 单元2: [[0.8318703450124101, 0.9551533298847994, -1.356952762339686], [-1.4770786935608917, 1.8738906335568797, 0.17838222118135907], [-2.0007410285997036, 1.9255939018718844, -1.0899626334849999], [-0.4992318096836366, 0.5719263655337985, -0.27190666824061005]] 单元3: [[2.1540058037580074, -0.09339932292011276, 0.9287012415275242], [1.201125742119399, -0.1325111790789848, -0.37753606965043596], [0.24579738234367068, -0.4124707432579135, -0.04713796565479969], [-0.4087656636791272, 0.6030166641852472, 1.092667594656701]] 输出层: 单元1: [-2.977057722622633, -3.3505413915525195, 1.596238692770884] 单元2: [3.862705218756803, 2.6321771395090976, -2.146219869563662]
偏置(前三个为隐藏层,后两个为输出层): [1.6127749794069484, -0.7653127531469095, -0.12850706330884437, 1.6513527302710038, -1.2700763868883462]
利用新得到的权重和偏置组成新的神经网络,然后构造两张图像对其进行预测。测试程序如下:
#单张图像 import numpy as np import math #激活函数 def a(x): return 1.0 / (1 + math.exp(-x)) #激活函数的导数 def aa(x): return a(x) * (1 - a(x)) #权重和偏置来自于上面得到的值 w2_1 = np.array([[-0.8505581996325163, 1.7507687371871914, -1.2994321754147657], [-1.9226757925048328, 0.968836216384317, -2.4045078505188395], [-0.6009430678057187, 0.9127852871224914, -0.4479745936935321], [-1.4402271604371404, 0.8471920856358344, -0.4887527173424544]]) w2_2 = np.array([[0.8318703450124101, 0.9551533298847994, -1.356952762339686], [-1.4770786935608917, 1.8738906335568797, 0.17838222118135907], [-2.0007410285997036, 1.9255939018718844, -1.0899626334849999], [-0.4992318096836366, 0.5719263655337985, -0.27190666824061005]]) w2_3 = np.array([[2.1540058037580074, -0.09339932292011276, 0.9287012415275242], [1.201125742119399, -0.1325111790789848, -0.37753606965043596], [0.24579738234367068, -0.4124707432579135, -0.04713796565479969], [-0.4087656636791272, 0.6030166641852472, 1.092667594656701]]) w3_1 = np.array([-2.977057722622633, -3.3505413915525195, 1.596238692770884]) w3_2 = np.array([3.862705218756803, 2.6321771395090976, -2.146219869563662]) bs = np.array([1.6127749794069484, -0.7653127531469095, -0.12850706330884437, 1.6513527302710038, -1.2700763868883462]) def getResult(_inputs): global w2_1,w2_2,w2_3,w3_1,w3_2,bs #求加权输入z x = _inputs.flatten() #隐藏层的三个加权输入 x = _inputs.flatten() z2_1 = np.sum(w2_1.flatten() * x) + bs[0] z2_2 = np.sum(w2_2.flatten() * x) + bs[1] z2_3 = np.sum(w2_3.flatten() * x) + bs[2] #激活函数的值 a2_1 = a(z2_1) a2_2 = a(z2_2) a2_3 = a(z2_3) _a = np.array([a2_1,a2_2,a2_3]) #输出层的两个加权输入 z3_1 = np.sum(w3_1 * _a) + bs[3] z3_2 = np.sum(w3_2 * _a) + bs[4] a3_1 = a(z3_1) a3_2 = a(z3_2) print('图像中为0的概率为:{0},为1的概率为:{1}'.format(a3_1,a3_2)) print("预测值:",1 if a3_1 < a3_2 else 0) #对图像1的测试 getResult(np.array([[0,1,1],[0,1,0],[0,1,0],[0,1,0]])) #对图像0的测试 getResult(np.array([[0,1,0],[1,0,1],[1,0,1],[1,0,1]]))至此我们已经完成了一个简单的识别数字0和1的神经网络,通过这个例子你可以更清楚的认识到“学习”即指根据跟定的学习数据确定权重和偏置。