Data Science

Dow Jones Prediction Model - Hybrid Deep Learning

bibidibabidiboop 2025. 11. 25. 21:32

기존에 만들어두었던 앙상블 모델(: 여러 머신러닝 모델의 예측 결과를 조합하여 단일 모델보다 더 나은 성능을 내는 기법)은 정확도가 너무 떨어져서 이를 보완하기 위해 하이브리드 딥러닝 모델 기법을 사용하려 한다.

 

Model Architecture

우리가 사용하려는 하이브리드 딥러닝 모델은 CNN + LSTM + Attention으로, 세 가지의 강력한 기법을 모드 결합한 모델이다.

1) CNN (for 단기 패턴 포착)

: 원래는 이미지 처리에 쓰이던 기술이다. 주식 차트에서 '갑자기 튀어 오르는 구간', '하락세로 꺾이는 모양'같은

  국소적인 특징(Local Features)을 아주 잘 찾아낸다. 지난 15일치 데이터 중에서 3~5일 간격의 짧은 변동 패턴을 감지한다.

2) LSTM (for 장기 흐름 파악)

:  시계열 데이터에 특화된 모델이다. 과거의 정보를 기억했다가 현재 판단에 사용한다.

   "지난주 거래량이 늘었으니 이번 주에는 오를거야"와 같은 "시간적 인과관계(Temporal Dependencies)"를 학습한다.

3) Attention(Multi-Head Attention) (for 중요 시점 강저)

: 입력된 15일치 데이터가 모두 똑같이 중요한 것은 아니다.

  어제 데이터보다 3일 전 '어닝 서프라이즈'가 있었던 날이 더 중요할 수도 있다.

  Attention 메커니즘은 "어떤 날의 데이터에 더 집중해 하는지" 가중치를 부여하여

  모델이 엉뚱한 데이터에 휘둘리지 않게 돕는다.

 

Why Hybrid?

기존 앙상블(RF,XGBoost..) 모델은 시계열 데이터의 복잡한 비선형선과 순차적 의존성을 동시에 포착하는 데 한계가 있었다.

따라서 본 연구에서는 다음과 같은 하이브리드 딥러닝 프레임워크를 제안한다:

1. CNN을 통해 주가 변동의 미세한 "지역적 패턴"을 추출하고

2. LSTM을 통해 장기적인 "시계열 추세"를 학습하며,

3. Attention Mechanism을 도입하여 예측에 결정적인 영향을 미치는 중요 시점(Time Step)에 가중치를 부여

이를 통해 기존 모델 대비 예측 정확도를 높이고자 한다.

 

 

Validation - Sanity Check

아래의 코드는 'Synthetic Data' 생성 코드이다. 

우리가 업로드한 실제 데이터셋을 사용하기 전에 하이브리드 딥러닝 모델과 XAI 도구가

정상적으로 작동하는지 검증하기 위해 검증해야 한다.

금융 데이터는 노이즈가 많아, 모델이 학습한 패턴이 진짜인지, 우연인지 검증하기 어렵다.
따라서 본 연구에서는 실제 Dow Jones 데이터를 분석하기에 앞서,
인과관계가 명확한 가상 데이터를 생성하여 제안하는 앙상블 모델과 XAI 기법의 신뢰성을 사전 검증한다.

따라서 아래와 같이 가상 데이터를 만들어 모델의 건전성 검증 단계(Sanity Check) 단계를 거친다.

*Sanity Check: 모델이 주어진 목적에 맞게 신뢰성 있고 안정적으로 작동하는 능력

step1. 복잡한 모델의 오류 확인

- LSTM + CNN + Attenion 이 결합된 하이브리드 모델은 구조가 복잡해서 코드를 잘 짰는지 확인이 어렵다.

  따라서 모델이 예측해야 하는 정보(정답)과 매우 유사하도록 조작한 데이터(Feature 0)를

  해당 모델이 정확도 90% 이상을 못 낸다면 모델 구조에 문제가 있음을 뜻한다.

  따라서 조작한 데이터를 넣어 모델이 예측하도록 한 후의 정확도를 확인한다.

 

Step2. XAI 도구 검증

- 이제 SHAP 같은 도구를 썼을 때 "Feature 0이 제일 중요했다"와 같은 결과가 나와야 한다.

 

# ---------------------------------------------------------
# 1. [설정] 재현성 확보
# ---------------------------------------------------------
np.random.seed(42)
tf.random.set_seed(42)

