前一章介绍了机器学习工作原理的基础知识。您了解了如何开始使用神经网络编程来将数据与标签匹配,以及如何从中推断可用于区分项的规则。一个合乎逻辑的下一步是将这些概念应用到计算机视觉中,在那里我们将有一个模型学习如何识别图片中的内容,以便它可以“看到”其中的内容。在这个章节中,您将使用一个流行的服装项目数据集,并建立一个可以区分它们的模型,从而“看到”不同类型的服装之间的差异。
识别服装项目
对于我们的第一个例子,让我们考虑一下如何识别图像中的衣服。例如,考虑图2-1中的项目。
![](https://i-blog.csdnimg.cn/blog_migrate/f42ed042f7e8ae525ebc6c1958284c26.png)
这里有许多不同的衣服,你可以认出来。你知道什么是衬衫、外套或连衣裙。但你如何向一个从未见过服装的人解释这一点呢?鞋子怎么样?这张图片中有两只鞋,但是你如何向某人描述它?这是我们在第1章中谈到的基于规则的编程可能失败的另一个领域。有时候用规则来描述一件事是不可行的。
当然,计算机视觉也不例外。但是想想你是如何学会识别所有这些物品的——通过看许多不同的例子,并获得如何使用它们的经验。我们能用电脑做同样的事情吗?答案是肯定的,但有局限性。让我们来看第一个例子,如何教计算机识别倪泽的服装,使用一个著名的数据集叫做时尚MNIST。
数据—Fashion Mnist
学习和基准算法的基础数据集之一是由安·勒肯、科琳娜·科尔特斯和克里斯多佛·伯格斯创建的MNIST数据库。该数据集由0到9的70,000个手写数字组成。图像为28 × 28灰度。
Fashion MNIST旨在取代MNIST,后者拥有相同数量的记录、相同的图像尺寸和相同数量的类别——因此,MNIST时尚包含10种不同类型服装的图像,而不是数字0到9的图像。您可以在图2-2中看到数据集内容的一个例子。这里,每种服装都有三行
![](https://i-blog.csdnimg.cn/blog_migrate/a817c24681c4360729c5ee33936b2511.png)
它有各种各样的衣服,包括衬衫、裤子、连衣裙和各种各样的鞋子。正如你可能注意到的,它是单色的,所以每张图片由一定数量的像素组成,像素值在0到255之间。这使得数据集更易于管理。
你可以从图2-3的数据集中看到一个特定图像的特写。
像任何图像一样,它是一个矩形像素网格。在这种情况下,网格大小为28 × 28,每个像素只是一个0到255之间的值,如前所述。现在让我们看看如何将这些像素值用于我们之前看到的函数。
视觉神经元-Neurons for Vision
在第1章中,您看到了一个非常简单的场景,其中一台机器被赋予了一组X和Y值,它了解到这些值之间的关系是Y = 2X–1。这是用一个非常简单的单层单神经元神经网络完成的。
如果你在可视化的绘制他,它可能看起来像图2-4。
![](https://i-blog.csdnimg.cn/blog_migrate/4e9b165e6b8e62c00e1eff0c12430839.png)
我们的每个图像都是一组介于0和255之间的784个值(28 × 28)。它们可以是我们的x。我们知道我们的数据集中有10种不同类型的图像,所以让我们把它们看作是我们的Y。现在我们想知道Y是x的函数时,函数是什么样子的。
假设我们每个图像有784个X值,我们的Y将在0到9之间,很明显,我们不能像前面那样做Y = mX + c。
但是我们能做的是让几个神经元一起工作。每一个都将学习参数,当我们有一个所有这些参数一起工作的组合函数时,我们可以看到我们是否可以将该模式与我们想要的答案相匹配(图2-5)。
![](https://i-blog.csdnimg.cn/blog_migrate/75c2c2717dc4dd58c2f52a214e4eee5f.png)
这个图表顶部的框可以被认为是图像中的像素,或者我们的X值。当我们训练神经网络时,我们将它们加载到一层神经元中——图2-5显示它们刚刚被加载到第一个神经元中,但是值被加载到它们中的每一个中。考虑每个神经元的权重和偏差(m和c)随机初始化。然后,当我们总结每个神经元的输出值时,我们会得到一个值。这将对输出层中的每个神经元进行,因此神经元0将包含像素加起来等于标签0的概率值,因此神经元1将包含像素加起来等于标签1的概率值,等等。
随着时间的推移,我们希望将该值与期望的输出相匹配——在这张图像中,我们可以看到数字9,即图2-3所示的踝靴标签。换句话说,这个神经元应该是所有输出神经元中最大的。
假设有10个标签,随机初始化应该有10%的时间得到正确的答案。从那以后,损失函数和优化器可以逐个时期地调整每个神经元的内部参数,以提高10%。因此,随着时间的推移,计算机将学会“看到”是什么让一只鞋就是鞋子或一件衣服就是衣服。
设计神经网络—Designing the Neural Network
现在让我们探索一下这在代码中是什么样子的。首先,我们来看看图2-5所示的神经网络的设计:
model = tf.keras.models.Sequential([tf.keras.layers.Flatten(input_shape=(28,28)),
tf.keras.layers.Dense(128, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)])
如果你还记得,在第一章中,我们有一个Sequential模型来指定我们有许多层。它只有一层,但在这种情况下,我们有多层。
第一个,Flatten扁平化层,不是神经元层,而是输入层规范。我们的输入是28 × 28的图像,但我们希望它们被视为一系列数值,如图2-5顶部的灰色方框。Flatten展平取“平方”的值(一个2D数组),并将其变成一条线(一个1D数组)。
下一个,Dense层,是神经元层,我们指定要128个神经元。这是如图2-5所示的中间层。你会经常听到这样的层被称为隐藏层。调用者看不到输入和输出之间的层,因此使用术语“隐藏”来描述它们。我们要求128个神经元随机初始化它们的内部参数。我经常会被问到的问题是“为什么是128?”这完全是随意的——神经元的数量并没有固定的规则。在设计层时,您希望选择适当数量的值,以使模型能够实际学习。更多的神经元意味着它会运行得更慢,因为它需要学习更多的参数。更多的神经元也可能导致一个在识别训练数据方面很出色的网络,但在识别之前没有见过的数据方面却不那么出色(这被称为过拟合,图2-6简单对比欠拟合和过拟合,我们将在本章后面讨论它)。另一方面,更少的神经元意味着模型可能没有足够的参数来学习。
![](https://i-blog.csdnimg.cn/blog_migrate/41fd26f6a51ea089d916ea44bf40fe56.png)
hyperparameter tuning-超参数调整
随着时间的推移,需要一些实验来选择正确的值。这个过程通常称为超参数调整。在机器学习中,超参数是用于控制训练的值,而不是被训练/学习的神经元的内部值,后者被称为参数。
activation-激活函数
神经元与激活函数
![](https://i-blog.csdnimg.cn/blog_migrate/e6bf6f248d421ab99767897662f44be1.png)
激活函数在神经元中非常重要的。为了增强网络的表示能力和学习 能力,激活函数需要具备以下几点性质:
(1)连续并可导(允许少数点上不可导)的非线性函数.可导的激活函数 可以直接利用数值优化的方法来学习网络参数。
(2)激活函数及其导函数要尽可能的简单,有利于提高网络计算效率。
(3)激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太 小,否则会影响训练的效率和稳定性。
你可能会注意到,在那个层中还指定了一个激活函数。激活函数是在层中的每个神经元上执行的代码。TensorFlow许多激活函数,但是中间层非常常见的一个是relu(Rectified Linear Unit)。这是一个简单的函数,如果大于0就返回值。在这种情况下,我们不希望负值被传递到下一层,从而潜在地影响求和函数,所以我们可以简单地用relu激活该层,而不是编写大量if-then代码。
![](https://i-blog.csdnimg.cn/blog_migrate/9a844fbe277e51f961545e8377748837.png)
最后,还有另一个Dense层,即输出层。这个有10个神经元,因为我们有10个类。这些神经元中的每一个都会以输入像素与该类匹配的概率结束,因此我们的工作是确定哪一个具有最高值。我们可以在它们之间循环来选择那个值,但是softmax激活功能为我们做到了。
现在,当我们训练神经网络时,目标是我们可以输入一个28×28像素阵列,中间层的神经元将具有权重和偏差(m和c值),当组合时,这些像素将与10个输出值之一匹配。
完整代码(The Complete Code)
现在我们已经探索了神经网络的体系结构,让我们来看看与Fashion Mnist数据训练的完整代码:
import tensorflow as tf
data=tf.keras.datasets.fashion_mnist
(training_images,training_labels),(test_images,test_labels)=data.load_data()
training_images=training_images/255
test_images=test_images/255
model=tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28,28]),
tf.keras.layers.Dense(128,activation=tf.nn.relu),
tf.keras.layers.Dense(10,activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(training_images,training_labels,epochs=5)
让我们逐一浏览这段代码,首先是用于访问数据的方便快捷方式
data=tf.keras.datasets.fashion_mnist
Keras拥有许多内置数据集,可以使用这样的单一代码访问。在这种情况下,您不必处理将70,000图像下载到训练和测试集中,依此类推是一行代码。使用Tensorflow DataSets的API后,这种方法已经改进,但是对于这些早期章节的目的,为了减少所需的新概念的数量,我们只需使用tf.keras.datasets.
我们可以调用其Load_Data方法来返回我们的培训和测试集:
(training_images,training_labels),(test_images,test_labels)=data.load_data()
Fashion Mnist旨在拥有60,000个培训图像和10,000个测试图像。因此,来自data.load_data的返回将为您提供60,000个28×28像素阵列的数组,称为training_images,以及training_labels的60,000个值(0-9)的数组。同样,test_Images阵列将包含10,000 28×28像素阵列,test_Labels数组将包含在0到9之间的10,000个值。
我们的工作将以类似的方式将培训图像与训练标签相适合,像第一章中那样。
我们将备注测试图像和测试标签,以便网络在培训时没有看到它们。这些可用于测试网络与迄今看不见的数据的功效。
归一化图像
下一行代码可能看起来有点不寻常:
training_images=training_images/255
test_images=test_images/255
Python允许您使用此表示法在整个数组上进行操作。回想一下,我们的图像中的所有像素都是灰度,值在0到255之间。这样可以确保每个像素由0到1之间的数字表示。该过程称为归一化图像。
为什么标准化数据的数学更好地训练神经网络超出了本书的范围,但在训练统计化中的神经网络时,请记住统计化将提高性能。通常,您的网络不会学习,并且在处理非标准化数据时会有大规模错误。来自第1章的Y = 2x - 1示例不需要将数据归一化,因为它非常简单,但有趣的尝试用不同的x和y值训练它,其中x更大,你会很快看到它失败!
接下来,我们定义了构成我们模型的神经网络,如前所述:
model=tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28,28]),
tf.keras.layers.Dense(128,activation=tf.nn.relu),
tf.keras.layers.Dense(10,activation=tf.nn.softmax)
])
当我们编译模型时,我们像之前那样指定损失函数和优化器:
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
损失函数
在这种情况下,损失函数称为稀疏分类交叉熵(sparse_categorical_crossentropy),并且它是构建在Tensorflow中的损失函数的库中的一个。同样,选择使用哪种损失函数本身就是艺术,随着时间的推移,您将了解哪些方案最佳使用。这个模型与我们在第1章中创建的一个主要区别是,这里我们正在挑选/确定一个类别,而不是我们试图预测一个数字,我们的服装物品将属于10个类别的服装中的1种,因此使用分类损失函数(categorical loss function)是需要的。稀疏的分类交叉熵是一个不错的选择。
优化器
同样适用于选择优化器。 ADAM优化器是我们在第1章中使用的随机梯度下降(SGD)优化器的演变,其已被证明更快,更高效。随着我们处理60,000次培训图像,我们可以获得的任何绩效改进都会有所帮助,因此这里被选中。
准确率
您可能会注意到,在此代码中也存在指定要报告的度量标准的新行。在这里,我们想在我们训练时报告网络的准确性。第1章的简单示例报告了损失,我们解释了网络通过查看损失如何减少而学习。在这种情况下,我们可以通过查看网络是如何学习的,在这种情况下,通过查看准确性 - 它将如何返回到输出标签将输入像素正确匹配的频率,更有用。
接下来,我们将通过将training_images和training_labels进行拟合5个批次来训练网络:
model.fit(training_images, training_labels, epochs=5)
这里是训练结果:
最后,我们可以使用单行代码做一些新的事情-评估模型。我们有一组10,000个图像和标签进行测试,我们可以将它们传递给已训练好的模型,以使其预测每个图像都认为它是什么,将其与其实际标签进行比较,并总结结果:
model.evaluate(test_images, test_labels)
以下是模型评估结果:
训练神经网络(Training the Nerual Network)
执行代码,你将看到逐批次的训练网络,训练完成之后,你将看到类似的结果:
请注意,它现在报告准确性。因此,在这种情况下,使用训练数据,我们的模型在仅进行5个批次的训练后准确率已有89%。
但测试数据怎么样?模型的结果。在我们的测试数据上如此:
在这种情况下,模型的准确性为87.36%,考虑到我们只培训了五个批次,这也不错。
你可能想知道为什么测试数据的准确性比训练数据低。这是非常常见的,当你考虑它时,它是有意义的:神经网络只真正知道如何将它所训练的输入与那些值的输出相匹配。我们的希望是,如果有足够的数据,它将能够从它所看到的例子中进行归纳,“了解”一只鞋或一件衣服是什么样子的。但总会有一些它没见过的东西与它混淆的东西完全不同的例子。
例如,如果你在成长过程中只见过运动鞋,那就是你眼中的鞋子,当你第一次看到高跟鞋时,你可能会有点困惑。从你的经验来看,大概是一只鞋,但你也不确定。这是一个类似的概念。
探索模型输出(Exploring the Model Output)
现在模型已经训练好了,并且我们已经使用测试集很好地测量了它的精度,让我们稍微探索一下:
classifications = model.predict(test_images)
print(classifications[0])
print(test_labels[0])
结果是:
你会注意到分类给了我们一系列的值。这是10个输出神经元的值。标签是服装的实际标签,在本例中为9。看看数组——你会发现有些值非常小,最后一个(数组索引9)是目前为止最大的。这些是图像匹配特定索引处标签的概率。因此,神经网络报告的是,索引0处的服装项目有91.4%的可能性是标签9。我们知道它是标签9,所以它是对的。
自己尝试一些不同的值,看看你是否能找到模型出错的地方。
更长时间的训练——发现过拟合
在这种情况下,我们只训练了五个批次。也就是说,我们遍历了整个训练循环,随机初始化神经元,对照它们的标签进行检查,用损失函数来衡量性能,然后由优化器更新五次。我们得到的结果非常好:训练集的准确率为89%,测试集的准确率为87%。那么如果我们训练的时间更长会发生什么呢?试着更新一下,训练50个纪元,而不是5个。
在我的例子中,我在训练集上得到了这些精确的数字:
这尤其令人兴奋,因为我们做得更好:96.27%的准确率。对于测试集,我们达到88.6%:
因此,我们在训练集上有了很大的改进,在测试集上有了较小的改进。这可能意味着对我们的网络进行更长时间的培训会产生更好的结果——但情况并非总是如此。网络在训练数据上做得更好,但它不一定是一个更好的模型。事实上,准确率的差异表明它已经过度专用于训练数据,这一过程通常被称为过拟合。当你建立更多的神经网络时,这是一件值得注意的事情,当你阅读这本书时,你会学到许多技巧来避免它。
停止训练(Stopping Training)
到目前为止,在每一个案例中,我们已经硬编码了我们所训练的时代的数量。虽然这是可行的,但我们可能希望训练直到达到所需的精度,而不是不断尝试不同数量的时代、训练和再训练,直到达到所需的值。
例如,如果我们想训练直到模型在训练集上达到95%的准确率,而不知道需要多少个时代,我们怎么能做到呢?最简单的方法是在训练中使用回调。让我们看看使用回调的更新代码:
import tensorflow as tf
class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if(logs.get('accuracy')>0.95):
print("\nReached 95% accuracy so cancelling training!")
self.model.stop_training = True
callbacks = myCallback()
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images/255.0
test_images=test_images/255.0
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(training_images, training_labels, epochs=50, callbacks=[callbacks]
让我们看看我们改变了什么。首先,我们创建了一个名为myCallback的新类。这需要一个tf.keras.callbacks.callback作为参数。在其中,我们定义了on_epoch_end函数,它将为我们提供有关该epoch日志的详细信息。在这些日志中是一个精度值,所以我们要做的就是看它是否大于0.95(或95%);如果是的话,我们可以通过self.model.stop_training = True停止训练。
一旦我们指定了这个,我们就创建一个回调对象作为我的回调函数的实例。
现在看看model.fit语句。您将会看到,我已经对它进行了更新,以适应50个时代,然后添加了一个回调参数。为此,我传递回调对象。
训练的时候,在每个批次结束的时候,都会调用回调函数。因此,在每个时期结束时,您将进行检查,在大约34个批次后,您将看到您的训练将结束,因为训练达到了95%的准确性(由于最初的随机初始化,您的数字可能略有不同,但很可能接近34):
总结(Summary)
在第1章中,您学习了机器学习是如何通过与神经网络的复杂模式匹配将特征与标签相匹配的。在这一章中,你将这一点提升到了一个新的水平,超越了单个神经元,并学习了如何创建你的第一个(非常基础的)计算机视觉神经网络。由于数据的原因,它有些有限。所有的图像都是28 × 28灰度,衣服项居中。这是一个好的开始,但这是一个非常可控的场景。为了在视觉方面做得更好,我们可能需要计算机来学习图像的特征,而不仅仅是原始像素。
我们可以通过一个叫做卷积的过程来实现。你将在下一章学习如何定义卷积神经网络来理解图像的内容。