본문 바로가기

Deep Learning/Fxxkin Easy Pytorch

[Fxxkin Easy Pytorch - 00] Pytorch를 이용한 선형회귀

반응형

기온 데이터를 통한 지면온도 예측

Linear Regression(선형회귀)를 적용한 간단한 예측

 

Pytorch에서 linear Regression을 사용해 분석하는 법을 알아보자

 

분석을 하는 개개인에 따라, 분석 데이터에 따라 다르겠지만 딥러닝을 적용한 데이터 분석은  보통 이런식으로 진행한다.

1. 문제(가설)설정 : 지면온도를 예측하고 싶다. 혹은 지면온도는 기온과 관계가 있을 것 같다.

2. 데이터수집 : 지면온도와 관련될 것 같은 데이터를 수집한다. ex. 기상자료개방포털, 공공자료개방포털 등

3. 데이터분석 : 데이터 평균, 최대값, 최소값 등 기초통계를 뽑아보고 그래프로 뿌려보는 등 특징을 추출한다.

4. 데이터전처리 : 데이터 학습에 필요없는 데이터를 제거하거나 순서를 섞는 등 학습에 사용할 데이터를 최적화한다.

5. 모델적용 : pytorch로 모델을 만들고 데이터를 학습한다.

6. 테스트 결과를 보고 재학습하거나 결과를 분석한다.

 

1. 데이터수집

내 문제는 기온을 기반으로 지면온도를 알고 싶은것이니 데이터를 수집해보자.

기온과 지면온도 관련 데이터는 기상자료개방포털(data.kma.go.kr)에서 다운 받을 수 있다.

기상청에서 제공하는 기상자료개방포털. 기상관련자료는 여기 다 있다.

종관기상관측으로 들어가서 원하는 ASOS 지점, 기간, 지면온도, 기온을 선택 후 조회하면 아래와 같은 표가 나오고

csv를 클릭해 csv파일을 다운 받자.

df.columns = ["stn_no","stn_name","date","temperature","land_temperature"]
df = df.drop(columns=["stn_no", "stn_name", "date"])
2. 데이터분석 및 전처리

pandas로 다운 받은 csv 파일을 읽어보자.