# ---------------------------------------------------------
# 2. [데이터 준비] "배경 지식(Context)" 추가
# ---------------------------------------------------------
print("--- [Trend & Graph Mining] 데이터 로드 및 기술적 지표 추가 ---")

def create_sequences(data, target, seq_length):
    xs, ys = [], []
    for i in range(len(data) - seq_length):
        xs.append(data[i:(i + seq_length)])
        ys.append(target[i + seq_length])
    return np.array(xs), np.array(ys)

# --- 가상 데이터 생성 (추세와 상관관계가 뚜렷한 데이터) ---
total_samples = 2000
n_features = 40
seq_length = 15 # 조금 더 길게 봐서 추세를 파악

t = np.linspace(0, 100, total_samples)
# 기본 데이터
X_full = np.random.randn(total_samples, n_features) * 0.5

# 배경 시뮬레이션: 특정 Feature가 Target과 강한 상관관계를 가짐
# Feature 0번은 Target과 80% 양의 상관관계 (Trend Follower)
X_full[:, 0] = np.sin(t) + np.random.normal(0, 0.2, total_samples)
# Feature 1번은 Target과 70% 음의 상관관계 (Inverse Indicator)
X_full[:, 1] = -np.sin(t) + np.random.normal(0, 0.3, total_samples)

# 타겟 생성 (Feature 0번의 추세를 따름)
# 단순히 값이 높은 게 아니라, '상승 추세'일 때 1
y_full = np.zeros(total_samples)
trend_slope = np.gradient(np.sin(t)) # 기울기 계산
y_full[trend_slope > 0] = 1 # 기울기가 양수(상승장)면 1

# 스케일링
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_full)
X_seq, y_seq = create_sequences(X_scaled, y_full, seq_length)

# Train/Test 분리
split_idx = int(len(X_seq) * 0.8)
X_train_raw, X_test = X_seq[:split_idx], X_seq[split_idx:]
y_train_raw, y_test = y_seq[:split_idx], y_seq[split_idx:]

# 밸런싱 (Oversampling)
def balance_data(X, y):
    idx_0 = np.where(y == 0)[0]
    idx_1 = np.where(y == 1)[0]
    target_n = max(len(idx_0), len(idx_1))

    if len(idx_0) < target_n:
        idx_0 = np.random.choice(idx_0, target_n, replace=True)
    elif len(idx_1) < target_n:
        idx_1 = np.random.choice(idx_1, target_n, replace=True)

    new_indices = np.concatenate([idx_0, idx_1])
    np.random.shuffle(new_indices)
    return X[new_indices], y[new_indices]

X_train, y_train = balance_data(X_train_raw, y_train_raw)
print(f"데이터 준비 완료. Train Shape: {X_train.shape}")

 

Hybrid Model Design (Methodology)

1) trend_aware_attention_block (Self-Attention 구현)

 

*****About Self-Attention*****

요약: 모든 데이터를 한 번에 펼쳐놓고, 서로 얼마나 친한지 점수를 매기는 과정.

- 기존 방식인 LSTM/RNN의 경우, 데이터를 순서대로 하나씩 읽는데, 이렇게 되면 오래 전의 중요한 데이터는 까먹기 쉽다.

=> Self-Attention의 방식: 20일치 데이터를 동시에 보고 질문을 한다.

     : "오늘 주가가 떨어진 게 3일차 거래량 폭등 때문일까? 아니면 10일차 뉴스 때문일까?"

       / AI는 스스로 모든 날짜끼리의 연관성을 계산한다.

