no image
[Python] 회귀 - 비선형 회귀
1) 개요- 회귀 계수의 결합이 비선형인 경우다. - 회귀 함수를 기반으로 하지 않는다. 2) KNN (K-Nearest Neighbors) 회귀- 새로운 데이터가 들어왔을 때 가장 가까운 이웃 몇 개를 찾아서 그 이웃의 데이터로분류는 투표를 해서 다수결로 타겟 결정회귀는 평균을 이용해서 타겟 결정- 가중 회귀 일반적인 경우 현재 위치에서 거리가 가장 가까운 3개를 추출했을 때 A는 거리가 3.2인데 5 B는 거리가 11.5인데 6.8 C는 거리가 1.1인데 값은 9.0 (5 + 6.8 + 9.0) / 3 = 약 6.9 거리에 따라서 가중치를 부여하는 방식 (5/3.2 + 6.8/11.5 + 9.0/1.1) / (1/3.2 + 1/11.5 + 1/1.1) KNN을 생성할 때 weights 매개변수에 di..
2024.03.07
no image
[Python] 분류
1. 분류- 데이터를 가지고 어떤 결정을 해야 하는 문제를 접하는 경우, 결정해야 하는 Target이 이미 알려진 범주형일 때 이미 알려져있으므로 지도학습 - 분류의 유형은 이진분류(2가지 중 하나)와 다중분류(3가지 이상 중 하나)로 분류하기도 하고, 선형분류와 비선형분류와 나누기도 함 - sklearn의 분류기들은 예측하기 위한 함수로 2가지 사용predict: 분류 결과predict_proba: 각 클래스에 대한 확률(확률이 가장 높은 결과가 predict의 결과 1-1) 분류 알고리즘- 판별 분석 - 랜덤 분류 - KNN - Support Vector Machine - 나이브 베이즈 - 로지스틱 회귀 - 결정 트리 - 최소 근접 - 신경망 - 앙상블 2. MNIST 데이터- 0부터 9까지의 숫자 ..
2024.03.07
no image
[Python] 머신러닝
1. 인공지능- 지능 : 문제를 해결할 수 있는 능력환자를 보고 병을 진단- 인공지능: 지능 작업을 수행할 수 있는 기계의 능력환자에 대한 정보를 입력하면 컴퓨터가 병을 진단- 구현 방법: 지식 공학(전문가 시스템): 문제 해결을 위한 알고리즘을 사람이 작성전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결컴퓨터의 역할은 결과를 만들어내는 것- Machine LearningData와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식 인공지능과 머신러닝의 관계- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습 - 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임..
2024.03.07
no image
[Python] 지도학습 연습 _ 자전거 대여 수요 예측
1. Kaggle 자전거 대여 수요 예측 - 예측 모델 및 분석 대회 플랫폼 - 현재는 구글이 운영 - url - https://www.kaggle.com/c/bike-sharing-demand Bike Sharing Demand | Kaggle www.kaggle.com - 미션: 날짜, 계절, 근무일 여부, 온도, 체감온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측 - 유형: 회귀 - 평가 지표: RMSLE sampleSubmission.csv 참고 실제 데이터 확인 * test 데이터에 없는 train 데이터의 피처는 삭제 해야함 2. 데이터 탐색 # 데이터 가져오기 import pandas as pd train = pd.read_csv('./bike/train.csv') test = p..
2024.03.06
no image
[Python] 추론 통계
1. 공통 코드 from pathlib import Path import random import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib import font_manager, rc import platform import matplotlib import seaborn as sns import scipy as sp from scipy import stats import statsmodels.api as sm import statsmodels.formula.api as smf from statsmodels.stats import power %matplotlib inline path = "c:/Windows..
2024.02.27
no image
[Python] 벡터 연산에서 기억할 부분
1. 데이터 유형 1) Scala - 하나의 값: 정수와 실수 그리고 하나의 글자나 단어 (자연어 처리) 2) Vector - 0개 이상의 값 - Vector 분류 Vector: 1차원 배열 (Record) Matrix: 벡터가 모여서 만들어진 이차원 배열 - Table 머신러닝의 기본 단위 Tensor: Matrix의 배열로 삼차원 배열 딥러닝의 기본 단위 from sklearn.datasets import load_iris iris = load_iris() #이차원 배열이라 iris.data는 Matrix print(iris.data.shape) #첫번째 행의 붓꽃 데이터 - 배열이므로 Vector print(iris.data[0, :]) (150, 4) [5.1 3.5 1.4 0.2] - 하나의 행..
2024.02.27
[Python] 샘플링 _ 표본 추출
1. 전수조사와 표본조사 - 전수조사 모집단 내에 있는 모든 대상을 조사하는 방법 모집단의 특성을 정확히 반영하지만 시간과 비용이 많이 소모됨 - 표본조사 모집단으로부터 추출된 표본을 대상으로 분석 실시 전수조사의 단점을 보완하지만 모집단의 특성을 반영하는 표본이 제대로 추출되지 못하면 수집된 자료가 무용지물 2. 용어 - Sample: 큰 데이터 집합으로부터 얻은 부분집합 - Population: 데이터 집합을 구성하는 전체 - N(n): 모집단의 크기 - 랜덤 표본추출: 무작위로 표본을 추출하는 것 층화 랜덤 표본추출: 모집단을 여러개의 층으로 나누고 각 층에서 무작위로 표본 추출 단순 랜덤 표본추출: 층화 없이 단순하게 무작위로 표본 추출 - 표본 편항 (Sample Bias): 모집단을 잘못 대표..
2024.02.26
no image
[Python] 확률 분포 모형
1. 확률적 데이터와 확률 변수 1-1) 확률적 데이터 - 결정론적 데이터: 실험, 측정, 조사 등을 통해 어떤 데이터 값을 반복적으로 얻을 때 누가 언제 얻더라도 항상 동일한 값이 나오는 것 - 확률적 데이터: 정확히 예측할 수 없는 값 (ex. 혈압) - 대다수 데이터는 확률적 데이터다. 여러 조건이나 상황에 따라 데이터 값이 변할 수 있고 측정 시에 발생하는 오차 때문! 1-2) 분포 - 확률적 데이터를 살펴보면 어떤 값은 자주 등장하고 어떤 값은 드물게 등장하는데 어떤 값이 자주 나오고 어떤 값이 드물게 나오는지를 나타내는 정보를 의미 - 범주형 데이터 - 교차분할표, plot으로 확인 - 연속형 데이터 - binning 후 히스토그램 등으로 확인 1-3) 분포 추정을 위한 기술 통계값 - 평균,..
2024.02.26

1) 개요

- 회귀 계수의 결합이 비선형인 경우다. 
- 회귀 함수를 기반으로 하지 않는다.
 
 

2) KNN (K-Nearest Neighbors) 회귀

- 새로운 데이터가 들어왔을 때 가장 가까운 이웃 몇 개를 찾아서 그 이웃의 데이터로

  • 분류는 투표를 해서 다수결로 타겟 결정
  • 회귀는 평균을 이용해서 타겟 결정

- 가중 회귀

  • 일반적인 경우
    • 현재 위치에서 거리가 가장 가까운 3개를 추출했을 때
      A는 거리가 3.2인데 5
      B는 거리가 11.5인데 6.8
      C는 거리가 1.1인데 값은 9.0
    • (5 + 6.8 + 9.0) / 3 = 약 6.9
  • 거리에 따라서 가중치를 부여하는 방식
    • (5/3.2 + 6.8/11.5 + 9.0/1.1) / (1/3.2 + 1/11.5 + 1/1.1)  
  • KNN을 생성할 때 weights 매개변수에 distance를 설정하면 거리에 따른 가중치 적용

- KNN은 게으른 알고리즘

  • 데이터를 예측하기 위해서 모든 데이터가 메모리에 상주해야 하는 알고리즘

- 이해하기 쉬운 화이트 박스 알고리즘이고 별도의 훈련을 거치지 않는 간단한 알고리즘이라서 데이터 전처리(결측치 대체나 새로운 피처 추가)에 많이 이용 

  • 결측치나 새로운 피처 추가: 지역적 정보를 이용하기 때문에 다중공선성 문제는 고려할 필요가 없음. 전체 데이터를 사용하는게 아니라서 실제 예측하는 데는 사용하지 않음.
  • 화이트 박스 알고리즘: 내가 보고 이해하기 쉬운 알고리즘, 대표적으로 KNN과 트리 / 블랙박스 알고리즘: 딥러닝

- API는 sklearn.neighbors.KNeighborsRegressor

.
 

 
 
- KNN을 사용할 때는 스케일링 반드시 수행
 
 
 
 
 

3) Decision Tree

- 트리 기반
- 불순도 지표

  • 지니계수 (오분류 오차)
    • 1 - (예측한 개수 / 전체 개수 - (예측한 개수 / 전체 개수) ...
      한쪽으로만 분류가 되면 0: 가장 좋은 형태
      가장 나쁜 값이 0.5
  • 엔트로피

- API: DecisionTreeRegressor
- RandomForest나 GBM, XGBoost, LightGBM의 기반이 되는 모델
- 다양한 파라미터가 존재하기 때문에 하이퍼 파라미터 튜닝 필수
- 스케일링 과정은 필요 없음

m = 200
X = np.random.rand(m, 1)
y = 4 * (X - 0.5) ** 2 + np.random.randn(m, 1)/10

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg.fit(X, y)
#시각화
from graphviz import Source
from sklearn.tree import export_graphviz

#decision_tree_dot 이라는 파일로 저장
export_graphviz(tree_reg, out_file='decision_tree.dot', 
               feature_names=['X'], class_names=['y'], 
               rounded=True, filled=True)

#파일을 출력
with open('decision_tree.dot') as f:
    dot_graph = f.read()
    
src = Source(dot_graph)
src

 

tree_reg = DecisionTreeRegressor(max_depth=12, random_state=42)
tree_reg.fit(X_data, y_target)

#피처의 중요도
print(tree_reg.feature_importances_)
[0.07125588 0.00142092 0.00421452 0.00076625 0.0256092  0.57625877
 0.01265629 0.07238728 0.00095654 0.01204435 0.02101767 0.00643705
 0.1949753 ]

 
 
 

4) SVM  (Support Vector Machine)

- 분류에서는 가장 가까운 데이터와의 거리가 가장 먼 결정 경계를 만들어가는 방식
- 회귀에서는 제한된 마진 오류 안에서 가능한 많은 샘플이 들어가도록 학습

  • 마진 오류의 폭은 epsilion 파라미터로 설정
#시드 고정 후 가우시안 분포를 따르는 데이터셋을 만듭니다.
np.random.seed(42)
m = 50
X = 2 * np.random.rand(m,1)
y = (4 + 3 * X + np.random.randn(m,1)).ravel()

X_train = X[:40]
X_test = X[40:]

y_train = y[:40]
y_test = y[40:]

 
 
 

# 모델 생성 과 훈련
from sklearn.svm import LinearSVR

epsilons = [0.1, 0.5, 1.0, 1.5, 2.0]
for epsilon in epsilons: 
    svm_reg = LinearSVR(epsilon=1.5, random_state=42)
    svm_reg.fit(X_train,y_train)

# svm_reg = 마진이 큰 모형(epsilon=1.5)

    y_pred = svm_reg.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    rmse = np.sqrt(mse)
    print(epsilon, ":", rmse)
  • 별로 좋아지지는 않는다.

 
 
 
 

[Python] 분류

0ㅑ채
|2024. 3. 7. 14:00

1. 분류

- 데이터를 가지고 어떤 결정을 해야 하는 문제를 접하는 경우, 결정해야 하는 Target이 이미 알려진 범주형일 때
이미 알려져있으므로 지도학습
- 분류의 유형은 이진분류(2가지 중 하나)와 다중분류(3가지 이상 중 하나)로 분류하기도 하고, 선형분류와 비선형분류와 나누기도 함
- sklearn의 분류기들은 예측하기 위한 함수로 2가지 사용

  • predict: 분류 결과
  • predict_proba: 각 클래스에 대한 확률(확률이 가장 높은 결과가 predict의 결과

 

1-1) 분류 알고리즘

- 판별 분석
- 랜덤 분류
- KNN
- Support Vector Machine
- 나이브 베이즈
- 로지스틱 회귀
- 결정 트리
- 최소 근접
- 신경망
- 앙상블
 
 

2. MNIST 데이터

- 0부터 9까지의 숫자 이미지 70,000개로 이루어진 데이터, 레이블이 존재
- sklearn을 이용해서 다운로드 받으면 data 속성에 피처가 존재하고 target 속성으로 레이블을 제공
 

1-1) MNIST 데이터 가져오기

from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784', version=1, as_frame=False)

X, y = mnist['data'], mnist['target']

print(X.shape)
print(y.shape)
(70000, 784)
(70000,)
  • 이미지가 7만개 존재, 각 이미지에는 784개의 특성 존재
  • 특성은 픽셀의 개수 (28x28)

#이미지 출력

#이미지 데이터 1개 가져오기
some_digit = X[0]

#이미지를 다시 2차원으로 변경
some_digit_image = some_digit.reshape(28, 28)

#출력
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis('off')
plt.show()

 
 

1-2) 훈련 데이터와 테스트 데이터 분리

print(y.dtype)
object
  • 정수로 형변환
#레이블 자료형을 정수로 변경
y = y.astype(np.uint8)
uint8

 
 

3. 이진 분류

3-1) 이진 분류를 위한 데이터 준비

- 타겟이 True 또는 False
- 이진 분류는 맞다 틀리다를 구분하는 것
- 레이블의 값을 True와 False로 생성

  • 5가 맞는지 여부로 레이블 수정

# 이진 분류를 위한 레이블 생성

y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)
print(y_train_5[:10])
print(y_test_5[:10])
[ True False False False False False False False False False]
[False False False False False False False False  True False]

 
 

3-2) sklearn.linear_model 의 SGDClassifier

- 확률적 경사 하강법 (Stochastic Gradient Descent -  SGD) 사용하는 분류 모델 클래스

  • 타겟을 찾아갈 때 한번에 찾아가지 않고 학습률 값을 이용해서 작게 분할해서 타겟을 찾아가는 방식

- 매우 큰 데이터 세트를 효율적으로 처리
- 한번에 하나씩 훈련 샘플을 독립적으로 처리하기 때문에 온라인 학습에 적합

  • 온라인 학습: 실시간으로 데이터가 주어지는 형태의 학습 ↔ 배치 학습

- 하이퍼 파라미터

  • max_iter: 최대 수행 횟수
  • tol: 중지기준. 이 값보다 손실의 값이 작으면 훈련 중지 (어느정도까지 틀릴 것인지?)
  • random_state

# SGDClassifier를 이용한 훈련

#모델 생성 및 학습
from sklearn.linear_model import SGDClassifier

#max_iter는 작업 횟수이고 tol은 중지 기준으로 loss가 tol 보다 작으면 훈련 중지  random_state는 seed 값
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=42)
#훈련
sgd_clf.fit(X_train, y_train_5)
#첫 번째 데이터를 이용해 이미지 감지\
sgd_clf.predict([some_digit])
array([ True])

 
 

3-3) 분류의 평가 지표

- 오차 행렬: 정답이 True와 False로 나누어져 있고 분류 결과도 True와 False로 나누어서 표로 만든 형태

  • True Positive(TP) : 실제 True인 정답을 True라고 예측 (정답)
  • False Positive(FP) : 실제 False인 정답을 True라고 예측 (오답)
  • False Negative(FN) : 실제 True인 정답을 False라고 예측 (오답)
  • True Negative(TN) : 실제 False인 정답을 False라고 예측 (정답)

- confusion_matrix 함수를 이용해서 오차행렬 생성

  • 타겟 클래스의 레이블과 예측 클래스의 레이블을 대입
  • 예측 클래스의 레이블은 cross_val_predict(분류기, 피처, 레이블, cv=교차검증횟수) 호출

#이전 데이터와 분류기 이용해서 오차행렬 생성

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

#예측값 생성
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
#오차행렬 생성
confusion_matrix(y_train_5, y_train_pred)
array([[53892,   687],
       [ 1891,  3530]], dtype=int64)
  • 53892는 5가 아닌 것으로 제대로 분류
  • 687는 5라고 잘못 분류한 것
  • 1891은 5 이미지가 맞는데 5가 아니라고 분류한 개수
  • 3550은 5라고 맞게 분류한 데이터

- Accuracy (정확도)

  • True를 True로, False를 False로 예측한 비율
  • 옳은 경우를 고려하는 지표
  • 정확도는 가장 직관적으로 모델의 성능을 평가할 수 있는 지표
  • 정확도를 사용할 때는 타겟의 비율을 확인해야 함
    • 타겟의 비율이 어느정도 고르게 분포되어 있다면 고려할 수 있는 평가지표이지만,
      타겟의 비율이 고르지 않다면 다른지표를 사용해야할 수 있다.
    • 어느 곳의 날씨가 99일은 맑고 1일은 비가 온다면, 무조건 맑다고 예측할 때 정확도가 99%가 된다.

- Recall (재현율)

  • True를 True라고 예측한 비율
    • 실제 날씨가 맑은데 맑다고 예측한 비율
  • sensitivity(민감도) 또는 hit rate 라고도 한다.

- Precision (정밀도)

  • True라고 예측한 것 중에서 실제 True인 것의 비율
    • 날씨가 맑다고 예측했는데 실제로 맑은 비율

 

  • 정밀도와 재현율은 같이 사용하는 경우가 많다.

- F1 Score

  • Precision과 Recall의 조화 평균
  • Target의 비율이 불균형 구조일 때 모델의 성능 지표로 많이 이용 

- sklearn.metrics에서 지표계산 API 제공

  • 대입해야 하는 데이터: 실제 값과 예측한 값의 배열

- 보통의 경우 F1 Score가 좋으면 성능이 좋은 분류기라고 하지만, 상황에 따라서는 다른 지표 사용 고려

  • 감시 카메라를 이용해서 도둑을 잡아내는 분류기를 훈련시킨다고 하면 정확도가 낮더라도 재현율이 좋은 것이 좋은 분류기가 될 수 있다.
  • 어린 아이에게 동영상을 추천하는 시스템의 경우는 어린 아이에게 성인이 볼 영상을 추천하면 안됨
    이런 경우에는 안전한 것들만 추천해줘야 하므로 정밀도가 높은 것이 좋은 분류기가 될 수 있다.
  • 일반적으로 정밀도를 올리면 재현율이 떨어지고, 재현율을 높이면 정밀도가 떨어진다.
    이를 정밀도와 재현율의 트레이드 오프라고 한다. 
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

print("accuracy: %.2f" %accuracy_score(y_train_5, y_train_pred))
print("Precision : %.3f" % precision_score(y_train_5, y_train_pred))
print("Recall : %.3f" % recall_score(y_train_5, y_train_pred))
print("F1 : %.3f" % f1_score(y_train_5, y_train_pred))
accuracy: 0.96
Precision : 0.837
Recall : 0.651
F1 : 0.733

 

- ROC 곡선

  • Receiver Operation Characteristic(ROC -수신기 조작 특성) 곡선도 이빈 분류에서 널리 사용되는 도구
  • 거짓 양성 비율(False Positive Rate -FPR)에 대한 진짜 양성 비율(재현율)의 곡선
    • 거짓 양성 비율은 양성으로 잘못 분류된 음성 샘플의 비율.
    • 1 - 진짜 음성 비율 (음성으로 정확하게 분류한 음성 샘플의 비율)  \
  • 음성으로 정확하게 분류한 비율을 특이도(Specificity)라고 하는데 ROC 곡선은 재현율에 대한 1-특이도 그래프
  • 이 값이 1에 가까우면 좋은 모델, 0.5에 가까우면 나쁜(랜덤) 모델
from sklearn.metrics import roc_auc_score
print('ROC_AUC:', roc_auc_score(y_train_5, y_train_pred))
ROC_AUC: 0.8192920558800075

 
 

3-4) RandomForestClassifier를 활용한 분류

- 예측하는 방법이 다른다. 이 클래스는 예측한 범주를 리턴하는게 아니라 predit_proba 함수를 이용해서 각 범주에 대한 확률값을 리턴

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, 
                                    cv=3, method='predict_proba')
print(y_probas_forest([1]))
[0.99 0.01]
  • 훈련하고 예측하면 결과가 확률로 나온다.
#예측 - 양성 클래스의 확률
y_scores_forest = y_probas_forest[:, 1]
print(y_scores_forest[1])
0.01
  • y_scores_forest를 가지고 평가 지표를 구해야 함
print(roc_auc_score(y_train_5, y_scores_forest))
0.9983436731328145
  • RandomForest를 이용한 모델의 ROC 값이 이전 모델보다 높게 나옴

 

3-5) 교차 검증

- 여러 개의 fold로 나누어서 모델을 생성하고 훈련한 후 평가지표를 얻어서 평균을 구하기도 함

#교차 검증
from sklearn.model_selection import cross_val_score

#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])
#교차 검증
from sklearn.model_selection import cross_val_score

#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])

 
 
 
 

4. 다중 분류

- 둘 이상의 클래스를 구별하는 작업
- SGD, RandomForest, Naive Bayes 알고리즘은 여러개의 범주를 직접 처리할 수 있지만
- Logistic Regression, Support Vector Machine 같은 알고리즘은 이진분류만 가능

  • 이진 분류기를 여러개 결합하면 다중 분류가 가능

 

4-1) 이진 분류 알고리즘을 이용한 다중 분류

- OvR(One-versus-the-Rest)

  • 범주의 개수만큼 이진 분류기를 만들어서 가장 높은 결정 점수를 가진 분류기가 분류한 것을 선택
    • 숫자분류기라면 숫자는 0부터 9까지 총 10개가 있으므로 분류기를 10개 만든다. 
    • 각 숫자에 해당하는 확률을 구해서 그 확률 중에서 가장 높은 것을 선택한다.

- OvO(One-versus-One) 

  • 두가지로 분류하는 이진분류기를 모두  (범주개수 * 범주개수-1) / 2  생성한 후 가장 많이 양성으로 분류된 범주를 선택
    • 0/1, 0/2, 0/3, ... 8/9 구분까지 생성
    • 그 중에서 가장 많이 분류된 클래스 선택
  • sklearn에서는 다중 클래스 분류에 이진 분류 알고리즘을 사용하는 분류기를 선택하면 알아서 자동으로 알고리즘 선택
    • 강제로 알고리즘을 선택할 수도 있다.

- SVM(Support Vector Machine)을 이용한 다중 분류

  • SVM은 이진 분류기: 기본적으로 다중 분류를 하지 못함
  • 이 분류기를 이용해서 다중 분류를 위한 데이터를 학습시키면 스스로 알고리즘을 선택해서 다중분류 결과 예측 가능
### 이진 분류기를 이용한 다중 분류
from sklearn.svm import SVC

#모델 생성
svm_clf = SVC(random_state=42)

#훈련
svm_clf.fit(X_train[:1000], y_train[:1000])

#예측
svm_clf.predict([some_digit])
array([5], dtype=uint8)
some_digit = X[1]
svm_clf.predict([some_digit])
array([0], dtype=uint8)
  • 여러개의 분류기를 생성한 후 각각의 분류기를 가지고 예측을 해서 점수 배정
  • 이 점수가 가장 높은 클래스 선택
some_digit = X[0]
some_digit_scores = svm_clf.decision_function([some_digit])
print(some_digit_scores)

some_digit = X[1]
some_digit_scores = svm_clf.decision_function([some_digit])
print(some_digit_scores)
[[ 1.758  2.75   6.138  8.285 -0.287  9.301  0.742  3.793  7.208  4.858]]
[[ 9.305 -0.287  7.214  5.881  0.725  8.272  2.779  1.775  3.792  4.984]]
  • 이 점수나 확률은 확인할 필요가 있다.
  • 5하고 제일 점수가 가까운 것은 8.285, [3]이다. 그러므로 5를 3으로 오분류할 가능성이 크다. 
    • 그래서 나중에는 오분류된 데이터만 가지고 다시 학습시키기도 한다.

- 강제로 알고리즘을 설정

#강제로 알고리즘 선택
from sklearn.multiclass import OneVsRestClassifier
ovr_clf = OneVsRestClassifier(SVC(random_state=42))
ovr_clf.fit(X_train[:1000], y_train[:1000])

some_digit = X[2]
print(ovr_clf.predict([some_digit]))
[4]

 
 

4-2) 스케일링 적용 후 분류

#현재 데이터의 스케일링 상태 확인
print(X_train[0])
from sklearn.linear_model import SGDClassifier

#SGD 분류기 생성
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=42)

#훈련
sgd_clf.fit(X_train, y_train) 

#교차 검증 수행
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([0.874, 0.858, 0.869])
from sklearn.preprocessing import StandardScaler
#스케일을 해서 교차검증
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
array([0.898, 0.891, 0.902])

 
-오차 행렬을 확인해서 어떤 부분에서 오류가 많이 발생하는지 확인

  • 오류가 많이 발생하는 부분이 있다면 그에 해당하는 데이터를 더 수집해서 좀더 정확한 모델을 만드는 것을 고려하거나 이미지 데이터의 경우는 전처리를 수행해서 보정하기도 함
#오차 행렬
y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
conf_mx
array([[5577,    0,   22,    5,    8,   43,   36,    6,  225,    1],
       [   0, 6400,   37,   24,    4,   44,    4,    7,  212,   10],
       [  27,   27, 5220,   92,   73,   27,   67,   36,  378,   11],
       [  22,   17,  117, 5227,    2,  203,   27,   40,  403,   73],
       [  12,   14,   41,    9, 5182,   12,   34,   27,  347,  164],
       [  27,   15,   30,  168,   53, 4444,   75,   14,  535,   60],
       [  30,   15,   42,    3,   44,   97, 5552,    3,  131,    1],
       [  21,   10,   51,   30,   49,   12,    3, 5684,  195,  210],
       [  17,   63,   48,   86,    3,  126,   25,   10, 5429,   44],
       [  25,   18,   30,   64,  118,   36,    1,  179,  371, 5107]],
      dtype=int64)

 
 
 

5. 다중 레이블 분류

- 분류기가 샘플 별로 여러개의 결과를 출력하는 경우
- 이미지에서 객체를 탐지하는 경우 하나의 객체만 탐지하는 것이 아니고 여러개의 객체를 탐지해야 하는 경우나 얼굴에서 얼굴의 각 부위를 탐지하는 경우 등

#다중 레이블 생성
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]
print(y_multilabel[0])
[False  True]

 
 
 

6. 판별 분석 (Discriminant Analysis)

- 2개 이상의 모집단에서 추출된 표본들이 지니고 있는 정보를 이용해서 이 표본들이 어느 모집단에서 추출된 것인지를 결정해줄 수 있는 기준을 찾는 분석

  • 은행에서 부동산 담보 대출할 때 이 고객이 대출을 상환할 것인가 아니면 상환하지 않을 것인가를 판별하는 경우 은행에서는 기존의 대출을 상환하지 않은 고객들의 특성과 대출을 상환한 고객들의 특성을 별도로 만들어두고 이 고객의 특성이 어느쪽에 더 가까운건지 파악할 수 있다.

- 초창기에는 LDA(Linear Discriminant Analysis - 선형 판별 분석)를 많이 사용

  • 최근에는 신경망 등이 등장하면서 사용빈도가 낮아졌다. 
  • 아직도 다른 머신러닝 알고리즘의 기반 알고리즘으로 사용됨

- 전체 표본의 크기가 독립 변수의 개수보다 3배 이상 많아야 함
 
 

6-1) LDA

- 내부 제곱합에 대한 사이 제곱합의 비율을 최대화하는 것이 목적

  • 그룹 내부의 제곱합은 적게, 그룹과 그룹 사이의 제곱합은 크게 만드는 알고리즘
  • 클러스터링의 기반이 되는 방법

- sklearn.discriminant.LinearDicriminantAnalysis 클래스 이용

from sklearn.neighbors import KNeighborsClassifier

knn_clf = KNeighborsClassifier()
#훈련할 때 여러개의 label인 묶인 타겟을 설정하면 다중 레이블 분류
knn_clf.fit(X_train, y_multilabel)
knn_clf.predict([some_digit])
array([[False, False]])
some_digit = X[0]
knn_clf.predict([some_digit])
array([[False,  True]])

 
 

7. DummyClassifier

- 랜덤하게 분류
- 타겟의 비율을 보고 그대로 랜덤하게 예측하는 방식
 
 

