데이터 분석 part

LSTM 이해와 마케팅 데이터를 활용한 실습, LSTM 모델 성능평가

bleufonce 2025. 3. 18. 11:52

 

< LSTM >

 

LSTM(Long Short-Term Memory)의 개념

LSTM은 RNN(Recurrent Neural Network)의 한 종류로, 장기 의존성(Long-term dependency) 문제를 해결하기 위해 개발된 모델이다.

기본 RNN은 시간이 지남에 따라 기울기 소실(Vanishing Gradient) 문제가 발생하는데, LSTM은 셀 상태(Cell State)와 게이트 구조(Gates)를 활용하여 이를 해결한다. (w가 지워지지 않도록)

 

LSTM의 주요 구성 요소

  1. 셀 상태(Cell State): 네트워크의 장기 기억을 유지하는 경로
  2. 입력 게이트(Input Gate): 새로운 정보를 셀 상태에 추가할지 여부 결정
  3. 망각 게이트(Forget Gate): 불필요한 정보를 제거
  4. 출력 게이트(Output Gate): 현재 상태에서 어떤 값을 출력할지 결정

LSTM은 이 3가지 게이트를 통해 장기 기억을 유지하면서 시계열 데이터를 효과적으로 학습할 수 있다.

 

LSTM의 수식

- LSTM의 동작을 수식으로 정리

 

# 게이트 연산

각 게이트는 시그모이드(𝜎) 활성화 함수를 사용하여 0~1 사이 값을 출력하고, 이는 얼마나 정보를 유지할지 결정한다.

 

  • 망각 게이트(Forget Gate)
  • 입력 게이트(Input Gate)
  • 셀 상태 업데이트(Candidate Cell State)
  • 최종 셀 상태(Cell State)
  • 출력 게이트(Output Gate)
  • 은닉 상태(hidden state) 업데이트
* 가중치 행렬 → 기울기 / * 이전은닉상태 → 이전 히든레이어 (얘 때문에 기억가능)

 

 
 
LSTM 셀의 구조 (작동방식)
 
 

 

LSTM 모델 성능평가 지표

(1) 평균제곱오차(Mean Squared Error, MSE)

 

- 모델이 실제값과 예측값 간의 차이를 얼마나 작게 만드는지 평가

  • 예측값과 실제값의 차이를 제곱한 후 평균을 구한 값
  • 값이 작을수록 모델의 예측이 정확함
(2) 평균절대오차(Mean Absolute Error, MAE)
 
- 예측값과 실제값 간의 절대적인 차이를 평균한 값
  • 예측값과 실제값의 차이를 절대값으로 변환하여 평균을 구한 값
  • 모델이 얼마나 실제값에 가까운지 직관적으로 확인 가능
(3) 결정계수(R² Score)
 
- 모델이 얼마나 실제값을 설명할 수 있는지 측정
  • 1에 가까울수록 예측 모델이 실제 데이터를 잘 설명함
  • 0에 가까우면 모델의 설명력이 낮음을 의미

(4) 평균절대백분율오차(Mean Absolute Percentage Error, MAPE)

 

- 상대적인 오차율을 측정하여 모델이 얼마나 정확한지를 백분율로 표시

  • 예측값의 오차를 백분율로 나타내어 상대적 성능 평가 가능
 

LSTM 모델 성능평가 수식

# 손실 함수(loss function)

LSTM 모델의 훈련 과정에서 사용되는 손실 함수는 보통 MSE(Mean Squared Error)이다.

손실함수를 최소화하는 방향으로 모델이 학습된다.

 

 

 


 
 
 
 

※ LLM과 LSTM의 관계

 

LLM(대형 언어 모델)과 LSTM(장단기 기억 네트워크)은 둘 다 자연어 처리(NLP)에서 사용되는 모델이지만, 구조와 사용 목적이 다르면서도 관계가 있다.

1. LSTM이 LLM의 기반 기술 중 하나였음.

과거에는 LSTM이 NLP 모델의 핵심 기술로 사용되었고, LLM이 등장하기 전까지 많은 발전을 이루었다. 하지만 현재 LLM은 주로 트랜스포머(Transformer) 기반 모델(GPT, BERT 등)을 사용한다.

 

2. LSTM vs. LLM의 차이점

구분 LSTM LLM (GPT 등)
모델 유형 순환신경망(RNN) 기반 트랜스포머(Transformer) 기반
기억 방식 순차적 처리(시간축을 따라 학습) 병렬 처리(Self-Attention)
학습 데이터 주로 짧은 문장 또는 시퀀스 방대한 양의 텍스트 데이터
한계점 장기 의존성 문제, 느린 학습 속도 대규모 데이터 학습 가능, 성능 우수

 

