2023. 12. 16. 14:44ㆍDevelopers 공간 [Shorts]/Software Basic
<분류>
A. 수단
- OS/Platform/Tool : Linux, Kubernetes(k8s), Docker, AWS
- Package Manager : node.js, yarn, brew,
- Compiler/Transpillar : React, Nvcc, gcc/g++, Babel, Flutter
- Module Bundler : React, Webpack, Parcel
B. 언어
- C/C++, python, Javacsript, Typescript, Go-Lang, CUDA, Dart, HTML/CSS
C. 라이브러리 및 프레임워크 및 SDK
- OpenCV, OpenCL, FastAPI, PyTorch, Tensorflow, Nsight
1. What? (현상)
copy 방법에는 얕은 복사와 깊은 복사가 있습니다.
- 얕은 복사(Shallow Copy) : 객체의 참조값(주소값)만을 복사하며, 복사시 메모리 상의 주소가 바뀌지 않는 것을 의미합니다.
- 깊은복사(Deep Copy) : 객체의 실제 메모리를 다른 공간에 복사하며, 복사시 메모리 상의 주소가 바뀝니다.
이번 글에서는 python과 pyTorch에서 다양한 copy방법일 때 위 두 방법 중 어떤 방법으로 copy하는지 기록해두려고합니다.
살펴보기에 앞서 일반적으로 알고있는 정보를 기록해두고자 합니다. pythom의 객체는 두가지로 나뉩니다.
- immutable 객체(int,float,str,tuple,bool) : 원시 타입과 비슷하게 값이 바뀌면 다른 메모리 공간을 할당해 넣어 주게 됩니다. 즉, 항상 deep copy를 진행합니다.
** 원시 타입(Primitive Type), 내장형, 기본형 : 메모리 상에 1:1로 대응되는 객체로, Low-level에서 한번의 연산으로 처리될 수 있는 객체들을 의미합니다. 원시 타입의 값은 변경이 불가능하므로, 값 자체를 변경할 수 없는 읽기 전용의 값입니다.
("변수" 값을 변경할 수 없다는 것이 X. 변수에 새로운 원시 타입값을 할당하면 됩니다. 변수 값을 변경할 수 없는것은 "상수")
보통 정수, 실수, 문자 등의 기본적인 자료형을 의미하는데, Python은 C와 Java와 다르게 모든 것을 원시타입이 아닌 객체(Object)로 관리하므로, 원시 자료형을 지원하지 않습니다.따라서 이에 대체로 immutable object(불변형 객체)를 만들었으며, "bool, str, int, float, tuple"이 그것들입니다.
a = 5
print(id(a)) # 111117
b = 5
print(id(b)) # 111117
a = 6
print(id(a)) # 111118
- mutable 객체(list, dict, set) : 값이 동일해도, 그안의 값을 바꿀 수 있으므로 deep copy와 shallow copy로 나뉩니다.
a = [5]
print(id(a)) # 111117
b = [5]
print(id(b)) # 111118
a = [6]
print(id(a)) # 111119
a[0] = 5
print(id(a)) # 111119
Case3. list,torch.Tensor 복사(=) : 값이 바뀌는 변수(mutable)
- B=A, B = A.copy() 혹은 리스트인경우 B=A[:] : shallow copy
- B = copy.deepcopy(A) 혹은 텐서인 경우 B = A.clone(): 새로운 공간에 allocation
- 함수 :
- input :
- value : deep
- list : shallow
- return :
- value :
- list : shallow
- input :
- torch에서 deepcopy
- 원래 b= copy.deepcopy(a)를 활용해 복사할수있지만, clone()을하면 복사할 수 있다. 하지만 clone을하면 graph에서유지가 되므로 graph의영향을 받을 수가 있다. 따라서 .clone().detach()를해주면 graph에서 빼줄 수 있다.
2. Why? (원인)
- X
3. How? (해결책)
실험에 앞서, python 에서의 객체의 address를 얻기 위한 함수를 getid()라는 함수로 정의해두겠습니다.
def getid(x):
return hex(id(x))
Python Object
먼저 값을 선언해보겠습니다. immutable 객체인 integer와 float, 그리고 mutable 객체인 list와 dict입니다.
주소는 아래와 같습니다.
print("=============================================values")
value_int:int = 5
print("value_int : {}".format(getid(value_int)))
value_float :float = 5.0
print("value_float : {}".format(getid(value_float)))
value_list = [1,2,3]
print("value_list : {}".format(getid(value_list)))
value_dict = {"a":4, "b":5, "c":6}
print("value_dict : {}".format(getid(value_dict)))
=============================================values
value_int : 0xa68b40
value_float : 0x7f051b18a240
value_list : 0x7f051af95dc8
value_dict : 0x7f051b12f5a0
1-1. 객체 할당
일반적으로 객체를 다른 곳에 할당하는 경우에는 무조건 Shallow Copy를 진행합니다.
print("=============================================assigned to")
a1 = value_int
print("value_int moved : {} --> {}".format(getid(value_int), getid(a1)))
b1 = value_float
print("value_float moved : {} --> {}".format(getid(value_float), getid(b1)))
c1 = value_list
print("value_list moved : {} --> {}".format(getid(value_list), getid(c1)))
d1 = value_dict
print("value_dict moved : {} --> {}".format(getid(value_dict), getid(d1)))
=============================================assigned to
value_int moved : 0xa68b40 --> 0xa68b40
value_float moved : 0x7f051b18a240 --> 0x7f051b18a240
value_list moved : 0x7f051af95dc8 --> 0x7f051af95dc8
value_dict moved : 0x7f051b12f5a0 --> 0x7f051b12f5a0
1-2. 새로운 객체 할당
해당 객체에 새로운 객체를 할당하는 경우, 새로운 객체의 주소로 새로 부여받게 됩니다. 당연하겠죠?
print("=============================================assigned from")
a1=2
print("value_int moved : {} --> {}".format(getid(value_int), getid(a1)))
b1 = 3.0
print("value_float moved : {} --> {}".format(getid(value_float), getid(b1)))
c1 = [4,5,6]
print("value_list moved : {} --> {}".format(getid(value_list), getid(c1)))
d1 = {"d":7,"e":8}
print("value_dict moved : {} --> {}".format(getid(value_dict), getid(d1)))
=============================================assigned from
value_int moved : 0xa68b40 --> 0xa68ae0
value_float moved : 0x7f051b18a240 --> 0x7f051b18a078
value_list moved : 0x7f051af95dc8 --> 0x7f051af9e748
value_dict moved : 0x7f051b12f5a0 --> 0x7f051af9a948
2-1. copy()
copy.copy()는 python에 내장된 객체.copy()와 같습니다. (immutable 객체는 내장 copy함수가 없습니다)
보통 shallow copy라고 알고 있지만 아래 결과를 살펴보겠습니다.
print("=============================================copy")
import copy
a2 = copy.copy(value_int)
print("value_int moved : {} --> {}".format(getid(value_int), getid(a2)))
b2 = copy.copy(value_float)
print("value_float moved : {} --> {}".format(getid(value_float), getid(b2)))
c2 = copy.copy(value_list)
print("value_list moved : {} --> {}".format(getid(value_list), getid(c2)))
d2 = copy.copy(value_dict)
print("value_dict moved : {} --> {}".format(getid(value_dict), getid(d2)))
=============================================copy
value_int moved : 0xa68b40 --> 0xa68b40
value_float moved : 0x7f051b18a240 --> 0x7f051b18a240
value_list moved : 0x7f051af95dc8 --> 0x7f051af48688
value_dict moved : 0x7f051b12f5a0 --> 0x7f051af9a7e0
특이한 것이 보이시나요? shallow copy를 진행한다고 했는데, mutable객체인 list와 dict의 주소가 바뀌었습니다.
즉, 객체 자체를 shallow copy하겠다는 것이 아니고, deep copy를 하되 nested 된 것에 대해서는 shallow copy를 진행하는 것이죠. 아래 예를 보면 알 수 있습니다.
import copy
i = [1,2,3,[4,5]]
j = copy.copy(i)
getid(i)==getid(j) # False
getid(i[3])==getid(j[3]) # True
2-2. deepcopy()
이번엔 copy.deepcopy()를 살펴보겠습니다.
우리가 알고 있는 완전한 deepcopy일 것입니다.
print("=============================================deepcopy")
a3 = copy.deepcopy(value_int)
print("value_int moved : {} --> {}".format(getid(value_int), getid(a3)))
b3 = copy.deepcopy(value_float)
print("value_float moved : {} --> {}".format(getid(value_float), getid(b3)))
c3 = copy.deepcopy(value_list)
print("value_list moved : {} --> {}".format(getid(value_list), getid(c3)))
d3 = copy.deepcopy(value_dict)
print("value_dict moved : {} --> {}".format(getid(value_dict), getid(d3)))
=============================================deepcopy
value_int moved : 0xa68b40 --> 0xa68b40
value_float moved : 0x7f051b18a240 --> 0x7f051b18a240
value_list moved : 0x7f051af95dc8 --> 0x7f051af48648
value_dict moved : 0x7f051b12f5a0 --> 0x7f051af9a9d8
위와 다른 점을 살펴보기 위해 아래 예시를 보면 알 수 있습니다.
import copy
i = [1,2,3,[4,5]]
j = copy.deepcopy(i)
getid(i)==getid(j) # False
getid(i[3])==getid(j[3]) # False
3. 함수
이번엔 함수의 경우 어떻게 될지 살펴보겠습니다.
함수 내부에 들어갈 때는 해당 객체를 그대로 가지고 가게되지만(shallow copy), return 되고 나서는 immutable 객체의 경우 deep copy가 되고, mutable 객체의 경우는 shallow copy를 하게 됩니다.
print("=============================================function")
def function_int(input):
print("---> {}".format(getid(input)), end='')
temp = input
temp += 1
return temp
def function_float(input):
print("---> {}".format(getid(input)), end='')
temp = input
temp += 1.0
return temp
def function_list(input):
print("---> {}".format(getid(input)), end='')
temp = input
temp[0] += 1
return temp
def function_dict(input):
print("---> {}".format(getid(input)), end='')
temp = input
temp['a'] += 1
return temp
print("value_int moved : {} --> ".format(getid(value_int)), end='')
a4 = function_int(value_int)
print(" --> {}".format(getid(a4)))
print("value_float moved : {} --> ".format(getid(value_float)), end='')
b4 = function_float(value_float)
print(" --> {}".format(getid(b4)))
print("value_list moved : {} --> ".format(getid(value_list)), end='')
c4 = function_list(value_list)
print(" --> {}".format(getid(c4)))
print("value_dict moved : {} --> ".format(getid(value_dict)), end='')
d4 = function_dict(value_dict)
print(" --> {}".format(getid(d4)))
=============================================function
value_int moved : 0xa68b40 --> ---> 0xa68b40 --> 0xa68b60
value_float moved : 0x7f051b18a240 --> ---> 0x7f051b18a240 --> 0x7f051b18a2e8
value_list moved : 0x7f051af95dc8 --> ---> 0x7f051af95dc8 --> 0x7f051af95dc8
value_dict moved : 0x7f051b12f5a0 --> ---> 0x7f051b12f5a0 --> 0x7f051b12f5a0
PyTorch Tensor Object
먼저 값을 선언해보겠습니다. immutable 객체인 integer와 float, 그리고 mutable 객체인 list입니다. Torch Tensor에는 Dict와 비슷한 객체를 지원하지 않습니다. (따로 TensorDict라는 것이 존재합니다.)
주소는 아래와 같습니다. Tensor의 주소를 얻는 방법은 위에서 우리가 정의한 getid()함수가 객체.storage().data_ptr()가 있습니다.
Torch Tensor는 "Wrapper"입니다. 따라서, __getitem__()이라는 내장함수를 통해 실제 값을 얻어 낼 수 있습니다. 따라서 Tensor의 실제 Storage 값의 주소를 얻어내기 위해서는 객체.storage().data_ptr()을 활용하는 것이 좋습니다. 실제 id(Tensor)를 하면 Tensor 객체의 주소를 얻어낼 수는 있겠지만, id(Tensor[9])와 같이 하면 id를 wrapper object에 사용하는 것이나 다름없기 때문입니다.
하지만 우리는 Tensor 자체의 주소를 볼 것이기 때문에 id() 함수를 통해 살펴볼 것이고, 위와 같은 내용을 참고하기 위해 가로() 안에 storage의 주소도 표시해 두었습니다.
print("=============================================tensors")
import torch
tensor_int = torch.tensor(5, dtype=torch.int)
print("tensor_int : {}({})".format(getid(tensor_int), tensor_int.storage().data_ptr()))
tensor_float = torch.tensor(5.0, dtype=torch.float)
print("tensor_float : {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr()))
tensor_list = torch.tensor([1,2,3], dtype=torch.int)
print("tensor_list : {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr()))
=============================================tensors
tensor_int : 0x7f1595c9dd68(29813120)
tensor_float : 0x7f1595c9ddb8(76152448)
tensor_list : 0x7f1595cbe048(81358336)
1-1. 객체 할당
역시나 일반적으로 객체를 다른 곳에 할당하는 경우에는 무조건 Shallow Copy를 진행합니다.
print("=============================================assigned to")
a5 = tensor_int
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a5), a5.storage().data_ptr()))
b5 = tensor_float
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b5), b5.storage().data_ptr()))
c5 = tensor_list
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c5), c5.storage().data_ptr()))
=============================================assigned to
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f1595c9dd68(29813120)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f1595c9ddb8(76152448)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f1595cbe048(81358336)
1-2. 새로운 객체 할당
역시나 해당 객체에 새로운 객체를 할당하는 경우, 새로운 객체의 주소로 새로 부여받게 됩니다. 당연하겠죠?
print("=============================================assigned from")
a5 = torch.tensor(2 , dtype=torch.int)
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a5), a5.storage().data_ptr()))
b5 = torch.tensor(3.0, dtype=torch.float)
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b5), b5.storage().data_ptr()))
c5 = torch.tensor([4,5,6], dtype=torch.int)
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c5), c5.storage().data_ptr()))
=============================================assigned from
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f1595cabc28(29868608)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f1595cabc78(81378624)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f1595cabcc8(77870720)
2-1. detach()
Tensor에는 detech() 라는 함수가 내장되어 있습니다. Torch Tenseor는 복사가 되더라도 Back Propagation 연산에서 gradient계산에 활용되기 위해 graph에 여전히 포함되어 있게 되는데, 이런 graph에서 제외시키기 위한 함수입니다.
따라서 detach()만 실행하면 Tensor를 복사(deep copy)하고 실제 storage 메모리는 공유(shallow copy)하게됩니다. 아래 결과를 보면 알 수 있습니다. 다만 graph에서는 제외되었겠죠?
print("=============================================detach")
a6 = tensor_int.detach()
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a6), a6.storage().data_ptr()))
b6 = tensor_float.detach()
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b6), b6.storage().data_ptr()))
c6 = tensor_list.detach()
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c6), c6.storage().data_ptr()))
=============================================detach
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f1595cabd18(29813120)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f14d23129f8(76152448)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f14d0ca1368(81358336)
2-2. clone()
Tensor에는 clone() 이라는 함수가 내장되어 있습니다. 이는 그냥 PyTorch에서 제공하는 deep copy라고 이해해주시면 좋습니다.
따라서 Tensor를 deep copy하고, 실제 메모리도 deep copy합니다.
print("=============================================clone")
a7 = tensor_int.clone()
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a7), a7.storage().data_ptr()))
b7 = tensor_float.clone()
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b7), b7.storage().data_ptr()))
c7 = tensor_list.clone()
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c7), c7.storage().data_ptr()))
=============================================clone
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f14d0ca14f8(79637504)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f14d0ca1548(29429952)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f14d0ca1598(29212736)
clone()은 detach()와 다르게 graph에서 제외시키지는 않습니다. 따라서 tensor를 따로 graph에서 제외시키면서 deepcopy하기 위해서는 아래와 같이 사용합니다.
before = torch.tensor(tensor_float, requires_grad=True)
copied1 = before.clone().detach()
copied2 = before.clone().detach().requires_grad_(True)
3-1. copy()
copy.copy()는 python에 내장된 객체.copy()와 같습니다.
위의 일반적인 Python 객체와 같이 deep copy하게 되지만, 앞에서 언급된 python 객체가 Nested 된 값에 대해서는 shallow copy하는 것처럼 내부적인 실제 storage 메모리는 공유(shallow copy)하게됩니다.
print("=============================================copy")
import copy
a8 = copy.copy(tensor_int)
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a8), a8.storage().data_ptr()))
b8 = copy.copy(tensor_float)
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b8), b8.storage().data_ptr()))
c8 = copy.copy(tensor_list)
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c8), c8.storage().data_ptr()))
=============================================copy
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f14d0ca15e8(29813120)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f14d0ca1638(76152448)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f14d0ca1688(81358336)
3-2. deepcopy()
이번엔 copy.deepcopy()를 살펴보겠습니다.
우리가 알고 있는 완전한 deepcopy일 것입니다. 위 clone()과 동일하게 동작하네요.
print("=============================================deepcopy")
a9 = copy.deepcopy(tensor_int)
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a9), a9.storage().data_ptr()))
b9 = copy.deepcopy(tensor_float)
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b9), b9.storage().data_ptr()))
c9 = copy.deepcopy(tensor_list)
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c9), c9.storage().data_ptr()))
=============================================deepcopy
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f14d0ca16d8(81261824)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f14d0ca1728(81264320)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f14d0ca1778(81264896)
4-1. 함수
이번엔 함수의 경우 어떻게 될지 살펴보겠습니다.
위와 다르게 함수 내부에 들어갈 때는 해당 객체를 그대로 가지고 가게되며(shallow copy), return 되고 나서도 모든 객체가 shallow copy를 하게 됩니다.
print("=============================================function")
def function_int(input):
print("---> {}({})".format(getid(input), input.storage().data_ptr()), end='')
temp = input
temp += 1
return temp
def function_float(input):
print("---> {}({})".format(getid(input), input.storage().data_ptr()), end='')
temp = input
temp += 1.0
return temp
def function_list(input):
print("---> {}({})".format(getid(input), input.storage().data_ptr()), end='')
temp = input
temp[0] += 1
return temp
print("tensor_int moved : {}({}) --> ".format(getid(tensor_int), tensor_int.storage().data_ptr()), end='')
a10 = function_int(tensor_int)
print(" --> {}({})".format(getid(a10), a10.storage().data_ptr()))
print("tensor_float moved : {}({}) --> ".format(getid(tensor_float), tensor_float.storage().data_ptr()), end='')
b10 = function_float(tensor_float)
print(" --> {}({})".format(getid(b10), b10.storage().data_ptr()))
print("tensor_list moved : {}({}) --> ".format(getid(tensor_list), tensor_list.storage().data_ptr()), end='')
c10 = function_list(tensor_list)
print(" --> {}({})".format(getid(c10), c10.storage().data_ptr()))
=============================================function
tensor_int moved : 0x7f1595c9dd68(29813120) --> ---> 0x7f1595c9dd68(29813120) --> 0x7f1595c9dd68(29813120)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> ---> 0x7f1595c9ddb8(76152448) --> 0x7f1595c9ddb8(76152448)
tensor_list moved : 0x7f1595cbe048(81358336) --> ---> 0x7f1595cbe048(81358336) --> 0x7f1595cbe048(81358336)
4-2. 내장 함수
이번엔 함수의 경우 어떻게 될지 살펴보겠습니다. 내장된 함수중 transpose()함수를 활용해서 변화를 주어봤습니다.
주소가 바뀐 것을 보니 deep copy를 진행한 것 같습니다만, 내부적인 실제 storage 메모리는 공유(shallow copy)하게됩니다.
print("=============================================function2")
a11 = tensor_int.T
print("tensor_int moved : {}({}) --> {}({})".format(getid(tensor_int),tensor_int.storage().data_ptr(), getid(a11), a11.storage().data_ptr()))
b11 = tensor_float.T
print("tensor_float moved : {}({}) --> {}({})".format(getid(tensor_float), tensor_float.storage().data_ptr(), getid(b11), b11.storage().data_ptr()))
c11 = tensor_list.T
print("tensor_list moved : {}({}) --> {}({})".format(getid(tensor_list), tensor_list.storage().data_ptr(), getid(c11), c11.storage().data_ptr()))
==============================================function2
tensor_int moved : 0x7f1595c9dd68(29813120) --> 0x7f14d0ca1868(29813120)
tensor_float moved : 0x7f1595c9ddb8(76152448) --> 0x7f14d0ca1908(76152448)
tensor_list moved : 0x7f1595cbe048(81358336) --> 0x7f14d0ca1958(81358336)
'Developers 공간 [Shorts] > Software Basic' 카테고리의 다른 글
[Bash] apt-get 사용시 GPG error(Couldn't create temporary file) (0) | 2023.12.31 |
---|---|
[Docker] Dockerfile 내 상위 디렉토리 COPY하고 싶을 때 (0) | 2023.12.30 |
[Python] yaml, json,csv 읽고 쓰기 (0) | 2023.12.08 |
[Docker] no space left on device (0) | 2023.11.18 |
[PyTorch] 특정 GPU로 정해 동작시키기 (1) | 2023.11.08 |