8. KNN(K-Nearest Neighbor) 최근접 이웃

- 특징들이 가장 유사한 K개의 데이터를 찾아서 K개의 데이터를 가지고 다수결로 클래스를 선택해서 할당
- 회귀에 사용할 때는 그 값의 평균을 구해서 예측
 

8-1) 특징

- 간단: 전처리 과정에서 결측치를 대체하는 데 사용하기도 함
- 모델을 피팅하는 과정이 없음
- 모든 예측 변수들은 수치형이어야 함

  • 거리를 계산해야 하기 때문
  • 이 경우 범주형 데이터는 특별한 경우가 아니면 원핫 인코딩을 수행해야 함

- 게으른 알고리즘이라고 하는데 훈련 데이터 세트를 메모리에 전부 저장하고 거리 계산을 수행

  • 온라인 처리가 안됨

 

8-2) API

- sklearn.neighbors.KNeighborsClassifier 클래스

  • 인스턴스를 생성할 때 n_neighbors를 이용해서 이웃의 개수 설정
  • metric을 이용해서 거리 계산 알고리즘을 설정하는데
    1이면 맨하튼 거리,  2이면 유클리드 거리, 설정하지 않으면 mincowski 거리

- 데이터: loan_200.csv

  • payment_inc_ratio: 소득에 따른 대출 상환 비율
  • dti: 소득에 대한 부채 비율
  • outcome: 상환 여부

# KNeighborsClassifier를 이용해서 payment_inc_ratio와 dit에 따른 outcome 분류

#데이터 가져오기
loan200 = pd.read_csv('./python_machine_learning-main/data/loan200.csv')
#데이터 분리
#테스트를 위한 데이터 1개 추출
newloan = loan200.loc[0:0, ['payment_inc_ratio', 'dti']]

#피처 추출
X = loan200.loc[1:, ['payment_inc_ratio', 'dti']]
#타겟 추출
y = loan200.loc[1:, 'outcome']

from sklearn.neighbors import KNeighborsClassifier
# 인스턴스 생성 - 필수적인 파라미터는 n_neighbors
# 모델 생성
knn = KNeighborsClassifier(n_neighbors=21)

#훈련
knn.fit(X, y)
#예측
knn.predict(newloan)
array(['paid off'], dtype=object)
#클래스별 예측 확률
print(knn.predict_proba(newloan))
[[0.476   0.524]]
  • predict 함수는 거의 모든 분류 모델이 소유하고 있지만
  • predict_proba 함수는 없을 수 있다.

 

8-3) 거리 지표

- 유클리드 거리

  • 서로의 차이에 대한 제곱합을 구한 뒤 그 값의 제곱근을 취하는 방식
  • 유클리드 거리를 사용할 때는 수치형 데이터의 범위를 확인

- 맨하튼 거리

  • 서로의 차이에 대한 절대값을 구한 뒤 모두 더한 거리

- 마할라노비스 거리

  • 두 변수 간의 상관관계를 사용
  • 유클리드 거리나 맨하튼 거리는 상관성을 고려하지 않기 때문에 상관관계가 있는 피처들의 거리를 크게 반영
  • 주성분간의 유클리드 거리를 의미
  • 많은 계산이 필요하고 복잡성이 증가
  • 피처들의 상관관계가 높을 때 사용

- 민코프스키 거리

  • 1차원 공간에서는 맨하튼 거리를 사용하고 2차원 공간에서는 유클리드 거리를 사용

 

8-4) 표준화

- 거리의 개념을 이용하므로 스케일링이나 표준화를 수행을 해주어야 함
- 표준화를 했을 때와 그렇지 않을 때 이웃이 달라지게 됨

  • 10000 1 1 1 1
    20000 1 1 1 1
    11000 100 100 100 100
    제곱을 하니까 단위 자체가 다름

 
 

8-5) 피처 엔지니어링

- KNN은 구현이 간단하고 직관적
- 성능은 다른 분류 알고리즘에 비해서 그렇게 우수한 편은 아니다.
- 다른분류 방법들의 특정 단계에 사용할 수 있게 모델에 지역적 정보를 추가하기 위해서 사용하는 경우가 많음

  • 새로운 피처를 만드는 데 많이 사용
  • 기존의 피처를 이용해서 새로운 피처를 만드는 것이라서 다중 공선성 문제를 야기할 것 같은데 KNN으로 만들어진 피처는 다중 공선성 문제가 거의 발생하지 않는다.
  • KNN은 피처 전체를 이용하는 것이 주위 데이터 몇개만 이용하기 때문에 매우 지엽적인 정보 이용

- loan_data.csv.gz 데이터에서 새로운 피처 추가

  • csv 파일의 크기가 너무 커지는 경우 파일을 gz 타입으로 압축해서 용량 줄일 수 있음
    • pandas는 gz로 압축된 csv 파일의 내용을 읽을 수 있다. 
    • python은 zip이나 tar로 압축된 파일을 압축을 해제할 수 있고 여러 파일을 압축할 수 있는 API를 제공

# outcome 컬럼을 타겟으로 하고 나머지를 피처 특성으로 만들고, 피처 특성으로 타겟을 예측하도록 KNN으로 학습한 후 그 때의 예측 확률을 새로운 피처로 추가

loan_data = pd.read_csv('./python_machine_learning-main/data/loan_data.csv.gz')
print(loan_data.info())
print(loan_data['outcome'].value_counts())
15  outcome            45342 non-null  object 

outcome
default     22671
paid off    22671
Name: count, dtype: int64
  • outcome은 카테고리인데, 자료형이 Object(문자열)

# 문자열을 범주형으로 전환

loan_data['outcome'] = pd.Categorical(loan_data['outcome'], 
                                     categories=['paid off', 'default'], 
                                     ordered=True)

loan_data.info()
15  outcome            45342 non-null  category

 
#특성 행렬과 타겟 생성

X = loan_data[['dti', 'revol_bal', 'open_acc']]
y = loan_data['outcome']

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=20)
knn.fit(X, y)

 
# knn으로 만들어진 예측 확률을 새로운 피처로 추가

# 예측 결과는 이차원 배열이므로 첫번째 열만 추가
loan_data['borrow_score'] = knn.predict_proba(X)[:, 0]
print(loan_data.info())
loan_data['borrow_score'].head()
21  borrow_score       45342 non-null  float64 

0    0.50
1    0.40
2    0.40
3    0.55
4    0.55
Name: borrow_score, dtype: float64
  • default, 빚을 갚지 않을 확률들!
예측 결과가 0이거나 1인 경우에는 1이 1일 확률만 내보낸다. 두번째 열은 없긴 함
그렇지만 머신러닝은 이차원 배열만 리턴한다. 하나의 데이터로만 예측하려 하진 않을테니까!

 
 

8-6) 특징

- 장점

  • 이해하기 쉬운 모델
  • 많이 조정하지 않아도 좋은 성능을 내는 경우가 있음

- 단점

  • 예측이 느림
    • 어떤 알고리즘이 특별히 있는게 아니라 예측할 데이터가 오면 그때 가서 이웃을 선정하고 예측함 
  • 많은 특성을 처리하는 능력이 부족
    • 데이터를 전부 메모리에 로드해야하기 때문에 데이터가 많으면 사용하기 어려움 - 게으른 알고리즘

 
 

9. 나이브 베이즈

- 나이브 베이즈 알고리즘은 주어진 결과에 대해 예측 변수 값을 확률을 사용해서 예측 변수가 주어졌을 때 결과를 확률로 추정
- 완전한 또는 정확한 베이지언 분류 - 나이브 하지 않은 방식 (현실성이 없음)

  • 예측 변수 프로파일이 동일한 모든 레코드를 찾음
  • 해당 레코드가 가장 많이 속한 클래스(타겟값) 결정
  • 새로운 레코드에 해당 클래스 지정
  • 모든 예측 변수들이 동일하다면 같은 클래스에 할당될 가능성이 높기 때문에 예측 변수들이 같은 레코드를 찾는 데 무게를 두는 방식

- 나이브 베이즈

  • 확률을 계산하기 위해 정확히 일치하는 레코드로만 제한하지 않고 전체 데이터 활용
    • overcast일 때 경기를 한 확률만이 아니라, 전체 날씨 중에 overcast인 확률도 넣었어야 한다.
    • 단일 항목을 가지고 분류 - 날씨가 overcast일 때 경기를 할 확률은?
      P(Yes|Overcast) = P(Overcast|Yes) P(Yes) / P(Overcast)
  • 사전 확률
    P(Overcast) = 4/14 = 0.29
    P(Yes) = 9/14 = 0.64
  • 사후 확률

    P(Overcast|Yes) = 4/9 = 0.44

 

  • 베이즈 정리 공식에 대입
    P(Yes|Overcast) = P(Overcast|Yes) P(Yes) / P(Overcast) = 0.44 * 0.64 / 0.29 = 0.98
  • 결론: 날짜가  Overcast일 때 경기를 할 확률이 0.98이라는 의미

 

9-1) 나이브 베이즈 특징

- 일반적인 선형 분류기보다 훈련 속도가 빠른 편
- 일반화 성능은 일반적인 선형 분류기보다 떨어짐
- 효과적인 이유는 각 특성을 개별로 취급해서 파라미터를 학습하고 각 특성에서 클래스 별 통계를 단순하게 취합

  • 텍스트 처리에서 주로 이용

 

9-2) API

- GaussianNB : 연속적인 데이터에 사용
- BernouliNB: 이진 데이터에 사용
- MultinomialNB: 카운트 데이터 (특성의 개수를 헤아린 정수값) 

  • BernouliNB와 MultinomialNB을 자연어 처리에 이용
loan_data = pd.read_csv('./python_machine_learning-main/data/loan_data.csv.gz')

loan_data['outcome'] = pd.Categorical(loan_data['outcome'], 
                                     categories=['paid off', 'default'], 
                                     ordered=True)

#purpose_, home_, emp_len은 범주형
loan_data['purpose_'] = loan_data['purpose_'].astype('category')
loan_data['home_'] = loan_data['home_'].astype('category')
loan_data['emp_len_'] = loan_data['emp_len_'].astype('category')
  • 타겟은 순서가 중요하다. 분류 할 때 각 타겟의 순서대로 확률을 제시하기 때문.
  • 피처는 순서가 중요하지 않다. 바로 카테고리로 변환해도 된다. 나중에 원핫인코딩을 하기 때문.
#타겟과 피처 분리
predictors = ['purpose_', 'home_', 'emp_len_']
outcome = 'outcome'

#원핫 인코딩을 수행해서 특성 배열(피처 배열)을 생성
X = pd.get_dummies(loan_data[predictors], prefix='', prefix_sep='')
y = loan_data[outcome]

X.head()
from sklearn.naive_bayes import MultinomialNB

naive_model = MultinomialNB()
naive_model.fit(X, y)

#146번 데이터 예측
new_loan=X.loc[146:146, :]
print("146번-", naive_model.predict(new_loan))
print(naive_model.classes_)
print("146번-", naive_model.predict_proba(new_loan))
146번- ['default']
['default' 'paid off']
146번- [[0.65344472 0.34655528]]
  • default일 확률이 0.65, paid off일 확률이 0.34
print("146번-", naive_model.predict(new_loan)[0])
146번- default

 
 
 
 

10. Logistic Regression

- 타겟(종속변수)이 범주형인 경우 사용하는 회귀모형
- 분류만 가능
- 타겟이 2개 값을 갖는 경우에만 사용
- 피처의 자료형이 연속형에도 사용할 수 있고 이산형에도 사용 가능
 
 

10-1) Odds Ratio(특정 이벤트가 발생할 확률)

- Odds = 성공율 / 실패율 =>  성공율 / (1-성공율)
- Odds Ratio = 한 그룹의 Odds / 다른 그룹의 Odds

  • 비만일 확률/(1-비만일 확률) =(402/4016)/(1-402/4016)) = 0.1001/0.8889 = 0.111
  • 혈중 콜레스테롤이 정상인 그룹에서 비만이 아닌 경우의 오즈  0.899/0.1001 = 8.99

- Logit(P) = log(P/1-P)

  • 값의 범위를 0- 1 범위로 입력받아서 자연 로그를 취한 함수가 Logit 함수
  • P는 특성 x가 주어졌을 때 이 샘플이 클래스 1에 속할 조건부 확률
  • 샘플이 특정 클래스에 속할 확률을 예측하는 것이 관심 대상이므로 logit 함수를 거꾸로 뒤집는데 이러한 함수가 로지스틱 시그모이드(logistic sigmoid function) 함수

- 약물 치료에 대한 환자의 반응(종속 변수)을 예측하고자 할 때 약물 치료 적용 후 환자가 살아남은 경우 1 살아남지 못한 경우를 0으로 표현할 수 있음

단순선형회귀Logistic Regression 분석

 
 
 

10-2) Logit 함수

- Odds Ratio에 로그를 취한 것
- 자연 로그를 취하면 부드러운 곡선의 형태
- 직선의 형태로 구분하는 단순 회귀보다는 조금 더 정확한 모델을 만들 가능성이 높다. 

  • 경계선 부분에 데이터가 많은 경우

 

10-3) API

- sklearn.linear_model LogisticRegression

  • penalty : 페널티를 부여할 때 사용할 기준을 결정('l1', 'l2')
  • dual : Dual Formulation인지 Primal Formulation인지를 결정(True, False)
  • tol : 중지 기준에 대한 허용 오차 값
  • C : 규칙의 강도의 역수 값
  • fit_intercept : 의사 결정 기능에 상수를 추가할 지 여부 결정(True, False)
  • class_weight : 클래스에 대한 가중치들의 값
    • 타겟 값 비율이 다를 때
  • random_state : 데이터를 섞을 때 사용하는 랜덤 번호 생성기의 시드 값
  • solver : 최적화에 사용할 알고리즘 결정('newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga')
  • max_iter : solver가 수렴하게 만드는 최대 반복 횟수 값
  • multi_class : ('ovr', 'multinomial')
  • warm_start : 이전 호출에 사용했던 solution을 재사용 할지 여부 결정(True, False)
  • _jobs : 병렬 처리 시 사용 할 CPU 코어의 수
    • n_jobs: n개만큼 동시에 작업, -1을 쓰면 최대만큼 묶어서 작업
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target

print(X[:5])
print(iris.feature_names)
print(y[:5])
print(iris.target_names)
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]

['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

[0 0 0 0 0]

['setosa' 'versicolor' 'virginica']
from sklearn.linear_model import LogisticRegression

log_reg = LogisticRegression()
log_reg.fit(X, y)

print(log_reg.predict([X[0]])) #X[0]하나만 보더라도 [X[0]]
[0]
print(log_reg.predict(X[0].reshape(-1, 4))) 
print(y[0])
[0]
0
  • 맞춤
print(X[0].reshape(-4, 1))
[[5.1]
 [3.5]
 [1.4]
 [0.2]]

 
 

10-4) 소프트맥스 회귀

 - 이진 분류 알고리즘을 가지고 다중 클래스 분류를 할 때는 여러 개의 분류를 만들어서 처리

  • 클래스 개수만큼 이진 분류기를 만들어서 분류기를 통과한 결과 중 가장 높은 확률을 가진 클래스로 판정
  • 2개씩 쌍으로 분류할 수 있는 모든 분류기를 만들어서 판정하는 방식

- 여러 개의 이진 분류기를 훈련시켜 연결하지 않고 직접 다중 클래스를 지원하도록 일반화한 방식 : 소프트맥스 회귀

  • 소프트맥스 함수는 정규화된 지수함수라고도 하는데, 각 피처들을 가지고 클래스에 대한 점수를 계산하는 선형 회귀식을 만들어서 사용
  • 각 클래스에 대한 선형 회귀식을 별도로 만들어 회귀식에 의해서 샘플에 대한 점수가 계산이 되면 소프트맥스 함수를 통과시켜서 확률 추정

 

10-5) 크로스 앤트로피

* 앤트로피: 불확실성. 값이 작을 수록 데이터가 잘 분리되어있다는 뜻.

- 모델에서 예측한 확률값이 실제값과 비교했을 때 틀릴 수 있는 정보량
-  값 이 적을 수록 모델이 데이터를 더 잘 예측하는 모델
- 정보 이론에서 유래
- 여러개의 상태가 있을 때 이 상태를 표현하는 방법을 비트 수를 다르게 해서 표현하는 방식

  • 자주 등장하는 상태는 비트 수를 적게 해서 만들고 자주 등장하지 않는 상태는 비트 수를 크게 만드는 것

 
현재 상태가 4가지 (첫번째 상태가 90%이고, 나머지 상태가 7% , 2% , 1% 인 경우
- 일련번호 방식

  • 00
  • 01
  • 10
  • 11

- 원핫인코딩

  • 1000
  • 0100
  • 0010
  • 0001

- 크로스 앤트로피

  • 0 - 첫번째 상태
  • 10 - 두번째 상태
  • 110 - 세번째 상태
  • 1111 - 네번째 상태

 

10-6) 로지스틱 회귀에서 소프트맥스 사용법

- multi_class 옵션에 multinomial 적용
- solver 옵션에 lbfgs 같은 소프트맥스 알고리즘을 적용할 수 있는 알고리즘 설정

log_reg = LogisticRegression(multi_class = 'multinomial', 
                            solver='lbfgs', random_state=42)
log_reg.fit(X, y)

print(log_reg.predict(X[0].reshape(-1, 4)))
print(y[0])
print(log_reg.predict(X[51].reshape(-1, 4)))
print(y[51])
[0]
0
[1]
1

 
 
 
 

11. SVM(Support Vector Machine)

- 일반적인 머신러닝 기법 중에서 매우 강력하고 선형 또는 비선형, 회귀, 이상치 탐색에도 사용하는 머신러닝 모델
- 초창기에는 가장 인기있는 모델에 속함, 현재는 아님
- 복잡한 분류 문제에 적합했고, 작거나 중간 크기의 데이터 세트에 적합
- 모든 속성을 활용하는 전역적 분류 모형
- 군집별로 초평면을 만드는데 이 초평면은 다른 초평면의 가장 가까운 자료까지의 거리가 가장 크게 만드는 것
 

11-1) 특징

- 장점

  • 에러율이 낮음
  • 결과를 해석하기 용이

- 단점

  • 파라미터 및 커널 선택에 민감
  • 이진분류만 가능
  • 특성의 스케일에 굉장히 민감. 반드시 스케일링 수행해야 함

 

11-2) 하드마진과 소프트마진

- 하드마진

  • 모든 데이터가 정확하게 올바르게 분류된 것
  • 하드마진이 성립되려면 데이터가 선형적으로 구분될 수 있어야 하고, 이상치에 민감

- 소프트마진

  • 이상치로 인해 발생하는 문제를 피하기 위해서 좀더 유연한 모델을 생성 
  • 어느정도의 오류를 감안하고 결정경계를 만들어내는 방식
  • SVM에서 매개변수 C로 마진 오류 설정
    • 마진 오류는 적은 것이 좋지만 너무 적게 설정하면 일반화가 잘 안됨
      *일반화: 지금껏 보지 못한 데이터에 올바르게 적용되는 성질

 

11-3) 선형 SVM 모델을 이용한 붓꽃 분류

- Pipeline을 사용

  • 피처 스케일링과 모델 훈련을 하나의 파이프라인으로 묶어서 수행
#피처 스케일링
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

#모델 생성해서 훈련
from sklearn.svm import LinearSVC
svc_clf = LinearSVC(C=1, random_state=42)
svc_clf.fit(X, y)

#예측
svc_clf.predict([[5.5, 1.7]])
array([1.])

 
- 모든 컬럼에 스케일링하는 것이 동일하다면 파이프라인으로 묶는 것이 가능

#피처 스케일링
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

#스케일러와 분류기를 하나로 묶어서 사용
svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('linear_SVC', LinearSVC(C=1, random_state=42))
])

svm_clf.fit(X, y)
svc_clf.predict([[5.5, 1.7]])
array([1.])

 
 

11-4) 비선형SVM

- 선형 SVM 분류기가 효율적이지만 선형적으로 분류할 수 없는 데이터가 많음 
- 선형 SVM(커널)의 문제점중 하나가 XOR을 구분할 수 없는 점  => 다항식을 이용하여 해결
- 다항식을 이용하는 SVM을 생성하기 위해서는 PolynomialFeatures 변환기를 추가하면 됨

#데이터 생성
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=100, noise=0.14, random_state=42)

#데이터 시각화
def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel("x_1", fontsize=20)
    plt.ylabel("x_2", fontsize=20, rotation=0)

plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])

 
# 다항식을 추가한 선형 분류기

from sklearn.preprocessing import PolynomialFeatures

polynomial_svm_clf = Pipeline([
        ("poly_features", PolynomialFeatures(degree=3)),
        ("scaler", StandardScaler()),
        ("svm_clf", LinearSVC(C=1,random_state=42))
    ])

polynomial_svm_clf.fit(X, y)

 

다항식 없으면degree = 10degree = 30


 

  • degree는 추가되는 다항의 지수승을 의미한다.
    • 숫자가 높아지면 잘 분류할 가능성은 높아지지만 학습할 피처의 개수가 늘어나고, 이로인해 훈련 속도도 느려지고 학습 속도가 느려진다.
    • OVerfitting 될 가능성이 높아진다.

 
 

11-5) 다항식 커널

- 다항식 특성을 추가하면 성능이 좋아졌는데 낮은 차수의 다항식은 매우 복잡한 데이터 세트에는 잘 맞지 않을 것이고 높은 차수의 다항식은 굉장히 많은 특성을 추가하므로 모델을 느리게 함
- Kernel, Trick이라는 수학적 기교를 이용해서 실제로는 특성을 추가하지 않으면서 다항식 특성을 추가한 것과 같은 효과를 얻는 방법 사용
- 이 기법을 사용할 때는 SVC 클래스에서 매개변수 kernel에 poly 설정, 매개변수 degree에 적용하고자 하는 차수를 설정, 매개변수 coef()에 정수값을 설정. 낮은 차수와 높은 차수 중에서 어떤 다항식에 영향을 받을지 설정

#다항식 커널은 실제로 다항식을 추가하는 것이 아니라 추가하는 것과 같은 효과를 냄
from sklearn.svm import SVC

#스케일러와 분류기를 하나로 묶어서 사용
poly_kernel_svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('svm_SVC', SVC(kernel='poly', degree=10, coef0=1, C=1, random_state=42))
])

poly_kernel_svm_clf.fit(X, y)

plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

 
 

11-6) 유사도 특성을 추가하는 가우시안 RBF 커널

- 몇 개의 랜드마크를 추가한 후 이 랜드마크와의 유사도를 측정해서 그 값을 특성으로 추가
- SVC로 생성, 매개변수 kernel에 RBF, gamma 설정

  • gamma를 너무 적은 수로 설정하면 선형 분류에 가까워지고 높게 설정하면 다항식을 이용하는 분류에 가까워짐
#가우시안 커널 이용
from sklearn.svm import SVC

rbf_kernel_svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('svm_SVC', SVC(kernel='rbf', gamma=0.01, C=1, random_state=42))
])

rbf_kernel_svm_clf.fit(X, y)

plot_predictions(rbf_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

 
 

11-7) 커널의 종류로 sigmoid도 존재

 

11-8) 시간복잡도

* 문자를 이용해서 행열을 표현할 때 앞의 숫자가 행의 수, 뒤의 숫자가 열의 수

- SGDClassifier(선형 회귀 분류):  O(m*n)
- LinealSVC: O(m*n)
- SVC: O(m^ * n) ~ O(m^^ * n)
 
 
 

12. Decision Tree(결정 트리)

- 데이터 사이에 존재하는 패턴을 예측 가능한 트리 모양의 조건문 형태로 만드는 방식
- 복잡한 데이터세트도 학습하는 강력한 알고리즘
- RandomForest 모델의 기본 구성 요소
- 분류와 회귀에 모두 사용할 수 있고 다중 출력도 가능

  • 분류에서는 마지막 터미널 값을 그대로 리턴하고 회귀의 경우는 평균을 리턴

- 트리는 한번 분기할 때마다 변수 영역을 2개로 구분

  • 한번 구분한 뒤 순도가 증가하거나 불순도가 감소하는 방향으로 학습을 수행
  • 순도가 증가하거나 불순도가 감소하는 것을 정보획득이라고 함

- API: DecisionTreeClassifer, DecisionTreeRegression

  • max_depth: 트리의 깊이 
iris = datasets.load_iris()

X = iris.data[:, 2:]
y = iris.target

from sklearn.tree import DecisionTreeClassifier
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)

 
- leaf: (Terminal) 자식이 없는 노드
 
 

12-1) 트리 시각화를 위한 준비

- graphviz.org 다운로드, 환경변수에 path 추가

!pip install graphviz
!pip install pygraphviz
from sklearn.tree import export_graphviz
import graphviz

export_graphviz(
        tree_clf,
        out_file="iris_tree.dot",
        feature_names=iris.feature_names[2:],
        class_names=iris.target_names,
        rounded=True,
        filled=True
    )
    
with open('iris_tree.dot') as f:
    dot_graph = f.read()
src = graphviz.Source(dot_graph)

src

 
 
 
12-2) 트리 시각화
 
 

12-3) 불순도 지표 

- 트리 모델은 순도가 증가하거나 불순도가 감소하는 방향으로 학습
- 지니계수(Gini Index): 오분류 오차

  • 1에서 전체 데이터 개수에서 분류된 클래스의 데이터 개수를 제곱한 값을 전부 뺀 값
  • 작은 값이 좋은 값
  • 총 54개의 데이터를 분류했는데 클래스1이 0개, 클래스2가 49개, 클래스3이 5개로 분류된 경우
    1 - (0/54)^ - (49/54)^ - (5/54)^

- 앤트로피

  • 기본적으로는 지니 계수를 사용하지만 crieterion을 entropy로 설정하면 불순도 지표가 변경됨
  • 하나의 영역에 2가지 클래스가 배정된 경우
    하나의 영역에 16개의 데이터가 배정었고 1번이 10개, 2번이 6개가 배정되면
    10/16 * log2(10/16) - 6/16 * log2(6/16)
  • 앞뒤 비율이 비슷해질수록 오분류 가능성이 커진다.
  • 한쪽으로만 데이터가 배정되면 앞의 값이 0이 되고 뒤의 값도 0이 되어 결국 0이 됨.
    0에 가까운 값이 좋은 값이다.

 

12-4) 규제 매개변수

- 결정 트리는 훈련 데이터에 대한 제약사항이 거의 없음
- 규제를 하지 않으면 트리가 훈련 데이터에 아주 가깝게 맞추려고 해서 과대적합이 발생
- 결정 트리는 모델의 하이퍼 파라미터가 없는 것이 아니라 훈련되기 전에 파라미터 수가 결정되지 않기 때문에 모델 구조가 데이터에 맞춰져서 고정되지 않고 자유로움

  • 하이퍼 파라미터의 기본값이 대부분 Max 값이거나 None이다.
  • 이전의 다른 모델은 하이퍼 파라미터의 값이 설정되어 있음
  • 하이퍼 파라미터의 값이 기본값을 가지는 경우는 과대 적합될 가능성은 낮지만 과소 적합될 가능성이 있고 하이퍼 파라미터의 값이 기본값을 가지지 않고 MAX 값을 가지는 경우 과대 적합될 가능성은 높지만 과소 적합될 가능성은 낮다.
비선형 커널에서 degree라는 하이퍼 파라미터가 있다.
degree가 높으면 데이터를 정확하게 분류할 확률이 높다. 
degree를 -1로 설정할 수 있다고 하면 정확하게 분류할 가능성은 높아지지만

 
- 결정 트리의 형태를 제한하는 매개변수

  • max_depth: 최대 깊이, 특성의 개수를 확인해서 적절하게 설정
  • min_samples_split: 분할을 할 때 가져야 하는 데이터의 최소 개수
  • min_samples_leaf: 하나의 터미널이 가져야 하는 최소 개수
  • min_weight_fraction_leaf: 개수가 아니라 비율로 설정
  • max_leaf_nodes: 터미널의 개수 
  • max_features: 분할에 사용할 특성의 개수

min으로 시작하는 매개변수를 증가시키거나 max로 시작하는 매개변수를 감소시키면 규제가 커짐

  • 규제가 커지면 오차가 발생할 확률은 높아진다.

 