3. LSTM이 LLM에 미친 영향

LSTM은 과거 NLP 모델에서 중요한 역할을 했으며, 특히 시퀀스 데이터를 처리하는 능력이 뛰어났다. 하지만 트랜스포머 모델이 등장하면서 LSTM이 가진 한계(긴 문맥을 기억하는 어려움, 병렬 연산 불가능 등)를 극복할 수 있게 되었다.
즉, LSTM은 LLM의 직전 세대 기술로 볼 수 있으며, NLP 발전 과정에서 중요한 역할을 했다.

결론적으로, LSTM은 LLM의 직접적인 구성 요소는 아니지만, NLP 모델이 발전하는 과정에서 중요한 역할을 했고, 트랜스포머 모델이 LSTM을 대체하면서 LLM이 등장하게 된 것이다.

 

※ 트랜스포머는 기존 RNN/LSTM의 한계를 극복하고 NLP 분야에서 혁신을 일으킨 모델이다. 현재 ChatGPT, Google Bard, Claude 등 대부분의 대형 언어 모델(LLM)은 트랜스포머 기반으로 만들어졌다.

 


 

 

 

- LSTM 실습 코드

* 시나리오 설정 (LSTM의 간단한 부분만 확인)

한 전자상거래 기업이 과거 3년간의 월별 매출 데이터를 기반으로 다음 달 매출을 예측하려 한다.

  • 입력 데이터(X): 과거 12개월의 매출
  • 출력 데이터(y): 다음 달의 매출
  • 데이터를 LSTM 모델로 학습하여, 패턴을 찾아 매출 변동을 예측

* 데이터

  • 데이터는 월별 매출로 구성되며, 3년치(36개월) 데이터를 사용한다.
  • 특징(Feature)으로 최근 12개월 데이터를 입력으로 사용하여 다음 달 매출을 예측하는 구조.

→ 36개월 학습된거에다가 입력은 12개월

# # LSTM 실습 코드

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

# 1. 하드코딩된 마케팅 매출 데이터 (3년간 월별 매출)
data = {
    'Month': pd.date_range(start='2020-01-01', periods=36, freq='M'),
    'Sales': [
        200, 220, 250, 270, 300, 350, 400, 380, 360, 340, 310, 280,
        240, 260, 290, 310, 340, 390, 420, 400, 370, 350, 320, 290,
        260, 280, 310, 330, 360, 410, 440, 420, 390, 370, 340, 310
    ]
}

df = pd.DataFrame(data)
df.set_index('Month', inplace=True)

# 2. 데이터 정규화
scaler = MinMaxScaler(feature_range=(0, 1))
df_scaled = scaler.fit_transform(df)

# 3. LSTM 입력 데이터 생성 (과거 12개월 -> 다음 달 예측)
def create_sequences(data, seq_length=12):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

seq_length = 12
X, y = create_sequences(df_scaled, seq_length)

# 4. 학습 데이터와 테스트 데이터 분할 (80% 학습, 20% 테스트)
train_size = int(len(X) * 0.8)
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

# 5. LSTM 모델 구축
model = Sequential([
    LSTM(50, activation='relu', input_shape=(seq_length, 1), return_sequences=True),
    LSTM(50, activation='relu'),
    Dense(1)
])

# 6. 모델 컴파일 (# 5.번 위의 구조를 만듬(설계도))
model.compile(optimizer='adam', loss='mse')

# 7. 모델 학습
history = model.fit(X_train, y_train, epochs=100, batch_size=16, validation_data=(X_test, y_test))

# 8. 예측 수행
y_pred = model.predict(X_test)

# 9. 결과 되돌리기 (Inverse Transform)
y_test_inv = scaler.inverse_transform(y_test.reshape(-1, 1))
y_pred_inv = scaler.inverse_transform(y_pred)

# 10. 결과 시각화
plt.figure(figsize=(12, 6))
plt.plot(df.index[-len(y_test):], y_test_inv, label="Actual Sales", marker="o")
plt.plot(df.index[-len(y_test):], y_pred_inv, label="Predicted Sales", linestyle="--", marker="s")
plt.xlabel("Month")
plt.ylabel("Sales")
plt.title("LSTM Marketing Sales Prediction")
plt.legend()
plt.show()

결과 해석 : 

 

