STEP 16 / 18

진짜 데이터로 배치 만들기

토큰화한 문장을 텐서로 바꿔 배치로 묶는다.

  • collate_batch: 문장마다 <s> + 단어 id들 + </s>를 이어 붙이고, max_padding 길이까지 <blank>(2)로 채운다. 그다음 배치로 쌓는다(torch.stack). DataLoader가 배치를 만들 때마다 이 함수를 부른다.
  • create_dataloaders: train/valid용 DataLoader를 만든다. 여러 GPU로 나눠 학습할 때는 DistributedSampler를 쓴다.

이 배치를 11단계의 Batch로 감싸면 학습에 필요한 tgt, tgt_y, 마스크가 생긴다. 14단계 복사 과제에서 data_gen이 하던 일을 진짜 데이터로 하는 것이다.

실행 결과에서 볼 것

  • 문장 길이는 제각각이지만 모두 16칸으로 맞춰져 있고, 뒤는 2(<blank>)로 채워져 있다.
  • id를 사전으로 되돌리면 원래 문장이 나온다.
  • 세 번째 문장은 16칸보다 길어서 뒤가 잘렸다. </s>도 없다. max_padding - len(...)이 음수가 되면 pad가 텐서를 잘라 버리기 때문이다. 원본 코드의 # warning - overwrites values for negative values of padding - len 주석이 이 현상을 가리킨다. 실제 학습에서는 max_padding=72라 거의 일어나지 않는다.

코드 변화

+126 −1 · 파일 전체 757줄

이전 단계의 transformer.py와 비교했다. 초록 줄이 새로 생긴 줄, 빨간 줄이 사라진 줄이다.

GitHub에서 보기 ↗
88import spacy
99import torch
1010import torch.nn as nn
11−from torch.nn.functional import log_softmax
11+from torch.nn.functional import log_softmax, pad
1212from torch.optim.lr_scheduler import LambdaLR
13+from torch.utils.data import DataLoader
14+from torch.utils.data.distributed import DistributedSampler
1315
1416
1517def attention(query, key, value, mask=None, dropout=None):
630632 print(len(vocab_src))
631633 print(len(vocab_tgt))
632634 return vocab_src, vocab_tgt
635+
636+
637+def collate_batch(
638+ batch,
639+ src_pipeline,
640+ tgt_pipeline,
641+ src_vocab,
642+ tgt_vocab,
643+ device,
644+ max_padding=128,
645+ pad_id=2,
646+):
647+ bs_id = torch.tensor([0], device=device) # <s> token id
648+ eos_id = torch.tensor([1], device=device) # </s> token id
649+ src_list, tgt_list = [], []
650+ for (_src, _tgt) in batch:
651+ processed_src = torch.cat(
652+ [
653+ bs_id,
654+ torch.tensor(
655+ src_vocab(src_pipeline(_src)),
656+ dtype=torch.int64,
657+ device=device,
658+ ),
659+ eos_id,
660+ ],
661+ 0,
662+ )
663+ processed_tgt = torch.cat(
664+ [
665+ bs_id,
666+ torch.tensor(
667+ tgt_vocab(tgt_pipeline(_tgt)),
668+ dtype=torch.int64,
669+ device=device,
670+ ),
671+ eos_id,
672+ ],
673+ 0,
674+ )
675+ src_list.append(
676+ # warning - overwrites values for negative values of padding - len
677+ pad(
678+ processed_src,
679+ (
680+ 0,
681+ max_padding - len(processed_src),
682+ ),
683+ value=pad_id,
684+ )
685+ )
686+ tgt_list.append(
687+ pad(
688+ processed_tgt,
689+ (0, max_padding - len(processed_tgt)),
690+ value=pad_id,
691+ )
692+ )
693+
694+ src = torch.stack(src_list)
695+ tgt = torch.stack(tgt_list)
696+ return (src, tgt)
697+
698+
699+def create_dataloaders(
700+ device,
701+ vocab_src,
702+ vocab_tgt,
703+ spacy_de,
704+ spacy_en,
705+ batch_size=12000,
706+ max_padding=128,
707+ is_distributed=True,
708+):
709+ # def create_dataloaders(batch_size=12000):
710+ def tokenize_de(text):
711+ return tokenize(text, spacy_de)
712+
713+ def tokenize_en(text):
714+ return tokenize(text, spacy_en)
715+
716+ def collate_fn(batch):
717+ return collate_batch(
718+ batch,
719+ tokenize_de,
720+ tokenize_en,
721+ vocab_src,
722+ vocab_tgt,
723+ device,
724+ max_padding=max_padding,
725+ pad_id=vocab_src.get_stoi()["<blank>"],
726+ )
727+
728+ train_iter, valid_iter, test_iter = Multi30k(
729+ language_pair=("de", "en")
730+ )
731+
732+ train_iter_map = to_map_style_dataset(
733+ train_iter
734+ ) # DistributedSampler needs a dataset len()
735+ train_sampler = (
736+ DistributedSampler(train_iter_map) if is_distributed else None
737+ )
738+ valid_iter_map = to_map_style_dataset(valid_iter)
739+ valid_sampler = (
740+ DistributedSampler(valid_iter_map) if is_distributed else None
741+ )
742+
743+ train_dataloader = DataLoader(
744+ train_iter_map,
745+ batch_size=batch_size,
746+ shuffle=(train_sampler is None),
747+ sampler=train_sampler,
748+ collate_fn=collate_fn,
749+ )
750+ valid_dataloader = DataLoader(
751+ valid_iter_map,
752+ batch_size=batch_size,
753+ shuffle=(valid_sampler is None),
754+ sampler=valid_sampler,
755+ collate_fn=collate_fn,
756+ )
757+ return train_dataloader, valid_dataloader

