PaperSpace-博客中文翻译-七-

PaperSpace 博客中文翻译(七)

原文:PaperSpace Blog

协议:CC BY-NC-SA 4.0

在 Keras 中使用 Lambda 图层

原文:https://blog.paperspace.com/working-with-the-lambda-layer-in-keras/

Keras 是一个流行且易于使用的库,用于构建深度学习模型。它支持所有已知类型的层:输入层、密集层、卷积层、转置卷积层、整形层、归一化层、下降层、展平层和激活层。每一层都对数据执行特定的操作。

也就是说,您可能希望对未应用于任何现有图层的数据执行操作,然后这些现有图层类型将无法满足您的任务。举一个简单的例子,假设您需要一个层来执行在模型架构的给定点添加固定数字的操作。因为没有现有的层可以做到这一点,所以您可以自己构建一个。

在本教程中,我们将讨论在 Keras 中使用Lambda层。这允许您指定要作为函数应用的操作。我们还将看到在构建具有 lambda 层的模型时如何调试 Keras 加载特性。

本教程涵盖的部分如下:

  • 使用Functional API构建 Keras 模型
  • 添加一个Lambda
  • 将多个张量传递给λ层
  • 保存和加载带有 lambda 层的模型
  • 加载带有 lambda 层的模型时解决系统错误

使用Functional API 构建 Keras 模型

有三种不同的 API 可用于在 Keras 中构建模型:

  1. 顺序 API
  2. 功能 API
  3. 模型子类化 API

你可以在这篇文章中找到关于这些的更多信息,但是在本教程中,我们将重点关注使用 Keras Functional API来构建一个定制模型。由于我们想专注于我们的架构,我们将只使用一个简单的问题示例,并建立一个模型来识别 MNIST 数据集中的图像。

要在 Keras 中构建模型,您需要将层堆叠在另一层之上。这些层在keras.layers模块中可用(在下面导入)。模块名由tensorflow前置,因为我们使用 TensorFlow 作为 Keras 的后端。

import tensorflow.keras.layers

要创建的第一层是Input层。这是使用tensorflow.keras.layers.Input()类创建的。传递给这个类的构造函数的必要参数之一是shape参数,它指定了将用于训练的数据中每个样本的形状。在本教程中,我们将只使用密集层,因此输入应该是 1-D 矢量。因此,shape参数被赋予一个只有一个值的元组(如下所示)。值为 784,因为 MNIST 数据集中每个影像的大小为 28 x 28 = 784。可选的name参数指定该层的名称。

input_layer = tensorflow.keras.layers.Input(shape=(784), name="input_layer")

下一层是根据下面的代码使用Dense类创建的密集层。它接受一个名为units的参数来指定该层中神经元的数量。请注意该图层是如何通过在括号中指定该图层的名称来连接到输入图层的。这是因为函数式 API 中的层实例可在张量上调用,并且也返回张量。

dense_layer_1 = tensorflow.keras.layers.Dense(units=500, name="dense_layer_1")(input_layer)

在密集层之后,根据下一行使用ReLU类创建一个激活层。

activ_layer_1 = tensorflow.keras.layers.ReLU(name="activ_layer_1")(dense_layer_1)

根据下面的代码行,添加了另外两个 dense-ReLu 层。

dense_layer_2 = tensorflow.keras.layers.Dense(units=250, name="dense_layer_2")(activ_layer_1)
activ_layer_2 = tensorflow.keras.layers.ReLU(name="relu_layer_2")(dense_layer_2)

dense_layer_3 = tensorflow.keras.layers.Dense(units=20, name="dense_layer_3")(activ_layer_2)
activ_layer_3 = tensorflow.keras.layers.ReLU(name="relu_layer_3")(dense_layer_3)

下一行根据 MNIST 数据集中的类数量将最后一个图层添加到网络架构中。因为 MNIST 数据集包括 10 个类(每个数字对应一个类),所以此图层中使用的单位数为 10。

dense_layer_4 = tensorflow.keras.layers.Dense(units=10, name="dense_layer_4")(activ_layer_3)

为了返回每个类的分数,根据下一行在前一密集层之后添加一个softmax层。

output_layer = tensorflow.keras.layers.Softmax(name="output_layer")(dense_layer_4)

我们现在已经连接了层,但是模型还没有创建。为了构建一个模型,我们现在必须使用Model类,如下所示。它接受的前两个参数代表输入和输出层。

model = tensorflow.keras.models.Model(input_layer, output_layer, name="model")

在加载数据集和训练模型之前,我们必须使用compile()方法编译模型。

model.compile(optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005), loss="categorical_crossentropy")

使用model.summary()我们可以看到模型架构的概述。输入层接受一个形状张量(None,784 ),这意味着每个样本必须被整形为一个 784 元素的向量。输出Softmax图层返回 10 个数字,每个数字都是该类 MNIST 数据集的分数。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_layer (InputLayer)     [(None, 784)]             0         
_________________________________________________________________
dense_layer_1 (Dense)        (None, 500)               392500    
_________________________________________________________________
relu_layer_1 (ReLU)          (None, 500)               0         
_________________________________________________________________
dense_layer_2 (Dense)        (None, 250)               125250    
_________________________________________________________________
relu_layer_2 (ReLU)          (None, 250)               0         
_________________________________________________________________
dense_layer_3 (Dense)        (None, 20)                12550     
_________________________________________________________________
relu_layer_3 (ReLU)          (None, 20)                0         
_________________________________________________________________
dense_layer_4 (Dense)        (None, 10)                510       
_________________________________________________________________
output_layer (Softmax)       (None, 10)                0         
=================================================================
Total params: 530,810
Trainable params: 530,810
Non-trainable params: 0
_________________________________________________________________

现在我们已经构建并编译了模型,让我们看看数据集是如何准备的。首先,我们将从keras.datasets模块加载 MNIST,将它们的数据类型更改为float64,因为这使得训练网络比将其值留在 0-255 范围内更容易,最后重新调整,使每个样本都是 784 个元素的向量。

(x_train, y_train), (x_test, y_test) = tensorflow.keras.datasets.mnist.load_data()

x_train = x_train.astype(numpy.float64) / 255.0
x_test = x_test.astype(numpy.float64) / 255.0

x_train = x_train.reshape((x_train.shape[0], numpy.prod(x_train.shape[1:])))
x_test = x_test.reshape((x_test.shape[0], numpy.prod(x_test.shape[1:])))

因为在compile()方法中使用的损失函数是categorical_crossentropy,样本的标签应该根据下一个代码进行热编码。

y_test = tensorflow.keras.utils.to_categorical(y_test)
y_train = tensorflow.keras.utils.to_categorical(y_train)

最后,模型训练开始使用fit()方法。

model.fit(x_train, y_train, epochs=20, batch_size=256, validation_data=(x_test, y_test))

至此,我们已经使用已经存在的层类型创建了模型架构。下一节讨论使用Lambda层构建定制操作。

使用λ层

假设在名为dense_layer_3的稠密层之后,我们想要对张量进行某种操作,比如给每个元素加值 2。我们如何做到这一点?现有的层都没有这样做,所以我们必须自己建立一个新的层。幸运的是,Lambda层的存在正是为了这个目的。大家讨论一下怎么用吧。

首先构建将执行所需操作的函数。在这种情况下,名为custom_layer的函数创建如下。它只接受输入张量并返回另一个张量作为输出。如果不止一个张量被传递给函数,那么它们将作为一个列表被传递。

在这个例子中,只有一个张量作为输入,输入张量中的每个元素加 2。

def custom_layer(tensor):
    return tensor + 2

在构建了定义操作的函数之后,接下来我们需要使用下一行中定义的Lambda类创建 lambda 层。在这种情况下,只有一个张量被提供给custom_layer函数,因为 lambda 层可以在名为dense_layer_3的稠密层返回的单个张量上调用。

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")(dense_layer_3)

下面是使用 lambda 层后构建完整网络的代码。

input_layer = tensorflow.keras.layers.Input(shape=(784), name="input_layer")

dense_layer_1 = tensorflow.keras.layers.Dense(units=500, name="dense_layer_1")(input_layer)
activ_layer_1 = tensorflow.keras.layers.ReLU(name="relu_layer_1")(dense_layer_1)

dense_layer_2 = tensorflow.keras.layers.Dense(units=250, name="dense_layer_2")(activ_layer_1)
activ_layer_2 = tensorflow.keras.layers.ReLU(name="relu_layer_2")(dense_layer_2)

dense_layer_3 = tensorflow.keras.layers.Dense(units=20, name="dense_layer_3")(activ_layer_2)

def custom_layer(tensor):
    return tensor + 2

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")(dense_layer_3)

activ_layer_3 = tensorflow.keras.layers.ReLU(name="relu_layer_3")(lambda_layer)

dense_layer_4 = tensorflow.keras.layers.Dense(units=10, name="dense_layer_4")(activ_layer_3)
output_layer = tensorflow.keras.layers.Softmax(name="output_layer")(dense_layer_4)

model = tensorflow.keras.models.Model(input_layer, output_layer, name="model")

为了查看馈送到 lambda 层之前和之后的张量,我们将创建两个新模型,除了上一个模型。我们将这些称为before_lambda_modelafter_lambda_model。两种模型都使用输入层作为输入,但输出层不同。before_lambda_model模型返回dense_layer_3的输出,T3 是正好存在于 lambda 层之前的层。after_lambda_model模型的输出是来自名为lambda_layer的λ层的输出。这样做,我们可以看到应用 lambda 层之前的输入和之后的输出。

before_lambda_model = tensorflow.keras.models.Model(input_layer, dense_layer_3, name="before_lambda_model")
after_lambda_model = tensorflow.keras.models.Model(input_layer, lambda_layer, name="after_lambda_model")

下面列出了构建和训练整个网络的完整代码。

import tensorflow.keras.layers
import tensorflow.keras.models
import tensorflow.keras.optimizers
import tensorflow.keras.datasets
import tensorflow.keras.utils
import tensorflow.keras.backend
import numpy

input_layer = tensorflow.keras.layers.Input(shape=(784), name="input_layer")

dense_layer_1 = tensorflow.keras.layers.Dense(units=500, name="dense_layer_1")(input_layer)
activ_layer_1 = tensorflow.keras.layers.ReLU(name="relu_layer_1")(dense_layer_1)

dense_layer_2 = tensorflow.keras.layers.Dense(units=250, name="dense_layer_2")(activ_layer_1)
activ_layer_2 = tensorflow.keras.layers.ReLU(name="relu_layer_2")(dense_layer_2)

dense_layer_3 = tensorflow.keras.layers.Dense(units=20, name="dense_layer_3")(activ_layer_2)

before_lambda_model = tensorflow.keras.models.Model(input_layer, dense_layer_3, name="before_lambda_model")

def custom_layer(tensor):
    return tensor + 2

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")(dense_layer_3)
after_lambda_model = tensorflow.keras.models.Model(input_layer, lambda_layer, name="after_lambda_model")

activ_layer_3 = tensorflow.keras.layers.ReLU(name="relu_layer_3")(lambda_layer)

dense_layer_4 = tensorflow.keras.layers.Dense(units=10, name="dense_layer_4")(activ_layer_3)
output_layer = tensorflow.keras.layers.Softmax(name="output_layer")(dense_layer_4)

model = tensorflow.keras.models.Model(input_layer, output_layer, name="model")

model.compile(optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005), loss="categorical_crossentropy")
model.summary()

(x_train, y_train), (x_test, y_test) = tensorflow.keras.datasets.mnist.load_data()

x_train = x_train.astype(numpy.float64) / 255.0
x_test = x_test.astype(numpy.float64) / 255.0

x_train = x_train.reshape((x_train.shape[0], numpy.prod(x_train.shape[1:])))
x_test = x_test.reshape((x_test.shape[0], numpy.prod(x_test.shape[1:])))

y_test = tensorflow.keras.utils.to_categorical(y_test)
y_train = tensorflow.keras.utils.to_categorical(y_train)

model.fit(x_train, y_train, epochs=20, batch_size=256, validation_data=(x_test, y_test))

请注意,您不必编译或训练这两个新创建的模型,因为它们的层实际上是从存在于model变量中的主模型中重用的。在这个模型被训练之后,我们可以使用predict()方法返回before_lambda_modelafter_lambda_model模型的输出,看看 lambda 层的结果如何。

p = model.predict(x_train)

m1 = before_lambda_model.predict(x_train)
m2 = after_lambda_model.predict(x_train)

下一段代码只打印前两个样本的输出。可以看到,从m2数组返回的每个元素实际上都是m1加 2 后的结果。这正是我们在自定义 lambda 层中应用的操作。

print(m1[0, :])
print(m2[0, :])

[ 14.420735    8.872794   25.369402    1.4622561   5.672293    2.5202641
 -14.753801   -3.8822086  -1.0581762  -6.4336205  13.342142   -3.0627508
  -5.694006   -6.557313   -1.6567478  -3.8457105  11.891999   20.581928
   2.669979   -8.092522 ]