LSTM 모델 구조

  • 첫 번째 LSTM 레이어 (50 뉴런, relu 활성화, return_sequences=True)
  • 두 번째 LSTM 레이어 (50 뉴런, relu 활성화)
  • Dense 레이어 (출력 뉴런 1개)
  • 최적화 알고리즘: Adam
  • 손실 함수: MSE (Mean Squared Error)

학습 과정

  • 과거 12개월 데이터를 입력으로 사용하여 다음 달 매출 예측
  • 100 Epoch 동안 학습 후 검증

결과 시각화

  • 실제 매출(Actual Sales) vs. 예측 매출(Predicted Sales) 그래프 출력

※ epoch 100번 돌린 것과 epoch 300번 돌린 것 비교.

좌 : 에폭100번 / 우 : 에폭 300번

 

 

 

 

# 예제 : 고객 유지율 예측

LSTM 모델을 사용하여 다음 달의 고객 유지율을 예측하고, 이를 기반으로 마케팅 전략을 수립한다.

  • Month: 월별 데이터
  • Subscribers: 해당 월의 전체 구독자 수
  • Churn: 해당 월의 이탈 고객 수
  • Retention Rate (%): 고객 유지율

# LSTM 모델을 사용하여 고객 유지율 예측

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

# 1. 데이터 정의
data = {
    'Month': pd.date_range(start='2020-01-01', periods=36, freq='M'),
    'Subscribers': [1000, 1050, 1100, 1200, 1300, 1400, 1500, 1550, 1600, 1700, 1750, 1800,
                    1850, 1900, 1950, 2000, 2050, 2100, 2200, 2250, 2300, 2350, 2400, 2450,
                    2500, 2550, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500],
    'Churn': [50, 40, 55, 60, 65, 70, 75, 80, 85, 90, 85, 80,
              75, 70, 65, 60, 55, 50, 50, 55, 60, 65, 70, 75,
              80, 85, 90, 95, 100, 110, 115, 120, 125, 130, 135, 140]
}
df = pd.DataFrame(data)
df['Retention Rate'] = (1 - df['Churn'] / df['Subscribers']) * 100
df.set_index('Month', inplace=True)

# 2. 데이터 정규화
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[['Retention Rate']])

# 3. LSTM 입력 데이터 생성
def create_sequences(data, seq_length=12):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

seq_length = 12
X, y = create_sequences(df_scaled, seq_length)

# 4. 모델 학습 및 예측
train_size = int(len(X) * 0.8)
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

model = Sequential([
    LSTM(50, activation='relu', input_shape=(seq_length, 1)),
    Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=300, batch_size=16, validation_data=(X_test, y_test))

# 5. 예측 및 시각화
y_pred = model.predict(X_test)
y_test_inv = scaler.inverse_transform(y_test.reshape(-1, 1))
y_pred_inv = scaler.inverse_transform(y_pred)

plt.figure(figsize=(10, 5))
plt.plot(df.index[-len(y_test):], y_test_inv, label="Actual Retention Rate", marker="o")
plt.plot(df.index[-len(y_test):], y_pred_inv, label="Predicted Retention Rate", linestyle="--", marker="s")
plt.xlabel("Month")
plt.ylabel("Retention Rate (%)")
plt.title("Customer Retention Rate Prediction using LSTM")
plt.legend()
plt.show()

좌 : 에폭 100번 / 우: 에폭 300번

(그래프 보고 epoch 수 조절하기)

 

 

 

 

# 예제 : 광고 캠페인 성과 예측

LSTM 모델을 사용하여 다음 달의 광고 투자 대비 매출 성과(ROAS, Return on Ad Spend)를 예측한다.

  • Month: 월별 데이터
  • Ad Spend: 월별 광고비 (단위: 만원)
  • Revenue: 월별 광고로 인한 매출 (단위: 만원)
  • ROAS: 광고 수익률 (ROAS = Revenue / Ad Spend)
# LSTM 모델을 사용하여 광고투자대비매출성과(ROAS)측정

# 1. 데이터 정의
data = {
    'Month': pd.date_range(start='2020-01-01', periods=36, freq='M'),
    'Ad Spend': np.linspace(50, 150, 36) + np.random.randn(36) * 5,  # 광고비 (만원)
    'Revenue': np.linspace(100, 300, 36) + np.random.randn(36) * 10  # 광고 매출 (만원)
}
df = pd.DataFrame(data)
df['ROAS'] = df['Revenue'] / df['Ad Spend']
df.set_index('Month', inplace=True)

# 2. 데이터 정규화 및 LSTM 적용
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[['ROAS']])
X, y = create_sequences(df_scaled, seq_length=12)
train_size = int(len(X) * 0.8)
X_train, X_test, y_train, y_test = X[:train_size], X[train_size:], y[:train_size], y[train_size:]

