[빅데이터] 와인 데이터 분석

구글 코랩 환경에서 데이터 분석을 진행하였으며, 사용한 데이터의 링크는 여길 눌러 들어가라


이 데이터는 와인의 여러 화학적 성분과 와인의 종류(화이트 와인 또는 레드 와인), 그리고 품질 등급을 포함하고 있다. 주요 열의 내용은 다음과 같다.

  • fixed acidity (고정 산도): 와인에 포함된 고정된 산(예: 주석산).
  • volatile acidity (휘발성 산도): 와인에서 휘발성 산이 포함된 양으로, 너무 많으면 식초 같은 맛을 줄 수 있다.
  • citric acid (구연산): 와인의 신선함과 맛을 더해주는 성분.
  • residual sugar (잔당): 발효가 끝난 후에도 남아있는 설탕의 양.
  • chlorides (염화물): 와인에 포함된 염분.
  • free sulfur dioxide (유리 아황산): 미생물 성장을 막고 산화를 방지하는 자유 형태의 아황산.
  • total sulfur dioxide (총 아황산): 유리 및 결합된 아황산의 총량.
  • density (밀도): 와인의 밀도.
  • pH: 와인의 산도.
  • sulphates (황산염): 와인의 쓴맛에 기여할 수 있으며, 항균 특성을 가짐.
  • alcohol (알코올): 와인의 알코올 함량(백분율).
  • quality (품질): 와인의 품질 등급(1에서 10 사이로 추정됨).
  • Type (종류): 와인의 종류, 화이트 와인 또는 레드 와인.

먼저 다운받은 데이터 파일을 코랩 환경에 올리고, 다음의 코드를 작성해주고 실행하자.

import pandas as pd
wine_data = pd.read_csv('WineQuality.csv')
wine_data.head()

다음과 같은 결과가 나올 것이다.

Unnamed: 0은 원래 인덱스로 사용되었던 열이기 때문에, 데이터 자체에 의미 있는 정보가 없고, 상관관계 분석에는 영향을 주지 않기 때문에 그냥 무시해도 된다.

데이터 분석을 위한 준비 단계에서는 데이터의 상태를 파악하고, 데이터 전처리 및 시각화를 통해 분석을 진행할 수 있다. 각 코드를 설명하며 간단히 분석해 보겠다.

데이터 기본 정보 확인

데이터의 기본 구조, 결측값, 데이터 타입 등을 파악한다

# 데이터의 기본 정보 확인
wine_data.info()

이 코드는 각 열의 데이터 유형, 누락된 값이 있는지, 그리고 데이터 프레임의 전체 크기를 확인하는 데 사용된다. 데이터에 대한 전반적인 이해를 할 수 있는 코드다.

데이터 기본 정보 확인 결과 (wine_data.info()):

  • 총 32,485개의 데이터가 있으며, 14개의 열이 있다.
  • 모든 열에 결측값이 없으며, 수치형 데이터는 float64, int64로 구성되어 있고, ‘Type’은 문자열(object)로 되어 있다.

결측값 확인

결측값이 있으면 데이터를 처리하거나 제거하는 작업이 필요하다.

# 결측값 확인
wine_data.isnull().sum()

이 코드는 각 열에 결측값이 몇 개인지 확인하여 결측값 처리 여부를 결정하는 데 도움이 된다.

결측값 확인 결과 (wine_data.isnull().sum()):

  • 모든 열에 결측값이 없음을 확인했다. 따라서 결측값 처리는 필요하지 않다.

기술 통계 확인

데이터의 기초 통계 정보(평균, 표준편차, 최소값, 최대값 등)를 확인한다.

# 기초 통계 정보 확인
wine_data.describe()

이 코드를 사용하여 데이터의 분포, 범위, 이상치 등을 쉽게 파악할 수 있다.

기술 통계 확인 결과 (wine_data.describe()):

  • 각 열의 기초 통계값 확인 결과
    • fixed acidity: 평균값은 7.21, 최소값은 3.8, 최대값은 15.9로 다양한 값이 분포하고 있다.
    • volatile acidity: 평균값은 0.34, 최소값은 0.08, 최대값은 1.58로 휘발성 산도의 변동 폭이 크다.
    • residual sugar: 평균값은 5.44g/L, 최대값은 65.8g/L로 특정 와인에서 잔여 당의 비율이 매우 높은 경우가 있다.
    • quality(품질): 평균 품질은 5.81로, 대부분의 와인이 중간 품질에 분포해 있음을 알 수 있다.