def trend_aware_attention_block(x, num_heads=4, key_dim=16):
    """
    [Graph Mining 대체]
    Self-Attention은 사실상 'Fully Connected Graph'입니다.
    데이터(Feature)들 사이의 보이지 않는 연관성을 찾아 가중치를 둡니다.
    """
    # 1. 상관관계 학습 (Attention)
    attn_output = MultiHeadAttention(num_heads=num_heads, key_dim=key_dim)(x, x
    x = Add()([x, attn_output])
    x = LayerNormalization()(x)

    # 2. 정보 통합 (Feed Forward)
    ffn = Dense(x.shape[-1], activation='swish')(x) # ReLU 대신 Swish (최신 트렌드)
    x = Add()([x, ffn])
    x = LayerNormalization()(x)
    return x

 

- 역할: 데이터(Feature)들끼리 서로 얼마나 관련이 있는지 계산

- Add() (Residual Connection) : 기존 정보(x)를 잃어버리지 않도록, 새로 배운 정보(attn_output)에 더해준다. (학습 안정성 help)

- activation='swish' : 최근 구글이 개발한 활성화 함수로, 기존의 ReLU보다 깊은 신경망에서 학습이 더 잘 되는 경향이 있어 최신 연구에서 많이 쓰인다.

 

추가로 상관관계 학습에서는, 자기 자신이 가지고 있는 모든 변수끼리의 상관관계를 동시에 계산하는데,

이것이 Attention 모델이 하는 일이다.

여기서 주목할 부분은 Attention 모델이 동시에 상관관계를 계산한다는 것이다.

기존의 LSTM은 순차적으로 계산을 하기에(Day1 읽고-기억-Day2 읽고-기억...)이전의 내용을 까먹기 쉽지만,

즉 비효율적이지만, Attention은 행렬곱셉(Matrix Multipltcation)으로 모든 날짜끼리의 관계점수를 한 번에 계산한다.

 

또한 변수끼리의 상관관계를 계산하는 과정을 살펴보면

입력: 가격, 거래량, 배당금 등의 정보가 뭉쳐진 그날의 상태( $x$ )

계산: (예시) 1월 5일의 상태(입력값)이 1월 20일의 상태와 얼마나 비슷한 패턴인가?

       : 배당락일(Day A)의 상황이 오늘(Day B) 주가에 얼마나 영향을 주는가?

결과: 단순히 변수 하나하나를 보는 것을 넘어, 특정 시점의 종합적인 상황끼리의 연결고리를 찾아낸다.

 

Self-Attention 메커니즘을 통해 시계열 데이터 내 잠재된 Feature 간의
비선형적 상호작용(Global Context)을 포착한다.

 

[한 줄씩 해석]

@ 입력x: 모델에 들어온 데이터(예: 15일치 주가 데이터 덩어리)

@상관관계 해석- MultiHeadAttention : 한 번만 훑어보는 게 아니라, 여러 개의 관점에서 동시에 분석.

   (거래량과 주가는 무슨 관계? & 최고가랑 최저가의 사이는 어떻지?)

@ 상관관계 해석 - (x,x)의 의미: Self-Attention의 증거. 입력 데이터 x 안에서, x 자기 자신끼리의 연관성을 찾아라~ 라는 명령어

@ 정보 결합 - Add() : 원래 데이터(x)에 방금 알아낸 연관성 정보(attn_output)를 더해준다

     (AI가 원관성을 찾다가, 자칫 원래 주가 데이터를 망칠 수 있다. 그래서 원래 정보는 보존하되, 새로운 깨달음만 얹어주는 방식을 씅다. 이를 잔차 연결=Residual Connection이라소 하며, 딥러닝 성능을 비약적으로 높여준다.)

@ 안정화- x=LayerNormalization()(x) : 데이터의 범위를 깔끔하게 정리해주는 메서드. 데이터를 계속 더하다 보면 숫자가 너무 커져서 계산이 터질 수 있다. 이를 방지하기 위해 평균과 분산을 맞춰주어 학습을 안정적으로 만든다. 

@ 정보 가공 및 해석(Feed Forward Network)

    - ffn = Dense() : 위에서 찾은 연관성 정보를 바탕으로 AI가 심층적인 판단을 내리는 단계이다.

   - activation = 'swish' : 판단을 내릴 때 사용하는 '뇌세포 활성화 방식'(최신 기법)

     (해석: "아, 3일 전 거래량이랑 오늘의 주가가 관련이 깊구나(Attention). 그렇다면 내일은 오를 확률이 높겠네(Dense 판단)")

@ 마지막 세줄 : 위와 마찬가지로 AI가 판단한 정보(ffn)를 다시 원본에 더해주고(Add), 숫자를 정리(LayerNormalization)한 뒤 결과를 내보낸다.

 

 

2) CNN + Attention + LSTM을 하나로 합치는 메인 모델

  : 단순히 말하자면 