# 3. 모델 학습 및 예측
model.fit(X_train, y_train, epochs=100, batch_size=16, validation_data=(X_test, y_test))
y_pred = model.predict(X_test)

# 4. 예측 결과 시각화
y_test_inv = scaler.inverse_transform(y_test.reshape(-1, 1))
y_pred_inv = scaler.inverse_transform(y_pred)

plt.figure(figsize=(10, 5))
plt.plot(df.index[-len(y_test):], y_test_inv, label="Actual ROAS", marker="o")
plt.plot(df.index[-len(y_test):], y_pred_inv, label="Predicted ROAS", linestyle="--", marker="s")
plt.xlabel("Month")
plt.ylabel("ROAS")
plt.title("Ad Campaign Performance Prediction using LSTM")
plt.legend()
plt.show()

좌: 에폭100 / 우: 에폭 500

 

에폭 1000번 돌린 결과 

 

 

 

 

# 예제 : 바이럴 마케팅 효과 예측

LSTM을 사용하여 바이럴 마케팅이 발생한 후 몇 개월 후까지 매출 증가가 지속되는지 예측한다.

  • Month: 월별 데이터
  • Mentions: 소셜미디어에서 해당 브랜드가 언급된 횟수
  • Sales: 해당 월의 매출
# 바이럴마케팅 효과 예측

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

# 1. 데이터 생성
data = {
    'Month': pd.date_range(start='2020-01-01', periods=36, freq='ME'),  # 'M' → 'ME'로 수정!
    'Mentions': np.linspace(100, 500, 36) + np.random.randn(36) * 20,
    'Sales': np.linspace(200, 800, 36) + np.random.randn(36) * 30
}
df = pd.DataFrame(data)
df.set_index('Month', inplace=True)

# create_sequences 함수 정의
def create_sequences(data, seq_length):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i : i + seq_length])    # 입력 시퀀스 (seq_length 길이만큼)
        y.append(data[i + seq_length])        # # 정답 (시퀀스 다음 값)
    return np.array(X), np.array(y)

# 2. 데이터 정규화 및 모델 학습
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[['Sales']])
X, y = create_sequences(df_scaled, seq_length=12)
train_size = int(len(X) * 0.8)
X_train, X_test, y_train, y_test = X[:train_size], X[train_size:], y[:train_size], y[train_size:]

# LSTM 모델 생성
model = Sequential([
    LSTM(50, return_sequences=True, input_shape=(12, 1)),
    LSTM(50),
    Dense(1)
])
model.compile(optimizer='adam', loss='mse')

# 모델 학습
model.fit(X_train, y_train, epochs=200, batch_size=16, validation_data=(X_test, y_test))

# 3. 예측 결과 시각화
y_pred = model.predict(X_test)
y_test_inv = scaler.inverse_transform(y_test.reshape(-1, 1))
y_pred_inv = scaler.inverse_transform(y_pred)

plt.figure(figsize=(10, 5))
plt.plot(df.index[-len(y_test):], y_test_inv, label="Actual Sales", marker="o")
plt.plot(df.index[-len(y_test):], y_pred_inv, label="Predicted Sales", linestyle="--", marker="s")
plt.xlabel("Month")
plt.ylabel("Sales")
plt.title("Viral Marketing Effect Prediction using LSTM")
plt.legend()
plt.show()

에폭 200번

 

 

 

# 그로스마케팅 데이터를 기반으로 LSTM 모델을 활용하여 방문자 수, 클릭 수, 전환율 예측

  1. 데이터 생성
    • 가상의 그로스마케팅 데이터(방문자 수, 클릭 수, 전환 수, 전환율)를 생성하여 CSV 파일로 저장한다.
  2. 모델 학습 및 저장
    • CSV 데이터를 불러와 LSTM 모델을 학습하고, 학습된 모델을 파일로 저장한다.
  3. 모델 로딩 및 예측
    • 저장된 모델을 로딩하여 최근 10일 데이터를 기반으로 미래 방문자 수 및 전환율을 예측한다.

growth_marketing_data.csv
0.02MB

 

- (1) LSTM 모델학습 및 저장