기타 산도, pH, 알코올 등의 분포도 이와 유사하게 다양한 분포를 보인다.

데이터의 중복 여부 확인

데이터에 중복된 행이 있는지 확인하고 제거할 필요가 있다.

# 중복된 데이터 확인 및 제거
wine_data.duplicated().sum()
wine_data = wine_data.drop_duplicates()

중복된 데이터가 있을 경우 이를 제거함으로써 분석의 정확도를 높일 수 있다.

중복값 확인 결과 (wine_data.duplicated().sum()):

  • 중복된 데이터는 없다. 중복 데이터 처리가 필요하지 않다.

데이터의 상관관계 분석

# 문자열 열(Type)을 제외하고 상관관계 행렬 계산
correlation_matrix = wine_data.drop(columns=['Type']).corr()

# 상관관계 히트맵 시각화
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.show()

문자열 데이터를 제외하고 상관관계를 계산한 코드다. ‘Type’ 열을 제외한 나머지 수치형 데이터에 대해서만 상관관계를 계산하도록 했고, 각 변수 간의 상관관계를 히트맵으로 시각적으로 파악할 수 있다. 예를 들어 ‘알코올’이 ‘품질’과 어느 정도 관련이 있는지 확인할 수 있다.

히트맵 결과를 바탕으로 각 변수들 간의 상관관계를 해석할 수 있다. 여기서 중요한 상관관계는 다음과 같다.

품질(quality)과의 상관관계

  • 알코올(alcohol): 0.44로, 긍정적인 상관관계가 있다. 이는 알코올 함량이 높을수록 와인의 품질이 더 높을 가능성이 있음을 나타낸다.
  • 휘발성 산도(volatile acidity): -0.27로, 부정적인 상관관계를 보여준다. 휘발성 산도가 높을수록 와인의 품질이 낮아질 수 있다.
  • 밀도(density): -0.30으로, 부정적인 상관관계를 보인다. 밀도가 높을수록 품질이 낮아질 가능성이 있다.

다른 변수 간의 상관관계

  • 총 아황산(total sulfur dioxide)유리 아황산(free sulfur dioxide)의 상관관계는 0.72로 매우 높다. 이는 이 두 변수 간에 높은 연관성이 있음을 보여준다.
  • 잔여 설탕(residual sugar)밀도(density)의 상관관계는 0.55로 양의 상관관계를 가지고 있다. 이는 설탕이 많을수록 와인의 밀도가 높아질 수 있음을 의미한다.

알코올(alcohol)밀도(density)는 -0.69로 매우 높은 음의 상관관계를 보인다. 알코올이 높아질수록 와인의 밀도가 낮아지는 경향이 있다.

전체적으로 품질과 가장 밀접한 변수는 알코올, 휘발성 산도, 밀도이고, 이 변수들은 품질 예측에 중요할 수 있다.

이 다음부터 데이터 모델링을 위한 분석을 단계별로 진행해보겠다. 주요 통계적 검정(t-검정), 회귀분석, 그리고 선형회귀분석을 해보겠다.

1. T-검정 (t-test)

  • 두 그룹(예: White WineRed Wine) 간의 차이를 검정하기 위해 사용된다
  • 먼저, 와인 종류에 따라 품질(quality)의 평균 차이를 비교해보겠다.
from scipy import stats

# 와인 종류별로 데이터 분리
white_wine = wine_data[wine_data['Type'] == 'White Wine']
red_wine = wine_data[wine_data['Type'] == 'Red Wine']

# White Wine과 Red Wine 간 품질 차이에 대한 t-검정
t_stat, p_value = stats.ttest_ind(white_wine['quality'], red_wine['quality'])

print(f'T-검정 통계량: {t_stat}, p-value: {p_value}')
  • t-검정은 두 집단의 평균 차이를 비교하며, p-value가 0.05 미만이면 통계적으로 유의미한 차이가 있다고 해석할 수 있다.

T-검정 결과

  • T-검정 통계량: 9.54
  • p-value: 1.92e-21

이 결과는 화이트 와인과 레드 와인 간의 품질 차이가 매우 유의미하다는 것을 의미한다. p-value가 0.05보다 훨씬 작기 때문에, 두 와인의 품질 평균에 통계적으로 유의한 차이가 있음을 알 수 있다.

