如何在 Keras 中使用小实验开发图像标题生成模型

图像描述生成是一个具有挑战性的人工智能问题,需要为照片生成文本描述。

它需要计算机视觉方法来理解图像内容,还需要自然语言处理领域的语言模型将图像理解转化为正确顺序的词语。最近,深度学习方法在此类问题上取得了最先进的成果。

开发自己的图像描述生成模型可能很困难,主要是因为数据集和模型都非常庞大,需要数天才能训练完成。一种替代方法是使用完整数据集的一小部分样本来探索模型配置。

在本教程中,您将了解如何使用标准照片描述数据集的一小部分样本来探索不同的深度模型设计。

完成本教程后,您将了解:

  • 如何为照片描述建模准备数据。
  • 如何设计基线和测试工具来评估模型的技能并控制其随机性。
  • 如何评估模型技能、特征提取模型和词嵌入等属性,以提升模型技能。

通过我的新书《深度学习自然语言处理》**启动您的项目**,其中包括所有示例的**分步教程**和 **Python 源代码**文件。

让我们开始吧。

  • 2019 年 4 月/2 月:提供了 Flickr8k_Dataset 数据集的直接链接,因为官方网站已下线。
How to Use Small Experiments to Develop a Caption Generation Model in Keras

如何在 Keras 中使用小实验开发图像标题生成模型
照片由 Per 拍摄,保留部分权利。

教程概述

本教程分为6个部分;它们是

  1. 数据准备
  2. 基线描述生成模型
  3. 网络大小参数
  4. 配置特征提取模型
  5. 词嵌入模型
  6. 结果分析

Python 环境

本教程假设您已安装 Python SciPy 环境,最好是 Python 3。

您必须安装 Keras(2.0 或更高版本),并使用 TensorFlow 或 Theano 后端。

本教程还假设您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。

如果您需要环境方面的帮助,请参阅本教程

我建议在具有 GPU 的系统上运行代码。

您可以在 Amazon Web Services 上廉价访问 GPU。在本教程中了解如何操作

让我们开始吧。

需要深度学习处理文本数据的帮助吗?

