안녕하세요.🤗 머신러닝 모델링 과정 A to Z를 녹여내고자 이번 글을 작성해보았습니다.
비전공자였던 제 경험을 살려서 머신러닝 모델링을 처음 해보시는 분들을 위한 로드맵입니다. 머신러닝 모델링을 처음 봤을 때는 막막하고 어디서부터 시작해야 할지 모를 수 있습니다. 하지만 시간이 지나면서 데이터 분석가 혹은 AI 개발자들이 머신러닝 모델을 구현하며 효율적으로 문제를 해결하기 위해 쌓아온 '베스트 프렉티스'가 있기 마련입니다. 이번 글은 이런 '베스트 프렉티스'를 저의 경험에 비추어서 적어보았습니다.
머신러닝 모델링을 할 때는 내가 전체 프로세스 중에서 어떤 스텝을 밟고 있는지, 앞으로 어떤 스텝이 남아 있는지를 알기만 해도 문제를 풀어나갈 수 있습니다. 따라서, 실제 데이터셋과 코드를 활용하는 것보다는 전체 프로세스를 적어보고자 했습니다. 이 글의 목적은 로드맵을 구성하는 것이고, 코드는 구글링과 LLM 모델들이 자세히 다 알려주기 때문이죠.💯
로드맵 구성은 아래와 같습니다.
- 데이터랑 친해지기 (데이터 확인/탐색)
- 꼬질꼬질한 데이터 청소하기 (이상치 & 결측치 핸들링, 스케일링)
- 문자를 숫자로 변환 (카테고리화)
- 변수들간의 팀워크 조절 (다중공선성 확인 & 변수 제거)
- 데이터 패턴 찾기 (데이터 분리, 학습, 예측, 하이퍼파라미터 튜닝)
- 결과 평가 (평가 지표, 교차검증)
데이터랑 친해지기 (데이터 확인과 탐색)
머신러닝 모델링을 한다는 것은 머신러닝 모델을 개발하여 데이터의 표현을 효과적으로 학습하겠다는 것을 의미합니다. 그럼 우리는 데이터가 어떤 패턴(what)을 가지고 있는지 어떻게(how) 알 수 있을까요? 여러 방법이 있겠지만 가장 효과적인 방법은 데이터를 눈으로 뜯어보는 것입니다. 우리는 이 과정을 EDA(Exploratory Data Anlaysis)라고 합니다.
모델링 시작 전에 데이터를 뜯어보는 것을 처음 만난 사람에게 "안녕하세요? 이름이 뭐에요? 어디 사세요?"라고 물어보는 것과 같습니다. 데이터가 어떻게 생겼는지, 어디가 꼬질꼬질한지 자세히는 몰라도 넓은 시야에서 바라볼 수 있어야 앞으로의 계획을 미리 세울 수 있습니다.
우리는 아래와 같은 코드들을 통해 데이터를 넓은 관점에서 뜯어볼 수 있습니다.
# 데이터의 상위, 하위 값들을 보며 어떻게 생겼나 눈으로 파악하기
df.head()
df.tail()
# 데이터의 각 컬럼에 어떤 형태의 데이터가 있는지, 결측치가 존재하는지 살펴보기
df.info()
# 숫자형 데이터의 기본적인 컬럼 값 확인하기
# 평균, 분산, 표준편차 등
df.describe()
# 이외에 데이터 분포를 한 눈에 확인할 수 있는 시각화 코드 작성하기
꼬질꼬질한 데이터 청소하기 (이상치 & 결측치 핸들링, 스케일링)
이상치와 결측치
우리가 현실에서 마주하는 데이터는 절대 깨끗하지 않습니다. 현실에는 다양한 관측값들이 존재할 뿐만 아니라 휴먼 에러나 측정 기기 결함으로 이상치와 결측치가 존재할 수 있습니다. 이상치와 결측치는 머신러닝 모델이 학습할 수 있는 정규분포를 만드는데 방해가 됩니다. 결과적으로, 머신러닝 모델의 결과에 매우 부정적인 영향을 미칠 수 있습니다. 그러니 이상치와 결측치를 적절히 제거하거나 변경해야 합니다. 사실 머신러닝 모델링 과정 중 50% 이상의 공수가 들어가는 부분이 전처리입니다. 이상치와 결측치는 도메인 지식도 필요하고 데이터의 형태에 따라 다르게 핸들링 해야 하니 연구자의 직관과 지속적인 실험이 중요한 부분입니다.
일례로, 제가 직접 데이터셋을 수집하고 모델링하는 공모전에 참가했을 때, 전체 본선 기간 8주 중 5주를 데이터 수집과 전처리 하는데 투자했습니다. 하루에 6시간 씩 크롤링 코드를 수정하고 데이터를 전처리해도 문제들이 계속해서 튀어 나옵니다.
우리는 아래와 같은 방법들을 통해 데이터의 이상치와 결측치를 확인할 수 있습니다.
- 이상치
- 시각화(Box plot)로 튀는 값 찾기
- IQR or Z-score와 같은 통계 기법으로 이상치 정의하고 제거
- 결측치
- 결측치 제외하기
- 평균, 중앙값, 최빈값으로 대체하기
- 결측치 보간 알고리즘으로 빈 값 채우기(보간의 유효성은 실제 머신러닝 학습 이후 성과 비교로 알 수 있음)
- 시계열 보간: 선형, 스플라인 보간
- 변수들 간의 관계 고려: KNN 보간
# 결측치 제거하기
df.dropna()
# 결측치 채우기
df.fillna()
스케일링
데이터 셋에 있는 데이터는 열마다 다른 단위의 데이터를 가질 수 있습니다. 만약 1~100 사이의 값으로 표시되는 단위를 가진 데이터와 1000~10000 사이의 값으로 표시되는 단위를 가진 데이터에 가중치를 곱하게 되면 당연히 후자의 값이 훨씬 더 크게 나오게 됩니다. 따라서, 우리는 각 데이터를 공정하게 비교하기 위해서 스케일링을 해야 합니다.
스케일링은 대표적으로 아래 2가지가 있고, 모델마다 다른 스케일링 방법에 따라 성능이 달라질 수 있기 때문에 실험을 통해 스케일링 방법을 적절히 선택해야 합니다.
- MinMax scaler: 데이터를 0에서 1사이(음수 값이 있으면 -1에서 1사이)로 값을 변환해주는 정규화 방법
- Standard scaler: 데이터 feature 각각이 평균이 0이고, 분산이 1인 가우시안 정규분포로 변환하는 표준화 방법
# MinMax Scaler
from sklearn.preprocessing import MinMaxScaler
minmax_scaler = MinMaxScaler()
minmax_scaled = minmax_scaler.fit_transform(X_train)
# Standard Scaler
from sklearn.preprocessing import StandardScaler
standard_scaler = StandardScaler()
standard_scaler.fit_transform(X_train)
Tip: 거리 기반 모델과 경사하강법 기반 모델들은 Scaling에 민감하니 꼭 진행해주셔야 합니다.
- SVM: 데이터 포인트 거리에 기반해서 분류 경계를 찾습니다. 스케일링이 되어 있지 않으면 특정 데이터에 과도하게 의존할 가능성이 있습니다.
- 군집화(Clustering) 모델: 데이터 포인트 간의 거리를 기준으로 군집을 만듭니다. 스케일링이 되어 있지 않으면 특정 데이터에 과도하게 의존할 가능성이 있습니다.
- 신경망 모델: 경사하강법을 사용해서 모델을 업데이트 합니다. 스케일링이 되어 있지 않으면 global minimum을 찾기 어려워집니다.
- 선형 모델: 학습 안정성과 모델 성능 개선을 위해 스케일링이 필요합니다.
문자를 숫자로 변환 (카테고리화)
머신러닝 모델은 문자형 변수를 학습할 수 없습니다. 따라서 문자를 숫자형으로 바꿔야 합니다.
카테고리화는 크게 아래와 같이 2가지 방법으로 진행할 수 있습니다.
- One-hot encoding: 범주의 개수만큼 새로운 컬럼을 만들어서 해당하면 1, 아니면 0으로 변환합니다. (ex. 성별(남/여) -> 성별_남 (0/1), 성별_여(1/0))
- Label encoding: 각 범주에 0부터 순서대로 숫자를 부여하는 방식. 데이터 간 순서가 있을 때 주로 사용합니다. (ex. 소/중/대 -> 0/1/2)
Tip: 어떤 카테고리화 방법을 사용할지는 데이터의 특성과 모델마다 다릅니다. 프로젝트를 진행하면서 어떤 방법을 사용할지 충분히 고민해야 합니다.
Tip: ColumnTransformer를 사용하면 손쉽게 컬럼별 스케일링을 할 수 있습니다.
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler, ['numerical_feature_1', 'numerical_feature_2']), # 'num'이라는 이름으로 숫자 컬럼에 StandardScaler 적용
('cat', OneHotEncoder, ['categorical_feature_1', 'categorical_feature_2']) # 'cat'이라는 이름으로 범주형 컬럼에 OneHotEncoder 적용
],
remainder='passthrough' # 위에 명시되지 않은 컬럼은 그대로 통과
)
변수들간의 팀워크 조절(다중공선성 확인 & 변수 제거)
프로젝트를 진행할 때, 팀원 간 잘하는게 달라야 좋은 시너지를 발생시킬 수 있듯이, 종속변수를 설명하는 독립변수 간 상관관계가 낮을 때 모델의 성능에 좋은 영향을 미칩니다.
예를 들어, 카페인 섭취량과 공부시간이라는 독립변수가 있고, 시험 성적이라는 종속변수가 있다고 가정해보겠습니다. 카페인 섭취량과 공부시간이 모두 시험 성적에 양의 상관관계를 보이는 상황에서 카페인 섭취량과 공부시간의 상관관계가 높다면 어떤 변수가 시험 성적에 영향을 미치는지 명확하게 알 수 없습니다. 카페인 섭취량이 공부시간을 늘려서 시험 성적이 올라갔는지, 공부시간이 늘어서 카페인 섭취량을 늘리고 시험 성적이 올라갔는지 명확히 알 수 없을 것입니다. 아래는 다중공선성을 확인할 수 있는 방법입니다.
- VIF(Variance Inflation Factor): VIF가 10을 넘어가면 다중공선성이 높게 나타나는 것으로 봅니다.
- 상관계수 행렬: 상관계수 행렬을 heatmap으로 시각화해서 상관계수가 높게 나오는 변수들을 확인할 수 있습니다.

