PyTorch - 19 - 可调用神经网络 - 深度的线性层

How Linear Layers Work

在本系列的最后一篇文章中,我们学习了线性层如何使用矩阵乘法将其内部特征转换为外部特征。
在这里插入图片描述

当输入特征被线性层接收时,它们将以平坦的一维张量形式接收,然后与权重矩阵相乘。 该矩阵乘法产生输出特征。

让我们在代码中看一个例子。

Transform Using A Matrix

in_features = torch.tensor([1,2,3,4], dtype=torch.float32)

weight_matrix = torch.tensor([
    [1,2,3,4],
    [2,3,4,5],
    [3,4,5,6]
], dtype=torch.float32)

> weight_matrix.matmul(in_features)
tensor([30., 40., 50.])

在这里,我们创建了一个称为in_features的一维张量。 我们还创建了一个权重矩阵,它当然是二维张量。 然后,我们使用matmul()函数来执行产生一维张量的矩阵乘法运算。

通常,权重矩阵定义一个线性函数,该函数将具有四个元素的一维张量映射到具有三个元素的一维张量。 我们可以将此函数视为从4维欧几里得空间到3维欧几里得空间的映射。

这也是线性层如何工作的方式。 他们使用权重矩阵将in_feature空间映射到out_feature空间。

Transform Using A PyTorch Linear Layer

让我们看看如何创建一个PyTorch线性图层来执行相同的操作。

fc = nn.Linear(in_features=4, out_features=3, bias=False)

在这里,我们有它。我们定义了一个线性图层,该图层接受4个输入要素并将其转换为3个输出要素,因此我们从4维空间转换为3维空间。我们知道,权重矩阵用于执行此操作,但是此示例中的权重矩阵在哪里?

我们将权重矩阵存在于PyTorch LinearLayer类中,并由PyTorch创建。 PyTorch LinearLayer类使用传递给构造函数的数字4和3创建3 x 4权重矩阵。让我们通过查看PyTorch源代码来验证这一点。

# torch/nn/modules/linear.py (version 1.0.1)

def __init__(self, in_features, out_features, bias=True):
    super(Linear, self).__init__()
    self.in_features = in_features
    self.out_features = out_features
    self.weight = Parameter(torch.Tensor(out_features, in_features))
    if bias:
        self.bias = Parameter(torch.Tensor(out_features))
    else:
        self.register_parameter('bias', None)
    self.reset_parameters()

如我们所见,当我们将3 x 4矩阵与4 x 1矩阵相乘时,结果是3 x 1矩阵。这就是为什么PyTorch以此方式构建权重矩阵的原因。这些是用于矩阵乘法的线性代数规则。

让我们看看如何通过传递in_features张量来调用层。

> fc(in_features)
tensor([-0.8877,  1.4250,  0.8370], grad_fn=<SqueezeBackward3>)

我们可以这样称呼对象实例,因为PyTorch神经网络模块是可调用的Python对象。我们将在一分钟内更仔细地研究这个重要的细节,但首先,请查看此输出。实际上,我们确实获得了包含三个元素的一维张量。但是,产生了不同的值。

这是因为PyTorch会创建一个权重矩阵,并使用随机值对其进行初始化。这意味着两个示例中的线性函数是不同的,因此我们使用不同的函数来生成这些输出。
在这里插入图片描述

请记住,权重矩阵内的值定义了线性函数。这说明了在训练过程中,网络的映射如何随着权重的更新而变化。

让我们显式设置线性层的权重矩阵,使其与我们在其他示例中使用的权重矩阵相同。

fc.weight = nn.Parameter(weight_matrix)  

PyTorch模块权重需要作为参数。这就是为什么我们将权重矩阵张量包装在参数类实例中的原因。现在让我们看一下该层如何使用新的权重矩阵转换输入。我们希望看到与前面的示例相同的结果。

> fc(in_features)
tensor([30.0261, 40.1404, 49.7643], grad_fn=<AddBackward0>)

这次我们更接近30、40和50的值。但是,我们是正确的。为什么是这样?我们会说的不对,因为线性层在输出上添加了一个偏置张量。观察当我们关闭偏差时会发生什么。为此,我们将False标志传递给构造函数。

fc = nn.Linear(in_features=4, out_features=3, bias=False)
fc.weight = nn.Parameter(weight_matrix)
> fc(in_features)
tensor([30., 40., 50.], grad_fn=<SqueezeBackward3>)

在那里,现在我们有一个完全匹配。这就是线性层的工作方式。

Mathematical Notation Of The Linear Transformation

有时我们会看到称为:
y = A x + b y=Ax + b y=Ax+b
在此等式中,我们具有以下内容:

变量 定义
A 权重矩阵张量
x 输入张量
b 偏张量
y 输出张量

我们将注意到,这类似于直线的方程式
y = m x + b y=mx + b y=mx+b

Callable Layers And Neural Networks

我们之前指出了将图层对象实例称为函数是多么奇怪。

> fc(in_features)
tensor([30.0261, 40.1404, 49.7643], grad_fn=<AddBackward0>)

使之成为可能的是PyTorch模块类实现了另一个特殊的Python函数__call __()。如果一个类实现__call __()方法,则在调用对象实例时将调用特殊的调用方法。

这个事实是一个重要的PyTorch概念,因为__call __()方法与我们的图层和网络的forward()方法交互的方式。

代替直接调用forward()方法,我们调用对象实例。调用对象实例后,将在幕后调用__call __()方法,然后__call __()依次调用forward()方法。这适用于所有PyTorch神经网络模块,即网络和层。

让我们在PyTorch源代码中看到这一点。

# torch/nn/modules/module.py (version 1.0.1)

def __call__(self, *input, **kwargs):
    for hook in self._forward_pre_hooks.values():
        hook(self, input)
    if torch._C._get_tracing_state():
        result = self._slow_forward(*input, **kwargs)
    else:
        result = self.forward(*input, **kwargs)
    for hook in self._forward_hooks.values():
        hook_result = hook(self, input, result)
        if hook_result is not None:
            raise RuntimeError(
                "forward hooks should never return any values, but '{}'"
                "didn't return None".format(hook))
    if len(self._backward_hooks) > 0:
        var = result
        while not isinstance(var, torch.Tensor):
            if isinstance(var, dict):
                var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
            else:
                var = var[0]
        grad_fn = var.grad_fn
        if grad_fn is not None:
            for hook in self._backward_hooks.values():

PyTorch在__call __()方法中运行的额外代码是为什么我们从不直接调用forward()方法的原因。如果这样做,将不会执行其他PyTorch代码。结果,任何时候我们想要调用forward()方法时,我们都将调用对象实例。这适用于层和网络,因为它们都是PyTorch神经网络模块。

现在,我们准备实现网络的forward()方法。下一个见!

猜你喜欢

转载自blog.csdn.net/weixin_48367136/article/details/112531455