2. 단순 회귀분석

  • 품질(quality)에 가장 영향을 미칠 수 있는 변수(예: alcohol)와의 관계를 살펴보겠다.
  • alcoholquality 사이의 단순 회귀분석을 진행한다.
import statsmodels.api as sm

# 회귀분석을 위한 데이터 준비
X = wine_data['alcohol']  # 독립 변수
y = wine_data['quality']  # 종속 변수

# 상수항 추가 (절편)
X = sm.add_constant(X)

# 회귀모델 적합
model = sm.OLS(y, X).fit()

# 회귀분석 결과 요약 출력
print(model.summary())
  • 여기서 OLS(Ordinary Least Squares)는 가장 기본적인 회귀분석 방법이다. 이 결과를 통해 alcoholquality에 미치는 영향을 분석할 수 있다.

단순 회귀분석 결과 (alcohol vs quality)

  • R-squared: 0.197
  • 알코올에 대한 회귀계수(coef): 0.325
  • F-통계량(F-statistic): 1585, p-value: 3.37e-310

회귀분석에서는 alcoholquality에 미치는 영향이 유의미하다는 것을 확인할 수 있다. R-squared 값은 0.197로, 이 모델이 quality의 변동을 19.7% 정도 설명한다는 뜻이다. 즉, alcohol 변수만으로는 와인 품질의 19.7%만 설명할 수 있다는 뜻이므로, 추가적인 변수가 필요하다.

3. 다중 선형 회귀분석

  • 여러 독립 변수들이 quality에 어떻게 영향을 미치는지 보기 위해 다중 선형 회귀분석을 진행한다.
  • 예를 들어, alcohol, volatile acidity, sulphates 등을 사용해 품질을 예측한다.
# 다중 선형 회귀분석을 위한 변수 선택
X = wine_data[['alcohol', 'volatile acidity', 'sulphates']]  # 독립 변수들
y = wine_data['quality']  # 종속 변수

# 상수항 추가 (절편)
X = sm.add_constant(X)

# 다중 선형 회귀모델 적합
model = sm.OLS(y, X).fit()

# 회귀분석 결과 요약 출력
print(model.summary())
  • 이 분석을 통해 alcohol, volatile acidity, sulphatesquality에 어떻게 영향을 미치는지 알 수 있다.

다중 선형 회귀분석 결과 (alcohol, volatile acidity, sulphates vs quality)

  • R-squared: 0.270
  • 알코올의 회귀계수(coef): 0.3177
  • 휘발성 산도의 회귀계수(coef): -1.4475
  • 황산염의 회귀계수(coef): 0.5932
  • F-통계량(F-statistic): 793.2, p-value: 0.00

다중 회귀분석에서는 alcohol, volatile acidity, sulphates가 함께 quality에 미치는 영향을 분석한 결과다. 이 모델의 R-squared 값은 0.270로, 이 모델이 quality의 변동을 27% 정도 설명한다는 뜻이다.

  • alcohol은 긍정적인 영향을 미치며, 알코올 농도가 높을수록 품질이 높아지는 경향을 보인다.
  • volatile acidity는 부정적인 영향을 미치며, 휘발성 산도가 높을수록 품질이 낮아지는 경향이 강하다.
  • sulphates는 긍정적인 영향을 미치며, 황산염 농도가 높을수록 품질이 높아지는 경향을 보인다.

4. 시각화를 통한 결과 해석

  • 단순 회귀분석 결과를 시각화하여 alcoholquality 간의 관계를 직관적으로 볼 수 있다.
import matplotlib.pyplot as plt
import seaborn as sns

# 산점도와 회귀선 시각화
sns.regplot(x='alcohol', y='quality', data=wine_data)
plt.title('Alcohol vs Quality')
plt.xlabel('Alcohol')
plt.ylabel('Quality')
plt.show()

이전 회귀 분석에서 alcohol 변수만으로는 와인 품질의 19.7%만 설명할 수 있었고, 추가적인 변수가 필요하다 볼 수 있었다. 따라서 알코올이 아닌 다른 변수를 사용하여 quality에 대한 회귀분석을 진행해 보겠다. 또 다른 변수로 휘발성 산도(volatile acidity), 황산염(sulphates), 그리고 pH를 사용 해 보겠다.

이제 각 변수와 quality 간의 관계를 단순 회귀분석으로 확인해보고, 시각화를 통해 분석하겠다. 이번 비교에서는 시각화 자료는 글에서 생략하겠다.