立即参加我的免费7天电子邮件速成课程(附代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

数据准备

首先,我们需要准备数据集以训练模型。

我们将使用 Flickr8K 数据集,它包含 8,000 多张照片及其描述。

您可以从此处下载数据集

更新(2019 年 4 月):官方网站似乎已下线(尽管表格仍然有效)。以下是我 GitHub 存储库中一些直接下载链接

将照片和描述分别解压缩到当前工作目录的 `Flicker8k_Dataset` 和 `Flickr8k_text` 目录中。

数据准备分为两个部分:

  1. 准备文本
  2. 准备照片

准备文本

数据集包含每张照片的多个描述,并且描述文本需要进行一些基本的清理。

首先,我们将加载包含所有描述的文件。

每张照片都有一个唯一的标识符。此标识符用于照片文件名和描述文本文件中。接下来,我们将遍历照片描述列表,并保存每张照片的第一个描述。下面定义了一个名为 `load_descriptions()` 的函数,该函数在给定已加载文档文本的情况下,将返回一个从照片标识符到描述的字典。

接下来,我们需要清理描述文本。

描述已被分词,易于处理。我们将以下列方式清理文本,以减少我们需要处理的词汇量:

  • 将所有单词转换为小写。
  • 删除所有标点符号。
  • 删除所有长度为一个字符或更短的单词(例如“a”)。

下面定义了 `clean_descriptions()` 函数,该函数给定图像标识符到描述的字典,遍历每个描述并清理文本。

最后,我们将图像标识符和描述的字典保存到一个名为 `descriptions.txt` 的新文件中,每行一个图像标识符和描述。

下面定义了 `save_doc()` 函数,该函数给定包含标识符到描述映射的字典和文件名,将映射保存到文件。

综合以上所有代码,完整列表如下。

运行示例后,首先会打印加载的照片描述数量 (8,092) 和清理后的词汇表大小 (4,484 个单词)。

清理后的描述随后被写入“`descriptions.txt`”文件。查看文件,我们可以看到描述已准备好进行建模。

查看文件,我们可以看到描述已准备好进行建模。

准备照片

我们将使用预训练模型来解释照片内容。

有许多模型可供选择。在这种情况下,我们将使用牛津视觉几何组 (Oxford Visual Geometry Group) 或 VGG 模型,该模型赢得了 2014 年 ImageNet 竞赛。在此处了解更多有关该模型的信息

Keras 直接提供了这个预训练模型。请注意,第一次使用此模型时,Keras 将从互联网下载模型权重,大约 500 兆字节。这可能需要几分钟,具体取决于您的互联网连接速度。

我们可以将此模型作为更广泛的图像描述模型的一部分。问题是,它是一个大型模型,每次我们想要测试新的语言模型配置(下游)时,都要让每张照片通过网络,这是多余的。

相反,我们可以使用预训练模型预先计算“照片特征”并将其保存到文件中。然后,我们可以在以后加载这些特征,并将它们作为数据集中给定照片的解释输入到我们的模型中。这与将照片通过完整的 VGG 模型运行没有什么不同,只是我们提前完成了一次。

这是一种优化,将使我们的模型训练更快并消耗更少的内存。

我们可以在 Keras 中使用 VGG 类加载 VGG 模型。我们将加载不带顶部的模型;这意味着不带网络末端用于解释从输入中提取的特征并将其转换为类别预测的层。我们对图像的网络分类不感兴趣,我们将训练我们自己对图像特征的解释。

Keras 还提供了将加载的照片重塑为模型首选大小(例如 3 通道 224 x 224 像素图像)的工具。

下面是一个名为 `extract_features()` 的函数,它给定一个目录名,将加载每张照片,为其准备 VGG,并从 VGG 模型中收集预测特征。图像特征是一个形状为 (7, 7, 512) 的 3 维数组。

该函数返回一个从图像标识符到图像特征的字典。

我们可以调用此函数来准备照片数据以测试我们的模型,然后将生成的字典保存到名为“`features.pkl`”的文件中。

完整的示例如下所示。

根据您的硬件情况,此数据准备步骤可能需要一段时间,在配备现代工作站的 CPU 上可能需要一个小时。

运行结束时,您将获得存储在“`features.pkl`”中的提取特征,以备后用。

基线描述生成模型

在本节中,我们将定义一个用于生成照片描述的基线模型,以及如何评估它,以便将其与该基线的变体进行比较。

本节分为 5 个部分

  1. 加载数据。
  2. 拟合模型。
  3. 评估模型。
  4. 完整示例
  5. “A”与“A”测试
  6. 生成照片描述

1. 加载数据

我们不会在所有字幕数据上训练模型,甚至不会在大量数据样本上训练模型。

在本教程中,我们旨在快速测试一套不同配置的字幕模型,以了解在此数据上哪些有效。这意味着模型配置的评估需要快速完成。为此,我们将在 100 张照片和字幕上训练模型,然后在其训练数据集和 100 张照片和字幕的新测试集上进行评估。

首先,我们需要加载预定义的照片子集。提供的数据集有单独的训练集、测试集和开发集,它们实际上只是不同的照片标识符组。我们将加载开发集,并使用前 100 个标识符作为训练集,后 100 个标识符(例如从 100 到 200)作为测试集。

下面的函数 `load_set()` 将加载一组预定义的标识符,我们将以“`Flickr_8k.devImages.txt`”文件名作为参数调用它。

接下来,我们需要将集合分成训练集和测试集。

我们将首先对标识符进行排序,以确保它们在不同机器和运行中始终保持一致的分裂,然后将前 100 个用于训练,接下来的 100 个(例如从 100 到 200)用于测试。

下面的 `train_test_split()` 函数将根据加载的标识符集合创建此划分。

现在,我们可以使用预定义的训练或测试标识符集加载照片描述。

下面是 `load_clean_descriptions()` 函数,它从 `descriptions.txt` 中加载给定标识符集的清理文本描述,并返回一个标识符到文本的字典。

我们将开发的模型将根据一张照片生成一个描述,并且该描述将一次生成一个单词。先前生成的单词序列将作为输入提供。因此,我们需要一个“`first word`”来启动生成过程,以及一个“`last word`”来表示描述的结束。我们将为此目的使用字符串“`startseq`”和“`endseq`”。

接下来,我们可以为给定的数据集加载照片特征。

下面定义了一个名为 `load_photo_features()` 的函数,该函数加载所有照片描述的完整集合,然后返回给定照片标识符集合中感兴趣的子集。这不是很高效,因为加载的所有照片特征字典大约有 700 兆字节。尽管如此,这将使我们能够快速启动和运行。

注意:如果您有更好的方法,请在下面的评论中分享。

我们可以在这里暂停,测试到目前为止开发的所有内容。

完整的代码示例如下所示。

运行此示例首先加载开发数据集中1,000个图片标识符。选择一个训练集和测试集,并用它们来过滤干净的图片描述集和准备好的图像特征。

我们快要完成了。

描述文本需要先编码成数字,然后才能作为输入呈现给模型,或者与模型的预测进行比较。

编码数据的第一步是创建一个从单词到唯一整数值的一致映射。Keras提供了Tokenizer类,可以从加载的描述数据中学习这个映射。

下面定义了`create_tokenizer()`函数,它将根据加载的图片描述文本来拟合一个Tokenizer。

我们现在可以对文本进行编码了。

每个描述都将被分成单词。模型将接收一个单词和图片作为输入,并生成下一个单词。然后,描述的前两个单词将与图片一起作为输入提供给模型,以生成下一个单词。这就是模型训练的方式。

例如,输入序列“小女孩在田野里奔跑”将被分成6个输入-输出对来训练模型:

稍后当模型用于生成描述时,生成的词将被连接起来,并递归地作为输入提供,以生成图像的标题。

下面的`create_sequences()`函数,给定分词器、一个干净的描述、一张照片的特征和最大描述长度,将为模型训练准备一组输入-输出对。调用此函数将返回`X1`和`X2`(用于图像数据和输入序列数据的数组)以及`y`值(用于输出词)。

输入序列被整数编码,输出词被独热编码,以表示预期词在所有可能词汇中的概率分布。

2. 拟合模型

我们快要准备好拟合模型了。

模型的一部分已经讨论过了,但让我们再重申一下。

该模型基于论文“Show and Tell: A Neural Image Caption Generator”(2015年)中提出的示例。

该模型包含三个部分:

  • 图片特征提取器。这是一个在ImageNet数据集上预训练的16层VGG模型。我们已经使用VGG模型(不包括顶部层)对照片进行了预处理,并将使用该模型预测的提取特征作为输入。
  • 序列处理器。这是一个用于处理文本输入的词嵌入层,后跟一个LSTM层。LSTM的输出由一个Dense层一次一个输出地解释。
  • 解释器(暂无更好的名称)。特征提取器和序列处理器都输出一个固定长度的向量,其长度是最大序列的长度。这些向量被连接在一起,然后经过一个LSTM和Dense层处理,最后进行最终预测。

基础模型中使用了保守数量的神经元。具体来说,特征提取器之后是一个128的Dense层,一个50维的词嵌入,接着是一个256单元的LSTM和序列处理器之后一个128神经元的Dense层,最后在网络末端是一个500单元的LSTM,接着是一个500神经元的Dense层。

该模型预测词汇表上的概率分布,因此在拟合网络时使用softmax激活函数并最小化分类交叉熵损失函数。

函数`define_model()`根据词汇表大小和照片描述的最大长度定义了基线模型。Keras函数式API用于定义模型,因为它提供了定义一个接受两个输入流并将其组合的模型所需的灵活性。

为了了解模型的结构,特别是层的形状,请参阅下面的摘要。

我们还创建了一个图来可视化网络结构,这有助于更好地理解两个输入流。

Plot of the Baseline Captioning Deep Learning Model

基线字幕深度学习模型图

我们将使用数据生成器来训练模型。这并非严格要求,因为字幕和提取的照片特征可能作为单个数据集存储在内存中。然而,当您在整个数据集上训练最终模型时,这是一个很好的实践。

生成器在被调用时会产生一个结果。在Keras中,它会产生一个单一批次的输入-输出样本,用于估计误差梯度和更新模型权重。

函数`data_generator()`定义了数据生成器,给定已加载照片描述的字典、照片特征、用于整数编码序列的分词器以及数据集中最大序列长度。

生成器会无限循环,并在需要时不断生成批次的输入-输出对。我们还有一个`n_step`参数,它允许我们调整每个批次要生成多少张图像的输入-输出对。平均序列有10个单词,即10个输入-输出对,一个好的批次大小可能是30个样本,大约是2到3张图像的量。

可以通过调用`fit_generator()`并将数据生成器以及所有必要的参数传递给它来拟合模型。在拟合模型时,我们还可以指定每个 epoch 运行的批次数和 epoch 的数量。

在这些实验中,我们将使用每批2张图片,每个epoch 50批(即100张图片),以及50个训练epoch。您可以在自己的实验中尝试不同的配置。

3. 评估模型

现在我们知道了如何准备数据和定义模型,我们必须定义一个测试工具来评估给定的模型。

我们将通过以下方式评估模型:在数据集上训练模型,为训练数据集中所有照片生成描述,使用成本函数评估这些预测,然后多次重复此评估过程。

结果将是模型技能分数的分布,我们可以通过计算平均值和标准差来总结。这是评估深度学习模型的首选方法。请参阅此帖子。

首先,我们需要能够使用训练好的模型为照片生成描述。

这涉及传入起始描述标记“`startseq`”,生成一个单词,然后递归调用模型,以生成的单词作为输入,直到达到序列结束标记“`endseq`”或达到最大描述长度。

下面名为`generate_desc()`的函数实现了这种行为,给定一个训练好的模型和准备好的照片作为输入,它会生成文本描述。它调用`word_for_id()`函数将整数预测映射回单词。

我们将为训练数据集和测试数据集中的所有照片生成预测。

下面名为 evaluate_model() 的函数将根据给定的照片描述和照片特征数据集评估训练过的模型。实际和预测的描述将使用语料库BLEU分数进行收集和集体评估,该分数总结了生成的文本与预期文本的接近程度。

BLEU 分数用于文本翻译中,以评估翻译文本与一个或多个参考翻译的对比。实际上,我们确实可以访问每个图像的多个参考描述,可以进行比较,但为了简单起见,我们将使用数据集中每张照片的第一个描述(例如,清理后的版本)。

您可以在这里了解有关 BLEU 分数的更多信息

NLTK Python 库在 corpus_bleu() 函数中实现了 BLEU 分数计算。分数越高越好,接近 1.0 的分数更好,接近零的分数更差。

最后,我们只需在循环中多次定义、拟合和评估模型,然后报告最终的平均分数。

理想情况下,我们应该重复实验 30 次或更多次,但这对于我们的小型测试工具来说太耗时了。相反,我们将评估模型 3 次。它会更快,但平均分数会有更高的方差。

下面定义了模型评估循环。运行结束时,训练集和测试集的 BLEU 分数分布将保存到文件中。

我们将运行参数化如下,允许我们命名每次运行并将结果保存到单独的文件中。

4. 完整示例

完整的示例如下所示。

运行示例首先打印加载的训练数据的摘要统计信息。

该示例在 GPU 硬件上大约需要 20 分钟,在 CPU 硬件上会稍长一些。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

运行结束时,训练集上的平均 BLEU 为 0.06,测试集上的平均 BLEU 为 0.04。结果存储在 baseline1.csv 中。

这提供了一个基线模型,用于与不同配置进行比较。

“A”与“A”测试

在开始测试模型的变体之前,了解测试工具是否稳定很重要。

也就是说,模型在 5 次运行中的总结能力是否足以控制模型的随机性。

我们可以通过在 A/B 测试中再次运行 A 对 A 测试来了解这一点。如果再次运行相同的实验,我们期望得到相同的结果;如果不是,可能需要额外的重复来控制方法和数据集的随机性。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

以下是算法第二次运行的结果。

我们可以看到,这次运行获得了非常相似的平均值和标准差 BLEU 分数。具体来说,训练集上的平均 BLEU 从 0.06 变为 0.03,测试集上为 0.04 到 0.04。

该工具略有噪音,但足以进行比较。

这个模型好吗?

生成照片描述

我们期望该模型训练不足,甚至可能配置不足,但它能生成任何可读文本吗?

重要的是,基线模型应具备一定的能力,以便我们将基线的 BLEU 分数与所生成描述的质量联系起来。

让我们训练一个单一模型,并从训练集和测试集中生成一些描述作为健全性检查。

将重复次数更改为 1,将运行名称更改为“baseline_generate”。

然后更新 evaluate_model() 函数,使其仅评估数据集中前 5 张照片并打印描述,如下所示。

重新运行示例。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

您应该看到训练集的结果如下所示

您应该看到测试数据集的结果如下

我们可以看到,描述并不完美,有些有点粗糙,但总的来说,模型正在生成一些可读的文本。这是一个很好的改进起点。

接下来,让我们看看一些实验,以改变不同子模型的大小或容量。

网络大小参数

在本节中,我们将了解网络结构的粗略变化如何影响模型技能。

我们将研究模型大小的以下方面

  1. “编码器”的固定向量输出大小。
  2. 序列编码器模型的大小。
  3. 语言模型的大小。

让我们开始吧。

固定长度向量的大小

在基线模型中,照片特征提取器和文本序列编码器都输出一个 128 元素的向量。然后这些向量被连接起来,由语言模型处理。

每个子模型中的 128 元素向量包含有关输入序列和照片的所有已知信息。我们可以改变这个向量的大小,看看它是否影响模型技能

首先,我们可以将大小减半,从 128 元素减到 64 元素。

我们将此模型命名为“size_sm_fixed_vec”。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

运行此实验会产生以下 BLEU 分数,在测试集上可能比基线略有提高。

我们还可以将固定长度向量的大小从 128 增加一倍到 256 个单位。

我们将此配置命名为“size_lg_fixed_vec”。

运行此实验表明 BLEU 分数表明模型并没有变得更好。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

可能随着更多数据和/或更长的训练,我们会看到不同的结果。

序列编码器大小

我们可以将到目前为止解释已生成的词语输入序列的子模型称为序列编码器。

首先,我们可以尝试查看减少序列编码器的表示能力是否会影响模型技能。我们可以将 LSTM 层中的内存单元数量从 256 减少到 128。

运行此示例,我们可以看到训练和测试集上都可能比基线略有提升。这可能是小训练集大小造成的假象。

另一方面,我们可以将 LSTM 层数从一个增加到两个,看看是否会产生显著差异。

运行此实验表明训练集和测试集上的 BLEU 都有显著提升。

我们还可以尝试将词嵌入的表示能力从 50 维增加一倍到 100 维。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

我们看到训练数据集上有很大的变化,但测试数据集上变化不大。

语言模型大小

我们可以将从连接序列和照片特征输入中学习的模型称为语言模型。它负责生成词语。

首先,我们可以通过将 LSTM 和密集层从 500 个神经元减少到 256 个来查看对模型技能的影响。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

我们可以看到,这对训练和测试数据集的 BLEU 都产生了小的积极影响,这再次可能与数据集的小尺寸有关。

我们还可以通过添加第二个相同大小的 LSTM 层来查看将语言模型容量翻倍的影响。

同样,我们看到 BLEU 的微小变动,可能噪音和数据集大小的假象。测试的改进。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

测试数据集的改进可能是一个好兆头。这可能是一个值得探索的改变。

在更小的数据集上调整模型大小具有挑战性。

配置特征提取模型

预训练的 VGG16 模型的使用提供了一些额外的配置点。

基线模型从 VGG 模型中移除了顶部,包括一个全局最大池化层,然后将其馈入特征编码为 128 元素向量。

在本节中,我们将研究对基线模型的以下修改

  1. 在 VGG 模型后使用全局平均池化层。
  2. 不使用任何全局池化。

全局平均池化

我们可以用 GlobalAveragePooling2D 层替换 GlobalMaxPooling2D 层,以实现平均池化。

全局平均池化是为了减少图像分类问题中的过拟合而开发的,但可能在解释从图像中提取的特征方面提供一些好处。

有关全局平均池化的更多信息,请参阅论文

更新的 define_model() 函数和实验名称如下所示。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

结果表明训练数据集的性能显著提高,这可能是过拟合的迹象。我们还看到测试技能略有提升。这可能是一个值得探索的改变。

我们还看到测试技能略有提升。这可能是一个值得探索的改变。

无池化

我们可以移除 GlobalMaxPooling2D 并将 3D 图片特征展平,然后直接将其馈送到 Dense 层。

我预计这不是一个好的模型设计,但值得测试这个假设。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

令人惊讶的是,我们在训练数据上看到了小幅提升,在测试数据上看到了大幅提升。这(对我来说)很令人惊讶,可能值得进一步调查。

我们可以尝试重复这个实验,并为解释提取的图片特征提供更多的能力。在 Flatten 层之后添加了一个包含 500 个神经元的新 Dense 层。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

这导致了不太令人印象深刻的变化,并且在测试数据集上 BLEU 结果可能更差。

词嵌入模型

模型的一个关键部分是序列学习模型,它必须解释为图片生成的词序列。

该子模型的输入是词嵌入,改进词嵌入的一种好方法是使用预训练的词嵌入,而不是将其作为模型的一部分从头开始学习(如基线模型中所示)。

在本节中,我们将探讨使用预训练词嵌入对模型的影响。具体来说

  1. 训练 Word2Vec 模型
  2. 训练 Word2Vec 模型 + 微调

训练好的 word2vec 嵌入

用于从文本语料库预训练词嵌入的有效学习算法是 word2vec 算法

你可以在这里了解更多关于 word2vec 算法的信息

我们可以使用此算法,利用数据集中清理过的图片描述来训练一组新的独立词向量。

Gensim 库提供了对该算法实现的访问,我们可以使用它来预训练嵌入。

首先,我们必须像以前一样加载训练数据集的干净图片描述。

接下来,我们可以在所有干净的描述上拟合 word2vec 模型。我们应该注意,这包括比训练数据集中使用的 50 个描述更多的描述。这些实验的更公平的模型应该只在训练数据集中的那些描述上进行训练。

一旦拟合,我们可以将单词和词向量保存到 ASCII 文件中,或许是为了以后检查或可视化。

词嵌入保存到文件“custom_embedding.txt”中。

现在,我们可以将嵌入加载到内存中,只检索我们词汇表中单词的词向量,然后将它们保存到新文件中。

完整的示例如下所示。

运行此示例将创建一个新的词到词向量的字典映射,存储在文件“word2vec_embedding.pkl”中。

接下来,我们可以加载此嵌入并使用词向量作为 Embedding 层中的固定权重。

下面提供了 load_embedding() 函数,该函数加载自定义 word2vec 嵌入并返回新的 Embedding 层,供模型使用。

我们可以在模型中直接调用 define_model() 函数来使用它。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

我们可以看到训练数据集有一些提升,测试数据集可能没有真正的显著变化。

训练好的 word2vec 嵌入与微调

我们可以重复之前的实验,并允许模型在拟合模型时调整词向量。

下面列出了允许对嵌入层进行微调的更新的 load_embedding() 函数。

注意:考虑到算法或评估过程的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。

同样,我们没有看到使用这些预训练词嵌入向量与基线模型相比有太大差异。

结果分析

我们对 Flickr8k 训练数据集中的 8,000 张图片进行了少量样本(1.6%)的实验。

样本可能太小,模型训练时间不够长,以及每个模型重复 3 次导致方差过大。这些方面也可以通过设计实验来测试和评估,例如:

  1. 模型技能是否随数据集大小而变化?
  2. 更多训练周期是否会带来更好的技能?
  3. 更多重复是否会带来方差较小的技能?

尽管如此,我们对如何为更完整的数据集配置模型有了一些想法。

下面是本教程中进行的实验的平均结果摘要。

回顾结果图表很有帮助。如果重复次数更多,每个分数分布的箱线图可能是一个很好的可视化。这里我们使用一个简单的条形图。请记住,BLEU 分数越大越好。

训练数据集上的结果

Bar Chart of Experiment vs Model Skill on the Training Dataset

实验与训练数据集上模型技能的条形图

测试数据集上的结果

Bar Chart of Experiment vs Model Skill on the Test Dataset

实验与测试数据集上模型技能的条形图

仅从测试数据集的平均结果来看,我们可以提出以下建议:

  • 也许在图片特征提取器之后不需要池化(fe_flat 为 0.135231)。
  • 也许在图片特征提取器之后,平均池化比最大池化更有优势(fe_avg_pool 为 0.060847)。
  • 也许在子模型之后使用较小尺寸的固定长度向量是一个好主意(size_sm_fixed_vec 为 0.063148)。
  • 也许在语言模型中添加更多层会带来一些好处(size_lg_lang_model 为 0.067658)。
  • 也许在序列模型中添加更多层会带来一些好处(size_lg_seq_model 为 0.09697)。

我还建议探索这些建议的组合。

我们还可以查看结果分布。

下面是一些代码,用于加载每个实验的保存结果,并创建训练集和测试集结果的箱线图以供查看。

训练数据集上的结果分布。

Box and Whisker Plot of Experiment vs Model Skill on the Training Dataset

实验与训练数据集上模型技能的箱线图

测试数据集上的结果分布。

Box and Whisker Plot of Experiment vs Model Skill on the Test Dataset

实验与测试数据集上模型技能的箱线图

对这些分布的审查表明:

  • 平面结果的扩散很大;也许使用平均池化会更安全。
  • 大型语言模型的扩散很大,并且向错误/危险的方向倾斜。
  • 大型序列模型的扩散很大,并且向正确的方向倾斜。
  • 较小的固定长度向量尺寸可能会带来一些好处。

我预计将重复次数增加到 5、10 或 30 会在某种程度上收紧这些分布。

进一步阅读

如果您想深入了解此主题,本节提供了更多资源。

论文

相关字幕项目

其他

API

总结

在本教程中,你学习了如何使用图片字幕数据集的小样本来探索不同的模型设计。

具体来说,你学到了:

  • 如何为照片描述建模准备数据。
  • 如何设计基线和测试工具来评估模型的技能并控制其随机性。
  • 如何评估模型技能、特征提取模型和词嵌入等属性,以提升模型技能。

你能想出什么实验?
你还尝试过什么?
在训练和测试数据集上你能获得最好的结果是什么?

在下面的评论中告诉我。

立即开发文本数据的深度学习模型!

Deep Learning for Natural Language Processing

在几分钟内开发您自己的文本模型

...只需几行python代码

在我的新电子书中探索如何实现
用于自然语言处理的深度学习

它提供关于以下主题的自学教程
词袋模型、词嵌入、语言模型、标题生成、文本翻译等等...

最终将深度学习应用于您的自然语言处理项目

跳过学术理论。只看结果。

查看内容

《如何使用小型实验开发 Keras 中的字幕生成模型》的 85 条回复

  1. Emil 2017 年 11 月 24 日 上午 6:39 #

    脱帽致敬,又一篇精彩的教程!

    我很好奇 TimeDistributed 层在拼接之前如何影响数据。是否可以跳过它?另外,除了内存/计算限制之外,你使用 VGG 而不是 InceptionResNetV2 类是否有原因?

    谢谢!

    • Jason Brownlee 2017 年 11 月 24 日 上午 9:52 #

      我选择 VGG 是因为它更小、更简单。你可以使用任何你想要的。

      你可以跳过 TimeDistributed,因为我相信 Dense 现在可以支持时间步。我喜欢把它放在那里,因为它提醒我正在发生什么(例如,输出时间步)。

  2. Alex 2017 年 11 月 24 日 下午 6:16 #

    你好 Jason,为什么你不在与不同图像相关的输入之间重置 LSTM 状态?因为它们与相同的序列无关。

    • Jason Brownlee 2017 年 11 月 25 日 上午 10:14 #

      为了训练速度。

      不过这是一个很好的建议,试试看它是否能提高技能!告诉我你的进展。

      • Alex 2017 年 11 月 28 日 上午 1:39 #

        谢谢!为了尝试这种方法,我是否应该设置 stateful=True(避免 LSTM 自动重置)并在训练单个批次之前手动运行 model.reset_states()?(每个批次都与单个图像的序列相关)。

  3. Emil 2017 年 12 月 13 日 上午 12:10 #

    当你创建词汇表长度时,“vocab_size = len(tokenizer.word_index) + 1”中的 +1 的逻辑是什么?是为了留出 0 吗?

    谢谢

    • Jason Brownlee 2017 年 12 月 13 日 上午 5:39 #

      好问题,是为了为 0 腾出空间——词汇表中的单词从 1 开始。

      • Yang Cheng 2018 年 3 月 12 日 下午 8:50 #

        这是否意味着我们将索引 0 留给 'endseq' 标记?

        • Jason Brownlee 2018 年 3 月 13 日 上午 6:27 #

          不,开始和结束标记是问题的合法部分。模型必须指定序列何时结束。

  4. xiaolian 2017 年 12 月 23 日 上午 1:54 #

    我收到一个错误:检查输入时出错:预期 input_11 有 4 个维度,但得到的数组形状为 (28, 4096)

    • Jason Brownlee 2017 年 12 月 23 日 上午 5:21 #

      您能确认您的库是最新的,并且您复制了帖子中的所有代码吗?

    • Nadeem 2020 年 6 月 29 日 下午 6:13 #

      我也遇到过这种情况。但这发生在我尝试将预训练的 word2vec 与最初定义的数据生成器等代码一起使用时。我正在尝试查看哪些修改可以使预训练的 word2vec 运行

  5. xiaolian 2017 年 12 月 23 日 下午 1:40 #

    我可以在 github 上获取代码吗?

    • Jason Brownlee 2017 年 12 月 24 日 上午 4:51 #

      代码在帖子中,你为什么需要它在 github 上?

  6. Niels 2018 年 2 月 24 日 上午 12:41 #

    你好,Jason。

    非常感谢您的文章。
    我有一个关于训练编码器-解码器网络的实际问题。
    所以基本上我有一些序列号的图像,我想预测完整的序列号。(例如 018F6176)
    所以如果我训练一个网络来预测下一个字符,我实际上必须创建一个 for 循环来预测,直到达到最大长度或停止词。
    这我明白了。但是,我如何组织我的数据?
    我拥有以下内容
    数据是一个 numpy 浮点数组,形状为 (nb_samples 宽度,高度,nb_channels=3,)。
    标签是一个 numpy 整数数组,形状为 (nb_samples, max_caption_len)
    因此,如果我要构建一个具有与 create_sequences() 相同的结构的数据集,我的样本数量将增加,我如何确保图像以正确的顺序加载,以便每个图像的序列都以正确的顺序出现(或者这是否重要?)
    希望你理解我的意思。
    此致
    尼尔斯

    • Jason Brownlee 2018 年 2 月 24 日 上午 9:17 #

      很好的问题。

      我相信您想使用字幕模型方法。

      请参阅这篇帖子,特别是标题为“逐词模型”的部分
      https://machinelearning.org.cn/prepare-photo-caption-dataset-training-deep-learning-model/

      它将向您展示如何准备数据以及如何思考数据。

      • Niels 2018 年 2 月 26 日 下午 8:01 #

        谢谢。这正是我所寻找的。不过有一个问题。输入的顺序是否重要,例如图像 1 的所有样本按顺序出现,然后是图像 2 的所有样本等等。
        因为那样我将不得不创建一个批生成器,但我希望避免这样做。
        我最初的想法是这并不重要,因为你只是训练算法来识别根据之前的输入接下来会发生什么。这个假设正确吗?
        此致
        尼尔斯

        • Jason Brownlee 2018年2月27日 早上6:25 #

          我认为是这样,直觉上,一张照片的所有样本应该放在一起(LSTM 在批次之间具有记忆),但测试所有假设是个好主意。

          • Niels 2018年2月27日 晚上9:27 #

            好的。我使用 data_generator 和随机拆分训练、测试(图像顺序打乱)两种方式创建了模型。训练、测试模型似乎没有收敛(最大验证准确率为 17%)。然而,data_generator 模型(其中顺序保留)达到了 99.5% 的验证准确率,这非常棒。
            谢谢你的时间 ????

          • Jason Brownlee 2018年2月28日 早上6:03 #

            非常好。感谢您运行此实验!

  7. Binay 2018年3月7日 中午12:18 #

    为什么 input_1 的维度是 (7, 7, 512)?

    • Jason Brownlee 2018年3月7日 下午3:06 #

      这是我们保存从照片中提取的特征的形状。

  8. Steven 2018年3月23日 晚上9:03 #

    感谢您的教程,Jason。我可以运行这个教程,与关于相同数据集的教程 https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/ 相比,后者出现了内存错误。
    我比较了这两个示例,发现图像的特征提取方式不同。这个示例的特征是形状为 (7, 7, 512) 的三维数组,而另一个示例的结果是一个一维的 4,096 元素向量。这个实验的 features.pkl 文件比另一个示例大得多。造成特征形状不同的原因是什么?

    • Jason Brownlee 2018年3月24日 早上6:29 #

      也许 VGG 模型在不同的地方被裁剪了?例如,保留全连接层或丢弃它并使用 CNN 输出。我不记得了,但比较代码会更清楚。

  9. Ashish 2018年3月28日 下午4:56 #

    感谢您的教程,Jason。这里您使用的是预训练 CNN 模型的特征。但我也想通过 CNN 反向传播误差,从而联合训练 CNN 和 LSTM。也就是说,通过预训练模型加载 CNN,然后进一步训练它。您能提供一个方法吗?谢谢。

    • Jason Brownlee 2018年3月29日 早上6:31 #

      是的,您可以将 CNN 作为内存中模型的一部分加载。

  10. Vector 2018年4月7日 下午3:09 #

    你好 Jason,

    感谢这篇精彩的文章和这里的这篇 (https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/)!!

    我正在尝试运行您在这里发布的示例。我能够使用不同的学习率和更多时期在您的“平均池化”模型上进行训练,并将它们保存到 .h5 文件中。

    然而,当我尝试使用 (https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/) 中的代码用我训练的“平均模型”生成新标题时,我遇到了这个错误:
    ValueError: Error when checking : expected input_1 to have 4 dimensions, but got array with shape (1, 4096)

    只是想知道您是否知道如何使用我刚刚训练的这个 .h5 文件对另一张照片进行预测?谢谢!!

    • Jason Brownlee 2018年4月8日 早上6:14 #

      看起来您的照片特征可能维度过多。更改您的代码以直接提供照片像素。例如:photo[0]

  11. Md. Zakir Hossain 2018年5月25日 晚上6:46 #

    嗨 Jason,
    这篇博文很棒。真的对我很有帮助。但是,我收到了以下错误:

    File “”, line 1, in
    runfile(‘C:/Users/33083707/Codes/Projects/Final.py’, wdir=’C:/Users/33083707/Codes/Projects’)

    File “C:\Users\33083707\Anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py”, line 866, in runfile
    execfile(filename, namespace)

    File “C:\Users\33083707\Anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py”, line 102, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

    File “C:/Users/33083707/Codes/Projects/Final.py”, line 244, in
    test_score = evaluate_model(model, test_descriptions, test_features, tokenizer, max_length)

    File “C:/Users/33083707/Codes/Projects/Final.py”, line 202, in evaluate_model
    bleu = corpus_bleu(actual, predicted)

    File “C:\Users\33083707\Anaconda3\lib\site-packages\nltk\translate\bleu_score.py”, line 146, in corpus_bleu
    p_i = modified_precision(references, hypothesis, i)

    File “C:\Users\33083707\Anaconda3\lib\site-packages\nltk\translate\bleu_score.py”, line 287, in modified_precision
    return Fraction(numerator, denominator, _normalize=False)

    File “C:\Users\33083707\Anaconda3\lib\fractions.py”, line 186, in __new__
    raise ZeroDivisionError(‘Fraction(%s, 0)’ % numerator)

    ZeroDivisionError: Fraction(0, 0)

  12. Mhemmed Elly 2018年6月29日 晚上9:29 #

    Jason博士您好,

    我想在我自己的数据上尝试这个,但我不知道如何准备它。我有一个图像文件夹和一个 CSV 文件。CSV 文件中的每一行都包含一个图像 ID/名称和一个描述该图像的标题。关于如何进行,有什么建议吗?

    谢谢你

    • Jason Brownlee 2018年6月30日 早上6:07 #

      首先编写代码将图像加载到内存中。

      也许你可以使用 PIL 或 Pillo 来加载图像?

  13. Jeet Sen Sarma 2018年7月6日 晚上6:53 #

    在代码的以下部分

    # 运行实验
    train_results, test_results = list(), list()
    for i in range(n_repeats)
    # 定义模型
    model = define_model(vocab_size, max_length)
    # 拟合模型
    model.fit_generator(data_generator(train_descriptions, train_features, tokenizer, max_length, n_photos_per_update), steps_per_epoch=n_batches_per_epoch, epochs=n_epochs, verbose=verbose)
    # evaluate model on training data
    train_score = evaluate_model(model, train_descriptions, train_features, tokenizer, max_length)
    test_score = evaluate_model(model, test_descriptions, test_features, tokenizer, max_length)
    # 存储
    train_results.append(train_score)
    test_results.append(test_score)
    print(‘>%d: train=%f test=%f’ % ((i+1), train_score, test_score))

    你为什么在 for 循环中定义模型?它不应该定义在循环外部吗?

    • Jason Brownlee 2018年7月7日 早上6:13 #

      不,我希望每次重复都有一个新模型(随机权重)。

      我为什么要定义在循环外部?

      • Jeet 2018年7月9日 下午2:47 #

        您能解释一下为什么吗?我认为模型必须被定义,并且权重通过从示例中学习来训练。但是每次定义一个新模型都会删除之前学习到的权重。我哪里错了?

  14. Md. Zakir Hossain 2018年7月9日 晚上6:13 #

    嗨,Jason,非常感谢。

    我尝试使用 DenseNet-121 模型而不是 VGG16 模型来提取特征。但我得到了一个错误,例如:

    ValueError: Error when checking input: expected input_1 to have 4 dimensions, but got array with shape (21, 1024)。

    您能给我一些关于这个的建议吗?

    • Jeet 2018年7月10日 早上4:26 #

      这是因为两个 CNN 模型的最后一层输出维度不同,你从中提取图像特征。最好的猜测是将 DenseNet 模型的输出维度转换为与 VGG-16 相同的维度。
      此外,如果你使用 Flatten() 而不是 GlobalMaxPooling2D(),我想你就不会遇到同样的问题。

    • Jason Brownlee 2018年7月10日 早上6:44 #

      不完全是,我不了解你正在尝试的事情。

  15. Omnia 2018年10月24日 早上8:25 #

    嗨 Jason

    一如既往,感谢您的精彩教程

    我得到了 70 个时期和 3 次重复的结果
    似乎当我们有大量时期时,准确性会提高

    但我不明白为什么我得到 train 和 test = 0

    这是我的结果

    时期 70/70
    – 14s – 损失:1.8873 – 准确率:0.4100
    实际:startseq 孩子和女人在大城市的水边 endseq
    预测:startseq 孩子女人在边缘边缘在大 endseq

    实际:startseq 拿着棍子的男孩跪在守门员网前 endseq
    预测:startseq 男孩男孩在在前面和和和的的在前面男孩前面和的在前面

    实际:startseq 女人蹲在田野里三只狗旁边 endseq
    预测:startseq 两只狗在旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边旁边

    实际:startseq 男孩在外面坐着时用力咬零食 endseq
    预测:startseq 男孩咬坐着同时同时外面 endseq

    实际:startseq 人边看小电视边吃外卖 endseq
    预测:startseq 人吃同时外卖小小电视 endseq

    实际:startseq 一对夫妇带着裹在毯子里的幼儿坐在水泥台阶上 endseq
    预测:startseq 男孩女孩在在在自行车 endseq

    实际:startseq 成人和儿童站在靠近林区台阶前玩耍 endseq
    预测:startseq 男孩和年轻男孩和和和和和和和和和和和和和和和和和和和

    实际:startseq 穿灰色睡衣的男孩在沙发上跳 endseq
    预测:startseq 男孩男人如果粗糙 endseq

    实际:startseq 男孩拿着厨具做出威胁性的表情 endseq
    预测:startseq 女孩拿着短裤短裤短裤和她 endseq

    实际:startseq 戴绿帽子的男人在高处 endseq
    预测:startseq 男孩在和和波浪 endseq

    >3:train=0.000000 test=0.000000
    训练 测试
    计数 3.000000e+00 3.000000e+00
    平均值 6.038250e-02 4.013559e-155
    标准差 1.045856e-01 2.160777e-156
    最小值 2.413005e-78 3.808820e-155
    25% 2.799294e-78 3.900627e-155
    50% 3.185584e-78 3.992435e-155
    75% 9.057374e-02 4.115929e-155
    最大值 1.811475e-01 4.239423e-155

    此外,我收到了这个警告

    用户警告
    假设包含 0 个 3-gram 重叠。
    因此,BLEU 分数评估为 0,独立于
    它包含的较低阶 N-gram 重叠的数量。
    考虑使用较低的 n-gram 阶或使用 SmoothingFunction()
    warnings.warn(_msg)

    您介意就我的实验提出您的意见并为警告提供更好的解决方案吗?

    谢谢

  16. janarddan sarkar 2019年3月11日 晚上11:21 #

    图像字幕模型所能达到的最高准确率是多少?

    • Jason Brownlee 2019年3月12日 早上6:53 #

      我们不衡量准确性,而是衡量困惑度或 BLEU 分数。

      最佳分数将取决于所使用的特定基准数据集。

  17. nehna 2019年3月15日 下午12:57 #

    嗨,杰森,你的文章很好。
    但我无法获得高于 0.3 的准确率。
    我也尝试增加 epoch,但准确率没有变化。
    您能帮助我将准确率提高到至少 0.7 或 0.8 吗?

  18. Artem 2019年4月6日 下午3:12 #

    嗨,Jason!我无法下载数据集。Framing_Image_Description。404 未找到

  19. Ankit Rathi 2019年7月11日 晚上8:29 #

    您好,先生,我看了您的博客“如何从零开始开发深度学习照片字幕生成器”。在该教程中,您没有使用 RepeatVector 和 TimeDistributed 函数。在此教程中,您使用了这两个函数。由于我是深度学习新手,您能通过解释 RepeatVector 和 TimeDistributed 函数的用法来帮助我吗?如果我们不应用这些函数,会影响模型性能吗?

    谢谢,

    Ankit

    • Jason Brownlee 2019年7月12日 早上8:36 #

      您可以选择任何您想要的模型架构。没有一个最好的模型。

      也许是另一篇文章中的那个,或者是你自己设计的,或者两者都测试一下,看看哪个最适合你?

  20. abbas 2019年8月26日 下午4:28 #

    杰森,很棒的教程!我想问一个关于 CNN 的问题:教程中 CNN 使用的窗口大小是多少?

  21. abbas 2019年8月26日 下午4:51 #

    我的 pyplot 库运行不正常。请提供关于在 Python 中安装和设置 pyplot 库的详细教程。提前感谢

    • Jason Brownlee 2019年8月27日 早上6:35 #

      也许暂时注释掉 plot_model 行?

      • abbas 2019年9月2日 晚上10:22 #

        n_repeats = 3 是什么意思?我是说,如果我将 n_repeats = 1,会有什么不同?

        • Jason Brownlee 2019年9月3日 早上6:16 #

          实验重复的次数,结果会取平均值。

          更多的重复次数将更好地反映该方法的预期性能。

          • abbas 2019年9月22日 下午5:25 #

            改变 CNN 架构能提高 BLUE 分数吗?
            其次,我如何解释这些结果?请深入概述一下…哪些分数应该高才能获得更好的模型性能?
            >3:train=0.000000 test=0.000000
            训练 测试
            计数 3.000000e+00 3.000000e+00
            平均值 1.219064e-78 3.030008e-155
            标准差 2.247343e-79 8.613845e-156
            最小值 1.078167e-78 2.152162e-155
            25% 1.089478e-78 2.608047e-155
            50% 1.100788e-78 3.063932e-155
            75% 1.289512e-78 3.468930e-155
            最大值 1.478235e-78 3.873929e-155

          • Jason Brownlee 2019年9月23日 早上6:36 #

            也许尝试一些更改并比较结果?

          • abbas 2019年10月3日 下午1:31 #

            Jason,我有一些问题。请再解释清楚一些。
            在教程的“固定长度向量大小”部分。
            fe2 = Dense(64/128/256, activation=’relu’)(fe1)
            1) 64/128/256 代表什么?是照片特征向量还是其他什么?
            2) 我们如何以及为什么要增加或减少特征向量?
            3) VGG16 的输出在这种情况下是 4096 维向量……那么我们把(图像)特征作为模型的输入在哪里?
            3) 为什么我们取 64 的倍数?为什么我们不能随机取?

          • Jason Brownlee 2019年10月4日 早上5:37 #

            这是层中的节点数量。

            你说的增加/减少特征向量是什么意思?你是说照片的特征向量的长度吗?如果是这样,也许可以尝试使用不同的预训练模型。

            特征向量是 X1,也许再读一遍教程?

            64 的倍数在模型节点中很常见,没有特别的原因。

  22. abbas 2019年10月4日 晚上6:40 #

    那么,我们可以改变 vgg16 中节点的数量吗?如果可以,请解释 vgg16 的哪一层中的节点正在改变?

  23. abbas 2019年10月10日 早上2:20 #

    我如何知道我的训练是正确的?我是说,我如何确认我的模型学习得很好?

    • Jason Brownlee 2019年10月10日 早上7:01 #

      使用鲁棒的测试工具,如 K 折交叉验证进行评估。

      或者一个大的训练/测试拆分。

      • abbas 2019年10月10日 晚上11:18 #

        我该如何使用 K 折交叉验证?您有任何教程可以给我提供一些思路吗?

          • abbas 2019年10月11日 下午2:01 #

            我的测试数据集上的 BLUE 分数很低,接近于零。我的训练出了什么问题?我的训练平均分是 0.194386,而我的测试平均分是 0.01468624

          • Jason Brownlee 2019年10月12日 早上6:45 #

            也许您的模型过度拟合了训练数据集?

            也许可以尝试较小的学习率或提前停止训练等等?

  24. abbas 2019年10月29日 早上12:46 #

    我如何更新教程的当前代码,以便我可以对我的数据集进行 K 折交叉验证,然后在此基础上评估我的模型性能(以确保低偏置或无偏置)?

    • Jason Brownlee 2019年10月29日 早上5:27 #

      也许这会有帮助。
      https://machinelearning.org.cn/k-fold-cross-validation/

      • abbas 2019年11月8日 下午1:28 #

        在上一篇文章中,您使用了形状为 4096 的一维图像特征,而在这篇文章中,您使用了形状为 (7,7,512) 的三维图像特征……为什么?这两种不同的特征有什么区别?它们都是 VGG 特征,但来自不同的层吗?

        • Jason Brownlee 2019年11月8日 下午1:51 #

          链接的帖子介绍了最普遍意义上的k折交叉验证。

          • abbas 2019年11月10日 下午2:31 #

            请再检查一下我的问题……我问的是图像特征维度……而不是k折

  25. Jun 2020年3月17日 下午5:10 #

    你好 Jason,

    当我应用您的代码时,这一部分

    我收到“NotImplementedError: 无法将符号张量 (args_2:0) 转换为numpy数组。”

    我不确定如何解决这个问题。您能帮我解决吗?

    谢谢你。

发表评论

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。