[ 16.420734    10.872794    27.369402     3.462256     7.672293
   4.520264   -12.753801    -1.8822086    0.94182384  -4.4336205
  15.342142    -1.0627508   -3.694006    -4.557313     0.34325218
  -1.8457105   13.891999    22.581928     4.669979    -6.0925217 ]

在本节中,lambda 层用于对单个输入张量进行运算。在下一节中,我们将看到如何将两个输入张量传递给这一层。

将一个以上的张量传递给λ层

假设我们想要做一个依赖于名为dense_layer_3relu_layer_3的两层的操作。在这种情况下,我们必须调用 lambda 层,同时传递两个张量。这可以简单地通过创建一个包含所有这些张量的列表来完成,如下一行所示。

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")([dense_layer_3, activ_layer_3])

这个列表被传递给custom_layer()函数,我们可以简单地根据下一个代码获取各个层。它只是把这两层加在一起。Keras 中实际上有一个名为Add的层,可以用来添加两层或更多层,但是我们只是展示如果有另一个 Keras 不支持的操作,你可以自己怎么做。

def custom_layer(tensor):
    tensor1 = tensor[0]
    tensor2 = tensor[1]
    return tensor1 + tensor2

接下来的代码构建了三个模型:两个用于捕获传递给 lambda 层的来自dense_layer_3activ_layer_3的输出,另一个用于捕获 lambda 层本身的输出。

before_lambda_model1 = tensorflow.keras.models.Model(input_layer, dense_layer_3, name="before_lambda_model1")
before_lambda_model2 = tensorflow.keras.models.Model(input_layer, activ_layer_3, name="before_lambda_model2")

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")([dense_layer_3, activ_layer_3])
after_lambda_model = tensorflow.keras.models.Model(input_layer, lambda_layer, name="after_lambda_model")

为了查看来自dense_layer_3activ_layer_3lambda_layer层的输出,下一段代码预测它们的输出并打印出来。

m1 = before_lambda_model1.predict(x_train)
m2 = before_lambda_model2.predict(x_train)
m3 = after_lambda_model.predict(x_train)

print(m1[0, :])
print(m2[0, :])
print(m3[0, :])

[ 1.773366   -3.4378722   0.22042789 11.220362    3.4020965  14.487111
  4.239182   -6.8589864  -6.428128   -5.477719   -8.799093    7.264849
 17.503246   -6.809489   -6.846208   16.094025   24.483786   -7.084775
 17.341183   20.311539  ]
[ 1.773366    0\.          0.22042789 11.220362    3.4020965  14.487111
  4.239182    0\.          0\.          0\.          0\.          7.264849
 17.503246    0\.          0\.         16.094025   24.483786    0.
 17.341183   20.311539  ]
[ 3.546732   -3.4378722   0.44085577 22.440723    6.804193   28.974222
  8.478364   -6.8589864  -6.428128   -5.477719   -8.799093   14.529698
 35.006493   -6.809489   -6.846208   32.18805    48.96757    -7.084775
 34.682365   40.623077  ]

使用 lambda 层现在很清楚了。下一节将讨论如何保存和加载使用 lambda 层的模型。

保存并加载带有 Lambda 图层的模型

为了保存一个模型(不管它是否使用 lambda 层),使用了save()方法。假设我们只对保存主模型感兴趣,下面是保存它的代码行。

model.save("model.h5")

我们还可以使用load_model()方法加载保存的模型,如下一行所示。

loaded_model = tensorflow.keras.models.load_model("model.h5")

希望模型能够成功加载。不幸的是,Keras 中的一些问题可能会导致在加载带有 lambda 层的模型时出现SystemError: unknown opcode。这可能是由于使用 Python 版本构建模型并在另一个版本中使用它。我们将在下一节讨论解决方案。

加载带有 Lambda 层的模型时解决系统错误

为了解决这个问题,我们不打算以上面讨论的方式保存模型。相反,我们将使用save_weights()方法保存模型权重。

现在我们只保留了重量。模型架构呢?将使用代码重新创建模型架构。为什么不将模型架构保存为 JSON 文件,然后再次加载呢?原因是加载架构后错误仍然存在。

总之,经过训练的模型权重将被保存,模型架构将使用代码被复制,并且最终权重将被加载到该架构中。

可以使用下一行保存模型的权重。

model.save_weights('model_weights.h5')

下面是复制模型架构的代码。model将不会被训练,但保存的权重将再次分配给它。

input_layer = tensorflow.keras.layers.Input(shape=(784), name="input_layer")

dense_layer_1 = tensorflow.keras.layers.Dense(units=500, name="dense_layer_1")(input_layer)
activ_layer_1 = tensorflow.keras.layers.ReLU(name="relu_layer_1")(dense_layer_1)

dense_layer_2 = tensorflow.keras.layers.Dense(units=250, name="dense_layer_2")(activ_layer_1)
activ_layer_2 = tensorflow.keras.layers.ReLU(name="relu_layer_2")(dense_layer_2)

dense_layer_3 = tensorflow.keras.layers.Dense(units=20, name="dense_layer_3")(activ_layer_2)
activ_layer_3 = tensorflow.keras.layers.ReLU(name="relu_layer_3")(dense_layer_3)

def custom_layer(tensor):
    tensor1 = tensor[0]
    tensor2 = tensor[1]

    epsilon = tensorflow.keras.backend.random_normal(shape=tensorflow.keras.backend.shape(tensor1), mean=0.0, stddev=1.0)
    random_sample = tensor1 + tensorflow.keras.backend.exp(tensor2/2) * epsilon
    return random_sample

lambda_layer = tensorflow.keras.layers.Lambda(custom_layer, name="lambda_layer")([dense_layer_3, activ_layer_3])

dense_layer_4 = tensorflow.keras.layers.Dense(units=10, name="dense_layer_4")(lambda_layer)
after_lambda_model = tensorflow.keras.models.Model(input_layer, dense_layer_4, name="after_lambda_model")

output_layer = tensorflow.keras.layers.Softmax(name="output_layer")(dense_layer_4)

model = tensorflow.keras.models.Model(input_layer, output_layer, name="model")

model.compile(optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005), loss="categorical_crossentropy")

下面是如何使用load_weights()方法加载保存的权重,并将其分配给复制的架构。

model.load_weights('model_weights.h5')

结论

本教程讨论了使用Lambda层创建自定义层,该层执行 Keras 中预定义层不支持的操作。Lambda类的构造函数接受一个指定层如何工作的函数,该函数接受调用层的张量。在函数内部,您可以执行任何想要的操作,然后返回修改后的张量。

尽管 Keras 在加载使用 lambda 层的模型时存在问题,但我们也看到了如何通过保存训练好的模型权重、使用代码重新生成模型架构,并将权重加载到该架构中来简单地解决这个问题。

成为 Paperspace 贡献者💎

原文:https://blog.paperspace.com/write-for-paperspace/

TLDR:写关于机器学习、深度学习、三维图形等主题的文章可以获得报酬。

我们不断地将我们的博客发展成为一个有价值的社区资源,涵盖所有与机器学习、深度学习、3d 建模、游戏等相关的事物。我们一直在寻找想要在这个领域有所作为的人。

为什么要为 Paperspace 写作?

产生影响,发展你的个人品牌,并在做的同时获得回报。💵

我们的文章每个月都有成千上万的观众想要了解更多关于人工智能的话题。如果你一直在关注我们的博客,你可能已经看过像 Ravi Munde 的构建指南 Dino Run 这样的文章,Harsh Sikka 的关于用 fastai 构建细菌分类器的教程,或者 Sudharshan Babu 的合成媒体信息指南。

syntheticmedia

我们为所有的作者提供个性化的支持和反馈,包括写作和代码。我们正在寻找一些有教育意义、有洞察力的作品,帮助人们了解更多关于 ML 和相关主题的知识。

如果愿意,我们也可以用 GPU 信用支付作家(免费使用我们的 GPU)。

我应该写些什么?

我们一直在寻找关于以下主题的文章和教程:

  • 三维建模、动画、渲染等。
  • 解释常见问题的答案(例如,“我应该使用 PyTorch 还是 TensorFlow?”)深入
  • 涉及或专注于特定 ML/DL 库(PyTorch、TensorFlow、Scikit-learn 等)的项目。)
  • 剖析/基准测试最近的模型
  • 自然语言处理
  • 计算机视觉
  • AI/ML/DL 中的热点话题
  • 技术深度潜水
  • 框架比较
  • 工具集概述
  • 最佳实践
  • 操作指南

还没想好主题吗?我们总是有一个现成的建议清单。

我如何开始?

只需填写我们的谷歌表格!

Apply Today

用 PyTorch 从头开始编写 CNN

原文:https://blog.paperspace.com/writing-cnns-from-scratch-in-pytorch/

介绍

在本文中,我们将在 PyTorch 中从头开始构建卷积神经网络(CNN ),并在现实世界的数据集上训练和测试它们。

我们将从探索 CNN 是什么以及它们是如何工作的开始。然后我们将研究 PyTorch,并从使用torchvision(一个包含各种数据集和与计算机视觉相关的辅助函数的库)加载 CIFAR10 数据集开始。然后我们将从头开始建立和训练我们的 CNN。最后,我们将测试我们的模型。

下面是文章的提纲:

  • 介绍
  • 卷积神经网络
  • PyTorch
  • 数据加载
  • CNN 从零开始
  • 设置超参数
  • 培养
  • 测试

卷积神经网络

卷积神经网络(CNN)获取输入图像并将其分类到任何输出类别中。每个图像经过一系列不同的层,主要是卷积层、池层和全连接层。下图总结了一幅图像在 CNN 中的表现:

Source: https://www.mathworks.com/discovery/convolutional-neural-network-matlab.html

卷积层

卷积层用于从输入图像中提取特征。它是输入图像和内核(过滤器)之间的数学运算。滤波器通过图像,输出计算如下:

Source: https://www.ibm.com/cloud/learn/convolutional-neural-networks

不同的过滤器用于提取不同种类的特征。下面给出了一些常见的特征:

Source: https://en.wikipedia.org/wiki/Kernel_(image_processing)

池层

池层用于减小任何图像的大小,同时保持最重要的功能。最常用的池层类型是最大池和平均池,它们分别从给定大小的过滤器(如 2x2、3x3 等)中获取最大值和平均值。

例如,最大池的工作方式如下:

Source: https://cs231n.github.io/convolutional-networks/


PyTorch

PyTorch 是最受欢迎和使用最广泛的深度学习库之一——尤其是在学术研究领域。这是一个开源的机器学习框架,加速了从研究原型到生产部署的过程,我们今天将在本文中使用它来创建我们的第一个 CNN。


数据加载

资料组

让我们从加载一些数据开始。我们将使用 CIFAR-10 数据集。该数据集具有 60,000 幅 32px x 32px 的彩色图像(RGB ),属于 10 个不同的类(6000 幅图像/类)。数据集分为 50,000 个训练图像和 10,000 个测试图像。

您可以在下面看到数据集及其类的示例:

Source: https://www.cs.toronto.edu/~kriz/cifar.html

导入库

让我们从导入所需的库并定义一些变量开始:

# Load in relevant libraries, and alias where appropriate
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

# Define relevant variables for the ML task
batch_size = 64
num_classes = 10
learning_rate = 0.001
num_epochs = 20

# Device will determine whether to run the training on GPU or CPU.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 

Importing Libraries

device将决定是在 GPU 还是 CPU 上运行训练。

数据集加载

为了加载数据集,我们将使用torchvision中的内置数据集。它为我们提供了下载数据集和应用任何我们想要的转换的能力。

让我们先看看代码:

# Use transforms.compose method to reformat images for modeling,
# and save to variable all_transforms for later use
all_transforms = transforms.Compose([transforms.Resize((32,32)),
                                     transforms.ToTensor(),
                                     transforms.Normalize(mean=[0.4914, 0.4822, 0.4465],
                                                          std=[0.2023, 0.1994, 0.2010])
                                     ])
# Create Training dataset
train_dataset = torchvision.datasets.CIFAR10(root = './data',
                                             train = True,
                                             transform = all_transforms,
                                             download = True)

# Create Testing dataset
test_dataset = torchvision.datasets.CIFAR10(root = './data',
                                            train = False,
                                            transform = all_transforms,
                                            download=True)

# Instantiate loader objects to facilitate processing
train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)

Loading and Transforming Data

让我们仔细分析这段代码:

  • 我们从写一些转换开始。我们调整图像的大小,将其转换为张量,并通过使用输入图像中每个波段的平均值和标准偏差对其进行归一化。你也可以计算这些,但是可以在网上找到。
  • 然后,我们加载数据集:训练和测试。我们将 download 设置为 True,以便在尚未下载的情况下下载它。
  • 一次将整个数据集加载到 RAM 中不是一个好的做法,这会严重地使您的计算机停机。这就是我们使用数据加载器的原因,它允许您通过批量加载数据来遍历数据集。
  • 然后,我们创建两个数据加载器(用于训练/测试),并将批处理大小和 shuffle 设置为 True,这样每个类的图像都包含在一个批处理中。

