최근에 pytorch로 간단한 모듈을 재구현하다가 loss와 dev score가 원래 구현된 결과와 달라서 의아해하던 찰나, tensor 차원을 변경하는 과정에서 의도하지 않은 방향으로 구현된 것을 확인하게 되었다. 그리고 그 이유는 transpose 와 view 의 기능을 헷갈려했기 때문이었다. 두 함수의 차이는 contiguous 를 이해해야 알 수 있는 내용이었고, 혹시 이 개념이 헷갈리는 사람들을 위해 간단한 예시를 바탕으로 정리해보았다.
contiguous 란 matrix 의 눈에 보이는 (advertised) 순차적인 shape information 과 실제로 matrix 의 각 데이터가 저장된 위치가 같은지의 여부이다. 아래의 예시에서 t 는 contiguous 하다. 왜냐하면 t[0][0][0] → t[0][0][1] → t[0][1][0] ... 의 데이터 포인터 위치 (0 → 1 → 2 ... ) 또한 연속적이기 때문이다. 아직 이해가 되지 않아도 괜찮다. 예시를 좀 더 보자!
또한 t 와 tv 의 데이터는 pointer 값이 동일하여 한 쪽의 tensor data 를 수정하면 다른 쪽의 값도 동시에 변경된다.
t.storage().data_ptr() == tv.storage().data_ptr() # data pointer 값이 일치함 --- True
# Modifying view tensor changes base tensor as well. t[0][0][0] = 99 tv[0][0][0] --- tensor(99)
transpose
transpose 를 통해 t 라는 텐서의 shape을 변경시켜보았다. shape은 tv와 동일하나, 구성이 조금 다른 것을 확인할 수 있다. 참고로, 보통 (batch_size, hidden_dim, input_dim) 을 (batch_size, input_dim, hidden_dim) 으로 바꿔주는 작업을 할 때에 transpose 를 사용한다.
tt = t.transpose(2, 1) # (4, 2, 3)
print(tt) >folded
tensor([[[ 0, 2, 4], [ 1, 3, 5]],
[[ 6, 8, 10], [ 7, 9, 11]],
[[12, 14, 16], [13, 15, 17]],
[[18, 20, 22], [19, 21, 23]]])
앞선 예시에서 t 의 데이터 포인터는 0 → 1 → 2 ... 순서대로 저장된 것을 알 수 있었다. t와 tv 모두 데이터 포인터의 물리적 순서와 shape 상에서의 데이터 순서가 같았기 때문에 contiguous 했다. 하지만 tt 의 경우 0 → 1 → 2 ... ≠ tt[0][0][0] → tt[0][0][1] → tt[0][0][2] ... 이기 때문에 contiguous 하지 않다.
tt.is_contiguous() --- False
tt 를 flatten 시키면 물리적 순서 (0 → 1 → 2 ... ) 와 shape 상의 순서가 다른 것을 확인할 수 있다.
RuntimeError: view size isnot compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.