# 그로스마케팅 활용 코드
# LSTM 모델 학습 및 저장

import pandas as pd
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import pickle

# CSV 데이터 로드
df = pd.read_csv("growth_marketing_data.csv", parse_dates=["date"])
df.set_index("date", inplace=True)

# 데이터 정규화
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(df)

# LSTM 입력 형태 변환
def create_sequences(data, seq_length):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

seq_length = 10  # 10일 데이터를 사용하여 다음 날 예측
X, y = create_sequences(scaled_data, seq_length)

# 데이터 분리
train_size = int(len(X) * 0.8)
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

# LSTM 모델 생성
model = Sequential([
    LSTM(50, activation='relu', return_sequences=True, input_shape=(seq_length, X.shape[2])),
    LSTM(50, activation='relu'),
    Dense(X.shape[2])  # 예측할 특성 수만큼 출력층 설정
])

model.compile(optimizer=Adam(learning_rate=0.001), loss='mse')

# 모델 학습
history = model.fit(X_train, y_train, epochs=40, batch_size=16, validation_data=(X_test, y_test))

# 학습 과정 시각화 (손실 값 그래프)
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label="Train Loss")
plt.plot(history.history['val_loss'], label="Validation Loss")
plt.xlabel("Epochs")
plt.ylabel("Loss (MSE)")
plt.title("Training and Validation Loss")
plt.legend()
plt.show()

# 모델 평가
y_test_pred = model.predict(X_test)
y_test_pred_rescaled = scaler.inverse_transform(y_test_pred)
y_test_rescaled = scaler.inverse_transform(y_test)

mse = mean_squared_error(y_test_rescaled, y_test_pred_rescaled)
mae = mean_absolute_error(y_test_rescaled, y_test_pred_rescaled)
r2 = r2_score(y_test_rescaled, y_test_pred_rescaled)  # 결정계수 계산

# 성능 평가 결과 출력
print(f"🔹 MSE (Mean Squared Error): {mse:.4f}")
print(f"🔹 MAE (Mean Absolute Error): {mae:.4f}")
print(f"🔹 R² (결정계수): {r2:.4f}")  # 결정계수 출력

# 모델 저장
model.save("lstm_growth_marketing_model.h5")

# 스케일러 저장
with open("scaler.pkl", "wb") as f:
    pickle.dump(scaler, f)

print("✅ 모델 및 스케일러 저장 완료!")

※ 답없는 MSE와 MAE, 결정계수 때문에 chatGPT에게서 고쳐본 코드를 아래에 첨부했다. 추후 한번 뜯어볼 것.

 

 

- (2) 모델 로딩 및 예측

# 모델 로딩 및 예측

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import load_model
import pickle

# CSV 데이터 로드
df = pd.read_csv("growth_marketing_data.csv", parse_dates=["date"])
df.set_index("date", inplace=True)

# 스케일러 로딩
with open("scaler.pkl", "rb") as f:
    scaler = pickle.load(f)

# 모델 로딩 (손실 함수 지정)
model = load_model("lstm_growth_marketing_model.h5", custom_objects={"mse": tf.keras.losses.MeanSquaredError()})

# 최근 10일 데이터 준비
seq_length = 10
last_seq = df[-seq_length:].values
last_seq_scaled = scaler.transform(last_seq)

# 입력 데이터 형태 변환 (LSTM 입력 차원 맞추기)
X_new = np.array([last_seq_scaled])

# 예측 수행
y_pred_scaled = model.predict(X_new)
y_pred = scaler.inverse_transform(y_pred_scaled)

# 예측 결과 출력
print(f"📊 예측된 방문자 수: {y_pred[0, 0]:.2f}")
print(f"📊 예측된 클릭 수: {y_pred[0, 1]:.2f}")
print(f"📊 예측된 전환 수: {y_pred[0, 2]:.2f}")
print(f"📊 예측된 전환율: {y_pred[0, 3]:.4f}")

 

 

- (3) 특정시기 예측

# 특정시기 예측

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import load_model
import pickle

# CSV 데이터 로드
df = pd.read_csv("growth_marketing_data.csv", parse_dates=["date"])
df.set_index("date", inplace=True)

# 스케일러 로딩
with open("scaler.pkl", "rb") as f:
    scaler = pickle.load(f)

# 모델 로딩
model = load_model("lstm_growth_marketing_model.h5", custom_objects={"mse": tf.keras.losses.MeanSquaredError()})

# 특정 날짜 입력 (예: "2023-06-15")
target_date = "2023-06-15"
target_date = pd.to_datetime(target_date)