CNN 从零开始

在深入研究代码之前,让我们解释一下如何在 PyTorch 中定义神经网络。

  • 首先创建一个新类,它扩展了 PyTorch 的nn.Module类。当我们创建神经网络时,这是需要的,因为它为我们提供了一堆有用的方法
  • 然后,我们必须定义神经网络的层次。这是在类的__init__方法中完成的。我们简单地命名我们的层,然后将它们分配到我们想要的适当的层;例如卷积层、汇集层、全连接层等。
  • 最后要做的是在我们的类中定义一个forward方法。此方法的目的是定义输入数据通过不同层的顺序

现在,让我们深入代码:

# Creating a CNN class
class ConvNeuralNet(nn.Module):
	#  Determine what layers and their order in CNN object 
    def __init__(self, num_classes):
        super(ConvNeuralNet, self).__init__()
        self.conv_layer1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)
        self.conv_layer2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3)
        self.max_pool1 = nn.MaxPool2d(kernel_size = 2, stride = 2)

        self.conv_layer3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)
        self.conv_layer4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3)
        self.max_pool2 = nn.MaxPool2d(kernel_size = 2, stride = 2)

        self.fc1 = nn.Linear(1600, 128)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(128, num_classes)

    # Progresses data across layers    
    def forward(self, x):
        out = self.conv_layer1(x)
        out = self.conv_layer2(out)
        out = self.max_pool1(out)

        out = self.conv_layer3(out)
        out = self.conv_layer4(out)
        out = self.max_pool2(out)

        out = out.reshape(out.size(0), -1)

        out = self.fc1(out)
        out = self.relu1(out)
        out = self.fc2(out)
        return out

CNN

正如我上面解释的,我们首先创建一个继承了nn.Module类的类,然后我们分别在__init__forward中定义层和它们的执行顺序。

这里需要注意一些事情:

  • nn.Conv2d用于定义卷积层数。我们定义它们接收的通道,以及它们应该返回多少以及内核大小。我们从 3 个通道开始,因为我们使用的是 RGB 图像
  • nn.MaxPool2d是一个最大池层,只需要内核大小和跨度
  • nn.Linear是全连接层,nn.ReLU是使用的激活函数
  • forward方法中,我们定义了序列,并且在完全连接的层之前,我们对输出进行整形以匹配完全连接的层的输入

设置超参数

现在让我们为我们的训练目的设置一些超参数。

model = ConvNeuralNet(num_classes)

# Set Loss function with criterion
criterion = nn.CrossEntropyLoss()

# Set optimizer with optimizer
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)  

total_step = len(train_loader)

Hyperparameters

我们首先用类的数量初始化我们的模型。然后我们选择交叉熵和 SGD(随机梯度下降)分别作为我们的损失函数和优化器。这些有不同的选择,但是我发现在实验中这些能产生最大的准确性。我们还定义了变量total_step,使不同批次的迭代更加容易。


培养

现在,让我们开始训练模型:

# We use the pre-defined number of epochs to determine how many iterations to train the network on
for epoch in range(num_epochs):
	#Load in the data in batches using the train_loader object
    for i, (images, labels) in enumerate(train_loader):  
        # Move tensors to the configured device
        images = images.to(device)
        labels = labels.to(device)

        # Forward pass
        outputs = model(images)
        loss = criterion(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, loss.item())) 

Training

这可能是代码中最棘手的部分。让我们看看代码做了什么:

  • 我们首先遍历历元数,然后遍历训练数据中的批次
  • 我们根据正在使用的设备(即 GPU 或 CPU)来转换图像和标签
  • 在正向传递中,我们使用我们的模型进行预测,并根据这些预测和我们的实际标签计算损失
  • 接下来,我们进行反向传递,我们实际上更新我们的权重,以改善我们的模型
  • 然后,在每次更新之前,我们使用optimizer.zero_grad()函数将梯度设置为零
  • 然后,我们使用loss.backward()函数计算新的梯度
  • 最后,我们用optimizer.step()函数更新权重

我们可以看到如下输出:

Training Losses

正如我们所看到的,随着越来越多的时代,损失略有减少。这是一个好迹象。但是你可能会注意到它在最后波动,这可能意味着模型过度拟合或者batch_size很小。我们将不得不进行测试,以查明发生了什么情况。


测试

现在让我们测试我们的模型。测试的代码与训练没有太大的不同,除了计算梯度,因为我们没有更新任何权重:

with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in train_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    print('Accuracy of the network on the {} train images: {} %'.format(50000, 100 * correct / total)) 

Testing

我们将代码包装在torch.no_grad()中,因为不需要计算任何梯度。然后,我们使用我们的模型预测每一批,并计算它正确预测了多少批。我们得到大约 83%准确率的最终结果:

Accuracy

仅此而已。我们设法在 PyTorch 中从头开始创建了一个卷积神经网络!


结论

我们从学习 CNN 开始——它们有什么样的层以及它们是如何工作的。然后我们介绍了 PyTorch,这是目前最流行的深度学习库之一。我们了解到 PyTorch 如何让我们更容易尝试 CNN。

接下来,我们加载了 CIFAR-10 数据集(一个包含 60,000 幅图像的流行训练数据集),并对其进行了一些转换。

然后,我们从零开始构建了一个 CNN,并为它定义了一些超参数。最后,我们在 CIFAR10 上训练和测试了我们的模型,并设法在测试集上获得了不错的准确性。

在 PyTorch 中从头开始编写 LeNet5

原文:https://blog.paperspace.com/writing-lenet5-from-scratch-in-python/

作为我上一篇帖子的后续,我们将通过构建一些经典的 CNN,继续在 PyTorch 中从头开始编写卷积神经网络,并在数据集上看到它们的作用。

介绍

在本文中,我们将构建有史以来最早引入的卷积神经网络之一,LeNet5 ( 论文)。我们正在 PyTorch 中从头开始构建这个 CNN,并且还将看看它在真实世界数据集上的表现。

我们将从探索 LeNet5 的架构开始。然后,我们将使用torchvision中提供的类加载并分析我们的数据集 MNIST。使用PyTorch,我们将从头开始构建 LeNet5,并根据我们的数据对其进行训练。最后,我们将看到模型如何在看不见的测试数据上执行。

LeNet5

LeNet5 是最早的卷积神经网络(CNN)之一。它是由 Yann LeCun 等人在 1998 年提出的。可以在这里阅读原文:http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf。本文将 LeNet5 用于手写字符的识别。

现在让我们了解 LeNet5 的架构,如下图所示:

LeNet5 Architecture (Source: http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf)

顾名思义,LeNet5 有 5 层,包括两个卷积层和三个全连接层。让我们从输入开始。LeNet5 接受 32x32 的灰度图像作为输入,表明该架构不适合 RGB 图像(多通道)。因此输入图像应该只包含一个通道。之后,我们从卷积层开始

第一卷积层具有 5×5 的滤波器大小,具有 6 个这样的滤波器。这将减少图像的宽度和高度,同时增加深度(通道数)。输出将是 28x28x6。在此之后,应用池化来将特征图减少一半,即 14x14x6。具有 16 个过滤器的相同过滤器大小(5x5)现在被应用于输出,接着是池层。这将输出特征映射减少到 5x5x16。

在此之后,应用具有 120 个滤波器的大小为 5×5 的卷积层,以将特征图展平为 120 个值。然后是第一个完全连接的层,有 84 个神经元。最后,我们有输出层,它有 10 个输出神经元,因为 MNIST 数据对于所表示的 10 个数字中的每一个都有 10 个类别。


数据加载

资料组

让我们从加载和分析数据开始。我们将使用 MNIST 数据集。MNIST 数据集包含手写数字的图像。这些图像都是灰度图像,大小都是 28x28,由 60,000 幅训练图像和 10,000 幅测试图像组成。

你可以在下面看到一些图片样本:

Source: https://paperswithcode.com/dataset/mnist

导入库

我们先导入需要的库,定义一些变量(超参数和device也有详细说明,帮助包确定是在 GPU 上训练还是在 CPU 上训练):

# Load in relevant libraries, and alias where appropriate
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

# Define relevant variables for the ML task
batch_size = 64
num_classes = 10
learning_rate = 0.001
num_epochs = 10

# Device will determine whether to run the training on GPU or CPU.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

Importing the libraries

加载数据集

使用torchvision,我们将加载数据集,因为这将允许我们轻松地执行任何预处理步骤。

#Loading the dataset and preprocessing
train_dataset = torchvision.datasets.MNIST(root = './data',
                                           train = True,
                                           transform = transforms.Compose([
                                                  transforms.Resize((32,32)),
                                                  transforms.ToTensor(),
                                                  transforms.Normalize(mean = (0.1307,), std = (0.3081,))]),
                                           download = True)

test_dataset = torchvision.datasets.MNIST(root = './data',
                                          train = False,
                                          transform = transforms.Compose([
                                                  transforms.Resize((32,32)),
                                                  transforms.ToTensor(),
                                                  transforms.Normalize(mean = (0.1325,), std = (0.3105,))]),
                                          download=True)

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)

Loading and Transforming the Data

让我们来理解代码:

  • 首先,MNIST 数据不能用于 LeNet5 架构。LeNet5 架构接受的输入为 32x32,MNIST 图像为 28x28。我们可以通过调整图像大小,使用预先计算的平均值和标准偏差(在线提供)对它们进行归一化,最后将它们存储为张量来解决这个问题。
  • 我们设置download=True以防数据尚未下载。
  • 接下来,我们使用数据加载器。对于像 MNIST 这样的小数据集,这可能不会影响性能,但对于大数据集,这确实会影响性能,通常被认为是一种好的做法。数据加载器允许我们批量迭代数据,数据是在迭代时加载的,而不是在 start 中一次加载。
  • 我们指定批量大小,并在加载时打乱数据集,以便每一批都有一些标签类型的差异。这将增加我们最终模型的效率。

LeNet5 从零开始

让我们先来看看代码:

#Defining the convolutional neural network
class LeNet5(nn.Module):
    def __init__(self, num_classes):
        super(ConvNeuralNet, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0),
            nn.BatchNorm2d(6),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size = 2, stride = 2))
        self.layer2 = nn.Sequential(
            nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size = 2, stride = 2))
        self.fc = nn.Linear(400, 120)
        self.relu = nn.ReLU()
        self.fc1 = nn.Linear(120, 84)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(84, num_classes)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.reshape(out.size(0), -1)
        out = self.fc(out)
        out = self.relu(out)
        out = self.fc1(out)
        out = self.relu1(out)
        out = self.fc2(out)
        return out

Defining the LeNet5 Model

我将线性地解释代码:

  • 在 PyTorch 中,我们通过创建一个继承自nn.Module的类来定义神经网络,因为它包含了我们将需要使用的许多方法。
  • 之后有两个主要步骤。首先是初始化我们将在 CNN 中使用的图层,另一个是定义这些图层处理图像的顺序。这是在forward函数中定义的。
  • 对于架构本身,我们首先使用具有适当内核大小和输入/输出通道的nn.Conv2D函数定义卷积层。我们还使用nn.MaxPool2D函数应用最大池。PyTorch 的好处在于,我们可以使用nn.Sequential函数将卷积层、激活函数和最大池合并到一个单独的层中(它们将单独应用,但有助于组织)。
  • 然后我们定义完全连接的层。请注意,我们也可以在这里使用nn.Sequential,将激活函数和线性层结合起来,但是我想说明这两者都是可能的。
  • 最后,我们的最后一层输出 10 个神经元,这是我们对数字的最终预测。

设置超参数

在训练之前,我们需要设置一些超参数,比如损失函数和要使用的优化器。

model = LeNet5(num_classes).to(device)

#Setting the loss function
cost = nn.CrossEntropyLoss()

#Setting the optimizer with the model parameters and learning rate
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

#this is defined to print how many steps are remaining when training
total_step = len(train_loader)

Setting the Hyperparameters

我们首先使用类的数量作为参数来初始化我们的模型,在本例中是 10。然后我们定义我们的成本函数为交叉熵损失,优化器为 Adam。有很多选择,但这些往往会给出模型和给定数据的良好结果。最后,我们定义total_step以便在训练时更好地跟踪步骤。


培养

现在,我们可以训练我们的模型:

total_step = len(train_loader)
for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):  
        images = images.to(device)
        labels = labels.to(device)

        #Forward pass
        outputs = model(images)
        loss = cost(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if (i+1) % 400 == 0:
            print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}' 
        		           .format(epoch+1, num_epochs, i+1, total_step, loss.item()))

Training the model