데이터 패턴 찾기(데이터 분리, 학습, 예측, 하이퍼파라미터 튜닝)
지금까지 우리는 머신러닝이 학습하기 좋게 데이터를 구하고, 다듬고, 요리까지 했습니다. 이제 모델에 떠먹여줄 시간입니다. 데이터 모델을 구현하는 것은 간단합니다. 이미 전문가들이 API로 누구나 사용하기 편리하게 만들었기 때문입니다.
- 데이터 분리: 전체 데이터를 학습 데이터와 시험 데이터로 나눠줍니다. 일반적으로 7:3 혹은 8:2로 나눠줍니다.
- 모델 선택: 회귀 문제인지 분류 문제인지에 따라 모델 선택이 달라집니다. 일반 회귀 모델부터 Decision Tree, XGBoost 같이 굉장히 다양한 모델들이 있습니다.
- 모델 학습: 선택한 모델에 학습용 데이터를 넣고 model.fit()을 진행합니다.
- 하이퍼파라미터 튜닝: 모델이 똑똑하게 학습할 수 있도록 optimizer와 같은 하이퍼파리미터를 변경해가며 최적의 성능을 찾아갑니다.
Tip: 우리의 프로젝트 목표가 무엇인지 명확하게 하는 것이 0순위입니다. 이후 모델 후보군들을 고르고, 실험을 통해 최적의 모델을 선정합니다.
데이터 분리
# 1단계: 필요한 라이브러리 불러오기
from sklearn.model_selection import train_test_split # 데이터를 훈련용/테스트용으로 나누는 함수
# 학습용(Train) 데이터와 평가용(Test) 데이터 분리
# 보통 7:3 또는 8:2 비율로 많이 나눔. 여기서는 8:2
# random_state는 일반적으로 42로 고정해서 매번 같은 데이터셋으로 분리할 수 있도록 함
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 분류 문제에서 특정 데이터의 비율이 중요할 경우 stratify 추가
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=df[col])
print(f" 전체 데이터 {len(X)}개 중...")
print(f" - 훈련용 데이터: {len(X_train)}개")
print(f" - 평가용 데이터: {len(X_test)}개")
데이터 학습
학습에 사용할 수 있는 모델은 굉장히 많지만, 본 글의 목적은 모델의 구조와 개념을 설명하는 것이 아니기에 설명하지 않겠습니다.
# 4단계: 머신러닝 모델 선택 및 객체 생성
from sklearn.linear_model import LogisticRegression # 간단하지만 강력한 분류 모델 (로지스틱 회귀)
# 참고: 프로젝트 목표에 따라 다양한 모델을 시도
model = LogisticRegression(max_iter=200, random_state=42) # max_iter는 반복 횟수 설정
# 5단계: 모델 학습 시키기
model.fit(X_train, y_train)
Tip: Pipeline을 사용하면 전처리와 학습 fit을 하나로 처리해 데이터 누수를 방지하고, 코드를 간결하게 만들 수 있습니다.
# Pipeline 구축.
pipeline = Pipeline(steps=[
('scaler', StandardScaler()), # 첫 번째 단계: StandardScaler (스케일링 전처리)
('classifier', LogisticRegression()) # 두 번째 단계: LogisticRegression (분류 모델)
])
# 전처리와 학습을 한 번에!
pipeline.fit(X_train, y_train)
데이터 예측 및 평가
# 6단계: 학습된 모델로 새로운 데이터 예측하기
from sklearn.metrics import accuracy_score # 모델 성능 평가 지표 (정확도)
# 평가용 데이터(X_test)를 던져주고 예측 값(y_pred)을 받아봄
y_pred = model.predict(X_test)
print(f"실제 정답: {y_test}")
print(f"모델 예측값: {y_pred}")
# 7단계: 모델 성능 평가
# 예측한 값이 실제 정답이랑 얼마나 맞았는지 정확도(Accuracy)로 확인
accuracy = accuracy_score(y_test, y_pred)
print(f"모델의 정확도(Accuracy): {accuracy:.4f}") # 소수점 4째자리까지 표시
하이퍼파라미터 튜닝
모델의 성능이 기대보다 낮다면 우리는 데이터 전처리를 다시하거나 하이퍼 파라미터를 변경하는 방법을 고려할 수 있습니다. 하이퍼 파라미터란 우리가 직접 설정할 수 있는 모델의 변수로 학습률, 에포크 수, 배치 크기 등이 있으며 모델 성능에 직접적인 영향을 미칩니다. 그래서 우리는 적절한 하이퍼 파라미터를 설정하는 것이 중요합니다. 하지만 수 많은 하이퍼 파라미터를 일일히 실험하는 것은 시간상, 자원상 비효율적입니다. 그래서 우리는 GridSearchCV라는 클래스를 사용해서 최적의 성능 결과를 나타내는 하이퍼 파라미터를 찾을 수 있습니다. 아래는 GridSearchCV 예시 코드 입니다.
# GridSearchCV의 예시 코드
class sklearn.model_selection.GridSearchCV(estimator, param_grid, *, scoring=None, n_jobs=None, refit=True,
cv=None, verbose=0, pre_dispatch='2*n_jobs',
error_score=nan, return_train_score=False)
# svm 분류기
from sklearn.svm import SVC
# 하이퍼 파라미터 종류
param_grid = {
'C': [0.1, 1, 10, 100], # 0.1, 1, 10, 100 네 가지 값 시도
'kernel': ['rbf', 'linear'], # rbf, linear 두 가지 커널 시도
'gamma': [0.001, 0.01, 0.1, 1] # 0.001, 0.01, 0.1, 1 네 가지 값 시도
}
# GridSearchCV 객체 생성
grid_search = GridSearchCV(estimator=svm,
param_grid=param_grid,
cv=5,
scoring='accuracy',
verbose=2, # 학습 진행 상황을 좀 더 자세히 보고 싶으면 2나 3으로 설정
n_jobs=-1) # 모든 코어를 사용해서 병렬 처리
# 학습 시작
grid_search.fit(X_train, y_train)
결과 평가(평가 지표, 교차 검증)
머신러닝 모델을 학습하고, 시험 데이터로 예측까지 했다면 과연 우리의 모델이 얼마나 정답을 잘 맞췄는지를 확인해야 합니다.
결과 평가는 회귀와 분류에 따라 다르게 진행해야 합니다.
회귀 모델 평가 (예: 주가 예측, 매출 예측 등)
주요 예측 평가 지표
- MAE(Mean Absolute Error, 평균 절대 오차)
- 예측값과 실제값 차이의 '절대값'을 평균 낸 것. 오차 크기를 직관적으로 알 수 있고, 이상치에 덜 민감함.
- MSE(Mean Squared Error, 평균 제곱 오차)
- 예측값과 실제값 차이의 '제곱'을 평균 낸 것. 오차가 크면 더 큰 패널티를 줘서 모델이 오차를 더 잘 이해할 수 있지만 이상치에 민감함.
- RMSE(Root Mean Squared Error, 제곱근 평균 제곱 오차)
- MSE에 제곱근을 씌운 것. MSE의 단점을 보완해서 MAE와 함께 가장 흔하게 쓰이는 회귀 평가지표 중 하나
- R-squared(결정계수)
- 모델이 실제 변동성을 얼마나 잘 설명하는지 나타내는 지표로 0 ~ 1 사이의 값을 가짐. 예측력이 아닌 설명력을 나타내는 지표로 결정계수가 높다고 무조건 좋은 예측 모델은 아님.
# 회귀 평가 지표 예시 코드
# MAE (Mean Absolute Error, 평균 절대 오차)
mae = mean_absolute_error(y_test, y_pred)
print(f"MAE (평균 절대 오차): {mae:.4f}")
# MSE (Mean Squared Error, 평균 제곱 오차)
mse = mean_squared_error(y_test, y_pred)
print(f"MSE (평균 제곱 오차): {mse:.4f}")
# RMSE (Root Mean Squared Error, 제곱근 평균 제곱 오차)
rmse = np.sqrt(mse) # MSE에 제곱근 씌우기
print(f"RMSE (제곱근 평균 제곱 오차): {rmse:.4f}")
# R2_score (결정계수)
r2 = r2_score(y_test, y_pred)
print(f"R-squared (결정계수): {r2:.4f}")
분류 모델 평가 (예: 스팸 메일 분류, 질병 진단 등)
기본 개념: 혼동 행렬 (Confusion Matrix)🚨
- True Positive (TP): 실제 Positive를 Positive로 예측 (정답)
- True Negative (TN): 실제 Negative를 Negative로 예측 (정답)
- False Positive (FP): 실제 Negative를 Positive로 잘못 예측 (오답) - 1종 오류
- False Negative (FN): 실제 Positive를 Negative로 잘못 예측 (오답) - 2종 오류
주요 분류 평가 지표
- Accuracy (정확도)
- 전체 예측 중 올바르게 예측한 비율: (TP + TN) / (TP + TN + FP + FN)
- 클래스 불균형이 심할 때는 Precision만 확인하는 것이 치명적인 문제가 될 수 있음
- Precision (정밀도)
- Positive로 예측한 것들 중에서 실제로 Positive인 비율: TP / (TP + FP)
- FP를 줄이는게 중요할 때 사용. (예: 스팸 메일 분류)
- Recall (재현율)
- 실제로 Positive 중에서 모델이 Positive로 예측한 비율: TP / (TP + FN)
- FN을 줄이는게 중요할 때 사용: (예: 암 진단)
- F1-Score
- 정밀도(Precision)와 재현율(Recall)의 조화 평균
- 정밀도와 재현율이 균형을 이룰 때 높은 값. 두 지표 모두 중요할 때 사용. (예: 클래스 불균형 모델 평가)
- ROC Curve & AUC
- ROC Curve: 분류 모델의 임계값(Threshold)을 바꿔가며 TP 비율과 FP 비율의 변화를 시각화한 그래프
- AUC: ROC 곡선 아래의 면적. 1에 가까울 수록 모델 성능이 높다고 평가
# 분류 평가 예시 코드
# 정확도 (Accuracy)
accuracy = accuracy_score(y_test, y_pred)
print(f"정확도 (Accuracy): {accuracy:.4f}")
# 정밀도 (Precision) - 이진 분류의 경우 pos_label=1 (양성 클래스) 지정
precision = precision_score(y_test, y_pred, pos_label=1)
print(f"정밀도 (Precision): {precision:.4f}")
# 재현율 (Recall)
recall = recall_score(y_test, y_pred, pos_label=1)
print(f"재현율 (Recall): {recall:.4f}")
# F1-Score
f1 = f1_score(y_test, y_pred, pos_label=1)
print(f"F1-Score: {f1:.4f}")
마치며
https://www.kaggle.com/code/vishalyo990/prediction-of-quality-of-wine
Prediction of quality of Wine
Explore and run machine learning code with Kaggle Notebooks | Using data from Red Wine Quality
www.kaggle.com
https://www.kaggle.com/code/mariapushkareva/medical-insurance-cost-with-linear-regression
Medical Insurance Cost with Linear Regression
Explore and run machine learning code with Kaggle Notebooks | Using data from Medical Cost Personal Datasets
www.kaggle.com
위 두 글은 SGD classifier, linear regression, ridge regression, lasso regression, random forest regressor로 와인 등급과 의료 보험료를 예측하는 간단한 모델을 만드는 글입니다. 전체 프로세스가 간단해 보이지만 캐글에서 투표를 2000, 1200개 넘게 받은 머신러닝 분류, 회귀 분석의 교과서와 같은 글입니다.
처음부터 끝까지 차근차근 보시면 알겠지만 튜터님이 알려주시고, 제가 가이드해드린 방법과 일치합니다. 이처럼 머신러닝 프로세스는 정형화 되어 있습니다. 머신러닝을 처음 접하시는 분들이 모델링을 어려워 하시는 이유는 낯설어서입니다. 기본 프로세스는 정형화되어 있어 체계적으로 접근하면 충분히 배울 수 있습니다. 공부한다면 전공자, 비전공자 누구나 할 수 있습니다. 후에 배울 딥러닝도 마찬가지 입니다. Pytorch로 모델을 구현하는 것도 dataset 생성 -> dataloader 생성 -> 모델 구현 -> main에서 학습 진행 이 4단계가 핵심 흐름입니다. 그러니 어렵다고 낙담하지 않길 바라겠습니다. 추가로, 혼자 머신러닝을 깊게 공부하고 싶으신 분들은 '혼자 공부하는 머신러닝' 책을 추천 드립니다.
비전공자로서 인공지능을 공부할 때, 가장 고생스러운 점은 파편화된 지식을 찾아가야 한다는 것입니다. 다른 분들은 덜 고생하시길 바라는 마음으로 본 글을 작성했습니다.
긴 글 읽어주셔서 감사합니다.