오전에는 현직자 강의를 들었습니다.
모두의 연구소 소장님께서 AI의 역사와 발전과정, 그리고 그 안에서 어떻게 대처해야 하는지에
대해 들어보는 시간이었습니다.
발전 과정이 정말 빨라서 현재 나와있는 모델 확인하기도 어렵다는 말이 참 무섭다는 생각이 듭니다.
뉴스에서 나오는 반도체 발전에 대해 나올때마다 흠칫 놀랍니다.
항상 하드웨어가 소프트웨어 발전에 따라가지 못하는 느낌인데 하드웨어가 발전해서 보완이 된다면
어떤 수준의 AI가 나올지 무서워 집니다. 진짜 자비스 나오는거 아니에요?^^


현직자 강의후 바로 머신러닝을 배웠습니다.
어렵네요. 전체적으로 크게 이해하려고 노력은 했지만 나열되는 수치들의 중요성도 잘 모르기도 하고,
통계 관련하여 지식이 부족하다 보니 핵심을 이해하지 못하는 느낌이 들었습니다.
좀 아쉽지만 과정이 머신러닝을 주로 배우는 과정은 아니니까요.
아쉬움을 뒤로하고 배운걸 정리해 보도록 하겠습니다.


머신러닝

1. 데이터 import

from sklearn.datasets import fetch_openml

# fetch_openml을 사용하여 타이타닉 데이터셋을 불러옵니다.
titanic = fetch_openml('titanic', version=1, as_frame=True)

# 데이터 프레임으로 변환
titanic_data = titanic.frame

실습데이터를 불러왔습니다. 타이타닉 데이터입니다.
잘 몰랐지만 데이터 처리할때 연습용으로 많이 사용하는 데이터셋 이라고 합니다.

2. 데이터 확인

print(titanic_data.head())

데이터의 처음 5행을 출력하여 데이터를 살펴봅니다.

print(titanic_data.describe())

데이터셋의 통계적 요약을 출력해 봅니다.

print(titanic_data.isnull().sum())
결과출력

pclass          0
survived        0
name            0
sex             0
age           263
sibsp           0
parch           0
ticket          0
fare            1
cabin        1014
embarked        2
boat          823
body         1188
home.dest     564
dtype: int64

결측치가 있는지 확인합니다.

print(titanic_data.info())

info() 통해서 결측치를 확인할수도 있습니다.


3. 데이터전처리 - 결측치 처리

import pandas as pd

# 예시: 나이 결측치 처리 - ['age'] 변수의 평균값으로 null값을 채워라
titanic_data['age'].fillna(titanic_data['age'].mean(), inplace=True)    # inplace=True 기존 데이터를 업데이트 해라


# 추가 : 새로운 변수는 없을까? 라는 고민도 필요하다. name과 cabin 가지고 변수 추가 가능할 수도 있다.
# name 에 master 있고, cabin(선실) 에서 비싼 C 등급 객실에 투숙했다면 귀족으로 추측할 수 있다.

# 성별 인코딩 - 남자는 0, 여자는 1 로 수정해서 데이터 처리가 용이하게 함.
titanic_data['sex'] = titanic_data['sex'].map({'male': 0, 'female': 1})

# 불필요한 특성 제거 - 숫자만 가지고 처리할 것이기 때문에 불필요한 컬럼(열)에 대해 제거함
titanic_data = titanic_data.drop(['name', 'ticket', 'cabin', 'embarked', 'boat', 'body', 'home.dest'], axis=1)

위에서 age컬럼에 결측치가 많은걸 알 수 있었습니다.
titanic_data['age'].fillna(titanic_data['age'].mean(), inplace=True) 이 문장에서 ['age'] 변수의 평균값으로
null 값을 채웁니다. 나이이다 보니 나이가 없는 null 인 경우 컬럼내의 나이 평균을 그 나이 컬럼에 넣어주는 것으로
결측치를 처리했습니다.
결국은 선택을 해야 하는 것입니다.

첫번째
age 에서 250 여개의 결측치 만약에 이 데이터를 다 제거했을 경우 1300개에서 20% 데이터 손실이 발생합니다.
모델성능도 떨어질 수 있음을 감안해야 합니다.

두번째
age 에서 250개를 살렸을때 -> 값을 모르지만 위처럼 상태 대체했다면 1300개 다 쓸 수 있으니 데이터는 확보됩니다.
하지만 신빙성이 떨어질 수 있음을 감안해야 합니다.

준지도학습 으로 다른 데이터에 기반해서 추정해보자! 이렇게 선택할 수도 있습니다.

