파이썬/ai

[ai, python] 인천광역시 집 값 예측 - 모델 구축 (MLP)

hojung 2022. 5. 21.
728x90
반응형

1. 모델 구축

앞 선 포스팅까지 데이터를 수집하여 모델이 학습시키기 좋은 형태로 데이터의 형태를 바꿔주는 작업을 진행했다. 지금 부터 할 것은 모델을 구축하는 일이다. 나는 모델을 MLP(multi Layer Perceptron) 다층 신경망 모델을 선택했다. 

MLP란 입력층과 중간에 숨겨진 은닉층 그리고 결과를 출력하는 출력층으로 구분되고 나는 은닉층의 개수를 2개로 진행했다. 

 

class Regressor(nn.Module):
    def __init__(self):
        super().__init__() # 모델 연산 정의
        self.fc1 = nn.Linear(11, 50, bias=True) # 입력층(11) -> 은닉층1(50)으로 가는 연산
        self.fc2 = nn.Linear(50, 30, bias=True) # 은닉층1(50) -> 은닉층2(30)으로 가는 연산
        self.fc3 = nn.Linear(30, 1, bias=True) # 은닉층2(30) -> 출력층(1)으로 가는 연산
        self.dropout = nn.Dropout(0.2) # 연산이 될 때마다 20%의 비율로 랜덤하게 노드를 없앤다.

    def forward(self, x): # 모델 연산의 순서를 정의
        x = F.relu(self.fc1(x)) # Linear 계산 후 활성화 함수 ReLU를 적용한다.  
        x = self.dropout(F.relu(self.fc2(x))) # 은닉층2에서 드랍아웃을 적용한다.(즉, 30개의 20%인 6개의 노드가 계산에서 제외된다.)
        x = F.relu(self.fc3(x)) # Linear 계산 후 활성화 함수 ReLU를 적용한다.  
      
        return x

  #drop out 은 과적합(overfitting)을 방지하기 위해 노드의 이부를 배제하고 계산하는 방식이기 때문에 출력층에 사용하면 안된다.

다음이 MLP의 모델이다. 처음에 들어오면 fc1에서 입력을 받는다. 변수의 개수가 11개이므로 11개에서 50개의 결과를 내보낸다. 그 후 Fc2에서는 50개를 다시 30개로 줄이는 과정을 Linear함수를 이용해 진행한다. 

그 후 과적합을 방지하기 위해 Dropout함수를 이용해서 한 epoch를 돌 때마다 20%의 비율로 랜덤하게 노드를 없애는 과정을 진행했다. 나는 Linear함수를 통과한 후 활성화 함수로 Relu를 이용하여 진행했다. 왜냐하면 Relu 함수는 음수의 영역에서는 0이다가 양수의 영역에서는 linear하게 증가하는 nonLinear함수인데 non Linear함수를 곱해주지 않으면 결국 Linear해지는 문제도 존재하고 집 값이라는 데이터의 특성 상 음수가 존재하지 않기 때문에 음수의 함수값이 존재하지 않는 Relu함수가 적합하다는 생각이 들었다. 


2. 모델, 손실함수, 최적화 방법 선언

한 번 학습을 시키면 Loss(정답과이 차이)를 구해 그 Loss를 줄이는 방향으로 학습을 시켜야한다. 이 때 필요한 것이 일단 Loss를 구해줄 Loss함수 그 후 Loss를 줄이는 과정인 최적화 방법을 선택하는 것이다. 

나는 MSELoss로 Loss를 구하기로 했다. MSE란 Mean Squared Error의 약자로 예측 값 - 정답 값을 제곱한 Loss를 의미한다. 

 

최적화 방법으로는 요즘 제일 핫 한 Adam을 선택했고 learning rate로는 0.00.1.을 넣어주었다. learning rate는 아주 중요한 하이퍼 파라미터의 일종으로 Loss함수를 얼만 큼의 간격으로 이동하며 최소 값을 찾을지 정해주는 비율에 해당한다. 

model = Regressor()
criterion = nn.MSELoss() # hyper parameter 활성화 함수 종류

#lr == learning rate
#weight_decay는 L2 정규화에서의 penalty 정도

optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-7)#하이퍼 파라미터 lr weight decay optimizer 종류

3. 학습

나는 각 학습마다 발생하는 Loss를 알기위해 우선 Loss를 저장해줄 빈 배열을 선언했다. 