12-5) 트리 모델의 단점

- 훈련 데이터의 작은 변화에도 매우 민감

  • 조건문을 사용하는 방식이기 때문에 하나의 데이터가 추가되었는데 이 데이터가 조건에 맞지 않으면 트리를 처음부터 다시 만들어야 할 수도 있다.
  • 트리 모델을 훈련시켜서 다른트리 모델과 비교하고자 할 때는 반드시 random_state를 고정시켜야 한다.

 

12-6) 피처의 중요도 확인

- 트리 모델에는 feature_importance 

#피처의 중요도 확인
print(tree_clf.feature_importances_)
print(iris.feature_names[2:])
[0.56199095 0.43800905]
['petal length (cm)', 'petal width (cm)']
iris_importances_ = pd.Series(tree_clf.feature_importances_,
                             index=iris.feature_names[2:])
sns.barplot(x=iris_importances_, y= iris_importances_.index)
plt.show()

 
 

12-7) 모델 생성 및 훈련

from sklearn.datasets import load_iris
#붓꽃 데이터 가져오기
iris = load_iris()
#피처 선택
#iris.data - 열의 개수가 4개인 2차원 배열
#iris.feature_names - 피처 이름 확인 
#print(iris.feature_names)
 

 

#피처 선택
X = iris.data
#타켓 선택
y = iris.target
#결정 트리를 생성해서 훈련
from sklearn.tree import DecisionTreeClassifier
#max_depth 가 깊으면 정확한 분류를 해낼 가능성은 높지만 시간이 오래 걸리고
#과대 적합 문제 발생 가능성이 있음
#트리 모델은 트리를 한 개 생성하는 것이 아니고 여러 개 생성하므로
#각 트리가 동일한 데이터를 가지고 훈련해야 하므로 random_state를 고정시켜야 합니다.
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)
 

 
 

12-8) 예측

#예측
result = tree_clf.predict([[5.1, 3.5, 1.4, 0.2]])
print(result)

 
 
 

12-9) 트리 모델 시각화

1) 트리 모델의 강점

- 화이트박스, 이해가 쉽다. 

  • 트리 구조 시각화

- 피처의 중요도 파악이 쉽다
- 별도의 파라미터 튜닝을 하지 않아도 좋은 성능
- 데이터 전처리가 거의 필요 없음
 

2) 시각화 준비

- graphviz 설치: 화면에 트리를 출력하는 것이 목적
 - Windows
   https://graphviz.org/download/#windows 에서 graphviz를 다운로드 받아서 설치
   graphviz 가 설치된 디렉토리의 bin 디렉토리 와 bin 안에 있는 dot.exe 경로를 path에 추가

 # 패키지 설치

   pip install graphviz

 
- pygraphviz 설치: 출력된 트리를 이미지 파일로 만드는 것이 목적

  •  python -m pip install --use-pep517 --config-settings="--global-option=build_ext" --config-settings="--global-option=-IC:\Program Files\Graphviz\include"  --config-settings="--global-option=-LC:\Program Files\Graphviz\lib" pygraphviz
    \

 

3) 이미지로 출력

from sklearn.tree import export_graphviz
import graphviz
export_graphviz(
    tree_clf,
    out_file="iris_tree.dot",
    feature_names = iris.feature_names,
    class_names = iris.target_names,
    rounded=True,
    filled=True
)
#강제로 path 에 경로를 추가
import os
os.environ["PATH"] += os.pathsep + 'C:\Program Files\Graphviz\bin'
with open("iris_tree.dot") as f:
    dot_graph = f.read()
src = graphviz.Source(dot_graph)
src

 
 

4) 이미지 파일로 저장

import pygraphviz as pga
from IPython.display import Image
graph = pga.AGraph("./iris_tree.dot")
graph.draw("iris_tree.png", prog='dot')
Image("iris_tree.png")

 
 

5) 최적의 파라미터 찾기 - 하이퍼 파라미터 튜닝( max_depth, min_sample_split)

from sklearn.model_selection import GridSearchCV

#파라미터 모음 만들기
params = {
    'max_depth': [2, 4, 6, 8, 12, 16],
    'min_samples_split': [2, 4, 8, 10]
}

grid_cv = GridSearchCV(tree_clf, param_grid=params, scoring='accuracy',
                      cv = 5, verbose=1)
grid_cv.fit(X, y)

print("가장 좋은 파라미터:", grid_cv.best_params_)
print("가장 좋은 정확도:", grid_cv.best_score_)


6) 피처의 중요도 확인

best_iris_clf = grid_cv.best_estimator_
iris_importances = pd.Series(best_iris_clf.feature_importances_, 
                             index=iris.feature_names)
#2개의 피처는 영향력이 거의 없음
print(iris_importances)

iris_top3 = iris_importances.sort_values(ascending=False)[:3]
print(iris_top3)

sns.barplot(x=iris_top3, y=iris_top3.index)
plt.show()

 

[Python] 머신러닝

0ㅑ채
|2024. 3. 7. 14:00

1. 인공지능

- 지능 : 문제를 해결할 수 있는 능력

  • 환자를 보고 병을 진단

- 인공지능: 지능 작업을 수행할 수 있는 기계의 능력

  • 환자에 대한 정보를 입력하면 컴퓨터가 병을 진단

- 구현 방법: 지식 공학(전문가 시스템): 문제 해결을 위한 알고리즘을 사람이 작성

  • 전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결
  • 컴퓨터의 역할은 결과를 만들어내는 것

- Machine Learning

  • Data와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식

 

인공지능과 머신러닝의 관계

- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습
- 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습

  • 딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임에 적합
  • 이미지 데이터가 존재하는 경우 머신 러닝은 이미지 데이터 1개를 데이터 1개로 바라보지만 딥러닝은 이를 작게 쪼개서 그 안에서 알고리즘을 찾을 수 있음

 
 
 

2. Machine Learning

- 데이터를 가지고 학습하도록 컴퓨터를 프로그래밍하는 과학
- 명시적으로 프로그램 되는 것이 아니라 훈련되며 작업과 관련있는 샘플을 제공하면 이 데이터에서 통계적 구조를 찾아 그 작업을 자동화하기 위한 규칙을 만들어내는 것
- 필요 요소

  • 입력 데이터 포인트: 최근에는 여러 입력 데이터 포인트로 얻어진 데이터를 한 곳에 모아서 처리하는 부분에 대해 중점
    • 데이터 발생지가 여러 곳인 경우 별도로 처리하는 것이 어렵기 때문에 한 곳에 잘 정리를 해서 모으는 것이 중요
  • 기대 출력: 어떤 결과를 원하는지
  • 알고리즘 성능 측정 방법: 평가 지표

 

1) 사용하는 이유

- 전통적인 방식으로는 너무 복잡하거나 알려진 알고리즘이 없는 문제

  • 음성인식은 직접 알고리즘을 만들기에는 너무 복잡해서 머신러닝을 이용

- 머신 러닝을 통해 학습을 할 수 있기 때문
대용량의 데이터를 분석하다보면 기존에 알고 있지 않은 또는 겉으로는 보이지 않는 패턴 발견 가능 - 데이터 마이닝
 
 

2) 역사

- 확률적 모델링

  • 나이브 베이즈
  • 로지스틱 회귀

- 신경망

  • 등장은 1950년대, 이때는 컴퓨터 성능이 좋지 못해서 효과적인 훈련 방법을 찾지 못했다.
  • 1989년 얀 르쿤에 의해 초창기 합성곱 신경망이 등장하면서 다시 각광

- 커널 방법

  • 분류에 사용되었는데 선형이 아닌 비선형으로 결정 경계 만들기 시작

- Decision Tree, Random Forest, Gradient Boosting: 앙상블 모형

  • 컴퓨터 성능이 좋아지면서 하나의 알고리즘을 부트스트랩을 이용하거나 여러 개의 알고리즘을 한꺼번에 학습하는 방법

- 신경망
- 생성형 AI
 
 

3) 분류

- 레이블(정답)의 존재 여부에 따른 분류

  • 지도학습: 레이블 존재 (회귀 or 분류)
  • 비지도학습: 레이블 없음 (주성분분석, 군집, 연관분석 등)
  • 준지도학습: 레이블을 만드는 작업 (레이블이 일부분밖에 없어서)
  • 강화학습: 보상이 주어지는 방식

- 실시간 점진적으로 학습을 할 수 있는지 여부

  • 온라인학습: 점진적 학습 가능
    • 실시간으로 데이터가 들어오는 경우도 학습이 가능하다.
  • 배치학습: 점진적 학습 불가능
    • 데이터가 확정이 되어있어야 한다.

- 사례 기반 학습과 모델 기반 학습

  • 사례 기반 학습: 알고 있는 데이터와 새 데이터를 비교하는게 목적
  • 모델 기반 학습: 패턴을 발견해서 예측 모델을 만드는 방식

 
 

4) 지도학습 (Supervised Learning)

- 레이블이 존재하는 학습
- 입력-출력 쌍들을 매핑해주는 함수 학습
- 종류

  • 출력이 이산적일 때: 분류
  • 출력이 연속적일 때: 회귀
  • 출력이 확률인 경우: 추정 (Deep Learning)

- 단점

  • 사용할 수 있는 데이터에 한계 
  • 데이터를 생성하는 데 많은 비용

- 선형회귀
- 로지스틱 회귀
- k-최근접 이웃
- 서포트 벡터 머신
- 결정 트리
- 랜덤 포레스트
- 신경망
 
- 로지스틱 회귀를 제외하고는 분류와 회귀에 모두 사용 가능

  • 로지스틱 회귀는 분류만 가능

 
 

5) 비지도 학습(Unsupervised Learning)

- 레이블이 존재하지 않는 학습
- 데이터에 내재된 고유의 특징을 탐색하기 위해서 사용
- 지도 학습에 비해서 학습하기 어려움
 
- 군집

  • k means 
  • DBSCAN
  • 계층군집
  • 이상치 탐지와 특이치 탐지
  • 원클래스
  • 아이솔레이션 포레스트

- 시각화와 차원 축소

  • PCA (주성분 분석)
  • LLE (지역적 선형 임베딩)
  • t-SNE

- 연관 규칙 학습

  • Apriori
  • eclat

 

6) 준지도 학습

- 라벨링이 일부분만 되어있어서 그 데이터를 이용해서 라벨이 없는 데이터에 라벨을 붙이기 위해서 사용
 
 

7) 강화 학습

- 결과가 바로 주어지지 않고 시간이 지나서 주어지는 방식
- 최근에 로봇 같은 분야에서 많이 이용
 
 

8) 애플리케이션 사례

- Netflix의 영화 추천 시스템: 고객의 평점작성 내역과 구매 내역을 이용해서 추천
- 미국 국가 안보국의 SKYNET: 파키스탄의 테러리스트를 식별해서 사살하기 위한 프로그램

  • 휴대 전화 기록을 이용하여 휴대 전화를 자주 끄거나 USIM을 변경하는 사람을 테러리스트로 식별해서 사살했는데 잘못된 알고리즘으로 무고한 사람이 희생됨

 
 

3. scikit learn

- 파이썬 머신러닝 패키지 중 가장 많이 사용되는 라이브러리
- 가장 Python스러운 API
 - 패키지: sklearn

  • 아나콘다는 내장

 
 

4. 데이터 표현 방식

1) 테이블로서의 데이터

- 기본 테이블은 2차원 데이터 그리드 형태
- 행: 데이터 세트의 개별 요소, sample이라 부르고 행의 개수는 n_sample이라 표현
- 열:각 요소와 관련된 수량,  feature 또는 target이라고 부르는 경우가 많고 열의 개수를 n_features라고 표현

  • feature: 독립적인 데이터 
  • Target : feature로 인해서 만들어지는 데이터로 label이라고도 함

 

2) feature

- 보통은 X라는 변수에 저장
- 특징 행렬이라는 표현을 사용. [n_sampels, n_features]의 모양을 가진 2차원 행렬이라 가정하며 실제 자료형은 numpy의 ndarray나 pandas의 DataFrame으로 되어 있는 경우가 많은데 가끔 sklearn의 희소 행렬인 경우도 있음
- 정량적인 데이터여야 하기 때문에 대부분의 경우는 실수지만 이산적인 데이터아 부울도 가ㅡㄴㅇ
 
 

3) target

- 대상 행렬이라고 하는데 y로 표시
- numpy의 1차원 ndarray나 pandas의 Series인 경우가 많음
- 연속적인 수치가 이산 클래스를 가질 수 있음

import seaborn as sns
iris = sns.load_dataset('iris')
#특징 행렬과 타겟 분리
X_iris = iris.drop('species', axis=1)
Y_iris = iris['species']
#species를 분류하기 위한 데이터 (타겟)
#나머지 데이터는 특징행렬

 
 
 

5. Estimator API(머신러닝 모델 API)

1) 기본 원칙

- 일관성

  • 모든 객체는 일관된 문서를 갖춘 제한된 함수 집합에서 비롯된 공통 인터페이스 공유
  • 거의 모든 객체는 동일한 작업을 수행하는 메소드를 공통으로 소유
  • 템플릿 메소드 패턴: 공통으로 사용될 것 같은 메소드를 인터페이스에 등록하고 이를 클래스에서 구현해서 사용

- 검사 (inspection)

  • parameter: 함수나 기능을 수행하기 위해서 내부적으로 사용하는 데이터. argument, 인수, 인자, 매개변수라고 하기도 함
  • hyper parameter: 개발자가 직접 설정하는 파라미터. 
    • hyper parameter 튜닝은 값을 변경해서 더 좋은 모델을 만들거나 최적의 값을 찾아가는 작업이다.
  • 모든 hyper parameter를 public 속성으로 노출해서 확인이 가능하도록 함

- 제한된 객체 계층 구조

  • 알고리즘만 python 클래스에 의해 표현, 데이터세트는 표준 포맷으로 표현, 매개변수 이름은 문자열
    • 표준 포맷(numpy의 ndarray, pandas의 DataFrame, scipy의 Sparse Matrix)

- 구성

  • 대부분의 머신러닝 작업은 기본 알고리즘의 시퀀스로 나타낼 수 있음 (순서대로)

- 합리적인 기본값

  • 대다수의 하이퍼 파라미터는 라이브러리가 적절한 기본값을 가지도록 정의

 
 

2) 사용 방법

- 적절한 모델 클래스 mport
- 모델 클래스를 인스턴스화 할 때 적절한 하이퍼 파라미터를 설정
- 데이터를 특징 배열과 타겟 배열로 생성
- 모델 클래스의 인스턴스의 fit 메소드를 호출해서 모델을 데이터에 적합하도록 훈련
- 모델을 새 데이터에 적용

  • 지도 학습의 경우: predict 함수에 새로운 데이터 사용해서 예측
  • 비지도 학습의 경우: transform이나 predict를 이용해서 데이터의 속성을 변환하거나 예측

 
 

3) 선형 회귀 수행

 - sklearn.linear_model.LinearRegression

#샘플 데이터 생성
rng = np.random.RandomState(42)

#데이터 50개 생성
x = 10 * rng.rand(50)
#데이터를 이용해서 타겟 데이터 생성 - rng.randn(50)은 잡음
y = 2 * x -1 + rng.randn(50)

#x 데이터를 특징 행렬로 변환
print(x.shape) #1차원 배열 - 특성 배열은 2차원 배열, DataFrame, 희소 행렬
X = x.reshape(x.shape[0], -1) #x[:, np.newaxis]도 가능
print(X.shape) # X는 특성 배열이 되었다.
(50,)
(50, 1)
#추정기 인스턴스 생성
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)

#기존 데이터로 훈련
model.fit(X, y)

#훈련 결과 확인
print("회귀 계수:", model.coef_)
print("절편:", model.intercept_)
회귀 계수: [1.9776566]
절편: -0.9033107255311164
  • 훈련 결과를 확인 - 스스로 만든 파라미터나 결과에는 _가 붙음
  • 하이퍼 파라미터에는 _가 붙지 않음
#회귀는 지도학습: predict 함수로 예측
xfit = np.linspace(-1, 11)
Xfit = xfit[:, np.newaxis]
yfit = model.predict(Xfit)

#시각화
plt.scatter(x, y)
plt.plot(xfit, yfit)
  • 잡음을 섞어서 일정하게 나오지는 않는다.

 
 

4) 평가 지표를 이용한 성능 평가

- 앞의 방식은 모든 데이터를 가지고 훈련을 해서 모델을 만든 후 새로운 데이터에 적용한 방법

  • 여러 모델을 이용해서 만든 모델을 평가할 수 없다.

- 일반적으로 머신러닝에서는 기존 데이터를 분할해서 훈련에 사용하고 나머지 데이터를 이용해서 모델을 평가한다.

  • 여러 머신러닝 추정기를 적용하고 최적의 성능을 가진 모델을 선택하는 것이 일반적

 
 
 

6. Machine Learning 절차

1) 순서

- 작업 환경 설정: 파이썬 설치, 가상 환경 생성, 필요한 패키지 설치

  • 서비스를 만들고자 하는 경우는 가상 환경 생성 작업이 필수

- 문제 정의
- 데이터 수집 
- 데이터 탐색
- 데이터 전처리
- 모델을 선택하고 훈련
- 평가하고 전처리부터 다시 수행
- 최적의 모델을 선택하고 솔루션 제시
- 시스템을 런칭하고 모니터링 하면서 유지보수
 
 
2) 실행

- 환경 설정 
- 데이터 수집 : housing.csv

  • 캘리포니아 주택 데이터셋
  • 블록 별로 여러 컬럼들을 소유하고 있음

- 문제 정의: 중간 주택 가격 예측

  • 주택 가격이 이미 존재하므로 지도학습이다.
  • 주책 가격은 연속형 데이터이므로 회귀 분석

- 데이터를 불러와서 탐색

# 데이터 탐색
housing = pd.read_csv('./python_machine_learning-main/data/housing.csv')
housing
#각 열읱 특성 파악
housing.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   longitude           20640 non-null  float64
 1   latitude            20640 non-null  float64
 2   housing_median_age  20640 non-null  float64
 3   total_rooms         20640 non-null  float64
 4   total_bedrooms      20433 non-null  float64
 5   population          20640 non-null  float64
 6   households          20640 non-null  float64
 7   median_income       20640 non-null  float64
 8   median_house_value  20640 non-null  float64
 9   ocean_proximity     20640 non-null  object 
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
  • total_berooms는 결측치 존재
  • ocean_proximity는 자료형이 객체 - 문자열
#문자열 컬럼은 범주형인지 확인
housing['ocean_proximity'].value_counts()
ocean_proximity
<1H OCEAN     9136
INLAND        6551
NEAR OCEAN    2658
NEAR BAY      2290
ISLAND           5
Name: count, dtype: int64
  • 5개 종류인 것으로 봐서 범주형이다.
#숫자데이터 범위 확인
housing.describe()
#숫자 데이터는 분포 확인 - 히스토그램
housing.hist(bins=50, figsize=(20, 15))
plt.show()
  • 데이터의 분포가 한쪽으로 몰린 경우에는 데이터의 분포가 조금 더 중앙에 많이 몰리도록 수정할 필요가 있다. (로그변환)
  • 좌우로 너무 넓게 펼쳐져있으면 극단치를 제거하는 것에 대해서 고려
  • 각 숫자 데이터의 범위를 비교해서 범위가 차이가 많이 나면 스케일링 고려
    • population이 너무 크다.
    • total_rooms의 max가 이상하다. 

 

- 데이터 분리

  • 훈련 데이터와 테스트 데이터 또는 검증용 데이터로 분리하는 것이 일반적
  • 데이터 전체를 파악하기 전에 분리하는 것이 좋다. 
  • 전체 데이터를 가지고 데이터 탐색을 수행하게 되면 과대적합 가능성이 발생한다.
  • 사람의 뇌가 패턴을 감지해버리기 때문이다.
  • 이러한 현상을 Data Snooping이라고 함

  • 데이터를 나눌 때 보통 7:3, 8:2를 많이 이용하지만 데이터 개수에 따라 다른 선택을 할 수 있다. 일반적으로 데이터가 많으면 훈련 데이터의 비율을 낮추고 데이터의 개수가 적으면 훈련 데이터의 비율을 높인다.
  • 데이터가 아주 많으면 검증용 데이터를 별도로 분할해도 된다.
  • 시계열 데이터는 데이터를 순차적으로 분할한다.

  • 훈련을 여러번 반복해서 수행하면 알고리즘의 비교 측면이나 모든 데이터를 샘플로 사용하게 되는 상황을 방지하기 위해서 일정한 데이터를 랜덤하게 추출하는 경우도 있다.

  • 분류 문제에서는 타겟의 비율이 다르다면 층화 추출도 고려
    • 100일 중 1번만 비가 온다면, 안온다고 할 떄 정확도가 99%가 된다.
      비가 오는 경우와 안오는 경우를 적절히 섞어야 한다. 

# sklearn 의 model_selection.train_test_split API를 이용한 데이터 분리

import sklearn
help(sklearn.model_selection.train_test_split)
  • arrys는 데이터 배열
  • test_size는 테스트 데이터 비율
  • train_size는 훈련 데이터의 비율로 test_size를 설정하면 설정하지 않음
  • 데이터가 아주 많으면 훈련하는 데 시간이 너무 많이 걸릴 수 있어서 설정하기도 함
  • random_state는 시드 번호로 설정하는 것을 권장
  • shuffle은 데이터를 섞을지 여부로 random_state를 설정하면 무의미
  • stratify는 측화추출을 하고자 할 때 데이터의 비율
  • 리턴되는 데이터는 훈련데이터와 테스트데이터의 튜플
#8:2로 분할 - 데이터를 하나의 데이터로 제공하면 2개로 리턴하고
#특성 배열과 타겟 배열 2개를 대입하면 4개로 리턴

train_set, test_set = sklearn.model_selection.train_test_split(housing, 
                                                              test_size=0.2, 
                                                              random_state=42)
#분할된 데이터의 차원 확인
print(train_set.shape)
print(test_set.shape)
(16512, 10)
(4128, 10)
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

result = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(type(result))
<class 'list'>

 
 
- 층화 추출: 계층적 샘플링 (데이터를 일정한 비율로 샘플링)

  • 회귀나 분류를 할 때 타겟의 데이터 분포가 일정하지 않은 경우 왜곡된 결과를 만들 수 있음
    • 분류의 경우 1과 0의 비율을 10대 1 정도 되는 상황에서 훈련 데이터에 0으로 분류되는 데이터가 하나도 없다면 이 경우 모델은 테스트 데이터에 결과가 좋지 않을 것
    • 0으로 분류되는 모든 데이터가 훈련 데이터에 포함되어 버리면 잘못하면 테스트 데이터에 완전하게 맞는 결과가 나와버릴 수 있다.
  • 회귀의 경우 타겟이 연속형이라면 범주형으로 변환해서 수행해야 한다.
    • 이때 사용할 수 있는 함수는 pandas의 cut 함수
    • 데이터와 구간의 리스트, 레이블의 리스트 대입
  • API 함수는 StratifiedShuffleSplit
    • 이 함수의 리턴되는 데이터는 데이터가 아니고 데이터의 인덱스
    • 결과를 가지고 다시 데이터 추출을 해야 함
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(16512, 9)
(4128, 9)
(16512,)
(4128,)

 
- median_income의 비율을 이용한 층화 추출 (계층적 샘플링)

# 연속형 데이터를 범주형으로 변환

#pd.cut(데이터, bins =[경계값 나열] ,labels =[ 레이블 나열 ])

housing['income_cat'] = pd.cut(housing['median_income'],
                               bins=[0, 1.5 ,3.0 ,4.5, 6, np.inf],
                               labels=[1,2,3,4,5])

housing['income_cat'].value_counts()
income_cat
3    7236
2    6581
4    3639
5    2362
1     822
Name: count, dtype: int64

 
# 층화 추출을 위한 객체 생성

#층화 추출을 위한 객체 생성
#n_splits은 조각의 개수
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

result = split.split(housing, housing['income_cat'])

for train_index, test_index in result:
    start_train_set = housing.loc[train_index]
    start_test_set = housing.loc[test_index]
    
#비율확인
start_train_set['income_cat'].value_counts() / len(start_train_set)
  • k-ford validation: 데이터를 몇조각으로 등분해서, 5조각을 만들고 4개로 모델 만들고 1개로 테스트하고. 
  • 인덱스를 리턴해주어서 행단위 추출을 다시 해줘야 한다. (for문)
income_cat
3    0.350594
2    0.318859
4    0.176296
5    0.114462
1    0.039789
Name: count, dtype: float64

 
 
- 데이터 탐색을 완료하기 전에 데이터를 분리하는 것을 권장
 

- 데이터 탐색: 상관계수 출력

  • 데이터의 독립성을 살펴볼 때는 상관계수만 보면 안된다.
    여러개의 피쳐가 다른 피쳐를 설명할 수 있기 때문이다.
  • corr() 함수는 숫자가 아닌 컬럼을 제거하고 사용해야 한다. 
#숫자 이외의 컬럼 제거 - housing.info()
corr_matrix = housing.drop("ocean_proximity", axis=1).corr()
corr_matrix
  • 방 개수와 침대방 개수는 상관계수가 높다. 당연히 높겠지

# pandas로 해보기

from pandas.plotting import scatter_matrix

scatter_matrix(housing[['median_house_value', 'median_income', 'total_rooms']], figsize=(12, 8))
plt.show()

 
#출력된 이미지 저장

plt.savefig('산포도', format='png', dpi=300)

 
 

- 특성 조합을 이용한 탐색

  • 여러 특성을 조합해서 새로운 특성을 만들고, 그 특성과 다른 특성간의 관계 확인하는 것도 중요
  • 방의 개수보다 방의 개수 대비 침실의 개수와 같은 특성을 만들어서 확인해볼 수 있다.
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
corr_matrix = housing.drop('ocean_proximity', axis=1).corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.688075
income_cat            0.643892
total_rooms           0.134153
housing_median_age    0.105623
households            0.065843
total_bedrooms        0.049686
population           -0.024650
longitude            -0.045967
latitude             -0.144160
bedrooms_per_room    -0.255880
Name: median_house_value, dtype: float64
  • median_house_value 를 타겟으로 회귀하려 한다. 
  • 방의 개수와 침실의 개수는 상관계수가 낮다. 그런데 bedrooms_per_room은 -0.25니까 회귀를 하려면 차라리 이걸 쓰는게 낫다.
  • 그래서 도메인 지식이 중요하다.

 

- 피처와 타겟 분리

#훈련 데이터 복제 - 레이블을 제외한 데이터 복제
housing_feature = start_train_set.drop('median_house_value', axis=1)

#훈련 세트를 위해 레이블 삭제
#레이블에 변형을 적용하지 않기 위해서 레이블값도 복제
housing_leabels = start_train_set['median_house_value'].copy()

 
 

- 전처리 - 누락된 데이터 처리
 

#결측치 확인

#NaN 결측치 확인
sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows

 
#누락 행 제거

#total_bedrooms가 누락된 행 제거
housing_feature.dropna(subset=["total_bedrooms"]).info()
 #   Column              Non-Null Count  Dtype   
---  ------              --------------  -----   
 0   longitude           16354 non-null  float64 
 1   latitude            16354 non-null  float64 
 2   housing_median_age  16354 non-null  float64 
 3   total_rooms         16354 non-null  float64 
 4   total_bedrooms      16354 non-null  float64 
 5   population          16354 non-null  float64 
 6   households          16354 non-null  float64 
 7   median_income       16354 non-null  float64 
 8   ocean_proximity     16354 non-null  object  
 9   income_cat          16354 non-null  category
  • 모두 16354!

# 누락 열 제거

#열 제거
sample_incomplete_rows.drop('total_bedrooms', axis=1)

 
# 누락 값 중간값으로 대체

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")

#숫자로된 컬럼만 추출해서 NaN->중간값 대체
housing_num = housing.select_dtypes(include=[np.number])
imputer.fit(housing_num)

X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
                          index = list(housing.index.values))

housing_tr.head()

 
 

- 범주형 데이터 처리

  • 머신러닝은 숫자만 다룸
  • 범주형이나 문자열 데이터가 있다면 숫자로 변환해야 함
  • 범주형의 경우 일련번호 형태로 변경하거나 원핫인코딩 필요
