빈집 예측 모델¶
프로젝트 개요¶
집계구(OA, Output Area) 단위로 빈집 발생 확률을 예측하는 ML 파이프라인이다. 지방자치단체의 선제적 빈집 관리 정책 수립을 지원한다.
| 항목 | 내용 |
|---|---|
| 목적 | 단년후 빈집 발생 고위험 지역 예측 |
| 단위 | 집계구 (약 108,000개) |
| 예측 대상 | 빈집 발생 확률 (0-1) |
| 갱신 주기 | 재학습 버튼 클릭시 |
| 인프라 | Oracle + PostgreSQL, Airflow |
최근 업데이트 (2026-02)¶
| 날짜 | 변경 사항 |
|---|---|
| 02-25 | 클러스터링 알고리즘 HDBSCAN으로 변경 (노이즈 처리 개선) |
| 02-20 | 새로운 파생변수 추가: 공실률 변화 추세 |
| 02-15 | 챗봇 연동 API 완료 |
| 02-10 | Calibration 개선 (Isotonic Regression 적용) |
파이프라인 구조¶
┌─────────────────────────────────────────────────────────────┐
│ ML Pipeline (6 Steps) │
│ │
│ Step 1: 데이터 통합 │
│ ├─ 인구 데이터 (통계청) │
│ ├─ 건축물 데이터 (건축물대장) │
│ ├─ 토지 데이터 (토지대장) │
│ ├─ 거래 데이터 (실거래가) │
│ └─ 인프라 데이터 (학교, 병원 등) │
│ │ │
│ ▼ │
│ Step 2: 파생변수 생성 │
│ ├─ 인구 변화율 │
│ ├─ 고령화 지수 │
│ ├─ 건물 노후도 │
│ ├─ 접근성 지표 │
│ ├─ 거래 활성도 │
│ └─ [NEW] 공실률 변화 추세 │
│ │ │
│ ▼ │
│ Step 3: 클러스터링 [UPDATED: HDBSCAN] │
│ ├─ 지역 유형 분류 (도시/농촌/산업 등) │
│ ├─ 노이즈 포인트 별도 처리 │
│ └─ 클러스터별 특성 프로파일 │
│ │ │
│ ▼ │
│ Step 4: 모델 학습 │
│ ├─ 클러스터별 개별 모델 │
│ └─ XGBoost, LightGBM, CatBoost │
│ │ │
│ ▼ │
│ Step 5: 앙상블 + Calibration │
│ ├─ Soft Voting │
│ └─ [NEW] Isotonic Regression Calibration │
│ │ │
│ ▼ │
│ Step 6: 단년 예측 │
│ └─ 현재 상태 → 미래 빈집 확률 │
└─────────────────────────────────────────────────────────────┘
데이터 소스¶
원천 데이터¶
| 데이터 | 출처 | 갱신 주기 | 주요 변수 |
|---|---|---|---|
| 인구 통계 | 통계청 SGIS | 연간 | 인구수, 연령별 인구, 세대수 |
| 건축물 대장 | 국토부 | 월간 | 건축년도, 용도, 면적, 층수 |
| 토지 대장 | 국토부 | 월간 | 지목, 면적, 공시지가 |
| 실거래가 | 국토부 | 월간 | 거래가격, 거래일, 면적 |
| 빈집 현황 | 지자체 | 연간 | 빈집 수, 위치, 상태 |
파생 변수¶
# 인구 관련
population_features = {
'pop_change_rate': '5년간 인구 변화율',
'aging_index': '고령화 지수 (65세 이상 / 15세 미만)',
'youth_ratio': '청년 인구 비율 (20-39세)',
'single_household_ratio': '1인 가구 비율',
'dependency_ratio': '부양비 ((0-14 + 65+) / 15-64)'
}
# 건축물 관련
building_features = {
'avg_building_age': '평균 건물 연령',
'old_building_ratio': '30년 이상 건물 비율',
'vacant_ratio_current': '현재 빈집 비율',
'building_density': '건물 밀도 (건물수/면적)',
'vacancy_trend': '[NEW] 3년간 공실률 변화 추세' # 2026-02 추가
}
# 거래 관련
transaction_features = {
'transaction_count': '연간 거래 건수',
'price_change_rate': '5년간 가격 변화율',
'transaction_velocity': '거래 회전율'
}
# 인프라 관련
infra_features = {
'school_distance': '최근접 학교 거리',
'hospital_distance': '최근접 병원 거리',
'station_distance': '최근접 역/정류장 거리',
'commercial_density': '상업시설 밀도'
}
모델링 상세¶
Step 3: 클러스터링 (HDBSCAN 변경)¶
기존 K-means 대비 HDBSCAN 장점: - 클러스터 수 자동 결정 - 노이즈 포인트 식별 - 비구형 클러스터 처리
from hdbscan import HDBSCAN
clusterer = HDBSCAN(
min_cluster_size=500,
min_samples=50,
cluster_selection_epsilon=0.5,
metric='euclidean'
)
clusters = clusterer.fit_predict(X_scaled)
# 노이즈 포인트 (-1) 별도 처리
noise_mask = clusters == -1
X_noise = X_scaled[noise_mask]
# 노이즈 포인트는 전역 모델 사용
클러스터 분포:
| 클러스터 | 특성 | 집계구 수 | 비율 |
|---|---|---|---|
| 0 | 도시 중심부 | 28,000 | 26% |
| 1 | 신규 개발 지역 | 15,000 | 14% |
| 2 | 고령화 농촌 | 32,000 | 30% |
| 3 | 산업단지 주변 | 18,000 | 17% |
| 4 | 관광/휴양 지역 | 8,000 | 7% |
| -1 | 노이즈 | 7,000 | 6% |
Step 5: Calibration 적용¶
예측 확률의 신뢰성 향상을 위해 Isotonic Regression 적용.
from sklearn.calibration import CalibratedClassifierCV
from sklearn.isotonic import IsotonicRegression
# 방법 1: sklearn 내장
calibrated_model = CalibratedClassifierCV(
base_estimator=ensemble_model,
method='isotonic',
cv=5
)
# 방법 2: 후처리 calibration
iso_reg = IsotonicRegression(out_of_bounds='clip')
iso_reg.fit(val_probs, val_labels)
calibrated_probs = iso_reg.predict(test_probs)
Calibration 효과:
| 지표 | Before | After |
|---|---|---|
| Brier Score | 0.082 | 0.071 |
| ECE (Expected Calibration Error) | 0.045 | 0.018 |
| 실제 위험 그룹 포착률 | 82% | 89% |
평가 지표¶
| 지표 | 이전 성능 | 현재 성능 | 목표 |
|---|---|---|---|
| AUC-ROC | 0.82 | 0.85 | > 0.80 |
| Precision@10% | 0.45 | 0.52 | > 0.40 |
| Recall@10% | 0.38 | 0.44 | > 0.35 |
| 지역별 Calibration | 0.91 | 0.96 | > 0.90 |
해석 가능성¶
import shap
# SHAP 분석
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
# 전역 피처 중요도
shap.summary_plot(shap_values, X_test)
# 개별 예측 설명
shap.waterfall_plot(shap_values[0])
상위 피처 중요도 (2026-02 기준): 1. 인구 변화율 (5년) - 21% 2. 고령화 지수 - 18% 3. 공실률 변화 추세 [NEW] - 15% 4. 평균 건물 연령 - 12% 5. 거래 빈도 - 10%
챗봇 연동 API¶
빈집 챗봇에서 예측 결과 조회 가능.
# API 엔드포인트
@app.get("/api/v1/prediction/{oa_code}")
async def get_prediction(oa_code: str):
"""집계구별 빈집 예측 조회"""
result = prediction_service.get_by_oa(oa_code)
return {
"oa_code": oa_code,
"current_vacant_ratio": result.current,
"predicted_vacant_ratio": result.predicted,
"risk_level": result.risk_level,
"top_factors": result.shap_top_3,
"cluster": result.cluster_name
}
@app.get("/api/v1/prediction/region/{region}")
async def get_region_summary(region: str):
"""지역 전체 요약"""
return prediction_service.get_region_summary(region)
챗봇 질의 예시:
사용자: 의성군 빈집 예측 결과 알려줘
응답:
경북 의성군 빈집 예측 결과입니다:
**현황:**
- 현재 빈집률: 12.3%
- 등록 빈집: 3,050채
**1년 후 예측:**
- 예측 빈집률: 14.8% (+2.5%p)
- 위험 등급: 매우 높음
**주요 위험 요인:**
1. 인구 변화율 -8.2%/년 (고령화 가속)
2. 30년 이상 노후 건물 65%
3. 거래 건수 전년 대비 -23%
**고위험 집계구 TOP 3:**
- 금성면 대리: 예측 빈집률 28%
- 춘산면 신흥리: 예측 빈집률 25%
- 가음면 현리: 예측 빈집률 23%
Airflow DAG¶
from airflow import DAG
from airflow.operators.python import PythonOperator
dag = DAG(
'vacant_house_prediction',
schedule_interval=None, # 수동 트리거
start_date=datetime(2024, 1, 1)
)
# Step 1: 데이터 수집
collect_data = PythonOperator(
task_id='collect_data',
python_callable=collect_all_sources,
dag=dag
)
# Step 2: 파생변수 생성
create_features = PythonOperator(
task_id='create_features',
python_callable=generate_derived_features,
dag=dag
)
# Step 3-6: 모델링
run_pipeline = PythonOperator(
task_id='run_ml_pipeline',
python_callable=run_prediction_pipeline,
dag=dag
)
# API 캐시 갱신
refresh_api_cache = PythonOperator(
task_id='refresh_api_cache',
python_callable=refresh_prediction_cache,
dag=dag
)
# 의존성
collect_data >> create_features >> run_pipeline >> refresh_api_cache
결과 활용¶
대시보드 출력¶
| 집계구 코드 | 클러스터 | 현재 빈집률 | 단년 후 예측 | 위험 등급 | 주요 요인 |
|---|---|---|---|---|---|
| 11010101 | 도시 중심 | 2.3% | 2.8% | 낮음 | 건물 노후화 |
| 36020205 | 고령 농촌 | 8.5% | 11.2% | 매우 높음 | 인구 감소 |
| 28030103 | 산업단지 | 5.1% | 5.9% | 중간 | 거래 감소 |
정책 연계¶
위험 등급별 권고 조치:
├─ 매우 높음: 선제적 정비 계획, 예산 우선 배정
├─ 높음: 모니터링 강화, 소유자 상담
├─ 중간: 정기 점검, 예방 프로그램
└─ 낮음: 현행 유지
향후 계획¶
| 일정 | 작업 | 상태 |
|---|---|---|
| 3월 | 시계열 모델 추가 (N년 후 예측) | 계획 |
| 3월 | 공간 자기상관 반영 | 계획 |
| 4월 | 모델 자동 재학습 스케줄러 | 계획 |
마지막 업데이트: 2026-02-25