RNN, LSTM, GRU, Transformer Model에 대해 살펴보고,
LSTM과 GRU를 통해 주가 데이터 예측을 진행해 보았다.
이제 BEMS(Building Energy Management System)의 실제 Data set에 적용해보자.
시드 고정 및 GPU장비 설정
import os
import random
# 시드 값 고정
seed = 42
# Python 내장 해시 함수의 시드를 고정하여 재현성을 확보 (Python 3.3 이상에서만 적용)
os.environ['PYTHONHASHSEED'] = str(seed)
random.seed(seed) # Python의 random 모듈
np.random.seed(seed )# NumPy의 난수 생성기
torch.manual_seed(seed) # PyTorch의 난수 생성기
torch.cuda.manual_seed(seed) #GPU의 난수 생성기
torch.backends.cudnn.deterministic = True # CuDNN을 사용할 때, 비결정론적 알고리즘을 사용하지 않도록 설정
torch.backends.cudnn.benchmark = False # CuDNN의 벤치마크 기능을 비활성화하여 매번 동일한 연산 경로가 사용되도록 설정
# GPU가 사용 가능하면 CUDA 장치로 설정, 그렇지 않으면 CPU로 설정
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 설정된 장치(device) 출력
device
데이터 준비
BEMS관련 논문에서 제공해주는 dataset을 사용해보겠다.
3년간의 사무실 건물에서의 에너지 소비 정보를 제공해주고 있으며,
그 중 zone_022의 co2 column의 값들을 사용할 예정이다. (약 1년반간의 데이터)
EDA 및 이상치 처리
데이터를 확인해본 결과 date가 끊겨있는 부분을 발견할 수 있었다.
끊겨 있는 부분의 수: 1 date 2020-04-27 01:50:00 116 days 09:51:00 Name: date, dtype: timedelta64[ns]
결측치 수 확인 zone_022_co2 0 dtype: int64
date 자체가 건너뛰어져있기에 결측치로는 잡히지 않았다.
이대로 분석을 진행해보기도 하고, 2020년 5월1일 이후의 (끊기지 않은) 데이터로도 진행하였다.
이상치의 값들이 꽤 많은 것을 확인 할 수 있었으며, 400중반에 머물러져있는 데이터와 달리
72, 853 정도의 값들을 확인해 볼 수 있었다.
Z-score > 3 을 이상치로 판단 : 이상치 수: 21155 이상치 비율: 5.81%
제거 / 이전 값으로 대체 시도 => 이상치 수: 9895 이상치 비율: 2.72%
정규화 - MinMaxScaler()
정규화와 표준화
정규화 : 0~1 사이의 값으로 데이터 범주를 정해주는 작업.
표준화 : 평균을 0으로, 표준편차를 1로 변환해주는 작업.
- 표준화가 아닌 정규화를 해주는 이유
시그모이드 활성화 함수와 함께 사용하기 용이
시계열 데이터 예측에서는 데이터의 상대적 크기를 유지할 수 있어 정규화를 보다 선호
from sklearn.preprocessing import MinMaxScaler
# 데이터 로드 및 전처리 (데이터셋 나누기 전에 수행하는 것이 좋음!)
def load_data_from_csv(df, column_name):
data = df
data = data[column_name].values.reshape(-1, 1)
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(data)
return scaled_data, scaler
data, scaler = load_data_from_csv(df, column_name)
MinMaxScaler( ) 인자
- feature_range: 변환할 범위를 설정. Default (0, 1)
- copy: 원본 데이터를 변경하지 않고 사본을 반환할지 설정. Default : True
- clip: 정규화된 데이터가 설정된 범위를 벗어날 경우, 그 값을 범위 경계값으로 대체할지 설정. Default : False
그 외 함수 : MaxAbsScaler, StandardScaler, RobustScaler
- 정규화 이전에 이상치 처리를 해주어야 하는 이유
이상치가 존재하면 최소,최댓값을 잘못 설정할 확률이 높음. ⇒ 이상치에 민감 - scaling을 미리 해주는 이유
훈련/테스트 데이터 셋에 대해 각각 fit_transform( )을 진행할 경우,
fit ( )의 정도가 달라질 수 있음 ! ⇒ 테스트셋에는 transform( )만 적용
이러한 과정이 복잡하기에, 가능하다면 데이터 셋 분리 전 미리 스케일링 적용 - 이러한 전처리과정이 ML에서 중요한 이유
모델이 잘못된 데이터로 학습 하지 않도록 해주며, 학습 속도 및 예측 성능의 향상이 가능
⚠️ 트리 기반 모델에서는 피쳐 스케일링이 필요하지 않음.
데이터의 크기보다는 대소 관계에 영향을 받기 때문
Pytorch - DataSet(), DataLoader()
# 데이터셋 클래스 정의
class Co2Dataset(Dataset):
def __init__(self, data, window_size):
self.data = data
self.window_size = window_size
def __len__(self):
return len(self.data) - self.window_size
def __getitem__(self, idx):
x = self.data[idx:idx+self.window_size]
y = self.data[idx+self.window_size]
return torch.tensor(x, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)
Dataset : PyTorch에서 데이터셋을 표현하기 위해 사용되는 추상 클래스, 여러 데이터셋 제공
- init( ) : 데이터셋의 전처리 작업이나 데이터를 읽어오는 등의 초기화를 수행
- len( ) : 데이터셋의 크기(샘플 수)를 반환
- getitem( ) : 주어진 인덱스에 해당하는 데이터를 반환
❔ Tensor 란?
Pytorch 에서 모델의 입출력에 사용하는 NumPy의 ndarray와 유사한 자료구조
❔ window_size 란?
시계열 데이터 처리 시, 슬라이딩 윈도우(sliding window) 방식으로 분할하여 학습하기 위한 변수
# 데이터셋 생성
window_size = 10
dataset = Co2Dataset(data, window_size)
# 훈련/검증 데이터 분리 (75% 훈련 데이터, 25% 테스트 데이터)
train_size = int(len(dataset) * 0.75)
# 학습과 테스트 데이터셋을 슬라이싱으로 나누기
train_dataset = Subset(dataset, list(range(train_size)))
test_dataset = Subset(dataset, list(range(train_size, len(dataset))))
# 데이터 로더 생성
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
- DataLoader( )데이터셋의 순서를 섞거나(shuffle), 병렬 처리를 통해 데이터를 로드하는 등의 기능도 제공
- batch_size : 2^n 권장
- 데이터셋을 배치(batch) 단위로 나누어 모델에 제공할 수 있도록 도와주는 클래스
LSTM 모델 설계
torch.nn.Module ⇒ RNNBase ⇒ torch.nn.LSTM
Class LSTM - Init()
class LSTMModel(nn.Module):
def __init__(self, input_size=1, hidden_size=50, num_layers=2, output_size=1, dropout=0.2): # LSTM 전체 모델 정의
super(LSTMModel, self).__init__() # Moudle(부모 클래스의 생성자 호출
self.hidden_size = hidden_size
self.num_layers = num_layers
# nn.LSTM을 통해 LSTM 레이어 정의
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)
# 출력 크기 조정 (FC레이어 = 선형 변환 레이어 정의)
self.fc = nn.Linear(hidden_size, output_size)
# 가중치와 편향을 초기화
self._initialize_weights()
def _initialize_weights(self):
for name, param in self.lstm.named_parameters():
if 'weight' in name:
nn.init.xavier_uniform_(param.data)
elif 'bias' in name:
nn.init.zeros_(param.data)
nn.init.xavier_uniform_(self.fc.weight)
nn.init.zeros_(self.fc.bias)
def forward(self, x): # x는 입력 데이터 (하나의 batch)
# 초기 은닉 상태와 셀 상태 정의
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
# LSTM 통과
out, _ = self.lstm(x, (h0, c0))
# 마지막 시퀀스의 출력만 선택하여 FC 레이어에 통과
out = self.fc(out[:, -1, :])
out = torch.sigmoid(out) # 시그모이드 활성화 함수 추가
return out
- input_size: 입력 x에서 기대되는 특징의 수
- hidden_size: 숨겨진 상태 h에서의 특징 수 (50 단위)
- num_layers: 순환 레이어의 수. 기본값: 1 (1~2)
- dropout: 마지막 레이어를 제외한 각 LSTM 레이어의 출력에 Dropout 레이어를 도입하며, 드롭아웃 확률 값 설정. 기본값: 0 (0.2)
- 그 외
- bias: False로 설정하면, 해당 레이어는 b_ih와 b_hh라는 편향 가중치를 사용하지 않습니다. 기본값: True (4차원의 input_hidden, hidden_hidden간의 편향성)
- batch_first: True로 설정하면, 입력과 출력 텐서가 (batch, seq, feature) 형식으로 제공되며,기본 형식인 (seq, batch,feature) 대신 사용됩니다. 이는 숨겨진 상태나 셀 상태에는 적용되지 않습니다. 기본값: False
- bidirectional: True로 설정하면, 양방향 LSTM이 됩니다. 기본값: False
- proj_size: 0보다 크면, 해당 크기의 프로젝션이 있는 LSTM을 사용하게 됩니다. (차원축소) 기본값: 0
가중치 초기화 함수와 활성화 함수 간 궁합이 잘 맞는 쌍이 존재
He - ReLu, xavier - sigmoid...
모델 학습
# 모델 학습 함수
def train_model(train_loader, num_epochs=5, learning_rate=0.001, device=device):
model = LSTMModel().to(device)
criterion = nn.MSELoss() #손실 함수
optimizer = optim.Adam(model.parameters(), lr=learning_rate) #최적 가중치를 찾아주는 알고리즘
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for seqs, targets in train_loader:
seqs, targets = seqs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(seqs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch {epoch+1}/{num_epochs}, Loss: {avg_loss:.4f}')
return model
print(device)
➕ 학습에서 사용되는 파라미터
ephocs : 10~100 (데이터 셋의 크기가 클수록 적게 설정)
learning_rate : 0.001
이상치 이전 값으로 대체, 2020년 5월 이후의 데이터셋 사용
ephoc 찾기 (early stopping?)
예측 및 성능 평가
# 예측 함수
def predict(model, test_loader, scaler, device=device):
model.eval()
predictions = []
actuals = []
with torch.no_grad():
for seqs, targets in test_loader:
seqs, targets = seqs.to(device), targets.to(device)
outputs = model(seqs)
predictions.extend(outputs.cpu().numpy())
actuals.extend(targets.cpu().numpy())
predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1))
actuals = scaler.inverse_transform(np.array(actuals).reshape(-1, 1))
return predictions, actuals
위에서 scaler를 return해주었던 이유. inverse_transform을 사용할 때 재사용하기 위함!
# 데이터를 720개씩 묶어서 평균을 계산하는 함수
def average_data(data, interval):
# 데이터 길이를 interval로 나누어 묶음의 수를 결정
data = np.array(data)
averaged_data = [np.mean(data[i:i+interval]) for i in range(0, len(data), interval)]
return averaged_data
# 720개마다 평균을 계산
interval = 720
averaged_actuals = average_data(actuals, interval)
averaged_predictions = average_data(predictions, interval)
# 시각화
plt.figure(figsize=(12, 6))
plt.plot(averaged_actuals, label='Averaged Actual Data')
plt.plot(averaged_predictions, label='Averaged Predicted Data')
# 데이터셋 분할점을 나타내는 빨간 점선 추가 (80:20 비율로 가정)
split_point = int(0.8 * len(averaged_actuals)) # 평균을 낸 데이터에서의 분할점
plt.axvline(x=split_point, color='red', linestyle='--', label='Train/Test Split')
# 범례 추가 및 시각화
plt.legend()
plt.show()
75%,25%로 비율을 바꾸었는데.. 빨간 점선표시에는 반영이 안되어있다.. ㅠ
MSE : 1102
RMSE : 33
MAE : 17
- 이상치에 따른 예측 결과 비교
MSE (Mean Squared Error) : 평균 제곱 오차. 예측값과 실제 값 간의 차이가 얼마나 되는가
RMSE : MSE에 Root를 씌워 원래의 단위로 다시 변환.
MAE : Mean Absolute Error : 평균 절대 오차, 모든 오차를 동일하게 취급하며, 이상치에 덜 민감
⚠️ 이상 탐지 시스템을 구축한다고 했을 때, 이상치들이 모두 과연 쓸모 없는 값일까…? 판단 필요
어느 정도의 값까지를 이상치로 판단할 것인가...!
- ephoc:10, hidden_size = 50, num_layers = 2 , 학습 시간 : 15분 최종 loss: 0.0016
MSE : 329 MAE :15 RMSE : 18 - ephoc : 20, hidden_size = 25, num_layers = 1 , 학습 시간 :: 33분 최종 loss: 0.0015
MSE : 139 MAE : 8 RMSE : 11 - sigmoid활성화 함수 적용, ephoc: 5, hidden_size:50, num_layers ; 2 학습 시간: 5분 최종 loss: 0.0016
MSE : 193 MAE : 10 RMSE : 13
BEMS/MY_LSTM.ipynb at main · codrae/BEMS
Contribute to codrae/BEMS development by creating an account on GitHub.
github.com
Dryad | Data -- A three-year building operational performance dataset for informing energy efficiency
This dataset was curated from an office building constructed in 2015 in Berkeley, California, which includes whole-building and end-use energy consumption, HVAC system operating conditions, indoor and outdoor environmental parameters, and occupant counts.
datadryad.org
참고 논문 : https://www.nature.com/articles/s41597-022-01257-x#auth-Na-Luo-Aff1
'AI' 카테고리의 다른 글
CNN 이란? (2) | 2024.09.30 |
---|---|
Local 원격저장소를 통해 DVC 사용하기 (0) | 2024.08.21 |
Pytorch를 통한 주가 분석 (LSTM, GRU) (0) | 2024.08.20 |