PyTorch의 복제 및 로컬 작업 상세 정보

9337 단어 pytorch카피현지

앞말


PyTroch에서 우리는Numpy를 사용하여 데이터를 처리한 다음Tensor로 전환하는 것을 자주 사용하지만, 데이터의 변경과 관련될 때 우리는 공유 주소가 맞는지 주의해야 한다. 이것은 전체 네트워크의 업데이트와 관련된다.이 글은 In-palce 작업, 복사 작업의 주의점에 대해 총괄한다.

In-place 작업


pytorch 중원지 조작의 접미사는_,예컨대add_() 또는.scatter_(), 로컬 작업은 복사하지 않고 지정된 Tensor의 내용을 직접 변경하는 작업입니다. 변수에 새 메모리를 할당하지 않습니다.Python 작업은 += 또는 *=와 마찬가지로 현장에서 수행됩니다.(내가 나를 추가했다~)
왜 in-place 작업은 고차원 데이터를 처리할 때 메모리 사용을 줄이는 데 도움을 줄 수 있습니까? 다음 예시를 사용하여 다음과 같은 간단한 함수를 정의하여 PyTorch의 이위 ReLU(out-of-place)와 현지 ReLU(in-place)가 분배한 메모리를 측정합니다.

import torch # import main library
import torch.nn as nn # import modules like nn.ReLU()
import torch.nn.functional as F # import torch functions like F.relu() and F.relu_()

def get_memory_allocated(device, inplace = False):
 '''
 Function measures allocated memory before and after the ReLU function call.
 INPUT:
 - device: gpu device to run the operation
 - inplace: True - to run ReLU in-place, False - for normal ReLU call
 '''
 
 # Create a large tensor
 t = torch.randn(10000, 10000, device=device)
 
 # Measure allocated memory
 torch.cuda.synchronize()
 start_max_memory = torch.cuda.max_memory_allocated() / 1024**2
 start_memory = torch.cuda.memory_allocated() / 1024**2
 
 # Call in-place or normal ReLU
 if inplace:
 F.relu_(t)
 else:
 output = F.relu(t)
 
 # Measure allocated memory after the call
 torch.cuda.synchronize()
 end_max_memory = torch.cuda.max_memory_allocated() / 1024**2
 end_memory = torch.cuda.memory_allocated() / 1024**2
 
 # Return amount of memory allocated for ReLU call
 return end_memory - start_memory, end_max_memory - start_max_memory
 # setup the device
device = torch.device('cuda:0' if torch.cuda.is_available() else "cpu")
# 
# Call the function to measure the allocated memory for the out-of-place ReLU
memory_allocated, max_memory_allocated = get_memory_allocated(device, inplace = False)
print('Allocated memory: {}'.format(memory_allocated))
print('Allocated max memory: {}'.format(max_memory_allocated))
'''
Allocated memory: 382.0
Allocated max memory: 382.0
'''
#Then call the in-place ReLU as follows:
memory_allocated_inplace, max_memory_allocated_inplace = get_memory_allocated(device, inplace = True)
print('Allocated memory: {}'.format(memory_allocated_inplace))
print('Allocated max memory: {}'.format(max_memory_allocated_inplace))
'''
Allocated memory: 0.0
Allocated max memory: 0.0
'''
로컬 작업을 사용하면 GPU 메모리를 절약할 수 있을 것으로 보입니다.그러나 현지에서 조작할 때는 각별히 신중해야 한다.
현지 조작의 주요 단점은 두 가지가 있다공식 문서 :
1. 사다리 계산에 필요한 값을 덮어쓸 수 있는데 이것은 모델의 훈련 과정을 파괴했다는 것을 의미한다.
2. 모든 현지 조작은 실제로 계산도를 다시 쓰기 위해 실현되어야 한다.오프사이트 작업 Out-of-place는 새 대상을 분배하고 낡은 그림에 대한 인용을 유지하며, 오프사이트 작업은 이 동작을 나타내는 함수의 모든 입력을 바꾸는 창설자를 변경해야 합니다.
Autograd에서 로컬 작업을 지원하는 것은 매우 어렵고 대부분의 경우 사용을 권장하지 않습니다.Autograd의 적극적인 버퍼 방출과 재사용은 매우 효율적이며, 현지에서 실제로 메모리 사용량을 줄이는 경우는 매우 적다.무거운 메모리 압력 아래에서 실행하지 않으면, 그것들을 영원히 사용할 필요가 없을 수도 있다.
요약: Autograd는 매우 향기롭기 때문에 현지에서 조작하는 것은 신중하게 사용해야 한다.

