Element-Wise操作
是神经网络编程中带有张量的极其常见的操作。让我们以元素操作的定义开始讨论。
逐元素操作是两个张量之间的操作,其对相应张量内的相应元素进行操作
。
基于元素的操作对张量之间的相应元素进行操作。
如果两个元素在张量内占据相同位置,则认为这两个元素是对应的。该位置由用于定位每个元素的索引确定。
假设我们有以下两个张量:
> t1 = torch.tensor([
[1,2],
[3,4]
], dtype=torch.float32)
> t2 = torch.tensor([
[9,8],
[7,6]
], dtype=torch.float32)
这两个张量均为2 x 2
形状的2级张量
。
这意味着我们有两个轴,每个轴的长度均为两个元素。第一轴的元素是数组,第二轴的元素是数字。
# Example of the first axis
> print(t1[0])
tensor([1., 2.])
# Example of the second axis
> print(t1[0][0])
tensor(1.)
这是我们现在在本系列中经常看到的那种东西。好吧,让我们以此为基础。
我们知道,如果两个元素在张量内处于相同位置,则认为这两个元素是对应的,并且该位置由用于定位每个元素的索引确定。让我们看一个对应元素的例子。
> t1[0][0]
tensor(1.)
> t2[0][0]
tensor(9.)
这使我们看到t1中1的对应元素是t2
中9
的元素。
对应关系由索引定义。这很重要,因为它揭示了元素方式操作的重要特征。我们可以推断出张量必须具有相同数量的元素才能执行按元素的操作。
我们将继续进行此声明,使其更具限制性。两个张量必须具有相同的形状,以便对其执行按元素的操作。
让我们看一下我们第一个基于元素的操作加法。 不用担心 它将变得更加有趣。
> t1 + t2
tensor([[10., 10.],
[10., 10.]])
这使我们看到张量之间的加法是元素操作。 对应位置的每对元素被加在一起以产生相同形状的新张量。
因此,加法是按元素进行的运算,实际上,所有算术运算(加,减,乘和除)都是按元素进行的运算。
我们通常用张量看到的运算是使用标量值的算术运算。我们可以通过两种方式执行此操作:
(1)使用以下符号操作:
> print(t + 2)
tensor([[3., 4.],
[5., 6.]])
> print(t - 2)
tensor([[-1., 0.],
[ 1., 2.]])
> print(t * 2)
tensor([[2., 4.],
[6., 8.]])
> print(t / 2)
tensor([[0.5000, 1.0000],
[1.5000, 2.0000]])
或等效地,(2)这些内置的张量对象方法:
> print(t1.add(2))
tensor([[3., 4.],
[5., 6.]])
> print(t1.sub(2))
tensor([[-1., 0.],
[ 1., 2.]])
> print(t1.mul(2))
tensor([[2., 4.],
[6., 8.]])
> print(t1.div(2))
tensor([[0.5000, 1.0000],
[1.5000, 2.0000]])
这两个选项的工作原理相同。我们可以看到,在两种情况下,标量值2均通过相应的算术运算应用于每个元素。
这里似乎有些问题。这些例子打破了我们建立的规则,即所述基于元素的运算在相同形状的张量上进行操作。
标量值是Rank-0张量
,这意味着它们没有形状,而我们的张量t1
是2 x 2
形状的rank-2
张量。
那么这如何适合呢?让我们分解一下。
可能想到的第一个解决方案是,该操作仅使用单个标量值并对张量内的每个元素进行操作。
这种逻辑的作品。但是,这有点令人误解,并且在我们注意到使用标量的更一般情况下,它会崩溃。
为了以不同的方式考虑这些操作,我们需要引入张量广播或广播
的概念 (broadcasting or broadcasting)
。
广播
描述在元素操作期间如何处理具有不同形状的张量。
广播是一个概念,其实现使我们可以将标量添加到高维张量。
让我们考虑一下t1 + 2
运算。在此,将定标器值张量广播为t1的形状,然后执行逐元素运算。
我们可以使用broadcast_to()
Numpy函数来查看广播的标量值:
> np.broadcast_to(2, t1.shape)
array([[2, 2],
[2, 2]])
这意味着标量值就像t1
一样被转换为2级张量,正因为如此,形状匹配,并且具有相同形状的逐元素规则又重新发挥了作用。当然,这全都隐藏了。
这段代码可以说是一幅画。这个
> t1 + 2
tensor([[3., 4.],
[5., 6.]])
确实是这样的:
> t1 + torch.tensor(
np.broadcast_to(2, t1.shape)
,dtype=torch.float32
)
tensor([[3., 4.],
[5., 6.]])
在这一点上,您可能认为这似乎有些令人费解,所以让我们看一个更棘手的例子来说明这一点。假设我们有以下两个张量。
让我们来看一个更棘手的例子。假设我们有以下张量。
t1 = torch.tensor([
[1,1],
[1,1]
], dtype=torch.float32)
t2 = torch.tensor([2,4], dtype=torch.float32)
这种按元素加法运算的结果是什么?甚至可以为元素操作指定相同的形状规则吗?
# t1 + t2 ???????
> t1.shape
torch.Size([2, 2])
> t2.shape
torch.Size([2])
即使这两个男高音具有不同的形状,也可以进行按元素的操作,而广播使之成为可能。较低等级的张量t2将通过广播进行变换,以匹配较高等级的张量t1的形状,并且像往常一样执行逐元素运算。
广播的概念是理解如何执行此操作的关键。和以前一样,我们可以使用broadcast_to()
numpy函数检查广播转换。
> np.broadcast_to(t2.numpy(), t1.shape)
array([[2., 4.],
[2., 4.]], dtype=float32)
> t1 + t2
tensor([[3., 5.],
[3., 5.]])
广播后,这两个张量之间的加法运算是相同形状的张量之间的常规按元素运算。
广播是比基本的元素操作更高级的主题,因此不用担心花费更长的时间来熟悉这个想法。
理解逐元素的操作和相同的形状要求为广播的概念以及为什么使用它提供了基础。
我们什么时候真正使用广播?在预处理数据时,尤其是在规范化例程期间,我们经常需要使用广播。
TensorFlow.js
系列中有一篇文章详细介绍了广播。有一个实际的例子,并且还介绍了用于确定如何广播特定张量的算法,因此请仔细阅读有关广播的更深入的讨论。
不用担心不了解TensorFlow.js
。这不是必须的,我强烈建议您在广播中观看其中的内容。
比较操作也是按元素操作。
对于两个张量之间的给定比较操作,将返回一个具有相同形状的新张量,每个元素都包含torch.bool值为True或False的元素。
Version 1.2.0
返回的比较操作dtype
已从torch.uint8
更改为torch.bool(21113)
。
版本1.1:
> torch.tensor([1, 2, 3]) < torch.tensor([3, 1, 2])
tensor([1, 0, 0], dtype=torch.uint8)
版本1.2:
> torch.tensor([1, 2, 3]) < torch.tensor([3, 1, 2])
tensor([True, False, False])
相关链接:
以下示例显示了PyTorch版本1.2.0
及更高版本的输出。
假设我们有以下张量:
> t = torch.tensor([
[0,5,0],
[6,0,7],
[0,8,0]
], dtype=torch.float32)
我们来看看其中的一些比较操作。
> t.eq(0)
tensor([[True, False, True],
[False, True, False],
[True, False, True]])
> t.ge(0)
tensor([[True, True, True],
[True, True, True],
[True, True, True]])
> t.gt(0)
tensor([[False, True, False],
[True, False, True],
[False, True, False]])
> t.lt(0)
tensor([[False, False, False],
[False, False, False],
[False, False, False]])
> t.le(7)
tensor([[True, True, True],
[True, True, True],
[True, False, True]])
从广播角度考虑这些操作,我们可以看到最后一个t.le(7)
实际上是这样的:
> t <= torch.tensor(
np.broadcast_to(7, t.shape)
,dtype=torch.float32
)
tensor([[True, True, True],
[True, True, True],
[True, False, True]])
等效地:
> t <= torch.tensor([
[7,7,7],
[7,7,7],
[7,7,7]
], dtype=torch.float32)
tensor([[True, True, True],
[True, True, True],
[True, False, True]])
使用作为函数的逐元素运算,可以很好地假定该函数应用于张量的每个元素。
这里有些例子:
> t.abs()
tensor([[0., 5., 0.],
[6., 0., 7.],
[0., 8., 0.]])
> t.sqrt()
tensor([[0.0000, 2.2361, 0.0000],
[2.4495, 0.0000, 2.6458],
[0.0000, 2.8284, 0.0000]])
> t.neg()
tensor([[-0., -5., -0.],
[-6., -0., -7.],
[-0., -8., -0.]])
> t.neg().abs()
tensor([[0., 5., 0.],
[6., 0., 7.],
[0., 8., 0.]])
还有其他一些方法可以引用基于元素的操作,因此我只想提及所有这些都意味着同一件事:
如果您在野外遇到任何这些术语,请记住这一点。
现在,我们应该对基于元素的操作以及如何将它们应用于神经网络和深度学习的张量操作有一个很好的了解。在下一篇文章中,我们将介绍张量操作的最后两类:
下一个见!