Method: ROAM은 4단계로 구성: 1) ROI추출 -> 2) Self-attetion을 이용한 인스턴스 표현 3) attention weigthed aggregation, 4) Multi-level supervision의 4단계


1. ROI 추출 및 특징화: WSI로부터 2,048 x 2,048 (0.5mpp, x20)으로 ROI을 추출합니다.

  •  추출과정에서는 Overlapping 없이 진행합니다.
  • 2048x2048 ROI에서 2배, 4배의 다운샘플링을 진행합니다 (1024x1024, 512x512)가 생성. 논문에서는 $R_{i}^{0}, R_{i}^{1}, R_{i}^{2}$ 로 표현함
  • 각 ROI에서 다시, 256x256사이즈의 패치를 뽑아서 pretrained CNN에 통과. 각 통과한 출력은 ($X_{i}^{0}\in \mathbb{R}^{64\times c }, X_{i}^{1}\in \mathbb{R}^{16\times c } , X_{i}^{2}\in \mathbb{R}^{4 \times c }$로 표현.
  • 각 ROI의 출력을 FC을 하나 통과시켜서, 초기의 특징값(d차원으로 변경)을 구성. 이를 하나로 합쳐(concat) 하나의 특징으로 활용 $X_{i} \in \mathbb{R}^{84 \times d}$ . 즉 $\mathbf{X}_{i} = [X_{i}^{0}, X_{i}^{1}, X_{i}^{2}]$
  • 한 슬라이드(b)은 M개의 ROI을 가짐
  • 스케일 s에 대해서 $\mathbf{X}_{s} = [X_{i}^{s}, X_{i}^{s}, ...X_{n_{s}}^{s}]$ 로 표현함. 각 ROI은 d차원을 가짐

 

2. Self-attention(SA)을 이용한 인스턴스 표현

  • 2.1. embedding projection + positional embedding
    • $Z_{s0} = \begin{bmatrix} x^{s}_{cls}; & x^{s}_{1}E,..., x^{s}_{n_{s}E} \end{bmatrix} + E_{pos}$
  • 2.2.  Intra-scale SA(동일스캐일 내): ViT에서 원래 해주던 SA
    • 추가로 Relative position bias을 도입합니다. $\text{Attention Scores} = \frac{\mathbf{Q} \mathbf{K}^\top}{\sqrt{d_k}} + \text{Relative Position Bias}$
    • 이 Relative position bias을 주는 이유는 PE(Positional embedding)은 절대적인 위치로만 임베딩 되기에 이를 보정하기위한 bias을 추가한 경우입니다. 예를 들어, A단어는 B단어의 2칸 옆에 떨어져있다와 같은 정보가 relative position이 되겠습니다.
    • $h_{cls}^{s}$: Intrascale SA에서 나온 출력시퀀스에 첫 토큰으로, cls token에 해당합니다.

  • 2.3 Inter-scale SA:
    • Inter-scale SA은 고배율의 4개의 패치의 특징저배율의 1개의 패치(특징아님)을 concat하여 또 다른 transformer에 전달합니다. Transformer에서는 positional embedding도 해줍니다 (아마, 첫번째 토큰이 저배율임을 인식시키기 위함일 듯 합니다)
    • 전달한 결과의 가장 첫 토큰을 inter-scale 표현으로 사용합니다. $Z^{s+1}, \_ = InterscaleSA(Z^{s->(s+1)})$
    • 집계: Intrascale 결과의 cls_token결과만 weighted sum해서 사용합니다. 이 $w_{s}$은 [0,1]의 값을 가지고 sum to 1으로 정규화한갑입니다.

3. Pooling: attention weighted aggregation

  • 한 슬라이드내에 ROI별로 $h$을 구했다면, 이를 모두 attention weighted sum하여 슬라이드 예측에 사용합니다.
  • 식(6)은 gated attention weighted 입니다.
  • 식(7)은 attention weighted sum

 

4. Multi-level supervision: Slide prediction과 patch level prediction을 multi-task로 학습

  • Slide level prediction은 $h_{slide,k}$을 이용해서 FC을 통과하여 예측합니다.
  • Instance level prediction은 $h_{i}$을 이용해서 FC을 통과하여 예측합니다.
    • instance level label이 없기 때문에, CLAM의 방법과 동일하게 top k 의 라벨을 그대로 따라 씁니다.
반응형

요약


Gradient Accumulation은 GPU 메모리 한계를 극복하면서 더 큰 배치(batch) 크기로 학습한 것과 동일한 효과를 내기 위해 사용하는 기법입니다.

 

문제: GPU 메모리 한계로 큰 배치를 한번에 학습할 수 없는 경우 발생

해결방법

  1. 작은 배치에서 계산된 loss을 gradient을 누적합니다. (=.backward()만 호출하면 gradient값이 더해집니다.)
  2. 정해진 횟수(gradient accumulation step)만큼 누적되었다면 optimizer로 가중키를 업데이트 합니다.
  3. 최종기울기가 큰 배치에서 한번에 처리한 결과와 동일하도록 각 loss을 gradient accumulation step만큼 나눠주어 스케일링합니다.

 

코드 스니펫


아래와 같은 코드스니펫으로 사용될 수 있습니다. 주요 코드 설명은 아래와 같습니다.

  • loss = loss / accumulation_steps : loss을 누적하여 이후에 backward을 할거기에 미리 accumulation_steps수로 나눠줍니다.
  • loss.backward(): backward을 호출하면 gradient가 이전의 gradient값과 내부적으로 더해(add)집니다. 여기서는 step()과 zero_grad()을 호출하지 않앗기때문에 backward을 호출할때마다 gradient값이 누적됩니다.
  • (step+1) % accumlation_steps == 0: 조건식을 만족하면 누적되어 평균된 gradient값에 대해서 모델의 가중치를 업데이트(step)하고, 이후 step에 반영되지 않도록 0으로 초기화해줍니다(zero_grad())
accumulation_steps = 4

model.train()
optimizer.zero_grad()

for step, (input_data, target_data) in enumerate(dataloader):
    outputs = model(input_data)
    loss = criterion(outputs, target_data)
    loss = loss / accumulation_steps
    loss.backward()

    if (step + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

 

수치적인 예시


예를 들어, 큰 배치(8)을 작은 배치(n=8)로 나누는 예시를 생각해볼 수 있습니다. 이 때, accumulation_steps은 2가 됩니다.

첫 번재 미니배치에서는 4개의 샘플이 들어옵니다

$loss_{1} = \frac{L_{1} + L_{2} + L_{3} + L_{4}}{4}$: 첫 번째 미니배치의 손실값입니다. pytorch에서 reduce을 미리 해주기에 평균값으로 자동으로 계산됩니다. 하지만, 최종적으로 8개 샘플에 대한 평균을 맞춰야 하므로, accumulation_steps = 2로 설정했다면, 아래와 같습니다.

$loss'_{1}  = \frac{1}{2} \times \frac{L_{1} + L_{2} + L_{3} + L_{4}}{4}$ 

이 때, loss_prime_1.backward()을 수행하면 $\nabla (loss_{1})/2$가 됩니다.

두 번째, 미니배치에서도 4개의 샘플이 들어옵니다.

$loss_{2} = \frac{L_{5} + L_{6} + L_{7} + L_{8}}{4}$: 두 번째 미니배치의 손실값입니다. 여기서도 accumulation_steps = 2로 설정햇기에 아래와 같습니다.

$loss'_{2}  = \frac{1}{2} \times \frac{L_{5} + L_{6} + L_{7} + L_{8}}{4}$ 

여기서도 loss_prime_2.backward()을 수행하면 $\nabla (loss_{2})/2$ 가 더해집니다.

 

최종적인 손실함수은 아래와 같습니다.

$\frac{1}{2} \times \frac{L_{1} + L_{2} + L_{3} + L_{4}}{4} + \frac{1}{2} \times \frac{L_{5} + L_{6} + L_{7} + L_{8}}{4} = \frac{L_{1} + ... + L_{8}}{8}$ 

 

그리고 최종적인 gradient은 아래와 같습니다. 즉, loss을 계산한후에 accumulation_step으로 나눠주어야, 원하는 큰 배치사이즈에 맞춰서 같은 스케일로 gradient을 업데이트할 수 있습니다.

$\frac{1}{2} \times \frac{\nabla (L_{1} + L_{2} + L_{3} + L_{4})}{4} + \frac{1}{2} \times \frac{\nabla (L_{5} + L_{6} + L_{7} + L_{8})}{4}  = \frac{ \nabla  (L_{1} + ... + L_{8})}{8} $

반응형

GitHub Actions에서 Python 의존성 설치 시간을 단축하려면, 캐시(Cache)를 활용해서 이미 설치한 라이브러리를 재사용하도록 구성하는 방법이 가장 간단하고 효과적입니다. 특히 actions/cache를 이용해 pip가 내려받은 패키지를 캐싱해두면, 매번 새로 설치할 필요가 없어져 실행 시간이 크게 단축됩니다.

 

아래와 같이 케시 작업을 workflow내에 추가합니다.

  - name: Cache pip dependencies
    uses: actions/cache@v2
    with:
      path: ~/.cache/pip
      key: ${{ runner.os }}-pip-${{ hashFiles('requirements-dev.txt') }}
      restore-keys: |
        ${{ runner.os }}-pip-

 

설명하자면 아래와 같습니다.

  1. uses: actions/cache@v2: GitHub 마켓플레이스에서 공식 엑션을 쓰겠다는 것입니다. 버전은 v2입니다. 현재나와있는 버전은 v4까지지원된다고하며, 2025년 2월 1일내에 v4또는 v3로 버전업하라고 안내가 되어있내요.
  2. path: ~/.cache/pip: pip가 패키지를다운로드할 때 사용하는 캐시 디렉터리입니다. pip install이 실행되면 라이브러리를 먼저 '~/.cache/pip'에 설치를 진행합니다. 이 경로를 캐시해둠으로써, 다음 엑션시에 패키지 다운로드 과정을 스킵합니다.
  3. key: ${{ runner.os }}-pip-${{ hashFiles('requirements-dev.txt') }} : 캐시를 구분하기 위한 식별자입니다. runner.os은 (os별로 캐시를 구분하는 역할, 예, linux, Windows, macOS)에 해당합니다. 그리고 뒤에 hashFiles('requirements-dev.txt') 은 의존성 목록에 해당하는 'requirements-dev.txt'을 해시하여(checksum)하여 키에 추가합니다. 만일 'requirements-dev.txt'가 일부라도 바뀐다면 체크섬이 다르기에 새로운 캐시를 생성합니다.
  4. restore-keys: |: 키가 완전히 일치하지 않는 경우, 접두사(Prefix)와 일치하는 근접한 캐시를 살펴보기 위한 명령입니다. 딱히 필요없을 수 도 있습니다. 이 기능은 위의 key가 'Linux-pip-'라는 것으로 시작하기만해도 비슷한 내용으로 일부라도 캐시를 사용하겠다는 것입니다.

 

첫 적용시에는 아래와 같이 cache pip dependencies에 key을 조회하고 설치가 없다고 나옵니다.

같은 액션에서, 하단에 케시를 아래와 같이 저장합니다.

 

이후, 이 엑션이 다시 실행되면 아래와 같이 케시를 사용하는 것을 알 수 있습니다.

 

 

주의

* actions/cache 사용시 캐시 크기 제한이 약5GB가 있으므로, 너무 큰 라이브러리는 불가능할 수 있습니다.

 

반응형

 

요약


 

Motivation


1. 염색정규화(Stain normalization)은 타깃(target image, 또는 reference image)라고 불리는 이미지를 정하고, 이 타깃이미지에 맞춰서 소스이미지의 염색톤을 변화하는 것을 의미합니다.

2. 그러나, 염색정규화에서 이런 타깃이미지를 선정하는 과정이 매우 작위적이고(=관찰자마다 다르며), 타깃이미지를 어떤 것을 고르냐에 따라 인공지능 모델의 성능이 크게 차이가 납니다(아래 Fig1은 어떤 이미지를 고르냐에따라서 성능이 크게 좌우된다는 예시를 표하기위한 그림입니다).

Method: 염색정규화과정에 필요한 trainable parameters을 인공지능 모델에 포함하여 학습

저자들은 데이터로부터 염색정규화에 필요한 파라미터를 모델에 추가합니다. 이 레이어명은 "LStrainNorm"이라고 합니다.

방법은 크게 "입력이미지 전달" -> "각 색공간에서 정규화" -> "다시 RGB로 변경" -> "집계" 입니다.

 

1. 입력이미지는 RGB 색상에 있는 이미지입니다. $\mathbf{x} \in \mathbb{R}^{H \times H \times 3}$

2. 색 공간에서 정규화

   2.1. 색공간에서 정규화하기위해서, 임의의 색공간 $s$로 변환합니다. 논문내에서는 $\mathbf{x}'^{s} = Transform(\mathbf{x};s)$로 표기합니다.

   2.2. 채널별로 정규화를 진행합니다. $\hat{x}'_{ijk} = \gamma _{k} \frac{x'_{ijk} - \mu_{k}}{\sigma_{k} + \epsilon} + \beta_{k}$

     - 여기서 $\mu_{k}$은  채널별로 이미지의 평균 픽셀값을 의미합니다. 또한, $\sigma_{k}$도 픽셀의 표준편차값을 의미합니다.

     - $\gamma_{k}, \beta_{k}$은 trainable parameter로 스케일(scale)과 값증감(shift)에 활용됩니다. Batch normalization에도 활용되는 값입니다.

     - 이전의 염색정규화과정에서는 이런 값들은 타깃이미지를 정함으로써, 결정되는데 이 논문에서는 이 수치들이 학습가능하게됩니다.

     - 단 완전히 학습값에만 의존하지 않도록 $\gamma_{k} = \gamma_{k}^{int} + \Delta \gamma_{k}^{opt}$로 분리합니다.

    - 그리고,  $ \Delta \gamma_{k}^{opt} $학습하여 조절할수 있도록 합니다. beta에 대해서도 동일히 작업합니다.

  2.3. 스케일링을 합니다. 정규화한 값이 너무 크거나 작거나하지않도록 [0, 1] 사이의 값으로 clip합니다. (식 9)

3. 어텐션을 이용한 컬러 공강 앙상블

  - 위의 과정을 컬러스페이스 s에 대해서 다양하게 진행합니다. 즉, 컬러공간(HSV, CEILAB 등)에 대해서 수행해서 Attention값을 구합니다.

  - Attention 값은 $Att(Q^{s}, K^{s}, V^{s})= softmax(\frac{Q^{s} \cdot   (K^{s})^{T}}{d})\cdot   V^{s}$ 로 계산합니다.

 - Self-attention을 계산할 때 사용되는 Q, K, V은 1x1 kernel을 2D conv하여 구합니다. 즉, 1x1으로 하니 채널축으로만 선형결합한 값입니다.

 - $Q^{s} = Flatten \circ  MaxPool \circ  Conv_{1}^{s}(\hat{ \mathbb{x}^{s}})$ 으로 컬러공간별로 convolution을 따로둡니다. (식11~12)

 - $K^{s} = Flatten \circ  MaxPool \circ  Conv_{2}^{s}(\hat{ \mathbb{x}^{s}})$: 마찬가지로 K도 컬러공간별로 convolution branch을 따로둡니다. 이 QK을 계산하면 patch내의 픽셀들사이의 global context을 얻는것입니다.

 - $V^{s} = Flatten \circ  MaxPool(\hat{ \mathbb{x}^{s}})$: Value은 풀링만하여 넣습니다.  위에서 계산한 global context와 곱합니다. 결과적으로 $(B, C, hw) \cdot (B, hw, hw)$을 하는모양입니다.

 - 이 결과를 원본 (B, C, W, H)와 곱해주기위해서 unflatten(B, C, w, h) 후에 업샘플하여 사이즈를 맞춰주고 (B, C, W, H) 원소별 곱하여 정규화된 이미지를 구합니다.

 - 이 이미지를 LAB, HSV 등 여러 이미지에 대해서 가중합을 하여 최종 정규화된 이미지를 획득합니다. 식(14)

반응형

 

요약


SimCLR에서는 Instance discrimiation이 학습목적으로 원본이미지를 서로 다른 증강방법을 이용해도, 서로 같은 임베딩이 되게끔 유도한다. CSI은 SimCLR방법과 매우 유사한데, 원본이미지를 증강한 경우만 OOD(Out Of Distribution)으로 학습하는 방법이다.

 

  • 이미지 증강방법들의 집합 $S:=\{S_{0}, S_{i}, ..., S_{K-1}\}$
  • 동일 이미지 반환: $I=S_{0}$

 

여러 이미지 증강방법S로부터 하나를 뽑아, 이미지 모든 이미지를 증강하고(원본반환 포함)이를 SimCLR을 돌림. 이 과정을 여러 증강방법에 대해서 반복함. 

추가적인 학습 테스크로, 증강된 이미지가, 어떤 증강방법으로 이용되었는지를 분류하는 방법도 진행

 

최종학습 Objective은 증강에 따른 이미지를 OOD Image로 SImCLR하는 목적과 증강방법론 분류모델을 혼합하여 예측하게 됨

반응형
  • Diagnostic slide: FFPE(Formalin-fixed Paraffin-embedded)한 슬라이드. 
  • Tissue slide: Frozen slide. 수술장에서 동TCGA에서 TS#, BS# 라고 표기

주의: Diagnostic slide여도 Fronzen 처리 후에 FFPE하는 경우 Frozen permanent라고 부르려 그냥 FFPE하는 경우보다 dissection 등의 artifact가 더 있을 수 있음

반응형

+ Recent posts