후보 모델
재료 다 넣기
로지스틱 + 상호작용
위치·시간이 만든 기본 위험 위에 실시간 조합의 추가분만 얹는다.
1. 왜 이 방법인가
Q. 정류장·시간만으로 AUC가 .9613인데 무엇을 더 보나?
검증 범위는 20일 코퍼스 보조 원천의 평일 고정창이다. validation 10,040건 중 만석은 583건이다. 직접 수집 validation은 443건, 만석은 14건이다. 다만 수집 날짜가 하루뿐이라 날짜 간 변동은 아직 못 본다.
정류장×30분 셀은 833개다. 이 중 281개, 33.73%는 학습 표본이 10건 이하다. 719개, 86.31%는 만석을 한 번도 못 봤다. 셀 비율을 그대로 쓰면 얇은 셀을 위험 0으로 외우기 쉽다.
그렇다고 위치를 버리면 안 된다. 아무것도 안 본 기준선은 AUC .5000이다. 앞 통과 버스 한 대만 보면 .7187이다. 정류장·시간을 보면 .9613이다. 원자료에서 커 보였던 2차 만석 효과도 정류장×30분으로 나누자 사라졌다. 위치와 시간이 먼저다.
이 모델은 그 위에서 묻는다. 같은 정류장, 같은 시간이라도 직전 버스가 만석이었는지, 상류 버스에 몇 석이 남았는지, 노선 전체에 만석 버스가 몇 대인지가 위험을 더 바꾸는가. 정류장×시간대 기준선이 이미 맞힌 몫은 새 모델의 공으로 세지 않는다.
| 지표 | 정류장×시간대 | 로지스틱+상호작용 isotonic | 짝지은 차이 [95% 구간] |
|---|---|---|---|
| ECE ↓ | .0063 | .0065 | +.0002 [-.0028, +.0048] |
| Brier ↓ | .0327 | .0231 | -.0096 [-.0120, -.0073] |
| log loss ↓ | .1033 | .0787 | -.0247 [-.0315, -.0180] |
| AUC ↑ | .9613 | .9775 | +.0162 [.0093, .0222] |
순위와 확률 손실은 좋아졌다. ECE는 기준선보다 좋아졌다고 말할 근거가 없다. 다만 운영 후보의 보정 변형 가운데 ECE .0065는 2위다. 실제 만석인데 p_full<.5로 본 비율도 .6003에서 .3019로 줄었다.
시공간 위험과 실시간 상태를 1,176개 원항·짝항으로 펼친 뒤 보정된 만석확률을 낸다.
2. 어떻게 작동하나
Q. 위치·시간의 시작점은 어떻게 만드나?
먼저 노선×정류장×30분의 만석률을 만든다. 셀 표본에 노선 만석률 한 건어치를 섞는다.
학습 행은 자기 라벨을 뺀 LOO 값만 본다. 자기 정답을 자기 입력에 넣는 누수를 막는다. 예측 때는 학습 전체로 만든 값을 쓴다. 처음 보는 셀은 노선×정류장, 노선×30분, 노선, 전체 순서로 물러난다.
Q. 상호작용은 무엇을 더하나?
실제 코퍼스 학습에서 기초 입력은 37열이다. 결측 중앙값과 결측 표시 11열을 붙이면 48열이 된다. 이를 표준화한 뒤 원래 항 48개와 두 항씩 짝지은 항 1,128개를 만든다. 합계 1,176개다.
짝은 직전 버스 만석×상류 버스 잔여석, 셀 위험×노선 평균 잔여석, 시각×배차간격처럼 생긴다. 기본요금에 옵션 요금을 붙이는 계산과 닮았다. 위치·시간이 기본요금이고, 특정 재료가 함께 나타날 때만 붙는 조정값이 상호작용이다.
1,176개 계수는 모든 행에서 같은 고정값이다. 계수들이 공통 분포에서 서로 힘을 빌리지는 않는다. 대신 L2 규제로 전부 0 쪽으로 누른다. 날짜 순방향 교차검증이 고른 세기는 C=.003이다.
Q. 점수는 어떻게 확률이 되나?
작은 식 하나면 끝난다.
βx는 원래 항, γxx는 두 재료의 상호작용이다. 계수 부호는 인과효과가 아니다. 같은 데이터 안에서 다른 입력을 고정했을 때 점수가 움직인 방향이다.
원점수는 시간순 OOF 10,236건, 만석 572건으로 학습한 isotonic 보정기를 지난다. validation 라벨은 보정기에 들어가지 않았다. 마지막 출력은 p_full이고, 사용자에게 보일 값은 보수적으로 매핑한 p_board=1-p_full이다.
| 지표 | 기준선 | 상호작용 | 차이 |
|---|---|---|---|
| ECE | 0.0063 | 0.0065 | +.0002 |
| Brier | 0.0327 | 0.0231 | -.0096 |
| log loss | 0.1033 | 0.0787 | -.0247 |
| AUC | 0.9613 | 0.9775 | +.0162 |
| 만석을 가용으로 본 비율 | 0.6003 | 0.3019 | -.2985 |
같은 corpus validation 10,040건·만석 583건에서 비교했다.
3. 실제 한 건은 어떻게 나왔나
Q. 2026년 8월 3일 아침 범계역에서는 어떤 숫자가 들어갔나?
질의 시각은 07:24:01 KST다. 노선은 3330, 목표는 55번 롯데백화점.범계역이다.
| 입력 | 실제 값 |
|---|---|
| 같은 셀의 학습 기록 | 23건 중 만석 14건 |
| 3330 축소 노선 만석률 | .052628 |
| 직전 통과 | 569.270초 전, 출발 후 0석 |
| 최근접 상류 버스 | 축 1칸 전, stateCd=1, 잔여 22석 |
| 노선 응답 | 24대 중 만석 2대 |
| 노선 잔여석 | 평균 32.75석, 최소 0석, 중앙값 35석 |
| 계획 배차 | 5분 |
셀 시작값부터 계산한다.
37개 입력과 상호작용을 모두 더한 로짓은 z=-.101958이었다. 따라서 원 만석확률은 sigmoid(z)=.474533이다. isotonic 보정 뒤에는 .530631이 됐다. 화면용 탈 확률은 1-.530631=.469369다.
이 버스의 정답은 출발 후 0석, label_full=1이었다. 이 한 건에서는 .5 기준의 만석 쪽에 섰다. 셀 시작값 .585526이 원점수 .474533으로 내려간 이유를 앞차 한 항의 공으로 떼어 말하지는 않는다. 1,176개 고정계수의 합이 만든 값이다.
4. 무엇을 쓰고 무엇을 빼나
Q. 모델에 들어가는 재료는 어디까지인가?
| 묶음 | 쓰는 값 | 맡는 일 |
|---|---|---|
| 위치·시간 | 노선, 정류장 셀 위험, 순번, 분·요일의 주기값, 방향 | 먼저 깔리는 시공간 위험 |
| 직전 통과 | 존재 여부, 만석 여부, 출발 후 잔여석, 지난 초 | 같은 정류장의 최근 상태 |
| 상류·통과 버스 | 존재, stateCd, 잔여석, 축거리, 좌석 변화율, 순번 속도 | 지금 접근하는 차량과 주변 흐름 |
| 노선 응답 | 차량 수, 좌석값이 있는 차량 수, 만석 대수, 잔여석 평균·최소·중앙값 | 한 스냅샷의 노선 전체 상태 |
| 간격·계획 | 공간 헤드웨이, 관측 헤드웨이 상·하한, 계획 배차간격 | 차량 사이 간격과 계획 운행 |
| 학습 정답 | 목표 정류장 출발 이후 잔여석 0 | 만석 라벨. 서빙 입력에는 없음 |
remainSeatCnt=-1은 0으로 바꾸지 않는다. 결측으로 둔 뒤 학습 중앙값과 결측 표시를 함께 넣는다. 좌석 변화율과 속도의 시간축은 로컬 응답 수신시각이다.
Q. 왜 정원·점유율·혼잡도는 빼나?
| 아직 못 써본 재료 | 이유 |
|---|---|
| 정원·점유율 | 코퍼스 차량키는 HMAC 가명이고 정원 사전은 원 vehId라 교집합이 0/77이다. 라벨도 정원이 아니라 출발 후 잔여석 0만으로 정해진다. 관측 최대를 정원으로 대신하면 기간이 늘 때 값이 바뀌는 하한을 분모로 쓰게 된다. 점유율은 안정된 새 관측이 아니다. |
crowded | 공식 제공 노선유형은 13·15·23인데 대상은 11이다. 180초 이하 인접 81,147쌍에서 혼잡도만 바뀐 쌍은 0건이었고 직접 수집분 연속쌍 1,934건에서도 0건이다. 다만 같은 잔여석에 다른 코드가 붙은 값이 26개라 잔여석만의 함수는 아니다. 차량별 정원 임계화일 가능성이 남아 특징 후보로 되살렸다. |
API queryTime | 같은 노선 재호출 38쌍 중 7쌍에서 시간이 되감겼고 직접 호출 92/92에서 로컬 수신보다 미래였다. 나이·속도·헤드웨이 계산에 쓰지 않는다. |
| 목표 차량 ID·validation 라벨 | 차량 암기와 미래 누수를 막는다. 셀 prior도 학습 행마다 자기 라벨을 뺀다. |
보정된 p_full이 .7 이상 .8 미만인 177건의 평균이다.
5. 언제 실패하나
Q. 점수가 좋은데 바로 운영하면 왜 안 되나?
- 정답 자체가 개인 승차 성공은 아니다. 하차한 만큼 승차한 뒤 0석으로 출발해도 실제 승차자는 있다. 모델은 개인이 못 탄 사건이 아니라 좌석이 소진된 사건을 배운다.
p_board도 그 라벨을 뒤집은 보수적 값이다. - 얇은 셀에 맞춘 적응형 부분 풀링이 없다. 셀 prior는 노선 평균 한 건어치만 섞고, 1,176개 계수에는 같은 L2 규제를 건다. 표본 10건 이하 셀이 33.73%다. 새 셀 validation은 2건, 만석 0건뿐이라 외삽 성능은 아직 모른다.
- 고위험 구간이 낙관적이다.
p_full∈[.7,.8)177건의 평균 예측은 .7268, 실제 만석률은 .8701이다. 14.33%p 낮게 잡았다. - isotonic의 ECE는 raw .0098에서 .0065로 내려갔지만 짝지은 개선 구간
[-.0059, +.0009]가 0을 지난다. 단조 계단이 원점수 여러 개를 같은 값으로 묶어 AUC도 .9792에서 .9775로 내려갔다. - 직접 수집분 표본이 아직 얇다. 1회차 재대결의 train은 1,081건·만석 40건, validation은 443건·만석 14건이고 수집 날짜가 하루뿐이다. 이 모형의 직접분 AUC는 0.9650이지만 AUC 비교 21쌍 중 16쌍을 못 가려 승자를 정하지 않았다.
- 학습 범위는 두 노선의 평일 07:00~08:30·18:00~19:30이다. 주말, 새 노선, 새 정류장 조합, 마스터 개편 뒤 성능은 아직 없다.
- 상류 버스가 실제 바로 다음 버스라는 보장이 없다. 90초 이하 관측에서도 숨은 제3차량 하한은 3330 17.24%, 1650 12.01%였다. 잘못 고른 앞차의 잔여석과 상태가 상호작용 전체를 흔든다.
6. 프로토타입과 무엇이 다른가
Q. 예전 화면의 탑승 확률을 회귀식으로 바꾼 것뿐인가?
아니다. 목표와 계산 경계가 달라졌다.
| 프로토타입 | 로지스틱+상호작용 |
|---|---|
화면 탑승률은 셀의 1-만석 관측 비율, 차량 좌석 예보는 별도 정규분포 전파였다. | 출발 후 만석확률 하나만 학습한다. 보수적 탈 확률은 1-p_full이다. |
| 도착 전 좌석과 출발 후 좌석의 뜻이 계산 경로마다 섞였다. | 목표 정류장 도착값은 라벨에서 뺀다. 출발 이후 0석만 만석으로 센다. |
| 셀 비율을 그대로 써 얇은 셀이 0이나 1에 붙었다. | train-only 셀 prior, 자기 라벨 제외 LOO, C=.003 규제를 쓴다. |
| 정류장당 2분, 정규 수요, 10석 여유폭, 만석 연속 1회 이상 같은 고정 규칙이 판정을 만들었다. | 관측 헤드웨이 범위, 절대 잔여석, 상태, 배차, 노선 스냅샷의 계수를 학습한다. 몇 석·몇 분·대기열·이동 추천은 내지 않는다. |
| 확률 보정층이 없었고 화면 탑승률과 차량 예보의 채점 대상도 달랐다. | 시간순 OOF로 raw·Platt·isotonic을 따로 맞추고 같은 label_full로 비교한다. |
좌석 상태를 0~80으로 잘랐고 crowded를 원본에 보존했다. | 정원·점유율·혼잡도를 입력에서 모두 뺀다. 절대 잔여석만 쓴다. |
이 모델도 채택된 운영 모델은 아니다. 직접 수집분에서 만석과 비만석이 함께 모인 뒤 같은 계산을 다시 검증해야 한다.
재료 한눈에
아직 못 써본 것과 그 이유
- 정원·점유율 — 코퍼스 가명 차량키와 정원 사전 원 vehId의 교집합이 0/77이다. 라벨에 정원이 필요 없고, 관측 최대를 정원으로 대신한 점유율은 안정된 새 관측이 아니다.
- crowded — 대상 노선유형 11은 공식 제공 유형 밖이다. 180초 이하 인접 81,147쌍에서 혼잡도만 바뀐 쌍도 0건이다.
- API queryTime — 재호출 38쌍 중 7쌍에서 되감겼고 직접 호출 92/92에서 로컬 수신보다 미래였다. 나이·속도·헤드웨이에 쓰지 않는다.
- 목표 차량 ID·validation 라벨 — 차량 암기와 미래 누수를 막기 위해 입력에서 뺀다. 학습 셀 prior에서도 자기 라벨을 제외한다.
아래 성적은 남의 20일 코퍼스로 낸 것이다. 그 코퍼스는 차량 ID가 가명이라 정원·차종·저상 여부를 붙일 수 없었다(조인 0/77). 이 재료들은 테스트 자체를 못 했다.
우리 직접 수집분에는 원본 차량 ID가 남아 정원이 실제로 붙는다 — 고유차량 55/62대(88.7%). 다만 1회차 재대결에서 정원·차종을 넣어도 뚜렷한 개선은 확인되지 않았다. 표본이 얇아 판정이 안 될 뿐, 쓸모없다는 뜻은 아니다. 관측 최대 잔여석으로 정원을 추정하는 것은 계속 금지다 — 원본 ID를 써도 그 값은 관측할수록 올라가서 정원이 아니다.
직접 수집분으로 다시 잰 성적 1회차
우리 수집기가 직접 쌓은 데이터만 쓰고 코퍼스는 뺐다. 검증분 443행·만석 14건, 정류소×날짜 125군집 부트스트랩 5,000회.
| 지표 | 직접 수집분 (95% 구간) | 코퍼스 |
|---|---|---|
| 확률이 정직한가 | 0.0271[0.0172, 0.0444] | 0.0065 |
| 얼마나 빗나갔나 | 0.0223[0.0100, 0.0367] | 0.0231 |
| 확신하고 틀린 벌점 | 0.0918[0.0563, 0.1332] | 0.0787 |
| 만석을 골라내나 | 0.9650[0.9389, 0.9916] | 0.9775 |
표본이 얇아 21쌍 중 16쌍이 구분되지 않았다. 검증 날짜가 하루뿐이라 날짜 간 변동도 못 본다. 21쌍 중 16쌍을 못 가리고 validation 날짜가 1개라 전체 순위를 발표하지 않는다
코퍼스 순위가 뒤집힌 쌍은 0개다. 방향은 틀리지 않았고 미세한 차이만 미지로 남았다. ΔAUC 0.05를 가리려면 8 수집일, 날짜 변동까지 보려면 15일이 더 든다.
주의 — 이번 실행에서는 보정층이 작동하지 않았다. 검증 날짜가 하루뿐이라 적격 보정 fold가 없었다. 이름에 isotonic이 붙어 있어도 위 숫자는 보정 전 예측이다. 보정 효과는 반증된 것이 아니라 아직 재지 못한 것이다.
쓰는 재료 33개 — 출처 필드까지
| 재료 | 종류 | 어느 API의 어느 필드 |
|---|---|---|
직전 통과 만석 여부 (previous_passage_is_full)인과 조건을 만족하는 가장 최근 같은 정류장 통과의 label_full을 가져온다.주의 — 직전 API 응답 한 대와 같은 뜻이 아니다. 직전으로 확정된 통과 사건이다. | 파생 | S1 passage_events.label_full, passage_events.label_observed_at, passage_events.passage_interval_end; 특징 previous_passage_is_fullS1 passage_events |
직전 통과 출발 후 잔여석 (previous_passage_remain_seats)인과 조건을 만족하는 같은 정류장의 가장 최근 통과에서 label_remain_seats를 가져온다.주의 — 현재 목표 차량의 출발 후 좌석이 아니다. | 파생 | S1 passage_events.label_remain_seats, passage_events.label_observed_at, passage_events.passage_interval_end; 특징 previous_passage_remain_seatsS1 passage_events |
직전 통과 라벨 나이 (previous_passage_label_age_sec)현재 질의 응답 수신시각에서 직전 통과 라벨 관측시각을 뺀다.주의 — msgHeader.queryTime으로 빼지 않는다. | 파생 | timing.response_received_at.kst, timing.response_received_at.utc; S1 E4 response_received_kst, response_received_utc; 코퍼스 collectedAt; S1 passage_events.label_observed_at, previous_passage_label_age_sec우리 수집기 응답 수신시각 + S1 passage_events |
상류 버스 잔여석 (upstream_remain_seats)축거리와 동률 규칙으로 고른 상류 차량의 remainSeatCnt를 붙인다.주의 — -1은 0으로 바꾸지 않고 결측으로 둔다. 이 값은 목표 차량의 미래 라벨이 아니다. | 파생 | msgBody.busLocationList[].remainSeatCnt, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd, msgBody.busLocationList[].vehId; S1 upstream_remain_seatsGBIS 버스위치정보 getBusLocationListv2 + S1 최근접 상류차 선택 |
상류 버스 축거리 (upstream_axis_gap_seq)상류 후보 중 목표에 가장 가까운 차량을 고르고 target station_seq-last_passed를 계산한다.주의 — 동률은 출발 stateCd=2, 교차 0, 도착 1 순, 더 최신 운행, 차량키 사전순으로 푼다. 좌석값은 선택 기준이 아니다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; S1 정규화 last_passed; S1 upstream_axis_gap_seqGBIS 버스위치정보 getBusLocationListv2 + S1 스냅샷 특징 빌더 |
상류 버스 좌석 변화율 (upstream_seat_change_per_min)선택된 차량을 직전 적격 응답의 같은 차량과 잇고 (현재 좌석-직전 좌석)/(경과초/60)을 계산한다.주의 — 두 좌석값이 비음수이고 0<경과<=90초, 정규화 순번 변화가 0 또는 1일 때만 만든다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd, msgBody.busLocationList[].remainSeatCnt; timing.response_received_at.kst, timing.response_received_at.utc; S1 upstream_seat_change_per_minGBIS 버스위치정보 getBusLocationListv2 + 우리 수집기 응답 수신시각 + S1 차량 연결 |
상류 버스 축 속도 (upstream_speed_seq_per_min)같은 차량의 정규화 순번 변화량을 (경과초/60)으로 나눈다.주의 — 0<경과<=90초이고 순번 변화가 0 또는 1일 때만 만든다. +2 이상 도약은 속도로 쓰지 않는다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; timing.response_received_at.kst, timing.response_received_at.utc; S1 upstream_speed_seq_per_minGBIS 버스위치정보 getBusLocationListv2 + 우리 수집기 응답 수신시각 + S1 차량 연결 |
지난 버스 잔여석 (passed_remain_seats)축거리와 동률 규칙으로 고른 지난 차량의 remainSeatCnt를 붙인다.주의 — -1은 결측이다. 목표 정류장을 지난 뒤의 관측이라도 목표 차량의 정답 라벨과는 다른 스냅샷 차량이다. | 파생 | msgBody.busLocationList[].remainSeatCnt, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd, msgBody.busLocationList[].vehId; S1 passed_remain_seatsGBIS 버스위치정보 getBusLocationListv2 + S1 최근접 지난 차 선택 |
지난 버스 축거리 (passed_axis_gap_seq)이미 지난 후보 중 목표에 가장 가까운 차량을 고르고 last_passed-target station_seq를 계산한다.주의 — 동률 규칙은 상류 선택과 같다. 좌석값은 선택 기준이 아니다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; S1 정규화 last_passed; S1 passed_axis_gap_seqGBIS 버스위치정보 getBusLocationListv2 + S1 스냅샷 특징 빌더 |
목표 축 순번 (station_seq)stateCd=1이면 현재 stationSeq-1, stateCd=0·2이면 현재 stationSeq를 마지막 통과 순번으로 놓는다. 인접 적격 응답에서 이 값이 전진한 끝점을 통과 사건 순번으로 삼는다.주의 — 모델 축은 routestation 전체 순번이다. 학습 행은 서비스 정차점만 남겨도 축 간격에는 미정차·경유 지점이 들어간다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; routestation.staOrder; S1 station_seqGBIS 버스위치정보 getBusLocationListv2 + GBIS routestation 마스터 + S1 통과 사건 빌더 |
KST 하루 중 분 (query_minute_of_day)KST 시각에서 hour*60+minute을 계산한다.주의 — msgHeader.queryTime은 쓰지 않는다. 초는 버린다. | 파생 | timing.response_received_at.kst, timing.response_received_at.utc; S1 E4 response_received_kst, response_received_utc; 코퍼스 collectedAt; S1 query_minute_of_day우리 수집기 응답 수신시각; 제한 코퍼스는 수집기가 붙인 collectedAt |
계획 배차간격 (master_dispatch_interval_min)현재 구현은 평일 06:30~09:29·17:00~20:29에 peekAlloc, 그 밖 평일에 npeekAlloc을 고른다. 토요일은 satNpeekAlloc, 일요일은 sunNpeekAlloc을 고른다.주의 — 계획값이지 실제 헤드웨이가 아니다. 현재 코드의 피크 조건에 평일 검사가 들어가 토·일 *PeekAlloc 분기는 닿지 않는다. we*도 고르지 않는다. | 파생 | route.peekAlloc, route.npeekAlloc, route.satPeekAlloc, route.satNpeekAlloc, route.sunPeekAlloc, route.sunNpeekAlloc, route.wePeekAlloc, route.weNpeekAlloc; timing.response_received_at.kst, timing.response_received_at.utc; S1 master_dispatch_interval_minGBIS route 마스터 + 우리 수집기 응답 수신시각 + S1 특징 빌더 |
응답 내 노선 차량 수 (route_vehicle_count)make_observation 정규화에 성공한 차량 행 수를 센다.주의 — 실제 운행 대수 보장이 아니다. 차량키·순번·상태가 잘못된 원행은 먼저 빠진다. 정상 0대와 실패도 학습 스냅샷 행이 아니다. | 파생 | msgBody.busLocationList[]; S1 E4 derived/observations.jsonl 행; 코퍼스 vehicles[]; S1 route_vehicle_countGBIS 버스위치정보 getBusLocationListv2 |
좌석값이 있는 차량 수 (route_known_seat_count)remainSeatCnt>=0인 차량 수를 센다.주의 — -1은 포함하지 않는다. | 파생 | msgBody.busLocationList[].remainSeatCnt; S1 route_known_seat_countGBIS 버스위치정보 getBusLocationListv2 |
응답 내 0석 차량 수 (route_full_bus_count)remainSeatCnt==0인 차량 수를 센다.주의 — 응답에 잡힌 차량만 센다. | 파생 | msgBody.busLocationList[].remainSeatCnt; S1 route_full_bus_countGBIS 버스위치정보 getBusLocationListv2 |
노선 응답 잔여석 평균 (route_seat_mean)응답 안의 비음수 remainSeatCnt에 산술평균을 적용한다.주의 — remainSeatCnt=-1은 제외한다. 유효 좌석값이 없으면 결측이다. | 파생 | msgBody.busLocationList[].remainSeatCnt; S1 route_seat_meanGBIS 버스위치정보 getBusLocationListv2 + S1 노선 응답 요약 |
노선 응답 잔여석 최솟값 (route_seat_min)응답 안의 비음수 remainSeatCnt에 최솟값을 적용한다.주의 — remainSeatCnt=-1은 제외한다. 유효 좌석값이 없으면 결측이다. | 파생 | msgBody.busLocationList[].remainSeatCnt; S1 route_seat_minGBIS 버스위치정보 getBusLocationListv2 + S1 노선 응답 요약 |
노선 응답 잔여석 중앙값 (route_seat_median)응답 안의 비음수 remainSeatCnt에 numpy.quantile 기본 선형 보간의 0.50 분위수를 적용한다.주의 — remainSeatCnt=-1은 제외한다. 유효 좌석값이 없으면 결측이다. | 파생 | msgBody.busLocationList[].remainSeatCnt; S1 route_seat_medianGBIS 버스위치정보 getBusLocationListv2 + S1 노선 응답 요약 |
스냅샷 공간 헤드웨이 (spatial_headway_axis_seq)선택된 지난 차량의 last_passed에서 선택된 상류 차량의 last_passed를 뺀다.주의 — 양쪽 차량이 모두 있어야 한다. 시간 헤드웨이가 아니라 전체 순번축의 간격이다. | 파생 | msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd, msgBody.busLocationList[].vehId; S1 spatial_headway_axis_seqGBIS 버스위치정보 getBusLocationListv2 + S1 상류·지난 차 선택 |
| 관측 헤드웨이 하한 (observed_headway_lower_sec)같은 원천·노선·정류장의 최근 통과 시작에서 그 전 통과 끝을 빼고 0 아래를 자른다.주의 — 정확 통과시각 차가 아니라 두 검열 구간이 허용하는 하한이다. | 파생 | S1 passage_events.passage_interval_start, passage_events.passage_interval_end; 특징 observed_headway_lower_secS1 passage_events의 연속 통과 구간 |
| 관측 헤드웨이 상한 (observed_headway_upper_sec)최근 통과 끝에서 그 전 통과 시작을 빼고 0 아래를 자른다.주의 — 계획 배차간격과 다른 관측 구간 상한이다. | 파생 | S1 passage_events.passage_interval_start, passage_events.passage_interval_end; 특징 observed_headway_upper_secS1 passage_events의 연속 통과 구간 |
요일 (query_day_of_week)KST 날짜에 Python weekday()를 적용한다. 월요일 0, 일요일 6이다.주의 — 공휴일 표시는 없다. 요일만 구분한다. | 파생 | timing.response_received_at.kst, timing.response_received_at.utc; S1 E4 response_received_kst, response_received_utc; 코퍼스 collectedAt; S1 query_day_of_week우리 수집기 응답 수신시각; 제한 코퍼스는 수집기가 붙인 collectedAt |
노선 (route)직접 수집은 calls.jsonl.route_name, 코퍼스는 route.name을 모델 범주로 읽고 같은 route.routeName 마스터 행을 찾는다. 수집기 설정의 route.route_id와 원응답 routeId는 원문에 남는다.주의 — 현재 S1 구현은 실제로 routeName으로 마스터를 찾는다. msgBody.busLocationList[].routeId를 모델의 route로 복사하지 않는다. 대상 두 노선 밖으로 넓힐 때는 ID 검증이 필요하다. | 응답 원본 | msgBody.busLocationList[].routeId; 운영 수집기 route.name, route.route_id; S1 E4 calls.jsonl.route_name; 코퍼스 route.name; route.routeName, route.routeId; S1 routeGBIS 버스위치정보 getBusLocationListv2 + 우리 수집기 노선 메타데이터 + 제한 코퍼스 + GBIS route 마스터 |
회차 전 여부 (is_outbound)station_seq<=turn_seq면 1, 아니면 0이다.주의 — 공식 upDown을 그대로 쓴 값이 아니다. 내부 순번축을 회차점에서 둘로 나눈 표시다. | 파생 | route.turnSeq, S1 station_seq; S1 is_outboundGBIS route 마스터 + S1 통과 사건 |
| 직전 통과 라벨 존재 (previous_passage_exists)같은 원천·노선·정류장에서 질의시각 전에 구간이 끝나고 라벨까지 관측된 가장 최근 통과가 있으면 1이다.주의 — 현재 행의 미래 라벨은 후보가 될 수 없다. 원천을 넘겨 연결하지 않는다. | 파생 | S1 passage_events.source, passage_events.route, passage_events.station_seq, passage_events.passage_interval_end, passage_events.label_observed_at, passage_events.label_status; 특징 previous_passage_existsS1 passage_events |
상류 버스 존재 (upstream_bus_exists)정규화 마지막 통과 순번이 목표 station_seq보다 작은 차량이 하나라도 있으면 1이다.주의 — API가 노선의 모든 차량을 포착한다는 뜻은 아니다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; S1 정규화 last_passed; S1 upstream_bus_existsGBIS 버스위치정보 getBusLocationListv2 + S1 스냅샷 특징 빌더 |
이미 지난 버스 존재 (passed_bus_exists)정규화 마지막 통과 순번이 목표 station_seq 이상인 차량이 하나라도 있으면 1이다.주의 — 그 차량이 사용자가 탈 다음 차량이라는 뜻은 아니다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; S1 정규화 last_passed; S1 passed_bus_existsGBIS 버스위치정보 getBusLocationListv2 + S1 스냅샷 특징 빌더 |
상류 버스 상태 (upstream_state_cd)선택된 상류 차량의 원 stateCd를 붙인다.주의 — 로지스틱은 0·1·2를 원핫 표시로 바꾼다. 상태 의미와 축 위치 정규화는 별개다. | 파생 | msgBody.busLocationList[].stateCd, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].vehId; S1 upstream_state_cdGBIS 버스위치정보 getBusLocationListv2 + S1 최근접 상류차 선택 |
지난 버스 상태 (passed_state_cd)선택된 지난 차량의 원 stateCd를 붙인다.주의 — 로지스틱은 0·1·2 원핫 표시로 바꾼다. | 파생 | msgBody.busLocationList[].stateCd, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].vehId; S1 passed_state_cdGBIS 버스위치정보 getBusLocationListv2 + S1 최근접 지난 차 선택 |
목표 정류장 ID (station_id)차량별 정규화 위치가 전진한 끝점의 station_seq를 만들고, 같은 노선의 staOrder와 맞춰 stationId를 붙인다.주의 — 응답의 msgBody.busLocationList[].stationId를 목표 통과 정류장 ID로 복사하지 않는다. 그 값은 응답 시점 차량 위치의 정류장이다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; routestation.routeName, routestation.staOrder, routestation.stationId; S1 station_idGBIS 버스위치정보 getBusLocationListv2 + GBIS routestation 마스터 + S1 통과 사건 빌더 |
KST 30분대 (query_half_hour)응답 수신시각을 KST로 바꾸고 hour*2 + 1[minute>=30]으로 0~47을 만든다.주의 — msgHeader.queryTime과 HTTP Date는 시간 계산에 쓰지 않는다. 코퍼스 collectedAt은 본문 파싱 뒤 시각이다. | 파생 | timing.response_received_at.kst, timing.response_received_at.utc; S1 E4 response_received_kst, response_received_utc; 코퍼스 collectedAt; S1 query_half_hour우리 수집기 응답 수신시각; 제한 코퍼스는 수집기가 붙인 collectedAt |
| 훈련 셀 사전 로그오즈 (train_only_cell_prior_logit)노선×정류장×30분 셀의 만석률을 전역 사전으로 축소해 로그오즈로 바꾼다. 훈련 행은 자기 라벨을 뺀 leave-one-out 집계를 쓴다.주의 — 예측·validation에는 훈련 전체 집계만 적용한다. 원 API 필드가 아니며 현재 행 라벨을 섞지 않는다. | 파생 | S1 route, station_id, query_half_hour, label_full; 모델 내부 train_only_cell_prior_logit로지스틱+상호작용 내부 경험적 베이즈 집계 |
출발 후 만석 라벨 (label_full)정류장 s 통과 뒤 같은 차량에서 (stateCd,stationSeq)=(2,s),(0,s),(1,s+1) 중 처음 잡힌 유효 좌석값을 찾는다. 0이면 1, 양수면 0이다.주의 — remainSeatCnt=-1은 결측이다. (stateCd,stationSeq)=(1,s)는 도착 상태라 라벨이 아니다. 예측 시점 뒤에 생기므로 학습 정답으로만 쓴다. | 파생 | msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd, msgBody.busLocationList[].remainSeatCnt; timing.response_received_at.kst, timing.response_received_at.utc; S1 passage_events.label_full, label_remain_seatsGBIS 버스위치정보 getBusLocationListv2 + 우리 수집기 응답 수신시각 + S1 통과 사건 빌더 |
지금은 안 쓰는 재료 4개
- 차량 정원·점유율 —
TBBMSVEHINFOM.SEAT_RDNG_PSN_CNT; 점유율 후보식1-msgBody.busLocationList[].remainSeatCnt/TBBMSVEHINFOM.SEAT_RDNG_PSN_CNT20일 코퍼스 차량키가plateNo우선 HMAC이라 외부 정원과 조인된 차량이 0/77이다. 기존 231,048행의capacity_exact도 모두 0이다. 관측 최대 잔여석은 정원의 하한일 뿐 고정 정원이 아니다. 라벨은 출발 후 좌석이 0인지에 정원이 필요 없다. 프로토타입의defaultSeatCapacity=80은 분포 배열 상한이며 점유율 분모가 아니다. - 혼잡도 — GBIS getBusLocationListv2
msgBody.busLocationList[].crowded공식 제공 노선유형은 13·15·23이고 대상 노선은 11이다. 직접 수집분에서 같은 잔여석 값에 다른 코드가 붙은 값이 26개 나와 잔여석만의 전역 함수라는 해석은 반증됐다. 다만 같은(vehId, remainSeatCnt)에서는 충돌이 0셀이고crowded만 바뀐 연속쌍도 0건이라 차량별 임계화일 가능성이 남는다. 산식은 미확정이고 아직 특징으로 써 보지 않았다. - API queryTime 파생 시간 — GBIS getBusLocationListv2
msgHeader.queryTime; HTTP 응답 헤더Date같은 노선 연속 38쌍 중 7쌍에서queryTime이 뒤로 갔다. 직접 수집 92/92건에서는 응답 수신시각보다 미래였고 중앙 +17.265초였다. HTTPDate는queryTime의 초 절삭과 92/92 일치해 독립 시계가 아니다. 감사 원문만 보존하고 나이·속도·헤드웨이·분할에 쓰지 않는다. - 목표 차량 ID와 미래 출발 후 좌석·라벨 —
msgBody.busLocationList[].remainSeatCnt; S1label_remain_seats,label_full,target_vehicle_hashtarget_vehicle_hash는 차량 암기를 막기 위해 감사키로만 둔다. 출발 후 좌석과label_full은 목표 통과 뒤 생기는 미래 정답이라 학습의 y로만 쓰고 예측 행렬에는 넣지 않는다.
언제 실패하나
- 정답 자체가 개인 승차 성공은 아니다. 하차한 만큼 승차한 뒤 0석으로 출발해도 실제 승차자는 있다. 모델은 개인이 못 탄 사건이 아니라 좌석이 소진된 사건을 배운다.
p_board도 그 라벨을 뒤집은 보수적 값이다. - 얇은 셀에 맞춘 적응형 부분 풀링이 없다. 셀 prior는 노선 평균 한 건어치만 섞고, 1,176개 계수에는 같은 L2 규제를 건다. 표본 10건 이하 셀이 33.73%다. 새 셀 validation은 2건, 만석 0건뿐이라 외삽 성능은 아직 모른다.
- 고위험 구간이 낙관적이다.
p_full∈[.7,.8)177건의 평균 예측은 .7268, 실제 만석률은 .8701이다. 14.33%p 낮게 잡았다. - isotonic의 ECE는 raw .0098에서 .0065로 내려갔지만 짝지은 개선 구간
[-.0059, +.0009]가 0을 지난다. 단조 계단이 원점수 여러 개를 같은 값으로 묶어 AUC도 .9792에서 .9775로 내려갔다. - 직접 수집분 표본이 아직 얇다. 1회차 재대결의 train은 1,081건·만석 40건, validation은 443건·만석 14건이고 수집 날짜가 하루뿐이다. 이 모형의 직접분 AUC는 0.9650이지만 AUC 비교 21쌍 중 16쌍을 못 가려 승자를 정하지 않았다.
- 학습 범위는 두 노선의 평일 07:00~08:30·18:00~19:30이다. 주말, 새 노선, 새 정류장 조합, 마스터 개편 뒤 성능은 아직 없다.
- 상류 버스가 실제 바로 다음 버스라는 보장이 없다. 90초 이하 관측에서도 숨은 제3차량 하한은 3330 17.24%, 1650 12.01%였다. 잘못 고른 앞차의 잔여석과 상태가 상호작용 전체를 흔든다.
기존 프로토타입과 다른 점
- 프로토타입은 화면의 셀 비만석률과 차량 좌석 예보가 별도 계산이었다. 이 모델은 출발 후 만석확률 하나만 학습하고 보수적 탈 확률을 1-p_full로 낸다.
- 프로토타입은 도착 전·출발 후 좌석 의미가 경로마다 섞였다. 이 모델은 목표 정류장 도착값을 빼고 출발 이후 0석만 만석으로 센다.
- 프로토타입은 셀 비율을 그대로 썼다. 이 모델은 train-only 셀 prior, 자기 라벨 제외 LOO, C=.003 규제를 쓴다.
- 프로토타입은 정류장당 2분·정규 수요·10석 여유폭·만석 연속 임계 규칙을 썼다. 이 모델은 관측 헤드웨이·잔여석·상태·배차·노선 스냅샷의 계수를 학습한다.
- 프로토타입에는 확률 보정층이 없었다. 이 모델은 시간순 OOF로 raw·Platt·isotonic을 맞추고 같은 label_full로 비교한다.
- 프로토타입은 좌석 상태를 0~80으로 잘랐다. 이 모델은 정원·점유율·혼잡도를 입력에서 빼고 절대 잔여석만 쓴다.