cuda toolkit 12.1 지원

pytorch 2버전이상부터는 cuda toolkit 12.1으로 사용하는게 pip 이나 설치가 편하기에, toolkit 12.1을 설치하려고함.

sudo ubuntu-drivers autoinstall

 

cuda toolkit 12.1 install

ubuntu 공식 도큐먼트(URL)을 따라서 설치하려고함. 

 

Trouble shooting 1: package has invalid Support PBheader, cannot determine support level

ubuntu 그레픽드라이버 유틸리티인 `ubuntu-drivers list`로 현재 설치가능한 드라이버를 찾다보면 위와 같은 경고를 볼 수 있는 습니다. 아래와 같이 해결했습니다 [URL]. 

$ sudo apt update
$ sudo apt upgrade

 

Trouble shooting 2: but it is not going to be installed

 

sudo apt remove nvidia*
sudo apt autoremove
반응형

 

DataParallel (DP)

DP은 데이터 병렬화 기술 중, 싱글노드에서만 사용할 수 있는 병렬화 기술입니다.

Forward and Backward passes with torch.nn.DataParallel. src: https://medium.com/huggingface/training-larger-batches-practical-tips-on-1-gpu-multi-gpu-distributed-setups-ec88c3e51255

위의 그림과 같이 포워딩(첫 행)에서는 배치를 GPU 개수만큼 쪼개 보내고, 모델도 복제(replicas)하고 각 장치에서 포워딩후에 그 결과값을 가져오는 형식입니다. 백워드에서는 [o1,o2,o3,o4]을 GPU-1에서 받은 것을 label과의 차이를 계산하여 각 손실 [loss1, loss2, loss3, loss4] 을 계산하여 각 gradient을 계산합니다. 이렇게 계산한 각 gradient은 다시 각자의 장치로보내 백워드를 하고, 다시 집계합니다.

def data_parallel(module, input, device_ids, output_device=None):
    if not device_ids:
        return module(input)

    if output_device is None:
        output_device = device_ids[0]

    replicas = nn.parallel.replicate(module, device_ids)
    inputs = nn.parallel.scatter(input, device_ids)
    replicas = replicas[:len(inputs)]
    outputs = nn.parallel.parallel_apply(replicas, inputs)
    return nn.parallel.gather(outputs, output_device)
  • `nn.parallel.replicate`: pytorch model을 각 장치에 복사합니다.
  • `nn.parallel.scatter`: pytorch 장치를 첫차원(=배치)에 대해서 복제합니다.
  • `nn.parallel.parallel_apply`: 복제된 모델에 입력값을 포워딩합니다.
  • `nn.parallel.gather`: output결과값을 `output_device`에 모아와 concat합니다.

 

아래와 같이 DP은 `torch.DataParallel(model)`의 원라인만 추가하면 사용할 수 있던 기술입니다 [1, 2]. 이 클레스는 데이터 병렬(DP)은 모듈레벨(torch.nn.module) 에서 손 쉽게 사용할 수 있도록 구현한 것입니다. 일단 데이터를 작은 배치사이즈로 나눈 후(split), 데이모델을 각 장치(GPU)에 복제한 후, 데이터를 fowarding합니다. 이후, 각 복제된 모델로부터 백워드(backpropagation)을 진행하고, 원본(original module, 첫 번째 장치에 있던)로 모듈로 가져와 집계합니다[3]. 

CLASS torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
  • module: pytorch model을 의미
  • device_ids:cuda devices (디폴트가 all devices)
  • output_devices: 복제한 모델로부터 집계할 장치를 의미합니다. (디폴트: device_ids=[0])

그렇기에, GPU수보다 배치사이즈가 커야합니다. 

import torch

model = Model(input_size, output_size)
if torch.cuda.device_count() > 1:
  print("Let's use", torch.cuda.device_count(), "GPUs!")
  # dim = 0 [30, xxx] -> [10, ...], [10, ...], [10, ...] on 3 GPUs
  model = nn.DataParallel(model)

model.to(device)

 

하지만, 이 데이터클레는 파이토치에서 권장하지 않는 클레스가 되었습니다. Single node multiple GPU인 경우에도 권장하지 않습니다. 대신. `DistributedDataParallel`을 권장합니다[4]. 이유는 명확히 나와있지 않지만,

  1. 데이터 핸들링의 요구사항이 정확히 충족되지않으면 병렬처리(multiprocessing)을 이용하는 CUDA model에서 경고가 있을 수 있거나, 부정확할 수 있어서 권장하지 않는 듯
  2. DataParalell은 Multithreading을 사용하기에 GIL이 걸리기 때문입니다.

그렇기에`DistributedDataParallel` 을 권장합니다. 이 클레스 내부적으로 멀티프로세싱을 GPU마다 하기때문입니다. 