# 특정 날짜 이전 10일 데이터를 가져오기
if target_date not in df.index:
    print("🚨 입력한 날짜가 데이터에 없습니다. 다시 입력하세요.")
else:
    date_index = df.index.get_loc(target_date)

    if date_index < 10:
        print("🚨 해당 날짜 이전의 10일 데이터가 부족합니다. 더 이후 날짜를 선택하세요.")
    else:
        # 직전 10일 데이터 가져오기
        last_seq = df.iloc[date_index-10:date_index].values
        last_seq_scaled = scaler.transform(last_seq)

        # 입력 데이터 형태 변환 (LSTM 입력 차원 맞추기)
        X_new = np.array([last_seq_scaled])

        # 예측 수행
        y_pred_scaled = model.predict(X_new)
        y_pred = scaler.inverse_transform(y_pred_scaled)

        # 예측 결과 출력
        print(f"📊 예측 기준 날짜: {target_date.strftime('%Y-%m-%d')}")
        print(f"📊 예측된 방문자 수: {y_pred[0, 0]:.2f}")
        print(f"📊 예측된 클릭 수: {y_pred[0, 1]:.2f}")
        print(f"📊 예측된 전환 수: {y_pred[0, 2]:.2f}")
        print(f"📊 예측된 전환율: {y_pred[0, 3]:.4f}")

 

 

- 위 코드에 대한 웹서비스 구현 코드

- app.py

from flask import Flask, request, jsonify, render_template
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import load_model
import pickle

app = Flask(__name__)

# 모델 및 데이터 로드
MODEL_PATH = "lstm_growth_marketing_model.h5"
SCALER_PATH = "scaler.pkl"
DATA_PATH = "growth_marketing_data.csv"

model = load_model(MODEL_PATH, custom_objects={"mse": tf.keras.losses.MeanSquaredError()})
with open(SCALER_PATH, "rb") as f:
    scaler = pickle.load(f)

df = pd.read_csv(DATA_PATH, parse_dates=["date"])
df.set_index("date", inplace=True)

@app.route("/")
def home():
    return render_template("index.html")

@app.route("/predict", methods=["POST"])
def predict():
    try:
        data = request.get_json()
        target_date = data.get("date")

        if not target_date:
            return jsonify({"error": "날짜를 입력하세요."}), 400

        target_date = pd.to_datetime(target_date)

        if target_date not in df.index:
            return jsonify({"error": "입력한 날짜가 데이터에 없습니다."}), 400

        date_index = df.index.get_loc(target_date)

        if date_index < 10:
            return jsonify({"error": "이 날짜 이전 10일 데이터가 부족합니다."}), 400

        last_seq = df.iloc[date_index-10:date_index].values
        last_seq_scaled = scaler.transform(last_seq)

        X_new = np.array([last_seq_scaled])
        y_pred_scaled = model.predict(X_new)
        y_pred = scaler.inverse_transform(y_pred_scaled)

        result = {
            "date": target_date.strftime("%Y-%m-%d"),
            "predicted_visitors": round(float(y_pred[0, 0]), 2),
            "predicted_clicks": round(float(y_pred[0, 1]), 2),
            "predicted_conversions": round(float(y_pred[0, 2]), 2),
            "predicted_conversion_rate": round(float(y_pred[0, 3]), 4)
        }
        return jsonify(result)

    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5001, debug=True)

 