def build_context_model(input_shape):
    inputs = Input(shape=input_shape)

    # [Branch 1] Trend Feature Extraction (CNN)
    # 최근 15일간의 등락 패턴(기울기)을 1D-CNN으로 포착
    trend = Conv1D(64, kernel_size=3, padding='same', activation='swish')(inputs)
    trend = BatchNormalization()(trend)
    trend = Dropout(0.2)(trend)

    # [Branch 2] Context Mining (Transformer/Attention)
    # 주변 배경(Feature 상관관계)을 학습
    context = trend_aware_attention_block(inputs, num_heads=4, key_dim=32)
    context = trend_aware_attention_block(context, num_heads=4, key_dim=32)

    # [Merge] 추세와 맥락의 결합
    combined = Concatenate()([trend, context])

    # [Sequential Learning] 시계열 흐름 정리 (LSTM)
    x = LSTM(64, return_sequences=False)(combined)
    x = Dropout(0.3)(x)

    # [Decision Head]
    x = Dense(64, activation='swish')(x)
    x = BatchNormalization()(x)
    x = Dropout(0.2)(x)

    outputs = Dense(1, activation='sigmoid')(x)

 

  2-1)  [Branch1] CNN - 단기 패턴 탐정

    # [Branch 1] Trend Feature Extraction (CNN)
    # 최근 15일간의 등락 패턴(기울기)을 1D-CNN으로 포착
    trend = Conv1D(64, kernel_size=3, padding='same', activation='swish')(inputs)
    trend = BatchNormalization()(trend)
    trend = Dropout(0.2)(trend)

 - Conv1D (CNN) : 3일치 데이터를 한 번에 묶어서 본다. 주가의 급등/급락 같은 국소적 패턴을 아주 잘 찾는다.

 ""

    # [Branch 2] Context Mining (Transformer/Attention)
    # 주변 배경(Feature 상관관계)을 학습
    context = trend_aware_attention_block(inputs, num_heads=4, key_dim=32)
    context = trend_aware_attention_block(context, num_heads=4, key_dim=32)

2-2) [Branch2] Context Mining (Transform/Attention)

  - 앞에서 언급한 '상관관계 계산기' CNN은 국소적 패턴을 매우 잘 찾지만, 그것은 곳 CNN의 한계가 된다.

    즉, 전역적 맥락을 파악하지 못한다는 것이다. 그러나 이 부분은 Attention이 매울 수 있기 때문에

    CNN과 함께 사용한다. 나머지 괄호 안의 내용은 4가지 관점에서의 상관관계 값을 계산함을 의미하고,

    같은 함수를 두 번 실행한 것은 같은 블록을 Stacking하여 복합적 추론을 통해

    단순한 수치 비교를 넘어 더 고차원적이고 정확한 맥락을 이해하게 된다.

     *CNN과 Attention은 순차적으로 실행되는 것이 아닌 병렬적으로 실행된다.

 

    # [Merge] 추세와 맥락의 결합
    combined = Concatenate()([trend, context])

2-3) [Merge] : 정보 통합(Feature Fusion)

     : 병렬적으로 진행된 CNN이 찾은 특징(trend)과 Attention이 찾은 특징(context)을 물리적으로 결합한다.

       이제 데이터는 패턴 정보(국소)와 맥락 정보(전역)를 모두 가지게 된다.

 

    # [Sequential Learning] 시계열 흐름 정리 (LSTM)
    x = LSTM(64, return_sequences=False)(combined)
    x = Dropout(0.3)(x)
    # [Decision Head]
    x = Dense(64, activation='swish')(x)
    x = BatchNormalization()(x)
    x = Dropout(0.2)(x)

    outputs = Dense(1, activation='sigmoid')(x)

2-4) 시계열 최종 판단(LSTM)

     : 앞에서 CNN과 Attention이 합쳐진 데이터(combined)를 시간 순서대로 입력받는다.

       과거의 정보가 현재에 미치는 영향을 계산한다.

     - return_sequences = False : 매일매일 예측하는 것이 아닌 15일 치를 다 보고 나서 맨 마지막 날의 요약본 딱 하나만 내놓음

 

    # [Decision Head]
    x = Dense(64, activation='swish')(x)
    x = BatchNormalization()(x)
    x = Dropout(0.2)(x)

    outputs = Dense(1, activation='sigmoid')(x)