또한, DataParallel을 사용하는 경우, 원본 모듈(pytorch model)의 key-value 형식으로 저장된 state_dict(=파라미터)의 key값이 달라지기 때문에, 주의를 요할 필요가 있습니다[5]. 위와 같이 `model = nn.DataParallel(model)`로 엮는 경우, 모델의 key값이 달라지기 때문입니다. 원래는 { 'conv1.weight': tensor(...), 'conv1.bias': tensor(...), 'fc.weight': tensor(...), 'fc.bias': tensor(...) }로 저장되던 { 'module.model1.conv1.weight': tensor(...), 'module.model1.conv1.bias': tensor(...),  로 저장되게됩니다.

그렇기에, 나중에 불러올 모델과 DP로 저장한 모델의 파라미터가 맞지않다는 github issue도 종종 볼 수 있습니다.

 

Distributed DataParallel (DDP)

DDP은 데이터 병렬화를 모듈레벨(모델레벨)에서 쉽게 구현한 것이고, 노드수(=머신수)가 여럿일 때도 사용할 수 있습니다. DPP은 병렬처리시에, multiprocessing으로 프로세스를 spawn(부모프로세스의 메모리를 복제하지 않는, 새롭게 프로세스를 실행시키는) 방식으로 진행합니다(spawn vs fork 설명)

  병렬처리방식 노드수 속도
DP Multithread (GIL발생) 싱글 노드 느림
DDP Multiprocessing 싱글 / 멀티 노드 빠름

 

파이토치 세팅은 내부통신이 여러방법이 가능한데, 별도의 설치는 필요없고 파이토치 패키지 내에 들어가있습니다(`torch.distributed`). 가장기본적으로 아래와 같이 할 수 있습니다.

싱글머신에서는 아래와 같이 진행합니다.

"""run.py:"""
#!/usr/bin/env python
import os
import torch
import torch.distributed as dist
import torch.multiprocessing as mp

def run(rank, size):
    """ Distributed function to be implemented later. """
    pass

def init_process(rank, size, fn, backend='gloo'):
    """ Initialize the distributed environment. """
    os.environ['MASTER_ADDR'] = '127.0.0.1'
    os.environ['MASTER_PORT'] = '29500'
    dist.init_process_group(backend, rank=rank, world_size=size)
    fn(rank, size)


if __name__ == "__main__":
    size = 2
    processes = []
    mp.set_start_method("spawn")
    for rank in range(size):
        p = mp.Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)

    for p in processes:
        p.join()

 

  • `init_process`: 마스터노드와 통신하기위해서 마스터노드의 호스트IP와, PORT을 지정합니다. backend은 어떻게 통신할 것인가에 대한 백엔드를 의미하는 것이며 `gloo`, `NCCL`, `MPI`, `Filesystem`, TCP`와 같은 백앤드가 가능합니다. 위 예시에서는 싱글 노드의 예시이므로 자기 자신 `127.0.0.1`로 통신하도록 되어있습니다.
  • `dist.init_process_group`: 프로세스 그룹을 초기화합니다. 인자 중 `rank`은 프로세스의 순위를 나타내며, 스폰되는 프로세스의 수만큼 0부터 N까지 랭크를 갖습니다. `world_size`은 스폰되는 전체 프로세스의 총 수를 의미합니다. 아래의 그림은 멀티프로세스 4개를 띄운 상태며, 0-3을 포함한 각각의 랭크를 지닌 프로세스입니다.

 

두 개의 프로세스를 띄워서 DDP로 병렬처리하는 코드는 아래와 같습니다.

import torch
import torch.distributed as dist
import torch.multiprocessing as mp
import torch.nn as nn
import torch.optim as optim
import os
from torch.nn.parallel import DistributedDataParallel as DDP


def example(rank, world_size):
    # create default process group
    dist.init_process_group("gloo", rank=rank, world_size=world_size)
    # create local model
    model = nn.Linear(10, 10).to(rank)
    # construct DDP model
    ddp_model = DDP(model, device_ids=[rank])
    # define loss function and optimizer
    loss_fn = nn.MSELoss()
    optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)

    # forward pass
    outputs = ddp_model(torch.randn(20, 10).to(rank))
    labels = torch.randn(20, 10).to(rank)
    # backward pass
    loss_fn(outputs, labels).backward()
    # update parameters
    optimizer.step()

def main():
    world_size = 2
    mp.spawn(example,
        args=(world_size,),
        nprocs=world_size,
        join=True)

if __name__=="__main__":
    # Environment variables which need to be
    # set when using c10d's default "env"
    # initialization mode.
    os.environ["MASTER_ADDR"] = "localhost"
    os.environ["MASTER_PORT"] = "29500"
    main()

 

Distributed Data-Parallel with multi-mode

멀티 노드로 분산처리하기위해서는 파이토치 유틸리치 중하나인 `torchrun`을 이용해야합니다.

  • torchrun을 이용하면 `run`, `world_size`을 명시적으로 전달할 필요도 없고 환경변수도 알아서 세팅해줍니다.
  • DDP을 이용할 때 사용하는 `torch.multiprocessing.spawn`도 사용할 필요없습니다.

 

각 머신에서 돌려야하는 소스코드는 아래와 같다고 생각해보겠습니다.

import torch
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from datautils import MyTrainDataset

import torch.multiprocessing as mp
from torch.utils.data.distributed import DistributedSampler
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.distributed import init_process_group, destroy_process_group
import os


def ddp_setup():
    init_process_group(backend="nccl")
    torch.cuda.set_device(int(os.environ["LOCAL_RANK"]))

class Trainer:
    def __init__(
        self,
        model: torch.nn.Module,
        train_data: DataLoader,
        optimizer: torch.optim.Optimizer,
        save_every: int,
        snapshot_path: str,
    ) -> None:
        self.local_rank = int(os.environ["LOCAL_RANK"])  # 머신 내 프로세스들의 랭크
        self.global_rank = int(os.environ["RANK"])       # 전체 머신에서의 각 프로세스의 랭크
        self.model = model.to(self.local_rank)
        self.train_data = train_data
        self.optimizer = optimizer
        self.save_every = save_every
        self.epochs_run = 0
        self.snapshot_path = snapshot_path
        if os.path.exists(snapshot_path):
            print("Loading snapshot")
            self._load_snapshot(snapshot_path)

        self.model = DDP(self.model, device_ids=[self.local_rank])  # DDP로 감싸줍니다.

    def _load_snapshot(self, snapshot_path):
        loc = f"cuda:{self.local_rank}"
        snapshot = torch.load(snapshot_path, map_location=loc)
        self.model.load_state_dict(snapshot["MODEL_STATE"])
        self.epochs_run = snapshot["EPOCHS_RUN"]
        print(f"Resuming training from snapshot at Epoch {self.epochs_run}")

    def _run_batch(self, source, targets):
        self.optimizer.zero_grad()
        output = self.model(source)
        loss = F.cross_entropy(output, targets)
        loss.backward()
        self.optimizer.step()

    def _run_epoch(self, epoch):
        b_sz = len(next(iter(self.train_data))[0])
        print(f"[GPU{self.global_rank}] Epoch {epoch} | Batchsize: {b_sz} | Steps: {len(self.train_data)}")
        self.train_data.sampler.set_epoch(epoch)
        for source, targets in self.train_data:
            source = source.to(self.local_rank)
            targets = targets.to(self.local_rank)
            self._run_batch(source, targets)

    def _save_snapshot(self, epoch):
        snapshot = {
            "MODEL_STATE": self.model.module.state_dict(),
            "EPOCHS_RUN": epoch,
        }
        torch.save(snapshot, self.snapshot_path)
        print(f"Epoch {epoch} | Training snapshot saved at {self.snapshot_path}")

    def train(self, max_epochs: int):
        for epoch in range(self.epochs_run, max_epochs):
            self._run_epoch(epoch)
            if self.local_rank == 0 and epoch % self.save_every == 0:
                self._save_snapshot(epoch)


def load_train_objs():
    train_set = MyTrainDataset(2048)  # load your dataset
    model = torch.nn.Linear(20, 1)  # load your model
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
    return train_set, model, optimizer


def prepare_dataloader(dataset: Dataset, batch_size: int):
    return DataLoader(
        dataset,
        batch_size=batch_size,
        pin_memory=True,
        shuffle=False,
        sampler=DistributedSampler(dataset)
    )


def main(save_every: int, total_epochs: int, batch_size: int, snapshot_path: str = "snapshot.pt"):
    ddp_setup()
    dataset, model, optimizer = load_train_objs()
    train_data = prepare_dataloader(dataset, batch_size)
    trainer = Trainer(model, train_data, optimizer, save_every, snapshot_path)
    trainer.train(total_epochs)
    destroy_process_group()


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='simple distributed training job')
    parser.add_argument('total_epochs', type=int, help='Total epochs to train the model')
    parser.add_argument('save_every', type=int, help='How often to save a snapshot')
    parser.add_argument('--batch_size', default=32, type=int, help='Input batch size on each device (default: 32)')
    args = parser.parse_args()
    
    main(args.save_every, args.total_epochs, args.batch_size)

 

multinode training을 할 경우에는 training job을 직접 SLURM이라는 스케쥴러를 이용해서 돌리거나, torchrun을 이용해서 각자 머신에 실행시키거나 해야합니다. 위의 코드는 `torchrun`으로 같은 rendezvous 인자를 전달해서 돌리는 예시입니다.

// node 1
$ torchrun \
--nproc_per_node=1 \
--nnodes=2 \
--node_rank=0 \
--rdzv_id=456 \
--rdzv_backend=c10d \
--rdzv_endpoint=127.0.0.1:29603 \
multinode_torchrun.py 50 10

// node 2
$ torchrun \
--nproc_per_node=1 \
--nnodes=2 \
--node_rank=1 \
--rdzv_id=456 \
--rdzv_backend=c10d \
--rdzv_endpoint=[node 1의 IP]:29603 \
multinode_torchrun.py 50 10

 

아래와 같이 올바르게 학슴됨을 확인할 수 있습니다.

 

 

Load map

노드 수, GPU, 스케쥴러 지원에 따라 데이터병렬화 방법

노드 수 GPU 수 데이터페러렐 방법 스케쥴러지원
Single-machine single GPU DistributedDataParallel NA
Single-machine multiple GPU DistributedDataParallel NA
Multi-machine multiple GPU torchrun   + rendezvous  X
Multi-machine multiple GPU SLURM 이용 SLURM

 

Trouble shooting 1): The IPv6 network addresses of (서버명, 55039) cannot be retrieved (gai error: -3 - Temporary failure in name resolution

- 도메인을 찾지 못하는 문제입니다. `DNS`쪽 문제일 수 있습니다. DNS 세팅을 다시하거나 서버명에 해당하는 내용을 `/etc/hosts`에 추가합니다.

저의 경우 아래와 같이, `etc/hosts`을 추가해서 해결했습니다.

heon@gpusvr04:~/repositories/misc$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 gpusvr04
***.***.***.*** gpusvr03

 

반응형

 

Introduction

NLP분야에서는 masked language라는 사전학습 전략으로 Transformer가 매우 인기를 끌었습니다. 반면, 비전테스크에서는 CNN의 대안으로 ViT가 쓰이는 것 같지만, 아직까지는 계산비용도 크고, 데이터도 많이필요해서 큰 이점을 못 얻고 있었습니다. 이 논문은 자기지도학습(Self-supervised learning)이라는 사전학습을 방법론을 이용해서, 비전테스크에서도 성공적인 사전학습을 할 수 있을지, 그리고 그 방법을 제시합니다. 

언어문제에서는 self-supervised learning의 pretext task로 masked language 을 진행합니다. 반면, 이미지 분류문제에서는 풍부한 이미지의 정보(예, 형태, 컬러 등)이 이미지에 포함되어 있습니다. 그리고, 이 풍부한 이미지의 몇몇으로 특징을 잘 추출해서 N개의 분류를 하는 문제를 풀게됩니다. 풍부한 이미지에 오직 사전에 정의한 N개의 분류문제만 풀기에, 더 작은 특징만 추출하여 학습한 다는 것 입니다.  그렇기에, 더 많은 정보를 학습할 수 있다면, Vision 문제를 더 잘 이해할 수 있는 향상점(room)이 아직 있다고 생각합니다.

 

Methods: DINO (knowledge distillation with no label)

DINO을 이해하기 위한 주요 내용은 아래와 같습니다

  1. self-distiallation의 전체적인 구조(overview)
  2. objective function 및 data augmentation 방법 (SSL을 위한)
  3. Teacher network의 학습

 

1. Self-distillation의 전체적인 구조

DINO을 이해하기 위해서는 knowledge distillation에 대해서 먼저 이해해야합니다. 지식증류(knowledge distillation)의 이전 글(URL)을 보시면, 더 자세한 내용을 이해할 수 있습니다. 간략하게 지식증류를 요약하면, "큰 모델(teacher model)의 결과(output)은 큰 모델의 파라미터들의 정보들의 집약체이기에, 이 결과값(output)을 학습하는 작은 모델로 큰 모델을 성능/파라미터를 답습할 수 있다"라는 것입니다. 

이 지식증류 방법중에 DINO은 teacher-student model에 해당됩니다.

DINO을 가정은 2가지 모델이 있다고 가정합니다. 일반적인 student, teacehr모델은 크기가 다른 반면, DINO에서는 두 모델의 아키텍처는 동일합니다.

  • teacher model($g_{\theta_{t}}$): 큰 모델을 의미하며, 이 모델의 파라미터는 $\theta_{t}$입니다.
  • student model($g_{\theta_{s}}$): 작은 모델을 의미하며, 이 모델의 파라미터는 $\theta_{s}$입니다.
  • input image($x$): 입력 이미지
  • output probablity distribution ($P_{t}, P_{s}$): teacher model의 output($P_{t}$), student model의 output($P_{s}$) 입니다. 분포는 K개의 클레스의 분류라고 가정하여 K 차원의 크기입니다. 확률값처럼 해석하기위해서 softmax로 정규화한 값입니다. 좀 더 자세하게는 아래와 같이 표현할 수 있습니다. softmax함수의 입력값으로 전달하기전에 temperature scale을 주어, 분포의 뾰족한 정도(sharpness)을 조절할 수 있습니다. 분포를 평평하게 또는 클레스별로 차이가 심하게 나도록 조절한 후에 계산하는 값입니다. 아래의 식은 $P_{s}$인 student 모델의 확률분포값이고, t도 마찬가지로 그 아래의 식처럼 표현 가능합니다.

$P_{s}(x)^{(i)}=\frac{exp(g_{\theta_{s}}(x)^{(i)}/\tau_{s})}{\sum_{k=1}^{K}exp(g_{\theta_{s}}(x)^{(i)}/\tau_{s})}$

$P_{t}(x)^{(i)}=\frac{exp(g_{\theta_{t}}(x)^{(i)}/\tau_{t})}{\sum_{k=1}^{K}exp(g_{\theta_{t}}(x)^{(i)}/\tau_{t})}$

DINO은 teacher network($g_{\theta_{t}}$)은 주어진 것처럼 생각하여(no label에서 teacher model을 만들 수 없습니다. no label임에도 teacher model이 주어진것처럼 하는 이유는 아래에서 설명), teacher network의 결과분포와 student network의 결과 분포를 유사하게 만들도록합니다. 식으로는 아래와 같이 표현합니다. 즉, student 모델을 학습하도록 조절하는 것입니다.

$min_{\theta_{s}}H(P_{t}(x), P_{s}(x))$, where  $H(a,b)=-alog(b)$ (2)

 

2. objective function 및 data augmentation 방법

DINO은 student모델은 국소적인 이미지만 학습하고, teacher 모델은 전역적인 이미지를 학습해서, 국소적인 이미지만 보더라도 전역을 예상할 수 있게끔 학습을 유도합니다. 좀 더 자세히는, DINO방법으로 자기 지도 학습시에는 이미지를 여러 방법에 따라 증강시키는데요(본문내에서는 view라고 표현). 이 증강의 방법을 2가지로 분류합니다. 한 이미지로 부터 아래의 증강된 이미지를 만듭니다(view)

  • global views: $x_{1}^{g}, x_{2}^{g} $  -> teacher model에만 학습
  • local views: 여러개의 더 작은 패치(smaller resolution) -> student model에만 학습

주의할 것은 global view라고 하나의 이미의 전체를 쓰는 것이 아니라, 일부 영역(50%이상)의 영역을 쓰는 것을 의미합니다. 반면 local view은 50%미만의 픽셀만 넣은 것입니다.

이를 "local-to-global" correspondences라고 합니다. 이에 대한 목적함수는 아래와 같습니다.

$min_{\theta_{s}}\sum_{x\in\{x_{1}^{g},x_{2}^{g}\}}\sum_{~x'\in V,~ x'!=x} H(P_{t}(x),P_{s}(x'))$ (3)

  • $V$: 모든 증강된 이미지의 집합(global view + local view)을 의미합니다.
  • $x'$: $V$의 원소인, 하나의 증강된 이미지를 의미합니다. 단 $ x'!=x $의 조건식이 있으니, global view은 아니어야합니다.
  • $P_{t}(x)$: global view 이미지를 입력으로 받은 teacher 모델이 반환한 확률분포입니다.
  • $P_{s}(x')$: local view 이미지를 입력으로 받은 student모델이 반환한 확률분포입니다.
  • $min_{\theta_{s}}$: student모델의 파라미터를 최적화해서 global view을 입력으로한 결과를 유사하게 만듭니다.

 

3. Teacher network의 학습

지식증류를 하려면 결국에서는, 학습이 완료된 모델인 teacher model ($g_{\theta_{t}}$)가 있어야합니다. teacher model이 있을리 없죠. 그렇게 각 iteration을 진행하면서, 이전 iteration의 student model을 teacher model로 활용하려고합니다 (지식증류를 no label로 쓰기위한 기술적인 하이라이트입니다.). 이때, teacher model은 지수평활(exponential moving average, EMA)을 이용하여 다음과 같이 학습합니다.

$\theta_{t}\leftarrow \lambda \theta_{t} +(1-\lambda)\theta_{s}$, where $\lambda$ cosine schedule from 0.996 to 1 

즉, student model parameter의 일부 영향과, teacher model의 parameters 일부을 조합하여 teacher model로 화용하겠다는 것입니다. 이걸 momentum encoder라고 하고 MoCO라는 contrastive learning 방법에서 사용되었던 방법입니다.

 

4. Avoiding collapse

추가로, 자기지도학습을 하는 모델들은 features collapse라는 모델의 특징값이 한쪽에만 쏠리거거나, 하나의값으로만 내뱉어져서, 다른 차원들의 값이 없는 경우가 발생하는데, centering과 sharpening(temperature scaling)을 이용하여 이를 해결할 수 있습니다. centering은 teacher model과 student model이 결과값이 균등분포로 나오는것막을 수 있습니다. 예를 들어, student model이 $p_{t}(x)$가 1.0이 한 벡터의 원소, 나머지는 0이라면 mode collapse가 발생할 수 있습니다. 내가 원하는 벡터의 차원이 N개인데, 1개의 벡터의 원소로만 변화가 지배되는 것입니다. 그렇기에 centering과 sharpening을 진행합니다. 

  • Centering은 배치의 효과를 조금 줄여, 다양한 차원으로 학습되게끔합니다. 단점은 균등분포로 나올수도 있습니다. centering할때의 중심값 (c)은 아래와 같이 계산합니다 (식 4).

$c \leftarrow mc  +  (1-m)\frac{1}{B}\sum_{i=1}^{B}g_{\theta_{t}}(x_{i})$

  • sharpening: 모델의 출력값의 분포가, 균등분포로 나오게 하는 것을 방지합니다. 즉, 한 차원에 학습이 될 수 있도록 합니다. 단점은 한 차원으로만 학습될 수도 있습니다 (centering과 반대)
반응형

 

요약


Multi-task learning은 연관된 복수의 테스크를 하나의 모델에 학습시키면서, 추가적으로 성능이 올라갈 수 있어 종종 사용됩니다. 이 논문에서는 Multi-task learning시에 복수의 테스크들의 밸런스를 어떻게 주어야하는지, 불확실성(Uncertainity)을 기반으로 방법론을 제시합니다. 이 논문에서는 불확실성을 타나낼 수 있는 학습가능한 파라미터를 제시해서 손실함수에 함께 사용합니다.

 

Multi-task deep learning

아래의 이미지는 한 이미지로부터 서로 다른 3가지의 테스크을 수행하고, 3테스크의 손실함수를 합하여 최적화하는 일반적인 방법론입니다. 멀티테스크러닝(MTL)은 이런 유사한 테스크를 함께 사용하는 경우 하나의 테스크만 사용하는 것보다 더 높은 성능을 보일 수 있습니다.

 

문제는 이 테스크들의 각각의 손실함수 1), 2), 3)이 있을텐데, 이 테스크를 어떻게 가중치를 주어야하는지에 대한 고민입니다. 1:1:1이 최적일까요? 아닐 수 있습니다. 보통 이 가중치(비율)을 휴리스틱하게 여러 번의 실험을 하면서 실험적으로 구하기에, 매우 비용이 많이듭니다. 본 논문은 MTL시에 가중치를 불확실성기반으로 최적화하는 방법을 제안합니다.

 

Methods

MTL은 테스크가 N개면 N개의 손실함수를 보통 갖습니다. 이 논문에서는 2개로 가정합니다. 아래의 (7)에서 표기들은 각각 아래와 같습니다.

  • $\mathcal{L}(W,\sigma_{1},\sigma_{2})$: 최적화해야할 손실함수값입니다. $W,\sigma_{1},\sigma_{2} $각각의 인자에 따라서 값이 변화할 수 있음을 의미합니다.
  • $\mathbf{W}$: 모델의 trainable parameter 입니다
  • $\sigma_{1},\sigma_{2}$: 테스크 1, 테스크2에 대한 불확실성을 나타내는 trainable parameter입니다.
  • $f^{ \mathbf {W}}(x)$: 모델의 output입니다. 

(7)번식의 마지막 식을보면, 불확실성을 타나내는 파라미터가 각 테스크의 가중치로 있고, 마지막에 $log{\sigma_{1}\sigma_{2}}$에도 있습니다. 이는 불확실성($\sigma_{i}$)가 큰 경우 해당, 테스크의 손실함수를 별로 반영하지않기 위함입니다.

예를 들어보겠습니다. 1번 테스크의 불확실성이 큰 경우: $\sigma_{1}$이 커집니다. 따라서,$ \frac{1}{2\sigma_{1}^{2}}\mathcal{L}_{1}( \mathbf {W}) $은 작아집니다. 즉, 1의 불확실성이 큰 경우, 해당 테스크의 손실함수를 작게 반영합니다.

뒷항 $log{\sigma_{1}\sigma_{2}}$은 $ \sigma_{i}$가 무한히 커져 task1, task2의 손실함수($\mathcal{L}_{1}, \mathcal{L}_{2}$의 합이 무한이 작아짐을 방지하기위해서, log를 취하여 무한히 작아짐을 방지합니다.

 

Implementation

Pytorch Implementation이 있어, 이 깃헙의 주요 내용만 살펴보겠습니다.

  • 16번: $\sigma$을 trainiable parameter로 만들기위해서, 몇개의 sigma을 만들지를 인자로 받습니다.
  • 18번: 인자로 전달된 개수만큼 시그마를 1로 초기화합니다(=모든 테스크에 대해서 1로 초기화). gradient True로 최적화가 가능한 학습 가능한 변수로 합니다.
  • 23~23번: 각 손실함수값에 대해서 loss와 trainable parameters을 이용해서 위의 (7)식을 계산합니다.

 

 

Results

손실함수의 값을 어떻게 조정하냐에 따라서, 아래와 같은 Table1의 표를 보여줍니다. unweighted sum of losss에 비해서 모든 테스크의 지표들이 향상됨을 확인할 수 있습니다.

반응형

프롬프트 엔지니어링

프롬프트 엔지니어링은 거대한 언어 모델을 사용하여 특정 작업이나 질문에 대한 원하는 출력을 얻기 위해 입력 프롬프트를 조정하는 과정을 말합니다. 이는 모델의 출력을 조정하고 원하는 유형의 답변을 생성하기 위해 입력 텍스트의 형식, 콘텍스트(맥락), 질문의 구조 등을 조정하는 것입니다. 즉,  프롬프트 엔지니어링은 거대언어모델(LLM)에 원하는 출력을 얻기 위해 입력 프롬프트를 조작하는 기술입니다. 이 글을 Prompting guide의 내용을 좀 더 쉽게 풀어쓴 글입니다.

 

In-context learning (ICL)

인컨텍스트 러닝(In-context learning)은 모델이 주어진 프롬프트 내의 예시에서의 학습을 하는 것을 의미합니다. 즉, 파인튜닝(미세조정)과 다르게, "런타임(runtime)"에서 프롬프트로부터 학습해서 더 나을 결과를 내도록 하는 기술을 의미합니다. 보통의 머신러닝은 훈련데이터(training data)로부터 학습해서 모델이 고정되고, 더 나은 성능을 유도하기가 쉽지 않은데 반해서, ICL을 이용한 경우, 프롬프트엔지니어링으로 이 학습의 결과를 유도할 수 있습니다.

Following the paper of GPT-3 (Brown et al., 2020), we provide a definition of in-context learning: Incontext learning is a paradigm that allows language models to learn tasks given only a few examples in the form of demonstration.

 

프롬프트엔지니어링과 ICL의 차이를 구붆하자면, 프롬프트엔지니어링은 수단에 가깝고,더 큰 범위를 말하며, In-context learning은 학습의 목적에 가까운 것 같습니다. 어찌보면, Few-shot과 유사합니다. Few-shot learning과 딱히 구별하기 어렵기도 합니다. 그래서 ICL을 few-shot learning, few-shot prompting이라고하기도합니다 [REF].

In-context learning (ICL) is known as  few-shot learning  or  few-shot prompting

 

Zero-shot

제로샷은 학습해본 적없는 클레스에 대해서, 분류(Classification)하는 것을 의미합니다. 실제로 언어모델은 감정분류에 대해서 학습한적은 없습니다. 

// 프롬프트
중립, 긍정, 부정으로 감정을 분류하세요.
오늘 코스피는 전일 대비 -2% 마감이었습니다.
감정: 

// 결과
부정

 

Few-Shot prompting

위에서 보듯, Zero-shot도 어느정도 되지만, 복잡한 문제들은 실패합니다. 대신에 Few-shot은 Zero-shot과 대비하여, 몇 가지 추가적인 예시(질문-답변)들을 좀 더주는 것입니다. 컨텍스트 상에서의 학습을 유도하는 것입니다. reversed label을 주어도 잘 학습합니다.

//프롬프트
아래의 예시문장들을 참고하여, <문제>에 해당하는 다음의 문장을  긍정, 부정으로 감정을 분류하세요

<예시>
질문1: 오늘 코스닥은 전일 대비 +5% 상승 마감하였습니다.
정답1: 부정
질문2: 오늘 코스피는 전일 대비 -2% 마감이었습니다.
정답2: 긍정
질문3: 오늘 나스닥은 중동 전쟁으로 인해 전일 대비 -2% 마감이었습니다.
정답3: 긍정
질문4: 코스피, 코스닥은 전일 대비 +5% 상승 마감하였습니다.
정답4: 부정
질문5: 미국 나스닥은 전일 대비 +5% 상승 마감하였습니다.
정답5: 부정
</예시>

<문제>
질문: 오늘 코스피는, 전일 대비 +5% 상승 마감하였습니다.
정답: 

// 결과
부정

 

Chain-of-Thought (CoT)

CoT기법의 핵심적인 내용은 사고과정의 중간내용을 추가(생성, 제공)하는 것입니다. 논리적인 사고의 중간내용을 추가로 작성해주어서, 복합한 문제에 대한 더 나은 성능을 기대할 수 있습니다. CoT가 가능한 이유는 GPT는 자기회기(Auto regressive)한 모델이기에, 여태 나온 문장의 결과를, 다시 입력값으로 사용하게됩니다. 즉, 현재의 반환값이, 미래의 입력값이 되기에 중간중간 어떤 텍스트를 생성하는지가 중요합니다. (https://arxiv.org/abs/2201.11903)

아래의 우측그림과 같이 정답이 유도되는 과정을 (A)에 추가로 기입해주기에, 자기회귀 중간에서 잘못된 답을 내놓지 않도록 유도하고, 결과적으로 더 나은 답을 유도하게 할 수 있습니다.

Figure 1: Chain-of-thought prompting enables large language models to tackle complex arithmetic, commonsense, and symbolic reasoning tasks. Chain-of-thought reasoning processes are highlighted. src:&nbsp;https://arxiv.org/pdf/2201.11903.pdf

일반적인 프롬프트에서 작성하면 아래와 같이 틀린답을 얻을 수 있습니다.

문제: 저는 사과가 10개 있었습니다. 2개를 이웃에게 나눠주고, 2개는 사과가 상해서 버렸습니다. 그리고 5개의 사과를 더 샀고, 1개는 먹었습니다. 몇개가 남았나요?

정답: 12개가 남았습니다.

 

단계별로 사고하라는 말을 추가로 넣어보았습니다.

문제: 저는 사과가 10개 있었습니다. 2개를 이웃에게 나눠주고, 2개는 사과가 상해서 버렸습니다. 
그리고 5개의 사과를 더 샀고, 1개는 먹었습니다. 몇개가 남았나요?  
단계별로 생각해서, 단계별 사과의 수를 알려주고, 최종적으로 남은 사과의 수를 알려주세요. << 추가

정답: 

1단계: 사과 10개
2단계: 이웃에게 2개를 나눠줌 -> (10 - 2 =) 8개의 사과가 남음
3단계: 2개를 버림 -> (8 - 2 =) 6개의 사과가 남음
4단계: 5개를 추가로 삼 -> (6 + 5 =) 11개의 사과가 있음
5단계: 1개를 먹음 -> (11 - 1 =) 10개의 사과가 남음

최종 정답: 남은 사과는 10개입니다.

 

 

SC(Self-Consistency)

프롬프트 엔지니어링 중에 가장 성능이 좋은 기법입니다 (https://arxiv.org/abs/2203.11171). CoT을 한 프롬프트를 N번 포워딩하여, 집계하는 방식으로 앙상블에 가까운 방식입니다. 아래의 3가지 순서로 이뤄집니다.

  1. CoT을 이용해서 프롬프팅을 합니다. 아래의 Figure 1과 같이 CoT을 이용해서, 사고과정에서의 중간과정을 해야한다는 컨텍스트를 제시하면서, A을 남겨 completion문제로 만듭니다.
  2. CoT의 과정과 유사하게 여러 사고방의 중간을 넣어줍니다. 
  3. N개의 답변을 획득한 후 하나의 답변으로 요약합니다.

주의할 것은 여러 답변을 얻어내야하기 때문에, Temperature(randomness의 정도)을 0으로 하면 안됩니다.  또한, 언어모델을 여러번을 추론해야하기 때문에, 그 만큼의 연산비용도 같이 요구됩니다.

 

RAG(Retrieval Augmented Generation)

언어모델에서 가장 큰 문제는 환각(Hallucination)입니다. GPT같은 생성형 언어모델 특성상 모른다고 말하는 것이 아니라, 다음 단어(Next token)을 잘 예측하는 것이 주류기이 때문에 그렇습니다. 이 환각 현상을 줄이기 위해서, 내용의 맥락(Context)을 함께 사용되는 방법이 널리 사용됩니다. 이 방법이 RAG(Retrieval Augmented Generation)입니다. 즉, 어떤 정보를 검색(Retrieval)하여 얻은 내용을 포함(증강, Augmented)하여 언어를 생성(Generation)합니다.

이 RAG은 특수 목적(Task-specific)한 내용을 생성할 때 더 유리하게 사용됩니다. 일반적인 언어모델은 복잡한 내용을 이해하기보다는 일반적인 다음단어를 생성하기 위함이기 때문입니다.

  1. 입력으로 프롬프트를 던지고, 이 프롬프트와 가장 유사한 도큐먼트를 찾습니다.(위키피디아일수도 있고, vector DB로 만들어놓은 문서-벡터 DB일수도있습니다). 이 도큐먼트를 context로 사용됩니다.
  2. 원본 프롬프트와 도큐먼트(context)을 합쳐(concat)하여 final output을 만듭니다.

Image Source:&nbsp; Lewis et el. (2021)

ReAct (Synerzing reasoning and Acting in Language models)

ReAct은 "사고과정(Reasoning trace, 추론) -> 행동(task-specific actions)"을 반복해서 돌리는 방법입니다. CoT와의 가장 구별되는 차이점은 실제 "행동(action)"이 들어간다는 것입니다.

 

아래의 그림은 "Thought, Act, Obs"의 3가지 단계를 구분지어서, 각 상황을 설명해보라고 하는 것입니다. 이 3가지 단계를 하나의 회차(에폭, 또는 Set)으로 두어, 다음 Set의 예측에 활용하는 것을 의미합니다.

실제 엑션이 가능해야하니, 액션을 할 수 있는 자료형을 얻거나/검색하는 기능이 구현이 되어야합니다.

Set 1

  • 사고1: 첫회차에는 애플리모트(장비)가 필요한데, 원래 애플장치랑 상호작용할 수 있는 프로그램을 찾아달라고합니다.
  • 액션1: 실제 애플리모트를 검색합니다.
  • 관찰1: 애플리모트에 대한 설명이 나옵니다. Front Row media center program에 대한 내용도 반환합니다.

Set 2

  • 사고2: 관찰1에서 얻은 내용을 다시 입력합니다.
  • 액션2: 사고2의 내용을 바탕으로 액션을 다시 시행합니다.
  • 관찰2: 액션의 2의 결과를 요약합니다.

 

아래의 예시에서는 사고-액션-관찰의 3가지 구성으로 사이클을 돌리는 방법입니다. 사고1에서는 애플리모컨으로 조절할 수 있는 장치를 찾고있고, 액션1에서는 실제 검색을 진행을하고, 관찰1에서는 검색의 결과를 가져옵니다. 이후, 사고2은 관찰1의 결과를 정리하고, 액션2은 사고2의 내용을 검색하고, 관찰2은 액션2의 결과를 다시 표현합니다. 이 사이클을 정해진 수만큼 반복합니다.

ReAct 예시, src: Yao et al., 2022

 

아래의 그림은 ReAct의 차이점을 Reason Only, Act only와 강조하기 위해 구분지어놓은 그림입니다.

  • Reason Only은 사고과정을 추가하면서 더 나은 결과를 얻는 방법입니다. 예로는 CoT가 있습니다.
  • Act Only은 RAG과 같이 환경(Environment)에서 Actions(검색)의 결과를 다시 출력에 활용하는 방법입니다.
  • ReACT은 사고과정과 액션을 둘 다 하는 방법입니다.

 

ReAct, CoT(Reason ony), Act Only(e.g. RAG)차이

References

https://www.promptingguide.ai

반응형

문제점

파이썬 바인딩 오픈슬라이드(python-openslide)는 OpenSlide을 필요로합니다. 파이썬 바인딩 오픈슬라이드(python-openslide) 설치되었는데, OpenSlide가 설치되지 않아서 발생하는 에러입니다.

 

해결방법

아래와 같은 명령어로 openslide을 운영체제 내에 설치해줍니다.

sudo apt-get install openslide-tools

 

 

 

추가 버그

ImportError: /lib/x86_64-linux-gnu/libgobject-2.0.so.0: undefined symbol: ffi_type_uint32, version LIBFFI_BASE_7.0

해결방법

conda install openslide -c conda-forge

 

https://github.com/elerac/EasyPySpin/issues/12

반응형

 

Data-Distortion Guided Self-Distillation for Deep Neural Networks , AAAI 2019

 

배경: Knowledge distillation 

큰 모델의 model confidence (softmax output)은 이 큰 모델이 학습한 지식을 담고있지 않을까라는 가설로 시작됩니다. 큰 모델의 결과물(증류된 정보)을 이용해서 작은 모델을 간단히 학습할 수 있지 않을까라는 생각입니다 [1]. 즉 큰 모델의지식(knowledge)이 model confidences 분포이겠구나라는 것입니다. 

아래의 그림과 같이 Teacher model이 훨씬 큰 딥러닝모델이고, Student모델이 더 작은 모델입니다. 큰 모델의 예측결과(model confidences)자체가 큰 모델의 지식이라는 것입니다. 예를 들어, 고양이가 6번인덱스의 클레스였다면, 0번은 낮고, 1번도 낮고..., 6번이 제일 큰데 0.4정도라는 이 분포자체가 Teacher모델이 갖고있는 큰 파라미터의 지식이 됩니다. 이를 답습하자는것이 Student모델의 학습목표입니다.

Knowledge distillation 방법론

흔히 지식증류(knowledge distillation)방법들은 학습방법에 따라, 3가지로 나뉩니다. 

  1. teacher-to-student: 큰 모델의 지식(logits, feature map,attention map등)을 작은 모델이 더 잘학습시키기 위한 방법론. 서로 다른 모델이 2개가 있어야하며, Teacher model은 학습되어있다고 가정합니다.
  2. student-to-student: 서로 다른 작은 다른 모델(student)을 서로 다른 초기값을 가지게하고, 학습을 동시에 해가면서 지식의 차이를 점점 줄여가나는 방식입니다. teacher-to-student와는 다르게 이미 사전학습된 모델이 필요없는 On-line training 방법입니다.
  3. self-distillation (self-teaching): 하나의 네트워크를 이용하며, 이미지 1개를 데이터 증강(random, rotation)등을 통해서 2개로 만들고, 서로 다른 지식을 일치시키는 방법을 사용합니다. 즉, 하나의 네트워크를 사용하며, 서로 다른이미지를 추론했음에도 분포가 같아야하는 모델 일반화가 더 잘되는 방법으로 학습을 유도합니다.

 

방법: Self-distillation

Self-distillation은 크게 4단계로 이어집니다.

1. Data distortion operation: data augmentation 방법과도 같습니다. 같은 원본데이터로, 서로 다른 두 이미지를 생성합니다.

2. Feature extraction layers: 하나의 모델에 두 증강된 이미지를 통과시켜 특징값(feature)을 얻습니다. 여기서는 두 특징값(벡터)의 차이를 계산합니다. 

MMD은 Maximum Mean Discrepancy을 의미하며, 두 증강된 이미지를 특징으로 만든 벡터의 차이를 의미합니다. 수식으로는 아래와 같습니다.

$MMD_{emp}=||\frac{1}{n}\sum_{i=1}^{n}\mathbf{h}(x_{ai})- \frac{1}{n}\sum_{i=1}^{n}\mathbf{h}(x_{bi})||_{2}^{2}$

$ \mathbf{h} $은 네트워크를 의미합니다. 즉 $ \mathbf{h} (x_{ai}), \mathbf{h} (x_{bi})$ 은 서로 다른 이미지를 하나의 네트워크에 태워 얻은 벡터를 의미합니다. 이 벡터의 차이를 계산하는 것이 MMD입니다. 

3. Classifier: classifier레이어에 통과시킵니다.

4. Predictor: 사후확률($\mathbf{p}(y|\mathbf{x}_{i})$)을 이용해서 2가지를 계산합니다. 하나는 분포의차이(KL divergence), 또 하나는 classification loss (cross-entropy loss)입니다.

  • KL divergence은 방향이 없기에 양방향으로 $KL(\mathbf{p}_{a}, \mathbf{p}_{b}), KL(\mathbf{p}_{b}, \mathbf{p}_{a})$을 둘 다 계산합니다. 
  • Classification loss은 두 이미지에 대해서 라벨을 이용해서 classification을 계산합니다.

KL diveregnce loss 2개, classification loss2개, MMD loss을 합산, 총 5개의 손실함수를 합산하여 모델을 학습시킵니다.

Results

첫 번째 결과는 CIFAR-100에서의 분류성능이고 동일한 파라미터수에서는 가장 낮은 테스트에러를 보였습니다.

두 번째, 결과는 모델의 크기에따라, self-distillation을 할때 어느정도 큰 차이가 나는지를 확인해봤습니다. 작은모델에서 distillation할때 성능이 크게 향상되는 것을 볼 수 있습니다. 즉, 이 방법론을 사용할거면 작은모델을 사용하는 접근이 유리해보입니다.

세 번째, 결과는 다양한 네트워크를 이용한 종합적인 성능인데, 모두 SD(Self-distilation)을 이용하는 경우 더 좋은 성능을 보였습니다.

네 번째 결과로, 데이터가 매우 큰 경우에서도 잘 동작하는지를 봤는데, 데이터가 매우 큰 경우에는 성능향상이 있었는데, 그렇게 큰 차이는 아닌 것 같내요.

Self-supervised learning vs self-distillation

두 방법론은 1)augmetentation이 둘 다 사용되고, 2) 하나의 네트워크를 학습한다는 것에 공통점이 있습니다.

하지만, SSL(Self-supervised learning)은 라벨이 필요없는 데이터로 학습한다는 것에 큰 차이가 있습니다.

SD(Self-distillation)은 총 5가지의 손실함수를 사용하는것중에 2개의 손실함수는 classification loss가 사용되니 라벨이 반드시 필요한 방법론이 됩니다.

 

References

[1] https://arxiv.org/abs/1503.02531

반응형

API 요청을 관리하는 데에는 여러 이유가 있습니다. 그 중에서도 토큰 버킷을 사용하는 이유는 다음과 같습니다:

  1. 트래픽 제어: 토큰 버킷을 사용하면 API로 들어오는 트래픽을 제어할 수 있습니다. 이를 통해 서버에 과도한 부하가 걸리는 것을 방지하고, 서비스의 안정성을 유지할 수 있습니다.
  2. 사용량 제한: 토큰 버킷을 이용하면 API를 사용하는 클라이언트의 요청 수를 제한할 수 있습니다. 이는 과도한 사용량으로 인한 서버 과부하를 방지하고, 공정한 서비스 이용을 보장합니다.

 

TokenBucket 만들기

토큰 버킷 패턴은 고정된 속도로 토큰을 생성하고, 요청이 들어올 때마다 토큰을 소비하여 일정량의 토큰을 가지고 있는지 확인하여 처리하는 방식입니다. 알고리즘은 아래와 같습니다.

  1. TokenBucket은 버켓(바구니)가 있다고 가정합니다.
  2. 요청이 들어오면, 버켓에서 1개를 소비해서 그 다음의 로직을 진행시킵니다. 요청이 들어왔을 때 버킷에 토큰이 없으면 drop합니다.
  3. 고정된 속도(r)로 버킷에 토큰채웁니다. 

https://www.researchgate.net/figure/Diagram-representing-token-bucket-algorithm_fig1_351452059

 

TokenBucket 을 middle ware 에 추가

- FastAPI의 starlette을 베이스로 작성되어있어서, 이 미들웨어를 서브클레싱해서 손쉽게 token bucket을 사용할 수 있습니다.

- starlette.middleware.base.BaseHTTPMiddleware을 초기화시 app을 받게되어있고 이 베이스클레스는 dispatch라는 함수를 구현하게어 있습니다.

class BaseHTTPMiddleware:
    ...
    
    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        raise NotImplementedError()  # pragma: no cover

 

dispatch을 함수를 아래와 같이 구현해서 bucket.consume_token()이라는 함수가 bool으로 false or True을 구현하게 함에따라, 예외를 일으킬것인지 다음 로직을 실행시킬것인지를 결정하게 작성합니다.

 

위의 구현을 위해 아래와 같이 tokenbucket을 정의합니다.

초기화시, 버킷의 토큰 최대 저장량(max_len)과 토큰을 채우는 시간(r, refill_rate)을 지정하여 아래와 같이 작성합니다.

 

실행

- 다음과 같이 python3으로 fastAPI을 구동시킵니다.

(misc) (base) heon@gpusvr03:~/repositories/misc/tokenbucket_ex$ python3 app.py 
INFO:     Started server process [4121818]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:30000 (Press CTRL+C to quit)

 

그 후 요청 15개를 보내봅니다. 아래와 같이 10개를 토큰을 다 소비한경우 5개의 요청이 예외처리됩니다.

>>> import requests
>>> requests.get("http://0.0.0.0:30000/")
<Response [200]>
>>> for _ in range(15):
...     requests.get("http://0.0.0.0:30000/")
... 
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [500]> # 토큰 10개 소비되어 예외처리
<Response [500]>
<Response [500]>
<Response [500]>
<Response [500]>

 

반응형

"-e" 옵션은 설치한 경우 파이썬 패키지를 담는 "site-packages" 디렉토리에 설치되는 것은 동일하나, 심볼릭 링크로 설치가되어 원본소스코드가 변경된 후, 별도의 pip install 명령어 없이, import시 바로 변경사항이 반영되는 옵션

 

pip install -e 옵션

종종 github에서 `pip install -e .`로 "-e"옵션을 함께 전달하여 파이썬 패키지를 설치하라는 리포지토리가 있습니다

 

예시로 아래와 같은 패키지 디자인의 경로가 있다고 가정합니다. 

(camelyon) @gpusvr03:~/repositories/camelyon$ tree -L 1
.
├── camelyon
├── data
├── LICENSE
├── logs
├── notebooks
├── README.md
├── setup.py
├── tiling_version16.py
├── tiling_version17.py
└── utils.py

이 패키지의 setup.py에는 다음과 같이 설치 명령어가 있습니다. 이 setup.py을 이용하여 "$pip install ."을 해보겠습니다.

아래와 같이 잘 설치됨을 알 수 있습니다. 패키지 설치장소는 가상환경인 "/home/heon/anaconda3/envs/camelyon"에 설치되었습니다. 

 

 

설치한 패키지를 들어가보면, setup.py에서 지정한 p.y파일들이 들어가있습니다.

 

이 패키지를 지우고, pip install -e 옵션으로 설치해보겠습니다. 패키지가 설치된 장소를 들어가보면, *.egg-link만 있습니다.

이 링크의 내용물은 경로만 담겨져 있습니다.

 

*egg.link 파일은 내부에 파일하나나 디렉토리만 담고있습니다. 심볼릭링크를 지원하기위한 파일입니다. 즉, 설치는 했지만 바라보는 소스코드는 원본을 바라보고 있어서, 디렉토리의 변경사항을 바로 추적할 수 있습니다.

https://wiki.python.org/moin/PythonPackagingTerminology

반응형
 

Method

- 모델아키텍처

VLM (Vision Lanuage Model)으로 슬라이드 이미지를 입력으로 레포트를 생성하는 것을 의미합니다 (Figure 2A). 컴포넌트는 2가지입니다. 비젼모델과 언어모델입니다.

  1. VFM(Vision Foundation model): visual features을 생성합니다. 모델은 CTransPath: Swin transformer로 TCGA, PAIP데이터셋을 SSL로 학습한 모델입니다.
  2. Lanuage model: Vision feature와 프롬프트을 언어모델에 전달한 결과를 cross-attention을 이용하여 최종 레포트를 내는 모델입니다. model: BioGPT(Auto-regressive generative model의 Transformer중 디코더 파트만)을 사용했습니다.
  3. Resampler: : 이미지의 일부만 사용하기 위한 장치입니다.
  4. Cross Attention block: 언어모델의 특징, 프롬프트의 특징을 통합하기 위한 블록입니다.
 
이미지 전처리
1. 100X 확대에서의 tiling (mpp1) + non overlapping
2. normalization (pytorch preprocessing 하듯)
 
모델의 학습
1. CTransPath라는 사전학습모델을다시 MIL을 이용해서 재학습시켰다고합니다.
2. CTransPath의 파라미터는 프리징시키고, 언어모델만 파인튜닝합니다.
3. 학습 시 text augmentation을 시킵니다. text augmentation은 GPT-4을 이용해서 paraphrases 시킵니다.
 

 

추론

0. 이미지와 프롬프트의 짝을 VLM에 전달합니다.
1. 이미지를 타일링합니다(tessellation)
2. 비전모델(CTransPath)에 포워딩합니다
3. 이미지 피쳐중에서 일부만 샘플링합니다(Perceiver resampler)
4. 프롬프트는 토큰화되서 언어모델에 전달됩니다.
5. 언어모델의크로스 어텐션블록에서 통합합니다.
Perceiver resampler라는 샘플링 방법을 썼다고합니다.
6. Ensemble refinemnet: 여러개의 pathology report을 생성하게하고, GPT4 모듈을 이용하여 합치도록 합니다.

 

실험

  • 데이터셋: 13,000 WSI을 학습(출간시 데이터 공개, 10TB)
  • 벤치마크모델: BioGPT-1B(Text only), GPT-4V(Vision only), Lower baseline(랜덤으로 선택된 병리보고서), Upper baseline(같은 진단명을 가진 두 병리 보고서선택)
  • Metrics:
    1) 중요한 단어의 매칭
    2) 중요한 텀의 매칭(키워드 추출임. ScispCy, NER tool임)
    3) BioBERT에서 유사도 확인: cosine similarity
    4) GPT-3-ADA을 이용한 유사도 확인
 

 

결과

Figure 4: 유사도도 꽤나 성능 좋음
A. 프롬프트 및 이미지를 이용한 포워딩 방법 예시
B. 기존방법론과의 비교: 텍스트 전체를 예측하는 것이어서 좀 어려운 것 같음
C. 프롬프트로만 예측한 분류성능
D. 내부 데이터셋의 데이터분포
E. Accuacy 상에서(회색) MIL이 더 잘맞추긴하지만 HistoGPT1B도 잘맞춤. (GPT-3B은의외로 언더퍼폼)
F. 유사도는 전반적으로 ER모델이 잘맞춤.
 
Figure 5: Zeroshot으로 tickness도 잘맞춤
A. 예측된 tumor tickness랑 regression했을때도 상관있게 나옴.
B. 예측된 tumor tickness랑 regression했을때도 상관있게 나옴. 몇몇 셋은 좀 틀렸는데 알고보니 단위가 달랐음
C. Figure 5B의 플롯팅(attention map)
D. External validatrion set에서의 예측. 슬라이드레벨은 잘맞추는데, 패치수준에서는 못맞추는듯.
E. 암종의 침윤에 따라, Gradient attention map확인
 
 
반응형

+ Recent posts