loss_ = [] # 그래프를 그리기 위한 loss 저장용 리스트 
n = len(trainLoader)

for epoch in range(400): # 400번 학습을 진행한다.

    running_loss = 0.0
    
    for i, data in enumerate(trainLoader, 0): # 무작위로 섞인 32개 데이터가 있는 배치가 하나 씩 들어온다.

        inputs, values = data # data에는 X, Y가 들어있다.

        optimizer.zero_grad() # 최적화 초기화
        
        outputs = model(inputs) # 모델에 입력값 대입 후 예측값 산출
        loss = criterion(outputs, values) # 손실 함수 계산
        loss.backward() # 손실 함수 기준으로 역전파 설정 
        optimizer.step() # 역전파를 진행하고 가중치 업데이트
        running_loss += loss.item() # epoch 마다 평균 loss를 계산하기 위해 배치 loss를 더한다.
 
    print(running_loss)
    loss_.append(running_loss/n) # MSE(Mean Squared Error) 계산

        
print('Finished Training')

그 후 epoch를 돌면서 학습을 시키는데 1batch사이즈를 아까 32로 설정해주었고 enumerate for문을 이용해서 앞선 포스팅에서 DataLoader함수를 이용해 생성한 배치 사이즈의 데이터들을 불러오고 model에 넣어주었다. 그 후 앞서 MSELoss로 설정해준 criterion함수를 이용해 loss를 구하고 optimizer adam에 넣어주어 다시 역전파를 시켜 가중치를 조절해 loss함수가 최소가 되도록 조절해준다. 

그 후 loss를 출력해보면 다음과 같다. 

경향성이 보이는가 많이 줄어들지는 않지만 결국 Loss가 줄어드는 방향으로 나아가고 있다. 이러한 경우 학습이 잘 되고 있는 것이다. 

plt.plot(loss_)
plt.title("Training Loss")
plt.xlabel("epoch")
plt.show()

matplotlib을 이요해 아까 누적하여 저장한 loss배열을 그래프를 통해 나타내주었다. 

Loss가 줄어드는 것으로 보아 학습이 잘 진행되고 있다. 


5. 모델 평가

#predictions = torch.tensor([]) # 예측 값을 저장하는 tensor
#actual = torch.tensor([]) # 실제 정답을 저장하는 tensor
def evaluation(dataLoader):
  predictions = torch.tensor([]) # 예측 값을 저장하는 tensor
  actual = torch.tensor([]) # 실제 정답을 저장하는 tensor

  with torch.no_grad():
    model.eval() #평가를 할 때는 .eval()을 반드시 사용
    for data in dataLoader:
      inputs, values = data
      outputs = model(inputs)
      print(outputs)
      predictions = torch.cat((predictions, outputs), 0) #cat을 통해 예측값을 누적
      actual = torch.cat((actual, values), 0)
  predictions = predictions.numpy()
  actual = actual.numpy()
  #print(predictions)
  #print(actual)
  rmse = np.sqrt(mean_squared_error(predictions, actual)) # sklearn을 이용해서 RMSE
  return rmse
evaluation(trainLoader) 
#평가 시 , eval()
#평가 시에는 온전한 모델로 평가를 해야하는데, .eval()이 아닌, .train()의 경우 드랍아웃이 활성화 되어있다. 
#따라서 드랍아웃이나 배치 정규화 등과 같이 학습 시에만 사용하는 기술들을 평가 시에는 비활성화 해야한다.

평가 class를 왜 따로 만들어야하냐면 dropout과 같이 학습에만 필요한 과정들을 비활성화 해주기 위함이다. 이 것이 평가의 결과에는 영향을 미치지 않기 때문이다. .eval함수를 이용해 진행해주었고 모델로 예측한 prediction값들을 cat함수를 통해 누적 시켜주었다. 또한 실제 정답인 actual값도 cat함수를 통해 누적해주었다. 그 후 rmse를 구해 결과를 보면 다음과 같다. 

train_rmse = evaluation(trainLoader) #학습 데이터의 RMSE
vali_rmse = evaluation(validationLoader) #validation의 RMSE
test_rmse = evaluation(testloader)
print("Train RMSE: ", train_rmse)
print("Vali RMSE: ", vali_rmse)
print("Test RMSE: ", test_rmse)

train data에서 0.012오차

validation data에서 0.014오차

test data에서 0.015오차 가 발생했다. 생각보다 결과가 잘나와서 만족스러웠다. 

 

728x90
반응형

댓글