PaperSpace博客中文翻译五绝不原创的飞龙

在本文中,我们将看到GauGAN算法如何在粒度级别上工作。我们还将深入了解为什么Nvidia在这些算法的使用上投资如此之大。

这是四部分系列的第一部分。我们还将介绍:

我们开始吧!

另一方面,有条件的gan通常采用特定的输入来确定要生成的数据(换句话说,生成的数据是以我们提供的输入为条件的)。例如,在GauGAN中,输入是语义分割图,GAN根据输入图像生成真实图像,如下例所示。

GauGANtakesaSemanticSegmentationMap(left)asinputandproducesaphoto-realisticimageastheoutput(right).ImageCredits:Nvidia

类似地,其他条件生成网络可以创建:

还有更多。

发电机的输入包括:

第一个是必需的,而第二个和第三个输入是可选的。无论使用哪一组输入,它们在发送到生成器之前都会进行深度连接。

语义分割图基本上是一次性编码的,因此我们对每个类别都有一个一次性编码的图。这些地图然后被深度连接。

Diagramillustratinghowsemanticmapsareone-hotencodedbeforebeingsenttothegeneratorinGauGAN

只有当图像中的对象具有实例id时,边缘图才是可能的。在分段标签中,我们只关心对象的类别。如果两辆汽车重叠,那么汽车标签将是由两辆重叠汽车组成的斑点。当两个对象重叠时,这可能会混淆算法并导致其性能下降。

为了克服这个问题,在Pix2PixHD中引入了语义标签映射的使用。这是一个0-1二进制映射,其中除了四个相邻像素不属于同一个实例的像素之外,每个像素都是零。

通过将用作风格指南的图像传递通过编码器来产生编码的特征向量。

GauGAN的生成器是一个由SPADE块组成的全卷积解码器。SPADE代表空间自适应归一化块。当我们完成架构的高级概述后,我们将详细讨论这个组件。

GauGAN生成器和Pix2PixHD的架构有很大的不同。

首先,不涉及下采样。缩减像素采样层构建语义信息。相反,作者选择直接提供语义分割图作为每个SPADE块(对应于网络中的不同级别)的输入。

第二,与Pix2PixHD不同,上采样是通过最近邻调整大小来执行的,而不是通过使用转置卷积层。转置卷积层失去了很多支持,因为它们容易在图像中产生棋盘状伪像。从业者已经开始转向不可学习的上采样,然后是卷积层。作者只是跟随了这一趋势。

在论文中,作者认为无条件规范化会导致语义信息的丢失。这种形式的规范化包括批量规范化和实例规范化。

深度学习中的规范化通常包括三个步骤:

批次定额和实例定额在步骤1中有所不同,即如何计算统计数据。在批处理规范中,我们计算图像批处理的统计数据。在实例规范中,我们计算每个图像。

这里有一个例子可以帮助你发挥想象力。

Inbatchnormalization,thestatisticsarecomputedoverfeaturemapsacrossallbatches.Ininstancenormalization,thestatisticsarecomputedoverfeaturemapsacrossasingleimage.

然而,在SPADE中,我们以这样的方式修改批范数(注意,我们仍然跨每个特征图计算小批的统计),即我们学习特征图中每个像素的不同参数集,而不是学习每个通道的单个参数集。我们直接通过增加批范数参数的数量来达到像素的数量。

作者介绍了一个SPADE生成器模块,这是一个小的残差卷积网络,它产生两个特征映射:一个对应于逐像素的$\beta$,另一个对应于逐像素的$\gamma$。这些图的每个像素代表用于重新缩放特征图中相应像素的值的参数。

DesignofaSPADEUnit.Note,thisdescribestheSPADEunitintheSPADEResBlkdemonstratedinthediagramabove.

上图可能会让一些读者感到困惑。我们有一个叫做BatchNorm的模块,它只执行统计数据的计算。SPADE中统计量的计算类似于批量定额。稍后进行重新缩放。

在多GPU系统上如何实现规范化的上下文中,同步规范化被称为同步规范化。通常,如果您的批处理大小为32,并且您有8个GPU,PyTorch'snn.BatchNorm2dlayer将跨每个GPU分别计算4个批处理的统计数据,并更新参数。在同步批处理规范中,统计数据是在全部32幅图像上计算的。当每个GPU的批处理大小很小时,比如1或2,同步规范化很有用。小批量计算统计数据可能会产生非常嘈杂的估计值,导致训练紧张。

作为outoftheSPADE模块获得的特征映射被逐元素相乘并添加到归一化的输入映射中。

这是描述高根的方程式。

第一个原因是GauGAN的输入是一个语义图,它被进一步一次性编码。

这意味着GAN必须采用统一值的区域,精确地说是1,并产生具有不同值的像素,以便它们看起来像真实的物体。与特征图中每个通道的单组批范数参数相比,为每个像素设置不同组的批范数参数有助于更好地处理这项任务。

作者还声称SPADE导致更多的区别性语义信息。为了支持他们的观点,他们拍摄了两幅图像,这两幅图像都只有一个标签,一幅是天空,另一幅是草地。虽然我发现下面的推理很弱,但为了明智地涵盖这篇论文,我还是要指出来。

作者给出了一个例子来支持他们的说法,他们拍摄了两张图像,只包含一个标签。一个是天空,一个是草地。对这两个图像应用卷积会产生不同的值,但值是一致的。作者接着指出,应用实例范数将把两个不同值但一致的特征映射变成一个只含零点的同值特征映射。这导致语义信息的丢失。

然后,他们继续展示SPADE和InstanceNorm的输出如何在一个包含完全相同标签的语义图中有所不同。

然而,这似乎不像是苹果之间的比较。首先,作者声称标准化的结果是信息被抽取。然而,黑桃和实例规范中的标准化步骤是相同的。它们不同的地方是重新缩放步骤。

第二,在Pix2PixHD中,实例范数层的参数是不可学习的,实例范数仅仅是执行归一化($\gamma\(设置为1,\)\beta$设置为0)。然而,在GauGAN,SPADE有可学习的参数。

第三,比较批量规范和SPADE是比实例规范和SPADE更好的比较。这是因为实例Norm的有效批处理大小为1,而SPADE和BatchNorm都可以利用更大的批处理大小(更大的批处理大小导致更少的噪声统计估计)。

通常,鉴别器是一个分类器网络,末端是完全连接的层,根据鉴别器对图像的逼真程度,产生0到1之间的单一输出。

多尺度PatchGAN鉴别器是一个完全卷积的神经网络。它输出一个特征图,然后对其进行平均,以获得图像的“逼真度”得分。完全卷积有助于GAN工艺尺寸不变。

与普通的GANs不同,GauGAN不采用随机噪声向量,而只采用语义图。这意味着给定一个单一的输入语义图,输出总是确定的。这违背了图像合成的精神,因为生成不同输出的能力是非常重要的。仅重构输入的GAN与单位函数一样好。我们进行合成以生成超出我们训练数据的数据,而不仅仅是使用神经网络来重建它。

为此,作者设计了一种编码器。编码器主要获取一幅图像,将图像编码成两个向量。这两个向量用作正态高斯分布的均值和标准差。然后,从该分布中采样一个随机向量,然后与输入语义图一起连接,作为生成器的输入。

当我们对不同的向量进行采样时,合成结果也随之多样化。

在推断时,该编码器可以用作要生成的图像的样式指南。我们通过编码器传递要用作样式向导的图像。然后,生成的随机向量与输入语义图连接。

当我们从分布(其平均值和标准偏差由编码器预测)中采样不同的值时,我们将能够探索数据的不同模式。例如,每个随机向量将产生具有相同语义布局但不同模态特征(如颜色、亮度等)的图像。

GauGAN的损失函数由以下损失项组成。当我们经过的时候,我会逐一检查。

GauGAN合并了一个铰链损耗,这也见于SAGAN和GeometricGAN等论文。下面是损失函数

给定生成器生成的图像,我们创建一个图像金字塔,将生成的图像调整到多个比例。然后,我们使用每个尺度的鉴别器计算真实度得分,并反向传播损失。

这种损失促使GAN产生不仅能够欺骗发生器的图像,而且所产生的图像还应该具有与真实图像相同的统计特性。为了实现这一点,我们惩罚真实图像的鉴别器特征图和伪图像的鉴别器特征图之间的L1距离。

为生成的图像的所有尺度计算特征匹配损失。

作者对编码器使用KL发散损失$$L_{KLD}=D_{KL}(q(z|x)||p(z))$$

在上面的loss中,\(q(z|x)\)被称为变分分布,根据给定的实像\(x\)我们从中抽取出随机向量\(z\)而\(p(z)\)是标准的高斯分布

请注意,虽然我们可以在推断过程中使用样式图像,但分割图的基础事实在训练过程中充当我们的样式图像。这是有意义的,因为地面真相的风格和我们试图合成的图像是一样的。

你可以把上面的损失函数看作是自动编码器损失变化的正则化损失项。对于编码器,GauGAN表现为一种变化的自动编码器,GauGAN扮演解码器的角色。

对于熟悉变分自动编码器的本笔记,KL发散损失项充当编码器的正则项。这种损失不利于我们的编码器预测的分布和零均值高斯分布之间的KL散度。

如果没有这种损失,编码器可能会通过为我们数据集中的每个训练示例分配不同的随机向量来作弊,而不是实际学习捕获我们数据形态的分布。如果你对这个解释不清楚,我推荐你阅读更多关于可变自动编码器的内容,我在下面提供了链接。

所以,这就结束了我们对GauGAN架构及其目标函数的讨论。

在下一部分中,我们将讨论GauGAN是如何训练的,以及它与竞争对手的算法相比表现如何,尤其是它的前身Pix2PixHD。在那之前,你可以查看GauGAN的网络演示,它允许你使用一个类似绘画的应用程序创建随机的风景。

对象检测是一种重要的计算机视觉应用,它使计算机能够看到图像内部的内容。由于对象在图像中的位置不可预测,所以存在密集的计算来处理整个图像以定位对象。

本教程的大纲如下:

第一步负责提出候选区域。这一步的输出只是一些可能包含对象的区域。在第二步中,独立处理每个区域以确定是否存在对象。这是通过提取一组足以描述该区域的特征来完成的。最后,来自每个区域的特征被馈送到一个分类器,该分类器预测是否存在对象。如果有一个对象,那么分类器预测它的类别标签。

使用选择性搜索算法生成区域建议。该算法返回大量区域,其中图像中的对象很可能在这些区域之一中。

在ImageNet数据集上预先训练的CNN用于从每个区域提取特征。在R-CNN模型的末尾,区域特征向量被馈送到特定于类别的支持向量机(SVM),以预测对象的类别标签。

更快的R-CNN只是一个由3部分组成的大网络,其中训练从RPN开始端到端地进行,直到Softmax层。

下一节集中在区域提案,解释本教程的主要贡献之一;即使用静态而不是动态的区域提议。

importsysimportcv2img_path="img.jpg"im=cv2.imread(img_path)newHeight=400newWidth=int(im.shape[1]*400/im.shape[0])im=cv2.resize(im,(newWidth,newHeight))ss=cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()ss.setBaseImage(im)#HighrecallSelectiveSearchss.switchToSelectiveSearchQuality()rects=ss.process()print('TotalNumberofRegionProposals:{}'.format(len(rects)))numShowRects=1000imOut=im.copy()fori,rectinenumerate(rects):if(i

本节仅给出一个使用掩码R-CNN模型加载和进行预测的示例。下面的代码加载模型的预训练权重,然后检测对象。该模型在COCO对象检测数据集上进行训练。

该项目有一个名为model的模块,其中有一个名为MaskRCNN的类。创建该类的一个实例来加载MaskR-CNN模型的架构。使用load_weights()方法加载预训练的模型权重。

加载模型后,它就可以进行预测了。图像被读取并输入到detect()方法。此方法返回4个输出,分别代表以下内容:

最后,调用visualize模块中的display_instances()函数来突出显示检测到的对象。

下图显示了带标签的对象。每个对象都有一个边界框、遮罩、类别标签和一个预测分数。

要了解更多有关使用TensorFlow1和2在Keras中使用MaskR-CNN的信息,请查看以下资源:

为了达到本教程的目的,即构建一个只调查一组预定义区域的定向掩码R-CNN,下一节将讨论如何操作区域建议网络(RPN)来检索和编辑区域建议。

MaskR-CNN架构中负责产生区域提议的部分是(正如您可能已经猜到的)区域提议网络(RPN)。它使用锚的概念,这有助于产生不同比例和长宽比的区域建议。

默认情况下,调用detect()方法后返回的模型输出是ROI、类标签、预测分数和分段掩码。该部分编辑模型以返回由ProposalLayer返回的区域建议,然后调查这些区域。

本质上,MaskRCNN类被编辑,因此它的最后一层变成了ProposalLayer。下面是返回地区建议的MaskRCNN类的简化代码。

与之前的示例相比,模块名称从mrcnn更改为mrcnn_directed。

以下是模型返回的前4个区域建议:

r[0]Out[8]:array([0.49552074,0.0,0.53763664,0.09105143],dtype=float32)r[1]Out[9]:array([0.5294977,0.39210293,0.63644147,0.44242138],dtype=float32)r[2]Out[10]:array([0.36204672,0.40500385,0.6706183,0.54514766],dtype=float32)r[3]Out[11]:array([0.48107424,0.08110721,0.51513755,0.17086479],dtype=float32)区域的坐标范围从0.0到1.0。为了将它们返回到原始比例,区域坐标乘以图像的宽度和高度。以下是新的值:

r2[0]array([144.19653,0.0,156.45226,40.517887],dtype=float32)r2[1]Out[5]:array([154.08383,174.48581,185.20447,196.87752],dtype=float32)r2[2]Out[6]:array([105.3556,180.22672,195.14992,242.59071],dtype=float32)r2[3]Out[7]:array([139.9926,36.09271,149.90503,76.03483],dtype=float32)请注意,有大量的区域需要处理。如果这个数字减少了,那么模型就会快很多。下一节将讨论如何指导MaskR-CNN模型,其中用户告诉模型要在哪些区域中搜索对象。

我们可以从一个实验开始,在这个实验中,模型被迫只保留前4个区域建议。这里的想法是将保存区域提议的张量乘以掩码,对于前4个区域提议的坐标,该掩码被设置为1.0,否则为0.0。

在ProposalLayer结束时,正好在returnproposals行之前,可以使用以下3行。

zeros=np.zeros(shape=(1,self.config.POST_NMS_ROIS_INFERENCE,4),dtype=np.float32)zeros[0,:4,:]=1.0proposals=KL.Multiply()([proposals,tf.convert_to_tensor(zeros)])这里是只使用了4个区域的ProposalLayer的新代码。

也可以通过硬编码它们的值来为模型提供您自己的自定义坐标,但是它们必须在0.0和1.0之间缩放。

以下代码分配5个区域建议的静态坐标值。

zeros=np.zeros(shape=(1,self.config.POST_NMS_ROIS_INFERENCE,4),dtype=np.float32)zeros[0,0,:]=[0.0,0.0,0.2,0.3]zeros[0,1,:]=[0.42,0.02,0.8,0.267]zeros[0,2,:]=[0.12,0.52,0.55,0.84]zeros[0,3,:]=[0.61,0.71,0.87,0.21]zeros[0,4,:]=[0.074,0.83,0.212,0.94]proposals=tf.convert_to_tensor(zeros)ProposalLayer的代码现在可以简化为以下内容。

classProposalLayer(KE.Layer):def__init__(self,proposal_count,nms_threshold,config=None,**kwargs):super(ProposalLayer,self).__init__(**kwargs)self.config=configdefcall(self,inputs):zeros=np.zeros(shape=(1,self.config.POST_NMS_ROIS_INFERENCE,4),dtype=np.float32)zeros[0,0,:]=[0.0,0.0,0.2,0.3]zeros[0,1,:]=[0.42,0.02,0.8,0.267]zeros[0,2,:]=[0.12,0.52,0.55,0.84]zeros[0,3,:]=[0.61,0.71,0.87,0.21]zeros[0,4,:]=[0.074,0.83,0.212,0.94]proposals=tf.convert_to_tensor(zeros)returnproposals下图显示了用户指定的5个区域的边界框。这样,用户迫使模型在预定义的区域内搜索对象。

注意,Config类中的POST_NMS_ROIS_INFERENCE属性的默认值是1000,这意味着前面的代码在1000个区域中搜索对象。如果您想在特定数量的区域中搜索对象,您可以在Config类中设置POST_NMS_ROIS_INFERENCE属性,如下面的代码所示。这迫使模型只能在5个区域上工作。

用户可以设置该属性的值,以传递一些预定义的区域建议,模型将在这些区域中查看。这就是用户如何指导MaskR-CNN模型查看某些特定区域。

在下一个代码块中,POST_NMS_ROIS_INFERENCE属性被设置为5,以便只使用5个区域建议。创建新的NumPy个零数组,然后指定区域建议的坐标。

其中ImgHeight和ImgWidth分别是输入图像的高度和宽度。

r=model.detect([image])r=r[0]r2=r.copy()mrcnn_directed.visualize.display_instances_RPN(image=image,boxes=r2)使用定向屏蔽R-CNN的示例定向屏蔽R-CNN模型有两个主要用途:

下一个代码块给出了一个使用了MaskRCNNDirectedRPN类的用户定义区域建议的例子。也可以将SimpleConfig类中的REGION_PROPOSALS属性设置为None,以强制模型使用RPN生成区域提议。

也可以将SimpleConfig类中的REGION_PROPOSALS属性设置为None,以强制模型使用RPN生成区域建议。

Detectron2包括原始Detectron中可用的所有型号,如更快的R-CNN、MaskR-CNN、RetinaNet和DensePose。它还具有几个新的模型,包括级联R-CNN,全景FPN和张量掩模,我们将继续添加更多的算法。我们还增加了一些功能,如同步批处理规范和对LVIS等新数据集的支持

花卉数据集是各种花卉种类(如蒲公英和雏菊)的分类检测数据集。值得注意的是,血细胞检测不是Detectron2中可用的功能——我们需要训练底层网络来适应我们的定制任务。

准确标记的数据对于成功的机器学习至关重要,计算机视觉也不例外。

在这一节中,我们将演示如何使用LabelImg开始为对象检测模型标记您自己的数据。

LabelImg是一个免费的开源工具,用于图形化标记图像。它是用Python编写的,图形界面使用QT。这是一个简单、免费的方法,可以标记几百张图片来尝试你的下一个项目。

sudoapt-getinstallpyqt5-dev-toolssudopip3install-rrequirements/requirements-linux-python3.txtmakeqt5py3python3labelImg.pypython3labelImg.py[IMAGE_PATH][PRE-DEFINEDCLASSFILE]用标签标记图像

通过选择LabelImg左侧的“打开目录”打开您想要的图像集

要启动标签,请键入w,并绘制所需的标签。然后,键入ctrl(或command)S保存标签。键入d转到下一幅图像(键入a返回一幅图像)。

为了运行它:

现在,我们必须将自定义数据集上传到s3bucket。

为了让detectron2知道如何获得名为“my_dataset”的数据集,用户需要实现一个函数来返回数据集中的项目,然后告诉detectron2这个函数:

Metadata(evaluator_type='coco',image_root='./data/images',json_file='./data/trainval.json',name=my_dataset,thing_classes=[‘type1’,‘type2’,‘type3’],thing_dataset_id_to_contiguous_id={1:0,2:1,3:2})要获得关于数据集的目录存储信息的实际内部表示以及如何获得它们,可以调用dataset_dicts=datasetcatalog.get("my_dataset")。内部格式使用一个dict来表示一个图像的注释。

为了验证数据加载是否正确,让我们可视化数据集中随机选择的样本的注释:

importrandomfromdetectron2.utils.visualizerimportVisualizerfordinrandom.sample(dataset_dicts,3):img=cv2.imread(d["file_name"])visualizer=Visualizer(img[:,:,::-1],metadata=my_dataset_metadata,scale=0.5)vis=visualizer.draw_dataset_dict(d)cv2_imshow(vis.get_image()[:,:,::-1])训练模型现在,让我们在my_dataset数据集上微调coco预训练的R50-FPN掩模R-CNN模型。根据数据集的复杂程度和大小,可能需要5分钟到几小时。

预训练模型可从以下网址下载:

如果一切设置正确,您应该会看到如下内容:

梯度支持Tensorboard开箱即用

在实验页面上,您可以创建新的Tensorboard,只需点击“添加到Tensorboard”即可实时查看数据,即使在训练仍在进行中。

在Tensorboard中,您可以查看精度:

Livemetricsduringtraining

你也可以在他们训练的时候现场对比多个模特!

规模至关重要

为了加快这个过程,我们必须在多GPU设置中运行训练。

参数

当配置是为不同于当前使用的工作线程数的特定工作线程数(根据**cfg.SOLVER.REFERENCE_WORLD_SIZE**)定义时,返回一个新的cfg,其中总批处理大小被缩放,以便每个GPU的批处理大小与原始的**IMS_PER_BATCH//REFERENCE_WORLD_SIZE**保持相同。

其他配置选项也相应调整:训练步数和热身步数成反比。学习率按比例缩放。

例如,原始配置如下所示:

IMS_PER_BATCH:16BASE_LR:0.1REFERENCE_WORLD_SIZE:8MAX_ITER:5000STEPS:(4000,)CHECKPOINT_PERIOD:1000当在16个GPU上使用此配置而不是参考数字8时,调用此方法将返回一个新的配置,其中包含:

IMS_PER_BATCH:32BASE_LR:0.2REFERENCE_WORLD_SIZE:16MAX_ITER:2500STEPS:(2000,)CHECKPOINT_PERIOD:500请注意,原始配置和这个新配置都可以在16个GPU上进行训练。由用户决定是否启用该功能(通过设置**REFERENCE_WORLD_SIZE**)。

在本例中,我们比较了单个P4000和4个p4000。

你可以看到在重新训练模型时的速度差异——由于模型已经在更早的时候被训练过,开始时的准确率非常高,约为90%。

如果在训练部分结束时一切运行正常,我们应该在梯度仪表板中看到一个训练好的模型

Successfullytrainedmodel

如何在梯度上部署模型

此示例将加载之前训练的模型,并启动一个具有简单界面的webapp应用程序。在我们解释它如何工作之前,让我们在平台上启动它!

SuccessfullydeployedApp

importiofromflaskimportFlask,render_template,request,send_from_directory,send_filefromPILimportImageimportrequestsimportosimporturllib.requestapp=Flask(__name__)@app.route("/")defindex(): #rendertheindex.htmltemplate returnrender_template('index.html')if__name__=="__main__": #getport.Defaultto8080 port=int(os.environ.get('PORT',8080)) #setdebuglevel logging.basicConfig(level=logging.DEBUG) #runapp app.run(host='0.0.0.0',port=port)这个应用程序将简单地呈现模板index.html。我已经手动指定了端口。

接下来,我们将添加函数来获取图像。我们希望能够上传图像到网站。我们也希望能够提供一个网址和图像将自动下载的网站。我已经创建了如下代码。

如果代码因为某种原因不能下载图像,它将返回failure.html模板。这基本上只是一个简单的html页面,说明在检索图像时出现了错误。

另外,我指定了一个不同的@app.route。这将需要反映在index.html文件中。

前端

现在我将创建前端html代码。该界面允许用户上传图像或指定图像的URL。

Detectron2InstanceSegmentation

Uploadyourfile

FileSelectImage
Submit模型在这一部分中,我们将得到一个之前训练的detectron2模型来对图像进行推理。然后我们将它链接到我们现有的后端。

这部分稍微复杂一点。我们将创建一个名为Detector的新类。我们将创建探测器2所需的cfg。然后,我们将创建另一个函数来对图像进行推理。

首先,我们必须从repo加载模型配置:

#obtaindetectron2'sdefaultconfigself.cfg=get_cfg()#LoadModelConfigself.model=os.getenv('MODEL_CONFIG','mask_rcnn_R_50_FPN_3x.yaml')#loadvaluesfromafileself.cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/"+self.model))然后我们要设置是要在CPU还是GPU上运行推理。请注意,在GPU上训练的模型在CPU上无法正常工作。

#AdditionalInfowhenusingcudaiftorch.cuda.is_available():self.cfg.MODEL.DEVICE="cuda"else:#setdevicetocpuself.cfg.MODEL.DEVICE="cpu"下一阶段是加载我们之前训练过的训练模型文件:

#getModelfrompaperspacetrainedmodeldirectorymodel_path=os.path.abspath('/models/model/detectron/model_final.pth')ifos.path.isfile(model_path):print('UsingTrainedModel{}'.format(model_path),flush=True)else:#LoaddefaultpretrainedmodelfromModelZooprint('NoModelFoundat{}'.format(model_path),flush=True)model_path=model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/"+self.model)self.cfg.MODEL.WEIGHTS=model_path就是这样!

现在让我们创建推理函数

#detectronmodeldefinference(self,file):predictor=DefaultPredictor(self.cfg) im=cv.imread(file) rgb_image=im[:,:,::-1] outputs=predictor(rgb_image) #getmetadata metadata=MetadataCatalog.get(self.cfg.DATASETS.TRAIN[0]) #visualise v=Visualizer(rgb_image[:,:,::-1],metadata=metadata,scale=1.2) v=v.draw_instance_predictions(outputs["instances"].to("cpu")) #getimage img=Image.fromarray(np.uint8(v.get_image()[:,:,::-1])) returnimg我们还想跟踪我们的服务收到了多少请求——我们使用了PaperspaceUtils的内置度量系统

Servicereadyforaction

最终结果:

图像定位对我来说是一个有趣的应用,因为它正好介于图像分类和对象检测之间。这是使用PyTorch的对象定位系列的第二部分,所以如果您还没有阅读过前一部分,请务必阅读。

在进入本教程的机器学习部分之前,让我们用边界框来可视化数据集。这里我们可以看到如何通过与图像大小相乘来检索坐标。我们使用OpenCV来显示图像。这将打印出20个图像及其边界框的样本。

Visualizationofthedataset

#Splitthedataofimages,labelsandtheirannotationstrain_images,val_images,train_labels,\val_labels,train_boxes,val_boxes=train_test_split(np.array(img_list),np.array(labels),np.array(boxes),test_size=0.2,random_state=43)print('TrainingImagesCount:{},ValidationImagesCount:{}'.format(len(train_images),len(val_images)))现在,我们已经通过可视化快速浏览了数据集,并完成了数据集拆分。让我们继续为数据集构建自定义PyTorch数据加载器,目前数据集分散在变量中。

顾名思义,DataLoaders返回一个对象,该对象将在我们训练模型时处理整个数据提供系统。它在创建对象时提供了像shuffle这样的功能,它有一个“getitem”方法,可以处理每次迭代中应该输入的数据,所有这些东西都可以让你按照你希望的方式设计一切,而不会在训练部分使代码变得混乱。这使您可以更专注于其他优化。让我们从进口开始PyTorch部分。

fromPILimportImageimporttorchimporttorchvisionfromtorchvision.transformsimportToTensorimporttorchvision.transformsastransformsimportnumpyasnpimportmatplotlib.pyplotaspltimporttorch.nnasnnimporttorch.optimasoptimimporttorch.nn.functionalasFimportosimportpickleimportrandomimporttime如果可以的话,重要的事情之一是使用GPU来训练ML模型,尤其是当目标很大的时候。如果在PaperspaceGradient中运行,请选择带有GPU的机器。

device=torch.device('cuda'iftorch.cuda.is_available()else'cpu')device设备(type='cuda')

上面的输出表明你有一个GPU,而设备可以用来将数据和模型转换成GPU以便利用。继续讨论数据加载器。

classDataset():def__init__(self,train_images,train_labels,train_boxes):self.images=torch.permute(torch.from_numpy(train_images),(0,3,1,2)).float()self.labels=torch.from_numpy(train_labels).type(torch.LongTensor)self.boxes=torch.from_numpy(train_boxes).float()def__len__(self):returnlen(self.labels)#Toreturnx,yvaluesineachiterationoverdataloaderasbatches.def__getitem__(self,idx):return(self.images[idx],self.labels[idx],self.boxes[idx])#InheritingfromDatasetclassclassValDataset(Dataset):def__init__(self,val_images,val_labels,val_boxes):self.images=torch.permute(torch.from_numpy(val_images),(0,3,1,2)).float()self.labels=torch.from_numpy(val_labels).type(torch.LongTensor)self.boxes=torch.from_numpy(val_boxes).float()这里我们将创建Dataset类,首先加载图像、标签和盒子坐标,缩放到类变量的范围[0-1]。然后,我们使用“getitem”来设计每次迭代中的加载器输出。类似地,我们将构建ValDataset(验证数据集)数据加载器类。由于数据的结构和性质是相同的,我们将从上面的类继承。

现在我们有了数据加载器的类,让我们马上从各自的类创建数据加载器对象。

因此,为了理解我们必须如何设计架构,我们必须首先理解输入和输出。这里输入的是一批图片,所以会像(BS,C,H,W)一样风格化。BS批量大小,然后是通道、高度和重量。顺序很重要,因为在PyTorch中,图像就是这样存储的。在TensorFlow中,对于每幅图像是(H,W,C)。

说到输出,我们有两个输出,正如我们在上一篇博客开始时所讨论的。首先是你的分类输出,大小为(1,N),N是类的数量。第二个输出的大小为(1,4),为xmin、ymin、xmax和ymax,但在(0,1)的范围内。这有助于您以后根据图像大小缩放坐标。因此输出将是(CLF,BOX),第一个是上面讨论的分类,另一个是坐标。

现在,从机器学习部分解释,CLF将用于通过在输出上使用argmax来获得类索引,并且我们可以在末尾添加softmax激活函数用于概率输出。BOX输出将在通过sigmoid激活函数后产生,该函数使范围(0,1)。也就是说,其他一切都类似于传统的图像分类模型。让我们看看代码。

defget_num_correct(preds,labels):returntorch.round(preds).argmax(dim=1).eq(labels).sum().item()现在,我们将为训练和验证数据集创建数据加载器,以便在训练时输入成批的图像。

默认情况下,PyTorch模型设置为train(self.training=True)。当我们到达代码的验证部分时,我们将讨论为什么切换状态及其影响。

从我们创建的数据加载器中,我们访问由x、y和z组成的每批数据,它们分别是图像、标签和边界框。然后,我们将它们转换成我们喜欢的设备,例如,如果GPU可用的话。优化器处理深度学习中的反向传播,因此在训练之前,我们通过optimizer.zero_grad()将每批的梯度设置为零。

将输入(x)输入到模型后,我们进入损耗计算。这是一个重要的部分,因为这里涉及两种类型的损失:分类问题的交叉熵损失,以及寻找边界框坐标的回归部分的均方误差。如果我们在这里观察,我们可以看到我们如何将损失的总和发送给反向传播,而不是个体治疗。

之后,代码的评估部分类似于训练部分,除了我们不做反向传播。一旦我们转移到验证部分,我们使用model.eval()将模型状态切换到eval。正如我们前面所讨论的,默认情况下,模型处于训练状态,切换非常重要。一些层在训练/和评估期间具有不同的行为(如BatchNorm、Dropout),从而影响整体性能。这是一个有趣的点,因为当模型处于训练状态时,BatchNorm使用每批统计数据,并且删除层被激活。但是当模型处于评估(推断)模式时,BatchNorm层使用运行统计,而Dropout层被停用。这里需要注意的是,这两个函数调用都不运行向前/向后传递。它们告诉模型在运行时如何行动。这一点很重要,因为一些模块(层)被设计为在训练和推理期间表现不同,如果在错误的模式下运行,模型将产生意想不到的结果。在这里,我们使用之前编写的函数来计算准确性,以找到正确预测的数量。

在这个例子中,我们将训练30个历元,但是这是任意的,我们可以自由地试验更长或更短的历元值。

一旦您理解了培训功能,我们就开始培训吧。

train(model)这里需要理解的一件重要事情是,如果您希望重新训练,您必须重新初始化模型。没有该步骤的重新训练只会增加现有的模型参数。因此,处理这个潜在问题的一个好方法是在训练之前初始化模型,以便再次重复相同的过程。

当谈到图像定位时,预测不仅仅是从模型中获得输出。我们还必须处理边界框坐标,以生成一个实际的边界框来可视化结果,这甚至可能有助于生产。本节将有3个主要组件,预测脚本、预处理和后处理脚本,所以让我们开始吧。

defpreprocess(img,image_size=256):image=cv2.resize(img,(image_size,image_size))image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)image=image.astype("float")/255.0#Expanddimensionsaspredictexpectimageinbatchesimage=np.expand_dims(image,axis=0)returnimage后加工一旦我们得到输出,用更简单的术语来说,它们将是[CLF,BOX]的形式,我们将不得不使用边界框值来创建可视化的结果。边界框输入被缩放到范围[0,1],并且,当我们在最后使用sigmoid激活函数时,我们的预测也在范围[0,1]内。我们将不得不重新调整它们以得到xmin,ymin等。为此,我们只需将这些值乘以图像大小(此处为256)。

defpostprocess(image,results):#Splittheresultsintoclassprobabilitiesandboxcoordinates[class_probs,bounding_box]=results#Firstlet'sgettheclasslabel#Theindexofclasswiththehighestconfidenceisourtargetclassclass_index=torch.argmax(class_probs)#Usethisindextogettheclassname.class_label=num_to_labels[class_index]#Nowyoucanextracttheboundingboxtoo.#Gettheheightandwidthoftheactualimageh,w=256,256#ExtracttheCoordinatesx1,y1,x2,y2=bounding_box[0]##Convertthecoordinatesfromrelative(i.e.0-1)toactualvaluesx1=int(w*x1)x2=int(w*x2)y1=int(h*y1)y2=int(h*y2)#returnthelableandcoordinatesreturnclass_label,(x1,y1,x2,y2),torch.max(class_probs)*100现在我们已经有了预处理和后处理,让我们进入预测脚本。

在预测脚本中,我们首先从早期的网络中获得模型架构,然后将模型移动到我们首选的设备:Gradient笔记本中的GPU。一旦完成,我们加载模型的state_dict(),就像我们之前在验证中讨论的那样。我们将模型设置为eval状态,并使用预处理功能将图像准备好输入模型。然后我们可以使用PyTorch中的permute函数将图像数组从[N,H,W,C]重新制作成[N,C,H,W]。结果被提供给后处理函数,后者返回实际的坐标和标签。最后,我们使用matplotlib绘制带有边界框的结果图像。

#Wewillusethisfunctiontomakepredictiononimages.defpredict(image,scale=0.5):model=Network()model=model.to(device)model.load_state_dict(torch.load("models/model_ep29.pth"))model.eval()#ReadingImageimg=cv2.imread(image)##Beforewecanmakeapredictionweneedtopreprocesstheimage.processed_image=preprocess(img)result=model(torch.permute(torch.from_numpy(processed_image).float(),(0,3,1,2)).to(device))#Afterpostprocessing,wecaneasilyuseourresultslabel,(x1,y1,x2,y2),confidence=postprocess(image,result)#Nowannotatetheimagecv2.rectangle(img,(x1,y1),(x2,y2),(0,255,100),2)cv2.putText(img,'{},CONFIDENCE:{}'.format(label,confidence),(30,int(35*scale)),cv2.FONT_HERSHEY_COMPLEX,scale,(200,55,100),2)#ShowtheImagewithmatplotlibplt.figure(figsize=(10,10))plt.imshow(img[:,:,::-1])让我们继续在一幅图像上做推论。

image=''predict(image)下面给出了输出,你可以看到我们是如何在图像上得到一个边界框的。

ResultingImagewithBoundingBox

随着图像分类模型的流行,深度学习领域引起了人们的注意,剩下的就是历史了。今天,我们仅仅从一个单一的图像输入就能产生未来的技术。GAN模型生成图像,图像分割模型精确标记区域,对象检测模型检测正在发生的一切,就像通过普通的闭路电视摄像机识别繁忙街道上的人一样。这些成就真正为进一步的研究铺平了道路,也为随着可解释人工智能的到来,基本建模实践进入医学领域等严肃领域铺平了道路。图像定位对我来说是一个有趣的应用,因为它正好介于图像分类和对象检测之间。每当一个项目需要在检测到的类别上获得边界框时,人们经常会跳到对象检测和YOLO,这很好,但在我看来,如果你只是进行分类和定位单个类别,那就太过了。让我们深入研究图像本地化:概念、PyTorch中的实现和可能性。

架构和用例的多样性导致了分类的改进和对象的本地化。模型不仅可以将图像分类为x或y,还可以使用强大的边界框在图像中定位x或y,这种想法成倍地增加了可以围绕它构建的用例。定位的方法非常类似于图像分类,所以如果你不熟悉CNN,可能很难理解。在深入本地化领域之前,让我们先探讨一下它们。

如果你是神经网络的新手,请读一下这个。

假设你理解深度学习模型如何在基本的全连接层模型上工作,随着参数的不断增加和大规模全连接层的增加,它们的计算量会变得很大。

在这里,如果你看上面的图像,集中在红色和第一个蓝色层,它们之间的计算将采取(不准确,只是为了说明)3*4操作。现在,即使对于CPU来说,这也是相当轻量级的。但是当我们处理图像时,让我们取一个典型的64X64大小的图像。图像大小为64x64,每个有3个通道。每幅图像的RGB通道总共有12,288个像素。现在,这听起来很大,但当你想到用与上面完全连接的层相同的方式处理图像时,计算量会变得更大。当你将12288个神经元中的每一个与下一层的其他神经元相乘时,从计算的角度来看这是非常昂贵的,成本会急剧上升。

为了补救这一点,我们可以使用CNN:它以一种计算量更轻的方式来完成许多更重的模型架构的任务,同时提供更好的准确性。在卷积神经网络中,我们在张量上进行计算,核在通道上,并不断减少张量的大小,同时增加通道的数量,其中每个通道都有助于学习一些独特的东西,直到我们最终将通道堆栈展平为完全连接的层,如下所示。这是对CNN的一个简单的解释,你必须深入这个架构才能掌握它的每一个方面。

与目标检测模型相比,定位的迷人之处在于它的简单性。毫无疑问,对象检测模型处理更大的问题,并且必须对某些架构中的初始区域选择层所建议的所有区域进行分类。尽管如此,我们可以像回归问题一样预测坐标的想法还是很有趣的。因此,我们的通用架构(类似于图像分类模型)将具有额外的1个回归输出(具有4个坐标),并行训练以获得坐标。

对于对象定位,我们不仅需要图像和标签,还需要包含对象的边界框的坐标。我们必须使用图像注释/对象标记工具来准备这样的数据集,而XML是用于存储每个图像的坐标值的流行文件格式。

像往常一样,我们从导入库开始,包括pandas、CSV和XML,这将帮助我们处理输入。

首先,我们将解压数据集开始。上面给出了数据集链接。

!unziplocalization_dataset.zip让我们在读取和处理输入之前分析XML文件。理解这种结构将有助于您理解在遇到新的XML时如何阅读它。

Annotationcat.0.jpgCat-PascalVOC-export/Annotations/cat.0.jpgUnknown25625630catUnspecified0055.3582053319209110.992090947210452197.38757944915255171.24521098163842接下来,让我们开始阅读和处理图像和注释。我们将使用XMLminidom来读取XML文件。

defextract_xml_contents(annot_directory,image_dir):file=minidom.parse(annot_directory)#Gettheheightandwidthforourimageheight,width=cv2.imread(image_dir).shape[:2]#Gettheboundingboxco-ordinatesxmin=file.getElementsByTagName('xmin')x1=float(xmin[0].firstChild.data)ymin=file.getElementsByTagName('ymin')y1=float(ymin[0].firstChild.data)xmax=file.getElementsByTagName('xmax')x2=float(xmax[0].firstChild.data)ymax=file.getElementsByTagName('ymax')y2=float(ymax[0].firstChild.data)class_name=file.getElementsByTagName('name')ifclass_name[0].firstChild.data=="cat":class_num=0else:class_num=1files=file.getElementsByTagName('filename')filename=files[0].firstChild.data#Returntheextractedattributesreturnfilename,width,height,class_num,x1,y1,x2,y2让我们创建一个num_to_labels字典来从预测中检索标签。

num_to_labels={0:'cat',1:'dog'}一旦我们获得了所有这些数据,我们就可以将其存储为一个CSV文件,这样我们就不必为每次训练读取XML文件。我们将使用Pandas数据帧来存储数据,稍后我们将把它保存为CSV。

defpreprocess_dataset():#Liststhatwillcontainthewholedatasetlabels=[]boxes=[]img_list=[]h=256w=256image_dir='dataset/images'withopen('dataset.csv')ascsvfile:rows=csv.reader(csvfile)columns=next(iter(rows))forrowinrows:labels.append(int(row[3]))#ScalingCoordinatestotherangeof[0,1]bydividingthecoordinatewithimagesize,256here.arr=[float(row[4])/256,float(row[5])/256,float(row[6])/256,float(row[7])/256]boxes.append(arr)img_path=row[0]#Readtheimageimg=cv2.imread(os.path.join(image_dir,img_path))#Resizeallimagestoafixsizeimage=cv2.resize(img,(256,256))##ConverttheimagefromBGRtoRGBasNasNetMobilewastrainedonRGBimagesimage=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)#Normalizetheimagebydividingitby255.0image=image.astype("float")/255.0#Appendittothelistofimagesimg_list.append(image)returnlabels,boxes,img_list现在这个函数已经准备好了,让我们调用它并加载输入数据。作为数据预处理的最后一步,我们将混洗数据。

#Allimageswillresizedto300,300image_size=256#GetAugmentedimagesandboundingboxeslabels,boxes,img_list=preprocess_dataset()#Nowweneedtoshufflethedata,sozipalllistsandshufflecombined_list=list(zip(img_list,boxes,labels))random.shuffle(combined_list)#Extractbackthecontentsofeachlistimg_list,boxes,labels=zip(*combined_list)到目前为止,我们已经完成了数据预处理,包括从XML文件中读取数据注释,现在我们准备在PyTorch和模型架构设计中构建定制的数据加载器。实现将在本文的下一部分继续,我们将详细讨论上述方法来完成实现。感谢阅读:)

也就是说,这是一门粗糙的科学。像许多计算机视觉任务一样,将风格转移到图像的粗糙和较大区域的挑战远比将相同的风格转移到面部的精细特征容易。特别是像眼睛和嘴巴这样的区域很难让人工智能正确地近似生成。

AnexampleofJoJoGAN(trainedonfacesfromthetvshowArcane)applyingitsstylizationtorandomlysampledfaces.

在本教程中,我们将看看JoJoGAN-一种新颖的方法进行一次性风格的面部图像转移。这个PyTorch编写的架构旨在捕捉历史上难以解释的风格细节,例如传递保留眼睛形状或嘴巴细节等面部细节的风格效果。JoJoGAN旨在解决这个问题,首先近似成对的训练数据集,然后微调StyleGAN以执行一次性人脸风格化。

JoJoGAN能够摄取任何一张人脸图像(理想情况下是某种高质量的头像),使用GAN反演来近似成对的真实数据,并使用这些数据来精确调整预先训练的StyleGAN2模型。然后使StyleGAN2模型可推广,以便赋予的样式可以随后应用于新图像。以前的一次和几次拍摄尝试已经接近成功的水平,但JoJoGAN已经成功地实现了其生成的图像的极高质量水平。

按照下面的步骤,看看如何在渐变笔记本上运行JoJoGAN!

#importsimporttorchtorch.backends.cudnn.benchmark=Truefromtorchvisionimporttransforms,utilsfromutilimport*fromPILimportImageimportmathimportrandomimportosimportnumpyasnpfromtorchimportnn,autograd,optimfromtorch.nnimportfunctionalasFfromtqdmimporttqdmimportlpipsimportwandbfrommodelimport*frome4e_projectionimportprojectionase4e_projectionfromcopyimportdeepcopyos.makedirs('inversion_codes',exist_ok=True)os.makedirs('style_images',exist_ok=True)os.makedirs('style_images_aligned',exist_ok=True)os.makedirs('models',exist_ok=True)下一个单元将包导入到笔记本中,因为它们已经安装在机器上了。值得注意的是,我们同时使用本地和python安装包。确保不要更改.ipynb文件的位置,以确保其正常工作。然后,下面的os.makedirs()语句创建并检查我们将用于JoJoGAN的目录是否包含在内。

#Finishsetupdevice='cuda'#@param['cuda','cpu']latent_dim=512#Loadoriginalgeneratororiginal_generator=Generator(1024,latent_dim,8,2).to(device)ckpt=torch.load('stylegan2-ffhq-config-f.pt')original_generator.load_state_dict(ckpt["g_ema"],strict=False)mean_latent=original_generator.mean_latent(10000)#tobefinetunedgeneratorgenerator=deepcopy(original_generator)transform=transforms.Compose([transforms.Resize((1024,1024)),transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),])为了结束设置,我们需要实例化我们的生成器。我们将设备指定为cuda,因为我们使用的是GPU。我们还将两个生成器的潜在维度设置为512。

对于设置,我们首先实例化一个未经训练的生成器,以便在整个过程中进行微调。它与来自ffhqStyleGAN2模型检查点的状态字典相匹配,因此我们可以更新它的副本,以反映我们希望通过训练传递的风格。然后,副本可用于将输出与原始版本进行比较。最后,我们定义了一个在图像上使用的转换,以帮助简化样式转换本身。

%matplotlibinlinefilename='iu.jpeg'#@param{type:"string"}filepath=f'test_input/{filename}'#uploaded=files.upload()#filepath=list(uploaded.keys())[0]name=strip_path_extension(filepath)+'.pt'#alignsandcropsfacealigned_face=align_face(filepath)#my_w=restyle_projection(aligned_face,name,device,n_iters=1).unsqueeze(0)my_w=e4e_projection(aligned_face,name,device).unsqueeze(0)display_image(aligned_face,title='Alignedface')在我们继续训练模型或生成图像之前,让我们检查一下这里提供的辅助函数。最重要的是align_face功能。它将拍摄任何合适大小的照片,检查图像中是否有清晰的人脸,然后旋转它,使眼线与图像的底部平面平行。这确保了用于训练或风格转换的每个提交的照片都是合适的类型。

plt.rcParams['figure.dpi']=150pretrained='arcane_multi'#@param['art','arcane_multi','supergirl','arcane_jinx','arcane_caitlyn','jojo_yasuho','jojo','disney']#@markdownPreservecolortriestopreservecoloroforiginalimagebylimitingfamilyofallowabletransformations.Otherwise,thestylizedimagewillinheritthecolorsofthereferenceimages,leadingtoheavierstylizations.preserve_color=False#@param{type:"boolean"}ckpt=torch.load('arcane_multi.pt')generator.load_state_dict(ckpt["g"],strict=False)为了便于理解,下一个单元格已被拆分。在第一小节中,我们从检查点实例化新模型,并将状态字典加载到生成器中。这将设置我们的生成器使用该样式创建图像。

Theoriginalstyletemplate,theoriginalphoto,andthestyletransferredphoto

正如你所看到的,JoJoGAN能够在照片中表现出很多训练对象的特征。值得注意的是,蓝色的眼睛,浓眉,人中的轻微隆起,脸颊变色,较暗的调色板,和较重的阴影都被传递到新的图像上。让我们看看随机生成的面孔,看看是否存在相同的特征:

Thestyleimageofthetypethemodelwastrainedon,theoriginalphoto,andtheinputphotoafterthestyletransferisapplied.

从上面的照片中我们可以看到,风格转移的效果非常一致。这表明JoJoGAN可以有效地将风格转移到单张照片上,并且它高度通用化,能够在各种各样的面部、肤色和面部结构上工作。现在我们已经确认了JoJoGAN的功效,让我们看看下一节,看看我们如何在自己的图像上训练JoJoGAN。

为了训练一个新的JoJoGAN生成器,我们首先需要获得一个好的图像数据集。对于这个例子,我们将使用神秘电视节目中提供的图像来训练模型。

但在此之前,我们需要迈出关键的一步。早期的安装导致了libjpeg库的冲突。导航到~/usr/lib并通过在终端中运行以下命令删除冲突的库:

cd../usr/lib/rmlibturbojpeg.armlibturbojpeg.larmlibturbojpeg.sormlibturbojpeg.so.0rmlibturbojpeg.so.1.0rmlibjpeg.armlibjpeg.larmlibjpeg.sormlibturbojpeg.so.0rmlibturbojpeg.so.0.1.0既然已经解决了这个问题,剩下的就是运行最后两个单元。这将运行一个python脚本Run_Stylizer.py,该脚本根据第110行的num_iters变量指定的迭代次数,训练我们的生成器。另外,一定要在第75行的style_images/后面加上你的文件夹名。在运行单元之前,在脚本文件中为训练序列设置这些参数。如果遇到OOM错误,请尝试减小训练图像集的大小。

TheresultsofmypersonaltrainingrunusingtheArcaneimages

本教程向我们展示了如何使用JoJoGAN生成高质量的图像,并将输入样式转换到输入图像上。这个过程很容易调整,只需要在运行时将文件名添加到python脚本中。一定要在Gradient的免费GPU笔记本上免费亲自试用。

在深入研究优化器之前,我们先来看看梯度下降。梯度下降是一种优化算法,通过在与最陡上升相反的方向上移动来迭代地减少损失函数。在给定初始点的情况下,任何曲线上最陡上升的方向都是通过计算该点的坡度来确定的。与之相反的方向会把我们带到最小最快的地方。

在数学上,这是一种最小化目标函数J()的方法,其中代表模型的参数。深度架构通过遵循前馈机制进行预测,其中每一层都将前一层的输出作为输入,并使用由表示的参数(或者许多熟悉神经网络优化的人会称之为权重和偏差),并最终输出传递到下一层的转换后的特征。将最终层的输出与我们期望的真实输出进行比较,并计算损失函数。然后使用反向传播来更新这些参数,反向传播使用梯度下降来找到应该更新参数的确切方式。这些参数的更新取决于优化算法的梯度和学习速率。

基于梯度下降的参数更新遵循以下规则:

θ=θ∏j(θ)

其中η为学习率。

1D函数相对于其输入的梯度的数学公式如下:

虽然这对于连续函数是准确的,但是在计算神经网络的梯度时,我们将主要处理离散函数,并且计算极限并不像上面显示的那样简单。

上述方法(前向差分)被证明是不太准确的,因为截断误差的量级是O(h)。相反,使用中心差分方案,如下所示:

在中心差分法中,截断误差的量级为O(h2)。截断误差基于两个公式的泰勒展开式。这里的和这里的可以很好地解释这一点。

有几种梯度下降尝试解决香草算法的某些限制,如随机梯度下降和允许在线学习的小批量梯度下降。普通梯度下降法计算整个数据集的梯度,而批量梯度下降法允许我们在处理几批数据的同时更新梯度,从而在处理大型数据集时提高内存效率。

让我们从如何实现香草和动量梯度下降开始,看看算法是如何工作的。然后,我们将在等高线图上可视化2D函数的梯度更新,以更好地理解算法。

更新将不会基于一个损失函数,而只是朝着与最陡上升相反的方向迈出一步。为了得到最陡上升的方向,我们将首先编写函数来计算函数的梯度,给出需要计算梯度的点。我们还需要另一个参数来定义我们的数值微分步骤的大小,用h表示。

采用多个坐标作为输入的函数的数值微分的中心差分方案可以如下实现。

importnumpyasnpdefgradient(f,X,h):grad=[]foriinrange(len(X)):Xgplus=np.array([xifnoti==jelsex+hforj,xinenumerate(X)])Xgminus=np.array([xifnoti==jelsex-hforj,xinenumerate(X)])grad.append(f(*Xgplus)-f(*Xgminus)/(2*h))returnnp.array(grad)普通梯度下降更新将如下所示:

defvanilla_update(epoch,X,f,lr,h):grad=gradient(f,X,h)X1=np.zeros_like(X)foriinrange(len(X)):X1[i]=X[i]-lr*grad[i]print('epoch:',epoch,'point:',X1,'gradient:',grad)returnX1你可以把学习率想象成梯度更新的步长。

我们将在Ackley函数上测试我们的算法,Ackley函数是测试优化算法的流行函数之一。阿克利的函数看起来像这样。

importnumpyasnpdefackleys_function(x,y):return-20*np.exp(-0.2*np.sqrt(0.5*(x**2+y**2)))\-np.exp(0.5*(np.cos(2*np.pi*x)+np.cos(2*np.pi*y)))\+np.e+20现在,为了最终测试我们的香草梯度下降:

if__name__=='__main__':h=1e-3f=ackleys_functionpoint=np.array([-2.,-2.])i=0lr=0.00001whileTrue:new_point=vanilla_update(i+1,point,f,lr,h)plt.plot(*point,'ro',ms=1)ifnp.sum(abs(new_point-point))

为了实现动量更新,除了计算当前点的梯度之外,还必须存储先前步骤的梯度,用于动量步骤的计算。参数m定义为动量,函数可以实现如下。

defmomentum_update(epoch,X,f,lr,m,h,vel=[]):grad=gradient(f,X,h)X1=np.zeros_like(X)foriinrange(len(X)):vel[i]=m*vel[i]+lr*grad[i]X1[i]=X[i]-vel[i]print('epoch:',epoch,'point:',X1,'gradient:',grad,'velocity:',vel)returnX1,vel最终的循环将如下所示:

if__name__=='__main__':h=1e-3f=ackleys_functionpoint=np.array([-2.,-2.])vel=np.zeros_like(point)i=0lr=0.00001m=0.9grads=[]whileTrue:new_point,vel=momentum_update(i+1,point,f,lr,m,h,vel=vel)plt.plot(*point,'bo',ms=1)ifnp.sum(abs(new_point-point))

2DAckley'sfunction

2DcontoursofAckley'sfunction

SurfaceplotforAckley'sfunction

3DcontoursforAckley'sfunction

这里,X和Y恰好是一个网格而不是1D阵列。我们可以使用numpy函数np.meshgrid创建一个meshgrid。

您还可以利用令人惊叹的VisPy库快速创建3D可视化效果,并实时研究它们。

importsysfromvispyimportapp,scenedefget_vispy_surface_plot(x,y,function):canvas=scene.SceneCanvas(keys='interactive',bgcolor='w')view=canvas.central_widget.add_view()view.camera=scene.TurntableCamera(up='z',fov=60)X,Y=np.meshgrid(x,y)Z=function(X,Y)p1=scene.visuals.SurfacePlot(x=x,y=y,z=Z,color=(0.3,0.3,1,1))view.add(p1)scene.Axis(font_size=16,axis_color='r',tick_color='r',text_color='r',parent=view.scene)scene.Axis(font_size=16,axis_color='g',tick_color='g',text_color='g',parent=view.scene)scene.visuals.XYZAxis(parent=view.scene)canvas.show()ifsys.flags.interactive==0:app.run()returnscene,app这应该给你一个画布来玩这个情节。

要在等高线图上显示普通梯度下降的梯度下降更新,请使用以下代码。

if__name__=='__main__':x=np.linspace(-2,2,1000)h=1e-3f=ackleys_functiona,b=np.meshgrid(x,x)Z=f(a,b)contours=plt.contour(a,b,Z,colors='black',linestyles='dashed',linewidths=1)plt.clabel(contours,inline=1,fontsize=10)plt.contourf(a,b,Z)plt.xlabel('X')plt.ylabel('Y')point=np.array([-2.,-2.])i=0lr=0.00001whileTrue:new_point=vanilla_update(i+1,point,f,lr,h)plt.plot(*point,'ro',ms=1)ifnp.sum(abs(new_point-point))

对于应用动量更新和绘图,您可以使用以下代码:

if__name__=='__main__':x=np.linspace(-2,2,1000)h=1e-3f=ackleys_functiona,b=np.meshgrid(x,x)Z=f(a,b)contours=plt.contour(a,b,Z,colors='black',linestyles='dashed',linewidths=1)plt.clabel(contours,inline=1,fontsize=10)plt.contourf(a,b,Z)plt.xlabel('X')plt.ylabel('Y')point=np.array([-2.,-2.])vel=np.zeros_like(point)i=0lr=0.00001m=0.1grads=[]whileTrue:new_point,vel=momentum_update(i+1,point,f,lr,m,h,vel=vel)plt.plot(*point,'bo',ms=1)ifnp.sum(abs(new_point-point))

梯度下降算法有几种实现,它们都有解决特定问题的小调整。

一些流行的梯度下降算法包括:

让我们导入开始我们的培训脚本所需的所有东西。我们将使用PyTorch优化器及其ResNet18实现。我们将使用Matplotlib来可视化我们的结果。

importtorchfromtorchimportnnfromtorchimportoptimfromtorch.utils.dataimportRandomSampler,DataLoaderfromtorchvisionimportmodelsfromtorchvisionimporttransformsfromtorchvision.datasetsimportCIFAR10ascifarfromtorchvisionimportdatasetsimporttimeimportpickleimportrandomimportnumpyasnpfromtqdmimporttqdmfrommatplotlibimportpyplotasplt为了确保我们的结果是可重复的,让我们为torch、NumPy和Pythonrandom模块设置PRNG种子。

然后,我们创建一个扩充和规范化的数据集。CIFAR数据集用于创建我们的训练和测试数据加载器。

torch.manual_seed(0)torch.cuda.manual_seed(0)np.random.seed(0)random.seed(0)DATA_PATH='cifar'trans=transforms.Compose([[transforms.RandomHorizontalFlip(),transforms.RandomCrop(32,padding=4),transforms.ToTensor(),transforms.Normalize(mean=[n/255\.fornin[129.3,124.1,112.4]],std=[n/255\.fornin[68.2,65.4,70.4]])])train=cifar(DATA_PATH,train=True,transform=trans,download=False)test=cifar(DATA_PATH,train=False,transform=trans,download=False)batch_size=64train_size=len(train)test_size=len(test)train_dataloader=DataLoader(train,shuffle=True,batch_size=batch_size)test_dataloader=DataLoader(test,shuffle=False,batch_size=batch_size)我们将为此任务训练一个ResNet18模型。ResNet18模型默认输出1000个特征。为了使其适用于我们的数据集,我们添加了一个具有1000个输入要素和10个输出要素的线性图层。

classCifar10_Resnet18(nn.Module):def__init__(self,):super(Cifar10_Resnet18,self).__init__()self.base=models.resnet18(pretrained=True)self.classification=nn.Linear(in_features=1000,out_features=10)defforward(self,inputs):out=self.base(inputs)out=self.classification(out)returnout如果使用GPU,请将设备设置为CUDA类型。

device=torch.device(type='cuda')让我们定义一个所有优化器的字典,这样我们就可以创建一个循环来遍历所有优化器。字典的值是定义放入字符串的优化器的命令。稍后我们将使用eval函数来实现优化器。

optimizers={'SGD':'optim.SGD(model.parameters(),lr=0.01,momentum=0.9)','Adam':'optim.Adam(model.parameters())','Adadelta':'optim.Adadelta(model.parameters())','Adagrad':'optim.Adagrad(model.parameters())','AdamW':'optim.AdamW(model.parameters())','Adamax':'optim.Adamax(model.parameters())','ASGD':'optim.ASGD(model.parameters())',}主训练循环训练每个优化器50个时期,并通知我们关于训练准确性、验证准确性、训练损失和测试损失的信息。我们使用CrossEntropyLoss作为我们的损失标准,最后我们将所有的度量保存为pickle文件。对于每个优化器,都会初始化一个新的模型,然后我们使用eval函数根据未训练的模型参数定义优化器。

我们看到,对于我们选择的任务,Adamax始终比所有其他优化器执行得更好。其次是SGD、Adam和AdamW,分别是训练精度。

对于看不见的数据,在50个时期之后,Adamax、SGD、Adam和AdamW的模型性能是相似的。Adamax和SGD在最初几个时期的改进最大。

SGD和Adamax再次看到了持续的强劲表现,这也反映在训练精度上。

然而,验证集的失败让亚当和亚当成为了胜利者。对于SGD和Adamax,损失在接近结束时增加,表明需要早期停止机制。

从图中可以清楚地看到,对于我们选择的任务,Adagrad和Adadelta线性提高,但性能不如Adammax、SGD、Adam或AdamW。

其他鲜为人知的优化器包括:

要更深入地研究这些稍微不太流行的梯度下降算法,请查看本文。为了比较上面提到的一些优化器,他们使用了6个测试问题:

并测试了以下自适应学习率优化器:

结果可通过下表进行总结:

它们还在下面的图中展示了几个时期的训练和验证损失。

对于相同的任务,他们还测试了非自适应学习率优化器,例如:

对于CIFAR10-ResNet18任务,结果如下:

而对于其他任务:

损失图如下所示:

我们研究了梯度下降,并在Python中从头开始实现了香草和动量更新机制。我们还将Ackley函数的梯度更新可视化为沿等高线图的移动。我们使用CIFAR10数据集对图像分类任务的几个优化器进行了基准测试,并为此训练了一个ResNet18。结果表明,Adamax、SGD、AdamW和Adam表现良好,而Adagrad和Adadelta表现不佳。然后,我们看了几个不太流行的基于梯度下降的优化器,它们目前正在深度学习中使用。最后,我们看了我们讨论的几个优化器在不同任务上的表现,包括为卷积架构、LSTMs和可变自动编码器调整权重。

当处理用例时,例如银行领域的欺诈交易检测、电子商务领域的销售峰谷分析、识别网络中的恶意节点/数据包等,您可能希望检测数据集中的异常。除了不一定要求将检测异常值作为最终目标的用例之外,在尝试拟合学习大多数数据点所拥有的一般模式的模型时,您可能希望考虑处理数据集中的这些数据点。通常,剔除异常值的决定取决于开发人员考虑的因素,如潜在的异常值是否是群体的自然组成部分,或者它是否是由一些仪器误差等因素导致的。数据中的异常会导致学习扭曲的数据表示,提供底层数据的误导性表示。因此,在处理这些点时,有必要通过坚持机器学习模型的假设来更好地拟合机器学习模型。

误报——模型说这是个异常值。但不是离群值。

假阴性——模型表示这不是异常值。但这是个例外。

现在让我们深入了解ABOD及其变体的细节-

该算法非常简单,描述如下-

让我们也从视觉上来理解这一点

ABODillustration****

如左图所示,我们可以看到两个集群,一个是正常点,另一个是异常点,即单个蓝点。如果我们选择红点作为兴趣点(pivot,我们想看看这个点是不是离群点;我们将计算这个点和空间中任何其他两点所围成的角度。在对所有的对进行迭代并计算这个枢轴所包含的角度时,我们可能会观察到角度的许多变化。这样的模式表明支点是具有高内聚力的集群的一部分。

Approximate(Fast)ABOD****

从这两个例子中可以看出,在计算包围角时,只考虑了枢轴点c(左边红色,右边绿色)的k个最近邻居,这使得计算速度快得惊人。

****##密码

您可以使用如下所示的pip安装PyOD-

$>pipinstallpyod接下来,我们可以使用PyOD的generate_data方法在2-D空间中生成150个随机样本。我特别选择了2-D空间,而不是更高的值,因为它易于可视化。我还将污染率设置为10%,即150个数据点中的15个数据点是异常值。如文档中所述,正常点通过多元高斯分布生成,异常点通过均匀分布生成。

frompyod.utils.dataimportgenerate_dataX_train,Y_train=generate_data(n_train=150,n_features=2,train_only=True,contamination=0.1,random_state=42)x1,x2=X_train[:,0],X_train[:,1]让我们看看数据分布是怎样的-

SyntheticDatawithOutlier(markedinred)

接下来,我们通过将N^3搜索空间缩小到仅计算10个邻居形成的角度来快速拟合ABOD。然后我们继续计算误差%。

frompyod.models.abodimportABODabod_model=ABOD(contamination=0.1,method='fast',n_neighbors=10)abod_model.fit(X_train)pred=abod_model.predict(X_train)error=(pred!=Y_train).sum()print(f'Error%={(error/len(pred))*100}')>>Error%=2.6666接下来,我们绘制内点和外点的预测。

ABODpredictionsforOutlierDetection

从上图可以看出,橙色和紫色点分别是预测的异常值和内嵌值。我们的模型总共产生4个错误(2个假阳性,2个假阴性)。

因此,我们以此结束这篇博客。请随意检查PyOD库提供的其他算法,并尝试将其中一些算法组合起来以获得最佳结果。人们经常使用的另一种流行的无监督异常检测技术称为隔离林。请随意查看这个博客,获得这个概念的精彩演示。

谢谢大家!****

对于经典的机器学习算法,超参数优化问题已经以许多不同的方式得到解决。一些例子包括使用网格搜索、随机搜索、贝叶斯优化、元学习等等。但是当考虑深度学习架构时,这个问题变得更加难以处理。在本文中,我们将涵盖神经架构搜索的问题和当前的艺术状态。本文假设不同的神经网络和深度学习架构的基本知识。

这是一个系列的第1部分,将带您了解什么是神经架构搜索(NAS)问题,以及如何使用Keras实现各种有趣的NAS方法。在这一部分中,我们将涉及控制器(RNNs)、加强梯度、遗传算法、控制搜索空间和设计搜索策略等主题。这将包括该领域的最新论文的文献综述。

深度学习工程师应该对什么架构可能最适合什么情况有直观的理解,但这种情况很少发生。一个人可以创造的可能架构是无穷无尽的。想想我们用于迁移学习的所有在ImageNet上训练的卷积骨干。以ResNet为例,它具有50、100和150层的变体。有许多方法可以调整这些架构——添加额外的跳过连接,删除卷积块等。

神经结构搜索的目的是在给定数据集的情况下,自动执行寻找最佳模型结构的过程。

为了理解应用于这项任务的主要方法,我们需要一些深度学习、优化和计算机科学的跨领域知识。人们解决这个问题的方法从强化学习到进化算法。

神经架构搜索的早期解决方案可以被视为一个多步循环,大致如下:

换句话说:

这种广泛的概述让我们对我们试图解决的问题有了一个概念,但留下了几个未回答的问题,例如:

要回答这些问题,我们应该先讨论一些基础理论。

阅读本文的大多数人可能已经知道什么是rnn,但是为了完整起见,我们仍然会触及一些基本概念。递归神经网络接受顺序输入,并根据训练数据预测序列中的下一个元素。普通的递归网络将基于所提供的输入,处理它们先前的隐藏状态,并输出下一个隐藏状态和顺序预测。将该预测与地面真实值进行比较,以使用反向传播来更新权重。

我们也知道rnn容易受到消失和爆炸梯度的影响。为了解决这个问题,LSTMs应运而生。LSTMs使用不同的门来管理序列中每个先前元素的重要性。还有LSTMs的双向变体,其从左到右以及从右到左学习不同元素的顺序依赖性。

在神经结构搜索的背景下,这种或那种形式的递归网络将会派上用场,因为它们可以作为控制器来创建顺序输出。这些顺序输出将被解码,以创建神经网络架构,我们将反复训练和测试这些架构,以实现更好的架构建模。

强化学习流程看起来像这样。给定环境、当前状态和一组可能的操作:

“策略梯度”指的是更新此策略的不同方式。加强是一种策略梯度算法,最初用于NAS上,以优化控制器的体系结构搜索过程。强化策略梯度试图通过最大化每个搜索步骤的对数似然性与每个步骤的报酬的乘积,以最大化代理的总报酬的方式优化目标函数。奖励可以是由控制器创建的每个架构的验证准确性的某个函数。

对于神经结构搜索,我们将发现我们的控制器可以通过使用增强梯度来优化以创建更好的结构。这里,动作是您的架构中可能的层或连接,策略是每个控制器步骤中这些层的softmax分布。

同样的优化过程可以使用遗传算法来完成,这是一类旨在复制种群在自然界中如何进化以优化函数的算法。整个工作流程看起来像这样:给定一组初始的解决方案...

执行这些步骤有几种方法。遗传算法变得越来越复杂,包括寿命、多目标优化、精英主义、多样性、好奇心、存档前代等。

至少在最初的几个时代,这看起来像是创建架构的盲目尝试。

NAS算法设计一个特定的搜索空间,并在搜索空间中搜寻更好的体系结构。上面提到的论文中卷积网络设计的搜索空间可以在下图中看到。

如果层数超过最大值,算法将停止。在他们后来的实验中,他们还在搜索空间中添加了跳过连接、批量标准化和重新激活。类似地,他们通过使用如下所示的搜索空间创建不同的递归单元架构来创建RNN架构。

他们使用扩展的搜索空间,包括各种内核大小的卷积、池化和深度可分离卷积,首先创建:

进入神经架构搜索的大多数工作都是针对这部分问题的创新:找出哪些优化方法效果最好,以及如何改变或调整它们,以使搜索过程更快地产生更好的结果,并保持一致的稳定性。已经尝试了几种方法,包括贝叶斯优化、强化学习、神经进化、网络变形和博弈论。我们将逐一研究所有这些方法。

混合奖励函数被定义为:

他们提出了一个受生物认知理论启发的镜像刺激函数来提取专家人类设计网络(ResNet)的抽象拓扑知识。他们在工作中回答了两个主要问题:

我们来看看他们是如何回答这两个问题的。

作者使用网络前两层的超参数(内核大小、操作类型和索引)来编码他们所谓的“状态特征码”。它们使用特征计数将这些特征代码编码到嵌入中,如下式所述。

在这里,他们面临着经典的探索-利用权衡,其中算法需要创建拓扑结构,类似于专家网络,同时还有效地探索搜索空间。政策模仿会导致我们的网络提高强先验并抑制搜索过程。为了避免这一点,他们设计了一个镜像刺激函数:

他们应用Q学习,使用上面显示的奖励的标准化版本来指导他们的搜索。IRLAS能够在ImageNet和移动环境中实现最先进的结果。

这种表示可以是:

HillclimbingalgorithmforNAS

初始网络的结构是四个块[Conv、巴奇诺姆、雷卢、最大池],后面是最后一个块[Conv、巴奇诺姆、雷卢、西格蒙德]。卷积层包含128个跨度为1的3×3内核的过滤器,而最大池层包含跨度为2的2×2窗口。网络用热重启随机梯度下降法训练,学习率下降。

Shapley值和边际收益如何工作的解释可以在这里找到。

参与者可以被视为网络的结构组件。例如,在这个联盟游戏中,单一隐藏层网络中的每一个神经元都会扮演一个玩家的角色。对于收益函数,存在几种不同的可能选择,如训练损失、验证准确性等。在这项工作中,交叉熵准确性被用作支付函数,因为它的有界性质。他们从评估精度中减去基线(回归精度)来获得收益。Shapley值通过随机采样来近似,并用于最终修剪模型,以根据哪些玩家具有最低的Shapley值来减少玩家的数量。

在本系列的下一部分,我们将开始研究如何为多层感知器实现NAS,以便了解这里讨论的核心概念是如何工作的。

在这一部分中,我们将对RL世界进行一次简短的游览,包括基本术语、RL和其他形式的机器学习之间的相似性和不同性、RL算法的剖析等等。

以下是我们将在本帖中涉及的内容。

那么,我们开始吧。

强化学习的中心目标是最大化累积回报,或回报。为了做到这一点,代理应该找到一个策略$\pi(s)\(来预测一个动作\)a\(,给定状态\)s$,使回报最大化。

就像我们在标准ML中使用梯度下降来最小化损失函数一样,我们也可以使用梯度上升来最大化回报吗?(梯度上升不是找到最小值,而是收敛到最大值。)还是有问题?

考虑回归问题的均方损失。损失的定义如下:

一般来说,当我们谈论最大化期望回报作为解决RL任务的目标时,我们谈论最大化$R_1$即从初始状态的回报。

为了区分$R_1$return和一般return$R_t$,我们称前者为测试return,而称后者为return。

Thereturnforanystateissumofcumulativerewardfromthatstatetotheterminalstate.

在这种情况下,我们基本上会多次运行该策略,并对单个回报进行平均,以获得回报的估计值。下面的表达式估计了预期收益。

这是贴现收益$G_1$的样子。

$$g_1=r_1+\gammar_{2}+∞r_3+\gamma=3r_4+……$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

这通常被称为贴现预期回报。$\gamma$称为折扣系数

Ahigher$\gamma$meansthatthetheagentlooksmuchfurtherintothefuture.Conversely,theeffectofcollisiononthereturnisseenonmuchearliertimestepswithahigher$\gamma$

在合理的假设下,贴现无限和可以被证明是收敛的,这使得它们在没有终态的连续环境的情况下是有用的。

虽然我们可以用上面提到的方法定义计算贴现回报,但这里有一个问题。计算任何一个州的回报都需要你先完成这一集或者执行大量的步骤,然后才能计算回报。

计算回报的另一种方法是提出一个函数,它将州$s\(和保单\)\pi$作为输入,并直接估计贴现回报的价值,而不是我们必须将未来的回报相加。

策略$\pi\(的值函数写为\)V^\pi(s)\(。如果我们从状态\)s\(开始遵循策略\)\pi$的话,它的值等于预期收益。在RL文献中,以下符号用于描述价值函数。

$$v^\pi(s)=\mathbb{e}[g_t|s_t=s]$$

注意,我们没有把$G_t\(写成州和政策的函数。这是因为我们把\)G_t$仅仅当作环境回报的总和。我们不知道(在无模型环境中)这些奖励是如何计算的。

然而,$V^\pi(s)$是一个函数,我们用神经网络这样的估计器来预测自己。在这里,它被写成符号中的一个函数。

价值函数的一个微小修改是Q函数,它估计如果我们在州$s\(采取行动\)a\(在政策\)\pi$下的预期贴现回报。

$$q^\pi(s,a)=\mathbb{e}[g_t|s_t=s,A_t=a]$$

最大化所有状态的价值函数的策略被称为最优策略。

在状态空间太大的情况下,或者我们处于无模型设置中,我们可以使用监督学习来估计价值函数,其中标签是通过平均许多回报来估计的。

我们可以混合回报和价值函数,得出所谓的n步回报。

$$g^{n}t=r_{t}+\伽马r+...+\伽马^{n-1}r_{t+n-1}+\伽马^3V(s_{t+n})$$

这里有一个3步返回的例子。

$$g^{3}t=r_{t}+\伽马r_{t+1}+\gamma^2r_{t+2}+\伽马^3V(s)$$

在文献中,使用价值函数来计算剩余的回报(在n步之后)也被称为自举。

在第三部分,我们更新政策,使其采取行动最大化贴现期望报酬(DER)

在DeepRL中有几种流行的方法来学习最优策略。这两种方法都包含可以使用反向传播进行优化的目标。

在Q学习中,我们学习一个Q函数,并通过对每个动作的Q值表现贪婪来选择动作。我们优化损失,目的是学习Q函数。

我们还可以通过对其进行梯度上升来最大化预期回报。这个梯度被称为政策梯度。

在这种方法中,我们直接修改策略,使得在所收集的经验中导致更多奖励的行为更有可能发生,而在策略的概率分布中导致不好的奖励的行为不太可能发生。

另一类算法叫做行动者-批评家算法,结合了Q学习和策略梯度。

这是我们结束这一部分的地方。在这一部分中,我们讨论了使用RL方法的动机和RL算法的基本结构。

图像处理/计算机视觉上下文中的卷积是一个过程,通过该过程,图像被滤波器“扫描”,以便以某种方式对其进行处理。让我们在细节上稍微技术性一点。

对计算机来说,图像只是一组数字类型(整数或浮点数),这些数字类型被恰当地称为像素。事实上,1920像素乘1080像素(1080p)的高清图像只是一个具有1080行和1920列的数字类型的表格/数组。另一方面,滤波器本质上是相同的,但通常尺寸较小,常见的(3,3)卷积滤波器是3行3列的阵列。

当对图像进行卷积时,对图像的连续片应用滤波器,其中在滤波器的元素和该片中的像素之间发生逐元素乘法,然后累积和作为其自己的新像素返回。例如,当使用(3,3)滤波器执行卷积时,9个像素被聚集以产生单个像素。由于这种聚集过程,一些像素丢失了。

Filterscanningoveranimagetogenerateanewimageviaconvolution.

要理解像素丢失的原因,请记住,如果卷积滤波器在扫描图像时超出界限,则特定的卷积实例将被忽略。举例来说,考虑一个6×6像素的图像被一个3×3的滤波器卷积。如下图所示,前4个卷积落在图像内,为第一行生成4个像素,而第5个和第6个卷积落在边界外,因此被忽略。同样,如果滤波器向下移动1个像素,则对于第二行,同样的模式重复,同时损失2个像素。当该过程完成时,6×6像素图像变成4×4像素图像,因为它在dim0(x)中丢失了2列像素,在dim1(y)中丢失了2行像素。

Convolutioninstancesusinga3x3filter.

同样,如果使用5×5滤波器,则在dim0(x)和dim1(y)中分别丢失4列和4行像素,从而产生2×2像素图像。

Convolutioninstancesusinga5x5filter.

不要相信我的话,试试下面的函数,看看是不是真的如此。随意调整论点。

importnumpyasnpimporttorchimporttorch.nn.functionalasFimportcv2importtorch.nnasnnfromtqdmimporttqdmimportmatplotlib.pyplotaspltdefcheck_convolution(filter=(3,3),image_size=(6,6)):"""Thisfunctioncreatesapseudoimage,performsconvolutionandreturnsthesizeofboththepseudoandconvolvedimage"""#creatingpseudoimageoriginal_image=torch.ones(image_size)#addingchannelastypicaltoimages(1channel=grayscale)original_image=original_image.view(1,6,6)#perfomingconvolutionconv_image=nn.Conv2d(1,1,filter)(original_image)print(f'originalimagesize:{original_image.shape}')print(f'imagesizeafterconvolution:{conv_image.shape}')passcheck_convolution()像素丢失的方式似乎有一种模式。似乎每当使用m×n滤波器时,m-1列像素在dim0中丢失,n-1行像素在dim1中丢失。让我们更数学一点...

图像大小=(x,y)滤波器大小=(m,n)卷积后的图像大小=(x-(m-1),y-(n-1))=(x-m+1,y-n+1)

每当使用大小为(m,n)的滤波器对大小为(x,y)的图像进行卷积时,就会产生大小为(x-m+1,y-n+1)的图像。

虽然这个等式可能看起来有点复杂(没有双关的意思),但它背后的逻辑很容易理解。由于大多数普通滤波器在尺寸上是正方形的(在两个轴上尺寸相同),所有要知道的是一旦使用(3,3)滤波器完成卷积,2行和2列像素丢失(3-1);如果使用(5,5)过滤器,则丢失4行和4列像素(5-1);如果使用(9,9)过滤器,你猜对了,8行和8列像素丢失(9-1)。

丢失2行和2列像素可能看起来没有那么大的影响,特别是在处理大图像时,例如,would图像(3840,2160)在被(3,3)滤波器卷积成(3838,2158)时,看起来不受丢失2行和2列像素的影响,丢失了其总像素的大约0.1%。当涉及多层卷积时,问题就开始出现了,这在当前CNN体系结构中是很典型的。以RESNET128为例,该架构具有大约50(3,3)个卷积层,这将导致大约100行和100列像素的损失,将图像大小减小到(3740,2060),图像总像素的大约7.2%的损失,所有这些都没有考虑下采样操作。

即使是浅架构,丢失像素也会产生巨大的影响。在大小为(28,28)的MNIST数据集中的图像上使用仅应用了4个卷积层的CNN将导致8行和8列像素的损失,将其大小减小到(20,20),损失了其总像素的57.1%,这是相当可观的。

由于卷积运算是从左到右、从上到下进行的,因此最右边和最下边的像素会丢失。因此,可以有把握地说卷积导致边缘像素的丢失,这些像素可能包含手头的计算机视觉任务所必需的特征。

因为我们知道像素在卷积后必然会丢失,所以我们可以通过预先添加像素来抢占先机。例如,如果要使用(3,3)滤波器,我们可以预先向图像添加2行和2列像素,以便在进行卷积时,图像大小与原始图像相同。

让我们再来一点数学...

图像尺寸=(x,y)滤波器尺寸=(m,n)

填充后的图像大小=(x+2,y+2)

使用等式==>(x-m+1,y-n+1)

卷积后的图像大小(3,3)=(x+2-3+1,y+2-3+1)=(x,y)

因为我们处理的是数字数据类型,所以额外像素的值也应该是数字。通常采用的值是像素值零,因此经常使用术语“零填充”。

抢先将像素的行和列添加到图像阵列的问题是,必须在两侧均匀地完成。例如,当添加2行和2列像素时,它们应该被添加为顶部一行、底部一行、左侧一列和右侧一列。

看下面的图像,已经添加了2行和2列来填充左边的6x6数组,同时在右边添加了4行和4列。如前一段所述,附加的行和列沿所有边缘均匀分布。

仔细观察左边的数组,似乎6×6的1数组被一层0包围起来,所以padding=1。另一方面,右边的数组似乎被两层零包围,因此padding=2。

Layersofzerosaddedviapadding.

将所有这些放在一起,可以有把握地说,当一个人在准备(3,3)卷积时想要添加2行和2列像素时,他需要一个单层填充。在相同的叶片中,如果需要添加6行和6列像素以准备(7,7)卷积,则需要3层填充。用更专业的术语来说,

给定大小为(m,n)的滤波器,需要(m-1)/2层填充以在卷积后保持图像大小不变;假设m=n并且m是奇数。

为了演示填充过程,我编写了一些普通代码来复制填充和卷积的过程。

首先,让我们看看下面的填充函数,该函数接受一个图像作为参数,默认填充层为2。当display参数保留为True时,该函数通过显示原始图像和填充图像的大小来生成迷你报告;还会返回两个图像的绘图。

defpad_image(image_path,padding=2,display=True,title=''):"""Thisfunctionperformszeropaddingusingthenumberofpaddinglayerssuppliedasargumentandreturnthepaddedimage."""#readingimageasgrayscaleimage=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)#creatinganarrayofzerospadded=arr=np.zeros((image.shape[0]+padding*2,image.shape[1]+padding*2))#insertingimageintozeroarraypadded[int(padding):-int(padding),int(padding):-int(padding)]=imageifdisplay:print(f'originalimagesize:{image.shape}')print(f'paddedimagesize:{padded.shape}')#displayingresultsfigure,axes=plt.subplots(1,2,sharey=True,dpi=120)plt.suptitle(title)axes[0].imshow(image,cmap='gray')axes[0].set_title('original')axes[1].imshow(padded,cmap='gray')axes[1].set_title('padded')axes[0].axis('off')axes[1].axis('off')plt.show()print('imagearraypreview:')returnpaddedPaddingfunction.

为了测试填充功能,考虑下面的图片大小(375,500)。将此图像通过padding=2的padding函数应该会产生相同的图像,在左右边缘有两列零,在顶部和底部有两行零,从而将图像大小增加到(379,504)。我们来看看是不是这样...

Imageofsize(375,500)

pad_image('image.jpg')输出:原始图像尺寸:(375,500)填充图像尺寸:(379,504)

Noticethethinlineofblackpixelsalongtheedgesofthepaddedimage.

有用!请随意在您可能找到的任何图像上尝试该功能,并根据需要调整参数。下面是复制卷积的普通代码。

对于过滤器,我选择了值为0.01的(5,5)数组。这背后的想法是,在求和以产生单个像素之前,过滤器将像素强度降低99%。简单来说,这个滤镜应该对图像有模糊效果。

filter_1=np.ones((5,5))/100filter_1[[0.01,0.01,0.01,0.01,0.01][0.01,0.01,0.01,0.01,0.01][0.01,0.01,0.01,0.01,0.01][0.01,0.01,0.01,0.01,0.01][0.01,0.01,0.01,0.01,0.01]](5,5)ConvolutionFilter

在没有填充的原始图像上应用过滤器应该产生大小为(371,496)的模糊图像,丢失4行和4列。

convolve('image.jpg',filter=filter_1)Performingconvolutionwithoutpadding

输出:原始图像尺寸:(375,500)卷积图像尺寸:(371,496)

(5,5)convolutionwithoutpadding

但是,当pad设置为true时,图像大小保持不变。

convolve('image.jpg',pad=True,padding=2,filter=filter_1)Convolutionwith2paddinglayers.

输出:原始图像尺寸:(375,500)卷积图像尺寸:(375,500)

(5,5)convolutionwithpadding

让我们重复相同的步骤,但这次使用(9,9)过滤器...

filter_2=np.ones((9,9))/100filter_2filter_2[[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01]])(9,9)filter

如果没有填充,生成的图像会缩小...

convolve('image.jpg',filter=filter_2)输出:原始图像尺寸:(375,500)卷积图像尺寸:(367,492)

(9,9)convolutionwithoutpadding

使用(9,9)过滤器,为了保持图像大小不变,我们需要指定填充层为4(9-1/2),因为我们将向原始图像添加8行和8列。

convolve('image.jpg',pad=True,padding=4,filter=filter_2)输出:原始图像尺寸:(375,500)卷积图像尺寸:(375,500)

(9,9)convolutionwithpadding

为了便于说明,我在上一节中选择使用普通代码来解释这些过程。可以在PyTorch中复制相同的过程,但是要记住,由于PyTorch会随机初始化不是为任何特定目的而设计的过滤器,因此生成的图像很可能很少或没有经过变换。

为了演示这一点,让我们修改前面一节中定义的check_convolution()函数...

defcheck_convolution(image_path,filter=(3,3),padding=0):"""Thisfunctionperformsconvolutiononanimageandreturnsthesizeofboththeoriginalandconvolvedimage"""image=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)image=torch.from_numpy(image).float()#addingchannelastypicaltoimages(1channel=grayscale)image=image.view(1,image.shape[0],image.shape[1])#perfomingconvolutionwithtorch.no_grad():conv_image=nn.Conv2d(1,1,filter,padding=padding)(image)print(f'originalimagesize:{image.shape}')print(f'imagesizeafterconvolution:{conv_image.shape}')passFunctionperformconvolutionusingthedefaultPyTorchconvolutionclass

注意,在函数中我使用了默认的PyTorch2D卷积类,函数的填充参数直接提供给卷积类。现在让我们尝试不同的过滤器,看看产生的图像大小...

check_convolution('image.jpg',filter=(3,3))(3,3)convolutionwithoutpadding

输出:原图尺寸:火炬。Size(1,375,500)卷积后的图像大小:torch。尺寸(1,373,498)

check_convolution('image.jpg',filter=(3,3),padding=1)(3,3)convolutionwithonepaddinglayer.-

输出:原图尺寸:火炬。Size(1,375,500)卷积后的图像大小:torch。尺寸(1,375,500)

check_convolution('image.jpg',filter=(5,5))(5,5)convolutionwithoutpadding-

输出:原图尺寸:火炬。Size(1,375,500)卷积后的图像大小:torch。尺寸(1,371,496)

check_convolution('image.jpg',filter=(5,5),padding=2)(5,5)convolutionwith2layersofpadding-

从上面的例子可以明显看出,当卷积在没有填充的情况下完成时,得到的图像尺寸减小了。但是,当使用正确数量的填充层进行卷积时,生成的图像与原始图像大小相等。

PaperspaceCEODillonErbsharesthecriticalroleATGFellowsplayatPaperspace

ATG奖学金为期10-15周,是带薪的全职职位。该计划旨在吸引希望在Paperspaceengineeringorg的支持和合作下从事机器学习和深度学习研究的研究生和研究生。

我们依靠ATG研究员获得广泛的专业知识。我们与ATG研究员一起设计有意义的研究项目,以探索尖端的机器学习技术和库,并推动机器学习的可能性。

今年,我们希望支持三个研究方向:

Paperspace上次支持ATG奖学金是在2018年和2019年。ATG研究员来自广泛的学术背景。我们接待了来自领先的人工智能和人工智能机构的研究人员,包括NYU大学、佐治亚理工学院和哈佛大学。前两批人继续攻读博士学位,成为领先公司的研究科学家,并建立了风险投资支持的机器学习公司,如T2跑道T3。

PastATGFellowsdiscusstheirtimeatPaperspace

PaperspaceATG项目的一个显著特点是,在Paperspace世界级工程组织的全力支持下,可以自由探索研究课题。

对于一家软件公司来说,我们拥有异常广阔的表面积,因此我们在从低延迟流到分布式系统架构、加速计算和生产级机器学习系统编排的所有领域都拥有主题专业知识。

作为一名ATG研究员,你的工作很有可能会提供给近50万用户。您将能够影响Paperspace产品的未来,同时还能进行有意义的合作研究。

我们邀请您今天就申请!

虽然不像Paperspace原生协议那样“简洁”,但RDP是稳定的、功能齐全的,并且为大多数IT专业人员所熟悉。RDP通过第三方应用和微软自己提供的版本在Windows、Mac和Linux上得到支持。

RDP提供的最重要的好处之一是能够从目前市场上的大多数瘦客户机上访问纸张空间。另一个好处是,RDP是作为安卓和苹果智能手机/平板电脑上的移动应用程序提供的。

RDP的主要特色:

我们在2014年秋季创立了Paperspace,目标很简单:让云计算变得更容易实现。当时,公共云对于托管网站和服务很有用,但对于其他类型的应用程序来说并不是理想的环境。我们认为,有大量新的应用程序也可以从利用公共云的巨大规模和灵活性中受益。我们认为GPU加速和简化用户体验是我们的主要优势。

在2015年完成YCombinator后,我们侵入了几个版本,并决定采用GPU驱动的虚拟机来支持各种新兴的用例。最引人注目的是当时有点小众的观众,这是机器学习中一个名为深度学习的新兴领域。这对我们来说是一个完美的受众,原因有二:我们在公共云中有最大的GPU实例选择,该领域的其他参与者使IaaS对这种新型开发人员来说太复杂了。

在过去的几年里,只有少数几个工程师,我们已经能够达到一些惊人的里程碑。我们拥有超过150,000个活跃用户帐户,每天都依赖于Paperspace的企业数量也在快速增长。我们发布了迄今为止最重要的产品T1,我们与T2一些令人难以置信的机构T3合作,我们建立了一个专注于ML的开发者社区。

我们将继续专注于构建世界上最好的深度学习平台。我们强烈地感觉到,训练和部署模型的过程对于个人开发者和大规模运营的公司来说都过于昂贵和复杂。我们将努力消除瓶颈,简化这项技术的投资过程。

来自整个Paperspace团队:

谢谢!

云中的完整VDI实施

Paperspace在云中提供了一个完整的虚拟桌面解决方案,没有内部VDI带来的任何麻烦。它易于设置、易于管理,并且可以大幅削减It成本。Paperspace使员工能够从世界上任何地方的任何设备安全地访问他们的应用程序和文件。

PaperspaceGradient和AmazonSageMaker是两个最受欢迎的端到端机器学习平台。

端到端机器学习平台是指支持从研究或原型阶段到大规模部署的机器学习模型开发的工具集。

平台意味着存在某种程度的自动化,使得执行机器学习任务变得更加容易。

PaperspaceGradient和AmazonSageMaker使机器学习模型更容易从研究走向生产。

以前,需要将工具和服务组合在一起才能生产一个机器学习模型,而现在,通过您选择的框架和软件包,可以在一个平台上编写、管理和编排模型。

使用Gradient或SageMaker等完全托管服务的优势有很多:

Gradient和SageMaker都具有加快机器学习模型开发周期的功能。

而非使用托管服务开发机器学习模型的缺点还包括其他几个因素:

随着您的机器学习部署的规模和复杂性的增长,ML平台将对您的团队变得更加有用。以下文档介绍了选择ML平台时需要考虑的一些重要因素。

差异解释如下:

轻松的ML意味着编写机器学习代码,而不是排除基础设施工具的故障。

使用AmazonSageMaker,您需要具备丰富的DevOps知识。您需要使用IAM管理用户权限。您将需要能够执行应用程序规模的工具来设置ML管道。

如果你已经拥有丰富的AWS和/或SageMaker专业知识,你会发现Gradient让许多重复性的编排任务变得更加容易。

从哲学上讲,Gradient让您可以访问整个基础架构堆栈。关于整个部署管道的信息浮出水面,例如,从作业运行器到应用层,再到底层计算硬件。

PaperspaceGradient是一种多云解决方案。Gradient背后的动机是为每个机器学习团队提供最好的工具——不管他们住在哪里。

许多AWS应用程序(包括SageMaker中的应用程序)旨在鼓励最终用户采用其他AWS应用程序或托管服务。例如,SageMaker已经有了一个托管服务,用于预建算法,部署到边缘,以及为模型预测添加人工审查。

如果你完全围绕AWS进行组织,你可能知道AWS托管服务可能是一个黑洞——使用单一云ML平台可能会将你推向云的昂贵托管服务。

梯度适合您的生产力。它不会通过架构锁定来限制您,不会推动您采用托管服务,也不会通过固执己见的特定于平台的设计来强行限制您的ML能力。

CI/CDforMachineLearning

许多高功能软件团队在他们的工作流程中使用一些CI/CD版本。优势是多方面的:

机器学习过程受益于同样的原因。使用像PaperspaceGradient这样的CI/CD工具可以提高模型开发和部署周期的效率。

AmazonSageMaker也有用于调整的工具(例如,一键超参数优化),但你会发现这些功能在渐变中与标准功能相同。

众所周知,AWS应用程序很难管理,但在规模上非常强大。许多AWS服务非常复杂,以至于它们有独立的认证程序!

为了大规模部署机器学习模型,AmazonSageMaker需要了解权限(例如IAM)、计算资源的分配(例如EC2),以及对通过AWS属性的数据流的深入了解。

基于Gradient的工具基础设施要简单得多。Gradient具有集成的作业运行器,能够以最经济高效的方式自动调配计算资源。

GradientModelview

以基础设施性能著称的亚马逊SageMaker需要付出更多努力才能获得洞察力——无论是第一次还是每次。Paperspace更加直观,尤其是对于不习惯编排计算负载的数据科学家来说。

Gradient和SageMaker都提供“一键式部署”,但在Gradient中,这包括计算资源的供应(使用无服务器作业运行器),而在SageMaker中,供应仍然是一个工具挑战。

PaperspaceGradient和AmazonSageMaker都有某种形式的按实例计费。

TheGradientJobRunnereliminatestheneedtoprovisionanentiremachineformodeltrainingordeployment.

许多机器学习和深度学习应用需要在边缘部署或与物联网框架集成的训练模型。模型通常为推理而优化。执行修剪或量化。模型会缩小以符合部署标准。

因为Gradient是一个Kubernetes应用程序,所以很容易将训练分布到处理中心,处理中心可以很容易地上下旋转。可以按照适合业务需求的方式设置集群,并且有一个单一平台来管理多云计算工作负载。

相比之下,SageMaker在默认情况下将您限制在特定的数据中心区域,并使其难以分发。

亚马逊SageMaker只在AWS上运行。如果你在AWS上运营业务或者对AWS生态系统非常熟悉,这就不是问题。然而,一个额外的问题出现了,不是因为供应商锁定,而是因为可扩展性。

有了SageMaker,机器学习工作流就受制于AWS生态系统。如果新产品或工具在其他公共云上提供,就没有办法访问它们。

相比之下,Gradient将允许您将任何云上的任何新功能连接到您的机器学习工作流。最终,锁定的缺乏和可扩展性保护了关键的商业知识产权和竞争优势。

随着你的机器学习越来越复杂,你的算法工作也可能越来越复杂。

同样,经典的机器学习技术可能不足以满足您的应用——尤其是如果您的应用涉及音频或视频,或者如果某个特定的经典算法限制了您的结果的准确性。

当需要深度学习时,PaperspaceGradient可以更轻松地将模型部署到GPU。

原因是多方面的:

至关重要的是,Gradient还使得将模型扩展到大规模分布式训练变得异常容易。

一流的数据科学团队通常还包括开发人员和/或基础架构工程师。

渐变让我们更容易忘记工具,回到真正的工作中。

轻松的机器学习意味着编写ML代码,而不是排除基础设施工具的故障。CI/CD平台提供对整个体系的访问——从计算层到管理层——同时抽象出不可扩展的任务。

最终,用于机器学习的CI/CD工作流的成功实施将意味着更好的可靠性、稳定性、确定性和更快的创新步伐。

人工智能模型的规模和复杂性正以令人震惊的速度增长。训练最先进模型所需的计算周期需求通常超过数百亿个参数,这种需求确实非常迅速,使现有芯片的供应和能力紧张。

作为一个旨在支持每一盎司性能都很重要的尖端加速应用的云平台,我们与基础设施的接近让我们对令人兴奋和快速增长的人工智能芯片领域有了独特的见解。特别是,我们已经看到Graphcore成为一个关键角色,通过其智能处理单元或IPU实现其提高人工智能系统效率和性能的愿景。

你可以构建各种奇异的硬件,但是如果你不能实际构建一个软件,将一个人在非常简单的水平上的描述能力转化为硬件,你就不能真正产生一个解决方案。–奈杰尔·图恩,Graphcore首席执行官

今天,我们激动地宣布GraphcoreIPUs集成到渐变笔记本中。这种新的梯度机器类型提供了免费访问IPU-POD16经典机器提供4petaFLOPS的人工智能计算。

Paperspace和Graphcore将继续合作,将IPUs集成到完整的端到端梯度平台中,提供ML管道和推理功能,随着人工智能行业的成熟,这些功能越来越普遍。

快捷方式应该预装在你的iOS设备上,但是如果你删除了它,你可以在这里重新下载。

总而言之:

解决方案:一个与Paperspace的API交互的快捷方式,只需点击几下鼠标即可管理您的机器*

请注意,与应用程序不同,快捷方式不会通过appstore自动更新,因此请务必返回此处下载最新版本。

管理多台机器,每台机器:

Inafewtaps,youcanquicklymanageyourPaperspaceCoreMachinefromyouriOSdevice.Nologinrequired.

按照以下步骤开始:

如果这是您第一次使用快捷方式,您将无法更改此设置,除非您已经运行了至少一个快捷方式。打开快捷方式应用程序,尝试一下。然后,您将能够返回到步骤#1。

设置完成!

PaperspaceiOS快捷方式是在旅途中快速管理您的Paperspace核心机器的便捷工具。

注意:与应用程序不同,快捷方式不会通过appstore自动更新。请务必回来这里下载最新版本。*

在过去的六个月中,对支持远程工作的灵活、安全的解决方案的需求变得至关重要,并且有望持续下去。企业越来越多地转向基于云的解决方案,这种解决方案提供了支持随处远程工作所需的灵活性和简化的管理。

作为NPN云服务提供商计划的合格合作伙伴,Paperspace与企业合作,为现代工作负载部署NVIDIAGPU加速解决方案,包括AI、数据科学、HPC和虚拟工作站,这些工作站采用最新的Quadro技术和计算机图形,用于CAD/CAM、数字内容创建和渲染。

NVIDIAGPUs用于构建下一代应用,如人工智能驱动的癌症检测和自动驾驶汽车,以及技术和创意专业人士使用的图形密集型应用。数据科学家等领域专家从位于原始基础设施之上的软件抽象层中受益匪浅。Paperspace开发软件来协调和扩展基于GPU的应用程序,提供一种无缝方式来利用GPU计算能力,而不需要专业的开发人员和基础架构知识。这种硬件和软件的强大组合对于采用基于GPU的应用程序至关重要。

Paperspace的联合创始人丹尼尔·科布兰(DanielKobran)表示:“Paperspace早期押注于云GPU,这实际上是由GPU技术的领导者英伟达实现的。“我们已经与NVIDIA密切合作了几年,我们很高兴能够继续作为CSP合作伙伴进行合作。”

NVIDIA云计算和战略合作伙伴全球业务开发总监MattMcGrigg表示:“基于云的解决方案提供了企业所需的灵活性和简化的管理,支持在任何地方工作的新常态。“NVIDIAGPUs和Paperspace应用的结合有助于拓展远程工作的可能性。”

今天,我们很高兴终于向您展示了我们在过去几个月里与NVIDIA的同事们一起工作的成果。

Layersofabstractionwithinamachinelearningworkflow.

迄今为止,我们已经帮助数万名研究人员、数据科学家和人工智能工程师在商用云资源的基础上构建确定性和可再现的工作流。我们的工具为数千名设计师、工程师、研究人员等开启了基于云的GPU和尖端分布式处理。今年早些时候,我们启动了第百万台虚拟机!

Gradient的核心任务之一是为机器学习工程师抽象出复杂的计算基础设施。我们的用户喜欢Gradient的可移植性,并要求更多的方法来将ML工作负载从单个工作站扩展到云。

今天,我们很高兴地宣布与NVIDIA正式合作,这是由我们在企业中实现人工智能的共同愿景推动的。

通过这种合作关系,我们已经为NVIDIA的任何DGX系统开发了开箱即用的一流梯度支持。

这是一件大事,原因如下:

作为DGX就绪软件计划的创始成员,我们很高兴能够为英伟达DGX深度学习架构提供全面验证的支持。

英伟达DGX软件产品管理高级总监JohnBarco说:

“我们开发了NVIDIADGX就绪软件程序,以加速企业中的人工智能开发。Paperspace开发了一种独特的CI/CD方法来构建机器学习模型,简化了过程,并利用了英伟达DGX系统的强大功能。”

这是一项鼓舞人心的努力,旨在打造能够让一代建设者受益的技术,我们怀着极大的自豪感承担这项工作。我们很高兴与NVIDIA发展这种合作关系,我们迫不及待地想向您展示我们的合作成果。

今天,世界上数百万家公司正在投资机器学习,以解决几乎每个行业的问题。TensorFlow是GoogleBrain团队创建的一个开源项目,是一个非常受欢迎的ML框架,支持从训练到模型服务的端到端工作流。TensorFlow对分布式培训和其他一些高级功能提供了本机支持,这使得该框架在交付生产应用程序时尤其有价值。

SelectingaTensorFlowcontainerinGradient

这种合作将开启新的可能性,我们非常高兴正式确定这种关系。

无论你是机器学习爱好者、研究人员还是专业人士,设置和管理你的工作环境都可能是一个复杂和令人分心的过程。即使您已经解决了安装和版本兼容性问题,您仍将面临克隆最新模型、与他人共享您的成果以及简单地跟踪您的工作的挑战。更糟糕的是,许多云GPU的价格高得惊人。

GradientCommunity笔记本是公共的、可共享的Jupyter笔记本,运行在免费的云GPU和CPU上。笔记本可以在任何DL或ML框架上运行,预先配置为开箱即用。使用你自己的容器或者从大量模板中选择,包括流行的驱动程序和依赖项,比如CUDA和cuDNN。

免费笔记本可用的实例类型包括:

MLShowcase是一个现成的、完整的ML项目库,可以开箱即用。现在有了免费的CPU和GPU,你可以轻松地选择一个项目,并将其作为自己工作或探索的基础。

您可以根据项目的类型和类别来浏览项目。一旦你找到一个你感兴趣的项目,在Gradient中打开它浏览代码。

通过克隆并在免费的GPU上运行该项目,让它成为你自己的项目。调整参数,交换数据集,或者将其作为自己工作的基础。

开始使用您的第一台免费GPU笔记本电脑非常简单。

您可以运行无限数量的会话,一次最多可运行6个小时。您的笔记本将保持完整版本,并且您可以重新启动您的实例来运行另外6个小时,次数不限。

我们希望你喜欢!

图纸空间的设计以安全性为首要考虑因素。我们知道安全性是所有业务的基石,我们致力于提供世界上最值得信赖的虚拟桌面环境。在当今的环境中,了解您公司的数据是安全的,权限是受管理的,并且与可能的攻击者完全隔离,这是迁移到云的基本要求。Paperspace在各方面都超过了,可以成为您安全IT基础架构的主要支柱。

Paperspace的理念是只有您才能访问您的数据,我们孜孜不倦地设计符合这一目标的解决方案。这发生在应用层、网络层和物理数据中心(对于我们的托管产品)。

进出您的Paperspace虚拟机的所有通信都通过完全加密的通道进行保护。

当您将Paperspace虚拟机放在网络上时,Paperspace很可能是该网络上最安全的机器。

我们的数据中心采用了各种安全机制,包括严格的访问策略以及安全的保险库和保险箱。

Paperspace提供工具来集中管理身份和协作者,具有强身份验证和细粒度权限。

信用卡处理由Stripe处理。Stripe符合PCI标准,所有与其API交互的流量都在安全通道(HTTPS)上运行。存储在他们服务器上的信用卡信息使用AES-256加密。

Paperspace独特的“零本地存储”模式是最安全的虚拟桌面交付系统。由于端点仅渲染从数据中心流出的像素,因此无法提取虚拟环境中的信息(从机器或共享驱动器)。我们的零本地存储政策在所有平台(web、桌面、移动)上实施,不受设备限制。

如今,公司部署大量资源来管理需要持续支持和维护的内部ML管道。Gradient通过提供生产就绪平台即服务来加速他们的人工智能应用程序的开发,从而消除了这一令人头痛的问题。平台能力包括ML团队运行Jupyter笔记本、分布式培训、超参数调优、将模型部署为RESTfulAPIs的能力。Gradient使开发人员能够构建复杂的端到端管道,跨越异构基础设施,所有这些都从一个中心开始。

核心优势包括:

Paperspace很自豪能够与英特尔合作,为他们的新人工智能芯片提供软件抽象层。

“Paperspace有助于激励下一代人工智能开发者;它们还将帮助释放即将推出的英特尔Nervana神经网络处理器的强大功能。这些新芯片将提供突破性的性能,Paperspace将帮助公司快速高效地运营这一令人惊叹的新人工智能硬件。”—英特尔人工智能软件和人工智能产品事业部总经理卡洛斯·莫拉莱斯。

我们经常遇到的一个问题是,我们与Citrix有何不同?有两个主要区别:

(在本文中,我们将使用Citrix作为示例,但这同样适用于所有传统的VDI提供商)。

VDI提供商销售的是软件,而不是机器。由专业IT团队对服务器进行机架安装、虚拟化和管理是当今利用虚拟桌面的先决条件。这是一个极其昂贵和复杂的系统,难以配置和管理,因此对于大多数没有大型It团队/预算的公司来说是力所不及的。随着无处不在的带宽和公共云的出现,VDI最终有可能摆脱企业被迫管理自己的服务器的内部模式。

Paperspace正在将VDI迁移到它所属的云中。所有固有的复杂性都包装在一个简单的层中,使任何人都可以从虚拟桌面中受益。

我们喜欢用Dropbox、box和Drive之前的各种文件同步技术进行类比。当时,这项技术非常复杂,只有拥有复杂it部门的公司才能使用。将它转移到公共云,并将一切都放在直观的界面之后,这使得该技术面向更广泛的受众。

简而言之,VDI仅适用于拥有大量IT预算的财富500强公司。有了Paperspace,每个人都可以使用同样的技术。

通过一系列教程,梯度下降(GD)算法将在Python中从头实现,用于优化人工神经网络(ANN)在反向传播阶段的参数。GD实现将是通用的,可以与任何人工神经网络架构一起工作。教程将遵循一个简单的路径来完全理解如何实现GD。每个教程将涵盖所需的理论,然后在Python中应用。

在本教程中,这是系列的第1部分,我们将通过为一个特定的ANN架构实现GD来启动worm,其中有一个具有1个输入的输入层和一个具有1个输出的输出层。本教程将不使用任何隐藏层。为了简单起见,在开始时不使用偏差。

通用实现GD算法的第一步是实现一个非常简单的架构,如下图所示。只有1个输入和1个输出,根本没有隐藏层。在考虑在向后传递中使用GD算法之前,让我们从向前传递开始,看看如何从输入开始直到计算误差。

计算完SOP后,下一步是将其馈送到输出层神经元中的激活函数。这种函数有助于捕捉输入和输出之间的非线性关系,从而提高网络的精度。在本教程中,将使用sigmoid函数。下图给出了它的公式。

假设本例中的输出范围为0到1,则从sigmoid返回的结果可以被视为预测输出。这个示例是一个回归示例,但是通过将sigmoid返回的分数映射到类标签,可以很容易地将其转换为分类示例。

计算预测输出后,下一步是使用下面定义的平方误差函数测量预测误差。

此时,向前传球完成。基于计算的误差,我们可以返回并计算用于更新当前权重的权重梯度。

在后向过程中,我们通过改变网络权重来了解误差是如何变化的。因此,我们想建立一个方程,其中误差和重量都存在。怎么做呢?

根据上图,误差是用两项计算的,它们是:

不要忘记预测值是作为sigmoid函数的输出计算的。因此,我们可以将sigmoid函数代入误差方程,结果如下所示。但是在这一点上,误差和重量不包括在这个等式中。

这是正确的,但还要记住,sop是作为输入X1与其重量W之间的乘积计算的。因此,我们可以删除sop,并使用其等效物X1*W,如下所示。

此时,我们可以开始计算误差相对于重量的梯度,如下图所示。使用以下公式计算梯度可能会很复杂,尤其是当存在更多输入和权重时。作为一种选择,我们可以使用简化计算的链式法则。

之后,我们可以通过计算sigmoid函数的导数,根据下图计算预测到sop的导数。

最后,我们可以计算sop和重量之间的导数,如下图所示。

经过一系列导数后,我们可以通过将所有导数相乘,将误差与重量联系起来,如下所示。

在理论上理解了这个过程的工作原理之后,我们就可以很容易地应用它了。下面列出的代码经历了前面讨论的步骤。输入X1值为0.1,目标值为0.3。使用numpy.rand()随机初始化权重,返回0到1之间的数字。之后,输入和权重被传播到向前传递。这是通过计算输入和重量之间的乘积,然后调用sigmoid()函数实现的。请记住,sigmoid()函数的输出被视为预测输出。计算出预测输出后,最后一步是使用error()函数计算误差。这样,向前传球就完成了。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defactivation_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx=0.1target=0.3learning_rate=0.001w=numpy.random.rand()print("InitialW:",w)#ForwardPassy=w*xpredicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=activation_sop_deriv(predicted)g3=sop_w_deriv(x)grad=g3*g2*g1print(predicted)w=update_w(w,grad,learning_rate)在反向传递中,使用error_predicted_deriv()函数计算误差对预测输出的导数,结果存储在变量g1中。之后,使用activation_sop_deriv()函数计算预测(激活)输出到sop的导数。结果存储在变量g2中。最后,使用sop_w_deriv()函数计算sop对重量的导数,并将结果存储在变量g3中。

计算完链中的所有导数后,下一步是通过将所有导数g1、g2和g3相乘来计算误差对重量的导数。这将返回权重值可以更新的梯度。使用update_w()函数更新权重。它接受3个参数:

这将返回替换旧权重的更新后的权重。注意,前面的代码没有使用更新的权重重复重新训练网络。我们可以进行几次迭代,其中梯度下降算法可以根据下面修改的代码达到更好的权重值。请注意,您可以更改学习速率和迭代次数,直到网络做出正确的预测。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defactivation_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx=0.1target=0.3learning_rate=0.01w=numpy.random.rand()print("InitialW:",w)forkinrange(10000):#ForwardPassy=w*xpredicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=activation_sop_deriv(predicted)g3=sop_w_deriv(x)grad=g3*g2*g1print(predicted)w=update_w(w,grad,learning_rate)下图显示了网络预测如何通过迭代增强。网络可以在50000次迭代后达到期望的输出。请注意,通过改变学习速率,您可以通过更少的迭代次数达到期望的输出。

当学习率为0.5时,网络仅经过10000次迭代就达到了期望的输出。

下图显示了当学习率为0.5时,网络误差如何随着迭代而变化。

在构建了对于具有1个输入和1个输出的基本架构能够有效工作的GD算法之后,我们可以在下一节中将输入的数量从1增加到2。请注意,理解前面的实现是如何工作的非常重要,因为接下来的部分将高度依赖于它。

到目前为止,我们已经成功实现了GD算法,可以处理1个输入或2个输入。在下一个教程中,前面的实现将被扩展,以允许算法处理更多的输入。使用将在下一个教程中讨论的例子,将推导出允许GD算法处理任意数量的输入的一般规则。

本教程是本系列的第2部分,有两个部分。每一节都讨论了为具有不同数量输入的体系结构构建GD算法。第一种架构,输入神经元数量为2。第二个将包括10个神经元。通过这些例子,我们可以推导出实现GD算法的一些通用规则,该算法可以处理任意数量的输入。

下图给出了W1和W2误差的导数链。有什么区别?区别在于如何计算SOP和重量之间的最后一个导数。W1和W2的前两个导数是相同的。

下面列出的代码给出了计算上述导数的实现。与第1部分的实施相比,有3个主要区别。

第一个是使用numpy.rand()初始化2个权重有2行代码。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defactivation_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx1=0.1x2=0.4target=0.3learning_rate=0.1w1=numpy.random.rand()w2=numpy.random.rand()print("InitialW:",w1,w2)#ForwardPassy=w1*x1+w2*x2predicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=activation_sop_deriv(predicted)g3w1=sop_w_deriv(x1)g3w2=sop_w_deriv(x2)gradw1=g3w1*g2*g1gradw2=g3w2*g2*g1w1=update_w(w1,gradw1,learning_rate)w2=update_w(w2,gradw2,learning_rate)print(predicted)前面的代码只适用于1次迭代。我们可以使用一个循环来进行多次迭代,其中权重可以更新为一个更好的值。这是新的代码。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defactivation_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx1=0.1x2=0.4target=0.3learning_rate=0.1w1=numpy.random.rand()w2=numpy.random.rand()print("InitialW:",w1,w2)forkinrange(80000):#ForwardPassy=w1*x1+w2*x2predicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=activation_sop_deriv(predicted)g3w1=sop_w_deriv(x1)g3w2=sop_w_deriv(x2)gradw1=g3w1*g2*g1gradw2=g3w2*g2*g1w1=update_w(w1,gradw1,learning_rate)w2=update_w(w2,gradw2,learning_rate)print(predicted)下图显示了人工神经网络的预测值如何变化,直到达到所需的输出值0.3。在大约5000次迭代之后,网络能够做出正确的预测。

下图显示了迭代次数对误差的影响。经过5000次迭代后,误差为0.0。

到目前为止,我们已经成功实现了GD算法,可以处理1个输入或2个输入。在下一节中,前面的实现将被扩展,以允许该算法处理10个输入。

具有10个输入和1个输出的网络架构如下所示。有10个输入X1至X10和10个权重W1至W10,每个输入都有其权重。为训练这样一个网络建立GD算法类似于前面的例子,但是只使用10个输入而不是2个。只需重复计算SOP和每个重量之间的导数的代码行。

在编写计算导数的代码之前,最好列出要计算的必要导数,这些导数在下图中进行了总结。很明显,所有重量的前两个导数是固定的,但最后一个导数(重量导数的SOP)是每个重量的变化。

在前面的示例中,只有两个权重,因此有以下内容:

在本例中,10行将替换2行,因此将存在以下内容:

下面给出了用于实现具有10个输入的网络的GD算法的代码。

下图显示了误差和迭代次数之间的关系。大约26,000次迭代后,误差为0.0。

至此,用于优化具有10个输入的网络的GD算法的实现完成。您可能想知道如果有10个以上的输入会怎么样。我们必须为每个输入神经元添加更多的线吗?使用当前的实现,我们必须复制这些行,但这不是唯一的方法。我们可以改进前面的代码,这样就根本不需要修改代码来处理任何数量的输入。

目前实现GD算法所遵循的策略是为每个新输入复制一些行代码。尽管这是一种遗留的方法,但理解每一小步是如何工作的是有帮助的。在本节中,将对前面的实现进行改进,这样当输入数量增加或减少时,我们就不必编辑代码了。我们要做的是检查之前的实现,寻找为每个输入神经元重复的线。之后,这些线将被一条适用于所有输入的单线所取代。在前面的代码中,有6个部分需要改进:

让我们研究每一部分,看看我们能做些什么。

上面代码中用于指定所有输入值的部分如下所示。如果要添加更多的输入,将会写入更多的行。

x1=0.1x2=0.4x3=1.1x4=1.3x5=1.8x6=2.0x7=0.01x8=0.9x9=0.8x10=1.6我们可以使用一种更好的方法,将所有这些行替换为下面给出的一行。NumPy数组保存所有这些输入。使用索引,我们可以返回所有单独的输入。例如,如果要检索第一个输入,那么索引0用于索引数组x。

x=numpy.array([0.1,0.4,1.1,1.3,1.8,2.0,0.01,0.9,0.8,1.6])权重初始化下面给出了前面用于初始化权重的代码部分。如果要初始化更多的权重,将会写入更多的行。

w1=numpy.random.rand()w2=numpy.random.rand()w3=numpy.random.rand()w4=numpy.random.rand()w5=numpy.random.rand()w6=numpy.random.rand()w7=numpy.random.rand()w8=numpy.random.rand()w9=numpy.random.rand()w10=numpy.random.rand()我们可以用下面的行代替所有这些行,而不是添加单独的行来初始化每个权重。这将返回一个有10个值的NumPy数组,每个值对应一个权重。同样,使用索引,我们可以检索单个权重。

w=numpy.random.rand(10)计算SOP先前代码中的SOP计算如下。对于每个输入,我们必须在下面的等式中添加一个新项,用于乘以其权重。

y=w1*x1+w2*x2+w3*x3+w4*x4+w5*x5+w6*x6+w7*x7+w8*x8+w9*x9+w10*x10我们可以使用更好的方法,而不是用这种方法将每个输入乘以其权重。请记住,SOP的计算方法是将每个输入乘以其权重。另外,记住x和w现在都是NumPy数组,并且每个都有10个值,其中数组w中索引i处的权重对应于数组x处索引i处的输入。我们需要的是将每个输入乘以其权重,并返回这些乘积的总和。好消息是NumPy支持数组逐值相乘。因此,写入w*x将返回一个新数组,其中的10值表示每个输入的权重乘积。我们可以将所有这些产品相加,并使用下面给出的行返回SOP。

y=numpy.sum(w*x)计算重量衍生产品的SOP需要编辑的下一部分代码是负责计算重量导数的SOP的部分。下面给出。

g3w1=sop_w_deriv(x1)g3w2=sop_w_deriv(x2)g3w3=sop_w_deriv(x3)g3w4=sop_w_deriv(x4)g3w5=sop_w_deriv(x5)g3w6=sop_w_deriv(x6)g3w7=sop_w_deriv(x7)g3w8=sop_w_deriv(x8)g3w9=sop_w_deriv(x9)g3w10=sop_w_deriv(x10)不用调用sop_w_deriv()函数来计算每个权重的导数,我们可以简单地将数组x传递给这个函数,如下所示。当这个函数接收一个NumPy数组时,它独立地处理该数组中的每个值,并返回一个新的NumPy数组和结果。

g3=sop_w_deriv(x)计算权重的梯度下面给出了负责计算权重梯度的代码部分。

gradw1=g3w1*g2*g1gradw2=g3w2*g2*g1gradw3=g3w3*g2*g1gradw4=g3w4*g2*g1gradw5=g3w5*g2*g1gradw6=g3w6*g2*g1gradw7=g3w7*g2*g1gradw8=g3w8*g2*g1gradw9=g3w9*g2*g1gradw10=g3w10*g2*g1我们可以简单地使用下面的行,而不是添加一个新的行来乘以链中每个权重的所有导数。记住g1和g2都是保存单个值的数组,但是g3是保存10个值的Numpy数组。这可以看作是一个数组乘以一个标量值。

grad=g3*g2*g1更新权重下面列出了要编辑的最终代码部分,它负责更新权重。

w1=update_w(w1,gradw1,learning_rate)w2=update_w(w2,gradw2,learning_rate)w3=update_w(w3,gradw3,learning_rate)w4=update_w(w4,gradw4,learning_rate)w5=update_w(w5,gradw5,learning_rate)w6=update_w(w6,gradw6,learning_rate)w7=update_w(w7,gradw7,learning_rate)w8=update_w(w8,gradw8,learning_rate)w9=update_w(w9,gradw9,learning_rate)w10=update_w(w10,gradw10,learning_rate)不用为每个权重调用update_w()函数,我们可以简单地将上一步计算的grad数组和权重数组w一起传递给这个函数,如下所示。在函数内部,将为每个权重及其梯度调用权重更新等式。它将返回一个新的10值数组,代表可以在下一次迭代中使用的新权重。

w=update_w(w,grad,learning_rate)完成所有编辑后,最终优化的代码如下所示。它显示了前面的代码,但是作为一种映射每个部分及其编辑的方式进行了注释。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defsigmoid_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx=numpy.array([0.1,0.4,1.1,1.3,1.8,2.0,0.01,0.9,0.8,1.6])target=numpy.array([0.2])learning_rate=0.1w=numpy.random.rand(10)print("InitialW:",w)forkinrange(1000000000):#ForwardPassy=numpy.sum(w*x)predicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=sigmoid_sop_deriv(y)g3=sop_w_deriv(x)grad=g3*g2*g1w=update_w(w,grad,learning_rate)print(predicted)假设我们要创建一个有5个输入的网络,我们可以简单地做两个改变:

下面给出了适用于5个输入的代码。

importnumpydefsigmoid(sop):return1.0/(1+numpy.exp(-1*sop))deferror(predicted,target):returnnumpy.power(predicted-target,2)deferror_predicted_deriv(predicted,target):return2*(predicted-target)defsigmoid_sop_deriv(sop):returnsigmoid(sop)*(1.0-sigmoid(sop))defsop_w_deriv(x):returnxdefupdate_w(w,grad,learning_rate):returnw-learning_rate*gradx=numpy.array([0.1,0.4,1.1,1.3,1.8])target=numpy.array([0.2])learning_rate=0.1w=numpy.random.rand(5)print("InitialW:",w)forkinrange(1000000000):#ForwardPassy=numpy.sum(w*x)predicted=sigmoid(y)err=error(predicted,target)#BackwardPassg1=error_predicted_deriv(predicted,target)g2=sigmoid_sop_deriv(y)g3=sop_w_deriv(x)grad=g3*g2*g1w=update_w(w,grad,learning_rate)print(predicted)结论在这一点上,我们成功地实现了GD算法,用于处理具有输入层和输出层的ANN,其中输入层可以包括任意数量的输入。在下一个教程中,这个实现将被扩展为在ANN中添加一个单独的隐藏层,并使用GD算法进行优化。

在Python中实现通用梯度下降(GD)算法以优化反向传播阶段的人工神经网络(ANN)参数的系列教程中,再次向您问好。GD实现将是通用的,可以与任何人工神经网络架构一起工作。

在第2部分中,实现了GD算法,以便它可以处理任意数量的输入神经元。在第3部分中,这是本系列的第三篇教程,第2部分的实现将被扩展,以允许GD算法处理具有2个神经元的单个隐藏层。本教程有两个部分。在第一部分,人工神经网络将有3个输入,1个具有3个神经元的隐藏层,和一个具有1个神经元的输出层。在第二部分中,输入数量将从3个增加到10个。

本节扩展了第2部分中GD算法的实现,使其能够处理一个包含两个神经元的隐藏层。第2部分使用了10个输入,但为了简单起见,本部分将只使用3个输入。下图给出了具有3个输入、1个具有2个神经元的隐藏层和1个输出神经元的ANN图。

现在,3个输入中的每个输入都连接到2个隐藏的神经元。对于每个连接,都有不同的权重。输入层和隐藏层之间的权重被标记为Wzy,其中z是指输入层神经元索引,y是指隐藏神经元的索引。

第一个输入X1和第一个隐神经元之间连接的权重是W11。同样,权重W12用于X1和第二个隐藏神经元之间的连接。关于X2,权重W21和W22分别用于第一和第二隐藏神经元的连接。同样,X3有两个权重W31和W32。

除了输入层和隐藏层之间的权重之外,还有2个权重将2个隐藏神经元连接到输出神经元,它们是W41和W42。

如何让GD算法与所有这些参数一起工作?在写出从误差开始直到达到每个单独重量的导数链之后,答案会简单得多。通常,在考虑GD算法更新权重的向后传递之前,我们必须从向前传递开始。

在前向传递中,隐藏层中的神经元除了接受它们的权重之外,还接受来自输入层的输入。然后,计算输入与其权重之间的乘积之和(SOP)。关于第一个隐藏神经元,它分别接受3个输入X1、X2、X3以及它们的权重W11、W21和W31。该神经元的SOP通过对每个输入与其权重之间的乘积求和来计算,因此结果是:

SOP1=X1*W11+X2*W21+X3*W31第一个隐藏神经元的SOP在图中标为SOP1以供参考。对于第二个隐藏神经元,其SOP标记为SOP2,如下所示:

SOP2=X1*W12+X2*W22+X3*W32在计算了所有隐藏神经元的SOP之后,接下来是将这样的SOP馈送到激活函数。此系列中使用的函数是sigmoid函数,其计算方法如下图中的等式所示。

通过将SOP1输入到sigmoid函数,结果是由下式计算的Activ1:

由下式计算出的SOP2为Activ2:

请记住,在前向传递中,一层的输出被视为下一层的输入。隐藏层的输出Activ1和Activ2被视为输出层的输入。重复该过程以计算输出层神经元中的SOP。输出神经元的每个输入都有一个权重。对于第一个输入Activ1,其权重为W41。第二输入Activ2的重量为W42。输出神经元的SOP标记为SOP3,计算如下:

SOP3=Activ1*W41+Activ2*W42SOP3被馈送到sigmoid函数以返回Activ3,如下式所示:

在本教程中,激活函数的输出被视为网络的预测输出。网络做出预测后,下一步是使用下面给出的平方误差函数计算误差。

此时,向前传球完成,我们准备好通过向后传球。

在反向传递中,目标是计算更新网络中每个权重的梯度。因为我们从正向传递中结束的地方开始,所以首先计算最后一层的梯度,然后移动直到到达输入层。让我们开始计算隐藏层和输出层之间的权重梯度。

因为没有包含误差和权重(W41和W42)的显式方程,所以最好使用链式法则。计算这些权重的梯度所需的导数链是什么?

从第一个权重开始,我们需要找到误差对W41的导数。误差方程有两项,如下所示:

在这两项中,哪一项将误差与重量W41联系起来?确定它是预测的,因为它是使用sigmoid函数计算的,该函数接受包含W41的SOP3。因此,要计算的一阶导数是预测输出导数的误差,其计算如下式所示。

之后,接下来是通过用SOP3代入sigmoid函数的导数来计算预测的到SOP3的导数,如下式所示。

接下来是计算SOP3对W41的导数。还记得包含SOP3和W41的等式吗?下面重复一遍。

SOP3=Activ1*W41+Activ2*W42SOP3对W41的导数由下式给出。

通过计算链中从误差到W41的所有导数,我们可以通过将所有这些导数相乘来计算误差到W41的导数,如下式所示。

类似于计算误差对W41的导数,我们可以很容易地计算出误差对W42的导数。与上一个等式不同的唯一一项是最后一项。现在,我们不是计算SOP3至W41的导数,而是计算SOP3至W42的导数,如下式所示。

最后,根据下式计算W42导数的误差。

此时,我们成功地计算了隐藏层和输出层之间所有权重的梯度。接下来是计算输入层和隐藏层之间的权重梯度。这两层之间的误差和权重之间的导数链是什么?当然,前两个导数是前一个链中使用的前两个导数,如下所示:

我们需要计算SOP3对Activ1和Activ2的导数,而不是计算SOP3对W41和W4s的导数。SOP3到Activ1导数有助于计算连接到第一个隐藏神经元的权重梯度,即W11、W21和W31。SOP3到Activ2的导数有助于计算连接到第二个隐藏神经元的权重的梯度,即W12、W22和W32。

SOP3=Activ1*W41+Activ2*W42SOP3对Activ1的导数计算如下式所示:

类似地,SOP3对Activ2的导数计算如下式所示:

之后,我们可以计算链中的下一个导数,即Activ1对SOP1的导数,SOP1是通过在sigmoid函数的导数方程中代入soP1计算的,如下所示。这将用于更新权重W11、W21和W31。

类似地,Activ2至SOP2的导数计算如下。这将用于更新权重W12、W22和W32。

SOP1=X1*W11+X2*W21+X3*W31SOP1对所有这3个权重的导数在下面的等式中给出。

SOP2=X1*W12+X2*W22+X3*W32下图给出了SOP2至W12、W22和W32的导数。

在计算了从误差到输入层和隐藏层之间的所有权重的链中的所有导数之后,接下来是将它们相乘以计算梯度,通过该梯度来更新这些权重。

对于连接到第一个隐藏神经元(W11、W21和W31)的权重,将使用下面的链来计算它们的梯度。注意,所有这些链共享所有导数,除非最后一个导数。

对于连接到第二个隐藏神经元W12、W22和W32的权重,将使用下面的链来计算它们的梯度。注意,所有这些链共享所有导数,除非最后一个导数。

此时,我们已经成功准备好了计算整个网络中所有权重梯度的链。我们可以在下图中总结所有这些链。

理解了为当前网络实现GD算法背后的理论之后,接下来是开始用Python实现这样的算法。请注意,该实现高度依赖于本系列前面部分中开发的实现。

下面列出了实现具有3个输入、1个具有2个神经元的隐藏层和1个输出神经元的ANN并使用GD算法对其进行优化的完整代码。将讨论该代码的各个部分。

x=numpy.array([0.1,0.4,4.1])target=numpy.array([0.2])之后,根据这些线准备网络权重。请注意,w1_3是一个数组,包含将3个输入连接到第一个隐藏神经元的3个权重。w2_3是一个包含3个权重的数组,将3个输入连接到第二个隐藏神经元。最后,w3_2是一个具有2个权重的数组,用于连接隐含层神经元和输出神经元。

w1_3=numpy.random.rand(3)w2_3=numpy.random.rand(3)w3_2=numpy.random.rand(2)准备好输入和权重后,接下来是根据下面的代码进行正向传递。它首先计算两个隐藏神经元的乘积之和,然后将它们提供给sigmoid函数。sigmoid函数的2个输出乘以连接到输出神经元的2个权重,以返回sop3。这也被用作sigmoid函数的输入,以返回预测输出。最后计算误差。

#ForwardPass#HiddenLayerCalculationssop1=numpy.sum(w1_3*x)sop2=numpy.sum(w2_3*x)sig1=sigmoid(sop1)sig2=sigmoid(sop2)#OutputLayerCalculationssop3=numpy.sum(w3_2*numpy.array([sig1,sig2]))predicted=sigmoid(sop3)err=error(predicted,target)向前传球完成后,接下来是向后传球。下面给出了负责更新隐藏层和输出层之间的权重的代码部分。预测输出导数的误差被计算并保存在变量g1中。g2保存对SOP3导数的预测输出。最后,计算SOP3到W41和W42的导数,并保存在变量g3中。在计算W41和W41的梯度所需的所有导数后,梯度被计算并保存在grad_hidden_output变量中。最后,通过传递旧的权重、梯度和学习率,使用update_w()函数更新这些权重。

#BackwardPassg1=error_predicted_deriv(predicted,target)###Workingwithweightsbetweenhiddenandoutputlayerg2=sigmoid_sop_deriv(sop3)g3=numpy.zeros(w3_2.shape[0])g3[0]=sop_w_deriv(sig1)g3[1]=sop_w_deriv(sig2)grad_hidden_output=g3*g2*g1w3_2=update_w(w3_2,grad_hidden_output,learning_rate)更新隐藏层和输出层之间的权重后,接下来是处理输入层和隐藏层之间的权重。下面是更新连接到第一个隐藏神经元的权重所需的代码。g3代表SOP3对Activ1的导数。因为这种导数是使用隐藏层和输出层之间的旧的权重值而不是更新的权重值来计算的,所以旧的权重被保存到w3_2_old变量中,以便在该步骤中使用。g4代表Activ1到SOP1的导数。最后,g5代表SOP1对权重(W11、W21和W31)的导数。

当计算这3个权重的梯度时,g3、g4和g5彼此相乘。在更新隐藏层和输出层之间的权重时,它们还会乘以g2和g1。基于计算的梯度,将3个输入连接到第一个隐藏神经元的权重被更新。

###Workingwithweightsbetweeninputandhiddenlayer#FirstHiddenNeurong3=sop_w_deriv(w3_2_old[0])g4=sigmoid_sop_deriv(sop1)g5=sop_w_deriv(x)grad_hidden1_input=g5*g4*g3*g2*g1w1_3=update_w(w1_3,grad_hidden1_input,learning_rate)类似于处理连接到第一个隐藏神经元的3个权重,连接到第二个隐藏神经元的其他3个权重根据下面的代码进行更新。

#SecondHiddenNeurong3=sop_w_deriv(w3_2_old[1])g4=sigmoid_sop_deriv(sop2)g5=sop_w_deriv(x)grad_hidden2_input=g5*g4*g3*g2*g1w2_3=update_w(w2_3,grad_hidden2_input,learning_rate)在代码结束时,w3_2_old变量被设置为等于w3_2。

w3_2_old=w3_2到了这一步,我们示例中实现GD算法的全部代码就完成了。剩下的编辑是使用一个循环来进行多次迭代,以更新权重,从而做出更好的预测。下面是更新后的代码。

下图显示了迭代过程中误差的变化。

之前的实现使用只有3个输入的输入层。如果使用更多的输入会怎样?是否需要对代码做大量修改?答案是否定的,因为有两处小改动:

下面列出了使用10个输入的实现。除了保存10个值的输入数组x之外,代码中的所有内容都与上一节中的内容相同。同样,使用numpy.rand()函数返回10个权重。这就是你需要做的一切。

在本教程中,我们将梯度下降的实现扩展到具有任意数量神经元的单个隐藏层。

第4部分分为两节。首先,我们将扩展第3部分的实现,允许在一个隐藏层中有5个神经元,而不是只有2个。第二部分将解决使梯度下降(GD)算法神经元不可知的问题,因为任意数量的隐藏神经元可以被包括在单个隐藏层中。

我们将从扩展前面的实现开始,允许在隐藏层中有5个神经元。下图示意性地显示了这一点。扩展算法的一个简单方法就是重复我们已经写好的几行代码,现在针对所有5个神经元。

在查看向后传递之前,值得回忆的是,在向前传递中使用了sigmoid激活函数(定义如下)。注意SOP代表积之和。

使用标准平方误差函数计算误差。

在反向传递中,用于更新隐藏层和输出层之间的权重的梯度被简单地计算,如第3部分中所讨论的,没有任何改变。一阶导数是下面给出的预测输出导数的误差。

二阶导数是预测输出对SOP6的导数。

第三个也是最后一个导数是隐藏层和输出层之间权重的SOP6。因为有5个权重将5个隐藏神经元连接到输出神经元,所以将有5个导数,每个权重一个。记住SOP6是根据下面的等式计算的:

SOP6=Activ1*W41+Activ2*W42+Activ3*W43+Activ4*W44+Activ5*W45比如SOP6对W41的导数等于Activ1,SOP6对W42的导数等于Activ2等等。

为了计算这5个权重的梯度,将前面3个导数的链相乘。所有梯度都是根据下图中的等式计算的。所有这些梯度共享链中的前两个导数。

计算隐藏层和输出层之间的权重梯度后,接下来是计算输入层和隐藏层之间的权重梯度。

用于计算此类梯度的导数链将从之前计算的前两个导数开始,它们是:

SOP6=Activ1*W41+Activ2*W42+Activ3*W43+Activ4*W44+Activ5*W45链中的下一个导数是sigmoid函数对隐藏层中的SOP的导数。例如,Activ1对SOP1的导数根据下式计算。要计算Activ2对SOP2的导数,只需将SOP1替换为SOP2。这适用于所有其他衍生品。

链中的最后一个导数是计算每个隐藏神经元的SOP相对于与其相连的权重的导数。为简单起见,下图显示了ANN体系结构,除了与第一个隐藏神经元的连接之外,输入层和隐藏层之间的所有连接都被删除。

SOP1=X1*W11+X2*W21+X3*W31如果将输入神经元连接到第二个隐藏神经元的权重是W12、W22和W32,则SOP2计算如下。因此,SOP2至W12的导数是X1,SOP2至W22的导数是X2,依此类推。对于所有其他隐藏的神经元,该过程继续。

SOP2=X1*W12+X2*W22+X3*W32你可能会注意到,任何SOP对其3个权重的导数的结果将是X1、X2和X3。

在计算了从误差到输入层权重的链中的所有导数之后,我们可以计算梯度。例如,连接到第一个隐藏神经元的3个权重的3个梯度是根据下面列出的等式计算的。注意,除了最后一个导数,所有的链都有相同的导数。

对于第二个隐藏神经元,每个Activ1由Activ2代替,每个SOP1由SOP2代替。这对于处理其他隐藏神经元也是有效的。

此时,我们成功地准备了用于计算网络中所有权重的梯度的所有导数链。下一步是用Python实现它。

下面列出了用于实现GD算法的Python脚本,该算法用于优化具有3个输入和5个神经元的隐藏层的ANN。我们将讨论这段代码的每一部分。

x=numpy.array([0.1,0.4,4.1])target=numpy.array([0.2])下一步是准备网络权重,如下所示。每个隐藏神经元的权重在一个单独的变量中创建。例如,第一个隐藏神经元的权重存储在w1_3变量中。变量w6_5保存将5个隐藏神经元连接到输出神经元的5个权重。

w1_3=numpy.random.rand(3)w2_3=numpy.random.rand(3)w3_3=numpy.random.rand(3)w4_3=numpy.random.rand(3)w5_3=numpy.random.rand(3)w6_5=numpy.random.rand(5)变量w6_5_old保存w6_5变量中的权重,作为计算SOP6到Activ1-Activ5导数时使用的备份。

w6_5_old=w6_5准备好输入、输出和权重后,下一步是开始向前传递。第一个任务是计算每个隐藏神经元的SOP,如下所示。这是通过将3个输入乘以3个权重来实现的。

#ForwardPass#HiddenLayerCalculationssop1=numpy.sum(w1_3*x)sop2=numpy.sum(w2_3*x)sop3=numpy.sum(w3_3*x)sop4=numpy.sum(w4_3*x)sop5=numpy.sum(w5_3*x)之后,sigmoid函数应用于所有这些乘积的和。

sig1=sigmoid(sop1)sig2=sigmoid(sop2)sig3=sigmoid(sop3)sig4=sigmoid(sop4)sig5=sigmoid(sop5)sigmoid函数的输出被视为输出神经元的输入。这种神经元的SOP使用下面的线计算。

#OutputLayerCalculationssop_output=numpy.sum(w6_5*numpy.array([sig1,sig2,sig3,sig4,sig5]))输出神经元的SOP被馈送到sigmoid函数,以返回预测输出。计算出预测输出后,接下来使用error()函数计算误差。误差计算是正向传递的最后一步。接下来是开始向后传球。

predicted=sigmoid(sop_output)err=error(predicted,target)在后向传递中,根据下面的线,计算的一阶导数是预测输出导数的误差。结果保存在变量g1中以备后用。

g1=error_predicted_deriv(predicted,target)下一个导数是根据下一行对SOP6导数的预测输出。结果保存在变量g2中以备后用。

g2=sigmoid_sop_deriv(sop_output)为了计算隐藏层和输出层之间的权重梯度,剩余的导数是SOP6到W41-W45的导数。它们在变量g3中根据下一行进行计算。

g3=numpy.zeros(w6_5.shape[0])g3[0]=sop_w_deriv(sig1)g3[1]=sop_w_deriv(sig2)g3[2]=sop_w_deriv(sig3)g3[3]=sop_w_deriv(sig4)g3[4]=sop_w_deriv(sig5)在准备了计算权重W41至W45的梯度所需的所有导数之后,接下来是使用下一条线来计算梯度。

grad_hidden_output=g3*g2*g1之后,可以使用下面给出的update_w()函数来更新这5个权重。它接受旧的权重、梯度和学习率,并返回新的权重。

w6_5=update_w(w6_5,grad_hidden_output,learning_rate)更新隐藏层和输出层之间的权重后,接下来是计算输入层和隐藏层之间的权重梯度。通过我们的讨论,我们将一次研究一个隐藏的神经元。

对于第一个隐藏神经元,为其权重准备梯度所需的计算如下所示。在变量g3中,计算SOP6到Activ1的导数。在g4中,计算Activ1到SOP1的导数。最后的导数是保存在g5变量中的SOP1至W11-W31导数。注意g5有3个导数,每个重量一个,而g4和g3只有一个导数。

在计算链中的所有导数之后,接下来是通过乘以变量g1至g5来计算梯度,用于更新将3个输入神经元连接到第一个隐藏神经元的3个权重。结果保存在grad_hidden1_input变量中。最后,使用update_w()函数更新3个权重。

#FirstHiddenNeurong3=sop_w_deriv(w6_5_old[0])g4=sigmoid_sop_deriv(sop1)g5=sop_w_deriv(x)grad_hidden1_input=g5*g4*g3*g2*g1w1_3=update_w(w1_3,grad_hidden1_input,learning_rate)对其他隐藏神经元的处理与上面的代码非常相似。从上面的5行来看,只需对前2行进行修改。对于第二个隐藏神经元的工作,使用索引1为w6_5_old计算g3。计算g4时,使用sop2而不是sop1。下面列出了负责更新第二个隐藏神经元的权重的代码部分。

#SecondHiddenNeurong3=sop_w_deriv(w6_5_old[1])g4=sigmoid_sop_deriv(sop2)g5=sop_w_deriv(x)grad_hidden2_input=g5*g4*g3*g2*g1w2_3=update_w(w2_3,grad_hidden2_input,learning_rate)对于第三个隐藏神经元,使用索引2来计算g3的w6_5_old。为了计算g4,使用sop3。其代码如下。

#ThirdHiddenNeurong3=sop_w_deriv(w6_5_old[2])g4=sigmoid_sop_deriv(sop3)g5=sop_w_deriv(x)grad_hidden3_input=g5*g4*g3*g2*g1w3_3=update_w(w3_3,grad_hidden3_input,learning_rate)对于第四个隐藏神经元,使用索引3来计算g3的w6_5_old。为了计算g4,使用sop4。其代码如下。

#FourthHiddenNeurong3=sop_w_deriv(w6_5_old[3])g4=sigmoid_sop_deriv(sop4)g5=sop_w_deriv(x)grad_hidden4_input=g5*g4*g3*g2*g1w4_3=update_w(w4_3,grad_hidden4_input,learning_rate)对于第五个也是最后一个隐藏神经元,使用索引4为w6_5_old计算g3。为了计算g4,使用sop5。其代码如下。

#FifthHiddenNeurong3=sop_w_deriv(w6_5_old[4])g4=sigmoid_sop_deriv(sop5)g5=sop_w_deriv(x)grad_hidden5_input=g5*g4*g3*g2*g1w5_3=update_w(w5_3,grad_hidden5_input,learning_rate)此时,计算所有网络权重的梯度,并更新权重。只要记得在最后将w6_5_old变量设置为新的w6_5即可。

w6_5_old=w6_5在为正在使用的架构实现了GD算法之后,我们可以允许使用一个循环在多次迭代中应用该算法。这在下面列出的代码中实现。

下图给出了误差和迭代之间的关系。

之前的GD算法实现不仅适用于单个隐藏层,还适用于该层中特定数量的神经元。为了推广该算法,我们可以继续编辑之前的实现,以便它可以在单个隐藏层内对任意数量的神经元起作用。随后,可以添加更多的隐藏层,并且该算法将不依赖于固定数量的隐藏层。

根据前面的实现,每个神经元的计算几乎相同。使用了相同的代码,但只是输入了适当的输入。使用循环,我们可以编写一次这样的代码,并在每次迭代中使用不同的输入。新代码如下所示。

network_architecture=numpy.array([x.shape[0],5,1])使用遍历架构中指定的每一层的for循环,可以在名为w的单个数组中创建网络的权重。代码如下所示。与使用单个变量保存每个单独图层的权重相比,这是一种更好的构建网络权重的方法。

#Initializingtheweightsoftheentirenetworkw=[]w_temp=[]forlayer_counterinnumpy.arange(network_architecture.shape[0]-1):forneuron_nounterinnumpy.arange(network_architecture[layer_counter+1]):w_temp.append(numpy.random.rand(network_architecture[layer_counter]))w.append(numpy.array(w_temp))w_temp=[]w=numpy.array(w)对于这个例子,数组w的形状是(2),这意味着其中只有2个元素。第一个元素的形状是(5,3),它保存有3个输入的输入层和有5个神经元的隐藏层之间的权重。数组w中第二个元素的形状是(1,5),它保存了具有5个神经元的隐藏层和只有一个神经元的输出层之间的权重。

以这种方式准备重量有利于向前和向后传球。所有乘积的总和都是使用一条直线计算的,如下所示。请注意,w[0]表示输入层和隐藏层之间的权重。

sop_hidden=numpy.matmul(w[0],x)类似地,sigmoid函数被调用一次,以应用于所有乘积和,如下所示。

sig_hidden=sigmoid(sop_hidden)隐藏层和输出层之间的乘积之和是根据这条单线计算的。请注意,w[1]返回这两个层之间的权重。

sop_output=numpy.sum(w[1][0]*sig_hidden)通常,预测输出和误差计算如下。

predicted=sigmoid(sop_output)err=error(predicted,target)这是向前传球的终点。在后向传递中,因为在输出层中只有一个神经元,所以它的权重将以先前使用的相同方式更新。

#BackwardPassg1=error_predicted_deriv(predicted,target)###Workingwithweightsbetweenhiddenandoutputlayerg2=sigmoid_sop_deriv(sop_output)g3=sop_w_deriv(sig_hidden)grad_hidden_output=g3*g2*g1w[1][0]=update_w(w[1][0],grad_hidden_output,learning_rate)当更新输入层和隐藏层之间的权重时,使用下面给出的循环的。它遍历隐藏层中的每个神经元,并将适当的输入用于函数sop_w_deriv()和sigmoid_sop_deriv()。

###Workingwithweightsbetweeninputandhiddenlayerg5=sop_w_deriv(x)forneuron_idxinnumpy.arange(w[0].shape[0]):g3=sop_w_deriv(w_old[1][0][neuron_idx])g4=sigmoid_sop_deriv(sop_hidden[neuron_idx])grad_hidden_input=g5*g4*g3*g2*g1w[0][neuron_idx]=update_w(w[0][neuron_idx],grad_hidden_input,learning_rate)通过这样做,我们成功地最小化了GD算法代码,并将其推广到单个隐藏层中的任意数量的隐藏神经元。在用不同数量的隐藏神经元测试代码之前,让我们确保它像前面的实现一样正确工作。下图显示了预测的输出如何随着迭代而变化。它与先前获得的结果相同,这意味着实现是正确的。

下图显示了误差是如何随着迭代而变化的,这也与上一个实现中的情况相同。

在确保代码正确工作后,下一步是使用不同数量的隐藏神经元。唯一需要的改变是在network_architecture变量中指定所需的隐藏神经元数量。下一个代码使用了8个隐藏的神经元。

下图给出了误差和迭代次数之间的关系。

到本系列的这一部分结束时,我们已经成功地实现了GD算法,可以在单个隐藏层中处理可变数量的隐藏神经元。它也可以接受可变数量的输入。在下一部分中,实现将被扩展,以允许GD算法处理不止一个隐藏层。

在测绘中使用摄影来测量物体之间的距离的概念被称为摄影测量。这是通过从不同角度和距离拍摄的一系列照片中观察同一物体来实现的。通过对一些物体上的这些距离进行三角测量,有可能创建物体、土地或空间的三维表示,然后我们可以利用3D模型进行大量不同的任务,如进行测量。这个过程在许多不同的用例中被大量利用,如土地测量、医学或体育。例如,对一块土地进行航空摄影测量可以极大地提高工程师、建筑师和施工团队对地块工作做出明智决策的能力。

让我们看看如何使用强大的GPU来处理摄影测量任务所需的大量数据。

Pix4D可以说是帮助大规模土地勘测和无人机测绘的首要服务,但这项技术的实际功能用例是什么?

Pix4Dmapper是Pix4D的较小规模无人机测绘软件。使用Pix4Dmapper,可以用任何相机或无人机捕捉RGB、热或多光谱图像,在应用程序中将地面或空中图像数字化为可用的3D和数字表示,然后使用这些表示创建感兴趣数据的精确测量和检查。然后,这些测量值可用于许多任务,如指导施工、土地测量等。然而,Pix4Dmapper在快速处理多少数据方面受到限制。

Pix4Dmatic是Pix4D处理大数据的解决方案。它支持下一代陆地、走廊和大比例制图的摄影测量。Pix4Dmatic在其他方面的功能与Pix4Dmapper非常相似,但在摄影测量的处理步骤上有显著的加速。因为我们可以访问大型示例数据集和核心GPU,所以我们将在本教程中使用Pix4Dmatic。

在开始之前,在Paperspace上加载一个Windows核心机器。我建议您选择Ampere机器类型(A4000、A5000、A6000和A100)中的一种,如果它们可用的话,因为运行Pix4D的计算成本非常高。建议至少有32GB(20MP时有2,000-5,000个图像)到64GB(20mp时有5,000-10,000个图像)的CPURAM,以及至少100GB-200GB的可用内存空间(20mp时有2,000-5,000个图像)。您可以在“机器类型”部分的“核心机器创建”页面中更改可用的存储量。

一旦你完成了这些步骤,点击底部的“创建”来创建核心机器。然后,单击GUI中的“Launch”按钮访问您的Windows桌面。

现在,我们在我们的核心机器里。我们需要做的第一件事是用Pix4D创建一个帐户并下载软件。Pix4D是一个商业产品,因此您需要注册一个付费帐户来使用该软件套件。但是,如果您愿意,您可以获得15天的免费试用期来演示他们的产品。

现在我们已经有了数据和软件设置,我们可以开始我们的摄影测量任务。加载应用程序,并单击File>New在应用程序中启动一个新项目。您可以将路径保留为默认文件夹,但最好重命名项目以代表作品的示例性质。

现在,在您的新项目中,我们可以通过简单地将包含数据集zip文件内容的文件拖放到窗口中来加载我们的数据。这将自动用按地理位置组织的100幅图像填充地图特征空间。

对于下一步,请确保您已激活Pix4Dmatic的许可证。否则,只有前两个处理选项可用,您将无法输出报告。

要处理我们现在上传和设置的城市区域数据集,请选择屏幕右上角带有播放按钮的按钮。这将打开处理菜单。在顶部,您可以更改处理模板,以反映不同类型的无人机飞行和使用的图像捕捉模式。在这种情况下,我们将使用默认的“最低点”。

然后,它给你5个处理选项:校准,增密,网格,DSM和正射镶嵌。您可以单击其中每一项的切换按钮,将它们添加到我们的流程工作流中。让我们来讨论一下它们各自的功能:

“校准处理”选项有助于为其他处理选项准备Pix4Dmatic数据,并通过进一步优化数据为未来运行服务。在实践中,这意味着分配校准模板以决定将使用什么处理选项,分配管道以允许用户控制相机的内部和外部参数,分配图像比例以定义用于提取关键点的图像大小,分配设置以确定提取的关键点的数量。和内部置信度度量来设置初始摄像机校准的置信度。置信度决定了摄像机在校准过程中可以重新校准和调整的程度。

Thedensepointcloudfortheurbanareadataset

增密处理步骤根据提交的数据创建密集点云。密集点云是3D对象建模的常见起点,并且由关于特征空间中许多点的位置的一系列测量来表示。在摄影测量任务中,通过测量图像表面上的许多点来计算它们,然后使用这些距离来推断物体彼此相对位置的三维理解。

Theimagemeshfortheurbanareadataset

在3D计算机图形和实体建模中,多边形网格是定义多面体对象形状的顶点、边和面的集合。在网格处理阶段,Pix4Dmatic生成并提取特征空间中对象的网格表示。这个网格(一个.obj文件)然后可以在许多第三方应用程序中使用,如SculptGL或Blender。

DSM步骤将密集点云作为输入,用于定义分辨率、启用表面平滑和启用插值以创建数字表面模型(DSM)。DSM被标记为感兴趣区域的2.5D模型。它可以导出为两种不同类型的文件:栅格地理标记或点云(。xyz,。las,。laz)。栅格geotiff文件的每个像素和点云中的每个点都包含三维(X,Y,Z)的位移信息,但它们没有第四维来表示捕获的颜色。对于每个(X,Y)位置,DSM只有1个Z值(该(X,Y)位置最高点的高度)。

Theorthomosaicfortheurbanareadataset.

正射镶嵌是所采集影像的2D表示法,其中包含有关映射区域的X、Y和颜色值的信息。它不同于照相拼接,因为正射校正方法使用DSM从图像中移除透视变形。然而,这需要大量的匹配/关键点(超过1000个)来生成模型。实际上,正射镶嵌会校正相机的视角,并根据对象/地面的每个点与相机的距离来增强制图表达以使用不同的比例。这使得正射镶嵌在测量中非常有用,因为物体是根据拍摄的物体彼此之间的相对距离和大小来创建的。

在单击start运行处理之前,请注意在屏幕底部还有一个标题为“exports”的附加部分。这些输出将决定我们之后可以检查的内容。如果你需要一个输出。例如,obj文件或DSMGeoTIFF,确保选择那些框。否则,您现在可以只输出质量报告,然后单击“开始”

处理后,我们可以开始查看我们新创建的城市区域数据集的3D表示。通过这样做,我们可以更好地理解这个模型是如何创建的。通过点击每个连接点,我们可以看到每个不同角度的“摄像机”是如何连接到这些点的。这些不同的视线中的每一条都为Pix4D提供了不同的信息来创建这个3D表示。

您可以通过选择菜单栏下方屏幕左上角的标尺图标进行简单的测量。然后,只需将鼠标拖过有问题的位置,即可测量两点之间的距离。

Pix4dmatic是进行大规模摄影测量的一个非常强大的工具。用户可以期待该应用程序能够处理数百到数千个相机输入,以比Pix4dmapper更高的速度创建大型3D场景模型。如果你打算进行大规模的摄影测量,那么Pix4dmatic绝对是与你的核心机器一起运行的首选软件。

人们可以简单地问:为什么我们不把行动空间离散化?

在本教程中,我们将经历以下步骤:

作为概述,本文介绍的算法称为深度确定性策略梯度(DDPG)。它延续了之前成功的DeepMind论文使用深度强化学习与经验重放缓冲的概念玩雅达利,其中通过对经验批次进行采样来偏离策略地训练网络,以及冻结目标网络,其中制作网络的副本,目的是在目标函数中使用,以避免复杂和非线性函数逼近器(如神经网络)的发散和不稳定性。

强化学习是机器学习的一个子领域。它不同于经典的有监督和无监督学习范式,因为它是一种试错法。这意味着代理实际上不是在数据集上训练的,而是通过与环境交互来训练的,环境被认为是我们希望代理作用的整个系统(像游戏或机械臂)。线索点是环境必须提供一个奖励来回应代理人的行为。这种奖励是根据任务设计的,必须经过深思熟虑,因为它对整个学习过程至关重要。

试错法的基本要素是值函数,通过离散场景中的贝尔曼方程求解,其中我们有低维状态和动作空间。当我们处理高维状态空间或动作空间时,我们必须引入复杂的非线性函数逼近器,例如深度神经网络。为此,在文献中引入了深度强化学习的概念。

现在,让我们从简要描述DDPG带来的主要创新开始,这些创新用于在强化学习框架中处理连续的、因此是高维的动作空间。

策略基本上是代理行为,是从状态到动作的映射(在确定性策略的情况下)或动作的分配(在随机策略的情况下)。这两种策略的存在是因为它们适用于某些特定的任务:确定性策略非常适用于物理控制问题,而随机策略是解决博弈问题的一个很好的选择。

在这种情况下,策略网络的输出是对应于要对环境采取的动作的值。

像DDPG这样的算法中的探索问题可以用一种非常简单的方式来解决,并且独立于学习算法。然后,通过将从噪声进程N采样的噪声添加到行动者策略来构建探索策略。勘探政策因此变成:

$\pi/center>(S[t])=\(\pi\)(S[t],\(\theta\))+\(\nu\)

目的是在中央致动器上施加扭矩,以保持摆锤在垂直轴上的平衡。该问题具有三维状态空间,即角度的余弦和正弦以及角度的导数。动作空间是一维的,它是施加到关节上的力矩,被限制在\([-2,2]\)。

奖励的精确等式:

-(theta^2+0.1*theta_dt^2+0.001*action^2)θ在-π和π之间归一化。所以成本最低的是-(pi^2+0.1*8^2+0.001*2^2)=-16.2736044,成本最高的是0。本质上,目标是保持零度角(垂直),最小的旋转速度和最少的努力。

有关钟摆环境的更多细节,请查看GitHub或T2的openaienv页面。

漏斗的任务是使一个有三个关节和四个身体部分的漏斗尽可能快地向前跳跃。健身房也有,但需要MuJoCo许可证,所以你必须申请并安装它,健身房才能工作。

这个问题有一个11维的状态向量,包括:位置(如果是旋转或棱柱关节,则以辐射或米为单位),位置的导数以及旋转关节角度相对于其相对参考系的正弦和余弦函数。动作空间对应于一个三维空间,其中每个动作都是一个连续值,其范围在\([1,1]\)。因此网络架构应该有3个输出神经元,具有【tanh】激活功能。这些扭矩施加在位于大腿关节、腿关节和脚关节的致动器上,这些动作的范围归一化为\([1,1]\)。

由于任务的目标是使料斗向前移动,因此定义奖励函数时考虑了活着的奖励、向前速度的正贡献(通过对每一步的位移求导来计算)以及动作控制空间中欧几里德范数的负贡献。

其中a是动作(即网络的输出),vx是前进速度,b是活着的奖励。当至少一个故障条件发生时,情节终止,它们是:

其中θ是物体的前倾角。

有几个健身房环境适合连续控制,因为它们有连续的行动空间。有些需要MuJoCo,有些不需要。

在Paperspace上设置实例:

公共容器“Paperspace+Fast。“人工智能很适合我们的实验。

配置:打开终端安装健身房,升级火炬版本。

一旦你为你想玩的环境安装了MuJoCo或者PyBox2d(《钟摆-v0》不需要这些中的任何一个,只需要gym包),你就可以打开一个Jupyter笔记本开始编码了。

不管怎样,下面是一个没有优先级的简单重放缓冲区的实现。

fromcollectionsimportdequeimportrandomimportnumpyasnpclassreplayBuffer(object):def__init__(self,buffer_size,name_buffer=''):self.buffer_size=buffer_size#choosebuffersizeself.num_exp=0self.buffer=deque()defadd(self,s,a,r,t,s2):experience=(s,a,r,t,s2)ifself.num_exp

ForPendulum-v0task,theobjectiveistomaintainthependuluminaverticalposition,sothetotalcumulativerewardoverallthestepsmustbeascloseaspossibletozero;after200stepstheagentlearnedtoreachandstayinthatcondition.

在本文中,我们将探讨GAN的一种变体:pix2pixGANs,这种变体近年来因其能够高精度地将图像从源域转换到目标域而广受欢迎。在我们开始理解pix2pixGAN的研究论文和体系结构分析之前,我们将首先简要介绍阅读这篇博客所需的基础知识。最后,我们将从头开始使用pix2pixGANs开发一个卫星图像到地图的翻译项目。

通过使用以下链接的repo作为“工作区URL”来创建PaperspaceGradient笔记本,可以利用PaperspaceGradient平台来运行以下项目通过切换笔记本创建页面上的高级选项按钮,可以找到该字段。

条件GANs的主要应用之一是这些网络执行高精度图像到图像翻译的能力。图像到图像的翻译是一项任务,其中我们从一个特定的域获取图像,并通过根据特定任务的需要对其进行变换,将其转换为另一个域中的图像。有各种各样的图像翻译项目可供使用,包括将黑白图像转换为彩色图像,将动画草图转换为逼真的人类图片,以及许多其他类似的想法。

先前已经利用了许多方法来高精度地执行图像到图像的转换。然而,一种简单的CNN方法来最小化预测像素和真实像素之间的欧几里德距离往往会产生模糊的结果。模糊效应的主要原因是通过平均所有似是而非的输出来最小化欧几里德距离。pix2pix条件GAN解决了大部分这些潜在问题。模糊图像将被鉴别器网络确定为假样本,解决了以前CNN方法的一个主要问题。在下一节中,我们将对这些pix2pix条件gan有一个更概念性的理解。

先前已经使用了许多方法来执行各种图像转换任务。然而,历史上大多数原始方法都失败了,包括一些流行的深度学习框架。当大多数方法在生成任务中挣扎时,生成对抗网络在大多数情况下成功了。最好的图像翻译网络之一是pix2pixGANs。在本节中,我们将分解这些pix2pixGAN的程序工作,并尝试理解pix2pixGAN架构的发生器和鉴别器网络的复杂细节。

因此,pix2pixGAN的发电机网络中使用的U-Net架构是原始U-Net架构的略微修改版本。而编码器-解码器结构以及跳跃连接是两个网络的关键方面;有一些关键的值得注意的差异。原始U-Net架构中的图像大小从原始尺寸变为新的较小高度和宽度。在pix2pixGAN网络中,发生器网络保留图像大小和尺寸。在向下步进之前,我们在pix2pix生成器网络中仅使用一个卷积层模块,而不是最初使用的两个模块。最后,U-Net网络缩小到大约32x32的最大值,而发电机网络一直缩小到1x1。

至于所提出的鉴别器架构,pix2pixGAN使用了一种分片方法,该方法仅在分片尺度上惩罚结构。虽然GAN架构中的大多数复杂鉴别器利用整个图像来建立伪或实(0或1)值,但是块GAN试图对图像中的每个N×N块是真还是假进行分类。对于每个特定的任务,N×N小块的大小可以不同,但是最终的输出是所考虑的小块的所有响应的平均值。贴片GAN鉴别器的主要优势在于它们具有更少的训练参数、运行更快,并且可以应用于任意大的图像。

在pix2pixGANs的帮助下,我们可以进行许多实验。其中一些实验包括城市景观照片翻译的语义标签,立面数据集照片的建筑标签,黑白图像到彩色图像,动画草图到现实人类图片,等等!在这个项目中,我们将把重点放在卫星地图到航空照片的转换,从谷歌地图刮数据训练。

如前所述,我们将利用的两个主要深度学习框架是TensorFlow和Keras。最有用的层包括卷积层、泄漏ReLU激活函数、批量归一化、漏失层和其他一些基本层。我们还将导入NumPy库来处理数组,并相应地生成真实和虚假的图像。Matplotlib库用于绘制所需的图形和必要的绘图。请查看下面的代码块,了解我们将在这个项目的构造中使用的所有必需的库和导入。

您还可以下载任何其他想要测试模型工作过程的下载内容。对于本文,我们将使用地图数据集。在下面的代码块中,我定义了到我的目录的特定路径,该目录包含带有训练和验证目录的地图数据。请随意相应地设置自己的路径位置。我们还将定义一些基本参数,使用这些参数可以更轻松地完成一些编码过程。由于图像包含卫星图像及其各自的地图,我们可以将它们平均分割,因为它们的尺寸都是256x256,如下面的代码块所示。

为了构建pix2pixGAN架构的发生器网络,我们将把该结构分成几个部分。我们将从编码器模块开始,这里我们将定义步长为2的卷积层,然后是泄漏ReLU激活函数。大多数卷积层之后还会有批量标准化层,如下面的代码块所示。一旦我们返回了发生器网络的编码器模块,我们可以如下构建网络的前半部分-C64-C128-C256-C512-C512-C512-C512-C512-C512。

#EncoderBlockdefdefine_encoder_block(layer_in,n_filters,batchnorm=True):init=RandomNormal(stddev=0.02)g=Conv2D(n_filters,(4,4),strides=(2,2),padding='same',kernel_initializer=init)(layer_in)ifbatchnorm:g=BatchNormalization()(g,training=True)g=LeakyReLU(alpha=0.2)(g)returng我们将定义的发生器网络的下一部分是解码器模块。在该功能中,我们将对所有先前下采样的图像进行上采样,并添加(连接)从编码器到解码器网络必须进行的必要跳跃连接,类似于U-Net架构。对于模型的上采样,我们可以利用卷积转置层和批量归一化层以及可选的丢弃层。发生器网络的解码器模块包含如下架构-CD512-CD512-CD512-C512-C256-C128-C64。下面是以下结构的代码块。

#definethecombinedGANarchitecturedefdefine_gan(g_model,d_model,image_shape):forlayerind_model.layers:ifnotisinstance(layer,BatchNormalization):layer.trainable=Falsein_src=Input(shape=image_shape)gen_out=g_model(in_src)dis_out=d_model([in_src,gen_out])model=Model(in_src,[dis_out,gen_out])#compilemodelopt=Adam(lr=0.0002,beta_1=0.5)model.compile(loss=['binary_crossentropy','mae'],optimizer=opt,loss_weights=[1,100])returnmodel定义所有基本参数:下一步,我们将定义训练pix2pixGAN模型所需的所有基本函数和参数。首先,让我们定义生成真样本和假样本的函数。下面提供了执行以下操作的代码片段。

defgenerate_real_samples(dataset,n_samples,patch_shape):trainA,trainB=datasetix=randint(0,trainA.shape[0],n_samples)X1,X2=trainA[ix],trainB[ix]y=ones((n_samples,patch_shape,patch_shape,1))return[X1,X2],ydefgenerate_fake_samples(g_model,samples,patch_shape):X=g_model.predict(samples)y=zeros((len(X),patch_shape,patch_shape,1))returnX,y在下一个代码块中,我们将创建一个函数来总结模型的性能。生成的图像将与它们的原始对应物进行比较,以获得期望的响应。我们可以为源图像、生成的图像和目标输出图像绘制三幅图。我们可以保存绘图和发电机模型,以便以后根据需要进行进一步计算。

我们将定义输入源图像和目标图像,然后根据输出双曲正切函数的执行情况,对这些图像进行相应的归一化处理,使其在-1到1的理想范围内缩放。我们最终可以开始模型的训练,并在十个时期后评估性能。为每个时期的每批(总共1096个)报告参数。对于十个纪元,我们应该注意到总数为10960。下面是训练模型的代码片段。

从一幅图像到另一幅图像的图像到图像转换是一项非常复杂的任务,因为简单的卷积网络由于缺乏特征提取能力而无法以最理想的方式完成这项任务。另一方面,GANs在生成一些高精度和准确度的最佳图像方面做得非常出色。它们还有助于避免简单卷积网络的一些暗淡效果,如输出清晰、逼真的图像等。因此,推出的pix2pixGAN架构是解决此类问题的最佳GAN版本之一。艺术家和多个用户甚至通过互联网使用pix2pixGAN软件来实现高质量的结果。

任何熟悉卷积神经网络的人都会对术语“汇集”感到熟悉,因为它是每个卷积层之后通常使用的过程。在本文中,我们将探索CNN架构中这一基本过程背后的原因和方法。

与卷积类似,池化过程也利用过滤器/内核,尽管它没有任何元素(有点像空数组)。它本质上包括在图像的连续补丁上滑动这个过滤器,并以某种方式处理内核中捕获的像素;基本上与卷积运算相同。

在深度学习框架中,有一个不太流行但非常基本的参数,它决定了卷积和池类的行为。从更一般的意义上来说,它控制任何由于某种原因使用滑动窗口的类的行为。该参数被称为'步距'它被称为滑动窗口,因为用滤镜扫描图像类似于在图像像素上滑动一个小窗口)。

跨距参数决定了在执行卷积和池化等滑动窗口操作时,mxuch滤波器如何在任一维度上移动。

Stridesinaslidingwindowoperationusingakernel/filterofsize(2,2)

在上面的图像中,滤镜在(6,6)图像上的第0维度(水平)和第1维度(垂直)滑动。当stride=1时,滤波器滑动一个像素。然而,当步幅=2时,滤波器滑动两个像素;步幅=3时为三个像素。当通过滑动窗口过程生成新图像时,这具有有趣的效果;因为在两个维度上的步幅2基本上生成了一个图像,该图像是其原始图像的一半大小。同样,步幅为3将产生大小为其参考图像的三分之一的图像,依此类推。

当步幅>1时,产生的表示是其参考图像大小的一部分。

在执行池操作时,一定要注意默认情况下stride总是等于过滤器的大小。例如,如果要使用(2,2)滤波器,stride的默认值为2。

在细胞神经网络中主要使用两种类型的池操作,它们是最大池和平均池。这两种池操作的全局变体也存在,但是它们超出了本文的范围(全局最大池和全局平均池)。

最大池需要使用过滤器扫描图像,并在每个实例中返回过滤器中捕捉的最大像素值,作为新图像中它自己的像素。

Themaxpoolingoperation

从图中可以看出,空的(2,2)滤波器在(4,4)图像上滑动,跨距为2,如上一节所述。每个实例的最大像素值作为其自身的独特像素返回,以形成新的图像。所得到的图像被认为是原始图像的最大合并表示(注意,由于前面章节中讨论的默认步幅为2,所得到的图像是原始图像的一半大小)。

就像最大池一样,空过滤器也在图像上滑动,但在这种情况下,过滤器中捕获的所有像素的平均值将返回,以形成原始图像的平均池表示,如下图所示。

Theaveragepoolingoperation

从上一节中的插图可以清楚地看到,与平均混合表示相比,最大混合表示中的像素值要大得多。用更简单的话来说,这仅仅意味着从最大池产生的表示通常比从平均池产生的表示更清晰。

用不太专业的术语来说,汇集生成小尺寸的图像,这些图像保留了参考图像的所有基本属性(像素)。基本上,可以产生汽车的(25,25)像素图像,通过使用(2,2)核迭代汇集4次,该图像将保留大小为(400,400)的参考图像的所有一般细节和组成。它通过利用大于1的步幅来做到这一点,允许产生是原始图像的一部分的表示。

回到CNN,随着卷积层变得更深,特征图(由卷积产生的表示)的数量增加。如果特征图的大小与提供给网络的图像的大小相同,由于网络中存在大量数据,特别是在训练期间,计算速度将受到严重阻碍。通过逐步对这些特征地图进行下采样,即使特征地图的数量增加,网络中的数据量也能得到有效控制。这意味着网络将逐步处理合理数量的数据,而不会丢失由先前卷积层提取的任何基本特征,从而提高计算速度。

在本节中,我们将使用一个手动编写的池函数来可视化池过程,以便更好地理解实际发生了什么。提供了两个函数,一个用于最大池,另一个用于平均池。使用这些函数,我们将尝试合并以下大小为(446,550)像素的图像。

Referenceimage.

#importthesedependenciesimporttorchimportnumpyasnpimportmatplotlib.pyplotaspltimporttorch.nn.functionalasFfromtqdmimporttqdmDon'tforgettoimportthesedependencies

defmax_pool(image_path,kernel_size=2,visualize=False,title=''):"""Thisfunctionreplicatesthemaxpoolingprocess"""#assessingimageparameteriftype(image_path)isnp.ndarrayandlen(image_path.shape)==2:image=image_pathelse:image=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)#creatinganemptylisttostoreconvolutionspooled=np.zeros((image.shape[0]//kernel_size,image.shape[1]//kernel_size))#instantiatingcounterk=-1#maxpoolingforiintqdm(range(0,image.shape[0],kernel_size)):k+=1l=-1ifk==pooled.shape[0]:breakforjinrange(0,image.shape[1],kernel_size):l+=1ifl==pooled.shape[1]:breaktry:pooled[k,l]=(image[i:(i+kernel_size),j:(j+kernel_size)]).max()exceptValueError:passifvisualize:#displayingresultsfigure,axes=plt.subplots(1,2,dpi=120)plt.suptitle(title)axes[0].imshow(image,cmap='gray')axes[0].set_title('referenceimage')axes[1].imshow(pooled,cmap='gray')axes[1].set_title('maxpooled')returnpooledMaxPoolingFunction.

上面的函数复制了最大池化过程。使用函数,让我们尝试使用(2,2)内核最大化参考图像池。

Producingamaxpooledrepresentation

查看每个轴上的数字线,可以清楚地看到图像的尺寸缩小了,但所有细节都保持不变。这几乎就像该过程已经提取了最显著的像素,并且产生了大小为参考图像一半的概括表示(一半是因为使用了(2,2)核)。

下面的函数允许最大池化过程的多次迭代的可视化。

defvisualize_pooling(image_path,iterations,kernel=2):"""Thisfunctionhelpstovisualiseseveraliterationsofthepoolingprocess"""image=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)#creatingemptylisttoholdpoolspools=[]pools.append(image)#performingpoolingforiterationinrange(iterations):pool=max_pool(pools[-1],kernel)pools.append(pool)#visualisationfig,axis=plt.subplots(1,len(pools),dpi=700)foriinrange(len(pools)):axis[i].imshow(pools[i],cmap='gray')axis[i].set_title(f'{pools[i].shape}',fontsize=5)axis[i].axis('off')passPoolingvisualizationfunction

使用此函数,我们可以使用(2,2)过滤器可视化3代最大池表示,如下所示。图像从(446,450)像素的大小变为(55,56)像素的大小(本质上是1.5%的总和),同时保持其总体构成。

Referenceimagethrough3progressiveiterationsofmaxpoolingusinga(2,2)kernel.

使用更大的核(3,3)的效果如下所示,正如预期的那样,对于每次迭代,参考图像减小到其先前大小的1/3。到第三次迭代,产生像素化的(16,16)下采样表示(0.1%总和)。虽然是像素化的,但图像的整体概念仍然保持不变。

Referenceimagethrough3iterationsofmaxpoolingusinga(3,3)kernel.

为了正确地尝试模拟卷积神经网络中的最大池化过程,让我们使用Prewitt算子对图像中检测到的垂直边缘进行几次迭代。

Maxpoolingoverdetectededges.

到第三次迭代时,虽然图像的尺寸减小了,但是可以看到它的特征(边缘)逐渐变得清晰。

defaverage_pool(image_path,kernel_size=2,visualize=False,title=''):"""Thisfunctionreplicatestheaveragepoolingprocess"""#assessingimageparameteriftype(image_path)isnp.ndarrayandlen(image_path.shape)==2:image=image_pathelse:image=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)#creatinganemptylisttostoreconvolutionspooled=np.zeros((image.shape[0]//kernel_size,image.shape[1]//kernel_size))#instantiatingcounterk=-1#averagepoolingforiintqdm(range(0,image.shape[0],kernel_size)):k+=1l=-1ifk==pooled.shape[0]:breakforjinrange(0,image.shape[1],kernel_size):l+=1ifl==pooled.shape[1]:breaktry:pooled[k,l]=(image[i:(i+kernel_size),j:(j+kernel_size)]).mean()exceptValueError:passifvisualize:#displayingresultsfigure,axes=plt.subplots(1,2,dpi=120)plt.suptitle(title)axes[0].imshow(image,cmap='gray')axes[0].set_title('referenceimage')axes[1].imshow(pooled,cmap='gray')axes[1].set_title('averagepooled')returnpooledAveragepoolingfunction.

上面的函数复制了平均的池化过程。请注意,这是与maxpooling函数相同的代码,不同之处在于在内核滑过图像时使用了mean()方法。下面是我们参考图像的平均合并表示。

Producinganaveragepooledrepresentation.

与maxpooling类似,可以看到图像已经缩小到一半大小,同时保留了其最重要的属性。这非常有趣,因为与最大池不同,平均池不直接使用参考图像中的像素,而是将它们组合起来,基本上创建新的属性(像素),而参考图像中的细节仍会保留。

让我们使用下面的可视化功能来看看平均池化过程是如何通过3次迭代进行的。

defvisualize_pooling(image_path,iterations,kernel=2):"""Thisfunctionhelpstovisualiseseveraliterationsofthepoolingprocess"""image=cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)#creatingemptylisttoholdpoolspools=[]pools.append(image)#performingpoolingforiterationinrange(iterations):pool=average_pool(pools[-1],kernel)pools.append(pool)#visualisationfig,axis=plt.subplots(1,len(pools),dpi=700)foriinrange(len(pools)):axis[i].imshow(pools[i],cmap='gray')axis[i].set_title(f'{pools[i].shape}',fontsize=5)axis[i].axis('off')passPoolingvisualizationfunction.

同样,即使图像大小在每次迭代后逐渐减少一半,其细节仍然保持,尽管逐渐像素化(如小尺寸图像中所预期的)。

Referenceimagethrough3iterationsofaveragepoolingusinga(2,2)kernel.

使用(3,3)内核的平均池产生以下结果。不出所料,图像大小被缩小到之前值的1/3。就像在maxpooling中一样,在第3次迭代中出现大量像素,但图像的整体属性相当完整。

Referenceimagethrough3iterationsofaveragepoolingusinga(3,3)kernel.

使用Prewitt算子在检测到的垂直边缘上运行(2,2)平均汇集产生以下结果。正如在最大池中一样,图像特征(边缘)在渐进平均池中变得更加明显。

Averagepoolingoverdetectededges.

在了解了最大和平均池过程之后,一个自然的反应是想知道对于计算机视觉应用来说,哪一个更优越。事实是,两种观点都有可能。

一方面,由于maxpooling选择内核中捕获的最高像素值,它会产生更清晰的表示。

Comparingrepresentationsproducedusingbothmethods.

在卷积神经网络环境中,这意味着它可以更好地将检测到的边缘聚焦到特征地图中,如下图所示。

Comparingeffectonedges.

另一方面,也可以支持平均池的观点,即平均池可以生成更一般化的要素地图。考虑我们的大小为(444,448)的参考图像,当与大小为(2,2)的核合并时,其合并表示的大小为(222,224),基本上是参考图像中总像素的25%。因为maxpooling基本上选择像素,一些人认为它会导致数据丢失,这可能对网络性能有害。相反,平均池不是选择像素,而是通过计算它们的平均值将像素合并成一个像素,因此一些人认为平均池只是将像素压缩75%,而不是显式地移除像素,这将产生更一般化的特征图,从而在对抗过拟合方面做得更好。

我属于分界线的哪一边?我个人认为maxpooling在特征地图中进一步突出边缘的能力使其在计算机视觉/深度学习应用中具有优势,因此它更受欢迎。这并不是说使用平均池会严重降低网络性能,只是个人观点。

在本文中,我们对卷积神经网络环境下的池化有了直观的认识。我们已经研究了两种主要的池化类型,以及每种类型所产生的池化表示的差异。

对于CNN中关于池化的所有讨论,请记住,目前大多数架构都倾向于使用步长卷积层,而不是用于下采样的池化层,因为它们可以降低网络的复杂性。无论如何,池仍然是卷积神经网络的一个重要组成部分。

从图像识别到图像生成和标记的问题已经从各种深度学习(DL)架构进步中受益匪浅。理解不同DL模型的复杂性将有助于您理解该领域的发展,并找到适合您试图解决的问题的方法。

在过去的几年中,出现了许多体系结构,它们在许多方面都有所不同,比如层的类型、超参数等。在这个系列中,我们将回顾几个最著名的DL架构,它们定义了这个领域,并重新定义了我们解决关键问题的能力。

在本系列的第一部分,我们将介绍2012年至2014年发布的“早期”车型。这包括:

面临的挑战是开发一个深度卷积神经网络,将ImageNetLSVRC-2010数据集中的120万幅高分辨率图像分类为1000多个不同的类别。该架构实现了15.3%的前5个错误率(在模型的前5个预测中找不到给定图像的真实标签的比率)。第二好的成绩是26.2%,远远落后。

AlexNetArchitecture

网络的输入维度为(256×256×3),这意味着AlexNet的输入是(256×256)像素的RGB(3通道)图像。该架构涉及超过6000万个参数和65万个神经元。为了减少训练过程中的过拟合,网络使用了脱落层。被“放弃”的神经元对正向传递没有贡献,也不参与反向传播。这些层存在于前两个完全连接的层中。

以下是使用AlexNet架构获得的结果截图:

ResultsUsingAlexNetontheImageNetDataset

关于在ILSVRC-2010数据集上的结果,AlexNet在比赛进行时取得了37.5%和17.0%的top-1和top-5测试集错误率。

与AlexNet相比,VGG的主要改进包括相继使用大内核大小的滤波器(在第一和第二卷积层中分别为11和5)和多个(3×3)内核大小的滤波器。

架构的输入尺寸固定为图像尺寸(244×244)。在预处理步骤中,从图像中的每个像素中减去平均RGB值。

预处理完成后,图像被传递到一堆卷积层,这些卷积层带有尺寸为(3×3)的小型感受野滤波器。在一些配置中,滤波器大小设置为(1×1),这可视为输入通道的线性变换(后跟非线性)。

卷积运算的步距固定为1。空间池由五个最大池层执行,这五个最大池层跟随几个卷积层。最大池在(2×2)像素窗口上执行,步长设置为2。

全连接层的配置总是相同的;前两层各有4096个通道,第三层执行1000路ILSVRC分类(因此包含1000个通道,每个类别一个通道),最后一层是softmax层。VGG网络的所有隐藏层之后是ReLu激活函数。

VGG网络有五种配置,分别命名为A到e。配置的深度从左(A)到右(B)增加,并增加了更多的层。下表描述了所有可能的网络架构:

所有配置都遵循架构中的通用模式,只是深度不同;从网络A中的11个权重层(8个卷积层和3个全连接层),到网络E中的19个权重层(16个卷积层和3个全连接层)。卷积层的信道数量相当少,从第一层的64开始,然后在每个最大汇集层之后增加2倍,直到达到512。下图显示了参数的总数(以百万计):

在VGG网络上训练图像使用类似于前面提到的Krizhevsky等人的技术(即AlexNet的训练)。当涉及多尺度训练图像时,只有少数例外。整个训练过程通过使用基于反向传播的小批量梯度下降优化多项式逻辑回归目标来执行。批量大小和动量分别设置为256和0.9。为前两个完全连接的层添加了下降正则化,将下降率设置为0.5。网络的学习率最初设置为0.001,然后当验证集精度停止提高时,学习率降低10倍。总的来说,学习率降低了3倍,并且在370,000次迭代(74个时期)之后停止学习。

在ILSVRC-2012和ILSVRC-2013比赛中,VGG16都明显优于上一代车型。关于单网性能,VGG16架构取得了最好的结果(7.0%测试误差)。下表显示了错误率。

初始网络是神经网络领域的重大突破之一,尤其是对CNN而言。迄今为止,盗梦网络有三个版本,分别命名为盗梦版本1、2和3。第一个版本于2014年进入该领域,正如其名称“GoogleNet”所示,它是由谷歌的一个团队开发的。该网络负责在ILSVRC中建立新的分类和检测技术。这个初始网络的第一个版本被称为GoogleNet。

如果网络由许多深层构成,它可能会面临过度拟合的问题。为了解决这个问题,研究论文中的作者提出了GoogleNet架构,其想法是拥有可以在同一级别上操作的多种大小的过滤器。有了这个想法,网络实际上变得更宽而不是更深。下图显示了一个简单的初始模块。

从上图可以看出,卷积运算是对三种滤波器大小的输入执行的:(1×1)、(3×3)和(5×5)。还对卷积执行最大池操作,然后将其发送到下一个初始模块。

由于训练神经网络既耗时又昂贵,作者通过在(3×3)和(5×5)卷积之前添加额外的(1×1)卷积来限制输入通道的数量,以降低网络的维数并执行更快的计算。下面的图片展示了一个简单的Inception模块。

这些是谷歌网的组成部分。下面是关于其架构的详细报告。

GoogleNet架构有22层,包括27个池层。总共有9个初始模块线性堆叠。初始模块的末端连接到全局平均池层。下面是整个GoogleNet架构的缩小图。

下图解释了详细的架构和参数。

我们将在此讨论的架构包括:

让我们开始吧。

传统的n层的卷积网络有n个连接;每层与其后续层之间有一个。在DenseNet中,每一层都以前馈方式连接到其他每一层,这意味着DenseNet总共有n(n+1)/2个连接。对于每一层,所有先前层的特征图被用作输入,并且它自己的特征图被用作所有后续层的输入。

DenseNet比传统的深度CNN有一个很大的优势:通过许多层传递的信息在到达网络末端时不会被洗掉或消失。这是通过一个简单的连接模式实现的。为了理解这一点,人们必须知道普通CNN中的层是如何连接的。

这是一个简单的CNN,其中各层是顺序连接的。但是,在密集块中,每一层都从所有前面的层获得额外的输入,并将自己的要素地图传递给所有后面的层。下图描绘了致密的块状物。

随着网络中的图层从所有先前图层接收要素地图,网络将变得更细、更紧凑。下面是一个通道数设置为4的5层密集块。

DenseNet已经应用于各种不同的数据集。基于输入的维度,使用不同类型的密集块。下面是对这些层的简要描述。

原始研究论文中定义的DenseNet架构应用于三个数据集:CIFAR、SVHN和ImageNet。所有架构都使用随机梯度下降优化器进行训练。CIFAR和SVHN的训练批次大小分别为64、300和40个时期。初始学习率设置为0.1,并进一步降低。以下是在ImageNet上培训的DenseNet的指标:

以下是详细的结果,显示了DenseNet的不同配置与CIFAR和SVHN数据集上的其他网络相比的差异。蓝色数据表示最佳结果。

以下是ImageNet上不同大小的DenseNet的前1名和前5名错误。

ResNeXt的基本架构由两条规则定义。首先,如果块产生相同维度的空间图,它们共享相同的超参数集,并且如果空间图以因子2被下采样,则块的宽度乘以因子2。

如表中所示,ResNeXt-50的基数32重复了4次(深度)。[]中的尺寸表示剩余块结构,而写在它们旁边的数字表示堆叠块的数量。32精确地表示在分组卷积中有32个组。

上述网络结构解释了什么是分组卷积,以及它如何胜过其他两种网络结构。

在上述三种方法中,(c)被证明是最好的,因为它易于实现。

ImageNet已经被用来展示当基数而不是宽度/深度被考虑时准确性的提高。

当基数较高时,ResNeXt-50和ResNeXt-101都不太容易出错。此外,与ResNet相比,ResNeXt表现良好。

下面是一些重要的链接,

FLOPs是衡量网络计算性能的常用指标。然而,一些研究证实了这样一个事实,即失败并不能完全挖掘出潜在的真理;具有类似FLOPs的网络在速度上有所不同,这可能是因为内存访问成本、并行度、目标平台等。所有这些都不属于失败,因此,被忽视。ShuffleNetv2通过提出建模网络的四个准则克服了这些麻烦。

在了解网络体系结构之前,网络构建所依据的指导原则应简要介绍如何考虑各种其他直接指标:

所有这些都集成在ShuffleNetv2架构中,以提高网络效率。

信道分割操作符将信道分为两组,其中一组作为身份保留(第3条准则)。另一个分支沿着三个回旋具有相同数量的输入和输出通道(第一条准则)。1x1卷积不是分组的(第二条准则)。像ReLU、Concat、深度卷积这样的基于元素的操作被限制在一个分支中(4准则)。

ShuffleNetv2的整体架构列表如下:

结果是关于输出声道的不同变化。

Imagenet已被用作数据集,以获得各种数据集的结果。

复杂度、错误率、GPU速度和ARM速度已经被用于在预期的模型中导出稳健且有效的模型。虽然ShuffleNetv2缺乏GPU速度,但它记录了最低的top-1错误率,这超过了其他限制。

下面是几个额外的链接,你可能会对自己实现ShuffleNet感兴趣,或者深入阅读原文。

该架构通常由两个阶段组成-搜索空间和强化学习方法。

可以注意到,搜索空间由几个块组成。所有层都根据其尺寸和过滤器大小进行隔离。每个块都有一组特定的层,在这些层中可以选择操作(如蓝色所示)。如果输入或输出维度不同,则每个块中的第一层的跨度为2,其余层的跨度为1。从第二层开始到第N层重复相同的一组操作,其中N是块号。

与其他传统的移动CNN模型相比,Imagenet已被用于描述MnasNet模型所实现的准确性。这是一个代表相同情况的表格:

MnasNet无疑减少了延迟,提高了准确性。

如果您想查看原始论文或在PyTorch中自己实现MnasNet,请查看以下链接:

在本文中,我们将深入探讨这一时期(2015-2016年)之后出现的模型。仅在这两年中就取得了巨大的进步,导致了精确度和性能的提高。具体来说,我们将了解:

当训练深度网络时,深度的增加会导致精度饱和,然后迅速下降。这被称为“退化问题”这突出了并非所有的神经网络架构都同样容易优化。

ResNet使用一种称为“残差映射”的技术来解决这个问题。残差网络明确地让这些层拟合残差映射,而不是希望每几个堆叠层直接拟合所需的底层映射。下面是剩余网络的构建模块。

F(x)+x的公式可以通过具有快捷连接的前馈神经网络来实现。

许多问题可以使用ResNets解决。当网络深度增加时,它们易于优化并实现更高的精度,产生比以前的网络更好的结果。像我们在第一部分中介绍的前辈一样,ResNet首先在ImageNet的120多万张属于1000个不同类别的训练图像上进行训练和测试。

与传统的神经网络结构相比,ResNets相对容易理解。下图是一个VGG网络、一个普通的34层神经网络和一个34层残差神经网络。在平面网络中,对于相同的输出要素地图,图层具有相同数量的过滤器。如果输出特征的大小减半,则过滤器的数量加倍,使得训练过程更加复杂。

同时,在残差神经网络中,正如我们可以看到的,在关于VGG的训练期间,有少得多的滤波器和较低的复杂度。添加了一个快捷连接,将网络转换为其对应的剩余版本。这种快捷连接执行标识映射,并为增加的维度填充额外的零条目。该选项不引入额外的参数。投影快捷方式在数学上表示为F(x{W}+x),用于匹配由1×1卷积计算的维度。

下表显示了不同ResNet架构中的层和参数。

每个ResNet块要么是两层深(用于ResNet18或34等小型网络),要么是三层深(ResNet50、101或152)。

来自ImageNet数据集的样本被重新缩放到224×224,并通过每像素均值减法进行归一化。随机梯度下降用于最小批量为256的优化。学习率从0.1开始,误差增大时除以10,模型训练到60×104次迭代。重量衰减和动量分别设定为0.0001和0.9。不使用脱落层。

ResNet在更深层次的架构中表现非常好。下图显示了两个18层和34层神经网络的错误率。左边的图显示了普通网络,而右边的图显示了它们的ResNet等价物。图像中细的红色曲线表示训练错误,粗的曲线表示验证错误。

下表显示了ImageNet验证中最大的错误(%),10次裁剪测试。

正如我们今天所知,ResNet在定义深度学习领域方面发挥了重要作用。

如果您对自己实现ResNet感兴趣,下面是一些重要的链接:

深度残差网络已经显示出显著的准确性,有助于相对容易地执行图像识别等任务。尽管如此,深层网络仍然面临着网络退化以及爆炸或消失梯度的挑战。深度残余网络也不能保证所有残余块的使用;只有几个可以被跳过,或者只有几个可以进入更大的贡献块(即提取有用的信息)。这个问题可以通过禁用随机块来解决——这是广义的辍学。从这个想法得到启示,WideResNet的作者已经证明了宽残差网络甚至可以比深残差网络表现得更好。

宽ResNet有一组堆叠在一起的ResNet块,其中每个ResNet块都遵循BatchNormalization-ReLU-Conv结构。该结构描述如下:

有五个组组成了一个大范围的搜索网。这里的块是指残差块B(3,3)。Conv1在任何网络中都保持不变,而conv2、conv3和conv4则根据k、定义宽度的值而变化。卷积层之后是平均池层和分类层。

可以调整以下可变度量,以得出残差块的各种表示:

下表比较了CIFAR-10和CIFAR-100上的WideResNet与其他几个型号(包括原始ResNet)的复杂性和性能:

以下是几个你自己实现WideResNet的重要环节:

与VGGNet相比,InceptionNetworks(Googlenet/Inceptionv1)已证明在网络生成的参数数量和产生的经济成本(内存和其他资源)方面计算效率更高。如果要对初始网络进行任何更改,需要注意确保计算优势不会丧失。因此,由于新网络效率的不确定性,针对不同用例的初始网络的适应变成了一个问题。在Inceptionv3模型中,已经提出了几种用于优化网络的技术来放松约束,以便更容易地适应模型。这些技术包括分解卷积、正则化、降维以及并行计算。

Inceptionv3网络的架构是逐步构建的,如下所述:

1。因式分解卷积:这有助于降低计算效率,因为它减少了网络中涉及的参数数量。它还保持对网络效率的检查。

2。更小的卷积:用更小的卷积替换更大的卷积肯定会导致更快的训练。假设一个5×5的滤波器有25个参数;代替5×5卷积的两个3×3滤波器只有18(33+33)个参数。

Inthemiddleweseea3x3convolution,andbelowafully-connectedlayer.Sinceboth3x3convolutionscanshareweightsamongthemselves,thenumberofcomputationscanbereduced.

3。不对称卷积:一个3×3卷积可以被一个1×3卷积和一个3×1卷积代替。如果用2×2卷积代替3×3卷积,参数的数量将比提出的非对称卷积略高。

4。辅助分类器:辅助分类器是训练时插在层间的小CNN,产生的损耗加到主网损耗上。在GoogLeNet中,辅助分类器用于更深的网络,而在Inceptionv3中,辅助分类器充当正则化器。

5。网格尺寸缩减:网格尺寸缩减通常通过池化操作来完成。然而,为了克服计算成本的瓶颈,提出了一种更有效的技术:

上述所有概念都被整合到最终的架构中。

Inceptionv3在ImageNet上进行了训练,并与其他当代模型进行了比较,如下所示。

如表中所示,当使用辅助分类器、卷积因子分解、RMSProp和标签平滑进行增强时,Inceptionv3可以实现与其同时代产品相比最低的错误率。

以下是SqueezeNet背后的关键理念:

SqueezeNet架构由“挤压”和“扩展”层组成。一个压缩卷积层只有1×1个滤波器。这些信号被送入一个扩展层,该层混合了1×1和3×3卷积滤波器。如下所示。

A"FireModule"

本文作者使用术语“火灾模块”来描述挤压层和膨胀层。

输入图像首先被发送到独立的卷积层。根据上面的策略一,这一层之后是8个“消防模块”,它们被命名为“消防2-9”。下图显示了生成的挤压网。

Fromlefttoright:SqueezeNet,SqueezeNetwithsimplebypass,andSqueezeNetwithcomplexbypass

按照策略二,每个火灾模块的过滤器通过“简单旁路”增加最后,SqueezeNet在层conv1、fire4、fire8和conv10之后以2的步长执行最大池化。根据策略三,池被给予相对较晚的位置,导致SqueezeNet具有“复杂旁路”(上图中最右边的架构)

下图显示了SqueezeNet与原版AlexNet的对比。

正如我们所观察到的,AlexNet的压缩模型的权重是240MB,并且达到了80.3%的准确率。同时,深度压缩SqueezeNet消耗0.47MB的内存并实现相同的性能。

以下是网络中使用的其他参数的详细信息:

SqueezeNet由于体积小,使得部署过程更加容易。最初,这个网络是在Caffe中实现的,但这个模型后来越来越流行,并被许多不同的平台采用。

在本系列的第三和最后部分,我们将涵盖2017年至2019年最新发布的模型:DenseNet、ResNeXt、MnasNet和ShuffleNetv2。

姿态估计是用于检测物体姿态(即方向和位置)的计算机视觉任务。它通过检测一些关键点来工作,这样我们就可以了解物体的主要部分,并估计其当前的方向。基于这样的关键点,我们将能够在2D或3D中形成物体的形状。

本教程介绍了如何构建一个Android应用程序,使用预训练的TFLitePoseNet模型来估计独立RGB图像中的人体姿势。该模型预测了人体17个关键点的位置,包括眼睛、鼻子、肩膀等的位置。通过估计关键点的位置,我们将在第二个教程中看到如何使用该应用程序来制作特殊效果和滤镜,就像你在Snapchat上看到的那样。

该项目是用Kotlin编程语言实现的,并访问Android摄像头来捕捉图像。对于每个捕捉的图像,该模型预测关键点的位置,并显示这些关键点重叠的图像。

在本教程中,我们将尽可能地简化这个项目。首先,该项目将被编辑,以处理从图库中选择的单个图像,而不是用相机拍摄的图像。一旦我们有了单幅图像的结果,我们将在眼睛上添加一个遮罩,这是Snapchat等图像编辑应用程序中的一个已知效果。

下一节讨论从项目中移除不必要的代码。

从PosenetActivity.kt文件开始,下面是要删除的代码行列表:

由于删除了前面的代码,因此不再需要以下变量:

完成前面的更改后,也不再需要这三行代码:

下面是做出这些改变后的PosenetActivity.kt的当前形式。

从三个文件PosenetActivity.kt、activity_posenet.xml和AndroidManifest.xml中删除所有不必要的代码后,我们仍然需要做一些修改来处理单个图像。下一节讨论编辑activity_posenet.xml文件,以便能够加载和显示图像。

下面列出了活动布局文件的内容。它只有两个元素:Button和ImageView。该按钮将用于在单击后加载图像。它被赋予一个IDselectImage,以便在活动内部访问。

ImageView将有两个用途。第一个是显示选中的图像。第二个是显示应用眼睛过滤器后的结果。ImageView被赋予IDimageView,以便从活动中访问。

在实现按钮clicklistener之前,有必要在AndroidManifest.xml文件中添加下一行来请求访问外部存储器的许可。

下一节将讨论如何实现按钮点击监听器来从图库中加载图像。

下面给出了onStart()回调方法的当前实现。如果您还没有这样做,请删除对openCamera()方法的调用,因为不再需要它了。onStart()方法只是创建了一个PoseNet类的实例,这样以后就可以用它来预测关键点的位置。变量posenet保存创建的实例,稍后将在processImage()方法中使用。

overridefunonStart(){super.onStart()posenet=Posenet(this.context!!)}在onStart()方法中,我们可以将一个点击监听器绑定到selectImage按钮。下面是这种方法的新实现。使用Intent,画廊将被打开,要求用户选择一个图像。调用startActivityForResult()方法,请求代码存储在设置为100的REQUEST_CODE变量中。

overridefunonStart(){super.onStart()posenet=Posenet(this.context!!)selectImage.setOnClickListener(View.OnClickListener{valintent=Intent(Intent.ACTION_PICK)intent.type="image/jpg"startActivityForResult(intent,REQUEST_CODE)})}一旦用户返回到应用程序,就会调用onActivityResult()回调方法。下面是它的实现。使用一个if语句,检查结果以确保图像被成功选择。如果结果不成功(例如,用户没有选择图像),则显示toast消息。

overridefunonActivityResult(requestCode:Int,resultCode:Int,data:Intent){if(resultCode==Activity.RESULT_OK&&requestCode==REQUEST_CODE){imageView.setImageURI(data.data)valimageUri=data.getData()valbitmap=MediaStore.Images.Media.getBitmap(context.contentResolver,imageUri)processImage(bitmap)}else{Toast.makeText(context,"Noimageisselected.",Toast.LENGTH_LONG).show()}}如果结果成功,则使用setImageURI()方法在ImageView上显示所选图像。

为了能够处理选定的图像,它需要作为一个Bitmap可用。因此,图像根据其URI被读取为位图。首先使用getData()方法返回URI,然后使用getBitmap()方法返回位图。位图可用后,调用processImage()方法准备图像并估计人体姿态。这将在接下来的两节中讨论。

下面显示了将在整个教程中使用的图像。

从图库中选择这样的图像后,它将显示在ImageView上,如下图所示。

在图像作为位图加载之后,在估计人体姿态之前,需要两个额外的步骤:裁剪和缩放图像。我们现在将讨论这些。

本节介绍在应用PoseNet模型之前准备映像。

在项目内部有一个名为processImage()的方法,它调用必要的方法来完成四个重要任务:

privatefunprocessImage(bitmap:Bitmap){//Cropbitmap.valcroppedBitmap=cropBitmap(bitmap)//Createdscaledversionofbitmapformodelinput.valscaledBitmap=Bitmap.createScaledBitmap(croppedBitmap,MODEL_WIDTH,MODEL_HEIGHT,true)//Performinference.valperson=posenet.estimateSinglePose(scaledBitmap)//Drawkeypointsovertheimage.valcanvas:Canvas=surfaceHolder!!.lockCanvas()draw(canvas,person,scaledBitmap)}为什么我们需要裁剪或缩放图像?是否需要同时应用裁剪和缩放操作,或者只应用一种操作就足够了?大家讨论一下。

PoseNet模型接受大小为(257,257)的图像。在Constants.kt文件中定义了两个变量MODEL_WIDTH和MODEL_HEIGHT,分别代表模型输入的宽度和高度。两者都被设定为257。

如果一个图像要传递给PoseNet模型,那么它的大小必须是(257,257)。否则,将引发异常。例如,如果从图库中读取的图像大小为(547,783),那么必须将其调整到模型输入的大小(257,257)。

基于此,似乎只需要scale(即调整大小)操作就可以将图像转换为所需的大小。Bitmap.createScaledBitmap()方法接受输入位图、所需的宽度和高度,并返回所需大小的新位图。那么,为什么还要应用裁剪操作呢?答案是保留模型的长宽比。否则我们很容易出现图像质量问题。下图显示了应用裁剪和调整大小操作后的结果。

由于裁剪图像,图像顶部的一些行会丢失。只要人体出现在图像的中心,这就不是问题。您可以检查cropBitmap()方法的实现是如何工作的。

在讨论了processImage()方法的前两个任务的目的之后,现在让我们讨论剩下的两个:姿态估计和关键点绘制。

要估计所选图像的人体姿态,您需要做的就是调用estimateSinglePose()方法,如下所示。该方法接受缩放后的图像作为输入,并在保存模型预测的person变量中返回一个对象。

valperson=posenet.estimateSinglePose(scaledBitmap)基于模型预测,关键点将被绘制在图像上。为了能够在图像上绘制,必须首先创建一个画布。下面的代码行(在processImage()方法中)使用了surfaceHolder来绘制画布,但是我们将删除它:

valcanvas:Canvas=surfaceHolder!!.lockCanvas()用这个替换它:

valcanvas=Canvas(scaledBitmap)现在我们准备调用draw()方法在图像上绘制关键点。不要忘记从draw()方法的末尾删除这一行:surfaceHolder!!.unlockCanvasAndPost(canvas)。

draw(canvas,person,scaledBitmap)既然我们已经讨论了processImage()方法中的所有方法调用,下面是它的实现。

privatefunprocessImage(bitmap:Bitmap){//Cropbitmap.valcroppedBitmap=cropBitmap(bitmap)//Createdscaledversionofbitmapformodelinput.valscaledBitmap=Bitmap.createScaledBitmap(croppedBitmap,MODEL_WIDTH,MODEL_HEIGHT,true)//Performinference.valperson=posenet.estimateSinglePose(scaledBitmap)//Drawkeypointsovertheimage.valcanvas=Canvas(scaledBitmap)draw(canvas,person,scaledBitmap)}下图显示了在画出模型确信的关键点后的结果。这些点被画成圆圈。这是来自draw()方法的代码部分,负责在图像上画圆。您可以编辑变量circleRadius的值来增加或减小圆的大小。

请注意,所绘制的关键点的置信度大于设置为0.5的minConfidence变量中指定的值。你可以把它改成最适合你的。

下一节将展示如何打印一些关于关键点的信息。

从estimateSinglePose()方法返回的对象person保存了一些关于检测到的关键点的信息。这些信息包括:

接下来的代码创建了一个for循环,用于遍历所有的关键点,并在日志消息中打印每个关键点的前三个属性。

for(keyPointinperson.keyPoints){Log.d("KEYPOINT","BodyPart:"+keyPoint.bodyPart+",KeypointLocation:("+keyPoint.position.x.toFloat().toString()+","+keyPoint.position.y.toFloat().toString()+"),Confidence"+keyPoint.score);}下面是运行循环的结果。请注意,LEFT_EYE、RIGHT_EYE、RIGHT_SHOULDER等身体部位的置信度大于0.5,这也是它们被绘制在图像上的原因。

下图总结了该项目中应用的步骤。我们首先从图库中加载一张图片,裁剪并调整大小到(257,257)。在加载PoseNet模型之后,图像被馈送给它,用于预测关键点的位置。最后,将检测到的置信度在0.5以上的关键点绘制在图像上。

下一个教程继续这个项目,在关键点的位置上放置滤镜,应用类似Snapchat中看到的效果。

在上一个教程中,我们看到了如何使用PoseNet模型来检测人体的各个关键点的位置,例如眼睛、耳朵和鼻子。基于这些检测到的关键点,我们可以应用像Snapchat这样的流行应用程序中可用的效果。在本教程中,我们将继续在图像上应用眼睛过滤器的项目。

大纲如下:

这个项目由两部分组成:使用PoseNet的关键点检测(我们在之前的教程中已经介绍过了),并将其应用到创建眼睛过滤器的用例中。

下图总结了我们在上一个教程中完成的内容。我们首先加载、裁剪和调整图像大小,使其大小与模型输入的大小相匹配。该图像然后被馈送到PoseNet模型,用于预测关键点位置。

当前教程在上一个教程停止的地方继续;检测完所有的关键点后,我们将会锁定眼睛的位置。加载并准备好过滤器图像后,将其放置在目标关键点上。这将在下面演示。

我们将从下载和准备过滤器图像开始。

眼睛过滤器只是一个放在我们的目标图像上的图像。您可以使用任何您想要的滤镜图像,但是您需要遵循我们将在这里讨论的准备它的说明。

让我们从左上角开始。

左眼和右眼看起来是相连的,所以为了方便起见,我们将它们分开。这是左眼:

这是正确的:

准备好过滤器后,我们将回顾如何定位由PoseNet模型返回的左眼和右眼的关键点。

在之前教程中讨论的AndroidStudio项目中,活动PosenetActivity.kt有一个名为processImage()的方法,负责准备图库图像(即裁剪和调整大小)、预测关键点位置以及绘制它们的所有工作。它的实现如下所示。

privatefunprocessImage(bitmap:Bitmap){//Cropbitmap.valcroppedBitmap=cropBitmap(bitmap)//Createdscaledversionofbitmapformodelinput.valscaledBitmap=Bitmap.createScaledBitmap(croppedBitmap,MODEL_WIDTH,MODEL_HEIGHT,true)//Performinference.valperson=posenet.estimateSinglePose(scaledBitmap)//Drawkeypointsovertheimage.valcanvas=Canvas(scaledBitmap)draw(canvas,person,scaledBitmap)}对象person保存了关于检测到的关键点的信息,包括它们的位置、置信度得分以及它们所代表的身体部位。这个对象作为参数与画布和位图一起提供给了draw()方法。

这里是draw()方法在关键点位置画圆的最小代码。变量MODEL_WIDTH和MODEL_HEIGHT分别代表模型的输入宽度和高度。在AndroidStudio项目的Constants.kt文件中,两者都被赋予了值257。

privatefundraw(canvas:Canvas,person:Person,bitmap:Bitmap){setPaint()valwidthRatio=canvas.width.toFloat()/MODEL_WIDTHvalheightRatio=canvas.height.toFloat()/MODEL_HEIGHT//Drawkeypointsovertheimage.for(keyPointinperson.keyPoints){if(keyPoint.score>minConfidence){valposition=keyPoint.positionvaladjustedX:Float=position.x.toFloat()*widthRatiovaladjustedY:Float=position.y.toFloat()*heightRatiocanvas.drawCircle(adjustedX,adjustedY,circleRadius,paint)}}}下图显示了绘制关键点后的图像。

从person对象中,我们可以检索模型返回的所有17关键点的以下信息:

下一个for循环将此信息打印为日志消息。

for(keyPointinperson.keyPoints){Log.d("KEYPOINT","BodyPart:"+keyPoint.bodyPart+",KeypointLocation:("+keyPoint.position.x.toFloat().toString()+","+keyPoint.position.y.toFloat().toString()+"),Confidence"+keyPoint.score);}我们对所有的关键点都不感兴趣,只对眼睛感兴趣。在这个循环中,我们可以使用下面的if语句来检查左眼:

if(keyPoint.bodyPart==BodyPart.LEFT_EYE){}然后是右眼。

if(keyPoint.bodyPart==BodyPart.RIGHT_EYE){}注意,前面两个if语句只是检查身体部分,忽略了置信度得分。下面的代码也考虑了置信度得分。请注意,minConfidence变量在PosenetActivity.kt中定义,其值为0.5,这意味着关键点必须具有0.5或更高的置信度才能被接受。

for(keyPointinperson.keyPoints){if(keyPoint.bodyPart==BodyPart.LEFT_EYE&&keyPoint.score>minConfidence){}if(keyPoint.bodyPart==BodyPart.RIGHT_EYE&&keyPoint.score>minConfidence){}}现在我们可以定位眼睛,我们将看到如何加载过滤器。

两只眼睛的滤镜将作为资源图像添加到AndroidStudio项目中。只需复制两张图片并粘贴到项目的drawable文件夹中。为了方便定位图像,选择Android视图,然后导航到app/res/drawable目录,如下图所示。在我的例子中,我将这两个图像命名为left.png和right.png。

下一行使用decodeResource()方法加载图像。要加载左眼滤镜,只需将当前IDR.drawable.right替换为R.drawable.left。记得根据您为两个图像选择的名称替换单词right和left。

varfilterImage=BitmapFactory.decodeResource(context.getResources(),R.drawable.right)加载左眼和右眼滤镜后,在将它们绘制到图像上之前,我们需要调整它们的大小。下一行使用Bitmap.createScaledBitmap()方法将每个调整到(100,100)。

filterImage=Bitmap.createScaledBitmap(filterImage,100,100,true)最后,根据下面的代码在画布上绘制过滤器。从x和y坐标中减去的值50用于将尺寸为(100,100)的滤光器置于眼睛的中心。

canvas.drawBitmap(filterImage,keyPoint.position.x.toFloat()*widthRatio-50,keyPoint.position.y.toFloat()*heightRatio-50,null)我们现在已经在图像上加载、调整和绘制了滤镜。下面列出了draw()方法的完整实现,包括if语句。

privatefundraw(canvas:Canvas,person:Person,bitmap:Bitmap){setPaint()valwidthRatio=canvas.width.toFloat()/MODEL_WIDTHvalheightRatio=canvas.height.toFloat()/MODEL_HEIGHT//Drawkeypointsovertheimage.for(keyPointinperson.keyPoints){if(keyPoint.bodyPart==BodyPart.LEFT_EYE){varfilterImage=BitmapFactory.decodeResource(context.getResources(),R.drawable.left)filterImage=Bitmap.createScaledBitmap(filterImage,100,100,true)canvas.drawBitmap(filterImage,keyPoint.position.x.toFloat()*widthRatio-50,keyPoint.position.y.toFloat()*heightRatio-50,null)}if(keyPoint.bodyPart==BodyPart.RIGHT_EYE){varfilterImage=BitmapFactory.decodeResource(context.getResources(),R.drawable.right)filterImage=Bitmap.createScaledBitmap(filterImage,100,100,true)canvas.drawBitmap(filterImage,keyPoint.position.x.toFloat()*widthRatio-50,keyPoint.position.y.toFloat()*heightRatio-50,null)}}}下图显示了滤镜如何查看图像。结果看起来相当不错。

让我们用下面的图片再试一次。

下图显示了如何应用过滤器。在这种情况下,过滤器太大了。

问题是滤镜的大小永远是(100,100),与眼睛大小无关。在某些情况下,就像上图中的一个,眼睛可能远离相机,因此它们的尺寸远小于(100,100)。因此,滤镜不仅覆盖眼睛,还可能覆盖整张脸。

我们将在下一节通过根据两眼之间的距离调整眼睛滤镜的大小来解决这个问题。

为了相对于眼睛大小来调整过滤器图像的大小,两只眼睛的X位置将被保存到一个数组中。下一行创建了一个名为eyesXLocation的数组来保存眼睛的X位置。

for(keyPointinperson.keyPoints){if(keyPoint.bodyPart==BodyPart.LEFT_EYE){eyesXLocation[0]=keyPoint.position.x.toFloat()*widthRatio}if(keyPoint.bodyPart==BodyPart.RIGHT_EYE){eyesXLocation[1]=keyPoint.position.x.toFloat()*widthRatio}}基于存储在eyesXLocation数组中的值,将根据下一行计算两眼之间的水平绝对距离。

vareyeFilterSize=abs(eyesXLocation[1]-eyesXLocation[0])下一张图应该有助于阐明距离是如何计算的。关键点位置用红色圆圈标记。关键点之间的距离是连接它们的红线的长度。如果线的长度是L,那么眼睛过滤器的尺寸将是(L,L)。在这里,L是变量eyeFilterSize。每个眼睛过滤器以关键点为中心。

eyeFilterSize中的值将被提供给Bitmap.createScaledBitmap()方法,以调整加载的过滤器图像的大小,如下所示。

filterImage=Bitmap.createScaledBitmap(filterImage,eyeFilterSize.toInt(),eyeFilterSize.toInt(),true)下面显示了draw()方法的新实现。

注意,基于眼睛关键点之间的距离动态计算过滤器尺寸也有助于使过滤器完全覆盖眼睛。这可以在下图中看到。

现在,我们可以自动调整任何过滤器,以适应图像。在我们结束教程之前,让我们看看如何使用其他眼睛过滤器。

我们可以很容易地改变我们使用的过滤器。您需要做的就是将图像作为资源文件添加到AndroidStudio项目中,并将其加载到draw()方法中。让我们使用下面的心脏图像代替前面的过滤器。

下载完图片后,只需按照下图将其作为可绘制资源添加到AndroidStudio项目中即可。我将资源文件命名为heart.png。

现在,根据下一行,我们将使用draw()方法来加载它,以代替前面的过滤器。

varfilterImage=BitmapFactory.decodeResource(context.getResources(),R.drawable.heart)如果您想使用另一个过滤器,只需将其作为资源添加,并将其ID提供给decodeResource()方法。

下图显示了使用心脏过滤器后的结果。

基于使用PoseNet深度学习模型检测到的关键点,可以定位左眼和右眼。在被定位之后,加载过滤器图像并准备在被检测的眼睛上绘制。

为了使过滤器图像的大小取决于眼睛的大小,计算两只眼睛之间的距离并用于调整过滤器的大小。

与其他问题相比,处理文本数据的过程略有不同。这是因为数据通常是文本形式的。因此,你必须弄清楚如何以机器学习模型可以理解的数字形式来表示数据。在本文中,我们将看看如何做到这一点。最后,您将使用TensorFlow建立一个深度学习模型,对给定的文本进行分类。

让我们开始吧。请注意,您可以从渐变社区笔记本的免费GPU上运行本教程中的所有代码。

第一步是下载和加载数据。我们将使用的数据是一个情感分析数据集。它有两列;一个有情感,另一个有标签。让我们下载并加载它。

现在让我们选择特性和目标,然后将数据分成训练集和测试集。

X=df['text']y=df['sentiment']fromsklearn.model_selectionimporttrain_test_splitX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.20)数据预处理由于这是文本数据,有几件事你必须清理它。这包括:

以上都可以在TensorFlow中使用Tokenizer实现。该类需要几个参数:

一旦用首选参数实例化了训练集上的Tokenizer,就用fit_on_texts函数来拟合它。

fromkeras.preprocessing.textimportTokenizervocab_size=10000oov_token=""tokenizer=Tokenizer(num_words=vocab_size,oov_token=oov_token)tokenizer.fit_on_texts(X_train)word_index可以用来显示单词到数字的映射。

下一步是将每种情绪表示为一系列数字。这可以使用texts_to_sequences功能来完成。

X_train_sequences=tokenizer.texts_to_sequences(X_train)这是这些序列的样子。

让我们对测试集做同样的事情。当您检查序列样本时,您可以看到不在词汇表中的单词由1表示。

让我们从定义每个序列的最大长度、填充类型和截断类型开始。填充和截断类型“post”意味着这些操作将发生在序列的末尾。

max_length=100padding_type='post'truncation_type='post'有了这些,让我们开始填充X_test_sequences。这是在传递上面定义的参数时使用pad_sequences函数完成的。

fromkeras.preprocessing.sequenceimportpad_sequencesX_test_padded=pad_sequences(X_test_sequences,maxlen=max_length,padding=padding_type,truncating=truncation_type)对X_train_sequences也应该这样做。

X_train_padded=pad_sequences(X_train_sequences,maxlen=max_length,padding=padding_type,truncating=truncation_type)打印最终结果显示,在序列的末尾添加了零,使它们具有相同的长度。

第一步是获得单词嵌入,并将它们添加到字典中。之后,您需要为训练集中的每个单词创建一个嵌入矩阵。让我们从下载手套单词嵌入开始。

importosimportzipfilewithzipfile.ZipFile('/tmp/glove.6B.zip','r')aszip_ref:zip_ref.extractall('/tmp/glove')接下来,用这些嵌入创建字典。让我们使用glove.6B.100d.tx嵌入。名称中的100与为序列选择的最大长度相同。

importnumpyasnpembeddings_index={}f=open('/tmp/glove/glove.6B.100d.txt')forlineinf:values=line.split()word=values[0]coefs=np.asarray(values[1:],dtype='float32')embeddings_index[word]=coefsf.close()print('Found%swordvectors.'%len(embeddings_index))下一步是为前面获得的单词索引中的每个单词创建单词嵌入矩阵。如果一个单词在GloVe中没有嵌入,它将呈现一个零矩阵。

embedding_matrix=np.zeros((len(word_index)+1,max_length))forword,iinword_index.items():embedding_vector=embeddings_index.get(word)ifembedding_vectorisnotNone:#wordsnotfoundinembeddingindexwillbeall-zeros.embedding_matrix[i]=embedding_vector下面是单词“shop”的单词embedding的样子。

下一步是使用上面获得的嵌入作为Keras嵌入层的权重。你还必须将该层的trainable参数设置为False,这样就不会被训练。如果再次进行训练,权重将被重新初始化。这将类似于从零开始训练单词嵌入。还有几件其他事情需要注意:

fromtensorflow.keras.layersimportEmbedding,LSTM,Dense,Bidirectionalembedding_layer=Embedding(input_dim=len(word_index)+1,output_dim=max_length,weights=[embedding_matrix],input_length=max_length,trainable=False)创建张量流模型下一步是在Keras模型中使用嵌入层。让我们将模型定义如下:

fromtensorflow.keras.modelsimportSequentialmodel=Sequential([embedding_layer,Bidirectional(LSTM(150,return_sequences=True)),Bidirectional(LSTM(150)),Dense(128,activation='relu'),Dense(1,activation='sigmoid')])训练模型下一步是编译和训练模型。

model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])当模型正在训练时,您可以设置一个EarlyStopping回调,以便在模型停止改进时停止训练过程。还可以设置TensorBoard回调,以便稍后快速查看模型的性能。

fromtensorflow.keras.callbacksimportEarlyStopping,TensorBoard%load_exttensorboardrm-rflogslog_folder='logs'callbacks=[EarlyStopping(patience=10),TensorBoard(log_dir=log_folder)]num_epochs=600history=model.fit(X_train_padded,y_train,epochs=num_epochs,validation_data=(X_test_padded,y_test),callbacks=callbacks)可以使用evaluate方法快速检查模型的性能。

从日志目录启动TensorBoard可以看到模型的性能。

您也可以使用TensorBoard的图形部分来深入可视化模型。

在本文中,您已经通过一个示例了解了如何在自然语言处理问题中使用预先训练的单词嵌入。您可以尝试通过以下方式改进这一模式:

您也可以通过在GradientCommunity笔记本中的免费GPU上运行这个示例来尝试一下。

科罗拉多州丹佛市—2016年6月21日—CoreSiteRealtyCorporation(NYSE:COR)是美国安全、可靠、高性能数据中心和互连解决方案的主要提供商,该公司今天宣布,Paperspace是一家技术公司,为其客户提供一台完整的个人电脑,该电脑位于云中,可从任何网络浏览器访问,该公司刚刚完成了CoreSite在纽约和圣克拉拉园区的数据中心部署。

Paperspace与其IT服务提供商COLOpeople合作,寻找可扩展的高性能数据中心解决方案,以满足其新推出的云计算平台的需求。基于客户对Paperspace云解决方案的强烈初始需求,Paperspace选择CoreSite来访问领先企业、云提供商和互联网对等交换,以及灵活和可扩展的数据中心解决方案。对于Paperspace来说,在由具有良好可靠性记录的提供商运营的高密度、可扩展的国家数据中心平台内进行部署至关重要。

Papersppace的联合创始人DanielKobran表示:“我们对数据中心合作伙伴的主要需求是灵活地适应我们在高密度电力环境中的快速增长,并为美国各地强大的企业客户以及网络和云服务合作伙伴提供连接。"CoreSite继续超越我们所有的标准."

Paperspace为企业提供了一种创新的解决方案,以易于使用的界面按需提供额外的处理能力。通过在CoreSite平台的东西两岸部署,Paperspace可以在CoreSite强大的客户群体中获得更广泛的受众,包括不断增长的企业需求基础。

CoreSite在纽约的园区包括位于曼哈顿和新泽西州Secaucus的数据中心设施,包括超过280,000平方英尺的互连、可靠、高性能数据中心容量。CoreSite的纽约园区托管了超过45家网络服务提供商,可直接访问一些世界领先的云服务提供商,所有这些提供商均可通过稳定、低延迟的网络访问曼哈顿,从而在支持提升客户IT性能的同时降低客户成本。

CoreSite的硅谷数据中心市场由位于圣克拉拉、圣何塞和米尔皮塔斯的运营设施组成,位于圣克拉拉的另外两个数据中心目前正在建设中。竣工后,这七处设施将提供近780,000平方英尺的数据中心容量。包括企业、国际和国内运营商、社交媒体公司、云计算提供商以及媒体和娱乐公司在内的近200家客户在CoreSite的硅谷数据中心开展业务。

CoreSite销售和营销高级副总裁史蒂夫·史密斯表示:“我们很高兴欢迎Paperspace加入我们在全国平台上不断壮大的云服务社区。“我们很高兴能与Paperspace合作,推出他们的创新云计算产品。我们期待通过可靠且可扩展的数据中心解决方案来支持他们的快速增长,同时提供无缝、高度接触的客户体验。”

CoreSiteRealtyCorporation(NYSE:COR)为北美八个主要市场中不断增长的客户生态系统提供安全、可靠、高性能的数据中心和互连解决方案。超过900家全球领先企业、网络运营商、云提供商和支持服务提供商选择CoreSite来连接、保护和优化其性能敏感型数据、应用和计算工作负载。我们可扩展、灵活的解决方案和350多名敬业的员工始终如一地提供无与伦比的数据中心选项,所有这些都带来了一流的客户体验和持久的关系。欲了解更多信息,请访问www.CoreSite.com。

Gradientexperiments中的代码查看器现在包括一个visualdiff工具,面向开发人员,用于更好地可视化实验中的代码更改。

diff视图帮助您比较Gradient中的文件、目录和版本控制项目。它提供了两个文件的双向比较,使理解代码更改更加容易。

Gradientcodediffingtool

新的比较查看器支持“并排”和“逐行”的可视化表示。使用切换按钮在两者之间切换,如下图所示:

我们经常从用户那里听到的一件事是,他们希望在使用Paperspace产品时能够更快地切换上下文。

我们邀请您使用这些新的漂亮热键在Paperspace控制台上操作:

如果您对希望实现的其他快捷方式有任何建议,请告诉我们!

一如既往,让我们知道你在Twitter上的想法,或者通过hello@paperspace.com联系我们。

在ProGANs之前建立的大多数生成网络使用独特的技术,主要涉及损失函数的修改以获得期望的结果。这些体系结构的生成器和鉴别器中的层总是同时被训练。此时,大多数生成网络都在改进其他基本特征和参数,以提高结果,但并不真正涉及渐进增长。然而,随着逐渐增长的生成性对抗网络的引入,训练程序的重点是逐渐增长网络,一次一层。

除了渐进生长网络的概念之外,本文还介绍了其他一些有意义的主题,即最小批量标准偏差、新层中的褪色、像素归一化和均衡学习速率。在我们着手实现这些概念之前,我们将在本节中更详细地探索和理解每一个概念。

由于只考虑小批量,所以小批量标准偏差鼓励生成网络在生成的图像中产生更多变化。由于只考虑小批量,鉴别器更容易区分图像的真假,迫使生成器生成更多种类的图像。这种简单的技术解决了生成网络的一个主要问题,与各自的训练数据相比,生成的图像通常变化较小。

研究论文中讨论的另一个重要概念是在新层中引入淡化。当从一个阶段过渡到另一个阶段时,即从较低分辨率切换到下一个较高分辨率时,新的层被平滑地淡入。这防止了在添加这个新层时先前的层受到突然的“冲击”。参数\(\alpha\)用于控制衰落。该参数在多次训练迭代中线性插值。如上图所示,最终公式可以写成如下形式:

disoutputoffunctionf(x,y)=x+y

现在,我们可以很容易地计算出\(f\)相对于其输入值$\frac{\partial{f}}{\partial{w_3b}}\(和\)\frac{\partial{f}}{\partial{w_4c}}$(都是1)的梯度。现在,给进入节点的边标上它们各自的渐变,如下图所示。

LocalGradients

我们对整个图形都这样做。图表看起来像这样。

BackpropagationinaComputationalGraph

接下来,我们描述计算该图中任何节点相对于损失\(L\)的导数的算法。假设我们要计算导数,$\frac{\partial{f}}{\partial{w_4}}$。

如果你看到,这个乘积就是我们用链式法则推导出的表达式。如果从L到变量有多条路径,那么我们沿着每条路径乘边,然后把它们加在一起。例如,$\frac{\partial{L}}{\partial{a}}$的计算方法如下

$$\frac{\partial{f}}{\partial{w_4}}=\frac{\partial{L}}*\frac{\partial{d}}*\frac{\partial{b}}{\partial{a}}+\frac{\partial{L}}{\partial{d}}*\frac{\partial{d}}*\frac{\partial{c}}{\partial{a}$$

现在我们知道了什么是计算图,让我们回到PyTorch并理解上面的内容是如何在PyTorch中实现的。

Tensor是一种数据结构,是PyTorch的基本构建模块。与numpy阵列非常相似,除了与numpy不同,tensors被设计为利用GPU的并行计算能力。许多张量语法类似于numpy数组的语法。

In[1]:importtorchIn[2]:tsr=torch.Tensor(3,5)In[3]:tsrOut[3]:tensor([[0.0000e+00,0.0000e+00,8.4452e-29,-1.0842e-19,1.2413e-35],[1.4013e-45,1.2416e-35,1.4013e-45,2.3331e-35,1.4013e-45],[1.0108e-36,1.4013e-45,8.3641e-37,1.4013e-45,1.0040e-36]])一个它自己的,Tensor就像一个numpyndarray。一个可以让你快速做线性代数选项的数据结构。如果您希望PyTorch创建一个对应于这些操作的图形,您必须将Tensor的requires_grad属性设置为True。

这里的API可能有点混乱。PyTorch中有多种初始化张量的方法。虽然有些方法可以让你在构造函数本身中显式定义requires_grad,但其他方法需要你在创建张量后手动设置它。

>>t1=torch.randn((3,3),requires_grad=True)>>t2=torch.FloatTensor(3,3)#Nowaytospecifyrequires_gradwhileinitiating>>t2.requires_grad=Truerequires_grad具有传染性。这意味着当一个Tensor通过操作其他Tensor而被创建时,如果至少一个用于创建的张量的requires_grad被设置为True,那么结果Tensor的requires_grad将被设置为True。

每个Tensor都有一个叫做grad_fn、、的属性,它是指创建变量的数学运算符。如果requires_grad设置为假,则grad_fn为无。

在我们的例子中,其中$d=f(w_3b,w_4c)$,d的grad函数将是加法运算符,因为f将其与输入相加。注意,加法运算符也是我们图中的节点,它输出的是d。如果我们的Tensor是一个叶节点(由用户初始化),那么grad_fn也是None。

importtorcha=torch.randn((3,3),requires_grad=True)w1=torch.randn((3,3),requires_grad=True)w2=torch.randn((3,3),requires_grad=True)w3=torch.randn((3,3),requires_grad=True)w4=torch.randn((3,3),requires_grad=True)b=w1*ac=w2*ad=w3*b+w4*cL=10-dprint("Thegradfnforais",a.grad_fn)print("Thegradfnfordis",d.grad_fn)如果您运行上面的代码,您会得到下面的输出。

ThegradfnforaisNoneThegradfnfordis可以使用成员函数is_leaf来确定变量是否是叶子Tensor。

PyTorch中的所有数学运算都由torch.nn.autograded.function类实现。这个类有两个我们需要了解的重要成员函数。

第一个是它的forward函数,它使用输入简单地计算输出。

backward函数获取来自其前方网络部分的输入梯度。如你所见,从函数f反向传播的梯度基本上是从其前面的层反向传播到f的梯度乘以f的输出相对于其输入的局部梯度。这正是backward函数的作用。

让我们再次用$$d=f(w_3b,w_4c)$$的例子来理解

从算法上来说,这是计算图的反向传播过程。(非实际执行,仅具有代表性)

defbackward(incoming_gradients): self.Tensor.grad=incoming_gradients forinpinself.inputs: ifinp.grad_fnisnotNone: new_incoming_gradients=// incoming_gradient*local_grad(self.Tensor,inp) inp.grad_fn.backward(new_incoming_gradients) else: pass这里的self.Tensor基本就是亲笔签名创造的Tensor。函数,在我们的例子中是d。

上面已经描述了输入梯度和局部梯度。

为了在我们的神经网络中计算导数,我们通常在代表我们损失的Tensor上调用backward。然后,我们从代表我们损失的grad_fn的节点开始回溯图。

如上所述,当我们回溯时,backward函数在图中被递归调用。有一次,我们到达一个叶节点,由于grad_fn都不存在,而是停止原路返回。

这里需要注意的一点是,如果你在向量值张量上调用backward(),PyTorch会给出一个错误。这意味着你只能在标量值张量上调用backward。在我们的例子中,如果我们假设a是一个向量值张量,并在L上调用backward,它将抛出一个错误。

importtorcha=torch.randn((3,3),requires_grad=True)w1=torch.randn((3,3),requires_grad=True)w2=torch.randn((3,3),requires_grad=True)w3=torch.randn((3,3),requires_grad=True)w4=torch.randn((3,3),requires_grad=True)b=w1*ac=w2*ad=w3*b+w4*cL=(10-d)L.backward()运行上面的代码片段会导致以下错误。

RuntimeError:gradcanbeimplicitlycreatedonlyforscalaroutputs这是因为根据定义,梯度可以相对于标量值来计算。你不能精确区分一个矢量和另一个矢量。用于这种情况的数学实体称为雅可比矩阵,关于它的讨论超出了本文的范围。

有两种方法可以克服这一点。

如果你只是对上面的代码设置L做一个小小的改动,使之成为所有错误的总和,我们的问题就解决了。

importtorcha=torch.randn((3,3),requires_grad=True)w1=torch.randn((3,3),requires_grad=True)w2=torch.randn((3,3),requires_grad=True)w3=torch.randn((3,3),requires_grad=True)w4=torch.randn((3,3),requires_grad=True)b=w1*ac=w2*ad=w3*b+w4*c#ReplaceL=(10-d)byL=(10-d).sum()L.backward()一旦完成,您就可以通过调用Tensor的grad属性来访问渐变。

第二种方法是,由于某种原因,必须绝对调用向量函数上的backward,你可以传递一个张量形状大小的torch.ones,你试图用它向后调用。

#ReplaceL.backward()withL.backward(torch.ones(L.shape))注意backward过去是如何将引入的渐变作为输入的。这样做使得backward认为引入的梯度只是与L大小相同的张量,并且它能够反向传播。

这样,我们可以为每个Tensor设置梯度,并且我们可以使用我们选择的优化算法来更新它们。

w1=w1-learning_rate*w1.grad诸如此类。

PyTorch创建了一个叫做的动态计算图,这意味着这个图是动态生成的。

在变量的forward函数被调用之前,图中的Tensor(是grad_fn)没有节点。

`a=torch.randn((3,3),requires_grad=True)#Nographyet,asaisaleafw1=torch.randn((3,3),requires_grad=True)#Samelogicasaboveb=w1*a#Graphwithnode`mulBackward`iscreated.`该图是调用多个张量的forward函数的结果。只有这样,为图形和中间值分配的非叶节点的缓冲区(用于以后计算梯度。当您调用backward时,随着梯度的计算,这些缓冲区(用于非叶变量)基本上被释放,并且图被破坏(在某种意义上,您不能通过它反向传播,因为保存值以计算梯度的缓冲区已经不在了)。

下一次,您将在同一个张量集上调用forward,上一次运行的叶节点缓冲区将被共享,而非叶节点缓冲区将被再次创建。****

如果在有非叶节点的图上多次调用backward,您会遇到下面的错误。

`RuntimeError:Tryingtobackwardthroughthegraphasecondtime,butthebuffershavealreadybeenfreed.Specifyretain_graph=Truewhencallingbackwardthefirsttime.`这是因为非叶子缓冲区在第一次调用backward()时被破坏,因此,当第二次调用backward时,没有路径导航到叶子。您可以通过向backward函数添加retain_graph=True参数来撤销这种非叶缓冲区破坏行为。

`loss.backward(retain_graph=True)`如果您执行上述操作,您将能够通过同一图形再次反向传播,并且梯度将被累积,即,下一次您反向传播时,梯度将被添加到先前反向传递中已存储的梯度中。

动态图范例允许您在运行时对网络架构进行更改,因为只有在运行一段代码时才会创建一个图。

这意味着一个图可以在程序的生命周期中被重新定义,因为你不必预先定义它。

然而,这对于静态图形是不可能的,在静态图形中,图形是在运行程序之前创建的,只是在以后执行。

这是Tensor类的一个属性。默认情况下,它是False。当你不得不冻结一些层,并阻止他们在训练时更新参数时,这是很方便的。您可以简单地将requires_grad设置为False,这些Tensors不会参与计算图形。

因此,没有梯度会传播到这些层,或那些依赖于这些层的梯度流requires_grad。当设置为真时,requires_grad具有传染性,即使运算的一个操作数的requires_grad设置为真,结果也是如此。

当我们计算梯度时,我们需要缓存输入值和中间要素,因为稍后可能需要它们来计算梯度。

$b=w_1*a$w.r.t其输入\(w_1\)和\(a\)的梯度分别为\(a\)和\(w_1\)。我们需要存储这些值,以便在反向过程中进行梯度计算。这会影响网络的内存占用。

当我们执行推理时,我们不计算梯度,因此,不需要存储这些值。事实上,在推理过程中不需要创建图,因为这将导致无用内存消耗。

PyTorch提供了一个上下文管理器,为此称为torch.no_grad。

`withtorch.no_grad: inferencecodegoeshere`没有为在此上下文管理器下执行的操作定义图形。

理解亲笔签名的和计算图形是如何工作的,可以让PyTorch的使用变得更加容易。我们的基础坚如磐石,接下来的帖子将详细介绍如何创建定制的复杂架构,如何创建定制的数据管道和更多有趣的东西。

读者们好。欢迎来到我们的PyTorch调试和可视化教程。至少现在,这是我们PyTorch系列的最后一部分,从对图形的基本理解开始,一直到本教程。

在本教程中,我们将介绍PyTorch钩子,以及如何使用它们来调试我们的反向传递,可视化激活和修改渐变。

在我们开始之前,让我提醒您这是我们PyTorch系列的第5部分。

PyTorch中的钩子为桌面带来的功能性严重不足。就像超级英雄的医生命运一样。没听说过他?没错。这才是重点。

我如此喜欢钩子的一个原因是它们让你在反向传播过程中做一些事情。钩子就像是许多英雄留在恶棍巢穴中获取所有信息的工具之一。

你可以在Tensor或nn.Module上登记一个钩子。钩子基本上是一个函数,当forward或backward被调用时执行。

注意,像nn.Linear一样的nn.Module有多个forward调用。它的输出由两个操作创建,(Y=W*X+B),加法和乘法,因此将有两个forward调用。这可能会把事情弄糟,并可能导致多个输出。我们将在本文后面更详细地讨论这一点。

PyTorch提供了两种类型的挂钩。

前向钩子是在向前传递的过程中执行的,而后向钩子是在调用backward函数时执行的。再次提醒你,这些是一个Autograd.Function对象的forward和backward函数。

一个钩子基本上是一个函数,有一个非常具体的签名。当我们说一个钩子被执行时,实际上,我们说的是这个函数被执行。

对于张量来说,后弯的特征是,

hook(grad)->TensororNone张量没有forward钩子。

grad基本上就是调用backward后张量的grad属性中包含的值。函数不应该修改它的参数。它必须返回None或一个张量,该张量将代替grad用于进一步的梯度计算。下面我们提供一个例子。

importtorcha=torch.ones(5)a.requires_grad=Trueb=2*ab.retain_grad()#Sincebisnon-leafandit'sgradwillbedestroyedotherwise.c=b.mean()c.backward()print(a.grad,b.grad)#Redotheexperimentbutwithahookthatmultipliesb'sgradby2\.a=torch.ones(5)a.requires_grad=Trueb=2*ab.retain_grad()b.register_hook(lambdax:print(x))b.mean().backward()print(a.grad,b.grad)如上所述,功能有多种用途。

a=torch.ones(5)a.requires_grad=Trueb=2*ab.retain_grad()b.mean().backward()print(a.grad,b.grad)b.grad*=2print(a.grad,b.grad)#a'sgradientneedstoupdatedmanually钩子为nn。模块对象对于nn.Module对象,钩子函数的签名,

hook(module,grad_input,grad_output)->TensororNone对于后弯钩,以及

hook(module,input,output)->None向前勾拳。

在我们开始之前,让我澄清一下,我不喜欢在nn.Module对象上使用钩子。首先,因为它们迫使我们打破抽象。nn.Module应该是一个表示层的模块化对象。然而,一个hook被赋予一个forward和一个backward,其中在一个nn.Module对象中可以有任意数量。这就需要我知道模块化对象的内部结构。

例如,一个nn.Linear在其执行期间包含两个forward调用。乘法和加法(y=w*****x+b)。这就是为什么钩子函数的input可以是一个包含两个不同的forward调用的输入和output前向调用的输出的元组。

grad_input是nn.Module对象w.r.t的输入对损耗的梯度(dL/dx,dL/dw,dL/b)。grad_output是nn.Module对象的输出相对于梯度的梯度。由于在一个nn.Module对象中的多次调用,这些可能会非常不明确。

考虑下面的代码。

importtorchimporttorch.nnasnnclassmyNet(nn.Module):def__init__(self):super().__init__()self.conv=nn.Conv2d(3,10,2,stride=2)self.relu=nn.ReLU()self.flatten=lambdax:x.view(-1)self.fc1=nn.Linear(160,5)defforward(self,x):x=self.relu(self.conv(x))returnself.fc1(self.flatten(x))net=myNet()defhook_fn(m,i,o):print(m)print("------------InputGrad------------")forgradini:try:print(grad.shape)exceptAttributeError:print("NonefoundforGradient")print("------------OutputGrad------------")forgradino:try:print(grad.shape)exceptAttributeError:print("NonefoundforGradient")print("\n")net.conv.register_backward_hook(hook_fn)net.fc1.register_backward_hook(hook_fn)inp=torch.randn(1,3,8,8)out=net(inp)(1-out.mean()).backward()产生的输出是。

linear令人费解。两个grad_inputs都是大小[5]但是线性层的权重矩阵不应该是160x5吗?

对于这样的混乱,我不喜欢用钩子来处理nn.Modules。你可以做像ReLU这样简单的事情,但是对于复杂的事情呢?不是我喜欢的。

所以,我完全赞成在张量上使用钩子。通过使用named_parameters函数,我已经成功地用PyTorch完成了我所有的渐变修改/裁剪需求。named_parameters允许我们更多地控制要修补的渐变。这么说吧,我想做两件事。

importtorchimporttorch.nnasnnclassmyNet(nn.Module):def__init__(self):super().__init__()self.conv=nn.Conv2d(3,10,2,stride=2)self.relu=nn.ReLU()self.flatten=lambdax:x.view(-1)self.fc1=nn.Linear(160,5)defforward(self,x):x=self.relu(self.conv(x))x.register_hook(lambdagrad:torch.clamp(grad,min=0))#Nogradientshallbebackpropagated#convoutsidelessthan0#printwhetherthereisanynegativegradx.register_hook(lambdagrad:print("Gradientslessthanzero:",bool((grad<0).any())))returnself.fc1(self.flatten(x))net=myNet()forname,paraminnet.named_parameters():#iftheparamisfromalinearandisabiasif"fc"innameand"bias"inname:param.register_hook(lambdagrad:torch.zeros(grad.shape))out=net(torch.randn(1,3,8,8))(1-out).mean().backward()print("Thebiasesare",net.fc1.bias.grad)#biasgradsarezero产生的输出是:

Gradientslessthanzero:FalseThebiasesaretensor([0.,0.,0.,0.,0.])用于可视化激活的向前挂钩如果你注意到了,Tensor没有前向钩子,而nn.Module有,当调用forward时执行。尽管我已经强调了将钩子附加到PyTorch的问题,但我已经看到许多人使用前向钩子通过将特征映射保存到钩子函数外部的python变量来保存中间特征映射。类似这样的。

visualisation={}inp=torch.randn(1,3,8,8)defhook_fn(m,i,o):visualisation[m]=onet=myNet()forname,layerinnet._modules.items():layer.register_forward_hook(hook_fn)out=net(inp)一般来说,一个nn.Module的output是最后一个forward的输出。但是,不使用钩子也可以安全地复制上述功能。只需将nn.Module对象的forward函数中的中间输出添加到一个列表中。不过,打印nn.Sequential内部模块的中间激活可能会有点问题。为了解决这个问题,我们需要将一个钩子注册到Sequential的子模块,而不是注册到Sequential本身。

importtorchimporttorch.nnasnnclassmyNet(nn.Module):def__init__(self):super().__init__()self.conv=nn.Conv2d(3,10,2,stride=2)self.relu=nn.ReLU()self.flatten=lambdax:x.view(-1)self.fc1=nn.Linear(160,5)self.seq=nn.Sequential(nn.Linear(5,3),nn.Linear(3,2))defforward(self,x):x=self.relu(self.conv(x))x=self.fc1(self.flatten(x))x=self.seq(x)net=myNet()visualisation={}defhook_fn(m,i,o):visualisation[m]=odefget_all_layers(net):forname,layerinnet._modules.items():#Ifitisasequential,don'tregisterahookonit#butrecursivelyregisterhookonallit'smodulechildrenifisinstance(layer,nn.Sequential):get_all_layers(layer)else:#it'sanonsequential.Registerahooklayer.register_forward_hook(hook_fn)get_all_layers(net)out=net(torch.randn(1,3,8,8))#Justtocheckwhetherwegotalllayersvisualisation.keys()#outputincludessequentiallayers最后,您可以将这个张量转换成numpy数组并绘制激活图。

PyTorchLightning是一个使用PyTorch进行研究的框架,它简化了我们的代码,而没有带走原始PyTorch的功能。它抽象出样板代码,并将我们的工作组织成类,例如,使数据处理和模型训练分离,否则它们会很快混合在一起,难以维护。

通过这种方式,它能够在高级项目上构建和快速迭代,并获得其他方式难以达到的结果。

结合Paperspace的易用性及其随时可用的GPU硬件,这为希望在实际项目中领先的数据科学家提供了一个绝佳的途径,而不必成为代码和基础设施的全职维护者。

PyTorchLightning是为从事人工智能研究的专业研究人员和博士生创建的

这是公平的,要从它为我们的代码提供的结构中获得全部好处,需要做真实大小的项目。反过来,这要求用户已经熟悉PyTorch、Python类和深度学习概念。

然而,它并不比那些工具更难使用,而且在某些方面更容易。因此,对于那些正在成为数据科学家的人来说,这是一条很好的路线。

Lightning将我们端到端数据科学工作流的主要部分分成几个类。这将数据准备和模型训练分离开来,使事情可以重用,而不必一行一行地检查代码。例如,在新数据集上训练模型所需的更改变得更加清晰。

Lightning中的主要类别有

LightningModule通过使用PyTorchnn.Module,使深度学习模型能够在Lightning中进行训练。这可以包括作为类中独立方法的训练、验证和测试。

DataModule允许我们将特定数据集所需的所有处理放在一个地方。在现实世界的项目中,从我们的原始数据到模型就绪的东西可能是获得结果所需的全部代码和工作的很大一部分。因此,以这种方式组织它,使数据准备不与模型混合,是非常有价值的。

然后,培训师允许我们一起使用来自上述两个类的数据集和模型,而不必编写更多的工程或样板代码。

PaperspaceGradientNotebookcreation

或者,我们也可以使用下面的梯度运行按钮,只需点击一下就可以启动笔记本电脑

在第一幅图中,数据准备和拆分成训练/验证/测试集的特定线性排列封装在一个数据模块中。每个准备步骤都有自己的方法。

OrganizationandmodularizationofdatapreparationviatheLightningDataModule(fromtheblogentryintroducingLightning)

在第二幅图中,一屏实例化模型训练循环的样板代码被简化为不到一半长度的LightningModule,训练和分类步骤依次变成类方法,而不是for和with循环。

SimplificationofmodeltrainingloopviatheLightningModule(fromtheblogentryintroducingLightning)

虽然对于简单的例子来说,开销可能会很大,但是当全尺寸真实项目的数据准备开始占用许多屏幕的代码时,这种组织以及随之而来的模块化和可重用性只会变得更有价值。

Lightning文档也有很棒的动画示例,通过典型的数据准备和模型训练过程更详细地展示了代码安排和简化。

作为一名拥有20多年经验的数据科学家,您的作者已经看到了许多产品和网站的文档。Lightning的文档和教程肯定是我用过的质量最高、最全面的。所有内容都覆盖了高质量的内容,他们“获得”了我们作为数据科学家希望看到的内容。

PyTorchLightningdocumentationatreadthedocs

这不是一个完全面向初学者的网站,因为在第一个快速入门页面上,我们直接进入了PyTorch代码、Python类和autoencoder深度学习模型。你不需要成为所有这些方面的专家,也可以了解正在发生的事情,但是有一些熟悉感是有帮助的。

话虽如此,还是有一套动手的例子和教程,它们和文档一样全面且呈现良好。标有1-13的是基于阿姆斯特丹大学深度学习课程的2020年版本。

教程1-13从介绍PyTorch开始,然后转到Lightning,所以如果你对pytorch-classes-deeplearningtrifecta不太熟悉,这些将是一个好去处。

所有的教程都是Jupyter笔记本的形式,因此可以在PaperspaceGradient上运行,不需要任何设置。

页面上的全套文档是

我们已经介绍了PyTorchLightning,并展示了如何使用它来简化和组织我们现实世界的深度学习项目。

它假设您对PyTorch、Python类和深度学习有一定的了解,但是除了Lightning之外,它还包含了帮助我们学习PyTorch的优秀文档和教程。

除了闪电本身:

损失函数是ML模型训练中的基础,并且在大多数机器学习项目中,如果没有损失函数,就没有办法驱动您的模型做出正确的预测。通俗地说,损失函数是一个数学函数或表达式,用于衡量模型在某些数据集上的表现。了解模型在特定数据集上的表现有多好,可以让开发人员在训练期间深入了解许多决策,例如使用新的、更强大的模型,甚至将损失函数本身更改为不同的类型。说到损失函数的类型,有几种损失函数是多年来发展起来的,每一种都适用于特定的训练任务。

在本文中,我们将探讨这些不同的损失函数,它们是PyTorch神经网络模块的一部分。我们将进一步深入研究PyTorch如何通过构建一个自定义的模块API向用户公开这些损失函数,作为其nn模块API的一部分。

现在我们已经对损失函数有了一个高层次的理解,让我们探索一些关于损失函数如何工作的更多技术细节。

我们之前说过,损失函数告诉我们模型在特定数据集上的表现如何。从技术上讲,它是通过测量预测值与实际值的接近程度来做到这一点的。当我们的模型在训练和测试数据集上做出非常接近实际值的预测时,这意味着我们有一个非常稳健的模型。

虽然损失函数为我们提供了关于模型性能的关键信息,但这不是损失函数的主要功能,因为有更稳健的技术来评估我们的模型,如准确性和F分数。损失函数的重要性主要是在训练过程中实现的,在训练过程中,我们将模型的权重向损失最小化的方向推动。通过这样做,我们增加了我们的模型做出正确预测的概率,如果没有损失函数,这可能是不可能的。

不同的损失函数适合不同的问题,每个损失函数都由研究人员精心制作,以确保训练期间稳定的梯度流。

有时,损失函数的数学表达式可能有点令人生畏,这导致一些开发人员将它们视为黑盒。我们稍后将揭示PyTorch的一些最常用的损失函数,但在此之前,让我们先看看在PyTorch的世界中我们是如何使用损失函数的。

PyTorch开箱即用,提供了许多规范的损失函数和简单的设计模式,允许开发人员在培训期间快速轻松地迭代这些不同的损失函数。PyTorch的所有损失函数都封装在nn模块中,nn模块是PyTorch用于所有神经网络的基类。这使得在项目中添加损失函数就像添加一行代码一样简单。让我们看看如何在PyTorch中添加均方误差损失函数。

importtorch.nnasnnMSE_loss_fn=nn.MSELoss()上述代码返回的函数可用于计算预测值与实际值之间的差距,格式如下。

#predicted_valueisthepredictionfromourneuralnetwork#targetistheactualvalueinourdataset#loss_valueisthelossbetweenthepredictedvalueandtheactualvalueLoss_value=MSE_loss_fn(predicted_value,target)现在我们已经了解了如何在PyTorch中使用损失函数,让我们深入到PyTorch提供的几个损失函数的幕后。

PyTorch附带的许多损失函数大致分为3类——回归损失、分类损失和排序损失。

回归损失主要与连续值有关,它可以取两个极限之间的任何值。这方面的一个例子是社区房价的预测。

分类损失函数处理离散值,例如将对象分类为盒子、笔或瓶子。

排名损失预测值之间的相对距离。这种情况的一个例子是面部验证,其中我们想要知道哪些面部图像属于特定的面部,并且可以通过经由它们与目标面部扫描的相对近似程度对哪些面部属于和不属于原始面部持有者进行排序来做到这一点。

L1损失函数计算预测张量中的每个值和目标张量之间的平均绝对误差。它首先计算预测张量中的每个值与目标张量中的每个值之间的绝对差,并计算从每个绝对差计算返回的所有值的总和。最后,它计算这个和值的平均值,以获得平均绝对误差(MAE)。L1损失函数对于处理噪声非常稳健。

MeanAverageErrorFormula

importtorch.nnasnn#size_averageandreducearedeprecated#reductionspecifiesthemethodofreductiontoapplytooutput.Possiblevaluesare'mean'(default)wherewecomputetheaverageoftheoutput,'sum'wheretheoutputissummedand'none'whichappliesnoreductiontooutputLoss_fn=nn.L1Loss(size_average=None,reduce=None,reduction='mean')input=torch.randn(3,5,requires_grad=True)target=torch.randn(3,5)output=loss_fn(input,target)print(output)#tensor(0.7772,grad_fn=)返回的单个值是维数为3乘5的两个张量之间的计算损失。

均方差与平均绝对误差有一些惊人的相似之处。与平均绝对误差的情况不同,它不是计算预测张量和目标张量的值之间的绝对差,而是计算预测张量和目标张量的值之间的平方差。通过这样做,相对较大的差异被罚得更多,而相对较小的差异被罚得更少。然而,在处理异常值和噪声方面,MSE被认为不如MAE稳健。

MeanSquaredErrorFormula

importtorch.nnasnnloss=nn.MSELoss(size_average=None,reduce=None,reduction='mean')#L1lossfunctionparametersexplanationapplieshere.input=torch.randn(3,5,requires_grad=True)target=torch.randn(3,5)output=loss(input,target)print(output)#tensor(0.9823,grad_fn=)交叉熵损失交叉熵损失用于涉及许多离散类的分类问题。它测量一组给定随机变量的两个概率分布之间的差异。通常,在使用交叉熵损失时,我们的网络的输出是一个Softmax层,这确保了神经网络的输出是一个概率值(0-1之间的值)。

softmax层由两部分组成-特定类的预测指数。

yi是特定类的神经网络的输出。这个函数的输出是一个接近于零的数,但如果yi大且为负,则永远不会为零,如果yi为正且非常大,则更接近于1。

importnumpyasnpnp.exp(34)#583461742527454.9np.exp(-34)#1.713908431542013e-15第二部分是归一化值,用于确保softmax层的输出始终是概率值。

这是通过将每个类值的所有指数相加得到的。softmax的最终等式如下所示:

在PyTorch的nn模块中,交叉熵损失将log-softmax和负Log-Likelihood损失组合成一个损失函数。

请注意打印输出中的梯度函数是如何成为负对数似然损失(NLL)的。这实际上揭示了交叉熵损失将遮光罩下的NLL损失与log-softmax层相结合。

NLL损失函数的工作方式与交叉熵损失函数非常相似。如前面交叉熵部分所述,交叉熵损失将log-softmax图层和NLL损失相结合,以获得交叉熵损失的值。这意味着通过使神经网络的最后一层是log-softmax层而不是正常的softmax层,可以使用NLL损失来获得交叉熵损失值。

m=nn.LogSoftmax(dim=1)loss=nn.NLLLoss()#inputisofsizeNxC=3x5input=torch.randn(3,5,requires_grad=True)#eachelementintargethastohave0<=value)#creditNLLLoss—PyTorch1.9.0documentation二元交叉熵损失二进制交叉熵损失是一类特殊的交叉熵损失,用于将数据点仅分类为两类的特殊问题。这类问题的标签通常是二进制的,因此我们的目标是推动模型来预测一个接近零的零标签数和一个接近一的一个一标签数。通常当使用BCE损失进行二进制分类时,神经网络的输出是Sigmoid层,以确保输出是接近零的值或接近一的值。

importtorch.nnasnnloss=nn.SmoothL1Loss()input=torch.randn(3,5,requires_grad=True)target=torch.randn(3,5)output=loss(input,target)print(output)#tensor(0.7838,grad_fn=)铰链嵌入损耗铰链嵌入损失主要用于半监督学习任务,以衡量两个输入之间的相似性。当有一个输入张量和一个包含值1或-1的标签张量时使用。它主要用于涉及非线性嵌入和半监督学习的问题。

importtorchimporttorch.nnasnninput=torch.randn(3,5,requires_grad=True)target=torch.randn(3,5)hinge_loss=nn.HingeEmbeddingLoss()output=hinge_loss(input,target)output.backward()print('input:',input)print('target:',target)print('output:',output)#input:tensor([[1.4668e+00,2.9302e-01,-3.5806e-01,1.8045e-01,#1.1793e+00],#[-6.9471e-05,9.4336e-01,8.8339e-01,-1.1010e+00,#1.5904e+00],#[-4.7971e-02,-2.7016e-01,1.5292e+00,-6.0295e-01,#2.3883e+00]],#requires_grad=True)#target:tensor([[-0.2386,-1.2860,-0.7707,1.2827,-0.8612],#[0.6747,0.1610,0.5223,-0.8986,0.8069],#[1.0354,0.0253,1.0896,-1.0791,-0.0834]])#output:tensor(1.2103,grad_fn=)利润排名损失差值排序损失属于排序损失,与其他损失函数不同,其主要目标是测量数据集中一组输入之间的相对距离。边际排名损失函数采用两个输入和一个仅包含1或-1的标签。如果标签为1,则假设第一输入应该具有比第二输入更高的等级,如果标签为-1,则假设第二输入应该具有比第一输入更高的等级。下面的等式和代码显示了这种关系。

importtorch.nnasnnloss=nn.MarginRankingLoss()input1=torch.randn(3,requires_grad=True)input2=torch.randn(3,requires_grad=True)target=torch.randn(3).sign()output=loss(input1,input2,target)print('input1:',input1)print('input2:',input2)print('output:',output)#input1:tensor([-1.1109,0.1187,0.9441],requires_grad=True)#input2:tensor([0.9284,-0.3707,-0.7504],requires_grad=True)#output:tensor(0.5648,grad_fn=)三重边际损失该标准通过使用训练数据样本的三元组来测量数据点之间的相似性。所涉及的三元组是锚定样本、阳性样本和阴性样本。目标是1)使正样本和锚之间的距离尽可能小,以及2)使锚和负样本之间的距离大于一个差值加上正样本和锚之间的距离。通常情况下,正样本与主播属于同一类,负样本则不是。因此,通过使用该损失函数,我们旨在使用三元组边际损失来预测锚和阳性样本之间的高相似性值以及锚和阴性样本之间的低相似性值。

importtorch.nnasnntriplet_loss=nn.TripletMarginLoss(margin=1.0,p=2)anchor=torch.randn(100,128,requires_grad=True)positive=torch.randn(100,128,requires_grad=True)negative=torch.randn(100,128,requires_grad=True)output=triplet_loss(anchor,positive,negative)print(output)#tensor(1.1151,grad_fn=)余弦嵌入损失余弦嵌入损失测量给定输入x1、x2和包含值1或-1的标签张量y的损失。它用于测量两个输入相似或不相似的程度。

importtorch.nnasnnloss=nn.KLDivLoss(size_average=None,reduce=None,reduction='mean',log_target=False)input1=torch.randn(3,6,requires_grad=True)input2=torch.randn(3,6,requires_grad=True)output=loss(input1,input2)print('output:',output)#tensor(-0.0284,grad_fn=)构建您自己的定制损失函数PyTorch为我们提供了两种流行的方法来建立我们自己的损失函数,以适应我们的问题;即使用类实现和使用函数实现。让我们从函数实现开始,看看如何实现这两种方法。

这是编写自定义损失函数最简单的方法。这就像创建一个函数一样简单,向它传递所需的输入和其他参数,使用PyTorch的核心API或函数API执行一些操作,然后返回值。让我们来看一个自定义均方误差的演示。

defcustom_mean_square_error(y_predictions,target):square_difference=torch.square(y_predictions-target)loss_value=torch.mean(square_difference)returnloss_value在上面的代码中,我们定义了一个自定义损失函数来计算给定预测张量和目标传感器的均方误差

y_predictions=torch.randn(3,5,requires_grad=True);target=torch.randn(3,5)pytorch_loss=nn.MSELoss();p_loss=pytorch_loss(y_predictions,target)loss=custom_mean_square_error(y_predictions,target)print('customloss:',loss)print('pytorchloss:',p_loss)#customloss:tensor(2.3134,grad_fn=)#pytorchloss:tensor(2.3134,grad_fn=)我们可以使用我们的自定义损失函数和PyTorch的MSE损失函数来计算损失,观察我们已经获得了相同的结果。

这种方法可能是PyTorch中定义自定义损耗的标准和推荐方法。通过对nn模块进行子类化,损失函数被创建为神经网络图中的节点。这意味着我们的自定义损失函数是PyTorch层,与卷积层完全相同。让我们来看一个演示,看看这是如何处理定制MSE损失的。

我们已经讨论了很多PyTorch中可用的损失函数,并且深入研究了这些损失函数的内部工作原理。为特定问题选择正确的损失函数可能是一项艰巨的任务。希望本教程和PyTorch官方文档可以作为理解哪种损失函数更适合您的问题的指南。

在这一部分中,我们将讨论,

在我们开始之前,让我提醒您这是我们PyTorch系列的第4部分。

PyTorch中的每个张量都有一个to()成员函数。它的工作是把调用它的张量放到某个设备上,不管是CPU还是GPU。to功能的输入是一个torch.device对象,可通过以下任一输入进行初始化。

一般来说,每当你初始化一个张量,它就被放在CPU上。然后就可以把它移到GPU上了。您可以通过调用torch.cuda.is_available函数来检查GPU是否可用。

iftorch.cuda.is_available(): dev="cuda:0"else: dev="cpu"device=torch.device(dev)a=torch.zeros(4,3)a=a.to(device)#alternatively,a.to(0)你也可以通过将张量的索引作为to函数的参数,将张量移动到某个GPU。

重要的是,上面这段代码是设备不可知的,也就是说,您不必单独更改它就可以在GPU和CPU上工作。

将张量放在GPU上的另一种方法是对它们调用cuda(n)函数,其中n是GPU的索引。如果你只是调用cuda,那么张量放在GPU0上。

torch.nn.Module类也有to和cuda功能,将整个网络放在一个特定的设备上。不像,Tensors在nn.Module对象上调用to就足够了,不需要分配to函数的返回值。

clf=myNetwork()clf.to(torch.device("cuda:0")#orclf=clf.cuda()GPU的自动选择虽然能够明确决定张量在哪个GPU上运行是件好事,但一般来说,我们在运算过程中会创建很多张量。我们希望它们在某个设备上自动创建,以减少跨设备传输,这会降低我们代码的速度。在这方面,PyTorch为我们提供了一些功能来实现这一点。

首先,是torch.get_device函数。只支持GPU张量。它返回张量所在的GPU的索引。我们可以用这个函数来确定张量的设备,这样我们就可以把一个创建的张量自动移动到这个设备上。

#makingsuret2isonthesamedeviceast2a=t1.get_device()b=torch.tensor(a.shape).to(dev)我们还可以在创建新张量的同时调用cuda(n)。默认情况下,由cuda调用创建的所有张量都放在GPU0上,但这可以通过以下语句来更改。

torch.cuda.set_device(0)#or1,2,3如果一个张量是由同一设备上的两个操作数之间的运算产生的,那么结果张量也是如此。如果操作数在不同的设备上,就会导致错误。

你也可以使用pytorch1.0版本中的一堆new_函数。当在Tensor上调用类似new_ones的函数时,它返回一个相同数据类型的新张量cof,并且与调用new_ones函数的张量在同一设备上。

ones=torch.ones((2,)).cuda(0)#Createatensorofonesofsize(3,4)onsamedeviceasof"ones"newOnes=ones.new_ones((3,4))randTensor=torch.randn(2,4)在PyTorch文档中可以找到new_函数的详细列表,我在下面提供了该文档的链接。

我们可以通过两种方式来利用多个GPU。

PyTorch中的数据并行是通过nn.DataParallel类实现的。你用一个代表你的网络的nn.Module对象和一个GPUIDs列表初始化一个nn.DataParallel对象,批处理必须通过它们被并行化。

parallel_net=nn.DataParallel(myNet,gpu_ids=[0,1,2])现在,您可以像执行nn.Module一样简单地执行nn.DataParallel对象。

predictions=parallel_net(inputs)#Forwardpassonmulti-GPUsloss=loss_function(predictions,labels)#Computelossfunctionloss.mean().backward()#AverageGPU-losses+backwardpassoptimizer.step()然而,有几件事我想说明一下。尽管我们的数据必须在多个GPU上并行处理,但我们最初必须将它存储在单个GPU上。

我们还需要确保DataParallel对象也在那个特定的GPU上。语法仍然类似于我们之前对nn.Module所做的。

input=input.to(0)parallel_net=parellel_net.to(0)实际上,下图描述了nn.DataParallel是如何工作的。

DataParallel获取输入,将其分成更小的批次,在所有设备上复制神经网络,执行传递,然后在原始GPU上收集输出。

DataParallel的一个问题是它可能会给一个GPU(主节点)带来不对称的负载。通常有两种方法来避免这些问题。

模型并行意味着将网络分成更小的子网,然后放在不同的GPU上。这样做的主要动机是,您的网络可能太大,无法容纳在单个GPU中。

请注意,模型并行性通常比数据并行性慢,因为将单个网络分成多个GPU会在GPU之间引入依赖性,从而阻止它们以真正并行的方式运行。从模型并行性中获得的优势不是速度,而是运行网络的能力,这些网络的规模太大,无法在单个GPU上运行。

正如我们在图b中看到的,子网2在正向传递期间等待子网1,而子网1在反向传递期间等待子网2。

ModelParallelismwithDependencies

只要记住两件事,实现模型并行是非常容易的。

我们将使用下面这段代码来更好地理解这一点。

classmodel_parallel(nn.Module): def__init__(self): super().__init__() self.sub_network1=... self.sub_network2=... self.sub_network1.cuda(0) self.sub_network2.cuda(1) defforward(x): x=x.cuda(0) x=self.sub_network1(x) x=x.cuda(1) x=self.sub_network2(x) returnx在init函数中,我们将子网分别放在GPUs0和1上。

注意,在forward函数中,我们将中间输出从sub_network1传送到GPU1,然后再传送给sub_network2。由于cuda有亲笔签名的支持,从sub_network2反向传播的丢失将被复制到sub_network1的缓冲区,以供进一步反向传播。

在这一节中,我们将介绍如何诊断内存问题,以及如果您的网络使用的内存超过需求时可能的解决方案。

虽然用尽内存可能需要减少批处理大小,但是可以进行某些检查来确保内存的使用是最佳的。

使用GPUtil跟踪内存使用情况

跟踪GPU使用情况的一种方法是在控制台中使用nvidia-smi命令监控内存使用情况。这种方法的问题是,GPU使用高峰和内存不足发生得如此之快,以至于您无法确定是哪部分代码导致了内存溢出。

为此,我们将使用一个名为GPUtil的扩展,您可以通过运行以下命令用pip安装它。

pipinstallGPUtil用法也很简单。

importGPUtilGPUtil.showUtilization()只需将第二行放在您想要查看GPU利用率的地方。通过将该语句放在代码的不同位置,您可以找出是哪个部分导致了网络崩溃。

现在让我们来谈谈纠正OOM错误的可能方法。

PyTorch有一个相当激进的垃圾收集器。一旦变量超出范围,垃圾收集就会释放它。

因此,即使你退出训练循环,保持你的input、output张量的张量所占用的内存仍然不能被释放。考虑下面的代码块。

forxinrange(10): i=xprint(i)#9isprinted运行上面的代码片段将打印出i的值,即使我们在初始化i的循环之外。同样,持有loss和output的张量可以活出训练圈。为了真正释放这些张量占据的空间,我们使用del关键字。

delout,loss事实上,根据一般的经验法则,如果你完成了一个张量,你应该del因为它不会被垃圾收集,除非没有对它的引用。

通常,我们在训练循环中聚合值来计算一些指标。最大的例子是我们在每次迭代中更新运行损失。但是,如果在PyTorch中不小心的话,这样的事情可能会导致内存的过度使用。

考虑下面的代码片段。

total_loss=0forxinrange(10):#assumelossiscomputediter_loss=torch.randn(3,4).mean()iter_loss.requires_grad=True#lossesaresupposedtodifferentiabletotal_loss+=iter_loss#usetotal_loss+=iter_loss.item)instead我们期望在随后的迭代中,对iter_loss的引用被重新分配给新的iter_loss,并且从早期表示中表示iter_loss的对象将被释放。但这不会发生。为什么?

由于iter_loss是可微的,所以线total_loss+=iter_loss创建了具有一个AddBackward功能节点的计算图。在随后的迭代中,AddBackward节点被添加到这个图中,并且不释放保存有iter_loss值的对象。通常,当调用backward时,分配给计算图的内存被释放,但是这里没有调用backward的范围。

Thecomputationgraphcreatedwhenyoukeepaddingthelosstensortothevariableloss

这个问题的解决方案是添加一个python数据类型,而不是添加一个张量到total_loss中,这会阻止任何计算图的创建。

我们只是用total_loss+=iter_loss.item()替换了行total_loss+=iter_loss。item从包含单个值的张量返回python数据类型。

虽然PyTorch积极地释放内存,但pytorch进程可能不会将内存归还给操作系统,即使在您完成tensors之后。此内存被缓存,以便可以快速分配给正在分配的新张量,而无需向操作系统请求新的额外内存。

当您在工作流中使用两个以上的流程时,这可能会成为一个问题。

第一个进程可以占用GPU内存,即使它的工作已经完成,当第二个进程启动时会导致OOM。要解决这个问题,您可以在代码末尾编写命令。

torch.cuda.empy_cache()这将确保进程占用的空间被释放。

importtorchfromGPUtilimportshowUtilizationasgpu_usageprint("InitialGPUUsage")gpu_usage()tensorList=[]forxinrange(10):tensorList.append(torch.randn(10000000,10).cuda())#reducethesizeoftensorifyouaregettingOOMprint("GPUUsageafterallcoatingabunchofTensors")gpu_usage()deltensorListprint("GPUUsageafterdeletingtheTensors")gpu_usage()print("GPUUsageafteremptyingthecache")torch.cuda.empty_cache()gpu_usage()在TeslaK80上执行此代码时,会产生以下输出

InitialGPUUsage|ID|GPU|MEM|------------------|0|0%|5%|GPUUsageafterallcoatingabunchofTensors|ID|GPU|MEM|------------------|0|3%|30%|GPUUsageafterdeletingtheTensors|ID|GPU|MEM|------------------|0|3%|30%|GPUUsageafteremptyingthecache|ID|GPU|MEM|------------------|0|3%|5%|使用torch.no_grad()进行推理默认情况下,PyTorch将在向前传递期间创建一个计算图形。在创建此图的过程中,它将分配缓冲区来存储梯度和中间值,这些值用于在反向传递过程中计算梯度。

在向后传递期间,除了分配给叶变量的缓冲区之外,所有这些缓冲区都被释放。

然而,在推理过程中,没有向后传递,这些缓冲区也不会被释放,导致内存堆积。因此,每当你想执行一段不需要反向传播的代码时,把它放在一个torch.no_grad()上下文管理器中。

withtorch.no_grad() #yourcode使用CuDNN后端您可以利用cudnn基准来代替普通基准。CuDNN可以提供很多优化,可以降低您的空间使用,特别是当输入到您的神经网络是固定大小。在代码顶部添加以下代码行,以启用CuDNN基准测试。

torch.backends.cudnn.benchmark=Truetorch.backends.cudnn.enabled=True使用16位浮点nVidia的新RTX和沃尔特卡支持16位训练和推理。

model=model.half()#convertamodelto16-bitinput=input.half()#convertamodelto16-bit然而,16位培训选项必须有所保留。

虽然使用16位张量可以将GPU的使用量减少近一半,但它们也存在一些问题。

model.half()#converttohalfprecisionforlayerinmodel.modules():ifisinstance(layer,nn.BatchNorm2d):layer.float()此外,您需要确保当输出通过forward函数中的不同层时,批处理规范层的输入从float16转换为float32,然后输出需要转换回float16

2.16位浮点数可能会有溢出问题。有一次,我记得在试图将两个边界框的合并区域(用于计算借据)存储在一个float16中时,出现了这样的溢出。所以要确保你有一个现实的界限来限制你试图存进float16的值。

在GPU上运行机器学习代码可以获得巨大的性能提升。但是GPU针对需要并行执行数千次相同操作的代码进行了优化。因此,我们以这种方式编写代码也很重要。

本周早些时候,我在训练一些单词嵌入。回想一下,单词嵌入是密集向量,其被认为捕获单词含义,并且如果单词在含义上相似,则两个单词嵌入之间的距离(余弦距离或欧几里德距离)应该更小。

我想通过对单词相似度数据集(如斯坦福稀有单词相似度数据集)进行评估来评估我训练的单词嵌入的质量。单词相似度数据集收集人类对单词之间距离的判断。词汇的词语相似度数据集V可以表示为|V|x|V|矩阵S,其中S[i][j]表示词语V[i]和V[j]之间的相似度。

我需要写一些Pytorch代码来计算每对嵌入之间的余弦相似度,从而产生一个单词嵌入相似度矩阵,我可以将它与的进行比较。

我们遍历嵌入矩阵E,并计算每对嵌入的余弦相似度a和b。这给了我们一个浮点数列表。然后我们使用torch.cat将每个子列表转换成一个张量,然后我们torch.stack将整个列表转换成一个单一的2D(n×n)张量。

在P6000上的快速性能测试表明,该函数从20,000个100维嵌入中计算相似性矩阵仅需3.779秒!

让我们浏览一下代码。关键的想法是,我们将余弦相似度函数分解成它的分量运算,这样我们就可以并行处理10,000次计算,而不是按顺序进行。

两个向量的余弦相似度就是它们之间夹角的余弦值:

就是这样!我们已经计算了包含每对嵌入之间的成对余弦相似性的矩阵,并从矢量化和广播中获得了巨大的性能提升!

结论下次当你想知道为什么你的机器学习代码运行缓慢时,即使是在GPU上,也要考虑对任何有圈圈的代码进行矢量化!

如果你想了解更多我们在这篇博文中提到的东西,请点击以下链接:

自然语言处理中的问题回答子域由于在用参考文档回答问题方面的广泛应用而受到欢迎。这个问题的解决方案是通过使用由输入文本、查询和包含查询答案的输入文本的文本片段或范围组成的数据集来解决的。在深度学习模型的帮助下,从数据中实现人类水平的预测有了显著的提高。

在本教程中,我们将涵盖:

Adogcamerunningatme.Ithadcuteeyes。

在F1和EM分数的帮助下完成评估。这些是使用精度和召回率计算的。精度是真阳性的数量除以真阳性和假阳性的数量。召回率是真阳性的数量除以真阳性和假阴性的数量。F1的分数是:

2*((precision*recall)/(precision+recall))EM(精确匹配)分数衡量与任何一个基本事实答案精确匹配的预测的百分比。这里的基本事实是数据集中的目标标注。

SQuAD数据集仅包含大约150,000个问题。我们将对这些方面的预训练模型进行微调,因为迁移学习被证明可以在有限的数据下提高性能。当BERT在SQuAD上接受训练时,输入问题和参考文本使用一个[sep]标记分开。概率分布用于从参考文本中确定start和end记号,其中包含答案的跨度由start和end记号的位置确定。

!pipinstalltorch!pipinstalltransformers!pipinstallsentencepiecefromtransformersimportBertTokenizer,AlbertTokenizer,AutoTokenizer,AutoModelForQuestionAnswering,BertForQuestionAnsweringimporttorchmodel_name='bert-large-uncased-whole-word-masking-finetuned-squad'model=BertForQuestionAnswering.from_pretrained(model_name)这里我们加载了标准的bert-large-uncased-whole-word-masking-finetuned-squad,大小在1.34GB左右。接下来,我们将看到相同标记的标记器如何标记我们的输入。

接下来,我将把我们的问答实现封装在一个函数中,这样我们就可以重用它来尝试这个例子中的不同模型,只需要对代码做很小的修改。

defqa(question,answer_text,model,tokenizer):inputs=tokenizer.encode_plus(question,answer_text,add_special_tokens=True,return_tensors="pt")input_ids=inputs["input_ids"].tolist()[0]text_tokens=tokenizer.convert_ids_to_tokens(input_ids)print(text_tokens)outputs=model(**inputs)answer_start_scores=outputs.start_logitsanswer_end_scores=outputs.end_logitsanswer_start=torch.argmax(answer_start_scores)#Getthemostlikelybeginningofanswerwiththeargmaxofthescoreanswer_end=torch.argmax(answer_end_scores)+1#Getthemostlikelyendofanswerwiththeargmaxofthescoreanswer=tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]))#Combinethetokensintheanswerandprintitout.""answer=answer.replace("#","")print('Answer:"'+answer+'"')returnanswer`qa(question,answer_text,model,tokenizer)`Output:Answer:"200,000tonnes"1.1班的BERT的F1和EM分数分别为91.0和84.3左右。

ELECTRA(有效学习准确分类令牌替换的编码器)是另一个提供预训练方法的模型,其中它通过新颖的方法破坏了MLM(掩蔽语言建模);被称为替换令牌检测(RTD)。该方法训练两个转换器模型:生成器和鉴别器,它们都试图胜过对方。生成器试图通过替换序列中的标记来欺骗鉴别器,而鉴别器的工作是找到生成器替换的标记。由于任务是在所有记号上定义的,而不是在被屏蔽的部分上定义的,所以这种方法更有效,并且在特征提取方面优于BERT。

脸书研究公司的BART结合了双向(如BERT)和自回归(如GPT和GPT-2)模型的能力。自回归模型从给定上下文的一组单词中预测未来单词。通过破坏一些记号或其他记号,对输入文本进行噪声处理。LM(语言模型)试图通过预测用原始标记替换损坏的标记。

tokenizer=AutoTokenizer.from_pretrained("valhalla/bart-large-finetuned-squadv1")model=AutoModelForQuestionAnswering.from_pretrained("valhalla/bart-large-finetuned-squadv1")question="UptohowmanytokensofsequencescanBARThandle"answer_text="TouseBARTforquestionansweringtasks,wefeedthecompletedocumentintotheencoderanddecoder,andusethetophiddenstateofthedecoderasarepresentationforeachword.Thisrepresentationisusedtoclassifythetoken.Asgiveninthepaperbart-largeachivescomparabletoROBERTaonSQuAD.AnothernotablethingaboutBARTisthatitcanhandlesequenceswithupto1024tokens."qa(question,answer_text,model,tokenizer)Output:Answer:"1024"通过结合两个世界的优点,即双向和自回归模型的特征,BART提供了比BERT更好的性能(尽管参数增加了10%)。在这里,BART-large的EM得分为88.8,F1得分为94.6。

fromtransformersimportBertTokenizer,BertForQuestionAnsweringmodel_name='bert-large-uncased-whole-word-masking-finetuned-squad'tokenizer=BertTokenizer.from_pretrained(model_name)model=BertForQuestionAnswering.from_pretrained(model_name)qa(question,answer_text,model,tokenizer)Output:RuntimeError:Thesizeoftensora(548)mustmatchthesizeoftensorb(512)atnon-singletondimension1我们需要另一个模型,它已经过预训练,包括一个更长的输入文档序列,也可以处理相同的架构。

对于上面的问答对,我们来看看longformer的表现如何。

在本文中,我们介绍了各种基于transformer的问答模型,以及用于预训练它们的数据集。我们还看到了每个模型在F1和EM分数方面的不同基线。这些年来体系结构的改进导致了基线性能的提高。如果我们将这种模型转换为Longformer模型,那么它对于长文档的性能仍然可以得到改善。

在本文中,我们将了解以下模块:

随机森林是一种监督机器学习分类算法。在监督学习中,算法是用带标签的数据训练的,这些数据指导你完成训练过程。使用随机森林算法的主要优势是它支持分类和回归的能力。

如前所述,随机森林使用许多决策树来给你正确的预测。人们普遍认为,由于许多树木的存在,这可能会导致过度适应。然而,这看起来并不像是一个障碍,因为只有最好的预测(投票最多的)会从可能的输出类中挑选出来,从而确保平滑、可靠和灵活的执行。现在,让我们看看随机森林是如何创建的,以及它们是如何克服决策树中存在的缺点而进化的。

有一些差异会阻碍决策树的顺利实现,包括:

为了解决这些问题,兰登森林公司来了。

大量的采油树可以通过减少在考虑单个采油树时通常出现的误差来超越单个采油树。当一棵树出错时,另一棵树可能表现良好。这是随之而来的一个额外的优势,这种组合被称为随机森林。

随机分割的数据集分布在所有树中,其中每个树集中于它已经被提供的数据。从每棵树上收集投票,选择最受欢迎的类作为最终输出,这是为了分类。在回归中,对所有输出取平均值,并将其视为最终结果。

与决策树不同,在决策树中,性能最佳的功能被作为分割节点,在随机森林中,这些功能是随机选择的。只考虑选择的特征包,并使用随机阈值来创建决策树。

RandomForest

算法是装袋的扩展。在Bagging技术中,从给定的数据集创建几个数据子集。这些数据集被分离并单独训练。在随机森林中,随着数据的划分,特征也被划分,并且不是所有的特征都用于生长树。这种技术被称为特征装袋。每棵树都有自己的一组特征。

与基于给定数据生成规则的决策树不同,随机森林分类器随机选择特征来构建几个决策树,并对观察到的结果进行平均。此外,过拟合问题是通过获取几个随机的数据子集并将它们提供给各种决策树来解决的。

然而,与随机森林相比,决策树的计算速度更快,因为它易于生成规则。在随机森林分类器中,需要考虑几个因素来解释数据点之间的模式。

随机森林分类器被用于跨越不同部门的若干应用中,如银行、医药、电子商务等。由于其分类的准确性,其使用逐年增加。

和任何机器学习算法一样,随机森林也包括两个阶段,训练和测试。一个是森林的创建,另一个是从输入模型的测试数据中预测结果。让我们看看构成伪代码主干的数学。

训练:对于中的B1,2,…B,(B为随机森林中的决策树数量)

或者在分类的情况下,收集每棵树的投票,将投票最多的类作为最终预测。

可以基于数据集的大小、交叉验证或外差来选择随机森林中的最佳树数(B)。让我们来理解这些术语。

交叉验证一般用于减少机器学习算法中的过拟合。它获取训练数据,并在k表示的多次迭代中使用各种测试数据集对其进行测试,因此得名k倍交叉验证。这可以告诉我们基于k值的树的数量。

出袋误差是在每个训练样本x_i上的平均预测误差,仅使用那些在其引导样本中没有x_i的树。这类似于留一交叉验证方法。

从这里开始,让我们了解如何使用Python中的scikit-learn库对RandomForest进行编码。

首先,测量特征的重要性可以更好地概述哪些特征实际上会影响预测。Scikit-learn提供了一个很好的特性指示器,表示所有特性的相对重要性。这是使用基尼指数或平均杂质减少量(MDI)计算的,该指数衡量使用该特征的树节点在一个森林中的所有树中减少杂质的程度。

它描述了每个特性在训练阶段所做的贡献,并对所有分数进行了缩放,使其总和为1。反过来,这有助于筛选出重要的特性,并删除那些对模型构建过程影响不大(没有影响或影响较小)的特性。仅考虑几个特征的原因是为了减少过度拟合,这通常在有大量属性时实现。

Scikit-learn提供了一些与随机森林分类器一起使用的功能或参数,以提高模型的速度和准确性。

第一步:探索数据

首先从sklearn包中的数据集库中,导入MNIST数据。

fromsklearnimportdatasetsmnist=datasets.load_digits()X=mnist.dataY=mnist.target然后通过打印数据集的数据(输入)和目标(输出)来浏览数据。

Output:[[0\.0\.5\.13\.9\.1\.0\.0\.0\.0\.13\.15\.10\.15\.5\.0\.0\.3.15\.2\.0\.11\.8\.0\.0\.4\.12\.0\.0\.8\.8\.0\.0\.5\.8\.0.0\.9\.8\.0\.0\.4\.11\.0\.1\.12\.7\.0\.0\.2\.14\.5\.10\.12.0\.0\.0\.0\.6\.13\.10\.0\.0\.0.]][0]输入有64个值,表示数据中有64个属性,输出类标签为0。为了证明这一点,观察存储数据和目标的X和y的形状。

print(mnist.data.shape)print(mnist.target.shape)Output:(1797,64)(1797,)数据集中有1797个数据行和64个属性。

第二步:数据预处理

这一步包括使用熊猫创建一个数据帧。目标和数据分别存储在y和X变量中。pd。序列用于获取int数据类型的1D数组。这些是属于类别数据的一组有限的值。pd。DataFrame将数据转换成一组表格值。head()返回数据帧的前五个值。让我们打印它们。

importpandasaspdy=pd.Series(mnist.target).astype('int').astype('category')X=pd.DataFrame(mnist.data)print(X.head())print(y.head())Output:0123456789...545556\00.00.05.013.09.01.00.00.00.00.0...0.00.00.010.00.00.012.013.05.00.00.00.00.0...0.00.00.020.00.00.04.015.012.00.00.00.00.0...5.00.00.030.00.07.015.013.01.00.00.00.08.0...9.00.00.040.00.00.01.011.00.00.00.00.00.0...0.00.00.05758596061626300.06.013.010.00.00.00.010.00.011.016.010.00.00.020.00.03.011.016.09.00.030.07.013.013.09.00.00.040.00.02.016.04.00.00.0[5rowsx64columns]0011223344dtype:categoryCategories(10,int64):[0,1,2,3,...,6,7,8,9]使用从sklearn下的model_selection包导入的train_test_split将输入(X)和输出(y)分离成训练和测试数据。test_size表示70%的数据点属于训练数据,30%属于测试数据。

fromsklearn.model_selectionimporttrain_test_splitX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3)X_train是训练数据中的输入。

X_test是测试数据中的输入。

y_train是训练数据中的输出。y_test是测试数据中的输出。

步骤3:创建分类器

使用从sklearn中的系综包中获取的RandomForestClassifier在训练数据上训练模型。n_estimators参数表示随机森林中要包含100棵树。fit()方法是通过在X_train和y_train上训练模型来拟合数据。

fromsklearn.ensembleimportRandomForestClassifierclf=RandomForestClassifier(n_estimators=100)clf.fit(X_train,y_train)使用应用于X_test数据的predict()方法预测输出。这给出了存储在y_pred中的预测值。

y_pred=clf.predict(X_test)使用从sklearn中的度量包导入的accuracy_score方法检查准确性。根据实际值(y_test)和预测值(y_pred)来估计精度。

fromsklearn.metricsimportaccuracy_scoreprint("Accuracy:",accuracy_score(y_test,y_pred))Output:Accuracy:0.9796296296296296这给出了97.96%作为训练的随机森林分类器的估计精度。的确是个好成绩!

步骤4:估计特征重要性

在前面的章节中,已经提到特征重要性是随机森林分类器的一个重要特征。现在让我们来计算一下。

feature_importances_由sklearn库提供,作为RandomForestClassifier的一部分。提取值,然后按降序排序,首先打印最重要的值。

feature_imp=pd.Series(clf.feature_importances_).sort_values(ascending=False)print(feature_imp[:10])Output:210.049284430.044338260.042334360.038272330.034299dtype:float64左栏表示属性标签,即第26个属性、第43个属性等等,右栏是表示特征重要性的数字。

步骤5:可视化特征重要性

导入库matplotlib.pyplot和seaborn来可视化上述特征重要性输出。给出输入和输出值,其中x由特征重要性值给出,y分别是64个属性中最重要的10个特征。

FeaturesvsFeatureImportanceScore

虽然随机森林可用于分类和回归,但它在以下方面优于其他算法:

一些不利因素伴随着优势,

RandomForest的简单性、多样性、健壮性和可靠性胜过其他可用的算法。通过调整超参数和选择关键特征,它为提高模型的精度提供了很大的空间。

本文从描述决策树如何经常充当绊脚石,以及随机森林分类器如何拯救开始。继续,差异和实时应用的探索。后来,通过同时探索其中的数学味道,伪代码被分解成不同的阶段。在下一节中,我们将介绍实际的编码经验。

还有很多,随机森林是解决机器学习可解决问题的一种方式。

机器理解自然语言意味着什么?有许多不同的方法试图回答这个问题。我们期望从这种机器中得到的一种能力是告诉我们一个给定句子的可能性有多大的能力。在这种方法中,我们假设一个句子的可能性可以从语言的日常使用中确定。

神经语言模型试图解决确定一个句子在现实世界中的可能性的问题。尽管从表面上看,这似乎不是世界上最令人兴奋的任务,但这种类型的建模是理解自然语言的基本构件,也是自然语言处理(NLP)中的基本任务。一旦我们有了这样的系统,我们就可以用它来生成新的文本,产生一些奇妙的结果。一旦我们用这个模型武装起来,我们也可以在其他任务中获得重要的洞察力,比如机器翻译和对话生成。

关于语言单位的说明:我们大概可以认同一个事实,一个句子可以理解为一个序列,但是序列是什么呢?人物?文字?中间的东西?这是一个活跃的研究领域,对于什么是书面语的基本单位还没有达成共识。现在,我们将简单地使用单词作为我们的语言单位。

我们现在可以开始形式化我们的想法,让我们考虑一个由\(T\)个单词组成的句子\(S\),这样

同时,每个符号\(w_i\)是包含所有可能单词的词汇表\(V\)的一部分,

其中\(|V|\)表示词汇表的大小。

或者,更明智地说,

$$p(s)=\prod_{t=1}^tp(w_t|w_{\ltt})$$

人类的大部分理解都基于上下文。我们来考虑以下顺序——巴黎是________最大的城市。用法填空很容易。这意味着在序列的前面的元素中有关于编码的最后一个字的信息。

重要的是要注意,我们可以像输入序列中的元素一样多次展开这个网络。此外,RNN单元的每个“实现”的参数是相同的,使得模型的参数数量与序列的长度无关。

值得注意的是,递归神经网络可以用于各种场景,这取决于如何输入和解释输出。这些场景可以分为三个主要的不同类别:

在这一节中,我们来看看如何使用这个架构来完成前面定义的语言建模任务。假设我们要计算句子的可能性,老板是一只叫Joe的猫。为此,我们需要估计以下概率,

我们首先要考虑的是如何定义语言建模函数的输入。很明显,输入是\(T-1\)个单词的序列,但是,这些单词中的每一个应该如何表示呢?正如在大多数深度学习系统中一样,我们希望尽可能少地用先验知识来约束模型。满足该标准的一种编码方案是1-of-K编码(one-hot-encoding)。

在这种方案下,词汇表\(V\)中的每个单词\(i\)都表示为一个二进制向量\(\mathbf{w}_i\)。为了用向量\(\mathbf{w}_i\)表示第\(i\),我们将它的第$I$-个元素设置为1,所有其他元素设置为0。

在任何两个字向量之间的距离在两个字不同的情况下等于1,而在两个字是同一个字的情况下等于0的意义上,嵌入在这种编码方案中的先验知识是最小的。

现在,我们模型的输入是一个$T-1$one-hotvectors\((\mathbf{w}_1,\mathbf{w}_2,...,\mathbf{w}_{T-1})\)。然后,将这些向量中的每一个乘以权重矩阵\(\mathbf{E}\),以获得一系列的个连续的个向量\((\mathbf{x}_1,\mathbf{x}_2,...,\mathbf{x}_{T-1})\)这样,

性能提示:实际上不执行这个矩阵向量乘法。由于vector\(\mathbf{w}_j\)只有一个元素等于1(第I\(-个元素),其余的都是零,所以乘法运算相当于只取\)\mathbf{E}$的第I$-行。因为对矩阵进行切片比执行乘法要快,所以这就是实际操作的方式。

$$p(w_t=k|w_{\ltt})=\frac{exp(\mathbf{v}_k^th_t+b_k)}{\sum_{k'}exp(\mathbf{v}_{k'}^th_t+b_{k'})}$$

我们已经有了一个架构,可以潜在地学习利用递归神经网络对序列进行评分。唯一缺少的部分是为网络定义一个适当的损失函数,以实际学习我们期望它学习的东西。

给定序列的损失是模型分配给正确输出的负对数概率。这是\(L=-log(p(w_1,w_2,...,w_T))\)。使用链式法则和乘积的对数等于对数之和的事实,对于句子\(\mathbf{x}\),我们得到,

$$L(\mathbf{x})=-\sum_tlog\p_{model}(w_t=x_{t+1})=-\sum_tlog\\mathbf{o}_t[x_{t+1}]$$

其中\(\mathbf{o}_t[x_{t+1}]\)是对应于真实单词\(x_{t+1}\)的输出softmax的元素。

定义了损失,并假定整个系统是可微的,我们可以通过所有先前的RNN单元和嵌入矩阵反向传播损失,并相应地更新其权重。

我们的模型可以被认为是两个函数(\(f\circg\))的组合。第一个函数\(f\)将前面的单词序列(在\(n-1\)个单词之前)映射到一个连续的向量空间。结果向量\(\mathbf{h}\)是内存状态或上下文向量。$$f:{0,1}^{|v|\次(n-1)}\右箭头\mathbbR^d$$

由\(g\)表征的第二阶段通过应用仿射变换(与矩阵相乘并与偏置向量相加),然后是softmax归一化(将输出转换成有效的概率分布),将连续向量\(h\)映射到目标词概率。

让我们更仔细地看看\(g\)执行的操作。我们可以忽略偏差项的影响。我们可以将矩阵向量乘法表示如下,

这意味着字典的第\(i\)个单词的模型的预测概率与\(\mathbf{U}^T\)的第\(i\)列与上下文向量\(\mathbf{h}\)的对齐程度成比例。

现在,如果我们有两个上下文单词序列,后面通常跟有一组相似的单词,那么上下文向量\(\mathbf{h}_1\)和\(\mathbf{h}_2\)必须相似。为什么?因为一旦我们把它们乘以\(\mathbf{U}^T\),我们需要相同的一组单词来获得高概率。

这最终意味着,神经语言模型必须将后面跟有相同单词的\(n\)-grams投射到上下文向量空间中的附近点。如果不满足该属性,并且两个$n\(-gram被映射到完全不同的向量,那么当将每个向量乘以\)\mathbf{U}^T$时,我们将获得下一个单词的非常不同的概率,从而导致糟糕的语言模型。

我们将把重点放在粗体短语上。每个二元模型的第一个单词是上下文单词,语言模型必须预测下一个单词的概率。

从我们之前的分析中,我们注意到模型必须将上下文单词“three”和“four”映射到上下文空间中的附近点。这是因为我们需要它们给单词“teams”(训练集的第一句和第二句)一个相似的概率。

目标单词向量\(\mathbf{u}_{teams}\)和\(\mathbf{u}_{groups}\)也必然彼此接近,因为否则“teams”给定“four”($p(\text{teams}|\text{four})\propto\mathbf{u}_{teams}\mathbf{h}_{four}\()的概率和“groups”给定“four”(\)p(\text{groups}|\text{four})\propto\mathbf{1

现在,假设我们用这个微小的语料库训练我们的语言模型,我们可以要求模型从未见过的二元组“三个组”的概率。我们的语言模型会将上下文词“三”投射到上下文空间中一个接近“四”的点上。根据该上下文向量,模型将必须向单词“groups”分配高概率,因为上下文向量\(\mathbf{h}_{three}\)和目标单词向量\(\mathbf{u}_{gropus}\)被很好地对齐。所以,即使没有见过二元模型“三组”,这种方法也能分配一个合理的概率。

这种方法的强大之处就在于此。这里的关键属性是使用单词和上下文的分布式表示,即我们使用单词和上下文的连续矢量表示。该方法基于语言的分布假设:

这种方法不仅在两个主要方面利用了语言的概率性质。一方面,它学习给定上下文的一个单词的概率,另一方面,它在上下文空间中用相似向量表示相似上下文,在单词空间中用相似向量表示相似单词。

我们来举个具体的例子。假设不只是想生成通用文本,但我们希望它有某些特征。例如,我们希望生成的句子就像是谢尔顿·库珀写的一样。一种方法是获取《生活大爆炸》的所有脚本,经谢尔顿过滤,然后根据这些数据训练我们的语言模型。

引入推荐代码

您推荐给Paperspace并使用您的唯一推荐代码注册的任何人,在添加有效的支付方式后,都会立即收到$10的点数。

收到15美元

作为帮助我们传播消息的回报,我们将为每个注册的朋友提供15美元的积分,一旦他们作为Paperspace客户被收取25美元。您可以推荐的朋友数量和获得的积分没有限制。

我该如何开始?

您可以从控制台的帐户信息页面访问您的唯一推荐代码。您可以与任何人共享此代码。

只需点击屏幕右上角的共享:

事实上,当我开始目前的工作时,那正是我发现自己的地方。有过3年左右从事计算机视觉工作的经历,突然投入到深度强化学习中。这看起来像是使用神经网络来做事情,但标准深度学习的大多数方法都不起作用。

所以让我们开始吧。

在本帖中,我们将介绍:

切换到RL时遇到的第一个差异也是最基本的一个差异是,神经网络是通过与动态环境而不是静态数据集的交互来训练的。通常你会有一个由图像/句子/音频文件组成的数据集,你会一次又一次地迭代它,以更好地完成你训练的任务。

代理人在整个剧集中累积的累积报酬被称为回报。强化学习的目标就是让这个回报最大化。解决这个问题包括解决信用分配问题。它得名于这样一个事实,即在我们可以采取的所有可能的行动中,我们必须给每一个行动分配信用(回报),以便我们可以选择产生最大回报的行动。

一个环境的例子是Atari游戏,Breakout。

在中断中,座席可以向左、向右移动滑块,也可以不做任何操作。这些是代理可以执行的动作。当一个砖块被球击中时,环境会奖励代理人。做RL的目标是训练代理,让所有砖块都被摧毁。

如果状态提供的信息足以确定给定任何动作的环境的未来状态,那么该状态被认为具有马尔可夫性质(或者在某些文献中,该状态被认为是马尔可夫)。这是因为我们在进行强化学习时处理的环境被建模为马尔可夫决策过程。我们稍后会详细介绍这些,但是现在,请理解这些环境的未来状态(对于任何操作)可以使用当前状态来确定。

想想雅达利游戏的突破。如果我们把游戏任意时刻的一张截图当做状态,是马尔科夫吗?

答案是否定的,因为根据刚才的截图,没有办法确定球的方向!考虑下面的图像。

球可能会朝两个白色箭头标记的任何一个方向运动,但我们无法判断是哪个方向。所以也许我们需要更多的信息来使状态马尔可夫化。我们把过去的4帧作为我们的状态,怎么样,这样我们至少对球的方向有个概念?

给定这4帧,我们可以计算球的速度和方向来预测未来的4帧(其中的后3帧与当前状态的前3帧相同)。

环境在本质上也可能是随机的。也就是说,假设相同的动作应用于相同的状态,结果仍然可能是不同的状态。如果所有动作的未来状态的概率分布可以仅使用当前状态和当前状态来确定,则该状态也被称为马尔可夫。当我们过一会儿看马尔可夫决策过程的数学公式时,这一点会变得更清楚。

考虑我们必须预测未来帧的情况。这里,我们必须预测球会以什么角度弹开。

当然,给定球的速度和角度,我们可以预测球反弹时的角度和速度。然而,只有当我们假设系统是理想的,球和圆盘之间的碰撞是纯弹性的,这才是正确的。

如果我们在现实生活中模拟这样的场景,碰撞的非弹性本质将违反状态的马尔可夫性质。然而,如果我们可以为状态添加更多细节,例如恢复系数(这将允许我们在计算中考虑碰撞的非弹性性质),状态将再次变为马尔可夫状态,因为我们可以精确地预测球的未来位置。谢天谢地,在突破中,碰撞是纯弹性的。

为现实生活环境构建马尔可夫状态是非常困难的。第一个大障碍是传感器总是在读数中加入一些噪音,这使得正确预测(甚至测量)环境的真实状态变得不可能。第二,有许多国家的元素可能根本不为我们所知。

考虑一个经典的强化学习任务,包括平衡一根垂直的柱子。下面是真实任务的视频。

下面的摘录摘自巴尔托和萨顿的名著《强化学习:导论》中关于马尔可夫性质的讨论,该书谈到了极点平衡实验中状态的马尔可夫性质。

在前面介绍的杆平衡任务中,如果状态信号精确地指定了小车沿轨道的位置和速度、小车和杆之间的角度以及该角度变化的速率(角速度),或者使得精确地重建该位置和速度成为可能,则该状态信号将是马尔可夫的。在理想的车-杆系统中,给定控制器采取的行动,该信息将足以准确预测车和杆的未来行为。然而,在实践中,永远不可能准确地知道这一信息,因为任何真实的传感器都会在其测量中引入一些失真和延迟。此外,在任何真实的车杆系统中,总会有其他影响,如杆的弯曲、车轮和杆轴承的温度以及各种形式的反冲,这些都会轻微影响系统的行为。如果状态信号仅仅是小车和杆子的位置和速度,这些因素将导致对马尔可夫性质的破坏。

虽然很难有具有马尔可夫性质的状态,特别是在现实生活中,但好消息是,具有近似马尔可夫性质的近似在解决强化学习任务的实践中已经足够好了。

考虑极点平衡实验的情况。一种将车的位置分为三个区域的状态——右、左和中间——并使用类似的内在状态变量的粗略量化值(如车的速度、杆的角速度等)。),对于通过RL方法解决平衡问题来说足够好。这样的状态与马尔科夫状态相去甚远,但可能会通过迫使代理忽略对解决任务毫无用处的细微差别来加速学习。

在对这种环境进行理论分析时,我们假设确实存在一种状态,通常仅称为“状态”或“真实状态”,具有我们无法观察到的马尔可夫性质。我们所能观察到的只是真实状态的一部分或更嘈杂的版本,这就叫做观察。这被称为部分可观测性,这样的环境被建模为部分可观测的马尔可夫决策过程(或简称为POMDPs)。

请注意,在如何表示状态和观察值的文献中存在一些差异。一些文献使用单词“状态”来表示具有马尔可夫属性的真实隐藏状态,而其他文献将神经网络的输入称为状态,而将具有马尔可夫属性的隐藏状态称为“真实状态”。

现在我们已经建立了对马尔可夫性质的理解,让我们正式定义马尔可夫决策过程。

几乎强化学习中的所有问题在理论上都被建模为在马尔可夫决策过程中最大化回报,或者简称为MDP。MDP有四个特点:

马尔可夫决策过程通常表示为$\mathcal{M}=\乐浪\mathcal{S}、\mathcal{A}、\mathcal{P}、\mathcal{R}\rangle$。现在让我们更详细地研究一下它们。

这组状态$\mathcal{S}\(,对应于我们在上一节中讨论的*状态*。状态可以是有限的,也可以是无限的。它们可以是离散的,也可以是连续的。\)\mathcal{S}$描述了MDP的状态可以采用的合法值,因此也被称为状态空间。

有限离散状态空间的一个例子是我们用于突围游戏的状态,即过去的4帧。该状态由4个210x160图像按通道连接而成,因此该状态是一个由4x210x160个整数组成的数组。每个像素可以取从0到255的值。因此,状态空间中可能状态的数量是$255^{4*210*160}。这是一个巨大但有限的状态空间。

CartPole-v0environmentfromOpenAIGym

该状态由4个内在状态变量组成:小车位置、速度、极点角度和尖端的极点速度。以下是状态变量的合法值。

StateSpaceoftheCartPoleEnvironmentofGym

状态空间是无限的,因为连续变量可以在边界之间取任意值。

操作空间$\mathcal{A}$描述代理可以在MDP中执行的合法操作。这些动作将环境转换到一个不同的状态,在这种转换之后,代理会得到一个奖励。

就像状态空间一样,动作空间基本上是合法动作的范围。它们既可以像突围赛的动作空间一样离散,也就是[NO-OP,FIRE,RIGHT,LEFT],让滑球什么都不做,发射球,分别向右或向左移动。连续动作空间可以是赛车游戏的空间,其中加速度是代理可以执行的动作。这个加速度可以是一个连续变量,范围从比如说$-3m/s^2美元到3m/s^2美元。

转移函数给出了状态空间上的概率,该概率描述了给定动作$a\(应用于处于状态\)s\(的环境时,环境转移到各种状态的可能性。函数\)\mathcal{P}:\mathcal{S}\times\mathcal{A}\rightarrow\mathcal{S}$给出未来状态的概率分布。

转移函数的形式赋予了MDP状态的马尔可夫性质。该函数被定义为在给定当前状态$s\(和仅应用\)a$的操作的情况下,产生未来状态$s'$的分布。如果转移函数需要状态和动作的历史,那么状态将不再具有马尔可夫属性。

因此,马尔可夫属性也用数学术语表示如下:

$$\mathbb{P}[S_{t+1}=S'\vertS_t=S,A_t=A]=\mathbb{P}[S_{t+1}=S'\vertS_t,A_t,S_{t-1},a_{t-1}....S_0,a_0]$$

也就是说,您只需要当前状态和动作来定义未来状态的分布,而不考虑代理访问的状态和采取的动作的历史。

奖励函数告诉我们,如果代理在状态$s_t\(中采取行动\)a\(将会得到什么奖励。函数\)\mathcal{R}:\mathcal{S}\times\mathcal{A}\rightarrow\mathbb{R}$为我们提供了代理在特定状态下执行特定操作的报酬。

不难扩展MDPs的理论框架来定义部分可观测的MDP,或POMDP。POMDP被定义为$\mathcal{M}=\乐浪\mathcal{S}、\mathcal{A}、\mathcal{P}、\mathcal{R}、\Omega、\mathcal{O}\rangle$。

除了\(\mathcal{S}、\mathcal{A}、\mathcal{P}、\mathcal{R}、\)我们还定义了:

正如我们所讨论的,对于游戏的突破,我们的状态是一个非常高的维度,维度为255美元^{4*210*160}。对于如此高的状态,转换函数需要输出这些状态的分布。正如您可能已经发现的那样,在当前的硬件能力下,做到这一点是很困难的,记住,每一步都需要计算转移概率。

有几种方法可以避开这个问题。

我们可以努力学习突破游戏本身的模型。这将涉及使用深度学习来学习转换函数$/mathcal{P}\(和奖励函数\)\mathcal{R}$。我们可以将状态建模为一个4x210x160维的连续空间,其中每个维度的范围从0到255,并提出一个生成模型。

当我们学习这些东西时,据说我们正在学习环境的模型。我们使用这个模型来学习一个名为策略的函数,$\pi:\mathcal{S}\times\mathcal{A}\rightarrow[0,1]\(,它基本上为我们提供了代理在经历状态\)s\(时要采取的操作\)a\(的概率分布\)\pi(s,a)$。

一般来说,当你有一个环境模型时,学习一个最优策略(一个最大化回报的策略)被称为计划。学习环境模型以达到最优策略的RL方法被归类于基于模型的强化学习。

或者,我们可能会发现底层环境太难建模,也许直接从经验中学习比之前试图学习环境的模型更好。

这与人类学习大部分任务的方式相似。汽车驾驶员实际上并没有学到控制汽车的物理定律,而是学会了根据他对汽车如何对不同动作做出反应的经验做出决定(如果你试图加速太快,汽车就会打滑等等)。

我们也可以这样训练我们的策略$\pi$。这样的任务被称为控制任务。试图直接从经验中学习控制任务的最优策略的RL方法被归类为无模型强化学习。

这部分到此为止。这部分是关于设置问题,试图给你一个我们在强化学习中处理问题的框架的感觉,首先通过直觉,然后通过数学。

在过去几年中,纸张空间云已经大规模增长。我们现在支持60多万用户,为我们的用户提供近1亿小时的GPU计算服务。

随着我们的发展,我们遇到了一些扩展障碍。我们充分意识到停机和错误是不可接受的,尤其是当我们越来越多的用户群在生产中运行时。

ConsoledatafromMay2022

在接下来的几个版本中,我们将继续在幕后进行这些改进。感谢您的支持,我们正在扩展世界上最好的云来加速计算。

PS工程

这太糟糕了,有时感觉就像回到了没有源代码控制的时候。—皮特·沃顿

作为开发人员,我们有大量的工具来对代码进行版本控制。GitHub、Docker和PyPI就是三个例子。我们使用这些服务来共享和发现应用程序的构建模块。构建块是版本化的和可部署的,这使得它们具有高度的可复制性。

但是可重用的数据呢?在本文中,我们将创建像PyPI包一样部署的可重用数据单元:

$quiltinstallakarve/BSDS300在GitHub上存储数据怎么样?如果你曾经试图在GitHub上存储数据,你可能会发现大数据是不受欢迎的。GitHub将文件限制为100MB,将存储库限制为1GB。GitHubLFS放宽了这些限制,但幅度不大。

$lsiids_test.txtiids_train.txtimages可选地,添加一个README.md文件,以便您的数据包是自文档化的:

接下来,从当前工作目录的内容构建一个数据包:

$quiltlogin$quiltpushYOUR_USERNAME/BSDS300--public现在,世界上任何人都可以精确地复制相同的数据:

quiltinstallakarve/BSDS300-xe472cf0-x参数指定了包实例的SHA-256摘要。

正如PyPI托管可重用的软件包(如pandas、numpy和torch),Quilt托管可重用的数据包。

在远程机器上安装Quilt,然后安装BSDS300包。

我们现在准备探索Python中的BSDS300:

In[1]:fromquilt.data.akarveimportBSDS300asbsdsbsds.imagesOut[1]:test/train/包是可浏览的,就像文件系统一样。bsds.images.test包含图片:

In[2]:bsds.images.testOut[2]:n101085n101087n102061n103070n105025…我们可以使用Quilt的asa=回调来显示bsds.images.test“作为一个情节”。

在引擎罩下,quilt.asa.img.plot()对每张图片做如下处理:

超分辨率成像优雅地推断出测试实例中缺失的像素值。为了让模型推断分辨率,它需要高分辨率图像的训练语料库(在我们的例子中,是BSDS300训练集)。

那么,我们如何将包中的数据放入PyTorch呢?

Quilt提供了一个高阶函数asa.pytorch.dataset(),它将打包的数据转换成一个torch.utils.data.Dataset对象:

有了Quilt管理数据,几十行样板代码就消失了。不再需要下载、解包和加载数据的一次性功能。

除了数据之外,还可以在Quilt中存储模型及其完整的修订历史。

现在我们的模型已经训练好了,我们可以重新水合epoch500并超解析测试集中的图像:

$bashinfer_super_resolution.sh500304034下面是结果。

我们已经使用Quilt打包数据,将数据部署到远程机器,然后训练PyTorch模型。

我们可以把可复制的机器学习想象成一个三变量的方程:代码+数据+模型=可复制性

通过将版本化数据和版本化模型添加到我们的工作流中,我们使开发人员能够更轻松地获得跨机器、跨合作者的一致结果。

由于杰克·范德普拉斯详述的原因,从笔记本内部安装软件包很复杂。以下是正确的做法:

importsys!{sys.executable}-mpipinstallquilt2:在特定目录下安装软件包如果你希望你的数据包存在于一个特定的目录中,例如在一个共享的驱动器上,创建一个quilt_packages目录。如果您使用PaperspaceGradient,持久化的/storage目录是数据包的理想之家。

$mkdir-p/storage/quilt_packages使用环境变量告诉Quilt在哪里搜索包:

%envQUILT_PRIMARY_PACKAGE_DIR=/storage/quilt_packages3:给我看文件Quilt对文件进行重复数据删除,将其序列化为高速格式,并将其存储在quilt_packages中的唯一标识符(文件的SHA-256哈希)下。重复数据消除的结果是,跨数据包重复的文件在磁盘上只存储一次。包含相同文件的数据包,例如foo.csv,使用对quilt_packages中foo.csv片段的单一引用。

以上所有这些都提高了性能并减少了磁盘空间,但有时您希望使用底层文件。为此,使用quilt.export:

quilt.export("akarve/BSDS300","SOME_DIRECTORY")你现在可以将你的机器学习代码指向SOME_DIRECTORY,一切都正常了。这里有一个用PyTorch模型导出图像来推断其分辨率的例子。

自摄影发明以来的近200年里,我们一直面临着同样的问题:我们如何防止损害的积累和破坏图像的质量。印在胶片及其前体介质上的照片会因暴露于自然环境和老化而受损,更不用说材料本身的脆弱性和对急性损伤的敏感性了。

虽然数码照片已经消除了很多潜在的问题,包括储存和保护,但数码照片仍然存在一种内在的模糊性,这种模糊性在胶片中是没有的。这在很大程度上是因为一张35毫米的胶片能够捕捉的信息是4k数字图像捕捉设备的几倍。

因此,这两种摄影媒介各自的缺点都有一个相似的问题:如何恢复或提升这些图像的分辨率和质量。

GFP-GAN的组成如下:

接下来,预训练的StyleGAN2模型充当生成面部先验。在GAN和DRM之间,潜在特征由几个多层感知器转换成风格向量。这些向量然后被用来产生中间卷积特征,目的是使用空间特征来进一步调制最终输出。

通道分割要素变换允许空间要素用于预测变换参数,这些变换参数可用于缩放和置换生成器中要素地图中的要素。这种情况只发生在某些通道中,因此如果模型认为没有必要更改某些特征,则允许这些特征不加更改地通过。

最后,使用所生成图像的生成器感知重建损失、对抗性损失、ID损失和面部成分损失来进一步细化所生成的图像,直到训练完成。

因为这个包是用PyTorch编写的,所以根据您的目的选择PyTorch运行时和合适的GPU。这应该在我们提供给所有用户的免费GPU上运行良好,这取决于供应商。

Anexampleofthephotorestorationinpractice.Noticehowtheeffectismorepronouncedonfaces.

一旦你的笔记本准备好了,打开笔记本“Run-GFPGAN.ipynb”

您可以使用这个笔记本来运行一个简单的演示,使用repo的创建者提供的预训练GFP-GAN模型实例。你可以运行所有现在看到提供的样本图像演示工作,但如果你想使用自己的图像:他们需要直接上传到梯度。

!pipinstall-rrequirements.txt!pipinstallopencv-python==4.5.5.64您可能还需要在终端中输入以下命令。这需要在终端本身中运行,因为在安装过程中需要肯定的“是”才能在终端中进行安装。

apt-getupdate&&apt-getinstalllibgl1下一个代码单元包含确保我们的环境可以运行GFP-GAN所需的剩余包。

!pythoninference_gfpgan.py-iinputs/whole_imgs-oresults-v1.3-s2要实际运行生成器,请运行笔记本中包含该命令的最后一个单元格。它会将您新恢复的图像直接输出到新创建的结果目录中。

AnexampleimadeusingarandomimageIfoundonReddit

本教程分解了GFP-GAN的基本架构,并演示了如何使用GFP-GAN及其堂兄包REAL-esrGAN来大幅恢复老化和损坏的照片。虽然许多人将照片修复作为一种爱好,但这可能很快会使这种努力变得更加复杂,耗时更少。

感谢您的阅读!

我们很高兴地宣布,我们正在添加NVIDIARTX卡到我们的GPU实例阵容。NVIDIA的RTX一代引入了用于3D加速的专用光线跟踪(RT)核心和用于人工智能处理的张量核心的概念。

NVIDIA对新光线追踪技术的描述:

支持RTX的GPU包括专用的光线跟踪加速硬件,使用先进的加速结构,并实施全新的GPU渲染管道,以实现游戏和其他图形应用程序中的实时光线跟踪。开发人员通过NVIDIAOptiX、使用NVIDIA光线跟踪库增强的MicrosoftDXR以及即将推出的Vulkan光线跟踪API来利用光线跟踪加速。

英伟达关于RTX人工智能功能的简介:

NVIDIANGX功能利用张量内核来最大限度地提高其运行效率,并需要支持RTX的GPU。NGX使开发人员可以通过预先训练的网络轻松地将人工智能功能集成到他们的应用程序中。

除了这些新的板载技术,RTX一代还包括新一代GPU的典型性能提升,因此将RTX加入我们的GPU阵容是显而易见的。

从今天起,我们提供RTX4000芯片,但我们也计划提供其他卡。

便携性很好,但这是一种权衡

那么Chromebooks不擅长什么呢?基本上任何比加载你的Twitterfeed需要更多能量的东西。Chromebooks是第一台挑战摩尔定律(计算能力每两年翻一番的概念)的计算机。大多数Chromebooks(也有例外)的内存约为2GB,比目前大多数智能手机的内存少2GB左右。这禁止他们做任何超过加载一个基本网页的事情——12个Chrome标签之后,你几乎不能做任何事情。在移动性和性能之间有一个真正的权衡,在现实世界中,这很重要。

一种新的模式出现了

如果你能拥有不可思议的便携性和无限的能力,那会怎么样?这是20世纪50年代大型机/瘦客户机模型的前提,也是我们称之为Paperspace的新技术的前提。瘦客户机模式失败的原因有很多,最明显的是因为这个叫做互联网的小东西不存在。如果除了直接插入超级计算机之外,你不能在任何地方使用终端,那么它对超级计算机有什么好处呢?Chromebooks和Paperspace都受益于几乎无处不在的高速互联网的普及。除了Paperspace,在可移植性和性能之间没有权衡,因为所有繁重的工作都在云中远程完成。你的Chromebook成为了一个轻量级的入口,可以根据你的需求无限扩展。

你可以做一些很酷的事情

这是一个被大多数建筑师和工程师用来设计和建模复杂建筑的程序。正如你所想象的,它需要大量的电力来运行(例如,他们推荐16GBRAM),这意味着比Chromebook多了很多的电力。在Paperspace的支持下,这款Chromebook被“改造”成了一台64GB内存的机器。它有1gb的互联网,只需点击一个按钮,它的存储就可以随时升级。查看您可以使用Paperspace做的其他一些很酷的事情:

要开始在Chromebook上运行Windows:

TensorFlow依赖于NVIDIA开发的一项名为CUDA的技术。GPU+机器包括一个支持CUDA的GPU,非常适合TensorFlow和一般的机器学习。不使用GPU(使用CPU)也可以运行TensorFlow,但是您将在下面看到使用GPU的性能优势。

在Windows上,cuDNN以zip存档的形式分发。提取它并添加Windows路径。我将它提取到C:\tools\cuda\bin并运行:

如果您还没有安装Python,Anaconda的Python3.5很容易安装。这是一个相当大的安装,所以需要几分钟。TensorFlow目前需要Python2.7、3.4或3.5。

首先,我们将为我们的项目创建一个虚拟环境:

condacreate--nametensorflow-gpupython=3.5然后激活或切换到这个虚拟环境:

activatetensorflow-gpu最后,安装带GPU支持的TensorFlow:

pipinstalltensorflow-gpu测试TensorFlow安装

python...>>>importtensorflowastf>>>hello=tf.constant('Hello,TensorFlow!')>>>sess=tf.Session()>>>print(sess.run(hello))Hello,TensorFlow!>>>a=tf.constant(10)>>>b=tf.constant(32)>>>print(sess.run(a+b))42>>>安装完成后,我们就可以运行我们的第一个模型了。

现在是有趣的部分。TensorFlow附带了一些演示模型。我们将导航到它们所在的目录,并运行一个简单的模型来对来自MNIST数据集的手写数字进行分类:

值得一提的是,我们是在强大的8核英特尔至强处理器上运行的GPU加速通常会超过这些结果。

最后,我有两种方法可以监控我的GPU使用情况:

英伟达-SMI

NVIDIA-SMI是一个内置在NVIDIA驱动程序中的工具,可以直接在命令提示符下显示GPU的使用情况。导航到它的位置并运行它。

cdC:\ProgramFiles\NVIDIACorporation\NVSMInvidia-smi.exeGPU-Z

NVIDIA-SMI和GPU-Z并行运行

就是这样。让我们知道你的想法!

资源有限

使用Parallels、Fusion和VirtualBox意味着你同时运行两个操作系统,所以你需要一个相当强大的机器,以便从你的虚拟机获得像样的性能。换句话说,如果你愿意使用Mac一半的资源,这些解决方案是很棒的。[1]

其次,大多数虚拟化软件会限制您可以分配给Windows操作系统的资源数量。借助Paperspace,您可以运行高达192GBRAM、32个vCPUs、无限存储和4GBGPU。最后,您可以在Mac上运行强大的Windows专用应用程序,如Revit、SolidWorks和3DStudioMax。

成本

接下来是您的Windows许可证和虚拟化软件本身的成本问题。Paperspace允许您按月甚至按小时支付Windows许可证和您需要的任何硬件资源。这更灵活,也更实惠。

任何设备

Paperspace的一大优势是,您的桌面可以在任何设备上使用,并且您不会被某个物理硬件所束缚。本地虚拟化并非如此,您需要找到第三方解决方案来保持文件同步,并且当您使用新设备时,您将无法访问Windows应用程序。

复杂度

最后,在本地虚拟化Windows是一件令人头疼的事情。您需要研究、购买并安装虚拟化软件的副本,找到Windows的DVD或ISO副本,购买Windows产品密钥,并完成操作系统安装过程。有了Paperspace,你不到三分钟就能开始工作。我们负责Windows许可,不需要安装任何东西,您需要的只是一个web浏览器。

TheAWSSageMakerStudioconsole

作为SageMakerStudio的一部分,AWS提供了可以在Jupyter或JupyterLab中启动的笔记本,而无需公共资源。

让我们看看SageMakerStudio笔记本的优缺点,然后看看它们与Paperspace渐变笔记本的对比。

也就是说,来自Paperspace的Gradient笔记本电脑提供了几乎相同的生产级(就笔记本电脑可以说是生产级)可靠性,但在协作、实例选择、计量定价简单性等方面更易于使用。

SageMakerStudio笔记本电脑最适合那些已经购买了SageMaker并且对笔记本电脑供应和访问有公司要求的用户。渐变笔记本更适合希望完成快速探索和协作并尽快启动和运行的团队。

亚马逊在2017年推出了SageMaker,为需要完全托管环境来完成机器学习任务的机器学习工程师提供一站式商店。

SageMakerStudioNotebookLauncher

AWS对SageMakerStudio笔记本电脑进行了如下描述:

SageMaker可惜没有宣传的那么简单。第一次开始非常困难——无论是在提供资源还是添加合作者方面。

我们建议,如果您确实在探索SageMakerStudio笔记本,请确保您的AWS权限足够大,可以创建您不可避免地需要的所有资源!

让我们来看看这两家笔记本电脑提供商的一些共同特点。对于新用户来说,最引人注目的比较点是SageMaker只需要一张信用卡和一个AWS实例就可以启动一个笔记本。

与此同时,Gradient将允许您使用自由层CPU和GPU实例,无需信用卡即可开始使用。

由于可以选择拥有公共(只读)笔记本以及可能被分叉的笔记本,因此在Gradient上与其他用户共享笔记本也更加简单。

需要注意的一点是,AWSSageMakerStudio笔记本和Gradient笔记本都有两种笔记本查看选项:笔记本的自定义IDE版本以及传统的JupyterLab笔记本选项。

这里我们有SageMaker选项:

[Left]SageMakerStudioNotebookcustomIDE,[Right]SamenotebookbutviewedinthetraditionalJupyterLabIDE

这里我们有渐变选项:

[Left]GradientNotebookscustomIDE,[Right]SamenotebookbutviewedinthetraditionalJupyterLabIDE

这是值得注意的,因为许多基于云的笔记本电脑不提供这两个选项!例如——GoogleColab和Kaggle内核因不允许完全访问JupyterLab而臭名昭著。

这两款产品都将在您需要时实现完整的JupyterLab体验,这太棒了——您永远不知道何时会需要访问JupyterLab的一些不常用功能!

AWS计量计费是出了名的复杂。SageMakerStudio笔记本没有什么不同!

同时,在选择可用实例(包括自由实例)时,Gradient提供了更多的易用性,这在任何Gradient笔记本的侧面板实例选择器中都是可能的。

Gradientoffersalargenumberofinstancesintheinstanceselectoraccessiblefromanynotebook

让我们来看一些实例定价:

Paperspace的付费实例需要订阅计划,而SageMaker机器学习笔记本不需要订阅。梯度订购层级如下:

虽然Gradient需要订阅层(和信用卡)来启用付费实例,但您始终可以免费使用免费层CPU和GPU笔记本电脑。

若要开始使用渐变笔记本:

您或您的公司很可能已经在使用AWS生态系统中的资源。不幸的是,这并没有让在AWSSageMaker上开始使用笔记本变得更加容易。

您仍然需要为您和您的队友创建一个SageMakerStudio工作区。您需要通过IAM提供实例以供您的笔记本使用,并且您需要为您希望能够访问您正在创建的笔记本资源的任何人创建IAM角色。

也就是说,一旦你在AWSSageMakerStudio中设置了笔记本,这肯定是对AWS旧笔记本功能的改进,它主要只是JupyterLab的托管版本。现在,您可以从AWS(如S3)连接其他资源,这很有用,尽管在我们的测试中,我们无法正确集成。

如果您重视易用性而不是企业级访问控制和资源管理,那么PaperspaceGradient是一个不错的选择。Gradient将帮助您和您的团队在各种资源上设置和运行——比AWS快得多,但代价是配置选项更少。

这是在一般情况下使用AWS产品时的一个常见问题——因此,请确保了解风险和好处,并做出最适合您团队当前需求的选择!

Gradient面向处于ML开发周期任何阶段的机器学习开发者。该免费计划旨在测试最先进的模型和原型新想法。当到了超越概念阶段的时候,Gradient提供了对更大的机器类型、增加的并发性、协作和其他工具的访问,使得生产模型成为可能。在成功发布免费IPUs的基础上,下一步显然是让用户在构建和扩展生产应用程序时可以利用它们。

SciSpace(原名Typeset)是一家软件公司,致力于让科学研究更具协作性和可访问性。

我们很高兴有机会与SciSpace的高级研究科学家RohanTondulkar坐在一起,深入研究他的团队正在进行的一些机器学习工作。

paperspace:SciSpace是研究人员和出版商的平台。为了帮助我们的读者更好地理解产品,你能告诉我们一些你的用户的常见用例吗?

SciSpace(formerlyTypeset)isbuildingML-assistedtoolsforresearchersandpublishers.*

Paperspace:使用科学文献的一个令人畏惧的事情是,至少可以说,语料库是……庞大的。我们真正谈论的是什么样的规模?您每天都要处理什么样的数据集?

Paperspace:你的职位是高级研究科学家——能告诉我们更多关于你的工作吗?你的团队正在应对什么样的挑战,这与公司更广泛的使命有什么关系?

作为一名高级研究科学家,我有一个更广泛的角色,涵盖端到端的ML堆栈。我的职责包括研究和试验各种问题陈述,协调团队内部的工作,建立部署管道,解决可伸缩性问题,检查模型输出的质量,并为我们的模型提供服务。

ArticlesonSciSpacereferencerelatedworks,citations,andmore.**

Paperspace:作为一家GPU云提供商,我们注意到人们对NLP用例及应用有着浓厚的兴趣。关于使用加速GPU计算来训练NLP模型,您能告诉我们些什么?

Tondulkar:自从用于机器学习用例的GPU问世以来,我们也看到了NLP领域创新的激增。近年来,随着GPU在培训和部署过程中的易用性,这一数字才开始增加。各种GPU提供商,如Paperspace、AWS和GoogleCloud等,在帮助研究人员以超快的速度训练和部署他们的模型方面发挥了重要作用。

“我们的团队需要对各种问题陈述进行实验。这涉及到使用PyTorch、TensorFlow、拥抱脸等训练大型深度学习模型。数百万个数据点。因此,我们需要一个可以扩展到多个用户和项目的平台。我们探索了许多最适合我们进行实验的选项——paperspace、GoogleColab、AWSSagemaker和Vast.ai。

我们发现,对于我们这些研究人员来说,Paperspace是最方便、最具成本效益的选择。Paperspace通过允许选择GPU/CPU机器以及按需使用GPU来提供更多控制,这有助于显著优化成本。"

RohanTondulkar,SciSpace高级研究科学家**

纸空间:纸空间是如何进入画面的?您的团队开始使用Paperspace机器有什么特别的原因吗?

Tondulkar:我们团队需要对各种问题语句进行实验。这涉及到使用PyTorch、TensorFlow、拥抱脸等训练大型深度学习模型。数百万个数据点。因此,我们需要一个可以扩展到多个用户和项目的平台。我们探索了许多最适合我们进行实验的选项——paperspace、GoogleColab、AWSSagemaker和Vast.ai。

我们发现,对于我们这些研究人员来说,Paperspace是最方便、最具成本效益的选择。Paperspace通过允许选择GPU/CPU机器以及按需使用GPU来提供更多控制,这有助于显著优化成本。

其他功能,如创建数据集和与Git集成,对我们也有好处。我们发现Paperspace中的环境稳定且易于设置。多亏了Paperspace,我们可以可靠而系统地进行这些实验。

Paperspace:在构建生产规模的ML应用程序时,你的团队遇到了哪些意想不到或意料之中的挑战?

Paperspace:如果我们的读者想看看SciSpace并参与到社区中来,你会建议他们如何开始?

TheTracefeaturesurfacesvaluableinformationaboutscholarlyarticles.**

Paperspace:还有什么你想大声喊出来的吗?

首先,我们将看看这篇论文背后的动机,然后与GhostNet(CVPR2020)进行一些微妙的比较。然后,我们将对SC-Conv的结构进行研究,并提供PyTorch代码,然后以结果和一些批评意见结束帖子。

虽然大多数研究方向都是人工设计架构或组件,如注意机制或非局部块,以丰富经典卷积神经网络的特征表示,但这是次优的,并且非常迭代。

在本文中,我们没有设计复杂的网络架构来加强特征表示,而是引入自校准卷积作为一种有效的方法,通过增加每层的基本卷积变换来帮助卷积网络学习区分表示。类似于分组卷积,它将特定层的卷积滤波器分成多个部分,但是不均匀地,每个部分内的滤波器以不同的方式被利用。具体来说,自校准卷积首先通过下采样将输入转换为低维嵌入,而不是在原始空间中均匀地对输入执行所有卷积。由一个滤波器部分变换的低维嵌入被用来校准另一部分内的滤波器的卷积变换。受益于这种异构卷积和滤波器间通信,每个空间位置的感受野可以被有效地扩大。

作为标准卷积的增强版本,我们的自校准卷积有两个优点。首先,它使每个空间位置能够自适应地编码来自长距离区域的信息上下文,打破了在小区域(例如,3×3)内卷积运算的传统。这使得由我们的自校准卷积产生的特征表示更具鉴别性。第二,所提出的自校准卷积是通用的,并且可以容易地应用于标准卷积层,而不引入任何参数和复杂度开销或者改变超参数。

上面的示意图展示了自校准回旋(SC-Conv)的结构设计。乍一看,该结构似乎是鬼卷积(CVPR2020)和常规注意机制的交叉。在深入了解SCConv之前,让我们快速浏览一下GhostNet。

好的,现在回到SCConv,在SCconv有两个不同的分支,一个是身份分支,类似于固有残差分支,而另一个分支负责自校准。给定一个输入张量\(X\in\mathbb{R}^{C\astH\astW}\)和一个相同形状的期望输出张量\(\hat{X}\),该张量首先被分成每个\(\frac{C}{2}\)通道的两个集合,第一个分支简单地对其中一个集合应用2D卷积运算。在第二个分支中,集合通过三个并行操作。第一个操作是简单的信道保持2D卷积,而在第二个操作中,空间维度以因子\(r\)下采样,然后对其应用2D卷积,其输出随后以因子\(r\)上采样以保持相同的形状。最后一个操作是一个单位函数,它按元素添加到来自第二个操作的上采样特征映射中。合成张量通过sigmoid激活,然后与从第一个张量获得的张量按元素相乘。最后,这个张量通过另一个保持2D卷积的通道。然后,将来自两个分支的两个张量连接起来,形成完整的集合。

以下是论文中展示的一些SCNets结果:

事不宜迟,让我们深入研究scSE的动机,然后分析该模块的结构设计,最后通过研究观察到的结果及其PyTorch代码进行总结。

全卷积神经网络(F-CNNs)已经为大量应用建立了图像分割的最新技术。F-CNN的架构创新主要集中在改善空间编码或网络连接以帮助梯度流。在本文中,我们探索了另一个自适应地重新校准特征图的方向,以增强有意义的特征,同时抑制弱的特征。我们从最近提出的挤压和激发(SE)模块中获得灵感,用于图像分类的特征图的通道重新校准。为此,我们介绍了用于图像分割的SE模块的三种变体,(I)空间压缩和通道方式激发(cSE),(ii)通道方式压缩和空间激发(sSE)以及(iii)并行空间和通道压缩和激发(scSE)。我们将这些se模块有效地整合到三种不同的先进F-CNN(denseNet、SD-Net、U-Net)中,并观察到所有架构的性能持续改善,同时对模型复杂性的影响最小。对两个具有挑战性的应用进行评估:MRI扫描上的全脑分割和全身对比增强CT扫描上的器官分割。

这里有三个组件需要分析,如上面摘自论文的图表所示:

本质上,这表示传统的挤压和激励模块。SE模块由3个模块组成,即挤压模块、激励模块和缩放模块。让我们假设此cSE块的输入是\mathbb{R}^{N中的四维特征映射张量$\textbf{x}\其中\(N\astC\astH\astW\)表示批量大小、通道数和空间维度(分别为各个特征映射/通道的高度和宽度),然后,挤压模块将$\textbf{x}\减少到\mathbb{R}^{N中的$\tilde{\textbf{x}}\1这样做是为了确保与在全尺寸输入张量\(\textbf{X}\)上计算注意力权重相比,计算复杂度显著降低。此外,\(\tilde{\textbf{X}}\)作为输入被传递到激励模块,该模块将这个简化的张量传递通过多层感知器(MLP)瓶颈。这将输出合成张量\(\hat{\tilde{\textbf{X}}}\)与\(\tilde{\textbf{X}}\)的维数相同。最后,缩放模块将sigmoid激活单元应用于该张量\(\hat{\tilde{\textbf{X}}}\)然后该张量按元素乘以原始输入张量\(\textbf{X}\)。

与cSE、sSE互补,本质上是一种与它的对应物相反的东西,它在空间维度上减少特征张量,在通道维度上激发。与之相反,sSE挤压通道,在空间维度上激发。类似于cSE,我们假设这个cSE块的输入是一个4维特征映射张量$\textbf{x}\in\mathbb{r}^{n\astc\asth\astw}\(。首先,输入张量\)\textbf{X}\(通过将\)C\(通道减少到1的2D逐点卷积核被减少到\mathbb{R}^{N中的\)\tilde{\textbf{X}}。最后,该压缩张量\(\tilde{\textbf{X}}\)通过sigmoid激活单元,然后与原始输入张量\(\textbf{X}\)逐元素相乘。

简单来说,scSE是之前讨论的cSE和sSE区块的合并。首先,类似于cSE和sSE,让我们假设这个cSE块的输入是一个4维特征映射张量$\textbf{x}\in\mathbb{r}^{n\astc\asth\astw}\(。这个张量\)\textbf{X}$并行通过cSE和sSE块。然后将两个结果输出逐元素相加,以提供最终输出。然而,有一些扩展,研究人员和用户都发现,计算两个张量上的元素最大值也是一种理想的策略,而不是进行元素求和,并且与作者在论文中描述的原始scSE变体相比,提供了有竞争力和可比较的结果。

下面的片段提供了用于scSE、cSE和sSE块的PyTorch代码,这些代码可以很容易地插入到计算机视觉领域中的任何传统卷积神经网络架构的块中。

scSE模块是第一个成功展示在F-CNNs模型中应用通道方式和每像素注意力算子的重要性和益处的方法。展示的结果是有希望的,但是,由于应用了sSE分支,计算量相当大。然而,这归结于用户在自己的数据集上尝试,以了解与增加的计算开销相比,该块在精度提升方面的实际效率。

我们很高兴地宣布Gradient中新的秘密管理的到来!

SecretsarenowavailableinGradientProjects!

秘密通常用于存储API密钥、S3凭证、SSH密钥或运行机器学习工作负载所需的任何其他安全文本。它们尤其适用于需要安全方法来认证项目或实验中使用的外部资源的协作团队。

一旦创建了一个秘密,只需使用语法secret:将该秘密作为环境变量注入即可。

可以在渐变控制台中或通过渐变CLI创建密码。

有关CLI用法的更多信息,请阅读文档!

在本文中,我们将简要介绍以下内容:

假设你有两个向量,每个向量都有不同的方向和大小。如果这是真实世界的物理学,你可以通过取向量之间角度的余弦来计算向量之间的相似性。在计算机科学的上下文中,向量是由整数值或浮点值的数组组成的表示。为了计算这种阵列之间的相似性,我们可以使用余弦相似性度量。

输出是范围在0和1之间的相似性分数。下面是一个示例python函数,其中有两个向量x和y作为输入,返回输入的余弦相似性得分作为结果。

importnumpyasnpdefcos_sim(x,y):"""input:Twonumpyarrays,xandyoutput:similarityscorerangebetween0and1"""" #Takingdotproductforobtainingthenumeratornumerator=np.dot(x,y) #Takingrootofsquaredsumofxandyx_normalised=np.sqrt(np.sum(x**2))y_normalised=np.sqrt(np.sum(y**2))denominator=x_normalised*y_normalisedcosine_similarity=numerator/denominatorreturncosine_similarity单词嵌入与句子嵌入自然语言处理领域从单词嵌入的出现中获益良多。在解决自然语言处理问题时,单词嵌入的使用有助于更好地理解自然语言的语境,并有助于其在各种监督和非监督任务中的使用。

单词嵌入被定义为一个单词的固定大小的向量,使得一种语言中的每个单词都可以根据自然语言空间中的语义上下文来更好地表示。这种表示允许单词嵌入用于数学计算、训练神经网络等任务。Word2Vec和Glove是两种最流行的早期单词嵌入模型。

句子转换器是一个PythonAPI,其中有来自100多种语言的句子嵌入。代码针对快速计算进行了很好的优化。API中还提供了不同的度量标准来计算和查找相似的句子,进行释义挖掘,并帮助进行语义搜索。

让我们看看如何在句子转换器API中编码句子,并使用其内置的API计算句子对之间的余弦相似度。

在我们开始之前,我们需要从pip安装这个包:

pipinstallsentence_transformers

现在让我们编码:

fromsentence_transformersimportSentenceTransformer,utilmodel=SentenceTransformer('all-MiniLM-L6-v2')#usingarelativelysmallersizemodelfromtheapi#twocolumnswithindexescorrespondingtopairscol1=["Iliketowatchtv","Thecatwasrunningafterthebutterfly"]col2=["WatchingTelevisionismyfavoritetimepass","Itissowindytoday"]#Computeencodingsforbothlistsvectors1=model.encode(col1,convert_to_tensor=True)vectors2=model.encode(col2,convert_to_tensor=True)#Computingthecosinesimilarityforeverypaircosine_scores=util.cos_sim(vectors1,vectors2)#Displaycosinesimilarityscoreforthecomputedembeddingsfori,(sent1,sent2)inenumerate(zip(col1,col2)):ifcosine_scores[i][i]>=0.5:label="Similar"else:label="NotSimilar"print("sentence1:{}|sentence2:{}|prediction:{}".format(sent1,sent2,label))输出:

sentence1:Iliketowatchtv|sentence2:WatchingTelevisionismyfavoritetimepass|prediction:Similar

sentence1:Thecatwasrunningafterthebutterfly|sentence2:Itissowindytoday|prediction:NotSimilar

到目前为止,我们已经了解了如何编写代码来计算句子相似度。但是当涉及到扩展模型或在生产中使用它时,编写笔记本风格的代码通常是不够的。如果您使用PyTorch,您将处理一个训练循环、一个验证循环、一个测试循环、优化器和其他配置。Pytorch-lightning通过简单地提供一个框架,可以轻松地以一种可伸缩、易于使用的方式包装所有这些模块,为您省去所有这些麻烦。

让我们熟悉一些在lightning模块上使用的概念和工具包,以进一步理解如何处理我们的文本数据。

Datamodule从云/本地存储中获取数据,应用其他预处理/转换,如清理、标记化等,并将其包装在DataLoader对象中。这有助于为数据创建有组织的管理,而不是读取和预处理分布在几个文件或位置的数据。Dataloader也有助于将数据分割成列车测试和验证。

Trainer通过培训所需的必要功能,帮助您实现管道自动化。这意味着处理整个训练循环,管理超参数,加载模型和数据加载器,处理批处理和回调,根据给定的测试数据进行预测,最后保存模型检查点。培训师为您抽象出所有这些方面,无需任何额外的PyTorch代码。

Flash是PyTorchLightning团队提供给你的一个子项目,作为你大部分机器学习问题的一站式工具包。Flash将其任务包装在一个lightning模块中,适当使用Trainer和Datamodule来利用PyTorch必须提供的每一个功能。可以用这种方式分析的一些流行的领域和任务包括音频、文本、图像、图形、结构化数据等。

让我们看一个例子,看看lightning-flash如何帮助我们计算句子嵌入,并尝试解决句子相似性任务,而实际上不需要对下游任务进行微调,并使用无监督的方法。

在本文中,我们已经介绍了句子转换器的基础知识以及使用句子转换器解决句子相似性问题。对于这样的任务,我们还评估了句子嵌入相对于单词嵌入的优势。通过查看pytorch-lightning实现句子转换器的示例,我们学会了为生产就绪的应用程序扩展代码,现在我们可以通过避免样板代码来简化编写pytorch训练循环所需的管道。

在本教程中,我们将介绍RNNs中的注意机制:它们如何工作,网络架构,它们的应用,以及如何使用Keras实现注意机制。

具体来说,我们将涵盖:

注意:本系列中的所有例子(高级RNNs)都是在TensorFlow2.x上训练的。

机器翻译问题促使我们发明了“注意力机制”。机器翻译是从一种语言到另一种语言的自动转换。这种转换必须使用计算机程序进行,该程序必须具有将文本从一种语言转换成另一种语言的智能。当神经网络执行这项工作时,它被称为“神经机器翻译”。

由于人类语言的模糊性和复杂性,机器翻译是人工智能中最具挑战性的问题之一。

尽管很复杂,我们已经看到了许多解决这个问题的方法。

神经网络在设计机器翻译过程自动化的方法中发挥了至关重要的作用。第一个适合这种应用的神经网络是序列对序列模型。

有趣的是,这就是通常的翻译过程。

Asequence-to-sequencemodelwith‘c’astheencoded(context)vector

编码器-解码器序列到序列模型本身类似于当前的翻译过程。它包括将源语言编码成合适的表示形式,然后将其解码成目标语言,其中输入和输出向量不必大小相同。然而,这种模式也存在一些问题:

这正是注意力机制所做的!

按照典型的英语词汇,“注意力”指的是将你的注意力集中在某件事情上。如果我们考虑神经机器翻译的例子,你认为“注意力”在哪里?

注意机制旨在解决我们在用序列到序列模型训练神经机器翻译模型时讨论的两个问题。首先,当存在注意力整合时,该模型不需要将编码输出压缩到单个上下文向量中。相反,它将输入序列编码成向量序列,并根据解码器的隐藏状态选择这些向量的子集。简而言之,注意力被用来选择什么是必要的,而不会放过其他必要的信息。

这就是带有注意力机制的RNN模型的样子:

AnRNNModelwith“AttentionMechanism”

下面是如何使用注意力机制解决机器翻译问题的一步一步的过程:

我们可以将注意机制大致分为三类:自我注意、软注意和硬注意机制。

这里的注意力是在相同的序列内计算的。换句话说,自我注意使输入元素能够相互作用。

软注意“柔和地”将注意力权重放在输入(图像/句子)的所有块上,即,它采用加权平均机制。它测量关于输入的各种组块的注意力,并输出加权的输入特征。它通过给与手头任务无关的区域分配较低的权重来抹黑它们。这样,软注意就不会把注意力局限在图像或句子的特定部分;而是不断学习。

软注意是一种完全可微分的注意机制,其中梯度可以在反向传播期间自动传播。

“硬”,顾名思义,只针对图像/句子的特定部分。在反向传播过程中,为了估计所有其他状态的梯度,我们需要使用蒙特卡罗方法进行采样并对结果进行平均。

注意力机制的几个应用包括:

RNN可以用来实现机器翻译。一般来说,一个简单的RNN加上一个编码器-解码器序列到序列模型就可以完成这项工作。然而,如章节“神经机器翻译的序列到序列模型的问题中所述,编码输出的压缩表示可能会忽略翻译过程所需的重要特征。为了根除这个问题,当我们用编码器-解码器序列到序列模型嵌入注意机制时,当涉及长的文本序列时,我们不必在信息损失上妥协。

注意力机制集中在所有的输入上,这些输入是产生输出所真正需要的。这里不涉及压缩。相反,它会考虑编码器的所有输出,并根据解码器的隐藏状态为它们分配重要性。

下面是一个使用RNN模型(带注意机制的编码器-解码器序列到序列)进行法语到英语翻译的逐步过程。

解包数据集并存储txt文件路径。

#Untarthedataset!unzip'fra-eng.zip'#GetthetxtfilewhichhasEnglish->Frenchtranslationpath_to_file="fra.txt"步骤2:预处理数据集数据集包含Unicode字符,必须对其进行规范化。

此外,必须使用正则表达式库清理序列中的所有标记。

删除不需要的空格,在每个单词和其后的标点符号之间包含一个空格(以区分两者),用空格替换不需要的字符,并添加标记来指定序列的开始和结束。

将unicode转换封装在函数unicode_to_ascii()中,将序列预处理封装在函数preprocess_sentence()中。

importio#CreatetheDatasetdefcreate_dataset(path,num_examples):lines=io.open(path,encoding='UTF-8').read().strip().split('\n')#Loopthroughlines(sequences)andextracttheEnglishandFrenchsequences.Storethemasaword-pairword_pairs=[[preprocess_sentence(w)forwinl.split('\t',2)[:-1]]forlinlines[:num_examples]]returnzip(*word_pairs)检查数据集是否已正确创建。

en,fra=create_dataset(path_to_file,None)print(en[-1])print(fra[-1])#Outputifsomeonewhodoesntknowyourbackgroundsaysthatyousoundlikeanativespeaker,itmeanstheyprobablynoticedsomethingaboutyourspeakingthatmadethemrealizeyouwerentanativespeaker.inotherwords,youdontreallysoundlikeanativespeaker.siquelquunquineconnaitpasvosantecedentsditquevousparlezcommeunlocuteurnatif,celaveutdirequilaprobablementremarquequelquechoseaproposdevotreelocutionquiluiafaitprendreconsciencequevousnetespasunlocuteurnatif.endautrestermes,vousneparlezpasvraimentcommeunlocuteurnatif.现在标记序列。记号化是创建包括英语和法语记号(即单词)的内部词汇表、将记号(或者一般来说,序列)转换成整数、并填充它们以使序列具有相同长度的机制。总而言之,标记化促进了模型训练过程。

创建一个函数tokenize()来封装上述所有需求。

importtensorflowastf#Convertsequencestotokenizersdeftokenize(lang):lang_tokenizer=tf.keras.preprocessing.text.Tokenizer(filters='')#Convertsequencesintointernalvocablang_tokenizer.fit_on_texts(lang)#Convertinternalvocabtonumberstensor=lang_tokenizer.texts_to_sequences(lang)#Padthetensorstoassignequallengthtoallthesequencestensor=tf.keras.preprocessing.sequence.pad_sequences(tensor,padding='post')returntensor,lang_tokenizer通过调用create_dataset()和tokenize()函数加载标记化的数据集。

#Consider50kexamplesnum_examples=50000input_tensor,target_tensor,inp_lang,targ_lang=load_dataset(path_to_file,num_examples)#Calculatemax_lengthofthetargettensorsmax_length_targ,max_length_inp=target_tensor.shape[1],input_tensor.shape[1]输入和目标张量的max_length对于确定每个序列的最大填充长度至关重要。

分离训练和验证数据集。

!pip3installsklearnfromsklearn.model_selectionimporttrain_test_split#Createtrainingandvalidationsetsusingan80/20splitinput_tensor_train,input_tensor_val,target_tensor_train,target_tensor_val=train_test_split(input_tensor,target_tensor,test_size=0.2)print(len(input_tensor_train),len(target_tensor_train),len(input_tensor_val),len(target_tensor_val))#Output40000400001000010000验证在序列的标记和索引之间创建的映射。

#Showthemappingb/wwordindexandlanguagetokenizerdefconvert(lang,tensor):fortintensor:ift!=0:print("%d---->%s"%(t,lang.index_word[t]))print("InputLanguage;indextowordmapping")convert(inp_lang,input_tensor_train[0])print()print("TargetLanguage;indextowordmapping")convert(targ_lang,target_tensor_train[0])#OutputInputLanguage;indextowordmapping1---->140---->quel408---->idiot3---->.2---->TargetLanguage;indextowordmapping1---->33---->what86---->an661---->idiot36---->!2---->步骤5:初始化模型参数有了数据集,开始初始化模型参数。

#EssentialmodelparametersBUFFER_SIZE=len(input_tensor_train)BATCH_SIZE=64steps_per_epoch=len(input_tensor_train)//BATCH_SIZEembedding_dim=256units=1024vocab_inp_size=len(inp_lang.word_index)+1vocab_tar_size=len(targ_lang.word_index)+1接下来,调用tf.data.DatasetAPI并创建一个合适的数据集。

dataset=tf.data.Dataset.from_tensor_slices((input_tensor_train,target_tensor_train)).shuffle(BUFFER_SIZE)dataset=dataset.batch(BATCH_SIZE,drop_remainder=True)验证新创建的数据集的输入批次和目标批次的形状。

#Sizeofinputandtargetbatchesexample_input_batch,example_target_batch=next(iter(dataset))example_input_batch.shape,example_target_batch.shape#Output(TensorShape([64,19]),TensorShape([64,11]))19和11表示输入(法语)和目标(英语)序列的最大填充长度。

创建编码器-解码器序列到序列模型(具有注意机制)的第一步是创建编码器。对于手头的应用程序,创建一个编码器,其嵌入层后跟一个GRU(门控递归单元)层。输入首先通过嵌入层,然后进入GRU层。GRU层输出编码器网络输出和隐藏状态。

将模型的__init__()和call()方法放在一个类Encoder中。

在方法__init__()中,初始化批量大小和编码单位。添加一个嵌入层,接受vocab_size作为输入维度,接受embedding_dim作为输出维度。另外,添加一个接受units(输出空间的维度)和第一个隐藏维度的GRU层。

在方法call()中,定义必须通过编码器网络发生的正向传播。

此外,定义一个方法initialize_hidden_state(),用维度batch_size和units初始化隐藏状态。

添加以下代码作为您的Encoder类的一部分。

#EncoderclassclassEncoder(tf.keras.Model):def__init__(self,vocab_size,embedding_dim,enc_units,batch_sz):super(Encoder,self).__init__()self.batch_sz=batch_szself.enc_units=enc_units#Embedthevocabtoadenseembeddingself.embedding=tf.keras.layers.Embedding(vocab_size,embedding_dim)#GRULayer#glorot_uniform:Initializerfortherecurrent_kernelweightsmatrix,#usedforthelineartransformationoftherecurrentstateself.gru=tf.keras.layers.GRU(self.enc_units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')#EncodernetworkcomprisesanEmbeddinglayerfollowedbyaGRUlayerdefcall(self,x,hidden):x=self.embedding(x)output,state=self.gru(x,initial_state=hidden)returnoutput,state#Toinitializethehiddenstatedefinitialize_hidden_state(self):returntf.zeros((self.batch_sz,self.enc_units))调用encoder类来检查编码器输出的形状和隐藏状态。

encoder=Encoder(vocab_inp_size,embedding_dim,units,BATCH_SIZE)sample_hidden=encoder.initialize_hidden_state()sample_output,sample_hidden=encoder(example_input_batch,sample_hidden)print('Encoderoutputshape:(batchsize,sequencelength,units){}'.format(sample_output.shape))print('EncoderHiddenstateshape:(batchsize,units){}'.format(sample_hidden.shape))#OutputEncoderoutputshape:(batchsize,sequencelength,units)(64,19,1024)EncoderHiddenstateshape:(batchsize,units)(64,1024)第七步:注意机制类这一步抓住了注意力机制。

到目前为止,所有的东西都需要被捕获到一个类BahdanauAttention中。Bahdanau注意力也被称为“加法注意力”,一种软注意力技术。由于这是附加注意,我们做编码器的输出和解码器隐藏状态的总和(如第一步所述)。

这个类必须有__init__()和call()方法。

在__init__()方法中,初始化三个Dense层:一个用于解码器状态(“单位”是大小),另一个用于编码器的输出(“单位”是大小),另一个用于全连接网络(一个节点)。

在call()方法中,通过获取最终的编码器隐藏状态来初始化解码器状态(\(s_0\))。将生成的解码器隐藏状态通过一个密集层。此外,通过另一个密集层插入编码器的输出。将两个输出相加,将它们封装在一个tanh激活中,并将它们插入全连接层。这个全连接层有一个节点;因此,最终输出的尺寸为batch_size*max_lengthofthesequence*1。

稍后,对全连接网络的输出应用softmax以生成注意力权重。

通过对注意力权重和编码器的输出进行加权求和来计算context_vector。

attention_layer=BahdanauAttention(10)attention_result,attention_weights=attention_layer(sample_hidden,sample_output)print("Attentionresultshape:(batchsize,units){}".format(attention_result.shape))print("Attentionweightsshape:(batch_size,sequence_length,1){}".format(attention_weights.shape))#OutputAttentionresultshape:(batchsize,units)(64,1024)Attentionweightsshape:(batch_size,sequence_length,1)(64,19,1)sample_hidden这里是编码器的隐藏状态,sample_output表示编码器的输出。

这一步封装了解码机制。这个Decoder类必须有两个方法:__init__()和call()。

在__init__()方法中,初始化批量大小、解码器单元、嵌入维度、GRU层和密集层。另外,创建一个BahdanauAttention类的实例。

在call()方法中:

添加以下代码来定义Decoder类。

decoder=Decoder(vocab_tar_size,embedding_dim,units,BATCH_SIZE)sample_decoder_output,_,_=decoder(tf.random.uniform((BATCH_SIZE,1)),sample_hidden,sample_output)print('Decoderoutputshape:(batch_size,vocabsize){}'.format(sample_decoder_output.shape))#OutputDecoderoutputshape:(batch_size,vocabsize)(64,5892)步骤9:优化器和损失函数定义优化器和损失函数。

由于输入序列是用零填充的,所以当real值为零时,可以消除损失。

#Initializeoptimizerandlossfunctionsoptimizer=tf.keras.optimizers.Adam()loss_object=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True,reduction='none')#Lossfunctiondefloss_function(real,pred):#Takecareofthepadding.Notallsequencesareofequallength.#Ifthere'sa'0'inthesequence,thelossisbeingnullifiedmask=tf.math.logical_not(tf.math.equal(real,0))loss_=loss_object(real,pred)mask=tf.cast(mask,dtype=loss_.dtype)loss_*=maskreturntf.reduce_mean(loss_)步骤10:训练模型在训练期间检查你的模型的重量。这有助于在评估模型时自动检索权重。

importoscheckpoint_dir='./training_checkpoints'checkpoint_prefix=os.path.join(checkpoint_dir,"ckpt")checkpoint=tf.train.Checkpoint(optimizer=optimizer,encoder=encoder,decoder=decoder)接下来,定义培训程序。首先,调用编码器类并获取编码器输出和最终隐藏状态。初始化解码器输入,使令牌分布在所有输入序列上(使用BATCH_SIZE指示)。使用教师强制技术通过将目标作为下一个输入来迭代所有解码器状态。这个循环一直持续到目标序列(英语)中的每个标记都被访问。

用解码器输入、解码器隐藏状态和编码器输出调用解码器类。获取解码器输出和隐藏状态。通过比较目标的实际值和预测值来计算损失。获取目标令牌并将其馈送到下一个解码器状态(关于后续目标令牌)。此外,请注意,目标解码器隐藏状态将是下一个解码器隐藏状态。

教师强制技术完成后,计算批量损失,并运行优化器来更新模型的变量。

用Y轴上的max_length_target和X轴上的max_length_input初始化稍后要绘制的空白注意力图。

对句子进行预处理,转换成张量。

然后把句子代入模型。

初始化一个空的隐藏状态,该状态将在初始化编码器时使用。通常,编码器类中的initialize_hidden_state()方法给出了维度为batch_size*hidden_units的隐藏状态。现在,由于批处理大小为\(1\),初始隐藏状态必须手动初始化。

调用编码器类并获得编码器输出和最终隐藏状态。

通过循环遍历max_length_targ,调用解码器类,其中dec_input是令牌,dec_hidden状态是编码器隐藏状态,enc_out是编码器的输出。获取解码器输出、隐藏状态和注意力权重。

下一个解码器输入将是先前预测的索引(关于令牌)。

添加以下代码作为evaluate()函数的一部分。

importmatplotlib.pyplotaspltimportmatplotlib.tickerasticker#Functionforplottingtheattentionweightsdefplot_attention(attention,sentence,predicted_sentence):fig=plt.figure(figsize=(10,10))ax=fig.add_subplot(1,1,1)ax.matshow(attention,cmap='viridis')fontdict={'fontsize':14}ax.set_xticklabels(['']+sentence,fontdict=fontdict,rotation=90)ax.set_yticklabels(['']+predicted_sentence,fontdict=fontdict)ax.xaxis.set_major_locator(ticker.MultipleLocator(1))ax.yaxis.set_major_locator(ticker.MultipleLocator(1))plt.show()定义一个函数translate(),它在内部调用evaluate()函数。

#Translatefunction(whichinternallycallstheevaluatefunction)deftranslate(sentence):result,sentence,attention_plot=evaluate(sentence)print('Input:%s'%(sentence))print('Predictedtranslation:{}'.format(result))attention_plot=attention_plot[:len(result.split('')),:len(sentence.split(''))]plot_attention(attention_plot,sentence.split(''),result.split(''))将保存的检查点恢复到model。

#Restorethelatestcheckpointincheckpoint_dircheckpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))#Output通过输入几个法语句子来调用translate()函数。

实际翻译过来就是“你读过这本书吗?”

实际的翻译是“你过得怎么样?”

可以推断,预测的翻译接近实际翻译。

这是我的RNNs系列的最后一部分。在本教程中,你已经了解了注意力机制是怎么回事。您已经了解了它如何优于一般的编码器-解码器序列到序列模型。你还训练了一个神经机器翻译模型,将句子从法语翻译成英语。您可以进一步调整模型的超参数来衡量模型的表现。

我希望你喜欢阅读这个系列!

神经机器翻译是使用深度学习来生成从一种语言到另一种语言的文本的准确翻译的实践。这意味着训练一个深度神经网络来预测一个单词序列作为正确翻译的可能性。

这种技术的用途几乎是无限的。今天,我们有翻译人员能够对用其他语言编写的整个网页进行几乎即时且相对准确的翻译。我们可以将相机对准一段文本,并使用增强现实来用翻译代替文本。我们甚至可以动态地将现场演讲翻译成其他语言的文本。这种能力在很大程度上实现了技术全球化,如果没有序列到序列神经机器翻译的概念,这是不可能的。

在这篇博文中,我们将分解Seq2Seq翻译的理论和设计。然后,我们将从头开始浏览Seq2Seq翻译的官方PyTorch指南的增强版本,其中我们将首先改进原始框架,然后演示如何使其适应新的数据集。

对于深度学习,Seq2Seq翻译器以相对简单的方式工作。这类模型的目标是将固定长度的字符串输入映射到固定长度的成对字符串输出,其中这两个长度可以不同。如果输入语言中的一个字符串有8个单词,而目标语言中的同一个句子有4个单词,那么高质量的翻译者应该推断出这一点,并缩短输出的句子长度。

Seq2Seq翻译器通常共享一个公共框架。任何Seq2Seq转换器的三个主要组件是编码器和解码器网络以及它们之间的中间矢量编码。这些网络通常是递归神经网络(RNN),但它们通常是由更专业的门控递归单元(GRU)和长短期记忆(LSTM)组成的。这是为了限制潜在的消失梯度影响平移。

编码器网络是一系列这些RNN单元。它使用这些来对编码器向量的输入中的元素进行顺序编码,最终的隐藏状态被写入中间向量。

编码器向量包含来自编码器的输入的数字表示。如果一切顺利,它会从最初的输入句子中捕获所有信息。然后,这个编码向量充当解码器网络的初始隐藏状态。

解码器网络本质上与编码器相反。它将编码的矢量中间体作为隐藏状态,并顺序生成翻译。输出中的每个元素通知解码器对下一个元素的预测。

实际上,一个NMT将接受一种语言的输入字符串,并创建一个表示句子中每个元素(单词)的嵌入序列。编码器中的RNN单元将先前的隐藏状态和原始输入嵌入的单个元素作为输入,并且每一步可以通过访问前一步的隐藏状态来通知预测的元素,从而顺序地改进前一步。值得一提的是,除了对句子进行编码之外,句子结束标记表示也作为一个元素包含在序列中。这种句尾标记有助于翻译者知道翻译语言中的哪些单词将触发解码器退出解码并输出翻译的句子。

最终的隐藏状态嵌入被编码在中间编码器矢量中。编码捕获尽可能多的关于输入句子的信息,以便于解码器将它们解码成翻译。这可以通过被用作解码器网络的初始隐藏状态来实现。

使用来自编码器向量的信息,解码器中的每个递归单元接受来自前一个单元的隐藏状态,并产生输出以及它自己的隐藏状态。隐藏状态通知解码器对序列进行预测,对于每个顺序预测,解码器使用来自前一个隐藏状态的信息预测序列的下一个实例。因此,最终输出是翻译句子中每个元素的逐步预测的最终结果。由于句尾标签,这个句子的长度与输入句子的长度无关,它告诉解码器何时停止向句子添加术语。

在下一节中,我们将展示如何使用定制函数和PyTorch实现每个步骤。

PyTorch网站上有一个关于从头开始创建Seq2Seq翻译器的精彩教程。下一节将修改那里的大部分代码,因此在开始实现这些更新之前,浏览一下他们的教程笔记本可能是值得的。

要将数据集放到Gradient上,只需进入终端并运行

withopen('french.pkl','rb')asf:fr_voc=pickle.load(f)withopen('english.pkl','rb')asf:eng_voc=pickle.load(f)data=pd.DataFrame(zip(eng_voc,fr_voc),columns=['English','French'])data我们可以使用pickle.load()来加载现在保存的文件,然后我们可以使用方便的PandasDataFrame来合并这两个文件。

为了给翻译人员创建一个更完整的数据集,让我们将现有的两个数据集结合起来。

data2=pd.read_csv('eng-fra.txt','\t',names=['English','French'])我们需要从规范的PyTorch教程中加载原始数据集。有了这两个数据帧,我们现在可以将它们连接起来,并以PyTorch示例数据集使用的原始格式保存它们。

data=pd.concat([data,data2],ignore_index=True,axis=0)data.to_csv('eng-fra.txt')现在,我们的数据集可以应用于我们的代码,就像规范的PyTorch教程!但首先,让我们看看准备数据集的步骤,看看我们可以做出哪些改进。打开笔记本seq2seq_translation_combo.ipynb并运行第一个单元格,以确保matplotlibinline正在工作并且导入已经完成。

from__future__importunicode_literals,print_function,divisionfromioimportopenimportunicodedataimportstringimportreimportrandomimporttorchimporttorch.nnasnnfromtorchimportoptimimporttorch.nn.functionalasFimporttorchtextfromtorchtext.dataimportget_tokenizerdevice=torch.device("cuda"iftorch.cuda.is_available()else"cpu")数据集准备辅助函数SOS_token=0EOS_token=1classLang:def__init__(self,name):self.name=nameself.word2index={}self.word2count={}self.index2word={0:"SOS",1:"EOS"}self.n_words=2#CountSOSandEOSdefaddSentence(self,sentence):forwordinsentence.split(''):self.addWord(word)defaddWord(self,word):ifwordnotinself.word2index:self.word2index[word]=self.n_wordsself.word2count[word]=1self.index2word[self.n_words]=wordself.n_words+=1else:self.word2count[word]+=1为了给翻译者处理数据集,我们可以使用这个Lang类为我们的language类提供有用的功能,比如word2index、index2word和word2count。下一个像元也将包含用于清理原始数据集的有用函数。

defreadLangs(lang1,lang2,reverse=False):print("Readinglines...")#Readthefileandsplitintolineslines=open('%s-%s2.txt'%(lang1,lang2),encoding='utf-8').\read().strip().split('\n')#Spliteverylineintopairsandnormalizepairs=[[normalizeString(s)forsinl.split('\t')]forlinlines]#Reversepairs,makeLanginstancesifreverse:pairs=[list(reversed(p))forpinpairs]input_lang=Lang(lang2)output_lang=Lang(lang1)else:input_lang=Lang(lang1)output_lang=Lang(lang2)returninput_lang,output_lang,pairs接下来,readLangs函数接收我们的csv来创建input_lang、output_lang,并配对我们将用来准备数据集的变量。这个函数使用助手函数来清理文本并规范化字符串。

defprepareData(lang1,lang2,reverse=False):input_lang,output_lang,pairs=readLangs(lang1,lang2,reverse)print("Read%ssentencepairs"%len(pairs))pairs=filterPairs(pairs)print("Trimmedto%ssentencepairs"%len(pairs))print("Countingwords...")forpairinpairs:input_lang.addSentence(pair[0])output_lang.addSentence(pair[1])print("Countedwords:")print(input_lang.name,input_lang.n_words)print(output_lang.name,output_lang.n_words)returninput_lang,output_lang,pairsinput_lang,output_lang,pairs=prepareData('eng','fra',True)print(random.choice(pairs))最后,prepareData函数将所有的辅助函数放在一起,筛选并最终确定NMT培训的语言对。现在我们的数据集已经准备好了,让我们直接进入翻译器本身的代码。

classEncoderRNN(nn.Module):def__init__(self,input_size,hidden_size):super(EncoderRNN,self).__init__()self.hidden_size=hidden_sizeself.embedding=nn.Embedding(input_size,hidden_size)self.gru=nn.GRU(hidden_size,hidden_size)defforward(self,input,hidden):embedded=self.embedding(input).view(1,1,-1)output=embeddedoutput,hidden=self.gru(output,hidden)returnoutput,hiddendefinitHidden(self):returntorch.zeros(1,1,self.hidden_size,device=device)我们使用的编码器与本教程基本相同,可能是我们在本文中要剖析的最简单的代码。我们可以从forward函数中看到,对于每个输入元素,编码器如何输出一个输出向量和一个隐藏状态。然后返回隐藏状态,因此可以在接下来的步骤中与输出一起使用。

首先,init()函数有另外两个参数:max_length和dropout_p。max_length是一个句子所能容纳的最大元素数量。我们这样设置是因为两个配对数据集中句子长度的差异很大。dropout_p用于帮助调节和防止神经元的共同适应。

这些额外的参数和注意机制使得解码器需要少得多的训练和全部信息来理解序列中所有单词的关系。

还要看老师逼比。该值设置为0.5,用于帮助提高模型的功效。在.5处,它随机确定是否将目标作为下一个输入提供给解码器或者使用解码器自己的预测。这可以帮助平移更快地收敛,但也可能导致不稳定。例如,过度使用教师强制可能会创建一个输出具有准确语法但与输入没有翻译关系的模型。

要更深入地了解这个翻译器的代码,请务必查看包含所有这些信息的Gradient笔记本演示以及Github页面。

hidden_size=256encoder=EncoderRNN(input_lang.n_words,hidden_size).to(device)attn_decoder=AttnDecoderRNN(hidden_size,output_lang.n_words,dropout_p=0.1).to(device)trainIters(encoder1,attn_decoder1,75000,print_every=5000)现在我们已经设置了我们的翻译器,我们需要做的就是实例化我们的编码器和注意力解码器模型用于训练并执行trainIters函数。确保在训练单元之前运行笔记本中的所有单元,以启用辅助功能。

我们将使用256的隐藏大小,并确保您的设备设置为device(type='cuda')。这将确保RNN使用GPU训练。

当您运行此单元时,您的模型将训练75,000次迭代。培训完成后,使用提供的评估功能来评估您的新翻译模型的质量性能。这里有一个例子,说明我们为演示而训练的模型如何在随机抽样的翻译上表现。

现在,您应该能够获取任何适当的翻译数据集,并将其插入到这个翻译器代码中。我推荐从其他的WMT欧帕尔配对开始,但是有无限的选择。一定要在Gradient上运行,以获得强大的GPU!

如果您克隆Githubrepo或将其用作Gradient中的工作区URL,您可以访问包含本文代码的笔记本。有三本笔记本。数据处理是你首先要进入并运行的笔记本。然后是本文中用于在Europarl法语-英语数据集上实现Seq2Seq翻译的代码,

注意:使用Parsec时,不要同时使用Paperspace流。

Parsec可用于任何运行macOS10.9+的Mac,任何运行Windows7+或RaspberryPi3的具有该硬件的PC。

该论文提出了一种称为混洗注意力的新颖注意力机制,该机制可用于传统骨干网,以最小的计算成本提高性能。在本文中,我们将通过讨论随机注意力背后的动机来开始这一仪式,然后是对网络的结构性剖析,最后以论文中展示的结果及其代码来结束本文。

本文旨在回答以下问题:

我们能不能以一种更轻但更有效的方式融合不同的注意力模块?

作者从三个基本概念中获得灵感,回答了上述问题:

作者从流行的ShuffleNetv2架构中获得灵感,该架构有效地构建了多分支结构并并行处理不同的分支。确切地说,输入被分成两个分支,每个分支有\(\frac{c}{2}\)个通道,其中\(c\)是通道总数。然后,这两个分支在通过级联合并形成最终输出之前,经过后续卷积层。

随机注意力(SA)中的功能分组属性是两级层次结构。假设注意模块的输入张量是$\textbf{x}\in\mathbb{r}^{c\asth\astw}$,其中$c$表示通道维度,而$h\astw$表示每个特征图的空间维度,则SA首先沿着通道维度将$\textbf{x}$分成$g$组,使得每个组现在变成$\tilde{\textbf{x}}\in\mathbb{r}^{\frac{c}{g}\asth\astw}$。这些特征组然后被传递到注意模块,在那里它们沿着通道维度被进一步分成两组,每组用于空间和通道注意分支。因此,通过每个空间或通道注意分支的子特征组可以用$\hat{\textbf{x}}\in\mathbb{r}^{\frac{c}{2g}\asth\astw}$表示。

$=\sigma(\\mathematical{f}_{c})\CDO\hat{\textf{x}=\sigma(w_{1s}+b_{1})\CDO\hat{\textf{x}$。

其中,$w_{1}\在\mathbb{r}^{\frac{c}{2g}\ast1\ast1}\(和\)b_{1}\在\mathbb{r}^{\frac{c}{2g}\ast1\ast1}\(是用于缩放和移动间隙的参数(\)\hat{\textbf{X}}$)。

对于空间注意力,输入\(\hat{\textbf{X}}\)被减少组范数以获得空间方面的统计。随后,\(\mathcal{F}_{c}(\cdot)\)用于增强约化张量的表示。这可以用下面的数学公式来表示:

=\sigma(w_{2}\CDOgn(\\hat{\textf{x})+b_2)\CDO\hat{\textf{x}$)

空间注意和通道注意分支的输出首先被连接。与ShuffleNetv2类似,在级联之后,采用信道混洗策略来实现沿着信道维度的跨组信息流。因此,最终输出与SA层的输入张量具有相同的维数。

以下代码片段提供了PyTorch中SA层的结构定义。

无序注意力可能是最接近于在注意力机制提供的计算开销和性能提升之间实现正确平衡的方式。该文件提供了坚实的重要结果以及良好的背景直觉,以支持设计选择。看到SA层在更复杂和困难的任务中接受测试将会很有趣。

对于不同类型的图形设计,人体模型有很多解释。为现有的软件和图形管道构建类似于更人性化的模型的3d结构是设计的主要方面之一。蒙皮多人线性(SMPL)模型是一个基于蒙皮顶点的模型,它精确地表示自然人体姿态中的各种身体形状。SMPL模型代表了现实人类模型的一些最佳作品,因为它们与当前用于图形生成的大多数流行平台兼容,如Blender、Maya、UnrealEngine和Unity。这种高度的兼容性使得许多成功的工作有可能用这些SMPL模型来完成。

一旦您完成了所有的安装、创建和提取GitHub存储库,您就可以继续创建一个虚拟环境来存储这个任务的所有基本需求。一旦创建了虚拟环境并在其中安装了所有必需的依赖项,请确保为特定任务激活了虚拟环境。将目录更改为下载GitHubrepo,首先运行预处理Python脚本。如果您在特定平台上没有GPU支持,您可以运行smpl_np.py脚本来相应地生成男性或女性模型。模型将在。obj格式,现在可以导入Blender平台了。

source./ss_h_mesh_venv/bin/activate(Inthepreviousfolderorthedirectoryyoucreatedyourvenv)cdSMPL-master/pythonpreprocess.pypythonsmpl_np.py如果您的平台上安装了GPU,您可以运行TensorFlow脚本,而不是Numpy变体。请注意,您可以在脚本中做一些小的改动,以获得SMPL模特的不同姿势。

现在我们已经简单了解了如何创建和生成不同姿态的SMPL模型,我们可以继续了解如何导入SMPL模型。我们知道这些文件保存为。系统中的obj文件。在Windows平台上,您会注意到这些文件被保存为3D对象,单击属性时,特定格式显示为.obj文件。本文的大部分内容将集中在如何使用Python脚本来处理SMPL模型。然而,对于导入模型和其他类似的步骤,我们还将介绍如何通过使用Blender平台及其提供的众多功能来完成以下步骤。

首先,让我们了解借助Python脚本将SMPL模型导入Blender平台的代码结构。下面的代码块解释了如何实现下面的任务。在文本编辑器部分,我们用格式。py并继续导入必要的库,如bpy和数学库,它们将帮助我们完成本文中几乎所有的必要任务。第一步是删除搅拌机屏幕的默认立方体。这个过程有两种可能。注释行根据需要执行操作,但是如果您试图多次运行该脚本,您更愿意检查是否有要删除的多维数据集,并且只在存在多维数据集时才删除它。导入所需SMPL模型的最后一步非常简单,因为您需要指定将代码连接到男性或女性模型的存储位置的路径。

导入的模型将需要一些重新调整和缩放。我们将在下一节介绍如何有效地执行以下步骤。

一旦我们完成了这些任务,我们就可以从多个角度来观察我们最终的渲染图像。在多个摄像机视图的帮助下,这一任务是可能的。本文的这一部分更侧重于在代码的帮助下完成预期的任务。然而,我将简单地介绍一下如何利用现有的Blender工具来完成这些任务。但是问题是你不能复制和构建许多不同结构的模型,因为没有代码的工作的复杂性会显著增加。

在上一节中,我们已经详细介绍了如何将SMPL模型导入Blender平台。代码摘要如下面的代码片段所示。

importbpyfrombpyimportcontext,data,opsimportmath###RemoveTheDefaultCudeObject#bpy.ops.object.delete(use_global=False)foroinbpy.context.scene.objects:ifo.name=="Cube":bpy.ops.object.delete(use_global=False)###ImportTheSMPLModelfile_loc='YourPathtosmpl_male.objorsmpl_female.obj'imported_object=bpy.ops.import_scene.obj(filepath=file_loc)obj_object=bpy.context.selected_objects[0]print('Importedname:',obj_object.name)下一个重要步骤是调整模型的位置,相应地旋转模型以匹配相机的最佳视图,最后将模型缩放到合适的大小,以便我们可以清楚地查看模型。在Python脚本的帮助下,可以轻松执行以下操作。需要选择分配给模型的创建对象,我们可以相应地改变所有需要的参数。位置、旋转欧拉角和缩放参数在x轴、y轴和z轴上都是可变的。一旦您在特定的轴上改变了这些属性,您将会得到一个更加令人满意的SMPL的修改,这对于我们在本文中试图完成的大多数任务来说都是非常有用的。

#Locationsobj_object.location.x=0obj_object.location.y=0obj_object.location.z=0.5#Rotationsobj_object.rotation_euler[0]=math.radians(90)obj_object.rotation_euler[1]=math.radians(-0)obj_object.rotation_euler[2]=math.radians(60)#Scaleobj_object.scale.x=2obj_object.scale.y=2obj_object.scale.z=2如果你试图用Blender中分配的工具来完成下面的步骤,这个过程非常简单。在屏幕上选择SMPL模型后,转到属性布局。默认情况下,应选择对象属性。但是,如果不是,您可以相应地调整位置、旋转和缩放的所有参数。您可以借助鼠标手动调整模型的位置,借助键盘上的'R键沿所需轴调整旋转角度,或借助键盘上的'S键调整模型的比例,并沿所有轴均匀缩放。

既然我们已经成功地导入了模型并添加了背景,那么给模型添加一个新的纹理使它看起来更真实就变得非常重要了。借助Python脚本添加单个纹理的过程非常简单。您可以创建材质的名称并将其存储为所需的变量,然后继续创建节点来定义您的特定属性。您还可以选择下载您认为最适合您的SMPL模型的自定义皮肤颜色,并创建相应的节点链接来映射它们。整个模型将使用下面代码片段中提到的代码块导入您在本节中添加的纹理。您可以相应地随意修改、更改和试验代码!

###RenderingProcedurerender=bpy.context.scene.renderscale=render.resolution_percentage/100FILE_NAME="figure2.png"FILE_PATH="SavePath"#SavePreviousPathprevious_path=bpy.context.scene.render.filepath#RenderImagebpy.context.scene.render.filepath=FILE_PATHbpy.ops.render.render(write_still=True)#RestorePreviousPathbpy.context.scene.render.filepath=previous_path您也可以选择使用Blender平台中提供的工具直接渲染图像,方法是在主菜单栏中选择要渲染的选项,然后单击渲染图像选项。确保选择相应的摄像机来执行渲染操作。也可以直接点击键盘上的F12按钮渲染图像。但是,如果您有多个摄像机,这个过程会非常复杂,因为您在摄像机切换过程中的工作量会增加。要在多台摄像机之间切换并拥有多摄像头视图,请选择所需的摄像机,然后单击键盘上的“ctrl”+“numpad0”。值得注意的是,创建多个这样的项目的脚本过程非常简单。

在本文的最后一节,我们将学习如何创建多摄像头视图,并查看SMPL模型以及相应的背景细节和纹理。要创建新的摄像机,选择摄像机角度,并给它一个您选择的名称。根据所需的值设置镜头,并创建您的对象。您可以设置位置、旋转角度,并将所有对象链接到创建的相机。渲染过程也可以再次完成,类似于上一节中提到的步骤。您可以在不同的位置创建多个这样的相机,并捕捉模型的许多视图和透视图。按照下面显示的代码块,可以相应地更改摄像机的变量和名称。

在本文的这一部分中,我们将研究一些可以添加到我们的SMPL模型中的额外改进。我们可以做的一个改进是添加许多不同类型的纹理。我们可以将多种皮肤颜色(如上图所示)或不同种类的纹理结合到SMPL模型中,以达到多种目的。以下任务也可以通过UV映射在编码和Python脚本的帮助下完成,但您也可以在Blender工具的帮助下完成以下任务。

为了精确计算添加多个纹理的后续部分,我建议切换Blender布局屏幕右上角的x射线模式,并从实体视口材质预览切换到渲染状态。在左上角的屏幕上,从对象模式切换到编辑模式,这样你就可以开始对你的SMPL模型的纹理做一些改变。下一步是在材质属性布局中添加你选择的多种纹理和颜色阴影。你可以使用默认的颜色阴影,或者你已经下载的皮肤/其他纹理。

现在,您可以继续用鼠标选择特定区域,并为这些特定区域和部分指定您选择的纹理或颜色。确保放大SMPL模型,以便可以更近距离地看到这些区域,然后可以更精确地选择所需的区域。这种多纹理模型的一个例子如上图所示。您可以使用这些模型进行许多实验,强烈建议您尝试它们的各种变体。

为了获得适用于众多软件设计平台的最佳架构类型,已经有了一些人类模型的创造。对于生成逼真的人体模型,诸如混合蒙皮、自动装配、学习姿势模型、学习姿势形状和学习混合形状等主题具有重要意义。有了这些概念的绝对理论和实践知识,就可以生成现实的人体模型结构。这些生成的人体模型有助于创建各种逼真和似是而非的人体形状和结构,这些形状和结构在许多作品和项目中有很大的用途。

总部位于旧金山的技术公司SpectrumLabs提供情境人工智能、自动化和服务,以帮助消费者品牌识别和应对有毒行为,该公司与Paperspace合作,向互联网约会、游戏、市场和社交媒体社区提供有毒聊天检测模型。

GuadianisSpectrumLabs'contentmoderationplatform.Itallowscustomerstomanageamoderationqueue,buildautomatedresponses,analyzeoverallhealth,andtrainmodels.

幸运的是,在线毒性是一个非常适合机器学习的问题。社区生成大量的文本数据和上下文元数据,这些数据和元数据形成了应用基于NLP(自然语言处理)的机器学习技术来检测有毒和非法行为的坚实基础。

SpectrumLabs正在快速发展其机器学习团队和客户群。由于他们的每个最终用户都通过唯一的端点使用模型(每个端点代表一个经过调整以适应特定应用的基础模型),该团队面临着扩大团队规模和增加唯一模型部署数量的双重挑战。这些需求为Gradient建立了一个强大的用例:帮助内部团队协作,并通过CI/CD、可追溯性和确定性帮助扩展模型部署。

“保护用户体验免受在线毒害需要一种最先进的机器学习方法。Paperspace有助于团队高效运作。”

JoshNewman,SpectrumLabs联合创始人兼首席技术官

Paperspace首席运营官公司的丹·科布兰(DanKobran)表示:“与Spectrum实验室及其才华横溢的机器学习团队合作,我们感到无比兴奋。SpectrumLabs的技术正在使互联网成为一个更安全、更包容、更友好的地方,我们期待帮助他们成功完成这一重要使命。"

确切地说,在本帖中,我们将涉及:

在本帖中,我们将优化k-means聚类算法的实现。因此,我们必须至少对算法的工作原理有一个基本的了解。当然,详细的讨论也超出了本文的范围;如果你想深入研究k-means,你可以在下面找到几个推荐链接。

简而言之,k-means是一种无监督的学习算法,它根据相似性将数据分成不同的组。由于这是一个无监督的算法,这意味着我们没有数据标签。

k-means算法最重要的超参数是聚类数,即k.一旦我们决定了k的值,算法的工作方式如下。

这是算法的伪代码。

Pseudo-codefortheK-MeansClusteringAlgorithm

我要离开K-Means。这足以帮助我们编写算法。然而,还有更多,如如何选择一个好的值k,如何评估性能,可以使用哪些距离度量,预处理步骤,以及理论。如果你想深入研究,这里有几个链接供你进一步研究。

现在,让我们继续算法的实现。

在本节中,我们将使用Python和loops实现K-Means算法。我们不会为此使用NumPy。这段代码将作为我们优化版本的基准。

要执行聚类,我们首先需要我们的数据。虽然我们可以从多个在线数据集进行选择,但让事情保持简单和直观。我们将通过从多个高斯分布中采样来合成一个数据集,这样对我们来说可视化聚类就很容易了。

如果你不知道什么是高斯分布,那就去看看吧!

我们将从四个具有不同平均值和分布的高斯分布中创建数据。

importnumpyasnp#Sizeofdatasettobegenerated.Thefinalsizeis4*data_sizedata_size=1000num_iters=50num_clusters=4#samplefromGaussiansdata1=np.random.normal((5,5,5),(4,4,4),(data_size,3))data2=np.random.normal((4,20,20),(3,3,3),(data_size,3))data3=np.random.normal((25,20,5),(5,5,5),(data_size,3))data4=np.random.normal((30,30,30),(5,5,5),(data_size,3))#Combinethedatatocreatethefinaldatasetdata=np.concatenate((data1,data2,data3,data4),axis=0)#Shufflethedatanp.random.shuffle(data)为了有助于我们的可视化,让我们在三维空间中绘制这些数据。

3-DVisualizationoftheDataset

在上图中很容易看到四组数据。首先,这使得我们可以轻松地为我们的实现选择一个合适的值k。这符合尽可能保持算法细节简单的精神,因此我们可以专注于实现。

我们首先初始化我们的质心,以及一个记录每个数据点被分配到哪个质心的列表。

#Setrandomseedforreproducibilityrandom.seed(0)#Initializethelisttostorecentroidscentroids=[]#Sampleinitialcentroidsrandom_indices=random.sample(range(data.shape[0]),4)foriinrandom_indices:centroids.append(data[i])#Createalisttostorewhichcentroidisassignedtoeachdatasetassigned_centroids=[0]*len(data)在实现我们的循环之前,我们将首先实现几个助手函数。

compute_l2_distance取两个点(比如说[0,1,0]和[4,2,3])并根据以下公式计算它们之间的L2距离。

THE END
1.阿兹特克的永生者小说挥剑斩云梦阿兹特克的永生者最新章节手机阅读《阿兹特克的永生者》无弹窗纯文字全文免费阅读 公元1469年,中美洲阿兹特克帝国极盛的年代。一个后世的灵魂穿越而来,他要拯救印第安人毁灭的命运,建立起中美洲的帝国! 此时的大航海时代刚刚拉开序幕,欧洲的殖民者探索着未知的世界。欧洲人正在崛起,他们要征服美洲富饶的土地,殖民广阔的新世界。 他们本应开启http://www.xbiquge.la/72/72978/
2.斛岚的全部小说作品–言情小说吧作家斛岚,TA的最新作品是《逃荒有空间,大佬全家种田吃肉香》,斛岚写的经典小说代表作品文风干练,辞藻唯美,故事精彩纷呈,深受读者喜爱。斛岚的小说作品在线免费阅读下载,就来言情小说吧!https://www.xs8.cn/writer/15577487705374701
3.大秦:三岁圣孙,我教始皇打天下完整版在线免费阅读番茄小说提供大秦:三岁圣孙,我教始皇打天下完整版在线免费阅读,精彩小说尽在番茄小说网。【无系统+无女主+剧情争霸流】穿越而来,成了赢子婴,秦朝的最后一位皇帝!我大秦奋六世之余烈,终得一统https://fanqienovel.com/page/7292319436441127955
4.阿兹特克的永生者最新章节(挥剑斩云梦),阿兹特克的永生者全文阅读书海阁小说网免费提供挥剑斩云梦写的奇幻经典作品阿兹特克的永生者,阿兹特克的永生者小说免费阅读,阿兹特克的永生者最新章节,阿兹特克的永生者无弹窗免费阅读。https://www.shuhaige.net/22235/
5.阿兹特克的永生者5200,阿兹特克的永生者全文阅读加入书架,方便下次阅读 作者:挥剑斩云梦 更新时间:2024-12-02 02:29:00 介绍: 公元1469年,中美洲阿兹特克帝国极盛的年代。一个后世的灵魂穿越而来,他要拯救印第安人毁灭的命运,建立起中美洲的帝国! 此时的大航海时代刚刚拉开序幕,欧洲的殖民者探索着未知的世界。欧洲人正在崛起,他们要征服美洲富饶的土地,殖民http://www.pksge.com/azitekedeyongshengzhe/
6.阿兹特克的永生者在线阅读最新章节公元1469年,中美洲阿兹特克帝国极盛的年代。一个后世的灵魂穿越而来,他要拯救印第安人毁灭的命运,建立起中美洲的帝国!此时的大航海时代刚刚拉开序幕,欧洲的殖民者探索着未知的世界。欧洲人正在崛起,他们要征服美洲富饶的土地,殖民广阔的新世界。他们本应开启从美洲,到非洲,到印度,到远东的扩张之路,掌握未来的世界http://www.youbaxs.com/11/11028/
7.为了成为英灵我只好在历史里搞事最新章节列表由于需要维持自己的存在,他踏上了成为英灵——在人类史中留下姓名的人物,这条路。 更新很不稳定,而且此书不会收费,作者平时工作时间很不稳定……工作是生活,写书是梦想吧。 目前维持的是一周两到三更,在尽力了,如果有闲余会多更的。感谢各位看我的书。 书友群805546552欢迎大家来玩耍推荐https://www.mayiwxw.com/80_80109/index.html
8.《只怪龙龙尾巴太可爱》粉泪晋江文学城主角:黛娜,史前恐龙 配角:大总统,保安官,土豆尼 一句话简介:穿成有钱寡妇后被史前恐龙求婚了 立意:勇气的赞歌 状态:已签约/完结/129289字 简介:黛娜穿成了19世纪美国的一位俏寡妇,继承了丈夫留下的大笔遗产和产业,每天奢靡度日,直到她发现原身似乎给她留下了一些混乱的修罗场关系。 深受人民爱戴的大总统,https://wap.jjwxc.com/book2/7320020
9.四保临江战役我军歼灭杜聿明4万国军,毛泽东盛赞:东北第一大捷南满1946年10月19日,国民党军队驻东北保安总司令杜聿明调集了约10万兵力,向着共产党领导下的南满根据地发起攻击,然后再进攻北满根据地,企图夺取整个东北地区。 此时的南满军区第三纵队和第四纵队仅仅只有不到四万人,在兵力人数上和武器装备是都处于劣势。 在这种情况下,有人就提出,把主力部队暂时撤退到北满根据地。 https://www.163.com/dy/article/JJ1QB6NG05566KOW.html
10.阿兹特克的永生者最新章节列表阿兹特克的永生者全文阅读阿兹特克的永生者最新章节列表由网友提供,《阿兹特克的永生者》全文阅读情节跌宕起伏、扣人心弦,是一本情节与文笔俱佳的历史军事小说,来读读小说免费提供阿兹特克的永生者最新清爽干净的手打文字章节在线阅读。https://www.laidudu.com/book/64889/
11.www.xinhsen.com/aplpage83023.html此外,美国总统奥巴马23日致信阿总统卡尔扎伊,就此次焚烧古兰经事件正式道歉,并承诺展开全面调查,惩处相关人员。-——。 文件格式: 应用分类:ios-Android 使用语言:中文 :需要联网 应用介绍 一,黑鬼大屌操,狂操视频青青视频网同人 二,苗種毛片,玖瑰网搞点激情 http://www.xinhsen.com/aplpage83023.html
12.巨人阅读的“古兰经”的翻译是:什么意思?中文翻译英文,英文翻译巨人阅读的“古兰经”问题补充:匿名 2013-05-23 12:21:38 Giants read the Koran 匿名 2013-05-23 12:23:18 The giant read the Koran." 匿名 2013-05-23 12:24:58 The giant reads “Koran” 匿名 2013-05-23 12:26:38 Giant reading "Koran" 匿名 2013-05-23 12:28:18 正在http://www.zaixian-fanyi.com/fan_yi_10228109
13.AppStore上的“古兰经:中文音频朗诵,翻译和解释?免费 提供App 内购买项目 截屏 iPad iPhone Apple Watch 简介 “可兰经卡里姆”是一个非常棒的可兰经应用程序。它让你在忙碌中也能阅读和听《古兰经》,得到祝福,让生活更美好。这个最真实的古兰经应用程序以Mushaf al-Madina,Uthmanic和IndoPak字体提供古兰经的精美渲染,提供完整的《古兰经》,语音诵读和翻译,以及一https://itunes.apple.com/cn/app/id1561560973
14.TowardsDataScience博客中文翻译2019(三百六十一)xooloolooTowardsDataScience 博客中文翻译 2019(三百六十一) 原文:TowardsDataScience Blog 协议:CC BY-NC-SA 4.0 神经背包 原文:https://towardsdatascience.com/neural-knapsack-8edd737bdc15?source=collection_archive---8--- 用神经网络求解背包问题 在数据科学的某些https://blog.csdn.net/wizardforcel/article/details/142581260
15.古兰经佚名《古兰经》是伊斯兰教一部节文精确而详明的经典。伊斯兰教相信《古兰经》的原文在世界未创造之先早已存在天国,后由加百列天使启示穆罕默德才传至世间。穆罕默德从第一次得到启示(穆罕默德被安拉委为先知)到逝世的二十二年两个月又二十二天中,神的“圣言”逐节逐章不断地赐下来,由他口传给门徒。 http://fb.newdu.com/book/mb52.html
16.阿兹特克的永生者最新章节列表重回七百年前的中美洲,阿兹特克人极盛的时代,祭祀们站立在太阳金字塔的顶端,十万战士布满整片旷野,作者:挥剑斩云梦所写的《阿兹特克的永生者》无弹窗免费全文阅读为转载作品,章节由网友发布。无弹窗推荐https://www.biqubook.com/37_37455/
17.阿兹特克的永生者(挥剑斩云梦)最新章节,阿兹特克的永生者全文阅读历史小说推荐阅读 无敌六皇子 抗战:开局喀秋莎,这叫送外卖? 每天饮酒赏乐,我成了千古一帝 影视从海豹突击队开始 大汉储君 阿兹特克的永生者 诸天皇帝聊天群 寒门帝师 始皇让我当二世,不小心延续千年 前世毁容,涅槃医妃杀疯了 特种兵之最强军医 偷听糟老头子心声,我得到个皇位 我是特种兵之终极战士 海贼之最强https://www.shu111.com/book/241814.html
18.目前可兰经古兰经最新章节目录可兰经古兰经34章异常免费阅读全文无弹窗,《好姑娘1免费观看完整版中文》2k高清手机在线播放,《野花高清在线观看免费》超清免费免费手机观看,直男而上最新章节列表,直男而上最新更新免费阅读,背德的豪宅最新章节免费阅读,背德的豪宅免费阅读,金花瓶楷梅花尼姑在线:探寻古典艺术与文化的结合,高树三姊妹最新章节免费阅读,高树三姊妹免费阅读。http://m.ouzhehua.com/v/video/3791386lF6jedq.shtml?scm=12301003
19.抖音小时报激荡瑜伽1V2PO奇幻社会新闻日韩字幕在线播放-一流电影网,《甜蜜惩罚2未增删翻译带中文》完整版正片免费在线观看-电,「我身边的极品美女们txt下载」玄幻小说章节列表我身边的,刺猬玫瑰(高干)刺猬玫瑰(高干)全文免费阅读阅读之旅,《农场主的三个女儿》新版最新完整版高清在线观看-媚娘,《女儿的男朋友5中汉字晋通话》157分钟无删减全集免费在线。http://m.ruhrg.com/v/video/75097912_20241204.shtml?id=61963.scm
20.《古兰经韵译》及其注释尽管《古兰经》中文注释的质量,较之阿拉伯文注释具有一定差距,但总体上它是世界范围内《古兰经》注释的重要组成部分。林松先生的《古兰经韵译》及其注释是《古兰经》中文翻译和注释的代表作之一。 引用:1次下载:190次 打开APP,下载管理本文 关键词 《古兰经韵译》《古兰经韵译》注释伊斯兰文化https://read.cnki.net/web/Journal/Article/XBDR201105019.html
21.马坚译《古兰经》· [马坚译《古兰经》] 第一零章 优努斯2008-12-18 15:18:32 · [马坚译《古兰经》] 第一一章 呼德2008-12-18 15:18:32 · [马坚译《古兰经》] 第一二章 优素福2008-12-18 15:18:32 · [马坚译《古兰经》] 第一三章 雷霆2008-12-18 15:18:32 · [马坚译《古兰经》] 第一四章 易卜http://www.duost.com/cat/1837.html
22.古兰经简体中文简体中文 马来语 信息 古兰经 al-quran 最后更新:2013-09-27 使用频率: 3 质量: 好极了 参考:Wikipedia 《古兰经》背诵之湖 iqra 最后更新:2024-03-03 使用频率: 1 质量: 好极了 参考:Wikipedia 你确已奉到从至睿全知的主降示的《古兰经》。https://mymemory.translated.net/zh/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87/%E9%A9%AC%E6%9D%A5%E8%AF%AD/%E5%8F%A4%E5%85%B0%E7%BB%8F
23.古兰经阅读器8gb热卖古兰经阅读笔,带翻译语言套装Alibaba.com古兰经阅读器8gb热卖古兰经阅读笔,带翻译语言套装, You can get more details about 古兰经阅读器8gb热卖古兰经阅读笔,带翻译语言套装 from mobile site on Alibaba.comhttp://chinese.alibaba.com/product-detail/Quran-Reader-16gb-Hot-Selling-Quran-1600714365562.html
24.法律英语阅读与翻译教程(初版)(772页)法律英语阅读与翻译教程(初版).docx 关闭预览 想预览更多内容,点击免费在线预览全文 免费在线预览全文 提高法律英语水平与实际应用能力的法律英语学习教材,可以用于“法律英语”教学,也可以用于“法律翻译”教学。 一本以培养与提高法律翻译能力为中心的法律英语教材(前言)近几年来,中国大陆的“法律英语”(legal https://max.book118.com/html/2020/0722/5333032112002321.shtm
25.古兰经译文第一章 开端 (法谛海) 这章是麦加的 全章共计七节 奉至仁至慈的真主之名 一、一切赞颂 全归真主 全世界的主 二、至仁至慈的主 三、报应日的主 四、我们只崇拜你 只求你佑助 五、求你引导我们上正路 2014-03-10 第二章 黄牛 (巴格勒)http://www.hnislam.com/gljyw
26.拜登就阿富汗讲话全文(中英文对照)及相关材料–Tao'sNewsletter喀布尔陷落,拜登发表的演讲全文(中文翻译)及评析 原创 猫爪社猫爪猫爪会2021-08-17 人不能两次掉进一条河里,孔子云不贰过。 然而拜登就是这么一位两次义无反顾跳河的二傻子,在伊拉克最需要增兵的时候,时任副总统的他主持撤军,成就了臭名昭著的ISIS伊斯兰国,被美国前国防部长马蒂斯称为白痴。 https://taosnewsletter.wordpress.com/2021/08/17/%E6%8B%9C%E7%99%BB%E5%B0%B1%E9%98%BF%E5%AF%8C%E6%B1%97%E8%AE%B2%E8%AF%9D%E5%85%A8%E6%96%87%EF%BC%88%E4%B8%AD%E8%8B%B1%E6%96%87%E5%AF%B9%E7%85%A7%EF%BC%89%E5%8F%8A%E7%9B%B8%E5%85%B3%E6%9D%90%E6%96%99/