大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 Metal Performance Shaders Graph 加快机器学习速度
Metal Performance Shaders Graph 是一种计算引擎,可帮助您为线性代数、机器学习、计算机视觉和图像处理构建、编译和执行定制的多维图形。了解 MPSGraph 如何通过 Apple 产品的 Metal 后台为热门的 TensorFlow 平台加速。了解如何向图形添加控制流、如何管理图形选集以获得最佳性能,以及如何借助 MPSGraph 的运作,仅使用几行代码就能为计算难度极高的 app 加快计算速度。
资源
- Metal
- Metal Performance Shaders
- Metal Shading Language Specification
- Training a Neural Network with Metal Performance Shaders
相关视频
WWDC22
WWDC20
-
下载
嗨 我是萨许 我是Apple GPU软件工程小组的成员 今天我要和我的组员 优利亚 一起介绍MPSGraph的最新运用 我们开始吧 MPS是一个基于Metal、高效 由GPU驱动的原始码函数库 广泛应用于图像处理、线性代数计算 光线追踪与机器学习 MPS发展小组优化了Metal的核心 让Metal在Apple的各项硬件上 都能有最好的表现 去年我们介绍了MPSGraph的框架 为GPU计算图像的基本逻辑 MPSGraph系统支持了macOS、iOS iPadOS与tvOS 和MPS框架相同 请参考我们去年的介绍以获得 更多MPSGraph的入门信息 我们来看一下今天的内容 内容非常多 我们会介绍MPSGraph如何 加速机器学习的训练与推论 MPSGraph令人惊奇的新运作模式 我们会介绍 掌控MPSGraph编码的新方法 最后还有 MPSGraph全新高效的控制流程 我想向你们介绍我的伙伴 优利亚 她会向你们介绍更多 机器推论与训练方面的性能提升 谢谢 萨许 嗨 我是优利亚 我是Apple GPU软件小组的组员 我想向你们分享我们在 增强GPU训练与推论上所做的改善 我们直接进入主题 MPSGraph的框架已经被 Core ML和TensorFlow等 更高阶的机器学习框架采用 以加速他们的GPU运算效能 今年 我们更进一步地 在处理核心与接口的采用上 更优化了MPSGraph 这令使用MPS的机器学习系统 获得了更好的表现 现在我们来看一下 TensorFlow新的Metal插件 TensorFlow是个广被使用的 机器学习训练平台 而GPU是最主要的加速器 今年 我们利用 TensorFlow2.5发布的 TensorFlow插件界面 发展了一个全新的Metal插件 通过MPS与MPSGraph 您将能在TensorFlow中感受到 Metal的强大功能 这让我们可以训练任何在Mac平台 GPU上的机器学习模型 而且中间不用经过修改 现在让我来实际操作给你们看 这次示范 我想要以Jupyter的环境为例 在我的M1系统中 我安装了最新版本的TensorFlow 检视硬件设备时 您可以看到只有CPU一项装置
我要先说明一下ResNet50 这是一个广被使用的机器学习模型 应用于图像分类、转印学习等
目前的模型用的是 标准的ImageNet视觉数据库 图像大小为224*224
您可以发现 以目前的ETA CPU将会花20分钟 来完成第一阶段的工作 现在来安装刚介绍的 TensorFlow Metal插件 看能否加速目前的工作流程 我要先用pip 来安装tensorflow-metal
之后回到同样的ResNet50模型
只不过这次 我们装了一个新的GPU硬件 这是我们刚介绍过的GPU装置 是TensorFlow中 使用了Metal外挂的部分
在所有回调函数与网络设定 维持不变的情况下 让我们重新连网来比较ETA 您可以发现在使用相同网络之下 加装了TensorFlow Metal外挂的 GPU版本 训练速度是原本的四倍
我们再来看一下其他的网络 这张图为以CPU为基准的 机器学习训练速率对照表 您可以看到几乎所有基准点 都显著往上提升 在M1 MacBook Pro的表现上 速率最高还可达到八倍之快
给TensorFlow安装新的Metal外挂 很简单 利用pip安装完 MacOS版本的基本TensorFlow之后 您便可以再利用pip来安装 TensorFlow专门的Metal插件 您可以在python的官方套件pypi.org.中 找到Metal的插件 更详细的环境设置及安装细节信息 请至Metal的开发者服务区咨询 TensorFlow的部分就到这边 接下来 我们来介绍Core ML部分的推论加速
Core ML是Apple的机器学习推论框架 有了MPSGraph 我们能感受到Core ML的表现 有显著提升 这张表显示了使用M1芯片的 主要机器学习网络的推论加速程度 BERT是自然语言处理应用程序 为一种典型的转换网络 BERT的推论速度增长为原本的两倍 而计算机视觉应用程序的核心ResNet50 在之前的版本中被设计成 适合纹理路径的模式 有了MPSGraph在后端的加持 ResNet50有了16%的速度增长 Core ML与TensorFlow的增长表现 来自MPS原式的改善 如Convolution2D 这里我们看到的是Convolution2D 分别在 NHWC与NCHW两种 数据格式间的速度增长 分别应用于机器训练与机器推论 以上是我们在机器推论 与训练中的修正成果 接下来 我们请萨许继续为我们介绍 MPSGraph中新的运作模式 谢谢 优利亚 现在我们来看由MPSGraph支持的 新一套运作模式
MPSGraph支持非常多种运作模式 包含多种卷积与降低操作 及所有计算图中 可能会需要的基本数学运算 今年我们新增了一些特殊的运算模式 让您可以通过MPSGraph达成更多可能 我们加入了三种新的原式 控制依赖、模板运算符 及提取运算符 首先 我们先来看控制依赖 控制依赖可以让 图运算的指令更加清楚 为了增进理解 我们来检视一下图运算的轮廓 图中的运算通过 三种边来连结其他图 输入张量代表 将数据输入节点的张量 输出张量由节点自己产生 而最后 就是这特殊设计的边 我们称它为控制依赖 他们必须在目前的运算之前开始执行 即使目前的运算并不依赖这个边 这个界面也提供了一个方便的方式 来避免运算因MPSGraph的优化 而被松动 这在执行机器学习层 如批标准化时 会派上用场 我们实际来操作看看 批标准化在机器学习训练中 是一项标准化层级的技术 这能使神经网络更加稳定 并提升输入效能 现在看到的是机器训练时 批标准化所运用的计算图 第一步要先计算平均值与方差 这些是为了更新推论所需的 移动平均与移动方差 然而 训练图的成果 并不需要这些变量 因此MPSGraph有可能 会将它们优化掉 我们可以运用控制依赖关系 在最终的正规化运算前 给予更清晰的指令 来解决这个问题 我们来看一个简单的编码例子 看我们能如何使用这项API 这张图有一个指数运算符 及一个指派运算符 这个指派运算符并没有被图中的 其他元素运用 所以它可能会被优化掉 其中一个解决方法 是明确地将指派运算符 设定为目标节点 然而 这需要开发者 去追踪图中全局的依赖关系 通过新的控制依赖API 您便可以指派 指数运算节点的依赖关系 我们便不用设定目标节点 也能确保计算图 不会把运算节点优化掉 接下来 我们实际在编码中看一次
我们首先 先定义指数运算符的依赖关系 接着 我们设置一个依赖阻断 来定义指数运算符 最后我们调用计算图中的编译器来跑 可以注意我们不需要全局追踪 目标运算节点 控制依赖关系的部分就到这边 现在我们来介绍模板运算符
模板运算节点归纳自 一组滑动窗口运算符 如图像卷积 这些运算符对于有限单元法 机器学习及图像处理应用程序 都是无可或缺的 这里有一个二维的五点模板 常用于执行拉普拉斯运算 这个模板运算符 也可以被运用于更高维度 如这个三维的七点模板图 我们来仔细看一下这个运算符 在每个输出值中 运算符会通过输入张量的模板窗口 来计算出加权的降低值 这个运算符支持多种降低模式 包含自变量最小/最大值 与多种填补模式 像是反射与clampToZero MPSGraph使得MPS的核心 得以接合进而优化表现 有了接合的支持 模板运算符可以让您 在只启动单一核心的情况下 执行复杂的数学运算 让我们直接来看个例子 局部响应归一化 是一个PyTorch的运算 运用于维度的标准化 通过新的模板运算节点 执行这项工作变得非常直观简单 现在 我们看到的是这套 标准化技术的计算图 可以看到在模板运算周围的 都是元素积的运算节点 没有新的运算模式 我们会需要进行多分派 现在 由于模板运算符 能相互接合运算符 整个计算图只需分派一次就能完成 模板运算符的部分就到这边 接下来 让我们来看一下 提取运算符部分的更新
今年 MPSGraph新增了 新的提取运算符 它们让处在非相邻内存位置的 任意大小的切片 能够高效地复制 概念上来说 我们想要从内存块中 提取位于图中蓝色位置的量值 这些集结层可以更有效率地 执行嵌入查找与动态矩阵的复制 GatherND是一项 集结运算功能的强大插件 一般的集结功能只支持线性索引 GatherND运算功能 却能支持多维度的索引 这让您能直接复制 所有多维度输入的数据 此组运算的中输入坐标以向量来表示 而每个坐标值最高 可至输入张量的值 坐标中任何非指定的维度 都会以切片形式被复制 我们可以拿三维张量中 如何提取切片的操作为例 在这个例子中 这些指数指出了两种坐标 分别相当于矩阵坐标 与列坐标 在没有第三项栏坐标值的情况下 这个GatherND会直接复制一整列 计算出的张量结果便会是个 从输入矩阵中只提取了列的二维矩阵 GatherND可以呈现几乎所有模式的 提取运算工作 并有优良的表现 举例来说 我们来看一下 如何运用提取运算功能 来执行嵌入查找
嵌入查找是一项常用的操作 常用于为输入对象 寻找嵌入向量 一般来说 嵌入查找会应用于语言处理网络 嵌入矩阵会在此 将词汇表中的字词 联系到对应的嵌入向量 词汇中字词的ID 可以作为提取操作时的指数 而嵌入矩阵便是我们的输入张量 我们会需要为每个字词ID 找到对应的列 而提取层可以轻易帮您完成 我们只有指定一项坐标 因此每个输入的字词 都会提取矩阵的一整列 我们得出的张量结果 便是一个二维的矩阵 而每个字词的嵌入向量 会跟着列一起呈现出来 今年MPSGraph新的操作 就介绍到这边 接下来让我们来谈一下编译的API 今年 我们为您带来 新MPSGraphExecutable的API 此编译API 有两项重点突破 首先 它让开发人员能 决定何时进行编译 再者 它能通过推迟型别推论 让您减少调用编译的次数 现在让我们来分别详细解说 去年 我们提供了一种 极为便利的API 来定义并执行一项MPSGraph 背后的原理其实是 当MPSGraph第一次接收到求值指令 它会根据输入值的形式进行编译 让指令可以被执行 而对于后续接收到的求值指令 MPSGraph会迅速地快取方才的设定 以确保不重复已执行过的编译流程 相较之前 使用者现在 可以提前执行编译 以自行选择编译的时间轴 当编译码可被执行 您便能直接在 MPSGraphExecutable中跑程序了 这让使用者能掌控图被编译的时机 及快取编译完成的可执行文件 以完成更高效的操作 让我们在程序代码中看一次 这边我们有一个 需要加入两个张量的图 我们在操作中为填充张量及目标张量 提供了型别来进行编译 我们会得到一个编译完的图 及可被执行的编译码 求值的方法也同样简单 我们会提供Metal的指令排序 及我们的输入张量数据 那以上就是 编译MPSGraph的基本操作 接下来 我们来说明 该如何通过延迟型别推论 来减少调用编译的次数 型别推论是一套编译传递程序 MPSGraph会在此时判别 未被使用者指定的张量形状 这张图中 我们正在执行 两个二维张量的矩阵乘法 我们可以看到输入张量的形状 然而 我们却不知道输出张量的形状 一旦型别推论的传递程序完成 输出张量的形状便会 根据参数及操作的型态而定 在标准的神经网络中网络的参数 并非只有一种尺寸 在自然语言处理中 句子或序列 可能长短不同 在卷积神经网络中 我们可以看到被输入的 待评估的不同尺寸的影像 在今年的更新之前 系统编译针对 每个新尺寸的图像 都会对整个图 重新进行一次型态推论 对编译有了更多掌握后 身为开发人员的您 便可以绕过型态推论传递 直接进行程序编译了 您可以在每次的迭代中省下 大把的编译时间 以达成最佳表现
MPSGraph的执行期 包含了型态推论作业 与编码同时进行 这权衡了编译所需的时间 与获得最佳结果间的矛盾 现在来看它能如何运用于 我们刚才的编码例子
如画面所示 您只需设置编译描述符 便能取消型态推论传递程序 编译API的部分就到这边 最后 让我们来谈MPSGraph中 新的API控制流程 这些界面让您能根据 图已推算出的张量来动态分派操作 这在批标准化及循环神经网络 这类的应用程序中很常见 我们来看MPSGraph 在没有新界面的情况下 如何执行While循环
首先 我们先创一个 计算谓词的图 接着 通过显式的内存同步 谓词会在CPU中被计算出来 若谓词正确 前面建立好的图 会以新的参数重新执行 反之 若谓词错误 循环便会终止 系统会生成第二个MPSGraph 来消耗结果 API有了新的控制流程后 这些步骤 在执行时都可在MPSGraph中 作为单一项执行
这更加方便了实行的流程 因为您不用再导入 显式记忆同步的原式了 现在让我们来看 它潜在的效率提升点 这里我们可以看到没有新API的 控制流时间轴 我们将第一个核心编码进CPU 核心完成后 我们必须同步内存来读取结果 这是缺乏效率的 因为CPU会需要等待GPU完成执行 同样地 GPU也会需要等待 CPU的同步 及后续的编码来完成工作 这在每次迭代中都会发生 现在我们来看一下 MPSGraph新界面的优点 我们只有要调用一个CPU编码 因为谓词已经在 GPU的时间轴上计算出来 不需再有额外的同步 核心也能无缝地被执行
我们来看新的API有哪些
我们新增了三项控制流程界面 if/else、For循环 与While循环 我们先从if/else的原式开始 我们都很熟悉了 根据一个谓词 会有不同的编码路径被执行 我们会得到一个布尔式的谓词 及一个可输入if/else条件的编码区块 若这个谓词正确 我们便执行Then区块的编码 反之 若不正确 则执行Else区块 if/else的运算操作 在神经网络中非常实用 一个典型的用法可见于 批标准化的操作中 因为这操作在训练及推论中 会有不同的行为 利用“isTraining”的布尔式 我们可以生成一个简单的图 来代表标准化的两种变量 我们来看一下如何在编码中 设置一项if/else的条件分支
我们来看一个非常简单的例子 是两个输入的标量张量 如果第一个张量小于第二个 我们就回到运算的总和 若非如此 我们就回到运算的差 首先 我们先计算出谓词 并传送到API 接着 若谓词正确 我们便计算Then区块 并加上张量 最后 若谓词错误 我们便计算区块Else 并减去张量 现在 让我们来看如何执行一个 For循环
For循环的原式会以固定的次数 循环一套运算 这在循环神经网络中很常见 因为我们在训练时 会循环到不同长度的序列 我们需要为循环提供 迭代次数 在每次迭代循环中 索引值会初始化为零 并与迭代次数相比较 若索引值低于迭代次数 我们便执行For循环的内容 并增加索引值至1
若索引值等于 或大于迭代次数 我们便停止循环 我们来看要怎么在编码中执行
假使我们想要执行 一个非常简单的例子 我们会将变量较大的结果初始化 接着我们会循环四次 每次都将结果 乘以另一个输入值 首先 我们先建两个张量图 输出张量会被初始化为“input0” 在每次的迭代中 这个张量都会乘上“input1” 接着 我们把“numberOfIterations”设为4 我们便可以执行循环4次 从index0到index3 接着 我们再设定循环的body 只要设定一个 代表一次迭代的闭包就行了 每层迭代都会得到当前迭代的索引 还有前次迭代的输出值 接着 我们会更新结果并回传 将其传递到下一层迭代 最后 我们将所有的自变量传递到 For循环的API就行了 可以注意body中的 “iterationArguments” 是初始到input0张量的 循环的部分就到这边 现在让我们来看一下 While循环的API
这组原式会在条件被设置之后 执行一套运算操作 我们需要提供两组区块的编码 来执行这个界面 在第一个区块中 运算的条件是有谓词的 当谓词正确 后面区块中While循环的内容 便会被执行 谓词会重新被运算 MPSGraph之后便会在 先前区块的下个迭代里 运用这个谓词 若计算出的条件是错的 则会退出循环 这个API也可以通过 互换body 与condition的编码区块 来执行do-while循环 假使我们想要执行 一个非常简单的例子 我们会先将结果设置为初始值 接着我们会在每个循环中 把结果乘上一个乘数 直到达到一个临界值 首先 我们会定义一区编码 利用先前迭代的结果来计算谓词 先前迭代的结果 也会储存于 returnTensors的NSArray中 这个数组会在谓词正确时 被当作下一层迭代的参数 而谓词错误时 这个数组则会被当作最终结果 接着 我们会定义While循环的body 在这里张量是相乘的 相乘的积会回归到 condition区块中被读取
最后 如画面所示 我们会把所有自变量传递到 While循环中的API 请注意“initialInputs”的自变量有 使用于before区块中的第一层迭代
While循环的部分就到这边 接着 让我们来看一下 这能如何运用于真正的应用程序中 影像合成是个常见的影像编辑功能 这里 有个对象被移植到目标影像中 我们首先会有一个来源影像 和一个背景影像 如图所示 接着 我们会在 来源影像上放一个遮罩 我们来把来源影像的遮罩 直接放到背景上 但看起来并不美观 我们可以清楚看到来源影像的边缘 通过影像合成 我们想要羽化这些边缘 结合拉普拉斯边缘滤波器 与迭代线性求解器 是一个常见的解法 现在让我们来看一下细节 这边可以看到 用MPSGraph执行合成影像 所需的管线 我们首先有输入张量、背景影像 来源影像与对象遮罩 接着我们运用拉普拉斯边缘滤波器 与迭代线性求解器的结合 输出的是一个 边缘柔滑的合成影像 我们来看一下拉普拉斯边缘滤波器 执行拉普拉斯边缘滤波器时 会在来源影像上 利用一套参数进行窗口折减 如图所示 模板运算符可以这样运用 通过这个运算符 我们就能看到来源对象的边缘了 这里计算好的边缘 便会成为线性求解器的输入参数 接着 让我们来看一下线性求解器
首先先看如何将背景影像 导入线性求解器 求解器会先更新影像 再将结果读取回系统 如图所示 这是一个迭代的过程 迭代进行时 影像会持续优化 直到边缘完美融合 当影像优化到低于容错标准 循环就会停止 这会需要运用到While循环 您现在可以用MPSGraph的控制流程 API 来执行了 我们来看一下示范 我们已经在iPad Pro用了 MPSGraph应用程序 来执行影像合成工作了
我们以来源影像在上 目标影像在下作为开始 我们会将对象从来源影像 复制到目标影像 首先要做的事 是在牛的周围画一个遮罩 作为我们想移动的范围
我们来看直接复制看起来如何
看起来不是很美观 可以看到边缘很粗糙 现在让我们来试试刚刚介绍的 影像合成技术 我们会先将初始解 设置到背景影像上 我们来跑大概50次迭代
很显然地 影像还未相互融合 我们再跑50层
边缘越来越柔和之后 看起来也更自然了 MPSGraph在使用上的简便 让我们能更直接地 尝试各项技术 利用复制影像来初始求解器 在融合影像上 比利用背景影像还要快多了 我们可以通过 切换模式来初始化 我们先再将迭代次数设为50 重设至初始的复制图 来看初始化如何进行
现在我们来重跑求解器 我们可以看到合成图 在50次迭代后看起来非常自然 由于我们已经从来源对象开始操作 边缘看起来更干净了 这非常棒 然而我们真正想要的 是在容错标准下 自动化融合作业 这会需要一个While循环 而我们可以通过这个开关来实行 我们有利用新的MPSGraph API来执行了 容错率可以通过这个拉条来调整 现在是设为0.1 让我们重设回初始复制图 现在来开始跑求解器 有了While循环 我们可以在80次迭代中 修正合成图 中间不需要特别修改任何一次迭代 现在我们来玩玩看 试着合成其他动物到背景中 我们来试试这只可爱的狗
好了 描绘完成 影像的右下角应该是个 不错的位子
接着来放一只鸟好了
放在右上角应该不错 新的背景有了这些动物 看起来更生动了 示范就先到这边
总结来说 我们示范了 如何在CoreML及TensorFlow中 运用MPSGraph来达成 令人惊艳的效果 推论速度是原本的两倍之快 我们介绍了实用的新计算原式 包含即将能广泛运用于各应用程序的 模板运算符
我们展示了新MPSGraph 带来的高弹性编译 这能解决推论网络的延迟问题
最后 我们示范了 MPSGraph新控制流程的所有新功能 这组API能很好地 呈现多种线性代数的应用程序 有益于机器学习网络发展
我们期待看见更多 您的运用及发挥 谢谢 希望您喜欢WWDC 2021 [轻快音乐]
-
-
8:35 - Control dependencies 1
// Execute the graph let results = graph.run(feeds: [inputTensor: inputs], targetTensors: [exp], targetOperations: [assign])
-
9:01 - Control dependencies 2
// Create control dependency let exp = graph.controlDependency(with: [assign], dependentBlock: { return [graph.exponent(with: input, name: nil)] }, name: nil) // Execute the graph let results = graph.run(feeds: [inputTensor: inputs], targetTensors: [exp], targetOperations: nil)
-
14:42 - Evaluation method
// Create the graph let placeholder0 = graph.placeholder(shape: [1, 3], dataType: .float32, name: nil) let placeholder1 = graph.placeholder(shape: [2, 1], dataType: .float32, name: nil) let addTensor = graph.addition(placeholder0, placeholder1, name: nil) // Compile the graph into an executable let executable = graph.compile(with: nil, feeds: [placeholder0: MPSGraphShapedType(shape: [1, 3], dataType: .float32), placeholder1: MPSGraphShapedType(shape: [2, 1], dataType: .float32)], targetTensors: [addTensor], targetOperations: nil, compilationDescriptor: nil) // Execute the graph into an executable let fetch = executable.run(with: commandQueue, inputs: [MPSGraphTensorData(input0), MPSGraphTensorData(input1)], results: nil, executionDescriptor: nil)
-
16:38 - Disabling the type inference pass
// Create the graph compilation descriptor let descriptor = MPSGraphCompilationDescriptor() // Disable type inference descriptor.disableTypeInference() // Compile the graph into an executable let executable = graph.compile(with: nil, feeds: /* feeds */, targetTensors: /* target tensors */, targetOperations: nil, compilationDescriptor: descriptor) // execute the graph
-
19:22 - If/else in batch normalization
// Different behavior during inference and training let results = graph.if(isTraining, then: { ... }, // compute mean and variance else: { ... }, // use running_mean and running_variance name: nil)
-
19:46 - If/else
let predicate = graph.lessThan(a, b, name: nil) let results = graph.if(predicate, then: {[ graph.addition(a, b, name: nil) ]}, else: {[ graph.subtraction(a, b, name: nil) ]}, name: nil)
-
20:58 - For loop 1
var result = input0 for i in 0..<4 { result *= input1 }
-
21:12 - For Loop 2
// Initialize inputs let input0 = graph.placeholder(shape: [], dataType: .int32, name: nil) let input1 = graph.placeholder(shape: [], dataType: .int32, name: nil) let numberOfIterations = graph.constant(4, shape: [], dataType: .int32)
-
21:33 - For Loop 3
// Define Body let body = { (index: MPSGraphTensor, iterationArguments: [MPSGraphTensor]) -> [MPSGraphTensor] in let iterationResult = graph.multiplication(iterationArguments[0], input1, name: nil) return [iterationResult] }
-
21:52 - For Loop 4
// Create for loop operation let result = graph.for(numberOfIterations: numberOfIterations, initialIterationArguments: [input0], body: body)
-
22:51 - While loop 1
var result = initialValue while result < threshold { result *= multiplier }
-
23:01 - While loop 2
// Evaluate condition let condition = { (inputs: [MPSGraphTensor], returnTensors: NSMutableArray) -> MPSGraphTensor in let predicate = graph.lessThan(inputs[0], threshold, name: nil) returnTensors.add(inputs[0]) return predicate }
-
23:22 - While loop 3
// Define body let body = { (inputs: [MPSGraphTensor]) -> [MPSGraphTensor] in let iterationResult = graph.multiplication(inputs[0], multiplier, name: nil) return [iterationResult] }
-
23:33 - While loop 4
// Create while loop operation let results = graph.while(initialInputs: [initialValue], before: condition, after: body, name: nil)
-
25:00 - Edge filter
// Apply the laplacian edge filter on the source image let edges = graph.stencil(with: source, weights: laplacianWeights, descriptor: desc, name: nil)
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。