스마트빌딩 ① — 한 줄로 31%: HVAC 제어 대회 공략 계획
건물 HVAC을 5분마다 제어하는 대회를 시작했습니다. 라벨을 예측하는 대회가 아니라 정책 하나를 노트북으로 제출하면 시뮬레이터가 30개 시나리오를 30일씩 돌려 점수를 매깁니다. 공식 베이스라인 중 가장 좋은 것이 로컬 0.05414, 상금권(8등) 컷이 0.01141이니 4.7배 개선이 목표입니다.
하루를 조사와 실측에 썼고, 가장 큰 개선은 코드 한 줄에서 나왔습니다. 점수를 0.05414 → 0.03753(−30.7%)로 내렸습니다. 그런데 그게 효과가 난 이유가 제가 예상한 것과 완전히 달랐습니다.
대회 진행 중이라 정책 코드와 대회 데이터는 싣지 않습니다. ‘실측’은 공개된 스타터킷 시뮬레이터로 다른 조건을 그대로 두고 한 가지만 바꿔 30일 전체 롤아웃을 돌려 비교한 값입니다. 확인하지 못한 것은 그렇다고 적었습니다.
1. 점수가 어떻게 생겼는가
매 스텝 보상은 세 항의 가중합이고 전부 패널티입니다.
\[r_t = -\left(0.6\,p_t + 0.2\,c_t + 0.2\,e_t\right),\qquad r_t \le 0\]$p$는 쾌적 regret, $c$는 에너지 비용, $e$는 탄소입니다. 최종 점수는 시나리오별 평균 보상의 절댓값 평균이라 낮을수록 좋습니다.
쾌적항의 생김새가 전략을 정합니다. 존 온도가 쾌적 밴드를 $\delta$만큼 벗어나면
\[\text{shortfall} = \begin{cases} \delta/5 & (\delta \le 5\text{K}) \\ 1 + \left(\frac{\delta-5}{5}\right)^2 & (\delta > 5\text{K})\end{cases}\]이고, 5K를 넘으면 상한 없이 2차로 발산합니다. 그리고 소스를 읽어보니 정규화 상수의 최솟값이 0이라 쾌적항은 정확히 재실자 가중 ‘평균’ shortfall로 떨어집니다. 총 재실자 수가 약분된다는 뜻이고, 실제로 건물이 거의 빈 시간대가 꽉 찬 시간대보다 스텝당 패널티가 더 큽니다(한 시나리오에서 3명 미만 스텝 0.919 vs 15명 이상 스텝 0.321).
현 위치는 이렇습니다.
| 점수 | 비고 | |
|---|---|---|
| bang_bang | 0.22520 | |
| pid | 0.06736 | |
| fixed_setpoint | 0.06592 | |
| pid_water (최선 베이스라인) | 0.05414 | 로컬 30 시나리오 |
| 시나리오별 최선을 고르는 오라클 | 0.04728 | 조합만으로는 여기가 끝 |
| 공개 노트북 계보 천장 | 0.05195 | 4팀 동점(46~49위) |
| 공개 LB 중앙값 | 0.04532 | |
| 상금권(8등) 컷 | 0.01141 | 71팀 기준 |
| 공개 LB 1위 | 0.00608 | 제출 2회 |
2. 점수를 쪼개면 할 일이 정해진다
pid_water의 30 시나리오를 항별로 분해했습니다.
| 항 | 값 | 비중 |
|---|---|---|
| 쾌적 | 0.03425 | 63.3% |
| 에너지 비용 | 0.01138 | 21.0% |
| 탄소 | 0.00851 | 15.7% |
여기서 두 가지가 바로 나옵니다.
- 에너지와 탄소를 전부 0으로 만들어도 쾌적 0.03425가 남아 상금권 컷의 3배입니다.
- 쾌적을 완벽히 0으로 만들어도 에너지+탄소 0.01989가 남아 역시 컷보다 나쁩니다.
즉 어느 한쪽만 공략하는 경로는 산술적으로 존재하지 않습니다. 이걸 먼저 계산해 둔 덕에 “에너지를 아끼는 아이디어” 여러 개를 측정 전에 걸러낼 수 있었습니다.
3. 가장 큰 레버는 한 줄이었다 — 그런데 기전이 달랐다
AHU 급기 정압(static pressure) 지령을 베이스라인처럼 변조하지 않고 하한에 고정했습니다. 액션 한 줄입니다.
| 30 시나리오 점수 | |
|---|---|
| pid_water | 0.05414 |
| 정압 하한 고정 | 0.03753 |
| (참고) 베이스라인 오라클 | 0.04728 |
30개 중 17개 개선 / 13개 악화인데 최대 이득 −0.1296, 최악 손실 +0.0063으로 비대칭이 압도적입니다. 가장 나빴던 두 시나리오가 이렇게 움직였습니다.
| 시나리오 | before | after |
|---|---|---|
| fairbanks_1 | 0.21168 | 0.08211 |
| fairbanks_8 | 0.07723 | 0.05508 |
제가 세웠던 가설은 “팬 동력을 줄여 에너지항을 깎는다”였습니다. 팬 전력은 유량 × 정압 / 효율이고 VAV 유량은 댐퍼 × 최대유량 × √(P/2000)이라, 댐퍼를 고정하면 전력이 정압의 1.5제곱으로 들어가니까요.
틀렸습니다. 이득의 대부분은 에너지가 아니라 쾌적이었습니다. 한 시나리오에서 이렇게 나왔습니다.
| pid_water | 정압 고정 | |
|---|---|---|
| 재실 스텝 중 5K 절벽 진입 비율 | 74.4% | 0.0% |
| 최대 밴드 이탈 | 9.28K | 3.36K |
| 평균 존 온도 | 24.15℃ | 24.31℃ |
평균 온도는 거의 안 움직였는데 절벽만 사라졌습니다. 원인은 베이스라인의 정압 스케줄이었습니다. 존이 밴드를 벗어나면 정압을 최대로 때리고, 그 과도한 풍량이 존을 밴드 반대편으로 관통시키고, 다시 반대로 때립니다. 발진입니다. 정압을 고정한 건 에너지 절약이 아니라 발진을 멈춘 것이었습니다.
추운 기후에서 풍량 부족으로 쾌적이 무너질 거라는 제 반박도 데이터가 기각했습니다. 위 표의 fairbanks 두 개가 그 답입니다.
정압 고정 뒤 점수 구성은 이렇게 바뀝니다.
| 항 | 정압 고정 후 비중 |
|---|---|
| 쾌적 | 77.8% |
| 에너지 | 11.3% |
| 탄소 | 10.9% |
남은 길은 거의 전부 쾌적입니다. 여기서 에너지+탄소(0.0083)를 전부 0으로 만들어도 0.0292라 컷의 2.6배입니다.
4. 레버 우선순위
| 순위 | 레버 | 무엇을 어떻게 | 근거 | 상태 |
|---|---|---|---|---|
| 1 | 정압 하한 고정 | AHU 정압 액션을 변조하지 않고 하한 고정 | 30 전수 −0.0166 | 확정 |
| 2 | 총 풍량 축소 | 정압은 그대로 두고 댐퍼 지령에 스케일(≈0.55) | 2개 시나리오 −65%, −62%(단축 롤아웃) | 유망, 전수 필요 |
| 3 | 냉방 최소 가동시간 | 냉방으로 켜진 AHU를 최소 288스텝 유지(난방은 제외) | 제3자 전수 0.05493→0.03791 | 중복 가능, 재측정 |
| 4 | 존별 PI 제어 | 존마다 독립 적분기로 밴드 중심 추종, $u$ 하나로 댐퍼와 재열을 동시 구동, 반포화 조건부 적분 | 공개 코드 복제는 0.05195에 착지하나 구조 자체는 남은 쾌적 손실의 유일한 후보 | 미측정 |
| 5 | 급기온도 분위수 리셋 | AHU 급기온도를 ‘최악 존’이 아니라 담당 존들의 하위 분위수로 | 더운 기후에서 동시 냉난방 스텝 24.9% 관측 | 미측정 |
| 6 | 온화기후 한정 밴드 마진 | 전역 상수 금지, AHU 포화 여부로 게이팅 | 온화 5개 평균 −0.0051 / 더운 5개 +0.0163 | 조건부 |
1번과 2번은 둘 다 풍량을 줄이는 레버라 합이 선형이 아닙니다. 200 Pa에서 댐퍼를 100% 열어도 최대 유량이 설계치의 31.6%가 상한이라 “정압만 낮추고 풍량은 보전”이 구조적으로 불가능합니다. 그래서 두 개를 따로 스윕하지 않고 2차원 격자로 묶어야 합니다.
3번은 1번과 같은 병증(AHU가 하루 50~107회 켜고 끄는 채터링)을 치므로 중복일 가능성이 높습니다. 각각이 아니라 상호작용을 재는 게 핵심입니다.
5. 버릴 것
측정으로 죽은 것과 구조적으로 죽은 것을 나눠 적습니다.
실측으로 반증된 것
| 아이디어 | 결과 | 왜 틀렸나 |
|---|---|---|
| 비점유 시간 전면 셧다운 | 10 시나리오 쌍대 +0.0312(9/10 악화) | 재실 0이면 쾌적 패널티가 하드코딩 0인 건 맞다. 그러나 야간 상태가 아침의 초기조건이다. 야간 eco 밴드는 채점자가 공짜로 주는 예비조절이고, 끄면 아침에 2차 절벽으로 들어간다. 더운 기후는 쾌적뿐 아니라 에너지까지 올랐다(따라잡기) |
| 밴드를 0.5K 좁혀 중앙 조준 | +0.0138(손실이 이득의 6.4배) | 제거 가능한 정상상태 오차가 아니라 오버슛이다. 목표를 안쪽으로 당기면 풍량만 늘고 오버슛이 커진다. 유량 배급(starvation)은 측정상 정확히 0회 |
| worst-zone 집계로 AHU 구동 | +0.0249(8/10 악화) | 도달 불가능한 한 존을 쫓느라 AHU가 전 에피소드 최대 effort에 고정된다. 에너지가 3배가 되는데 쾌적은 오히려 나빠진다 |
| 온수 setpoint 펄스(회계 구멍) | 점수 +22%, 청구/전달 비 1.046→1.075 | 구멍은 코드에 실재한다. 그러나 탱크 방출 상한이 10.45 kW인데 실제 재열 부하가 약 47 kW다. 저수조가 너무 작다 |
산술·물리로 닫힌 것
| 아이디어 | 왜 닫혔나 |
|---|---|
| 요금 시간대 부하 이전(TOU) | 정규화 분모가 분자와 같은 시각으로 가격된다. 피크→심야 이전의 한계 패널티 비가 1.0755, 전기 20¢→16¢ 이전의 정규화 비용 절감은 20%가 아니라 5.4%. 가스는 월별 단가뿐이라 에피소드 내 차익 0 |
| 아침 복구 스파이크 평탄화 | 시간대별 쾌적 비용이 평탄하다(06시 0.00280 vs 08~18시 0.00257~0.00271). 한랭지에만 진짜 스파이크가 있고 기후 게이팅을 해도 상한 0.00086 |
| 한랭지 AHU 최소가동 + 재열 전담 난방 | 재열 열량이 풍량에 비례하고 토출 온도 상한(308.15K)이 걸려 있다. AHU를 끄면 정압 지령이 0이 되어 열 전달 경로 자체가 사라진다. ‘최소 풍량’과 ‘충분한 재열’이 같은 다이얼이다 |
| 꼬리 시나리오만 고치기 | 시나리오 가중이 1/30이라 최악 10개를 전부 중앙값으로 끌어와도 0.0323 |
| 정규화 포화를 노린 에너지 폭식 | 정규화 상수는 클리핑이 아니라 분모다. 실측 정규화 탄소 1.0762(>1) 확인 |
| 같은 시나리오 반복 롤아웃 | 시뮬레이터가 결정론이다(재실 시드·대류 시드 고정, 기상은 결정론적 보간). 공식 baseline 값과 소수점까지 일치 확인 |
| 외부 건물에너지 공개 데이터셋 | 존별 댐퍼·재열밸브·급기온도 텔레메트리와 대응 행동 공간이 없다. 게다가 우리는 평가 분포의 생성기를 통째로 갖고 있다 |
| 스텝당 시계열 파운데이션 모델 | 예산이 없다(아래 9절). 예측 대상인 외기온은 시각별 기후값만으로 RMSE 2.3K |
| 기상 지문 매칭으로 예보 만들기 | 기술적으로는 가능하다(외기온 2시간이면 2,004 후보 중 유일 특정, 정답 RMSE 0.0). 그러나 스타터킷이 보류된 config·기상 참조의 하드코딩은 실격 사유라고 명시한다. 게다가 기대 이득이 거의 0이다 |
마지막 항목은 “할 수 있다”와 “해도 된다”가 다른 사례라 따로 적어 둡니다. 안 합니다.
6. 우리 검증 세트가 평가 분포와 다르다
공개 대회 데이터 파일을 전수 열거하다 GitHub 스타터킷에 없는 기상 CSV 5개를 찾았습니다. 월 접미사가 붙은 파일들인데, md5를 비교하니 기존 관측소 CSV와 바이트 단위로 동일했습니다. 내용은 같고 새 정보는 파일명의 월(月)뿐입니다. 그 명명 규칙이 평가 게이트웨이 소스의 예시 시나리오 이름과 정확히 일치합니다.
여기서 나오는 추론(사실이 아니라 추론입니다)은 평가 기상 5종이 애틀랜타 5월 / 덴버 4월 / 페어뱅크스 1월 / 미니애폴리스 1월 / 피닉스 7월이고 싱가포르는 빠진다는 것입니다. 이게 맞다면 우리 베이스라인 0.05414의 출처인 출하 30 시나리오는 편향된 대리표본입니다.
| 출하 30 시나리오 | 추정 평가 분포 | |
|---|---|---|
| 싱가포르 | 5개(평균의 1/6) | 0개 |
| 미니애폴리스 | 0개 | 1/5 |
| 한겨울 regime | 일부 | 2/5 |
미니애폴리스는 출하 시나리오가 하나도 없어 아무도 측정해 본 적이 없는 regime입니다. 직접 시나리오를 만들어 돌려보니 0.08952가 나왔고, 제가 가진 31개 측정 중 네 번째로 나쁩니다. 분해하면 쾌적 40.6% / 탄소 37.4% / 에너지 21.9%로 평소와 구성도 다릅니다.
그리고 평가의 40%가 한겨울이라면 급기온도 액션이 과반 이상 죽어 있는 regime입니다. 외기가 −6.8℃ 아래면 혼합공기가 이미 최저 지령값보다 차가워서 AHU 급기온도 설정이 무효가 되는데, 페어뱅크스 1월은 그 비율이 88.2%, 미니애폴리스 1월은 56.4%입니다. 제어 수단이 재열밸브와 온수로 좁아집니다.
7. 시나리오는 무한히 만들 수 있다 — 전략이 바뀐다
스타터킷에 건물 생성기와 기상 생성기가 전부 들어 있고, 공식 README가 평가 건물이 “같은 생성기·같은 파라미터 범위”에서 나온다고 명시합니다. 실제로 돌려서 확인했습니다.
| 확인한 것 | 결과 |
|---|---|
| 출하 건물 재생성 | floor_plan md5 완전 일치 — 킷의 생성기가 출하본을 만든 그 코드다 |
| 신규 건물 + 신규 기상 윈도 롤아웃 | 정상 완료(6 AHU / 55 존) |
| 생성 비용 | 시나리오당 약 1초 |
| 시드 수용률 | 7/16(존 최소면적 검사에서 탈락) |
| 만들 수 있는 기상 스트립 | 관측소당 약 335개 시작일 → 약 2,000개 |
이게 전략을 네 군데 바꿉니다.
- 검증 세트를 평가 분포로 복제할 수 있다. 출하 30개를 그대로 쓰는 대신 평가 5개 station-month로 다시 짠다.
- 제출 할당을 아낄 수 있다. 무제한 로컬 평가가 있으니 제출은 로컬↔LB 보정용으로만 쓴다.
- 표준오차를 설계할 수 있다. 시나리오 간 표준편차가 0.04482라 30개 평균의 SE가 0.0082인데, 상금권 1~8위 전체 폭이 0.0053입니다. 30개로는 순위를 구분할 수 없습니다. 300개면 SE 0.0026까지 내려갑니다.
- 꼬리를 과표집할 수 있다. 다만 재질로 꼬리를 설계하는 건 생각보다 안 됩니다 — 외피 U값은 고정 필름항 때문에 전 범위에서 2배밖에 안 벌어집니다. 효과가 큰 축은 외피 열용량(로그균등 350~2400)이라 가벼운 쪽을 뽑으면 야간 coasting이 죽습니다.
주의할 모순이 하나 있습니다. 생성기 기본 footprint 범위가 출하 6동의 실제 크기보다 큽니다(킷이 README에서 자인). 평가 건물이 어느 쪽인지 확인 못 했으므로, dev 세트는 출하 범위로 만들되 게이트 세트에 기본 범위 건물을 20% 섞어 두 가설 모두에서 점수를 봅니다.
8. 측정 설계 — 무엇으로 고르고 무엇으로 판정할까
| 단계 | 세트 | 크기 | 쓰임 |
|---|---|---|---|
| 진단 | phoenix_4 · fairbanks_1 · denver_7 | 3 | 기전 확인, 로그 분해. 판정 금지 |
| dev | 출하 6동 × 평가 5 station-month | 30 | 레버 A/B, 격자 스윕 |
| gate | 생성 건물 × 평가 5 regime(+기본범위 20%) | ~300 | 제출 후보 최종 판정 |
| 보정 | 실제 제출 | 소수 | 로컬↔LB 오프셋 측정 |
규칙은 네 가지로 정했습니다.
- 쌍대 비교만 한다. 시뮬레이터가 결정론이라 반복은 무의미하고, 같은 시나리오 목록에서 한 가지만 바꾼 델타를 본다.
- 평균만 보지 않는다. 시나리오별 델타와 최악 퇴행을 같이 본다. 정압 고정이 통과한 이유는 평균이 아니라 “최악 손실 +0.0063”이었다.
- 2~3개 시나리오로 레버를 죽이지 않는다. 공개 노트북이 2개 시나리오로 기각한 아이디어를 우리가 10개로 다시 쟀고, 반대로 2개로 통과시킨 것이 30개에서 흔들렸다.
- 적합 상수가 없는 레버를 선호한다. 정압 하한 고정은 튜닝된 숫자가 없어(액션 범위의 끝값) 과적합 위험이 가장 낮다. 밴드 마진 K, 선행 시간 같은 적합된 스칼라는 과적합 위험이 가장 높아 gate 세트 통과를 요구한다.
공개 LB 33%(약 10 시나리오)는 선택에 쓰지 않습니다. 10개 평균의 SE가 0.0142로 상금권 전체 폭의 2.7배라, 거기서 보이는 변화는 대부분 노이즈입니다. 로컬 0.05414 계열이 LB 0.05104~0.06439로 찍히는 걸 보면 수준은 ±20% 어긋나지만 순위는 보존되므로, LB는 “방향이 뒤집히지 않았는지” 확인용으로만 씁니다.
9. 첫 주에 할 일
server4(20코어)만으로는 모자랍니다. 30 시나리오 1패스가 4.72 코어시간인데 그 박스는 이미 포화 상태였습니다. server3은 다른 대회 전용이라 제외하고 server1·server2에 환경을 깝니다.
| 일자 | 작업 | 산출물 |
|---|---|---|
| 1 | server1·2에 스타터킷 설치 + 결정론 검증(공식 baseline 값 재현) | 약 110코어 확보 |
| 1 | 정압 × 댐퍼 스케일 2차원 격자를 진단 3개로 좁힌 뒤 dev 30 전수 | 1·2번 레버 확정값 |
| 2 | dev 세트 재구성(평가 5 station-month) + pid_water·정압고정 기준선 재측정 | 새 기준선 |
| 2–3 | 냉방 최소 가동시간을 정압 고정 위에 얹어 상호작용 측정 | 중복 여부 판정 |
| 3–4 | 존별 PI를 정압 고정 위에 이식, KP/KI/댐퍼하한만 좌표하강 | 쾌적 레버 1차 |
| 4–5 | 급기온도 분위수 리셋(동시 냉난방 제거) | 더운 기후 전용 |
| 5–6 | gate 세트 ~300 시나리오 생성 + 런타임 예산 실측(최대 건물) | 판정 기준·안전 마진 |
| 7 | 보정 제출 1회(정압 고정 + 생존 레버) | 로컬↔LB 오프셋 |
GPU는 쓸 데가 없습니다. 제출이 CPU 전용인 건 물론이고, 병목이 전부 시뮬레이터 롤아웃(코어 수)입니다. A5000 두 장이 놀고 있지만 이 대회에서 GPU가 임계경로에 오는 경로를 아직 못 찾았습니다.
- 축소 열모델은 존당 9파라미터 선형회귀로 충분합니다(1일치 데이터 학습 → 6시간 개루프 예측 오차 중앙값 0.568K, 밴드 폭이 3K). GPU가 필요 없습니다.
- 정책을 신경망으로 학습(RL)해도 롤아웃이 CPU 병목이라 GPU는 대기합니다.
- 유일하게 정당화될 여지는 수천 개 파라미터 조합을 배치 평가하는 GPU 대리 시뮬레이터인데, 대리 모델이 진짜 시뮬레이터와 갈라지면 잘못된 최적점을 고릅니다. 1주차에는 하지 않습니다.
10. 제출이 통째로 날아가는 경로를 먼저 막는다
게이트웨이 소스를 읽고 안전장치를 설계 제약으로 못박았습니다.
| 위험 | 결과 | 대응 |
|---|---|---|
| 행동 범위 초과·형상 불일치 | INVALID_SUBMISSION | 모든 출력 clip + 형상 단언 |
| 건물이 행동 거부 | 보상 −∞, 즉시 중단 | 제약(최소 정압·댐퍼 하한·재순환 상한) 내부에서만 지령 |
| NaN | 경계검사를 전부 통과해 들어가 이후 모든 보상을 오염 | 최종 출력 전 NaN 가드 + 규칙기반 폴백 |
| 런타임 초과 | 런 종료 | 큰 건물에서 정책 ≤ 20 ms/step을 설계 제약으로 |
마지막 항목의 근거: 9시간 / 259,200스텝 = 평균 125 ms/step인데 시뮬레이터 자체가 22.5~119.3 ms/step을 먹고 그 시간이 참가자 할당에 과금됩니다. 가장 큰 건물(9 AHU / 85 존)에서는 여유가 5.7 ms/step밖에 없습니다. 참고로 베이스라인 정책의 get_action은 0.535 ms/step입니다. 턴당 하드 캡은 60초, 시나리오 첫 턴만 15분이라 시나리오당 1회 적합은 허용됩니다.
11. 확인 못 한 것
판단에 영향이 큰 순서로 적어 둡니다.
- 평가 시나리오가 30개인지 48개인지. 대회 페이지는 30이라 하는데 게이트웨이 소스 주석은 “48 scenario sweep”이라고 씁니다. 48이면 런타임 예산 계산이 전부 바뀝니다.
- 평가 기상 5종 추정이 맞는지. 파일 존재와 md5 일치는 사실이고, 그로부터의 구성 추론은 추론입니다.
- 평가 건물 footprint 범위. 킷이 자기모순입니다.
- 공개 LB 33%가 어느 10개인지. 호스트 공개 없음.
- 상위권 기법. 1·2위는 노트북 비공개, Discussion 토픽 3개에 호스트 답변 0건. 공개된 최고가 0.05195인데 31개 팀(44%)이 이미 0.0192 이하입니다. 공개 지식과 실제 전선 사이에 2.7배 격차가 있습니다.
12. 남는 생각
하루에서 가장 값나간 건 코드가 아니라 분해였습니다. 쾌적·에너지·탄소를 항별로 쪼개 놓으니 “에너지를 아끼는 아이디어”로 상금권에 갈 수 없다는 걸 롤아웃 없이 알 수 있었고, 측정해 봤자 소용없는 레버 여섯 개를 먼저 지울 수 있었습니다.
그리고 통과한 한 줄도 제가 생각한 이유로 통과한 게 아니었습니다. 팬 동력을 줄이려고 넣었는데 실제로는 제어 발진을 멈춘 것이었고, 그걸 로그로 확인하지 않았다면 “에너지 레버가 먹혔다”고 오독한 채 같은 방향으로 더 밀었을 겁니다.
효과가 났다는 것과 왜 났는지는 따로 확인해야 합니다.