회귀(Regression)

부천대 IOT 응용소프트웨어를 수강하는 학생들을 위해 강의내용을 정리했습니다. 강의내용은 모두 파이썬 머신러닝 완벽 가이드를 참고하였습니다.

회귀(Regression)란 무엇인가?

지도학습의 가장 큰 축인 회귀분석은 현업에서 데이터분석가들이 가장 많이 쓰는 분석기법으로 주로 상품매출 예측이나 , 제조공정에서 불량률이라던지, 생산품 예측 외에도 경제학,의학이나 공학 등 가장 많이 활용되는 기법입니다.

회귀의 어원은 영국의 통계학 전공인 갈톤(Galton)이 유전학 특성 연구에 서 유래되었다고 합니다.

당시 부모와 자식 간 키의 상관관계를 분석했던 갈톤은 부모의 키가 크면 자식의 키는 부모 이상으로 클 것이라고 가정했지만, 부모를 능가할 정도로 크지 않았고, 키가 작은 부모에서 자식의 키도 작긴 하지만, 자식의 키가 부모보다는 큰 경향을 발견하게 되었습니다. 부모의 키가 아주 크더라도 자식의 키가 무한정 커지는 것은 아니며 부모의 키가 작더라도 세대를 거듭해 무한정 작아지는 것이 아니었습니다.

이를통해 알게 된 것은 사람의 키는 평균 키로 회귀(regression to the mean)하려는 경향을 가진다는 자연의 법칙이 있습니다.

이처럼 회귀분석은 데이터 값이 평균과 같은 일정한 값으로 돌아가려는 경향을 이용한 통계학 기법입니다.

통계학 용어를 빗대어 설명하면, 회귀는 한개 이상의 독립변수(independent variables,예측 변수)와 한개의 종속변수(dependent variable, 예측 값) 간 상관관계를 모델링 하는 기법입니다. 각 변수의 이름에서 알 수 있듯이 종속변수는 독립변수들의 값에 따라 달라지게 됩니다.

회귀분석에 대해 이해를 돕고자 쉬운 예시 하나를 들어 설명하겠습니다. 우리가 중,고등학교때 배웠던 직선의 방정식인 y = ax+b를 기억해봅시다.

y값은 x값에 따라 달라지게 되는데, 이때 y값은 종속 변수를 가리키고, x는 독립변수를 가리킵니다.

기울기 a는 x가 증가할때 직선이 얼마나 올라가는지 나타내는데, 이때 독립변수의 값에 영향을 주는 a를 회귀 계수(Regression coeficients)라 부릅니다. b는 절편이라고 하는데 직선이 새로 y축과 교차하는 지점을 말하죠. 우리는 x값이 주어졌을 때 a와 b를 구했던 것처럼 회귀방정식도 유사한 기울기- 절편형식으로 데이터를 모델링합니다. 이를 기반으로 데이터가 주어졌을 때 우리는 기계를 통해 x 값과 y값의 관계를 잘 나타내는 a와 b를 찾아내는 것입니다.

머신러닝에서는 독립변수를 피처(feature)라고 부르며, 종속 값은 결정값이라 하는데, 주어진 피처와 결정 값이 주어진 데이터 기반에서 기계학습을 통해 최적의 회귀 계수를 찾아내는 것이 핵심 포인트입니다.

일반적으로 회귀의 유형은 회귀 계수의 선형 여부와 독립변수의 수에 따라 크게 2가지로 나뉘어 생각해볼 수 있습니다.

독립변수 개수

회귀계수 결합

1개: 단일 회귀

선 : 선형회귀

1개이상: 다중회귀

비선형: 비선형 회귀

첫번째, 독립변수의 개수에 따라 나뉩니다. 독립변수가 하나만 있다면 단순 선형 회귀(Simple linear regression)이라 하고, 독립변수가 두개 이상인 경우 다중 선형회귀(multiple linear regression) 또는 다중 회귀(multiple regression)이라 합니다. 두 방법모두 종속변수는 연속 범위에서 측정된다고 가정합니다.

번째, 회귀 계수가 선형여부에 따라 선형회귀와 비선형 회귀로 나뉩니다. 이중에서 선형회귀가 많이 사용되는데 실제 값과 예측값 차이(오류 제곱값 ,RSS)를 최소화하는 직선의 회귀선을 찾는 것이 핵심입니다.

선형 회귀는 또 규제(Regularization)방법에 따라 여러 유형으로 나뉘는데, 여기서 말한 규제는 선형 회귀의 과적합(overfitting) 문제를 해결하기 위해 회귀 계수에 패널티(penalty) 값 적용하는 것을 말합니다.