print(titanic_data.info())
출력결과

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1309 entries, 0 to 1308
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype   
---  ------    --------------  -----   
 0   pclass    1309 non-null   float64 
 1   survived  1309 non-null   category
 2   sex       1309 non-null   category
 3   age       1309 non-null   float64 
 4   sibsp     1309 non-null   float64 
 5   parch     1309 non-null   float64 
 6   fare      1308 non-null   float64 
dtypes: category(2), float64(5)
memory usage: 54.1 KB
None

fare 컬럼에 결측치가 한개가 있습니다.

titanic_data['fare'].fillna(titanic_data['fare'].mean(), inplace=True) 

fare 결측치 1개도 평균값으로 채웠습니다. inplace=True는 원본을 수정합니다.^^
다시 print(titanic_data.info()) 출력해보면 위의 출력결과에서 6 fare 1308 non-null float64
1308 이 1309로 변경됐음을 확인할 수 있습니다.
데이터가 전체적으로 정리되어야 추후 진행이 가능합니다.
전처리가 중요합니다. 정확한 데이터를 위한 방안을 고민해야 합니다.
위처럼 평균으로 넣지 말고, 빼는 것도 방법이 될 수 있습니다. 물론 모델 성능이 떨어지는 것도
감안해야겠지요. 데이터가 더 많다면 빼는 것도 고려할 수 있을 것 같습니다.


4. 데이터 분할

from sklearn.model_selection import train_test_split

# 입력 변수와 타겟 변수 분리
X = titanic_data.drop(['survived'], axis=1) # 'survived' 컬럼 제외 # X에는 타겟이 있으면 안됨
y = titanic_data['survived'] # 타겟 변수    # y에만 정답이 있어야 한다.

# 데이터 분할: 훈련 세트 80%, 테스트 세트 20%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)   # random_state -> 고정된 값으로 분할이 가능, 사용하지 않으면 출력시 마다 값이 달라질 수 있음.

['survived'] 타겟 변수입니다. 결국 어떤 조건의 사람들이 살아남았는지 알고 싶은 거니까요.
훈련세트 X_train, y_train 과 테스트세트 X_test와 y_test를 위해 80%, 20%로 데이터를 분할합니다.
random_state는 고정값을 주는 것이 좋다고 하네요. chatGPT 에게 물어봤더니 결과의 재현성을 보장하기 위해서 라고 합니다.
머신러닝 알고리즘은 종종 무작위성을 포함하고 있어서 이 무작위성이 데이터셋의 분할, 초기화, 알고리즘 내부의 결정에
영향을 미칠 수 있다고 합니다. random_state 를 고정하고, 값을 변경해가면서 여러번 실험하는 것이 모델의 일반화 능력을
더 잘 평가 할 수 있다고 합니다.


5. 알고리즘 선택! - 베이스라인 모델

from sklearn.ensemble import RandomForestClassifier

# 랜덤 포레스트 분류기 인스턴스 생성
model = RandomForestClassifier(random_state=42)

모델 선택도 중요하지만 전처리가 그만큼 더 중요합니다. 모델을 어떻게 보면 정답같은 모델이 있습니다.
여기서 사용된 랜덤포레스트 분류기는 강력한 머신러닝 알고리즘 중 하나로 여러 개의 결정 트리를 조합하여
사용한다고 합니다. 여러 개의 작은 모델(결정 트리)이 모여 전체적인 예측 성능을 향상시키는 방식으로 작동합니다.

랜덤 포레스트의 작동 방식

# 각 트리는 데이터의 무작위 선택된 서브셋으로 훈련됩니다.
# 분할 시, 각 노드는 모든 특징이 아닌 무작위로 선택된 일부 특징을 기반으로 최적의 분할을 찾습니다.
# 예측 시, 개별 트리의 예측 결과를 집계(예: 다수결)하여 최종 예측을 결정합니다.



6. 학습

model.fit(X_train, y_train)

학습은 그냥 이겁니다. 모델 선택하고, 트레이닝하려고 분할한 데이터셋 넣어주면 끝입니다.
데이터가 진실되고, 전처리가 잘 되어 있는것이 그래서 중요하다고 하신 것 같습니다.


7. 예측

predictions = model.predict(X_test)

테스트 데이터를 가지고 predict(예측) 합니다. 예측도 학습과 마찬가지로 간단합니다.

