前言:
本篇文章 主要是朱某两年软件学习关于深度学习、机器学习。以及所谓人工智能(靠人工的智能)第一阶段学习的总结。篇幅较长 比较详细。介于数据集的问题。选择mnist数据集(MINST数据库是由Yann提供的手写数字数据库文件)。基于Python语言。不调用Keras、tensorflow、sk-learn等库。使用numpy和部分数学函数库做一些复杂的数学运算。纯手肛一个手写图片识别的系统出来。
本文适合像入门或刚入门AI/深度学习/机器学习不久的人看(因为我也是刚刚入门)。
技术要求:Python语言基础 矩阵基础概念 矩阵四则运算 函数求导 求偏导 求极值 泰勒展开 拉格朗日中值定理 函数的极限 数字特征 多维随机变量及其分布(尤其是边缘分布 ) 电信号在神经元上的传导等
不要看到技术要求就怕 大致懂得这些东西就行 在学习中运用 在运用中巩固 在运用中提高。
一:深度学习简介
为了理解电脑的学习过程,我在这举一个很简单的例子来阐述:
我们现在的需求是根据人民币和美元的对应关系 求中美汇率
根据观察 我们猜测中美货币之间有美元=k*人民币这样的关系。其中K未知。我们将这种关系告诉电脑:
电脑的处理过程会是这样的:
总结上述的过程,给机器一个参数,然后根据这个大量的数据计算所得的误差。不断的调整这个参数。最后得到近乎正确的值,将这个参数应用到实际情况中去。
为此。总结一个好的机器学习算法需要一下要素:
简而言之,让机器去学习与我党的实事求是路线有点像:"实事"就是客观存在的真实事物,”是“就是事物的内在联系。”求“就是让我们去研究。实事求是就是在客观存在的基础上,研究他的内部规律。
当然,我们现实中很多现象并不是Y=KX这样的线性关系,很多事物的决定规律都有多元维度,因此计算机科学家DemisHassabis根据脑神经元的特点,提出了神经网络算法。下面进行神经网络算法的简单介绍
神经信号是一种电信号,其传导速度极快,信号在神经上传递时表现为电位变化,但在胞体间传递时却有不同的介质。产生不同的介质是因为,电冲动打开了电压门通道,使得末端中的一些化学物质释放,被相邻神经元的受体结合,打开这个神经元的配体门控通道,有转变为电冲动。
最左边的部分"dendrite" 叫做突触 它用来接收外界输入的电信号。中间部分axon叫轴突。它把突触接收的信号进行整合处理。右边部分terminals叫做终端输出,它把轴突整合后的信号切分成多部分。分别传送给其他神经元。下图是脑神经学家从鸽子脑子获取的神经元图像。
人的大脑大概有一千亿个神经元组成一个庞大的计算网络。苍蝇大脑只有十万个神经元。尽管如此,苍蝇就能控制控制飞行,寻找食物 识别和躲避危险,这些看似简单的懂做操控就连现在最强大的计算机都无法实现。大脑的运行机制目前人类还没有完全搞懂(所以说人工智能是靠人工的智能,如果有一天真正搞懂了那才叫牛)。目前人类已知的就是生物大脑运算存在“激活性和模糊性”,电子计算机可以模拟激活性。但无法模拟模糊性。
激活性就是:神经传导存在阈电位。若是传导的电信号强度不够大,那么神经元就不会做出任何反应,如果电信号强度大于某个界限。神经元就会做出反应。就好像你把手伸入水中,水温不高,你不会觉得烫。水温到了一定温度,你才会觉得烫。
在计算机中,我们用sigmod函数模拟这种情况。sigmod函数图像&函数表达式如图所示:
在生物学中,一个神经元会同时接收多个电信号,把这些电信号统一起来,用根据一定的处理后再输出新的电信号。计算机也是如此,同时接收多个电信号后,用激活函数处理后再输出新的电信号,如下图:
神经元不是各自为战,而是练成一整个网络。并对电信号的处理形成一种链式反应。前一个神经元接收输入信号,处理后把会把输出信号分别传送给下一层的多个神经元,在神经网络算法,我们也去模拟这样的特性。在算法设计中,我们构造如下的数据结构:
上面总共有3层三个节点。每个节点上都有一个权值,第一层节点接收输入,在进行矩阵运算后,把输出结果分别提交给下一层的三个节点,如此类推直到最后一层。根据输入的结果和实际结果的误差,对每个节点的权值做一个调整。
举一个两层的神经网络对算法过程做一个简单介绍:
计算的第一步是网络第一层的两个节点分别把信号传送给第二层的两个节点。为了求出第二层节点接收到的数据。我们可以用矩阵运算进行实现:
用W表示上层节点相对本层节点的关系。其中W为:[1节点每边的权值,2节点每边的权值]。其中每个结点每边的权值用列的表示。则第二层接收到的数据X为
上面主要介绍了神经网络的层层关系和层层运算过程,上文中我们提到了机器的学习方法是根据计算的输出值和实际真实值之间的误差进行一个权值的调整,应用在神经网络中,步骤就是这样的:先给神经网络的每个节点初始化权值--代入数据进行计算--根据输出的结果与实际结果的误差,根据误差对每个节点的权值进行调整。现在我们看看误差的几种计算方式:
下面聊聊如何根据产生的误差进行权值调整:
拿刚刚的例子来说:
根据上面我们所得出的神经网络逐层公式,我们可以自然而然的想到。反向分配给每个节点的误差公式为:
每个节点的收到所分配的误差时,就要根据所分配到的误差值修改相应的权重了。
到底如何根据所分配到的误差修改权重呢?现在先让我们看看从下一层神经元回退到上上一层的运算过程吧
因为这个超级复杂计算的原因 在过去写一个车牌识别的系统需要200万代码。这套系统可以卖到300万美金。
但是所幸70年代时数学家们发现了一种在非线性函数构成的复杂曲面上求极值的方法--梯度下降法,这种方法有点像一个人在一片漆黑的山顶上只拿一个手电筒下山的感觉。想象一下,当你面对这样的情况时。你怎么下山呢?方法很简单。你环顾四周,看哪一个方向是往下走的,你就朝那个方向一直走下去。不能继续走时你就继续环顾四周。看哪个方向继续往下走。知道下山
从数学的角度看待这个问题,就是。我们不管一个函数形成的曲面有多复杂,我们只要在定点上做这条曲线或曲面的切线,如果切线斜率为负,那么朝着切线方向增大自变量值,因变量就会逐渐变小。反复执行此步骤,我们就可以得到越来越小的函数值。神经网络的信号传递过程,可以看作一个非常复杂的多变量。非线性函数,每个变量对应一个方向。因此我们可以通过求偏导的方式看出函数的变化趋势。
举个小例子来说明这个问题:
在二元函数的积分里我们学过,当一个函数里有两个变量时,它的图像是三维空间里的一个曲面。
我们要从曲面的顶部下到曲面的最底部,我们只要求出变量x和变量y所在位置的切线,根据两条切线的斜率进行对x值和y值的调整,最终找到最小值,找到一条最适合的路。
当然这种方法也存在问题,一个山谷可能有多个最低谷,我们到了一个低谷时无法知道哪个才是最低的,如下图:
综上所述,我们试着将梯度下降法应用在求误差中,我们前面所描述的erro_sum ,他是由(计算结果-正确结果)^2加总构成的。这里面中的变量”计算结果“是受网络中每一条链路权重的影响,因此我们可以认为error_Sum是一个含有多个变量的函数,每个变量对应着网络中每条链路的权重。为此求这个权值上最小误差的变化点,就相当于用erro_sum对相应的权值求偏导数:
经过上述推导,我们得到求偏导的最终结果为:
求出上述内容后,我们根据这个公式对权值进行调整:
二、用代码从零开始构建神经网络
在这一部分中,我将会纯手杠一个手写文字识别识别的系统出来。让我们先来回忆回忆深度学习系统的过程:
接下来我会一步一步照着这个过程构建一步一个脚印走下去:
数据:
我们用csv格式的mininst数据去做本次实验,数据下载路径为:
https://raw.githubusercontent.com/makeyourownneuralnetwork/makeyourownneuralnetw ork/master/mnist_dataset/mnist_test_10.csv
数据下载下来后,数据的格式是
import numpy
import matplotlib.pyplot
%matplotlib inline
all_values = data_list[0].split(',')
#把数据依靠','分割,并分别读入
image_array = numpy.asfarray(all_values[1:]).reshape((28, 28))
#第一个值对应的是图片的表示的数字,所以我们读取图片数据时要去掉第一个数值
matplotlib.pyplot.imshow(image_array, cmap='Greys', interpolation='None')
然后我们再对数据格式做些调整,为了方便输入到神经网络中进行计算分析。我们把所有数值全部转换到0.01到1.0之间,由于表示图片的二维数组中,每个数大小不超过255,由此我们只要把所有数组除以255,就能让数据全部落入到0和1之间,有些数值虽然很小,除以255后会变为0,这样会导致链路权重更新出问题,所以我们需要把除以255后的结果先乘以0.99,然后再加上0.01,这样所有数据就处于0.01到1之间。代码实现为:
scaled_input = image_array / 255.0 * 0.99 + 0.01
综上所述,我们输入神经网络进行训练的,应该是一个28*28的矩阵,期望的输出层最终应该有十个节点(期望0~9十个数字中的一个)设图片对应的是数字0,那么输出层网络中,第一个节点应该输出一个高百分比,其他节点输出低百分比,如果图片对应的数字是9,那么输出层最后一个节点应该输出高百分比,其他节点输出低百分比,例如下图:
搭建神经网络:
首先让我们会议一下,一个神经网络需要些什么:需要训练层训练数据,需要输入层输入数据并计算结果,为了方便网络的初始化,我们再加上网络的初始化:
首先我们再init方法里初始化网络,设置输入层,中间层,和输出层节点数:
import numpy
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
#初始化网络,设置输入层,中间层,和输出层节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
#设置学习率
self.lr = learningrate
'''
初始化权重矩阵,我们有两个权重矩阵,一个是wih表示输入层和中间层节点间链路权重形成的矩阵
一个是who,表示中间层和输出层间链路权重形成的矩阵
'''
self.wih = numpy.random.rand(self.hnodes, self.inodes) - 0.5
# 借用numpy的random函数随机生成-0.5~0.5的初始化权值
self.who = numpy.random.rand(self.onodes, self.hnodes) - 0.5
self.activation_function = lambda x:scipy.special.expit(x)
#初始化网络
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.3
n = NeuralNetWork(input_nodes, hidden_nodes, output_nodes, learning_rate)
然后我们写一下训练层:
训练层涉及--传入数据--运算---计算误差--根据误差反向调整权重,下面我们来看看:
def train(self, inputs_list, targets_list):
#根据输入的训练数据更新节点链路权重
'''
把inputs_list, targets_list转换成numpy支持的二维矩阵
.T表示做矩阵的转置
'''
inputs = numpy.array(inputs_list, ndmin=2).T
targets = numpy.array(targets_list, nmin=2).T
#计算信号经过输入层后产生的信号量
hidden_inputs = numpy.dot(self.wih, inputs)
#中间层神经元对输入的信号做激活函数后得到输出信号
hidden_outputs = self.activation_function(hidden_inputs)
#输出层接收来自中间层的信号量
final_inputs = numpy.dot(self.who, hidden_outputs)
#输出层对信号量进行激活函数后得到最终输出信号
final_outputs = self.activation_function(final_inputs)
#计算误差
output_errors = targets - final_outputs
hidden_errors = numpy.dot(self.who.T, output_errors)
#根据误差计算链路权重的更新量,然后把更新加到原来链路权重上,梯度下山法,看一看看上面梯度下山法最后推导公式
self.who += self.lr * numpy.dot((output_errors * final_outputs *(1 - final_outputs)),
numpy.transpose(hidden_outputs))
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1 - hidden_outputs)),
numpy.transpose(inputs))
pass
然后我们看看计算输出层:
def query(self, inputs):
#根据输入数据计算并输出答案
#计算中间层从输入层接收到的信号量
hidden_inputs = numpy.dot(self.wih, inputs)
#计算中间层经过激活函数后形成的输出信号量
hidden_outputs = self.activation_function(hidden_inputs)
#计算最外层接收到的信号量
final_inputs = numpy.dot(self.who, hidden_outputs)
#计算最外层神经元经过激活函数后输出的信号量
final_outputs = self.activation_function(final_inputs)
print(final_outputs) pass
开始带入数据进行运算,
training_data_file = open("mnist_train_100.csv")
trainning_data_list = training_data_file.readlines()
print(len(trainning_data_list))
training_data_file.close()
#把数据依靠','区分,并分别读入
for record in trainning_data_list:
all_values = record.split(',')
inputs = (numpy.asfarray(all_values[1:]))/255.0 * 0.99 + 0.01
#设置图片与数值的对应关系,设置输出层的权值于上面图片对应
targets = numpy.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
经过训练后,结果如图,我们可以看出正确率并不是十分高
为此,我们设置一个epoch。增加循环次数
7
#初始化网络
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.3
n = NeuralNetWork(input_nodes, hidden_nodes, output_nodes, learning_rate)
#读入训练数据
#open函数里的路径根据数据存储的路径来设定
training_data_file = open("mnist_train.csv")
trainning_data_list = training_data_file.readlines()
print(len(trainning_data_list))
training_data_file.close()
#加入epocs,设定网络的训练循环次数
epochs = 7
print("begin trainning")
for e in range(epochs):
#把数据依靠','区分,并分别读入
for record in trainning_data_list:
all_values = record.split(',')
inputs = (numpy.asfarray(all_values[1:]))/255.0 * 0.99 + 0.01
#设置图片与数值的对应关系
targets = numpy.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
print("trainning complete")
设置了epoch后,最后结果为:
完整源码
import numpy
import scipy.special
class NeuralNetWork:
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 初始化网络,设置输入层,中间层,和输出层节点数
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
# 设置学习率
self.lr = learningrate
'''
初始化权重矩阵,我们有两个权重矩阵,一个是wih表示输入层和中间层节点间链路权重形成的矩阵
一个是who,表示中间层和输出层间链路权重形成的矩阵
'''
self.wih = numpy.random.rand(self.hnodes, self.inodes) -0.5
# 借用numpy的random函数随机生成-0.5~0.5的初始化权值
self.who = numpy.random.rand(self.onodes, self.hnodes) -0.5
self.activation_function =lambda x: scipy.special.expit(x)
def train(self, inputs_list, targets_list):
# 根据输入的训练数据更新节点链路权重
'''
把inputs_list, targets_list转换成numpy支持的二维矩阵
.T表示做矩阵的转置
'''
inputs = numpy.array(inputs_list, ndmin=2).T
targets = numpy.array(targets_list, ndmin=2).T
# 计算信号经过输入层后产生的信号量
hidden_inputs = numpy.dot(self.wih, inputs)
# 中间层神经元对输入的信号做激活函数后得到输出信号
hidden_outputs =self.activation_function(hidden_inputs)
# 输出层接收来自中间层的信号量
final_inputs = numpy.dot(self.who, hidden_outputs)
# 输出层对信号量进行激活函数后得到最终输出信号
final_outputs =self.activation_function(final_inputs)
# 计算误差
output_errors = targets - final_outputs
hidden_errors = numpy.dot(self.who.T, output_errors)
# 根据误差计算链路权重的更新量,然后把更新加到原来链路权重上,梯度下山法,看一看看上面梯度下山法最后推导公式
self.who +=self.lr * numpy.dot((output_errors * final_outputs * (1 - final_outputs)),
numpy.transpose(hidden_outputs))
self.wih +=self.lr * numpy.dot((hidden_errors * hidden_outputs * (1 - hidden_outputs)),
numpy.transpose(inputs))
pass
def query(self, inputs):
# 根据输入数据计算并输出答案
# 根据输入数据计算并输出答案
# 计算中间层从输入层接收到的信号量
hidden_inputs = numpy.dot(self.wih, inputs)
# 计算中间层经过激活函数后形成的输出信号量
hidden_outputs =self.activation_function(hidden_inputs)
# 计算最外层接收到的信号量
final_inputs = numpy.dot(self.who, hidden_outputs)
# 计算最外层神经元经过激活函数后输出的信号量
final_outputs =self.activation_function(final_inputs)
print(final_outputs)
pass
input_nodes =784
hidden_nodes =100
output_nodes =10
learning_rate =0.3
n = NeuralNetWork(input_nodes, hidden_nodes, output_nodes, learning_rate)
training_data_file =open("mnist_train.csv")
trainning_data_list = training_data_file.readlines()
print(len(trainning_data_list))
training_data_file.close()
#把数据依靠','区分,并分别读入
for recordin trainning_data_list:
all_values = record.split(',')
inputs = (numpy.asfarray(all_values[1:]))/255.0 *0.99 +0.01
#设置图片与数值的对应关系,设置输出层的权值于上面图片对应
targets = numpy.zeros(output_nodes) +0.01
targets[int(all_values[0])] =0.99
n.train(inputs, targets)
test_data_file =open("mnist_test.csv")
test_data_list = test_data_file.readlines()
test_data_file.close()
scores = []
for recordin test_data_list:
all_values = record.split(',')
correct_number =int(all_values[0])
#预处理数字图片
inputs = (numpy.asfarray(all_values[1:])) /255.0 *0.99 +0.01
#让网络判断图片对应的数字
outputs = n.query(inputs)
#找到数值最大的神经元对应的编号
label = numpy.argmax(outputs)
# print("网络认为图片的数字是:", label)
if label == correct_number:
scores.append(1)
else:
scores.append(0)
scores_array = numpy.asarray(scores)
print("perfermance = ", scores_array.sum() / scores_array.size)
参考文献:
[2] 刘凡平. 《神经网络与深度学习应用实战》[B] 2019