t.stride()에서 결과가 (1, 4)라는 것은 t(0, 0)에서 t(1, 0)으로갈 때 요소 1개만큼 메모리 주소가 이동하고 t(0, 0)에서 t(0, 1)로 이동할 때 요소 4만큼 메모리 주소가 바뀐다는 뜻이다.
Pytorch에서 자주 사용하는 view()나 reshape()같은 함수를 보면 비슷한 역할을 하는데 굳이 나눠져 있는 것을 볼 수 있다.
이는 Contiguous한 Tensor를 Input으로 받을 수 있는지, output이 Contiguous Tensor인지에 대한 차이 때문이라고 볼 수 있다.
예를 들어 view()는 Contiguous한 Tensor를 받고 Contiguous한 Tensor를 리턴하고,
transpose()는 Non-contiguous Tensor를 받을 순 있지만 리턴하는 Tensor는 Non-contiguous하다.
pytorch method
복사본과 메모리 공유 (새로운 Tensor 생성 X)
Contiguous tensor에서 동작
Non-Contiguous tensor에서 동작
반환 Tensor
view()
O
O
X
Contiguous
reshape()
O
O
O
Contiguous same as contiguous().view()
transpose()
O
O
O
Non-Contiguous
permute()
O
O
O
Non-Contiguous
narrow()
O
O
O
Non-Contiguous
expand()
O
O
O
Non-Contiguous
bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())
#(1, 3)
#False
ccc = aaa.narrow(1,1,2) ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())
#(3, 1)
#False
ddd = aaa.repeat(2,1) # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())
#(3, 1)
#True
## expand is different from repeat.
## if a tensor has a shape [d1,d2,1], it can only be expanded using "expand(d1,d2,d3)", which
## means the singleton dimension is repeated d3 times
eee = aaa.unsqueeze(2).expand(2,3,3)
print(eee.stride())
print(eee.is_contiguous())
#(3, 1, 0)
#False
fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())
복사본과 메모리를 공유하지 않는 다는 것은 연산 후 결과를 새로운 Tensor로 생성하지 않는 다는 것을 의미한다. 즉, Tensor object의 새로운 shape을 설명하기 위한 offset과 stride가 포함된 meta information 만 modify 함을 의미한다. 따라서 반환된 값은 원본과 data(memory)를 공유하기 때문에 둘 중 하나만 수정해도 두개 모두 수정된다.
x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42
z = y.contiguous()
x는 contiguous하지만 y는 contiguous하지 않고 x와 메모리를 공유한다. copy를 만들어서 메모리에 요소들이 순서대로 저장되게 하고 싶다면 contiguous()를 이용하면 된다.