Sim-to-Real: Model Uncertainty와 Action Delay
학습 목표
시뮬레이터에서 좋은 정책도 물리 계수와 지연이 달라지면 실패할 수 있습니다. 실제 장치로 옮기기 전에 이 차이를 수치로 확인합니다.
실행 조건
- 필요 패키지: gymnasium·stable-baselines3·PyTorch.
- SB3 정책:
MlpPolicy. - Gymnasium
step: 5개 반환값. - 공개 환경 시각화:
gymnasium[classic-control]추가.
필요한 패키지가 없으면 Colab·Jupyter 코드 셀에서 %pip install control==0.10.2 gymnasium==1.3.0 stable-baselines3==2.9.2를 실행하세요.
적용 사례
구동계의 추종 성능과 입력 비용
각 실험에서 일정한 Reference Angular Velocity를 추종하는 합성 1차 Motor 모델입니다. 학습은 목표 1, 평가는 0.5·1·1.5에서 구분합니다.
Observation·State: Angular Velocity , 목표 .
행동: 연속 , 실제 입력 .
Reward·Constraint: . 에서 종료, 100 step에서 시간 제한.
비교 기준: 동일 입력 제한의 P 제어.
확인할 지표: 추종 RMSE, 평균 u², 안전 종료 수. 목표별 분리 평가.
공조·열 공정 제어
온도 편차와 에너지 사용을 함께 줄이는 제어 문제의 설계 사례입니다.
Observation·State: 온도·목표·외기 조건·최근 입력. 지연이 크면 이력 포함.
행동: 제한된 연속 가열·냉각 출력.
Reward·Constraint: 온도 오차와 에너지 비용. 안전 한계는 보상과 별도 지표로도 기록.
비교 기준: 온도 조절기, PI 제어.
확인할 지표: 오차, 에너지, 한계 위반 시간. 외기 조건별 분리 평가.
명목 모델에서의 기준 제어
시작 조건
Motor 기준 모델과 P 제어입니다. Reference Angular Velocity 0.5·1·1.5를 비교합니다.
진행 방법
- 모델의 입력 제한·시간 간격·관측 단위를 적고 기준 제어를 실행하세요.
- 목표별 RMSE·입력 비용·Return을 비교하세요.
남길 결과
- 목표별 평가 표와 대표 추종 곡선.
합성 모델에서 인 Action은 인 모델 입력에 대응합니다. 의 물리 단위는 지정하지 않았습니다.
"""Synthetic motor; not a hardware interface. dt=0.1 s, scalar speed and bounded input."""
import numpy as np
class Motor:
def reset(self, seed=0, reference=1., noise=.01):
self.rng = np.random.default_rng(seed)
self.omega, self.t = 0., 0
self.reference, self.noise = reference, noise
return np.array([self.omega / 5, reference / 2], dtype=np.float32)
def step(self, action):
u = 2 * float(np.clip(np.asarray(action).item(), -1, 1))
self.omega += .1 * (-.4 * self.omega + .8 * u + self.rng.uniform(-self.noise, self.noise))
self.t += 1; error = self.reference - self.omega
reward = -(error**2 + .05 * u**2)
return np.array([self.omega / 5, self.reference / 2], dtype=np.float32), reward, abs(self.omega) > 5, self.t >= 100, {'error': error, 'u': u}
def motor_evaluate(policy, references=(.5, 1., 1.5)):
rows=[]
for reference in references:
env=Motor(); obs=env.reset(100, reference)
errors=[]; inputs=[]; total=0.
while True:
obs,r,terminated,truncated,info=env.step(policy(obs))
errors.append(info['error']);inputs.append(info['u']);total+=r
if terminated or truncated:break
rows.append([reference, np.sqrt(np.mean(np.square(errors))),np.mean(np.square(inputs)),int(terminated),total])
return np.array(rows)
def proportional(obs):return np.clip((2 * obs[1] - 5 * obs[0]), -1, 1)
print('reference / RMSE / mean u² / safety termination / Return')
print(motor_evaluate(proportional))
결과 기록·자가 점검
목표:
RMSE:
평균 u²:
Return:
확인할 점
- Action 범위와 모델 입력 범위의 대응을 구분해 기록했는가?
- Steady-State Error와 과도 오차를 구분했는가?
모델이 틀릴 때 무너지는 지점
시작 조건
Gain .8→.6 또는 입력 지연 0→0.2 s를 하나씩 적용합니다. P 제어와 짧게 학습한 SAC를 같은 조건에서 비교합니다.
진행 방법
- 제공된 StressMotor가 명목 모델에서 바꾼 항만 찾아 표시하세요.
- 명목·Gain 저하·입력 지연의 평가를 실행하고 지표를 비교하세요.
- 각 제어기가 취약한 조건 하나를 찾아 실제 장치에서 측정할 항목과 연결하세요.
남길 결과
- 정책×조건 표와 취약 조건의 추종 그림.
- 기록 위치:
stress_test의 기존while루프, Step 직후. - 보존할 값:
env.t*0.1,env.omega,env.reference. 정책·조건·seed별 구분. - 그림: 표에서 선택한 취약 조건, 기존
seed=100에서 두 정책의 추종 응답과 목표선. - 축: 시간 [s]–Angular Velocity [rad/s]. 기존 평가 기록만 사용합니다.
# Run the Queue or Motor definition first. Install gymnasium and stable-baselines3 if absent.
import gymnasium as gym
from gymnasium import spaces
from stable_baselines3 import A2C, PPO, SAC
class QueueGym(gym.Env):
def __init__(self):
self.model=Queue();self.action_space=spaces.Discrete(3)
self.observation_space=spaces.Box(0.,1.,(1,),dtype=np.float32)
def reset(self,seed=None,options=None):
super().reset(seed=seed)
return np.array([self.model.reset(seed)/20],dtype=np.float32),{}
def step(self,a):
q,r,te,tr,info=self.model.step(a)
return np.array([q/20],dtype=np.float32),r/20,te,tr,info
class MotorGym(gym.Env):
def __init__(self):
self.model=Motor();self.action_space=spaces.Box(-1.,1.,(1,),dtype=np.float32)
self.observation_space=spaces.Box(-np.inf,np.inf,(2,),dtype=np.float32)
def reset(self,seed=None,options=None):
super().reset(seed=seed)
return self.model.reset(seed),{}
def step(self,a):return self.model.step(a)
def fit_sb3(algorithm='PPO',seed=0,steps=2048):
cls={'A2C':A2C,'PPO':PPO,'SAC':SAC}[algorithm]
env=MotorGym() if algorithm=='SAC' else QueueGym()
options={'policy_kwargs':dict(net_arch=[32,32]),'seed':seed,'verbose':0,'device':'cpu'}
if algorithm=='PPO':options.update(n_steps=128,batch_size=64,n_epochs=3)
if algorithm=='A2C':options.update(n_steps=32)
if algorithm=='SAC':options.update(learning_starts=100,buffer_size=5000,batch_size=64)
model=cls('MlpPolicy',env,**options);model.learn(total_timesteps=steps)
return model
motor_model=fit_sb3('SAC')
# Run the Motor baseline first. These are sensitivity tests, not real-robot results.
from collections import deque
class StressMotor(Motor):
def __init__(self,damping=.4,gain=.8,delay=0):
self.damping,self.gain,self.delay=damping,gain,delay
def reset(self,seed=0,reference=1.,noise=.01):
obs=super().reset(seed,reference,noise)
self.pending=deque([0.]*self.delay)
return obs
def step(self,action):
command=2*float(np.clip(np.asarray(action).item(),-1,1))
self.pending.append(command);u=self.pending.popleft()
self.omega+=.1*(-self.damping*self.omega+self.gain*u+self.rng.uniform(-self.noise,self.noise))
self.t+=1;error=self.reference-self.omega
return np.array([self.omega/5,self.reference/2],dtype=np.float32),-(error**2+.05*u**2),abs(self.omega)>5,self.t>=100,{'error':error,'u':u}
def stress_test(policy):
rows=[]
for name,params in [('nominal',{}),('gain loss',dict(gain=.6)),('delay 0.2s',dict(delay=2))]:
for seed in range(100,103):
env=StressMotor(**params);obs=env.reset(seed);errors=[];energy=[];total=0.
while True:
obs,r,te,tr,info=env.step(policy(obs));errors.append(info['error']);energy.append(info['u']**2);total+=r
if te or tr:break
rows.append([name,seed,total,float(np.sqrt(np.mean(np.square(errors)))),float(np.mean(energy)),te])
return rows
for row in stress_test(proportional):print(row)
for row in stress_test(lambda o:motor_model.predict(o,deterministic=True)[0]):print('SAC',row)
결과 기록·자가 점검
정책:
조건:
seed:
Return:
RMSE:
입력 비용:
확인할 점
- 여러 물리 Parameter를 동시에 바꾸지 않았는가?
- 합성 모델의 강건성을 실제 장치 성능으로 주장하지 않았는가?
내 연구의 Reality gap 정하기
시작 조건
앞의 세 조건 중 본인의 연구와 가장 가까운 하나를 선택합니다.
진행 방법
- 식별할 물리 계수·센서 잡음·지연 중 우선순위를 하나 정하세요.
- 명목값·측정할 범위·학습에 사용할 범위·별도 평가 조건을 표로 정하세요.
- 실측 자료가 있다면 추정 방법을, 없다면 필요한 최소 관측을 적으세요.
남길 결과
- Domain randomization 또는 System identification 실험 계획 표.
결과 기록·자가 점검
불확실성:
측정 방법:
학습 범위:
평가 범위:
확인할 점
- 학습·평가 범위를 무조건 같게 두지 않았는가?
- 안전 한계와 실제 장치 적용 전 확인할 지표를 정했는가?