3. 기본 작업

3. 기본 작업

이 장에서는 첫 번째 EvoX 최적화 작업을 실행하는 방법을 안내합니다. EvoX 시작최적화 프로세스 초기화 방법, EvoX 프로젝트 구성 방법(알고리즘과 문제 선택 및 조립), 그리고 최적화 프로세스를 제어하기 위한 일반적으로 사용되는 기본 명령어(또는 메서드)를 포함합니다. 간단한 예제를 통해 EvoX의 기본 사용법을 배우게 됩니다.

시작 및 초기화

설치를 확인한 후 EvoX를 사용하여 최적화 스크립트를 작성할 수 있습니다. 모든 Python 환경(터미널, Jupyter Notebook, IDE 등)에서 EvoX를 임포트할 수 있습니다.

먼저 EvoX와 관련 모듈을 임포트하고 간단한 최적화 작업을 초기화해 보겠습니다. 예를 들어, 입자 군집 최적화(PSO) 알고리즘을 사용하여 고전적인 Ackley 함수를 최적화합니다. Ackley 함수는 ((0,0,\dots,0))에서 알려진 전역 최적값을 가진 일반적인 벤치마크 함수로, 시연에 적합합니다. 다음은 최적화를 시작하고 실행하는 방법을 보여주는 최소한의 EvoX 예제 코드입니다:

import torch
from evox.algorithms import PSO                      # PSO 알고리즘 임포트
from evox.problems.numerical import Ackley           # Ackley 최적화 문제 임포트
from evox.workflows import StdWorkflow, EvalMonitor  # 표준 워크플로우 및 모니터 임포트

# 1. 최적화 알고리즘과 문제 정의
algorithm = PSO(
    pop_size=50,                    # 개체군 크기 50
    lb=-32 * torch.ones(2),         # 결정 변수 하한: 2D 벡터, 각각 -32
    ub= 32 * torch.ones(2)          # 결정 변수 상한: 2D 벡터, 각각 32
)
problem = Ackley()                  # 최적화 문제: Ackley 함수 (기본 차원은 알고리즘과 일치)

# 2. 워크플로우 조립 및 결과 추적을 위한 모니터 추가
monitor = EvalMonitor()
workflow = StdWorkflow(algorithm, problem, monitor)

# 3. 워크플로우 초기화
workflow.init_step()  # 알고리즘과 문제의 내부 상태 초기화

# 4. 최적화 반복 실행
for i in range(100):
    workflow.step()   # 최적화를 한 단계 진행

# 5. 결과 얻기 (예: 최적값 출력)
best_fitness = monitor.get_best_fitness() # 모니터에서 최적 적합도 값 가져오기
print("반복 완료, 현재 발견된 최적 적합도 값:", float(best_fitness))

위 코드는 다음 단계를 포함합니다:

  • 먼저 PSO 알고리즘의 파라미터를 설정합니다: 개체군 크기 50, [-32, 32] 범위의 2D 탐색 공간.
  • 그런 다음 Ackley 문제를 정의합니다(Ackley 함수는 기본적으로 2D로 정의됩니다).
  • 알고리즘과 문제를 조립하는 표준 워크플로우 StdWorkflow를 생성하고, 최적화 과정 데이터를 기록하기 위한 모니터 EvalMonitor를 전달합니다.
  • 다음으로 workflow.init_step()을 사용하여 초기화 과정을 완료합니다. 이는 자동으로 개체군, 랜덤 시드 및 기타 내부 상태를 초기화합니다.
  • 그런 다음 루프를 실행하여 workflow.step()을 사용하여 100번의 반복을 연속적으로 실행합니다. step()이 호출될 때마다 알고리즘은 새로운 솔루션을 생성하고 적합도를 평가하며, 지속적으로 최적 솔루션에 접근합니다.
  • 마지막으로 모니터가 제공하는 get_min_fitness() 메서드를 사용하여 반복 과정에서 최적 적합도 값을 얻고 출력합니다.

이 스크립트를 실행하면 최적화 반복의 출력을 볼 수 있습니다. 예를 들어:

반복 완료, 현재 발견된 최적 적합도 값: 9.5367431640625e-07

루프에서 중간 결과를 명시적으로 출력하지 않았으므로 중간 결과는 표시되지 않습니다. 그러나 최종 적합도 값을 기반으로 알고리즘이 수렴했는지 판단할 수 있습니다. 예를 들어, Ackley 함수의 최적값은 0이며, 출력이 0에 가까우면 PSO가 전역 최적에 가까운 솔루션을 찾았음을 나타냅니다. print(monitor.history)를 호출하여 모니터가 기록한 이력 데이터를 보거나 monitor.plot()을 사용하여 수렴 곡선을 그릴 수도 있습니다(Plotly와 같은 시각화 지원 필요).

참고: StdWorkflow는 EvoX가 제공하는 표준 최적화 프로세스 캡슐화입니다. 내부적으로 전통적인 진화 알고리즘에서 발견되는 “초기화-반복 업데이트” 로직을 구현하고 알고리즘과 문제 간의 상호작용을 캡슐화합니다. 대부분의 간단한 응용에서는 StdWorkflow를 직접 사용하면 충분합니다. EvalMonitorget_best_fitness()plot()과 같은 메서드를 구현하여 최적화 과정에서 성능 지표를 수집하고 표시하는 모니터입니다. 초보자는 이를 나중에 분석하기 위해 각 반복의 최적 결과를 기록하는 기록부로 이해할 수 있습니다.

위의 예제에서 알고리즘 선택, 문제 정의, 워크플로우 조립을 포함하는 EvoX 프로젝트의 기본 구성을 만들었습니다. 일반적으로 EvoX 프로젝트를 구성하는 것은 다음 단계를 포함합니다:

  1. 최적화 문제 선택/정의: 어떤 최적화 문제를 해결하려는지 명확히 하세요. 예를 들어, 수학 함수를 최적화하는 경우 EvoX는 evox.problems 모듈 아래에 많은 내장 문제(예: Sphere, Rastrigin, Ackley와 같은 고전적 함수)를 제공하여 직접 사용할 수 있습니다. 내장 문제에 포함되지 않은 경우 직접 정의할 수 있습니다(이후 장에서 다룹니다). 문제를 구성할 때 일반적으로 결정 변수의 차원값 범위를 알아야 합니다.

  2. 최적화 알고리즘 선택/구성: 문제 유형에 따라 적절한 진화 알고리즘을 선택하세요. EvoX는 evox.algorithms 아래에 단일 목적 알고리즘(PSO, GA, CMA-ES 등)과 다목적 알고리즘(NSGA-II, RVEA 등)을 포함한 풍부한 알고리즘 세트를 제공합니다. 알고리즘을 선택한 후 일반적으로 개체군 크기(pop_size)와 알고리즘별 파라미터(GA의 교차 확률 및 돌연변이 확률 등)를 설정해야 합니다. 대부분의 알고리즘은 개체군을 초기화하기 위해 변수 범위(하한 lb 및 상한 ub)와 문제 차원이 필요합니다. 다목적 알고리즘을 사용하는 경우 목적 수(n_objs)도 지정해야 합니다. EvoX의 알고리즘은 일반적인 하이퍼파라미터에 대한 기본값을 제공하지만, 초보자는 더 나은 성능을 위해 작업에 따라 이러한 파라미터를 조정하는 것을 고려해야 합니다.

  3. 워크플로우 조립: 알고리즘과 문제 인스턴스가 준비되면 전체 최적화 프로세스 제어를 나타내는 워크플로우로 “조립”해야 합니다. EvoX에서는 일반적으로 StdWorkflow를 사용하여 알고리즘과 문제를 결합합니다. 최적화 진행 상황을 모니터링하려면 워크플로우에 모니터(EvalMonitor 등)를 추가할 수 있습니다. 모니터는 필수가 아니지만 디버깅과 분석 중에 매우 유용할 수 있습니다. 워크플로우 조립은 일반적으로 한 줄의 코드로 이루어집니다: workflow = StdWorkflow(algo, prob, monitor).

  4. 초기화: 워크플로우의 초기화 메서드를 호출하여 최적화를 시작합니다. 최신 버전의 EvoX는 한 번의 호출로 초기화 과정을 완료하는 편리한 StdWorkflow.init_step() 메서드를 제공합니다.

  5. 반복 실행: 루프를 사용하여 workflow.step()을 반복적으로 호출하여 진화 과정을 진행합니다. 각 호출은 알고리즘 내부에서 “새 솔루션 생성 -> 평가 -> 선택”과 같은 단계를 포함하는 하나의 반복을 수행합니다. 반복 중에 모니터를 사용하여 실시간 결과를 관찰할 수 있습니다. 예를 들어 몇 세대마다 현재 최적 적합도를 출력합니다. 종료 기준은 필요에 따라 설정할 수 있습니다. 일반적인 것으로는 고정된 세대 수(예: 100세대 실행) 또는 모니터링된 지표가 수렴할 때 중지(예: 여러 세대에 걸쳐 유의미한 개선이 없을 때)가 있습니다.

  6. 결과 얻기: 반복이 끝나면 알고리즘에서 최종 결과를 추출해야 합니다. 예를 들어 최적 솔루션과 그 목적 값입니다. EvoX에서는 일반적으로 모니터를 통해 얻습니다. 예를 들어, EvalMonitor.get_best_fitness()는 최적 적합도 값을 반환합니다. 최적 솔루션 벡터를 얻으려면 문제 객체가 평가 중에 최적 후보를 저장하게 하거나 모니터의 인터페이스를 사용하는 방법이 있습니다. EvoX의 표준 구현에서 EvalMonitor는 각 세대의 최적 개체와 적합도를 기록하며, 그 속성을 통해 접근할 수 있습니다. monitor.history가 이력을 저장한다고 가정하면 마지막 세대에서 최적 개체를 검색할 수 있습니다. 물론 EvalMonitor를 건너뛰고 루프 후에 알고리즘 객체를 직접 쿼리할 수도 있습니다. 이는 알고리즘 구현에 따라 다릅니다. 사용자 정의 알고리즘이 get_best()를 구현하거나 상태에 최적 개체를 저장하는 경우 직접 추출할 수 있습니다. 그러나 EvoX는 순수 함수와 모듈성을 강조하므로 결과는 일반적으로 모니터링 모듈을 통해 접근합니다.

