Make your own Neural NetWork之代码详解中

本文介绍了如何使用Python实现神经网络,以MNIST数据集为训练素材,讲解了数据预处理、网络训练和测试的过程。通过对MNIST数据集的加载、解析,展示了训练和测试神经网络的代码,强调了数据预处理的重要性以及如何调整输入和输出以适应神经网络的训练。
摘要由CSDN通过智能技术生成

这篇博客接上一篇博客Make Your Own Neural Network之代码详解上。本文也是出自Make your own Neural NetWork这本书。上一篇博客讲了神经网络类的功能模块,本文主要介绍如何对这个神经网络类进行训练和测试。
声明:

  1. 代码用的Python编写
  2. 红字部分为后面要讲的内容的中心句

MNIST数据集——手写汉字数据

对MNIST数据集熟悉的大大可以跳过这一部分。

  识别人的手写数字是测试人造智能的理想挑战,因为这个问题足够困难和模糊。它不像许多递增或递减数列一样规律,没有很清楚的定义。手写数字识别也是图像识别的一个分支——使计算机正确分类图像包含什么的问题。这个问题经过了数十年的研究,只到最近才有良好的进展,而且像神经网络这样的方法一直是这些跨越式发展的关键部分。我们人类有时候也会对图像包含的内容产生分歧。因为我们会写出不同的手写数字图片,特别是如果人物是着急或不小心写的。为了让您了解图像识别问题的难度,请看下图,是4或9?


  人工智能研究人员使用手写数字的图像集合作为一个受欢迎的设备来测试他们最新的想法和算法。事实上这个数据集常被用来检查我们最新的疯狂想法的图像识别方法,并与其他作品进行对比。也就是,用相同的数据集对不同的想法和算法进行测试。

数据来源

  该数据集称为手写数字的MNIST数据库,可从传奇的神经网络专家Yann LeCun的网站下载。该页面还列出了新旧想法在学习和正确率方面的表现。我们会多次回到这个名单,看看怎么样我们自己的网络与专业网络的差别!MNIST数据库的格式不是最容易使用的,所以其他人又帮助创建了一个更简单格式的数据文件,如这个。这些文件称为CSV文件,这意味着每个值都是以逗号分隔的纯文本(逗号分隔值)。您可以在任何文本编辑器和大多数电子表格中轻松查看它们或数据分析软件将与CSV文件一起工作。它们几乎是一个通用标准。该网站提供两个CSV文件,这也将是本文将要使用的数据集

  1. 训练集http://www.pjreddie.com/media/files/mnist_train.csv
  2. 测试集http://www.pjreddie.com/media/files/mnist_test.csv

  顾名思义,训练集是用于训练神经网络的60000个标记示例的集合。标签表示输入具有所需的输出,也就是目标值。使用较小的10,000个测试集来查看我们的想法或算法是否有效。这个也是包含正确的标签,所以我们可以检查一下我们自己的神经网络是否得到了正确结果。相互独立的训练集合测试集是为了,确保我们测试时用的是以前没有看到的数据。否则,我们可以骗自己来简单地记住训练数据,以获得完美的、尽管是欺骗性的得分。将训练与测试的数据分离的思想在机器学习中是常见的。下面显示了加载这些MNIST测试集的一部分到一个文本编辑器。
nn40.png
  哇!看起来有些事情出了问题!像80年代电脑被黑客入侵的电影之一。其实一切OK。显然,这些行由数字组成,用逗号分隔。由于一条数据相当长,所以它们换行了好几次。这个文本编辑器很贴心地在左边栏显示了真实的行号。现在,我们可以看到四条完整的手写数字样本,和第五条的部分数据。这些记录或文本行的内容很容易理解:

  1. 第一个值是标签,即手写应该表示的实际数字,例如“7”或“9”。这就是神经网络试图学习正确的答案。
  2. 后续值(逗号分隔)是手写数字的像素值。像素阵列的大小是28×28,所以在标签后面有784个值。

  每一条记录的第一个值所示的数字“5”,代表该行的其余文本是某人的手写编号5的像素值。第二个记录表示手写的“0”,第三个表示“ 4“,第四纪录是”1“和第五代表“9”,分别代表后面的是图片0、4、1、9的像素列表。您可以从MNIST数据文件中选择任何一行,第一个数字将告诉您以下图像数据的标签。但是,很难看出784个值的长列表如何构成了某人的手写编号5的图片。我们应该将这些数字作为图像进行展示,以确认它们是手写编号的像素值。在我们深入了解之前,我们应该下载较小的MNIST数据集。 毕竟MNIST数据数据文件相当大,使用较小的子集比较方便。因为它意味着我们可以尝试,试用和开发我们的代码,而不会因为操作一个大的数据集减慢了我们的电脑。一旦我们解决了一个算法和代码,我们就可以开心的使用完整的数据集了。
以下是MNIST数据集的较小子集的链接,也是CSV格式:

  1. MNIST测试数据集中的10条记录
  2. MNIST培训数据集的100条记录