#범주형 데이터 추출
housing_cat = housing_features['ocean_proximity']
#일련번호로 만들기
housing_cat_encoded, housing_categoris = housing_cat.factorize()
print(housing_cat_encoded[:10])
print(housing_categoris)
[0 1 0 1 2 3 2 2 2 2]
Index(['INLAND', 'NEAR OCEAN', '<1H OCEAN', 'NEAR BAY', 'ISLAND'], dtype='object')
  • 원핫 인코딩 - 범주형의 개수만큼 열을 만들어서 해당하는 열에만 1 표시
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features['ocean_proximity'])
print(result[:10])
ValueError: Expected 2D array, got 1D array instead:
array=['INLAND' 'NEAR OCEAN' 'INLAND' ... '<1H OCEAN' '<1H OCEAN' 'INLAND'].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
[[1.]
 [4.]
 [1.]
 [4.]
 [0.]
 [3.]
 [0.]
 [0.]
 [0.]
 [0.]]
#원 핫 인코딩 - 범주형의 개수 만큼 열을 만들어서 해당하는 열에만 1을 표시
from sklearn.preprocessing import OneHotEncoder
oneHotEncoder = OneHotEncoder()

#기본적으로 희소 행렬(sparse matrix)
result = oneHotEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
#밀집 행렬로 변환
print(result.toarray()[:10])
  (0, 1) 1.0
  (1, 4) 1.0
  (2, 1) 1.0
  (3, 4) 1.0
  (4, 0) 1.0
  (5, 3) 1.0
  (6, 0) 1.0
  (7, 0) 1.0
  (8, 0) 1.0
  (9, 0) 1.0

[[0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]]

 
 

- 특성 스케일링

  • 입력 숫자 특성들의 스케일이 많이 다르면 정확한 예측을 하지 못하는 경우가 발생
  • 입력 숫자 특성들만 스케일링을 수행하는데 타겟의 경우는 범위가 너무 큰 경우 로그 스케일링을 한다.
  • 훈련 데이터와 테스트 데이터로 분할된 경우 일반적으로 훈련 데이터에만 fit 수행하고
    훈련 데이터와 테스트 데이터에 transform 수행
  • min-max scaler와 standard scaler 주로 이용

- 정규화 : MinMaxScaler

  • (데이터 - 최소값) / (최대값 - 최소값)
  • 최대값이 이상치인 경우 영향을 많이 받게 됨

- 표준화: StandardScaler

  • 먼저 평균을 뺀 후 (평균=0) 표준편차로 나눠서 분포의 분산이 1이 되도록 함
  • 범위의 상한과 하한이 없어서 어느정도 값이 만들어지질지 예측하기 어려움
    • 딥러닝에서는 0 ~ 1의 값만을 요구하는 경우가 있어서 사용이 불가
  • 이상치의 영향을 덜 받는다. 로버스트함

- 전처리 작업을 위한 Pipeline

  • 연속해서 작업 수행 - 여러 작업을 하나로 묶어서 수행하는 것
  • 결측치 대체를 하고 스케일링을 수행한다고 했을 때 따로 작업을 해도 되지만 묶어서 한번에 수행 가능
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

#수행하고자 하는 작업을 이름 과 추정기를 튜플로 묶어서 list로 설정해주면 됩니다.
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy = 'median')),
    ('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)
print(housing_num_tr)
[[-0.94135046  1.34743822  0.02756357 ...  0.73260236  0.55628602    -0.8936472 ]
 [ 1.17178212 -1.19243966 -1.72201763 ...  0.53361152  0.72131799   1.292168  ]
 [ 0.26758118 -0.1259716   1.22045984 ... -0.67467519 -0.52440722   -0.52543365]
 ...
 [-1.5707942   1.31001828  1.53856552 ... -0.86201341 -0.86511838    -0.36547546]
 [-1.56080303  1.2492109  -1.1653327  ... -0.18974707  0.01061579    0.16826095]
 [-1.28105026  2.02567448 -0.13148926 ... -0.71232211 -0.79857323    -0.390569  ]]
  • 수행하고자 하는작업을이름과 추정기를 튜플로 묶어서 list로 설정

- ColumnTransformer

  • sklearn 0.20 버전에서 추가된 변환기로 여러 특성에 다른 변환 처리를 할 수 있도록 해주는 클래스
  • Pipeline과 유사하게 생성, 튜플을 만들 때 (변환기 이름, 변환기, 컬럼 이름리스트) 형태로 대입
from sklearn.compose import ColumnTransformer

#숫자 컬럼 이름 리스트
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
#print(num_attribs)

#열 별로 다른 변환기를 적용하기 위한 인스턴스 생성
full_pipeline = ColumnTransformer([
    ("num", num_pipeline, num_attribs),
    ("cat", OneHotEncoder(), cat_attribs)
])

#변환기 적용
hosing_prepared = full_pipeline.fit_transform(housing)
print(hosing_prepared)
[[-1.32783522  1.05254828  0.98214266 ...  0.          1.     0.        ]
 [-1.32284391  1.04318455 -0.60701891 ...  0.          1.     0.        ]
 [-1.33282653  1.03850269  1.85618152 ...  0.          1.     0.        ]
 ...
 [-0.8237132   1.77823747 -0.92485123 ...  0.          0.     0.        ]
 [-0.87362627  1.77823747 -0.84539315 ...  0.          0.     0.        ]
 [-0.83369581  1.75014627 -1.00430931 ...  0.          0.     0.        ]]

 
#피처 전처리

housing_prepared = full_pipeline.fit_transform(housing_features)

 
 

- 모델 적용 및 테스트

- 선형 회귀 모델

#선형 회귀 모델을 이용
from sklearn.linear_model import LinearRegression

#예측 모델 인스턴스 생성
lin_reg = LinearRegression()

#훈련
lin_reg.fit(housing_prepared, housing_labels)

#테스트
some_data = housing_features.iloc[:5]
some_labels = housing_labels.iloc[:5]

#샘플 피처 전처리
some_data_prepared = full_pipeline.transform(some_data)

print("예측한 값:", lin_reg.predict(some_data_prepared))
print("실제 값:", list(some_labels))
예측한 값: [ 88983.14806384 305351.35385026 153334.71183453 184302.55162102
 246840.18988841]
실제 값: [72100.0, 279600.0, 82700.0, 112500.0, 238300.0]

 
 

- 평가 지표

  • 모델을 생성하고 나면 이 모델을 다른 모델과 비교하기 위해서 평가지표 사용
  • 회귀의 평가지표로 사용 - RMSE, RMAE
    • RMSE: 예측한 값과 실제 값의 차이(residual-잔차)를 제곱해서 더한 후 제곱근을 해서 사용
    • RMAE: 잔차에 절대값을 적용한 후 합계를 구한 것
    • 이 값이 적은 쪽이 우수한 모델
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

#모델을 만들 때 사용한 데이터를 가지고 예측
housing_predictions = lin_reg.predict(housing_prepared)

#잔차 제곱합
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)

#잔차의 절대값 합
lin_mae = mean_absolute_error(housing_labels, housing_predictions)

print("잔차 제곱합:", lin_mse)
print("잔차 제곱합의 제곱근:", lin_rmse)
print("잔차 절대값합:", lin_mae)
잔차 제곱합: 4767980139.451871
잔차 제곱합의 제곱근: 69050.56219504567
잔차 절대값합: 49905.329442715316
  • 제곱합은 단위가 너무 큼. 판정하기 어렵기 때문에 제곱근을 해서 스케일링한다.
  • 집값은 120000 ~ 260000 사이가 많은데, 실제로는 7만에서 5만 차이가 나니까 잔차가 많다.
  • 훈련에 사용한 데이터로 테스트 했는데 훈련에 사용한 데이터도 잘 맞지 않으면 과소 적합이라고 한다.
    • 데이터를 더 수집하기
    • 다른 모델(알고리즘)을 사용해보기
    • 하이퍼파라미터를 조정해보기
  • 훈련 데이터에는 잘 맞지만, 테스트 데이터나 새로운 데이터에 잘 맞지 않으면 과대 적합이라고 한다. 

 

- 새로운 모델 적용 

- Decision Tree

  • 트리 모델은 일반적인 형태로 검증하지 않고 데이터를 K개의 그룹으로 나눈 후 (k-1)개의 데이터 그룹을 사용해서 모델을 생성하고 나머지 그룹을 이용해서 테스트 수행하고 이 작업을 k번 반복해서 그 때의 평균을 리턴한다.
  • sklearn에서는 점수가 높은 쪽이 좋다고 생각하기 때문에 평가지표를 설정할 때 (-)를 곱해서 사용한다.
from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)

housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
0.0
  • 얜 안되겠네

- K-fold cross-validation (K 겹 교차 검증)

 

from sklearn.model_selection import cross_val_score

#10개의 K를 설정
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, 
                        scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
print(tree_rmse_scores)
[71177.6601991  69770.07865373 64770.5639395  68536.60203993
 67057.08155801 68847.12456973 70977.38255647 69208.86346929
 67187.87131535 73280.38732407]
  • 그닥 별로. 오히려 나쁜듯

 

- 그리드 탐색

  • 가장 좋은 하이퍼 파라미터를 찾는 것
    • 파라미터: 함수 또는 메소드가 수행할 때 결정하는 데이터
    • 하이퍼 파라미터: 사용자가 직접 설정하는 데이터
      대부분의 하이퍼파라미터는 가장 알맞은 기본값을 소유하고 있다.
  • GridSearchCV를 이용하면 여러종류의 하이퍼 파라미터를 설정해서 훈련한 후 가장 좋은 하이퍼 파라미터를 추천
  • 파라미터를 설정할 때는 list의 dict로 설정하게 되는데 dict에 하이퍼 파라미터 이름과 값의 list를 설정하는데 하나의 dict 내에 있는 파라미터는 모든 조합을 가지고 수행하고 각 dict는 별개로 수행
  • 시간이 오래 걸릴 가능성이 높음
from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(random_state=42)

#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_grid = [{'n_estimators':[3, 10, 30], 'max_features': [2, 4, 6, 8]}, 
              {'bootstrap':[False], 'n_estimators':[3, 10], 'max_features':[2, 3, 4]}]

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                          scoring='neg_mean_squared_error', 
                          return_train_score=True, n_jobs=2)

grid_search.fit(housing_prepared, housing_labels)
print(grid_search.best_params_)
#내장 파라미터는 뒤에 _ 붙여야 함
{'max_features': 8, 'n_estimators': 30}

 
# 평가 점수 확인

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
    print(np.sqrt(-mean_score), params)
63827.76261554265 {'max_features': 2, 'n_estimators': 3}
55056.82212305312 {'max_features': 2, 'n_estimators': 10}
52673.5498401615 {'max_features': 2, 'n_estimators': 30}
60299.48845134689 {'max_features': 4, 'n_estimators': 3}
53106.41271952157 {'max_features': 4, 'n_estimators': 10}
50370.55528306362 {'max_features': 4, 'n_estimators': 30}
58363.22748437211 {'max_features': 6, 'n_estimators': 3}
52446.057900340325 {'max_features': 6, 'n_estimators': 10}
50177.91173851986 {'max_features': 6, 'n_estimators': 30}
58058.12321723554 {'max_features': 8, 'n_estimators': 3}
51849.42681935635 {'max_features': 8, 'n_estimators': 10}
49941.11534754462 {'max_features': 8, 'n_estimators': 30}
62820.05402812565 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
53846.18083156347 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59026.17902108823 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52996.55803561763 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
58842.47703118809 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
51891.75100173946 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

 
#최적의 모델 가져오기

print(grid_search.best_estimator_)
RandomForestRegressor(max_features=8, n_estimators=30, random_state=42)

 

- 랜덤 서치

  • 그리드 서치는 파라미터 값을 직접 입력해서 선택하도록 하는데 파라미터가 적을 때는 유용하지만 많을 때는 어려움
  • RandomizedSearchCV는 파라미터의 상한과 하한을 
forest_reg = RandomForestRegressor(random_state=42)

from scipy.stats import randint
#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_distribs = {'n_estimators':randint(low=1, high=200), 
                  'max_features': randint(low=1, high=8)}

from sklearn.model_selection import RandomizedSearchCV
random_search = RandomizedSearchCV(forest_reg, param_distributions = param_distribs, cv=5,
                          scoring='neg_mean_squared_error', n_jobs=-1,
                                  n_iter=10)

grid_search.fit(housing_prepared, housing_labels)

 
- 앙상블

  • 여러 개의 모델을 사용하는 방식
  • DecisionTree는 하나의 트리를 이용하지만 RandomForest는 여러개의 DecisionTree를 가지고 예측

 
- 테스트를 할 때는 모델을 만들 때 사용한 데이터가 아닌 테스트 데이터로 수행하기도 함

  • Overfitting(과대적합): 훈련 데이터에 잘 맞지만 테스트 데이터에 잘 맞지 않는 상황
  • Underfitting(과소적합): 훈련데이터에도 잘 맞지 않는 상황

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

1. Kaggle 자전거 대여 수요 예측 

- 예측 모델 및 분석 대회 플랫폼

- 현재는 구글이 운영

- url - https://www.kaggle.com/c/bike-sharing-demand 

 

Bike Sharing Demand | Kaggle

 

www.kaggle.com

- 미션: 날짜, 계절, 근무일 여부, 온도, 체감온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측

- 유형: 회귀

- 평가 지표: RMSLE

 

  • sampleSubmission.csv 참고

 

실제 데이터 확인

* test 데이터에 없는 train 데이터의 피처는 삭제 해야함

 

 

 

2. 데이터 탐색

# 데이터 가져오기

import pandas as pd

train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')
submission = pd.read_csv('./bike/sampleSubmission.csv')

 

 

# 훈련 데이터 구조 확인

train.info()

  • 훈련 데이터에는 결측치가 없다.
  • datetime이 진짜 객체 타입인지 확인 (dtype)

# 테스트 데이터 구조 확인

test.info()
  • train 데이터에는 존재하지만 test 데이터에는 존재하지 않는 컬럼을 발견
    • casual, registered, count
    • count는 타겟이라서 없는 것
    • 훈련을 할 때 casual과 registered는 제외

 

- 피처에 대한 설명

  • datetime - hourly date + timestamp  
  • season -  1 = spring, 2 = summer, 3 = fall, 4 = winter
  • holiday - whether the day is considered a holiday   *0: 공휴일 아닌 날 / 1: 공휴일
  • workingday - whether the day is neither a weekend nor holiday   *0: 주말 및 국경일 / 1: 근무날
  • weather
    1: Clear, Few clouds, Partly cloudy, Partly cloudy
    2: Mist + Cloudy, Mist + Broken clouds, Mist + Few clouds, Mist
    3: Light Snow, Light Rain + Thunderstorm + Scattered clouds, Light Rain + Scattered clouds
    4: Heavy Rain + Ice Pallets + Thunderstorm + Mist, Snow + Fog
  • temp - temperature in Celsius
  • atemp - "feels like" temperature in Celsius 
  • humidity - relative humidity
  • windspeed - wind speed
  • casual - number of non-registered user rentals initiated   *예약하지 않은 사용자의 대여 횟수
  • registered - number of registered user rentals initiated   *예약 사용자의 대여 횟수
  • count - number of total rentals

- 날짜의 경우는 DateTime으로 변환해서 분할해보는 것이 좋다.

train['datetime'] = train.datetime.apply(pd.to_datetime)
0   datetime    10886 non-null  datetime64[ns]
train ['year'] = train['datetime'].apply(lambda x:x.year)
train ['month'] = train['datetime'].apply(lambda x:x.month)
train ['day'] = train['datetime'].apply(lambda x:x.day)
train ['hour'] = train['datetime'].apply(lambda x:x.hour)
train ['minute'] = train['datetime'].apply(lambda x:x.minute)
train ['second'] = train['datetime'].apply(lambda x:x.second)
train ['weekday'] = train['datetime'].apply(lambda x:x.weekday())

 

- 범주형은 원래 이름으로 변경해 두는 것이 좋다. 

  • 탐색을 할 때는 시각화를 많이 하게 되는데 시각화할 때 0이나 1같은 숫자보다는 spring이나 fall 같은 명시적인 의미를 가진 데이터가 보기 좋기 때문이다.
  • season, weather 변환
train['season'] = train['season'].map({
    1:'봄', 2:'여름', 3:'가을', 4:'겨울'
})

train['weather'] = train['weather'].map({
    1:'맑음', 2:'흐림', 3:'약한 눈/비', 4:'강한 눈/비'
})

 

#시각화를 위한 설정

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

 

#한글을 위한 설정

#시각화에서 한글을 사용하기 위한 설정
import platform
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

 

#경진대회에서만 사용

import warnings
warnings.filterwarnings(action='ignore')

 

- 타겟 확인

  • 타겟이 범주형인 경우 확인해서 특정 범주의 데이터가 아주 많거나 작으면 층화 추출이나 오버샘플링이나 언더샘플링 고려
  • 타겟이 연속형인 경우 정규 분포에 가까운지 확인해서 한쪽으로 몰려있으면 로그변환으로 분포 변경
sns.displot(train['count'])
  • 데이터의 분포가 왼쪽으로 치우쳐져 있다.

# 타겟을 로그 변환해서 시각화

import numpy as np
sns.distplot(np.log(train['count']))

 

# 년월일 시분초에 따른 타겟의 변환

figure, axes = plt.subplots(nrows=3, ncols=2)

sns.barplot(x='year', y='count', data=train, ax=axes[0, 0])
sns.barplot(x='month', y='count', data=train, ax=axes[0, 1])
sns.barplot(x='day', y='count', data=train, ax=axes[1, 0])
sns.barplot(x='hour', y='count', data=train, ax=axes[1, 1])
sns.barplot(x='minute', y='count', data=train, ax=axes[2, 0])
sns.barplot(x='second', y='count', data=train, ax=axes[2, 1])
  • 각 데이터가 유의미한 변화를 가져오면 훈련에 사용을 해야 하고 거의 변화가 없다면 제거
  • 분산이 아주 작다면 유의미한 변화를 가져올 수 없기 때문에 제거
    • 일은 별로 상관이 없음 - 제거
    • 시간은 유의미한 변화가 있음
    • 분하고 초는 0인걸 보니 빌릴 때 시간까지만 기록해놨음 - 제거

# 계절, 날씨, 공휴일 여부, 근무일 별 대여 수량 시각화

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.barplot(x='season', y='count', data=train, ax=axes[0, 0])
sns.barplot(x='weather', y='count', data=train, ax=axes[0, 1])
sns.barplot(x='holiday', y='count', data=train, ax=axes[1, 0])
sns.barplot(x='workingday', y='count', data=train, ax=axes[1, 1])
  • holiday, workingday 여부는 상관이 없는듯함
  • 직선이 분산이기도 하다.  

# 박스플롯 - 이상치 분포 여부 확인 가능

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.boxplot(x='season', y='count', data=train, ax=axes[0, 0])
sns.boxplot(x='weather', y='count', data=train, ax=axes[0, 1])
sns.boxplot(x='holiday', y='count', data=train, ax=axes[1, 0])
sns.boxplot(x='workingday', y='count', data=train, ax=axes[1, 1])

 

# 근무일, 공휴일, 요일, 계절, 날씨에 따른 시간대별 대여수량 확인

figure, axes = plt.subplots(nrows=5)

sns.pointplot(x='hour', y='count', hue='workingday', data=train, ax=axes[0])
sns.pointplot(x='hour', y='count', hue='holiday', data=train, ax=axes[1])
sns.pointplot(x='hour', y='count', hue='weekday', data=train, ax=axes[2])
sns.pointplot(x='hour', y='count', hue='season', data=train, ax=axes[3])
sns.pointplot(x='hour', y='count', hue='weather', data=train, ax=axes[4])
  • workingday는 출퇴근 시간에, 휴일은 낮에 많다.

 

- 연속형 데이터는 피처와 상관관계를 파악할 필요가 있다.

  • 상관관계가 거의 없다면 제거 가능
  • 상관관계 파악 방법
    • 상관계수 확인
    • 산포도(regplot: 선형회귀 직선을 같이 출력)
    • 상관계수 확인 이후 히트맵 같은 도구로 시각화 함

# 온도, 체감온도, 풍속, 습도별 대여수량 확인

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.regplot(x='temp', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[0,0])
sns.regplot(x='atemp', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[0,1])
sns.regplot(x='windspeed', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[1,0])
sns.regplot(x='humidity', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[1,1])

 

 

 

 

 

3. 규제가 없는 선형회귀 적용

  • 이전에 데이터 탐색을 할 때 변경된 내용이 있을 수 있기 때문에 다시 가져오기

# 데이터 가져오기

import pandas as pd

train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')

 

# 데이터 전처리

  • 이상치 제거
    • 범주형에서 기본값 이외의 데이터나 수치형 데이터에서 범위에 맞지 않는 데이터 혹은 극단치
    • 도메인에 따라 정상적인 입력이라도 이상치로 판단할 수 있다. 
      ex. 심한 눈이나 비가 내리는데 자전거를 대여하는 상황 등
train = train[train['weather'] != 4]

 

 

# train 데이터와 test 데이터 합치기 (동일한 구조 만들기)

  • 일반적인 경우 train 데이터와 test 데이터 구조가 같다. 
  • 경진대회의 경우 train.csv와 test.csv 구조가 다르기도 하다. test.csv에는 타겟이 없다.
    • 경진대회는 train 데이터를 가지고 모델을 만들어서 test 데이터의 타겟을 예측해서 제출하고 채점하기 때문
all_data_temp = pd.concat([train, test])

 

- 여러개의 데이터를 행 방향으로 결합할 때 주의할 점!

  • 별도의 인덱스를 설정하지 않으면 인덱스는 0부터 시작하는 일련 번호라서
    인덱스를 설정하지 않은 상태로 만든 DataFrame을 행 방향으로 결합하면 인덱스가 중첩될 수 있다.
  • 행은 분명 17383개인데, 번호는 6492번까지밖에 없는 상황
    인덱스가 중복돼서 나오기 때문에 데이터 구별이 안될 수 있으므로 기존 인덱스는 무시하고 결합한다.

# 기존 인덱스 무시하고 결합

all_data_temp = pd.concat([train, test], ignore_index=True)

 

 

# 파생 피처 생성

  • datetime을 이용해서 새로운 열 (년 월 일 시 분 초 요일) 생성
  • 탐색했을 때 필요 없었던 일, 분, 초 제거
  • 년월일, 년, 시, 요일 생성해보기
all_data['datetime']
  1. 공백단위로 쪼개기 _ split 사용 - 정규표현식
  2. 문자 개수만큼 읽어오기 _ substring(위치, 개수)
from datetime import datetime

#datetime 필드에서 앞의 날짜 부분만 잘라서 date 필드 생성
all_data['date'] = all_data['datetime'].apply(lambda x : x.split()[0])

#datetime 필드에서 앞의 년도 부분만 잘라서 year 필드 생성
all_data['year'] = all_data['datetime'].apply(lambda x : x.split()[0].split('-')[0])

#datetime 필드에서 앞의 시간 부분만 잘라서 hour 필드 생성
all_data['hour'] = all_data['datetime'].apply(lambda x : x.split()[1].split(':')[0])

#datetime 필드에서 요일로 weekday 필드 생성
#날짜 컬럼을 datetime으로 변환하고 weekday 메소드를 호출해서 요일 리턴
all_data['weekday'] = all_data['date'].apply(
    lambda x : datetime.strptime(x, '%Y-%m-%d').weekday())

 

 

# 불필요한 피처 제거

  • casual, registered, datetime, date, windspeed

<초보자의 코딩>

all_data = all_data.drop(['casual', 'registered', 'datetime', 'date', 'windspeed'], axis=1)
  • literal을 많이 쓴다. 그냥 지워버린다.
  • 어떤 컬럼을 지웠는지 찾고 싶을 때 나중에 찾아봐야 한다.

<숙련자의 코딩>

all_data = all_data.drop(drop_features, axis=1)
  • 삭제 작업을 할 때는 내가 무엇을 지웠는지를 변수에 저장해두는 것이 좋다.
  • 리터럴을 직접 이용하는 것보다 변수 이용

 

# 모델 생성 및 훈련

#훈련 데이터와 테스트 데이터 분리
X_train = all_data[~pd.isnull(all_data['count'])]
X_test = all_data[pd.isnull(all_data['count'])]

#피처와 타겟 분리
X_train = X_train.drop(['count'], axis=1)
X_test = X_test.drop(['count'], axis=1)

#원천 데이터에서 count 추출 후 타겟으로 만들기
y = train['count']

 

 

#평가 지표 함수 생성

  • 평가지표: RMSLE
  • 타겟이 치우쳐 있어서 로그 변환을 수행하는 것이 좋은 모델을 만들 수 있다.
import numpy as np

#y_true는 실제 값, y_pred는 예측 값, convertExp는 로그 변환 여부
def rmsle(y_true, y_pred, convertExp=True):
    #로그 변환을 한 경우에는 원래 값으로 복원
    if convertExp:
        y_true = np.exp(y_true)
        y_pred = np.exp(y_pred)
    #로그 변환을 할 때 1을 더해주지 않으면 0이 될 수 있고 에러 발생
    #로그 변환을 할 때는 1을 더해서  이를 방지해야 한다.
    log_true = np.nan_to_num(np.log(y_true + 1))
    log_pred = np.nan_to_num(np.log(y_pred + 1))
    
    #RMSLE 계산
    output = np.sqrt((np.mean(log_true-log_pred)**2))

 

#모델 생성 및 훈련

#모델 생성 및 훈련
from sklearn.linear_model import LinearRegression

linear_reg_model = LinearRegression()

#타겟의 로그 변환
log_y = np.log(y)

#훈련
linear_reg_model.fit(X_train, log_y)

#예측
preds = linear_reg_model.predict(X_train)

print("일반 선형 회귀의 RMSLE:", rmsle(log_y, preds, True))
일반 선형 회귀의 RMSLE: 1.0204980189305026

 

답안 생성 후 제출

linearreg_preds = linear_reg_model.predict(X_test)

#로그변환을 원래 데이터로 복원
submission['count'] = np.exp(linearreg_preds)
submission.to_csv('submission.csv', index=False)

 

 

 

 

 

4. 규제가 있는 모델 

  • Ridge(가중치 감소)
  • Lasso(제거 가능)
  • ElasticNet(절충형) : alpha라는 규제 강도가 있음

# 모델 생성 및 하이퍼 파라미터 튜닝

from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV
from sklearn import metrics

#기본 모델
ridge_model = Ridge()

#파라미터 생성
ridge_params = {
    'max_iter' : [3000], 
    'alpha' : [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400, 800, 900, 1000]
}

#사용자 정의 함수를 평가 지표로 사용
rmsle_scorer = metrics.make_scorer(rmsle, greater_is_better=False)
gridsearch_ridge_model = GridSearchCV(estimator = ridge_model,
                                     param_grid = ridge_params, 
                                     scoring = rmsle_scorer,
                                     cv=5)

log_y = np.log(y)
gridsearch_ridge_model.fit(X_train, log_y)

 

# Ridge

#최적의 모델로 예측
preds = gridsearch_ridge_model.best_estimator_.predict(X_train)
print("릿지 적용한 RMSLE:", rmsle(log_y, preds, True))
릿지 적용한 RMSLE: 1.020497980747181
  • 이전 선형 회귀와 별 차이가 없네.. 규제를 줬는데도?
  • 그럼 얘는 선형이 아니구먼
  • 비선형으로 예측허자

 

결론

규제를 추가해도 별 다른 성능 현상이 없으면 대부분의 경우 비선형이기 때문에 규제로 성능 향상을 꾀하기는 어려운 상황

 

 

 

 

5. 부스팅 모델

RandomForest, AdaBoosting, GradientBoosting, HistGradientBoosting, XGBM, LightGBM, CatBoost

pip install xgboost
pip install lightGBM
pip install catboost
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import HistGradientBoostingRegressor

from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from catboost import CatBoostRegressor

 

#인스턴스 생성

rf_reg = RandomForestRegressor()
gbm_reg = GradientBoostingRegressor(n_estimators=500)
hgbm_reg = HistGradientBoostingRegressor(max_iter=500)

xgb_reg = XGBRegressor(n_estimators=500)
lgbm_reg = LGBMRegressor(n_estimators=500)
catgbm_reg = CatBoostRegressor(iterations=500)

 

#파라미터 값 생성