선형회귀의 대표적인 모델은 다음과 같습니다.

  • 일반선형회귀: 예측값과 실제값의 RSS를 최소화할 수있도록 회귀계수를 최적화하고, 규제를 적용하지 않은 모델

  • 릿지(Ridge): 선형회귀에 L2 규제를 추가한 회귀모델

  • 라쏘(Lasso): 선형회귀에 L1규제를 추가한 모델

  • 엘라스틱넷(Elastic Net): L1, L2규제를 함께 결합한 모델로 주로 피처가 많은 데이터 세트에 적용

  • 로지스틱회귀(Logistic Regression): 사실 분류에 사용되는 선형모델로 이진분류뿐만 아니라 희소영역을 분류하거나 텍스트 분류에서도 사

선형회귀의 다중공선성 문제

선형회귀는 입력 피처(독립변수)의 독립성에 많은 영향을 받습니다. 피처간 상관관계가 높은 경우 분산이 커져 오류에 매우 민감해지는데, 이러한 현상을 다중공선성(multi-colinearity)문제라고 합니다. 따라서 상관관계가 높은 피처가 있는 경우, 하나를 제거하거나 규제를 적용합니다.

그리고 매우 많은 피처가 다중공선성 문제를 가지고 있을 경우, PCA를 통해 차원축소를 시도해볼 필요가 있습니다.

회귀 평가지표

회귀의 평가를 위한 지표는 실제값과 예측값의 차이를 기반으로 한 지표가 중심입니다.

실제값과 예측값 차이를 그냥 더할 경우 오류가 0이되는 상쇄문제가 생기게 되는데, 오류의 절댓값 평균이나 제곱 혹은 제곱의 루트를 씌운 평군값을 구합니다.

평가지표

MAE(Mean Absolute Error)

실제값과 예측값의 차이를 절댓값으로 변환하여 평균을 구한 것

MSE(Mean Squared Error)

RSS와 똑같 실제값과 예측값의 차이를 제곱해 평균을 구한

RMSE(Root Mean Squared Error)

MSE의 값이 실제 오류평균보다 커지는 문제가 있어,MSE에 루트를 씌움. 사이킷런에서 RMSE를 제공하지 않아 MSE에 제곱근을 씌어 계산하는 function을 만들어야 한다.

R^2

분산기반으로 예측 성능을 평가. 실제 값의 분산 대비 예측값의 분산 비율을 지표로 하며, 1에 가까울수록 예측 정확도가 높다.

사이킷런을 활용한 보스턴주택 가격 예측

Linear Regression 예제

(Tensorflow 1.X code)

import tensorflow as tf
#입력 데이터
x_data = [11, 19, 23,26,29,30,38] 
y_data = [29, 33, 51,40,49,50,69]

#placeholders
X= tf.placeholder(tf.float32) 
Y= tf.placeholder(tf.float32)

#기울기(W), y절편값(b) 초기 입력
W = tf.Variable(tf.random_normal([1]), name="weight")
b = tf.Variable(tf.random_normal([1]),name='bias')

#hypothesis  식
hypothesis = W*x_data + b
#cost
cost= tf.reduce_mean(tf.square(hypothesis -y_data))
#Minimize
optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.00008) 
train=optimizer.minimize(cost)

#Launch Session
sess= tf.Session()

#Initialize global variables in the graph
sess.run(tf.global_variables_initializer()) 
for step in range(2000000): 
    sess.run(train) 
    if step %1000==0: 
        print(step, "Cost", sess.run(cost), "W",sess.run(W),"b",sess.run(b))

(Tensorflow 2.X code)

#tensorflow 2.0 code

import tensorflow as tf
import cProfile
import numpy 
print(tf.executing_eagerly())
tf.compat.v1.enable_eager_execution() 
#tf.enable_eager_execution()


x_data = [1,2,3,4,5]
y_data = [1,2,3,4,5]

#w,b initiallize
W= tf.Variable(2.9)
b= tf.Variable(0.5)


# Build hyphothesis and cost
hypothesis = W*x_data + b
#예측과 실제 데이터 차이 = 오차제곱의 평균이 최소화 할수 있는 값(w,b)을 찾기
cost = tf.reduce_mean(tf.square(hypothesis - y_data)) 
#코드를 이해하기 쉽게 분해해봄 
#v=[1,2,3,4]
#차원이 하나 줄어들면서 평균을 구한다.
#print(tf.reduce_mean(v)) #2.5 
#print(tf.square(3)) #9 

#경사하강법 . Gradient Descnet . cost를 최소화하는 것을 찾기
# Learning Rate initailize
learning_rate = 0.01