让我们看看代码做了什么:

  • 我们首先遍历历元数,然后遍历训练数据中的批次。
  • 我们根据正在使用的设备(即 GPU 或 CPU)来转换图像和标签。
  • 在正向传递中,我们使用我们的模型进行预测,并根据这些预测和我们的实际标签计算损失。
  • 接下来,我们进行反向传递,我们实际上更新我们的权重,以改善我们的模型
  • 然后,在每次更新之前,我们使用optimizer.zero_grad()函数将梯度设置为零。
  • 然后,我们使用loss.backward()函数计算新的梯度。
  • 最后,我们用optimizer.step()函数更新权重。

我们可以看到如下输出:

Training Losses

如我们所见,损失随着每个时期而减少,这表明我们的模型确实在学习。请注意,这种损失是在训练集上,如果损失太小(就像我们的情况一样),这可能表明过度拟合。有多种方法可以解决这个问题,比如正则化、数据扩充等等,但我们不会在本文中深入讨论。现在让我们测试我们的模型,看看它的表现如何。


测试

现在让我们测试我们的模型:

# Test the model
# In test phase, we don't need to compute gradients (for memory efficiency)

with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    print('Accuracy of the network on the 10000 test images: {} %'.format(100 * correct / total)) 

Testing the model

如您所见,代码与用于培训的代码没有太大的不同。唯一的区别是,我们没有计算梯度(使用with torch.no_grad()),也没有计算损失,因为我们不需要在这里反向传播。为了计算模型的最终精度,我们可以简单地计算图像总数中正确预测的总数。

使用该模型,我们获得了大约 98.8%的准确率,这是相当不错的:

Testing Accuracy

请注意,对于今天的标准来说,MNIST 数据集非常基本而且很小,对于其他数据集来说,很难获得类似的结果。尽管如此,当学习深度学习和 CNN 时,这是一个很好的起点。


结论

现在让我们总结一下我们在本文中所做的工作:

  • 我们从学习 LeNet5 的架构和其中不同种类的层开始。
  • 接下来,我们研究了 MNIST 数据集,并使用torchvision加载数据。
  • 然后,我们从零开始构建 LeNet5,并为模型定义超参数。
  • 最后,我们在 MNIST 数据集上训练和测试了我们的模型,该模型在测试数据集上表现良好。

未来的工作

虽然这看起来是 PyTorch 中深度学习的一个很好的介绍,但是您也可以扩展这项工作来学习更多内容:

  • 您可以尝试使用不同的数据集,但对于这个模型,您将需要灰度数据集。一个这样的数据集是 FashionMNIST
  • 您可以试验不同的超参数,并查看它们在模型中的最佳组合。
  • 最后,您可以尝试在数据集中添加或移除图层,以查看它们对模型功能的影响。

在这里找到本教程的 Github repo:https://github.com/gradient-ai/LeNet5-Tutorial

用 PyTorch 从头开始编写 ResNet

原文:https://blog.paperspace.com/writing-resnet-from-scratch-in-pytorch/

为了结束我在 PyTorch 中从头构建经典卷积神经网络的系列,我们将构建 ResNet,这是计算机视觉中的一项重大突破,它解决了网络太深时网络性能下降的问题。它还引入了剩余连接的概念(稍后将详细介绍)。我们可以在我的个人资料中访问该系列的前几篇文章,即 LeNet5AlexNetVGG

我们将从研究 ResNet 如何工作背后的架构和直觉开始。然后,我们将把它与 VGG 进行比较,并考察它是如何解决 VGG 的一些问题的。然后,像以前一样,我们将加载我们的数据集 CIFAR10,并对其进行预处理,以便为建模做好准备。然后,我们将首先实现一个 ResNet 的基本构建块(我们将称之为 ResidualBlock),并使用它来构建我们的网络。然后,这个网络将在预处理的数据上进行训练,最后,我们将看到经过训练的模型在看不见的数据(测试集)上的表现。


ResNet

VGG 的一个缺点是它不能达到预期的深度,因为它开始失去泛化能力(也就是说,它开始过度拟合)。这是因为随着神经网络变得更深,来自损失函数的梯度开始收缩到零,因此权重没有更新。这个问题被称为消失梯度问题。ResNet 通过使用跳过连接基本上解决了这个问题。

A Residual Block. Source: ResNet Paper

在上图中,我们可以看到,除了正常的连接之外,还有一个跳过模型中某些层的直接连接(skip connection)。通过跳跃连接,输出从 h(x) = f(wx +b) 变为h(x)= f(x)+x。这些跳跃连接很有帮助,因为它们允许梯度流过另一条捷径。下面是 34 层 ResNet 的架构。

Source: ResNet Paper


数据加载

资料组

在本文中,我们将使用著名的 CIFAR-10 数据集,它已经成为初学计算机视觉数据集的最常见选择之一。该数据集是8000 万微小图像数据集的标记子集。它们由 Alex Krizhevsky、Vinod Nair 和 Geoffrey Hinton 收藏。CIFAR-10 数据集由 10 类 60000 幅 32x32 彩色图像组成,每类 6000 幅图像。有 50000 个训练图像和 10000 个测试图像。

数据集分为五个训练批次和一个测试批次,每个批次有 10000 幅图像。测试批次包含从每个类别中随机选择的 1000 个图像。训练批次以随机顺序包含剩余的图像,但是一些训练批次可能包含来自一个类别的比来自另一个类别的更多的图像。在它们之间,训练批次正好包含来自每个类的 5000 个图像。这些类是完全互斥的。汽车和卡车之间没有重叠。“汽车”包括轿车、越野车以及诸如此类的东西。“卡车”仅包括大卡车。两者都不包括皮卡车。

以下是数据集中的类,以及每个类中的 10 幅随机图像:

Source: https://www.cs.toronto.edu/~kriz/cifar.html

导入库

我们将从导入我们将使用的库开始。除此之外,我们将确保笔记本电脑使用 GPU 来训练模型(如果可用的话)

import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler

# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

Importing the libraries

加载数据集

现在我们继续加载数据集。为此,我们将使用torchvision库,该库不仅提供了对数百个计算机视觉数据集的快速访问,还提供了简单直观的方法来预处理/转换它们,以便为建模做好准备

  • 我们首先定义我们的data_loader函数,它根据参数返回训练或测试数据
  • 在深度学习项目中标准化我们的数据总是一个好的做法,因为它使训练更快,更容易收敛。为此,我们用数据集中每个通道(红色、绿色和蓝色)的平均值和标准偏差来定义变量normalize。这些可以手动计算,但也可以在线获得。这在transform变量中使用,我们调整数据的大小,将其转换成张量,然后归一化
  • 我们使用数据加载器。数据加载器允许我们批量迭代数据,数据是在迭代时加载的,而不是一次全部加载到我们的 RAM 中。如果我们要处理大约 100 万张图片的大型数据集,这是非常有用的。
  • 根据test参数,我们要么装载火车(如果test=False)拆分,要么装载test(如果test=True)拆分。在训练的情况下,分裂被随机分成训练集和验证集(0.9:0.1)。
def data_loader(data_dir,
                batch_size,
                random_seed=42,
                valid_size=0.1,
                shuffle=True,
                test=False):

    normalize = transforms.Normalize(
        mean=[0.4914, 0.4822, 0.4465],
        std=[0.2023, 0.1994, 0.2010],
    )

    # define transforms
    transform = transforms.Compose([
            transforms.Resize((224,224)),
            transforms.ToTensor(),
            normalize,
    ])

    if test:
        dataset = datasets.CIFAR10(
          root=data_dir, train=False,
          download=True, transform=transform,
        )

        data_loader = torch.utils.data.DataLoader(
            dataset, batch_size=batch_size, shuffle=shuffle
        )

        return data_loader

    # load the dataset
    train_dataset = datasets.CIFAR10(
        root=data_dir, train=True,
        download=True, transform=transform,
    )

    valid_dataset = datasets.CIFAR10(
        root=data_dir, train=True,
        download=True, transform=transform,
    )

    num_train = len(train_dataset)
    indices = list(range(num_train))
    split = int(np.floor(valid_size * num_train))

    if shuffle:
        np.random.seed(42)
        np.random.shuffle(indices)

    train_idx, valid_idx = indices[split:], indices[:split]
    train_sampler = SubsetRandomSampler(train_idx)
    valid_sampler = SubsetRandomSampler(valid_idx)

    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=batch_size, sampler=train_sampler)

    valid_loader = torch.utils.data.DataLoader(
        valid_dataset, batch_size=batch_size, sampler=valid_sampler)

    return (train_loader, valid_loader)

# CIFAR10 dataset 
train_loader, valid_loader = data_loader(data_dir='./data',
                                         batch_size=64)

test_loader = data_loader(data_dir='./data',
                              batch_size=64,
                              test=True)

Loading the Dataset


从头开始

PyTorch 中模型的工作方式

在开始构建残差块和 ResNet 之前,我们将首先研究并理解 PyTorch 中是如何定义神经网络的:

  • nn.Module提供了一个样板文件,用于创建定制模型以及一些有助于培训的必要功能。这就是为什么每个定制模型都倾向于继承nn.Module
  • 那么在每个定制模型中有两个主要的函数。第一个是初始化函数__init__,其中我们定义了我们将使用的各种层,第二个是forward函数,它定义了在给定输入上执行上述层的顺序

PyTorch 中的图层

现在来看看 PyTorch 中对我们有用的不同类型的图层:

  • 这些卷积层接受输入和输出通道的数量作为参数,以及过滤器的内核大小。如果我们想要应用这些,它也接受任何步幅或填充
  • nn.BatchNorm2d:对卷积层的输出进行批量归一化
  • nn.ReLU:这是一种应用于网络中各种输出的激活功能
  • nn.MaxPool2d:这将最大池应用于给定内核大小的输出
  • nn.Dropout:用于以给定的概率对输出进行丢弃
  • 这基本上是一个完全连接的层
  • 从技术上来说,这不是一种类型的层,但它有助于将同一步骤中的不同操作结合起来

残余块

在开始构建网络之前,我们需要构建一个剩余块,以便在整个网络中重复使用。该块(如架构所示)包含一个可选参数(downsample)的跳过连接。注意,在forward中,这直接应用于输入x,而不是输出out

class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride = 1, downsample = None):
        super(ResidualBlock, self).__init__()
        self.conv1 = nn.Sequential(
                        nn.Conv2d(in_channels, out_channels, kernel_size = 3, stride = stride, padding = 1),
                        nn.BatchNorm2d(out_channels),
                        nn.ReLU())
        self.conv2 = nn.Sequential(
                        nn.Conv2d(out_channels, out_channels, kernel_size = 3, stride = 1, padding = 1),
                        nn.BatchNorm2d(out_channels))
        self.downsample = downsample
        self.relu = nn.ReLU()
        self.out_channels = out_channels

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.conv2(out)
        if self.downsample:
            residual = self.downsample(x)
        out += residual
        out = self.relu(out)
        return out

ResidualBlock

ResNet

现在,我们已经创建了剩余块,我们可以构建我们的 ResNet。

请注意,该架构中有三个模块,分别包含 3、3、6 和 3 层。为了制作这个块,我们创建一个助手函数_make_layer。该函数将层与残差块一起逐个添加。在区块之后,我们添加平均池和最终的线性层。

class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes = 10):
        super(ResNet, self).__init__()
        self.inplanes = 64
        self.conv1 = nn.Sequential(
                        nn.Conv2d(3, 64, kernel_size = 7, stride = 2, padding = 3),
                        nn.BatchNorm2d(64),
                        nn.ReLU())
        self.maxpool = nn.MaxPool2d(kernel_size = 3, stride = 2, padding = 1)
        self.layer0 = self._make_layer(block, 64, layers[0], stride = 1)
        self.layer1 = self._make_layer(block, 128, layers[1], stride = 2)
        self.layer2 = self._make_layer(block, 256, layers[2], stride = 2)
        self.layer3 = self._make_layer(block, 512, layers[3], stride = 2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512, num_classes)

    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes:

            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes, kernel_size=1, stride=stride),
                nn.BatchNorm2d(planes),
            )
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool(x)
        x = self.layer0(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x 

ResNet34


设置超参数

总是建议在我们的模型中为各种超参数尝试不同的值,但是这里我们将只使用一个设置。无论如何,我们建议每个人尝试不同的方法,看看哪种效果最好。超参数包括定义时期数、批量大小、学习率、损失函数以及优化器。当我们构建 ResNet 的 34 层变体时,我们也需要传递适当数量的层:

num_classes = 10
num_epochs = 20
batch_size = 16
learning_rate = 0.01

model = ResNet(ResidualBlock, [3, 4, 6, 3]).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.001, momentum = 0.9)  

# Train the model
total_step = len(train_loader)

Setting Hyper-parameters


培养