- index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LSTM 예측 서비스</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f8f9fa;
        }
        .container {
            max-width: 600px;
            margin-top: 50px;
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
        }
        .btn-primary {
            width: 100%;
        }
        #result {
            margin-top: 20px;
            padding: 20px;
            background-color: #e9ecef;
            border-radius: 8px;
            display: none;
        }
        .error {
            color: red;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2 class="text-center">📊 LSTM 기반 방문자 예측</h2>
        <p class="text-center">날짜를 입력하면 예측된 방문자 수, 클릭 수, 전환율을 확인할 수 있습니다.</p>

        <form id="predictForm">
            <label for="date" class="form-label">예측할 날짜 선택:</label>
            <input type="date" class="form-control" id="date" required>
            <button type="submit" class="btn btn-primary mt-3">예측하기</button>
        </form>

        <div id="result">
            <h4 class="text-center">📅 예측 결과</h4>
            <p><strong>날짜:</strong> <span id="pred_date"></span></p>
            <p><strong>예측된 방문자 수:</strong> <span id="pred_visitors"></span></p>
            <p><strong>예측된 클릭 수:</strong> <span id="pred_clicks"></span></p>
            <p><strong>예측된 전환 수:</strong> <span id="pred_conversions"></span></p>
            <p><strong>예측된 전환율:</strong> <span id="pred_conversion_rate"></span></p>
        </div>

        <p id="errorMessage" class="error text-center mt-3"></p>
    </div>

    <script>
        document.getElementById("predictForm").addEventListener("submit", function(event) {
            event.preventDefault();

            let dateInput = document.getElementById("date").value;
            if (!dateInput) {
                document.getElementById("errorMessage").textContent = "날짜를 입력하세요.";
                return;
            }

            document.getElementById("errorMessage").textContent = "";

            fetch("/predict", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({ date: dateInput })
            })
            .then(response => response.json())
            .then(data => {
                if (data.error) {
                    document.getElementById("errorMessage").textContent = data.error;
                    document.getElementById("result").style.display = "none";
                } else {
                    document.getElementById("pred_date").textContent = data.date;
                    document.getElementById("pred_visitors").textContent = data.predicted_visitors;
                    document.getElementById("pred_clicks").textContent = data.predicted_clicks;
                    document.getElementById("pred_conversions").textContent = data.predicted_conversions;
                    document.getElementById("pred_conversion_rate").textContent = data.predicted_conversion_rate;
                    document.getElementById("result").style.display = "block";
                }
            })
            .catch(error => {
                document.getElementById("errorMessage").textContent = "서버 오류가 발생했습니다.";
                document.getElementById("result").style.display = "none";
            });
        });
    </script>
</body>
</html>

 

 

# 웹서비스로 LSTM모델 예측서비스 구현

30일차 실습.Zip
0.31MB

※ templates 폴더에 index.html파일, 나머지는 templates의 상위폴더 위치

 

 

 

※ 위 코드는 리눅스에서 python3.11 버전으로 설치하고 tensorflow keras 최신버전으로 업데이트해야 실행이 된다. 기존 python 버전이 3.8이라서 계속..오류가 났다. 파이썬을 제거 후 다시 설치했는데도 충돌이 일어나서 파이썬 3.11을 설치하고 그 다음 명령어부터 매번 python 3.11을 명시해주었더니 실행되었다...

app.py 실행하는 것도 python3.11 app.py해서 실행하였더니 되었다......

 

- 실행한 명령어 코드

# 기존 Python 3 제거
sudo apt-get remove python3

# 패키지 리스트 업데이트 (최신 버전 확인)
sudo apt update

# 필수 패키지 설치 (소프트웨어 저장소 관리 도구)
sudo apt install software-properties-common -y

# Deadsnakes PPA 추가 (다양한 Python 버전을 제공하는 저장소)
sudo add-apt-repository ppa:deadsnakes/ppa

# 패키지 리스트 다시 업데이트 (추가된 PPA 반영)
sudo apt update

# Python 3.11 및 관련 패키지 설치
sudo apt install python3.11 python3.11-dev python3.11-venv -y

# get-pip.py 스크립트를 다운로드 (pip 설치 스크립트)
sudo curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py

# Python 3.11을 사용하여 pip 설치
python3.11 /tmp/get-pip.py

# pip 최신 버전으로 업그레이드
python3.11 -m pip install --upgrade pip

# Flask (웹 프레임워크) 설치
python3.11 -m pip install flask

# NumPy (수학, 행렬 연산 라이브러리) 설치
python3.11 -m pip install numpy

# Pandas (데이터 분석 라이브러리) 설치
python3.11 -m pip install pandas

# Scikit-learn (머신러닝 라이브러리) 설치
python3.11 -m pip install scikit-learn

# TensorFlow (딥러닝 프레임워크) 설치
python3.11 -m pip install tensorflow

 

 


 

 

※ 다시 생각해 볼 것들

# 1. 그래프 확인하고 epoch 수 조절 (과적합의 예)

- "(1) LSTM 모델학습 및 저장" 코드

 

그래프를 보면 Train Loss(파란색 선)는 점점 감소하는데, Validation Loss(주황색 선)는 50epoch 이후부터 점차 증가하는 패턴을 보이고 있음. 이런 현상은 과적합(overfitting) 이 발생했다는 신호이다. 즉, 훈련 데이터에는 잘 맞지만, 검증 데이터에 대한 일반화 성능이 떨어지고 있다는 뜻이다.

 

 * 최적의 Epoch 수 추천 :

