본문 바로가기

카테고리 없음

내일배움캠프 2022.05.13 TIL

논리회귀 (Logistic Regression)

선형회귀로 풀기 어려운 문제들이 생기기 시작했다. 모든 문제의 원인과 결과가 직선 형태로 표현되기 힘들어졌다는 것이다. 직선으로 표현할 경우, 정확도가 떨어지는 문제점이 발생하면서 논리회귀라는 개념이 도입되었다. 논리회귀는 직선이 아닌 S자 곡선 형태를 이용하여 분류를 한다. 

예를 들어, 시험 전 날 공부한 시간과 그 시험의 통과 여부를 예측하는 문제가 있다고 하자. 입력값은 공부 시간, 출력값은 통과 여부가 될 것이다.

선형회귀

 이 문제를 선형회귀로 풀게 된다면 위와 같은 그래프가 그려지게 된다. 빨간 점들이 실제값인데 예측 그래프가 정확하지 않다는 것을 확인할 수 있다. 이를 보완하기 위한 함수의 형태가 아까 말한 S자 곡선 형태의 함수인데, 이를 로지스틱 함수 또는 시그모이드(Sigmoid) 함수라고 한다. 이 함수의 특징은 그래프가 입력값이 음수이건 양수이건 간에 y=0에서 y=1 사이에 존재한다는 것이다.

논리회귀

이 문제를 논리회귀로 풀게 된다면 위와 같은 그래프가 그려지게 된다. 선형회귀의 그래프보다 정확도가 확연히 높아진 것을 확인할 수 있다. 

논리회귀 시, 임계치(Threshold)를 직접 정해줄 수 있다. 임계치란 위와 같은 문제에서 이진 분류를 하기 위한 기준치라고 생각하면 된다. 예를 들어, '출력값이 0.5 이상이면 Pass고 아니면 Fail이다.' 라고 가정하면, 임계치는 0.5가 되는 것이다. Pass라는 결과의 정확도가 중요할 경우에는 임계치를 높임으로써 Pass에 대한 정확도를 높일 수도 있다.

 

선형회귀의 식이 H(x) = Wx + b 였다면, 논리회귀의 식은 이 식에 시그모이드 함수를 씌운 것과 같다. 시그모이드 함수를 S(x)라 하면, 논리회귀의 식은 S(H(x)), 즉, S(Wx+b)가 되는 것이다.

 

논리회귀에서는 손실함수로 Cross Entropy라는 함수를 사용한다. 

 

 

 

다항논리회귀 (Multinomial Logistic Regression)

위의 Pass/Fail은 결과가 둘 중 하나이기 때문에 단항논리회귀에 속한다. 그렇다면 시험 전 날 공부한 시간과 그 시험의 성적(A, B, C, D, F)을 예측하는 문제는 어떻게 풀면 될까? 

 

우선, 성적 클래스를 One-hot Encoding을 통해 분류해준다. One-hot Encoding이란 여러 개의 항을 0과 1만을 이용하여 표현하는 방법이다. 예를 들어 성적 A, B, C, D, F에서 A를 [1, 0, 0, 0, 0], B를 [0, 1, 0, 0, 0], C를 [0, 0, 1, 0, 0], D를 [0, 0, 0, 1, 0], F를 [0, 0, 0, 0, 1] 과 같이 표현하는 것이다. 즉, One-hot Encoding의 방법은 다음과 같다.

 

1. 클래스의 개수만큼 배열을 0으로 채운다.

2. 각 클래스의 index를 정해준다.

3. 각 클래스가 해당하는 index에 1을 넣어준다.

 

그렇다면 시그모이드 함수는 0과 1사이에 그려지는데 그럼 분류 클래스가 둘 밖에 없는 경우만 예측이 가능하지는 않을까? 다항논리회귀의 경우에는 어떻게 문제를 해결할까?

 

다항논리회귀에서는 시그모이드 대신 소프트맥스(Softmax) 함수를 사용한다. 소프트맥스 함수는 입력값이 들어오면 이들을 0과 1사이의 값으로 정규화해준다. 이때, 정규화된 값들을 모두 더하면 1이 된다는 특징이 있다. (사실 시그모이드 함수는 소프트맥스 함수의 일부분!!)

 

다항논리회귀에서는 손실함수로 Categorical Cross Entropy라는 함수를 사용한다.

 

 

이진논리회귀 실습 - 타이타닉 생존자 예측하기

우선, head() 메소드를 이용하여 데이터의 column을 확인해보자.

데이터 확인해보기

 

너무 많은 column들이 존재한다. 사용할 column들만 추출하자. 원래는 correlation을 분석하여 y축과 관련 높은 column들만 추출하는 것이 맞지만, 이번 실습에서는 그냥 적당히 관계 있어 보이는 column들을 직접 분석하여 추출했다.

df = pd.read_csv('train_and_test2.csv', usecols=[
  'Age', # 나이
  'Fare', # 승차 요금
  'Sex', # 성별
  'sibsp', # 타이타닉에 탑승한 형제자매, 배우자의 수
  'Parch', # 타이타니게 탑승한 부모, 자식의 수
  'Pclass', # 티켓 등급 (1, 2, 3등석)
  'Embarked', # 탑승국
  '2urvived' # 생존 여부 (0: 사망, 1: 생존)
])

필요한 column들만 추출한 데이터

 

isnull() 메소드를 이용하여 NaN이나 null값이 있는지 확인해주고, 있다면 dropna() 메소드로 그 row를 제거해주도록 한다.

 

남겨둔 column중 '2urvived'는 우리가 y축으로 사용할 결과 column이다. 즉, x_data에 들어갈 column이 아니다. 데이터들을 분리해준다.