# Python读取文件
data_file = open(r"E:\keras\sample\famous_data\mnist_train_100.csv", 'r')  #一个可读的文件对象
# 因为文件不大,所以一次读了整个文件。理论上应该一行一行的读。
data_list = data_file .readlines()  # 返回一个数据列表,data_list[i]表示第i样本
data_file.close()

  可以看到第一条记录data_list [0]的内容:第一个数字是“5”,它是标签,784个数字中的其余数字是构成图像的像素的颜色值。如果你仔细观察,你可以告诉这些颜色值的范围在0到255之间。你可能想查看其他记录,看看那是否也是真的。您会发现颜色值确实在0到255之间。我们可以使用imshow()函数绘制一个矩形数组的数组,但是我们需要将逗号分隔的数字列表转换成合适的数组
以下是执行此操作的步骤:

  1. 将逗号分隔的长文本字符串分隔为各个值,使用逗号作为拆分的位置。
  2. 忽略作为标签的第一个值,并取剩余的28 * 28 = 784个列表,并将其转换为28行×28列的形状。
  3. 绘制阵列!

同样,最简单的方法就是显示相当简单的Python代码,并且通过代码来更详细的解释发生了什么。

# 首先,我们不能忘记导入Python扩展库,这将帮助我们使用数组和绘图:
import numpy
import matplotlib.pyplot
%matplotlib inline
all_values = data_list[0].split(',')
image_array = numpy.array(all_values[1:], dtype=float).reshape((28,28))
matplotlib.pyplot.imshow(image_array, cmap='Greys', interpolation='None')

这里写图片描述

代码解释:第一行读取刚刚打印出来的第一个记录data_list[0],并用逗号分隔长字符串。你可以看到split()函数这样做,一个参数告诉它要拆分的符号。在这种情况下,符号是逗号,结果将被放入all_values。您可以打印出来,以检查这个确实是一个长的Python列表值。下一行看起来更复杂,因为同一行发生了几件事情。让我们从核心工作,核心有这个all_value列表,但是这个时候方括号[1:]被用来取代这个列表的第一个元素。这就是我们如何忽略第一个标签值,只能取其余的784个值。
numpy.array()是一个numpy函数,用于将文本字符串转换成实数,并创建一个数组。

代码解释:那么文件是以文本形式读取的,每一行或记录仍然是文本。用逗号分隔每一行仍然会产生一些文本,该文本可以是“apple”,“orange123”或“567”。文本字符串“567”与数字567不同。
这就是为什么我们需要将文本字符串转换为数字,即使文本看起来像数字。
  最后的.reshape((28,28))确保数字列表围绕每28个元素进行包围,以使矩阵28由28构成。由此得到的28×28数组称为image_array。唷!这是一个公平的发生在一行。第三行使用imshow()函数简单地绘制了image_array,就像我们刚才看到的那样。这次我们选择了一个带有cmap=’Grays’的灰度调色板,以更好的显示手写字符。

准备MNIST训练数据

X_train

  我们已经知道了如何从MNIST数据文件中获取数据并解决它的可视化问题。我们想用这些数据训练我们的神经网络,但是我们需要考虑一下准备这些数据,然后再把它们抛到我们的神经网络上。你可能听说过,如果输入数据和输出值都是正确的形状,以便它们保持在网络节点激活功能的舒适区域内, 神经网络会工作得更好。
  接下来第一件事是将输入颜色值从0到255之间的范围缩小到0.01 1.0的范围。我们故意选择0.01作为范围的下限,以避免零值输入的问题,因为它们可以强行消除权重更新(因为数据都变成0了)。然而,我们不必为网络前端的输入选择0.99,因为我们不需要为输入避免1.0,它们毕竟还要乘以系数,只需要避免输出为1就行了。将0-255范围内的原始输入除以255将使它们进入范围0-1。然后,我们需要乘以0.99,使它们在0.0 0.99范围内。然后加0.01,将其移动到所需的范围0.01到1.00。以下Python代码显示了这一点:

# scale input to range 0.01 to 1.00
scaled_input = (numpy.array(all_values[1:], dtype='float') / 255.0 * 0.99) + 0.01
print(scaled_input)

这里写图片描述

 X_label

  因此,通过预处理的MNIST数据,准备被投入我们的神经网络进行训练和查询。现在需要考虑神经网络的输出。我们以前看到,输出应该与激活功能可以推出的值的范围相匹配。我们使用的逻辑功能不能推出像2.0或255之类的数字。范围在0.0到1.0之间,实际上你不能达到0.0或1.0,因为logistic函数只能接近这些极限,所以看起来我们必须在训练时调整我们的目标值
  但实际上我们有一个更深层次的问题要问自己。输出应该是甚么?应该是之前的形象的图片吗?这意味着我们要有一个28x28 = 784个输出节点。如果我们退后一步,想想我们正在问神经网络,我们意识到我们要求它对图像进行分类并分配正确的标签。该标签是从0到9的10个数字之一。这意味着应该能够具有10个节点的输出层,每个可能的答案一个或标签。如果答案为“0”,则第一个输出层节点将触发,其余的应该保持沉默。如果答案为“9”,则最后一个输出层节点将触发,其余的将保持静音。