predictions    # 테스트데이터 가지고 예측해서 살아남은 사람은 1로 표기했습니다.
출력결과
array(['0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '0', '0', '0',
       '0', '0', '1', '0', '0', '1', '0', '1', '0', '0', '1', '1', '1',
       '0', '1', '0', '1', '0', '1', '1', '1', '0', '0', '1', '0', '1',
       '0', '1', '0', '1', '0', '0', '0', '0', '0', '1', '1', '1', '0',
       '1', '1', '1', '0', '0', '0', '0', '0', '0', '0', '1', '0', '1',
       '1', '1', '0', '0', '0', '0', '0', '0', '0', '1', '0', '0', '1',
       '0', '1', '0', '0', '0', '1', '0', '0', '0', '1', '1', '1', '1',
       '0', '1', '0', '0', '0', '0', '1', '0', '1', '0', '0', '1', '1',
       '0', '0', '0', '0', '1', '1', '0', '0', '0', '1', '1', '0', '0',
       '1', '1', '1', '0', '0', '0', '1', '0', '1', '0', '1', '1', '0',
       '1', '1', '0', '0', '0', '1', '0', '0', '0', '1', '0', '0', '0',
       '1', '0', '1', '0', '0', '1', '0', '0', '0', '0', '0', '0', '0',
       '1', '0', '0', '0', '0', '1', '0', '0', '1', '1', '0', '1', '0',
       '1', '1', '1', '1', '0', '1', '0', '1', '0', '1', '0', '1', '1',
       '0', '1', '0', '1', '0', '1', '0', '1', '0', '0', '0', '0', '0',
       '0', '0', '0', '1', '1', '0', '0', '0', '0', '1', '0', '1', '0',
       '0', '0', '0', '1', '0', '1', '0', '1', '1', '0', '1', '0', '1',
       '1', '1', '0', '0', '1', '0', '1', '0', '0', '0', '1', '0', '0',
       '0', '1', '0', '0', '0', '0', '0', '1', '0', '0', '0', '0', '0',
       '0', '0', '0', '0', '0', '1', '1', '0', '0', '1', '1', '1', '0',
       '0', '1'], dtype=object)



8. 평가

from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, predictions)  # 실제 값과 예측값을 비교해줘
print(f"Accuracy: {accuracy}")  # 두 데이터가 얼마나 일치하는지 점검

실제 값과 예측값을 비교합니다. 두 데이터가 얼마나 일치하는지 점검하는게 정확도 검사입니다.
생존여부(1/0) -> 분류 -> 이진 분류 -> 맞는지 틀리는지 정확도 볼 수 있습니다.

실행결과
Accuracy: 0.7862595419847328

약 78% 정확도입니다.^^
결과를 확인하고 나면 두가지 방법이 있다고 합니다.

첫번째
78% 결과가 마음에 안들어서 90%까지 올려야겠다고 판단한다면 (자의적이든 타의적이든)
EDA(전처리)로 돌아가서 처음부터 다시 진행합니다.

두번째
이 성능에 만족하니까 하이퍼파라미터 튜닝을 진행합니다.
전처리로 여러번 돌아가서 진행하고, 확정났을때 마지막에 보통 한다고 합니다.


import matplotlib.pyplot as plt

# 변수 중요도 추출
feature_importances = model.feature_importances_

# 변수 이름과 중요도를 매핑
feature_names = X_train.columns
feature_importance_dict = dict(zip(feature_names, feature_importances))

# 중요도에 따라 변수 정렬
sorted_importance = sorted(feature_importance_dict.items(), key=lambda x: x[1], reverse=True)

# 중요도 시각화
plt.figure(figsize=(10, 8))
plt.barh([item[0] for item in sorted_importance], [item[1] for item in sorted_importance])
plt.xlabel('Importance')
plt.ylabel('Feature')
plt.title('Feature Importances in Random Forest Model')
plt.gca().invert_yaxis() # 높은 중요도가 위로 오게 정렬
plt.show()


결과가 이렇게 나오게된 중요한 변수를 출력해볼 수 있습니다.


9. 튜닝

from sklearn.model_selection import GridSearchCV

# param_grid = {'n_estimators': [50, 100, 200], 'max_depth': [5, 10, 20]}
param_grid = {'n_estimators': [50, 100], 'max_depth': [5, 10]}  # 1000 ~ 2000번 경우의 수를 구비!
grid_search = GridSearchCV(model, param_grid, cv=5)
grid_search.fit(X_train, y_train)

다시 한번 다른 모델로 학습합니다.

predictions_grid = grid_search.predict(X_test)

다시 한번 테스트 데이터를 가지고 predict(예측) 합니다.


# 재 평가
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, predictions_grid)  # 실제 값과 예측값을 비교해줘
print(f"Accuracy: {accuracy}")  # 두 데이터가 얼마나 일치하는지 점검

재평가 합니다. 똑같이 데이터가 실제 데이터와 얼마나 일치하는지 체크합니다.

출력결과
Accuracy: 0.7748091603053435

77% 더 안좋아졌네요. 전처리과정의 변화 없이 모델만 바꿔본거니 비슷했던거 같습니다.

