Continual Learning (CL)
메타 정보
| 항목 |
내용 |
| 분류 |
Learning Paradigm / Lifelong Learning |
| 핵심 논문 |
"Overcoming Catastrophic Forgetting in Neural Networks" (Kirkpatrick et al., PNAS 2017 - EWC), "Progressive Neural Networks" (Rusu et al., arXiv 2016), "Gradient Episodic Memory for Continual Learning" (Lopez-Paz & Ranzato, NeurIPS 2017 - GEM), "Dark Experience for General Continual Learning" (Buzzega et al., NeurIPS 2020 - DER++), "PackNet: Adding Multiple Tasks to a Single Network by Iterative Pruning" (Mallya & Lazebnik, CVPR 2018), "A Comprehensive Survey of Continual Learning" (Wang et al., IEEE TPAMI 2024) |
| 주요 저자 |
James Kirkpatrick (EWC), Andrei Rusu (Progressive Nets), David Lopez-Paz (GEM), Pietro Buzzega (DER++), Gido van de Ven, Andreas Tolias |
| 핵심 개념 |
새로운 태스크를 순차적으로 학습하면서 이전 지식을 유지하는 학습 패러다임 |
| 관련 분야 |
Transfer Learning, Meta-Learning, Multi-Task Learning, Curriculum Learning, Knowledge Distillation |
정의
Continual Learning(CL)은 모델이 순차적으로 도착하는 태스크/데이터를 학습하면서, 이전에 학습한 지식을 catastrophic forgetting(파국적 망각) 없이 유지하는 것을 목표로 하는 학습 패러다임이다.
기존 학습 방식과의 비교
| 항목 |
정적 학습 (Static) |
Multi-Task Learning |
Continual Learning |
| 데이터 접근 |
전체 데이터 동시 |
전체 태스크 동시 |
순차적 (이전 데이터 접근 불가) |
| 태스크 수 |
고정 |
고정 |
증가 |
| 메모리 |
전체 저장 |
전체 저장 |
제한적 |
| 주요 과제 |
과적합 |
태스크 간 간섭 |
망각 vs 가소성 |
| 현실 반영도 |
낮음 |
중간 |
높음 |
핵심 문제: Catastrophic Forgetting
Catastrophic Forgetting의 메커니즘:
[Task A 학습]
+------------------------------------------+
| 파라미터가 Task A의 최적해로 수렴 |
| theta_A* = argmin L_A(theta) |
+------------------------------------------+
|
v
[Task B 학습 (Task A 데이터 없이)]
+------------------------------------------+
| 파라미터가 Task B 방향으로 이동 |
| theta_B* = argmin L_B(theta) |
| |
| --> Task A 성능이 급격히 하락! |
| --> theta_B*는 theta_A*에서 멀어짐 |
+------------------------------------------+
핵심 딜레마: Stability-Plasticity Tradeoff
- Stability: 이전 지식을 안정적으로 유지
- Plasticity: 새로운 지식을 유연하게 습득
- 둘 다 완벽하게 달성하는 것은 이론적으로 어려움
CL 시나리오 분류
CL 문제는 태스크 정보의 가용성에 따라 세 가지 시나리오로 구분된다:
Continual Learning 시나리오:
+---------------------------------------------+
| 1. Task-Incremental Learning (Task-IL) |
| - 추론 시 태스크 ID 제공됨 |
| - 태스크별 독립적 출력 헤드 사용 가능 |
| - 난이도: 가장 쉬움 |
| - 예: "이것은 Task 2의 입력이다" 정보 제공 |
+---------------------------------------------+
|
v
+---------------------------------------------+
| 2. Domain-Incremental Learning (Domain-IL) |
| - 태스크 ID 미제공, 출력 공간 동일 |
| - 입력 분포만 변화 |
| - 난이도: 중간 |
| - 예: 다른 환경의 자율주행 데이터 |
+---------------------------------------------+
|
v
+---------------------------------------------+
| 3. Class-Incremental Learning (Class-IL) |
| - 태스크 ID 미제공, 출력 공간 확장 |
| - 새로운 클래스가 지속적으로 추가 |
| - 난이도: 가장 어려움 |
| - 예: 새로운 객체 카테고리 추가 |
+---------------------------------------------+
| 시나리오 |
태스크 ID (학습) |
태스크 ID (추론) |
출력 공간 |
대표 벤치마크 |
| Task-IL |
제공 |
제공 |
태스크별 분리 |
Permuted MNIST |
| Domain-IL |
제공 |
미제공 |
공유 |
Rotated MNIST |
| Class-IL |
제공 |
미제공 |
확장 |
Split CIFAR-100 |
핵심 방법론
CL 방법론은 크게 세 가지 패밀리로 분류된다:
Continual Learning 방법론 분류:
+---------------------------------------------+
| 1. Regularization-Based |
| - 중요 파라미터의 변화를 제한 |
| - 추가 메모리 불필요 (파라미터 수준) |
| - EWC, SI, MAS, LwF |
+---------------------------------------------+
|
v
+---------------------------------------------+
| 2. Replay-Based |
| - 이전 데이터를 버퍼에 저장/생성하여 재학습 |
| - 메모리 버퍼 필요 |
| - ER, GEM, A-GEM, DER++, GDumb |
+---------------------------------------------+
|
v
+---------------------------------------------+
| 3. Architecture-Based |
| - 태스크별 전용 파라미터 할당 |
| - 모델 크기 증가 |
| - Progressive Nets, PackNet, DEN |
+---------------------------------------------+
1. Regularization-Based Methods
EWC (Elastic Weight Consolidation)
| 항목 |
내용 |
| 논문 |
Kirkpatrick et al., PNAS 2017 |
| 핵심 |
Fisher Information Matrix로 중요 파라미터 식별 후 변화 억제 |
| 직관 |
이전 태스크에 중요한 가중치일수록 더 강하게 고정 |
| 장점 |
단순하고 구현 용이, 추가 데이터 저장 불필요 |
| 단점 |
Fisher 근사의 부정확성, 태스크 수 증가 시 누적 제약 |
EWC 손실 함수:
\[
L(\theta) = L_B(\theta) + \frac{\lambda}{2} \sum_i F_i (\theta_i - \theta_{A,i}^*)^2
\]
- \(L_B(\theta)\): 새 태스크 B의 손실
- \(F_i\): Fisher Information Matrix의 대각 원소 (파라미터 \(i\)의 중요도)
- \(\theta_{A,i}^*\): 태스크 A 학습 후 최적 파라미터
- \(\lambda\): 정규화 강도 (stability-plasticity 균형 조절)
EWC의 작동 원리:
파라미터 공간에서의 최적화 경로:
theta_2 (파라미터 2)
^
| * theta_A* (Task A 최적해)
| /|
| / | <-- Fisher가 큰 방향: 이동 억제
| / |
| / |
|/ _ _|_ _ _ _ _ _ _> theta_1 (파라미터 1)
| ^
| |
| Fisher가 작은 방향: 자유롭게 이동 가능
|
--> Task B 학습 시 중요 파라미터 방향의 이동만 제한
--> 덜 중요한 방향으로는 자유롭게 이동하여 새 태스크 학습
SI (Synaptic Intelligence)
| 항목 |
내용 |
| 논문 |
Zenke et al., ICML 2017 |
| 핵심 |
온라인으로 파라미터 중요도를 축적 (path integral) |
| vs EWC |
EWC는 학습 후 Fisher 계산, SI는 학습 중 실시간 추적 |
| 장점 |
별도 중요도 계산 단계 불필요 |
\[
L(\theta) = L_{current}(\theta) + c \sum_k \Omega_k (\theta_k - \theta_k^*)^2
\]
- \(\Omega_k\): 파라미터 \(k\)의 누적 중요도 (경로 적분으로 계산)
LwF (Learning without Forgetting)
| 항목 |
내용 |
| 논문 |
Li & Hoiem, IEEE TPAMI 2018 |
| 핵심 |
Knowledge Distillation을 CL에 적용 |
| 방식 |
새 데이터에 대한 이전 모델의 출력을 soft target으로 사용 |
| 장점 |
이전 데이터 저장 불필요, 파라미터 중요도 계산 불필요 |
| 단점 |
태스크 간 유사도가 낮으면 효과 감소 |
2. Replay-Based Methods
ER (Experience Replay)
| 항목 |
내용 |
| 핵심 |
이전 태스크의 샘플을 고정 크기 메모리 버퍼에 저장 |
| 버퍼 관리 |
Reservoir Sampling으로 균등 분포 유지 |
| 학습 |
현재 태스크 배치 + 버퍼 배치를 함께 학습 |
| 장점 |
단순하지만 강력한 베이스라인 |
| 단점 |
개인정보 문제 (원본 데이터 저장), 메모리 제한 |
GEM / A-GEM
| 항목 |
GEM |
A-GEM |
| 논문 |
Lopez-Paz & Ranzato, NeurIPS 2017 |
Chaudhry et al., ICLR 2019 |
| 핵심 |
이전 태스크 손실 증가 방지 제약 |
GEM의 효율적 근사 |
| 제약 |
모든 이전 태스크에 대해 gradient projection |
랜덤 샘플된 이전 배치 1개로 근사 |
| 복잡도 |
O(태스크 수) QP 문제 |
O(1) 단일 projection |
| 성능 |
정확하지만 느림 |
빠르지만 다소 부정확 |
GEM의 제약 조건:
\[
\langle g, g_k \rangle \geq 0, \quad \forall k < t
\]
- \(g\): 현재 태스크의 gradient
- \(g_k\): 이전 태스크 \(k\)의 메모리 기반 gradient
- 현재 업데이트가 이전 태스크의 손실을 증가시키지 않도록 보장
DER++ (Dark Experience Replay++)
| 항목 |
내용 |
| 논문 |
Buzzega et al., NeurIPS 2020 |
| 핵심 |
입력 + 레이블뿐 아니라 모델의 logit 출력도 함께 저장 |
| 직관 |
"dark knowledge" (soft output)가 hard label보다 풍부한 정보 제공 |
| 손실 |
cross-entropy (현재) + MSE (버퍼 logit) + cross-entropy (버퍼 label) |
| 성능 |
단순 ER 대비 일관된 개선 |
DER++ 손실 함수:
\[
L = L_{ce}(f_\theta(x), y) + \alpha \cdot \|f_\theta(x_{buf}) - z_{buf}\|^2 + \beta \cdot L_{ce}(f_\theta(x_{buf}), y_{buf})
\]
- \((x, y)\): 현재 태스크 샘플
- \((x_{buf}, y_{buf}, z_{buf})\): 버퍼의 (입력, 레이블, 저장된 logit)
- \(\alpha\): logit matching 강도
- \(\beta\): 레이블 matching 강도
3. Architecture-Based Methods
Progressive Neural Networks
| 항목 |
내용 |
| 논문 |
Rusu et al., arXiv 2016 (DeepMind) |
| 핵심 |
새 태스크마다 새 네트워크 칼럼 추가 + lateral connection |
| 장점 |
망각 완전 방지 (이전 칼럼 동결) |
| 단점 |
태스크 수에 비례하여 모델 크기 선형 증가 |
| 적용 |
RL 환경에서의 태스크 전이에 효과적 |
Progressive Neural Networks 구조:
Task 1 Task 2 Task 3
Column Column Column
[Layer 3] [Layer 3] [Layer 3]
| | <--- | <--- <---
[Layer 2] [Layer 2] [Layer 2]
| | <--- | <--- <---
[Layer 1] [Layer 1] [Layer 1]
| | |
Input Input Input
<--- : Lateral Connection (이전 칼럼에서 지식 전이)
- 이전 칼럼: frozen (수정 불가)
- 새 칼럼: 자유롭게 학습
- Lateral connection: 이전 지식 재활용
PackNet
| 항목 |
내용 |
| 논문 |
Mallya & Lazebnik, CVPR 2018 |
| 핵심 |
프루닝으로 태스크별 서브네트워크 할당 |
| 과정 |
(1) 학습 -> (2) 프루닝 -> (3) 재학습 -> (4) 마스킹 고정 |
| 장점 |
단일 네트워크 내에서 여러 태스크 수용 |
| 단점 |
수용 가능한 태스크 수가 네트워크 용량에 제한 |
PackNet의 파라미터 할당:
하나의 네트워크 내부:
+-----------------------------------------+
| [Task 1 전용] [Task 2 전용] [공유] |
| ############ oooooooooooo ........ |
| ############ oooooooooooo ........ |
| ############ oooooooooooo ........ |
| ############ oooooooooooo [미사용] |
+-----------------------------------------+
# = Task 1 파라미터 (고정됨)
o = Task 2 파라미터 (고정됨)
. = 공유 파라미터
= 미사용 (향후 태스크용)
각 태스크 학습 후 프루닝으로 사용 파라미터 축소
--> 남은 용량을 다음 태스크에 할당
방법론 비교
성능 비교 (Split CIFAR-100, Class-IL, Buffer=2000)
| 방법 |
유형 |
평균 정확도 (%) |
망각률 (%) |
추가 메모리 |
| Fine-tuning (baseline) |
- |
~8-12 |
~80+ |
없음 |
| EWC |
Regularization |
~20-25 |
~50-60 |
Fisher 행렬 |
| SI |
Regularization |
~19-24 |
~55-65 |
중요도 행렬 |
| LwF |
Regularization |
~18-22 |
~60-70 |
없음 |
| ER |
Replay |
~40-45 |
~20-30 |
버퍼 2000 |
| A-GEM |
Replay |
~20-25 |
~40-50 |
버퍼 2000 |
| GDumb |
Replay |
~35-42 |
N/A |
버퍼 2000 |
| DER++ |
Replay |
~45-52 |
~15-25 |
버퍼 2000 + logit |
| ER-ACE |
Replay |
~48-55 |
~12-20 |
버퍼 2000 |
| PackNet |
Architecture |
~50-60 (Task-IL) |
~0 |
마스크 |
| Progressive Nets |
Architecture |
~55-65 (Task-IL) |
0 |
추가 칼럼 |
주: 정확한 수치는 실험 설정(backbone, epoch, buffer 관리 등)에 따라 달라진다. 위 범위는 여러 논문에서 보고된 대략적 경향이다.
장단점 비교
| 방법 유형 |
장점 |
단점 |
적합 상황 |
| Regularization |
추가 메모리 불필요, 단순 |
Class-IL에서 성능 약함 |
메모리 제약 환경, 단순 태스크 |
| Replay |
강력한 성능, 범용적 |
데이터 저장 필요, 프라이버시 문제 |
대부분의 CL 시나리오 |
| Architecture |
망각 없음, 이론적 보장 |
모델 크기 증가, 확장성 제한 |
소수 태스크, 자원 충분 |
평가 지표
| 지표 |
수식 |
의미 |
| Average Accuracy (AA) |
\(AA = \frac{1}{T} \sum_{i=1}^{T} a_{T,i}\) |
T개 태스크 학습 후 모든 태스크의 평균 정확도 |
| Backward Transfer (BWT) |
\(BWT = \frac{1}{T-1} \sum_{i=1}^{T-1} (a_{T,i} - a_{i,i})\) |
이후 학습이 이전 태스크에 미치는 영향 (음수 = 망각) |
| Forward Transfer (FWT) |
\(FWT = \frac{1}{T-1} \sum_{i=2}^{T} (a_{i-1,i} - \bar{b}_i)\) |
이전 학습이 새 태스크 학습에 미치는 영향 (양수 = 전이) |
| Forgetting |
\(F_i = \max_{t \in \{1,...,T-1\}} a_{t,i} - a_{T,i}\) |
태스크 \(i\)에서의 최대 성능 대비 최종 성능 하락 |
- \(a_{t,i}\): 태스크 \(t\)까지 학습한 후 태스크 \(i\)에서의 정확도
- \(\bar{b}_i\): 태스크 \(i\)의 랜덤 초기화 성능
주요 벤치마크
| 벤치마크 |
유형 |
설명 |
| Permuted MNIST |
Domain-IL |
MNIST 픽셀을 태스크마다 다르게 permutation |
| Rotated MNIST |
Domain-IL |
MNIST를 태스크마다 다른 각도로 회전 |
| Split MNIST |
Task-IL / Class-IL |
MNIST 10개 클래스를 5개 태스크로 분할 |
| Split CIFAR-10 |
Class-IL |
CIFAR-10을 5개 태스크 (2 클래스/태스크) |
| Split CIFAR-100 |
Class-IL |
CIFAR-100을 10-20개 태스크로 분할 |
| Split Mini-ImageNet |
Class-IL |
Mini-ImageNet 100 클래스를 분할 |
| CORe50 |
복합 |
50개 객체, 11개 세션의 실제 연속 학습 |
| Split TinyImageNet |
Class-IL |
TinyImageNet 200 클래스 분할 |
Python 구현 예시
EWC (Elastic Weight Consolidation)
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from copy import deepcopy
class EWC:
"""Elastic Weight Consolidation 구현.
이전 태스크에 중요한 파라미터의 변화를 억제하여
catastrophic forgetting을 완화한다.
"""
def __init__(self, model: nn.Module, lambda_ewc: float = 400.0):
self.model = model
self.lambda_ewc = lambda_ewc
self.saved_params = {} # 태스크별 최적 파라미터
self.fisher_diags = {} # 태스크별 Fisher 대각 행렬
self.task_count = 0
def compute_fisher(self, dataloader: DataLoader,
num_samples: int = 2000) -> dict:
"""Fisher Information Matrix의 대각 원소를 경험적으로 추정.
F_i = E[(d log p(y|x, theta) / d theta_i)^2]
"""
fisher = {}
for name, param in self.model.named_parameters():
fisher[name] = torch.zeros_like(param)
self.model.eval()
count = 0
for inputs, targets in dataloader:
if count >= num_samples:
break
inputs, targets = inputs.cuda(), targets.cuda()
self.model.zero_grad()
outputs = self.model(inputs)
loss = nn.functional.cross_entropy(outputs, targets)
loss.backward()
for name, param in self.model.named_parameters():
if param.grad is not None:
fisher[name] += param.grad.data ** 2
count += inputs.size(0)
# 평균
for name in fisher:
fisher[name] /= count
return fisher
def register_task(self, dataloader: DataLoader):
"""현재 태스크 학습 완료 후 호출. 파라미터와 Fisher 저장."""
self.fisher_diags[self.task_count] = self.compute_fisher(dataloader)
self.saved_params[self.task_count] = {
name: param.data.clone()
for name, param in self.model.named_parameters()
}
self.task_count += 1
def penalty(self) -> torch.Tensor:
"""EWC 정규화 항 계산.
L_ewc = (lambda/2) * sum_i F_i * (theta_i - theta_i*)^2
"""
if self.task_count == 0:
return torch.tensor(0.0).cuda()
loss = torch.tensor(0.0).cuda()
for task_id in range(self.task_count):
for name, param in self.model.named_parameters():
fisher = self.fisher_diags[task_id][name]
old_param = self.saved_params[task_id][name]
loss += (fisher * (param - old_param) ** 2).sum()
return (self.lambda_ewc / 2.0) * loss
def train_with_ewc(model, ewc, dataloader, optimizer, epochs=10):
"""EWC를 적용한 학습 루프."""
model.train()
for epoch in range(epochs):
total_loss = 0.0
for inputs, targets in dataloader:
inputs, targets = inputs.cuda(), targets.cuda()
outputs = model(inputs)
ce_loss = nn.functional.cross_entropy(outputs, targets)
ewc_loss = ewc.penalty()
loss = ce_loss + ewc_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f}")
Experience Replay with Reservoir Sampling
import torch
import numpy as np
from collections import defaultdict
class ReservoirBuffer:
"""Reservoir Sampling 기반 Experience Replay 버퍼.
스트림 데이터에서 균등 확률로 샘플링하여
고정 크기 버퍼를 유지한다.
"""
def __init__(self, buffer_size: int = 2000):
self.buffer_size = buffer_size
self.buffer_x = []
self.buffer_y = []
self.n_seen = 0
def add(self, x: torch.Tensor, y: torch.Tensor):
"""Reservoir Sampling으로 버퍼에 추가.
i번째 샘플이 버퍼에 남을 확률 = buffer_size / i
"""
batch_size = x.size(0)
for i in range(batch_size):
self.n_seen += 1
if len(self.buffer_x) < self.buffer_size:
self.buffer_x.append(x[i].cpu())
self.buffer_y.append(y[i].cpu())
else:
# Reservoir sampling: 확률 buffer_size/n_seen으로 교체
idx = np.random.randint(0, self.n_seen)
if idx < self.buffer_size:
self.buffer_x[idx] = x[i].cpu()
self.buffer_y[idx] = y[i].cpu()
def sample(self, batch_size: int):
"""버퍼에서 랜덤 배치 샘플링."""
indices = np.random.choice(
len(self.buffer_x),
size=min(batch_size, len(self.buffer_x)),
replace=False
)
x = torch.stack([self.buffer_x[i] for i in indices]).cuda()
y = torch.stack([self.buffer_y[i] for i in indices]).cuda()
return x, y
def __len__(self):
return len(self.buffer_x)
def train_with_replay(model, buffer, dataloader, optimizer,
epochs=10, replay_batch=64):
"""Experience Replay를 적용한 학습 루프."""
model.train()
for epoch in range(epochs):
total_loss = 0.0
for inputs, targets in dataloader:
inputs, targets = inputs.cuda(), targets.cuda()
# 현재 태스크 손실
outputs = model(inputs)
loss = nn.functional.cross_entropy(outputs, targets)
# 리플레이 손실 (버퍼가 비어있지 않으면)
if len(buffer) > 0:
buf_x, buf_y = buffer.sample(replay_batch)
buf_outputs = model(buf_x)
replay_loss = nn.functional.cross_entropy(
buf_outputs, buf_y
)
loss = loss + replay_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 현재 배치를 버퍼에 추가
buffer.add(inputs, targets)
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f}")
DER++ (Dark Experience Replay++)
import torch
import torch.nn as nn
import numpy as np
class DERPlusPlusBuffer:
"""DER++ 버퍼: 입력, 레이블, logit을 함께 저장."""
def __init__(self, buffer_size: int = 2000, num_classes: int = 100):
self.buffer_size = buffer_size
self.buffer_x = []
self.buffer_y = []
self.buffer_logits = [] # dark knowledge 저장
self.n_seen = 0
def add(self, x, y, logits):
batch_size = x.size(0)
for i in range(batch_size):
self.n_seen += 1
if len(self.buffer_x) < self.buffer_size:
self.buffer_x.append(x[i].cpu())
self.buffer_y.append(y[i].cpu())
self.buffer_logits.append(logits[i].detach().cpu())
else:
idx = np.random.randint(0, self.n_seen)
if idx < self.buffer_size:
self.buffer_x[idx] = x[i].cpu()
self.buffer_y[idx] = y[i].cpu()
self.buffer_logits[idx] = logits[i].detach().cpu()
def sample(self, batch_size):
indices = np.random.choice(
len(self.buffer_x),
size=min(batch_size, len(self.buffer_x)),
replace=False
)
x = torch.stack([self.buffer_x[i] for i in indices]).cuda()
y = torch.stack([self.buffer_y[i] for i in indices]).cuda()
logits = torch.stack(
[self.buffer_logits[i] for i in indices]
).cuda()
return x, y, logits
def __len__(self):
return len(self.buffer_x)
def train_with_der_pp(model, buffer, dataloader, optimizer,
alpha=0.5, beta=0.5, epochs=10):
"""DER++ 학습 루프.
L = L_ce(current) + alpha * MSE(logit matching) + beta * L_ce(buffer)
Args:
alpha: logit matching 가중치
beta: buffer cross-entropy 가중치
"""
model.train()
for epoch in range(epochs):
total_loss = 0.0
for inputs, targets in dataloader:
inputs, targets = inputs.cuda(), targets.cuda()
# 현재 태스크 forward
outputs = model(inputs)
loss = nn.functional.cross_entropy(outputs, targets)
if len(buffer) > 0:
buf_x, buf_y, buf_logits = buffer.sample(64)
buf_outputs = model(buf_x)
# Logit matching (dark knowledge)
logit_loss = nn.functional.mse_loss(
buf_outputs, buf_logits
)
# Buffer cross-entropy
ce_buf_loss = nn.functional.cross_entropy(
buf_outputs, buf_y
)
loss = loss + alpha * logit_loss + beta * ce_buf_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 현재 배치를 logit과 함께 버퍼에 저장
with torch.no_grad():
current_logits = model(inputs)
buffer.add(inputs, targets, current_logits)
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f}")
CL 평가 프레임워크
import torch
import numpy as np
class CLEvaluator:
"""Continual Learning 평가 지표 계산기.
Average Accuracy, Backward Transfer, Forward Transfer,
Forgetting을 체계적으로 측정한다.
"""
def __init__(self, num_tasks: int):
self.num_tasks = num_tasks
# accuracy_matrix[i][j] = 태스크 i까지 학습 후 태스크 j 정확도
self.accuracy_matrix = np.zeros((num_tasks, num_tasks))
def record(self, trained_up_to: int, task_id: int, accuracy: float):
"""정확도 기록.
Args:
trained_up_to: 현재까지 학습한 태스크 수 (0-indexed)
task_id: 평가 대상 태스크 (0-indexed)
accuracy: 정확도 (0~1)
"""
self.accuracy_matrix[trained_up_to][task_id] = accuracy
def average_accuracy(self) -> float:
"""Average Accuracy: 마지막 태스크까지 학습 후 전체 평균."""
T = self.num_tasks
return np.mean(self.accuracy_matrix[T-1, :T])
def backward_transfer(self) -> float:
"""Backward Transfer: 이후 학습이 이전 태스크에 미치는 영향.
BWT < 0: 망각 발생
BWT > 0: 이후 학습이 이전 태스크에 도움 (드묾)
"""
T = self.num_tasks
bwt = 0.0
for i in range(T - 1):
bwt += self.accuracy_matrix[T-1, i] - self.accuracy_matrix[i, i]
return bwt / (T - 1)
def forward_transfer(self, random_baselines: np.ndarray) -> float:
"""Forward Transfer: 이전 학습이 새 태스크 초기 성능에 미치는 영향.
Args:
random_baselines: 각 태스크의 랜덤 초기화 성능
"""
T = self.num_tasks
fwt = 0.0
for i in range(1, T):
fwt += self.accuracy_matrix[i-1, i] - random_baselines[i]
return fwt / (T - 1)
def forgetting(self) -> float:
"""Average Forgetting: 각 태스크의 최대 성능 대비 최종 성능 하락."""
T = self.num_tasks
forget = 0.0
for i in range(T - 1):
max_acc = np.max(self.accuracy_matrix[:T, i])
forget += max_acc - self.accuracy_matrix[T-1, i]
return forget / (T - 1)
def print_summary(self):
"""전체 평가 결과 출력."""
print("=" * 50)
print("Continual Learning Evaluation Summary")
print("=" * 50)
print(f"Average Accuracy: {self.average_accuracy():.4f}")
print(f"Backward Transfer: {self.backward_transfer():.4f}")
print(f"Average Forgetting: {self.forgetting():.4f}")
print("-" * 50)
print("Accuracy Matrix (row=trained_up_to, col=task):")
print(np.round(self.accuracy_matrix, 3))
최신 동향 (2024-2025)
LLM과 Continual Learning
| 주제 |
설명 |
| Continual Pre-training |
기존 LLM에 새 도메인 지식 추가 (의료, 법률 등) |
| Continual Instruction Tuning |
새로운 instruction 유형 순차 학습 |
| Continual RLHF |
사용자 피드백 기반 지속적 정렬 |
| Prompt-based CL |
L2P, DualPrompt 등 프롬프트 풀 활용 |
주요 연구 방향
| 방향 |
대표 연구 |
| Online CL |
데이터를 한 번만 볼 수 있는 환경 (single-pass) |
| Task-Free CL |
태스크 경계 없이 연속 학습 |
| Multimodal CL |
비전-언어 모델의 연속 학습 |
| Federated CL |
분산 환경에서의 연속 학습 |
| CL for Foundation Models |
LoRA, Adapter 기반 효율적 CL |
실무 적용 가이드
어떤 CL 방법을 선택할 것인가
| 상황 |
추천 방법 |
이유 |
| 메모리 제약 심한 환경 |
EWC / SI |
데이터 저장 불필요 |
| 범용 CL (Class-IL) |
DER++ / ER-ACE |
가장 강력한 성능 |
| 프라이버시 중요 |
Generative Replay / LwF |
원본 데이터 미저장 |
| 소수 태스크 + 자원 풍부 |
Progressive Nets |
망각 완전 방지 |
| LLM 연속 학습 |
LoRA + Replay |
효율적 파라미터 관리 |
| 온라인 스트리밍 |
ER + MIR (Maximally Interfered Retrieval) |
단일 패스 대응 |
라이브러리
| 라이브러리 |
설명 |
링크 |
| Avalanche |
CL 전용 프레임워크 (ContinualAI) |
https://github.com/ContinualAI/avalanche |
| Mammoth |
DER++ 저자들의 CL 벤치마크 |
https://github.com/aimagelab/mammoth |
| continuum |
다양한 CL 시나리오 데이터 로더 |
https://github.com/Continvvm/continuum |
참고 자료
| 자료 |
링크 |
| EWC 논문 |
https://arxiv.org/abs/1612.00796 |
| Progressive Nets 논문 |
https://arxiv.org/abs/1606.04671 |
| GEM 논문 |
https://arxiv.org/abs/1706.08840 |
| DER++ 논문 |
https://arxiv.org/abs/2004.07211 |
| PackNet 논문 |
https://arxiv.org/abs/1711.05769 |
| SI 논문 |
https://arxiv.org/abs/1703.04200 |
| LwF 논문 |
https://arxiv.org/abs/1606.09282 |
| CL Survey (Wang et al., TPAMI 2024) |
https://arxiv.org/abs/2302.00487 |
| Online CL Survey (2025) |
https://arxiv.org/abs/2501.04897 |
| Avalanche 문서 |
https://avalanche.continualai.org/ |
| ContinualAI Wiki |
https://wiki.continualai.org/ |