params = {'random_state':[42], 'n_estimators': [100, 300, 500, 700]}
hgbm_params = {'random_state':[42], 'max_iter': [100, 300, 500, 700]}
catgbm_params = {'random_state':[42], 'iterations': [100, 300, 500, 700]}

 

#타겟 값 생성

log_y = np.log(y)

 

#랜덤 포레스트

gridsearch_random_forest_model = GridSearchCV(estimator=rf_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_random_forest_model.fit(X_train, log_y)
preds = gridsearch_random_forest_model.best_estimators_.predict(X_train)
print("랜덤 포레스트의 RMSLE 값:", rmsle(log_y, preds, True))
랜덤 포레스트의 RMSLE 값: 0.11124607292494956

 

#XGB

gridsearch_xgb_model = GridSearchCV(estimator=xgb_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_xgb_model.fit(X_train.values, log_y.values)
preds = gridsearch_xgb_model.best_estimator_.predict(X_train.values)
print("XGB의 RMSLE 값:", rmsle(log_y, preds, True))
  • XBG는 데이터프레임을 사용할 수 없다. numpy의 ndarray만 가능
XGB의 RMSLE 값: 0.19626309218676358

 

#GBM

gridsearch_gbm_model = GridSearchCV(estimator=gbm_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_gbm_model.fit(X_train, log_y)
preds = gridsearch_gbm_model.best_estimator_.predict(X_train)
print("GBM의 RMSLE 값:", rmsle(log_y, preds, True))
GBM의 RMSLE 값: 0.27007893677151384

 

#LGBM

gridsearch_lgbm_model = GridSearchCV(estimator=lgbm_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_lgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_lgbm_model.best_estimator_.predict(X_train.values)
print("LGBM의 RMSLE 값:", rmsle(log_y, preds, True))
LGBM의 RMSLE 값: 0.25772337517896476

 

# HistGradientBoosting

  • 피처를 255개 구간으로 나누어 학습
gridsearch_hgbm_model = GridSearchCV(estimator=hgbm_reg, 
                                             param_grid=hgbm_params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_hgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_hgbm_model.best_estimator_.predict(X_train.values)
print("Hist의 RMSLE 값:", rmsle(log_y, preds, True))
Hist의 RMSLE 값: 0.23245216702971983

 

#CatBoost

gridsearch_catgbm_model = GridSearchCV(estimator=catgbm_reg, 
                                             param_grid=catgbm_params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_catgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_catgbm_model.best_estimator_.predict(X_train.values)
print("Catboost의 RMSLE 값:", rmsle(log_y, preds, True))

...
Catboost의 RMSLE 값: 0.23012371614685823

 

 

- 부스팅 사용 결과

  • RF: 0.111
  • GBM: 0.27
  • XGBM: 0.196
  • LGBM: 0.257
  • Hist: 0.232 
  • Cat: 0.230

* 시간 때문에 n_estimators의 값만 하이퍼 파라미터 튜닝을 했는데 학습률이나 max_depth 등의 매개변수도 파라미터 튜닝을 하게 되면 더 좋은 성능을 기대할 수 있다.

* 필요하다면 피처 엔지니어링도 수행해보는 것이 좋다.

 

result = gridsearch_random_forest_model.best_estimator_.predict(X_test)
#로그변환을 원래 데이터로 복원
submission['count'] = np.exp(result)
submission.to_csv('submission.csv', index=False)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 분류  (0) 2024.03.07
[Python] 머신러닝  (0) 2024.03.07
[Python] 벡터 연산에서 기억할 부분  (0) 2024.02.27
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 확률 분포 모형  (1) 2024.02.26

1. 공통 코드

from pathlib import Path
import random

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
import platform
import matplotlib
import seaborn as sns

import scipy as sp
from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats import power
%matplotlib inline

path = "c:/Windows/Fonts/malgun.ttf"
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
    
matplotlib.rcParams['axes.unicode_minus'] = False

import warnings
warnings.filterwarnings('ignore')

 

 

2. 추론 통계 - 통계적 추론

- 기술통계: 주어진 데이터의 분포나 빈도, 평균 등의 통계량으로 데이터를 설명하기 위한 목적

  vs 추론통계: 주어진 데이터(sample)을 이용하여 모집단의 특성(모수)을 추론하는 것

  • 모집단은 전수조사가 어렵고, 정확한 범위를 알지 못하는 경우도 있어서 분산이나 평균을 구하기 어렵다. 
  • 그래서 실제 모집단에서 표본 몇십~몇백개를 추출하여 표본의 분산이나 표본의 평균을 사용하는 경우가 대부분이다.

- 추론: 제한된 데이터로 주어진 실험 결과를 더 큰 과정이나 모집단에 적용하려는 의도를 반영하는 것

- 추론통계: 가설 검정으로 모수를 판단하는 것

  1. 가설 세우기
  2. 실험 설계하기
  3. 데이터 수집하기
  4. 추론 및 결론 도출하기

 

 

3. 확률 분포의 추정

- 분석할 데이터는 어떤 확률 변수로부터 실현된 표본이다 가정

- 우리의 관심은 데이터가 아니라 데이터를 만들어내는 확률 변수의 분포

- 학률론적인 관점에서 데이터는 확률 변수의 분포를 알아내는 데 참고 자료일 뿐

 

3-1) 확률 분포의 결정 과정

- 확률 변수가 어떤 확률 분포를 따르는지 알아내는 과정

- 데이터로부터 해당 확률 분포의 모수의 값을 구함

-  데이터의 생성 원리나 특성을 알면 추측 가능. 히스토그램으로 확률 분포의 모양을 살펴보고 힌트를 얻을 수 있음.

  • 데이터가 0 또는 1: 베르누이 분포
  • 데이터가 카테고리 값 - 카테고리 분포
  • 데이터가 0과 1 사이의 실수 - 베타 분포
  • 데이터가 항상 0 또는 양수 - 로그 정규 분포, 감마 분포, F 분포, 카이 제곱 분포, 지수 분포, 하프코시분포 등
  • 데이터에 크기 제한이 없는 실수 - 정규 분포, t 분포, 코시 부포, 라플라스 분포 등
  • 균일 분포
  • 푸아송 분포 (=지수 분포)
  • 베이블 분포 (실제 사건은 패턴을 가지고 시간이 늘어나거나 줄어들게 되는데 그 때 지수분포 대신 사용)

 

 

4. 검정

- 데이터 뒤에 숨어있는 확률 변수의 분포와 모수에 대한 가설의 진위를 정량적으로 증명하는 작업

- 어떤 동전을 15번 던졌더니 앞면이 12번 나왔다.이 동전은 조작되지 않은 동전인가, 조작된 동전인가를 판단하는 것

- 주식의 수익률이 -2.5%, -5%, 4.3%, -3.7%, -5.6%일 때 이 주식은 장기적으로 수익일까 손실일까?

 

 

5. 추정

5-1) 점추정

  • 모집단의 특성을 하나의 값으로 추정하는 방식
  • 분산이나 표준편차를 구해서 모집단의 분산이나 표준편차라고 추정할 때   자유도: 데이터개수 -1
# 모집단의 특성을 하나의 값으로 추정
fish = pd.read_csv("./data/fish_length.csv")

#표본의 평균
mu = sp.mean(fish['length'])

print(mu)
4.187039324504523
  • 표본 평균이 4.187 이므로 모 평균도 4.187 이라고 추정 - 점 추정
  • 분산이나 표준 편차를 구해서 모집단의 분산이나 표준 편차라고 추정할 때는 자유도는 데이터개수 - 1 입니다.
sigma = sp.var(fish['length'], ddof=1)
print(sigma)
#분산을 추정할 때는 표본에서 계산량을 통계량을 이용할 수 있는데 
#이 경우에는 불편 분산을 이용
0.6803017080832622

 

 

5-2) 구간추정

- 모집단의 특성을 적절한 구간을 이용하여 추정하는 방식으로 하한값과 상한값으로 추정

- 용어

  • 신뢰계수: 구간 추정의 폭에 대한 신뢰 정보를 확률로 표현한 것. 90%, 95%, 99% 많이 이용
  • 신뢰수준: 계산된 구간이 모수를 포함한 확률을 의미.  90%, 95%, 99% 등으로 표현
  • 신뢰구간: 신뢰 수준 하에서 모수를 포함하는 구간으로 [하한값, 상한값]으로 표시
  • 표본오차: 모집단에서 추출한 표본이 모집단의 특성과 정확히 일치하지 않아서 발생하는 확률의 차이

- 대통령 후보의 지지율 여론 조사에서 후보의 지지율이 95% 신뢰 수준에서 표본 오차 ±3% 범위 내에서 32.4%로 조사되었다고 하면, 29.4% ~ 35.4% 이내에 존재할 확률이 95% 된다는 뜻. 5% 수준에서는 틀릴 수 있다는 뜻. 

 

 

5-3) 가설

- 데이터를 특정한 확률분포를 가진 확률 변수로 모형화하면 모수를 추정할 수 있음

- 확률 분포에 대한 어떤 주장을 가설(hypothesis)라고 하면 H를 이텔릭으로 표현

- 가설을 증명하는 행위를 통계적 가설 검정이라 함. 줄여서 검정

- 확률 분포의 모숫값이 특정한 값을 가진다는 가설을 검정하는 것을 모수 검정이라고 함

 

귀무가설(null hypothesis)

  • 검정 통계 분포를 결정하는 최초의 가설이 귀무 가설
  • 우연에 의해 발생한 것이라는 개념을 구체화하기 위해, 귀무가설을 먼저 세우고 대립 가설을 채택한다.
  • 실험에서 얻은 그룹 간의 차이가 무작위로 얻을 수 있는 합리적인 수준과는 극단적으로 다르다는 증거가 필요해서 그룹들이 보이는 결과는 서로 동일하면 그룹간의 차이는 우연히 발생한 결과임을 기본 가정으로 설정
  • 귀무가설은 기본적으로 같다, 차이가 없다에서 출발
  • 일반적으로 검정의 목적은 대다수 귀무 가설이 틀렸음을 확인하기 위한 작업

대립가설(alternative hypothesis)

  • 귀무 가설과 대립되는 가설
  • 귀무 가설이 그룹A와 B의 평균에는 차이가 없다는 것이라면 대립가설은 그룹 A와 B의 평균에는 차이가 있다는 가설

 

5-4) 검정

- 일원 검정(one-way test, single-tailed test - 단측 검정)과 이원검정(two-way test, 양측 검정)

 

일원검정

  • 한 방향으로만 우연히 일어날 확률을 계산하는 가설 검정
  • 기존에 사용하던 옵션과 비교해서 새 옵션이 어떤지 검정을 하고자 하는 경우 새로운 옵션이 기존의 옵션보다 성능이 좋아야 변경을 할 것이므로 새 옵션이 기존의 옵션보다 성능이 우수하게 평가되는 것이 우연에 의해서 발생한 것이 아니라는 것을 검정해야 하는데, 이러한 경우를 단측검정이라고 한다.
  • (다르다가 아니라 좋다 나쁘다)

이원검정

  • (다르다)  
  • A와 B는 다르다는 것을 검정.
  • R과 파이썬의 scipy는 기본적으로 이원 검정(보수적)을 채택
  • 통계는 대부분 이원검정을 쓰지만, 사실 실무에서는 일원검정을 많이 사용한다. 

 

5-5) 통계적 유의성 

- 통계학자가 자신의 실험 결과가 우연히 일어난 것인지 아니면 우연히 일어날 수 없는 것인지 판단하는 방법

- 결과가 우연히 벌어질 수 있는 변동성의 바깥에 존재한다면 이 경우를 통계적으로 유의하다고 판단

 

- p-value(유의확률): 귀무가설을 구체화한 기회 모델이 주어졌을 때, 관측된 결과와 같이 특이하거나 극단적인 결과를 얻을 확률.

  • p-value가  낮으면 귀무 가설을 기각 - 대립 가설 채택
  • 유의 확률이 0.02 라면 귀무 가설을 기각 했을 때 이 기각 결정이 잘못될 확률이 2%라는 것

- alpha(유의수준): 우연에 의한 결과가 능가해야 하는 비정상적인 가능성의 임계 확률.

  • 유의 확률 < 유의 수준: 귀무 가설을 기각하고 대립 가설을 채택
  • 유의 확률 > 유의 수준: 대립 가설을 기각하고 귀무 가설을 채택
  • 이 값은 분석하는 사람이 결정

- 제1종 오류: 우연에 의한 결과를 실제 효과라고 잘못 결론내리는 것

  • 데이터가 부족하기 때문이다. 
  • 데이터 샘플을 늘리면 문제 해결됨

- 제2종 오류: 실제 효과를 우연에 의한 효과라고 잘못 결론내리는 것

  • 2종 오류를 찾아내는게 어렵다.

- 기각역(Critical Value): 유의 수준에 대해서 계산된 검정 통계량

 

- 동전을 15번 던졌을 때 한쪽 면이 12번 나온 경우 이 동전은 공정한 동전인가?

  • 동전 던지기는 이항분포(베르누이 분포)이고 확률은 0.5
  • 이항 분포의 누적밀도함수(cdf) 이용해서 12번 나올 확률을 찾음
  • 귀무가설: 앞면과 뒷면이 나올 확률은 일치한다. 이 동전은 공정하다.
    대립가설: 앞면과 뒷면이 나올 확률은 일치하지 않는다. 이 동전은 공정하지 않다.
  • 유의 수준을 설정해서 유의 수준보다 작은 확률이었다면 귀무 가설 기각, 유의 수준보다 크면 귀무가설 채택
  • 비교할 떄   단측 검정: (1-누적밀도함수의 결과)    양측 검정: (1-누적밀도함수의 결과) * 2
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)

#축 설정을 위한 데이터
xx = np.arange(N + 1)

#확률 질량 함수(pmf) - 이산 확률 분포에서 각 사건이 발생할 확률을 계산해주는 함수
#그래프를 2행 1열로 표시하고 첫번째 영역에 표시
plt.subplot(211)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.pmf(xx))
plt.ylabel('pmf')
plt.title("확률 질량 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
            arrowprops={"facecolor":"black"})
plt.show()

plt.subplot(212)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.cdf(xx))
plt.ylabel('cdf')
plt.title("누적 확률 밀도 함수 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
            arrowprops={"facecolor":"black"})
plt.show()

  • 이산확률질량함수와 누적밀도함수는 stem으로 시각화

 

# 양측 검정

  • 동전을 던졌을 때 15번 던졌을 때 12번 나온 경우에 대한 검정
  • 대립가설: μ ≠ 0.5
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)

#양측 검정에서 사용할 유의 확률: 11번까지 나올 수 있는 확률을 1에서 빼면 됩니다.
p = 2 * (1 - rv.cdf(12-1))
print(p)
#우연히 12번 이상 나올 확률은 0.035(대략 3.5% 정도)
#유의 수준을 5%로 설정하면 이 경우는 귀무 가설이 기각 - 이 동전은 공정하지 않다.
#유의 수준을 1%로 설정하면 이 경우는 귀무 가설을 기각할 수 없음 - 이 동전은 공정한 동전
0.03515625
  • 우연히 12번 이상 나올 확률은 0.035 (약 3.5%)
  • 유의 수준 5%에서는 귀무가설 기각, 이 동전은 공정하지 않다.
  • 유의 수준 1%에서는 귀무가설 채택, 이 동전은 공정하다.

# 단측 검정

  • 앞면이 12번 이상 나왔을 때 이 동전은 앞면이 더 많이 나오는 동전인가?
  • 특정 면에 대해서 더 많이, 적게 나온 것을 구하기에 단측 검정!
  • 대립 가설: μ > 0.5
  • 이항분포, 1이 나올 확률 0.5, 시행횟수 15번
#단측 검정 - 앞 면이 12번 이상 나왔을 때 이 동전은 앞 면이 더 많이 나오는 동전인가
#어떤 분포 - 이항 분포
#1이 나올 확률 - 0.5
N = 15
mu = 0.5
rv = sp.stats.binom(N, mu)

#앞면이 11번까지 나올 수 있는 확률을 구해서 1에서 빼기
p = (1 - rv.cdf(11))
print(p)

#이제 유의 확률을 계산했으므로 유의 수준을 설정해서 판정
if 0.01 < p:
    print("유의 확률이 더 높으므로 귀무 가설 채택")
else:
    print("유의 확률이 더 높으므로 대립 가설 채택")
0.017578125
유의 확률이 더 높으므로 귀무 가설 채택

 

 

 

6. 이항 검정

6-1) A/B 검정 (A/B Test)

- 두 처리 방법 또는 제품중 어느 쪽이 다른 쪽보다 더 우월한지를 입증하기 위해 실험군을 두 그룹으로 나누어 동시에 진행하는 실험

  • 대조군: 기준이 되는 기존 방법. 아무런 조치도 적용하지 않음.
  • 처리군: 반대되는 실험에 의해 만들어진 집단이나 처리법. 
  • 가설: 새로운 처리법을 적용한 처리군이 대조군보다 더 낫다.

- 디자인이나 마케팅에서 일반적으로 사용

- 웹 전환율을 A/B 테스트로 검정하고자 하는 경우 부트스트래핑 방식을 추가적으로 도입해야 함

  • 실제 웹의 전환율(다음 행위를 할 확률)은 굉장히 낮기 때문에 하나의 실험 결과만으로 판정하기는 어렵다.
  • 다른 독립변인의 영향을 받아서 실험의 결과가 달라질 수 있기 때문에 실험군을 만드는게 굉장히 어렵다.
  • 샘플링을 여러개 놓고 굉장히 여러번 검증해봐야 한다. 

- 활용 분야

  • 종자 발아가 어디에서 더 잘되는지 알아보기 위해 두 가지 토양 처리를 검정
  • 암을 더 효과적으로 억제하는 두 가지 치료법을 검정
  • 두 가지 가격을 검정하여 더 많은 순이익을 산출하는 쪽을 결정
  • 두 개의 인터넷 뉴스 제목을 검정하여 더 많은 클릭을 생성하는 쪽을 결정
  • 두 개의 인터넷 광고를 검정하여 어느 것이 더 높은 전환율을 얻을 지 판단

 

6-2) 이항 검정 API

-scipy.stats.binom_test(x, n=None, p=0.5, alternative='two-sided')

  • x: 검정통계량. 1이 나온 횟수
  • n: 총 시도 횟수
  • p: 귀무 가설의 μ 값
  • alternative: 양측 검정인 경우에는 two-sided, 단측 검정인 경우에는 less 또는 'greater‘

* 이항 검정은 이산이라서 직접 연산을 수행하여 판정하는 것이 쉽기 때문에 누적확률분포함수나 확률질량함수로 직접 판정하는 경우도 많음.

 

# 게임에서 내가 이길 확률이 0.3인데 100번 했을 때 60번 이상 이기는 것이 가능한지 유의 수준 5%로 검정

#누적확률분포함수 이용해서 59번 이길 확률을 빼주면 60번 이상 확률 계산
r = 1-sp.stats.binom(100, 0.3).cdf(30 - 1)

print(p)

if r > 0.05:
    print("p-value가 0.05보다 크므로 정상적인 상황입니다.")
else:
    print("p-value가 0.05보다 작으므로 발생할 가능성이 낮은 상황입니다.")
5.12994979828818e-10
귀무 가설을 기각하고 대립 가설을 채택해서 이런 결과가 나올 확률은 극히 희박

 

# 여자 손님 중 비흡연자가 흡연자보다 많다고 할 수 있는가? (유의 수준 10%)

#데이터 가져오기
tips = sns.load_dataset("tips")
#print(tips.head())

#female이 Female 인 데이터만 추출
female = tips[tips['sex'] == 'Female']
#print(female['sex'].value_counts())

#흡연자의 인원 수 와 비흡연자의 인원 수를 계산
#전체 인원수
fem_cnt = female['sex'].count()
smoke_cnt = female[female['smoker'] == 'Yes'].count()
non_smoke_cnt = female[female['smoker'] == 'No'].count()
#print(fem_cnt)
#print(smoke_cnt)
#print(non_smoke_cnt)

#흡연자의 유의 확률
p = 1- (sp.stats.binom(fem_cnt, 0.5).cdf(smoke_cnt[0] - 1))
print(p)
if p > 0.1:
    print("귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.")
else :
    print("대립 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 있다.")
0.991086223535132
귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.

 

 

 

7. t 검정

- 모집단의 분산을 모를 때 표본으로 추정하여 평균의 차이를 알아보는 검정

- 검정 통계량으로 스튜던트 t 분포를 가진 통계량 사용

- 표본 평군의 분포를 근사화하기 위해 개발

- 최대 2개의 집단까지 비교 가능

 

- T 검정은 다음 조건을 만족한다.

  • 연속형 데이터
  • 랜덤 표본 데이터
  • 모집단의 분포가 정규 분포에 유사
  • 분산에 동질성이 있음

- 종류

  • 1표본 T검정 - 평균 검정
  • 종속표본 T검정 - 한 집단의 두 개의 평균 비교 (데이터 개수가 같아야 함)
  • 독립표본 T검정 - 서로 다른 집단의 평균 비교

 

7-1) 단일 표본 T검정

- 정규 분포의 표본에 대해서 기대값(평균)을 조사하는 검정 방법

- scipy.stats.ttest_1samp(a, popmean, alternative)

  • a: 표본 데이터 배열
  • popmean: 귀무가설의 기댓값

- 귀무가설: 데이터의 평균이 popmean이다.

   p-value: 이 데이터의 평균이 popmean일 확률

- 데이터 개수가 많은 영향을 미친다. 

  • 데이터의 개수가 작으면 제1종 오류 발생 가능성이 높다.

- 양측검정

N = 10
mu_0 = 0
np.random.seed(0)

#정규 분포를 따르고 평균이 0인 샘플 데이터 10개 추출
x = sp.stats.norm(mu_0).rvs(N)

p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478 
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
TtestResult(statistic=2.28943967238967, pvalue=0.04781846490857058, df=9)
N = 100
mu_0 = 0
np.random.seed(0)

#정규 분포를 따르고 평균이 0인 샘플 데이터 100개 추출
x = sp.stats.norm(mu_0).rvs(N)

p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478 
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
TtestResult(statistic=0.5904283402851698, pvalue=0.5562489158694675, df=99)

 

# 평균 검정과 신뢰구간 구하기

#아래와 같은 데이터가 있을 때 펴균이 50이라고 할 수 있는지 유의수준 0.05에서 검정
data = np.array([49, 52, 45, 53, 49, 50, 55, 43, 44, 48])

stat,pv = stats.ttest_1samp(data,50)
print(f"양측 검정으로 p-value는 {pv:.3f}")

if pv>0.05:
  print("귀무 가설을 기각할 수 없다. 따라서 광고는 유의하다. ")
  print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하다.")
else:
  print("귀무 가설을 기각한다. 따라서 광고는 유의하지 않다.")
  print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하지 않다.")
 
#신뢰 구간을 구하기 위해서 t 분포를 생성
t_ = sp.stats.t(len(data) - 1)

#0.975 되는 지점의 좌표를 찾기 - 양쪽 검정이므로
p_05 = t_.ppf(0.975)

data_mean = data.mean()
data_std = data.std(ddof = 1)
#하한 과 상한 구하기
lp = data_mean - p_05 * (data_std / np.sqrt(len(data)))
hp = data_mean + p_05 * (data_std / np.sqrt(len(data)))
print(f'유의 수준 0.05 일 때 신뢰 구간: [{lp}, {hp}]')
유의 수준 0.05 일 때 신뢰 구간: [45.98262777665255, 51.617372223347445]

 

 

- 단측 검정

  • 데이터를 측정한 결과: 74, 73, 75, 73, 75, 74, 73, 73, 74, 72
  • 신뢰구간 95%(유의수준 0.05)에서 홍길동의 몸무게가 73보다 작다고 할 수 있는지 검정
  • alternative 인자를 ‘less’ 혹은 ‘greater’로 전달하면 단측 검정을 할 수 있음
#데이터를 측정한 결과: 
data = np.array([74, 73, 75, 73, 75, 74, 73, 73, 74, 72])
#신뢰구간 95%(유의수준 - 5%)에서 평균이 73보다 작다고 할 수 있는지 검정?
mean = 73
_, p = sp.stats.ttest_1samp(data, mean, alternative='less')
print(p)
if p > 0.05:
    print("귀무가설을 기각할 수 없다. 평균은 73보다 크거나 같다.")
else:
    print("귀무가설을 기각할 수 있다. 평균은 73보다 작다.")
0.040563094422921574
귀무가설을 기각할 수 있다. 평균은 73보다 작다.

 

 

 

7-2) 대응(종속) 표본 t 검정

- 동일한 표본에 대해서 관측된 결과의 기댓값이 같은지 검정

  • 실제로는 기대값이 다르다는 것을 확인하기 위해서 수행

- 예시: 

  • 어떤 학생들이 특강을 수강하기 전과 수강한 이후에 대해서 시험을 치렀을 때 학생들의 점수 변화가 있었는지 
  • 어떤 약을 복용하기 전과 후가 달라졌는지 확인
#귀무 가설: 2집단의 평균이 같다
#대립 가설: 2집단의 평균이 다르다

x1 = np.array([0.7, -1.6, 0.2])
x2 = np.array([1.2, -1.1, 0.2])

_, pvalue = sp.stats.ttest_rel(x1, x2)

if pvalue > 0.05:
    print("귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다")
else:
    print("귀무 가설을 기각할 수 있다. 두 집단의 평균은 다르다")
귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다

 

#당뇨 약 복용 전과 복용 후 검정

  • 동일한 환자에 대한 측정치라고 가정: 대응표본t검정
  • 변화가 있는지 알아보는 검정 - 양측 검정
  • 유의 수준 5%
#데이터
data_a = np.array([440, 90, 120, 220, 230, 320, 450, 180])
data_b = np.array([220, 80, 100, 110, 180, 250, 350, 170])

#양측 검정 수행
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "two-sided")
print(p)
if p > 0.05:
    print("귀무 가설을 기각할 수 없다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 없다.")
else:
    print("귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.")
0.02139085535260364
귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.
#단측 검정 수행
#단측 검정을 수행할 때는 앞의 데이터를 기준으로 판정
#앞의 데이터가 더 커야 하면 greater 더 작아야 하면 less
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "greater")
#단측 검정의 p-value 값이 양측 검정의 p-value/2 입니다.
print(p)
if p > 0.05:
    print("귀무 가설을 기각할 수 없다, 첫번째 데이터가 더 크지 않다.");
else:
    print("귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.")
nan
귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.

 

 

7-3) 독립 표본 t검정

- 두 개의 독립적인 정규 분포에서 나온 두 개의 데이터 셋을 사용해서 두 정규 분포의 기대값(평균)이 동일한지를 검사

- scipy.stats.ttest_ind(a, b, axis=0, equal_var=True, alternative='two-sided'..)

  • equal_var 옵션: 2개 데이터의 분산이 같은지 설정

- 이 경우에도 샘플 데이터의 개수가 너무 적으면 제2종 오류 (실제 효과를 우연에 의한 효과라고 잘못 결론 내리는 것 - 대립 가설을 채택해야 하는데 귀무가설을 채택해야 하는 경우) 발생

np.random.seed(0)

#첫번째 데이터
N_1 = 10
mu_1 = 0
sigma_1 = 1

#두번째 데이터
N_2 = 10
mu_2 = 0.5
sigma_2 = 1

#2개 데이터 집단의 평균은 0 과 0.5 로 다름
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)

#그래프로 출력
ax = sns.distplot(x1, kde=False, fit=sp.stats.norm, label="1번 데이터 집합")
ax = sns.distplot(x2, kde=False, fit=sp.stats.norm, label="2번 데이터 집합")

ax.lines[0].set_linestyle(":")
plt.legend()
plt.show()

print(sp.stats.ttest_ind(x1, x2, equal_var=False))

 

- 샘플의 개수가 적으면 제2종 오류 발생 가능성이 높아짐

 

 

7-4) 등분산 검정

- 독립표본t검정에서는 기대값이 같은지 확인을 하고자 하는 경우 분산의 값이 다른지 같은지를 알아야 한다.

- 등분산 검정에는 sp.stats 패키지  bartlett, fligner, levene 검정 함수 제공

  • 2개 데이터의 집단만 대입하면 p-value 값을 얻어낼 수 있다.
#첫번째 데이터 - 분산이 1
N_1 = 100
mu_1 = 0
sigma_1 = 1

