前言
前面学到,单层的逻辑回归模型无法解决线性不可分的问题,因此可以使用多层神经网络模型,其中神经网络的隐藏层可以被理解为用来将线性不可分的数据变为线性可分。
李弘毅老师的机器学习课程在讲完反向传播后就直接开始讲keras的使用了,而我则迫切想自己从零开始实现一个简单的神经网络,因此便有了这篇文章。
我设计的神经网络为全连接网络,如下:
由于存在问题,这里只重点记录存在的问题。
问题
- 初始化权重的问题:
测试发现,权重初始化的值对于模型准确率的影响非常大,例如最初我使用numpy.random.rand()
对权重初始化,这会使权重固定在 [ 0 1 ) [0\ 1) [0 1),在这种情况下,模型表现出对异或的分辨能力时好时坏(总体来说很差劲,即使分对了,可能性也只在0.6左右);后来,我使用numpy.random.randn()
进行权重初始化,模型似乎准确了一些(0.7);由此我想到是否是随机权重的权值太低的问题,在上面的基础上乘以了一个系数(反复尝试得出)2,此时模型才表现出了相对令人置信的结果(仍然不是特别好):
- 使用adagrad优化出现的问题:
使用adagrad进行优化,但一直报错说除零错误,看了一下如果加上adagrad,迭代没几次权重和偏置就变成了0,百思不得其解…
代码
# -*- coding:utf-8 -*-
import numpy as np
x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
# 含有一个隐藏层的神经网络实现逻辑异或运算
y = np.array([[0], [1], [1], [0]])
# 如果实现的是与运算,将上面改为 y = np.array([[0], [0], [0], [1]])
w1 = 2 * np.random.randn(2, 3)
b1 = np.zeros((3, 1))
w2 = 2 * np.random.randn(3, 1)
b2 = 0.0
hidden = np.zeros((3, 1))
output = 0.0
dc_dz1 = np.zeros((3, 1))
dc_dz2 = 0.0
def sigmoid(z):
# sigmoid
return 1.0 / (1.0 + np.exp(-z))
# tanh
# return (np.exp(z)-np.exp(-z))/(np.exp(z)+np.exp(-z))
# leaky relu
# z = np.array(z)
# z[z <= 0] *= 0.01
# z = np.matrix(z)
# return z
def dsigmoid(z):
# sigmoid
return sigmoid(z) * (1-sigmoid(z))
# tanh
# return 1 - sigmoid(z)**2
# leaky relu
# z[z <= 0] = 0.01
# z[z > 1] = 1
# return z
def forward_pass(index):
global z1, z2
z1 = w1.T.dot(np.matrix(x[index]).T) + b1
hidden = sigmoid(z1) # 3x1 计算的是中间层的值
z2 = w2.T.dot(hidden) + b2
output = sigmoid(z2) # 输出为单个值,表示[x1, x2]属于类别一的概率
def back_pass(index):
global dc_dz1, dc_dz2
# 先计算output的C对z2的偏导数
# dc_dz2 = dsigmoid(z2) * (-y[index]/sigmoid(z2) + (1-y[index])/(1-sigmoid(z2)))
# 下面这是上面化简的结果,因为 d_sig(x) = sig(x) * (1 - sig(x))
# 而如果是上式的话,可能在分母部分出现除零错误
dc_dz2 = (sigmoid(z2) - y[index])
# 再计算隐藏层的C对z1的偏导数
for i in range(len(dc_dz1)):
dc_dz1[i] = dc_dz2 * w2[i] * dsigmoid(z1[i])
def gradient_descent():
global w1, w2, b1, b2
epochs = 1000
learning_rate = 1.0
sum_b1, sum_b2, sum_w1, sum_w2 = 0., 0., 0., 0.
for i in range(epochs):
# 初始化梯度为0
grad_w1 = np.zeros((2, 3))
grad_w2 = np.zeros((3, 1))
grad_b1 = np.zeros((3, 1))
grad_b2 = 0.0
length = len(x)
# 计算所有样本梯度之和
for j in range(length):
forward_pass(j) # 前向传播计算所有神经元的值
back_pass(j) # 反向传播计算C对z的梯度
# print(dc_dz2)
grad_w2 += hidden * dc_dz2
# t = np.matrix(x[j])
grad_w1 += dc_dz1.dot(np.matrix(x[j])).T
grad_b1 += dc_dz1
grad_b2 += dc_dz2
# print(dc_dz2)
# 取样本平均梯度,防止计算溢出
#grad_w1 /= length
#grad_w2 /= length
#grad_b1 /= length
#grad_b2 /= length
# adagrad
#sum_b1 += grad_b1**2
#sum_b2 += grad_b2**2
#sum_w1 += grad_w1**2
#um_w2 += grad_w2**2
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
b1 -= learning_rate * grad_b1
b2 -= learning_rate * grad_b2
def test_network():
for i in range(len(x)):
t = np.matrix(x[i])
hid = sigmoid(w1.T.dot(t.T)+b1)
out = sigmoid(hid.T.dot(w2)+b2)
flag = 0
if out >= 0.5:
flag = 1
print('input:', x[i], 'output:', flag, 'probability:%.6f' % out)
def show_variable():
print(w1)
print(w2)
print(b1)
print(b2)
if __name__ == '__main__':
# 前向传播与反向传播的计算结果相乘即为C对w[i]的偏导数
gradient_descent()
test_network()
# show_variable()
模型在与或门的表现
1.或门
2.与门
使用tanh作为激活函数区分异或的效果
其中一次的效果出奇的好:
使用ReLU的效果
(并不是每次都这么好,和上面一样取决于初始化的权重,这两个相比sigmoid似乎更不稳定)