2-5) Decision Head

     : 순서대로 해설

       x = Dense(64, activation='swish')(x)   -> LSTM에서 나온 정보는 아직 '시계열 정보'의 성격이 강하다. 이를 상승 또는 하락이라는 정답과 연결하기 위해 한 번 더 섞어서 해석하는 완전 연결 계층이다. 하 완전연결계층이라는데 이게 뭔 개소리인지 모르겠어서 나중에 공부하려고 남겨놓겠음....머리 끝까지 화가 나서 화병 걸릴 것 같음

       x = BatchNormalization()(x) 과 x = Dropout(0.2)(x)  ---->  판결을 내리기 전에 데이터를 깔끔하게 정돈하고(BatchNorm), 섣부른 판단을 막기 위해 일부 뉴런을 끈다(Dropout). 마지막까지 신중함을 유지하는 장치이다.

       outputs = Dense(1, activation='sigmod') -----> 우리가 원하는 단 하나의 예측값을 0과1 사이의 확률로 압축시킨다.

 

3) 모델 조립의 마지막 단계(설정)

    model = Model(inputs=inputs, outputs=outputs, name="Trend_Context_Network")

    # Optimizer & Loss
    model.compile(optimizer=Adam(learning_rate=0.0005),
                  loss='binary_crossentropy',
                  metrics=['accuracy', tf.keras.metrics.AUC(name='auc')])
    return model

model = build_context_model((X_train.shape[1], X_train.shape[2]))
model.summary()

       

3-1) optimizer=Adam(learning_rate=0.0005): "과외 선생님과 진도 속도"

  • Adam (과외 선생님): 현재 가장 인기 있고 성능 좋은 최적화 알고리즘입니다. 수학 못하는 모델을 달래가면서 정답(최저점)으로 안내하는 역할을 합니다.
  • learning_rate=0.0005 (학습률): "공부하는 보폭"입니다.
    • 너무 크면(0.1): 성큼성큼 가다가 정답을 지나쳐 버립니다.
    • 너무 작으면(0.00001): 개미처럼 기어가서 날 밤새웁니다.
    • 0.0005: 아주 신중하게(천천히) 조금씩 정답을 찾아가라는 뜻입니다. (금융 데이터는 예민하니까요!)

3-2) loss='binary_crossentropy': "틀렸을 때 때리는 회초리"

  • Binary (이진): 우리는 상승(1) vs 하락(0) 두 개만 다루죠?
  • Crossentropy (오차 측정):
    • 모델이 "상승 확률 90%!"라고 했는데 실제로 하락(0)했으면? → 엄청 크게 혼냅니다 (Loss가 커짐).
    • 모델이 "상승 확률 51%!"라고 했는데 하락(0)했으면? → 살짝 혼냅니다.
  • 이 점수(Loss)를 줄이는 방향으로 공부하게 됩니다.

3-3) metrics=['accuracy', ...AUC...]: "성적표 항목"

공부하는 중간중간에 우리한테 보여줄 점수입니다.

  • Accuracy (정확도): "100문제 중에 몇 개 맞췄니?" (예: 60점)
  • AUC (Area Under Curve): "얼마나 확신을 갖고 맞췄니?"
    • 금융에서는 단순히 맞추는 것보다, 확신이 있을 때만 배팅하는 게 중요해서 AUC 지표가 매우 중요합니다.

3-4) model = build_context_model(...): "주문 제작 완료"

 

 

  • 설계도(build_context_model)에다가 실제 우리 데이터의 크기(15일치, 변수 9개 등)를 알려주고 "자, 이제 이 사이즈로 실물 기계를 뽑아내라!" 하고 명령하는 겁니다.

3-5) model.summary(): "최종 점검 (영수증 확인)"

  • 모델의 전체 구조를 요약해서 표로 보여줍니다.
  • "CNN 층에선 파라미터가 몇 개고, LSTM 층 모양은 어떻고..." 이런 게 쭉 뜹니다.
  • 확인할 점: 맨 마지막 줄에 Total params(총 파라미터 수)가 나옵니다. 이 숫자가 모델의 지능(뇌세포 수)이라고 보시면 됩니다.

 

정리!!!

-모델 개요

: 본 연구에서 제한하는 CANTAN(Context-Aware Trend Attention Network)은 금융 시계열 데이터의 복합적인 특성을 포착하기 위해 Dual-Branch 구조를 채택하였다. 이 모델은 국소적 특징을 추출하는 CNN 모듈과 전역적 상관관계를 학습하는 Self-Attention 모듈을 병렬로 배치한 후, 이를 LSTM을 통해 시계열적으로 통합하는 하이브리드 아키텍쳐이다.

- 국소 패턴 추출 모듈(Local Feature Extracrtion via CNN)

