1차 운영 선택 · 2026-08-19
오고 있는 버스 보기
차량 상태 조건부 좌석 분포 · seat_distribution · A18
한 줄 결론 — 지금 오고 있는 그 버스가 목표 정류장에 도착할 때 잔여좌석이 몇 석일지를 분포로 예측하고, 만석 확률은 그 분포의 0 지점으로 읽는다. 다른 후보들이 쓰지 않는 실시간 잔여석을 쓰므로 쓰임이 다르다.
2026-08-19 팀 검토로 1차 운영 모델이 됐다. 그 전(08-17~08-19)에는 02번 「재료 다 넣기」였고, 그 모델은 폐기가 아니라 차량 상태를 쓰지 않는 레인에 남는다. 1차 운영 선택은 운영 적용을 위한 팀 선택이고 성능 입증이 아니다 — 평가는 여전히 잠정이고 일일 채점도 계속된다. 이 모델을 서빙할 v4 계약은 아직 검토 요청 상태다.
이 모델이 나온 배경
앞선 후보 하나는 아침 러시에서 현행 기본보다 6~7% 나빴다. 그것이 채택되지 못한 이유였다. 원인을 찾는 과정에서 세 가지가 드러났다.
- 종합 지표가 반대 방향 효과를 숨긴다. 오전과 오후를 합치면 상쇄되어 무해해 보이고, 휴일과 평일을 합쳐도 마찬가지다. 목표 구간으로 분해하지 않으면 판정을 그르친다. 이 페이지 뒤의 12정류장 종합 점수가 그 사례다.
- 결과를 보기 전에 정한 규칙에도 결함이 있을 수 있다. 층을 남길지 정하는 규칙을 하루치 점추정으로 등록했더니 여섯 층이 남고, 여러 날의 신뢰구간으로 보면 한 층만 남았다. 같은 자료로 정반대다.
- 이론적으로 옳은 제약이 성능으로 이어지지 않을 수 있다. 좌석이 0~정원을 벗어날 수 없다는 것은 자명한데, 모델이 실제로 그 바깥에 두는 확률을 재 보니 정확히 0이었다. 자를 것이 없어서 그 층은 뺐다.
A18 은 그 셋을 고친 결과다. 아침 러시에서 현행 기본보다 21~23% 낫고, 앞선 후보가 실패했던 구간이다.
Q. 이 모델은 무엇을 계산하나?
버스가 목표 정류장에 도착할 때 잔여좌석이 0석일 확률, 1석일 확률, 2석일 확률…을 전부 낸다. 하나의 숫자가 아니라 분포다.
만석 경보는 그 분포의 0 지점을 읽은 것이다. 그래서 "만석이다/아니다"만 답하는 모델과 달리 "만석은 아닌데 두 자리밖에 안 남을 것 같다" 같은 것도 말할 수 있다. 다만 지금 서비스가 파는 것은 만석 경보이고, 이 페이지의 점수도 만석 확률만 채점한 것이다.
Q. 왜 분포로 예측하나?
만석은 표본의 1.7%뿐인 드문 사건이다. 드문 사건만 따로 배우려 하면 표본이 모자란다. 반면 좌석 분포 전체를 배우면 나머지 98.3%도 학습에 쓸 수 있고, 만석은 그 분포의 끝에서 나온다.
다만 0석은 다른 숫자와 성질이 다르다. 0석과 1석의 차이는 1석과 2석의 차이와 같지 않다 — 0석은 "탈 수 없음"이라는 질적 경계다. 그래서 만석 확률만은 분포 끝에서 파생하지 않고 라벨에서 직접 배운다. 이 두 단계를 합치는 구조를 허들이라 부른다.
Q. 원리가 무엇인가?
구조는 관측 하나에서 나왔다 — 지평이 멀어지면 정보의 출처가 옮겨간다.
가장 순진한 방법은 도착 좌석을 곧바로 맞히는 것이다. 그런데 그 값의 분산이 크다. 대신 지금 좌석에서 얼마나 변할지를 맞히면, 2분 뒤라면 변화량의 분산이 절대값 분산의 3.1%다. 이미 아는 정보를 버리지 않는 쪽이 유리하다.
그런데 멀어지면 이게 무너진다. 평일 아침 8정류장을 지나는 동안 좌석이 평균 13.8석 움직인다. 지금 좌석과 도착 좌석의 관계를 자유롭게 추정하면 기울기가 0.434까지 떨어진다 — 절반도 설명하지 못한다.
그 변화를 만드는 것은 정류장이다. 어느 정류장에서 몇 명이 타는지가 좌석을 줄인다. 그래서 정류장의 과거 이력을 함께 쓴다. 위 그림에서 8정류장 지평의 설명력이 차량만으로 0.179 인데 정류장을 더하면 0.442 로 오르는 것이 그 몫이다.
Q. 어떻게 계산하나?
네 단계
지금 좌석에서 출발
지평에 따라 신뢰도 조절
변할까
통과할 정류장들의
평소 수요를 합산
제약
좌석은 0~정원을
벗어날 수 없다
따로
라벨에서 직접 배운
만석 확률을 합침
②의 "통과할 정류장들"이 핵심이다. 8개 정류장을 지난다면 그 8개 각각의 평소 수요를 더한다. 도착 정류장 하나만 보면 나머지 일곱을 무시하는 셈이다.
이 모델이 도는 두 노선
지평·커버율·회차 이야기는 전부 이 제원 위에서 이뤄진다. 값은 상류 기준정보 API
(getBusRouteInfoItemv2 · getBusRouteStationListv2)를 직접 조회해 확정했다.
| 항목 | 1650 | 3330 |
|---|---|---|
routeId | 234000050 | 204000057 |
| 정류장 수 | 89 | 85 |
turnSeq (회차 순번) | 44 | 43 |
| 회차 정류장 | 안양역 · stationId 208000069 — 두 노선 공통 | |
| 기점 (순번 1) | 구리수택차고지 221000046 | 도촌동9단지앞 205000227 |
| 종점 (마지막 순번) | 구리수택차고지 221000047 | 도촌동9단지앞 205000226 |
| 첫차 / 막차 (상행) | 04:10 / 22:35 | 04:50 / 23:30 |
| 첫차 / 막차 (하행) | 05:25 / 23:55 | 05:00 / 23:30 |
| 배차 (첨두 / 비첨두) | 6분 / 15분 | 5분 / 15분 |
| 운수사 | 경기여객 | 대원버스 |
| 노선 종류 | 직행좌석형시내버스 — 입석이 없다 | |
stationId 가 다르고, 두 노선 모두 stationId 중복이 0건이라
정류장은 ID 하나로 유일하게 지목된다.Q. 정류장의 무엇을 기억하나?
(노선, 정류장, 시간대) 조합마다 두 값을 기억한다.
- 평균 점유율 — 그 정류장 도착 시 평균 몇 % 찼는지
- 순수요 — 그 정류장을 지날 때 좌석이 평균 몇 % 줄어드는지
기억하지 않는 것도 있다. 과거 만석률은 쓰지 않는다. 셀의 87%가 만석을 한 번도 겪지 않아서, 그 값이 사실상 "그 셀을 몇 번 관측했나"의 함수가 되기 때문이다 (만석 0건 셀에서 상관계수 1.000). 그리고 관측 횟수는 수요가 아니라 수집 밀도를 반영한다 — 우리 폴링은 닷새 사이 8.5배 늘었다.
점유율과 순수요는 그 오염이 없다. 휴일에서 평일로 넘어가는 전이도 훨씬 낫다 — 순위 상관이 순수요 0.920, 점유율 0.805인데 만석률은 0.302다.
Q. 노선을 합쳐서 배우나?
아니다. 노선별로 완전히 나눠 배운다. 계수를 공유하지 않는다.
만석 판정은 잔여석 0이라는 정확한 경계 문제이고, 어느 정류장에서 어떤 속도로 차는지가 노선마다 다르다. 합치면 그 차이만큼 편향이 들어간다. 표본이 늘면 분산은 줄지만 편향은 그대로이므로 결국 노선별이 이긴다.
실측으로도 확인했다. 3330 에서 합침 대비 격차가 학습 2일 +0.190 에서 4일 +0.029 로 6.6배 줄었다. 그리고 노선별로 배운 A18 은 현행 기본 모델보다 36칸 전부에서 CRPS 가 낮다. 36칸 모두 부트스트랩 신뢰구간이 0 을 넘지 않는다 — 전환 손해가 없다.
재료는 어떻게 들어오나
출처는 실시간 차량 위치 API 하나다. 이 API 가 한 번에 주는 것은
vehId · stationSeq · remainSeatCnt · lowPlate ·
crowded · stateCd 뿐이고, 모델의 재료는 전부 여기서 파생된다.
수집 주기는 고정이 아니라 적응형이다
"15초마다 읽는다"는 정확한 말이 아니다. 수집기는 시간대에 따라 주기를 바꾼다
(adaptive-kst-v1.0.1). 만석이 몰리는 시간에 호출을 몰아주고 심야에는 성기게 두되 끊지는 않는다.
| 구획 | 시각 (KST) | 관측 간격 | 이유 |
|---|---|---|---|
| 첨두 | 07–09 · 17–20 | 15초 (1분 안에 4연발) | 정류장 통과를 놓치지 않아야 라벨이 성립한다 |
| 주간 | 09–17 · 20–23 | 60초 | 만석이 드물어 호출을 아낀다 |
| 심야 | 23–07 | 600초 | 막차 이후. 차량 0대 응답 자체가 검증에 필요하다 |
하루 예상 호출 3,816회, 하드 상한 10,000회다. 그래서 코퍼스의 관측 밀도가 균일하지 않다 — 조밀 구간 6,144건, 한산 구간 2,058건으로, 같은 "8정류장 전"이라도 첨두에는 촘촘히 찍히고 주간에는 성기게 찍힌다. 이 페이지의 점수는 그 혼합 위에서 나온 값이다. 60초 구간만 떼어 다시 채점해 본 적은 없다.
상류 응답의 queryTime 은 1,294건 전부에서 우리 수신 시각보다
2.2~20.5초 뒤였다. 시계가 어긋나 있어 그대로 쓸 수 없다.
그래서 observedAt 은 우리 수집기가 상류 응답을 받은 시각으로 정의한다.
설계행렬 31열이 어디서 오나
차량에서 오는 것 · 정류장에서 오는 것 · 위치와 시간, 셋으로 나뉜다.
차량에서 오는 것 — 예측 시점의 상태
| 현재 잔여석 ÷ 정원 | 지금 얼마나 비어 있나. remainSeatCnt / lowPlate |
| 현재 만석 여부 | 이미 0석인가 |
| 낮은 좌석 구간 | 20석 이하를 더 촘촘히 본다 |
| 혼잡도 4단계 | crowded — 다만 잔여석의 거친 재부호화라 추가 정보는 거의 없다 |
| 정원 | 그 차량이 보여 준 최대 잔여석 그대로.
remainSeatCnt 의 차량별 최대
종전에는 lowPlate 로 45·40·68 세 갈래를 붙였는데,
계열 판정이 어긋나는 차량이 있었다 — lowPlate=0 인데 6,334회 관측에서 40 을 한 번도
넘지 않는 차량이 셋, 49 에서 멈추는 단층이 하나. 관측 최대를 그대로 쓰면 계열 판정 자체가
필요 없어지므로 오판도 없다. 말뭉치가 일곱 갈래로 갈리고(44석 388,825행 · 45석 62,404 ·
40석 55,263 · 68석 49,303 · 49석 13,435 · 36석 138 · 41석 114) 최빈값은 45가 아니라 44다.
정원 초과로 버려지는 행이 0건이 된다(종전 규칙에서는 222행을 버렸다).
차량의 형식승인 좌석수는 이것과 다른 양이라 합치지 않는다 — 저상 신차는 41 이 유력하다.
종전 47 은 어느 근거에도 없던 값이었다. |
| 상류 좌석 기울기 | 앞선 정류장들에서 좌석이 얼마나 빠르게 줄고 있었나 |
정류장에서 오는 것 — 그 지점의 이력
| 직전 도착 차량 | 그 차의 잔여석과 만석 여부 |
| 직전 연속 만석 | 최근 몇 대가 연속으로 만석이었나 |
| 셀 평균 점유율 | 노선·시간대 구획 안에서 표준화. 학습일만 사용 |
| 셀 순수요 구간합 | 통과할 정류장 전체의 합. 대기열 정보가 들어올 자리 |
| 셀 결측 지시자 | 이 정류장 이력이 없을 때 켜진다 |
위치와 시간
| 정류장 상대위치 | 노선 안 0~1 로 정규화한 뒤 8매듭 곡선. 라벨을 전혀 쓰지 않는다 |
| 시간대 | 아침 07–09 · 저녁 17–20 · 그 외 |
| 지평 | 지평마다 전용 모델을 따로 적합한다. 1~12 전부와 16 을 적합해 재 봤고, 서빙은 12까지다. 지평 자체는 특징이 아니라 모델을 나누는 축이다 |
지금은 쓰지 않는 것
실제 경과 시간은 도착한 뒤에만 알 수 있으므로 특징으로 쓰면 미래를 훔쳐보는 것이 된다. 배차 간격은 넣어 봤으나 해로웠다 — 직전 도착 차량 정보와 중복이고, 3330 의 8정류장 아침에서 0.41석 나빠졌다. 도착 예정 API 는 수집조차 하지 않는다. 완벽한 값을 안다고 가정한 오라클 상한이 0.045석에 그쳐 수집 대상에 넣지 않았다. 그래서 이 모델도 API 도 "몇 분 후"를 낼 수 없다 — v3 도 주지 않았고 v4 도 주지 않는다.
Q. 무엇을 못 보나?
센서가 잔여좌석 하나뿐이라는 사실에서 오는 한계가 있다. 두 노선은 직행좌석형이라 입석이 없다 — 이것은 확정 사실이다. 그래서 잔여석 0은 곧 승차 불가이고, 좌석 감소량이 곧 순 승차 인원이라는 등식은 정확하다. 문제는 다른 데 있다.
| 못 보는 것 | 왜 |
|---|---|
| 도착 예정 시각 | 도착정보 API 를 수집하지 않는다. 지평은 정류장 수로만 준다 |
| 정류장 대기 인원 | 어떤 채널로도 들어오지 않는다. 만석이 새로 생기는 순간을 결정하는 값이다 |
| 만석일 때의 초과 수요 | 좌석이 0이면 관측값도 계속 0이다. 센서가 포화된다 |
| 승차와 하차의 분리 | 순변화만 보인다. 앞문 승차와 뒷문 하차가 동시에 일어난다 |
| 실제 정원의 근거 | 차량마다 그 차가 실제로 보여 준 최대 잔여석이다. 연구 자료와 운영 평가기가 같은 규칙을 쓴다(2026-08-19 양쪽 교정). 관측이 적어 상한에 도달하지 못한 차량은 정원이 과소평가되고 그때 점유율이 위쪽으로 눌린다 — 이 말뭉치에서 가장 낮은 값이 36석(관측 138행)이다 |
좌석이 0이면 몇 명이 더 타려 했는지 관측값은 계속 0이다. 연속 만석 구간이 중앙값 6개, 최대 12개 정류장이고, 그동안 못 탄 사람은 전부 정류장에 남는다. 그 수를 세는 채널이 없다.
혼잡도 필드로 회복되지도 않는다 — 만석 구간 169개 중 값이 변한 것은 1개(0.6%)뿐이다.
승차와 하차를 따로 보는 것도 불가능하다. 15초 폴링이 도는 첨두 구간에서 정류장 체류의 63.1%를 세 번 이상 관측하는데도, 하차 뒤 승차를 뜻하는 비단조 궤적은 0.55%뿐이다. 앞문 승차와 뒷문 하차가 동시에 일어나기 때문이다. 한 구간 변화의 57.1%가 0인데, 그것이 "아무 일 없었다"인지 "3명 타고 3명 내렸다"인지 구분할 수 없다.
이 수치는 첨두의 조밀한 관측에서 나온 것이다. 60초로 벌어지는 주간 구획에서는 같은 보증이 없다 — 체류를 한 번밖에 못 보는 통과가 늘고, 그만큼 라벨의 검열 폭이 커진다.
회차를 어떻게 다루나
두 노선은 왕복이다. 순번 1~85(3330)·1~89(1650)가 하나의 연속 경로이고
중간의 안양역이 회차 지점이다 — 상류 노선 기준정보의 turnSeq 로
1650은 44, 3330은 43이다. 상류를 직접 조회해 확정했다.
v3 초판 예시의 turnSequence: 31 과 3330 의 routeId: 234000016 은 실제와 다른 값이라
2026-08-19 에 43·204000057 로 교정했다.
회차 너머의 버스도 예보한다
재구성한 운행의 순번 폭이 중앙 86/89(1650)·81/85(3330)이고, 회차를 이어서 지난 운행이 94.0%·72.4%다. 회차 전후 관측 간격은 중앙 0.3분 — 차고지에 서지 않고 그냥 지나간다. 회차는 운행의 끝이 아니다. 그러므로 회차 너머 정류장에 대해서도 그 차량은 도착 예정이 맞고, "도착 예정 버스 없음"이라고 쓰면 거짓이 된다. 실제로 API 가 내보내는 도착 항목의 10.0%(1650)·12.8%(3330)가 회차를 넘는 건이다.
다만 근거가 바뀐다
회차를 넘는 순간 차량의 현재 잔여석이 설명력을 잃는다. 도착 잔여석에 대한 R² 다.
| 지평 | 회차 안 넘음 (R²) | 회차 넘음 (R²) |
|---|---|---|
| 2정류장 | 0.873 | 0.370 |
| 4정류장 | 0.645 | 0.034 |
| 8정류장 | 0.178 | 0.011 (상관이 음수 −0.105 로 뒤집힌다) |
그런데도 예보는 맞는다. 회차를 넘는 표본의 평균 점유율이 0.105이고 점유율 0.7 이상은 0건이기 때문이다 — 승객은 이미 다 내린 뒤다. 정류장 통계만으로도 "여기는 만석이 아니다"가 맞는 답이 된다.
| 지평 | 구분 | 표본 | 실제 만석률 | 예측 평균 | 최대 예측 | 브라이어 |
|---|---|---|---|---|---|---|
| 8 | 회차 안 넘음 | 37,919 | 1.872% | 1.534% | 0.993 | 0.01202 |
| 8 | 회차 넘음 | 4,294 | 0.000% | 0.052% | 0.020 | 0.00000 |
| 12 | 회차 안 넘음 | 33,516 | 1.930% | 1.878% | 0.981 | 0.01371 |
| 12 | 회차 넘음 | 6,324 | 0.617% | 0.238% | 0.338 | 0.00563 |
회차 너머 예보를 내는 것은 차량 상태가 아니라 정류장 통계다. 답은 맞지만 근거가 다르다.
서빙 상한을 12로 올리면 이 구간이 더는 안전하지 않다. 8정류장에서는 회차 너머 표본이 전체의 10.2%이고 실제 만석이 한 건도 없었지만, 12정류장에서는 15.9%로 늘고 실제 만석률이 0.617%가 된다. 모델은 이 구간에 평균 0.238% 만 얹으므로 과소예측이다. 절대량이 작아 종합 지표에는 거의 안 보이지만 (브라이어 0.00563), 회차 너머만 따로 코호트로 채점해 본 적이 아직 없다 — 아래 「다음 확인 사항」에 남겨 두었다.
모델은 회차점을 입력으로 받지 않는다
turnSeq 는 모델의 특징이 아니다. 설계행렬 어디에도 "회차를 넘는가"라는 열이 없다.
그런데도 위 표처럼 맞는 이유는, 셀 층이 (노선, 정류장, 시간대)별 통계를 학습하므로
앞 그림의 공백 구간을 이미 알고 있기 때문이다. 회차라는 개념을 따로 주입하지 않아도
그 정류장들에는 낮은 만석 확률을 낸다.
turnSeq 가 쓰이는 곳은 두 군데뿐이다 — 순번을 UP/DOWN 으로 가르는 것,
그리고 화면에 "회차 후 도착"을 표시할지 고르는 것. 뒤엣것은 API 필드가 아니다 —
클라이언트가 sequence − horizonStops 를 turnSequence 와 견줘 직접 계산한다.
그 값이 틀려도 확률은 틀리지 않는다. 틀리는 것은 방향 표시와 회차 표시다.
Q. 노선을 따라가면 무엇이 보이나?
Q. 지표를 왜 넷이나 보나?
만석이 표본의 1.7%뿐이라 분포 정확도 지표 하나로는 만석 경보의 품질을 못 잰다. 나머지 98.3%가 지표를 지배하기 때문이다.
이번 설계에서 분포 지표와 만석 지표가 정반대를 가리킨 층이 세 번 나왔다. 분포 정확도로는 무의미한데 만석 확률의 정확도로는 가장 크게 기여한 층이 있었고, 그 반대도 있었다. 그래서 네 지표를 함께 보고, 어느 하나만으로 판정하지 않는다는 것을 규칙으로 두었다.
| 지표 | 재는 것 | 좋은 방향 |
|---|---|---|
| 브라이어 | 확률 예측이 얼마나 정확한가 | 낮게 |
| 로그 손실 | 틀린 것을 얼마나 확신했나 | 낮게 |
| 보정 | "30% 확률"이라 한 것 중 실제로 30%가 일어났나 | 0에 가깝게 |
| 만석 구분 | 만석인 것을 위쪽에 잘 세우나 | 높게 |
이 점수는 어디서 나왔나
다른 후보들의 점수는 매일 도는 일일 평가가 원장이다. A18 은 아직 일일 평가에 들어 있지 않다. 여기 실린 값은 2026-08-19 설계 세션에서 같은 평가 규약·같은 지표 구현·같은 점수 정책을 그대로 불러 오프라인으로 계산한 것이다. 학습은 앞선 한국 표준시 날짜만, 채점은 뒤 날짜만 쓰는 확장창 롤링 오리진도 동일하다.
같은 규칙으로 쟀지만 원장이 다르므로 다른 후보와 같은 칸에 놓고 순위를 매기지 않는다. 일일 평가에 편입되면 그때부터 다른 후보들과 같은 방식으로 갱신된다.
기존 일곱 후보는 정류장과 시간대로 예측한다. "이 정류장은 화요일 아침에 만석일 확률" 같은 형태다. 버스를 특정하지 않으므로 언제든 답할 수 있다.
A18 은 지금 몇 정류장 앞에 있는 그 버스의 잔여석을 함께 쓴다. 그래서 답이 더 정확하지만, 버스가 특정되고 추적되고 있어야 한다. 더 나은 모델이라기보다 다른 질문에 답하는 모델이고, 점수를 나란히 볼 때 이 조건 차이를 함께 읽어야 한다.
이 페이지가 쓰는 말
| 말 | 뜻 |
|---|---|
| 칸 | 채점을 나누는 단위. 노선 2 × 지평 6 × 시간대 3 = 36칸이 기본이고, 분포 정확도는 노선 2 × 지평 6 × 지표 2 = 24칸, 아침 구간만 보면 노선 2 × 지평 6 = 12칸이다. "96 판정"은 이 조합에 검정 종류를 곱한 다중비교 시험군의 크기다 |
| 층 | 모델에 얹는 재료 묶음 하나. A18 은 여섯 층이다 — 셀 통계 · 정류장 위치 곡선 · 구간합 수요 · 사전확률 이동 · 지평 적응 기준점 · 상대 구간. 위 「어떻게 계산하나」의 네 단계는 계산 순서이고 층과는 다른 축이다 |
| 밴드 | 시간대 구획. 아침 07–09 · 저녁 17–20 · 그 외 |
| CRPS | 분포 예측의 오차. 만석 여부만 보는 브라이어와 달리 좌석 수 분포 전체를 채점하며 단위가 좌석(석)이다. 낮을수록 좋다 |
| 판별력 | 만석과 비만석을 가르는 능력. 값이 0.5 부근이면 무작위다. 이 페이지의 「만석 구분」(PR-AUC)과는 다른 지표(ROC-AUC)이므로 값의 크기를 견주지 마라 |
| 현행 기본 | 지금 운영 후보로 올라 있는 상호작용 로지스틱 회귀. 차량 상태를 쓰지 않는다. 이 페이지에서 "현행 기본보다 N% 낫다"는 CRPS 기준이다 |
| j12 | 지평 12정류장판 A18. 평가기에 seat_distribution_j12 로 등록돼 있다 |
오늘 성적
이 페이지의 모든 숫자는 노선 2개 · 닷새(8/14~8/18) · 그중 평일 하루에서 나왔다. 그리고 정원 규칙이 연구 경로와 운영 평가기에서 다르다 — 고치면 아래 점수가 전부 움직인다. 자세한 것은 맨 아래 「다음 확인 사항」에 있다. 확정치가 아니라 현시점 산출로 읽어라.
지평은 목표 정류장 몇 개 전에서 예측하는가다. 멀리서 물을수록 어렵다.
서빙 상한은 12정류장으로 확정했다. 화면에는 12정류장 앞까지의 버스를 담는다. 다만 이 페이지의 상세 수치와 코호트 분해는 8정류장을 보고 기준으로 삼는다 — 앞선 설계 판정이 전부 8에서 이뤄졌고 다른 후보와 비교 가능한 축이 8이기 때문이다. 두 숫자는 역할이 다르므로 섞어 읽으면 안 된다.
83.2
브라이어 0.00551
만석 구분 0.840
77.5
브라이어 0.00775
만석 구분 0.725
69.3
브라이어 0.01079
만석 구분 0.536
보고 기준
71.6
브라이어 0.01242
만석 구분 0.419
서빙 상한 · 채택
종합 점수 0~100. 기준 50점은 기후값이다 — 평가일보다 이른 학습일의 만석률만 쓰는 예측. 100점은 완벽, 기준보다 손실이 두 배면 0점이다. 채점 표본은 이 네 지평에서 3.98~4.54만 건, 만석 686~741건(지평 2에서 45,406/741, 지평 12에서 39,840/686). 열두 지평 전체는 아래 표에 있다.
Q. 12정류장 점수가 8보다 높은데, 더 잘하는 것 아닌가?
아니다. 종합 점수 하나만 보면 안 되는 사례가 바로 이것이다. 종합 점수는 다섯 항목의 가중 평균인데, 8에서 12로 가면 네 항목이 떨어지고 한 항목만 오른다. 오른 한 항목의 가중치가 커서 합이 뒤집힌 것이다.
| 구성 항목 | 가중 | 8정류장 | 12정류장 | 변화 |
|---|---|---|---|---|
| 확률 맞춤 | 20 | 60.6 | 89.1 | +28.5 |
| 확률 오차 | 35 | 67.6 | 63.5 | −4.1 |
| 만석 구분 | 15 | 76.5 | 70.5 | −6.0 |
| 놓침 비용 | 15 | 69.6 | 66.0 | −3.6 |
| 과신 벌점 | 15 | 77.1 | 73.7 | −3.4 |
| 종합 | 100 | 69.3 | 71.6 | +2.3 |
확률 맞춤이 뛴 이유는 모델이 나아져서가 아니다. 먼 지평에서는 차량 상태가 힘을 잃고 정류장 통계에 기대게 되는데, 그쪽은 정의상 평균이 잘 맞는다. 예측을 평평하게 깔면 보정 지표는 좋아진다. 정작 만석 버스를 골라내는 능력인 만석 구분(PR-AUC)은 0.536 → 0.419로, 점수 환산으로도 76.5 → 70.5 로 계속 떨어진다. 그래서 12를 채택한 근거는 이 종합 점수가 아니라 커버율이다(아래).
코호트로 쪼개면
합친 점수는 서로 반대인 구간을 상쇄한다. 그래서 시간대와 요일로 쪼개 본다.
| 코호트 | 8정류장(보고 기준) | 12정류장(서빙 상한) | 표본(8정류장) |
|---|---|---|---|
| 전체 | 69.3 | 71.6 | 42,233 |
| 아침 통근 | 81.2 | 77.0 | 5,569 |
| 저녁 통근 | 74.0 | 70.6 | 8,825 |
| 그 외 시간 | 64.1 | 66.8 | 27,839 |
| 평일 | 75.6 | 75.0 | 17,674 |
| 주말·공휴일 | 72.0 | 67.0 | 24,559 |
두 열을 섞어 읽지 마라. 전체는 12정류장이 높은데 아침·저녁·평일·주말은 8정류장이 높다 — 종합 점수의 보정 항목이 코호트마다 다르게 움직이기 때문이다. 어느 코호트에서도 만석 구분은 12정류장이 낮다.
종합 점수는 어떤 재료로 만들어지나
다섯 항목은 아래 원지표에서 그대로 온다. 각 항목을 기준(기후값) 50점 · 완벽 100점 · 기준보다 손실 두 배면 0점으로 환산한 뒤 가중합한다. 항목 이름은 화면용 우리말이고, 괄호 안이 실제로 계산되는 값이다.
| 항목 | 원지표 | 좋은 방향 | 가중치 | 8정류장 원값 | → 환산 점수 |
|---|---|---|---|---|---|
| 확률 오차 | brier — 확률 예측의 평균 제곱오차 | 낮을수록 | 35 | 0.010794 | 67.6 |
| 확률 맞춤 | calibration_bias와 ecce_mad — 0에 가까울수록 | 절댓값이 작을수록 | 20 | −0.002983 · 0.003250 | 60.6 |
| 만석 구분 | pr_auc — 만석을 위쪽에 세우는 능력 | 높을수록 | 15 | 0.536370 | 76.5 |
| 과신 벌점 | log_loss — 틀린 확신에 큰 벌점 | 낮을수록 | 15 | 0.040792 | 77.1 |
| 놓침 비용 | cost_4 — 만석을 놓치는 쪽에 4배 비중 | 낮을수록 | 15 | 0.040888 | 69.6 |
가중합하면 67.6×0.35 + 60.6×0.20 + 76.5×0.15 + 77.1×0.15 + 69.6×0.15 = 69.26 이고 게시값 69.3 과 맞는다. 같은 이름이 두 눈금으로 나오는 자리가 있다 — 성적 카드의 "만석 구분 0.536"은 원값(PR-AUC)이고, 위 표의 76.5는 그것을 환산한 점수다. 같은 항목이니 헷갈리지 마라.
규약 출처는 score_policy.json(composite-score-v1)이며
기준 예측은 각 평가일보다 이른 학습일의 만석률만 쓰는 값이다.
종합 점수는 규약 자신이 "설명과 비교를 돕는 보조값이며 자료·재현성 관문을 대신하지 않는다"고 적어 둔 값이다.
8정류장 지평(중앙 15.3분) · 원지표 — 보고 기준
| 지표 | A18 | 기준(기후값) | 뜻 |
|---|---|---|---|
| 브라이어 | 0.010794 | 0.016633 | 확률 예측의 평균 제곱오차. 낮을수록 좋다 |
| 로그 손실 | 0.040792 | 0.089082 | 과신에 큰 벌점. 낮을수록 좋다 |
| 보정 편향 | −0.002983 | −0.002452 | 예측 확률 평균과 실제 발생률의 차. 0에 가까울수록 좋다 |
| 누적 보정 오차 | 0.003250 | 0.009023 | 확률 구간별로 얼마나 어긋나는지 |
| 만석 구분(PR-AUC) | 0.536370 | 0.015509 | 만석 경보의 순위 품질. 높을수록 좋다 |
| ROC-AUC | 0.973517 | 0.359644 | 만석과 비만석을 가르는 능력 |
| 놓침 비용 | 0.040888 | 0.067278 | 만석을 놓치는 쪽에 4배 비중을 둔 비용 |
기준의 ROC-AUC 가 0.5 아래인 것은 기후값이 날짜별 상수라 날짜 사이 순서가 뒤집혔기 때문이다. 같은 날 안에서는 모든 예측이 같은 값이므로 순위를 매길 수 없다.
지평을 어디서 끊나
몇 정류장 전까지 예보할지는 얼마나 많은 정류장을 덮느냐와 만석 버스를 골라내는 능력이 얼마나 남느냐의 교환이다. 덮는 범위는 실제 운행 기록에서, 골라내는 능력은 채점에서 나온 값이다.
| 지평 | 중앙 소요 | 정류장 커버율 | 만석 구분 | 무작위 대비 | 판정 |
|---|---|---|---|---|---|
| 8정류장 | 15.3분 | 79.2% | 0.536 | 31배 | 보고 기준 |
| 12정류장 | 25.0분 | 86.3% | 0.419 | 24배 | 서빙 상한 · 채택 |
| 16정류장 | 34.0분 | 87.4% | 0.325 | 17배 | 기각 |
8에서 12로 늘리면 커버율이 7.1%p 오른다. 12에서 16으로 더 늘리면 1.1%p 뿐인데 골라내는 능력은 24배에서 17배로 떨어진다. 12에서 끊는다. 무작위 대비는 만석 구분(PR-AUC)을 기저 만석률로 나눈 값이고, 기저 만석률은 지평별 1.68~1.86%다.
이 판정에 종합 점수는 쓰지 않았다. 여기서 교환하는 것은 덮는 범위와 골라내는 능력이다.
커버율은 실제 운행 기록으로 쟀다 — 어느 순간을 잡아 노선의 모든 정류장을 보고, 그 뒤 지평 안에 같은 운행의 버스가 있는 정류장의 비율이다. 표는 하루 전체(심야 포함) 기준이고, 운행 시간대(06~23시)로 좁히면 지평 12에서 90.4%(1650)·91.1%(3330)다.
커버율의 상한은 순번 구조가 정한다
지평 12 로는 순번 12 이하 정류장에서 뒤로 볼 수 있는 칸이 순번−1 개뿐이다.
그런 정류장이 두 노선 각각 12개, 전체 174개 중 13.8% 다.
실제로 그 구간의 커버율은 37~44% 로 떨어지고, 나머지 구간은 98.7~98.9% 다.
커버율을 더 올리는 길은 지평을 늘리는 것이 아니라 이 구간을 따로 푸는 것이다.
| 구간 | 정류장 비중 | 지평 12 커버율(운행 시간대) |
|---|---|---|
| 순번 13 이상 | 86.2% | 98.7% · 98.9% |
| 순번 12 이하 | 13.8% | 37.4% · 43.6% |
한 바퀴 돌아 오는 차량은 담지 않는다
순번 12 이하에서 빈칸을 메우려면 이미 종점 쪽에 있는 차량이 한 바퀴 돌아 오는 것을 세면 된다. 커버율이 +5.6%p 오른다. 그런데 재보니 그 차량이 실제로 그 정류장에 다음으로 도착하는 차량인 경우가 5.1%(1650)·15.6%(3330) 뿐이었다. 그 앞에 중앙 4~7대가 먼저 지나간다.
진짜 이유는 이렇다 — 그 정류장에 실제로 다음 오는 버스는 중앙 8.3~12.0분 뒤에 도착하는데, 그 차는 96.2~96.8%의 경우 차고지에서 대기 중이라 차량위치 API 에 아직 없다. 버스는 마지막 정류장을 한 번도 보고하지 않고 그 직전에서 피드에서 사라지며, 다시 나타나기까지 중앙 65분(1650)·92분(3330)이 걸린다.
그래서 랩어라운드를 담으면 커버율은 오르지만 가리키는 대상이 틀린다. 중앙 86~97분 뒤에 오는 엉뚱한 차량을 "다음 버스"로 보여주게 되고, 지평 12의 시간 규모(24~28분)를 3.5~4배 넘는다. 담지 않는다. 그 구간은 차고지 발차 시각이 있어야 풀리는 별개 문제로 남긴다.
지평은 시간이 아니라 정류장 수다
둘은 서로 환산되지 않는다. 표에 적은 "중앙 소요"는 사후에 잰 중앙값일 뿐 변환식이 아니다. 같은 8정류장이 10%분위 9.1분에서 90%분위 23.0분까지 2.5배로 흩어지고 (25%분위 12.0분 · 75%분위 19.0분), 거꾸로 5~10분 구간에는 2·3·4·6·8정류장이 모두 섞여 있다 (4정류장 38%, 3정류장 26%, 6정류장 23%). 정체 여부에 따라 달라지기 때문이다.
그런데도 정류장 수로 정의하는 이유는, 걸린 시간은 도착한 뒤에만 알 수 있는 값이라
예측에 쓰면 미래를 훔쳐보게 되기 때문이다. 모델은 이 시간 차이를 입력으로 받지 않는다.
화면에 "몇 분 후"를 띄우려면 도착정보 API를 따로 불러야 하는데 그것은 지금 수집 대상이 아니다.
그래서 API 도 horizonStops(몇 정류장 남았나)만 주고 도착 예정 시각은 주지 않는다.
지평 1~12 전부의 성적
클라이언트 계약이 1정류장부터 12정류장까지 예보를 약속하므로, 그 열두 지평을 모두 적합하고
같은 규약으로 채점했다. 처음에는 1·2·3·4·6·8 만 있었고 5·7·9·10·11 은 표본조차 없었다 —
계약이 근거 없는 약속을 하고 있던 셈이라, 자료를 다시 만들어 메웠다.
| 지평 | 중앙 소요 | 종합 점수 | 브라이어 | 만석 구분 | 보정 편향 | 채점 표본 |
|---|---|---|---|---|---|---|
| 1 | 0.3분 | 90.3 | 0.00285 | 0.9295 | -0.0014 | 47,397 |
| 2 | 2.3분 | 83.2 | 0.00550 | 0.8402 | -0.0023 | 45,420 |
| 3 | 4.3분 | 79.9 | 0.00692 | 0.7761 | -0.0025 | 45,067 |
| 4 | 6.5분 | 77.5 | 0.00774 | 0.7258 | -0.0025 | 44,532 |
| 5 | 8.7분 | 74.8 | 0.00854 | 0.6804 | -0.0030 | 43,909 |
| 6 | 11.0분 | 72.8 | 0.00925 | 0.6400 | -0.0032 | 43,354 |
| 7 | 13.0분 | 71.1 | 0.01002 | 0.5951 | -0.0031 | 42,763 |
| 8 | 15.3분 | 69.3 | 0.01077 | 0.5377 | -0.0030 | 42,233 |
| 9 | 18.0분 | 69.5 | 0.01129 | 0.4966 | -0.0026 | 41,672 |
| 10 | 20.0분 | 70.0 | 0.01177 | 0.4596 | -0.0019 | 41,092 |
| 11 | 22.6분 | 72.1 | 0.01215 | 0.4318 | -0.0008 | 40,524 |
| 12 | 25.0분 | 71.6 | 0.01241 | 0.4194 | -0.0011 | 39,864 |
브라이어·로그 손실·만석 구분은 열두 지평 모두에서 단조다 — 멀수록 나빠진다. 꺾이는 것은 종합 점수뿐이고 그 원인은 보정 항목이다. 표본이 지평마다 줄어드는 것은 목표 정류장 뒤로 그만큼의 관측 이력이 필요하기 때문이다. 서빙 상한 12 의 근거는 위 「지평을 어디서 끊나」에 있다.
Q. 언제 실패하나?
- 학습 기간에 없던 상황. 코퍼스는 8/14~8/18 닷새인데, 8/14 금요일은 16시부터라 아침이 없고 8/15 토 · 8/16 일 · 8/17 대체공휴일은 전부 휴일이다. 즉 학습에 쓸 수 있는 날 가운데 평일 아침이 있는 날이 하나도 없어 평일 아침 만석 사건이 0건이다. 자료 문제이지 모델 문제가 아니라는 것은 대조 실험으로 확인했다 — 휴일 아침을 세 배로 늘려도 판별력(ROC-AUC)이 0.518 그대로인데, 평일 아침 반나절이 들어오면 0.958 로 뛴다.
- 만석이 새로 발생하는 순간. 8정류장 예보에서 만석 도착의 88%는 그 시점에 아직 만석이 아니었다. 즉 "만석이 될 것"을 맞혀야 하는데, 그 사건이 코퍼스 전체에 119건뿐이다.
- 이력이 없는 정류장·노선. 노선별로 배우므로 새 노선은 자기 이력이 쌓이기 전까지 쓸 수 없다. 그래서 신규 노선은 수집만 하고 예보를 내지 않는다.
- 시간대가 크게 바뀔 때. 시간표 변경이나 계절 변화는 아직 겪어 보지 않았다.
Q. 어떻게 검증했나?
채택 규칙을 결과를 보기 전에 문서로 고정하고 그대로 적용했다.
| 규칙 | 기준 | 실측 |
|---|---|---|
| 유의하게 나빠진 칸 | 0건 / 96 판정 | 0건 |
| 분포 정확도 개선 | 24칸 중 20칸 이상 | 24/24, 전부 유의 |
| 아침 밴드 개선 | 12칸 전부 | 12/12 |
| 만석 확률 정확도 | 나빠진 칸 없음 | 0칸 |
| 결정성 | 같은 입력이면 같은 출력 | 두 노선 통과 |
여기에 네 가지 반증 시험을 더 걸었다.
- 미래를 훔쳐보는가 — 시험일 정오 이후에 도착한 사건의 정답을 전부 뒤집어도, 정오 이전 시점의 예측이 정확히 0 변했다. 대조로 과거 정답을 뒤집으면 0.788 변하므로 시험 자체는 민감하다.
- 여러 번 재서 운 좋은 것을 고른 것인가 — 검정 96개에 다중비교 보정을 걸어도 분포 정확도가 23/24 살아남는다. 순수한 우연에서 기대되는 겉보기 이득이 0.21석인데 관측된 것은 2.07석으로 열 배다.
- 자료가 적어서 잘 보이는 것인가 — 학습을 하루에서 나흘로 늘려도 격차가 좁아지지 않는다. 오히려 정류장 이력 층은 이득이 커진다.
- 서빙 시점에 알 수 없는 값을 쓰고 있는가 — 학습에 쓴 시간대 꼬리표가
도착 시각 기준임을 확인했다(실측 100%). 그런데 예보를 낼 때는 도착 시각을 모른다.
알 수 있는 것은 예측 시각의 시간대뿐이고, 둘은 12정류장 지평에서 10.11% 어긋난다
(2정류장 1.00% · 4정류장 2.72% · 8정류장 6.35%).
그래서 예측 시각 기준으로 바꿔 처음부터 다시 적합하고 다시 채점했다.
가장 큰 손실이 0.3점이다. 우려는 실재했으나 크기가 무시할 만하다 — 시간대 꼬리표는 셀 통계의 묶음 키일 뿐이고, 경계를 넘는 3.65%가 이웃 셀로 옮겨가도 그 셀들의 통계가 크게 다르지 않기 때문이다. 12정류장에서 오히려 오른 것은 경계 부근 표본이 예측 시각 쪽에서 더 고르게 나뉘어서다. 보고하는 점수는 도착 시각 기준 그대로 둔다. 기존 7개 모델과 같은 라벨 정의를 써야 같은 표에서 비교할 수 있기 때문이다. 서빙 시 실제 성적은 위 오른쪽 칸에 가깝다.
지평 도착 시각 기준(현행 보고값) 예측 시각 기준(서빙 가능) 차이 2정류장 83.2 83.1 −0.1 4정류장 77.5 77.3 −0.2 8정류장 69.3 69.0 −0.3 12정류장 71.6 71.7 +0.1
이번에 확정한 것
- 서빙 지평은 12정류장. 커버율 86.3%(운행 시간대 90.4~91.1%) 대 만석 구분 24배의 교환으로 끊었다. 16은 기각.
- 한 바퀴 돌아 오는 차량은 도착 예정에 담지 않는다. 커버율 +5.6%p 를 포기한다 — 그 차량이 실제 다음 도착 차량인 경우가 5.1~15.6% 뿐이기 때문이다.
- 회차 순번은 1650=44 · 3330=43, 회차 정류장은 두 노선 모두 안양역(
208000069). 상류를 직접 조회해 확정했고, 이전 문서의turnSequence: 31은 틀린 값이었다. - 회차 너머 버스도 예보에 담는다. 운행이 회차에서 끊기지 않고 0.3분 만에 지나간다는 것을 실측으로 확인했다.
- 노선별 학습, 계수 공유 없음. 36칸 전부에서 기존 기준선을 이겼다.
- 정원은 그 차량이 보여 준 최대 잔여석 그대로. 연구 자료와 운영 평가기가 같은 규칙을 쓴다.
지평 1~12 말뭉치 569,260행을 교정 전
lowPlate규칙으로 가르면 45석 499,256행 · 68석 49,303행 · 40석 20,701행 세 갈래였다. 관측 최대로 가른 지금의 구성은 일곱 갈래이고 최빈값이 44다 — 위 특징 표에 적었다.
다음 확인 사항
- 연구 경로와 평가기가 보는 사건 수가 다르다.
정원 규칙은 2026-08-19 에 양쪽을 관측 최대로 맞췄고, 그 뒤 재채점해도 열두 지평 모두
최대 0.1점밖에 안 움직였다 — 정원은 점유율의 분모일 뿐 라벨(잔여석 0)과 무관하기 때문이다.
남은 차이는 말뭉치 구성이다. 이 페이지의 숫자는 계약 라벨(direct_rows)을 결합해 만든 연구 자료에서 나오고, 운영 평가기는 레코드에서 통과 사건을 직접 만든다. 평가기가 더 적게 보고 지평이 멀수록 벌어진다 — 2정류장 −65행 · 4정류장 −185 · 8정류장 −409 · 12정류장 −630(만석 −11). 점수도 갈린다 — 4정류장 77.5 대 77.9 · 8정류장 69.3 대 69.7 · 12정류장 71.6 대 71.9.
두 경로가 통과 사건을 어디서 다르게 세는지 아직 짚지 못했다. 매일 게시될 값은 평가기 쪽이므로 그쪽에 맞추는 것이 맞고, 원인을 찾은 뒤 정해야 한다. - 노선 앞머리 12개 정류장을 어떻게 할 것인가. 전체 정류장의 13.8% 이고 그 구간 커버율이 37~44% 다. 실제 다음 버스는 중앙 8.3~12.0분 뒤에 오는데 차고지 대기 중이라 차량위치 API 에 안 보인다(96.2~96.8%). 지평을 늘려도 안 풀리고 한 바퀴 돌아 오는 차량을 세도 틀린 차를 가리킨다. 차고지 발차 시각이 있어야 풀린다. 지금은 그 정류장에서 빈 답을 낸다.
- 3330 의 일부 차량이 차고지가 아니라 회차지 부근(순번 42~43)에서 밤을 넘긴다. 30분 초과 공백 106건 중 56건이 300분 초과이고 직전 순번이 전부 거기다. 운행 시작 판정에 영향을 주는지 확인해야 한다.
- 회차 대기 시간이 시간대에 따라 변한다 — 1650 기준 07시 106.7분에서 21시 46.0분까지 줄어든다. 지금 모델은 이것을 쓰지 않는다.
- 60초 수집 주기에서의 성적 재산출. 지금 점수는 15초·60초·600초가 섞인 코퍼스에서 나왔다. 주간 구획만 떼어 채점하면 라벨 검열이 커지는 만큼 떨어질 것으로 보이는데, 재 본 적이 없다. 관측 밀도별 성적을 분해하는 것이 다음 순서다.
- 평일 자료. 평일 코호트 점수 75.6은 8월 18일 화요일 하루(17,674건)에서만 나왔다. "새로운 평일에도 이럴 것"은 아직 보증되지 않는다. 평일이 며칠 쌓이면 그때 확정으로 옮긴다.
- 계수 번들이 없다. 지금 숫자는 매번 학습해 다시 채점한 것이고, 서빙에 실을 고정된 계수 묶음이 아직 만들어지지 않았다. 그것이 없으면 배포도 재현도 성립하지 않는다.
- 회차 너머 예보의 코호트별 성적. 지평 12에서 회차 너머 표본의 실제 만석률이 0.617%인데 예측 평균은 0.238%다. 이 구간을 아침·저녁·노선별로 쪼개 채점한 적이 없어, 과소예측이 특정 시간대에 몰려 있는지 고르게 퍼져 있는지 모른다.
- j12 를 평가기에 편입한 뒤의 실측. 지금 숫자는 오프라인 산출이다. 평가기에 들어가면 다른 후보들과 같은 원장을 쓰고, 그때 값이 여기 실린 값과 같은지 대조해야 한다.
- 층 두 개의 재판정. 노선별로 나눈 뒤 기준점 조절 층과 상대 구간 층의 기여가 거의 사라졌다. 빼도 성능이 유지되면 구조가 여섯 층에서 네 층으로 줄어든다.
- 대기열 정보. 만석이 새로 발생하는 순간을 결정하는 것은 정류장에 서 있는 사람 수인데, 그 값이 재료에 없다. 구간 수요 열이 그 자리이며, 장기 평균 대신 실시간 추정치를 끼우면 구조를 바꾸지 않고 승격된다.