이러한 단계를 따르면 최적화 작업 코드를 명확하게 구조화할 수 있습니다. 초보자에게는 알고리즘-문제-워크플로우 삼중 구조가 어떻게 함께 작동하는지 이해하는 것이 중요합니다: 알고리즘은 솔루션 생성과 개선을 처리하고, 문제는 품질을 평가하며, 워크플로우는 이들을 반복 루프로 연결합니다.

다음으로 최적화 프로세스에 대한 이해를 깊게 하기 위해 EvoX에서 사용할 수 있는 기본 명령어와 함수 호출을 소개합니다.

기본 명령어 개요

EvoX를 사용할 때 익숙해져야 할 일반적으로 사용되는 메서드와 함수가 있으며, 이들은 “명령어” 역할을 합니다:

워크플로우 관련 메서드

  • StdWorkflow.init_step(): 초기화. 최적화 프로세스를 시작하기 위한 빠른 시작 명령어로, 스크립트 시작 부분에서 자주 사용됩니다. 알고리즘과 문제 모두의 초기화 로직을 호출하고, 초기 개체군을 생성하며, 적합도를 평가합니다. 이후 워크플로우는 초기 상태를 포함하며 반복할 준비가 됩니다.

  • StdWorkflow.step(): 최적화를 한 단계 진행합니다. 각 호출은 알고리즘이 현재 개체군 상태를 기반으로 새로운 후보 솔루션을 생성하고, 평가하며, 다음 세대를 선택하게 합니다. 사용자는 일반적으로 루프 내에서 이를 여러 번 호출합니다. step() 함수는 일반적으로 아무것도 반환하지 않습니다(내부 상태는 워크플로우 내에서 업데이트됩니다). 초보자는 반환 값에 대해 걱정하지 않고 간단히 호출할 수 있습니다.