복제 방법


얕은 복사 방법: 데이터의 메모리 주소를 공유하면 데이터가 동기화됩니다.
* a.numpy() # Tensor―>Numpy array
*view()#tensor의 모양을 바꾸지만 데이터 메모리를 공유하고 id로 판단하지 마십시오
* y = x[:] # 인덱스
* torch.from_numpy() # Numpy array―>Tensor
* torch.detach () # 새로운tensor는 계산도에서 벗어나 사다리 계산에 연루되지 않습니다.
* model:forward()
그리고 많은 선택 함수도 데이터 공유 메모리입니다. 예를 들어 index_select() masked_select() gather().
그리고 뒷글에서 언급한 현지 조작 in-place.

딥 카피 방법:


* torch.clone () # 새로운tensor는 계산도에 보존되어 계단식 계산에 참여합니다
다음은 먼저 얕은 복사본을 검증합니다.

import torch as t
import numpy as np
a = np.ones(4)
b = t.from_numpy(a) # Numpy->Tensor
print(a)
print(b)
''' :
[1. 1. 1. 1.]
tensor([1., 1., 1., 1.], dtype=torch.float64)
'''
b.add_(1)# add_ b 
print(a)
print(b)
''' :
[2. 2. 2. 2.]
tensor([2., 2., 2., 2.], dtype=torch.float64)
b add , a,b 
'''
Tensor와numpy 대상은 메모리 (얕은 복사 작업) 를 공유하기 때문에 그들 사이의 전환이 빠르고 동기화됩니다.
torch에서 y=x+y 같은 연산은 메모리를 새로 열고 y를 새 메모리로 가리킨다.검증을 하기 위해서, 우리는 Python이 가지고 있는 id 함수를 사용할 수 있습니다. 만약 두 개의 실례의 ID가 일치한다면, 그것들이 대응하는 메모리 주소는 같습니다.그러나 주의해야 할 것은 torch에서 특수한 것이 있기 때문에 데이터 공유를 할 때tensor를 직접 출력하는 id는 여전히 다르다.

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_0 = id(y)
y = y + x
print(id(y) == id_0) 
# False 
이 때 색인 조작을 사용하면 새로운 메모리를 열 수 없습니다. 결과를 원래의 y의 메모리로 지정하려면 색인을 사용하여 교체 작업을 할 수 있습니다.예를 들어 x+y의 결과를 [:]를 통해 y에 대응하는 메모리에 쓴다.

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_0 = id(y)
y[:] = y + x
print(id(y) == id_0) 
# True
또한 다음 두 가지 방법으로 같은 메모리에 인덱싱할 수 있습니다.
  • torch.add(x, y, out=y)
  • y += x, y.add_(x)
  • 
    x = torch.tensor([1, 2])
    y = torch.tensor([3, 4])
    id_0 = id(y)
    torch.add(x, y, out=y) 
    # y += x, y.add_(x)
    print(id(y) == id_0) 
    # True
    

    clone () 과 detach () 비교


    Torch는 속도를 높이기 위해 벡터나 행렬의 값이 같은 메모리를 가리키는데 이것은 Matlab와 다르다.오래된 tensor를 저장하려면 인용이 아닌 새로운 저장 주소를 열어야 합니다. clone () 으로 깊이 복사할 수 있습니다.
    우선 clone () 작업 후의 데이터 형식 정의 변화를 출력합니다.
    (1). 단순 인쇄 유형
    
    import torch
    
    a = torch.tensor(1.0, requires_grad=True)
    b = a.clone()
    c = a.detach()
    a.data *= 3
    b += 1
    
    print(a) # tensor(3., requires_grad=True)
    print(b)
    print(c)
    
    '''
     :
    tensor(3., requires_grad=True)
    tensor(2., grad_fn=<AddBackward0>)
    tensor(3.)  # detach() a 
    '''
    
    grad_fn=, clone 후의 반환 값은 중간 변수이기 때문에 사다리의 소거를 지원합니다.clone 조작은 어느 정도에identity-mapping 함수로 볼 수 있다.
    detach () 작업 후의tensor는 원시tensor와 데이터 메모리를 공유합니다. 원시tensor가 계산도에서 수치가 역방향으로 전파되는 등 업데이트가 발생하면detach () 의tensor 값도 변경됩니다.
    주의:pytorch에서 우리는 id가 같은지 아닌지를 직접 사용해서 텐서가 메모리를 공유하는지 판단하지 마십시오. 이것은 충분한 조건입니다. 왜냐하면 밑바닥에서 데이터 메모리를 공유할 수도 있지만 여전히 새로운 텐서, 예를 들어 detach () 입니다. 만약에 우리가 직접 id를 인쇄하면 다음과 같은 상황이 발생할 수 있습니다.
    
    import torch as t
    a = t.tensor([1.0,2.0], requires_grad=True)
    b = a.detach()
    #c[:] = a.detach()
    print(id(a))
    print(id(b))
    #140568935450520
    140570337203616
    
    분명히 직접 출력된 id는 같지 않다. 우리는 간단한 값을 부여한 후에 데이터의 변화를 관찰하여 판단할 수 있다.
    (2). clone () 의 사다리 회전
    detach () 함수는 완전히 같은tensor를 되돌려줍니다. 낡은 tensor와 메모리를 공유하고 계산도에서 벗어나 사다리 계산에 연루되지 않습니다.
    반면에 clone은 중간 변수를 충당하면 사다리를 원량에 전달하여 중첩되지만 그 자체는grad를 저장하지 않습니다. 즉, 값은 None입니다.
    
    import torch
    a = torch.tensor(1.0, requires_grad=True)
    a_ = a.clone()
    y = a**2
    z = a ** 2+a_ * 3
    y.backward()
    print(a.grad) # 2
    z.backward()
    print(a_.grad)   # None.  variable, grad
    print(a.grad) 
    '''
     :
    tensor(2.) 
    None
    tensor(7.) # 2*2+3=7
    '''
    
    torch를 사용합니다.clone () 에서 얻은 새로운tensor와 원래의 데이터는 더 이상 메모리를 공유하지 않지만 계산도에 남아 있습니다. clone 작업은 데이터 메모리를 공유하지 않는 동시에 사다리 전달과 중첩을 지원하기 때문에 신경 네트워크의 어떤 단원이 중복적으로 사용해야 하는 장면에 자주 사용됩니다.
    보통 원tensor의requires_grad=True:
  • clone () 조작 후 tensor requires_grad=True
  • detach() 작업 후tensorrequires_grad=False.
  • 
    import torch
    torch.manual_seed(0)
    
    x= torch.tensor([1., 2.], requires_grad=True)
    clone_x = x.clone() 
    detach_x = x.detach()
    clone_detach_x = x.clone().detach() 
    
    f = torch.nn.Linear(2, 1)
    y = f(x)
    y.backward()
    
    print(x.grad)
    print(clone_x.requires_grad)
    print(clone_x.grad)
    print(detach_x.requires_grad)
    print(clone_detach_x.requires_grad)
    '''
     :
    tensor([-0.0053, 0.3793])
    True
    None
    False
    False
    '''
    
    또 다른 특이한 것은 원본 장량의 require_grad=False, clone 후 장량 require_grad=True, 이때 장량 회전 현상이 존재하지 않으며,clone 후의 장량 구도를 얻을 수 있습니다.
    다음과 같습니다.
    
    import torch
    a = torch.tensor(1.0)
    a_ = a.clone()
    a_.requires_grad_() #require_grad=True
    y = a_ ** 2
    y.backward()
    print(a.grad) # None
    print(a_.grad) 
    '''
     :
    None
    tensor(2.)
    '''
    

    요약:


    torch.detach () - 새로운tensor는 계산도에서 벗어나 사다리 계산에 연루되지 않습니다
    torch.clone () - 새로운tensor는 중간 변수를 충당하여 계산도에 보존하고 사다리 계산(회전 중첩)에 참여하지만 일반적으로 자신의 사다리를 보존하지 않습니다.
    제자리 작업(in-place, such as resize_/resize_as_/set_/transpose_)위에서 두 가지를 실행하면 모두 오류나 경고를 일으킬 수 있다.
    공식 문서를 인용하면: 만약 당신이 in-placeoperation을 사용하고 오류를 보고하지 않았다면, 당신의 사다리 계산이 정확하다는 것을 확정할 수 있습니다.또한 가능한 한 in-place의 사용을 피해야 한다.
    이 PyTorch의 복사와 현지 조작에 관한 글은 여기 소개되어 있습니다. 더 많은 PyTorch 복사와 현지 조작 내용은 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기