numpy实现一个简单的神经网络(python3)
感知机(Perceptron)
一种简单的感知机结构如下图所示,由三个输入节点和一个输出节点构成,三个输入节点x1,x2,x3分别代表一个输入样本x的三个特征值;w1,w2,w3分别代表三个特征值对应的权重;b为偏置项;输出节点中的z和o分别代表线性变换后的输出值和非线性变换后的输出值。
image其中映射函数为激活函数,下面列几个常见的激活函数:
函数名 | 函数表达式 | 导数 |
---|---|---|
经常用其构成的 损失函数的导数: [1] |
神经网络(Neural Network)
神经网络基本结构
神经网络与感知机类似,但是它的节点更加复杂,下图是一个含有1层隐藏层的神经网络,也是一种最简单的神经网络,我们可以看到这个神经网络的输入层有2个节点,隐藏层有3个节点,输出层有1个节点。我们可以认为神经网络由多个感知机构成。我们以下图所示结构为例,实现一个可以进行数据分类的神经网络。
image
假设我们有N个样本,对于每一个样本来说,都有两个特征值,对于这样的每一个样本都满足公式2,公式中带小括号的上标代表神经网络的层数,为相邻两层两个节点之间的权重系数,其中的代表前一层的第个节点,代表后一层的第个节点。
我们可以用矩阵形式改写公式2:
公式2中为输入矩阵,为隐藏层偏置矩阵,为输入层到隐藏层的权重矩阵,为隐藏层到输出层的权重矩阵,为输出层偏置矩阵,为输出矩阵(结果预测矩阵),,矩阵维度为,矩阵维度为。
神经网络损失函数
我们这里用改写的方差公式作为神经网络预测分类结果的损失函数,正确的分类结果矩阵记为:
根据梯度下降法,我们需要求损失函数的梯度,梯度下降法的实现可以看这里。损失函数可以表示为的形式(类似地,,),由于是我们需要训练的参数,所以我们需要分别求对的梯度(这里涉及到矩阵的求导,见附录)。
根据梯度下降法,我们在求完梯度以后,需要更新我们的参数值,这里以为例:
由公式6可以看出,的梯度矩阵应该与维度相同,即与维度相同,因此应该为1。所以我们在编程时应该一个样本一个样本的训练,而不是个样本一起训练。当时,公式5可以简化为:
按照上述思路进行编程,这里隐藏层激活函数选择sigmoid函数,输出层激活函数选择tanh函数,得到分类结果的错误率为0.01~0.06,当隐藏层和输出层激活函数都选择tanh函数时,错误率更低。下图为错误率为0.025时的分类结果图。我们可以看到图中有5个数据点分类错误。
局限性
由于我们是一个样本一个样本训练的,所以我们得到的参数也是和这些样本一一对应的,因此这个模型无法画出决策边界,也无法预测新的数据,预测新的数据好像是应该对训练好的参数进行插值,但是我看别人没有那么做的,可能这样不大好。
附录
神经网络代码
# -*- encoding=utf-8 -*-
__Author__ = "stubborn vegeta"
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from matplotlib.colors import ListedColormap
class neuralNetwork(object):
def __init__(self, X, Y, inputLayer, outputLayer, hiddenLayer=3,learningRate=0.01, epochs=10):
"""
learningRate:学习率
epochs:训练次数
inputLayer:输入层节点数
hiddenLayer:隐藏层节点数
outputLayer:输出层节点数
"""
self.learningRate = learningRate
self.epochs = epochs
self.inputLayer = inputLayer
self.hiddenLayer = hiddenLayer
self.outputLayer = outputLayer
self.X = X
self.Y = Y
self.lenX,_ = np.shape(self.X)
s=np.random.seed(0)
# W1:输入层与隐藏层之间的权重;W2:隐藏层与输出层之间的权重;B1:隐藏层各节点的偏置项;B2:输出层各节点的偏置项
self.W1 = np.array(np.random.random([self.inputLayer, self.hiddenLayer])*0.5) #2*3
self.B1 = np.array(np.random.random([self.lenX,self.hiddenLayer])*0.5) #200*3
self.W2 = np.array(np.random.random([self.hiddenLayer, self.outputLayer])*0.5) #3*1
self.B2 = np.array(np.random.random([self.lenX,self.outputLayer])*0.5) #200*1
def activationFunction(self, funcName:str, X):
"""
激活函数
sigmoid: 1/1+e^(-z)
tanh: [e^z-e^(-z)]/[e^z+e^(-z)]
softmax: e^zi/sum(e^j)
"""
switch = {
"sigmoid": 1/(1+np.exp(-X)),
"tanh": np.tanh(X),
# "softmax": np.exp(X-np.max(X))/np.sum(np.exp(X-np.max(X)), axis=0)
}
return switch[funcName]
def activationFunctionGrad(self, funcName:str, X):
"""
激活函数的导数
"""
switch = {
"sigmoid": np.exp(-X)/(1+np.exp(-X))**2,
"tanh": 1-(np.tanh(X)**2),
# "softmax": np.exp(X-np.max(X))/np.sum(np.exp(X-np.max(X)), axis=0)
}
return switch[funcName]
def train(self, funcNameH:str, funcNameO:str):
"""
funcNameH: 隐藏层激活函数
funcNameO: 输出层激活函数
"""
for i in range(0,self.epochs):
j = np.random.randint(self.lenX)
x = np.array([self.X[j]])
y = np.array([self.Y[j]])
b1 = np.array([self.B1[j]])
b2 = np.array([self.B2[j]])
# 前向传播
zHidden = x.dot(self.W1)+b1
z1 = self.activationFunction(funcNameH, zHidden) #1*3
zOutput = z1.dot(self.W2)+b2
z2 = self.activationFunction(funcNameO, zOutput) #1*1
# 反向传播
dW2 = (z2-y)*(z1.T*self.activationFunctionGrad(funcNameO,zOutput))
db2 = (z2-y)*self.activationFunctionGrad(funcNameO,zOutput)
dW1 = (z2-y)*(self.activationFunctionGrad(funcNameO,zOutput)*self.W2.T)*(x.T.dot(self.activationFunctionGrad(funcNameH,zHidden)))
db1 = (z2-y)*(self.activationFunctionGrad(funcNameO,zOutput)*self.W2.T)*self.activationFunctionGrad(funcNameH,zHidden)
#更新参数
self.W2 -= self.learningRate*dW2
self.B2[j] -= self.learningRate*db2[0]
self.W1 -= self.learningRate*dW1
self.B1[j] -= self.learningRate*db1[0]
return 0
def predict(self, xNewData, funcNameH:str, funcNameO:str):
X = xNewData #200*2
N,_ = np.shape(X)
yPredict = []
for j in range(0,N):
x = np.array([X[j]])
b1 = np.array([self.B1[j]])
b2 = np.array([self.B2[j]])
# 前向传播
zHidden = x.dot(self.W1)+b1
z1 = self.activationFunction(funcNameH, zHidden) #1*3
zOutput = z1.dot(self.W2)+b2
z2 = self.activationFunction(funcNameO, zOutput) #1*1
z2 = 1 if z2>0.5 else 0
yPredict.append(z2)
return yPredict,N
if __name__ == "__main__":
X,Y = datasets.make_moons(200, noise=0.15)
neural_network = neuralNetwork (X=X, Y=Y, learningRate=0.2, epochs=1000, inputLayer=2, hiddenLayer=3, outputLayer=1)
funcNameH = "sigmoid"
funcNameO = "tanh"
neural_network.train(funcNameH=funcNameH,funcNameO=funcNameO)
yPredict,N = neural_network.predict(xNewData=X,funcNameH=funcNameH,funcNameO=funcNameO)
print("错误率:", sum((Y-yPredict)**2)/N)
colormap = ListedColormap(['royalblue','forestgreen']) # 用colormap中的颜色表示分类结果
plt.subplot(1,2,1)
plt.scatter(X[:,0],X[:,1],s=40, c=Y, cmap=colormap)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Standard data")
plt.subplot(1,2,2)
plt.scatter(X[:,0],X[:,1],s=40, c=yPredict, cmap=colormap)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Predicted data")
plt.show()
感知机结构图代码
digraph network{
edge[fontname="Monaco"]
node[fontname="Monaco"]
rankdir=LR
b[shape=plaintext]
x1->"z|o"[label=w1]
x2->"z|o"[label=w2]
x3->"z|o"[label=w3]
b->"z|o"
{rank=same;b;"z|o"}
}
神经网络结构图代码
digraph network{
edge[fontname="Monaco"]
node[fontname="Monaco",shape=circle]
rankdir=LR
subgraph cluster_1{
color = white
fontname="Monaco"
x1,x2;
label = "Input Layer";
}
subgraph cluster_2{
color = white
fontname="Monaco"
h3,h1,h2;
label = "Hidden Layer";
}
subgraph cluster_3{
// rank=same
color = white
fontname="Monaco"
o;
label = "Output Layer";
}
x1->h1
x1->h2
x1->h3
x2->h1
x2->h2
x2->h3
rank=same;h1;h2;h3
h1->o
h2->o
h3->o
}