모니터 관련 메서드

EvalMonitor를 예로 들면, 일반적인 메서드는 다음과 같습니다:

  • EvalMonitor.get_best_fitness(): 기록된 최저 적합도(최소화 문제의 경우) 또는 최고 적합도(최대화 문제의 경우; 모니터가 일반적으로 이를 구분합니다)를 반환합니다. 현재 최적 결과를 알기에 유용합니다.
  • EvalMonitor.get_history() 또는 monitor.history: 각 세대의 최적값과 같은 전체 이력을 검색합니다. 수렴 추세를 분석하는 데 유용합니다.
  • EvalMonitor.plot(): 수렴 또는 성능 곡선을 그립니다; 그래픽 환경 또는 Notebook이 필요합니다. 모니터는 일반적으로 Plotly를 사용하여 그래프를 렌더링하여 알고리즘 성능을 시각적으로 평가하는 데 도움을 줍니다. 내부적으로 모니터는 각 세대의 평가 횟수와 결과를 기록합니다. 일반적으로 개입할 필요 없이 필요할 때 데이터를 추출하면 됩니다.

알고리즘 관련 메서드

  • Algorithm.__init__() 메서드: 알고리즘의 초기화 메서드입니다. 변수는 일반적으로 evox.core.Mutable()을 사용하여 래핑하고 하이퍼파라미터는 evox.core.Parameter()로 래핑합니다.

  • Algorithm.step() 메서드: 특정 시나리오에서 또는 사용자 정의 알고리즘/문제를 사용할 때 알고리즘의 step() 메서드를 직접 호출할 수 있으며, 이는 일반적으로 알고리즘의 전체 반복 로직을 캡슐화합니다.

  • Algorithm.init_step() 메서드: init_step() 메서드는 알고리즘의 첫 번째 반복을 포함합니다. 오버라이드되지 않으면 단순히 step() 메서드를 호출합니다. 일반적인 경우 첫 번째 반복은 다른 반복과 다르지 않으므로 많은 알고리즘이 사용자 정의 init_step()이 필요하지 않을 수 있습니다. 그러나 하이퍼파라미터 조정을 포함하는 알고리즘의 경우 여기서 하이퍼파라미터나 관련 변수를 업데이트해야 할 수 있습니다.

장치 및 병렬 제어

  • .to(device) 메서드: 프로그램에서 계산 장치를 전환해야 하는 경우 PyTorch의 .to(device) 메서드를 사용하여 텐서(torch.Tensor)를 GPU/CPU로 이동합니다(torch.randn과 같은 일부 PyTorch 메서드도 장치를 지정해야 합니다). 일반적으로 torch.set_default_device()를 사용하여 장치를 cuda:0으로 설정하면(시스템이 지원하고 EvoX와 종속성이 올바르게 설치되었다고 가정 — torch.cuda.is_available()로 확인) 대부분의 EvoX 고성능 병렬 계산이 자동으로 GPU에서 실행됩니다. 사용자 정의 알고리즘, 문제 또는 모니터를 작성할 때 새 텐서를 생성하거나 장치에 민감한 PyTorch 메서드를 사용하는 경우 devicecuda:0으로 명시적으로 지정하거나 torch.get_default_device()를 사용하여 다른 장치에 분산된 계산으로 인한 성능 저하를 방지하는 것이 좋습니다.

초보자에게는 위의 메서드만으로도 일반적인 최적화 작업을 처리하기에 충분합니다. 요약하면: 문제/알고리즘 초기화 — 모니터 설정 — 워크플로우 조립 — 실행 및 결과 검색이 가장 일반적인 EvoX 워크플로우입니다. 이를 마스터하면 EvoX를 사용하여 기본적인 최적화 작업을 처리할 수 있습니다.

다음 장으로 넘어가기 전에 예제를 수정해 보세요: PSO를 다른 알고리즘으로 전환하거나, Ackley 함수를 다른 테스트 함수로 교체하거나, 모니터를 사용하여 더 많은 정보를 추출해 보세요. 이렇게 하면 EvoX 프로젝트 구성의 유연성을 체감할 수 있습니다.