: 주가 데이터는 단기간 내에 발생하는 급격한 변동 패턴이 중요한 정보를 담고 있다. 이를 포착하기 위해 1D-CNN(Convolutional Neural Network)을 첫 번째 브랜치로 사용하였다. 필터 크기(Kernel Size)를 3으로 설정하여 최근 3일간의 가격 및 거래량 변동 패턴을 슬라이딩 윈도우 방식으로 추출하여, Swish 활성화 함수를 사용하여 비선형 학습 능력을 강화하였다.

- 전역 맥락 학습 모듈(Global Context Mining via Attention)

: 단순한 패턴 매칭을 넘어 배당금이나 거래량 변화가 특정 시점의 주가에 미치는 잠재적 영향력을 분석하기 위해 Multi-Head Self-Attention을 두 번째 브랜치로 적용하였다. 이 모듈은 시퀀스 내 모든 시점 간의 상호 연관성을 병렬적으로 계산하여, 모델이 예측 시 중요하게 고려해야 할 시점에 가중치를 부여하도록 설계되었다.

- 특징 융합 및 시계열 모델링

: CNN과 Attention 모듈에서 추출된 이직적인 Feature Vector는 Concatenation Layer를 통해 결합된다.

  Concatenation Layer은 서로 다른 경로(Branch)에서 추출된 Feature Vector들을 특정 차원(축)을 따라 연결한다. 

  CNN과 Attention 모듈에서 추출된 특징 벡터를 결합할 때 Concatenation Layer는 두 종류의 보완적인 정보를(지역적 패턴과 전역적 패턴) 손실 없이 물리적으로 하나의 벡터 공간으로 모아준다. 결합된 벡터는 LSTM층으로 입력되어, 과거로부터 현재까지 이어지는 시계열적 순서 정보와 장기 의존성이 반영된 최종 예측 확률을 산출한다. 

 

 

 

4) 모델 학습

# ---------------------------------------------------------
# 4. [학습]
# ---------------------------------------------------------
print("\n--- 학습 시작 ---")
early_stopping = EarlyStopping(monitor='val_auc', patience=12, restore_best_weights=True, mode='max')
reduce_lr = ReduceLROnPlateau(monitor='val_auc', factor=0.5, patience=5, min_lr=1e-6)

history = model.fit(
    X_train, y_train,
    validation_split=0.2,
    epochs=100,
    batch_size=32,
    callbacks=[early_stopping, reduce_lr],
    verbose=1
)

 

: 모델 학습 전략 - 최대 100Epoch 동안 진행되었으며, Batch Size는 32로 설정하였다.

데이터의 20%를 검증 셋(Validation Set)으로 분리하여 학습 과정에서 모니터링하였다.

모델의 일반화 성능을 극대화 하기 위해 두 가지 Callback전략(* 프로그래밍에서 특정 작업이 완료된 후에 실행되도록 다른 함수를 인자로 전달하는 '콜백 함수'를 이용한 전략)을 사용하였다.

첫째, ReduceLROnPlateau를 통해 검증 AUC가 5 Epoch 동안 정체될 시 학습률을 0.5배씩 감소시켜 미세조정을 유도한다.

둘째, Early Stopping을 적용하여 12Epoch 동안 성능 향상이 없을 경우 학습을 조기 종료하고, 과적합 되기 전의 최적 가중치를 복원하였다.

 

5) 모델 평가 및 최적화

# ---------------------------------------------------------
# 5. [평가] F1-Score 중심의 벤치마크 리포트
# ---------------------------------------------------------
print("\n--- Context-Aware Model Benchmark ---")

y_pred_prob = model.predict(X_test)

# [Threshold Tuning] F1-Score를 최대화하는 임계값 탐색
thresholds = np.arange(0.1, 0.9, 0.01)
f1_scores = []
for t in thresholds:
    pred_labels = (y_pred_prob > t).astype(int)
    f1_scores.append(f1_score(y_test, pred_labels, average='macro'))

best_th = thresholds[np.argmax(f1_scores)]
y_pred_final = (y_pred_prob > best_th).astype(int)

# 4대 지표
acc = accuracy_score(y_test, y_pred_final)
prec = precision_score(y_test, y_pred_final, average='macro')
rec = recall_score(y_test, y_pred_final, average='macro')
f1 = f1_score(y_test, y_pred_final, average='macro')
auc = roc_auc_score(y_test, y_pred_prob)

