代码
# author:yck
# readme:使用时,请从第77行开始阅读
import numpy as np
class neutral_network(object):
def __init__(self,nodes_num,learning_rate):
self.nodes_num = nodes_num # nodes_num:每层的结点数,最后一层必须为1,第一层符合输入的特征维度
self.layers = len(nodes_num) #BP神经网络层数
self.learning_rate = learning_rate #学习率
self.w = [] # weight
self.grad = [] # 梯度方向
self.loss_func = "MSE" #损失函数
self.node = [] # 记录各结点的值
for i in range(0,self.layers-1):
self.w.append(np.random.rand(self.nodes_num[i+1],self.nodes_num[i]+1)) #+1是因为偏置项,也可合并为w
self.grad.append(np.zeros((self.nodes_num[i+1],self.nodes_num[i]+1)))
layer_node = [0.0] * self.nodes_num[i]
layer_node.append(1.0) #这里多一个1,同样是把偏置项考虑进w中。x就多一个1。
self.node.append(np.array(layer_node))
self.node.append(np.array([0.0]))
def sigmoid(self,x):
if x >= 0:
return 1.0 / (1 + np.exp(-x))
else:
return np.exp(x) / (np.exp(x) + 1)
def grad_sigmoid(self,y): ## sigmoid函数求导
return y * (1 - y)
def forward(self,x): # x为一个向量,代表训练数据。x向量长度必须与nodes_num第一维相同
# 赋值
for i in range(len(x)):
self.node[0][i] = x[i]
# 前向传播
layer_num = 0
for layer in self.w:
layer_num += 1
for vector_num in range(len(layer)):
if vector_num < len(layer)-1:
self.node[layer_num][vector_num] = self.sigmoid(np.dot(layer[vector_num],self.node[layer_num-1]))
else:
self.node[layer_num][vector_num] = np.dot(layer[vector_num], self.node[layer_num - 1])
def backwards(self,y): # y为一个输出,代表真实值
# 反向传播,做的个别地方可能不一定那么对,也有可能有大问题,欢迎大家指正。
if self.loss_func == "MSE":
x_predict = self.node[-1][0]
xishu = -2 * (y - x_predict) # 光对损失函数求导
for i in range (len(self.grad)-1,-1,-1):
if i == len(self.grad)-1: # 最后一层不做激活
self.grad[i] = xishu * self.node[i]
elif i ==len(self.grad)-2: #倒数第二层
self.grad[i] = xishu * self.w[i+1] * np.array([self.grad_sigmoid(self.node[i+1])])
else: # 前面的层
w_new = self.w[i+1] * np.array([self.grad_sigmoid(self.node[i+1])])
w_new = np.concatenate((self.w[i+1],np.array([[0] * w_new.shape[1]])),axis=0) # 有具体解释
self.grad[i] = np.matmul(self.grad[i+1],w_new) # 链式法则
for i in range(0,len(self.grad)-1):
self.grad[i] = np.matmul(self.grad[i].T,np.array([self.node[i]])) #乘小尾巴X
#展示梯度
for i in range(len(self.grad)):
if i < len(self.grad)-1 :
self.grad[i] = self.grad[i][:-1]
self.w[i] = self.w[i] - self.learning_rate * self.grad[i]
else:
self.w[i] = self.w[i] - self.learning_rate * self.grad[i]
print("已更新权重为:")
print(self.w)
# 使用说明:
# nodes_num:从输入层到输出层依次的结点数,因为这个代码是预测,最后一个一定是1。第一个必须和输入的特征x的长度相匹配。
# 比如下例子中,x的长度为5,这个nodes_num第一个数也得是5。
# 当前仅拟合一组数据
n = neutral_network(nodes_num=[5,2,1],learning_rate=0.05)
x = [0.1,0.2,0.3,0.4,0.5]
y = 3 #真实值
train_epoch = 20 #训练轮数
n.forward(x)
for i in range(train_epoch):
print("---------------------------------------------")
print("第{}次训练开始".format(i))
n.backwards(y)
n.forward(x)
print("当前预测值为 :{}, 真实值为 :{}".format(n.node[-1][0],y))
测试:
**
这个是个笨办法,我感觉也不一定对。请各位指正。
**