1) 사용할 패키지들`

#pytorch
import torch
#pytorch 네트워크
from torch import nn
#pytorch 학습 데이터셋 생성
from torch.utils.data import TensorDataset
#pytorch 데이터로더
from torch.utils.data import DataLoader

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

#pytorch와 비교해볼 scikit-learn 패키지
from sklearn import linear_model
from sklearn.model_selection import train_test_split

#csv파일을 읽기 위해 glob 패키지를 사용하자
import glob

 

2) 데이터읽기

stn_number = "232"

dir_path = "dataset/ASOS/" + stn_number
filename = glob.glob(dir_path + "/*.csv") 

df = pd.read_csv(filename[0], encoding="cp949")

기상청에서 다운받은 csv파일에는 한글이 포함되어 있기 때문에 cp949로 읽어준다. 안그러면 에러가 난다.

# 컬럼명 영어로 변경
df.columns = ["stn_no","stn_name","date","temperature","land_temperature"]
# 필요없는 컬럼 삭제
df = df.drop(columns=["stn_no", "stn_name", "date"])

컬럼명을 영어로 바꾸고 필요없는 컬럼을 삭제한다. 사실 영어로 바꿀 필요는 없긴 한데 인코딩 등 에러를 아예 안생기게 하고 싶어서 습관적으로 영어로 바꾼다.

# 데이터 프레임의 nan 값을 찾기
print(df.isnull().sum())

# result
# temperature         3
# land_temperature    0
# dtype: int64

데이터프레임에서 nan(null)값이 있는지 확인하기 위해서 위 코드를 실행해보자. 보다시피 temperature컬럼에 3개의 nan값이 있었다. 사실 이런 작은 부분이 분석에서는 많은 부분을 차지한다. 결측값이 3개면 이런식으로 삭제를 해주는 것이 전체 데이터에 큰 영향이 없지만, 결측값이 굉장히 많아지면 고려해야할 부분이 많아진다. 데이터 분석의 현실이기도 한 부분. 그래도 이번에는 time series(시계열) 데이터가 아니기 때문에 이 정도만 처리해주면 될 듯 하다. 

df = df.dropna()
print(df.isnull().sum())

# result
# temperature         0
# land_temperature    0
# dtype: int64

dropna 함수를 통해 nan 값을 제거해주고 다시 한 번 확인해보자.

 

2) 데이터 분석

데이터 모양과 특성을 확인하기 위해 그래프로 뿌려보자

# seaborn으로 데이터 시각화
sns.scatterplot(x=df["temperature"], y=df["land_temperature"])
plt.show()

matplotlib으로 뿌려도 되는데 seaborn이 속도가 훨씬 빠르다. 비교적 굉장히 깔끔한 모양의 산점도로 표현된다. 신기한 점은 지면온도가 50도 이상 올라간다는 점?? 데이터가 많기 때문에 시각화 해서 보면 확실히 직관적으로 파악할 수 있는 장점이 있다. 물론 2차원인 경우에만.

회귀분석만 할 때는 굳이 해볼 필요는 없지만 상관계수도 한 번 뿌려보자. 보고서나 어디 제출해야 할 때는 뭐라도 하나 더 써서 보여주는게 좋다. 

 

sns.heatmap(data = df.corr(method="pearson"), annot=True, fmt = '.2f', linewidths=.5, cmap='Blues')
plt.show()

이정도면 사실 매우 높은 상관계수라고 볼 수 있다. 실제로는 0.95같은 상관계수는 잘 안나오더라. 무조건 딥러닝 돌려서 결과 뽑아 보는 것도 좋지만 대충이라도 고전적인 방법을 이용해 데이터 특성을 파악해보는 편이 좋다. 

3) 데이터 전처리

torch에 데이터를 돌려보기 전에 torch가 데이터를 이해할 수 있는 형태로 바꿔줘야 한다. 여러 방법이 있는데 TensorDataset과 DataLoader를 사용하는 편이 가장 편했다.

train, test = train_test_split(df,  test_size = 0.2)
print(train, test)

#      temperature  land_temperature
# 1217          9.5               6.3
# 855          -3.5               2.2
# 8456         -1.9              -0.7
# 4404         26.7              34.8
# 4690         19.7              23.1
# ...           ...               ...
# 3759         27.6              36.4
# 534          -1.2              -0.2
# 1115          0.1               4.2
# 8449         -2.8              -2.1
# 5686         24.7              25.3

# [7004 rows x 2 columns]       temperature  land_temperature
# 3485         14.5              16.2
# 6254         22.6              24.2
# 5377         22.8              23.3
# 1000         13.4               9.3
# 6968         13.1              18.0
# ...           ...               ...
# 1925          1.1               1.9
# 1180         -6.6              -3.6
# 7553          7.6               4.7
# 7541         -0.8              -0.7
# 2083          7.0               5.2

sklearn에서 제공하는 train_test_split을 이용해서 train test 셋을 나눠주자. 굳이 나눌 필요는 없는데 소개를 위해 나눠 봄. print 된 dataframe의 index를 보면 자동으로 shuffle까지 해주는 것을 알 수 있다. 어썸!

X_train = torch.Tensor([[x] for x in list(train.temperature)])
y_train = torch.Tensor([[x] for x in list(train.land_temperature)])

# tensor([[26.6000],
#         [-9.6000],
#         [16.9000],
#         ...,
#         [15.7000],
#         [15.2000],
#         [-4.6000]])
# tensor([[27.1000],
#         [-7.7000],
#         [16.0000],
#         ...,
#         [14.5000],
#         [15.7000],
#         [-3.4000]])

torch가 데이터를 읽을 수 있는 형태로 변환해주자. print 된 내용을 보면 tensor 객체로 변환됐다.

train_data=TensorDataset(X_train, y_train)
print(train_data)

# <torch.utils.data.dataset.TensorDataset object at 0x0000025B480A6EC8>

TensorDataset을 이용해서 train_data 변수에 학습 데이터를 넣어준다. 이러면 DataLoader에 데이터를 넣어줄 준비가 된 것이다.

batch_size = len(X_train)
train_dl = DataLoader(train_data, batch_size, shuffle=True)

print(train_dl)

# <torch.utils.data.dataloader.DataLoader object at 0x000002BD70852E88>

DataLoader로 데이터를 넣어준다. 데이터로더에는 배치 사이즈를 입력해줘야 하는데 이딴 작은 데이터는 한 방에 넣을 수 있기 때문에 배치사이즈를 학습데이터 사이즈로 설정한다. 그래픽카드 메모리가 적으면 배치사이즈를 설정해서 나눠줘야 하는데 이정도는 아마 다 들어갈 듯

model = nn.Linear(1, 1) 

loss = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

선형회귀분석을 위한 모델을 설정해준다. 1차원식으로 가정해서 할 것이기 때문에 Linear 모델의 input = 1, output = 1이다. 비용함수는 MSE로, 활성함수는 Sigmoid로 설정해준다. 모델은 보통이면 클래스를 선언해서 작성하는데 선형회귀는 레이어가 하나밖에 없는 것과 마찬 가지이니 굳이 클래스를 선언할 필요는 없다.

def lrmodel(num_epochs, model, loss_fn, optimizer, train_dl):
    for epoch in range(num_epochs):
        for xb,yb in train_dl:
            pred = model(xb)
            loss = loss_fn(pred, yb)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()            
        
        print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, loss.item()))

num_epochs=100
lrmodel(num_epochs, model, loss, optimizer, train_dl)

설정한 모델에 학습 데이터를 넣고 weight를 세팅하고 backpropagation을 하는 함수를 만든다. 이 틀은 거의 유지된다. 그리고 epoch 마다 비용함수를 출력하는 print 코드를 작성했다. for loop 를 1000번 돌리는 것으로 테스트를 진행해보자.

# Epoch [982/1000], Loss: 12.7181
# Epoch [983/1000], Loss: 12.7181
# Epoch [984/1000], Loss: 12.7181
# Epoch [985/1000], Loss: 12.7181
# Epoch [986/1000], Loss: 12.7181
# Epoch [987/1000], Loss: 12.7181
# Epoch [988/1000], Loss: 12.7180
# Epoch [989/1000], Loss: 12.7180
# Epoch [990/1000], Loss: 12.7180
# Epoch [991/1000], Loss: 12.7180
# Epoch [992/1000], Loss: 12.7180
# Epoch [993/1000], Loss: 12.7180
# Epoch [994/1000], Loss: 12.7180
# Epoch [995/1000], Loss: 12.7180
# Epoch [996/1000], Loss: 12.7180
# Epoch [997/1000], Loss: 12.7180
# Epoch [998/1000], Loss: 12.7180
# Epoch [999/1000], Loss: 12.7180
# Epoch [1000/1000], Loss: 12.7180

cost가 위와 같이 출력되었다. 뭐 계속 돌리면 아주 조금 더 줄것 같기는 한데 크리티컬 하지는 않으니 1000번 정도에서 멈춘다. 

X_test = torch.Tensor([[x] for x in list(test.temperature)])
preds = model(X_test)
print(preds)

print(float(model.weight))
print(float(model.bias))

# tensor([[27.0550],
#         [14.9569],
#         [29.4746],
#         ...,
#         [33.2140],
#         [-2.7504],
#        [24.7453]], grad_fn=<AddmmBackward>)
# 1.0998272895812988
# 0.5491118431091309

아까 만들어 놨던 테스트 데이터를 사용할 때가 왔다. 테스트 데이터를 tensor로 변환하고 model에 넣어주면 값을 뿜어낸다. 또 model의 weight와 bias를 뽑아 내고 싶다면 model.weight, model.bias를 사용하면 추출할 수 있다. 여기서 weight는 a, bias는 b(y = ax + b)

weight와 bias를 뽑았으니 그래프로도 뿌려볼 수 가 있다.

tensorboard를 이용해서 체크할 수 도 있지만 나는 내 코드로만 다 해결하고 싶기 때문에 이런식으로 뿌려본다. 사실 그래프 기울기가 좀 더 높을 줄 알았는데 생각보다 아래쪽으로 데이터가 몰려 있나보다.

자, 이제 pytorch의 선형회귀를 이용해서 기온-지면온도 모델을 만들었으니 지면온도를 예측하면 되는 것일까? 실상은 전혀 그렇지가 않다. 저것은 그냥 모델일 뿐이고 실제에 적용하기는 어렵다. 온도에 있어서 10도 차이는 어마어마 한 것인데 이 정도 예측가지고는 어림도 없다. 이것 또한 데이터 모델링의 한계이다. 지면온도를 예측하려면 훨씬 많은 변수가 필요할 것으로 보인다. 예를 들면 풍속, 일조량, 일사량 등등. 그래도 의미가 있다는 것은 pytorch로 해보는 선형회귀모델을 만들어봤다는 것. 그것에 의의가 있다. 사실 pytorch로 모델을 만들건 tensorflow로 만들건 별로 중요하지 않다. 만든 모델이 product에 적용가능하냐는 것이다.(경영지원의 목적이라면 해석). 모델을 만드는 방법론은 해석에 비해 훨씬 제한적이라고 할 수 있다. 그렇기에 모델을 프레임워크에 돌려서 뽑아보는 것은 기본이고 원인과 결과에에 대한 해석이 참으로 중요한 것이다.

ps. 사실 선형회귀는 sckit-learn으로 코드 몇 줄만 돌리면 답을 뽑아낼 수 있다.

regr = linear_model.LinearRegression()
X = df.temperature.values.reshape(-1,1)
y = df.land_temperature.values.reshape(-1,1)
regr.fit(X, y)
print(regr.coef_[0])
print(regr.intercept_)

# [1.09059341]
# [0.73344096]

위 코드를 돌리면 그냥 정확한 기울기와 절편 값을 알려준다. 

pytorch를 이용해 미분노다가를 하는 선형회귀를 돌려봤다는 것에 의의가 있다. ㅋ

반응형