1. 휘발성 산도(volatile acidity)와 품질(quality) 비교 (단순 회귀분석)

# 휘발성 산도와 품질 간의 회귀분석
X = wine_data['volatile acidity']
y = wine_data['quality']

# 상수항 추가
X = sm.add_constant(X)

# 회귀모델 적합
model = sm.OLS(y, X).fit()

# 회귀분석 결과 요약 출력
print(model.summary())

# 시각화
sns.regplot(x='volatile acidity', y='quality', data=wine_data)
plt.title('Volatile Acidity vs Quality')
plt.xlabel('Volatile Acidity')
plt.ylabel('Quality')
plt.show()

휘발성 산도(volatile acidity)와 품질(quality) 비교

  • R-squared: 0.071
  • 회귀계수(coef): -1.4123
  • F-통계량(F-statistic): 492.7, p-value: 3.02e-105

휘발성 산도는 품질에 부정적인 영향을 미치고 있다. 회귀계수가 -1.4123인 것을 보면, 휘발성 산도가 증가할수록 품질은 낮아지는 경향이 있다. p-value가 매우 낮기 때문에 이 결과는 통계적으로 유의미하다.

2. 황산염(sulphates)과 품질(quality) 비교 (단순 회귀분석)

# 황산염과 품질 간의 회귀분석
X = wine_data['sulphates']
y = wine_data['quality']

# 상수항 추가
X = sm.add_constant(X)

# 회귀모델 적합
model = sm.OLS(y, X).fit()

# 회귀분석 결과 요약 출력
print(model.summary())

# 시각화
sns.regplot(x='sulphates', y='quality', data=wine_data)
plt.title('Sulphates vs Quality')
plt.xlabel('Sulphates')
plt.ylabel('Quality')
plt.show()

황산염(sulphates)과 품질(quality) 비교

  • R-squared: 0.001
  • 회귀계수(coef): 0.2263
  • F-통계량(F-statistic): 9.620, p-value: 0.00193

황산염은 품질에 긍정적인 영향을 미치고 있다. 회귀계수가 0.2263으로, 황산염의 증가에 따라 품질도 약간 증가하는 경향이 있다. 하지만 R-squared가 매우 낮고, 설명력은 거의 없다고 할 수 있다. 그러나 p-value는 0.05 미만으로 통계적으로 의미는 있다.

3. pH와 품질(quality) 비교 (단순 회귀분석)

# pH와 품질 간의 회귀분석
X = wine_data['pH']
y = wine_data['quality']

# 상수항 추가
X = sm.add_constant(X)

# 회귀모델 적합
model = sm.OLS(y, X).fit()

# 회귀분석 결과 요약 출력
print(model.summary())

# 시각화
sns.regplot(x='pH', y='quality', data=wine_data)
plt.title('pH vs Quality')
plt.xlabel('pH')
plt.ylabel('Quality')
plt.show()

pH와 품질(quality) 비교

  • R-squared: 0.000
  • 회귀계수(coef): 0.1043
  • F-통계량(F-statistic): 2.385, p-value: 0.123

pH와 품질 사이에는 거의 관계가 없으며, 회귀계수도 0.1043으로 매우 작다. p-value가 0.123으로 0.05를 넘기 때문에, pH와 품질 간의 관계는 통계적으로 유의하지 않다고 볼 수 있다.

데이터 모델링을 진행하는 과정에서 다양한 머신러닝 알고리즘을 사용하여 quality(품질)를 예측할 수 있는 모델을 만들고 평가할 수 있다. 몇 가지 대표적인 모델을 사용하여 진행할 것이다.

단계

  1. 데이터 전처리: 모델에 맞게 데이터를 준비한다.
  2. 데이터 분할: 데이터를 학습용(training)과 테스트용(test)으로 나눈다.
  3. 모델 선택: 몇 가지 대표적인 모델을 사용해 예측을 진행한다.
    • 선형 회귀 (Linear Regression)
    • 랜덤 포레스트 (Random Forest)
    • 의사결정 트리 (Decision Tree)
  4. 모델 평가: 각 모델의 성능을 평가한다.

1. 데이터 전처리

  • 먼저 범주형 변수인 Type을 숫자로 변환하고, 나머지 필요한 전처리를 진행한다.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder

# 범주형 변수인 'Type'을 숫자로 변환
le = LabelEncoder()
wine_data['Type'] = le.fit_transform(wine_data['Type'])  # White Wine=0, Red Wine=1