print(f"Optimization Threshold: {best_th:.2f}")
print("=" * 40)
print(f"Accuracy  : {acc:.4f}")
print(f"Precision : {prec:.4f}")
print(f"Recall    : {rec:.4f}")
print(f"F1-Score  : {f1:.4f}  <-- [비교 핵심 지표]")
print(f"AUC Score : {auc:.4f}")
print("=" * 40)

print("\nClassification Report:")
print(classification_report(y_test, y_pred_final))

# 시각화: Attention Weight (가상) - 어떤 시점이 중요했나?
# 실제 Attention Weight를 뽑으려면 모델 구조를 조금 바꿔야 하지만,
# 여기서는 학습 곡선과 혼동 행렬로 대체합니다.
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
sns.heatmap(confusion_matrix(y_test, y_pred_final), annot=True, fmt='d', cmap='Greens')
plt.title('Confusion Matrix (Trend & Context)')

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.title('Learning Curve')
plt.legend()
plt.tight_layout()
plt.show()

  5-1) 임계값 최적화

  : 금융 시계열 데이터는 노이즈가 많고 클래스 불균형이 발생하기 쉬워, 일반적인 분류 임계값인 0.5를 일괄 적용할 경우,

    예측의 신뢰도가 하락할 수 있다. 이에 본 연구는 F1-Score를 최대화 하는 동적 임계값 탐색(Dynamic Threshold Search)

    기법을 적용하였다. 0.1부터 0.9까지 0.01 단위로 임계값을 조정하며 검증한 결과, 최적 임계값은 0.37로 도출되었다.

    이는 일반적인 기준인 0.5보다 낮은 수치로, 제안 모델이 상승 추세를 포착하기 위해 보다 민감하게 조정되었음을 의미한다.

    즉, 이 임계값 조정을 통해 놓칠 수 있었던 매수 기회(Recall)을 확보하면서도, 예측의 정확성(Precision)을 훼손하지 않는 

    최적의 균형점을 찾아내었다.

 

  5-2) 정량적 성능 평가

  : 최적화된 임계값(0.37)을 적용한 제안 모델의 최종 성능 지표는 매우 고무적이다.

    전체적인 정확도(Accuracy)는 64.23%, 실제 투자 성과와 직결되는 정밀도(Precision)은 64.16%,

    재현율(Recall)은 64.17%로 나타났다. 특히 주목할 점은 정밀도와 재현율의 차이가 0.01%p 내외로 거의 동일하다는 것.

    이는 모델이 매수 신호를 남발하거나(과도한 재현율), 지나칙게 보수적으로 판단하지 않고(과도한 정밀도), 

    매우 안정적인 예측 성능을 유지하고 있음을 입증한다. 이에 따른 최종 F1-Score는 64.17%이며,

    AUC Socre 또한 0.6519를 기록하여 무작위 예측(0.5) 대비 뚜렷한 예측 우위를 확보하였다.

 

  5-3) 결과의 함의

  : 본 연구의 64%대 성능은 Dow Jones 지수와 같은 우량주 시장에서도 펀더멘털 변수와 기술적 지표의 앙상블이 유효함을 시사한다. 특히 0.37이라는 낮은 임계값에서도 64% 이상의 정밀도를 유지한 것은, 모델이 노이즈 속에서도 유의미한 상승 시그널을 효과적으로 필터링하고 있음을 보여준다.

 

 

6) 예측의 근거를 설명하는 단계

- 제안하는 모델이 학습 과정에서 데이터의 노이즈를 배제하고 유의미한 변수를 기반으로 의사결정을 수행하는지

  검증하기 위해 SHAP 기법을 적용한 결과를 분석했다.

  분석 대상 -> 사전 정의된 인과관계 시나리오가 주입된 검증용 데이터셋

 

  6-1) 전역적 특정 중요도 분석(Global Feature Importance)

  : 모델의 전반적인 의사결정 구조를 파악하기 위해

    전체 테스트 데이터에 대한 SHAP 절대값의 평균(Mean Absolute SHAP Value)을 산출하였다.