#두번째 데이터 - 분산이 1.5
N_2 = 100
mu_2 = 0
sigma_2 = 1.5

np.random.seed(0)

#평균은 같은데 분산이 다른 데이터를 생성
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)

_, p = sp.stats.bartlett(x1, x2)
#유의 확률이 낮게 나오기 때문에 2개 데이터의 분산이 다르다
print(p)
2.4364708986190797e-05

 

 

 

8. 윌콕슨의 분호 순위 검정

- 대응 표본(종속표본 - 동일한표본) 차이에 정규분포를 적용할 수 없는 경우 사용하는 중앙값의 차이에 대한 검정

- 차이의 절대값을 이용해서 순위 부여

  • 부여된 순위에 평균의 차이를 +와 -부호를 부여해서 곱한 후 얻어진 2개의 값의 차이를 가지고 판정
  • 차이가 있는 경우에는 순위의 편차가 심할 것이고 그렇지 않은 경우는 순위의 편차가 별로 없음

- 차이에 편향이 있을수록 순위에도 편향이 생기고, 검정 통계량은 작은 값이 돼서 중앙값에 차이가 있다는 주장을 할 수 있음

- 알고리즘을 직접 구현해도 되고  scipy.stats.wilcoxon 함수 제공

  • 함수를 이용하게 되면 중간에 표준화를 수행하고 정규분포로 검정을 하기 때문에 직접 구현하는 경우와 값은 다르게 나옴

 

8-1) 알고리즘 직접 구현

#데이터 가져오기 - 전 후의 차이가 별로 없는 경우
training_rel = pd.read_csv('./data/training_rel.csv')
#print(training_rel.head())

#알고리즘을 이해하기 위해서 데이터의 개수를 줄임
toy_df = training_rel[:6].copy()
#print(toy_df)

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후   차  순위
0  59  41 -18   5
1  52  63  11   3
2  55  68  13   4
3  61  59  -2   1
4  59  84  25   6
5  45  37  -8   2
8 13
검정 통계량: 8
#데이터 가져오기 - 전 후의 차이가 있는 경우
toy_df['후'] = toy_df['전'] +  np.arange(1, 7)

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후  차  순위
0  59  60  1   1
1  52  54  2   2
2  55  58  3   3
3  61  65  4   4
4  59  64  5   5
5  45  51  6   6
0 21
검정 통계량: 0
#데이터 가져오기 - 전 후의 차이가 랜덤한 경우
#순위가 낮아진 것도 높아진 것도 있기 때문에 줄어든 데이터의 순위 와 늘어난 데이터의 순위
#합의 차이가 얼마 안남
#순위의 변화 합을 계산하면 전체적으로 늘어나거나 줄어들었는지 아니면
#랜덤한 변화를 가져왔는지 알 수 있습니다.
toy_df['후'] = toy_df['전'] +  [1, -2, -3, 4, 5, -6]

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후  차  순위
0  59  60  1   1
1  52  50 -2   2
2  55  52 -3   3
3  61  65  4   4
4  59  64  5   5
5  45  39 -6   6
11 10
검정 통계량: 11

 

 

8-2) 제공되는 API 활용

_, p = sp.stats.wilcoxon(training_rel['전'], training_rel['후'])
print(p)
0.03623390197753906
  • 변화가 없다가 귀무 가설
  • 이 경우는 유의 수준을 0.05 로 잡으면 귀무가설을 기각할 수 있고 0.01로 잡으면 귀무 가설을 기각할 수 없음

 

 

9. Mann-Whitney Rank Test

- 대응되는 표본이 아닌 독립 표본인 경우 정규 분포를 가정할 수 없는 경우 중앙값의 차이에 대한 검정

- 정규 분포가 아니라면 평균이나 분산을 가정할 수 없어서 평균을 사용하지 못하고 중앙값을 사용

- 윌콕슨의 부호 순위 합 검정은 대응되는 표본이라서 각 열의 차이를 가지고 순위를 구하고 순위의 변화량을 측정하는데 전체 데이터를 가지고 순위를 구한 후 각 열의 순위 합을 가지고 검정

 

9-1) 실습

#열의 각 데이터가 동일한 표본이 아닌 경우
training_rel = pd.read_csv('./data/training_ind.csv')
toy_df = training_rel[:6].copy()
#print(toy_df)
#두 열의 데이터를 합산해서 순위를 구하는 구조
rank = sp.stats.rankdata(np.concatenate([toy_df["A"], toy_df["B"]]))
rank_df = pd.DataFrame({"A":rank[:5], "B":rank[5:10]}).astype(int)
print(rank_df)
    A   B
0   4   3
1   7   6
2   1   9
3  12  11
4   2   5
  • 데이터가 일정한 변화를 가져온다면 A 나 B에 좋은 순위가 모여있을 가능성이 높음
  • 데이터의 편차가 아주 크지 않다면 이 이론은 성립
  • 한쪽에 좋은 순위나 나쁜 순위가 몰려있으면 데이터에 편향이 발생한 것이므로 유의미한
  • 차이가 있다라고 판정하는 방식

 

 

 

10. 분산 분석

- 여러 글룹의 수치 데이터를 비교해서 여러 그룹 간의 통계적으로 유의미한 차이를 검정하는 통계적 절차가 분산 분석 또는 ANOVA

- 3가지 조건이 필요

  • 정규성: 각각의 그룹에서 변인은 정규 분포
  • 분산의 동질성: 모집단의 분산은 각각의 모집단에서 동일
  • 관찰의 독립성: 각각의 모집단에서 크기가 각각인 표본들이 독립적으로 표집

- 용어

  • 쌍별 비교: 여러 그룹 중에서 두 그룹 간의 가설 검정
  • 총괄 검정: 여러 그룹 평균들의 전체 분산에 대한 단일 가설 검정
  • 분산 분해: 구성 요소를 분리하는 것으로 전체 평균, 처리 평균, 잔차 오차로부터 개별 값들에 대한 기여
  • F 통계량: 그룹 평균 간의 차이가 랜덤 모델에서 예상하는 것보다 벗어나는 정도를 측정하는 표준적인 통계량
  • SS(Sum of Squares): 어떤 평균으로부터의 편차들의 제곱합

 

10-1) 분산의 중요성

- 어떤 그룹 사이에 평균이 같다고 해서 2 그룹 사이에 연관성이 있다고 말하기는 어려움

  • 분산이 다르면 분포는 완전히 달라지기 때문
  • 그룹 과 그룹을 비교할 때는 평균 과 분산을 모두 확인해야 함
#중앙점의 좌표 - 평균
centers = [5, 5.3, 4.7]

#표준 편차 - 분산의 제곱근
#0.1 과 2.0으로 변경했을 때 분포가 아예 달라집니다.
#0.1 인 경우 2개의 분포가 상단에 배치되고 1개의 분포가 하단에 배치됩니다.
#2.0 인 경우 3개의 분포가 동일선상에 배치가 됩니다.

#std = 0.1
std = 2.0

colors = 'brg'

data_1 = []
for i in range(3):
    data_1.append(sp.stats.norm(centers[i], std).rvs(100))
    plt.plot(np.arange(len(data_1[i])) + i * len(data_1[0]), data_1[i], '.', color=colors[i])
  • 분산이 클수록 집단의 평균 값의 차이는 무의미
  • 집단 평균 값의 분산이 클수록 집단 내 분산이 작아질수록 평균의 차이가 분명해짐

- 집단 간 분산 과 집단 내 분산 이 두 가지를 이용해서 분석을 하는 것이 분산 분석 - 가장 기본적인 클러스터링(그룹화 - 집단 내의 분산은 작게 집단 과 집단의 분산은 크게)을 할 때 원리

- 분산 분석에서는 일원 분산 분석(종속 변인이 1개이고 독립 변인의 수도 1개) 과 이원 분산 분석(독립 변인의 수가 2개 이상)으로 나눔

 

 

10-2) 일원 분산 분석

- 종속 변인이 1개이고 독립 변인의 집단도 1개인 경우

- 한 가지 변수의 변화(독립 변인)가 결과 변수(종속 변인)에 미치는 영향을 알아보기 위해서 수행

- python에서는 One-Way ANOVA 분석은 scipy.stats 나 statsmodel 라이브러리를 이용

  • statsmodel를 이용하는 것이 더 많은 분석 정보를 얻어낼 수 있음

- Altman 910 데이터

  • 22명의 심장 우회 수술을 받은 환자를 3개의 그룹으로 분류해서 시술한 결과
    • 그룹1 - 50% 아산화 질소 와 50%의 산소 혼합물을 24시간 동안 흡입
    • 그룹2 - 50% 아산화 질소 와 50%의 산소 혼합물을 수술받는 동안만 흡입
    • 그룹3 - 35% ~ 50%의 산소만 24시간 동안 흡입
  • 적혈구의 엽산 수치를 24시간 이후에 측정
  • 적혈구의 엽산 수치가 종속 변인이 되고 3개의 그룹으로 나눈 변인이 독립 변인
  • 독립 변인에 따른 종속 변인의 평균에 차이가 있는 지 검정
    • p-value 가 유의 수준보다 낮으면 평균에 차이가 있는 것이고 그렇지 않으면 평균에 차이가 없다라고 판정

- scipy.stats.f_oneway(그룹별 데이터 설정) 와 statsmodels.formula.api 를 이용(R 의 형태를 갖는 API 

  • 여러 개의 컬럼을 설정할 때 python 은 컬럼 이름의 list를 이용하지만 R은 종속 변인 과 독립 변인을 설정할 때 ~ 로 구분해서 설정
#Altman 910 데이터 가져오기

#텍스트 파일의 내용을 가지고 numpy 의 ndarray 생성
#대부분의 경우 pandas를 이용해서 데이터를 읽어오고 ndarray 로 변환하지만
#텍스트 데이터(txt, tsv, csv )의 경우는 데이터를 가지고 직접 ndarray 생성 가능

import urllib.request
url = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/altman_910.txt'
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=',')
#print(data)

#첫번째 열이 측정치이고 두번째 열이 그룹

#데이터를 그룹 별로 분할
group1 = data[data[:, 1] == 1, 0]
group2 = data[data[:, 1] == 2, 0]
group3 = data[data[:, 1] == 3, 0]
#print(group1)

#시각화를 통해서 데이터의 분포를 확인
#중앙값의 위치나 박스 와 수염의 크기가 거의 비슷하다면 데이터에 차이가 없다라고 판단
plot_data = [group1, group2, group3]
plt.boxplot(plot_data)
plt.show()

 

# sp.stats.f_oneway API

_, p_value = sp.stats.f_oneway(group1, group2, group3)
print("유의 확률:", p_value)

#유의 수준을 5%
if p_value > 0.05:
    print("유의 수준이 유의 확률보다 크기때문에 이 데이터들은 평균 값이 유의미하게 차이가 나지 않음")
else:
    print("유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다.")
    
print(sp.stats.f_oneway(group1, group2, group3))
유의 확률: 0.043589334959178244
유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다.
F_onewayResult(statistic=3.7113359882669763, pvalue=0.043589334959178244)

 

# statsmodels API 활용

from statsmodels.formula.api import ols

#기존 배열을 가지고 DataFrame을 생성
df = pd.DataFrame(data, columns=['value', 'treatment'])

#모델을 생성
model = ols('value ~ C(treatment)', df).fit()
#print(model)

#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
                df        sum_sq      mean_sq         F    PR(>F)
C(treatment)   2.0  15515.766414  7757.883207  3.711336  0.043589
Residual      19.0  39716.097222  2090.320906       NaN       NaN
<class 'pandas.core.frame.DataFrame'>

 

 

10-3) 이원 분산 분석

- 독립 변인의 수가 2개 이상일 때 집단 간 차이가 유의한지를 검증

- Interaction Effect(상호 작용 효과)를 확인하기 위해 사용

  • 한 변인의 변화가 결과에 미치는 영향이 다른 변인의 수준에 따라 달라지는가 하는 것을 확인

- altman_12_6

  • 태아의 머리 둘레 측정 데이터
  • 4명의 관측자가 3명의 태아를 대상으로 측정
inFile = 'altman_12_6.txt'
url_base = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/'
url = url_base + inFile

#첫번째 열이 머리 둘레 크기 - 종속 변인
#두번째 열이 태아를 구별하기 위한 번호 - 독립 변인
#세번째 열이 관측자를 구별하기 위한 번호 - 독립 변인
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=",")
#print(data)

#이 경우는 이원 분산 분석을 수행
df = pd.DataFrame(data, columns=['head_size', 'fetus', 'observer'])
model = ols('head_size ~ C(fetus) + C(observer) + C(fetus):C(observer)', df).fit()
#print(model)

#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
                        df      sum_sq     mean_sq            F        PR(>F)
C(fetus)               2.0  324.008889  162.004444  2113.101449  1.051039e-27
C(observer)            3.0    1.198611    0.399537     5.211353  6.497055e-03
C(fetus):C(observer)   6.0    0.562222    0.093704     1.222222  3.295509e-01
Residual              24.0    1.840000    0.076667          NaN           NaN
<class 'pandas.core.frame.DataFrame'>

 

 

 

10-4) 웹 점착성

- 웹 전환율

  • 현재 페이지에서 다른 페이지로 이동하는 비율
  • 상품 목록 페이지에서 상품 상세 보기 페이지로 이동하는 것 또는 상품 상세 보기 페이지에서 상품 결재 페이지로 이동하는 것 등의 비율
  • 포탈의 광고 또는 SNS 광고를 통해서 들어오는 고객의 비율

- 웹 점착성

  • 방문자가 페이지에서 보낸 시간
  • 여러 명의 유저가 여러 페이지에서 보낸 시간을 가진 데이터를 가지고 분산 분석을 수행
  • 이 데이터가 앞의 데이터 와 다른 점은 페이지를 선택하는 권리가 유저에게 있다는 것
    •  모든 유저가 모든 페이지에 방문하는 것은 아니며 그 확률은 매우 낮음
  • 부트스트래핑 이나 순열 검정(재표본 추출) 같은 방식을 이용
    • 모든 데이터를 한 상자에 모아서 페이지 개수 별로 각 페이지의 개수만큼 추출
    • 각 그룹의 평균을 기록한 후 각 그룹의 평균 사이의 분산을 기록
    • 이 작업을 여러 번 반복한 후 이 분산이 관찰된 변화가 p 값
#웹 페이지에 머무르는 시간을 웹 점착성이라고 합니다.
#4개의 페이지에 머무르는 시간이 기록된 데이터를 가져오기
four_sessions = pd.read_csv('./data/four_sessions.csv')
#print(four_sessions.head())

#그룹 별 Time 데이터의 평균의 분산을 구합니다.
observed_variance = four_sessions.groupby('Page').mean().var()[0]
print("그룹 별 Time 의 평균에 대한 분산:", observed_variance)

#랜덤하게 섞어서 그룹 별로 TIme에 대한 평균의 분산을 구해주는 함수
def perm_test(df):
    df = df.copy()
    df['Time'] = np.random.permutation(df['Time'].values)
    return df.groupby('Page').mean().var()[0]

#print(perm_test(four_sessions))

np.random.seed(1)
#3000번을 수행해서 observed_variance 보다 분산이 높은 비율을 계산
perm_variance = [perm_test(four_sessions) for _ in range(3000)]
#이 비율의 값이 p-value
#유의 수준을 정해서 분산이 의미가 있는지 판정
#유의 수준 보다 p-value 값이 작다면 분산의 차이가 의미가 있습니다.
#점착성에 차이가 있는 것
#2개의 동일한 콘텐츠를 가진 페이지로 이 작업을 수행하게 되면 A/B 테스트가 됩니다.
print("p-value:", np.mean([var > observed_variance for var in perm_variance]))
그룹 별 Time 의 평균에 대한 분산: 55.426666666666655
p-value: 0.08433333333333333

 

 

 

11. 카이 제곱 검정

- 범주 별로 관측빈도와 기대빈도의 차이를 통해서 확률 모형이 데이터를 얼마나 잘 설명하는지 검정하는 통계 방법

- 관측된 데이터를 대상으로 유의 확률을 적용하여 적합도 검정이나 변수 간의 독립성 여부를 검정하거나 동질성 검정을 수행할 수 있다.

  • 적합도 검정: 표본이 기대에 적합한지 검정
  • 독립성 검정: 두 항목이 서로 독립인지 검정
  • 동질성 검정: 두 집단의 데이터 분포가 같은지 검정

 - 웹 테스트 시 종종 단순한 A/B 검정을 넘어서 동시에 여러가지 처리를 한 번에 테스트할 필요가 있을 때 카이제곱검정 사용

  • 리스트 페이지에 3개의 페이지에 대한 링크가 존재할 때 1000명의 유저가 방문하면
  A 페이지        B페이지  C페이지
클릭 14 8 12
클릭하지 않은 경우 986 992 998
  • 전환율이 실제로 굉장히 낮기 때문에 단순 비교는 어렵다. => 재표본 추출(순열검정, 부트스트랩)을 이용해서 클릭률이 우연히 발생할 수 있는 것보다 유의미한 정도로 큰 차이인지 검정
  • 귀무가설: 모두가 동일한 클릭률을 갖는다.
  • 전체 클릭률은 34/3000, 계산해서 1.133%이며 이렇게 되는 경우 아래와 같이 통계 가정
  A 페이지        B페이지  C페이지
클릭 11.3 11.33 11.33
클릭하지 않은 경우 988.67 988.67 988.67
  • 피어슨 잔차 계산: (실제 관측값 - 기대값) / 기대값의 제곱근
  • 카이제곱 통계량 계산: 피어슨 잔차들의 제곱합

r=행, c=열

  • 카이제곱 통계량이 귀무가설로부터 얻을 수 있는 값보다 클까?
    => 재표본 알고리즘을 통해 검정
    • 34개의 1과 2966개의 0이 들어있는 배열 생성
    • 상자의 내용물을 랜덤하게 1000개씩 추출한 후 1의 개수 계산
    • 이렇게 얻은 횟수와 기대한 횟수의 차이를 제곱해서 합산 (=카이제곱통계량계산)
    • 이 과정을 1000번 반복
    • 처음 계산한 카이제곱통계량보다 몇번이나 초과되었는지 확인 (p-value)

- 카이제곱 검정을 수행하는 함수나 메소드를 만들어야 한다면 수식을 기억해야하고, 그렇지 않으면 API 함수를 사용

- scipy.stats.chi2_contingency()

  • 리턴 값은 statistic(카이제곱통계량), pvalue(제일 중요) 등

# 웹 전환율 차이에 대한 카이 제곱 검정

  • 유의 수준 5%로 결정
click_rate = pd.read_csv('./python_statistics-main/data/click_rates.csv')
#피봇
clicks = click_rate.pivot(index='Click', columns='Headline', values='Rate')
print(clicks)
Headline  Headline A  Headline B  Headline C
Click                                       
Click             14           8          12
No-click         986         992         988
row_average = clicks.mean(axis=1)
pd.DataFrame({
    'Headline A': row_average,
    'Headline B': row_average,
    'Headline C': row_average,
})
result = sp.stats.chi2_contingency(clicks)
print(result)
Chi2ContingencyResult(
statistic=1.6659394708658917,
pvalue=0.4347562562343731,
dof=2,
expected_freq=array([[ 11.33333333,  11.33333333,  11.33333333], [988.66666667, 988.66666667, 988.66666667]]))
print(dir(result))
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getnewargs_ex__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_asdict', '_extra_fields', '_fields', 'count', 'dof', 'expected_freq', 'index', 'pvalue', 'statistic']
  • p-value가 있는데, 속성인지 메소드인지 알아보려면
print(help(result.pvalue))
class float64(floating, builtins.float)
...
  • 값이군
if result.pvalue > 0.05:
    print("귀무가실 기각 불가")
else:
    print("귀무가실 기각")
귀무가실 기각 불가
  • 전환율의 차이가 없다.
그래서 UI에서 유의미한 결과 개선을 만드는게 굉장히 어려운 작업이다.

 

 

11-1) 일원 카이 제곱 검정

- 한개의 변인을 대상으로 검정을 수행하는 것으로 적합도 검정이나 선호도 분석에 이용 가능

- scipy.stats.chisquare(f_obs, f_exp=None)

  • f_obs : 데이터 행렬 (거짓에 관련된 데이터)
  • f_exp : 기댓값 행렬

# 예시: 5가지 음료에 대한 선호도 조사: 응답 -> [41,30,51,71,61]

  • 음료 선택에 선호도가 같은지 다른지 확인
  • 유의수준 1%
data = np.array([41,30,51,71,61])
result = sp.stats.chisquare(data)

if result.pvalue > 0.01:
    print("귀무가설 기각 불가")
else:
    print("귀무가설 기각")
귀무가설 기각
  • 선호도에 차이가 있다.
  • 모든 사람이 5가지 음료 중 하나를 선택한 경우에 대한 검정이었고, 음료를 선택하지 않은 사람도 있는 경우에는 두번째 매개변수로 선택하지 않은 사람의 인원수를 대입해야 함

 

11-2) 이원 카이 제곱 검정

- 카이 제곱 독립 검정

  • 2개의 변인에 대해서 서로 독립적인지 여부 판정
  • 귀무가설: 2개의 변인은 독립적이다.
  • p-value를 반대로 판정하면 동질성 검정이 됨

- scipy.stats.chi2_contingency(2차원 배열의 행렬이나 각 배열의 list) 

objs = np.array([np.array([2, 4]), np.array([3, 6])])
result = sp.stats.chi2_contingency(objs)
print(result)
Chi2ContingencyResult(
statistic=0.0,
pvalue=1.0,
dof=1,
expected_freq=array([[2., 4.], [3., 6.]]))
  • 귀무가설 기각 불가

 

3) 피셔의 정확 검정

- 카이제곱분포는 재표본 검정의 좋은 근사치 제공

- 대부분 통계 소프트웨어는 발생할 수 있는 모든 조합을 열거하고 빈도를 집계하고 관찰된 결과가 얼마나 극단적으로 발생할 수 있는지를 나타내는 p값을 구해서 리턴한다. (=피셔의 정확검정)

- 웹 전환율 테스트나 출판을 위해 통계적으로 유의미한 p값을 찾는 논문 연구에서 널리 사용

 

 

12. Z 검정

- 모집단의 평균과 표준편차가 얼마인지 알려졌을 때, 새롭게 조사된 표본의 평균이 모집단의 평균과 같은지 추정

- 표본의 크기는 30보다 커야 하고 모집단에서 균일한 확률로 선택되어야 함

  • 표본의 크기가 작다면 t검정 수행

- 제약조건

  • 종속변수가 양적 변수
  • 모집단의 평균과 표준편차를 알아야 함
  • 모집단의 분포가 정규분포여야 함: 정규성 검정 수행
  • 두집단을 비교할 경우 두집단의 분산이 같아야 함: 등분산 검정 먼저 수행

- 귀무 가설:모집단의 평균 = 표본의 평균

 

 

 

12-1) Z 검정 통계량

(표본평균 - 모평균) / (모표준편차/표본의개수의 제곱근)

- 표본의 평균이 모평균과 모표준편차에 의해 그려지는 정규분포에서 위치 값

 

12-2) 단일 표본 z 검정

 - 분산의 값을 정확히 알고 있는 정규 분포의 표본에 대해서 기댓값을 조사하는 방법

- 이 API는 존재하지 않아서 직접 구현해서 사용

# 평균이 0이고 표준편차가 1인 정규분포에서 10개의 데이터 추출
N = 10
mu_0 = 0
np.random.seed(0)
x = sp.stats.norm(mu_0).rvs(N)
print(x)
[ 1.76405235  0.40015721  0.97873798  2.2408932   1.86755799 -0.97727788
  0.95008842 -0.15135721 -0.10321885  0.4105985 ]
# z 통계량과 유의확률 리턴해주는 함수
ef ztest_1samp(x, sigma2=1, mu=0):
    z = (x.mean() - mu) / np.sqrt(sigma2/len(x))
    return z, 2 * sp.stats.norm().sf(np.abs(z))

ztest_1samp(x)
(2.3338341854824276, 0.019604406021683538)
  • pvalue가 0.019
  • N의 크기를 100 이상으로 늘려보면 p-value가 0.1 이상으로 나옴

 

12-3) 비율 검정

- 두 집단을 대상으로 비율 차이 검정을 수행해서 두 집단의 비율이 같은지  다른지 검정

- statsmodels.stats.proportion 패키지 proportions_ztest 함수 이용

from statsmodels.stats.proportion import proportions_ztest

r= proportions_ztest(count=135, nobs=150, value=110/150)
print(r)

if(r[1] >= 0.05):
    print("유의 확룰이 0.5보다 크므로 효과가 없다.")
else:
    print("유의 확룰이 0.5보다 작으므로 효과가 있다.")
(6.80413817439772, 1.0165593635824276e-11)
유의 확룰이 0.5보다 작으므로 효과가 있다.
  • scipy는 항상 앞에 이름이 있었는데, 지금은 이름이 없다.이건 완전한 튜플이다.
  • 유의확률을 꺼내는 방법은 r[1]
  • 혹은 처음부터 r 대신 staticvalue와 pvalue 값을 나눠서 받는다.

 

 

 

13. 정규성 검정

- 데이터의 분포가 가우시안 정규 분포를 따르는지 확인하는 검정

- 매우 중요한 작업이라서 다양한 API 제공

  • 골모고로프-스미르노프 검정: scipy.stats.ke_2samp
  • 샤피로 윌크 검정: scipy.stats.shapiro
  • 앤더스 달링 검정: scipy.stats.anderson
  • 다고스티노 K-제곱 검정: scipy.stats.mstats.normaltest

- statsmodel 에서도 제공

  • statsmodels.stats.diagnostic.kstest_normal
  • 기타 등
np.random.seed(0)

N = 100
x1 = sp.stats.norm(0, 1).rvs(N)
print(sp.stats.shapiro(x1))

x2 = sp.stats.norm(0, 1).rvs(N)
print(sp.stats.ks_2samp(x1, x2))
ShapiroResult(statistic=0.9926937818527222, pvalue=0.8689165711402893)
KstestResult(statistic=0.1, pvalue=0.7020569828664881, statistic_location=-0.41361898075974735, statistic_sign=-1)

 

 

 

1. 데이터 유형

1) Scala

- 하나의 값: 정수와 실수 그리고 하나의 글자나 단어 (자연어 처리)

 

2) Vector

- 0개 이상의 값

- Vector 분류

  • Vector: 1차원 배열 (Record)
  • Matrix: 벡터가 모여서 만들어진 이차원 배열 - Table
    • 머신러닝의 기본 단위
  • Tensor: Matrix의 배열로 삼차원 배열
    • 딥러닝의 기본 단위
from sklearn.datasets import load_iris

iris = load_iris()

#이차원 배열이라 iris.data는 Matrix
print(iris.data.shape)
#첫번째 행의 붓꽃 데이터 - 배열이므로 Vector
print(iris.data[0, :])
(150, 4)
[5.1 3.5 1.4 0.2]

 

- 하나의 행을 추출했을 때 하나의 행이 입력 데이터로 사용되면 Feature Vector라고 함. (Feature)

# 컬러 이미지 배열이 Tensor
from scipy import misc
img_rgb= misc.face()
print(img_rgb.shape)
(768, 1024, 3)
  • 차원이 3개면 Tensor

# Matrix를 3차원으로 만들기

x = iris.data.copy()
# 3번째 값으로 1을 설정해도 되고 -1을 설정해도 됨
# -1은 남은 것은 모두 다 배정
y = x.reshape((x.shape[0], x.shape[1], -1))
print(y.shape)
(150, 4, 1)

 

#1차원으로 만들기

#1차원으로 만들 때는 flatten이라는 메소드를 이용해도 되고 reshape를 이용해도 가능
print(y.flatten().shape)
(600,)

 

 

 

 

2. Vector의 연산

1) 행과 열 전치

- T 속성이나 transpose 메소드 이용

  • 메소드를 사용할 때는 변경된 순서 직접 설정
  • 2차원까지는 T 사용
  • 3차원 배열 이상에서 transpose 사용

- VT(윗첨자)

 

 

2) 대칭 행렬

- 자신을 전치한 행렬과 동일한 행렬

ar = np.array([[1, 2, 3], [4, 5, 6]])
print(ar)
print(ar.T)

ar = np.array([[], [], [], []])
[[1 2 3]
 [4 5 6]]
[[1 4]
 [2 5]
 [3 6]]