# 독립 변수(X)와 종속 변수(y) 설정
X = wine_data.drop(columns=['quality'])  # 'quality'를 제외한 모든 변수를 독립 변수로 사용
y = wine_data['quality']  # 품질을 예측해야 하므로 종속 변수는 'quality'

# 데이터셋을 학습용(80%)과 테스트용(20%)으로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

2. 모델 선택 및 학습

(1) 선형 회귀

먼저, 선형 회귀를 사용 해 보겠다. 다음은 선형 회귀 코드다.

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# 선형 회귀 모델 학습
linear_model = LinearRegression()
linear_model.fit(X_train, y_train)

# 테스트 데이터로 예측
y_pred_linear = linear_model.predict(X_test)

# 모델 평가
mse_linear = mean_squared_error(y_test, y_pred_linear)
r2_linear = r2_score(y_test, y_pred_linear)

print(f'선형 회귀 모델 MSE: {mse_linear}, R^2: {r2_linear}')

선형 회귀 모델 결과

  • MSE: 0.5550
  • : 0.3109

선형 회귀 모델의 경우, R² 값이 0.311으로 이는 quality의 변동을 약 31.1% 정도 설명할 수 있음을 의미한다. 즉, 선형 회귀는 와인 품질 예측에서 다소 제한적인 설명력을 보인다. 하지만 기본적인 성능은 가지고 있다.

(2) 랜덤 포레스트 모델

랜덤 포레스트는 여러 결정 트리의 앙상블을 사용하여 예측 정확도를 높인다.

from sklearn.ensemble import RandomForestRegressor

# 랜덤 포레스트 모델 생성 및 학습
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# 예측
y_pred_rf = rf_model.predict(X_test)

# 평가
mse_rf = mean_squared_error(y_test, y_pred_rf)
r2_rf = r2_score(y_test, y_pred_rf)

print(f"랜덤 포레스트 모델의 MSE: {mse_rf}")
print(f"랜덤 포레스트 모델의 R2 Score: {r2_rf}")

랜덤 포레스트 모델 결과

  • MSE: 0.4031
  • : 0.4996

랜덤 포레스트 모델은 선형 회귀보다 MSE가 더 낮고 R² 값이 더 높다. R² 값이 0.4966으로, 이 모델은 quality의 변동을 약 50% 정도 설명할 수 있다. 따라서 랜덤 포레스트가 선형 회귀보다 더 나은 성능을 보이고 있다. 이 모델은 비선형적인 관계를 더 잘 포착할 수 있기 때문에 품질 예측에 더 적합할 가능성이 크다.

(3) 로지스틱 회귀 모델 (분류)

와인의 품질을 몇 개의 범주(예: ‘Low’, ‘Medium’, ‘High’)로 분류하기 위해 로지스틱 회귀를 사용할 수 있다. 먼저 품질을 범주형으로 변환한 후, 로지스틱 회귀를 적용한다.

다음 코드는 최대 반복 횟수를 조정한 코드다.

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import KBinsDiscretizer
from sklearn.metrics import accuracy_score

# 품질을 범주형 변수로 변환 (예: 3개의 구간으로 분류)
kbins = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
y_binned = kbins.fit_transform(y.values.reshape(-1, 1)).ravel()

# 학습 및 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y_binned, test_size=0.2, random_state=42)

# 로지스틱 회귀 모델 학습
logistic_model = LogisticRegression(max_iter=2000)
logistic_model.fit(X_train, y_train)

# 테스트 데이터로 예측
y_pred_logistic = logistic_model.predict(X_test)

# 모델 평가
accuracy = accuracy_score(y_test, y_pred_logistic)
print(f'로지스틱 회귀 모델 정확도: {accuracy}')

로지스틱 회귀 모델 결과

  • 정확도: 0.7519

로지스틱 회귀는 와인의 품질을 범주형으로 예측할 때, 약 75%의 정확도를 보였다. 하지만, 로그에 경고 메시지가 나오는 것을 보면, 로지스틱 회귀 모델이 수렴하지 않은 것을 알 수 있다. 이 문제를 해결하기 위한 두 방법이 있다.

  1. 데이터 스케일링을 적용하여 모델 학습을 더 빠르고 안정적으로 만들기
  2. max_iter 값을 증가시켜 반복 횟수를 늘리기 (기본값은 1000이지만 더 높일 수 있다.)

