본문으로 건너뛰기
김신건의 로그

담금질 기법 (Simulated Annealing)

· 수정 · 📖 약 3분 · 995자/단어 #algorithm #optimization #simulated-annealing
simulated annealing, 담금질 기법, SA, simulated-annealing

정의

담금질 기법 (Simulated Annealing, SA) 은 금속의 담금질 (annealing) 과정에서 영감을 받은 확률적 메타휴리스틱. 온도 T 가 높을 때는 나쁜 해도 확률적으로 수용하여 local optimum 탈출을 시도하고, T 가 낮아질수록 greedy 수용으로 전환.

문제 상황과 동기

휴리스틱 중 Local Search 는 지역 최적 (local optimum) 에 갇히는 fatal 한 단점. SA 는 확률적 탈출로 이 문제를 완화.

  • Local Search: 항상 개선 방향으로만 이동 -> 지역 최적.
  • SA: 나쁜 이동도 P = exp(-delta / T) 확률로 수용 -> 전역 탐색 가능.

핵심 통찰: 초기에는 “exploration”, 후기에는 “exploitation”.

시각화

핵심 아이디어

에너지와 온도

  • E(s): 현재 해 s 의 비용 (목적 함수)
  • T: 온도 (제어 파라미터), 시간에 따라 감소
  • delta = E(s') - E(s): 새 해와 현 해의 비용 차

수용 확률 (Metropolis Criterion)

P(accept) = 1                if delta < 0 (개선됨)
P(accept) = exp(-delta / T)  if delta >= 0 (악화됨)

T 가 클수록 나쁜 해도 자주 수용. T -> 0 이면 greedy 와 동일.

알고리즘

1. 초기 해 s, 초기 온도 T0 설정
2. while T > T_min:
3.   s' = neighbor(s)   # 이웃 생성
4.   delta = E(s') - E(s)
5.   if delta < 0 or random() < exp(-delta / T):
6.     s = s'
7.   T = T * alpha      # 냉각 (cooling)
8. return best_s

알고리즘 흐름도

flowchart TD
    A["초기 해 s, T = T0"] --> B["이웃 해 s_new 생성"]
    B --> C["delta = cost(s_new) - cost(s)"]
    C --> D{"delta 음수?"}
    D -->|"Yes"| E["s = s_new 수용"]
    D -->|"No"| F{"확률 수용?"}
    F -->|"Yes"| E
    F -->|"No"| G["거절, s 유지"]
    E --> H{"T 최솟값 초과?"}
    G --> H
    H -->|"Yes"| I["T *= alpha 냉각"]
    I --> B
    H -->|"No"| J["최적 해 반환"]

구현

// TSP simulated annealing, 2-opt swap neighbor
#include <bits/stdc++.h>
using namespace std;

double cost(const vector<int>& ord, const vector<vector<double>>& d) {
  double sum = 0; int n = ord.size();
  for (int i = 0; i < n; i++)
      sum += d[ord[i]][ord[(i+1)%n]];
  return sum;
}

int main() {
  int n; cin >> n;
  vector<vector<double>> dist(n, vector<double>(n));
  for (int i = 0; i < n; i++)
      for (int j = 0; j < n; j++)
          cin >> dist[i][j];
  vector<int> cur(n);
  iota(cur.begin(), cur.end(), 0);
  random_device rd;
  mt19937 gen(rd());
  uniform_real_distribution<> dis(0, 1);
  double T = 1000, alpha = 0.997, T_min = 1e-8;
  double curE = cost(cur, dist), bestE = curE;
  vector<int> best = cur;
  while (T > T_min) {
      int i = uniform_int_distribution<>(0, n-1)(gen);
      int j = uniform_int_distribution<>(0, n-1)(gen);
      if (i == j) continue;
      swap(cur[i], cur[j]);
      double newE = cost(cur, dist);
      double delta = newE - curE;
      if (delta < 0 || dis(gen) < exp(-delta / T)) {
          curE = newE;
          if (curE < bestE) { bestE = curE; best = cur; }
      } else {
          swap(cur[i], cur[j]);
      }
      T *= alpha;
  }
  cout << (int)bestE << "\n";
  for (int v : best) cout << v << " ";
  cout << "\n";
}
stdin
4
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0
결과
80
0 1 3 2

복잡도

항목
시간 (iteration 당)O(N) (이웃 생성 + 평가)
전체 iterationO(log(T0 / T_min) / log(1/alpha))
공간O(N)
냉각 방식기하급수적 alpha=0.99~0.999

변형 / 활용

변형설명
Adaptive SA온도 자동 조절 (reannealing)
Parallel SA여러 체인 동시 실행
Quantum Annealing양자 요동을 활용한 SA (D-Wave)

응용: TSP, VLSI 배치, 일정 최적화, 단백질 폴딩, hyperparameter tuning.

냉각 스케줄 비교

방식공식특징
기하급수적T = T * alpha가장 흔함, alpha = 0.99-0.999
선형T = T - dt빠른 냉각, 수렴 불안정
로그T = T0 / log(1 + t)이론적 수렴 보장, 매우 느림
Adaptive수용률 기반 자동 조절파라미터 조율 복잡

냉각 속도가 너무 빠르면 local optimum 에 갇히고, 너무 느리면 수렴 시간이 지수적으로 증가. alpha = 0.997 ~ 0.999 가 실전 균형점.

함정

1. 냉각 속도

alpha 가 너무 작으면 (급속 냉각) 지역 최적. 너무 크면 수렴 느림. alpha=0.997 ~ 0.999 권장.

2. 초기 온도

T0 가 너무 낮으면 초기부터 greedy. 초기 수용률 ~80% 가 되도록 T0 설정.

3. 이웃 생성

neighbor 구조가 SA 성능을 결정. TSP 에서 2-opt 가 swap 보다 훨씬 효과적.

4. 초기 해의 질

무작위 초기 해로 시작해도 SA 는 동작하지만, greedy 해를 초기 해로 주면 수렴이 빠름. TSP 에서 nearest neighbor heuristic 으로 구한 해를 SA 시작점으로 활용하면 품질이 높아짐.

5. 시간 기반 냉각

PS 에서 실행 시간 제한이 있을 때는 iteration 대신 경과 시간으로 냉각:

auto start = chrono::steady_clock::now();
// ... 루프 내부 ...
auto now = chrono::steady_clock::now();
double elapsed = chrono::duration<double>(now - start).count();
double T = T0 * (1.0 - elapsed / TIME_LIMIT);

제한 시간 2초라면 TIME_LIMIT = 1.95 로 안전 마진 확보.

6. 비결정성

SA 는 확률적 알고리즘. 같은 입력에도 결과가 다를 수 있음. 제출 시 mt19937 에 고정 seed 를 쓰거나, 시간 seed 로 여러 번 실행 후 최소값 선택.

BOJ 연습 문제

번호제목정답률링크
BOJ 1699제곱수의 합 (SA)-kokoa-lab
BOJ 19192TSP (SA 로 풀이 가능)-kokoa-lab

참고

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기