실행 결과

실행한 코드 demo.py 보기
1import torch
2
3from transformer import Batch, create_dataloaders, load_tokenizers, load_vocab
4
5torch.manual_seed(0)
6
7spacy_de, spacy_en = load_tokenizers()
8vocab_src, vocab_tgt = load_vocab(spacy_de, spacy_en)
9print()
10
11train_dl, valid_dl = create_dataloaders(
12 torch.device("cpu"), vocab_src, vocab_tgt, spacy_de, spacy_en,
13 batch_size=3, max_padding=16, is_distributed=False,
14)
15src, tgt = next(iter(train_dl))
16print("한 배치 (batch_size=3, max_padding=16)")
17print("src", tuple(src.shape))
18print(src)
19print("tgt", tuple(tgt.shape))
20print(tgt)
21print()
22itos_src, itos_tgt = vocab_src.get_itos(), vocab_tgt.get_itos()
23print("id를 다시 단어로")
24for i in range(3):
25 print(" de:", " ".join(itos_src[t] for t in src[i]))
26 print(" en:", " ".join(itos_tgt[t] for t in tgt[i]))
27print()
28b = Batch(src, tgt, pad=2)
29print("Batch로 감싸면 학습에 필요한 마스크가 붙는다")
30print(" src_mask", tuple(b.src_mask.shape), " tgt_mask", tuple(b.tgt_mask.shape), " ntokens", b.ntokens.item())
31print()
32print(f"학습 배치 수: {len(train_dl):,} (batch_size=3일 때). 실제 학습은 batch_size=32, max_padding=72")
Finished.
Vocabulary sizes:
8316
6384

한 배치 (batch_size=3, max_padding=16)
src (3, 16)
tensor([[   0,  124,  508, 2153,   19,  907,    4,    1,    2,    2,    2,    2,
            2,    2,    2,    2],
        [   0, 1071,   45,  114,   92,    9,  747,  202,    8, 1422,  845,   50,
            4,    1,    2,    2],
        [   0,   21,   43,    9,   18,   10,    6,   52,   41,    9,   56,   57,
           66,   10,  338,   11]])
tgt (3, 16)
tensor([[   0,   49, 1012,   17,  231,   50,    8,  516,    5,    1,    2,    2,
            2,    2,    2,    2],
        [   0, 2080,   13,   25,   35,   14,   27,  157,   11,  303,  531,    5,
            1,    2,    2,    2],
        [   0,   19,   22,   15,   52,    7,    4,   32,   23,   15,   17,   31,
           48,    8,   47,  605]])

id를 다시 단어로
  de: <s> Die Wanderer erklimmen die Berge . </s> <blank> <blank> <blank> <blank> <blank> <blank> <blank> <blank>
  en: <s> The hikers are climbing up the mountains . </s> <blank> <blank> <blank> <blank> <blank> <blank>
  de: <s> Nahaufnahme eines kleinen Jungen , dessen Gesicht und Körper bemalt ist . </s> <blank> <blank>
  en: <s> Closeup of young boy with his face and body painted . </s> <blank> <blank> <blank>
  de: <s> Zwei Personen , eine mit einem roten Hemd , sitzen am Wasser mit blicken auf
  en: <s> Two people , one in a red shirt , are sitting by the water facing

Batch로 감싸면 학습에 필요한 마스크가 붙는다
  src_mask (3, 1, 16)  tgt_mask (3, 15, 15)  ntokens 36

학습 배치 수: 9,667 (batch_size=3일 때). 실제 학습은 batch_size=32, max_padding=72