이미 max_iter값을 조정시킨 코드이기 때문에, 데이터 스케일링을 적용 해 보겠다.

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import KBinsDiscretizer, StandardScaler
from sklearn.metrics import accuracy_score

# 품질을 범주형 변수로 변환 (예: 3개의 구간으로 분류)
kbins = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
y_binned = kbins.fit_transform(y.values.reshape(-1, 1)).ravel()

# 데이터 스케일링 적용
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 로지스틱 회귀 모델 학습
logistic_model = LogisticRegression(max_iter=2000)
logistic_model.fit(X_train_scaled, y_train)

# 테스트 데이터로 예측
y_pred_logistic = logistic_model.predict(X_test_scaled)

# 모델 평가
accuracy = accuracy_score(y_test, y_pred_logistic)
print(f'로지스틱 회귀 모델 정확도: {accuracy}')

로지스틱 회귀 모델의 정확도가 0.7581로 향상되었다. 75.8%의 정확도로 와인의 품질을 분류하고 있음을 의미한다. 스케일링을 적용한 후 수렴 문제가 해결되었고, 모델 성능도 약간 개선된 것으로 보인다.

다음은 로지스틱 회귀의 결과를 시각화하는 방법 및 과정이다.

1. Confusion Matrix (혼동 행렬) 시각화

로지스틱 회귀의 분류 성능을 평가하기 위한 기본적인 시각화 방법 중 하나다. 혼동 행렬을 통해 모델이 얼마나 잘 분류했는지, 어디서 오류가 발생했는지 알 수 있다.

from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

# 혼동 행렬 계산
cm = confusion_matrix(y_test, y_pred_logistic)

# 혼동 행렬 시각화
plt.figure(figsize=(6, 4))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=[0, 1, 2], yticklabels=[0, 1, 2])
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix')
plt.show()

Confusion Matrix (혼동 행렬):

  • 모델이 품질을 분류한 결과가 혼동 행렬로 시각화되었다.
  • Class 1(중간 품질)에서 대부분의 예측이 이루어졌으며, Class 2(고품질)에서는 예측이 다소 어려운 경향이 있다. 특히 Class 2에서 Class 1으로 잘못 분류된 경우가 많다.
  • 정확도가 약 75% 정도였으므로, 모델이 대체로 중간 품질을 잘 예측하고 있으나 고품질에 대한 예측은 더 개선이 필요하다.

2. ROC Curve (Receiver Operating Characteristic Curve)

ROC 곡선은 이진 분류 모델의 성능을 평가하는데 자주 사용된다. 하지만 여러 범주로 나눈 경우, ROC-AUC 스코어를 사용하여 다중 클래스 분류 문제를 평가할 수 있다.

from sklearn.metrics import roc_curve, auc
from sklearn.preprocessing import label_binarize
from sklearn.metrics import roc_auc_score

# 타겟 변수 이진화 (One-vs-Rest 방식)
y_test_binarized = label_binarize(y_test, classes=[0, 1, 2])
n_classes = y_test_binarized.shape[1]

# 예측 확률 추출
y_score = logistic_model.predict_proba(X_test_scaled)

# ROC 커브 시각화
plt.figure()
for i in range(n_classes):
    fpr, tpr, _ = roc_curve(y_test_binarized[:, i], y_score[:, i])
    roc_auc = auc(fpr, tpr)
    plt.plot(fpr, tpr, label=f'Class {i} (area = {roc_auc:.2f})')

plt.plot([0, 1], [0, 1], 'k--', label='Random Guessing')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for Logistic Regression')
plt.legend(loc='lower right')
plt.show()
  • ROC 곡선을 통해 분류 성능을 시각적으로 확인할 수 있다.
  • Class 2(고품질)의 **AUC(0.81)**가 가장 높으며, Class 0(저품질)과 Class 1(중간 품질)의 AUC는 각각 0.80과 0.76이다.
  • 전반적으로 모델이 세 클래스에 대해 비교적 잘 구분하고 있는 것을 볼 수 있다. AUC가 0.70 이상이므로 양호한 성능을 보인다.

3. Precision-Recall Curve

정밀도-재현율 곡선은 특히 클래스가 불균형할 때 모델 성능을 평가하는 데 유용하다. 다중 클래스의 경우, One-vs-Rest 방식으로 시각화할 수 있다.

from sklearn.metrics import precision_recall_curve
from sklearn.metrics import average_precision_score

