상세 컨텐츠

본문 제목

Packing, Unpacking (feat. Transformers 예시 코드)

개발/python-심화(Advanced)

by Matthew0633 2022. 5. 5. 22:23

본문

함수 또는 변수할당 시 가변길이를 허용하여, flexibility를 높여주기 위한 의미에서 파이썬에서는 packing, unpacking 기능을 지원하고 있다. 연산자 * (asterisk) 의 사용 방식에 따라 packing, unpacking 수행이 나뉘어지므로, 어떤 때에 packing 또는 unpacking이 수행되는지를 정확히 알아야 에러없이 수월하게 사용할 수 있다

* 연산자 (asterisk) 는 non-keyworded, 즉 인자(argument)들에게 key-value로 맵핑되지 않은 값들에 대해 packing 및 unpacking 이라는 기능을 수행하는데 packing 이란, 여러개의 값을 하나의 tuple 로 묶어주는 기능이며, unpacking이란, 하나의 자료구조에 들어있던 원소의 값들을 개수에 맞게 자동으로 원소에 할당하는 것을 뜻한다.

정의된 함수를 호출(call) 할 때, 인자로 sequence type 앞에 해당 연산자를 붙여준다면 unpacking이 실행된다. 즉, 여러개 원소를 포함한 하나의 데이터가 인자로 주어졌지만, 원소들을 풀어서 (unpacking) 여러개로 함수 인자로 전달한다. 따라서 아래 예시에서 divmod(*(100, 9)) 는 곧 divmod(100, 9))와 동일하게 된다고 이해할 수 있다.

print(*(divmod(100, 9))) 의 경우에는 divmod함수가 계산된 뒤 print(*(11, 1)) 가 되고, print문 안에서 unpacking을 통해 11과 1이 따로 전달되어 하나의 tuple아닌, 두개의 값을 공백으로 이은 string이 출력된 것을 알 수 있다

print(divmod(100, 9))    # (11, 1)
print(divmod(*(100, 9))) # (11, 1)
print(*(divmod(100, 9))) # 11 1

그렇다면, 이번에는 packing이 실행되는 예를 보자. 변수 할당 시에도 변수 앞에 * 연산자를 활용하게 되면, 해당 변수에게 할당될 수 있는 값들을 packing하여 전달하게 된다. 아래 예시와 같이 여러개의 변수에 여러 개의 값을 할당할 때, 길이가 정해진 부분을 미리 할당하고, 나머지 값들에 대해 packing 으로써 변수에 할당되게 할 수 있다

# unpacking + packing
x, y, *rest = range(10)
print(x, y, rest)           # 0 1 [2, 3, 4, 5, 6, 7, 8, 9]

x, y, *rest = range(2)
print(x, y, rest)           # 0 1 []

x, y, *rest = 1, 2, 3, 4, 5
print(x, y, rest)           # 1 2 [3, 4, 5]

추가내용

강의에는 없었지만, packing, unpacking과 관련된 개념들을 좀 더 조사해서 추가적으로 함께 정리하고자 한다. 함수호출할 때는 인자로 넣은 값 앞에 * 를 붙인다면 해당 값이 인자들로 unpacking 되는 것을 확인하였다. 그러나, 함수정의할 때 매개변수(parameter) 앞에 * 를 사용한다면 packing 기능을 수행한다. 아래 예제를 보면, func함수에서 매개변수 args가 여러개의 값을 받았을 때 packing을 수행하여 print 하도록 정의하였다. 그 결과 함수의 return 값을 출력하면 tuple 객체가 출력되는 것을 확인할 수 있다. 매개변수명이 꼭 args 가 아니여도 에러가 나지 않지만, 일종의 convention이니 args를 쓰고 있으니 맞추어 쓰도록 하자

# packing - 함수의 매개변수들에 대해 packing 수행
def func(*args):
    return args

print(func(2,3,4)) # (2, 3, 4)

추가적으로 ** 는 keyworded argument, 즉 인자와 값이 key-value로 맵핑된 Dict type 과 관련하여 packing 또는 unpacking을 수행한다. 아래 코드의 함수를 정의한 부분을 보면, job 매개변수 이외에 더 많은 매개변수와 값들이 함께 들어올 때는 이들을 Dict type으로 packing하여 kwargs 변수에 전달하도록 되어있다. 따라서 함수를 실행할 때 job외에 다른 인자와 값을 명시해도, Dict type으로 kwargs로 전달되었고 return 된 것을 확인할 수 있다

def func(job, **kwargs):
    return job, kwargs

print(func(job = "MLEngineer", domain = 'NLP', lang = "python")) # ('MLEngineer', {'domain': 'NLP', 'lang': 'python'})

**unpacking 예를 보자. 함수를 실행할 때, 인자와 값을 Dict type으로 생성하여 함수 인자로 전달하고 해당 연산자를 사용한다. 그러면, 각 unpacking 이 수행되어 인자들에 값을 풀어서 전달한 것과 같게 된다

params = {'job' : "MLEngineer", 'domain' : 'NLP', 'lang' : "python"}
print(func(**params))  # ('MLEngineer', {'domain': 'NLP', 'lang': 'python'})

Transformers 라이브러리를 사용자들은 이러한 **unpacking 이 너무나 익숙하다.

전처리 단계에서, Tokenizer 가 문장들을 encoding 하게 되면 “input_ids”, “attention_mask”, “token_type_ids” 를 key로 가지고 해당 tensor들을 value로 가지는 Dict 객체를 반환하게 된다. (아래에 batch와 유사) 이것을 model의 forward method 내에 unpacking 해주기 위해 **batch 를 model의 인자로 넣어주는 것이 일반적인 코드이다

# Huggingface Transformers 사용 시
model = ElectraForSequenceClassification(ckpt, num_labels = 2) # Binary Classification 으로 Fine-tuning
....
....
for epoch in range(num_epochs):
  for step, batch in enumerate(train_loader):
	"""
    batch = {
            "input_ids": torch.tensor([ .... ]),
            "attention_mask": torch.tensor([ .... ]),
            "token_type_ids": torch.tensor([ .... ])
            }
    """
    
    ...
    outputs = model(**batch)
    ...

 

관련글 더보기

댓글 영역