原文:
annas-archive.org/md5/9ea32aa91e5ec8d38bb57552873cedb5译者:飞龙
第五章:训练计算机视觉模型
在上一章中,你学习了如何使用 SageMaker 内置的算法来解决传统机器学习问题,包括分类、回归和异常检测。我们看到这些算法在处理表格数据(如 CSV 文件)时效果良好。然而,它们不太适合图像数据集,通常在 CV(计算机视觉)任务上表现非常差。
近年来,计算机视觉(CV)已经席卷全球,几乎每个月都有新的突破,能够从图像和视频中提取模式。本章中,你将学习三种专为计算机视觉任务设计的内置算法。我们将讨论你可以用它们解决的各种问题,并详细解释如何准备图像数据集,因为这一关键主题经常被莫名其妙地忽视。当然,我们还会训练并部署模型。
本章内容包括以下主题:
-
发现 Amazon SageMaker 中的计算机视觉内置算法
-
准备图像数据集
-
使用内置的计算机视觉算法:图像分类、目标检测和语义分割
技术要求
你需要一个 AWS 账户来运行本章中的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应当熟悉 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。
你需要为你的账户安装并配置 AWS 命令行界面(CLI)(aws.amazon.com/cli/)。
你需要一个可用的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制性的,但强烈建议安装,因为它包含了我们将需要的许多项目(Jupyter、pandas、numpy 等)。
本书中的代码示例可以在 GitHub 上找到:github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端来访问这些示例(git-scm.com/)。
发现 Amazon SageMaker 中的计算机视觉内置算法
SageMaker 包括三种基于深度学习网络的计算机视觉算法。在本节中,你将了解这些算法,它们能帮助你解决什么样的问题,以及它们的训练场景:
-
图像分类将一个或多个标签分配给图像。
-
目标检测是在图像中检测并分类物体。
-
语义分割将图像的每一个像素分配到一个特定的类别。
发现图像分类算法
从输入图像开始,图像分类算法为训练数据集中每个类别预测一个概率。该算法基于ResNet卷积神经网络(arxiv.org/abs/1512.03385)。ResNet于 2015 年发布,并在同年获得 ILSVRC 分类任务的冠军(www.image-net.org/challenges/LSVRC/)。从那时起,它成为了一个流行且多用途的图像分类选择。
可以设置许多超参数,包括网络的深度,范围从 18 层到 200 层。通常,网络层数越多,学习效果越好,但训练时间也会增加。
请注意,图像分类算法支持单标签和多标签分类。在本章中,我们将重点讨论单标签分类。处理多个标签非常类似,您可以在github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_amazon_algorithms/imageclassification_mscoco_multi_label/找到一个完整的示例。
发现物体检测算法
从输入图像开始,物体检测算法预测图像中每个物体的类别和位置。当然,该算法只能检测训练数据集中存在的物体类别。每个物体的位置由一组四个坐标定义,称为边界框。
该算法基于单次多框检测器(SSD)架构(arxiv.org/abs/1512.02325)。对于分类,您可以选择两个基础网络:VGG-16(arxiv.org/abs/1409.1556)或ResNet-50。
以下输出展示了一个物体检测示例(来源:www.dressagechien.net/wp-content/uploads/2017/11/chien-et-velo.jpg):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_1.jpg
图 5.1 – 测试图像
发现语义分割算法
从输入图像开始,语义分割算法预测图像中每个像素的类别。这比图像分类(仅考虑整幅图像)或物体检测(仅关注图像的特定部分)要困难得多。通过使用预测中包含的概率,可以构建分割掩码,覆盖图像中的特定物体。
三种神经网络可以用于分割:
-
全卷积网络(FCNs):
arxiv.org/abs/1411.4038 -
金字塔场景解析(PSP):
arxiv.org/abs/1612.01105 -
DeepLab v3:
arxiv.org/abs/1706.05587
编码器网络是ResNet,有 50 层或 101 层。
以下输出显示了对前一张图像进行分割后的结果。我们可以看到分割掩膜,每个类别都分配了一个独特的颜色;背景为黑色,等等:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_2.jpg
图 5.2 – 分割后的测试图像
现在让我们看看如何在我们自己的数据上训练这些算法。
使用计算机视觉(CV)算法进行训练
这三种算法都是基于监督学习的,因此我们的起点将是一个带标签的数据集。当然,这些标签的性质对于每个算法会有所不同:
-
图像分类的类别标签
-
目标检测的边界框和类别标签
-
语义分割的分割掩膜和类别标签
对图像数据集进行标注是一项繁重的工作。如果你需要构建自己的数据集,Amazon SageMaker Ground Truth绝对能帮助你,我们在第二章中研究了它,处理数据准备技术。在本章稍后部分,我们将展示如何使用标注过的图像数据集。
在打包数据集时,强烈建议使用RecordIO文件(mxnet.apache.org/api/faq/recordio)。将图像打包成少量的记录结构化文件,可以使数据集的移动和分割变得更容易,便于分布式训练。话虽如此,如果你愿意,也可以在单独的图像文件上进行训练。
一旦你的数据集准备好并存储在 S3 上,你需要决定是从头开始训练,还是从一个预训练的网络开始。
如果你有大量数据,而且确信从这些数据中构建特定模型是有价值的,那么从头开始训练是没问题的。然而,这将耗费大量时间,可能需要数百次的迭代,并且超参数选择在获得好结果方面至关重要。
使用预训练网络通常是更好的选择,即使你有大量数据。得益于迁移学习,你可以从一个在大量图像(假设是数百万张)上训练的模型开始,然后根据你的数据和类别进行微调。这样训练的时间会大大缩短,并且你会更快地获得更高准确率的模型。
鉴于模型的复杂性和数据集的大小,使用 CPU 实例进行训练显然不可行。我们将为所有示例使用 GPU 实例。
最后但同样重要的是,所有这三种算法都是基于Apache MXNet的。这使得你可以将它们的模型导出到 SageMaker 之外,并在任何地方进行部署。
在接下来的章节中,我们将深入探讨图像数据集,以及如何为训练准备它们。
准备图像数据集
图像数据集的输入格式比表格数据集更为复杂,我们需要确保格式完全正确。SageMaker 中的计算机视觉算法支持三种输入格式:
-
图像文件
-
RecordIO文件
-
由SageMaker Ground Truth构建的增强清单
在本节中,你将学习如何准备这些不同格式的数据集。据我所知,这个话题很少被如此详细地讨论。准备好学习很多内容吧!
处理图像文件
这是最简单的格式,所有三种算法都支持它。让我们看看如何将其与图像分类算法一起使用。
将图像分类数据集转换为图像格式
图像格式的数据集必须存储在 S3 中。图像文件不需要按任何特定方式排序,你可以将它们全部存储在同一个存储桶中。
图像描述存储在列表文件中,列表文件是一个文本文件,每行代表一张图像。对于图像分类,文件中有三列:图像的唯一标识符、类标签以及路径。以下是一个示例:
1023 5 prefix/image2753.jpg
38 6 another_prefix/image72.jpg
983 2 yet_another_prefix/image863.jpg
第一行告诉我们,image2753.jpg属于第 5 类,且已分配 ID 1023。
每个通道都需要一个列表文件,因此你需要为训练数据集、验证数据集等创建一个。你可以编写定制的代码来生成它们,或者使用im2rec中的一个简单程序,该程序在 Python 和 C++中都有。我们将使用 Python 版本。
让我们使用Kaggle上的 Dogs vs. Cats 数据集(www.kaggle.com/c/dogs-vs-cats)。该数据集为 812 MB。不出所料,包含两类:狗和猫。它已经分为训练集和测试集(分别为 25,000 张和 12,500 张图片)。具体操作如下:
-
我们创建一个
kaggle命令行工具(github.com/Kaggle/kaggle-api)。 -
在我们的本地机器上,我们下载并提取训练数据集(你可以忽略测试集,它只用于竞赛):
$ kaggle competitions download -c dogs-vs-cats $ sudo yum -y install zip unzip $ unzip dogs-vs-cats.zip $ unzip train.zip -
狗和猫的图像混合在同一个文件夹中。我们为每个类别创建一个子文件夹,并将相应的图像移动到这些子文件夹中:
$ cd train $ mkdir dog cat $ find . -name 'dog.*' -exec mv {} dog \; $ find . -name 'cat.*' -exec mv {} cat \; -
我们需要验证图像,因此让我们将 1,250 张随机狗图像和 1,250 张随机猫图像移动到指定目录。我这里使用
bash脚本,但你可以根据需要使用任何工具:$ mkdir -p val/dog val/cat $ ls dog | sort -R | tail -1250 | while read file; do mv dog/$file val/dog; done $ ls cat | sort -R | tail -1250 | while read file; do mv cat/$file val/cat; done -
我们将剩余的 22,500 张图片移动到训练文件夹:
$ mkdir train $ mv dog cat train -
现在我们的数据集如下所示:
$ du -h 33M ./val/dog 28M ./val/cat 60M ./val 289M ./train/dog 248M ./train/cat 537M ./train 597M . -
我们从 GitHub 下载
im2rec工具(github.com/apache/incubator-mxnet/blob/master/tools/im2rec.py)。它需要依赖项,我们需要安装这些依赖(你可能需要根据自己的环境和 Linux 版本调整命令):$ wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/tools/im2rec.py $ sudo yum -y install python-devel python-pip opencv opencv-devel opencv-python $ pip3 install mxnet opencv-python -
我们运行
im2rec以生成两个列表文件,一个用于训练数据,另一个用于验证数据:dogscats-train.lst and dogscats-val.lst files. Their three columns are a unique image identifier, the class label (0 for cats, 1 for dogs), and the image path, as follows:3197 0.000000 cat/cat.1625.jpg
15084 1.000000 dog/dog.12322.jpg
1479 0.000000 cat/cat.11328.jpg
5262 0.000000 cat/cat.3484.jpg
20714 1.000000 dog/dog.6140.jpg
-
我们将列表文件移动到特定目录。这是必要的,因为它们将作为两个新通道
train_lst和validation_lst传递给Estimator:$ mkdir train_lst val_lst $ mv dogscats-train.lst train_lst $ mv dogscats-val.lst val_lst -
数据集现在看起来是这样的:
$ du -h 33M ./val/dog 28M ./val/cat 60M ./val 700K ./train_lst 80K ./val_lst 289M ./train/dog 248M ./train/cat 537M ./train 597M . -
最后,我们将这个文件夹同步到 SageMaker 默认存储桶中以备后用。请确保只同步这四个文件夹,其他的不要同步:
$ aws s3 sync . s3://sagemaker-eu-west-1-123456789012/dogscats-images/input/
现在,让我们继续在目标检测算法中使用图像格式。
将检测数据集转换为图像格式
一般原则是相同的。我们需要构建一个文件树,表示四个通道:train、validation、train_annotation和validation_annotation。
主要的区别在于标签信息的存储方式。我们需要构建 JSON 文件,而不是列表文件。
这是一个虚构图像的示例,来自一个目标检测数据集。对于图像中的每个物体,我们定义其边界框的左上角坐标、其高度和宽度。我们还定义了类标识符,它指向一个类别数组,该数组还存储类名称:
{
"file": " my-prefix/my-picture.jpg",
"image_size": [{"width": 512,"height": 512,"depth": 3}],
"annotations": [
{
"class_id": 1,
"left": 67, "top": 121, "width": 61, "height": 128
},
{
"class_id": 5,
"left": 324, "top": 134, "width": 112, "height": 267
}
],
"categories": [
{ "class_id": 1, "name": "pizza" },
{ "class_id": 5, "name": "beer" }
]
}
我们需要对数据集中的每张图片执行此操作,为训练集构建一个 JSON 文件,为验证集构建一个 JSON 文件。
最后,让我们看看如何在语义分割算法中使用图像格式。
将分割数据集转换为图像格式
图像格式是图像分割算法唯一支持的格式。
这次,我们需要构建一个文件树,表示四个通道:train、validation、train_annotation和validation_annotation。前两个通道包含源图像,最后两个通道包含分割掩膜图像。
文件命名在将图像与其掩膜匹配时至关重要:源图像和掩膜图像必须在各自的通道中具有相同的名称。以下是一个示例:
├── train
│ ├── image001.png
│ ├── image007.png
│ └── image042.png
├── train_annotation
│ ├── image001.png
│ ├── image007.png
│ └── image042.png
├── validation
│ ├── image059.png
│ ├── image062.png
│ └── image078.png
└── validation_annotation
│ ├── image059.png
│ ├── image062.png
│ └── image078.png
你可以在下图中看到示例图像。左侧的源图像应放入train文件夹,右侧的掩膜图像应放入train_annotation文件夹。它们必须具有完全相同的名称,以便算法可以匹配它们:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_3.jpg
图 5.3 – Pascal VOC 数据集中的示例图像
这种格式的一个巧妙特点是它如何将类标识符与掩膜颜色匹配。掩膜图像是具有 256 色调色板的 PNG 文件。数据集中的每个类都分配了调色板中的一个特定条目。这些颜色就是你在该类物体的掩膜中看到的颜色。
如果你的标签工具或现有数据集不支持这种 PNG 特性,你可以添加自己的颜色映射文件。详情请参考 AWS 文档:docs.aws.amazon.com/sagemaker/latest/dg/semantic-segmentation.html。
现在,让我们准备 Pascal VOC 数据集。这个数据集常用于对目标检测和语义分割模型进行基准测试:
-
我们首先下载并提取数据集的 2012 版本。我建议使用 AWS 托管的实例来加速网络传输:
$ wget https://data.deepai.org/PascalVOC2012.zip $ unzip PascalVOC2012.zip -
我们创建一个工作目录,在这里构建四个通道:
$ mkdir input $ cd input $ mkdir train validation train_annotation validation_annotation -
使用数据集中定义的训练文件列表,我们将相应的图像复制到
train文件夹。我在这里使用的是bash脚本,当然你也可以使用你喜欢的工具:$ for file in 'cat ../ImageSets/Segmentation/train.txt | xargs'; do cp ../JPEGImages/$file".jpg" train; done -
然后我们对验证图像、训练掩码和验证掩码做相同的处理:
$ for file in 'cat ../ImageSets/Segmentation/val.txt | xargs'; do cp ../JPEGImages/$file".jpg" validation; done $ for file in 'cat ../ImageSets/Segmentation/train.txt | xargs'; do cp ../SegmentationClass/$file".png" train_annotation; done $ for file in 'cat ../ImageSets/Segmentation/val.txt | xargs'; do cp ../SegmentationClass/$file".png" validation_annotation; done -
我们检查两个训练通道和两个验证通道中的图像数量是否一致:
$ for dir in train train_annotation validation validation_annotation; do find $dir -type f | wc -l; done我们看到了 1,464 个训练文件和掩码,以及 1,449 个验证文件和掩码。我们准备好了:
1464 1464 1449 1449 -
最后一步是将文件树同步到 S3 以备后用。再次提醒,请确保只同步四个文件夹:
$ aws s3 sync . s3://sagemaker-eu-west-1-123456789012/pascalvoc-segmentation/input/
我们知道如何准备图像格式的分类、检测和分割数据集。这是一个关键步骤,你必须确保一切都准确无误。
不过,我敢肯定你会觉得本节中的步骤有些麻烦,我也是!现在想象一下,面对数百万张图像做同样的事情。这听起来可不太刺激,对吧?
我们需要一种更简便的方法来准备图像数据集。让我们看看如何使用 RecordIO 文件简化数据集准备。
使用 RecordIO 文件
RecordIO 文件更容易搬动。对于算法来说,读取一个大的顺序文件比读取很多存储在随机磁盘位置的小文件更高效。
将图像分类数据集转换为 RecordIO
让我们将 Dogs vs. Cats 数据集转换为 RecordIO:
-
从新提取的数据集副本开始,我们将图像移到相应的类别文件夹中:
$ cd train $ mkdir dog cat $ find . -name 'dog.*' -exec mv {} dog \; $ find . -name 'cat.*' -exec mv {} cat \; -
我们运行
im2rec来为训练数据集(90%)和验证数据集(10%)生成列表文件。我们无需自己分割数据集!$ python3 im2rec.py --list --recursive --train-ratio 0.9 dogscats . -
我们再次运行
im2rec来生成 RecordIO 文件:.rec) containing the packed images, and two index files (.idx) containing the offsets of these images inside the record files:$ ls dogscats*
dogscats_train.idx dogscats_train.lst dogscats_train.rec
dogscats_val.idx dogscats_val.lst dogscats_val.rec
-
我们将 RecordIO 文件存储在 S3 上,因为我们稍后会用到它们:
$ aws s3 cp dogscats_train.rec s3://sagemaker-eu-west-1-123456789012/dogscats/input/train/ $ aws s3 cp dogscats_val.rec s3://sagemaker-eu-west-1-123456789012/dogscats/input/validation/
这要简单得多,不是吗?im2rec 还提供了额外的选项来调整图像大小等等。它还可以将数据集分割成几个块,这是 管道模式 和 分布式训练 中一个有用的技术。我们将在第九章,“扩展你的训练任务”中学习这些内容。
现在,让我们继续使用 RecordIO 文件进行目标检测。
将目标检测数据集转换为 RecordIO
过程非常相似。一个主要的区别是列表文件的格式。我们不仅要处理类标签,还需要存储边界框。
让我们看看这对 Pascal VOC 数据集意味着什么。以下图像取自该数据集:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_4.jpg
图 5.4 – 来自 Pascal VOC 数据集的示例图像
它包含三个椅子。标注信息存储在一个单独的 XML 文件中,文件内容略有简化,如下所示:
<annotation>
<folder>VOC2007</folder>
<filename>003988.jpg</filename>
. . .
<object>
<name>chair</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>1</xmin>
<ymin>222</ymin>
<xmax>117</xmax>
<ymax>336</ymax>
</bndbox>
</object>
<object>
<name>chair</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>1</difficult>
<bndbox>
<xmin>429</xmin>
<ymin>216</ymin>
<xmax>448</xmax>
<ymax>297</ymax>
</bndbox>
</object>
<object>
<name>chair</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>1</difficult>
<bndbox>
<xmin>281</xmin>
<ymin>149</ymin>
<xmax>317</xmax>
<ymax>211</ymax>
</bndbox>
</object>
</annotation>
将此转换为列表文件条目的格式应如下所示:
9404 2 6 8.0000 0.0022 0.6607 0.2612 1.0000 0.0000 8.0000 0.9576 0.6429 1.0000 0.8839 1.0000 8.0000 0.6272 0.4435 0.7076 0.6280 1.0000 VOC2007/JPEGImages/003988.jpg
让我们解码每一列:
-
9404是唯一的图像标识符。 -
2是包含头信息的列数,包括这一列。 -
6是标注信息的列数。这六列分别是类别标识符、四个边界框坐标,以及一个标志,告诉我们该对象是否难以看到(我们不会使用它)。 -
以下是第一个对象的信息:
a)
8是类别标识符。这里,8代表chair类别。b)
0.0022 0.6607 0.2612 1.0000是0表示该对象不困难的相对坐标。 -
对于第二个对象,我们有如下信息:
a)
8是类别标识符。b)
0.9576 0.6429 1.0000 0.8839是第二个对象的坐标。c)
1表示该对象是困难的。 -
第三个对象具有以下信息:
a)
8是类别标识符。b)
0.6272 0.4435 0.7076 0.628是第三个对象的坐标。c)
1表示该对象是困难的。 -
VOC2007/JPEGImages/003988.jpg是图片的路径。
那么,我们如何将成千上万的 XML 文件转换为几个列表文件呢?除非你喜欢写解析器,否则这并不是一项非常有趣的任务。
幸运的是,我们的工作已经简化了。Apache MXNet 包含一个 Python 脚本 prepare_dataset.py,可以处理这项任务。让我们看看它是如何工作的:
-
对于接下来的步骤,我建议使用至少有 10 GB 存储的 Amazon Linux 2 EC2 实例。以下是设置步骤:
$ sudo yum -y install git python3-devel python3-pip opencv opencv-devel opencv-python $ pip3 install mxnet opencv-python --user $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ec2-user/.local/lib/python3.7/site-packages/mxnet/ $ sudo ldconfig -
使用
wget下载 2007 和 2012 版本的 Pascal VOC 数据集,并将它们提取到同一个目录中:$ mkdir pascalvoc $ cd pascalvoc $ wget https://data.deepai.org/PascalVOC2012.zip $ wget https://data.deepai.org/PASCALVOC2007.zip $ unzip PascalVOC2012.zip $ unzip PASCALVOC2007.zip $ mv VOC2012 VOCtrainval_06-Nov-2007/VOCdevkit -
克隆 Apache MXNet 仓库 (
github.com/apache/incubator-mxnet/):$ git clone --single-branch --branch v1.4.x https://github.com/apache/incubator-mxnet -
运行
prepare_dataset.py脚本来构建我们的训练数据集,将 2007 和 2012 版本的训练集和验证集合并:$ cd VOCtrainval_06-Nov-2007 $ python3 ../incubator-mxnet/example/ssd/tools/prepare_dataset.py --dataset pascal --year 2007,2012 --set trainval --root VOCdevkit --target VOCdevkit/train.lst $ mv VOCdevkit/train.* .. -
让我们遵循类似的步骤来生成验证数据集,使用 2007 版本的测试集:
$ cd ../VOCtest_06-Nov-2007 $ python3 ../incubator-mxnet/example/ssd/tools/prepare_dataset.py --dataset pascal --year 2007 --set test --root VOCdevkit --target VOCdevkit/val.lst $ mv VOCdevkit/val.* .. $ cd .. -
在顶级目录中,我们看到脚本生成的文件。可以随意查看文件列表,它们应该与之前展示的格式一致:
train.idx train.lst train.rec val.idx val.lst val.rec -
让我们将 RecordIO 文件存储到 S3,因为我们稍后会使用它们:
$ aws s3 cp train.rec s3://sagemaker-eu-west-1-123456789012/pascalvoc/input/train/ $ aws s3 cp val.rec s3://sagemaker-eu-west-1-123456789012/pascalvoc/input/validation/
prepare_dataset.py 脚本确实简化了很多事情。它还支持 COCO 数据集 (cocodataset.org),并且工作流程非常相似。
那么,如何转换其他公共数据集呢?嗯,结果可能因情况而异。你可以在 gluon-cv.mxnet.io/build/examples_datasets/index.html 上找到更多示例。
RecordIO 绝对是一个进步。然而,在处理自定义数据集时,很可能你需要编写自己的列表文件生成器。这虽然不是一件大事,但也是额外的工作。
使用Amazon SageMaker Ground Truth标注的数据集解决了这些问题。让我们看看它是如何工作的!
与 SageMaker Ground Truth 文件合作
在第二章《数据准备技巧处理》中,您了解了 SageMaker Ground Truth 工作流及其结果,一个增强的清单文件。该文件采用JSON Lines格式:每个 JSON 对象描述了一个特定的标注。
这是我们在第二章《数据准备技巧处理》中运行的语义分割任务的一个示例(对于其他任务类型,流程相同)。我们看到原始图像和分割掩模的路径,以及颜色映射信息,告诉我们如何将掩模颜色与类别匹配:
{"source-ref":"s3://julien-sagemaker-book/chapter2/cat/cat1.jpg",
"my-cat-job-ref":"s3://julien-sagemaker-book/chapter2/cat/output/my-cat-job/annotations/consolidated-annotation/output/0_2020-04-21T13:48:00.091190.png",
"my-cat-job-ref-metadata":{
"internal-color-map":{
"0":{"class-name":"BACKGROUND","hex-color": "#ffffff",
"confidence": 0.8054600000000001},
"1":{"class-name":"cat","hex-color": "#2ca02c",
"confidence":0.8054600000000001}
},
"type":"groundtruth/semantic-segmentation",
"human-annotated":"yes",
"creation-date":"2020-04-21T13:48:00.562419",
"job-name":"labeling-job/my-cat-job"}}
以下是前面 JSON 文档中引用的图像:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_5.jpg
图 5.5 – 原始图像和分割图像
这正是我们训练模型所需要的。事实上,我们可以将增强的清单直接传递给 SageMaker 的Estimator,完全不需要任何数据处理。
要使用指向 S3 中标注图像的增强清单,我们只需传递其位置和 JSON 属性的名称(在前面的示例中已突出显示):
training_data_channel = sagemaker.s3_input(
s3_data=augmented_manifest_file_path,
s3_data_type='AugmentedManifestFile',
attribute_names=['source-ref', 'my-job-cat-ref'])
就这样!这比我们之前看到的任何内容都要简单。
您可以在github.com/awslabs/amazon-sagemaker-examples/tree/master/ground_truth_labeling_jobs找到更多使用 SageMaker Ground Truth 的示例。
现在我们已经知道如何为训练准备图像数据集,让我们开始使用计算机视觉(CV)算法。
使用内置的计算机视觉(CV)算法
在本节中,我们将使用公共图像数据集,通过所有三种算法训练并部署模型。我们将涵盖从头开始训练和迁移学习两种方法。
训练图像分类模型
在这个第一个示例中,我们将使用图像分类算法来构建一个模型,分类我们在前一节准备的狗与猫数据集。我们将首先使用图像格式进行训练,然后使用 RecordIO 格式。
使用图像格式进行训练
我们将通过以下步骤开始训练:
-
在 Jupyter 笔记本中,我们定义了适当的数据路径:
import sagemaker sess = sagemaker.Session() bucket = sess.default_bucket() prefix = 'dogscats-images' s3_train_path = 's3://{}/{}/input/train/'.format(bucket, prefix) s3_val_path = 's3://{}/{}/input/val/'.format(bucket, prefix) s3_train_lst_path = 's3://{}/{}/input/train_lst/'.format(bucket, prefix) s3_val_lst_path = 's3://{}/{}/input/val_lst/'.format(bucket, prefix) s3_output = 's3://{}/{}/output/'.format(bucket, prefix) -
我们为图像分类算法配置
Estimator:from sagemaker.image_uris import retrieve region_name = sess.boto_session.boto_region_name container = retrieve('image-classification', region) ic = sagemaker.estimator.Estimator(container, sagemaker.get_execution_role(), instance_count=1, instance_type='ml.p3.2xlarge', output_path=s3_output)我们使用名为
ml.p3.2xlarge的 GPU 实例,这个实例对这个数据集提供了足够的计算力(在 eu-west-1 区域的价格为每小时$4.131)。超参数怎么样(
docs.aws.amazon.com/sagemaker/latest/dg/IC-Hyperparameter.html)?我们设置类别数(2)和训练样本数(22,500)。由于我们使用图像格式,我们需要显式调整图像大小,将最小尺寸设置为 224 像素。由于数据充足,我们决定从头开始训练。为了保持训练时间短,我们选择一个 18 层的ResNet模型,只训练 10 个 epochs:ic.set_hyperparameters(num_layers=18, use_pretrained_model=0, num_classes=2, num_training_samples=22500, resize=224, mini_batch_size=128, epochs=10) -
我们定义四个通道,设置它们的内容类型为
application/x-image:from sagemaker import TrainingInput train_data = TrainingInput ( s3_train_path, content_type='application/x-image') val_data = TrainingInput ( s3_val_path, content_type='application/x-image') train_lst_data = TrainingInput ( s3_train_lst_path, content_type='application/x-image') val_lst_data = TrainingInput ( s3_val_lst_path, content_type='application/x-image') s3_channels = {'train': train_data, 'validation': val_data, 'train_lst': train_lst_data, 'validation_lst': val_lst_data} -
我们按以下方式启动训练作业:
ic.fit(inputs=s3_channels)在训练日志中,我们看到数据下载大约需要 3 分钟。令人惊讶的是,我们还看到算法在训练之前构建 RecordIO 文件。这一步骤大约持续 1 分钟:
Searching for .lst files in /opt/ml/input/data/train_lst. Creating record files for dogscats-train.lst Done creating record files... Searching for .lst files in /opt/ml/input/data/validation_lst. Creating record files for dogscats-val.lst Done creating record files... -
训练开始时,我们看到一个 epoch 大约需要 22 秒:
Epoch[0] Time cost=22.337 Epoch[0] Validation-accuracy=0.605859 -
该作业总共持续了 506 秒(约 8 分钟),花费了我们(506/3600)*$4.131=$0.58。它达到了**91.2%**的验证准确率(希望您看到类似的结果)。考虑到我们甚至还没有调整超参数,这是相当不错的结果。
-
然后,我们在小型 CPU 实例上部署模型如下:
ic_predictor = ic.deploy(initial_instance_count=1, instance_type='ml.t2.medium') -
我们下载以下测试图像,并以
application/x-image格式发送进行预测。https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_6.jpgimport boto3, json import numpy as np with open('test.jpg', 'rb') as f: payload = f.read() payload = bytearray(payload) runtime = boto3.Session().client( service_name='runtime.sagemaker') response = runtime.invoke_endpoint( EndpointName=ic_predictor.endpoint_name, ContentType='application/x-image', Body=payload) result = response['Body'].read() result = json.loads(result) index = np.argmax(result) print(result[index], index)打印出概率和类别后,我们的模型指示这是一只狗,置信度为 99.997%,并且图像属于类别 1:
0.9999721050262451 1 -
完成后,我们按以下方式删除端点:
ic_predictor.delete_endpoint()
现在让我们使用 RecordIO 格式的数据集运行相同的训练作业。
训练 RecordIO 格式
唯一的区别在于我们如何定义输入通道。这次我们只需要两个通道,以便提供我们上传到 S3 的 RecordIO 文件。因此,内容类型设置为application/x-recordio:
from sagemaker import TrainingInput
prefix = 'dogscats'
s3_train_path=
's3://{}/{}/input/train/'.format(bucket, prefix)
s3_val_path=
's3://{}/{}/input/validation/'.format(bucket, prefix)
train_data = TrainingInput(
s3_train_path,
content_type='application/x-recordio')
validation_data = TrainingInput(
s3_val_path,
content_type='application/x-recordio')
再次训练时,我们看到数据下载需要 1 分钟,文件生成步骤已消失。虽然从单次运行中很难得出任何结论,但使用 RecordIO 数据集通常会节省您时间和金钱,即使在单个实例上进行训练。
狗与猫数据集每类样本超过 10,000 个,足以从头开始训练。现在,让我们尝试一个数据集,情况不同。
微调图像分类模型
请考虑Caltech-256数据集,这是一个受欢迎的公共数据集,包含 256 类共 15,240 张图像,还有一个混乱的类别(www.vision.caltech.edu/Image_Datasets/Caltech256/)。浏览图像类别,我们发现所有类别都只有少量样本。例如,“鸭子”类仅有 60 张图像:用这么少的数据,无论多么复杂的深度学习算法都很难提取出鸭子的独特视觉特征。
在这种情况下,从头开始训练根本不可行。相反,我们将使用一种叫做迁移学习的技术,在一个已经在非常大且多样化的图像数据集上训练过的网络基础上开始训练。ImageNet(www.image-net.org/)可能是最流行的预训练选择,包含 1,000 个类别和数百万张图像。
预训练的网络已经学会了如何从复杂图像中提取特征。假设我们数据集中的图像与预训练数据集中的图像足够相似,我们的模型应该能够继承这些知识。只需在我们的数据集上训练几个周期,我们应该能够微调预训练模型,以适应我们的数据和类别。
让我们看看如何通过 SageMaker 轻松实现这一点。实际上,我们将重用前一个示例中的代码,只做最小的修改。我们开始吧:
-
我们下载 Caltech-256 数据集的 RecordIO 格式版本(如果你愿意,可以下载图像格式,并转换成 RecordIO 格式:实践出真知!):
%%sh wget http://data.mxnet.io/data/caltech-256/caltech-256-60-train.rec wget http://data.mxnet.io/data/caltech-256/caltech-256-60-val.rec -
我们将数据集上传到 S3:
import sagemaker session = sagemaker.Session() bucket = session.default_bucket() prefix = 'caltech256/' s3_train_path = session.upload_data( path='caltech-256-60-train.rec', bucket=bucket, key_prefix=prefix+'input/train') s3_val_path = session.upload_data( path='caltech-256-60-val.rec', bucket=bucket, key_prefix=prefix+'input/validation') -
我们配置
Estimator函数来进行图像分类算法。代码与前一个示例中的步骤 2完全相同。 -
我们使用
use_pretrained_network来 1. 预训练网络的最终全连接层将根据我们数据集中的类别数量进行调整,并将其权重赋予随机值。我们设置正确的类别数量(256+1)和训练样本数,如下所示:
ic.set_hyperparameters(num_layers=50, use_pretrained_model=1, num_classes=257, num_training_samples=15240, learning_rate=0.001, epochs=5)由于我们在进行微调,所以我们只训练 5 个周期,并使用较小的学习率 0.001。
-
我们配置通道,并启动训练任务。代码与前一个示例中的步骤 4完全相同。
-
在经过 5 个周期和 272 秒后,我们在训练日志中看到以下指标:
Epoch[4] Validation-accuracy=0.8119这对于仅仅几分钟的训练来说已经相当不错。即使数据量充足,从头开始也需要更长的时间才能获得这样的结果。
-
为了部署和测试模型,我们将重用前一个示例中的步骤 7-9。
如你所见,迁移学习是一种非常强大的技术。即使数据量很少,它也能提供优秀的结果。你还会训练更少的周期,从而节省时间和资金。
现在,让我们进入下一个算法——物体检测。
训练一个物体检测模型
在这个例子中,我们将使用物体检测算法,在前一部分准备好的 Pascal VOC 数据集上构建一个模型:
-
我们从定义数据路径开始:
import sagemaker sess = sagemaker.Session() bucket = sess.default_bucket() prefix = 'pascalvoc' s3_train_data = 's3://{}/{}/input/train'.format(bucket, prefix) s3_validation_data = 's3://{}/{}/input/validation'.format(bucket, prefix) s3_output_location = 's3://{}/{}/output'.format(bucket, prefix) -
我们选择物体检测算法:
from sagemaker.image_uris import retrieve region = sess.boto_region_name container = retrieve('object-detection', region) -
我们配置
Estimator:od = sagemaker.estimator.Estimator( container, sagemaker.get_execution_role(), instance_count=1, instance_type='ml.p3.2xlarge', output_path=s3_output_location) -
我们设置所需的超参数。我们选择一个预训练的 ResNet-50 网络作为基础网络。我们设置类别数量和训练样本数。我们决定训练 30 个周期,应该足够开始看到结果:
od.set_hyperparameters(base_network='resnet-50', use_pretrained_model=1, num_classes=20, num_training_samples=16551, epochs=30) -
然后我们配置两个通道,并启动训练任务:
from sagemaker.session import TrainingInput train_data = TrainingInput ( s3_train_data, content_type='application/x-recordio') validation_data = TrainingInput ( s3_validation_data, content_type='application/x-recordio') data_channels = {'train': train_data, 'validation': validation_data} od.fit(inputs=data_channels)在SageMaker 组件与注册表 | 实验与试验中选择我们的任务,我们可以看到接近实时的指标和图表。下一张图显示了验证集的平均精度指标(mAP),这是目标检测模型的一个关键指标。
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_7.jpg
图 5.7 – 验证集 mAP
请浏览其他标签页(指标、参数、工件等)。它们包含了关于特定任务的所有信息。请注意右上角的停止训练任务按钮,您可以随时使用它来终止任务。
-
训练持续了 1 小时 40 分钟。这个模型相当庞大!我们得到了平均精度指标(mAP)为 0.5151。生产环境中使用需要更多的训练,但我们现在应该可以测试模型了。
-
由于模型的复杂性,我们将其部署到一个更大的 CPU 实例上:
od_predictor = od.deploy( initial_instance_count = 1, instance_type= 'ml.c5.2xlarge') -
我们从维基百科下载一张测试图像,并用我们的模型进行预测:
import boto3,json with open('test.jpg', 'rb') as image: payload = image.read() payload = bytearray(payload) runtime = boto3.Session().client( service_name='runtime.sagemaker') response = runtime.invoke_endpoint( EndpointName=od_predictor.endpoint_name, ContentType='image/jpeg', Body=payload) response = response['Body'].read() response = json.loads(response) -
响应包含一个预测列表。每个单独的预测包含一个类别标识符、置信度得分以及边界框的相对坐标。以下是响应中的前几个预测:
{'prediction': [[14.0, 0.7515302300453186, 0.39770469069480896, 0.37605002522468567, 0.5998836755752563, 1.0], [14.0, 0.6490200161933899, 0.8020403385162354, 0.2027685046195984, 0.9918708801269531, 0.8575668931007385]利用这些信息,我们可以在源图像上绘制边界框。为了简洁起见,我不会在此处包含代码,但您可以在本书的 GitHub 仓库中找到。以下输出展示了结果:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_8.jpg
图 5.8 – 测试图像
-
当我们完成后,我们删除端点,如下所示:
od_predictor.delete_endpoint()
这就结束了我们的目标检测探索。我们还有一个算法要介绍:语义分割。
训练语义分割模型
在这个示例中,我们将使用语义分割算法,在之前章节中准备的 Pascal VOC 数据集上构建模型:
-
如常,我们定义数据路径,如下所示:
import sagemaker sess = sagemaker.Session() bucket = sess.default_bucket() prefix = 'pascalvoc-segmentation' s3_train_data = 's3://{}/{}/input/train'.format(bucket, prefix) s3_validation_data = 's3://{}/{}/input/validation'.format(bucket, prefix) s3_train_annotation_data = 's3://{}/{}/input/train_annotation'.format(bucket, prefix) s3_validation_annotation_data = 's3://{}/{}/input/validation_annotation'.format(bucket, prefix) s3_output_location = 's3://{}/{}/output'.format(bucket, prefix) -
我们选择语义分割算法,并配置
Estimator函数:from sagemaker.image_uris import retrieve container = retrieve('semantic-segmentation', region) seg = sagemaker.estimator.Estimator( container, sagemaker.get_execution_role(), instance_count = 1, instance_type = 'ml.p3.2xlarge', output_path = s3_output_location) -
我们定义了所需的超参数。我们选择了一个预训练的 ResNet-50 网络作为基础网络,并选择一个预训练的FCN进行检测。我们设置了类别数和训练样本数。再次,我们选择了 30 个训练周期,这应该足以开始看到结果:
seg.set_hyperparameters(backbone='resnet-50', algorithm='fcn', use_pretrained_model=True, num_classes=21, num_training_samples=1464, epochs=30) -
我们配置四个通道,设置源图像的内容类型为
image/jpeg,掩膜图像的内容类型为image/png。然后,我们启动训练任务:from sagemaker import TrainingInput train_data = TrainingInput( s3_train_data, content_type='image/jpeg') validation_data = TrainingInput( s3_validation_data, content_type='image/jpeg') train_annotation = TrainingInput( s3_train_annotation_data, content_type='image/png') validation_annotation = TrainingInput( s3_validation_annotation_data, content_type='image/png') data_channels = { 'train': train_data, 'validation': validation_data, 'train_annotation': train_annotation, 'validation_annotation':validation_annotation } seg.fit(inputs=data_channels) -
训练大约持续了 32 分钟。我们得到了平均交并比指标(mIOU)为 0.4874,如下图所示:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_9.jpg
图 5.9 – 验证集 mIOU
-
我们将模型部署到一个 CPU 实例上:
seg_predictor = seg.deploy( initial_instance_count=1, instance_type='ml.c5.2xlarge') -
一旦端点开始服务,我们获取一张测试图像,并将其作为字节数组发送进行预测,附上正确的内容类型:
!wget -O test.jpg https://bit.ly/3yhXB9l filename = 'test.jpg' with open(filename, 'rb') as f: payload = f.read() payload = bytearray(payload) runtime = boto3.Session().client( service_name='runtime.sagemaker') response = runtime.invoke_endpoint( EndpointName=od_predictor.endpoint_name, ContentType='image/jpeg', Body=payload) response = response['Body'].read() response = json.loads(response) -
使用Python 图像库(PIL),我们处理响应掩膜并显示它:
import PIL from PIL import Image import numpy as np import io num_classes = 21 mask = np.array(Image.open(io.BytesIO(response))) plt.imshow(mask, vmin=0, vmax=num_classes-1, cmap='gray_r') plt.show()以下图像展示了源图像和预测的掩码。这个结果很有前景,随着更多训练,它会得到改进:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_05_10.jpg
图 5.10 – 测试图像和分割后的测试图像
-
再次使用
application/x-protobuf接收类型进行预测时,我们会收到源图像中所有像素的类别概率。响应是一个 protobuf 缓冲区,我们将其保存到一个二进制文件中:response = runtime.invoke_endpoint( EndpointName=seg_predictor.endpoint_name, ContentType='image/jpeg', Accept='application/x-protobuf', Body=payload) result = response['Body'].read() seg_predictor.accept = 'application/x-protobuf' response = seg_predictor.predict(img) results_file = 'results.rec' with open(results_file, 'wb') as f: f.write(response) -
该缓冲区包含两个张量:一个是概率张量的形状,另一个是实际的概率值。我们使用
values张量加载它们,values张量描述的是一个 289x337 大小的图像,其中每个像素都被分配了 21 个概率值,分别对应 Pascal VOC 的每个类别。您可以验证 28933721=2,045,253。 -
知道这些之后,我们现在可以重新塑形
values张量,获取(0,0)像素的 21 个概率,并打印出概率最高的类别标识符:mask = np.reshape(np.array(values), shape) pixel_probs = mask[0,:,0,0] print(pixel_probs) print(np.argmax(pixel_probs))这是输出结果:
[9.68291104e-01 3.72813665e-04 8.14868137e-04 1.22414716e-03 4.57380433e-04 9.95167647e-04 4.29908326e-03 7.52388616e-04 1.46311778e-03 2.03254796e-03 9.60668200e-04 1.51833100e-03 9.39570891e-04 1.49350625e-03 1.51627266e-03 3.63648031e-03 2.17934581e-03 7.69103528e-04 3.05095245e-03 2.10589729e-03 1.12741732e-03] 0最高概率位于索引 0:像素(0,0)的预测类别为类别 0,即背景类别。
-
完成后,我们可以按如下方式删除端点:
seg_predictor.delete_endpoint()
总结
如您所见,这三种算法使得训练计算机视觉(CV)模型变得简单。即使使用默认的超参数,我们也能很快得到不错的结果。不过,我们开始感觉到需要扩展训练工作。别担心,一旦相关内容在后续章节中讲解完毕,我们会再次回顾一些 CV 示例,并大幅扩展它们!
本章您学习了图像分类、目标检测和语义分割算法。您还学习了如何准备图像、RecordIO 和 SageMaker Ground Truth 格式的数据集。标注和准备数据是一个非常关键的步骤,需要大量的工作,我们在这一部分进行了详细的讲解。最后,您学习了如何使用 SageMaker SDK 来训练和部署这三种算法的模型,并了解如何解读结果。
在下一章中,您将学习如何使用内置算法进行自然语言处理。
第六章:训练自然语言处理模型
在前一章中,您学习了如何使用 SageMaker 的内置算法进行 计算机视觉(CV)任务,解决包括图像分类、物体检测和语义分割等问题。
自然语言处理(NLP)是机器学习中另一个非常有前景的领域。事实上,NLP 算法在建模语言和从非结构化文本中提取上下文方面已经证明非常有效。凭借这一点,像搜索和翻译应用程序以及聊天机器人等应用程序如今已经非常普遍。
在本章中,您将学习专门为 NLP 任务设计的内置算法,我们将讨论您可以用它们解决哪些类型的问题。与前一章一样,我们还将详细讲解如何准备实际数据集,如亚马逊客户评论。当然,我们还将训练和部署模型。我们将在以下主题中覆盖所有内容:
-
发现亚马逊 SageMaker 中的 NLP 内置算法
-
准备自然语言数据集
-
使用内置的 NLP 算法
技术要求
您需要一个 Amazon Web Services(AWS)账户才能运行本章中的示例。如果您还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。您还应该了解 AWS 免费套餐(aws.amazon.com/free/),它允许您在一定的使用限制内免费使用许多 AWS 服务。
您需要为您的账户安装并配置 AWS 命令行界面(CLI)工具(aws.amazon.com/cli/)。
您需要一个可用的 Python 3.x 环境。虽然安装 Anaconda 发行版(www.anaconda.com/)不是必需的,但强烈推荐安装,因为它包括我们将需要的许多项目(Jupyter、pandas、numpy 等)。
本书中的代码示例可以在 GitHub 上找到,链接为 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。您需要安装 Git 客户端才能访问这些代码(git-scm.com/)。
发现亚马逊 SageMaker 中的 NLP 内置算法
SageMaker 包含四种 NLP 算法,支持 有监督学习(SL)和 无监督学习(UL)场景。在本节中,您将了解这些算法,它们能解决哪些问题,以及它们的训练场景。让我们先来看一下我们将讨论的算法概述:
-
BlazingText 构建文本分类模型(SL)或计算词向量(UL)。BlazingText 是亚马逊发明的算法。
-
LDA 构建 UL 模型,将一组文本文档分组为多个主题。这种技术称为 主题建模。
-
NTM 是另一个基于神经网络的主题建模算法,它能让你更深入地了解主题是如何构建的。
-
Sequence to Sequence(seq2seq)构建深度学习(DL)模型,从一系列输入标记预测一系列输出标记。
发现 BlazingText 算法
BlazingText 算法是由亚马逊发明的。你可以在dl.acm.org/doi/10.1145/3146347.3146354阅读更多关于它的内容。BlazingText 是FastText的演进,FastText 是 Facebook 开发的一个高效文本分类和表示学习库 (fasttext.cc)。
它让你能够训练文本分类模型,并计算词向量。词向量也叫做嵌入,是许多自然语言处理(NLP)任务的基石,比如寻找词语相似度、词语类比等。Word2Vec 是计算这些向量的领先算法之一 (arxiv.org/abs/1301.3781),而 BlazingText 正是实现了这一算法。
BlazingText 的主要改进是能够在图形处理单元(GPU)实例上进行训练,而 FastText 只支持中央处理单元(CPU)实例。
速度提升是显著的,这也是它名字的来源:“blazing”比“fast”更快!如果你对基准测试感兴趣,一定会喜欢这篇博客文章:aws.amazon.com/blogs/machine-learning/amazon-sagemaker-blazingtext-parallelizing-word2vec-on-multiple-cpus-or-gpus/。
最后,BlazingText 与 FastText 完全兼容。你可以非常轻松地导出并测试模型,正如你稍后在本章中将看到的那样。
发现 LDA 算法
这个 UL 算法使用一种生成技术,叫做主题建模,来识别大规模文本文档集合中的主题。它首次应用于机器学习是在 2003 年 (jmlr.csail.mit.edu/papers/v3/blei03a.html)。
请注意,LDA 并不是一个分类算法。你给它传递的是要构建的主题数量,而不是你期望的主题列表。用福雷斯特·甘普的话说:“主题建模就像一盒巧克力,你永远不知道自己会得到什么。”
LDA 假设文档集合中的每个文本都是由几个潜在(即“隐藏”)主题生成的。一个主题通过词语概率分布来表示。对于文档集合中的每个词,这个分布给出该词在由该主题生成的文档中出现的概率。例如,在“金融”主题中,分布会为“收入”、“季度”或“收益”等词汇给出较高的概率,而“弩炮”或“鸭嘴兽”等词汇则会有较低的概率(我想应该是这样)。
主题分布不是独立考虑的。它们通过Dirichlet 分布表示,这是单变量分布的多变量推广(en.wikipedia.org/wiki/Dirichlet_distribution)。这个数学对象赋予了算法它的名称。
给定词汇表中的词语数量和潜在主题的数量,LDA 算法的目的是建立一个尽可能接近理想 Dirichlet 分布的模型。换句话说,它会尝试将词语分组,使得分布尽可能良好,并与指定的主题数量匹配。
训练数据需要仔细准备。每个文档需要转换为词袋(BoW)表示:每个词语被一对整数替换,表示一个唯一的词语标识符(ID)和文档中该词的出现次数。最终的数据集可以保存为逗号分隔值(CSV)格式或RecordIO 包装的 protobuf格式,这是一种我们在第四章《训练机器学习模型》中已经学习过的技术。
一旦模型训练完成,我们可以对任何文档进行评分,并为每个主题打分。预期是,包含相似词语的文档应该具有相似的得分,从而使我们能够识别它们的主要主题。
探索 NTM 算法
NTM 是另一种主题建模算法。你可以在arxiv.org/abs/1511.06038上阅读更多关于它的信息。以下博客文章也总结了论文的关键要素:aws.amazon.com/blogs/machine-learning/amazon-sagemaker-neural-topic-model-now-supports-auxiliary-vocabulary-channel-new-topic-evaluation-metrics-and-training-subsampling/。
与 LDA 一样,文档需要转换为 BoW 表示,并且数据集可以保存为 CSV 格式或 RecordIO 包装的 protobuf 格式。
对于训练,NTM 采用一种完全不同的方法,基于神经网络——更准确地说,是基于编码器架构(en.wikipedia.org/wiki/Autoencoder)。真正的深度学习方式是,编码器在文档的小批量上进行训练。它通过反向传播和优化调整网络参数,试图学习它们的潜在特征。
与 LDA 不同,NTM 可以告诉我们每个主题中最具影响力的词语。它还为每个主题提供了两个度量指标,词嵌入主题一致性(WETC)和主题独特性(TU)。这些在这里有更详细的描述:
-
WETC 告诉我们主题词之间的语义相似度。该值介于 0 和 1 之间,值越高表示越好。它是通过预训练的 全球向量 (GloVe) 模型中的对应词向量的 余弦相似度 (
en.wikipedia.org/wiki/Cosine_similarity) 计算得到的(这是另一个类似于 Word2Vec 的算法)。 -
TU 告诉我们该主题的独特性——也就是说,其词汇是否在其他主题中出现过。该值介于 0 和 1 之间,得分越高,主题越独特。
一旦模型训练完成,我们就可以对文档进行评分,并为每个主题获得评分。
发现 seq2sea 算法
seq2seq 算法基于 长短期记忆 (LSTM) 神经网络 (arxiv.org/abs/1409.3215)。顾名思义,seq2seq 可以被训练成将一系列标记映射到另一系列标记。它的主要应用是机器翻译,训练大规模的双语语料库,例如 统计机器翻译研讨会 (WMT) 数据集 (www.statmt.org/wmt20/)。
除了在 SageMaker 中提供的实现外,AWS 还将 seq2seq 算法打包成了一个开源项目,AWS Sockeye (github.com/awslabs/sockeye),该项目还包括数据集准备工具。
本章不会介绍 seq2seq。详细讲解会占用太多篇幅,而且没有必要重复 Sockeye 文档中已经包含的内容。
你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/introduction_to_amazon_algorithms/seq2seq_translation_en-de 找到一个 seq2seq 示例。不幸的是,它使用了低级别的 boto3 应用程序编程接口 (API),我们将在 第十二章,自动化机器学习工作流 中讲解。尽管如此,它仍然是一本有价值的读物,而且你不会遇到太多麻烦就能搞明白。
使用 NLP 算法进行训练
就像 CV 算法一样,训练是相对简单的,尤其是在使用 SageMaker 软件开发工具包 (SDK) 时。到目前为止,你应该已经熟悉了工作流程和 API,并且我们将在本章中继续使用它们。
为 NLP 算法准备数据是另一回事。首先,现实生活中的数据集通常非常庞大。在本章中,我们将处理数百万个样本和数亿个单词。当然,这些数据需要清洗、处理,并转换为算法所期望的格式。
在本章中,我们将使用以下技术:
-
使用
pandas库加载和清理数据(pandas.pydata.org) -
使用自然语言工具包(NLTK)库去除停用词并进行词形还原(
www.nltk.org) -
使用
spaCy库进行分词(spacy.io/) -
使用
gensim库构建词汇表并生成 BoW 表示(radimrehurek.com/gensim/) -
使用Amazon SageMaker Processing运行数据处理任务,我们在第二章中学习过,处理数据准备技术
当然——这不是一本 NLP 书籍,我们不会深入数据处理。但这将是一次非常有趣的学习机会,希望能了解一些流行的开源 NLP 工具。
准备自然语言数据集
在上一章的 CV 算法中,数据准备侧重于数据集所需的技术格式(图像格式、RecordIO或增强清单)。图像本身没有被处理。
对于 NLP 算法,情况完全不同。文本需要经过大量处理、转换,并以正确的格式保存。在大多数学习资源中,这些步骤通常会被简化或忽略。数据已经“自动”准备好进行训练,这让读者感到沮丧,有时甚至不知从何处开始准备自己的数据集。
这里没有这种情况!在本节中,您将学习如何准备不同格式的 NLP 数据集。再一次,准备好学习大量内容吧!
让我们从为 BlazingText 准备数据开始。
使用 BlazingText 准备分类数据
BlazingText 期望标签输入数据与 FastText 相同的格式,详见此处:
-
一个纯文本文件,每行一个样本。
-
每行包含两个字段,如下所示:
a) 以
__label__LABELNAME__形式的标签b) 文本本身,形成以空格分隔的词元(单词和标点符号)
让我们开始工作,准备一个用于情感分析(正面、中立或负面)的客户评论数据集。我们将使用Amazon Customer Reviews数据集,可以在s3.amazonaws.com/amazon-reviews-pds/readme.html找到。那应该是充足的现实数据。
在开始之前,请确保您有足够的存储空间。在这里,我使用的是具有 10 千兆字节(GB)存储的笔记本实例。我还选择了一个 C5 实例类型,以更快地运行处理步骤。我们将按以下方式进行:
-
让我们通过运行以下代码来下载相机评论:
%%sh aws s3 cp s3://amazon-reviews-pds/tsv/amazon_reviews_us_Camera_v1_00.tsv.gz /tmp -
我们使用
pandas加载数据,忽略任何会引发错误的行,并删除任何包含缺失值的行。以下代码演示了这一过程:data = pd.read_csv( '/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', sep='\t', compression='gzip', error_bad_lines=False, dtype='str') data.dropna(inplace=True) -
我们打印数据的形状和列名,如下所示:
print(data.shape) print(data.columns)这将给我们以下输出:
(1800755, 15) Index(['marketplace','customer_id','review_id','product_id','product_parent', 'product_title','product_category', 'star_rating','helpful_votes','total_votes','vine', 'verified_purchase','review_headline','review_body', 'review_date'], dtype='object') -
180 万行!我们保留了 100,000 行,足够满足我们的需求。我们还删除了除
star_rating和review_body之外的所有列,以下代码片段展示了这一过程:data = data[:100000] data = data[['star_rating', 'review_body']] -
基于星级评分,我们新增了一个名为
label的列,标签格式已正确。你不得不佩服pandas让这一切变得如此简单。然后,我们删除了star_rating列,以下代码片段展示了这一过程:data['label'] = data.star_rating.map({ '1': '__label__negative__', '2': '__label__negative__', '3': '__label__neutral__', '4': '__label__positive__', '5': '__label__positive__'}) data = data.drop(['star_rating'], axis=1) -
BlazingText 期望每行的开头是标签,因此我们将
label列移到前面,如下所示:data = data[['label', 'review_body']] -
数据现在应该像这样:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_1.jpg
图 6.1 – 查看数据集
-
BlazingText 期望标记符号之间用空格分隔:每个单词和标点符号必须与下一个单词或符号用空格分隔开。让我们使用
nltk库中的便捷punkt分词器。根据你使用的实例类型,这可能需要几分钟。下面是你需要的代码:!pip -q install nltk import nltk nltk.download('punkt') data['review_body'] = data['review_body'].apply(nltk.word_tokenize) -
我们将标记符号连接成一个字符串,并将其转换为小写,如下所示:
data['review_body'] = data.apply(lambda row: "".join(row['review_body']) .lower(), axis=1) -
数据现在应该像这样(请注意,所有的标记符号都已正确地用空格分隔):https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_2.jpg
图 6.2 – 查看标记化后的数据集
-
最后,我们将数据集拆分为训练集(95%)和验证集(5%),并将这两个拆分保存为纯文本文件,以下代码片段展示了这一过程:
from sklearn.model_selection import train_test_split training, validation = train_test_split(data, test_size=0.05) np.savetxt('/tmp/training.txt', training.values, fmt='%s') np.savetxt('/tmp/validation.txt', validation.values, fmt='%s') -
打开其中一个文件,你应该会看到许多类似这样的行:
__label__neutral__ really works for me , especially on the streets of europe . wished it was less expensive though . the rain cover at the base really works . the padding which comes in contact with your back though will suffocate & make your back sweaty .
数据准备其实并不算太糟糕,对吧?不过,标记化过程运行了一两分钟。现在,想象一下如果是在数百万个样本上运行这个过程会怎样。没错,你可以启动一个更大的环境来运行它,在 SageMaker Studio 上运行。不过,你还得为使用它支付更多费用,特别是当只有这一道步骤需要额外计算力时,这显然是浪费的。另外,想象一下如果你需要对许多其他数据集运行同样的脚本。你会想一次又一次地手动做这些事情,等待 20 分钟,每次还要希望你的笔记本不会崩溃吗?我想应该不会!
你已经知道这两个问题的答案了。那就是 Amazon SageMaker Processing,我们在 第二章《数据准备技术》中学过这个内容。你应该能够两全其美,在最小且最便宜的环境中进行实验,并在需要更多资源时运行按需作业。日复一日,你将节省成本,并更快完成任务。
让我们将这些处理代码移到 SageMaker Processing。
为使用 BlazingText 进行分类准备数据,第 2 版
我们在 第二章《数据准备技术》中已经详细讨论了这个内容,所以这次我会讲得更快一些。我们将按如下步骤进行:
-
我们将数据集上传到 简单存储服务 (S3),如下所示:
import sagemaker session = sagemaker.Session() prefix = 'amazon-reviews-camera' input_data = session.upload_data( path='/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', key_prefix=prefix) -
我们通过运行以下代码来定义处理器:
from sagemaker.sklearn.processing import SKLearnProcessor sklearn_processor = SKLearnProcessor( framework_version='0.23-1', role= sagemaker.get_execution_role(), instance_type='ml.c5.2xlarge', instance_count=1) -
我们运行处理作业,传递处理脚本及其参数,如下所示:
from sagemaker.processing import ProcessingInput, ProcessingOutput sklearn_processor.run( code='preprocessing.py', inputs=[ ProcessingInput( source=input_data, destination='/opt/ml/processing/input') ], outputs=[ ProcessingOutput( output_name='train_data', source='/opt/ml/processing/train'), ProcessingOutput( output_name='validation_data', source='/opt/ml/processing/validation') ], arguments=[ '--filename', 'amazon_reviews_us_Camera_v1_00.tsv.gz', '--num-reviews', '100000', '--split-ratio', '0.05' ] ) -
简化的预处理脚本如下所示,完整版本可以在本书的 GitHub 仓库中找到。我们首先安装
nltk包,如下所示:import argparse, os, subprocess, sys import pandas as pd import numpy as np from sklearn.model_selection import train_test_split def install(package): subprocess.call([sys.executable, "-m", "pip", "install", package]) if __name__=='__main__': install('nltk') import nltk -
我们读取命令行参数,如下所示:
parser = argparse.ArgumentParser() parser.add_argument('--filename', type=str) parser.add_argument('--num-reviews', type=int) parser.add_argument('--split-ratio', type=float, default=0.1) args, _ = parser.parse_known_args() filename = args.filename num_reviews = args.num_reviews split_ratio = args.split_ratio -
我们读取输入数据集并进行处理,如下所示:
input_data_path = os.path.join('/opt/ml/processing/input', filename) data = pd.read_csv(input_data_path, sep='\t', compression='gzip', error_bad_lines=False, dtype='str') # Process data . . . -
最后,我们将数据分为训练集和验证集,并将其保存到两个文本文件中,如下所示:
training, validation = train_test_split( data, test_size=split_ratio) training_output_path = os.path.join(' /opt/ml/processing/train', 'training.txt') validation_output_path = os.path.join( '/opt/ml/processing/validation', 'validation.txt') np.savetxt(training_output_path, training.values, fmt='%s') np.savetxt(validation_output_path, validation.values, fmt='%s')
如你所见,将手动处理的代码转化为 SageMaker 处理作业并不难。你实际上还可以重用大部分代码,因为它涉及一些通用的主题,比如命令行参数、输入和输出。唯一的技巧是使用subprocess.call在处理容器内安装依赖项。
配备了这个脚本后,你现在可以按需大规模处理数据,而不必运行和管理长期的笔记本。
现在,让我们为另一个 BlazingText 场景——词向量——准备数据!
使用 BlazingText 准备词向量数据
BlazingText 让你能够轻松且大规模地计算词向量。它期望输入数据为以下格式:
-
一个纯文本文件,每行一个样本。
-
每个样本必须具有以空格分隔的标记(词语和标点符号)。
让我们处理与上一节相同的数据集,如下所示:
-
我们将需要
spaCy库,因此让我们安装它并一起安装其英语语言模型,如下所示:%%sh pip -q install spacy python -m spacy download en_core_web_sm python -m spacy validate -
我们使用
pandas加载数据,忽略任何导致错误的行。我们还会删除任何包含缺失值的行。无论如何,我们的数据量应该足够多。以下是你需要的代码:data = pd.read_csv( '/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', sep='\t', compression='gzip', error_bad_lines=False, dtype='str') data.dropna(inplace=True) -
我们保留 100,000 行,并且删除除了
review_body之外的所有列,如下所示:data = data[:100000] data = data[['review_body']]我们编写一个函数来使用
spaCy对评论进行分词,并将其应用到DataFrame中。这个步骤应该比前一个例子中使用nltk分词要明显更快,因为spaCy基于cython(cython.org)。以下是代码示例:import spacy spacy_nlp = spacy.load('en_core_web_sm') def tokenize(text): tokens = spacy_nlp.tokenizer(text) tokens = [ t.text for t in tokens ] return " ".join(tokens).lower() data['review_body'] = data['review_body'].apply(tokenize)现在数据应该是这样的:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_3.jpg
图 6.3 – 查看分词后的数据集
-
最后,我们将评论保存到一个纯文本文件中,如下所示:
import numpy as np np.savetxt('/tmp/training.txt', data.values, fmt='%s') -
打开这个文件,你应该看到每行一个分词后的评论,正如以下代码片段所示:
Ok perfect , even sturdier than the original !
在这里,我们也应该使用 SageMaker 处理来运行这些步骤。你可以在这本书的 GitHub 仓库中找到相应的笔记本和预处理脚本。
现在,让我们为 LDA 和 NTM 算法准备数据。
为 LDA 和 NTM 准备主题建模数据
在这个示例中,我们将使用 百万新闻头条 数据集(doi.org/10.7910/DVN/SYBGZL),该数据集也可以在 GitHub 仓库中找到。顾名思义,它包含来自澳大利亚新闻源 ABC 的一百万条新闻头条。与产品评论不同,头条新闻通常是非常简短的句子。构建一个主题模型将是一个有趣的挑战!
分词数据
正如你所预料的,两个算法都需要一个已经分词的数据集,所以我们按以下步骤进行:
-
我们需要安装
nltk和gensim库,步骤如下:%%sh pip -q install nltk gensim -
下载数据集后,我们使用
pandas完整加载它,如下所示:num_lines = 1000000 data = pd.read_csv('abcnews-date-text.csv.gz', compression='gzip', error_bad_lines=False, dtype='str', nrows=num_lines) -
数据应该是这样的:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_4.jpg
图 6.4 – 查看已分词的数据集
-
数据按日期排序,并且为了安全起见我们将其打乱。然后,我们运行以下代码删除
date列:data = data.sample(frac=1) data = data.drop(['publish_date'], axis=1) -
我们编写一个函数来清理和处理头条新闻。首先,我们去除所有标点符号和数字。使用
nltk,我们还会去除停用词——即那些非常常见且不添加任何上下文的单词,例如“this”,“any”等。为了减少词汇量并保持上下文,我们可以应用 词干提取(https://en.wikipedia.org/wiki/Stemming)或 词形还原(en.wikipedia.org/wiki/Lemmatisation),这两种是流行的自然语言处理技术。这里我们选择后者。根据你的实例类型,这个过程可能需要几分钟。以下是你需要的代码:import string import nltk from nltk.corpus import stopwords #from nltk.stem.snowball import SnowballStemmer from nltk.stem import WordNetLemmatizer nltk.download('stopwords') stop_words = stopwords.words('english') #stemmer = SnowballStemmer("english") wnl = WordNetLemmatizer() def process_text(text): for p in string.punctuation: text = text.replace(p, '') text = ''.join([c for c in text if not c.isdigit()]) text = text.lower().split() text = [w for w in text if not w in stop_words] #text = [stemmer.stem(w) for w in text] text = [wnl.lemmatize(w) for w in text] return text data['headline_text'] = data['headline_text'].apply(process_text) -
数据处理后应该是这样的:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_5.jpg
图 6.5 – 查看已词形还原的数据集
现在评论已经分词,我们需要将其转换为 BoW 表示,将每个单词替换为一个唯一的整数 ID 和其频率计数。
将数据转换为 BoW 表示
我们将按照以下步骤将评论转换为 BoW 表示:
-
gensim库正好包含了我们需要的功能!我们使用以下代码构建一个字典,它包含了文档集合中的所有单词:from gensim import corpora dictionary = corpora.Dictionary(data['headline_text']) print(dictionary)字典看起来是这样的:
Dictionary(83131 unique tokens: ['aba', 'broadcasting', 'community', 'decides', 'licence']...)这个数字看起来很高。如果维度太多,训练将会非常长,算法可能会很难拟合数据;例如,NTM 是基于神经网络架构的。输入层的大小将根据分词的数量来决定,因此我们需要保持维度合理。这样可以加速训练,并帮助编码器学习一个可管理的潜在特征数量。
-
我们可以回头再清理一下头条新闻。不过,我们使用
gensim的一个函数来移除极端单词——即那些非常稀有或非常频繁的异常词。然后,冒着风险,我们决定将词汇限制为剩余的前 512 个单词。是的——这不到 1%。以下是执行此操作的代码:dictionary.filter_extremes(keep_n=512) -
我们将词汇表写入一个文本文件。这不仅有助于我们检查哪些单词最重要,而且我们还将把这个文件作为额外的通道传递给 NTM 算法。训练模型时,你会明白为什么这很重要。执行此操作的代码示例如下:
with open('vocab.txt', 'w') as f: for index in range(0,len(dictionary)): f.write(dictionary.get(index)+'\n') -
我们使用字典为每个标题构建一个词袋(BoW)。它被存储在一个名为
tokens的新列中。当我们完成后,会删除文本评论。代码示例如下:data['tokens'] = data.apply(lambda row: dictionary.doc2bow(row['headline_text']), axis=1) data = data.drop(['headline_text'], axis=1) -
现在,数据应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_6.jpg
图 6.6 – 查看 BoW 数据集
如您所见,每个单词已经被替换为其唯一的 ID 以及在评论中的频率。例如,最后一行告诉我们单词#11 出现一次,单词#12 出现一次,依此类推。
数据处理现已完成。最后一步是将其保存为适当的输入格式。
保存输入数据
NTM 和 LDA 期望数据以 CSV 格式或 RecordIO 封装的 protobuf 格式提供。正如在第四章中关于矩阵分解的例子所讨论的,训练机器学习模型,我们正在处理的数据非常稀疏。任何给定的评论只包含词汇表中的少数几个单词。由于 CSV 格式是稠密格式,我们最终会得到大量的零频率单词。这可不是什么好主意!
再次,我们将使用lil_matrix,一个SciPy库。它的行数与评论数相等,列数与字典中的单词数相等。我们按以下方式进行:
-
我们按如下方式创建稀疏矩阵:
from scipy.sparse import lil_matrix num_lines = data.shape[0] num_columns = len(dictionary) token_matrix = lil_matrix((num_lines,num_columns)) .astype('float32') -
我们编写一个函数,将标题添加到矩阵中。对于每个令牌,我们只需在相应的列中写下其频率,如下所示:
def add_row_to_matrix(line, row): for token_id, token_count in row['tokens']: token_matrix[line, token_id] = token_count return -
然后,我们遍历标题并将其添加到矩阵中。快速提示:我们不能使用行索引值,因为它们可能大于行数。代码示例如下:
line = 0 for _, row in data.iterrows(): add_row_to_matrix(line, row) line+=1 -
最后的步骤是将这个矩阵写入
protobuf格式的内存缓冲区,并上传到 S3 以供将来使用,如下所示:import io, boto3 import sagemaker import sagemaker.amazon.common as smac buf = io.BytesIO() smac.write_spmatrix_to_sparse_tensor(buf, token_matrix, None) buf.seek(0) bucket = sagemaker.Session().default_bucket() prefix = 'headlines-lda-ntm' train_key = 'reviews.protobuf' obj = '{}/{}'.format(prefix, train_key)) s3 = boto3.resource('s3') s3.Bucket(bucket).Object(obj).upload_fileobj(buf) s3_train_path = 's3://{}/{}'.format(bucket,obj) -
构建(1000000,512)的矩阵需要几分钟。上传到 S3 后,我们可以看到它只有 42 兆字节(MB)。确实是小矩阵。代码示例如下:
$ aws s3 ls s3://sagemaker-eu-west-1-123456789012/amazon-reviews-ntm/training.protobuf 43884300 training.protobuf
这就是 LDA 和 NTM 的数据准备工作。现在,让我们看看如何使用SageMaker Ground Truth准备的文本数据集。
使用 SageMaker Ground Truth 标记的数据集
如在第二章中讨论的,数据准备技术处理,SageMaker Ground Truth 支持文本分类任务。我们完全可以使用它的输出构建一个用于 FastText 或 BlazingText 的数据集。
首先,我快速运行了一个文本分类任务,应用了两种标签之一:“aws_service”如果句子提到 AWS 服务,和“no_aws_service”如果没有提到。
一旦任务完成,我可以从 S3 获取 增强后的清单。它是 JavaScript 对象表示法行(JSON Lines)格式,下面是其中一个条目:
{"source":"With great power come great responsibility. The second you create AWS resources, you're responsible for them: security of course, but also cost and scaling. This makes monitoring and alerting all the more important, which is why we built services like Amazon CloudWatch, AWS Config and AWS Systems Manager.","my-text-classification-job":0,"my-text-classification-job-metadata":{"confidence":0.84,"job-name":"labeling-job/my-text-classification-job","class-name":"aws_service","human-annotated":"yes","creation-date":"2020-05-11T12:44:50.620065","type":"groundtruth/text-classification"}}
我们来写点 Python 代码,把它转成 BlazingText 格式怎么样?当然可以!来看看:
-
我们直接从 S3 加载增强后的清单,如下所示:
import pandas as pd bucket = 'sagemaker-book' prefix = 'chapter2/classif/output/my-text-classification-job/manifests/output' manifest = 's3://{}/{}/output.manifest'.format(bucket, prefix) data = pd.read_json(manifest, lines=True) -
数据看起来像这样:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_7.jpg
](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_7.jpg)
图 6.7 – 查看标注过的数据集
-
标签隐藏在
my-text-classification-job-metadata列中。我们将其提取到一个新列,如下所示:def get_label(metadata): return metadata['class-name'] data['label'] = data['my-text-classification-job-metadata'].apply(get_label) data = data[['label', 'source']] -
数据现在看起来像下面的截图所示。从此以后,我们可以应用分词等操作。很简单,不是吗?
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_7.jpg
](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_8.jpg)
图 6.8 – 查看处理过的数据集
现在,让我们构建 NLP 模型!
使用内置的 NLP 算法
在本节中,我们将使用 BlazingText、LDA 和 NTM 训练并部署模型。当然,我们将使用前一节准备的数据集。
使用 BlazingText 进行文本分类
BlazingText 让构建文本分类模型变得极其简单,尤其是如果你没有 NLP 技能。让我们看看,以下是步骤:
-
我们将训练和验证数据集上传到 S3。或者,我们可以使用 SageMaker 处理作业返回的输出路径。代码如下所示:
import sagemaker session = sagemaker.Session() bucket = session.default_bucket() prefix = 'amazon-reviews' s3_train_path = session.upload_data(path='/tmp/training.txt', bucket=bucket, key_prefix=prefix+'/input/train') s3_val_path = session.upload_data( path='/tmp/validation.txt', bucket=bucket, key_prefix=prefix+'/input/validation') s3_output = 's3://{}/{}/output/'.format(bucket, prefix) -
我们为 BlazingText 配置
Estimator函数,如下所示:from sagemaker.image_uris import retrieve region_name = session.boto_session.region_name container = retrieve('blazingtext', region) bt = sagemaker.estimator.Estimator(container, sagemaker.get_execution_role(), instance_count=1, instance_type='ml.p3.2xlarge', output_path=s3_output) -
我们设置一个超参数,告诉 BlazingText 以监督模式进行训练,如下所示:
bt.set_hyperparameters(mode='supervised') -
我们定义通道,设置内容类型为
text/plain,然后启动训练,如下所示:from sagemaker import TrainingInput train_data = TrainingInput( s3_train_path, content_type='text/plain') validation_data = TrainingInput( s3_val_path, content_type='text/plain') s3_channels = {'train': train_data, 'validation': validation_data} bt.fit(inputs=s3_channels) -
我们得到了 88.4% 的验证准确率,在没有调整任何超参数的情况下,这个结果相当不错。然后,我们将模型部署到一个小型 CPU 实例,如下所示:
bt_predictor = bt.deploy(initial_instance_count=1, instance_type='ml.t2.medium') -
一旦端点启动,我们将发送三个已分词的样本进行预测,并请求所有三个标签,如下所示:
import json sentences = ['This is a bad camera it doesnt work at all , i want a refund . ' , 'The camera works , the pictures are decent quality, nothing special to say about it . ' , 'Very happy to have bought this , exactly what I needed . '] payload = {"instances":sentences, "configuration":{"k": 3}} bt_predictor.serializer = sagemaker.serializers.JSONSerializer() response = bt_predictor.predict(json.dumps(payload)) -
打印响应后,我们可以看到三个样本已正确分类,如下图所示:
[{'prob': [0.9758228063583374, 0.023583529517054558, 0.0006236258195713162], 'label': ['__label__negative__', '__label__neutral__', '__label__positive__']}, {'prob': [0.5177792906761169, 0.2864232063293457, 0.19582746922969818], 'label': ['__label__neutral__', '__label__positive__', '__label__negative__']}, {'prob': [0.9997835755348206, 0.000205090589588508, 4.133415131946094e-05], 'label': ['__label__positive__', '__label__neutral__', '__label__negative__']}] -
像往常一样,一旦完成,我们通过运行以下代码删除端点:
bt_predictor.delete_endpoint()
现在,让我们训练 BlazingText 来计算词向量。
使用 BlazingText 计算词向量
这段代码几乎与前一个示例相同,只有两个不同点。首先,只有一个通道,包含训练数据。其次,我们需要将 BlazingText 设置为 UL 模式。
BlazingText 支持在 Word2Vec 中实现的训练模式:skipgram 和 连续的 BoW(CBOW)。它还添加了一种第三种模式,batch_skipgram,用于更快的分布式训练。它还支持 子词嵌入,这是一种可以为拼写错误或不在词汇表中的词返回词向量的技术。
我们选择使用 skipgram 和子词嵌入。我们保持向量的维度不变(默认是 100)。以下是所需的代码:
bt.set_hyperparameters(mode='skipgram', subwords=True)
与其他算法不同,这里没有需要部署的内容。模型文件存储在 S3 中,并且可以用于下游 NLP 应用程序。
说到这里,BlazingText 与 FastText 兼容,那么如何将我们刚训练的模型加载到 FastText 中呢?
使用 BlazingText 模型与 FastText
首先,我们需要编译 FastText,这非常简单。你甚至可以在笔记本实例上进行编译,无需安装任何东西。下面是你需要的代码:
$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make
让我们首先尝试我们的分类模型。
使用 BlazingText 分类模型与 FastText
我们将通过以下步骤来尝试该模型:
-
我们从 S3 复制模型文件并进行解压,方法如下:
$ aws s3 cp s3://sagemaker-eu-west-1-123456789012/amazon-reviews/output/JOB_NAME/output/model.tar.gz . $ tar xvfz model.tar.gz -
我们使用 FastText 加载
model.bin,方法如下:$ ./fasttext predict model.bin - -
我们预测样本并查看它们的 top 类别,方法如下:
This is a bad camera it doesnt work at all , i want a refund . __label__negative__ The camera works , the pictures are decent quality, nothing special to say about it . __label__neutral__ Very happy to have bought this , exactly what I needed __label__positive__
我们通过 Ctrl + C 退出。现在,让我们来探索我们的向量。
使用 BlazingText 词向量与 FastText
现在,我们将使用 FastText 和向量,方法如下:
-
我们从 S3 复制模型文件并解压,方法如下:
$ aws s3 cp s3://sagemaker-eu-west-1-123456789012/amazon-reviews-word2vec/output/JOB_NAME/output/model.tar.gz . $ tar xvfz model.tar.gz -
我们可以探索词语相似度。例如,让我们找出与“远摄”最相近的词汇。这可能有助于我们改进搜索查询的处理方式或推荐相似产品的方式。以下是你需要的代码:
$ ./fasttext nn vectors.bin Query word? Telephoto telephotos 0.951023 75-300mm 0.79659 55-300mm 0.788019 18-300mm 0.782396 . . . -
我们也可以寻找类比。例如,假设我们问我们的模型以下问题:尼康 D3300 相机的佳能等效型号是什么?代码如下所示:
$ ./fasttext analogies vectors.bin Query triplet (A - B + C)? nikon d3300 canon xsi 0.748873 700d 0.744358 100d 0.735871根据我们的模型,你应该考虑 XSI 和 700D 相机!
如你所见,词向量非常强大,BlazingText 使得在任何规模下计算词向量都变得非常容易。现在,让我们继续进行主题建模,这是另一个令人着迷的主题。
使用 LDA 进行主题建模
在前面的部分,我们准备了百万条新闻标题,现在我们将使用它们进行主题建模,采用 LDA 方法,具体如下:
-
首先,我们通过运行以下代码定义有用的路径:
import sagemaker session = sagemaker.Session() bucket = session.default_bucket() prefix = reviews-lda-ntm' train_key = 'reviews.protobuf' obj = '{}/{}'.format(prefix, train_key) s3_train_path = 's3://{}/{}'.format(bucket,obj) s3_output = 's3://{}/{}/output/'.format(bucket, prefix) -
我们配置
Estimator函数,如下所示:from sagemaker.image_uris import retrieve region_name = session.boto_session.region_name container = retrieve('lda', region) lda = sagemaker.estimator.Estimator(container, role = sagemaker.get_execution_role(), instance_count=1, instance_type='ml.c5.2xlarge', output_path=s3_output) -
我们设置超参数:希望构建的主题数量(10),问题的维度(词汇大小)以及我们正在训练的样本数量。可选地,我们可以设置一个名为
alpha0的参数。根据文档:“较小的值更容易生成稀疏的主题混合,而较大的值(大于 1.0)则生成更加均匀的混合。”我们将其设置为 0.1,并希望算法能够构建出良好识别的主题。以下是所需的代码:lda.set_hyperparameters(num_topics=5, feature_dim=len(dictionary), mini_batch_size=num_lines, alpha0=0.1) -
我们启动训练。由于 RecordIO 是该算法期望的默认格式,我们无需定义通道。代码如下所示:
lda.fit(inputs={'train': s3_train_path}) -
一旦训练完成,我们将模型部署到一个小型 CPU 实例,方法如下:
lda_predictor = lda.deploy( initial_instance_count=1, instance_type='ml.t2.medium') -
在发送样本进行预测之前,我们需要像处理训练集一样处理它们。我们编写一个函数来完成这个任务:构建稀疏矩阵,填充 BoW,并保存到内存中的 protobuf 缓冲区,方法如下:
def process_samples(samples, dictionary): num_lines = len(samples) num_columns = len(dictionary) sample_matrix = lil_matrix((num_lines, num_columns)).astype('float32') for line in range(0, num_lines): s = samples[line] s = process_text(s) s = dictionary.doc2bow(s) for token_id, token_count in s: sample_matrix[line, token_id] = token_count line+=1 buf = io.BytesIO() smac.write_spmatrix_to_sparse_tensor( buf, sample_matrix, None) buf.seek(0) return buf请注意,我们在这里需要字典。这就是为什么相应的 SageMaker 处理任务保存了它的 pickle 版本,我们可以稍后将其反序列化并使用它。
-
然后,我们定义一个包含五个标题的 Python 数组,命名为
samples。这些是真实的标题,我从 ABC 新闻网站上复制的,网址是www.abc.net.au/news/。代码示例如下:samples = [ "Major tariffs expected to end Australian barley trade to China", "Satellite imagery sparks more speculation on North Korean leader Kim Jong-un", "Fifty trains out of service as fault forces Adelaide passengers to 'pack like sardines", "Germany's Bundesliga plans its return from lockdown as football world watches", "All AFL players to face COVID-19 testing before training resumes" ] -
让我们处理并预测它们,如下所示:
lda_predictor.serializer = sagemaker.serializers.CSVSerializer() response = lda_predictor.predict( process_samples(samples, dictionary)) print(response) -
响应中包含每个评论的得分向量(为了简洁起见,已删除额外的小数)。每个向量反映了各个主题的混合,并为每个主题分配得分。所有得分总和为 1。代码示例如下:
{'predictions': [ {'topic_mixture': [0,0.22,0.54,0.23,0,0,0,0,0,0]}, {'topic_mixture': [0.51,0.49,0,0,0,0,0,0,0,0]}, {'topic_mixture': [0.38,0,0.22,0,0.40,0,0,0,0,0]}, {'topic_mixture': [0.38,0.62,0,0,0,0,0,0,0,0]}, {'topic_mixture': [0,0.75,0,0,0,0,0,0.25,0,0]}]} -
这并不容易读取。让我们打印出主要主题及其得分,如下所示:
import numpy as np vecs = [r['topic_mixture'] for r in response['predictions']] for v in vecs: top_topic = np.argmax(v) print("topic %s, %2.2f"%(top_topic,v[top_topic]))这将打印出以下结果:
topic 2, 0.54 topic 0, 0.51 topic 4, 0.40 topic 1, 0.62 topic 1, 0.75 -
和往常一样,训练完成后我们会删除终端节点,如下所示:
lda_predictor.delete_endpoint()
解释 LDA 结果并不容易,所以我们在这里要小心。不要有过度的幻想!
-
我们看到每个标题都有一个明确的主题,这是好消息。显然,LDA 能够识别出稳固的主题,也许得益于较低的
alpha0值。 -
与无关的标题相比,主要的主题是不同的,这很有前景。
-
最后的两个标题都是关于体育的,它们的主要主题是相同的,这是另一个好兆头。
-
所有五条评论在主题 5、6、8 和 9 上得分为零。这可能意味着已经构建了其他主题,我们需要运行更多示例来发现它们。
这是一个成功的模型吗?可能是。我们能确定主题 0 是关于世界事务的,主题 1 是关于体育的,主题 2 也是关于体育的吗?直到我们预测了几千个评论并检查相关标题是否被分配到同一主题,才能得出结论。
正如本章开头提到的,LDA 并不是一种分类算法。它有自己的“思想”,可能会构建出完全出乎意料的主题。也许它会根据情感或城市名称将标题进行分组。一切取决于这些词语在文档集合中的分布。
如果我们能够看到哪些词语在某个主题中“更重要”,那该有多好呢?这无疑会帮助我们更好地理解这些主题。于是,NTM 应运而生!
使用 NTM 建模主题
这个例子与前一个非常相似。我们将仅强调其中的不同之处,完整的示例可以在书籍的 GitHub 仓库中找到。让我们开始吧,如下所示:
-
我们将词汇文件上传到 S3,像这样:
s3_auxiliary_path = session.upload_data(path='vocab.txt', key_prefix=prefix + '/input/auxiliary') -
我们选择 NTM 算法,如下所示:
from sagemaker.image_uris import retrieve region_name = session.boto_session.region_name container = retrieve('ntm', region) -
一旦我们配置了
Estimator函数,就可以设置超参数,如下所示:ntm.set_hyperparameters(num_topics=10, feature_dim=len(dictionary), optimizer='adam', mini_batch_size=256, num_patience_epochs=10) -
我们启动训练,将词汇文件传递到
auxiliary通道,如下所示:ntm.fit(inputs={'train': s3_training_path, 'auxiliary': s3_auxiliary_path})
当训练完成后,我们可以在训练日志中看到大量信息。首先,我们看到 10 个主题的平均 WETC 和 TU 分数,如下所示:
(num_topics:10) [wetc 0.42, tu 0.86]
这些结果不错。主题的独特性很高,主题词之间的语义距离适中。
对于每个话题,我们看到它的 WETC 和 TU 分数,以及它的关键词——即在与该话题相关的文档中最有可能出现的词汇。
让我们详细看看每个话题,尝试为它们命名。
第 0 个话题相当明显,我认为。几乎所有的词汇都与犯罪相关,所以我们称它为crime。你可以在这里查看该话题:
[0.51, 0.84] stabbing charged guilty pleads murder fatal man assault bail jailed alleged shooting arrested teen girl accused boy car found crash
以下的第 1 个话题有些模糊。怎么样,叫它legal?请在这里查看它:
[0.36, 0.85] seeker asylum climate live front hears change export carbon tax court wind challenge told accused rule legal face stand boat
第 2 个话题是关于事故和火灾的。我们称它为disaster。你可以在这里查看该话题:
[0.39, 0.78] seeker crew hour asylum cause damage truck country firefighter blaze crash warning ta plane near highway accident one fire fatal
第 3 个话题显而易见:sports。TU 分数最高,表明体育文章使用的是非常特定的词汇,其他地方找不到,正如我们在这里看到的那样:
[0.54, 0.93] cup world v league one match win title final star live victory england day nrl miss beat team afl player
第 4 个话题是天气信息和自然资源的奇怪混合。它的 WETC 分数和 TU 分数都是最低的。我们称它为unknown1。请在这里查看它:
[0.35, 0.77] coast korea gold north east central pleads west south guilty queensland found qld rain beach cyclone northern nuclear crop mine
第 5 个话题似乎是关于世界事务的。我们称它为international。你可以在这里查看该话题:
[0.38, 0.88] iraq troop bomb trade korea nuclear kill soldier iraqi blast pm president china pakistan howard visit pacific u abc anti
第 6 个话题感觉像是地方新闻,因为它包含了澳大利亚地区的缩写:qld是昆士兰,ta是塔斯马尼亚,nsw是新南威尔士州,等等。我们称它为local。该话题如下:
[0.25, 0.88] news hour country rural national abc ta sport vic abuse sa nsw weather nt club qld award business
第 7 个话题显而易见:finance。它具有最高的 WETC 分数,表明从语义角度来看,它的词汇密切相关。话题的独特性也非常高,我们可能在医学或工程等领域特定话题中看到相同的情况。请查看该话题:
[0.62, 0.90] share dollar rise rate market fall profit price interest toll record export bank despite drop loss post high strong trade
第 8 个话题是关于政治的,带有一些犯罪元素。有些人会说,这实际上是同一回事。由于我们已经有了一个crime话题,我们将这个命名为politics。请查看该话题:
[0.41, 0.90] issue election vote league hunt interest poll parliament gun investigate opposition raid arrest police candidate victoria house northern crime rate
第 9 个话题是一个混合的例子。很难说它是关于农业还是失踪人员!我们称之为unknown2。你可以在这里看到该话题:
[0.37, 0.84] missing search crop body found wind rain continues speaks john drought farm farmer smith pacific crew river find mark tourist
综合来看,这是一个相当不错的模型:10 个话题中有 8 个非常清晰。
让我们定义我们的话题列表,并在部署模型后运行示例头条新闻,如下所示:
topics = ['crime','legal','disaster','sports','unknown1',
'international','local','finance','politics',
'unknown2']
samples = [ "Major tariffs expected to end Australian barley trade to China", "US woman wanted over fatal crash asks for release after coronavirus halts extradition", "Fifty trains out of service as fault forces Adelaide passengers to 'pack like sardines", "Germany's Bundesliga plans its return from lockdown as football world watches", "All AFL players to face COVID-19 testing before training resumes" ]
我们使用以下函数来打印前 3 个话题及其分数:
import numpy as np
for r in response['predictions']:
sorted_indexes = np.argsort(r['topic_weights']).tolist()
sorted_indexes.reverse()
top_topics = [topics[i] for i in sorted_indexes]
top_weights = [r['topic_weights'][i]
for i in sorted_indexes]
pairs = list(zip(top_topics, top_weights))
print(pairs[:3])
这是输出结果:
[('finance', 0.30),('international', 0.22),('sports', 0.09)]
[('unknown1', 0.19),('legal', 0.15),('politics', 0.14)]
[('crime', 0.32), ('legal', 0.18), ('international', 0.09)]
[('sports', 0.28),('unknown1', 0.09),('unknown2', 0.08)]
[('sports', 0.27),('disaster', 0.12),('crime', 0.11)]
头条 0、2、3 和 4 完全符合预期。这并不令人惊讶,因为这些话题的强度非常高。
头条 1 在我们称为legal的话题中得分很高。也许阿德莱德的乘客应该起诉火车公司?说真的,我们需要找到其他匹配的头条新闻,以便更好地了解该话题的真实含义。
如你所见,NTM 使得理解话题变得更加容易。我们可以通过处理词汇文件来改进模型,添加或移除特定词汇来影响话题,增加话题数目,调整alpha0等。我直觉上认为,我们应该在其中看到一个“天气”话题。请尝试一下,看看是否能让它出现。
如果你想运行另一个示例,你可以在这个笔记本中找到有趣的技巧:
总结
自然语言处理(NLP)是一个非常激动人心的主题。它也是一个困难的主题,因为语言本身的复杂性,以及构建数据集所需的处理工作量。尽管如此,SageMaker 中的内置算法将帮助你轻松获得良好的结果。训练和部署模型是直接的过程,这为你提供了更多时间来探索、理解和准备数据。
在本章中,你了解了 BlazingText、LDA 和 NTM 算法。你还学习了如何使用流行的开源工具,如nltk、spaCy和gensim,来处理数据集,并将其保存为适当的格式。最后,你学习了如何使用 SageMaker SDK 训练和部署这三种算法的模型,以及如何解读结果。这标志着我们对内置算法的探索的结束。
在下一章中,你将学习如何使用内置的机器学习框架,如scikit-learn、TensorFlow、PyTorch和Apache MXNet。
第七章:使用内置框架扩展机器学习服务
在过去的三章中,你学习了如何使用内置算法来训练和部署模型,而无需编写一行机器学习代码。然而,这些算法并没有涵盖所有的机器学习问题。在许多情况下,你需要编写自己的代码。幸运的是,几个开源框架让这个过程相对容易。
在本章中,你将学习如何使用最流行的开源机器学习和深度学习框架来训练和部署模型。我们将涵盖以下主题:
-
发现 Amazon SageMaker 中的内置框架
-
在 Amazon SageMaker 上运行你的框架代码
-
使用内置框架
让我们开始吧!
技术要求
你需要一个 AWS 账户来运行本章中包含的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该了解 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。
你需要为你的账户安装并配置 AWS 命令行界面(aws.amazon.com/cli/)。
你需要一个可用的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是必须的,但强烈建议安装,因为它包含了我们需要的许多项目(Jupyter、pandas、numpy 等)。
你需要一个可用的 Docker 安装。你可以在 docs.docker.com 找到安装说明和相关文档。
本书中包含的代码示例可以在 GitHub 上找到,网址是 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端才能访问它们(git-scm.com/)。
发现 Amazon SageMaker 中的内置框架
SageMaker 让你使用以下机器学习和深度学习框架来训练和部署模型:
-
Scikit-learn,无疑是最广泛使用的开源机器学习库。如果你是这个领域的新手,可以从这里开始:
scikit-learn.org。 -
XGBoost,一种非常流行且多功能的开源算法,适用于回归、分类和排序问题(
xgboost.ai)。它也作为内置算法提供,如在第四章《训练机器学习模型》中所展示的那样。以框架模式使用它将为我们提供更多的灵活性。 -
TensorFlow,一个极受欢迎的深度学习开源库(
www.tensorflow.org)。SageMaker 还支持受人喜爱的 Keras API(keras.io)。 -
PyTorch,另一个备受欢迎的深度学习开源库(
pytorch.org)。特别是研究人员喜欢它的灵活性。 -
Apache MXNet,一个有趣的深度学习挑战者。它是用 C++ 原生实现的,通常比其竞争对手更快且更具可扩展性。其 Gluon API 提供了丰富的计算机视觉工具包(
gluon-cv.mxnet.io)、自然语言处理(NLP)(gluon-nlp.mxnet.io)和时间序列数据(gluon-ts.mxnet.io)。 -
Chainer,另一个值得关注的深度学习挑战者(
chainer.org)。 -
Hugging Face,最流行的、用于自然语言处理的最前沿工具和模型集合(
huggingface.co)。 -
强化学习框架,如 Intel Coach、Ray RLlib 和 Vowpal Wabbit。由于这可能需要一本书的篇幅,我在这里不会讨论这个话题!
-
Spark,借助一个专用的 SDK,允许你直接从 Spark 应用程序中使用 PySpark 或 Scala 训练和部署模型(
github.com/aws/sagemaker-spark)。
你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-python-sdk 找到这些的许多示例。
在本章中,我们将重点关注最流行的框架:XGBoost、scikit-learn、TensorFlow、PyTorch 和 Spark。
最好的入门方式是先运行一个简单的示例。如你所见,工作流与内置算法是相同的。我们将在过程中强调一些差异,稍后将在本章深入讨论。
使用 XGBoost 运行第一个示例
在这个例子中,我们将使用 XGBoost 内置框架构建一个二分类模型。写这篇文章时,SageMaker 支持的最新版本是 1.3-1。
我们将使用基于 xgboost.XGBClassifier 对象和直接营销数据集的自定义训练脚本,这个数据集我们在 第三章,使用 Amazon SageMaker Autopilot 进行自动化机器学习 中用过:
-
首先,我们下载并解压数据集:
%%sh wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip unzip -o bank-additional.zip -
我们导入 SageMaker SDK,并为任务定义一个 S3 前缀:
import sagemaker sess = sagemaker.Session() bucket = sess.default_bucket() prefix = 'xgboost-direct-marketing' -
我们加载数据集并进行非常基础的处理(因为这不是我们的重点)。简单地对分类特征进行独热编码,将标签移动到第一列(这是 XGBoost 的要求),打乱数据集,分割为训练集和验证集,并将结果保存在两个单独的 CSV 文件中:
import pandas as pd import numpy as np from sklearn.model_selection import train_test_split data = pd.read_csv('./bank-additional/bank-additional-full.csv') data = pd.get_dummies(data) data = data.drop(['y_no'], axis=1) data = pd.concat([data['y_yes'], data.drop(['y_yes'], axis=1)], axis=1) data = data.sample(frac=1, random_state=123) train_data, val_data = train_test_split( data, test_size=0.05) train_data.to_csv( 'training.csv', index=False, header=False) val_data.to_csv( 'validation.csv', index=False, header=False) -
我们将这两个文件上传到 S3:
training = sess.upload_data(path='training.csv', key_prefix=prefix + '/training') validation = sess.upload_data(path='validation.csv', key_prefix=prefix + "/validation") output = 's3://{}/{}/output/'.format(bucket,prefix) -
我们定义两个输入,数据格式为 CSV:
from sagemaker import TrainingInput train_input = TrainingInput( training_path, content_type='text/csv') val_input = TrainingInput( validation_path, content_type='text/csv') -
为训练任务定义一个估算器。当然,我们也可以使用通用的
Estimator对象,并传递 XGBoost 容器在XGBoost估算器中的名称,这样就会自动选择正确的容器:from sagemaker.xgboost import XGBoost xgb_estimator = XGBoost( role= sagemaker.get_execution_role(), entry_point='xgb-dm.py', instance_count=1, instance_type='ml.m5.large', framework_version='1.2-2', output_path=output, hyperparameters={ 'num_round': 100, 'early_stopping_rounds': 10, 'max-depth': 5, 'eval-metric': 'auc'} )这里有几个熟悉的参数:角色、基础设施要求和输出路径。其他参数呢?
entry_point是我们训练脚本的路径(可以在本书的 GitHub 仓库中找到)。hyperparameters会传递给训练脚本。我们还需要选择一个framework_version值;这是我们想要使用的 XGBoost 版本。 -
我们照常进行训练:
xgb_estimator.fit({'train':training, 'validation':validation}) -
我们也照常进行部署,创建一个唯一的端点名称:
from time import strftime,gmtime xgb_endpoint_name = prefix+strftime("%Y-%m-%d-%H-%M-%S", gmtime()) xgb_predictor = xgb_estimator.deploy( endpoint_name=xgb_endpoint_name, initial_instance_count=1, instance_type='ml.t2.medium')然后,我们从验证集加载一些样本,并将它们以 CSV 格式发送进行预测。响应包含每个样本的 0 到 1 之间的分数:
payload = val_data[:10].drop(['y_yes'], axis=1) payload = payload.to_csv(header=False, index=False).rstrip('\n') xgb_predictor.serializer = sagemaker.serializers.CSVSerializer() xgb_predictor.deserializer = sagemaker.deserializers.CSVDeserializer() response = xgb_predictor.predict(payload) print(response)这将输出以下概率:
[['0.07206538'], ['0.02661967'], ['0.16043524'], ['4.026455e-05'], ['0.0002120432'], ['0.52123886'], ['0.50755614'], ['0.00015006188'], ['3.1439096e-05'], ['9.7614546e-05']] -
完成后,我们删除端点:
xgb_predictor.delete_endpoint()
我们在这里使用了 XGBoost,但这个工作流程对于其他框架也是完全相同的。这种标准的训练和部署方式使得从内置算法切换到框架,或从一个框架切换到下一个框架变得非常简单。
我们需要重点关注的要点如下:
-
框架容器:它们是什么?我们能看到它们是如何构建的吗?我们可以自定义它们吗?我们能用它们在本地机器上进行训练吗?
-
训练:SageMaker 训练脚本与普通框架代码有何不同?它如何接收超参数?它应该如何读取输入数据?模型应该保存在哪里?
-
部署:模型是如何部署的?脚本需要提供一些相关代码吗?预测的输入格式是什么?
-
entry_point脚本?我们可以为训练和部署添加库吗?
所有这些问题现在都将得到解答!
使用框架容器
SageMaker 为每个内置框架提供训练和推理容器,并定期更新到最新版本。不同的容器也可供 CPU 和 GPU 实例使用。所有这些容器统称为深度学习容器 (aws.amazon.com/machine-learning/containers)。
正如我们在前面的例子中看到的,它们允许你使用自己的代码,而无需维护定制的容器。在大多数情况下,你不需要再进一步了解容器的细节,你可以高兴地忘记它们的存在。如果这个话题目前对你来说太高级,可以暂时跳过,继续阅读本地训练与部署部分。
如果你感到好奇或有定制需求,你会很高兴得知这些容器的代码是开源的:
-
Scikit-learn:
github.com/aws/sagemaker-scikit-learn-container -
TensorFlow,PyTorch,Apache MXNet 和 Hugging Face:
github.com/aws/deep-learning-containers
首先,这可以帮助你理解这些容器是如何构建的,以及 SageMaker 是如何使用它们进行训练和预测的。你还可以执行以下操作:
-
在本地机器上构建并运行它们进行本地实验。
-
在你最喜欢的托管 Docker 服务上构建并运行它们,例如Amazon ECS、Amazon EKS或Amazon Fargate(
aws.amazon.com/containers)。 -
自定义它们,推送到 Amazon ECR,并使用 SageMaker SDK 中的估算器。我们将在第八章中演示这一点,使用你的算法和代码。
这些容器有一个很好的特性。你可以与 SageMaker SDK 一起使用它们,在本地机器上训练和部署模型。让我们看看这个是如何工作的。
本地训练和部署
本地模式是通过 SageMaker SDK 训练和部署模型,而无需启动 AWS 中的按需托管基础设施。你将使用本地机器代替。在此上下文中,“本地”指的是运行笔记本的机器:它可以是你的笔记本电脑、本地服务器,或者一个小型笔记本实例。
注意
在写本文时,本地模式在 SageMaker Studio 中不可用。
这是一个快速实验和迭代小型数据集的极好方式。你无需等待实例启动,也无需为此支付费用!
让我们重新审视之前的 XGBoost 示例,重点讲解使用本地模式时所需的更改:
-
显式设置 IAM 角色的名称。
get_execution_role()在本地机器上不起作用(在笔记本实例上有效):#role = sagemaker.get_execution_role() role = 'arn:aws:iam::0123456789012:role/Sagemaker-fullaccess' -
从本地文件加载训练和验证数据集。将模型存储在本地
/tmp目录中:training = 'file://training.csv' validation = 'file://validation.csv' output = 'file:///tmp' -
在
XGBoost估算器中,将instance_type设置为local。对于本地 GPU 训练,我们将使用local_gpu。 -
在
xgb_estimator.deploy()中,将instance_type设置为local。
这就是使用与 AWS 大规模环境中相同的容器在本地机器上进行训练所需的一切。此容器会被拉取到本地机器,之后你将一直使用它。当你准备好大规模训练时,只需将local或local_gpu实例类型替换为适当的 AWS 实例类型,就可以开始训练了。
故障排除
如果遇到奇怪的部署错误,可以尝试重启 Docker(sudo service docker restart)。我发现它在部署过程中经常被中断,尤其是在 Jupyter Notebooks 中工作时!
现在,让我们看看在这些容器中运行自己代码所需的条件。这个功能叫做脚本模式。
使用脚本模式进行训练
由于您的训练代码运行在 SageMaker 容器内,它需要能够执行以下操作:
-
接收传递给估算器的超参数。
-
读取输入通道中可用的数据(训练数据、验证数据等)。
-
将训练好的模型保存到正确的位置。
脚本模式是 SageMaker 使这一切成为可能的方式。该名称来自于您的代码在容器中被调用的方式。查看我们 XGBoost 作业的训练日志,我们可以看到:
Invoking script with the following command:
/miniconda3/bin/python3 -m xgb-dm --early-stopping-rounds 10
--eval-metric auc --max-depth 5
我们的代码像普通的 Python 脚本一样被调用(因此称为脚本模式)。我们可以看到,超参数作为命令行参数传递,这也回答了我们应该在脚本中使用什么来读取它们:argparse。
这是我们脚本中相应的代码片段:
parser = argparse.ArgumentParser()
parser.add_argument('--max-depth', type=int, default=4)
parser.add_argument('--early-stopping-rounds', type=int,
default=10)
parser.add_argument('--eval-metric', type=str,
default='error')
那么输入数据和保存模型的位置呢?如果我们稍微仔细查看日志,就会看到:
SM_CHANNEL_TRAIN=/opt/ml/input/data/train
SM_CHANNEL_VALIDATION=/opt/ml/input/data/validation
SM_MODEL_DIR=/opt/ml/model
这三个环境变量定义了容器内的本地路径,指向训练数据、验证数据和保存模型的相应位置。这是否意味着我们必须手动将数据集和模型从 S3 复制到容器中并返回?不!SageMaker 会为我们自动处理这一切。这是容器中支持代码的一部分。
我们的脚本只需要读取这些变量。我建议再次使用 argparse,这样我们可以在 SageMaker 之外训练时将路径传递给脚本(稍后会详细介绍)。
这是我们脚本中相应的代码片段:
parser.add_argument('--model-dir', type=str,
default=os.environ['SM_MODEL_DIR'])
parser.add_argument('--training-dir', type=str,
default=os.environ['SM_CHANNEL_TRAIN'])
parser.add_argument('--validation', type=str,
default=os.environ['SM_CHANNEL_VALIDATION'])
通道名称
SM_CHANNEL_xxx 变量是根据传递给 fit() 的通道命名的。例如,如果您的算法需要一个名为 foobar 的通道,您需要在 fit() 中将其命名为 foobar,并在脚本中使用 SM_CHANNEL_FOOBAR。在您的容器中,该通道的数据会自动保存在 /opt/ml/input/data/foobar 目录下。
总结一下,为了在 SageMaker 上训练框架代码,我们只需要做以下几件事:
-
使用
argparse读取作为命令行参数传递的超参数。您可能已经在代码中这样做了! -
读取
SM_CHANNEL_xxx环境变量并从中加载数据。 -
读取
SM_MODEL_DIR环境变量并将训练好的模型保存到该位置。
现在,让我们讨论在脚本模式下部署训练好的模型。
理解模型部署
通常,您的脚本需要包括以下内容:
-
一个加载模型的函数。
-
一个在将输入数据传递给模型之前处理数据的函数。
-
一个在返回预测结果给调用方之前处理预测结果的函数。
所需的实际工作量取决于您使用的框架和输入格式。让我们看看这对 TensorFlow、PyTorch、MXNet、XGBoost 和 scikit-learn 意味着什么。
使用 TensorFlow 部署
TensorFlow 推理容器依赖于TensorFlow Serving模型服务器进行模型部署(www.tensorflow.org/tfx/guide/serving)。因此,你的训练代码必须以这种格式保存模型。模型加载和预测功能会自动提供。
JSON 是预测的默认输入格式,并且由于自动序列化,它也适用于numpy数组。JSON Lines 和 CSV 也被支持。对于其他格式,你可以实现自己的预处理和后处理函数,input_handler()和output_handler()。你可以在sagemaker.readthedocs.io/en/stable/using_tf.html#deploying-from-an-estimator找到更多信息。
你也可以深入了解 TensorFlow 推理容器,访问github.com/aws/deep-learning-containers/tree/master/tensorflow/inference。
使用 PyTorch 进行部署
PyTorch 推理容器依赖于__call__()方法。如果没有,你应该在推理脚本中提供predict_fn()函数。
对于预测,numpy是默认的输入格式。JSON Lines 和 CSV 也被支持。对于其他格式,你可以实现自己的预处理和后处理函数。你可以在sagemaker.readthedocs.io/en/stable/frameworks/pytorch/using_pytorch.html#serve-a-pytorch-model找到更多信息。
你可以深入了解 PyTorch 推理容器,访问github.com/aws/deep-learning-containers/tree/master/pytorch/inference。
使用 Apache MXNet 进行部署
Apache MXNet 推理容器依赖于多模型服务器(MMS)进行模型部署(github.com/awslabs/multi-model-server)。它使用默认的 MXNet 模型格式。
基于Module API 的模型不需要模型加载函数。对于预测,它们支持 JSON、CSV 或numpy格式的数据。
Gluon 模型确实需要一个模型加载函数,因为参数需要显式初始化。数据可以通过 JSON 或numpy格式发送。
对于其他数据格式,你可以实现自己的预处理、预测和后处理函数。你可以在sagemaker.readthedocs.io/en/stable/using_mxnet.html找到更多信息。
你可以深入了解 MXNet 推理容器,访问github.com/aws/deep-learning-containers/tree/master/mxnet/inference/docker。
使用 XGBoost 和 scikit-learn 进行部署
同样,XGBoost 和 scikit-learn 分别依赖于github.com/aws/sagemaker-xgboost-container和github.com/aws/sagemaker-scikit-learn-container。
你的脚本需要提供以下内容:
-
一个
model_fn()函数用于加载模型。与训练类似,加载模型的位置通过SM_MODEL_DIR环境变量传递。 -
两个可选函数,用于反序列化和序列化预测数据,分别命名为
input_fn()和output_fn()。只有当你需要其他格式的输入数据(例如非 JSON、CSV 或numpy)时,才需要这些函数。 -
一个可选的
predict_fn()函数将反序列化的数据传递给模型并返回预测结果。仅当你需要在预测之前对数据进行预处理,或对预测结果进行后处理时才需要这个函数。
对于 XGBoost 和 scikit-learn,model_fn()函数非常简单且通用。以下是一些大多数情况下都能正常工作的示例:
# Scikit-learn
def model_fn(model_dir):
clf = joblib.load(os.path.join(model_dir,
'model.joblib'))
return clf
# XGBoost
def model_fn(model_dir):
model = xgb.Booster()
model.load_model(os.path.join(model_dir, 'xgb.model'))
return model
SageMaker 还允许你导入和导出模型。你可以将现有模型上传到 S3 并直接在 SageMaker 上部署。同样,你也可以将训练好的模型从 S3 复制到其他地方进行部署。我们将在第十一章,《部署机器学习模型》中详细介绍这一点。
现在,让我们来讨论训练和部署所需的依赖关系。
管理依赖关系
在许多情况下,你需要向框架的容器中添加额外的源文件和库。让我们看看如何轻松做到这一点。
添加训练所需的源文件
默认情况下,所有估算器都会从当前目录加载入口脚本。如果你需要额外的源文件来进行训练,估算器允许你传递一个source_dir参数,指向存储额外文件的目录。请注意,入口脚本必须位于同一目录中。
在以下示例中,myscript.py和所有额外的源文件必须放在src目录中。SageMaker 将自动打包该目录并将其复制到训练容器中:
sk = SKLearn(entry_point='myscript.py',
source_dir='src',
. . .
添加训练所需的库
你可以使用不同的技术来添加训练所需的库。
对于可以通过pip安装的库,最简单的方法是将requirements.txt文件与入口脚本放在同一文件夹中。SageMaker 会自动在容器内安装这些库。
另外,你可以通过在训练脚本中执行pip install命令,使用pip直接安装库。我们在第六章,《训练自然语言处理模型》中使用了这个方法,处理了 LDA 和 NTM。这个方法在你不想或者不能修改启动训练作业的 SageMaker 代码时非常有用:
import subprocess, sys
def install(package):
subprocess.call([sys.executable, "-m",
"pip", "install", package])
if __name__=='__main__':
install('gensim')
import gensim
. . .
对于不能通过 pip 安装的库,你应该使用 dependencies 参数。这个参数在所有估算器中都可用,它允许你列出要添加到训练作业中的库。这些库需要在本地、虚拟环境或特定目录中存在。SageMaker 会将它们打包并复制到训练容器中。
在以下示例中,myscript.py 需要 mylib 库。我们将在 lib 本地目录中安装它:
$ mkdir lib
$ pip install mylib -t lib
然后,我们将其位置传递给估算器:
sk = SKLearn(entry_point='myscript.py',
dependencies=['lib/mylib'],
. . .
最后的技术是将库安装到 Dockerfile 中的容器里,重建镜像并将其推送到 Amazon ECR。如果在预测时也需要这些库(例如,用于预处理),这是最好的选择。
为部署添加库
如果你需要在预测时提供特定的库,可以使用一个 requirements.txt 文件,列出那些可以通过 pip 安装的库。
对于其他库,唯一的选择是自定义框架容器。你可以通过image_uri参数将其名称传递给估算器:
sk = SKLearn(entry_point='myscript.py', image_uri= '123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-sklearn'
. . .
我们在本节中涵盖了许多技术主题。现在,让我们来看一下大局。
将所有内容整合在一起
使用框架时的典型工作流程如下所示:
-
在你的代码中实现脚本模式;也就是说,读取必要的超参数、输入数据和输出位置。
-
如有需要,添加一个
model_fn()函数来加载模型。 -
在本地测试你的训练代码,避免使用任何 SageMaker 容器。
-
配置适当的估算器(如
XGBoost、TensorFlow等)。 -
使用估算器在本地模式下训练,使用内置容器或你自定义的容器。
-
在本地模式下部署并测试你的模型。
-
切换到托管实例类型(例如,
ml.m5.large)进行训练和部署。
这个逻辑进展每一步所需的工作很少。它最大程度地减少了摩擦、错误的风险和挫败感。它还优化了实例时间和成本——如果你的代码因为一个小错误立即崩溃,就不必等待并支付托管实例的费用。
现在,让我们开始运用这些知识。在接下来的部分中,我们将运行一个简单的 scikit-learn 示例。目的是确保我们理解刚刚讨论的工作流程。
在 Amazon SageMaker 上运行你的框架代码
我们将从一个简单的 scikit-learn 程序开始,该程序在波士顿住房数据集上训练并保存一个线性回归模型,数据集在第四章中使用过,训练机器学习模型:
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import joblib
data = pd.read_csv('housing.csv')
labels = data[['medv']]
samples = data.drop(['medv'], axis=1)
X_train, X_test, y_train, y_test = train_test_split(
samples, labels, test_size=0.1, random_state=123)
regr = LinearRegression(normalize=True)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
print('Mean squared error: %.2f'
% mean_squared_error(y_test, y_pred))
print('Coefficient of determination: %.2f'
% r2_score(y_test, y_pred))
joblib.dump(regr, 'model.joblib')
让我们更新它,使其可以在 SageMaker 上运行。
实现脚本模式
现在,我们将使用框架实现脚本模式,如下所示:
-
首先,读取命令行参数中的超参数:
import argparse if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--normalize', type=bool, default=False) parser.add_argument('--test-size', type=float, default=0.1) parser.add_argument('--random-state', type=int, default=123) args, _ = parser.parse_known_args() normalize = args.normalize test_size = args.test_size random_state = args.random_state data = pd.read_csv('housing.csv') labels = data[['medv']] samples = data.drop(['medv'], axis=1) X_train, X_test, y_train, y_test = train_test_split( samples, labels,test_size=test_size, random_state=random_state) . . . -
将输入和输出路径作为命令行参数读取。我们可以决定去除拆分代码,改为传递两个输入通道。我们还是坚持使用一个通道,也就是
training:import os if __name__ == '__main__': . . . parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) parser.add_argument('--training', type=str, default=os.environ['SM_CHANNEL_TRAINING']) . . . model_dir = args.model_dir training_dir = args.training . . . filename = os.path.join(training_dir, 'housing.csv') data = pd.read_csv(filename) . . . model = os.path.join(model_dir, 'model.joblib') dump(regr, model) -
由于我们使用的是 scikit-learn,我们需要添加
model_fn()以便在部署时加载模型:def model_fn(model_dir): model = joblib.load(os.path.join(model_dir, 'model.joblib')) return model
到此为止,我们完成了。是时候测试了!
本地测试
首先,我们在本地机器上的 Python 3 环境中测试我们的脚本,不依赖任何 SageMaker 容器。我们只需要确保安装了pandas和 scikit-learn。
我们将环境变量设置为空值,因为我们将在命令行上传递路径:
$ source activate python3
$ export SM_CHANNEL_TRAINING=
$ export SM_MODEL_DIR=
$ python sklearn-boston-housing.py --normalize True –test-ration 0.1 --training . --model-dir .
Mean squared error: 41.82
Coefficient of determination: 0.63
很好。我们的代码在命令行参数下运行得很顺利。我们可以使用它进行本地开发和调试,直到我们准备好将其迁移到 SageMaker 本地模式。
使用本地模式
我们将按照以下步骤开始:
-
仍然在我们的本地机器上,我们配置一个
SKLearn估算器以本地模式运行,根据我们使用的设置来设定角色。只使用本地路径:role = 'arn:aws:iam::0123456789012:role/Sagemaker-fullaccess' sk = SKLearn(entry_point='sklearn-boston-housing.py', role=role, framework_version='0.23-1', instance_count=1, instance_type='local', output_path=output_path, hyperparameters={'normalize': True, 'test-size': 0.1}) sk.fit({'training':training_path}) -
如预期的那样,我们可以在训练日志中看到如何调用我们的代码。当然,我们得到的是相同的结果:
/miniconda3/bin/python -m sklearn-boston-housing --normalize True --test-size 0.1 . . . Mean squared error: 41.82 Coefficient of determination: 0.63 -
我们在本地部署并发送一些 CSV 样本进行预测:
sk_predictor = sk.deploy(initial_instance_count=1, instance_type='local') data = pd.read_csv('housing.csv') payload = data[:10].drop(['medv'], axis=1) payload = payload.to_csv(header=False, index=False) sk_predictor.serializer = sagemaker.serializers.CSVSerializer() sk_predictor.deserializer = sagemaker.deserializers.CSVDeserializer() response = sk_predictor.predict(payload) print(response)通过打印响应,我们将看到预测值:
[['29.801388899699845'], ['24.990809475886074'], ['30.7379654455552'], ['28.786967125316544'], ['28.1421501991961'], ['25.301714533101716'], ['22.717977231840184'], ['19.302415613883348'], ['11.369520911229536'], ['18.785593532977657']]使用本地模式,我们可以快速迭代模型。我们仅受限于本地机器的计算和存储能力。当达到限制时,我们可以轻松迁移到托管基础设施。
使用托管基础设施
当需要进行大规模训练并在生产环境中部署时,我们只需确保输入数据存储在 S3 中,并将“本地”实例类型替换为实际的实例类型:
sess = sagemaker.Session()
bucket = sess.default_bucket()
prefix = 'sklearn-boston-housing'
training_path = sess.upload_data(path='housing.csv',
key_prefix=prefix + "/training")
output_path = 's3://{}/{}/output/'.format(bucket,prefix)
sk = SKLearn(. . ., instance_type='ml.m5.large')
sk.fit({'training':training_path})
. . .
sk_predictor = sk.deploy(initial_instance_count=1,
instance_type='ml.t2.medium')
由于我们使用的是相同的容器,我们可以放心训练和部署会按预期工作。再次强调,我强烈建议您遵循以下逻辑流程:首先进行本地工作,然后是 SageMaker 本地模式,最后是 SageMaker 托管基础设施。这将帮助你集中精力处理需要做的事以及何时做。
在本章的其余部分,我们将运行更多示例。
使用内建框架
我们已经覆盖了 XGBoost 和 scikit-learn。现在,是时候看看如何使用深度学习框架了。让我们从 TensorFlow 和 Keras 开始。
使用 TensorFlow 和 Keras
在这个示例中,我们将使用 TensorFlow 2.4.1 来训练一个简单的卷积神经网络,数据集使用 Fashion-MNIST(github.com/zalandoresearch/fashion-mnist)。
我们的代码分成了两个源文件:一个是入口点脚本(fmnist.py),另一个是模型(model.py,基于 Keras 层)。为了简洁起见,我只讨论 SageMaker 的步骤。你可以在本书的 GitHub 仓库中找到完整代码:
-
fmnist.py首先从命令行读取超参数:import tensorflow as tf import numpy as np import argparse, os from model import FMNISTModel parser = argparse.ArgumentParser() parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--learning-rate', type=float, default=0.01) parser.add_argument('--batch-size', type=int, default=128) -
接下来,我们读取环境变量,即训练集和验证集的输入路径、模型的输出路径以及实例上可用的 GPU 数量。这是我们第一次使用后者。它对于调整多 GPU 训练的批量大小非常有用,因为通常做法是将初始批量大小乘以 GPU 数量:
parser.add_argument('--training', type=str, default=os.environ['SM_CHANNEL_TRAINING']) parser.add_argument('--validation', type=str, default=os.environ['SM_CHANNEL_VALIDATION']) parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) parser.add_argument('--gpu-count', type=int, default=os.environ['SM_NUM_GPUS']) -
将参数存储在本地变量中。然后,加载数据集。每个通道为我们提供一个压缩的
numpy数组,用于存储图像和标签:x_train = np.load(os.path.join(training_dir, 'training.npz'))['image'] y_train = np.load(os.path.join(training_dir, 'training.npz'))['label'] x_val = np.load(os.path.join(validation_dir, 'validation.npz'))['image'] y_val = np.load(os.path.join(validation_dir, 'validation.npz'))['label'] -
然后,通过重新调整图像张量的形状、标准化像素值、进行独热编码图像标签,并创建将数据传输给模型的
tf.data.Dataset对象,为训练准备数据。 -
创建模型、编译并拟合它。
-
训练完成后,将模型以 TensorFlow Serving 格式保存到适当的输出位置。此步骤非常重要,因为这是 SageMaker 用于 TensorFlow 模型的模型服务器:
model.save(os.path.join(model_dir, '1'))
我们使用常规工作流程训练和部署模型:
-
在一个由 TensorFlow 2 内核支持的笔记本中,我们下载数据集并将其上传到 S3:
import os import numpy as np import keras from keras.datasets import fashion_mnist (x_train, y_train), (x_val, y_val) = fashion_mnist.load_data() os.makedirs("./data", exist_ok = True) np.savez('./data/training', image=x_train, label=y_train) np.savez('./data/validation', image=x_val, label=y_val) prefix = 'tf2-fashion-mnist' training_input_path = sess.upload_data( 'data/training.npz', key_prefix=prefix+'/training') validation_input_path = sess.upload_data( 'data/validation.npz', key_prefix=prefix+'/validation') -
我们配置
TensorFlow估算器。我们还设置source_dir参数,以便将模型文件也部署到容器中:from sagemaker.tensorflow import TensorFlow tf_estimator = TensorFlow(entry_point='fmnist.py', source_dir='.', role=sagemaker.get_execution_role(), instance_count=1, instance_type='ml.p3.2xlarge', framework_version='2.4.1', py_version='py37', hyperparameters={'epochs': 10}) -
像往常一样训练和部署。我们将直接使用托管基础设施,但相同的代码也可以在本地模式下在你的本地机器上正常运行:
from time import strftime,gmtime tf_estimator.fit( {'training': training_input_path, 'validation': validation_input_path}) tf_endpoint_name = 'tf2-fmnist-'+strftime("%Y-%m-%d-%H-%M-%S", gmtime()) tf_predictor = tf_estimator.deploy( initial_instance_count=1, instance_type='ml.m5.large', endpoint_name=tf_endpoint_name) -
验证准确率应为 91-92%。通过加载并显示一些验证数据集中的样本图像,我们可以预测它们的标签。
numpy负载会自动序列化为 JSON,这是预测数据的默认格式:response = tf_predictor.predict(payload) prediction = np.array(reponse['predictions']) predicted_label = prediction.argmax(axis=1) print('Predicted labels are: {}'.format(predicted_label))输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_1.jpg
图 7.1 – 查看预测类别
-
完成后,我们删除端点:
tf_predictor.delete_endpoint()
如你所见,脚本模式与内置容器的结合使得在 SageMaker 上运行 TensorFlow 变得非常简单。一旦进入常规流程,你会惊讶于将模型从笔记本电脑迁移到 AWS 的速度有多快。
现在,让我们来看一下 PyTorch。
使用 PyTorch
PyTorch 在计算机视觉、自然语言处理等领域非常流行。
在此示例中,我们将训练一个图神经网络(GNN)。这种类型的网络在图结构数据上表现特别好,如社交网络、生命科学等。事实上,我们的 PyTorch 代码将使用深度图书馆(DGL),这是一个开源库,可以更轻松地使用 TensorFlow、PyTorch 和 Apache MXNet 构建和训练 GNN(www.dgl.ai/)。DGL 已经安装在这些容器中,所以我们可以直接开始工作。
我们将使用 Zachary 空手道俱乐部数据集(konect.cc/networks/ucidata-zachary/)。以下是该图的内容:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_2.jpg
图 7.2 – Zachary 空手道俱乐部数据集
节点 0 和 33 是教师,而其他节点是学生。边表示这些人之间的关系。故事是这样的,两位老师发生了争执,俱乐部需要被分成两部分。
训练任务的目的是找到“最佳”分割。这可以定义为一个半监督分类任务。第一位老师(节点 0)被分配为类别 0,而第二位老师(节点 33)被分配为类别 1。所有其他节点是未标记的,它们的类别将由图卷积网络计算。在最后一个周期结束时,我们将提取节点类别,并根据这些类别来分割俱乐部。
数据集被存储为一个包含边的 pickle 格式的 Python 列表。以下是前几条边:
[('0', '8'), ('1', '17'), ('24', '31'), . . .
SageMaker 的代码简洁明了。我们将数据集上传到 S3,创建一个PyTorch估算器,然后进行训练:
import sagemaker
from sagemaker.pytorch import PyTorch
sess = sagemaker.Session()
prefix = 'dgl-karate-club'
training_input_path = sess.upload_data('edge_list.pickle',
key_prefix=prefix+'/training')
estimator = PyTorch(role=sagemaker.get_execution_role(),
entry_point='karate_club_sagemaker.py',
hyperparameters={'node_count': 34, 'epochs': 30},
framework_version='1.5.0',
py_version='py3',
instance_count=1,
instance_type='ml.m5.large')
estimator.fit({'training': training_input_path})
这一点几乎无需任何解释,对吧?
让我们来看一下简化的训练脚本,在这里我们再次使用了脚本模式。完整版本可在本书的 GitHub 仓库中找到:
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--epochs', type=int, default=30)
parser.add_argument('--node_count', type=int)
args, _ = parser.parse_known_args()
epochs = args.epochs
node_count = args.node_count
training_dir = os.environ['SM_CHANNEL_TRAINING']
model_dir = os.environ['SM_MODEL_DIR']
with open(os.path.join(training_dir, 'edge_list.pickle'),
'rb') as f:
edge_list = pickle.load(f)
# Build the graph and the model
. . .
# Train the model
. . .
# Print predicted classes
last_epoch = all_preds[epochs-1].detach().numpy()
predicted_class = np.argmax(last_epoch, axis=-1)
print(predicted_class)
# Save the model
torch.save(net.state_dict(), os.path.join(model_dir,
'karate_club.pt'))
预测了以下类别。节点 0 和 1 是类别 0,节点 2 是类别 1,依此类推:
[0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1]
通过绘制它们,我们可以看到俱乐部已经被干净利落地分开了:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_3.jpg
图 7.3 – 查看预测类别
再次强调,SageMaker 的代码并不会妨碍你。工作流和 API 在各个框架之间保持一致,你可以专注于机器学习问题本身。现在,让我们做一个新的示例,使用 Hugging Face,同时还会看到如何通过内建的 PyTorch 容器部署一个 PyTorch 模型。
与 Hugging Face 合作
Hugging Face (huggingface.co) 已迅速成为最受欢迎的自然语言处理开源模型集合。在撰写本文时,他们托管了近 10,000 个最先进的模型(huggingface.co/models),并在超过 250 种语言的预训练数据集上进行了训练(huggingface.co/datasets)。
为了快速构建高质量的自然语言处理应用,Hugging Face 积极开发了三个开源库:
-
Transformers:使用 Hugging Face 模型进行训练、微调和预测(
github.com/huggingface/transformers)。 -
Datasets:下载并处理 Hugging Face 数据集(
github.com/huggingface/datasets)。 -
Tokenizers:为 Hugging Face 模型的训练和预测进行文本标记化(
github.com/huggingface/tokenizers)。Hugging Face 教程
如果你完全是 Hugging Face 的新手,请先通过他们的教程:
huggingface.co/transformers/quicktour.html。
SageMaker 在 2021 年 3 月增加了对 Hugging Face 的支持,涵盖 TensorFlow 和 PyTorch。正如你所料,你可以使用HuggingFace估算器和内置容器。让我们运行一个示例,构建一个英文客户评论的情感分析模型。为此,我们将微调一个DistilBERT模型(arxiv.org/abs/1910.01108),该模型使用 PyTorch 实现,并在两个大型英语语料库(Wikipedia 和 BookCorpus 数据集)上进行了预训练。
准备数据集
在这个例子中,我们将使用一个名为generated_reviews_enth的 Hugging Face 数据集(huggingface.co/datasets/generated_reviews_enth)。它包含英文评论、其泰语翻译、一个标记指示翻译是否正确,以及星级评分:
{'correct': 0, 'review_star': 4, 'translation': {'en': "I had a hard time finding a case for my new LG Lucid 2 but finally found this one on amazon. The colors are really pretty and it works just as well as, if not better than the otterbox. Hopefully there will be more available by next Xmas season. Overall, very cute case. I love cheetah's. :)", 'th': 'ฉันมีปัญหาในการหาเคสสำหรับ LG Lucid 2 ใหม่ของฉันแต่ในที่สุดก็พบเคสนี้ใน Amazon สีสวยมากและใช้งานได้ดีเช่นเดียวกับถ้าไม่ดีกว่านากหวังว่าจะมีให้มากขึ้นในช่วงเทศกาลคริสต์มาสหน้าโดยรวมแล้วน่ารักมากๆฉันรักเสือชีตาห์ :)'}}
这是 DistilBERT 分词器所期望的格式:一个labels变量(0代表负面情绪,1代表正面情绪)和一个text变量,包含英文评论:
{'labels': 1,
'text': "I had a hard time finding a case for my new LG Lucid 2 but finally found this one on amazon. The colors are really pretty and it works just as well as, if not better than the otterbox. Hopefully there will be more available by next Xmas season. Overall, very cute case. I love cheetah's. :)"}
让我们开始吧!我将向你展示每个步骤,你也可以在这本书的 GitHub 仓库中找到一个SageMaker 处理版本:
-
我们首先安装
transformers和datasets库:!pip -q install "transformers>=4.4.2" "datasets[s3]==1.5.0" --upgrade -
我们下载了数据集,该数据集已经分为训练集(141,369 个实例)和验证集(15,708 个实例)。所有数据均为 JSON 格式:
from datasets import load_dataset train_dataset, valid_dataset = load_dataset('generated_reviews_enth', split=['train', 'validation']) -
在每个评论中,我们创建一个名为
labels的新变量。当review_star大于或等于 4 时,我们将其设置为1,否则设置为0:def map_stars_to_sentiment(row): return { 'labels': 1 if row['review_star'] >= 4 else 0 } train_dataset = train_dataset.map(map_stars_to_sentiment) valid_dataset = valid_dataset.map(map_stars_to_sentiment) -
这些评论是嵌套的 JSON 文档,这使得移除我们不需要的变量变得困难。让我们将两个数据集扁平化:
train_dataset = train_dataset.flatten() valid_dataset = valid_dataset.flatten() -
现在我们可以轻松丢弃不需要的变量。我们还将
translation.en变量重命名为text:train_dataset = train_dataset.remove_columns( ['correct', 'translation.th', 'review_star']) valid_dataset = valid_dataset.remove_columns( ['correct', 'translation.th', 'review_star']) train_dataset = train_dataset.rename_column( 'translation.en', 'text') valid_dataset = valid_dataset.rename_column( 'translation.en', 'text')
现在,训练和验证实例已经具有 DistilBERT 分词器所期望的格式。我们已经在第六章,训练自然语言处理模型中讲解了分词。一个显著的区别是,我们使用的是一个在与模型相同的英语语料库上预训练的分词器:
-
我们下载我们预训练模型的分词器:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained( 'distilbert-base-uncased') def tokenize(batch): return tokenizer(batch['text'], padding='max_length', truncation=True) -
我们对两个数据集进行分词。单词和标点符号会被相应的标记替换。如果需要,每个序列会被填充或截断,以适应模型的输入层(512 个标记):
train_dataset = train_dataset.map(tokenize, batched=True, batch_size=len(train_dataset)) valid_dataset = valid_dataset.map(tokenize, batched=True, batch_size=len(valid_dataset)) -
我们丢弃
text变量,因为它不再需要:train_dataset = train_dataset.remove_columns(['text']) valid_dataset = valid_dataset.remove_columns(['text']) -
打印出一个实例,我们可以看到注意力掩码(全为 1,意味着输入序列中的没有标记被掩盖)、输入 ID(标记序列)和标签:
'{"attention_mask": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,<zero padding>], "input_ids": [101,1045, 2018,1037,2524,2051,4531,1037,2553,2005,2026,2047,1048,2290,12776,3593,1016,2021,2633,2179,2023,2028,2006,9733,1012,1996,6087,2024,2428,3492,1998,2009,2573,2074,2004,2092,2004,1010,2065,2025,2488,2084,1996,22279,8758,1012,11504,2045,2097,2022,2062,2800,2011,2279,1060,9335,2161,1012,3452,1010,2200,10140,2553,1012,1045,2293,18178,12928,2232,1005,1055,1012,1024,1007,102,<zero padding>], "labels": 1}'
数据准备工作完成。接下来我们进入模型训练阶段。
微调 Hugging Face 模型
我们不打算从头开始训练:那样会花费太长时间,而且我们可能数据量也不足。相反,我们将对模型进行微调。从一个在大规模文本语料库上训练的模型开始,我们只会在我们自己的数据上再训练一个 epoch,以便模型能够学习到数据中的特定模式:
-
我们首先将两个数据集上传到 S3。
datasets库提供了一个方便的 API 来实现这一点:import sagemaker from datasets.filesystems import S3FileSystem bucket = sagemaker.Session().default_bucket() s3_prefix = 'hugging-face/sentiment-analysis' train_input_path = f's3://{bucket}/{s3_prefix}/training' valid_input_path = f's3://{bucket}/{s3_prefix}/validation' s3 = S3FileSystem() train_dataset.save_to_disk(train_input_path, fs=s3) valid_dataset.save_to_disk(valid_input_path, fs=s3) -
我们定义超参数并配置一个
HuggingFace估算器。请注意,我们将仅对模型进行一个 epoch 的微调:hyperparameters={ 'epochs': 1, 'train_batch_size': 32, 'model_name':'distilbert-base-uncased' } from sagemaker.huggingface import HuggingFace huggingface_estimator = HuggingFace( role=sagemaker.get_execution_role(), entry_point='train.py', hyperparameters=hyperparameters, transformers_version='4.4.2', pytorch_version='1.6.0', py_version='py36', instance_type='ml.p3.2xlarge', instance_count=1 )为了简洁起见,我不会讨论训练脚本(
train.py),该脚本可以在本书的 GitHub 仓库中找到。它没有特别之处:我们使用TrainerHugging Face API,并通过脚本模式与 SageMaker 进行接口。由于我们只训练一个 epoch,因此禁用了检查点保存(save_strategy='no')。这有助于缩短训练时间(不保存检查点)和部署时间(模型工件较小)。 -
还值得注意的是,你可以在 Hugging Face 网站上为你的估算器生成模板代码。如以下截图所示,你可以点击Amazon SageMaker,选择任务类型,然后复制并粘贴生成的代码:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_4.jpg
](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_4.jpg)
图 7.4 – 在 Hugging Face 网站上查看我们的模型
-
我们像往常一样启动训练作业,持续了大约 42 分钟:
huggingface_estimator.fit( {'train': train_input_path, 'valid': valid_input_path})
就像其他框架一样,我们可以调用deploy() API 来将模型部署到 SageMaker 端点。你可以在aws.amazon.com/blogs/machine-learning/announcing-managed-inference-for-hugging-face-models-in-amazon-sagemaker/找到一个示例。
相反,让我们看看如何使用内置的 PyTorch 容器和TorchServe部署我们的模型。实际上,这个部署示例可以推广到任何你希望通过 TorchServe 提供的 PyTorch 模型。
我发现我同事 Todd Escalona 写的这篇精彩博客文章在理解如何通过 TorchServe 提供 PyTorch 模型方面非常有帮助:aws.amazon.com/blogs/machine-learning/serving-pytorch-models-in-production-with-the-amazon-sagemaker-native-torchserve-integration/。
部署一个 Hugging Face 模型
与之前的示例相比,唯一的区别是我们必须使用 S3 中的模型工件来创建一个PyTorchModel对象,并构建一个Predictor模型,我们可以在其上使用deploy()和predict()。
-
从模型工件开始,我们定义一个
Predictor对象,然后用它创建一个PyTorchModel:from sagemaker.pytorch import PyTorchModel from sagemaker.serializers import JSONSerializer from sagemaker.deserializers import JSONDeserializer model = PyTorchModel( model_data=huggingface_estimator.model_data, role=sagemaker.get_execution_role(), entry_point='torchserve-predictor.py', framework_version='1.6.0', py_version='py36') -
聚焦于推理脚本(
torchserve-predictor.py),我们编写了一个模型加载函数,解决 Hugging Face 特有的 PyTorch 容器无法默认处理的情况:def model_fn(model_dir): config_path = '{}/config.json'.format(model_dir) model_path ='{}/pytorch_model.bin'.format(model_dir) config = AutoConfig.from_pretrained(config_path) model = DistilBertForSequenceClassification .from_pretrained(model_path, config=config) return model -
我们还添加了一个返回文本标签的预测函数:
tokenizer = AutoTokenizer.from_pretrained( 'distilbert-base-uncased') CLASS_NAMES = ['negative', 'positive'] def predict_fn(input_data, model): inputs = tokenizer(input_data['text'], return_tensors='pt') outputs = model(**inputs) logits = outputs.logits _, prediction = torch.max(logits, dim=1) return CLASS_NAMES[prediction] -
推理脚本还包括基本的
input_fn()和output_fn()函数,用于检查数据是否为 JSON 格式。你可以在本书的 GitHub 仓库中找到相关代码。 -
回到我们的笔记本,我们像往常一样部署模型:
predictor = model.deploy( initial_instance_count=1, instance_type='ml.m5.xlarge') -
一旦端点启动,我们预测一个文本样本并打印结果:
predictor.serializer = JSONSerializer() predictor.deserializer = JSONDeserializer() sample = {'text':'This camera is really amazing!} prediction = predictor.predict(test_data) print(prediction) ['positive'] -
最后,我们删除端点:
predictor.delete_endpoint()
如你所见,使用 Hugging Face 模型非常简单。这也是一种具有成本效益的构建高质量 NLP 模型的方式,因为我们通常只需对模型进行非常少的训练周期(epoch)微调。
为了结束本章,让我们看看 SageMaker 和 Apache Spark 如何协同工作。
使用 Apache Spark
除了我们一直在使用的 Python SageMaker SDK,SageMaker 还包括 Spark 的 SDK(github.com/aws/sagemaker-spark)。这使你能够直接从运行在 Spark 集群上的 PySpark 或 Scala 应用程序中运行 SageMaker 作业。
结合 Spark 和 SageMaker
首先,你可以将提取-转换-加载(ETL)步骤与机器学习步骤解耦。每个步骤通常有不同的基础设施需求(实例类型、实例数量、存储),这些需求需要在技术上和财务上都能满足。为 ETL 设置合适的 Spark 集群,并在 SageMaker 上使用按需基础设施进行训练和预测是一个强大的组合。
其次,尽管 Spark 的 MLlib 是一个令人惊叹的库,但你可能还需要其他工具,如不同语言的自定义算法或深度学习。
最后,将模型部署到 Spark 集群进行预测可能不是最佳选择。应考虑使用 SageMaker 端点,尤其是因为它们支持MLeap格式(combust.github.io/mleap-docs/)。
在以下示例中,我们将结合 SageMaker 和 Spark 构建一个垃圾邮件检测模型。数据将托管在 S3 中,垃圾邮件和非垃圾邮件(“ham”)各有一个文本文件。我们将使用在 Amazon EMR 集群上运行的 Spark 进行数据预处理。然后,我们将使用 SageMaker 中可用的 XGBoost 算法训练并部署模型。最后,我们将在 Spark 集群上进行预测。为了语言的多样性,这次我们使用 Scala 编写代码。
首先,我们需要构建一个 Spark 集群。
创建 Spark 集群
我们将如下方式创建集群:
-
从
sagemaker-cluster开始,再次点击下一步,然后点击创建集群。你可以在docs.aws.amazon.com/emr/找到更多详细信息。 -
在集群创建过程中,我们在左侧垂直菜单的Notebooks条目中定义我们的 Git 仓库。然后,我们点击Add repository:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_6.jpg
图 7.6 – 添加 Git 仓库
-
然后,我们创建一个连接到集群的 Jupyter 笔记本。从左侧垂直菜单中的Notebooks条目开始,如下图所示,我们为其命名,并选择我们刚刚创建的 EMR 集群和仓库。然后,我们点击Create notebook:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_7.jpg
图 7.7 – 创建 Jupyter 笔记本
-
一旦集群和笔记本准备好,我们可以点击Open in Jupyter,这将带我们进入熟悉的 Jupyter 界面。
一切准备就绪。让我们编写一个垃圾邮件分类器!
使用 Spark 和 SageMaker 构建垃圾邮件分类模型
在这个示例中,我们将利用 Spark 和 SageMaker 的结合优势,通过几行 Scala 代码来训练、部署和预测垃圾邮件分类模型:
-
首先,我们需要确保数据集已在 S3 中可用。在本地机器上,将这两个文件上传到默认的 SageMaker 桶(也可以使用其他桶):
$ aws s3 cp ham s3://sagemaker-eu-west-1-123456789012 $ aws s3 cp spam s3://sagemaker-eu-west-1-123456789012 -
返回到 Jupyter 笔记本,确保它正在运行 Spark 内核。然后,从 Spark MLlib 和 SageMaker SDK 中导入必要的对象。
-
从 S3 加载数据。将所有句子转换为小写字母。然后,移除所有标点符号和数字,并修剪掉任何空白字符:
val spam = sc.textFile( "s3://sagemaker-eu-west-1-123456789012/spam") .map(l => l.toLowerCase()) .map(l => l.replaceAll("[^ a-z]", "")) .map(l => l.trim()) val ham = sc.textFile( "s3://sagemaker-eu-west-1-123456789012/ham") .map(l => l.toLowerCase()) .map(l => l.replaceAll("[^ a-z]", "")) .map(l => l.trim()) -
然后,将消息拆分成单词,并将这些单词哈希到 200 个桶中。这个技术比我们在第六章,“训练自然语言处理模型”中使用的单词向量简单得多,但应该能奏效:
val tf = new HashingTF(numFeatures = 200) val spamFeatures = spam.map( m => tf.transform(m.split(" "))) val hamFeatures = ham.map( m => tf.transform(m.split(" ")))例如,以下消息中,桶 15 中的单词出现了一次,桶 83 中的单词出现了一次,桶 96 中的单词出现了两次,桶 188 中的单词也出现了两次:
Array((200,[15,83,96,188],[1.0,1.0,2.0,2.0])) -
我们为垃圾邮件消息分配
1标签,为正常邮件消息分配0标签:val positiveExamples = spamFeatures.map( features => LabeledPoint(1, features)) val negativeExamples = hamFeatures.map( features => LabeledPoint(0, features)) -
合并消息并将其编码为LIBSVM格式,这是XGBoost支持的格式之一:
val data = positiveExamples.union(negativeExamples) val data_libsvm = MLUtils.convertVectorColumnsToML(data.toDF)现在样本看起来类似于这样:
Array([1.0,(200,[2,41,99,146,172,181],[2.0,1.0,1.0,1.0,1.0])]) -
将数据分为训练集和验证集:
val Array(trainingData, testData) = data_libsvm.randomSplit(Array(0.8, 0.2)) -
配置 SageMaker SDK 中可用的 XGBoost 估算器。在这里,我们将一次性训练并部署:
val roleArn = "arn:aws:iam:YOUR_SAGEMAKER_ROLE" val xgboost_estimator = new XGBoostSageMakerEstimator( trainingInstanceType="ml.m5.large", trainingInstanceCount=1, endpointInstanceType="ml.t2.medium", endpointInitialInstanceCount=1, sagemakerRole=IAMRole(roleArn)) xgboost_estimator.setObjective("binary:logistic") xgboost_estimator.setNumRound(25) -
启动一个训练任务和一个部署任务,在托管基础设施上,就像我们在第四章,“训练机器学习模型”中使用内置算法时那样。SageMaker SDK 会自动将 Spark DataFrame 传递给训练任务,因此我们无需做任何工作:
val xgboost_model = xgboost_estimator.fit(trainingData_libsvm)正如你所期待的,这些活动将在 SageMaker Studio 的Experiments部分中可见。
-
部署完成后,转换测试集并对模型进行评分。这会自动调用 SageMaker 端点。再次提醒,我们无需担心数据迁移:
val transformedData = xgboost_model.transform(testData_libsvm) val accuracy = 1.0*transformedData.filter( $"label"=== $"prediction") .count/transformedData.count()准确率应该在 97% 左右,表现得还不错!
-
完成后,删除作业创建的所有 SageMaker 资源。这将删除模型、端点以及端点配置(一个我们还没有讨论过的对象):
val cleanup = new SageMakerResourceCleanup( xgboost_model.sagemakerClient) cleanup.deleteResources( xgboost_model.getCreatedResources) -
别忘了终止笔记本和 EMR 集群。你可以在 EMR 控制台轻松完成这一步。
这个示例演示了如何轻松地结合 Spark 和 SageMaker 各自的优势。另一种方法是构建包含 Spark 和 SageMaker 阶段的 MLlib 流水线。你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-spark 找到相关示例。
摘要
开源框架,如 scikit-learn 和 TensorFlow,简化了机器学习和深度学习代码的编写。它们在开发者社区中非常受欢迎,原因显而易见。然而,管理训练和部署基础设施仍然需要大量努力和技能,而这些通常是数据科学家和机器学习工程师所不具备的。SageMaker 简化了整个过程。你可以迅速从实验阶段过渡到生产环境,无需担心基础设施问题。
在本章中,你了解了 SageMaker 中用于机器学习和深度学习的不同框架,以及如何自定义它们的容器。你还学习了如何使用脚本模式和本地模式进行快速迭代,直到你准备好在生产环境中部署。最后,你运行了几个示例,其中包括一个结合了 Apache Spark 和 SageMaker 的示例。
在下一章中,你将学习如何在 SageMaker 上使用你自己的自定义代码,而无需依赖内置容器。
11万+

被折叠的 条评论
为什么被折叠?