# Precision-Recall 곡선 시각화
plt.figure()
for i in range(n_classes):
    precision, recall, _ = precision_recall_curve(y_test_binarized[:, i], y_score[:, i])
    avg_precision = average_precision_score(y_test_binarized[:, i], y_score[:, i])
    plt.plot(recall, precision, label=f'Class {i} (AP = {avg_precision:.2f})')

plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve for Logistic Regression')
plt.legend(loc='lower left')
plt.show()
  • Precision-Recall 곡선은 클래스 불균형이 있을 때 모델의 성능을 평가하는 데 유용하다.
  • Class 1(중간 품질)의 정밀도와 재현율 곡선이 가장 높으며, AP (Average Precision)도 0.89로 가장 우수하다.
  • 반면, Class 0(저품질)과 Class 2(고품질)의 경우 정밀도와 재현율이 낮은 편이다. 특히 Class 0의 AP는 0.37로, 저품질 예측의 성능이 상대적으로 떨어진다.

4. Feature Importance (특성 중요도) 시각화

로지스틱 회귀 모델에서 특성 중요도를 시각화할 수 있다. 각 특성의 회귀 계수를 확인하여 어떤 변수가 모델에 중요한 영향을 미치는지 파악할 수 있다.

import numpy as np

# 특성 중요도 시각화
coefficients = logistic_model.coef_[0]
features = X.columns
indices = np.argsort(coefficients)

plt.figure(figsize=(10, 6))
plt.barh(range(len(indices)), coefficients[indices], align='center')
plt.yticks(range(len(indices)), [features[i] for i in indices])
plt.title('Feature Importance (Logistic Regression)')
plt.xlabel('Coefficient Value')
plt.show()

Feature Importance (특성 중요도):

  • 각 특성의 회귀 계수를 통해 로지스틱 회귀 모델이 어떤 특성에 의존하는지를 시각화한 결과다.
  • Type(와인 종류: 레드/화이트)이 가장 중요한 특성으로 나타났으며, 그 뒤를 volatile acidity(휘발성 산도), density(밀도)가 따른다.
  • 반면, pH, citric acid 등은 모델에서 상대적으로 중요도가 낮게 나타났다.
  • Residual Sugar는 부정적인 영향을 미치는 것으로 나타났다. 즉, 설탕의 양이 많을수록 품질이 낮아지는 경향이 있다.

결론

  • 중간 품질을 예측하는 성능은 매우 좋지만, 고품질저품질에 대한 예측 정확도는 상대적으로 낮은 편이다.
  • Type, volatile acidity, density가 모델 성능에 가장 중요한 특성으로 나타났다.
  • Precision-RecallROC 곡선은 모델의 성능을 직관적으로 이해하는 데 큰 도움을 준다. 특히 클래스 간 성능 차이를 파악할 수 있다.

로지스틱 회귀 모델의 하이퍼파라미터를 조정하는 것은 모델 성능을 향상시키는 방법이다. 이를 위해 GridSearchCV 또는 RandomizedSearchCV와 같은 방법을 사용해 최적의 하이퍼파라미터를 찾을 수 있다.

주요 하이퍼파라미터

  1. C: 정규화 강도를 조절하는 하이퍼파라미터다. 작은 값은 강한 정규화(모델이 단순해짐)를 의미하며, 큰 값은 약한 정규화를 의미한다.
  2. solver: 최적화 알고리즘을 선택한다. 일반적으로 ‘lbfgs’, ‘liblinear’, ‘saga’ 등이 사용된다.
  3. penalty: 정규화 방법입니다. ‘l1’ 또는 ‘l2’ 규제를 사용하여 과적합을 방지한다.

1. GridSearchCV를 통한 하이퍼파라미터 조정

GridSearch는 하이퍼파라미터의 여러 값을 시도해보고 최적의 조합을 찾는다.

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# 데이터 스케일링 적용
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 로지스틱 회귀 모델과 하이퍼파라미터 그리드 정의
logistic_model = LogisticRegression(max_iter=2000)
param_grid = {
    'C': [0.001, 0.01, 0.1, 1, 10, 100],
    'solver': ['lbfgs', 'liblinear', 'saga'],
    'penalty': ['l2']
}

