神经网络NN:
神经网络技术起源于上世纪五、六十年代,当时叫感知机(perceptron)。随着数学的发展,到上世纪八十年代出现了多层感知机,多层感知机有多个隐含层,可以摆脱早期离散传输函数的束缚,使用sigmoid或tanh等连续函数模拟神经元对激励的响应,在训练算法上则使用Werbos发明的反向传播BP算法。这就是我们现在所说的神经网络NN,或者叫BP神经网络。
深度神经网络DNN:
2006年,Hinton利用预训练方法缓解了局部最优解问题,将隐含层推动到了7层,神经网络真正意义上有了深度。深度没有固定定义,在语音识别中4层神经网络就被认为是较深的,而在图像识别中20层以上的网络屡见不鲜。为了克服梯度消失,ReLU、maxout等传输函数代替了sigmoid,形成了如今DNN的基本形式。单从结构上来说全连接的DNN和多层感知机是没有任何区别的。
卷积神经网络CNN:
对于CNN来说,并不是所有上下层神经元都能直接相连,而是通过卷积作为中介,同一个卷积核在所有图像内是共享的,图像通过卷积操作后仍然保留原先的位置关系。训练算法也是用的BP,因为加入卷积,可以更好的处理2D数据,例如图像和语音。
全连接的DNN还存在另一个问题,无法对时间序列上的变化进行建模。然而,样本出现的时间顺序对于自然语言处理、语音识别、手写体识别等应用非常重要。为了适应这种需求,就出现了循环神经网络。在普通的全连接网络或CNN中,每层神经元的信号只能向上一层传播,样本的处理在各个时刻独立,因此又被称为前向神经网络(Feed-forward Neural Network)。而在RNN中,神经元的输出可以在下一个时间戳直接作用到自身。
BP神经网络的学习过程由信号的正向传播与误差的反向传播两个过程组成:
正向传播:输入样本从输入层传入,经隐含层逐层处理后,传向输出层。
误差的反向传播:将输出误差以某种形式通过隐层向输入层逐层反传,并将误差分摊给各层的所有单元,从而获得各层单元的误差信号,此误差信号即作为修正各单元权值的依据。
这种信号正向传播与误差反向传播的各层权值调整过程周而复始地进行。权值不断调整过程,也就是网络的学习训练过程。此过程一直进行到网络输出的误差减少到可以接受的程度,或进行到预先设定的学习次数为止。
常见的3层BP网络模型:其中,各节点的传递函数f必须满足处处可导的条件,最常用的为Sigmoid函数,第i个神经元的净输入为nwti,输出为Oi.如果网络输出层第k个神经元的期望输出为yk*,则网络的平方型误差函数为:
由于BP算法按照误差函数E的负梯度修改权值,故权值的更新公式可表示为:
其中,t表示迭代次数, 对于输出层神经元权值的更新公式为:
其中,δk称作输出层第k 个神经元的学习误差.对隐含层神经元权值的更新公式为:
其中,δj称作隐含层第j个神经元的学习误差.BP的误差反向传播思想可以概括为:利用输出层的误差来估计出其直接前导层的误差,再借助于这个新的误差来计算更前一层的误差,按照这样的方式逐层反传下去便可以得到所有各层的误差估计。
1、S型函数(sigmoid Function)
2、双S型函数
关于偏移:
w1x1+w2x2+....+wnxn>=t
w1x1+w2x2+....+wnxn+t(-1)>=0
因此阈值t可以看成输入为-1的权重,这个特殊的权重就叫偏移,也是为什么每个神经细胞初始化时都要增加一个权重的理由。
步骤:
1、网络初始化,初始化各连接权值,设定误差函数,设定计算精度和最大学习次数
2、随机选取输入样本和对应的输出样本
3、计算隐含层各神经元的输入和输出
4、求偏导数,计算误差函数对输出层的各神经元的偏导数
5、修正权值,利用输出层和隐含层来修正连接权值
6、修正权值,利用隐含层和输入层来修正连接权值
7、计算全局误差
有了理论基础和公式,实现时就只需要选择是用for循环还是迭代了!
方案一、:
def rand(a, b):
return (b - a) * random.random() + a
def make_matrix(m, n, fill=0.0):
mat = []
for i in range(m):
mat.append([fill] * n)
return mat
def sigmoid(x):
return 1.0 / (1.0 + math.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x)
class BPNeuralNetwork:
def __init__(self):
self.input_n = 0
self.hidden_n = 0
self.output_n = 0
self.input_cells = []
self.hidden_cells = []
self.output_cells = []
self.input_weights = []
self.output_weights = []
self.input_correction = []
self.output_correction = []
def setup(self, ni, nh, no):
self.input_n = ni + 1
self.hidden_n = nh
self.output_n = no
# init cells
self.input_cells = [1.0] * self.input_n
self.hidden_cells = [1.0] * self.hidden_n
self.output_cells = [1.0] * self.output_n
# init weights
self.input_weights = make_matrix(self.input_n, self.hidden_n)
self.output_weights = make_matrix(self.hidden_n, self.output_n)
# random activate
for i in range(self.input_n):
for h in range(self.hidden_n):
self.input_weights[i][h] = rand(-0.2, 0.2)
for h in range(self.hidden_n):
for o in range(self.output_n):
self.output_weights[h][o] = rand(-2.0, 2.0)
# init correction matrix
self.input_correction = make_matrix(self.input_n, self.hidden_n)
self.output_correction = make_matrix(self.hidden_n, self.output_n)
def predict(self, inputs):
# activate input layer
for i in range(self.input_n - 1):
self.input_cells[i] = inputs[i]
# activate hidden layer
for j in range(self.hidden_n):
total = 0.0
for i in range(self.input_n):
total += self.input_cells[i] * self.input_weights[i][j]
self.hidden_cells[j] = sigmoid(total)
# activate output layer
for k in range(self.output_n):
total = 0.0
for j in range(self.hidden_n):
total += self.hidden_cells[j] * self.output_weights[j][k]
self.output_cells[k] = sigmoid(total)
return self.output_cells[:]
def back_propagate(self, case, label, learn, correct):
# feed forward
self.predict(case)
# get output layer error
output_deltas = [0.0] * self.output_n
for o in range(self.output_n):
error = label[o] - self.output_cells[o]
output_deltas[o] = sigmoid_derivative(self.output_cells[o]) * error
# get hidden layer error
hidden_deltas = [0.0] * self.hidden_n
for h in range(self.hidden_n):
error = 0.0
for o in range(self.output_n):
error += output_deltas[o] * self.output_weights[h][o]
hidden_deltas[h] = sigmoid_derivative(self.hidden_cells[h]) * error
# update output weights
for h in range(self.hidden_n):
for o in range(self.output_n):
change = output_deltas[o] * self.hidden_cells[h]
self.output_weights[h][o] += learn * change + correct * self.output_correction[h][o]
self.output_correction[h][o] = change
# update input weights
for i in range(self.input_n):
for h in range(self.hidden_n):
change = hidden_deltas[h] * self.input_cells[i]
self.input_weights[i][h] += learn * change + correct * self.input_correction[i][h]
self.input_correction[i][h] = change
# get global error
error = 0.0
for o in range(len(label)):
error += 0.5 * (label[o] - self.output_cells[o]) ** 2
return error
def train(self, cases, labels, limit=10000, learn=0.05, correct=0.1):
for j in range(limit):
error = 0.0
for i in range(len(cases)):
label = labels[i]
case = cases[i]
error += self.back_propagate(case, label, learn, correct)
def test(self):
cases = [
[0, 0],
[0, 1],
[1, 0],
[1, 1],
]
labels = [[0], [1], [1], [0]]
self.setup(2, 5, 1)
self.train(cases, labels, 10000, 0.05, 0.1)
for case in cases:
print(self.predict(case))
方案二、用类和对象包装:
若分为神经元类,网络层类,整个神经网络类。
首先定义激活函数
def logistic(x):
return 1 / (1 + np.exp(-x))
def logistic_derivative(x):
return logistic(x) * (1 - logistic(x))
Neuron类设计代码如下:
class Neuron:
def __init__(self, len_input):
# 输入的初始参数, 随机取很小的值(<0.1)
self.weights = np.random.random(len_input) * 0.1
# 当前实例的输入
self.input = np.ones(len_input)
# 对下一层的输出值
self.output = 1
# 误差项
self.deltas_item = 0
# 上一次权重增加的量,记录起来方便后面扩展时可考虑增加冲量
self.last_weight_add = 0
def calc_output(self, x):
# 计算输出值
self.input = x
self.output = logistic(np.dot(self.weights.T, self.input))
return self.output
def get_back_weight(self):
# 获取反馈差值
return self.weights * self.deltas_item
def update_weight(self, target=0, back_weight=0, learning_rate=0.1, layer="OUTPUT"):
# 更新权传
if layer == "OUTPUT":
self.deltas_item = (target - self.output) * logistic_derivative(self.output)
elif layer == "HIDDEN":
self.deltas_item = back_weight * logistic_derivative(self.output)
weight_add = self.input * self.deltas_item * learning_rate + 0.9 * self.last_weight_add
self.weights += weight_add
self.last_weight_add = weight_add
二、网络层主要功能:管理一个网络层的代码,分为隐藏层和输出层。 (输入层可直接用输入数据,不简单实现。)网络层主要管理自己层的神经元,所以封装的结果与神经元的接口一样。对向实现自己的功能。同时为了方便处理,添加了他下一层的引用。
class NetLayer:
'''
网络层封装
管理当前网络层的神经元列表
'''
def __init__(self, len_node, in_count):
'''
:param len_node: 当前层的神经元数
:param in_count: 当前层的输入数
'''
# 当前层的神经元列表
self.neurons = [Neuron(in_count) for _ in range(len_node)]
# 记录下一层的引用,方便递归操作
self.next_layer = None
def calc_output(self, x):
output = np.array([node.calc_output(x) for node in self.neurons])
if self.next_layer is not None:
return self.next_layer.calc_output(output)
return output
def get_back_weight(self):
return sum([node.get_back_weight() for node in self.neurons])
def update_weight(self, learning_rate, target):
'''
更新当前网络层及之后层次的权重
使用了递归来操作,所以要求外面调用时必须从网络层的第一层(输入层的下一层)来调用
:param learning_rate: 学习率
:param target: 输出值
'''
layer = "OUTPUT"
back_weight = np.zeros(len(self.neurons))
if self.next_layer is not None:
back_weight = self.next_layer.update_weight(learning_rate, target)
layer = "HIDDEN"
for i, node in enumerate(self.neurons):
三、NeuralNetWork类:
管理整个网络,对外提供训练接口及预测接口。构建网络参数为一列表, 第一个元素代码输入参数个数, 最后一个代码输出神经元个数,中间的为各个隐藏层中的神经元的个数。由于各层间代码链式存储, 所以layers[0]操作就代码了整个网络。
class NeuralNetWork:
def __init__(self, layers):
self.layers = []
self.construct_network(layers)
pass
def construct_network(self, layers):
last_layer = None
for i, layer in enumerate(layers):
if i == 0:
continue
cur_layer = NetLayer(layer, layers[i-1])
self.layers.append(cur_layer)
if last_layer is not None:
last_layer.next_layer = cur_layer
last_layer = cur_layer
def fit(self, x_train, y_train, learning_rate=0.1, epochs=100000, shuffle=False):
'''
训练网络, 默认按顺序来训练
方法 1:按训练数据顺序来训练
方法 2: 随机选择测试
:param x_train: 输入数据
:param y_train: 输出数据
:param learning_rate: 学习率
:param epochs:权重更新次数
:param shuffle:随机取数据训练
'''
indices = np.arange(len(x_train))
for _ in range(epochs):
if shuffle:
np.random.shuffle(indices)
for i in indices:
self.layers[0].calc_output(x_train[i])
self.layers[0].update_weight(learning_rate, y_train[i])
pass
def predict(self, x):
return self.layers[0].calc_output(x)