File size: 7,641 Bytes
5eccb5b
 
 
 
 
 
98799e8
5eccb5b
558291d
98799e8
 
5eccb5b
98799e8
 
5eccb5b
 
98799e8
 
5eccb5b
 
98799e8
 
 
5eccb5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98799e8
5eccb5b
98799e8
5eccb5b
81397db
5eccb5b
 
 
 
 
 
98799e8
 
5eccb5b
 
b444514
5eccb5b
 
98799e8
 
 
0fda1cf
5eccb5b
 
 
 
98799e8
5eccb5b
 
98799e8
 
5eccb5b
98799e8
5eccb5b
 
 
 
81397db
5eccb5b
 
98799e8
 
 
5eccb5b
 
 
 
 
 
98799e8
5eccb5b
 
98799e8
 
 
5eccb5b
 
 
98799e8
5eccb5b
98799e8
 
 
 
5eccb5b
98799e8
 
 
 
5eccb5b
98799e8
5eccb5b
 
98799e8
 
 
 
 
5eccb5b
 
98799e8
5eccb5b
 
3f479c4
98799e8
 
bd6bbcc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import torch
import numpy as np
import pandas as pd
import argparse
import joblib
import os
from models import TimeXer # 사용하는 모델에 맞게 수정
from tqdm import tqdm

# 1. 인자 파싱 (필요한 정보만)
# --- 1. 설정 및 인자 파싱 (이 부분을 전체 교체) ---
parser = argparse.ArgumentParser(description='Time Series Prediction')

# 공통 필수 인자
parser.add_argument('--checkpoint_path', type=str, required=True, help='Path to the model checkpoint file (.pth)')
parser.add_argument('--scaler_path', type=str, required=True, help='Path to the saved scaler file (.gz)')

# 모드 선택 인자 (둘 중 하나만 사용)
parser.add_argument('--predict_input_file', type=str, default=None, help='[Mode 1] Path to the CSV file for single future prediction')
parser.add_argument('--evaluate_file', type=str, default=None, help='[Mode 2] Path to the CSV file for rolling evaluation')

# --- 모델 아키텍처 인자 (학습 때와 동일하게) ---
parser.add_argument('--model', type=str, default='TimeXer', help='model name') # 모델 이름 추가
parser.add_argument('--task_name', type=str, default='long_term_forecast', help='task name')
parser.add_argument('--seq_len', type=int, required=True, help='input sequence length')
parser.add_argument('--pred_len', type=int, required=True, help='prediction sequence length')
parser.add_argument('--label_len', type=int, required=True, help='start token length')
parser.add_argument('--features', type=str, required=True, help='M, S, or MS')
parser.add_argument('--enc_in', type=int, required=True, help='encoder input size')
parser.add_argument('--dec_in', type=int, required=True, help='decoder input size')
parser.add_argument('--c_out', type=int, required=True, help='output size')
parser.add_argument('--d_model', type=int, required=True, help='dimension of model')
parser.add_argument('--n_heads', type=int, required=True, help='num of heads')
parser.add_argument('--e_layers', type=int, required=True, help='num of encoder layers')
parser.add_argument('--d_layers', type=int, required=True, help='num of decoder layers')
parser.add_argument('--d_ff', type=int, required=True, help='dimension of fcn')
parser.add_argument('--factor', type=int, required=True, help='attn factor')
parser.add_argument('--patch_len', type=int, required=True, help='patch length for TimeXer')
parser.add_argument('--expand', type=int, required=True)
parser.add_argument('--d_conv', type=int, required=True)
parser.add_argument('--dropout', type=float, default=0.1, help='dropout')
parser.add_argument('--embed', type=str, default='timeF', help='time features encoding')
parser.add_argument('--activation', type=str, default='gelu', help='activation')
parser.add_argument('--output_attention', action='store_true', help='whether to output attention in ecoder')
parser.add_argument('--use_norm', type=int, default=1, help='whether to use normalize')
parser.add_argument('--freq', type=str, default='t', help='freq for time features encoding')

args = parser.parse_args()

# --- 2. 공통 함수: 모델 및 스케일러 로드 ---
def load_model_and_scaler(args):
    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
    model = TimeXer.Model(args).float().to(device)
    model.load_state_dict(torch.load(args.checkpoint_path, map_location=device))
    model.eval()
    scaler = joblib.load(args.scaler_path)
    print(f"Using device: {device}")
    print("Model and scaler loaded successfully.")
    return model, scaler, device

