Python神经网络编程.pdf
链接: https://pan.baidu.com/s/1mZTCas8GVjLdSyNehFAHkg 提取码: que5
第二章 使用Python进行DIY
-
numpy模块创建3乘以2的数组
import numpy a = numpy.zeros([3,2]) print(a) [[0. 0.] [0. 0.] [0. 0.]]
-
使用matplotlib给数组使用颜色
import matplotlib.pyplot # %matplotlib inline # 确保绘制的图形在notebook上显示 # 创建绘图的指令是imshow(),第一个参数是我们要绘制的数组。 # 最后一项“interpolation”是告诉Python,不要为了让绘图看起来 # 更加平滑而混合颜色,这是Python为了帮助我们而进行的缺省设置。 matplotlib.pyplot.imshow(a, interpolation="nearest")
-
python类的一个简单实例
class Dog: def __init__(self, petname, temp): self.name = petname; self.temperature = temp; def status(self): print("dog name is ", self.name) print("dog temperature is ", self.temperature) pass def setTemperature(self,temp): self.temperature = temp; pass def bark(self): print("woof!") pass pass lassie = Dog("Lassie", 37) print(lassie.status()) lassie.setTemperature(40) print(lassie.status()) dog name is Lassie dog temperature is 37 None dog name is Lassie dog temperature is 40 None
-
使用Python制作神经网络
勾勒一下神经网络类的大概样子,它应该至少有3个函数:
- 初始化函数——设定输入层节点、隐藏层节点和输出层节点的数量。
- 训练——学习给定训练集样本后,优化权重。
- 查询——给定输入,从输出节点给出答案。
代码框架:
# neural network class definition class neuralNetwork: # initialise the neural network def __init__() pass # train the neural network def train() pass # query the neural network def query() pass
-
初始化网络
我们需要设置输入层节点、隐藏层节点和输出层节点的数量。
这些节点数量定义了神经网络的形状和尺寸。
我们不会将这 些数量固定,而是当我们使用参数创建一个新的神经网络对象时,才会确定这些数量。通过这种方式,我们保留了选择的余地,轻松地创建不同大 小的新神经网络。(在我们刚刚所做出决定中,其底层蕴含着一个重要意义。优秀的程序 员、计算机科学家和数学家,只要可能,都尽力创建一般代码,而不是具 体的代码。这是一种好习惯,它迫使我们以一种更深更广泛的适用方式思 考求解问题。如果能做到这点,就意味着我们的解决方案可以适用于不同 的场景。在此处,这意味着,我们将尽可能地为神经网络开发代码,使神 经网络保持尽可能多地开放有用的选项,并将假设降低到最低限度,从而 使代码很容易根据不同需要得到使用。我们希望同一个类可以创建一个小 型的神经网络,也可创建一个大型的神经网络——只需传递所需的大小给 参数即可。)
同时也请不要忘了学习率。
__ init__()函数
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate) # set number of nodes in each input, hidden, output layer self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # learning rate self.lr = learningrate pass
让我们使用所定义的神经网络类,尝试创建每层3个节点、学习率为 0.5的小型神经网络对象。
# number of input, hidden and output nodes input_nodes = 3 hidden_nodes = 3 output_nodes = 3 # learning rate is 0.5 learning_rate = 0.5 # create instance of neural network n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
当然,这段代码创建了一个对象,但是由于我们还没有编码任何函数执行实际的工作,因此这个对象还没有任何用途。没关系,从小处着眼, 让代码逐步成长,在通往目标的途中,查找并解决问题,这是一种很好的技术。
-
权重——网络的核心
下一步是创建网络的节点和链接。网络中最重要的部分是链接权重, 我们使用这些权重来计算前馈信号、反向传播误差,并且在试图改进网络 时优化链接权重本身。
- 在输入层与隐藏层之间的链接权重矩阵Winput_hidden,大小为hidden_nodes 乘以 input_nodes。
- 在隐藏层和输出层之间的链接权重矩阵Whidden_output,大小为hidden_nodes乘以 output_nodes。
-
生成3*3的数组,数组中的每个值都是0~1的随机值。
numpy. random. rand(3,3) array([[0.95669773, 0.86421237, 0.76923625], [0.87079086, 0.84554158, 0.53004889], [0.79264941, 0.730875 , 0.6135984 ]])
因为权重的范围在-1.0到+1.0之间。为了简单起见,我们可以将上面数组中 的每个值减去0.5,这样,在效果上,数组中的每个值都成为了-0.5到0.5之 间的随机值。
-
创建初始权重矩阵
numpy. random. rand(3,3) - 0.5 array([[-0.39259059, -0.22955753, 0.24067198], [-0.4549577 , 0.23078032, 0.06623471], [ 0.2263834 , -0.29358245, 0.40512887]])
权重是神经 网络的固有部分,与神经网络共存亡,它不是一个临时数据集,不会随着 函数调用结束而消失。这意味着,权重必须也是初始化的一部分,并且可 以使用其他函数(如训练函数和查询函数)来访问。
-
神经网络的心脏——创建两个链接权重矩阵
# link weight matrices, wih and who # weights inside the arrays are w_i_j, where link is from node i to node j in the next layer # w11 w21 # w12 w22 etc self.wih = (numpy.random.rand(self.hnodes, self.inodes) - 0.5) self.wh0 = (numpy.random.rand(self.onodes, self.hnodes) - 0.5)
-
较复杂的权重——正态概率分布采样权重,其中平均值为0,标准方差为节点传入链接数目的开方, 即1/ 根号传入链接数目。
具体代码???
-
query()函数
query()函数接受神经网络的输入,返回网络的输出。这个功能非常简 单,但是,为了做到这一点,你要记住,我们需要传递来自输入层节点的 输入信号,通过隐藏层,最后从输出层输出。你还要记住,当信号馈送至 给定的隐藏层节点或输出层节点时,我们使用链接权重调节信号,还应用 S激活函数抑制来自这些节点的信号。
下式显示了输入层 和隐藏层之间的链接权重矩阵Winput_hidden如何与输入矩阵I相乘,给出隐藏层节点的输 入信号。
hidden_inputs = numpy.dot(self.wih, inputs)
这一段简单的Python完成了所有的工作,将所有的输入与所有正确的 链接权重组合,生成了组合调节后的信号矩阵,传输给每个隐藏层节点。 如果下一次选择使用不同数量的输入层节点或隐藏层节点,不必重写这段 代码就可以进行工作。
为了获得从隐藏层节点处出现的信号,我们简单地将S抑制函数应用 到每一个出现的信号上:
Scipy python库里的sigmoid函数叫expit()
# scipy.special for the sigmoid function expit() import scipy.special
在神经网络初始化部分的代码内部,下列代码定义了希望使用的激活函数。
# activation function is the sigmoid function self.activation_function = lambda x : scipy.special.expit(x)
这个函数接受了x,返回 scipy.special.expit(x),这就是S函数。使用lambda创建的函数是没有名字 的,经验丰富的程序员喜欢称它们为匿名函数,但是这里分配给它一个名 字self.activation_function()。所有这些事情意味着,无论何时任何人需要使用激活函数,那么他所需做的就是调用self.activation_function()。
接下来,我们要将激活函数应用到组合调整后,准备进入隐藏层节点的信号。
# calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs)
也就是说,隐藏层节点的输出信号在名为hidden_outputs的矩阵中。
那么信号如何到达最终输出层呢?
下面的代码总结了我们如何计算隐藏层信号和输出层信号。
# calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs)
-
在训练神经网络的过程中有两个阶段,第一个阶段就是计算输出, 如同query()所做的事情,第二个阶段就是反向传播误差,告知如何优化链接权重。
下图显示的是所创建的小型网络,其中,在输入层、隐藏层和输出层 中,每层有3个节点,并且使用随机选择的输入(1.0,0.5,-1.5)查询网络。
-
训练网络
训练任务分为两个部分:
第一部分,针对给定的训练样本计算输出。这与我们刚刚在query()函数上所做的没什么区别。
第二部分,将计算得到的输出与所需输出对比,使用差值来指导网络权重的更新。
def train(self, input_list, targets_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T targets = numpy.array(targets_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) pass
我们使用完全相同的方式从输入层前馈信号到最终输出层,所以这段 代码与在query()函数中的几乎完全一样。
由于需要使用包含期望值或目标答案的训练样本来训练网络——因此 唯一的区别是,这部分代码中有一个额外的参数,即在函数的名称中定义 的targets_list。
接下来越来越接近神经网络工作的核心,即基于所计算输出与目标输出之间的误差,改进权重。
首先需要计算误差,这个值等于训练样本所提供的预期目标输出值与 实际计算得到的输出值之差。这个差也就是将矩阵targets和矩阵 final_outputs中每个对应元素相减得到的。
# error is the (target - actual) output_errors = targets - final_outputs
我们可以计算出隐含层节点反向传播的误差。请回忆一下如何根据所 连接的权重分割误差,为每个隐藏层节点重组这些误差。对于这个计算过 程,我们得到了其矩阵形式:
# hidden layer error is the output_errors, split by weigths, recombined at hidden nodes hidden_errors = numpy.dot(self.who.T, output_errors)
这样,我们就拥有了所需要的一切,可以优化各个层之间的权重了。 对于在隐蔽层和最终层之间的权重,我们使用output_errors进行优化。对于 输入层和隐藏层之间的权重,我们使用刚才计算得到的hidden_errors进行优化。
先前,我们得到了用于更新节点j与其下一层节点k之间链接权重的矩 阵形式的表达式:
我们首先为隐藏层和最终层之间的权重进行编码。
# update the weights for the links between the hidden and output layers self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
然后用于输入层和隐藏层之间权重的代码也是类似的。我们只是利用对称性,重写代码,更换名字,这样它们指的就是神经网络的前一层了。
# update the weights for the links between the input and hidden layers self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outpyts)), numpy.transpose(inputs))
-
完整的神经网络代码
import numpy # scipy.special for the sigmoid function expit() import scipy.special ''' 这些代码可用于创建、训练和查询3层神经网络,进行几乎任何任务, 这么看来,代码不算太多。 ''' # neural network class definition class neuralNetwork: # initialise the neural network def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate): # set number of nodes in each input, hidden, output layer self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # link weight matrices, wih and who # weights inside the arrays are w_i_j, where link is from node i to node j in the next layer # w11 w21 # w12 w22 etc self.wih = (numpy.random.rand(self.hnodes, self.inodes) - 0.5) self.who = (numpy.random.rand(self.onodes, self.hnodes) - 0.5) # learning rate self.lr = learningrate # activation function is the sigmoid function self.activation_function = lambda x : scipy.special.expit(x) pass # train the neural network def train(self, inputs_list, targets_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T targets = numpy.array(targets_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) # error is the (target - actual) output_errors = targets - final_outputs # hidden layer error is the output_errors, split by weigths, recombined at hidden nodes hidden_errors = numpy.dot(self.who.T, output_errors) # update the weights for the links between the hidden and output layers self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs)) # update the weights for the links between the input and hidden layers self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs)) pass # query the neural network def query(self, inputs_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) return final_outputs # number of input, hidden and output nodes input_nodes = 3 hidden_nodes = 3 output_nodes = 3 # learning rate is 0.5 learning_rate = 0.5 # create instance of neural network n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate) n.query([1.0, 0.5, -1.5]) array([[0.49313182], [0.54598096], [0.51478851]])
-
手写数字的数据集MNIST
-
训练集是用来训练神经网络的60 000个标记样本集。标记 是指输入与期望的输出匹配,也就是答案应该是多少。
-
可以使用较小的只有10 000个样本的测试集来测试我们的想法或算法 工作的好坏程度。由于这也包含了正确的标记,因此可以观察神经网络是 否得到正确的答案。
-
将训练和测试数据集分开的想法,是为了确保可以使用神经网络之前 没有见过的数据进行再次测试。否则,我们就可以采用欺骗手段,让神经 网络简单地记忆训练数据,得到一个完美、但是有欺骗性的得分。在整个 机器学习领域,将测试数据与训练数据分开是一种很常见的想法。
- 第一个值是标签,即书写者实际希望表示的数字,如“7”或“9”。这是我们希望神经网络学习得到的正确答案。
- 随后的值,由逗号分隔,是手写体数字的像素值。像素数组的尺寸是 28 乘以28,因此在标签后有784个值。如果想知道这是否有784个值, 可以一个一个地数一下。
- 第一个数字是“5”,这是标签,并且其余的784个数字是构成图像像素的颜色值。这些颜色值介于0和255之间。
因此,第一个记录表示数字“5”,就是所显示的第一个值,这行文本的 其余部分是某人的手写数字5的像素值。第二个记录表示数字“0”,第三个 记录表示数字“4”,第四个记录表示“1”,第五个表示“9”。你可以从MNIST 数据文件中挑选任一行,第一个数字告诉你接下来图像数据的标签是什 么。
-
-
使用Python打开文件并获取其中的内容
data_file = open("mnist_dataset/mnist_train_100.csv",'r') data_list = data_file.readlines() data_file.close()
-
open()函数创建了此文件的一个文件句柄、一个 引用,我们将这个句柄分配给命名为data_file的变量。现在已经打开了文 件,任何进一步的操作,如读取文件,都将通过句柄完成。
-
data _list这个变量包含了一个列表,列表中的一项是表示文件中一行的字符串。data_list [0] 是第一条记录,data_list [9]是第十条记录,以此类推。
(顺便说一句,由于readlines()会将整个文件读取到内存中,因此你可能 会听到别人告诉你不要使用这种方法。他们会告诉你,一次读取一行,对 这行进行所需要进行的操作,然后移动到下一行。他们都没有错,不要将 整个文件读入内存中,而是一次在一行上工作,这更有效率。但是,我们 的文件不是很大,如果使用readlines(),那么代码相对容易一些,对我们而 言,在学习Python时简单和清晰是很重要的。)
-
最后一行代码关闭文件。在用完如文件这样的资源后,关闭和清理文 件是一种很好的做法。如果不这样做,文件依然开着,这可能会造成问 题。什么问题呢?有些程序可能不希望写入处在打开状态的文件,以免导 致不一致。这就像是两个人试图在同一张纸上写信!有时候,计算机可能 会锁定文件,防止发生这种冲突。如果使用完文件不清理,那么你就有一 堆锁定的文件。最起码应该关闭文件,让计算机释放用于保存文件的部分 内存。
-
-
使用imshow()函数绘制数字矩形数组
- 先将由逗号分隔,长的文本字符串值,拆分成单个值,在逗号处进行分割。
- 忽略第一个值,这是标签,将剩余的28 × 28 = 784个值转换成28列28 行的数组。
- 绘制数组!
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')
可以看到绘制的图像是5,这就是标签所表示的预期数字。如果转而选择下一条记录data_list [1],就可以得到图片0。
-
准备MNIST训练数据
我们需要做的第一件事情是将输入颜色值从较大的0到255的范围,缩放至较小的0.01到1.0的范围。
将在0到255范围内的原始输入值除以255,就可以得到0到1范围的输入值。
然后,需要将所得到的输入乘以0.99,把它们的范围变成0.0 到0.99。 接下来,加上0.01,将这些值整体偏移到所需的范围0.01到1.00。
scaled_input = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01 print(scaled_input)
输出确认,这些值当前的范围为0.01到0.99。
我们已经通过缩放和移位让MNIST数据准备就绪,可以输入神经网络进行训练和查询了。
-
思考神经网络的输出
先前,我们看到输出值应该匹配激活函数可以输出值的范围。我们使用的逻辑函数不能输出如-2.0或 255 这样的数字,能输出的范围为0.0到1.0,事实上不能达到0.0或1.0,这是逻 辑函数的极限值,逻辑函数仅接近这两个极限,但不能真正到达那里。因 此,看起来在训练时必须调整目标值。
但是,实际上,我们要问自己一个更深层次的问题。输出应该是什么 样子的?这应该是图片答案吗?这意味着有28×28 = 784个输出节点。
如果退后一步,想想要求神经网络做什么,我们会意识到,要求神经 网络对图像进行分类,分配正确的标签。这些标签是0到9共10个数字中的 一个。这意味着神经网络应该有10个输出层节点,每个节点对应一个可能 的答案或标签。如果答案是“0”,输出层第一个节点激发,而其余的输出节 点则保持抑制状态。如果答案是“9”,输出层的最后节点会激发,而其余的输出节点则保持抑制状态。下图详细阐释了这个方案,并显示了一些示例输出。
现在,我们需要把这些想法转换成目标数组,用于神经网络的训练。
如果训练样本的标签为“5”,那么需要创建输出节点的目标数组,其中除了对应于标签“5”的节点,其他所有节点的值应该都很小,这个数组看起来可能如[0,0,0,0,0,1,0,0,0,0]。
我们将使用值0.01和0.99来代替0和1,这样标签为“5”的目标输出数组为[0.01, 0.01, 0.01, 0.01, 0.01, 0.99, 0.01, 0.01, 0.01, 0.01]。
目标矩阵代码
# output nodes is 10(example) onodes = 10 targets = numpy.zeros(onodes) + 0.01 targets[int(all_values[0])] = 0.09 print(targets) # [0.09 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01]
最后一行代码获得了MNIST数据集记录中的第一个元素,也就是训练目 标标签,将其从字符串形式转换为整数形式。请记住,从源文件读取的记 录是文本字符串,而不是数字。一旦转换完成,我们使用目标标签,将目 标列表的正确元素设置为0.99。标签“0”将转换为整数0,这与标签对应的 targets []中的索引是一致的,因此这看起来非常整洁。类似地,标签“9”将 转换为整数9,targets [9]确实是此数组的最后一个元素。
-
title
# python notebook for make your own neural network # code for a 3-layer neural network, and code for learning the MNIST dataset import numpy # scipy.special for the sigmoid function expit() import scipy.special # library for plotting arrays import matplotlib.pyplot # ensure the plots are inside this notebook, not an external window %matplotlib inline # neural network class definition class neuralNetwork: # initialise the neural network def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate): # set number of nodes in each input, hidden, output layer self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # link weight matrices, wih and who # weights inside the arrays are w_i_j, where link is from node i to node j in the next layer # w11 w21 # w12 w22 etc self.wih = (numpy.random.rand(self.hnodes, self.inodes) - 0.5) self.who = (numpy.random.rand(self.onodes, self.hnodes) - 0.5) # learning rate self.lr = learningrate # activation function is the sigmoid function self.activation_function = lambda x : scipy.special.expit(x) pass # train the neural network def train(self, inputs_list, targets_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T targets = numpy.array(targets_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) # error is the (target - actual) output_errors = targets - final_outputs # hidden layer error is the output_errors, split by weigths, recombined at hidden nodes hidden_errors = numpy.dot(self.who.T, output_errors) # update the weights for the links between the hidden and output layers self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs)) # update the weights for the links between the input and hidden layers self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs)) pass # query the neural network def query(self, inputs_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) return final_outputs # number of input, hidden and output nodes input_nodes = 784 hidden_nodes = 100 output_nodes = 10 # learning rate is 0.3 learning_rate = 0.3 # create instance of neural network n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate) # load the mnist training data CSV file into a list training_data_file = open("mnist_dataset/mnist_train_100.csv", 'r') training_data_list = training_data_file.readlines() training_data_file.close() # train the neural network # go through all records in the training data set for record in training_data_list: # split the record by the ',' commas all_values = record.split(',') # scale and shift the inputs inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # create the target output values (all 0.01, except the desired label which is 0.99) targets = numpy.zeros(output_nodes) + 0.01 # all_values[0] is the target label for this record targets[int(all_values[0])] = 0.99 n.train(inputs, targets) pass
为什么选择784个输入节点呢?请记住,这是28×28的结果,即组成手 写数字图像的像素个数。
选择使用100个隐藏层节点并不是通过使用科学的方法得到的。我们认 为,神经网络应该可以发现在输入中的特征或模式,这些模式或特征可以 使用比输入本身更简短的形式表达,因此没有选择比784大的数字。通过选 择使用比输入节点的数量小的值,强制网络尝试总结输入的主要特点。但 是,如果选择太少的隐藏层节点,那么就限制了网络的能力,使网络难以 找到足够的特征或模式,也就会剥夺神经网络表达其对MNIST数据理解的 能力。给定的输出层需要10个标签,对应于10个输出层节点,因此,选择 100这个中间值作为中间隐藏层的节点数量,似乎有点道理。
这里应该强调一点。对于一个问题,应该选择多少个隐藏层节点,并 不存在一个最佳方法。同时,我们也没有最佳方法选择需要几层隐藏层。 就目前而言,最好的办法是进行实验,直到找到适合你要解决的问题的一 个数字。
-
测试网络
首先需要获得测试记录,这与用于获取训练数据的Python代码非常相似。
# load the mnist test data CSV file into a list test_data_file = open("mnist_dataset/mnist_test_10.csv", 'r') test_data_list = test_data_file.readlines() test_data_file.close()
下图显示从测试数据集中取出第一条记录,查询当前 已得到训练的神经网络。
可以看到,测试数据集的第一条记录具有标签“7”。这是当我们查询这 条记录时,我们希望神经网络给出的回答。
# get the first test record all_values = test_data_list[0].split(',') # print the label print(all_values[0]) # 7
绘制像素值,使数据变成图像,我们确认该手写数字的确为“7”。
image_array = numpy.asfarray(all_values[1:]).reshape((28,28)) matplotlib.pyplot.imshow(image_array,cmap='Greys',interpolation='None')
查询已得到训练的网络,生成了对应每个输出节点所输出的一串数 字。你很快就会发现,其中一个输出值比其他输出值大很多,且对应于标 签“7”。由于第一个元素对应于标签“0”,因此这就是第8个元素。
n.query((numpy.asfarray(all_values[1:])/255.0 * 0.99) + 0.01) array([[0.14975374], [0.02160699], [0.01228465], [0.04529811], [0.06070987], [0.0029977 ], [0.02369097], [0.78854187], [0.01373058], [0.07526987]])
成功了!
这是一个需要细细品味的时刻。我们在本书中进行的辛勤工作都有了 价值!
我们训练了神经网络,让神经网络告诉我们图片中所代表的数字是什 么。请记住,神经网络之前没有见过那张图片,它不是训练数据集的一部 分。因此,神经网络能够正确区分它从来没有见过的手写字符。这真是让 人印象深刻啊!
只需几行简单的Python,我们就已经创建了一个神经网络,这个神经 网络可以执行许多人认为是具备人工智能的事情——它学会了识别人的笔迹图片。
-
我们可以记录分数,来看看神经网络对数据集的其余记录有何表现。
# test the neural network # scorecard for how well the network performs, initially empty scorecard = [] # go through all the records in the test data set for record in test_data_list: # split the record by the ',' commas all_values = record.split(',') # correct answer is first value correct_label = int(all_values[0]) print(correct_label, "correct label") # scale and shift the inputs inputs = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01 # query the network outputs = n.query(inputs) # the index of the highest value corresponds to the label label = numpy.argmax(outputs) print(label, "network's answer") # append correct or incorrect to list if (label==correct_label): # network's answer matches correct answer, add 1 to scorecard scorecard.append(1) else: # network's answer doesn't match correct answer, add 0 to scorecard scorecard.append(0) pass pass print(scorecard) 7 correct label 7 network's answer 2 correct label 1 network's answer 1 correct label 1 network's answer 0 correct label 0 network's answer 4 correct label 4 network's answer 1 correct label 1 network's answer 4 correct label 7 network's answer 9 correct label 4 network's answer 5 correct label 6 network's answer 9 correct label 7 network's answer [1, 0, 1, 1, 1, 1, 0, 0, 0, 0]
循环可以使用测试数据集中的所有记录进行测试,在跳进这个循环之 前,创建一个空的列表,称为计分卡(scorecard),这个记分卡在测试每 条记录之后都会进行更新。
可以看到,在循环内部,我们所做的与先前所做的一样,根据逗号拆 分文本记录,分离出数值。记下第一个数字,这是正确答案。然后,重新 调整剩下的值,让它们适合用于查询神经网络。
我们将来自神经网络的回答保存在名为outputs的变量中。
-
将测试成绩作为分数并打印出来,结束程序。
# calculate the performance score, the fraction of correct answers scorecard_array = numpy.asarray(scorecard) print("performance=",scorecard_array.sum()/scorecard_array.size) # performance= 0.5
-
使用完整数据集进行训练和测试
使用60 000个训练样本训练简单的3层神经网络,然后使用10 000条记 录对网络进行测试,得到的总表现分数为0.9473。这个表现简直太棒了, 几乎是95%的准确率!
这个略低于95%的准确性,可以与记录 在http://yann.lecun.com/exdb/mnist/ 网页的行业标准媲美。我们可以看到, 比起一些历史基准,这个准确率还是略胜一筹的,这里列出的最简单的神 经网络方法所表现的准确率为95.3%,而我们的神经网络的性能大致相 当。
这一点也不糟糕。我们应该感到高兴,第一次尝试的简单神经网络就 实现了研究者所开发的专业神经网络的性能。
顺便说一句,计算60 000个训练样本,每个样本的计算都需要进行一 组784个输入节点、经过100个隐藏层节点的前馈计算,同时还要进行误差 反馈和权重更新,即使对于一台快速的现代家用计算机而言,这一切也需 要花上一段时间,这一点都不令人吃惊。我的新笔记本计算机花了约2分钟 时间完成了训练循环。你的计算机应该也差不多。(我的笔记本大约是10秒钟。)
-
一些改进:调整学习率
原先的学习率是0.3,试一下将学习率翻倍,设置为0.6,看看提高学习率对整个网络的学习 能力是否有益。如果此时运行代码,会得到0.9047性能得分。这比以前更 糟。因此,看起来好像大的学习率导致了在梯度下降过程中有一些来回跳 动和超调。
使用0.1的学习率再试一次。这次,性能有所改善,得到了0.9523分。 在性能上,这与网站上列出的具有1000个隐藏层节点的神经网络类似。我 们“以少胜多”了。
如果继续设置一个更小的0.01学习率,会发生什么情况?性能没有变 得更好,得分为0.9241。因此,似乎过小的学习率也是有害的。
性能与学习率的关系
上图表明,学习率在0.1和0.3之间可能会有较好的表现,因此,尝试 0.2的学习率,得到0.9537的性能得分。比起0.1或0.3,这个表现确实好了一 些。我们可以绘制图表,对所发生的事情得到一种较好的认识,在其他情 况下,你也应该考虑这种方法——和一串数字相比,图表有助于更好地理 解!因此,我们将坚持使用0.2的学习率,这看起来似乎是MNIST数据集和 神经网络的甜蜜点。
-
一些改进:多次运行
接下来可以做的改进,是使用数据集,重复多次进行训练。
有些人把训练一次称为一个世代。因此,具有10个世代的训练,意味 着使用整个训练数据集运行程序10次。为什么要这么做呢?特别是,如果 这次计算机花的时间增加到10或20甚至30分钟呢?这是值得的,原因是通 过提供更多爬下斜坡的机会,有助于在梯度下降过程中进行权重更新。
试一下使用2个世代。由于现在我们在训练代码外围添加了额外的循 环,因此代码稍有改变。下面的代码显示了外围循环,将代码着色有助于 看到发生了什么。
# train the neural network # epochs is the number of times the training data set is used for training epochs = 2 for e in range(epochs): # go through all records in the training data set for record in training_data_list: # split the record by the ',' commas all_values = record.split(',') # scale and shift the inputs inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # create the target output values (all 0.01, except the desired label which is 0.99) targets = numpy.zeros(output_nodes) + 0.01 # all_values[0] is the target label for this record targets[int(all_values[0])] = 0.99 n.train(inputs, targets) pass
使用2个世代神经网络所得到的性能得分为0.9579,比只有1个世代的神经网络有所改进。
就像调整学习率一样,让我们使用几个不同的世代进行实验并绘图, 以可视化这些效果。直觉告诉我们,所做的训练越多,所得到的性能越 好。有人可能会注意到,太多的训练实际上会过犹不及,这是由于网络过 度拟合训练数据,因此网络在先前没有见到过的新数据上表现不佳。不仅 是神经网络,在各种类型的机器学习中,这种过度拟合也是需要注意的。
性能与世代数目的关系
结果呈现出不可预测性。在大约5或7个世代时,有一个甜蜜点。在此 之后,性能会下降,这可能是过度拟合的效果。性能在6个世代的情况下下 降,这可能是运行中出了问题,导致网络在梯度下降过程中被卡在了一个 局部的最小值中。事实上,由于没有对每个数据点进行多次实验,无法减 小随机过程的影响,因此我们已经预见到结果会有各种变化。这就是为什 么保留了6个世代这个奇怪的点,这是为了提醒我们,神经网络的学习过程 其核心是随机过程,有时候工作得不错,有时候工作得很糟。
另一个可能的原因是,在较大数目的世代情况下,学习率可能设置过 高了。继续这个实验,将学习率从0.2减小到0.1,看看会发生什么情况。
在7个世代的情况下,峰值性能高达0.9628或96.28%。
下图显示了在学习率为0.1情况下,得到的新性能与前一幅图叠加的情 况。
可以看到,在更多世代的情况下,减小学习率确实能够得到更好的性 能。0.9689的峰值表示误差率接近3%,这可以与Yann LeCun网站上的神经 网络标准相媲美了。
直观上,如果你打算使用更长的时间(多个世代)探索梯度下降,那 么你可以承受采用较短的步长(学习率),并且在总体上可以找到更好的 路径,这是有道理的。确实,对于MNIST学习任务,我们的神经网络的甜 蜜点看起来是5个世代。请再次记住,我们在使用一种相当不科学的方式来 进行实验。要正确、科学地做到这一点,就必须为每个学习率和世代组合 进行多次实验,尽量减少在梯度下降过程中随机性的影响。
-
改变网络形状
试着改变中间隐藏层节点的数目。之前是100个。
如果隐藏层节点太少,比如说3个,那么你可以想象,这不可能有足够 的空间让网络学习任何知识,并将所有输入转换为正确的输出。这就像要5 座车去载10个人。你不可能将那么多人塞进去。计算机科学家称这种限制 为学习容量。虽然学习能力不可能超过学习容量,但是可以通过改变车辆 或网络形状来增加容量。
如果有10 000个隐藏层节点,会发生什么情况呢?虽然我们不会缺少 学习容量,但是由于目前有太多的路径供学习选择,因此可能难以训练网 络。这也许需要使用10 000个世代来训练这样的网络。
性能与隐藏层节点的数目
我们还创造了准确度的新纪录,使用200个节点,得分0.9751。使用 500个节点,运行较长的时间,我们的神经网络得到了0.9762分。相比于 Yann LeCun的网站上列出的基准,这是相当不错的成绩了。
-
最终代码
# python notebook for make your own neural network # code for a 3-layer neural network, and code for learning the MNIST dataset import numpy # scipy.special for the sigmoid function expit() import scipy.special # library for plotting arrays import matplotlib.pyplot # ensure the plots are inside this notebook, not an external window %matplotlib inline # neural network class definition class neuralNetwork: # initialise the neural network def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate): # set number of nodes in each input, hidden, output layer self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # link weight matrices, wih and who # weights inside the arrays are w_i_j, where link is from node i to node j in the next layer # w11 w21 # w12 w22 etc self.wih = (numpy.random.rand(self.hnodes, self.inodes) - 0.5) self.who = (numpy.random.rand(self.onodes, self.hnodes) - 0.5) # learning rate self.lr = learningrate # activation function is the sigmoid function self.activation_function = lambda x : scipy.special.expit(x) pass # train the neural network def train(self, inputs_list, targets_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T targets = numpy.array(targets_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) # error is the (target - actual) output_errors = targets - final_outputs # hidden layer error is the output_errors, split by weigths, recombined at hidden nodes hidden_errors = numpy.dot(self.who.T, output_errors) # update the weights for the links between the hidden and output layers self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs)) # update the weights for the links between the input and hidden layers self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs)) pass # query the neural network def query(self, inputs_list): # convert inputs list to 2d array inputs = numpy.array(inputs_list, ndmin=2).T # calculate signals into hidden layer hidden_inputs = numpy.dot(self.wih, inputs) # calculate the signals emerging from hidden layer hidden_outputs = self.activation_function(hidden_inputs) # calculate signals into final output layer final_inputs = numpy.dot(self.who, hidden_outputs) # calculate the signals emerging from final output layer final_outputs = self.activation_function(final_inputs) return final_outputs # number of input, hidden and output nodes input_nodes = 784 hidden_nodes = 200 output_nodes = 10 # learning rate is 0.3 learning_rate = 0.1 # create instance of neural network n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate) # load the mnist training data CSV file into a list training_data_file = open("mnist_dataset/mnist_train.csv", 'r') training_data_list = training_data_file.readlines() training_data_file.close() # train the neural network # epochs is the number of times the training data set is used for training epochs = 5 for e in range(epochs): # go through all records in the training data set for record in training_data_list: # split the record by the ',' commas all_values = record.split(',') # scale and shift the inputs inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # create the target output values (all 0.01, except the desired label which is 0.99) targets = numpy.zeros(output_nodes) + 0.01 # all_values[0] is the target label for this record targets[int(all_values[0])] = 0.99 n.train(inputs, targets) pass pass # load the mnist test data CSV file into a list test_data_file = open("mnist_dataset/mnist_test.csv", 'r') test_data_list = test_data_file.readlines() test_data_file.close() # test the neural network # scorecard for how well the network performs, initially empty scorecard = [] # go through all the records in the test data set for record in test_data_list: # split the record by the ',' commas all_values = record.split(',') # correct answer is first value correct_label = int(all_values[0]) #print(correct_label, "correct label") # scale and shift the inputs inputs = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01 # query the network outputs = n.query(inputs) # the index of the highest value corresponds to the label label = numpy.argmax(outputs) #print(label, "network's answer") # append correct or incorrect to list if (label==correct_label): # network's answer matches correct answer, add 1 to scorecard scorecard.append(1) else: # network's answer doesn't match correct answer, add 0 to scorecard scorecard.append(0) pass pass # calculate the performance score, the fraction of correct answers scorecard_array = numpy.asarray(scorecard) print("performance=",scorecard_array.sum()/scorecard_array.size) # performance= 0.9735