mirror of
https://github.com/apachecn/ailearning.git
synced 2026-04-14 02:10:01 +08:00
870 lines
27 KiB
Python
870 lines
27 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: UTF-8 -*-
|
||
|
||
import random
|
||
from functools import reduce
|
||
from numpy import *
|
||
|
||
# sigmoid 函数
|
||
def sigmoid(inX):
|
||
'''
|
||
Desc:
|
||
sigmoid 函数实现
|
||
Args:
|
||
inX --- 输入向量
|
||
Returns:
|
||
对输入向量作用 sigmoid 函数之后得到的输出
|
||
'''
|
||
return 1.0 / (1 + exp(-inX))
|
||
|
||
|
||
# 定义神经网络的节点类
|
||
class Node(object):
|
||
'''
|
||
Desc:
|
||
神经网络的节点类
|
||
'''
|
||
def __init__(self, layer_index, node_index):
|
||
'''
|
||
Desc:
|
||
初始化一个节点
|
||
Args:
|
||
layer_index --- 层的索引,也就是表示第几层
|
||
node_index --- 节点的索引,也就是表示节点的索引
|
||
Returns:
|
||
None
|
||
'''
|
||
# 设置节点所在的层的位置
|
||
self.layer_index = layer_index
|
||
# 设置层中的节点的索引
|
||
self.node_index = node_index
|
||
# 设置此节点的下游节点,也就是这个节点与下一层的哪个节点相连
|
||
self.downstream = []
|
||
# 设置此节点的上游节点,也就是哪几个节点的下游节点与此节点相连
|
||
self.upstream = []
|
||
# 此节点的输出
|
||
self.output = 0
|
||
# 此节点真实值与计算值之间的差值
|
||
self.delta = 0
|
||
|
||
def set_output(self, output):
|
||
'''
|
||
Desc:
|
||
设置节点的 output
|
||
Args:
|
||
output --- 节点的 output
|
||
Returns:
|
||
None
|
||
'''
|
||
self.output = output
|
||
|
||
def append_downstream_connection(self, conn):
|
||
'''
|
||
Desc:
|
||
添加此节点的下游节点的连接
|
||
Args:
|
||
conn --- 当前节点的下游节点的连接的 list
|
||
Returns:
|
||
None
|
||
'''
|
||
# 使用 list 的 append 方法来将 conn 中的节点添加到 downstream 中
|
||
self.downstream.append(conn)
|
||
|
||
def append_upstream_connection(self, conn):
|
||
'''
|
||
Desc:
|
||
添加此节点的上游节点的连接
|
||
Args:
|
||
conn ---- 当前节点的上游节点的连接的 list
|
||
Returns:
|
||
None
|
||
'''
|
||
# 使用 list 的 append 方法来将 conn 中的节点添加到 upstream 中
|
||
self.upstream.append(conn)
|
||
|
||
def calc_output(self):
|
||
'''
|
||
Desc:
|
||
计算节点的输出,依据 output = sigmoid(wTx)
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 使用 reduce() 函数对其中的因素求和
|
||
output = reduce(lambda ret, conn: ret + conn.upstream_node.output * conn.weight, self.upstream, 0)
|
||
# 对上游节点的 output 乘 weights 之后求和得到的结果应用 sigmoid 函数,得到当前节点的 output
|
||
self.output = sigmoid(output)
|
||
|
||
def calc_hidden_layer_delta(self):
|
||
'''
|
||
Desc:
|
||
计算隐藏层的节点的 delta
|
||
Args:
|
||
output --- 节点的 output
|
||
Returns:
|
||
None
|
||
'''
|
||
# 根据 https://www.zybuluo.com/hanbingtao/note/476663 的 式4 计算隐藏层的delta
|
||
downstream_delta = reduce(lambda ret, conn: ret + conn.downstream_node.delta * conn.weight, self.downstream, 0.0)
|
||
# 计算此节点的 delta
|
||
self.delta = self.output * (1 - self.output) * downstream_delta
|
||
|
||
def calc_output_layer_delta(self, label):
|
||
'''
|
||
Desc:
|
||
计算输出层的 delta
|
||
Args:
|
||
label --- 输入向量对应的真实标签,不是计算得到的结果
|
||
Returns:
|
||
None
|
||
'''
|
||
# 就是那输出层的 delta
|
||
self.delta = self.output * (1 - self.output) * (label - self.output)
|
||
|
||
def __str__(self):
|
||
'''
|
||
Desc:
|
||
将节点的信息打印出来
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 打印格式:第几层 - 第几个节点,output 是多少,delta 是多少
|
||
node_str = '%u-%u: output: %f delta: %f' % (self.layer_index, self.node_index, self.output, self.delta)
|
||
# 下游节点
|
||
downstream_str = reduce(lambda ret, conn: ret + '\n\t' + str(conn), self.downstream, '')
|
||
# 上游节点
|
||
upstream_str = reduce(lambda ret, conn: ret + '\n\t' + str(conn), self.upstream, '')
|
||
# 将本节点 + 下游节点 + 上游节点 的信息打印出来
|
||
return node_str + '\n\tdownstream:' + downstream_str + '\n\tupstream:' + upstream_str
|
||
|
||
|
||
# ConstNode 对象,为了实现一个输出恒为 1 的节点(计算偏置项 wb 时需要)
|
||
class ConstNode(object):
|
||
'''
|
||
Desc:
|
||
常数项对象,即相当于计算的时候的偏置项
|
||
'''
|
||
def __init__(self, layer_index, node_index):
|
||
'''
|
||
Desc:
|
||
初始化节点对象
|
||
Args:
|
||
layer_index --- 节点所属的层的编号
|
||
node_index --- 节点的编号
|
||
Returns:
|
||
None
|
||
'''
|
||
self.layer_index = layer_index
|
||
self.node_index = node_index
|
||
self.downstream = []
|
||
self.output = 1
|
||
|
||
|
||
def append_downstream_connection(self, conn):
|
||
'''
|
||
Desc:
|
||
添加一个到下游节点的连接
|
||
Args:
|
||
conn --- 到下游节点的连接
|
||
Returns:
|
||
None
|
||
'''
|
||
# 使用 list 的 append 方法将包含下游节点的 conn 添加到 downstream 中
|
||
self.downstream.append(conn)
|
||
|
||
|
||
def calc_hidden_layer_delta(self):
|
||
'''
|
||
Desc:
|
||
计算隐藏层的 delta
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 使用我们的 公式 4 来计算下游节点的 delta,求和
|
||
downstream_delta = reduce(lambda ret, conn: ret + conn.downstream_node.delta * conn.weight, self.downstream, 0.0)
|
||
# 计算隐藏层的本节点的 delta
|
||
self.delta = self.output * (1 - self.output) * downstream_delta
|
||
|
||
|
||
def __str__(self):
|
||
'''
|
||
Desc:
|
||
将节点信息打印出来
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 将节点的信息打印出来
|
||
# 格式 第几层-第几个节点的 output
|
||
node_str = '%u-%u: output: 1' % (self.layer_index, self.node_index)
|
||
# 此节点的下游节点的信息
|
||
downstream_str = reduce(lambda ret, conn: ret + '\n\t' + str(conn), self.downstream, '')
|
||
# 将此节点与下游节点的信息组合,一起打印出来
|
||
return node_str + '\n\tdownstream:' + downstream_str
|
||
|
||
|
||
# 神经网络的层对象,负责初始化一层。此外,作为 Node 的集合对象,提供对 Node 集合的操作
|
||
class Layer(object):
|
||
'''
|
||
Desc:
|
||
神经网络的 Layer 类
|
||
'''
|
||
|
||
def __init__(self, layer_index, node_count):
|
||
'''
|
||
Desc:
|
||
神经网络的层对象的初始化
|
||
Args:
|
||
layer_index --- 层的索引
|
||
node_count --- 节点的个数
|
||
Returns:
|
||
None
|
||
'''
|
||
# 设置 层的索引
|
||
self.layer_index = layer_index
|
||
# 设置层中的节点的 list
|
||
self.nodes = []
|
||
# 将 Node 节点添加到 nodes 中
|
||
for i in range(node_count):
|
||
self.nodes.append(Node(layer_index, i))
|
||
# 将 ConstNode 节点也添加到 nodes 中
|
||
self.nodes.append(ConstNode(layer_index, node_count))
|
||
|
||
def set_output(self, data):
|
||
'''
|
||
Desc:
|
||
设置层的输出,当层是输入层时会用到
|
||
Args:
|
||
data --- 输出的值的 list
|
||
Returns:
|
||
None
|
||
'''
|
||
# 设置输入层中各个节点的 output
|
||
for i in range(len(data)):
|
||
self.nodes[i].set_output(data[i])
|
||
|
||
def calc_output(self):
|
||
'''
|
||
Desc:
|
||
计算层的输出向量
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 遍历本层的所有节点(除去最后一个节点,因为它是恒为常数的偏置项b)
|
||
# 调用节点的 calc_output 方法来计算输出向量
|
||
for node in self.nodes[:-1]:
|
||
node.calc_output()
|
||
|
||
def dump(self):
|
||
'''
|
||
Desc:
|
||
将层信息打印出来
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 遍历层的所有的节点 nodes,将节点信息打印出来
|
||
for node in self.nodes:
|
||
print(node)
|
||
|
||
|
||
# Connection 对象类,主要负责记录连接的权重,以及这个连接所关联的上下游的节点
|
||
class Connection(object):
|
||
'''
|
||
Desc:
|
||
Connection 对象,记录连接权重和连接所关联的上下游节点,注意,这里的 connection 没有 s ,不是复数
|
||
'''
|
||
def __init__(self, upstream_node, downstream_node):
|
||
'''
|
||
Desc:
|
||
初始化 Connection 对象
|
||
Args:
|
||
upstream_node --- 上游节点
|
||
downstream_node --- 下游节点
|
||
Returns:
|
||
None
|
||
'''
|
||
# 设置上游节点
|
||
self.upstream_node = upstream_node
|
||
# 设置下游节点
|
||
self.downstream_node = downstream_node
|
||
# 设置权重,这里设置的权重是 -0.1 到 0.1 之间的任何数
|
||
self.weight = random.uniform(-0.1, 0.1)
|
||
# 设置梯度 为 0.0
|
||
self.gradient = 0.0
|
||
|
||
def calc_gradient(self):
|
||
'''
|
||
Desc:
|
||
计算梯度
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 下游节点的 delta * 上游节点的 output 计算得到梯度
|
||
self.gradient = self.downstream_node.delta * self.upstream_node.output
|
||
|
||
def update_weight(self, rate):
|
||
'''
|
||
Desc:
|
||
根据梯度下降算法更新权重
|
||
Args:
|
||
rate --- 学习率 / 或者成为步长
|
||
Returns:
|
||
None
|
||
'''
|
||
# 调用计算梯度的函数来将梯度计算出来
|
||
self.calc_gradient()
|
||
# 使用梯度下降算法来更新权重
|
||
self.weight += rate * self.gradient
|
||
|
||
def get_gradient(self):
|
||
'''
|
||
Desc:
|
||
获取当前的梯度
|
||
Args:
|
||
None
|
||
Returns:
|
||
当前的梯度 gradient
|
||
'''
|
||
return self.gradient
|
||
|
||
def __str__(self):
|
||
'''
|
||
Desc:
|
||
将连接信息打印出来
|
||
Args:
|
||
None
|
||
Returns:
|
||
连接信息进行返回
|
||
'''
|
||
# 格式为:上游节点的层的索引+上游节点的节点索引 ---> 下游节点的层的索引+下游节点的节点索引,最后一个数是权重
|
||
return '(%u-%u) -> (%u-%u) = %f' % (
|
||
self.upstream_node.layer_index,
|
||
self.upstream_node.node_index,
|
||
self.downstream_node.layer_index,
|
||
self.downstream_node.node_index,
|
||
self.weight)
|
||
|
||
|
||
|
||
# Connections 对象,提供 Connection 集合操作。
|
||
class Connections(object):
|
||
'''
|
||
Desc:
|
||
Connections 对象,提供 Connection 集合的操作,看清楚后面有没有 s ,不要看错
|
||
'''
|
||
def __init__(self):
|
||
'''
|
||
Desc:
|
||
初始化 Connections 对象
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 初始化一个列表 list
|
||
self.connections = []
|
||
|
||
def add_connection(self, connection):
|
||
'''
|
||
Desc:
|
||
将 connection 中的节点信息 append 到 connections 中
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
self.connections.append(connection)
|
||
|
||
def dump(self):
|
||
'''
|
||
Desc:
|
||
将 Connections 的节点信息打印出来
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
for conn in self.connections:
|
||
print(conn)
|
||
|
||
|
||
# Network 对象,提供相应 API
|
||
class Network(object):
|
||
'''
|
||
Desc:
|
||
Network 类
|
||
'''
|
||
def __init__(self, layers):
|
||
'''
|
||
Desc:
|
||
初始化一个全连接神经网络
|
||
Args:
|
||
layers --- 二维数组,描述神经网络的每层节点数
|
||
Returns:
|
||
None
|
||
'''
|
||
# 初始化 connections,使用的是 Connections 对象
|
||
self.connections = Connections()
|
||
# 初始化 layers
|
||
self.layers = []
|
||
# 我们的神经网络的层数
|
||
layer_count = len(layers)
|
||
# 节点数
|
||
node_count = 0
|
||
# 遍历所有的层,将每层信息添加到 layers 中去
|
||
for i in range(layer_count):
|
||
self.layers.append(Layer(i, layers[i]))
|
||
# 遍历除去输出层之外的所有层,将连接信息添加到 connections 对象中
|
||
for layer in range(layer_count - 1):
|
||
connections = [Connection(upstream_node, downstream_node) for upstream_node in self.layers[layer].nodes for downstream_node in self.layers[layer + 1].nodes[:-1]]
|
||
# 遍历 connections,将 conn 添加到 connections 中
|
||
for conn in connections:
|
||
self.connections.add_connection(conn)
|
||
# 为下游节点添加上游节点为 conn
|
||
conn.downstream_node.append_upstream_connection(conn)
|
||
# 为上游节点添加下游节点为 conn
|
||
conn.upstream_node.append_downstream_connection(conn)
|
||
|
||
|
||
def train(self, labels, data_set, rate, epoch):
|
||
'''
|
||
Desc:
|
||
训练神经网络
|
||
Args:
|
||
labels --- 数组,训练样本标签,每个元素是一个样本的标签
|
||
data_set --- 二维数组,训练样本的特征数据。每行数据是一个样本的特征
|
||
rate --- 学习率
|
||
epoch --- 迭代次数
|
||
Returns:
|
||
None
|
||
'''
|
||
# 循环迭代 epoch 次
|
||
for i in range(epoch):
|
||
# 遍历每个训练样本
|
||
for d in range(len(data_set)):
|
||
# 使用此样本进行训练(一条样本进行训练)
|
||
self.train_one_sample(labels[d], data_set[d], rate)
|
||
# print 'sample %d training finished' % d
|
||
|
||
def train_one_sample(self, label, sample, rate):
|
||
'''
|
||
Desc:
|
||
内部函数,使用一个样本对网络进行训练
|
||
Args:
|
||
label --- 样本的标签
|
||
sample --- 样本的特征
|
||
rate --- 学习率
|
||
Returns:
|
||
None
|
||
'''
|
||
# 调用 Network 的 predict 方法,对这个样本进行预测
|
||
self.predict(sample)
|
||
# 计算根据此样本得到的结果的 delta
|
||
self.calc_delta(label)
|
||
# 更新权重
|
||
self.update_weight(rate)
|
||
|
||
def calc_delta(self, label):
|
||
'''
|
||
Desc:
|
||
计算每个节点的 delta
|
||
Args:
|
||
label --- 样本的真实值,也就是样本的标签
|
||
Returns:
|
||
None
|
||
'''
|
||
# 获取输出层的所有节点
|
||
output_nodes = self.layers[-1].nodes
|
||
# 遍历所有的 label
|
||
for i in range(len(label)):
|
||
# 计算输出层节点的 delta
|
||
output_nodes[i].calc_output_layer_delta(label[i])
|
||
# 这个用法就是切片的用法, [-2::-1] 就是将 layers 这个数组倒过来,从没倒过来的时候的倒数第二个元素开始,到翻转过来的倒数第一个数,比如这样:aaa = [1,2,3,4,5,6,7,8,9],bbb = aaa[-2::-1] ==> bbb = [8, 7, 6, 5, 4, 3, 2, 1]
|
||
# 实际上就是除掉输出层之外的所有层按照相反的顺序进行遍历
|
||
for layer in self.layers[-2::-1]:
|
||
# 遍历每层的所有节点
|
||
for node in layer.nodes:
|
||
# 计算隐藏层的 delta
|
||
node.calc_hidden_layer_delta()
|
||
|
||
def update_weight(self, rate):
|
||
'''
|
||
Desc:
|
||
更新每个连接的权重
|
||
Args:
|
||
rate --- 学习率
|
||
Returns:
|
||
None
|
||
'''
|
||
# 按照正常顺序遍历除了输出层的层
|
||
for layer in self.layers[:-1]:
|
||
# 遍历每层的所有节点
|
||
for node in layer.nodes:
|
||
# 遍历节点的下游节点
|
||
for conn in node.downstream:
|
||
# 根据下游节点来更新连接的权重
|
||
conn.update_weight(rate)
|
||
|
||
def calc_gradient(self):
|
||
'''
|
||
Desc:
|
||
计算每个连接的梯度
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 按照正常顺序遍历除了输出层之外的层
|
||
for layer in self.layers[:-1]:
|
||
# 遍历层中的所有节点
|
||
for node in layer.nodes:
|
||
# 遍历节点的下游节点
|
||
for conn in node.downstream:
|
||
# 计算梯度
|
||
conn.calc_gradient()
|
||
|
||
def get_gradient(self, label, sample):
|
||
'''
|
||
Desc:
|
||
获得网络在一个样本下,每个连接上的梯度
|
||
Args:
|
||
label --- 样本标签
|
||
sample --- 样本特征
|
||
Returns:
|
||
None
|
||
'''
|
||
# 调用 predict() 方法,利用样本的特征数据对样本进行预测
|
||
self.predict(sample)
|
||
# 计算 delta
|
||
self.calc_delta(label)
|
||
# 计算梯度
|
||
self.calc_gradient()
|
||
|
||
def predict(self, sample):
|
||
'''
|
||
Desc:
|
||
根据输入的样本预测输出值
|
||
Args:
|
||
sample --- 数组,样本的特征,也就是网络的输入向量
|
||
Returns:
|
||
使用我们的感知器规则计算网络的输出
|
||
'''
|
||
# 首先为输入层设置输出值output为样本的输入向量,即不发生任何变化
|
||
self.layers[0].set_output(sample)
|
||
# 遍历除去输入层开始到最后一层
|
||
for i in range(1, len(self.layers)):
|
||
# 计算 output
|
||
self.layers[i].calc_output()
|
||
# 将计算得到的输出,也就是我们的预测值返回
|
||
return list(map(lambda node: node.output, self.layers[-1].nodes[:-1]))
|
||
|
||
def dump(self):
|
||
'''
|
||
Desc:
|
||
打印出我们的网络信息
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 遍历所有的 layers
|
||
for layer in self.layers:
|
||
# 将所有的层的信息打印出来
|
||
layer.dump()
|
||
|
||
|
||
# # ------------------------- 至此,基本上我们把 我们的神经网络实现完成,下面还会介绍一下对应的梯度检查相关的算法,现在我们首先回顾一下我们上面写道的类及他们的作用 ------------------------
|
||
'''
|
||
1、节点类的实现 Node :负责记录和维护节点自身信息以及这个节点相关的上下游连接,实现输出值和误差项的计算。如下:
|
||
layer_index --- 节点所属的层的编号
|
||
node_index --- 节点的编号
|
||
downstream --- 下游节点
|
||
upstream ---- 上游节点
|
||
output ---- 节点的输出值
|
||
delta ------ 节点的误差项
|
||
|
||
2、ConstNode 类,偏置项类的实现:实现一个输出恒为 1 的节点(计算偏置项的时候会用到),如下:
|
||
layer_index --- 节点所属层的编号
|
||
node_index ---- 节点的编号
|
||
downstream ---- 下游节点
|
||
没有记录上游节点,因为一个偏置项的输出与上游节点的输出无关
|
||
output ----- 偏置项的输出
|
||
|
||
3、layer 类,负责初始化一层。作为的是 Node 节点的集合对象,提供对 Node 集合的操作。也就是说,layer 包含的是 Node 的集合。
|
||
layer_index ---- 层的编号
|
||
node_count ----- 层所包含的节点的个数
|
||
def set_ouput() -- 设置层的输出,当层是输入层时会用到
|
||
def calc_output -- 计算层的输出向量,调用的 Node 类的 计算输出 方法
|
||
|
||
4、Connection 类:负责记录连接的权重,以及这个连接所关联的上下游节点,如下:
|
||
upstream_node --- 连接的上游节点
|
||
downstream_node -- 连接的下游节点
|
||
weight -------- random.uniform(-0.1, 0.1) 初始化为一个很小的随机数
|
||
gradient -------- 0.0 梯度,初始化为 0.0
|
||
def calc_gradient() --- 计算梯度,使用的是下游节点的 delta 与上游节点的 output 相乘计算得到
|
||
def get_gradient() ---- 获取当前的梯度
|
||
def update_weight() --- 根据梯度下降算法更新权重
|
||
|
||
5、Connections 类:提供对 Connection 集合操作,如下:
|
||
def add_connection() --- 添加一个 connection
|
||
|
||
6、Network 类:提供相应的 API,如下:
|
||
connections --- Connections 对象
|
||
layers -------- 神经网络的层
|
||
layer_count --- 神经网络的层数
|
||
node_count --- 节点个数
|
||
def train() --- 训练神经网络
|
||
def train_one_sample() --- 用一个样本训练网络
|
||
def calc_delta() --- 计算误差项
|
||
def update_weight() --- 更新每个连接权重
|
||
def calc_gradient() --- 计算每个连接的梯度
|
||
def get_gradient() --- 获得网络在一个样本下,每个连接上的梯度
|
||
def predict() --- 根据输入的样本预测输出值
|
||
'''
|
||
|
||
# #--------------------------------------回顾完成了,有些问题可能还是没有弄懂,没事,我们接着看下面---------------------------------------------
|
||
|
||
class Normalizer(object):
|
||
'''
|
||
Desc:
|
||
归一化工具类
|
||
Args:
|
||
object --- 对象
|
||
Returns:
|
||
None
|
||
'''
|
||
def __init__(self):
|
||
'''
|
||
Desc:
|
||
初始化
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 初始化 16 进制的数,用来判断位的,分别是
|
||
# 0x1 ---- 00000001
|
||
# 0x2 ---- 00000010
|
||
# 0x4 ---- 00000100
|
||
# 0x8 ---- 00001000
|
||
# 0x10 --- 00010000
|
||
# 0x20 --- 00100000
|
||
# 0x40 --- 01000000
|
||
# 0x80 --- 10000000
|
||
self.mask = [0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80]
|
||
|
||
def norm(self, number):
|
||
'''
|
||
Desc:
|
||
对 number 进行规范化
|
||
Args:
|
||
number --- 要规范化的数据
|
||
Returns:
|
||
规范化之后的数据
|
||
'''
|
||
# 此方法就相当于判断一个 8 位的向量,哪一位上有数字,如果有就将这个数设置为 0.9 ,否则,设置为 0.1,通俗比较来说,就是我们这里用 0.9 表示 1,用 0.1 表示 0
|
||
return list(map(lambda m: 0.9 if number & m else 0.1, self.mask))
|
||
|
||
def denorm(self, vec):
|
||
'''
|
||
Desc:
|
||
对我们得到的向量进行反规范化
|
||
Args:
|
||
vec --- 得到的向量
|
||
Returns:
|
||
最终的预测结果
|
||
'''
|
||
# 进行二分类,大于 0.5 就设置为 1,小于 0.5 就设置为 0
|
||
binary = list(map(lambda i: 1 if i > 0.5 else 0, vec))
|
||
# 遍历 mask
|
||
for i in range(len(self.mask)):
|
||
binary[i] = binary[i] * self.mask[i]
|
||
# 将结果相加得到最终的预测结果
|
||
return reduce(lambda x,y: x + y, binary)
|
||
|
||
|
||
def mean_square_error(vec1, vec2):
|
||
'''
|
||
Desc:
|
||
计算平均平方误差
|
||
Args:
|
||
vec1 --- 第一个数
|
||
vec2 --- 第二个数
|
||
Returns:
|
||
返回 1/2 * (x-y)^2 计算得到的值
|
||
'''
|
||
return 0.5 * reduce(lambda a, b: a + b, map(lambda v: (v[0] - v[1]) * (v[0] - v[1]), zip(vec1, vec2)))
|
||
|
||
|
||
|
||
def gradient_check(network, sample_feature, sample_label):
|
||
'''
|
||
Desc:
|
||
梯度检查
|
||
Args:
|
||
network --- 神经网络对象
|
||
sample_feature --- 样本的特征
|
||
sample_label --- 样本的标签
|
||
Returns:
|
||
None
|
||
'''
|
||
# 计算网络误差
|
||
network_error = lambda vec1, vec2: 0.5 * reduce(lambda a, b: a + b, map(lambda v: (v[0] - v[1]) * (v[0] - v[1]), zip(vec1, vec2)))
|
||
|
||
# 获取网络在当前样本下每个连接的梯度
|
||
network.get_gradient(sample_feature, sample_label)
|
||
|
||
# 对每个权重做梯度检查
|
||
for conn in network.connections.connections:
|
||
# 获取指定连接的梯度
|
||
actual_gradient = conn.get_gradient()
|
||
|
||
# 增加一个很小的值,计算网络的误差
|
||
epsilon = 0.0001
|
||
conn.weight += epsilon
|
||
error1 = network_error(network.predict(sample_feature), sample_label)
|
||
|
||
# 减去一个很小的值,计算网络的误差
|
||
conn.weight -= 2 * epsilon # 刚才加过了一次,因此这里需要减去2倍
|
||
error2 = network_error(network.predict(sample_feature), sample_label)
|
||
|
||
# 根据式6计算期望的梯度值
|
||
expected_gradient = (error2 - error1) / (2 * epsilon)
|
||
|
||
# 打印
|
||
print('expected gradient: \t%f\nactual gradient: \t%f' % (expected_gradient, actual_gradient))
|
||
|
||
|
||
def train_data_set():
|
||
'''
|
||
Desc:
|
||
获取训练数据集
|
||
Args:
|
||
None
|
||
Returns:
|
||
labels --- 训练数据集每条数据对应的标签
|
||
'''
|
||
# 调用 Normalizer() 类
|
||
normalizer = Normalizer()
|
||
# 初始化一个 list,用来存储后面的数据
|
||
data_set = []
|
||
labels = []
|
||
# 0 到 256 ,其中以 8 为步长
|
||
for i in range(0, 256, 8):
|
||
# 调用 normalizer 对象的 norm 方法
|
||
n = normalizer.norm(int(random.uniform(0, 256)))
|
||
# 在 data_set 中 append n
|
||
data_set.append(n)
|
||
# 在 labels 中 append n
|
||
labels.append(n)
|
||
# 将它们返回
|
||
return labels, data_set
|
||
|
||
|
||
def train(network):
|
||
'''
|
||
Desc:
|
||
使用我们的神经网络进行训练
|
||
Args:
|
||
network --- 神经网络对象
|
||
Returns:
|
||
None
|
||
'''
|
||
# 获取训练数据集
|
||
labels, data_set = train_data_set()
|
||
labels = list(labels)
|
||
data_set = list(labels)
|
||
# 调用 network 中的 train方法来训练我们的神经网络
|
||
network.train(labels, data_set, 0.3, 50)
|
||
|
||
|
||
def test(net,data):
|
||
#此函数不明觉厉,但是传参就有问题,如果跑不通就把这段代码注释掉吧。。。
|
||
|
||
'''
|
||
Desc:
|
||
对我们的全连接神经网络进行测试
|
||
Args:
|
||
network --- 神经网络对象
|
||
data ------ 测试数据集
|
||
Returns:
|
||
None
|
||
'''
|
||
# 调用 Normalizer() 类
|
||
|
||
normalizer = Normalizer()
|
||
# 调用 norm 方法,对数据进行规范化
|
||
norm_data = normalizer.norm(data)
|
||
norm_data = list(norm_data)
|
||
# 对测试数据进行预测
|
||
predict_data = net.predict(norm_data)
|
||
# 将结果打印出来
|
||
print('\ttestdata(%u)\tpredict(%u)' % (data, normalizer.denorm(predict_data)))
|
||
|
||
|
||
def correct_ratio(network):
|
||
'''
|
||
Desc:
|
||
计算我们的神经网络的正确率
|
||
Args:
|
||
network --- 神经网络对象
|
||
Returns:
|
||
None
|
||
'''
|
||
normalizer = Normalizer()
|
||
correct = 0.0
|
||
for i in range(256):
|
||
if normalizer.denorm(network.predict(normalizer.norm(i))) == i:
|
||
correct += 1.0
|
||
print('correct_ratio: %.2f%%' % (correct / 256 * 100))
|
||
|
||
|
||
def gradient_check_test():
|
||
'''
|
||
Desc:
|
||
梯度检查测试
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 创建一个有 3 层的网络,每层有 2 个节点
|
||
net = Network([2, 2, 2])
|
||
# 样本的特征
|
||
sample_feature = [0.9, 0.1]
|
||
# 样本对应的标签
|
||
sample_label = [0.9, 0.1]
|
||
# 使用梯度检查来查看是否正确
|
||
gradient_check(net, sample_feature, sample_label)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
'''
|
||
Desc:
|
||
主函数
|
||
Args:
|
||
None
|
||
Returns:
|
||
None
|
||
'''
|
||
# 初始化一个神经网络,输入层 8 个节点,隐藏层 3 个节点,输出层 8 个节点
|
||
net = Network([8, 3, 8])
|
||
# 训练我们的神经网络
|
||
train(net)
|
||
# 将我们的神经网络的信息打印出来
|
||
net.dump()
|
||
# 打印出神经网络的正确率
|
||
correct_ratio(net)
|