x_data = df.drop(columns=['2urvived'], axis=1)
x_data = x_data.astype(np.float32)

y_data = df[['2urvived']]
y_data = y_data.astype(np.float32)

 

표준화를 진행한다.

scaler = StandardScaler()
x_data_scaled = scaler.fit_transform(x_data)

 

train_test_split() 메소드를 통해 학습 데이터와 검증 데이터를 나눠준다.

x_train, x_val, y_train, y_val = train_test_split(x_data, y_data, test_size=0.2, random_state=201802118)

 

이진논리회귀이기 때문에 activation 함수로는 시그모이드 함수를, 손실 함수로는 binary_crossentropy를 사용한다고 선언해준다. 그리고 나서 20번의 학습을 진행해주었다.

model = Sequential([
  Dense(1, activation='sigmoid')
])

model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.01), metrics=['acc'])

model.fit(
    x_train,
    y_train,
    validation_data=(x_val, y_val),
    epochs=20
)

 

결과를 확인해보자.

y_pred = model.predict(x_val)

print(y_pred)

결과

결과가 다음과 같이 나왔지만 우리는 임계값을 정할 수 있다. 0.6이 넘으면 살았다고 가정할 수도 있고, 0.7이 넘어야 살았다고 가정할 수도 있다.

 

 

다항논리회귀 실습 - 와인 종류 예측

이번엔 다항논리회귀를 연습해보자. 여러 데이터로부터 와인 종류를 예측하는 것이다. 우선, 위와 동일하게 데이터부터 확인해보자.

데이터 확인해보기

 

데이터가 이상하게 생겼다. column의 이름들이 보이지를 않는다...! 데이터셋을 찾아보면 설명이 나와있으므로 직접 그 설명을 읽고 헤더를 채워넣어주자.

df = pd.read_csv('Wine.csv', names=[
  'name'
  ,'alcohol'
  ,'malicAcid'
  ,'ash'
  ,'ashalcalinity'
  ,'magnesium'
  ,'totalPhenols'
  ,'flavanoids'
  ,'nonFlavanoidPhenols'
  ,'proanthocyanins'
  ,'colorIntensity'
  ,'hue'
  ,'od280_od315'
  ,'proline'
])

데이터 헤더 추가

 

데이터의 각각의 column이 무엇을 나타내는지 이제 바로 알 수 있다.

 

이제, isnull() 메소드를 이용하여 NaN이나 null값이 있는지 확인해주고, 있다면 dropna() 메소드로 그 row를 제거해주도록 한다. 이번 데이터에서는 NaN이나 null값이 없다.

 

우리가 이번 데이터에서 y축으로 쓸 값은 'name' column이다. 이를 이용하여 데이터를 분리해주자.

x_data = df.drop(columns=['name'], axis=1)
x_data = x_data.astype(np.float32)

y_data = df[['name']]
y_data = y_data.astype(np.float32)

 

표준화를 진행한다.

scaler = StandardScaler()
x_data_scaled = scaler.fit_transform(x_data)

 

이진논리회귀와 달리 다항논리회귀에서는 One-hot Encoding을 진행해준다.

encoder = OneHotEncoder()
y_data_encoded = encoder.fit_transform(y_data).toarray()

 

train_test_split() 메소드를 통해 학습 데이터와 검증 데이터를 나눠준다.

x_train, x_val, y_train, y_val = train_test_split(x_data_scaled, y_data_encoded, test_size=0.2, random_state=201802118)

 

다항논리회귀이기 때문에 activation 함수로는 소프트맥스 함수를, 손실 함수로는 categorical_crossentropy를 사용한다고 선언해준다. 그리고 나서 20번의 학습을 진행해주었다.

model = Sequential([
  Dense(3, activation='softmax')
])

model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=0.02), metrics=['acc'])

model.fit(
    x_train,
    y_train,
    validation_data=(x_val, y_val),
    epochs=20
)

 

결과를 확인해보자.

y_pred = model.predict(x_val)

print(y_pred)

결과

 

가장 1에 가까운 값이 들어있는 열의 클래스를 가진다고 생각하면 된다.

 

 

NaN과 null 값의 처리에 대한 생각

데이터의 전처리는 머신러닝 시 굉장히 중요하다. 이때, NaN값과 null값을 처리해주는 것도 개발자가 해야하는 일 중 하나이다. 물론 drop() 메소드를 통해 그냥 없앨 수도 있지만, 개발자 역량껏 데이터들의 평균값을 넣어준다든지, 최빈값을 넣어준다든지, 중앙값을 넣어준다든지 등 작업을 수행할 수 있다. 그런데 NaN은 뭐고 null은 뭘까?

 

NaN과 null은 모두 정해지지 않은 값을 의미한다. (적어도 파이썬에서는 같은 의미로 사용된다.) 그렇기 때문에 isnull() 메소드만을 통해 두 값 모두를 검사해낼 수 있다. 

 

실생활의 대부분의 데이터들은 모든 column들에 대한 정보를 가지고 있기 힘들다. 어떤 column의 데이터를 가지고 있더라도 어떤 column의 데이터는 가지고 있지 않을 수도 있다. 그렇기 때문에 결측치를 모두 drop 처리해버린다는 것은 데이터적으로 굉장히 큰 손실을 야기할 수 있다. 결측치를 어떻게 처리할 수 있을지 다음 프로젝트 때 열심히 공부해보자! 평균값으로 대체하는 것이 좋은 것만은 아닐 것이다. 예를 들어, 성별 column이 여자이고 키 column이 NaN인 경우와 성별 column이 남자이고 키 column이 NaN인 경우에, 성별 column이 남자일 경우에 좀 더 큰 값으로 NaN을 대체할 수 있을지도 모른다. 🤔...? 이상! 개인적인 생각이었습니다!