# GridSearchCV로 하이퍼파라미터 튜닝
grid_search = GridSearchCV(logistic_model, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid_search.fit(X_train_scaled, y_train)

# 최적의 하이퍼파라미터와 성능 확인
best_params = grid_search.best_params_
best_score = grid_search.best_score_

print(f'최적의 하이퍼파라미터: {best_params}')
print(f'훈련 세트에서의 최고 성능: {best_score}')

# 최적의 모델로 테스트 세트 평가
best_model = grid_search.best_estimator_
y_pred_best = best_model.predict(X_test_scaled)
test_accuracy = accuracy_score(y_test, y_pred_best)
print(f'테스트 세트 정확도: {test_accuracy}')

2. RandomizedSearchCV를 통한 하이퍼파라미터 조정

RandomizedSearchCV는 GridSearch와 유사하지만, 각 하이퍼파라미터의 랜덤한 값 조합을 시도하여 더 빠르게 최적의 하이퍼파라미터를 찾을 수 있다.

from sklearn.model_selection import RandomizedSearchCV
import numpy as np

# 하이퍼파라미터 그리드 설정
param_distributions = {
    'C': np.logspace(-3, 3, 10),
    'solver': ['lbfgs', 'liblinear', 'saga'],
    'penalty': ['l2']
}

# RandomizedSearchCV로 하이퍼파라미터 튜닝
random_search = RandomizedSearchCV(logistic_model, param_distributions, n_iter=10, cv=5, scoring='accuracy', n_jobs=-1, random_state=42)
random_search.fit(X_train_scaled, y_train)

# 최적의 하이퍼파라미터와 성능 확인
best_params_random = random_search.best_params_
best_score_random = random_search.best_score_

print(f'RandomSearch 최적의 하이퍼파라미터: {best_params_random}')
print(f'훈련 세트에서의 최고 성능: {best_score_random}')

# 최적의 모델로 테스트 세트 평가
best_model_random = random_search.best_estimator_
y_pred_best_random = best_model_random.predict(X_test_scaled)
test_accuracy_random = accuracy_score(y_test, y_pred_best_random)
print(f'테스트 세트 정확도: {test_accuracy_random}')

결과 해석

  • GridSearchCVRandomizedSearchCV 모두 비슷한 성능을 보였으며, 두 방법 모두 최적의 하이퍼파라미터로 학습된 모델은 훈련 세트에서 약 79.0%의 성능을 보였다.
  • 테스트 세트에서는 76.0% ~ 76.1% 정도의 정확도를 보였으며, 두 방법의 결과가 거의 유사하다. 이는 두 하이퍼파라미터 조정 방법이 모두 좋은 모델을 찾았다는 것을 의미한다.

최종적으로 튜닝된 하이퍼파라미터를 바탕으로 학습된 로지스틱 회귀 모델의 예측 결과를 시각화해보겠다.

로지스틱 회귀는 분류 문제이므로, 시각화는 주로 혼동 행렬(Confusion Matrix)나 ROC 곡선 등을 사용하여 모델의 성능을 평가할 수 있다.

  1. 혼동 행렬 (Confusion Matrix) 시각화: 모델이 얼마나 잘 분류했는지 확인할 수 있습니다.
  2. ROC 곡선 및 AUC (Receiver Operating Characteristic curve): 이진 분류 문제에서 모델의 성능을 평가할 때 자주 사용되지만, 튜닝 후의 시각적으로 그다지 변화가 크지 않아 생략하도록 하겠다.

혼동 행렬 (Confusion Matrix) 시각화

튜닝 후 중간 품질(Class 1)에 대한 예측 성능이 향상되었으며, 모델의 전반적인 성능은 개선되었다.

아쉽게도 각 클래스의 분포가 로지스틱 회귀 모델로 분류하기에 어려운 패턴을 가지고 있다. 특히 중간 품질의 클래스가 많이 분포해 있기 때문에, 모델이 그쪽으로 예측을 집중하는 경향이 있었다. 튜닝이 크게 성능을 바꾸지 않는 원인이 된 것 같다. 로지스틱 회귀는 선형적 분류 방법이기 때문에, 비선형적인 패턴을 잡아내기 어려운 경우가 있다. 이 때문에 ROC 곡선에서 큰 성능 향상이 보이지 않았던 것이다.

로지스틱 회귀 외에, 비선형적인 관계를 더 잘 반영할 수 있는 Random Forest, Gradient Boosting, XGBoost 등의 모델을 해볼 수 있기는 하다. 비선형 관계를 더 잘 포착할 수 있어 튜닝을 거치면 성능이 향상될 가능성이 있다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