# --- 3. 모드 1: 단일 미래 예측 함수 ---
def predict_future(args, model, scaler, device):
    df_input = pd.read_csv(args.predict_input_file)
    if 'date' in df_input.columns:
        df_input = df_input.drop(columns=['date'])
    raw_input = df_input.tail(args.seq_len).values
    
    input_scaled = scaler.transform(raw_input)
    batch_x = torch.from_numpy(input_scaled).float().unsqueeze(0).to(device)
    
    with torch.no_grad():
        outputs = model(batch_x, None, None, None)[0]
        
    prediction_scaled = outputs.detach().cpu().numpy()[0]
    if args.features == 'MS':
        padding = np.zeros((prediction_scaled.shape[0], scaler.n_features_in_ - 1))
        prediction_padded = np.concatenate((padding, prediction_scaled), axis=1)
        prediction = scaler.inverse_transform(prediction_padded)[:, -1]
    else:
        prediction = scaler.inverse_transform(prediction_scaled)
    return prediction

# --- 4. 모드 2: 전체 기간 롤링 평가 함수 ---
def evaluate_performance(args, model, scaler, device):
    df_eval = pd.read_csv(args.evaluate_file)
    if 'date' in df_eval.columns:
        df_eval = df_eval.drop(columns=['date'])
    raw_data = df_eval.values
    data_scaled = scaler.transform(raw_data)

    preds_unscaled = []
    trues_unscaled = []

    num_samples = len(data_scaled) - args.seq_len - args.pred_len + 1
    for i in tqdm(range(num_samples), desc="Evaluating"):
        s_begin = i
        s_end = s_begin + args.seq_len
        input_scaled = data_scaled[s_begin:s_end]
        batch_x = torch.from_numpy(input_scaled).float().unsqueeze(0).to(device)

        true_begin = s_end
        true_end = true_begin + args.pred_len
        true_scaled = data_scaled[true_begin:true_end]
        
        with torch.no_grad():
            outputs = model(batch_x, None, None, None)[0]
        
        # --- ★★★ 이 부분이 추가/수정되었습니다 ★★★ ---
        # 1. 스케일링된 결과 가져오기
        pred_scaled = outputs.detach().cpu().numpy()[0]

        # 2. 예측값(pred) 스케일 복원
        if args.features == 'MS':
            padding = np.zeros((pred_scaled.shape[0], scaler.n_features_in_ - 1))
            pred_padded = np.concatenate((padding, pred_scaled), axis=1)
            pred_unscaled = scaler.inverse_transform(pred_padded)[:, -1:]
        else:
            pred_unscaled = scaler.inverse_transform(pred_scaled)
        
        # 3. 실제값(true) 스케일 복원
        # true_scaled는 이미 모든 feature를 포함하므로 패딩 불필요
        true_unscaled = scaler.inverse_transform(true_scaled)[:, -1:]

        preds_unscaled.append(pred_unscaled)
        trues_unscaled.append(true_unscaled)
        # ---------------------------------------------

    return np.array(preds_unscaled), np.array(trues_unscaled)

# --- 5. 메인 로직 ---
if __name__ == '__main__':
    # 결과 저장 폴더 생성
    output_dir = 'pred_results'
    os.makedirs(output_dir, exist_ok=True)
    
    model, scaler, device = load_model_and_scaler(args)

    if args.predict_input_file:
        print("\n--- Running in Single Prediction Mode ---")
        prediction = predict_future(args, model, scaler, device)
        output_path = os.path.join(output_dir, 'prediction_future.npy')
        np.save(output_path, prediction)
        print(f"\n✅ Future prediction saved to {output_path}")

    elif args.evaluate_file:
        print("\n--- Running in Rolling Evaluation Mode ---")
        eval_preds, eval_trues = evaluate_performance(args, model, scaler, device)
        pred_path = os.path.join(output_dir, 'evaluation_preds.npy')
        true_path = os.path.join(output_dir, 'evaluation_trues.npy')
        np.save(pred_path, eval_preds)
        np.save(true_path, eval_trues)
        print(f"\n✅ Evaluation results saved to {output_dir}")
        print(f"   - Predictions shape: {eval_preds.shape}")
        print(f"   - Truths shape: {eval_trues.shape}")
        
    else:
        print("오류: --predict_input_file 또는 --evaluate_file 중 하나의 모드를 선택해야 합니다.")