너무 오래 학습하면 과적합이 심해지기 때문에, Validation Loss가 최소일 때 학습을 멈추는 게 좋다.
그래프를 보면 대략 30~50 epoch 사이에서 Validation Loss가 가장 낮은 상태를 유지하는 것 같아 보인다.
따라서 최적의 epoch 수는 약 40~50으로 조정하는 게 좋아 보인다.


 

# 2. 오늘 학습 코드 중 "- (1) LSTM 모델학습 및 저장" 코드의 MSE와 MAE, 결정계수 개선 시도해 본 코드.

- 챗 지피티에게 부탁해서 난감한 MSE, MAE, 결정계수 개선을 시도해 보았으나 결정계수는 어떻게 해도 좋아지지 않았다. 대신 MSE와 MAE 오차값은 많이 줄어들었다. 

추후 다시 확인해보고 다시 시도해보고자 첨부.

 

* 아래 수정된 코드에서 고쳐진 것

  • LSTM 층을 2개 → 3개로 늘림 (각 층 뉴런 수도 50 → 100으로 증가)
  • 학습률을 0.0005로 낮춤 (더 안정적인 학습을 위해)
  • 예측값과 실제값을 원래 스케일로 변환하는 과정 명확히 수정
  • Epoch 수를 100으로 설정 (40은 너무 적고, 200은 과적합 우려)

※ LSTM 층을 2개에서 3개로 늘리고, 각 층의 뉴런 수도 50에서 100으로 증가시키면 모델의 복잡도가 증가하기 때문에, 기존보다 epoch 수를 늘리는 것이 좋을 수 있음. 학습 과정에서 모델의 성능 향상이 없거나 과적합이 발생한다면 적절히 조절하자.

# 수정된 코드 (- (1) LSTM 모델학습 및 저장)

# 다음을 개선하였음:

# LSTM 층을 2개 → 3개로 늘림 (각 층 뉴런 수도 50 → 100으로 증가)
# 학습률을 0.0005로 낮춤 (더 안정적인 학습을 위해)
# 예측값과 실제값을 원래 스케일로 변환하는 과정 명확히 수정
# Epoch 수를 100으로 설정 (40은 너무 적고, 200은 과적합 우려)


from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 데이터 스케일링
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape)
X_test_scaled = scaler.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape)
y_train_scaled = scaler.fit_transform(y_train)
y_test_scaled = scaler.transform(y_test)

# LSTM 모델 생성
model = Sequential([
    LSTM(100, activation='relu', return_sequences=True, input_shape=(X_train.shape[1], X_train.shape[2])),
    Dropout(0.2),
    LSTM(100, activation='relu', return_sequences=True),
    Dropout(0.2),
    LSTM(100, activation='relu'),
    Dropout(0.2),
    Dense(y_train.shape[1])
])

# 모델 컴파일
model.compile(optimizer=Adam(learning_rate=0.0005), loss='mse')

# 모델 학습
history = model.fit(X_train_scaled, y_train_scaled, epochs=100, batch_size=32, validation_data=(X_test_scaled, y_test_scaled), verbose=1)

# 예측
y_test_pred_scaled = model.predict(X_test_scaled)

# 원래 스케일로 변환
y_test_pred = scaler.inverse_transform(y_test_pred_scaled)
y_test = scaler.inverse_transform(y_test_scaled)

# 성능 평가
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
mse = mean_squared_error(y_test, y_test_pred)
mae = mean_absolute_error(y_test, y_test_pred)
r2 = r2_score(y_test, y_test_pred)

print(f"MSE: {mse}")
print(f"MAE: {mae}")
print(f"R²: {r2}")

# 학습 과정 시각화 (손실 값 그래프)
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label="Train Loss")
plt.plot(history.history['val_loss'], label="Validation Loss")
plt.xlabel("Epochs")
plt.ylabel("Loss (MSE)")
plt.title("Training and Validation Loss")
plt.legend()
plt.show()

 


 

 

# 3. 데이터가 적은 경우에는 회귀식이 효과적이다. 모델이 간단해서 과적합을 방지할 수 있음. 단, 비선형 특성에는 효과 못 냄. 비선형 특성을 처리하려면 결정 트리, 랜덤 포레스트, 서포트 벡터 머신(SVM) 등 다른 알고리즘을 사용한다. LSTM의 최대 단점은 막대한 양의 데이터가 필요하다는 것이다. 작은 데이터셋에서는 LSTM이 과적합되거나 성능이 좋지 않을 수 있다.