SHAP Value

  위 그래프는 각 변수가 모델의 예측 출력에 미친 영향력을 중요도 순으로 나열한 결과이다.

  분석 결과, 사전 시나리오에서 종속변수와 직접적인 상관관계를 갖도록 설계된 Trend_indicatior와 Inverse_Metric이

  최상위 중요 변수로 도출되었다. 반면, 종속변수와 무관하게 생성된 무작위 변수군(Noise_A~Noise_E)은 하위권에 머물렀고,

  이들의 SHAP 값은 상위 두 변수 대비 현저히 낮은 수준을 기록하였다.

  이는 제안 모델이 다차원 시계열 데이터 내에서 불필요한 정보(Noise)를 효과적으로 필터링하고, 예측에 필수적인 핵심 인자를

  정확하게 식별하여 학습하였음을 정량적으로 입증한다.

 

  6-2) 지역적 예측 근거 분석 (Local Interpretation via Waterfall Plot)

  - 개별 예측 사례에 대한 모델의 추론 과정을 규명하기 위해 특정 샘플에 대한 Waterfall plot 분석을 수행함.

    해당 샘플에 대해 모델은 0.0004의 확률로 '하락'을 강혁하게 예측했다. 

Waterfall Plot

  위의 그래프에서 E[f(x)]는 전체 데이터셋에 대한 모델의 평균 예측값을 의미하며,

  f(x)는 해당 샘플의 최종 예측값(0.0004)을 나타낸다. 분석 결과,

   1. 부정적 기여 요인(Negative Contribution)

    : 차트의 파란색 막대는 예측 확률을 낮추는 요인을 나타낸다. 해당 샘플에서는 Trend_Indicatior의 특정 시점 값들이

     예측 확률을 낮추는 요인을 나타낸다. 해당 샘플에서는 Trend_Indicator의 특정 시점 값들이 예측 확률을 0(하락) 방향으로

     강력하게 견인하였다. 이는 해당 시점에서 추세 지표가 하락 반전 신호를 보였거나,

     역상관 지표인 'Inverse_Metric'이 상승 압력을 행사했음을 시사한다.

   2. 예측의 신뢰성

    : 모델은 0.5(무작위 확률) 근처의 불확실한 예측이 아니라, 결정적인 변수들의 복합적인 작용을 통해

      0.0004라는 확신에 찬 하락 예측을 도출하였다.

  결론적으로, Waterfall Plot은 모델이 단순히 결과를 출력하는 것에 그치지 않고, 기저 확률에서 출발하여

  각 변수의 가감을 통해 최종 결론에 도달하는 논리적 과정을 투명하게 보여준다.

  이는 본 연구가 제안하는 XAI 프레임워크가 실제 금융 시장의 복잡한 변동성 요인을 분해햐고 해석하는 데 유효함을 시사한다.

 

Fully connected layer
AUC
ROC 곡선
ReduceLROnPlateau: 더이상 학습이 진행되지 않을 때 learning rate를 감소시키는 scheduler. scheduler에 input으로 metric을 주면, 일정 epoch동안 변화가 없을 때 learning rate를 감소시킨다. 

1D Convolutional Neural Network: 머신러닝 분야에서 예측 모델을 만드는데 가장 많이 사용되는 신경망 모델은 Convolutional Neural Network이다. CNN은 이미지 분류에서 높은 정확도를 보이며, 많은 예측 모델의 토대를 이루었다. 그러나 1차원 CNN은 이미지가 아닌 시계열 분석(time-series analysis)나 텍스트 분석을 하는데 주로 많이 사용된다. 여기에서 1차원이라는 것은 합성곱을 위한 커널(입력 데이터에서 특정 Feature을 추출하기 위한 작은 가중치 행렬 또는 필터)과 적용하는 데이터의 sequence가 1차원의 모양을 가진다는 것을 의미한다. 1D-CNN은 필터가 길이 축을 ㄸ라 1방향으로 슬라이딩하고, 시간적 또는 순서적 패턴을 학습하는 순서와 지역적 패턴을 추출하는 데 특화되어 있어, 시간적 또는 순서적 특징이 중요한 시계열 및 텍스트 데이터에 적합하다. 

Epoch(에포크)
: 훈련 데이터셋에 포함된 모든 데이터들이 한 번씩 모델을 통과한 횟수로, 모든 학습 데이터셋을 학습하는 횟수를 의미. epoch를 높일수록, 다양한 무작위 가중치로 학습을 해보기에, 적합한 파라미터를 찾을 확률이 올라간다. 다만 지나치게 epoch를 높이면 데이터셋이 과적합되어 다른 데이터에 대해선 제대로 된 예측이 어려워짐.

Batch Size : 연산 한 번에 들어가는 데이터의 크기.

임계값 : 어떤 현상이나 과정이 달라지기 시작하는 경계가 되는 값