#Gradient descent

for i in range(101):
    #tape에 w와 b를 기록한 미분값을 튜플로 반환
    with tf.GradientTape() as tape:
        hypothesis= W* x_data + b
        cost= tf.reduce_mean(tf.square(hypothesis- y_data))
    W_grad, b_grad = tape.gradient(cost,[W,b])
    
    #A.asign_sub(B) = A= A-B 파이썬에서 뺀값을 다시 할당, 
    #러닝레이트를 통해 조절해가며 w,b를 업데이트
    W.assign_sub(learning_rate * W_grad)
    b.assign_sub(learning_rate * b_grad)
    # 실행갯수, W값, b값, cost값 
    if i %10 ==0 :
        print( "{:5}| {:10.4f}|{:10.4f}|{:10.6f}".format(i,W.numpy(),b.numpy(),cost)) 

로지스틱 회귀(Logistic Regression)란 무엇인가?

로지스틱회귀분석은 선형회귀분석과 비슷한 원리이지만 종속변수가 연속형이 아닐 때 사용하는 분석방법이다. 즉 독립변수가 연속형 변수이지만, 종속변수가 비연속형 변수(ex) 범주 변수)인 경우 사용된다.

범주 변수는 Kaggle의 Titanic survival 예측에서 생존여부를 확인하는 변수와 같이 경우의 수가 2개만 존재하는 변수로 어떠한 사건이 발생했고, 발생하지 발생하지 않았는지 밖에 없는 변수다.

예를들어 이상윤 교수가 회사를 이직했다, 이직하지 못했다. 혹은 어떤사람이 핸드폰을 바꾼다 바꾸지 않았다가 들 수 있다. 그러므로 아래 왼쪽그림과 같이 Y축의 종속변수가 Y=0,1 로 분포를 보이게 되는데, 선형회귀분석처럼 1차 직선과 같이 표시할 경우, 오차가 커져 예측이 힘들어질 수 있다. 그래서 이러한 직선을 오른쪽 그림과 같이 곡선의 모양으로 만들어 주면 오차를 줄일 수 있다. 이렇게 직선이 아닌 곡선형태로 만들어 주는 방법은 로그함수를 이용하는 것이고, 이러한 로그함수를 이용해 분석을 이뤄졌다고 해서 로지스틱 회귀분석이라 한다.

로지스틱회귀분석의 원리

로지스틱 회귀분석에서는 오즈비(Odds ratio)를 사용한다. 오즈(Odds)는 어떤사건이 일어날 가능성을 말하고, 오즈비는 특정사건이 발생할 확률과 발생하지 않을 확률간의 비율이다.

오즈비를 수식으로 표현하면 아래그림과 같이 P/(1-P)로 나타낼 수 있다.

이 오즈비에 자연로그를 취한 것이 로짓(logit)이라 하는데, 이를 회귀분석 종속변수에 배치하면 로지스틱 회귀분석이 만들어진다. 독립변수나 y절편과 같이 다른부분들은 선형회귀와 비슷하다.

그러면 왜 로그를 붙이는지 궁금해할 것이다. 종속변수에 로그를 씌우면 기존의 0~1 사이의 변수 값들을 음의 무한대에서 양의 무한대까지 일반 연속처럼 바뀌게 되어 우리는 일반 선형회귀분석(OLS)처럼 사용이 가능하기 때문이다.

단 주의할 점은 로지스틱회귀분석은 일반 회귀분석과 다르게 해석을 해야야한다.

로지스틱 회귀분석의 회귀계수(X)에 대해 해석을 할때 EXP(X)를 사용해 로그를 제거하고 해석해야한다.

로지스틱 회귀분석의 회귀계수 해석방법

예를들어 Titanic의 탑승객의 생존여부에 대해 해석을 하면 다음과 같다.

위의 예제는 타이타닉 탑승객의 생존여부에 대한 로지스틱 회귀분석결과이다.

Age의 회귀계수는 -0.002 이고, exp(-0.002)를 계산하면 0.98로 나이가 1살 많을수록 생존확률은 0.98배 증가한다. 0.98배 증가한다는 말은 생존확률이 1보다 작기에 생존확률이 낮아진다고 볼 수 있다.

이렇게 해석하면 어려우니 {exp(로짓값) -1 }*100 수식을 대입하면 % 증가/감소 했는지 쉽게 설명할 수 있다.

Age를 다시 예로 들면, {exp(로짓값) -1 }*100 수식에 대입했을 때 -2%가 나온다. 이는 나이가 많을수록 생존확률은 2% 감소한다고 이해할 수 있다.

