用 Jax 和 Elegy 进行深度学习
超越张量流、Pytorch 和 Keras
在这篇文章中,我们将探索如何利用 Jax 和 Elegy 来创建深度学习模型。在这个过程中,我们将看到 Jax 如何与 TensorFlow 和 Pytorch 相比较,以及 Elegy 如何与 Keras 相比较。
Jax 是什么?
毫无疑问,TensorFlow (2015)和 Pytorch (2016)在 ML 社区中产生了巨大的影响,他们之间的“军备竞赛”使他们收敛到一组类似的功能(查看 ML 框架 2019 年的状态,可以很好地总结他们的斗争)。Jax (2018)是最新加入的一员,它很好地体现了这种融合。但是 Jax 没有直接成为一个深度学习框架,而是创建了一个超级完美的线性代数库,具有自动微分和 XLA 支持,一些人将其称为类固醇上的 Numpy。
这里有一些让 Jax 变得令人敬畏的东西。
Numpy API
Jax 实现了 Numpy API,并使它成为操作 Jax 数组的主要方式。
这实际上是一件大事,因为 Numpy 是 Python 中数值计算的通用语言,每个数据科学家都已经有了无数小时的 Numpy 经验,而不管其具体的实践领域。这让和 Jax 一起工作变得非常愉快。不仅如此,Jax 的ndarray
基类继承了 Numpy 的ndarray
类型,这意味着第三方库可以接受这些结构。您甚至可以开始使用 Jax“仅仅”加速您现有的 Numpy 代码,只需很少的改动。
统一的 API
Jax 为 eager 和 JIT 执行提供了一个干净统一的 API。
现在 TensorFlow 和 Pytorch 都有 eager 和 compiled 执行模式,但是,它们都添加了在框架生命周期后期缺失的模式,这留下了伤疤。例如,在 TensorFlow 中,急切模式是以这样一种方式添加的,它与图形模式不是 100%兼容,这导致了糟糕的开发人员体验。在 Pytorch 的案例中,它最初被迫使用不太直观的张量格式(如其 vision API 上的NCWH
),只是因为它们在 eager 模式下更具性能,并且出于兼容性原因保留了它们。另一方面,Jax 天生就有这两种模式,并受这两种模式的影响,它主要侧重于使用 eager for debugging 和 JIT 来实际执行繁重的计算,但是您可以在方便的时候混合使用这两种模式。
XLA
Jax 基于下一代 ML 编译器技术。
Jax 专门使用 XLA ,而不是求助于设备相关的 C++和 CUDA 代码的混合。虽然 TensorFlow 是 XLA 存在的原因,但它的使用并没有在其代码库中完全普及,仍然有设备相关的代码。另一方面,Pytorch 有令人沮丧的大量依赖于设备的代码,以至于某些操作只在特定设备上受支持( pytorch/xla 是个东西,但它只关注 TPU 支持)。
特种作战
Jax 带来了一组独特的强大的函数转换。
Jax 有一些新颖的、易于使用的转换,使用户能够执行在其他框架中很难甚至不可能执行的复杂操作。例如,grad
使计算 n 阶阶梯度变得极其容易,vmap
使用户能够编写每个样本的操作,并自动将它们应用到整个批次,而pmap
使用户能够轻松地在器件之间分配计算。你可以在 Jax 的官方文档中找到更多的转换。
兼容性
Jax 是 Pythonic 的。
这曾经是 Pytorch 的发明,但是 Jax 通过将它的架构建立在函数组合和基本 python 类型的基础上,将它带到了另一个层次,也就是说,Jax 可以区分列表、元组和字典等类型!这不仅仅是一个巧妙的技巧,许多基于 Jax 的框架都依赖于这个特性。Jax 还实现了像__array__
和__array_module__
这样的协议,最大化了它与 Python 数据科学生态系统的兼容性。
挽歌深度学习
虽然 Jax 从一开始就具备了创建神经网络的所有要素,但它并没有一个用于此目的的成熟框架。然而,在过去的几个月中,出现了一些面向研究的框架,如 Flax 、 Trax 和 Haiku ,这些库专注于定义一个层接口,同时试图提出策略来执行与 Jax 的功能纯度限制兼容的状态管理。
虽然这些努力是朝着正确方向迈出的一大步,但如果你正在寻找像 Keras 这样更实用的东西,它们会让你感到有点格格不入,因为你会发现自己在编写自己的训练循环、损失函数和指标。
进入挽歌。
什么是挽歌?
Elegy 是一个基于 Jax,受 Keras 和俳句启发的深度学习框架。挽歌有以下目标:
- 易于使用:Keras 模型 API 超级简单易用,所以 Elegy 移植了它,并试图尽可能地遵循它。Keras 用户在使用 Elegy 时要有宾至如归的感觉。
- 灵活性:虽然 Keras 很简单,但对于某些用例来说也非常严格,Elegy 使用依赖注入在定义模型、损失和指标时给你最大的灵活性。
- 简洁:与 Keras 或 Pytorch 相比,Elegy 基于钩子的模块系统使得编写模型代码更加容易(不那么冗长),因为它允许您直接在
call
(forward)方法中声明子模块、参数和状态。
要查看 Jax 和 Elegy 的运行情况,让我们看一个简单但重要的例子。
研究案例:混合密度网络
我们将为这个 1D 回归问题创建一个模型:
如你所见,这是一个逆问题,这意味着对于 X 中的一些值,在 Y 中有不止一个可能的解。这个问题很有趣,因为开箱即用的分类器不能很好地处理这种类型的数据。如果我们将这些数据进行简单的线性回归,将会产生这个模型:
这显然不是一个好的解决方案,因为它的大多数预测都在数据分布之外。对此建模的一个好方法是使用混合密度网络,这是一种混合模型。我们不会在这里深入讨论这个理论的细节,但是你可以参考混合密度网络的便车指南了解更多信息。
进口
我们将从导入我们将使用的一些库开始,包括 Numpy、Jax 和 Elegy。
定义架构
接下来,我们将定义模型的架构。在《挽歌》中,基本的抽象被称为“T0”,这个命名惯例是从俳句“T23”中借用来的。像 Keras 中的Layer
一样,Module
必须定义代表网络前向计算的call
方法。在call
方法中,你可以使用 Jax 函数和其他模块来定义架构,你可以在elegy.nn
中找到像Linear
或Conv2D
这样的通用模块。
对于我们的混合密度网络,我们将定义一个简单的主干结构(x
),然后将其分成多个组件 ( y
),其中每个组件将尝试对一部分数据的均值和方差进行建模。从主干我们还将创建一个门控头(probs
),它将分配概率权重给由x
调节的每个组件。
这段代码中发生了很多事情,您现在不需要理解所有内容,但是请注意以下事项:
- 我们正在使用
k
组件创建一个混合密度网络,其中k
是一个超参数。我们将该值设置为5
。 - 我们正在对
elegy.nn.Linear
进行各种各样的内联调用,事实上,我们甚至在列表理解中直接这样做。相比之下,Keras 或 Pytorch 通常会先在__init__
上定义子模块,然后在call
/forward
中使用它们,从而分割代码。这些内联调用被称为模块挂钩,它们使得读写模型代码更加容易。 - 我们使用来自
jax.nn
模块的函数,像relu
和softmax
,这个模块包含了很多对神经网络有用的函数。 - 我们使用了来自 Jax 的 Numpy API 的
stack
函数,我们将其作为jnp
导入。
如果我们为这个模型生成一个 Keras 风格的摘要(我们将在后面看到如何做),我们将得到下面的描述:
这里我们使用的是64
的批量大小。请注意,我们有 2 个输出:
- 我们称之为
y
的第一个包含了我们的5
分量的均值和方差,这就是为什么它具有形状[64, 5, 2]
。 - 第二个我们称之为
probs
的是我们每个5
组件的概率权重,这就是为什么它有形状[64, 5]
。
创建损失函数
接下来我们将定义我们的损失函数。对于这种问题,如果我们假设每个组件都由正态分布建模,那么损失应该是给定数据的模型的负对数似然性。损耗由以下公式给出:
这里𝜋 k 代表每个成分k
的概率权重,我们称之为probs
,函数 N (…)代表每个成分的正态分布的概率密度函数,这将由y
参数化。我们将通过创建一个从elegy.Loss
继承的类来实现这个损失,并使用常规的 Jax 操作来计算这个公式。
在 Keras 中,定义复杂的损失函数和指标是一个公认的痛点,幸好 Elegy 给了我们更多的灵活性,我们实际上可以基于多个输出创建一个单一损失,而无需匹配数量的标签🥳.默认情况下,y_pred
只是模型返回的内容,在这种情况下是一个元组,所以我们在第三行将其销毁为y
和probs
,而y_true
只是用户传递的标签,在这种情况下,它只是一个带有 Y 值的数组,所以我们不必做任何事情。
代码或多或少以一对一的方式实现了上面的公式。注意,我们使用jax.scipy.stats.norm.pdf
来计算给定模型输出参数的数据的概率,这很酷,因为大多数 Numpy 用户都熟悉 Scipy,可以利用他们对这个库的了解。safe_log
只是log
的一个简单的自定义函数,数值稳定。
训练模型
Elegy 附带了一个模型接口,它实现了 Keras 上的大多数方法,如fit
、evaluate
、predict
、summary
等,只做了一些小的改动。这使得挽歌中的训练模型超级容易!Model
的构造函数接受一个Module
作为它的第一个参数,并且大多数参数被keras.Model.compile
接受,我们将使用它来传递我们的MixtureModel
和MixtureNLL
损失的一些实例,加上来自 optax 库的adam
优化器。
有了这个模型实例,我们可以使用summary
来打印带有我们之前看到的架构描述的表格,它与 Keras 版本略有不同,因为它要求您传递一个样本输入作为其第一个参数,它还接受一个可选的depth
参数,该参数允许您控制您希望摘要有多详细。
最后,我们使用fit
方法训练模型。虽然在这种情况下,给出的例子在 Keras 中也是有效的,但是 Elegy 的fit
方法对输入数据做了轻微的修改。特别是,Elegy 对您使用的数据管道框架保持不可知,因为它只支持ndarrays
或包含ndarrays
(元组、列表、字典)的结构以及ndarrays
的生成器/迭代器或这些结构。你可以通过利用dataset . as _ numpy _ iterator方法或 Pytorch 的DataLoader
来避免to_tensor
转换,从而轻松使用tf.data
。
在培训过程中,您将看到通常的 Keras 进度条,显示损失和指标的值😊。总的来说,您会发现,与其他更面向研究的 Jax 库相比,Elegy 中的培训要简单得多,因为它提供了常见损失、指标和 Keras 中可用的大多数回调的实现,这些实现支持创建检查点、提前停止、TensorBoard 等。
查看 Elegy 的文档了解更多信息。
结果
一旦训练完毕,我们就可以通过绘制覆盖在数据上的预测来检查每个组件学习到的分布。
这里黑线是成分的预测平均值,红线代表数据的估计标准偏差。我们将每个分量的图限制在概率高于某个阈值的区域,以了解其沿数据的分布。但是,我们也可以独立地将 X 中每个点的每个分量的概率权重可视化:
正如所料,有一个分量在 X < -0.75 的点(靠近左下方的分量)占优势,另一个分量在 X > 0.75 的点(靠近右上方的分量)占优势。其余的点将其概率分布在不同的成分中,但根据不同点的数据密度略有不同。
代码
如果你想要更详细的代码视图或者自己运行它,你可以在cgarciae/simple-mixture-models 查看。
概述
- Jax 是一个干净的线性代数库,内置自动微分功能,在 XLA 的基础上实现,吸取了前人的所有经验。
- Jax 非常 Pythonic 化,它的 Numpy API 非常棒,并且它与数据科学生态系统的其余部分非常兼容。
- Elegy 为创建深度学习模型提供了类似 Keras 的体验,而且它比 Flax、Trax 和 Haiku 等替代品更容易使用。
- Elegy 引入了一些机制,使得定义模型代码变得更容易/不那么冗长,并且与 Keras 相比,在损失和度量的定义方面提供了更多的灵活性。
我们希望你喜欢这篇文章。如果你喜欢它请分享它,如果你喜欢挽歌在 Github 上给它一颗星。欢迎反馈。
与 Julia 深度学习,Flux.jl story
步入淡水
介绍
数据科学领域出现了一个新的挑战者:Julia。它速度快,易于输入,有很好的文档和社区。
但它缺乏例子和教程来学习,所以在这篇文章中,我们将建立一个经典的:MNIST 分类器使用卷积神经网络。
是给谁的?
对于那些对深度学习略知一二,但对尝试一门新语言充满好奇的人来说,我将把重点放在对初学者更友好的风格上。我将尝试解释 Julia 语法与 Python 的不同之处。
我们将使用什么?
Flux.jl .是 Julia 主要的深度学习库之一。你可以在这里查看一些例子,尽管它们对初学者来说有些吓人。
来自 flux 主页的官方 logo:https://fluxml.ai/
我为什么要在乎?
虽然 PyTorch 或 TensorFlow 已经为 Python 的深度学习提供了一个很好的生态系统,但它们大多是用 C++甚至 Cuda 编写的,以获得出色的 GPU 性能。
PyTorch GitHub 页面的语言统计
因此,如果你想写一些自定义代码来做一点试验,这可能会变得相当复杂*(尽管在其中编写生产代码可能仍然是一个更好的主意)*。
来自 TensorFlow GitHub 页面的语言统计
另一方面,朱莉娅从一开始就为你提供了速度。所以如果你想写一些自定义的损失函数,你可以在 Julia 中完成。将它与很少的努力结合起来,放在 GPU 上并清除源代码,即使对初学者来说,你也有相当吸引人的东西。
Flux GitHub 页面的语言统计
包和数据集
导入包非常简单。为了更简单的数据准备,我们将导入通量 *(当然)*统计和 MLDatasets 。
using Flux
using Flux: Data.DataLoader
using Flux: onehotbatch, onecold, crossentropy
using Flux: @epochs
using Statistics
using MLDatasets# Load the data
x_train, y_train = MLDatasets.MNIST.traindata()
x_valid, y_valid = MLDatasets.MNIST.testdata()# Add the channel layer
x_train = Flux.unsqueeze(x_train, 3)
x_valid = Flux.unsqueeze(x_valid, 3)# Encode labels
y_train = onehotbatch(y_train, 0:9)
y_valid = onehotbatch(y_valid, 0:9)# Create the full dataset
train_data = DataLoader(x_train, y_train, batchsize=128)
(亲提示:默认情况下 Julia 会打印该函数的输出。您可以抑制它,但键入“;”结束)
Flux 将期望我们的图像数据按照 WHCN 顺序*(宽度,高度,#通道,批量)*,所以我们必须添加一个通道层。幸运的是,已经有了一个名为unsqueeze
的函数。
稍后我们将使用交叉熵损失,因此我们还需要使用onehotbatch
对标签进行编码。
模型
层
我们的模型将有 8 层。其中 4 个将是 Relu 的卷积,然后我们将意味着池化,展平它,最后用 softmax 填充到一个线性层中。
我们可以使用Chain
函数将所有东西“链接”在一起
model = Chain(
# 28x28 => 14x14
Conv((5, 5), 1=>8, pad=2, stride=2, relu),
# 14x14 => 7x7
Conv((3, 3), 8=>16, pad=1, stride=2, relu),
# 7x7 => 4x4
Conv((3, 3), 16=>32, pad=1, stride=2, relu),
# 4x4 => 2x2
Conv((3, 3), 32=>32, pad=1, stride=2, relu),
# Average pooling on each width x height feature map
GlobalMeanPool(),
flatten,
Dense(32, 10),
softmax)
每个卷积层获取一个图像,并从中创建通道层。因此,下一个卷积层将采取一个更小的图像,其中有更多的通道。我们还在第一层应用填充,并在所有层上应用步长 2。
如果你需要复习一下下面的 gif 和这篇文章。对于内核来说,填充基本上使我们的图像更大,而 stride 定义了它需要多大的步长。
Vincent Dumoulin,Francesco vision—深度学习卷积算法指南
然后是平均池层。它从卷积层获取特征图,并从每个通道获取平均值。
由作者提供
但是在我们将数据进一步输入到线性层之前,我们必须去掉称为单线态的 1x1 维度。这就是扁平化层的目的。
如果你想知道每一层的尺寸是如何变化的,我会在文章末尾提供一个 jupyter 笔记本的链接。
现在是我们可以将数据输入模型并获得预测的时候了。它们还没有任何用处,但这是检查我们是否做对了所有事情的好方法。要解码预测,使用onecold
函数。
# Getting predictions
ŷ = model(x_train)
# Decoding predictions
ŷ = onecold(ŷ)
println("Prediction of first image: $(ŷ[1])")
损失函数、优化器和指标
现在是时候选择如何更新模型参数以及如何检查其性能了。
accuracy(ŷ, y) = mean(onecold(ŷ) .== onecold(y))
loss(x, y) = Flux.crossentropy(model(x), y)# learning rate
lr = 0.1
opt = Descent(lr)ps = Flux.params(model)
我们有标准的精度度量和通量的交叉熵损失。对于优化器,我们将选择学习率为 0.1 的简单梯度下降。当然还有更好的选择,比如Momentum
或者ADAM
,但是对于一个简单的向导来说已经足够了。
例如,Flux 的分化库 Zygote 的工作方式与 PyTorch 中使用的有些不同。它本身是值得研究的,但是现在我们只需要知道我们必须从我们的模型中获取参数。
为此,我们简单地调用params
函数,将我们的模型作为输入。
培养
现在我们在等待的事情是:训练模型。
number_epochs = 10
@epochs number_epochs Flux.train!(loss, ps, train_data, opt)accuracy(model(x_train), y_train)
就是这样。这就是训练。我们用损失、参数、数据和优化器调用train!
函数。我们使用@epochs
宏,指定我们希望它执行的次数*(默认情况下它只执行一次)*。
现在来看看朱莉娅的更多方面。
“!”在函数名中,通常意味着函数会产生副作用或者就地执行*(如果在 Python 中使用 numpy)*。
“@”位于宏之前。这是通向所谓元编程的大门。他们改变了函数的代码,所以你可以将它与 Pyton 中的 decorators 进行比较(尽管严格来说它们不是同一个东西)。
更深入
现在让我们自己编写训练循环。最好的一点是,如果我们比较执行这两种方法所花的时间,会发现它们实际上是相似的。
从文档中:
Flux.train!
函数可以非常方便,特别是对于简单的问题。回调的使用也非常灵活。但是对于一些问题来说,编写自己的定制训练循环要干净得多。
所以,我们就这么做吧。
for batch in train_data
gradient = Flux.gradient(ps) do
# Remember that inside the loss() is the model
# `...` syntax is for unpacking data
training_loss = loss(batch...)
return training_loss
end
Flux.update!(opt, ps, gradient)
end
我们循环训练数据集*(来自数据加载器)*。然后,我们使用do
关键字将损失计算映射到梯度函数。然后我们用优化器、参数和保存的梯度调用update!
,就完成了。
更深
哦,你不喜欢update!
功能?没问题。让我们编写自己的循环。
for x in ps
x .-= lr .* gradient[x] # Update parameters
end
每个符号前的.
告诉 Julia 按元素进行操作。
如果你仔细查看 Flux 的源代码,你会发现,仅仅在两个函数中。不相信我?自己看这里和这里。
这就是朱莉娅最吸引人的地方。它会很快,并且已经为 GPU 做好了准备。厉害!
甚至更深
想要更多吗?让我们创建一些回调。它们本质上是在训练时将被调用的函数。
loss_vector = Vector{Float64}()
callback() = push!(loss_vector, loss(x_train, y_train))Flux.train!(loss, ps, train_data, opt, cb=callback)
你可以把push!
当作 numpy 的一个append
函数。
现在,我们有了一个列表,列出了每批数据之后的损失*(请记住,这是针对更大数据集的大量计算)*。
包扎
现在你知道如何在朱莉娅使用通量。您甚至知道如何为您的模型编写高效的定制函数。花了多长时间?不多,不是吗?
毫无疑问,Julia 和 Flux 的时代还很早,但是如果你喜欢这种语言和这些包的编写方式,我认为值得尝试一下。
毕竟,如果我们有一些与他人交流的经验,我们都可以用自己喜欢的语言写出更好的代码。
你可以在这里看到一个 jupyter 笔记本,里面有所有的代码。
来和我一起在推特上闲逛吧,感谢你的阅读!
使用 PyTorch 进行深度学习
深度强化学习讲解— 04
初学 PyTorch
我们将在本系列的许多文章中使用 PyTorch,所以读者需要确保她/他熟悉它。这篇文章将向读者介绍 PyTorch 的基本特性,它使我们能够使用 Python 语言实现深度学习模型。这篇文章并没有假装是 PyTorch 的完整手册,它只是介绍了 PyTorch 的基本知识,以开始在 PyTorch 中编码神经网络,我们将在整个系列中引入我们需要的新功能。好好享受吧!
请访问第 8 页的自由介绍
medium.com](https://medium.com/aprendizaje-por-refuerzo/8-pytorch-b%C3%A1sico-a60ce5fc8b74)
深度学习框架
深度学习框架领域的明确领导者现在是谷歌开发的 TensorFlow 和脸书开发的 PyTorch,它们正在从使用量、份额和势头上脱离市场的其余部分。
三年前,第一个版本的 PyTorch 问世,毫无疑问,它正在获得巨大的发展势头。PyTorch 最初由脸书孵化,作为快速实验和原型制作的理想灵活框架,迅速赢得了声誉,在深度学习社区中赢得了成千上万的粉丝。例如,我的研究团队中的博士生更喜欢使用 PyTorch,因为它允许他们编写看起来像本机的 Python 代码,并且仍然可以获得良好框架的所有好处,如自动微分和内置优化。这就是我决定在这个系列中使用 PyTorch 的原因。
虽然 PyTorch 由于脸书(和 AWS)而在市场上获得了动力,但 TensorFlow 仍然在各个方面保持领先,并且是目前行业中使用最多的。你可以阅读这篇简短的文章“ TensorFlow vs PyTorch:战斗仍在继续”来了解关于这两种环境的更多细节。
环境设置
我建议使用 Google 提供的(Colab)来执行本文描述的代码。它基本上由一个 Jupyter 笔记本环境组成,不需要配置,完全在云中运行,允许使用不同的深度学习库,如 PyTorch 和 TensorFlow 。Colab 的一个重要特点是它完全免费提供 GPU(和 TPU)。关于该服务的详细信息可以在常见问题页面上找到。
默认情况下,Colab 笔记本运行在 CPU 上。你可以切换你的笔记本电脑运行与 GPU(或 TPU)。为了访问一个 GPU,您需要选择“运行时”选项卡,然后选择“更改运行时类型”,如下图所示:
当弹出窗口出现时,选择 GPU。确保“硬件加速器”设置为 GPU(默认为 CPU)。然后,确保您已连接到运行时(在菜单功能区中“已连接”旁边有一个绿色复选标记):
现在你可以运行这篇文章中的代码了。我建议将这篇文章的代码复制粘贴到一个 Colab 笔记本上,以便在你阅读这篇文章的同时看到执行过程。准备好了吗?
这篇文章的完整代码可以在 GitHub 上找到,并且可以使用这个链接作为一个 Colab google 笔记本运行。
使用 PyTorch 的手写数字示例
在这篇文章中,我们将编写一个神经网络模型,对在上一篇文章中出现的手写数字进行分类。请记住,我们创建了一个数学模型,给定一幅图像,该模型识别它所代表的数字,返回一个具有 10 个位置的向量,指示 10 个可能数字中每一个的可能性。
来源: torres.ai
为了引导解释,我们将遵循为神经网络编程所要采取的步骤列表:
- 导入所需的库
- 加载和预处理数据
- 定义模型
- 定义优化器和损失函数
- 训练模型
- 评估模型
让我们去吧!
1.导入所需的库
我们总是需要导入 PyTorch 的核心 Python 库torch
。对于我们的例子,我们还将导入torchvision
包,以及常用的库numpy
和matplotlib
。
**import torch
import torchvision**
为了代码的清晰,我们可以在这里定义一些训练所需的超参数:
**import numpy as np
import matplotlib.pyplot as plt EPOCH = 10
BATCH_SIZE= 64**
2.加载和预处理数据
加载数据
下一步是加载将用于训练我们的神经网络的数据。我们将使用前一篇文章中已经介绍过的 MNIST 数据集,可以从MNIST 数据库页面下载使用torchvision.dataset.
PyTorch 数据集是根据请求返回单个数据点的对象。然后,它被传递到处理数据点批处理和并行性的数据加载器。这是我们示例的代码:**
**xy_trainPT = torchvision.datasets.MNIST(root='./data',
train=True, download=True,transform=
torchvision.transforms.Compose(
[torchvision.transforms.ToTensor()]))xy_trainPT_loader = torch.utils.data.DataLoader
(xy_trainPT, batch_size=BATCH_SIZE)**
因为数据通常太大,无法一次将数据放入 CPU 或 GPU 内存中,所以将数据分成大小相等的批次。每一批都包括数据样本和目标标签,并且两者都必须是张量(我们将在下面介绍)。BATCH_SIZE
参数表示我们将在每次更新模型参数时使用的数据数量。
该数据集包含 60,000 个手工制作的数字图像来训练模型,对于首次进入模式识别技术来说是理想的,无需花费大量时间预处理和格式化数据,这在数据分析中是非常重要和昂贵的步骤,并且在处理图像时具有特殊的复杂性。
我们可以验证前面的代码已经用库matplotlib.pyplot
加载了预期的数据:
**fig = plt.figure(figsize=(25, 4))
for idx in np.arange(20):
image, label = xy_trainPT [idx]
ax = fig.add_subplot(2, 20/2, idx+1, xticks=[], yticks=[])
ax.imshow(torch.squeeze(image, dim = 0).numpy(),
cmap=plt.cm.binary)
ax.set_title(str(label))**
预处理数据
记得在上一篇文章中,我们解释过,为了便于将数据输入到我们的神经网络中,我们将输入(图像)从二维(2D)转换为一维(1D)的向量。也就是说,28×28 个数字的矩阵可以由 784 个数字(逐行连接)的向量(数组)来表示。
当我们使用这种类型的变换(例如,应用于第一幅图像)将数据摄取到神经网络时,我们将应用这种变换:
**image, _ = xy_trainPT[0]
print(image.size())
image_flatten = image.view(image.shape[0], -1)
print (image_flatten.size())torch.Size([1, 28, 28])
torch.Size([1, 784])**
张量
张量是一个多维数组,是 PyTorch 的基本构造块,相当于 NumPy,它存储一组数字:
**a = torch.randn(2, 3)
print(a)tensor([[ 1.1049, 0.2676, -0.4528],
[ 0.0105, -0.5095, 0.7777]])**
我们可以知道它的尺寸和大小:
**print(a.size())
print(a.dim())torch.Size([2, 3])
2**
除了维度,张量的特征还在于其元素的类型。为此,我们有一个dtype
参数,它故意与同名的标准 NumPy 参数类型相似:
**matrix=torch.zeros([2, 4], dtype=torch.int32)
print(matrix)tensor([[0, 0, 0, 0],
[0, 0, 0, 0]], dtype=torch.int32) print(matrix.dtype)torch.int32**
Torch 定义了九种类型的 CPU 张量和九种类型的 GPU 张量:
如你所见,GPU 张量有特定的类型。PyTorch 透明支持 CUDA GPUs,这意味着所有操作都有两个版本——CPU 和 GPU——自动选择。这个决定是基于你正在操作的张量的类型做出的。
在 PyTorch 中创建张量有不同的方法:调用所需类型的构造函数,将 NumPy 数组(或 Python 列表)转换为张量或要求 PyTorch 创建具有特定数据的张量。例如,我们可以使用torch.zeros()
函数创建一个填充零值的张量:
**b = torch.zeros(2, 3)
print(b)tensor([[0., 0., 0.],
[0., 0., 0.]]) c = torch.ones(2, 3)
print(c)tensor([[1., 1., 1.],
[1., 1., 1.]])**
张量的元素可以使用其索引(从 0 开始)来访问:
**c[0,0]=222
print(c)tensor([[222., 1., 1.],
[ 1., 1., 1.]])**
此外,就像 Python 中常见的数据结构一样,我们可以在“:
”字符的帮助下,在索引中使用范围标记来选择和操作张量的各个部分。索引从 0 开始,我们可以对索引使用负值,其中-1
是最后一个元素,依此类推。让我们来看下面的一段代码作为例子:
**x = torch.Tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print (x) tensor([[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.]]) print (“x column 1: “, x[:, 1])
print (“x row 0: “, x[0, :])
print (“x rows 0,1 & cols 1,2: \n”, x[0:2, 1:3])x column 1: tensor([ 2., 6., 10.])
x row 0: tensor([1., 2., 3., 4.])
x rows 0,1 & cols 1,2:
tensor([[2., 3.],
[6., 7.]])**
PyTorch 张量可以非常有效地转换为 NumPy 矩阵,反之亦然。通过这样做,我们可以利用 Python 生态系统中围绕 NumPy 数组类型发展起来的大量功能。让我们用一个简单的代码来看看它是如何工作的:
**x = np.array([[1,2], [3,4], [5,6]])
print (x) [[1 2]
[3 4]
[5 6]]**
这个数组x
可以很容易地转换成张量,如下所示:
**y=torch.from_numpy(x)
print(y)tensor([[1, 2],
[3, 4],
[5, 6]])**
我们可以看到第二个印记表明它是一个张量。相反,如果我们想把一个张量转换成一个 NumPy 数组,我们可以这样做:
**z = y.numpy()
print (z)[[1\. 2.]
[3\. 4.]
[5\. 6.]]**
我们将使用reshape()
函数,它返回一个与输入具有相同数据和元素数量的张量,但是具有指定的形状。如果可能,返回的张量将是输入的视图。否则,它将是一个副本(在内存中):
**one_d = torch.arange(0,16)
print (one_d)two_d= one_d.reshape(4,4)
print (two_d)print(two_d.size())tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])torch.Size([4, 4])**
3.定义模型
在torch.nn
包中,您可以找到许多预定义的类,它们提供了编程神经网络所需的基本功能块。要定义上一篇文章中呈现的模型,可以使用该包中的Sequential
类来完成:
**modelPT= torch.nn.Sequential(
torch.nn.Linear(784,10),
torch.nn.Sigmoid(),
torch.nn.Linear(10,10),
torch.nn.LogSoftmax(dim=1)
)**
该代码定义了由两个密集层(线性层)组成的神经网络,每个密集层 10 个神经元,一个具有 Sigmoid 激活函数,另一个具有 Softmax 激活函数。随着本系列的推进,我们将引入其他激活函数作为 ReLU,我们将在本系列的下一篇文章中使用。
我想强调的是,前面的代码对前一篇文章中介绍的神经网络进行了一个小的转换:此外,它对最后一层的每个输出应用了对数运算。具体来说, LogSoftmax 函数可视为:
其中 Softmax 按照上一篇中的定义计算。与 Softmax 相比,LogSoftmax 有许多实际和理论上的优势,这些优势促使我们在构建神经网络时使用它,我们将在后面的部分中对此进行讨论。
总之,我们定义的网络可以直观地表示出来,如下图所示:
来源: torres.ai
神经网络的第一层接收 784 个特征的张量,这些特征表示传递给第一层中 10 个神经元中的每一个的像素。一旦这 10 个神经元处理了这些信息,它们中的每一个都将信息传递给下一层的所有神经元,即第一层的所有 10 个神经元都与第二层的所有 10 个神经元相连。
第二层是具有 10 个神经元的 softmax 激活函数的层,这意味着它将返回 10 个概率值的张量,代表 10 个可能的数字。一般来说,分类网络的输出层将具有与类一样多的神经元,除了在二进制分类中,其仅需要一个神经元。让我们记住,我们使用的是 LogSoftmax 层,而不是 Softmax 层,因此返回的每个值将是当前数字的图像属于每个类的概率的对数。
我们可以用这个简单的例子来分析构成神经网络的参数。例如,在第一层中,对于 10 个神经元中的每一个,权重需要 784 个参数,因此需要 10 × 784 个参数来存储 10 个神经元的权重。此外,对应于每个神经元的 10 个偏置需要 10 个附加参数。因此,对于第一层,需要 7850 个参数。
在第二层中,作为 softmax 函数,需要将其所有 10 个神经元与前一层的 10 个神经元连接,因此,权重需要 10 × 10 个参数;除了对应于每个节点的 10 个偏置。这给了我们第二层总共 110 个必需的参数。
总之,对于我们极其简单的神经网络,我们看到需要 7960 个参数,第一层需要 7850 个参数,第二层需要 110 个参数。
通过对所有神经网络模块的基类
nn.Module
进行子类化,我们可以创建自己的构建块,这些构建块可以堆叠在一起并在以后重用,这是通常所做的。但是考虑到这篇文章的初始性质,我们可以用这种基本的方式来定义我们的神经网络。读者可以查阅官方文件,了解关于这个话题的更多细节。
4.定义优化器和损失函数
正如我们在上一篇文章中所展示的,这些模型是通过迭代求解一个无约束优化问题来训练的。在每次迭代中,随机的一批训练数据被输入到模型中以计算损失函数值。然后,计算损失函数相对于网络权重的梯度(反向传播),并在梯度的负方向上更新权重。这些网络被训练,直到它们收敛到损失函数最小值。
损失函数
PyTorch 中大约有 20 种不同的损失函数,驻留在nn
包中,并作为nn.Module
子类实现。我们将在本系列中使用的一些常见标准损失函数有:
nn.MSELoss
:自变量之间的均方误差,是回归问题的标准损失。nn.NLLLoss
:它计算“最大似然”标准,一般用于多类分类问题(如我们的 MNIST 例子)。nn.CrossEntropyLoss
:计算与nn.NLLLoss
相同,但是它期望每个类的原始分数,并在内部应用 LogSoftmax(而 nn。NLLLoss 期望将对数概率作为输入)。
由于我们正在处理一个多类分类问题,我们选择交叉熵作为我们的损失函数。在这个例子中,我们使用负对数似然神经网络。NLLLoss()与 softmax nn 结合使用。LogSoftmax()函数,我们已经介绍过了。正如我们所说的,我们不应用 Softmax 来增加训练过程的数值稳定性,提出了一种替代方法来首先计算 Softmax,它使用指数运算,然后计算交叉熵损失,它使用概率的对数。如果读者对这方面的更多细节感兴趣,我推荐看一看这篇文章。
使用 LogSoftmax 定义神经网络的缺点是,每次我们需要从神经网络输出中获取概率时,我们都需要记住应用 Softmax。
一般来说,读者会看到 PyTorch 代码中分配给criterion
的损失函数:
**criterion = torch.nn.NLLLoss()**
因此,计算误差的方法如下:
**loss = criterion(logps, labels)**
我们在自变量中指出神经网络的输出和正确的标签。
【计算机】优化程序
请记住,优化器采用模型参数的梯度并更改这些参数,以减少损失值。我们使用 PyTorch 提供的模块torch.optim
来优化模型,执行梯度下降,并通过反向传播来更新权重。这个软件包允许我们在几个算法(AdaGrad,RMSProp,Adam 等)中进行选择。)是梯度下降算法的不同变体,梯度下降算法是一种能够找到各种问题的最优解的通用优化算法。此刻,在这个例子中,我们将使用基本的随机梯度下降 (SGD):
**optimizer = torch.optim.SGD(modelPT.parameters(), lr=0.01)**
参数是优化器必须调整的参数和指示这些调整应该如何进行的学习率。请记住,优化器会以正确的方向迭代调整模型的参数(权重和偏差)(在其值上加一个“小”或减一个“小”,其中这个“小”是由学习率定义的),从而减少误差。一般来说,重复该过程,直到误差降到可接受的水平以下。
导数用于计算正确的方向,特别是误差相对于参数的梯度。PyTorch 中的自动签名包正是通过自动微分来自动计算神经网络中的反向传递,从而提供了这种功能。
5.训练模型
一旦我们的模型被定义,学习方法被配置,它就可以被训练了。因此,我们只需定义将迭代所有数据的训练循环,以便优化器迭代地调整权重。让我们用这几行代码来讨论一个训练循环的通用蓝图:
**1: for e in range(EPOCHS):
running_loss = 0
2: for images, labels in xy_trainPT_loader:
3: images = images.view(images.shape[0], -1)
4: output = modelPT(images)
5: loss = criterion(output, labels)
6: loss.backward()
7: optimizer.step()
8: optimizer.zero_grad()
running_loss += loss.item()
print(“Epoch {} — Training loss: {}”.format(e,
running_loss/len(xy_trainPT_loader)))**
第 1 行:通常,训练循环会反复迭代我们的数据。记住,对一组完整的例子的一次迭代被称为一个时期。EPOCHS
变量表示在整个例子集上的迭代次数。**
第 2 行:**我们已经介绍过,数据通常太大,无法一次放入 CPU 或 GPU 内存,因此它被分成大小相等的批次。每个批次都包含数据样本和目标标签,两者都必须是张量。
****第 3 行:为了便于将数据输入我们的神经网络,我们必须将输入(图像)从二维(2D)转换为一维(1D)向量。
****第 4 行:我们将每批图像张量传递到模型中,该模型将返回一个对该批图像进行预测的张量,即前向传递。
****第 5 行:得到预测后,我们将它们和它们的实际标签一起传递到交叉熵损失函数(criterion
)中,并计算损失。通常,损失函数接受两个参数:网络输出(预测)和期望输出(真实数据,也称为数据样本的标注)。
一些 PyTorch 的损失函数将类标签作为它们的目标(例如 NLLloss ),所以如果我们使用它们(如在我们的例子中),我们不需要像我们在上一篇文章中介绍的那样将目标转换成一个热点向量(为了便于解释)。
****第 6 行:我们使用损失值进行反向传递,以计算损失相对于模型参数的梯度。在loss.backward()
调用结束后,我们累积了梯度。
****第 7 行:现在是优化器使用方法step()
修改模型参数的时候了,该方法从参数中提取所有梯度并应用它们。
****第 8 行:训练循环的最后一部分,也是最重要的一部分,是我们对参数梯度归零的责任。在我们的网络上调用zero_grad()
清除所有优化变量的梯度。
为了检查训练过程是如何发展的,我们在这个训练循环中添加了几行代码。首先,通过将每批迭代的所有损失相加,我们得到整个时期的训练损失:
**running_loss += loss.item()**
第二步,用迭代次数对其求平均,并打印出来:
**print(“Epoch {} — Training loss: {}”.format(e,
running_loss/len(xy_trainPT_loader)))**
查看该输出,我们可以看到训练循环如何调整网络的权重,以便在每次迭代中,损失函数产生更小的损失。
**Epoch 0 — Training loss: 2.1822925415882932
Epoch 1 — Training loss: 1.8671700155048736
Epoch 2 — Training loss: 1.5379922698809902
Epoch 3 — Training loss: 1.287035460029838. . .Epoch 15 — Training loss: 0.5162741374264139
Epoch 16 — Training loss: 0.49991638108547815
Epoch 17 — Training loss: 0.48541215611800453
Epoch 18 — Training loss: 0.4724724407929347
Epoch 19 — Training loss: 0.4608637086554631**
在 PyTorch 中,没有像 Keras 或 Scikit-learn 中的
fit()
那样的“预制”数据模型调优函数,所以训练循环必须由程序员指定。
6.评估和使用模型
现在,我们已经完成了模型的训练,我们可能想要通过在测试数据集上应用它来测试我们的模型的一般化程度。
在 PyTorch 中,再次要求程序员指定评估循环:
**xy_testPT = torchvision.datasets.MNIST(root='./data',
train=False, download=True,
transform=torchvision.transforms.
Compose([torchvision.transforms.ToTensor()]))xy_test_loaderPT = torch.utils.data.DataLoader(xy_testPT)correct_count, all_count = 0, 0
for images,labels in xy_test_loaderPT:
for i in range(len(labels)):
img = images[i].view(1, 784)
logps = modelPT(img)
ps = torch.exp(logps)
probab = list(ps.detach().numpy()[0])
pred_label = probab.index(max(probab))
true_label = labels.numpy()[i]
if(true_label == pred_label):
correct_count += 1
all_count += 1print("\nAccuracy of the model =", (correct_count/all_count)) Accuracy of the model = 0.8657**
读者可以看到这个循环中的指令与前一个训练循环中的指令相似。但在这种情况下,不是保留损失计算,而是计算准确性,即模型从未见过的数据的命中率。
仅此而已!你已经设计了一个完整的神经网络。恭喜你。
下一篇见!
深度强化学习讲解系列
由 UPC 巴塞罗那理工 和 巴塞罗那超级计算中心
一个轻松的介绍性系列以一种实用的方式逐渐向读者介绍这项令人兴奋的技术,它是人工智能领域最新突破性进展的真正推动者。
本系列的内容](https://torres.ai/deep-reinforcement-learning-explained-series/)
关于这个系列
我在五月份开始写这个系列,那是在巴塞罗那的封锁期。老实说,由于封锁,在业余时间写这些帖子帮助了我 #StayAtHome 。感谢您当年阅读这份刊物;它证明了我所做的努力。
免责声明 —这些帖子是在巴塞罗纳被封锁期间写的,目的是分散个人注意力和传播科学知识,以防对某人有所帮助,但不是为了成为 DRL 地区的学术参考文献。如果读者需要更严谨的文档,本系列的最后一篇文章提供了大量的学术资源和书籍供读者参考。作者意识到这一系列的帖子可能包含一些错误,如果目的是一个学术文件,则需要对英文文本进行修订以改进它。但是,尽管作者想提高内容的数量和质量,他的职业承诺并没有留给他这样做的自由时间。然而,作者同意提炼所有那些读者可以尽快报告的错误。**
React Native 深度学习(仅限 iOS)
(图片由作者提供)
文章原载于 dev.to
介绍
在本教程中,我将涵盖如何构建移动应用程序和训练深度学习模型的所有步骤,以便您可以通过使用手机的摄像头预测 0 到 9 之间的手写数字。
预测手写数字的应用程序(作者图片)
但是在我们开始构建移动应用之前,我们需要想出一个高层次的策略。让我们回顾一下思考过程:
- 我们是构建一个纯 React-Native ( RN )还是一个 Expo 应用?
- 我们想用哪个相机库?
- 我们需要裁剪图像吗?我们需要使用什么样的库?
- 我们如何训练一个深度学习模型?
- 我们如何对照照片使用那个模型?
- 我们如何显示结果?
注意:本教程需要一些先决条件和对 RN 和 Javascript 的全面理解。如果你是一个绝对的初学者,我建议在继续学习本教程之前,先在 Youtube、Udemy 或 Egghead 上学习一门好的课程。
我们开始吧
我将把这个教程分成三个部分
第一章:创建 RN 应用
第二章:训练深度学习模型
第三章:实现模型,预测并展示结果
第 1 章:创建 RN 应用程序
还记得我们思考过程的第一点是创建一个裸应用还是 Expo 样板应用吗?
经过一些研究,我决定在本地加载训练好的模型。这是最简单的方法,不需要从云服务器获取模型,但是你也可以这样做。
在本教程中,我们将使用[@tensorflow/tfjs-react-native](http://twitter.com/tensorflow/tfjs-react-native)
中不幸与 Expo 不兼容的bundleResourceIO
。
此外,因为我们想使用相机,我们必须使用物理手机,而不是模拟器。为此,你必须有一个苹果开发者帐户来签署你的应用程序,否则你将无法运行该应用程序。
让我们使用以下命令创建应用程序:
$ react-native init MyFirstMLApp
安装过程完成后,请确保您的所有豆荚也已安装!
$ cd MyFirstMLApp
$ npx pod-install
让我们在你的物理 iPhone 上第一次运行这个应用程序。打开 Xcode,找到MyFirstMLApp.xcworkspace
并打开。使用 lightning 线缆将 iPhone 连接到 Mac,然后选择您的手机。首次构建和运行应用程序时,请按播放按钮。你应该会在你的 iPhone 上看到欢迎反应屏幕。
🏆牛逼!
让我们为这个应用程序添加一些包:
yarn add [@react](http://twitter.com/react)-native-community/async-storage [@react](http://twitter.com/react)-native-community/cameraroll [@tensorflow/tfjs](http://twitter.com/tensorflow/tfjs) [@tensorflow/tfjs-react-native](http://twitter.com/tensorflow/tfjs-react-native) expo-camera expo-gl expo-gl-cpp expo-image-manipulator react-native-fs react-native-svg react-native-unimodules victory-native
最后,安装导航库。
yarn add react-native-navigation && npx rnn-link
后一个命令会将导航包添加到 iOS 和 Android 中。但是我们还没有完成。
因为我们使用 RN 的裸框架,单模块需要手动安装。
请点击链接,并按照 iOS 部分所述修改Podfile
。那次跑步之后
$ npx pod-install
并构建 Xcode 项目,看看是否所有东西都已正确安装。
然后继续将单模块的代码添加到AppDelegate.m
中,并再次构建项目。
因为我们想用相机拍照,我们还需要给Info.plist
添加几个私钥
<?xml version=”1.0" encoding=”UTF-8"?>
<!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “[http://www.apple.com/DTDs/PropertyList-1.0.dtd](http://www.apple.com/DTDs/PropertyList-1.0.dtd)">
<plist version=”1.0">
<dict>
<! — Required for iOS 10 and higher -->
<key>NSCameraUsageDescription</key>
<string>We need to use the camera for taking pictures of the digits</string><! — Required for iOS 11 and higher: include this only if you are planning to use the camera roll -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to access the photo library to upload the images</string><! — Include for only if you are planning to use the camera roll -->
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to access the photo library to upload the images</string><! — Include this only if you are planning to use the microphone for video recording -->
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
如果 Xcode 构建良好,您可以继续从 Xcode 运行应用程序,或者只使用终端。
如果你决定从现在开始从命令行运行这个应用程序,像我一样,请将— device
添加到你的package.json
文件的ios
脚本中并运行
yarn ios
一旦应用程序在你的 iPhone 上启动,不要惊讶你再也看不到欢迎页面了。那是因为我们用了react-native-navigation
。但是你应该看到加载屏幕 MyFirstMLApp
现在是时候创建我们的 2 个屏幕,并将这些屏幕的导航添加到我们的项目中。
请在我们项目的根目录下创建src/screens/CameraView
和src/screens/EvaluationView
目录。
在src/screens/CameraView
中创建一个index.js
文件并添加以下代码:
import React, { useState, useRef, useEffect } from "react";
import {
SafeAreaView,
TouchableOpacity,
View,
Text,
StatusBar,
} from "react-native";
import { Navigation } from "react-native-navigation";
import { Camera } from "expo-camera";const MASK_DIMENSION = 100;export const CameraView = (props) => {
const [hasPermission, setHasPermission] = useState(null);
const [showShutterButton, setShowShutterButton] = useState(false);
const cameraRef = useRef();useEffect(() => {
(async () => {
const { status } = await Camera.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);const handlePictureProcessing = async () => {
goToEvaluationView();
};const goToEvaluationView = () => {
Navigation.push(props.componentId, {
component: {
name: "evaluationView",
options: {
topBar: {
title: {
text: "Evaluating ML result",
color: "white",
},
background: {
color: "#4d089a",
},
backButton: {
color: "white",
showTitle: false,
},
},
},
passProps: {},
},
});
};if (hasPermission === null) {
return <View />;
}if (hasPermission === false) {
return <Text> No access to camera </Text>;
}return (
<React.Fragment>
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.safeArea}>
<Camera
ref={cameraRef}
type={Camera.Constants.Type.back}
whiteBalance={Camera.Constants.WhiteBalance.auto}
onCameraReady={() => setShowShutterButton(true)}>
<View style={styles.cameraView}>
<View style={styles.mask} />
{showShutterButton && (
<TouchableOpacity
style={styles.shutterButton}
onPress={handlePictureProcessing}>
<Text style={styles.shutterButtonText}>
Take a picture
</Text>
</TouchableOpacity>
)}
</View>
</Camera>
</SafeAreaView>
</React.Fragment>
);
};const styles = {
safeArea: {
backgroundColor: "#4d089a",
},
cameraView: {
height: "100%",
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
},
mask: {
height: MASK_DIMENSION,
width: MASK_DIMENSION,
borderWidth: 3,
borderColor: "white",
borderStyle: "dotted",
borderRadius: 15,
},
shutterButton: {
position: "absolute",
bottom: 0,
width: 150,
height: 40,
justifyContent: "center",
alignItems: "center",
borderWidth: 1,
borderColor: "white",
borderRadius: 15,
marginBottom: 20,
},
shutterButtonText: {
fontSize: 18,
color: "white",
},
};CameraView.options = {
statusBar: {
backgroundColor: null,
},
topBar: {
title: {
text: "Take a picture",
color: "white",
},
background: {
color: "#4d089a",
},
},
tapBar: {
background: {
color: "#4d089a",
},
},
};
在src/screens/EvaluationView
中创建一个index.js
文件并添加以下代码:
import React from "react";
import { SafeAreaView, View, Text, StatusBar } from "react-native";export const EvaluationView = (props) => {
return (
<React.Fragment>
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.headerText}>ANALYSIS</Text>
</View>
</SafeAreaView>
</React.Fragment>
);
};const styles = {
safeArea: {
backgroundColor: "#4d089a",
},
container: {
height: "100%",
alignItems: "center",
backgroundColor: "white",
},
headerText: {
fontSize: 20,
fontWeight: "500",
color: "#4d089a",
margin: 20,
},
};
然后用下面的代码覆盖根目录中的index.js
文件:
import { Navigation } from "react-native-navigation";
import { CameraView } from "./src/screens/CameraView";
import { EvaluationView } from "./src/screens/EvaluationView";Navigation.registerComponent("cameraView", () => CameraView);
Navigation.registerComponent("evaluationView", () => EvaluationView);Navigation.setDefaultOptions({
statusBar: {
style: "light",
backgroundColor: "#4d089a",
},
topBar: {
title: {
color: "white",
},
background: {
color: "#4d089a",
},
backButton: {
color: "white",
showTitle: false,
},
},
});Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: {
children: [
{
component: {
name: "cameraView",
},
},
],
},
},
});
});
最后,您可以删除App.js
文件,因为不再需要它了。
重启你的 metro bundler,你应该会看到这个应用程序是这样运行的…
带有工作导航的应用程序的屏幕录制(图片由作者提供)
🏆恭喜恭喜!
您已经创建了基本应用程序,该应用程序还不能拍照,但可以从一个屏幕导航到另一个屏幕。
第二章:训练深度学习模型
最初,我使用的是这个来自 Kaggle 的预训练模型,但是让这个应用程序工作起来的努力是巨大的。
我不得不创建了一个 AWS EC2 深度学习 AMI(Amazon Linux 2)30.1 版本实例并使用 SSH 访问,因为我的 Macbook 不支持 CUDA。(训练模型需要 GPU 支持)
然后我必须从 Kaggle 复制Jupyter笔记本,运行笔记本在 AWS 实例上训练模型(它运行了 3 个小时)并将模型移回我的项目。
此外,我不得不安装 OpenGL 来修改图像,并编写了一个非常复杂的脚本来将 base64 字符串整形为张量,以匹配模型[1, 28, 28, 1]
的预期输入。
模型开始在 AWS 上训练(图片由作者提供)
所有这些让我重新思考如何写这篇教程。毕竟,本教程应该是为那些只想玩机器学习模型而没有事先学习 Python 、 Jupyter 、 Tensorflow 和 Keras 的人准备的。此外,教程的长度将是现在的 5 倍。
注:如果你想学习如何使用tensor flow**&Keras我用 deeplizard 找到了一个很好的关于深度学习的 Youtube 频道,内容非常丰富,也很符合我们在本教程中想要做的事情。
还有,Udemy上的这门课也不错,可惜不是免费的。😕**
反正为了这个教程,我决定用 谷歌的可教机器 来训练图像。
这个想法是用我们刚刚建立的应用程序拍摄 28 x 28 像素的图像,将图像上传到可教机器,并将训练好的模型下载回我们的项目。
以防你问我为什么用 28 x 28 像素的图片?这是我首先使用的模型的原始输入大小。所以我坚持了下来。
这也意味着我们必须裁剪并保存拍摄的图像到相机库中。为了做到这一点,我们需要稍微修改一下我们的代码。
请在CameraView
文件夹中创建一个helper.js
文件,并粘贴以下代码:
**import { Dimensions } from "react-native";
import * as ImageManipulator from "expo-image-manipulator";
import CameraRoll from "[@react](http://twitter.com/react)-native-community/cameraroll";const { height: DEVICE_HEIGHT, width: DEVICE_WIDTH } = Dimensions.get("window");// got the dimension from the trained data of the *Teachable Machine*; pixel resolution conversion (8x)
export const BITMAP_DIMENSION = 224;export const cropPicture = async (imageData, maskDimension) => {
try {
const { uri, width, height } = imageData;
const cropWidth = maskDimension * (width / DEVICE_WIDTH);
const cropHeight = maskDimension * (height / DEVICE_HEIGHT);
const actions = [
{
crop: {
originX: width / 2 - cropWidth / 2,
originY: height / 2 - cropHeight / 2,
width: cropWidth,
height: cropHeight,
},
},
{
resize: {
width: BITMAP_DIMENSION,
height: BITMAP_DIMENSION,
},
},
];
const saveOptions = {
compress: 1,
format: ImageManipulator.SaveFormat.JPEG,
base64: false,
};
return await ImageManipulator.manipulateAsync(uri, actions, saveOptions);
} catch (error) {
console.log("Could not crop & resize photo", error);
}
};export const saveToCameraRoll = async (uri) => {
try {
return await CameraRoll.save(uri, "photo");
} catch (error) {
console.log("Could not save the image", error);
}
};**
在src/screens/CameraView/index.js
中添加导入该文件
**import { cropPicture, saveToCameraRoll } from ‘./helpers’;**
添加takePicture
功能,并修改handlePictureProcessing
功能
**const handlePictureProcessing = async () => {
const imageData = await takePicture();
const croppedData = await cropPicture(imageData, MASK_DIMENSION);
await saveToCameraRoll(croppedData.uri);
// we don't want to go to the evaluation view now
//goToEvaluationView();
};const takePicture = async () => {
const options = {
quality: 0.1,
fixOrientation: true,
};
try {
return await cameraRef.current.takePictureAsync(options);
} catch (error) {
console.log("Could not take photo", error);
}
};**
如你所见,我们注释掉了行//goToEvaluationView();
,这样我们就不会转到另一个屏幕。这意味着您可以连续拍摄任意多张照片。现在,所有照片都将保存在您的照片库中。
我们的下一个任务是在一张纸上写出尽可能多的 0 到 9 之间的数字变化。我们使用的数字、颜色和笔的形状越多,预测就越准确。
我很懒,最后每个数字大概有 10 个变化,但是对于一些数字,比如 4 和 8,预测有点偏差。
书写数字形状的变化(图片由作者提供)
所以由你决定让可教机器训练多少个数字。
当你完成拍摄图像后,把它们全部空投回你的 Mac,从那里把它们上传到 可教机器 并开始训练它们。
训练好的模型截图(图片由作者提供)
完成后,你可以用你的应用程序拍摄更多的照片并上传,以测试训练好的模型。
如果你对结果满意,点击Export Model
->-Tensorflow.js
->-Download
->-Download my model
,会下载一个 ZIP 文件。
下载模型 poup(图片由作者提供)
解压 zip 文件,在src
目录(src/model
)下创建一个model
文件夹,并将model.json
和weights.bin
复制到该文件夹中。
我们还需要告诉 metro 处理新的文件格式:*.bin
。所以请这样修改metro.config.js
:
**const { getDefaultConfig } = require("metro-config");module.exports = (async () => {
const {
resolver: { assetExts },
} = await getDefaultConfig();
return {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
assetExts: [...assetExts, "bin"],
},
};
})();**
太好了!现在我们的模型已经在项目中了,让我们开始使用模型来预测数字。
第三章:实现模型,预测并展示结果
首先,我们不想再保存照片到我们的照片库中。(除非你愿意)。
于是注释掉了那行//await saveToCameraRoll(croppedData.uri);
。
我们还需要裁剪图像的base64 string
,最后,我们想通过props
将那个base64 string
传递给EvaluationView
。
让我们像这样再次修改我们的 CameraView src/screens/CameraView/index.js
文件:
**const handlePictureProcessing = async () => {
const imageData = await takePicture();
const croppedData = await cropPicture(imageData, MASK_DIMENSION);
// await saveToCameraRoll(croppedData.uri);
goToEvaluationView(croppedData);
};const goToEvaluationView = (croppedData) => {
Navigation.push(props.componentId, {
component: {
name: "evaluationView",
options: {
topBar: {
title: {
text: "Evaluating ML result",
color: "white",
},
background: {
color: "#4d089a",
},
backButton: {
color: "white",
showTitle: false,
},
},
},
passProps: {
base64: croppedData.base64 || null,
},
},
});
};**
🏆太棒了!
让我们在EvaluationView
中显示图像。从react-native
导入图像并将图像组件添加到View
容器中
**<View style={styles.container}>
<Text style={styles.headerText}>ANALYSIS</Text>
<Image
style={styles.imageContainer}
source={{ uri: `data:image/gif;base64,${props.base64}` }}
resizeMethod="scale"
/>
</View>;**
并在headerText
样式下添加imageContainer
的样式。
**imageContainer: {
height: 300,
width: 300,
},**
最后一步是转到src/screens/CameraView/helpers.js
文件,将saveOptions
更改为base64: true
。
🏆瞧!
你应该在分析文本下方的EvaluationView
中看到拍摄的图像。**
现在我们要显示预测结果。我们需要将胜利图表和一些react-native
包一起添加到EvaluationView
中
**import React from "react";
import {
Dimensions,
ActivityIndicator,
SafeAreaView,
View,
Image,
Text,
StatusBar,
} from "react-native";
import {
VictoryChart,
VictoryAxis,
VictoryBar,
VictoryTheme,
} from "victory-native";const { width: DEVICE_WIDTH } = Dimensions.get("window");**
为了获得设备的【the VictoryChart 需要的),我们使用了Dimensions
库。
然后添加胜利图容器。因为我们只想在得到预测结果后显示图表,所以我们添加了一个基于graphData.
长度的条件
由于我们还没有工作模型,我们必须添加一些假的图表数据来查看图表的水平条。
**import React from "react";
import {
Dimensions,
ActivityIndicator,
SafeAreaView,
View,
Image,
Text,
StatusBar,
} from "react-native";
import {
VictoryChart,
VictoryAxis,
VictoryBar,
VictoryTheme,
} from "victory-native";const { width: DEVICE_WIDTH } = Dimensions.get("window");export const EvaluationView = (props) => {
const graphData = [
{ number: 0, prediction: 0.04 },
{ number: 1, prediction: 0.02 },
{ number: 2, prediction: 0.02 },
{ number: 3, prediction: 0.1 },
{ number: 4, prediction: 0.85 },
{ number: 5, prediction: 0.04 },
{ number: 6, prediction: 0.2 },
{ number: 7, prediction: 0.12 },
{ number: 8, prediction: 0.0 },
{ number: 9, prediction: 0.0 },
];return (
<React.Fragment>
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.headerText}>ANALYSIS</Text>
<Image
style={styles.imageContainer}
source={{
uri: `data:image/gif;base64,${props.base64}`
}}
resizeMethod="scale"/>
<View style={styles.resultContainer}>
{graphData.length ? (
<VictoryChart
width={DEVICE_WIDTH - 20}
padding={{
top: 30, bottom: 70, left: 50, right: 30
}}
theme={VictoryTheme.material}>
<VictoryAxis
tickValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
tickFormat={[1, 2, 3, 4, 5, 6, 7, 8, 9]}/>
<VictoryAxis
dependentAxis
tickFormat={(tick) => tick} />
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
barRatio={0.8}
alignment="start"
data={graphData}
x="number"
y="prediction"/>
</VictoryChart>
) : (
<ActivityIndicator size="large" color="#4d089a" />
)}
</View>
</View>
</SafeAreaView>
</React.Fragment>
);
};**
你应该有一个这样的屏幕:
聊天显示虚假数据(图片由作者提供)
🏆你是冠军!
我们正在慢慢进入教程的最后一部分,在这里我们将加载模型,并将拍摄的照片与模型进行比较。
请在src
目录下创建一个util.js
并粘贴以下代码。
**/* eslint-disable no-bitwise */
/*
Copyright (c) 2011, Daniel Guerrero
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*//**
* Uses the new array typed in javascript to binary base64 encode/decode
* at the moment just decodes a binary base64 encoded
* into either an ArrayBuffer (decodeArrayBuffer)
* or into an Uint8Array (decode)
*
* References:
* [https://developer.mozilla.org/en/JavaScript_typed_arrays/ArrayBuffer](https://developer.mozilla.org/en/JavaScript_typed_arrays/ArrayBuffer)
* [https://developer.mozilla.org/en/JavaScript_typed_arrays/Uint8Array](https://developer.mozilla.org/en/JavaScript_typed_arrays/Uint8Array)
*/export const Base64Binary = {
_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",/* will return a Uint8Array type */
decodeArrayBuffer: function (input) {
var bytes = (input.length / 4) * 3;
var ab = new ArrayBuffer(bytes);
this.decode(input, ab);return ab;
},removePaddingChars: function (input) {
var lkey = this._keyStr.indexOf(input.charAt(input.length - 1));
if (lkey === 64) {
return input.substring(0, input.length - 1);
}
return input;
},decode: function (input, arrayBuffer) {
//get last chars to see if are valid
input = this.removePaddingChars(input);
input = this.removePaddingChars(input);var bytes = parseInt((input.length / 4) * 3, 10);var uarray;
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
var j = 0;if (arrayBuffer) {
uarray = new Uint8Array(arrayBuffer);
} else {
uarray = new Uint8Array(bytes);
}input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");for (i = 0; i < bytes; i += 3) {
//get the 3 octects in 4 ascii chars
enc1 = this._keyStr.indexOf(input.charAt(j++));
enc2 = this._keyStr.indexOf(input.charAt(j++));
enc3 = this._keyStr.indexOf(input.charAt(j++));
enc4 = this._keyStr.indexOf(input.charAt(j++));chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;uarray[i] = chr1;
if (enc3 !== 64) {
uarray[i + 1] = chr2;
}
if (enc4 !== 64) {
uarray[i + 2] = chr3;
}
}return uarray;
},
};**
出于对开发者的尊重请不要删除版权免责声明😃**
现在创建另一个helpers.js
文件,但这次是在EvaluationView
目录src/screens/EvaluationView/helpers.js
中,并复制这段代码
**import * as tf from "[@tensorflow/tfjs](http://twitter.com/tensorflow/tfjs)";
import "[@tensorflow/tfjs-react-native](http://twitter.com/tensorflow/tfjs-react-native)";
import { bundleResourceIO, decodeJpeg } from "[@tensorflow/tfjs-react-native](http://twitter.com/tensorflow/tfjs-react-native)";
import { Base64Binary } from "../../util";
import { BITMAP_DIMENSION } from "../CameraView/helpers";const modelJson = require("../../model/model.json");
const modelWeights = require("../../model/weights.bin");// 0: channel from JPEG-encoded image
// 1: gray scale
// 3: RGB image
const TENSORFLOW_CHANNEL = 3;export const getModel = async () => {
try {
// wait until tensorflow is ready
await tf.ready();
// load the trained model
return await tf.loadLayersModel(bundleResourceIO(modelJson, modelWeights));
} catch (error) {
console.log("Could not load model", error);
}
};export const convertBase64ToTensor = async (props) => {
try {
const uIntArray = Base64Binary.decode(props.base64);
// decode a JPEG-encoded image to a 3D Tensor of dtype
const decodedImage = decodeJpeg(uIntArray, 3);
// reshape Tensor into a 4D array
return decodedImage.reshape([
1,
BITMAP_DIMENSION,
BITMAP_DIMENSION,
TENSORFLOW_CHANNEL,
]);
} catch (error) {
console.log("Could not convert base64 string to tesor", error);
}
};export const startPrediction = async (model, tensor) => {
try {
// predict against the model
const output = await model.predict(tensor);
// return typed array
return output.dataSync();
} catch (error) {
console.log("Error predicting from tesor image", error);
}
};export const populateData = (typedArray) => {
const predictions = Array.from(typedArray);
return predictions.map((item, index) => {
return {
number: index,
prediction: item,
};
});
};**
这些是我们加载模型、将 base64 字符串转换为张量、预测数字和填充胜利图表数据的函数。
最后但同样重要的是,我们在src/screens/EvaluationView/index.js
的useEffect()
钩子中调用这些函数。
这是该视图的完整代码:
**import React, { useState, useEffect } from "react";
import {
Dimensions,
ActivityIndicator,
SafeAreaView,
View,
Image,
Text,
StatusBar,
} from "react-native";
import {
VictoryChart,
VictoryAxis,
VictoryBar,
VictoryTheme,
} from "victory-native";
import {
getModel,
convertBase64ToTensor,
startPrediction,
populateData,
} from "./helpers";const { width: DEVICE_WIDTH } = Dimensions.get("window");export const EvaluationView = (props) => {
const [graphData, setGraphData] = useState([]);useEffect(() => {
const predictDigits = async () => {
const model = await getModel();
const tensor = await convertBase64ToTensor(props);
const typedArray = await startPrediction(model, tensor);
setGraphData(populateData(typedArray));
};
predictDigits();
}, [props]);return (
<React.Fragment>
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.headerText}>ANALYSIS</Text>
<Image
style={styles.imageContainer}
source={{ uri: `data:image/gif;base64,${props.base64}` }}
resizeMethod="scale"
/>
<View style={styles.resultContainer}>
{graphData.length ? (
<VictoryChart
width={DEVICE_WIDTH - 20}
padding={{ top: 30, bottom: 70, left: 50, right: 30 }}
theme={VictoryTheme.material}
>
<VictoryAxis
tickValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
tickFormat={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
/>
<VictoryAxis dependentAxis tickFormat={(tick) => tick} />
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
barRatio={0.8}
alignment="start"
data={graphData}
x="number"
y="prediction"
/>
</VictoryChart>
) : (
<ActivityIndicator size="large" color="#4d089a" />
)}
</View>
</View>
</SafeAreaView>
</React.Fragment>
);
};const styles = {
safeArea: {
backgroundColor: "#4d089a",
},
container: {
height: "100%",
alignItems: "center",
backgroundColor: "white",
},
headerText: {
fontSize: 20,
fontWeight: "500",
color: "#4d089a",
margin: 20,
},
imageContainer: {
height: 300,
width: 300,
},
resultContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
};**
正如我之前提到的,模型会像你训练的模型一样好。
在现实世界中,数据工程师会使用数万种不同的手写数字来训练模型。然后将使用另一个集合来调整模型,并使用一个全新的集合来检查模型性能。
在我结束本教程之前,顺便提一下;如果你是一个经验丰富的 React 原生开发者,你现在应该已经意识到,通过一些手动导入,特别是
*react-native-unimodules*
、*expo-camera*
和权限设置,这个项目也可以在 Android 上开箱即用。🤓
我希望你已经学到了一些新东西。
如果我可以做一些不同的事情,或者如果你喜欢这个教程,请留下评论。毕竟我们都是来学习的,对吧?👨🏼🎓
10 分钟用深度 Java 库中的 Spark 进行深度学习
介绍
Apache Spark 是一种广泛用于数据处理的技术,被机器学习用户大量使用。Spark 可用于产品分类、需求预测和个性化推荐。虽然 Spark 支持多种编程语言,但首选的 Spark SDK 是为 Scala 实现的,大多数深度学习框架都没有很好的支持。大多数机器学习框架喜欢 Python 及其 SDK,给 Spark 开发人员留下了次优选择:将他们的代码移植到 Python 或实现定制的 Scala 包装器。这些选项会影响开发人员的速度,并通过脆弱的代码威胁生产环境。
在这篇博客中,我们展示了用户如何使用 Deep Java 库 (DJL)直接从 Scala 执行深度学习工作负载。DJL 是一个框架无关的库,开发它是为了在用 Java 开发的 Spark jobs 中直接提供深度学习。在下面的教程中,我们将使用 MXNet 完成一个图像分类场景,尽管 PyTorch 和 TensorFlow 也受支持。完整代码见 DJL 星火图像分类示例。
示例:使用 DJL 和火花进行图像分类
在本教程中,我们使用 resnet50 ,一个预先训练好的模型来运行推理。对于本教程,我们将使用一个具有三个工作节点的集群进行分类。工作流程如下图所示:
具有 3 个工作节点的 Spark 上的示例图像分类工作流
我们的示例将在流程中创建几个执行者,并为他们每个人分配任务。每个执行器包含一个或多个在不同线程中执行任务的核心。这为每个工作节点提供了平衡的大数据处理工作负载。
第一步。创建 Spark 项目
我们使用流行的开源工具 sbt 在 Scala 中构建这个 Spark 项目。您可以在这里找到更多关于如何开始使用 sbt 的资源。我们使用以下代码块在 sbt 中定义我们的项目:
name := "sparkExample"version := "0.1"scalaVersion := "2.11.12"
scalacOptions += "-target:jvm-1.8"resolvers += Resolver.mavenLocallibraryDependencies += "org.apache.spark" %% "spark-core" % "2.3.0"libraryDependencies += "ai.djl" % "api" % "0.5.0"
libraryDependencies += "ai.djl" % "repository" % "0.5.0"
// Using MXNet Engine
libraryDependencies += "ai.djl.mxnet" % "mxnet-model-zoo" % "0.5.0"
libraryDependencies += "ai.djl.mxnet" % "mxnet-native-auto" % "1.6.0"
本教程使用 MXNet 作为其底层引擎。切换到另一个框架很简单,如下例所示:
// Using PyTorch Engine
libraryDependencies += "ai.djl.pytorch" % "pytorch-model-zoo" % "0.5.0"
libraryDependencies += "ai.djl.pytorch" % "pytorch-native-auto" % "1.5.0"
步骤 2:配置 Spark
在本教程中,我们在本地机器上运行这个例子。Spark 应用程序将使用以下配置:
// Spark configuration
val conf = new SparkConf()
.setAppName("Simple Image Classification")
.setMaster("local[*]")
.setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine")
val sc = new SparkContext(conf)
MXNet 中的多线程推理需要NaiveEngine
参数。如果使用 PyTorch 或 TensorFlow,可以删除以下行:
.setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine")
步骤 3:识别输入数据
在本教程中,输入数据表示为包含要分类的图像的文件夹。Spark 将加载这些二进制文件,并将它们划分到不同的分区。每个分区由一个执行器执行。以下语句将把文件夹中的所有图像均匀地分布在每个分区上。
val partitions = sc.binaryFiles("images/*")
步骤 4:定义 Spark 工作
接下来,我们使用上一步中创建的分区为这个作业创建执行图。在 Spark 中,每个执行器以多线程的方式执行任务。因此,在执行推理之前,我们需要将每个模型加载到执行器中。我们使用以下代码进行设置:
// Start assign work for each worker node
val result = partitions.mapPartitions( partition => {
// before classification
val criteria = Criteria.builder
.optApplication(Application.CV.IMAGE_CLASSIFICATION)
.setTypes(classOf[BufferedImage], classOf[Classifications])
.optFilter("dataset", "imagenet")
.optFilter("layers", "50")
.optProgress(new ProgressBar)
.build
val model = ModelZoo.loadModel(criteria)
val predictor = model.newPredictor()
// classification
partition.map(streamData => {
val img = ImageIO.read(streamData._2.open())
predictor.predict(img).toString
})
})
需要为每个分区指定模型动物园的标准,以定位相应的模型并创建预测器。在分类过程中,我们从 RDD 加载图像,并为它们创建推论。
这个模型用 ImageNet 数据集训练,存储在 DJL 模型动物园。
步骤 5:定义输出位置
在我们完成映射过程之后,主节点将收集、聚合结果并将其保存在文件系统中。
result.collect().foreach(*print*)
result.saveAsTextFile("output")
运行这段代码会产生前面列出的输出类。输出文件将保存到不同分区的output
文件夹中。本教程完整代码请见 Scala 示例。
控制台的预期输出:
[
class: "n02085936 Maltese dog, Maltese terrier, Maltese", probability: 0.81445
class: "n02096437 Dandie Dinmont, Dandie Dinmont terrier", probability: 0.08678
class: "n02098286 West Highland white terrier", probability: 0.03561
class: "n02113624 toy poodle", probability: 0.01261
class: "n02113712 miniature poodle", probability: 0.01200
][
class: "n02123045 tabby, tabby cat", probability: 0.52391
class: "n02123394 Persian cat", probability: 0.24143
class: "n02123159 tiger cat", probability: 0.05892
class: "n02124075 Egyptian cat", probability: 0.04563
class: "n03942813 ping-pong ball", probability: 0.01164
][
class: "n03770679 minivan", probability: 0.95839
class: "n02814533 beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon", probability: 0.01674
class: "n03769881 minibus", probability: 0.00610
class: "n03594945 jeep, landrover", probability: 0.00448
class: "n03977966 police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria", probability: 0.00278
]
在您的生产系统中
在这种方法中,我们使用 RDD 来执行我们的图像作业,用于演示目的。随着数据帧使用和节省高速缓存的趋势,生产用户应该考虑为这些图像创建一个模式,并以数据帧格式存储它们。从 Spark 3.0 开始,Spark 提供了一个二进制文件阅读器选项,使得图像转换为数据帧更加方便。
个案研究
亚马逊零售系统(ARS)使用 DJL 通过 Spark 路由的大量数据流进行了数百万次预测。这些预测使用成千上万的客户属性来确定客户在多个类别中采取行动的倾向,然后向客户呈现相关的广告和横幅。
ARS 使用数十万个特征,拥有数亿个客户——超过十万亿个数据点。他们需要一个能够有效扩展的解决方案。为了解决这个关键问题,他们最初为自己的工作创建了一个 Scala 包装器,但是这个包装器有内存问题,执行起来很慢。在采用 DJL 之后,他们的解决方案与 Spark 完美配合,总的推理时间从几天降到了几个小时。
敬请关注我们的下一篇博文,我们将深入探讨并介绍更多 ARS 面临的挑战,以及他们如何通过与 DJL 建立管道来解决这个问题。
了解更多关于 DJL 的信息
完成本教程后,你可能想知道 DJL 是什么。深度 Java 库(DJL)是一个开源库,用于在 Java 中构建和部署深度学习。该项目于 2019 年 12 月推出,由亚马逊各地的工程团队使用。这一努力受到了其他 DL 框架的启发,但是是从底层开始开发的,以更好地适应 Java 开发实践。DJL 是框架不可知的,支持 Apache MXNet、PyTorch、TensorFlow 2.x(实验)和 fastText(实验)。
要了解更多,请查看我们的网站、 Github 资源库和 Slack channel。
使用尖峰网络的深度学习:优化能耗
神经形态硬件提供了通过尖峰进行通信的超低功耗神经网络,有点像真正的神经元。但是我们如何训练他们呢?
神经形态芯片的特写。照片 Ole Richter/aiCTX AG
在如今被称为“神经网络”(在深度学习的意义上)和大脑中的神经元网络之间有几个重要的区别。一个特别明显的例子是,人工网络具有模拟激活——人工神经元的输出是一个连续的数字。相反,生物神经元通过称为动作电位或*尖峰的离散、全有或全无的电事件进行交流。*当然,我们也可以模拟和使用尖峰网络(SNNs)。
你可能想训练一个尖峰神经网络有两个原因:也许,你正试图理解大脑计算或学习的某些方面;或者,您可能希望使用脉冲网络来执行机器学习任务,就像您使用卷积网络(CNN)等“常规”神经网络一样。
你为什么要做这种事?如何训练一个尖峰网络肯定不如深度学习中使用的反向传播和梯度下降的过程那样为人所知。尖峰网络不会优于 CNN,并且训练和模拟会困难得多。然而,人们有兴趣这样做。这是因为与传统计算机相比,脉冲网络可以在极低功耗的神经形态硬件上运行*。这是因为神经形态芯片是专门为低功率推理而构建的,并在其电路中复制一个尖峰神经元的动态,而不是在计算机上模拟它。其次,尖峰网络以基于事件的方式进行计算,这使得它们成为处理基于事件的数据的理想工具,例如动态视觉传感器的输出。*
DVS 录音示例。图像由记录了位置和时间的事件(类似于尖峰)组成。没有帧率。当场景中没有变化时,就没有事件(因此背景大部分是不可见的)。(自己的工作)
神经形态工程是一个不断发展的领域,开发有用的脉冲网络模型是一个相应的有趣的挑战。可以使用“替代梯度”技巧通过梯度下降来训练脉冲网络,避免信号离散性的问题,但这仍然复杂而缓慢[1,4]。当我们对生物合理性不感兴趣,也对时间相关方面不太感兴趣时,事实证明训练传统(模拟)CNN 更容易,然后在尖峰网络上使用相同的权重[2]。你可以想象,如果放电频率足够高,在给定的时间间隔内接收到的棘波数量可以很好地近似一个连续的数字:CNN 中相应神经元的激活值。这就是计算神经科学家所说的速率编码。
优化能源消耗的费率代码
我们试图以稍微聪明一点的方式使用这种蛮力方法。如我所说,在速率编码中,每个神经元都试图用特定的尖峰频率来代表激活。这个频率应该和激活成正比,但是应该有多大呢?1.42 这个数字应该用每秒多少个尖峰来表示?有两种风险:
- 如果尖峰信号的数量太低,我们最终会离散化激活,我们将无法安全地分辨出,比如说,1.42 和 1.35,因为它们都由相同数量的尖峰信号表示。
- 如果尖峰信号太多,我们将会消耗更多的能量。当网络静默时,神经形态硬件几乎不使用任何功率,但每当每个下游神经元接收到一个尖峰信号时,都会消耗能量。这些突触操作的数量与神经形态芯片的有效功耗成正比。顺便说一句,大脑也面临类似的限制。
我们介绍了两种对策,帮助我们在这两个问题之间找到正确的平衡。我们的方法类似于[5]中为 TrueNorth 神经形态系统开发的方法。
量化感知训练
首先,虽然训练是在常规的模拟 CNN 上进行的,但我们希望我们的网络至少能够意识到它的激活将在某个点上被转移到一个脉冲 convnet 上,信号将被强制离散化。为此,我们在每个 ReLU 层将所有 CNN 激活向下舍入到下一个整数值。这是为了保护我们免受上面列出的两个问题中的第一个。训练过程将考虑离散化误差。
正如一些读者已经猜到的,这导致梯度几乎在所有地方都变为零,对反向传播造成灾难性的后果。但不要担心:我们总是可以用一个替代(即假)梯度来装备我们的“量化”ReLU (QReLU),这忽略了离散化。换句话说,我们在向前传递时进行向下舍入,但在向后传递时不进行向下舍入。
QReLU 响应函数。(自己的工作)
如果您想知道如何欺骗 PyTorch 将手动选择的渐变替换为实际渐变,那么使用自定义的 backward()定义 PyTorch 函数非常简单:
# Defining a PyTorch function with a custom backward()
class _Quantize(torch.autograd.Function):
@staticmethod
# ctx is a "context object" that we don't need
def forward(ctx, input):
# round down
return input.floor()
@staticmethod
def backward(ctx, grad_output):
# don't do anything (identity function)
return grad_output.clone()
训练期间的量化并不是一个新的想法,实际上也越来越受欢迎,但在一个相当不同的背景下。当我们想为激活、网络参数或两者使用低精度数据类型(通常是 int8,而不是 PyTorch 中的标准,即 float32)来节省内存时,这很有用。PyTorch 最近引入了这个特性。
最小化突触操作的数量
我们在做机器学习,我们想最小化一些东西,我们该怎么做?当然是加在损失上。
即使在训练期间,我们也可以估计 SNN 中将要发生的突触操作的数量,这是在它完全变成 SNN 之前。我们通过观察我们的量化激活来估计尖峰的数量,并将每个神经元的尖峰率乘以该神经元的“扇出”——将接收其尖峰的神经元数量。我们为这个量设定一个目标(我称之为“SynOps”),并给损失加上一个二次惩罚。
有了这两个工具,即“SynOp loss”和量化感知训练,我们可以训练更好的脉冲卷积神经网络来解决类似 CNN 的计算机视觉任务。值得重复的是,我们离大脑中的尖峰网络学习的目标还很远。但这不是我们的目标(我希望现在已经很清楚了)。
测试
我们在两个物体识别任务上测试了我们的 snn。一个是常见的 CIFAR10,另一个是 MNIST-DVS ,这是一组用动态视觉传感器(基于事件的相机)记录的移动 MNIST 手指的短视频。
来自 MNIST-DVS 数据集的样本,显示数字 2。(自己的工作)
我们在适当的尖峰网络模拟上测试结果,并在测试集上测量概要(记住,它与网络的能量消耗成比例)和分类准确度。
MNIST-DVS 的调查结果。(来源:[3])
在 MNIST-DVS 数据集上,与上下缩放权重(或输入)以减少活动的基线方法相比,我们获得了明显更好的能量准确性平衡。例如,用我们的方法训练的特别好的网络具有比基线稍低的准确度(从 96.3%到 95.0%),几乎低一个数量级的天气计数。在 CIFAR10 上,我们实现了非常高的 SNN 精度,但功耗却低得多。
文献学
[1] E. Neftci,H. Mostafa 和 F. Zenke,脉冲神经网络中的代理梯度学习:将基于梯度的优化的力量引入脉冲神经网络 (2019),IEEE 信号处理杂志 36,6。
[2] B. Rueckauer 等连续值深度网络向高效事件驱动网络的转换用于图像分类, (2017)神经科学前沿。
[3] M. Sorbaro,Q. Liu,M. Bortone 和 S. Sheik,优化用于神经形态应用的脉冲神经网络的能量消耗 (2019),arXiv 预印本。
[4] Shrestha,S. B .,& Orchard,G. (2018 年)。杀戮者:钉层错误重新分配时间。在神经信息处理系统的进展(第 1412-1421 页)。
[5]埃塞、S. K .、梅罗拉、P. A .、阿瑟、J. V .、卡西迪、阿普斯瓦米、r .、安德烈奥普洛斯、a .、… &巴奇、D. R. (2016)。用于快速、节能神经形态计算的卷积网络。2016.ArXiv 上的预印本。。
更多信息
请阅读(并引用)我们在 arXiv 上提供的预印本。这项工作已经作为原始研究文章提交给神经科学前沿专题。
复制我们的作品
GitLab 上有一个公共知识库供那些想要复制这项工作的人使用。您还需要下载(免费提供的)数据集。这在自述文件中有解释,但是如果您需要帮助,请联系我们。
使用 TensorFlow 进行深度学习
深度学习
深度学习是机器学习的子集,机器学习是人工智能的子集。人工智能是一种使机器能够模仿人类行为的技术。深度学习是一种机器学习,受人脑结构的启发。这种结构被称为人工神经网络。
深度学习 vs 机器学习
机器学习使用算法来解析数据,从数据中学习,并根据所学内容做出决策。深度学习将算法结构化为多层,以创建一个“人工神经网络”,它可以自己学习并做出智能决策。虽然两者都属于人工智能的大范畴,但深度学习是最像人类的人工智能的动力。
什么是神经网络?
神经网络是人脑的简化。它是由神经元层构成的。这些神经元是网络的核心处理单元。一个神经网络由三种类型的层组成:输入层、输出层和隐藏层。首先,我们有接收输入的输入层和预测最终输出的输出层。在中间,我们有隐藏层,它执行我们的网络所需的大部分计算。此外,值得一提的是,神经网络被设计用来识别数据中的模式。
问题
让我们尝试解决上一篇帖子的问题—https://towards data science . com/big-data-analyses-with-machine-learning-and-py spark-135119 ef6b 31。在这个特殊的案例中,我们需要在一个金融机构中进行多种统计计算。一个例子是根据独立变量的数量来确定客户是否有存款。这次我们将尝试使用 TensorFlow 来训练模型。
数据预处理
数据预处理是机器学习中至关重要的一步,对模型的准确性至关重要。这种技术允许我们将原始数据转换成干净可用的数据集。使用这一步,我们将通过重新缩放、标准化、二进制化等使数据更有意义。我们正在使用的数据集是经过预处理的(关于这一点的更多信息,请参见上一篇文章https://towardsdatascience . com/data-preprocessing-for-machine-learning-in-python-2d 465 f 83 f 18 c)。
在这种情况下,我们应该对数据集做的唯一事情就是将它分成训练集、验证集和测试集。
- 训练数据集:用于拟合模型的数据样本。
- 验证数据集:数据样本,用于在调整模型超参数时,对训练数据集上的模型拟合提供无偏评估。
- 测试数据集:用于提供最终模型在训练数据集上的无偏评估的数据样本。
此外,我们将把数据保存在。npz 文件格式。NPZ 是 numpy 开发的一种文件格式,使用 gzip 压缩提供数组数据的存储。
张量流
深度学习的主要软件工具是 TensorFlow。它是一个开源的人工智能库,使用数据流图来建立模型。张量流主要用于:分类、感知、理解、发现、预测、创造。TensorFlow 的五个主要用例是:
- 语音/声音识别
- 基于文本的应用
- 图像识别
- 时间序列
- 视频检波
导入库
我们需要导入将要使用的库。在我们的例子中,我们需要:
- Numpy—Python 编程语言的库,增加了对大型多维数组和矩阵的支持,以及对这些数组进行操作的大量高级数学函数
- TensorFlow —一个免费的开源软件库,用于数据流和跨一系列任务的差异化编程。
加载数据
首先,我们需要从之前保存的文件中加载数据。这可以通过使用 numpy 的 load 方法来完成。
定义模型
我们需要定义输出大小—在我们的例子中,我们期望两个输出(1 —是和 0 —否),所以大小将是 2。
接下来,我们需要定义隐藏层的大小。隐藏层的大小应介于输入层和输出层的大小之间。在这种情况下,隐藏层大小将为 30。
唯一剩下的事情就是定义模型。本例中的模型是使用 keras 定义的。顺序模型是层的线性堆叠。当我们定义模型时,我们需要为隐藏层和输出层选择激活函数。
激活功能
激活函数是神经网络的组成部分之一。如果我们有一个没有激活函数的神经网络,每个神经元将只使用权重和偏差对输入执行线性变换。这样定义的话,神经网络的功能将会减弱,它将无法从数据中学习复杂的模式。因此,激活函数用于将非线性变换添加到输入中。在下图中,我们可以看到可以使用的常用激活功能。
在这种情况下,我们对隐藏层使用 ReLU(校正线性单位),对输出层使用 Softmax。
- ReLU 是神经网络中最常用的激活函数,尤其是在 CNN 中。如果您不确定在您的网络中使用什么激活功能,ReLU 通常是一个不错的首选。
- Softmax 是指数型的,会扩大差异—将一个结果推至接近 1,而将另一个结果推至接近 0。它将分数即逻辑转换成概率。
编译模型
在这一步中,我们将为培训配置模型。Compile 定义了损失函数、优化器和指标。我们需要一个编译好的模型来训练(因为训练使用了损失函数和优化器)。
- 优化器:优化算法或策略负责减少损失,并尽可能提供最准确的结果。在这个例子中,我们使用了一种叫做 Adam 的优化算法。 Adam 是一种优化算法,可用于替代经典的随机梯度下降过程,根据训练数据迭代更新网络权重。
- Loss :这是一种评估特定算法对给定数据建模程度的方法。如果预测与实际结果相差太多,损失函数就会产生一个非常大的数字。分类交叉熵和稀疏分类交叉熵具有相同的损失函数,唯一的区别是,当输入是一次热编码时,我们使用分类交叉熵,当输入是整数时,我们使用稀疏分类交叉熵。
- 指标:指标是用来判断模型性能的函数。
拟合模型
当我们拟合模型时,我们实际上是在为固定数量的历元(数据集上的迭代)训练模型。我们需要定义批量大小、最大时期和提前停止。
- 批量 —一次正反向通过的训练样本数。
- 最大时期 —训练模型的时期数。一个历元是对所提供的整个 x 和 y 数据的迭代。
- 提前停止 —当监控的数量停止提高时,停止训练。耐心是一个数字,它定义了产生无改善的监控量的时期数,在此之后训练将停止。
结果
在下图中,我们可以看到训练的结果。如果我们将准确性(86%)与之前博客帖子中的准确性(82%)进行比较,我们可以看到,在这种情况下,它更大。
评估模型
返回测试模式下模型的损耗值和度量值。在这个函数中,输入数据将是我们的测试集。该函数将返回标量测试损失(如果模型只有一个输出,没有指标)或标量列表(如果模型有多个输出和/或指标)。
结论
这就是深度学习的基本思想。有许多类型的深度学习、不同种类的神经网络、各种架构和训练算法。神经网络背后的想法已经存在了很长时间,但今天它有了很大的进展。深度学习的未来特别光明!未来深度学习的可能性是无限的,从无人驾驶汽车到探索宇宙的机器人。
如果你对这个话题感兴趣,请随时联系我。
领英简介:【https://www.linkedin.com/in/ceftimoska/
博客原文可从以下链接获得:【https://interworks.com.mk/deep-learning-with-tensorflow/
另外,你可以在下面的链接中找到另一篇类似的博文:https://interworks.com.mk/focusareas/data-management/
基于 FastAI 的不平衡表格数据加权交叉熵损失深度学习
轻松构建深度学习模型同时避免陷阱的指南
照片由 Unsplash 上的 Aditya Das 拍摄
FastAI 是一个非常方便和强大的机器学习库,将深度学习(DL)带给大众。我写这篇文章的动机是为了解决与训练二进制分类任务的模型相关的一些问题。我的目标是向您介绍使用 FastAI 为一个 表格 , 不平衡 数据集构建一个简单有效的 DL 模型所需的步骤,同时避免我曾经犯过的错误。本文中的讨论是根据下面列出的部分组织的。
- 资料组
- 示例代码
- 代码分解
- FastAI 与 PySpark ML & Scikit-Learn 的比较
- 结论
1.资料组
数据集来自 ad 转换的上下文,其中 二进制 目标变量 1 和 0 对应转换成功和失败。这个专有数据集(不,我没有权利)有一些特别有趣的属性,因为它的维度、类别不平衡以及特征和目标变量之间相当弱的关系。
首先,数据的维度:这个表格数据集包含相当大量的记录和 分类特征 ,它们具有非常高的*。*
注 :在 FastAI 中,分类特征使用 嵌入 来表示,这样可以提高高基数特征的分类性能。
二、二进制 类标签 高度不平衡由于成功的广告转换比较少见。在本文中,我们通过算法级方法(加权交叉熵损失函数)来适应这种约束,而不是数据级方法(重采样)。**
三、 关系特征 与 目标变量 之间的 是相当弱的 。例如,在显著的模型调整后,逻辑回归模型的 ROC 曲线下的验证面积为 0.74。**
数据集属性摘要
- 维度:17 个特征,1 个目标变量,3738937 行
- 二元目标类
- 阶层失衡比例为 1:87
- 6 个数字特征
- 8 个分类特征
- 分类要素的组合基数为 44,000
2.示例代码
这段代码是在 Google Cloud — AI 平台 上的 Jupyter Lab 笔记本上运行的,规格如下。
- 4 个 N1 标准 vCPUs,15 GB 内存
- 1 个英伟达特斯拉 P4 GPU
- 环境:PyTorch 1.4
- 操作系统:Debian 9
模型培训管道将在下一节中解释,如下所示。
用于表格数据二进制分类的 FastAI 流水线
3.代码分解
导入包
通过命令终端安装fastai
和fastbook
。有关设置的更多详细信息,请查看此链接。
**conda install -c fastai -c pytorch fastai
git clone https://github.com/fastai/fastbook.git
pip install -Uqq fastbook**
将 FastAI 库和熊猫导入笔记本。
**import pandas as pd
from fastai.tabular.all import ***
按要素类型加载数据和分组列名
由于隐私原因,列名必须匿名。
**df = pd.read_csv('data/training.csv')# Categorical Features
CAT_NAMES = ['col_1', 'col_2', 'col_3', 'col_4',
'col_5', 'col_6', 'col_7', 'col_8'] # Continuous Features
CONT_NAMES = ['col_9', 'col_10', 'col_11',
'col_12', 'col_13', 'col_14'] # Target Variable
TARGET = 'target'**
强制转换目标变量数据类型
将二进制目标变量的数据类型改为类别。
**df[TARGET] = df[TARGET].astype('category')**
陷阱#1 :如果目标变量数据类型保留为数值,FastAI/PyTorch 会将其视为数值,并产生运行时错误。**
实例化数据加载器
接下来,列出数据预处理程序、训练/验证集拆分并创建表格数据加载器。
**# Data Processors
procs = [Categorify, FillMissing, Normalize] # Training/Validation Dataset 80:20 Split
splits = RandomSplitter(valid_pct=0.2)(range_of(df)) dls = TabularDataLoaders.from_df(df,
y_names=TARGET,
cat_names=CAT_NAMES,
cont_names=CONT_NAMES,
procs=procs,
splits=splits)**
使用dls.xs
查看转换后的训练数据。
构造损失函数权重
类别不平衡被用于创建交叉熵损失函数的权重,从而确保多数类别被相应地向下加权。此处使用的砝码公式与 scikit-learn 和 PySPark ML 中的公式相同。
**class_count_df = df.groupby(TARGET).count()
n_0, n_1 = class_count_df.iloc[0, 0], class_count_df.iloc[1, 0]
w_0 = (n_0 + n_1) / (2.0 * n_0)
w_1 = (n_0 + n_1) / (2.0 * n_1)
class_weights=torch.FloatTensor([w_0, w_1]).cuda()**
陷阱#2 : 确保将类权重转换为浮点张量,并通过.cuda()
启用 cuda 操作。否则,你会得到一个类型的错误。**
**TypeError: cannot assign 'list' object to buffer 'weight' (torch Tensor or None required)**
实例化 ROC 指标下的区域
**roc_auc = RocAucBinary()**
陷阱#3 :对于二进制类标签,使用RocAucBinary()
而不是RocAuc()
,以避免值错误。**
**ValueError: y should be a 1d array, got an array of shape (2000, 2) instead.**
实例化损失函数
**loss_func = CrossEntropyLossFlat(weight=class_weights)**
陷阱#5: 使用 FastAI 交叉熵损失函数,与torch.nn.CrossEntropyLoss()
的 PyTorch 等价函数相反,以避免错误。此处列出了 FastAI 损失函数。使用 PyTorch 交叉熵损失给了我以下运行时错误。
**RuntimeError: Expected object of scalar type Long but got scalar type Char for argument #2 'target' in call to _thnn_nll_loss_forward**
实例化学习者
使用 FastAI 中的tabular_learner
轻松实例化一个架构。
**learn = tabular_learner(dls,
layers=[500, 250],
loss_func=loss_func,
metrics=roc_auc)**
仔细检查learn
是否使用了正确的损失函数:
**learn.loss_func
Out [1]: FlattenedLoss of CrossEntropyLoss()**
模型训练和验证分数
在所需的历元数上训练模型。
**learn.fit_one_cycle(3)**
训练和验证集的性能度量和损失函数值如下所示。
ROC 曲线下面积在短短 3 个时期内达到 0.75!
太好了!通过最小的调整,FastAI 模型比使用 PySpark 和 Scikit-Learn 精心构建的模型性能更好。
4.FastAI 与 PySpark ML & Scikit-Learn 逻辑回归模型的比较
在这一节中,我们比较了这三个 ML 库的模型性能和计算时间。
****注:虽然神经网络在没有大量超参数调整的情况下表现良好,但 PySpark ML 和 Scikit-Learn 则不然。因此,我添加了这些时间,因为它们与训练下降模型相关。
模型训练时间
- FastAI — 6 分钟
- PySpark ML — 0.7 秒+ 38 分钟用于超参数调整
- Scikit-Learn — 36 秒+ 8 分钟用于超参数调整(基于数据的子样本)
ROC 曲线下面积
- FastAI — 0.75
- PySpark ML — 0.74
- Scikit-Learn — 0.73
更多详情,请查看我的 Github 回购。
5.结论
在本文中,我们看到了 FastAI 在快速构建 DL 模型方面的强大功能。在使用 FastAI 之前,我会推迟使用神经网络,直到我已经尝试了逻辑回归、随机森林等等。因为神经网络难以调整且计算昂贵。然而,随着通过谷歌人工智能平台笔记本和分层 FastAI 方法对 GPU 的可访问性增加,现在它肯定会是我在大型复杂数据集上进行分类任务时首先使用的工具之一。
深度学习的数学
罗马法师在 Unsplash 上拍摄的照片
探索深度学习成功背后的数学和方程式
深度学习是基于人工神经网络的机器学习科学的一个分支。它有几个衍生物,如多层感知器-MLP,卷积神经网络-CNN-和递归神经网络-RNN-可应用于许多领域,包括计算机视觉,自然语言处理,机器翻译…
深度学习的兴起有三个主要原因:
- 本能特征工程:虽然大多数机器学习算法需要人类的专业知识来进行特征工程和提取,但深度学习会自动处理变量及其权重的选择
- 巨大的数据集:持续不断的数据收集产生了大型数据库,这使得更深层次的神经网络成为可能
- 硬件发展:用于图形处理单元的新 GPU 允许更快的代数计算,这是 DL 的核心基础
在这篇博客中,我们将主要关注多层感知器-MLP-我们将详细介绍深度学习成功背后的数学背景,并探索用于提高其性能的优化算法。
摘要如下:
- 定义
- 学习算法
- 参数初始化
- 正向-反向传播
- 激活功能
- 最优化算法
注意:因为 Medium 不支持 LaTeX,所以数学表达式是作为图像插入的。因此,为了更好的阅读体验,我建议你关闭黑暗模式。
1-定义
神经元
它是一组连接实体的数学运算
让我们考虑这样一个问题,我们根据房子的大小来估计房子的价格,它可以被图解如下:
一般来说,神经网络更好地被称为 MLP,意为“多层感知器”,是一种直接形式的神经网络,被组织成若干层,其中信息仅从输入层流向输出层。
每一层都由一定数量的神经元组成,我们区分:
-输入层
-隐藏层
-输出层
下图表示一个神经网络,其输入端有 5 个神经元,第一个隐藏层有 3 个,第二个隐藏层有 3 个,输出端有 2 个。
隐含层中的一些变量可以根据输入特征来解释:在房屋定价的情况下,在假设第一个隐含层的第一个神经元更关注变量x1etx2的情况下,可以解释为例如房屋的家庭规模的量化。
作为监督任务的 DL
在大多数 DL 问题中,我们倾向于使用一组变量 X 来预测输出 y,在这种情况下,我们假设对于数据库的每一行 X_i 我们都有相应的预测 y_i ,因此有标记的数据。
应用:房地产、语音识别、图像分类……
使用的数据可以是:
- 结构化:明确定义了特性的数据库
- 非结构化:音频、图像、文本……
通用逼近定理
现实生活中的深度学习是给定函数 f 的近似。由于以下定理,这种近似是可能的和精确的:
(*)在有限维中,如果一个集合是闭且有界的,则称它是紧的。更多详情,请访问此链接。
这种算法的主要优点是深度学习允许解决任何可以用数学表达的问题
数据预处理
一般来说,在任何机器学习项目中,我们将数据分为 3 组:
-训练集:用于训练算法和构造批次
- Dev set :用于微调算法,评估偏差和方差
-测试集:用于概括最终算法的误差/精度
下表根据数据集 m 的大小总结了三个数据集的重新划分:
标准的深度学习算法需要一个大的数据集,其中样本数量大约为行。现在数据已经准备好了,我们将在下一节看到训练算法。通常,在分割数据之前,我们还会对输入进行归一化处理,这一步将在本文后面详细介绍。
2.学习算法
神经网络中的学习是计算与整个网络中各种回归相关的参数权重的步骤。换句话说,我们的目标是从输入开始,找到给出真实值的最佳预测/近似的最佳参数。
为此,我们定义了一个目标函数,称为loss function
,记为J
,它量化了整个训练集的实际值和预测值之间的距离。我们通过以下两个主要步骤来最小化 J:
**- Forward Propagation**
:我们通过网络完整地或成批地传播数据,并且我们计算该批上的损失函数,该损失函数只不过是在不同行的预测输出处提交的误差的总和。
**- Backpropagation**
:包括计算关于不同参数的成本函数的梯度,然后应用下降算法来更新它们。
我们多次重复同样的过程,称为epoch number
。定义架构后,学习算法编写如下:
(∫)成本函数 L 计算单个点上实际值和预测值之间的距离。
3.参数初始化
定义神经网络结构后的第一步是参数初始化。这相当于将初始噪声注入模型的权重。
- **零初始化:**我们可以考虑用 0 来初始化参数,即:W=0,b=0
使用正向传播方程,我们注意到所有隐藏单元将是对称的,这不利于学习阶段。
- **随机初始化:**这是一种常用的替代方法,包括在参数中注入随机噪声。如果噪声太大,一些激活函数可能会饱和,这可能会影响梯度的计算。
两种最著名的初始化方法是:
Xavier
的:它包括用从服从正态分布的中心变量中随机抽样的值填充参数;
Glorot
的:相同的方法有不同的方差:
4.正向和反向传播
在深入研究深度学习背后的代数之前,我们将首先设置注释,该注释将用于解释正向和反向传播的方程。
神经网络的表示法
神经网络是一系列的regressions
后跟一个activation function
。它们都定义了我们所说的正向传播。并且是每层的学习参数。反向传播也是一系列从输出到输入的代数运算。
正向传播
- 通过网络代数
让我们考虑具有如下L layers
的神经网络:
通过训练集的代数
让我们考虑通过神经网络预测单行数据帧的输出。
当处理一个 m 行的数据集时,对每一行分别重复这些操作是非常昂贵的。
我们在每一层都有[ 我 ]:
参数 b_i 使用广播通过列重复自身。下图对此进行了总结:
反向传播
反向传播是学习的第二步,它包括将预测(正向)阶段犯下的错误注入网络,并更新其参数以在下一次迭代中表现更好。
因此,函数 J 的优化,通常通过下降法。
计算图形
大多数下降方法需要计算表示为∇ J ( θ )的损失函数的梯度。
在神经网络中,使用将函数 J 分解成几个中间变量的计算图进行运算。
我们来考虑下面这个函数: f ( x , y ,z)=(x+y)。 z
我们使用两个通道进行计算:
- 正向传播:计算从输入到输出的 f 的值:f(2,5,4)=-12
- 反向传播:递归应用链规则计算从输出到输入的梯度:
导数可以在下面的计算图中恢复:
方程式
数学上,我们计算成本函数 J ,w.r.t 架构参数 W 和 b 的梯度。
其中(⋆)是逐元素乘法。
我们递归地将这些等式应用于 i = L ,L1,…,1
梯度检查
当执行反向传播时,增加了额外的检查以确保代数计算是正确的。
算法:
它应该接近 ϵ 的值,当量值比 ϵ.高一千倍时,怀疑有错误
我们可以在下面的方框中总结前向和后向传播:
参数与超参数
-参数,表示为 θ ,是我们通过迭代学习的元素,我们对其应用反向传播和更新: W 和 b.
-超参数是我们在算法中定义的所有其他变量,可以调整以改进神经网络:
- 学习率 α
- 迭代次数
- 激活功能的选择
- 层数 L
- 每层中的单元数量
5.激活功能
激活函数是一种选择在神经网络中传播的数据的传递函数。潜在的解释是,只有当网络中的神经元被充分激发时,才允许它传播学习数据(如果它处于学习阶段)。
以下是最常见的函数列表:
备注:如果激活函数都是线性的,那么神经网络就相当于一个简单的线性回归
6.最优化算法
风险
让我们考虑一个用 f 表示的神经网络。优化的真正目标被定义为所有语料库的预期损失:
其中 X 是来自一个连续的可观测空间的一个元素,对应于一个目标 Y 和 p ( X , Y )是观测到该对的边际概率( X , Y )。
经验风险
由于我们不能拥有所有的语料库,因此我们忽略了分布,我们将风险的估计限制在能很好地代表整个语料库的某个数据集上,并考虑所有情况都是等概率的。
在这种情况下:我们将 m 设为代表性语料库的大小,我们得到:∫=∑和 p ( X , Y )=1/m。因此,我们迭代优化损失函数,定义如下:
另外我们可以断言:
存在许多技术和算法,主要基于梯度下降来执行优化。在下面的章节中,我们将介绍最著名的几个。值得注意的是,这些算法可能会陷入局部极小值,没有什么能保证达到全局最优。
标准化输入
在优化损失函数之前,我们需要对输入进行归一化,以加快学习速度。在这种情况下, J ( θ )变得更紧密和更对称,这有助于梯度下降更快地找到最小值,从而减少迭代次数。
标准数据是常用的方法,包括减去变量的平均值并除以它们的标准差。考虑到这一点,下图说明了对右侧标准数据等高线上的输入进行归一化的效果:
假设 X 是我们数据库中的一个变量,我们设置:
梯度下降
通常,我们倾向于构造一个convex
和differentiable
函数 J ,其中任何局部极小值都是全局极小值。从数学上讲,寻找凸函数的全局最小值等价于求解方程∇ J ( θ )=0,我们把它的解记为 θ ⋆。
大多数使用的算法是这样的:
小批量梯度下降
这项技术包括将训练集分成几批:
小批量的选择:
- 少量行 2000 行
- 典型大小:2 的幂,有利于记忆
- 小批量应该适合 CPU/GPU 内存
备注:在批处理中只有一条数据线的情况下,该算法称为随机梯度下降
动量梯度下降
包括动量概念的梯度下降的变体,算法如下:
( α , β )为超参数。
由于 dθ 是在小批量上计算的,因此产生的梯度∇ J 噪声很大,动量中包含的指数加权平均值可以更好地估计导数。
RMSprop
均方根 prop 非常类似于带动量的梯度下降,唯一的区别是它包括二阶动量而不是一阶动量,加上参数更新的微小变化:
( α , β )是超参数,而 ϵ 确保数值稳定性(≈108)
圣经》和《古兰经》传统中)亚当(人类第一人的名字
Adam 是一种自适应学习率优化算法,专门用于训练深度神经网络。Adam 可以看作是 RMSprop 和带动量的梯度下降的组合。
它使用平方梯度将学习率设置为 RMSprop,并通过使用梯度的移动平均值而不是梯度本身来利用动量,因为梯度随动量下降。
主要思想是通过加速向正确方向下降来避免优化过程中的振荡。
Adam 优化器的算法如下:
学习率衰减
学习率衰减的主要目的是随着时间/迭代缓慢降低学习率。它在这样一个事实中找到了合理性,即我们在学习开始时可以迈出大步,但当接近全局最小值时,我们会放慢速度,从而降低学习速度。
学习率存在许多衰减规律,下面是一些最常见的:
正规化
方差/偏差
训练神经网络时,它可能会遇到以下问题:
- 偏高:或者欠拟合,网络在数据中找不到路径,这种情况下 J_train 非常高与 J_dev 相同。从数学上讲,进行交叉验证时;在所有考虑的褶皱上, J 的平均值较高。
- 高方差或过拟合,模型完全符合训练数据,但无法对未见过的数据进行概括,在这种情况下, J_train 非常低,而 J_dev 相对较高。从数学上讲,进行交叉验证时;在所有考虑的褶皱上 J 的方差很高。
让我们考虑一下飞镖游戏,击中红色目标是最好的情况。拥有低偏差(第一行)意味着平均而言我们接近目标。在**低方差的情况下,**击中全部集中在目标周围(击中分布的方差低)。当方差较高时,在低偏差的假设下,命中分散开,但仍在红色圆圈周围。
反之亦然,我们可以用低/高方差来定义高偏差。
从数学上讲,设 f 为真回归函数:y =f(x)+ϵ其中: ϵ~N(0,σ )
我们用 MSE 拟合一个假设h(x)=wx+b并认为 x_0 是一个新的数据点,【t
通过使用 AIC 标准或交叉验证,必须在方差和偏差之间找到一个平衡点,以找到模型的最佳复杂性。
以下是解决偏差/差异问题的简单方案:
L1 — L2 正规化
正则化是一种防止过度拟合的优化技术。
它包括在目标函数中添加一项,以最小化如下:
λ 是正则化的超参数。
反向传播和正则化
反向传播期间参数的更新取决于梯度∇ J ,其中增加了新的正则化项。在 L2 正规化,它变成如下:
考虑到 λ > > 1,最小化成本函数导致参数的弱值,因为项(λ/2m)∨θ∨)简化了网络并使其更加一致,因此较少暴露于过拟合。
退学正规化
粗略地说,主要思想是采样一个均匀的随机变量,for each layer for each node
,并且有 p 的机会保留该节点,并且有 1p的机会移除该节点,这减小了网络。辍学的主要直觉是基于这样的想法,网络不应该依赖于一个特定的特征,而是应该分散权重!
从数学上讲,当 dropout 关闭时,考虑到第I层的第j节点,我们有以下等式:
当 dropout 打开时,方程式如下:
提前停止
该技术非常简单,包括当 J_train 和 J_dev 开始分离时,停止该区域周围的迭代:
梯度问题
梯度的计算有两个主要问题:梯度消失和梯度爆炸。
为了说明这两种情况,让我们考虑一个神经网络,其中所有激活函数 ψ [ i ]都是线性的,并且:
我们注意到,1,5^((T7)将作为深度 l 的函数按指数规律爆炸。如果我们使用 0.5 而不是 1.5,那么 0,5^(l-1(T7)也将按指数规律消失。
渐变也会出现同样的问题。
结论
作为一名数据科学家,了解神经网络背景下的数学转变非常重要。这允许更好的理解和更快的调试。
不要犹豫,检查我以前的文章处理:
机器学习快乐!
参考
原载于 2020 年 1 月 31 日https://www.ismailmebsout.com。