ar = np.array([[1, 5, 6, 7], [5, 2, 8, 9], [6, 8, 3, 10], [7, 9, 10, 4]])
print(ar)
print(ar.T)
[[ 1  5  6  7]
 [ 5  2  8  9]
 [ 6  8  3 10]
 [ 7  9 10  4]]
[[ 1  5  6  7]
 [ 5  2  8  9]
 [ 6  8  3 10]
 [ 7  9 10  4]]
  • 대칭행렬 - 전치를 해도 동일한 모양이 나오는 행렬
  • 정방행렬 - 행과 열의 개수가 같은 행렬
  • 대칭행렬은 정방행렬에서만 가능

 

3) 벡터와 벡터 연산

-   동일한 차원의 벡터끼리는 동일한 위치의 데이터와 연산을 수행
-   차원이 다른 스칼라와 벡터 또는 벡터와 벡터 사이의 연산은 작은 차원의 데이터를 큰 차원의 데이터로 크기를 변경해서 연산을 수행

  • 이를 브로드캐스트 연산이라고 하고 컴퓨터 프로그래밍에서만 존재

-   차원이 같더라도 원소의 개수가 다르면 연산이 수행되지 않음

ar = np.array([1,2,3])
br = np.array([4,5,6])
cr = np.array([7,8,9,10])
print(ar + br)
#벡터의 차원이 1차원으로 동일하지만 원소의 개수가 3개와 4개로 달라서 에러
# print(ar + cr)
[5 7 9]

# ValueError: operands could not be broadcast together with shapes (3,) (4,) 

 

 

4) 선형 가중 결합

- 각 벡터에 스칼라 가중치를 적용해서 혼합하는 방법

l1 = 1
l2 = 2
l3 = -3

v1 = np.array([4.5, 1])
v2 = np.array([-4.5, -1])
v3 = np.array([1.5, 2])

print(l1 * v1 + l2 * v2 + l3 * v3)
[-9. -7.]

 

- 활용

  • 선형회귀 분석에서 회귀 변수와 계수의 관계를 선형 가중 결합으로 표현
  • 주성분분석 같은 차원 축소 과정에서 각 성분의 분산을 최대화하는 가중치와 계수의 관계 표현

- 선형독립성

  • 각 열의 독립적인 정도
  • 하나의 벡터를 다른 벡터들의 선형 가중 결합으로 나타낼 수 있는지 확인
    상관계수는 2개의 벡터 사이의 독립성을 판단

 

5) 벡터의 내적

- 벡터를 기하학적으로 표현했을 때 2개의 벡터를 확장해서 만든 사각형의 너비

- 내적을 구하려면 한쪽 벡터를 전치했을 때 차원과 동일해야 함

- 계산은 dot 함수 @ 연산자 이용

  • 1차원의 경우 전치를 하지 않아도 데이터의 개수만 같으면 전치를 해서 수행
  • 2차원 이상의 경우는 전치를 했을 때의 차원이 같아야 함
#일차원 벡터의 내적
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])

#내적 구하기 - 일차원 벡터의 경우는 데이터의 개수만 같으면 알아서 수행
print(np.dot(x, y))
print(x @ y)

# 이 경우는 이차원 배열이라서 첫번째 데이터의 열의 개수와 두번째 데이터의 행의 개수가 맞아야 수행
x = np.array([[1], [2], [3]])
y = np.array([[4], [5], [6]])
print(np.dot(x.T, y))
print(x.T @ y)
32
32
[32]
[32]

 

- 가중합이나 가중평균에 사용

- 벡터의 내적은 두 벡터 간의 유사도를 계산하는데도 이용

  • 두 벡터가 닮은 정도 : 유사도
  • 내적을 이용해서 구한 유사도 : 코사인 유사도
  • 정규화를 한 후 내적 : 피어슨 상관계수
#이미지 가져오기
from sklearn.datasets import load_digits
import matplotlib.gridspec as gridspec

digits = load_digits()
digits.images[0]
array([[ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.],
       [ 0.,  0., 13., 15., 10., 15.,  5.,  0.],
       [ 0.,  3., 15.,  2.,  0., 11.,  8.,  0.],
       [ 0.,  4., 12.,  0.,  0.,  8.,  8.,  0.],
       [ 0.,  5.,  8.,  0.,  0.,  9.,  8.,  0.],
       [ 0.,  4., 11.,  0.,  1., 12.,  7.,  0.],
       [ 0.,  2., 14.,  5., 10., 12.,  0.,  0.],
       [ 0.,  0.,  6., 13., 10.,  0.,  0.,  0.]])
#0번 이미지 가져오기
d1 = digits.images[0]
d2 = digits.images[10]

#1번 이미지 가져오기
d3 = digits.images[1]
d4 = digits.images[11]

#유사도 계산을 위해 이미지의 shape 조정
v1 = d1.reshape(64, 1)
v2 = d2.reshape(64, 1)
v3 = d3.reshape(64, 1)
v4 = d4.reshape(64, 1)
#이미지 출력해보기
plt.figure(figsize=(9,9))

#이미지를 동일한 크기로 출력하기 위해서 영역 설정
gs = gridspec.GridSpec(1, 4, height_ratios=[1], width_ratios=[9, 9, 9, 9])

#eval: 문자열로 대입하면 문자열에 해당하는 인스턴스를 찾아오는 함수
for i in range(4):
    plt.subplot(gs[i])
    plt.imshow(eval("d" + str(i+1)))
    plt.grid(False)
    plt.title("image{}".format(i+1))

 

# 코사인 유사도 계산

#동일한 이미지
print(v1.T @ v2)
print(v3.T @ v4)

#다른 이미지
print(v1.T @ v3)
print(v2.T @ v4)
[[3064.  ]]
[[3661.  ]]
[[1866.  ]]
[[2479.  ]]

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 머신러닝  (0) 2024.03.07
[Python] 지도학습 연습 _ 자전거 대여 수요 예측  (0) 2024.03.06
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 확률 분포 모형  (1) 2024.02.26
[Python] 기술통계  (0) 2024.02.22

1. 전수조사와 표본조사

- 전수조사

  • 모집단 내에 있는 모든 대상을 조사하는 방법
  • 모집단의 특성을 정확히 반영하지만 시간과 비용이 많이 소모됨

- 표본조사

  • 모집단으로부터 추출된 표본을 대상으로 분석 실시
  • 전수조사의 단점을 보완하지만 모집단의 특성을 반영하는 표본이 제대로 추출되지 못하면 수집된 자료가 무용지물

 

2. 용어

- Sample: 큰 데이터 집합으로부터 얻은 부분집합

- Population: 데이터 집합을 구성하는 전체

- N(n): 모집단의 크기

 

- 랜덤 표본추출: 무작위로 표본을 추출하는 것

  • 층화 랜덤 표본추출: 모집단을 여러개의 층으로 나누고 각 층에서 무작위로 표본 추출
  • 단순 랜덤 표본추출: 층화 없이 단순하게 무작위로 표본 추출

- 표본 편항 (Sample Bias): 모집단을 잘못 대표하는 표본 추출

 

- 표준 오차

  • 통계에 대한 표본 분포의 변동성
  • 표본 값들의 표준편차(s)와 표본 크기(n)를 기반으로 추정
  • 표준오차와 표본 크기 사이의 관계는 n제곱근의 법칙이라고 하는데 표준오차를 2배 줄이려면 표본 크기를 4배로 증가시켜야 함

 

3. 표본 추출

- 전체 데이터(모집단) 중 일부를 표본(샘플)로 추출하는 작업이 데이터 분석에서 필수

- 훈련 데이터(80%), 테스트 데이터(20%)로 분리하여 데이터에 대한 모델링은 훈련 데이터로만 수행하고 모델의 성능은 테스트 데이터로 평가하면 모델의 성능을 가장 적절히 평가

- 데이터의 분포가 일정하지 않다면 가중치를 이용해서 데이터 추출하는 부분도 고려

- random.sample

 

3-1) 머신러닝에서 표본 추출

- train data(모델 생성) 와 test data(모델 테스트)로 나누는 방법

- train data 와 test data 와 validation data(모델 검증)로 나누는 방법

 

3-2) 복원추출과 비복원추출

- 복원 추출: 추출된 데이터를 모집단에 포함시켜서 추출

  • python에서는 random.random, randint, randrange 등의 함수가 있음

- 비복원 추출: 한 번 추출된 데이터는 모집단에 포함시키지 않고 추출

  • python에서는 random.sample 함수가 제공됨
import random

li = ["SES", "소녀시대", "f(x)", "레드벨벳", "에스파"]

#복원 추출
for i in range(5):
    print(li[random.randint(0, len(li)-1)], end='\t')

print()
#비복원 추출
print(random.sample(li, k=5))
f(x) f(x) 레드벨벳 SES f(x)
['f(x)', '레드벨벳', '소녀시대', 'SES', '에스파']

 

 

3-3) 가중치를 적용한 표본추출

- 표본의 비율이 달라져야 하는 경우 표본을 추출할 때 비율을 설정해서 추출하는 것

- numpy.random.choice(a, size=None, replace=True, p=None)

  • a: 배열,   size: 개수,   replace: 복원여부,   p: 확률
  • 추출하고자 하는 배열, 개수, 확률을 제시하면 가중치를 적용해서 표본 추출
  • 현실 세계의 문제에서는 간혹 가중치를 적용한 표본 추출이 어려우면 표본 추출을 해서 분석을 한 후 가중치 적용
ar = ["라투", "오미크론", "다크스펙터", "나이즈", "슬리피"]

print(np.random.choice(ar, 10, [0.05, 0.15, 0.2, 0.3, 0.3]))
['다크스펙터' '라투' '나이즈' '슬리피' '다크스펙터' '라투' '라투' '슬리피' '오미크론' '슬리피']

 

 

3-4) pandas의 표본추출

- Series나 DataFrame에서 sample 함수 제공

Series.sample(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)

  • n: 추출한 샘플의 수 (frac과 중복 사용 불가)
  • frac: 전체 개수의 비율만큼 샘플을 반환하려 할 경우 사용
  • replace: 복원추출 여부, 기본값음 False(중복불가)
  • weights: 샘플 추출 시 샘플마다 뽑힐 확률 조정
  • random_state: 랜덤 샘플 추출 시 시드를 입력받음
  • axis: 샘플을 추출할 방향. 기본=0(행)
ex_df = pd.DataFrame(np.arange(0,12).reshape(4, 3))
print(ex_df)
print()

#2개의 행을 추출
print(ex_df.sample(n=2))
print()

#1개의 열을 랜덤하게 추출
print(ex_df.sample(n=1, axis=1))
print()
   0   1   2
0  0   1   2
1  3   4   5
2  6   7   8
3  9  10  11

   0  1  2
2  6  7  8
0  0  1  2

   0
0  0
1  3
2  6
3  9

 

 

3-5) sklearn을 이용한 샘플 추출

- scikit-learn.model_selection 의 train_test_split()

  • 훈련 데이터 와 테스트 데이터를 분할하기 위한 API
  • shuffle 옵션: 기본값은 True 로 되어 있는데 시계열 데이터와 같은 데이터를 추출할 때처럼 랜덤한 추출이 아닌 순차적 분할을 해야 하는 경우, False 를 설정해서 추출
  • test_size: 테스트 데이터의 비율을 설정
    • 일반적으로 8:2 또는 7:3을 선호하지만 데이터가 아주 많은 경우에는 5:5 설정 가능
    • 이렇게 데이터가 많으면 테스트 데이터를 다시 테스트 데이터 와 검증을 위한 데이터로 분할하기도 함
  • 리턴되는 데이터는 4개의 데이터 그룹에 대한 tuple
    • 피처의 훈련데이터, 피처의 테스트 데이터, 타겟의 훈련 데이터, 타겟의 테스트 데이터
#데이터 생성
#특별한 경우가 아니면 피처는 대문자 X로 나타냅니다.
#타겟은 소문자 y로 나타냅니다.
#numpy 의 1차원 ndarray는 출력을 하면 옆으로 펼쳐지지만 하나의 열로 간주합니다.
X = np.arange(20).reshape(10, 2)
print(X)  
y = np.arange(10)
print(y)
[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]
 [14 15]
 [16 17]
 [18 19]]
[0 1 2 3 4 5 6 7 8 9]

 

#순차적 분할 7:3

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, shuffle=False)
print('X_train shape:', X_train.shape)
print('X_test shape:', X_test.shape)
print('y_train shape:', y_train.shape)
print('y_test shape:', y_test.shape)
print()
print(X_train)
X_train shape: (7, 2)
X_test shape: (3, 2)
y_train shape: (7,)
y_test shape: (3,)

[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]]

 

- 무작위 추출

  • shuffle 옵션 제거하고 동일한 데이터 추출을 위해 random_state에 seed값 고정
#순차적 분할 - 7:3
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(X_train)
print()
print(y_train)
[[ 0  1]
 [14 15]
 [ 4  5]
 [18 19]
 [ 8  9]
 [ 6  7]
 [12 13]]

[0 7 2 9 4 3 6]
l1 = np.array(range(0, 10))
l2 = np.array(range(10, 20))

print(np.vstack((l1, l2)).T)
[[ 0 10]
 [ 1 11]
 [ 2 12]
 [ 3 13]
 [ 4 14]
 [ 5 15]
 [ 6 16]
 [ 7 17]
 [ 8 18]
 [ 9 19]]

 

- 층화 추출

  • 데이터를 일정한 비율로 무작위 추출
  • 머신러닝에서 타겟의 비율의 일정하지 않은 경우 사용
    • 타켓의 비율이 편차가 심한 경우 랜덤하게 추출했을 때 비율이 낮은 데이터가 추출이 안될 수 도 있고 너무 많이 추출될 수 도 있다.
    • 이렇게 머신러닝의 결과가 엉뚱한 결과가 추출될 수 있습니다.
  • 층화 추출을 할 때는 stratify 옵션에 리스트를 대입하면 비율 계산을 해서 추출
X = np.arange(30).reshape(15, 2)
y = np.arange(15)
grep = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

#단순한 방식으로 추출을 하게 되면 한쪽으로 샘플이 쏠리는 현상이 발생하고
#이렇게 되면 샘플 데이터에서는 잘 맞지만 실제 서비스 환경에서는 제대로 맞추지 못하는
#현상이 발생할 수 있습니다.
'''
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                   test_size=0.2,
                                                   shuffle=True,
                                                   random_state = 1004)
print(y_train)
print(y_test)
'''

#stratify 에 대입한 리스트의 비율을 확인해서 샘플링
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                   test_size=0.2,
                                                   shuffle=True,
                                                   stratify=grep,
                                                   random_state = 32)
print(y_train)
print(y_test)
[14 12  7  9 10  4  6  2 11  1  0  5]
[ 8  3 13]

 

 

3-6) 층화 추출 API

- scikit-learn.model_selection 의 StratifiedShuffleSplit()

- n_splits: 데이터를 몇 개의 그룹으로 분할할지를 설정

- 데이터를 리턴하지 않고 데이터의 인덱스를 리턴

- k-folds cross-validation을 수행하기 위해서 만든 API

  • 모델을 만들고 이를 검증을 할 때 전체 데이터를 N 등분해서 N-1 개의 그룹으로 모델을 만들고 1개의 그룹으로 테스트를 수행하는데 N 번 수행
#교차 검증을 위해서 데이터를 n 등분 층화 추출을 해주는 API
#직접 사용하지 않고 머신러닝 모델이 내부적으로 사용
from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=1002)

#인덱스를 리턴하므로 인덱스를 이용해서 데이터를 추출해서 확인
for train_idx, test_idx in split.split(X, grep):
    X_train = X[train_idx]
    X_test = X[test_idx]
    y_train = y[train_idx]
    y_test = y[test_idx]    

print(y_train)
print(y_test)
[ 8  4  7  0 10 11 12  2 14  3  5  6]
[13  9  1]

 

 

3-7) 재표본 추출

- 랜덤한 변동성을 알아보자는 일반적인 목표를 가지고 관찰된 데이터의 값에서 표본을 반복적으로 추출하는 것

- 종류

순열 검정

- 두 개 이상의 표본을 함께 결합해서 관측값을 무작위로 재표본으로 추출하는 과정

- 두 개 이상의 표본을 사용하여 A/B 검정(하나의 UI 와 새로운 UI를 도입했을 때 결과 비교 등에 사용하는 방식)  등에 이용

  1. 여러 그룹의 결과를 하나의 데이터 집합으로 합침
  2. 결합된 데이터를 잘 섞은 다음 A 그룹과 동일한 크기의 표본을 비복원 무작위로 추출
  3. 나머지 데이터에서 B 그룹과 동일한 크기의 샘플을 비복원 무작위로 추출
  4. 그룹이 더 존재하면 동일한 방식으로 추출
  5. 원래의 표본에 대해 구한 통계량 또는 추정값이 무엇이었든지 간에 추출한 재표본에 대해서 다시 계산하고 기록을 한 다음 원래의 표본에 대해서 구한 값과 비교
  6. 이 작업을 여러 번 반복

부트스트래핑

- 모수의 분포를 추정하는 방법으로 현재있는 표본에서 추가적으로 표본을 복원 추출하고 각 표본에 대한 통계량을 다시 계산하는 것

- 개념적으로만 보면 원래 표본을 수천 수백만 번 복제하는 것

  1. 1억개의 모집단에서 200개의 표본을 추출
  2. 200개의 표본 중에서 하나를 추출해서 기록하고 복원
  3. 이 작업을 n번 반복
  4. n번 반복한 데이터를 가지고 통계량을 계산
  5. 이전 과정을 여러 번 반복해서 통계량을 구하고 이 통계량을 이용해서 신뢰구간을 구하는 방식

- 시간이 오래 걸린다.

  • coffee_dataset.csv 파일의 데이터
    21살 미만 여부
    커피를 마시는지 여부
    키에 대한 데이터
diffHeightListOver21 = []
for _ in range(iterationNum):
    bootSample = df_sample.sample(200, replace=True) # 복원 추출
    nonCoffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == False").height.mean() # 21살 이상이며 커피를 마시지 않는 사람 평균 키
    coffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == True").height.mean() # 21살 이상이며 커피를 마시는 사람 평균 키

    diff = nonCoffeeHeightMeanOver21 - coffeeHeightMeanOver21
    diffHeightListOver21.append(diff)

np.percentile(diffHeightListOver21, 0.5), np.percentile(diffHeightListOver21, 99.5)
0.37151963778610253 3.266041922112605
# 1. 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df[df['drinks_coffee'] == False].height.mean() - df[df['drinks_coffee'] == True].height.mean())
# 2. 21살 이상과 21살 미만인 사람들의 평균 키 차이
print(df[df['age'] == '>=21'].height.mean() - df[df['age'] == '<21'].height.mean())
# 3. 21살 미만인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df.query("age == '<21' and drinks_coffee == False").height.mean() - df.query("age == '<21' and drinks_coffee == True").height.mean())
# 4. 21살 이상인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df.query("age != '<21' and drinks_coffee == False").height.mean() - df.query("age != '<21' and drinks_coffee == True").height.mean())
-1.9568024933369799
3.88229124992111
1.6993900935511732
1.9509354889786579

 

[Python] 확률 분포 모형

0ㅑ채
|2024. 2. 26. 19:50

1. 확률적 데이터와 확률 변수

1-1) 확률적 데이터

- 결정론적 데이터: 실험, 측정, 조사 등을 통해 어떤 데이터 값을 반복적으로 얻을 때 누가 언제 얻더라도 항상 동일한 값이 나오는 것

- 확률적 데이터: 정확히 예측할 수 없는 값 (ex. 혈압)

- 대다수 데이터는 확률적 데이터다. 여러 조건이나 상황에 따라 데이터 값이 변할 수 있고 측정 시에 발생하는 오차 때문!

 

1-2) 분포

- 확률적 데이터를 살펴보면 어떤 값은 자주 등장하고 어떤 값은 드물게 등장하는데

  어떤 값이 자주 나오고 어떤 값이 드물게 나오는지를 나타내는 정보를 의미

- 범주형 데이터 - 교차분할표, plot으로 확인

- 연속형 데이터 - binning 후 히스토그램 등으로 확인

 

1-3) 분포 추정을 위한 기술 통계값

- 평균, 중앙값, 최빈값

- 편차, 분산, 표준편차, 평균절대편차, 중위값의 중위절대편차

- 범위(Range), 순서통계량(Order Statistics), 백분위수(Percentile), 사분위수(Interquartile Range)

 

1-4) 대칭 분포

- 좌우가 동일한 모양의 분포

- 분포 모양에 따른 평균, 중앙값, 최빈값의 특성

  • 평균을 기준으로 대칭이면, 평균 = 중앙값
  • 대칭 분포이면서 하나의 최대값만을 가지는 단봉분포이면, 최빈값 = 평균
  • 대칭 분포를 비대칭으로 만드는 데이터가 추가되면,   평균 -> 중앙 값 -> 최빈값   순으로 영향

 

1-5) 표본 비대칭도 - 왜도(Skewness)

- 평균과의 거리를 세제곱해서 구한 특징 값

- 표본 비대칭도가 0이면, 분포가 대칭

- 표본 비대칭도가 음수면, 표본 평균에서 왼쪽 값의 표본이 나올 가능성이 더 높음

 

1-6) 표본 첨도(kurtosis)

- 정규 분포보다 중앙에 값이 몰려있는 정도

- 평균과의 거리를 네제곱 해서 구한 특징 값

- 데이터가 중앙에 몰려있는 정도를 정밀하게 비교하는 데 사용

- 정규 분포보다 첨도가 높으면 양수가 되고 정규 분포포다 첨도가 낮으면 음수로 정의

  • 값이 3이면 정규 분포와 동일한 분포
  • 값이 3보다 작으면 정규 분포보다 꼬리가 얇은(넓게 펼쳐짐) 분포
  • 값이 3보다 크면 정규 분포보다 꼬리가 두꺼운(좁고 뾰족) 분포

 

 

1-7) 표본 모멘트

- k 제곱을 한 모멘트를 k차 표본 모멘트

- 평균은 1차 모멘트, 분산이 2차 모멘트, 비대칭도가 3차 모멘트, 첨도가 4차 모멘트

- scipy.stats 패키지의 skew 함수(왜도) kurtosis 함수(첨도),  moment 함수(데이터와 차수) 설정해서 구함

import numpy as np
import scipy as sp

# 샘플 데이터 생성
x = np.random.normal(size = 1000)
print(x)
[ 7.09400466e-01  2.90488628e-02  1.57024913e+00 -2.54960161e+00
  3.28136877e-01  1.22922309e-01  2.48529952e+00 -8.75967514e-01
 -1.67285266e+00  1.24635639e-01 -5.71475047e-01  5.97966093e-02
  1.05283163e+00  1.39524451e-01 -1.60198487e+00  1.34187712e+00
...
print("표본 평균 :", np.mean(x))
print("1차 모멘트 :", sp.stats.moment(x,1))

print("표본 분산 :", np.var(x))
print("2차 모멘트 :", sp.stats.moment(x,2))

print("표본 왜도 :", sp.stats.skew(x))
print("3차 모멘트 :", sp.stats.moment(x,3))

print("표본 첨도 :", sp.stats.kurtosis(x))
print("4차 모멘트 :", sp.stats.moment(x,4))
표본 평균 : 0.013852167418918475
1차 모멘트 : 0.0

표본 분산 : 0.9187724279137195
2차 모멘트 : 0.9187724279137195

표본 왜도 : 0.031172968025046505
3차 모멘트 : 0.02745301735258748

표본 첨도 : -0.11861329022879596
4차 모멘트 : 2.4323017710014816
  • excess kurtosis : 정규 분포의 첨도는 3인데 해석을 편하게 하기 위해서 3을 빼서 0을 만들기도 함
  • 2개의 값이 다른이유는 kurtosis는 4차 모멘트에서 3을 뺸 값이기 때문

 

 

2. 확률 분포 함수

2-1) 확률 질량 함수

- 어떤 사건의 확률 값을 이용해서 다른 사건의 확률 값 계산 가능

- 유한개의 사건이 존재하는 경우 각 단순 사건에 대한 확률만 정의하는 함수

- 단순 사건은 서로 교집합을 가지지 않는 사건 (사건이 이산적인 데이터)

  • 주사위에서 1, 2, 3, 4, 5, 6은 다른사건과 교집합을 가지지 않으므로 단순 사건, 이러한 단순 사건의 확률 합은 1
  • 확률 질량 함수는 각 사건이 발생할 수 있는 확률을 계산해주는 함수
    • 범주형이면 각 사건의 확률을 명확하게 나타낼 수 있지만 연속형은 구간이 필요하다. 
    • 연속형 데이터에서는 2개의 데이터 사이에 무한대의 데이터가 존재할 수 있기 때문에 하나의 값에 해당하는 확률은 0임
    • 둥그런 원반에 화살을 쏴서 특정 각도에 해당하는 확률을 계산
      원반에서 0 ~ 180 도 사이에 위치할 확률은? 1/2

- 소문자 p로 표시

 

# 확률 질량 함수 출력

#주사위의 각 눈을 나타내는 수 - 사건
x = np.arange(1, 7)

# 조작된 주사위의 확률질량함수
y = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.5])

plt.stem(x, y)

 

 

2-2) 확률 밀도(분포) 함수 (Probability Density Function, pdf)

- 표본 수가 무한대(ex.연속형 데이터)인 경우 확률 질량 함수로 정의할 수 없음

    • 회전하는 원반에 화살을 쏘고 화살이 박힌 위치의 각도를 결정하는 문제에서 정확하게 0도가 될 확률은 ?
    • 이런 경우 구간을 이용해서 확률 정의
      각도가 0도 보다 크거나 같고 30보다 작은 경우의 확률은 정의가 가능 -> 동일한 가능성을 가진 사건이 12개(유한개)로 정의 가능 

- 확률 밀도 함수는 구간에 대한 확률을 정의한 함수. pdf

 

2-3) 누적 밀도(분포) 함수( Cumulative Distribution Function - cdf )

- 구간을 설정하기 위해 2개의 값이 필요한 확률 밀도 함수와 달리 하나의 값만으로 사건을 정의하는 방법이다.
- 시작점의 위치를 음의 무한대로 설정하고 하나의 값을 무조건 종료점으로 판정해서 확률을 정의한 함수

  • 음의 무한대에 대한 누적분포함수 값 = 0
  • 양의 무한대에 대한 누적분포함수 값 = 1
  • 입력이 크면 누적분포함수의 값은 같거나 커짐

- 0에서 시작해서 천천히 증가하면서 1로 다가가는 형태

- 단조 증가 설질에 의해 절대로 내려가지는 않음

 

 

 

3. scipy

- 수치 해석 기능을 제공하는 패키지

- stats 서브 패키지에서 확률 분포 분석을 위한 다양한 기능 제공

- 확률 분포 클래스 

  •  

 

3-1) 사용법

- 정규 분포 객체 생성

  • 모수 2개 (loc - 기대값·평균, scale - 표준편차)를 가지고 생성
  • rv = scipy.stats.norm( loc=1, scale=2 )  #평균이 1이고 표준편차가 2인 정규분포객체 생성

 

3-2) 확률 분포 객체의 함수

- pmf: probability mass function 확률 질량 함수 (이 사건이 발생할 확률)

- pdf: probability density function 확률 밀도 함수 (구간에 대한 확률값)

- cdf: cumulative distribution function 누적 분포 함수 (0에서부터 x까지의 확률)

----------------------어떤 특정한 사건의 확률

- ppf: percent point function 누적 분포 함수의 역함수 

- sf: survival function = 생존 함수 (1-누적분포함수)   (x부터 끝까지의 확률)

- isf: inverse survival function 생존 함수의 역함수

- rvs: random variable sampling 랜덤 표본 생성

----------------------- 확률을 통해 사건을 발견

 

# 확률 밀도 함수

xx = np.linspace(-8, 8, 100)

rv = sp.stats.norm(loc=1, scale=2)

pdf = rv.pdf(xx)
plt.plot(xx, pdf)

 

 

3-5) 정규 분포 객체의 누적 분포 함수

xx = np.linspace(-8, 8, 100)

rv = sp.stats.norm(loc=1, scale=2)

pdf = rv.cdf(xx)
plt.plot(xx, cdf)

 

 

 

 

 

4. 확률 분포 모형

4-1) 베르누이 분포

