후보 모델

못 본 것도 세기

구간 검열 반영

못 본 통과를 가짜 정답으로 채우지 않는다. 양끝 상태 사이의 가능한 경로를 모두 우도에 남긴다.

확률이 정직한가0.0053얼마나 빗나갔나0.0268확신하고 틀린 벌점0.0878만석을 골라내나0.9751

Q. 사진 사이에서 사라진 통과는 버려야 하나?

아니다. 폴링은 버스를 계속 보는 영상이 아니라 띄엄띄엄 찍는 사진이다. 앞 사진에서 순번 20이던 버스가 다음 사진에서 순번 23이면, 21과 22의 출발 후 좌석 상태는 보이지 않는다. 그렇다고 두 통과가 없었던 일은 아니다.

재구축 자료의 검열행은 2,779건이다. 이 가운데 정상 순번 도약에서 생긴 미관측 중간 통과가 2,090건이다. 물리적으로 불가능한 이동 689건은 PX로 빼고 라벨 우도에 넣지 않았다. 시간 경계, 정차 정류장, 90초 연속성까지 통과해 코퍼스 train 우도에 들어간 것은 양끝 상태쌍 800개다. 그 안에서 중간 통과 821개를 잠재 상태로 합산했다. 점 라벨을 새로 만든 건 0건이다.

이 재료가 필요한 배경은 셀의 얇음이다. 노선×정류장×30분 셀은 833개다. 표본 10건 이하가 281개, 33.73%다. 만석을 한 번도 못 본 셀은 719개, 86.31%다. 셀 빈도만 믿으면 “못 봤다”를 “없다”로 읽기 쉽다. 구간 검열 모형은 셀 위험을 축소한 뒤, 폴링 사이 전이가 남긴 정보를 따로 보탠다.

코퍼스 보조 검증에서는 잠정 운영 선두다. 검증 표본은 10,040건, 만석은 583건이다.

모형ECEBrierlog lossAUC
정류장×시간 기준선.0063.0327.1033.9613
구간 검열 raw.0200.0284.0968.9748
구간 검열 isotonic.0053.0268.0878.9751

전이 정보만 섞은 raw는 순위를 잘 세웠지만 확률 눈금은 틀어졌다. 시간순 OOF로 학습한 isotonic 보정이 그 눈금을 다시 맞췄다. 기준선 대비 ECE 차이 -.0010의 95% 구간은 [-.0037, +.0040]이라 보정 개선 자체는 유의하지 않다. Brier와 log loss, AUC의 짝지은 95% 구간은 모두 0을 포함하지 않았다.

두 갈래가 한 확률이 되는 과정
노선×정류장×30분 기록
의사표본 강도 4
축소 셀 위험
양끝 상태·못 본 통과 수
중간 상태 합산
T^k 전이 위험
10%
log-odds 90:10 혼합
OOF 10,236건
isotonic 보정
1에서 뺌
탈 확률 = 1-p_full

축소 셀 위험과 검열 전이 위험을 섞고, 시간순 OOF 보정 뒤 탈 확률로 뒤집는다.

Q. 양끝만 보고 가운데를 어떻게 세나?

Q. 무엇이 보이고 무엇이 안 보이나?

상태는 둘만 둔다. 출발 후 잔여석 0이면 F(만석), 양수면 A(여유)다. -1은 결측이라 어느 쪽에도 넣지 않는다.

예를 들어 F → ? → A가 관측됐다고 하자. 가운데를 FA로 찍지 않는다. F→F→AF→A→A 두 경로를 모두 남긴다. 못 본 상태가 두 개면 가능한 네 경로를 모두 더한다.

Q. 모든 경로를 직접 늘어놓나?

아니다. 두 상태의 전이표를 T라 두고 작은 우도 하나만 계산한다.

P(Y_end | Y_start, T^k)

k는 양끝 사이의 축상 이동 칸 수다. T^k가 중간의 모든 F/A 경로를 한 번에 합친다. 가짜 행도, 가짜 통과시각도 생기지 않는다.

전이표는 노선, 오전·오후, 정류장 순번 10칸 묶음으로 나눈 29개 그룹에서 배운다. 각 그룹에는 A→FF→A 두 확률이 있다. 그룹 표본이 얇으면 전역 전이 쪽으로 당긴다. 선택된 전이 규제값은 5다.

Q. 전이 확률만으로 답하나?

아니다. 첫 갈래는 노선×정류장×30분 셀 위험이다. 만석 수와 표본 수를 세고, 의사표본 강도 4로 노선 평균 쪽에 축소한다. 둘째 갈래는 가장 가까운 상류 관측 버스의 0석/양수, 목표까지의 내부 축거리, 위 전이표로 계산한 위험이다.

