
BigQuery ML의 TimesFM을 사용하여 전력 수요를 시계열 예측해 보았다
요약
Google Research의 시계열 파운데이션 모델인 TimesFM을 BigQuery ML의 AI.FORECAST 함수를 통해 활용하여 전력 수요를 예측하는 과정을 다룹니다. Terraform을 이용한 인프라 관리부터 데이터 전처리, 결측치 보간 및 예측 실행까지의 전체 워크플로우를 설명합니다.
핵심 포인트
- TimesFM은 별도의 모델 학습이나 배포 없이 BigQuery ML에서 즉시 호출 가능함
- Terraform을 사용하여 BigQuery 데이터셋과 테이블 인프라를 관리함
- 시계열 데이터의 일관성을 위해 GAP_FILL 함수로 선형 보간 처리를 수행함
- BigQuery Sandbox 환경의 제약 사항을 고려한 Terraform 설정 방법을 제시함
서론
Fusic의 레오나입니다.
이번에는 BigQuery ML의 TimesFM을 사용하여 전력 수요를 1주일 앞까지 예측해 보았습니다.
TimesFM은 Google Research가 공개한 시계열 예측용 파운데이션 모델 (Foundation Model)입니다.
BigQuery ML에서는 내장된 TimesFM을 AI.FORECAST 함수를 통해 호출할 수 있으므로, 모델을 학습하거나 Endpoint로 배포하지 않고도 시계열 예측을 실행할 수 있습니다. [1][2]
모델에 대해 자세히 해설한 블로그가 있으니 함께 참고해 주시기 바랍니다.
사용하는 데이터셋
검증에는 Kaggle에서 배포되는 Hourly Energy Consumption의 PJME_hourly.csv를 사용합니다. [3] 이 데이터에는 PJM Interconnection의 PJM East에서 관측된 전력 수요가 1시간 단위의 MW로 기록되어 있습니다.
Kaggle Dataset API로 확인한 배포 조건은 다음과 같습니다.
| 항목 | 값 |
|---|---|
| Kaggle Dataset | robikscube/hourly-energy-consumption |
| ... |
데이터 품질 확인하기
PJME_hourly.csv의 입도는 1시각당 1건의 전력 수요입니다.
| 확인 항목 | 결과 |
|---|---|
| 행 수 | 145,366 |
| ... |
중복과 결손은 주로 3월과 10월 또는 11월에 나타났습니다. 날짜는 서머타임(Summer Time) 전환 시기와 일치하지만, 배포 데이터에는 타임존(Timezone)이 명시되어 있지 않습니다. 따라서 TIMESTAMP로 변환하여 특정 타임존을 가정하지 않고, BigQuery에서는 DATETIME으로 취급합니다. 동일한 시각이 2건 있는 경우에는 평균을 내고, 결손된 1시간은 GAP_FILL 함수로 선형 보간 (Linear Interpolation) 합니다. [4]
CREATE OR REPLACE TABLE `PROJECT_ID.gcp1_timesfm.pjme_hourly_clean` AS
WITH deduplicated AS (
SELECT
...
보간하는 부분은 전체 기간의 0.0206%입니다. 비율은 작지만, 시계열 간격이 일정하다는 전제를 명시하기 위해 암묵적으로 무시하지 않고 처리를 SQL에 남겨둡니다.
만드는 것
이번 검증은 다음 흐름으로 진행합니다.
Terraform은 BigQuery API, Dataset, Raw Table을 관리합니다. 데이터 로드와 예측은 상태를 가진 인프라가 아니므로, bq 커맨드와 GoogleSQL로 실행합니다.
Terraform으로 BigQuery 준비하기
Terraform에서는 7일 후에 검증 테이블이 만료되도록 Dataset을 만듭니다.
resource "google_bigquery_dataset" "timesfm" {
project = var.project_id
dataset_id = "gcp1_timesfm"
...
BigQuery Sandbox(결제 계정을 연결하지 않고 사용할 수 있는 무료 모드)에서는 Table이나 Partition이 60일 후에 만료되는 제약이 있기 때문에, Terraform에도 60일의 default_partition_expiration_ms를 명시합니다. [5] 이번 Table은 7일 후에 만료되므로 60일보다 먼저 삭제되지만, 이 값을 선언해 두면 Sandbox가 자동 설정한 값과의 Terraform 차분(Diff)을 방지할 수 있습니다.
Raw Table의 열은 타임존이 없는 DATETIME과 전력 수요인 FLOAT64뿐입니다.
resource "google_bigquery_table" "pjme_hourly_raw" {
dataset_id = google_bigquery_dataset.timesfm.dataset_id
table_id = "pjme_hourly_raw"
...
다음 순서로 생성합니다.
./scripts/download_dataset.sh
terraform -chdir=terraform init
terraform -chdir=terraform plan -var='project_id=PROJECT_ID'
...
테스트 기간 및 비교 대상
마지막 168시간을 테스트 기간으로 숨기고, 그 이전의 데이터만을 TimesFM에 전달합니다.
비교할 조건은 다음 네 가지입니다.
| 조건 | 모델 | Context Window |
|---|---|---|
| A | TimesFM 2.0 | 2,048 |
| ... |
조건 A와 조건 B는 Context Window (컨텍스트 윈도우)를 동일하게 맞춰 모델 버전에 따른 차이를 확인합니다. 조건 B와 조건 C는 TimesFM 2.5를 사용하며 이력(History)의 길이만 변경합니다. 베이스라인(Baseline)으로는 동일한 요일과 시간의 값을 사용하는 Seasonal Naive를 설정합니다.
전력 수요에는 요일과 시간대의 주기성이 있기 때문에, TimesFM이 이 단순한 방법을 능가하는지 확인합니다.
AI.EVALUATE로 평가하기
AI.EVALUATE는 이력 데이터로부터 생성된 예측값과 실측값을 비교하여 MAE, MSE, RMSE, MAPE, sMAPE, MASE를 반환합니다. [6]
SELECT *
FROM AI.EVALUATE(
(
...
주로 확인하는 값은 MAE와 MAPE입니다. MAE는 오차를 MW 단위 그대로 읽을 수 있어, 실제 전력 수요에 대해 어느 정도 벗어났는지 파악할 수 있습니다. MAPE는 비율로 비교할 수 있지만, 실측값이 0에 가까운 시계열에서는 불안정해집니다. 이번 데이터에는 0 이하의 값이 없으므로 보조 지표로 활용할 수 있습니다.
95% 예측 구간 확인하기
점 예측(Point Forecast)의 오차뿐만 아니라, 95% 예측 구간(Prediction Interval)에 실측값이 포함된 비율도 확인합니다.
SELECT *
FROM AI.FORECAST(
(
...
예측 구간이 넓으면 실측값을 포함하기 쉬워지므로, 커버리지(Coverage)만으로는 판단하지 않습니다. 실측 커버리지와 평균 구간 폭을 함께 기록합니다.
검증 결과
| 조건 | MAE | RMSE | MAPE | sMAPE |
|---|---|---|---|---|
| TimesFM 2.0 / 2,048 | 3,324.36MW | 3,825.31MW | 9.5844% | 9.2079% |
| ... |
주요 4가지 지표에서는 TimesFM 2.5의 Context Window 4,096이 가장 작은 오차를 기록했습니다.
Seasonal Naive와 비교하면 MAE는 약 42.2%, RMSE는 약 45.9% 감소했습니다.
TimesFM 2.5 모델끼리 비교했을 때도 Context Window를 2,048에서 4,096으로 확장하면 MAE가 약 8.5% 감소했습니다. 반면, TimesFM 2.0은 Seasonal Naive보다 MAE와 RMSE는 작지만, MAPE는 9.2048%에서 9.5844%로 악화되었습니다. 모델을 바꾼다고 해서 모든 지표가 일률적으로 개선되는 것은 아니므로, 용도에 맞춰 살펴볼 지표를 결정해야 합니다. TimesFM 2.5의 95% 예측 구간에 대해서는 다음 값을 추가합니다.
| 지표 | 결과 |
|---|---|
| 평가 점수 | 168 |
| ... | |
![]() |
PJME의 실측 전력 수요, TimesFM 2.5의 예측값, 95% 예측 구간
명목상으로는 95% 예측 구간이지만, 이번 일주일 동안은 168개 지점 모두 구간 안에 들어왔습니다. 다만, 100%라는 값만으로 구간이 적절하게 교정(Calibration)되었다고 판단할 수는 없습니다. 평균 구간 폭이 11,721.59MW로 넓기 때문에, 여러 기간에 걸쳐 커버리지와 구간 폭을 모두 확인해야 합니다.
결과
예측 테이블과 실측값의 결합 과정에서 행이 누락되거나 늘어나지 않았는지 확인했습니다.
| 확인 항목 | 결과 |
|---|---|
| Raw Table | 145,366행 |
| ... |
Seasonal Naive는 BigQuery와 별개로 pandas를 사용하여 중복 집계와 선형 보간(Linear Interpolation)을 재현하여 계산했습니다.
MAE, RMSE, MAPE는 반올림 오차 범위 내에서 BigQuery의 결과와 일치했습니다. TimesFM 2.5, Context Window 4,096에 대해서도 AI.EVALUATE 값과, 예측 테이블을 실측값에 결합하여 재계산한 MAE, RMSE, MAPE가 일치합니다.
리소스 삭제하기
검증 후에는 Terraform으로 Dataset과 Table을 삭제합니다.
terraform -chdir=terraform destroy -var='project_id=PROJECT_ID'
마치며
BigQuery ML의 TimesFM을 사용하면 모델을 생성하지 않고도 GoogleSQL만으로 시계열 예측 (Time series forecasting) 및 백테스트 (Backtest)를 실행할 수 있습니다. 이번에는 Kaggle의 Hourly Energy Consumption 데이터셋을 사용하여 데이터 품질을 확인한 후, TimesFM 2.0, TimesFM 2.5, Seasonal Naive를 동일한 일주일 기간 동안 비교했습니다. 모델 학습을 하지 않고 BigQuery에서 매니지드 (Managed) 방식으로 다룰 수 있다는 점이 큰 장점이라고 느꼈습니다.
The TimesFM model, https://docs.cloud.google.com/bigquery/docs/timesfm-model ↩︎
The AI.FORECAST function, https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-forecast ↩︎
Hourly Energy Consumption, https://www.kaggle.com/datasets/robikscube/hourly-energy-consumption ↩︎
Time series functions, https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/time-series-functions ↩︎
Try BigQuery using the sandbox, https://docs.cloud.google.com/bigquery/docs/sandbox ↩︎
The AI.EVALUATE function, https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-evaluate ↩︎
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기