现在,我们的模型已经为训练做好了准备,但是首先我们需要知道模型训练在 PyTorch 中是如何工作的:

  • 我们首先使用我们的train_loader为每个时期批量加载图像,并使用我们之前定义的device变量将数据移动到 GPU
  • 然后使用该模型对标签model(images)进行预测,然后我们使用上面定义的损失函数criterion(outputs, labels)计算预测值和实际值之间的损失
  • 现在学习部分来了,我们用损失反向传播法,loss.backward(),更新权重,optimizer.step()。每次更新前需要做的一件重要事情是使用optimizer.zero_grad()将梯度设置为零,因为否则梯度会累积(PyTorch 中的默认行为)
  • 最后,在每个时期之后,我们在验证集上测试我们的模型,但是,由于我们在评估时不需要梯度,我们可以使用with torch.no_grad()关闭它,以使评估更快。
import gc
total_step = len(train_loader)

for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):  
        # Move tensors to the configured device
        images = images.to(device)
        labels = labels.to(device)

        # Forward pass
        outputs = model(images)
        loss = criterion(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        del images, labels, outputs
        torch.cuda.empty_cache()
        gc.collect()

    print ('Epoch [{}/{}], Loss: {:.4f}' 
                   .format(epoch+1, num_epochs, loss.item()))

    # Validation
    with torch.no_grad():
        correct = 0
        total = 0
        for images, labels in valid_loader:
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            del images, labels, outputs

        print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total)) 

Training and Validation

分析代码的输出,我们可以看到,随着损失的减少,模型在学习,而验证集的准确性随着每个时期而增加。但是我们可能会注意到它在最后波动,这可能意味着模型过度拟合或者batch_size很小。我们将不得不进行测试以查明发生了什么:

Training Losses


测试

对于测试,我们使用与验证完全相同的代码,但是使用了test_loader:

with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        del images, labels, outputs

    print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total)) 

Testing

使用上述代码并训练 10 个时期的模型,我们能够在测试集上实现 82.87%的准确度:

Testing Accuracy


结论

现在让我们总结一下我们在本文中所做的工作:

  • 我们从理解 ResNet 的架构和工作原理开始
  • 接下来,我们使用torchvision加载并预处理了 CIFAR10 数据集
  • 然后,我们学习了 PyTorch 中自定义模型定义的工作方式,以及torch中可用的不同类型的层
  • 我们通过构建剩余模块从零开始构建我们的 ResNet
  • 最后,我们在 CIFAR10 数据集上训练和测试了我们的模型,该模型在测试数据集上表现良好,准确率为 75%

未来的工作

通过这篇文章,我们得到了很好的介绍和实践学习,但是如果我们将它扩展到其他挑战,我们可以学到更多:

  • 尝试使用不同的数据集。一个这样的数据集是 CIFAR100,它是 ImageNet 数据集的子集,即8000 万个微小图像数据集
  • 试验不同的超参数,并查看它们在模型中的最佳组合
  • 最后,尝试在数据集中添加或移除图层,以查看它们对模型功能的影响。更好的是,尝试构建该模型的 ResNet-51 版本

用于图像去噪的 xUnit 空间激活函数

原文:https://blog.paperspace.com/xunit-spatial-activation/

在现代深度学习领域,激活函数一直是研究和讨论的主要领域。迄今为止,已经提出了各种各样的新功能,这些功能似乎在深度学习的不同领域,特别是计算机视觉中工作得非常好。然而, xUnit 为设计满足特定任务的激活单元提供了一个新的视角,例如图像恢复和图像去噪。具体来说,在这篇文章中,我们将深入了解 Kligvasser 等人的 CVPR 2018 年论文。艾尔。,标题为“ xUnit:学习高效图像恢复的空间激活函数”。该论文围绕引入一种新的层块(或者说是一个堆栈)来取代常用的 ReLU 激活函数,以提高图像恢复和去噪领域的性能。

首先,我们将看看 xUnit 背后的动机,然后深入分析它的结构,随后是论文中展示的结果及其 PyTorch 代码。

目录

  1. 动机
  2. xUnit
  3. 密码
  4. 结果
  5. 结论
  6. 参考

摘要

近年来,深度神经网络在许多低级视觉任务中取得了前所未有的性能。然而,最先进的结果通常是通过非常深的网络实现的,这些网络可以达到几十层,具有数千万个参数。为了使 DNNs 能够在资源有限的平台上实现,有必要削弱性能和效率之间的权衡。本文提出了一种新的激活单元,特别适用于图像恢复问题。与广泛使用的每像素激活单元(如 ReLUs 和 sigmoids)相比,我们的单元实现了一个具有空间连接的可学习的非线性功能。这使得网络能够捕捉更复杂的特征,从而需要更少的层数来达到相同的性能。我们通过使用最先进的网络进行去噪、去训练和超分辨率的实验来说明我们的单元的有效性,这些已经被认为是非常小的。通过我们的方法,我们能够进一步将这些模型减少近 50%,而不会导致任何性能下降。

动机

在对深度神经网络中的架构组件的多年研究中,激活函数一直是活跃讨论的领域。ReLU 是迄今为止最成功的激活函数,主要用于计算机视觉领域的所有任务。这背后的原因本质上是 ReLU 的简单和高效,它允许激活单元尽可能地轻便和简单。除了激活函数之外,几个缩放因子已经成为在深度卷积神经网络(CNN)的性能方面推动最先进技术的探索中的重要组成部分。在这篇论文中,作者研究了使激活单元更有效,而不是增加网络的深度。他们通过引入一种新的激活机制来实现这一点,这种机制被称为 xUnitT3,这是一个具有空间和可学习连接的层。xUnit 计算一个连续值权重图,作为其输入的软门。

这篇论文的美妙之处在于,由于新颖的 xUnit 激活单元是一个具有代表性能力的可学习层堆栈,因此作者可以从预定义的 SOTA 轻量级 CNN 模型中删除层,并在保持相同性能的同时使它们便宜近 50%。

xUnit

上图显示了 xUnit 激活单元的结构设计。如前所述,xUnit 是不同可学习层的堆叠,而不是传统 ReLU 激活功能的直接替代。xUnit 层由以下几层组成(按顺序排列):

  1. 批量标准化
  2. 热卢
  3. Conv 2D 深度方向
  4. 批量标准化
  5. 高斯的

正如这里看到的,ReLU 仍然保留在 xUnit 中,但是它被嵌入到由其他可学习层组成的管道中。xUnit 中存在的高斯层与图像去噪更相关,因为它在抽象级别上起作用,如高斯模糊过滤器。当 xUnit 需要用于图像分类或分割等其他任务时,可以消除这种情况,但是,可以假设高斯滤波器在鲁棒性和抗锯齿方面也具有良好的效果。

密码

以下片段是 xUnits 的三个变种的结构定义,分别是香草 xUnit苗条 xUnit、稠密 xUnit ,如论文的官方 github 库所定义:

import torch.nn as nn

