上一篇:《教电脑“看”图片》
本篇序言: 本文介绍的计算机视觉,简单来说,是让计算机通过算法从图像中提取特征,并使用这些特征来表示图像中的各种元素及它们的相互关系,从而实现“看”的效果。这种方法在早期主要依赖于传统的机器学习(ML)技术,尤其是监督学习,在这种方法中,人类需要手动提供标注数据和设计特定的特征提取方式。
然而,自从Transformer模型和注意力机制的出现,尤其是自2017年谷歌发布的“Attention is All You Need”论文以来,无监督学习和自监督学习得到了极大的关注。这些方法可以在无需大量人工标注的情况下让模型自动学习特征,并通过对齐人类的感知方式来进行图像分析。这不仅显著提高了计算机视觉任务的准确性,还推动了该领域的前沿发展。如今,基于Transformer的模型,如Vision Transformers (ViT),在许多图像识别任务中已经能够超越人类的速度和精度。
本系列文章介绍的内容涵盖了人工智能领域的重要基础知识,掌握这些知识将帮助您更好地理解和设计AI技术。欢迎关注作者,期待与您一起探索更多AI的前沿内容!关注后,您的智慧将更上一层楼,不然就只能靠颜值取胜了哦! ^_^
用于视觉的神经元
在第一章中,你看到了一个非常简单的场景,机器被给了一组X和Y值,它学会了这些值之间的关系是Y = 2X - 1。这是通过一个非常简单的神经网络完成的,只有一层和一个神经元。
如果你将其可视化绘制出来,它可能看起来像图2-4。
我们的每张图像是一组784个值(28×28),这些值介于0到255之间。它们可以作为我们的X。我们知道数据集中有10种不同类型的图像,所以我们将它们视为我们的Y。现在我们想要学习一个函数的形式,在这个函数中,Y是X的函数。
鉴于每张图像有784个X值,而我们的Y将在0到9之间,显然我们不能像之前那样简单地用Y = mX + c来解决问题。
但我们可以让多个神经元一起工作。每个神经元将学习参数,当我们把所有这些参数结合起来形成一个综合函数时,我们可以看看是否能够将该模式与我们期望的答案匹配(见图2-5)。
这个图表顶部的方框可以视为图像中的像素,或者说是我们的X值。当我们训练神经网络时,会将这些值加载到神经元层中——图2-5展示的是这些值仅被加载到第一个神经元中,但实际上它们会被加载到每个神经元中。每个神经元的权重和偏差(m 和 c)被随机初始化。然后,当我们将每个神经元的输出值相加时,我们会得到一个值。这将在输出层的每个神经元中进行,因此,神经元0会包含像素相加为标签0的概率值,神经元1对应标签1,依此类推。
随着时间的推移,我们希望将这个值与期望的输出匹配——在这个图像中,我们可以看到期望的输出是数字9,也就是图2-3中展示的短靴的标签。换句话说,这个神经元的值应该是所有输出神经元中最大的。考虑到有10个标签,随机初始化应能大约在10%的时间里得到正确答案。从这一点开始,损失函数和优化器将会逐个迭代地调整每个神经元的内部参数,以提高那10%的正确率。于是,随着时间的推移,计算机将学会“看出”一只鞋是鞋、一件连衣裙是连衣裙的特征。
设计神经网络
现在让我们看看这在代码中是如何实现的。首先,我们将观察图2-5中展示的神经网络的设计:
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation=tf.nn.relu),
keras.layers.Dense(10, activation=tf.nn.softmax)
])
如果你还记得,在第1章中我们使用了Sequential模型来指定我们有多个层。当时只有一层,但在这里我们有多层。
首先,Flatten并不是一层神经元,而是一个输入层的规范。我们的输入是28×28的图像,但我们希望将它们视为一系列数值,就像图2-5顶部的灰色方块那样。Flatten会将这个“方形”值(即二维数组)转换成一条“线”(一维数组)。
接下来是Dense,它是一层神经元,并且我们指定了128个神经元。这是图2-5中间展示的那一层。你经常会听到这种层被称为隐藏层。处于输入和输出之间的层对调用者是不可见的,因此用“隐藏”来形容它们。我们要求128个神经元,并将它们的内部参数随机初始化。通常在这一点上,我会被问到“为什么是128个?”这完全是任意的——没有固定的规则规定应该使用多少个神经元。在设计层时,你需要选择合适的数量,以使模型能够真正学习。更多的神经元意味着模型运行得更慢,因为它需要学习更多的参数。更多的神经元还可能导致网络对训练数据表现很好,但对之前没见过的数据表现不佳(这被称为过拟合,我们将在本章稍后讨论)。另一方面,神经元太少可能会导致模型没有足够的参数来学习。
选择正确的值需要一些实验。这一过程通常称为超参数调优。在机器学习中,超参数是控制训练的值,而不是神经元内部的被训练/学习的值,这些内部值被称为参数。
你可能注意到在这一层还指定了一个激活函数。激活函数是在层中的每个神经元上执行的代码。TensorFlow支持许多激活函数,但在中间层中非常常见的一个是relu,它代表修正线性单元。它是一个简单的函数,只要返回大于0的值。在这种情况下,我们不希望负值传递到下一层,可能会影响求和函数,因此我们不需要写大量的if-then代码,只需通过relu激活该层即可。
最后是另一个Dense层,即输出层。这一层有10个神经元,因为我们有10个类别。每个神经元最终会输出一个概率值,表示输入的像素与该类别匹配的概率,因此我们的任务是确定哪个神经元的值最大。我们可以循环遍历这些神经元来选出那个值,但softmax激活函数为我们做了这件事。
现在,当我们训练神经网络时,目标是能够输入一个28×28像素的数组,中间层的神经元将具有权重和偏差(m和c值),当它们结合时,这些像素将被匹配到10个输出值之一。
完整代码
现在我们已经探讨了神经网络的架构,接下来让我们看看如何用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.0
test_images = test_images / 255.0
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 有许多内置的数据集,你可以通过这样一行代码访问它们。在这种情况下,你不必处理下载7万张图片、将它们划分为训练集和测试集等繁琐步骤——只需要一行代码。这种方法已经通过一个名为 TensorFlow Datasets 的 API 得到了改进,但为了减少你在这些早期章节中需要学习的新概念,我们仅使用 tf.keras.datasets。
我们可以调用它的 load_data 方法来返回我们的训练集和测试集,如下所示:
(training_images, training_labels),
(test_images, test_labels) = data.load_data()
Fashion MNIST 设计为包含6万张训练图像和1万张测试图像。因此,data.load_data 返回的是一个包含6万张28×28像素图像的数组,名为 training_images,以及一个包含6万个值(0-9)的数组,名为 training_labels。类似地,test_images 数组将包含1万张28×28像素的图像,而 test_labels 数组将包含1万个值,范围在0到9之间。
我们的任务是将训练图像与训练标签相匹配,类似于我们在第1章中将 Y 与 X 相匹配的方式。
我们将保留测试图像和测试标签,这样网络在训练时不会看到它们。这些数据将用于测试网络在面对未见过的数据时的效果。
接下来的代码可能看起来有点不寻常:
training_images = training_images / 255.0
test_images = test_images / 255.0
Python 允许你通过这种符号对整个数组进行操作。请回想一下,我们的图像中的所有像素都是灰度值,范围在0到255之间。除以255确保每个像素都用0到1之间的数值表示。这一过程称为图像归一化。
为什么归一化的数据对训练神经网络更好,这里的数学原理超出了本书的范围,但请记住,在 TensorFlow 中训练神经网络时,归一化会提高性能。当处理未归一化的数据时,你的网络往往无法学习,并且会出现巨大的误差。在第1章的 Y = 2X – 1 例子中,我们不需要归一化数据,因为它非常简单,但出于好奇,你可以尝试用更大的 X 和 Y 值来训练它,你会看到它很快就会失败!
接下来我们定义构成模型的神经网络,正如前面讨论的那样:
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'])
在这种情况下,损失函数称为稀疏分类交叉熵,这是 TensorFlow 内置的众多损失函数之一。选择哪个损失函数本身就是一门艺术,随着时间的推移,你会学会在不同的场景中使用哪个最合适。与我们在第1章中创建的模型的主要区别在于,这次我们不是预测一个单一的数值,而是选择一个类别。我们的服装物品将属于10种类别之一,因此使用分类损失函数是最合适的选择,稀疏分类交叉熵就是一个很好的选择。
同样,选择优化器也是如此。adam 优化器是我们在第1章中使用的随机梯度下降(sgd)优化器的进化版,它被证明更快、更高效。由于我们要处理60,000张训练图像,任何性能的提升都会有所帮助,因此这里选择了它。
你可能会注意到代码中还指定了一个新的行,说明了我们想要报告的指标。在这里,我们希望在训练时报告网络的准确性。在第1章的简单示例中,我们只报告了损失,通过观察损失的减少来推断网络在学习。而在本例中,观察网络的学习过程,通过准确性来反馈,显然对我们更有帮助——它将返回网络正确匹配输入像素和输出标签的频率。
接下来,我们将通过将训练图像与训练标签匹配,进行五个周期的训练:
model.fit(training_images, training_labels, epochs=5)
最后,我们可以做一些新的操作——用一行代码评估模型。我们有一组1万张图像和标签用于测试,我们可以将它们传递给训练好的模型,让它预测每张图像是什么,比较预测结果与实际标签,并总结结果:
model.evaluate(test_images, test_labels)
训练神经网络
执行代码,你将看到网络逐个周期地进行训练。训练结束后,你会看到类似于以下的输出:
58016/60000 [=====>.] - ETA: 0s - loss: 0.2941 - accuracy: 0.8907
59552/60000 [=====>.] - ETA: 0s - loss: 0.2943 - accuracy: 0.8906
60000/60000 [] - 2s 34us/sample - loss: 0.2940 - accuracy: 0.8906
请注意,现在它正在报告准确性。在这种情况下,使用训练数据后,我们的模型经过五个周期后达到了约89%的准确性。
那么测试数据呢?使用 model.evaluate 对测试数据的结果可能会是这样的:
10000/1 [====] - 0s 30us/sample - loss: 0.2521 - accuracy: 0.8736
在这种情况下,模型在测试数据上的准确性为87.36%,考虑到我们只训练了五个周期,这已经算不错了。
你可能会想,为什么测试数据的准确性比训练数据低?这是非常常见的现象,而且仔细想想也是合理的:神经网络实际上只知道如何匹配它见过的输入数据与对应的输出值。我们希望通过足够的数据,它能从它见过的例子中进行泛化,“学习”到鞋子或连衣裙的外观。但总会有它没见过的足够不同的物品让它困惑的情况。
例如,如果你从小只见过运动鞋,运动鞋就是你眼中的鞋子,那么当你第一次看到高跟鞋时,可能会有点困惑。从你的经验来看,它可能是一双鞋,但你并不能百分之百确定。这就是类似的概念。
探索模型输出
现在模型已经训练完毕,并且我们通过测试集有了一个对其准确性的良好估计,让我们稍微探索一下:
classifications = model.predict(test_images)
print(classifications[0])
print(test_labels[0])
通过将测试图像传递给 model.predict,我们将得到一组分类结果。然后让我们看看打印出的第一个分类结果,并将其与测试标签进行比较:
[1.9177722e-05 1.9856788e-07 6.3756357e-07 7.1702580e-08 5.5287035e-07
1.2249852e-02 6.0708484e-05 7.3229447e-02 8.3050705e-05 9.1435629e-01]
你会注意到分类结果给我们返回了一组数值。这些数值是10个输出神经元的值。标签是该服装物品的实际标签,在这种情况下是9。看看这个数组——你会发现有些值非常小,而最后一个值(数组索引为9的那个)远远是最大的。这些是图像匹配某个特定索引标签的概率。因此,神经网络报告的是,这件服装有91.4%的可能性对应标签9。我们知道它的标签是9,所以它答对了。
尝试使用不同的值,看看是否能找到模型出错的地方。
更长时间的训练——发现过拟合
在本例中,我们只训练了五个周期。也就是说,我们对神经元的随机初始化、与其标签的对比、由损失函数测量性能、并由优化器更新参数的整个训练循环进行了五次。而我们得到的结果相当不错:训练集上的准确性为89%,测试集上为87%。那么如果我们训练更长时间会发生什么呢?
尝试将训练周期数改为50。在我的案例中,我得到了以下训练集的准确率:
58112/60000 [==>.] - ETA: 0s - loss: 0.0983 - accuracy: 0.9627
59520/60000 [==>.] - ETA: 0s - loss: 0.0987 - accuracy: 0.9627
60000/60000 [====] - 2s 35us/sample - loss: 0.0986 - accuracy: 0.9627
这是特别令人兴奋的,因为我们做得好多了:96.27%的准确性。测试集上达到了88.6%的准确率:
[====] - 0s 30us/sample - loss: 0.3870 - accuracy: 0.8860
所以,我们在训练集上有了很大的改进,测试集上也有所提升。这可能暗示着,训练网络更长时间会带来更好的结果——但这并不总是如此。网络在训练数据上表现得更好,但这并不意味着它是一个更好的模型。事实上,准确率数字的差异表明它已经过度专门化于训练数据,这一过程通常被称为过拟合。在构建更多神经网络时,这是一件需要注意的事情,在本书的后续章节中,你将学到一些避免这种情况的技术。
到目前为止,我们在每种情况下都硬编码了训练的周期数。虽然这样可以工作,但我们可能希望训练直到达到期望的准确率,而不是反复尝试不同的周期数,进行训练和重新训练,直到得到我们想要的值。那么,比如说,我们想要训练直到模型在训练集上的准确率达到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 函数,该函数将为我们提供该周期的日志信息。这些日志中有一个准确率值,因此我们只需查看该值是否大于0.95(即95%);如果是,我们可以通过设置 self.model.stop_training = True 来停止训练。
一旦我们指定了这一点,我们创建一个 callbacks 对象作为 myCallback 函数的实例。
现在看看 model.fit 语句。你会发现我将其更新为训练50个周期,然后添加了一个 callbacks 参数。在这里,我传递了 callbacks 对象。当进行训练时,在每个周期结束时,回调函数将被调用。所以在每个周期结束时你都会检查,经过大约34个周期后,你会看到训练结束,因为训练已经达到了95%的准确率(你的数字可能会因为初始的随机初始化而略有不同,但可能会接近34):
56896/60000 [====>..] - ETA: 0s - loss: 0.1309 - accuracy: 0.9500
58144/60000 [====>.] - ETA: 0s - loss: 0.1308 - accuracy: 0.9502
59424/60000 [====>.] - ETA: 0s - loss: 0.1308 - accuracy: 0.9502
Reached 95% accuracy so cancelling training!
总结
在第1章中,你学到了机器学习如何通过神经网络的复杂模式匹配将特征与标签相匹配。在本章中,你更进一步,超越了单个神经元,学会了如何创建你的第一个(非常基础的)计算机视觉神经网络。由于数据的限制,它有些局限。所有图像都是28×28的灰度图像,服装物品位于画面中央。这是一个很好的开始,但它是一个非常受控的场景。为了在视觉识别上做得更好,我们可能需要让计算机学习图像的特征,而不仅仅是原始像素。
我们可以通过卷积操作来实现这一点。在下一章中,你将学习如何定义卷积神经网络来理解“看懂”图片的内容。
标签:training,揭开,训练,images,面纱,tf,视觉,我们,神经元 From: https://blog.csdn.net/JellyAI/article/details/143140990