- 베르누이 시행

  • 결과가 두가지 중 하나로만 나오는 실험이나 시행
  • ex. 동전을 던져서 앞면이나 뒷면이 나오는 경우

- 베르누이 확률 변수

  • 베르누이 시행 결과를 0 또는 1로 바꾼 것
    • -1이나 1로 표기하기도 함
  • 이산 확률 변수다. 두가지 중에 하나의 경우만 나올 수 있기 때문.

- 베르누이 분포는 1이 나올 확률을 의미하는 모수를 가지게 됨

  • 0이 나올 확률은   1 - (1이 나올 확률)  을 하면 나오기 때문에 별도로 정의하지 않는다.

- scipy.stats.bernoulli 클래스로 구현

  • p 인수로 분포 모수 설정
  • 기술 통계값들은 discribe() 함수 이용, 결과는 DescribeResult 타입으로 리턴.
    • 이 안에 데이터 개수, 최대, 최소, 평균, 분산, 왜도, 첨도를 순차적으로 저장
  • 이산 확률 변수를 이용하기 때문에 확률질량함수 pmf 함수를 사용 가능
# 확률변수 0.6으로 설정 
rv = sp.stats.bernoulli(0.6)
rv
#나올 수 있는 경우
xx = [0, 1]

#확률 질량 함수
plt.bar(xx, rv.pmf(xx))
#X축 수정
plt.xticks([0,1], ['x=0', 'x=1'])

 

 

- 시뮬레이션 (샘플링)

  • random_state는 seed 설정
  • seed를 고정시키면 동일한 데이터가 샘플링되고
  • seed를 고정하지 않으면 현재 시간 값을 seed로 설정하기 때문에 무작위로 샘플링
    • 머신러닝에서는 일반적으로 seed 고정
    • 동일한 데이터를 샘플링해서 비교해야 모델 또는 알고리즘 간 비교가 가능하기 때문
#샘플 데이터 생성
x = rv.rvs(1000, random_state = 42)
sns.countplot(x=x)

 

 

- 이론적인 모델과 시뮬레이션 한 결과 비교

  • 정확히는 주장과 비교
  • 여러차례 시뮬레이션을 한 결과가 비교를 해서 주장이 어느정도 타당성을 갖는지 확인
y = np.bincount(x, minlength=2) / float(len(x))
print(y)
[0.387   0.613]

 

#결과 비교

df = pd.DataFrame({'이론':rv.pmf(xx), '시뮬레이션':y})
df.index = [0, 1]
df

 

# 시뮬레이션 결과

result = sp.stats.describe(x)
print(result)
DescribeResult(
nobs=1000, 
minmax=(0, 1), 
mean=0.613, 
variance=0.23746846846846847, 
skewness=-0.46400506298458166, 
kurtosis=-1.7846993015246748)

 

 

 

4-2) 이항 분포

- 베르누이 시행을 N번 반복할 때, 어떤 경우에는 N번 모두 1이 나오고, 어떤 경우에는 1이 한번도 안나올 수 있는데

이때 1이 나온 횟수를 X라고 하면 X는 0부터 N까지를 가지는 확률 변수다. 이를 이항분포를 따르는 확률변수라고 한다.

- 표본 데이터가 1개이면 베르누이 분포, 표본 데이터가 여러 개면 이항 분포

- scipy.stats 서브 패키지의 binom 클래스로 구현

- rv = sp.stats.binom( 전체 시행 횟수, 1이 나올 확률 )

rv = sp.stats.binom(10 ,0.5) #1이 나올 확률은 0.5이고 10번 수행

xx = np.arange(11)
plt.bar(xx, rv.pmf(xx), align = "center")

 

# 시뮬레이션

x = rv.rvs(1000) # 1000번 시뮬레이션
sns.countplot(x=x)
print(x[x > 7])
[8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 8 8 8 8 8 8 8 8 8 8
 8 8 8 8 8 8 9 8 9 8]

 

# 앞 뒷면 확률이 동일한 동전을 100번 던졌을 때 앞면이 60회 이상 나올 확률

  • 실험횟수: n
  • 확률: p
  • 발생횟수: k
#pmf 이용 : 0 ~ 59까지 나올 확률을 모두 더한 후 1에서 확률 빼기

#cdf 이용 : 0번부터 나올 확률의 누적 확률
p = sp.stats.binom.cdf(n=100, p=0.5, k=59)
print(1-p)

#sf 이용 : 생존 함수는 1-cdf의 값을 가지는 함수
p = sp.stats.binom.sf(n=100, p=0.5, k=59)
print(p)
0.02844396682049044
0.028443966820490392

 

# 앞면이 20~60번 나올 확률

#cdf 이용 : 0번부터 나올 확률의 누적 확률
p1 = sp.stats.binom.cdf(n=100, p=0.5, k=19)
p2 = sp.stats.binom.cdf(n=100, p=0.5, k=60)
print(p2 - p1)
0.9823998997560095

 

 

- 퍼센트 포인트 함수 ppf 활용: 모수로 구하고자 하는 확률(p) 대입

  • 원하는 지점의 확률 q
  • 비율을 설정해서 횟수를 구해주는 함수
  • 동전을 던졌을 때 n회 이상 나올 확률이 80% 넘는 지점은?
p = sp.stats.binom.ppf(n=100, p=0.5, q=0.8)
print(p)

p1 = sp.stats.binom.cdf(n=100, p=0.5, k=p)
p2 = sp.stats.binom.cdf(n=100, p=0.5, k=p+1)

print(p1)
print(p2)
54.0
7.888609052210118e-31
7.967495142732219e-29

 

 

- 베르누이 분포와 이항분포 활용 - 스팸 메일 필터

  • 현재까지 모든 메일을 확인해서 스팸으로 가정할 수 있는 필터의 확률을 구함
  • 베르누이 분포를 이용!
  • 각 메일을 단어 단위로 원핫 인코딩 수행
  • 각 단어가 메일에 포함된 개수와 스팸 메일에 포함된 개수의 비율 계산
  • 현재 메일이 온 경우 메일에 포함된 내용들을 원핫 인코딩 해서 기존에 만들어진 단어들의 스팸 비율 확인해서 판정 
감성분석, 욕설 방지 등에 사용
utf8mb4 : 글자 + 이모티콘까지

 

 

4-3) 카테고리 분포

- 이항 분포가 성공인지 실패인지를 구별하는 거라면

  카테고리 분포는 2개 이상의 경우 중 하나가 나오는 경우

  대표적인 경우가 주사위 (k=6)

- 스칼라 값으로 표현하는 경우가 많지만 확률 변수에서는 카테고리를 원핫인코딩해서 표현

 

각 열로부터 각 베르누이 확률 분포의 모수 추정값



순서가 명확한 의미를 가지면 라벨인코딩, 순서가 의미없이 독립적이면 원핫인코딩

원핫인코딩을 하는 이유:
서로 독립적인 데이터들의 거리를 동일하게 맞추기 위해!
그렇지 않으면 편향이 될 가능성이 높다.

 

- scipy에서는 카테고리 분포 클래스 제공 X

  • 다항분포를 위한 multinomial 클래스에서 시행횟수를 1로 설정하면 카테고리 분포가 됨

- 베르누이 분포가 이진 분류 문제에 사용된 것처럼 카테고리 분포는 다중 분류 클래스에 이용

  • 모수는 원래 카테고리 개수와 카테고리별 확률이 되어야 하는데 scipy를 이용할 때는 1과 각 카테고리 확률의 vector가 됨
  • 시뮬레이션에 사용되는 데이터는 원핫인코딩 된 데이터
  • 원핫 인코딩은 pandas의 get_dummies()를 이용하거나 sklearn.preprocessing 패키지의 Encoder 클래스를 이용해서 수행 가능
#카테고리 별 확률
mu = [0.1, 0.1, 0.1, 0.1, 0.1, 0.5]

#카테고리 분포 인스턴스
rv = sp.stats.multinomial(1, mu)

#데이터 생성
xx = np.arange(1, 7)

#원핫 인코딩
xx_ohe = pd.get_dummies(xx)
#확률 질량 함수 출력
plt.bar(xx, rv.pmf(xx_ohe.values))
  • 통계는 scikit-learn을 이용하는 전처리
  • 머신러닝에는 numpy.ndarray가 기본 자료형
# 시뮬레이션
X = rv.rvs(100)
print(X)
[[0 0 0 0 0 1]
 [0 1 0 0 0 0]
 [0 0 0 0 0 1]
 [0 0 0 0 0 1]
 [0 0 0 0 0 1]
 [1 0 0 0 0 0]
#확인해보면 6이 굉장히 많다.

 

 

 

4-4) 다항 분포

- 베르누이 시행을 여러번 한 결과가 이항분포, 카테고리 시행을 여러번 한 결과가 다항분포

- scipy.stats.multinomial(시행횟수, 확률) 이용해서 인스턴스 생성

- 시뮬레이션을 하게 되면 각 카테고리의 횟수가 리턴됨

## 다항분포 - 카테고리 분포를 여러번 수행한 분포
rv = sp.stats.multinomial(100, [1/6, 1/6, 1/6, 1/6, 1/6, 1/6])

#1000번 시뮬레이션
X = rv.rvs(1000)
print(X[:5])
[[23 16 14 11 18 18]
 [24 15 17 18 13 13]
 [21 18 15 16 16 14]
 [13 12 24 12 19 20]
 [17 16 14 11 19 23]]

 

# 시각화

df = pd.DataFrame(X).stack().reset_index()
df.columns =['시도', '클래스', '데이터개수']
df.head(12)
  • 시행 횟수가 많으면 swarmplot 보다는 violinplot이 낫다.
sns.swarmplot(x='클래스', y='데이터개수', data=df)
sns.violinplot(x='클래스', y='데이터개수', data=df)

 

 

4-5) 균일 분포

- 모든 확률 변수에 대해 균일한 분포를 갖는 모형

- scipy.stats.uniform 

  • 버스의 배차 간격이 일정한 경우
  • 시간표를 모르고 버스 정류장에 나갔을 때 평균 대기 시간은?

- Rvs(loc=0, scale =1, size=1, random_state=None)

  • Loc : 기댓값, 평균
  • Scale : 범위라고도 하는데 표준 편차
  • Size : 개수
  • Random_state : seed값
  • seed값은 여기서 설정하지 않고 numpy를 이용해서 미리 설정한 후 사용해도 됩니다.

- 균일 분포는 동일한 크기의 구간에 대한 확률이 거의 비슷하지만, 2개의 균일분포 합은 그렇지 않다.

 

# 마을에 다니는 버스의 주기는 1시간일 때, 버스 시간표를 모르는 사람이 정류장에 갔을 때 대기 시간이 7분 이내일 확률

  • 균일분포
  • 편차(scale): 60
result = sp.stats.uniform.cdf(scale=60, x=7)
print(result)
0.11666666666666667

 

# 대기 시간이 5~20분 이내일 확률

result1 = sp.stats.uniform.cdf(scale=60, x=20)
result2 = sp.stats.uniform.cdf(scale=60, x=5)
print(result1 - result2)
0.25

 

# 50분 이상일 확률

result1 = sp.stats.uniform.cdf(scale=60, x=50)
result2 = sp.stats.uniform.sf(scale=60, x=50)
print(1- result1)
print(result2)
0.16666666666666663
0.16666666666666663
  • 연속형 분포이기 때문에 50을 0으로 잡고 설정!

 

# 시간이 m분 이내일 확률이 73%가 되는 지점은?

result = sp.stats.uniform.ppf(scale=60, q=0.73)
result
43.8

 

 

4-6) 정규분포

- 가우시안 정규분포 (Gaussian normal distribution) 또는 정규 분포

- 자연 현상에서 나타나는 숫자를 확률 모형으로 나타낼 때 가장 많이 사용되는 모형

- 데이터가 평균을 기준으로 좌우 대칭

- 평균을 기준으로 표준 편차 좌우 1배 내에 약 68% 그리고 좌우 2배 안에 약 95% 정도의 데이터가 분포된 경우

- 평균이 0이고 표준편차가 1인 경우를 특별히 표준 정규 분포라고 함

- scipy.stats.norm 

mu = 0
std = 1

#평균이 0이고 표준편차가 1인 정규분포 객체 생성
rv = sp.stats.norm(mu, std)

#시각화 할 구간 설정
xx = np.linspace(-10, 10, 200)

plt.plot(xx, rv.pdf(xx))
plt.ylabel('확률')
plt.title('정규 분포 곡선')
  • -2 ~ 2 사이가 표준편차의 약 4배

# 붓꽃 데이터에서 꽃잎 길이에 대한 히스토그램 - 정규분포와 유사한 모양

from sklearn.datasets import load_iris
setosa_sepal_length = load_iris().data[:50, 2]
array([1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.4,
       1.1, 1.2, 1.5, 1.3, 1.4, 1.7, 1.5, 1.7, 1.5, 1. , 1.7, 1.9, 1.6,
       1.6, 1.5, 1.4, 1.6, 1.6, 1.5, 1.5, 1.4, 1.5, 1.2, 1.3, 1.4, 1.3,
       1.5, 1.3, 1.3, 1.3, 1.6, 1.9, 1.4, 1.6, 1.4, 1.5, 1.4])
sns.histplot(setosa_sepal_length, kde=True)
plt.tight_layout()
  • kde: 분포 곡선을 그려준다.

 

- finance-datareader 패키지

  • 사용이 간편하고 다양한 시계열 데이터 수집해서 DaraFrame 생성
pip install finance-datareader

 

#한국 거래소 상장 종목 전체 가져오기

import FinanceDataReader as fdr

df_krx = fdr.StockListing('KRX')
df_krx.head()

 

#한국 코스피 지수 가져오기

import FinanceDataReader as fdr

#한국 코스피는 KS11, 시작날짜, 종료날짜(안쓰면 오늘날짜)
kospi = fdr.DataReader('KS11', '2011-01-01')
print(kospi.head())
                          Open     High      Low       Close     Volume   Change  UpDown  \
Date                                                                        
2011-01-03  2063.69  2070.09  2054.83  2070.08  354083359   0.009       1   
2011-01-04  2074.56  2085.14  2069.12  2085.14  415906517   0.007       1   
2011-01-05  2083.10  2087.14  2076.92  2082.55  386064920  -0.001       2   
2011-01-06  2094.35  2096.65  2066.10  2077.61  407829146  -0.002       2   
2011-01-07  2073.68  2086.20  2068.66  2086.20  335558949   0.004       1   
     
                     Comp         Amount            MarCap  
Date                                                
2011-01-03  19.08  5941470649203  1153204185044950  
2011-01-04  15.06  7799740341295  1163384106084700  
2011-01-05  -2.59  8609366845145  1162008605708700  
2011-01-06  -4.94  8804441828186  1159300878918060  
2011-01-07   8.59  7675864506306  1164242711087020  

 

# 현대 자동차 주가 지수 가져오기

#현대 자동차 주가 데이터 가져오기 (종복번호, 날짜)
df = fdr.DataReader('005380', '2011')
df.head(10)

 

# 나스닥 지수의 일자별 차이 확인

data = pd.DataFrame()

data['IXIC'] = fdr.DataReader('IXIC', '2011-01-01')['Close']

#결측치 제거
data = data.dropna()

#시각화
data.plot(legend=False)
  • 시간이 지나면서 증가
#일차별 차이값 구하기
daily_returns = data.pct_change().dropna()

#평균과 표준 편차 구하기 
mean = daily_returns.mean().values[0]
std = daily_returns.std().values[0]
print("평균 일간수익률: {:3.2f}%".format(mean * 100))
print("평균 일간변동성: {:3.2f}%".format(std * 100))
평균 일간수익률: 0.06%
평균 일간변동성: 1.29%
sns.histplot(daily_returns, kde=False)
ymin, ymax = plt.ylim()

plt.vlines(x=mean, ymin=0, ymax=ymax, ls="--") #가운데에 수직선
plt.ylim(0, ymax)
plt.title("나스닥 지수의 일간수익률 분포")
plt.xlabel("일간수익률")
kde = False
kde = True

 

 

 

- 로그 정규 분포

  • 데이터에 로그를 한 값 또는 변화율이 정규 분포가 되는 분포
  • 주가의 수익률이 정규 분포라면 주가 자체는 로그 정규 분포(log-normal distribution)
  • 로그 정규 분포를 가지는 데이터는 기본적으로 항상 양수라서 로그 변환을 수행한 다음 사용하는 것이 일반적
    • 머신러닝이나 딥러닝에 사용하는 경우 로그 변환 후 사용하기도 함
np.random.seed(0)
mu = 1
rv = sp.stats.norm(loc=mu)
x1 = rv.rvs(1000)

#정규 분포 데이터를 이용한 로그 정규 분포 데이터 생성
#시작하는 부분에 데이터가 치우침
#타겟 데이터가 한쪽으로 몰려있는 경우 로그 변환 고려
s = 0.5
x2 = np.exp(s * x1)

fig, ax = plt.subplots(1, 2)
sns.histplot(x1, kde=False, ax=ax[0])
ax[0].set_title("정규분포")
sns.histplot(x2, kde=False, ax=ax[1])
ax[1].set_title("로그정규분포")
plt.tight_layout()
plt.show()

 

- Q-Q (Quntile-Quantile)  Plot

  • 정규분포는 연속확률분포 중 가장 널리 사용되는 확률분포 => 정규분포인지 확인하는 것이 중요
  • 분석할 표본 데이터의 분포와 정규 분포의 분포 형태를 비교해서, 표본 데이터가 정규 분포를 따르는지 검사하는 간단한 시각적 도구
  • 정규 분포를 따르는 데이터를 Q-Q plot으로 그리면 대각선 방향의 직선 모양으로 만들어짐
  • scipy.stats.probplot(x, plot=plt)
#평균이 0이고 표준편차가 1인 정규 분포 객체로 데이터 샘플링
rv = sp.stats.norm(0, 1)
x = rv.rvs(20)

#랜덤하게 데이터 추출
# x = np.random.rand(100)

sp.stats.probplot(x, plot=plt)
plt.show()
정규분포
랜덤추출

 

- 중심 극한 정리

  • 모집단이 정규 분포가 아니더라도 표본 크기가 충분하고 데이터가 정규성을 크게 이탈하지 않는다면 여러 표본에서 추출한 평균은 종 모양의 정규 곡선을 따른다.
  • N 개의 임의의 분포로부터 얻은 표본의 평균은  N 이 증가할수록 기댓값이  μ , 분산이  σ2/N 인 정규 분포로 수렴
#여러개의 표본에서 추출한 데이터를 합치면 정규 분포와 유사해짐
xx = np.linspace(-2, 2, 100)

for i, N in enumerate([1, 2, 10]):
    X = np.random.rand(5000, N)

    Xbar = (X.mean(axis=1) - 0.5) * np.sqrt(12 * N)
    sp.stats.probplot(Xbar, plot=plt)
  • enumerate 함수: iterable 객체를 받아서 순회하고 (인덱스, 데이터)로 리턴 - 몇번째 데이터인지 알 수 있음
enumerate([1, 2, 10])
enumerate([1, 2, 5, 10, 20])
  • 점점 가까워진다.

중심극한정리

 

 

4-7) 스튜던트 T분포

- 현실의 데이터는 정규 분포와 거의 유사하지만, 양 끝단의 데이터가 정규 분포에 비해 극단적 현상이 더 자주 발생

- 분포의 모양을 볼 때 양 끝이 정규 분포보다 두껍기 때문에 fat tail 현상이라 한다.

  • 금융 시장에서는 black swan이라 한다.
#S&P 500, 나스닥(Nasdaq), 다우존스(Dow-Jones), 니케이255(Nikkei255) 
symbols=[
    'S&P500',
    'IXIC',
    'DJI',
    'N225', 
    'KS11'
]

#주가 가져오기
data = pd.DataFrame()
for sym in symbols:
    data[sym] = fdr.DataReader(sym, '2011-01-01')["Close"]
data = data.dropna()
data
# 단위 축소시켜주기
(data / data.iloc[0] * 100).plot()

plt.ylabel("날짜")
plt.ylabel("주가 수익률")
plt.show()

 

#t 분포 - 수익률

#로그화
log_returns = np.log(data / data.shift(1)) #shift: 오른쪽으로 한칸 밀기
log_returns.hist(bins=50)
  • 정규분포와 다른 점은 Q-Q 플롯을 통해 확인

#Q-Q 플롯

#두ㅕ
for i, sym in enumerate(symbols):
    ax = plt.subplot(2, 3, i+1)
    sp.stats.probplot(log_returns[sym].dropna(), plot=ax)
plt.tight_layout()

 

 

- 확률 밀도 함수나 누적 분포 함수를 사용하기 위해서는 t 클래스 이용

  • 매개변수는 df, loc(기대값), scale(표준편차)
    • df는 자유도, ddof는 제약조건
    • 자유도는 통계적 추정을 할 때 표본 자료 중 모집단에 대한 정보를 주는 독립적인 자료의 수(추정해야 할 미지수)의 개수를 내가 가진 정보의 수에서 뺀 값
    • 표준편차나 분산을 구할 때 데이터의 개수가 아닌 n-1이 되는 이유는 표준 편차나 분산을 구하기 위해서는 평균이 전제가 되어야 하기 때문. 하지만 실제 모수의 평균은 미지수이므로 평균을 가정해야 한다. 그럼 이 가정은 제약조건이 된다.
    • 실제로는 데이터의 개수가 많아지면 자유도는 의미가 없음

자유도가 커질수록 정규 분포에 가깝다

 

- t 통계량

  • 정규 분포의 표본을 표준편차로 나눠 정규화한 z 통계량은 항상 정규 분포
  • z 통계량을 구하려면 표준편차를 알아야 하지만, 현실적으로 모수의 표준편차를 정확히 알 수 없기 때문에 표본에서 측정한 표본 표준편차를 이용해서 정규화 수행
  • 정규 분포로부터 얻은 N개의 표본에서 계산한 표본 평균을 표본 평균 편차로 정규화한 값이 t 통계량
  • 분포를 알 수 없는 샘플 데이터만 주어진 경우에 확률이나 값을 추정할 때 사용
    • 기온을 측정했는데 특정 기온이 확률적으로 몇%에 해당하는지 알고자 하는 경우
  • scipy.stats.t.분포함수(x=score, loc=mean, scale=std, df=df)
scailing - 값을 줄이는 것
표준화(standardiztion) - 열의 값을 어떤 특정 범위(0~1, -1~1)로 변경하는 것
정규화(normalization) - 행의 값을 어떤 특정 범위로 변경하는 것 -1로 맞추는 직업

원핫인코딩이 정규화
data = np.array([23, 30,18, 33, 28, 33, 34, 38, 29, 31])

#자유도
df = len(data)-1

#평균
d_mean = data.mean()

#표준 편차
d_std = data.std(ddof=1) #모집단의 표준편차가 아니라 샘플 데이터의 표준편차이므로 평균을 가정해야 함 - 1 설정
#섬씨 25도일 때 상위 몇 %에 속하는 온도일까?
score = 25

p = sp.stats.t.cdf(x=score,loc=d_mean,scale=d_std,df=df)
print(f"{score}는 상위 {(1-p)*100:.2f}%로 예측한다.")
p2 = sp.stats.t.sf(x=score,loc=d_mean,scale=d_std,df=df)
print(f"{score}는 상위 {p2*100:.2f}%로 예측한다.")
25는 상위 78.31%로 예측한다.
25는 상위 78.31%로 예측한다.

 

 

4-8) 카이 제곱 분포

- 정규 분포를 따르는 확률 변수 X의 N개 개의 표본 x1,⋯,xN 의 합(또는 평균) 표본 분산으로 정규화하면 스튜던트 t분포를 따른다. N개의 표본을 제곱해서 더하면 양수만을 갖는 분포 생성 - 카이제곱분포

- scipy.stats.chi2 

- 제곱 합을 구하는 표본의 수가 2보다 커지면 0 근처의 값이 가장 많이 발생할 것 같지만, 실제로는 0보다 큰 어떤 수가 흔하게 발생

 

4-9) F 분포

- 카이 제곱 분포를 따르는 독립적인 두개의 확률변수의 확률 변수 표본을 각각 x1, x2라고 할 때 이를 각각 자유도  N1, N2로 나눈 뒤 비율을 구하면 F분포

- t 분포의 표본 값을 제곱한 값이 F 분포

  • N1과 N2의 값이 같을 경우:  1 근처 값이 가장 많이 발생할 것 같지만 실제는 1이 아닌 다른 수가 조금 더 흔하게 발생
  • N1과 N2의 값이 커지면:  1 근처의 값이 많이 발생

- scipy.stats.f

 

 

------------------------------------------------------------------------------------------------여기까지 정규분포 통계량 분포

  • 스튜던트 t 분포: 추정된 가중치에 대한 확률분포
  • 카이제곱 분포: 오차 제곱 합에 대한 확률분포
  • F분포: 비교 대상이 되는 선형 모형의 오차 제곱 합에 대한 비율의 확률 분포

 

 

4-10) 푸아송 분포

- 단위 시간 안에 어떤 사건이 몇번 일어날 것인지를 표현하는 이산확률분포

- 푸아송이 민사사건과 형사사건 재판에서 확률에 관한 연구 및 일반적인 확률계산 법칙에 대한 서문에서 최초로 사용

- scipy.stats.poisson.rvs()

  • μ: 모양 매개 변수로 사용
  • 방정식의 λ(람다): 단위 시간당 발생하는 사건의 개수 설정
  • loc: 분포 이동
  • zmrl: 분포에서 임의의 변량 수 결정
  • random_state 인수 포함: 재현성 유지

- 예시

  • 어떤 식당에 주말 오후 동안 시간당 평균 20명의 손님이 방문한다고 할 때, 다음주 주말 오후에 30분 동안 5명의 손님이 방문할 확률은?
    • 시간당 손님은 20명이므로 30분당 기대값은 10명

 

# 단위 시간당 사건이 10개가 발생하는 경우

data_poisson = sp.stats.poisson.rvs(mu=10, size=1000)

sns.histplot(data_poisson, bins=30, kde=True)

 

# 2022년 기준으로 한 시간 평균 신생아 수는 682 명인 경우에 한 시간에 650명 이하의 신생아를 낳을 확률은?

# p = sp.stats.poisson.cdf(mu=682, k=650)

x=range(500,800)
y = sp.stats.poisson.pmf(mu=682,k=x)
plt.plot(x,y,alpha=0.7,label='pmf')

x2 = range(500,630)
y2 = sp.stats.poisson.pmf(mu=682,k=x2)
plt.fill_between(x2,y2,color='r',label='x<630') #630명 미만일 때 확률(면적)

#630명 미만
x3 = range(630,651)
y3 = sp.stats.poisson.pmf(mu=682,k=x3)
plt.fill_between(x3,y3,color='c',label='630<x<=651') #630명 이상 650명 이하일 때 확률(면적)
plt.xticks([500,630,650,682,800])
plt.title("")
plt.show()
# 0.11328673711427531

 

#시간당 500M 정도의 트래픽을 소모하는데 99% 정도를 처리하고자 할 때 필요한 트래픽은?

#help(sp.stats.poisson.ppf)
p = sp.stats.poisson.ppf(mu=500, q=0.99)
print(p)
630.0

 

 

4-11) 지수분포

- 푸아송 분포: 사건이 독립적일 때 일정 시간 동안 발생하는 사건의 횟수

- 지수 분포: 다음 사건이 일어날 때까지 대기 시간

- scipy.stats.expon

  • scale: 시간 설정
  • loc: 사건의 발생 횟수
  • 사건의 대기 시간이 일정한 형태로 줄어들거나 늘어나는 경우 weibull distribution 이용

# 스마트폰의 배터리 수명은 평균 24시간인 경우, 20시간 이내에 소진할 확률은?

#  sp.stats.expon.pdf(scale=24,x=x)

x = range(0,100)
y = sp.stats.expon.pdf(scale=24,x=x)
for i in x:
  print(f'{i:02d}시간 {y[i]:.2f}', end=' ')
  if i%10==9:
    print()
    
# 시각화
plt.plot(x,y)
plt.xlabel("time")
plt.ylabel("probability")
# 0.5654017914929218

 

 

 

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 벡터 연산에서 기억할 부분  (0) 2024.02.27
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 기술통계  (0) 2024.02.22
[Python] 이미지 데이터 다루기  (0) 2024.02.21
[Python] 확률  (0) 2024.02.21