class xUnit(nn.Module):
    def __init__(self, num_features=64, kernel_size=7, batch_norm=False):
        super(xUnit, self).__init__()
        # xUnit
        self.features = nn.Sequential(
            nn.BatchNorm2d(num_features=num_features) if batch_norm else Identity(),
            nn.ReLU(),
            nn.Conv2d(in_channels=num_features, out_channels=num_features, kernel_size=kernel_size, padding=(kernel_size // 2), groups=num_features),
            nn.BatchNorm2d(num_features=num_features) if batch_norm else Identity(),
            nn.Sigmoid()
        )

    def forward(self, x):
        a = self.features(x)
        r = x * a
        return r

class xUnitS(nn.Module):
    def __init__(self, num_features=64, kernel_size=7, batch_norm=False):
        super(xUnitS, self).__init__()
        # slim xUnit
        self.features = nn.Sequential(
            nn.Conv2d(in_channels=num_features, out_channels=num_features, kernel_size=kernel_size, padding=(kernel_size // 2), groups=num_features),
            nn.BatchNorm2d(num_features=num_features) if batch_norm else Identity(),
            nn.Sigmoid()
        )

    def forward(self, x):
        a = self.features(x)
        r = x * a
        return r

class xUnitD(nn.Module):
    def __init__(self, num_features=64, kernel_size=7, batch_norm=False):
        super(xUnitD, self).__init__()
        # dense xUnit
        self.features = nn.Sequential(
            nn.Conv2d(in_channels=num_features, out_channels=num_features, kernel_size=1, padding=0),
            nn.BatchNorm2d(num_features=num_features) if batch_norm else Identity(),
            nn.ReLU(),
            nn.Conv2d(in_channels=num_features, out_channels=num_features, kernel_size=kernel_size, padding=(kernel_size // 2), groups=num_features),
            nn.BatchNorm2d(num_features=num_features) if batch_norm else Identity(),
            nn.Sigmoid()
        )

    def forward(self, x):
        a = self.features(x)
        r = x * a
        return r

结果

虽然本文的结果主要集中在超分辨率和图像去噪领域,但人们可以使用 xUnits 来取代其深度神经网络架构中的传统标准 ReLU 激活单元,并观察到稳定的性能改善,但代价是增加了计算要求。下面显示的结果强调了本文中展示的 xUnits 的效率和功能。

xUnits 效率分析

图像去噪

通过伪影去除的图像重建

超分辨率

基于设计和激活图可视化的消融研究

结论

xUnits 是图像重建任务的传统激活函数的可靠替代品,但是它们缺乏明确的身份,有点像自然激活函数池中的冒名顶替者,因为它们不是天然的非线性函数,而是深度神经网络中常用的不同层和组件的顺序堆栈。xUnits 也增加了大量的计算量,但是对于 xUnit 所能获得的性能提升来说,这种额外的计算开销是否合理,这取决于用户。

参考

  1. xUnit:学习有效图像恢复的空间激活函数
  2. xUnit 官方 github 资源库

新功能:梯度 YAML 配置文件

原文:https://blog.paperspace.com/yaml-config-feature/

[2021 年 12 月 2 日更新:本文包含关于梯度实验的信息。实验现已被弃用,渐变工作流已经取代了它的功能。请参见工作流程文档了解更多信息。]

您可以将首选的默认参数保存在 config.yaml 文件中,而不是每次想从 Gradient CLI 运行时都键入每个参数。如果您发现自己重复输入相同的命令,并且希望配置可重用(并且具有集成的版本控制),那么这个工具就是为您准备的。

定义默认参数

在 YAML 配置文件中,您可以为特定任务定义最常用或首选的命令参数,并为不同的任务创建多个文件。以单节点实验为例。而不是写下这样的话:

gradient experiments run singlenode \
  --projectId <your-project-id> \
  --name singleEx \
  --experimentEnv "{\"EPOCHS_EVAL\":5,\"TRAIN_EPOCHS\":10,\"MAX_STEPS\":1000,\"EVAL_SECS\":10}" \
  --container tensorflow/tensorflow:1.13.1-gpu-py3 \
  --machineType K80 \
  --command "python mnist.py" \
  --workspaceUrl https://github.com/Paperspace/mnist-sample.git \
  --modelType Tensorflow \
  --modelPath /artifacts

使用配置文件,您可以指定您的任务和您想要使用的特定配置。在这种情况下,您只需写:

gradient experiments run singlenode --optionsFile config.yaml

生成模板配置文件

您可以通过指定想要运行的任务并使用--createOptionsFile标志来生成一个新的配置文件:

gradient experiments run singlenode --createOptionsFile config_file_name.yaml

当您第一次创建配置文件时,它会自动创建一个所有潜在参数的列表,并将每个字段填充为空值。然后,您可以填写与您的任务相关的每个参数,将其他值保留为空,或者完全删除它们。

注意:您当前不能通过从命令行重新定义来覆盖文件中定义的特定参数。如果您想要进行任何特定的参数更改,您必须更改 config.yaml 本身的值。

示例使用案例

您可以为不同的任务使用不同的配置文件。配置文件可用于运行笔记本、实验、TensorBoard,以及基本上任何其他带有可从 CLI 运行的参数的任务。除了允许使用 GradientCI 进行集成版本控制之外,这还简化了协作,因为您可以共享您的 config.yaml 文件供其他人使用。

例如,如果您想要部署您的模型,您可能会生成以下配置文件:

deploymentType: TFServing
imageUrl: tensortensorflow/serving:latest-gpuflow/
instanceCount: 1
machineType: K80
modelId: mos3vkbikxc6c38
name: tfserving deployment

另一方面,如果您想要运行单节点实验,您的配置文件可能如下所示:

command: python mnist.py
container: tensorflow/tensorflow:1.13.1-gpu-py3
experimentEnv:
  EPOCHS_EVAL: 5
  EVAL_SECS: 10
  MAX_STEPS: 1000
  TRAIN_EPOCHS: 10
machineType: K80
modelPath: /artifacts
modelType: Tensorflow
name: mnist-cli-local-workspace
projectId: pr64qlxl0
tensorboard: false
workspace: 'https://github.com/Paperspace/mnist-sample.git'

更多信息请查看文档。

梯度中的彩色物体检测

原文:https://blog.paperspace.com/yolor/

深度学习的普及和研究中最受欢迎和立即可用的概念之一是对象检测。对象检测是使用对象识别和图像分割来创建带标签的边界框以识别和标记图像和视频中对象的分类的实践。由于免费使用 YOLO 算法,这一概念迅速普及。过去,我们在关于在 PyTorch 中实现对象检测器的系列文章中广泛讨论了 YOLOv4。

在本文中,我们将研究 YOLOR(您只学习一种表示)。YOLOR 是 2021 年发布的一种对象检测算法,它可以匹配甚至优于一个缩放的 YOLO v4 模型。YOLOR 在概念上不同于 YOLO,因为它使用统一的网络来同时编码隐性知识和显性知识。YOLOR 可以执行“卷积神经网络中的内核空间对齐、预测细化和多任务学习”,作者的研究结果表明,包含隐式信息有助于所有任务的执行。 (1)

在本教程中,我们将分解 YOLOR 如何检测对象,将其与众所周知的 YOLO 算法进行对比,并在使用 YOLOR 模型检测 Youtube 视频中的对象之前,以训练 YOLOR 模型的编码演示结束。

YOLOR:架构和能力

YOLOR 与 YOLO 的不同之处在于,它能够使用单一、统一的模型,将显性和隐性知识编码在同一个表示中,因此得名。为了澄清,显式深度学习指的是理解存储在网络浅层中的图像的粗略细节。隐式深度学习专注于更精细的细节,对应于网络的更深层。通过在单个模型中结合这两种努力,YOLOR 可以快速准确地检测高清照片中的细节,YOLOR 比缩放的 YOLOv4 模型快大约 88%。

我们来分解一下 YOLOR 是如何利用隐性知识学习的,然后考察一下统一模型。

YOLOR 如何解释隐性知识:

在我们研究统一网络之前,我们将介绍一些原始论文作者建议用于此任务的概念。这些技术允许作者为隐性知识建模并快速推理。

歧管空间缩减:

流形空间缩减是论文中提到的第一个技术。作者断言,一个好的、单一的表示应该能够在它所属的流形空间中定位相应的投影 (1) 。使用流形学习,这种技术旨在将流形空间的维度减少到平坦、无特征的空间。这种近似欧几里得空间允许算法在没有任何预定分类的情况下获得对数据的高维结构的隐含理解。如果可以在这个缩减的投影空间内对目标类别进行分类,我们就可以在降低维数的同时改进预测。

内核对齐:

如上图所示,使用多任务和多头神经网络处理内核空间错位可能会有问题。这种不一致会导致模型功能的重大中断。为了补救这一点,作者提出了对输出特征和隐式表示的一系列操作。这允许内核空间被适当地平移、旋转和缩放,使得每个输出内核空间被对齐 (1) 。实际上,这意味着在特征金字塔网络 (2) 中对齐输出的较小细节和较粗略细节的特征。这些特征金字塔允许识别系统在不同尺度下保持其能力。

其他建议方法:

如上面的部分 a 所示,加法的引入可以使神经网络预测中心坐标的偏移,然后可以用于通知最终的预测。在部分 b,中,我们看到引入乘法允许锚细化。锚定框允许一个网格单元检测多个对象,并防止重叠细节带来的复杂性。在实践中,通过有效地自动扫描锚的超参数集,这形成了更鲁棒的模型。最后,在章节 c 中,该图显示了如何应用点乘和串联来实现多任务特征选择,并为后续计算 (1) 设置前提条件。

架构:统一的模型

YOLOR 的作者在通过上述技术寻求一种可以处理传统神经网络的显式知识和隐式知识的模型时,创建了一个多模态统一网络(如上图)。该模型的功能是生成一个具有显式知识和隐式知识的表示,以服务于多个任务。

为了实现这一点,作者使用显性和隐性知识一起建模误差项。这个误差项然后又被用来指导多用途网络训练过程。这可以映射到以下用于训练的等式:

\(y = fθ(x) + ε + gφ(εₑₓ(x), εᵢₘ(z))\)

\(最小化ε + gφ(εₑₓ(x),εᵢₘ(z))\)

其中 εₑₓεᵢₘ 项代表分别对来自观测值 x 和潜在代码 z 的显式和隐式误差项建模的操作。然后,gφ 是一个特定于任务的操作,它从显式和隐式知识库中组合或选择信息。既然我们已经概述了将显式知识直接整合到 fθ,中的方法,那么我们可以将等式改写为

\(y =系数(x)φ(z)\) y

其中☆表示组合 fθ和 gφ的可能运算符。在这里,我们在隐性知识部分讨论的运算符,如加法、乘法和串联都可以使用。通过扩展误差项的推导过程以处理多个任务,我们得到以下等式:

$F(x、θ、z、φ、y、ψ)= 0 美元

其中 Z = {z1,z2,...,zT }是 T 个不同任务的一组隐含的潜在代码。接下来,φ充当从 z 生成隐式表示的参数。然后,ψ项用于从显式表示和隐式表示的不同组合计算最终输出参数。

最后,由此我们可以导出下面的公式来获得对所有 z ∈ Z 的预测,并使用它来解决各种任务。

$ dψ(fθ(x)、gφ(z)、y) = 0 美元

根据这个等式,我们用一个通用的统一表示法 fθ(x)开始所有的任务。每个任务然后经过任务特定的隐式表示,gφ(z)。任务鉴别器然后完成每个不同的任务。 (1)

在实践中,这创建了一个系统,该系统能够在整体表示中既接受对应于网络浅层的显性知识,又接受对应于网络深层的隐性知识。然后,鉴别器可以快速有效地指导不同的任务完成。

能力

到目前为止,我们已经谈论了很多关于 YOLOR 的内容,任何阅读这篇文章并且熟悉 YOLO 的人都可以猜到,YOLOR 也是一个极其强大的对象检测算法。通过模型中前面描述的过程的组合效果,YOLOR 算法能够在用带标签的边界框包围对象之前对其进行检测和分类。从上图中可以看出,作者已经证明,在相同的数据集上训练时,YOLOR 在平均精度和批处理 1 延迟方面都优于 YOLOX 和 Scaled-YOLOv4。

现在我们已经看到了 YOLOR 的工作原理,让我们看看如何训练 YOLOR 模型,并使用它从渐变笔记本中的图像和视频中检测对象。

演示

要在渐变笔记本上运行 YOLOR,首先创建一个带有 PyTorch 运行时的笔记本。如果你想加快训练的速度,你可以用增长包设置一个多 GPU 的机器,你可能需要采取措施来减少训练过程中使用的内存。任何可用的 GPU 都将运行检测脚本。

设置的最后一步是切换高级选项,并将以下 URL 设置为您的工作区 URL:

T2https://github.com/WongKinYiu/yolor

完成设置后,点击“创建”启动笔记本。

准备工作:

现在,在你的渐变笔记本中,我们可以开始设置。在我们可以训练模型或使用预训练模型进行一些检测之前,我们需要做两件事情。首先,我们需要准备和下载数据集。虽然数据作为公共数据集可用,但它当前存储在不可变的卷中。不幸的是,这产生了对工作目录中数据的需求。因此,为 YOLOR 准备好 COCO 数据的最快方法是使用脚本目录 get_coco.sh 中的内置 Python 脚本。在终端中执行以下命令,将数据加载到工作目录中:

mkdir coco
cd coco
bash get_coco.sh 

这将在 coco 目录中设置您的数据,并将图像、注释和标签文件放在正确的位置以运行 YOLOR。

接下来,用户将需要安装 gdown,一个 Google Drive 下载程序,以获得 YOLOR-CSP-X 的预训练模型权重,我们稍后将使用它来进行对象检测。您可以使用下面的代码片段安装 gdown 并下载权重。

pip install gdown 
gdown https://drive.google.com/uc?id=1NbMG3ivuBQ4S8kEhFJ0FIqOQXevGje_w

一旦完成,你就可以开始用我们刚刚下载的预训练模型来训练 YOLOR 或检测。

如何使用 YOLOR 检测视频和图像中的物体

我们先来看看 YOLOR 是如何实时工作的。用 youtube_dl 下载一段人在人群中穿行的视频。您可以安装它

pip install youtube_dl

然后用下面的代码执行一个 notebook 单元格,将视频下载到推理目录。

import youtube_dl

url = 'https://www.youtube.com/watch?v=b8QZJ5ZodTs'

url_list = [url]
youtube_dl.YoutubeDL({'outtmpl': 'inferenimg/inputvid.mp4', 'format_id': 'worstvideo/worst', 'format': '160', 'vcodec': 'utf-8'}).download(url_list)

推理目录存储了我们将与 YOLOR 一起用于对象检测任务的图像。它将与马的现有图片。我们将使用这张图片和我们刚刚下载的视频来演示 YOLOR 的速度和准确性。您可以通过在终端中执行以下命令来实现:

python detect.py --source inferenimg/* --cfg cfg/yolor_csp_x.cfg --weights yolor_csp_x.pt --conf 0.25 --img-size 1280 --device 0

这将对inferenimg/文件夹中的每个文件运行对象检测器,并将它们输出到inference/output/。当它完成运行时,该模型将输出一个视频和图像,其中包含每个文件中分类对象上的带标签的边界框。它们应该看起来像这样:

https://blog.paperspace.com/content/media/2022/04/upload-vid.mp4

现在,如果您想要运行相同的过程,您需要做的就是将您的图像或视频文件放入推理/图像文件夹中。

如何用 COCO 训练自己的 YOLOR 模型

设置培训可能有点复杂。前面,我们使用bash get_coco.sh设置了 COCO 数据集。这已经为 YOLOR 定位了用于培训的注释、标签和注解。要使用 COCO 数据集为新的 YOLOR 模型执行训练例程,可以将以下内容放入控制台:

python train.py --batch-size 8 --img 1280 1280 --data data/coco.yaml --cfg cfg/yolor_csp_x.cfg --weights '' --device 0 --name yolor_csp_x_run1 --hyp data/hyp.scratch.1280.yaml --epochs 300

coco.yaml 文件与我们的 YOLOR repo 克隆一起提供,它将引导训练到正确的目录并输入分类标签。这将运行 300 个纪元的训练,这将花费很长时间。一旦模型完成,您就可以使用输出的模型权重作为训练出来的最佳整体模型,以执行与预训练模型类似的检测过程:

python detect.py --source inferenimg/horses.jpg --cfg cfg/yolor_csp_x.cfg --weights runs/train/yolor_csp_x_run1/weights/best_overall.pt --conf 0.25 --img-size 1280 --device 0

总结想法

在本文中,我们详细研究了 YOLOR。我们看到 YOLOR 如何能够在一个统一的模型中集成隐式知识和显式知识,并使用对单个表示的理解来执行复杂的对象检测任务。最后,我们演示了如何在渐变笔记本上启动 YOLOR,并在下载的 Youtube 视频上执行检测任务。

想要了解更多关于 YOLOR 的信息,你可以在这里阅读原文并访问作者的 Github。

如何训练和使用定制的 YOLOv7 模型

原文:https://blog.paperspace.com/yolov7/

物体检测无疑是深度学习技术承诺的“圣杯”之一。结合图像分类和对象识别的实践中,对象检测涉及识别图像中离散对象的位置并对其进行正确分类。然后预测边界框并将其放置在图像的副本中,以便用户可以直接看到模型的预测分类。

YOLO 自创建以来一直是首要的目标检测网络之一,主要有两个原因:它的准确性、相对较低的成本和易用性。由于这种实用的组合,这些特征一起使 YOLO 无疑成为数据科学社区之外最著名的 DL 模型之一。经过多次迭代开发,YOLOv7 是流行算法的最新版本,并对其前辈进行了显著改进。

https://blog.paperspace.com/content/media/2022/08/luca2-1.mp4

Sample from code demo later shows side by side footage of NBA players with and without bounding box labels from YOLOv7

在这篇博客教程中,我们将首先检查 YOLO 行动背后的更大理论,它的架构,并将其与之前的版本进行比较。然后,我们将跳转到一个编码演示,详细说明为您的对象检测任务开发自定义 YOLO 模型所需的所有步骤。我们将使用 NBA 比赛镜头作为我们的演示数据集,并尝试创建一个模型,该模型可以将持球者与球场上的其他球员区分开来。

什么是 YOLO?

Generalization results on Picasso and People-Art datasets from original YOLO paper [Source]

最初的 YOLO 模型是在 2015 年的论文你只看一次:统一的实时物体检测中介绍的。当时,RCNN 模型是执行对象检测的最佳方式,其耗时、多步骤的训练过程使其在实践中难以使用。YOLO 的创建就是为了尽可能地消除这种麻烦,通过提供单阶段物体检测,他们减少了训练&推理时间,并大幅降低了运行物体检测的成本。

从那以后,各种各样的团体开始着手解决 YOLO 的问题,希望有所改进。这些新版本的一些例子包括强大的约洛夫 5约洛尔。每一次迭代都试图在过去的基础上进行改进,YOLOv7 现在是该系列发布的最高性能型号。

YOLO 是如何工作的?

YOLO 首先将图像分成 N 个网格,在一个阶段完成目标检测。这些网格中的每一个都具有相同的大小 SxS。这些区域中的每一个都用于检测和定位它们可能包含的任何对象。对于每个网格,用对象标签和预测对象存在的概率分数来预测潜在对象的边界框坐标 B。

正如您可能已经猜到的那样,这导致了网格累积预测中预测对象的显著重叠。为了处理这种冗余并将预测对象减少到感兴趣的对象,YOLO 使用非最大抑制来抑制所有具有相对较低概率分数的包围盒。

Image Divided into Grids; Before Non- Maximal Suppression; After Non Maximal Suppression (Final Output)

为了做到这一点,YOLO 首先比较与每个决策相关的概率分数,并取最大的分数。随后,它移除与所选高概率边界框的并集上具有最大交集的边界框。然后重复该步骤,直到只剩下所需的最终边界框。

YOLOv7 有什么变化

YOLOv7 做了很多新的改动。本节将尝试分解这些变化,并展示这些改进如何导致 YOLOv7 与前代模型相比性能的大幅提升。

扩展的高效层聚合网络

模型重新参数化是在推理阶段合并多个计算模型以加速推理时间的实践。在 YOLOv7 中,“扩展高效层聚合网络”或 E-ELAN 技术用于实现这一壮举。

[Source]

E-ELAN 实现了扩展、洗牌和合并基数技术,以不断提高网络的适应性和学习能力,而不会对原始梯度路径产生影响。该方法的目标是使用群卷积来扩展计算块的通道和基数。这是通过将相同的组参数和通道乘数应用于层中的每个计算块来实现的。然后,按块计算特征图,并按照变量 g 的设置,将特征图混洗到多个组中,并进行组合。这样,每组特征图中的通道数量与原始架构中的通道数量相同。然后,我们将这些组加在一起以合并基数。通过仅改变计算块中的模型架构,过渡层不受影响,并且梯度路径是固定的。【来源】

基于连接的模型的模型缩放

[Source]

YOLO 和其他对象检测模型通常会发布一系列按比例放大或缩小尺寸的模型,以用于不同的用例。对于缩放,对象检测模型需要知道网络的深度、网络的宽度以及网络被训练的分辨率。在 YOLOv7 中,模型在将层连接在一起的同时缩放网络深度和宽度。消融研究表明,这种技术在缩放不同尺寸的同时保持了模型架构的最优化。通常,像放大深度这样的事情会导致过渡层的输入通道和输出通道之间的比率变化,这可能会导致模型的硬件使用量减少。YOLOv7 中使用的复合缩放技术减轻了缩放时对性能产生的这种和其他负面影响。

可训练的免费赠品袋

[Source]

YOLOv7 作者使用梯度流传播路径来分析重新参数化卷积应该如何与不同的网络相结合。上图显示了卷积块应该以何种方式放置,带有复选标记的选项表示它们工作正常。

辅助压头粗糙,铅损失压头精细

[Source]

深度监督是一种在网络的中间层增加一个额外的辅助头的技术,使用带有辅助损失的浅层网络权值作为指导。即使在模型权重通常收敛的情况下,这种技术对于进行改进也是有用的。在 YOLOv7 架构中,负责最终输出的头叫做带头头,用来辅助训练的头叫做辅助头。YOLOv7 使用导联头预测作为指导,生成由粗到细的分层标签,分别用于辅助头和导联头学习。

综上所述,与之前的产品相比,这些改进显著提高了性能,降低了成本。

设置您的自定义数据集

既然我们理解了为什么 YOLOv7 比过去的技术有如此大的改进,我们可以试一试了!对于这个演示,我们将下载 NBA 集锦的视频,并创建一个 YOLO 模型,它可以准确地检测球场上哪些球员正在积极持球。这里的挑战是让模型能够可靠地检测和辨别球场上的持球者和其他球员。为此,我们可以去 Youtube 下载一些 NBA 集锦。然后我们可以使用 VLC 的快照过滤器将视频分解成图像序列。

要继续培训,首先需要选择合适的标注工具来标注新创建的自定义数据集。YOLO 和相关模型要求用于训练的数据具有准确标记的每个期望的分类,通常是手工标记的。我们选择使用 RoboFlow 来完成这项任务。该工具可以在线免费使用,快速,可以对上传的数据进行扩充和转换以使数据集多样化,甚至可以根据输入的扩充量自由地将训练数据量增加三倍。付费版本提供了更多有用的功能。

创建一个 RoboFlow 帐户,启动一个新项目,然后将相关数据上传到项目空间。

https://blog.paperspace.com/content/media/2022/08/Screen-Recording-2022-08-04-at-1.46.15-PM.mp4

我们将在这项任务中使用的两种可能的分类是“控球者”和“球员”要在上传后用 RoboFlow 标记数据,您需要做的就是单击左侧菜单上的“Annotate”按钮,单击数据集,然后将您的边界框拖到所需的对象上,在本例中是有球和无球的篮球运动员。

这些数据完全由游戏镜头组成,所有商业广告或大量 3d CGI 填充的帧都从最终数据集中排除。球场上的每个球员都被标识为“球员”,这是数据集中大多数边界框分类的标签。几乎每一帧,但不是全部,也包括一个“球处理器”。“持球者”是目前拥有篮球的球员。为了避免混淆,在任何帧中,持球者都不会被双重标记为球员。为了试图说明游戏镜头中使用的不同角度,我们包括了所有镜头的角度,并对每个角度保持相同的标记策略。最初,当镜头从地面拍摄时,我们尝试了一个单独的“球处理者-地板”和“球员-地板”标签,但这只是给模型增加了混乱。

一般来说,建议你每种分类有 2000 张图片。然而,手工标记如此多的图像(每个图像都有许多对象)非常耗时,因此我们将使用一个较小的样本进行演示。它仍然工作得相当好,但是如果您希望改进这个模型的功能,最重要的一步是将它暴露给更多的训练数据和更健壮的验证集。

我们对训练集使用了 1668 张(556x3)训练照片,对测试集使用了 81 张图像,对验证集使用了 273 张图像。除了测试集,我们将创建自己的定性测试,通过在新的 highlight reel 上测试模型来评估模型的可行性。您可以使用 RoboFlow 中的 generate 按钮生成数据集,然后通过 YOLOv7 - PyTorch 格式的curl终端命令将其输出到您的笔记本。下面是您可以用来访问本演示所用数据的代码片段:

curl -L "https://app.roboflow.com/ds/4E12DR2cRc?key=LxK5FENSbU" > roboflow.zip; unzip roboflow.zip; rm roboflow.zip 

代码演示

您可以通过单击下面的链接运行此演示所需的所有代码。

设置

要开始代码演示,只需点击下面的梯度运行链接。笔记本完成设置并运行后,导航到“notebook.ipynb”。此笔记本包含设置模型所需的所有代码。文件“data/coco.yaml”被配置为使用我们的数据。

首先,我们将加载所需的数据和我们将微调的模型基线:

!curl -L "https://app.roboflow.com/ds/4E12DR2cRc?key=LxK5FENSbU" > roboflow.zip; unzip roboflow.zip; rm roboflow.zip
!wget https://github.com/WongKinYiu/yolov7/releases/download/v0.1/yolov7_training.pt
! mkdir v-test
! mv train/ v-test/
! mv valid/ v-test/

接下来,我们需要安装一些必需的包,因此运行这个单元将使您的环境为培训做好准备。我们正在降级火炬和火炬视觉,因为 YOLOv7 不能在当前版本上工作。

!pip install -r requirements.txt
!pip install setuptools==59.5.0
!pip install torchvision==0.11.3+cu111 -f https://download.pytorch.org/whl/cu111/torch_stable.html

助手

import os 

# remove roboflow extra junk

count = 0
for i in sorted(os.listdir('v-test/train/labels')):
    if count >=3:
        count = 0
    count += 1
    if i[0] == '.':
        continue
    j = i.split('_')
    dict1 = {1:'a', 2:'b', 3:'c'}
    source = 'v-test/train/labels/'+i
    dest = 'v-test/train/labels/'+j[0]+dict1[count]+'.txt'
    os.rename(source, dest)

count = 0
for i in sorted(os.listdir('v-test/train/images')):
    if count >=3:
        count = 0
    count += 1
    if i[0] == '.':
        continue
    j = i.split('_')
    dict1 = {1:'a', 2:'b', 3:'c'}
    source = 'v-test/traimg/'+i
    dest = 'v-test/traimg/'+j[0]+dict1[count]+'.jpg'
    os.rename(source, dest)

for i in sorted(os.listdir('v-test/valid/labels')):
    if i[0] == '.':
        continue
    j = i.split('_')
    source = 'v-test/valid/labels/'+i
    dest = 'v-test/valid/labels/'+j[0]+'.txt'
    os.rename(source, dest)

for i in sorted(os.listdir('v-test/valid/images')):
    if i[0] == '.':
        continue
    j = i.split('_')
    source = 'v-test/valimg/'+i
    dest = 'v-test/valimg/'+j[0]+'.jpg'
    os.rename(source, dest)
for i in sorted(os.listdir('v-test/test/labels')):
    if i[0] == '.':
        continue
    j = i.split('_')
    source = 'v-test/test/labels/'+i
    dest = 'v-test/test/labels/'+j[0]+'.txt'
    os.rename(source, dest)

for i in sorted(os.listdir('v-test/test/images')):
    if i[0] == '.':
        continue
    j = i.split('_')
    source = 'v-test/teimg/'+i
    dest = 'v-test/teimg/'+j[0]+'.jpg'
    os.rename(source, dest)

笔记本的下一部分有助于设置。因为 RoboFlow 数据输出在文件名末尾附加了一个额外的数据和 id 字符串,所以我们首先删除所有多余的文本。这些会阻止训练运行,因为它们不同于 jpg 和相应的 txt 文件。训练文件也是一式三份,这就是为什么训练重命名循环包含额外的步骤。

火车

既然我们的数据已经设置好了,我们就可以开始在自定义数据集上训练我们的模型了。我们使用 2 x A6000 模型来训练我们的模型 50 个时期。这部分的代码很简单:

# Train on single GPU
!python train.py --workers 8 --device 0 --batch-size 8 --data data/coco.yaml --img 1280 720 --cfg cfg/training/yolov7.yaml --weights yolov7_training.pt --name yolov7-ballhandler --hyp data/hyp.scratch.custom.yaml --epochs 50

# Train on 2 GPUs
!python -m torch.distributed.launch --nproc_per_node 2 --master_port 9527 train.py --workers 16 --device 0,1 --sync-bn --batch-size 8 --data data/coco.yaml --img 1280 720 --cfg cfg/training/yolov7.yaml --weights yolov7_training.pt --name yolov7-ballhandler --hyp data/hyp.scratch.custom.yaml --epochs 50 

我们提供了两种在单 GPU 或多 GPU 系统上运行训练的方法。通过执行此单元,训练将开始使用所需的硬件。您可以在此处修改这些参数,此外,您还可以在“data/hyp.scratchcustom.yaml”处修改 YOLOv7 的超参数。让我们回顾一下这些参数中一些比较重要的参数。

  • workers (int):在培训期间要并行化多少个子流程
  • img (int):我们图像的分辨率。在这个项目中,图像的尺寸被调整为 1280 x 720
  • batch_size (int):确定在创建模型更新之前处理的样本数
  • nproc_per_node (int):训练时使用的机器数量。对于多 GPU 训练来说,这通常是指可以指向的可用机器的数量。

在训练期间,模型将在每个时期结束时输出为训练保留的内存、检查的图像数量、预测的标签总数、精度、召回和 mAP @.5。您可以使用这些信息来帮助确定模型何时可以完成训练,并了解模型在验证集上的功效。

在训练结束时,最好的、最后的和一些附加的模型阶段将被保存到“runs/train/yolov 7-ball handler[n]中的相应目录,其中 n 是训练已经运行的次数。它还会保存一些关于培训过程的相关数据。您可以在带有- name 标志的命令中更改保存目录的名称。

发现

一旦模型训练完成,我们就不能使用该模型来实时执行对象检测。这能够处理图像和视频数据,并将以包括边界框在内的帧的形式为您实时输出预测(在渐变笔记本之外)。我们将使用 detect 作为定性评估模型在其任务中的有效性的方法。为此,我们从 Youtube 上下载了不相关的 NBA 比赛片段,并上传到笔记本上,用作小说测试集。你也可以直接插入一个带有 HTTPS、RTPS 或 RTMP 视频流的 URL 作为 URL 字符串,但是 YOLOv7 可能会提示进行一些额外的安装才能继续。

一旦我们输入了用于训练的参数,我们就可以调用detect.py脚本来检测新测试视频中任何想要的对象。

!python detect.py --weights runs/train/yolov7-ballhandler/weights/best.pt --conf 0.25 --img-size 1280 --source video.mp4 --name test 

在使用与上述完全相同的方法训练了 50 个时期后,您可以预期您的模型的性能大致类似于以下视频中所示的性能:

https://blog.paperspace.com/content/media/2022/08/alley.mp4

由于所使用的训练图像角度的多样性,该模型能够考虑所有种类的拍摄,包括地面水平和相对基线的更远的地面水平。在绝大多数的镜头中,模型能够正确地识别持球者,并同时标记球场上的每个额外球员。

https://blog.paperspace.com/content/media/2022/08/transition.mp4

然而,这个模型并不完美。我们可以看到,有时球员身体的一部分在转身时被遮挡似乎会使模型困惑,因为它试图给这些位置的球员分配控球者标签。通常,这种情况发生在球员背对着摄像机的时候,可能是因为后卫在组织比赛或冲向篮筐时经常发生这种情况。

其他时候,该模型将场上的多名球员识别为控球,例如在上面显示的快攻期间。同样值得注意的是,在近距离摄像机视图上扣篮和阻挡也会混淆模型。最后,如果球场的一小块区域被大多数球员占据,它会使模型中的持球者变得模糊,从而引起混乱。

https://blog.paperspace.com/content/media/2022/08/dunk.mp4

总的来说,从我们定性的角度来看,该模型似乎在检测每个球员和持球者方面总体上是成功的,但在某些比赛中使用的罕见角度方面存在一些困难,当半场球员非常拥挤时,以及在进行更多训练数据中没有考虑的运动比赛时,如独特的扣篮。由此,我们可以推测,问题不在于我们数据的质量,也不在于训练时间的长短,而在于训练数据的数量。确保一个健壮的模型可能需要大约 3 倍于当前训练集中的图像量。

现在让我们使用 YOLOv7 的内置测试程序来评估我们在测试集上的数据。

试验

test.py脚本是使用测试集评估模型质量的最简单快捷的方法。它快速评估测试集上所做预测的质量,并以清晰的格式返回它们。当与我们的定性分析结合使用时,我们可以更全面地了解我们的模型是如何执行的。

RoboFlow 建议,除了每个分类 2000 张图像之外,当用于 YOLO 时,对数据集进行 70-20-10 的训练测试验证分割。由于我们的测试集很小,很可能有几个类没有被充分代表,所以对这些结果要有所保留,并使用比我们为自己的项目选择的更健壮的测试集。这里我们用 test.yaml 代替 coco.yaml。

!python test.py --data data/test.yaml --img 1280 --batch 16 --conf 0.001 --iou 0.65 --device 0 --weights runs/train/yolov7-ballhandler/weights/best.pt --name yolov7_ballhandler_testing 

然后,您将在日志中获得一个输出,以及几个数字和数据点,用于评估保存到指定位置的测试集上的模型的有效性。在日志中,您可以看到文件夹中图像的总数和这些图像中每个类别的标签数,以及累积预测和每个分类类型的精度、召回率和 mAP@.5。

正如我们所看到的,数据反映了一个健康的模型,该模型在预测测试集中的每个真实标签时至少达到了~0 . 79 mAP @ 0 . 5 功效。

考虑到我们明显的类别不平衡&类别之间的极端相似性,控球者相对较低的召回率、精确度和 mAP@.5,在使用了多少数据进行训练的背景下是完全合理的。可以说,定量结果证实了我们的定性发现,该模型是有能力的,但需要更多的数据来达到完全的效用。

结束语

正如我们所看到的,YOLOv7 不仅是一个强大的工具,其使用的准确性显而易见,而且在 RoboFlow 等强大的标签工具和 Paperspace Gradient 等强大的 GPU 的帮助下,也非常容易实现。我们选择这个挑战是因为很难辨别篮球运动员是否有球给人类,更不用说机器了。这些结果非常有希望,并且已经有许多用于跟踪玩家的应用,用于统计、赌丨博和玩家训练,这些都可以很容易地从这项技术中得到。

我们鼓励您在运行我们准备好的版本后,在您自己的自定义数据集上遵循本文中描述的工作流。此外,RoboFlow 的数据集存储上还有大量公共和社区数据集。在开始数据标注之前,请务必仔细阅读这些数据集。感谢您的阅读!

拥抱人脸的零镜头文本分类🤗在坡度上

原文:https://blog.paperspace.com/zero-shot-text-classification-with-hugging-face-on-gradient/

Zero-shot learning (ZSL)是一种机器学习范式,它引入了用初始训练阶段从未观察到的类别标签测试样本的思想。这类似于我们人类也是如何基于我们随着时间的推移收集的现有知识将我们的学习推断为新概念的。ZSL 范式最近变得更加流行,这主要是因为获得任何特定领域的标记数据是一个非常昂贵和耗时的过程。根据您想要优化的成本,您可以让主题专家(SME)标记每个输入样本,或者在编写任务和特定领域手工制作的规则时寻求他们的帮助,以每周监督的方式帮助启动训练阶段。ZSL 在机器学习的各个垂直领域有许多应用,一些流行和有趣的应用是文本分类图像分类文本到图像生成语音翻译等。

文本分类是将一组预定义的类别分配给给定文本片段的任务。它通常在受监督的设置中建模,其中您已经标记了特定于领域的文本数据及其关联的类别标签/类别。然后你学习一些映射函数 X->Y;其中,X:输入样本,Y:类别。文本分类的一些例子包括情感分析、垃圾邮件分类、新闻分类等等。欢迎关注这个博客,获取使用变形金刚进行文本分类的快速教程。

因此,零镜头文本分类是关于将给定的一段文本分类到某个预定义的组或类标签,而无需在包含文本和标签映射的下游数据集上显式训练专用的机器学习模型。

你可能没有听说过拥抱脸🤗如果你不经常练习 NLP。但是,作为一个复习,拥抱脸是机器学习技术的开源和平台提供商。它在 NLP 开发人员中非常受欢迎,因为它的 Transformers 支持提供了一种简单的方法来下载、训练和推断最新的 NLP 模型。渐变笔记本是一个易于使用的基于网络的 Jupyter IDE,带有免费的 GPU,允许使用任何底层的库或框架。它也促进了协作开发和公共共享——对 ML 开发者来说是完美的🚀

在这篇博客中,我们将快速浏览一遍从拥抱脸开始的零镜头文本分类管道🤗并讨论是什么使得算法成为可能。

恋恋笔记本

让我们从安装变压器库开始-

>> pip install transformers

拥抱脸提供了管道的概念,通过抽象大部分复杂的代码,使得从已经训练好的模型中进行推断变得非常容易。我们将把同样的想法用于“零射击分类”的任务。Pipeline 类是基类,所有特定于任务的管道都从该类继承。因此,在管道中定义任务会触发特定于任务的子管道,在这种情况下,它将是ZeroShotClassificationPipeline。还有许多其他的任务可以探索,值得花些时间在拥抱脸流水线任务上看完整的任务列表。

接下来,我们继续导入管道并定义一个相关的任务,促进该任务的底层模型(在后面的章节中有更多关于该模型的内容)、和设备(设备=0 或就此而言,任何正值都表示使用 GPU,设备=-1 表示使用 CPU)。

from transformers import pipeline
classifier = pipeline(
                      task="zero-shot-classification",
                      device=0,
                      model="facebook/bart-large-mnli"
                    )

Loading zero-shot pipeline

一旦我们的分类器对象准备好了,我们就为 text_piece 、候选标签以及是否选择多类预测传递我们的例子。

import pprint

text_piece = "The food at this place is really good."
labels = ["Food", "Employee", "Restaurant", "Party", "Nature", "Car"]

predictions = classifier(text_piece, labels, multi_class=False)
pprint.pprint(predictions)

Zero-shot Topic Classification

{'labels': ['Food', 'Restaurant', 'Employee', 'Car', 'Party', 'Nature'],
 'scores': [0.6570185422897339,
            0.15241318941116333,
            0.10275784879922867,
            0.04373772069811821,
            0.027072520926594734,
            0.01700017973780632],
 'sequence': 'The food at this place is really good.'}

从上面的片段中可以看出,我们的模型在候选标签集中输出了一个 Softmax 分布。该模型似乎完美地捕捉到了围绕谈论的中心主题(即食物)的意图。

现在,让我们通过添加一个特定的模式来调整这一点,该模式试图以我们喜欢的方式执行分类。我已经将模板记为“用餐者在{}”,其中模型应该用上下文相关的位置填充括号“{ }”。让我们看看这个模型是否足够聪明。

import pprint

text_piece = "The food at this place is really good."
labels = ["Food", "Employee", "Restaurant", "Party", "Nature", "Car"]
template = "The diners are in the {}"
predictions = classifier(text_piece, 
           labels, 
           multi_class=False, 
           hypothesis_template=template
           )

Zero-shot Question Answering

{'labels': ['Food', 'Restaurant', 'Employee', 'Car', 'Party', 'Nature'],
 'scores': [0.6570185422897339,
  0.15241318941116333,
  0.10275784879922867,
  0.04373772069811821,
  0.027072520926594734,
  0.01700017973780632],
 'sequence': 'The food at this place is really good.'}

哇!模型得到了这个正确的(在最可能的意义上)。鉴于我们的模型从未在问答式文本分类上进行过明确训练,性能似乎仍然相当不错!

让我们这次设计另一个模板和不同的候选集来定义文本中传达的整体情绪。

import pprint

text_piece = "The food at this place is really good."
labels = ["Positive", "Negative", "Neutral"]
template = "The sentiment of this review is {}"
predictions = classifier(text_piece, 
           labels, 
           multi_class=False, 
           hypothesis_template=template
           )
pprint.pprint(predictions)

Zero-shot Sentiment Classification

{'labels': ['Positive', 'Neutral', 'Negative'],
 'scores': [0.8981141448020935, 0.07974622398614883, 0.02213958650827408],
 'sequence': 'The food at this place is really good.'}

不错!通过上面讨论的例子,很明显这个问题公式可以推广到各种下游任务。现在,您可以着手构建其他零测试用例了。另外,请随时查看这个在线演示。现在,让我们继续深入研究小细节。

在后台

在这一部分中,我们将研究调用管道时的步骤,并了解系统如何正确地将我们的文本分类到相关的标签中,而无需对它们进行明确的训练。

管道工作流程是一组堆栈功能,定义如下-

HuggingFace Pipeline workflow

Hugging Face Pipeline workflow

如上图所示,我们从文本序列作为输入开始,然后添加任何必要的特殊标记(如 SEP、CLS 等。)根据基础预训练模型和用例的要求。然后,我们使用标记器,将我们的序列分成更小的块,将其映射到预定义的词汇索引,并通过我们的模型进行推理。下一步,后处理,是可选的,取决于用例以及底层模型的输出。这包括需要完成的任何额外工作,如删除特殊标记、修剪到特定的最大长度等。最后,我们以输出结束。

再来说说上图中的推理步骤,底层模型(Facebook/Bart-large-mnli)在自然语言推理 (NLI)的任务上进行训练。NLI 的任务是确定两个序列,“前提”和“假设”是否相互遵循(必然)或不(矛盾)或不确定(中性)或彼此无关。NLP progress跟随下面的例子来更好地理解它-

Natural Language Inference Example

NLI Example from Source

“Facebook/BART-large-MNLI”多体裁自然语言推理【MNLI】语料库上微调 BART 模型。语料库中有近 50 万个句子对标注了文本蕴涵信息。BART 模型的输入是一对序列(前提&假设)针对长度为 3 的一个热输出向量(蕴涵、中性、矛盾)进行训练。

令人惊讶的是,通过将文本片段和候选标签分别视为前提和假设,这个问题公式可以适用于零镜头文本分类的任务。人们希望,随着模型在 NLI 任务上接受预训练,它现在能够理解和学习将两个文本片段联系起来的复杂性。这种知识现在可以用于确定候选集合中的任何标签是否需要文本片段。如果是这样,我们将该候选标签视为真实标签。你可以通过加载"joeddav/xlm-RoBERTa-large-XNLI",一个在XNLI数据集上微调的跨语言模型,在XLM·罗伯塔之上。

总结想法

这就是这篇博客的内容。无论我们今天讨论的是什么,都只是执行零镜头文本分类的一种可能方式。我们看到了将 NLI 问题公式化扩展到主题识别、问题回答和情感分析的方法。但是根据提示的格式,可能性是无限的。你可以在现代自然语言处理中的零镜头学习了解更多方法,并关注这个播放列表了解自然语言处理中零镜头和少镜头学习的最新研究。

谢谢大家!

posted @ 2024-11-02 15:52  绝不原创的飞龙  阅读(27)  评论(0)    收藏  举报