국문과 유목민

[NLP과제] 함수 정리 (chain, Pythonic) 본문

IT 견문록/함수 및 코드 (디지털치매 대비)

[NLP과제] 함수 정리 (chain, Pythonic)

논곰 2022. 3. 19. 18:47

 부스트캠프_AITech 8주차에 진행되었던 과제를 풀고 Solution을 보면서, Pythonic하게 만들어진 코드를 보고서 기억해두면 좋을 것 같아서 정리하고자 한다. 해당 과제의 문제를 올리지는 않고, 주어진 문제에 대해서 어떻게 코드를 간결하게 짰는지에 대해서만 정리하고자 하며, 추가적으로 새로 알게 된 좋은 함수들을 정리하고자 한다.

기본과제3_Subword_level_Language_Model

Subword_level_Language_Model을 만들기 위해서 BPE Vocabulary를 만드는 함수를 구현하는 문제였다. 해당 문제에서 chain함수와 Counter 객체를 활용해 연산에 필요한 변수를 간결하게 정의할 수 있었다.

from itertools import chain
from collections import Counter

###  BPE Vocab을 만들기 위한 Word tokenizing방법
WORD_END = '_'
corpus = ['low'] * 5 + ['lower'] * 2 + ['newest'] * 6 + ['widest'] * 3
# Chain
vocabulary = list(set(chain.from_iterable(corpus)) | {WORD_END})
print(vocabulary
# Counter
corpus = {' '.join(word + WORD_END): count for word, count in Counter(corpus).items()}
print(corpus)
### 이하 생략 ###
### Output ###
# > ['w', 'r', 'd', 'e', 'o', 'n', 'i', 'l', '_', 't', 's']
# > {'l o w _': 5, 'l o w e r _': 2, 'n e w e s t _': 6, 'w i d e s t _': 3}

 해당 코드에서 내가 얻었던 인사이트는 다음과 같다.

  • Set을 통한 단어 추가
  • { }을 활용한 List Comprehension
  • chain함수의 사용

그 중 Chain 함수에 대해 알아보던 도중 꽤 유용하다는 생각이 들었다.  우선 Chain함수는 여러 개의 리스트를 하나의 리스트로 만들어주는 fancy한 함수이다. (단, itertools 객체 이므로 List로 형변환이 필요하다.) 여기서는 chain.from_iterable을 사용하는데, iterble 객체를 입력으로 받아서 chain 함수를 적용시켜준다.

import numpy as np
ls1 = np.random.randint(50, 100, 5)
ls2 = np.random.randint(1, 50, 5)
list(chain(ls1, ls2))
### Output ###
# > [76, 84, 51, 81, 72, 28, 4, 4, 2, 38]
ls3 = [[i]for i in range(10)]
print(ls3)
# [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]
list(chain.from_iterable(ls3))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

기본과제4_Preprocessing_for_NMT_Model

Natural Machine Translation을 하기 위해 필요한 함수들을 작성하는 과제였다. 해당 과제를 진행하면서 Pythonic하게 짜인 코드를 본 것 같아서 해당 코드를 좀 정리해두고자 한다. 특히, Pad_sequence함수를 사용함에 있어서 torch의 pad_sequence는 List가 아닌 Tensor를 입력으로 받기 때문에 이를 형변환 해줘야 한다는 사실을 새로 알게 됐다. 그리고 이 과정에서 좀 코드를 깔끔하게 짜지 못했었다. 

collate_fn

다양한 길이의 문장을 배치화하기 위하여 한 배치 내의 최대 길이 문장을 기준으로 문장에 패딩을 넣는 과정이 필요하다. 이때, 길이가 다른 문장들의 경우 차원이 맞지 않기 때문에, Padding(=0)을 넣어서 차원을 맞춰줘야 한다.

# dictionary는 튜플을 키값으로 줄 수 있다.
batched_samples = [([1, 2, 3, 4], [1]), ([1, 2, 3], [1, 2]), ([1, 2], [1, 2, 3]), ([1], [1, 2, 3, 4])]
src_sentence_list, trg_sentence_list = zip(*batched_samples)
src_sentences = pad_sequence([torch.Tensor(sentence).to(torch.long) for sentence in src_sentence_list],
                batch_first=True,
                padding_value=PAD)
tgt_sentences = pad_sequence([torch.Tensor(sentence).to(torch.long) for sentence in trg_sentence_list],
                batch_first=True,
                padding_value=PAD)

해당 코드에서 내가 얻었던 인사이트는 다음과 같다.

  • Pad_sequence는 텐서만 입력으로 가능하기 때문에 Lis로의 형변환이 필요하다.
  • [torch.Tensor(sentence).to(torch.long) for sentence in src_sentence_list]: 주어진 Sentence List를 Tensor로 바꿔주는 함수 
  • zip함수는 입력으로 들어오는 Tuple들을 idx별로 묶어준다. (반복문 없이 한 번에 List형태로 return되는 것은 처음 봤음)

Bucketing

Bucketing은 델의 학습 시간을 단축하기 위해 고안된 방법으로 주어진 문장의 길이에 따라 데이터를 그룹화하여 패딩을 적용하는 기법입니다. 이 문장의 길이에 따라 미리 그룹화하여 패딩을 적용하면 학습을 효율적으로 진행할 수 있습니다.

sentence_length = [(random.randint(min_len, max_len), random.randint(min_len, max_len))
                    for _ in range(dataset_length)]
batch_size = 64
max_pad_len = 5

bucket_dict = defaultdict(list)
for index, (src_length, trt_length) in enumerate(sentence_length):
    bucket_dict[(src_length // max_pad_len, trt_length // max_pad_len)].append(index)
batch_indices_list = [bucket[start:start+batch_size] for bucket in bucket_dict.values() for start in range(0, len(bucket), batch_size)]

해당 코드에서 내가 얻었던 인사이트는 다음과 같다.

  • src_lengthtrt_lengthpadding length와 나눠 나온 몫의 쌍을 Key값으로 활용한다. 그렇게 함으로써 특정 범위가 Key값으로 설정이 되고, 이를 활용해 해당 범위에 있는 Value들을 별도로 Grouping할 수 있다.
  • 이렇게 나온 Group들을 반복적으로 돌면서 batch_size만큼 잘라서 list에 넣어주면 된다.
  • 이럴 경우 문장 수가 batch_size보다 작은 배치가 만들어질 수도 있지만, 그 수는 'src_length//max_pad_len'과 'tgt_length//max_pad_len'의 각 개수들의 곱을 넘지 않는다. (5개, 5개이면 작은 배치는 25개를 넘지 않는다.)
Comments