다음은 오즈와 오즈비를 구하는 예제이다.

이해가 쉽도록 거주지역(도심지역과 비도심지역)에 따른 코로 감염여부조사를 만들어 보았다.

  • 종속변수 : 코로나 감염여부

  • 독립변수: 거주지역

거주지

발병 YES

발병NO

도심지역

1

99

100

비도심지역

1

299

300

합계

2

398

500

도심지역에서는 100명중 1명이 코로나에 감염 되었고, 비도심지역에서는 300명중 1명이 코로나에 감염되었다고 가정하자.

도심지역에서는 100명중 1명이 코로나에 감염되었으므로, 오즈는 (1/100) / (1- (1/100))을 계산하면 약 0.0101 이 되겠고, 비도심지역에는 300명중 1명이 코로나에 감염 됬으므로, 오즈는 (1/300)/(1-(1/300))를 계산하면 약 약 0.00334이 나온다. 그러면 오즈비는 약 3.02가 나오게 된다. 그러면 오즈비를 통해 알 수 있는 것은 비도심지역에 코로나 감염자가 1명 발생될 때, 도심지역에서는 3명이 발생됨을 알 수 있다. 여기서 오즈비에 로그를 붙이면 log(3.02)= 0.48 이 나온다.

타이타닉 데이터를 이용한 로지스틱회귀분석

import pandas as pd


train = pd.read_csv("train.csv", index_col="PassengerId")
print(train.shape)

train.head()


test = pd.read_csv("test.csv", index_col="PassengerId")
print(test.shape)
test.head()


%matplotlib inline
import seaborn as sns
import matplotlib.pyplot as plt

pd.pivot_table(train, index="Sex", values="Survived")
sns.countplot(data=train, x="Pclass", hue="Survived")
pd.pivot_table(train, index="Pclass", values="Survived")
sns.countplot(data=train, x="Embarked", hue="Survived")
pd.pivot_table(train, index="Embarked", values="Survived")
sns.lmplot(data=train, x="Age", y="Fare", hue="Survived", fit_reg=False)


low_fare = train[train["Fare"] < 500]
train.shape, low_fare.shape
sns.lmplot(data=low_fare, x="Age", y="Fare", hue="Survived", fit_reg=False)

low_low_fare = train[train["Fare"] < 100]
sns.lmplot(data=low_low_fare, x="Age", y="Fare", hue="Survived", fit_reg=False)


train.loc[train["Sex"] == "male", "Sex_encode"] = 0
train.loc[train["Sex"] == "female", "Sex_encode"] = 1

print(train.shape)
train[["Sex", "Sex_encode"]].head()


test.loc[test["Sex"] == "male", "Sex_encode"] = 0
test.loc[test["Sex"] == "female", "Sex_encode"] = 1
print(test.shape)
test[["Sex", "Sex_encode"]].head()


train["Fare_fillin"] = train["Fare"]
print(train.shape)
train[["Fare", "Fare_fillin"]].head()

test.loc[test["Fare"].isnull(), "Fare_fillin"] = 0
test.loc[test["Fare"].isnull(), ["Fare", "Fare_fillin"]]




train["Embarked_C"] = train["Embarked"] == "C"
train["Embarked_S"] = train["Embarked"] == "S"
train["Embarked_Q"] = train["Embarked"] == "Q"
print(train.shape)
train[["Embarked", "Embarked_C", "Embarked_S", "Embarked_Q"]].head()
test["Embarked_C"] = test["Embarked"] == "C"
test["Embarked_S"] = test["Embarked"] == "S"
test["Embarked_Q"] = test["Embarked"] == "Q"
print(test.shape)
test[["Embarked", "Embarked_C", "Embarked_S", "Embarked_Q"]].head()



feature_names = ["Pclass", "Sex_encode", "Fare_fillin",
                 "Embarked_C", "Embarked_S", "Embarked_Q"]
feature_names
label_name = "Survived"
label_name
X_train = train[feature_names]
print(X_train.shape)
X_train.head()

X_test = test[feature_names]
print(X_test.shape)
X_test.head()


y_train = train[label_name]
print(y_train.shape)
y_train.head()





from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)

predictions = model.predict(X_test)
print(predictions.shape)
predictions[0:10]


sample_submission = pd.read_csv('gender_submission.csv')
sample_submission['Survived'] = predictions
sample_submission.head()
sample_submission.to_csv("C:/Users/yunph/Desktop/db/logistic.csv")

참고자료

[1] https://danbi-ncsoft.github.io/study/2018/05/04/study-regression_model_summary.html

[2] https://youtu.be/HCcwgEnijoM

Last updated