分享

【Deep Learning with PyTorch 中文手册】(九)Size,Offset,Strides

 叽歪小糖豆 2021-01-09

Size, storage offset, and strides

为了索引到内存中,张量依赖于一些信息,这些信息明确地定义了:尺寸大小,内存偏移量和单位步长(见下图)。存储尺寸大小信息的变量(NumPy中的术语是形状)是一个元组,表示张量的每个维度上有多少个元素。内存偏移量是内存器中与张量中的第一个元素相对应的索引增量。单位步长是内存中需要跳过的元素数量,以便沿每个维度访问下一个元素。

我们可以通过给定相应的索引来访问张量中的第二个点。

>>> import torch>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> second_point = points[1]>>> second_point.storage_offset()2>>> second_point.size()torch.Size([2])

返回的张量在内存存储中的偏移量为2(因为我们需要跳过第一个点,该点有两个值)。由于该张量是一维的,因此size仅包含一个元素,它是torch.Size类的实例对象。重要说明:此信息与张量的shape属性中包含的信息相同:

>>> second_point.shapetorch.Size([2])

最后,单位步长是一个元组,指示当索引在每个维度上增加1时必须跳过的内存存储中元素的数量。例如,我们访问points张量的stride属性:

>>> points.stride()(2, 1)

在2D张量中访问位置处于i,j的元素,等价于访问内存存储中的位置处于storage_offset + stride [0] * i + stride [1] * j的元素。storage_offset通常为零,如果此张量是某个更大张量的一部分,则storage_offset可能为正值。

张量和内存存储之间的这种联系使得某些操作(例如张量转置或子张量提取)执行起来很高效,因为程序不会为新的张量重新分配内存;另一方面,分配的新张量又具有不同的尺寸大小,内存偏移量和单位步长。

我们已经了解了如何提取指定点的子张量,并且子张量的storage_offset是个正值。现在我们来看下size和stride:

>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> second_point = points[1]>>> second_point.size()torch.Size([2])>>> second_point.storage_offset()2>>> second_point.stride()(1,)

最重要的是,second_point子张量的维度降低了,同时仍然和原始的points张量指向相同的内存存储。更改子张量的值也会改变原始张量:

>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> second_point = points[1]>>> second_point[0] = 10.0>>> pointstensor([[ 1., 4.], [10., 1.], [ 3., 5.]])

这种副作用可能并不总是令人满意的,但是我们可以将子张量克隆到一个新的张量中,从而避免这种副作用的影响:

>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> second_point = points[1].clone()>>> second_point[0] = 10.0>>> pointstensor([[1., 4.], [2., 1.], [3., 5.]])

现在我们进行转置操作。points张量的行存储的是独立的点,列存储的是点的x和y坐标,将其旋转使得张量的列存储的是独立的点:

>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> pointstensor([[1., 4.], [2., 1.], [3., 5.]])>>> points_t = points.t()>>> points_ttensor([[1., 2., 3.], [4., 1., 5.]])

我们可以很轻松就验证出这两个张量共享同一块内存:

>>> id(points.storage()) == id(points_t.storage())True

但是它们的形状和单位步长都不一样:

>>> points.stride()(2, 1)>>> points_t.stride()(1, 2)

该结果告诉我们,将points张量中第一个索引值增加1(即将points [0,0]改为points [1,0]),会沿内存存储跳过两个元素。而将points张量中第二个索引值增加1,即将points [0,0]改为点[0,1],会沿内存存储跳过一个元素。换句话说,内存存储中张量的元素是按逐行顺序保存的。

我们可以将points转置为points_t,如下图所示。这改变了stride中元素的顺序。转置后,增加行(张量的第一个索引)会沿着存储跳过1,就像沿着points的列移动时一样,这就是转置的定义。没有分配新的内存:仅通过创建新的张量实例(其stride与原始张量不同)来获得张量的转置。

在PyTorch中进行转置不仅限于矩阵。我们可以通过指定两个维度来转置任意多维张量(例如shape和stride):

>>> some_tensor = torch.ones(3, 4, 5)>>> some_tensor_t = some_tensor.transpose(0, 2)>>> some_tensor.shapetorch.Size([3, 4, 5])>>> some_tensor_t.shapetorch.Size([5, 4, 3])>>> some_tensor.stride()(20, 5, 1)>>> some_tensor_t.stride()(1, 5, 20)

我们对连续性张量的定义是:值从最右边的维开始存储在内存中(例如,沿着2D张量的行进行存储)。连续性张量很方便,因为我们可以高效且有序地访问它们,而无需在内存存储中跳来跳去。(得益于在现代CPU中的内存访问方式,改善数据访问的局部性可提高性能。)

这个例子中,points是连续性的,但是它的转置不是:

>>> points.is_contiguous()True>>> points_t.is_contiguous()False

我i们可以使用contiguous从非连续性张量中获得新的连续性张量。张量的内容保持不变,但stride和storage发生变化:

>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])>>> points_t = points.t()>>> points_ttensor([[1., 2., 3.], [4., 1., 5.]])>>> points_t.storage() 1.0 4.0 2.0 1.0 3.0 5.0[torch.FloatStorage of size 6]>>> points_t.stride()(1, 2)>>> points_t_cont = points_t.contiguous()>>> points_t_conttensor([[1., 2., 3.], [4., 1., 5.]])>>> points_t_cont.stride()(3, 1)>>> points_t_cont.storage() 1.0 2.0 3.0 4.0 1.0 5.0[torch.FloatStorage of size 6]

请注意,已对storage进行了重组,以便在新的内存存储中逐行存储元素。stride也会改变,以反映张量在内存中新的布局。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多