nn59.png

  通过上图可以看到:第一个例子是神经网络认为它已经看到数字“5”的地方,从输出层出现的最大信号来自标签为5的节点(记住这是第六个节点,因为我们从标签开始为零)。这很简单输出节点的其余部分产生非常接近零的小信号。舍入误差可能导致输出为零,但实际上您会记住激活函数不会产生实际的零。下一个例子显示如果神经网络认为它已经看到手写的“零”,可能会发生什么。再次,目前最大的输出来自对应于标签“0”的第一个输出节点。最后一个例子更有意思。这里神经网络产生了最后一个节点的最大输出信号,对应于标号“9”。然而,它具有来自“4”的节点的适度大的输出。通常我们会以最大的信号去,但你可以看到网络部分认为答案可能是“4”。

  手写可能难以确定吗?这种不确定性确实发生在神经网络中,而不是将其看作是坏事,我们应该将其看作是另一个竞争者的有用见解。现在我们需要将这些想法转化为神经网络训练的目标阵列。
  如果训练示例的标签为“5”,则我们需要为输出节点创建一个目标数组,其中所有元素都很小,除了对应于标签“5”的元素。这可能看起来像以下[0,0,0,0,0,1,0,0,0,0]。实际上,我们需要重新调整这些数字,因为这对于Sigmoid激活函数来说是不可能的输出,它将导致大的权重和饱和的网络。所以我们使用0.01和0.99的值,所以标签“5”的目标应该[0.01,0.01,0.01,0.01,0.01,0.99,0.01,0.01,0.01,0.01]。

# 用Python构建目标函数
#output nodes is 10 (example)
onodes = 10
targets = numpy.zeros(onodes) + 0.01
targets[int(all_values[0])] = 0.99

这里写图片描述

非常完美,现在我们已经准备好了训练数据和训练标签。
这是书中完整代码的github链接

# 以下是完整代码
# 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
# 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.normal(0.0, pow(self.inodes, -0.5), (self.hnodes, self.inodes))
        self.who = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.onodes, self.hnodes))

        # 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)

        # output layer error is the (target - actual)
        output_errors = targets - final_outputs
        # hidden layer error is the output_errors, split by weights, 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
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(r"E:\keras\sample\famous_data\mnist_train_100.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

  您可以看到我们已经将绘图库导入顶部,添加了一些代码来设置输入,隐藏和输出层的大小,读取较小的MNIST训练数据集,然后用这些记录训练神经网络。为什么我们选择784个输入节点?记住,这是28 x 28,组成手写数字图像的像素。100个隐藏节点的选择不是那么科学。我们没有选择大于784的数字,因为这个想法是神经网络应该在输入中找到可以以比输入本身更短的形式表达的特征或模式。所以选择一个小于输入数量的值,可以迫使网络尝试总结主要功能。然而,如果我们选择太少的隐藏层节点,将会限制网络找到足够的特征或模式的能力,导致失去MNIST数据的学习能力。
  给定输出层需要10个标签,因此10个输出节点,隐藏层的中间体100的选择似乎是有意义的。这里值得一提。没有一个完美的方法来选择一个问题应该有多少个隐藏节点。事实上,还没有一种选择隐藏层数的完美方法。现在,最好的方法是进行实验,直到找到要解决的问题的良好配置。

测试网络

  现在我们已经对网络进行了训练,至少在100个记录的一小部分上,我们想测试这个工作的效果。 我们这样做是针对第二个数据集,即训练数据集。我们首先需要获得测试数据,Python代码与用于获取培训数据的代码非常相似。

# 加载测试集
# load the mnist test data CSV file into a list
test_data_file = open(r"E:\keras\sample\famous_data\mnist_test_10.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])
    # 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)
    # 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

  我们训练了我们的神经网络,使之能对训练样本合理的给出输出,但它对未参与训练的测试集样本的表现如何呢?这也成为神经网络(或者机器学习)中所说的泛化能力。能够正确地分类以前没有看到的手写字符,这就是人工智能的关键!

代码解释:在我们进入通过所有测试数据集记录工作的循环之前,我们创建一个空列表,称为记分卡,它将是我们在每个记录之后更新的记分卡。你可以看到,在循环中,我们做了以前的工作,我们用逗号分隔文本记录以分离出值。我们保留第一个值的注释作为正确的答案。我们截取剩余的值并重新调整它们,使之适用于输入神经网络,再将神经网络的响应保留在称为输出的变量中。接下来,我们知道最大值的输出节点是网络认为的答案,该节点的索引,即其位置对应于标签(第一个元素对应于标签“0”,第五个元素对应于标签“4”,依此类推)。有一个方便的numpy函数可以找到数组中最大的值,并告诉我们它的位置numpy.argmax()。如果返回0,我们知道网络认为答案为零,等等。最后一位代码将标签与已知的正确标签进行比较。如果它们相同,则在记分卡上附加“1”,否则附加“0”。

  在代码中包含了一些有用的print()命令,以便我们可以看到正确和预测的标签。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值