# 머신러닝을 할때 고려해야 할 것
# 1. 기존보다 성능이 좋아졌는가?(중요) -> 무엇을 하던 간에 기존보다 좋아야 성과로 이어진다.
# 2. 변수 중요도는 고르게 잘 구성되어있는가?
# 3. 새로운 변수로 사용할만한 것은 없는가?
# 4. 모델 비교는 충분하게 했는가?
# 5. 과적합을 발생하지 않았는가?    -> 중요문제중에.. 학습한 데이터와 평가한 데이터와의 갭이 많이 발생할 경우.
#    학습한 데이터에서는 97%, 평가한 데이터는 78%.. 내신은 상위 3% 인데 수능은 상위 22%?
#    내부적으로 학습은 잘 했지만 외부에서는 그러지 못할때 과적합 이라고 한다.
#    학습된 데이터만 잘 맞추는 것! / 그 외의 데이터는 잘 맞추지 못했다.
#    보통 학습과 평가간의 10% 이상 오차가 나면 과적합 이라고 한다. 딱 정해진건 아니다.
#    최대한 일치를 해야 한다.
# 6. Train과 Test간 성능차이가 10% 이상 차이는 나지 않는가?
# 7. 모델에 대한 설명이 가능한가? -> 한국 법 때문에 고객에게 설명이 불가능한 머신러닝 모델을 사용할 수 없다.

일단 여기까지 머신러닝의 기본 과정을 순서대로 정리해봤습니다.
수업시간에는 선형회귀와 로지스틱 회귀까지 배워서 실행해봤습니다.
여기에 정리하기는 코드나 결과 양도 많고, 제가 잘 모르겠습니다.^^

간단하게 정리해보면
선형회귀는 독립변수와 종속변수 사이의 선형적 관계를 찾는 방법입니다.
주어진 데이터에 가장 잘 맞는 직선을 찾고, 실제 값과 예측 값의 차이, 오차를 최소화하는 방향으로 설정됩니다.
데이터의 분포 안정화를 위해 극단적인 값을 갖는 이상치의 영향을 줄이기 위해 로그변환을 사용했습니다.
상관관계 분석을 위해 히트맵 출력을 하기도 했는데 대표적으로 말씀하신 주의 사항은
데이터에서 상관성이 있을 수 있는 것을 인과 관계인것 처럼 받아들이면 안된다는 것입니다.
실제 이렇게 발표가 되는 경우도 많다고 하셨습니다.
예로 초콜렛을 먹었는데 성적이 올라간 학생들이 많았어요. 일때
늦게 까지 공부하다 야식으로 초콜렛을 먹게된 학생들이 공부를 열심히 햇으니 성적이 올라갔을 수도 있는데
이것을 실제 초콜렛을 먹어서 성적이 올랐다고 판단할 수 있다는 것입니다. 재밌네요^^

로지스틱 회귀는 많이 사용한다고 합니다.
결정적으로 우리나라 법이 머신러닝을 사용하더라도 결과가 왜 그렇게 나오는지에 대해 설명해야 하는 의무가
있는데 실제 다양한 머신러닝 모델들은 설명하기 어렵다고 합니다.
이런 저런 판단을 한 트리갯수가 몇천개가 될 수 있는데 그걸 하나하나 설명할 수가 없는 거죠.
그래서 로지스틱회귀를 많이 쓴다고 합니다. 실제 이진분류를 하는 알고리즘입니다.
성능을 좀 포기하더라도 설명을 잘 할 수 있는 장점이 있다고 합니다.
하지만 이진 분류 이다 보니 1과 0으로 밖에 분류하지 못했는데 시그노이드 함수를 통해
1에 가까운지 0에 가까운지 확률을 구할 수 있게 되었다고 합니다.


데이터의 타겟이 약 20%로 100개 중에 20개가 타겟이라고 하면 인간이 처리를 해도
100개중에 20개는 처리할 수 있는 20%의 정확도를 가지고 있지만 이를 오즈비로 계산했을때
맞을수 있는 확률을 80%로 설정했다면 오즈비가 4.0으로 사람이 처리한 것보다 기계가
처리한 것이 4배가 성능이 좋다고 말할 수 있습니다.
로지스틱회귀에서는 오즈비를 구하는게 중요하다고 합니다.
직관적으로 알 수 있어서 그런 것 같습니다.


여기까지 입니다. 강사님이 표현하신 전문적인 용어나 언어가 아닌 강의를 들으면서
제가 이해할 수 있는 언어로 쓰다보니 틀리고, 맞지 않는 부분이 있을 것 같습니다.
저에겐 어려운 강의였고, 최대한 이해해 보려고 노력했습니다.
쉽지 않지만 이렇게 알아가는 거겠지요.


내일은 딥러닝.. 제가 계속 할 수 있겠지요? 화이팅!! 입니다.