두 갈래는 확률 자체가 아니라 log-odds에서 90:10으로 섞는다. 상류 버스나 축거리가 없으면 셀 위험만 쓴다. 마지막에는 train 안의 날짜 순방향 OOF 10,236건, 만석 572건으로 배운 isotonic 계단을 통과한다. validation 라벨은 이 과정에 들어가지 않았다.

가짜 복원과 주변화의 차이
관측: F → ? → A가짜 복원: F → F → A 한 경로만 학습
주변화: F→F→A와 F→A→A를 함께 학습

관측은 F와 A 두 끝뿐이다. 가운데 상태를 하나로 찍지 않는다.

Q. 실제 한 행은 어떻게 계산되나?

2026년 8월 3일 07:12:25 KST, 1650번 부흥고등학교.임광아파트 정류장이다. 정류장 ID는 209000147, 내부 순번은 54다. 가장 가까운 상류 관측 버스는 축상 2칸 앞에 있었고 잔여석은 0이었다. 실제 출발 후 라벨도 만석이었다. 아래 값은 소수 넷째 자리로 반올림했다.

단계실측값과 계산만석 확률
셀 위험train 14건 중 만석 7건. 1650 노선 사전확률 .0688을 의사표본 4개로 섞음: (7 + 4×.0688) / 18.4042
전이 위험1650·오전·순번 51~60 그룹의 A→F=.0033, F→A=.0376. 만석에서 2칸 뒤 만석은 .9624×.9624 + .0376×.0033.9263
raw 혼합셀 .4042와 전이 .9263의 log-odds를 90:10으로 혼합.4760
isotonic 보정과거 OOF에서 배운 단조 계단 적용.5976
사용자용 변환탈 확률 = 1 - 만석 확률.4024

전이 갈래만 보면 만석 지속 위험이 높다. 셀 갈래는 그보다 낮다. 10%만 섞었는데도 raw가 .4042에서 .4760으로 올랐다. 보정기는 이 raw 구간이 과거에 실제 만석으로 더 자주 이어졌다고 보고 .5976으로 올렸다.

Q. 무엇을 쓰고 무엇을 버리나?

쓰는 재료쓰는 이유
노선, 정류장 ID, 내부 순번, 로컬 시각축소 셀과 전이 그룹을 만든다. 시각은 응답 수신 KST만 쓴다.
출발 후 0/양수 라벨F/A 양끝 상태와 최종 정답을 만든다. 내 정류장 도착값은 쓰지 않는다.
정상 순번 도약의 양끝, 못 본 통과 수T^k 우도에 넣는다. 중간 상태는 복원하지 않는다.
가장 가까운 상류 관측 버스의 잔여석과 내부 축거리예측 시점의 시작 상태와 전이 횟수를 정한다. 차량 식별자는 모델 입력이 아니다.
날짜 순방향 OOF 예측 10,236건isotonic 보정층의 눈금을 잡는다.
안 쓰는 재료이유
정원·점유율라벨은 출발 후 잔여석 0만으로 정해진다. 코퍼스 차량키는 정원 자료와 0/77 조인이다. 관측 최대는 정원이 아니라 하한이고 시간에 따라 바뀐다.
crowded대상은 노선유형 11인데 공식 제공 유형은 13·15·23이다. 180초 이하 인접 81,147쌍에서 혼잡도만 바뀐 쌍은 0건이었다. 직접 수집분에서 같은 잔여석에 다른 코드가 붙은 값이 26개 나와 잔여석만의 함수는 아니다. 다만 같은 차량·같은 잔여석에서는 충돌이 0셀이라 차량별 정원 임계화일 가능성이 남는다. 산식이 미확정이라 이 모형에서는 아직 쓰지 않는다.
queryTime직접 수집 92호출 전부가 로컬 수신 시각보다 미래였고 중앙 +17.265초였다. 되감김은 인접 호출 전체 16건, 같은 노선 안 2건이다. HTTP Date도 초 절삭까지 92/92 일치해 독립 시계가 아니다. 경과시간 계산에 쓰지 않는다.
잔여석 -169건 모두 결측이다. 0으로 바꾸면 가짜 만석이 생긴다.
물리 불가능 이동 PX689건이다. 전이도 라벨도 만들지 않는다.
부흥고등학교 실제 검증행
단계만석 확률
축소 셀 위험.4042
2칸 전이 위험.9263
90:10 raw 혼합.4760
isotonic 보정.5976
탈 확률.4024

2026-08-03 07:12:25 KST, 1650번, 내부 순번 54의 계산이다.

Q. 언제 실패하나?

Q. 프로토타입의 검열과 무엇이 다른가?

같은 “검열”이라는 말이 같은 계산을 뜻하나? 아니다.

프로토타입구간 검열 반영 모형
좌석 변화 관측쌍의 양끝 중 하나가 0석이면 수요 숫자를 버리고 censoredWeight만 올렸다.폴링 사이에 못 본 중간 통과의 F/A를 잠재 상태로 두고 양끝 우도에 넣는다.
화면의 탑승률은 정류장×시간 셀의 1-zeroCount/samples였다. 차량별 좌석 예보와 다른 계산이었다.출발 후 만석확률 하나를 내고, 사용자용 탈 확률은 1-p_full로만 바꾼다.
좌석값을 도착 전과 처리 후 의미로 섞어 읽는 경로가 있었다.내 정류장 도착값은 금지한다. 출발 이후 0만 만석 라벨이다.
확률 보정층이 없었다.날짜 순방향 OOF로 isotonic을 학습한다. validation 라벨은 보지 않는다.
중간 상태 전이표가 없었다.T^k가 가능한 중간 상태 경로를 합산한다. 점 통과시각과 가짜 행은 만들지 않는다.

계승한 원칙도 있다. -1과 0을 가르고, 보지 못한 값을 사실처럼 만들지 않는다. 달라진 점은 못 본 사건을 경고 숫자로만 남기지 않고 학습 우도에 직접 넣었다는 데 있다.

재료 한눈에

노선, 정류장 ID, 내부 순번, 로컬 시각 — 축소 셀과 전이 그룹을 만든다. 시각은 응답 수신 KST만 쓴다.출발 후 0/양수 라벨 — F/A 양끝 상태와 최종 정답을 만든다. 내 정류장 도착값은 쓰지 않는다.정상 순번 도약의 양끝, 못 본 통과 수 — T^k 우도에 넣는다. 중간 상태는 복원하지 않는다.가장 가까운 상류 관측 버스의 잔여석과 내부 축거리 — 예측 시점의 시작 상태와 전이 횟수를 정한다. 차량 식별자는 모델 입력이 아니다.날짜 순방향 OOF 예측 10,236건 — isotonic 보정층의 눈금을 잡는다.정원·점유율crowdedqueryTime잔여석 -1물리 불가능 이동 PX

아직 못 써본 것과 그 이유

주의 — 이건 "검증해서 버린 재료"가 아니다

아래 성적은 남의 20일 코퍼스로 낸 것이다. 그 코퍼스는 차량 ID가 가명이라 정원·차종·저상 여부를 붙일 수 없었다(조인 0/77). 이 재료들은 테스트 자체를 못 했다.

우리 직접 수집분에는 원본 차량 ID가 남아 정원이 실제로 붙는다 — 고유차량 55/62대(88.7%). 다만 1회차 재대결에서 정원·차종을 넣어도 뚜렷한 개선은 확인되지 않았다. 표본이 얇아 판정이 안 될 뿐, 쓸모없다는 뜻은 아니다. 관측 최대 잔여석으로 정원을 추정하는 것은 계속 금지다 — 원본 ID를 써도 그 값은 관측할수록 올라가서 정원이 아니다.

직접 수집분으로 다시 잰 성적 1회차

우리 수집기가 직접 쌓은 데이터만 쓰고 코퍼스는 뺐다. 검증분 443행·만석 14건, 정류소×날짜 125군집 부트스트랩 5,000회.

지표직접 수집분 (95% 구간)코퍼스
확률이 정직한가0.0138[0.0072, 0.0351]0.0053
얼마나 빗나갔나0.0244[0.0117, 0.0389]0.0268
확신하고 틀린 벌점0.0987[0.0565, 0.1469]0.0878
만석을 골라내나0.9093[0.8462, 0.9711]0.9751
이 숫자로 순위를 매기지 마라

표본이 얇아 21쌍 중 16쌍이 구분되지 않았다. 검증 날짜가 하루뿐이라 날짜 간 변동도 못 본다. 21쌍 중 16쌍을 못 가리고 validation 날짜가 1개라 전체 순위를 발표하지 않는다

코퍼스 순위가 뒤집힌 쌍은 0개다. 방향은 틀리지 않았고 미세한 차이만 미지로 남았다. ΔAUC 0.05를 가리려면 8 수집일, 날짜 변동까지 보려면 15일이 더 든다.

주의 — 이번 실행에서는 보정층이 작동하지 않았다. 검증 날짜가 하루뿐이라 적격 보정 fold가 없었다. 이름에 isotonic이 붙어 있어도 위 숫자는 보정 전 예측이다. 보정 효과는 반증된 것이 아니라 아직 재지 못한 것이다.

쓰는 재료 17개 — 출처 필드까지

재료종류어느 API의 어느 필드
노선 (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 마스터
목표 정류장 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
출발 후 만석 라벨 (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 통과 사건 빌더
상류 버스 잔여석 (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_bus_exists)정규화 마지막 통과 순번이 목표 station_seq보다 작은 차량이 하나라도 있으면 1이다.주의 — API가 노선의 모든 차량을 포착한다는 뜻은 아니다.파생msgBody.busLocationList[].vehId, msgBody.busLocationList[].stationSeq, msgBody.busLocationList[].stateCd; S1 정규화 last_passed; S1 upstream_bus_existsGBIS 버스위치정보 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 스냅샷 특징 빌더
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
목표 축 순번 (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 통과 사건 빌더
원천 구분 (source_kind)직접 수집 행은 direct_e4, 제한 코퍼스 행은 corpus로 붙인다.주의 — GBIS 응답 필드가 아니다. GBM에는 범주형 예측 재료로 들어가고, 구간 검열에서는 같은 원천끼리 연결하는 데 쓴다.파생S1 source_kindS1 데이터셋 빌더
날짜 순번 (query_day_index)KST 날짜에서 2026-07-19를 뺀 일수를 쓴다.주의 — 이 데이터셋의 고정 기준일에 묶인 값이다. 달력 날짜의 보편적 ID가 아니다.파생timing.response_received_at.kst, timing.response_received_at.utc; S1 E4 response_received_kst, response_received_utc; 코퍼스 collectedAt; S1 query_day_index우리 수집기 응답 수신시각; 제한 코퍼스는 수집기가 붙인 collectedAt
같은 차량·운행 연결키같은 원천·노선·구간·가명 차량을 묶고 순번 회귀·긴 공백에서 운행을 나눠 journey_index를 붙인다.주의 — 코퍼스 차량키는 plateNo 우선 HMAC 앞 16 hex이며 외부 원 식별자로 되돌릴 수 없다. 원천을 넘겨 같은 차량으로 보지 않는다.파생msgBody.busLocationList[].vehId, msgBody.busLocationList[].plateNo; 직접 S1 target_vehicle_hash, 코퍼스 vehicles[].id; S1 source, route, segment_id, vehicle_hash, journey_indexGBIS 버스위치정보 getBusLocationListv2 + 수집기 가명화 + S1 운행 분할
미관측 중간 통과 구간인접 적격 응답의 정규화 순번이 +2 이상이면 끝점 사건만 만들고 각 중간 순번을 reason=sequence_jump_intermediate_passage_unobserved로 기록한다.주의 — 선형 보간한 가짜 통과시각을 만들지 않는다. 불가능 이동 사유는 이 모형의 전이쌍에서 제외한다.파생S1 censored_passages.reason, censored_passages.source, censored_passages.route, censored_passages.segment_id, censored_passages.vehicle_hash, censored_passages.interval_start, censored_passages.interval_end, censored_passages.station_seq, censored_passages.normalized_sequence_jumpS1 censored_passages
서비스 정차점 표시stationName(미정차)(경유)가 없으면 1로 둔다.주의 — 현재 S1 빌더는 station.stationName이 아니라 routestation.stationName을 읽는다.파생routestation.stationName; S1 is_service_stopGBIS routestation 마스터 + S1 정류장 필터
검열 구간 양끝 만석 상태검열 구간 끝점의 서비스 정차 통과 라벨과, 같은 차량·운행에서 90초 안에 앞서 확정된 서비스 정차 라벨을 연결한다.주의 — label_status=ok, is_service_stop=1만 쓴다. 같은 원천과 인과 시각 조건을 지킨다.파생S1 censored_passages.source, censored_passages.route, censored_passages.segment_id, censored_passages.vehicle_hash, censored_passages.interval_start, censored_passages.interval_end, censored_passages.station_seq; passage_events.source, passage_events.route, passage_events.segment_id, passage_events.vehicle_hash, passage_events.label_status, passage_events.is_service_stop, passage_events.label_full, passage_events.passage_interval_start, passage_events.passage_interval_end, passage_events.journey_index, passage_events.station_seq; 모델 내부 start_state, end_stateS1 censored_passages + S1 passage_events
전이 횟수와 숨은 통과 수같은 검열 구간의 중간 순번 행 수를 latent_passage_count로 세고 transition_count=latent_passage_count+1로 둔다.주의 — 한 구간의 중간 순번 행을 서로 독립 표본으로 세지 않는다.파생S1 censored_passages.normalized_sequence_jump; 모델 내부 latent_passage_count, transition_countS1 censored_passages + 구간 검열 모형 내부
검열 전이 그룹route | am·pm | b((station_seq-1)//10)으로 묶어 0↔1 전이율을 추정한다. 12:00 전은 am이다.주의 — API의 공식 시간대나 방향 코드가 아니다. 10순번 폭은 구현 규칙이다.파생S1 route, query_minute_of_day, station_seq; 모델 내부 transition_group구간 검열 모형 내부

지금은 안 쓰는 재료 5개

언제 실패하나

기존 프로토타입과 다른 점